More move scripts

This commit is contained in:
Deukhoofd 2025-05-03 16:51:44 +02:00
parent f8c43b6ba0
commit 1973ff50fa
Signed by: Deukhoofd
GPG Key ID: F63E044490819F6F
52 changed files with 704 additions and 78 deletions

View File

@ -10,7 +10,7 @@
"rollForward": false "rollForward": false
}, },
"jetbrains.resharper.globaltools": { "jetbrains.resharper.globaltools": {
"version": "2024.3.6", "version": "2025.1.1",
"commands": [ "commands": [
"jb" "jb"
], ],

View File

@ -4,6 +4,7 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<WarningsAsErrors>nullable</WarningsAsErrors>
<UseArtifactsOutput>true</UseArtifactsOutput> <UseArtifactsOutput>true</UseArtifactsOutput>
</PropertyGroup> </PropertyGroup>

View File

@ -172,6 +172,7 @@ public class BattleImpl : ScriptSource, IBattle
CanFlee = canFlee; CanFlee = canFlee;
NumberOfSides = numberOfSides; NumberOfSides = numberOfSides;
PositionsPerSide = positionsPerSide; PositionsPerSide = positionsPerSide;
Volatile = new ScriptSet(this);
var sides = new IBattleSide[numberOfSides]; var sides = new IBattleSide[numberOfSides];
for (byte i = 0; i < numberOfSides; i++) for (byte i = 0; i < numberOfSides; i++)
sides[i] = new BattleSideImpl(i, positionsPerSide, this); sides[i] = new BattleSideImpl(i, positionsPerSide, this);
@ -399,7 +400,7 @@ public class BattleImpl : ScriptSource, IBattle
} }
/// <inheritdoc /> /// <inheritdoc />
public IScriptSet Volatile { get; } = new ScriptSet(); public IScriptSet Volatile { get; }
/// <inheritdoc /> /// <inheritdoc />
public StringKey? WeatherName => WeatherScript.Script?.Name; public StringKey? WeatherName => WeatherScript.Script?.Name;

View File

@ -132,6 +132,9 @@ public class BattleChoiceQueue : IDeepCloneable
public ITurnChoice? FirstOrDefault(Func<ITurnChoice, bool> predicate) => public ITurnChoice? FirstOrDefault(Func<ITurnChoice, bool> predicate) =>
_choices.Skip(_currentIndex).WhereNotNull().FirstOrDefault(predicate); _choices.Skip(_currentIndex).WhereNotNull().FirstOrDefault(predicate);
public IEnumerable<ITurnChoice> Where(Func<ITurnChoice, bool> predicate) =>
_choices.Skip(_currentIndex).WhereNotNull().Where(predicate);
/// <summary> /// <summary>
/// Removes a choice from the queue. /// Removes a choice from the queue.
/// </summary> /// </summary>

View File

@ -174,7 +174,7 @@ public class BattleSideImpl : ScriptSource, IBattleSide
_fillablePositions[i] = true; _fillablePositions[i] = true;
} }
Battle = battle; Battle = battle;
VolatileScripts = new ScriptSet(); VolatileScripts = new ScriptSet(this);
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -55,6 +55,7 @@ public class MoveChoice : TurnChoice, IMoveChoice
ChosenMove = usedMove; ChosenMove = usedMove;
TargetSide = targetSide; TargetSide = targetSide;
TargetPosition = targetPosition; TargetPosition = targetPosition;
Volatile = new ScriptSet(this);
var secondaryEffect = usedMove.MoveData.SecondaryEffect; var secondaryEffect = usedMove.MoveData.SecondaryEffect;
if (secondaryEffect != null) if (secondaryEffect != null)
@ -86,7 +87,7 @@ public class MoveChoice : TurnChoice, IMoveChoice
public Dictionary<StringKey, object?>? AdditionalData { get; } public Dictionary<StringKey, object?>? AdditionalData { get; }
/// <inheritdoc /> /// <inheritdoc />
public IScriptSet Volatile { get; } = new ScriptSet(); public IScriptSet Volatile { get; }
/// <inheritdoc /> /// <inheritdoc />
public override int ScriptCount => 2 + User.ScriptCount; public override int ScriptCount => 2 + User.ScriptCount;

View File

@ -37,6 +37,11 @@ public enum MoveLearnMethod
/// The move is learned when the Pokémon changes form. /// The move is learned when the Pokémon changes form.
/// </summary> /// </summary>
FormChange, FormChange,
/// <summary>
/// The move is learned by using a move sketch.
/// </summary>
Sketch,
} }
/// <summary> /// <summary>

View File

