pokemon-showdown/data/statuses.js
Kevin Lau 0ec4ff912a Battle Engine Refactor: Add a DisableMove event
The intention for this event is to remove the move disabling code away
from ModifyPokemon and to an event that can be run a fewer number of
times. Since the disabledMoves index is used to gray-out moves that cannot
be used, and not for any other purpose, there is no need for the related
code to be run at the ModifyPokemon timing, instead working better as a
once per turn event.
2015-05-04 21:17:00 -07:00

640 lines
17 KiB
JavaScript

exports.BattleStatuses = {
brn: {
effectType: 'Status',
onStart: function (target, source, sourceEffect) {
if (sourceEffect && sourceEffect.id === 'flameorb') {
this.add('-status', target, 'brn', '[from] item: Flame Orb');
return;
}
this.add('-status', target, 'brn');
},
// Damage reduction is handled directly in the battle-engine.js damage function
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.hasAbility('quickfeet')) {
return this.chain(speMod, 0.25);
}
},
onBeforeMovePriority: 1,
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: 10,
onBeforeMove: function (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: {
effectType: 'Status',
onStart: function (target) {
this.add('-status', target, 'frz');
if (target.template.species === 'Shaymin-Sky' && target.baseTemplate.baseSpecies === 'Shaymin') {
var template = this.getTemplate('Shaymin');
target.formeChange(template);
target.baseTemplate = template;
target.setAbility(template.abilities['0']);
target.baseAbility = target.ability;
target.details = template.species + (target.level === 100 ? '' : ', L' + target.level) + (target.gender === '' ? '' : ', ' + target.gender) + (target.set.shiny ? ', shiny' : '');
this.add('detailschange', target, target.details);
this.add('-formechange', target, 'Shaymin', '[msg]');
}
},
onBeforeMovePriority: 10,
onBeforeMove: function (pokemon, target, move) {
if (move.flags['defrost']) return;
if (this.random(5) === 0) {
pokemon.cureStatus();
return;
}
this.add('cant', pokemon, 'frz');
return false;
},
onModifyMove: function (move, pokemon) {
if (move.flags['defrost']) {
this.add('-curestatus', pokemon, 'frz', '[from] move: ' + move);
pokemon.setStatus('');
}
},
onHit: function (target, source, move) {
if (move.thawsTarget || 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, source, sourceEffect) {
this.effectData.stage = 0;
if (sourceEffect && sourceEffect.id === 'toxicorb') {
this.add('-status', target, 'tox', '[from] item: Toxic Orb');
return;
}
this.add('-status', target, 'tox');
},
onSwitchIn: function () {
this.effectData.stage = 0;
},
onResidualOrder: 9,
onResidual: function (pokemon) {
if (this.effectData.stage < 15) {
this.effectData.stage++;
}
this.damage(this.clampIntRange(pokemon.maxhp / 16, 1) * this.effectData.stage);
}
},
confusion: {
// this is a volatile status
onStart: function (target, source, sourceEffect) {
var result = this.runEvent('TryConfusion', target, source, sourceEffect);
if (!result) return result;
this.add('-start', target, 'confusion');
this.effectData.time = this.random(2, 6);
},
onEnd: function (target) {
this.add('-end', target, 'confusion');
},
onBeforeMovePriority: 3,
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: 8,
onBeforeMove: function (pokemon) {
if (!this.runEvent('Flinch', pokemon)) {
return;
}
this.add('cant', pokemon, 'flinch');
return false;
}
},
trapped: {
noCopy: true,
onModifyPokemon: function (pokemon) {
pokemon.tryTrap();
},
onStart: function (target) {
this.add('-activate', target, 'trapped');
}
},
trapper: {
noCopy: true
},
partiallytrapped: {
duration: 5,
durationCallback: function (target, source) {
if (source.hasItem('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.hasItem('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;
}
},
twoturnmove: {
// Skull Bash, SolarBeam, Sky Drop...
duration: 2,
onStart: function (target, source, effect) {
this.effectData.move = effect.id;
// source and target are reversed since the event target is the
// pokemon using the two-turn move
this.effectData.targetLoc = this.getTargetLoc(source, target);
target.addVolatile(effect.id, source);
},
onEnd: function (target) {
target.removeVolatile(this.effectData.move);
},
onLockMove: function () {
return this.effectData.move;
},
onLockMoveTarget: function () {
return this.effectData.targetLoc;
}
},
choicelock: {
onStart: function (pokemon) {
if (!this.activeMove.id || this.activeMove.sourceEffect && this.activeMove.sourceEffect !== this.activeMove.id) return false;
this.effectData.move = this.activeMove.id;
},
onDisableMove: 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 < moves.length; i++) {
if (moves[i].id !== this.effectData.move) {
pokemon.disableMove(moves[i].id, false, this.effectData.sourceEffect);
}
}
}
},
mustrecharge: {
duration: 2,
onBeforeMovePriority: 11,
onBeforeMove: function (pokemon) {
this.add('cant', pokemon, 'recharge');
pokemon.removeVolatile('mustrecharge');
return false;
},
onLockMove: function (pokemon) {
this.add('-mustrecharge', pokemon);
return 'recharge';
}
},
futuremove: {
// this is a side condition
onStart: function (side) {
this.effectData.positions = [];
for (var i = 0; i < side.active.length; i++) {
this.effectData.positions[i] = null;
}
},
onResidualOrder: 3,
onResidual: function (side) {
var finished = true;
for (var i = 0; i < side.active.length; i++) {
var posData = this.effectData.positions[i];
if (!posData) continue;
posData.duration--;
if (posData.duration > 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('-end', target, 'move: ' + move.name);
target.removeVolatile('Protect');
target.removeVolatile('Endure');
if (posData.moveData.ignoreImmunity === undefined) {
posData.moveData.ignoreImmunity = false;
}
if (target.hasAbility('wonderguard') && this.gen > 5) {
this.debug('Wonder Guard immunity: ' + move.id);
if (target.runEffectiveness(move) <= 0) {
this.add('-activate', target, 'ability: Wonder Guard');
this.effectData.positions[i] = null;
return null;
}
}
// Prior to gen 5, these moves had no STAB and no effectiveness.
// This is done here and to moveData's type for two reasons:
// - modifyMove event happens before the moveHit function is run.
// - moveData here is different from move, as one is generated here and the other by the move itself.
// So here we centralise any future hit move getting typeless on hit as it should be.
if (this.gen < 5) {
posData.moveData.type = '???';
}
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: 729,
onStart: function () {
this.effectData.counter = 3;
},
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;
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 *= 3;
}
this.effectData.duration = 2;
}
},
gem: {
duration: 1,
affectsFainted: true,
onBasePower: function (basePower, user, target, move) {
this.debug('Gem Boost');
return this.chainModify([0x14CD, 0x1000]);
}
},
aura: {
duration: 1,
onBasePowerPriority: 8,
onBasePower: function (basePower, user, target, move) {
var modifier = 0x1547;
this.debug('Aura Boost');
if (user.volatiles['aurabreak']) {
modifier = 0x0C00;
this.debug('Aura Boost reverted by Aura Break');
}
return this.chainModify([modifier, 0x1000]);
}
},
// weather is implemented here since it's so important to the game
raindance: {
effectType: 'Weather',
duration: 5,
durationCallback: function (source, effect) {
if (source && source.hasItem('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');
}
},
primordialsea: {
effectType: 'Weather',
duration: 0,
onTryMove: function (target, source, effect) {
if (effect.type === 'Fire' && effect.category !== 'Status') {
this.debug('Primordial Sea fire suppress');
this.add('-fail', source, effect, '[from] Primordial Sea');
return null;
}
},
onBasePower: function (basePower, attacker, defender, move) {
if (move.type === 'Water') {
this.debug('Rain water boost');
return this.chainModify(1.5);
}
},
onStart: function () {
this.add('-weather', 'PrimordialSea');
},
onResidualOrder: 1,
onResidual: function () {
this.add('-weather', 'PrimordialSea', '[upkeep]');
this.eachEvent('Weather');
},
onEnd: function () {
this.add('-weather', 'none');
}
},
sunnyday: {
effectType: 'Weather',
duration: 5,
durationCallback: function (source, effect) {
if (source && source.hasItem('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');
}
},
desolateland: {
effectType: 'Weather',
duration: 0,
onTryMove: function (target, source, effect) {
if (effect.type === 'Water' && effect.category !== 'Status') {
this.debug('Desolate Land water suppress');
this.add('-fail', source, effect, '[from] Desolate Land');
return null;
}
},
onBasePower: function (basePower, attacker, defender, move) {
if (move.type === 'Fire') {
this.debug('Sunny Day fire boost');
return this.chainModify(1.5);
}
},
onStart: function () {
this.add('-weather', 'DesolateLand');
},
onImmunity: function (type) {
if (type === 'frz') return false;
},
onResidualOrder: 1,
onResidual: function () {
this.add('-weather', 'DesolateLand', '[upkeep]');
this.eachEvent('Weather');
},
onEnd: function () {
this.add('-weather', 'none');
}
},
sandstorm: {
effectType: 'Weather',
duration: 5,
durationCallback: function (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: 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.hasItem('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');
}
},
deltastream: {
effectType: 'Weather',
duration: 0,
onEffectiveness: function (typeMod, target, type, move) {
if (move && move.effectType === 'Move' && type === 'Flying' && typeMod > 0) {
this.add('-activate', '', 'deltastream');
return 0;
}
},
onStart: function () {
this.add('-weather', 'DeltaStream');
},
onResidualOrder: 1,
onResidual: function () {
this.add('-weather', 'DeltaStream', '[upkeep]');
this.eachEvent('Weather');
},
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.
onSwitchInPriority: 101,
onSwitchIn: function (pokemon) {
var type = 'Normal';
if (pokemon.ability === 'multitype') {
type = this.runEvent('Plate', pokemon);
if (!type || type === true) {
type = 'Normal';
}
}
pokemon.setType(type, true);
}
}
};