mirror of
https://github.com/smogon/pokemon-showdown.git
synced 2026-04-26 10:48:53 -05:00
ESLint has a whole new config format, so I figure it's a good time to make the config system saner. - First, we no longer have separate eslint-no-types configs. Lint performance shouldn't be enough of a problem to justify the relevant maintenance complexity. - Second, our base config should work out-of-the-box now. `npx eslint` will work as expected, without any CLI flags. You should still use `npm run lint` which adds the `--cached` flag for performance. - Third, whatever updates I did fixed style linting, which apparently has been bugged for quite some time, considering all the obvious mixed-tabs-and-spaces issues I found in the upgrade. Also here are some changes to our style rules. In particular: - Curly brackets (for objects etc) now have spaces inside them. Sorry for the huge change. ESLint doesn't support our old style, and most projects use Prettier style, so we might as well match them in this way. See https://github.com/eslint-stylistic/eslint-stylistic/issues/415 - String + number concatenation is no longer allowed. We now consistently use template strings for this.
573 lines
14 KiB
TypeScript
573 lines
14 KiB
TypeScript
export const Abilities: import('../../../sim/dex-abilities').ModdedAbilityDataTable = {
|
|
airlock: {
|
|
inherit: true,
|
|
onSwitchIn() {},
|
|
onStart(pokemon) {
|
|
pokemon.abilityState.ending = false;
|
|
},
|
|
},
|
|
angerpoint: {
|
|
inherit: true,
|
|
onAfterSubDamage(damage, target, source, move) {
|
|
if (!target.hp) return;
|
|
if (move && move.effectType === 'Move' && target.getMoveHitData(move).crit) {
|
|
target.setBoost({ atk: 6 });
|
|
this.add('-setboost', target, 'atk', 12, '[from] ability: Anger Point');
|
|
}
|
|
},
|
|
rating: 1.5,
|
|
},
|
|
baddreams: {
|
|
inherit: true,
|
|
onResidualOrder: 10,
|
|
onResidualSubOrder: 10,
|
|
},
|
|
blaze: {
|
|
onBasePowerPriority: 2,
|
|
onBasePower(basePower, attacker, defender, move) {
|
|
if (move.type === 'Fire' && attacker.hp <= attacker.maxhp / 3) {
|
|
this.debug('Blaze boost');
|
|
return this.chainModify(1.5);
|
|
}
|
|
},
|
|
name: "Blaze",
|
|
rating: 2,
|
|
num: 66,
|
|
},
|
|
cloudnine: {
|
|
inherit: true,
|
|
onSwitchIn() {},
|
|
onStart(pokemon) {
|
|
pokemon.abilityState.ending = false;
|
|
},
|
|
},
|
|
colorchange: {
|
|
inherit: true,
|
|
onDamagingHit(damage, target, source, move) {
|
|
if (!damage || !target.hp) return;
|
|
const type = move.type;
|
|
if (target.isActive && move.category !== 'Status' && type !== '???' && !target.hasType(type)) {
|
|
if (!target.setType(type)) return false;
|
|
this.add('-start', target, 'typechange', type, '[from] ability: Color Change');
|
|
}
|
|
},
|
|
onAfterMoveSecondary() {},
|
|
},
|
|
compoundeyes: {
|
|
onSourceModifyAccuracyPriority: 9,
|
|
onSourceModifyAccuracy(accuracy) {
|
|
if (typeof accuracy !== 'number') return;
|
|
this.debug('compoundeyes - enhancing accuracy');
|
|
return accuracy * 1.3;
|
|
},
|
|
inherit: true,
|
|
},
|
|
cutecharm: {
|
|
inherit: true,
|
|
onDamagingHit(damage, target, source, move) {
|
|
if (damage && move.flags['contact']) {
|
|
if (this.randomChance(3, 10)) {
|
|
source.addVolatile('attract', this.effectState.target);
|
|
}
|
|
}
|
|
},
|
|
},
|
|
download: {
|
|
inherit: true,
|
|
onStart(pokemon) {
|
|
let totaldef = 0;
|
|
let totalspd = 0;
|
|
for (const target of pokemon.foes()) {
|
|
if (target.volatiles.substitute) continue;
|
|
totaldef += target.getStat('def', false, true);
|
|
totalspd += target.getStat('spd', false, true);
|
|
}
|
|
if (totaldef && totaldef >= totalspd) {
|
|
this.boost({ spa: 1 });
|
|
} else if (totalspd) {
|
|
this.boost({ atk: 1 });
|
|
}
|
|
},
|
|
},
|
|
effectspore: {
|
|
inherit: true,
|
|
onDamagingHit(damage, target, source, move) {
|
|
if (damage && move.flags['contact'] && !source.status) {
|
|
const r = this.random(100);
|
|
if (r < 10) {
|
|
source.setStatus('slp', target);
|
|
} else if (r < 20) {
|
|
source.setStatus('par', target);
|
|
} else if (r < 30) {
|
|
source.setStatus('psn', target);
|
|
}
|
|
}
|
|
},
|
|
},
|
|
flamebody: {
|
|
inherit: true,
|
|
onDamagingHit(damage, target, source, move) {
|
|
if (damage && move.flags['contact']) {
|
|
if (this.randomChance(3, 10)) {
|
|
source.trySetStatus('brn', target);
|
|
}
|
|
}
|
|
},
|
|
},
|
|
flashfire: {
|
|
inherit: true,
|
|
onTryHit(target, source, move) {
|
|
if (target !== source && move.type === 'Fire') {
|
|
if (target.status === 'frz') {
|
|
return;
|
|
}
|
|
if (!target.addVolatile('flashfire')) {
|
|
this.add('-immune', target, '[from] ability: Flash Fire');
|
|
}
|
|
return null;
|
|
}
|
|
},
|
|
condition: {
|
|
noCopy: true, // doesn't get copied by Baton Pass
|
|
onStart(target) {
|
|
this.add('-start', target, 'ability: Flash Fire');
|
|
},
|
|
onModifyDamagePhase1(atk, attacker, defender, move) {
|
|
if (move.type === 'Fire') {
|
|
this.debug('Flash Fire boost');
|
|
return this.chainModify(1.5);
|
|
}
|
|
},
|
|
onEnd(target) {
|
|
this.add('-end', target, 'ability: Flash Fire', '[silent]');
|
|
},
|
|
},
|
|
},
|
|
flowergift: {
|
|
inherit: true,
|
|
onAllyModifyAtk(atk) {
|
|
if (this.field.isWeather('sunnyday')) {
|
|
return this.chainModify(1.5);
|
|
}
|
|
},
|
|
onAllyModifySpD(spd) {
|
|
if (this.field.isWeather('sunnyday')) {
|
|
return this.chainModify(1.5);
|
|
}
|
|
},
|
|
flags: { breakable: 1 },
|
|
},
|
|
forecast: {
|
|
inherit: true,
|
|
flags: { notrace: 1 },
|
|
},
|
|
forewarn: {
|
|
inherit: true,
|
|
onStart(pokemon) {
|
|
let warnMoves: Move[] = [];
|
|
let warnBp = 1;
|
|
for (const target of pokemon.foes()) {
|
|
for (const moveSlot of target.moveSlots) {
|
|
const move = this.dex.moves.get(moveSlot.move);
|
|
let bp = move.basePower;
|
|
if (move.ohko) bp = 160;
|
|
if (move.id === 'counter' || move.id === 'metalburst' || move.id === 'mirrorcoat') bp = 120;
|
|
if (!bp && move.category !== 'Status') bp = 80;
|
|
if (bp > warnBp) {
|
|
warnMoves = [move];
|
|
warnBp = bp;
|
|
} else if (bp === warnBp) {
|
|
warnMoves.push(move);
|
|
}
|
|
}
|
|
}
|
|
if (!warnMoves.length) return;
|
|
const warnMove = this.sample(warnMoves);
|
|
this.add('-activate', pokemon, 'ability: Forewarn', warnMove);
|
|
},
|
|
},
|
|
frisk: {
|
|
inherit: true,
|
|
onStart(pokemon) {
|
|
const target = pokemon.side.randomFoe();
|
|
if (target?.item && !target.itemState.knockedOff) {
|
|
this.add('-item', '', target.getItem().name, '[from] ability: Frisk', `[of] ${pokemon}`);
|
|
}
|
|
},
|
|
},
|
|
hustle: {
|
|
inherit: true,
|
|
onSourceModifyAccuracyPriority: 7,
|
|
onSourceModifyAccuracy(accuracy, target, source, move) {
|
|
if (move.category === 'Physical' && typeof accuracy === 'number') {
|
|
return accuracy * 0.8;
|
|
}
|
|
},
|
|
},
|
|
hydration: {
|
|
onWeather(target, source, effect) {
|
|
if (effect.id === 'raindance' && target.status) {
|
|
this.add('-activate', target, 'ability: Hydration');
|
|
target.cureStatus();
|
|
}
|
|
},
|
|
name: "Hydration",
|
|
rating: 1.5,
|
|
num: 93,
|
|
},
|
|
insomnia: {
|
|
inherit: true,
|
|
rating: 2.5,
|
|
},
|
|
intimidate: {
|
|
inherit: true,
|
|
onStart(pokemon) {
|
|
const activated = pokemon.adjacentFoes().some(target => (
|
|
!(target.volatiles['substitute'] || target.volatiles['substitutebroken']?.move === 'uturn')
|
|
));
|
|
|
|
if (!activated) {
|
|
this.hint("In Gen 4, Intimidate does not activate if every target has a Substitute (or the Substitute was just broken by U-turn).", false, pokemon.side);
|
|
return;
|
|
}
|
|
this.add('-ability', pokemon, 'Intimidate', 'boost');
|
|
|
|
for (const target of pokemon.adjacentFoes()) {
|
|
if (target.volatiles['substitute']) {
|
|
this.add('-immune', target);
|
|
} else if (target.volatiles['substitutebroken']?.move === 'uturn') {
|
|
this.hint("In Gen 4, if U-turn breaks Substitute the incoming Intimidate does nothing.");
|
|
} else {
|
|
this.boost({ atk: -1 }, target, pokemon, null, true);
|
|
}
|
|
}
|
|
},
|
|
},
|
|
leafguard: {
|
|
inherit: true,
|
|
onSetStatus(status, target, source, effect) {
|
|
if (effect && effect.id === 'rest') {
|
|
// do nothing
|
|
} else if (this.field.isWeather('sunnyday')) {
|
|
return false;
|
|
}
|
|
},
|
|
},
|
|
lightningrod: {
|
|
inherit: true,
|
|
onTryHit() {},
|
|
rating: 0,
|
|
},
|
|
liquidooze: {
|
|
inherit: true,
|
|
onSourceTryHeal(damage, target, source, effect) {
|
|
this.debug(`Heal is occurring: ${target} <- ${source} :: ${effect.id}`);
|
|
const canOoze = ['drain', 'leechseed'];
|
|
if (canOoze.includes(effect.id) && this.activeMove?.id !== 'dreameater') {
|
|
this.damage(damage, null, null, null, true);
|
|
return 0;
|
|
}
|
|
},
|
|
},
|
|
magicguard: {
|
|
onDamage(damage, target, source, effect) {
|
|
if (effect.effectType !== 'Move') {
|
|
return false;
|
|
}
|
|
},
|
|
onSetStatus(status, target, source, effect) {
|
|
if (effect && effect.id === 'toxicspikes') {
|
|
return false;
|
|
}
|
|
},
|
|
name: "Magic Guard",
|
|
rating: 4.5,
|
|
num: 98,
|
|
},
|
|
minus: {
|
|
onModifySpA(spa, pokemon) {
|
|
for (const ally of pokemon.allies()) {
|
|
if (ally.ability === 'plus') {
|
|
return spa * 1.5;
|
|
}
|
|
}
|
|
},
|
|
name: "Minus",
|
|
rating: 0,
|
|
num: 58,
|
|
},
|
|
naturalcure: {
|
|
inherit: true,
|
|
onCheckShow(pokemon) {},
|
|
onSwitchOut(pokemon) {
|
|
if (!pokemon.status || pokemon.status === 'fnt') return;
|
|
|
|
// Because statused/unstatused pokemon are shown after every switch
|
|
// in gen 3-4, Natural Cure's curing is always known to both players
|
|
|
|
this.add('-curestatus', pokemon, pokemon.status, '[from] ability: Natural Cure');
|
|
pokemon.clearStatus();
|
|
},
|
|
},
|
|
normalize: {
|
|
inherit: true,
|
|
onModifyMove(move) {
|
|
if (move.id !== 'struggle') {
|
|
move.type = 'Normal';
|
|
}
|
|
},
|
|
},
|
|
overgrow: {
|
|
onBasePowerPriority: 2,
|
|
onBasePower(basePower, attacker, defender, move) {
|
|
if (move.type === 'Grass' && attacker.hp <= attacker.maxhp / 3) {
|
|
this.debug('Overgrow boost');
|
|
return this.chainModify(1.5);
|
|
}
|
|
},
|
|
name: "Overgrow",
|
|
rating: 2,
|
|
num: 65,
|
|
},
|
|
pickup: {
|
|
name: "Pickup",
|
|
rating: 0,
|
|
num: 53,
|
|
},
|
|
plus: {
|
|
onModifySpA(spa, pokemon) {
|
|
for (const ally of pokemon.allies()) {
|
|
if (ally.ability === 'minus') {
|
|
return spa * 1.5;
|
|
}
|
|
}
|
|
},
|
|
name: "Plus",
|
|
rating: 0,
|
|
num: 57,
|
|
},
|
|
poisonpoint: {
|
|
inherit: true,
|
|
onDamagingHit(damage, target, source, move) {
|
|
if (damage && move.flags['contact']) {
|
|
if (this.randomChance(3, 10)) {
|
|
source.trySetStatus('psn', target);
|
|
}
|
|
}
|
|
},
|
|
},
|
|
pressure: {
|
|
onStart(pokemon) {
|
|
this.add('-ability', pokemon, 'Pressure');
|
|
},
|
|
onDeductPP(target, source) {
|
|
if (target === source) return;
|
|
return 1;
|
|
},
|
|
name: "Pressure",
|
|
rating: 1.5,
|
|
num: 46,
|
|
},
|
|
roughskin: {
|
|
inherit: true,
|
|
onDamagingHit(damage, target, source, move) {
|
|
if (damage && move.flags['contact']) {
|
|
this.damage(source.baseMaxhp / 8, source, target);
|
|
}
|
|
},
|
|
},
|
|
sandveil: {
|
|
inherit: true,
|
|
onModifyAccuracyPriority: 8,
|
|
onModifyAccuracy(accuracy) {
|
|
if (typeof accuracy !== 'number') return;
|
|
if (this.field.isWeather('sandstorm')) {
|
|
this.debug('Sand Veil - decreasing accuracy');
|
|
return accuracy * 0.8;
|
|
}
|
|
},
|
|
},
|
|
serenegrace: {
|
|
inherit: true,
|
|
onModifyMove(move) {
|
|
if (move.secondaries) {
|
|
this.debug('doubling secondary chance');
|
|
for (const secondary of move.secondaries) {
|
|
if (secondary.chance) secondary.chance *= 2;
|
|
}
|
|
}
|
|
},
|
|
},
|
|
shedskin: {
|
|
inherit: true,
|
|
onResidualOrder: 10,
|
|
onResidualSubOrder: 3,
|
|
},
|
|
simple: {
|
|
onModifyBoost(boosts) {
|
|
let key: BoostID;
|
|
for (key in boosts) {
|
|
boosts[key]! *= 2;
|
|
}
|
|
},
|
|
flags: { breakable: 1 },
|
|
name: "Simple",
|
|
rating: 4,
|
|
num: 86,
|
|
},
|
|
snowcloak: {
|
|
inherit: true,
|
|
onModifyAccuracyPriority: 8,
|
|
onModifyAccuracy(accuracy) {
|
|
if (typeof accuracy !== 'number') return;
|
|
if (this.field.isWeather('hail')) {
|
|
this.debug('Snow Cloak - decreasing accuracy');
|
|
return accuracy * 0.8;
|
|
}
|
|
},
|
|
},
|
|
speedboost: {
|
|
inherit: true,
|
|
onResidualOrder: 10,
|
|
onResidualSubOrder: 3,
|
|
},
|
|
static: {
|
|
inherit: true,
|
|
onDamagingHit(damage, target, source, move) {
|
|
if (damage && move.flags['contact']) {
|
|
if (this.randomChance(3, 10)) {
|
|
source.trySetStatus('par', target);
|
|
}
|
|
}
|
|
},
|
|
},
|
|
stench: {
|
|
name: "Stench",
|
|
rating: 0,
|
|
num: 1,
|
|
},
|
|
stickyhold: {
|
|
inherit: true,
|
|
onTakeItem(item, pokemon, source) {
|
|
if ((source && source !== pokemon) || (this.activeMove && this.activeMove.id === 'knockoff')) {
|
|
this.add('-activate', pokemon, 'ability: Sticky Hold');
|
|
return false;
|
|
}
|
|
},
|
|
},
|
|
stormdrain: {
|
|
inherit: true,
|
|
onTryHit() {},
|
|
rating: 0,
|
|
},
|
|
sturdy: {
|
|
inherit: true,
|
|
onDamage() {},
|
|
rating: 0,
|
|
},
|
|
swarm: {
|
|
onBasePowerPriority: 2,
|
|
onBasePower(basePower, attacker, defender, move) {
|
|
if (move.type === 'Bug' && attacker.hp <= attacker.maxhp / 3) {
|
|
this.debug('Swarm boost');
|
|
return this.chainModify(1.5);
|
|
}
|
|
},
|
|
name: "Swarm",
|
|
rating: 2,
|
|
num: 68,
|
|
},
|
|
synchronize: {
|
|
inherit: true,
|
|
onAfterSetStatus(status, target, source, effect) {
|
|
if (!source || source === target) return;
|
|
if (effect && effect.id === 'toxicspikes') return;
|
|
let id: string = status.id;
|
|
if (id === 'slp' || id === 'frz') return;
|
|
if (id === 'tox') id = 'psn';
|
|
source.trySetStatus(id, target);
|
|
},
|
|
},
|
|
tangledfeet: {
|
|
inherit: true,
|
|
onModifyAccuracyPriority: 6,
|
|
onModifyAccuracy(accuracy, target) {
|
|
if (typeof accuracy !== 'number') return;
|
|
if (target?.volatiles['confusion']) {
|
|
this.debug('Tangled Feet - decreasing accuracy');
|
|
return accuracy * 0.5;
|
|
}
|
|
},
|
|
},
|
|
thickfat: {
|
|
onSourceBasePowerPriority: 1,
|
|
onSourceBasePower(basePower, attacker, defender, move) {
|
|
if (move.type === 'Ice' || move.type === 'Fire') {
|
|
return this.chainModify(0.5);
|
|
}
|
|
},
|
|
flags: { breakable: 1 },
|
|
name: "Thick Fat",
|
|
rating: 3.5,
|
|
num: 47,
|
|
},
|
|
torrent: {
|
|
onBasePowerPriority: 2,
|
|
onBasePower(basePower, attacker, defender, move) {
|
|
if (move.type === 'Water' && attacker.hp <= attacker.maxhp / 3) {
|
|
this.debug('Torrent boost');
|
|
return this.chainModify(1.5);
|
|
}
|
|
},
|
|
name: "Torrent",
|
|
rating: 2,
|
|
num: 67,
|
|
},
|
|
trace: {
|
|
inherit: true,
|
|
onUpdate(pokemon) {
|
|
if (!this.effectState.seek) return;
|
|
const target = pokemon.side.randomFoe();
|
|
if (!target || target.fainted) return;
|
|
const ability = target.getAbility();
|
|
const bannedAbilities = ['forecast', 'multitype', 'trace'];
|
|
if (bannedAbilities.includes(target.ability)) {
|
|
return;
|
|
}
|
|
if (pokemon.setAbility(ability)) {
|
|
this.add('-ability', pokemon, ability, '[from] ability: Trace', `[of] ${target}`);
|
|
}
|
|
},
|
|
flags: { notrace: 1 },
|
|
},
|
|
unburden: {
|
|
inherit: true,
|
|
condition: {
|
|
onModifySpe(spe, pokemon) {
|
|
if ((!pokemon.item || pokemon.itemState.knockedOff) && !pokemon.ignoringAbility()) {
|
|
return this.chainModify(2);
|
|
}
|
|
},
|
|
},
|
|
},
|
|
vitalspirit: {
|
|
inherit: true,
|
|
rating: 2.5,
|
|
},
|
|
wonderguard: {
|
|
inherit: true,
|
|
onTryHit(target, source, move) {
|
|
if (move.id === 'firefang') {
|
|
this.hint("In Gen 4, Fire Fang is always able to hit through Wonder Guard.", true, target.side);
|
|
return;
|
|
}
|
|
if (target === source || move.category === 'Status' || move.type === '???' || move.id === 'struggle') return;
|
|
this.debug('Wonder Guard immunity: ' + move.id);
|
|
if (target.runEffectiveness(move) <= 0) {
|
|
this.add('-immune', target, '[from] ability: Wonder Guard');
|
|
return null;
|
|
}
|
|
},
|
|
},
|
|
};
|