@ -487,6 +487,7 @@ public class PokemonImpl : ScriptSource, IPokemon
WeightInKg = form.Weight; WeightInKg = form.Weight;
HeightInMeters = form.Height; HeightInMeters = form.Height;
Happiness = species.BaseHappiness; Happiness = species.BaseHappiness;
Volatile = new ScriptSet(this);
if (!library.StaticLibrary.Natures.TryGet(natureName, out var nature)) if (!library.StaticLibrary.Natures.TryGet(natureName, out var nature))
throw new KeyNotFoundException($"Nature {natureName} not found."); throw new KeyNotFoundException($"Nature {natureName} not found.");
Nature = nature; Nature = nature;
@ -532,6 +533,7 @@ public class PokemonImpl : ScriptSource, IPokemon
AbilityIndex = form.FindAbilityIndex(ability) ?? AbilityIndex = form.FindAbilityIndex(ability) ??
throw new KeyNotFoundException( throw new KeyNotFoundException(
$"Ability {ability.Name} not found on species {species.Name} form {form.Name}."); $"Ability {ability.Name} not found on species {species.Name} form {form.Name}.");
Volatile = new ScriptSet(this);
_learnedMoves = serializedPokemon.Moves.Select(move => _learnedMoves = serializedPokemon.Moves.Select(move =>
{ {
if (move == null) if (move == null)
@ -736,7 +738,7 @@ public class PokemonImpl : ScriptSource, IPokemon
public ScriptContainer StatusScript { get; } = new(); public ScriptContainer StatusScript { get; } = new();
/// <inheritdoc /> /// <inheritdoc />
public IScriptSet Volatile { get; } = new ScriptSet(); public IScriptSet Volatile { get; }
/// <inheritdoc /> /// <inheritdoc />
public bool HasHeldItem(StringKey itemName) => HeldItem?.Name == itemName; public bool HasHeldItem(StringKey itemName) => HeldItem?.Name == itemName;
@ -1097,6 +1099,11 @@ public class PokemonImpl : ScriptSource, IPokemon
{ {
if (!Library.ScriptResolver.TryResolve(ScriptCategory.Status, status, null, out var statusScript)) if (!Library.ScriptResolver.TryResolve(ScriptCategory.Status, status, null, out var statusScript))
throw new KeyNotFoundException($"Status script {status} not found"); throw new KeyNotFoundException($"Status script {status} not found");
var preventStatus = false;
this.RunScriptHook(script => script.PreventStatusChange(this, status, ref preventStatus));
if (preventStatus)
return false;
StatusScript.Set(statusScript); StatusScript.Set(statusScript);
return true; return true;
} }

View File

@ -4,7 +4,6 @@
<TargetFramework>netstandard2.1</TargetFramework> <TargetFramework>netstandard2.1</TargetFramework>
<LangVersion>12</LangVersion> <LangVersion>12</LangVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<WarningsAsErrors>nullable</WarningsAsErrors>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup> </PropertyGroup>

View File

@ -1,3 +1,4 @@
using System.Diagnostics;
using PkmnLib.Dynamic.Models; using PkmnLib.Dynamic.Models;
using PkmnLib.Dynamic.Models.Choices; using PkmnLib.Dynamic.Models.Choices;
using PkmnLib.Dynamic.ScriptHandling.Registry; using PkmnLib.Dynamic.ScriptHandling.Registry;
@ -12,6 +13,7 @@ namespace PkmnLib.Dynamic.ScriptHandling;
/// changes. This allows for easily defining generational differences, and add effects that the /// changes. This allows for easily defining generational differences, and add effects that the
/// developer might require. /// developer might require.
/// </summary> /// </summary>
[DebuggerDisplay("{Category} - {Name}")]
public abstract class Script : IDeepCloneable public abstract class Script : IDeepCloneable
{ {
internal event Action<Script>? OnRemoveEvent; internal event Action<Script>? OnRemoveEvent;
@ -683,4 +685,12 @@ public abstract class Script : IDeepCloneable
public virtual void OnBeforeHit(IExecutingMove move, IPokemon target, byte hitIndex) public virtual void OnBeforeHit(IExecutingMove move, IPokemon target, byte hitIndex)
{ {
} }
public virtual void PreventStatusChange(IPokemon pokemonImpl, StringKey status, ref bool preventStatus)
{
}
public virtual void PreventVolatileAdd(Script script, ref bool preventVolatileAdd)
{
}
} }

View File

@ -1,4 +1,5 @@
using System.Collections; using System.Collections;
using System.Diagnostics.CodeAnalysis;
using PkmnLib.Dynamic.ScriptHandling.Registry; using PkmnLib.Dynamic.ScriptHandling.Registry;
using PkmnLib.Static.Utils; using PkmnLib.Static.Utils;
@ -14,8 +15,11 @@ public interface IScriptSet : IEnumerable<ScriptContainer>
/// <summary> /// <summary>
/// Adds a script to the set. If the script with that name already exists in this set, this /// Adds a script to the set. If the script with that name already exists in this set, this
/// makes that script stack instead. The return value here is that script. /// makes that script stack instead. The return value here is that script.
/// If the script was blocked from being added, this will return null.
/// </summary> /// </summary>
ScriptContainer Add(Script script); /// <param name="script">The script to add.</param>
/// <param name="forceAdd">If true, the script cannot be blocked, and will always be added</param>
ScriptContainer? Add(Script script, bool forceAdd = false);
/// <summary> /// <summary>
/// Adds a script with a name to the set. If the script with that name already exists in this /// Adds a script with a name to the set. If the script with that name already exists in this
@ -77,8 +81,15 @@ public interface IScriptSet : IEnumerable<ScriptContainer>
/// <inheritdoc cref="IScriptSet"/> /// <inheritdoc cref="IScriptSet"/>
public class ScriptSet : IScriptSet public class ScriptSet : IScriptSet
{ {
private IScriptSource _source;
private readonly List<ScriptContainer> _scripts = []; private readonly List<ScriptContainer> _scripts = [];
public ScriptSet(IScriptSource source)
{
_source = source;
}
/// <inheritdoc /> /// <inheritdoc />
public IEnumerator<ScriptContainer> GetEnumerator() => _scripts.GetEnumerator(); public IEnumerator<ScriptContainer> GetEnumerator() => _scripts.GetEnumerator();
@ -86,8 +97,16 @@ public class ScriptSet : IScriptSet
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <inheritdoc /> /// <inheritdoc />
public ScriptContainer Add(Script script) public ScriptContainer? Add(Script script, bool forceAdd = false)
{ {
if (!forceAdd)
{
var preventVolatileAdd = false;
_source.RunScriptHook(x => x.PreventVolatileAdd(script, ref preventVolatileAdd));
if (preventVolatileAdd)
return null;
}
var existing = _scripts.FirstOrDefault(s => s.Script?.Name == script.Name); var existing = _scripts.FirstOrDefault(s => s.Script?.Name == script.Name);
if (existing != null) if (existing != null)
{ {

57
PkmnLib.NET.sln Normal file
View File

@ -0,0 +1,57 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PkmnLib.Static", "PkmnLib.Static\PkmnLib.Static.csproj", "{312782DA-1066-4490-BD0E-DF4DF8713B4A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PkmnLib.Tests", "PkmnLib.Tests\PkmnLib.Tests.csproj", "{42DE3095-0468-4827-AF5C-691C94BA7F92}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PkmnLib.Dynamic", "PkmnLib.Dynamic\PkmnLib.Dynamic.csproj", "{D0CBA9A9-7288-41B4-B76B-CB4F20036AB2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PkmnLib.Plugin.Gen7", "Plugins\PkmnLib.Plugin.Gen7\PkmnLib.Plugin.Gen7.csproj", "{FA5380F0-28CC-4AEC-8963-814B347A89BA}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugins", "Plugins", "{63C1B450-DC26-444A-AEBD-15979F3EEE53}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PkmnLib.Plugin.Gen7.Tests", "Plugins\PkmnLib.Plugin.Gen7.Tests\PkmnLib.Plugin.Gen7.Tests.csproj", "{FBB53861-081F-4DAC-B006-79EE238D0DFC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PkmnLib.Dataloader", "PkmnLib.Dataloader\PkmnLib.Dataloader.csproj", "{E6A733FD-C2A3-4206-B9A9-40DBFBDBE1AC}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionFiles", "SolutionFiles", "{2B99ADF8-10E2-4A3D-906F-27DC8E312A79}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{312782DA-1066-4490-BD0E-DF4DF8713B4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{312782DA-1066-4490-BD0E-DF4DF8713B4A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{312782DA-1066-4490-BD0E-DF4DF8713B4A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{312782DA-1066-4490-BD0E-DF4DF8713B4A}.Release|Any CPU.Build.0 = Release|Any CPU
{42DE3095-0468-4827-AF5C-691C94BA7F92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{42DE3095-0468-4827-AF5C-691C94BA7F92}.Debug|Any CPU.Build.0 = Debug|Any CPU
{42DE3095-0468-4827-AF5C-691C94BA7F92}.Release|Any CPU.ActiveCfg = Release|Any CPU
{42DE3095-0468-4827-AF5C-691C94BA7F92}.Release|Any CPU.Build.0 = Release|Any CPU
{D0CBA9A9-7288-41B4-B76B-CB4F20036AB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D0CBA9A9-7288-41B4-B76B-CB4F20036AB2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D0CBA9A9-7288-41B4-B76B-CB4F20036AB2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D0CBA9A9-7288-41B4-B76B-CB4F20036AB2}.Release|Any CPU.Build.0 = Release|Any CPU
{FA5380F0-28CC-4AEC-8963-814B347A89BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FA5380F0-28CC-4AEC-8963-814B347A89BA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FA5380F0-28CC-4AEC-8963-814B347A89BA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FA5380F0-28CC-4AEC-8963-814B347A89BA}.Release|Any CPU.Build.0 = Release|Any CPU
{FBB53861-081F-4DAC-B006-79EE238D0DFC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FBB53861-081F-4DAC-B006-79EE238D0DFC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FBB53861-081F-4DAC-B006-79EE238D0DFC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FBB53861-081F-4DAC-B006-79EE238D0DFC}.Release|Any CPU.Build.0 = Release|Any CPU
{E6A733FD-C2A3-4206-B9A9-40DBFBDBE1AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E6A733FD-C2A3-4206-B9A9-40DBFBDBE1AC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E6A733FD-C2A3-4206-B9A9-40DBFBDBE1AC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E6A733FD-C2A3-4206-B9A9-40DBFBDBE1AC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{FA5380F0-28CC-4AEC-8963-814B347A89BA} = {63C1B450-DC26-444A-AEBD-15979F3EEE53}
{FBB53861-081F-4DAC-B006-79EE238D0DFC} = {63C1B450-DC26-444A-AEBD-15979F3EEE53}
EndGlobalSection
EndGlobal

View File

@ -4,7 +4,6 @@
<TargetFramework>netstandard2.1</TargetFramework> <TargetFramework>netstandard2.1</TargetFramework>
<LangVersion>12</LangVersion> <LangVersion>12</LangVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<WarningsAsErrors>nullable</WarningsAsErrors>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup> </PropertyGroup>

View File

@ -1570,7 +1570,8 @@
"mirror", "mirror",
"sound", "sound",
"distance", "distance",
"ignore-substitute" "ignore-substitute",
"not_sketchable"
], ],
"effect": { "effect": {
"name": "confuse" "name": "confuse"
@ -3739,10 +3740,9 @@
"defrost" "defrost"
], ],
"effect": { "effect": {
"name": "set_status", "name": "flame_wheel",
"chance": 10,
"parameters": { "parameters": {
"status": "burned" "burn_chance": 10.0
} }
} }
}, },
@ -9249,7 +9249,13 @@
"protect", "protect",
"mirror", "mirror",
"defrost" "defrost"
] ],
"effect": {
"name": "flame_wheel",
"parameters": {
"burn_chance": 50.0
}
}
}, },
{ {
"name": "sacred_sword", "name": "sacred_sword",
@ -9264,7 +9270,10 @@
"contact", "contact",
"protect", "protect",
"mirror" "mirror"
] ],
"effect": {
"name": "chip_away"
}
}, },
{ {
"name": "safeguard", "name": "safeguard",
@ -9277,7 +9286,10 @@
"category": "status", "category": "status",
"flags": [ "flags": [
"snatch" "snatch"
] ],
"effect": {
"name": "safeguard"
}
}, },
{ {
"name": "sand_attack", "name": "sand_attack",
@ -9292,7 +9304,13 @@
"protect", "protect",
"reflectable", "reflectable",
"mirror" "mirror"
] ],
"effect": {
"name": "change_target_accuracy",
"parameters": {
"amount": -1
}
}
}, },
{ {
"name": "sand_tomb", "name": "sand_tomb",
@ -9306,7 +9324,10 @@
"flags": [ "flags": [
"protect", "protect",
"mirror" "mirror"
] ],
"effect": {
"name": "bind"
}
}, },
{ {
"name": "sandstorm", "name": "sandstorm",
@ -9317,7 +9338,13 @@
"priority": 0, "priority": 0,
"target": "All", "target": "All",
"category": "status", "category": "status",
"flags": [] "flags": [],
"effect": {
"name": "set_weather",
"parameters": {
"weather": "sandstorm"
}
}
}, },
{ {
"name": "savage_spin_out__physical", "name": "savage_spin_out__physical",
@ -9329,6 +9356,7 @@
"target": "Any", "target": "Any",
"category": "physical", "category": "physical",
"flags": [] "flags": []
// No secondary effect
}, },
{ {
"name": "savage_spin_out__special", "name": "savage_spin_out__special",
@ -9340,6 +9368,7 @@
"target": "Any", "target": "Any",
"category": "special", "category": "special",
"flags": [] "flags": []
// No secondary effect
}, },
{ {
"name": "scald", "name": "scald",
@ -9354,7 +9383,14 @@
"protect", "protect",
"mirror", "mirror",
"defrost" "defrost"
] ],
"effect": {
"name": "set_status",
"chance": 30,
"parameters": {
"status": "burned"
}
}
}, },
{ {
"name": "scary_face", "name": "scary_face",
@ -9369,7 +9405,13 @@
"protect", "protect",
"reflectable", "reflectable",
"mirror" "mirror"
] ],
"effect": {
"name": "change_target_speed",
"parameters": {
"amount": -2
}
}
}, },
{ {
"name": "scratch", "name": "scratch",
@ -9385,6 +9427,7 @@
"protect", "protect",
"mirror" "mirror"
] ]
// No secondary effect
}, },
{ {
"name": "screech", "name": "screech",
@ -9401,7 +9444,13 @@
"mirror", "mirror",
"sound", "sound",
"ignore-substitute" "ignore-substitute"
] ],
"effect": {
"name": "change_target_defense",
"parameters": {
"amount": -1
}
}
}, },
{ {
"name": "searing_shot", "name": "searing_shot",
@ -9415,7 +9464,14 @@
"flags": [ "flags": [
"protect", "protect",
"mirror" "mirror"
] ],
"effect": {
"name": "set_status",
"chance": 30,
"parameters": {
"status": "burned"
}
}
}, },
{ {
"name": "secret_power", "name": "secret_power",
@ -9429,7 +9485,10 @@
"flags": [ "flags": [
"protect", "protect",
"mirror" "mirror"
] ],
"effect": {
"name": "secret_power"
}
}, },
{ {
"name": "secret_sword", "name": "secret_sword",
@ -9443,7 +9502,10 @@
"flags": [ "flags": [
"protect", "protect",
"mirror" "mirror"
] ],
"effect": {
"name": "psyshock"
}
}, },
{ {
"name": "seed_bomb", "name": "seed_bomb",
@ -9459,6 +9521,7 @@
"mirror", "mirror",
"ballistics" "ballistics"
] ]
// No secondary effect
}, },
{ {
"name": "seed_flare", "name": "seed_flare",
@ -9472,7 +9535,14 @@
"flags": [ "flags": [
"protect", "protect",
"mirror" "mirror"
] ],
"effect": {
"name": "change_target_special_defense",
"chance": 40,
"parameters": {
"amount": -2
}
}
}, },
{ {
"name": "seismic_toss", "name": "seismic_toss",
@ -9488,7 +9558,10 @@
"protect", "protect",
"mirror", "mirror",
"nonskybattle" "nonskybattle"
] ],
"effect": {
"name": "night_shade"
}
}, },
{ {
"name": "self_destruct", "name": "self_destruct",
@ -9502,7 +9575,10 @@
"flags": [ "flags": [
"protect", "protect",
"mirror" "mirror"
] ],
"effect": {
"name": "self_destruct"
}
}, },
{ {
"name": "shadow_ball", "name": "shadow_ball",
@ -9517,7 +9593,14 @@
"protect", "protect",
"mirror", "mirror",
"ballistics" "ballistics"
] ],
"effect": {
"name": "change_target_special_defense",
"chance": 20,
"parameters": {
"amount": -1
}
}
}, },
{ {
"name": "shadow_bone", "name": "shadow_bone",
@ -9531,7 +9614,14 @@
"flags": [ "flags": [
"protect", "protect",
"mirror" "mirror"
] ],
"effect": {
"name": "change_target_defense",
"chance": 20,
"parameters": {
"amount": -1
}
}
}, },
{ {
"name": "shadow_claw", "name": "shadow_claw",
@ -9546,7 +9636,10 @@
"contact", "contact",
"protect", "protect",
"mirror" "mirror"
] ],
"effect": {
"name": "increased_critical_stage"
}
}, },
{ {
"name": "shadow_force", "name": "shadow_force",
@ -9561,14 +9654,17 @@
"contact", "contact",
"charge", "charge",
"mirror" "mirror"
] ],
"effect": {
"name": "shadow_force"
}
}, },
{ {
"name": "shadow_punch", "name": "shadow_punch",
"type": "ghost", "type": "ghost",
"power": 60, "power": 60,
"pp": 20, "pp": 20,
"accuracy": 0, "accuracy": 255,
"priority": 0, "priority": 0,
"target": "Any", "target": "Any",
"category": "physical", "category": "physical",
@ -9578,6 +9674,7 @@
"mirror", "mirror",
"punch" "punch"
] ]
// No secondary effect
}, },
{ {
"name": "shadow_sneak", "name": "shadow_sneak",
@ -9593,6 +9690,7 @@
"protect", "protect",
"mirror" "mirror"
] ]
// No secondary effect
}, },
{ {
"name": "sharpen", "name": "sharpen",
@ -9605,7 +9703,13 @@
"category": "status", "category": "status",
"flags": [ "flags": [
"snatch" "snatch"
] ],
"effect": {
"name": "change_user_attack",
"parameters": {
"amount": 1
}
}
}, },
{ {
"name": "shattered_psyche__physical", "name": "shattered_psyche__physical",
@ -9617,6 +9721,7 @@
"target": "Any", "target": "Any",
"category": "physical", "category": "physical",
"flags": [] "flags": []
// No secondary effect
}, },
{ {
"name": "shattered_psyche__special", "name": "shattered_psyche__special",
@ -9628,6 +9733,7 @@
"target": "Any", "target": "Any",
"category": "special", "category": "special",
"flags": [] "flags": []
// No secondary effect
}, },
{ {
"name": "sheer_cold", "name": "sheer_cold",
@ -9641,7 +9747,10 @@
"flags": [ "flags": [
"protect", "protect",
"mirror" "mirror"
] ],
"effect": {
"name": "one_hit_ko"
}
}, },
{ {
"name": "shell_smash", "name": "shell_smash",
@ -9654,7 +9763,10 @@
"category": "status", "category": "status",
"flags": [ "flags": [
"snatch" "snatch"
] ],
"effect": {
"name": "shell_smash"
}
}, },
{ {
"name": "shell_trap", "name": "shell_trap",
@ -9667,7 +9779,10 @@
"category": "special", "category": "special",
"flags": [ "flags": [
"protect" "protect"
] ],
"effect": {
"name": "shell_trap"
}
}, },
{ {
"name": "shift_gear", "name": "shift_gear",
@ -9680,14 +9795,21 @@
"category": "status", "category": "status",
"flags": [ "flags": [
"snatch" "snatch"
] ],
"effect": {
"name": "change_multiple_user_stat_boosts",
"parameters": {
"attack": 1,
"speed": 2
}
}
}, },
{ {
"name": "shock_wave", "name": "shock_wave",
"type": "electric", "type": "electric",
"power": 60, "power": 60,
"pp": 20, "pp": 20,
"accuracy": 0, "accuracy": 255,
"priority": 0, "priority": 0,
"target": "Any", "target": "Any",
"category": "special", "category": "special",
@ -9695,6 +9817,7 @@
"protect", "protect",
"mirror" "mirror"
] ]
// No secondary effect
}, },
{ {
"name": "shore_up", "name": "shore_up",
@ -9708,7 +9831,10 @@
"flags": [ "flags": [
"snatch", "snatch",
"heal" "heal"
] ],
"effect": {
"name": "shore_up"
}
}, },
{ {
"name": "signal_beam", "name": "signal_beam",
@ -9722,7 +9848,11 @@
"flags": [ "flags": [
"protect", "protect",
"mirror" "mirror"
] ],
"effect": {
"name": "confuse",
"chance": 10
}
}, },
{ {
"name": "silver_wind", "name": "silver_wind",
@ -9736,7 +9866,18 @@
"flags": [ "flags": [
"protect", "protect",
"mirror" "mirror"
] ],
"effect": {
"name": "change_multiple_user_stat_boosts",
"chance": 10,
"parameters": {
"attack": 1,
"defense": 1,
"specialAttack": 1,
"specialDefense": 1,
"speed": 1
}
}
}, },
{ {
"name": "simple_beam", "name": "simple_beam",
@ -9751,7 +9892,10 @@
"protect", "protect",
"reflectable", "reflectable",
"mirror" "mirror"
] ],
"effect": {
"name": "simple_beam"
}
}, },
{ {
"name": "sing", "name": "sing",
@ -9768,7 +9912,13 @@
"mirror", "mirror",
"sound", "sound",
"ignore-substitute" "ignore-substitute"
] ],
"effect": {
"name": "set_status",
"parameters": {
"status": "sleep"
}
}
}, },
{ {
"name": "sinister_arrow_raid", "name": "sinister_arrow_raid",
@ -9780,6 +9930,7 @@
"target": "Any", "target": "Any",
"category": "physical", "category": "physical",
"flags": [] "flags": []
// No secondary effect
}, },
{ {
"name": "sketch", "name": "sketch",
@ -9792,7 +9943,10 @@
"category": "status", "category": "status",
"flags": [ "flags": [
"ignore-substitute" "ignore-substitute"
] ],
"effect": {
"name": "sketch"
}
}, },
{ {
"name": "skill_swap", "name": "skill_swap",
@ -9807,7 +9961,10 @@
"protect", "protect",
"mirror", "mirror",
"ignore-substitute" "ignore-substitute"
] ],
"effect": {
"name": "skill_swap"
}
}, },
{ {
"name": "skull_bash", "name": "skull_bash",

View File

@ -1,7 +1,9 @@
using System.Text.Json.Nodes;
using PkmnLib.Dynamic.Libraries; using PkmnLib.Dynamic.Libraries;
using PkmnLib.Dynamic.ScriptHandling; using PkmnLib.Dynamic.ScriptHandling;
using PkmnLib.Static.Moves; using PkmnLib.Static.Moves;
using PkmnLib.Tests.Integration; using PkmnLib.Tests.Integration;
using TUnit.Core.Logging;
namespace PkmnLib.Tests.DataTests; namespace PkmnLib.Tests.DataTests;
@ -39,6 +41,17 @@ public class MoveDataTests
} }
catch (Exception e) catch (Exception e)
{ {
// Helper method to find the line number of the effect in the JSON file
var file = Path.GetFullPath("../../../../PkmnLib.Tests/Data/Moves.jsonc");
var json = await File.ReadAllLinesAsync(file);
var moveLineNumber = json.Select((line, index) => new { line, index })
.FirstOrDefault(x => x.line.Contains($"\"name\": \"{test.Move.Name}\""))?.index + 1;
var effectLineNumber = moveLineNumber + json.Skip(moveLineNumber ?? 0)
.Select((line, index) => new { line, index }).FirstOrDefault(x => x.line.Contains("effect"))
?.index +
1 ?? 0;
await TestContext.Current!.OutputWriter.WriteLineAsync("File: " + $"file://{file}:{effectLineNumber}");
throw new AggregateException($"Failed to resolve script for move {test.Move.Name} with effect {scriptName}", throw new AggregateException($"Failed to resolve script for move {test.Move.Name} with effect {scriptName}",
e); e);
} }

