From bf7f526919da20c4e14850ca37b49a7f10f8fda8 Mon Sep 17 00:00:00 2001 From: Ghabry Date: Fri, 8 Nov 2024 21:03:51 +0100 Subject: [PATCH 1/8] Common Events: Add GetId() function Not used but does not hurt... --- src/game_commonevent.cpp | 4 ++++ src/game_commonevent.h | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/src/game_commonevent.cpp b/src/game_commonevent.cpp index 8a7d8d1682..1f74ffe7d1 100644 --- a/src/game_commonevent.cpp +++ b/src/game_commonevent.cpp @@ -63,6 +63,10 @@ AsyncOp Game_CommonEvent::Update(bool resume_async) { return {}; } +int Game_CommonEvent::GetId() const { + return common_event_id; +} + int Game_CommonEvent::GetIndex() const { return common_event_id; } diff --git a/src/game_commonevent.h b/src/game_commonevent.h index c009707543..f8069a3a0d 100644 --- a/src/game_commonevent.h +++ b/src/game_commonevent.h @@ -54,6 +54,13 @@ class Game_CommonEvent { */ AsyncOp Update(bool resume_async); + /** + * Gets common event ID. + * + * @return ID of the common event + */ + int GetId() const; + /** * Gets common event index. * From adbf2f454956f8962728dfaee024a1a41a435604 Mon Sep 17 00:00:00 2001 From: Ghabry Date: Fri, 8 Nov 2024 21:05:06 +0100 Subject: [PATCH 2/8] Conditional Branch: Add Maniac indirection for Items, Actors and Events --- src/game_interpreter.cpp | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index 98b9edef57..c40bc20915 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -3407,21 +3407,33 @@ bool Game_Interpreter::CommandConditionalBranch(lcf::rpg::EventCommand const& co result = (Main_Data::game_party->GetGold() <= com.parameters[1]); } break; - case 4: + case 4: { // Item + int item_id = com.parameters[1]; + + if (Player::IsPatchManiac()) { + item_id = ValueOrVariable(com.parameters[3], item_id); + } + if (com.parameters[2] == 0) { // Having - result = Main_Data::game_party->GetItemCount(com.parameters[1]) - + Main_Data::game_party->GetEquippedItemCount(com.parameters[1]) > 0; + result = Main_Data::game_party->GetItemCount(item_id) + + Main_Data::game_party->GetEquippedItemCount(item_id) > 0; } else { // Not having - result = Main_Data::game_party->GetItemCount(com.parameters[1]) - + Main_Data::game_party->GetEquippedItemCount(com.parameters[1]) == 0; + result = Main_Data::game_party->GetItemCount(item_id) + + Main_Data::game_party->GetEquippedItemCount(item_id) == 0; } break; + } case 5: // Hero actor_id = com.parameters[1]; + + if (Player::IsPatchManiac()) { + actor_id = ValueOrVariable(com.parameters[4], actor_id); + } + actor = Main_Data::game_actors->GetActor(actor_id); if (!actor) { @@ -3471,13 +3483,20 @@ bool Game_Interpreter::CommandConditionalBranch(lcf::rpg::EventCommand const& co ; } break; - case 6: + case 6: { // Orientation of char - character = GetCharacter(com.parameters[1]); + int chara_id = com.parameters[1]; + + if (Player::IsPatchManiac()) { + chara_id = ValueOrVariable(com.parameters[3], chara_id); + } + + character = GetCharacter(chara_id); if (character != NULL) { result = character->GetFacing() == com.parameters[2]; } break; + } case 7: { // Vehicle in use Game_Vehicle::Type vehicle_id = (Game_Vehicle::Type) (com.parameters[1] + 1); @@ -3574,7 +3593,7 @@ bool Game_Interpreter::CommandConditionalBranch(lcf::rpg::EventCommand const& co } break; case 15: - // Maniac: string comparison + // Maniac: String comparison if (Player::IsPatchManiac()) { int modes[] = { (com.parameters[1] ) & 15, //str_l mode: 0 = direct, 1 = indirect @@ -4271,9 +4290,8 @@ bool Game_Interpreter::CommandManiacShowStringPicture(lcf::rpg::EventCommand con // x03 -> indirect reference // for the displayed string, the id argument is in com.parameters[22] // here we are capturing all the delimiters, but currently only need to support reading the first one - int i = 0; std::vector delims; - auto components = Utils::Tokenize(com.string, [p = &delims, &i](char32_t ch) { + auto components = Utils::Tokenize(com.string, [p = &delims](char32_t ch) { if (ch == '\x01' || ch == '\x02' || ch == '\x03') { p->push_back(static_cast(ch)); return true; From 8cea78982f1ad555679776fc4f3bc9b42704c209 Mon Sep 17 00:00:00 2001 From: Ghabry Date: Fri, 8 Nov 2024 21:06:45 +0100 Subject: [PATCH 3/8] Maniac Expressions: Support InPlace-Operations They are disabled when EasyRpg-Mode is active as I consider this bad practice. Can result in hard-to-find bugs. --- src/maniac_patch.cpp | 290 ++++++++++++++++++++++++++++++++----------- 1 file changed, 217 insertions(+), 73 deletions(-) diff --git a/src/maniac_patch.cpp b/src/maniac_patch.cpp index 7619adc554..d307e66270 100644 --- a/src/maniac_patch.cpp +++ b/src/maniac_patch.cpp @@ -27,6 +27,7 @@ #include "game_variables.h" #include "main_data.h" #include "output.h" +#include "player.h" #include #include @@ -38,6 +39,7 @@ All array functions (Array, Range and Subscript): They could be implemented but are not very useful All Inplace functions: +These functions are disabled when EasyRpg Extensions are active. Inplace assigns to variables while the ControlVariables event command is executed. This violates how the command is supposed to work because more variables than the target variables can be set. */ @@ -117,7 +119,62 @@ namespace { }; } -int process(std::vector::iterator& it, std::vector::iterator end, const Game_BaseInterpreterContext& ip) { +struct ProcessAssignmentRet { + Op op = Op::Null; + int id = 0; + + int fetch() const { + switch (op) { + case Op::Var: + return Main_Data::game_variables->Get(id); + case Op::Switch: + return Main_Data::game_switches->Get(id); + case Op::VarIndirect: { + return Main_Data::game_variables->GetIndirect(id); + } + case Op::SwitchIndirect: { + int var = Main_Data::game_variables->GetIndirect(id); + return Main_Data::game_switches->Get(var); + } + default: + Output::Warning("Maniac: Expression assignment {} is not a lvalue", static_cast(op)); + return 0; + } + } + + int assign(int value) const { + if (Player::HasEasyRpgExtensions()) { + Output::Warning("Maniac: Inplace assignments are not allowed in expressions when running in EasyRpg Mode"); + return fetch(); + } + + switch (op) { + case Op::Var: + Game_Map::SetNeedRefreshForVarChange(id); + return Main_Data::game_variables->Set(id, value); + case Op::Switch: + Game_Map::SetNeedRefreshForSwitchChange(id); + return Main_Data::game_switches->Set(id, value > 0); + case Op::VarIndirect: { + int var = Main_Data::game_variables->GetIndirect(id); + Game_Map::SetNeedRefreshForVarChange(var); + return Main_Data::game_variables->Set(var, value); + } + case Op::SwitchIndirect: { + int var = Main_Data::game_variables->GetIndirect(id); + Game_Map::SetNeedRefreshForSwitchChange(var); + return Main_Data::game_switches->Set(var, value > 0); + } + default: + Output::Warning("Maniac: Expression assignment {} is not a lvalue", static_cast(op)); + return 0; + } + } +}; + +ProcessAssignmentRet ProcessAssignment(std::vector::iterator& it, std::vector::iterator end, const Game_BaseInterpreterContext& ip); + +int Process(std::vector::iterator& it, std::vector::iterator end, const Game_BaseInterpreterContext& ip) { int value = 0; int imm = 0; int imm2 = 0; @@ -166,108 +223,169 @@ int process(std::vector::iterator& it, std::vector::iterator e value = (value << 24) + (imm3 << 16) + (imm2 << 8) + imm; return value; case Op::Var: - imm = process(it, end, ip); + imm = Process(it, end, ip); return Main_Data::game_variables->Get(imm); case Op::Switch: - imm = process(it, end, ip); + imm = Process(it, end, ip); return Main_Data::game_switches->GetInt(imm); case Op::VarIndirect: - imm = process(it, end, ip); + imm = Process(it, end, ip); return Main_Data::game_variables->GetIndirect(imm); case Op::SwitchIndirect: - imm = process(it, end, ip); + imm = Process(it, end, ip); return Main_Data::game_switches->GetInt(Main_Data::game_variables->Get(imm)); case Op::Negate: - imm = process(it, end, ip); + imm = Process(it, end, ip); return -imm; case Op::Not: - imm = process(it, end, ip); + imm = Process(it, end, ip); return !imm ? 0 : 1; case Op::Flip: - imm = process(it, end, ip); + imm = Process(it, end, ip); return ~imm; + case Op::AssignInplace: { + auto ret = ProcessAssignment(it, end, ip); + imm2 = Process(it, end, ip); + return ret.assign(imm2); + } + case Op::AddInplace: { + auto ret = ProcessAssignment(it, end, ip); + imm2 = Process(it, end, ip); + return ret.assign(static_cast(Utils::Clamp(static_cast(ret.fetch()) + imm2, std::numeric_limits::min(), std::numeric_limits::max()))); + } + case Op::SubInplace: { + auto ret = ProcessAssignment(it, end, ip); + imm2 = Process(it, end, ip); + return ret.assign(static_cast(Utils::Clamp(static_cast(ret.fetch()) - imm2, std::numeric_limits::min(), std::numeric_limits::max()))); + } + case Op::MulInplace: { + auto ret = ProcessAssignment(it, end, ip); + imm2 = Process(it, end, ip); + return ret.assign(static_cast(Utils::Clamp(static_cast(ret.fetch()) * imm2, std::numeric_limits::min(), std::numeric_limits::max()))); + } + case Op::DivInplace: { + auto ret = ProcessAssignment(it, end, ip); + imm2 = Process(it, end, ip); + if (imm2 == 0) { + return ret.fetch(); + } + return ret.assign(ret.fetch() / imm2); + } + case Op::ModInplace: { + auto ret = ProcessAssignment(it, end, ip); + imm2 = Process(it, end, ip); + if (imm2 == 0) { + return ret.fetch(); + } + return ret.assign(ret.fetch() % imm2); + } + case Op::BitOrInplace: { + auto ret = ProcessAssignment(it, end, ip); + imm2 = Process(it, end, ip); + return ret.assign(ret.fetch() | imm2); + } + case Op::BitAndInplace: { + auto ret = ProcessAssignment(it, end, ip); + imm2 = Process(it, end, ip); + return ret.assign(ret.fetch() & imm2); + } + case Op::BitXorInplace: { + auto ret = ProcessAssignment(it, end, ip); + imm2 = Process(it, end, ip); + return ret.assign(ret.fetch() ^ imm2); + } + case Op::BitShiftLeftInplace: { + auto ret = ProcessAssignment(it, end, ip); + imm2 = Process(it, end, ip); + return ret.assign(ret.fetch() << imm2); + } + case Op::BitShiftRightInplace: { + auto ret = ProcessAssignment(it, end, ip); + imm2 = Process(it, end, ip); + return ret.assign(ret.fetch() >> imm2); + } case Op::Add: - imm = process(it, end, ip); - imm2 = process(it, end, ip); + imm = Process(it, end, ip); + imm2 = Process(it, end, ip); return static_cast(Utils::Clamp(static_cast(imm) + imm2, std::numeric_limits::min(), std::numeric_limits::max())); case Op::Sub: - imm = process(it, end, ip); - imm2 = process(it, end, ip); + imm = Process(it, end, ip); + imm2 = Process(it, end, ip); return static_cast(Utils::Clamp(static_cast(imm) - imm2, std::numeric_limits::min(), std::numeric_limits::max())); case Op::Mul: - imm = process(it, end, ip); - imm2 = process(it, end, ip); + imm = Process(it, end, ip); + imm2 = Process(it, end, ip); return static_cast(Utils::Clamp(static_cast(imm) * imm2, std::numeric_limits::min(), std::numeric_limits::max())); case Op::Div: - imm = process(it, end, ip); - imm2 = process(it, end, ip); + imm = Process(it, end, ip); + imm2 = Process(it, end, ip); if (imm2 == 0) { return imm; } return imm / imm2; case Op::Mod: - imm = process(it, end, ip); - imm2 = process(it, end, ip); + imm = Process(it, end, ip); + imm2 = Process(it, end, ip); if (imm2 == 0) { return imm; } return imm % imm2; case Op::BitOr: - imm = process(it, end, ip); - imm2 = process(it, end, ip); + imm = Process(it, end, ip); + imm2 = Process(it, end, ip); return imm | imm2; case Op::BitAnd: - imm = process(it, end, ip); - imm2 = process(it, end, ip); + imm = Process(it, end, ip); + imm2 = Process(it, end, ip); return imm & imm2; case Op::BitXor: - imm = process(it, end, ip); - imm2 = process(it, end, ip); + imm = Process(it, end, ip); + imm2 = Process(it, end, ip); return imm ^ imm2; case Op::BitShiftLeft: - imm = process(it, end, ip); - imm2 = process(it, end, ip); + imm = Process(it, end, ip); + imm2 = Process(it, end, ip); return imm << imm2; case Op::BitShiftRight: - imm = process(it, end, ip); - imm2 = process(it, end, ip); + imm = Process(it, end, ip); + imm2 = Process(it, end, ip); return imm >> imm2; case Op::Equal: - imm = process(it, end, ip); - imm2 = process(it, end, ip); + imm = Process(it, end, ip); + imm2 = Process(it, end, ip); return imm == imm2 ? 1 : 0; case Op::GreaterEqual: - imm = process(it, end, ip); - imm2 = process(it, end, ip); + imm = Process(it, end, ip); + imm2 = Process(it, end, ip); return imm >= imm2 ? 1 : 0; case Op::LessEqual: - imm = process(it, end, ip); - imm2 = process(it, end, ip); + imm = Process(it, end, ip); + imm2 = Process(it, end, ip); return imm <= imm2 ? 1 : 0; case Op::Greater: - imm = process(it, end, ip); - imm2 = process(it, end, ip); + imm = Process(it, end, ip); + imm2 = Process(it, end, ip); return imm > imm2 ? 1 : 0; case Op::Less: - imm = process(it, end, ip); - imm2 = process(it, end, ip); + imm = Process(it, end, ip); + imm2 = Process(it, end, ip); return imm < imm2 ? 1 : 0; case Op::NotEqual: - imm = process(it, end, ip); - imm2 = process(it, end, ip); + imm = Process(it, end, ip); + imm2 = Process(it, end, ip); return imm != imm2 ? 1 : 0; case Op::Or: - imm = process(it, end, ip); - imm2 = process(it, end, ip); + imm = Process(it, end, ip); + imm2 = Process(it, end, ip); return !!imm || !!imm2 ? 1 : 0; case Op::And: - imm = process(it, end, ip); - imm2 = process(it, end, ip); + imm = Process(it, end, ip); + imm2 = Process(it, end, ip); return !!imm && !!imm2 ? 1 : 0; case Op::Ternary: - imm = process(it, end, ip); - imm2 = process(it, end, ip); - imm3 = process(it, end, ip); + imm = Process(it, end, ip); + imm2 = Process(it, end, ip); + imm3 = Process(it, end, ip); return imm != 0 ? imm2 : imm3; case Op::Function: imm = *it++; // function @@ -285,125 +403,125 @@ int process(std::vector::iterator& it, std::vector::iterator e Output::Warning("Maniac: Expression rnd args {} != 2", imm2); return 0; } - imm3 = process(it, end, ip); - return ControlVariables::Random(process(it, end, ip), imm3); + imm3 = Process(it, end, ip); + return ControlVariables::Random(Process(it, end, ip), imm3); case Fn::Item: if (imm2 != 2) { Output::Warning("Maniac: Expression item args {} != 2", imm2); return 0; } - imm3 = process(it, end, ip); - return ControlVariables::Item(process(it, end, ip), imm3); + imm3 = Process(it, end, ip); + return ControlVariables::Item(Process(it, end, ip), imm3); case Fn::Event: if (imm2 != 2) { Output::Warning("Maniac: Expression event args {} != 2", imm2); return 0; } - imm3 = process(it, end, ip); - return ControlVariables::Event(process(it, end, ip), imm3, ip); + imm3 = Process(it, end, ip); + return ControlVariables::Event(Process(it, end, ip), imm3, ip); case Fn::Actor: if (imm2 != 2) { Output::Warning("Maniac: Expression actor args {} != 2", imm2); return 0; } - return ControlVariables::Actor(process(it, end, ip), imm3); + return ControlVariables::Actor(Process(it, end, ip), imm3); case Fn::Party: if (imm2 != 2) { Output::Warning("Maniac: Expression member args {} != 2", imm2); return 0; } - imm3 = process(it, end, ip); - return ControlVariables::Party(process(it, end, ip), imm3); + imm3 = Process(it, end, ip); + return ControlVariables::Party(Process(it, end, ip), imm3); case Fn::Enemy: if (imm2 != 2) { Output::Warning("Maniac: Expression enemy args {} != 2", imm2); return 0; } - imm3 = process(it, end, ip); - return ControlVariables::Enemy(process(it, end, ip), imm3); + imm3 = Process(it, end, ip); + return ControlVariables::Enemy(Process(it, end, ip), imm3); break; case Fn::Misc: if (imm2 != 1) { Output::Warning("Maniac: Expression misc args {} != 1", imm2); return 0; } - return ControlVariables::Other(process(it, end, ip)); + return ControlVariables::Other(Process(it, end, ip)); case Fn::Pow: if (imm2 != 2) { Output::Warning("Maniac: Expression pow args {} != 2", imm2); return 0; } - return ControlVariables::Pow(process(it, end, ip), process(it, end, ip)); + return ControlVariables::Pow(Process(it, end, ip), Process(it, end, ip)); case Fn::Sqrt: if (imm2 != 2) { Output::Warning("Maniac: Expression sqrt args {} != 2", imm2); return 0; } - return ControlVariables::Sqrt(process(it, end, ip), process(it, end, ip)); + return ControlVariables::Sqrt(Process(it, end, ip), Process(it, end, ip)); case Fn::Sin: if (imm2 != 3) { Output::Warning("Maniac: Expression sin args {} != 3", imm2); return 0; } - return ControlVariables::Sin(process(it, end, ip), process(it, end, ip), process(it, end, ip)); + return ControlVariables::Sin(Process(it, end, ip), Process(it, end, ip), Process(it, end, ip)); case Fn::Cos: if (imm2 != 3) { Output::Warning("Maniac: Expression cos args {} != 3", imm2); return 0; } - return ControlVariables::Cos(process(it, end, ip), process(it, end, ip), process(it, end, ip)); + return ControlVariables::Cos(Process(it, end, ip), Process(it, end, ip), Process(it, end, ip)); case Fn::Atan2: if (imm2 != 3) { Output::Warning("Maniac: Expression atan2 args {} != 3", imm2); return 0; } - return ControlVariables::Atan2(process(it, end, ip), process(it, end, ip), process(it, end, ip)); + return ControlVariables::Atan2(Process(it, end, ip), Process(it, end, ip), Process(it, end, ip)); case Fn::Min: if (imm2 != 2) { Output::Warning("Maniac: Expression min args {} != 2", imm2); return 0; } - return ControlVariables::Min(process(it, end, ip), process(it, end, ip)); + return ControlVariables::Min(Process(it, end, ip), Process(it, end, ip)); case Fn::Max: if (imm2 != 2) { Output::Warning("Maniac: Expression max args {} != 2", imm2); return 0; } - return ControlVariables::Max(process(it, end, ip), process(it, end, ip)); + return ControlVariables::Max(Process(it, end, ip), Process(it, end, ip)); case Fn::Abs: if (imm2 != 1) { Output::Warning("Maniac: Expression abs args {} != 1", imm2); return 0; } - return ControlVariables::Abs(process(it, end, ip)); + return ControlVariables::Abs(Process(it, end, ip)); case Fn::Clamp: if (imm2 != 3) { Output::Warning("Maniac: Expression clamp args {} != 3", imm2); return 0; } - return ControlVariables::Clamp(process(it, end, ip), process(it, end, ip), process(it, end, ip)); + return ControlVariables::Clamp(Process(it, end, ip), Process(it, end, ip), Process(it, end, ip)); case Fn::Muldiv: if (imm2 != 3) { Output::Warning("Maniac: Expression muldiv args {} != 3", imm2); return 0; } - return ControlVariables::Muldiv(process(it, end, ip), process(it, end, ip), process(it, end, ip)); + return ControlVariables::Muldiv(Process(it, end, ip), Process(it, end, ip), Process(it, end, ip)); case Fn::Divmul: if (imm2 != 3) { Output::Warning("Maniac: Expression divmul args {} != 3", imm2); return 0; } - return ControlVariables::Divmul(process(it, end, ip), process(it, end, ip), process(it, end, ip)); + return ControlVariables::Divmul(Process(it, end, ip), Process(it, end, ip), Process(it, end, ip)); case Fn::Between: if (imm2 != 3) { Output::Warning("Maniac: Expression between args {} != 3", imm2); return 0; } - return ControlVariables::Between(process(it, end, ip), process(it, end, ip), process(it, end, ip)); + return ControlVariables::Between(Process(it, end, ip), Process(it, end, ip), Process(it, end, ip)); default: Output::Warning("Maniac: Expression Unknown Func {}", imm); for (int i = 0; i < imm2; ++i) { - process(it, end, ip); + Process(it, end, ip); } return 0; } @@ -413,6 +531,32 @@ int process(std::vector::iterator& it, std::vector::iterator e } } +ProcessAssignmentRet ProcessAssignment(std::vector::iterator& it, std::vector::iterator end, const Game_BaseInterpreterContext& ip) { + // Like process but it remembers the type (Variable or Switch) without evaluating it to allow assignments + int imm = 0; + + if (it == end) { + return {Op::Null, 0}; + } + + auto op = static_cast(*it); + ++it; + + // When entering the switch it is on the first argument + switch (op) { + case Op::Var: + case Op::Switch: + case Op::VarIndirect: + case Op::SwitchIndirect: + imm = Process(it, end, ip); + return {op, imm}; + default: + --it; // back on the op as op is fetched again by Process + imm = Process(it, end, ip); + return {op, imm}; + } +} + int32_t ManiacPatch::ParseExpression(Span op_codes, const Game_BaseInterpreterContext& interpreter) { std::vector ops; for (auto &o: op_codes) { @@ -423,7 +567,7 @@ int32_t ManiacPatch::ParseExpression(Span op_codes, const Game_Ba ops.push_back(static_cast((uo & 0xFF000000) >> 24)); } auto beg = ops.begin(); - return process(beg, ops.end(), interpreter); + return Process(beg, ops.end(), interpreter); } std::array ManiacPatch::GetKeyRange() { From f26d9c312d6fcf5065c632f2e7eadef3ecfeaaab Mon Sep 17 00:00:00 2001 From: Ghabry Date: Fri, 8 Nov 2024 21:07:46 +0100 Subject: [PATCH 4/8] String Variables: Fix off-by-one error in Split when counting the elements --- src/game_strings.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/game_strings.cpp b/src/game_strings.cpp index 52e74c1cea..8ced321aef 100644 --- a/src/game_strings.cpp +++ b/src/game_strings.cpp @@ -106,12 +106,15 @@ int Game_Strings::Split(Str_Params params, const std::string& delimiter, int str return -1; } - int splits = 0; std::string str = ToString(Get(params.string_id)); params.string_id = string_out_id; + int components = 0; + if (delimiter.empty()) { + // Count the characters (or the codepoints in our case) + components = 0; const char* iter = str.data(); const auto end = str.data() + str.size(); @@ -127,19 +130,20 @@ int Game_Strings::Split(Str_Params params, const std::string& delimiter, int str Set(params, std::string(start_copy, iter - start_copy)); params.string_id++; - splits++; + components++; } } else { + components = 1; + if (str.find(delimiter) == std::string::npos) { - // token not found -> 1 split - splits = 1; + // token not found } else { std::string token; for (auto index = str.find(delimiter); index != std::string::npos; index = str.find(delimiter)) { token = str.substr(0, index); Set(params, token); params.string_id++; - splits++; + components++; str.erase(0, index + delimiter.length()); } } @@ -147,8 +151,8 @@ int Game_Strings::Split(Str_Params params, const std::string& delimiter, int str // set the remaining string Set(params, str); - variables.Set(var_id, splits); - return splits; + variables.Set(var_id, components); + return components; } std::string Game_Strings::FromFile(StringView filename, int encoding, bool& do_yield) { From 1e423d9b863ef4f3c395f2f68adfd0d4ce17b3ed Mon Sep 17 00:00:00 2001 From: Ghabry Date: Fri, 8 Nov 2024 21:09:05 +0100 Subject: [PATCH 5/8] String Variables: Remove the UTF-8 byte order mask when reading in a file with Unicode encoding Maniac Patch appears to do something similiar --- src/game_strings.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/game_strings.cpp b/src/game_strings.cpp index 8ced321aef..c16d988a33 100644 --- a/src/game_strings.cpp +++ b/src/game_strings.cpp @@ -175,6 +175,11 @@ std::string Game_Strings::FromFile(StringView filename, int encoding, bool& do_y if (encoding == 0) { lcf::Encoder enc(Player::encoding); enc.Encode(file_content); + } else { + // UTF-8: Remove Byte Order Mask + if (file_content.size() >= 3 && file_content[0] == '\xEF' && file_content[1] == '\xBB' && file_content[2] == '\xBF') { + file_content.erase(0, 3); + } } return file_content; From d0e696a133fcb44b666e06b43ebeeaec90b76037 Mon Sep 17 00:00:00 2001 From: Ghabry Date: Fri, 8 Nov 2024 21:09:36 +0100 Subject: [PATCH 6/8] Assign a unique ID to string pictures Fix #3287 --- src/game_pictures.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/game_pictures.cpp b/src/game_pictures.cpp index 3fc5f26010..c2e92a2fbe 100644 --- a/src/game_pictures.cpp +++ b/src/game_pictures.cpp @@ -498,7 +498,10 @@ void Game_Pictures::Picture::AttachWindow(const Window_Base& window) { CreateSprite(); - sprite->SetBitmap(std::make_shared(window.GetWidth(), window.GetHeight(), data.use_transparent_color)); + auto bmp = std::make_shared(window.GetWidth(), window.GetHeight(), data.use_transparent_color); + bmp->SetId(fmt::format("W:{}{}{}", (void*)&window, window.GetWidth(), window.GetHeight())); + + sprite->SetBitmap(bmp); sprite->OnPictureShow(); sprite->SetVisible(true); From e7767ffb3e55af750a106bc1779ad3ebf4ebb3cb Mon Sep 17 00:00:00 2001 From: Ghabry Date: Fri, 8 Nov 2024 21:12:14 +0100 Subject: [PATCH 7/8] Correctly calculate the screen dimensions when the resolution is not dividable by the tile size. Rename the screen variables in the Parallax code as the same name (var shadowing) is confusing. Fixes incorrect scrolling of the map in Beloved Rapture --- src/game_map.cpp | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/src/game_map.cpp b/src/game_map.cpp index 5056405de4..46684779bf 100644 --- a/src/game_map.cpp +++ b/src/game_map.cpp @@ -104,9 +104,6 @@ void Game_Map::OnContinueFromBattle() { static Game_Map::Parallax::Params GetParallaxParams(); void Game_Map::Init() { - screen_width = (Player::screen_width / 16) * SCREEN_TILE_SIZE; - screen_height = (Player::screen_height / 16) * SCREEN_TILE_SIZE; - Dispose(); map_info = {}; @@ -156,8 +153,8 @@ int Game_Map::GetMapSaveCount() { void Game_Map::Setup(std::unique_ptr map_in) { Dispose(); - screen_width = (Player::screen_width / 16) * SCREEN_TILE_SIZE; - screen_height = (Player::screen_height / 16) * SCREEN_TILE_SIZE; + screen_width = (Player::screen_width / 16.0) * SCREEN_TILE_SIZE; + screen_height = (Player::screen_height / 16.0) * SCREEN_TILE_SIZE; map = std::move(map_in); @@ -637,7 +634,7 @@ void Game_Map::Scroll(int dx, int dy) { // that acc changed by. static void ClampingAdd(int low, int high, int& acc, int& inc) { int original_acc = acc; - acc = std::max(low, std::min(high, acc + inc)); + acc = std::clamp(acc + inc, low, high); inc = acc - original_acc; } @@ -1651,7 +1648,7 @@ void Game_Map::SetPositionX(int x, bool reset_panorama) { if (LoopHorizontal()) { x = Utils::PositiveModulo(x, map_width); } else { - x = std::max(0, std::min(map_width - screen_width, x)); + x = std::clamp(x, 0, map_width - screen_width); } map_info.position_x = x; if (reset_panorama) { @@ -1673,7 +1670,7 @@ void Game_Map::SetPositionY(int y, bool reset_panorama) { if (LoopVertical()) { y = Utils::PositiveModulo(y, map_height); } else { - y = std::max(0, std::min(map_height - screen_height, y)); + y = std::clamp(y, 0, map_height - screen_height); } map_info.position_y = y; if (reset_panorama) { @@ -2037,19 +2034,19 @@ void Game_Map::Parallax::ResetPositionX() { parallax_fake_x = false; if (!params.scroll_horz && !LoopHorizontal()) { - int screen_width = Player::screen_width; + int pan_screen_width = Player::screen_width; if (Player::game_config.fake_resolution.Get()) { - screen_width = SCREEN_TARGET_WIDTH; + pan_screen_width = SCREEN_TARGET_WIDTH; } - int tiles_per_screen = screen_width / TILE_SIZE; - if (screen_width % TILE_SIZE != 0) { + int tiles_per_screen = pan_screen_width / TILE_SIZE; + if (pan_screen_width % TILE_SIZE != 0) { ++tiles_per_screen; } - if (GetTilesX() > tiles_per_screen && parallax_width > screen_width) { + if (GetTilesX() > tiles_per_screen && parallax_width > pan_screen_width) { const int w = (GetTilesX() - tiles_per_screen) * TILE_SIZE; - const int ph = 2 * std::min(w, parallax_width - screen_width) * map_info.position_x / w; + const int ph = 2 * std::min(w, parallax_width - pan_screen_width) * map_info.position_x / w; if (Player::IsRPG2k()) { SetPositionX(ph); } else { @@ -2075,19 +2072,19 @@ void Game_Map::Parallax::ResetPositionY() { parallax_fake_y = false; if (!params.scroll_vert && !Game_Map::LoopVertical()) { - int screen_height = Player::screen_height; + int pan_screen_height = Player::screen_height; if (Player::game_config.fake_resolution.Get()) { - screen_height = SCREEN_TARGET_HEIGHT; + pan_screen_height = SCREEN_TARGET_HEIGHT; } - int tiles_per_screen = screen_height / TILE_SIZE; - if (screen_height % TILE_SIZE != 0) { + int tiles_per_screen = pan_screen_height / TILE_SIZE; + if (pan_screen_height % TILE_SIZE != 0) { ++tiles_per_screen; } - if (GetTilesY() > tiles_per_screen && parallax_height > screen_height) { + if (GetTilesY() > tiles_per_screen && parallax_height > pan_screen_height) { const int h = (GetTilesY() - tiles_per_screen) * TILE_SIZE; - const int pv = 2 * std::min(h, parallax_height - screen_height) * map_info.position_y / h; + const int pv = 2 * std::min(h, parallax_height - pan_screen_height) * map_info.position_y / h; SetPositionY(pv); } else { panorama.pan_y = 0; From 8552a45fd9a086c472cf76138837a56039583ee6 Mon Sep 17 00:00:00 2001 From: Ghabry Date: Mon, 11 Nov 2024 17:10:08 +0100 Subject: [PATCH 8/8] Fix crash when using a hue changed enemy Thats another regression caused by the requirement of an ID for the SpriteEffect. Replaced the assert with a debug log as this already caused 3 issues. --- src/cache.cpp | 12 +++++++++--- src/game_pictures.cpp | 2 +- src/sprite_enemy.cpp | 1 + 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/cache.cpp b/src/cache.cpp index 4d54b1eb70..b9d36d5e4a 100644 --- a/src/cache.cpp +++ b/src/cache.cpp @@ -463,8 +463,16 @@ BitmapRef Cache::Tile(StringView filename, int tile_id) { } BitmapRef Cache::SpriteEffect(const BitmapRef& src_bitmap, const Rect& rect, bool flip_x, bool flip_y, const Tone& tone, const Color& blend) { + std::string id = ToString(src_bitmap->GetId()); + + if (id.empty()) { + // assert caused too many regressions, use the pointer as the unique key and log instead + Output::Debug("Bitmap has no ID. Please report a bug!"); + id = fmt::format("{}", (void*)(src_bitmap.get())); + } + const effect_key_type key { - src_bitmap->GetId(), + id, src_bitmap->GetTransparent(), rect, flip_x, @@ -473,8 +481,6 @@ BitmapRef Cache::SpriteEffect(const BitmapRef& src_bitmap, const Rect& rect, boo blend }; - assert(!src_bitmap->GetId().empty()); - const auto it = cache_effects.find(key); if (it == cache_effects.end() || it->second.expired()) { diff --git a/src/game_pictures.cpp b/src/game_pictures.cpp index c2e92a2fbe..19e6591672 100644 --- a/src/game_pictures.cpp +++ b/src/game_pictures.cpp @@ -499,7 +499,7 @@ void Game_Pictures::Picture::AttachWindow(const Window_Base& window) { CreateSprite(); auto bmp = std::make_shared(window.GetWidth(), window.GetHeight(), data.use_transparent_color); - bmp->SetId(fmt::format("W:{}{}{}", (void*)&window, window.GetWidth(), window.GetHeight())); + bmp->SetId(fmt::format("Window:addr={},w={},h={}", (void*)&window, window.GetWidth(), window.GetHeight())); sprite->SetBitmap(bmp); sprite->OnPictureShow(); diff --git a/src/sprite_enemy.cpp b/src/sprite_enemy.cpp index 3214316051..1db6398cca 100644 --- a/src/sprite_enemy.cpp +++ b/src/sprite_enemy.cpp @@ -79,6 +79,7 @@ void Sprite_Enemy::OnMonsterSpriteReady(FileRequestResult* result) { if (hue_change) { BitmapRef new_graphic = Bitmap::Create(graphic->GetWidth(), graphic->GetHeight()); new_graphic->HueChangeBlit(0, 0, *graphic, graphic->GetRect(), hue); + new_graphic->SetId(fmt::format("{},hue={}", graphic->GetId(), hue)); graphic = new_graphic; }