Getting started with implementing an explicit AI, based on the Essentials one.
All checks were successful
Build / Build (push) Successful in 1m2s

This commit is contained in:
2025-07-11 17:03:08 +02:00
parent 084ae84130
commit a3a4993407
56 changed files with 2687 additions and 1274 deletions

View File

@@ -8,7 +8,7 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Dry_Skin_(Ability)">Bulbapedia - Dry Skin</see>
/// </summary>
[Script(ScriptCategory.Ability, "dry_skin")]
public class DrySkin : Script, IScriptChangeDamageModifier, IScriptOnEndTurn
public class DrySkin : Script, IScriptChangeDamageModifier, IScriptOnEndTurn, IAIInfoScriptExpectedEndOfTurnDamage
{
private IPokemon? _owningPokemon;
@@ -55,4 +55,13 @@ public class DrySkin : Script, IScriptChangeDamageModifier, IScriptOnEndTurn
_owningPokemon.Damage(_owningPokemon.MaxHealth / 8, DamageSource.Weather);
}
}
/// <inheritdoc />
public void ExpectedEndOfTurnDamage(IPokemon pokemon, ref int damage)
{
if (pokemon.BattleData?.Battle.WeatherName == ScriptUtils.ResolveName<Weather.HarshSunlight>())
{
damage += (int)(pokemon.MaxHealth / 8f);
}
}
}

View File

@@ -6,7 +6,8 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Solar_Power_(Ability)">Bulbapedia - Solar Power</see>
/// </summary>
[Script(ScriptCategory.Ability, "solar_power")]
public class SolarPower : Script, IScriptChangeOffensiveStatValue, IScriptOnEndTurn
public class SolarPower : Script, IScriptChangeOffensiveStatValue, IScriptOnEndTurn,
IAIInfoScriptExpectedEndOfTurnDamage
{
/// <inheritdoc />
public void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint defensiveStat,
@@ -29,6 +30,26 @@ public class SolarPower : Script, IScriptChangeOffensiveStatValue, IScriptOnEndT
if (!pokemon.IsUsable)
return;
var weatherName = pokemon.BattleData?.Battle.WeatherName;
if (weatherName != ScriptUtils.ResolveName<Weather.HarshSunlight>() &&
weatherName != ScriptUtils.ResolveName<Weather.DesolateLands>())
{
return;
}
pokemon.Damage(pokemon.MaxHealth / 8, DamageSource.Weather);
}
/// <inheritdoc />
public void ExpectedEndOfTurnDamage(IPokemon pokemon, ref int damage)
{
var weatherName = pokemon.BattleData?.Battle.WeatherName;
if (weatherName != ScriptUtils.ResolveName<Weather.HarshSunlight>() &&
weatherName != ScriptUtils.ResolveName<Weather.DesolateLands>())
{
return;
}
damage += (int)(pokemon.MaxHealth / 8f);
}
}

View File

