diff --git a/CMakeLists.txt b/CMakeLists.txt index ddbc0b3106..48b917b7a4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -239,6 +239,8 @@ add_library(${PROJECT_NAME} OBJECT src/input_source.h src/instrumentation.cpp src/instrumentation.h + src/json_helper.cpp + src/json_helper.h src/keys.h src/main_data.cpp src/main_data.h diff --git a/Makefile.am b/Makefile.am index 28b8b8e006..7ee65f6792 100644 --- a/Makefile.am +++ b/Makefile.am @@ -217,6 +217,8 @@ libeasyrpg_player_a_SOURCES = \ src/input_source.h \ src/instrumentation.cpp \ src/instrumentation.h \ + src/json_helper.cpp \ + src/json_helper.h \ src/keys.h \ src/main_data.cpp \ src/main_data.h \ diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index 0dc5ba3ec1..d903697b8f 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -46,6 +46,7 @@ #include "game_screen.h" #include "game_interpreter_control_variables.h" #include "game_windows.h" +#include "json_helper.h" #include "maniac_patch.h" #include "spriteset_map.h" #include "sprite_character.h" @@ -789,9 +790,11 @@ bool Game_Interpreter::ExecuteCommand(lcf::rpg::EventCommand const& com) { return CommandManiacCallCommand(com); case Cmd::EasyRpg_SetInterpreterFlag: return CommandEasyRpgSetInterpreterFlag(com); - case static_cast(2056): //EasyRPG_CloneMapEvent + case Cmd::EasyRpg_ProcessJson: + return CommandEasyRpgProcessJson(com); + case Cmd::EasyRpg_CloneMapEvent: return CommandEasyRpgCloneMapEvent(com); - case static_cast(2057): //EasyRPG_DestroyMapEvent + case Cmd::EasyRpg_DestroyMapEvent: return CommandEasyRpgDestroyMapEvent(com); default: return true; @@ -5055,13 +5058,95 @@ bool Game_Interpreter::CommandEasyRpgSetInterpreterFlag(lcf::rpg::EventCommand c Player::game_config.patch_key_patch.Set(flag_value); if (flag_name == "rpg2k3-cmds" || flag_name == "rpg2k3-commands") Player::game_config.patch_rpg2k3_commands.Set(flag_value); - if (flag_name == "rpg2k-battle") lcf::Data::system.easyrpg_use_rpg2k_battle_system = flag_value; return true; } +bool Game_Interpreter::CommandEasyRpgProcessJson(lcf::rpg::EventCommand const& com) { + if (!Player::HasEasyRpgExtensions()) { + return true; + } + +#ifndef HAVE_NLOHMANN_JSON + Output::Warning("CommandEasyRpgProcessJson: JSON not supported on this platform"); + return true; +#else + + int operation = ValueOrVariable(com.parameters[0], com.parameters[1]); + int source_var_id = ValueOrVariable(com.parameters[2], com.parameters[3]); + int target_var_type = ValueOrVariable(com.parameters[4], com.parameters[5]); + int target_var_id = ValueOrVariable(com.parameters[6], com.parameters[7]); + + std::string json_path = ToString(CommandStringOrVariable(com, 8, 9)); + auto* json_data = Main_Data::game_strings->ParseJson(source_var_id); + + if (!json_data) { + Output::Warning("JSON Parse error for {}", Main_Data::game_strings->Get(source_var_id)); + return true; + } + + if (target_var_type == 2 && !Player::IsPatchManiac()) { + Output::Warning("CommandEasyRpgProcessJson: String operations require Maniac Patch support"); + return true; + } + + std::optional result; + + if (operation == 0) { // Get operation: Extract a value from JSON data + result = Json_Helper::GetValue(*json_data, json_path); + + if (result) { + switch (target_var_type) { + case 0: // Switch + Main_Data::game_switches->Set(target_var_id, atoi(result->c_str()) != 0); + break; + case 1: // Variable + Main_Data::game_variables->Set(target_var_id, atoi(result->c_str())); + break; + case 2: // String + Main_Data::game_strings->Asg({ target_var_id }, *result); + break; + default: + Output::Warning("CommandEasyRpgProcessJson: Unsupported target_var_type {}", operation); + return true; + } + } + } + else if (operation == 1) { // Set operation: Update JSON data with a new value + std::string new_value; + + switch (target_var_type) { + case 0: // Switch + new_value = std::to_string(Main_Data::game_switches->Get(target_var_id)); + break; + case 1: // Variable + new_value = std::to_string(Main_Data::game_variables->Get(target_var_id)); + break; + case 2: // String + new_value = ToString(Main_Data::game_strings->Get(target_var_id)); + break; + default: + Output::Warning("CommandEasyRpgProcessJson: Unsupported target_var_type {}", operation); + return true; + } + + result = Json_Helper::SetValue(*json_data, json_path, new_value); + + if (result) { + Main_Data::game_strings->Asg({ source_var_id }, *result); + } + } + else { + Output::Warning("CommandEasyRpgProcessJson: Invalid Operation {}", operation); + } + + return true; + +#endif // !HAVE_NLOHMANN_JSON +} + bool Game_Interpreter::CommandEasyRpgCloneMapEvent(lcf::rpg::EventCommand const& com) { if (!Player::HasEasyRpgExtensions()) { return true; diff --git a/src/game_interpreter.h b/src/game_interpreter.h index 7149d59ca2..4956ebb44d 100644 --- a/src/game_interpreter.h +++ b/src/game_interpreter.h @@ -298,6 +298,7 @@ class Game_Interpreter : public Game_BaseInterpreterContext bool CommandManiacControlStrings(lcf::rpg::EventCommand const& com); bool CommandManiacCallCommand(lcf::rpg::EventCommand const& com); bool CommandEasyRpgSetInterpreterFlag(lcf::rpg::EventCommand const& com); + bool CommandEasyRpgProcessJson(lcf::rpg::EventCommand const& com); bool CommandEasyRpgCloneMapEvent(lcf::rpg::EventCommand const& com); bool CommandEasyRpgDestroyMapEvent(lcf::rpg::EventCommand const& com); diff --git a/src/game_strings.cpp b/src/game_strings.cpp index c16d988a33..0dfb4319ac 100644 --- a/src/game_strings.cpp +++ b/src/game_strings.cpp @@ -28,11 +28,34 @@ #include "player.h" #include "utils.h" +#ifdef HAVE_NLOHMANN_JSON +#include "json_helper.h" +#endif + void Game_Strings::WarnGet(int id) const { Output::Debug("Invalid read strvar[{}]!", id); --_warnings; } +#ifdef HAVE_NLOHMANN_JSON +nlohmann::json* Game_Strings::ParseJson(int id) { + auto it = _json_cache.find(id); + if (it != _json_cache.end()) { + return &(it->second); + } + + auto str = ToString(Get(id)); + auto res = Json_Helper::Parse(str); + + if (!res) { + return nullptr; + } else { + _json_cache[id] = *res; + return &_json_cache[id]; + } +} +#endif + StringView Game_Strings::Asg(Str_Params params, StringView string) { Set(params, string); return Get(params.string_id); diff --git a/src/game_strings.h b/src/game_strings.h index d630bc8ab5..dbee88cb3c 100644 --- a/src/game_strings.h +++ b/src/game_strings.h @@ -15,7 +15,8 @@ * along with EasyRPG Player. If not, see . */ - // Headers +// Headers +#include "system.h" #include #include #include @@ -27,6 +28,10 @@ #include "player.h" #include "string_view.h" +#ifdef HAVE_NLOHMANN_JSON +#include +#endif + /** * Game_Strings class. */ @@ -59,6 +64,10 @@ class Game_Strings { StringView GetWithMode(StringView str_data, int mode, int arg, const Game_Variables& variables) const; StringView GetWithModeAndPos(StringView str_data, int mode, int arg, int* pos, const Game_Variables& variables); +#ifdef HAVE_NLOHMANN_JSON + nlohmann::json* ParseJson(int id); +#endif + StringView Asg(Str_Params params, StringView string); StringView Cat(Str_Params params, StringView string); int ToNum(Str_Params params, int var_id, Game_Variables& variables); @@ -85,8 +94,11 @@ class Game_Strings { Strings_t _strings; mutable int _warnings = max_warnings; -}; +#ifdef HAVE_NLOHMANN_JSON + std::unordered_map _json_cache; +#endif +}; inline void Game_Strings::Set(Str_Params params, StringView string) { if (params.string_id <= 0) { @@ -108,10 +120,18 @@ inline void Game_Strings::Set(Str_Params params, StringView string) { } else { it->second = ins_string; } + +#ifdef HAVE_NLOHMANN_JSON + _json_cache.erase(params.string_id); +#endif } inline void Game_Strings::SetData(Strings_t s) { _strings = std::move(s); + +#ifdef HAVE_NLOHMANN_JSON + _json_cache.clear(); +#endif } inline void Game_Strings::SetData(const std::vector& s) { @@ -122,6 +142,10 @@ inline void Game_Strings::SetData(const std::vector& s) { } ++i; } + +#ifdef HAVE_NLOHMANN_JSON + _json_cache.clear(); +#endif } inline const Game_Strings::Strings_t& Game_Strings::GetData() const { diff --git a/src/json_helper.cpp b/src/json_helper.cpp new file mode 100644 index 0000000000..7205f361a1 --- /dev/null +++ b/src/json_helper.cpp @@ -0,0 +1,100 @@ +/* + * 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 . + */ + +#include "json_helper.h" + +#ifdef HAVE_NLOHMANN_JSON + +#include "output.h" +#include +#include +#include +#include "string_view.h" + +using json = nlohmann::json; + +namespace { + std::string GetValueAsString(const json& json_obj) { + std::string result; + + if (json_obj.is_string()) { + result = json_obj.get(); + } + else if (json_obj.is_number_integer()) { + result = std::to_string(json_obj.get()); + } + else if (json_obj.is_number_float()) { + result = std::to_string(json_obj.get()); + } + else if (json_obj.is_boolean()) { + result = json_obj.get() ? "true" : "false"; + } + else { + result = json_obj.dump(); + } + + return result; + } +} + +namespace Json_Helper { + std::optional Parse(std::string_view json_data) { + json json_obj = json::parse(json_data, nullptr, false); + if (json_obj.is_discarded()) { + return {}; + } + + return json_obj; + } + + std::optional GetValue(nlohmann::json& json_obj, std::string_view json_path) { + json::json_pointer ptr((std::string(json_path))); + + if (ptr.empty()) { + Output::Warning("JSON: Bad json pointer {}", json_path); + return {}; + } + + if (!json_obj.contains(ptr)) { + return ""; + } + + return GetValueAsString(json_obj[ptr]); + } + + std::string SetValue(nlohmann::json& json_obj, std::string_view json_path, std::string_view value) { + json::json_pointer ptr((std::string(json_path))); + + if (ptr.empty()) { + Output::Warning("JSON: Bad json pointer {}", json_path); + return {}; + } + + json obj_value = json::parse(value, nullptr, false); + + if (obj_value.is_discarded()) { + // If parsing fails, treat it as a string value + json_obj[ptr] = std::string(value); + } else { + json_obj[ptr] = obj_value; + } + + return json_obj.dump(); + } +} + +#endif // HAVE_NLOHMANN_JSON diff --git a/src/json_helper.h b/src/json_helper.h new file mode 100644 index 0000000000..f9cd117e12 --- /dev/null +++ b/src/json_helper.h @@ -0,0 +1,36 @@ +/* + * 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 JSON_HELPER_H +#define JSON_HELPER_H + +#include "system.h" + +#ifdef HAVE_NLOHMANN_JSON + +#include +#include +#include + +namespace Json_Helper { + std::optional Parse(std::string_view json_data); + std::optional GetValue(nlohmann::json& json_obj, std::string_view json_path); + std::string SetValue(nlohmann::json& json_obj, std::string_view json_path, std::string_view value); +} + +#endif // HAVE_NLOHMANN_JSON +#endif // JSON_HELPER_H