More work on refactor to interfaces
All checks were successful
Build / Build (push) Successful in 50s

This commit is contained in:
2025-06-29 12:03:51 +02:00
parent 436d1899e0
commit 1feb27e826
173 changed files with 713 additions and 562 deletions

View File

@@ -259,11 +259,15 @@ public static class MoveTurnExecutor
});
target.Damage(damage, DamageSource.MoveDamage, hitEventBatch);
if (!target.IsFainted)
{
target.RunScriptHookInterface<IScriptOnIncomingHit>(x =>
x.OnIncomingHit(executingMove, target, hitIndex));
}
else
{
executingMove.RunScriptHookInterface<IScriptOnOpponentFaints>(x =>
x.OnOpponentFaints(executingMove, target, hitIndex));
}
if (!target.IsFainted)
{
@@ -271,9 +275,9 @@ public static class MoveTurnExecutor
if (secondaryEffect != null)
{
var preventSecondary = false;
executingMove.RunScriptHook(x =>
executingMove.RunScriptHookInterface<IScriptPreventSecondaryEffect>(x =>
x.PreventSecondaryEffect(executingMove, target, hitIndex, ref preventSecondary));
target.RunScriptHook(x =>
target.RunScriptHookInterface<IScriptPreventIncomingSecondaryEffect>(x =>
x.PreventIncomingSecondaryEffect(executingMove, target, hitIndex,
ref preventSecondary));
@@ -288,8 +292,10 @@ public static class MoveTurnExecutor
}
}
if (target.IsFainted)
{
executingMove.RunScriptHookInterface<IScriptOnOpponentFaints>(x =>
x.OnOpponentFaints(executingMove, target, hitIndex));
}
}
}
}
@@ -303,7 +309,7 @@ public static class MoveTurnExecutor
if (!executingMove.User.IsFainted)
{
executingMove.RunScriptHook(x => x.OnAfterHits(executingMove, target));
executingMove.RunScriptHookInterface<IScriptOnAfterHits>(x => x.OnAfterHits(executingMove, target));
}
}
}

View File

@@ -60,15 +60,15 @@ public static class TurnRunner
{
scripts.Clear();
pokemon.GetOwnScripts(scripts);
scripts.RunScriptHook(x => x.OnEndTurn(pokemon, battle));
scripts.RunScriptHookInterface<IScriptOnEndTurn>(x => x.OnEndTurn(pokemon, battle));
}
scripts.Clear();
side.GetOwnScripts(scripts);
scripts.RunScriptHook(x => x.OnEndTurn(side, battle));
scripts.RunScriptHookInterface<IScriptOnEndTurn>(x => x.OnEndTurn(side, battle));
}
scripts.Clear();
battle.GetOwnScripts(scripts);
scripts.RunScriptHook(x => x.OnEndTurn(battle, battle));
scripts.RunScriptHookInterface<IScriptOnEndTurn>(x => x.OnEndTurn(battle, battle));
}
}

View File

@@ -38,8 +38,10 @@ public class BattleRandomImpl : RandomImpl, IBattleRandom
/// <inheritdoc />
public bool EffectChance(float chance, IExecutingMove executingMove, IPokemon target, byte hitNumber)
{
executingMove.RunScriptHook(script => script.ChangeEffectChance(executingMove, target, hitNumber, ref chance));
target.RunScriptHook(script => script.ChangeIncomingEffectChance(executingMove, target, hitNumber, ref chance));
executingMove.RunScriptHookInterface<IScriptChangeEffectChance>(script =>
script.ChangeEffectChance(executingMove, target, hitNumber, ref chance));
target.RunScriptHookInterface<IScriptChangeIncomingEffectChance>(script =>
script.ChangeIncomingEffectChance(executingMove, target, hitNumber, ref chance));
if (chance > 100.0)
return true;
if (chance < 0.0)

View File

@@ -889,11 +889,12 @@ public class PokemonImpl : ScriptSource, IPokemon
if (!force)
{
var prevented = false;
this.RunScriptHook(script =>
this.RunScriptHookInterface<IScriptPreventStatBoostChange>(script =>
script.PreventStatBoostChange(this, stat, change, selfInflicted, ref prevented));
if (prevented)
return false;
this.RunScriptHook(script => script.ChangeStatBoostChange(this, stat, selfInflicted, ref change));
this.RunScriptHookInterface<IScriptChangeStatBoostChange>(script =>
script.ChangeStatBoostChange(this, stat, selfInflicted, ref change));
if (change == 0)
return false;
}
@@ -917,7 +918,8 @@ public class PokemonImpl : ScriptSource, IPokemon
}
RecalculateBoostedStats();
this.RunScriptHook(script => script.OnAfterStatBoostChange(this, stat, selfInflicted, change));
this.RunScriptHookInterface<IScriptOnAfterStatBoostChange>(script =>
script.OnAfterStatBoostChange(this, stat, selfInflicted, change));
return true;
}

