diff --git a/src/Battling/EventHooks/EventDataClasses.hpp b/src/Battling/EventHooks/EventDataClasses.hpp index b465ecc..47fc15a 100644 --- a/src/Battling/EventHooks/EventDataClasses.hpp +++ b/src/Battling/EventHooks/EventDataClasses.hpp @@ -9,6 +9,7 @@ #include "Events/DisplayTextEvent.hpp" #include "Events/EventData.hpp" #include "Events/ExperienceGainEvent.hpp" +#include "Events/FailEvent.hpp" #include "Events/FaintEvent.hpp" #include "Events/HealEvent.hpp" #include "Events/MissEvent.hpp" diff --git a/src/Battling/EventHooks/Events/FailEvent.hpp b/src/Battling/EventHooks/Events/FailEvent.hpp new file mode 100644 index 0000000..449c204 --- /dev/null +++ b/src/Battling/EventHooks/Events/FailEvent.hpp @@ -0,0 +1,16 @@ +#ifndef FAILEVENT_HPP +#define FAILEVENT_HPP +#include "EventData.hpp" + +namespace CreatureLib::Battling { + class FailEvent final : public EventData { + ArbUt::BorrowedPtr _creature; + + public: + FailEvent(ArbUt::BorrowedPtr c) noexcept : _creature(c) {} + EventDataKind GetKind() const noexcept override { return EventDataKind ::Faint; } + const ArbUt::BorrowedPtr& GetCreature() const noexcept { return _creature; } + }; +} + +#endif // FAILEVENT_HPP diff --git a/src/Battling/Flow/TurnHandler.cpp b/src/Battling/Flow/TurnHandler.cpp index 31e8615..6944a26 100644 --- a/src/Battling/Flow/TurnHandler.cpp +++ b/src/Battling/Flow/TurnHandler.cpp @@ -63,6 +63,13 @@ void TurnHandler::ExecuteChoice(ArbUt::BorrowedPtr choice) { } } +#define FAIL_HANDLING(source, user, target) \ + battle.GetValue()->TriggerEventListener(user); \ + HOOK(OnFail, source, user) \ + if ((target) != (void*)0) { \ + HOOK(OnOpponentFail, (target), user) \ + } + void TurnHandler::ExecuteAttackChoice(const ArbUt::BorrowedPtr& choice) { auto battle = choice->GetUser()->GetBattle(); auto attackName = choice->GetAttack()->GetAttack()->GetName(); @@ -77,20 +84,20 @@ void TurnHandler::ExecuteAttackChoice(const ArbUt::BorrowedPtr try_creature(targets = TargetResolver::ResolveTargets(choice->GetTarget(), targetType, battle.GetValue()); , "Exception during target determination"); - auto attack = new ExecutingAttack(targets, 1, choice->GetUser(), choice->GetAttack(), choice->GetAttackScript()); + auto attackScoped = ArbUt::ScopedPtr( + new ExecutingAttack(targets, 1, choice->GetUser(), choice->GetAttack(), choice->GetAttackScript())); bool prevented = false; - HOOK(PreventAttack, attack, attack, &prevented); + HOOK(PreventAttack, attackScoped, attackScoped, &prevented); if (prevented) { - delete attack; return; } // HOOK: override targets if (!choice->GetAttack()->TryUse(1)) { - delete attack; return; } + auto* attack = attackScoped.TakeOwnership(); battle.GetValue()->TriggerEventListener(attack); battle.GetValue()->RegisterHistoryElement(attack); @@ -98,7 +105,7 @@ void TurnHandler::ExecuteAttackChoice(const ArbUt::BorrowedPtr bool fail = false; HOOK(FailAttack, attack, attack, &fail); if (fail) { - // TODO: Fail handling. + FAIL_HANDLING(attack, choice->GetUser(), (ScriptSource*)nullptr); return; } @@ -120,16 +127,17 @@ void TurnHandler::ExecuteAttackChoice(const ArbUt::BorrowedPtr void TurnHandler::HandleAttackForTarget(ExecutingAttack* attack, const ArbUt::BorrowedPtr& target) { EnsureNotNull(attack) - auto& user = attack->GetUser(); - auto& battle = user->GetBattle(); + const auto& user = attack->GetUser(); + const auto& battle = user->GetBattle(); Ensure(battle.HasValue()) - if (battle.GetValue()->HasEnded()) + if (battle.GetValue()->HasEnded()) { return; + } bool fail = false; HOOK(FailIncomingAttack, target, attack, target.GetRaw(), &fail); if (fail) { - // TODO: Fail handling. + FAIL_HANDLING(attack, user, target); return; } @@ -147,21 +155,22 @@ void TurnHandler::HandleAttackForTarget(ExecutingAttack* attack, const ArbUt::Bo return; } - auto& learnedAttack = attack->GetAttack(); - auto& attackData = learnedAttack->GetAttack(); + const auto& learnedAttack = attack->GetAttack(); + const auto& attackData = learnedAttack->GetAttack(); - auto& library = battle.GetValue()->GetLibrary(); - auto& dmgLibrary = library->GetDamageLibrary(); - auto& typeLibrary = library->GetTypeLibrary(); - auto& miscLibrary = library->GetMiscLibrary(); + const auto& library = battle.GetValue()->GetLibrary(); + const auto& dmgLibrary = library->GetDamageLibrary(); + const auto& typeLibrary = library->GetTypeLibrary(); + const auto& miscLibrary = library->GetMiscLibrary(); EnsureNotNull(dmgLibrary) EnsureNotNull(typeLibrary) EnsureNotNull(miscLibrary) - auto hitIterator = attack->GetTargetIteratorBegin(target); + auto* hitIterator = attack->GetTargetIteratorBegin(target); for (uint8_t hitIndex = 0; hitIndex < numberOfHits; hitIndex++) { - if (battle.GetValue()->HasEnded()) + if (battle.GetValue()->HasEnded()) { return; + } if (user->IsFainted()) { break; } @@ -182,16 +191,15 @@ void TurnHandler::HandleAttackForTarget(ExecutingAttack* attack, const ArbUt::Bo if (attackData->GetCategory() == Library::AttackCategory::Status) { if (attackData->HasSecondaryEffect()) { try { - auto& effect = attackData->GetSecondaryEffect(); - bool hasSecondaryEffect; - if (effect->GetChance() == -1) { - hasSecondaryEffect = true; - } else { - hasSecondaryEffect = - battle.GetValue()->GetRandom()->EffectChance(effect->GetChance(), attack, target.GetRaw()); - } + const auto& effect = attackData->GetSecondaryEffect(); + bool hasSecondaryEffect = + effect->GetChance() == -1 || + battle.GetValue()->GetRandom()->EffectChance(effect->GetChance(), attack, target.GetRaw()); if (hasSecondaryEffect) { HOOK(OnSecondaryEffect, attack, attack, target.GetRaw(), hitIndex); + if (hit.HasFailed()) { + FAIL_HANDLING(attack, user, target); + } } } catch (const ArbUt::Exception& e) { throw e; @@ -213,17 +221,16 @@ void TurnHandler::HandleAttackForTarget(ExecutingAttack* attack, const ArbUt::Bo HOOK(PreventSecondaryEffects, target, attack, target.GetRaw(), hitIndex, &preventSecondary); if (!preventSecondary) { try { - auto& effect = attackData->GetSecondaryEffect(); - bool hasSecondaryEffect; - if (effect->GetChance() == -1) { - hasSecondaryEffect = true; - } else { - auto random = battle.GetValue()->GetRandom(); - EnsureNotNull(random); - hasSecondaryEffect = random->EffectChance(effect->GetChance(), attack, target.GetRaw()); - } + const auto& effect = attackData->GetSecondaryEffect(); + bool hasSecondaryEffect = + effect->GetChance() == -1 || battle.GetValue()->GetRandom()->EffectChance( + effect->GetChance(), attack, target.GetRaw()); if (hasSecondaryEffect) { HOOK(OnSecondaryEffect, attack, attack, target.GetRaw(), hitIndex); + if (hit.HasFailed()) { + battle.GetValue()->TriggerEventListener(user); + FAIL_HANDLING(attack, user, target); + } } } catch (const ArbUt::Exception& e) { throw e; diff --git a/src/Battling/Models/ExecutingAttack.hpp b/src/Battling/Models/ExecutingAttack.hpp index 1a104be..4636d7f 100644 --- a/src/Battling/Models/ExecutingAttack.hpp +++ b/src/Battling/Models/ExecutingAttack.hpp @@ -12,6 +12,7 @@ namespace CreatureLib::Battling { float _effectiveness = 1; uint32_t _damage = 0; uint8_t _type = 0; + bool _hasFailed = false; public: HitData() noexcept {} @@ -21,12 +22,14 @@ namespace CreatureLib::Battling { [[nodiscard]] inline float GetEffectiveness() const noexcept { return _effectiveness; } [[nodiscard]] inline uint32_t GetDamage() const noexcept { return _damage; } [[nodiscard]] inline uint8_t GetType() const noexcept { return _type; } + [[nodiscard]] inline bool HasFailed() const noexcept { return _hasFailed; } inline void SetCritical(bool value) noexcept { _critical = value; } inline void SetBasePower(uint8_t value) noexcept { _basePower = value; } inline void SetEffectiveness(float value) noexcept { _effectiveness = value; } inline void SetDamage(uint32_t value) noexcept { _damage = value; } inline void SetType(uint8_t value) noexcept { _type = value; } + inline void Fail() noexcept { _hasFailed = true; } }; private: diff --git a/src/Battling/ScriptHandling/BattleScript.hpp b/src/Battling/ScriptHandling/BattleScript.hpp index 4ceaa45..ed55c58 100644 --- a/src/Battling/ScriptHandling/BattleScript.hpp +++ b/src/Battling/ScriptHandling/BattleScript.hpp @@ -75,6 +75,9 @@ namespace CreatureLib::Battling { [[maybe_unused]] Creature* target, [[maybe_unused]] float* chance){}; virtual void ModifyIncomingEffectChance([[maybe_unused]] const ExecutingAttack* attack, [[maybe_unused]] Creature* target, [[maybe_unused]] float* chance){}; + + virtual void OnFail([[maybe_unused]] Creature* target){}; + virtual void OnOpponentFail([[maybe_unused]] Creature* target){}; }; }