Implements move execution for battle
This commit is contained in:
parent
a049dda240
commit
488c717c5a
@ -1,5 +1,8 @@
|
|||||||
namespace PkmnLib.Dynamic.Events;
|
namespace PkmnLib.Dynamic.Events;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event triggered when a turn ends.
|
||||||
|
/// </summary>
|
||||||
public class EndTurnEvent : IEventData
|
public class EndTurnEvent : IEventData
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -8,7 +8,7 @@ namespace PkmnLib.Dynamic.Events;
|
|||||||
/// For example, when a Pokemon gets hurt by poison, we want to show the purple poison animation and the damage at the
|
/// For example, when a Pokemon gets hurt by poison, we want to show the purple poison animation and the damage at the
|
||||||
/// same time. This is done by batching the events together.
|
/// same time. This is done by batching the events together.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public record struct EventBatchId()
|
public readonly record struct EventBatchId()
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The unique identifier for this batch of events.
|
/// The unique identifier for this batch of events.
|
||||||
|
35
PkmnLib.Dynamic/Events/MoveHitEvent.cs
Normal file
35
PkmnLib.Dynamic/Events/MoveHitEvent.cs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
using PkmnLib.Dynamic.Models;
|
||||||
|
|
||||||
|
namespace PkmnLib.Dynamic.Events;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event triggered when a move hits.
|
||||||
|
/// </summary>
|
||||||
|
public class MoveHitEvent : IEventData
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The move that is being executed.
|
||||||
|
/// </summary>
|
||||||
|
public IExecutingMove ExecutingMove { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Data about the hit.
|
||||||
|
/// </summary>
|
||||||
|
public IHitData HitData { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The target of the move.
|
||||||
|
/// </summary>
|
||||||
|
public IPokemon Target { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="MoveHitEvent"/>
|
||||||
|
public MoveHitEvent(IExecutingMove executingMove, IHitData hitData, IPokemon target)
|
||||||
|
{
|
||||||
|
ExecutingMove = executingMove;
|
||||||
|
HitData = hitData;
|
||||||
|
Target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public EventBatchId BatchId { get; init; }
|
||||||
|
}
|
23
PkmnLib.Dynamic/Events/MoveMissEvent.cs
Normal file
23
PkmnLib.Dynamic/Events/MoveMissEvent.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using PkmnLib.Dynamic.Models;
|
||||||
|
|
||||||
|
namespace PkmnLib.Dynamic.Events;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Triggered when a move misses.
|
||||||
|
/// </summary>
|
||||||
|
public class MoveMissEvent : IEventData
|
||||||
|
{
|
||||||
|
/// <inheritdoc cref="MoveMissEvent"/>
|
||||||
|
public MoveMissEvent(IExecutingMove executingMove)
|
||||||
|
{
|
||||||
|
ExecutingMove = executingMove;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Data about the move that missed.
|
||||||
|
/// </summary>
|
||||||
|
public IExecutingMove ExecutingMove { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public EventBatchId BatchId { get; init; }
|
||||||
|
}
|
23
PkmnLib.Dynamic/Events/MoveUseEvent.cs
Normal file
23
PkmnLib.Dynamic/Events/MoveUseEvent.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using PkmnLib.Dynamic.Models;
|
||||||
|
|
||||||
|
namespace PkmnLib.Dynamic.Events;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs when a move is used, before the move is executed.
|
||||||
|
/// </summary>
|
||||||
|
public class MoveUseEvent : IEventData
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The move that is being executed.
|
||||||
|
/// </summary>
|
||||||
|
public IExecutingMove ExecutingMove { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="MoveUseEvent"/>
|
||||||
|
public MoveUseEvent(IExecutingMove executingMove)
|
||||||
|
{
|
||||||
|
ExecutingMove = executingMove;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public EventBatchId BatchId { get; init; }
|
||||||
|
}
|
@ -221,7 +221,7 @@ public class BattleImpl : ScriptSource, IBattle
|
|||||||
if (choice is IMoveChoice moveChoice)
|
if (choice is IMoveChoice moveChoice)
|
||||||
{
|
{
|
||||||
// TODO: Hook to change number of PP needed.
|
// TODO: Hook to change number of PP needed.
|
||||||
if (moveChoice.UsedMove.CurrentPp < 1)
|
if (moveChoice.ChosenMove.CurrentPp < 1)
|
||||||
return false;
|
return false;
|
||||||
// TODO: Validate target
|
// TODO: Validate target
|
||||||
}
|
}
|
||||||
@ -263,7 +263,7 @@ public class BattleImpl : ScriptSource, IBattle
|
|||||||
throw new InvalidOperationException("Choice is null.");
|
throw new InvalidOperationException("Choice is null.");
|
||||||
if (choice is IMoveChoice moveChoice)
|
if (choice is IMoveChoice moveChoice)
|
||||||
{
|
{
|
||||||
var priority = moveChoice.UsedMove.MoveData.Priority;
|
var priority = moveChoice.ChosenMove.MoveData.Priority;
|
||||||
choice.RunScriptHook(script => script.ChangePriority(moveChoice, ref priority));
|
choice.RunScriptHook(script => script.ChangePriority(moveChoice, ref priority));
|
||||||
moveChoice.Priority = priority;
|
moveChoice.Priority = priority;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
|
using PkmnLib.Dynamic.Events;
|
||||||
using PkmnLib.Dynamic.Models.Choices;
|
using PkmnLib.Dynamic.Models.Choices;
|
||||||
|
using PkmnLib.Dynamic.ScriptHandling;
|
||||||
|
using PkmnLib.Static.Moves;
|
||||||
|
using PkmnLib.Static.Utils;
|
||||||
|
|
||||||
namespace PkmnLib.Dynamic.Models.BattleFlow;
|
namespace PkmnLib.Dynamic.Models.BattleFlow;
|
||||||
|
|
||||||
@ -6,7 +10,204 @@ internal static class MoveTurnExecutor
|
|||||||
{
|
{
|
||||||
internal static void ExecuteMoveChoice(IBattle battle, IMoveChoice moveChoice)
|
internal static void ExecuteMoveChoice(IBattle battle, IMoveChoice moveChoice)
|
||||||
{
|
{
|
||||||
|
var chosenMove = moveChoice.ChosenMove;
|
||||||
|
var moveData = chosenMove.MoveData;
|
||||||
|
|
||||||
|
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.");
|
||||||
|
}
|
||||||
|
// FIXME: Change the script on the move when it is changed.
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
executingMove.RunScriptHook(x => x.ChangeAccuracy(executingMove, target, hitIndex, ref accuracy));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Deal with accuracy/evasion stats.
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
BatchId = hitEventBatch
|
||||||
|
});
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
96
PkmnLib.Dynamic/Models/BattleFlow/TargetResolver.cs
Normal file
96
PkmnLib.Dynamic/Models/BattleFlow/TargetResolver.cs
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
using PkmnLib.Static.Moves;
|
||||||
|
|
||||||
|
namespace PkmnLib.Dynamic.Models.BattleFlow;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper class for resolving the targets of a move.
|
||||||
|
/// </summary>
|
||||||
|
public static class TargetResolver
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Get the targets of a move based on the target type, and the selected side and position to target.
|
||||||
|
/// </summary>
|
||||||
|
public static IReadOnlyList<IPokemon?> ResolveTargets(IBattle battle, byte side, byte position, MoveTarget target)
|
||||||
|
{
|
||||||
|
return target switch
|
||||||
|
{
|
||||||
|
MoveTarget.Adjacent or MoveTarget.AdjacentAlly or MoveTarget.AdjacentAllySelf or MoveTarget.AdjacentOpponent
|
||||||
|
or MoveTarget.Any or MoveTarget.RandomOpponent
|
||||||
|
or MoveTarget.SelfUse => [battle.GetPokemon(side, position)],
|
||||||
|
MoveTarget.All => GetAllTargets(battle),
|
||||||
|
MoveTarget.AllAdjacentOpponent => GetAllAdjacentAndOpponent(battle, side, position),
|
||||||
|
MoveTarget.AllAdjacent => GetAllAdjacent(battle, side, position),
|
||||||
|
MoveTarget.AllAlly => battle.Sides[side].Pokemon.ToList(),
|
||||||
|
MoveTarget.AllOpponent => battle.Sides[GetOppositeSide(side)].Pokemon.ToList(),
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(target), target, null)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<IPokemon?> GetAllTargets(IBattle battle) =>
|
||||||
|
battle.Sides.SelectMany(x => x.Pokemon).ToList();
|
||||||
|
|
||||||
|
private static byte GetOppositeSide(byte side) => side == 0 ? (byte)1 : (byte)0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all Pokémon that are adjacent to of directly opposite of a Pokémon. This means the target,
|
||||||
|
/// the Pokémon left of it, the Pokémon right of it, and the Pokémon opposite of it.
|
||||||
|
/// </summary>
|
||||||
|
private static IReadOnlyList<IPokemon?> GetAllAdjacentAndOpponent(IBattle battle, byte side, byte position)
|
||||||
|
{
|
||||||
|
var left = position - 1;
|
||||||
|
var right = position + 1;
|
||||||
|
if (left < 0 && right >= battle.PositionsPerSide)
|
||||||
|
{
|
||||||
|
return [battle.GetPokemon(side, position), battle.GetPokemon(GetOppositeSide(side), position)];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (left < 0)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
[
|
||||||
|
battle.GetPokemon(side, position), battle.GetPokemon(GetOppositeSide(side), position),
|
||||||
|
battle.GetPokemon(side, (byte)right)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (right >= battle.PositionsPerSide)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
[
|
||||||
|
battle.GetPokemon(side, position), battle.GetPokemon(GetOppositeSide(side), position),
|
||||||
|
battle.GetPokemon(side, (byte)left)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
[
|
||||||
|
battle.GetPokemon(side, position), battle.GetPokemon(GetOppositeSide(side), position),
|
||||||
|
battle.GetPokemon(side, (byte)left), battle.GetPokemon(side, (byte)right)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<IPokemon?> GetAllAdjacent(IBattle battle, byte side, byte position)
|
||||||
|
{
|
||||||
|
var left = position - 1;
|
||||||
|
var right = position + 1;
|
||||||
|
if (left < 0 && right >= battle.PositionsPerSide)
|
||||||
|
{
|
||||||
|
return [battle.GetPokemon(side, position)];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (left < 0)
|
||||||
|
{
|
||||||
|
return [battle.GetPokemon(side, position), battle.GetPokemon(side, (byte)right)];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (right >= battle.PositionsPerSide)
|
||||||
|
{
|
||||||
|
return [battle.GetPokemon(side, position), battle.GetPokemon(side, (byte)left)];
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
[
|
||||||
|
battle.GetPokemon(side, position), battle.GetPokemon(side, (byte)left), battle.GetPokemon(side, (byte)right)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -4,8 +4,14 @@ using PkmnLib.Static.Utils;
|
|||||||
|
|
||||||
namespace PkmnLib.Dynamic.Models.BattleFlow;
|
namespace PkmnLib.Dynamic.Models.BattleFlow;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper class for handling the running of a turn in a battle.
|
||||||
|
/// </summary>
|
||||||
public static class TurnRunner
|
public static class TurnRunner
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Runs a single turn in a battle to completion.
|
||||||
|
/// </summary>
|
||||||
public static void RunTurn(this IBattle battle)
|
public static void RunTurn(this IBattle battle)
|
||||||
{
|
{
|
||||||
var queue = battle.ChoiceQueue;
|
var queue = battle.ChoiceQueue;
|
||||||
|
@ -10,7 +10,7 @@ public interface IMoveChoice : ITurnChoice
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The move that is used.
|
/// The move that is used.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ILearnedMove UsedMove { get; }
|
ILearnedMove ChosenMove { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The side the move is targeted at.
|
/// The side the move is targeted at.
|
||||||
@ -39,13 +39,13 @@ public class MoveChoice : TurnChoice, IMoveChoice
|
|||||||
/// <inheritdoc cref="MoveChoice"/>
|
/// <inheritdoc cref="MoveChoice"/>
|
||||||
public MoveChoice(IPokemon user, ILearnedMove usedMove, byte targetSide, byte targetPosition) : base(user)
|
public MoveChoice(IPokemon user, ILearnedMove usedMove, byte targetSide, byte targetPosition) : base(user)
|
||||||
{
|
{
|
||||||
UsedMove = usedMove;
|
ChosenMove = usedMove;
|
||||||
TargetSide = targetSide;
|
TargetSide = targetSide;
|
||||||
TargetPosition = targetPosition;
|
TargetPosition = targetPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ILearnedMove UsedMove { get; }
|
public ILearnedMove ChosenMove { get; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public byte TargetSide { get; }
|
public byte TargetSide { get; }
|
||||||
|
@ -5,6 +5,7 @@ namespace PkmnLib.Dynamic.Models.Choices;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class TurnChoiceComparer : IComparer<ITurnChoice>
|
public class TurnChoiceComparer : IComparer<ITurnChoice>
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc cref="TurnChoiceComparer"/>
|
||||||
public static TurnChoiceComparer Instance { get; } = new();
|
public static TurnChoiceComparer Instance { get; } = new();
|
||||||
|
|
||||||
private enum CompareValues
|
private enum CompareValues
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using PkmnLib.Dynamic.ScriptHandling;
|
using PkmnLib.Dynamic.ScriptHandling;
|
||||||
using PkmnLib.Static;
|
using PkmnLib.Static;
|
||||||
using PkmnLib.Static.Moves;
|
using PkmnLib.Static.Moves;
|
||||||
|
using PkmnLib.Static.Utils;
|
||||||
using PkmnLib.Static.Utils.Errors;
|
using PkmnLib.Static.Utils.Errors;
|
||||||
|
|
||||||
namespace PkmnLib.Dynamic.Models;
|
namespace PkmnLib.Dynamic.Models;
|
||||||
@ -132,11 +133,11 @@ public interface IExecutingMove : IScriptSource
|
|||||||
/// <inheritdoc cref="IExecutingMove"/>
|
/// <inheritdoc cref="IExecutingMove"/>
|
||||||
public class ExecutingMoveImpl : ScriptSource, IExecutingMove
|
public class ExecutingMoveImpl : ScriptSource, IExecutingMove
|
||||||
{
|
{
|
||||||
private readonly List<IPokemon?> _targets;
|
private readonly IReadOnlyList<IPokemon?> _targets;
|
||||||
private readonly IHitData[] _hits;
|
private readonly IHitData[] _hits;
|
||||||
|
|
||||||
/// <inheritdoc cref="ExecutingMoveImpl"/>
|
/// <inheritdoc cref="ExecutingMoveImpl"/>
|
||||||
public ExecutingMoveImpl(List<IPokemon?> targets, byte numberOfHits, IPokemon user, ILearnedMove chosenMove,
|
public ExecutingMoveImpl(IReadOnlyList<IPokemon?> targets, byte numberOfHits, IPokemon user, ILearnedMove chosenMove,
|
||||||
IMoveData useMove, ScriptContainer script)
|
IMoveData useMove, ScriptContainer script)
|
||||||
{
|
{
|
||||||
_targets = targets;
|
_targets = targets;
|
||||||
|
@ -105,7 +105,7 @@ public abstract class Script
|
|||||||
/// This function allows you to change the move that is used during execution. This is useful for
|
/// This function allows you to change the move that is used during execution. This is useful for
|
||||||
/// moves such as metronome, where the move chosen actually differs from the move used.
|
/// moves such as metronome, where the move chosen actually differs from the move used.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual void ChangeMove(IMoveChoice choice, ref string moveName)
|
public virtual void ChangeMove(IMoveChoice choice, ref StringKey moveName)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,14 +189,14 @@ public abstract class Script
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// This function allows a script to block an outgoing move from being critical.
|
/// This function allows a script to block an outgoing move from being critical.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual void BlockCriticalHit(IExecutingMove move, IPokemon target, ref bool block)
|
public virtual void BlockCriticalHit(IExecutingMove move, IPokemon target, byte hit, ref bool block)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This function allows a script to block an incoming move from being critical.
|
/// This function allows a script to block an incoming move from being critical.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual void BlockIncomingCriticalHit(IExecutingMove move, IPokemon target, ref bool block)
|
public virtual void BlockIncomingCriticalHit(IExecutingMove move, IPokemon target, byte hit, ref bool block)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using PkmnLib.Static.Utils;
|
|
||||||
|
|
||||||
namespace PkmnLib.Dynamic.ScriptHandling;
|
namespace PkmnLib.Dynamic.ScriptHandling;
|
||||||
|
|
||||||
@ -44,6 +43,10 @@ public class ScriptContainer : IEnumerable<ScriptContainer>
|
|||||||
return GetEnumerator();
|
return GetEnumerator();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Assigns a new script to this container. If there was a script already, it is removed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="script"></param>
|
||||||
public void Set(Script script)
|
public void Set(Script script)
|
||||||
{
|
{
|
||||||
if (Script is not null)
|
if (Script is not null)
|
||||||
|
@ -25,6 +25,10 @@ public static class ScriptExecution
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Executes a hook on all scripts in a list of sources. Note that this does not walk through the parents of the
|
||||||
|
/// sources, but only the sources themselves.
|
||||||
|
/// </summary>
|
||||||
public static void RunScriptHook(this IReadOnlyList<IEnumerable<ScriptContainer>> source, Action<Script> hook)
|
public static void RunScriptHook(this IReadOnlyList<IEnumerable<ScriptContainer>> source, Action<Script> hook)
|
||||||
{
|
{
|
||||||
foreach (var container in source.SelectMany(x => x))
|
foreach (var container in source.SelectMany(x => x))
|
||||||
|
@ -27,7 +27,7 @@ public interface IReadOnlyTypeLibrary
|
|||||||
/// Gets the effectiveness for a single attacking type against an amount of defending types.
|
/// Gets the effectiveness for a single attacking type against an amount of defending types.
|
||||||
/// This is equivalent to running get_single_effectiveness on each defending type, and multiplying the results with each other.
|
/// This is equivalent to running get_single_effectiveness on each defending type, and multiplying the results with each other.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
float GetEffectiveness(TypeIdentifier attacking, TypeIdentifier[] defending);
|
float GetEffectiveness(TypeIdentifier attacking, IReadOnlyList<TypeIdentifier> defending);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -66,7 +66,7 @@ public class TypeLibrary : IReadOnlyTypeLibrary
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public float GetEffectiveness(TypeIdentifier attacking, TypeIdentifier[] defending) =>
|
public float GetEffectiveness(TypeIdentifier attacking, IReadOnlyList<TypeIdentifier> defending) =>
|
||||||
defending.Aggregate<TypeIdentifier, float>(1,
|
defending.Aggregate<TypeIdentifier, float>(1,
|
||||||
(current, type) => current * GetSingleEffectiveness(attacking, type));
|
(current, type) => current * GetSingleEffectiveness(attacking, type));
|
||||||
|
|
||||||
|
@ -10,4 +10,27 @@ public static class EnumerableHelpers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static IEnumerable<T> WhereNotNull<T>(this IEnumerable<T?> enumerable) where T : class =>
|
public static IEnumerable<T> WhereNotNull<T>(this IEnumerable<T?> enumerable) where T : class =>
|
||||||
enumerable.Where(x => x is not null)!;
|
enumerable.Where(x => x is not null)!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds the index of a value in an enumerable. Returns -1 if the value is not found.
|
||||||
|
/// </summary>
|
||||||
|
public static int IndexOf<T>(this IEnumerable<T> enumerable, T value)
|
||||||
|
{
|
||||||
|
var index = 0;
|
||||||
|
foreach (var item in enumerable)
|
||||||
|
{
|
||||||
|
if (item is null)
|
||||||
|
{
|
||||||
|
if (value is null)
|
||||||
|
return index;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (item.Equals(value))
|
||||||
|
return index;
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user