Bunch more moves, changes in how additional information for items works.

This commit is contained in:
Deukhoofd 2025-04-14 15:29:26 +02:00
parent 2adbb12367
commit 7c2845502d
Signed by: Deukhoofd
GPG Key ID: F63E044490819F6F
60 changed files with 4275 additions and 963 deletions

View File

@ -29,12 +29,12 @@ public static class ItemDataLoader
public delegate IItem ItemFactoryDelegate(SerializedItem serialized, StringKey name, ItemCategory type,
BattleItemCategory battleType, int price, ImmutableHashSet<StringKey> flags, ISecondaryEffect? effect,
ISecondaryEffect? battleTriggerEffect, byte flingPower);
ISecondaryEffect? battleTriggerEffect, Dictionary<StringKey, object?> additionalData);
[PublicAPI]
public static ItemFactoryDelegate ItemConstructor { get; set; } =
(_, name, type, battleType, price, flags, effect, battleTriggerEffect, flingPower) => new ItemImpl(name, type,
battleType, price, flags, effect, battleTriggerEffect, flingPower);
(_, name, type, battleType, price, flags, effect, battleTriggerEffect, additionalData) => new ItemImpl(name,
type, battleType, price, flags, effect, battleTriggerEffect, additionalData);
private static IItem DeserializeItem(SerializedItem serialized)
{
@ -43,9 +43,12 @@ public static class ItemDataLoader
Enum.TryParse(serialized.BattleType, true, out BattleItemCategory battleType);
var effect = serialized.Effect?.ParseEffect();
var battleTriggerEffect = serialized.BattleEffect?.ParseEffect();
var additionalData =
serialized.AdditionalData?.ToDictionary(x => (StringKey)x.Key, x => x.Value.ToParameter()) ??
new Dictionary<StringKey, object?>();
return ItemConstructor(serialized, serialized.Name, itemType, battleType, serialized.Price,
serialized.Flags.Select(x => (StringKey)x).ToImmutableHashSet(), effect, battleTriggerEffect,
serialized.FlingPower);
additionalData);
}
}

View File

@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
namespace PkmnLib.Dataloader.Models;
@ -11,9 +12,10 @@ public class SerializedItem
public string BattleType { get; set; } = null!;
public string[] Flags { get; set; } = null!;
public int Price { get; set; }
public byte FlingPower { get; set; }
public SerializedMoveEffect? Effect { get; set; }
public SerializedMoveEffect? BattleEffect { get; set; }
public Dictionary<string, JsonNode>? AdditionalData { get; set; } = null!;
[JsonExtensionData] public Dictionary<string, JsonElement>? ExtensionData { get; set; }
}

View File

