Implements several more moves

This commit is contained in:
Deukhoofd 2025-01-27 12:18:48 +01:00
parent 549b92048a
commit 3a75493912
Signed by: Deukhoofd
GPG Key ID: F63E044490819F6F
26 changed files with 676 additions and 38 deletions

View File

@ -252,12 +252,18 @@ public interface IPokemon : IScriptSource, IDeepCloneable
/// <param name="stat">The stat to be changed</param> /// <param name="stat">The stat to be changed</param>
/// <param name="change">The amount to change the stat by</param> /// <param name="change">The amount to change the stat by</param>
/// <param name="selfInflicted">Whether the change was self-inflicted. This can be relevant in scripts.</param> /// <param name="selfInflicted">Whether the change was self-inflicted. This can be relevant in scripts.</param>
bool ChangeStatBoost(Statistic stat, sbyte change, bool selfInflicted); /// <param name="batchId">The event batch ID this change is a part of. This is relevant for visual handling</param>
bool ChangeStatBoost(Statistic stat, sbyte change, bool selfInflicted, EventBatchId batchId = default);
/// <summary>
/// Suppresses the ability of the Pokémon.
/// </summary>
public void SuppressAbility();
/// <summary> /// <summary>
/// Returns the currently active ability. /// Returns the currently active ability.
/// </summary> /// </summary>
IAbility ActiveAbility { get; } IAbility? ActiveAbility { get; }
/// <summary> /// <summary>
/// Calculates the flat stats on the Pokemon. This should be called when for example the base /// Calculates the flat stats on the Pokemon. This should be called when for example the base
@ -363,6 +369,11 @@ public interface IPokemon : IScriptSource, IDeepCloneable
/// </summary> /// </summary>
bool AddType(TypeIdentifier type); bool AddType(TypeIdentifier type);
/// <summary>
/// Replace the types of the Pokémon with the provided types.
/// </summary>
void SetTypes(IReadOnlyList<TypeIdentifier> types);
/// <summary> /// <summary>
/// Converts the data structure to a serializable format. /// Converts the data structure to a serializable format.
/// </summary> /// </summary>
@ -714,7 +725,7 @@ public class PokemonImpl : ScriptSource, IPokemon
} }
/// <inheritdoc /> /// <inheritdoc />
public bool ChangeStatBoost(Statistic stat, sbyte change, bool selfInflicted) public bool ChangeStatBoost(Statistic stat, sbyte change, bool selfInflicted, EventBatchId batchId = default)
{ {
var prevented = false; var prevented = false;
this.RunScriptHook(script => script.PreventStatBoostChange(this, stat, change, selfInflicted, ref prevented)); this.RunScriptHook(script => script.PreventStatBoostChange(this, stat, change, selfInflicted, ref prevented));
@ -736,18 +747,34 @@ public class PokemonImpl : ScriptSource, IPokemon
if (BattleData != null) if (BattleData != null)
{ {
var newBoost = StatBoost.GetStatistic(stat); var newBoost = StatBoost.GetStatistic(stat);
BattleData.Battle.EventHook.Invoke(new StatBoostEvent(this, stat, oldBoost, newBoost)); BattleData.Battle.EventHook.Invoke(new StatBoostEvent(this, stat, oldBoost, newBoost)
{
BatchId = batchId,
});
} }
RecalculateBoostedStats(); RecalculateBoostedStats();
return true; return true;
} }
/// <summary>
/// Whether the ability of the Pokémon is suppressed.
/// </summary>
public bool AbilitySuppressed { get; private set; }
/// <inheritdoc /> /// <inheritdoc />
public IAbility ActiveAbility public void SuppressAbility()
{
OverrideAbility = null;
}
/// <inheritdoc />
public IAbility? ActiveAbility
{ {
get get
{ {
if (AbilitySuppressed)
return null;
if (OverrideAbility != null) if (OverrideAbility != null)
return OverrideAbility; return OverrideAbility;
var ability = Form.GetAbility(AbilityIndex); var ability = Form.GetAbility(AbilityIndex);
@ -1004,6 +1031,9 @@ public class PokemonImpl : ScriptSource, IPokemon
Volatile.Clear(); Volatile.Clear();
WeightInKg = Form.Weight; WeightInKg = Form.Weight;
HeightInMeters = Form.Height; HeightInMeters = Form.Height;
Types = Form.Types;
OverrideAbility = null;
AbilitySuppressed = false;
} }
} }
} }
@ -1032,6 +1062,12 @@ public class PokemonImpl : ScriptSource, IPokemon
return true; return true;
} }
/// <inheritdoc />
public void SetTypes(IReadOnlyList<TypeIdentifier> types)
{
_types = types.ToList();
}
/// <inheritdoc /> /// <inheritdoc />
public SerializedPokemon Serialize() => new(this); public SerializedPokemon Serialize() => new(this);

