PkmnLib.NET/PkmnLib.Dynamic/Models/BattleFlow/MoveTurnExecutor.cs

222 lines
8.6 KiB
C#
Raw Normal View History

2024-08-10 09:18:10 +00:00
using PkmnLib.Dynamic.Events;
using PkmnLib.Dynamic.Models.Choices;
2024-08-10 09:18:10 +00:00
using PkmnLib.Dynamic.ScriptHandling;
using PkmnLib.Static.Moves;
using PkmnLib.Static.Utils;
namespace PkmnLib.Dynamic.Models.BattleFlow;
internal static class MoveTurnExecutor
{
internal static void ExecuteMoveChoice(IBattle battle, IMoveChoice moveChoice)
{
2024-08-10 09:18:10 +00:00
var chosenMove = moveChoice.ChosenMove;
var moveData = chosenMove.MoveData;
2024-08-10 09:18:10 +00:00
var moveDataName = moveData.Name;
moveChoice.RunScriptHook(x => x.ChangeMove(moveChoice, ref moveDataName));
if (moveData.Name != moveDataName)
{
if (!battle.Library.StaticLibrary.Moves.TryGet(moveDataName, out moveData))
{
throw new InvalidOperationException(
$"The move was changed to '{moveDataName}' by a script, but this move does not exist.");
}
var secondaryEffect = moveData.SecondaryEffect;
if (secondaryEffect != null)
{
2025-01-10 10:11:50 +00:00
if (moveChoice.User.Library.ScriptResolver.TryResolve(ScriptCategory.Move, secondaryEffect.Name,
secondaryEffect.Parameters, out var script))
{
moveChoice.Script.Set(script);
}
}
2024-08-10 09:18:10 +00:00
}
var targetType = moveData.Target;
var targets = TargetResolver.ResolveTargets(battle, moveChoice.TargetSide, moveChoice.TargetPosition, targetType);
byte numberOfHits = 1;
moveChoice.RunScriptHook(x => x.ChangeNumberOfHits(moveChoice, ref numberOfHits));
if (numberOfHits == 0)
{
return;
}
var executingMove = new ExecutingMoveImpl(targets, numberOfHits, moveChoice.User, chosenMove, moveData,
moveChoice.Script);
var prevented = false;
executingMove.RunScriptHook(x => x.PreventMove(executingMove, ref prevented));
if (prevented)
return;
byte ppUsed = 1;
// TODO: Modify the PP used by the move.
if (!executingMove.ChosenMove.TryUse(ppUsed))
return;
battle.EventHook.Invoke(new MoveUseEvent(executingMove));
var failed = false;
executingMove.RunScriptHook(x => x.FailMove(executingMove, ref failed));
if (failed)
{
// TODO: fail handling
return;
}
var stopped = false;
executingMove.RunScriptHook(x => x.StopBeforeMove(executingMove, ref stopped));
if (stopped)
return;
executingMove.RunScriptHook(x => x.OnBeforeMove(executingMove));
foreach (var target in targets.WhereNotNull())
{
ExecuteMoveChoiceForTarget(battle, executingMove, target);
}
}
private static void ExecuteMoveChoiceForTarget(IBattle battle, IExecutingMove executingMove, IPokemon target)
{
var failed = false;
target.RunScriptHook(x => x.FailIncomingMove(executingMove, target, ref failed));
if (failed)
{
// TODO: fail handling
return;
}
2024-08-10 09:18:10 +00:00
var isInvulnerable = false;
target.RunScriptHook(x => x.IsInvulnerableToMove(executingMove, target, ref isInvulnerable));
if (isInvulnerable)
{
// TODO: event?
return;
}
var numberOfHits = executingMove.NumberOfHits;
var targetHitStat = executingMove.GetTargetIndex(target) * numberOfHits;
for (byte i = 0; i < numberOfHits; i++)
{
if (battle.HasEnded)
break;
if (executingMove.User.IsFainted)
break;
if (target.IsFainted)
break;
var hitIndex = i;
var useMove = executingMove.UseMove;
var hitType = useMove.MoveType;
executingMove.RunScriptHook(x => x.ChangeMoveType(executingMove, target, hitIndex, ref hitType));
var hitData = (HitData)executingMove.GetDataFromRawIndex(targetHitStat + i);
hitData.Type = hitType;
var effectiveness = battle.Library.StaticLibrary.Types.GetEffectiveness(hitType, target.Types);
executingMove.RunScriptHook(x => x.ChangeEffectiveness(executingMove, target, hitIndex, ref effectiveness));
hitData.Effectiveness = effectiveness;
var blockCritical = false;
executingMove.RunScriptHook(x => x.BlockCriticalHit(executingMove, target, hitIndex, ref blockCritical));
target.RunScriptHook(x => x.BlockIncomingCriticalHit(executingMove, target, hitIndex, ref blockCritical));
if (!blockCritical)
{
var critical = battle.Library.DamageCalculator.IsCritical(battle, executingMove, target, hitIndex);
hitData.IsCritical = critical;
}
var basePower = battle.Library.DamageCalculator.GetBasePower(executingMove, target, hitIndex, hitData);
hitData.BasePower = basePower;
hitData.Damage = battle.Library.DamageCalculator.GetDamage(executingMove, target, hitIndex, hitData);
var accuracy = useMove.Accuracy;
// If the accuracy is 255, the move should always hit, and as such we should not allow
// modifying it.
if (accuracy != 255)
{
2024-12-22 11:19:42 +00:00
accuracy = battle.Library.StatCalculator.CalculateModifiedAccuracy(executingMove, target,
hitIndex, accuracy);
2024-08-10 09:18:10 +00:00
}
if (accuracy < 100 && battle.Random.GetInt(100) >= accuracy)
{
executingMove.RunScriptHook(x => x.OnMoveMiss(executingMove, target));
battle.EventHook.Invoke(new MoveMissEvent(executingMove));
break;
}
if (useMove.Category == MoveCategory.Status)
{
var secondaryEffect = useMove.SecondaryEffect;
if (secondaryEffect != null)
{
var chance = secondaryEffect.Chance;
if (chance < 0 || battle.Random.EffectChance(chance, executingMove, target, hitIndex))
{
executingMove.RunScriptHook(x => x.OnSecondaryEffect(executingMove, target, hitIndex));
}
}
}
// else if the move is a physical or special move, we should apply the damage.
else
{
var currentHealth = target.CurrentHealth;
if (hitData.Damage > currentHealth)
{
hitData.Damage = currentHealth;
}
var damage = hitData.Damage;
if (damage > 0)
{
var hitEventBatch = new EventBatchId();
battle.EventHook.Invoke(new MoveHitEvent(executingMove, hitData, target)
{
2024-08-23 07:24:00 +00:00
BatchId = hitEventBatch,
2024-08-10 09:18:10 +00:00
});
target.Damage(damage, DamageSource.MoveDamage, hitEventBatch);
if (!target.IsFainted)
target.RunScriptHook(x => x.OnIncomingHit(executingMove, target, hitIndex));
else
executingMove.RunScriptHook(x => x.OnOpponentFaints(executingMove, target, hitIndex));
if (!target.IsFainted)
{
var secondaryEffect = useMove.SecondaryEffect;
if (secondaryEffect != null)
{
var preventSecondary = false;
target.RunScriptHook(x => x.PreventSecondaryEffect(executingMove, target, hitIndex, ref preventSecondary));
if (!preventSecondary)
{
var chance = secondaryEffect.Chance;
if (chance < 0 || battle.Random.EffectChance(chance, executingMove, target, hitIndex))
{
executingMove.RunScriptHook(x => x.OnSecondaryEffect(executingMove, target, hitIndex));
}
}
}
}
}
}
}
if (numberOfHits == 0)
{
target.RunScriptHook(x => x.OnMoveMiss(executingMove, target));
battle.EventHook.Invoke(new MoveMissEvent(executingMove));
}
if (!executingMove.User.IsFainted)
{
executingMove.RunScriptHook(x => x.OnAfterHits(executingMove, target));
}
}
}