@ -11,18 +11,18 @@ internal static class MoveTurnExecutor
internal static void ExecuteMoveChoice(IBattle battle, IMoveChoice moveChoice)
{
var chosenMove = moveChoice.ChosenMove;
var moveData = chosenMove.MoveData;
var useMove = chosenMove.MoveData;
var moveDataName = moveData.Name;
var moveDataName = useMove.Name;
moveChoice.RunScriptHook(x => x.ChangeMove(moveChoice, ref moveDataName));
if (moveData.Name != moveDataName)
if (useMove.Name != moveDataName)
{
if (!battle.Library.StaticLibrary.Moves.TryGet(moveDataName, out moveData))
if (!battle.Library.StaticLibrary.Moves.TryGet(moveDataName, out useMove))
{
throw new InvalidOperationException(
$"The move was changed to '{moveDataName}' by a script, but this move does not exist.");
}
var secondaryEffect = moveData.SecondaryEffect;
var secondaryEffect = useMove.SecondaryEffect;
if (secondaryEffect != null)
{
if (moveChoice.User.Library.ScriptResolver.TryResolve(ScriptCategory.Move, secondaryEffect.Name,
@ -30,10 +30,18 @@ internal static class MoveTurnExecutor
{
moveChoice.Script.Set(script);
}
else
{
moveChoice.Script.Clear();
}
}
else
{
moveChoice.Script.Clear();
}
}
var targetType = moveData.Target;
var targetType = useMove.Target;
var targets =
TargetResolver.ResolveTargets(battle, moveChoice.TargetSide, moveChoice.TargetPosition, targetType);
moveChoice.RunScriptHook(x => x.ChangeTargets(moveChoice, ref targets));
@ -45,7 +53,7 @@ internal static class MoveTurnExecutor
return;
}
var executingMove = new ExecutingMoveImpl(targets, numberOfHits, chosenMove, moveData, moveChoice);
var executingMove = new ExecutingMoveImpl(targets, numberOfHits, chosenMove, useMove, moveChoice);
var prevented = false;
executingMove.RunScriptHook(x => x.PreventMove(executingMove, ref prevented));
@ -117,8 +125,14 @@ internal static class MoveTurnExecutor
var hitData = (HitData)executingMove.GetDataFromRawIndex(targetHitStat + i);
hitData.Type = hitType;
var effectiveness = battle.Library.StaticLibrary.Types.GetEffectiveness(hitType, target.Types);
var types = target.Types.ToList();
executingMove.RunScriptHook(x => x.ChangeTypesForMove(executingMove, target, hitIndex, types));
target.RunScriptHook(x => x.ChangeTypesForIncomingMove(executingMove, target, hitIndex, types));
var effectiveness = battle.Library.StaticLibrary.Types.GetEffectiveness(hitType, types);
executingMove.RunScriptHook(x => x.ChangeEffectiveness(executingMove, target, hitIndex, ref effectiveness));
target.RunScriptHook(x =>
x.ChangeIncomingEffectiveness(executingMove, target, hitIndex, ref effectiveness));
hitData.Effectiveness = effectiveness;
var blockCritical = false;

View File

@ -235,6 +235,7 @@ public class BattleSideImpl : ScriptSource, IBattleSide
var oldPokemon = _pokemon[position];
if (oldPokemon is not null)
{
oldPokemon.RunScriptHook(script => script.OnSwitchOut(oldPokemon, position));
oldPokemon.RunScriptHook(script => script.OnRemove());
oldPokemon.SetOnBattlefield(false);
}

View File

@ -183,6 +183,8 @@ public class ExecutingMoveImpl : ScriptSource, IExecutingMove
/// <inheritdoc />
public ScriptContainer Script => MoveChoice.Script;
public IScriptSet Volatile => MoveChoice.Volatile;
/// <inheritdoc />
public IHitData GetHitData(IPokemon target, byte hit)
{
@ -224,18 +226,19 @@ public class ExecutingMoveImpl : ScriptSource, IExecutingMove
public IMoveChoice MoveChoice { get; }
/// <inheritdoc />
public override int ScriptCount => 1 + User.ScriptCount;
public override int ScriptCount => 2 + User.ScriptCount;
/// <inheritdoc />
public override void GetOwnScripts(List<IEnumerable<ScriptContainer>> scripts)
{
scripts.Add(Volatile);
scripts.Add(Script);
}
/// <inheritdoc />
public override void CollectScripts(List<IEnumerable<ScriptContainer>> scripts)
{
scripts.Add(Script);
GetOwnScripts(scripts);
User.CollectScripts(scripts);
}
}

View File

@ -8,26 +8,46 @@ namespace PkmnLib.Dynamic.ScriptHandling.Registry;
/// </summary>
public static class ScriptUtils
{
private static readonly Dictionary<Type, StringKey> NameCache = new();
private static readonly Dictionary<Type, (ScriptCategory category, StringKey name)> Cache = new();
/// <summary>
/// Resolve name from the <see cref="ScriptAttribute"/> of the given script.
/// </summary>
public static StringKey ResolveName(this Script script) => ResolveName(script.GetType());
/// <summary>
/// Resolve name from the <see cref="ScriptAttribute"/> of the given type.
/// </summary>
public static StringKey ResolveName<T>() where T : Script => ResolveName(typeof(T));
/// <summary>
/// Resolve name from the <see cref="ScriptAttribute"/> of the given type.
/// </summary>
public static StringKey ResolveName(Type type)
public static StringKey ResolveName(Type type) => GetFromCacheOrAdd(type).name;
/// <summary>
/// Resolve category from the <see cref="ScriptAttribute"/> of the given script.
/// </summary>
public static ScriptCategory ResolveCategory(this Script script) => ResolveCategory(script.GetType());
/// <summary>
/// Resolve category from the <see cref="ScriptAttribute"/> of the given script.
/// </summary>
public static ScriptCategory ResolveCategory<T>() where T : Script => ResolveCategory(typeof(T));
/// <summary>
/// Resolve category from the <see cref="ScriptAttribute"/> of the given type.
/// </summary>
public static ScriptCategory ResolveCategory(Type type) => GetFromCacheOrAdd(type).category;
private static (ScriptCategory category, StringKey name) GetFromCacheOrAdd(Type type)
{
if (NameCache.TryGetValue(type, out var name))
return name;
if (Cache.TryGetValue(type, out var key))
return key;
var scriptAttr = type.GetCustomAttribute<ScriptAttribute>();
if (scriptAttr == null)
throw new InvalidOperationException($"Type {type} does not have a {nameof(ScriptAttribute)}.");
return NameCache[type] = scriptAttr.Name;
return Cache[type] = (scriptAttr.Category, scriptAttr.Name);
}
}

View File

@ -29,6 +29,12 @@ public abstract class Script : IDeepCloneable
/// </summary>
public virtual StringKey Name => this.ResolveName();
/// <summary>
/// The category of a script is used to determine what kind of script it is. This is used to
/// determine what kind of script it is, and what kind of events it can handle.
/// </summary>
public virtual ScriptCategory Category => this.ResolveCategory();
/// <summary>
/// A script can be suppressed by other scripts. If a script is suppressed by at least one script
/// we will not execute its methods. This should return the number of suppressions on the script.
@ -55,6 +61,10 @@ public abstract class Script : IDeepCloneable
/// </summary>
public void Unsuppress() => _suppressCount--;
public virtual void OnBeforeAnyHookInvoked(ref List<ScriptCategory>? suppressedCategories)
{
}
/// <summary>
/// This function is ran when a volatile effect is added while that volatile effect already is
/// in place. Instead of adding the volatile effect twice, it will execute this function instead.
@ -206,6 +216,11 @@ public abstract class Script : IDeepCloneable
{
}
public virtual void ChangeIncomingEffectiveness(IExecutingMove executingMove, IPokemon target, byte hitIndex,
ref float effectiveness)
{
}
/// <summary>
/// This function allows a script to block an outgoing move from being critical.
/// </summary>
@ -473,6 +488,10 @@ public abstract class Script : IDeepCloneable
{
}
public virtual void OnSwitchOut(IPokemon oldPokemon, byte position)
{
}
/// <summary>
/// This function is triggered on a Pokemon and its parents when the given Pokemon is switched into
/// the battlefield.
@ -567,4 +586,14 @@ public abstract class Script : IDeepCloneable
public virtual void PreventHeal(IPokemon pokemon, uint heal, bool allowRevive, ref bool prevented)
{
}
public virtual void ChangeTypesForMove(IExecutingMove executingMove, IPokemon target, byte hitIndex,
IList<TypeIdentifier> types)
{
}
public virtual void ChangeTypesForIncomingMove(IExecutingMove executingMove, IPokemon target, byte hitIndex,
IList<TypeIdentifier> types)
{
}
}

View File

@ -16,6 +16,14 @@ public static class ScriptExecution
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;
script.OnBeforeAnyHookInvoked(ref suppressedCategories);
}
foreach (var container in iterator)
{
if (container.IsEmpty)
@ -23,6 +31,8 @@ public static class ScriptExecution
var script = container.Script;
if (script.IsSuppressed)
continue;
if (suppressedCategories != null && suppressedCategories.Contains(script.Category))
continue;
hook(script);
}
}
@ -33,6 +43,14 @@ public static class ScriptExecution
/// </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;
script.OnBeforeAnyHookInvoked(ref suppressedCategories);
}
foreach (var container in source.SelectMany(x => x))
{
if (container.IsEmpty)
@ -40,6 +58,8 @@ public static class ScriptExecution
var script = container.Script;
if (script.IsSuppressed)
continue;
if (suppressedCategories != null && suppressedCategories.Contains(script.Category))
continue;
hook(script);
}
}

View File

@ -38,6 +38,11 @@ public interface IScriptSet : IEnumerable<ScriptContainer>
/// </summary>
void Remove(StringKey scriptKey);
/// <summary>
/// Removes a script from the set using its type.
/// </summary>
void Remove<T>() where T : Script => Remove(ScriptUtils.ResolveName<T>());
/// <summary>
/// Clears all scripts from the set.
/// </summary>

View File

@ -109,7 +109,9 @@ public interface IItem : INamedValue
ISecondaryEffect? Effect { get; }
ISecondaryEffect? BattleEffect { get; }
byte FlingPower { get; }
IReadOnlyDictionary<StringKey, object?> AdditionalData { get; }
bool TryGetAdditionalData<T>(StringKey key, out T? value);
/// <summary>
/// Checks whether the item has a specific flag.
@ -124,7 +126,8 @@ public class ItemImpl : IItem
{
/// <inheritdoc cref="ItemImpl"/>
public ItemImpl(StringKey name, ItemCategory category, BattleItemCategory battleCategory, int price,
IEnumerable<StringKey> flags, ISecondaryEffect? effect, ISecondaryEffect? battleTriggerEffect, byte flingPower)
IEnumerable<StringKey> flags, ISecondaryEffect? effect, ISecondaryEffect? battleTriggerEffect,
Dictionary<StringKey, object?> additionalData)
{
Name = name;
Category = category;
@ -132,7 +135,7 @@ public class ItemImpl : IItem
Price = price;
Effect = effect;
BattleEffect = battleTriggerEffect;
FlingPower = flingPower;
AdditionalData = additionalData;
Flags = [..flags];
}
@ -158,7 +161,35 @@ public class ItemImpl : IItem
public ISecondaryEffect? BattleEffect { get; }
/// <inheritdoc />
public byte FlingPower { get; }
public IReadOnlyDictionary<StringKey, object?> AdditionalData { get; }
/// <inheritdoc />
public bool TryGetAdditionalData<T>(StringKey key, out T? value)
{
if (AdditionalData.TryGetValue(key, out var obj))
{
switch (obj)
{
case T t:
value = t;
return true;
case IConvertible convertible:
try
{
value = (T)convertible.ToType(typeof(T), null);
return true;
}
catch (InvalidCastException)
{
// Ignore
}
break;
}
}
value = default;
return false;
}
/// <inheritdoc />
public bool HasFlag(string key) => Flags.Contains(key);