View File

@@ -61,172 +61,6 @@ public abstract class Script : IDeepCloneable
{
}
/// <summary>
/// This function allows a script to bypass defensive stat boosts for a move hit.
/// If this is true, the damage will be calculated as if the target has no positive stat boosts. Negative
/// stat boosts will still be applied.
/// </summary>
public virtual void BypassDefensiveStatBoosts(IExecutingMove move, IPokemon target, byte hit, ref bool bypass)
{
}
/// <summary>
/// This function allows a script to bypass evasion stat boosts for a move hit.
/// If this is true, the move will handle the evasion stat boosts as if the target has no positive stat boosts.
/// </summary>
public virtual void BypassEvasionStatBoosts(IExecutingMove move, IPokemon target, byte hitIndex, ref bool bypass)
{
}
/// <summary>
/// This function allows a script to bypass offensive stat boosts for a move hit.
/// If this is true, the damage will be calculated as if the user has no negative offensive stat boosts. Positive
/// stat boosts will still be applied.
/// </summary>
public virtual void BypassOffensiveStatBoosts(IExecutingMove move, IPokemon target, byte hit, ref bool bypass)
{
}
/// <summary>
/// This function allows a script to change the actual offensive stat values used when calculating damage
/// </summary>
public virtual void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint defensiveStat,
ImmutableStatisticSet<uint> targetStats, Statistic stat, ref uint value)
{
}
/// <summary>
/// This function allows a script to change the actual defensive stat values used when calculating damage.
/// </summary>
public virtual void ChangeDefensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint offensiveStat,
ImmutableStatisticSet<uint> targetStats, Statistic stat, ref uint value)
{
}
/// <summary>
/// This function allows a script to change the offensive stat value of an incoming move.
/// </summary>
public virtual void ChangeIncomingMoveOffensiveStatValue(IExecutingMove executingMove, IPokemon target,
byte hitNumber, uint defensiveStat, StatisticSet<uint> targetStats, Statistic offensive, ref uint offensiveStat)
{
}
/// <summary>
/// This function allows a script to change the defensive stat value of an incoming move.
/// </summary>
public virtual void ChangeIncomingMoveDefensiveStatValue(IExecutingMove executingMove, IPokemon target,
byte hitNumber, uint origOffensiveStat, StatisticSet<uint> targetStats, Statistic defensive,
ref uint defensiveStat)
{
}
/// <summary>
/// This function allows a script to change the raw modifier we retrieved from the stats of the
/// defender and attacker. The default value is the offensive stat divided by the defensive stat.
/// </summary>
public virtual void ChangeDamageStatModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier)
{
}
/// <summary>
/// This function allows a script to apply a raw multiplier to the damage done by a move.
/// </summary>
public virtual void ChangeDamageModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier)
{
}
/// <summary>
/// This function allows a script to change the damage modifier of an incoming move.
/// </summary>
public virtual void ChangeIncomingMoveDamageModifier(IExecutingMove executingMove, IPokemon target, byte hitNumber,
ref float modifier)
{
}
/// <summary>
/// This function allows a script to modify the outgoing damage done by a move.
/// </summary>
public virtual void ChangeMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage)
{
}
/// <summary>
/// This function allows a script to modify the incoming damage done by a move.
/// </summary>
public virtual void ChangeIncomingMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage)
{
}
/// <summary>
/// This function allows a script attached to a Pokemon or its parents to prevent stat boost
/// changes on that Pokemon.
/// </summary>
public virtual void PreventStatBoostChange(IPokemon target, Statistic stat, sbyte amount, bool selfInflicted,
ref bool prevent)
{
}
/// <summary>
/// This function allows a script attached to a Pokemon or its parents to modify the amount by
/// which the stat boost will change. If the stat boost is done by the user itself, self
/// inflicted will be true, otherwise it will be false.
/// </summary>
public virtual void ChangeStatBoostChange(IPokemon target, Statistic stat, bool selfInflicted, ref sbyte amount)
{
}
/// <summary>
/// This function allows a script to run after a stat boost change has been applied.
/// </summary>
public virtual void OnAfterStatBoostChange(IPokemon pokemon, Statistic stat, bool selfInflicted, sbyte change)
{
}
/// <summary>
/// This function allows a script to prevent a secondary effect of a move from being applied.
/// This means the move will still hit and do damage, but not trigger its secondary effect. Note that this
/// function is not called for status moves.
/// </summary>
public virtual void PreventSecondaryEffect(IExecutingMove move, IPokemon target, byte hit, ref bool prevent)
{
}
/// <summary>
/// This function allows a script attached to a Pokemon or its parents to prevent an incoming
/// secondary effect. This means the move will still hit and do damage, but not trigger its
/// secondary effect. Note that this function is not called for status moves.
/// </summary>
public virtual void PreventIncomingSecondaryEffect(IExecutingMove move, IPokemon target, byte hit, ref bool prevent)
{
}
/// <summary>
/// This function allows a script attached to a move or its parents to change the chance the
/// secondary effect of a move will trigger. The chance is depicted in percentage here, so
/// changing this to above or equal to 100 will make it always hit, while setting it to equal or
/// below 0 will make it never hit.
/// </summary>
public virtual void ChangeEffectChance(IExecutingMove move, IPokemon target, byte hit, ref float chance)
{
}
/// <summary>
/// This function allows a script attached to a Pokemon or its parents to change the chance the
/// secondary effect of an incoming move will trigger. The chance is depicted in percentage here,
/// so changing this to above or equal to 100 will make it always hit, while setting it to equal
/// or below 0 will make it never hit.
/// </summary>
public virtual void ChangeIncomingEffectChance(IExecutingMove move, IPokemon target, byte hit, ref float chance)
{
}
/// <summary>
/// This function triggers on a move or its parents when all hits on a target are finished.
/// </summary>
public virtual void OnAfterHits(IExecutingMove move, IPokemon target)
{
}
/// <summary>
/// This function prevents the Pokemon it is attached to from being able to switch out.
/// </summary>
@@ -270,17 +104,6 @@ public abstract class Script : IDeepCloneable
{
}
/// <summary>
/// This function id triggered on all scripts active in the battle after all choices have finished
/// running. Note that choices are not active anymore here, so their scripts do not call this
/// function.
/// </summary>
/// <param name="owner"></param>
/// <param name="battle"></param>
public virtual void OnEndTurn(IScriptSource owner, IBattle battle)
{
}
/// <summary>
/// This function is triggered on a Pokemon and its parents when the given Pokemon takes damage.
/// </summary>
@@ -973,4 +796,261 @@ public interface IScriptChangeBasePower
/// This function allows a script to change the effective base power of a move hit.
/// </summary>
void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref ushort basePower);
}
/// <summary>
/// This interface allows scripts to bypass defensive stat boosts for a move hit.
/// </summary>
public interface IScriptBypassDefensiveStatBoosts
{
/// <summary>
/// This function allows a script to bypass defensive stat boosts for a move hit.
/// If this is true, the damage will be calculated as if the target has no positive stat boosts. Negative
/// stat boosts will still be applied.
/// </summary>
void BypassDefensiveStatBoosts(IExecutingMove move, IPokemon target, byte hit, ref bool bypass);
}
/// <summary>
/// This interface allows scripts to bypass evasion stat boosts for a move hit.
/// </summary>
public interface IScriptBypassEvasionStatBoosts
{
/// <summary>
/// This function allows a script to bypass evasion stat boosts for a move hit.
/// If this is true, the move will handle the evasion stat boosts as if the target has no positive stat boosts.
/// </summary>
void BypassEvasionStatBoosts(IExecutingMove move, IPokemon target, byte hitIndex, ref bool bypass);
}
/// <summary>
/// This interface allows scripts to bypass offensive stat boosts for a move hit.
/// </summary>
public interface IScriptBypassOffensiveStatBoosts
{
/// <summary>
/// This function allows a script to bypass offensive stat boosts for a move hit.
/// If this is true, the damage will be calculated as if the user has no negative offensive stat boosts. Positive
/// stat boosts will still be applied.
/// </summary>
void BypassOffensiveStatBoosts(IExecutingMove move, IPokemon target, byte hit, ref bool bypass);
}
/// <summary>
/// This interface allows scripts to change the actual offensive stat values used when calculating damage.
/// </summary>
public interface IScriptChangeOffensiveStatValue
{
/// <summary>
/// This function allows a script to change the actual offensive stat values used when calculating damage
/// </summary>
void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint defensiveStat,
ImmutableStatisticSet<uint> targetStats, Statistic stat, ref uint value);
}
/// <summary>
/// This interface allows scripts to change the actual defensive stat values used when calculating damage.
/// </summary>
public interface IScriptChangeDefensiveStatValue
{
/// <summary>
/// This function allows a script to change the actual defensive stat values used when calculating damage.
/// </summary>
void ChangeDefensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint offensiveStat,
ImmutableStatisticSet<uint> targetStats, Statistic stat, ref uint value);
}
/// <summary>
/// This interface allows scripts to change the offensive stat value of an incoming move.
/// </summary>
public interface IScriptChangeIncomingMoveOffensiveStatValue
{
/// <summary>
/// This function allows a script to change the offensive stat value of an incoming move.
/// </summary>
void ChangeIncomingMoveOffensiveStatValue(IExecutingMove executingMove, IPokemon target, byte hitNumber,
uint defensiveStat, StatisticSet<uint> targetStats, Statistic offensive, ref uint offensiveStat);
}
/// <summary>
/// This interface allows scripts to change the defensive stat value of an incoming move.
/// </summary>
public interface IScriptChangeIncomingMoveDefensiveStatValue
{
/// <summary>
/// This function allows a script to change the defensive stat value of an incoming move.
/// </summary>
void ChangeIncomingMoveDefensiveStatValue(IExecutingMove executingMove, IPokemon target, byte hitNumber,
uint origOffensiveStat, StatisticSet<uint> targetStats, Statistic defensive, ref uint defensiveStat);
}
/// <summary>
/// This interface allows scripts to change the raw modifier retrieved from the stats of the defender and attacker.
/// </summary>
public interface IScriptChangeDamageStatModifier
{
/// <summary>
/// This function allows a script to change the raw modifier we retrieved from the stats of the
/// defender and attacker. The default value is the offensive stat divided by the defensive stat.
/// </summary>
void ChangeDamageStatModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier);
}
/// <summary>
/// This interface allows scripts to apply a raw multiplier to the damage done by a move.
/// </summary>
public interface IScriptChangeDamageModifier
{
/// <summary>
/// This function allows a script to apply a raw multiplier to the damage done by a move.
/// </summary>
void ChangeDamageModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier);
}
/// <summary>
/// This interface allows scripts to change the damage modifier of an incoming move.
/// </summary>
public interface IScriptChangeIncomingMoveDamageModifier
{
/// <summary>
/// This function allows a script to change the damage modifier of an incoming move.
/// </summary>
void ChangeIncomingMoveDamageModifier(IExecutingMove executingMove, IPokemon target, byte hitNumber,
ref float modifier);
}
/// <summary>
/// This interface allows scripts to modify the outgoing damage done by a move.
/// </summary>
public interface IScriptChangeMoveDamage
{
/// <summary>
/// This function allows a script to modify the outgoing damage done by a move.
/// </summary>
void ChangeMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage);
}
/// <summary>
/// This interface allows scripts to modify the incoming damage done by a move.
/// </summary>
public interface IScriptChangeIncomingMoveDamage
{
/// <summary>
/// This function allows a script to modify the incoming damage done by a move.
/// </summary>
void ChangeIncomingMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage);
}
/// <summary>
/// This interface allows scripts attached to a Pokemon or its parents to prevent stat boost changes.
/// </summary>
public interface IScriptPreventStatBoostChange
{
/// <summary>
/// This function allows a script attached to a Pokemon or its parents to prevent stat boost
/// changes on that Pokemon.
/// </summary>
void PreventStatBoostChange(IPokemon target, Statistic stat, sbyte amount, bool selfInflicted, ref bool prevent);
}
/// <summary>
/// This interface allows scripts attached to a Pokemon or its parents to modify stat boost changes.
/// </summary>
public interface IScriptChangeStatBoostChange
{
/// <summary>
/// This function allows a script attached to a Pokemon or its parents to modify the amount by
/// which the stat boost will change. If the stat boost is done by the user itself, self
/// inflicted will be true, otherwise it will be false.
/// </summary>
void ChangeStatBoostChange(IPokemon target, Statistic stat, bool selfInflicted, ref sbyte amount);
}
/// <summary>
/// This interface allows scripts to run after a stat boost change has been applied.
/// </summary>
public interface IScriptOnAfterStatBoostChange
{
/// <summary>
/// This function allows a script to run after a stat boost change has been applied.
/// </summary>
void OnAfterStatBoostChange(IPokemon pokemon, Statistic stat, bool selfInflicted, sbyte change);
}
/// <summary>
/// This interface allows scripts to prevent a secondary effect of a move from being applied.
/// </summary>
public interface IScriptPreventSecondaryEffect
{
/// <summary>
/// This function allows a script to prevent a secondary effect of a move from being applied.
/// This means the move will still hit and do damage, but not trigger its secondary effect. Note that this
/// function is not called for status moves.
/// </summary>
void PreventSecondaryEffect(IExecutingMove move, IPokemon target, byte hit, ref bool prevent);
}
/// <summary>
/// This interface allows scripts attached to a Pokemon or its parents to prevent incoming secondary effects.
/// </summary>
public interface IScriptPreventIncomingSecondaryEffect
{
/// <summary>
/// This function allows a script attached to a Pokemon or its parents to prevent an incoming
/// secondary effect. This means the move will still hit and do damage, but not trigger its
/// secondary effect. Note that this function is not called for status moves.
/// </summary>
void PreventIncomingSecondaryEffect(IExecutingMove move, IPokemon target, byte hit, ref bool prevent);
}
/// <summary>
/// This interface allows scripts attached to a move or its parents to change the chance of secondary effects.
/// </summary>
public interface IScriptChangeEffectChance
{
/// <summary>
/// This function allows a script attached to a move or its parents to change the chance the
/// secondary effect of a move will trigger. The chance is depicted in percentage here, so
/// changing this to above or equal to 100 will make it always hit, while setting it to equal or
/// below 0 will make it never hit.
/// </summary>
void ChangeEffectChance(IExecutingMove move, IPokemon target, byte hit, ref float chance);
}
/// <summary>
/// This interface allows scripts attached to a Pokemon or its parents to change the chance of incoming secondary effects.
/// </summary>
public interface IScriptChangeIncomingEffectChance
{
/// <summary>
/// This function allows a script attached to a Pokemon or its parents to change the chance the
/// secondary effect of an incoming move will trigger. The chance is depicted in percentage here,
/// so changing this to above or equal to 100 will make it always hit, while setting it to equal
/// or below 0 will make it never hit.
/// </summary>
void ChangeIncomingEffectChance(IExecutingMove move, IPokemon target, byte hit, ref float chance);
}
/// <summary>
/// This interface allows scripts to trigger when all hits on a target are finished.
/// </summary>
public interface IScriptOnAfterHits
{
/// <summary>
/// This function triggers on a move or its parents when all hits on a target are finished.
/// </summary>
void OnAfterHits(IExecutingMove move, IPokemon target);
}
/// <summary>
/// This interface allows scripts to trigger at the end of each turn.
/// </summary>
public interface IScriptOnEndTurn
{
/// <summary>
/// This function id triggered on all scripts active in the battle after all choices have finished
/// running. Note that choices are not active anymore here, so their scripts do not call this
/// function.
/// </summary>
void OnEndTurn(IScriptSource owner, IBattle battle);
}