View File

@ -15,7 +15,7 @@ public class SerializationTests
var library = LibraryHelpers.LoadLibrary(); var library = LibraryHelpers.LoadLibrary();
await Assert.That(library.StaticLibrary.Species.TryGet("bulbasaur", out var species)).IsTrue(); await Assert.That(library.StaticLibrary.Species.TryGet("bulbasaur", out var species)).IsTrue();
var pokemon = new PokemonImpl(library, species!, species!.GetDefaultForm(), new AbilityIndex() var pokemon = new PokemonImpl(library, species!, species!.GetDefaultForm(), new AbilityIndex
{ {
Index = 0, Index = 0,
IsHidden = false, IsHidden = false,

View File

@ -54,8 +54,8 @@ public class Gen7BattleStatCalculator : IBattleStatCalculator
byte moveAccuracy) byte moveAccuracy)
{ {
var accuracyModifier = 1.0f; var accuracyModifier = 1.0f;
executingMove.RunScriptHook( executingMove.RunScriptHook(x =>
x => x.ChangeAccuracyModifier(executingMove, target, hitIndex, ref accuracyModifier)); x.ChangeAccuracyModifier(executingMove, target, hitIndex, ref accuracyModifier));
var modifiedAccuracy = (int)(moveAccuracy * accuracyModifier); var modifiedAccuracy = (int)(moveAccuracy * accuracyModifier);
// ReSharper disable once AccessToModifiedClosure // ReSharper disable once AccessToModifiedClosure
executingMove.RunScriptHook(x => x.ChangeAccuracy(executingMove, target, hitIndex, ref modifiedAccuracy)); executingMove.RunScriptHook(x => x.ChangeAccuracy(executingMove, target, hitIndex, ref modifiedAccuracy));

View File

@ -12,7 +12,7 @@ public class Gen7MiscLibrary : IMiscLibrary
{ {
private readonly IMoveData _struggleData = new MoveDataImpl("struggle", new TypeIdentifier(0, "none"), private readonly IMoveData _struggleData = new MoveDataImpl("struggle", new TypeIdentifier(0, "none"),
MoveCategory.Physical, 50, 255, 255, MoveTarget.Any, 0, MoveCategory.Physical, 50, 255, 255, MoveTarget.Any, 0,
new SecondaryEffectImpl(-1, "struggle", new Dictionary<StringKey, object?>()), []); new SecondaryEffectImpl(-1, "struggle", new Dictionary<StringKey, object?>()), ["not_sketchable"]);
/// <inheritdoc /> /// <inheritdoc />
public ITurnChoice ReplacementChoice(IPokemon user, byte targetSide, byte targetPosition) => public ITurnChoice ReplacementChoice(IPokemon user, byte targetSide, byte targetPosition) =>

View File

@ -4,12 +4,11 @@
<TargetFramework>netstandard2.1</TargetFramework> <TargetFramework>netstandard2.1</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<LangVersion>12</LangVersion> <LangVersion>12</LangVersion>
<WarningsAsErrors>nullable</WarningsAsErrors>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\PkmnLib.Dynamic\PkmnLib.Dynamic.csproj" /> <ProjectReference Include="..\..\PkmnLib.Dynamic\PkmnLib.Dynamic.csproj"/>
<ProjectReference Include="..\..\PkmnLib.Static\PkmnLib.Static.csproj" /> <ProjectReference Include="..\..\PkmnLib.Static\PkmnLib.Static.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

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

View File

@ -13,7 +13,7 @@ public abstract class BaseChargeMove<TVolatile> : Script where TVolatile : Requi
return; return;
move.User.Volatile.Add(CreateVolatile(move.User)); move.User.Volatile.Add(CreateVolatile(move.User));
move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("began_charging", new Dictionary<string, object>() move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("began_charging", new Dictionary<string, object>
{ {
{ "user", move.User }, { "user", move.User },
})); }));

View File

@ -13,7 +13,7 @@ public class BeakBlast : Script
if (battleData == null) if (battleData == null)
return; return;
choice.User.Volatile.Add(new BeakBlastEffect()); choice.User.Volatile.Add(new BeakBlastEffect());
battleData.Battle.EventHook.Invoke(new DialogEvent("beak_blast_charge", new Dictionary<string, object>() battleData.Battle.EventHook.Invoke(new DialogEvent("beak_blast_charge", new Dictionary<string, object>
{ {
{ "user", choice.User }, { "user", choice.User },
})); }));

View File

@ -13,7 +13,7 @@ public class Bounce : Script
return; return;
move.User.Volatile.Add(new ChargeBounceEffect(move.User)); move.User.Volatile.Add(new ChargeBounceEffect(move.User));
move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("bounce_charge", new Dictionary<string, object>() move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("bounce_charge", new Dictionary<string, object>
{ {
{ "user", move.User }, { "user", move.User },
})); }));

View File

@ -3,5 +3,5 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "camouflage")] [Script(ScriptCategory.Move, "camouflage")]
public class Camouflage : Script public class Camouflage : Script
{ {
// FIXME: Implement this. How to get the terrain in a sane manner? // FIXME: Implement this. How to get the terrain in a sane manner? See also SecretPower.cs
} }

