mirror of
https://github.com/smogon/pokemon-showdown.git
synced 2026-03-21 17:25:10 -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.
544 lines
20 KiB
TypeScript
544 lines
20 KiB
TypeScript
export const Scripts: ModdedBattleScriptsData = {
|
|
gen: 9,
|
|
init() {
|
|
for (const i in this.data.Items) {
|
|
const item = this.data.Items[i];
|
|
if (!item.megaStone && !item.onDrive && !(item.onPlate && !item.zMove) && !item.onMemory) continue;
|
|
this.modData('Items', i).onTakeItem = false;
|
|
if (item.isNonstandard) this.modData('Items', i).isNonstandard = null;
|
|
if (item.megaStone) {
|
|
this.modData('FormatsData', this.toID(item.megaStone)).isNonstandard = null;
|
|
}
|
|
}
|
|
},
|
|
start() {
|
|
// Deserialized games should use restart()
|
|
if (this.deserialized) return;
|
|
// need all players to start
|
|
if (!this.sides.every(side => !!side)) throw new Error(`Missing sides: ${this.sides}`);
|
|
|
|
if (this.started) throw new Error(`Battle already started`);
|
|
|
|
const format = this.format;
|
|
this.started = true;
|
|
if (this.gameType === 'multi') {
|
|
this.sides[1].foe = this.sides[2]!;
|
|
this.sides[0].foe = this.sides[3]!;
|
|
this.sides[2]!.foe = this.sides[1];
|
|
this.sides[3]!.foe = this.sides[0];
|
|
this.sides[1].allySide = this.sides[3]!;
|
|
this.sides[0].allySide = this.sides[2]!;
|
|
this.sides[2]!.allySide = this.sides[0];
|
|
this.sides[3]!.allySide = this.sides[1];
|
|
// sync side conditions
|
|
this.sides[2]!.sideConditions = this.sides[0].sideConditions;
|
|
this.sides[3]!.sideConditions = this.sides[1].sideConditions;
|
|
} else {
|
|
this.sides[1].foe = this.sides[0];
|
|
this.sides[0].foe = this.sides[1];
|
|
if (this.sides.length > 2) { // ffa
|
|
this.sides[2]!.foe = this.sides[3]!;
|
|
this.sides[3]!.foe = this.sides[2]!;
|
|
}
|
|
}
|
|
|
|
for (const side of this.sides) {
|
|
this.add('teamsize', side.id, side.pokemon.length);
|
|
}
|
|
|
|
this.add('gen', this.gen);
|
|
|
|
this.add('tier', format.name);
|
|
if (this.rated) {
|
|
if (this.rated === 'Rated battle') this.rated = true;
|
|
this.add('rated', typeof this.rated === 'string' ? this.rated : '');
|
|
}
|
|
|
|
if (format.onBegin) format.onBegin.call(this);
|
|
for (const rule of this.ruleTable.keys()) {
|
|
if ('+*-!'.includes(rule.charAt(0))) continue;
|
|
const subFormat = this.dex.formats.get(rule);
|
|
if (subFormat.onBegin) subFormat.onBegin.call(this);
|
|
}
|
|
for (const pokemon of this.getAllPokemon()) {
|
|
const item = pokemon.getItem();
|
|
if (item.forcedForme && !item.zMove && item.forcedForme !== pokemon.species.name) {
|
|
const rawSpecies = (this.actions as any).getMixedSpecies(pokemon.m.originalSpecies, item.forcedForme, pokemon);
|
|
const species = pokemon.setSpecies(rawSpecies);
|
|
if (!species) continue;
|
|
pokemon.baseSpecies = rawSpecies;
|
|
pokemon.details = pokemon.getUpdatedDetails();
|
|
pokemon.ability = this.toID(species.abilities['0']);
|
|
pokemon.baseAbility = pokemon.ability;
|
|
}
|
|
}
|
|
|
|
if (this.sides.some(side => !side.pokemon[0])) {
|
|
throw new Error('Battle not started: A player has an empty team.');
|
|
}
|
|
|
|
if (this.debugMode) {
|
|
this.checkEVBalance();
|
|
}
|
|
|
|
if (format.onTeamPreview) format.onTeamPreview.call(this);
|
|
for (const rule of this.ruleTable.keys()) {
|
|
if ('+*-!'.includes(rule.charAt(0))) continue;
|
|
const subFormat = this.dex.formats.get(rule);
|
|
if (subFormat.onTeamPreview) subFormat.onTeamPreview.call(this);
|
|
}
|
|
|
|
this.queue.addChoice({ choice: 'start' });
|
|
this.midTurn = true;
|
|
if (!this.requestState) this.turnLoop();
|
|
},
|
|
runAction(action) {
|
|
const pokemonOriginalHP = action.pokemon?.hp;
|
|
let residualPokemon: (readonly [Pokemon, number])[] = [];
|
|
// returns whether or not we ended in a callback
|
|
switch (action.choice) {
|
|
case 'start': {
|
|
for (const side of this.sides) {
|
|
if (side.pokemonLeft) side.pokemonLeft = side.pokemon.length;
|
|
}
|
|
|
|
this.add('start');
|
|
|
|
// Change Pokemon holding Rusted items into their Crowned formes
|
|
for (const pokemon of this.getAllPokemon()) {
|
|
let rawSpecies: Species | null = null;
|
|
const item = pokemon.getItem();
|
|
if (item.id === 'rustedsword') {
|
|
rawSpecies = (this.actions as any).getMixedSpecies(pokemon.m.originalSpecies, 'Zacian-Crowned', pokemon);
|
|
} else if (item.id === 'rustedshield') {
|
|
rawSpecies = (this.actions as any).getMixedSpecies(pokemon.m.originalSpecies, 'Zamazenta-Crowned', pokemon);
|
|
}
|
|
if (!rawSpecies) continue;
|
|
const species = pokemon.setSpecies(rawSpecies);
|
|
if (!species) continue;
|
|
pokemon.baseSpecies = rawSpecies;
|
|
pokemon.details = pokemon.getUpdatedDetails();
|
|
pokemon.ability = this.toID(species.abilities['0']);
|
|
pokemon.baseAbility = pokemon.ability;
|
|
|
|
const behemothMove: { [k: string]: string } = {
|
|
'Rusted Sword': 'behemothblade', 'Rusted Shield': 'behemothbash',
|
|
};
|
|
const ironHead = pokemon.baseMoves.indexOf('ironhead');
|
|
if (ironHead >= 0) {
|
|
const move = this.dex.moves.get(behemothMove[pokemon.getItem().name]);
|
|
pokemon.baseMoveSlots[ironHead] = {
|
|
move: move.name,
|
|
id: move.id,
|
|
pp: move.noPPBoosts ? move.pp : move.pp * 8 / 5,
|
|
maxpp: move.noPPBoosts ? move.pp : move.pp * 8 / 5,
|
|
target: move.target,
|
|
disabled: false,
|
|
disabledSource: '',
|
|
used: false,
|
|
};
|
|
pokemon.moveSlots = pokemon.baseMoveSlots.slice();
|
|
}
|
|
}
|
|
|
|
if (this.format.onBattleStart) this.format.onBattleStart.call(this);
|
|
for (const rule of this.ruleTable.keys()) {
|
|
if ('+*-!'.includes(rule.charAt(0))) continue;
|
|
const subFormat = this.dex.formats.get(rule);
|
|
if (subFormat.onBattleStart) subFormat.onBattleStart.call(this);
|
|
}
|
|
|
|
for (const side of this.sides) {
|
|
for (let i = 0; i < side.active.length; i++) {
|
|
if (!side.pokemonLeft) {
|
|
// forfeited before starting
|
|
side.active[i] = side.pokemon[i];
|
|
side.active[i].fainted = true;
|
|
side.active[i].hp = 0;
|
|
} else {
|
|
this.actions.switchIn(side.pokemon[i], i);
|
|
}
|
|
}
|
|
}
|
|
for (const pokemon of this.getAllPokemon()) {
|
|
this.singleEvent('Start', this.dex.conditions.getByID(pokemon.species.id), pokemon.speciesState, pokemon);
|
|
}
|
|
this.midTurn = true;
|
|
break;
|
|
}
|
|
|
|
case 'move':
|
|
if (!action.pokemon.isActive) return false;
|
|
if (action.pokemon.fainted) return false;
|
|
this.actions.runMove(action.move, action.pokemon, action.targetLoc, {
|
|
sourceEffect: action.sourceEffect, zMove: action.zmove,
|
|
maxMove: action.maxMove, originalTarget: action.originalTarget,
|
|
});
|
|
break;
|
|
case 'megaEvo':
|
|
this.actions.runMegaEvo(action.pokemon);
|
|
break;
|
|
case 'runDynamax':
|
|
action.pokemon.addVolatile('dynamax');
|
|
action.pokemon.side.dynamaxUsed = true;
|
|
if (action.pokemon.side.allySide) action.pokemon.side.allySide.dynamaxUsed = true;
|
|
break;
|
|
case 'terastallize':
|
|
this.actions.terastallize(action.pokemon);
|
|
break;
|
|
case 'beforeTurnMove':
|
|
if (!action.pokemon.isActive) return false;
|
|
if (action.pokemon.fainted) return false;
|
|
this.debug('before turn callback: ' + action.move.id);
|
|
const target = this.getTarget(action.pokemon, action.move, action.targetLoc);
|
|
if (!target) return false;
|
|
if (!action.move.beforeTurnCallback) throw new Error(`beforeTurnMove has no beforeTurnCallback`);
|
|
action.move.beforeTurnCallback.call(this, action.pokemon, target);
|
|
break;
|
|
case 'priorityChargeMove':
|
|
if (!action.pokemon.isActive) return false;
|
|
if (action.pokemon.fainted) return false;
|
|
this.debug('priority charge callback: ' + action.move.id);
|
|
if (!action.move.priorityChargeCallback) throw new Error(`priorityChargeMove has no priorityChargeCallback`);
|
|
action.move.priorityChargeCallback.call(this, action.pokemon);
|
|
break;
|
|
|
|
case 'event':
|
|
this.runEvent(action.event!, action.pokemon);
|
|
break;
|
|
case 'team':
|
|
if (action.index === 0) {
|
|
action.pokemon.side.pokemon = [];
|
|
}
|
|
action.pokemon.side.pokemon.push(action.pokemon);
|
|
action.pokemon.position = action.index;
|
|
// we return here because the update event would crash since there are no active pokemon yet
|
|
return;
|
|
|
|
case 'pass':
|
|
return;
|
|
case 'instaswitch':
|
|
case 'switch':
|
|
if (action.choice === 'switch' && action.pokemon.status) {
|
|
this.singleEvent('CheckShow', this.dex.abilities.getByID('naturalcure' as ID), null, action.pokemon);
|
|
}
|
|
if (this.actions.switchIn(action.target, action.pokemon.position, action.sourceEffect) === 'pursuitfaint') {
|
|
// a pokemon fainted from Pursuit before it could switch
|
|
if (this.gen <= 4) {
|
|
// in gen 2-4, the switch still happens
|
|
this.hint("Previously chosen switches continue in Gen 2-4 after a Pursuit target faints.");
|
|
action.priority = -101;
|
|
this.queue.unshift(action);
|
|
break;
|
|
} else {
|
|
// in gen 5+, the switch is cancelled
|
|
this.hint("A Pokemon can't switch between when it runs out of HP and when it faints");
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case 'revivalblessing':
|
|
action.pokemon.side.pokemonLeft++;
|
|
if (action.target.position < action.pokemon.side.active.length) {
|
|
this.queue.addChoice({
|
|
choice: 'instaswitch',
|
|
pokemon: action.target,
|
|
target: action.target,
|
|
});
|
|
}
|
|
action.target.fainted = false;
|
|
action.target.faintQueued = false;
|
|
action.target.subFainted = false;
|
|
action.target.status = '';
|
|
action.target.hp = 1; // Needed so hp functions works
|
|
action.target.sethp(action.target.maxhp / 2);
|
|
this.add('-heal', action.target, action.target.getHealth, '[from] move: Revival Blessing');
|
|
action.pokemon.side.removeSlotCondition(action.pokemon, 'revivalblessing');
|
|
break;
|
|
case 'runSwitch':
|
|
this.actions.runSwitch(action.pokemon);
|
|
break;
|
|
case 'shift':
|
|
if (!action.pokemon.isActive) return false;
|
|
if (action.pokemon.fainted) return false;
|
|
this.swapPosition(action.pokemon, 1);
|
|
break;
|
|
|
|
case 'beforeTurn':
|
|
this.eachEvent('BeforeTurn');
|
|
break;
|
|
case 'residual':
|
|
this.add('');
|
|
this.clearActiveMove(true);
|
|
this.updateSpeed();
|
|
residualPokemon = this.getAllActive().map(pokemon => [pokemon, pokemon.getUndynamaxedHP()] as const);
|
|
this.fieldEvent('Residual');
|
|
this.add('upkeep');
|
|
break;
|
|
}
|
|
|
|
// phazing (Roar, etc)
|
|
for (const side of this.sides) {
|
|
for (const pokemon of side.active) {
|
|
if (pokemon.forceSwitchFlag) {
|
|
if (pokemon.hp) this.actions.dragIn(pokemon.side, pokemon.position);
|
|
pokemon.forceSwitchFlag = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
this.clearActiveMove();
|
|
|
|
// fainting
|
|
|
|
this.faintMessages();
|
|
if (this.ended) return true;
|
|
|
|
// switching (fainted pokemon, U-turn, Baton Pass, etc)
|
|
|
|
if (!this.queue.peek() || (this.gen <= 3 && ['move', 'residual'].includes(this.queue.peek()!.choice))) {
|
|
// in gen 3 or earlier, switching in fainted pokemon is done after
|
|
// every move, rather than only at the end of the turn.
|
|
this.checkFainted();
|
|
} else if (action.choice === 'megaEvo' && this.gen === 7) {
|
|
this.eachEvent('Update');
|
|
// In Gen 7, the action order is recalculated for a Pokémon that mega evolves.
|
|
for (const [i, queuedAction] of this.queue.list.entries()) {
|
|
if (queuedAction.pokemon === action.pokemon && queuedAction.choice === 'move') {
|
|
this.queue.list.splice(i, 1);
|
|
queuedAction.mega = 'done';
|
|
this.queue.insertChoice(queuedAction, true);
|
|
break;
|
|
}
|
|
}
|
|
return false;
|
|
} else if (this.queue.peek()?.choice === 'instaswitch') {
|
|
return false;
|
|
}
|
|
|
|
if (this.gen >= 5 && action.choice !== 'start') {
|
|
this.eachEvent('Update');
|
|
for (const [pokemon, originalHP] of residualPokemon) {
|
|
const maxhp = pokemon.getUndynamaxedHP(pokemon.maxhp);
|
|
if (pokemon.hp && pokemon.getUndynamaxedHP() <= maxhp / 2 && originalHP > maxhp / 2) {
|
|
this.runEvent('EmergencyExit', pokemon);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (action.choice === 'runSwitch') {
|
|
const pokemon = action.pokemon;
|
|
if (pokemon.hp && pokemon.hp <= pokemon.maxhp / 2 && pokemonOriginalHP! > pokemon.maxhp / 2) {
|
|
this.runEvent('EmergencyExit', pokemon);
|
|
}
|
|
}
|
|
|
|
const switches = this.sides.map(
|
|
side => side.active.some(pokemon => pokemon && !!pokemon.switchFlag)
|
|
);
|
|
|
|
for (let i = 0; i < this.sides.length; i++) {
|
|
let reviveSwitch = false; // Used to ignore the fake switch for Revival Blessing
|
|
if (switches[i] && !this.canSwitch(this.sides[i])) {
|
|
for (const pokemon of this.sides[i].active) {
|
|
if (this.sides[i].slotConditions[pokemon.position]['revivalblessing']) {
|
|
reviveSwitch = true;
|
|
continue;
|
|
}
|
|
pokemon.switchFlag = false;
|
|
}
|
|
if (!reviveSwitch) switches[i] = false;
|
|
} else if (switches[i]) {
|
|
for (const pokemon of this.sides[i].active) {
|
|
if (pokemon.switchFlag && pokemon.switchFlag !== 'revivalblessing' && !pokemon.skipBeforeSwitchOutEventFlag) {
|
|
this.runEvent('BeforeSwitchOut', pokemon);
|
|
pokemon.skipBeforeSwitchOutEventFlag = true;
|
|
this.faintMessages(); // Pokemon may have fainted in BeforeSwitchOut
|
|
if (this.ended) return true;
|
|
if (pokemon.fainted) {
|
|
switches[i] = this.sides[i].active.some(sidePokemon => sidePokemon && !!sidePokemon.switchFlag);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const playerSwitch of switches) {
|
|
if (playerSwitch) {
|
|
this.makeRequest('switch');
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (this.gen < 5) this.eachEvent('Update');
|
|
|
|
if (this.gen >= 8 && (this.queue.peek()?.choice === 'move' || this.queue.peek()?.choice === 'runDynamax')) {
|
|
// In gen 8, speed is updated dynamically so update the queue's speed properties and sort it.
|
|
this.updateSpeed();
|
|
for (const queueAction of this.queue.list) {
|
|
if (queueAction.pokemon) this.getActionSpeed(queueAction);
|
|
}
|
|
this.queue.sort();
|
|
}
|
|
|
|
return false;
|
|
},
|
|
actions: {
|
|
canMegaEvo(pokemon) {
|
|
if (pokemon.species.isMega) return null;
|
|
|
|
const item = pokemon.getItem();
|
|
if (item.megaStone) {
|
|
if (item.megaStone === pokemon.baseSpecies.name) return null;
|
|
return item.megaStone;
|
|
} else {
|
|
return null;
|
|
}
|
|
},
|
|
runMegaEvo(pokemon) {
|
|
if (pokemon.species.isMega) return false;
|
|
|
|
const species: Species = (this as any).getMixedSpecies(pokemon.m.originalSpecies, pokemon.canMegaEvo, pokemon);
|
|
|
|
/* Do we have a proper sprite for it? Code for when megas actually exist
|
|
if (this.dex.species.get(pokemon.canMegaEvo!).baseSpecies === pokemon.m.originalSpecies) {
|
|
pokemon.formeChange(species, pokemon.getItem(), true);
|
|
} else { */
|
|
const oSpecies = this.dex.species.get(pokemon.m.originalSpecies);
|
|
const oMegaSpecies = this.dex.species.get((species as any).originalSpecies);
|
|
pokemon.formeChange(species, pokemon.getItem(), true);
|
|
this.battle.add('-start', pokemon, oMegaSpecies.requiredItem, '[silent]');
|
|
if (oSpecies.types.length !== pokemon.species.types.length || oSpecies.types[1] !== pokemon.species.types[1]) {
|
|
this.battle.add('-start', pokemon, 'typechange', pokemon.species.types.join('/'), '[silent]');
|
|
}
|
|
// }
|
|
|
|
pokemon.canMegaEvo = null;
|
|
return true;
|
|
},
|
|
terastallize(pokemon) {
|
|
if (pokemon.illusion?.species.baseSpecies === 'Ogerpon') {
|
|
this.battle.singleEvent('End', this.dex.abilities.get('Illusion'), pokemon.abilityState, pokemon);
|
|
}
|
|
if (pokemon.illusion?.species.baseSpecies === 'Terapagos') {
|
|
this.battle.singleEvent('End', this.dex.abilities.get('Illusion'), pokemon.abilityState, pokemon);
|
|
}
|
|
|
|
let type = pokemon.teraType;
|
|
if (pokemon.species.baseSpecies !== 'Ogerpon' && pokemon.getItem().name.endsWith('Mask')) {
|
|
type = this.dex.species.get(pokemon.getItem().forcedForme).forceTeraType!;
|
|
}
|
|
this.battle.add('-terastallize', pokemon, type);
|
|
pokemon.terastallized = type;
|
|
for (const ally of pokemon.side.pokemon) {
|
|
ally.canTerastallize = null;
|
|
}
|
|
pokemon.addedType = '';
|
|
pokemon.knownType = true;
|
|
pokemon.apparentType = type;
|
|
if (pokemon.species.baseSpecies === 'Ogerpon') {
|
|
const tera = pokemon.species.id === 'ogerpon' ? 'tealtera' : 'tera';
|
|
pokemon.formeChange(pokemon.species.id + tera, pokemon.getItem(), true);
|
|
} else {
|
|
if (pokemon.getItem().name.endsWith('Mask')) {
|
|
const species: Species = (this as any).getMixedSpecies(pokemon.m.originalSpecies,
|
|
pokemon.getItem().forcedForme! + '-Tera', pokemon);
|
|
const oSpecies = this.dex.species.get(pokemon.m.originalSpecies);
|
|
const originalTeraSpecies = this.dex.species.get((species as any).originalSpecies);
|
|
pokemon.formeChange(species, pokemon.getItem(), true);
|
|
this.battle.add('-start', pokemon, originalTeraSpecies.requiredItem, '[silent]');
|
|
if (oSpecies.types.length !== pokemon.species.types.length || oSpecies.types[1] !== pokemon.species.types[1]) {
|
|
this.battle.add('-start', pokemon, 'typechange', pokemon.species.types.join('/'), '[silent]');
|
|
}
|
|
}
|
|
}
|
|
if (pokemon.species.name === 'Terapagos-Terastal' && type === 'Stellar') {
|
|
pokemon.formeChange('Terapagos-Stellar', null, true);
|
|
}
|
|
this.battle.runEvent('AfterTerastallization', pokemon);
|
|
},
|
|
getMixedSpecies(originalForme, formeChange, pokemon) {
|
|
const originalSpecies = this.dex.species.get(originalForme);
|
|
const formeChangeSpecies = this.dex.species.get(formeChange);
|
|
if (originalSpecies.baseSpecies === formeChangeSpecies.baseSpecies &&
|
|
!formeChangeSpecies.isMega && !formeChangeSpecies.isPrimal) {
|
|
return formeChangeSpecies;
|
|
}
|
|
const deltas = (this as any).getFormeChangeDeltas(formeChangeSpecies, pokemon);
|
|
const species = (this as any).mutateOriginalSpecies(originalSpecies, deltas);
|
|
return species;
|
|
},
|
|
getFormeChangeDeltas(formeChangeSpecies, pokemon) {
|
|
const baseSpecies = this.dex.species.get(formeChangeSpecies.baseSpecies);
|
|
const deltas: {
|
|
ability: string,
|
|
baseStats: SparseStatsTable,
|
|
weighthg: number,
|
|
heightm: number,
|
|
originalSpecies: string,
|
|
requiredItem: string | undefined,
|
|
type?: string,
|
|
formeType?: string,
|
|
} = {
|
|
ability: formeChangeSpecies.abilities['0'],
|
|
baseStats: {},
|
|
weighthg: formeChangeSpecies.weighthg - baseSpecies.weighthg,
|
|
heightm: ((formeChangeSpecies.heightm * 10) - (baseSpecies.heightm * 10)) / 10,
|
|
originalSpecies: formeChangeSpecies.name,
|
|
requiredItem: formeChangeSpecies.requiredItem,
|
|
};
|
|
let statId: StatID;
|
|
for (statId in formeChangeSpecies.baseStats) {
|
|
deltas.baseStats[statId] = formeChangeSpecies.baseStats[statId] - baseSpecies.baseStats[statId];
|
|
}
|
|
let formeType: string | null = null;
|
|
if (['Arceus', 'Silvally'].includes(baseSpecies.name)) {
|
|
deltas.type = formeChangeSpecies.types[0];
|
|
formeType = 'Arceus';
|
|
} else if (formeChangeSpecies.types.length > baseSpecies.types.length) {
|
|
deltas.type = formeChangeSpecies.types[1];
|
|
} else if (formeChangeSpecies.types.length < baseSpecies.types.length) {
|
|
deltas.type = this.battle.ruleTable.has('mixandmegaoldaggronite') ? 'mono' : baseSpecies.types[0];
|
|
} else if (formeChangeSpecies.types[1] !== baseSpecies.types[1]) {
|
|
deltas.type = formeChangeSpecies.types[1];
|
|
}
|
|
if (formeChangeSpecies.isMega) formeType = 'Mega';
|
|
if (formeChangeSpecies.isPrimal) formeType = 'Primal';
|
|
if (formeChangeSpecies.name.endsWith('Crowned')) formeType = 'Crowned';
|
|
if (formeType) deltas.formeType = formeType;
|
|
if (!deltas.formeType && formeChangeSpecies.abilities['H'] &&
|
|
pokemon && pokemon.baseSpecies.abilities['H'] === pokemon.getAbility().name) {
|
|
deltas.ability = formeChangeSpecies.abilities['H'];
|
|
}
|
|
return deltas;
|
|
},
|
|
mutateOriginalSpecies(speciesOrForme, deltas) {
|
|
if (!deltas) throw new TypeError("Must specify deltas!");
|
|
const species = this.dex.deepClone(this.dex.species.get(speciesOrForme));
|
|
species.abilities = { '0': deltas.ability };
|
|
if (deltas.formeType === 'Arceus') {
|
|
const secondType = species.types[1];
|
|
species.types = [deltas.type];
|
|
if (secondType && secondType !== deltas.type) species.types.push(secondType);
|
|
} else if (species.types[0] === deltas.type) {
|
|
species.types = [deltas.type];
|
|
} else if (deltas.type === 'mono') {
|
|
species.types = [species.types[0]];
|
|
} else if (deltas.type) {
|
|
species.types = [species.types[0], deltas.type];
|
|
}
|
|
const baseStats = species.baseStats;
|
|
for (const statName in baseStats) {
|
|
baseStats[statName] = this.battle.clampIntRange(baseStats[statName] + deltas.baseStats[statName], 1, 255);
|
|
}
|
|
species.weighthg = Math.max(1, species.weighthg + deltas.weighthg);
|
|
species.heightm = Math.max(0.1, ((species.heightm * 10) + (deltas.heightm * 10)) / 10);
|
|
species.originalSpecies = deltas.originalSpecies;
|
|
species.requiredItem = deltas.requiredItem;
|
|
if (deltas.formeType === 'Mega') species.isMega = true;
|
|
if (deltas.formeType === 'Primal') species.isPrimal = true;
|
|
return species;
|
|
},
|
|
},
|
|
};
|