Finish script interface refactor
All checks were successful
Build / Build (push) Successful in 1m1s

This commit is contained in:
2025-07-06 10:27:56 +02:00
parent 83f6a183e3
commit 7c270a6d52
117 changed files with 669 additions and 648 deletions

View File

@@ -14,7 +14,7 @@ namespace PkmnLib.Dynamic.ScriptHandling;
/// developer might require.
/// </summary>
[DebuggerDisplay("{Category} - {Name}")]
public abstract class Script : IDeepCloneable
public abstract class Script : IDeepCloneable, IScriptOnRemove
{
internal event Action<Script>? OnRemoveEvent;
@@ -52,239 +52,17 @@ public abstract class Script : IDeepCloneable
public virtual void OnAddedToParent(IScriptSource source)
{
}
}
/// <summary>
/// This interface is used to allow scripts to run when they are removed from their owner.
/// </summary>
public interface IScriptOnRemove
{
/// <summary>
/// This function is triggered on a Pokemon and its parents when the given Pokemon gains experience,
/// and allows for changing this amount of experience.
/// This function is ran when this script stops being in effect, and is removed from its owner.
/// </summary>
public virtual void ChangeExperienceGained(IPokemon faintedPokemon, IPokemon winningPokemon, ref uint amount)
{
}
/// <summary>
/// This function is triggered on a Pokemon and its parents when the given Pokemon gains experience,
/// and allows for making the experience be shared across multiple Pokemon.
/// Amount is the modifier for how much experience is shared, with 1 being the default amount.
/// </summary>
public virtual void ShareExperience(IPokemon faintedPokemon, IPokemon winningPokemon, ref bool share,
ref float amount)
{
}
/// <summary>
/// This function is triggered on a battle and its parents when something attempts to change the
/// weather, and allows for blocking the weather change.
/// </summary>
public virtual void BlockWeatherChange(IBattle battle, ref bool block)
{
}
/// <summary>
/// This function is called when a Pokeball is thrown at a Pokemon, and allows modifying the catch
/// rate of this attempt. Pokeball modifier effects should be implemented here, as well as for
/// example status effects that change capture rates.
/// </summary>
public virtual void ChangeCatchRateBonus(IPokemon pokemon, IItem pokeball, ref byte modifier)
{
}
/// <summary>
/// Custom triggers for scripts. This allows scripts to run custom events that are not part of the
/// standard battle flow.
/// </summary>
/// <param name="eventName">
/// The name of the event that is triggered. This should be unique for each different event. Overriding scripts
/// should validate the event name is one they should handle.
/// </param>
/// <param name="args">
/// The parameters that are passed to the event.
/// </param>
public virtual void CustomTrigger(StringKey eventName, ICustomTriggerArgs args)
{
}
/// <summary>
/// This function allows a script to change the accuracy of a move used. The value for accuracy is in percentage.
/// A custom case goes when 255 is returned, in which case the entire accuracy check is skipped, and the move
/// will always hit.
/// </summary>
/// <param name="executingMove"></param>
/// <param name="target"></param>
/// <param name="hitIndex"></param>
/// <param name="modifiedAccuracy"></param>
public virtual void ChangeAccuracy(IExecutingMove executingMove, IPokemon target, byte hitIndex,
ref int modifiedAccuracy)
{
}
/// <summary>
/// This function allows a script to change the accuracy of a move that is incoming. The value for accuracy is in percentage.
/// A custom case goes when 255 is returned, in which case the entire accuracy check is skipped, and the move
/// will always hit.
/// </summary>
public virtual void ChangeIncomingAccuracy(IExecutingMove executingMove, IPokemon target, byte hitIndex,
ref int modifiedAccuracy)
{
}
/// <summary>
/// This function allows a script to change the weather duration of a weather effect.
/// </summary>
public virtual void ChangeWeatherDuration(StringKey weatherName, ref int duration)
{
}
/// <summary>
/// This function allows a script to prevent a Pokemon from being healed.
/// </summary>
public virtual void PreventHeal(IPokemon pokemon, uint heal, bool allowRevive, ref bool prevented)
{
}
/// <summary>
/// This function allows a script to change the types a target has. Multiple types can be set, and will be used
/// for the effectiveness calculation.
/// </summary>
public virtual void ChangeTypesForMove(IExecutingMove executingMove, IPokemon target, byte hitIndex,
IList<TypeIdentifier> types)
{
}
/// <summary>
/// This function allows a script to change the types a Pokemon has for a move that's incoming. Multiple types can
/// be set, and will be used for the effectiveness calculation.
/// </summary>
public virtual void ChangeTypesForIncomingMove(IExecutingMove executingMove, IPokemon target, byte hitIndex,
IList<TypeIdentifier> types)
{
}
/// <summary>
/// This function allows a script to change the handling of the move category. This is used for moves that
/// are sometimes a status move, and sometimes a damaging move, such as pollen puff.
/// </summary>
public virtual void ChangeCategory(IExecutingMove move, IPokemon target, byte hitIndex, ref MoveCategory category)
{
}
/// <summary>
/// Triggers first when we're about to hit a target.
/// </summary>
public virtual void OnBeforeHit(IExecutingMove move, IPokemon target, byte hitIndex)
{
}
/// <summary>
/// This function allows a script to prevent a Pokemon from being affected by a status condition.
/// </summary>
public virtual void PreventStatusChange(IPokemon pokemon, StringKey status, bool selfInflicted,
ref bool preventStatus)
{
}
/// <summary>
/// This function triggers after a status condition has been applied to a Pokemon.
/// </summary>
public virtual void OnAfterStatusChange(IPokemon pokemon, StringKey status, IPokemon? originPokemon)
{
}
/// <summary>
/// This function allows a script to prevent a Pokémon from being affected by a volatile status condition.
/// </summary>
public virtual void PreventVolatileAdd(IScriptSource parent, Script script, ref bool preventVolatileAdd)
{
}
/// <summary>
/// This function allows a script to make the Pokémon it is attached to float. This is used for moves
/// such as levitate, and allows for moves such as earthquake to not hit the Pokémon.
/// </summary>
/// <param name="pokemon"></param>
/// <param name="isFloating"></param>
public virtual void IsFloating(IPokemon pokemon, ref bool isFloating)
{
}
/// <summary>
/// This function allows a script to prevent the weather from changing. This is used for abilities such as
/// Delta Stream, which prevent the weather from changing to anything other than strong winds.
/// </summary>
public virtual void PreventWeatherChange(StringKey? weatherName, ref bool preventWeatherChange)
{
}
/// <summary>
/// This function triggers when the weather changes.
/// </summary>
public virtual void OnWeatherChange(IBattle battle, StringKey? weatherName, StringKey? oldWeatherName)
{
}
/// <summary>
/// Modifies the weight of a Pokemon.
/// </summary>
/// <param name="weight">The weight in kilograms</param>
public virtual void ModifyWeight(ref float weight)
{
}
/// <summary>
/// Modifies whether a move is a contact move or not. This is used for abilities such as Long Reach.
/// </summary>
public virtual void ModifyIsContact(IExecutingMove executingMove, IPokemon target, byte hitIndex,
ref bool isContact)
{
}
/// <summary>
/// This function allows a script to prevent a held item from being stolen by an effect such as Thief or Covet.
/// </summary>
public virtual void PreventHeldItemSteal(IPokemon pokemon, IItem heldItem, ref bool prevent)
{
}
/// <summary>
/// This function allows a script to run after a held item has changed.
/// </summary>
public virtual void OnAfterHeldItemChange(IPokemon pokemon, IItem? previous, IItem? item)
{
}
/// <summary>
/// This function allows a script to modify the PP used by a move.
/// </summary>
public virtual void ModifyPPUsed(IExecutingMove executingMove, ref byte ppUsed)
{
}
/// <summary>
/// This function allows a script to modify the PP used by an incoming move. This is used for abilities such as Pressure.
/// </summary>
public virtual void ModifyPPUsedForIncomingMove(IExecutingMove executingMove, ref byte ppUsed)
{
}
/// <summary>
/// This function triggers just before a move choice is executed.
/// </summary>
public virtual void OnBeforeMoveChoice(IMoveChoice moveChoice)
{
}
/// <summary>
/// This function triggers after a move choice has been executed in its entirety.
/// </summary>
public virtual void OnAfterMoveChoice(IMoveChoice moveChoice)
{
}
/// <summary>
/// This function allows a script to change the turn choice that is made by a Pokemon.
/// </summary>
public virtual void ChangeTurnChoice(ref ITurnChoice choice)
{
}
void OnRemove();
}
/// <summary>
@@ -1102,4 +880,328 @@ public interface IScriptChangeIncomingDamage
/// This function allows a script to change any kind of damage that is incoming.
/// </summary>
void ChangeIncomingDamage(IPokemon pokemon, DamageSource source, ref uint damage);
}
/// <summary>
/// This interface allows scripts to change the weather duration of a weather effect.
/// </summary>
public interface IScriptChangeWeatherDuration
{
/// <summary>
/// This function allows a script to change the weather duration of a weather effect.
/// </summary>
void ChangeWeatherDuration(StringKey weatherName, ref int duration);
}
/// <summary>
/// This interface allows scripts to prevent a Pokemon from being healed.
/// </summary>
public interface IScriptPreventHeal
{
/// <summary>
/// This function allows a script to prevent a Pokemon from being healed.
/// </summary>
void PreventHeal(IPokemon pokemon, uint heal, bool allowRevive, ref bool prevented);
}
/// <summary>
/// This interface allows scripts to change the types a target has for effectiveness calculation.
/// </summary>
public interface IScriptChangeTypesForMove
{
/// <summary>
/// This function allows a script to change the types a target has. Multiple types can be set, and will be used
/// for the effectiveness calculation.
/// </summary>
void ChangeTypesForMove(IExecutingMove executingMove, IPokemon target, byte hitIndex, IList<TypeIdentifier> types);
}
/// <summary>
/// This interface allows scripts to change the types a Pokemon has for incoming moves.
/// </summary>
public interface IScriptChangeTypesForIncomingMove
{
/// <summary>
/// This function allows a script to change the types a Pokemon has for a move that's incoming. Multiple types can
/// be set, and will be used for the effectiveness calculation.
/// </summary>
void ChangeTypesForIncomingMove(IExecutingMove executingMove, IPokemon target, byte hitIndex,
IList<TypeIdentifier> types);
}
/// <summary>
/// This interface allows scripts to change the handling of the move category.
/// </summary>
public interface IScriptChangeCategory
{
/// <summary>
/// This function allows a script to change the handling of the move category. This is used for moves that
/// are sometimes a status move, and sometimes a damaging move, such as pollen puff.
/// </summary>
void ChangeCategory(IExecutingMove move, IPokemon target, byte hitIndex, ref MoveCategory category);
}
/// <summary>
/// This interface allows scripts to trigger when about to hit a target.
/// </summary>
public interface IScriptOnBeforeHit
{
/// <summary>
/// Triggers first when we're about to hit a target.
/// </summary>
void OnBeforeHit(IExecutingMove move, IPokemon target, byte hitIndex);
}
/// <summary>
/// This interface allows scripts to prevent a Pokemon from being affected by a status condition.
/// </summary>
public interface IScriptPreventStatusChange
{
/// <summary>
/// This function allows a script to prevent a Pokemon from being affected by a status condition.
/// </summary>
void PreventStatusChange(IPokemon pokemon, StringKey status, bool selfInflicted, ref bool preventStatus);
}
/// <summary>
/// This interface allows scripts to trigger after a status condition has been applied.
/// </summary>
public interface IScriptOnAfterStatusChange
{
/// <summary>
/// This function triggers after a status condition has been applied to a Pokemon.
/// </summary>
void OnAfterStatusChange(IPokemon pokemon, StringKey status, IPokemon? originPokemon);
}
/// <summary>
/// This interface allows scripts to prevent a Pokemon from being affected by a volatile status condition.
/// </summary>
public interface IScriptPreventVolatileAdd
{
/// <summary>
/// This function allows a script to prevent a Pokémon from being affected by a volatile status condition.
/// </summary>
void PreventVolatileAdd(IScriptSource parent, Script script, ref bool preventVolatileAdd);
}
/// <summary>
/// This interface allows scripts to make the Pokemon float.
/// </summary>
public interface IScriptIsFloating
{
/// <summary>
/// This function allows a script to make the Pokémon it is attached to float. This is used for moves
/// such as levitate, and allows for moves such as earthquake to not hit the Pokémon.
/// </summary>
void IsFloating(IPokemon pokemon, ref bool isFloating);
}
/// <summary>
/// This interface allows scripts to prevent weather from changing.
/// </summary>
public interface IScriptPreventWeatherChange
{
/// <summary>
/// This function allows a script to prevent the weather from changing. This is used for abilities such as
/// Delta Stream, which prevent the weather from changing to anything other than strong winds.
/// </summary>
void PreventWeatherChange(StringKey? weatherName, ref bool preventWeatherChange);
}
/// <summary>
/// This interface allows scripts to trigger when the weather changes.
/// </summary>
public interface IScriptOnWeatherChange
{
/// <summary>
/// This function triggers when the weather changes.
/// </summary>
void OnWeatherChange(IBattle battle, StringKey? weatherName, StringKey? oldWeatherName);
}
/// <summary>
/// This interface allows scripts to modify the weight of a Pokemon.
/// </summary>
public interface IScriptModifyWeight
{
/// <summary>
/// Modifies the weight of a Pokemon.
/// </summary>
void ModifyWeight(ref float weight);
}
/// <summary>
/// This interface allows scripts to modify whether a move makes contact.
/// </summary>
public interface IScriptModifyIsContact
{
/// <summary>
/// Modifies whether a move is a contact move or not. This is used for abilities such as Long Reach.
/// </summary>
void ModifyIsContact(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool isContact);
}
/// <summary>
/// This interface allows scripts to prevent held item theft.
/// </summary>
public interface IScriptPreventHeldItemSteal
{
/// <summary>
/// This function allows a script to prevent a held item from being stolen by an effect such as Thief or Covet.
/// </summary>
void PreventHeldItemSteal(IPokemon pokemon, IItem heldItem, ref bool prevent);
}
/// <summary>
/// This interface allows scripts to trigger after held item changes.
/// </summary>
public interface IScriptOnAfterHeldItemChange
{
/// <summary>
/// This function allows a script to run after a held item has changed.
/// </summary>
void OnAfterHeldItemChange(IPokemon pokemon, IItem? previous, IItem? item);
}
/// <summary>
/// This interface allows scripts to modify the PP used by a move.
/// </summary>
public interface IScriptModifyPPUsed
{
/// <summary>
/// This function allows a script to modify the PP used by a move.
/// </summary>
void ModifyPPUsed(IExecutingMove executingMove, ref byte ppUsed);
}
/// <summary>
/// This interface allows scripts to modify the PP used by an incoming move.
/// </summary>
public interface IScriptModifyPPUsedForIncomingMove
{
/// <summary>
/// This function allows a script to modify the PP used by an incoming move. This is used for abilities such as Pressure.
/// </summary>
void ModifyPPUsedForIncomingMove(IExecutingMove executingMove, ref byte ppUsed);
}
/// <summary>
/// This interface allows scripts to trigger just before a move choice is executed.
/// </summary>
public interface IScriptOnBeforeMoveChoice
{
/// <summary>
/// This function triggers just before a move choice is executed.
/// </summary>
void OnBeforeMoveChoice(IMoveChoice moveChoice);
}
/// <summary>
/// This interface allows scripts to trigger after a move choice has been executed.
/// </summary>
public interface IScriptOnAfterMoveChoice
{
/// <summary>
/// This function triggers after a move choice has been executed in its entirety.
/// </summary>
void OnAfterMoveChoice(IMoveChoice moveChoice);
}
/// <summary>
/// This interface allows scripts to change the turn choice that is made by a Pokemon.
/// </summary>
public interface IScriptChangeTurnChoice
{
/// <summary>
/// This function allows a script to change the turn choice that is made by a Pokemon.
/// </summary>
void ChangeTurnChoice(ref ITurnChoice choice);
}
/// <summary>
/// This interface allows scripts to change the experience gained when a Pokemon faints.
/// </summary>
public interface IScriptChangeExperienceGained
{
/// <summary>
/// This function is triggered on a Pokemon and its parents when the given Pokemon gains experience,
/// and allows for changing this amount of experience.
/// </summary>
void ChangeExperienceGained(IPokemon faintedPokemon, IPokemon winningPokemon, ref uint amount);
}
/// <summary>
/// This interface allows scripts to share experience across multiple Pokemon.
/// </summary>
public interface IScriptShareExperience
{
/// <summary>
/// This function is triggered on a Pokemon and its parents when the given Pokemon gains experience,
/// and allows for making the experience be shared across multiple Pokemon.
/// Amount is the modifier for how much experience is shared, with 1 being the default amount.
/// </summary>
void ShareExperience(IPokemon faintedPokemon, IPokemon winningPokemon, ref bool share, ref float amount);
}
/// <summary>
/// This interface allows scripts to block weather changes.
/// </summary>
public interface IScriptBlockWeatherChange
{
/// <summary>
/// This function allows a script to block weather changes.
/// </summary>
void BlockWeatherChange(IBattle battle, ref bool block);
}
/// <summary>
/// This interface allows scripts to change the catch rate bonus when a Pokeball is thrown.
/// </summary>
public interface IScriptChangeCatchRateBonus
{
/// <summary>
/// This function is called when a Pokeball is thrown at a Pokemon, and allows modifying the catch
/// rate of this attempt. Pokeball modifier effects should be implemented here, as well as for
/// example status effects that change capture rates.
/// </summary>
void ChangeCatchRateBonus(IPokemon pokemon, IItem pokeball, ref byte modifier);
}
/// <summary>
/// This interface allows scripts to handle custom trigger events.
/// </summary>
public interface IScriptCustomTrigger
{
/// <summary>
/// Custom triggers for scripts. This allows scripts to run custom events that are not part of the
/// standard battle flow.
/// </summary>
void CustomTrigger(StringKey eventName, ICustomTriggerArgs args);
}
/// <summary>
/// This interface allows scripts to change the accuracy of a move.
/// </summary>
public interface IScriptChangeAccuracy
{
/// <summary>
/// This function allows a script to change the accuracy of a move used. The value for accuracy is in percentage.
/// A custom case goes when 255 is returned, in which case the entire accuracy check is skipped, and the move
/// will always hit.
/// </summary>
void ChangeAccuracy(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref int modifiedAccuracy);
}
/// <summary>
/// This interface allows scripts to change the accuracy of an incoming move.
/// </summary>
public interface IScriptChangeIncomingAccuracy
{
/// <summary>
/// This function allows a script to change the accuracy of a move that is incoming. The value for accuracy is in percentage.
/// A custom case goes when 255 is returned, in which case the entire accuracy check is skipped, and the move
/// will always hit.
/// </summary>
void ChangeIncomingAccuracy(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref int modifiedAccuracy);
}