View File

@ -1,4 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using JetBrains.Annotations;
using PkmnLib.Plugin.Gen7.Scripts.Side;
using PkmnLib.Static.Utils; using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves; namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
@ -6,16 +8,16 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "defog")] [Script(ScriptCategory.Move, "defog")]
public class Defog : Script public class Defog : Script
{ {
public static HashSet<StringKey> DefoggedEffects = new() [PublicAPI] public static HashSet<StringKey> DefoggedEffects =
{ [
"mist", ScriptUtils.ResolveName<MistEffect>(),
"light_screen", ScriptUtils.ResolveName<LightScreenEffect>(),
"reflect", ScriptUtils.ResolveName<ReflectEffect>(),
"safe_guard", ScriptUtils.ResolveName<SafeguardEffect>(),
"spikes", "spikes",
"toxic_spikes", "toxic_spikes",
"stealth_rock", "stealth_rock",
}; ];
/// <inheritdoc /> /// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)

View File

@ -13,7 +13,7 @@ public class Dig : Script
return; return;
move.User.Volatile.Add(new DigEffect(move.User)); move.User.Volatile.Add(new DigEffect(move.User));
move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("dig_charge", new Dictionary<string, object>() move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("dig_charge", new Dictionary<string, object>
{ {
{ "user", move.User }, { "user", move.User },
})); }));