View File

@ -33,4 +33,13 @@ public static class EnumerableHelpers
return -1;
}
public static void RemoveAll<T>(this IList<T> list, Func<T, bool> predicate)
{
for (var i = list.Count - 1; i >= 0; i--)
{
if (predicate(list[i]))
list.RemoveAt(i);
}
}
}

View File

@ -55,4 +55,10 @@ public static class NumericHelpers
var result = (ulong)value * multiplier;
return result > uint.MaxValue ? uint.MaxValue : (uint)result;
}
public static uint MultiplyOrMax(this uint value, float multiplier)
{
var result = value * multiplier;
return result > uint.MaxValue ? uint.MaxValue : (uint)result;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -6478,14 +6478,17 @@
"category": "status",
"flags": [
"mirror"
]
],
"effect": {
"name": "magic_room"
}
},
{
"name": "magical_leaf",
"type": "grass",
"power": 60,
"pp": 20,
"accuracy": 0,
"accuracy": 255,
"priority": 0,
"target": "Any",
"category": "special",
@ -6493,6 +6496,7 @@
"protect",
"mirror"
]
// No secondary effect
},
{
"name": "magma_storm",
@ -6506,14 +6510,17 @@
"flags": [
"protect",
"mirror"
]
],
"effect": {
"name": "magma_storm"
}
},
{
"name": "magnet_bomb",
"type": "steel",
"power": 60,
"pp": 20,
"accuracy": 0,
"accuracy": 255,
"priority": 0,
"target": "Any",
"category": "physical",
@ -6522,6 +6529,7 @@
"mirror",
"ballistics"
]
// No secondary effect
},
{
"name": "magnet_rise",
@ -6535,7 +6543,10 @@
"flags": [
"snatch",
"gravity"
]
],
"effect": {
"name": "magnet_rise"
}
},
{
"name": "magnetic_flux",
@ -6565,7 +6576,10 @@
"protect",
"mirror",
"nonskybattle"
]
],
"effect": {
"name": "magnitude"
}
},
{
"name": "malicious_moonsault",
@ -6579,6 +6593,7 @@
"flags": [
"contact"
]
// No secondary effect
},
{
"name": "mat_block",
@ -6592,7 +6607,10 @@
"flags": [
"snatch",
"nonskybattle"
]
],
"effect": {
"name": "mat_block"
}
},
{
"name": "me_first",
@ -6606,7 +6624,10 @@
"flags": [
"protect",
"ignore-substitute"
]
],
"effect": {
"name": "me_first"
}
},
{
"name": "mean_look",
@ -6620,7 +6641,10 @@
"flags": [
"reflectable",
"mirror"
]
],
"effect": {
"name": "mean_look"
}
},
{
"name": "meditate",
@ -6633,7 +6657,13 @@
"category": "status",
"flags": [
"snatch"
]
],
"effect": {
"name": "change_user_attack",
"parameters": {
"amount": 1
}
}
},
{
"name": "mega_drain",
@ -6648,7 +6678,13 @@
"protect",
"mirror",
"heal"
]
],
"effect": {
"name": "drain",
"parameters": {
"drain_modifier": 0.5
}
}
},
{
"name": "mega_kick",
@ -6664,6 +6700,7 @@
"protect",
"mirror"
]
// No secondary effect
},
{
"name": "mega_punch",
@ -6680,6 +6717,7 @@
"mirror",
"punch"
]
// No secondary effect
},
{
"name": "megahorn",
@ -6695,6 +6733,7 @@
"protect",
"mirror"
]
// No secondary effect
},
{
"name": "memento",
@ -6708,7 +6747,10 @@
"flags": [
"protect",
"mirror"
]
],
"effect": {
"name": "memento"
}
},
{
"name": "metal_burst",
@ -6722,7 +6764,10 @@
"flags": [
"protect",
"mirror"
]
],
"effect": {
"name": "metal_burst"
}
},
{
"name": "metal_claw",
@ -6737,7 +6782,14 @@
"contact",
"protect",
"mirror"
]
],
"effect": {
"name": "change_user_attack",
"chance": 10,
"parameters": {
"amount": 1
}
}
},
{
"name": "metal_sound",
@ -6754,7 +6806,13 @@
"mirror",
"sound",
"ignore-substitute"
]
],
"effect": {
"name": "change_target_special_defense",
"parameters": {
"amount": -2
}
}
},
{
"name": "meteor_mash",
@ -6770,7 +6828,14 @@
"protect",
"mirror",
"punch"
]
],
"effect": {
"name": "change_user_attack",
"chance": 20,
"parameters": {
"amount": 1
}
}
},
{
"name": "metronome",
@ -6781,7 +6846,10 @@
"priority": 0,
"target": "Self",
"category": "status",
"flags": []
"flags": [],
"effect": {
"name": "metronome"
}
},
{
"name": "milk_drink",
@ -6795,7 +6863,13 @@
"flags": [
"snatch",
"heal"
]
],
"effect": {
"name": "heal_percent",
"parameters": {
"healPercent": 0.5
}
}
},
{
"name": "mimic",
@ -6809,7 +6883,10 @@
"flags": [
"protect",
"ignore-substitute"
]
],
"effect": {
"name": "mimic"
}
},
{
"name": "mind_reader",
@ -6823,27 +6900,36 @@
"flags": [
"protect",
"mirror"
]
],
"effect": {
"name": "lock_on"
}
},
{
"name": "minimize",
"type": "normal",
"power": 0,
"pp": 10,
"accuracy": 0,
"accuracy": 255,
"priority": 0,
"target": "Self",
"category": "status",
"flags": [
"snatch"
]
],
"effect": {
"name": "change_user_evasion",
"parameters": {
"amount": 2
}
}
},
{
"name": "miracle_eye",
"type": "psychic",
"power": 0,
"pp": 40,
"accuracy": 0,
"accuracy": 255,
"priority": 0,
"target": "Any",
"category": "status",
@ -6852,7 +6938,10 @@
"reflectable",
"mirror",
"ignore-substitute"
]
],
"effect": {
"name": "miracle_eye"
}
},
{
"name": "mirror_coat",
@ -6865,7 +6954,10 @@
"category": "special",
"flags": [
"protect"
]
],
"effect": {
"name": "mirror_coat"
}
},
{
"name": "mirror_move",
@ -6876,7 +6968,10 @@
"priority": 0,
"target": "Any",
"category": "status",
"flags": []
"flags": [],
"effect": {
"name": "mirror_move"
}
},
{
"name": "mirror_shot",
@ -6890,7 +6985,14 @@
"flags": [
"protect",
"mirror"
]
],
"effect": {
"name": "change_target_special_defense",
"chance": 30,
"parameters": {
"amount": -1
}
}
},
{
"name": "mist",
@ -6903,7 +7005,10 @@
"category": "status",
"flags": [
"snatch"
]
],
"effect": {
"name": "mist"
}
},
{
"name": "mist_ball",
@ -6917,7 +7022,14 @@
"flags": [
"protect",
"mirror"
]
],
"effect": {
"name": "change_target_special_attack",
"chance": 50,
"parameters": {
"amount": -1
}
}
},
{
"name": "misty_terrain",
@ -6930,7 +7042,10 @@
"category": "status",
"flags": [
"nonskybattle"
]
],
"effect": {
"name": "misty_terrain"
}
},
{
"name": "moonblast",
@ -6944,7 +7059,14 @@
"flags": [
"protect",
"mirror"
]
],
"effect": {
"name": "change_target_special_attack",
"chance": 30,
"parameters": {
"amount": -1
}
}
},
{
"name": "moongeist_beam",
@ -6958,7 +7080,10 @@
"flags": [
"protect",
"mirror"
]
],
"effect": {
"name": "moongeist_beam"
}
},
{
"name": "moonlight",
@ -6972,7 +7097,10 @@
"flags": [
"snatch",
"heal"
]
],
"effect": {
"name": "moonlight"
}
},
{
"name": "morning_sun",
@ -6986,7 +7114,10 @@
"flags": [
"snatch",
"heal"
]
],
"effect": {
"name": "moonlight"
}
},
{
"name": "mud_bomb",
@ -7001,7 +7132,14 @@
"protect",
"mirror",
"ballistics"
]
],
"effect": {
"name": "change_target_accuracy",
"chance": 30,
"parameters": {
"amount": -1
}
}
},
{
"name": "mud_shot",
@ -7015,7 +7153,13 @@
"flags": [
"protect",
"mirror"
]
],
"effect": {
"name": "change_target_speed",
"parameters": {
"amount": -1
}
}
},
{
"name": "mud_slap",
@ -7029,7 +7173,13 @@
"flags": [
"protect",
"mirror"
]
],
"effect": {
"name": "change_target_accuracy",
"parameters": {
"amount": -1
}
}
},
{
"name": "mud_sport",
@ -7042,7 +7192,10 @@
"category": "status",
"flags": [
"nonskybattle"
]
],
"effect": {
"name": "mud_sport"
}
},
{
"name": "muddy_water",
@ -7057,7 +7210,14 @@
"protect",
"mirror",
"nonskybattle"
]
],
"effect": {
"name": "change_target_accuracy",
"chance": 30,
"parameters": {
"amount": -1
}
}
},
{
"name": "multi_attack",
@ -7072,7 +7232,10 @@
"contact",
"protect",
"mirror"
]
],
"effect": {
"name": "multi_attack"
}
},
{
"name": "mystical_fire",
@ -7086,7 +7249,13 @@
"flags": [
"protect",
"mirror"
]
],
"effect": {
"name": "change_target_special_attack",
"parameters": {
"amount": -1
}
}
},
{
"name": "nasty_plot",
@ -7099,7 +7268,13 @@
"category": "status",
"flags": [
"snatch"
]
],
"effect": {
"name": "change_user_special_attack",
"parameters": {
"amount": 2
}
}
},
{
"name": "natural_gift",
@ -7113,7 +7288,10 @@
"flags": [
"protect",
"mirror"
]
],
"effect": {
"name": "natural_gift"
}
},
{
"name": "nature_power",
@ -7124,7 +7302,10 @@
"priority": 0,
"target": "Any",
"category": "status",
"flags": []
"flags": [],
"effect": {
"name": "nature_power"
}
},
{
"name": "natures_madness",
@ -7138,7 +7319,10 @@
"flags": [
"protect",
"mirror"
]
],
"effect": {
"name": "natures_madness"
}
},
{
"name": "needle_arm",
@ -7153,7 +7337,11 @@
"contact",
"protect",
"mirror"
]
],
"effect": {
"name": "flinch",
"chance": 30
}
},
{
"name": "never_ending_nightmare__physical",
@ -7165,6 +7353,7 @@
"target": "Any",
"category": "physical",
"flags": []
// No secondary effect
},
{
"name": "never_ending_nightmare__special",
@ -7176,6 +7365,7 @@
"target": "Any",
"category": "special",
"flags": []
// No secondary effect
},
{
"name": "night_daze",
@ -7189,7 +7379,14 @@
"flags": [
"protect",
"mirror"
]
],
"effect": {
"name": "change_target_accuracy",
"chance": 40,
"parameters": {
"amount": -1
}
}
},
{
"name": "night_shade",
@ -7203,7 +7400,10 @@
"flags": [
"protect",
"mirror"
]
],
"effect": {
"name": "night_shade"
}
},
{
"name": "night_slash",
@ -7218,7 +7418,10 @@
"contact",
"protect",
"mirror"
]
],
"effect": {
"name": "increased_critical_stage"
}
},
{
"name": "nightmare",
@ -7232,7 +7435,10 @@
"flags": [
"protect",
"mirror"
]
],
"effect": {
"name": "nightmare"
}
},
{
"name": "noble_roar",
@ -7249,7 +7455,14 @@
"mirror",
"sound",
"ignore-substitute"
]
],
"effect": {
"name": "change_multiple_target_stat_boosts",
"parameters": {
"attack": -1,
"specialAttack": -1
}
}
},
{
"name": "nuzzle",
@ -7264,7 +7477,13 @@
"contact",
"protect",
"mirror"
]
],
"effect": {
"name": "set_status",
"parameters": {
"status": "paralyzed"
}
}
},
{
"name": "oblivion_wing",
@ -7280,7 +7499,13 @@
"mirror",
"distance",
"heal"
]
],
"effect": {
"name": "drain",
"parameters": {
"drain_modifier": 0.75
}
}
},
{
"name": "oceanic_operetta",
@ -7292,6 +7517,7 @@
"target": "Any",
"category": "special",
"flags": []
// No secondary effect
},
{
"name": "octazooka",
@ -7306,7 +7532,14 @@
"protect",
"mirror",
"ballistics"
]
],
"effect": {
"name": "change_target_accuracy",
"chance": 50,
"parameters": {
"amount": -1
}
}
},
{
"name": "odor_sleuth",

View File

@ -0,0 +1,89 @@
using PkmnLib.Dynamic.Libraries;
using PkmnLib.Dynamic.Models;
using PkmnLib.Plugin.Gen7.Scripts.Moves;
using PkmnLib.Static;
using PkmnLib.Static.Libraries;
namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
public class MultiAttackTests
{
public record TestCaseData(string? ItemName, string ExpectedTypeName)
{
public override string ToString() => $"MultiAttack {ItemName ?? "None"} => {ExpectedTypeName}";
}
public static IEnumerable<Func<TestCaseData>> ChangeMoveTypeData()
{
yield return () => new TestCaseData("bug_memory", "bug");
yield return () => new TestCaseData("dark_memory", "dark");
yield return () => new TestCaseData("dragon_memory", "dragon");
yield return () => new TestCaseData("electric_memory", "electric");
yield return () => new TestCaseData("fairy_memory", "fairy");
yield return () => new TestCaseData("fighting_memory", "fighting");
yield return () => new TestCaseData("fire_memory", "fire");
yield return () => new TestCaseData("flying_memory", "flying");
yield return () => new TestCaseData("ghost_memory", "ghost");
yield return () => new TestCaseData("grass_memory", "grass");
yield return () => new TestCaseData("ground_memory", "ground");
yield return () => new TestCaseData("ice_memory", "ice");
yield return () => new TestCaseData("poison_memory", "poison");
yield return () => new TestCaseData("psychic_memory", "psychic");
yield return () => new TestCaseData("rock_memory", "rock");
yield return () => new TestCaseData("steel_memory", "steel");
yield return () => new TestCaseData("water_memory", "water");
yield return () => new TestCaseData("something_random", "normal");
yield return () => new TestCaseData(null, "normal");
}
[Test, MethodDataSource(nameof(ChangeMoveTypeData))]
public async Task ChangeMoveType_UserHoldingMemoryItem_ChangeMoveType(TestCaseData test)
{
var typeLibrary = new TypeLibrary();
typeLibrary.RegisterType("normal");
typeLibrary.RegisterType("fighting");
typeLibrary.RegisterType("flying");
typeLibrary.RegisterType("poison");
typeLibrary.RegisterType("ground");
typeLibrary.RegisterType("rock");
typeLibrary.RegisterType("bug");
typeLibrary.RegisterType("ghost");
typeLibrary.RegisterType("steel");
typeLibrary.RegisterType("fire");
typeLibrary.RegisterType("water");
typeLibrary.RegisterType("grass");
typeLibrary.RegisterType("electric");
typeLibrary.RegisterType("psychic");
typeLibrary.RegisterType("ice");
typeLibrary.RegisterType("dragon");
typeLibrary.RegisterType("dark");
typeLibrary.RegisterType("fairy");
// Arrange
var move = new Mock<IExecutingMove>();
var target = new Mock<IPokemon>();
var user = new Mock<IPokemon>();
var typeIdentifier = new TypeIdentifier(1, "Normal");
var dynamicLibrary = new Mock<IDynamicLibrary>();
var staticLibrary = new Mock<IStaticLibrary>();
var item = new Mock<IItem>();
user.Setup(u => u.Library).Returns(dynamicLibrary.Object);
dynamicLibrary.Setup(d => d.StaticLibrary).Returns(staticLibrary.Object);
staticLibrary.Setup(s => s.Types).Returns(typeLibrary);
item.Setup(i => i.Name).Returns(test.ItemName!);
move.Setup(m => m.User).Returns(user.Object);
if (test.ItemName != null)
move.Setup(m => m.User.HeldItem).Returns(item.Object);
else
move.Setup(m => m.User.HeldItem).Returns((IItem?)null);
var multiAttack = new MultiAttack();
// Act
multiAttack.ChangeMoveType(move.Object, target.Object, 0, ref typeIdentifier);
// Assert
await Assert.That(typeIdentifier.Name).IsEqualTo(test.ExpectedTypeName);
}
}

View File

@ -1,4 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using PkmnLib.Static;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Battle;
@ -6,24 +9,15 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Battle;
public class Gravity : Script
{
/// <inheritdoc />
public override void ChangeEffectiveness(IExecutingMove move, IPokemon target, byte hit, ref float effectiveness)
public override void ChangeTypesForIncomingMove(IExecutingMove executingMove, IPokemon target, byte hitIndex,
IList<TypeIdentifier> types)
{
var battleData = target.BattleData;
if (battleData == null)
return;
var typeLibrary = target.Library.StaticLibrary.Types;
if (move.UseMove.MoveType.Name == "ground")
{
var targetTypes = target.Types;
var typeLibrary = battleData.Battle.Library.StaticLibrary.Types;
effectiveness =
// Get the effectiveness of the move against each target type
targetTypes.Select(x => typeLibrary.GetSingleEffectiveness(move.UseMove.MoveType, x))
// Ignore all types that are immune to ground moves
.Where(x => x > 0)
// Multiply all effectiveness values together
.Aggregate(1.0f, (current, x) => current * x);
}
if (executingMove.UseMove.MoveType.Name != "ground")
return;
// Remove all types that are immune to ground moves
types.RemoveAll(x => typeLibrary.GetSingleEffectiveness(executingMove.UseMove.MoveType, x) == 0);
}
/// <inheritdoc />

View File

@ -0,0 +1,32 @@
using System.Collections.Generic;
using PkmnLib.Static;
namespace PkmnLib.Plugin.Gen7.Scripts.Battle;
[Script(ScriptCategory.Battle, "magic_room")]
public class MagicRoomEffect : Script
{
private int _turnsLeft = 5;
/// <inheritdoc />
public override void PreventHeldItemConsume(IPokemon pokemon, IItem heldItem, ref bool prevented)
{
prevented = true;
}
/// <inheritdoc />
public override void OnBeforeAnyHookInvoked(ref List<ScriptCategory>? suppressedCategories)
{
suppressedCategories ??= [];
suppressedCategories.Add(ScriptCategory.ItemBattleTrigger);
}
/// <inheritdoc />
public override void OnEndTurn(IBattle battle)
{
if (_turnsLeft > 0)
_turnsLeft--;
else
RemoveSelf();
}
}

View File

@ -0,0 +1,28 @@
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Battle;
[Script(ScriptCategory.Battle, "mud_sport")]
public class MudSportEffect : Script
{
private int _turnsLeft = 5;
/// <inheritdoc />
public override void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref byte basePower)
{
if (move.UseMove.MoveType.Name == "electric")
{
basePower = basePower.MultiplyOrMax(2f / 3f);
}
}
/// <inheritdoc />
public override void OnEndTurn(IBattle battle)
{
_turnsLeft--;
if (_turnsLeft <= 0)
{
RemoveSelf();
}
}
}

View File

@ -0,0 +1,11 @@
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.MoveVolatile;
[Script(ScriptCategory.MoveVolatile, "me_first")]
public class MeFirstPowerBoost : Script
{
/// <inheritdoc />
public override void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref byte basePower) =>
basePower = basePower.MultiplyOrMax(1.5f);
}

