From 8834ceba10c5e51b9c76b886cc2ff8d88b972177 Mon Sep 17 00:00:00 2001 From: Shriansh Chari <30420527+shrianshChari@users.noreply.github.com> Date: Fri, 30 Aug 2024 10:30:56 -0400 Subject: [PATCH] Programmatically compute lesser offensive moves --- stats/src/classifier.ts | 46 +++++++++++++++++++++++++------ stats/src/test/classifier.test.ts | 12 ++++++++ 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/stats/src/classifier.ts b/stats/src/classifier.ts index 7687fc9..99ac6e7 100644 --- a/stats/src/classifier.ts +++ b/stats/src/classifier.ts @@ -20,8 +20,9 @@ export const Classifier = new class { paralysis: PARALYSIS_MOVES, confusion: CONFUSION_MOVES, sleep: SLEEP_MOVES, - greaterOffensive: GREATER_OFFENSIVE_MOVES, ohko: OHKO_MOVES, + greaterOffensive: GREATER_OFFENSIVE_MOVES, + lesserOffensive: LESSER_OFFENSIVE_MOVES, } : this.caches[gen.num] || (this.caches[gen.num] = { greaterSetup: computeGreaterSetupMoves(gen), lesserSetup: computeLesserSetupMoves(gen), @@ -33,8 +34,9 @@ export const Classifier = new class { paralysis: computeParalysisMoves(gen), confusion: computeConfusionMoves(gen), sleep: computeSleepMoves(gen), - greaterOffensive: computeGreaterOffensiveMoves(gen), ohko: computeOHKOMoves(gen), + greaterOffensive: computeGreaterOffensiveMoves(gen), + lesserOffensive: computeLesserOffensiveMoves(gen), }); let teamBias = 0; @@ -473,12 +475,6 @@ function itemStallinessModifier(pokemon: PokemonSet) { return 0; } -const LESSER_OFFENSIVE_MOVES = new Set([ - 'jumpkick', 'doubleedge', 'submission', 'petaldance', 'hijumpkick', 'outrage', - 'volttackle', 'closecombat', 'flareblitz', 'bravebird', 'woodhammer', 'headsmash', - 'headcharge', 'wildcharge', 'takedown', 'dragonascent', -]); - function movesStallinessModifier(pokemon: PokemonSet, tables: {[name: string]: Set}) { const moves = new Set(pokemon.moves as string[]); @@ -501,7 +497,7 @@ function movesStallinessModifier(pokemon: PokemonSet, tables: {[name: string if (pokemon.moves.some((m: ID) => tables.paralysis.has(m))) mod += 0.5; if (pokemon.moves.some((m: ID) => tables.confusion.has(m))) mod += 0.5; if (pokemon.moves.some((m: ID) => tables.sleep.has(m))) mod -= 0.5; - if (pokemon.moves.some((m: ID) => LESSER_OFFENSIVE_MOVES.has(m))) mod -= 0.5; + if (pokemon.moves.some((m: ID) => tables.lesserOffensive.has(m))) mod -= 0.5; if (pokemon.moves.some((m: ID) => tables.greaterOffensive.has(m))) mod -= 1.0; if (pokemon.moves.some((m: ID) => tables.ohko.has(m))) mod -= 1.0; @@ -761,6 +757,38 @@ export function computeGreaterOffensiveMoves(gen: Generation) { ]); } +export const LESSER_OFFENSIVE_MOVES = new Set([ + 'jumpkick', 'doubleedge', 'submission', 'petaldance', 'hijumpkick', 'outrage', + 'volttackle', 'closecombat', 'flareblitz', 'bravebird', 'woodhammer', 'headsmash', + 'headcharge', 'wildcharge', 'takedown', 'dragonascent', +] as ID[]); + +export function computeLesserOffensiveMoves(gen: Generation) { + const moves = Array.from(gen.moves); + + // Moves that inflict recoil + const recoil = moves.filter(m => m.recoil).map(m => m.id); + // Moves that inflict damage to the user if they miss + const crashDamage = moves.filter(m => m.hasCrashDamage).map(m => m.id); + // Moves that lock the user into using the move for multiple turns + const lockedMove = moves.filter(m => m.self && m.self.volatileStatus === 'lockedmove') + .map(m => m.id); + // Moves that drop the user's defenses (but not their attack or speed) + const dropDefenses = moves.filter(m => m.self?.boosts && + ((m.self.boosts.def && m.self.boosts.def < 0) || + (m.self.boosts.spd && m.self.boosts.spd < 0)) && + !((m.self.boosts.atk && m.self.boosts.atk < 0) || + (m.self.boosts.spa && m.self.boosts.spa < 0) || + (m.self.boosts.spe && m.self.boosts.spe < 0))).map(m => m.id); + + return new Set([ + ...recoil, + ...crashDamage, + ...lockedMove, + ...dropDefenses, + ]); +} + function targetsFoes(move: Move) { return ['normal', 'adjacentFoe', 'allAdjacentFoes', 'foeSide'].includes(move.target); } diff --git a/stats/src/test/classifier.test.ts b/stats/src/test/classifier.test.ts index b2e2af5..9973130 100644 --- a/stats/src/test/classifier.test.ts +++ b/stats/src/test/classifier.test.ts @@ -111,4 +111,16 @@ describe('Classifier', () => { expect(COMPUTED_GREATER_OFFENSIVE_MOVES).toEqual(classifier.GREATER_OFFENSIVE_MOVES); }); + test('LESSER_OFFENSIVE_MOVES', () => { + const COMPUTED_LESSER_OFFENSIVE_MOVES = classifier.computeLesserOffensiveMoves(GEN); + + // Lesser offensive that Antar incorrectly included (nothing) + // "High" Jump Kick used to be spelled as "Hi" and that spelling was used + expect(getDifference(classifier.LESSER_OFFENSIVE_MOVES, COMPUTED_LESSER_OFFENSIVE_MOVES)) + .toEqual(new Set(['hijumpkick'])); + + // OHKO moves that Antar forgot to include + expect(getDifference(COMPUTED_LESSER_OFFENSIVE_MOVES, classifier.LESSER_OFFENSIVE_MOVES)) + .toEqual(new Set(['hyperspacefury', 'highjumpkick', 'thrash'])); + }); });