diff --git a/src/game_battle.cpp b/src/game_battle.cpp index 8703c704ef..5bcc6b9c9e 100644 --- a/src/game_battle.cpp +++ b/src/game_battle.cpp @@ -219,11 +219,28 @@ void Game_Battle::UpdateAtbGauges() { const auto multiplier = std::max(1.0, static_cast(275000 - cur_atb) / 55000.0); increment = Utils::RoundTo(multiplier * increment); } + + ManiacBattleHook( + Game_Interpreter_Battle::ManiacBattleHookType::AtbIncrement, + bat->GetType() == Game_Battler::Type_Enemy, + bat->GetPartyIndex(), + bat->GetAtbGauge(), + increment + ); + bat->IncrementAtbGauge(increment); } } } +bool Game_Battle::ManiacBattleHook(Game_Interpreter_Battle::ManiacBattleHookType hook_type, int var1, int var2, int var3, int var4, int var5, int var6) { + return interpreter->ManiacBattleHook(hook_type, var1, var2, var3, var4, var5, var6); +} + +bool Game_Battle::ManiacProcessSubEvents() { + return interpreter->ProcessManiacSubEvents(); +} + void Game_Battle::ChangeBackground(const std::string& name) { background_name = name; } diff --git a/src/game_battle.h b/src/game_battle.h index 6c828df4ef..7eeece4b71 100644 --- a/src/game_battle.h +++ b/src/game_battle.h @@ -23,6 +23,7 @@ #include #include #include "teleport_target.h" +#include "game_interpreter_battle.h" #include "utils.h" #include "point.h" @@ -109,6 +110,15 @@ namespace Game_Battle { */ void UpdateAtbGauges(); + /** + * Convenience function to call a maniacs battle hook, which processes sub-events at any time. + */ + bool ManiacBattleHook(Game_Interpreter_Battle::ManiacBattleHookType hook_type, int var1, int var2, int var3, int var4 = 0, int var5 = 0, int var6 = 0); + /** + * Convenience function to process all maniacs sub-events, and return whether they're currently running + */ + bool ManiacProcessSubEvents(); + void ChangeBackground(const std::string& name); const std::string& GetBackground(); diff --git a/src/game_battlealgorithm.cpp b/src/game_battlealgorithm.cpp index 3c939b64e4..f2eda7702d 100644 --- a/src/game_battlealgorithm.cpp +++ b/src/game_battlealgorithm.cpp @@ -88,6 +88,14 @@ Game_BattleAlgorithm::AlgorithmBase::AlgorithmBase(Type ty, Game_Battler* source party_target = target; } +int Game_BattleAlgorithm::AlgorithmBase::GetActionType() { + return -1; +} + +int Game_BattleAlgorithm::AlgorithmBase::GetActionId() { + return -1; +} + void Game_BattleAlgorithm::AlgorithmBase::Reset() { hp = 0; sp = 0; @@ -185,7 +193,20 @@ int Game_BattleAlgorithm::AlgorithmBase::ApplySpEffect() { // Only absorb the sp that were left source->ChangeSp(-sp); } + + if (Player::IsPatchManiac()) { + Game_Battle::ManiacBattleHook( + Game_Interpreter_Battle::ManiacBattleHookType::StatChange, + target->GetType() == Game_Battler::Type_Enemy, + target->GetPartyIndex(), + target->GetDisplayX(), + target->GetDisplayY(), + 3, + sp + ); + } } + return sp; } @@ -198,6 +219,18 @@ int Game_BattleAlgorithm::AlgorithmBase::ApplyAtkEffect() { if (IsAbsorbAtk()) { source->ChangeAtkModifier(-atk); } + + if (Player::IsPatchManiac()) { + Game_Battle::ManiacBattleHook( + Game_Interpreter_Battle::ManiacBattleHookType::StatChange, + target->GetType() == Game_Battler::Type_Enemy, + target->GetPartyIndex(), + target->GetDisplayX(), + target->GetDisplayY(), + 4, + atk + ); + } } return atk; } @@ -211,6 +244,18 @@ int Game_BattleAlgorithm::AlgorithmBase::ApplyDefEffect() { if (IsAbsorbDef()) { source->ChangeDefModifier(-def); } + + if (Player::IsPatchManiac()) { + Game_Battle::ManiacBattleHook( + Game_Interpreter_Battle::ManiacBattleHookType::StatChange, + target->GetType() == Game_Battler::Type_Enemy, + target->GetPartyIndex(), + target->GetDisplayX(), + target->GetDisplayY(), + 5, + def + ); + } } return def; } @@ -224,6 +269,18 @@ int Game_BattleAlgorithm::AlgorithmBase::ApplySpiEffect() { if (IsAbsorbSpi()) { source->ChangeSpiModifier(-spi); } + + if (Player::IsPatchManiac()) { + Game_Battle::ManiacBattleHook( + Game_Interpreter_Battle::ManiacBattleHookType::StatChange, + target->GetType() == Game_Battler::Type_Enemy, + target->GetPartyIndex(), + target->GetDisplayX(), + target->GetDisplayY(), + 6, + spi + ); + } } return spi; } @@ -237,6 +294,18 @@ int Game_BattleAlgorithm::AlgorithmBase::ApplyAgiEffect() { if (IsAbsorbAgi()) { source->ChangeAgiModifier(-agi); } + + if (Player::IsPatchManiac()) { + Game_Battle::ManiacBattleHook( + Game_Interpreter_Battle::ManiacBattleHookType::StatChange, + target->GetType() == Game_Battler::Type_Enemy, + target->GetPartyIndex(), + target->GetDisplayX(), + target->GetDisplayY(), + 7, + agi + ); + } } return agi; } @@ -532,6 +601,26 @@ AlgorithmBase(Type::None, source, source) { // no-op } +int Game_BattleAlgorithm::None::GetActionId() { + return lcf::rpg::EnemyAction::Basic_nothing; +} + +int Game_BattleAlgorithm::None::GetActionType() { + return lcf::rpg::EnemyAction::Kind_basic; +} + +int Game_BattleAlgorithm::Normal::GetActionId() { + if (IsDualAttack()) { + return lcf::rpg::EnemyAction::Basic_dual_attack; + } + + return lcf::rpg::EnemyAction::Basic_attack; +} + +int Game_BattleAlgorithm::Normal::GetActionType() { + return lcf::rpg::EnemyAction::Kind_basic; +} + Game_BattleAlgorithm::Normal::Normal(Game_Battler* source, Game_Battler* target, int hits_multiplier, Style style) : AlgorithmBase(Type::Normal, source, target), hits_multiplier(hits_multiplier) { @@ -740,12 +829,16 @@ bool Game_BattleAlgorithm::Normal::vExecute() { return SetIsSuccess(); } +bool Game_BattleAlgorithm::Normal::IsDualAttack() const { + return GetSource()->GetType() == Game_Battler::Type_Enemy && hits_multiplier == 2; +} + std::string Game_BattleAlgorithm::Normal::GetStartMessage(int line) const { if (line == 0) { if (Feature::HasRpg2kBattleSystem()) { return BattleMessage::GetNormalAttackStartMessage2k(*GetSource()); } - if (GetSource()->GetType() == Game_Battler::Type_Enemy && hits_multiplier == 2) { + if (IsDualAttack()) { return BattleMessage::GetDoubleAttackStartMessage2k3(*GetSource()); } } @@ -849,6 +942,14 @@ Game_BattleAlgorithm::Skill::Skill(Game_Battler* source, const lcf::rpg::Skill& { } +int Game_BattleAlgorithm::Skill::GetActionType() { + return lcf::rpg::EnemyAction::Kind_skill; +} + +int Game_BattleAlgorithm::Skill::GetActionId() { + return skill.ID; +} + void Game_BattleAlgorithm::Skill::Init() { } @@ -1236,6 +1337,14 @@ Game_BattleAlgorithm::Item::Item(Game_Battler* source, Game_Party_Base* target, // no-op } +int Game_BattleAlgorithm::Item::GetActionType() { + return 3; +} + +int Game_BattleAlgorithm::Item::GetActionId() { + return item.ID; +} + bool Game_BattleAlgorithm::Item::vStart() { Main_Data::game_party->ConsumeItemUse(item.ID); return true; @@ -1340,6 +1449,14 @@ Game_BattleAlgorithm::Defend::Defend(Game_Battler* source) : source->SetIsDefending(true); } +int Game_BattleAlgorithm::Defend::GetActionType() { + return lcf::rpg::EnemyAction::Kind_basic; +} + +int Game_BattleAlgorithm::Defend::GetActionId() { + return lcf::rpg::EnemyAction::Basic_defense; +} + std::string Game_BattleAlgorithm::Defend::GetStartMessage(int line) const { if (line == 0) { if (Feature::HasRpg2kBattleSystem()) { @@ -1360,6 +1477,14 @@ AlgorithmBase(Type::Observe, source, source) { // no-op } +int Game_BattleAlgorithm::Observe::GetActionType() { + return lcf::rpg::EnemyAction::Kind_basic; +} + +int Game_BattleAlgorithm::Observe::GetActionId() { + return lcf::rpg::EnemyAction::Basic_observe; +} + std::string Game_BattleAlgorithm::Observe::GetStartMessage(int line) const { if (line == 0) { if (Feature::HasRpg2kBattleSystem()) { @@ -1376,6 +1501,14 @@ AlgorithmBase(Type::Charge, source, source) { // no-op } +int Game_BattleAlgorithm::Charge::GetActionType() { + return lcf::rpg::EnemyAction::Kind_basic; +} + +int Game_BattleAlgorithm::Charge::GetActionId() { + return lcf::rpg::EnemyAction::Basic_charge; +} + std::string Game_BattleAlgorithm::Charge::GetStartMessage(int line) const { if (line == 0) { if (Feature::HasRpg2kBattleSystem()) { @@ -1396,6 +1529,14 @@ AlgorithmBase(Type::SelfDestruct, source, target) { // no-op } +int Game_BattleAlgorithm::SelfDestruct::GetActionType() { + return lcf::rpg::EnemyAction::Kind_basic; +} + +int Game_BattleAlgorithm::SelfDestruct::GetActionId() { + return lcf::rpg::EnemyAction::Basic_autodestruction; +} + std::string Game_BattleAlgorithm::SelfDestruct::GetStartMessage(int line) const { if (line == 0) { if (Feature::HasRpg2kBattleSystem()) { @@ -1450,6 +1591,14 @@ Game_BattleAlgorithm::Escape::Escape(Game_Battler* source) : // no-op } +int Game_BattleAlgorithm::Escape::GetActionType() { + return lcf::rpg::EnemyAction::Kind_basic; +} + +int Game_BattleAlgorithm::Escape::GetActionId() { + return lcf::rpg::EnemyAction::Basic_escape; +} + std::string Game_BattleAlgorithm::Escape::GetStartMessage(int line) const { if (line == 0) { if (Feature::HasRpg2kBattleSystem()) { @@ -1487,6 +1636,14 @@ AlgorithmBase(Type::Transform, source, source), new_monster_id(new_monster_id) { // no-op } +int Game_BattleAlgorithm::Transform::GetActionType() { + return lcf::rpg::EnemyAction::Kind_transformation; +} + +int Game_BattleAlgorithm::Transform::GetActionId() { + return new_monster_id; +} + std::string Game_BattleAlgorithm::Transform::GetStartMessage(int line) const { if (line == 0 && Feature::HasRpg2kBattleSystem()) { auto* enemy = lcf::ReaderUtil::GetElement(lcf::Data::enemies, new_monster_id); @@ -1509,3 +1666,11 @@ AlgorithmBase(Type::DoNothing, source, source) { // no-op } +int Game_BattleAlgorithm::DoNothing::GetActionType() { + return lcf::rpg::EnemyAction::Kind_basic; +} + +int Game_BattleAlgorithm::DoNothing::GetActionId() { + return lcf::rpg::EnemyAction::Basic_nothing; +} + diff --git a/src/game_battlealgorithm.h b/src/game_battlealgorithm.h index 542e29a905..59f85adb90 100644 --- a/src/game_battlealgorithm.h +++ b/src/game_battlealgorithm.h @@ -83,6 +83,12 @@ class AlgorithmBase { public: virtual ~AlgorithmBase() {} + /** @return the category associated with this action */ + virtual int GetActionType(); + + /** @return the unique identifier associated with this specific action */ + virtual int GetActionId(); + /** @return the source of the battle action. */ Game_Battler* GetSource() const; @@ -616,6 +622,12 @@ class AlgorithmBase { class None : public AlgorithmBase { public: None(Game_Battler* source); + + /** @return the type associated with this action */ + int GetActionType() override; + + /** @return the id associated with this action */ + int GetActionId() override; }; @@ -628,6 +640,12 @@ class Normal : public AlgorithmBase { Style_MultiHit, }; + /** @return the type associated with this action */ + int GetActionType() override; + + /** @return the id associated with this action */ + int GetActionId() override; + static Style GetDefaultStyle(); Normal(Game_Battler* source, Game_Battler* target, int hits_multiplier = 1, Style style = GetDefaultStyle()); @@ -635,6 +653,7 @@ class Normal : public AlgorithmBase { bool vExecute() override; bool vStart() override; + bool IsDualAttack() const; int GetAnimationId(int i) const override; std::string GetStartMessage(int line) const override; @@ -667,6 +686,12 @@ class Skill : public AlgorithmBase { Skill(Game_Battler* source, Game_Party_Base* target, const lcf::rpg::Skill& skill, const lcf::rpg::Item* item = NULL); Skill(Game_Battler* source, const lcf::rpg::Skill& skill, const lcf::rpg::Item* item = NULL); + /** @return the type associated with this action */ + int GetActionType() override; + + /** @return the id associated with this action */ + int GetActionId() override; + bool IsTargetValid(const Game_Battler&) const override; bool vExecute() override; bool vStart() override; @@ -704,6 +729,12 @@ class Item : public AlgorithmBase { Item(Game_Battler* source, Game_Party_Base* target, const lcf::rpg::Item& item); Item(Game_Battler* source, const lcf::rpg::Item& item); + /** @return the type associated with this action */ + int GetActionType() override; + + /** @return the id associated with this action */ + int GetActionId() override; + bool IsTargetValid(const Game_Battler&) const override; bool vExecute() override; bool vStart() override; @@ -724,6 +755,12 @@ class Defend : public AlgorithmBase { public: Defend(Game_Battler* source); + /** @return the type associated with this action */ + int GetActionType() override; + + /** @return the id associated with this action */ + int GetActionId() override; + std::string GetStartMessage(int line) const override; int GetSourcePose() const override; }; @@ -732,6 +769,12 @@ class Observe : public AlgorithmBase { public: Observe(Game_Battler* source); + /** @return the type associated with this action */ + int GetActionType() override; + + /** @return the id associated with this action */ + int GetActionId() override; + std::string GetStartMessage(int line) const override; }; @@ -739,6 +782,12 @@ class Charge : public AlgorithmBase { public: Charge(Game_Battler* source); + /** @return the type associated with this action */ + int GetActionType() override; + + /** @return the id associated with this action */ + int GetActionId() override; + std::string GetStartMessage(int line) const override; void ApplyCustomEffect() override; }; @@ -747,6 +796,12 @@ class SelfDestruct : public AlgorithmBase { public: SelfDestruct(Game_Battler* source, Game_Party_Base* target); + /** @return the type associated with this action */ + int GetActionType() override; + + /** @return the id associated with this action */ + int GetActionId() override; + std::string GetStartMessage(int line) const override; const lcf::rpg::Sound* GetStartSe() const override; bool vExecute() override; @@ -759,6 +814,12 @@ class Escape : public AlgorithmBase { public: Escape(Game_Battler* source); + /** @return the type associated with this action */ + int GetActionType() override; + + /** @return the id associated with this action */ + int GetActionId() override; + std::string GetStartMessage(int line) const override; int GetSourcePose() const override; const lcf::rpg::Sound* GetStartSe() const override; @@ -769,6 +830,12 @@ class Transform : public AlgorithmBase { public: Transform(Game_Battler* source, int new_monster_id); + /** @return the type associated with this action */ + int GetActionType() override; + + /** @return the id associated with this action */ + int GetActionId() override; + std::string GetStartMessage(int line) const override; void ApplyCustomEffect() override; @@ -780,6 +847,12 @@ class Transform : public AlgorithmBase { class DoNothing : public AlgorithmBase { public: DoNothing(Game_Battler* source); + + /** @return the type associated with this action */ + int GetActionType() override; + + /** @return the id associated with this action */ + int GetActionId() override; }; inline Type AlgorithmBase::GetType() const { diff --git a/src/game_battler.cpp b/src/game_battler.cpp index 03eafdb57a..221975a69e 100644 --- a/src/game_battler.cpp +++ b/src/game_battler.cpp @@ -367,6 +367,13 @@ bool Game_Battler::AddState(int state_id, bool allow_battle_states) { } } + Game_Battle::ManiacBattleHook( + Game_Interpreter_Battle::ManiacBattleHookType::SetState, + GetType() == Game_Battler::Type_Enemy, + GetPartyIndex(), + state_id + ); + return was_added; } @@ -586,6 +593,10 @@ Game_Party_Base& Game_Battler::GetParty() const { } } +int Game_Battler::GetPartyIndex() { + return GetParty().GetMemberIndex(this); +} + void Game_Battler::UpdateBattle() { Shake::Update(shake.position, shake.time_left, shake.strength, shake.speed, false); Flash::Update(flash.current_level, flash.time_left); diff --git a/src/game_battler.h b/src/game_battler.h index 48ce45d179..79b4acad7c 100644 --- a/src/game_battler.h +++ b/src/game_battler.h @@ -769,6 +769,13 @@ class Game_Battler { */ Game_Party_Base& GetParty() const; + /** + * Convenience function to access the battlers party member index. + * + * @return Index of this member in their party. + */ + int GetPartyIndex(); + /** * Gets the maximal atb gauge value. * When GetAtbGauge() >= this, the battler can act. diff --git a/src/game_interpreter_battle.cpp b/src/game_interpreter_battle.cpp index 8fc9a65495..023752e57d 100644 --- a/src/game_interpreter_battle.cpp +++ b/src/game_interpreter_battle.cpp @@ -15,7 +15,7 @@ * along with EasyRPG Player. If not, see . */ -// Headers + // Headers #include "game_actors.h" #include "game_battle.h" #include "game_enemyparty.h" @@ -43,14 +43,41 @@ enum TargetType { Enemy, }; +// Implemented as a static map, since maniac hooks can only have one common event callback at a time. +// Subsequent calls will simply override the previous common event callback. +std::map> Game_Interpreter_Battle::maniac_hooks; + +void Game_Interpreter_Battle::InitBattle() { + if (Player::IsPatchManiac()) { + Game_Interpreter_Battle::maniac_hooks = { + {Game_Interpreter_Battle::ManiacBattleHookType::AtbIncrement, std::make_tuple(0, 0)}, + {Game_Interpreter_Battle::ManiacBattleHookType::DamagePop, std::make_tuple(0, 0)}, + {Game_Interpreter_Battle::ManiacBattleHookType::Targetting, std::make_tuple(0, 0)}, + {Game_Interpreter_Battle::ManiacBattleHookType::SetState, std::make_tuple(0, 0)}, + {Game_Interpreter_Battle::ManiacBattleHookType::StatChange, std::make_tuple(0, 0)} + }; + } +} + static const char* target_text[] = { "actor", "party member", "enemy" }; static const void MissingTargetWarning(const char* command_name, TargetType target_type, int target_id) { Output::Warning("{}: Invalid {} ID: {}", command_name, target_text[target_type], target_id); } + +// Provides a facility for battle sub-events to be run immediately +// without blocking the standard interpreter from actually processing them. +std::unique_ptr maniac_interpreter; + Game_Interpreter_Battle::Game_Interpreter_Battle(Span pages) : Game_Interpreter(true), pages(pages), executed(pages.size(), false) +{ + maniac_interpreter.reset(new Game_Interpreter_Battle()); +} + +Game_Interpreter_Battle::Game_Interpreter_Battle() + : Game_Interpreter(true) { } @@ -600,12 +627,98 @@ bool Game_Interpreter_Battle::CommandEndBranchBattle(lcf::rpg::EventCommand cons return true; } -bool Game_Interpreter_Battle::CommandManiacControlBattle(lcf::rpg::EventCommand const&) { +bool Game_Interpreter_Battle::ManiacBattleHook(ManiacBattleHookType hook_type, int var1, int var2, int var3, int var4, int var5, int var6) { + if (!Player::IsPatchManiac()) { + return false; + } + + int common_event_id = std::get<0>(maniac_hooks[hook_type]); + int variable_start_id = std::get<1>(maniac_hooks[hook_type]); + + if (common_event_id <= 0) { + return false; + } + + Game_CommonEvent* common_event = lcf::ReaderUtil::GetElement(Game_Map::GetCommonEvents(), common_event_id); + if (!common_event) { + Output::Warning("CommandManiacControlBattle: Can't call invalid common event {}", common_event_id); + return false; + } + + // pushes the common event to be run into the queue of events. + maniac_interpreter->Push(common_event); + + // pushes the change variable events into the interpreters + // event queue, so we don't run into a race condition. + std::vector pre_commands; + for (size_t i = 0; i < 6; i++) + { + auto event_command = lcf::rpg::EventCommand(); + event_command.code = static_cast(lcf::rpg::EventCommand::Code::ControlVars); + event_command.parameters = lcf::DBArray(7); + event_command.parameters[1] = variable_start_id + i; + switch (i) + { + case 0: + event_command.parameters[5] = var1; + break; + case 1: + event_command.parameters[5] = var2; + break; + case 2: + event_command.parameters[5] = var3; + break; + case 3: + event_command.parameters[5] = var4; + break; + case 4: + event_command.parameters[5] = var5; + break; + case 5: + event_command.parameters[5] = var6; + break; + default: + break; + } + pre_commands.push_back(event_command); + } + + // Push is actually "push_back", so this gets added before other events. + maniac_interpreter->Push(pre_commands, 0); + + // Necessary to start the sub-event. + maniac_interpreter->Update(); + + return true; +} + +bool Game_Interpreter_Battle::ProcessManiacSubEvents() { + // If we have sub-events we're going to update them immediately + // until the queue is empty while making the rest of the game wait. + if (Player::IsPatchManiac() && maniac_interpreter->IsRunning()) { + maniac_interpreter->Update(); + return true; + } + return false; +} + +bool Game_Interpreter_Battle::CommandManiacControlBattle(lcf::rpg::EventCommand const& com) { if (!Player::IsPatchManiac()) { return true; } - Output::Warning("Maniac Patch: Command ControlBattle not supported"); + ManiacBattleHookType control_type_flags = static_cast(com.parameters[0]); + int common_event_flags = com.parameters[1]; + int common_event_identifier = com.parameters[2]; + int value_reference_identifier = com.parameters[3]; + + int common_event_id = ValueOrVariable(common_event_flags, common_event_identifier); + + // Sets the maniacs battle event hook to: + // the common event id and the variable id the developer would like to use. + std::get<0>(maniac_hooks[control_type_flags]) = common_event_identifier; + std::get<1>(maniac_hooks[control_type_flags]) = value_reference_identifier; + return true; } diff --git a/src/game_interpreter_battle.h b/src/game_interpreter_battle.h index 96ec7d191f..4b83790eca 100644 --- a/src/game_interpreter_battle.h +++ b/src/game_interpreter_battle.h @@ -40,6 +40,7 @@ class Game_Interpreter_Battle : public Game_Interpreter { public: explicit Game_Interpreter_Battle(Span pages); + explicit Game_Interpreter_Battle(); int GetNumPages() const; @@ -60,6 +61,28 @@ class Game_Interpreter_Battle : public Game_Interpreter bool ExecuteCommand(lcf::rpg::EventCommand const& com) override; + static void InitBattle(); + + /** + * All possible hook type events that maniacs offers. + */ + enum class ManiacBattleHookType { + AtbIncrement, + DamagePop, + Targetting, + SetState, + StatChange + }; + + /** + * Calls a maniacs battle hook, which processes sub-events at any time. + */ + bool ManiacBattleHook(ManiacBattleHookType hook_type, int var1, int var2, int var3, int var4 = 0, int var5 = 0, int var6 = 0); + /** + * Processes all maniacs sub-events, and returns whether it's currently running + */ + bool ProcessManiacSubEvents(); + private: bool CommandCallCommonEvent(lcf::rpg::EventCommand const& com); bool CommandForceFlee(lcf::rpg::EventCommand const& com); @@ -87,6 +110,7 @@ class Game_Interpreter_Battle : public Game_Interpreter int current_actor_id = 0; bool targets_single_enemy = false; bool force_flee_enabled = false; + static std::map> maniac_hooks; }; inline int Game_Interpreter_Battle::GetNumPages() const { diff --git a/src/game_party_base.cpp b/src/game_party_base.cpp index 88c11db579..4709070bb1 100644 --- a/src/game_party_base.cpp +++ b/src/game_party_base.cpp @@ -119,3 +119,18 @@ int Game_Party_Base::GetAverageAgility() { return battlers.empty() ? 1 : agi /= battlers.size(); } +int Game_Party_Base::GetMemberIndex(Game_Battler* battler) { + std::vector battlers; + GetBattlers(battlers); + + auto iterator = std::find(battlers.begin(), battlers.end(), battler); + + if (iterator != battlers.end()) { + int index = std::distance(battlers.begin(), iterator); + return index; + } + else { + return -1; + } +} + diff --git a/src/game_party_base.h b/src/game_party_base.h index 1834e9a2e9..cdd1457066 100644 --- a/src/game_party_base.h +++ b/src/game_party_base.h @@ -108,6 +108,13 @@ class Game_Party_Base { */ int GetAverageAgility(); + /** + * Gets the index of the battler in the party + * + * @return the battlers party index, or -1 if not in the party + */ + int GetMemberIndex(Game_Battler* battler); + private: }; diff --git a/src/scene_battle.cpp b/src/scene_battle.cpp index aaf6219f05..d388e8f5a9 100644 --- a/src/scene_battle.cpp +++ b/src/scene_battle.cpp @@ -570,6 +570,21 @@ void Scene_Battle::RemoveCurrentAction() { void Scene_Battle::ActionSelectedCallback(Game_Battler* for_battler) { assert(for_battler->GetBattleAlgorithm() != nullptr); + auto single_target = for_battler->GetBattleAlgorithm()->GetOriginalSingleTarget(); + auto group_targets = for_battler->GetBattleAlgorithm()->GetOriginalPartyTarget(); + // Target: 0 None, 1 Single Enemy, 2 All Enemies, 3 Single Ally, 4 All Allies + Game_Battle::ManiacBattleHook( + Game_Interpreter_Battle::ManiacBattleHookType::Targetting, + for_battler->GetType() == Game_Battler::Type_Enemy, + for_battler->GetPartyIndex(), + for_battler->GetBattleAlgorithm()->GetActionType(), + for_battler->GetBattleAlgorithm()->GetActionId(), + single_target + ? (single_target->GetType() != Game_Battler::Type_Enemy ? 1 : 3) + : (group_targets->GetRandomActiveBattler()->GetType() != Game_Battler::Type_Enemy ? 2 : 4), + single_target ? single_target->GetPartyIndex() : 0 + ); + if (for_battler->GetBattleAlgorithm() == nullptr) { Output::Warning("ActionSelectedCallback: Invalid action for battler {} ({})", for_battler->GetId(), for_battler->GetName()); diff --git a/src/scene_battle_rpg2k.cpp b/src/scene_battle_rpg2k.cpp index 1026e3c9dd..9d61a317d3 100644 --- a/src/scene_battle_rpg2k.cpp +++ b/src/scene_battle_rpg2k.cpp @@ -205,6 +205,12 @@ void Scene_Battle_Rpg2k::vUpdate() { break; } + // this is checked separately because we want normal events to be processed + // just not sub-events called by maniacs battle hooks. + if (state != State_Victory && state != State_Defeat && Game_Battle::ManiacProcessSubEvents()) { + break; + } + if (!CheckWait()) { break; } diff --git a/src/scene_battle_rpg2k3.cpp b/src/scene_battle_rpg2k3.cpp index aeb3ea65a4..9d2826f523 100644 --- a/src/scene_battle_rpg2k3.cpp +++ b/src/scene_battle_rpg2k3.cpp @@ -60,6 +60,7 @@ Scene_Battle_Rpg2k3::Scene_Battle_Rpg2k3(const BattleArgs& args) : void Scene_Battle_Rpg2k3::Start() { Scene_Battle::Start(); + Game_Interpreter_Battle::InitBattle(); InitBattleCondition(Game_Battle::GetBattleCondition()); CreateEnemySprites(); CreateActorSprites(); @@ -498,7 +499,24 @@ void Scene_Battle_Rpg2k3::UpdateAnimations() { } } -void Scene_Battle_Rpg2k3::DrawFloatText(int x, int y, int color, StringView text) { +void Scene_Battle_Rpg2k3::DrawFloatText(int x, int y, int color, StringView text, Game_Battler* battler, FloatTextType type) { + std::stringstream ss(text.to_string()); + int value = 0; + ss >> value; + bool should_override = Game_Battle::ManiacBattleHook( + Game_Interpreter_Battle::ManiacBattleHookType::DamagePop, + battler->GetType() == Game_Battler::Type_Enemy, + battler->GetPartyIndex(), + x, + y, + static_cast(type), + value + ); + + if (should_override) { + return; + } + Rect rect = Text::GetSize(*Font::Default(), text); BitmapRef graphic = Bitmap::Create(rect.width, rect.height); @@ -953,6 +971,12 @@ void Scene_Battle_Rpg2k3::vUpdate() { break; } + // this is checked separately because we want normal events to be processed + // just not sub-events called by maniacs battle hooks. + if (state != State_Victory && state != State_Defeat && Game_Battle::ManiacProcessSubEvents()) { + break; + } + if (!CheckWait()) { break; } @@ -2209,7 +2233,9 @@ Scene_Battle_Rpg2k3::BattleActionReturn Scene_Battle_Rpg2k3::ProcessBattleAction b->GetBattlePosition().x, b->GetBattlePosition().y, damageTaken < 0 ? Font::ColorDefault : Font::ColorHeal, - std::to_string(std::abs(damageTaken))); + std::to_string(std::abs(damageTaken)), + b, + damageTaken < 0 ? Scene_Battle_Rpg2k3::FloatTextType::Damage : Scene_Battle_Rpg2k3::FloatTextType::Heal); } if (b->GetType() == Game_Battler::Type_Ally) { auto* sprite = static_cast(b)->GetActorBattleSprite(); @@ -2632,14 +2658,18 @@ Scene_Battle_Rpg2k3::BattleActionReturn Scene_Battle_Rpg2k3::ProcessBattleAction target->GetBattlePosition().x, target->GetBattlePosition().y, hp > 0 ? Font::ColorHeal : Font::ColorDefault, - std::to_string(std::abs(hp))); + std::to_string(std::abs(hp)), + target, + hp > 0 ? Scene_Battle_Rpg2k3::FloatTextType::Heal : Scene_Battle_Rpg2k3::FloatTextType::Damage); if (action->IsAbsorbHp()) { DrawFloatText( source->GetBattlePosition().x, source->GetBattlePosition().y, hp > 0 ? Font::ColorDefault : Font::ColorHeal, - std::to_string(std::abs(hp))); + std::to_string(std::abs(hp)), + source, + hp > 0 ? Scene_Battle_Rpg2k3::FloatTextType::Damage : Scene_Battle_Rpg2k3::FloatTextType::Heal); } } @@ -2660,7 +2690,9 @@ Scene_Battle_Rpg2k3::BattleActionReturn Scene_Battle_Rpg2k3::ProcessBattleAction target->GetBattlePosition().x, target->GetBattlePosition().y, 0, - lcf::Data::terms.miss); + lcf::Data::terms.miss, + target, + Scene_Battle_Rpg2k3::FloatTextType::Miss); } status_window->Refresh(); @@ -2905,7 +2937,9 @@ void Scene_Battle_Rpg2k3::OnEventHpChanged(Game_Battler* battler, int hp) { battler->GetBattlePosition().x, battler->GetBattlePosition().y, hp < 0 ? Font::ColorDefault : Font::ColorHeal, - std::to_string(std::abs(hp))); + std::to_string(std::abs(hp)), + battler, + hp < 0 ? Scene_Battle_Rpg2k3::FloatTextType::Damage : Scene_Battle_Rpg2k3::FloatTextType::Heal); } void Scene_Battle_Rpg2k3::RecreateSpWindow(Game_Battler* battler) { diff --git a/src/scene_battle_rpg2k3.h b/src/scene_battle_rpg2k3.h index d411cd309a..045d3ce6ac 100644 --- a/src/scene_battle_rpg2k3.h +++ b/src/scene_battle_rpg2k3.h @@ -123,7 +123,12 @@ class Scene_Battle_Rpg2k3 : public Scene_Battle { void RefreshCommandWindow(const Game_Actor* actor); void SetActiveActor(int idx); - void DrawFloatText(int x, int y, int color, StringView text); + enum class FloatTextType { + Damage = 0, + Heal = 1, + Miss = 2, + }; + void DrawFloatText(int x, int y, int color, StringView text, Game_Battler* battler, FloatTextType type); bool IsTransparent() const;