diff --git a/play.pokemonshowdown.com/src/battle-dex-data.ts b/play.pokemonshowdown.com/src/battle-dex-data.ts index f4429238d..2a6af9b48 100644 --- a/play.pokemonshowdown.com/src/battle-dex-data.ts +++ b/play.pokemonshowdown.com/src/battle-dex-data.ts @@ -1597,7 +1597,7 @@ export class Species implements Effect { } export interface Type extends Effect { - damageTaken?: AnyObject; + damageTaken?: Record; HPivs?: Partial; HPdvs?: Partial; } diff --git a/play.pokemonshowdown.com/src/battle-dex.ts b/play.pokemonshowdown.com/src/battle-dex.ts index c3afc408e..b9236d2d4 100644 --- a/play.pokemonshowdown.com/src/battle-dex.ts +++ b/play.pokemonshowdown.com/src/battle-dex.ts @@ -45,6 +45,11 @@ export declare namespace Dex { export type GenderName = DexData.GenderName; export type NatureName = DexData.NatureName; export type MoveTarget = DexData.MoveTarget; + export type REGULAR = 0; + export type WEAK = 1; + export type RESIST = 2; + export type IMMUNE = 3; + export type WeaknessType = REGULAR | WEAK | RESIST | IMMUNE; export type StatsTable = { hp: number, atk: number, def: number, spa: number, spd: number, spe: number }; /** * Dex.PokemonSet can be sparse, in which case that entry should be @@ -249,6 +254,11 @@ export const Dex = new class implements ModdedDex { readonly modid = 'gen9' as ID; readonly cache = null!; + readonly REGULAR = 0; + readonly WEAK = 1; + readonly RESIST = 2; + readonly IMMUNE = 3; + readonly statNames: readonly Dex.StatName[] = ['hp', 'atk', 'def', 'spa', 'spd', 'spe']; readonly statNamesExceptHP: readonly Dex.StatNameExceptHP[] = ['atk', 'def', 'spa', 'spd', 'spe']; @@ -512,6 +522,7 @@ export const Dex = new class implements ModdedDex { types = { allCache: null as Type[] | null, + namesCache: null as Dex.TypeName[] | null, get: (type: any): Type => { if (!type || typeof type === 'string') { const id = toID(type) as string; @@ -535,6 +546,13 @@ export const Dex = new class implements ModdedDex { if (types.length) this.types.allCache = types; return types; }, + names: (): readonly Dex.TypeName[] => { + if (this.types.namesCache) return this.types.namesCache; + const names = this.types.all().map(type => type.name as Dex.TypeName); + names.splice(names.indexOf('Stellar'), 1); + if (names.length) this.types.namesCache = names; + return names; + }, isName: (name: string | null): boolean => { const id = toID(name); if (name !== id.substr(0, 1).toUpperCase() + id.substr(1)) return false; @@ -1092,7 +1110,20 @@ export class ModdedDex { }; types = { - get: (name: string): Dex.Effect => { + namesCache: null as readonly Dex.TypeName[] | null, + names: (): readonly Dex.TypeName[] => { + if (this.types.namesCache) return this.types.namesCache; + const names = Dex.types.names(); + if (!names.length) return []; + const curNames = [...names]; + // if (this.gen < 9) curNames.splice(curNames.indexOf('Stellar'), 1); + if (this.gen < 6) curNames.splice(curNames.indexOf('Fairy'), 1); + if (this.gen < 2) curNames.splice(curNames.indexOf('Dark'), 1); + if (this.gen < 2) curNames.splice(curNames.indexOf('Steel'), 1); + this.types.namesCache = curNames; + return curNames; + }, + get: (name: string): Dex.Type => { const id = toID(name); name = id.substr(0, 1).toUpperCase() + id.substr(1); diff --git a/play.pokemonshowdown.com/src/battle-team-editor.tsx b/play.pokemonshowdown.com/src/battle-team-editor.tsx index 2e0412bd9..0f758eff4 100644 --- a/play.pokemonshowdown.com/src/battle-team-editor.tsx +++ b/play.pokemonshowdown.com/src/battle-team-editor.tsx @@ -304,6 +304,70 @@ class TeamEditorState extends PSModel { this.sets = PSTeambuilder.importTeam(value); this.save(); } + getTypeWeakness(type: Dex.TypeName, attackType: Dex.TypeName): 0 | 0.5 | 1 | 2 { + const weaknessType = this.dex.types.get(type).damageTaken?.[attackType]; + if (weaknessType === Dex.IMMUNE) return 0; + if (weaknessType === Dex.RESIST) return 0.5; + if (weaknessType === Dex.WEAK) return 2; + return 1; + } + getWeakness(types: readonly Dex.TypeName[], abilityid: ID, attackType: Dex.TypeName): number { + if (attackType === 'Ground' && abilityid === 'levitate') return 0; + if (attackType === 'Water' && abilityid === 'dryskin') return 0; + if (attackType === 'Fire' && abilityid === 'flashfire') return 0; + if (attackType === 'Electric' && abilityid === 'lightningrod' && this.gen >= 5) return 0; + if (attackType === 'Grass' && abilityid === 'sapsipper') return 0; + if (attackType === 'Electric' && abilityid === 'motordrive') return 0; + if (attackType === 'Water' && abilityid === 'stormdrain' && this.gen >= 5) return 0; + if (attackType === 'Electric' && abilityid === 'voltabsorb') return 0; + if (attackType === 'Water' && abilityid === 'waterabsorb') return 0; + if (attackType === 'Ground' && abilityid === 'eartheater') return 0; + if (attackType === 'Fire' && abilityid === 'wellbakedbody') return 0; + + if (abilityid === 'wonderguard') { + for (const type of types) { + if (this.getTypeWeakness(type, attackType) <= 1) return 0; + } + } + + let factor = 1; + for (const type of types) { + factor *= this.getTypeWeakness(type, attackType); + } + return factor; + } + pokemonDefensiveCoverage(set: Dex.PokemonSet) { + const coverage: Record = {}; + const species = this.dex.species.get(set.species); + const abilityid = toID(set.ability); + for (const type of this.dex.types.names()) { + coverage[type] = this.getWeakness(species.types, abilityid, type); + } + return coverage as Record; + } + teamDefensiveCoverage() { + const counters: Record> = {} as any; + for (const type of this.dex.types.names()) { + counters[type] = { + resists: 0, + neutrals: 0, + weaknesses: 0, + }; + } + for (const set of this.sets) { + const coverage = this.pokemonDefensiveCoverage(set); + for (const [type, value] of Object.entries(coverage) as [Dex.TypeName, number][]) { + if (value < 1) { + counters[type].resists++; + } else if (value === 1) { + counters[type].neutrals++; + } else { + counters[type].weaknesses++; + } + } + } + return counters; + } save() { this.team.packedTeam = PSTeambuilder.packTeam(this.sets); this.team.iconCache = null; @@ -987,75 +1051,86 @@ class TeamTextbox extends preact.Component<{ editor: TeamEditorState, onChange?: bottomY() { return this.setInfo[this.setInfo.length - 1]?.bottomY ?? 8; } + copyAll = () => { + this.textbox.select(); + document.execCommand('copy'); + }; render() { const editor = this.props.editor; const statsDetailsOffset = editor.gen >= 3 ? 18 : -1; - return
-