diff --git a/data/abilities.ts b/data/abilities.ts index 6feb2f2439..3c86fe588d 100644 --- a/data/abilities.ts +++ b/data/abilities.ts @@ -740,33 +740,29 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = { num: 238, }, cudchew: { - onEatItem(item, pokemon) { - if (item.isBerry && pokemon.addVolatile('cudchew')) { - pokemon.volatiles['cudchew'].berry = item; + onEatItem(item, pokemon, source, effect) { + if (item.isBerry && (!effect || !['bugbite', 'pluck'].includes(effect.id))) { + this.effectState.berry = item; + this.effectState.counter = 2; + // This is needed in case the berry was eaten during residuals, preventing the timer from decreasing this turn + if (!this.queue.peek()) this.effectState.counter--; } }, - onEnd(pokemon) { - delete pokemon.volatiles['cudchew']; - }, - condition: { - noCopy: true, - duration: 2, - onRestart() { - this.effectState.duration = 2; - }, - onResidualOrder: 28, - onResidualSubOrder: 2, - onEnd(pokemon) { - if (pokemon.hp) { - const item = this.effectState.berry; - this.add('-activate', pokemon, 'ability: Cud Chew'); - this.add('-enditem', pokemon, item.name, '[eat]'); - if (this.singleEvent('Eat', item, null, pokemon, null, null)) { - this.runEvent('EatItem', pokemon, null, null, item); - } - if (item.onEat) pokemon.ateBerry = true; + onResidualOrder: 28, + onResidualSubOrder: 2, + onResidual(pokemon) { + if (!this.effectState.berry || !pokemon.hp) return; + if (--this.effectState.counter <= 0) { + const item = this.effectState.berry; + this.add('-activate', pokemon, 'ability: Cud Chew'); + this.add('-enditem', pokemon, item.name, '[eat]'); + if (this.singleEvent('Eat', item, null, pokemon, null, null)) { + this.runEvent('EatItem', pokemon, null, null, item); } - }, + if (item.onEat) pokemon.ateBerry = true; + delete this.effectState.berry; + delete this.effectState.counter; + } }, flags: {}, name: "Cud Chew", @@ -4233,34 +4229,30 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = { }, slowstart: { onStart(pokemon) { - pokemon.addVolatile('slowstart'); + this.add('-start', pokemon, 'ability: Slow Start'); + this.effectState.counter = 5; }, - onEnd(pokemon) { - delete pokemon.volatiles['slowstart']; - this.add('-end', pokemon, 'Slow Start', '[silent]'); - }, - condition: { - duration: 5, - onResidualOrder: 28, - onResidualSubOrder: 2, - onStart(target) { - this.add('-start', target, 'ability: Slow Start'); - }, - onResidual(pokemon) { - if (!pokemon.activeTurns) { - this.effectState.duration! += 1; + onResidualOrder: 28, + onResidualSubOrder: 2, + onResidual(pokemon) { + if (pokemon.activeTurns && this.effectState.counter) { + this.effectState.counter--; + if (!this.effectState.counter) { + this.add('-end', pokemon, 'Slow Start'); + delete this.effectState.counter; } - }, - onModifyAtkPriority: 5, - onModifyAtk(atk, pokemon) { + } + }, + onModifyAtkPriority: 5, + onModifyAtk(atk, pokemon) { + if (this.effectState.counter) { return this.chainModify(0.5); - }, - onModifySpe(spe, pokemon) { + } + }, + onModifySpe(spe, pokemon) { + if (this.effectState.counter) { return this.chainModify(0.5); - }, - onEnd(target) { - this.add('-end', target, 'Slow Start'); - }, + } }, flags: {}, name: "Slow Start", diff --git a/data/mods/gen4/moves.ts b/data/mods/gen4/moves.ts index 3e90ffd0c6..f8fb631d38 100644 --- a/data/mods/gen4/moves.ts +++ b/data/mods/gen4/moves.ts @@ -550,8 +550,8 @@ export const Moves: import('../../../sim/dex-moves').ModdedMoveDataTable = { this.debug(`BP: ${move.basePower}`); if (item.isBerry) { move.onHit = function (foe) { - if (this.singleEvent('Eat', item, null, foe, null, null)) { - this.runEvent('EatItem', foe, null, null, item); + if (this.singleEvent('Eat', item, source.itemState, foe, source, move)) { + this.runEvent('EatItem', foe, source, move, item); if (item.id === 'leppaberry') foe.staleness = 'external'; } if (item.onEat) foe.ateBerry = true; diff --git a/data/mods/gen7/abilities.ts b/data/mods/gen7/abilities.ts index d70cff4602..83905da67e 100644 --- a/data/mods/gen7/abilities.ts +++ b/data/mods/gen7/abilities.ts @@ -87,33 +87,18 @@ export const Abilities: import('../../../sim/dex-abilities').ModdedAbilityDataTa }, slowstart: { inherit: true, - condition: { - duration: 5, - onResidualOrder: 28, - onResidualSubOrder: 2, - onStart(target) { - this.add('-start', target, 'ability: Slow Start'); - }, - onModifyAtkPriority: 5, - onModifyAtk(atk, pokemon, target, move) { - // This is because the game checks the move's category in data, rather than what it is currently, unlike e.g. Huge Power - if (this.dex.moves.get(move.id).category === 'Physical') { - return this.chainModify(0.5); - } - }, - onModifySpAPriority: 5, - onModifySpA(spa, pokemon, target, move) { - // Ordinary Z-moves like Breakneck Blitz will halve the user's Special Attack as well - if (this.dex.moves.get(move.id).category === 'Physical') { - return this.chainModify(0.5); - } - }, - onModifySpe(spe, pokemon) { + onModifyAtk(atk, pokemon, target, move) { + // This is because the game checks the move's category in data, rather than what it is currently, unlike e.g. Huge Power + if (this.effectState.counter && this.dex.moves.get(move.id).category === 'Physical') { return this.chainModify(0.5); - }, - onEnd(target) { - this.add('-end', target, 'Slow Start'); - }, + } + }, + onModifySpAPriority: 5, + onModifySpA(spa, pokemon, target, move) { + // Ordinary Z-moves like Breakneck Blitz will halve the user's Special Attack as well + if (this.effectState.counter && this.dex.moves.get(move.id).category === 'Physical') { + return this.chainModify(0.5); + } }, }, soundproof: { diff --git a/data/moves.ts b/data/moves.ts index 78ad165459..8f26c3e7ee 100644 --- a/data/moves.ts +++ b/data/moves.ts @@ -1982,12 +1982,12 @@ export const Moves: import('../sim/dex-moves').MoveDataTable = { pp: 20, priority: 0, flags: { contact: 1, protect: 1, mirror: 1, metronome: 1 }, - onHit(target, source) { + onHit(target, source, move) { const item = target.getItem(); if (source.hp && item.isBerry && target.takeItem(source)) { this.add('-enditem', target, item.name, '[from] stealeat', '[move] Bug Bite', `[of] ${source}`); - if (this.singleEvent('Eat', item, null, source, null, null)) { - this.runEvent('EatItem', source, null, null, item); + if (this.singleEvent('Eat', item, target.itemState, source, source, move)) { + this.runEvent('EatItem', source, source, move, item); if (item.id === 'leppaberry') target.staleness = 'external'; } if (item.onEat) source.ateBerry = true; @@ -5950,9 +5950,12 @@ export const Moves: import('../sim/dex-moves').MoveDataTable = { move.basePower = item.fling.basePower; this.debug(`BP: ${move.basePower}`); if (item.isBerry) { + if (source.hasAbility('cudchew')) { + this.singleEvent('EatItem', source.getAbility(), source.abilityState, source, source, move, item); + } move.onHit = function (foe) { - if (this.singleEvent('Eat', item, null, foe, null, null)) { - this.runEvent('EatItem', foe, null, null, item); + if (this.singleEvent('Eat', item, source.itemState, foe, source, move)) { + this.runEvent('EatItem', foe, source, move, item); if (item.id === 'leppaberry') foe.staleness = 'external'; } if (item.onEat) foe.ateBerry = true; @@ -13918,12 +13921,12 @@ export const Moves: import('../sim/dex-moves').MoveDataTable = { pp: 20, priority: 0, flags: { contact: 1, protect: 1, mirror: 1, distance: 1, metronome: 1 }, - onHit(target, source) { + onHit(target, source, move) { const item = target.getItem(); if (source.hp && item.isBerry && target.takeItem(source)) { this.add('-enditem', target, item.name, '[from] stealeat', '[move] Pluck', `[of] ${source}`); - if (this.singleEvent('Eat', item, null, source, null, null)) { - this.runEvent('EatItem', source, null, null, item); + if (this.singleEvent('Eat', item, target.itemState, source, source, move)) { + this.runEvent('EatItem', source, source, move, item); if (item.id === 'leppaberry') target.staleness = 'external'; } if (item.onEat) source.ateBerry = true; diff --git a/sim/dex-conditions.ts b/sim/dex-conditions.ts index dd58a299f9..990ce7cd93 100644 --- a/sim/dex-conditions.ts +++ b/sim/dex-conditions.ts @@ -48,7 +48,7 @@ export interface EventMethods { onDeductPP?: (this: Battle, target: Pokemon, source: Pokemon) => number | void; onDisableMove?: (this: Battle, pokemon: Pokemon) => void; onDragOut?: (this: Battle, pokemon: Pokemon, source?: Pokemon, move?: ActiveMove) => void; - onEatItem?: (this: Battle, item: Item, pokemon: Pokemon) => void; + onEatItem?: (this: Battle, item: Item, pokemon: Pokemon, source?: Pokemon, effect?: Effect) => void; onEffectiveness?: MoveEventMethods['onEffectiveness']; onEntryHazard?: (this: Battle, pokemon: Pokemon) => void; onFaint?: CommonHandlers['VoidEffect']; diff --git a/sim/pokemon.ts b/sim/pokemon.ts index d1fe3d5b63..47d4b2d727 100644 --- a/sim/pokemon.ts +++ b/sim/pokemon.ts @@ -1751,7 +1751,7 @@ export class Pokemon { this.battle.add('-enditem', this, item, '[eat]'); this.battle.singleEvent('Eat', item, this.itemState, this, source, sourceEffect); - this.battle.runEvent('EatItem', this, null, null, item); + this.battle.runEvent('EatItem', this, source, sourceEffect, item); if (RESTORATIVE_BERRIES.has(item.id)) { switch (this.pendingStaleness) { diff --git a/test/sim/abilities/cudchew.js b/test/sim/abilities/cudchew.js new file mode 100644 index 0000000000..f04630c69b --- /dev/null +++ b/test/sim/abilities/cudchew.js @@ -0,0 +1,91 @@ +'use strict'; + +const assert = require('./../../assert'); +const common = require('./../../common'); + +let battle; + +describe('Cud Chew', () => { + afterEach(() => { + battle.destroy(); + }); + + it(`should re-activate a berry, eaten in the previous turn`, () => { + battle = common.createBattle([[ + { species: 'tauros', ability: 'cudchew', item: 'lumberry', moves: ['sleeptalk'] }, + ], [ + { species: 'toxicroak', moves: ['toxic'] }, + ]]); + const tauros = battle.p1.active[0]; + battle.makeChoices(); + assert.equal(tauros.status, ''); + battle.makeChoices(); + assert.equal(tauros.status, ''); + battle.makeChoices(); + assert.equal(tauros.status, 'tox'); + }); + + it(`should re-activate a berry flung in the previous turn, for both the attacker and the target`, () => { + battle = common.createBattle({ gameType: 'doubles' }, [[ + { species: 'tauros', ability: 'cudchew', item: 'lumberry', moves: ['fling', 'sleeptalk'] }, + { species: 'foongus', moves: ['toxic', 'sleeptalk'] }, + ], [ + { species: 'farigiraf', ability: 'cudchew', moves: ['sleeptalk'] }, + { species: 'grimer', moves: ['toxic', 'sleeptalk'] }, + ]]); + const tauros = battle.p1.active[0]; + const farigiraf = battle.p2.active[0]; + battle.makeChoices('move fling 1, move toxic 1', 'move sleeptalk, move toxic 1'); + assert.equal(tauros.status, 'tox'); + assert.equal(farigiraf.status, 'tox'); + battle.makeChoices('move sleeptalk, move sleeptalk', 'move sleeptalk, move sleeptalk'); + assert.equal(tauros.status, ''); + assert.equal(farigiraf.status, ''); + }); + + it(`should not re-activate a berry eaten by Bug Bite, for either the attacker or the target`, () => { + battle = common.createBattle({ gameType: 'doubles' }, [[ + { species: 'tauros', ability: 'cudchew', moves: ['bugbite', 'sleeptalk'] }, + { species: 'foongus', moves: ['toxic', 'sleeptalk'] }, + ], [ + { species: 'farigiraf', ability: 'cudchew', item: 'lumberry', moves: ['sleeptalk'] }, + { species: 'grimer', moves: ['toxic', 'sleeptalk'] }, + ]]); + const tauros = battle.p1.active[0]; + const farigiraf = battle.p2.active[0]; + battle.makeChoices('move bugbite 1, move toxic 1', 'move sleeptalk, move toxic 1'); + assert.equal(tauros.status, 'tox'); + assert.equal(farigiraf.status, 'tox'); + battle.makeChoices('move sleeptalk, move sleeptalk', 'move sleeptalk, move sleeptalk'); + assert.equal(tauros.status, 'tox'); + assert.equal(farigiraf.status, 'tox'); + }); + + it(`should not be prevented by Unnerve`, () => { + battle = common.createBattle({ gameType: 'doubles' }, [[ + { species: 'tauros', ability: 'cudchew', item: 'lumberry', moves: ['sleeptalk'] }, + { species: 'wynaut', moves: ['sleeptalk'] }, + ], [ + { species: 'toxicroak', moves: ['toxic'] }, + { species: 'magikarp', moves: ['sleeptalk'] }, + { species: 'mewtwo', ability: 'unnerve', moves: ['sleeptalk'] }, + ]]); + const tauros = battle.p1.active[0]; + battle.makeChoices(); + assert.equal(tauros.status, ''); + battle.makeChoices('auto', 'move toxic 1, switch 3'); + assert.equal(tauros.status, ''); + }); + + it(`should still activate in the following turn if the berry was consumed during residuals`, () => { + battle = common.createBattle([[ + { species: 'tauros', ability: 'cudchew', item: 'sitrusberry', moves: ['bellydrum'] }, + ], [ + { species: 'toxicroak', moves: ['toxic'] }, + ]]); + const tauros = battle.p1.active[0]; + battle.makeChoices(); + battle.makeChoices(); + assert(tauros.hp > tauros.maxhp * 3 / 4, 'Tauros should have eaten its berry twice'); + }); +}); diff --git a/test/sim/abilities/neutralizinggas.js b/test/sim/abilities/neutralizinggas.js index b3f543844c..9aff3055a4 100644 --- a/test/sim/abilities/neutralizinggas.js +++ b/test/sim/abilities/neutralizinggas.js @@ -274,6 +274,43 @@ describe('Neutralizing Gas', () => { assert.species(eiscue, 'Eiscue-Noice'); }); + it(`should delay the activation of Cud Chew`, () => { + battle = common.createBattle({ gameType: 'doubles' }, [[ + { species: 'tauros', ability: 'cudchew', item: 'lumberry', moves: ['sleeptalk'] }, + { species: 'wynaut', moves: ['sleeptalk'] }, + ], [ + { species: 'toxicroak', moves: ['toxic'] }, + { species: 'magikarp', moves: ['sleeptalk'] }, + { species: 'weezing', ability: 'neutralizinggas', moves: ['sleeptalk'] }, + ]]); + let tauros = battle.p1.active[0]; + battle.makeChoices(); + assert.equal(tauros.status, ''); + battle.makeChoices('auto', 'move toxic 1, switch 3'); + assert.equal(tauros.status, 'tox'); + battle.makeChoices('auto', 'move toxic 1, switch 3'); + assert.equal(tauros.status, ''); + + battle = common.createBattle({ gameType: 'doubles' }, [[ + { species: 'tauros', ability: 'cudchew', item: 'lumberry', moves: ['sleeptalk'] }, + { species: 'wynaut', moves: ['sleeptalk'] }, + ], [ + { species: 'toxicroak', moves: ['toxic'] }, + { species: 'magikarp', moves: ['sleeptalk', 'uturn'] }, + { species: 'weezing', ability: 'neutralizinggas', moves: ['sleeptalk'] }, + ]]); + tauros = battle.p1.active[0]; + battle.makeChoices('auto', 'move toxic 1, move uturn 2'); + battle.makeChoices(); + assert.equal(tauros.status, ''); // 0 turns passed before Neutralizing Gas + battle.makeChoices(); + assert.equal(tauros.status, 'tox'); + battle.makeChoices('auto', 'move toxic 1, switch 3'); + assert.equal(tauros.status, 'tox'); // 1 turn has passed + battle.makeChoices(); + assert.equal(tauros.status, ''); + }); + it(`should not work if it was obtained via Transform`, () => { battle = common.createBattle([[ { species: 'Ditto', moves: ['transform'] },