mirror of
https://github.com/smogon/pokemon-showdown.git
synced 2026-05-06 13:47:24 -05:00
The current implementation of ModifyAtk, ModifyDef, etc. is to pass the actual stat into the event, have event handlers run battle.chainModify(), ModifySpe passes in a modifier variable, calls battle.chain(), and then ends with battle.modify() using the modifier variable. The way chainModify is implemented within the event system also means that it chains together modifiers in the same manner and finishes with battle.modify(), meaning that there is no need for ModifySpe to use battle.chain() and battle.modify() manually. This also fixes the effect of BattlePokemon#getStat on stats other than speed.
647 lines
18 KiB
JavaScript
647 lines
18 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 (spe, pokemon) {
|
|
if (!pokemon.hasAbility('quickfeet')) {
|
|
return this.chainModify(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;
|
|
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: 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.damage(this.getDamage(pokemon, pokemon, 40), pokemon, pokemon, {
|
|
id: 'confused',
|
|
effectType: 'Move',
|
|
type: '???'
|
|
});
|
|
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)) {
|
|
delete pokemon.volatiles['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;
|
|
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.ignoringItem()) {
|
|
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 = pokemon.getItem().onPlate;
|
|
if (!type || type === true) {
|
|
type = 'Normal';
|
|
}
|
|
}
|
|
pokemon.setType(type, true);
|
|
}
|
|
}
|
|
};
|