mirror of
https://github.com/smogon/pokemon-showdown.git
synced 2026-04-23 02:57:48 -05:00
For side conditions, `onStart`/`onRestart`/`onResidual`/`onEnd` have been renamed `onSideStart`/`onSideRestart`/`onSideResidual`/`onSideEnd`, with the `onResidualOrder` properties renamed `onSideResidualOrder`. For field conditions, `onStart`/`onRestart`/`onResidual`/`onEnd` have been renamed `onFieldStart`/`onFieldRestart`/`onFieldResidual`/`onFieldEnd`, with the `onResidualOrder` properties renamed `onFieldResidualOrder`. (The `onField` and `onSide` part helps make it clear to the type system that the first argument is a Field or Side, not a Pokemon.) Side and field conditions can now use `onResidual` to tick separately on each pokemon in Speed order. `onResidualOrder` (the per-pokemon tick) can be timed separate from `onSideResidualOrder` (the per-condition tick), allowing conditions to end at a different priority than they tick per-pokemon. Relatedly, `onTeamPreview` and `onStart` in formats now need to be `onFieldTeamPreview` and `onFieldStart`. Unrelatedly, `effectData` has been renamed `effectState`, and the corresponding state containers (`pokemon.statusData`, `pokemon.speciesData`, `pokemon.itemData`, `pokemon.abilityData`, `field.weatherData`, `field.terrainData`) have been similarly renamed. I renamed the types a while ago, but I was holding off renaming the fields because it would be a breaking change. But this is a breaking change anyway, so we might as well do it now. Note: `onResidual` will tick even on `onSideEnd` turns, although `onSideResidual` won't. When refactoring weather, remember to check `this.state.duration` so you don't deal weather damage on the ending turn. Intended as a better fix for #8216
923 lines
31 KiB
TypeScript
923 lines
31 KiB
TypeScript
export const Abilities: {[k: string]: ModdedAbilityData} = {
|
|
// Set 1
|
|
porous: { // Feel like this might be wrong
|
|
name: "Porous",
|
|
shortDesc: "Absorbs self-KO moves and Water-type moves, and restores 1/4 max HP.",
|
|
onTryHit(target, source, move) {
|
|
if (target !== source &&
|
|
(move.type === 'Water' || ['explosion', 'mindblown', 'mistyexplosion', 'selfdestruct'].includes(move.id))) {
|
|
if (!this.heal(target.baseMaxhp / 4)) {
|
|
this.add('-immune', target, '[from] ability: Porous');
|
|
}
|
|
return null;
|
|
}
|
|
},
|
|
onAnyDamage(damage, target, source, effect) {
|
|
if (effect?.id === 'aftermath') {
|
|
this.heal(this.effectState.target.baseMaxhp / 4);
|
|
this.add('-immune', this.effectState.target, '[from] ability: Porous');
|
|
}
|
|
},
|
|
},
|
|
despicable: {
|
|
name: "Despicable",
|
|
shortDesc: "This Pokemon's attacks are critical hits if the target is burned or poisoned.",
|
|
onModifyCritRatio(critRatio, source, target) {
|
|
// PLACEHOLDER FOR STURDY MOLD
|
|
let ignore = false;
|
|
|
|
if (target.hasAbility('sturdymold')) {
|
|
ignore = true;
|
|
return;
|
|
}
|
|
|
|
if (ignore) return;
|
|
// END PLACEHOLDER
|
|
if (target && ['psn', 'tox', 'brn'].includes(target.status)) return 5;
|
|
},
|
|
},
|
|
kingsguard: {
|
|
name: "King's Guard",
|
|
shortDesc: "Protected from enemy priority moves and Attack reduction.",
|
|
onBoost(boost, target, source, effect) {
|
|
if (source && target === source) return;
|
|
if (boost.atk && boost.atk < 0) {
|
|
delete boost.atk;
|
|
if (!(effect as ActiveMove).secondaries) {
|
|
this.add("-fail", target, "unboost", "Attack", "[from] ability: King's Guard", "[of] " + target);
|
|
}
|
|
}
|
|
},
|
|
onFoeTryMove(target, source, move) {
|
|
const targetAllExceptions = ['perishsong', 'flowershield', 'rototiller'];
|
|
if (move.target === 'foeSide' || (move.target === 'all' && !targetAllExceptions.includes(move.id))) {
|
|
return;
|
|
}
|
|
|
|
const dazzlingHolder = this.effectState.target;
|
|
if ((source.side === dazzlingHolder.side || move.target === 'all') && move.priority > 0.1) {
|
|
this.attrLastMove('[still]');
|
|
this.add('cant', dazzlingHolder, "ability: King's Guard", move, '[of] ' + target);
|
|
return false;
|
|
}
|
|
},
|
|
},
|
|
growthveil: { // Too long
|
|
name: "Growth Veil",
|
|
shortDesc: "Restores 1/3 max HP on switch-out; ally Grass-types safe from enemy stat drops & status.",
|
|
desc: "Restores 1/3 max HP on switch-out; ally Grass-types can't have stats lowered or status inflicted.",
|
|
onSwitchOut(pokemon) {
|
|
pokemon.heal(pokemon.baseMaxhp / 3);
|
|
},
|
|
onAllyBoost(boost, target, source, effect) {
|
|
if ((source && target === source) || !target.hasType('Grass')) return;
|
|
let showMsg = false;
|
|
let i: BoostID;
|
|
for (i in boost) {
|
|
if (boost[i]! < 0) {
|
|
delete boost[i];
|
|
showMsg = true;
|
|
}
|
|
}
|
|
if (showMsg && !(effect as ActiveMove).secondaries) {
|
|
const effectHolder = this.effectState.target;
|
|
this.add('-block', target, 'ability: Growth Veil', '[of] ' + effectHolder);
|
|
}
|
|
},
|
|
onAllySetStatus(status, target, source, effect) {
|
|
if (target.hasType('Grass') && source && target !== source && effect && effect.id !== 'yawn') {
|
|
this.debug('interrupting setStatus with Growth Veil');
|
|
if (effect.id === 'synchronize' || (effect.effectType === 'Move' && !effect.secondaries)) {
|
|
const effectHolder = this.effectState.target;
|
|
this.add('-block', target, 'ability: Growth Veil', '[of] ' + effectHolder);
|
|
}
|
|
return null;
|
|
}
|
|
},
|
|
onAllyTryAddVolatile(status, target) {
|
|
if (target.hasType('Grass') && status.id === 'yawn') {
|
|
this.debug('Growth Veil blocking yawn');
|
|
const effectHolder = this.effectState.target;
|
|
this.add('-block', target, 'ability: Growth Veil', '[of] ' + effectHolder);
|
|
return null;
|
|
}
|
|
},
|
|
},
|
|
surgeoneye: {
|
|
name: "Surgeon Eye",
|
|
shortDesc: "Restores 1/3 max HP on switch-out. Attack power 1.3x if it moves last in a turn.",
|
|
onSwitchOut(pokemon) {
|
|
pokemon.heal(pokemon.baseMaxhp / 3);
|
|
},
|
|
onBasePowerPriority: 21,
|
|
onBasePower(basePower, pokemon) {
|
|
let boosted = true;
|
|
for (const target of this.getAllActive()) {
|
|
if (target === pokemon || target.hasAbility('sturdymold')) continue; // PLACEHOLDER
|
|
if (this.queue.willMove(target)) {
|
|
boosted = false;
|
|
break;
|
|
}
|
|
}
|
|
if (boosted) {
|
|
this.debug('Surgeon Eye boost');
|
|
return this.chainModify([0x14CD, 0x1000]);
|
|
}
|
|
},
|
|
},
|
|
// Set 2
|
|
roughresult: { // Too long
|
|
name: "Rough Result",
|
|
shortDesc: "Foes making contact lose 1/8 max HP; if KOed by contact, attacker loses 1/4 max HP.",
|
|
desc: "Pokemon making contact lose 1/8 max HP; if KOed by a contact move, attacker loses 1/4 max HP.",
|
|
onDamagingHitOrder: 1,
|
|
onDamagingHit(damage, target, source, move) {
|
|
if (move.flags['contact']) {
|
|
this.damage(source.baseMaxhp / 8, source, target);
|
|
// I dunno how to make Porous differentiate between the two kinds of damage this ability can deal,
|
|
// So I'm just gonna CHEAT because i am a HACK and a fraud.
|
|
if (!target.hp) {
|
|
if (source.hasAbility('Porous')) {
|
|
this.add('-ability', source, 'Porous');
|
|
this.heal(source.baseMaxhp / 4, source, target, move);
|
|
} else {
|
|
this.damage(source.baseMaxhp / 4, source, target);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
},
|
|
eyeforaneye: {
|
|
name: "Eye for an Eye",
|
|
shortDesc: "This Pokemon blocks Dark-type moves and bounces them back to the user.",
|
|
onTryHitPriority: 1,
|
|
onTryHit(target, source, move) {
|
|
if (target === source || move.hasBounced || move.type !== 'Dark') {
|
|
return;
|
|
}
|
|
const newMove = this.dex.getActiveMove(move.id);
|
|
newMove.hasBounced = true;
|
|
newMove.pranksterBoosted = false;
|
|
this.actions.useMove(newMove, target, source);
|
|
return null;
|
|
},
|
|
onAllyTryHitSide(target, source, move) {
|
|
if (target.side === source.side || move.hasBounced || move.type !== 'Dark') {
|
|
return;
|
|
}
|
|
const newMove = this.dex.getActiveMove(move.id);
|
|
newMove.hasBounced = true;
|
|
newMove.pranksterBoosted = false;
|
|
this.actions.useMove(newMove, this.effectState.target, source);
|
|
return null;
|
|
},
|
|
condition: {
|
|
duration: 1,
|
|
},
|
|
},
|
|
naturalheal: {
|
|
name: "Natural Heal",
|
|
shortDesc: "Restores 1/3 max HP and cures non-volatile status on switch-out.",
|
|
onCheckShow(pokemon) {
|
|
// This is complicated
|
|
// For the most part, in-game, it's obvious whether or not Natural Cure activated,
|
|
// since you can see how many of your opponent's pokemon are statused.
|
|
// The only ambiguous situation happens in Doubles/Triples, where multiple pokemon
|
|
// that could have Natural Cure switch out, but only some of them get cured.
|
|
if (pokemon.side.active.length === 1) return;
|
|
if (pokemon.showCure === true || pokemon.showCure === false) return;
|
|
|
|
const cureList = [];
|
|
let noCureCount = 0;
|
|
for (const curPoke of pokemon.side.active) {
|
|
// pokemon not statused
|
|
if (!curPoke || !curPoke.status) {
|
|
// this.add('-message', "" + curPoke + " skipped: not statused or doesn't exist");
|
|
continue;
|
|
}
|
|
if (curPoke.showCure) {
|
|
// this.add('-message', "" + curPoke + " skipped: Natural Cure already known");
|
|
continue;
|
|
}
|
|
const species = curPoke.species;
|
|
// pokemon can't get Natural Cure
|
|
if (!Object.values(species.abilities).includes('Natural Cure') &&
|
|
!Object.values(species.abilities).includes('Natural Heal')) {
|
|
// this.add('-message', "" + curPoke + " skipped: no Natural Cure");
|
|
continue;
|
|
}
|
|
// pokemon's ability is known to be Natural Cure
|
|
if (!species.abilities['1'] && !species.abilities['H']) {
|
|
// this.add('-message', "" + curPoke + " skipped: only one ability");
|
|
continue;
|
|
}
|
|
// pokemon isn't switching this turn
|
|
if (curPoke !== pokemon && !this.queue.willSwitch(curPoke)) {
|
|
// this.add('-message', "" + curPoke + " skipped: not switching");
|
|
continue;
|
|
}
|
|
|
|
if (curPoke.hasAbility('naturalcure') || curPoke.hasAbility('naturalheal')) {
|
|
// this.add('-message', "" + curPoke + " confirmed: could be Natural Cure (and is)");
|
|
cureList.push(curPoke);
|
|
} else {
|
|
// this.add('-message', "" + curPoke + " confirmed: could be Natural Cure (but isn't)");
|
|
noCureCount++;
|
|
}
|
|
}
|
|
|
|
if (!cureList.length || !noCureCount) {
|
|
// It's possible to know what pokemon were cured
|
|
for (const pkmn of cureList) {
|
|
pkmn.showCure = true;
|
|
}
|
|
} else {
|
|
// It's not possible to know what pokemon were cured
|
|
|
|
// Unlike a -hint, this is real information that battlers need, so we use a -message
|
|
this.add('-message', "(" + cureList.length + " of " + pokemon.side.name + "'s pokemon " + (cureList.length === 1 ? "was" : "were") + " cured by Natural Heal.)");
|
|
|
|
for (const pkmn of cureList) {
|
|
pkmn.showCure = false;
|
|
}
|
|
}
|
|
},
|
|
onSwitchOut(pokemon) {
|
|
pokemon.heal(pokemon.baseMaxhp / 3);
|
|
if (!pokemon.status) return;
|
|
|
|
// if pokemon.showCure is undefined, it was skipped because its ability
|
|
// is known
|
|
if (pokemon.showCure === undefined) pokemon.showCure = true;
|
|
|
|
if (pokemon.showCure) this.add('-curestatus', pokemon, pokemon.status, '[from] ability: Natural Heal');
|
|
pokemon.setStatus('');
|
|
|
|
// only reset .showCure if it's false
|
|
// (once you know a Pokemon has Natural Cure, its cures are always known)
|
|
if (!pokemon.showCure) pokemon.showCure = undefined;
|
|
},
|
|
},
|
|
kingofpowerpoints: { // Too long
|
|
name: "King of Power Points",
|
|
shortDesc: "Moves targeting it: -1 extra PP. Restores 1/3 max PP of its moves on switch-out.",
|
|
desc: "Moves targeting this Pokemon lose 1 additional PP. Restores 1/3 max PP of its moves on switch-out, rounded down.",
|
|
onStart(pokemon) {
|
|
this.add('-ability', pokemon, 'King of Power Points');
|
|
},
|
|
onDeductPP(target, source) {
|
|
if (target.isAlly(source)) return;
|
|
return 1;
|
|
},
|
|
onSwitchOut(pokemon) {
|
|
for (const moveSlot of pokemon.moveSlots) {
|
|
moveSlot.pp += Math.floor(moveSlot.maxpp / 3);
|
|
if (moveSlot.pp > moveSlot.maxpp) moveSlot.pp = moveSlot.maxpp;
|
|
}
|
|
},
|
|
},
|
|
porousfat: {
|
|
name: "Porous Fat",
|
|
shortDesc: "Fire/Ice/Water moves against this Pokemon deal damage with a halved attacking stat.",
|
|
onSourceModifyAtkPriority: 6,
|
|
onSourceModifyAtk(atk, attacker, defender, move) {
|
|
if (move.type === 'Ice' || move.type === 'Fire' || move.type === 'Water') {
|
|
this.debug('Porous Fat weaken');
|
|
return this.chainModify(0.5);
|
|
}
|
|
},
|
|
onSourceModifySpAPriority: 5,
|
|
onSourceModifySpA(atk, attacker, defender, move) {
|
|
if (move.type === 'Ice' || move.type === 'Fire' || move.type === 'Water') {
|
|
this.debug('Porous Fat weaken');
|
|
return this.chainModify(0.5);
|
|
}
|
|
},
|
|
},
|
|
|
|
// set 3
|
|
nullsystem: {
|
|
name: "Null System",
|
|
shortDesc: "This Pokemon can be any type (selected in teambuilder).",
|
|
},
|
|
inthicktrator: {
|
|
name: "Inthicktrator",
|
|
shortDesc: "Ignores Screens/Substitutes. Fire/Ice moves: 1/2 power against this Pokemon.",
|
|
onSourceModifyAtkPriority: 6,
|
|
onSourceModifyAtk(atk, attacker, defender, move) {
|
|
if (move.type === 'Ice' || move.type === 'Fire') {
|
|
this.debug('Inthicktrator weaken');
|
|
return this.chainModify(0.5);
|
|
}
|
|
},
|
|
onSourceModifySpAPriority: 5,
|
|
onSourceModifySpA(atk, attacker, defender, move) {
|
|
if (move.type === 'Ice' || move.type === 'Fire') {
|
|
this.debug('Inthicktrator weaken');
|
|
return this.chainModify(0.5);
|
|
}
|
|
},
|
|
onModifyMove(move, pokemon) {
|
|
// PLACEHOLDER FOR STURDY MOLD
|
|
let ignore = false;
|
|
for (const target of pokemon.side.foe.active) {
|
|
if (target.hasAbility('sturdymold')) {
|
|
ignore = true;
|
|
this.debug("Target has Sturdy Mold");
|
|
return;
|
|
} else {
|
|
this.debug("Target does not have Sturdy Mold");
|
|
}
|
|
}
|
|
if ((move.target === 'allAdjacentFoes' || move.target === 'allAdjacent') && ignore) return;
|
|
// END PLACEHOLDER
|
|
move.infiltrates = true;
|
|
},
|
|
},
|
|
magicsurge: {
|
|
name: "Magic Surge",
|
|
shortDesc: "Summons Magic Room for 5 turns when switching in.",
|
|
onStart(source) {
|
|
this.add('-activate', source, 'ability: Magic Surge');
|
|
this.field.addPseudoWeather('magicroom');
|
|
},
|
|
},
|
|
multiantlers: {
|
|
name: "Multi Antlers",
|
|
shortDesc: "User takes half damage when switching in.",
|
|
onSourceModifyDamage(damage, source, target, move) {
|
|
if (!target.activeTurns) {
|
|
this.debug('Multi Antlers weaken');
|
|
return this.chainModify(0.5);
|
|
}
|
|
},
|
|
},
|
|
concussion: {
|
|
name: "Concussion",
|
|
shortDesc: "Halves the effects of stat changes when taking or dealing damage.",
|
|
onAnyModifyBoost(boosts, pokemon) {
|
|
const unawareUser = this.effectState.target;
|
|
if (unawareUser === pokemon) return;
|
|
if (unawareUser === this.activePokemon && pokemon === this.activeTarget) {
|
|
if (boosts['def']) boosts['def'] = Math.ceil(boosts['def'] / 2);
|
|
if (boosts['spd']) boosts['spd'] = Math.ceil(boosts['spd'] / 2);
|
|
if (boosts['evasion']) boosts['evasion'] = Math.ceil(boosts['evasion'] / 2);
|
|
}
|
|
if (pokemon === this.activePokemon && unawareUser === this.activeTarget) {
|
|
if (boosts['atk']) boosts['atk'] = Math.ceil(boosts['atk'] / 2);
|
|
if (boosts['def']) boosts['def'] = Math.ceil(boosts['def'] / 2);
|
|
if (boosts['spa']) boosts['spa'] = Math.ceil(boosts['spa'] / 2);
|
|
if (boosts['accuracy']) boosts['accuracy'] = Math.ceil(boosts['accuracy'] / 2);
|
|
}
|
|
},
|
|
rating: 4,
|
|
num: 109,
|
|
},
|
|
notfunny: {
|
|
name: "Not Funny",
|
|
shortDesc: "No Guard + Prankster.",
|
|
onModifyPriority(priority, pokemon, target, move) {
|
|
if (move?.category === 'Status') {
|
|
if (target && target !== pokemon && target.hasAbility('sturdymold')) return;
|
|
move.pranksterBoosted = true;
|
|
return priority + 1;
|
|
}
|
|
},
|
|
onAnyInvulnerabilityPriority: 1,
|
|
onAnyInvulnerability(target, source, move) {
|
|
if (move && (source === this.effectState.target || target === this.effectState.target)) return 0;
|
|
},
|
|
onAnyAccuracy(accuracy, target, source, move) {
|
|
if (move && (source === this.effectState.target || target === this.effectState.target)) {
|
|
return true;
|
|
}
|
|
return accuracy;
|
|
},
|
|
},
|
|
fowlbehavior: {
|
|
name: "Fowl Behavior",
|
|
shortDesc: "This Pokemon's Sp. Atk is 1.5x, but it can only select the first move it executes.",
|
|
onStart(pokemon) {
|
|
pokemon.abilityState.choiceLock = "";
|
|
},
|
|
onBeforeMove(pokemon, target, move) {
|
|
if (move.isZOrMaxPowered || move.id === 'struggle') return;
|
|
if (pokemon.abilityState.choiceLock && pokemon.abilityState.choiceLock !== move.id) {
|
|
// Fails unless ability is being ignored (these events will not run), no PP lost.
|
|
this.addMove('move', pokemon, move.name);
|
|
this.attrLastMove('[still]');
|
|
this.debug("Disabled by Fowl Behavior");
|
|
this.add('-fail', pokemon);
|
|
return false;
|
|
}
|
|
},
|
|
onModifyMove(move, pokemon) {
|
|
if (pokemon.abilityState.choiceLock || move.isZOrMaxPowered || move.id === 'struggle') return;
|
|
pokemon.abilityState.choiceLock = move.id;
|
|
},
|
|
onModifySpAPriority: 5,
|
|
onModifySpA(atk, pokemon, t, move) {
|
|
if (pokemon.volatiles['dynamax']) return;
|
|
// PLACEHOLDER FOR STURDY MOLD
|
|
let ignore = false;
|
|
for (const target of pokemon.foes()) {
|
|
if (target.hasAbility('sturdymold')) {
|
|
ignore = true;
|
|
return;
|
|
}
|
|
}
|
|
if (['allAdjacentFoes', 'allAdjacent'].includes(move.target) && ignore) return;
|
|
// END PLACEHOLDER
|
|
// PLACEHOLDER
|
|
this.debug('Fowl Behavior Sp. Atk Boost');
|
|
return this.chainModify(1.5);
|
|
},
|
|
onDisableMove(pokemon) {
|
|
if (!pokemon.abilityState.choiceLock) return;
|
|
if (pokemon.volatiles['dynamax']) return;
|
|
for (const moveSlot of pokemon.moveSlots) {
|
|
if (moveSlot.id !== pokemon.abilityState.choiceLock) {
|
|
pokemon.disableMove(moveSlot.id, false, this.effectState.sourceEffect);
|
|
}
|
|
}
|
|
},
|
|
onEnd(pokemon) {
|
|
pokemon.abilityState.choiceLock = "";
|
|
},
|
|
},
|
|
pillage: {
|
|
name: "Pillage",
|
|
shortDesc: "On switch-in, swaps ability with the opponent.",
|
|
onStart(pokemon) {
|
|
if (pokemon.adjacentFoes().some(foeActive => foeActive.ability === 'noability') ||
|
|
pokemon.species.id !== 'yaciancrowned') {
|
|
this.effectState.gaveUp = true;
|
|
}
|
|
},
|
|
onUpdate(pokemon) {
|
|
if (!pokemon.isStarted || this.effectState.gaveUp) return;
|
|
const possibleTargets = pokemon.adjacentFoes();
|
|
while (possibleTargets.length) {
|
|
let rand = 0;
|
|
if (possibleTargets.length > 1) rand = this.random(possibleTargets.length);
|
|
const target = possibleTargets[rand];
|
|
const ability = target.getAbility();
|
|
const additionalBannedAbilities = [
|
|
// Zen Mode included here for compatability with Gen 5-6
|
|
'noability', 'flowergift', 'forecast', 'hungerswitch', 'illusion', 'pillage',
|
|
'imposter', 'neutralizinggas', 'powerofalchemy', 'receiver', 'trace', 'zenmode',
|
|
];
|
|
if (target.getAbility().isPermanent || additionalBannedAbilities.includes(target.ability)) {
|
|
possibleTargets.splice(rand, 1);
|
|
continue;
|
|
}
|
|
target.setAbility('pillage', pokemon);
|
|
pokemon.setAbility(ability);
|
|
|
|
this.add('-activate', pokemon, 'ability: Pillage');
|
|
this.add('-activate', pokemon, 'Skill Swap', '', '', '[of] ' + target);
|
|
this.add('-activate', pokemon, 'ability: ' + ability.name);
|
|
this.add('-activate', target, 'ability: Pillage');
|
|
return;
|
|
}
|
|
},
|
|
},
|
|
magneticwaves: {
|
|
name: "Magnetic Waves",
|
|
shortDesc: "Normal moves: Electric type, 1.2x power. Immune to Ground moves.",
|
|
// airborneness implemented in sim/pokemon.js:Pokemon#isGrounded (via scripts.ts in this case)
|
|
onModifyTypePriority: -1,
|
|
onModifyType(move, pokemon) {
|
|
// PLACEHOLDER FOR STURDY MOLD
|
|
let ignore = false;
|
|
for (const target of pokemon.side.foe.active) {
|
|
if (target.hasAbility('sturdymold')) {
|
|
ignore = true;
|
|
return;
|
|
}
|
|
}
|
|
if ((move.target === 'allAdjacentFoes' || move.target === 'allAdjacent') && ignore) return;
|
|
// END PLACEHOLDER
|
|
const noModifyType = [
|
|
'judgment', 'multiattack', 'naturalgift', 'revelationdance', 'technoblast', 'terrainpulse', 'weatherball',
|
|
];
|
|
if (move.type === 'Normal' && !noModifyType.includes(move.id) && !(move.isZ && move.category !== 'Status')) {
|
|
move.type = 'Electric';
|
|
move.galvanizeBoosted = true;
|
|
}
|
|
},
|
|
onBasePowerPriority: 23,
|
|
onBasePower(basePower, pokemon, target, move) {
|
|
if (move.galvanizeBoosted) return this.chainModify([0x1333, 0x1000]);
|
|
},
|
|
},
|
|
doggysmaw: {
|
|
name: "Doggy's Maw",
|
|
shortDesc: "This Pokemon's Normal, Fighting and Dragon moves ignore type-based immunities.",
|
|
onModifyMovePriority: -5,
|
|
onModifyMove(move, pokemon) {
|
|
// PLACEHOLDER FOR STURDY MOLD
|
|
let ignore = false;
|
|
for (const target of pokemon.side.foe.active) {
|
|
if (target.hasAbility('sturdymold')) {
|
|
ignore = true;
|
|
return;
|
|
}
|
|
}
|
|
if ((move.target === 'allAdjacentFoes' || move.target === 'allAdjacent') && ignore) return;
|
|
// END PLACEHOLDER
|
|
if (!move.ignoreImmunity) move.ignoreImmunity = {};
|
|
if (move.ignoreImmunity !== true) {
|
|
move.ignoreImmunity['Fighting'] = true;
|
|
move.ignoreImmunity['Normal'] = true;
|
|
move.ignoreImmunity['Dragon'] = true;
|
|
}
|
|
},
|
|
},
|
|
// slate 5
|
|
sturdymold: { // this one's gonna be a fucking adventure
|
|
name: "Sturdy Mold",
|
|
shortDesc: "One-hit KOs leave it with 1 HP. Ignores attacker's ability when taking damage.",
|
|
onTryHit(pokemon, target, move) {
|
|
if (move.ohko) {
|
|
this.add('-immune', pokemon, '[from] ability: Sturdy Mold');
|
|
return null;
|
|
}
|
|
},
|
|
onDamagePriority: -100,
|
|
onDamage(damage, target, source, effect) {
|
|
if (target.hp === target.maxhp && damage >= target.hp && effect && effect.effectType === 'Move') {
|
|
this.add('-ability', target, 'Sturdy Mold');
|
|
return target.hp - 1;
|
|
}
|
|
},
|
|
// I'm gonna figure out how to code this legit at some point, I swear,
|
|
// but for now, since we have so few abilities,
|
|
// I'm just gonna hard-code it into everything.
|
|
},
|
|
therapeutic: {
|
|
name: "Therapeutic",
|
|
shortDesc: "Heals 1/8 max HP each turn when statused. Ignores non-Sleep effects of status.",
|
|
// Burn attack reduction bypass hard-coded in scripts.ts (in battle: {})
|
|
// There's probably a more elegant way to ignore the effects of status
|
|
// that isn't hard-coding a check for the ability into every status condition,
|
|
// But that works so that is what I did.
|
|
onResidualSubOrder: 1,
|
|
onResidual(pokemon) {
|
|
if (pokemon.status) {
|
|
this.heal(pokemon.baseMaxhp / 8);
|
|
}
|
|
},
|
|
},
|
|
solarpanel: {
|
|
name: "Solar Panel",
|
|
shortDesc: "If hit by Grass, Electric or Fire: +1 SpA. Grass/Electric/Fire immunity.",
|
|
onTryHit(target, source, move) {
|
|
if (target !== source && (move.type === 'Electric' || move.type === 'Fire')) {
|
|
if (!this.boost({spa: 1})) {
|
|
this.add('-immune', target, '[from] ability: Solar Panel');
|
|
}
|
|
return null;
|
|
}
|
|
},
|
|
},
|
|
// For purposes of cancelling this ability out for Sturdy Mold:
|
|
toughclaws: {
|
|
onBasePowerPriority: 21,
|
|
onBasePower(basePower, attacker, defender, move) {
|
|
if (move.flags['contact'] && !defender.hasAbility('sturdymold')) {
|
|
return this.chainModify([0x14CD, 0x1000]);
|
|
}
|
|
},
|
|
name: "Tough Claws",
|
|
rating: 3.5,
|
|
num: 181,
|
|
},
|
|
hustle: {
|
|
// This should be applied directly to the stat as opposed to chaining with the others
|
|
onModifyAtkPriority: 5,
|
|
onModifyAtk(atk, pokemon, t, move) {
|
|
// PLACEHOLDER FOR STURDY MOLD
|
|
let ignore = false;
|
|
for (const target of pokemon.foes()) {
|
|
if (target.hasAbility('sturdymold')) {
|
|
ignore = true;
|
|
return;
|
|
}
|
|
}
|
|
if ((move.target === 'allAdjacentFoes' || move.target === 'allAdjacent') && ignore) return;
|
|
// END PLACEHOLDER
|
|
return this.modify(atk, 1.5);
|
|
},
|
|
onSourceModifyAccuracyPriority: 7,
|
|
onSourceModifyAccuracy(accuracy, target, source, move) {
|
|
if (move.category === 'Physical' && typeof accuracy === 'number' && !target.hasAbility('sturdymold')) {
|
|
return accuracy * 0.8;
|
|
}
|
|
},
|
|
name: "Hustle",
|
|
rating: 3.5,
|
|
num: 55,
|
|
},
|
|
scrappy: {
|
|
onModifyMovePriority: -5,
|
|
onModifyMove(move, pokemon) {
|
|
// PLACEHOLDER FOR STURDY MOLD
|
|
let ignore = false;
|
|
for (const target of pokemon.side.foe.active) {
|
|
if (target.hasAbility('sturdymold')) {
|
|
ignore = true;
|
|
return;
|
|
}
|
|
}
|
|
if ((move.target === 'allAdjacentFoes' || move.target === 'allAdjacent') && ignore) return;
|
|
// END PLACEHOLDER
|
|
if (!move.ignoreImmunity) move.ignoreImmunity = {};
|
|
if (move.ignoreImmunity !== true) {
|
|
move.ignoreImmunity['Fighting'] = true;
|
|
move.ignoreImmunity['Normal'] = true;
|
|
}
|
|
},
|
|
onBoost(boost, target, source, effect) {
|
|
if (effect.id === 'intimidate') {
|
|
delete boost.atk;
|
|
this.add('-immune', target, '[from] ability: Scrappy');
|
|
}
|
|
},
|
|
name: "Scrappy",
|
|
rating: 3,
|
|
num: 113,
|
|
},
|
|
sandforce: {
|
|
onBasePowerPriority: 21,
|
|
onBasePower(basePower, attacker, defender, move) {
|
|
if (this.field.isWeather('sandstorm')) {
|
|
if (defender && defender.hasAbility('sturdymold')) return;
|
|
if (move.type === 'Rock' || move.type === 'Ground' || move.type === 'Steel') {
|
|
this.debug('Sand Force boost');
|
|
return this.chainModify([0x14CD, 0x1000]);
|
|
}
|
|
}
|
|
},
|
|
onImmunity(type, pokemon) {
|
|
if (type === 'sandstorm') return false;
|
|
},
|
|
name: "Sand Force",
|
|
rating: 2,
|
|
num: 159,
|
|
},
|
|
// next
|
|
noguard: { // Edited for Sturdy Mold
|
|
onAnyInvulnerabilityPriority: 1,
|
|
onAnyInvulnerability(target, source, move) {
|
|
if (move && [source, target].includes(this.effectState.target) && !target.hasAbility('sturdymold')) return 0;
|
|
},
|
|
onAnyAccuracy(accuracy, target, source, move) {
|
|
if (move && [source, target].includes(this.effectState.target) && !target.hasAbility('sturdymold')) {
|
|
return true;
|
|
}
|
|
return accuracy;
|
|
},
|
|
name: "No Guard",
|
|
rating: 4,
|
|
num: 99,
|
|
},
|
|
bigpressure: {
|
|
name: "Big Pressure",
|
|
shortDesc: "Moves targeting this Pokemon lose 1 additional PP; Foes cannot lower its Defense.",
|
|
onStart(pokemon) {
|
|
this.add('-ability', pokemon, 'Big Pressure');
|
|
},
|
|
onDeductPP(target, source) {
|
|
if (target.side === source.side) return;
|
|
return 1;
|
|
},
|
|
onBoost(boost, target, source, effect) {
|
|
if (source && target === source) return;
|
|
if (boost.def && boost.def < 0) {
|
|
delete boost.def;
|
|
if (!(effect as ActiveMove).secondaries && effect.id !== 'octolock') {
|
|
this.add("-fail", target, "unboost", "Defense", "[from] ability: Big Pecks", "[of] " + target);
|
|
}
|
|
}
|
|
},
|
|
},
|
|
friendshield: {
|
|
name: "Friend Shield",
|
|
shortDesc: "Gets +1 Defense on switch-in. Allies recieve 3/4 damage from foes' attacks.",
|
|
onStart(pokemon) {
|
|
this.boost({def: 1}, pokemon);
|
|
},
|
|
onAnyModifyDamage(damage, source, target, move) {
|
|
if (target !== this.effectState.target && target.side === this.effectState.target.side) {
|
|
this.debug('Friend Shield weaken');
|
|
return this.chainModify(0.75);
|
|
}
|
|
},
|
|
},
|
|
debilitate: {
|
|
name: "Debilitate",
|
|
shortDesc: "On switch-in, this Pokemon lowers the Special Attack of adjacent opponents by 1 stage.",
|
|
onStart(pokemon) {
|
|
let activated = false;
|
|
for (const target of pokemon.adjacentFoes()) {
|
|
if (!activated) {
|
|
this.add('-ability', pokemon, 'Debilitate', 'boost');
|
|
activated = true;
|
|
}
|
|
if (target.volatiles['substitute']) {
|
|
this.add('-immune', target);
|
|
} else {
|
|
this.boost({spa: -1}, target, pokemon, null, true);
|
|
}
|
|
}
|
|
},
|
|
},
|
|
leafyarmor: { // unsure
|
|
name: "Leafy Armor",
|
|
shortDesc: "If a mental status is inflicted on this Pokemon: Cure status, -1 Defense, +2 Speed.",
|
|
onUpdate(pokemon) {
|
|
if (pokemon.status && !pokemon.m.orbItemStatus) {
|
|
this.add('-activate', pokemon, 'ability: Leafy Armor');
|
|
pokemon.cureStatus();
|
|
this.boost({def: -1, spe: 2}, pokemon, pokemon);
|
|
}
|
|
},
|
|
},
|
|
surroundsound: { // unsure
|
|
name: "Surround Sound",
|
|
shortDesc: "This Pokemon recieves 1/2 damage from multitarget moves. Its own have 1.3x power.",
|
|
onBasePowerPriority: 7,
|
|
onBasePower(basePower, attacker, defender, move) {
|
|
if (['allAdjacent', 'allAdjacentFoes', 'all'].includes(move.target)) {
|
|
if (defender.hasAbility('sturdymold')) return;
|
|
this.debug('Surround Sound boost');
|
|
return this.chainModify([0x14CD, 0x1000]);
|
|
}
|
|
},
|
|
onSourceModifyDamage(damage, source, target, move) {
|
|
if (['allAdjacent', 'allAdjacentFoes', 'all'].includes(move.target)) {
|
|
this.debug('Surround Sound weaken');
|
|
return this.chainModify(0.5);
|
|
}
|
|
},
|
|
},
|
|
spikyhold: {
|
|
name: "Spiky Hold",
|
|
shortDesc: "Cannot lose held item due to others' attacks; others making contact lose 1/8 max HP.",
|
|
onTakeItem(item, pokemon, source) {
|
|
if (this.suppressingAttackEvents(pokemon) || !pokemon.hp || pokemon.item === 'stickybarb') return;
|
|
if (!this.activeMove) throw new Error("Battle.activeMove is null");
|
|
if ((source && source !== pokemon) || this.activeMove.id === 'knockoff') {
|
|
this.add('-activate', pokemon, 'ability: Spiky Hold');
|
|
return false;
|
|
}
|
|
},
|
|
onDamagingHitOrder: 1,
|
|
onDamagingHit(damage, target, source, move) {
|
|
if (move.flags['contact']) {
|
|
this.damage(source.baseMaxhp / 8, source, target);
|
|
}
|
|
},
|
|
},
|
|
|
|
// slate 7
|
|
sinkorswim: {
|
|
name: "Sink or Swim",
|
|
shortDesc: "On switch-in, lowers adjacent opponents' Speed by 1 stage.",
|
|
onStart(pokemon) {
|
|
let activated = false;
|
|
for (const target of pokemon.adjacentFoes()) {
|
|
if (!activated) {
|
|
this.add('-ability', pokemon, 'Sink or Swim', 'boost');
|
|
activated = true;
|
|
}
|
|
if (target.volatiles['substitute']) {
|
|
this.add('-immune', target);
|
|
} else {
|
|
this.boost({spe: -1}, target, pokemon, null, true);
|
|
}
|
|
}
|
|
},
|
|
},
|
|
downpour: {
|
|
name: "Downpour",
|
|
shortDesc: "If targeted by a foe's move: move loses 1 extra PP, this Pokemon restores 1/16 max HP.",
|
|
onStart(pokemon) {
|
|
this.add('-ability', pokemon, 'Downpour');
|
|
},
|
|
onDeductPP(target, source) {
|
|
if (target.side === source.side) return;
|
|
this.heal(target.baseMaxhp / 16);
|
|
return 1;
|
|
},
|
|
rating: 2.5,
|
|
num: 46,
|
|
},
|
|
overclock: {
|
|
name: "Overclock",
|
|
shortDesc: "If stats are lowered by foe or if hit by Electric move: Atk +2.",
|
|
onAfterEachBoost(boost, target, source, effect) {
|
|
if (!source || target.side === source.side) {
|
|
if (effect.id === 'stickyweb') {
|
|
this.hint("Court Change Sticky Web counts as lowering your own Speed, and Defiant only affects stats lowered by foes.", true, source.side);
|
|
}
|
|
return;
|
|
}
|
|
let statsLowered = false;
|
|
let i: BoostID;
|
|
for (i in boost) {
|
|
if (boost[i]! < 0) {
|
|
statsLowered = true;
|
|
}
|
|
}
|
|
if (statsLowered) {
|
|
this.add('-ability', target, 'Overclock');
|
|
this.boost({atk: 2}, target, target, null, true);
|
|
}
|
|
},
|
|
onDamagingHit(damage, target, source, move) {
|
|
if (move.type === 'Electric') {
|
|
this.boost({atk: 2});
|
|
}
|
|
},
|
|
},
|
|
magicmissile: {
|
|
/**
|
|
* Need to test:
|
|
* - any Berry
|
|
* - Toxic Orb, Flame Orb or Light Ball (just one they're the same code)
|
|
* - White Herb
|
|
* - Mental Herb
|
|
* - um, I guess making sure Razor Claw or Razor Fang (just one they're the same code) doesn't immediately crash,
|
|
* but it would be basically impossible for them to cause a flinch in a singles context (how does this behave
|
|
* with Instruct? maybe you could test with that if you're doing the doubles format Aquatic mentioned)
|
|
*/
|
|
name: "Magic Missile",
|
|
shortDesc: "If hit by a contact move while holding an item: lose item, apply item Fling effects, attacker loses 1/4 max HP. If hitting a foe with a contact move while not holding an item: steals the foe's item.",
|
|
onSourceHit(target, source, move) {
|
|
if (!move || !target) return;
|
|
if (target !== source && move.category !== 'Status') {
|
|
if (source.item || source.volatiles['gem'] || move.id === 'fling') return;
|
|
const yourItem = target.takeItem(source);
|
|
if (!yourItem) return;
|
|
if (!source.setItem(yourItem)) {
|
|
target.item = yourItem.id; // bypass setItem so we don't break choicelock or anything
|
|
return;
|
|
}
|
|
this.add('-item', source, yourItem, '[from] ability: Magic Missile', '[of] ' + target);
|
|
}
|
|
},
|
|
onDamagingHit(damage, target, source, move) {
|
|
if (target.isSemiInvulnerable()) return;
|
|
if (target.ignoringItem()) return false;
|
|
const item = target.getItem();
|
|
if (!this.singleEvent('TakeItem', item, target.itemState, target, target, move, item)) return false;
|
|
if (item) {
|
|
target.addVolatile('fling');
|
|
this.damage(source.baseMaxhp / 4, source, target);
|
|
if (item.isBerry) {
|
|
if (this.singleEvent('Eat', item, null, source, null, null)) {
|
|
this.runEvent('EatItem', source, null, null, item);
|
|
if (item.id === 'leppaberry') source.staleness = 'external';
|
|
}
|
|
if (item.onEat) source.ateBerry = true;
|
|
} else if (item.id === 'mentalherb') {
|
|
const conditions = ['attract', 'taunt', 'encore', 'torment', 'disable', 'healblock'];
|
|
for (const firstCondition of conditions) {
|
|
if (source.volatiles[firstCondition]) {
|
|
for (const secondCondition of conditions) {
|
|
source.removeVolatile(secondCondition);
|
|
if (firstCondition === 'attract' && secondCondition === 'attract') {
|
|
this.add('-end', source, 'move: Attract', '[from] item: Mental Herb');
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
} else if (item.id === 'whiteherb') {
|
|
let activate = false;
|
|
const boosts: SparseBoostsTable = {};
|
|
let i: BoostID;
|
|
for (i in source.boosts) {
|
|
if (source.boosts[i] < 0) {
|
|
activate = true;
|
|
boosts[i] = 0;
|
|
}
|
|
}
|
|
if (activate) {
|
|
source.setBoost(boosts);
|
|
this.add('-clearnegativeboost', source, '[silent]');
|
|
}
|
|
} else {
|
|
if (item.fling && item.fling.status) {
|
|
source.trySetStatus(item.fling.status, target);
|
|
} else if (item.fling && item.fling.volatileStatus) {
|
|
source.addVolatile(item.fling.volatileStatus, target);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
},
|
|
};
|