View File

@ -28,6 +28,8 @@ public interface IReadOnlyTypeLibrary
/// This is equivalent to running get_single_effectiveness on each defending type, and multiplying the results with each other. /// This is equivalent to running get_single_effectiveness on each defending type, and multiplying the results with each other.
/// </summary> /// </summary>
float GetEffectiveness(TypeIdentifier attacking, IReadOnlyList<TypeIdentifier> defending); float GetEffectiveness(TypeIdentifier attacking, IReadOnlyList<TypeIdentifier> defending);
IEnumerable<(TypeIdentifier type, float effectiveness)> GetAllEffectivenessFromAttacking(TypeIdentifier attacking);
} }
/// <inheritdoc /> /// <inheritdoc />
@ -70,6 +72,17 @@ public class TypeLibrary : IReadOnlyTypeLibrary
defending.Aggregate<TypeIdentifier, float>(1, defending.Aggregate<TypeIdentifier, float>(1,
(current, type) => current * GetSingleEffectiveness(attacking, type)); (current, type) => current * GetSingleEffectiveness(attacking, type));
/// <inheritdoc />
public IEnumerable<(TypeIdentifier, float)> GetAllEffectivenessFromAttacking(TypeIdentifier attacking)
{
if (attacking.Value < 1 || attacking.Value > _effectiveness.Count)
throw new ArgumentOutOfRangeException(nameof(attacking));
for (var i = 0; i < _effectiveness.Count; i++)
{
yield return (new TypeIdentifier((byte)(i + 1)), _effectiveness[attacking.Value - 1][i]);
}
}
/// <summary> /// <summary>
/// Registers a new type in the library. /// Registers a new type in the library.
/// </summary> /// </summary>

View File

@ -29,4 +29,16 @@ public enum Statistic : byte
/// Speed determines the order that a Pokémon can act in battle. /// Speed determines the order that a Pokémon can act in battle.
/// </summary> /// </summary>
Speed, Speed,
/// <summary>
/// Evasion determines the likelihood that a Pokémon will dodge an attack.
/// This is not part of base stats, but is a temporary stat boost.
/// </summary>
Evasion,
/// <summary>
/// Accuracy determines the likelihood that a Pokémon will hit an attack.
/// This is not part of base stats, but is a temporary stat boost.
/// </summary>
Accuracy,
} }

View File

@ -168,12 +168,24 @@ public record StatisticSet<T> : ImmutableStatisticSet<T>, IEnumerable<T>, IDeepC
Speed = Add(Speed, value); Speed = Add(Speed, value);
break; break;
default: default:
throw new ArgumentException("Invalid statistic."); SetUnknownStat(stat, Add(GetUnknownStat(stat), value));
break;
} }
return true; return true;
} }
protected virtual T GetUnknownStat(Statistic stat)
{
throw new ArgumentException($"Invalid statistic {stat}");
}
protected virtual void SetUnknownStat(Statistic stat, T value)
{
throw new ArgumentException($"Invalid statistic {stat}");
}
/// <summary> /// <summary>
/// Decreases a statistic in the set by a value. /// Decreases a statistic in the set by a value.
/// </summary> /// </summary>
@ -200,14 +212,15 @@ public record StatisticSet<T> : ImmutableStatisticSet<T>, IEnumerable<T>, IDeepC
Speed = Subtract(Speed, value); Speed = Subtract(Speed, value);
break; break;
default: default:
throw new ArgumentException("Invalid statistic."); SetUnknownStat(stat, Subtract(GetUnknownStat(stat), value));
break;
} }
return true; return true;
} }
/// <inheritdoc /> /// <inheritdoc />
public IEnumerator<T> GetEnumerator() public virtual IEnumerator<T> GetEnumerator()
{ {
yield return Hp; yield return Hp;
yield return Attack; yield return Attack;
@ -332,6 +345,47 @@ public record StatBoostStatisticSet : ClampedStatisticSet<sbyte>
sbyte speed) : base(hp, attack, defense, specialAttack, specialDefense, speed) sbyte speed) : base(hp, attack, defense, specialAttack, specialDefense, speed)
{ {
} }
/// <inheritdoc />
protected override sbyte GetUnknownStat(Statistic stat)
{
return stat switch
{
Statistic.Evasion => Evasion,
Statistic.Accuracy => Accuracy,
_ => throw new ArgumentException($"Invalid statistic {stat}"),
};
}
/// <inheritdoc />
/// <inheritdoc />
protected override void SetUnknownStat(Statistic stat, sbyte value)
{
switch (stat)
{
case Statistic.Evasion:
Evasion = value;
break;
case Statistic.Accuracy:
Accuracy = value;
break;
default:
throw new ArgumentException($"Invalid statistic {stat}");
}
}
/// <inheritdoc />
public override IEnumerator<sbyte> GetEnumerator()
{
yield return Hp;
yield return Attack;
yield return Defense;
yield return SpecialAttack;
yield return SpecialDefense;
yield return Speed;
yield return Evasion;
yield return Accuracy;
}
} }
/// <summary> /// <summary>

