diff --git a/config/formats.ts b/config/formats.ts index 03a53c0690..2e58088b14 100644 --- a/config/formats.ts +++ b/config/formats.ts @@ -895,6 +895,135 @@ export const Formats: FormatList = [ } }, }, + { + name: "[Gen 9] Fortemons", + desc: `Put an attacking move in the item slot to have all of a Pokémon's attacks inherit its properties.`, + threads: [ + `• Fortemons`, + ], + + mod: 'gen9', + searchShow: false, + ruleset: ['Standard OMs', 'Sleep Clause Mod', 'Min Source Gen = 9'], + banlist: ['Koraidon', 'Miraidon', 'Palafin', 'Covert Cloak', 'Fake Out'], + restricted: ['Dynamic Punch', 'Inferno', 'Mud Slap', 'Nuzzle', 'Power Trip', 'Rapid Spin', 'Stored Power', 'Zap Cannon'], + validateSet(set, teamHas) { + const item = set.item; + const species = this.dex.species.get(set.species); + const move = this.dex.moves.get(item); + if (!move.exists || move.id === 'metronome' || move.category === 'Status') { + return this.validateSet(set, teamHas); + } + set.item = ''; + const problems = this.validateSet(set, teamHas) || []; + set.item = item; + if (this.checkCanLearn(move, species, this.allSources(species), set)) { + problems.push(`${species.name} can't learn ${move.name}.`); + } + if ((move.secondaries?.some(secondary => secondary.boosts?.accuracy && secondary.boosts.accuracy < 0) || + move.multihit || move.id === 'beatup' || move.flags['charge'] || move.priority > 0 || move.damageCallback) && + !this.ruleTable.has(`+move:${move.id}`)) { + problems.push(`The move ${move.name} can't be used as an item.`); + } + return problems.length ? problems : null; + }, + onBegin() { + for (const pokemon of this.getAllPokemon()) { + const move = this.dex.getActiveMove(pokemon.set.item); + if (move.exists && move.category !== 'Status') { + pokemon.m.forte = move; + pokemon.item = 'mail' as ID; + } + } + }, + onModifyMovePriority: 1, + onModifyMove(move, pokemon, target) { + const forte: ActiveMove = pokemon.m.forte; + if (move.category !== 'Status' && forte) { + move.flags = {...move.flags, ...forte.flags}; + if (forte.self) { + if (forte.self.onHit && move.self?.onHit) { + for (const i in forte.self) { + if (i.startsWith('onHit')) continue; + (move.self as any)[i] = (forte.self as any)[i]; + } + } else { + move.self = {...(move.self || {}), ...forte.self}; + } + } + if (forte.selfBoost?.boosts) { + if (!move.selfBoost?.boosts) move.selfBoost = {boosts: {}}; + let boostid: BoostID; + for (boostid in forte.selfBoost.boosts) { + if (!move.selfBoost.boosts![boostid]) move.selfBoost.boosts![boostid] = 0; + move.selfBoost.boosts![boostid]! += forte.selfBoost.boosts[boostid]!; + } + } + if (forte.secondaries) { + move.secondaries = [...(move.secondaries || []), ...forte.secondaries]; + } + move.critRatio = (move.critRatio || 1) + (forte.critRatio || 1) - 1; + const VALID_PROPERTIES = [ + 'alwaysHit', 'basePowerCallback', 'breaksProtect', 'drain', 'forceSTAB', 'forceSwitch', 'hasCrashDamage', 'hasSheerForce', + 'ignoreAbility', 'ignoreAccuracy', 'ignoreDefensive', 'ignoreEvasion', 'ignoreImmunity', 'mindBlownRecoil', 'noDamageVariance', + 'ohko', 'overrideDefensivePokemon', 'overrideDefensiveStat', 'overrideOffensivePokemon', 'overrideOffensiveStat', 'pseudoWeather', + 'recoil', 'selfdestruct', 'selfSwitch', 'sleepUsable', 'smartTarget', 'stealsBoosts', 'thawsTarget', 'volatileStatus', 'willCrit', + ] as const; + for (const property of VALID_PROPERTIES) { + if (forte[property]) { + move[property] = forte[property] as any; + } + } + } + }, + onModifyPriority(priority, source, target, move) { + if (move.category !== 'Status' && source?.m.forte) { + if (source.hasAbility('Triage') && source.m.forte.flags['heal']) { + return priority + (move.flags['heal'] ? 0 : 3); + } + return priority + source.m.forte.priority; + } + }, + onHitPriority: 1, + onHit(target, source, move) { + const forte = source.m.forte; + if (move?.category !== 'Status' && forte) { + if (forte.onHit) this.singleEvent('Hit', forte, {}, target, source, move); + if (forte.self?.onHit) this.singleEvent('Hit', forte.self, {}, source, source, move); + if (forte.onAfterHit) this.singleEvent('AfterHit', forte, {}, target, source, move); + } + }, + onAfterSubDamage(damage, target, source, move) { + const forte = source.m.forte; + if (move?.category !== 'Status' && forte?.onAfterSubDamage) { + this.singleEvent('AfterSubDamage', forte, null, target, source, move); + } + }, + onModifySecondaries(secondaries, target, source, move) { + if (secondaries.some(s => !!s.self)) move.selfDropped = false; + }, + onAfterMoveSecondaryPriority: 1, + onAfterMoveSecondarySelf(source, target, move) { + const forte = source.m.forte; + if (move?.category !== 'Status' && forte?.onAfterMoveSecondarySelf) { + this.singleEvent('AfterMoveSecondarySelf', forte, null, source, target, move); + } + }, + onBasePowerPriority: 1, + onBasePower(basePower, source, target, move) { + const forte = source.m.forte; + if (move.category !== 'Status' && forte?.onBasePower) { + this.singleEvent('BasePower', forte, null, source, target, move, basePower); + } + }, + pokemon: { + getItem() { + const move = this.battle.dex.moves.get(this.m.forte); + if (!move.exists) return Object.getPrototypeOf(this).getItem.call(this); + return {...this.battle.dex.items.get('mail'), ignoreKlutz: true, onTakeItem: false}; + }, + }, + }, { name: "[Gen 9] Full Potential", desc: `Pokémon's moves hit off of their highest stat.`,