mirror of
https://github.com/smogon/pokemon-showdown.git
synced 2026-08-20 16:14:34 -05:00
DRY is particularly relevant because of the hardcodes, which are unlikely to be kept in sync in OM code.
541 lines
20 KiB
TypeScript
541 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 === "Past") 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]!;
|
|
}
|
|
}
|
|
|
|
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 : '');
|
|
}
|
|
|
|
format.onBegin?.call(this);
|
|
for (const rule of this.ruleTable.keys()) {
|
|
if ('+*-!'.includes(rule.charAt(0))) continue;
|
|
const subFormat = this.dex.formats.get(rule);
|
|
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.customRules) {
|
|
const plural = format.customRules.length === 1 ? '' : 's';
|
|
const open = format.customRules.length <= 5 ? ' open' : '';
|
|
this.add(`raw|<div class="infobox"><details class="readmore"${open}><summary><strong>${format.customRules.length} custom rule${plural}:</strong></summary> ${format.customRules.join(', ')}</details></div>`);
|
|
}
|
|
|
|
this.runPickTeam();
|
|
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('teamsize', side.id, 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();
|
|
}
|
|
}
|
|
|
|
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);
|
|
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).requiredTeraType!;
|
|
}
|
|
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;
|
|
},
|
|
},
|
|
};
|