Spaces:
Running
Running
/** | |
* Gen 2 moves | |
*/ | |
export const Moves: import('../../../sim/dex-moves').ModdedMoveDataTable = { | |
aeroblast: { | |
inherit: true, | |
critRatio: 3, | |
}, | |
beatup: { | |
inherit: true, | |
onModifyMove(move, pokemon) { | |
move.type = '???'; | |
move.category = 'Special'; | |
move.allies = pokemon.side.pokemon.filter(ally => !ally.fainted && !ally.status); | |
move.multihit = move.allies.length; | |
}, | |
}, | |
bellydrum: { | |
inherit: true, | |
onHit(target) { | |
if (target.boosts.atk >= 6) { | |
return false; | |
} | |
if (target.hp <= target.maxhp / 2) { | |
this.boost({ atk: 2 }, null, null, this.dex.conditions.get('bellydrum2')); | |
return false; | |
} | |
this.directDamage(target.maxhp / 2); | |
const originalStage = target.boosts.atk; | |
let currentStage = originalStage; | |
let boosts = 0; | |
let loopStage = 0; | |
while (currentStage < 6) { | |
loopStage = currentStage; | |
currentStage++; | |
if (currentStage < 6) currentStage++; | |
target.boosts.atk = loopStage; | |
if (target.getStat('atk', false, true) < 999) { | |
target.boosts.atk = currentStage; | |
continue; | |
} | |
target.boosts.atk = currentStage - 1; | |
break; | |
} | |
boosts = target.boosts.atk - originalStage; | |
target.boosts.atk = originalStage; | |
this.boost({ atk: boosts }); | |
}, | |
}, | |
bide: { | |
inherit: true, | |
condition: { | |
duration: 3, | |
durationCallback(target, source, effect) { | |
return this.random(3, 5); | |
}, | |
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; | |
}, | |
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: 100, | |
damage: this.effectState.totalDamage * 2, | |
category: "Physical", | |
priority: 0, | |
flags: { contact: 1, protect: 1 }, | |
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]'); | |
}, | |
}, | |
}, | |
counter: { | |
inherit: true, | |
damageCallback(pokemon, target) { | |
const lastAttackedBy = pokemon.getLastAttackedBy(); | |
if (!lastAttackedBy?.move || !lastAttackedBy.thisTurn) return false; | |
// Hidden Power counts as physical | |
if (this.getCategory(lastAttackedBy.move) === 'Physical' && target.lastMove?.id !== 'sleeptalk') { | |
return 2 * lastAttackedBy.damage; | |
} | |
return false; | |
}, | |
beforeTurnCallback() {}, | |
onTry() {}, | |
condition: {}, | |
priority: -1, | |
}, | |
crabhammer: { | |
inherit: true, | |
critRatio: 3, | |
}, | |
crosschop: { | |
inherit: true, | |
critRatio: 3, | |
}, | |
curse: { | |
inherit: true, | |
condition: { | |
onStart(pokemon, source) { | |
this.add('-start', pokemon, 'Curse', `[of] ${source}`); | |
}, | |
onAfterMoveSelf(pokemon) { | |
this.damage(pokemon.baseMaxhp / 4); | |
}, | |
}, | |
}, | |
detect: { | |
inherit: true, | |
priority: 2, | |
}, | |
dig: { | |
inherit: true, | |
onPrepareHit(target, source) { | |
return source.status !== 'slp'; | |
}, | |
condition: { | |
duration: 2, | |
onImmunity(type, pokemon) { | |
if (type === 'sandstorm') return false; | |
}, | |
onInvulnerability(target, source, move) { | |
if (move.id === 'earthquake' || move.id === 'magnitude' || move.id === 'fissure') { | |
return; | |
} | |
if (['attract', 'curse', 'foresight', 'meanlook', 'mimic', 'nightmare', 'spiderweb', 'transform'].includes(move.id)) { | |
// Oversight in the interaction between these moves and the Lock-On effect | |
return false; | |
} | |
if (source.volatiles['lockon'] && target === source.volatiles['lockon'].source) return; | |
return false; | |
}, | |
onSourceBasePower(basePower, target, source, move) { | |
if (move.id === 'earthquake' || move.id === 'magnitude') { | |
return this.chainModify(2); | |
} | |
}, | |
}, | |
}, | |
doubleedge: { | |
inherit: true, | |
recoil: [25, 100], | |
}, | |
encore: { | |
inherit: true, | |
condition: { | |
durationCallback() { | |
return this.random(3, 7); | |
}, | |
onStart(target) { | |
const lockedMove = target.lastMoveEncore?.id || ''; | |
const moveIndex = lockedMove ? target.moves.indexOf(lockedMove) : -1; | |
if (moveIndex < 0 || target.lastMoveEncore?.flags['failencore'] || target.moveSlots[moveIndex].pp <= 0) { | |
// it failed | |
return false; | |
} | |
this.effectState.move = lockedMove; | |
this.add('-start', target, 'Encore'); | |
}, | |
onOverrideAction(pokemon) { | |
return this.effectState.move; | |
}, | |
onResidualOrder: 13, | |
onResidual(target) { | |
const lockedMoveIndex = target.moves.indexOf(this.effectState.move); | |
if (lockedMoveIndex >= 0 && target.moveSlots[lockedMoveIndex].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); | |
} | |
} | |
}, | |
}, | |
}, | |
endure: { | |
inherit: true, | |
priority: 2, | |
}, | |
explosion: { | |
inherit: true, | |
flags: { protect: 1, mirror: 1, metronome: 1, noparentalbond: 1, nosketch: 1 }, | |
}, | |
flail: { | |
inherit: true, | |
noDamageVariance: true, | |
willCrit: false, | |
}, | |
fly: { | |
inherit: true, | |
onPrepareHit(target, source) { | |
return source.status !== 'slp'; | |
}, | |
condition: { | |
duration: 2, | |
onInvulnerability(target, source, move) { | |
if (move.id === 'gust' || move.id === 'twister' || move.id === 'thunder' || move.id === 'whirlwind') { | |
return; | |
} | |
if (move.id === 'earthquake' || move.id === 'magnitude' || move.id === 'fissure') { | |
// These moves miss even during the Lock-On effect | |
return false; | |
} | |
if (['attract', 'curse', 'foresight', 'meanlook', 'mimic', 'nightmare', 'spiderweb', 'transform'].includes(move.id)) { | |
// Oversight in the interaction between these moves and the Lock-On effect | |
return false; | |
} | |
if (source.volatiles['lockon'] && target === source.volatiles['lockon'].source) return; | |
return false; | |
}, | |
onSourceBasePower(basePower, target, source, move) { | |
if (move.id === 'gust' || move.id === 'twister') { | |
return this.chainModify(2); | |
} | |
}, | |
}, | |
}, | |
focusenergy: { | |
inherit: true, | |
condition: { | |
onStart(pokemon) { | |
this.add('-start', pokemon, 'move: Focus Energy'); | |
}, | |
onModifyCritRatio(critRatio) { | |
return critRatio + 1; | |
}, | |
}, | |
}, | |
foresight: { | |
inherit: true, | |
onTryHit(target) { | |
if (target.volatiles['foresight']) return false; | |
}, | |
condition: { | |
onStart(pokemon) { | |
this.add('-start', pokemon, 'Foresight'); | |
}, | |
onNegateImmunity(pokemon, type) { | |
if (pokemon.hasType('Ghost') && ['Normal', 'Fighting'].includes(type)) return false; | |
}, | |
onModifyBoost(boosts) { | |
if (boosts.evasion && boosts.evasion > 0) { | |
boosts.evasion = 0; | |
} | |
}, | |
}, | |
}, | |
frustration: { | |
inherit: true, | |
basePowerCallback(pokemon) { | |
return Math.floor(((255 - pokemon.happiness) * 10) / 25) || null; | |
}, | |
}, | |
healbell: { | |
inherit: true, | |
onHit(target, source) { | |
this.add('-cureteam', source, '[from] move: Heal Bell'); | |
for (const pokemon of target.side.pokemon) { | |
pokemon.clearStatus(); | |
} | |
}, | |
}, | |
highjumpkick: { | |
inherit: true, | |
onMoveFail(target, source, move) { | |
if (target.runImmunity('Fighting')) { | |
const damage = this.actions.getDamage(source, target, move, true); | |
if (typeof damage !== 'number') throw new Error("Couldn't get High Jump Kick recoil"); | |
this.damage(this.clampIntRange(damage / 8, 1), source, source, move); | |
} | |
}, | |
}, | |
jumpkick: { | |
inherit: true, | |
onMoveFail(target, source, move) { | |
if (target.runImmunity('Fighting')) { | |
const damage = this.actions.getDamage(source, target, move, true); | |
if (typeof damage !== 'number') throw new Error("Couldn't get Jump Kick recoil"); | |
this.damage(this.clampIntRange(damage / 8, 1), source, source, move); | |
} | |
}, | |
}, | |
karatechop: { | |
inherit: true, | |
critRatio: 3, | |
}, | |
leechseed: { | |
inherit: true, | |
onHit() {}, | |
condition: { | |
onStart(target) { | |
this.add('-start', target, 'move: Leech Seed'); | |
}, | |
onAfterMoveSelfPriority: 2, | |
onAfterMoveSelf(pokemon) { | |
if (!pokemon.hp) return; | |
const leecher = this.getAtSlot(pokemon.volatiles['leechseed'].sourceSlot); | |
if (!leecher || leecher.fainted || leecher.hp <= 0) { | |
return; | |
} | |
const toLeech = this.clampIntRange(pokemon.maxhp / 8, 1); | |
const damage = this.damage(toLeech, pokemon, leecher); | |
if (damage) { | |
this.heal(damage, leecher, pokemon); | |
} | |
}, | |
}, | |
}, | |
lightscreen: { | |
inherit: true, | |
condition: { | |
duration: 5, | |
// Sp. Def boost applied directly in stat calculation | |
onSideStart(side) { | |
this.add('-sidestart', side, 'move: Light Screen'); | |
}, | |
onSideResidualOrder: 9, | |
onSideEnd(side) { | |
this.add('-sideend', side, 'move: Light Screen'); | |
}, | |
}, | |
}, | |
lockon: { | |
inherit: true, | |
onTryHit(target) { | |
if (target.volatiles['foresight'] || target.volatiles['lockon']) return false; | |
}, | |
condition: { | |
duration: 2, | |
onSourceAccuracy(accuracy, target, source, move) { | |
if (move && source === this.effectState.target && target === this.effectState.source) return true; | |
}, | |
}, | |
}, | |
lowkick: { | |
inherit: true, | |
accuracy: 90, | |
basePower: 50, | |
basePowerCallback() { | |
return 50; | |
}, | |
secondary: { | |
chance: 30, | |
volatileStatus: 'flinch', | |
}, | |
}, | |
meanlook: { | |
inherit: true, | |
flags: { reflectable: 1, mirror: 1, metronome: 1 }, | |
}, | |
metronome: { | |
inherit: true, | |
flags: { failencore: 1, nosketch: 1 }, | |
}, | |
mimic: { | |
inherit: true, | |
accuracy: 100, | |
flags: { protect: 1, bypasssub: 1, allyanim: 1, failencore: 1, noassist: 1, nosketch: 1 }, | |
}, | |
mindreader: { | |
inherit: true, | |
onTryHit(target) { | |
if (target.volatiles['foresight'] || target.volatiles['lockon']) return false; | |
}, | |
}, | |
mirrorcoat: { | |
inherit: true, | |
damageCallback(pokemon, target) { | |
const lastAttackedBy = pokemon.getLastAttackedBy(); | |
if (!lastAttackedBy?.move || !lastAttackedBy.thisTurn) return false; | |
// Hidden Power counts as physical | |
if (this.getCategory(lastAttackedBy.move) === 'Special' && target.lastMove?.id !== 'sleeptalk') { | |
return 2 * lastAttackedBy.damage; | |
} | |
return false; | |
}, | |
beforeTurnCallback() {}, | |
onTry() {}, | |
condition: {}, | |
priority: -1, | |
}, | |
mirrormove: { | |
inherit: true, | |
flags: { metronome: 1, failencore: 1, nosketch: 1 }, | |
onHit(pokemon) { | |
const noMirror = ['metronome', 'mimic', 'mirrormove', 'sketch', 'sleeptalk', 'transform']; | |
const target = pokemon.side.foe.active[0]; | |
const lastMove = target?.lastMove && target?.lastMove.id; | |
if (!lastMove || (!pokemon.activeTurns && !target.moveThisTurn)) { | |
return false; | |
} | |
if (noMirror.includes(lastMove) || pokemon.moves.includes(lastMove)) { | |
return false; | |
} | |
this.actions.useMove(lastMove, pokemon); | |
}, | |
}, | |
mist: { | |
num: 54, | |
accuracy: true, | |
basePower: 0, | |
category: "Status", | |
name: "Mist", | |
pp: 30, | |
priority: 0, | |
flags: { metronome: 1 }, | |
volatileStatus: 'mist', | |
condition: { | |
onStart(pokemon) { | |
this.add('-start', pokemon, 'Mist'); | |
}, | |
onTryBoost(boost, target, source, effect) { | |
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'); | |
} | |
} | |
}, | |
}, | |
secondary: null, | |
target: "self", | |
type: "Ice", | |
}, | |
moonlight: { | |
inherit: true, | |
onHit(pokemon) { | |
if (this.field.isWeather(['sunnyday', 'desolateland'])) { | |
this.heal(pokemon.maxhp); | |
} 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); | |
} else if (this.field.isWeather(['raindance', 'primordialsea', 'sandstorm', 'hail'])) { | |
this.heal(pokemon.baseMaxhp / 4); | |
} else { | |
this.heal(pokemon.baseMaxhp / 2); | |
} | |
}, | |
}, | |
nightmare: { | |
inherit: true, | |
condition: { | |
noCopy: true, | |
onStart(pokemon) { | |
if (pokemon.status !== 'slp') { | |
return false; | |
} | |
this.add('-start', pokemon, 'Nightmare'); | |
}, | |
onAfterMoveSelfPriority: 1, | |
onAfterMoveSelf(pokemon) { | |
if (pokemon.status === 'slp') this.damage(pokemon.baseMaxhp / 4); | |
}, | |
}, | |
}, | |
outrage: { | |
inherit: true, | |
onMoveFail(target, source, move) { | |
source.addVolatile('lockedmove'); | |
}, | |
onAfterMove(pokemon) { | |
if (pokemon.volatiles['lockedmove'] && pokemon.volatiles['lockedmove'].duration === 1) { | |
pokemon.removeVolatile('lockedmove'); | |
} | |
}, | |
}, | |
painsplit: { | |
inherit: true, | |
accuracy: 100, | |
}, | |
perishsong: { | |
inherit: true, | |
condition: { | |
duration: 4, | |
onEnd(target) { | |
this.add('-start', target, 'perish0'); | |
target.faint(); | |
}, | |
onResidualOrder: 4, | |
onResidual(pokemon) { | |
const duration = pokemon.volatiles['perishsong'].duration; | |
this.add('-start', pokemon, `perish${duration}`); | |
}, | |
}, | |
}, | |
petaldance: { | |
inherit: true, | |
onMoveFail(target, source, move) { | |
source.addVolatile('lockedmove'); | |
}, | |
onAfterMove(pokemon) { | |
if (pokemon.volatiles['lockedmove'] && pokemon.volatiles['lockedmove'].duration === 1) { | |
pokemon.removeVolatile('lockedmove'); | |
} | |
}, | |
}, | |
poisongas: { | |
inherit: true, | |
ignoreImmunity: false, | |
}, | |
poisonpowder: { | |
inherit: true, | |
ignoreImmunity: false, | |
}, | |
protect: { | |
inherit: true, | |
priority: 2, | |
}, | |
psywave: { | |
inherit: true, | |
damageCallback(pokemon) { | |
return this.random(1, pokemon.level + Math.floor(pokemon.level / 2)); | |
}, | |
}, | |
pursuit: { | |
inherit: true, | |
onModifyMove() {}, | |
condition: { | |
duration: 1, | |
onBeforeSwitchOut(pokemon) { | |
this.debug('Pursuit start'); | |
let alreadyAdded = false; | |
for (const source of this.effectState.sources) { | |
if (source.speed < pokemon.speed || (source.speed === pokemon.speed && this.randomChance(1, 2))) { | |
// Destiny Bond ends if the switch action "outspeeds" the attacker, regardless of host | |
pokemon.removeVolatile('destinybond'); | |
} | |
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)); | |
} | |
}, | |
}, | |
}, | |
razorleaf: { | |
inherit: true, | |
critRatio: 3, | |
}, | |
razorwind: { | |
inherit: true, | |
accuracy: 75, | |
critRatio: 3, | |
onPrepareHit(target, source) { | |
return source.status !== 'slp'; | |
}, | |
}, | |
reflect: { | |
inherit: true, | |
condition: { | |
duration: 5, | |
// Defense boost applied directly in stat calculation | |
onSideStart(side) { | |
this.add('-sidestart', side, 'Reflect'); | |
}, | |
onSideResidualOrder: 9, | |
onSideEnd(side) { | |
this.add('-sideend', side, 'Reflect'); | |
}, | |
}, | |
}, | |
rest: { | |
inherit: true, | |
onTry(pokemon) { | |
if (pokemon.hp < pokemon.maxhp) return; | |
this.add('-fail', pokemon); | |
return null; | |
}, | |
onHit(target, source, move) { | |
if (target.status !== 'slp') { | |
if (!target.setStatus('slp', source, move)) return; | |
} else { | |
this.add('-status', target, 'slp', '[from] move: Rest'); | |
} | |
target.statusState.time = 3; | |
target.statusState.startTime = 3; | |
target.statusState.source = target; | |
this.heal(target.maxhp); | |
}, | |
secondary: null, | |
}, | |
return: { | |
inherit: true, | |
basePowerCallback(pokemon) { | |
return Math.floor((pokemon.happiness * 10) / 25) || null; | |
}, | |
}, | |
reversal: { | |
inherit: true, | |
noDamageVariance: true, | |
willCrit: false, | |
}, | |
roar: { | |
inherit: true, | |
onTryHit() { | |
for (const action of this.queue) { | |
// Roar only works if it is the last action in a turn, including when it's called by Sleep Talk | |
if (action.choice === 'move' || action.choice === 'switch') return false; | |
} | |
}, | |
priority: -1, | |
}, | |
safeguard: { | |
inherit: true, | |
condition: { | |
duration: 5, | |
durationCallback(target, source, effect) { | |
if (source?.hasAbility('persistent')) { | |
this.add('-activate', source, 'ability: Persistent', effect); | |
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) { | |
this.add('-sidestart', side, 'Safeguard'); | |
}, | |
onSideResidualOrder: 8, | |
onSideEnd(side) { | |
this.add('-sideend', side, 'Safeguard'); | |
}, | |
}, | |
}, | |
selfdestruct: { | |
inherit: true, | |
flags: { protect: 1, mirror: 1, metronome: 1, noparentalbond: 1, nosketch: 1 }, | |
}, | |
sketch: { | |
inherit: true, | |
flags: { bypasssub: 1, failencore: 1, noassist: 1, nosketch: 1 }, | |
onHit() { | |
// Sketch always fails in Link Battles | |
this.add('-nothing'); | |
}, | |
}, | |
skullbash: { | |
inherit: true, | |
onPrepareHit(target, source) { | |
return source.status !== 'slp'; | |
}, | |
}, | |
skyattack: { | |
inherit: true, | |
critRatio: 1, | |
onPrepareHit(target, source) { | |
return source.status !== 'slp'; | |
}, | |
secondary: null, | |
}, | |
slash: { | |
inherit: true, | |
critRatio: 3, | |
}, | |
sleeptalk: { | |
inherit: true, | |
flags: { failencore: 1, nosleeptalk: 1, nosketch: 1 }, | |
onHit(pokemon) { | |
const moves = []; | |
for (const moveSlot of pokemon.moveSlots) { | |
const moveid = moveSlot.id; | |
const move = this.dex.moves.get(moveid); | |
if (moveid && !move.flags['nosleeptalk'] && !move.flags['charge']) { | |
moves.push(moveid); | |
} | |
} | |
let randomMove = ''; | |
if (moves.length) randomMove = this.sample(moves); | |
if (!randomMove) return false; | |
this.actions.useMove(randomMove, pokemon); | |
}, | |
}, | |
solarbeam: { | |
inherit: true, | |
onPrepareHit(target, source) { | |
return source.status !== 'slp'; | |
}, | |
// Rain weakening done directly in the damage formula | |
onBasePower() {}, | |
}, | |
spiderweb: { | |
inherit: true, | |
flags: { reflectable: 1, mirror: 1, metronome: 1 }, | |
}, | |
spikes: { | |
inherit: true, | |
condition: { | |
// this is a side condition | |
onSideStart(side) { | |
if (!this.effectState.layers || this.effectState.layers === 0) { | |
this.add('-sidestart', side, 'Spikes'); | |
this.effectState.layers = 1; | |
} else { | |
return false; | |
} | |
}, | |
onSwitchIn(pokemon) { | |
if (!pokemon.runImmunity('Ground')) return; | |
const damageAmounts = [0, 3]; | |
this.damage(damageAmounts[this.effectState.layers] * pokemon.maxhp / 24); | |
}, | |
}, | |
}, | |
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 (move.stallingMove) { | |
this.add('-fail', source); | |
return null; | |
} | |
if (target === source) { | |
this.debug('sub bypass: self hit'); | |
return; | |
} | |
if (move.id === 'twineedle') { | |
move.secondaries = move.secondaries!.filter(p => !p.kingsrock); | |
} | |
if (move.drain) { | |
this.add('-miss', source); | |
this.hint("In Gen 2, draining moves always miss against Substitute."); | |
return null; | |
} | |
if (move.category === 'Status') { | |
const SubBlocked = ['leechseed', 'lockon', 'mindreader', 'nightmare', 'painsplit', 'sketch']; | |
if (move.id === 'swagger') { | |
// this is safe, move is a copy | |
delete move.volatileStatus; | |
} | |
if ( | |
move.status || (move.boosts && move.id !== 'swagger') || | |
move.volatileStatus === 'confusion' || SubBlocked.includes(move.id) | |
) { | |
this.add('-activate', target, 'Substitute', '[block] ' + move.name); | |
return null; | |
} | |
return; | |
} | |
let damage = this.actions.getDamage(source, target, move); | |
if (!damage) { | |
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'); | |
} else { | |
this.add('-activate', target, 'Substitute', '[damage]'); | |
} | |
if (move.recoil) { | |
this.damage(1, source, target, 'recoil'); | |
} | |
this.runEvent('AfterSubDamage', target, source, move, damage); | |
return this.HIT_SUBSTITUTE; | |
}, | |
onEnd(target) { | |
this.add('-end', target, 'Substitute'); | |
}, | |
}, | |
}, | |
swagger: { | |
inherit: true, | |
onTryHit(target, pokemon) { | |
if (target.boosts.atk >= 6 || target.getStat('atk', false, true) === 999) { | |
this.add('-miss', pokemon); | |
return null; | |
} | |
}, | |
}, | |
synthesis: { | |
inherit: true, | |
onHit(pokemon) { | |
if (this.field.isWeather(['sunnyday', 'desolateland'])) { | |
this.heal(pokemon.maxhp); | |
} else if (this.field.isWeather(['raindance', 'primordialsea', 'sandstorm', 'hail'])) { | |
this.heal(pokemon.baseMaxhp / 4); | |
} else { | |
this.heal(pokemon.baseMaxhp / 2); | |
} | |
}, | |
}, | |
thief: { | |
inherit: true, | |
onAfterHit() {}, | |
secondary: { | |
chance: 100, | |
onHit(target, source) { | |
if (source.item || source.volatiles['gem']) { | |
return; | |
} | |
const yourItem = target.takeItem(source); | |
if (!yourItem) { | |
return; | |
} | |
if (!source.setItem(yourItem)) { | |
target.item = yourItem.id; // bypass setItem so we don't break choicelock or anything | |
return; | |
} | |
this.add('-item', source, yourItem, '[from] move: Thief', `[of] ${target}`); | |
}, | |
}, | |
}, | |
thrash: { | |
inherit: true, | |
onMoveFail(target, source, move) { | |
source.addVolatile('lockedmove'); | |
}, | |
onAfterMove(pokemon) { | |
if (pokemon.volatiles['lockedmove'] && pokemon.volatiles['lockedmove'].duration === 1) { | |
pokemon.removeVolatile('lockedmove'); | |
} | |
}, | |
}, | |
toxic: { | |
inherit: true, | |
ignoreImmunity: false, | |
}, | |
transform: { | |
inherit: true, | |
flags: { bypasssub: 1, metronome: 1, failencore: 1, nosketch: 1 }, | |
}, | |
triattack: { | |
inherit: true, | |
onHit(target, source, move) { | |
move.statusRoll = ['par', 'frz', 'brn'][this.random(3)]; | |
}, | |
secondary: { | |
chance: 20, | |
onHit(target, source, move) { | |
if (move.statusRoll) { | |
target.trySetStatus(move.statusRoll, source); | |
} | |
}, | |
}, | |
}, | |
triplekick: { | |
inherit: true, | |
multiaccuracy: false, | |
multihit: [1, 3], | |
}, | |
whirlwind: { | |
inherit: true, | |
onTryHit() { | |
for (const action of this.queue) { | |
// Whirlwind only works if it is the last action in a turn, including when it's called by Sleep Talk | |
if (action.choice === 'move' || action.choice === 'switch') return false; | |
} | |
}, | |
priority: -1, | |
}, | |
}; | |