diff --git a/PkmnLib.Dynamic/BattleFlow/MoveTurnExecutor.cs b/PkmnLib.Dynamic/BattleFlow/MoveTurnExecutor.cs index 9e88cb2..6648f85 100644 --- a/PkmnLib.Dynamic/BattleFlow/MoveTurnExecutor.cs +++ b/PkmnLib.Dynamic/BattleFlow/MoveTurnExecutor.cs @@ -189,7 +189,8 @@ public static class MoveTurnExecutor var basePower = battle.Library.DamageCalculator.GetBasePower(executingMove, target, hitIndex, hitData); hitData.BasePower = basePower; - hitData.Damage = battle.Library.DamageCalculator.GetDamage(executingMove, target, hitIndex, hitData); + hitData.Damage = battle.Library.DamageCalculator.GetDamage(executingMove, executingMove.UseMove.Category, + executingMove.User, target, executingMove.TargetCount, hitIndex, hitData); var accuracy = useMove.Accuracy; // If the accuracy is 255, the move should always hit, and as such we should not allow diff --git a/PkmnLib.Dynamic/Libraries/DamageCalculator.cs b/PkmnLib.Dynamic/Libraries/DamageCalculator.cs index c589e54..93f3590 100644 --- a/PkmnLib.Dynamic/Libraries/DamageCalculator.cs +++ b/PkmnLib.Dynamic/Libraries/DamageCalculator.cs @@ -1,4 +1,5 @@ using PkmnLib.Dynamic.Models; +using PkmnLib.Static.Moves; namespace PkmnLib.Dynamic.Libraries; @@ -10,7 +11,8 @@ public interface IDamageCalculator /// /// Calculate the damage for a given hit on a Pokemon. /// - uint GetDamage(IExecutingMove executingMove, IPokemon target, byte hitNumber, IHitData hitData); + uint GetDamage(IExecutingMove? executingMove, MoveCategory category, IPokemon user, IPokemon target, + int targetCount, byte hitNumber, IHitData hitData); /// /// Calculate the base power for a given hit on a Pokemon. diff --git a/Plugins/PkmnLib.Plugin.Gen7.Tests/DamageCalculatorTests.cs b/Plugins/PkmnLib.Plugin.Gen7.Tests/DamageCalculatorTests.cs index 4dc92e7..3ed9f1e 100644 --- a/Plugins/PkmnLib.Plugin.Gen7.Tests/DamageCalculatorTests.cs +++ b/Plugins/PkmnLib.Plugin.Gen7.Tests/DamageCalculatorTests.cs @@ -56,7 +56,8 @@ public class DamageCalculatorTests // has a double weakness to the move's Ice type hit.Effectiveness.Returns(4.0f); - var damage = damageCalculator.GetDamage(executingMove, defender, 0, hit); + var damage = damageCalculator.GetDamage(executingMove, executingMove.UseMove.Category, executingMove.User, + defender, executingMove.TargetCount, 0, hit); // That means Ice Fang will do between 168 and 196 HP damage, depending on luck. // Note that we are testing deterministic damage, so we expect the maximum damage. await Assert.That(damage).IsEqualTo((uint)196); diff --git a/Plugins/PkmnLib.Plugin.Gen7/Libraries/Battling/Gen7DamageCalculator.cs b/Plugins/PkmnLib.Plugin.Gen7/Libraries/Battling/Gen7DamageCalculator.cs index d2d0a63..8b663c5 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Libraries/Battling/Gen7DamageCalculator.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Libraries/Battling/Gen7DamageCalculator.cs @@ -6,30 +6,30 @@ namespace PkmnLib.Plugin.Gen7.Libraries.Battling; public class Gen7DamageCalculator(Gen7PluginConfiguration configuration) : IDamageCalculator { /// - public uint GetDamage(IExecutingMove executingMove, IPokemon target, byte hitNumber, IHitData hitData) + public uint GetDamage(IExecutingMove? executingMove, MoveCategory category, IPokemon user, IPokemon target, + int targetCount, byte hitNumber, IHitData hitData) { - var category = executingMove.UseMove.Category; if (category == MoveCategory.Status) return 0; if (hitData.Effectiveness == 0) return 0; - var levelModifier = 2.0f * executingMove.User.Level / 5.0f + 2.0f; + var levelModifier = 2.0f * user.Level / 5.0f + 2.0f; var basePower = (float)hitData.BasePower; - var statModifier = GetStatModifier(executingMove, target, hitNumber, hitData); - var damageModifier = GetDamageModifier(executingMove, target, hitNumber); + var statModifier = GetStatModifier(executingMove, category, user, target, hitNumber, hitData); + var damageModifier = executingMove == null ? 1.0f : GetDamageModifier(executingMove, target, hitNumber); var floatDamage = MathF.Floor(levelModifier * basePower); floatDamage = MathF.Floor(floatDamage * statModifier); floatDamage = MathF.Floor(floatDamage / 50.0f) + 2.0f; floatDamage = MathF.Floor(floatDamage * damageModifier); - if (executingMove.TargetCount > 1) + if (targetCount > 1) floatDamage = MathF.Floor(floatDamage * 0.75f); if (hitData.IsCritical) { var critModifier = 1.5f; - executingMove.RunScriptHook(script => + executingMove?.RunScriptHook(script => script.ChangeCriticalModifier(executingMove, target, hitNumber, ref critModifier)); floatDamage = MathF.Floor(floatDamage * critModifier); } @@ -46,12 +46,12 @@ public class Gen7DamageCalculator(Gen7PluginConfiguration configuration) : IDama var stabModifier = 1.0f; var isStab = false; - if (hitData.Type != null && executingMove.User.Types.Contains(hitData.Type.Value)) + if (hitData.Type != null && user.Types.Contains(hitData.Type.Value)) { stabModifier = 1.5f; isStab = true; } - executingMove.RunScriptHook(script => + executingMove?.RunScriptHook(script => script.ChangeStabModifier(executingMove, target, hitNumber, isStab, ref stabModifier)); floatDamage = MathF.Floor(floatDamage * stabModifier); @@ -62,8 +62,14 @@ public class Gen7DamageCalculator(Gen7PluginConfiguration configuration) : IDama < 1 => 1, _ => (uint)floatDamage, }; - executingMove.RunScriptHook(script => script.ChangeMoveDamage(executingMove, target, hitNumber, ref damage)); - target.RunScriptHook(script => script.ChangeIncomingMoveDamage(executingMove, target, hitNumber, ref damage)); + + if (executingMove is not null) + { + executingMove.RunScriptHook(script => + script.ChangeMoveDamage(executingMove, target, hitNumber, ref damage)); + target.RunScriptHook(script => + script.ChangeIncomingMoveDamage(executingMove, target, hitNumber, ref damage)); + } return damage; } @@ -97,10 +103,9 @@ public class Gen7DamageCalculator(Gen7PluginConfiguration configuration) : IDama }; } - private static float GetStatModifier(IExecutingMove executingMove, IPokemon target, byte hitNumber, - IHitData hitData) + private static float GetStatModifier(IExecutingMove? executingMove, MoveCategory category, IPokemon user, + IPokemon target, byte hitNumber, IHitData hitData) { - var category = executingMove.UseMove.Category; if (category == MoveCategory.Status) return 1; @@ -114,19 +119,19 @@ public class Gen7DamageCalculator(Gen7PluginConfiguration configuration) : IDama // move is critical, and the target has a defensive stat boost of > 0, but a script is // allowed to change this. var bypassDefense = hitData.IsCritical && target.StatBoost.GetStatistic(defensive) > 0; - executingMove.RunScriptHook(script => + executingMove?.RunScriptHook(script => script.BypassDefensiveStatBoosts(executingMove, target, hitNumber, ref bypassDefense)); // Check if we can bypass the offensive stat boost on the user. We default to this if the // move is critical, and the user has an offensive stat boost of < 0, but a script is // allowed to change this. - var bypassOffense = hitData.IsCritical && executingMove.User.StatBoost.GetStatistic(offensive) < 0; - executingMove.RunScriptHook(script => + var bypassOffense = hitData.IsCritical && user.StatBoost.GetStatistic(offensive) < 0; + executingMove?.RunScriptHook(script => script.BypassOffensiveStatBoosts(executingMove, target, hitNumber, ref bypassOffense)); - var userStats = executingMove.User.BoostedStats; + var userStats = user.BoostedStats; if (bypassOffense) - userStats = executingMove.User.FlatStats; + userStats = user.FlatStats; var offensiveStat = userStats.GetStatistic(offensive); var targetStats = target.BoostedStats; @@ -135,17 +140,20 @@ public class Gen7DamageCalculator(Gen7PluginConfiguration configuration) : IDama var defensiveStat = targetStats.GetStatistic(defensive); var origOffensiveStat = offensiveStat; - executingMove.RunScriptHook(script => script.ChangeOffensiveStatValue(executingMove, target, hitNumber, - defensiveStat, targetStats, offensive, ref offensiveStat)); - executingMove.RunScriptHook(script => script.ChangeDefensiveStatValue(executingMove, target, hitNumber, - origOffensiveStat, targetStats, defensive, ref defensiveStat)); - target.RunScriptHook(script => script.ChangeIncomingMoveOffensiveStatValue(executingMove, target, hitNumber, - defensiveStat, targetStats, offensive, ref offensiveStat)); - target.RunScriptHook(script => script.ChangeIncomingMoveDefensiveStatValue(executingMove, target, hitNumber, - origOffensiveStat, targetStats, defensive, ref defensiveStat)); + if (executingMove != null) + { + executingMove.RunScriptHook(script => script.ChangeOffensiveStatValue(executingMove, target, hitNumber, + defensiveStat, targetStats, offensive, ref offensiveStat)); + executingMove.RunScriptHook(script => script.ChangeDefensiveStatValue(executingMove, target, hitNumber, + origOffensiveStat, targetStats, defensive, ref defensiveStat)); + target.RunScriptHook(script => script.ChangeIncomingMoveOffensiveStatValue(executingMove, target, hitNumber, + defensiveStat, targetStats, offensive, ref offensiveStat)); + target.RunScriptHook(script => script.ChangeIncomingMoveDefensiveStatValue(executingMove, target, hitNumber, + origOffensiveStat, targetStats, defensive, ref defensiveStat)); + } var modifier = (float)offensiveStat / defensiveStat; - executingMove.RunScriptHook(script => + executingMove?.RunScriptHook(script => script.ChangeDamageStatModifier(executingMove, target, hitNumber, ref modifier)); return modifier; diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Battle/FutureSightEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Battle/FutureSightEffect.cs index c294700..e724b56 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Battle/FutureSightEffect.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Battle/FutureSightEffect.cs @@ -27,7 +27,8 @@ public class FutureSightEffect : Script var executingMove = new ExecutingMoveImpl([target], 1, _moveChoice.ChosenMove, _moveChoice.ChosenMove.MoveData, _moveChoice, battle); var hitData = executingMove.GetHitData(target, 0); - var damage = damageCalculator.GetDamage(executingMove, target, 1, hitData); + var damage = damageCalculator.GetDamage(executingMove, executingMove.UseMove.Category, executingMove.User, + target, executingMove.TargetCount, 1, hitData); target.Damage(damage, DamageSource.Misc); } diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/Confusion.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/Confusion.cs index 6e35836..d310416 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/Confusion.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/Confusion.cs @@ -1,7 +1,98 @@ +using PkmnLib.Static.Moves; + namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon; [Script(ScriptCategory.Pokemon, "confusion")] public class Confusion : Script { - // TODO: Implement confusion + private int _turnsConfused; + + /// + public override void OnAddedToParent(IScriptSource source) + { + if (source is not IPokemon pokemon) + throw new InvalidOperationException("Confusion script can only be added to a Pokemon."); + var battleData = pokemon.BattleData; + if (battleData == null) + throw new InvalidOperationException("Confusion script requires a Pokemon to be in a battle."); + _turnsConfused = battleData.Battle.Random.GetInt(2, 5); + } + + /// + public override void StopBeforeMove(IExecutingMove move, ref bool stop) + { + _turnsConfused--; + move.Battle.EventHook.Invoke(new DialogEvent("pokemon_is_confused", new Dictionary + { + { "pokemon", move.User }, + })); + if (_turnsConfused <= 0) + { + RemoveSelf(); + move.Battle.EventHook.Invoke(new DialogEvent("pokemon_snapped_out_of_confusion", + new Dictionary + { + { "pokemon", move.User }, + })); + return; + } + + if (move.Battle.Random.GetInt(0, 100) < 33) + { + var damage = move.Battle.Library.DamageCalculator.GetDamage(null, MoveCategory.Physical, move.User, + move.User, 1, 0, new ConfusionHitData()); + + EventBatchId batchId = new(); + + move.User.Damage(damage, DamageSource.Confusion, batchId); + move.Battle.EventHook.Invoke(new DialogEvent("pokemon_hit_itself_in_confusion", + new Dictionary + { + { "pokemon", move.User }, + { "damage", damage }, + }) + { + BatchId = batchId, + }); + + stop = true; + } + } + + private class ConfusionHitData : IHitData + { + /// + public bool IsCritical => false; + + /// + public ushort BasePower => 40; + + /// + public float Effectiveness => 1.0f; + + /// + public uint Damage { get; } = 0; + + /// + public TypeIdentifier? Type => null; + + /// + public bool IsContact => true; + + /// + public bool HasFailed => false; + + /// + public void Fail() + { + } + + /// + public void SetFlag(StringKey flag) + { + } + + /// + public bool HasFlag(StringKey flag) => false; + } } \ No newline at end of file