/** * Gen 3 moves */ export const Moves: import('../../../sim/dex-moves').ModdedMoveDataTable = { absorb: { inherit: true, pp: 20, }, acid: { inherit: true, secondary: { chance: 10, boosts: { def: -1, }, }, }, ancientpower: { inherit: true, flags: { contact: 1, protect: 1, mirror: 1, metronome: 1 }, }, assist: { inherit: true, flags: { metronome: 1, noassist: 1, nosleeptalk: 1 }, }, astonish: { inherit: true, basePowerCallback(pokemon, target) { if (target.volatiles['minimize']) return 60; return 30; }, }, beatup: { inherit: true, onModifyMove(move, pokemon) { pokemon.addVolatile('beatup'); move.type = '???'; move.category = 'Special'; move.allies = pokemon.side.pokemon.filter(ally => !ally.fainted && !ally.status); move.multihit = move.allies.length; }, condition: { duration: 1, onModifySpAPriority: -101, onModifySpA(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; }, onFoeModifySpDPriority: -101, onFoeModifySpD(def, pokemon) { this.event.modifier = 1; return pokemon.species.baseStats.def; }, }, }, bide: { inherit: true, accuracy: 100, priority: 0, 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; }, 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' as ID, 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]'); }, }, }, blizzard: { inherit: true, onModifyMove() { }, }, brickbreak: { inherit: true, onTryHit(target, source) { // will shatter screens through sub, before you hit const foe = source.side.foe; foe.removeSideCondition('reflect'); foe.removeSideCondition('lightscreen'); }, }, charge: { inherit: true, boosts: null, }, conversion: { inherit: true, onHit(target) { const possibleTypes = target.moveSlots.map(moveSlot => { const move = this.dex.moves.get(moveSlot.id); if (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); }, }, counter: { inherit: true, condition: { duration: 1, noCopy: true, onStart(target, source, move) { this.effectState.slot = null; this.effectState.damage = 0; }, onRedirectTargetPriority: -1, onRedirectTarget(target, source, source2) { if (source !== this.effectState.target || !this.effectState.slot) return; return this.getAtSlot(this.effectState.slot); }, onDamagePriority: -101, onDamage(damage, target, source, effect) { if ( effect.effectType === 'Move' && !source.isAlly(target) && (effect.category === 'Physical' || effect.id === 'hiddenpower') ) { this.effectState.slot = source.getSlot(); this.effectState.damage = 2 * damage; } }, }, }, covet: { inherit: true, flags: { protect: 1, mirror: 1, noassist: 1 }, }, crunch: { inherit: true, secondary: { chance: 20, boosts: { spd: -1, }, }, }, dig: { inherit: true, basePower: 60, }, disable: { inherit: true, accuracy: 55, flags: { protect: 1, mirror: 1, bypasssub: 1, metronome: 1 }, volatileStatus: 'disable', condition: { durationCallback() { return this.random(2, 6); }, 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; }, onEnd(pokemon) { this.add('-end', pokemon, 'move: Disable'); }, 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); } } }, }, }, dive: { inherit: true, basePower: 60, }, doomdesire: { inherit: true, onTry(source, target) { if (!target.side.addSlotCondition(target, 'futuremove')) return false; const moveData = { name: "Doom Desire", basePower: 120, category: "Physical", 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: "Physical", flags: { metronome: 1, futuremove: 1 }, effectType: 'Move', type: '???', }, }); this.add('-start', source, 'Doom Desire'); return null; }, }, encore: { inherit: true, volatileStatus: 'encore', condition: { durationCallback() { return this.random(3, 7); }, 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); } } }, }, }, extrasensory: { inherit: true, basePowerCallback(pokemon, target) { if (target.volatiles['minimize']) return 160; return 80; }, }, fakeout: { inherit: true, flags: { protect: 1, mirror: 1, metronome: 1 }, }, feintattack: { inherit: true, flags: { protect: 1, mirror: 1, metronome: 1 }, }, flail: { inherit: true, basePowerCallback(pokemon) { const ratio = Math.max(Math.floor(pokemon.hp * 48 / pokemon.maxhp), 1); let bp; if (ratio < 2) { bp = 200; } else if (ratio < 5) { bp = 150; } else if (ratio < 10) { bp = 100; } else if (ratio < 17) { bp = 80; } else if (ratio < 33) { bp = 40; } else { bp = 20; } this.debug(`BP: ${bp}`); return bp; }, }, flash: { inherit: true, accuracy: 70, }, fly: { inherit: true, basePower: 70, }, followme: { inherit: true, volatileStatus: undefined, slotCondition: 'followme', condition: { duration: 1, onStart(target, source, effect) { this.add('-singleturn', target, 'move: Follow Me'); this.effectState.slot = target.getSlot(); }, onFoeRedirectTargetPriority: 1, onFoeRedirectTarget(target, source, source2, move) { const userSlot = this.getAtSlot(this.effectState.slot); if (this.validTarget(userSlot, source, move.target)) { return userSlot; } }, }, }, foresight: { inherit: true, accuracy: 100, }, furycutter: { inherit: true, onHit(target, source) { source.addVolatile('furycutter'); }, }, gigadrain: { inherit: true, pp: 5, }, glare: { inherit: true, ignoreImmunity: false, }, hiddenpower: { inherit: true, category: "Physical", onModifyMove(move, pokemon) { move.type = pokemon.hpType || 'Dark'; const specialTypes = ['Fire', 'Water', 'Grass', 'Ice', 'Electric', 'Dark', 'Psychic', 'Dragon']; move.category = specialTypes.includes(move.type) ? 'Special' : 'Physical'; }, }, highjumpkick: { inherit: true, basePower: 85, onMoveFail(target, source, move) { if (target.runImmunity('Fighting')) { const damage = this.actions.getDamage(source, target, move, true); if (typeof damage !== 'number') throw new Error("HJK recoil failed"); this.damage(this.clampIntRange(damage / 2, 1, Math.floor(target.maxhp / 2)), source, source, move); } }, }, hypnosis: { inherit: true, accuracy: 60, }, jumpkick: { inherit: true, basePower: 70, onMoveFail(target, source, move) { if (target.runImmunity('Fighting')) { const damage = this.actions.getDamage(source, target, move, true); if (typeof damage !== 'number') throw new Error("Jump Kick didn't recoil"); this.damage(this.clampIntRange(damage / 2, 1, Math.floor(target.maxhp / 2)), source, source, move); } }, }, leafblade: { inherit: true, basePower: 70, }, lockon: { inherit: true, accuracy: 100, }, megadrain: { inherit: true, pp: 10, }, memento: { inherit: true, accuracy: true, }, mindreader: { inherit: true, accuracy: 100, }, mimic: { inherit: true, flags: { protect: 1, bypasssub: 1, allyanim: 1, failencore: 1, noassist: 1, failmimic: 1 }, }, mirrorcoat: { inherit: true, condition: { duration: 1, noCopy: true, onStart(target, source, move) { this.effectState.slot = null; this.effectState.damage = 0; }, onRedirectTargetPriority: -1, onRedirectTarget(target, source, source2) { if (source !== this.effectState.target || !this.effectState.slot) return; return this.getAtSlot(this.effectState.slot); }, onDamagePriority: -101, onDamage(damage, target, source, effect) { if ( effect.effectType === 'Move' && !source.isAlly(target) && effect.category === 'Special' && effect.id !== 'hiddenpower' ) { this.effectState.slot = source.getSlot(); this.effectState.damage = 2 * damage; } }, }, }, mirrormove: { inherit: true, flags: { metronome: 1, failencore: 1, nosleeptalk: 1, noassist: 1 }, onTryHit() { }, onHit(pokemon) { const noMirror = [ 'assist', 'curse', 'doomdesire', 'focuspunch', 'futuresight', 'magiccoat', 'metronome', 'mimic', 'mirrormove', 'naturepower', 'psychup', 'roleplay', 'sketch', 'sleeptalk', 'spikes', 'spitup', 'taunt', 'teeterdance', 'transform', ]; const lastAttackedBy = pokemon.getLastAttackedBy(); if (!lastAttackedBy?.source.lastMove || !lastAttackedBy.move) { return false; } if (noMirror.includes(lastAttackedBy.move) || !lastAttackedBy.source.hasMove(lastAttackedBy.move)) { return false; } this.actions.useMove(lastAttackedBy.move, pokemon); }, target: "self", }, naturepower: { inherit: true, accuracy: 95, onHit(target) { this.actions.useMove('swift', target); }, }, needlearm: { inherit: true, basePowerCallback(pokemon, target) { if (target.volatiles['minimize']) return 120; return 60; }, }, nightmare: { inherit: true, accuracy: true, }, odorsleuth: { inherit: true, accuracy: 100, }, outrage: { inherit: true, basePower: 90, }, overheat: { inherit: true, flags: { contact: 1, protect: 1, mirror: 1, metronome: 1 }, }, petaldance: { inherit: true, basePower: 70, }, recover: { inherit: true, pp: 20, }, reversal: { inherit: true, basePowerCallback(pokemon) { const ratio = Math.max(Math.floor(pokemon.hp * 48 / pokemon.maxhp), 1); let bp; if (ratio < 2) { bp = 200; } else if (ratio < 5) { bp = 150; } else if (ratio < 10) { bp = 100; } else if (ratio < 17) { bp = 80; } else if (ratio < 33) { bp = 40; } else { bp = 20; } this.debug(`BP: ${bp}`); return bp; }, }, rocksmash: { inherit: true, basePower: 20, }, sketch: { inherit: true, flags: { bypasssub: 1, failencore: 1, noassist: 1, failmimic: 1, nosketch: 1 }, }, sleeptalk: { inherit: true, onHit(pokemon) { const moves = []; for (const moveSlot of pokemon.moveSlots) { const moveid = moveSlot.id; const pp = moveSlot.pp; const move = this.dex.moves.get(moveid); if (moveid && !move.flags['nosleeptalk'] && !move.flags['charge']) { moves.push({ move: moveid, pp }); } } if (!moves.length) { return false; } const randomMove = this.sample(moves); if (!randomMove.pp) { this.add('cant', pokemon, 'nopp', randomMove.move); return; } this.actions.useMove(randomMove.move, pokemon); }, }, spite: { inherit: true, onHit(target) { const roll = this.random(2, 6); if (target.lastMove && target.deductPP(target.lastMove.id, roll)) { this.add("-activate", target, 'move: Spite', target.lastMove.id, roll); return; } return false; }, }, stockpile: { inherit: true, pp: 10, condition: { noCopy: true, onStart(target) { this.effectState.layers = 1; this.add('-start', target, 'stockpile' + this.effectState.layers); }, onRestart(target) { if (this.effectState.layers >= 3) return false; this.effectState.layers++; this.add('-start', target, 'stockpile' + this.effectState.layers); }, onEnd(target) { this.effectState.layers = 0; this.add('-end', target, 'Stockpile'); }, }, }, struggle: { inherit: true, flags: { contact: 1, protect: 1, noassist: 1, failencore: 1, failmimic: 1, nosketch: 1 }, accuracy: 100, recoil: [1, 4], struggleRecoil: false, }, surf: { inherit: true, target: "allAdjacentFoes", }, taunt: { inherit: true, flags: { protect: 1, bypasssub: 1, metronome: 1 }, condition: { duration: 2, onStart(target) { this.add('-start', target, 'move: Taunt'); }, onResidualOrder: 10, onResidualSubOrder: 15, onEnd(target) { this.add('-end', target, 'move: Taunt', '[silent]'); }, onDisableMove(pokemon) { for (const moveSlot of pokemon.moveSlots) { if (this.dex.moves.get(moveSlot.move).category === 'Status') { pokemon.disableMove(moveSlot.id); } } }, onBeforeMove(attacker, defender, move) { if (move.category === 'Status') { this.add('cant', attacker, 'move: Taunt', move); return false; } }, }, }, teeterdance: { inherit: true, flags: { protect: 1, metronome: 1 }, }, tickle: { inherit: true, flags: { protect: 1, reflectable: 1, mirror: 1, bypasssub: 1, metronome: 1 }, }, uproar: { inherit: true, condition: { onStart(target) { this.add('-start', target, 'Uproar'); // 2-5 turns this.effectState.duration = this.random(2, 6); }, 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; } }, }, }, vinewhip: { inherit: true, pp: 10, }, volttackle: { inherit: true, secondary: null, }, waterfall: { inherit: true, secondary: null, }, weatherball: { inherit: true, onModifyMove(move) { switch (this.field.effectiveWeather()) { case 'sunnyday': move.type = 'Fire'; move.category = 'Special'; break; case 'raindance': move.type = 'Water'; move.category = 'Special'; break; case 'sandstorm': move.type = 'Rock'; break; case 'hail': move.type = 'Ice'; move.category = 'Special'; break; } if (this.field.effectiveWeather()) move.basePower *= 2; }, }, zapcannon: { inherit: true, basePower: 100, }, };