ResearchTree: add basic functionality and some nodes.

This commit is contained in:
Guillaume Jacquemin 2021-07-21 11:30:41 +02:00
parent 9999a7dcb0
commit 0e2546391b
9 changed files with 687 additions and 0 deletions

View file

@ -48,6 +48,9 @@ add_executable(MassBuilderSaveTool WIN32
Mass/Mass.cpp
Maps/LastMissionId.h
Maps/StoryProgress.h
ResearchTree/NodeIDs.h
ResearchTree/ResearchTree.h
ResearchTree/ResearchTree.cpp
FontAwesome/IconsFontAwesome5.h
FontAwesome/IconsFontAwesome5Brands.h
resource.rc

View file

@ -109,6 +109,8 @@ void Profile::refreshValues() {
getMuscularConstruction();
getMineralExoskeletology();
getCarbonizedSkin();
getEngineUnlocks();
}
auto Profile::companyName() const -> std::string const& {
@ -1001,3 +1003,54 @@ auto Profile::setCarbonizedSkin(Int amount) -> bool {
return false;
}
}
auto Profile::engineInventory() -> Containers::ArrayView<Int> {
return _engineInventory;
}
auto Profile::gearInventory() -> Containers::ArrayView<Int> {
return _gearInventory;
}
void Profile::getEngineUnlocks() {
auto mmap = Utility::Directory::map(Utility::Directory::join(_profileDirectory, _filename));
auto iter = std::search(mmap.begin(), mmap.end(), &engine_inventory_locator[0], &engine_inventory_locator[33]);
if(iter != mmap.end()) {
Int* int_iter = reinterpret_cast<Int*>(iter + 0x3B);
Int size = *(int_iter++);
if(size > 0) {
_engineInventory = Containers::Array<Int>{DefaultInit, std::size_t(size)};
std::copy_n(int_iter, size, _engineInventory.data());
Utility::Debug{} << "_engineInventory:" << _engineInventory;
iter = std::search(mmap.begin(), mmap.end(), &gear_inventory_locator[0], &gear_inventory_locator[31]);
if(iter == mmap.end()) {
return;
}
int_iter = reinterpret_cast<Int*>(iter + 0x39);
size = *(int_iter++);
if(size <= 0) {
return;
}
_gearInventory = Containers::Array<Int>{DefaultInit, std::size_t(size)};
std::copy_n(int_iter, size, _gearInventory.data());
Utility::Debug{} << "_gearInventory:" << _gearInventory;
}
else {
_lastError = "An array can't have a null or negative size.";
Utility::Fatal{EXIT_FAILURE} << _lastError.c_str();
}
}
else {
_lastError = "The profile save seems to be corrupted or the game didn't release the handle on the file.";
}
}

View file

@ -18,6 +18,8 @@
#include <string>
#include <Corrade/Containers/Array.h>
#include <Magnum/Magnum.h>
using namespace Magnum;
@ -141,6 +143,10 @@ class Profile {
auto getCarbonizedSkin() -> Int;
auto setCarbonizedSkin(Int amount) -> bool;
auto engineInventory() -> Containers::ArrayView<Int>;
auto gearInventory() -> Containers::ArrayView<Int>;
void getEngineUnlocks();
private:
std::string _profileDirectory;
std::string _filename;
@ -183,4 +189,13 @@ class Profile {
Int _muscularConstruction;
Int _mineralExoskeletology;
Int _carbonizedSkin;
Containers::Array<Int> _engineInventory;
Containers::Array<Int> _gearInventory;
Containers::Array<Int> _osInventory;
Containers::Array<Int> _moduleInventory;
Containers::Array<Int> _archInventory;
Containers::Array<Int> _techInventory;
};

View file

@ -0,0 +1,94 @@
#pragma once
// MassBuilderSaveTool
// Copyright (C) 2021 Guillaume Jacquemin
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
enum EngineIDs: Int {
// Tier 1
VerseEngine = 100000,
LoadedEngine = 100020,
MetalPlatings1 = 200000,
HeatTurbines1 = 200040,
Microcontroller1 = 200060,
CombustionController1 = 200020,
// Tier 2
ModAlloyEngine = 110000,
ChargedEngine = 110040,
ReinforcedLoadedEngine = 110020,
HeavyImpactsEnabler = 110100,
MetalPlatings2 = 210000,
HeatTurbines2 = 210040,
Microcontroller2 = 210060,
WeaponsCargo1 = 210061,
CombustionController2 = 210020,
PoweredRewiring1 = 210021,
// Tier 3
ModSteelEngine,
SuperchargedEngine,
NecriumAlloyEngine,
MetalPlatings3,
HeatTurbines3,
Microcontroller3,
WeaponsCargo2,
CombustionController3,
PoweredRewiring2,
// Tier 4
ModLunariteEngine,
ChargedLunariteEngine,
LunariteEngine,
InfusedEngine,
MetalPlatings4,
HeatTurbines4,
Microcontroller4,
WeaponsCargo3,
CombustionController4,
PoweredRewiring3,
ArmouredCargo1,
ArmouredFuelTank1,
ExtraCapacity1,
HighmetalEngine,
PowerRedirector1,
// Tier 5
AsteriteCarbonEngine,
ChargedAsteriteEngine,
AsteriteSteelEngine,
MeldedEngine,
MetalPlatings5,
HeatTurbines5,
Microcontroller5,
WeaponsCargo4,
CombustionController5,
PoweredRewiring4,
ArmouredCargo2,
ArmouredFuelTank2,
ExtraCapacity2,
CastHighmetalEngine,
PowerRedirector2,
};
enum OSIDs: Int {
// Tier 1
NeuralOS = 300010,
};
enum ArchIDs: Int {
// Tier 1
StandardFrame = 500099,
};

View file

@ -0,0 +1,277 @@
// MassBuilderSaveTool
// Copyright (C) 2021 Guillaume Jacquemin
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include "ResearchTree.h"
#include "NodeIDs.h"
//region Node
Node::Node(Node::Type type, const char* name, UnsignedByte tier, UnsignedByte slots,
const char* description, const char* active_effect, const char* passive_effect):
_type{type}, _state{State::Unavailable}, _name{name}, _tier{tier}, _slots{slots},
_description{description}, _activeEffect{active_effect}, _passiveEffect{passive_effect}
{
//ctor
}
auto Node::type() const -> Node::Type {
return _type;
}
auto Node::state() const -> Node::State {
return _state;
}
void Node::setState(Node::State state) {
if(_state == state) {
return;
}
_state = state;
if(_state == State::Available) {
for(Node* n : _children) {
n->setState(State::Unavailable, NotClicked);
}
}
else if(_state == State::Unlocked) {
for(Node* n : _children) {
n->setState(State::Available, NotClicked);
}
}
}
auto Node::name() const -> const char* {
return _name;
}
auto Node::tier() const -> UnsignedByte {
return _tier;
}
auto Node::slots() const -> UnsignedByte {
return _slots;
}
auto Node::description() const -> const char* {
return _description;
}
auto Node::activeEffect() const -> const char* {
return _activeEffect;
}
auto Node::passiveEffect() const -> const char* {
return _passiveEffect;
}
auto Node::children() -> Containers::ArrayView<Node*> {
return _children;
}
void Node::addChild(Node& child) {
arrayAppend(_children, &child);
child.addParent(*this);
}
void Node::addParent(Node& parent) {
arrayAppend(_parents, &parent);
}
void Node::setState(State state, NotClickedT) {
if(_state == state) {
return;
}
if(state == State::Unavailable) {
for(Node* node : _parents) {
if(node->state() == State::Unlocked) {
return;
}
}
}
else if(state == State::Available && _state == State::Unlocked) {
return;
}
_state = state;
if(_state != State::Unlocked) {
for(Node* node : _children) {
node->setState(State::Unavailable, NotClicked);
}
}
else {
for(Node* node : _children) {
node->setState(State::Available, NotClicked);
}
}
}
//endregion
void ResearchTree::generateEngineTree() {
if(!_engineNodes.empty()) {
return;
}
_engineNodes.emplace(VerseEngine, Node{Node::Type::Engine, "Verse Engine", 1, 3,
"A basic low-speed engine with great durability.",
"Durability +346, Power +60, Armour +22, Acceleration +75, Magazine load +35, Energy capacity +35",
"Durability +3%"});
_engineNodes.emplace(LoadedEngine, Node{Node::Type::Engine, "Loaded Engine", 1, 3,
"An alternate heavier model of engine used to operate a M.A.S.S.",
"Durability +288, Power +84, Armour +14, Acceleration +75, Magazine load +35, Energy capacity +35",
"Power +3%"});
_engineNodes.emplace(MetalPlatings1, Node{Node::Type::Gear, "Metal Platings 1", 1, 0,
"Level 1 metal plating that adds durability and armour to your engine.",
"Durability +60, Armour +5, Acceleration -15", ""});
_engineNodes.emplace(HeatTurbines1, Node{Node::Type::Gear, "Heat Turbines 1", 1, 0,
"Modified heat turbines to increase speed for a M.A.S.S.",
"Acceleration +75, Fuel capacity +5", ""});
_engineNodes.emplace(Microcontroller1, Node{Node::Type::Gear, "Microcontroller 1", 1, 0,
"A microchip that enhances various aspects of a M.A.S.S.",
"Durability +36, Power +1, Armour +11, Magazine load +5, Energy capacity +5, Fuel capacity +3", ""});
_engineNodes.emplace(CombustionController1, Node{Node::Type::Gear, "Combustion Controller 1", 1, 0,
"Controlled combustion allows increased power generation through specific ignition.",
"Power +2, Magazine load +10, Energy capacity +10, Acceleration -25", ""});
_engineNodes.emplace(ModAlloyEngine, Node{Node::Type::Engine, "Mod. Alloy Engine", 2, 3,
"Built with a modified alloy and able to sustain greater attacks from any enemy.",
"Durability +1152, Power +75, Armour +194, Acceleration +75, Magazine load +40, Energy capacity +40",
"Durability +3%"});
_engineNodes.emplace(ChargedEngine, Node{Node::Type::Engine, "Charged Engine", 2, 3,
"Remove most armours to attain more speed and power, and fuel.",
"Durability +960, Power +75, Acceleration +300, Magazine load +40, Energy capacity +40, Fuel capacity +52",
"Acceleration +5"});
_engineNodes.emplace(ReinforcedLoadedEngine, Node{Node::Type::Engine, "Reinforced Loaded Engine", 2, 3,
"An upgraded build of the basic model, with reinforced Verse Steel for more strength.",
"Durability +960, Power +105, Armour +130, Acceleration +75, Magazine load +40, Energy capacity +40",
"Power +3%"});
_engineNodes.emplace(HeavyImpactsEnabler, Node{Node::Type::Engine, "Heavy Impacts Enabler", 2, 0,
"Enable the usage of Heavy Combos in close combat attacks.", "", ""});
_engineNodes.emplace(MetalPlatings2, Node{Node::Type::Gear, "Metal Platings 2", 2, 0,
"Level 2 Metal plating that adds durability and armour to your engine.",
"Durability +180, Armour +41, Acceleration -15", ""});
_engineNodes.emplace(HeatTurbines2, Node{Node::Type::Gear, "Heat Turbines 2", 2, 0,
"Level 2 Modified heat turbines to increase speed for a M.A.S.S.",
"Acceleration +75, Fuel capacity +16", ""});
_engineNodes.emplace(Microcontroller2, Node{Node::Type::Gear, "Microcontroller 2", 2, 0,
"Level 2 Microchip that enhances various aspects of a M.A.S.S.",
"Durability +108, Power +3, Armour +17, Magazine load +6, Energy capacity +6, Fuel capacity +8", ""});
_engineNodes.emplace(WeaponsCargo1, Node{Node::Type::Gear, "Weapons Cargo 1", 2, 0,
"Added another cargo hold for ammo and energy recharger",
"Magazine load +24, Energy capacity +24, Acceleration -40", ""});
_engineNodes.emplace(CombustionController2, Node{Node::Type::Gear, "Combustion Controller 2", 2, 0,
"Level 2 Controlled combustion allows increased power generation through specific ignition.",
"Power +5, Magazine load +12, Energy capacity +12, Acceleration -25", ""});
_engineNodes.emplace(PoweredRewiring1, Node{Node::Type::Gear, "Powered Rewiring 1", 2, 0,
"Rewiring that efficiently improves power and engine durability.",
"Durability +180, Power +5", ""});
_engineNodes.at(VerseEngine).addChild(_engineNodes.at(MetalPlatings1));
_engineNodes.at(VerseEngine).addChild(_engineNodes.at(HeatTurbines1));
_engineNodes.at(VerseEngine).addChild(_engineNodes.at(LoadedEngine));
_engineNodes.at(LoadedEngine).addChild(_engineNodes.at(Microcontroller1));
_engineNodes.at(LoadedEngine).addChild(_engineNodes.at(CombustionController1));
_engineNodes.at(MetalPlatings1).addChild(_engineNodes.at(ModAlloyEngine));
_engineNodes.at(HeatTurbines1).addChild(_engineNodes.at(ChargedEngine));
_engineNodes.at(Microcontroller1).addChild(_engineNodes.at(ChargedEngine));
_engineNodes.at(CombustionController1).addChild(_engineNodes.at(ReinforcedLoadedEngine));
_engineNodes.at(ModAlloyEngine).addChild(_engineNodes.at(MetalPlatings2));
_engineNodes.at(ModAlloyEngine).addChild(_engineNodes.at(HeatTurbines2));
_engineNodes.at(ChargedEngine).addChild(_engineNodes.at(HeatTurbines2));
_engineNodes.at(ChargedEngine).addChild(_engineNodes.at(Microcontroller2));
_engineNodes.at(ChargedEngine).addChild(_engineNodes.at(WeaponsCargo1));
_engineNodes.at(ReinforcedLoadedEngine).addChild(_engineNodes.at(CombustionController2));
_engineNodes.at(ReinforcedLoadedEngine).addChild(_engineNodes.at(PoweredRewiring1));
_engineNodes.at(ReinforcedLoadedEngine).addChild(_engineNodes.at(HeavyImpactsEnabler));
_engineNodes.at(VerseEngine).setState(Node::State::Unlocked);
}
void ResearchTree::generateOSTree() {
if(!_osNodes.empty()) {
return;
}
_osNodes.emplace(NeuralOS, Node{Node::Type::OS, "Neural OS", 1, 3,
"Synchronise the links between your nervous system and the M.A.S.S. perfectly.",
"Accuracy +24, Shield +624, Fuel burn rate +200, Magazine reload +24, Energy recharge +66, Shield recover +1620",
"Shield +3%"});
_osNodes.at(NeuralOS).setState(Node::State::Unlocked);
}
void ResearchTree::generateArchTree() {
if(!_archNodes.empty()) {
return;
}
_archNodes.emplace(StandardFrame, Node{Node::Type::Architect, "Standard Frame", 1, 2,
"The standard frame architecture for mass-produced humanoid robots. It uses the most common technologies and materials.",
"Durability +300, Shield +200, Physical damage +600, Piercing damage +300, Plasma damage +300",
""});
_archNodes.at(StandardFrame).setState(Node::State::Unlocked);
}
void ResearchTree::readEngineUnlocks(Containers::ArrayView<Int> engines, Containers::ArrayView<Int> gears) {
if(engines == nullptr || engines.size() == 0) {
Utility::Error{} << "Engines can't be empty";
}
for(Int& engine : engines) {
if(_engineNodes.find(engine) != _engineNodes.end()) {
_engineNodes.at(engine).setState(Node::State::Unlocked);
}
}
if(gears == nullptr || engines.size() == 0) {
return;
}
for(Int& gear : gears) {
if(_engineNodes.find(gear) != _engineNodes.end()) {
_engineNodes.at(gear).setState(Node::State::Unlocked);
}
}
}
auto ResearchTree::getEngineRootNode() -> Node& {
return _engineNodes.at(VerseEngine);
}
auto ResearchTree::getOSRootNode() -> Node& {
return _osNodes.at(NeuralOS);
}
auto ResearchTree::getArchRootNode() -> Node& {
return _archNodes.at(StandardFrame);
}

View file

@ -0,0 +1,112 @@
#pragma once
// MassBuilderSaveTool
// Copyright (C) 2021 Guillaume Jacquemin
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <unordered_map>
#include <Corrade/Containers/ArrayView.h>
#include <Corrade/Containers/GrowableArray.h>
#include <Magnum/Magnum.h>
using namespace Corrade;
using namespace Magnum;
class Node {
public:
enum class Type {
Engine,
Gear,
OS,
Module,
Architect,
Tech
};
enum class State {
Unavailable,
Available,
Unlocked
};
Node(Type type, const char* name, UnsignedByte tier, UnsignedByte slots,
const char* description, const char* active_effect, const char* passive_effect);
Node(const Node& other) = delete;
Node& operator=(const Node& other) = delete;
Node(Node&& other) = default;
Node& operator=(Node&& other) = default;
auto type() const -> Type;
auto state() const -> State;
void setState(State state);
auto name() const -> const char*;
auto tier() const -> UnsignedByte;
auto slots() const -> UnsignedByte;
auto description() const -> const char*;
auto activeEffect() const -> const char*;
auto passiveEffect() const -> const char*;
auto children() -> Containers::ArrayView<Node*>;
void addChild(Node& child);
private:
void addParent(Node& parent);
struct NotClickedT {} NotClicked;
void setState(State state, NotClickedT);
Type _type;
State _state;
const char* _name;
UnsignedByte _tier;
UnsignedByte _slots;
const char* _description;
const char* _activeEffect;
const char* _passiveEffect;
Containers::Array<Node*> _parents;
Containers::Array<Node*> _children;
};
class ResearchTree {
public:
void generateEngineTree();
void generateOSTree();
void generateArchTree();
void readEngineUnlocks(Containers::ArrayView<Int> engines, Containers::ArrayView<Int> gears = nullptr);
auto getEngineRootNode() -> Node&;
auto getOSRootNode() -> Node&;
auto getArchRootNode() -> Node&;
private:
using Tree = std::unordered_map<Int, Node>;
Tree _engineNodes;
Tree _osNodes;
Tree _archNodes;
};

View file

@ -126,6 +126,9 @@ void SaveTool::handleFileAction(efsw::WatchID watch_id,
case efsw::Actions::Modified:
if(filename == _currentProfile->filename()) {
_currentProfile->refreshValues();
if(_tree) {
_tree->readEngineUnlocks(_currentProfile->engineInventory(), _currentProfile->gearInventory());
}
}
else if(Utility::String::endsWith(filename, _currentProfile->steamId() + ".sav")) {
if(Utility::String::beginsWith(filename, Utility::formatString("{}Unit", _currentProfile->type() == ProfileType::Demo ? "Demo" : ""))) {

View file

@ -34,6 +34,9 @@
#include "../MassBuilderManager/MassBuilderManager.h"
#include "../ProfileManager/ProfileManager.h"
#include "../MassManager/MassManager.h"
#include "../ResearchTree/ResearchTree.h"
class Node;
using namespace Corrade;
using namespace Magnum;
@ -94,6 +97,8 @@ class SaveTool: public Platform::Sdl2Application, public efsw::FileWatchListener
auto drawRenamePopup(Containers::ArrayView<char> name_view) -> bool;
void drawGeneralInfo();
void drawResearchInventory();
void drawResearchTree();
void drawNode(Node& node);
void drawMassManager();
auto drawDeleteMassPopup(int mass_index) -> ImGuiID;
auto drawDeleteStagedMassPopup(const std::string& filename) -> ImGuiID;
@ -165,5 +170,7 @@ class SaveTool: public Platform::Sdl2Application, public efsw::FileWatchListener
};
Containers::StaticArray<2, efsw::WatchID> _watchIDs;
Containers::Pointer<ResearchTree> _tree;
bool _unsafeMode{false};
};

View file

@ -87,6 +87,11 @@ void SaveTool::drawManager() {
ImGui::EndTabItem();
}
if(ImGui::BeginTabItem("Research tree")) {
drawResearchTree();
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
@ -396,6 +401,124 @@ void SaveTool::drawResearchInventory() {
#undef matRow
}
void SaveTool::drawResearchTree() {
if(!_tree) {
_tree.emplace();
_tree->generateEngineTree();
_tree->readEngineUnlocks(_currentProfile->engineInventory(), _currentProfile->gearInventory());
_tree->generateOSTree();
_tree->generateArchTree();
}
if(ImGui::BeginTabBar("##TreeTabBar")) {
if(ImGui::BeginTabItem("Engine")) {
if(ImGui::BeginTable("##EngineTable", 1, ImGuiTableFlags_RowBg)) {
ImGui::TableSetupColumn("##Column", ImGuiTableColumnFlags_WidthStretch);
drawNode(_tree->getEngineRootNode());
ImGui::EndTable();
}
ImGui::EndTabItem();
}
if(ImGui::BeginTabItem("OS")) {
if(ImGui::BeginTable("##OSTable", 1, ImGuiTableFlags_RowBg)) {
ImGui::TableSetupColumn("##Column", ImGuiTableColumnFlags_WidthStretch);
drawNode(_tree->getOSRootNode());
ImGui::EndTable();
}
ImGui::EndTabItem();
}
if(ImGui::BeginTabItem("Architect")) {
if(ImGui::BeginTable("##ArchTable", 1, ImGuiTableFlags_RowBg)) {
ImGui::TableSetupColumn("##Column", ImGuiTableColumnFlags_WidthStretch);
drawNode(_tree->getArchRootNode());
ImGui::EndTable();
}
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
}
void SaveTool::drawNode(Node& node) {
auto nodeTooltip = [this, &node]{
if(ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::Text("Tier %u", node.tier());
if(node.type() == Node::Type::Engine ||
node.type() == Node::Type::OS ||
node.type() == Node::Type::Architect)
{
ImGui::SameLine();
ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
ImGui::SameLine();
switch(node.type()) {
case Node::Type::Engine:
ImGui::Text("%u gear slots", node.slots());
break;
case Node::Type::OS:
ImGui::Text("%u module slots", node.slots());
break;
case Node::Type::Architect:
ImGui::Text("%u tech slots", node.slots());
break;
default:
break;
}
}
ImGui::Separator();
ImGui::PushTextWrapPos(windowSize().x() * 0.40f);
ImGui::TextUnformatted(node.description());
if(std::strncmp("", node.activeEffect(), 3) != 0) {
ImGui::Separator();
ImGui::Text("Active effect: %s", node.activeEffect());
}
if(std::strncmp("", node.passiveEffect(), 3) != 0) {
ImGui::Separator();
ImGui::Text("Passive effect: %s", node.passiveEffect());
}
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
};
ImGui::TableNextRow();
ImGui::TableNextColumn();
bool has_children = (node.children().size() > 0);
if(has_children) {
bool open = ImGui::TreeNodeEx(node.name(), ImGuiTreeNodeFlags_SpanAvailWidth|(node.state() == Node::State::Unlocked ? ImGuiTreeNodeFlags_Selected : 0));
nodeTooltip();
if(open) {
for(Node* child : node.children()) {
drawNode(*child);
}
ImGui::TreePop();
}
}
else {
ImGui::TreeNodeEx(node.name(), ImGuiTreeNodeFlags_SpanAvailWidth|ImGuiTreeNodeFlags_Leaf|ImGuiTreeNodeFlags_NoTreePushOnOpen|
ImGuiTreeNodeFlags_Bullet|(node.state() == Node::State::Unlocked ? ImGuiTreeNodeFlags_Selected : 0));
nodeTooltip();
}
}
void SaveTool::drawMassManager() {
if(!_massManager) {
return;