mirror of
https://github.com/smogon/pokemon-showdown.git
synced 2026-06-02 22:08:36 -05:00
Merge 073a47bf4a into 6765d8422d
This commit is contained in:
commit
dfd9a8ef0d
|
|
@ -821,9 +821,8 @@ export const Moves: import('../../../sim/dex-moves').ModdedMoveDataTable = {
|
|||
}
|
||||
// Drain/recoil/secondary effect confusion do not happen if the substitute breaks
|
||||
if (target.volatiles['substitute']) {
|
||||
if (move.recoil) {
|
||||
this.damage(this.clampIntRange(Math.floor(uncappedDamage * move.recoil[0] / move.recoil[1]), 1),
|
||||
source, target, 'recoil');
|
||||
if (uncappedDamage) {
|
||||
this.actions.calcRecoilDamage(uncappedDamage, move, source);
|
||||
}
|
||||
if (move.drain) {
|
||||
const amount = this.clampIntRange(Math.floor(uncappedDamage * move.drain[0] / move.drain[1]), 1);
|
||||
|
|
|
|||
|
|
@ -588,6 +588,7 @@ export const Scripts: ModdedBattleScriptsData = {
|
|||
}
|
||||
if ((damage || damage === 0) && !target.fainted) {
|
||||
damage = this.battle.damage(damage, target, pokemon, move);
|
||||
if (damage) this.calcRecoilDamage(damage, move, pokemon);
|
||||
if (!(damage || damage === 0)) return false;
|
||||
didSomething = true;
|
||||
} else if (damage === false && typeof hitResult === 'undefined') {
|
||||
|
|
|
|||
|
|
@ -47,8 +47,8 @@ export const Moves: import('../../../sim/dex-moves').ModdedMoveDataTable = {
|
|||
}
|
||||
// Drain/recoil does not happen if the substitute breaks
|
||||
if (target.volatiles['substitute']) {
|
||||
if (move.recoil) {
|
||||
this.damage(Math.round(uncappedDamage * move.recoil[0] / move.recoil[1]), source, target, 'recoil');
|
||||
if (uncappedDamage) {
|
||||
this.actions.calcRecoilDamage(uncappedDamage, move, source);
|
||||
}
|
||||
if (move.drain) {
|
||||
this.heal(Math.ceil(uncappedDamage * move.drain[0] / move.drain[1]), source, target, 'drain');
|
||||
|
|
|
|||
|
|
@ -433,6 +433,7 @@ export const Scripts: ModdedBattleScriptsData = {
|
|||
}
|
||||
if ((damage || damage === 0) && !target.fainted) {
|
||||
damage = this.battle.damage(damage, target, pokemon, move);
|
||||
if (damage && target.hp > 0) this.calcRecoilDamage(damage, move, pokemon);
|
||||
if (!(damage || damage === 0)) return false;
|
||||
didSomething = true;
|
||||
} else if (damage === false && typeof hitResult === 'undefined') {
|
||||
|
|
|
|||
|
|
@ -295,8 +295,8 @@ export const Scripts: ModdedBattleScriptsData = {
|
|||
this.battle.singleEvent('AfterMoveSecondary', move, null, target, pokemon, move);
|
||||
this.battle.runEvent('AfterMoveSecondary', target, pokemon, move);
|
||||
|
||||
if (move.recoil && move.totalDamage) {
|
||||
this.battle.damage(this.calcRecoilDamage(move.totalDamage, move, pokemon), pokemon, target, 'recoil');
|
||||
if (move.totalDamage) {
|
||||
this.calcRecoilDamage(move.totalDamage, move, pokemon);
|
||||
}
|
||||
return damage;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -233,8 +233,8 @@ export const Scripts: ModdedBattleScriptsData = {
|
|||
// Implementing Recoil mechanics from Stadium 2.
|
||||
// If a pokemon caused the other to faint with a recoil move and only one pokemon remains on both sides,
|
||||
// recoil damage will not be taken.
|
||||
if (move.recoil && move.totalDamage && (pokemon.side.pokemonLeft > 1 || target.side.pokemonLeft > 1 || target.hp)) {
|
||||
this.battle.damage(this.calcRecoilDamage(move.totalDamage, move, pokemon), pokemon, target, 'recoil');
|
||||
if (move.totalDamage && (pokemon.side.pokemonLeft > 1 || target.side.pokemonLeft > 1 || target.hp)) {
|
||||
this.calcRecoilDamage(move.totalDamage, move, pokemon);
|
||||
}
|
||||
return damage;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -188,7 +188,6 @@ export const Scripts: ModdedBattleScriptsData = {
|
|||
|
||||
if (!this.battle.singleEvent('TryMove', move, null, pokemon, target, move) ||
|
||||
!this.battle.runEvent('TryMove', pokemon, target, move)) {
|
||||
move.mindBlownRecoil = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -457,8 +456,8 @@ export const Scripts: ModdedBattleScriptsData = {
|
|||
move.totalDamage = damage;
|
||||
}
|
||||
|
||||
if (move.recoil && move.totalDamage) {
|
||||
this.battle.damage(this.calcRecoilDamage(move.totalDamage, move, pokemon), pokemon, target, 'recoil');
|
||||
if (move.totalDamage) {
|
||||
this.calcRecoilDamage(move.totalDamage, move, pokemon);
|
||||
}
|
||||
|
||||
if (target && pokemon !== target) target.gotAttacked(move, damage, pokemon);
|
||||
|
|
@ -476,9 +475,5 @@ export const Scripts: ModdedBattleScriptsData = {
|
|||
|
||||
return damage;
|
||||
},
|
||||
|
||||
calcRecoilDamage(damageDealt, move) {
|
||||
return this.battle.clampIntRange(Math.floor(damageDealt * move.recoil![0] / move.recoil![1]), 1);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1307,8 +1307,8 @@ export const Moves: import('../../../sim/dex-moves').ModdedMoveDataTable = {
|
|||
this.add('-activate', target, 'Substitute', '[damage]');
|
||||
}
|
||||
if (move.ohko) this.add('-ohko');
|
||||
if (move.recoil && damage) {
|
||||
this.damage(this.actions.calcRecoilDamage(damage, move, source), source, target, 'recoil');
|
||||
if (damage) {
|
||||
this.actions.calcRecoilDamage(damage, move, source);
|
||||
}
|
||||
if (move.drain) {
|
||||
this.heal(Math.ceil(damage * move.drain[0] / move.drain[1]), source, target, 'drain');
|
||||
|
|
|
|||
|
|
@ -199,8 +199,19 @@ export const Scripts: ModdedBattleScriptsData = {
|
|||
}
|
||||
return hitResults;
|
||||
},
|
||||
calcRecoilDamage(damageDealt, move) {
|
||||
return this.battle.clampIntRange(Math.floor(damageDealt * move.recoil![0] / move.recoil![1]), 1);
|
||||
calcRecoilDamage(damageDealt: number, move: Move, pokemon: Pokemon): number | null {
|
||||
let recoilDamage = 0;
|
||||
if (move.struggleRecoil) recoilDamage = this.battle.clampIntRange(Math.floor(pokemon.baseMaxhp / 4), 1);
|
||||
else if (move.recoil) {
|
||||
recoilDamage = this.battle.clampIntRange(Math.floor(damageDealt * move.recoil[0] / move.recoil[1]), 1);
|
||||
} else return null;
|
||||
|
||||
if (move.struggleRecoil) {
|
||||
this.battle.directDamage(recoilDamage, pokemon, pokemon, { id: 'strugglerecoil' } as Condition);
|
||||
} else {
|
||||
this.battle.damage(recoilDamage, pokemon, pokemon, 'recoil');
|
||||
}
|
||||
return recoilDamage;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -872,8 +872,8 @@ export const Moves: import('../../../sim/dex-moves').ModdedMoveDataTable = {
|
|||
} else {
|
||||
this.add('-activate', target, 'Substitute', '[damage]');
|
||||
}
|
||||
if (move.recoil && damage) {
|
||||
this.damage(this.actions.calcRecoilDamage(damage, move, source), source, target, 'recoil');
|
||||
if (damage) {
|
||||
this.actions.calcRecoilDamage(damage, move, source);
|
||||
}
|
||||
if (move.drain) {
|
||||
this.heal(Math.ceil(damage * move.drain[0] / move.drain[1]), source, target, 'drain');
|
||||
|
|
|
|||
|
|
@ -1314,7 +1314,6 @@ export const Scripts: ModdedBattleScriptsData = {
|
|||
|
||||
if (!this.battle.singleEvent('TryMove', move, null, pokemon, target, move) ||
|
||||
!this.battle.runEvent('TryMove', pokemon, target, move)) {
|
||||
move.mindBlownRecoil = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -1476,14 +1475,6 @@ export const Scripts: ModdedBattleScriptsData = {
|
|||
// Total damage dealt is accumulated for the purposes of recoil (Parental Bond).
|
||||
move.totalDamage += damage[i];
|
||||
}
|
||||
if (move.mindBlownRecoil) {
|
||||
const hpBeforeRecoil = pokemon.hp;
|
||||
this.battle.damage(Math.round(pokemon.maxhp / 2), pokemon, pokemon, this.dex.conditions.get(move.id), true);
|
||||
move.mindBlownRecoil = false;
|
||||
if (pokemon.hp <= pokemon.maxhp / 2 && hpBeforeRecoil > pokemon.maxhp / 2) {
|
||||
this.battle.runEvent('EmergencyExit', pokemon, pokemon);
|
||||
}
|
||||
}
|
||||
this.battle.eachEvent('Update');
|
||||
if (!pokemon.hp && targets.length === 1) {
|
||||
hit++; // report the correct number of hits for multihit moves
|
||||
|
|
@ -1498,26 +1489,8 @@ export const Scripts: ModdedBattleScriptsData = {
|
|||
this.battle.add('-hitcount', targets[0], hit - 1);
|
||||
}
|
||||
|
||||
if ((move.recoil || move.id === 'chloroblast') && move.totalDamage) {
|
||||
const hpBeforeRecoil = pokemon.hp;
|
||||
this.battle.damage(this.calcRecoilDamage(move.totalDamage, move, pokemon), pokemon, pokemon, 'recoil');
|
||||
if (pokemon.hp <= pokemon.maxhp / 2 && hpBeforeRecoil > pokemon.maxhp / 2) {
|
||||
this.battle.runEvent('EmergencyExit', pokemon, pokemon);
|
||||
}
|
||||
}
|
||||
|
||||
if (move.struggleRecoil) {
|
||||
const hpBeforeRecoil = pokemon.hp;
|
||||
let recoilDamage;
|
||||
if (this.dex.gen >= 5) {
|
||||
recoilDamage = this.battle.clampIntRange(Math.round(pokemon.baseMaxhp / 4), 1);
|
||||
} else {
|
||||
recoilDamage = this.battle.clampIntRange(this.battle.trunc(pokemon.maxhp / 4), 1);
|
||||
}
|
||||
this.battle.directDamage(recoilDamage, pokemon, pokemon, { id: 'strugglerecoil' } as Condition);
|
||||
if (pokemon.hp <= pokemon.maxhp / 2 && hpBeforeRecoil > pokemon.maxhp / 2) {
|
||||
this.battle.runEvent('EmergencyExit', pokemon, pokemon);
|
||||
}
|
||||
if (move.totalDamage) {
|
||||
this.calcRecoilDamage(move.totalDamage, move, pokemon);
|
||||
}
|
||||
|
||||
// smartTarget messes up targetsCopy, but smartTarget should in theory ensure that targets will never fail, anyway
|
||||
|
|
|
|||
|
|
@ -140,15 +140,10 @@ export const Moves: import('../../../sim/dex-moves').ModdedMoveDataTable = {
|
|||
},
|
||||
mindblown: {
|
||||
inherit: true,
|
||||
onAfterMove(pokemon, target, move) {
|
||||
if (move.mindBlownRecoil && !move.multihit) {
|
||||
const hpBeforeRecoil = pokemon.hp;
|
||||
const calc = calculate(this, pokemon, pokemon, 'mindblown');
|
||||
this.damage(Math.round(calc * pokemon.maxhp / 2), pokemon, pokemon, this.dex.conditions.get('Mind Blown'), true);
|
||||
if (pokemon.hp <= pokemon.maxhp / 2 && hpBeforeRecoil > pokemon.maxhp / 2) {
|
||||
this.runEvent('EmergencyExit', pokemon, pokemon);
|
||||
}
|
||||
}
|
||||
onMoveFail(target, source, move) {
|
||||
if (move.multihit) return;
|
||||
const calc = calculate(this, source, source, 'mindblown');
|
||||
this.damage(Math.round(calc * source.maxhp / 2), source, source, this.dex.conditions.get('Mind Blown'));
|
||||
},
|
||||
},
|
||||
nightmare: {
|
||||
|
|
@ -244,15 +239,10 @@ export const Moves: import('../../../sim/dex-moves').ModdedMoveDataTable = {
|
|||
},
|
||||
steelbeam: {
|
||||
inherit: true,
|
||||
onAfterMove(pokemon, target, move) {
|
||||
if (move.mindBlownRecoil && !move.multihit) {
|
||||
const hpBeforeRecoil = pokemon.hp;
|
||||
const calc = calculate(this, pokemon, pokemon, 'steelbeam');
|
||||
this.damage(Math.round(calc * pokemon.maxhp / 2), pokemon, pokemon, this.dex.conditions.get('Steel Beam'), true);
|
||||
if (pokemon.hp <= pokemon.maxhp / 2 && hpBeforeRecoil > pokemon.maxhp / 2) {
|
||||
this.runEvent('EmergencyExit', pokemon, pokemon);
|
||||
}
|
||||
}
|
||||
onMoveFail(target, source, move) {
|
||||
if (move.multihit) return;
|
||||
const calc = calculate(this, source, source, 'steelbeam');
|
||||
this.damage(Math.round(calc * source.maxhp / 2), source, source, this.dex.conditions.get('Mind Blown'));
|
||||
},
|
||||
},
|
||||
supercellslam: {
|
||||
|
|
|
|||
|
|
@ -110,15 +110,6 @@ export const Scripts: ModdedBattleScriptsData = {
|
|||
// Total damage dealt is accumulated for the purposes of recoil (Parental Bond).
|
||||
move.totalDamage += damage[i];
|
||||
}
|
||||
if (move.mindBlownRecoil) {
|
||||
const hpBeforeRecoil = pokemon.hp;
|
||||
const calc = calculate(this.battle, pokemon, pokemon, move.id);
|
||||
this.battle.damage(Math.round(calc * pokemon.maxhp / 2), pokemon, pokemon, this.dex.conditions.get(move.id), true);
|
||||
move.mindBlownRecoil = false;
|
||||
if (pokemon.hp <= pokemon.maxhp / 2 && hpBeforeRecoil > pokemon.maxhp / 2) {
|
||||
this.battle.runEvent('EmergencyExit', pokemon, pokemon);
|
||||
}
|
||||
}
|
||||
this.battle.eachEvent('Update');
|
||||
if (!pokemon.hp && targets.length === 1) {
|
||||
hit++; // report the correct number of hits for multihit moves
|
||||
|
|
@ -133,27 +124,8 @@ export const Scripts: ModdedBattleScriptsData = {
|
|||
this.battle.add('-hitcount', targets[0], hit - 1);
|
||||
}
|
||||
|
||||
if ((move.recoil || move.id === 'chloroblast') && move.totalDamage) {
|
||||
const hpBeforeRecoil = pokemon.hp;
|
||||
const recoilDamage = this.calcRecoilDamage(move.totalDamage, move, pokemon);
|
||||
if (recoilDamage !== 1.1) this.battle.damage(recoilDamage, pokemon, pokemon, 'recoil');
|
||||
if (pokemon.hp <= pokemon.maxhp / 2 && hpBeforeRecoil > pokemon.maxhp / 2) {
|
||||
this.battle.runEvent('EmergencyExit', pokemon, pokemon);
|
||||
}
|
||||
}
|
||||
|
||||
if (move.struggleRecoil) {
|
||||
const hpBeforeRecoil = pokemon.hp;
|
||||
let recoilDamage;
|
||||
if (this.dex.gen >= 5) {
|
||||
recoilDamage = this.battle.clampIntRange(Math.round(pokemon.baseMaxhp / 4), 1);
|
||||
} else {
|
||||
recoilDamage = this.battle.clampIntRange(this.battle.trunc(pokemon.maxhp / 4), 1);
|
||||
}
|
||||
this.battle.directDamage(recoilDamage, pokemon, pokemon, { id: 'strugglerecoil' } as Condition);
|
||||
if (pokemon.hp <= pokemon.maxhp / 2 && hpBeforeRecoil > pokemon.maxhp / 2) {
|
||||
this.battle.runEvent('EmergencyExit', pokemon, pokemon);
|
||||
}
|
||||
if (move.totalDamage) {
|
||||
this.calcRecoilDamage(move.totalDamage, move, pokemon);
|
||||
}
|
||||
|
||||
// smartTarget messes up targetsCopy, but smartTarget should in theory ensure that targets will never fail, anyway
|
||||
|
|
@ -194,12 +166,28 @@ export const Scripts: ModdedBattleScriptsData = {
|
|||
|
||||
return damage;
|
||||
},
|
||||
calcRecoilDamage(damageDealt, move, pokemon): number {
|
||||
calcRecoilDamage(damageDealt: number, move: Move, pokemon: Pokemon): number | null {
|
||||
const calc = calculate(this.battle, pokemon, pokemon, move.id);
|
||||
if (calc === 0) return 1.1;
|
||||
if (move.id === 'chloroblast') return Math.round(calc * pokemon.maxhp / 2);
|
||||
const recoil = Math.round(damageDealt * calc * move.recoil![0] / move.recoil![1]);
|
||||
return this.battle.clampIntRange(recoil, 1);
|
||||
if (!calc) return 0;
|
||||
|
||||
let recoilDamage = 0;
|
||||
if (move.struggleRecoil) recoilDamage = this.battle.clampIntRange(Math.round(pokemon.baseMaxhp / 4), 1);
|
||||
else if (move.mindBlownRecoil) recoilDamage = Math.round(calc * pokemon.maxhp / 2);
|
||||
else if (move.recoil) {
|
||||
recoilDamage = this.battle.clampIntRange(Math.round(damageDealt * calc * move.recoil[0] / move.recoil[1]), 1);
|
||||
} else return null;
|
||||
|
||||
const hpBeforeRecoil = pokemon.hp;
|
||||
if (move.struggleRecoil) {
|
||||
this.battle.directDamage(recoilDamage, pokemon, pokemon, { id: 'strugglerecoil' } as Condition);
|
||||
} else {
|
||||
this.battle.damage(recoilDamage, pokemon, pokemon, 'recoil');
|
||||
}
|
||||
if (pokemon.hp <= pokemon.maxhp / 2 && hpBeforeRecoil > pokemon.maxhp / 2) {
|
||||
this.battle.runEvent('EmergencyExit', pokemon, pokemon);
|
||||
}
|
||||
|
||||
return recoilDamage;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -939,7 +939,7 @@ export const Moves: import('../sim/dex-moves').MoveDataTable = {
|
|||
flags: { contact: 1, protect: 1, mirror: 1, metronome: 1 },
|
||||
hasCrashDamage: true,
|
||||
onMoveFail(target, source, move) {
|
||||
this.damage(source.baseMaxhp / 2, source, source, this.dex.conditions.get('High Jump Kick'));
|
||||
this.damage(source.baseMaxhp / 2, source, source, this.dex.conditions.get('Axe Kick'));
|
||||
},
|
||||
secondary: {
|
||||
chance: 30,
|
||||
|
|
@ -2443,7 +2443,8 @@ export const Moves: import('../sim/dex-moves').MoveDataTable = {
|
|||
pp: 5,
|
||||
priority: 0,
|
||||
flags: { protect: 1, mirror: 1, metronome: 1 },
|
||||
// Recoil implemented in battle-actions.ts
|
||||
mindBlownRecoil: true,
|
||||
// Contrary to Mind Blown, Chloroblast does not implement the MoveFail event
|
||||
target: "normal",
|
||||
type: "Grass",
|
||||
},
|
||||
|
|
@ -11885,14 +11886,9 @@ export const Moves: import('../sim/dex-moves').MoveDataTable = {
|
|||
priority: 0,
|
||||
flags: { protect: 1, mirror: 1 },
|
||||
mindBlownRecoil: true,
|
||||
onAfterMove(pokemon, target, move) {
|
||||
if (move.mindBlownRecoil && !move.multihit) {
|
||||
const hpBeforeRecoil = pokemon.hp;
|
||||
this.damage(Math.round(pokemon.maxhp / 2), pokemon, pokemon, this.dex.conditions.get('Mind Blown'), true);
|
||||
if (pokemon.hp <= pokemon.maxhp / 2 && hpBeforeRecoil > pokemon.maxhp / 2) {
|
||||
this.runEvent('EmergencyExit', pokemon, pokemon);
|
||||
}
|
||||
}
|
||||
onMoveFail(target, source, move) {
|
||||
if (move.multihit) return;
|
||||
this.damage(Math.round(source.maxhp / 2), source, source, this.dex.conditions.get('Mind Blown'));
|
||||
},
|
||||
target: "allAdjacent",
|
||||
type: "Fire",
|
||||
|
|
@ -17888,14 +17884,9 @@ export const Moves: import('../sim/dex-moves').MoveDataTable = {
|
|||
priority: 0,
|
||||
flags: { protect: 1, mirror: 1 },
|
||||
mindBlownRecoil: true,
|
||||
onAfterMove(pokemon, target, move) {
|
||||
if (move.mindBlownRecoil && !move.multihit) {
|
||||
const hpBeforeRecoil = pokemon.hp;
|
||||
this.damage(Math.round(pokemon.maxhp / 2), pokemon, pokemon, this.dex.conditions.get('Steel Beam'), true);
|
||||
if (pokemon.hp <= pokemon.maxhp / 2 && hpBeforeRecoil > pokemon.maxhp / 2) {
|
||||
this.runEvent('EmergencyExit', pokemon, pokemon);
|
||||
}
|
||||
}
|
||||
onMoveFail(target, source, move) {
|
||||
if (move.multihit) return;
|
||||
this.damage(Math.round(source.maxhp / 2), source, source, this.dex.conditions.get('Steel Beam'));
|
||||
},
|
||||
target: "normal",
|
||||
type: "Steel",
|
||||
|
|
@ -18369,8 +18360,8 @@ export const Moves: import('../sim/dex-moves').MoveDataTable = {
|
|||
} else {
|
||||
this.add('-activate', target, 'move: Substitute', '[damage]');
|
||||
}
|
||||
if (move.recoil || move.id === 'chloroblast') {
|
||||
this.damage(this.actions.calcRecoilDamage(damage, move, source), source, target, 'recoil');
|
||||
if (damage) {
|
||||
this.actions.calcRecoilDamage(damage, move, source);
|
||||
}
|
||||
if (move.drain) {
|
||||
this.heal(Math.ceil(damage * move.drain[0] / move.drain[1]), source, target, 'drain');
|
||||
|
|
|
|||
|
|
@ -488,7 +488,6 @@ export class BattleActions {
|
|||
tryMoveResult = this.battle.runEvent('TryMove', pokemon, target, move);
|
||||
}
|
||||
if (!tryMoveResult) {
|
||||
move.mindBlownRecoil = false;
|
||||
return tryMoveResult;
|
||||
}
|
||||
|
||||
|
|
@ -524,7 +523,13 @@ export class BattleActions {
|
|||
}
|
||||
|
||||
if (!moveResult) {
|
||||
const originalHp = pokemon.hp;
|
||||
this.battle.singleEvent('MoveFail', move, null, target, pokemon, move);
|
||||
if (pokemon && pokemon !== target && move.category !== 'Status') {
|
||||
if (pokemon.hp <= pokemon.maxhp / 2 && originalHp > pokemon.maxhp / 2) {
|
||||
this.battle.runEvent('EmergencyExit', pokemon, pokemon);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -959,14 +964,6 @@ export class BattleActions {
|
|||
// Total damage dealt is accumulated for the purposes of recoil (Parental Bond).
|
||||
move.totalDamage += damage[i];
|
||||
}
|
||||
if (move.mindBlownRecoil) {
|
||||
const hpBeforeRecoil = pokemon.hp;
|
||||
this.battle.damage(Math.round(pokemon.maxhp / 2), pokemon, pokemon, this.dex.conditions.get(move.id), true);
|
||||
move.mindBlownRecoil = false;
|
||||
if (pokemon.hp <= pokemon.maxhp / 2 && hpBeforeRecoil > pokemon.maxhp / 2) {
|
||||
this.battle.runEvent('EmergencyExit', pokemon, pokemon);
|
||||
}
|
||||
}
|
||||
this.battle.eachEvent('Update');
|
||||
if (!pokemon.hp && targets.length === 1) {
|
||||
hit++; // report the correct number of hits for multihit moves
|
||||
|
|
@ -981,26 +978,8 @@ export class BattleActions {
|
|||
this.battle.add('-hitcount', targets[0], hit - 1);
|
||||
}
|
||||
|
||||
if ((move.recoil || move.id === 'chloroblast') && move.totalDamage) {
|
||||
const hpBeforeRecoil = pokemon.hp;
|
||||
this.battle.damage(this.calcRecoilDamage(move.totalDamage, move, pokemon), pokemon, pokemon, 'recoil');
|
||||
if (pokemon.hp <= pokemon.maxhp / 2 && hpBeforeRecoil > pokemon.maxhp / 2) {
|
||||
this.battle.runEvent('EmergencyExit', pokemon, pokemon);
|
||||
}
|
||||
}
|
||||
|
||||
if (move.struggleRecoil) {
|
||||
const hpBeforeRecoil = pokemon.hp;
|
||||
let recoilDamage;
|
||||
if (this.dex.gen >= 5) {
|
||||
recoilDamage = this.battle.clampIntRange(Math.round(pokemon.baseMaxhp / 4), 1);
|
||||
} else {
|
||||
recoilDamage = this.battle.clampIntRange(this.battle.trunc(pokemon.maxhp / 4), 1);
|
||||
}
|
||||
this.battle.directDamage(recoilDamage, pokemon, pokemon, { id: 'strugglerecoil' } as Condition);
|
||||
if (pokemon.hp <= pokemon.maxhp / 2 && hpBeforeRecoil > pokemon.maxhp / 2) {
|
||||
this.battle.runEvent('EmergencyExit', pokemon, pokemon);
|
||||
}
|
||||
if (move.totalDamage) {
|
||||
this.calcRecoilDamage(move.totalDamage, move, pokemon);
|
||||
}
|
||||
|
||||
// smartTarget messes up targetsCopy, but smartTarget should in theory ensure that targets will never fail, anyway
|
||||
|
|
@ -1397,9 +1376,25 @@ export class BattleActions {
|
|||
return retVal === true ? undefined : retVal;
|
||||
}
|
||||
|
||||
calcRecoilDamage(damageDealt: number, move: Move, pokemon: Pokemon): number {
|
||||
if (move.id === 'chloroblast') return Math.round(pokemon.maxhp / 2);
|
||||
return this.battle.clampIntRange(Math.round(damageDealt * move.recoil![0] / move.recoil![1]), 1);
|
||||
calcRecoilDamage(damageDealt: number, move: Move, pokemon: Pokemon): number | null {
|
||||
let recoilDamage = null;
|
||||
if (move.struggleRecoil) recoilDamage = this.battle.clampIntRange(Math.round(pokemon.baseMaxhp / 4), 1);
|
||||
else if (move.mindBlownRecoil) recoilDamage = Math.round(pokemon.maxhp / 2);
|
||||
else if (move.recoil) {
|
||||
recoilDamage = this.battle.clampIntRange(Math.round(damageDealt * move.recoil[0] / move.recoil[1]), 1);
|
||||
} else return null;
|
||||
|
||||
const hpBeforeRecoil = pokemon.hp;
|
||||
if (move.struggleRecoil) {
|
||||
this.battle.directDamage(recoilDamage, pokemon, pokemon, { id: 'strugglerecoil' } as Condition);
|
||||
} else {
|
||||
this.battle.damage(recoilDamage, pokemon, pokemon, 'recoil');
|
||||
}
|
||||
if (pokemon.hp <= pokemon.maxhp / 2 && hpBeforeRecoil > pokemon.maxhp / 2) {
|
||||
this.battle.runEvent('EmergencyExit', pokemon, pokemon);
|
||||
}
|
||||
|
||||
return recoilDamage;
|
||||
}
|
||||
|
||||
getZMove(move: Move, pokemon: Pokemon, skipChecks?: boolean): string | undefined {
|
||||
|
|
|
|||
|
|
@ -2109,7 +2109,7 @@ export class Battle {
|
|||
}
|
||||
if (targetDamage !== 0) targetDamage = this.clampIntRange(targetDamage, 1);
|
||||
|
||||
if (effect.id !== 'struggle-recoil') { // Struggle recoil is not affected by effects
|
||||
if (effect.id !== 'strugglerecoil') { // Struggle recoil is not affected by effects
|
||||
if (effect.effectType === 'Weather' && !target.runStatusImmunity(effect.id)) {
|
||||
this.debug('weather immunity');
|
||||
retVals[i] = 0;
|
||||
|
|
@ -2158,12 +2158,6 @@ export class Battle {
|
|||
}
|
||||
|
||||
if (targetDamage && effect.effectType === 'Move') {
|
||||
if (this.gen <= 1 && effect.recoil && source) {
|
||||
if (this.dex.currentMod !== 'gen1stadium' || target.hp > 0) {
|
||||
const amount = this.clampIntRange(Math.floor(targetDamage * effect.recoil[0] / effect.recoil[1]), 1);
|
||||
this.damage(amount, source, target, 'recoil');
|
||||
}
|
||||
}
|
||||
if (this.gen <= 4 && effect.drain && source) {
|
||||
const amount = this.clampIntRange(Math.floor(targetDamage * effect.drain[0] / effect.drain[1]), 1);
|
||||
// Draining can be countered in gen 1
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ interface BattleScriptsData {
|
|||
interface ModdedBattleActions {
|
||||
inherit?: true;
|
||||
afterMoveSecondaryEvent?: (this: BattleActions, targets: Pokemon[], pokemon: Pokemon, move: ActiveMove) => undefined;
|
||||
calcRecoilDamage?: (this: BattleActions, damageDealt: number, move: Move, pokemon: Pokemon) => number;
|
||||
calcRecoilDamage?: (this: BattleActions, damageDealt: number, move: Move, pokemon: Pokemon) => number | null;
|
||||
canMegaEvo?: (this: BattleActions, pokemon: Pokemon) => string | undefined | null;
|
||||
canMegaEvoX?: (this: BattleActions, pokemon: Pokemon) => string | undefined | null;
|
||||
canMegaEvoY?: (this: BattleActions, pokemon: Pokemon) => string | undefined | null;
|
||||
|
|
|
|||
|
|
@ -401,6 +401,32 @@ describe(`Emergency Exit`, () => {
|
|||
assert.equal(battle.requestState, 'switch');
|
||||
});
|
||||
|
||||
it(`should request a switchout after crash damage`, () => {
|
||||
battle = common.createBattle([[
|
||||
{ species: 'Golisopod', ability: 'Emergency Exit', moves: ['highjumpkick'], evs: { hp: 4 } },
|
||||
{ species: 'Wynaut', moves: ['sleeptalk'] },
|
||||
], [
|
||||
{ species: 'Chansey', moves: ['protect'] },
|
||||
]]);
|
||||
const eePokemon = battle.p1.active[0];
|
||||
battle.makeChoices();
|
||||
assert.atMost(eePokemon.hp, eePokemon.maxhp / 2);
|
||||
assert.equal(battle.requestState, 'switch');
|
||||
});
|
||||
|
||||
it(`should request a switchout after Mind Blown recoil damage`, () => {
|
||||
battle = common.createBattle([[
|
||||
{ species: 'Golisopod', ability: 'Emergency Exit', moves: ['mindblown'], evs: { hp: 4 } },
|
||||
{ species: 'Wynaut', moves: ['sleeptalk'] },
|
||||
], [
|
||||
{ species: 'Chansey', moves: ['protect'] },
|
||||
]]);
|
||||
const eePokemon = battle.p1.active[0];
|
||||
battle.makeChoices();
|
||||
assert.atMost(eePokemon.hp, eePokemon.maxhp / 2);
|
||||
assert.equal(battle.requestState, 'switch');
|
||||
});
|
||||
|
||||
it(`should request a switchout after taking struggle recoil damage`, () => {
|
||||
battle = common.createBattle([[
|
||||
{ species: 'Golisopod', item: 'Assault Vest', ability: 'Emergency Exit', moves: ['protect'] },
|
||||
|
|
|
|||
|
|
@ -27,4 +27,15 @@ describe('Mind Blown', () => {
|
|||
]]);
|
||||
assert.hurtsBy(battle.p1.active[0], Math.ceil(battle.p1.active[0].maxhp / 2), () => battle.makeChoices());
|
||||
});
|
||||
|
||||
it('should not deal damage to the user if there is no target', () => {
|
||||
battle = common.createBattle([[
|
||||
{ species: 'Dugtrio', ability: 'sandveil', moves: ['memento'] },
|
||||
{ species: 'Dugtrio', ability: 'sandveil', moves: ['memento'] },
|
||||
], [
|
||||
{ species: 'Blacephalon', ability: 'limber', moves: ['mindblown'] },
|
||||
]]);
|
||||
|
||||
assert.false.hurts(battle.p2.active[0], () => battle.makeChoices());
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user