View File

@ -13,7 +13,7 @@ public class Dive : Script
return; return;
move.User.Volatile.Add(new DigEffect(move.User)); move.User.Volatile.Add(new DigEffect(move.User));
move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("dive_charge", new Dictionary<string, object>() move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("dive_charge", new Dictionary<string, object>
{ {
{ "user", move.User }, { "user", move.User },
})); }));

View File

@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "flame_wheel")]
public class FlameWheel : Script
{
private float _burnChance;
/// <inheritdoc />
public override void OnInitialize(IReadOnlyDictionary<StringKey, object?>? parameters)
{
if (parameters is null || !parameters.TryGetValue("burn_chance", out var burnChance) ||
burnChance is not float chance)
{
throw new Exception("Flame Wheel: Missing or invalid parameters.");
}
_burnChance = chance;
}
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
if (move.User.HasStatus("frozen"))
{
move.User.ClearStatus();
}
var burnChance = _burnChance;
if (move.Battle.Random.EffectChance(_burnChance, move, target, hit))
{
target.SetStatus("burned");
}
}
}

View File

@ -13,7 +13,7 @@ public class Fly : Script
return; return;
move.User.Volatile.Add(new ChargeFlyEffect(move.User)); move.User.Volatile.Add(new ChargeFlyEffect(move.User));
move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("fly_charge", new Dictionary<string, object>() move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("fly_charge", new Dictionary<string, object>
{ {
{ "user", move.User }, { "user", move.User },
})); }));

