'use strict'; /**@type {{[k: string]: PureEffectData}} */ let BattleStatuses = { brn: { name: 'brn', id: 'brn', num: 0, effectType: 'Status', onStart(target, source, sourceEffect) { if (sourceEffect && sourceEffect.id === 'flameorb') { this.add('-status', target, 'brn', '[from] item: Flame Orb'); } else if (sourceEffect && sourceEffect.effectType === 'Ability') { this.add('-status', target, 'brn', '[from] ability: ' + sourceEffect.name, '[of] ' + source); } else { this.add('-status', target, 'brn'); } }, // Damage reduction is handled directly in the sim/battle.js damage function onResidualOrder: 9, onResidual(pokemon) { this.damage(pokemon.maxhp / 16); }, }, par: { name: 'par', id: 'par', num: 0, effectType: 'Status', onStart(target, source, sourceEffect) { if (sourceEffect && sourceEffect.effectType === 'Ability') { this.add('-status', target, 'par', '[from] ability: ' + sourceEffect.name, '[of] ' + source); } else { this.add('-status', target, 'par'); } }, onModifySpe(spe, pokemon) { if (!pokemon.hasAbility('quickfeet')) { return this.chainModify(0.5); } }, onBeforeMovePriority: 1, onBeforeMove(pokemon) { if (this.randomChance(1, 4)) { this.add('cant', pokemon, 'par'); return false; } }, }, slp: { name: 'slp', id: 'slp', num: 0, effectType: 'Status', onStart(target, source, sourceEffect) { if (sourceEffect && sourceEffect.effectType === 'Ability') { this.add('-status', target, 'slp', '[from] ability: ' + sourceEffect.name, '[of] ' + source); } else if (sourceEffect && sourceEffect.effectType === 'Move') { this.add('-status', target, 'slp', '[from] move: ' + sourceEffect.name); } else { this.add('-status', target, 'slp'); } // 1-3 turns this.effectData.startTime = this.random(2, 5); this.effectData.time = this.effectData.startTime; }, onBeforeMovePriority: 10, onBeforeMove(pokemon, target, move) { if (pokemon.hasAbility('earlybird')) { pokemon.statusData.time--; } pokemon.statusData.time--; if (pokemon.statusData.time <= 0) { pokemon.cureStatus(); return; } this.add('cant', pokemon, 'slp'); if (move.sleepUsable) { return; } return false; }, }, frz: { name: 'frz', id: 'frz', num: 0, effectType: 'Status', onStart(target, source, sourceEffect) { if (sourceEffect && sourceEffect.effectType === 'Ability') { this.add('-status', target, 'frz', '[from] ability: ' + sourceEffect.name, '[of] ' + source); } else { this.add('-status', target, 'frz'); } if (target.template.species === 'Shaymin-Sky' && target.baseTemplate.baseSpecies === 'Shaymin') { target.formeChange('Shaymin', this.effect, true); } }, onBeforeMovePriority: 10, onBeforeMove(pokemon, target, move) { if (move.flags['defrost']) return; if (this.randomChance(1, 5)) { pokemon.cureStatus(); return; } this.add('cant', pokemon, 'frz'); return false; }, onModifyMove(move, pokemon) { if (move.flags['defrost']) { this.add('-curestatus', pokemon, 'frz', '[from] move: ' + move); pokemon.setStatus(''); } }, onHit(target, source, move) { if (move.thawsTarget || move.type === 'Fire' && move.category !== 'Status') { target.cureStatus(); } }, }, psn: { name: 'psn', id: 'psn', num: 0, effectType: 'Status', onStart(target, source, sourceEffect) { if (sourceEffect && sourceEffect.effectType === 'Ability') { this.add('-status', target, 'psn', '[from] ability: ' + sourceEffect.name, '[of] ' + source); } else { this.add('-status', target, 'psn'); } }, onResidualOrder: 9, onResidual(pokemon) { this.damage(pokemon.maxhp / 8); }, }, tox: { name: 'tox', id: 'tox', num: 0, effectType: 'Status', onStart(target, source, sourceEffect) { this.effectData.stage = 0; if (sourceEffect && sourceEffect.id === 'toxicorb') { this.add('-status', target, 'tox', '[from] item: Toxic Orb'); } else if (sourceEffect && sourceEffect.effectType === 'Ability') { this.add('-status', target, 'tox', '[from] ability: ' + sourceEffect.name, '[of] ' + source); } else { this.add('-status', target, 'tox'); } }, onSwitchIn() { this.effectData.stage = 0; }, onResidualOrder: 9, onResidual(pokemon) { if (this.effectData.stage < 15) { this.effectData.stage++; } this.damage(this.clampIntRange(pokemon.maxhp / 16, 1) * this.effectData.stage); }, }, confusion: { name: 'confusion', id: 'confusion', num: 0, // this is a volatile status onStart(target, source, sourceEffect) { if (sourceEffect && sourceEffect.id === 'lockedmove') { this.add('-start', target, 'confusion', '[fatigue]'); } else { this.add('-start', target, 'confusion'); } this.effectData.time = this.random(2, 6); }, onEnd(target) { this.add('-end', target, 'confusion'); }, onBeforeMovePriority: 3, onBeforeMove(pokemon) { pokemon.volatiles.confusion.time--; if (!pokemon.volatiles.confusion.time) { pokemon.removeVolatile('confusion'); return; } this.add('-activate', pokemon, 'confusion'); if (!this.randomChance(1, 3)) { return; } this.activeTarget = pokemon; let damage = this.getDamage(pokemon, pokemon, 40); if (typeof damage !== 'number') throw new Error("Confusion damage not dealt"); this.damage(damage, pokemon, pokemon, /** @type {ActiveMove} */ ({ id: 'confused', effectType: 'Move', type: '???', })); return false; }, }, flinch: { name: 'flinch', id: 'flinch', num: 0, duration: 1, onBeforeMovePriority: 8, onBeforeMove(pokemon) { if (!this.runEvent('Flinch', pokemon)) { return; } this.add('cant', pokemon, 'flinch'); return false; }, }, trapped: { name: 'trapped', id: 'trapped', num: 0, noCopy: true, onTrapPokemon(pokemon) { pokemon.tryTrap(); }, onStart(target) { this.add('-activate', target, 'trapped'); }, }, trapper: { name: 'trapper', id: 'trapper', num: 0, noCopy: true, }, partiallytrapped: { name: 'partiallytrapped', id: 'partiallytrapped', num: 0, duration: 5, durationCallback(target, source) { if (source && source.hasItem('gripclaw')) return 8; return this.random(5, 7); }, onStart(pokemon, source) { this.add('-activate', pokemon, 'move: ' + this.effectData.sourceEffect, '[of] ' + source); }, onResidualOrder: 11, onResidual(pokemon) { if (this.effectData.source && (!this.effectData.source.isActive || this.effectData.source.hp <= 0 || !this.effectData.source.activeTurns)) { delete pokemon.volatiles['partiallytrapped']; this.add('-end', pokemon, this.effectData.sourceEffect, '[partiallytrapped]', '[silent]'); return; } if (this.effectData.source.hasItem('bindingband')) { this.damage(pokemon.maxhp / 6); } else { this.damage(pokemon.maxhp / 8); } }, onEnd(pokemon) { this.add('-end', pokemon, this.effectData.sourceEffect, '[partiallytrapped]'); }, onTrapPokemon(pokemon) { if (this.effectData.source && this.effectData.source.isActive) pokemon.tryTrap(); }, }, lockedmove: { // Outrage, Thrash, Petal Dance... name: 'lockedmove', id: 'lockedmove', num: 0, duration: 2, onResidual(target) { if (target.status === 'slp') { // don't lock, and bypass confusion for calming delete target.volatiles['lockedmove']; } this.effectData.trueDuration--; }, onStart(target, source, effect) { this.effectData.trueDuration = this.random(2, 4); this.effectData.move = effect.id; }, onRestart() { if (this.effectData.trueDuration >= 2) { this.effectData.duration = 2; } }, onEnd(target) { if (this.effectData.trueDuration > 1) return; target.addVolatile('confusion'); }, onLockMove(pokemon) { return this.effectData.move; }, }, twoturnmove: { // Skull Bash, SolarBeam, Sky Drop... name: 'twoturnmove', id: 'twoturnmove', num: 0, duration: 2, onStart(target, source, effect) { this.effectData.move = effect.id; target.addVolatile(effect.id, source); this.attrLastMove('[still]'); }, onEnd(target) { target.removeVolatile(this.effectData.move); }, onLockMove() { return this.effectData.move; }, onMoveAborted(pokemon) { pokemon.removeVolatile('twoturnmove'); }, }, choicelock: { name: 'choicelock', id: 'choicelock', num: 0, noCopy: true, onStart(pokemon) { if (!this.activeMove) throw new Error("Battle.activeMove is null"); if (!this.activeMove.id || this.activeMove.hasBounced) return false; this.effectData.move = this.activeMove.id; }, onBeforeMove(pokemon, target, move) { if (!pokemon.getItem().isChoice) { pokemon.removeVolatile('choicelock'); return; } if (!pokemon.ignoringItem() && move.id !== this.effectData.move && move.id !== 'struggle') { // Fails unless the Choice item is being ignored, and no PP is lost this.addMove('move', pokemon, move.name); this.attrLastMove('[still]'); this.add('-fail', pokemon); return false; } }, onDisableMove(pokemon) { if (!pokemon.getItem().isChoice || !pokemon.hasMove(this.effectData.move)) { pokemon.removeVolatile('choicelock'); return; } if (pokemon.ignoringItem()) { return; } for (const moveSlot of pokemon.moveSlots) { if (moveSlot.id !== this.effectData.move) { pokemon.disableMove(moveSlot.id, false, this.effectData.sourceEffect); } } }, }, mustrecharge: { name: 'mustrecharge', id: 'mustrecharge', num: 0, duration: 2, onBeforeMovePriority: 11, onBeforeMove(pokemon) { this.add('cant', pokemon, 'recharge'); pokemon.removeVolatile('mustrecharge'); pokemon.removeVolatile('truant'); return null; }, onStart(pokemon) { this.add('-mustrecharge', pokemon); }, onLockMove: 'recharge', }, futuremove: { // this is a slot condition name: 'futuremove', id: 'futuremove', num: 0, duration: 3, onResidualOrder: 3, onEnd(target) { const data = this.effectData; // time's up; time to hit! :D const move = this.getMove(data.move); if (target.fainted || target === data.source) { this.hint(`${move.name} did not hit because the target is ${(data.fainted ? 'fainted' : 'the user')}.`); return; } this.add('-end', target, 'move: ' + move.name); target.removeVolatile('Protect'); target.removeVolatile('Endure'); if (data.source.hasAbility('infiltrator') && this.gen >= 6) { data.moveData.infiltrates = true; } if (data.source.hasAbility('normalize') && this.gen >= 6) { data.moveData.type = 'Normal'; } if (data.source.hasAbility('adaptability') && this.gen >= 6) { data.moveData.stab = 2; } const hitMove = new this.Data.Move(data.moveData); this.trySpreadMoveHit([target], data.source, /** @type {ActiveMove} */(/** @type {unknown} */(hitMove))); }, }, healreplacement: { // this is a slot condition name: 'healreplacement', id: 'healreplacement', num: 0, onStart(side, source, sourceEffect) { this.effectData.sourceEffect = sourceEffect; this.add('-activate', source, 'healreplacement'); }, onSwitchInPriority: 1, onSwitchIn(target) { if (!target.fainted) { target.heal(target.maxhp); this.add('-heal', target, target.getHealth, '[from] move: ' + this.effectData.sourceEffect, '[zeffect]'); target.side.removeSlotCondition(target, 'healreplacement'); } }, }, stall: { // Protect, Detect, Endure counter name: 'stall', id: 'stall', num: 0, duration: 2, counterMax: 729, onStart() { this.effectData.counter = 3; }, onStallMove(pokemon) { // this.effectData.counter should never be undefined here. // However, just in case, use 1 if it is undefined. let counter = this.effectData.counter || 1; this.debug("Success chance: " + Math.round(100 / counter) + "%"); let success = this.randomChance(1, counter); if (!success) delete pokemon.volatiles['stall']; return success; }, onRestart() { // @ts-ignore if (this.effectData.counter < this.effect.counterMax) { this.effectData.counter *= 3; } this.effectData.duration = 2; }, }, gem: { name: 'gem', id: 'gem', num: 0, duration: 1, affectsFainted: true, onBasePower(basePower, user, target, move) { this.debug('Gem Boost'); return this.chainModify([0x14CD, 0x1000]); }, }, // weather is implemented here since it's so important to the game raindance: { name: 'RainDance', id: 'raindance', num: 0, effectType: 'Weather', duration: 5, durationCallback(source, effect) { if (source && source.hasItem('damprock')) { return 8; } return 5; }, onWeatherModifyDamage(damage, attacker, defender, move) { if (move.type === 'Water') { this.debug('rain water boost'); return this.chainModify(1.5); } if (move.type === 'Fire') { this.debug('rain fire suppress'); return this.chainModify(0.5); } }, onStart(battle, source, effect) { if (effect && effect.effectType === 'Ability') { if (this.gen <= 5) this.effectData.duration = 0; this.add('-weather', 'RainDance', '[from] ability: ' + effect, '[of] ' + source); } else { this.add('-weather', 'RainDance'); } }, onResidualOrder: 1, onResidual() { this.add('-weather', 'RainDance', '[upkeep]'); this.eachEvent('Weather'); }, onEnd() { this.add('-weather', 'none'); }, }, primordialsea: { name: 'PrimordialSea', id: 'primordialsea', num: 0, effectType: 'Weather', duration: 0, onTryMovePriority: 1, onTryMove(attacker, defender, move) { if (move.type === 'Fire' && move.category !== 'Status') { this.debug('Primordial Sea fire suppress'); this.add('-fail', attacker, move, '[from] Primordial Sea'); this.attrLastMove('[still]'); return null; } }, onWeatherModifyDamage(damage, attacker, defender, move) { if (move.type === 'Water') { this.debug('Rain water boost'); return this.chainModify(1.5); } }, onStart(battle, source, effect) { this.add('-weather', 'PrimordialSea', '[from] ability: ' + effect, '[of] ' + source); }, onResidualOrder: 1, onResidual() { this.add('-weather', 'PrimordialSea', '[upkeep]'); this.eachEvent('Weather'); }, onEnd() { this.add('-weather', 'none'); }, }, sunnyday: { name: 'SunnyDay', id: 'sunnyday', num: 0, effectType: 'Weather', duration: 5, durationCallback(source, effect) { if (source && source.hasItem('heatrock')) { return 8; } return 5; }, onWeatherModifyDamage(damage, attacker, defender, move) { if (move.type === 'Fire') { this.debug('Sunny Day fire boost'); return this.chainModify(1.5); } if (move.type === 'Water') { this.debug('Sunny Day water suppress'); return this.chainModify(0.5); } }, onStart(battle, source, effect) { if (effect && effect.effectType === 'Ability') { if (this.gen <= 5) this.effectData.duration = 0; this.add('-weather', 'SunnyDay', '[from] ability: ' + effect, '[of] ' + source); } else { this.add('-weather', 'SunnyDay'); } }, onImmunity(type) { if (type === 'frz') return false; }, onResidualOrder: 1, onResidual() { this.add('-weather', 'SunnyDay', '[upkeep]'); this.eachEvent('Weather'); }, onEnd() { this.add('-weather', 'none'); }, }, desolateland: { name: 'DesolateLand', id: 'desolateland', num: 0, effectType: 'Weather', duration: 0, onTryMovePriority: 1, onTryMove(attacker, defender, move) { if (move.type === 'Water' && move.category !== 'Status') { this.debug('Desolate Land water suppress'); this.add('-fail', attacker, move, '[from] Desolate Land'); this.attrLastMove('[still]'); return null; } }, onWeatherModifyDamage(damage, attacker, defender, move) { if (move.type === 'Fire') { this.debug('Sunny Day fire boost'); return this.chainModify(1.5); } }, onStart(battle, source, effect) { this.add('-weather', 'DesolateLand', '[from] ability: ' + effect, '[of] ' + source); }, onImmunity(type) { if (type === 'frz') return false; }, onResidualOrder: 1, onResidual() { this.add('-weather', 'DesolateLand', '[upkeep]'); this.eachEvent('Weather'); }, onEnd() { this.add('-weather', 'none'); }, }, sandstorm: { name: 'Sandstorm', id: 'sandstorm', num: 0, effectType: 'Weather', duration: 5, durationCallback(source, effect) { if (source && source.hasItem('smoothrock')) { return 8; } return 5; }, // This should be applied directly to the stat before any of the other modifiers are chained // So we give it increased priority. onModifySpDPriority: 10, onModifySpD(spd, pokemon) { if (pokemon.hasType('Rock') && this.field.isWeather('sandstorm')) { return this.modify(spd, 1.5); } }, onStart(battle, source, effect) { if (effect && effect.effectType === 'Ability') { if (this.gen <= 5) this.effectData.duration = 0; this.add('-weather', 'Sandstorm', '[from] ability: ' + effect, '[of] ' + source); } else { this.add('-weather', 'Sandstorm'); } }, onResidualOrder: 1, onResidual() { this.add('-weather', 'Sandstorm', '[upkeep]'); if (this.field.isWeather('sandstorm')) this.eachEvent('Weather'); }, onWeather(target) { this.damage(target.maxhp / 16); }, onEnd() { this.add('-weather', 'none'); }, }, hail: { name: 'Hail', id: 'hail', num: 0, effectType: 'Weather', duration: 5, durationCallback(source, effect) { if (source && source.hasItem('icyrock')) { return 8; } return 5; }, onStart(battle, source, effect) { if (effect && effect.effectType === 'Ability') { if (this.gen <= 5) this.effectData.duration = 0; this.add('-weather', 'Hail', '[from] ability: ' + effect, '[of] ' + source); } else { this.add('-weather', 'Hail'); } }, onResidualOrder: 1, onResidual() { this.add('-weather', 'Hail', '[upkeep]'); if (this.field.isWeather('hail')) this.eachEvent('Weather'); }, onWeather(target) { this.damage(target.maxhp / 16); }, onEnd() { this.add('-weather', 'none'); }, }, deltastream: { name: 'DeltaStream', id: 'deltastream', num: 0, effectType: 'Weather', duration: 0, onEffectiveness(typeMod, target, type, move) { if (move && move.effectType === 'Move' && move.category !== 'Status' && type === 'Flying' && typeMod > 0) { this.add('-activate', '', 'deltastream'); return 0; } }, onStart(battle, source, effect) { this.add('-weather', 'DeltaStream', '[from] ability: ' + effect, '[of] ' + source); }, onResidualOrder: 1, onResidual() { this.add('-weather', 'DeltaStream', '[upkeep]'); this.eachEvent('Weather'); }, onEnd() { this.add('-weather', 'none'); }, }, // Arceus and Silvally's actual typing is implemented here. // Their true typing for all their formes is Normal, and it's only // Multitype and RKS System, respectively, that changes their type, // but their formes are specified to be their corresponding type // in the Pokedex, so that needs to be overridden. // This is mainly relevant for Hackmons Cup and Balanced Hackmons. arceus: { name: 'Arceus', id: 'arceus', num: 493, onTypePriority: 1, onType(types, pokemon) { if (pokemon.transformed) return types; /** @type {string | undefined} */ let type = 'Normal'; if (pokemon.ability === 'multitype') { type = pokemon.getItem().onPlate; if (!type) { type = 'Normal'; } } return [type]; }, }, silvally: { name: 'Silvally', id: 'silvally', num: 773, onTypePriority: 1, onType(types, pokemon) { if (pokemon.transformed) return types; /** @type {string | undefined} */ let type = 'Normal'; if (pokemon.ability === 'rkssystem') { type = pokemon.getItem().onMemory; if (!type) { type = 'Normal'; } } return [type]; }, }, }; exports.BattleStatuses = BattleStatuses;