pokemon-showdown/data/mods/fusionevolutionuu/abilities.ts
Guangcong Luo f9fdc73133
Support per-pokemon Residual handlers in Side/Field conditions (#8222)
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
2021-04-25 10:55:54 -07:00

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);
}
}
}
},
},
};