From fb54bd244f746e41db9ced1ae58d4ad7309b1590 Mon Sep 17 00:00:00 2001 From: Mauro Junior <45118493+jetrotal@users.noreply.github.com> Date: Thu, 6 Aug 2026 13:48:08 -0300 Subject: [PATCH] Maniacs Patch - Expand variable array operations Extends Maniac ControlVarArray with new operations for dereference, sync sort (asc/desc), sync shuffle, range fill, reverse, and copy-from behavior. Game_Variables now implements these operations and updates array write/copy/swap logic to use ID-based access via Get(), improving handling of out-of-range IDs and overlapping ranges while keeping values clamped. I had some problems with deref and edge cases related to out of bounds assertions. Test project also included --- src/game_interpreter.cpp | 29 ++++++++++ src/game_variables.cpp | 116 +++++++++++++++++++++++++++++++++------ src/game_variables.h | 4 ++ 3 files changed, 133 insertions(+), 16 deletions(-) diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index deb29dd6d8..b2d9fee832 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -4896,6 +4896,35 @@ bool Game_Interpreter::CommandManiacControlVarArray(lcf::rpg::EventCommand const // Shift right Main_Data::game_variables->BitShiftRightArray(target_a, last_target_a, target_b); break; + case 16: + // Dereference + Main_Data::game_variables->DerefArray(target_a, last_target_a, target_b); + break; + case 17: + // Sort asc sync + Main_Data::game_variables->SortSyncRange(target_a, last_target_a, target_b, true); + break; + case 18: + // Sort desc sync + Main_Data::game_variables->SortSyncRange(target_a, last_target_a, target_b, false); + break; + case 19: + // Shuffle sync + Main_Data::game_variables->ShuffleSyncRange(target_a, last_target_a, target_b); + break; + case 20: + // Repeat / Fill + // target_b evaluates to the scalar value in Repeat + Main_Data::game_variables->SetRange(target_a, last_target_a, target_b); + break; + case 21: + // Reverse + Main_Data::game_variables->ReverseRange(target_a, last_target_a); + break; + case 22: + // Copy From (effectively SetArray mirrored) + Main_Data::game_variables->SetArray(target_a, last_target_a, target_b); + break; default: Output::Warning("ManiacControlVarArray: Unknown operation {}", op); } diff --git a/src/game_variables.cpp b/src/game_variables.cpp index 4f77f700bb..478066534b 100644 --- a/src/game_variables.cpp +++ b/src/game_variables.cpp @@ -201,11 +201,11 @@ void Game_Variables::WriteRange(const int first_id, const int last_id, V&& value template void Game_Variables::WriteArray(const int first_id_a, const int last_id_a, const int first_id_b, F&& op) { auto& vv = _variables; - int out_b = std::max(0, first_id_b - 1); - for (int i = std::max(0, first_id_a - 1); i < last_id_a; ++i) { - auto& v_a = vv[i]; - auto v_b = vv[out_b++]; - v_a = Utils::Clamp(op(v_a, v_b), _min, _max); + int id_b = first_id_b; + for (int id_a = first_id_a; id_a <= last_id_a; ++id_a, ++id_b) { + if (id_a > 0 && id_a <= static_cast(vv.size())) { + vv[id_a - 1] = Utils::Clamp(op(vv[id_a - 1], Get(id_b)), _min, _max); + } } } @@ -531,15 +531,16 @@ void Game_Variables::SetArray(int first_id_a, int last_id_a, int first_id_b) { // This ensures overlapping areas are copied properly if (first_id_a < first_id_b) { WriteArray(first_id_a, last_id_a, first_id_b, VarSet); - } else { + } + else { auto& vv = _variables; const int steps = std::max(0, last_id_a - first_id_a + 1); - int out_b = std::max(0, first_id_b + steps - 2); - int out_a = std::max(0, last_id_a - 1); - for (int i = 0; i < steps; ++i) { - auto& v_a = vv[out_a--]; - auto v_b = vv[out_b--]; - v_a = Utils::Clamp(VarSet(v_a, v_b), _min, _max); + int id_a = last_id_a; + int id_b = first_id_b + steps - 1; + for (int i = 0; i < steps; ++i, --id_a, --id_b) { + if (id_a > 0 && id_a <= static_cast(vv.size())) { + vv[id_a - 1] = Utils::Clamp(Get(id_b), _min, _max); + } } } } @@ -597,11 +598,94 @@ void Game_Variables::BitShiftRightArray(int first_id_a, int last_id_a, int first void Game_Variables::SwapArray(int first_id_a, int last_id_a, int first_id_b) { PrepareArray(first_id_a, last_id_a, first_id_b, "Invalid write var[{},{}] <-> var[{},{}]!"); auto& vv = _variables; - const int steps = std::max(0, last_id_a - first_id_a + 1); - int out_b = std::max(0, first_id_b + steps - 2); - int out_a = std::max(0, last_id_a - 1); + int id_b = first_id_b; + for (int id_a = first_id_a; id_a <= last_id_a; ++id_a, ++id_b) { + Var_t val_a = Get(id_a); + Var_t val_b = Get(id_b); + if (id_a > 0 && id_a <= static_cast(vv.size())) vv[id_a - 1] = val_b; + if (id_b > 0 && id_b <= static_cast(vv.size())) vv[id_b - 1] = val_a; + } +} + +void Game_Variables::DerefArray(int first_id_a, int last_id_a, int first_id_b) { + PrepareArray(first_id_a, last_id_a, first_id_b, "Invalid write var[{},{}] = deref(var[{},{}])!"); + auto& vv = _variables; + int id_b = first_id_b; + for (int id_a = first_id_a; id_a <= last_id_a; ++id_a, ++id_b) { + if (id_a > 0 && id_a <= static_cast(vv.size())) { + vv[id_a - 1] = Utils::Clamp(GetIndirect(id_b), _min, _max); + } + } +} + +void Game_Variables::SortSyncRange(int first_id, int last_id, int sync_id, bool asc) { + PrepareArray(first_id, last_id, sync_id, "Invalid write sort_sync(var[{},{}], var[{},{}])!"); + auto& vv = _variables; + int steps = last_id - first_id + 1; + if (steps <= 1) return; + + std::vector> pairs; + pairs.reserve(steps); for (int i = 0; i < steps; ++i) { - std::swap(vv[out_a--], vv[out_b--]); + pairs.emplace_back(Get(first_id + i), Get(sync_id + i)); + } + + if (asc) { + std::stable_sort(pairs.begin(), pairs.end(), [](const auto& a, const auto& b) { + return a.first < b.first; + }); + } + else { + std::stable_sort(pairs.begin(), pairs.end(), [](const auto& a, const auto& b) { + return a.first > b.first; + }); + } + + for (int i = 0; i < steps; ++i) { + int id_a = first_id + i; + int id_b = sync_id + i; + if (id_a > 0 && id_a <= static_cast(vv.size())) vv[id_a - 1] = pairs[i].first; + if (id_b > 0 && id_b <= static_cast(vv.size())) vv[id_b - 1] = pairs[i].second; + } +} + +void Game_Variables::ShuffleSyncRange(int first_id, int last_id, int sync_id) { + PrepareArray(first_id, last_id, sync_id, "Invalid write shuffle_sync(var[{},{}], var[{},{}])!"); + auto& vv = _variables; + int steps = last_id - first_id + 1; + if (steps <= 1) return; + + int offset_b = sync_id - first_id; + for (int id_a = first_id; id_a <= last_id; ++id_a) { + int rnd_id = Rand::GetRandomNumber(first_id, last_id); + + Var_t val_a1 = Get(id_a); + Var_t val_a2 = Get(rnd_id); + Var_t val_b1 = Get(id_a + offset_b); + Var_t val_b2 = Get(rnd_id + offset_b); + + if (id_a > 0 && id_a <= static_cast(vv.size())) vv[id_a - 1] = val_a2; + if (rnd_id > 0 && rnd_id <= static_cast(vv.size())) vv[rnd_id - 1] = val_a1; + + if (id_a + offset_b > 0 && id_a + offset_b <= static_cast(vv.size())) vv[id_a + offset_b - 1] = val_b2; + if (rnd_id + offset_b > 0 && rnd_id + offset_b <= static_cast(vv.size())) vv[rnd_id + offset_b - 1] = val_b1; + } +} + +void Game_Variables::ReverseRange(int first_id, int last_id) { + PrepareRange(first_id, last_id, "Invalid write reverse(var[{},{}])!"); + auto& vv = _variables; + int steps = last_id - first_id + 1; + if (steps <= 1) return; + + int half_steps = steps / 2; + for (int i = 0; i < half_steps; ++i) { + int id_a = first_id + i; + int id_b = last_id - i; + Var_t val_a = Get(id_a); + Var_t val_b = Get(id_b); + if (id_a > 0 && id_a <= static_cast(vv.size())) vv[id_a - 1] = val_b; + if (id_b > 0 && id_b <= static_cast(vv.size())) vv[id_b - 1] = val_a; } } diff --git a/src/game_variables.h b/src/game_variables.h index bd4d5d5211..06c3d7f1bc 100644 --- a/src/game_variables.h +++ b/src/game_variables.h @@ -127,6 +127,10 @@ class Game_Variables { void BitShiftLeftArray(int first_id_a, int last_id_a, int first_id_b); void BitShiftRightArray(int first_id_a, int last_id_a, int first_id_b); void SwapArray(int first_id_a, int last_id_a, int first_id_b); + void DerefArray(int first_id_a, int last_id_a, int first_id_b); + void SortSyncRange(int first_id, int last_id, int sync_id, bool asc); + void ShuffleSyncRange(int first_id, int last_id, int sync_id); + void ReverseRange(int first_id, int last_id); std::string_view GetName(int _id) const;