View File

@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Linq;
using PkmnLib.Static;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "change_multiple_target_stat_boosts")]
public class ChangeMultipleTargetStatBoosts : Script
{
private Dictionary<Statistic, sbyte> _statBoosts = new();
/// <inheritdoc />
public override void OnInitialize(IReadOnlyDictionary<StringKey, object?>? parameters)
{
if (parameters == null)
{
throw new ArgumentNullException(nameof(parameters));
}
foreach (var par in parameters)
{
if (!Enum.TryParse<Statistic>(par.Key, true, out var stat))
throw new ArgumentException($"Invalid stat name: {par.Key}");
if (par.Value is sbyte value)
{
_statBoosts[stat] = value;
}
}
}
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
EventBatchId batchId = new();
foreach (var stat in _statBoosts!)
{
target.ChangeStatBoost(stat.Key, stat.Value, true, batchId);
}
}
}

View File

@ -21,8 +21,13 @@ public class Fling : Script
move.GetHitData(target, hit).Fail();
return;
}
if (!item.TryGetAdditionalData<byte>("fling_power", out var flingPower))
{
move.GetHitData(target, hit).Fail();
return;
}
basePower = item.FlingPower;
basePower = flingPower;
}
/// <inheritdoc />

View File

@ -1,20 +1,18 @@
using System.Collections.Generic;
using PkmnLib.Static;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "flying_press")]
public class FlyingPress : Script
{
/// <inheritdoc />
public override void ChangeEffectiveness(IExecutingMove move, IPokemon target, byte hit, ref float effectiveness)
public override void ChangeTypesForMove(IExecutingMove executingMove, IPokemon target, byte hitIndex,
IList<TypeIdentifier> types)
{
var battleData = move.User.BattleData;
if (battleData == null)
return;
var typeLibrary = battleData.Battle.Library.StaticLibrary.Types;
// If flying type is not found, return
var typeLibrary = executingMove.User.Library.StaticLibrary.Types;
if (!typeLibrary.TryGetTypeIdentifier("flying", out var flyingType))
return;
var flyingEffectiveness = typeLibrary.GetEffectiveness(flyingType, target.Types);
effectiveness *= flyingEffectiveness;
types.Add(flyingType);
}
}

