#include "TurnHandler.hpp" #include "../Models/Battle.hpp" #include "../../Core/Exceptions/NotImplementedException.hpp" #include "../ScriptHandling/ScriptMacros.cpp" using namespace CreatureLib::Battling; void TurnHandler::RunTurn(Battle* battle, ChoiceQueue* queue) { for (auto choice: queue->GetInnerQueue()){ HOOK(OnBeforeTurn, choice, choice); } while (queue->HasNext()){ if (!battle->HasRecalledSlots()){ return; } auto item = queue->Dequeue(); ExecuteChoice(item); delete item; } queue->HasCompletedQueue = true; } void TurnHandler::ExecuteChoice(BaseTurnChoice *choice) { if (choice == nullptr) { return; } auto choiceKind = choice->GetKind(); if (choiceKind == TurnChoiceKind::Pass) { return; } auto user = choice->GetUser(); // If the user is fainted, we don't want to execute its choice. if (user->IsFainted()){ return; } auto battle = user->GetBattle(); // If the user is not in the field, we don't want to execute its choice. if (!battle->CreatureInField(user)){ return; } // If the choice is not valid, we don't want to execute it. if (!battle->CanUse(choice)){ return; } switch (choiceKind){ case TurnChoiceKind::Pass: throw NotReachableException(); case TurnChoiceKind::Attack: return ExecuteAttackChoice(dynamic_cast(choice)); case TurnChoiceKind::Item: case TurnChoiceKind::Switch: case TurnChoiceKind::RunAway: throw NotImplementedException(); } } void TurnHandler::ExecuteAttackChoice(AttackTurnChoice *choice) { auto attackName = choice->GetAttack()->GetAttack()->GetName(); HOOK(ChangeAttack, choice, choice, attackName); if (attackName != choice->GetAttack()->GetAttack()->GetName()){ //TODO: Change attack } // FIXME: Resolve all targets auto target = choice->GetUser()->GetBattle()->GetTarget(choice->GetTarget()); std::vector targets = {target}; auto attack = new ExecutingAttack(targets, 1, choice->GetUser(), choice->GetAttack(), choice->GetAttackScript()); bool prevented = false; HOOK(PreventAttack, attack, attack, prevented); if (prevented){ return; } //HOOK: override targets if (!choice->GetAttack()->TryUse(1)){ return; } //HOOK: check if attack fails bool fail = false; HOOK(FailAttack, attack, attack, fail); if (fail){ //TODO: Fail handling. return; } HOOK(StopBeforeAttack, attack, attack); HOOK(OnBeforeAttack, attack, attack); for (auto& kv: attack->GetTargets()){ HandleAttackForTarget(attack, kv.first, kv.second); } //TODO: We currently delete this, but we probably want to store this in a log, so scripts can look it up. delete attack; } void TurnHandler::HandleAttackForTarget(ExecutingAttack* attack, Creature *target, const ExecutingAttack::TargetData &targetData) { auto user = attack->GetUser(); ScriptSource* targetSource = target; ScriptSource* userSource = attack; bool fail = false; HOOK(FailIncomingAttack, targetSource, attack, target, fail); if (fail){ //TODO: Fail handling. return; } bool invulnerable = fail; HOOK(IsInvulnerable, targetSource, attack, target, invulnerable); if (invulnerable){ //TODO: We should probably do something when a target is invulnerable. return; } if (!targetData.IsHit()){ HOOK(OnAttackMiss, targetSource, attack, target); return; } auto numHits = targetData.GetNumberOfHits(); if (numHits == 0) return; auto attackData = attack->GetAttack()->GetAttack(); auto library = user->GetBattle()->GetLibrary(); auto dmgLibrary = library->GetDamageLibrary(); for (uint8_t hitIndex = 0; hitIndex < numHits; hitIndex++){ if (user->IsFainted()){ break; } if (target->IsFainted()){ // STOP, STOP! HE'S ALREADY DEAD ;_; break; } auto hit = targetData.GetHit(hitIndex); auto hitType = hit.GetType(); HOOK(ChangeAttackType, targetSource, attack, target, hitIndex, hitType); hit.SetEffectiveness(library->GetTypeLibrary()->GetEffectiveness(hitType, target->GetTypes())); hit.SetCritical(library->GetCriticalLibrary()->IsCritical(attack, target, hitIndex)); hit.SetBasePower(dmgLibrary->GetBasePower(attack, target, hitIndex)); hit.SetDamage(dmgLibrary->GetDamage(attack, target, hitIndex)); if (attackData->GetCategory() == Library::AttackCategory::Status){ HOOK(OnStatusMove, userSource, attack, target, hitIndex); } else{ auto damage = hit.GetDamage(); if (damage > target->GetCurrentHealth()){ damage = target->GetCurrentHealth(); hit.SetDamage(damage); } if (damage > 0){ target->Damage(damage, DamageSource::AttackDamage); bool preventSecondary = false; HOOK(PreventSecondaryEffects, targetSource, attack, target, hitIndex, preventSecondary); if (!preventSecondary){ HOOK(OnSecondaryEffect, userSource, attack, target, hitIndex); } } } } if (!user->IsFainted()){ HOOK(OnAfterHits, userSource, attack, target); } }