@@ -1,3 +1,6 @@
using PkmnLib.Dynamic.AI.Explicit;
using PkmnLib.Plugin.Gen7.AI;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
public abstract class ChangeUserStats : Script, IScriptOnInitialize, IScriptOnSecondaryEffect
@@ -31,6 +34,19 @@ public abstract class ChangeUserStats : Script, IScriptOnInitialize, IScriptOnSe
{
move.User.ChangeStatBoost(_stat, _amount, true, false);
}
protected static void GetMoveEffectScore(MoveOption option, Statistic stat, ref int score)
{
if (option.Move.Move.SecondaryEffect == null ||
!option.Move.Move.SecondaryEffect.Parameters.TryGetValue("amount", out var amountObj) ||
amountObj is not int amount)
{
return;
}
var statisticSet = new StatBoostStatisticSet();
statisticSet.SetStatistic(stat, (sbyte)amount);
score = AIHelperFunctions.GetScoreForTargetStatRaise(score, option.Move, option.Move.User, statisticSet);
}
}
[Script(ScriptCategory.Move, "change_user_attack")]
@@ -39,6 +55,10 @@ public class ChangeUserAttack : ChangeUserStats
public ChangeUserAttack() : base(Statistic.Attack)
{
}
[AIMoveScoreFunction]
public static void AIMoveEffectScore(MoveOption option, ref int score) =>
GetMoveEffectScore(option, Statistic.Attack, ref score);
}
[Script(ScriptCategory.Move, "change_user_defense")]
@@ -47,6 +67,10 @@ public class ChangeUserDefense : ChangeUserStats
public ChangeUserDefense() : base(Statistic.Defense)
{
}
[AIMoveScoreFunction]
public static void AIMoveEffectScore(MoveOption option, ref int score) =>
GetMoveEffectScore(option, Statistic.Defense, ref score);
}
[Script(ScriptCategory.Move, "change_user_special_attack")]
@@ -55,6 +79,10 @@ public class ChangeUserSpecialAttack : ChangeUserStats
public ChangeUserSpecialAttack() : base(Statistic.SpecialAttack)
{
}
[AIMoveScoreFunction]
public static void AIMoveEffectScore(MoveOption option, ref int score) =>
GetMoveEffectScore(option, Statistic.SpecialAttack, ref score);
}
[Script(ScriptCategory.Move, "change_user_special_defense")]
@@ -63,6 +91,10 @@ public class ChangeUserSpecialDefense : ChangeUserStats
public ChangeUserSpecialDefense() : base(Statistic.SpecialDefense)
{
}
[AIMoveScoreFunction]
public static void AIMoveEffectScore(MoveOption option, ref int score) =>
GetMoveEffectScore(option, Statistic.SpecialDefense, ref score);
}
[Script(ScriptCategory.Move, "change_user_speed")]
@@ -71,6 +103,10 @@ public class ChangeUserSpeed : ChangeUserStats
public ChangeUserSpeed() : base(Statistic.Speed)
{
}
[AIMoveScoreFunction]
public static void AIMoveEffectScore(MoveOption option, ref int score) =>
GetMoveEffectScore(option, Statistic.Speed, ref score);
}
[Script(ScriptCategory.Move, "change_user_accuracy")]
@@ -79,6 +115,10 @@ public class ChangeUserAccuracy : ChangeUserStats
public ChangeUserAccuracy() : base(Statistic.Accuracy)
{
}
[AIMoveScoreFunction]
public static void AIMoveEffectScore(MoveOption option, ref int score) =>
GetMoveEffectScore(option, Statistic.Accuracy, ref score);
}
[Script(ScriptCategory.Move, "change_user_evasion")]
@@ -87,4 +127,8 @@ public class ChangeUserEvasion : ChangeUserStats
public ChangeUserEvasion() : base(Statistic.Evasion)
{
}
[AIMoveScoreFunction]
public static void AIMoveEffectScore(MoveOption option, ref int score) =>
GetMoveEffectScore(option, Statistic.Evasion, ref score);
}

View File

