function clampIntRange(num, min, max) { num = Math.floor(num); if (num < min) num = min; if (typeof max !== 'undefined' && num > max) num = max; return num; } exports.BattleStatuses = { brn: { effectType: 'Status', onStart: function(target) { this.add('-status', target, 'brn'); }, onBasePower: function(basePower, attacker, defender, move) { if (move && move.category === 'Physical' && attacker && attacker.ability !== 'guts' && move.id !== 'facade') { return this.chainModify(0.5); // This should really take place directly in the damage function but it's here for now } }, onResidualOrder: 9, onResidual: function(pokemon) { this.damage(pokemon.maxhp/8); } }, par: { effectType: 'Status', onStart: function(target) { this.add('-status', target, 'par'); }, onModifySpe: function(speMod, pokemon) { if (pokemon.ability !== 'quickfeet') { return this.chain(speMod, 0.25); } }, onBeforeMovePriority: 2, onBeforeMove: function(pokemon) { if (this.random(4) === 0) { this.add('cant', pokemon, 'par'); return false; } } }, slp: { effectType: 'Status', onStart: function(target) { this.add('-status', target, 'slp'); // 1-3 turns this.effectData.startTime = this.random(2,5); this.effectData.time = this.effectData.startTime; }, onBeforeMovePriority: 2, onBeforeMove: function(pokemon, target, move) { if (pokemon.getAbility().isHalfSleep) { 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: { effectType: 'Status', onStart: function(target) { this.add('-status', target, 'frz'); }, onBeforeMovePriority: 2, onBeforeMove: function(pokemon, target, move) { if (move.thawsUser || this.random(5) === 0) { pokemon.cureStatus(); return; } this.add('cant', pokemon, 'frz'); return false; }, onHit: function(target, source, move) { if (move.type === 'Fire' && move.category !== 'Status') { target.cureStatus(); } } }, psn: { effectType: 'Status', onStart: function(target) { this.add('-status', target, 'psn'); }, onResidualOrder: 9, onResidual: function(pokemon) { this.damage(pokemon.maxhp/8); } }, tox: { effectType: 'Status', onStart: function(target) { this.add('-status', target, 'tox'); this.effectData.stage = 0; }, onSwitchIn: function() { this.effectData.stage = 0; }, onResidualOrder: 9, onResidual: function(pokemon) { if (this.effectData.stage < 15) { this.effectData.stage++; } this.damage(clampIntRange(pokemon.maxhp/16, 1)*this.effectData.stage); } }, confusion: { // this is a volatile status onStart: function(target, source) { var result = this.runEvent('TryConfusion', target, source); if (!result) return result; this.add('-start', target, 'confusion'); this.effectData.time = this.random(2,6); }, onEnd: function(target) { this.add('-end', target, 'confusion'); }, onBeforeMove: function(pokemon) { pokemon.volatiles.confusion.time--; if (!pokemon.volatiles.confusion.time) { pokemon.removeVolatile('confusion'); return; } this.add('-activate', pokemon, 'confusion'); if (this.random(2) === 0) { return; } this.directDamage(this.getDamage(pokemon,pokemon,40)); return false; } }, flinch: { duration: 1, onBeforeMovePriority: 1, onBeforeMove: function(pokemon) { if (!this.runEvent('Flinch', pokemon)) { return; } this.add('cant', pokemon, 'flinch'); return false; } }, trapped: { noCopy: true, onModifyPokemon: function(pokemon) { if (!this.effectData.source || !this.effectData.source.isActive) { delete pokemon.volatiles['trapped']; return; } pokemon.tryTrap(); }, onStart: function(target) { this.add('-activate', target, 'trapped') } }, partiallytrapped: { duration: 5, durationCallback: function(target, source) { if (source.item === 'gripclaw') return 8; return this.random(5,7); }, onStart: function(pokemon, source) { this.add('-activate', pokemon, 'move: ' +this.effectData.sourceEffect, '[of] '+source); }, onResidualOrder: 11, onResidual: function(pokemon) { if (this.effectData.source && (!this.effectData.source.isActive || this.effectData.source.hp <= 0)) { pokemon.removeVolatile('partiallytrapped'); return; } if (this.effectData.source.item === 'bindingband') { this.damage(pokemon.maxhp/6); } else { this.damage(pokemon.maxhp/8); } }, onEnd: function(pokemon) { this.add('-end', pokemon, this.effectData.sourceEffect, '[partiallytrapped]'); }, onModifyPokemon: function(pokemon) { pokemon.tryTrap(); } }, lockedmove: { // Outrage, Thrash, Petal Dance... duration: 2, onResidual: function(target) { if (target.status === 'slp') { // don't lock, and bypass confusion for calming delete target.volatiles['lockedmove']; } this.effectData.trueDuration--; }, onStart: function(target, source, effect) { this.effectData.trueDuration = this.random(2,4); this.effectData.move = effect.id; }, onRestart: function() { if (this.effectData.trueDuration >= 2) { this.effectData.duration = 2; } }, onEnd: function(target) { if (this.effectData.trueDuration > 1) return; this.add('-end', target, 'rampage'); target.addVolatile('confusion'); }, onLockMove: function(pokemon) { return this.effectData.move; } }, choicelock: { onStart: function(pokemon) { this.effectData.move = this.activeMove.id; if (!this.effectData.move) return false; }, onModifyPokemon: function(pokemon) { if (!pokemon.getItem().isChoice || !pokemon.hasMove(this.effectData.move)) { pokemon.removeVolatile('choicelock'); return; } if (pokemon.ignore['Item']) { return; } var moves = pokemon.moveset; for (var i=0; i 0) { finished = false; continue; } // time's up; time to hit! :D var target = side.foe.active[posData.targetPosition]; var move = this.getMove(posData.move); if (target.fainted) { this.add('-hint', ''+move.name+' did not hit because the target is fainted.'); this.effectData.positions[i] = null; continue; } this.add('-message', ''+move.name+' hit! (placeholder)'); target.removeVolatile('Protect'); target.removeVolatile('Endure'); if (typeof posData.moveData.affectedByImmunities === 'undefined') { posData.moveData.affectedByImmunities = true; } this.moveHit(target, posData.source, move, posData.moveData); this.effectData.positions[i] = null; } if (finished) { side.removeSideCondition('futuremove'); } } }, stall: { // Protect, Detect, Endure counter duration: 2, counterMax: 256, onStart: function() { this.effectData.counter = 2; }, onStallMove: function() { // this.effectData.counter should never be undefined here. // However, just in case, use 1 if it is undefined. var counter = this.effectData.counter || 1; if (counter >= 256) { // 2^32 - special-cased because Battle.random(n) can't handle n > 2^16 - 1 return (this.random()*4294967296 < 1); } this.debug("Success chance: "+Math.round(100/counter)+"%"); return (this.random(counter) === 0); }, onRestart: function() { if (this.effectData.counter < this.effect.counterMax) { this.effectData.counter *= 2; } this.effectData.duration = 2; } }, gem: { duration: 1, onBasePower: function(basePower, user, target, move) { this.debug('Gem Boost'); return this.chainModify([0x14CD, 0x1000]); } }, // weather // weather is implemented here since it's so important to the game raindance: { effectType: 'Weather', duration: 5, durationCallback: function(source, effect) { if (source && source.item === 'damprock') { return 8; } return 5; }, onBasePower: function(basePower, 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: function(battle, source, effect) { if (effect && effect.effectType === 'Ability' && this.gen <= 5) { this.effectData.duration = 0; this.add('-weather', 'RainDance', '[from] ability: '+effect, '[of] '+source); } else { this.add('-weather', 'RainDance'); } }, onResidualOrder: 1, onResidual: function() { this.add('-weather', 'RainDance', '[upkeep]'); this.eachEvent('Weather'); }, onEnd: function() { this.add('-weather', 'none'); } }, sunnyday: { effectType: 'Weather', duration: 5, durationCallback: function(source, effect) { if (source && source.item === 'heatrock') { return 8; } return 5; }, onBasePower: function(basePower, 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: function(battle, source, effect) { if (effect && effect.effectType === 'Ability' && this.gen <= 5) { this.effectData.duration = 0; this.add('-weather', 'SunnyDay', '[from] ability: '+effect, '[of] '+source); } else { this.add('-weather', 'SunnyDay'); } }, onImmunity: function(type) { if (type === 'frz') return false; }, onResidualOrder: 1, onResidual: function() { this.add('-weather', 'SunnyDay', '[upkeep]'); this.eachEvent('Weather'); }, onEnd: function() { this.add('-weather', 'none'); } }, sandstorm: { effectType: 'Weather', duration: 5, durationCallback: function(source, effect) { if (source && source.item === '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: function(spd, pokemon) { if (pokemon.hasType('Rock') && this.isWeather('sandstorm')) { return this.modify(spd, 1.5); } }, onStart: function(battle, source, effect) { if (effect && effect.effectType === 'Ability' && this.gen <= 5) { this.effectData.duration = 0; this.add('-weather', 'Sandstorm', '[from] ability: '+effect, '[of] '+source); } else { this.add('-weather', 'Sandstorm'); } }, onResidualOrder: 1, onResidual: function() { this.add('-weather', 'Sandstorm', '[upkeep]'); if (this.isWeather('sandstorm')) this.eachEvent('Weather'); }, onWeather: function(target) { this.damage(target.maxhp/16); }, onEnd: function() { this.add('-weather', 'none'); } }, hail: { effectType: 'Weather', duration: 5, durationCallback: function(source, effect) { if (source && source.item === 'icyrock') { return 8; } return 5; }, onStart: function(battle, source, effect) { if (effect && effect.effectType === 'Ability' && this.gen <= 5) { this.effectData.duration = 0; this.add('-weather', 'Hail', '[from] ability: '+effect, '[of] '+source); } else { this.add('-weather', 'Hail'); } }, onResidualOrder: 1, onResidual: function() { this.add('-weather', 'Hail', '[upkeep]'); if (this.isWeather('hail')) this.eachEvent('Weather'); }, onWeather: function(target) { this.damage(target.maxhp/16); }, onEnd: function() { this.add('-weather', 'none'); } }, arceus: { // Arceus's actual typing is implemented here // Arceus's true typing for all its formes is Normal, and it's only // Multitype that changes its type, but its formes are specified to // be their corresponding type in the Pokedex, so that needs to be // overridden. This is mainly relevant for Hackmons and Balanced // Hackmons. onModifyPokemon: function(pokemon) { if (pokemon.transformed) return; var type = 'Normal'; if (pokemon.ability === 'multitype') { var type = this.runEvent('Plate', pokemon); if (!type || type === true) { type = 'Normal'; } } pokemon.types = [type]; } } };