pokemon-showdown/data/mods/gen3/scripts.js
Guangcong Luo b48733cb7a Rename resolveTarget -> getRandomTarget
Should be much clearer in terms of what it's actually doing.
2020-01-18 15:43:34 -07:00

388 lines
12 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: 'gen4',
gen: 3,
init() {
for (let i in this.data.Pokedex) {
delete this.data.Pokedex[i].abilities['H'];
}
let specialTypes = ['Fire', 'Water', 'Grass', 'Ice', 'Electric', 'Dark', 'Psychic', 'Dragon'];
let newCategory = '';
for (let i in this.data.Movedex) {
if (!this.data.Movedex[i]) console.log(i);
if (this.data.Movedex[i].category === 'Status') continue;
newCategory = specialTypes.includes(this.data.Movedex[i].type) ? 'Special' : 'Physical';
if (newCategory !== this.data.Movedex[i].category) {
this.modData('Movedex', i).category = newCategory;
}
}
},
useMoveInner(moveOrMoveName, pokemon, target, sourceEffect, zMove) {
if (!sourceEffect && this.effect.id) sourceEffect = this.effect;
if (sourceEffect && sourceEffect.id === 'instruct') sourceEffect = null;
let move = this.dex.getActiveMove(moveOrMoveName);
if (this.activeMove) {
move.priority = this.activeMove.priority;
}
const baseTarget = move.target;
if (target === undefined) target = this.getRandomTarget(pokemon, move);
if (move.target === 'self' || move.target === 'allies') {
target = pokemon;
}
if (sourceEffect) {
move.sourceEffect = sourceEffect.id;
move.ignoreAbility = false;
}
let moveResult = false;
this.setActiveMove(move, pokemon, target);
this.singleEvent('ModifyMove', move, null, pokemon, target, move, move);
if (baseTarget !== move.target) {
// Target changed in ModifyMove, so we must adjust it here
// Adjust before the next event so the correct target is passed to the
// event
target = this.getRandomTarget(pokemon, move);
}
move = this.runEvent('ModifyMove', pokemon, target, move, move);
if (baseTarget !== move.target) {
// Adjust again
target = this.getRandomTarget(pokemon, move);
}
if (!move || pokemon.fainted) {
return false;
}
let attrs = '';
let movename = move.name;
if (move.id === 'hiddenpower') movename = 'Hidden Power';
if (sourceEffect) attrs += `|[from]${this.dex.getEffect(sourceEffect)}`;
this.addMove('move', pokemon, movename, target + attrs);
if (!target) {
this.attrLastMove('[notarget]');
this.add('-notarget', pokemon);
return false;
}
const {targets, pressureTargets} = pokemon.getMoveTargets(move, target);
if (!sourceEffect || sourceEffect.id === 'pursuit') {
let extraPP = 0;
for (const source of pressureTargets) {
const ppDrop = this.runEvent('DeductPP', source, pokemon, move);
if (ppDrop !== true) {
extraPP += ppDrop || 0;
}
}
if (extraPP > 0) {
pokemon.deductPP(move, extraPP);
}
}
if (!this.singleEvent('TryMove', move, null, pokemon, target, move) ||
!this.runEvent('TryMove', pokemon, target, move)) {
move.mindBlownRecoil = false;
return false;
}
this.singleEvent('UseMoveMessage', move, null, pokemon, target, move);
if (move.ignoreImmunity === undefined) {
move.ignoreImmunity = (move.category === 'Status');
}
if (move.selfdestruct === 'always') {
this.faint(pokemon, pokemon, move);
}
/** @type {number | false | undefined | ''} */
let damage = false;
if (move.target === 'all' || move.target === 'foeSide' || move.target === 'allySide' || move.target === 'allyTeam') {
damage = this.tryMoveHit(target, pokemon, move);
if (damage === this.NOT_FAIL) pokemon.moveThisTurnResult = null;
if (damage || damage === 0 || damage === undefined) moveResult = true;
} else if (move.target === 'allAdjacent' || move.target === 'allAdjacentFoes') {
if (!targets.length) {
this.attrLastMove('[notarget]');
this.add('-notarget', pokemon);
return false;
}
if (targets.length > 1) move.spreadHit = true;
const hitSlots = [];
for (const source of targets) {
const hitResult = this.tryMoveHit(source, pokemon, move);
if (hitResult || hitResult === 0 || hitResult === undefined) {
moveResult = true;
hitSlots.push(source.getSlot());
}
if (damage) {
damage += hitResult || 0;
} else {
if (damage !== false || hitResult !== this.NOT_FAIL) damage = hitResult;
}
if (damage === this.NOT_FAIL) pokemon.moveThisTurnResult = null;
}
if (move.spreadHit) this.attrLastMove('[spread] ' + hitSlots.join(','));
} else {
target = targets[0];
let lacksTarget = !target || target.fainted;
if (!lacksTarget) {
if (move.target === 'adjacentFoe' || move.target === 'adjacentAlly' || move.target === 'normal' || move.target === 'randomNormal') {
lacksTarget = !this.isAdjacent(target, pokemon);
}
}
if (lacksTarget && !move.isFutureMove) {
this.attrLastMove('[notarget]');
this.add('-notarget', pokemon);
return false;
}
damage = this.tryMoveHit(target, pokemon, move);
if (damage === this.NOT_FAIL) pokemon.moveThisTurnResult = null;
if (damage || damage === 0 || damage === undefined) moveResult = true;
}
if (move.selfBoost && moveResult) this.moveHit(pokemon, pokemon, move, move.selfBoost, false, true);
if (!pokemon.hp) {
this.faint(pokemon, pokemon, move);
}
if (!moveResult) {
this.singleEvent('MoveFail', move, null, target, pokemon, move);
return false;
}
if (!move.negateSecondary && !(move.hasSheerForce && pokemon.hasAbility('sheerforce'))) {
this.singleEvent('AfterMoveSecondarySelf', move, null, pokemon, target, move);
this.runEvent('AfterMoveSecondarySelf', pokemon, target, move);
}
return true;
},
tryMoveHit(target, pokemon, move) {
this.setActiveMove(move, pokemon, target);
let naturalImmunity = false;
let accPass = true;
let hitResult = this.singleEvent('PrepareHit', move, {}, target, pokemon, move);
if (!hitResult) {
if (hitResult === false) {
this.add('-fail', pokemon);
this.attrLastMove('[still]');
}
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', pokemon);
this.attrLastMove('[still]');
}
return false;
}
return this.moveHit(target, pokemon, move);
}
hitResult = this.runEvent('Invulnerability', target, pokemon, move);
if (hitResult === false) {
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)) {
naturalImmunity = true;
} else {
hitResult = this.singleEvent('TryImmunity', move, {}, target, pokemon, move);
if (hitResult === false) {
naturalImmunity = true;
}
}
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.dex.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.dex.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)) {
accPass = false;
}
if (accPass) {
hitResult = this.runEvent('TryHit', target, pokemon, move);
if (!hitResult) {
if (hitResult === false) {
this.add('-fail', pokemon);
this.attrLastMove('[still]');
}
return false;
} else if (naturalImmunity) {
this.add('-immune', target);
return false;
}
} else {
if (naturalImmunity) {
this.add('-immune', target);
} else {
if (!move.spreadHit) this.attrLastMove('[miss]');
this.add('-miss', pokemon, target);
}
return false;
}
move.totalDamage = 0;
/** @type {number | undefined | 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 | undefined | 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.dex.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.dex.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.dex.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 (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.dex.clampIntRange(Math.floor(damageDealt * move.recoil[0] / move.recoil[1]), 1);
},
};
exports.BattleScripts = BattleScripts;