mirror of
https://github.com/smogon/pokemon-showdown.git
synced 2026-03-21 17:25:10 -05:00
* Implement Mega Stones as pairs {key: value}
* Fix Mega Evolution check
* Add constructor guards to Dex getters
* Update config/formats.ts
---------
Co-authored-by: Kris Johnson <11083252+KrisXV@users.noreply.github.com>
547 lines
20 KiB
TypeScript
547 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" || item.isNonstandard === "Future") 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 || item.megaStone[pokemon.baseSpecies.name]) return null;
|
|
return Object.values(item.megaStone)[0];
|
|
},
|
|
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.join('/') !== pokemon.species.types.join('/')) {
|
|
this.battle.add('-start', pokemon, 'typechange', pokemon.species.types.join('/'), '[silent]');
|
|
}
|
|
// }
|
|
|
|
pokemon.canMegaEvo = false;
|
|
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) {
|
|
// Should be fine as long as Necrozma-U doesn't get added or Game Freak makes me sad with some convoluted forme change
|
|
let baseSpecies = this.dex.species.get(formeChangeSpecies.isMega ?
|
|
formeChangeSpecies.battleOnly as string : formeChangeSpecies.baseSpecies);
|
|
if (formeChangeSpecies.name === 'Zygarde-Mega') {
|
|
baseSpecies = this.dex.species.get('Zygarde-Complete');
|
|
}
|
|
const deltas: {
|
|
ability: string,
|
|
baseStats: SparseStatsTable,
|
|
weighthg: number,
|
|
heightm: number,
|
|
originalSpecies: string,
|
|
requiredItem: string | undefined,
|
|
type?: string,
|
|
formeType?: string,
|
|
isMega?: boolean,
|
|
} = {
|
|
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 = 'Primary';
|
|
} 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];
|
|
} else if (formeChangeSpecies.types[0] !== baseSpecies.types[0]) {
|
|
deltas.type = formeChangeSpecies.types[0];
|
|
formeType = 'Primary';
|
|
deltas.isMega = true;
|
|
}
|
|
if (formeChangeSpecies.isMega && !formeType) 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 === 'Primary') {
|
|
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' || deltas.isMega) species.isMega = true;
|
|
if (deltas.formeType === 'Primal') species.isPrimal = true;
|
|
return species;
|
|
},
|
|
},
|
|
};
|