Initial setup for testing AI performance, random fixes
All checks were successful
Build / Build (push) Successful in 54s

This commit is contained in:
2025-07-05 13:56:33 +02:00
parent 4499927551
commit 32aaa5150a
33 changed files with 511 additions and 26 deletions

View File

@@ -66,6 +66,7 @@ public interface IBattle : IScriptSource, IDeepCloneable, IDisposable
/// <summary>
/// Whether the battle has ended.
/// </summary>
[MemberNotNull(nameof(Result))]
bool HasEnded { get; }
/// <summary>
@@ -231,6 +232,7 @@ public class BattleImpl : ScriptSource, IBattle
public IBattleRandom Random { get; }
/// <inheritdoc />
[MemberNotNull(nameof(Result))]
public bool HasEnded { get; private set; }
/// <inheritdoc />
@@ -320,7 +322,10 @@ public class BattleImpl : ScriptSource, IBattle
{
if (!choice.User.IsUsable)
return false;
if (HasForcedTurn(choice.User, out var forcedChoice) && choice != forcedChoice)
// Always allow moves such as Struggle. If we block this, we can run into an infinite loop
if (Library.MiscLibrary.IsReplacementChoice(choice))
return true;
if (HasForcedTurn(choice.User, out var forcedChoice) && !Equals(choice, forcedChoice))
return false;
if (choice is IMoveChoice moveChoice)

View File

@@ -49,5 +49,6 @@ public class BattlePartyImpl : IBattleParty
public bool IsResponsibleForIndex(ResponsibleIndex index) => _responsibleIndices.Contains(index);
/// <inheritdoc />
public bool HasPokemonNotInField() => Party.Any(x => x is { IsUsable: true, BattleData.IsOnBattlefield: false });
public bool HasPokemonNotInField() =>
Party.WhereNotNull().Any(x => x.IsUsable && x.BattleData?.IsOnBattlefield != true);
}

View File

@@ -27,4 +27,25 @@ public class FleeTurnChoice : TurnChoice, IFleeChoice
/// <inheritdoc />
public override void CollectScripts(List<IEnumerable<ScriptContainer>> scripts) => User.CollectScripts(scripts);
/// <inheritdoc />
public override string ToString() => $"FleeChoice: {User}";
protected bool Equals(FleeTurnChoice other) => other.User == User;
/// <inheritdoc />
public override bool Equals(object? obj)
{
if (obj is null)
return false;
if (ReferenceEquals(this, obj))
return true;
if (obj is not FleeTurnChoice other)
return false;
return Equals(other);
}
/// <inheritdoc />
public override int GetHashCode() =>
User?.GetHashCode() ?? 0;
}

View File

@@ -59,4 +59,28 @@ public class ItemChoice : TurnChoice, IItemChoice
{
User.CollectScripts(scripts);
}
/// <inheritdoc />
public override string ToString() =>
$"ItemChoice: User: {User}, Item: {Item.Name}, TargetSide: {TargetSide}, TargetPosition: {TargetPosition}";
protected bool Equals(ItemChoice other) =>
other.User == User && other.Item.Equals(Item) && other.TargetSide == TargetSide &&
other.TargetPosition == TargetPosition;
/// <inheritdoc />
public override bool Equals(object? obj)
{
if (obj is null)
return false;
if (ReferenceEquals(this, obj))
return true;
if (obj is not ItemChoice other)
return false;
return Equals(other);
}
/// <inheritdoc />
public override int GetHashCode() =>
HashCode.Combine(User, Item, TargetSide, TargetPosition);
}

View File

@@ -105,4 +105,28 @@ public class MoveChoice : TurnChoice, IMoveChoice
GetOwnScripts(scripts);
User.CollectScripts(scripts);
}
/// <inheritdoc />
public override string ToString() =>
$"MoveChoice: {ChosenMove.MoveData.Name}, Target: {TargetSide}:{TargetPosition}, User: {User}";
protected bool Equals(MoveChoice other) =>
other.User == User && other.ChosenMove.Equals(ChosenMove) && other.TargetSide == TargetSide &&
other.TargetPosition == TargetPosition;
/// <inheritdoc />
public override bool Equals(object? obj)
{
if (obj is null)
return false;
if (ReferenceEquals(this, obj))
return true;
if (obj is not MoveChoice other)
return false;
return Equals(other);
}
/// <inheritdoc />
public override int GetHashCode() =>
HashCode.Combine(User, ChosenMove, TargetSide, TargetPosition);
}

