From 6f82443101ec42778eb9a9cbd2a0dc0ddb182987 Mon Sep 17 00:00:00 2001 From: Kirk Scheibelhut Date: Tue, 5 Mar 2019 10:28:01 -0800 Subject: [PATCH] Fix GSC Toxic interaction with Baton Pass/Heal Bell (#5226) --- data/mods/gen1/statuses.js | 33 ++------------- data/mods/gen2/statuses.js | 45 +++++++++++++++----- sim/battle.ts | 20 +++++++++ test/simulator/misc/statuses.js | 73 +++++++++++++++++++++++++++++++++ 4 files changed, 131 insertions(+), 40 deletions(-) diff --git a/data/mods/gen1/statuses.js b/data/mods/gen1/statuses.js index f3ea0222975e..f89ffcdbb477 100644 --- a/data/mods/gen1/statuses.js +++ b/data/mods/gen1/statuses.js @@ -23,11 +23,7 @@ let BattleStatuses = { }, onAfterMoveSelfPriority: 2, onAfterMoveSelf(pokemon) { - let toxicCounter = 1; - if (pokemon.volatiles['residualdmg']) { - pokemon.volatiles['residualdmg'].counter++; - toxicCounter = pokemon.volatiles['residualdmg'].counter; - } + let toxicCounter = pokemon.volatiles['residualdmg'] ? pokemon.volatiles['residualdmg'].counter : 1; this.damage(this.clampIntRange(Math.floor(pokemon.maxhp / 16), 1) * toxicCounter, pokemon); }, onSwitchIn(pokemon) { @@ -118,11 +114,7 @@ let BattleStatuses = { }, onAfterMoveSelfPriority: 2, onAfterMoveSelf(pokemon) { - let toxicCounter = 1; - if (pokemon.volatiles['residualdmg']) { - pokemon.volatiles['residualdmg'].counter++; - toxicCounter = pokemon.volatiles['residualdmg'].counter; - } + let toxicCounter = pokemon.volatiles['residualdmg'] ? pokemon.volatiles['residualdmg'].counter : 1; this.damage(this.clampIntRange(Math.floor(pokemon.maxhp / 16), 1) * toxicCounter, pokemon); }, onAfterSwitchInSelf(pokemon) { @@ -130,27 +122,8 @@ let BattleStatuses = { }, }, tox: { - name: 'tox', - id: 'tox', - num: 0, - effectType: 'Status', - onStart(target) { - this.add('-status', target, 'tox'); - if (!target.volatiles['residualdmg']) target.addVolatile('residualdmg'); - target.volatiles['residualdmg'].counter = 0; - }, + inherit: true, onAfterMoveSelfPriority: 2, - onAfterMoveSelf(pokemon) { - pokemon.volatiles['residualdmg'].counter++; - this.damage(this.clampIntRange(Math.floor(pokemon.maxhp / 16), 1) * pokemon.volatiles['residualdmg'].counter, pokemon, pokemon); - }, - onSwitchIn(pokemon) { - // Regular poison status and damage after a switchout -> switchin. - pokemon.setStatus('psn'); - }, - onAfterSwitchInSelf(pokemon) { - this.damage(this.clampIntRange(Math.floor(pokemon.maxhp / 16), 1)); - }, }, confusion: { name: 'confusion', diff --git a/data/mods/gen2/statuses.js b/data/mods/gen2/statuses.js index 932d4226950b..691e9f255a97 100644 --- a/data/mods/gen2/statuses.js +++ b/data/mods/gen2/statuses.js @@ -12,10 +12,10 @@ let BattleStatuses = { }, onAfterMoveSelfPriority: 3, onAfterMoveSelf(pokemon) { - this.damage(pokemon.maxhp / 8); + residualdmg(this, pokemon); }, onAfterSwitchInSelf(pokemon) { - this.damage(pokemon.maxhp / 8); + residualdmg(this, pokemon); }, }, par: { @@ -89,10 +89,10 @@ let BattleStatuses = { }, onAfterMoveSelfPriority: 3, onAfterMoveSelf(pokemon) { - this.damage(pokemon.maxhp / 8); + residualdmg(this, pokemon); }, onAfterSwitchInSelf(pokemon) { - this.damage(pokemon.maxhp / 8); + residualdmg(this, pokemon); }, }, tox: { @@ -102,18 +102,15 @@ let BattleStatuses = { effectType: 'Status', onStart(target) { this.add('-status', target, 'tox'); - this.effectData.stage = 0; + if (!target.volatiles['residualdmg']) target.addVolatile('residualdmg'); + target.volatiles['residualdmg'].counter = 0; }, onAfterMoveSelfPriority: 3, onAfterMoveSelf(pokemon) { - if (this.effectData.stage < 15) { - this.effectData.stage++; - } - this.damage(this.clampIntRange(pokemon.maxhp / 16, 1) * this.effectData.stage); + this.damage(this.clampIntRange(Math.floor(pokemon.maxhp / 16), 1) * pokemon.volatiles['residualdmg'].counter, pokemon, pokemon); }, onSwitchIn(pokemon) { // Regular poison status and damage after a switchout -> switchin. - this.effectData.stage = 0; pokemon.setStatus('psn'); }, onAfterSwitchInSelf(pokemon) { @@ -226,6 +223,34 @@ let BattleStatuses = { this.effectData.duration = 2; }, }, + residualdmg: { + name: 'residualdmg', + id: 'residualdmg', + num: 0, + onStart(target) { + target.volatiles['residualdmg'].counter = 0; + }, + onAfterMoveSelfPriority: 100, + onAfterMoveSelf(pokemon) { + if (['brn', 'psn', 'tox'].includes(pokemon.status)) pokemon.volatiles['residualdmg'].counter++; + }, + onAfterSwitchInSelf(pokemon) { + if (['brn', 'psn', 'tox'].includes(pokemon.status)) pokemon.volatiles['residualdmg'].counter++; + }, + }, }; +/** + * @param {Battle} battle + * @param {Pokemon} pokemon + */ +function residualdmg(battle, pokemon) { + if (pokemon.volatiles['residualdmg']) { + battle.damage(battle.clampIntRange(Math.floor(pokemon.maxhp / 16) * pokemon.volatiles['residualdmg'].counter, 1), pokemon); + battle.hint('In GSC, Toxic\'s counter is retained through Baton Pass/Heal Bell and applies to PSN/BRN.', true); + } else { + battle.damage(battle.clampIntRange(Math.floor(pokemon.maxhp / 8), 1), pokemon); + } +} + exports.BattleStatuses = BattleStatuses; diff --git a/sim/battle.ts b/sim/battle.ts index f3101989319c..04a692c69348 100644 --- a/sim/battle.ts +++ b/sim/battle.ts @@ -92,6 +92,7 @@ export class Battle extends Dex.ModdedDex { firstStaleWarned?: boolean; staleWarned?: boolean; activeTurns?: number; + hints: Set; constructor(options: BattleOptions) { let format = Dex.getFormat(options.formatid, true); @@ -157,6 +158,7 @@ export class Battle extends Dex.ModdedDex { // bound function for faster speedSort // (so speedSort doesn't need to bind before use) this.comparePriority = this.comparePriority.bind(this); + this.hints = new Set(); const inputOptions: {formatid: string, seed: PRNGSeed, rated?: string | true} = {formatid: options.formatid, seed: this.prng.seed}; if (this.rated) inputOptions.rated = this.rated; @@ -2987,6 +2989,24 @@ export class Battle extends Dex.ModdedDex { return false; } + hint(hint: string, once?: boolean, sideid?: 'p1' | 'p2') { + if (this.hints.has(hint)) return; + + if (sideid) { + for (const line of [false, this.sides[0], this.sides[1], true]) { + if (line === true || (line && line.id === sideid)) { + this.add('-hint', hint); + } else { + this.log.push(''); + } + } + } else { + this.add('-hint', hint); + } + + if (once) this.hints.add(hint); + } + add(...parts: (string | number | boolean | ((side: Side | boolean) => string) | AnyObject | null | undefined)[]) { if (!parts.some(part => typeof part === 'function')) { this.log.push(`|${parts.join('|')}`); diff --git a/test/simulator/misc/statuses.js b/test/simulator/misc/statuses.js index 9a168b8dc94e..c1bc9a17276f 100644 --- a/test/simulator/misc/statuses.js +++ b/test/simulator/misc/statuses.js @@ -205,3 +205,76 @@ describe('Toxic Poison [Gen 1]', function () { assert.strictEqual(pokemon.maxhp - pokemon.hp, Math.floor(pokemon.maxhp / 16) * 6); }); }); + + +describe('Toxic Poison [Gen 2]', function () { + afterEach(function () { + battle.destroy(); + }); + + it('should not affect Leech Seed damage counter', function () { + battle = common.gen(2).createBattle([ + [{species: 'Venusaur', moves: ['toxic', 'leechseed']}], + [{species: 'Chansey', moves: ['splash']}], + ]); + battle.makeChoices('move toxic', 'move splash'); + let pokemon = battle.p2.active[0]; + assert.strictEqual(pokemon.maxhp - pokemon.hp, Math.floor(pokemon.maxhp / 16)); + battle.makeChoices('move leechseed', 'move splash'); + // (1/16) + (2/16) + (1/8) = (5/16) + assert.strictEqual(pokemon.maxhp - pokemon.hp, Math.floor(pokemon.maxhp / 16) * 5); + }); + + it('should pass the damage counter to Pokemon with Baton Pass', function () { + battle = common.gen(2).createBattle([ + [{species: 'Smeargle', moves: ['toxic', 'sacredfire', 'splash']}], + [ + {species: 'Chansey', moves: ['splash']}, + {species: 'Celebi', moves: ['batonpass', 'splash']}, + ], + ]); + battle.resetRNG(); // Guarantee Sacred Fire burns + battle.makeChoices('move sacredfire', 'move splash'); + let pokemon = battle.p2.active[0]; + battle.resetRNG(); // Guarantee Toxic hits. + battle.makeChoices('move toxic', 'switch 2'); + battle.makeChoices('move splash', 'move splash'); + battle.makeChoices('move splash', 'move splash'); + battle.makeChoices('move splash', 'move batonpass'); + battle.makeChoices('pass', 'switch 2'); + let hp = pokemon.hp; + battle.makeChoices('move splash', 'move splash'); + assert.strictEqual(hp - pokemon.hp, Math.floor(pokemon.maxhp / 16) * 4); + + // Only hint about this once per battle, not every turn. + assert.strictEqual(battle.log.filter(m => m.startsWith('|-hint')).length, 1); + + // Damage counter should be removed on regular switch out + battle.makeChoices('move splash', 'switch 2'); + hp = pokemon.hp; + battle.makeChoices('move splash', 'switch 2'); + assert.strictEqual(hp - pokemon.hp, Math.floor(pokemon.maxhp / 8)); + }); + + it('should not have its damage counter affected by Heal Bell', function () { + battle = common.gen(2).createBattle([ + [{species: 'Smeargle', moves: ['toxic', 'sacredfire', 'splash']}], + [{species: 'Chansey', moves: ['splash', 'healbell']}], + ]); + battle.makeChoices('move toxic', 'move splash'); + let pokemon = battle.p2.active[0]; + battle.makeChoices('move splash', 'move healbell'); + battle.resetRNG(); // Guarantee Sacred Fire burns + battle.makeChoices('move sacredfire', 'move splash'); + let hp = pokemon.hp; + battle.makeChoices('move splash', 'move splash'); + assert.strictEqual(hp - pokemon.hp, Math.floor(pokemon.maxhp / 16) * 3); + hp = pokemon.hp; + + battle.makeChoices('move splash', 'move healbell'); + battle.resetRNG(); // Guarantee Toxic hits + battle.makeChoices('move toxic', 'move splash'); + // Toxic counter should be reset by a successful Toxic + assert.strictEqual(hp - pokemon.hp, Math.floor(pokemon.maxhp / 16)); + }); +});