mirror of
https://github.com/smogon/pokemon-showdown.git
synced 2026-03-21 17:25:10 -05:00
Refactor scripts to battle-actions (#8138)
This introduces a new class, BattleActions, available as `battle.actions`, moving all functions from `data/scripts.ts` to `sim/battle-actions.ts`. This makes it so that "go to definition" will now work correctly for functions previously in scripts; we no longer need UnimplementedError, and there's now a clean conceptual separation between `battle` and `battle-actions` (whereas the previous distinction between `battle` and `scripts` was basically nonexistent). This will be a difficult migration if you maintain a fork with custom scripted mods. I'm sorry! Migration instructions are here: https://github.com/smogon/pokemon-showdown/pull/8138
This commit is contained in:
parent
e42fa69c22
commit
ed454ef76a
|
|
@ -1539,7 +1539,7 @@ export const Formats: FormatList = [
|
|||
name: move.name,
|
||||
onStart(this: Battle, pokemon: Pokemon) {
|
||||
this.add('-activate', pokemon, 'ability: ' + move.name);
|
||||
this.useMove(move, pokemon);
|
||||
this.actions.useMove(move, pokemon);
|
||||
},
|
||||
toString() {
|
||||
return "";
|
||||
|
|
|
|||
|
|
@ -148,14 +148,14 @@ export const Abilities: {[abilityid: string]: AbilityData} = {
|
|||
},
|
||||
arenatrap: {
|
||||
onFoeTrapPokemon(pokemon) {
|
||||
if (!this.isAdjacent(pokemon, this.effectData.target)) return;
|
||||
if (!pokemon.isAdjacent(this.effectData.target)) return;
|
||||
if (pokemon.isGrounded()) {
|
||||
pokemon.tryTrap(true);
|
||||
}
|
||||
},
|
||||
onFoeMaybeTrapPokemon(pokemon, source) {
|
||||
if (!source) source = this.effectData.target;
|
||||
if (!source || !this.isAdjacent(pokemon, source)) return;
|
||||
if (!source || !pokemon.isAdjacent(source)) return;
|
||||
if (pokemon.isGrounded(!pokemon.knownType)) { // Negate immunity if the type is unknown
|
||||
pokemon.maybeTrapped = true;
|
||||
}
|
||||
|
|
@ -1362,11 +1362,8 @@ export const Abilities: {[abilityid: string]: AbilityData} = {
|
|||
if (pokemon.side.active.length === 1) {
|
||||
return;
|
||||
}
|
||||
for (const allyActive of pokemon.side.active) {
|
||||
if (
|
||||
allyActive &&
|
||||
(allyActive.hp && this.isAdjacent(pokemon, allyActive) && allyActive.status) && this.randomChance(3, 10)
|
||||
) {
|
||||
for (const allyActive of pokemon.adjacentAllies()) {
|
||||
if (allyActive.status && this.randomChance(3, 10)) {
|
||||
this.add('-activate', pokemon, 'ability: Healer');
|
||||
allyActive.cureStatus();
|
||||
}
|
||||
|
|
@ -1672,8 +1669,7 @@ export const Abilities: {[abilityid: string]: AbilityData} = {
|
|||
intimidate: {
|
||||
onStart(pokemon) {
|
||||
let activated = false;
|
||||
for (const target of pokemon.side.foe.active) {
|
||||
if (!target || !this.isAdjacent(target, pokemon)) continue;
|
||||
for (const target of pokemon.adjacentFoes()) {
|
||||
if (!activated) {
|
||||
this.add('-ability', pokemon, 'Intimidate', 'boost');
|
||||
activated = true;
|
||||
|
|
@ -1883,7 +1879,7 @@ export const Abilities: {[abilityid: string]: AbilityData} = {
|
|||
const newMove = this.dex.getActiveMove(move.id);
|
||||
newMove.hasBounced = true;
|
||||
newMove.pranksterBoosted = false;
|
||||
this.useMove(newMove, target, source);
|
||||
this.actions.useMove(newMove, target, source);
|
||||
return null;
|
||||
},
|
||||
onAllyTryHitSide(target, source, move) {
|
||||
|
|
@ -1893,7 +1889,7 @@ export const Abilities: {[abilityid: string]: AbilityData} = {
|
|||
const newMove = this.dex.getActiveMove(move.id);
|
||||
newMove.hasBounced = true;
|
||||
newMove.pranksterBoosted = false;
|
||||
this.useMove(newMove, this.effectData.target, source);
|
||||
this.actions.useMove(newMove, this.effectData.target, source);
|
||||
return null;
|
||||
},
|
||||
condition: {
|
||||
|
|
@ -1947,13 +1943,13 @@ export const Abilities: {[abilityid: string]: AbilityData} = {
|
|||
},
|
||||
magnetpull: {
|
||||
onFoeTrapPokemon(pokemon) {
|
||||
if (pokemon.hasType('Steel') && this.isAdjacent(pokemon, this.effectData.target)) {
|
||||
if (pokemon.hasType('Steel') && pokemon.isAdjacent(this.effectData.target)) {
|
||||
pokemon.tryTrap(true);
|
||||
}
|
||||
},
|
||||
onFoeMaybeTrapPokemon(pokemon, source) {
|
||||
if (!source) source = this.effectData.target;
|
||||
if (!source || !this.isAdjacent(pokemon, source)) return;
|
||||
if (!source || !pokemon.isAdjacent(source)) return;
|
||||
if (!pokemon.knownType || pokemon.hasType('Steel')) {
|
||||
pokemon.maybeTrapped = true;
|
||||
}
|
||||
|
|
@ -2561,12 +2557,9 @@ export const Abilities: {[abilityid: string]: AbilityData} = {
|
|||
onResidualSubOrder: 1,
|
||||
onResidual(pokemon) {
|
||||
if (pokemon.item) return;
|
||||
const pickupTargets = [];
|
||||
for (const target of this.getAllActive()) {
|
||||
if (target.lastItem && target.usedItemThisTurn && this.isAdjacent(pokemon, target)) {
|
||||
pickupTargets.push(target);
|
||||
}
|
||||
}
|
||||
const pickupTargets = this.getAllActive().filter(target => (
|
||||
target.lastItem && target.usedItemThisTurn && pokemon.isAdjacent(target)
|
||||
));
|
||||
if (!pickupTargets.length) return;
|
||||
const randomTarget = this.sample(pickupTargets);
|
||||
const item = randomTarget.lastItem;
|
||||
|
|
@ -3218,13 +3211,13 @@ export const Abilities: {[abilityid: string]: AbilityData} = {
|
|||
},
|
||||
shadowtag: {
|
||||
onFoeTrapPokemon(pokemon) {
|
||||
if (!pokemon.hasAbility('shadowtag') && this.isAdjacent(pokemon, this.effectData.target)) {
|
||||
if (!pokemon.hasAbility('shadowtag') && pokemon.isAdjacent(this.effectData.target)) {
|
||||
pokemon.tryTrap(true);
|
||||
}
|
||||
},
|
||||
onFoeMaybeTrapPokemon(pokemon, source) {
|
||||
if (!source) source = this.effectData.target;
|
||||
if (!source || !this.isAdjacent(pokemon, source)) return;
|
||||
if (!source || !pokemon.isAdjacent(source)) return;
|
||||
if (!pokemon.hasAbility('shadowtag')) {
|
||||
pokemon.maybeTrapped = true;
|
||||
}
|
||||
|
|
@ -3935,15 +3928,13 @@ export const Abilities: {[abilityid: string]: AbilityData} = {
|
|||
},
|
||||
trace: {
|
||||
onStart(pokemon) {
|
||||
if (pokemon.side.foe.active.some(
|
||||
foeActive => foeActive && this.isAdjacent(pokemon, foeActive) && foeActive.ability === 'noability'
|
||||
)) {
|
||||
if (pokemon.adjacentFoes().some(foeActive => foeActive.ability === 'noability')) {
|
||||
this.effectData.gaveUp = true;
|
||||
}
|
||||
},
|
||||
onUpdate(pokemon) {
|
||||
if (!pokemon.isStarted || this.effectData.gaveUp) return;
|
||||
const possibleTargets = pokemon.side.foe.active.filter(foeActive => foeActive && this.isAdjacent(pokemon, foeActive));
|
||||
const possibleTargets = pokemon.adjacentFoes();
|
||||
while (possibleTargets.length) {
|
||||
let rand = 0;
|
||||
if (possibleTargets.length > 1) rand = this.random(possibleTargets.length);
|
||||
|
|
@ -4381,7 +4372,7 @@ export const Abilities: {[abilityid: string]: AbilityData} = {
|
|||
}
|
||||
const newMove = this.dex.getActiveMove(move.id);
|
||||
newMove.hasBounced = true;
|
||||
this.useMove(newMove, target, source);
|
||||
this.actions.useMove(newMove, target, source);
|
||||
return null;
|
||||
},
|
||||
onAllyTryHitSide(target, source, move) {
|
||||
|
|
@ -4392,7 +4383,7 @@ export const Abilities: {[abilityid: string]: AbilityData} = {
|
|||
}
|
||||
const newMove = this.dex.getActiveMove(move.id);
|
||||
newMove.hasBounced = true;
|
||||
this.useMove(newMove, this.effectData.target, source);
|
||||
this.actions.useMove(newMove, this.effectData.target, source);
|
||||
return null;
|
||||
},
|
||||
condition: {
|
||||
|
|
|
|||
|
|
@ -172,7 +172,7 @@ export const Conditions: {[k: string]: ConditionData} = {
|
|||
return;
|
||||
}
|
||||
this.activeTarget = pokemon;
|
||||
const damage = this.getDamage(pokemon, pokemon, 40);
|
||||
const damage = this.actions.getDamage(pokemon, pokemon, 40);
|
||||
if (typeof damage !== 'number') throw new Error("Confusion damage not dealt");
|
||||
const activeMove = {id: this.toID('confused'), effectType: 'Move', type: '???'};
|
||||
this.damage(damage, pokemon, pokemon, activeMove as ActiveMove);
|
||||
|
|
@ -376,7 +376,7 @@ export const Conditions: {[k: string]: ConditionData} = {
|
|||
}
|
||||
const hitMove = new this.dex.Move(data.moveData) as ActiveMove;
|
||||
|
||||
this.trySpreadMoveHit([target], data.source, hitMove, true);
|
||||
this.actions.trySpreadMoveHit([target], data.source, hitMove, true);
|
||||
},
|
||||
},
|
||||
healreplacement: {
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
}
|
||||
this.add('-end', pokemon, 'Bide');
|
||||
const target = this.effectData.sourceSide.active[this.effectData.sourcePosition];
|
||||
this.moveHit(target, pokemon, move, {damage: this.effectData.totalDamage * 2} as ActiveMove);
|
||||
this.actions.moveHit(target, pokemon, move, {damage: this.effectData.totalDamage * 2} as ActiveMove);
|
||||
return false;
|
||||
}
|
||||
this.add('-activate', pokemon, 'Bide');
|
||||
|
|
@ -584,7 +584,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
if (!foe?.lastMove || foe.lastMove.id === 'mirrormove') {
|
||||
return false;
|
||||
}
|
||||
this.useMove(foe.lastMove.id, pokemon);
|
||||
this.actions.useMove(foe.lastMove.id, pokemon);
|
||||
},
|
||||
},
|
||||
mist: {
|
||||
|
|
@ -861,7 +861,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
// NOTE: In future generations the damage is capped to the remaining HP of the
|
||||
// Substitute, here we deliberately use the uncapped damage when tracking lastDamage etc.
|
||||
// Also, multi-hit moves must always deal the same damage as the first hit for any subsequent hits
|
||||
let uncappedDamage = move.hit > 1 ? source.lastDamage : this.getDamage(source, target, move);
|
||||
let uncappedDamage = move.hit > 1 ? source.lastDamage : this.actions.getDamage(source, target, move);
|
||||
if (!uncappedDamage) return null;
|
||||
uncappedDamage = this.runEvent('SubDamage', target, source, move, uncappedDamage);
|
||||
if (!uncappedDamage) return uncappedDamage;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -197,7 +197,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
return;
|
||||
}
|
||||
if (move.volatileStatus && target === source) return;
|
||||
let damage = this.getDamage(source, target, move);
|
||||
let damage = this.actions.getDamage(source, target, move);
|
||||
if (!damage) return null;
|
||||
damage = this.runEvent('SubDamage', target, source, move, damage);
|
||||
if (!damage) return damage;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -143,7 +143,7 @@ export const Conditions: {[k: string]: ModdedConditionData} = {
|
|||
flags: {},
|
||||
selfdestruct: move.selfdestruct,
|
||||
} as unknown as ActiveMove;
|
||||
const damage = this.getDamage(pokemon, pokemon, move);
|
||||
const damage = this.actions.getDamage(pokemon, pokemon, move);
|
||||
if (typeof damage !== 'number') throw new Error("Confusion damage not dealt");
|
||||
this.directDamage(damage);
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
effectType: 'Move',
|
||||
type: 'Normal',
|
||||
} as unknown as ActiveMove;
|
||||
this.tryMoveHit(target, pokemon, moveData);
|
||||
this.actions.tryMoveHit(target, pokemon, moveData);
|
||||
return false;
|
||||
}
|
||||
this.add('-activate', pokemon, 'move: Bide');
|
||||
|
|
@ -319,7 +319,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
inherit: true,
|
||||
onMoveFail(target, source, move) {
|
||||
if (target.runImmunity('Fighting')) {
|
||||
const damage = this.getDamage(source, target, move, true);
|
||||
const damage = this.actions.getDamage(source, target, move, true);
|
||||
if (typeof damage !== 'number') throw new Error("Couldn't get High Jump Kick recoil");
|
||||
this.damage(this.clampIntRange(damage / 8, 1), source, source, move);
|
||||
}
|
||||
|
|
@ -329,7 +329,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
inherit: true,
|
||||
onMoveFail(target, source, move) {
|
||||
if (target.runImmunity('Fighting')) {
|
||||
const damage = this.getDamage(source, target, move, true);
|
||||
const damage = this.actions.getDamage(source, target, move, true);
|
||||
if (typeof damage !== 'number') throw new Error("Couldn't get Jump Kick recoil");
|
||||
this.damage(this.clampIntRange(damage / 8, 1), source, source, move);
|
||||
}
|
||||
|
|
@ -455,7 +455,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
if (noMirror.includes(lastMove) || pokemon.moves.includes(lastMove)) {
|
||||
return false;
|
||||
}
|
||||
this.useMove(lastMove, pokemon);
|
||||
this.actions.useMove(lastMove, pokemon);
|
||||
},
|
||||
noSketch: true,
|
||||
},
|
||||
|
|
@ -648,7 +648,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
let randomMove = '';
|
||||
if (moves.length) randomMove = this.sample(moves);
|
||||
if (!randomMove) return false;
|
||||
this.useMove(randomMove, pokemon);
|
||||
this.actions.useMove(randomMove, pokemon);
|
||||
},
|
||||
noSketch: true,
|
||||
},
|
||||
|
|
@ -726,7 +726,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
}
|
||||
return;
|
||||
}
|
||||
let damage = this.getDamage(source, target, move);
|
||||
let damage = this.actions.getDamage(source, target, move);
|
||||
if (!damage) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -55,8 +55,8 @@ export const Abilities: {[k: string]: ModdedAbilityData} = {
|
|||
inherit: true,
|
||||
onStart(pokemon) {
|
||||
let activated = false;
|
||||
for (const target of pokemon.side.foe.active) {
|
||||
if (target && this.isAdjacent(target, pokemon) && !target.volatiles['substitute']) {
|
||||
for (const target of pokemon.adjacentFoes()) {
|
||||
if (!target.volatiles['substitute']) {
|
||||
activated = true;
|
||||
break;
|
||||
}
|
||||
|
|
@ -68,9 +68,7 @@ export const Abilities: {[k: string]: ModdedAbilityData} = {
|
|||
}
|
||||
this.add('-ability', pokemon, 'Intimidate', 'boost');
|
||||
|
||||
for (const target of pokemon.side.foe.active) {
|
||||
if (!target || !this.isAdjacent(target, pokemon)) continue;
|
||||
|
||||
for (const target of pokemon.adjacentFoes()) {
|
||||
if (target.volatiles['substitute']) {
|
||||
this.add('-immune', target);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
effectType: 'Move',
|
||||
type: 'Normal',
|
||||
} as unknown as ActiveMove;
|
||||
this.tryMoveHit(target, pokemon, moveData);
|
||||
this.actions.tryMoveHit(target, pokemon, moveData);
|
||||
return false;
|
||||
}
|
||||
this.add('-activate', pokemon, 'move: Bide');
|
||||
|
|
@ -224,7 +224,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
willCrit: false,
|
||||
type: '???',
|
||||
} as unknown as ActiveMove;
|
||||
const damage = this.getDamage(source, target, moveData, true);
|
||||
const damage = this.actions.getDamage(source, target, moveData, true);
|
||||
Object.assign(target.side.slotConditions[target.position]['futuremove'], {
|
||||
duration: 3,
|
||||
move: 'doomdesire',
|
||||
|
|
@ -353,7 +353,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
basePower: 85,
|
||||
onMoveFail(target, source, move) {
|
||||
if (target.runImmunity('Fighting')) {
|
||||
const damage = this.getDamage(source, target, move, true);
|
||||
const damage = this.actions.getDamage(source, target, move, true);
|
||||
if (typeof damage !== 'number') throw new Error("HJK recoil failed");
|
||||
this.damage(this.clampIntRange(damage / 2, 1, Math.floor(target.maxhp / 2)), source, source, move);
|
||||
}
|
||||
|
|
@ -368,7 +368,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
basePower: 70,
|
||||
onMoveFail(target, source, move) {
|
||||
if (target.runImmunity('Fighting')) {
|
||||
const damage = this.getDamage(source, target, move, true);
|
||||
const damage = this.actions.getDamage(source, target, move, true);
|
||||
if (typeof damage !== 'number') throw new Error("Jump Kick didn't recoil");
|
||||
this.damage(this.clampIntRange(damage / 2, 1, Math.floor(target.maxhp / 2)), source, source, move);
|
||||
}
|
||||
|
|
@ -433,7 +433,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
if (noMirror.includes(lastAttackedBy.move) || !lastAttackedBy.source.hasMove(lastAttackedBy.move)) {
|
||||
return false;
|
||||
}
|
||||
this.useMove(lastAttackedBy.move, pokemon);
|
||||
this.actions.useMove(lastAttackedBy.move, pokemon);
|
||||
},
|
||||
target: "self",
|
||||
},
|
||||
|
|
@ -441,7 +441,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
inherit: true,
|
||||
accuracy: 95,
|
||||
onHit(target) {
|
||||
this.useMove('swift', target);
|
||||
this.actions.useMove('swift', target);
|
||||
},
|
||||
},
|
||||
needlearm: {
|
||||
|
|
@ -513,7 +513,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
this.add('cant', pokemon, 'nopp', randomMove.move);
|
||||
return;
|
||||
}
|
||||
this.useMove(randomMove.move, pokemon);
|
||||
this.actions.useMove(randomMove.move, pokemon);
|
||||
},
|
||||
},
|
||||
spiderweb: {
|
||||
|
|
|
|||
|
|
@ -13,449 +13,452 @@ export const Scripts: ModdedBattleScriptsData = {
|
|||
}
|
||||
}
|
||||
},
|
||||
modifyDamage(baseDamage, pokemon, target, move, suppressMessages = false) {
|
||||
// RSE divides modifiers into several mathematically important stages
|
||||
// The modifiers run earlier than other generations are called with ModifyDamagePhase1 and ModifyDamagePhase2
|
||||
actions: {
|
||||
inherit: true,
|
||||
modifyDamage(baseDamage, pokemon, target, move, suppressMessages = false) {
|
||||
// RSE divides modifiers into several mathematically important stages
|
||||
// The modifiers run earlier than other generations are called with ModifyDamagePhase1 and ModifyDamagePhase2
|
||||
|
||||
if (!move.type) move.type = '???';
|
||||
const type = move.type;
|
||||
if (!move.type) move.type = '???';
|
||||
const type = move.type;
|
||||
|
||||
// Burn
|
||||
if (pokemon.status === 'brn' && baseDamage && move.category === 'Physical' && !pokemon.hasAbility('guts')) {
|
||||
baseDamage = this.modify(baseDamage, 0.5);
|
||||
}
|
||||
|
||||
// Other modifiers (Reflect/Light Screen/etc)
|
||||
baseDamage = this.runEvent('ModifyDamagePhase1', pokemon, target, move, baseDamage);
|
||||
|
||||
// Double battle multi-hit
|
||||
// In Generation 3, the spread move modifier is 0.5x instead of 0.75x. Moves that hit both foes
|
||||
// and the user's ally, like Earthquake and Explosion, don't get affected by spread modifiers
|
||||
if (move.spreadHit && move.target === 'allAdjacentFoes') {
|
||||
const spreadModifier = move.spreadModifier || 0.5;
|
||||
this.debug('Spread modifier: ' + spreadModifier);
|
||||
baseDamage = this.modify(baseDamage, spreadModifier);
|
||||
}
|
||||
|
||||
// Weather
|
||||
baseDamage = this.runEvent('WeatherModifyDamage', pokemon, target, move, baseDamage);
|
||||
|
||||
if (move.category === 'Physical' && !Math.floor(baseDamage)) {
|
||||
baseDamage = 1;
|
||||
}
|
||||
|
||||
baseDamage += 2;
|
||||
|
||||
const isCrit = target.getMoveHitData(move).crit;
|
||||
if (isCrit) {
|
||||
baseDamage = this.modify(baseDamage, move.critModifier || 2);
|
||||
}
|
||||
|
||||
// Mod 2 (Damage is floored after all multipliers are in)
|
||||
baseDamage = Math.floor(this.runEvent('ModifyDamagePhase2', pokemon, target, move, baseDamage));
|
||||
|
||||
// this is not a modifier
|
||||
baseDamage = this.randomizer(baseDamage);
|
||||
|
||||
// STAB
|
||||
if (move.forceSTAB || type !== '???' && pokemon.hasType(type)) {
|
||||
// The "???" type never gets STAB
|
||||
// Not even if you Roost in Gen 4 and somehow manage to use
|
||||
// Struggle in the same turn.
|
||||
// (On second thought, it might be easier to get a MissingNo.)
|
||||
baseDamage = this.modify(baseDamage, move.stab || 1.5);
|
||||
}
|
||||
// types
|
||||
let typeMod = target.runEffectiveness(move);
|
||||
typeMod = this.clampIntRange(typeMod, -6, 6);
|
||||
target.getMoveHitData(move).typeMod = typeMod;
|
||||
if (typeMod > 0) {
|
||||
if (!suppressMessages) this.add('-supereffective', target);
|
||||
|
||||
for (let i = 0; i < typeMod; i++) {
|
||||
baseDamage *= 2;
|
||||
// Burn
|
||||
if (pokemon.status === 'brn' && baseDamage && move.category === 'Physical' && !pokemon.hasAbility('guts')) {
|
||||
baseDamage = this.battle.modify(baseDamage, 0.5);
|
||||
}
|
||||
}
|
||||
if (typeMod < 0) {
|
||||
if (!suppressMessages) this.add('-resisted', target);
|
||||
|
||||
for (let i = 0; i > typeMod; i--) {
|
||||
baseDamage = Math.floor(baseDamage / 2);
|
||||
// Other modifiers (Reflect/Light Screen/etc)
|
||||
baseDamage = this.battle.runEvent('ModifyDamagePhase1', pokemon, target, move, baseDamage);
|
||||
|
||||
// Double battle multi-hit
|
||||
// In Generation 3, the spread move modifier is 0.5x instead of 0.75x. Moves that hit both foes
|
||||
// and the user's ally, like Earthquake and Explosion, don't get affected by spread modifiers
|
||||
if (move.spreadHit && move.target === 'allAdjacentFoes') {
|
||||
const spreadModifier = move.spreadModifier || 0.5;
|
||||
this.battle.debug('Spread modifier: ' + spreadModifier);
|
||||
baseDamage = this.battle.modify(baseDamage, spreadModifier);
|
||||
}
|
||||
}
|
||||
|
||||
if (isCrit && !suppressMessages) this.add('-crit', target);
|
||||
// Weather
|
||||
baseDamage = this.battle.runEvent('WeatherModifyDamage', pokemon, target, move, baseDamage);
|
||||
|
||||
// Final modifier.
|
||||
baseDamage = this.runEvent('ModifyDamage', pokemon, target, move, baseDamage);
|
||||
if (move.category === 'Physical' && !Math.floor(baseDamage)) {
|
||||
baseDamage = 1;
|
||||
}
|
||||
|
||||
if (!Math.floor(baseDamage)) {
|
||||
return 1;
|
||||
}
|
||||
baseDamage += 2;
|
||||
|
||||
return Math.floor(baseDamage);
|
||||
},
|
||||
useMoveInner(moveOrMoveName, pokemon, target, sourceEffect, zMove) {
|
||||
if (!sourceEffect && this.effect.id) sourceEffect = this.effect;
|
||||
if (sourceEffect && sourceEffect.id === 'instruct') sourceEffect = null;
|
||||
const isCrit = target.getMoveHitData(move).crit;
|
||||
if (isCrit) {
|
||||
baseDamage = this.battle.modify(baseDamage, move.critModifier || 2);
|
||||
}
|
||||
|
||||
let move = this.dex.getActiveMove(moveOrMoveName);
|
||||
pokemon.lastMoveUsed = move;
|
||||
// Mod 2 (Damage is floored after all multipliers are in)
|
||||
baseDamage = Math.floor(this.battle.runEvent('ModifyDamagePhase2', pokemon, target, move, baseDamage));
|
||||
|
||||
if (this.activeMove) {
|
||||
move.priority = this.activeMove.priority;
|
||||
}
|
||||
const baseTarget = move.target;
|
||||
if (target === undefined) target = this.getRandomTarget(pokemon, move);
|
||||
if (move.target === 'self' || move.target === 'allies') {
|
||||
target = pokemon;
|
||||
}
|
||||
if (sourceEffect) {
|
||||
move.sourceEffect = sourceEffect.id;
|
||||
move.ignoreAbility = false;
|
||||
}
|
||||
let moveResult = false;
|
||||
// this is not a modifier
|
||||
baseDamage = this.battle.randomizer(baseDamage);
|
||||
|
||||
this.setActiveMove(move, pokemon, target);
|
||||
// STAB
|
||||
if (move.forceSTAB || type !== '???' && pokemon.hasType(type)) {
|
||||
// The "???" type never gets STAB
|
||||
// Not even if you Roost in Gen 4 and somehow manage to use
|
||||
// Struggle in the same turn.
|
||||
// (On second thought, it might be easier to get a MissingNo.)
|
||||
baseDamage = this.battle.modify(baseDamage, move.stab || 1.5);
|
||||
}
|
||||
// types
|
||||
let typeMod = target.runEffectiveness(move);
|
||||
typeMod = this.battle.clampIntRange(typeMod, -6, 6);
|
||||
target.getMoveHitData(move).typeMod = typeMod;
|
||||
if (typeMod > 0) {
|
||||
if (!suppressMessages) this.battle.add('-supereffective', target);
|
||||
|
||||
this.singleEvent('ModifyMove', move, null, pokemon, target, move, move);
|
||||
if (baseTarget !== move.target) {
|
||||
// Target changed in ModifyMove, so we must adjust it here
|
||||
// Adjust before the next event so the correct target is passed to the
|
||||
// event
|
||||
target = this.getRandomTarget(pokemon, move);
|
||||
}
|
||||
move = this.runEvent('ModifyMove', pokemon, target, move, move);
|
||||
if (baseTarget !== move.target) {
|
||||
// Adjust again
|
||||
target = this.getRandomTarget(pokemon, move);
|
||||
}
|
||||
if (!move || pokemon.fainted) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let attrs = '';
|
||||
|
||||
let movename = move.name;
|
||||
if (move.id === 'hiddenpower') movename = 'Hidden Power';
|
||||
if (sourceEffect) attrs += `|[from]${this.dex.getEffect(sourceEffect)}`;
|
||||
this.addMove('move', pokemon, movename, target + attrs);
|
||||
|
||||
if (!target) {
|
||||
this.attrLastMove('[notarget]');
|
||||
this.add('-notarget', pokemon);
|
||||
return false;
|
||||
}
|
||||
|
||||
const {targets, pressureTargets} = pokemon.getMoveTargets(move, target);
|
||||
|
||||
if (!sourceEffect || sourceEffect.id === 'pursuit') {
|
||||
let extraPP = 0;
|
||||
for (const source of pressureTargets) {
|
||||
const ppDrop = this.runEvent('DeductPP', source, pokemon, move);
|
||||
if (ppDrop !== true) {
|
||||
extraPP += ppDrop || 0;
|
||||
for (let i = 0; i < typeMod; i++) {
|
||||
baseDamage *= 2;
|
||||
}
|
||||
}
|
||||
if (extraPP > 0) {
|
||||
pokemon.deductPP(move, extraPP);
|
||||
if (typeMod < 0) {
|
||||
if (!suppressMessages) this.battle.add('-resisted', target);
|
||||
|
||||
for (let i = 0; i > typeMod; i--) {
|
||||
baseDamage = Math.floor(baseDamage / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.singleEvent('TryMove', move, null, pokemon, target, move) ||
|
||||
!this.runEvent('TryMove', pokemon, target, move)) {
|
||||
move.mindBlownRecoil = false;
|
||||
return false;
|
||||
}
|
||||
if (isCrit && !suppressMessages) this.battle.add('-crit', target);
|
||||
|
||||
this.singleEvent('UseMoveMessage', move, null, pokemon, target, move);
|
||||
// Final modifier.
|
||||
baseDamage = this.battle.runEvent('ModifyDamage', pokemon, target, move, baseDamage);
|
||||
|
||||
if (move.ignoreImmunity === undefined) {
|
||||
move.ignoreImmunity = (move.category === 'Status');
|
||||
}
|
||||
if (!Math.floor(baseDamage)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (move.selfdestruct === 'always') {
|
||||
this.faint(pokemon, pokemon, move);
|
||||
}
|
||||
return Math.floor(baseDamage);
|
||||
},
|
||||
useMoveInner(moveOrMoveName, pokemon, target, sourceEffect, zMove) {
|
||||
if (!sourceEffect && this.battle.effect.id) sourceEffect = this.battle.effect;
|
||||
if (sourceEffect && sourceEffect.id === 'instruct') sourceEffect = null;
|
||||
|
||||
let damage: number | false | undefined | '' = false;
|
||||
if (move.target === 'all' || move.target === 'foeSide' || move.target === 'allySide' || move.target === 'allyTeam') {
|
||||
damage = this.tryMoveHit(target, pokemon, move);
|
||||
if (damage === this.NOT_FAIL) pokemon.moveThisTurnResult = null;
|
||||
if (damage || damage === 0 || damage === undefined) moveResult = true;
|
||||
} else if (move.target === 'allAdjacent' || move.target === 'allAdjacentFoes') {
|
||||
if (!targets.length) {
|
||||
this.attrLastMove('[notarget]');
|
||||
this.add('-notarget', pokemon);
|
||||
let move = this.dex.getActiveMove(moveOrMoveName);
|
||||
pokemon.lastMoveUsed = move;
|
||||
|
||||
if (this.battle.activeMove) {
|
||||
move.priority = this.battle.activeMove.priority;
|
||||
}
|
||||
const baseTarget = move.target;
|
||||
if (target === undefined) target = this.battle.getRandomTarget(pokemon, move);
|
||||
if (move.target === 'self' || move.target === 'allies') {
|
||||
target = pokemon;
|
||||
}
|
||||
if (sourceEffect) {
|
||||
move.sourceEffect = sourceEffect.id;
|
||||
move.ignoreAbility = false;
|
||||
}
|
||||
let moveResult = false;
|
||||
|
||||
this.battle.setActiveMove(move, pokemon, target);
|
||||
|
||||
this.battle.singleEvent('ModifyMove', move, null, pokemon, target, move, move);
|
||||
if (baseTarget !== move.target) {
|
||||
// Target changed in ModifyMove, so we must adjust it here
|
||||
// Adjust before the next event so the correct target is passed to the
|
||||
// event
|
||||
target = this.battle.getRandomTarget(pokemon, move);
|
||||
}
|
||||
move = this.battle.runEvent('ModifyMove', pokemon, target, move, move);
|
||||
if (baseTarget !== move.target) {
|
||||
// Adjust again
|
||||
target = this.battle.getRandomTarget(pokemon, move);
|
||||
}
|
||||
if (!move || pokemon.fainted) {
|
||||
return false;
|
||||
}
|
||||
if (targets.length > 1) move.spreadHit = true;
|
||||
const hitSlots = [];
|
||||
for (const source of targets) {
|
||||
const hitResult = this.tryMoveHit(source, pokemon, move);
|
||||
if (hitResult || hitResult === 0 || hitResult === undefined) {
|
||||
moveResult = true;
|
||||
hitSlots.push(source.getSlot());
|
||||
}
|
||||
if (damage) {
|
||||
damage += hitResult || 0;
|
||||
} else {
|
||||
if (damage !== false || hitResult !== this.NOT_FAIL) damage = hitResult;
|
||||
}
|
||||
if (damage === this.NOT_FAIL) pokemon.moveThisTurnResult = null;
|
||||
}
|
||||
if (move.spreadHit) this.attrLastMove('[spread] ' + hitSlots.join(','));
|
||||
} else {
|
||||
target = targets[0];
|
||||
let lacksTarget = !target || target.fainted;
|
||||
if (!lacksTarget) {
|
||||
if (['adjacentFoe', 'adjacentAlly', 'normal', 'randomNormal'].includes(move.target)) {
|
||||
lacksTarget = !this.isAdjacent(target, pokemon);
|
||||
}
|
||||
}
|
||||
if (lacksTarget && !move.isFutureMove) {
|
||||
this.attrLastMove('[notarget]');
|
||||
this.add('-notarget', pokemon);
|
||||
|
||||
let attrs = '';
|
||||
|
||||
let movename = move.name;
|
||||
if (move.id === 'hiddenpower') movename = 'Hidden Power';
|
||||
if (sourceEffect) attrs += `|[from]${this.dex.getEffect(sourceEffect)}`;
|
||||
this.battle.addMove('move', pokemon, movename, target + attrs);
|
||||
|
||||
if (!target) {
|
||||
this.battle.attrLastMove('[notarget]');
|
||||
this.battle.add('-notarget', pokemon);
|
||||
return false;
|
||||
}
|
||||
damage = this.tryMoveHit(target, pokemon, move);
|
||||
if (damage === this.NOT_FAIL) pokemon.moveThisTurnResult = null;
|
||||
if (damage || damage === 0 || damage === undefined) moveResult = true;
|
||||
}
|
||||
if (move.selfBoost && moveResult) this.moveHit(pokemon, pokemon, move, move.selfBoost, false, true);
|
||||
if (!pokemon.hp) {
|
||||
this.faint(pokemon, pokemon, move);
|
||||
}
|
||||
|
||||
if (!moveResult) {
|
||||
this.singleEvent('MoveFail', move, null, target, pokemon, move);
|
||||
return false;
|
||||
}
|
||||
const {targets, pressureTargets} = pokemon.getMoveTargets(move, target);
|
||||
|
||||
if (!move.negateSecondary && !(move.hasSheerForce && pokemon.hasAbility('sheerforce'))) {
|
||||
this.singleEvent('AfterMoveSecondarySelf', move, null, pokemon, target, move);
|
||||
this.runEvent('AfterMoveSecondarySelf', pokemon, target, move);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
tryMoveHit(target, pokemon, move) {
|
||||
this.setActiveMove(move, pokemon, target);
|
||||
let naturalImmunity = false;
|
||||
let accPass = true;
|
||||
|
||||
let hitResult = this.singleEvent('PrepareHit', move, {}, target, pokemon, move) &&
|
||||
this.runEvent('PrepareHit', pokemon, target, move);
|
||||
if (!hitResult) {
|
||||
if (hitResult === false) {
|
||||
this.add('-fail', pokemon);
|
||||
this.attrLastMove('[still]');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.singleEvent('Try', move, null, pokemon, target, move)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (move.target === 'all' || move.target === 'foeSide' || move.target === 'allySide' || move.target === 'allyTeam') {
|
||||
if (move.target === 'all') {
|
||||
hitResult = this.runEvent('TryHitField', target, pokemon, move);
|
||||
} else {
|
||||
hitResult = this.runEvent('TryHitSide', target, pokemon, move);
|
||||
}
|
||||
if (!hitResult) {
|
||||
if (hitResult === false) {
|
||||
this.add('-fail', pokemon);
|
||||
this.attrLastMove('[still]');
|
||||
if (!sourceEffect || sourceEffect.id === 'pursuit') {
|
||||
let extraPP = 0;
|
||||
for (const source of pressureTargets) {
|
||||
const ppDrop = this.battle.runEvent('DeductPP', source, pokemon, move);
|
||||
if (ppDrop !== true) {
|
||||
extraPP += ppDrop || 0;
|
||||
}
|
||||
}
|
||||
if (extraPP > 0) {
|
||||
pokemon.deductPP(move, extraPP);
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.battle.singleEvent('TryMove', move, null, pokemon, target, move) ||
|
||||
!this.battle.runEvent('TryMove', pokemon, target, move)) {
|
||||
move.mindBlownRecoil = false;
|
||||
return false;
|
||||
}
|
||||
return this.moveHit(target, pokemon, move);
|
||||
}
|
||||
|
||||
hitResult = this.runEvent('Invulnerability', target, pokemon, move);
|
||||
if (hitResult === false) {
|
||||
if (!move.spreadHit) this.attrLastMove('[miss]');
|
||||
this.add('-miss', pokemon, target);
|
||||
return false;
|
||||
}
|
||||
this.battle.singleEvent('UseMoveMessage', move, null, pokemon, target, move);
|
||||
|
||||
if (move.ignoreImmunity === undefined) {
|
||||
move.ignoreImmunity = (move.category === 'Status');
|
||||
}
|
||||
|
||||
if (
|
||||
(!move.ignoreImmunity || (move.ignoreImmunity !== true && !move.ignoreImmunity[move.type])) &&
|
||||
!target.runImmunity(move.type)
|
||||
) {
|
||||
naturalImmunity = true;
|
||||
} else {
|
||||
hitResult = this.singleEvent('TryImmunity', move, {}, target, pokemon, move);
|
||||
if (hitResult === false) {
|
||||
naturalImmunity = true;
|
||||
if (move.ignoreImmunity === undefined) {
|
||||
move.ignoreImmunity = (move.category === 'Status');
|
||||
}
|
||||
}
|
||||
|
||||
const boostTable = [1, 4 / 3, 5 / 3, 2, 7 / 3, 8 / 3, 3];
|
||||
if (move.selfdestruct === 'always') {
|
||||
this.battle.faint(pokemon, pokemon, move);
|
||||
}
|
||||
|
||||
// calculate true accuracy
|
||||
let accuracy = move.accuracy;
|
||||
let boosts: SparseBoostsTable = {};
|
||||
let boost: number;
|
||||
if (accuracy !== true) {
|
||||
if (!move.ignoreAccuracy) {
|
||||
boosts = this.runEvent('ModifyBoost', pokemon, null, null, {...pokemon.boosts});
|
||||
boost = this.clampIntRange(boosts['accuracy'], -6, 6);
|
||||
if (boost > 0) {
|
||||
accuracy *= boostTable[boost];
|
||||
} else {
|
||||
accuracy /= boostTable[-boost];
|
||||
}
|
||||
}
|
||||
if (!move.ignoreEvasion) {
|
||||
boosts = this.runEvent('ModifyBoost', target, null, null, {...target.boosts});
|
||||
boost = this.clampIntRange(boosts['evasion'], -6, 6);
|
||||
if (boost > 0) {
|
||||
accuracy /= boostTable[boost];
|
||||
} else if (boost < 0) {
|
||||
accuracy *= boostTable[-boost];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (move.ohko) { // bypasses accuracy modifiers
|
||||
if (!target.isSemiInvulnerable()) {
|
||||
accuracy = 30;
|
||||
if (pokemon.level >= target.level && (move.ohko === true || !target.hasType(move.ohko))) {
|
||||
accuracy += (pokemon.level - target.level);
|
||||
} else {
|
||||
this.add('-immune', target, '[ohko]');
|
||||
let damage: number | false | undefined | '' = false;
|
||||
if (move.target === 'all' || move.target === 'foeSide' || move.target === 'allySide' || move.target === 'allyTeam') {
|
||||
damage = this.tryMoveHit(target, pokemon, move);
|
||||
if (damage === this.battle.NOT_FAIL) pokemon.moveThisTurnResult = null;
|
||||
if (damage || damage === 0 || damage === undefined) moveResult = true;
|
||||
} else if (move.target === 'allAdjacent' || move.target === 'allAdjacentFoes') {
|
||||
if (!targets.length) {
|
||||
this.battle.attrLastMove('[notarget]');
|
||||
this.battle.add('-notarget', pokemon);
|
||||
return false;
|
||||
}
|
||||
if (targets.length > 1) move.spreadHit = true;
|
||||
const hitSlots = [];
|
||||
for (const source of targets) {
|
||||
const hitResult = this.tryMoveHit(source, pokemon, move);
|
||||
if (hitResult || hitResult === 0 || hitResult === undefined) {
|
||||
moveResult = true;
|
||||
hitSlots.push(source.getSlot());
|
||||
}
|
||||
if (damage) {
|
||||
damage += hitResult || 0;
|
||||
} else {
|
||||
if (damage !== false || hitResult !== this.battle.NOT_FAIL) damage = hitResult;
|
||||
}
|
||||
if (damage === this.battle.NOT_FAIL) pokemon.moveThisTurnResult = null;
|
||||
}
|
||||
if (move.spreadHit) this.battle.attrLastMove('[spread] ' + hitSlots.join(','));
|
||||
} else {
|
||||
target = targets[0];
|
||||
let lacksTarget = !target || target.fainted;
|
||||
if (!lacksTarget) {
|
||||
if (['adjacentFoe', 'adjacentAlly', 'normal', 'randomNormal'].includes(move.target)) {
|
||||
lacksTarget = !target.isAdjacent(pokemon);
|
||||
}
|
||||
}
|
||||
if (lacksTarget && !move.isFutureMove) {
|
||||
this.battle.attrLastMove('[notarget]');
|
||||
this.battle.add('-notarget', pokemon);
|
||||
return false;
|
||||
}
|
||||
damage = this.tryMoveHit(target, pokemon, move);
|
||||
if (damage === this.battle.NOT_FAIL) pokemon.moveThisTurnResult = null;
|
||||
if (damage || damage === 0 || damage === undefined) moveResult = true;
|
||||
}
|
||||
if (move.selfBoost && moveResult) this.moveHit(pokemon, pokemon, move, move.selfBoost, false, true);
|
||||
if (!pokemon.hp) {
|
||||
this.battle.faint(pokemon, pokemon, move);
|
||||
}
|
||||
} else {
|
||||
accuracy = this.runEvent('ModifyAccuracy', target, pokemon, move, accuracy);
|
||||
}
|
||||
if (move.alwaysHit) {
|
||||
accuracy = true; // bypasses ohko accuracy modifiers
|
||||
} else {
|
||||
accuracy = this.runEvent('Accuracy', target, pokemon, move, accuracy);
|
||||
}
|
||||
if (accuracy !== true && !this.randomChance(accuracy, 100)) {
|
||||
accPass = false;
|
||||
}
|
||||
|
||||
if (accPass) {
|
||||
hitResult = this.runEvent('TryHit', target, pokemon, move);
|
||||
if (!moveResult) {
|
||||
this.battle.singleEvent('MoveFail', move, null, target, pokemon, move);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!move.negateSecondary && !(move.hasSheerForce && pokemon.hasAbility('sheerforce'))) {
|
||||
this.battle.singleEvent('AfterMoveSecondarySelf', move, null, pokemon, target, move);
|
||||
this.battle.runEvent('AfterMoveSecondarySelf', pokemon, target, move);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
tryMoveHit(target, pokemon, move) {
|
||||
this.battle.setActiveMove(move, pokemon, target);
|
||||
let naturalImmunity = false;
|
||||
let accPass = true;
|
||||
|
||||
let hitResult = this.battle.singleEvent('PrepareHit', move, {}, target, pokemon, move) &&
|
||||
this.battle.runEvent('PrepareHit', pokemon, target, move);
|
||||
if (!hitResult) {
|
||||
if (hitResult === false) {
|
||||
this.add('-fail', pokemon);
|
||||
this.attrLastMove('[still]');
|
||||
this.battle.add('-fail', pokemon);
|
||||
this.battle.attrLastMove('[still]');
|
||||
}
|
||||
return false;
|
||||
} else if (naturalImmunity) {
|
||||
this.add('-immune', target);
|
||||
}
|
||||
|
||||
if (!this.battle.singleEvent('Try', move, null, pokemon, target, move)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (naturalImmunity) {
|
||||
this.add('-immune', target);
|
||||
} else {
|
||||
if (!move.spreadHit) this.attrLastMove('[miss]');
|
||||
this.add('-miss', pokemon, target);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
move.totalDamage = 0;
|
||||
let damage: number | undefined | false = 0;
|
||||
pokemon.lastDamage = 0;
|
||||
if (move.multihit) {
|
||||
let hits = move.multihit;
|
||||
if (Array.isArray(hits)) {
|
||||
// yes, it's hardcoded... meh
|
||||
if (hits[0] === 2 && hits[1] === 5) {
|
||||
hits = this.sample([2, 2, 2, 3, 3, 3, 4, 5]);
|
||||
if (move.target === 'all' || move.target === 'foeSide' || move.target === 'allySide' || move.target === 'allyTeam') {
|
||||
if (move.target === 'all') {
|
||||
hitResult = this.battle.runEvent('TryHitField', target, pokemon, move);
|
||||
} else {
|
||||
hits = this.random(hits[0], hits[1] + 1);
|
||||
hitResult = this.battle.runEvent('TryHitSide', target, pokemon, move);
|
||||
}
|
||||
if (!hitResult) {
|
||||
if (hitResult === false) {
|
||||
this.battle.add('-fail', pokemon);
|
||||
this.battle.attrLastMove('[still]');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return this.moveHit(target, pokemon, move);
|
||||
}
|
||||
|
||||
hitResult = this.battle.runEvent('Invulnerability', target, pokemon, move);
|
||||
if (hitResult === false) {
|
||||
if (!move.spreadHit) this.battle.attrLastMove('[miss]');
|
||||
this.battle.add('-miss', pokemon, target);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (move.ignoreImmunity === undefined) {
|
||||
move.ignoreImmunity = (move.category === 'Status');
|
||||
}
|
||||
|
||||
if (
|
||||
(!move.ignoreImmunity || (move.ignoreImmunity !== true && !move.ignoreImmunity[move.type])) &&
|
||||
!target.runImmunity(move.type)
|
||||
) {
|
||||
naturalImmunity = true;
|
||||
} else {
|
||||
hitResult = this.battle.singleEvent('TryImmunity', move, {}, target, pokemon, move);
|
||||
if (hitResult === false) {
|
||||
naturalImmunity = true;
|
||||
}
|
||||
}
|
||||
hits = Math.floor(hits);
|
||||
let nullDamage = true;
|
||||
let moveDamage: number | undefined | false;
|
||||
// There is no need to recursively check the ´sleepUsable´ flag as Sleep Talk can only be used while asleep.
|
||||
const isSleepUsable = move.sleepUsable || this.dex.getMove(move.sourceEffect).sleepUsable;
|
||||
let i: number;
|
||||
for (i = 0; i < hits && target.hp && pokemon.hp; i++) {
|
||||
if (pokemon.status === 'slp' && !isSleepUsable) break;
|
||||
move.hit = i + 1;
|
||||
|
||||
if (move.multiaccuracy && i > 0) {
|
||||
accuracy = move.accuracy;
|
||||
if (accuracy !== true) {
|
||||
if (!move.ignoreAccuracy) {
|
||||
boosts = this.runEvent('ModifyBoost', pokemon, null, null, {...pokemon.boosts});
|
||||
boost = this.clampIntRange(boosts['accuracy'], -6, 6);
|
||||
if (boost > 0) {
|
||||
accuracy *= boostTable[boost];
|
||||
} else {
|
||||
accuracy /= boostTable[-boost];
|
||||
}
|
||||
}
|
||||
if (!move.ignoreEvasion) {
|
||||
boosts = this.runEvent('ModifyBoost', target, null, null, {...target.boosts});
|
||||
boost = this.clampIntRange(boosts['evasion'], -6, 6);
|
||||
if (boost > 0) {
|
||||
accuracy /= boostTable[boost];
|
||||
} else if (boost < 0) {
|
||||
accuracy *= boostTable[-boost];
|
||||
}
|
||||
}
|
||||
}
|
||||
accuracy = this.runEvent('ModifyAccuracy', target, pokemon, move, accuracy);
|
||||
if (!move.alwaysHit) {
|
||||
accuracy = this.runEvent('Accuracy', target, pokemon, move, accuracy);
|
||||
if (accuracy !== true && !this.randomChance(accuracy, 100)) break;
|
||||
const boostTable = [1, 4 / 3, 5 / 3, 2, 7 / 3, 8 / 3, 3];
|
||||
|
||||
// calculate true accuracy
|
||||
let accuracy = move.accuracy;
|
||||
let boosts: SparseBoostsTable = {};
|
||||
let boost: number;
|
||||
if (accuracy !== true) {
|
||||
if (!move.ignoreAccuracy) {
|
||||
boosts = this.battle.runEvent('ModifyBoost', pokemon, null, null, {...pokemon.boosts});
|
||||
boost = this.battle.clampIntRange(boosts['accuracy'], -6, 6);
|
||||
if (boost > 0) {
|
||||
accuracy *= boostTable[boost];
|
||||
} else {
|
||||
accuracy /= boostTable[-boost];
|
||||
}
|
||||
}
|
||||
if (!move.ignoreEvasion) {
|
||||
boosts = this.battle.runEvent('ModifyBoost', target, null, null, {...target.boosts});
|
||||
boost = this.battle.clampIntRange(boosts['evasion'], -6, 6);
|
||||
if (boost > 0) {
|
||||
accuracy /= boostTable[boost];
|
||||
} else if (boost < 0) {
|
||||
accuracy *= boostTable[-boost];
|
||||
}
|
||||
}
|
||||
|
||||
moveDamage = this.moveHit(target, pokemon, move);
|
||||
if (moveDamage === false) break;
|
||||
if (nullDamage && (moveDamage || moveDamage === 0 || moveDamage === undefined)) nullDamage = false;
|
||||
// Damage from each hit is individually counted for the
|
||||
// purposes of Counter, Metal Burst, and Mirror Coat.
|
||||
damage = (moveDamage || 0);
|
||||
move.totalDamage += damage;
|
||||
this.eachEvent('Update');
|
||||
}
|
||||
if (i === 0) return false;
|
||||
if (nullDamage) damage = false;
|
||||
this.add('-hitcount', target, i);
|
||||
} else {
|
||||
damage = this.moveHit(target, pokemon, move);
|
||||
move.totalDamage = damage;
|
||||
}
|
||||
if (move.ohko) { // bypasses accuracy modifiers
|
||||
if (!target.isSemiInvulnerable()) {
|
||||
accuracy = 30;
|
||||
if (pokemon.level >= target.level && (move.ohko === true || !target.hasType(move.ohko))) {
|
||||
accuracy += (pokemon.level - target.level);
|
||||
} else {
|
||||
this.battle.add('-immune', target, '[ohko]');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
accuracy = this.battle.runEvent('ModifyAccuracy', target, pokemon, move, accuracy);
|
||||
}
|
||||
if (move.alwaysHit) {
|
||||
accuracy = true; // bypasses ohko accuracy modifiers
|
||||
} else {
|
||||
accuracy = this.battle.runEvent('Accuracy', target, pokemon, move, accuracy);
|
||||
}
|
||||
if (accuracy !== true && !this.battle.randomChance(accuracy, 100)) {
|
||||
accPass = false;
|
||||
}
|
||||
|
||||
if (move.recoil && move.totalDamage) {
|
||||
this.damage(this.calcRecoilDamage(move.totalDamage, move), pokemon, target, 'recoil');
|
||||
}
|
||||
if (accPass) {
|
||||
hitResult = this.battle.runEvent('TryHit', target, pokemon, move);
|
||||
if (!hitResult) {
|
||||
if (hitResult === false) {
|
||||
this.battle.add('-fail', pokemon);
|
||||
this.battle.attrLastMove('[still]');
|
||||
}
|
||||
return false;
|
||||
} else if (naturalImmunity) {
|
||||
this.battle.add('-immune', target);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (naturalImmunity) {
|
||||
this.battle.add('-immune', target);
|
||||
} else {
|
||||
if (!move.spreadHit) this.battle.attrLastMove('[miss]');
|
||||
this.battle.add('-miss', pokemon, target);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (target && pokemon !== target) target.gotAttacked(move, damage, pokemon);
|
||||
move.totalDamage = 0;
|
||||
let damage: number | undefined | false = 0;
|
||||
pokemon.lastDamage = 0;
|
||||
if (move.multihit) {
|
||||
let hits = move.multihit;
|
||||
if (Array.isArray(hits)) {
|
||||
// yes, it's hardcoded... meh
|
||||
if (hits[0] === 2 && hits[1] === 5) {
|
||||
hits = this.battle.sample([2, 2, 2, 3, 3, 3, 4, 5]);
|
||||
} else {
|
||||
hits = this.battle.random(hits[0], hits[1] + 1);
|
||||
}
|
||||
}
|
||||
hits = Math.floor(hits);
|
||||
let nullDamage = true;
|
||||
let moveDamage: number | undefined | false;
|
||||
// There is no need to recursively check the ´sleepUsable´ flag as Sleep Talk can only be used while asleep.
|
||||
const isSleepUsable = move.sleepUsable || this.dex.getMove(move.sourceEffect).sleepUsable;
|
||||
let i: number;
|
||||
for (i = 0; i < hits && target.hp && pokemon.hp; i++) {
|
||||
if (pokemon.status === 'slp' && !isSleepUsable) break;
|
||||
move.hit = i + 1;
|
||||
|
||||
if (move.ohko && !target.hp) this.add('-ohko');
|
||||
if (move.multiaccuracy && i > 0) {
|
||||
accuracy = move.accuracy;
|
||||
if (accuracy !== true) {
|
||||
if (!move.ignoreAccuracy) {
|
||||
boosts = this.battle.runEvent('ModifyBoost', pokemon, null, null, {...pokemon.boosts});
|
||||
boost = this.battle.clampIntRange(boosts['accuracy'], -6, 6);
|
||||
if (boost > 0) {
|
||||
accuracy *= boostTable[boost];
|
||||
} else {
|
||||
accuracy /= boostTable[-boost];
|
||||
}
|
||||
}
|
||||
if (!move.ignoreEvasion) {
|
||||
boosts = this.battle.runEvent('ModifyBoost', target, null, null, {...target.boosts});
|
||||
boost = this.battle.clampIntRange(boosts['evasion'], -6, 6);
|
||||
if (boost > 0) {
|
||||
accuracy /= boostTable[boost];
|
||||
} else if (boost < 0) {
|
||||
accuracy *= boostTable[-boost];
|
||||
}
|
||||
}
|
||||
}
|
||||
accuracy = this.battle.runEvent('ModifyAccuracy', target, pokemon, move, accuracy);
|
||||
if (!move.alwaysHit) {
|
||||
accuracy = this.battle.runEvent('Accuracy', target, pokemon, move, accuracy);
|
||||
if (accuracy !== true && !this.battle.randomChance(accuracy, 100)) break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!damage && damage !== 0) return damage;
|
||||
moveDamage = this.moveHit(target, pokemon, move);
|
||||
if (moveDamage === false) break;
|
||||
if (nullDamage && (moveDamage || moveDamage === 0 || moveDamage === undefined)) nullDamage = false;
|
||||
// Damage from each hit is individually counted for the
|
||||
// purposes of Counter, Metal Burst, and Mirror Coat.
|
||||
damage = (moveDamage || 0);
|
||||
move.totalDamage += damage;
|
||||
this.battle.eachEvent('Update');
|
||||
}
|
||||
if (i === 0) return false;
|
||||
if (nullDamage) damage = false;
|
||||
this.battle.add('-hitcount', target, i);
|
||||
} else {
|
||||
damage = this.moveHit(target, pokemon, move);
|
||||
move.totalDamage = damage;
|
||||
}
|
||||
|
||||
this.eachEvent('Update');
|
||||
if (move.recoil && move.totalDamage) {
|
||||
this.battle.damage(this.calcRecoilDamage(move.totalDamage, move), pokemon, target, 'recoil');
|
||||
}
|
||||
|
||||
if (target && !move.negateSecondary) {
|
||||
this.singleEvent('AfterMoveSecondary', move, null, target, pokemon, move);
|
||||
this.runEvent('AfterMoveSecondary', target, pokemon, move);
|
||||
}
|
||||
if (target && pokemon !== target) target.gotAttacked(move, damage, pokemon);
|
||||
|
||||
return damage;
|
||||
},
|
||||
if (move.ohko && !target.hp) this.battle.add('-ohko');
|
||||
|
||||
calcRecoilDamage(damageDealt, move) {
|
||||
return this.clampIntRange(Math.floor(damageDealt * move.recoil![0] / move.recoil![1]), 1);
|
||||
if (!damage && damage !== 0) return damage;
|
||||
|
||||
this.battle.eachEvent('Update');
|
||||
|
||||
if (target && !move.negateSecondary) {
|
||||
this.battle.singleEvent('AfterMoveSecondary', move, null, target, pokemon, move);
|
||||
this.battle.runEvent('AfterMoveSecondary', target, pokemon, move);
|
||||
}
|
||||
|
||||
return damage;
|
||||
},
|
||||
|
||||
calcRecoilDamage(damageDealt, move) {
|
||||
return this.battle.clampIntRange(Math.floor(damageDealt * move.recoil![0] / move.recoil![1]), 1);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -172,15 +172,9 @@ export const Abilities: {[k: string]: ModdedAbilityData} = {
|
|||
intimidate: {
|
||||
inherit: true,
|
||||
onStart(pokemon) {
|
||||
let activated = false;
|
||||
for (const target of pokemon.side.foe.active) {
|
||||
if (target && this.isAdjacent(target, pokemon) &&
|
||||
!(target.volatiles['substitute'] ||
|
||||
target.volatiles['substitutebroken'] && target.volatiles['substitutebroken'].move === 'uturn')) {
|
||||
activated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
|
@ -188,12 +182,10 @@ export const Abilities: {[k: string]: ModdedAbilityData} = {
|
|||
}
|
||||
this.add('-ability', pokemon, 'Intimidate', 'boost');
|
||||
|
||||
for (const target of pokemon.side.foe.active) {
|
||||
if (!target || !this.isAdjacent(target, pokemon)) continue;
|
||||
|
||||
for (const target of pokemon.adjacentFoes()) {
|
||||
if (target.volatiles['substitute']) {
|
||||
this.add('-immune', target);
|
||||
} else if (target.volatiles['substitutebroken'] && target.volatiles['substitutebroken'].move === 'uturn') {
|
||||
} 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);
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
if (!randomMove) {
|
||||
return false;
|
||||
}
|
||||
this.useMove(randomMove, target);
|
||||
this.actions.useMove(randomMove, target);
|
||||
},
|
||||
},
|
||||
beatup: {
|
||||
|
|
@ -140,7 +140,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
effectType: 'Move',
|
||||
type: 'Normal',
|
||||
} as unknown as ActiveMove;
|
||||
this.tryMoveHit(target, pokemon, moveData);
|
||||
this.actions.tryMoveHit(target, pokemon, moveData);
|
||||
return false;
|
||||
}
|
||||
this.add('-activate', pokemon, 'move: Bide');
|
||||
|
|
@ -217,7 +217,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
if (!this.lastMove || noCopycat.includes(this.lastMove.id)) {
|
||||
return false;
|
||||
}
|
||||
this.useMove(this.lastMove.id, pokemon);
|
||||
this.actions.useMove(this.lastMove.id, pokemon);
|
||||
},
|
||||
},
|
||||
cottonspore: {
|
||||
|
|
@ -344,7 +344,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
willCrit: false,
|
||||
type: '???',
|
||||
} as unknown as ActiveMove;
|
||||
const damage = this.getDamage(source, target, moveData, true);
|
||||
const damage = this.actions.getDamage(source, target, moveData, true);
|
||||
Object.assign(target.side.slotConditions[target.position]['futuremove'], {
|
||||
duration: 3,
|
||||
move: 'doomdesire',
|
||||
|
|
@ -546,7 +546,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
willCrit: false,
|
||||
type: '???',
|
||||
} as unknown as ActiveMove;
|
||||
const damage = this.getDamage(source, target, moveData, true);
|
||||
const damage = this.actions.getDamage(source, target, moveData, true);
|
||||
Object.assign(target.side.slotConditions[target.position]['futuremove'], {
|
||||
duration: 3,
|
||||
move: 'futuresight',
|
||||
|
|
@ -660,7 +660,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
pp: 20,
|
||||
onMoveFail(target, source, move) {
|
||||
move.causedCrashDamage = true;
|
||||
let damage = this.getDamage(source, target, move, true);
|
||||
let damage = this.actions.getDamage(source, target, move, true);
|
||||
if (!damage) damage = target.maxhp;
|
||||
this.damage(this.clampIntRange(damage / 2, 1, Math.floor(target.maxhp / 2)), source, source, move);
|
||||
},
|
||||
|
|
@ -688,7 +688,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
pp: 25,
|
||||
onMoveFail(target, source, move) {
|
||||
move.causedCrashDamage = true;
|
||||
let damage = this.getDamage(source, target, move, true);
|
||||
let damage = this.actions.getDamage(source, target, move, true);
|
||||
if (!damage) damage = target.maxhp;
|
||||
this.damage(this.clampIntRange(damage / 2, 1, Math.floor(target.maxhp / 2)), source, source, move);
|
||||
},
|
||||
|
|
@ -794,7 +794,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
target.removeVolatile('magiccoat');
|
||||
const newMove = this.dex.getActiveMove(move.id);
|
||||
newMove.hasBounced = true;
|
||||
this.useMove(newMove, target, source);
|
||||
this.actions.useMove(newMove, target, source);
|
||||
return null;
|
||||
},
|
||||
},
|
||||
|
|
@ -887,7 +887,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
if (noMirror.includes(lastAttackedBy.move) || !lastAttackedBy.source.hasMove(lastAttackedBy.move)) {
|
||||
return false;
|
||||
}
|
||||
this.useMove(lastAttackedBy.move, pokemon);
|
||||
this.actions.useMove(lastAttackedBy.move, pokemon);
|
||||
},
|
||||
target: "self",
|
||||
},
|
||||
|
|
@ -931,7 +931,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
naturepower: {
|
||||
inherit: true,
|
||||
onHit(pokemon) {
|
||||
this.useMove('triattack', pokemon);
|
||||
this.actions.useMove('triattack', pokemon);
|
||||
},
|
||||
},
|
||||
odorsleuth: {
|
||||
|
|
@ -1174,7 +1174,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
if (target === source || move.flags['authentic']) {
|
||||
return;
|
||||
}
|
||||
let damage = this.getDamage(source, target, move);
|
||||
let damage = this.actions.getDamage(source, target, move);
|
||||
if (!damage && damage !== 0) {
|
||||
this.add('-fail', source);
|
||||
this.attrLastMove('[still]');
|
||||
|
|
@ -1198,7 +1198,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
this.add('-activate', target, 'Substitute', '[damage]');
|
||||
}
|
||||
if (move.recoil && damage) {
|
||||
this.damage(this.calcRecoilDamage(damage, move), source, target, 'recoil');
|
||||
this.damage(this.actions.calcRecoilDamage(damage, move), source, target, 'recoil');
|
||||
}
|
||||
if (move.drain) {
|
||||
this.heal(Math.ceil(damage * move.drain[0] / move.drain[1]), source, target, 'drain');
|
||||
|
|
|
|||
|
|
@ -1,155 +1,158 @@
|
|||
export const Scripts: ModdedBattleScriptsData = {
|
||||
inherit: 'gen5',
|
||||
gen: 4,
|
||||
modifyDamage(baseDamage, pokemon, target, move, suppressMessages = false) {
|
||||
// DPP divides modifiers into several mathematically important stages
|
||||
// The modifiers run earlier than other generations are called with ModifyDamagePhase1 and ModifyDamagePhase2
|
||||
|
||||
if (!move.type) move.type = '???';
|
||||
const type = move.type;
|
||||
actions: {
|
||||
inherit: true,
|
||||
modifyDamage(baseDamage, pokemon, target, move, suppressMessages = false) {
|
||||
// DPP divides modifiers into several mathematically important stages
|
||||
// The modifiers run earlier than other generations are called with ModifyDamagePhase1 and ModifyDamagePhase2
|
||||
|
||||
// Burn
|
||||
if (pokemon.status === 'brn' && baseDamage && move.category === 'Physical' && !pokemon.hasAbility('guts')) {
|
||||
baseDamage = this.modify(baseDamage, 0.5);
|
||||
}
|
||||
if (!move.type) move.type = '???';
|
||||
const type = move.type;
|
||||
|
||||
// Other modifiers (Reflect/Light Screen/etc)
|
||||
baseDamage = this.runEvent('ModifyDamagePhase1', pokemon, target, move, baseDamage);
|
||||
|
||||
// Double battle multi-hit
|
||||
if (move.spreadHit) {
|
||||
const spreadModifier = move.spreadModifier || (this.gameType === 'free-for-all' ? 0.5 : 0.75);
|
||||
this.debug('Spread modifier: ' + spreadModifier);
|
||||
baseDamage = this.modify(baseDamage, spreadModifier);
|
||||
}
|
||||
|
||||
// Weather
|
||||
baseDamage = this.runEvent('WeatherModifyDamage', pokemon, target, move, baseDamage);
|
||||
|
||||
if (this.gen === 3 && move.category === 'Physical' && !Math.floor(baseDamage)) {
|
||||
baseDamage = 1;
|
||||
}
|
||||
|
||||
baseDamage += 2;
|
||||
|
||||
const isCrit = target.getMoveHitData(move).crit;
|
||||
if (isCrit) {
|
||||
baseDamage = this.modify(baseDamage, move.critModifier || 2);
|
||||
}
|
||||
|
||||
// Mod 2 (Damage is floored after all multipliers are in)
|
||||
baseDamage = Math.floor(this.runEvent('ModifyDamagePhase2', pokemon, target, move, baseDamage));
|
||||
|
||||
// this is not a modifier
|
||||
baseDamage = this.randomizer(baseDamage);
|
||||
|
||||
// STAB
|
||||
if (move.forceSTAB || type !== '???' && pokemon.hasType(type)) {
|
||||
// The "???" type never gets STAB
|
||||
// Not even if you Roost in Gen 4 and somehow manage to use
|
||||
// Struggle in the same turn.
|
||||
// (On second thought, it might be easier to get a MissingNo.)
|
||||
baseDamage = this.modify(baseDamage, move.stab || 1.5);
|
||||
}
|
||||
// types
|
||||
let typeMod = target.runEffectiveness(move);
|
||||
typeMod = this.clampIntRange(typeMod, -6, 6);
|
||||
target.getMoveHitData(move).typeMod = typeMod;
|
||||
if (typeMod > 0) {
|
||||
if (!suppressMessages) this.add('-supereffective', target);
|
||||
|
||||
for (let i = 0; i < typeMod; i++) {
|
||||
baseDamage *= 2;
|
||||
// Burn
|
||||
if (pokemon.status === 'brn' && baseDamage && move.category === 'Physical' && !pokemon.hasAbility('guts')) {
|
||||
baseDamage = this.battle.modify(baseDamage, 0.5);
|
||||
}
|
||||
}
|
||||
if (typeMod < 0) {
|
||||
if (!suppressMessages) this.add('-resisted', target);
|
||||
|
||||
for (let i = 0; i > typeMod; i--) {
|
||||
baseDamage = Math.floor(baseDamage / 2);
|
||||
// Other modifiers (Reflect/Light Screen/etc)
|
||||
baseDamage = this.battle.runEvent('ModifyDamagePhase1', pokemon, target, move, baseDamage);
|
||||
|
||||
// Double battle multi-hit
|
||||
if (move.spreadHit) {
|
||||
const spreadModifier = move.spreadModifier || (this.battle.gameType === 'free-for-all' ? 0.5 : 0.75);
|
||||
this.battle.debug('Spread modifier: ' + spreadModifier);
|
||||
baseDamage = this.battle.modify(baseDamage, spreadModifier);
|
||||
}
|
||||
}
|
||||
|
||||
if (isCrit && !suppressMessages) this.add('-crit', target);
|
||||
// Weather
|
||||
baseDamage = this.battle.runEvent('WeatherModifyDamage', pokemon, target, move, baseDamage);
|
||||
|
||||
// Final modifier.
|
||||
baseDamage = this.runEvent('ModifyDamage', pokemon, target, move, baseDamage);
|
||||
|
||||
if (!Math.floor(baseDamage)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return Math.floor(baseDamage);
|
||||
},
|
||||
hitStepInvulnerabilityEvent(targets, pokemon, move) {
|
||||
const hitResults = this.runEvent('Invulnerability', targets, pokemon, move);
|
||||
for (const [i, target] of targets.entries()) {
|
||||
if (hitResults[i] === false) {
|
||||
this.attrLastMove('[miss]');
|
||||
this.add('-miss', pokemon, target);
|
||||
if (this.battle.gen === 3 && move.category === 'Physical' && !Math.floor(baseDamage)) {
|
||||
baseDamage = 1;
|
||||
}
|
||||
}
|
||||
return hitResults;
|
||||
},
|
||||
hitStepAccuracy(targets, pokemon, move) {
|
||||
const hitResults = [];
|
||||
for (const [i, target] of targets.entries()) {
|
||||
this.activeTarget = target;
|
||||
// calculate true accuracy
|
||||
let accuracy = move.accuracy;
|
||||
if (move.ohko) { // bypasses accuracy modifiers
|
||||
if (!target.isSemiInvulnerable()) {
|
||||
if (pokemon.level < target.level) {
|
||||
this.add('-immune', target, '[ohko]');
|
||||
hitResults[i] = false;
|
||||
continue;
|
||||
}
|
||||
accuracy = 30 + pokemon.level - target.level;
|
||||
|
||||
baseDamage += 2;
|
||||
|
||||
const isCrit = target.getMoveHitData(move).crit;
|
||||
if (isCrit) {
|
||||
baseDamage = this.battle.modify(baseDamage, move.critModifier || 2);
|
||||
}
|
||||
|
||||
// Mod 2 (Damage is floored after all multipliers are in)
|
||||
baseDamage = Math.floor(this.battle.runEvent('ModifyDamagePhase2', pokemon, target, move, baseDamage));
|
||||
|
||||
// this is not a modifier
|
||||
baseDamage = this.battle.randomizer(baseDamage);
|
||||
|
||||
// STAB
|
||||
if (move.forceSTAB || type !== '???' && pokemon.hasType(type)) {
|
||||
// The "???" type never gets STAB
|
||||
// Not even if you Roost in Gen 4 and somehow manage to use
|
||||
// Struggle in the same turn.
|
||||
// (On second thought, it might be easier to get a MissingNo.)
|
||||
baseDamage = this.battle.modify(baseDamage, move.stab || 1.5);
|
||||
}
|
||||
// types
|
||||
let typeMod = target.runEffectiveness(move);
|
||||
typeMod = this.battle.clampIntRange(typeMod, -6, 6);
|
||||
target.getMoveHitData(move).typeMod = typeMod;
|
||||
if (typeMod > 0) {
|
||||
if (!suppressMessages) this.battle.add('-supereffective', target);
|
||||
|
||||
for (let i = 0; i < typeMod; i++) {
|
||||
baseDamage *= 2;
|
||||
}
|
||||
} else {
|
||||
const boostTable = [1, 4 / 3, 5 / 3, 2, 7 / 3, 8 / 3, 3];
|
||||
}
|
||||
if (typeMod < 0) {
|
||||
if (!suppressMessages) this.battle.add('-resisted', target);
|
||||
|
||||
let boosts;
|
||||
let boost!: number;
|
||||
if (accuracy !== true) {
|
||||
if (!move.ignoreAccuracy) {
|
||||
boosts = this.runEvent('ModifyBoost', pokemon, null, null, {...pokemon.boosts});
|
||||
boost = this.clampIntRange(boosts['accuracy'], -6, 6);
|
||||
if (boost > 0) {
|
||||
accuracy *= boostTable[boost];
|
||||
} else {
|
||||
accuracy /= boostTable[-boost];
|
||||
}
|
||||
}
|
||||
if (!move.ignoreEvasion) {
|
||||
boosts = this.runEvent('ModifyBoost', target, null, null, {...target.boosts});
|
||||
boost = this.clampIntRange(boosts['evasion'], -6, 6);
|
||||
if (boost > 0) {
|
||||
accuracy /= boostTable[boost];
|
||||
} else if (boost < 0) {
|
||||
accuracy *= boostTable[-boost];
|
||||
for (let i = 0; i > typeMod; i--) {
|
||||
baseDamage = Math.floor(baseDamage / 2);
|
||||
}
|
||||
}
|
||||
|
||||
if (isCrit && !suppressMessages) this.battle.add('-crit', target);
|
||||
|
||||
// Final modifier.
|
||||
baseDamage = this.battle.runEvent('ModifyDamage', pokemon, target, move, baseDamage);
|
||||
|
||||
if (!Math.floor(baseDamage)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return Math.floor(baseDamage);
|
||||
},
|
||||
hitStepInvulnerabilityEvent(targets, pokemon, move) {
|
||||
const hitResults = this.battle.runEvent('Invulnerability', targets, pokemon, move);
|
||||
for (const [i, target] of targets.entries()) {
|
||||
if (hitResults[i] === false) {
|
||||
this.battle.attrLastMove('[miss]');
|
||||
this.battle.add('-miss', pokemon, target);
|
||||
}
|
||||
}
|
||||
return hitResults;
|
||||
},
|
||||
hitStepAccuracy(targets, pokemon, move) {
|
||||
const hitResults = [];
|
||||
for (const [i, target] of targets.entries()) {
|
||||
this.battle.activeTarget = target;
|
||||
// calculate true accuracy
|
||||
let accuracy = move.accuracy;
|
||||
if (move.ohko) { // bypasses accuracy modifiers
|
||||
if (!target.isSemiInvulnerable()) {
|
||||
if (pokemon.level < target.level) {
|
||||
this.battle.add('-immune', target, '[ohko]');
|
||||
hitResults[i] = false;
|
||||
continue;
|
||||
}
|
||||
accuracy = 30 + pokemon.level - target.level;
|
||||
}
|
||||
} else {
|
||||
const boostTable = [1, 4 / 3, 5 / 3, 2, 7 / 3, 8 / 3, 3];
|
||||
|
||||
let boosts;
|
||||
let boost!: number;
|
||||
if (accuracy !== true) {
|
||||
if (!move.ignoreAccuracy) {
|
||||
boosts = this.battle.runEvent('ModifyBoost', pokemon, null, null, {...pokemon.boosts});
|
||||
boost = this.battle.clampIntRange(boosts['accuracy'], -6, 6);
|
||||
if (boost > 0) {
|
||||
accuracy *= boostTable[boost];
|
||||
} else {
|
||||
accuracy /= boostTable[-boost];
|
||||
}
|
||||
}
|
||||
if (!move.ignoreEvasion) {
|
||||
boosts = this.battle.runEvent('ModifyBoost', target, null, null, {...target.boosts});
|
||||
boost = this.battle.clampIntRange(boosts['evasion'], -6, 6);
|
||||
if (boost > 0) {
|
||||
accuracy /= boostTable[boost];
|
||||
} else if (boost < 0) {
|
||||
accuracy *= boostTable[-boost];
|
||||
}
|
||||
}
|
||||
}
|
||||
accuracy = this.battle.runEvent('ModifyAccuracy', target, pokemon, move, accuracy);
|
||||
}
|
||||
accuracy = this.runEvent('ModifyAccuracy', target, pokemon, move, accuracy);
|
||||
if (move.alwaysHit) {
|
||||
accuracy = true; // bypasses ohko accuracy modifiers
|
||||
} else {
|
||||
accuracy = this.battle.runEvent('Accuracy', target, pokemon, move, accuracy);
|
||||
}
|
||||
if (accuracy !== true && !this.battle.randomChance(accuracy, 100)) {
|
||||
if (!move.spreadHit) this.battle.attrLastMove('[miss]');
|
||||
this.battle.add('-miss', pokemon, target);
|
||||
hitResults[i] = false;
|
||||
continue;
|
||||
}
|
||||
hitResults[i] = true;
|
||||
}
|
||||
if (move.alwaysHit) {
|
||||
accuracy = true; // bypasses ohko accuracy modifiers
|
||||
} else {
|
||||
accuracy = this.runEvent('Accuracy', target, pokemon, move, accuracy);
|
||||
}
|
||||
if (accuracy !== true && !this.randomChance(accuracy, 100)) {
|
||||
if (!move.spreadHit) this.attrLastMove('[miss]');
|
||||
this.add('-miss', pokemon, target);
|
||||
hitResults[i] = false;
|
||||
continue;
|
||||
}
|
||||
hitResults[i] = true;
|
||||
}
|
||||
return hitResults;
|
||||
},
|
||||
|
||||
calcRecoilDamage(damageDealt, move) {
|
||||
return this.clampIntRange(Math.floor(damageDealt * move.recoil![0] / move.recoil![1]), 1);
|
||||
return hitResults;
|
||||
},
|
||||
calcRecoilDamage(damageDealt, move) {
|
||||
return this.battle.clampIntRange(Math.floor(damageDealt * move.recoil![0] / move.recoil![1]), 1);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
if (!randomMove) {
|
||||
return false;
|
||||
}
|
||||
this.useMove(randomMove, target);
|
||||
this.actions.useMove(randomMove, target);
|
||||
},
|
||||
},
|
||||
assurance: {
|
||||
|
|
@ -160,7 +160,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
if (!this.lastMove || noCopycat.includes(this.lastMove.id)) {
|
||||
return false;
|
||||
}
|
||||
this.useMove(this.lastMove.id, pokemon);
|
||||
this.actions.useMove(this.lastMove.id, pokemon);
|
||||
},
|
||||
},
|
||||
cottonspore: {
|
||||
|
|
@ -597,7 +597,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
inherit: true,
|
||||
onTryHit() {},
|
||||
onHit(pokemon) {
|
||||
this.useMove('earthquake', pokemon);
|
||||
this.actions.useMove('earthquake', pokemon);
|
||||
},
|
||||
target: "self",
|
||||
},
|
||||
|
|
@ -873,7 +873,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
if (target === source || move.flags['authentic']) {
|
||||
return;
|
||||
}
|
||||
let damage = this.getDamage(source, target, move);
|
||||
let damage = this.actions.getDamage(source, target, move);
|
||||
if (!damage && damage !== 0) {
|
||||
this.add('-fail', source);
|
||||
this.attrLastMove('[still]');
|
||||
|
|
@ -895,7 +895,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
this.add('-activate', target, 'Substitute', '[damage]');
|
||||
}
|
||||
if (move.recoil && damage) {
|
||||
this.damage(this.calcRecoilDamage(damage, move), source, target, 'recoil');
|
||||
this.damage(this.actions.calcRecoilDamage(damage, move), source, target, 'recoil');
|
||||
}
|
||||
if (move.drain) {
|
||||
this.heal(Math.ceil(damage * move.drain[0] / move.drain[1]), source, target, 'drain');
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ export const Conditions: {[k: string]: ModdedConditionData} = {
|
|||
if (this.randomChance(1, 2)) {
|
||||
return;
|
||||
}
|
||||
const damage = this.getDamage(pokemon, pokemon, 40);
|
||||
const damage = this.actions.getDamage(pokemon, pokemon, 40);
|
||||
if (typeof damage !== 'number') throw new Error("Confusion damage not dealt");
|
||||
this.damage(damage, pokemon, pokemon, {
|
||||
id: 'confused',
|
||||
|
|
|
|||
|
|
@ -675,7 +675,7 @@ export const Abilities: {[k: string]: ModdedAbilityData} = {
|
|||
},
|
||||
onFoeMaybeTrapPokemon(pokemon, source) {
|
||||
if (!source) source = this.effectData.target;
|
||||
if (!source || !this.isAdjacent(pokemon, source)) return;
|
||||
if (!source || !pokemon.isAdjacent(source)) return;
|
||||
if (pokemon.ability !== 'shadowtag' && !source.volatiles['shadowtag']) {
|
||||
pokemon.maybeTrapped = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ export const Conditions: {[k: string]: ModdedConditionData} = {
|
|||
pokemon.removeVolatile('confusion');
|
||||
return;
|
||||
}
|
||||
const damage = this.getDamage(pokemon, pokemon, 40);
|
||||
const damage = this.actions.getDamage(pokemon, pokemon, 40);
|
||||
if (typeof damage !== 'number') throw new Error("Confusion damage not dealt");
|
||||
this.directDamage(damage);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
if (target === source || move.flags['authentic'] || move.infiltrates) {
|
||||
return;
|
||||
}
|
||||
let damage = this.getDamage(source, target, move);
|
||||
let damage = this.actions.getDamage(source, target, move);
|
||||
if (!damage) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -663,7 +663,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
const moveData = {
|
||||
damage: this.effectData.totalDamage * 2,
|
||||
} as unknown as ActiveMove;
|
||||
this.moveHit(target, pokemon, this.dex.getActiveMove('bide'), moveData);
|
||||
this.actions.moveHit(target, pokemon, this.dex.getActiveMove('bide'), moveData);
|
||||
return false;
|
||||
}
|
||||
this.add('-activate', pokemon, 'Bide');
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
randomMove = this.sample(moves).id;
|
||||
}
|
||||
if (!randomMove) return false;
|
||||
this.useMove(randomMove, target);
|
||||
this.actions.useMove(randomMove, target);
|
||||
},
|
||||
},
|
||||
sappyseed: {
|
||||
|
|
|
|||
|
|
@ -350,7 +350,7 @@ export const Abilities: {[abilityid: string]: ModdedAbilityData} = {
|
|||
if (!activated) {
|
||||
this.add('-activate', pokemon, 'ability: Trash Compactor');
|
||||
activated = true;
|
||||
this.useMove('stockpile', pokemon);
|
||||
this.actions.useMove('stockpile', pokemon);
|
||||
}
|
||||
pokemon.side.removeSideCondition(sideCondition);
|
||||
this.add('-sideend', pokemon.side, this.dex.getEffect(sideCondition).name, '[from] Ability: Trash Compactor', '[of] ' + pokemon);
|
||||
|
|
@ -564,13 +564,13 @@ export const Abilities: {[abilityid: string]: ModdedAbilityData} = {
|
|||
desc: "Prevents adjacent opposing Flying-type Pokémon from choosing to switch out unless they are immune to trapping.",
|
||||
shortDesc: "Prevents adjacent Flying-type foes from choosing to switch.",
|
||||
onFoeTrapPokemon(pokemon) {
|
||||
if (pokemon.hasType('Flying') && this.isAdjacent(pokemon, this.effectData.target)) {
|
||||
if (pokemon.hasType('Flying') && pokemon.isAdjacent(this.effectData.target)) {
|
||||
pokemon.tryTrap(true);
|
||||
}
|
||||
},
|
||||
onFoeMaybeTrapPokemon(pokemon, source) {
|
||||
if (!source) source = this.effectData.target;
|
||||
if (!source || !this.isAdjacent(pokemon, source)) return;
|
||||
if (!source || !pokemon.isAdjacent(source)) return;
|
||||
if (!pokemon.knownType || pokemon.hasType('Flying')) {
|
||||
pokemon.maybeTrapped = true;
|
||||
}
|
||||
|
|
@ -767,7 +767,7 @@ export const Abilities: {[abilityid: string]: ModdedAbilityData} = {
|
|||
}
|
||||
} else {
|
||||
this.add('-message', `${(target.illusion ? target.illusion.name : target.name)} suddenly exploded!`);
|
||||
this.useMove('explosion', target, source, this.dex.getAbility('alchemist'));
|
||||
this.actions.useMove('explosion', target, source, this.dex.getAbility('alchemist'));
|
||||
}
|
||||
} else {
|
||||
this.add('-ability', source, 'Alchemist');
|
||||
|
|
@ -1059,7 +1059,7 @@ export const Abilities: {[abilityid: string]: ModdedAbilityData} = {
|
|||
desc: "On entry, this Pokémon's type changes to match its first move that's super effective against an adjacent opponent.",
|
||||
shortDesc: "On entry: type changes to match its first move that's super effective against an adjacent opponent.",
|
||||
onStart(pokemon) {
|
||||
const possibleTargets = pokemon.side.foe.active.filter(foeActive => foeActive && this.isAdjacent(pokemon, foeActive));
|
||||
const possibleTargets = pokemon.adjacentFoes();
|
||||
while (possibleTargets.length) {
|
||||
let rand = 0;
|
||||
if (possibleTargets.length > 1) rand = this.random(possibleTargets.length);
|
||||
|
|
@ -1286,12 +1286,9 @@ export const Abilities: {[abilityid: string]: ModdedAbilityData} = {
|
|||
onResidualSubOrder: 1,
|
||||
onResidual(pokemon) {
|
||||
if (pokemon.item) return;
|
||||
const pickupTargets = [];
|
||||
for (const target of this.getAllActive()) {
|
||||
if (target.lastItem && target.usedItemThisTurn && this.isAdjacent(pokemon, target)) {
|
||||
pickupTargets.push(target);
|
||||
}
|
||||
}
|
||||
const pickupTargets = this.getAllActive().filter(target => (
|
||||
target.lastItem && target.usedItemThisTurn && pokemon.isAdjacent(target)
|
||||
));
|
||||
if (!pickupTargets.length) return;
|
||||
const randomTarget = this.sample(pickupTargets);
|
||||
const item = randomTarget.lastItem;
|
||||
|
|
@ -1552,7 +1549,7 @@ export const Abilities: {[abilityid: string]: ModdedAbilityData} = {
|
|||
if (move.category === 'Special') {
|
||||
source.addVolatile('specialsound');
|
||||
}
|
||||
this.useMove('earthquake', this.effectData.target);
|
||||
this.actions.useMove('earthquake', this.effectData.target);
|
||||
}
|
||||
},
|
||||
name: "Seismic Scream",
|
||||
|
|
@ -1563,7 +1560,7 @@ export const Abilities: {[abilityid: string]: ModdedAbilityData} = {
|
|||
shortDesc: "On switch-in, this Pokémon poisons every Pokémon on the field.",
|
||||
onStart(pokemon) {
|
||||
for (const target of this.getAllActive()) {
|
||||
if (!target || !this.isAdjacent(target, pokemon) || target.status) continue;
|
||||
if (!target.isAdjacent(pokemon) || target.status) continue;
|
||||
if (target.hasAbility('soundproof')) {
|
||||
this.add('-ability', pokemon, 'Acid Rock');
|
||||
this.add('-immune', target, "[from] ability: Soundproof", "[of] " + target);
|
||||
|
|
@ -1869,13 +1866,13 @@ export const Abilities: {[abilityid: string]: ModdedAbilityData} = {
|
|||
data.moveData.isFutureMove = true;
|
||||
|
||||
if (move.category === 'Status') {
|
||||
this.useMove(move, target, data.target);
|
||||
this.actions.useMove(move, target, data.target);
|
||||
} else {
|
||||
const hitMove = new this.dex.Move(data.moveData) as ActiveMove;
|
||||
if (data.source.hp) { // the move should still activate, but animating can cause issues depending on the move
|
||||
this.add('-anim', data.source, hitMove, data.target);
|
||||
}
|
||||
this.trySpreadMoveHit([data.target], data.source, hitMove);
|
||||
this.actions.trySpreadMoveHit([data.target], data.source, hitMove);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ const longwhip: ConditionData = {
|
|||
if (data.source.isActive) {
|
||||
this.add('-anim', data.source, hitMove, data.target);
|
||||
}
|
||||
this.trySpreadMoveHit([data.target], data.source, hitMove);
|
||||
this.actions.trySpreadMoveHit([data.target], data.source, hitMove);
|
||||
},
|
||||
onEnd(target) {
|
||||
// unlike a future move, Long Whip activates each turn
|
||||
|
|
@ -71,7 +71,7 @@ const longwhip: ConditionData = {
|
|||
if (data.source.isActive) {
|
||||
this.add('-anim', data.source, hitMove, data.target);
|
||||
}
|
||||
this.trySpreadMoveHit([data.target], data.source, hitMove);
|
||||
this.actions.trySpreadMoveHit([data.target], data.source, hitMove);
|
||||
},
|
||||
};
|
||||
export const Conditions: {[k: string]: ConditionData} = {
|
||||
|
|
|
|||
|
|
@ -277,7 +277,7 @@ export const Moves: {[moveid: string]: ModdedMoveData} = {
|
|||
onSwitchIn(pokemon) {
|
||||
if (pokemon.hasAbility('trashcompactor') && !this.field.getPseudoWeather('stickyresidues')) {
|
||||
if (!pokemon.volatiles['stockpile']) {
|
||||
this.useMove('stockpile', pokemon);
|
||||
this.actions.useMove('stockpile', pokemon);
|
||||
}
|
||||
this.add('-sideend', pokemon.side, 'move: G-Max Steelsurge', '[of] ' + pokemon);
|
||||
pokemon.side.removeSideCondition('gmaxsteelsurge');
|
||||
|
|
@ -323,7 +323,7 @@ export const Moves: {[moveid: string]: ModdedMoveData} = {
|
|||
if (!pokemon.isGrounded()) return;
|
||||
if (pokemon.hasAbility('trashcompactor') && !this.field.getPseudoWeather('stickyresidues')) {
|
||||
if (!pokemon.volatiles['stockpile']) {
|
||||
this.useMove('stockpile', pokemon);
|
||||
this.actions.useMove('stockpile', pokemon);
|
||||
}
|
||||
this.add('-sideend', pokemon.side, 'move: Spikes', '[of] ' + pokemon);
|
||||
pokemon.side.removeSideCondition('spikes');
|
||||
|
|
@ -356,7 +356,7 @@ export const Moves: {[moveid: string]: ModdedMoveData} = {
|
|||
onSwitchIn(pokemon) {
|
||||
if (pokemon.hasAbility('trashcompactor') && !this.field.getPseudoWeather('stickyresidues')) {
|
||||
if (!pokemon.volatiles['stockpile']) {
|
||||
this.useMove('stockpile', pokemon);
|
||||
this.actions.useMove('stockpile', pokemon);
|
||||
}
|
||||
this.add('-sideend', pokemon.side, 'move: Stealth Rock', '[of] ' + pokemon);
|
||||
pokemon.side.removeSideCondition('stealthrock');
|
||||
|
|
@ -389,7 +389,7 @@ export const Moves: {[moveid: string]: ModdedMoveData} = {
|
|||
if (!pokemon.isGrounded()) return;
|
||||
if (pokemon.hasAbility('trashcompactor') && !this.field.getPseudoWeather('stickyresidues')) {
|
||||
if (!pokemon.volatiles['stockpile']) {
|
||||
this.useMove('stockpile', pokemon);
|
||||
this.actions.useMove('stockpile', pokemon);
|
||||
}
|
||||
this.add('-sideend', pokemon.side, 'move: Sticky Web', '[of] ' + pokemon);
|
||||
pokemon.side.removeSideCondition('stickyweb');
|
||||
|
|
@ -429,7 +429,7 @@ export const Moves: {[moveid: string]: ModdedMoveData} = {
|
|||
if (!pokemon.isGrounded()) return;
|
||||
if (pokemon.hasAbility('trashcompactor') && !this.field.getPseudoWeather('stickyresidues')) {
|
||||
if (!pokemon.volatiles['stockpile']) {
|
||||
this.useMove('stockpile', pokemon);
|
||||
this.actions.useMove('stockpile', pokemon);
|
||||
}
|
||||
this.add('-sideend', pokemon.side, 'move: Toxic Spikes', '[of] ' + pokemon);
|
||||
pokemon.side.removeSideCondition('toxicspikes');
|
||||
|
|
@ -652,7 +652,7 @@ export const Moves: {[moveid: string]: ModdedMoveData} = {
|
|||
move = 'triattack';
|
||||
}
|
||||
}
|
||||
this.useMove(move, pokemon, target);
|
||||
this.actions.useMove(move, pokemon, target);
|
||||
return null;
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -86,259 +86,262 @@ export const Scripts: ModdedBattleScriptsData = {
|
|||
newMoves("torterra", ["bodypress", "gravapple", "meteorbeam"]);
|
||||
newMoves("empoleon", ["flipturn", "haze", "originpulse", "roost"]);
|
||||
},
|
||||
canMegaEvo(pokemon) {
|
||||
const altForme = pokemon.baseSpecies.otherFormes && this.dex.getSpecies(pokemon.baseSpecies.otherFormes[0]);
|
||||
const item = pokemon.getItem();
|
||||
if (
|
||||
altForme?.isMega && altForme?.requiredMove &&
|
||||
pokemon.baseMoves.includes(this.toID(altForme.requiredMove)) && !item.zMove
|
||||
) {
|
||||
return altForme.name;
|
||||
}
|
||||
if (item.name === "Lycanite" && pokemon.baseSpecies.name === "Lycanroc-Midnight") {
|
||||
return "Lycanroc-Midnight-Mega";
|
||||
}
|
||||
if (item.name === "Lycanite" && pokemon.baseSpecies.name === "Lycanroc-Dusk") {
|
||||
return "Lycanroc-Dusk-Mega";
|
||||
}
|
||||
if (item.name === "Raichunite" && pokemon.baseSpecies.name === "Raichu-Alola") {
|
||||
return null;
|
||||
}
|
||||
if (item.name === "Slowbronite" && pokemon.baseSpecies.name === "Slowbro-Galar") {
|
||||
return null;
|
||||
}
|
||||
if (item.name === "Slowkinite" && pokemon.baseSpecies.name === "Slowking-Galar") {
|
||||
return null;
|
||||
}
|
||||
if (item.name === "Gourgeite" && pokemon.baseSpecies.name === "Gourgeist-Small") {
|
||||
return "Gourgeist-Small-Mega";
|
||||
}
|
||||
if (item.name === "Gourgeite" && pokemon.baseSpecies.name === "Gourgeist-Large") {
|
||||
return "Gourgeist-Large-Mega";
|
||||
}
|
||||
if (item.name === "Gourgeite" && pokemon.baseSpecies.name === "Gourgeist-Super") {
|
||||
return "Gourgeist-Super-Mega";
|
||||
}
|
||||
if (item.name === "Reginite" && pokemon.baseSpecies.name === "Regice") {
|
||||
return "Regice-Mega";
|
||||
}
|
||||
if (item.name === "Reginite" && pokemon.baseSpecies.name === "Registeel") {
|
||||
return "Registeel-Mega";
|
||||
}
|
||||
if (item.name === "Meowsticite" && pokemon.baseSpecies.name === "Meowstic-F") {
|
||||
return "Meowstic-F-Mega";
|
||||
}
|
||||
if (item.name === "Sawsbuckite" && pokemon.baseSpecies.name === "Delibird") {
|
||||
return "Delibird-Mega-Festive-Rider";
|
||||
}
|
||||
if (item.name === "Sawsbuckite" && pokemon.baseSpecies.name === "Sawsbuck-Summer") {
|
||||
return "Sawsbuck-Summer-Mega";
|
||||
}
|
||||
if (item.name === "Sawsbuckite" && pokemon.baseSpecies.name === "Sawsbuck-Autumn") {
|
||||
return "Sawsbuck-Autumn-Mega";
|
||||
}
|
||||
if (item.name === "Sawsbuckite" && pokemon.baseSpecies.name === "Sawsbuck-Winter") {
|
||||
return "Sawsbuck-Winter-Mega";
|
||||
}
|
||||
if (item.name === "Toxtricitite" && pokemon.baseSpecies.name === "Toxtricity-Low-Key") {
|
||||
return "Toxtricity-Low-Key-Mega";
|
||||
}
|
||||
if (item.name === "Ninetalesite" && pokemon.baseSpecies.name === "Ninetales") {
|
||||
return null;
|
||||
}
|
||||
if (item.name === "Dugtrionite" && pokemon.baseSpecies.name === "Dugtrio-Alola") {
|
||||
return null;
|
||||
}
|
||||
if (item.megaEvolves !== pokemon.baseSpecies.name || item.megaStone === pokemon.species.name) {
|
||||
return null;
|
||||
}
|
||||
return item.megaStone;
|
||||
},
|
||||
runMegaEvo(pokemon) {
|
||||
const speciesid = pokemon.canMegaEvo || pokemon.canUltraBurst;
|
||||
if (!speciesid) return false;
|
||||
const side = pokemon.side;
|
||||
|
||||
// Pokémon affected by Sky Drop cannot mega evolve. Enforce it here for now.
|
||||
for (const foeActive of side.foe.active) {
|
||||
if (foeActive.volatiles['skydrop'] && foeActive.volatiles['skydrop'].source === pokemon) {
|
||||
return false;
|
||||
actions: {
|
||||
canMegaEvo(pokemon) {
|
||||
const altForme = pokemon.baseSpecies.otherFormes && this.dex.getSpecies(pokemon.baseSpecies.otherFormes[0]);
|
||||
const item = pokemon.getItem();
|
||||
if (
|
||||
altForme?.isMega && altForme?.requiredMove &&
|
||||
pokemon.baseMoves.includes(this.battle.toID(altForme.requiredMove)) && !item.zMove
|
||||
) {
|
||||
return altForme.name;
|
||||
}
|
||||
}
|
||||
if (item.name === "Lycanite" && pokemon.baseSpecies.name === "Lycanroc-Midnight") {
|
||||
return "Lycanroc-Midnight-Mega";
|
||||
}
|
||||
if (item.name === "Lycanite" && pokemon.baseSpecies.name === "Lycanroc-Dusk") {
|
||||
return "Lycanroc-Dusk-Mega";
|
||||
}
|
||||
if (item.name === "Raichunite" && pokemon.baseSpecies.name === "Raichu-Alola") {
|
||||
return null;
|
||||
}
|
||||
if (item.name === "Slowbronite" && pokemon.baseSpecies.name === "Slowbro-Galar") {
|
||||
return null;
|
||||
}
|
||||
if (item.name === "Slowkinite" && pokemon.baseSpecies.name === "Slowking-Galar") {
|
||||
return null;
|
||||
}
|
||||
if (item.name === "Gourgeite" && pokemon.baseSpecies.name === "Gourgeist-Small") {
|
||||
return "Gourgeist-Small-Mega";
|
||||
}
|
||||
if (item.name === "Gourgeite" && pokemon.baseSpecies.name === "Gourgeist-Large") {
|
||||
return "Gourgeist-Large-Mega";
|
||||
}
|
||||
if (item.name === "Gourgeite" && pokemon.baseSpecies.name === "Gourgeist-Super") {
|
||||
return "Gourgeist-Super-Mega";
|
||||
}
|
||||
if (item.name === "Reginite" && pokemon.baseSpecies.name === "Regice") {
|
||||
return "Regice-Mega";
|
||||
}
|
||||
if (item.name === "Reginite" && pokemon.baseSpecies.name === "Registeel") {
|
||||
return "Registeel-Mega";
|
||||
}
|
||||
if (item.name === "Meowsticite" && pokemon.baseSpecies.name === "Meowstic-F") {
|
||||
return "Meowstic-F-Mega";
|
||||
}
|
||||
if (item.name === "Sawsbuckite" && pokemon.baseSpecies.name === "Delibird") {
|
||||
return "Delibird-Mega-Festive-Rider";
|
||||
}
|
||||
if (item.name === "Sawsbuckite" && pokemon.baseSpecies.name === "Sawsbuck-Summer") {
|
||||
return "Sawsbuck-Summer-Mega";
|
||||
}
|
||||
if (item.name === "Sawsbuckite" && pokemon.baseSpecies.name === "Sawsbuck-Autumn") {
|
||||
return "Sawsbuck-Autumn-Mega";
|
||||
}
|
||||
if (item.name === "Sawsbuckite" && pokemon.baseSpecies.name === "Sawsbuck-Winter") {
|
||||
return "Sawsbuck-Winter-Mega";
|
||||
}
|
||||
if (item.name === "Toxtricitite" && pokemon.baseSpecies.name === "Toxtricity-Low-Key") {
|
||||
return "Toxtricity-Low-Key-Mega";
|
||||
}
|
||||
if (item.name === "Ninetalesite" && pokemon.baseSpecies.name === "Ninetales") {
|
||||
return null;
|
||||
}
|
||||
if (item.name === "Dugtrionite" && pokemon.baseSpecies.name === "Dugtrio-Alola") {
|
||||
return null;
|
||||
}
|
||||
if (item.megaEvolves !== pokemon.baseSpecies.name || item.megaStone === pokemon.species.name) {
|
||||
return null;
|
||||
}
|
||||
return item.megaStone;
|
||||
},
|
||||
runMegaEvo(pokemon) {
|
||||
const speciesid = pokemon.canMegaEvo || pokemon.canUltraBurst;
|
||||
if (!speciesid) return false;
|
||||
const side = pokemon.side;
|
||||
|
||||
if (pokemon.illusion) {
|
||||
this.singleEvent('End', this.dex.getAbility('Illusion'), pokemon.abilityData, pokemon);
|
||||
} // only part that's changed
|
||||
pokemon.formeChange(speciesid, pokemon.getItem(), true);
|
||||
// Pokémon affected by Sky Drop cannot mega evolve. Enforce it here for now.
|
||||
for (const foeActive of side.foe.active) {
|
||||
if (foeActive.volatiles['skydrop'] && foeActive.volatiles['skydrop'].source === pokemon) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Limit one mega evolution
|
||||
const wasMega = pokemon.canMegaEvo;
|
||||
for (const ally of side.pokemon) {
|
||||
if (wasMega) {
|
||||
ally.canMegaEvo = null;
|
||||
if (pokemon.illusion) {
|
||||
this.battle.singleEvent('End', this.dex.getAbility('Illusion'), pokemon.abilityData, pokemon);
|
||||
} // only part that's changed
|
||||
pokemon.formeChange(speciesid, pokemon.getItem(), true);
|
||||
|
||||
// Limit one mega evolution
|
||||
const wasMega = pokemon.canMegaEvo;
|
||||
for (const ally of side.pokemon) {
|
||||
if (wasMega) {
|
||||
ally.canMegaEvo = null;
|
||||
} else {
|
||||
ally.canUltraBurst = null;
|
||||
}
|
||||
}
|
||||
|
||||
this.battle.runEvent('AfterMega', pokemon);
|
||||
return true;
|
||||
},
|
||||
|
||||
getDamage(
|
||||
pokemon: Pokemon, target: Pokemon, move: string | number | ActiveMove,
|
||||
suppressMessages = false
|
||||
): number | undefined | null | false {
|
||||
if (typeof move === 'string') move = this.dex.getActiveMove(move);
|
||||
|
||||
if (typeof move === 'number') {
|
||||
const basePower = move;
|
||||
move = new Dex.Move({
|
||||
basePower,
|
||||
type: '???',
|
||||
category: 'Physical',
|
||||
willCrit: false,
|
||||
}) as ActiveMove;
|
||||
move.hit = 0;
|
||||
}
|
||||
|
||||
if (!move.ignoreImmunity || (move.ignoreImmunity !== true && !move.ignoreImmunity[move.type])) {
|
||||
if (!target.runImmunity(move.type, !suppressMessages)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (move.ohko) return target.maxhp;
|
||||
if (move.damageCallback) return move.damageCallback.call(this.battle, pokemon, target);
|
||||
if (move.damage === 'level') {
|
||||
return pokemon.level;
|
||||
} else if (move.damage) {
|
||||
return move.damage;
|
||||
}
|
||||
|
||||
const category = this.battle.getCategory(move);
|
||||
const defensiveCategory = move.defensiveCategory || category;
|
||||
|
||||
let basePower: number | false | null = move.basePower;
|
||||
if (move.basePowerCallback) {
|
||||
basePower = move.basePowerCallback.call(this.battle, pokemon, target, move);
|
||||
}
|
||||
if (!basePower) return basePower === 0 ? undefined : basePower;
|
||||
basePower = this.battle.clampIntRange(basePower, 1);
|
||||
|
||||
let critMult;
|
||||
let critRatio = this.battle.runEvent('ModifyCritRatio', pokemon, target, move, move.critRatio || 0);
|
||||
if (this.battle.gen <= 5) {
|
||||
critRatio = this.battle.clampIntRange(critRatio, 0, 5);
|
||||
critMult = [0, 16, 8, 4, 3, 2];
|
||||
} else {
|
||||
ally.canUltraBurst = null;
|
||||
critRatio = this.battle.clampIntRange(critRatio, 0, 4);
|
||||
if (this.battle.gen === 6) {
|
||||
critMult = [0, 16, 8, 2, 1];
|
||||
} else {
|
||||
critMult = [0, 24, 8, 2, 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.runEvent('AfterMega', pokemon);
|
||||
return true;
|
||||
},
|
||||
|
||||
getDamage(
|
||||
pokemon: Pokemon, target: Pokemon, move: string | number | ActiveMove,
|
||||
suppressMessages = false
|
||||
): number | undefined | null | false {
|
||||
if (typeof move === 'string') move = this.dex.getActiveMove(move);
|
||||
|
||||
if (typeof move === 'number') {
|
||||
const basePower = move;
|
||||
move = new Dex.Move({
|
||||
basePower,
|
||||
type: '???',
|
||||
category: 'Physical',
|
||||
willCrit: false,
|
||||
}) as ActiveMove;
|
||||
move.hit = 0;
|
||||
}
|
||||
|
||||
if (!move.ignoreImmunity || (move.ignoreImmunity !== true && !move.ignoreImmunity[move.type])) {
|
||||
if (!target.runImmunity(move.type, !suppressMessages)) {
|
||||
return false;
|
||||
const moveHit = target.getMoveHitData(move);
|
||||
moveHit.crit = move.willCrit || false;
|
||||
if (move.willCrit === undefined && critRatio) {
|
||||
moveHit.crit = this.battle.randomChance(1, critMult[critRatio]);
|
||||
}
|
||||
}
|
||||
|
||||
if (move.ohko) return target.maxhp;
|
||||
if (move.damageCallback) return move.damageCallback.call(this, pokemon, target);
|
||||
if (move.damage === 'level') {
|
||||
return pokemon.level;
|
||||
} else if (move.damage) {
|
||||
return move.damage;
|
||||
}
|
||||
|
||||
const category = this.getCategory(move);
|
||||
const defensiveCategory = move.defensiveCategory || category;
|
||||
|
||||
let basePower: number | false | null = move.basePower;
|
||||
if (move.basePowerCallback) {
|
||||
basePower = move.basePowerCallback.call(this, pokemon, target, move);
|
||||
}
|
||||
if (!basePower) return basePower === 0 ? undefined : basePower;
|
||||
basePower = this.clampIntRange(basePower, 1);
|
||||
|
||||
let critMult;
|
||||
let critRatio = this.runEvent('ModifyCritRatio', pokemon, target, move, move.critRatio || 0);
|
||||
if (this.gen <= 5) {
|
||||
critRatio = this.clampIntRange(critRatio, 0, 5);
|
||||
critMult = [0, 16, 8, 4, 3, 2];
|
||||
} else {
|
||||
critRatio = this.clampIntRange(critRatio, 0, 4);
|
||||
if (this.gen === 6) {
|
||||
critMult = [0, 16, 8, 2, 1];
|
||||
} else {
|
||||
critMult = [0, 24, 8, 2, 1];
|
||||
if (moveHit.crit) {
|
||||
moveHit.crit = this.battle.runEvent('CriticalHit', target, null, move);
|
||||
}
|
||||
}
|
||||
|
||||
const moveHit = target.getMoveHitData(move);
|
||||
moveHit.crit = move.willCrit || false;
|
||||
if (move.willCrit === undefined && critRatio) {
|
||||
moveHit.crit = this.randomChance(1, critMult[critRatio]);
|
||||
}
|
||||
// happens after crit calculation
|
||||
basePower = this.battle.runEvent('BasePower', pokemon, target, move, basePower, true);
|
||||
|
||||
if (moveHit.crit) {
|
||||
moveHit.crit = this.runEvent('CriticalHit', target, null, move);
|
||||
}
|
||||
if (!basePower) return 0;
|
||||
basePower = this.battle.clampIntRange(basePower, 1);
|
||||
|
||||
// happens after crit calculation
|
||||
basePower = this.runEvent('BasePower', pokemon, target, move, basePower, true);
|
||||
const level = pokemon.level;
|
||||
|
||||
if (!basePower) return 0;
|
||||
basePower = this.clampIntRange(basePower, 1);
|
||||
const attacker = pokemon;
|
||||
const defender = target;
|
||||
let attackStat: StatNameExceptHP = category === 'Physical' ? 'atk' : 'spa';
|
||||
const defenseStat: StatNameExceptHP = defensiveCategory === 'Physical' ? 'def' : 'spd';
|
||||
if (move.useSourceDefensiveAsOffensive) {
|
||||
attackStat = defenseStat;
|
||||
// Body press really wants to use the def stat,
|
||||
// so it switches stats to compensate for Wonder Room.
|
||||
// Of course, the game thus miscalculates the boosts...
|
||||
if ('wonderroom' in this.battle.field.pseudoWeather) {
|
||||
if (attackStat === 'def') {
|
||||
attackStat = 'spd';
|
||||
} else if (attackStat === 'spd') {
|
||||
attackStat = 'def';
|
||||
}
|
||||
if (attacker.boosts['def'] || attacker.boosts['spd']) {
|
||||
this.battle.hint("Body Press uses Sp. Def boosts when Wonder Room is active.");
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((move as any).settleBoosted) {
|
||||
attackStat = 'atk';
|
||||
}
|
||||
|
||||
const level = pokemon.level;
|
||||
const statTable = {atk: 'Atk', def: 'Def', spa: 'SpA', spd: 'SpD', spe: 'Spe'};
|
||||
let attack;
|
||||
let defense;
|
||||
|
||||
const attacker = pokemon;
|
||||
const defender = target;
|
||||
let attackStat: StatNameExceptHP = category === 'Physical' ? 'atk' : 'spa';
|
||||
const defenseStat: StatNameExceptHP = defensiveCategory === 'Physical' ? 'def' : 'spd';
|
||||
if (move.useSourceDefensiveAsOffensive) {
|
||||
attackStat = defenseStat;
|
||||
// Body press really wants to use the def stat,
|
||||
// so it switches stats to compensate for Wonder Room.
|
||||
// Of course, the game thus miscalculates the boosts...
|
||||
if ('wonderroom' in this.field.pseudoWeather) {
|
||||
let atkBoosts = move.useTargetOffensive ? defender.boosts[attackStat] : attacker.boosts[attackStat];
|
||||
if ((move as any).bodyofwaterBoosted) {
|
||||
if (attackStat === 'def') {
|
||||
attackStat = 'spd';
|
||||
atkBoosts = attacker.boosts['atk'];
|
||||
} else if (attackStat === 'spd') {
|
||||
attackStat = 'def';
|
||||
}
|
||||
if (attacker.boosts['def'] || attacker.boosts['spd']) {
|
||||
this.hint("Body Press uses Sp. Def boosts when Wonder Room is active.");
|
||||
atkBoosts = attacker.boosts['spa'];
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((move as any).settleBoosted) {
|
||||
attackStat = 'atk';
|
||||
}
|
||||
let defBoosts = defender.boosts[defenseStat];
|
||||
|
||||
const statTable = {atk: 'Atk', def: 'Def', spa: 'SpA', spd: 'SpD', spe: 'Spe'};
|
||||
let attack;
|
||||
let defense;
|
||||
let ignoreNegativeOffensive = !!move.ignoreNegativeOffensive;
|
||||
let ignorePositiveDefensive = !!move.ignorePositiveDefensive;
|
||||
|
||||
let atkBoosts = move.useTargetOffensive ? defender.boosts[attackStat] : attacker.boosts[attackStat];
|
||||
if ((move as any).bodyofwaterBoosted) {
|
||||
if (attackStat === 'def') {
|
||||
atkBoosts = attacker.boosts['atk'];
|
||||
} else if (attackStat === 'spd') {
|
||||
atkBoosts = attacker.boosts['spa'];
|
||||
if (moveHit.crit) {
|
||||
ignoreNegativeOffensive = true;
|
||||
ignorePositiveDefensive = true;
|
||||
}
|
||||
}
|
||||
let defBoosts = defender.boosts[defenseStat];
|
||||
const ignoreOffensive = !!(move.ignoreOffensive || (ignoreNegativeOffensive && atkBoosts < 0));
|
||||
const ignoreDefensive = !!(move.ignoreDefensive || (ignorePositiveDefensive && defBoosts > 0));
|
||||
|
||||
let ignoreNegativeOffensive = !!move.ignoreNegativeOffensive;
|
||||
let ignorePositiveDefensive = !!move.ignorePositiveDefensive;
|
||||
if (ignoreOffensive) {
|
||||
this.battle.debug('Negating (sp)atk boost/penalty.');
|
||||
atkBoosts = 0;
|
||||
}
|
||||
if (ignoreDefensive) {
|
||||
this.battle.debug('Negating (sp)def boost/penalty.');
|
||||
defBoosts = 0;
|
||||
}
|
||||
|
||||
if (moveHit.crit) {
|
||||
ignoreNegativeOffensive = true;
|
||||
ignorePositiveDefensive = true;
|
||||
}
|
||||
const ignoreOffensive = !!(move.ignoreOffensive || (ignoreNegativeOffensive && atkBoosts < 0));
|
||||
const ignoreDefensive = !!(move.ignoreDefensive || (ignorePositiveDefensive && defBoosts > 0));
|
||||
if (move.useTargetOffensive) {
|
||||
attack = defender.calculateStat(attackStat, atkBoosts);
|
||||
} else {
|
||||
attack = attacker.calculateStat(attackStat, atkBoosts);
|
||||
}
|
||||
|
||||
if (ignoreOffensive) {
|
||||
this.debug('Negating (sp)atk boost/penalty.');
|
||||
atkBoosts = 0;
|
||||
}
|
||||
if (ignoreDefensive) {
|
||||
this.debug('Negating (sp)def boost/penalty.');
|
||||
defBoosts = 0;
|
||||
}
|
||||
attackStat = (category === 'Physical' ? 'atk' : 'spa');
|
||||
defense = defender.calculateStat(defenseStat, defBoosts);
|
||||
|
||||
if (move.useTargetOffensive) {
|
||||
attack = defender.calculateStat(attackStat, atkBoosts);
|
||||
} else {
|
||||
attack = attacker.calculateStat(attackStat, atkBoosts);
|
||||
}
|
||||
// Apply Stat Modifiers
|
||||
attack = this.battle.runEvent('Modify' + statTable[attackStat], attacker, defender, move, attack);
|
||||
defense = this.battle.runEvent('Modify' + statTable[defenseStat], defender, attacker, move, defense);
|
||||
|
||||
attackStat = (category === 'Physical' ? 'atk' : 'spa');
|
||||
defense = defender.calculateStat(defenseStat, defBoosts);
|
||||
if (this.battle.gen <= 4 && ['explosion', 'selfdestruct'].includes(move.id) && defenseStat === 'def') {
|
||||
defense = this.battle.clampIntRange(Math.floor(defense / 2), 1);
|
||||
}
|
||||
|
||||
// Apply Stat Modifiers
|
||||
attack = this.runEvent('Modify' + statTable[attackStat], attacker, defender, move, attack);
|
||||
defense = this.runEvent('Modify' + statTable[defenseStat], defender, attacker, move, defense);
|
||||
const tr = this.battle.trunc;
|
||||
|
||||
if (this.gen <= 4 && ['explosion', 'selfdestruct'].includes(move.id) && defenseStat === 'def') {
|
||||
defense = this.clampIntRange(Math.floor(defense / 2), 1);
|
||||
}
|
||||
// int(int(int(2 * L / 5 + 2) * A * P / D) / 50);
|
||||
const baseDamage = tr(tr(tr(tr(2 * level / 5 + 2) * basePower * attack) / defense) / 50);
|
||||
|
||||
const tr = this.trunc;
|
||||
|
||||
// int(int(int(2 * L / 5 + 2) * A * P / D) / 50);
|
||||
const baseDamage = tr(tr(tr(tr(2 * level / 5 + 2) * basePower * attack) / defense) / 50);
|
||||
|
||||
// Calculate damage modifiers separately (order differs between generations)
|
||||
return this.modifyDamage(baseDamage, pokemon, target, move, suppressMessages);
|
||||
// Calculate damage modifiers separately (order differs between generations)
|
||||
return this.modifyDamage(baseDamage, pokemon, target, move, suppressMessages);
|
||||
},
|
||||
},
|
||||
|
||||
pokemon: {
|
||||
|
|
|
|||
|
|
@ -7,108 +7,110 @@ export const Scripts: ModdedBattleScriptsData = {
|
|||
this.modData('FormatsData', id).isNonstandard = null;
|
||||
}
|
||||
},
|
||||
canMegaEvo(pokemon) {
|
||||
if (pokemon.species.isMega) return null;
|
||||
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;
|
||||
|
||||
// @ts-ignore
|
||||
const species: Species = this.getMixedSpecies(pokemon.m.originalSpecies, pokemon.canMegaEvo);
|
||||
const side = pokemon.side;
|
||||
|
||||
// Pokémon affected by Sky Drop cannot Mega Evolve. Enforce it here for now.
|
||||
for (const foeActive of side.foe.active) {
|
||||
if (foeActive.volatiles['skydrop'] && foeActive.volatiles['skydrop'].source === pokemon) {
|
||||
return false;
|
||||
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;
|
||||
|
||||
// Do we have a proper sprite for it?
|
||||
if (this.dex.getSpecies(pokemon.canMegaEvo!).baseSpecies === pokemon.m.originalSpecies) {
|
||||
pokemon.formeChange(species, pokemon.getItem(), true);
|
||||
} else {
|
||||
const oSpecies = this.dex.getSpecies(pokemon.m.originalSpecies);
|
||||
// @ts-ignore
|
||||
const oMegaSpecies = this.dex.getSpecies(species.originalMega);
|
||||
pokemon.formeChange(species, pokemon.getItem(), true);
|
||||
this.add('-start', pokemon, oMegaSpecies.requiredItem, '[silent]');
|
||||
if (oSpecies.types.length !== pokemon.species.types.length || oSpecies.types[1] !== pokemon.species.types[1]) {
|
||||
this.add('-start', pokemon, 'typechange', pokemon.species.types.join('/'), '[silent]');
|
||||
}
|
||||
}
|
||||
const species: Species = this.getMixedSpecies(pokemon.m.originalSpecies, pokemon.canMegaEvo);
|
||||
const side = pokemon.side;
|
||||
|
||||
pokemon.canMegaEvo = null;
|
||||
return true;
|
||||
},
|
||||
getMixedSpecies(originalForme, megaForme) {
|
||||
const originalSpecies = this.dex.getSpecies(originalForme);
|
||||
const megaSpecies = this.dex.getSpecies(megaForme);
|
||||
if (originalSpecies.baseSpecies === megaSpecies.baseSpecies) return megaSpecies;
|
||||
// @ts-ignore
|
||||
const deltas = this.getMegaDeltas(megaSpecies);
|
||||
// @ts-ignore
|
||||
const species = this.doGetMixedSpecies(originalSpecies, deltas);
|
||||
return species;
|
||||
},
|
||||
getMegaDeltas(megaSpecies) {
|
||||
const baseSpecies = this.dex.getSpecies(megaSpecies.baseSpecies);
|
||||
const deltas: {
|
||||
ability: string,
|
||||
baseStats: SparseStatsTable,
|
||||
weighthg: number,
|
||||
originalMega: string,
|
||||
requiredItem: string | undefined,
|
||||
type?: string,
|
||||
isMega?: boolean,
|
||||
} = {
|
||||
ability: megaSpecies.abilities['0'],
|
||||
baseStats: {},
|
||||
weighthg: megaSpecies.weighthg - baseSpecies.weighthg,
|
||||
originalMega: megaSpecies.name,
|
||||
requiredItem: megaSpecies.requiredItem,
|
||||
};
|
||||
let statId: StatName;
|
||||
for (statId in megaSpecies.baseStats) {
|
||||
deltas.baseStats[statId] = megaSpecies.baseStats[statId] - baseSpecies.baseStats[statId];
|
||||
}
|
||||
if (megaSpecies.types.length > baseSpecies.types.length) {
|
||||
deltas.type = megaSpecies.types[1];
|
||||
} else if (megaSpecies.types.length < baseSpecies.types.length) {
|
||||
deltas.type = 'mono';
|
||||
} else if (megaSpecies.types[1] !== baseSpecies.types[1]) {
|
||||
deltas.type = megaSpecies.types[1];
|
||||
}
|
||||
if (megaSpecies.isMega) deltas.isMega = true;
|
||||
return deltas;
|
||||
},
|
||||
doGetMixedSpecies(speciesOrForme, deltas) {
|
||||
if (!deltas) throw new TypeError("Must specify deltas!");
|
||||
const species = this.dex.deepClone(this.dex.getSpecies(speciesOrForme));
|
||||
species.abilities = {'0': deltas.ability};
|
||||
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.clampIntRange(baseStats[statName] + deltas.baseStats[statName], 1, 255);
|
||||
}
|
||||
species.weighthg = Math.max(1, species.weighthg + deltas.weighthg);
|
||||
species.originalMega = deltas.originalMega;
|
||||
species.requiredItem = deltas.requiredItem;
|
||||
if (deltas.isMega) species.isMega = true;
|
||||
return species;
|
||||
// Pokémon affected by Sky Drop cannot Mega Evolve. Enforce it here for now.
|
||||
for (const foeActive of side.foe.active) {
|
||||
if (foeActive.volatiles['skydrop'] && foeActive.volatiles['skydrop'].source === pokemon) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Do we have a proper sprite for it?
|
||||
if (this.dex.getSpecies(pokemon.canMegaEvo!).baseSpecies === pokemon.m.originalSpecies) {
|
||||
pokemon.formeChange(species, pokemon.getItem(), true);
|
||||
} else {
|
||||
const oSpecies = this.dex.getSpecies(pokemon.m.originalSpecies);
|
||||
// @ts-ignore
|
||||
const oMegaSpecies = this.dex.getSpecies(species.originalMega);
|
||||
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;
|
||||
},
|
||||
getMixedSpecies(originalForme, megaForme) {
|
||||
const originalSpecies = this.dex.getSpecies(originalForme);
|
||||
const megaSpecies = this.dex.getSpecies(megaForme);
|
||||
if (originalSpecies.baseSpecies === megaSpecies.baseSpecies) return megaSpecies;
|
||||
// @ts-ignore
|
||||
const deltas = this.getMegaDeltas(megaSpecies);
|
||||
// @ts-ignore
|
||||
const species = this.doGetMixedSpecies(originalSpecies, deltas);
|
||||
return species;
|
||||
},
|
||||
getMegaDeltas(megaSpecies) {
|
||||
const baseSpecies = this.dex.getSpecies(megaSpecies.baseSpecies);
|
||||
const deltas: {
|
||||
ability: string,
|
||||
baseStats: SparseStatsTable,
|
||||
weighthg: number,
|
||||
originalMega: string,
|
||||
requiredItem: string | undefined,
|
||||
type?: string,
|
||||
isMega?: boolean,
|
||||
} = {
|
||||
ability: megaSpecies.abilities['0'],
|
||||
baseStats: {},
|
||||
weighthg: megaSpecies.weighthg - baseSpecies.weighthg,
|
||||
originalMega: megaSpecies.name,
|
||||
requiredItem: megaSpecies.requiredItem,
|
||||
};
|
||||
let statId: StatName;
|
||||
for (statId in megaSpecies.baseStats) {
|
||||
deltas.baseStats[statId] = megaSpecies.baseStats[statId] - baseSpecies.baseStats[statId];
|
||||
}
|
||||
if (megaSpecies.types.length > baseSpecies.types.length) {
|
||||
deltas.type = megaSpecies.types[1];
|
||||
} else if (megaSpecies.types.length < baseSpecies.types.length) {
|
||||
deltas.type = 'mono';
|
||||
} else if (megaSpecies.types[1] !== baseSpecies.types[1]) {
|
||||
deltas.type = megaSpecies.types[1];
|
||||
}
|
||||
if (megaSpecies.isMega) deltas.isMega = true;
|
||||
return deltas;
|
||||
},
|
||||
doGetMixedSpecies(speciesOrForme, deltas) {
|
||||
if (!deltas) throw new TypeError("Must specify deltas!");
|
||||
const species = this.dex.deepClone(this.dex.getSpecies(speciesOrForme));
|
||||
species.abilities = {'0': deltas.ability};
|
||||
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.originalMega = deltas.originalMega;
|
||||
species.requiredItem = deltas.requiredItem;
|
||||
if (deltas.isMega) species.isMega = true;
|
||||
return species;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,114 +6,116 @@ export const Scripts: ModdedBattleScriptsData = {
|
|||
this.modData('Items', id).onTakeItem = false;
|
||||
}
|
||||
},
|
||||
canMegaEvo(pokemon) {
|
||||
if (pokemon.species.isMega || pokemon.species.isPrimal) return null;
|
||||
actions: {
|
||||
canMegaEvo(pokemon) {
|
||||
if (pokemon.species.isMega || pokemon.species.isPrimal) return null;
|
||||
|
||||
const item = pokemon.getItem();
|
||||
if (item.megaStone) {
|
||||
if (item.megaStone === pokemon.name) return null;
|
||||
return item.megaStone;
|
||||
} else if (pokemon.baseMoves.includes('dragonascent' as ID)) {
|
||||
return 'Rayquaza-Mega';
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
runMegaEvo(pokemon) {
|
||||
if (pokemon.species.isMega || pokemon.species.isPrimal) return false;
|
||||
|
||||
const isUltraBurst = !pokemon.canMegaEvo;
|
||||
// @ts-ignore
|
||||
const species: Species = this.getMixedSpecies(pokemon.m.originalSpecies, pokemon.canMegaEvo || pokemon.canUltraBurst);
|
||||
const side = pokemon.side;
|
||||
|
||||
// Pokémon affected by Sky Drop cannot Mega Evolve. Enforce it here for now.
|
||||
for (const foeActive of side.foe.active) {
|
||||
if (foeActive.volatiles['skydrop'] && foeActive.volatiles['skydrop'].source === pokemon) {
|
||||
return false;
|
||||
const item = pokemon.getItem();
|
||||
if (item.megaStone) {
|
||||
if (item.megaStone === pokemon.name) return null;
|
||||
return item.megaStone;
|
||||
} else if (pokemon.baseMoves.includes('dragonascent' as ID)) {
|
||||
return 'Rayquaza-Mega';
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
},
|
||||
runMegaEvo(pokemon) {
|
||||
if (pokemon.species.isMega || pokemon.species.isPrimal) return false;
|
||||
|
||||
// Do we have a proper sprite for it?
|
||||
// @ts-ignore assert non-null pokemon.canMegaEvo
|
||||
if (isUltraBurst || this.dex.getSpecies(pokemon.canMegaEvo).baseSpecies === pokemon.m.originalSpecies) {
|
||||
pokemon.formeChange(species, pokemon.getItem(), true);
|
||||
} else {
|
||||
const oSpecies = this.dex.getSpecies(pokemon.m.originalSpecies);
|
||||
const isUltraBurst = !pokemon.canMegaEvo;
|
||||
// @ts-ignore
|
||||
const oMegaSpecies = this.dex.getSpecies(species.originalMega);
|
||||
pokemon.formeChange(species, pokemon.getItem(), true);
|
||||
this.add('-start', pokemon, oMegaSpecies.requiredItem || oMegaSpecies.requiredMove, '[silent]');
|
||||
if (oSpecies.types.length !== pokemon.species.types.length || oSpecies.types[1] !== pokemon.species.types[1]) {
|
||||
this.add('-start', pokemon, 'typechange', pokemon.species.types.join('/'), '[silent]');
|
||||
}
|
||||
}
|
||||
const species: Species = this.getMixedSpecies(pokemon.m.originalSpecies, pokemon.canMegaEvo || pokemon.canUltraBurst);
|
||||
const side = pokemon.side;
|
||||
|
||||
pokemon.canMegaEvo = null;
|
||||
if (isUltraBurst) pokemon.canUltraBurst = null;
|
||||
return true;
|
||||
},
|
||||
getMixedSpecies(originalSpecies, megaSpecies) {
|
||||
const oSpecies = this.dex.getSpecies(originalSpecies);
|
||||
const mSpecies = this.dex.getSpecies(megaSpecies);
|
||||
if (oSpecies.baseSpecies === mSpecies.baseSpecies) return mSpecies;
|
||||
// @ts-ignore
|
||||
const deltas = this.getMegaDeltas(mSpecies);
|
||||
// @ts-ignore
|
||||
const species = this.doGetMixedSpecies(oSpecies, deltas);
|
||||
return species;
|
||||
},
|
||||
getMegaDeltas(megaSpecies) {
|
||||
const baseSpecies = this.dex.getSpecies(megaSpecies.baseSpecies);
|
||||
const deltas: {
|
||||
ability: string,
|
||||
baseStats: SparseStatsTable,
|
||||
weighthg: number,
|
||||
originalMega: string,
|
||||
requiredItem: string | undefined,
|
||||
type?: string,
|
||||
isMega?: boolean,
|
||||
isPrimal?: boolean,
|
||||
} = {
|
||||
ability: megaSpecies.abilities['0'],
|
||||
baseStats: {},
|
||||
weighthg: megaSpecies.weighthg - baseSpecies.weighthg,
|
||||
originalMega: megaSpecies.name,
|
||||
requiredItem: megaSpecies.requiredItem,
|
||||
};
|
||||
let stat: StatName;
|
||||
for (stat in megaSpecies.baseStats) {
|
||||
deltas.baseStats[stat] = megaSpecies.baseStats[stat] - baseSpecies.baseStats[stat];
|
||||
}
|
||||
if (megaSpecies.types.length > baseSpecies.types.length) {
|
||||
deltas.type = megaSpecies.types[1];
|
||||
} else if (megaSpecies.types.length < baseSpecies.types.length) {
|
||||
deltas.type = baseSpecies.types[0];
|
||||
} else if (megaSpecies.types[1] !== baseSpecies.types[1]) {
|
||||
deltas.type = megaSpecies.types[1];
|
||||
}
|
||||
if (megaSpecies.isMega) deltas.isMega = true;
|
||||
if (megaSpecies.isPrimal) deltas.isPrimal = true;
|
||||
return deltas;
|
||||
},
|
||||
doGetMixedSpecies(speciesOrSpeciesName, deltas) {
|
||||
if (!deltas) throw new TypeError("Must specify deltas!");
|
||||
const species = this.dex.deepClone(this.dex.getSpecies(speciesOrSpeciesName));
|
||||
species.abilities = {'0': deltas.ability};
|
||||
if (species.types[0] === deltas.type) {
|
||||
species.types = [deltas.type];
|
||||
} else if (deltas.type) {
|
||||
species.types = [species.types[0], deltas.type];
|
||||
}
|
||||
const baseStats = species.baseStats;
|
||||
for (const statName in baseStats) {
|
||||
baseStats[statName] = this.clampIntRange(baseStats[statName] + deltas.baseStats[statName], 1, 255);
|
||||
}
|
||||
species.weighthg = Math.max(1, species.weighthg + deltas.weighthg);
|
||||
species.originalMega = deltas.originalMega;
|
||||
species.requiredItem = deltas.requiredItem;
|
||||
if (deltas.isMega) species.isMega = true;
|
||||
if (deltas.isPrimal) species.isPrimal = true;
|
||||
return species;
|
||||
// Pokémon affected by Sky Drop cannot Mega Evolve. Enforce it here for now.
|
||||
for (const foeActive of side.foe.active) {
|
||||
if (foeActive.volatiles['skydrop'] && foeActive.volatiles['skydrop'].source === pokemon) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Do we have a proper sprite for it?
|
||||
// @ts-ignore assert non-null pokemon.canMegaEvo
|
||||
if (isUltraBurst || this.dex.getSpecies(pokemon.canMegaEvo).baseSpecies === pokemon.m.originalSpecies) {
|
||||
pokemon.formeChange(species, pokemon.getItem(), true);
|
||||
} else {
|
||||
const oSpecies = this.dex.getSpecies(pokemon.m.originalSpecies);
|
||||
// @ts-ignore
|
||||
const oMegaSpecies = this.dex.getSpecies(species.originalMega);
|
||||
pokemon.formeChange(species, pokemon.getItem(), true);
|
||||
this.battle.add('-start', pokemon, oMegaSpecies.requiredItem || oMegaSpecies.requiredMove, '[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;
|
||||
if (isUltraBurst) pokemon.canUltraBurst = null;
|
||||
return true;
|
||||
},
|
||||
getMixedSpecies(originalSpecies, megaSpecies) {
|
||||
const oSpecies = this.dex.getSpecies(originalSpecies);
|
||||
const mSpecies = this.dex.getSpecies(megaSpecies);
|
||||
if (oSpecies.baseSpecies === mSpecies.baseSpecies) return mSpecies;
|
||||
// @ts-ignore
|
||||
const deltas = this.getMegaDeltas(mSpecies);
|
||||
// @ts-ignore
|
||||
const species = this.doGetMixedSpecies(oSpecies, deltas);
|
||||
return species;
|
||||
},
|
||||
getMegaDeltas(megaSpecies) {
|
||||
const baseSpecies = this.dex.getSpecies(megaSpecies.baseSpecies);
|
||||
const deltas: {
|
||||
ability: string,
|
||||
baseStats: SparseStatsTable,
|
||||
weighthg: number,
|
||||
originalMega: string,
|
||||
requiredItem: string | undefined,
|
||||
type?: string,
|
||||
isMega?: boolean,
|
||||
isPrimal?: boolean,
|
||||
} = {
|
||||
ability: megaSpecies.abilities['0'],
|
||||
baseStats: {},
|
||||
weighthg: megaSpecies.weighthg - baseSpecies.weighthg,
|
||||
originalMega: megaSpecies.name,
|
||||
requiredItem: megaSpecies.requiredItem,
|
||||
};
|
||||
let stat: StatName;
|
||||
for (stat in megaSpecies.baseStats) {
|
||||
deltas.baseStats[stat] = megaSpecies.baseStats[stat] - baseSpecies.baseStats[stat];
|
||||
}
|
||||
if (megaSpecies.types.length > baseSpecies.types.length) {
|
||||
deltas.type = megaSpecies.types[1];
|
||||
} else if (megaSpecies.types.length < baseSpecies.types.length) {
|
||||
deltas.type = baseSpecies.types[0];
|
||||
} else if (megaSpecies.types[1] !== baseSpecies.types[1]) {
|
||||
deltas.type = megaSpecies.types[1];
|
||||
}
|
||||
if (megaSpecies.isMega) deltas.isMega = true;
|
||||
if (megaSpecies.isPrimal) deltas.isPrimal = true;
|
||||
return deltas;
|
||||
},
|
||||
doGetMixedSpecies(speciesOrSpeciesName, deltas) {
|
||||
if (!deltas) throw new TypeError("Must specify deltas!");
|
||||
const species = this.dex.deepClone(this.dex.getSpecies(speciesOrSpeciesName));
|
||||
species.abilities = {'0': deltas.ability};
|
||||
if (species.types[0] === deltas.type) {
|
||||
species.types = [deltas.type];
|
||||
} 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.originalMega = deltas.originalMega;
|
||||
species.requiredItem = deltas.requiredItem;
|
||||
if (deltas.isMega) species.isMega = true;
|
||||
if (deltas.isPrimal) species.isPrimal = true;
|
||||
return species;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -340,7 +340,7 @@ export const Abilities: {[k: string]: ModdedAbilityData} = {
|
|||
newMove.hasBounced = true;
|
||||
newMove.pranksterBoosted = false;
|
||||
this.add('-ability', target, 'Carefree');
|
||||
this.useMove(newMove, target, source);
|
||||
this.actions.useMove(newMove, target, source);
|
||||
return null;
|
||||
},
|
||||
onAllyTryHitSide(target, source, move) {
|
||||
|
|
@ -351,7 +351,7 @@ export const Abilities: {[k: string]: ModdedAbilityData} = {
|
|||
newMove.hasBounced = true;
|
||||
newMove.pranksterBoosted = false;
|
||||
this.add('-ability', target, 'Carefree');
|
||||
this.useMove(newMove, this.effectData.target, source);
|
||||
this.actions.useMove(newMove, this.effectData.target, source);
|
||||
return null;
|
||||
},
|
||||
condition: {
|
||||
|
|
@ -407,7 +407,7 @@ export const Abilities: {[k: string]: ModdedAbilityData} = {
|
|||
newMove.hasBounced = true;
|
||||
newMove.pranksterBoosted = false;
|
||||
this.add('-ability', target, 'Magic Hat');
|
||||
this.useMove(newMove, target, source);
|
||||
this.actions.useMove(newMove, target, source);
|
||||
return null;
|
||||
},
|
||||
onAllyTryHitSide(target, source, move) {
|
||||
|
|
@ -418,7 +418,7 @@ export const Abilities: {[k: string]: ModdedAbilityData} = {
|
|||
newMove.hasBounced = true;
|
||||
newMove.pranksterBoosted = false;
|
||||
this.add('-ability', target, 'Magic Hat');
|
||||
this.useMove(newMove, this.effectData.target, source);
|
||||
this.actions.useMove(newMove, this.effectData.target, source);
|
||||
return null;
|
||||
},
|
||||
condition: {
|
||||
|
|
@ -1869,7 +1869,7 @@ export const Abilities: {[k: string]: ModdedAbilityData} = {
|
|||
if (target.species.id.includes('aggron') && !target.illusion && !target.transformed) {
|
||||
this.boost({atk: 1}, target);
|
||||
if (target.species.name !== 'Aggron') return;
|
||||
this.runMegaEvo(target);
|
||||
this.actions.runMegaEvo(target);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2215,7 +2215,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
this.add('-activate', pokemon, 'move: The Hunt is On!');
|
||||
alreadyAdded = true;
|
||||
}
|
||||
this.runMove('thehuntison', source, this.getTargetLoc(pokemon, source));
|
||||
this.actions.runMove('thehuntison', source, this.getTargetLoc(pokemon, source));
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
@ -2967,12 +2967,12 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
// fruit this move.
|
||||
onHit(target, source) {
|
||||
for (const move of ['Haze', 'Worry Seed', 'Poison Powder', 'Stun Spore', 'Leech Seed']) {
|
||||
this.useMove(move, source);
|
||||
this.actions.useMove(move, source);
|
||||
this.add(`c|${getName('Meicoo')}|That is not the answer - try again!`);
|
||||
}
|
||||
const strgl = this.dex.getActiveMove('Struggle');
|
||||
strgl.basePower = 150;
|
||||
this.useMove(strgl, source);
|
||||
this.actions.useMove(strgl, source);
|
||||
this.add(`c|${getName('Meicoo')}|That is not the answer - try again!`);
|
||||
},
|
||||
secondary: null,
|
||||
|
|
@ -3170,13 +3170,13 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
const hax = this.sample(['slp', 'brn', 'par', 'tox']);
|
||||
target.trySetStatus(hax, source);
|
||||
if (hax === 'slp') {
|
||||
this.useMove('Dream Eater', source);
|
||||
this.actions.useMove('Dream Eater', source);
|
||||
} else if (hax === 'par') {
|
||||
this.useMove('Iron Head', source);
|
||||
this.actions.useMove('Iron Head', source);
|
||||
} else if (hax === 'brn') {
|
||||
this.useMove('Fire Blast', source);
|
||||
this.actions.useMove('Fire Blast', source);
|
||||
} else if (hax === 'tox') {
|
||||
this.useMove('Venoshock', source);
|
||||
this.actions.useMove('Venoshock', source);
|
||||
}
|
||||
},
|
||||
secondary: null,
|
||||
|
|
@ -3393,7 +3393,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
this.add('-anim', source, "Celebrate", target);
|
||||
},
|
||||
onTryHit(target, source) {
|
||||
this.useMove('Substitute', source);
|
||||
this.actions.useMove('Substitute', source);
|
||||
},
|
||||
onHit(target, source) {
|
||||
target.trySetStatus('brn', source);
|
||||
|
|
@ -3958,7 +3958,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
},
|
||||
onHit(target, source) {
|
||||
if (source.species.id === 'charizard') {
|
||||
this.runMegaEvo(source);
|
||||
this.actions.runMegaEvo(source);
|
||||
}
|
||||
},
|
||||
secondary: null,
|
||||
|
|
@ -4687,7 +4687,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
for (let x = 1; x <= randomTurns; x++) {
|
||||
const randomMove = this.sample(supportMoves);
|
||||
supportMoves.splice(supportMoves.indexOf(randomMove), 1);
|
||||
this.useMove(randomMove, target);
|
||||
this.actions.useMove(randomMove, target);
|
||||
successes++;
|
||||
}
|
||||
if (successes === 1) {
|
||||
|
|
@ -4940,19 +4940,19 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
source.m.yukiCosplayForme = this.sample(formes);
|
||||
switch (source.m.yukiCosplayForme) {
|
||||
case 'Cleric':
|
||||
this.useMove("Strength Sap", source);
|
||||
this.actions.useMove("Strength Sap", source);
|
||||
break;
|
||||
case 'Ninja':
|
||||
this.useMove("Confuse Ray", source);
|
||||
this.actions.useMove("Confuse Ray", source);
|
||||
break;
|
||||
case 'Dancer':
|
||||
this.useMove("Feather Dance", source);
|
||||
this.actions.useMove("Feather Dance", source);
|
||||
break;
|
||||
case 'Songstress':
|
||||
this.useMove("Sing", source);
|
||||
this.actions.useMove("Sing", source);
|
||||
break;
|
||||
case 'Jester':
|
||||
this.useMove("Charm", source);
|
||||
this.actions.useMove("Charm", source);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
|
@ -5073,10 +5073,10 @@ export const Moves: {[k: string]: ModdedMoveData} = {
|
|||
},
|
||||
onTry(pokemon, target) {
|
||||
pokemon.addVolatile('bigstormcomingmod');
|
||||
this.useMove("Hurricane", pokemon);
|
||||
this.useMove("Thunder", pokemon);
|
||||
this.useMove("Blizzard", pokemon);
|
||||
this.useMove("Weather Ball", pokemon);
|
||||
this.actions.useMove("Hurricane", pokemon);
|
||||
this.actions.useMove("Thunder", pokemon);
|
||||
this.actions.useMove("Blizzard", pokemon);
|
||||
this.actions.useMove("Weather Ball", pokemon);
|
||||
},
|
||||
secondary: null,
|
||||
target: "normal",
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -568,7 +568,7 @@ export const Moves: {[moveid: string]: MoveData} = {
|
|||
if (!randomMove) {
|
||||
return false;
|
||||
}
|
||||
this.useMove(randomMove, target);
|
||||
this.actions.useMove(randomMove, target);
|
||||
},
|
||||
secondary: null,
|
||||
target: "self",
|
||||
|
|
@ -1218,7 +1218,7 @@ export const Moves: {[moveid: string]: MoveData} = {
|
|||
effectType: 'Move',
|
||||
type: 'Normal',
|
||||
};
|
||||
this.tryMoveHit(target, pokemon, moveData as ActiveMove);
|
||||
this.actions.tryMoveHit(target, pokemon, moveData as ActiveMove);
|
||||
return false;
|
||||
}
|
||||
this.add('-activate', pokemon, 'move: Bide');
|
||||
|
|
@ -2490,7 +2490,7 @@ export const Moves: {[moveid: string]: MoveData} = {
|
|||
if (noCopycat.includes(move.id) || move.isZ || move.isMax) {
|
||||
return false;
|
||||
}
|
||||
this.useMove(move.id, pokemon);
|
||||
this.actions.useMove(move.id, pokemon);
|
||||
},
|
||||
secondary: null,
|
||||
target: "self",
|
||||
|
|
@ -5038,20 +5038,16 @@ export const Moves: {[moveid: string]: MoveData} = {
|
|||
if (target.side.active.length === 1) {
|
||||
return;
|
||||
}
|
||||
for (const ally of target.side.active) {
|
||||
if (ally && this.isAdjacent(target, ally)) {
|
||||
this.damage(ally.baseMaxhp / 16, ally, source, this.dex.getEffect('Flame Burst'));
|
||||
}
|
||||
for (const ally of target.adjacentAllies()) {
|
||||
this.damage(ally.baseMaxhp / 16, ally, source, this.dex.getEffect('Flame Burst'));
|
||||
}
|
||||
},
|
||||
onAfterSubDamage(damage, target, source, move) {
|
||||
if (target.side.active.length === 1) {
|
||||
return;
|
||||
}
|
||||
for (const ally of target.side.active) {
|
||||
if (ally && this.isAdjacent(target, ally)) {
|
||||
this.damage(ally.baseMaxhp / 16, ally, source, this.dex.getEffect('Flame Burst'));
|
||||
}
|
||||
for (const ally of target.adjacentAllies()) {
|
||||
this.damage(ally.baseMaxhp / 16, ally, source, this.dex.getEffect('Flame Burst'));
|
||||
}
|
||||
},
|
||||
secondary: null,
|
||||
|
|
@ -9051,7 +9047,7 @@ export const Moves: {[moveid: string]: MoveData} = {
|
|||
return false;
|
||||
}
|
||||
this.add('-singleturn', target, 'move: Instruct', '[of] ' + source);
|
||||
this.runMove(target.lastMove.id, target, target.lastMoveTargetLoc!);
|
||||
this.actions.runMove(target.lastMove.id, target, target.lastMoveTargetLoc!);
|
||||
},
|
||||
secondary: null,
|
||||
target: "normal",
|
||||
|
|
@ -10003,7 +9999,7 @@ export const Moves: {[moveid: string]: MoveData} = {
|
|||
const newMove = this.dex.getActiveMove(move.id);
|
||||
newMove.hasBounced = true;
|
||||
newMove.pranksterBoosted = this.effectData.pranksterBoosted;
|
||||
this.useMove(newMove, target, source);
|
||||
this.actions.useMove(newMove, target, source);
|
||||
return null;
|
||||
},
|
||||
onAllyTryHitSide(target, source, move) {
|
||||
|
|
@ -10013,7 +10009,7 @@ export const Moves: {[moveid: string]: MoveData} = {
|
|||
const newMove = this.dex.getActiveMove(move.id);
|
||||
newMove.hasBounced = true;
|
||||
newMove.pranksterBoosted = false;
|
||||
this.useMove(newMove, this.effectData.target, source);
|
||||
this.actions.useMove(newMove, this.effectData.target, source);
|
||||
return null;
|
||||
},
|
||||
},
|
||||
|
|
@ -10781,7 +10777,7 @@ export const Moves: {[moveid: string]: MoveData} = {
|
|||
if (move.category === 'Status' || noMeFirst.includes(move.id)) return false;
|
||||
|
||||
pokemon.addVolatile('mefirst');
|
||||
this.useMove(move, pokemon, target);
|
||||
this.actions.useMove(move, pokemon, target);
|
||||
return null;
|
||||
},
|
||||
condition: {
|
||||
|
|
@ -11054,7 +11050,7 @@ export const Moves: {[moveid: string]: MoveData} = {
|
|||
if (!randomMove) {
|
||||
return false;
|
||||
}
|
||||
this.useMove(randomMove, target);
|
||||
this.actions.useMove(randomMove, target);
|
||||
},
|
||||
secondary: null,
|
||||
target: "self",
|
||||
|
|
@ -11288,7 +11284,7 @@ export const Moves: {[moveid: string]: MoveData} = {
|
|||
if (!move?.flags['mirror'] || move.isZ || move.isMax) {
|
||||
return false;
|
||||
}
|
||||
this.useMove(move.id, pokemon, target);
|
||||
this.actions.useMove(move.id, pokemon, target);
|
||||
return null;
|
||||
},
|
||||
secondary: null,
|
||||
|
|
@ -11774,7 +11770,7 @@ export const Moves: {[moveid: string]: MoveData} = {
|
|||
} else if (this.field.isTerrain('psychicterrain')) {
|
||||
move = 'psychic';
|
||||
}
|
||||
this.useMove(move, pokemon, target);
|
||||
this.actions.useMove(move, pokemon, target);
|
||||
return null;
|
||||
},
|
||||
secondary: null,
|
||||
|
|
@ -13455,13 +13451,13 @@ export const Moves: {[moveid: string]: MoveData} = {
|
|||
if (source.canMegaEvo || source.canUltraBurst) {
|
||||
for (const [actionIndex, action] of this.queue.entries()) {
|
||||
if (action.pokemon === source && action.choice === 'megaEvo') {
|
||||
this.runMegaEvo(source);
|
||||
this.actions.runMegaEvo(source);
|
||||
this.queue.list.splice(actionIndex, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.runMove('pursuit', source, this.getTargetLoc(pokemon, source));
|
||||
this.actions.runMove('pursuit', source, this.getTargetLoc(pokemon, source));
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
@ -15747,7 +15743,7 @@ export const Moves: {[moveid: string]: MoveData} = {
|
|||
if (!randomMove) {
|
||||
return false;
|
||||
}
|
||||
this.useMove(randomMove, pokemon);
|
||||
this.actions.useMove(randomMove, pokemon);
|
||||
},
|
||||
secondary: null,
|
||||
target: "self",
|
||||
|
|
@ -15981,7 +15977,7 @@ export const Moves: {[moveid: string]: MoveData} = {
|
|||
}
|
||||
snatchUser.removeVolatile('snatch');
|
||||
this.add('-activate', snatchUser, 'move: Snatch', '[of] ' + source);
|
||||
this.useMove(move.id, snatchUser);
|
||||
this.actions.useMove(move.id, snatchUser);
|
||||
return null;
|
||||
},
|
||||
},
|
||||
|
|
@ -17120,7 +17116,7 @@ export const Moves: {[moveid: string]: MoveData} = {
|
|||
if (target === source || move.flags['authentic'] || move.infiltrates) {
|
||||
return;
|
||||
}
|
||||
let damage = this.getDamage(source, target, move);
|
||||
let damage = this.actions.getDamage(source, target, move);
|
||||
if (!damage && damage !== 0) {
|
||||
this.add('-fail', source);
|
||||
this.attrLastMove('[still]');
|
||||
|
|
@ -17142,7 +17138,7 @@ export const Moves: {[moveid: string]: MoveData} = {
|
|||
this.add('-activate', target, 'move: Substitute', '[damage]');
|
||||
}
|
||||
if (move.recoil) {
|
||||
this.damage(this.calcRecoilDamage(damage, move), source, target, 'recoil');
|
||||
this.damage(this.actions.calcRecoilDamage(damage, move), source, target, 'recoil');
|
||||
}
|
||||
if (move.drain) {
|
||||
this.heal(Math.ceil(damage * move.drain[0] / move.drain[1]), source, target, 'drain');
|
||||
|
|
|
|||
1327
data/scripts.ts
1327
data/scripts.ts
File diff suppressed because it is too large
Load Diff
|
|
@ -10,8 +10,8 @@
|
|||
"sucrase": "^3.15.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"brain.js": "^2.0.0-beta.2",
|
||||
"better-sqlite3": "^7.1.0",
|
||||
"brain.js": "^2.0.0-beta.2",
|
||||
"cloud-env": "^0.2.3",
|
||||
"node-static": "^0.7.11",
|
||||
"nodemailer": "^6.4.6",
|
||||
|
|
|
|||
1757
sim/battle-actions.ts
Normal file
1757
sim/battle-actions.ts
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -234,7 +234,7 @@ export class BattleQueue {
|
|||
// TODO: what actually happens here?
|
||||
if (target) action.targetLoc = this.battle.getTargetLoc(target, action.pokemon);
|
||||
}
|
||||
action.originalTarget = this.battle.getAtLoc(action.pokemon, action.targetLoc);
|
||||
action.originalTarget = action.pokemon.getAtLoc(action.targetLoc);
|
||||
}
|
||||
if (!deferPriority) this.battle.getActionSpeed(action);
|
||||
return actions as any;
|
||||
|
|
|
|||
630
sim/battle.ts
630
sim/battle.ts
|
|
@ -11,15 +11,9 @@ import {PRNG, PRNGSeed} from './prng';
|
|||
import {Side} from './side';
|
||||
import {State} from './state';
|
||||
import {BattleQueue, Action} from './battle-queue';
|
||||
import {BattleActions} from './battle-actions';
|
||||
import {Utils} from '../lib';
|
||||
|
||||
/** A Pokemon that has fainted. */
|
||||
interface FaintedPokemon {
|
||||
target: Pokemon;
|
||||
source: Pokemon | null;
|
||||
effect: Effect | null;
|
||||
}
|
||||
|
||||
interface BattleOptions {
|
||||
format?: Format;
|
||||
formatid: ID;
|
||||
|
|
@ -88,8 +82,13 @@ export class Battle {
|
|||
reportPercentages: boolean;
|
||||
supportCancel: boolean;
|
||||
|
||||
actions: BattleActions;
|
||||
queue: BattleQueue;
|
||||
readonly faintQueue: FaintedPokemon[];
|
||||
readonly faintQueue: {
|
||||
target: Pokemon,
|
||||
source: Pokemon | null,
|
||||
effect: Effect | null,
|
||||
}[];
|
||||
|
||||
readonly log: string[];
|
||||
readonly inputLog: string[];
|
||||
|
|
@ -118,6 +117,7 @@ export class Battle {
|
|||
lastMove: ActiveMove | null;
|
||||
lastSuccessfulMoveThisTurn: ID | null;
|
||||
lastMoveLine: number;
|
||||
/** The last damage dealt by a move in the battle - only used by Gen 1 Counter. */
|
||||
lastDamage: number;
|
||||
abilityOrder: number;
|
||||
|
||||
|
|
@ -125,9 +125,6 @@ export class Battle {
|
|||
|
||||
readonly hints: Set<string>;
|
||||
|
||||
readonly zMoveTable: {[k: string]: string};
|
||||
readonly maxMoveTable: {[k: string]: string};
|
||||
|
||||
readonly NOT_FAIL: '';
|
||||
readonly HIT_SUBSTITUTE: 0;
|
||||
readonly FAIL: false;
|
||||
|
|
@ -148,11 +145,13 @@ export class Battle {
|
|||
this.gen = this.dex.gen;
|
||||
this.ruleTable = this.dex.getRuleTable(format);
|
||||
|
||||
this.zMoveTable = {};
|
||||
this.maxMoveTable = {};
|
||||
this.trunc = this.dex.trunc;
|
||||
this.clampIntRange = Utils.clampIntRange;
|
||||
Object.assign(this, this.dex.data.Scripts);
|
||||
// Object.assign(this, this.dex.data.Scripts);
|
||||
for (const i in this.dex.data.Scripts) {
|
||||
const entry = this.dex.data.Scripts[i];
|
||||
if (typeof entry === 'function') (this as any)[i] = entry;
|
||||
}
|
||||
if (format.battle) Object.assign(this, format.battle);
|
||||
|
||||
this.id = '';
|
||||
|
|
@ -163,8 +162,7 @@ export class Battle {
|
|||
this.gameType = (format.gameType || 'singles');
|
||||
this.field = new Field(this);
|
||||
const isFourPlayer = this.gameType === 'multi' || this.gameType === 'free-for-all';
|
||||
// @ts-ignore
|
||||
this.sides = Array(isFourPlayer ? 4 : 2).fill(null!);
|
||||
this.sides = Array(isFourPlayer ? 4 : 2).fill(null) as any;
|
||||
this.prng = options.prng || new PRNG(options.seed || undefined);
|
||||
this.prngSeed = this.prng.startingSeed.slice() as PRNGSeed;
|
||||
this.rated = options.rated || !!options.rated;
|
||||
|
|
@ -173,6 +171,7 @@ export class Battle {
|
|||
this.supportCancel = false;
|
||||
|
||||
this.queue = new BattleQueue(this);
|
||||
this.actions = new BattleActions(this);
|
||||
this.faintQueue = [];
|
||||
|
||||
this.inputLog = [];
|
||||
|
|
@ -1241,119 +1240,6 @@ export class Battle {
|
|||
return true;
|
||||
}
|
||||
|
||||
switchIn(pokemon: Pokemon, pos: number, sourceEffect: Effect | null = null, isDrag?: boolean) {
|
||||
if (!pokemon || pokemon.isActive) {
|
||||
this.hint("A switch failed because the Pokémon trying to switch in is already in.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const side = pokemon.side;
|
||||
if (pos >= side.active.length) {
|
||||
console.log(this.getDebugLog());
|
||||
throw new Error(`Invalid switch position ${pos} / ${side.active.length}`);
|
||||
}
|
||||
const oldActive = side.active[pos];
|
||||
const unfaintedActive = oldActive?.hp ? oldActive : null;
|
||||
if (unfaintedActive) {
|
||||
oldActive.beingCalledBack = true;
|
||||
let switchCopyFlag = false;
|
||||
if (sourceEffect && (sourceEffect as Move).selfSwitch === 'copyvolatile') {
|
||||
switchCopyFlag = true;
|
||||
}
|
||||
if (!oldActive.skipBeforeSwitchOutEventFlag && !isDrag) {
|
||||
this.runEvent('BeforeSwitchOut', oldActive);
|
||||
if (this.gen >= 5) {
|
||||
this.eachEvent('Update');
|
||||
}
|
||||
}
|
||||
oldActive.skipBeforeSwitchOutEventFlag = false;
|
||||
if (!this.runEvent('SwitchOut', oldActive)) {
|
||||
// Warning: DO NOT interrupt a switch-out if you just want to trap a pokemon.
|
||||
// To trap a pokemon and prevent it from switching out, (e.g. Mean Look, Magnet Pull)
|
||||
// use the 'trapped' flag instead.
|
||||
|
||||
// Note: Nothing in the real games can interrupt a switch-out (except Pursuit KOing,
|
||||
// which is handled elsewhere); this is just for custom formats.
|
||||
return false;
|
||||
}
|
||||
if (!oldActive.hp) {
|
||||
// a pokemon fainted from Pursuit before it could switch
|
||||
return 'pursuitfaint';
|
||||
}
|
||||
|
||||
// will definitely switch out at this point
|
||||
|
||||
oldActive.illusion = null;
|
||||
this.singleEvent('End', oldActive.getAbility(), oldActive.abilityData, oldActive);
|
||||
|
||||
// if a pokemon is forced out by Whirlwind/etc or Eject Button/Pack, it can't use its chosen move
|
||||
this.queue.cancelAction(oldActive);
|
||||
|
||||
let newMove = null;
|
||||
if (this.gen === 4 && sourceEffect) {
|
||||
newMove = oldActive.lastMove;
|
||||
}
|
||||
if (switchCopyFlag) {
|
||||
pokemon.copyVolatileFrom(oldActive);
|
||||
}
|
||||
if (newMove) pokemon.lastMove = newMove;
|
||||
oldActive.clearVolatile();
|
||||
}
|
||||
if (oldActive) {
|
||||
oldActive.isActive = false;
|
||||
oldActive.isStarted = false;
|
||||
oldActive.usedItemThisTurn = false;
|
||||
oldActive.position = pokemon.position;
|
||||
pokemon.position = pos;
|
||||
side.pokemon[pokemon.position] = pokemon;
|
||||
side.pokemon[oldActive.position] = oldActive;
|
||||
}
|
||||
pokemon.isActive = true;
|
||||
side.active[pos] = pokemon;
|
||||
pokemon.activeTurns = 0;
|
||||
pokemon.activeMoveActions = 0;
|
||||
for (const moveSlot of pokemon.moveSlots) {
|
||||
moveSlot.used = false;
|
||||
}
|
||||
this.runEvent('BeforeSwitchIn', pokemon);
|
||||
this.add(isDrag ? 'drag' : 'switch', pokemon, pokemon.getDetails);
|
||||
pokemon.abilityOrder = this.abilityOrder++;
|
||||
if (isDrag && this.gen === 2) pokemon.draggedIn = this.turn;
|
||||
if (sourceEffect) this.log[this.log.length - 1] += `|[from]${sourceEffect.fullname}`;
|
||||
pokemon.previouslySwitchedIn++;
|
||||
|
||||
if (isDrag && this.gen >= 5) {
|
||||
// runSwitch happens immediately so that Mold Breaker can make hazards bypass Clear Body and Levitate
|
||||
this.singleEvent('PreStart', pokemon.getAbility(), pokemon.abilityData, pokemon);
|
||||
this.runSwitch(pokemon);
|
||||
} else {
|
||||
this.queue.insertChoice({choice: 'runUnnerve', pokemon});
|
||||
this.queue.insertChoice({choice: 'runSwitch', pokemon});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
runSwitch(pokemon: Pokemon) {
|
||||
this.runEvent('Swap', pokemon);
|
||||
this.runEvent('SwitchIn', pokemon);
|
||||
if (this.gen <= 2 && !pokemon.side.faintedThisTurn && pokemon.draggedIn !== this.turn) {
|
||||
this.runEvent('AfterSwitchInSelf', pokemon);
|
||||
}
|
||||
if (!pokemon.hp) return false;
|
||||
pokemon.isStarted = true;
|
||||
if (!pokemon.fainted) {
|
||||
this.singleEvent('Start', pokemon.getAbility(), pokemon.abilityData, pokemon);
|
||||
this.singleEvent('Start', pokemon.getItem(), pokemon.itemData, pokemon);
|
||||
}
|
||||
if (this.gen === 4) {
|
||||
for (const foeActive of pokemon.side.foe.active) {
|
||||
foeActive.removeVolatile('substitutebroken');
|
||||
}
|
||||
}
|
||||
pokemon.draggedIn = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
canSwitch(side: Side) {
|
||||
return this.possibleSwitches(side).length;
|
||||
}
|
||||
|
|
@ -1374,20 +1260,6 @@ export class Battle {
|
|||
return canSwitchIn;
|
||||
}
|
||||
|
||||
dragIn(side: Side, pos: number) {
|
||||
const pokemon = this.getRandomSwitchable(side);
|
||||
if (!pokemon || pokemon.isActive) return false;
|
||||
const oldActive = side.active[pos];
|
||||
if (!oldActive) throw new Error(`nothing to drag out`);
|
||||
if (!oldActive.hp) return false;
|
||||
|
||||
if (!this.runEvent('DragOut', oldActive)) {
|
||||
return false;
|
||||
}
|
||||
if (!this.switchIn(pokemon, pos, null, true)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
swapPosition(pokemon: Pokemon, slot: number, attributes?: string) {
|
||||
if (slot >= pokemon.side.active.length) {
|
||||
throw new Error("Invalid swap position");
|
||||
|
|
@ -1525,7 +1397,7 @@ export class Battle {
|
|||
if (this.gameType === 'triples' && !this.sides.filter(side => side.pokemonLeft > 1).length) {
|
||||
// If both sides have one Pokemon left in triples and they are not adjacent, they are both moved to the center.
|
||||
const actives = this.getAllActive();
|
||||
if (actives.length > 1 && !this.isAdjacent(actives[0], actives[1])) {
|
||||
if (actives.length > 1 && !actives[0].isAdjacent(actives[1])) {
|
||||
this.swapPosition(actives[0], 1, '[silent]');
|
||||
this.swapPosition(actives[1], 1, '[silent]');
|
||||
this.add('-center');
|
||||
|
|
@ -2037,241 +1909,6 @@ export class Battle {
|
|||
return this.dex.getMove(move).category || 'Physical';
|
||||
}
|
||||
|
||||
/**
|
||||
* 0 is a success dealing 0 damage, such as from False Swipe at 1 HP.
|
||||
*
|
||||
* Normal PS return value rules apply:
|
||||
* undefined = success, null = silent failure, false = loud failure
|
||||
*/
|
||||
getDamage(
|
||||
pokemon: Pokemon, target: Pokemon, move: string | number | ActiveMove,
|
||||
suppressMessages = false
|
||||
): number | undefined | null | false {
|
||||
if (typeof move === 'string') move = this.dex.getActiveMove(move);
|
||||
|
||||
if (typeof move === 'number') {
|
||||
const basePower = move;
|
||||
move = new Dex.Move({
|
||||
basePower,
|
||||
type: '???',
|
||||
category: 'Physical',
|
||||
willCrit: false,
|
||||
}) as ActiveMove;
|
||||
move.hit = 0;
|
||||
}
|
||||
|
||||
if (!move.ignoreImmunity || (move.ignoreImmunity !== true && !move.ignoreImmunity[move.type])) {
|
||||
if (!target.runImmunity(move.type, !suppressMessages)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (move.ohko) return target.maxhp;
|
||||
if (move.damageCallback) return move.damageCallback.call(this, pokemon, target);
|
||||
if (move.damage === 'level') {
|
||||
return pokemon.level;
|
||||
} else if (move.damage) {
|
||||
return move.damage;
|
||||
}
|
||||
|
||||
const category = this.getCategory(move);
|
||||
const defensiveCategory = move.defensiveCategory || category;
|
||||
|
||||
let basePower: number | false | null = move.basePower;
|
||||
if (move.basePowerCallback) {
|
||||
basePower = move.basePowerCallback.call(this, pokemon, target, move);
|
||||
}
|
||||
if (!basePower) return basePower === 0 ? undefined : basePower;
|
||||
basePower = this.clampIntRange(basePower, 1);
|
||||
|
||||
let critMult;
|
||||
let critRatio = this.runEvent('ModifyCritRatio', pokemon, target, move, move.critRatio || 0);
|
||||
if (this.gen <= 5) {
|
||||
critRatio = this.clampIntRange(critRatio, 0, 5);
|
||||
critMult = [0, 16, 8, 4, 3, 2];
|
||||
} else {
|
||||
critRatio = this.clampIntRange(critRatio, 0, 4);
|
||||
if (this.gen === 6) {
|
||||
critMult = [0, 16, 8, 2, 1];
|
||||
} else {
|
||||
critMult = [0, 24, 8, 2, 1];
|
||||
}
|
||||
}
|
||||
|
||||
const moveHit = target.getMoveHitData(move);
|
||||
moveHit.crit = move.willCrit || false;
|
||||
if (move.willCrit === undefined) {
|
||||
if (critRatio) {
|
||||
moveHit.crit = this.randomChance(1, critMult[critRatio]);
|
||||
}
|
||||
}
|
||||
|
||||
if (moveHit.crit) {
|
||||
moveHit.crit = this.runEvent('CriticalHit', target, null, move);
|
||||
}
|
||||
|
||||
// happens after crit calculation
|
||||
basePower = this.runEvent('BasePower', pokemon, target, move, basePower, true);
|
||||
|
||||
if (!basePower) return 0;
|
||||
basePower = this.clampIntRange(basePower, 1);
|
||||
|
||||
const level = pokemon.level;
|
||||
|
||||
const attacker = pokemon;
|
||||
const defender = target;
|
||||
let attackStat: StatNameExceptHP = category === 'Physical' ? 'atk' : 'spa';
|
||||
const defenseStat: StatNameExceptHP = defensiveCategory === 'Physical' ? 'def' : 'spd';
|
||||
if (move.useSourceDefensiveAsOffensive) {
|
||||
attackStat = defenseStat;
|
||||
// Body press really wants to use the def stat,
|
||||
// so it switches stats to compensate for Wonder Room.
|
||||
// Of course, the game thus miscalculates the boosts...
|
||||
if ('wonderroom' in this.field.pseudoWeather) {
|
||||
if (attackStat === 'def') {
|
||||
attackStat = 'spd';
|
||||
} else if (attackStat === 'spd') {
|
||||
attackStat = 'def';
|
||||
}
|
||||
if (attacker.boosts['def'] || attacker.boosts['spd']) {
|
||||
this.hint("Body Press uses Sp. Def boosts when Wonder Room is active.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const statTable = {atk: 'Atk', def: 'Def', spa: 'SpA', spd: 'SpD', spe: 'Spe'};
|
||||
let attack;
|
||||
let defense;
|
||||
|
||||
let atkBoosts = move.useTargetOffensive ? defender.boosts[attackStat] : attacker.boosts[attackStat];
|
||||
let defBoosts = defender.boosts[defenseStat];
|
||||
|
||||
let ignoreNegativeOffensive = !!move.ignoreNegativeOffensive;
|
||||
let ignorePositiveDefensive = !!move.ignorePositiveDefensive;
|
||||
|
||||
if (moveHit.crit) {
|
||||
ignoreNegativeOffensive = true;
|
||||
ignorePositiveDefensive = true;
|
||||
}
|
||||
const ignoreOffensive = !!(move.ignoreOffensive || (ignoreNegativeOffensive && atkBoosts < 0));
|
||||
const ignoreDefensive = !!(move.ignoreDefensive || (ignorePositiveDefensive && defBoosts > 0));
|
||||
|
||||
if (ignoreOffensive) {
|
||||
this.debug('Negating (sp)atk boost/penalty.');
|
||||
atkBoosts = 0;
|
||||
}
|
||||
if (ignoreDefensive) {
|
||||
this.debug('Negating (sp)def boost/penalty.');
|
||||
defBoosts = 0;
|
||||
}
|
||||
|
||||
if (move.useTargetOffensive) {
|
||||
attack = defender.calculateStat(attackStat, atkBoosts);
|
||||
} else {
|
||||
attack = attacker.calculateStat(attackStat, atkBoosts);
|
||||
}
|
||||
|
||||
attackStat = (category === 'Physical' ? 'atk' : 'spa');
|
||||
defense = defender.calculateStat(defenseStat, defBoosts);
|
||||
|
||||
// Apply Stat Modifiers
|
||||
attack = this.runEvent('Modify' + statTable[attackStat], attacker, defender, move, attack);
|
||||
defense = this.runEvent('Modify' + statTable[defenseStat], defender, attacker, move, defense);
|
||||
|
||||
if (this.gen <= 4 && ['explosion', 'selfdestruct'].includes(move.id) && defenseStat === 'def') {
|
||||
defense = this.clampIntRange(Math.floor(defense / 2), 1);
|
||||
}
|
||||
|
||||
const tr = this.trunc;
|
||||
|
||||
// int(int(int(2 * L / 5 + 2) * A * P / D) / 50);
|
||||
const baseDamage = tr(tr(tr(tr(2 * level / 5 + 2) * basePower * attack) / defense) / 50);
|
||||
|
||||
// Calculate damage modifiers separately (order differs between generations)
|
||||
return this.modifyDamage(baseDamage, pokemon, target, move, suppressMessages);
|
||||
}
|
||||
|
||||
modifyDamage(
|
||||
baseDamage: number, pokemon: Pokemon, target: Pokemon, move: ActiveMove, suppressMessages = false
|
||||
) {
|
||||
const tr = this.trunc;
|
||||
if (!move.type) move.type = '???';
|
||||
const type = move.type;
|
||||
|
||||
baseDamage += 2;
|
||||
|
||||
// multi-target modifier (doubles only)
|
||||
if (move.spreadHit) {
|
||||
const spreadModifier = move.spreadModifier || (this.gameType === 'free-for-all' ? 0.5 : 0.75);
|
||||
this.debug('Spread modifier: ' + spreadModifier);
|
||||
baseDamage = this.modify(baseDamage, spreadModifier);
|
||||
}
|
||||
|
||||
// weather modifier
|
||||
baseDamage = this.runEvent('WeatherModifyDamage', pokemon, target, move, baseDamage);
|
||||
|
||||
// crit - not a modifier
|
||||
const isCrit = target.getMoveHitData(move).crit;
|
||||
if (isCrit) {
|
||||
baseDamage = tr(baseDamage * (move.critModifier || (this.gen >= 6 ? 1.5 : 2)));
|
||||
}
|
||||
|
||||
// random factor - also not a modifier
|
||||
baseDamage = this.randomizer(baseDamage);
|
||||
|
||||
// STAB
|
||||
if (move.forceSTAB || (type !== '???' && pokemon.hasType(type))) {
|
||||
// The "???" type never gets STAB
|
||||
// Not even if you Roost in Gen 4 and somehow manage to use
|
||||
// Struggle in the same turn.
|
||||
// (On second thought, it might be easier to get a MissingNo.)
|
||||
baseDamage = this.modify(baseDamage, move.stab || 1.5);
|
||||
}
|
||||
// types
|
||||
let typeMod = target.runEffectiveness(move);
|
||||
typeMod = this.clampIntRange(typeMod, -6, 6);
|
||||
target.getMoveHitData(move).typeMod = typeMod;
|
||||
if (typeMod > 0) {
|
||||
if (!suppressMessages) this.add('-supereffective', target);
|
||||
|
||||
for (let i = 0; i < typeMod; i++) {
|
||||
baseDamage *= 2;
|
||||
}
|
||||
}
|
||||
if (typeMod < 0) {
|
||||
if (!suppressMessages) this.add('-resisted', target);
|
||||
|
||||
for (let i = 0; i > typeMod; i--) {
|
||||
baseDamage = tr(baseDamage / 2);
|
||||
}
|
||||
}
|
||||
|
||||
if (isCrit && !suppressMessages) this.add('-crit', target);
|
||||
|
||||
if (pokemon.status === 'brn' && move.category === 'Physical' && !pokemon.hasAbility('guts')) {
|
||||
if (this.gen < 6 || move.id !== 'facade') {
|
||||
baseDamage = this.modify(baseDamage, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
// Generation 5, but nothing later, sets damage to 1 before the final damage modifiers
|
||||
if (this.gen === 5 && !baseDamage) baseDamage = 1;
|
||||
|
||||
// Final modifier. Modifiers that modify damage after min damage check, such as Life Orb.
|
||||
baseDamage = this.runEvent('ModifyDamage', pokemon, target, move, baseDamage);
|
||||
|
||||
if (move.isZOrMaxPowered && target.getMoveHitData(move).zBrokeProtect) {
|
||||
baseDamage = this.modify(baseDamage, 0.25);
|
||||
this.add('-zbroken', target);
|
||||
}
|
||||
|
||||
// Generation 6-7 moves the check for minimum 1 damage after the final modifier...
|
||||
if (this.gen !== 5 && !baseDamage) return 1;
|
||||
|
||||
// ...but 16-bit truncation happens even later, and can truncate to 0
|
||||
return tr(baseDamage, 16);
|
||||
}
|
||||
|
||||
randomizer(baseDamage: number) {
|
||||
const tr = this.trunc;
|
||||
return tr(tr(baseDamage * (100 - this.random(16))) / 100);
|
||||
|
|
@ -2319,14 +1956,6 @@ export class Battle {
|
|||
return this.validTargetLoc(this.getTargetLoc(target, source), source, targetType);
|
||||
}
|
||||
|
||||
getAtLoc(pokemon: Pokemon, targetLoc: number) {
|
||||
if (targetLoc > 0) {
|
||||
return pokemon.side.foe.active[targetLoc - 1];
|
||||
} else {
|
||||
return pokemon.side.active[-targetLoc - 1];
|
||||
}
|
||||
}
|
||||
|
||||
getTarget(pokemon: Pokemon, move: string | Move, targetLoc: number, originalTarget?: Pokemon) {
|
||||
move = this.dex.getMove(move);
|
||||
|
||||
|
|
@ -2341,7 +1970,7 @@ export class Battle {
|
|||
|
||||
// banning Dragon Darts from directly targeting itself is done in side.ts, but
|
||||
// Dragon Darts can target itself if Ally Switch is used afterwards
|
||||
if (move.smartTarget) return this.getAtLoc(pokemon, targetLoc);
|
||||
if (move.smartTarget) return pokemon.getAtLoc(targetLoc);
|
||||
|
||||
// Fails if the target is the user and the move can't target its own position
|
||||
if (['adjacentAlly', 'any', 'normal'].includes(move.target) && targetLoc === -(pokemon.position + 1) &&
|
||||
|
|
@ -2349,7 +1978,7 @@ export class Battle {
|
|||
return move.isFutureMove ? pokemon : null;
|
||||
}
|
||||
if (move.target !== 'randomNormal' && this.validTargetLoc(targetLoc, pokemon, move.target)) {
|
||||
const target = this.getAtLoc(pokemon, targetLoc);
|
||||
const target = pokemon.getAtLoc(targetLoc);
|
||||
if (target?.fainted && target.side === pokemon.side) {
|
||||
// Target is a fainted ally: attack shouldn't retarget
|
||||
return target;
|
||||
|
|
@ -2497,7 +2126,7 @@ export class Battle {
|
|||
if (action.choice === 'move') {
|
||||
let move = action.move;
|
||||
if (action.zmove) {
|
||||
const zMoveName = this.getZMove(action.move, action.pokemon, true);
|
||||
const zMoveName = this.actions.getZMove(action.move, action.pokemon, true);
|
||||
if (zMoveName) {
|
||||
const zMove = this.dex.getActiveMove(zMoveName);
|
||||
if (zMove.exists && zMove.isZ) {
|
||||
|
|
@ -2506,9 +2135,9 @@ export class Battle {
|
|||
}
|
||||
}
|
||||
if (action.maxMove) {
|
||||
const maxMoveName = this.getMaxMove(action.maxMove, action.pokemon);
|
||||
const maxMoveName = this.actions.getMaxMove(action.maxMove, action.pokemon);
|
||||
if (maxMoveName) {
|
||||
const maxMove = this.getActiveMaxMove(action.move, action.pokemon);
|
||||
const maxMove = this.actions.getActiveMaxMove(action.move, action.pokemon);
|
||||
if (maxMove.exists && maxMove.isMax) {
|
||||
move = maxMove;
|
||||
}
|
||||
|
|
@ -2557,7 +2186,7 @@ export class Battle {
|
|||
this.add('start');
|
||||
for (const side of this.sides) {
|
||||
for (let pos = 0; pos < side.active.length; pos++) {
|
||||
this.switchIn(side.pokemon[pos], pos);
|
||||
this.actions.switchIn(side.pokemon[pos], pos);
|
||||
}
|
||||
}
|
||||
for (const pokemon of this.getAllPokemon()) {
|
||||
|
|
@ -2570,11 +2199,11 @@ export class Battle {
|
|||
case 'move':
|
||||
if (!action.pokemon.isActive) return false;
|
||||
if (action.pokemon.fainted) return false;
|
||||
this.runMove(action.move, action.pokemon, action.targetLoc, action.sourceEffect,
|
||||
this.actions.runMove(action.move, action.pokemon, action.targetLoc, action.sourceEffect,
|
||||
action.zmove, undefined, action.maxMove, action.originalTarget);
|
||||
break;
|
||||
case 'megaEvo':
|
||||
this.runMegaEvo(action.pokemon);
|
||||
this.actions.runMegaEvo(action.pokemon);
|
||||
break;
|
||||
case 'runDynamax':
|
||||
action.pokemon.addVolatile('dynamax');
|
||||
|
|
@ -2610,7 +2239,7 @@ export class Battle {
|
|||
if (action.choice === 'switch' && action.pokemon.status && this.dex.data.Abilities.naturalcure) {
|
||||
this.singleEvent('CheckShow', this.dex.getAbility('naturalcure'), null, action.pokemon);
|
||||
}
|
||||
if (this.switchIn(action.target, action.pokemon.position, action.sourceEffect) === 'pursuitfaint') {
|
||||
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
|
||||
|
|
@ -2629,7 +2258,7 @@ export class Battle {
|
|||
this.singleEvent('PreStart', action.pokemon.getAbility(), action.pokemon.abilityData, action.pokemon);
|
||||
break;
|
||||
case 'runSwitch':
|
||||
this.runSwitch(action.pokemon);
|
||||
this.actions.runSwitch(action.pokemon);
|
||||
break;
|
||||
case 'runPrimal':
|
||||
if (!action.pokemon.transformed) {
|
||||
|
|
@ -2660,7 +2289,7 @@ export class Battle {
|
|||
for (const side of this.sides) {
|
||||
for (const pokemon of side.active) {
|
||||
if (pokemon.forceSwitchFlag) {
|
||||
if (pokemon.hp) this.dragIn(pokemon.side, pokemon.position);
|
||||
if (pokemon.hp) this.actions.dragIn(pokemon.side, pokemon.position);
|
||||
pokemon.forceSwitchFlag = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -3077,208 +2706,10 @@ export class Battle {
|
|||
}
|
||||
}
|
||||
|
||||
combineResults<T extends number | boolean | null | '' | undefined,
|
||||
U extends number | boolean | null | '' | undefined>(
|
||||
left: T, right: U
|
||||
): T | U {
|
||||
const NOT_FAILURE = 'string';
|
||||
const NULL = 'object';
|
||||
const resultsPriorities = ['undefined', NOT_FAILURE, NULL, 'boolean', 'number'];
|
||||
if (resultsPriorities.indexOf(typeof left) > resultsPriorities.indexOf(typeof right)) {
|
||||
return left;
|
||||
} else if (left && !right && right !== 0) {
|
||||
return left;
|
||||
} else if (typeof left === 'number' && typeof right === 'number') {
|
||||
return (left + right) as T;
|
||||
} else {
|
||||
return right;
|
||||
}
|
||||
}
|
||||
|
||||
getSide(sideid: SideID): Side {
|
||||
return this.sides[parseInt(sideid[1]) - 1];
|
||||
}
|
||||
|
||||
afterMoveSecondaryEvent(targets: Pokemon[], pokemon: Pokemon, move: ActiveMove): undefined {
|
||||
throw new UnimplementedError('afterMoveSecondary');
|
||||
}
|
||||
|
||||
calcRecoilDamage(damageDealt: number, move: Move): number {
|
||||
throw new UnimplementedError('calcRecoilDamage');
|
||||
}
|
||||
|
||||
canMegaEvo(pokemon: Pokemon): string | null | undefined {
|
||||
throw new UnimplementedError('canMegaEvo');
|
||||
}
|
||||
|
||||
canUltraBurst(pokemon: Pokemon): string | null {
|
||||
throw new UnimplementedError('canUltraBurst');
|
||||
}
|
||||
|
||||
canZMove(pokemon: Pokemon): (AnyObject | null)[] | void {
|
||||
throw new UnimplementedError('canZMove');
|
||||
}
|
||||
|
||||
forceSwitch(
|
||||
damage: SpreadMoveDamage, targets: SpreadMoveTargets, source: Pokemon, move: ActiveMove,
|
||||
moveData: ActiveMove, isSecondary?: boolean, isSelf?: boolean
|
||||
): SpreadMoveDamage {
|
||||
throw new UnimplementedError('forceSwitch');
|
||||
}
|
||||
|
||||
getActiveMaxMove(move: Move, pokemon: Pokemon): ActiveMove {
|
||||
throw new UnimplementedError('getActiveMaxMove');
|
||||
}
|
||||
|
||||
getActiveZMove(move: Move, pokemon: Pokemon): ActiveMove {
|
||||
throw new UnimplementedError('getActiveZMove');
|
||||
}
|
||||
|
||||
getMaxMove(move: Move, pokemon: Pokemon): Move | undefined {
|
||||
throw new UnimplementedError('getMaxMove');
|
||||
}
|
||||
|
||||
getSpreadDamage(
|
||||
damage: SpreadMoveDamage, targets: SpreadMoveTargets, source: Pokemon, move: ActiveMove,
|
||||
moveData: ActiveMove, isSecondary?: boolean, isSelf?: boolean
|
||||
): SpreadMoveDamage {
|
||||
throw new UnimplementedError('getSpreadDamage');
|
||||
}
|
||||
|
||||
getZMove(move: Move, pokemon: Pokemon, skipChecks?: boolean): string | undefined {
|
||||
throw new UnimplementedError('getZMove');
|
||||
}
|
||||
|
||||
hitStepAccuracy(targets: Pokemon[], pokemon: Pokemon, move: ActiveMove): boolean[] {
|
||||
throw new UnimplementedError('hitStepAccuracy');
|
||||
}
|
||||
|
||||
hitStepBreakProtect(targets: Pokemon[], pokemon: Pokemon, move: ActiveMove): undefined {
|
||||
throw new UnimplementedError('hitStepBreakProtect');
|
||||
}
|
||||
|
||||
hitStepMoveHitLoop(targets: Pokemon[], pokemon: Pokemon, move: ActiveMove): SpreadMoveDamage {
|
||||
throw new UnimplementedError('hitStepMoveHitLoop');
|
||||
}
|
||||
|
||||
hitStepTryImmunity(targets: Pokemon[], pokemon: Pokemon, move: ActiveMove): boolean[] {
|
||||
throw new UnimplementedError('hitStepTryImmunityEvent');
|
||||
}
|
||||
|
||||
hitStepStealBoosts(targets: Pokemon[], pokemon: Pokemon, move: ActiveMove): undefined {
|
||||
throw new UnimplementedError('hitStepStealBoosts');
|
||||
}
|
||||
|
||||
hitStepTryHitEvent(targets: Pokemon[], pokemon: Pokemon, move: ActiveMove): (boolean | '')[] {
|
||||
throw new UnimplementedError('hitStepTryHitEvent');
|
||||
}
|
||||
|
||||
hitStepInvulnerabilityEvent(targets: Pokemon[], pokemon: Pokemon, move: ActiveMove): boolean[] {
|
||||
throw new UnimplementedError('hitStepInvulnerabilityEvent ');
|
||||
}
|
||||
|
||||
hitStepTypeImmunity(targets: Pokemon[], pokemon: Pokemon, move: ActiveMove): boolean[] {
|
||||
throw new UnimplementedError('hitStepTypeImmunity');
|
||||
}
|
||||
|
||||
isAdjacent(pokemon1: Pokemon, pokemon2: Pokemon): boolean {
|
||||
throw new UnimplementedError('isAdjacent');
|
||||
}
|
||||
|
||||
moveHit(
|
||||
target: Pokemon | null, pokemon: Pokemon, move: ActiveMove,
|
||||
moveData?: Dex.HitEffect,
|
||||
isSecondary?: boolean, isSelf?: boolean
|
||||
): number | undefined | false {
|
||||
throw new UnimplementedError('moveHit');
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is also used for Ultra Bursting.
|
||||
* Takes the Pokemon that will Mega Evolve or Ultra Burst as a parameter.
|
||||
* Returns false if the Pokemon cannot Mega Evolve or Ultra Burst, otherwise returns true.
|
||||
*/
|
||||
runMegaEvo(pokemon: Pokemon): boolean {
|
||||
throw new UnimplementedError('runMegaEvo');
|
||||
}
|
||||
|
||||
runMove(
|
||||
moveOrMoveName: Move | string, pokemon: Pokemon, targetLoc: number,
|
||||
sourceEffect?: Effect | null, zMove?: string, externalMove?: boolean,
|
||||
maxMove?: string, originalTarget?: Pokemon
|
||||
) {
|
||||
throw new UnimplementedError('runMove');
|
||||
}
|
||||
|
||||
runMoveEffects(
|
||||
damage: SpreadMoveDamage, targets: SpreadMoveTargets, source: Pokemon, move: ActiveMove,
|
||||
moveData: ActiveMove, isSecondary?: boolean, isSelf?: boolean
|
||||
): SpreadMoveDamage {
|
||||
throw new UnimplementedError('runMoveEffects');
|
||||
}
|
||||
|
||||
runZPower(move: ActiveMove, pokemon: Pokemon) {
|
||||
throw new UnimplementedError('runZPower');
|
||||
}
|
||||
|
||||
secondaries(
|
||||
targets: SpreadMoveTargets, source: Pokemon, move: ActiveMove, moveData: ActiveMove,
|
||||
isSelf?: boolean
|
||||
): SpreadMoveDamage {
|
||||
throw new UnimplementedError('secondaries');
|
||||
}
|
||||
|
||||
selfDrops(
|
||||
targets: SpreadMoveTargets, source: Pokemon, move: ActiveMove, moveData: ActiveMove,
|
||||
isSecondary?: boolean
|
||||
): SpreadMoveDamage {
|
||||
throw new UnimplementedError('selfDrops');
|
||||
}
|
||||
|
||||
spreadMoveHit(
|
||||
targets: SpreadMoveTargets, pokemon: Pokemon, move: ActiveMove, moveData?: ActiveMove,
|
||||
isSecondary?: boolean, isSelf?: boolean
|
||||
): [SpreadMoveDamage, SpreadMoveTargets] {
|
||||
throw new UnimplementedError('spreadMoveHit');
|
||||
}
|
||||
|
||||
targetTypeChoices(targetType: string): boolean {
|
||||
throw new UnimplementedError('targetTypeChoices');
|
||||
}
|
||||
|
||||
tryMoveHit(target: Pokemon, pokemon: Pokemon, move: ActiveMove): number | undefined | false | '' {
|
||||
throw new UnimplementedError('tryMoveHit');
|
||||
}
|
||||
|
||||
tryPrimaryHitEvent(
|
||||
damage: SpreadMoveDamage, targets: SpreadMoveTargets, pokemon: Pokemon, move: ActiveMove,
|
||||
moveData: ActiveMove, isSecondary?: boolean
|
||||
): SpreadMoveDamage {
|
||||
throw new UnimplementedError('tryPrimaryHitEvent');
|
||||
}
|
||||
|
||||
trySpreadMoveHit(targets: Pokemon[], pokemon: Pokemon, move: ActiveMove, notActive?: boolean): boolean {
|
||||
throw new UnimplementedError('trySpreadMoveHit');
|
||||
}
|
||||
|
||||
useMove(
|
||||
move: string | Move, pokemon: Pokemon, target?: Pokemon | null,
|
||||
sourceEffect?: Effect | null, zMove?: string, maxMove?: string
|
||||
): boolean {
|
||||
throw new UnimplementedError('useMove');
|
||||
}
|
||||
|
||||
/**
|
||||
* target = undefined: automatically resolve target
|
||||
* target = null: no target (move will fail)
|
||||
*/
|
||||
useMoveInner(
|
||||
move: string | Move, pokemon: Pokemon, target?: Pokemon | null,
|
||||
sourceEffect?: Effect | null, zMove?: string, maxMove?: string
|
||||
): boolean {
|
||||
throw new UnimplementedError('useMoveInner');
|
||||
}
|
||||
|
||||
destroy() {
|
||||
// deallocate ourself
|
||||
|
||||
|
|
@ -3302,10 +2733,3 @@ export class Battle {
|
|||
(this as any).log = [];
|
||||
}
|
||||
}
|
||||
|
||||
class UnimplementedError extends Error {
|
||||
constructor(name: string) {
|
||||
super(`The ${name} function needs to be implemented in scripts.js or the battle format.`);
|
||||
this.name = 'UnimplementedError';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -205,6 +205,7 @@ export class Format extends BasicEffect implements Readonly<BasicEffect> {
|
|||
readonly pokemon?: ModdedBattlePokemon;
|
||||
readonly queue?: ModdedBattleQueue;
|
||||
readonly field?: ModdedField;
|
||||
readonly actions?: ModdedBattleActions;
|
||||
readonly cannotMega?: string[];
|
||||
readonly challengeShow?: boolean;
|
||||
readonly searchShow?: boolean;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
type Battle = import('./battle').Battle;
|
||||
type BattleQueue = import('./battle-queue').BattleQueue;
|
||||
type BattleActions = import('./battle-actions').BattleActions;
|
||||
type Field = import('./field').Field;
|
||||
type Action = import('./battle-queue').Action;
|
||||
type MoveAction = import('./battle-queue').MoveAction;
|
||||
|
|
@ -229,86 +230,100 @@ interface DynamaxOptions {
|
|||
|
||||
interface BattleScriptsData {
|
||||
gen: number;
|
||||
zMoveTable?: {[k: string]: string};
|
||||
maxMoveTable?: {[k: string]: string};
|
||||
afterMoveSecondaryEvent?: (this: Battle, targets: Pokemon[], pokemon: Pokemon, move: ActiveMove) => undefined;
|
||||
calcRecoilDamage?: (this: Battle, damageDealt: number, move: Move) => number;
|
||||
canMegaEvo?: (this: Battle, pokemon: Pokemon) => string | undefined | null;
|
||||
canUltraBurst?: (this: Battle, pokemon: Pokemon) => string | null;
|
||||
canZMove?: (this: Battle, pokemon: Pokemon) => ZMoveOptions | void;
|
||||
canDynamax?: (this: Battle, pokemon: Pokemon, skipChecks?: boolean) => DynamaxOptions | void;
|
||||
}
|
||||
|
||||
interface ModdedBattleActions {
|
||||
inherit?: true;
|
||||
afterMoveSecondaryEvent?: (this: BattleActions, targets: Pokemon[], pokemon: Pokemon, move: ActiveMove) => undefined;
|
||||
calcRecoilDamage?: (this: BattleActions, damageDealt: number, move: Move) => number;
|
||||
canMegaEvo?: (this: BattleActions, pokemon: Pokemon) => string | undefined | null;
|
||||
canUltraBurst?: (this: BattleActions, pokemon: Pokemon) => string | null;
|
||||
canZMove?: (this: BattleActions, pokemon: Pokemon) => ZMoveOptions | void;
|
||||
canDynamax?: (this: BattleActions, pokemon: Pokemon, skipChecks?: boolean) => DynamaxOptions | void;
|
||||
forceSwitch?: (
|
||||
this: Battle, damage: SpreadMoveDamage, targets: SpreadMoveTargets, source: Pokemon,
|
||||
this: BattleActions, damage: SpreadMoveDamage, targets: SpreadMoveTargets, source: Pokemon,
|
||||
move: ActiveMove, moveData: ActiveMove, isSecondary?: boolean, isSelf?: boolean
|
||||
) => SpreadMoveDamage;
|
||||
getActiveMaxMove?: (this: Battle, move: Move, pokemon: Pokemon) => ActiveMove;
|
||||
getActiveZMove?: (this: Battle, move: Move, pokemon: Pokemon) => ActiveMove;
|
||||
getMaxMove?: (this: Battle, move: Move, pokemon: Pokemon) => Move | undefined;
|
||||
getActiveMaxMove?: (this: BattleActions, move: Move, pokemon: Pokemon) => ActiveMove;
|
||||
getActiveZMove?: (this: BattleActions, move: Move, pokemon: Pokemon) => ActiveMove;
|
||||
getMaxMove?: (this: BattleActions, move: Move, pokemon: Pokemon) => Move | undefined;
|
||||
getSpreadDamage?: (
|
||||
this: Battle, damage: SpreadMoveDamage, targets: SpreadMoveTargets, source: Pokemon,
|
||||
this: BattleActions, damage: SpreadMoveDamage, targets: SpreadMoveTargets, source: Pokemon,
|
||||
move: ActiveMove, moveData: ActiveMove, isSecondary?: boolean, isSelf?: boolean
|
||||
) => SpreadMoveDamage;
|
||||
getZMove?: (this: Battle, move: Move, pokemon: Pokemon, skipChecks?: boolean) => string | true | undefined;
|
||||
hitStepAccuracy?: (this: Battle, targets: Pokemon[], pokemon: Pokemon, move: ActiveMove) => boolean[];
|
||||
hitStepBreakProtect?: (this: Battle, targets: Pokemon[], pokemon: Pokemon, move: ActiveMove) => undefined;
|
||||
hitStepMoveHitLoop?: (this: Battle, targets: Pokemon[], pokemon: Pokemon, move: ActiveMove) => SpreadMoveDamage;
|
||||
hitStepTryImmunity?: (this: Battle, targets: Pokemon[], pokemon: Pokemon, move: ActiveMove) => boolean[];
|
||||
hitStepStealBoosts?: (this: Battle, targets: Pokemon[], pokemon: Pokemon, move: ActiveMove) => undefined;
|
||||
hitStepTryHitEvent?: (this: Battle, targets: Pokemon[], pokemon: Pokemon, move: ActiveMove) => (boolean | '')[];
|
||||
hitStepInvulnerabilityEvent?: (this: Battle, targets: Pokemon[], pokemon: Pokemon, move: ActiveMove) => boolean[];
|
||||
hitStepTypeImmunity?: (this: Battle, targets: Pokemon[], pokemon: Pokemon, move: ActiveMove) => boolean[];
|
||||
isAdjacent?: (this: Battle, pokemon1: Pokemon, pokemon2: Pokemon) => boolean;
|
||||
getZMove?: (this: BattleActions, move: Move, pokemon: Pokemon, skipChecks?: boolean) => string | true | undefined;
|
||||
hitStepAccuracy?: (this: BattleActions, targets: Pokemon[], pokemon: Pokemon, move: ActiveMove) => boolean[];
|
||||
hitStepBreakProtect?: (this: BattleActions, targets: Pokemon[], pokemon: Pokemon, move: ActiveMove) => undefined;
|
||||
hitStepMoveHitLoop?: (this: BattleActions, targets: Pokemon[], pokemon: Pokemon, move: ActiveMove) => SpreadMoveDamage;
|
||||
hitStepTryImmunity?: (this: BattleActions, targets: Pokemon[], pokemon: Pokemon, move: ActiveMove) => boolean[];
|
||||
hitStepStealBoosts?: (this: BattleActions, targets: Pokemon[], pokemon: Pokemon, move: ActiveMove) => undefined;
|
||||
hitStepTryHitEvent?: (this: BattleActions, targets: Pokemon[], pokemon: Pokemon, move: ActiveMove) => (boolean | '')[];
|
||||
hitStepInvulnerabilityEvent?: (
|
||||
this: BattleActions, targets: Pokemon[], pokemon: Pokemon, move: ActiveMove
|
||||
) => boolean[];
|
||||
hitStepTypeImmunity?: (this: BattleActions, targets: Pokemon[], pokemon: Pokemon, move: ActiveMove) => boolean[];
|
||||
moveHit?: (
|
||||
this: Battle, target: Pokemon | null, pokemon: Pokemon, move: ActiveMove,
|
||||
this: BattleActions, target: Pokemon | null, pokemon: Pokemon, move: ActiveMove,
|
||||
moveData?: ActiveMove, isSecondary?: boolean, isSelf?: boolean
|
||||
) => number | undefined | false;
|
||||
runAction?: (this: Battle, action: Action) => void;
|
||||
runMegaEvo?: (this: Battle, pokemon: Pokemon) => boolean;
|
||||
runAction?: (this: BattleActions, action: Action) => void;
|
||||
runMegaEvo?: (this: BattleActions, pokemon: Pokemon) => boolean;
|
||||
runMove?: (
|
||||
this: Battle, moveOrMoveName: Move | string, pokemon: Pokemon, targetLoc: number, sourceEffect?: Effect | null,
|
||||
this: BattleActions, moveOrMoveName: Move | string, pokemon: Pokemon, targetLoc: number, sourceEffect?: Effect | null,
|
||||
zMove?: string, externalMove?: boolean, maxMove?: string, originalTarget?: Pokemon
|
||||
) => void;
|
||||
runMoveEffects?: (
|
||||
this: Battle, damage: SpreadMoveDamage, targets: SpreadMoveTargets, source: Pokemon,
|
||||
this: BattleActions, damage: SpreadMoveDamage, targets: SpreadMoveTargets, source: Pokemon,
|
||||
move: ActiveMove, moveData: ActiveMove, isSecondary?: boolean, isSelf?: boolean
|
||||
) => SpreadMoveDamage;
|
||||
runZPower?: (this: Battle, move: ActiveMove, pokemon: Pokemon) => void;
|
||||
runZPower?: (this: BattleActions, move: ActiveMove, pokemon: Pokemon) => void;
|
||||
secondaries?: (
|
||||
this: Battle, targets: SpreadMoveTargets, source: Pokemon, move: ActiveMove, moveData: ActiveMove, isSelf?: boolean
|
||||
this: BattleActions, targets: SpreadMoveTargets, source: Pokemon, move: ActiveMove,
|
||||
moveData: ActiveMove, isSelf?: boolean
|
||||
) => void;
|
||||
selfDrops?: (
|
||||
this: Battle, targets: SpreadMoveTargets, source: Pokemon,
|
||||
this: BattleActions, targets: SpreadMoveTargets, source: Pokemon,
|
||||
move: ActiveMove, moveData: ActiveMove, isSecondary?: boolean
|
||||
) => void;
|
||||
spreadMoveHit?: (
|
||||
this: Battle, targets: SpreadMoveTargets, pokemon: Pokemon, move: ActiveMove,
|
||||
this: BattleActions, targets: SpreadMoveTargets, pokemon: Pokemon, move: ActiveMove,
|
||||
moveData?: ActiveMove, isSecondary?: boolean, isSelf?: boolean
|
||||
) => [SpreadMoveDamage, SpreadMoveTargets];
|
||||
targetTypeChoices?: (this: Battle, targetType: string) => boolean;
|
||||
tryMoveHit?: (this: Battle, target: Pokemon, pokemon: Pokemon, move: ActiveMove) => number | undefined | false | '';
|
||||
targetTypeChoices?: (this: BattleActions, targetType: string) => boolean;
|
||||
tryMoveHit?: (
|
||||
this: BattleActions, target: Pokemon, pokemon: Pokemon, move: ActiveMove
|
||||
) => number | undefined | false | '';
|
||||
tryPrimaryHitEvent?: (
|
||||
this: Battle, damage: SpreadMoveDamage, targets: SpreadMoveTargets, pokemon: Pokemon,
|
||||
this: BattleActions, damage: SpreadMoveDamage, targets: SpreadMoveTargets, pokemon: Pokemon,
|
||||
move: ActiveMove, moveData: ActiveMove, isSecondary?: boolean
|
||||
) => SpreadMoveDamage;
|
||||
trySpreadMoveHit?: (
|
||||
this: Battle, targets: Pokemon[], pokemon: Pokemon, move: ActiveMove, notActive?: boolean
|
||||
this: BattleActions, targets: Pokemon[], pokemon: Pokemon, move: ActiveMove, notActive?: boolean
|
||||
) => boolean;
|
||||
useMove?: (
|
||||
this: Battle, move: Move, pokemon: Pokemon, target?: Pokemon | null,
|
||||
this: BattleActions, move: Move, pokemon: Pokemon, target?: Pokemon | null,
|
||||
sourceEffect?: Effect | null, zMove?: string, maxMove?: string
|
||||
) => boolean;
|
||||
useMoveInner?: (
|
||||
this: Battle, move: Move, pokemon: Pokemon, target?: Pokemon | null,
|
||||
this: BattleActions, move: Move, pokemon: Pokemon, target?: Pokemon | null,
|
||||
sourceEffect?: Effect | null, zMove?: string, maxMove?: string
|
||||
) => boolean;
|
||||
getDamage?: (
|
||||
this: BattleActions, pokemon: Pokemon, target: Pokemon, move: string | number | ActiveMove, suppressMessages: boolean
|
||||
) => number | undefined | null | false;
|
||||
modifyDamage?: (
|
||||
this: BattleActions, baseDamage: number, pokemon: Pokemon, target: Pokemon, move: ActiveMove, suppressMessages?: boolean
|
||||
) => void;
|
||||
|
||||
// oms
|
||||
doGetMixedSpecies?: (this: BattleActions, species: Species, deltas: AnyObject) => Species;
|
||||
getMegaDeltas?: (this: BattleActions, megaSpecies: Species) => AnyObject;
|
||||
getMixedSpecies?: (this: BattleActions, originalName: string, megaName: string) => Species;
|
||||
}
|
||||
|
||||
interface ModdedBattleSide {
|
||||
lastMove?: Move | null;
|
||||
}
|
||||
type ModdedBattleSide = never;
|
||||
|
||||
interface ModdedBattlePokemon {
|
||||
/** TODO: remove, completely meaningless */
|
||||
inherit?: true;
|
||||
lostItemForDelibird?: Item | null;
|
||||
boostBy?: (this: Pokemon, boost: SparseBoostsTable) => boolean | number;
|
||||
|
|
@ -356,7 +371,7 @@ interface ModdedField extends Partial<Field> {
|
|||
|
||||
interface ModdedBattleScriptsData extends Partial<BattleScriptsData> {
|
||||
inherit?: string;
|
||||
lastDamage?: number;
|
||||
actions?: ModdedBattleActions;
|
||||
pokemon?: ModdedBattlePokemon;
|
||||
queue?: ModdedBattleQueue;
|
||||
field?: ModdedField;
|
||||
|
|
@ -366,33 +381,13 @@ interface ModdedBattleScriptsData extends Partial<BattleScriptsData> {
|
|||
effect?: Effect | string | null, isSecondary?: boolean, isSelf?: boolean
|
||||
) => boolean | null | 0;
|
||||
debug?: (this: Battle, activity: string) => void;
|
||||
getDamage?: (
|
||||
this: Battle, pokemon: Pokemon, target: Pokemon, move: string | number | ActiveMove, suppressMessages: boolean
|
||||
) => number | undefined | null | false;
|
||||
getActionSpeed?: (this: Battle, action: AnyObject) => void;
|
||||
getEffect?: (this: Battle, name: string | Effect | null) => Effect;
|
||||
init?: (this: ModdedDex) => void;
|
||||
modifyDamage?: (
|
||||
this: Battle, baseDamage: number, pokemon: Pokemon, target: Pokemon, move: ActiveMove, suppressMessages?: boolean
|
||||
) => void;
|
||||
natureModify?: (this: Battle, stats: StatsTable, set: PokemonSet) => StatsTable;
|
||||
nextTurn?: (this: Battle) => void;
|
||||
runMove?: (
|
||||
this: Battle, moveOrMoveName: Move | string, pokemon: Pokemon, targetLoc: number, sourceEffect?: Effect | null,
|
||||
zMove?: string, externalMove?: boolean, maxMove?: string, originalTarget?: Pokemon
|
||||
) => void;
|
||||
spreadModify?: (this: Battle, baseStats: StatsTable, set: PokemonSet) => StatsTable;
|
||||
suppressingWeather?: (this: Battle) => boolean;
|
||||
trunc?: (n: number) => number;
|
||||
|
||||
// oms
|
||||
doGetMixedSpecies?: (this: Battle, species: Species, deltas: AnyObject) => Species;
|
||||
getMegaDeltas?: (this: Battle, megaSpecies: Species) => AnyObject;
|
||||
getMixedSpecies?: (this: Battle, originalName: string, megaName: string) => Species;
|
||||
getAbility?: (this: Battle, name: string | Ability) => Ability;
|
||||
getZMove?: (this: Battle, move: Move, pokemon: Pokemon, skipChecks?: boolean) => string | undefined;
|
||||
getActiveZMove?: (this: Battle, move: Move, pokemon: Pokemon) => ActiveMove;
|
||||
canZMove?: (this: Battle, pokemon: Pokemon) => ZMoveOptions | void;
|
||||
win?: (this: Battle, side?: SideID | '' | Side | null) => boolean;
|
||||
faintMessages?: (this: Battle, lastFirst?: boolean) => boolean | undefined;
|
||||
tiebreak?: (this: Battle) => boolean;
|
||||
|
|
|
|||
|
|
@ -424,8 +424,8 @@ export class Pokemon {
|
|||
this.speed = 0;
|
||||
this.abilityOrder = 0;
|
||||
|
||||
this.canMegaEvo = this.battle.canMegaEvo(this);
|
||||
this.canUltraBurst = this.battle.canUltraBurst(this);
|
||||
this.canMegaEvo = this.battle.actions.canMegaEvo(this);
|
||||
this.canUltraBurst = this.battle.actions.canUltraBurst(this);
|
||||
// Normally would want to use battle.canDynamax to set this, but it references this property.
|
||||
this.canDynamax = (this.battle.gen >= 8);
|
||||
this.canGigantamax = this.baseSpecies.canGigantamax || null;
|
||||
|
|
@ -549,7 +549,7 @@ export class Pokemon {
|
|||
|
||||
// stat modifier effects
|
||||
if (!unmodified) {
|
||||
const statTable: {[s in StatNameExceptHP]?: string} = {atk: 'Atk', def: 'Def', spa: 'SpA', spd: 'SpD', spe: 'Spe'};
|
||||
const statTable: {[s in StatNameExceptHP]: string} = {atk: 'Atk', def: 'Def', spa: 'SpA', spd: 'SpD', spe: 'Spe'};
|
||||
stat = this.battle.runEvent('Modify' + statTable[statName], this, null, null, stat);
|
||||
}
|
||||
|
||||
|
|
@ -609,7 +609,6 @@ export class Pokemon {
|
|||
let allies = this.side.active;
|
||||
if (this.battle.gameType === 'multi') {
|
||||
const team = this.side.n % 2;
|
||||
// @ts-ignore
|
||||
allies = this.battle.sides.flatMap(
|
||||
(side: Side) => side.n % 2 === team ? side.active : []
|
||||
);
|
||||
|
|
@ -617,15 +616,14 @@ export class Pokemon {
|
|||
return allies.filter(ally => ally && !ally.fainted);
|
||||
}
|
||||
|
||||
nearbyAllies(): Pokemon[] {
|
||||
return this.allies().filter(ally => this.battle.isAdjacent(this, ally));
|
||||
adjacentAllies(): Pokemon[] {
|
||||
return this.allies().filter(ally => this.isAdjacent(ally));
|
||||
}
|
||||
|
||||
foes(): Pokemon[] {
|
||||
let foes = this.side.foe.active;
|
||||
if (this.battle.gameType === 'multi') {
|
||||
const team = this.side.foe.n % 2;
|
||||
// @ts-ignore
|
||||
foes = this.battle.sides.flatMap(
|
||||
(side: Side) => side.n % 2 === team ? side.active : []
|
||||
);
|
||||
|
|
@ -633,8 +631,14 @@ export class Pokemon {
|
|||
return foes.filter(foe => foe && !foe.fainted);
|
||||
}
|
||||
|
||||
nearbyFoes(): Pokemon[] {
|
||||
return this.foes().filter(foe => this.battle.isAdjacent(this, foe));
|
||||
adjacentFoes(): Pokemon[] {
|
||||
return this.foes().filter(foe => this.isAdjacent(foe));
|
||||
}
|
||||
|
||||
isAdjacent(pokemon2: Pokemon) {
|
||||
if (this.fainted || pokemon2.fainted) return false;
|
||||
if (this.side === pokemon2.side) return Math.abs(this.position - pokemon2.position) === 1;
|
||||
return Math.abs(this.position + pokemon2.position + 1 - this.side.active.length) <= 1;
|
||||
}
|
||||
|
||||
getUndynamaxedHP(amount?: number) {
|
||||
|
|
@ -647,7 +651,7 @@ export class Pokemon {
|
|||
|
||||
/** Get targets for Dragon Darts */
|
||||
getSmartTargets(target: Pokemon, move: ActiveMove) {
|
||||
const target2 = target.nearbyAllies()[0];
|
||||
const target2 = target.adjacentAllies()[0];
|
||||
if (!target2 || target2 === this || !target2.hp) {
|
||||
move.smartTarget = false;
|
||||
return [target];
|
||||
|
|
@ -659,6 +663,14 @@ export class Pokemon {
|
|||
return [target, target2];
|
||||
}
|
||||
|
||||
getAtLoc(targetLoc: number) {
|
||||
if (targetLoc > 0) {
|
||||
return this.side.foe.active[targetLoc - 1];
|
||||
} else {
|
||||
return this.side.active[-targetLoc - 1];
|
||||
}
|
||||
}
|
||||
|
||||
getMoveTargets(move: ActiveMove, target: Pokemon): {targets: Pokemon[], pressureTargets: Pokemon[]} {
|
||||
let targets: Pokemon[] = [];
|
||||
|
||||
|
|
@ -678,10 +690,10 @@ export class Pokemon {
|
|||
}
|
||||
break;
|
||||
case 'allAdjacent':
|
||||
targets.push(...this.nearbyAllies());
|
||||
targets.push(...this.adjacentAllies());
|
||||
// falls through
|
||||
case 'allAdjacentFoes':
|
||||
targets.push(...this.nearbyFoes());
|
||||
targets.push(...this.adjacentFoes());
|
||||
if (targets.length && !targets.includes(target)) {
|
||||
this.battle.retargetLastMove(targets[targets.length - 1]);
|
||||
}
|
||||
|
|
@ -871,7 +883,7 @@ export class Pokemon {
|
|||
disabled = this.maxMoveDisabled(moveSlot.id) || disabled && canCauseStruggle.includes(moveSlot.disabledSource!);
|
||||
} else if (
|
||||
(moveSlot.pp <= 0 && !this.volatiles['partialtrappinglock']) || disabled &&
|
||||
this.side.active.length >= 2 && this.battle.targetTypeChoices(target!)
|
||||
this.side.active.length >= 2 && this.battle.actions.targetTypeChoices(target!)
|
||||
) {
|
||||
disabled = true;
|
||||
}
|
||||
|
|
@ -918,7 +930,7 @@ export class Pokemon {
|
|||
let atLeastOne = false;
|
||||
for (const moveSlot of this.moveSlots) {
|
||||
const move = this.battle.dex.getMove(moveSlot.id);
|
||||
const maxMove = this.battle.getMaxMove(move, this);
|
||||
const maxMove = this.battle.actions.getMaxMove(move, this);
|
||||
if (maxMove) {
|
||||
if (this.maxMoveDisabled(move)) {
|
||||
result.maxMoves.push({move: maxMove.id, target: maxMove.target, disabled: true});
|
||||
|
|
@ -979,7 +991,7 @@ export class Pokemon {
|
|||
if (!lockedMove) {
|
||||
if (this.canMegaEvo) data.canMegaEvo = true;
|
||||
if (this.canUltraBurst) data.canUltraBurst = true;
|
||||
const canZMove = this.battle.canZMove(this);
|
||||
const canZMove = this.battle.actions.canZMove(this);
|
||||
if (canZMove) data.canZMove = canZMove;
|
||||
|
||||
if (this.getDynamaxRequest()) data.canDynamax = true;
|
||||
|
|
|
|||
10
sim/side.ts
10
sim/side.ts
|
|
@ -67,6 +67,10 @@ export class Side {
|
|||
activeRequest: AnyObject | null;
|
||||
choice: Choice;
|
||||
|
||||
/**
|
||||
* In gen 1, all lastMove stuff is tracked on Side rather than Pokemon
|
||||
* (this is for Counter and Mirror Move)
|
||||
*/
|
||||
lastMove: Move | null;
|
||||
|
||||
constructor(name: string, battle: Battle, sideNum: number, team: PokemonSet[]) {
|
||||
|
|
@ -410,7 +414,7 @@ export class Side {
|
|||
|
||||
// Z-move
|
||||
|
||||
const zMove = megaDynaOrZ === 'zmove' ? this.battle.getZMove(move, pokemon) : undefined;
|
||||
const zMove = megaDynaOrZ === 'zmove' ? this.battle.actions.getZMove(move, pokemon) : undefined;
|
||||
if (megaDynaOrZ === 'zmove' && !zMove) {
|
||||
return this.emitChoiceError(`Can't move: ${pokemon.name} can't use ${move.name} as a Z-move`);
|
||||
}
|
||||
|
|
@ -423,7 +427,7 @@ export class Side {
|
|||
// Dynamax
|
||||
// Is dynamaxed or will dynamax this turn.
|
||||
const maxMove = (megaDynaOrZ === 'dynamax' || pokemon.volatiles['dynamax']) ?
|
||||
this.battle.getMaxMove(move, pokemon) : undefined;
|
||||
this.battle.actions.getMaxMove(move, pokemon) : undefined;
|
||||
if (megaDynaOrZ === 'dynamax' && !maxMove) {
|
||||
return this.emitChoiceError(`Can't move: ${pokemon.name} can't use ${move.name} as a Max Move`);
|
||||
}
|
||||
|
|
@ -434,7 +438,7 @@ export class Side {
|
|||
|
||||
if (autoChoose) {
|
||||
targetLoc = 0;
|
||||
} else if (this.battle.targetTypeChoices(targetType)) {
|
||||
} else if (this.battle.actions.targetTypeChoices(targetType)) {
|
||||
if (!targetLoc && this.active.length >= 2) {
|
||||
return this.emitChoiceError(`Can't move: ${move.name} needs a target`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,9 +33,9 @@ type Referable = Battle | Field | Side | Pokemon | Condition | Ability | Item |
|
|||
// need special treatment from these sets are then handled manually.
|
||||
|
||||
const BATTLE = new Set([
|
||||
'dex', 'gen', 'ruleTable', 'id', 'log', 'inherit', 'format', 'zMoveTable', 'teamGenerator',
|
||||
'dex', 'gen', 'ruleTable', 'id', 'log', 'inherit', 'format', 'teamGenerator',
|
||||
'HIT_SUBSTITUTE', 'NOT_FAIL', 'FAIL', 'SILENT_FAIL', 'field', 'sides', 'prng', 'hints',
|
||||
'deserialized', 'maxMoveTable', 'queue',
|
||||
'deserialized', 'queue', 'actions',
|
||||
]);
|
||||
const FIELD = new Set(['id', 'battle']);
|
||||
const SIDE = new Set(['battle', 'team', 'pokemon', 'choice', 'activeRequest']);
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ describe('Z Moves', function () {
|
|||
assert.statStage(chansey, 'atk', -1);
|
||||
battle.makeChoices('auto', 'move taunt');
|
||||
|
||||
assert(battle.canZMove(chansey), `Chansey should be able to use its Z Move`);
|
||||
assert(battle.actions.canZMove(chansey), `Chansey should be able to use its Z Move`);
|
||||
battle.makeChoices('move doubleteam zmove', 'auto'); // Z-Effect: Restores negative stat stages to 0
|
||||
assert.statStage(chansey, 'atk', 0);
|
||||
});
|
||||
|
|
@ -33,7 +33,7 @@ describe('Z Moves', function () {
|
|||
assert.statStage(chansey, 'atk', -1);
|
||||
battle.makeChoices('auto', 'move taunt');
|
||||
|
||||
assert.false(battle.canZMove(chansey), `Chansey should not be able to use its Z Move`);
|
||||
assert.false(battle.actions.canZMove(chansey), `Chansey should not be able to use its Z Move`);
|
||||
battle.makeChoices('auto', 'auto');
|
||||
assert.statStage(chansey, 'atk', -1);
|
||||
assert.cantMove(() => battle.makeChoices('move doubleteam zmove', ''));
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user