View File

@@ -30,4 +30,26 @@ public class PassChoice : TurnChoice, IPassChoice
{
User.CollectScripts(scripts);
}
/// <inheritdoc />
public override string ToString() =>
$"PassChoice: {User}";
protected bool Equals(PassChoice other) => other.User == User;
/// <inheritdoc />
public override bool Equals(object? obj)
{
if (obj is null)
return false;
if (ReferenceEquals(this, obj))
return true;
if (obj is not PassChoice other)
return false;
return Equals(other);
}
/// <inheritdoc />
public override int GetHashCode() =>
User?.GetHashCode() ?? 0;
}

View File

@@ -38,4 +38,27 @@ public class SwitchChoice : TurnChoice, ISwitchChoice
{
User.CollectScripts(scripts);
}
/// <inheritdoc />
public override string ToString() =>
$"SwitchChoice: {User} -> {SwitchTo}";
protected bool Equals(SwitchChoice other) =>
other.User == User && other.SwitchTo == SwitchTo;
/// <inheritdoc />
public override bool Equals(object? obj)
{
if (obj is null)
return false;
if (ReferenceEquals(this, obj))
return true;
if (obj is not SwitchChoice other)
return false;
return Equals(other);
}
/// <inheritdoc />
public override int GetHashCode() =>
User?.GetHashCode() ?? 0 ^ SwitchTo?.GetHashCode() ?? 0;
}

View File

@@ -138,7 +138,10 @@ public class LearnedMoveImpl : ILearnedMove
public bool TryUse(byte amount = 1)
{
if (CurrentPp < amount)
{
CurrentPp = 0;
return false;
}
CurrentPp -= amount;
return true;

View File

@@ -904,7 +904,7 @@ public class PokemonImpl : ScriptSource, IPokemon
changed = change switch
{
> 0 => StatBoost.IncreaseStatistic(stat, change),
< 0 => StatBoost.DecreaseStatistic(stat, change),
< 0 => StatBoost.DecreaseStatistic(stat, (sbyte)-change),
_ => changed,
};
if (!changed)
@@ -1003,7 +1003,21 @@ public class PokemonImpl : ScriptSource, IPokemon
Form = form;
Types = form.Types.ToList();
HeightInMeters = form.Height;
var newAbility = Form.GetAbility(AbilityIndex);
var abilityIndex = AbilityIndex;
abilityIndex = AbilityIndex.IsHidden switch
{
true when form.HiddenAbilities.Count <= abilityIndex.Index => new AbilityIndex
{
IsHidden = true, Index = (byte)(form.HiddenAbilities.Count - 1),
},
false when form.Abilities.Count <= abilityIndex.Index => new AbilityIndex
{
IsHidden = false, Index = (byte)(form.Abilities.Count - 1),
},
_ => abilityIndex,
};
var newAbility = Form.GetAbility(abilityIndex);
if (OverrideAbility == null && oldAbility != newAbility)
{
@@ -1122,6 +1136,7 @@ public class PokemonImpl : ScriptSource, IPokemon
BattleData.Battle.Sides[BattleData.SideIndex].MarkPositionAsUnfillable(BattleData.Position);
}
BattleData.BattleSide.MarkFaint(BattleData.Position);
BattleData.BattleSide.ForceClearPokemonFromField(BattleData.Position);
// Validate the battle state to see if the battle is over.
BattleData.Battle.ValidateBattleState();

View File

@@ -43,12 +43,12 @@ public interface IPokemonParty : IReadOnlyList<IPokemon?>, IDeepCloneable
}
/// <inheritdoc />
public class PokemonParty : IPokemonParty
public class PokemonPartyImpl : IPokemonParty
{
private readonly IPokemon?[] _pokemon;
/// <inheritdoc cref="PokemonParty" />
public PokemonParty(int size)
/// <inheritdoc cref="PokemonPartyImpl" />
public PokemonPartyImpl(int size)
{
_pokemon = new IPokemon[size];
}