View File

@ -0,0 +1,18 @@
using PkmnLib.Plugin.Gen7.Scripts.Battle;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "magic_room")]
public class MagicRoom : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var battleData = move.User.BattleData;
if (battleData == null)
return;
var battle = battleData.Battle;
battle.Volatile.Add(new MagicRoomEffect());
}
}

View File

@ -0,0 +1,13 @@
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "magma_storm")]
public class MagmaStorm : MultiHitMove
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
target.Volatile.StackOrAdd("magma_storm", () => new MagmaStormEffect(target));
}
}

View File

@ -0,0 +1,19 @@
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "magnet_rise")]
public class MagnetRise : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
if (move.User.Volatile.Contains(ScriptUtils.ResolveName<IngrainEffect>()) ||
move.User.ActiveAbility?.Name == "levitate")
{
move.GetHitData(target, hit).Fail();
return;
}
move.User.Volatile.Add(new MagnetRiseEffect());
}
}

View File

@ -0,0 +1,52 @@
using System.Collections.Generic;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "magnitude")]
public class Magnitude : Script
{
/// <inheritdoc />
public override void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref byte basePower)
{
var battleData = move.User.BattleData;
if (battleData == null)
return;
var random = battleData.Battle.Random;
var magnitudePercentage = random.GetInt(0, 100);
var magnitude = magnitudePercentage switch
{
// 5% chance for 4
< 5 => 4,
// 10% chance for 5
< 15 => 5,
// 20% chance for 6
< 35 => 6,
// 30% chance for 7
< 65 => 7,
// 20% chance for 8
< 85 => 8,
// 10% chance for 9
< 95 => 9,
// 5% chance for 10
_ => 10,
};
battleData.Battle.EventHook.Invoke(new DialogEvent("magnitude", new Dictionary<string, object>()
{
{ "magnitude", magnitude },
{ "user", move.User },
{ "target", target },
}));
basePower = magnitude switch
{
4 => 10,
5 => 30,
6 => 50,
7 => 70,
8 => 90,
9 => 110,
_ => 150,
};
}
}

