mirror of
https://github.com/smogon/pokemon-showdown.git
synced 2026-03-21 17:25:10 -05:00
This adds some new `data/text/` files, which are the new home for item/move/ability descriptions, as well as in-game messages, in one place for ease of translation.
953 lines
22 KiB
TypeScript
953 lines
22 KiB
TypeScript
/**
|
|
* A lot of Gen 1 moves have to be updated due to different mechanics.
|
|
* Some moves have had major changes, such as Bite's typing.
|
|
*/
|
|
|
|
export const Moves: {[k: string]: ModdedMoveData} = {
|
|
absorb: {
|
|
inherit: true,
|
|
},
|
|
acid: {
|
|
inherit: true,
|
|
secondary: {
|
|
chance: 33,
|
|
boosts: {
|
|
def: -1,
|
|
},
|
|
},
|
|
target: "normal",
|
|
},
|
|
amnesia: {
|
|
inherit: true,
|
|
boosts: {
|
|
spd: 2,
|
|
spa: 2,
|
|
},
|
|
},
|
|
aurorabeam: {
|
|
inherit: true,
|
|
secondary: {
|
|
chance: 33,
|
|
boosts: {
|
|
atk: -1,
|
|
},
|
|
},
|
|
},
|
|
barrage: {
|
|
inherit: true,
|
|
},
|
|
bide: {
|
|
inherit: true,
|
|
priority: 0,
|
|
accuracy: true,
|
|
ignoreEvasion: true,
|
|
condition: {
|
|
duration: 2,
|
|
durationCallback(target, source, effect) {
|
|
return this.random(3, 4);
|
|
},
|
|
onStart(pokemon) {
|
|
this.effectData.totalDamage = 0;
|
|
this.effectData.lastDamage = 0;
|
|
this.add('-start', pokemon, 'Bide');
|
|
},
|
|
onHit(target, source, move) {
|
|
if (source && source !== target && move.category !== 'Physical' && move.category !== 'Special') {
|
|
const damage = this.effectData.totalDamage;
|
|
this.effectData.totalDamage += damage;
|
|
this.effectData.lastDamage = damage;
|
|
this.effectData.sourcePosition = source.position;
|
|
this.effectData.sourceSide = source.side;
|
|
}
|
|
},
|
|
onDamage(damage, target, source, move) {
|
|
if (!source || source.side === target.side) return;
|
|
if (!move || move.effectType !== 'Move') return;
|
|
if (!damage && this.effectData.lastDamage > 0) {
|
|
damage = this.effectData.totalDamage;
|
|
}
|
|
this.effectData.totalDamage += damage;
|
|
this.effectData.lastDamage = damage;
|
|
this.effectData.sourcePosition = source.position;
|
|
this.effectData.sourceSide = source.side;
|
|
},
|
|
onAfterSetStatus(status, pokemon) {
|
|
// Sleep, freeze, and partial trap will just pause duration.
|
|
if (pokemon.volatiles['flinch']) {
|
|
this.effectData.duration++;
|
|
} else if (pokemon.volatiles['partiallytrapped']) {
|
|
this.effectData.duration++;
|
|
} else {
|
|
switch (status.id) {
|
|
case 'slp':
|
|
case 'frz':
|
|
this.effectData.duration++;
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
onBeforeMove(pokemon, t, move) {
|
|
if (this.effectData.duration === 1) {
|
|
if (!this.effectData.totalDamage) {
|
|
this.debug("Bide failed due to 0 damage taken");
|
|
this.add('-fail', pokemon);
|
|
return false;
|
|
}
|
|
this.add('-end', pokemon, 'Bide');
|
|
const target = this.effectData.sourceSide.active[this.effectData.sourcePosition];
|
|
this.moveHit(target, pokemon, move, {damage: this.effectData.totalDamage * 2} as ActiveMove);
|
|
return false;
|
|
}
|
|
this.add('-activate', pokemon, 'Bide');
|
|
return false;
|
|
},
|
|
onDisableMove(pokemon) {
|
|
if (!pokemon.hasMove('bide')) {
|
|
return;
|
|
}
|
|
for (const moveSlot of pokemon.moveSlots) {
|
|
if (moveSlot.id !== 'bide') {
|
|
pokemon.disableMove(moveSlot.id);
|
|
}
|
|
}
|
|
},
|
|
},
|
|
type: "???", // Will look as Normal but it's STAB-less
|
|
},
|
|
bind: {
|
|
inherit: true,
|
|
ignoreImmunity: true,
|
|
volatileStatus: 'partiallytrapped',
|
|
self: {
|
|
volatileStatus: 'partialtrappinglock',
|
|
},
|
|
// FIXME: onBeforeMove(pokemon, target) {target.removeVolatile('mustrecharge')}
|
|
onHit(target, source) {
|
|
/**
|
|
* The duration of the partially trapped must be always renewed to 2
|
|
* so target doesn't move on trapper switch out as happens in gen 1.
|
|
* However, this won't happen if there's no switch and the trapper is
|
|
* about to end its partial trapping.
|
|
**/
|
|
if (target.volatiles['partiallytrapped']) {
|
|
if (source.volatiles['partialtrappinglock'] && source.volatiles['partialtrappinglock'].duration > 1) {
|
|
target.volatiles['partiallytrapped'].duration = 2;
|
|
}
|
|
}
|
|
},
|
|
},
|
|
bite: {
|
|
inherit: true,
|
|
secondary: {
|
|
chance: 10,
|
|
volatileStatus: 'flinch',
|
|
},
|
|
type: "Normal",
|
|
},
|
|
blizzard: {
|
|
inherit: true,
|
|
accuracy: 90,
|
|
target: "normal",
|
|
},
|
|
bonemerang: {
|
|
inherit: true,
|
|
},
|
|
bubble: {
|
|
inherit: true,
|
|
secondary: {
|
|
chance: 33,
|
|
boosts: {
|
|
spe: -1,
|
|
},
|
|
},
|
|
target: "normal",
|
|
},
|
|
bubblebeam: {
|
|
inherit: true,
|
|
secondary: {
|
|
chance: 33,
|
|
boosts: {
|
|
spe: -1,
|
|
},
|
|
},
|
|
},
|
|
clamp: {
|
|
inherit: true,
|
|
accuracy: 75,
|
|
pp: 10,
|
|
volatileStatus: 'partiallytrapped',
|
|
self: {
|
|
volatileStatus: 'partialtrappinglock',
|
|
},
|
|
// FIXME: onBeforeMove(pokemon, target) {target.removeVolatile('mustrecharge')}
|
|
onHit(target, source) {
|
|
/**
|
|
* The duration of the partially trapped must be always renewed to 2
|
|
* so target doesn't move on trapper switch out as happens in gen 1.
|
|
* However, this won't happen if there's no switch and the trapper is
|
|
* about to end its partial trapping.
|
|
**/
|
|
if (target.volatiles['partiallytrapped']) {
|
|
if (source.volatiles['partialtrappinglock'] && source.volatiles['partialtrappinglock'].duration > 1) {
|
|
target.volatiles['partiallytrapped'].duration = 2;
|
|
}
|
|
}
|
|
},
|
|
},
|
|
cometpunch: {
|
|
inherit: true,
|
|
},
|
|
constrict: {
|
|
inherit: true,
|
|
secondary: {
|
|
chance: 33,
|
|
boosts: {
|
|
spe: -1,
|
|
},
|
|
},
|
|
},
|
|
conversion: {
|
|
inherit: true,
|
|
volatileStatus: 'conversion',
|
|
accuracy: true,
|
|
target: "normal",
|
|
onHit(target, source) {
|
|
source.types = target.types;
|
|
this.add('-start', source, 'typechange', source.types.join(', '), '[from] move: Conversion', '[of] ' + source);
|
|
},
|
|
},
|
|
counter: {
|
|
inherit: true,
|
|
ignoreImmunity: true,
|
|
willCrit: false,
|
|
damageCallback(pokemon, target) {
|
|
// Counter mechanics on gen 1 might be hard to understand.
|
|
// It will fail if the last move selected by the opponent has base power 0 or is not Normal or Fighting Type.
|
|
// If both are true, counter will deal twice the last damage dealt in battle, no matter what was the move.
|
|
// That means that, if opponent switches, counter will use last counter damage * 2.
|
|
const lastUsedMove = target.side.lastMove && this.dex.getMove(target.side.lastMove.id);
|
|
if (
|
|
lastUsedMove && lastUsedMove.basePower > 0 && ['Normal', 'Fighting'].includes(lastUsedMove.type) &&
|
|
this.lastDamage > 0 && !this.queue.willMove(target)
|
|
) {
|
|
return 2 * this.lastDamage;
|
|
}
|
|
this.debug("Gen 1 Counter failed due to conditions not met");
|
|
this.add('-fail', pokemon);
|
|
return false;
|
|
},
|
|
},
|
|
crabhammer: {
|
|
inherit: true,
|
|
critRatio: 2,
|
|
},
|
|
defensecurl: {
|
|
inherit: true,
|
|
},
|
|
dig: {
|
|
inherit: true,
|
|
basePower: 100,
|
|
condition: {
|
|
duration: 2,
|
|
onLockMove: 'dig',
|
|
onInvulnerability(target, source, move) {
|
|
if (move.id === 'swift') return true;
|
|
this.add('-message', 'The foe ' + target.name + ' can\'t be hit underground!');
|
|
return false;
|
|
},
|
|
onDamage(damage, target, source, move) {
|
|
if (!move || move.effectType !== 'Move') return;
|
|
if (!source) return;
|
|
if (move.id === 'earthquake') {
|
|
this.add('-message', 'The foe ' + target.name + ' can\'t be hit underground!');
|
|
return null;
|
|
}
|
|
},
|
|
},
|
|
},
|
|
disable: {
|
|
inherit: true,
|
|
condition: {
|
|
duration: 4,
|
|
durationCallback(target, source, effect) {
|
|
const duration = this.random(1, 7);
|
|
return duration;
|
|
},
|
|
onStart(pokemon) {
|
|
if (!this.queue.willMove(pokemon)) {
|
|
this.effectData.duration++;
|
|
}
|
|
const moves = pokemon.moves;
|
|
const move = this.dex.getMove(this.sample(moves));
|
|
this.add('-start', pokemon, 'Disable', move.name);
|
|
this.effectData.move = move.id;
|
|
return;
|
|
},
|
|
onResidualOrder: 14,
|
|
onEnd(pokemon) {
|
|
this.add('-end', pokemon, 'Disable');
|
|
},
|
|
onBeforeMove(attacker, defender, move) {
|
|
if (move.id === this.effectData.move) {
|
|
this.add('cant', attacker, 'Disable', move);
|
|
return false;
|
|
}
|
|
},
|
|
onDisableMove(pokemon) {
|
|
for (const moveSlot of pokemon.moveSlots) {
|
|
if (moveSlot.id === this.effectData.move) {
|
|
pokemon.disableMove(moveSlot.id);
|
|
}
|
|
}
|
|
},
|
|
},
|
|
},
|
|
dizzypunch: {
|
|
inherit: true,
|
|
secondary: null,
|
|
},
|
|
doubleedge: {
|
|
inherit: true,
|
|
basePower: 100,
|
|
},
|
|
doublekick: {
|
|
inherit: true,
|
|
},
|
|
doubleslap: {
|
|
inherit: true,
|
|
},
|
|
dragonrage: {
|
|
inherit: true,
|
|
basePower: 1,
|
|
},
|
|
dreameater: {
|
|
inherit: true,
|
|
},
|
|
earthquake: {
|
|
inherit: true,
|
|
},
|
|
explosion: {
|
|
inherit: true,
|
|
basePower: 170,
|
|
target: "normal",
|
|
},
|
|
fireblast: {
|
|
inherit: true,
|
|
secondary: {
|
|
chance: 30,
|
|
status: 'brn',
|
|
},
|
|
},
|
|
firespin: {
|
|
inherit: true,
|
|
accuracy: 70,
|
|
basePower: 15,
|
|
volatileStatus: 'partiallytrapped',
|
|
self: {
|
|
volatileStatus: 'partialtrappinglock',
|
|
},
|
|
// FIXME: onBeforeMove(pokemon, target) {target.removeVolatile('mustrecharge')}
|
|
onHit(target, source) {
|
|
/**
|
|
* The duration of the partially trapped must be always renewed to 2
|
|
* so target doesn't move on trapper switch out as happens in gen 1.
|
|
* However, this won't happen if there's no switch and the trapper is
|
|
* about to end its partial trapping.
|
|
**/
|
|
if (target.volatiles['partiallytrapped']) {
|
|
if (source.volatiles['partialtrappinglock'] && source.volatiles['partialtrappinglock'].duration > 1) {
|
|
target.volatiles['partiallytrapped'].duration = 2;
|
|
}
|
|
}
|
|
},
|
|
},
|
|
fissure: {
|
|
inherit: true,
|
|
},
|
|
fly: {
|
|
inherit: true,
|
|
condition: {
|
|
duration: 2,
|
|
onLockMove: 'fly',
|
|
onInvulnerability(target, source, move) {
|
|
if (move.id === 'swift') return true;
|
|
this.add('-message', 'The foe ' + target.name + ' can\'t be hit while flying!');
|
|
return false;
|
|
},
|
|
onDamage(damage, target, source, move) {
|
|
if (!move || move.effectType !== 'Move') return;
|
|
if (!source || source.side === target.side) return;
|
|
if (move.id === 'gust' || move.id === 'thunder') {
|
|
this.add('-message', 'The foe ' + target.name + ' can\'t be hit while flying!');
|
|
return null;
|
|
}
|
|
},
|
|
},
|
|
},
|
|
focusenergy: {
|
|
inherit: true,
|
|
condition: {
|
|
onStart(pokemon) {
|
|
this.add('-start', pokemon, 'move: Focus Energy');
|
|
},
|
|
// This does nothing as it's dealt with on critical hit calculation.
|
|
onModifyMove() {},
|
|
},
|
|
},
|
|
furyattack: {
|
|
inherit: true,
|
|
},
|
|
furyswipes: {
|
|
inherit: true,
|
|
},
|
|
glare: {
|
|
inherit: true,
|
|
ignoreImmunity: true,
|
|
},
|
|
growth: {
|
|
inherit: true,
|
|
boosts: {
|
|
spa: 1,
|
|
spd: 1,
|
|
},
|
|
},
|
|
guillotine: {
|
|
inherit: true,
|
|
},
|
|
gust: {
|
|
inherit: true,
|
|
type: "Normal",
|
|
},
|
|
haze: {
|
|
inherit: true,
|
|
onHit(target, source) {
|
|
this.add('-clearallboost');
|
|
for (const pokemon of this.getAllActive()) {
|
|
pokemon.clearBoosts();
|
|
|
|
if (pokemon !== source) {
|
|
// Clears the status from the opponent
|
|
pokemon.setStatus('');
|
|
}
|
|
if (pokemon.status === 'tox') {
|
|
pokemon.setStatus('psn');
|
|
}
|
|
for (const id of Object.keys(pokemon.volatiles)) {
|
|
if (id === 'residualdmg') {
|
|
pokemon.volatiles[id].counter = 0;
|
|
} else {
|
|
pokemon.removeVolatile(id);
|
|
this.add('-end', pokemon, id);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
target: "self",
|
|
},
|
|
highjumpkick: {
|
|
inherit: true,
|
|
onMoveFail(target, source, move) {
|
|
if (!target.types.includes('Ghost')) {
|
|
this.directDamage(1, source, target);
|
|
}
|
|
},
|
|
},
|
|
horndrill: {
|
|
inherit: true,
|
|
},
|
|
hyperbeam: {
|
|
inherit: true,
|
|
},
|
|
jumpkick: {
|
|
inherit: true,
|
|
onMoveFail(target, source, move) {
|
|
if (!target.types.includes('Ghost')) {
|
|
this.directDamage(1, source, target);
|
|
}
|
|
},
|
|
},
|
|
karatechop: {
|
|
inherit: true,
|
|
critRatio: 2,
|
|
type: "Normal",
|
|
},
|
|
leechseed: {
|
|
inherit: true,
|
|
onHit() {},
|
|
condition: {
|
|
onStart(target) {
|
|
this.add('-start', target, 'move: Leech Seed');
|
|
},
|
|
onAfterMoveSelfPriority: 1,
|
|
onAfterMoveSelf(pokemon) {
|
|
const leecher = pokemon.side.foe.active[pokemon.volatiles['leechseed'].sourcePosition];
|
|
if (!leecher || leecher.fainted || leecher.hp <= 0) {
|
|
this.debug('Nothing to leech into');
|
|
return;
|
|
}
|
|
// We check if leeched Pokémon has Toxic to increase leeched damage.
|
|
let toxicCounter = 1;
|
|
const residualdmg = pokemon.volatiles['residualdmg'];
|
|
if (residualdmg) {
|
|
residualdmg.counter++;
|
|
toxicCounter = residualdmg.counter;
|
|
}
|
|
const toLeech = this.clampIntRange(Math.floor(pokemon.baseMaxhp / 16), 1) * toxicCounter;
|
|
const damage = this.damage(toLeech, pokemon, leecher);
|
|
if (residualdmg) this.hint("In Gen 1, Leech Seed's damage is affected by Toxic's counter.", true);
|
|
if (!damage || toLeech > damage) {
|
|
this.hint("In Gen 1, Leech Seed recovery is not limited by the remaining HP of the seeded Pokemon.", true);
|
|
}
|
|
this.heal(toLeech, leecher, pokemon);
|
|
},
|
|
},
|
|
},
|
|
lightscreen: {
|
|
num: 113,
|
|
accuracy: true,
|
|
basePower: 0,
|
|
category: "Status",
|
|
name: "Light Screen",
|
|
pp: 30,
|
|
priority: 0,
|
|
flags: {},
|
|
volatileStatus: 'lightscreen',
|
|
onTryHit(pokemon) {
|
|
if (pokemon.volatiles['lightscreen']) {
|
|
return false;
|
|
}
|
|
},
|
|
condition: {
|
|
onStart(pokemon) {
|
|
this.add('-start', pokemon, 'Light Screen');
|
|
},
|
|
},
|
|
target: "self",
|
|
type: "Psychic",
|
|
},
|
|
metronome: {
|
|
inherit: true,
|
|
noMetronome: ["Metronome", "Struggle"],
|
|
secondary: null,
|
|
target: "self",
|
|
type: "Normal",
|
|
},
|
|
mimic: {
|
|
inherit: true,
|
|
onHit(target, source) {
|
|
const moveslot = source.moves.indexOf('mimic');
|
|
if (moveslot < 0) return false;
|
|
const moves = target.moves;
|
|
const moveid = this.sample(moves);
|
|
if (!moveid) return false;
|
|
const move = this.dex.getMove(moveid);
|
|
source.moveSlots[moveslot] = {
|
|
move: move.name,
|
|
id: move.id,
|
|
pp: source.moveSlots[moveslot].pp,
|
|
maxpp: move.pp * 8 / 5,
|
|
target: move.target,
|
|
disabled: false,
|
|
used: false,
|
|
virtual: true,
|
|
};
|
|
this.add('-start', source, 'Mimic', move.name);
|
|
},
|
|
},
|
|
minimize: {
|
|
inherit: true,
|
|
},
|
|
mirrormove: {
|
|
inherit: true,
|
|
onHit(pokemon) {
|
|
const foe = pokemon.side.foe.active[0];
|
|
if (!foe || !foe.lastMove || foe.lastMove.id === 'mirrormove') {
|
|
return false;
|
|
}
|
|
this.useMove(foe.lastMove.id, pokemon);
|
|
},
|
|
},
|
|
mist: {
|
|
inherit: true,
|
|
},
|
|
nightshade: {
|
|
inherit: true,
|
|
ignoreImmunity: true,
|
|
basePower: 1,
|
|
},
|
|
petaldance: {
|
|
inherit: true,
|
|
},
|
|
pinmissile: {
|
|
inherit: true,
|
|
},
|
|
poisonsting: {
|
|
inherit: true,
|
|
secondary: {
|
|
chance: 20,
|
|
status: 'psn',
|
|
},
|
|
},
|
|
psychic: {
|
|
inherit: true,
|
|
secondary: {
|
|
chance: 33,
|
|
boosts: {
|
|
spd: -1,
|
|
spa: -1,
|
|
},
|
|
},
|
|
},
|
|
psywave: {
|
|
inherit: true,
|
|
basePower: 1,
|
|
},
|
|
rage: {
|
|
inherit: true,
|
|
self: {
|
|
volatileStatus: 'rage',
|
|
},
|
|
condition: {
|
|
// Rage lock
|
|
duration: 255,
|
|
onStart(target, source, effect) {
|
|
this.effectData.move = 'rage';
|
|
},
|
|
onLockMove: 'rage',
|
|
onTryHit(target, source, move) {
|
|
if (target.boosts.atk < 6 && move.id === 'disable') {
|
|
this.boost({atk: 1});
|
|
}
|
|
},
|
|
onHit(target, source, move) {
|
|
if (target.boosts.atk < 6 && move.category !== 'Status') {
|
|
this.boost({atk: 1});
|
|
}
|
|
},
|
|
},
|
|
},
|
|
razorleaf: {
|
|
inherit: true,
|
|
critRatio: 2,
|
|
target: "normal",
|
|
},
|
|
razorwind: {
|
|
inherit: true,
|
|
critRatio: 1,
|
|
target: "normal",
|
|
},
|
|
recover: {
|
|
inherit: true,
|
|
heal: null,
|
|
onHit(target) {
|
|
// Fail when health is 255 or 511 less than max
|
|
if (target.hp === (target.maxhp - 255) || target.hp === (target.maxhp - 511) || target.hp === target.maxhp) {
|
|
this.hint("In Gen 1, recovery moves fail if (user's maximum HP - user's current HP + 1) is divisible by 256.");
|
|
return false;
|
|
}
|
|
this.heal(Math.floor(target.maxhp / 2), target, target);
|
|
},
|
|
},
|
|
reflect: {
|
|
num: 115,
|
|
accuracy: true,
|
|
basePower: 0,
|
|
category: "Status",
|
|
name: "Reflect",
|
|
pp: 20,
|
|
priority: 0,
|
|
flags: {},
|
|
volatileStatus: 'reflect',
|
|
onTryHit(pokemon) {
|
|
if (pokemon.volatiles['reflect']) {
|
|
return false;
|
|
}
|
|
},
|
|
condition: {
|
|
onStart(pokemon) {
|
|
this.add('-start', pokemon, 'Reflect');
|
|
},
|
|
},
|
|
secondary: null,
|
|
target: "self",
|
|
type: "Psychic",
|
|
},
|
|
rest: {
|
|
inherit: true,
|
|
onTryMove() {},
|
|
onHit(target, source, move) {
|
|
if (target.hp === target.maxhp) return false;
|
|
// Fail when health is 255 or 511 less than max
|
|
if (target.hp === (target.maxhp - 255) || target.hp === (target.maxhp - 511)) {
|
|
this.hint("In Gen 1, recovery moves fail if (user's maximum HP - user's current HP + 1) is divisible by 256.");
|
|
return false;
|
|
}
|
|
if (!target.setStatus('slp', source, move)) return false;
|
|
target.statusData.time = 2;
|
|
target.statusData.startTime = 2;
|
|
this.heal(target.maxhp); // Aesthetic only as the healing happens after you fall asleep in-game
|
|
},
|
|
},
|
|
roar: {
|
|
inherit: true,
|
|
forceSwitch: false,
|
|
onTryHit() {},
|
|
priority: 0,
|
|
},
|
|
rockslide: {
|
|
inherit: true,
|
|
secondary: null,
|
|
target: "normal",
|
|
},
|
|
rockthrow: {
|
|
inherit: true,
|
|
accuracy: 65,
|
|
},
|
|
sandattack: {
|
|
inherit: true,
|
|
ignoreImmunity: true,
|
|
type: "Normal",
|
|
},
|
|
seismictoss: {
|
|
inherit: true,
|
|
ignoreImmunity: true,
|
|
basePower: 1,
|
|
},
|
|
selfdestruct: {
|
|
inherit: true,
|
|
basePower: 130,
|
|
target: "normal",
|
|
},
|
|
skullbash: {
|
|
inherit: true,
|
|
onTryMove(attacker, defender, move) {
|
|
if (attacker.removeVolatile(move.id)) {
|
|
return;
|
|
}
|
|
this.add('-prepare', attacker, move.name);
|
|
if (!this.runEvent('ChargeMove', attacker, defender, move)) {
|
|
return;
|
|
}
|
|
attacker.addVolatile('twoturnmove', defender);
|
|
return null;
|
|
},
|
|
},
|
|
slash: {
|
|
inherit: true,
|
|
critRatio: 2,
|
|
},
|
|
sludge: {
|
|
inherit: true,
|
|
secondary: {
|
|
chance: 40,
|
|
status: 'psn',
|
|
},
|
|
},
|
|
softboiled: {
|
|
inherit: true,
|
|
heal: null,
|
|
onHit(target) {
|
|
// Fail when health is 255 or 511 less than max
|
|
if (target.hp === (target.maxhp - 255) || target.hp === (target.maxhp - 511) || target.hp === target.maxhp) {
|
|
this.hint("In Gen 1, recovery moves fail if (user's maximum HP - user's current HP + 1) is divisible by 256.");
|
|
return false;
|
|
}
|
|
this.heal(Math.floor(target.maxhp / 2), target, target);
|
|
},
|
|
},
|
|
solarbeam: {
|
|
inherit: true,
|
|
},
|
|
sonicboom: {
|
|
inherit: true,
|
|
},
|
|
spikecannon: {
|
|
inherit: true,
|
|
},
|
|
stomp: {
|
|
inherit: true,
|
|
},
|
|
struggle: {
|
|
inherit: true,
|
|
pp: 10,
|
|
recoil: [1, 2],
|
|
onModifyMove() {},
|
|
},
|
|
stunspore: {
|
|
inherit: true,
|
|
},
|
|
submission: {
|
|
inherit: true,
|
|
},
|
|
substitute: {
|
|
num: 164,
|
|
accuracy: true,
|
|
basePower: 0,
|
|
category: "Status",
|
|
name: "Substitute",
|
|
pp: 10,
|
|
priority: 0,
|
|
volatileStatus: 'substitute',
|
|
onTryHit(target) {
|
|
if (target.volatiles['substitute']) {
|
|
this.add('-fail', target, 'move: Substitute');
|
|
return null;
|
|
}
|
|
// We only prevent when hp is less than one quarter.
|
|
// If you use substitute at exactly one quarter, you faint.
|
|
if (target.hp === target.maxhp / 4) target.faint();
|
|
if (target.hp < target.maxhp / 4) {
|
|
this.add('-fail', target, 'move: Substitute', '[weak]');
|
|
return null;
|
|
}
|
|
},
|
|
onHit(target) {
|
|
// If max HP is 3 or less substitute makes no damage
|
|
if (target.maxhp > 3) {
|
|
this.directDamage(target.maxhp / 4, target, target);
|
|
}
|
|
},
|
|
condition: {
|
|
onStart(target) {
|
|
this.add('-start', target, 'Substitute');
|
|
this.effectData.hp = Math.floor(target.maxhp / 4) + 1;
|
|
delete target.volatiles['partiallytrapped'];
|
|
},
|
|
onTryHitPriority: -1,
|
|
onTryHit(target, source, move) {
|
|
if (move.category === 'Status') {
|
|
// In gen 1 it only blocks:
|
|
// poison, confusion, secondary effect confusion, stat reducing moves and Leech Seed.
|
|
const SubBlocked = ['lockon', 'meanlook', 'mindreader', 'nightmare'];
|
|
if (
|
|
move.status === 'psn' || move.status === 'tox' || (move.boosts && target !== source) ||
|
|
move.volatileStatus === 'confusion' || SubBlocked.includes(move.id)
|
|
) {
|
|
return false;
|
|
}
|
|
return;
|
|
}
|
|
if (move.volatileStatus && target === source) return;
|
|
// NOTE: In future generations the damage is capped to the remaining HP of the
|
|
// Substitute, here we deliberately use the uncapped damage when tracking lastDamage etc.
|
|
// Also, multi-hit moves must always deal the same damage as the first hit for any subsequent hits
|
|
let uncappedDamage = move.hit > 1 ? source.lastDamage : this.getDamage(source, target, move);
|
|
if (!uncappedDamage) return null;
|
|
uncappedDamage = this.runEvent('SubDamage', target, source, move, uncappedDamage);
|
|
if (!uncappedDamage) return uncappedDamage;
|
|
source.lastDamage = uncappedDamage;
|
|
target.volatiles['substitute'].hp -= uncappedDamage > target.volatiles['substitute'].hp ?
|
|
target.volatiles['substitute'].hp : uncappedDamage;
|
|
if (target.volatiles['substitute'].hp <= 0) {
|
|
target.removeVolatile('substitute');
|
|
target.subFainted = true;
|
|
} else {
|
|
this.add('-activate', target, 'Substitute', '[damage]');
|
|
}
|
|
// Drain/recoil does not happen if the substitute breaks
|
|
if (target.volatiles['substitute']) {
|
|
if (move.recoil) {
|
|
this.damage(Math.round(uncappedDamage * move.recoil[0] / move.recoil[1]), source, target, 'recoil');
|
|
}
|
|
if (move.drain) {
|
|
this.heal(Math.ceil(uncappedDamage * move.drain[0] / move.drain[1]), source, target, 'drain');
|
|
}
|
|
}
|
|
this.runEvent('AfterSubDamage', target, source, move, uncappedDamage);
|
|
// Add here counter damage
|
|
const lastAttackedBy = target.getLastAttackedBy();
|
|
if (!lastAttackedBy) {
|
|
target.attackedBy.push({source: source, move: move.id, damage: uncappedDamage, thisTurn: true});
|
|
} else {
|
|
lastAttackedBy.move = move.id;
|
|
lastAttackedBy.damage = uncappedDamage;
|
|
}
|
|
return 0;
|
|
},
|
|
onEnd(target) {
|
|
this.add('-end', target, 'Substitute');
|
|
},
|
|
},
|
|
secondary: null,
|
|
target: "self",
|
|
type: "Normal",
|
|
flags: {},
|
|
},
|
|
superfang: {
|
|
inherit: true,
|
|
ignoreImmunity: true,
|
|
basePower: 1,
|
|
},
|
|
swift: {
|
|
inherit: true,
|
|
},
|
|
takedown: {
|
|
inherit: true,
|
|
},
|
|
thrash: {
|
|
inherit: true,
|
|
},
|
|
thunder: {
|
|
inherit: true,
|
|
secondary: {
|
|
chance: 10,
|
|
status: 'par',
|
|
},
|
|
},
|
|
thunderwave: {
|
|
inherit: true,
|
|
accuracy: 100,
|
|
onTryHit(target) {
|
|
if (target.hasType('Ground')) {
|
|
this.add('-immune', target);
|
|
return null;
|
|
}
|
|
},
|
|
},
|
|
transform: {
|
|
inherit: true,
|
|
},
|
|
triattack: {
|
|
inherit: true,
|
|
onHit() {},
|
|
secondary: null,
|
|
},
|
|
twineedle: {
|
|
inherit: true,
|
|
},
|
|
whirlwind: {
|
|
inherit: true,
|
|
accuracy: 85,
|
|
forceSwitch: false,
|
|
onTryHit() {},
|
|
priority: 0,
|
|
},
|
|
wingattack: {
|
|
inherit: true,
|
|
basePower: 35,
|
|
},
|
|
wrap: {
|
|
inherit: true,
|
|
accuracy: 85,
|
|
ignoreImmunity: true,
|
|
volatileStatus: 'partiallytrapped',
|
|
self: {
|
|
volatileStatus: 'partialtrappinglock',
|
|
},
|
|
// FIXME: onBeforeMove(pokemon, target) {target.removeVolatile('mustrecharge')}
|
|
onHit(target, source) {
|
|
/**
|
|
* The duration of the partially trapped must be always renewed to 2
|
|
* so target doesn't move on trapper switch out as happens in gen 1.
|
|
* However, this won't happen if there's no switch and the trapper is
|
|
* about to end its partial trapping.
|
|
**/
|
|
if (target.volatiles['partiallytrapped']) {
|
|
if (source.volatiles['partialtrappinglock'] && source.volatiles['partialtrappinglock'].duration > 1) {
|
|
target.volatiles['partiallytrapped'].duration = 2;
|
|
}
|
|
}
|
|
},
|
|
},
|
|
};
|