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