pokemon-showdown/mods/gen4/scripts.js
2018-10-28 05:04:30 -05:00

299 lines
9.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict';
/**@type {ModdedBattleScriptsData} */
let BattleScripts = {
inherit: 'gen5',
gen: 4,
init() {
for (let i in this.data.Pokedex) {
delete this.data.Pokedex[i].abilities['H'];
}
},
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 = '???';
let type = move.type;
// Burn
if (pokemon.status === 'brn' && baseDamage && move.category === 'Physical' && !pokemon.hasAbility('guts')) {
baseDamage = this.modify(baseDamage, 0.5);
}
// Other modifiers (Reflect/Light Screen/etc)
baseDamage = this.runEvent('ModifyDamagePhase1', pokemon, target, move, baseDamage);
// Double battle multi-hit
if (move.spreadHit) {
let spreadModifier = move.spreadModifier || 0.75;
this.debug('Spread modifier: ' + spreadModifier);
baseDamage = this.modify(baseDamage, spreadModifier);
}
// Weather
baseDamage = this.runEvent('WeatherModifyDamage', pokemon, target, move, baseDamage);
if (this.gen === 3 && move.category === 'Physical' && !Math.floor(baseDamage)) {
baseDamage = 1;
}
baseDamage += 2;
if (move.crit) {
baseDamage = this.modify(baseDamage, move.critModifier || 2);
}
// Mod 2 (Damage is floored after all multipliers are in)
baseDamage = Math.floor(this.runEvent('ModifyDamagePhase2', pokemon, target, move, baseDamage));
// this is not a modifier
baseDamage = this.randomizer(baseDamage);
// STAB
if (move.hasSTAB || 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.modify(baseDamage, move.stab || 1.5);
}
// types
move.typeMod = target.runEffectiveness(move);
move.typeMod = this.clampIntRange(move.typeMod, -6, 6);
if (move.typeMod > 0) {
if (!suppressMessages) this.add('-supereffective', target);
for (let i = 0; i < move.typeMod; i++) {
baseDamage *= 2;
}
}
if (move.typeMod < 0) {
if (!suppressMessages) this.add('-resisted', target);
for (let i = 0; i > move.typeMod; i--) {
baseDamage = Math.floor(baseDamage / 2);
}
}
if (move.crit && !suppressMessages) this.add('-crit', target);
// Final modifier.
baseDamage = this.runEvent('ModifyDamage', pokemon, target, move, baseDamage);
if (!Math.floor(baseDamage)) {
return 1;
}
return Math.floor(baseDamage);
},
tryMoveHit(target, pokemon, move) {
this.setActiveMove(move, pokemon, target);
let hitResult = this.singleEvent('PrepareHit', move, {}, target, pokemon, move);
if (!hitResult) {
if (hitResult === false) this.add('-fail', target);
return false;
}
this.runEvent('PrepareHit', pokemon, target, move);
if (!this.singleEvent('Try', move, null, pokemon, target, move)) {
return false;
}
if (move.target === 'all' || move.target === 'foeSide' || move.target === 'allySide' || move.target === 'allyTeam') {
if (move.target === 'all') {
hitResult = this.runEvent('TryHitField', target, pokemon, move);
} else {
hitResult = this.runEvent('TryHitSide', target, pokemon, move);
}
if (!hitResult) {
if (hitResult === false) this.add('-fail', target);
return false;
}
return this.moveHit(target, pokemon, move);
}
hitResult = this.runEvent('TryImmunity', target, pokemon, move);
if (!hitResult) {
if (!move.spreadHit) this.attrLastMove('[miss]');
this.add('-miss', pokemon, target);
return false;
}
if (move.ignoreImmunity === undefined) {
move.ignoreImmunity = (move.category === 'Status');
}
if ((!move.ignoreImmunity || (move.ignoreImmunity !== true && !move.ignoreImmunity[move.type])) && !target.runImmunity(move.type, true)) {
return false;
}
let boostTable = [1, 4 / 3, 5 / 3, 2, 7 / 3, 8 / 3, 3];
// calculate true accuracy
/**@type {number | true} */
let accuracy = move.accuracy;
let boosts, boost;
if (accuracy !== true) {
if (!move.ignoreAccuracy) {
boosts = this.runEvent('ModifyBoost', pokemon, null, null, Object.assign({}, pokemon.boosts));
boost = this.clampIntRange(boosts['accuracy'], -6, 6);
if (boost > 0) {
accuracy *= boostTable[boost];
} else {
accuracy /= boostTable[-boost];
}
}
if (!move.ignoreEvasion) {
boosts = this.runEvent('ModifyBoost', target, null, null, Object.assign({}, target.boosts));
boost = this.clampIntRange(boosts['evasion'], -6, 6);
if (boost > 0) {
accuracy /= boostTable[boost];
} else if (boost < 0) {
accuracy *= boostTable[-boost];
}
}
}
if (move.ohko) { // bypasses accuracy modifiers
if (!target.isSemiInvulnerable()) {
accuracy = 30;
if (pokemon.level >= target.level && (move.ohko === true || !target.hasType(move.ohko))) {
accuracy += (pokemon.level - target.level);
} else {
this.add('-immune', target, '[ohko]');
return false;
}
}
} else {
accuracy = this.runEvent('ModifyAccuracy', target, pokemon, move, accuracy);
}
if (move.alwaysHit) {
accuracy = true; // bypasses ohko accuracy modifiers
} else {
accuracy = this.runEvent('Accuracy', target, pokemon, move, accuracy);
}
if (accuracy !== true && !this.randomChance(accuracy, 100)) {
if (!move.spreadHit) this.attrLastMove('[miss]');
this.add('-miss', pokemon, target);
return false;
}
hitResult = this.runEvent('TryHit', target, pokemon, move);
if (!hitResult) {
if (hitResult === false) this.add('-fail', target);
return false;
}
if (move.breaksProtect && target.removeVolatile('protect')) {
if (move.id === 'feint') {
this.add('-activate', target, 'move: Feint');
} else {
this.add('-activate', target, 'move: ' + move.name, '[broken]');
}
}
move.totalDamage = 0;
/**@type {number | false} */
let damage = 0;
pokemon.lastDamage = 0;
if (move.multihit) {
let hits = move.multihit;
if (Array.isArray(hits)) {
// yes, it's hardcoded... meh
if (hits[0] === 2 && hits[1] === 5) {
hits = this.sample([2, 2, 2, 3, 3, 3, 4, 5]);
} else {
hits = this.random(hits[0], hits[1] + 1);
}
}
hits = Math.floor(hits);
let nullDamage = true;
/**@type {number | false} */
let moveDamage;
// There is no need to recursively check the ´sleepUsable´ flag as Sleep Talk can only be used while asleep.
let isSleepUsable = move.sleepUsable || this.getMove(move.sourceEffect).sleepUsable;
let i;
for (i = 0; i < hits && target.hp && pokemon.hp; i++) {
if (pokemon.status === 'slp' && !isSleepUsable) break;
move.hit = i + 1;
if (move.multiaccuracy && i > 0) {
accuracy = move.accuracy;
if (accuracy !== true) {
if (!move.ignoreAccuracy) {
boosts = this.runEvent('ModifyBoost', pokemon, null, null, Object.assign({}, pokemon.boosts));
boost = this.clampIntRange(boosts['accuracy'], -6, 6);
if (boost > 0) {
accuracy *= boostTable[boost];
} else {
accuracy /= boostTable[-boost];
}
}
if (!move.ignoreEvasion) {
boosts = this.runEvent('ModifyBoost', target, null, null, Object.assign({}, target.boosts));
boost = this.clampIntRange(boosts['evasion'], -6, 6);
if (boost > 0) {
accuracy /= boostTable[boost];
} else if (boost < 0) {
accuracy *= boostTable[-boost];
}
}
}
accuracy = this.runEvent('ModifyAccuracy', target, pokemon, move, accuracy);
if (!move.alwaysHit) {
accuracy = this.runEvent('Accuracy', target, pokemon, move, accuracy);
if (accuracy !== true && !this.randomChance(accuracy, 100)) break;
}
}
moveDamage = this.moveHit(target, pokemon, move);
if (moveDamage === false) break;
if (nullDamage && (moveDamage || moveDamage === 0 || moveDamage === undefined)) nullDamage = false;
// Damage from each hit is individually counted for the
// purposes of Counter, Metal Burst, and Mirror Coat.
damage = (moveDamage || 0);
move.totalDamage += damage;
this.eachEvent('Update');
}
if (i === 0) return false;
if (nullDamage) damage = false;
this.add('-hitcount', target, i);
} else {
damage = this.moveHit(target, pokemon, move);
move.totalDamage = damage;
}
if (move.recoil && move.totalDamage) {
this.damage(this.calcRecoilDamage(move.totalDamage, move), pokemon, target, 'recoil');
}
if (move.struggleRecoil) {
this.directDamage(this.clampIntRange(Math.round(pokemon.maxhp / 4), 1), pokemon, pokemon, /** @type {Effect} */ ({id: 'strugglerecoil'}));
}
if (target && pokemon !== target) target.gotAttacked(move, damage, pokemon);
if (move.ohko) this.add('-ohko');
if (!damage && damage !== 0) return damage;
this.eachEvent('Update');
if (target && !move.negateSecondary) {
this.singleEvent('AfterMoveSecondary', move, null, target, pokemon, move);
this.runEvent('AfterMoveSecondary', target, pokemon, move);
}
return damage;
},
calcRecoilDamage(damageDealt, move) {
// @ts-ignore
return this.clampIntRange(Math.floor(damageDealt * move.recoil[0] / move.recoil[1]), 1);
},
};
exports.BattleScripts = BattleScripts;