#include "TurnHandler.hpp" #include "../../Core/Exceptions/NotImplementedException.hpp" #include "../Models/Battle.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()) { 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::Switch: return ExecuteSwitchChoice(dynamic_cast(choice)); case TurnChoiceKind::Item: 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, 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); } } void TurnHandler::ExecuteSwitchChoice(CreatureLib::Battling::SwitchTurnChoice* choice) { bool preventSwitch = false; HOOK(PreventSelfSwitch, choice, choice, preventSwitch); if (preventSwitch) { return; } // HOOK: PreventOpponentSwitch for each opponent. auto user = choice->GetUser(); user->ClearVolatileScripts(); auto userSide = user->GetBattleSide(); auto userIndex = userSide->GetCreatureIndex(user); userSide->SetCreature(choice->GetNewCreature(), userIndex); }