From 337ac233544868d75d608e45be7836fe821e7b1c Mon Sep 17 00:00:00 2001 From: Ghabry Date: Mon, 28 Sep 2020 18:23:09 +0200 Subject: [PATCH] DynRPG TextPlugin: Update to latest Player code --- src/dynrpg.cpp | 7 - src/dynrpg.h | 25 ++- src/dynrpg_textplugin.cpp | 424 +++++++++++++------------------------- src/dynrpg_textplugin.h | 14 +- src/game_message.cpp | 75 ++++++- src/game_message.h | 12 +- src/game_pictures.h | 4 +- src/pending_message.cpp | 77 ++++--- src/pending_message.h | 11 +- src/scene_map.cpp | 1 - src/sprite_picture.cpp | 4 +- 11 files changed, 298 insertions(+), 356 deletions(-) diff --git a/src/dynrpg.cpp b/src/dynrpg.cpp index d2429c706cd..b2dc0c06593 100644 --- a/src/dynrpg.cpp +++ b/src/dynrpg.cpp @@ -25,18 +25,12 @@ #include "player.h" #include "dynrpg_easyrpg.h" - -#include -#include - #include "dynrpg_textplugin.h" #include #include #include -#include "dynrpg_textplugin.h" - enum DynRpg_ParseMode { ParseMode_Function, ParseMode_WaitForComma, @@ -191,7 +185,6 @@ static std::string ParseToken(const std::string& token, const std::string& funct void create_all_plugins() { plugins.emplace_back(new DynRpg::EasyRpgPlugin()); - plugins.emplace_back(new DynRpg::TextPlugin()); for (auto& plugin : plugins) { diff --git a/src/dynrpg.h b/src/dynrpg.h index 5e14ea05ee2..80f809ecd0e 100644 --- a/src/dynrpg.h +++ b/src/dynrpg.h @@ -42,14 +42,14 @@ using dynfunc = bool(*)(dyn_arg_list); namespace DynRpg { namespace detail { template - inline bool parse_arg(StringView, dyn_arg_list, const int, T& value, bool& parse_okay) { + inline bool parse_arg(StringView, dyn_arg_list, const int, T&, bool&, bool) { static_assert(sizeof(T) == -1, "Only parsing int, float and std::string supported"); } // FIXME: Extracting floats that are followed by chars behaviour varies depending on the C++ library // see https://bugs.llvm.org/show_bug.cgi?id=17782 template <> - inline bool parse_arg(StringView func_name, dyn_arg_list args, const int i, float& value, bool& parse_okay) { + inline bool parse_arg(StringView func_name, dyn_arg_list args, const int i, float& value, bool& parse_okay, bool warn) { if (!parse_okay) return false; value = 0.0; if (args[i].empty()) { @@ -61,14 +61,15 @@ namespace DynRpg { iss >> value; parse_okay = !iss.fail(); if (!parse_okay) { - Output::Warning("{}: Arg {} ({}) is not numeric", func_name, i, args[i]); + if (warn) + Output::Warning("{}: Arg {} ({}) is not numeric", func_name, i, args[i]); parse_okay = false; } return parse_okay; } template <> - inline bool parse_arg(StringView func_name, dyn_arg_list args, const int i, int& value, bool& parse_okay) { + inline bool parse_arg(StringView func_name, dyn_arg_list args, const int i, int& value, bool& parse_okay, bool warn) { if (!parse_okay) return false; value = 0; if (args[i].empty()) { @@ -79,14 +80,15 @@ namespace DynRpg { iss >> value; parse_okay = !iss.fail(); if (!parse_okay) { - Output::Warning("{}: Arg {} ({}) is not an integer", func_name, i, args[i]); + if (warn) + Output::Warning("{}: Arg {} ({}) is not an integer", func_name, i, args[i]); parse_okay = false; } return parse_okay; } template <> - inline bool parse_arg(StringView, dyn_arg_list args, const int i, std::string& value, bool& parse_okay) { + inline bool parse_arg(StringView, dyn_arg_list args, const int i, std::string& value, bool& parse_okay, bool) { if (!parse_okay) return false; value = args[i]; parse_okay = true; @@ -94,8 +96,8 @@ namespace DynRpg { } template - inline void parse_args(StringView func_name, dyn_arg_list in, Tuple& value, bool& parse_okay, std::index_sequence) { - (void)std::initializer_list{parse_arg(func_name, in, I, std::get(value), parse_okay)...}; + inline void parse_args(StringView func_name, dyn_arg_list in, Tuple& value, bool& parse_okay, bool warn, std::index_sequence) { + (void)std::initializer_list{parse_arg(func_name, in, I, std::get(value), parse_okay, warn)...}; } } @@ -111,16 +113,17 @@ namespace DynRpg { void Save(int slot); template - std::tuple ParseArgs(StringView func_name, dyn_arg_list args, bool* parse_okay = nullptr) { + std::tuple ParseArgs(StringView func_name, dyn_arg_list args, bool* parse_okay = nullptr, bool warn = true) { std::tuple t; if (args.size() < sizeof...(Targs)) { if (parse_okay) *parse_okay = false; - Output::Warning("{}: Got {} args (needs {} or more)", func_name, args.size(), sizeof...(Targs)); + if (warn) + Output::Warning("{}: Got {} args (needs {} or more)", func_name, args.size(), sizeof...(Targs)); return t; } bool okay = true; - detail::parse_args(func_name, args, t, okay, std::make_index_sequence{}); + detail::parse_args(func_name, args, t, okay, warn, std::make_index_sequence{}); if (parse_okay) *parse_okay = okay; return t; diff --git a/src/dynrpg_textplugin.cpp b/src/dynrpg_textplugin.cpp index 5279a12f48e..f1c40c9da6b 100644 --- a/src/dynrpg_textplugin.cpp +++ b/src/dynrpg_textplugin.cpp @@ -17,21 +17,20 @@ // Headers #include +#include +#include #include "dynrpg_textplugin.h" #include "baseui.h" #include "bitmap.h" #include "drawable.h" #include "drawable_mgr.h" -#include "game_actors.h" #include "game_map.h" -#include "game_party.h" +#include "game_message.h" #include "game_pictures.h" -#include "game_screen.h" #include "game_variables.h" -#include "graphics.h" #include "main_data.h" -#include "player.h" +#include "pending_message.h" class DynRpgText; @@ -103,26 +102,36 @@ class DynRpgText : public Drawable { return; } - const Sprite* sprite = Main_Data::game_pictures->GetPicture(pic_id).GetSprite(); + const Game_Pictures::Picture* pic = Main_Data::game_pictures->GetPicturePtr(pic_id); + if (!pic) { + return; + } + const Sprite_Picture* sprite = pic->sprite; if (!sprite) { return; } + // For unknown reasons the official plugin has an y-offset of 2 if (fixed) { - dst.Blit(x - Game_Map::GetDisplayX() / 16, y - Game_Map::GetDisplayY() / 16, *bitmap, bitmap->GetRect(), sprite->GetOpacity()); + dst.Blit(x - Game_Map::GetDisplayX() / 16, y - Game_Map::GetDisplayY() / 16 + 2, *bitmap, bitmap->GetRect(), sprite->GetOpacity()); } else { - dst.Blit(x, y, *bitmap, bitmap->GetRect(), sprite->GetOpacity()); + dst.Blit(x, y + 2, *bitmap, bitmap->GetRect(), sprite->GetOpacity()); } }; void Update() { - const Sprite *sprite = Main_Data::game_pictures->GetPicture(pic_id).GetSprite(); + const Game_Pictures::Picture* pic = Main_Data::game_pictures->GetPicturePtr(pic_id); + if (!pic) { + return; + } + const Sprite_Picture* sprite = pic->sprite; + if (!sprite) { + return; + } - if (sprite) { - if (z != sprite->GetZ()) { - z = sprite->GetZ() + 1; - SetZ(z); - } + if (z != sprite->GetZ()) { + z = sprite->GetZ() + 1; + SetZ(z); } } @@ -154,198 +163,57 @@ class DynRpgText : public Drawable { return data; } - static int ParseParameter(bool& is_valid, std::u32string::iterator& text_index, std::u32string::iterator& end) { - ++text_index; - - if (text_index == end || - *text_index != '[') { - --text_index; - is_valid = false; - return 0; - } - - ++text_index; // Skip the [ - - bool null_at_start = false; - std::stringstream ss; - for (;;) { - if (text_index == end) { - break; - } else if (*text_index == '\n') { - --text_index; - break; - } - else if (*text_index == '0') { - // Truncate 0 at the start - if (!ss.str().empty()) { - ss << "0"; - } else { - null_at_start = true; - } - } - else if (*text_index >= '1' && - *text_index <= '9') { - ss << std::string(text_index, std::next(text_index)); - } else if (*text_index == ']') { - break; - } else { - // End of number - // Search for ] or line break - while (text_index != end) { - if (*text_index == '\n') { - --text_index; - break; - } else if (*text_index == ']') { - break; - } - ++text_index; - } - break; - } - ++text_index; - } + static DynRpgText* GetTextHandle(const std::string& id, bool silent = false) { + PendingMessage pm; + pm.PushLine(id, DynRpgText::CommandCodeInserter); + std::string new_id = pm.GetLines().front(); - if (ss.str().empty()) { - if (null_at_start) { - ss << "0"; - } else { - is_valid = false; - return 0; + auto it = graphics.find(new_id); + if (it == graphics.end()) { + if (!silent) { + Output::Warning("No text with ID %s found", new_id.c_str()); } + return nullptr; } - int num; - ss >> num; - is_valid = true; - return num; - } - - static std::string ParseCommandCode(bool& success, std::u32string::iterator& text_index, std::u32string::iterator& end) { - int parameter; - bool is_valid; - uint32_t cmd_char = *text_index; - success = true; - - switch (cmd_char) { - case 'n': - case 'N': - // Output Hero name - parameter = ParseParameter(is_valid, text_index, end); - if (is_valid) { - Game_Actor* actor = NULL; - if (parameter == 0) { - // Party hero - actor = Main_Data::game_party->GetActors()[0]; - } else { - actor = Game_Actors::GetActor(parameter); - } - if (actor != NULL) { - return actor->GetName(); - } - } - break; - case 'v': - case 'V': - // Show Variable value - parameter = ParseParameter(is_valid, text_index, end); - if (is_valid && Main_Data::game_variables->IsValid(parameter)) { - std::stringstream ss; - ss << Main_Data::game_variables->Get(parameter); - return ss.str(); - } else { - // Invalid Var is always 0 - return "0"; - } - case 'i': - parameter = ParseParameter(is_valid, text_index, end); - if (is_valid && parameter > 0 && parameter <= Data::items.size()) { - return Data::items[parameter - 1].name; - } - return ""; - case 'I': - parameter = ParseParameter(is_valid, text_index, end); - if (is_valid && parameter > 0 && parameter <= Data::items.size()) { - return Data::items[parameter - 1].description; - } - return ""; - case 't': - parameter = ParseParameter(is_valid, text_index, end); - if (is_valid && parameter > 0 && parameter <= Data::skills.size()) { - return Data::skills[parameter - 1].name; - } - return ""; - case 'T': - parameter = ParseParameter(is_valid, text_index, end); - if (is_valid && parameter > 0 && parameter <= Data::skills.size()) { - return Data::skills[parameter - 1].description; - } - return ""; - case 'x': - case 'X': - // Take text of ID referenced by X (if exists) TODO - { - - } - return ""; - default:; - // When this happens text_index was not on a \ during calling - } - success = false; - return ""; + return (*it).second.get(); } - static std::string Substitute(const std::string& text) { - std::u32string::iterator text_index, end; - std::u32string utext; - - utext = Utils::DecodeUTF32(text); - text_index = utext.end(); - end = utext.end(); - - uint32_t escape_char = Utils::DecodeUTF32(Player::escape_symbol).front(); - - if (!utext.empty()) { - // Move on first valid char - --text_index; - - // Apply commands that insert text - while (std::distance(text_index, utext.begin()) <= -1) { - switch (tolower(*text_index--)) { - case 'n': - case 'v': - case 'i': - case 't': - case 'x': - { - if (*text_index != escape_char) { - continue; - } - ++text_index; - - auto start_code = text_index - 1; - bool success; - std::u32string command_result = Utils::DecodeUTF32(ParseCommandCode(success, text_index, end)); - if (!success) { - text_index = start_code - 2; - continue; - } - utext.replace(start_code, text_index + 1, command_result); - // Start from the beginning, the inserted text might add new commands - text_index = utext.end(); - - // Move on first valid char - --text_index; - - break; - } - default: - break; - } + static std::string CommandCodeInserter(char ch, const char** iter, const char* end, uint32_t escape_char) { + if (ch == 'N' || ch == 'n') { + return PendingMessage::DefaultCommandInserter('n', iter, end, escape_char); + } else if (ch == 'V' || ch == 'v') { + return PendingMessage::DefaultCommandInserter('v', iter, end, escape_char); + } else if (ch == 'I' || ch == 'i') { + auto parse_ret = Game_Message::ParseParamImpl('I', 'i', *iter, end, escape_char, true); + *iter = parse_ret.next; + int value = parse_ret.value; + const auto* item = lcf::ReaderUtil::GetElement(lcf::Data::items, value); + if (!item) { + Output::Warning("Invalid Item Id {} in DynTextPlugin text", value); + } else{ + return ToString(ch == 'i' ? item->name : item->description); } + } else if (ch == 'T' || ch == 't') { + auto parse_ret = Game_Message::ParseParamImpl('I', 'i', *iter, end, escape_char, true); + *iter = parse_ret.next; + int value = parse_ret.value; + const auto* skill = lcf::ReaderUtil::GetElement(lcf::Data::skills, value); + if (!skill) { + Output::Warning("Invalid Item Id {} in DynTextPlugin text", value); + } else{ + return ToString(ch == 't' ? skill->name : skill->description); + } + } else if (ch == 'x' || ch == 'X') { + auto parse_ret = Game_Message::ParseStringParamImpl('X', 'x', *iter, end, escape_char, true); + *iter = parse_ret.next; + std::string value = parse_ret.value; + auto* handle = GetTextHandle(value); + if (handle) + return handle->texts[0]; } - - return Utils::EncodeUTF(utext); - } + return ""; + }; private: void Refresh() { @@ -360,7 +228,9 @@ class DynRpgText : public Drawable { const FontRef& font = Font::Default(); for (auto& t : texts) { - t = Substitute(t); + PendingMessage pm; + pm.PushLine(t, CommandCodeInserter); + t = pm.GetLines().front(); Rect r = font->GetSize(t); width = std::max(width, r.width); @@ -386,100 +256,95 @@ class DynRpgText : public Drawable { bool fixed = false; }; -DynRpgText* get_text(const std::string& id, bool silent = false) { - std::string new_id = DynRpgText::Substitute(id); +static bool WriteText(dyn_arg_list args) { + auto func = "write_text"; + bool okay; + std::string id, text; + int x, y; - auto it = graphics.find(new_id); - if (it == graphics.end()) { - if (!silent) { - Output::Warning("No text with ID %s found", new_id.c_str()); - } - return nullptr; - } - - return (*it).second.get(); -} - -static bool WriteText(const dyn_arg_list& args) { - DYNRPG_FUNCTION("write_text") - - DYNRPG_CHECK_ARG_LENGTH(4); - - DYNRPG_GET_STR_ARG(0, id) - DYNRPG_GET_INT_ARG(1, x) - DYNRPG_GET_INT_ARG(2, y) - DYNRPG_GET_STR_ARG(3, text) + std::tie(id, x, y, text) = DynRpg::ParseArgs(func, args, &okay); + if (!okay) + return true; - const std::string new_id = DynRpgText::Substitute(id); - graphics[new_id] = std::unique_ptr(new DynRpgText(1, x, y + 2, text)); + PendingMessage pm; + pm.PushLine(id, DynRpgText::CommandCodeInserter); + std::string new_id = pm.GetLines().front(); + graphics[new_id] = std::make_unique(1, x, y, text); if (args.size() > 4) { - DYNRPG_GET_STR_ARG(4, fixed) + std::string fixed = std::get<0>(DynRpg::ParseArgs(func, args.subspan(4), &okay)); + if (!okay) + return true; graphics[new_id]->SetFixed(fixed == "fixed"); } if (args.size() > 5) { - DYNRPG_GET_INT_ARG(5, color) + int color = std::get<0>(DynRpg::ParseArgs(func, args.subspan(5), &okay)); + if (!okay) + return true; graphics[new_id]->SetColor(color); } if (args.size() > 6) { - DYNRPG_GET_INT_ARG(6, pic_id) + int pic_id = std::get<0>(DynRpg::ParseArgs(func, args.subspan(6), &okay)); + if (!okay) + return true; graphics[new_id]->SetPictureId(pic_id); } return true; } -static bool AppendLine(const dyn_arg_list& args) { - DYNRPG_FUNCTION("append_line") - - DYNRPG_CHECK_ARG_LENGTH(2); +static bool AppendLine(dyn_arg_list args) { + auto func = "append_line"; + bool okay; + std::string id, text; - DYNRPG_GET_STR_ARG(0, id) - DYNRPG_GET_STR_ARG(1, text) - - DynRpgText* handle = get_text(id); + std::tie(id, text) = DynRpg::ParseArgs(func, args, &okay); + if (!okay) + return true; + DynRpgText* handle = DynRpgText::GetTextHandle(id); if (!handle) { return true; } handle->AddLine(text); - return true; } -static bool AppendText(const dyn_arg_list& args) { - DYNRPG_FUNCTION("append_text") - - DYNRPG_CHECK_ARG_LENGTH(2); +static bool AppendText(dyn_arg_list args) { + auto func = "append_line"; + bool okay; + std::string id, text; - DYNRPG_GET_STR_ARG(0, id) - DYNRPG_GET_STR_ARG(1, text) - - DynRpgText* handle = get_text(id); + std::tie(id, text) = DynRpg::ParseArgs(func, args, &okay); + if (!okay) + return true; + DynRpgText* handle = DynRpgText::GetTextHandle(id); if (!handle) { return true; } handle->AddText(text); - return true; } -static bool ChangeText(const dyn_arg_list& args) { - DYNRPG_FUNCTION("change_text") - - DYNRPG_CHECK_ARG_LENGTH(3); +static bool ChangeText(dyn_arg_list args) { + auto func = "change_text"; + bool okay; + std::string id, text; + int color; - DYNRPG_GET_STR_ARG(0, id) - DYNRPG_GET_STR_ARG(1, text) - DYNRPG_GET_INT_ARG(2, color) + std::tie(id, text, std::ignore) = DynRpg::ParseArgs(func, args, &okay); + if (!okay) + return true; - DynRpgText* handle = get_text(id); + // Color can be a string (usually "end") or a number, don't warn about it + std::tie(color) = DynRpg::ParseArgs(func, args.subspan(2), nullptr, false); + DynRpgText* handle = DynRpgText::GetTextHandle(id); if (!handle) { return true; } @@ -487,64 +352,51 @@ static bool ChangeText(const dyn_arg_list& args) { handle->ClearText(); handle->SetColor(color); handle->AddText(text); - return true; } -static bool ChangePosition(const dyn_arg_list& args) { - DYNRPG_FUNCTION("change_position") - - DYNRPG_CHECK_ARG_LENGTH(3); - - DYNRPG_GET_STR_ARG(0, id) - DYNRPG_GET_INT_ARG(1, x) - DYNRPG_GET_INT_ARG(2, y) +static bool ChangePosition(dyn_arg_list args) { + auto func = "change_position"; + bool okay; + std::string id; + int x, y; - DynRpgText* handle = get_text(id); + std::tie(id, x, y) = DynRpg::ParseArgs(func, args, &okay); + if (!okay) + return true; + DynRpgText* handle = DynRpgText::GetTextHandle(id); if (!handle) { return true; } - // Offset is somehow wrong compared to RPG_RT - handle->SetPosition(x, y + 2); - + handle->SetPosition(x, y); return true; } -static bool RemoveText(const dyn_arg_list& args) { - DYNRPG_FUNCTION("remove_text") - - DYNRPG_CHECK_ARG_LENGTH(2); - - DYNRPG_GET_STR_ARG(0, id) - DYNRPG_GET_STR_ARG(1, nothing) +static bool RemoveText(dyn_arg_list args) { + auto func = "remove_text"; + bool okay; + std::string id; - DynRpgText* handle = get_text(id, true); + std::tie(id) = DynRpg::ParseArgs(func, args, &okay); + if (!okay) + return true; + DynRpgText* handle = DynRpgText::GetTextHandle(id, true); if (!handle) { return true; } handle->ClearText(); - return true; } -static bool RemoveAll(const dyn_arg_list& args) { - DYNRPG_FUNCTION("remove_all") - - DYNRPG_CHECK_ARG_LENGTH(0); - +static bool RemoveAll(dyn_arg_list) { graphics.clear(); - return true; } -std::string DynRpg::TextPlugin::GetIdentifier() { - return "DynTextPlugin"; -} - void DynRpg::TextPlugin::RegisterFunctions() { DynRpg::RegisterFunction("write_text", WriteText); DynRpg::RegisterFunction("append_line", AppendLine); @@ -600,7 +452,7 @@ void DynRpg::TextPlugin::Load(const std::vector& in_buffer) { color = atoi(t.c_str()); break; case 4: - // transparency, but isn't that from the picture? + // ignore transparency, the picture defines this break; case 5: fixed = t == "1"; @@ -620,7 +472,7 @@ void DynRpg::TextPlugin::Load(const std::vector& in_buffer) { if (counter == 8) { counter = 0; - graphics[id] = std::unique_ptr(new DynRpgText(pic_id, x, y + 2, texts)); + graphics[id] = std::make_unique(pic_id, x, y, texts); texts.clear(); graphics[id]->SetColor(color); graphics[id]->SetFixed(fixed); diff --git a/src/dynrpg_textplugin.h b/src/dynrpg_textplugin.h index de3fd3ca7b2..14c2767218f 100644 --- a/src/dynrpg_textplugin.h +++ b/src/dynrpg_textplugin.h @@ -15,21 +15,21 @@ * along with EasyRPG Player. If not, see . */ -#ifndef _EASYRPG_DYNRPG_TEXTPLUGIN_H_ -#define _EASYRPG_DYNRPG_TEXTPLUGIN_H_ +#ifndef EP_DYNRPG_TEXTPLUGIN_H +#define EP_DYNRPG_TEXTPLUGIN_H #include "dynrpg.h" namespace DynRpg { class TextPlugin : public DynRpgPlugin { public: + TextPlugin() : DynRpgPlugin("DynTextPlugin") {} ~TextPlugin(); - std::string GetIdentifier(); - void RegisterFunctions(); - void Update(); - void Load(const std::vector&); - std::vector Save(); + void RegisterFunctions() override; + void Update() override; + void Load(const std::vector&) override; + std::vector Save() override; }; } diff --git a/src/game_message.cpp b/src/game_message.cpp index f55bdf7c64d..f73abecef53 100644 --- a/src/game_message.cpp +++ b/src/game_message.cpp @@ -150,10 +150,9 @@ bool Game_Message::CanShowMessage(bool foreground) { return window ? window->GetAllowNextMessage(foreground) : false; } - -static Game_Message::ParseParamResult ParseParamImpl( - const char upper, - const char lower, +Game_Message::ParseParamResult Game_Message::ParseParamImpl( + char upper, + char lower, const char* iter, const char* end, uint32_t escape_char, @@ -250,6 +249,72 @@ static Game_Message::ParseParamResult ParseParamImpl( return { iter, value }; } +Game_Message::ParseParamStringResult Game_Message::ParseStringParamImpl( + char upper, + char lower, + const char* iter, + const char* end, + uint32_t escape_char, + bool skip_prefix, + int max_recursion) +{ + if (!skip_prefix) { + const auto begin = iter; + if (iter == end) { + return { begin, "" }; + } + auto ret = Utils::UTF8Next(iter, end); + // Invalid commands + if (ret.ch != escape_char) { + return { begin, "" }; + } + iter = ret.next; + if (iter == end || (*iter != upper && *iter != lower)) { + return { begin, "" }; + } + ++iter; + } + + // If no bracket, return empty string + if (iter == end || *iter != '[') { + return { iter, "" }; + } + + std::string value; + ++iter; + + while (iter != end && *iter != ']') { + // Fast inline isdigit() + value += *iter; + + if (max_recursion > 0) { + auto ret = Utils::UTF8Next(iter, end); + auto ch = ret.ch; + iter = ret.next; + + // Recursive variable case. + if (ch == escape_char) { + if (iter != end && (*iter == 'V' || *iter == 'v')) { + ++iter; + + auto ret = ParseParamImpl('V', 'v', iter, end, escape_char, true, max_recursion - 1); + iter = ret.next; + int var_val = Main_Data::game_variables->Get(ret.value); + + value += std::to_string(var_val); + continue; + } + } + } + } + + if (iter != end) { + ++iter; + } + + return { iter, value }; +} + Game_Message::ParseParamResult Game_Message::ParseVariable(const char* iter, const char* end, uint32_t escape_char, bool skip_prefix, int max_recursion) { return ParseParamImpl('V', 'v', iter, end, escape_char, skip_prefix, max_recursion - 1); } @@ -265,5 +330,3 @@ Game_Message::ParseParamResult Game_Message::ParseSpeed(const char* iter, const Game_Message::ParseParamResult Game_Message::ParseActor(const char* iter, const char* end, uint32_t escape_char, bool skip_prefix, int max_recursion) { return ParseParamImpl('N', 'n', iter, end, escape_char, skip_prefix, max_recursion); } - - diff --git a/src/game_message.h b/src/game_message.h index 769ae931338..532d828052c 100644 --- a/src/game_message.h +++ b/src/game_message.h @@ -108,6 +108,14 @@ namespace Game_Message { int value = 0; }; + /** Struct returned by parameter parsing methods */ + struct ParseParamStringResult { + /** iterator to the next character after parsed content */ + const char* next = nullptr; + /** value that was parsed */ + std::string value; + }; + /** Parse a \v[] variable string * * @param iter start of utf8 string @@ -155,7 +163,9 @@ namespace Game_Message { * @return \refer ParseParamResult */ ParseParamResult ParseActor(const char* iter, const char* end, uint32_t escape_char, bool skip_prefix = false, int max_recursion = default_max_recursion); -} + Game_Message::ParseParamResult ParseParamImpl(char upper, char lower, const char* iter, const char* end, uint32_t escape_char, bool skip_prefix = false, int max_recursion = default_max_recursion); + Game_Message::ParseParamStringResult ParseStringParamImpl(char upper, char lower, const char* iter, const char* end, uint32_t escape_char, bool skip_prefix = false, int max_recursion = default_max_recursion); +} #endif diff --git a/src/game_pictures.h b/src/game_pictures.h index 0889a5f2d0f..4bab73bd4ea 100644 --- a/src/game_pictures.h +++ b/src/game_pictures.h @@ -86,8 +86,8 @@ class Game_Pictures { void OnMapScrolled(int dx, int dy); struct Picture { - Picture(int id) { data.ID = id; } - Picture(lcf::rpg::SavePicture data); + explicit Picture(int id) { data.ID = id; } + explicit Picture(lcf::rpg::SavePicture data); Sprite_Picture* sprite = nullptr; lcf::rpg::SavePicture data; diff --git a/src/pending_message.cpp b/src/pending_message.cpp index 9541ef4de56..79c39f1468d 100644 --- a/src/pending_message.cpp +++ b/src/pending_message.cpp @@ -1,3 +1,20 @@ +/* + * 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 "pending_message.h" #include "game_variables.h" #include "game_actors.h" @@ -17,26 +34,26 @@ static void RemoveControlChars(std::string& s) { s.erase(iter, s.end()); } -int PendingMessage::PushLineImpl(std::string msg) { +int PendingMessage::PushLineImpl(std::string msg, CommandInserter cmd_fn) { RemoveControlChars(msg); - msg = ApplyTextInsertingCommands(std::move(msg), Player::escape_char); + msg = ApplyTextInsertingCommands(std::move(msg), Player::escape_char, cmd_fn); texts.push_back(std::move(msg)); return texts.size(); } -int PendingMessage::PushLine(std::string msg) { +int PendingMessage::PushLine(std::string msg, CommandInserter cmd_fn) { assert(!HasChoices()); assert(!HasNumberInput()); - return PushLineImpl(std::move(msg)); + return PushLineImpl(std::move(msg), cmd_fn); } -int PendingMessage::PushChoice(std::string msg, bool enabled) { +int PendingMessage::PushChoice(std::string msg, bool enabled, CommandInserter cmd_fn) { assert(!HasNumberInput()); if (!HasChoices()) { choice_start = NumLines(); } choice_enabled[GetNumChoices()] = enabled; - return PushLineImpl(std::move(msg)); + return PushLineImpl(std::move(msg), cmd_fn); } int PendingMessage::PushNumInput(int variable_id, int num_digits) { @@ -69,7 +86,7 @@ void PendingMessage::SetChoiceResetColors(bool value) { choice_reset_color = value; } -std::string PendingMessage::ApplyTextInsertingCommands(std::string input, uint32_t escape_char) { +std::string PendingMessage::ApplyTextInsertingCommands(std::string input, uint32_t escape_char, CommandInserter cmd_fn) { if (input.empty()) { return input; } @@ -103,29 +120,8 @@ std::string PendingMessage::ApplyTextInsertingCommands(std::string input, uint32 const auto ch = *iter; ++iter; - if (ch == 'N' || ch == 'n') { - auto parse_ret = Game_Message::ParseActor(iter, end, escape_char, true); - iter = parse_ret.next; - int value = parse_ret.value; - - const auto* actor = Main_Data::game_actors->GetActor(value); - if (!actor) { - Output::Warning("Invalid Actor Id {} in message text", value); - } else{ - output.append(ToString(actor->GetName())); - } - - start_copy = iter; - } else if (ch == 'V' || ch == 'v') { - auto parse_ret = Game_Message::ParseVariable(iter, end, escape_char, true); - iter = parse_ret.next; - int value = parse_ret.value; - - int variable_value = Main_Data::game_variables->Get(value); - output.append(std::to_string(variable_value)); - - start_copy = iter; - } + output.append(cmd_fn(ch, &iter, end, escape_char)); + start_copy = iter; } if (start_copy == input.data()) { @@ -138,4 +134,25 @@ std::string PendingMessage::ApplyTextInsertingCommands(std::string input, uint32 return output; } +std::string PendingMessage::DefaultCommandInserter(char ch, const char** iter, const char* end, uint32_t escape_char) { + if (ch == 'N' || ch == 'n') { + auto parse_ret = Game_Message::ParseActor(*iter, end, escape_char, true); + *iter = parse_ret.next; + int value = parse_ret.value; + + const auto* actor = Main_Data::game_actors->GetActor(value); + if (!actor) { + Output::Warning("Invalid Actor Id {} in message text", value); + } else { + return ToString(actor->GetName()); + } + } else if (ch == 'V' || ch == 'v') { + auto parse_ret = Game_Message::ParseVariable(*iter, end, escape_char, true); + *iter = parse_ret.next; + int value = parse_ret.value; + int variable_value = Main_Data::game_variables->Get(value); + return std::to_string(variable_value); + } + return ""; +}; diff --git a/src/pending_message.h b/src/pending_message.h index f58990c5dfd..ba0a2db4d25 100644 --- a/src/pending_message.h +++ b/src/pending_message.h @@ -26,9 +26,11 @@ class PendingMessage { public: using ChoiceContinuation = std::function; + using CommandInserter = std::function; + static std::string DefaultCommandInserter(char ch, const char** iter, const char* end, uint32_t escape_char); - int PushLine(std::string msg); - int PushChoice(std::string msg, bool enabled = true); + int PushLine(std::string msg, CommandInserter cmd_fn = DefaultCommandInserter); + int PushChoice(std::string msg, bool enabled = true, CommandInserter cmd_fn = DefaultCommandInserter); int PushNumInput(int variable_id, int num_digits); void PushPageEnd(); @@ -63,10 +65,11 @@ class PendingMessage { void SetIsEventMessage(bool value) { is_event_message = value; } bool IsEventMessage() const { return is_event_message; } + private: - int PushLineImpl(std::string msg); + int PushLineImpl(std::string msg, CommandInserter cmd_fn); - std::string ApplyTextInsertingCommands(std::string input, uint32_t escape_char); + std::string ApplyTextInsertingCommands(std::string input, uint32_t escape_char, CommandInserter cmd_fn); private: ChoiceContinuation choice_continuation; diff --git a/src/scene_map.cpp b/src/scene_map.cpp index 11da5a11cf6..651572a0dac 100644 --- a/src/scene_map.cpp +++ b/src/scene_map.cpp @@ -231,7 +231,6 @@ void Scene_Map::Update() { return; } - DynRpg::Update(); MapUpdateAsyncContext actx; UpdateStage1(actx); } diff --git a/src/sprite_picture.cpp b/src/sprite_picture.cpp index 95a656aedb0..8e67f44b8c4 100644 --- a/src/sprite_picture.cpp +++ b/src/sprite_picture.cpp @@ -57,7 +57,9 @@ void Sprite_Picture::OnPictureShow() { priority = Drawable::GetPriorityForMapLayer(pic.data.map_layer); } if (priority > 0) { - SetZ(priority + z_mask + pic_id); + // Small offset (10) to ensure there is space for graphics that are + // drawn at the image position (e.g. DynRPG Text Plugin) + SetZ(priority + z_mask + pic_id * 10); } } }