View File

@ -11,7 +11,7 @@ public class FocusPunch : Script
{ {
choice.User.Volatile.Add(new FocusPunchEffect()); choice.User.Volatile.Add(new FocusPunchEffect());
choice.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("focus_punch_charge", choice.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("focus_punch_charge",
new Dictionary<string, object>() new Dictionary<string, object>
{ {
{ "pokemon", choice.User }, { "pokemon", choice.User },
})); }));

View File

@ -15,7 +15,7 @@ public class LightScreen : Script
return; return;
var turns = 5; var turns = 5;
var dict = new Dictionary<StringKey, object?>() var dict = new Dictionary<StringKey, object?>
{ {
{ "duration", turns }, { "duration", turns },
}; };

View File

@ -32,7 +32,7 @@ public class Magnitude : Script
// 5% chance for 10 // 5% chance for 10
_ => 10, _ => 10,
}; };
battleData.Battle.EventHook.Invoke(new DialogEvent("magnitude", new Dictionary<string, object>() battleData.Battle.EventHook.Invoke(new DialogEvent("magnitude", new Dictionary<string, object>
{ {
{ "magnitude", magnitude }, { "magnitude", magnitude },
{ "user", move.User }, { "user", move.User },

View File

@ -9,6 +9,11 @@ public class MeanLook : Script
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{ {
var targetEffect = target.Volatile.Add(new MeanLookEffectTarget()); var targetEffect = target.Volatile.Add(new MeanLookEffectTarget());
if (targetEffect == null)
{
move.GetHitData(target, hit).Fail();
return;
}
var userEffect = new MeanLookEffectUser(targetEffect); var userEffect = new MeanLookEffectUser(targetEffect);
move.User.Volatile.Add(userEffect); move.User.Volatile.Add(userEffect);
} }

View File

@ -13,6 +13,12 @@ public class RagePowder : Script
return; return;
var effect = battleData.BattleSide.VolatileScripts.Add(new RagePowderEffect(move.User)); var effect = battleData.BattleSide.VolatileScripts.Add(new RagePowderEffect(move.User));
if (effect == null)
{
move.GetHitData(target, hit).Fail();
return;
}
((RagePowderEffect)effect.Script!).User = move.User; ((RagePowderEffect)effect.Script!).User = move.User;
} }
} }

View File

@ -0,0 +1,29 @@
using System.Linq;
using PkmnLib.Plugin.Gen7.Scripts.MoveVolatile;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "round")]
public class Round : Script
{
/// <inheritdoc />
public override void OnAfterMove(IExecutingMove move)
{
var choiceQueue = move.Battle.ChoiceQueue;
if (choiceQueue is null)
{
return;
}
var otherRoundMoves = choiceQueue.Where(x => x is IMoveChoice mc && mc.ChosenMove.MoveData.Name == "round")
.Cast<IMoveChoice>().ToList();
// We reverse the order here, as we're constantly pushing the choices to the front of the queue.
// By reversing the order, we ensure that the first choice is the one that is up next.
foreach (var otherRoundMove in otherRoundMoves.AsEnumerable().Reverse())
{
otherRoundMove.Volatile.Add(new RoundPowerBoost());
choiceQueue.MovePokemonChoiceNext(otherRoundMove.User);
}
}
}