View File

@ -0,0 +1,22 @@
using PkmnLib.Plugin.Gen7.Scripts.Side;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "mat_block")]
public class MatBlock : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var battleData = move.User.BattleData;
if (battleData == null)
return;
if (battleData.SwitchInTurn != battleData.Battle.CurrentTurnNumber)
{
move.GetHitData(target, hit).Fail();
return;
}
battleData.BattleSide.VolatileScripts.Add(new MatBlockEffect());
}
}

View File

@ -0,0 +1,31 @@
using PkmnLib.Plugin.Gen7.Scripts.MoveVolatile;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "me_first")]
public class MeFirst : Script
{
/// <inheritdoc />
public override void ChangeMove(IMoveChoice choice, ref StringKey moveName)
{
var battleData = choice.User.BattleData;
if (battleData == null)
return;
var target = battleData.Battle.Sides[choice.TargetSide].Pokemon[choice.TargetPosition];
if (target == null)
{
choice.Fail();
return;
}
if (battleData.Battle.ChoiceQueue?.FirstOrDefault(x => x.User == target) is not IMoveChoice targetMove)
{
choice.Fail();
return;
}
var targetMoveData = targetMove.ChosenMove.MoveData;
moveName = targetMoveData.Name;
choice.Volatile.Add(new MeFirstPowerBoost());
}
}

View File

@ -0,0 +1,15 @@
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "mean_look")]
public class MeanLook : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var targetEffect = target.Volatile.Add(new MeanLookEffectTarget());
var userEffect = new MeanLookEffectUser(targetEffect);
move.User.Volatile.Add(userEffect);
}
}

View File

@ -0,0 +1,17 @@
using PkmnLib.Static;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "memento")]
public class Memento : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var evtBatch = new EventBatchId();
target.ChangeStatBoost(Statistic.Attack, -2, false, evtBatch);
target.ChangeStatBoost(Statistic.SpecialAttack, -2, false, evtBatch);
move.User.Faint(DamageSource.Misc);
}
}

View File

@ -0,0 +1,51 @@
using PkmnLib.Static.Moves;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "metal_burst")]
public class MetalBurst : Script
{
/// <inheritdoc />
public override void OnBeforeTurnStart(ITurnChoice choice)
{
choice.User.Volatile.Add(new MetalBurstHelper());
}
/// <inheritdoc />
public override void ChangeMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage)
{
var helper = target.Volatile.Get<MetalBurstHelper>();
if (helper?.LastAttacker == null || helper.LastAttacker != move.User)
{
move.GetHitData(target, hit).Fail();
return;
}
damage = helper.LastDamage.MultiplyOrMax(1.5f);
target.Volatile.Remove<MetalBurstHelper>();
}
[Script(ScriptCategory.Pokemon, "metal_burst_helper")]
private class MetalBurstHelper : Script
{
public IPokemon? LastAttacker { get; set; }
public uint LastDamage { get; set; }
/// <inheritdoc />
public override void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit)
{
if (move.UseMove.Category == MoveCategory.Status)
return;
LastAttacker = move.User;
LastDamage = move.GetHitData(target, hit).Damage;
}
/// <inheritdoc />
public override void OnEndTurn(IBattle battle)
{
RemoveSelf();
}
}
}

View File

@ -0,0 +1,38 @@
using System;
using PkmnLib.Plugin.Gen7.Scripts.Utils;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "metronome")]
public class Metronome : Script
{
/// <inheritdoc />
public override void ChangeMove(IMoveChoice choice, ref StringKey moveName)
{
var battleData = choice.User.BattleData;
if (battleData == null)
return;
var library = choice.User.Library;
var moveLibrary = library.StaticLibrary.Moves;
var retryCount = 0;
while (true)
{
// Breaker for infinite loop. The odds that we'd accidentally roll a non-copyable move 100 times in a row
// is extremely unlikely, unless the loaded move library only contains non-copyable moves.
// This is a failsafe.
if (retryCount > 100)
throw new Exception("Metronome failed to find a valid move after 100 attempts.");
var randomMove = moveLibrary.GetRandom(battleData.Battle.Random);
if (!randomMove.CanCopyMove())
{
retryCount++;
continue;
}
moveName = randomMove.Name;
break;
}
}
}

View File

@ -0,0 +1,9 @@
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "mimic")]
public class Mimic : Script
{
// FIXME: support for temporarily copying moves to a move slot.
}

View File

@ -0,0 +1,18 @@
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
using PkmnLib.Static;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "miracle_eye")]
public class MiracleEye : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
if (target.StatBoost.Evasion > 0)
{
target.ChangeStatBoost(Statistic.Evasion, (sbyte)-target.StatBoost.Evasion, false);
}
target.Volatile.Add(new MiracleEyeEffect());
}
}

View File

