164 lines
6.5 KiB
C#
164 lines
6.5 KiB
C#
using System;
|
|
using System.Linq;
|
|
using PkmnLib.Dynamic.Libraries;
|
|
using PkmnLib.Static;
|
|
using PkmnLib.Static.Moves;
|
|
|
|
namespace PkmnLib.Plugin.Gen7.Libraries;
|
|
|
|
public class Gen7DamageCalculator(bool hasRandomness) : IDamageCalculator
|
|
{
|
|
/// <inheritdoc />
|
|
public uint GetDamage(IExecutingMove executingMove, IPokemon target, 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 basePower = (float)hitData.BasePower;
|
|
var statModifier = GetStatModifier(executingMove, target, hitNumber, hitData);
|
|
var damageModifier = 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)
|
|
floatDamage = MathF.Floor(floatDamage * 0.75f);
|
|
|
|
if (hitData.IsCritical)
|
|
{
|
|
var critModifier = 1.5f;
|
|
executingMove.RunScriptHook(script =>
|
|
script.ChangeCriticalModifier(executingMove, target, hitNumber, ref critModifier));
|
|
floatDamage = MathF.Floor(floatDamage * critModifier);
|
|
}
|
|
|
|
if (hasRandomness)
|
|
{
|
|
var battle = target.BattleData?.Battle;
|
|
if (battle == null)
|
|
throw new InvalidOperationException("Randomness is enabled, but no battle is set.");
|
|
var random = battle.Random;
|
|
var randomFactor = random.GetInt(85, 101) / 100.0f;
|
|
floatDamage = MathF.Floor(floatDamage * randomFactor);
|
|
}
|
|
|
|
if (executingMove.User.Types.Contains(hitData.Type))
|
|
{
|
|
var stabModifier = 1.5f;
|
|
executingMove.RunScriptHook(script =>
|
|
script.ChangeStabModifier(executingMove, target, hitNumber, ref stabModifier));
|
|
floatDamage = MathF.Floor(floatDamage * stabModifier);
|
|
}
|
|
|
|
floatDamage = MathF.Floor(floatDamage * hitData.Effectiveness);
|
|
uint damage = floatDamage switch
|
|
{
|
|
> uint.MaxValue => uint.MaxValue,
|
|
< 1 => 1,
|
|
_ => (uint)floatDamage,
|
|
};
|
|
executingMove.RunScriptHook(script =>
|
|
script.ChangeDamage(executingMove, target, hitNumber, ref damage));
|
|
target.RunScriptHook(script =>
|
|
script.ChangeIncomingDamage(executingMove, target, hitNumber, ref damage));
|
|
|
|
return damage;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public byte GetBasePower(IExecutingMove executingMove, IPokemon target, byte hitNumber, IHitData hitData)
|
|
{
|
|
if (executingMove.UseMove.Category == MoveCategory.Status)
|
|
return 0;
|
|
var basePower = executingMove.UseMove.BasePower;
|
|
executingMove.RunScriptHook(script =>
|
|
script.ChangeBasePower(executingMove, target, hitNumber, ref basePower));
|
|
return basePower;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public bool IsCritical(IBattle battle, IExecutingMove executingMove, IPokemon target, byte hitNumber)
|
|
{
|
|
if (executingMove.UseMove.Category == MoveCategory.Status)
|
|
return false;
|
|
byte critStage = 0;
|
|
executingMove.RunScriptHook(script =>
|
|
script.ChangeCriticalStage(executingMove, target, hitNumber, ref critStage));
|
|
|
|
var random = battle.Random;
|
|
return critStage switch
|
|
{
|
|
0 => random.GetInt(24) == 0,
|
|
1 => random.GetInt(8) == 0,
|
|
2 => random.GetInt(2) == 0,
|
|
_ => true,
|
|
};
|
|
}
|
|
|
|
private static float GetStatModifier(IExecutingMove executingMove, IPokemon target, byte hitNumber, IHitData hitData)
|
|
{
|
|
var category = executingMove.UseMove.Category;
|
|
if (category == MoveCategory.Status)
|
|
return 1;
|
|
|
|
var (offensive, defensive) = category switch
|
|
{
|
|
MoveCategory.Physical => (Statistic.Attack, Statistic.Defense),
|
|
_ => (Statistic.SpecialAttack, Statistic.SpecialDefense),
|
|
};
|
|
|
|
// Check if we can bypass the defensive stat boost on the target. We default to this if the
|
|
// 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 =>
|
|
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 =>
|
|
script.BypassOffensiveStatBoosts(executingMove, target, hitNumber, ref bypassOffense));
|
|
|
|
var userStats = executingMove.User.BoostedStats;
|
|
if (bypassOffense)
|
|
userStats = executingMove.User.FlatStats;
|
|
var offensiveStat = userStats.GetStatistic(offensive);
|
|
|
|
var targetStats = target.BoostedStats;
|
|
if (bypassDefense)
|
|
targetStats = target.FlatStats;
|
|
var defensiveStat = targetStats.GetStatistic(defensive);
|
|
|
|
executingMove.RunScriptHook(script =>
|
|
script.ChangeOffensiveStatValue(executingMove, target, hitNumber, ref offensiveStat));
|
|
executingMove.RunScriptHook(script =>
|
|
script.ChangeDefensiveStatValue(executingMove, target, hitNumber, ref defensiveStat));
|
|
|
|
var modifier = (float)offensiveStat / defensiveStat;
|
|
executingMove.RunScriptHook(script =>
|
|
script.ChangeDamageStatModifier(executingMove, target, hitNumber, ref modifier));
|
|
|
|
return modifier;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the damage modifier. This is a value that defaults to 1.0, but can be modified by scripts
|
|
/// to apply a raw modifier to the damage.
|
|
/// </summary>
|
|
private static float GetDamageModifier(IExecutingMove executingMove, IPokemon target, byte hitNumber)
|
|
{
|
|
var modifier = 1.0f;
|
|
|
|
executingMove.RunScriptHook(script =>
|
|
script.ChangeDamageModifier(executingMove, target, hitNumber, ref modifier));
|
|
|
|
return modifier;
|
|
}
|
|
} |