View File

@@ -13,33 +13,7 @@ public static class ScriptExecution
/// <summary>
/// Executes a hook on all scripts in a source.
/// </summary>
public static void RunScriptHook(this IScriptSource source, Action<Script> hook)
{
var iterator = source.GetScripts();
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 (suppressedCategories != null && suppressedCategories.Contains(script.Category))
continue;
hook(script);
}
}
/// <summary>
/// Executes a hook on all scripts in a source.
/// </summary>
public static void RunScriptHookInterface<TScriptHook>(this IScriptSource source, Action<TScriptHook> hook)
public static void RunScriptHook<TScriptHook>(this IScriptSource source, Action<TScriptHook> hook)
{
var iterator = source.GetScripts();
List<ScriptCategory>? suppressedCategories = null;
@@ -68,35 +42,7 @@ public static class ScriptExecution
/// Executes a hook on all scripts in a source.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void RunScriptHook(this IEnumerable<IScriptSource> sources, Action<Script> 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 (suppressedCategories != null && suppressedCategories.Contains(script.Category))
continue;
hook(script);
}
}
/// <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)
public static void RunScriptHook<TScriptHook>(this IEnumerable<IScriptSource> sources, Action<TScriptHook> hook)
{
var iterator = sources.Distinct().SelectMany(x => x.GetScripts()).ToArray();
List<ScriptCategory>? suppressedCategories = null;
@@ -125,33 +71,7 @@ public static class ScriptExecution
/// 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)
{
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 (suppressedCategories != null && suppressedCategories.Contains(script.Category))
continue;
hook(script);
}
}
/// <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,
public static void RunScriptHook<TScriptHook>(this IReadOnlyList<IEnumerable<ScriptContainer>> source,
Action<TScriptHook> hook)
{
List<ScriptCategory>? suppressedCategories = null;

View File

@@ -108,7 +108,8 @@ public class ScriptSet : IScriptSet
if (!forceAdd)
{
var preventVolatileAdd = false;
_source.RunScriptHook(x => x.PreventVolatileAdd(_source, script, ref preventVolatileAdd));
_source.RunScriptHook<IScriptPreventVolatileAdd>(x =>
x.PreventVolatileAdd(_source, script, ref preventVolatileAdd));
if (preventVolatileAdd)
return null;
}