From effac1794e9280a46eb99eb895ed0ee1ce51f63f Mon Sep 17 00:00:00 2001 From: ShivaD173 Date: Fri, 1 Nov 2024 16:07:18 -0400 Subject: [PATCH] Fix Knock Off damage in Klutz/Magic Room (#651) A `Pokemon#disabledItem` field is used to check Knock Off damage during Klutz/Magic Room. --- calc/src/mechanics/gen56.ts | 18 +++++++++-------- calc/src/mechanics/gen789.ts | 39 ++++++++++++++++++------------------ calc/src/mechanics/util.ts | 1 + calc/src/pokemon.ts | 1 + calc/src/test/calc.test.ts | 17 ++++++++++++++++ 5 files changed, 49 insertions(+), 27 deletions(-) diff --git a/calc/src/mechanics/gen56.ts b/calc/src/mechanics/gen56.ts index a36fc8cf1..8000d4823 100644 --- a/calc/src/mechanics/gen56.ts +++ b/calc/src/mechanics/gen56.ts @@ -620,18 +620,20 @@ export function calculateBPModsBWXY( ) { const bpMods = []; + const defenderItem = (defender.item && defender.item !== '') + ? defender.item : defender.disabledItem; let resistedKnockOffDamage = - !defender.item || - (defender.named('Giratina-Origin') && defender.hasItem('Griseous Orb')) || - (defender.name.includes('Arceus') && defender.item.includes('Plate')) || - (defender.name.includes('Genesect') && defender.item.includes('Drive')) || - (defender.named('Groudon', 'Groudon-Primal') && defender.hasItem('Red Orb')) || - (defender.named('Kyogre', 'Kyogre-Primal') && defender.hasItem('Blue Orb')); + !defenderItem || + (defender.named('Giratina-Origin') && defenderItem === 'Griseous Orb') || + (defender.name.includes('Arceus') && defenderItem.includes('Plate')) || + (defender.name.includes('Genesect') && defenderItem.includes('Drive')) || + (defender.named('Groudon', 'Groudon-Primal') && defenderItem === 'Red Orb') || + (defender.named('Kyogre', 'Kyogre-Primal') && defenderItem === 'Blue Orb'); // The last case only applies when the Pokemon is holding the Mega Stone that matches its species // (or when it's already a Mega-Evolution) - if (!resistedKnockOffDamage && defender.item) { - const item = gen.items.get(toID(defender.item))!; + if (!resistedKnockOffDamage && defenderItem) { + const item = gen.items.get(toID(defenderItem))!; resistedKnockOffDamage = !!(item.megaEvolves && defender.name.includes(item.megaEvolves)); } diff --git a/calc/src/mechanics/gen789.ts b/calc/src/mechanics/gen789.ts index ea52b447d..cb8c16140 100644 --- a/calc/src/mechanics/gen789.ts +++ b/calc/src/mechanics/gen789.ts @@ -1028,30 +1028,31 @@ export function calculateBPModsSMSSSV( const bpMods = []; // Move effects - + const defenderItem = (defender.item && defender.item !== '') + ? defender.item : defender.disabledItem; let resistedKnockOffDamage = - (!defender.item || isQPActive(defender, field)) || - (defender.named('Dialga-Origin') && defender.hasItem('Adamant Crystal')) || - (defender.named('Palkia-Origin') && defender.hasItem('Lustrous Globe')) || + (!defenderItem || isQPActive(defender, field)) || + (defender.named('Dialga-Origin') && defenderItem === 'Adamant Crystal') || + (defender.named('Palkia-Origin') && defenderItem === 'Lustrous Globe') || // Griseous Core for gen 9, Griseous Orb otherwise - (defender.name.includes('Giratina-Origin') && defender.item.includes('Griseous')) || - (defender.name.includes('Arceus') && defender.item.includes('Plate')) || - (defender.name.includes('Genesect') && defender.item.includes('Drive')) || - (defender.named('Groudon', 'Groudon-Primal') && defender.hasItem('Red Orb')) || - (defender.named('Kyogre', 'Kyogre-Primal') && defender.hasItem('Blue Orb')) || - (defender.name.includes('Silvally') && defender.item.includes('Memory')) || - defender.item.includes(' Z') || - (defender.named('Zacian') && defender.hasItem('Rusted Sword')) || - (defender.named('Zamazenta') && defender.hasItem('Rusted Shield')) || - (defender.name.includes('Ogerpon-Cornerstone') && defender.hasItem('Cornerstone Mask')) || - (defender.name.includes('Ogerpon-Hearthflame') && defender.hasItem('Hearthflame Mask')) || - (defender.name.includes('Ogerpon-Wellspring') && defender.hasItem('Wellspring Mask')) || - (defender.named('Venomicon-Epilogue') && defender.hasItem('Vile Vial')); + (defender.name.includes('Giratina-Origin') && defenderItem.includes('Griseous')) || + (defender.name.includes('Arceus') && defenderItem.includes('Plate')) || + (defender.name.includes('Genesect') && defenderItem.includes('Drive')) || + (defender.named('Groudon', 'Groudon-Primal') && defenderItem === 'Red Orb') || + (defender.named('Kyogre', 'Kyogre-Primal') && defenderItem === 'Blue Orb') || + (defender.name.includes('Silvally') && defenderItem.includes('Memory')) || + defenderItem.includes(' Z') || + (defender.named('Zacian') && defenderItem === 'Rusted Sword') || + (defender.named('Zamazenta') && defenderItem === 'Rusted Shield') || + (defender.name.includes('Ogerpon-Cornerstone') && defenderItem === 'Cornerstone Mask') || + (defender.name.includes('Ogerpon-Hearthflame') && defenderItem === 'Hearthflame Mask') || + (defender.name.includes('Ogerpon-Wellspring') && defenderItem === 'Wellspring Mask') || + (defender.named('Venomicon-Epilogue') && defenderItem === 'Vile Vial'); // The last case only applies when the Pokemon has the Mega Stone that matches its species // (or when it's already a Mega-Evolution) - if (!resistedKnockOffDamage && defender.item) { - const item = gen.items.get(toID(defender.item))!; + if (!resistedKnockOffDamage && defenderItem) { + const item = gen.items.get(toID(defenderItem))!; resistedKnockOffDamage = !!item.megaEvolves && defender.name.includes(item.megaEvolves); } diff --git a/calc/src/mechanics/util.ts b/calc/src/mechanics/util.ts index 1f29a2b9d..d8fe1bc78 100644 --- a/calc/src/mechanics/util.ts +++ b/calc/src/mechanics/util.ts @@ -200,6 +200,7 @@ export function checkItem(pokemon: Pokemon, magicRoomActive?: boolean) { pokemon.hasAbility('Klutz') && !EV_ITEMS.includes(pokemon.item!) || magicRoomActive ) { + pokemon.disabledItem = pokemon.item; pokemon.item = '' as ItemName; } } diff --git a/calc/src/pokemon.ts b/calc/src/pokemon.ts index f34610701..721f4dcbe 100644 --- a/calc/src/pokemon.ts +++ b/calc/src/pokemon.ts @@ -25,6 +25,7 @@ export class Pokemon implements State.Pokemon { alliesFainted?: number; boostedStat?: I.StatIDExceptHP | 'auto'; item?: I.ItemName; + disabledItem?: I.ItemName; teraType?: I.TypeName; nature: I.NatureName; diff --git a/calc/src/test/calc.test.ts b/calc/src/test/calc.test.ts index 9fe61c395..16efa7235 100644 --- a/calc/src/test/calc.test.ts +++ b/calc/src/test/calc.test.ts @@ -479,6 +479,23 @@ describe('calc', () => { }); }); + inGens(6, 9, ({gen, calculate, Pokemon, Move}) => { + test('Knock Off vs. Klutz', () => { + const weavile = Pokemon('Weavile'); + const audino = Pokemon('Audino', {ability: 'Klutz', item: 'Leftovers'}); + const audinoMega = Pokemon('Audino', {ability: 'Klutz', item: 'Audinite'}); + const knockoff = Move('Knock Off'); + const result = calculate(weavile, audino, knockoff); + expect(result.desc()).toBe( + '0 Atk Weavile Knock Off (97.5 BP) vs. 0 HP / 0 Def Audino: 139-165 (40 - 47.5%) -- guaranteed 3HKO' + ); + const result2 = calculate(weavile, audinoMega, knockoff); + expect(result2.desc()).toBe( + '0 Atk Weavile Knock Off vs. 0 HP / 0 Def Audino: 93-111 (26.8 - 31.9%) -- guaranteed 4HKO' + ); + }); + }); + inGens(5, 9, ({gen, calculate, Pokemon, Move}) => { test(`Multi-hit interaction with Multiscale (gen ${gen})`, () => { const result = calculate(