@ -0,0 +1,51 @@
using PkmnLib.Static.Moves;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "mirror_coat")]
public class MirrorCoat : Script
{
/// <inheritdoc />
public override void OnBeforeTurnStart(ITurnChoice choice)
{
choice.User.Volatile.Add(new MirrorCoatHelper());
}
/// <inheritdoc />
public override void ChangeMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage)
{
var helper = target.Volatile.Get<MirrorCoatHelper>();
if (helper?.LastAttacker == null || helper.LastAttacker != move.User)
{
move.GetHitData(target, hit).Fail();
return;
}
damage = helper.LastDamage.MultiplyOrMax(2f);
target.Volatile.Remove<MirrorCoatHelper>();
}
[Script(ScriptCategory.Pokemon, "mirror_coat_helper")]
private class MirrorCoatHelper : Script
{
public IPokemon? LastAttacker { get; set; }
public uint LastDamage { get; set; }
/// <inheritdoc />
public override void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit)
{
if (move.UseMove.Category != MoveCategory.Special)
return;
LastAttacker = move.User;
LastDamage = move.GetHitData(target, hit).Damage;
}
/// <inheritdoc />
public override void OnEndTurn(IBattle battle)
{
RemoveSelf();
}
}
}

View File

@ -0,0 +1,30 @@
using System.Linq;
using PkmnLib.Plugin.Gen7.Scripts.Utils;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "mirror_move")]
public class MirrorMove : Script
{
/// <inheritdoc />
public override void ChangeMove(IMoveChoice choice, ref StringKey moveName)
{
var battleData = choice.User.BattleData;
if (battleData == null)
return;
var battle = battleData.Battle;
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);
if (lastMove == null || !lastMove.ChosenMove.MoveData.CanCopyMove())
{
choice.Fail();
return;
}
moveName = lastMove.ChosenMove.MoveData.Name;
}
}

View File

@ -0,0 +1,17 @@
using PkmnLib.Plugin.Gen7.Scripts.Side;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "mist")]
public class Mist : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var battleData = move.User.BattleData;
if (battleData == null)
return;
battleData.BattleSide.VolatileScripts.Add(new MistEffect());
}
}

View File

@ -0,0 +1,14 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "misty_terrain")]
public class MistyTerrain : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var battle = move.User.BattleData?.Battle;
if (battle is null)
return;
battle.SetTerrain(ScriptUtils.ResolveName<Terrain.MistyTerrain>());
}
}

View File

@ -0,0 +1,14 @@
using System.Collections.Generic;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "moongeist_beam")]
public class MoongeistBeam : Script
{
/// <inheritdoc />
public override void OnBeforeAnyHookInvoked(ref List<ScriptCategory>? suppressedCategories)
{
suppressedCategories ??= [];
suppressedCategories.Add(ScriptCategory.Ability);
}
}

View File

@ -0,0 +1,28 @@
using PkmnLib.Plugin.Gen7.Scripts.Weather;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "moonlight")]
public class Moonlight : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var battleData = move.User.BattleData;
if (battleData == null)
return;
var fraction = 0.5f;
var weather = battleData.Battle.WeatherName;
if (weather == ScriptUtils.ResolveName<Sunny>())
fraction = 2f / 3f;
else if (weather == ScriptUtils.ResolveName<Rain>() || weather == ScriptUtils.ResolveName<Hail>() ||
weather == ScriptUtils.ResolveName<Sandstorm>())
fraction = 0.25f;
var maxHp = target.BoostedStats.Hp;
var healAmount = maxHp.MultiplyOrMax(fraction);
move.User.Heal(healAmount);
}
}

View File

@ -0,0 +1,17 @@
using PkmnLib.Plugin.Gen7.Scripts.Battle;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "mud_sport")]
public class MudSport : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var battle = move.User.BattleData?.Battle;
if (battle == null)
return;
battle.Volatile.Add(new MudSportEffect());
}
}

View File

@ -0,0 +1,20 @@
using PkmnLib.Static;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "multi_attack")]
public class MultiAttack : Script
{
/// <inheritdoc />
public override void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier moveType)
{
var item = move.User.HeldItem?.Name.ToString();
var typeLibrary = move.User.Library.StaticLibrary.Types;
if (item?.EndsWith("_memory") != true)
return;
var memoryType = item[..^7];
typeLibrary.TryGetTypeIdentifier(memoryType, out moveType);
}
}

View File

@ -0,0 +1,60 @@
using System.Collections.Generic;
using PkmnLib.Static;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "natural_gift")]
public class NaturalGift : Script
{
/// <inheritdoc />
public override void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref byte basePower)
{
var naturalGiftData = GetNaturalGiftData(move.User.HeldItem);
if (naturalGiftData == null)
{
move.GetHitData(target, hit).Fail();
return;
}
basePower = (byte)naturalGiftData.Value.power;
}
/// <inheritdoc />
public override void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier moveType)
{
var naturalGiftData = GetNaturalGiftData(move.User.HeldItem);
if (naturalGiftData == null)
{
move.GetHitData(target, hit).Fail();
return;
}
var typeLibrary = move.User.Library.StaticLibrary.Types;
if (!typeLibrary.TryGetTypeIdentifier(naturalGiftData.Value.type, out var type))
{
move.GetHitData(target, hit).Fail();
return;
}
moveType = type;
}
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
_ = move.User.RemoveHeldItem();
}
private static (int power, StringKey type)? GetNaturalGiftData(IItem? item)
{
if (item == null)
return null;
if (!item.AdditionalData.TryGetValue("naturalGift", out var data) ||
data is not IReadOnlyDictionary<string, object> dict)
return null;
if (dict.TryGetValue("power", out var power) && dict.TryGetValue("type", out var type))
{
return ((int)power, (StringKey)(string)type);
}
return null;
}
}

View File

@ -0,0 +1,7 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "nature_power")]
public class NaturePower : Script
{
// FIXME: Implement this. How to get the terrain in a sane manner?
}

View File

@ -0,0 +1,12 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "natures_madness")]
public class NaturesMadness : Script
{
/// <inheritdoc />
public override void ChangeMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage)
{
var targetMaxHp = target.BoostedStats.Hp;
damage = targetMaxHp / 2;
}
}

View File

@ -0,0 +1,11 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "night_shade")]
public class NightShade : Script
{
/// <inheritdoc />
public override void ChangeMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage)
{
damage = move.User.Level;
}
}

View File

@ -0,0 +1,19 @@
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
using PkmnLib.Plugin.Gen7.Scripts.Status;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "nightmare")]
public class Nightmare : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
if (!target.HasStatus(ScriptUtils.ResolveName<Sleep>()))
{
move.GetHitData(target, hit).Fail();
return;
}
target.Volatile.Add(new NightmareEffect(target));
}
}

View File