@@ -13,11 +13,13 @@ public class MirrorMove : Script, IScriptChangeMove
return;
var battle = battleData.Battle;
var currentTurn = battle.ChoiceQueue!.LastRanChoice;
if (battle.ChoiceQueue == null)
return;
var currentTurn = battle.ChoiceQueue.LastRanChoice;
var lastMove = battle.PreviousTurnChoices.SelectMany(x => x).OfType<IMoveChoice>()
.TakeWhile(x => x != currentTurn).LastOrDefault(x => x.TargetPosition == choice.TargetPosition &&
x.TargetSide == choice.TargetSide &&
x.User.BattleData?.IsOnBattlefield == true);
.TakeWhile(x => !Equals(x, currentTurn)).LastOrDefault(x => x.TargetPosition == choice.TargetPosition &&
x.TargetSide == choice.TargetSide &&
x.User.BattleData?.IsOnBattlefield == true);
if (lastMove == null || !lastMove.ChosenMove.MoveData.CanCopyMove())
{
choice.Fail();

View File

@@ -1,7 +1,8 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
[Script(ScriptCategory.Pokemon, "bind")]
public class BindEffect : Script, IScriptOnEndTurn, IScriptPreventSelfSwitch, IScriptPreventSelfRunAway
public class BindEffect : Script, IScriptOnEndTurn, IScriptPreventSelfSwitch, IScriptPreventSelfRunAway,
IAIInfoScriptExpectedEndOfTurnDamage
{
private readonly IPokemon? _owner;
private int _turns;
@@ -33,4 +34,10 @@ public class BindEffect : Script, IScriptOnEndTurn, IScriptPreventSelfSwitch, IS
/// <inheritdoc />
public void PreventSelfRunAway(IFleeChoice choice, ref bool prevent) => prevent = _turns > 0;
/// <inheritdoc />
public void ExpectedEndOfTurnDamage(IPokemon pokemon, ref int damage)
{
damage += (int)(_owner?.MaxHealth * _percentOfMaxHealth ?? 0);
}
}

View File

@@ -1,7 +1,8 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
[Script(ScriptCategory.Pokemon, "fire_spin")]
public class FireSpinEffect : Script, IScriptOnEndTurn, IScriptPreventSelfRunAway, IScriptPreventSelfSwitch
public class FireSpinEffect : Script, IScriptOnEndTurn, IScriptPreventSelfRunAway, IScriptPreventSelfSwitch,
IAIInfoScriptExpectedEndOfTurnDamage
{
private readonly IPokemon _owner;
@@ -21,4 +22,10 @@ public class FireSpinEffect : Script, IScriptOnEndTurn, IScriptPreventSelfRunAwa
/// <inheritdoc />
public void PreventSelfSwitch(ISwitchChoice choice, ref bool prevent) => prevent = true;
/// <inheritdoc />
public void ExpectedEndOfTurnDamage(IPokemon pokemon, ref int damage)
{
damage += (int)(pokemon.MaxHealth / 8f);
}
}

View File

@@ -1,7 +1,7 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
[Script(ScriptCategory.Pokemon, "ghostcurse")]
public class GhostCurseEffect : Script, IScriptOnEndTurn
public class GhostCurseEffect : Script, IScriptOnEndTurn, IAIInfoScriptExpectedEndOfTurnDamage
{
private IPokemon _pokemon;
@@ -15,4 +15,10 @@ public class GhostCurseEffect : Script, IScriptOnEndTurn
{
_pokemon.Damage(_pokemon.CurrentHealth / 4, DamageSource.Misc);
}
/// <inheritdoc />
public void ExpectedEndOfTurnDamage(IPokemon pokemon, ref int damage)
{
damage += (int)(_pokemon.CurrentHealth / 4f);
}
}

View File

@@ -1,7 +1,8 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
[Script(ScriptCategory.Pokemon, "infestation")]
public class InfestationEffect : Script, IScriptOnEndTurn, IScriptPreventSelfSwitch, IScriptPreventSelfRunAway
public class InfestationEffect : Script, IScriptOnEndTurn, IScriptPreventSelfSwitch, IScriptPreventSelfRunAway,
IAIInfoScriptExpectedEndOfTurnDamage
{
private readonly IPokemon _owner;
private int _turns;
@@ -30,4 +31,10 @@ public class InfestationEffect : Script, IScriptOnEndTurn, IScriptPreventSelfSwi
RemoveSelf();
}
}
/// <inheritdoc />
public void ExpectedEndOfTurnDamage(IPokemon pokemon, ref int damage)
{
damage += (int)(_owner.MaxHealth / 8f);
}
}

View File

@@ -1,7 +1,7 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
[Script(ScriptCategory.Pokemon, "leech_seed")]
public class LeechSeedEffect : Script, IScriptOnEndTurn
public class LeechSeedEffect : Script, IScriptOnEndTurn, IAIInfoScriptExpectedEndOfTurnDamage
{
private readonly IPokemon _owner;
private readonly IPokemon _placer;
@@ -15,7 +15,7 @@ public class LeechSeedEffect : Script, IScriptOnEndTurn
/// <inheritdoc />
public void OnEndTurn(IScriptSource owner, IBattle battle)
{
var damage = _owner.BoostedStats.Hp / 8;
var damage = _owner.MaxHealth / 8;
if (_owner.CurrentHealth <= damage)
damage = _owner.CurrentHealth;
@@ -25,4 +25,10 @@ public class LeechSeedEffect : Script, IScriptOnEndTurn
else
_placer.Heal(damage);
}
/// <inheritdoc />
public void ExpectedEndOfTurnDamage(IPokemon pokemon, ref int damage)
{
damage += (int)(_owner.MaxHealth / 8f);
}
}

View File

