mirror of
https://github.com/smogon/pokemon-showdown.git
synced 2026-03-21 17:25:10 -05:00
287 lines
8.3 KiB
TypeScript
287 lines
8.3 KiB
TypeScript
export const Moves: import('../../../sim/dex-moves').ModdedMoveDataTable = {
|
|
bide: {
|
|
inherit: true,
|
|
priority: 0,
|
|
accuracy: true,
|
|
condition: {
|
|
durationCallback(target, source, effect) {
|
|
return this.random(3, 5);
|
|
},
|
|
onStart(pokemon) {
|
|
this.effectState.totalDamage = 0;
|
|
this.effectState.lastDamage = 0;
|
|
this.add('-start', pokemon, 'Bide');
|
|
},
|
|
onHit(target, source, move) {
|
|
if (source && source !== target && move.category !== 'Physical' && move.category !== 'Special') {
|
|
const damage = this.effectState.totalDamage;
|
|
this.effectState.totalDamage += damage;
|
|
this.effectState.lastDamage = damage;
|
|
this.effectState.sourceSlot = source.getSlot();
|
|
}
|
|
},
|
|
onDamage(damage, target, source, move) {
|
|
if (!source || source.isAlly(target)) return;
|
|
if (!move || move.effectType !== 'Move') return;
|
|
if (!damage && this.effectState.lastDamage > 0) {
|
|
damage = this.effectState.totalDamage;
|
|
}
|
|
this.effectState.totalDamage += damage;
|
|
this.effectState.lastDamage = damage;
|
|
this.effectState.sourceSlot = source.getSlot();
|
|
},
|
|
onAfterSetStatus(status, pokemon) {
|
|
// Sleep, freeze, and partial trap will just pause duration.
|
|
if (pokemon.volatiles['flinch']) {
|
|
this.effectState.duration!++;
|
|
} else if (pokemon.volatiles['partiallytrapped']) {
|
|
this.effectState.duration!++;
|
|
} else {
|
|
switch (status.id) {
|
|
case 'slp':
|
|
case 'frz':
|
|
this.effectState.duration!++;
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
onBeforeMove(pokemon, t, move) {
|
|
if (this.effectState.duration === 1) {
|
|
this.add('-end', pokemon, 'Bide');
|
|
if (!this.effectState.totalDamage) {
|
|
this.debug("Bide failed because no damage was taken");
|
|
this.add('-fail', pokemon);
|
|
return false;
|
|
}
|
|
const target = this.getAtSlot(this.effectState.sourceSlot);
|
|
if (target.isSemiInvulnerable()) {
|
|
this.add('-message', 'The foe ' + target.name + ' can\'t be hit while flying!');
|
|
pokemon.removeVolatile('bide');
|
|
return false;
|
|
}
|
|
this.actions.moveHit(target, pokemon, move, { damage: this.effectState.totalDamage * 2 } as ActiveMove);
|
|
pokemon.removeVolatile('bide');
|
|
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,
|
|
// FIXME: onBeforeMove() {},
|
|
},
|
|
clamp: {
|
|
inherit: true,
|
|
// FIXME: onBeforeMove() {},
|
|
},
|
|
counter: {
|
|
inherit: true,
|
|
ignoreImmunity: true,
|
|
willCrit: false,
|
|
basePower: 1,
|
|
damageCallback(pokemon, target) {
|
|
// Counter mechanics in Stadium 1:
|
|
// - a move is Counterable if it is Normal or Fighting type, has nonzero Base Power, and is not Counter
|
|
// - Counter succeeds if the target used a Counterable move earlier this turn
|
|
|
|
const lastMoveThisTurn = target.side.lastMove && target.side.lastMove.id === target.side.lastSelectedMove &&
|
|
!this.queue.willMove(target) && this.dex.moves.get(target.side.lastMove.id);
|
|
if (!lastMoveThisTurn) {
|
|
this.debug("Stadium 1 Counter: last move was not this turn");
|
|
this.add('-fail', pokemon);
|
|
return false;
|
|
}
|
|
|
|
const lastMoveThisTurnIsCounterable = lastMoveThisTurn && lastMoveThisTurn.basePower > 0 &&
|
|
['Normal', 'Fighting'].includes(lastMoveThisTurn.type) && lastMoveThisTurn.id !== 'counter';
|
|
if (!lastMoveThisTurnIsCounterable) {
|
|
this.debug(`Stadium 1 Counter: last move ${lastMoveThisTurn.name} was not Counterable`);
|
|
this.add('-fail', pokemon);
|
|
return false;
|
|
}
|
|
if (this.lastDamage <= 0) {
|
|
this.debug("Stadium 1 Counter: no previous damage exists");
|
|
this.add('-fail', pokemon);
|
|
return false;
|
|
}
|
|
|
|
return 2 * this.lastDamage;
|
|
},
|
|
},
|
|
firespin: {
|
|
inherit: true,
|
|
// FIXME: onBeforeMove() {},
|
|
},
|
|
haze: {
|
|
inherit: true,
|
|
onHit(target, source) {
|
|
this.add('-activate', target, 'move: Haze');
|
|
this.add('-clearallboost', '[silent]');
|
|
for (const pokemon of this.getAllActive()) {
|
|
pokemon.clearBoosts();
|
|
pokemon.cureStatus(true);
|
|
for (const id of Object.keys(pokemon.volatiles)) {
|
|
pokemon.removeVolatile(id);
|
|
this.add('-end', pokemon, id, '[silent]');
|
|
}
|
|
pokemon.recalculateStats!();
|
|
}
|
|
},
|
|
},
|
|
hyperbeam: {
|
|
inherit: true,
|
|
onMoveFail(target, source, move) {
|
|
source.addVolatile('mustrecharge');
|
|
},
|
|
},
|
|
psywave: {
|
|
inherit: true,
|
|
basePower: 1,
|
|
damageCallback(pokemon) {
|
|
return this.random(1, this.trunc(1.5 * pokemon.level));
|
|
},
|
|
},
|
|
rage: {
|
|
inherit: true,
|
|
self: {
|
|
volatileStatus: 'rage',
|
|
},
|
|
condition: {
|
|
inherit: true,
|
|
// Rage lock
|
|
onStart(target, source, effect) {
|
|
this.effectState.move = 'rage';
|
|
},
|
|
onHit(target, source, move) {
|
|
if (target.boosts.atk < 6 && (move.category !== 'Status' || move.id === 'disable')) {
|
|
this.boost({ atk: 1 });
|
|
}
|
|
},
|
|
},
|
|
},
|
|
recover: {
|
|
inherit: true,
|
|
heal: null,
|
|
onHit(target) {
|
|
if (target.hp === target.maxhp) {
|
|
return false;
|
|
}
|
|
this.heal(Math.floor(target.maxhp / 2), target, target);
|
|
},
|
|
},
|
|
rest: {
|
|
inherit: true,
|
|
onHit(target, source, move) {
|
|
// Fails if the difference between
|
|
// max HP and current HP is 0, 255, or 511
|
|
if (target.hp >= target.maxhp) return false;
|
|
if (!target.setStatus('slp', source, move)) return false;
|
|
target.statusState.time = 2;
|
|
target.statusState.startTime = 2;
|
|
target.recalculateStats!(); // Stadium Rest removes statdrops given by Major Status Conditions.
|
|
this.heal(target.maxhp); // Aesthetic only as the healing happens after you fall asleep in-game
|
|
},
|
|
},
|
|
softboiled: {
|
|
inherit: true,
|
|
heal: null,
|
|
onHit(target) {
|
|
// Fail when health is 255 or 511 less than max
|
|
if (target.hp === target.maxhp) {
|
|
return false;
|
|
}
|
|
this.heal(Math.floor(target.maxhp / 2), target, target);
|
|
},
|
|
},
|
|
substitute: {
|
|
inherit: true,
|
|
onTryHit(target) {
|
|
if (target.volatiles['substitute']) {
|
|
this.add('-fail', target, 'move: Substitute');
|
|
return null;
|
|
}
|
|
// Stadium fixes the 25% = you die gag
|
|
if (target.hp <= target.maxhp / 4) {
|
|
this.add('-fail', target, 'move: Substitute', '[weak]');
|
|
return null;
|
|
}
|
|
},
|
|
condition: {
|
|
inherit: true,
|
|
onTryHit(target, source, move) {
|
|
if (target === source) {
|
|
this.debug('sub bypass: self hit');
|
|
return;
|
|
}
|
|
if (move.drain) {
|
|
this.add('-miss', source);
|
|
return null;
|
|
}
|
|
if (move.category === 'Status') {
|
|
const SubBlocked = ['leechseed', 'lockon', 'mindreader', 'nightmare'];
|
|
if (move.status || move.boosts || move.volatileStatus === 'confusion' || SubBlocked.includes(move.id)) {
|
|
this.add('-activate', target, 'Substitute', '[block] ' + move.name);
|
|
return null;
|
|
}
|
|
return;
|
|
}
|
|
if (move.volatileStatus && target === source) return;
|
|
let damage = this.actions.getDamage(source, target, move);
|
|
if (damage && damage > target.volatiles['substitute'].hp) {
|
|
damage = target.volatiles['substitute'].hp;
|
|
}
|
|
if (!damage && damage !== 0) return null;
|
|
target.volatiles['substitute'].hp -= damage;
|
|
this.lastDamage = damage;
|
|
if (target.volatiles['substitute'].hp <= 0) {
|
|
this.debug('Substitute broke');
|
|
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(this.clampIntRange(Math.floor(damage * move.recoil[0] / move.recoil[1]), 1), source, target, 'recoil');
|
|
}
|
|
}
|
|
this.runEvent('AfterSubDamage', target, source, move, damage);
|
|
// Add here counter damage
|
|
const lastAttackedBy = target.getLastAttackedBy();
|
|
if (!lastAttackedBy) {
|
|
target.attackedBy.push({ source, move: move.id, damage, slot: source.getSlot(), thisTurn: true });
|
|
} else {
|
|
lastAttackedBy.move = move.id;
|
|
lastAttackedBy.damage = damage;
|
|
}
|
|
return 0;
|
|
},
|
|
},
|
|
secondary: null,
|
|
target: "self",
|
|
type: "Normal",
|
|
},
|
|
struggle: {
|
|
inherit: true,
|
|
ignoreImmunity: { 'Normal': true },
|
|
},
|
|
wrap: {
|
|
inherit: true,
|
|
// FIXME: onBeforeMove() {},
|
|
},
|
|
};
|