View File

@ -1424,7 +1424,10 @@
"category": "status", "category": "status",
"flags": [ "flags": [
"snatch" "snatch"
] ],
"effect": {
"name": "calm_mind"
}
}, },
{ {
"name": "camouflage", "name": "camouflage",
@ -1437,7 +1440,10 @@
"category": "status", "category": "status",
"flags": [ "flags": [
"snatch" "snatch"
] ],
"effect": {
"name": "camouflage"
}
}, },
{ {
"name": "captivate", "name": "captivate",
@ -1452,7 +1458,10 @@
"protect", "protect",
"reflectable", "reflectable",
"mirror" "mirror"
] ],
"effect": {
"name": "captivate"
}
}, },
{ {
"name": "catastropika", "name": "catastropika",
@ -1476,7 +1485,10 @@
"priority": 0, "priority": 0,
"target": "Self", "target": "Self",
"category": "status", "category": "status",
"flags": [] "flags": [],
"effect": {
"name": "celebrate"
}
}, },
{ {
"name": "charge", "name": "charge",
@ -1489,7 +1501,10 @@
"category": "status", "category": "status",
"flags": [ "flags": [
"snatch" "snatch"
] ],
"effect": {
"name": "charge"
}
}, },
{ {
"name": "charge_beam", "name": "charge_beam",
@ -1503,7 +1518,14 @@
"flags": [ "flags": [
"protect", "protect",
"mirror" "mirror"
] ],
"effect": {
"name": "change_user_special_attack",
"chance": 70,
"parameters": {
"amount": 1
}
}
}, },
{ {
"name": "charm", "name": "charm",
@ -1518,7 +1540,13 @@
"protect", "protect",
"reflectable", "reflectable",
"mirror" "mirror"
] ],
"effect": {
"name": "change_target_attack",
"parameters": {
"amount": -2
}
}
}, },
{ {
"name": "chatter", "name": "chatter",
@ -1535,7 +1563,10 @@
"sound", "sound",
"distance", "distance",
"ignore-substitute" "ignore-substitute"
] ],
"effect": {
"name": "confuse"
}
}, },
{ {
"name": "chip_away", "name": "chip_away",
@ -1550,7 +1581,10 @@
"contact", "contact",
"protect", "protect",
"mirror" "mirror"
] ],
"effect": {
"name": "chip_away"
}
}, },
{ {
"name": "circle_throw", "name": "circle_throw",
@ -1565,7 +1599,10 @@
"contact", "contact",
"protect", "protect",
"mirror" "mirror"
] ],
"effect": {
"name": "circle_throw"
}
}, },
{ {
"name": "clamp", "name": "clamp",
@ -1580,7 +1617,10 @@
"contact", "contact",
"protect", "protect",
"mirror" "mirror"
] ],
"effect": {
"name": "bind"
}
}, },
{ {
"name": "clanging_scales", "name": "clanging_scales",
@ -1596,7 +1636,13 @@
"mirror", "mirror",
"sound", "sound",
"ignore-substitute" "ignore-substitute"
] ],
"effect": {
"name": "change_user_defense",
"parameters": {
"amount": -1
}
}
}, },
{ {
"name": "clear_smog", "name": "clear_smog",
@ -1610,7 +1656,10 @@
"flags": [ "flags": [
"protect", "protect",
"mirror" "mirror"
] ],
"effect": {
"name": "reset_target_stats"
}
}, },
{ {
"name": "close_combat", "name": "close_combat",
@ -1625,7 +1674,13 @@
"contact", "contact",
"protect", "protect",
"mirror" "mirror"
] ],
"effect": {
"name": "change_user_defense",
"parameters": {
"amount": -1
}
}
}, },
{ {
"name": "coil", "name": "coil",
@ -1638,7 +1693,10 @@
"category": "status", "category": "status",
"flags": [ "flags": [
"snatch" "snatch"
] ],
"effect": {
"name": "coil"
}
}, },
{ {
"name": "comet_punch", "name": "comet_punch",
@ -1654,7 +1712,10 @@
"protect", "protect",
"mirror", "mirror",
"punch" "punch"
] ],
"effect": {
"name": "2_5_hit_move"
}
}, },
{ {
"name": "confide", "name": "confide",
@ -1670,7 +1731,13 @@
"mirror", "mirror",
"sound", "sound",
"ignore-substitute" "ignore-substitute"
] ],
"effect": {
"name": "change_target_special_attack",
"parameters": {
"amount": -1
}
}
}, },
{ {
"name": "confuse_ray", "name": "confuse_ray",
@ -1685,7 +1752,10 @@
"protect", "protect",
"reflectable", "reflectable",
"mirror" "mirror"
] ],
"effect": {
"name": "confuse"
}
}, },
{ {
"name": "confusion", "name": "confusion",
@ -1699,7 +1769,11 @@
"flags": [ "flags": [
"protect", "protect",
"mirror" "mirror"
] ],
"effect": {
"name": "confuse",
"chance": 10
}
}, },
{ {
"name": "constrict", "name": "constrict",
@ -1714,7 +1788,14 @@
"contact", "contact",
"protect", "protect",
"mirror" "mirror"
] ],
"effect": {
"name": "change_target_speed",
"chance": 10,
"parameters": {
"amount": -1
}
}
}, },
{ {
"name": "continental_crush__physical", "name": "continental_crush__physical",
@ -1749,7 +1830,10 @@
"category": "status", "category": "status",
"flags": [ "flags": [
"snatch" "snatch"
] ],
"effect": {
"name": "conversion"
}
}, },
{ {
"name": "conversion_2", "name": "conversion_2",
@ -1762,7 +1846,10 @@
"category": "status", "category": "status",
"flags": [ "flags": [
"ignore-substitute" "ignore-substitute"
] ],
"effect": {
"name": "conversion_2"
}
}, },
{ {
"name": "copycat", "name": "copycat",
@ -1773,7 +1860,10 @@
"priority": 0, "priority": 0,
"target": "Self", "target": "Self",
"category": "status", "category": "status",
"flags": [] "flags": [],
"effect": {
"name": "copycat"
}
}, },
{ {
"name": "core_enforcer", "name": "core_enforcer",
@ -1787,7 +1877,10 @@
"flags": [ "flags": [
"protect", "protect",
"mirror" "mirror"
] ],
"effect": {
"name": "core_enforcer"
}
}, },
{ {
"name": "corkscrew_crash__physical", "name": "corkscrew_crash__physical",
@ -1822,7 +1915,10 @@
"category": "status", "category": "status",
"flags": [ "flags": [
"snatch" "snatch"
] ],
"effect": {
"name": "cosmic_power"
}
}, },
{ {
"name": "cotton_guard", "name": "cotton_guard",
@ -1835,7 +1931,13 @@
"category": "status", "category": "status",
"flags": [ "flags": [
"snatch" "snatch"
] ],
"effect": {
"name": "change_user_defense",
"parameters": {
"amount": 3
}
}
}, },
{ {
"name": "cotton_spore", "name": "cotton_spore",
@ -1851,7 +1953,13 @@
"reflectable", "reflectable",
"mirror", "mirror",
"powder" "powder"
] ],
"effect": {
"name": "change_target_speed",
"parameters": {
"amount": -2
}
}
}, },
{ {
"name": "counter", "name": "counter",

View File

@ -8,7 +8,8 @@ public class BulkUp : Script
/// <inheritdoc /> /// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{ {
move.User.ChangeStatBoost(Statistic.Attack, 1, true); EventBatchId eventBatchId = new();
move.User.ChangeStatBoost(Statistic.Defense, 1, true); move.User.ChangeStatBoost(Statistic.Attack, 1, true, eventBatchId);
move.User.ChangeStatBoost(Statistic.Defense, 1, true, eventBatchId);
} }
} }