@@ -1,7 +1,8 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
[Script(ScriptCategory.Pokemon, "magma_storm")]
public class MagmaStormEffect : Script, IScriptOnEndTurn, IScriptPreventSelfRunAway, IScriptPreventSelfSwitch
public class MagmaStormEffect : Script, IScriptOnEndTurn, IScriptPreventSelfRunAway, IScriptPreventSelfSwitch,
IAIInfoScriptExpectedEndOfTurnDamage
{
private readonly IPokemon _owner;
@@ -21,4 +22,10 @@ public class MagmaStormEffect : Script, IScriptOnEndTurn, IScriptPreventSelfRunA
/// <inheritdoc />
public void PreventSelfSwitch(ISwitchChoice choice, ref bool prevent) => prevent = true;
/// <inheritdoc />
public void ExpectedEndOfTurnDamage(IPokemon pokemon, ref int damage)
{
damage += (int)(pokemon.MaxHealth / 16f);
}
}

View File

@@ -3,7 +3,7 @@ using PkmnLib.Plugin.Gen7.Scripts.Status;
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
[Script(ScriptCategory.Pokemon, "nightmare")]
public class NightmareEffect : Script, IScriptOnEndTurn
public class NightmareEffect : Script, IScriptOnEndTurn, IAIInfoScriptExpectedEndOfTurnDamage
{
private readonly IPokemon _owner;
@@ -23,4 +23,10 @@ public class NightmareEffect : Script, IScriptOnEndTurn
var maxHp = _owner.MaxHealth;
_owner.Damage(maxHp / 4, DamageSource.Misc);
}
/// <inheritdoc />
public void ExpectedEndOfTurnDamage(IPokemon pokemon, ref int damage)
{
damage += (int)(_owner.MaxHealth / 4f);
}
}

View File

@@ -3,7 +3,7 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
[Script(ScriptCategory.Pokemon, "substitute")]
public class SubstituteEffect(uint health) : Script, IScriptBlockIncomingHit
{
private uint _health = health;
public uint Health { get; private set; } = health;
/// <inheritdoc />
public void BlockIncomingHit(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool block)
@@ -19,12 +19,12 @@ public class SubstituteEffect(uint health) : Script, IScriptBlockIncomingHit
block = true;
var damage = executingMove.GetHitData(target, hitIndex).Damage;
if (damage >= _health)
if (damage >= Health)
{
executingMove.Battle.EventHook.Invoke(new DialogEvent("substitute_broken"));
RemoveSelf();
return;
}
_health -= damage;
Health -= damage;
}
}

View File

@@ -1,7 +1,8 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
[Script(ScriptCategory.Pokemon, "whirlpool")]
public class WhirlpoolEffect : Script, IScriptOnEndTurn, IScriptPreventOpponentRunAway, IScriptPreventOpponentSwitch
public class WhirlpoolEffect : Script, IScriptOnEndTurn, IScriptPreventOpponentRunAway, IScriptPreventOpponentSwitch,
IAIInfoScriptExpectedEndOfTurnDamage
{
public record PokemonTurn
{
@@ -80,4 +81,14 @@ public class WhirlpoolEffect : Script, IScriptOnEndTurn, IScriptPreventOpponentR
_targetedPokemon.Remove(turn);
}
}
/// <inheritdoc />
public void ExpectedEndOfTurnDamage(IPokemon pokemon, ref int damage)
{
var turn = _targetedPokemon.FirstOrDefault(x => x.Pokemon == pokemon);
if (turn != null)
{
damage += (int)(pokemon.MaxHealth * turn.DamagePercent);
}
}
}

View File

@@ -1,7 +1,7 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Status;
[Script(ScriptCategory.Status, "badly_poisoned")]
public class BadlyPoisoned : Poisoned, IScriptOnEndTurn
public class BadlyPoisoned : Poisoned
{
private int _turns = 1;

View File

@@ -3,7 +3,7 @@ using PkmnLib.Static.Moves;
namespace PkmnLib.Plugin.Gen7.Scripts.Status;
[Script(ScriptCategory.Status, "burned")]
public class Burned : Script, IScriptChangeMoveDamage, IScriptOnEndTurn
public class Burned : Script, IScriptChangeMoveDamage, IScriptOnEndTurn, IAIInfoScriptExpectedEndOfTurnDamage
{
private IPokemon? _target;
@@ -41,4 +41,11 @@ public class Burned : Script, IScriptChangeMoveDamage, IScriptOnEndTurn
});
_target.Damage(damage, DamageSource.Status, eventBatch);
}
/// <inheritdoc />
public void ExpectedEndOfTurnDamage(IPokemon pokemon, ref int damage)
{
if (_target != null)
damage += (int)(_target.MaxHealth / 16f);
}
}

