Compare commits
19 commits
9349b840a4
...
8ab9933f2f
Author | SHA1 | Date | |
---|---|---|---|
8ab9933f2f | |||
406349bbe8 | |||
22c9627b84 | |||
e99ff14749 | |||
d0716d6242 | |||
512fa4088b | |||
ab124174b0 | |||
51127241ef | |||
ee384843e9 | |||
7f32166ab0 | |||
017900afe2 | |||
52b60ff2a5 | |||
14c5a76891 | |||
d9efe8191e | |||
445d7323b3 | |||
83002868d9 | |||
d0eee0caeb | |||
4dd2064aae | |||
e2d473da44 |
20 changed files with 1269 additions and 57 deletions
8
.gitmodules
vendored
8
.gitmodules
vendored
|
@ -26,3 +26,11 @@
|
|||
path = third-party/efsw
|
||||
url = https://github.com/SpartanJ/efsw
|
||||
branch = master
|
||||
[submodule "third-party/cpr"]
|
||||
path = third-party/cpr
|
||||
url = https://github.com/whoshuu/cpr
|
||||
branch = master
|
||||
[submodule "json.hpp"]
|
||||
path = third-party/json
|
||||
url = https://github.com/nlohmann/json
|
||||
branch = master
|
||||
|
|
|
@ -88,4 +88,11 @@ set(BUILD_TEST_APP OFF CACHE BOOL "" FORCE)
|
|||
set(EFSW_INSTALL OFF CACHE BOOL "" FORCE)
|
||||
add_subdirectory(third-party/efsw EXCLUDE_FROM_ALL)
|
||||
|
||||
set(CPR_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||
set(CMAKE_USE_LIBSSH2 OFF CACHE BOOL "" FORCE) # For some reason, even when HTTP_ONLY is set to ON, libcurl will try to link to libssh2.
|
||||
add_subdirectory(third-party/cpr EXCLUDE_FROM_ALL)
|
||||
|
||||
set(JSON_BuildTests OFF CACHE BOOL "" FORCE)
|
||||
add_subdirectory(third-party/json)
|
||||
|
||||
add_subdirectory(src)
|
||||
|
|
|
@ -18,7 +18,7 @@ set(CMAKE_CXX_STANDARD 14)
|
|||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
set(SAVETOOL_PROJECT_VERSION 1.0.0)
|
||||
set(SAVETOOL_PROJECT_VERSION 1.1.0)
|
||||
|
||||
find_package(Corrade REQUIRED Main Containers Utility Interconnect)
|
||||
find_package(Magnum REQUIRED GL Sdl2Application)
|
||||
|
@ -46,6 +46,11 @@ add_executable(MassBuilderSaveTool WIN32
|
|||
Mass/Mass.cpp
|
||||
Maps/LastMissionId.h
|
||||
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
|
||||
|
@ -55,7 +60,7 @@ if(CMAKE_BUILD_TYPE STREQUAL Debug)
|
|||
add_compile_definitions(SAVETOOL_DEBUG_BUILD)
|
||||
endif()
|
||||
add_compile_definitions(SAVETOOL_VERSION="${SAVETOOL_PROJECT_VERSION}"
|
||||
SAVETOOL_CODENAME="Agonising Quark"
|
||||
SAVETOOL_CODENAME="Beautiful Reina"
|
||||
SUPPORTED_GAME_VERSION="0.7.6")
|
||||
|
||||
if(CMAKE_BUILD_TYPE STREQUAL Release)
|
||||
|
@ -75,5 +80,7 @@ target_link_libraries(MassBuilderSaveTool PRIVATE
|
|||
MagnumIntegration::ImGui
|
||||
efsw
|
||||
zip
|
||||
cpr::cpr
|
||||
nlohmann_json::nlohmann_json
|
||||
imm32
|
||||
wtsapi32)
|
||||
|
|
|
@ -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.";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
94
src/ResearchTree/NodeIDs.h
Normal file
94
src/ResearchTree/NodeIDs.h
Normal 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,
|
||||
};
|
277
src/ResearchTree/ResearchTree.cpp
Normal file
277
src/ResearchTree/ResearchTree.cpp
Normal 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);
|
||||
}
|
112
src/ResearchTree/ResearchTree.h
Normal file
112
src/ResearchTree/ResearchTree.h
Normal 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;
|
||||
};
|
|
@ -30,6 +30,10 @@
|
|||
#include <Magnum/ImGuiIntegration/Integration.h>
|
||||
#include <Magnum/ImGuiIntegration/Context.hpp>
|
||||
|
||||
#include <cpr/cpr.h>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <windef.h>
|
||||
#include <winuser.h>
|
||||
#include <processthreadsapi.h>
|
||||
|
@ -81,12 +85,15 @@ SaveTool::SaveTool(const Arguments& arguments):
|
|||
|
||||
initialiseGui();
|
||||
|
||||
if((_initEventId = SDL_RegisterEvents(1)) == UnsignedInt(-1)) {
|
||||
if((_initEventId = SDL_RegisterEvents(2)) == UnsignedInt(-1)) {
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error",
|
||||
"SDL_RegisterEvents failed in SaveTool::SaveTool(). Exiting...", window());
|
||||
exit(EXIT_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
_updateEventId = _initEventId + 1;
|
||||
|
||||
_backupsDir = Utility::Directory::join(Utility::Directory::path(Utility::Directory::executableLocation()), "backups");
|
||||
_stagingDir = Utility::Directory::join(Utility::Directory::path(Utility::Directory::executableLocation()), "staging");
|
||||
|
||||
|
@ -101,6 +108,7 @@ SaveTool::SaveTool(const Arguments& arguments):
|
|||
if(!findGameDataDirectory()) {
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error initialising the app", _lastError.c_str(), window());
|
||||
exit(EXIT_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
_configDir = Utility::Directory::join(_gameDataDir, "Saved/Config/WindowsNoEditor");
|
||||
|
@ -110,6 +118,7 @@ SaveTool::SaveTool(const Arguments& arguments):
|
|||
if(SDL_InitSubSystem(SDL_INIT_TIMER) != 0) {
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error initialising the app", SDL_GetError(), window());
|
||||
exit(EXIT_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
checkGameState();
|
||||
|
@ -122,11 +131,24 @@ SaveTool::SaveTool(const Arguments& arguments):
|
|||
if(_gameCheckTimerId == 0) {
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", SDL_GetError(), window());
|
||||
exit(EXIT_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
initialiseConfiguration();
|
||||
|
||||
if(_checkUpdatesOnStartup) {
|
||||
_thread = std::thread{[this]{ checkForUpdates(); }};
|
||||
_queue.addToast(Toast::Type::Default, "Checking for updates...");
|
||||
}
|
||||
}
|
||||
|
||||
SaveTool::~SaveTool() {
|
||||
SDL_RemoveTimer(_gameCheckTimerId);
|
||||
|
||||
_conf.setValue("cheat_mode", _cheatMode);
|
||||
_conf.setValue("unsafe_mode", _unsafeMode);
|
||||
_conf.setValue("startup_update_check", _checkUpdatesOnStartup);
|
||||
_conf.save();
|
||||
}
|
||||
|
||||
void SaveTool::handleFileAction(efsw::WatchID watch_id,
|
||||
|
@ -166,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" : ""))) {
|
||||
|
@ -247,6 +272,9 @@ void SaveTool::anyEvent(SDL_Event& event) {
|
|||
if(event.type == _initEventId) {
|
||||
initEvent(event);
|
||||
}
|
||||
else if(event.type == _updateEventId) {
|
||||
updateCheckEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
void SaveTool::initEvent(SDL_Event& event) {
|
||||
|
@ -266,6 +294,97 @@ void SaveTool::initEvent(SDL_Event& event) {
|
|||
}
|
||||
}
|
||||
|
||||
void SaveTool::updateCheckEvent(SDL_Event& event) {
|
||||
_thread.join();
|
||||
|
||||
cpr::Response r{std::move(*static_cast<cpr::Response*>(event.user.data1))};
|
||||
delete static_cast<cpr::Response*>(event.user.data1);
|
||||
|
||||
if(r.elapsed > 10.0) {
|
||||
_queue.addToast(Toast::Type::Error, "The request timed out.");
|
||||
return;
|
||||
}
|
||||
|
||||
if(r.status_code != 200) {
|
||||
_queue.addToast(Toast::Type::Error, Utility::formatString("The request failed with error code {}: {}", r.status_code, r.reason));
|
||||
return;
|
||||
}
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
json response = json::parse(r.text);
|
||||
|
||||
struct Version {
|
||||
explicit Version(const std::string& str) {
|
||||
std::size_t start_point = 0;
|
||||
|
||||
if(str[0] == 'v') {
|
||||
start_point++;
|
||||
}
|
||||
|
||||
major = std::atoi(str.c_str() + start_point);
|
||||
start_point = str.find('.', start_point) + 1;
|
||||
minor = std::atoi(str.c_str() + start_point);
|
||||
start_point = str.find('.', start_point) + 1;
|
||||
patch = std::atoi(str.c_str() + start_point);
|
||||
}
|
||||
Int major;
|
||||
Int minor;
|
||||
Int patch;
|
||||
|
||||
bool operator==(const Version& other) const {
|
||||
return (major == other.major) && (minor == other.minor) && (patch == other.patch);
|
||||
}
|
||||
bool operator>(const Version& other) const {
|
||||
return ( major * 10000 + minor * 100 + patch) >
|
||||
(other.major * 10000 + other.minor * 100 + other.patch);
|
||||
}
|
||||
};
|
||||
|
||||
static const Version current_ver{SAVETOOL_VERSION};
|
||||
Version latest_ver{response[0]["tag_name"]};
|
||||
|
||||
if(latest_ver == current_ver) {
|
||||
_queue.addToast(Toast::Type::Success, "The application is already up to date.");
|
||||
}
|
||||
else if(latest_ver > current_ver) {
|
||||
_queue.addToast(Toast::Type::Warning, "Your version is out of date.\nCheck the settings for more information.",
|
||||
std::chrono::milliseconds{5000});
|
||||
_updateAvailable = true;
|
||||
_latestVersion = Utility::formatString("{}.{}.{}", latest_ver.major, latest_ver.minor, latest_ver.patch);
|
||||
_releaseLink = response[0]["html_url"];
|
||||
_downloadLink = response[0]["assets"][0]["browser_download_url"];
|
||||
}
|
||||
else if(current_ver > latest_ver) {
|
||||
_queue.addToast(Toast::Type::Warning, "Your version is more recent than the latest one in the repo. How???");
|
||||
}
|
||||
}
|
||||
|
||||
void SaveTool::initialiseConfiguration() {
|
||||
if(_conf.hasValue("cheat_mode")) {
|
||||
_cheatMode = _conf.value<bool>("cheat_mode");
|
||||
}
|
||||
else {
|
||||
_conf.setValue("cheat_mode", _cheatMode);
|
||||
}
|
||||
|
||||
if(_conf.hasValue("unsafe_mode")) {
|
||||
_unsafeMode = _conf.value<bool>("unsafe_mode");
|
||||
}
|
||||
else {
|
||||
_conf.setValue("unsafe_mode", _unsafeMode);
|
||||
}
|
||||
|
||||
if(_conf.hasValue("startup_update_check")) {
|
||||
_checkUpdatesOnStartup = _conf.value<bool>("startup_update_check");
|
||||
}
|
||||
else {
|
||||
_conf.setValue("startup_update_check", _checkUpdatesOnStartup);
|
||||
}
|
||||
|
||||
_conf.save();
|
||||
}
|
||||
|
||||
void SaveTool::initialiseGui() {
|
||||
ImGui::CreateContext();
|
||||
|
||||
|
@ -415,13 +534,15 @@ void SaveTool::drawGui() {
|
|||
ImGui::ShowMetricsWindow(&_metricsWindow);
|
||||
}
|
||||
#endif
|
||||
|
||||
_queue.draw(windowSize());
|
||||
}
|
||||
|
||||
void SaveTool::drawDisclaimer() {
|
||||
ImGui::SetNextWindowPos(ImVec2{Vector2{windowSize() / 2.0f}}, ImGuiCond_Always, center_pivot);
|
||||
|
||||
if(ImGui::Begin("Disclaimer##DisclaimerWindow", nullptr,
|
||||
ImGuiWindowFlags_NoCollapse|ImGuiWindowFlags_NoMove|
|
||||
ImGuiWindowFlags_NoCollapse|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoBringToFrontOnFocus|
|
||||
ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_MenuBar))
|
||||
{
|
||||
if(ImGui::BeginMenuBar()) {
|
||||
|
@ -549,3 +670,15 @@ void SaveTool::checkGameState() {
|
|||
_gameState = GameState::Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
void SaveTool::checkForUpdates() {
|
||||
cpr::Response r = cpr::Get(cpr::Url{"https://williamjcm.ovh/git/api/v1/repos/williamjcm/MassBuilderSaveTool/releases"},
|
||||
cpr::Parameters{{"limit", "1"}}, cpr::Timeout{10000});
|
||||
|
||||
SDL_Event event;
|
||||
SDL_zero(event);
|
||||
event.type = _updateEventId;
|
||||
event.user.code = r.status_code;
|
||||
event.user.data1 = new cpr::Response{std::move(r)};
|
||||
SDL_PushEvent(&event);
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include <thread>
|
||||
|
||||
#include <Corrade/Containers/Pointer.h>
|
||||
#include <Corrade/Utility/Configuration.h>
|
||||
#include <Corrade/Utility/Resource.h>
|
||||
|
||||
#include <Magnum/Platform/Sdl2Application.h>
|
||||
|
@ -33,6 +34,10 @@
|
|||
|
||||
#include "../ProfileManager/ProfileManager.h"
|
||||
#include "../MassManager/MassManager.h"
|
||||
#include "../ToastQueue/ToastQueue.h"
|
||||
#include "../ResearchTree/ResearchTree.h"
|
||||
|
||||
class Node;
|
||||
|
||||
using namespace Corrade;
|
||||
using namespace Magnum;
|
||||
|
@ -70,8 +75,10 @@ class SaveTool: public Platform::Sdl2Application, public efsw::FileWatchListener
|
|||
ProfileManagerFailure
|
||||
};
|
||||
void initEvent(SDL_Event& event);
|
||||
void updateCheckEvent(SDL_Event& event);
|
||||
|
||||
// Initialisation methods
|
||||
void initialiseConfiguration();
|
||||
void initialiseGui();
|
||||
void initialiseManager();
|
||||
auto findGameDataDirectory() -> bool;
|
||||
|
@ -93,6 +100,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;
|
||||
|
@ -136,6 +145,9 @@ class SaveTool: public Platform::Sdl2Application, public efsw::FileWatchListener
|
|||
|
||||
void checkGameState();
|
||||
|
||||
void checkForUpdates();
|
||||
|
||||
Utility::Configuration _conf{"MassBuilderSaveTool.ini"};
|
||||
Utility::Resource _rs{"assets"};
|
||||
|
||||
// GUI-related members
|
||||
|
@ -155,8 +167,12 @@ class SaveTool: public Platform::Sdl2Application, public efsw::FileWatchListener
|
|||
bool _metricsWindow{false};
|
||||
#endif
|
||||
|
||||
std::thread _thread;
|
||||
ToastQueue _queue;
|
||||
|
||||
std::thread _thread; // used for threaded operations such as profile manager init or update checking.
|
||||
|
||||
UnsignedInt _initEventId;
|
||||
UnsignedInt _updateEventId;
|
||||
|
||||
std::string _lastError;
|
||||
|
||||
|
@ -186,5 +202,15 @@ class SaveTool: public Platform::Sdl2Application, public efsw::FileWatchListener
|
|||
};
|
||||
Containers::StaticArray<2, efsw::WatchID> _watchIDs;
|
||||
|
||||
Containers::Pointer<ResearchTree> _tree;
|
||||
|
||||
bool _checkUpdatesOnStartup{true};
|
||||
bool _unsafeMode{false};
|
||||
|
||||
bool _updateAvailable{false};
|
||||
std::string _latestVersion;
|
||||
std::string _releaseLink;
|
||||
std::string _downloadLink;
|
||||
|
||||
bool _cheatMode{false};
|
||||
};
|
||||
|
|
|
@ -42,12 +42,6 @@ void SaveTool::drawManager() {
|
|||
return;
|
||||
}
|
||||
|
||||
if(ImGui::BeginTable("##TopRow", 2)) {
|
||||
ImGui::TableSetupColumn("##ProfileInfo", ImGuiTableColumnFlags_WidthStretch);
|
||||
ImGui::TableSetupColumn("##Unsafe", ImGuiTableColumnFlags_WidthFixed);
|
||||
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::Text("Current profile: %s (%s)",
|
||||
_currentProfile->companyName().c_str(),
|
||||
|
@ -60,14 +54,6 @@ void SaveTool::drawManager() {
|
|||
_uiState = UiState::ProfileManager;
|
||||
}
|
||||
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
ImGui::Checkbox("Unsafe mode", &_unsafeMode);
|
||||
drawTooltip("Enabling this allows interactions deemed to be unsafe to do while the game is running.",
|
||||
Float(windowSize().x()) * 0.35f);
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
if(ImGui::BeginChild("##ProfileInfo",
|
||||
{ImGui::GetContentRegionAvailWidth() * 0.60f, 0.0f},
|
||||
true, ImGuiWindowFlags_MenuBar))
|
||||
|
@ -88,6 +74,11 @@ void SaveTool::drawManager() {
|
|||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
if(ImGui::BeginTabItem("Research tree")) {
|
||||
drawResearchTree();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
|
||||
|
@ -245,6 +236,10 @@ void SaveTool::drawGeneralInfo() {
|
|||
}
|
||||
}
|
||||
|
||||
if(!_cheatMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
static Int credits;
|
||||
|
@ -315,7 +310,8 @@ void SaveTool::drawResearchInventory() {
|
|||
ImGui::TextUnformatted((name)); \
|
||||
ImGui::TableSetColumnIndex(1); \
|
||||
if(_currentProfile->getter() != -1) { \
|
||||
drawUnsafeText("%i", _currentProfile->getter()); \
|
||||
ImGui::Text("%i", _currentProfile->getter()); \
|
||||
if(_cheatMode) { \
|
||||
ImGui::TableSetColumnIndex(2); \
|
||||
ImGui::PushID(#setter); \
|
||||
static Int var = _currentProfile->getter(); \
|
||||
|
@ -331,6 +327,7 @@ void SaveTool::drawResearchInventory() {
|
|||
} \
|
||||
ImGui::PopID(); \
|
||||
} \
|
||||
} \
|
||||
else { \
|
||||
ImGui::TextDisabled("Not found in the save file"); \
|
||||
}
|
||||
|
@ -397,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;
|
||||
|
|
|
@ -27,7 +27,7 @@ void SaveTool::drawProfileManager() {
|
|||
|
||||
ImGui::SetNextWindowPos(ImVec2{Vector2{windowSize() / 2.0f}}, ImGuiCond_Always, center_pivot);
|
||||
if(ImGui::Begin("Profile management##ProfileManager", nullptr,
|
||||
ImGuiWindowFlags_NoCollapse|ImGuiWindowFlags_AlwaysAutoResize|
|
||||
ImGuiWindowFlags_NoCollapse|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoBringToFrontOnFocus|
|
||||
ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_MenuBar))
|
||||
{
|
||||
if(ImGui::BeginMenuBar()) {
|
||||
|
|
|
@ -251,6 +251,58 @@ void SaveTool::drawAbout() {
|
|||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
if(ImGui::TreeNodeEx("C++ Requests (cpr)", ImGuiTreeNodeFlags_SpanAvailWidth)) {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
const char* cpr_website = "https://whoshuu.github.io/cpr/";
|
||||
ImGui::Text(ICON_FA_GLOBE " %s", cpr_website);
|
||||
ImGui::SameLine();
|
||||
if(ImGui::Button("Copy to clipboard")) {
|
||||
ImGui::SetClipboardText(cpr_website);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if(ImGui::Button("Open in browser")) {
|
||||
openUri(cpr_website);
|
||||
}
|
||||
|
||||
ImGui::TextUnformatted("Licence: MIT");
|
||||
|
||||
static const auto cpr_licence = _rs.get("LICENSE.cpr");
|
||||
if(ImGui::BeginChild("##cprLicence", {0.0f, windowSize().y() * 0.3f}, true)) {
|
||||
ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[1]);
|
||||
ImGui::TextUnformatted(cpr_licence.c_str());
|
||||
ImGui::PopFont();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
if(ImGui::TreeNodeEx("JSON for Modern C++ (aka json.hpp)", ImGuiTreeNodeFlags_SpanAvailWidth)) {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
const char* json_website = "https://json.nlohmann.me/";
|
||||
ImGui::Text(ICON_FA_GLOBE " %s", json_website);
|
||||
ImGui::SameLine();
|
||||
if(ImGui::Button("Copy to clipboard")) {
|
||||
ImGui::SetClipboardText(json_website);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if(ImGui::Button("Open in browser")) {
|
||||
openUri(json_website);
|
||||
}
|
||||
|
||||
ImGui::TextUnformatted("Licence: MIT");
|
||||
|
||||
static const auto json_licence = _rs.get("LICENSE.json");
|
||||
if(ImGui::BeginChild("##jsonLicence", {0.0f, windowSize().y() * 0.3f}, true)) {
|
||||
ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[1]);
|
||||
ImGui::TextUnformatted(json_licence.c_str());
|
||||
ImGui::PopFont();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
if(ImGui::TreeNodeEx("Font Awesome", ImGuiTreeNodeFlags_SpanAvailWidth)) {
|
||||
ImGui::TextUnformatted("Version used: 5.15.3");
|
||||
ImGui::AlignTextToFramePadding();
|
||||
|
|
|
@ -41,11 +41,11 @@ void SaveTool::drawMainMenu() {
|
|||
}
|
||||
|
||||
if(ImGui::BeginMenu(ICON_FA_FOLDER_OPEN " Open manager directory")) {
|
||||
if(ImGui::MenuItem(ICON_FA_FILE_ARCHIVE " Profile backups", nullptr, false, _profileManager != nullptr)) {
|
||||
if(ImGui::MenuItem(ICON_FA_FILE_ARCHIVE " Profile backups", nullptr, false, Utility::Directory::exists(_backupsDir))) {
|
||||
openUri(Utility::Directory::toNativeSeparators(_backupsDir));
|
||||
}
|
||||
|
||||
if(ImGui::MenuItem(ICON_FA_EXCHANGE_ALT " Staging area", nullptr, false, _massManager != nullptr)) {
|
||||
if(ImGui::MenuItem(ICON_FA_EXCHANGE_ALT " Staging area", nullptr, false, Utility::Directory::exists(_stagingDir))) {
|
||||
openUri(Utility::Directory::toNativeSeparators(_stagingDir));
|
||||
}
|
||||
|
||||
|
@ -54,6 +54,43 @@ void SaveTool::drawMainMenu() {
|
|||
|
||||
ImGui::Separator();
|
||||
|
||||
if(ImGui::BeginMenu(ICON_FA_COG " Settings")) {
|
||||
ImGui::Checkbox("Cheat mode", &_cheatMode);
|
||||
ImGui::SameLine();
|
||||
ImGui::AlignTextToFramePadding();
|
||||
drawHelpMarker("This gives access to save edition features that can be considered cheats.",
|
||||
Float(windowSize().x()) * 0.4f);
|
||||
|
||||
ImGui::Checkbox("Unsafe mode", &_unsafeMode);
|
||||
ImGui::SameLine();
|
||||
ImGui::AlignTextToFramePadding();
|
||||
drawHelpMarker("This allows changing the state of save files in the game's save folder even when the game is running.",
|
||||
Float(windowSize().x()) * 0.4f);
|
||||
|
||||
ImGui::Checkbox("Check for updates on startup", &_checkUpdatesOnStartup);
|
||||
ImGui::SameLine();
|
||||
if(ImGui::Button(ICON_FA_SYNC_ALT " Check now")) {
|
||||
_queue.addToast(Toast::Type::Default, "Checking for updates...");
|
||||
_thread = std::thread{[this]{ checkForUpdates(); }};
|
||||
}
|
||||
|
||||
if(_updateAvailable) {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::Text("Version %s is available.", _latestVersion.c_str());
|
||||
if(ImGui::Button(ICON_FA_FILE_SIGNATURE " Release notes")) {
|
||||
openUri(_releaseLink);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if(ImGui::Button(ICON_FA_DOWNLOAD " Download now")) {
|
||||
openUri(_downloadLink);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if(ImGui::MenuItem(ICON_FA_SIGN_OUT_ALT " Quit##QuitMenuItem")) {
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
|
162
src/ToastQueue/ToastQueue.cpp
Normal file
162
src/ToastQueue/ToastQueue.cpp
Normal file
|
@ -0,0 +1,162 @@
|
|||
// 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 <Corrade/Utility/FormatStl.h>
|
||||
|
||||
#include <Magnum/Math/Functions.h>
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include "../FontAwesome/IconsFontAwesome5.h"
|
||||
|
||||
#include "ToastQueue.h"
|
||||
|
||||
using namespace Corrade;
|
||||
|
||||
constexpr UnsignedInt success_colour = 0xff67d23bu;
|
||||
constexpr UnsignedInt info_colour = 0xffcc832fu;
|
||||
constexpr UnsignedInt warning_colour = 0xff2fcfc7u;
|
||||
constexpr UnsignedInt error_colour = 0xff3134cdu;
|
||||
|
||||
constexpr UnsignedInt fade_time = 150;
|
||||
constexpr Float base_opacity = 1.0f;
|
||||
constexpr Vector2 padding{20.0f, 20.0f};
|
||||
constexpr Float toast_spacing = 10.0f;
|
||||
|
||||
Toast::Toast(Type type, const std::string& message, std::chrono::milliseconds timeout):
|
||||
_type{type}, _message{message}, _timeout{timeout}, _creationTime{std::chrono::steady_clock::now()}
|
||||
{
|
||||
_phaseTrack = Animation::Track<UnsignedInt, Phase>{{
|
||||
{0, Phase::FadeIn},
|
||||
{fade_time, Phase::Wait},
|
||||
{fade_time + timeout.count(), Phase::FadeOut},
|
||||
{(fade_time * 2) + timeout.count(), Phase::TimedOut}
|
||||
}, Math::select, Animation::Extrapolation::Constant};
|
||||
}
|
||||
|
||||
auto Toast::type() -> Type {
|
||||
return _type;
|
||||
}
|
||||
|
||||
auto Toast::message() -> const std::string& {
|
||||
return _message;
|
||||
}
|
||||
|
||||
auto Toast::timeout() -> std::chrono::milliseconds {
|
||||
return _timeout;
|
||||
}
|
||||
|
||||
auto Toast::creationTime() -> std::chrono::steady_clock::time_point {
|
||||
return _creationTime;
|
||||
}
|
||||
|
||||
auto Toast::elapsedTime() -> std::chrono::milliseconds {
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - _creationTime);
|
||||
}
|
||||
|
||||
auto Toast::phase() -> Phase {
|
||||
return _phaseTrack.at(elapsedTime().count());
|
||||
}
|
||||
|
||||
auto Toast::opacity() -> Float {
|
||||
Phase phase = this->phase();
|
||||
Long elapsed_time = elapsedTime().count();
|
||||
|
||||
if(phase == Phase::FadeIn) {
|
||||
return Float(elapsed_time) / Float(fade_time);
|
||||
}
|
||||
else if(phase == Phase::FadeOut) {
|
||||
return 1.0f - ((Float(elapsed_time) - Float(fade_time) - Float(_timeout.count())) / Float(fade_time));
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
void ToastQueue::addToast(Toast&& toast) {
|
||||
_toasts.push_back(std::move(toast));
|
||||
}
|
||||
|
||||
void ToastQueue::addToast(Toast::Type type, const std::string& message, std::chrono::milliseconds timeout) {
|
||||
_toasts.emplace_back(type, message, timeout);
|
||||
}
|
||||
|
||||
void ToastQueue::draw(Vector2i viewport_size) {
|
||||
Float height = 0.0f;
|
||||
|
||||
for(UnsignedInt i = 0; i < _toasts.size(); i++) {
|
||||
Toast* current = &_toasts[i];
|
||||
|
||||
if(current->phase() == Toast::Phase::TimedOut) {
|
||||
removeToast(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string win_id = Utility::formatString("##Toast{}", i);
|
||||
|
||||
Float opacity = base_opacity * current->opacity();
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, opacity);
|
||||
|
||||
ImGui::SetNextWindowPos({viewport_size.x() - padding.x(), viewport_size.y() - padding.y() - height}, ImGuiCond_Always, {1.0f, 1.0f});
|
||||
if(ImGui::Begin(win_id.c_str(), nullptr,
|
||||
ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoDecoration|
|
||||
ImGuiWindowFlags_NoInputs|ImGuiWindowFlags_NoNav|ImGuiWindowFlags_NoFocusOnAppearing))
|
||||
{
|
||||
ImColor colour = 0xffffffff;
|
||||
|
||||
switch(current->type()) {
|
||||
case Toast::Type::Default:
|
||||
break;
|
||||
case Toast::Type::Success:
|
||||
colour = success_colour;
|
||||
ImGui::TextColored(colour, ICON_FA_CHECK_CIRCLE);
|
||||
break;
|
||||
case Toast::Type::Info:
|
||||
colour = info_colour;
|
||||
ImGui::TextColored(colour, ICON_FA_INFO_CIRCLE);
|
||||
break;
|
||||
case Toast::Type::Warning:
|
||||
colour = warning_colour;
|
||||
ImGui::TextColored(colour, ICON_FA_EXCLAMATION_TRIANGLE);
|
||||
break;
|
||||
case Toast::Type::Error:
|
||||
colour = error_colour;
|
||||
ImGui::TextColored(colour, ICON_FA_TIMES_CIRCLE);
|
||||
break;
|
||||
}
|
||||
|
||||
if(current->type() != Toast::Type::Default) {
|
||||
ImGui::SameLine();
|
||||
}
|
||||
|
||||
if(current->message().length() > 127) {
|
||||
ImGui::TextColored(colour, "%.*s...", 127, current->message().c_str());
|
||||
}
|
||||
else {
|
||||
ImGui::TextColored(colour, current->message().c_str());
|
||||
}
|
||||
|
||||
height += ImGui::GetWindowHeight() + toast_spacing;
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
}
|
||||
|
||||
void ToastQueue::removeToast(Long index) {
|
||||
_toasts.erase(_toasts.begin() + index);
|
||||
}
|
82
src/ToastQueue/ToastQueue.h
Normal file
82
src/ToastQueue/ToastQueue.h
Normal file
|
@ -0,0 +1,82 @@
|
|||
#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 <chrono>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <Magnum/Magnum.h>
|
||||
#include <Magnum/Animation/Track.h>
|
||||
|
||||
using namespace Magnum;
|
||||
|
||||
class Toast {
|
||||
public:
|
||||
enum class Type : UnsignedByte {
|
||||
Default, Success, Info, Warning, Error
|
||||
};
|
||||
|
||||
enum class Phase : UnsignedByte {
|
||||
FadeIn, Wait, FadeOut, TimedOut
|
||||
};
|
||||
|
||||
explicit Toast(Type type, const std::string& message,
|
||||
std::chrono::milliseconds timeout = std::chrono::milliseconds{3000});
|
||||
|
||||
Toast(const Toast& other) = delete;
|
||||
Toast& operator=(const Toast& other) = delete;
|
||||
|
||||
Toast(Toast&& other) = default;
|
||||
Toast& operator=(Toast&& other) = default;
|
||||
|
||||
auto type() -> Type;
|
||||
|
||||
auto message() -> std::string const&;
|
||||
|
||||
auto timeout() -> std::chrono::milliseconds;
|
||||
|
||||
auto creationTime() -> std::chrono::steady_clock::time_point;
|
||||
|
||||
auto elapsedTime() -> std::chrono::milliseconds;
|
||||
|
||||
auto phase() -> Phase;
|
||||
|
||||
auto opacity() -> Float;
|
||||
|
||||
private:
|
||||
Type _type{Type::Default};
|
||||
std::string _message;
|
||||
std::chrono::milliseconds _timeout;
|
||||
std::chrono::steady_clock::time_point _creationTime;
|
||||
Animation::Track<UnsignedInt, Phase> _phaseTrack;
|
||||
};
|
||||
|
||||
class ToastQueue {
|
||||
public:
|
||||
void addToast(Toast&& toast);
|
||||
|
||||
void addToast(Toast::Type type, const std::string& message,
|
||||
std::chrono::milliseconds timeout = std::chrono::milliseconds{3000});
|
||||
|
||||
void draw(Vector2i viewport_size);
|
||||
|
||||
private:
|
||||
void removeToast(Long index);
|
||||
|
||||
std::vector<Toast> _toasts;
|
||||
};
|
|
@ -43,3 +43,11 @@ alias=LICENSE.libzip
|
|||
[file]
|
||||
filename=../third-party/efsw/LICENSE
|
||||
alias=LICENSE.efsw
|
||||
|
||||
[file]
|
||||
filename=../third-party/cpr/LICENSE
|
||||
alias=LICENSE.cpr
|
||||
|
||||
[file]
|
||||
filename=../third-party/json/LICENSE.MIT
|
||||
alias=LICENSE.json
|
||||
|
|
24
src/main.cpp
24
src/main.cpp
|
@ -18,7 +18,25 @@
|
|||
|
||||
#include <fstream>
|
||||
|
||||
#include <errhandlingapi.h>
|
||||
#include <synchapi.h>
|
||||
#include <winerror.h>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
void* mutex_handle = CreateMutexW(nullptr, 0, L"MassBuilderSaveTool");
|
||||
|
||||
if(!mutex_handle) {
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error initialising the app",
|
||||
"There was an error initialising the mutex.",nullptr);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if(GetLastError() == ERROR_ALREADY_EXISTS) {
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error initialising the app",
|
||||
"There can be only one running instance of the application.",nullptr);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
#ifndef SAVETOOL_DEBUG_BUILD
|
||||
std::ofstream output{"SaveToolLog.txt", std::ios::trunc|std::ios::out};
|
||||
|
||||
|
@ -28,5 +46,9 @@ int main(int argc, char** argv) {
|
|||
#endif
|
||||
|
||||
SaveTool app({argc, argv});
|
||||
return app.exec();
|
||||
Int result = app.exec();
|
||||
|
||||
ReleaseMutex(mutex_handle);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
1
third-party/cpr
vendored
Submodule
1
third-party/cpr
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 814bd0926379cfc786dd1d9df9ce266a4bb6cf59
|
1
third-party/json
vendored
Submodule
1
third-party/json
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 350ff4f7ced7c4117eae2fb93df02823c8021fcb
|
Loading…
Reference in a new issue