mirror of
https://github.com/smogon/pokemon-showdown.git
synced 2026-03-21 17:25:10 -05:00
This involves a huge refactor for how battles are constructed, but it's totally worth it. Currently, tournaments, challenges, and laddering are unsupported; only unrated searches work. But it does work, and it's beautiful.
159 lines
5.2 KiB
TypeScript
159 lines
5.2 KiB
TypeScript
export const Scripts: ModdedBattleScriptsData = {
|
|
inherit: 'gen5',
|
|
gen: 4,
|
|
|
|
actions: {
|
|
inherit: true,
|
|
modifyDamage(baseDamage, pokemon, target, move, suppressMessages = false) {
|
|
// DPP divides modifiers into several mathematically important stages
|
|
// The modifiers run earlier than other generations are called with ModifyDamagePhase1 and ModifyDamagePhase2
|
|
|
|
if (!move.type) move.type = '???';
|
|
const type = move.type;
|
|
|
|
// Burn
|
|
if (pokemon.status === 'brn' && baseDamage && move.category === 'Physical' && !pokemon.hasAbility('guts')) {
|
|
baseDamage = this.battle.modify(baseDamage, 0.5);
|
|
}
|
|
|
|
// Other modifiers (Reflect/Light Screen/etc)
|
|
baseDamage = this.battle.runEvent('ModifyDamagePhase1', pokemon, target, move, baseDamage);
|
|
|
|
// Double battle multi-hit
|
|
if (move.spreadHit) {
|
|
const spreadModifier = move.spreadModifier || (this.battle.gameType === 'freeforall' ? 0.5 : 0.75);
|
|
this.battle.debug('Spread modifier: ' + spreadModifier);
|
|
baseDamage = this.battle.modify(baseDamage, spreadModifier);
|
|
}
|
|
|
|
// Weather
|
|
baseDamage = this.battle.runEvent('WeatherModifyDamage', pokemon, target, move, baseDamage);
|
|
|
|
if (this.battle.gen === 3 && move.category === 'Physical' && !Math.floor(baseDamage)) {
|
|
baseDamage = 1;
|
|
}
|
|
|
|
baseDamage += 2;
|
|
|
|
const isCrit = target.getMoveHitData(move).crit;
|
|
if (isCrit) {
|
|
baseDamage = this.battle.modify(baseDamage, move.critModifier || 2);
|
|
}
|
|
|
|
// Mod 2 (Damage is floored after all multipliers are in)
|
|
baseDamage = Math.floor(this.battle.runEvent('ModifyDamagePhase2', pokemon, target, move, baseDamage));
|
|
|
|
// this is not a modifier
|
|
baseDamage = this.battle.randomizer(baseDamage);
|
|
|
|
// STAB
|
|
if (move.forceSTAB || type !== '???' && pokemon.hasType(type)) {
|
|
// The "???" type never gets STAB
|
|
// Not even if you Roost in Gen 4 and somehow manage to use
|
|
// Struggle in the same turn.
|
|
// (On second thought, it might be easier to get a MissingNo.)
|
|
baseDamage = this.battle.modify(baseDamage, move.stab || 1.5);
|
|
}
|
|
// types
|
|
let typeMod = target.runEffectiveness(move);
|
|
typeMod = this.battle.clampIntRange(typeMod, -6, 6);
|
|
target.getMoveHitData(move).typeMod = typeMod;
|
|
if (typeMod > 0) {
|
|
if (!suppressMessages) this.battle.add('-supereffective', target);
|
|
|
|
for (let i = 0; i < typeMod; i++) {
|
|
baseDamage *= 2;
|
|
}
|
|
}
|
|
if (typeMod < 0) {
|
|
if (!suppressMessages) this.battle.add('-resisted', target);
|
|
|
|
for (let i = 0; i > typeMod; i--) {
|
|
baseDamage = Math.floor(baseDamage / 2);
|
|
}
|
|
}
|
|
|
|
if (isCrit && !suppressMessages) this.battle.add('-crit', target);
|
|
|
|
// Final modifier.
|
|
baseDamage = this.battle.runEvent('ModifyDamage', pokemon, target, move, baseDamage);
|
|
|
|
if (!Math.floor(baseDamage)) {
|
|
return 1;
|
|
}
|
|
|
|
return Math.floor(baseDamage);
|
|
},
|
|
hitStepInvulnerabilityEvent(targets, pokemon, move) {
|
|
const hitResults = this.battle.runEvent('Invulnerability', targets, pokemon, move);
|
|
for (const [i, target] of targets.entries()) {
|
|
if (hitResults[i] === false) {
|
|
this.battle.attrLastMove('[miss]');
|
|
this.battle.add('-miss', pokemon, target);
|
|
}
|
|
}
|
|
return hitResults;
|
|
},
|
|
hitStepAccuracy(targets, pokemon, move) {
|
|
const hitResults = [];
|
|
for (const [i, target] of targets.entries()) {
|
|
this.battle.activeTarget = target;
|
|
// calculate true accuracy
|
|
let accuracy = move.accuracy;
|
|
if (move.ohko) { // bypasses accuracy modifiers
|
|
if (!target.isSemiInvulnerable()) {
|
|
if (pokemon.level < target.level) {
|
|
this.battle.add('-immune', target, '[ohko]');
|
|
hitResults[i] = false;
|
|
continue;
|
|
}
|
|
accuracy = 30 + pokemon.level - target.level;
|
|
}
|
|
} else {
|
|
const boostTable = [1, 4 / 3, 5 / 3, 2, 7 / 3, 8 / 3, 3];
|
|
|
|
let boosts;
|
|
let boost!: number;
|
|
if (accuracy !== true) {
|
|
if (!move.ignoreAccuracy) {
|
|
boosts = this.battle.runEvent('ModifyBoost', pokemon, null, null, {...pokemon.boosts});
|
|
boost = this.battle.clampIntRange(boosts['accuracy'], -6, 6);
|
|
if (boost > 0) {
|
|
accuracy *= boostTable[boost];
|
|
} else {
|
|
accuracy /= boostTable[-boost];
|
|
}
|
|
}
|
|
if (!move.ignoreEvasion) {
|
|
boosts = this.battle.runEvent('ModifyBoost', target, null, null, {...target.boosts});
|
|
boost = this.battle.clampIntRange(boosts['evasion'], -6, 6);
|
|
if (boost > 0) {
|
|
accuracy /= boostTable[boost];
|
|
} else if (boost < 0) {
|
|
accuracy *= boostTable[-boost];
|
|
}
|
|
}
|
|
}
|
|
accuracy = this.battle.runEvent('ModifyAccuracy', target, pokemon, move, accuracy);
|
|
}
|
|
if (move.alwaysHit) {
|
|
accuracy = true; // bypasses ohko accuracy modifiers
|
|
} else {
|
|
accuracy = this.battle.runEvent('Accuracy', target, pokemon, move, accuracy);
|
|
}
|
|
if (accuracy !== true && !this.battle.randomChance(accuracy, 100)) {
|
|
if (!move.spreadHit) this.battle.attrLastMove('[miss]');
|
|
this.battle.add('-miss', pokemon, target);
|
|
hitResults[i] = false;
|
|
continue;
|
|
}
|
|
hitResults[i] = true;
|
|
}
|
|
return hitResults;
|
|
},
|
|
calcRecoilDamage(damageDealt, move) {
|
|
return this.battle.clampIntRange(Math.floor(damageDealt * move.recoil![0] / move.recoil![1]), 1);
|
|
},
|
|
},
|
|
};
|