From bb6de66b0c3fae45f0bd090131e036325cc8bdbe Mon Sep 17 00:00:00 2001 From: William JCM Date: Wed, 21 Jul 2021 11:30:41 +0200 Subject: [PATCH] ResearchTree: add basic functionality and some nodes. --- src/CMakeLists.txt | 3 + src/Profile/Profile.cpp | 53 +++++ src/Profile/Profile.h | 15 ++ src/ResearchTree/NodeIDs.h | 94 +++++++++ src/ResearchTree/ResearchTree.cpp | 277 ++++++++++++++++++++++++++ src/ResearchTree/ResearchTree.h | 112 +++++++++++ src/SaveTool/SaveTool.cpp | 3 + src/SaveTool/SaveTool.h | 7 + src/SaveTool/SaveTool_MainManager.cpp | 123 ++++++++++++ 9 files changed, 687 insertions(+) create mode 100644 src/ResearchTree/NodeIDs.h create mode 100644 src/ResearchTree/ResearchTree.cpp create mode 100644 src/ResearchTree/ResearchTree.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6e206a9..b796823 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -48,6 +48,9 @@ add_executable(MassBuilderSaveTool WIN32 Maps/StoryProgress.h ToastQueue/ToastQueue.h ToastQueue/ToastQueue.cpp + ResearchTree/NodeIDs.h + ResearchTree/ResearchTree.h + ResearchTree/ResearchTree.cpp FontAwesome/IconsFontAwesome5.h FontAwesome/IconsFontAwesome5Brands.h resource.rc diff --git a/src/Profile/Profile.cpp b/src/Profile/Profile.cpp index 46a1819..045d19b 100644 --- a/src/Profile/Profile.cpp +++ b/src/Profile/Profile.cpp @@ -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 { + return _engineInventory; +} + +auto Profile::gearInventory() -> Containers::ArrayView { + 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(iter + 0x3B); + Int size = *(int_iter++); + + if(size > 0) { + _engineInventory = Containers::Array{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(iter + 0x39); + size = *(int_iter++); + + if(size <= 0) { + return; + } + + _gearInventory = Containers::Array{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."; + } +} diff --git a/src/Profile/Profile.h b/src/Profile/Profile.h index 63cf5bc..b2c4cff 100644 --- a/src/Profile/Profile.h +++ b/src/Profile/Profile.h @@ -18,6 +18,8 @@ #include +#include + #include using namespace Magnum; @@ -141,6 +143,10 @@ class Profile { auto getCarbonizedSkin() -> Int; auto setCarbonizedSkin(Int amount) -> bool; + auto engineInventory() -> Containers::ArrayView; + auto gearInventory() -> Containers::ArrayView; + void getEngineUnlocks(); + private: std::string _profileDirectory; std::string _filename; @@ -183,4 +189,13 @@ class Profile { Int _muscularConstruction; Int _mineralExoskeletology; Int _carbonizedSkin; + + Containers::Array _engineInventory; + Containers::Array _gearInventory; + + Containers::Array _osInventory; + Containers::Array _moduleInventory; + + Containers::Array _archInventory; + Containers::Array _techInventory; }; diff --git a/src/ResearchTree/NodeIDs.h b/src/ResearchTree/NodeIDs.h new file mode 100644 index 0000000..551f5c7 --- /dev/null +++ b/src/ResearchTree/NodeIDs.h @@ -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 . + +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, +}; diff --git a/src/ResearchTree/ResearchTree.cpp b/src/ResearchTree/ResearchTree.cpp new file mode 100644 index 0000000..b23b4e4 --- /dev/null +++ b/src/ResearchTree/ResearchTree.cpp @@ -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 . + +#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 { + 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 engines, Containers::ArrayView 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); +} diff --git a/src/ResearchTree/ResearchTree.h b/src/ResearchTree/ResearchTree.h new file mode 100644 index 0000000..6f488af --- /dev/null +++ b/src/ResearchTree/ResearchTree.h @@ -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 . + +#include + +#include +#include + +#include + +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; + 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 _parents; + Containers::Array _children; +}; + +class ResearchTree { + public: + void generateEngineTree(); + void generateOSTree(); + void generateArchTree(); + + void readEngineUnlocks(Containers::ArrayView engines, Containers::ArrayView gears = nullptr); + + auto getEngineRootNode() -> Node&; + auto getOSRootNode() -> Node&; + auto getArchRootNode() -> Node&; + + private: + using Tree = std::unordered_map; + Tree _engineNodes; + Tree _osNodes; + Tree _archNodes; +}; diff --git a/src/SaveTool/SaveTool.cpp b/src/SaveTool/SaveTool.cpp index e95aff6..69f7eb1 100644 --- a/src/SaveTool/SaveTool.cpp +++ b/src/SaveTool/SaveTool.cpp @@ -188,6 +188,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" : ""))) { diff --git a/src/SaveTool/SaveTool.h b/src/SaveTool/SaveTool.h index 7577848..9b9e483 100644 --- a/src/SaveTool/SaveTool.h +++ b/src/SaveTool/SaveTool.h @@ -35,6 +35,9 @@ #include "../ProfileManager/ProfileManager.h" #include "../MassManager/MassManager.h" #include "../ToastQueue/ToastQueue.h" +#include "../ResearchTree/ResearchTree.h" + +class Node; using namespace Corrade; using namespace Magnum; @@ -97,6 +100,8 @@ class SaveTool: public Platform::Sdl2Application, public efsw::FileWatchListener auto drawRenamePopup(Containers::ArrayView 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; @@ -197,6 +202,8 @@ class SaveTool: public Platform::Sdl2Application, public efsw::FileWatchListener }; Containers::StaticArray<2, efsw::WatchID> _watchIDs; + Containers::Pointer _tree; + bool _checkUpdatesOnStartup{true}; bool _unsafeMode{false}; diff --git a/src/SaveTool/SaveTool_MainManager.cpp b/src/SaveTool/SaveTool_MainManager.cpp index 0e5ee5b..cf1ae18 100644 --- a/src/SaveTool/SaveTool_MainManager.cpp +++ b/src/SaveTool/SaveTool_MainManager.cpp @@ -74,6 +74,11 @@ void SaveTool::drawManager() { ImGui::EndTabItem(); } + if(ImGui::BeginTabItem("Research tree")) { + drawResearchTree(); + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); } @@ -389,6 +394,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;