mirror of
https://github.com/smogon/pokemon-showdown.git
synced 2026-05-20 05:58:08 -05:00
Inverse Mod needs to go first, to calculate the negated effectiveness. Disguise goes second, to suppress effectiveness. Delta Stream goes third, to weaken moves super-effective against Flying types. Tar Shot goes last, to make its victim weak to Fire type moves. This allows the existing test for Delta Stream with Tar Shot to pass. Additionally a new test for Delta Stream with Inverse Mod now passes. A test for Flying Press with Inverse Mod is also included.
757 lines
21 KiB
TypeScript
757 lines
21 KiB
TypeScript
export const BattleStatuses: {[k: string]: PureEffectData} = {
|
|
brn: {
|
|
name: 'brn',
|
|
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.baseMaxhp / 16);
|
|
},
|
|
},
|
|
par: {
|
|
name: 'par',
|
|
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',
|
|
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',
|
|
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.species.name === 'Shaymin-Sky' && target.baseSpecies.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',
|
|
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.baseMaxhp / 8);
|
|
},
|
|
},
|
|
tox: {
|
|
name: 'tox',
|
|
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.dex.clampIntRange(pokemon.baseMaxhp / 16, 1) * this.effectData.stage);
|
|
},
|
|
},
|
|
confusion: {
|
|
name: 'confusion',
|
|
// 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;
|
|
const damage = this.getDamage(pokemon, pokemon, 40);
|
|
if (typeof damage !== 'number') throw new Error("Confusion damage not dealt");
|
|
const activeMove = {id: toID('confused'), effectType: 'Move', type: '???'};
|
|
this.damage(damage, pokemon, pokemon, activeMove as ActiveMove);
|
|
return false;
|
|
},
|
|
},
|
|
flinch: {
|
|
name: 'flinch',
|
|
duration: 1,
|
|
onBeforeMovePriority: 8,
|
|
onBeforeMove(pokemon) {
|
|
this.add('cant', pokemon, 'flinch');
|
|
this.runEvent('Flinch', pokemon);
|
|
return false;
|
|
},
|
|
},
|
|
trapped: {
|
|
name: 'trapped',
|
|
noCopy: true,
|
|
onTrapPokemon(pokemon) {
|
|
pokemon.tryTrap();
|
|
},
|
|
onStart(target) {
|
|
this.add('-activate', target, 'trapped');
|
|
},
|
|
},
|
|
trapper: {
|
|
name: 'trapper',
|
|
noCopy: true,
|
|
},
|
|
partiallytrapped: {
|
|
name: 'partiallytrapped',
|
|
duration: 5,
|
|
durationCallback(target, source) {
|
|
if (source?.hasItem('gripclaw')) return 8;
|
|
return this.random(5, 7);
|
|
},
|
|
onStart(pokemon, source) {
|
|
this.add('-activate', pokemon, 'move: ' + this.effectData.sourceEffect, '[of] ' + source);
|
|
this.effectData.boundDivisor = source.hasItem('bindingband') ? 6 : 8;
|
|
},
|
|
onResidualOrder: 11,
|
|
onResidual(pokemon) {
|
|
const source = this.effectData.source;
|
|
// G-Max Centiferno and G-Max Sandblast continue even after the user leaves the field
|
|
const gmaxEffect = ['gmaxcentiferno', 'gmaxsandblast'].includes(this.effectData.sourceEffect.id);
|
|
if (source && (!source.isActive || source.hp <= 0 || !source.activeTurns) && !gmaxEffect) {
|
|
delete pokemon.volatiles['partiallytrapped'];
|
|
this.add('-end', pokemon, this.effectData.sourceEffect, '[partiallytrapped]', '[silent]');
|
|
return;
|
|
}
|
|
this.damage(pokemon.baseMaxhp / this.effectData.boundDivisor);
|
|
},
|
|
onEnd(pokemon) {
|
|
this.add('-end', pokemon, this.effectData.sourceEffect, '[partiallytrapped]');
|
|
},
|
|
onTrapPokemon(pokemon) {
|
|
const gmaxEffect = ['gmaxcentiferno', 'gmaxsandblast'].includes(this.effectData.sourceEffect.id);
|
|
if (this.effectData.source?.isActive || gmaxEffect) pokemon.tryTrap();
|
|
},
|
|
},
|
|
lockedmove: {
|
|
// Outrage, Thrash, Petal Dance...
|
|
name: 'lockedmove',
|
|
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) {
|
|
if (pokemon.volatiles['dynamax']) return;
|
|
return this.effectData.move;
|
|
},
|
|
},
|
|
twoturnmove: {
|
|
// Skull Bash, SolarBeam, Sky Drop...
|
|
name: 'twoturnmove',
|
|
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',
|
|
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() && !pokemon.volatiles['dynamax'] &&
|
|
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.debug("Disabled by Choice item lock");
|
|
this.add('-fail', pokemon);
|
|
return false;
|
|
}
|
|
},
|
|
onDisableMove(pokemon) {
|
|
if (!pokemon.getItem().isChoice || !pokemon.hasMove(this.effectData.move)) {
|
|
pokemon.removeVolatile('choicelock');
|
|
return;
|
|
}
|
|
if (pokemon.ignoringItem() || pokemon.volatiles['dynamax']) {
|
|
return;
|
|
}
|
|
for (const moveSlot of pokemon.moveSlots) {
|
|
if (moveSlot.id !== this.effectData.move) {
|
|
pokemon.disableMove(moveSlot.id, false, this.effectData.sourceEffect);
|
|
}
|
|
}
|
|
},
|
|
},
|
|
mustrecharge: {
|
|
name: 'mustrecharge',
|
|
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',
|
|
duration: 3,
|
|
onResidualOrder: 3,
|
|
onEnd(target) {
|
|
const data = this.effectData;
|
|
// time's up; time to hit! :D
|
|
const move = this.dex.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;
|
|
}
|
|
// @ts-ignore
|
|
const hitMove: ActiveMove = new this.dex.Data.Move(data.moveData);
|
|
|
|
this.trySpreadMoveHit([target], data.source, hitMove);
|
|
},
|
|
},
|
|
healreplacement: {
|
|
// this is a slot condition
|
|
name: 'healreplacement',
|
|
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',
|
|
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.
|
|
const counter = this.effectData.counter || 1;
|
|
this.debug("Success chance: " + Math.round(100 / counter) + "%");
|
|
const 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',
|
|
duration: 1,
|
|
affectsFainted: true,
|
|
onBasePowerPriority: 14,
|
|
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',
|
|
effectType: 'Weather',
|
|
duration: 5,
|
|
durationCallback(source, effect) {
|
|
if (source?.hasItem('damprock')) {
|
|
return 8;
|
|
}
|
|
return 5;
|
|
},
|
|
onWeatherModifyDamage(damage, attacker, defender, move) {
|
|
if (defender.hasItem('utilityumbrella')) return;
|
|
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?.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',
|
|
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 (defender.hasItem('utilityumbrella')) return;
|
|
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',
|
|
effectType: 'Weather',
|
|
duration: 5,
|
|
durationCallback(source, effect) {
|
|
if (source?.hasItem('heatrock')) {
|
|
return 8;
|
|
}
|
|
return 5;
|
|
},
|
|
onWeatherModifyDamage(damage, attacker, defender, move) {
|
|
if (defender.hasItem('utilityumbrella')) return;
|
|
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?.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, pokemon) {
|
|
if (pokemon.hasItem('utilityumbrella')) return;
|
|
if (type === 'frz') return false;
|
|
},
|
|
onResidualOrder: 1,
|
|
onResidual() {
|
|
this.add('-weather', 'SunnyDay', '[upkeep]');
|
|
this.eachEvent('Weather');
|
|
},
|
|
onEnd() {
|
|
this.add('-weather', 'none');
|
|
},
|
|
},
|
|
desolateland: {
|
|
name: 'DesolateLand',
|
|
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 (defender.hasItem('utilityumbrella')) return;
|
|
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, pokemon) {
|
|
if (pokemon.hasItem('utilityumbrella')) return;
|
|
if (type === 'frz') return false;
|
|
},
|
|
onResidualOrder: 1,
|
|
onResidual() {
|
|
this.add('-weather', 'DesolateLand', '[upkeep]');
|
|
this.eachEvent('Weather');
|
|
},
|
|
onEnd() {
|
|
this.add('-weather', 'none');
|
|
},
|
|
},
|
|
sandstorm: {
|
|
name: 'Sandstorm',
|
|
effectType: 'Weather',
|
|
duration: 5,
|
|
durationCallback(source, effect) {
|
|
if (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?.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.baseMaxhp / 16);
|
|
},
|
|
onEnd() {
|
|
this.add('-weather', 'none');
|
|
},
|
|
},
|
|
hail: {
|
|
name: 'Hail',
|
|
effectType: 'Weather',
|
|
duration: 5,
|
|
durationCallback(source, effect) {
|
|
if (source?.hasItem('icyrock')) {
|
|
return 8;
|
|
}
|
|
return 5;
|
|
},
|
|
onStart(battle, source, effect) {
|
|
if (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.baseMaxhp / 16);
|
|
},
|
|
onEnd() {
|
|
this.add('-weather', 'none');
|
|
},
|
|
},
|
|
deltastream: {
|
|
name: 'DeltaStream',
|
|
effectType: 'Weather',
|
|
duration: 0,
|
|
onEffectivenessPriority: -1,
|
|
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');
|
|
},
|
|
},
|
|
|
|
dynamax: {
|
|
name: 'Dynamax',
|
|
noCopy: true,
|
|
duration: 3,
|
|
onStart(pokemon) {
|
|
pokemon.removeVolatile('substitute');
|
|
if (pokemon.illusion) this.singleEvent('End', this.dex.getAbility('Illusion'), pokemon.abilityData, pokemon);
|
|
if (pokemon.volatiles['torment']) {
|
|
delete pokemon.volatiles['torment'];
|
|
this.add('-end', pokemon, 'Torment', '[silent]');
|
|
}
|
|
if (['cramorantgulping', 'cramorantgorging'].includes(pokemon.species.id) && !pokemon.transformed) {
|
|
pokemon.formeChange('cramorant');
|
|
}
|
|
this.add('-start', pokemon, 'Dynamax');
|
|
if (pokemon.canGigantamax) this.add('-formechange', pokemon, pokemon.canGigantamax);
|
|
if (pokemon.baseSpecies.name === 'Shedinja') return;
|
|
|
|
// Changes based on dynamax level, 2 is max (at LVL 10)
|
|
const ratio = 2;
|
|
|
|
pokemon.maxhp = Math.floor(pokemon.maxhp * ratio);
|
|
pokemon.hp = Math.ceil(pokemon.hp * ratio);
|
|
this.add('-heal', pokemon, pokemon.getHealth, '[silent]');
|
|
},
|
|
onTryAddVolatile(status, pokemon) {
|
|
if (status.id === 'flinch') return null;
|
|
},
|
|
onBeforeSwitchOutPriority: -1,
|
|
onBeforeSwitchOut(pokemon) {
|
|
pokemon.removeVolatile('dynamax');
|
|
},
|
|
onSourceModifyDamage(damage, source, target, move) {
|
|
if (move.id === 'behemothbash' || move.id === 'behemothblade' || move.id === 'dynamaxcannon') {
|
|
return this.chainModify(2);
|
|
}
|
|
},
|
|
onDragOutPriority: 2,
|
|
onDragOut(pokemon) {
|
|
this.add('-block', pokemon, 'Dynamax');
|
|
return null;
|
|
},
|
|
onResidualPriority: -100,
|
|
onEnd(pokemon) {
|
|
this.add('-end', pokemon, 'Dynamax');
|
|
if (pokemon.canGigantamax) this.add('-formechange', pokemon, pokemon.species.name);
|
|
if (pokemon.baseSpecies.name === 'Shedinja') return;
|
|
pokemon.hp = pokemon.getUndynamaxedHP();
|
|
pokemon.maxhp = pokemon.baseMaxhp;
|
|
this.add('-heal', pokemon, pokemon.getHealth, '[silent]');
|
|
},
|
|
},
|
|
|
|
// 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',
|
|
onTypePriority: 1,
|
|
onType(types, pokemon) {
|
|
if (pokemon.transformed || pokemon.ability !== 'multitype' && this.gen >= 8) return types;
|
|
let type: string | undefined = 'Normal';
|
|
if (pokemon.ability === 'multitype') {
|
|
type = pokemon.getItem().onPlate;
|
|
if (!type) {
|
|
type = 'Normal';
|
|
}
|
|
}
|
|
return [type];
|
|
},
|
|
},
|
|
silvally: {
|
|
name: 'Silvally',
|
|
onTypePriority: 1,
|
|
onType(types, pokemon) {
|
|
if (pokemon.transformed || pokemon.ability !== 'rkssystem' && this.gen >= 8) return types;
|
|
let type: string | undefined = 'Normal';
|
|
if (pokemon.ability === 'rkssystem') {
|
|
type = pokemon.getItem().onMemory;
|
|
if (!type) {
|
|
type = 'Normal';
|
|
}
|
|
}
|
|
return [type];
|
|
},
|
|
},
|
|
};
|