View File

@ -0,0 +1,11 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "safeguard")]
public class Safeguard : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
target.BattleData?.BattleSide.VolatileScripts.Add(new Side.SafeguardEffect());
}
}

View File

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

View File

@ -0,0 +1,14 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "self_destruct")]
public class SelfDestruct : Script
{
/// <inheritdoc />
public override void OnAfterMove(IExecutingMove move)
{
if (move.User.IsFainted)
return;
move.User.Faint(DamageSource.Misc);
}
}

View File

@ -0,0 +1,32 @@
using System.Collections.Generic;
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "shadow_force")]
public class ShadowForce : Script
{
/// <inheritdoc />
public override void PreventMove(IExecutingMove move, ref bool prevent)
{
if (move.User.Volatile.Contains<ShadowForceCharge>())
return;
move.User.Volatile.Add(new ShadowForceCharge(move.User));
move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("shadow_force_charge",
new Dictionary<string, object>
{
{ "user", move.User },
}));
prevent = true;
}
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
if (move.User.Volatile.Contains<ShadowForceCharge>())
return;
move.User.Volatile.Add(new ShadowForceCharge(move.User));
}
}

View File

@ -0,0 +1,19 @@
using PkmnLib.Static;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "shell_smash")]
public class ShellSmash : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
EventBatchId eventBatchId = new();
move.User.ChangeStatBoost(Statistic.Attack, 2, true, eventBatchId);
move.User.ChangeStatBoost(Statistic.SpecialAttack, 2, true, eventBatchId);
move.User.ChangeStatBoost(Statistic.Speed, 2, true, eventBatchId);
eventBatchId = new EventBatchId();
move.User.ChangeStatBoost(Statistic.Defense, -1, true, eventBatchId);
move.User.ChangeStatBoost(Statistic.SpecialDefense, -1, true, eventBatchId);
}
}

View File

@ -0,0 +1,23 @@
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "shell_trap")]
public class ShellTrap : Script
{
/// <inheritdoc />
public override void OnBeforeMove(IExecutingMove move)
{
move.User.Volatile.Add(new ShellTrapHelper());
}
/// <inheritdoc />
public override void FailMove(IExecutingMove move, ref bool fail)
{
var shellTrapHelper = move.User.Volatile.Get<ShellTrapHelper>();
if (shellTrapHelper is not { HasHit: true })
{
fail = true;
}
}
}