View File

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

View File

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

View File

@ -0,0 +1,25 @@
using PkmnLib.Static;
using PkmnLib.Static.Species;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "captivate")]
public class Captivate : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var user = move.User;
if (target.Gender == Gender.Genderless || target.Gender == user.Gender)
{
move.GetHitData(target, hit).Fail();
return;
}
if (target.ActiveAbility?.Name == "oblivious")
{
move.GetHitData(target, hit).Fail();
return;
}
target.ChangeStatBoost(Statistic.SpecialAttack, -2, false);
}
}

View File

@ -0,0 +1,13 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "celebrate")]
public class Celebrate : Script
{
/// <inheritdoc />
public override void PreventMoveSelection(IMoveChoice choice, ref bool prevent)
{
// This move is mostly useless, and it's not worth the effort to implement it.
// Prevent it from being selected.
prevent = true;
}
}

View File

@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using PkmnLib.Static;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
public abstract class ChangeUserStats : Script
{
private readonly Statistic _stat;
private sbyte _amount;
protected ChangeUserStats(Statistic stat)
{
_stat = stat;
}
/// <inheritdoc />
public override void OnInitialize(IReadOnlyDictionary<StringKey, object?>? parameters)
{
if (parameters == null)
{
throw new ArgumentNullException(nameof(parameters));
}
if (!parameters.TryGetValue("amount", out var amount) || amount == null)
{
throw new ArgumentException("Parameter 'amount' is required.");
}
_amount = (sbyte)amount;
}
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
move.User.ChangeStatBoost(_stat, _amount, true);
}
}
[Script(ScriptCategory.Move, "change_user_attack")]
public class ChangeUserAttack : ChangeUserStats
{
public ChangeUserAttack() : base(Statistic.Attack)
{
}
}
[Script(ScriptCategory.Move, "change_user_defense")]
public class ChangeUserDefense : ChangeUserStats
{
public ChangeUserDefense() : base(Statistic.Defense)
{
}
}
[Script(ScriptCategory.Move, "change_user_special_attack")]
public class ChangeUserSpecialAttack : ChangeUserStats
{
public ChangeUserSpecialAttack() : base(Statistic.SpecialAttack)
{
}
}
[Script(ScriptCategory.Move, "change_user_special_defense")]
public class ChangeUserSpecialDefense : ChangeUserStats
{
public ChangeUserSpecialDefense() : base(Statistic.SpecialDefense)
{
}
}
[Script(ScriptCategory.Move, "change_user_speed")]
public class ChangeUserSpeed : ChangeUserStats
{
public ChangeUserSpeed() : base(Statistic.Speed)
{
}
}

