mirror of
https://github.com/smogon/pokemon-showdown.git
synced 2026-04-26 10:48:53 -05:00
* adding new mons * adding new mons * adding new mons * adding new mons * adding focus band changes * add moves * +new mons * +new mons * +new mons * +new mons * +sinister arrows * +new abilities * fix * fix * fix * fixing sinister arrows * fixing oceanic blessing * fixing sinister arrows clarity * oops * fixing oceanic blessing * fixing sinister arrows * oop * fixing biogenesis * fixing oceanic blessing message * fixing some stuff * fixing sinister arrows * oops * updating test mons * removing test code * kyogre level down * lint issues * lint issues * lint fix (why) * lint badly * adding new ChatBats mons * adding new mons * adding new mons * new mons * raticite + fixing focus band * adding jello body * fixing electric terrain + Volbeat and adding Jello Body * adding last breakfast * adding nibble nibble * fix * fix * fixing electric terrain * fixing crowverload * fixing focus band hopefully * fix focus band (final) * testing breakfast * fixing breakfast * fixes * hopefully fixing breakfast * oops * removing debug text and adding descriptions * description * balance changes + fixing raticate ability * removing test code * lint fix * lint * lint fixes * lint fix for the lint fix * lint fix * fixing team generation failing sometimes
1753 lines
52 KiB
TypeScript
1753 lines
52 KiB
TypeScript
export const Moves: import('../../../sim/dex-moves').ModdedMoveDataTable = {
|
|
ancientpower: {
|
|
inherit: true,
|
|
category: "Physical",
|
|
secondary: null,
|
|
// Ancient Power is physical and boosts on-kill
|
|
onAfterMoveSecondarySelf(pokemon, target, move) {
|
|
if (!target || target.fainted || target.hp <= 0) {
|
|
this.boost({ atk: 1, def: 1, spa: 1, spd: 1, spe: 1 }, pokemon, pokemon, move);
|
|
}
|
|
},
|
|
desc: "If this move causes the opponent to faint, raises the user's Attack, Defense, Special Attack, Special Defense, and Speed by 1 stage.",
|
|
shortDesc: "Raise all stats by 1 if this move KOs the target.",
|
|
},
|
|
sandsearstorm: {
|
|
// Now always hits in Sand in addition to Rain
|
|
inherit: true,
|
|
onModifyMove(move, pokemon, target) {
|
|
if (target && ['raindance', 'sandstorm'].includes(target.effectiveWeather())) {
|
|
move.accuracy = true;
|
|
}
|
|
},
|
|
},
|
|
mountainmaw: {
|
|
// Copied from Psychic Fangs, just changed to be Rock type
|
|
num: -101,
|
|
accuracy: 100,
|
|
basePower: 85,
|
|
category: "Physical",
|
|
name: "Mountain Maw",
|
|
pp: 10,
|
|
priority: 0,
|
|
flags: { contact: 1, protect: 1, mirror: 1, metronome: 1, bite: 1 },
|
|
onTryMove() {
|
|
this.attrLastMove('[still]');
|
|
},
|
|
onTryHit(pokemon) {
|
|
// will shatter screens through sub, before you hit
|
|
pokemon.side.removeSideCondition('reflect');
|
|
pokemon.side.removeSideCondition('lightscreen');
|
|
pokemon.side.removeSideCondition('auroraveil');
|
|
},
|
|
onPrepareHit(target, source) {
|
|
this.add('-anim', source, 'Crunch', target);
|
|
this.add('-anim', source, 'Rock Slide', target);
|
|
},
|
|
secondary: null,
|
|
target: "normal",
|
|
type: "Rock",
|
|
contestType: "Clever",
|
|
shortDesc: "Breaks Screens.",
|
|
desc: "Breaks Screens.",
|
|
},
|
|
scavenge: {
|
|
num: -102,
|
|
accuracy: 100,
|
|
basePower: 100,
|
|
category: "Physical",
|
|
name: "Scavenge",
|
|
pp: 10,
|
|
priority: 0,
|
|
flags: { contact: 1, protect: 1, mirror: 1, metronome: 1 },
|
|
secondary: null,
|
|
target: "normal",
|
|
type: "Poison",
|
|
contestType: "Tough",
|
|
onTryMove() {
|
|
this.attrLastMove('[still]');
|
|
},
|
|
onPrepareHit(target, source) {
|
|
this.add('-anim', source, 'Thief', target);
|
|
},
|
|
onAfterMove(source) {
|
|
if (source.lastItem) {
|
|
const item = source.lastItem;
|
|
source.lastItem = '';
|
|
source.setItem(item);
|
|
this.add('-item', source, this.dex.items.get(item), '[from] move: Scavenge');
|
|
} else {
|
|
return null;
|
|
}
|
|
},
|
|
shortDesc: "User regains their last used item, similar to Recycle.",
|
|
desc: "If the user has consumed their item, it will be restored.",
|
|
},
|
|
aquaring: {
|
|
inherit: true,
|
|
condition: {
|
|
onStart(pokemon) {
|
|
this.add('-start', pokemon, 'Aqua Ring');
|
|
},
|
|
onResidualOrder: 6,
|
|
onResidual(pokemon) {
|
|
this.heal(pokemon.baseMaxhp / 16);
|
|
},
|
|
onSourceModifyAtkPriority: 5,
|
|
onSourceModifyAtk(atk, attacker, defender, move) {
|
|
if (move.type === 'Fire') {
|
|
return this.chainModify(0.5);
|
|
}
|
|
},
|
|
onSourceModifySpAPriority: 5,
|
|
onSourceModifySpA(atk, attacker, defender, move) {
|
|
if (move.type === 'Fire') {
|
|
return this.chainModify(0.5);
|
|
}
|
|
},
|
|
onModifyAtk(atk, attacker, defender, move) {
|
|
if (move.type === 'Water') {
|
|
return this.chainModify(2);
|
|
}
|
|
},
|
|
onModifySpA(atk, attacker, defender, move) {
|
|
if (move.type === 'Water') {
|
|
return this.chainModify(2);
|
|
}
|
|
},
|
|
},
|
|
shortDesc: "2x Water power, 0.5x Fire damage, heal 1/16 HP per turn.",
|
|
desc: "User recovers 1/16 max HP per turn. While this is active, this Pokemon's Water power is 2x and Fire power against it is halved.",
|
|
},
|
|
|
|
ragingbull: {
|
|
num: 9999,
|
|
accuracy: 100,
|
|
basePower: 90,
|
|
category: "Physical",
|
|
priority: 0,
|
|
pp: 10,
|
|
name: "Raging Bull",
|
|
type: "Normal",
|
|
effectType: "Move",
|
|
flags: { contact: 1, protect: 1, mirror: 1, metronome: 1 },
|
|
shortDesc: "Type swaps to most effective (Water, Fighting, Fire, or Normal).",
|
|
desc: "Changes the move's and user's forme to the most effective against the target (Water, Fighting, Fire, or Normal).",
|
|
beforeMoveCallback(source, target, move) {
|
|
if (target) {
|
|
const typeEffectiveness: { [k: string]: number } = {
|
|
Normal: this.dex.getEffectiveness('Normal', target),
|
|
Water: this.dex.getEffectiveness('Water', target),
|
|
Fighting: this.dex.getEffectiveness('Fighting', target),
|
|
Fire: this.dex.getEffectiveness('Fire', target),
|
|
};
|
|
let bestType = 'Normal';
|
|
let type: keyof typeof typeEffectiveness;
|
|
let maxEffectiveness = -Infinity;
|
|
// gets most effective type against target (defaults to normal)
|
|
for (type in typeEffectiveness) {
|
|
if (typeEffectiveness[type] > maxEffectiveness) {
|
|
maxEffectiveness = typeEffectiveness[type];
|
|
bestType = type;
|
|
}
|
|
}
|
|
// changes form to match most effective type
|
|
let forme = '';
|
|
switch (bestType) {
|
|
case 'Water': forme = '-Paldea-Aqua'; break;
|
|
case 'Fighting': forme = '-Paldea-Combat'; break;
|
|
case 'Fire': forme = '-Paldea-Blaze'; break;
|
|
}
|
|
source.formeChange('Tauros' + forme);
|
|
this.add('-ability', source, 'Adaptability');
|
|
source.m.ragingBullMoveType = bestType;
|
|
}
|
|
},
|
|
// animation was remnant of Techno Blast code being copied, decided to keep because funny
|
|
onPrepareHit(target, source, move) {
|
|
this.add('-anim', source, 'Techno Blast', target);
|
|
},
|
|
// sets type properly (failsafe)
|
|
onModifyType(move, pokemon, target) {
|
|
if (pokemon.m.ragingBullMoveType) {
|
|
move.type = pokemon.m.ragingBullMoveType;
|
|
}
|
|
},
|
|
target: "normal",
|
|
},
|
|
iciclestorm: {
|
|
num: -1044,
|
|
accuracy: 95,
|
|
basePower: 90,
|
|
category: "Physical",
|
|
name: "Icicle Storm",
|
|
pp: 15,
|
|
priority: 0,
|
|
flags: { protect: 1, mirror: 1, metronome: 1 },
|
|
onTryMove() {
|
|
this.attrLastMove('[still]');
|
|
},
|
|
onPrepareHit(target, source) {
|
|
this.add('-anim', source, 'Ice Shard', target);
|
|
this.add('-anim', source, 'Ice Shard', target);
|
|
this.add('-anim', source, 'Ice Shard', target);
|
|
},
|
|
self: {
|
|
onHit(source) {
|
|
this.field.setWeather('snowscape');
|
|
},
|
|
},
|
|
secondary: null,
|
|
target: "normal",
|
|
type: "Ice",
|
|
contestType: "Clever",
|
|
desc: "Sets Snow on-hit.",
|
|
shortDesc: "Sets Snow.",
|
|
},
|
|
springtidestorm: {
|
|
// Now always hits in Sand in addition to Rain
|
|
inherit: true,
|
|
onModifyMove(move, pokemon, target) {
|
|
if (target && ['sandstorm', 'raindance'].includes(target.effectiveWeather())) {
|
|
move.accuracy = true;
|
|
}
|
|
},
|
|
},
|
|
// Poison type now
|
|
spitup: {
|
|
inherit: true,
|
|
type: "Poison",
|
|
},
|
|
// clone of shell side arm (modified to be base Physical so the randbats algorithm gives Attack EVs to Phione
|
|
geyser: {
|
|
num: -104,
|
|
accuracy: 100,
|
|
basePower: 100,
|
|
category: "Physical",
|
|
name: "Geyser",
|
|
pp: 10,
|
|
priority: 0,
|
|
flags: { protect: 1, contact: 1, mirror: 1, metronome: 1 },
|
|
onTryMove() {
|
|
this.attrLastMove('[still]');
|
|
},
|
|
onModifyMove(move, pokemon, target) {
|
|
if (!target) return;
|
|
const atk = pokemon.getStat('atk', false, true);
|
|
const spa = pokemon.getStat('spa', false, true);
|
|
const def = target.getStat('def', false, true);
|
|
const spd = target.getStat('spd', false, true);
|
|
const physical = Math.floor(Math.floor(Math.floor(Math.floor(2 * pokemon.level / 5 + 2) * 90 * atk) / def) / 50);
|
|
const special = Math.floor(Math.floor(Math.floor(Math.floor(2 * pokemon.level / 5 + 2) * 90 * spa) / spd) / 50);
|
|
if (physical < special || (physical === special && this.randomChance(1, 2))) {
|
|
move.category = 'Special';
|
|
move.flags.contact = undefined;
|
|
}
|
|
},
|
|
onPrepareHit(target, source) {
|
|
this.add('-anim', source, 'Water Spout', target);
|
|
},
|
|
onHit(target, source, move) {
|
|
// Shell Side Arm normally reveals its category via animation on cart, but doesn't play either custom animation against allies
|
|
if (!source.isAlly(target)) this.hint(move.category + " Geyser");
|
|
},
|
|
onAfterSubDamage(damage, target, source, move) {
|
|
if (!source.isAlly(target)) this.hint(move.category + " Geyser");
|
|
},
|
|
secondary: null,
|
|
target: "normal",
|
|
type: "Water",
|
|
desc: "This move is Special + no contact if it would be stronger.",
|
|
shortDesc: "This move is Special + no contact if it would be stronger.",
|
|
},
|
|
// Encore + Rain Dance
|
|
tidalsurge: {
|
|
num: -105,
|
|
accuracy: 100,
|
|
basePower: 0,
|
|
category: "Status",
|
|
name: "Tidal Surge",
|
|
pp: 10,
|
|
priority: 0,
|
|
flags: { protect: 1, reflectable: 1, mirror: 1, metronome: 1 },
|
|
onTryMove() {
|
|
this.attrLastMove('[still]');
|
|
},
|
|
onPrepareHit(target, source, move) {
|
|
this.add('-anim', source, 'Surf', target);
|
|
},
|
|
onHit(target, source, move) {
|
|
this.add('-anim', source, 'Encore', target);
|
|
target.addVolatile('encore');
|
|
},
|
|
weather: 'raindance',
|
|
secondary: null,
|
|
target: "normal",
|
|
type: "Water",
|
|
zMove: { boost: { spe: 1 } },
|
|
contestType: "Beautiful",
|
|
desc: "Encore + Rain Dance",
|
|
shortDesc: "Encore + Rain Dance",
|
|
},
|
|
// prio + double power if opponent is using a water move
|
|
bonsaibounce: {
|
|
num: -106,
|
|
accuracy: 100,
|
|
basePower: 70,
|
|
category: "Physical",
|
|
name: "Bonsai Bounce",
|
|
pp: 10,
|
|
priority: 0,
|
|
flags: { protect: 1, contact: 1, mirror: 1, metronome: 1 },
|
|
// checks for water move usage from opponent
|
|
onModifyPriority(priority, source) {
|
|
// gets current foe in singles
|
|
const foe = source.foes()[0];
|
|
if (!foe || foe.fainted) {
|
|
return priority;
|
|
}
|
|
// gets attack of foe this turn
|
|
const action = this.queue.willMove(foe);
|
|
if (!action || action.choice !== 'move') {
|
|
return priority;
|
|
}
|
|
const move = action.move;
|
|
if (move?.type === 'Water') {
|
|
return priority + 1;
|
|
} else {
|
|
return priority;
|
|
}
|
|
},
|
|
// modifies base power
|
|
onBasePower(basePower, source, target) {
|
|
const foe = source.foes()[0];
|
|
if (!foe || foe.fainted) {
|
|
return basePower;
|
|
}
|
|
const action = this.queue.willMove(foe);
|
|
if (!action || action.choice !== 'move') {
|
|
return basePower;
|
|
}
|
|
const move = action.move;
|
|
if (move?.type === 'Water') {
|
|
this.add('-message', `Sudowoodo draws power from the water!`);
|
|
return basePower + 70;
|
|
} else {
|
|
return basePower;
|
|
}
|
|
},
|
|
onTryMove() {
|
|
this.attrLastMove('[still]');
|
|
},
|
|
onPrepareHit(target, source, move) {
|
|
this.add('-anim', source, 'Ivy Cudgel Rock', target);
|
|
this.add('-anim', source, 'Splash');
|
|
},
|
|
secondary: null,
|
|
target: "normal",
|
|
type: "Rock",
|
|
contestType: "Beautiful",
|
|
shortDesc: "+1 Priority and 2x power if target uses Water move.",
|
|
desc: "If the target is using a Water type move, this move will always move first and gains double power.",
|
|
},
|
|
ironstrike: {
|
|
// implemented via changes to Stealth Rocks and Spikes
|
|
num: -107,
|
|
accuracy: 100,
|
|
basePower: 50,
|
|
category: "Physical",
|
|
name: "Iron Strike",
|
|
pp: 15,
|
|
priority: 0,
|
|
flags: { protect: 1, contact: 1, mirror: 1, metronome: 1 },
|
|
onTryMove() {
|
|
this.attrLastMove('[still]');
|
|
},
|
|
onPrepareHit(target, source, move) {
|
|
this.add('-anim', source, 'Metal Claw', target);
|
|
},
|
|
secondary: null,
|
|
target: "normal",
|
|
type: "Steel",
|
|
contestType: "Beautiful",
|
|
shortDesc: "Inflicts damage from hazards on target's side.",
|
|
desc: "Target takes damage from all entry hazards on their side of the field, unless they are immune.",
|
|
},
|
|
thunderkick: {
|
|
num: -1192,
|
|
name: "Thunder Kick",
|
|
type: "Electric",
|
|
basePower: 50,
|
|
accuracy: 100,
|
|
category: "Physical",
|
|
priority: 0,
|
|
pp: 5,
|
|
target: "normal",
|
|
contestType: "Beautiful",
|
|
onTryMove() {
|
|
this.attrLastMove('[still]');
|
|
},
|
|
onPrepareHit(t, s, m) {
|
|
this.add('-anim', s, 'High Jump Kick', t);
|
|
this.add('-anim', s, 'Thunder', t);
|
|
},
|
|
flags: { contact: 1, protect: 1 },
|
|
},
|
|
thunderouskick: {
|
|
inherit: true,
|
|
secondary: null,
|
|
onHit(target, source, move) {
|
|
// random # 0 or 1
|
|
const randomNum = this.random(2);
|
|
// 50% chance to drop def
|
|
if (randomNum === 0) {
|
|
if (target.boosts.def !== -6) {
|
|
this.boost({ def: -1 }, target, source, move);
|
|
}
|
|
} else {
|
|
this.add('-message', `${source.name} follows up with a Thunder Kick!`);
|
|
// uses Thunder Kick
|
|
this.actions.useMove('thunderkick', source, { target });
|
|
}
|
|
},
|
|
desc: "50% chance to reduce Defense by 1, 50% chance to inflict an additional 50 BP Electric type damage.",
|
|
shortDesc: "50% -1 Defense, 50% extra 50 BP Electric damage.",
|
|
},
|
|
stealthrock: {
|
|
num: 446,
|
|
accuracy: true,
|
|
basePower: 0,
|
|
category: "Status",
|
|
name: "Stealth Rock",
|
|
pp: 20,
|
|
priority: 0,
|
|
flags: { reflectable: 1, metronome: 1, mustpressure: 1 },
|
|
sideCondition: 'stealthrock',
|
|
condition: {
|
|
// this is a side condition
|
|
onSideStart(side) {
|
|
this.add('-sidestart', side, 'move: Stealth Rock');
|
|
},
|
|
onSwitchIn(pokemon) {
|
|
// hardcode to prevent hazard damage during Order Up switches + enforce hazard damage while King of the Hill is active
|
|
if ((pokemon.hasItem('heavydutyboots') && !pokemon.side.getSideCondition('kingofthehill')) ||
|
|
pokemon.side.getSideCondition('orderup')) return;
|
|
const typeMod = this.clampIntRange(pokemon.runEffectiveness(this.dex.getActiveMove('stealthrock')), -6, 6);
|
|
this.damage(pokemon.maxhp * (2 ** typeMod) / 8);
|
|
},
|
|
// iron strike functionality (reapplies stealth rock damage when hit with Iron Strike)
|
|
onHit(pokemon, source, move) {
|
|
if (move.id === 'ironstrike') {
|
|
if (pokemon.hasItem('heavydutyboots')) return;
|
|
const typeMod = this.clampIntRange(pokemon.runEffectiveness(this.dex.getActiveMove('stealthrock')), -6, 6);
|
|
this.damage(pokemon.maxhp * (2 ** typeMod) / 8);
|
|
}
|
|
},
|
|
},
|
|
secondary: null,
|
|
target: "foeSide",
|
|
type: "Rock",
|
|
zMove: { boost: { def: 1 } },
|
|
contestType: "Cool",
|
|
},
|
|
spikes: {
|
|
num: 191,
|
|
accuracy: true,
|
|
basePower: 0,
|
|
category: "Status",
|
|
name: "Spikes",
|
|
pp: 20,
|
|
priority: 0,
|
|
flags: { reflectable: 1, nonsky: 1, metronome: 1, mustpressure: 1 },
|
|
sideCondition: 'spikes',
|
|
condition: {
|
|
// this is a side condition
|
|
onSideStart(side) {
|
|
this.add('-sidestart', side, 'Spikes');
|
|
this.effectState.layers = 1;
|
|
},
|
|
onSideRestart(side) {
|
|
if (this.effectState.layers >= 3) return false;
|
|
this.add('-sidestart', side, 'Spikes');
|
|
this.effectState.layers++;
|
|
},
|
|
onSwitchIn(pokemon) {
|
|
// Order Up + king of the hill functionality
|
|
if (((!pokemon.isGrounded() || pokemon.hasItem('heavydutyboots')) && !pokemon.side.getSideCondition('kingofthehill')) ||
|
|
pokemon.side.getSideCondition('orderup')) return;
|
|
const damageAmounts = [0, 3, 4, 6]; // 1/8, 1/6, 1/4
|
|
this.damage(damageAmounts[this.effectState.layers] * pokemon.maxhp / 24);
|
|
},
|
|
// iron strike functionality
|
|
onHit(pokemon, source, move) {
|
|
if (move.id === 'ironstrike') {
|
|
if (!pokemon.isGrounded() || pokemon.hasItem('heavydutyboots')) return;
|
|
const damageAmounts = [0, 3, 4, 6]; // 1/8, 1/6, 1/4
|
|
this.damage(damageAmounts[this.effectState.layers] * pokemon.maxhp / 24);
|
|
}
|
|
},
|
|
},
|
|
secondary: null,
|
|
target: "foeSide",
|
|
type: "Ground",
|
|
zMove: { boost: { def: 1 } },
|
|
contestType: "Clever",
|
|
},
|
|
orderup: {
|
|
num: 856,
|
|
accuracy: 100,
|
|
basePower: 80,
|
|
category: "Physical",
|
|
name: "Order Up",
|
|
pp: 10,
|
|
priority: 0,
|
|
flags: { protect: 1 },
|
|
condition: {
|
|
duration: 2,
|
|
onSwitchInPriority: -1,
|
|
onSwitchIn(pokemon) {
|
|
// when Dondozo switches back in after eating, it gains boost
|
|
if (pokemon.baseSpecies.baseSpecies === 'Dondozo') {
|
|
// reapplies volatiles and stat boosts
|
|
if ((pokemon as any).storedVolatiles) {
|
|
for (const volatile in (pokemon as any).storedVolatiles) {
|
|
pokemon.addVolatile(volatile);
|
|
}
|
|
}
|
|
if ((pokemon as any).storedBoosts) {
|
|
for (const stat in (pokemon as any).storedBoosts) {
|
|
const change = (pokemon as any).storedBoosts[stat as BoostID];
|
|
if (change !== 0) {
|
|
this.boost({ [stat]: change }, pokemon);
|
|
}
|
|
}
|
|
}
|
|
this.add('-message', `Dondozo enjoyed its meal!`);
|
|
// applies boost based on eaten mon stats
|
|
if (this.effectState.eatenBoost === 'atk' || this.effectState.eatenBoost === 'spa') {
|
|
this.boost({ atk: 3 }, pokemon);
|
|
} else if (this.effectState.eatenBoost === 'def' || this.effectState.eatenBoost === 'spd') {
|
|
this.boost({ def: 2, spd: 2 }, pokemon);
|
|
} else {
|
|
this.boost({ spe: 3 }, pokemon);
|
|
}
|
|
// adds volatile ordered, which prevents the order up effect from occurring again until Dondozo switches out
|
|
pokemon.addVolatile('ordered');
|
|
// removes the side condition
|
|
pokemon.side.removeSideCondition('orderup');
|
|
} else {
|
|
// after Dondozo switches out, this happens to the next pokemon that is switched in
|
|
const meal = pokemon;
|
|
// faints the eaten mon
|
|
pokemon.faint();
|
|
// finds highest stat of eaten mon, stored in effectState eatenBoost
|
|
const stats = ['atk', 'def', 'spa', 'spd', 'spe'];
|
|
let highestStat = stats[0];
|
|
let maxStatValue = meal.storedStats[highestStat as StatIDExceptHP];
|
|
|
|
for (const stat of stats) {
|
|
if (meal.storedStats[stat as StatIDExceptHP] > maxStatValue) {
|
|
highestStat = stat;
|
|
maxStatValue = meal.storedStats[stat as StatIDExceptHP];
|
|
}
|
|
}
|
|
this.effectState.eatenBoost = highestStat;
|
|
}
|
|
},
|
|
onFaint(pokemon) {
|
|
const side = pokemon.side;
|
|
const dondozo = side.pokemon.find(p => p.species.name === 'Dondozo' && !p.fainted);
|
|
if (!dondozo) return;
|
|
// forces Dondozo in after the eaten mon faints
|
|
this.queue.insertChoice({
|
|
choice: 'switch',
|
|
pokemon,
|
|
target: dondozo,
|
|
});
|
|
this.checkFainted();
|
|
},
|
|
},
|
|
// when order up hits, first checks for volatile ordered to ensure that Order Up has not already been used, then starts orderup side condition and switches Dondozo out
|
|
onHit(target, source, move) {
|
|
if (source.volatiles['ordered']) return;
|
|
if (source.species.id === 'mew') return;
|
|
source.side.addSideCondition('orderup');
|
|
// stores stat changes and volatiles to reapply after switch
|
|
(source as any).storedBoosts = { ...source.boosts };
|
|
(source as any).storedVolatiles = {};
|
|
for (const volatile in source.volatiles) {
|
|
(source as any).storedVolatiles[volatile] = source.volatiles[volatile];
|
|
}
|
|
if (source.side.getSideCondition('orderup')) {
|
|
this.add('-ability', source, 'Order Up');
|
|
this.add('-message', `Select the Pokemon you would like to eat. Its highest base stat affects the boost you gain from this move.`);
|
|
}
|
|
source.switchFlag = true;
|
|
},
|
|
secondary: null,
|
|
hasSheerForce: true,
|
|
target: "normal",
|
|
type: "Dragon",
|
|
desc: "Dondozo eats a mon on the user's team, KOing it. Dondozo then gains a stat boost depending on the eaten mon's highest stat: +3 Attack for Atk/SpA, +2 Def/+2 SpD for Def/SpD, and +3 Speed for Speed.",
|
|
shortDesc: "Dondozo KOs an ally mon. Gain stat boost in ally's highest stat.",
|
|
},
|
|
toxicspikes: {
|
|
// prevents Dondozo from being affected by Toxic Spikes during Order Up switching
|
|
inherit: true,
|
|
condition: {
|
|
// this is a side condition
|
|
onSideStart(side) {
|
|
this.add('-sidestart', side, 'move: Toxic Spikes');
|
|
this.effectState.layers = 1;
|
|
},
|
|
onSideRestart(side) {
|
|
if (this.effectState.layers >= 2) return false;
|
|
this.add('-sidestart', side, 'move: Toxic Spikes');
|
|
this.effectState.layers++;
|
|
},
|
|
onSwitchIn(pokemon) {
|
|
// hardcode for King of the Hill
|
|
if (!pokemon.isGrounded() && !pokemon.side.getSideCondition('kingofthehill')) return;
|
|
if (pokemon.hasType('Poison')) {
|
|
this.add('-sideend', pokemon.side, 'move: Toxic Spikes', `[of] ${pokemon}`);
|
|
pokemon.side.removeSideCondition('toxicspikes');
|
|
// hardcode for King of the Hill and Order Up
|
|
} else if (
|
|
pokemon.hasType('Steel') ||
|
|
(pokemon.hasItem('heavydutyboots') && !pokemon.side.getSideCondition('kingofthehill')) ||
|
|
pokemon.side.getSideCondition('orderup')
|
|
) {
|
|
// do nothing
|
|
} else if (this.effectState.layers >= 2) {
|
|
pokemon.trySetStatus('tox', pokemon.side.foe.active[0]);
|
|
} else {
|
|
pokemon.trySetStatus('psn', pokemon.side.foe.active[0]);
|
|
}
|
|
},
|
|
},
|
|
},
|
|
stickyweb: {
|
|
inherit: true,
|
|
condition: {
|
|
onSideStart(side) {
|
|
this.add('-sidestart', side, 'move: Sticky Web');
|
|
},
|
|
onSwitchIn(pokemon) {
|
|
// king of the hill
|
|
if ((!pokemon.isGrounded() || pokemon.hasItem('heavydutyboots')) &&
|
|
!pokemon.side.getSideCondition('kingofthehill')) return;
|
|
this.add('-activate', pokemon, 'move: Sticky Web');
|
|
this.boost({ spe: -1 }, pokemon, pokemon.side.foe.active[0], this.dex.getActiveMove('stickyweb'));
|
|
},
|
|
},
|
|
},
|
|
shatteredseal: {
|
|
num: -1002,
|
|
accuracy: true,
|
|
basePower: 90,
|
|
category: "Physical",
|
|
name: "Shattered Seal",
|
|
pp: 15,
|
|
pseudoWeather: 'gravity',
|
|
priority: 0,
|
|
onTryMove() {
|
|
this.attrLastMove('[still]');
|
|
},
|
|
onPrepareHit(target, source) {
|
|
this.add('-anim', source, 'Spite', target);
|
|
this.add('-anim', source, 'Spirit Shackle', target);
|
|
},
|
|
flags: { contact: 1, protect: 1, mirror: 1, metronome: 1 },
|
|
secondary: null,
|
|
target: "normal",
|
|
type: "Ghost",
|
|
contestType: "Clever",
|
|
desc: "Sets gravity.",
|
|
shortDesc: "Sets gravity.",
|
|
},
|
|
alloutassault: {
|
|
num: -1003,
|
|
accuracy: 100,
|
|
basePower: 120,
|
|
category: "Physical",
|
|
name: "All-Out Assault",
|
|
pp: 5,
|
|
priority: 0,
|
|
onTryMove() {
|
|
this.attrLastMove('[still]');
|
|
},
|
|
onPrepareHit(target, source) {
|
|
this.add('-anim', source, 'All-Out Pummeling', target);
|
|
},
|
|
onAfterMoveSecondarySelf(pokemon, target, move) {
|
|
if (!target || target.fainted || target.hp <= 0) {
|
|
this.boost({ atk: 1 }, pokemon, pokemon, move);
|
|
} else {
|
|
this.boost({ atk: -1 }, pokemon, pokemon, move);
|
|
}
|
|
},
|
|
flags: { contact: 1, protect: 1, mirror: 1, metronome: 1 },
|
|
secondary: null,
|
|
target: "normal",
|
|
type: "Fighting",
|
|
contestType: "Clever",
|
|
desc: "If this move KOs the target, raise the user's attack by 1. Otherwise, lower attack by 1.",
|
|
shortDesc: "On KO: +1 Atk. Otherwise -1 Atk.",
|
|
},
|
|
wickedblow: {
|
|
inherit: true,
|
|
beforeMoveCallback(source, target, move) {
|
|
if (target) {
|
|
this.effectState.surgingStrikesAlreadyUsed = 0;
|
|
this.add('-anim', source, 'Techno Blast', target);
|
|
const typeEffectiveness: { [k: string]: number } = {
|
|
Water: this.dex.getEffectiveness('Water', target),
|
|
Dark: this.dex.getEffectiveness('Dark', target),
|
|
};
|
|
let type: keyof typeof typeEffectiveness;
|
|
let bestType = 'Water';
|
|
let maxEffectiveness = -Infinity;
|
|
// gets most effective type against target (defaults to the current type)
|
|
for (type in typeEffectiveness) {
|
|
if (typeEffectiveness[type] > maxEffectiveness) {
|
|
maxEffectiveness = typeEffectiveness[type];
|
|
bestType = type;
|
|
}
|
|
}
|
|
// changes form to match most effective type
|
|
if (bestType === 'Dark') {
|
|
this.add('-message', `Urshifu takes pity on its foe and transforms into a weaker type!`);
|
|
source.formeChange('Urshifu-Rapid-Strike', null, true);
|
|
source.setAbility('Sniper');
|
|
this.add('-ability', source, 'Sniper');
|
|
const oldMove = 'wickedblow';
|
|
const newMove = 'surgingstrikes';
|
|
const oldMoveId = this.toID(oldMove);
|
|
const newMoveData = this.dex.moves.get(newMove);
|
|
const oldMoveIdx = source.moveSlots.findIndex(x => x.id === oldMoveId);
|
|
if (oldMoveIdx >= 0) {
|
|
source.moveSlots[oldMoveIdx] = source.baseMoveSlots[oldMoveIdx] = {
|
|
move: newMoveData.name,
|
|
id: newMoveData.id,
|
|
pp: newMoveData.pp,
|
|
maxpp: newMoveData.pp,
|
|
target: newMoveData.target,
|
|
disabled: false,
|
|
used: false,
|
|
};
|
|
}
|
|
this.actions.useMove('surgingstrikes', source, { target });
|
|
this.effectState.surgingStrikesAlreadyUsed = 1;
|
|
}
|
|
}
|
|
},
|
|
onTry(source, target, move) {
|
|
if (this.effectState.surgingStrikesAlreadyUsed === 1) {
|
|
return null;
|
|
}
|
|
},
|
|
desc: "This move will transform into Rapid Strike Urshifu/Surging Strikes if it would be less effective against the target.",
|
|
shortDesc: "Becomes Surging Strikes if it would be less effective.",
|
|
},
|
|
surgingstrikes: {
|
|
inherit: true,
|
|
beforeMoveCallback(source, target, move) {
|
|
if (source.species.id === 'araquanid') return;
|
|
if (target) {
|
|
this.effectState.wickedBlowAlreadyUsed = 0;
|
|
this.add('-anim', source, 'Techno Blast', target);
|
|
const typeEffectiveness: { [k: string]: number } = {
|
|
Dark: this.dex.getEffectiveness('Dark', target),
|
|
Water: this.dex.getEffectiveness('Water', target),
|
|
};
|
|
let type: keyof typeof typeEffectiveness;
|
|
let bestType = 'Dark';
|
|
let maxEffectiveness = -Infinity;
|
|
// gets most effective type against target (defaults to the current type)
|
|
for (type in typeEffectiveness) {
|
|
if (typeEffectiveness[type] > maxEffectiveness) {
|
|
maxEffectiveness = typeEffectiveness[type];
|
|
bestType = type;
|
|
}
|
|
}
|
|
// changes form to match most effective type
|
|
if (bestType === 'Water') {
|
|
this.add('-message', `Urshifu takes pity on its foe and transforms into a weaker type!`);
|
|
source.formeChange('Urshifu', null, true);
|
|
source.setAbility('Sniper');
|
|
this.add('-ability', source, 'Sniper');
|
|
const newMove = 'wickedblow';
|
|
const oldMoveId: ID = 'surgingstrikes' as ID;
|
|
const newMoveData = this.dex.moves.get(newMove);
|
|
const oldMoveIdx = source.moveSlots.findIndex(x => x.id === oldMoveId);
|
|
if (oldMoveIdx >= 0) {
|
|
source.moveSlots[oldMoveIdx] = source.baseMoveSlots[oldMoveIdx] = {
|
|
move: newMoveData.name,
|
|
id: newMoveData.id,
|
|
pp: newMoveData.pp,
|
|
maxpp: newMoveData.pp,
|
|
target: newMoveData.target,
|
|
disabled: false,
|
|
used: false,
|
|
};
|
|
}
|
|
this.actions.useMove('wickedblow', source, { target });
|
|
this.effectState.wickedBlowAlreadyUsed = 1;
|
|
}
|
|
}
|
|
},
|
|
onTry(source, target, move) {
|
|
if (this.effectState.wickedBlowAlreadyUsed === 1) {
|
|
return null;
|
|
}
|
|
},
|
|
desc: "This move will transform into Single Strike Urshifu/Wicked Blow if it would be less effective against the target. Does not work with Araquanid.",
|
|
shortDesc: "Becomes Wicked Blow if it would be less effective.",
|
|
},
|
|
twister: {
|
|
num: 239,
|
|
accuracy: 100,
|
|
basePower: 80,
|
|
category: "Special",
|
|
name: "Twister",
|
|
pp: 20,
|
|
priority: 0,
|
|
onHit(target, source, move) {
|
|
let success = !!this.boost({ evasion: -1 });
|
|
const removeAll = ['spikes', 'toxicspikes', 'stealthrock', 'stickyweb', 'gmaxsteelsurge'];
|
|
const removeTarget = ['reflect', 'lightscreen', 'auroraveil', 'safeguard', 'mist', ...removeAll];
|
|
for (const targetCondition of removeTarget) {
|
|
if (target.side.removeSideCondition(targetCondition)) {
|
|
if (!removeAll.includes(targetCondition)) continue;
|
|
this.add('-sideend', target.side, this.dex.conditions.get(targetCondition).name, '[from] move: Defog', `[of] ${source}`);
|
|
success = true;
|
|
}
|
|
}
|
|
for (const sideCondition of removeAll) {
|
|
if (source.side.removeSideCondition(sideCondition)) {
|
|
this.add('-sideend', source.side, this.dex.conditions.get(sideCondition).name, '[from] move: Defog', `[of] ${source}`);
|
|
success = true;
|
|
}
|
|
}
|
|
this.field.clearTerrain();
|
|
return success;
|
|
},
|
|
flags: { protect: 1, mirror: 1, metronome: 1, wind: 1 },
|
|
secondary: null,
|
|
target: "allAdjacentFoes",
|
|
type: "Dragon",
|
|
contestType: "Cool",
|
|
desc: "Removes hazards, side conditions, and terrain. Lowers Evasion by 1.",
|
|
shortDesc: "-1 evasion; ends user and target hazards/terrain.",
|
|
},
|
|
magnetbomb: {
|
|
num: 443,
|
|
accuracy: true,
|
|
basePower: 90,
|
|
category: "Special",
|
|
name: "Magnet Bomb",
|
|
pp: 20,
|
|
priority: 0,
|
|
onHit(target, source, move) {
|
|
target.setType('Steel');
|
|
this.add('-start', target, 'typechange', 'Steel');
|
|
},
|
|
flags: { protect: 1, mirror: 1, metronome: 1, bullet: 1 },
|
|
secondary: null,
|
|
target: "normal",
|
|
type: "Steel",
|
|
contestType: "Cool",
|
|
desc: "Changes the target's type to Steel.",
|
|
shortDesc: "Changes the target's type to Steel.",
|
|
},
|
|
triplekick: {
|
|
inherit: true,
|
|
basePower: 20,
|
|
basePowerCallback(pokemon, target, move) {
|
|
return 20 * move.hit;
|
|
},
|
|
},
|
|
freezingglare: {
|
|
inherit: true,
|
|
secondary: {
|
|
chance: 30,
|
|
onHit(target, source, move) {
|
|
if (!target.hasType('Ice')) {
|
|
target.trySetStatus('frostbite', source, move);
|
|
}
|
|
},
|
|
},
|
|
desc: "30% chance to inflict Frostbite.",
|
|
shortDesc: "30% chance to inflict Frostbite.",
|
|
},
|
|
zippyzap: {
|
|
inherit: true,
|
|
category: "Special",
|
|
onTryMove() {
|
|
this.attrLastMove('[still]');
|
|
},
|
|
onPrepareHit(target, source) {
|
|
this.add('-anim', source, 'Extreme Speed', target);
|
|
this.add('-anim', source, 'Thunder', target);
|
|
},
|
|
secondary: null,
|
|
desc: "Nearly always goes first.",
|
|
shortDesc: "Nearly always goes first.",
|
|
},
|
|
burnout: {
|
|
num: -1004,
|
|
accuracy: 100,
|
|
basePower: 70,
|
|
category: "Special",
|
|
name: "Burn Out",
|
|
pp: 20,
|
|
priority: 0,
|
|
onTryMove() {
|
|
this.attrLastMove('[still]');
|
|
},
|
|
onPrepareHit(target, source) {
|
|
this.add('-anim', source, 'Fire Spin', target);
|
|
},
|
|
onHit(target, source, move) {
|
|
if (source.species.id === 'jolteon' || source.species.id === 'vaporeon') {
|
|
this.add('-message', `Eevee uses its Fire Stone!`);
|
|
const currentHP = source.hp / source.maxhp;
|
|
source.formeChange('Flareon', null, true);
|
|
source.sethp(source.maxhp * currentHP);
|
|
this.add('-sethp', source, source.getHealth, '[from] move: Flip Turn', '[silent]');
|
|
// target.setAbility('Eeveelution');
|
|
// target.baseAbility = target.ability;
|
|
const newMoves = ['flipturn', 'voltswitch', 'sizzlyslide', 'facade'];
|
|
// Update move slots
|
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
|
source.moveSlots = newMoves.map(move => {
|
|
const moveData = this.dex.moves.get(move);
|
|
return {
|
|
move: moveData.name,
|
|
id: moveData.id,
|
|
pp: moveData.pp,
|
|
maxpp: moveData.pp,
|
|
target: moveData.target,
|
|
disabled: false,
|
|
used: false,
|
|
};
|
|
});
|
|
// this forces the UI to update move slots visually
|
|
(source as any).baseMoveSlots = source.moveSlots.slice();
|
|
}
|
|
},
|
|
flags: { contact: 1, protect: 1, mirror: 1, metronome: 1 },
|
|
selfSwitch: true,
|
|
secondary: null,
|
|
target: "normal",
|
|
type: "Fire",
|
|
contestType: "Cute",
|
|
desc: "User switches out after damaging the target.",
|
|
shortDesc: "User switches out after damaging the target.",
|
|
},
|
|
voltswitch: {
|
|
inherit: true,
|
|
onHit(target, source, move) {
|
|
if (source.species.id === 'flareon' || source.species.id === 'vaporeon') {
|
|
this.add('-message', `Eevee uses its Thunder Stone!`);
|
|
const currentHP = source.hp / source.maxhp;
|
|
source.formeChange('Jolteon', null, true);
|
|
source.sethp(source.maxhp * currentHP);
|
|
this.add('-sethp', source, source.getHealth, '[from] move: Flip Turn', '[silent]');
|
|
// target.setAbility('Eeveelution');
|
|
// target.baseAbility = target.ability;
|
|
const newMoves = ['flipturn', 'burnout', 'zippyzap', 'freezyfrost'];
|
|
// Update move slots
|
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
|
source.moveSlots = newMoves.map(move => {
|
|
const moveData = this.dex.moves.get(move);
|
|
return {
|
|
move: moveData.name,
|
|
id: moveData.id,
|
|
pp: moveData.pp,
|
|
maxpp: moveData.pp,
|
|
target: moveData.target,
|
|
disabled: false,
|
|
used: false,
|
|
};
|
|
});
|
|
// this forces the UI to update move slots visually
|
|
(source as any).baseMoveSlots = source.moveSlots.slice();
|
|
}
|
|
},
|
|
},
|
|
flipturn: {
|
|
inherit: true,
|
|
onHit(target, source, move) {
|
|
if (source.species.id === 'jolteon' || source.species.id === 'flareon') {
|
|
this.add('-message', `Eevee uses its Water Stone!`);
|
|
const currentHP = source.hp / source.maxhp;
|
|
source.formeChange('Vaporeon', null, true);
|
|
source.sethp(source.maxhp * currentHP);
|
|
this.add('-sethp', source, source.getHealth, '[from] move: Flip Turn', '[silent]');
|
|
// target.setAbility('Eeveelution');
|
|
// target.baseAbility = target.ability;
|
|
const newMoves = ['voltswitch', 'burnout', 'recover', 'scald'];
|
|
// Update move slots
|
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
|
source.moveSlots = newMoves.map(move => {
|
|
const moveData = this.dex.moves.get(move);
|
|
return {
|
|
move: moveData.name,
|
|
id: moveData.id,
|
|
pp: moveData.pp,
|
|
maxpp: moveData.pp,
|
|
target: moveData.target,
|
|
disabled: false,
|
|
used: false,
|
|
};
|
|
});
|
|
// this forces the UI to update move slots visually
|
|
(source as any).baseMoveSlots = source.moveSlots.slice();
|
|
}
|
|
},
|
|
},
|
|
sizzlyslide: {
|
|
inherit: true,
|
|
basePower: 80,
|
|
onTryMove() {
|
|
this.attrLastMove('[still]');
|
|
},
|
|
onPrepareHit(target, source) {
|
|
this.add('-anim', source, 'Flame Charge', target);
|
|
},
|
|
},
|
|
freezyfrost: {
|
|
inherit: true,
|
|
onTryMove() {
|
|
this.attrLastMove('[still]');
|
|
},
|
|
onPrepareHit(target, source) {
|
|
this.add('-anim', source, 'Blizzard', target);
|
|
},
|
|
},
|
|
bouncybubble: {
|
|
inherit: true,
|
|
onTryMove() {
|
|
this.attrLastMove('[still]');
|
|
},
|
|
onPrepareHit(target, source) {
|
|
this.add('-anim', source, 'Bubble Beam', target);
|
|
},
|
|
},
|
|
purify: {
|
|
inherit: true,
|
|
pp: 10,
|
|
flags: { reflectable: 1, heal: 1, metronome: 1 },
|
|
onHit(target, source) {
|
|
const foe = source.side.foe.active[0];
|
|
if (foe && !foe.fainted && foe.status) {
|
|
this.heal(Math.ceil(source.maxhp * 0.5), source);
|
|
} else {
|
|
this.heal(Math.ceil(source.maxhp * 0.25), source);
|
|
}
|
|
},
|
|
target: "self",
|
|
desc: "Heals for 25% HP, or 50% if foe is statused.",
|
|
shortDesc: "Heals for 25% HP, or 50% if foe is statused.",
|
|
},
|
|
saltcurse: {
|
|
num: -1006,
|
|
accuracy: 100,
|
|
basePower: 70,
|
|
basePowerCallback(pokemon, target, move) {
|
|
if (target.status === 'par') {
|
|
this.debug('BP doubled on paralyzed target');
|
|
return move.basePower * 2;
|
|
}
|
|
return move.basePower;
|
|
},
|
|
onEffectiveness(typeMod, target, type) {
|
|
if (type === 'Water') return 1;
|
|
if (type === 'Steel') return 1;
|
|
},
|
|
onTryMove() {
|
|
this.attrLastMove('[still]');
|
|
},
|
|
onPrepareHit(target, source) {
|
|
this.add('-anim', source, 'Glare', target);
|
|
this.add('-anim', source, 'Ivy Cudgel Rock', target);
|
|
},
|
|
category: "Physical",
|
|
name: "Salt Curse",
|
|
pp: 10,
|
|
priority: 0,
|
|
flags: { contact: 1, protect: 1, mirror: 1, metronome: 1 },
|
|
secondary: null,
|
|
target: "normal",
|
|
type: "Rock",
|
|
contestType: "Tough",
|
|
desc: "Double power if target is Paralyzed. Super-effective against Water and Steel.",
|
|
shortDesc: "2x BP if target is Paralyzed, Water type, or Steel type.",
|
|
},
|
|
flyby: {
|
|
num: -1006,
|
|
accuracy: 100,
|
|
basePower: 70,
|
|
category: "Special",
|
|
name: "Fly-by",
|
|
pp: 20,
|
|
priority: 0,
|
|
onTryMove() {
|
|
this.attrLastMove('[still]');
|
|
},
|
|
onPrepareHit(target, source) {
|
|
this.add('-anim', source, 'Dual Wingbeat', target);
|
|
},
|
|
flags: { protect: 1, mirror: 1, metronome: 1 },
|
|
selfSwitch: true,
|
|
secondary: {
|
|
chance: 50,
|
|
boosts: {
|
|
atk: -1,
|
|
},
|
|
},
|
|
target: "normal",
|
|
type: "Flying",
|
|
contestType: "Cute",
|
|
desc: "User switches out. Target: -1 Attack.",
|
|
shortDesc: "User switches out. Target: -1 Attack.",
|
|
},
|
|
silktrap: {
|
|
inherit: true,
|
|
condition: {
|
|
duration: 1,
|
|
onStart(target) {
|
|
this.add('-singleturn', target, 'Protect');
|
|
},
|
|
onTryHitPriority: 3,
|
|
onTryHit(target, source, move) {
|
|
if (!move.flags['protect'] || move.category === 'Status') {
|
|
if (move.isZ || move.isMax) target.getMoveHitData(move).zBrokeProtect = true;
|
|
return;
|
|
}
|
|
if (move.smartTarget) {
|
|
move.smartTarget = false;
|
|
} else {
|
|
this.add('-activate', target, 'move: Protect');
|
|
}
|
|
const lockedmove = source.getVolatile('lockedmove');
|
|
if (lockedmove) {
|
|
// Outrage counter is reset
|
|
if (source.volatiles['lockedmove'].duration === 2) {
|
|
delete source.volatiles['lockedmove'];
|
|
}
|
|
}
|
|
if (this.checkMoveMakesContact(move, source, target)) {
|
|
source.side.addSideCondition('stickyweb');
|
|
}
|
|
return this.NOT_FAIL;
|
|
},
|
|
onHit(target, source, move) {
|
|
if (move.isZOrMaxPowered && this.checkMoveMakesContact(move, source, target)) {
|
|
source.side.addSideCondition('stickyweb');
|
|
}
|
|
},
|
|
},
|
|
desc: "Protect. If contact: set Sticky Web.",
|
|
shortDesc: "Protect. If contact: set Sticky Web.",
|
|
},
|
|
heatsink: {
|
|
num: -1007,
|
|
accuracy: 100,
|
|
basePower: 80,
|
|
onTryMove() {
|
|
this.attrLastMove('[still]');
|
|
},
|
|
onPrepareHit(target, source) {
|
|
this.add('-anim', source, 'Fire Spin', target);
|
|
this.add('-anim', source, 'Bitter Blade', target);
|
|
},
|
|
onModifyMove(move, source, target) {
|
|
if (target?.status === 'brn') {
|
|
move.drain = [3, 4];
|
|
}
|
|
},
|
|
category: "Special",
|
|
name: "Heat Sink",
|
|
pp: 20,
|
|
priority: 0,
|
|
flags: { protect: 1, mirror: 1, metronome: 1 },
|
|
drain: [1, 2],
|
|
secondary: null,
|
|
target: "normal",
|
|
type: "Fire",
|
|
zMove: { basePower: 160 },
|
|
contestType: "Clever",
|
|
desc: "50% drain. 75% drain instead if target is Burned.",
|
|
shortDesc: "50% drain. 75% drain if target is Burned.",
|
|
},
|
|
terastarstorm: {
|
|
inherit: true,
|
|
onModifyType(move, pokemon) {
|
|
const types = pokemon.getTypes();
|
|
let type = types[0];
|
|
if (type === 'Bird') type = '???';
|
|
if (type === '???' && types[1]) type = types[1];
|
|
move.type = type;
|
|
if (pokemon.species.name === 'Terapagos-Stellar') {
|
|
move.type = 'Stellar';
|
|
if (pokemon.getStat('atk', false, true) > pokemon.getStat('spa', false, true)) {
|
|
move.category = 'Physical';
|
|
}
|
|
}
|
|
},
|
|
desc: "Type varies based on the user's primary type.",
|
|
shortDesc: "Type varies based on the user's primary type.",
|
|
},
|
|
grabapple: {
|
|
num: -1008,
|
|
accuracy: 100,
|
|
basePower: 100,
|
|
category: "Physical",
|
|
name: "Grab Apple",
|
|
pp: 10,
|
|
priority: 0,
|
|
flags: { contact: 1, protect: 1, mirror: 1, metronome: 1 },
|
|
secondary: null,
|
|
target: "normal",
|
|
type: "Grass",
|
|
contestType: "Tough",
|
|
onTryMove() {
|
|
this.attrLastMove('[still]');
|
|
},
|
|
onPrepareHit(target, source) {
|
|
this.add('-anim', source, 'Grav Apple', target);
|
|
this.add('-anim', source, 'Thief', target);
|
|
},
|
|
onAfterMove(source) {
|
|
if (source.lastItem) {
|
|
const item = source.lastItem;
|
|
source.lastItem = '';
|
|
source.setItem(item);
|
|
this.add('-item', source, this.dex.items.get(item), '[from] move: Grab Apple');
|
|
} else {
|
|
return null;
|
|
}
|
|
},
|
|
shortDesc: "User regains their last used item, similar to Recycle.",
|
|
desc: "If the user has consumed their item, it will be restored.",
|
|
},
|
|
sashimishuffle: {
|
|
num: -1009,
|
|
accuracy: true,
|
|
basePower: 0,
|
|
category: "Status",
|
|
name: "Sashimi Shuffle",
|
|
pp: 5,
|
|
priority: 0,
|
|
flags: { metronome: 1 },
|
|
onTryMove() {
|
|
this.attrLastMove('[still]');
|
|
},
|
|
onPrepareHit(target, source) {
|
|
this.add('-anim', source, 'Order Up', target);
|
|
this.add('-anim', source, 'Order Up', target);
|
|
},
|
|
onHit(target) {
|
|
if (!this.canSwitch(target.side) || target.volatiles['commanded']) {
|
|
this.attrLastMove('[still]');
|
|
this.add('-fail', target);
|
|
return this.NOT_FAIL;
|
|
}
|
|
},
|
|
self: {
|
|
onHit(source) {
|
|
source.skipBeforeSwitchOutEventFlag = true;
|
|
},
|
|
},
|
|
selfSwitch: true,
|
|
slotCondition: 'sashimishuffle',
|
|
condition: {
|
|
onSwitchIn(target) {
|
|
this.singleEvent('Swap', this.effect, this.effectState, target);
|
|
},
|
|
onSwap(target) {
|
|
if (!target.fainted) {
|
|
target.heal(target.maxhp / 3);
|
|
this.add('-heal', target, target.getHealth, '[from] move: Sashimi Shuffle');
|
|
target.side.removeSlotCondition(target, 'sashimishuffle');
|
|
}
|
|
},
|
|
},
|
|
secondary: null,
|
|
target: "self",
|
|
type: "Normal",
|
|
zMove: { effect: 'clearnegativeboost' },
|
|
contestType: "Cute",
|
|
shortDesc: "User switches. Next Pokemon heals 1/3 HP.",
|
|
desc: "User switches. Next Pokemon heals 1/3 HP.",
|
|
},
|
|
technoblast: {
|
|
inherit: true,
|
|
basePowerCallback(pokemon, target, move) {
|
|
if (this.field.isWeather('snowscape')) {
|
|
return move.basePower * 1.3;
|
|
} else return move.basePower;
|
|
},
|
|
},
|
|
crowverload: {
|
|
num: -1010,
|
|
accuracy: 100,
|
|
basePower: 12,
|
|
category: "Physical",
|
|
name: "Crowverload",
|
|
pp: 10,
|
|
priority: -4,
|
|
onTryMove() {
|
|
this.attrLastMove('[still]');
|
|
},
|
|
onPrepareHit(target, source) {
|
|
this.add('-anim', source, 'Glare', target);
|
|
this.add('-anim', source, 'X-Scissor', target);
|
|
},
|
|
onTryHit(source, target, move) {
|
|
if (source.volatiles['substitute']) {
|
|
this.add('-fail', source, 'move: Crowverload');
|
|
return this.NOT_FAIL;
|
|
}
|
|
},
|
|
onAfterMove(source, target, move) {
|
|
this.actions.useMove('substitute', source, { });
|
|
// source.addVolatile['substitute'];
|
|
// this.damage(Math.ceil(source.maxhp / 4));
|
|
},
|
|
flags: { protect: 1, mirror: 1, metronome: 1 },
|
|
multihit: [10, 10],
|
|
secondary: null,
|
|
target: "normal",
|
|
type: "Dark",
|
|
zMove: { basePower: 140 },
|
|
maxMove: { basePower: 130 },
|
|
contestType: "Tough",
|
|
shortDesc: "Hits 10 times. User creates a Substitute.",
|
|
desc: "Hits 10 times. User creates a Substitute.",
|
|
},
|
|
naturesfury: {
|
|
num: -1011,
|
|
accuracy: true,
|
|
basePower: 0,
|
|
category: "Status",
|
|
name: "Nature's Fury",
|
|
pp: 20,
|
|
priority: 0,
|
|
flags: { failencore: 1, nosleeptalk: 1, noassist: 1, failcopycat: 1, failmimic: 1, failinstruct: 1 },
|
|
onModifyPriority(priority, source, target, move) {
|
|
if (this.field.isTerrain('electricterrain')) {
|
|
return priority + 1;
|
|
} else if (this.field.isTerrain('grassyterrain')) {
|
|
return priority + 1;
|
|
} else if (this.field.isTerrain('mistyterrain')) {
|
|
return priority + 1;
|
|
} else if (this.field.isTerrain('psychicterrain')) {
|
|
return priority;
|
|
} else {
|
|
return priority + 2;
|
|
}
|
|
},
|
|
onTryMove() {
|
|
this.attrLastMove('[still]');
|
|
},
|
|
onTryHit(target, pokemon) {
|
|
let move = 'extremespeed';
|
|
if (this.field.isTerrain('electricterrain')) {
|
|
move = 'lightningleap';
|
|
} else if (this.field.isTerrain('grassyterrain')) {
|
|
move = 'grassyglide';
|
|
} else if (this.field.isTerrain('mistyterrain')) {
|
|
move = 'mistymarch';
|
|
} else if (this.field.isTerrain('psychicterrain')) {
|
|
move = 'wackywhack';
|
|
}
|
|
this.actions.useMove(move, pokemon, { target });
|
|
return null;
|
|
},
|
|
callsMove: true,
|
|
secondary: null,
|
|
target: "normal",
|
|
type: "Normal",
|
|
contestType: "Beautiful",
|
|
shortDesc: "Move used depends on Terrain.",
|
|
desc: "Move used depends on Terrain.",
|
|
},
|
|
mistymarch: {
|
|
num: -1012,
|
|
accuracy: 100,
|
|
basePower: 55,
|
|
category: "Physical",
|
|
name: "Misty March",
|
|
pp: 20,
|
|
priority: 0,
|
|
flags: { contact: 1, protect: 1, mirror: 1, metronome: 1 },
|
|
onTryMove() {
|
|
this.attrLastMove('[still]');
|
|
},
|
|
onPrepareHit(target, source) {
|
|
this.add('-anim', source, 'Play Rough', target);
|
|
},
|
|
onModifyPriority(priority, source, target, move) {
|
|
if (this.field.isTerrain('mistyterrain') && source.isGrounded()) {
|
|
return priority + 1;
|
|
}
|
|
},
|
|
secondary: null,
|
|
target: "normal",
|
|
type: "Fairy",
|
|
contestType: "Cool",
|
|
shortDesc: "User on Misty Terrain: +1 priority.",
|
|
desc: "User on Misty Terrain: +1 priority.",
|
|
},
|
|
lightningleap: {
|
|
num: -1013,
|
|
accuracy: 100,
|
|
basePower: 55,
|
|
category: "Physical",
|
|
name: "Lightning Leap",
|
|
pp: 20,
|
|
priority: 0,
|
|
flags: { contact: 1, protect: 1, mirror: 1, metronome: 1 },
|
|
onTryMove() {
|
|
this.attrLastMove('[still]');
|
|
},
|
|
onPrepareHit(target, source) {
|
|
this.add('-anim', source, 'Volt Tackle', target);
|
|
},
|
|
onModifyPriority(priority, source, target, move) {
|
|
if (this.field.isTerrain('electricterrain') && source.isGrounded()) {
|
|
return priority + 1;
|
|
}
|
|
},
|
|
secondary: null,
|
|
target: "normal",
|
|
type: "Electric",
|
|
contestType: "Cool",
|
|
shortDesc: "User on Electric Terrain: +1 priority.",
|
|
desc: "User on Electric Terrain: +1 priority.",
|
|
},
|
|
wackywhack: {
|
|
num: -1014,
|
|
accuracy: 100,
|
|
basePower: 80,
|
|
category: "Physical",
|
|
name: "Wacky Whack",
|
|
pp: 20,
|
|
priority: 0,
|
|
flags: { contact: 1, protect: 1, mirror: 1, metronome: 1 },
|
|
onTryMove() {
|
|
this.attrLastMove('[still]');
|
|
},
|
|
onPrepareHit(target, source) {
|
|
this.add('-anim', source, 'Zed Headbutt', target);
|
|
this.add('-anim', source, 'Thief', target);
|
|
},
|
|
onAfterMoveSecondarySelf(source, target, move) {
|
|
if (this.field.isTerrain('psychicterrain') && source.isGrounded()) {
|
|
this.boost({ spe: 1 }, source, source);
|
|
}
|
|
},
|
|
secondary: null,
|
|
target: "normal",
|
|
type: "Psychic",
|
|
contestType: "Cool",
|
|
shortDesc: "User in Psychic Terrain: +1 Speed.",
|
|
desc: "User in Psychic Terrain: +1 Speed.",
|
|
},
|
|
bonemerang: {
|
|
inherit: true,
|
|
onAfterMove(source) {
|
|
const item = source.lastItem || 'thickclub';
|
|
source.lastItem = '';
|
|
source.setItem(item);
|
|
this.add('-item', source, this.dex.items.get(item), '[from] move: Bonemerang');
|
|
},
|
|
shortDesc: "Returns last used item. Default Thick Club.",
|
|
desc: "Returns last used item. Defaults to Thick Club if none.",
|
|
},
|
|
electricterrain: {
|
|
inherit: true,
|
|
condition: {
|
|
effectType: 'Terrain',
|
|
duration: 5,
|
|
durationCallback(source, effect) {
|
|
if (source?.hasItem('terrainextender')) {
|
|
return 8;
|
|
}
|
|
return 5;
|
|
},
|
|
onSetStatus(status, target, source, effect) {
|
|
if (status.id === 'slp' && target.isGrounded() && !target.isSemiInvulnerable()) {
|
|
if (effect.id === 'yawn' || (effect.effectType === 'Move' && !effect.secondaries)) {
|
|
this.add('-activate', target, 'move: Electric Terrain');
|
|
}
|
|
return false;
|
|
}
|
|
},
|
|
onTryAddVolatile(status, target) {
|
|
if (!target.isGrounded() || target.isSemiInvulnerable()) return;
|
|
if (status.id === 'yawn') {
|
|
this.add('-activate', target, 'move: Electric Terrain');
|
|
return null;
|
|
}
|
|
},
|
|
onBasePowerPriority: 6,
|
|
onBasePower(basePower, attacker, defender, move) {
|
|
if (move.type === 'Electric' && attacker.isGrounded() && !attacker.isSemiInvulnerable()) {
|
|
this.debug('electric terrain boost');
|
|
return this.chainModify([5325, 4096]);
|
|
}
|
|
},
|
|
onFieldStart(field, source, effect) {
|
|
if (effect?.effectType === 'Ability') {
|
|
this.add('-fieldstart', 'move: Electric Terrain', '[from] ability: ' + effect.name, `[of] ${source}`);
|
|
} else {
|
|
this.add('-fieldstart', 'move: Electric Terrain');
|
|
}
|
|
},
|
|
onDisableMove(pokemon) {
|
|
for (const moveSlot of pokemon.moveSlots) {
|
|
if (this.dex.moves.get(moveSlot.id).flags['heal']) {
|
|
pokemon.disableMove(moveSlot.id);
|
|
}
|
|
}
|
|
},
|
|
onBeforeMovePriority: 6,
|
|
onBeforeMove(pokemon, target, move) {
|
|
if (move.flags['heal'] && !move.isZ && !move.isMax) {
|
|
this.add('cant', pokemon, 'move: Electric Terrain', move);
|
|
return false;
|
|
}
|
|
},
|
|
onModifyMove(move, pokemon, target) {
|
|
if (move.flags['heal'] && !move.isZ && !move.isMax) {
|
|
this.add('cant', pokemon, 'move: Electric Terrain', move);
|
|
return false;
|
|
}
|
|
},
|
|
onFieldResidualOrder: 27,
|
|
onFieldResidualSubOrder: 7,
|
|
onFieldEnd() {
|
|
this.add('-fieldend', 'move: Electric Terrain');
|
|
},
|
|
},
|
|
shortDesc: "5 turns. Grounded: +Electric power, can't sleep, can't heal.",
|
|
desc: "5 turns. Grounded: +Electric power, can't sleep, can't use healing moves.",
|
|
},
|
|
mistyterrain: {
|
|
inherit: true,
|
|
condition: {
|
|
effectType: 'Terrain',
|
|
duration: 5,
|
|
durationCallback(source, effect) {
|
|
if (source?.hasItem('terrainextender')) {
|
|
return 8;
|
|
}
|
|
return 5;
|
|
},
|
|
onSetStatus(status, target, source, effect) {
|
|
if (!target.isGrounded() || target.isSemiInvulnerable()) return;
|
|
if (effect && ((effect as Move).status || effect.id === 'yawn')) {
|
|
this.add('-activate', target, 'move: Misty Terrain');
|
|
}
|
|
return false;
|
|
},
|
|
onTryAddVolatile(status, target, source, effect) {
|
|
if (!target.isGrounded() || target.isSemiInvulnerable()) return;
|
|
if (status.id === 'confusion') {
|
|
if (effect.effectType === 'Move' && !effect.secondaries) this.add('-activate', target, 'move: Misty Terrain');
|
|
return null;
|
|
}
|
|
},
|
|
onBasePowerPriority: 6,
|
|
onBasePower(basePower, attacker, defender, move) {
|
|
if (move.type === 'Dragon' && defender.isGrounded() && !defender.isSemiInvulnerable()) {
|
|
this.debug('misty terrain weaken');
|
|
return this.chainModify(0.5);
|
|
}
|
|
if (move.type === 'Fairy' && attacker.isGrounded() && !attacker.isSemiInvulnerable()) {
|
|
this.debug('misty terrain boost');
|
|
return this.chainModify([5325, 4096]);
|
|
}
|
|
},
|
|
onFieldStart(field, source, effect) {
|
|
if (effect?.effectType === 'Ability') {
|
|
this.add('-fieldstart', 'move: Misty Terrain', '[from] ability: ' + effect.name, `[of] ${source}`);
|
|
} else {
|
|
this.add('-fieldstart', 'move: Misty Terrain');
|
|
}
|
|
},
|
|
onFieldResidualOrder: 27,
|
|
onFieldResidualSubOrder: 7,
|
|
onFieldEnd() {
|
|
this.add('-fieldend', 'Misty Terrain');
|
|
},
|
|
},
|
|
shortDesc: "5 turns. Can't status,-Dragon power vs grounded, +Fairy power.",
|
|
desc: "5 turns. Can't status,-Dragon power vs grounded, +Fairy power.",
|
|
},
|
|
lootbox: {
|
|
num: -1015,
|
|
accuracy: 100,
|
|
basePower: 0,
|
|
category: "Physical",
|
|
name: "Loot Box",
|
|
pp: 15,
|
|
priority: 0,
|
|
flags: { protect: 1, mirror: 1, metronome: 1 },
|
|
onModifyMove(move, pokemon, target) {
|
|
const rand = this.random(8);
|
|
if (rand < 2) {
|
|
move.basePower = 0;
|
|
} else if (rand < 4) {
|
|
move.basePower = 60;
|
|
} else if (rand < 6) {
|
|
move.basePower = 120;
|
|
} else {
|
|
move.basePower = 150;
|
|
}
|
|
},
|
|
onTryMove() {
|
|
this.attrLastMove('[still]');
|
|
},
|
|
onPrepareHit(target, source) {
|
|
this.add('-anim', source, 'Explosion', target);
|
|
this.add('-anim', source, 'Mind Blown', target);
|
|
},
|
|
secondary: null,
|
|
target: "normal",
|
|
type: "Normal",
|
|
contestType: "Cute",
|
|
shortDesc: "Present but better.",
|
|
},
|
|
sinisterarrows: {
|
|
num: -1016,
|
|
accuracy: 100,
|
|
basePower: 50,
|
|
category: "Physical",
|
|
name: "Sinister Arrows",
|
|
pp: 10,
|
|
priority: 0,
|
|
flags: { allyanim: 1, metronome: 1, futuremove: 1 },
|
|
ignoreImmunity: true,
|
|
onTry(source, target) {
|
|
if (!target.side.addSlotCondition(target, 'sinisterarrows')) return false;
|
|
Object.assign(target.side.slotConditions[target.position]['sinisterarrows'], {
|
|
move: 'sinisterarrows',
|
|
source,
|
|
moveData: {
|
|
id: 'sinisterarrows',
|
|
name: "Sinister Arrows",
|
|
accuracy: 100,
|
|
basePower: 50,
|
|
category: "Physical",
|
|
priority: 0,
|
|
flags: { allyanim: 1, metronome: 1, futuremove: 1 },
|
|
ignoreImmunity: false,
|
|
effectType: 'Move',
|
|
type: 'Ghost',
|
|
},
|
|
});
|
|
this.add('-start', source, 'move: Sinister Arrows');
|
|
return this.NOT_FAIL;
|
|
},
|
|
onTryMove(target, source, move) {
|
|
this.add('-anim', source, 'Curse', target);
|
|
this.add('-anim', source, 'Spite', target);
|
|
},
|
|
condition: {
|
|
onStart(target) {
|
|
this.effectState.targetSlot = target.getSlot();
|
|
this.effectState.endingTurn = (this.turn - 1) + 3;
|
|
},
|
|
onResidualOrder: 5,
|
|
onResidualSubOrder: 2,
|
|
onResidual(target) {
|
|
const data = this.effectState;
|
|
// time's up; time to hit! :D
|
|
const move = this.dex.moves.get(data.move);
|
|
if (target.fainted || target === data.source) {
|
|
this.hint(`${move.name} did not hit because the target is ${(target.fainted ? 'fainted' : 'the user')}.`);
|
|
return;
|
|
}
|
|
if (!this.getOverflowedTurnCount()) return;
|
|
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';
|
|
}
|
|
const hitMove = new this.dex.Move(data.moveData) as ActiveMove;
|
|
this.actions.trySpreadMoveHit([target], data.source, hitMove, true);
|
|
this.hint(`${move.name} hits.`);
|
|
if (data.source.isActive && data.source.hasItem('lifeorb') && this.gen >= 5) {
|
|
this.singleEvent('AfterMoveSecondarySelf', data.source.getItem(), data.source.itemState, data.source, target, data.source.getItem());
|
|
}
|
|
this.activeMove = null;
|
|
this.checkWin();
|
|
if (this.getOverflowedTurnCount() >= this.effectState.endingTurn) {
|
|
target.side.removeSlotCondition(this.getAtSlot(this.effectState.targetSlot), 'sinisterarrows');
|
|
}
|
|
},
|
|
},
|
|
secondary: null,
|
|
target: "normal",
|
|
type: "Ghost",
|
|
contestType: "Clever",
|
|
shortDesc: "Hits for 4 turns, even if user switches out.",
|
|
},
|
|
mortalspin: {
|
|
inherit: true,
|
|
category: "Special",
|
|
},
|
|
lastbreakfast: {
|
|
num: -1020,
|
|
accuracy: 100,
|
|
basePower: 80,
|
|
category: "Physical",
|
|
name: "Last Breakfast",
|
|
pp: 15,
|
|
priority: 0,
|
|
flags: { protect: 1, mirror: 1, metronome: 1, contact: 1, bite: 1 },
|
|
onHit(target, source, move) {
|
|
const numberBerries = 0 + 1 * Number(source.side.totalFainted);
|
|
for (let i = 0; i < numberBerries; i++) {
|
|
const possibleBerries = ['aguavberry', 'apicotberry', 'enigmaberry', 'figyberry', 'ganlonberry', 'iapapaberry',
|
|
'keeberry', 'lansatberry', 'leppaberry', 'liechiberry', 'lumberry', 'magoberry',
|
|
'marangaberry', 'micleberry',
|
|
'oranberry', 'petayaberry', 'salacberry', 'sitrusberry', 'starfberry', 'wikiberry',
|
|
'aspearberry', 'cheriberry', 'chestoberry', 'lumberry', 'pechaberry', 'rawstberry', 'persimberry'];
|
|
const chosenBerry = this.sample(possibleBerries);
|
|
const berry = this.dex.items.get(chosenBerry);
|
|
if (source.hp && berry.isBerry) {
|
|
if (this.singleEvent('Eat', berry, null, source, source, move)) {
|
|
this.runEvent('EatItem', source, source, move, berry);
|
|
}
|
|
if (berry.onEat) source.ateBerry = true;
|
|
}
|
|
}
|
|
},
|
|
onTryMove() {
|
|
this.attrLastMove('[still]');
|
|
},
|
|
onPrepareHit(target, source) {
|
|
this.add('-anim', source, 'Curse', target);
|
|
this.add('-anim', source, 'Bug Bite', target);
|
|
},
|
|
secondary: null,
|
|
target: "normal",
|
|
type: "Ghost",
|
|
contestType: "Cute",
|
|
shortDesc: "Eats X random berries, where X is fainted teammates.",
|
|
},
|
|
superfang: {
|
|
inherit: true,
|
|
flags: { contact: 1, protect: 1, mirror: 1, metronome: 1, bite: 1 },
|
|
},
|
|
};
|