diff --git a/CMakeLists.txt b/CMakeLists.txt index a6b165adee..ddbc0b3106 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -161,6 +161,8 @@ add_library(${PROJECT_NAME} OBJECT src/game_config.h src/game_config_game.cpp src/game_config_game.h + src/game_destiny.cpp + src/game_destiny.h src/game_dynrpg.cpp src/game_dynrpg.h src/game_enemy.cpp diff --git a/Makefile.am b/Makefile.am index d5baabd68c..28b8b8e006 100644 --- a/Makefile.am +++ b/Makefile.am @@ -141,6 +141,8 @@ libeasyrpg_player_a_SOURCES = \ src/game_config.h \ src/game_config_game.cpp \ src/game_config_game.h \ + src/game_destiny.cpp \ + src/game_destiny.h \ src/game_dynrpg.cpp \ src/game_dynrpg.h \ src/game_enemy.cpp \ @@ -736,6 +738,7 @@ test_runner_SOURCES = \ tests/game_character_flash.cpp \ tests/game_character_move.cpp \ tests/game_character_moveto.cpp \ + tests/game_destiny.cpp \ tests/game_enemy.cpp \ tests/game_event.cpp \ tests/game_player_input.cpp \ diff --git a/src/game_config_game.cpp b/src/game_config_game.cpp index 9cb133e4af..4a719b5556 100644 --- a/src/game_config_game.cpp +++ b/src/game_config_game.cpp @@ -241,6 +241,7 @@ void Game_ConfigGame::PrintActivePatches() { }; add_bool(patch_easyrpg); + add_bool(patch_destiny); add_bool(patch_dynrpg); add_bool(patch_common_this_event); add_bool(patch_unlock_pics); diff --git a/src/game_config_game.h b/src/game_config_game.h index ef8e70d567..fe9e3ec0c0 100644 --- a/src/game_config_game.h +++ b/src/game_config_game.h @@ -39,6 +39,7 @@ struct Game_ConfigGame { StringConfigParam engine_str{ "Engine", "", "Game", "Engine", std::string() }; BoolConfigParam fake_resolution{ "Fake Metrics", "Makes games run on higher resolutions (with some success)", "Game", "FakeResolution", false }; BoolConfigParam patch_easyrpg{ "EasyRPG", "EasyRPG Engine Extensions", "Patch", "EasyRPG", false }; + BoolConfigParam patch_destiny{ "Destiny Patch", "", "Patch", "Destiny", false }; BoolConfigParam patch_dynrpg{ "DynRPG", "", "Patch", "DynRPG", false }; ConfigParam patch_maniac{ "Maniac Patch", "", "Patch", "Maniac", 0 }; BoolConfigParam patch_common_this_event{ "Common This Event", "Support \"This Event\" in Common Events", "Patch", "CommonThisEvent", false }; diff --git a/src/game_destiny.cpp b/src/game_destiny.cpp new file mode 100644 index 0000000000..268c459d67 --- /dev/null +++ b/src/game_destiny.cpp @@ -0,0 +1,343 @@ +/* + * This file is part of EasyRPG Player. + * + * EasyRPG Player 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. + * + * EasyRPG Player 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 EasyRPG Player. If not, see . + */ + +// Headers +#include "game_destiny.h" +#include "filefinder.h" + +#ifndef EMSCRIPTEN +#include "exe_reader.h" +#endif // !EMSCRIPTEN +#include "output.h" + +using Destiny::InterpretFlag; +using Destiny::MainFunctions::Interpreter; + +using lcf::ToString; +using lcf::rpg::EventCommand; +using lcf::rpg::SaveEventExecFrame; + + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +// Game_Destiny implementations +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +void Game_Destiny::Load() +{ + // Do not load Destiny whether player cannot find "Destiny.dll" + if (! FileFinder::Game().Exists(DESTINY_DLL)) + { + return; + } + + uint32_t dllVersion = 0; + uint32_t language = 0; + uint32_t gameVersion = 0; + uint32_t extra = 0; + uint32_t dwordSize = 0; + uint32_t floatSize = 0; + uint32_t stringSize = 0; + +#ifndef EMSCRIPTEN + Filesystem_Stream::InputStream exe = FileFinder::Game().OpenFile(EXE_NAME); + + if (exe) + { + exe.seekg(0x00030689, std::ios_base::beg); + exe.read(reinterpret_cast(&stringSize), sizeof(uint32_t)); + exe.seekg(1, std::ios_base::cur); + exe.read(reinterpret_cast(&floatSize), sizeof(uint32_t)); + exe.seekg(1, std::ios_base::cur); + exe.read(reinterpret_cast(&dwordSize), sizeof(uint32_t)); + exe.seekg(1, std::ios_base::cur); + exe.read(reinterpret_cast(&extra), sizeof(uint32_t)); + exe.seekg(1, std::ios_base::cur); + exe.read(reinterpret_cast(&gameVersion), sizeof(uint32_t)); + exe.seekg(1, std::ios_base::cur); + exe.read(reinterpret_cast(&language), sizeof(uint32_t)); + exe.seekg(1, std::ios_base::cur); + exe.read(reinterpret_cast(&dllVersion), sizeof(uint32_t)); + exe.seekg(1, std::ios_base::cur); + } +#else + // TODO [XGB]: Find to manage Destiny initialization parameters on Emscripten + dllVersion = 0x20000; + language = Destiny::Language::ENGLISH; + gameVersion = 0x20000107; + extra = 0x01; + dwordSize = floatSize = stringSize = 0x64; +#endif // !EMSCRIPTEN + + Initialize(dllVersion, language, gameVersion, extra, dwordSize, floatSize, stringSize); +} + +Game_Destiny::Game_Destiny() +{ + _dllVersion = {}; + _gameVersion = {}; + _language = Destiny::Language::DEUTSCH; + _extra = 0U; + _trueColor = 0U; + _decimalComma = false; + _rm2k3 = false; + _protect = false; +} + +Game_Destiny::~Game_Destiny() +{ + Terminate(); +} + +void Game_Destiny::Initialize( + uint32_t dllVersion, + uint32_t language, + uint32_t gameVersion, + uint32_t extra, + uint32_t dwordSize, + uint32_t floatSize, + uint32_t stringSize +) +{ + _dllVersion = Destiny::Version(dllVersion); + _gameVersion = Destiny::Version(gameVersion); + _language = language <= 1 + ? static_cast(language) + : Destiny::Language::ENGLISH; + _extra = extra; + _decimalComma = _language == Destiny::Language::DEUTSCH; + + EvaluateExtraFlags(); + CheckVersionInfo(); + + // Init containers + _dwords.resize(dwordSize); + _floats.resize(floatSize); + _strings.resize(stringSize); + + // TODO: Init File container + // TODO: Init ClientSocket container + + // Debug + Output::Debug("[Destiny] Initialized"); + Output::Debug("[Destiny] Language: {}", _decimalComma ? "Deutsch" : "English"); + Output::Debug("[Destiny] DLL Version: {}", _dllVersion.toString()); + Output::Debug("[Destiny] Dwords: {}", dwordSize); + Output::Debug("[Destiny] Floats: {}", floatSize); + Output::Debug("[Destiny] Strings: {}", stringSize); + Output::Debug("[Destiny] RPG Maker version: {}", _rm2k3 ? 2003 : 2000); +} + +void Game_Destiny::Terminate() +{ + _dwords.clear(); + _floats.clear(); + _strings.clear(); + + // TODO [XGB]: Clear File container + // TODO [XGB]: Clear ClientSocket container +} + +bool Game_Destiny::Main(SaveEventExecFrame& frame) +{ + const char* script; + InterpretFlag flag; + + script = _interpreter.MakeString(frame); + flag = InterpretFlag::IF_EXIT; + + _interpreter.CleanUpData(); + _interpreter.LoadInterpretStack(); + + while (! _interpreter.IsEndOfScript()) + { + if (_interpreter.IsEndOfLine()) + { + _interpreter.ScriptNextChar(); + } + + flag = _interpreter.Interpret(); + if (flag == InterpretFlag::IF_EXIT) + { + break; + } + } + + Output::Debug("DestinyScript Code:\n{}", script); + _interpreter.FreeString(); + + return true; +} + +void Game_Destiny::EvaluateExtraFlags() +{ + _trueColor = (_extra & Destiny::DF_TRUECOLOR) << 9; + _protect = _extra & Destiny::DF_PROTECT; +} + +void Game_Destiny::CheckVersionInfo() +{ + uint32_t gameVersionMajor; + + gameVersionMajor = _gameVersion.major; + + if (! (gameVersionMajor == 0x2000 || gameVersionMajor == 0x2003)) + { + Output::Error("[Destiny]: {} is not a valid version", gameVersionMajor); + } + + _rm2k3 = gameVersionMajor == 0x2003; +} + + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +// MainFunctions / Interpreter +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +Interpreter::Interpreter() +{ + CleanUpData(); + _scriptPtr = nullptr; +} + +const char* Interpreter::MakeString(SaveEventExecFrame& frame) +{ + std::string code; + + int32_t& current = frame.current_command; + const std::vector& cmdList = frame.commands; + std::vector::const_iterator it = cmdList.begin() + current++; + + code = ToString((*it++).string); + + while (it != cmdList.cend() && it->code == static_cast(EventCommand::Code::Comment_2)) + { + code += '\n'; + code += ToString((*it++).string); + ++current; + } + + _destinyScript = code; + return _scriptPtr = _destinyScript.data(); +} + +void Interpreter::FreeString() +{ + _destinyScript = ""; + _scriptPtr = _destinyScript.data(); +} + +void Interpreter::SkipWhiteSpace() +{ + while (IsWhiteSpace(*_scriptPtr)) + { + ++_scriptPtr; + } +} + +const size_t Interpreter::GetWordLen() +{ + char* endPtr = _scriptPtr; + + while (IsWordChar(*endPtr)) + { + ++endPtr; + } + + return endPtr - _scriptPtr; +} + +const InterpretFlag Interpreter::Interpret() +{ + char* code; + //uint8_t flags[4]; + InterpretFlag returnType; + + size_t wordLen; + + code = nullptr; + returnType = IF_COMMAND; + + SkipSpace(); + wordLen = GetWordLen(); + + return IF_EXIT; +} + +void Interpreter::LoadInterpretStack() +{ + // +} + +void Interpreter::SkipSpace() +{ + bool finished = false; + char next = '\0'; + + while (! finished) + { + if (! IsWhiteSpace(*++_scriptPtr)) + { + if (*_scriptPtr == '/') + { + next = *(_scriptPtr + 1); + + if (next == '/') + { + finished = LineComment(); + } + else if (next == '*') + { + finished = BlockComment(); + } + } + else + { + finished = true; + } + } + } +} + +const bool Interpreter::LineComment() +{ + _scriptPtr += 2; + + while (*(++_scriptPtr)) + { + if (*_scriptPtr == 0x0A) + { + return false; + } + } + + return true; +} + +const bool Interpreter::BlockComment() +{ + _scriptPtr += 2; + + while (*(++_scriptPtr)) + { + if (*_scriptPtr == '*' && *(++_scriptPtr) == '/') + { + return false; + } + } + + return true; +} diff --git a/src/game_destiny.h b/src/game_destiny.h new file mode 100644 index 0000000000..b6cf5e5fad --- /dev/null +++ b/src/game_destiny.h @@ -0,0 +1,480 @@ +/* + * This file is part of EasyRPG Player. + * + * EasyRPG Player 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. + * + * EasyRPG Player 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 EasyRPG Player. If not, see . + */ + +#ifndef EP_GAME_DESTINY_H +#define EP_GAME_DESTINY_H + +#include +#include +#include + +#include "lcf/rpg/saveeventexecframe.h" + + +// Global constants +constexpr const char* DESTINY_DLL = "Destiny.dll"; + + +namespace Destiny +{ + // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // CONSTANTS + // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + + // Object flags + constexpr const uint16_t OF_NONE = 0x00; + constexpr const uint16_t OF_CALL = 0x01; + constexpr const uint16_t OF_ARRAY = 0x02; + constexpr const uint16_t OF_CONSTANT = 0x04; + + // Parameter flags + constexpr const uint16_t PF_NONE = 0x0000; + constexpr const uint16_t PF_BYTE = 0x0001; + constexpr const uint16_t PF_WORD = 0x0002; + constexpr const uint16_t PF_DWORD = 0x0003; + constexpr const uint16_t PF_DOUBLE = 0x0004; + constexpr const uint16_t PF_BOOL = 0x0005; + constexpr const uint16_t PF_STRING = 0x0006; + constexpr const uint16_t PF_EXTRA1 = 0x0100; + constexpr const uint16_t PF_EXTRA2 = 0x0200; + constexpr const uint16_t PF_EXTRA3 = 0x0400; + constexpr const uint16_t PF_EXTRA4 = 0x0800; + constexpr const uint16_t PF_FIXED = 0x1000; + constexpr const uint16_t PF_OBJECT = 0x2000; + constexpr const uint16_t PF_FUNCTION = 0x4000; + constexpr const uint16_t PF_POINTER = 0x8000; + + // Sign flags + constexpr const uint16_t VF_NONE = 0x0000; + constexpr const uint16_t VF_PLUS = 0x0001; + constexpr const uint16_t VF_MINUS = 0x0002; + constexpr const uint16_t VF_BINARYNOT = 0x0003; + constexpr const uint16_t VF_LOGICALNOT = 0x0004; + constexpr const uint16_t VF_INCREMENT = 0x0100; + constexpr const uint16_t VF_DECREMENT = 0x0200; + + // Operator flags + constexpr const uint16_t OF_LOGICALOR = 0x0001; + constexpr const uint16_t OF_LOGICALAND = 0x0002; + constexpr const uint16_t OF_EQUAL = 0x0003; + constexpr const uint16_t OF_UNEQUAL = 0x0004; + constexpr const uint16_t OF_ABOVE = 0x0005; + constexpr const uint16_t OF_ABOVEEQUAL = 0x0006; + constexpr const uint16_t OF_BELOW = 0x0007; + constexpr const uint16_t OF_BELOWEQUAL = 0x0008; + constexpr const uint16_t OF_CONCAT = 0x0009; + constexpr const uint16_t OF_ADD = 0x000A; + constexpr const uint16_t OF_SUBTRACT = 0x000B; + constexpr const uint16_t OF_MULTIPLY = 0x000C; + constexpr const uint16_t OF_DIVIDE = 0x000D; + constexpr const uint16_t OF_MODULO = 0x000E; + constexpr const uint16_t OF_SHIFTLEFT = 0x000F; + constexpr const uint16_t OF_SHIFTRIGHT = 0x0010; + constexpr const uint16_t OF_BINARYOR = 0x0011; + constexpr const uint16_t OF_BINARYXOR = 0x0012; + constexpr const uint16_t OF_BINARYAND = 0x0013; + constexpr const uint16_t OF_SET = 0x0100; + + // Destiny flags + constexpr const uint32_t DF_TRUECOLOR = 0b0001; // Use AuroraSheets 32-bit + constexpr const uint32_t DF_EVENTSYSTEM = 0b0010; // The Destiny event system is used (not yet implemented) + constexpr const uint32_t DF_HARMONY = 0b0100; // The Harmony.dll has a DestinyInterface (not yet implemented) + constexpr const uint32_t DF_PROTECT = 0b1000; // Potentially unsafe functions are blocked (not yet implemented) + + // File access flags + constexpr const uint8_t FILE_READ = 0b0001; + constexpr const uint8_t FILE_WRITE = 0b0010; + constexpr const uint8_t FILE_APPEND = 0b0100; + + + // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // ENUMS + // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + + enum Language + { + DEUTSCH = 0, + ENGLISH, + }; + + enum InterpretFlag + { + IF_ERROR = 0, + IF_COMMAND, + IF_IF, + IF_ELSEIF, + IF_ELSE, + IF_ENDIF, + IF_DO, + IF_LOOP, + IF_WHILE, + IF_UNTIL, + IF_BREAK, + IF_FOR, + IF_NEXT, + IF_SWITCH, + IF_CASE, + IF_DEFAULT, + IF_ENDSWITCH, + IF_CONTINUE, + IF_PAUSE, + IF_EXIT, + }; + + + // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // STRUCTS + // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + + struct Version + { + uint16_t major; + uint16_t minor; + + Version() + : major(0), minor(0) {} + Version(uint32_t version) + { + major = version >> 0x10; + minor = version & 0xFFFF; + } + + std::string toString() const + { + std::stringstream ss; + + ss << major << '.' << minor; + return ss.str(); + } + + inline bool operator==(Version& other) const + { + return major == other.major && minor == other.minor; + } + + inline bool operator!=(Version& other) const + { + return ! (*this == other); + } + + inline bool operator>(Version& other) const + { + return minor == other.minor + ? major > other.major + : minor > other.minor; + } + + inline bool operator<(Version& other) const + { + return minor == other.minor + ? major < other.major + : minor < other.minor; + } + + inline bool operator>=(Version& other) const + { + return ! (*this < other); + } + + inline bool operator<=(Version& other) const + { + return ! (*this > other); + } + }; + + + // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // CLASSES + // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + + namespace MainFunctions + { + class Interpreter + { + public: + Interpreter(); + + public: + // Member functions + + /** + * Generates a DestinyScript code. + * + * @param frame The event script data. + * @return A DestinyScript code. + */ + const char* MakeString(lcf::rpg::SaveEventExecFrame& frame); + + /** + * Releases the DestinyScript code. + * + * @return + */ + void FreeString(); + + /** + * Skip whitespaces from the DestinyScript code. + * + * @return + */ + void SkipWhiteSpace(); + + /* + * Retreives the length of the next DestinyScript word. + * + * @returns The word found length. + */ + const size_t GetWordLen(); + + /* + * Evaluates the DestinyScript code. + * + * @returns The interpreter state flag. + */ + const InterpretFlag Interpret(); + + /** + * Loads the interpreter stack. + * + * @return + */ + void LoadInterpretStack(); + + + // Inline functions + + /** + * Cleans the interpreter data. + * + * @return + */ + inline void CleanUpData() + { + _breaks = 0U; + _continues = 0U; + _loopOperation = 0U; + _loopOperationLevel = 0U; + } + + /** + * Moves the DestinyScript pointer to forward. + * + * @return + */ + inline void ScriptNextChar() + { + ++_scriptPtr; + } + + /** + * Checkes whether interpreter reached the + * end of instruction symbol. + * + * @return + */ + inline const bool IsEndOfLine() const + { + return _scriptPtr && *_scriptPtr == ';'; + } + + /** + * Checkes whether interpreter reached the + * end of the DestinyScript code. + * + * @return + */ + inline const bool IsEndOfScript() const + { + return _scriptPtr && *_scriptPtr == '\0'; + } + + private: + // Member data + std::string _destinyScript; + char* _scriptPtr; + uint32_t _breaks; + uint32_t _continues; + uint32_t _loopOperation; + uint32_t _loopOperationLevel; + + // Utility functions + + /** + * Skip all non-script objects. + * + * @return + */ + void SkipSpace(); + + /** + * Read a line comment. + * + * @return Flag to finish spaces skipping. + */ + const bool LineComment(); + + /** + * Read a line block. + * + * @return Flag to finish spaces skipping. + */ + const bool BlockComment(); + + /** + * Check whether character is a whitespace. + * + * @return Flag of whitespace character. + */ + inline const bool IsWhiteSpace(const char ch) const + { + return ch == ' ' || + ch == 0x09 || // Horizontal Tabulator (HT) + ch == 0x0D || // Carriage return (CR) + ch == 0x0A || // Line Feed (LF) + ch == 0x0B; // Vertical Tabulator (VT) + } + + /** + * Check whether character is a part of a word. + * + * @return Flag of word character. + */ + inline const bool IsWordChar(const char ch) const + { + return ch == '_' || + (ch >= '0' && ch <= '9') || + (ch >= 'A' && ch <= 'Z') || + (ch >= 'a' && ch <= 'z'); + } + }; + } +} + + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +// MAIN DESTINY CLASS +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +/** + * The Destiny Patch class. + */ +class Game_Destiny +{ +public: + // ctor and dtor + Game_Destiny(); + ~Game_Destiny(); + +public: + // Member functions + + /** + * Load the Destiny Patch. + * + * @return + */ + void Load(); + + /** + * Initialize and apply the patch to the game interpreter + * + * @param dllVersion The Destiny.dll version + * @param language The DLL language. Usually for displaying errors and for decimal format. + * @param gameVersion The RPG_RT version + * @param extra Extra flags + * @param dwordSize Length of dowrd container + * @param floatSize Length of float container + * @param stringSize Length of string container + */ + void Initialize( + uint32_t dllVersion, + uint32_t language, + uint32_t gameVersion, + uint32_t extra, + uint32_t dwordSize, + uint32_t floatSize, + uint32_t stringSize + ); + + /** + * Clear Destiny patch before close. + * + * @return + */ + void Terminate(); + + /** + * Call the Destiny Interpreter and run the received code. + * + * @param frame The event script data. + * @return Whether evaluation is successful. + */ + bool Main(lcf::rpg::SaveEventExecFrame& frame); + + + // Inline functions + + /** + * Retrieves the Destiny interpreter. + * + * @returns The Destiny interpreter. + */ + inline Destiny::MainFunctions::Interpreter& Interpreter() + { + return _interpreter; + } + +private: + // Member data + + // Destiny main functions + Destiny::MainFunctions::Interpreter _interpreter; + + // Destiny containers + std::vector _dwords; + std::vector _floats; + std::vector _strings; + + // Destiny data + Destiny::Version _dllVersion; + Destiny::Version _gameVersion; + Destiny::Language _language; + uint32_t _extra; + + // Settings + uint32_t _trueColor; + bool _decimalComma; + bool _rm2k3; + bool _protect; + + // Utility functions + + /** + * Evaluate the extra flags. + * + * @return + */ + void EvaluateExtraFlags(); + + /** + * Check the version information. + * + * @return + */ + void CheckVersionInfo(); +}; +#endif // !EP_GAME_DESTINY_H diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index a1b1d589cf..bfca8c3d64 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -29,6 +29,7 @@ #include "audio.h" #include "game_dynrpg.h" #include "filefinder.h" +#include "game_destiny.h" #include "game_map.h" #include "game_event.h" #include "game_enemyparty.h" @@ -2032,6 +2033,18 @@ bool Game_Interpreter::CommandComment(const lcf::rpg::EventCommand &com) { return Main_Data::game_dynrpg->Invoke(command, this); } + + + // DestinyScript + if (Player::IsPatchDestiny()) { + if (com.string.empty() || com.string[0] != '$') { + // Not a DestinyScript + return true; + } + + return Main_Data::game_destiny->Main(GetFrame()); + } + return true; } diff --git a/src/main_data.cpp b/src/main_data.cpp index 89ae3602f6..02e6523f72 100644 --- a/src/main_data.cpp +++ b/src/main_data.cpp @@ -21,6 +21,7 @@ #include "filefinder.h" #include "filefinder_rtp.h" #include "filesystem.h" +#include "game_destiny.h" #include "game_system.h" #include "game_actors.h" #include "game_party.h" @@ -73,6 +74,7 @@ namespace Main_Data { std::unique_ptr game_quit; std::unique_ptr game_dynrpg; std::unique_ptr game_ineluki; + std::unique_ptr game_destiny; bool global_save_opened = false; std::unique_ptr game_switches_global; std::unique_ptr game_variables_global; @@ -129,6 +131,7 @@ void Main_Data::Cleanup() { game_system.reset(); game_dynrpg.reset(); game_ineluki.reset(); + game_destiny.reset(); global_save_opened = false; game_switches_global.reset(); game_variables_global.reset(); diff --git a/src/main_data.h b/src/main_data.h index 9f5ddd0843..c0a1a4230d 100644 --- a/src/main_data.h +++ b/src/main_data.h @@ -41,6 +41,7 @@ class Game_Targets; class Game_Quit; class Game_DynRpg; class Game_Ineluki; +class Game_Destiny; class FileFinder_RTP; namespace Main_Data { @@ -60,6 +61,7 @@ namespace Main_Data { extern std::unique_ptr game_quit; extern std::unique_ptr game_dynrpg; extern std::unique_ptr game_ineluki; + extern std::unique_ptr game_destiny; extern bool global_save_opened; extern std::unique_ptr game_switches_global; // Used by Global Save command extern std::unique_ptr game_variables_global; diff --git a/src/player.cpp b/src/player.cpp index 3f7d17f3aa..494f09a052 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -44,6 +44,7 @@ #include "filesystem_hook.h" #include "game_actors.h" #include "game_battle.h" +#include "game_destiny.h" #include "game_map.h" #include "game_message.h" #include "game_enemyparty.h" @@ -826,6 +827,10 @@ void Player::CreateGameObjects() { if (!FileFinder::Game().FindFile("accord.dll").empty()) { game_config.patch_maniac.Set(true); } + + if (!FileFinder::Game().FindFile(DESTINY_DLL).empty()) { + game_config.patch_destiny.Set(true); + } } game_config.PrintActivePatches(); @@ -837,6 +842,10 @@ void Player::CreateGameObjects() { if (Player::IsPatchKeyPatch()) { Main_Data::game_ineluki->ExecuteScriptList(FileFinder::Game().FindFile("autorun.script")); } + + if (Player::IsPatchDestiny()) { + Main_Data::game_destiny->Load(); + } } bool Player::ChangeResolution(int width, int height) { @@ -920,6 +929,7 @@ void Player::ResetGameObjects() { Main_Data::game_variables_global = std::make_unique(min_var, max_var); Main_Data::game_dynrpg = std::make_unique(); Main_Data::game_ineluki = std::make_unique(); + Main_Data::game_destiny = std::make_unique(); Game_Clock::ResetFrame(Game_Clock::now()); diff --git a/src/player.h b/src/player.h index ed6d8022ce..c8ab392c65 100644 --- a/src/player.h +++ b/src/player.h @@ -292,6 +292,11 @@ namespace Player { */ bool IsPatchKeyPatch(); + /** + * @return True when Destiny Patch is active + */ + bool IsPatchDestiny(); + /** * @return True when EasyRpg extensions are on */ @@ -488,6 +493,10 @@ inline bool Player::IsPatchKeyPatch() { return game_config.patch_key_patch.Get(); } +inline bool Player::IsPatchDestiny() { + return game_config.patch_destiny.Get(); +} + inline bool Player::HasEasyRpgExtensions() { return game_config.patch_easyrpg.Get(); } diff --git a/tests/game_destiny.cpp b/tests/game_destiny.cpp new file mode 100644 index 0000000000..550c817108 --- /dev/null +++ b/tests/game_destiny.cpp @@ -0,0 +1,66 @@ +#include "doctest.h" +#include "game_destiny.h" +#include + + +TEST_SUITE_BEGIN("Game_Destiny"); + + +static const lcf::rpg::EventCommand* MakeCommand( + const lcf::rpg::EventCommand::Code code, + const std::string& string +) +{ + lcf::rpg::EventCommand* cmd = new lcf::rpg::EventCommand; + lcf::DBString dbStr(string); + + cmd->code = static_cast(code); + cmd->string = dbStr; + + return cmd; +} + +static lcf::rpg::SaveEventExecFrame* MakeFrame( + std::vector::const_iterator begin, + std::vector::const_iterator end +) +{ + lcf::rpg::SaveEventExecFrame* frame = new lcf::rpg::SaveEventExecFrame; + lcf::rpg::EventCommand::Code code; + + code = lcf::rpg::EventCommand::Code::Comment; + + while (begin != end) + { + const std::string& str = *begin++; + + frame->commands.push_back(*MakeCommand(code, str)); + code = lcf::rpg::EventCommand::Code::Comment_2; + } + + return frame; +} + + +TEST_CASE("AssertDestinyScript") +{ + Game_Destiny destiny; + std::vector lines { + "$", + "v[1] = 10;", + }; + lcf::rpg::SaveEventExecFrame* frame; + const char* destinyScript; + + frame = MakeFrame(lines.begin(), lines.end()); + destinyScript = destiny.Interpreter().MakeString(*frame); + + CHECK_EQ(*destinyScript, '$'); + + destiny.Interpreter().FreeString(); + delete frame; + frame = nullptr; +} + + +TEST_SUITE_END();