View File

@ -0,0 +1,15 @@
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
using PkmnLib.Static;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "charge")]
public class Charge : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
move.User.ChangeStatBoost(Statistic.SpecialDefense, 1, true);
move.User.Volatile.Add(new ChargeEffect());
}
}

View File

@ -0,0 +1,13 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "chip_away")]
public class ChipAway : Script
{
/// <inheritdoc />
public override void BypassDefensiveStatBoosts(IExecutingMove move, IPokemon target, byte hit, ref bool bypass)
{
bypass = true;
}
// TODO: bypass evasion stat.
}

View File

@ -0,0 +1,7 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "circle_throw")]
public class CircleThrow : Script
{
// TODO: Implement this. How to handle forced switch?
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,20 @@
using System.Linq;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "conversion")]
public class Conversion : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var moveType = move.User.Moves.WhereNotNull().FirstOrDefault()?.MoveData.MoveType;
if (moveType == null)
{
move.GetHitData(target, hit).Fail();
return;
}
move.User.SetTypes([moveType.Value]);
}
}

View File

@ -0,0 +1,57 @@
using System.Linq;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "conversion_2")]
public class Conversion2 : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var previousTurnChoices = target.BattleData?.Battle.PreviousTurnChoices;
var nextExecutingChoice = target.BattleData?.Battle.ChoiceQueue?.Peek();
var lastMoveByTarget = previousTurnChoices?
// The previous turn choices include the choices of the current turn, so we need to have special handling for
// the current turn
.Select((x, index) =>
{
// All choices before the current turn are valid
if (index < previousTurnChoices.Count - 1)
return x;
// If there is no next choice, we're at the end of the list, so we can just return the whole list
if (nextExecutingChoice == null)
return x;
// Otherwise we determine where the next choice is and return everything before that
var indexOfNext = x.IndexOf(nextExecutingChoice);
if (indexOfNext == -1)
return x;
return x.Take(indexOfNext);
})
.SelectMany(x => x)
// We only want the last move choice by the target
.OfType<IMoveChoice>().FirstOrDefault(x => x.User == target);
if (lastMoveByTarget == null)
{
move.GetHitData(target, hit).Fail();
return;
}
var typeLibrary = move.User.BattleData!.Battle.Library.StaticLibrary.Types;
// Get all types against which the last move would be not very effective
var type = typeLibrary.GetAllEffectivenessFromAttacking(lastMoveByTarget.ChosenMove.MoveData.MoveType)
.Where(x => x.effectiveness < 1)
// Shuffle them randomly, but deterministically
.OrderBy(_ => move.User.BattleData.Battle.Random.GetInt())
.ThenBy(x => x.type.Value)
// And grab the first one
.Select(x => x.type)
.FirstOrDefault();
if (type == null)
{
move.GetHitData(target, hit).Fail();
return;
}
move.User.SetTypes([type]);
}
}