View File

@@ -91,6 +91,36 @@ public static class ScriptExecution
}
}
/// <summary>
/// Executes a hook on all scripts in a source.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void RunScriptHookInterface<TScriptHook>(this IEnumerable<IScriptSource> sources,
Action<TScriptHook> hook)
{
var iterator = sources.Distinct().SelectMany(x => x.GetScripts()).ToArray();
List<ScriptCategory>? suppressedCategories = null;
foreach (var container in iterator)
{
if (container.IsEmpty)
continue;
var script = container.Script;
if (script is IScriptOnBeforeAnyHookInvoked onBeforeAnyHookInvoked)
onBeforeAnyHookInvoked.OnBeforeAnyHookInvoked(ref suppressedCategories);
}
foreach (var container in iterator)
{
if (container.IsEmpty)
continue;
var script = container.Script;
if (script is not TScriptHook scriptHook)
continue;
if (suppressedCategories != null && suppressedCategories.Contains(script.Category))
continue;
hook(scriptHook);
}
}
/// <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.
@@ -117,6 +147,35 @@ 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 RunScriptHookInterface<TScriptHook>(this IReadOnlyList<IEnumerable<ScriptContainer>> source,
Action<TScriptHook> hook)
{
List<ScriptCategory>? suppressedCategories = null;
foreach (var container in source.SelectMany(x => x))
{
if (container.IsEmpty)
continue;
var script = container.Script;
if (script is IScriptOnBeforeAnyHookInvoked onBeforeAnyHookInvoked)
onBeforeAnyHookInvoked.OnBeforeAnyHookInvoked(ref suppressedCategories);
}
foreach (var container in source.SelectMany(x => x))
{
if (container.IsEmpty)
continue;
var script = container.Script;
if (script is not TScriptHook scriptHook)
continue;
if (suppressedCategories != null && suppressedCategories.Contains(script.Category))
continue;
hook(scriptHook);
}
}
/// <summary>
/// Executes a script on an item.
/// </summary>