View File

@@ -1,7 +1,7 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Status;
[Script(ScriptCategory.Status, "poisoned")]
public class Poisoned : Script, IScriptOnEndTurn
public class Poisoned : Script, IScriptOnEndTurn, IAIInfoScriptExpectedEndOfTurnDamage
{
private IPokemon? _pokemon;
@@ -46,4 +46,11 @@ public class Poisoned : Script, IScriptOnEndTurn
else
_pokemon.Damage(damage, DamageSource.Status, eventBatchId);
}
/// <inheritdoc />
public virtual void ExpectedEndOfTurnDamage(IPokemon pokemon, ref int damage)
{
if (_pokemon != null)
damage += (int)(_pokemon.MaxHealth * GetPoisonMultiplier());
}
}

View File

@@ -1,7 +1,7 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Status;
[Script(ScriptCategory.Status, "sleep")]
public class Sleep : Script, IScriptPreventMove
public class Sleep : Script, IScriptPreventMove, IAIInfoScriptNumberTurnsLeft
{
private IPokemon? _pokemon;
public int Turns { get; set; }
@@ -54,4 +54,7 @@ public class Sleep : Script, IScriptPreventMove
{ "pokemon", _pokemon },
}));
}
/// <inheritdoc />
public int TurnsLeft() => Turns;
}

View File

@@ -27,7 +27,7 @@ public class PsychicTerrain : Script, IScriptIsInvulnerableToMove, IScriptChange
if (!IsAffectedByTerrain(target))
return;
// Psychic Terrain prevents priority moves from affecting affected Pokémon.
// Psychic Terrain prevents priority moves from affected Pokémon.
if (move.MoveChoice.Priority > 0)
{
invulnerable = true;

View File

@@ -1,7 +1,7 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Weather;
[Script(ScriptCategory.Weather, "hail")]
public class Hail : Script, ILimitedTurnsScript, IScriptOnEndTurn
public class Hail : Script, ILimitedTurnsScript, IScriptOnEndTurn, IAIInfoScriptExpectedEndOfTurnDamage
{
private int? _duration;
@@ -46,4 +46,15 @@ public class Hail : Script, ILimitedTurnsScript, IScriptOnEndTurn
battle.SetWeather(null, 0);
}
}
/// <inheritdoc />
public void ExpectedEndOfTurnDamage(IPokemon pokemon, ref int damage)
{
if (pokemon.Types.Any(x => x.Name == "ice"))
return; // Ice types are immune to Hail damage.
if (_duration.HasValue)
{
damage += (int)(pokemon.MaxHealth / 16f);
}
}
}

View File

@@ -1,7 +1,8 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Weather;
[Script(ScriptCategory.Weather, "sandstorm")]
public class Sandstorm : Script, IScriptChangeBasePower, IScriptChangeDefensiveStatValue, IScriptOnEndTurn
public class Sandstorm : Script, IScriptChangeBasePower, IScriptChangeDefensiveStatValue, IScriptOnEndTurn,
IAIInfoScriptExpectedEndOfTurnDamage
{
/// <inheritdoc />
public void OnEndTurn(IScriptSource owner, IBattle battle)
@@ -39,4 +40,12 @@ public class Sandstorm : Script, IScriptChangeBasePower, IScriptChangeDefensiveS
if (move.UseMove.Name == "solar_beam")
basePower /= 2;
}
/// <inheritdoc />
public void ExpectedEndOfTurnDamage(IPokemon pokemon, ref int damage)
{
if (pokemon.Types.Any(x => x.Name == "rock" || x.Name == "ground" || x.Name == "steel"))
return; // Rock, Ground, and Steel types are immune to Sandstorm damage.
damage += (int)(pokemon.MaxHealth / 16f);
}
}