View File

@ -0,0 +1,19 @@
using PkmnLib.Plugin.Gen7.Scripts.Weather;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "shore_up")]
public class ShoreUp : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var healMod = 0.5f;
if (move.Battle.WeatherName == ScriptUtils.ResolveName<Sandstorm>())
{
healMod = 2f / 3f;
}
var heal = (uint)(target.MaxHealth * healMod);
target.Heal(heal);
}
}

View File

@ -0,0 +1,16 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "simple_beam")]
public class SimpleBeam : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
if (!move.Battle.Library.StaticLibrary.Abilities.TryGet("simple", out var simpleAbility))
{
move.GetHitData(target, hit).Fail();
return;
}
target.ChangeAbility(simpleAbility);
}
}

View File

@ -0,0 +1,37 @@
using System.Linq;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "sketch")]
public class Sketch : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
// Defensive programming; If the move we're using is not the same as the one picked, something changed
// our move to sketch. This should never happen, as moves are not allowed to change to sketch, but in the
// case it does, we should fail the move.
if (move.ChosenMove.MoveData != move.UseMove)
{
move.GetHitData(target, hit).Fail();
return;
}
var moveSlot = move.User.Moves.IndexOf(move.ChosenMove);
if (moveSlot == -1)
{
move.GetHitData(target, hit).Fail();
return;
}
var choiceQueue = move.Battle.PreviousTurnChoices;
var lastMove = choiceQueue.SelectMany(x => x).OfType<IMoveChoice>().LastOrDefault(x => x.User == target);
if (lastMove == null || lastMove.ChosenMove.MoveData.HasFlag("not_sketchable"))
{
move.GetHitData(target, hit).Fail();
return;
}
move.User.LearnMove(lastMove.ChosenMove.MoveData.Name, MoveLearnMethod.Sketch, (byte)moveSlot);
}
}

View File

@ -0,0 +1,25 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "skill_swap")]
public class SkillSwap : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var targetAbility = target.ActiveAbility;
var userAbility = move.User.ActiveAbility;
if (targetAbility == null || userAbility == null)
{
move.GetHitData(target, hit).Fail();
return;
}
if (targetAbility.HasFlag("cant_be_changed") || userAbility.HasFlag("cant_be_changed"))
{
move.GetHitData(target, hit).Fail();
return;
}
move.User.ChangeAbility(targetAbility);
target.ChangeAbility(userAbility);
}
}

View File

@ -12,7 +12,7 @@ public class FocusPunchEffect : Script
{ {
WasHit = true; WasHit = true;
target.BattleData?.Battle.EventHook.Invoke(new DialogEvent("focus_punch_lost_focus", target.BattleData?.Battle.EventHook.Invoke(new DialogEvent("focus_punch_lost_focus",
new Dictionary<string, object>() new Dictionary<string, object>
{ {
{ "pokemon", target }, { "pokemon", target },
})); }));

View File

@ -33,7 +33,7 @@ public abstract class OutrageLikeEffect : Script
if (_turns <= 0) if (_turns <= 0)
{ {
RemoveSelf(); RemoveSelf();
_owner.Volatile.Add(new Confusion()); _owner.Volatile.Add(new Confusion(), true);
} }
} }
} }

View File

@ -23,7 +23,7 @@ public class RequiresRechargeEffect : Script
{ {
RemoveSelf(); RemoveSelf();
_owner.BattleData?.Battle.EventHook.Invoke(new DialogEvent("pokemon_must_recharge", _owner.BattleData?.Battle.EventHook.Invoke(new DialogEvent("pokemon_must_recharge",
new Dictionary<string, object>() new Dictionary<string, object>
{ {
{ "pokemon", _owner }, { "pokemon", _owner },
})); }));

View File

@ -0,0 +1,27 @@
using PkmnLib.Plugin.Gen7.Scripts.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
[Script(ScriptCategory.Pokemon, "shadow_force")]
public class ShadowForceCharge : Script
{
private readonly IPokemon _owner;
public ShadowForceCharge(IPokemon owner)
{
_owner = owner;
}
/// <inheritdoc />
public override void ForceTurnSelection(byte sideIndex, byte position, ref ITurnChoice? choice)
{
var opposingSideIndex = (byte)(_owner.BattleData?.SideIndex == 0 ? 1 : 0);
choice = TurnChoiceHelper.CreateMoveChoice(_owner, "shadow_force", opposingSideIndex, position);
}
/// <inheritdoc />
public override void BlockIncomingHit(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool block)
{
block = true;
}
}

View File

@ -0,0 +1,16 @@
using PkmnLib.Static.Moves;
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
[Script(ScriptCategory.Pokemon, "shell_trap")]
public class ShellTrapHelper : Script
{
public bool HasHit { get; private set; }
/// <inheritdoc />
public override void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit)
{
if (move.UseMove.Category == MoveCategory.Physical)
HasHit = true;
}
}

View File

@ -0,0 +1,20 @@
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Side;
public class SafeguardEffect : Script
{
/// <inheritdoc />
public override void PreventStatusChange(IPokemon pokemonImpl, StringKey status, ref bool preventStatus)
{
preventStatus = true;
}
/// <inheritdoc />
public override void PreventVolatileAdd(Script script, ref bool preventVolatileAdd)
{
if (script.Category == ScriptCategory.Pokemon && script.Name == ScriptUtils.ResolveName<Confusion>())
preventVolatileAdd = true;
}
}

View File

@ -32,11 +32,10 @@ public class Hail : Script, IWeatherScript
if (pokemon.Types.Contains(iceType)) if (pokemon.Types.Contains(iceType))
continue; continue;
var ignoresHail = false; var ignoresHail = false;
pokemon.RunScriptHook(x => x.CustomTrigger(CustomTriggers.IgnoreHail, pokemon.RunScriptHook(x => x.CustomTrigger(CustomTriggers.IgnoreHail, new Dictionary<StringKey, object?>
new Dictionary<StringKey, object?>() {
{ { "ignoresHail", ignoresHail },
{ "ignoresHail", ignoresHail }, }));
}));
if (ignoresHail) if (ignoresHail)
continue; continue;