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:
Guangcong Luo 2021-03-28 12:01:38 -07:00 committed by GitHub
parent e42fa69c22
commit ed454ef76a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 5536 additions and 5690 deletions

View File

@ -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 "";

View File

@ -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: {

View File

@ -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: {

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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 {

View File

@ -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: {

View File

@ -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);
},
},
};

View File

@ -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);

View File

@ -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');

View File

@ -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);
},
},
};

View File

@ -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');

View File

@ -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',

View File

@ -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;
}

View File

@ -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);
},

View File

@ -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');

View File

@ -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: {

View File

@ -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);
}
},
},

View File

@ -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} = {

View File

@ -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;
},
},

View File

@ -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: {

View File

@ -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;
},
},
};

View File

@ -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;
},
},
};

View File

@ -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);
}
}
},

View File

@ -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

View File

@ -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');

File diff suppressed because it is too large Load Diff

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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;

View File

@ -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';
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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`);
}

View File

@ -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']);

View File

@ -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', ''));