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.
555 lines
20 KiB
TypeScript
555 lines
20 KiB
TypeScript
export const Scripts: ModdedBattleScriptsData = {
|
|
inherit: 'gen8',
|
|
gen: 8,
|
|
getActionSpeed(action) {
|
|
if (action.choice === 'move') {
|
|
let move = action.move;
|
|
if (action.zmove) {
|
|
const zMoveName = this.actions.getZMove(action.move, action.pokemon, true);
|
|
if (zMoveName) {
|
|
const zMove = this.dex.getActiveMove(zMoveName);
|
|
if (zMove.exists && zMove.isZ) {
|
|
move = zMove;
|
|
}
|
|
}
|
|
}
|
|
if (action.maxMove) {
|
|
const maxMoveName = this.actions.getMaxMove(action.maxMove, action.pokemon);
|
|
if (maxMoveName) {
|
|
const maxMove = this.actions.getActiveMaxMove(action.move, action.pokemon);
|
|
if (maxMove.exists && maxMove.isMax) {
|
|
move = maxMove;
|
|
}
|
|
}
|
|
}
|
|
// take priority from the base move, so abilities like Prankster only apply once
|
|
// (instead of compounding every time `getActionSpeed` is called)
|
|
let priority = this.dex.moves.get(move.id).priority;
|
|
// Grassy Glide priority
|
|
priority = this.singleEvent('ModifyPriority', move, null, action.pokemon, null, null, priority);
|
|
priority = this.runEvent('ModifyPriority', action.pokemon, null, move, priority);
|
|
// Linked mod
|
|
const linkedMoves: [string, string] = action.pokemon.getLinkedMoves();
|
|
let linkIndex = -1;
|
|
if (linkedMoves.length && !move.isZ && !move.isMax && (linkIndex = linkedMoves.indexOf(this.toID(action.move))) >= 0) {
|
|
const linkedActions = action.linked || linkedMoves.map(moveid => this.dex.getActiveMove(moveid));
|
|
const altMove = linkedActions[1 - linkIndex];
|
|
const thisPriority = this.runEvent('ModifyPriority', action.pokemon, null, linkedActions[linkIndex], priority);
|
|
const thatPriority = this.runEvent('ModifyPriority', action.pokemon, null, altMove, altMove.priority);
|
|
priority = Math.min(thisPriority, thatPriority);
|
|
action.priority = priority + action.fractionalPriority;
|
|
if (this.gen > 5) {
|
|
// Gen 6+: Quick Guard blocks moves with artificially enhanced priority.
|
|
// This also applies to Psychic Terrain.
|
|
linkedActions[linkIndex].priority = priority;
|
|
altMove.priority = priority;
|
|
}
|
|
} else {
|
|
action.priority = priority + action.fractionalPriority;
|
|
// In Gen 6, Quick Guard blocks moves with artificially enhanced priority.
|
|
if (this.gen > 5) action.move.priority = priority;
|
|
}
|
|
}
|
|
|
|
if (!action.pokemon) {
|
|
action.speed = 1;
|
|
} else {
|
|
action.speed = action.pokemon.getActionSpeed();
|
|
}
|
|
},
|
|
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');
|
|
|
|
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;
|
|
// Linked moves
|
|
// @ts-expect-error modded
|
|
if (action.linked) {
|
|
// @ts-expect-error modded
|
|
const linkedMoves: ActiveMove[] = action.linked;
|
|
for (let i = linkedMoves.length - 1; i >= 0; i--) {
|
|
const validTarget = this.validTargetLoc(action.targetLoc, action.pokemon, linkedMoves[i].target);
|
|
const targetLoc = validTarget ? action.targetLoc : 0;
|
|
const pseudoAction: Action = {
|
|
choice: 'move', priority: action.priority, speed: action.speed, pokemon: action.pokemon,
|
|
targetLoc, moveid: linkedMoves[i].id, move: linkedMoves[i], mega: action.mega,
|
|
order: action.order, fractionalPriority: action.fractionalPriority, originalTarget: action.originalTarget,
|
|
};
|
|
this.queue.unshift(pseudoAction);
|
|
}
|
|
return;
|
|
}
|
|
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 '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 '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 '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) {
|
|
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++) {
|
|
if (switches[i] && !this.canSwitch(this.sides[i])) {
|
|
for (const pokemon of this.sides[i].active) {
|
|
pokemon.switchFlag = false;
|
|
}
|
|
switches[i] = false;
|
|
} else if (switches[i]) {
|
|
for (const pokemon of this.sides[i].active) {
|
|
if (pokemon.switchFlag && !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') {
|
|
// 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: {
|
|
runMove(moveOrMoveName, pokemon, targetLoc, options) {
|
|
pokemon.activeMoveActions++;
|
|
const zMove = options?.zMove;
|
|
const maxMove = options?.maxMove;
|
|
const externalMove = options?.externalMove;
|
|
const originalTarget = options?.originalTarget;
|
|
let sourceEffect = options?.sourceEffect;
|
|
let target = this.battle.getTarget(pokemon, maxMove || zMove || moveOrMoveName, targetLoc, originalTarget);
|
|
let baseMove = this.dex.getActiveMove(moveOrMoveName);
|
|
const priority = baseMove.priority;
|
|
const pranksterBoosted = baseMove.pranksterBoosted;
|
|
if (baseMove.id !== 'struggle' && !zMove && !maxMove && !externalMove) {
|
|
const changedMove = this.battle.runEvent('OverrideAction', pokemon, target, baseMove);
|
|
if (changedMove && changedMove !== true) {
|
|
baseMove = this.dex.getActiveMove(changedMove);
|
|
baseMove.priority = priority;
|
|
if (pranksterBoosted) baseMove.pranksterBoosted = pranksterBoosted;
|
|
target = this.battle.getRandomTarget(pokemon, baseMove);
|
|
}
|
|
}
|
|
let move = baseMove;
|
|
if (zMove) {
|
|
move = this.getActiveZMove(baseMove, pokemon);
|
|
} else if (maxMove) {
|
|
move = this.getActiveMaxMove(baseMove, pokemon);
|
|
}
|
|
|
|
move.isExternal = externalMove;
|
|
|
|
this.battle.setActiveMove(move, pokemon, target);
|
|
|
|
/* if (pokemon.moveThisTurn) {
|
|
// THIS IS PURELY A SANITY CHECK
|
|
// DO NOT TAKE ADVANTAGE OF THIS TO PREVENT A POKEMON FROM MOVING;
|
|
// USE this.queue.cancelMove INSTEAD
|
|
this.debug('' + pokemon.id + ' INCONSISTENT STATE, ALREADY MOVED: ' + pokemon.moveThisTurn);
|
|
this.clearActiveMove(true);
|
|
return;
|
|
} */
|
|
const willTryMove = this.battle.runEvent('BeforeMove', pokemon, target, move);
|
|
if (!willTryMove) {
|
|
if (pokemon.volatiles['twoturnmove']?.move === move.id) {
|
|
pokemon.removeVolatile('twoturnmove');
|
|
}
|
|
this.battle.runEvent('MoveAborted', pokemon, target, move);
|
|
this.battle.clearActiveMove(true);
|
|
// The event 'BeforeMove' could have returned false or null
|
|
// false indicates that this counts as a move failing for the purpose of calculating Stomping Tantrum's base power
|
|
// null indicates the opposite, as the Pokemon didn't have an option to choose anything
|
|
pokemon.moveThisTurnResult = willTryMove;
|
|
return;
|
|
}
|
|
if (move.beforeMoveCallback) {
|
|
if (move.beforeMoveCallback.call(this.battle, pokemon, target, move)) {
|
|
this.battle.clearActiveMove(true);
|
|
pokemon.moveThisTurnResult = false;
|
|
return;
|
|
}
|
|
}
|
|
pokemon.lastDamage = 0;
|
|
let lockedMove;
|
|
if (!externalMove) {
|
|
lockedMove = this.battle.runEvent('LockMove', pokemon);
|
|
if (lockedMove === true) lockedMove = false;
|
|
if (!lockedMove) {
|
|
if (!pokemon.deductPP(baseMove, null, target) && (move.id !== 'struggle')) {
|
|
this.battle.add('cant', pokemon, 'nopp', move);
|
|
const gameConsole = [
|
|
null, 'Game Boy', 'Game Boy Color', 'Game Boy Advance', 'DS', 'DS', '3DS', '3DS',
|
|
][this.battle.gen] || 'Switch';
|
|
this.battle.hint(`This is not a bug, this is really how it works on the ${gameConsole}; try it yourself if you don't believe us.`);
|
|
this.battle.clearActiveMove(true);
|
|
pokemon.moveThisTurnResult = false;
|
|
return;
|
|
}
|
|
} else {
|
|
sourceEffect = this.dex.conditions.get('lockedmove');
|
|
}
|
|
pokemon.moveUsed(move, targetLoc);
|
|
}
|
|
|
|
// Dancer Petal Dance hack
|
|
// TODO: implement properly
|
|
const noLock = externalMove && !pokemon.volatiles.lockedmove;
|
|
|
|
if (zMove) {
|
|
if (pokemon.illusion) {
|
|
this.battle.singleEvent('End', this.dex.abilities.get('Illusion'), pokemon.abilityState, pokemon);
|
|
}
|
|
this.battle.add('-zpower', pokemon);
|
|
pokemon.side.zMoveUsed = true;
|
|
}
|
|
const moveDidSomething = this.useMove(baseMove, pokemon, { target, sourceEffect, zMove, maxMove });
|
|
this.battle.lastSuccessfulMoveThisTurn = moveDidSomething ? this.battle.activeMove && this.battle.activeMove.id : null;
|
|
if (this.battle.activeMove) move = this.battle.activeMove;
|
|
this.battle.singleEvent('AfterMove', move, null, pokemon, target, move);
|
|
this.battle.runEvent('AfterMove', pokemon, target, move);
|
|
|
|
// Dancer's activation order is completely different from any other event, so it's handled separately
|
|
if (move.flags['dance'] && moveDidSomething && !move.isExternal) {
|
|
const dancers = [];
|
|
for (const currentPoke of this.battle.getAllActive()) {
|
|
if (pokemon === currentPoke) continue;
|
|
if (currentPoke.hasAbility('dancer') && !currentPoke.isSemiInvulnerable()) {
|
|
dancers.push(currentPoke);
|
|
}
|
|
}
|
|
// Dancer activates in order of lowest speed stat to highest
|
|
// Note that the speed stat used is after any volatile replacements like Speed Swap,
|
|
// but before any multipliers like Agility or Choice Scarf
|
|
// Ties go to whichever Pokemon has had the ability for the least amount of time
|
|
dancers.sort((a, b) => -(b.storedStats['spe'] - a.storedStats['spe']) ||
|
|
b.abilityState.effectOrder - a.abilityState.effectOrder);
|
|
for (const dancer of dancers) {
|
|
if (this.battle.faintMessages()) break;
|
|
if (dancer.fainted) continue;
|
|
this.battle.add('-activate', dancer, 'ability: Dancer');
|
|
const dancersTarget = !target!.isAlly(dancer) && pokemon.isAlly(dancer) ? target! : pokemon;
|
|
this.runMove(move.id, dancer, dancer.getLocOf(dancersTarget),
|
|
{ sourceEffect: this.dex.abilities.get('dancer'), externalMove: true });
|
|
}
|
|
}
|
|
if (noLock && pokemon.volatiles['lockedmove']) delete pokemon.volatiles['lockedmove'];
|
|
this.battle.faintMessages();
|
|
this.battle.checkWin();
|
|
},
|
|
},
|
|
queue: {
|
|
resolveAction(action, midTurn = false) {
|
|
if (!action) throw new Error(`Action not passed to resolveAction`);
|
|
if (action.choice === 'pass') return [];
|
|
const actions = [action];
|
|
|
|
if (!action.side && action.pokemon) action.side = action.pokemon.side;
|
|
if (!action.move && action.moveid) action.move = this.battle.dex.getActiveMove(action.moveid);
|
|
if (!action.order) {
|
|
const orders: { [choice: string]: number } = {
|
|
team: 1,
|
|
start: 2,
|
|
instaswitch: 3,
|
|
beforeTurn: 4,
|
|
beforeTurnMove: 5,
|
|
|
|
runUnnerve: 100,
|
|
runSwitch: 101,
|
|
// runPrimal: 102, (deprecated)
|
|
switch: 103,
|
|
megaEvo: 104,
|
|
runDynamax: 105,
|
|
|
|
shift: 200,
|
|
// default is 200 (for moves)
|
|
|
|
residual: 300,
|
|
};
|
|
if (action.choice in orders) {
|
|
action.order = orders[action.choice];
|
|
} else {
|
|
action.order = 200;
|
|
if (!['move', 'event'].includes(action.choice)) {
|
|
throw new Error(`Unexpected orderless action ${action.choice}`);
|
|
}
|
|
}
|
|
}
|
|
if (!midTurn) {
|
|
if (action.choice === 'move') {
|
|
if (!action.maxMove && !action.zmove && action.move.beforeTurnCallback) {
|
|
actions.unshift(...this.resolveAction({
|
|
choice: 'beforeTurnMove', pokemon: action.pokemon, move: action.move, targetLoc: action.targetLoc,
|
|
}));
|
|
}
|
|
if (action.mega && !action.pokemon.isSkyDropped()) {
|
|
actions.unshift(...this.resolveAction({
|
|
choice: 'megaEvo',
|
|
pokemon: action.pokemon,
|
|
}));
|
|
}
|
|
if (action.maxMove && !action.pokemon.volatiles['dynamax']) {
|
|
actions.unshift(...this.resolveAction({
|
|
choice: 'runDynamax',
|
|
pokemon: action.pokemon,
|
|
}));
|
|
}
|
|
action.fractionalPriority = this.battle.runEvent('FractionalPriority', action.pokemon, null, action.move, 0);
|
|
const linkedMoves: [string, string] = action.pokemon.getLinkedMoves();
|
|
if (
|
|
linkedMoves.length &&
|
|
!(action.pokemon.getItem().isChoice || action.pokemon.hasAbility('gorillatactics')) &&
|
|
!action.zmove && !action.maxMove
|
|
) {
|
|
const decisionMove = this.battle.toID(action.move);
|
|
if (linkedMoves.includes(decisionMove)) {
|
|
action.linked = linkedMoves.map(moveid => this.battle.dex.getActiveMove(moveid));
|
|
const linkedOtherMove = action.linked[1 - linkedMoves.indexOf(decisionMove)];
|
|
if (linkedOtherMove.beforeTurnCallback) {
|
|
this.addChoice({
|
|
choice: 'beforeTurnMove',
|
|
pokemon: action.pokemon,
|
|
move: linkedOtherMove,
|
|
targetLoc: action.targetLoc,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
} else if (['switch', 'instaswitch'].includes(action.choice)) {
|
|
if (typeof action.pokemon.switchFlag === 'string') {
|
|
action.sourceEffect = this.battle.dex.moves.get(action.pokemon.switchFlag as ID) as any;
|
|
}
|
|
action.pokemon.switchFlag = false;
|
|
}
|
|
}
|
|
|
|
const deferPriority = this.battle.gen === 7 && action.mega && action.mega !== 'done';
|
|
if (action.move) {
|
|
let target = null;
|
|
action.move = this.battle.dex.getActiveMove(action.move);
|
|
|
|
if (!action.targetLoc) {
|
|
target = this.battle.getRandomTarget(action.pokemon, action.move);
|
|
// TODO: what actually happens here?
|
|
if (target) action.targetLoc = action.pokemon.getLocOf(target);
|
|
}
|
|
action.originalTarget = action.pokemon.getAtLoc(action.targetLoc);
|
|
}
|
|
if (!deferPriority) this.battle.getActionSpeed(action);
|
|
return actions as any;
|
|
},
|
|
},
|
|
pokemon: {
|
|
moveUsed(move, targetLoc) {
|
|
if (!this.moveThisTurn) this.m.lastMoveAbsolute = move;
|
|
this.lastMove = move;
|
|
this.moveThisTurn = move.id;
|
|
this.lastMoveTargetLoc = targetLoc;
|
|
},
|
|
getLinkedMoves(ignoreDisabled) {
|
|
const linkedMoves = this.moveSlots.slice(0, 2);
|
|
if (linkedMoves.length !== 2 || linkedMoves[0].pp <= 0 || linkedMoves[1].pp <= 0) return [];
|
|
const ret = [linkedMoves[0].id, linkedMoves[1].id];
|
|
if (ignoreDisabled) return ret;
|
|
if (!this.ateBerry && ret.includes('belch' as ID)) return [];
|
|
if (this.hasItem('assaultvest') &&
|
|
(this.battle.dex.moves.get(ret[0]).category === 'Status' || this.battle.dex.moves.get(ret[1]).category === 'Status')) {
|
|
return [];
|
|
}
|
|
return ret;
|
|
},
|
|
hasLinkedMove(moveid) {
|
|
// @ts-expect-error modded
|
|
const linkedMoves: ID[] = this.getLinkedMoves(true);
|
|
if (!linkedMoves.length) return false;
|
|
return linkedMoves.some(x => x === moveid);
|
|
},
|
|
},
|
|
};
|