Jofthomas's picture
Jofthomas HF staff
Upload 4781 files
5c2ed06 verified
export const Moves: import('../../../sim/dex-moves').ModdedMoveDataTable = {
acupressure: {
inherit: true,
flags: { snatch: 1, metronome: 1 },
onHit(target) {
if (target.volatiles['substitute']) {
return false;
}
const stats: BoostID[] = [];
let stat: BoostID;
for (stat in target.boosts) {
if (target.boosts[stat] < 6) {
stats.push(stat);
}
}
if (stats.length) {
const randomStat = this.sample(stats);
const boost: SparseBoostsTable = {};
boost[randomStat] = 2;
this.boost(boost);
} else {
return false;
}
},
},
aromatherapy: {
inherit: true,
onHit(target, source) {
this.add('-cureteam', source, '[from] move: Aromatherapy');
const allies = [...target.side.pokemon, ...target.side.allySide?.pokemon || []];
for (const ally of allies) {
ally.clearStatus();
}
},
},
aquaring: {
inherit: true,
flags: { metronome: 1 },
condition: {
onStart(pokemon) {
this.add('-start', pokemon, 'Aqua Ring');
},
onResidualOrder: 10,
onResidualSubOrder: 2,
onResidual(pokemon) {
this.heal(pokemon.baseMaxhp / 16);
},
},
},
assist: {
inherit: true,
onHit(target) {
const moves = [];
for (const pokemon of target.side.pokemon) {
if (pokemon === target) continue;
for (const moveSlot of pokemon.moveSlots) {
const moveid = moveSlot.id;
const move = this.dex.moves.get(moveid);
if (
move.flags['noassist'] ||
(this.field.pseudoWeather['gravity'] && move.flags['gravity']) ||
(target.volatiles['healblock'] && move.flags['heal'])
) {
continue;
}
moves.push(moveid);
}
}
let randomMove = '';
if (moves.length) randomMove = this.sample(moves);
if (!randomMove) {
return false;
}
this.actions.useMove(randomMove, target);
},
},
beatup: {
inherit: true,
basePower: 10,
basePowerCallback(pokemon, target, move) {
if (!move.allies?.length) return null;
return 10;
},
onModifyMove(move, pokemon) {
pokemon.addVolatile('beatup');
move.type = '???';
move.category = 'Physical';
move.allies = pokemon.side.pokemon.filter(ally => !ally.fainted && !ally.status);
move.multihit = move.allies.length;
},
condition: {
duration: 1,
onModifyAtkPriority: -101,
onModifyAtk(atk, pokemon, defender, move) {
// https://www.smogon.com/forums/posts/8992145/
// this.add('-activate', pokemon, 'move: Beat Up', '[of] ' + move.allies![0].name);
this.event.modifier = 1;
return move.allies!.shift()!.species.baseStats.atk;
},
onFoeModifyDefPriority: -101,
onFoeModifyDef(def, pokemon) {
this.event.modifier = 1;
return pokemon.species.baseStats.def;
},
},
},
bide: {
inherit: true,
condition: {
duration: 3,
onLockMove: 'bide',
onStart(pokemon) {
this.effectState.totalDamage = 0;
this.add('-start', pokemon, 'move: Bide');
},
onDamagePriority: -101,
onDamage(damage, target, source, move) {
if (!move || move.effectType !== 'Move' || !source) return;
this.effectState.totalDamage += damage;
this.effectState.lastDamageSource = source;
},
onAfterSetStatus(status, pokemon) {
if (status.id === 'slp' || status.id === 'frz') {
pokemon.removeVolatile('bide');
}
},
onBeforeMove(pokemon, target, move) {
if (this.effectState.duration === 1) {
this.add('-end', pokemon, 'move: Bide');
if (!this.effectState.totalDamage) {
this.add('-fail', pokemon);
return false;
}
target = this.effectState.lastDamageSource;
if (!target) {
this.add('-fail', pokemon);
return false;
}
if (!target.isActive) {
const possibleTarget = this.getRandomTarget(pokemon, this.dex.moves.get('pound'));
if (!possibleTarget) {
this.add('-miss', pokemon);
return false;
}
target = possibleTarget;
}
const moveData = {
id: 'bide',
name: "Bide",
accuracy: true,
damage: this.effectState.totalDamage * 2,
category: "Physical",
priority: 1,
flags: { contact: 1, protect: 1 },
ignoreImmunity: true,
effectType: 'Move',
type: 'Normal',
} as unknown as ActiveMove;
this.actions.tryMoveHit(target, pokemon, moveData);
pokemon.removeVolatile('bide');
return false;
}
this.add('-activate', pokemon, 'move: Bide');
},
onMoveAborted(pokemon) {
pokemon.removeVolatile('bide');
},
onEnd(pokemon) {
this.add('-end', pokemon, 'move: Bide', '[silent]');
},
},
},
bind: {
inherit: true,
accuracy: 75,
},
bonerush: {
inherit: true,
accuracy: 80,
},
bravebird: {
inherit: true,
recoil: [1, 3],
},
bulletseed: {
inherit: true,
basePower: 10,
},
camouflage: {
inherit: true,
onHit(target) {
if (target.hasType('Normal') || !target.setType('Normal')) return false;
this.add('-start', target, 'typechange', 'Normal');
},
},
chatter: {
inherit: true,
secondary: {
chance: 31,
volatileStatus: 'confusion',
},
},
clamp: {
inherit: true,
accuracy: 75,
pp: 10,
},
conversion: {
inherit: true,
flags: { metronome: 1 },
onHit(target) {
const possibleTypes = target.moveSlots.map(moveSlot => {
const move = this.dex.moves.get(moveSlot.id);
if (move.id !== 'conversion' && move.id !== 'curse' && !target.hasType(move.type)) {
return move.type;
}
return '';
}).filter(type => type);
if (!possibleTypes.length) {
return false;
}
const type = this.sample(possibleTypes);
if (!target.setType(type)) return false;
this.add('-start', target, 'typechange', type);
},
},
copycat: {
inherit: true,
onHit(pokemon) {
const move: Move | ActiveMove | null = this.lastMove;
if (!move) return;
if (
move.flags['failcopycat'] ||
(this.field.pseudoWeather['gravity'] && move.flags['gravity']) ||
(pokemon.volatiles['healblock'] && move.flags['heal'])
) {
return false;
}
this.actions.useMove(move.id, pokemon);
},
},
cottonspore: {
inherit: true,
accuracy: 85,
},
covet: {
inherit: true,
basePower: 40,
},
crabhammer: {
inherit: true,
accuracy: 85,
},
crushgrip: {
inherit: true,
basePowerCallback(pokemon, target) {
const bp = Math.floor(target.hp * 120 / target.maxhp) + 1;
this.debug(`BP for ${target.hp}/${target.maxhp} HP: ${bp}`);
return bp;
},
},
curse: {
inherit: true,
flags: { metronome: 1 },
onModifyMove(move, source, target) {
if (!source.hasType('Ghost')) {
delete move.volatileStatus;
delete move.onHit;
move.self = { boosts: { atk: 1, def: 1, spe: -1 } };
move.target = move.nonGhostTarget!;
} else if (target?.volatiles['substitute']) {
delete move.volatileStatus;
delete move.onHit;
}
},
condition: {
onStart(pokemon, source) {
this.add('-start', pokemon, 'Curse', `[of] ${source}`);
},
onResidualOrder: 10,
onResidualSubOrder: 8,
onResidual(pokemon) {
this.damage(pokemon.baseMaxhp / 4);
},
},
type: "???",
},
defog: {
inherit: true,
flags: { protect: 1, mirror: 1, bypasssub: 1, metronome: 1 },
},
detect: {
inherit: true,
priority: 3,
condition: {
duration: 1,
onStart(target) {
this.add('-singleturn', target, 'Protect');
},
onTryHitPriority: 3,
onTryHit(target, source, move) {
if (!move.flags['protect']) return;
this.add('-activate', target, 'Protect');
const lockedmove = source.getVolatile('lockedmove');
if (lockedmove) {
// Outrage counter is NOT reset
if (source.volatiles['lockedmove'].trueDuration >= 2) {
source.volatiles['lockedmove'].duration = 2;
}
}
return null;
},
},
},
disable: {
inherit: true,
accuracy: 80,
flags: { protect: 1, mirror: 1, bypasssub: 1, metronome: 1 },
volatileStatus: 'disable',
condition: {
durationCallback() {
return this.random(4, 8);
},
noCopy: true,
onStart(pokemon) {
if (!this.queue.willMove(pokemon)) {
this.effectState.duration!++;
}
if (!pokemon.lastMove) {
return false;
}
for (const moveSlot of pokemon.moveSlots) {
if (moveSlot.id === pokemon.lastMove.id) {
if (!moveSlot.pp) {
return false;
} else {
this.add('-start', pokemon, 'Disable', moveSlot.move);
this.effectState.move = pokemon.lastMove.id;
return;
}
}
}
return false;
},
onResidualOrder: 10,
onResidualSubOrder: 13,
onEnd(pokemon) {
this.add('-end', pokemon, 'move: Disable');
},
onBeforeMovePriority: 7,
onBeforeMove(attacker, defender, move) {
if (move.id === this.effectState.move) {
this.add('cant', attacker, 'Disable', move);
return false;
}
},
onDisableMove(pokemon) {
for (const moveSlot of pokemon.moveSlots) {
if (moveSlot.id === this.effectState.move) {
pokemon.disableMove(moveSlot.id);
}
}
},
},
},
doomdesire: {
inherit: true,
accuracy: 85,
basePower: 120,
onTry(source, target) {
if (!target.side.addSlotCondition(target, 'futuremove')) return false;
const moveData = {
name: "Doom Desire",
basePower: 120,
category: "Special",
flags: { metronome: 1, futuremove: 1 },
willCrit: false,
type: '???',
} as unknown as ActiveMove;
const damage = this.actions.getDamage(source, target, moveData, true);
Object.assign(target.side.slotConditions[target.position]['futuremove'], {
duration: 3,
move: 'doomdesire',
source,
moveData: {
id: 'doomdesire',
name: "Doom Desire",
accuracy: 85,
basePower: 0,
damage,
category: "Special",
flags: { metronome: 1, futuremove: 1 },
effectType: 'Move',
type: '???',
},
});
this.add('-start', source, 'Doom Desire');
return null;
},
},
doubleedge: {
inherit: true,
recoil: [1, 3],
},
drainpunch: {
inherit: true,
basePower: 60,
pp: 5,
},
dreameater: {
inherit: true,
onTryImmunity(target) {
return target.status === 'slp' && !target.volatiles['substitute'];
},
},
embargo: {
inherit: true,
flags: { protect: 1, mirror: 1, metronome: 1 },
onTryHit(pokemon) {
if (pokemon.ability === 'multitype' || pokemon.item === 'griseousorb') {
return false;
}
},
condition: {
duration: 5,
onStart(pokemon) {
this.add('-start', pokemon, 'Embargo');
},
// Item suppression implemented in Pokemon.ignoringItem() within sim/pokemon.js
onResidualOrder: 10,
onResidualSubOrder: 18,
onEnd(pokemon) {
this.add('-end', pokemon, 'Embargo');
},
},
},
encore: {
inherit: true,
flags: { protect: 1, mirror: 1, bypasssub: 1, metronome: 1, failencore: 1 },
volatileStatus: 'encore',
condition: {
durationCallback() {
return this.random(4, 9);
},
onStart(target, source) {
const moveIndex = target.lastMove ? target.moves.indexOf(target.lastMove.id) : -1;
if (
!target.lastMove || target.lastMove.flags['failencore'] ||
!target.moveSlots[moveIndex] || target.moveSlots[moveIndex].pp <= 0
) {
// it failed
return false;
}
this.effectState.move = target.lastMove.id;
this.add('-start', target, 'Encore');
},
onOverrideAction(pokemon) {
return this.effectState.move;
},
onResidualOrder: 10,
onResidualSubOrder: 14,
onResidual(target) {
if (
target.moves.includes(this.effectState.move) &&
target.moveSlots[target.moves.indexOf(this.effectState.move)].pp <= 0
) {
// early termination if you run out of PP
target.removeVolatile('encore');
}
},
onEnd(target) {
this.add('-end', target, 'Encore');
},
onDisableMove(pokemon) {
if (!this.effectState.move || !pokemon.hasMove(this.effectState.move)) {
return;
}
for (const moveSlot of pokemon.moveSlots) {
if (moveSlot.id !== this.effectState.move) {
pokemon.disableMove(moveSlot.id);
}
}
},
},
},
endeavor: {
inherit: true,
onTry(pokemon, target) {
if (pokemon.hp >= target.hp) {
this.add('-fail', pokemon);
return null;
}
},
},
extremespeed: {
inherit: true,
priority: 1,
},
fakeout: {
inherit: true,
priority: 1,
},
feint: {
inherit: true,
basePower: 50,
onTry(source, target) {
if (!target.volatiles['protect']) {
this.add('-fail', source);
return null;
}
},
},
firespin: {
inherit: true,
accuracy: 70,
basePower: 15,
},
flail: {
inherit: true,
basePowerCallback(pokemon) {
const ratio = Math.max(Math.floor(pokemon.hp * 64 / pokemon.maxhp), 1);
let bp;
if (ratio < 2) {
bp = 200;
} else if (ratio < 6) {
bp = 150;
} else if (ratio < 13) {
bp = 100;
} else if (ratio < 22) {
bp = 80;
} else if (ratio < 43) {
bp = 40;
} else {
bp = 20;
}
this.debug(`BP: ${bp}`);
return bp;
},
},
flareblitz: {
inherit: true,
recoil: [1, 3],
},
fling: {
inherit: true,
onPrepareHit(target, source, move) {
if (source.ignoringItem()) return false;
if (source.hasAbility('multitype')) return false;
const item = source.getItem();
if (!this.singleEvent('TakeItem', item, source.itemState, source, source, move, item)) return false;
if (!item.fling) return false;
move.basePower = item.fling.basePower;
this.debug(`BP: ${move.basePower}`);
if (item.isBerry) {
move.onHit = function (foe) {
if (this.singleEvent('Eat', item, null, foe, null, null)) {
this.runEvent('EatItem', foe, null, null, item);
if (item.id === 'leppaberry') foe.staleness = 'external';
}
if (item.onEat) foe.ateBerry = true;
};
} else if (item.fling.effect) {
move.onHit = item.fling.effect;
} else {
if (!move.secondaries) move.secondaries = [];
if (item.fling.status) {
move.secondaries.push({ status: item.fling.status });
} else if (item.fling.volatileStatus) {
move.secondaries.push({ volatileStatus: item.fling.volatileStatus });
}
}
source.addVolatile('fling');
},
},
focuspunch: {
inherit: true,
priorityChargeCallback() {},
beforeTurnCallback(pokemon) {
pokemon.addVolatile('focuspunch');
},
beforeMoveCallback() {},
onTry(pokemon) {
if (pokemon.volatiles['focuspunch']?.lostFocus) {
this.attrLastMove('[still]');
this.add('cant', pokemon, 'Focus Punch', 'Focus Punch');
return null;
}
},
},
foresight: {
inherit: true,
flags: { protect: 1, mirror: 1, bypasssub: 1, metronome: 1 },
},
furycutter: {
inherit: true,
basePower: 10,
condition: {
duration: 2,
onStart() {
this.effectState.multiplier = 1;
},
onRestart() {
if (this.effectState.multiplier < 16) {
this.effectState.multiplier <<= 1;
}
this.effectState.duration = 2;
},
},
},
futuresight: {
inherit: true,
accuracy: 90,
basePower: 80,
pp: 15,
onTry(source, target) {
if (!target.side.addSlotCondition(target, 'futuremove')) return false;
const moveData = {
name: "Future Sight",
basePower: 80,
category: "Special",
flags: { metronome: 1, futuremove: 1 },
willCrit: false,
type: '???',
} as unknown as ActiveMove;
const damage = this.actions.getDamage(source, target, moveData, true);
Object.assign(target.side.slotConditions[target.position]['futuremove'], {
duration: 3,
move: 'futuresight',
source,
moveData: {
id: 'futuresight',
name: "Future Sight",
accuracy: 90,
basePower: 0,
damage,
category: "Special",
flags: { metronome: 1, futuremove: 1 },
effectType: 'Move',
type: '???',
},
});
this.add('-start', source, 'Future Sight');
return null;
},
},
gigadrain: {
inherit: true,
basePower: 60,
},
glare: {
inherit: true,
accuracy: 75,
},
gravity: {
inherit: true,
condition: {
duration: 5,
durationCallback(source, effect) {
if (source?.hasAbility('persistent')) {
this.add('-activate', source, 'ability: Persistent', '[move] Gravity');
return 7;
}
return 5;
},
onFieldStart(target, source) {
if (source?.hasAbility('persistent')) {
this.add('-fieldstart', 'move: Gravity', '[persistent]');
} else {
this.add('-fieldstart', 'move: Gravity');
}
for (const pokemon of this.getAllActive()) {
let applies = false;
if (pokemon.removeVolatile('bounce') || pokemon.removeVolatile('fly')) {
applies = true;
this.queue.cancelMove(pokemon);
pokemon.removeVolatile('twoturnmove');
}
if (pokemon.volatiles['skydrop']) {
applies = true;
this.queue.cancelMove(pokemon);
if (pokemon.volatiles['skydrop'].source) {
this.add('-end', pokemon.volatiles['twoturnmove'].source, 'Sky Drop', '[interrupt]');
}
pokemon.removeVolatile('skydrop');
pokemon.removeVolatile('twoturnmove');
}
if (pokemon.volatiles['magnetrise']) {
applies = true;
delete pokemon.volatiles['magnetrise'];
}
if (pokemon.volatiles['telekinesis']) {
applies = true;
delete pokemon.volatiles['telekinesis'];
}
if (applies) this.add('-activate', pokemon, 'move: Gravity');
}
},
onModifyAccuracy(accuracy) {
if (typeof accuracy !== 'number') return;
return this.chainModify([6840, 4096]);
},
onDisableMove(pokemon) {
for (const moveSlot of pokemon.moveSlots) {
if (this.dex.moves.get(moveSlot.id).flags['gravity']) {
pokemon.disableMove(moveSlot.id);
}
}
},
// groundedness implemented in battle.engine.js:BattlePokemon#isGrounded
onBeforeMovePriority: 6,
onBeforeMove(pokemon, target, move) {
if (move.flags['gravity'] && !move.isZ) {
this.add('cant', pokemon, 'move: Gravity', move);
return false;
}
},
onModifyMove(move, pokemon, target) {
if (move.flags['gravity'] && !move.isZ) {
this.add('cant', pokemon, 'move: Gravity', move);
return false;
}
},
onFieldResidualOrder: 9,
onFieldEnd() {
this.add('-fieldend', 'move: Gravity');
},
},
},
growth: {
inherit: true,
onModifyMove() {},
boosts: {
spa: 1,
},
},
healbell: {
inherit: true,
onHit(target, source) {
this.add('-activate', source, 'move: Heal Bell');
const allies = [...target.side.pokemon, ...target.side.allySide?.pokemon || []];
for (const ally of allies) {
if (ally.hasAbility('soundproof')) {
if (ally.isActive) this.add('-immune', ally, '[from] ability: Soundproof');
continue;
}
ally.cureStatus(true);
}
},
},
healblock: {
inherit: true,
flags: { protect: 1, mirror: 1, metronome: 1 },
condition: {
duration: 5,
durationCallback(target, source, effect) {
if (source?.hasAbility('persistent')) {
this.add('-activate', source, 'ability: Persistent', '[move] Heal Block');
return 7;
}
return 5;
},
onStart(pokemon) {
this.add('-start', pokemon, 'move: Heal Block');
},
onDisableMove(pokemon) {
for (const moveSlot of pokemon.moveSlots) {
if (this.dex.moves.get(moveSlot.id).flags['heal']) {
pokemon.disableMove(moveSlot.id);
}
}
},
onBeforeMovePriority: 6,
onBeforeMove(pokemon, target, move) {
if (move.flags['heal']) {
this.add('cant', pokemon, 'move: Heal Block', move);
return false;
}
},
onResidualOrder: 10,
onResidualSubOrder: 17,
onEnd(pokemon) {
this.add('-end', pokemon, 'move: Heal Block');
},
onTryHeal(damage, pokemon, source, effect) {
if (effect && (effect.id === 'drain' || effect.id === 'leechseed' || effect.id === 'wish')) {
return false;
}
},
},
},
healingwish: {
inherit: true,
flags: { heal: 1, metronome: 1 },
onAfterMove(pokemon) {
pokemon.switchFlag = true;
},
condition: {
duration: 1,
onSwitchInPriority: -1,
onSwitchIn(target) {
if (target.hp > 0) {
target.heal(target.maxhp);
target.clearStatus();
this.add('-heal', target, target.getHealth, '[from] move: Healing Wish');
target.side.removeSlotCondition(target, 'healingwish');
target.lastMove = this.lastMove;
} else {
target.switchFlag = true;
}
},
},
},
highjumpkick: {
inherit: true,
basePower: 100,
pp: 20,
onMoveFail(target, source, move) {
move.causedCrashDamage = true;
let damage = this.actions.getDamage(source, target, move, true);
if (!damage) damage = target.maxhp;
this.damage(this.clampIntRange(damage / 2, 1, Math.floor(target.maxhp / 2)), source, source, move);
},
},
iciclespear: {
inherit: true,
basePower: 10,
},
imprison: {
inherit: true,
flags: { bypasssub: 1, metronome: 1 },
onTryHit(pokemon) {
for (const target of pokemon.foes()) {
for (const move of pokemon.moves) {
if (target.moves.includes(move)) return;
}
}
return false;
},
},
ingrain: {
inherit: true,
condition: {
onStart(pokemon) {
this.add('-start', pokemon, 'move: Ingrain');
},
onResidualOrder: 10,
onResidualSubOrder: 1,
onResidual(pokemon) {
this.heal(pokemon.baseMaxhp / 16);
},
onTrapPokemon(pokemon) {
pokemon.tryTrap();
},
// groundedness implemented in battle.engine.js:BattlePokemon#isGrounded
onDragOut(pokemon) {
this.add('-activate', pokemon, 'move: Ingrain');
return null;
},
},
},
jumpkick: {
inherit: true,
basePower: 85,
pp: 25,
onMoveFail(target, source, move) {
move.causedCrashDamage = true;
let damage = this.actions.getDamage(source, target, move, true);
if (!damage) damage = target.maxhp;
this.damage(this.clampIntRange(damage / 2, 1, Math.floor(target.maxhp / 2)), source, source, move);
},
},
knockoff: {
inherit: true,
onAfterHit(target, source, move) {
if (!target.item || target.itemState.knockedOff) return;
if (target.ability === 'multitype') return;
const item = target.getItem();
if (this.runEvent('TakeItem', target, source, move, item)) {
target.itemState.knockedOff = true;
this.add('-enditem', target, item.name, '[from] move: Knock Off', `[of] ${source}`);
this.hint("In Gens 3-4, Knock Off only makes the target's item unusable; it cannot obtain a new item.", true);
}
},
},
lastresort: {
inherit: true,
basePower: 130,
},
leechseed: {
inherit: true,
condition: {
onStart(target) {
this.add('-start', target, 'move: Leech Seed');
},
onResidualOrder: 10,
onResidualSubOrder: 5,
onResidual(pokemon) {
const target = this.getAtSlot(pokemon.volatiles['leechseed'].sourceSlot);
if (!target || target.fainted || target.hp <= 0) {
this.debug('Nothing to leech into');
return;
}
const damage = this.damage(pokemon.baseMaxhp / 8, pokemon, target);
if (damage) {
this.heal(damage, target, pokemon);
}
},
},
},
lightscreen: {
inherit: true,
condition: {
duration: 5,
durationCallback(target, source, effect) {
if (source?.hasItem('lightclay')) {
return 8;
}
return 5;
},
onAnyModifyDamagePhase1(damage, source, target, move) {
if (target !== source && this.effectState.target.hasAlly(target) && this.getCategory(move) === 'Special') {
if (!target.getMoveHitData(move).crit && !move.infiltrates) {
this.debug('Light Screen weaken');
if (target.alliesAndSelf().length > 1) return this.chainModify(2, 3);
return this.chainModify(0.5);
}
}
},
onSideStart(side) {
this.add('-sidestart', side, 'Light Screen');
},
onSideResidualOrder: 2,
onSideEnd(side) {
this.add('-sideend', side, 'Light Screen');
},
},
},
lockon: {
inherit: true,
condition: {
duration: 2,
onSourceInvulnerabilityPriority: 1,
onSourceInvulnerability(target, source, move) {
if (move && source === this.effectState.target && target === this.effectState.source) return 0;
},
onSourceAccuracy(accuracy, target, source, move) {
if (move && source === this.effectState.target && target === this.effectState.source) return true;
},
},
},
luckychant: {
inherit: true,
flags: { metronome: 1 },
condition: {
duration: 5,
onSideStart(side) {
this.add('-sidestart', side, 'move: Lucky Chant');
},
onCriticalHit: false,
onSideResidualOrder: 6,
onSideEnd(side) {
this.add('-sideend', side, 'move: Lucky Chant');
},
},
},
lunardance: {
inherit: true,
flags: { heal: 1, metronome: 1 },
onAfterMove(pokemon) {
pokemon.switchFlag = true;
},
condition: {
duration: 1,
onSideStart(side) {
this.debug('Lunar Dance started on ' + side.name);
},
onSwitchInPriority: -1,
onSwitchIn(target) {
if (target.getSlot() !== this.effectState.sourceSlot) {
return;
}
if (target.hp > 0) {
target.heal(target.maxhp);
target.clearStatus();
for (const moveSlot of target.moveSlots) {
moveSlot.pp = moveSlot.maxpp;
}
this.add('-heal', target, target.getHealth, '[from] move: Lunar Dance');
target.side.removeSlotCondition(target, 'lunardance');
target.lastMove = this.lastMove;
} else {
target.switchFlag = true;
}
},
},
},
magiccoat: {
inherit: true,
condition: {
duration: 1,
onTryHitPriority: 2,
onTryHit(target, source, move) {
if (target === source || move.hasBounced || !move.flags['reflectable']) {
return;
}
target.removeVolatile('magiccoat');
const newMove = this.dex.getActiveMove(move.id);
newMove.hasBounced = true;
this.actions.useMove(newMove, target, { target: source });
return null;
},
},
},
magmastorm: {
inherit: true,
accuracy: 70,
},
magnetrise: {
inherit: true,
flags: { gravity: 1, metronome: 1 },
volatileStatus: 'magnetrise',
condition: {
duration: 5,
onStart(target) {
if (target.volatiles['ingrain'] || target.ability === 'levitate') return false;
this.add('-start', target, 'Magnet Rise');
},
onImmunity(type) {
if (type === 'Ground') return false;
},
onResidualOrder: 10,
onResidualSubOrder: 16,
onEnd(target) {
this.add('-end', target, 'Magnet Rise');
},
},
},
mefirst: {
inherit: true,
condition: {
duration: 1,
onModifyDamagePhase2(damage) {
return damage * 1.5;
},
},
},
metalburst: {
inherit: true,
flags: { protect: 1, mirror: 1, metronome: 1 },
},
metronome: {
inherit: true,
flags: { noassist: 1, failcopycat: 1, nosleeptalk: 1, failmimic: 1 },
onHit(pokemon) {
const moves = this.dex.moves.all().filter(move => (
(![2, 4].includes(this.gen) || !pokemon.moves.includes(move.id)) &&
(!move.isNonstandard || move.isNonstandard === 'Unobtainable') &&
move.flags['metronome'] &&
!(this.field.pseudoWeather['gravity'] && move.flags['gravity']) &&
!(pokemon.volatiles['healblock'] && move.flags['heal'])
));
let randomMove = '';
if (moves.length) {
moves.sort((a, b) => a.num - b.num);
randomMove = this.sample(moves).id;
}
if (!randomMove) return false;
pokemon.side.lastSelectedMove = this.toID(randomMove);
this.actions.useMove(randomMove, pokemon);
},
},
mimic: {
inherit: true,
flags: {
protect: 1, allyanim: 1, noassist: 1, failcopycat: 1, failencore: 1, failinstruct: 1, failmimic: 1,
},
onHit(target, source) {
if (source.transformed || !target.lastMove || target.volatiles['substitute']) {
return false;
}
if (target.lastMove.flags['failmimic'] || source.moves.includes(target.lastMove.id)) {
return false;
}
const mimicIndex = source.moves.indexOf('mimic');
if (mimicIndex < 0) return false;
const move = this.dex.moves.get(target.lastMove.id);
source.moveSlots[mimicIndex] = {
move: move.name,
id: move.id,
pp: 5,
maxpp: move.pp * 8 / 5,
disabled: false,
used: false,
virtual: true,
};
this.add('-activate', source, 'move: Mimic', move.name);
},
},
minimize: {
inherit: true,
boosts: {
evasion: 1,
},
},
miracleeye: {
inherit: true,
flags: { protect: 1, mirror: 1, bypasssub: 1, metronome: 1 },
},
mirrormove: {
inherit: true,
onTryHit() {},
onHit(pokemon) {
const lastAttackedBy = pokemon.getLastAttackedBy();
if (!lastAttackedBy?.source.lastMove || !lastAttackedBy.move) {
return false;
}
const noMirror = [
'acupressure', 'aromatherapy', 'assist', 'chatter', 'copycat', 'counter', 'curse', 'doomdesire', 'feint', 'focuspunch', 'futuresight', 'gravity', 'hail', 'haze', 'healbell', 'helpinghand', 'lightscreen', 'luckychant', 'magiccoat', 'mefirst', 'metronome', 'mimic', 'mirrorcoat', 'mirrormove', 'mist', 'mudsport', 'naturepower', 'perishsong', 'psychup', 'raindance', 'reflect', 'roleplay', 'safeguard', 'sandstorm', 'sketch', 'sleeptalk', 'snatch', 'spikes', 'spitup', 'stealthrock', 'struggle', 'sunnyday', 'tailwind', 'toxicspikes', 'transform', 'watersport',
];
if (noMirror.includes(lastAttackedBy.move) || !lastAttackedBy.source.hasMove(lastAttackedBy.move)) {
return false;
}
this.actions.useMove(lastAttackedBy.move, pokemon);
},
target: "self",
},
mist: {
inherit: true,
condition: {
duration: 5,
onTryBoost(boost, target, source, effect) {
if (effect.effectType === 'Move' && effect.infiltrates && !target.isAlly(source)) return;
if (source && target !== source) {
let showMsg = false;
let i: BoostID;
for (i in boost) {
if (boost[i]! < 0) {
delete boost[i];
showMsg = true;
}
}
if (showMsg && !(effect as ActiveMove).secondaries) {
this.add('-activate', target, 'move: Mist');
}
}
},
onSideStart(side) {
this.add('-sidestart', side, 'Mist');
},
onSideResidualOrder: 3,
onSideEnd(side) {
this.add('-sideend', side, 'Mist');
},
},
},
moonlight: {
inherit: true,
onHit(pokemon) {
if (this.field.isWeather(['sunnyday', 'desolateland'])) {
this.heal(pokemon.maxhp * 2 / 3);
} else if (this.field.isWeather(['raindance', 'primordialsea', 'sandstorm', 'hail'])) {
this.heal(pokemon.baseMaxhp / 4);
} else {
this.heal(pokemon.baseMaxhp / 2);
}
},
},
morningsun: {
inherit: true,
onHit(pokemon) {
if (this.field.isWeather(['sunnyday', 'desolateland'])) {
this.heal(pokemon.maxhp * 2 / 3);
} else if (this.field.isWeather(['raindance', 'primordialsea', 'sandstorm', 'hail'])) {
this.heal(pokemon.baseMaxhp / 4);
} else {
this.heal(pokemon.baseMaxhp / 2);
}
},
},
mudsport: {
inherit: true,
condition: {
onStart(pokemon) {
this.add('-start', pokemon, 'move: Mud Sport');
},
onAnyBasePowerPriority: 3,
onAnyBasePower(basePower, user, target, move) {
if (move.type === 'Electric') {
this.debug('Mud Sport weaken');
return this.chainModify(0.5);
}
},
},
},
naturepower: {
inherit: true,
flags: { metronome: 1 },
onHit(pokemon) {
this.actions.useMove('triattack', pokemon);
},
},
nightmare: {
inherit: true,
condition: {
noCopy: true,
onStart(pokemon) {
if (pokemon.status !== 'slp' && !pokemon.hasAbility('comatose')) {
return false;
}
this.add('-start', pokemon, 'Nightmare');
},
onResidualOrder: 10,
onResidualSubOrder: 7,
onResidual(pokemon) {
this.damage(pokemon.baseMaxhp / 4);
},
},
},
odorsleuth: {
inherit: true,
flags: { protect: 1, mirror: 1, bypasssub: 1, metronome: 1 },
},
outrage: {
inherit: true,
pp: 15,
onAfterMove() {},
},
payback: {
inherit: true,
basePowerCallback(pokemon, target) {
if (this.queue.willMove(target)) {
return 50;
}
this.debug('BP doubled');
return 100;
},
},
payday: {
inherit: true,
onHit() {
this.add('-fieldactivate', 'move: Pay Day');
},
},
perishsong: {
inherit: true,
condition: {
duration: 4,
onEnd(target) {
this.add('-start', target, 'perish0');
target.faint();
},
onResidualOrder: 12,
onResidual(pokemon) {
const duration = pokemon.volatiles['perishsong'].duration;
this.add('-start', pokemon, `perish${duration}`);
},
},
},
petaldance: {
inherit: true,
basePower: 90,
pp: 20,
onAfterMove() {},
},
poisongas: {
inherit: true,
accuracy: 55,
target: "normal",
},
powertrick: {
inherit: true,
flags: { metronome: 1 },
},
protect: {
inherit: true,
priority: 3,
condition: {
duration: 1,
onStart(target) {
this.add('-singleturn', target, 'Protect');
},
onTryHitPriority: 3,
onTryHit(target, source, move) {
if (!move.flags['protect']) return;
this.add('-activate', target, 'Protect');
const lockedmove = source.getVolatile('lockedmove');
if (lockedmove) {
// Outrage counter is NOT reset
if (source.volatiles['lockedmove'].trueDuration >= 2) {
source.volatiles['lockedmove'].duration = 2;
}
}
return null;
},
},
},
psychup: {
inherit: true,
flags: { snatch: 1, bypasssub: 1, metronome: 1 },
},
pursuit: {
inherit: true,
condition: {
duration: 1,
onBeforeSwitchOut(pokemon) {
this.debug('Pursuit start');
let alreadyAdded = false;
for (const source of this.effectState.sources) {
if (!this.queue.cancelMove(source) || !source.hp) continue;
if (!alreadyAdded) {
this.add('-activate', pokemon, 'move: Pursuit');
alreadyAdded = true;
}
// Run through each action in queue to check if the Pursuit user is supposed to Mega Evolve this turn.
// If it is, then Mega Evolve before moving.
if (source.canMegaEvo || source.canUltraBurst) {
for (const [actionIndex, action] of this.queue.entries()) {
if (action.pokemon === source && action.choice === 'megaEvo') {
this.actions.runMegaEvo(source);
this.queue.list.splice(actionIndex, 1);
break;
}
}
}
this.actions.runMove('pursuit', source, source.getLocOf(pokemon));
}
},
},
},
rapidspin: {
inherit: true,
self: {
onHit(pokemon) {
if (pokemon.removeVolatile('leechseed')) {
this.add('-end', pokemon, 'Leech Seed', '[from] move: Rapid Spin', `[of] ${pokemon}`);
}
const sideConditions = ['spikes', 'toxicspikes', 'stealthrock', 'stickyweb'];
for (const condition of sideConditions) {
if (pokemon.side.removeSideCondition(condition)) {
this.add('-sideend', pokemon.side, this.dex.conditions.get(condition).name, '[from] move: Rapid Spin', `[of] ${pokemon}`);
}
}
if (pokemon.volatiles['partiallytrapped']) {
pokemon.removeVolatile('partiallytrapped');
}
},
},
},
recycle: {
inherit: true,
flags: { metronome: 1 },
},
reflect: {
inherit: true,
condition: {
duration: 5,
durationCallback(target, source, effect) {
if (source?.hasItem('lightclay')) {
return 8;
}
return 5;
},
onAnyModifyDamagePhase1(damage, source, target, move) {
if (target !== source && this.effectState.target.hasAlly(target) && this.getCategory(move) === 'Physical') {
if (!target.getMoveHitData(move).crit && !move.infiltrates) {
this.debug('Reflect weaken');
if (target.alliesAndSelf().length > 1) return this.chainModify(2, 3);
return this.chainModify(0.5);
}
}
},
onSideStart(side) {
this.add('-sidestart', side, 'Reflect');
},
onSideResidualOrder: 1,
onSideEnd(side) {
this.add('-sideend', side, 'Reflect');
},
},
},
reversal: {
inherit: true,
basePowerCallback(pokemon) {
const ratio = Math.max(Math.floor(pokemon.hp * 64 / pokemon.maxhp), 1);
let bp;
if (ratio < 2) {
bp = 200;
} else if (ratio < 6) {
bp = 150;
} else if (ratio < 13) {
bp = 100;
} else if (ratio < 22) {
bp = 80;
} else if (ratio < 43) {
bp = 40;
} else {
bp = 20;
}
this.debug(`BP: ${bp}`);
return bp;
},
},
roar: {
inherit: true,
flags: { protect: 1, mirror: 1, sound: 1, bypasssub: 1, metronome: 1 },
},
rockblast: {
inherit: true,
accuracy: 80,
},
roleplay: {
inherit: true,
onTryHit(target, source) {
if (target.ability === source.ability || source.hasItem('griseousorb')) return false;
if (target.getAbility().flags['failroleplay'] || source.ability === 'multitype') {
return false;
}
},
},
safeguard: {
inherit: true,
condition: {
duration: 5,
durationCallback(target, source, effect) {
if (source?.hasAbility('persistent')) {
this.add('-activate', source, 'ability: Persistent', '[move] Safeguard');
return 7;
}
return 5;
},
onSetStatus(status, target, source, effect) {
if (!effect || !source) return;
if (effect.id === 'yawn') return;
if (effect.effectType === 'Move' && effect.infiltrates && !target.isAlly(source)) return;
if (target !== source) {
this.debug('interrupting setStatus');
if (effect.id === 'synchronize' || (effect.effectType === 'Move' && !effect.secondaries)) {
this.add('-activate', target, 'move: Safeguard');
}
return null;
}
},
onTryAddVolatile(status, target, source, effect) {
if (!effect || !source) return;
if (effect.effectType === 'Move' && effect.infiltrates && !target.isAlly(source)) return;
if ((status.id === 'confusion' || status.id === 'yawn') && target !== source) {
if (effect.effectType === 'Move' && !effect.secondaries) this.add('-activate', target, 'move: Safeguard');
return null;
}
},
onSideStart(side, source) {
if (source?.hasAbility('persistent')) {
this.add('-sidestart', side, 'Safeguard', '[persistent]');
} else {
this.add('-sidestart', side, 'Safeguard');
}
},
onSideResidualOrder: 4,
onSideEnd(side) {
this.add('-sideend', side, 'Safeguard');
},
},
},
sandtomb: {
inherit: true,
accuracy: 70,
basePower: 15,
},
scaryface: {
inherit: true,
accuracy: 90,
},
secretpower: {
inherit: true,
secondary: {
chance: 30,
status: 'par',
},
},
sketch: {
inherit: true,
flags: {
bypasssub: 1, allyanim: 1, failencore: 1, noassist: 1,
failcopycat: 1, failinstruct: 1, failmimic: 1, nosketch: 1,
},
onHit(target, source) {
if (source.transformed || !target.lastMove || target.volatiles['substitute']) {
return false;
}
if (target.lastMove.flags['nosketch'] || source.moves.includes(target.lastMove.id)) {
return false;
}
const sketchIndex = source.moves.indexOf('sketch');
if (sketchIndex < 0) return false;
const move = this.dex.moves.get(target.lastMove.id);
const sketchedMove = {
move: move.name,
id: move.id,
pp: move.pp,
maxpp: move.pp,
disabled: false,
used: false,
};
source.moveSlots[sketchIndex] = sketchedMove;
source.baseMoveSlots[sketchIndex] = sketchedMove;
this.add('-activate', source, 'move: Mimic', move.name);
},
},
skillswap: {
inherit: true,
onHit(target, source) {
const targetAbility = target.ability;
const sourceAbility = source.ability;
if (targetAbility === sourceAbility || source.hasItem('griseousorb') || target.hasItem('griseousorb')) {
return false;
}
this.add('-activate', source, 'move: Skill Swap');
source.setAbility(targetAbility);
target.setAbility(sourceAbility);
},
},
sleeptalk: {
inherit: true,
onTryHit(pokemon) {
return !pokemon.volatiles['choicelock'] && !pokemon.volatiles['encore'];
},
},
snatch: {
inherit: true,
flags: { bypasssub: 1, noassist: 1, failcopycat: 1 },
condition: {
duration: 1,
onStart(pokemon) {
this.add('-singleturn', pokemon, 'Snatch');
},
onAnyPrepareHitPriority: -1,
onAnyPrepareHit(source, target, move) {
const snatchUser = this.effectState.source;
if (snatchUser.isSkyDropped()) return;
if (!move || move.isZ || move.isMax || !move.flags['snatch']) {
return;
}
snatchUser.removeVolatile('snatch');
this.add('-activate', snatchUser, 'move: Snatch', `[of] ${source}`);
this.actions.useMove(move.id, snatchUser);
return null;
},
},
},
snore: {
inherit: true,
flags: { protect: 1, mirror: 1, sound: 1, metronome: 1 },
},
spikes: {
inherit: true,
flags: { metronome: 1, mustpressure: 1 },
condition: {
// this is a side condition
onSideStart(side) {
this.add('-sidestart', side, 'Spikes');
this.effectState.layers = 1;
},
onSideRestart(side) {
if (this.effectState.layers >= 3) return false;
this.add('-sidestart', side, 'Spikes');
this.effectState.layers++;
},
onEntryHazard(pokemon) {
if (!pokemon.isGrounded() || pokemon.hasItem('heavydutyboots')) return;
const damageAmounts = [0, 3, 4, 6]; // 1/8, 1/6, 1/4
this.damage(damageAmounts[this.effectState.layers] * pokemon.maxhp / 24);
},
},
},
spite: {
inherit: true,
flags: { protect: 1, mirror: 1, bypasssub: 1, metronome: 1 },
},
stealthrock: {
inherit: true,
flags: { metronome: 1, mustpressure: 1 },
condition: {
// this is a side condition
onSideStart(side) {
this.add('-sidestart', side, 'move: Stealth Rock');
},
onEntryHazard(pokemon) {
if (pokemon.hasItem('heavydutyboots')) return;
const typeMod = this.clampIntRange(pokemon.runEffectiveness(this.dex.getActiveMove('stealthrock')), -6, 6);
this.damage(pokemon.maxhp * 2 ** typeMod / 8);
},
},
},
struggle: {
inherit: true,
flags: {
contact: 1, protect: 1, failencore: 1, failmefirst: 1,
noassist: 1, failcopycat: 1, failinstruct: 1, failmimic: 1, nosketch: 1,
},
onModifyMove(move) {
move.type = '???';
},
},
substitute: {
inherit: true,
condition: {
onStart(target) {
this.add('-start', target, 'Substitute');
this.effectState.hp = Math.floor(target.maxhp / 4);
delete target.volatiles['partiallytrapped'];
},
onTryPrimaryHitPriority: -1,
onTryPrimaryHit(target, source, move) {
if (target === source || move.flags['bypasssub']) {
return;
}
let damage = this.actions.getDamage(source, target, move);
if (!damage && damage !== 0) {
this.add('-fail', source);
this.attrLastMove('[still]');
return null;
}
damage = this.runEvent('SubDamage', target, source, move, damage);
if (!damage) {
return damage;
}
if (damage > target.volatiles['substitute'].hp) {
damage = target.volatiles['substitute'].hp as number;
}
target.volatiles['substitute'].hp -= damage;
source.lastDamage = damage;
if (target.volatiles['substitute'].hp <= 0) {
target.removeVolatile('substitute');
target.addVolatile('substitutebroken');
if (target.volatiles['substitutebroken']) target.volatiles['substitutebroken'].move = move.id;
if (move.ohko) this.add('-ohko');
} else {
this.add('-activate', target, 'Substitute', '[damage]');
}
if (move.recoil && damage) {
this.damage(this.actions.calcRecoilDamage(damage, move, source), source, target, 'recoil');
}
if (move.drain) {
this.heal(Math.ceil(damage * move.drain[0] / move.drain[1]), source, target, 'drain');
}
this.runEvent('AfterSubDamage', target, source, move, damage);
return this.HIT_SUBSTITUTE;
},
onEnd(target) {
this.add('-end', target, 'Substitute');
},
},
},
suckerpunch: {
inherit: true,
onTry(source, target) {
const action = this.queue.willMove(target);
if (!action || action.choice !== 'move' || action.move.category === 'Status' || target.volatiles['mustrecharge']) {
this.add('-fail', source);
return null;
}
},
},
swallow: {
inherit: true,
onTry(source) {
return !!source.volatiles['stockpile'];
},
},
switcheroo: {
inherit: true,
onTryHit(target, source, move) {
if (target.hasAbility('multitype') || source.hasAbility('multitype')) return false;
},
},
synthesis: {
inherit: true,
onHit(pokemon) {
if (this.field.isWeather(['sunnyday', 'desolateland'])) {
this.heal(pokemon.maxhp * 2 / 3);
} else if (this.field.isWeather(['raindance', 'primordialsea', 'sandstorm', 'hail'])) {
this.heal(pokemon.baseMaxhp / 4);
} else {
this.heal(pokemon.baseMaxhp / 2);
}
},
},
tackle: {
inherit: true,
accuracy: 95,
basePower: 35,
},
tailglow: {
inherit: true,
boosts: {
spa: 2,
},
},
tailwind: {
inherit: true,
condition: {
duration: 3,
durationCallback(target, source, effect) {
if (source?.hasAbility('persistent')) {
this.add('-activate', source, 'ability: Persistent', '[move] Tailwind');
return 5;
}
return 3;
},
onSideStart(side, source) {
if (source?.hasAbility('persistent')) {
this.add('-sidestart', side, 'move: Tailwind', '[persistent]');
} else {
this.add('-sidestart', side, 'move: Tailwind');
}
},
onModifySpe(spe) {
return spe * 2;
},
onSideResidualOrder: 5,
onSideEnd(side) {
this.add('-sideend', side, 'move: Tailwind');
},
},
},
taunt: {
inherit: true,
flags: { protect: 1, mirror: 1, bypasssub: 1, metronome: 1 },
condition: {
durationCallback() {
return this.random(3, 6);
},
onStart(target) {
this.add('-start', target, 'move: Taunt');
},
onResidualOrder: 10,
onResidualSubOrder: 15,
onEnd(target) {
this.add('-end', target, 'move: Taunt');
},
onDisableMove(pokemon) {
for (const moveSlot of pokemon.moveSlots) {
if (this.dex.moves.get(moveSlot.id).category === 'Status') {
pokemon.disableMove(moveSlot.id);
}
}
},
onBeforeMovePriority: 5,
onBeforeMove(attacker, defender, move) {
if (move.category === 'Status') {
this.add('cant', attacker, 'move: Taunt', move);
return false;
}
},
},
},
thrash: {
inherit: true,
basePower: 90,
pp: 20,
onAfterMove() {},
},
torment: {
inherit: true,
flags: { protect: 1, mirror: 1, bypasssub: 1, metronome: 1 },
},
toxic: {
inherit: true,
accuracy: 85,
},
toxicspikes: {
inherit: true,
flags: { metronome: 1, mustpressure: 1 },
condition: {
// this is a side condition
onSideStart(side) {
this.add('-sidestart', side, 'move: Toxic Spikes');
this.effectState.layers = 1;
},
onSideRestart(side) {
if (this.effectState.layers >= 2) return false;
this.add('-sidestart', side, 'move: Toxic Spikes');
this.effectState.layers++;
},
onEntryHazard(pokemon) {
if (!pokemon.isGrounded()) return;
if (pokemon.hasType('Poison')) {
this.add('-sideend', pokemon.side, 'move: Toxic Spikes', `[of] ${pokemon}`);
pokemon.side.removeSideCondition('toxicspikes');
} else if (pokemon.volatiles['substitute'] || pokemon.hasType('Steel')) {
// do nothing
} else if (this.effectState.layers >= 2) {
pokemon.trySetStatus('tox', pokemon.side.foe.active[0]);
} else {
pokemon.trySetStatus('psn', pokemon.side.foe.active[0]);
}
},
},
},
transform: {
inherit: true,
flags: { bypasssub: 1, metronome: 1, failencore: 1 },
},
trick: {
inherit: true,
onTryHit(target, source, move) {
if (target.hasAbility('multitype') || source.hasAbility('multitype')) return false;
},
},
trickroom: {
inherit: true,
condition: {
duration: 5,
durationCallback(source, effect) {
if (source?.hasAbility('persistent')) {
this.add('-activate', source, 'ability: Persistent', '[move] Trick Room');
return 7;
}
return 5;
},
onFieldStart(target, source) {
if (source?.hasAbility('persistent')) {
this.add('-fieldstart', 'move: Trick Room', `[of] ${source}`, '[persistent]');
} else {
this.add('-fieldstart', 'move: Trick Room', `[of] ${source}`);
}
},
onFieldRestart(target, source) {
this.field.removePseudoWeather('trickroom');
},
// Speed modification is changed in Pokemon.getActionSpeed() in sim/pokemon.js
onFieldResidualOrder: 13,
onFieldEnd() {
this.add('-fieldend', 'move: Trick Room');
},
},
},
uproar: {
inherit: true,
basePower: 50,
condition: {
onStart(target) {
this.add('-start', target, 'Uproar');
// 3-6 turns
this.effectState.duration = this.random(3, 7);
},
onResidual(target) {
if (target.volatiles['throatchop']) {
target.removeVolatile('uproar');
return;
}
if (target.lastMove && target.lastMove.id === 'struggle') {
// don't lock
delete target.volatiles['uproar'];
}
this.add('-start', target, 'Uproar', '[upkeep]');
},
onResidualOrder: 10,
onResidualSubOrder: 11,
onEnd(target) {
this.add('-end', target, 'Uproar');
},
onLockMove: 'uproar',
onAnySetStatus(status, pokemon) {
if (status.id === 'slp') {
if (pokemon === this.effectState.target) {
this.add('-fail', pokemon, 'slp', '[from] Uproar', '[msg]');
} else {
this.add('-fail', pokemon, 'slp', '[from] Uproar');
}
return null;
}
},
},
},
volttackle: {
inherit: true,
recoil: [1, 3],
},
watersport: {
inherit: true,
condition: {
onStart(pokemon) {
this.add('-start', pokemon, 'move: Water Sport');
},
onAnyBasePowerPriority: 3,
onAnyBasePower(basePower, user, target, move) {
if (move.type === 'Fire') {
this.debug('Water Sport weaken');
return this.chainModify(0.5);
}
},
},
},
whirlpool: {
inherit: true,
accuracy: 70,
basePower: 15,
},
whirlwind: {
inherit: true,
flags: { protect: 1, mirror: 1, bypasssub: 1, metronome: 1 },
},
wish: {
inherit: true,
flags: { heal: 1, metronome: 1 },
slotCondition: 'Wish',
condition: {
duration: 2,
onResidualOrder: 7,
onEnd(target) {
if (!target.fainted) {
const source = this.effectState.source;
const damage = this.heal(target.baseMaxhp / 2, target, target);
if (damage) this.add('-heal', target, target.getHealth, '[from] move: Wish', '[wisher] ' + source.name);
}
},
},
},
woodhammer: {
inherit: true,
recoil: [1, 3],
},
worryseed: {
inherit: true,
onTryHit(pokemon) {
const bannedAbilities = ['multitype', 'truant'];
if (bannedAbilities.includes(pokemon.ability) || pokemon.hasItem('griseousorb')) {
return false;
}
},
},
wrap: {
inherit: true,
accuracy: 85,
},
wringout: {
inherit: true,
basePowerCallback(pokemon, target) {
const bp = Math.floor(target.hp * 120 / target.maxhp) + 1;
this.debug(`BP for ${target.hp}/${target.maxhp} HP: ${bp}`);
return bp;
},
},
yawn: {
inherit: true,
condition: {
noCopy: true, // doesn't get copied by Baton Pass
duration: 2,
onStart(target, source) {
this.add('-start', target, 'move: Yawn', `[of] ${source}`);
},
onResidualOrder: 10,
onResidualSubOrder: 19,
onEnd(target) {
this.add('-end', target, 'move: Yawn', '[silent]');
target.trySetStatus('slp', this.effectState.source);
},
},
},
};