View File

@ -0,0 +1,24 @@
using System.Linq;
using PkmnLib.Plugin.Gen7.Scripts.Utils;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "copycat")]
public class Copycat : Script
{
/// <inheritdoc />
public override void ChangeMove(IMoveChoice choice, ref StringKey moveName)
{
var lastMove = choice.User.BattleData?.Battle.PreviousTurnChoices
.SelectMany(x => x)
.OfType<IMoveChoice>()
.LastOrDefault();
if (lastMove == null || !lastMove.ChosenMove.MoveData.CanCopyMove())
{
choice.Fail();
return;
}
moveName = lastMove.ChosenMove.MoveData.Name;
}
}

View File

@ -0,0 +1,25 @@
using System.Linq;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "core_enforcer")]
public class CoreEnforcer : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var battleData = target.BattleData;
if (battleData == null)
return;
var turnChoices = battleData.Battle.PreviousTurnChoices.Last();
var currentChoiceIndex = turnChoices.IndexOf(move.MoveChoice);
if (currentChoiceIndex == -1 ||
!turnChoices.Take(currentChoiceIndex).Any(choice => choice is IMoveChoice or IItemChoice))
{
move.GetHitData(target, hit).Fail();
return;
}
target.SuppressAbility();
}
}

View File

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

View File

@ -0,0 +1,18 @@
using System;
using PkmnLib.Static;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "reset_target_stats")]
public class ResetTargetStats : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
EventBatchId eventBatchId = new();
foreach (Statistic stat in Enum.GetValues(typeof(Statistic)))
{
target.ChangeStatBoost(stat, (sbyte)-target.StatBoost.GetStatistic(stat), true, eventBatchId);
}
}
}

View File

@ -0,0 +1,30 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
[Script(ScriptCategory.Pokemon, "charge_effect")]
public class ChargeEffect : Script
{
private bool _turnOfUse = true;
/// <inheritdoc />
public override void ChangeDamageModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier)
{
var library = target.BattleData?.Battle.Library;
if (library == null)
return;
if (!library.StaticLibrary.Types.TryGetTypeIdentifier("electric", out var electricType))
return;
if (move.UseMove.MoveType == electricType)
modifier *= 2;
}
/// <inheritdoc />
public override void OnEndTurn(IBattle battle)
{
if (_turnOfUse)
{
_turnOfUse = false;
return;
}
RemoveSelf();
}
}

View File

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

View File

@ -36,7 +36,7 @@ public class Hail : Script
continue; continue;
if (pokemon.Types.Contains(iceType)) if (pokemon.Types.Contains(iceType))
continue; continue;
if (_hailIgnoreAbilities.Contains(pokemon.ActiveAbility.Name)) if (pokemon.ActiveAbility != null && _hailIgnoreAbilities.Contains(pokemon.ActiveAbility.Name))
continue; continue;
var maxHealth = pokemon.BoostedStats.Hp; var maxHealth = pokemon.BoostedStats.Hp;