@ -1,6 +1,8 @@
using System.Collections.Generic;
using System.Linq;
using PkmnLib.Static;
using PkmnLib.Static.Libraries;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
@ -29,12 +31,12 @@ public class ForesightEffect : Script
}
/// <inheritdoc />
public override void ChangeEffectiveness(IExecutingMove move, IPokemon target, byte hit, ref float effectiveness)
public override void ChangeTypesForIncomingMove(IExecutingMove executingMove, IPokemon target, byte hitIndex,
IList<TypeIdentifier> types)
{
var hitData = move.GetHitData(target, hit);
if (hitData.Type == _normalType && target.Types.Contains(_fightingType))
effectiveness = _typeLibrary.GetEffectiveness(_normalType, target.Types.Where(x => x != _ghostType));
else if (hitData.Type == _fightingType && target.Types.Contains(_ghostType))
effectiveness = _typeLibrary.GetEffectiveness(_fightingType, target.Types.Where(x => x != _ghostType));
if (executingMove.UseMove.MoveType == _normalType || executingMove.UseMove.MoveType == _fightingType)
{
types.RemoveAll(x => x == _ghostType);
}
}
}

View File

@ -1,4 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using PkmnLib.Static;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
@ -35,22 +38,14 @@ public class IngrainEffect : Script
}
/// <inheritdoc />
public override void ChangeEffectiveness(IExecutingMove move, IPokemon target, byte hit, ref float effectiveness)
public override void ChangeTypesForIncomingMove(IExecutingMove executingMove, IPokemon target, byte hitIndex,
IList<TypeIdentifier> types)
{
var battleData = target.BattleData;
if (battleData == null)
return;
if (move.UseMove.MoveType.Name != "ground")
return;
var targetTypes = target.Types;
var typeLibrary = battleData.Battle.Library.StaticLibrary.Types;
effectiveness =
// Get the effectiveness of the move against each target type
targetTypes.Select(x => typeLibrary.GetSingleEffectiveness(move.UseMove.MoveType, x))
// Ignore all types that are immune to ground moves
.Where(x => x > 0)
// Multiply all effectiveness values together
.Aggregate(1.0f, (current, x) => current * x);
if (executingMove.UseMove.MoveType.Name == "ground")
{
var typeLibrary = target.Library.StaticLibrary.Types;
// Remove all types that are immune to ground moves
types.RemoveAll(x => typeLibrary.GetSingleEffectiveness(executingMove.UseMove.MoveType, x) == 0);
}
}
}

View File

@ -0,0 +1,24 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
[Script(ScriptCategory.Pokemon, "magma_storm")]
public class MagmaStormEffect : Script
{
private readonly IPokemon _owner;
public MagmaStormEffect(IPokemon owner)
{
_owner = owner;
}
/// <inheritdoc />
public override void OnEndTurn(IBattle battle)
{
_owner.Damage(_owner.BoostedStats.Hp / 16, DamageSource.Misc);
}
/// <inheritdoc />
public override void PreventSelfRunAway(IFleeChoice choice, ref bool prevent) => prevent = true;
/// <inheritdoc />
public override void PreventSelfSwitch(ISwitchChoice choice, ref bool prevent) => prevent = true;
}

View File

@ -0,0 +1,25 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
[Script(ScriptCategory.Pokemon, "magnet_rise")]
public class MagnetRiseEffect : Script
{
private int _turnsRemaining = 5;
/// <inheritdoc />
public override void ChangeEffectiveness(IExecutingMove move, IPokemon target, byte hit, ref float effectiveness)
{
if (move.UseMove.MoveType.Name == "ground")
{
effectiveness = 0.0f;
}
}
/// <inheritdoc />
public override void OnEndTurn(IBattle battle)
{
if (_turnsRemaining > 0)
_turnsRemaining--;
else
RemoveSelf();
}
}

View File

@ -0,0 +1,29 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
[Script(ScriptCategory.Pokemon, "mean_look_user")]
public class MeanLookEffectUser : Script
{
private readonly ScriptContainer _targetScriptEffect;
public MeanLookEffectUser(ScriptContainer targetScriptEffect)
{
_targetScriptEffect = targetScriptEffect;
}
/// <inheritdoc />
public override void OnRemove()
{
// Remove the effect from the target
_targetScriptEffect.Clear();
}
}
[Script(ScriptCategory.Pokemon, "mean_look_target")]
public class MeanLookEffectTarget : Script
{
/// <inheritdoc />
public override void PreventSelfSwitch(ISwitchChoice choice, ref bool prevent) => prevent = true;
/// <inheritdoc />
public override void PreventSelfRunAway(IFleeChoice choice, ref bool prevent) => prevent = true;
}

View File

@ -0,0 +1,29 @@
using System.Collections.Generic;
using System.Linq;
using PkmnLib.Static;
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
[Script(ScriptCategory.Pokemon, "miracle_eye")]
public class MiracleEyeEffect : Script
{
/// <inheritdoc />
public override void PreventStatBoostChange(IPokemon target, Statistic stat, sbyte amount, bool selfInflicted,
ref bool prevent)
{
if (stat == Statistic.Evasion && amount > 0)
prevent = true;
}
/// <inheritdoc />
public override void ChangeTypesForIncomingMove(IExecutingMove executingMove, IPokemon target, byte hitIndex,
IList<TypeIdentifier> types)
{
if (executingMove.UseMove.MoveType.Name != "psychic")
return;
var darkType = types.FirstOrDefault(x => x.Name == "dark");
if (darkType == null)
return;
types.Remove(darkType);
}
}

View File

@ -0,0 +1,26 @@
using PkmnLib.Plugin.Gen7.Scripts.Status;
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
[Script(ScriptCategory.Pokemon, "nightmare")]
public class NightmareEffect : Script
{
private readonly IPokemon _owner;
public NightmareEffect(IPokemon owner)
{
_owner = owner;
}
/// <inheritdoc />
public override void OnEndTurn(IBattle battle)
{
if (!_owner.HasStatus(ScriptUtils.ResolveName<Sleep>()))
{
RemoveSelf();
return;
}
var maxHp = _owner.MaxHealth;
_owner.Damage(maxHp / 4, DamageSource.Misc);
}
}

View File

@ -0,0 +1,19 @@
using PkmnLib.Static.Moves;
namespace PkmnLib.Plugin.Gen7.Scripts.Side;
public class MatBlockEffect : Script
{
/// <inheritdoc />
public override void BlockIncomingHit(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool block)
{
if (executingMove.UseMove.Category != MoveCategory.Status)
block = true;
}
/// <inheritdoc />
public override void OnEndTurn(IBattle battle)
{
RemoveSelf();
}
}

View File

@ -0,0 +1,15 @@
using PkmnLib.Static;
namespace PkmnLib.Plugin.Gen7.Scripts.Side;
[Script(ScriptCategory.Side, "mist")]
public class MistEffect : Script
{
/// <inheritdoc />
public override void PreventStatBoostChange(IPokemon target, Statistic stat, sbyte amount, bool selfInflicted,
ref bool prevent)
{
if (amount < 0)
prevent = true;
}
}

View File

@ -0,0 +1,7 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Terrain;
[Script(ScriptCategory.Terrain, "misty_terrain")]
public class MistyTerrain : Script
{
// TODO: Implement Terrain
}

View File

@ -0,0 +1,7 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Weather;
[Script(ScriptCategory.Weather, "rain")]
public class Rain : Script
{
// TODO: Implement Rain
}

View File

@ -0,0 +1,7 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Weather;
[Script(ScriptCategory.Weather, "sandstorm")]
public class Sandstorm : Script
{
// TODO: Implement Sandstorm
}

View File

@ -0,0 +1,7 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Weather;
[Script(ScriptCategory.Weather, "sunny")]
public class Sunny : Script
{
// TODO: Implement Sunny
}