Spaces:
Paused
Paused
| 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); | |
| }, | |
| }, | |
| }, | |
| }; | |