mirror of
https://github.com/smogon/pokemon-showdown.git
synced 2026-04-07 09:36:23 -05:00
136 lines
4.5 KiB
TypeScript
136 lines
4.5 KiB
TypeScript
export const Scripts: ModdedBattleScriptsData = {
|
|
gen: 9,
|
|
actions: {
|
|
getDamage(source, target, move, suppressMessages) {
|
|
if (typeof move === 'string') move = this.dex.getActiveMove(move);
|
|
|
|
if (typeof move === 'number') {
|
|
const basePower = move;
|
|
move = new Dex.Move({
|
|
basePower,
|
|
type: '???',
|
|
category: 'Physical',
|
|
willCrit: false,
|
|
}) as ActiveMove;
|
|
move.hit = 0;
|
|
}
|
|
|
|
if (!move.ignoreImmunity || (move.ignoreImmunity !== true && !move.ignoreImmunity[move.type])) {
|
|
if (!target.runImmunity(move.type, !suppressMessages)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (move.ohko) return target.maxhp;
|
|
if (move.damageCallback) return move.damageCallback.call(this.battle, source, target);
|
|
if (move.damage === 'level') {
|
|
return source.level;
|
|
} else if (move.damage) {
|
|
return move.damage;
|
|
}
|
|
|
|
let basePower: number | false | null = move.basePower;
|
|
if (move.basePowerCallback) {
|
|
basePower = move.basePowerCallback.call(this.battle, source, target, move);
|
|
}
|
|
if (!basePower) return basePower === 0 ? undefined : basePower;
|
|
basePower = this.battle.clampIntRange(basePower, 1);
|
|
|
|
let critMult;
|
|
let critRatio = this.battle.runEvent('ModifyCritRatio', source, target, move, move.critRatio || 0);
|
|
if (this.battle.gen <= 5) {
|
|
critRatio = this.battle.clampIntRange(critRatio, 0, 5);
|
|
critMult = [0, 16, 8, 4, 3, 2];
|
|
} else {
|
|
critRatio = this.battle.clampIntRange(critRatio, 0, 4);
|
|
if (this.battle.gen === 6) {
|
|
critMult = [0, 16, 8, 2, 1];
|
|
} else {
|
|
critMult = [0, 24, 8, 2, 1];
|
|
}
|
|
}
|
|
|
|
const moveHit = target.getMoveHitData(move);
|
|
moveHit.crit = move.willCrit || false;
|
|
if (move.willCrit === undefined) {
|
|
if (critRatio) {
|
|
moveHit.crit = this.battle.randomChance(1, critMult[critRatio]);
|
|
}
|
|
}
|
|
|
|
if (moveHit.crit) {
|
|
moveHit.crit = this.battle.runEvent('CriticalHit', target, null, move);
|
|
}
|
|
|
|
// happens after crit calculation
|
|
basePower = this.battle.runEvent('BasePower', source, target, move, basePower, true);
|
|
|
|
if (!basePower) return 0;
|
|
basePower = this.battle.clampIntRange(basePower, 1);
|
|
// Hacked Max Moves have 0 base power, even if you Dynamax
|
|
if ((!source.volatiles['dynamax'] && move.isMax) || (move.isMax && this.dex.moves.get(move.baseMove).isMax)) {
|
|
basePower = 0;
|
|
}
|
|
|
|
const level = source.level;
|
|
|
|
const attacker = move.overrideOffensivePokemon === 'target' ? target : source;
|
|
const defender = move.overrideDefensivePokemon === 'source' ? source : target;
|
|
|
|
const isPhysical = move.category === 'Physical';
|
|
const defenseStat: StatIDExceptHP = move.overrideDefensiveStat || (isPhysical ? 'def' : 'spd');
|
|
|
|
const statTable: {[k in StatIDExceptHP]: string} = {atk: 'Atk', def: 'Def', spa: 'SpA', spd: 'SpD', spe: 'Spe'};
|
|
|
|
let maxAttack = 0;
|
|
|
|
let defBoosts = defender.boosts[defenseStat];
|
|
|
|
let ignoreNegativeOffensive = !!move.ignoreNegativeOffensive;
|
|
let ignorePositiveDefensive = !!move.ignorePositiveDefensive;
|
|
|
|
if (moveHit.crit) {
|
|
ignoreNegativeOffensive = true;
|
|
ignorePositiveDefensive = true;
|
|
}
|
|
|
|
const ignoreDefensive = !!(move.ignoreDefensive || (ignorePositiveDefensive && defBoosts > 0));
|
|
if (ignoreDefensive) {
|
|
this.battle.debug('Negating (sp)def boost/penalty.');
|
|
defBoosts = 0;
|
|
}
|
|
|
|
let attack = 0;
|
|
|
|
for (const attackStat in statTable) {
|
|
let atkBoosts = attacker.boosts[attackStat as keyof BoostsTable];
|
|
const ignoreOffensive = !!(move.ignoreOffensive || (ignoreNegativeOffensive && atkBoosts < 0));
|
|
if (ignoreOffensive) {
|
|
this.battle.debug('Negating (sp)atk boost/penalty.');
|
|
atkBoosts = 0;
|
|
}
|
|
attack = attacker.calculateStat(attackStat as any, atkBoosts, 1, source);
|
|
attack = this.battle.runEvent('Modify' + (statTable as any)[attackStat], source, target, move, attack);
|
|
if (attack > maxAttack) maxAttack = attack;
|
|
}
|
|
|
|
let defense = defender.calculateStat(defenseStat, defBoosts, 1, target);
|
|
|
|
// Apply Stat Modifiers
|
|
defense = this.battle.runEvent('Modify' + statTable[defenseStat], target, source, move, defense);
|
|
|
|
if (this.battle.gen <= 4 && ['explosion', 'selfdestruct'].includes(move.id) && defenseStat === 'def') {
|
|
defense = this.battle.clampIntRange(Math.floor(defense / 2), 1);
|
|
}
|
|
|
|
const tr = this.battle.trunc;
|
|
|
|
// int(int(int(2 * L / 5 + 2) * A * P / D) / 50);
|
|
const baseDamage = tr(tr(tr(tr(2 * level / 5 + 2) * basePower * maxAttack) / defense) / 50);
|
|
|
|
// Calculate damage modifiers separately (order differs between generations)
|
|
return this.modifyDamage(baseDamage, source, target, move, suppressMessages);
|
|
},
|
|
},
|
|
};
|