export const Scripts: ModdedBattleScriptsData = { gen: 9, init() { for (const id in this.data.Pokedex) { const species = this.data.Pokedex[id]; if (species.isCosmeticForme) continue; if (species.types.includes('Ground')) { if (this.data.Learnsets[id]?.learnset) this.modData('Learnsets', id).learnset.thousandarrows = ['9L1']; } if (species.types.includes('Grass') && !species.types.includes('Fire')) { if (this.data.Learnsets[id]?.learnset) this.modData('Learnsets', id).learnset.solarflare = ['9L1']; } const abilities = this.modData('Pokedex', id, true).abilities; if (species.baseStats['atk'] >= 130) { const hasHP = Object.values(abilities).includes('Huge Power') || Object.values(abilities).includes('Pure Power'); if (!hasHP) { const slot = !abilities['1'] ? '1' : !abilities['H'] ? 'H' : 'S'; abilities[slot] ||= 'Huge Power'; } } const hasRegen = Object.values(abilities).includes('Regenerator'); if (!hasRegen) { const slot = !abilities['1'] ? '1' : !abilities['H'] ? 'H' : 'S'; abilities[slot] ||= 'Regenerator'; } } this.modData('Learnsets', 'tyranitar').learnset.shoreup = ['9L1']; this.modData('Learnsets', 'bastiodon').learnset.blastiodon = ['9L1']; this.modData('Learnsets', 'seaking').learnset.boltbeak = ['9L1']; this.modData('Learnsets', 'seaking').learnset.fishiousrend = ['9L1']; this.modData('Learnsets', 'ampharos').learnset.tailglow = ['9L1']; this.modData('Learnsets', 'ampharos').learnset.dracometeor = ['9L1']; this.modData('Learnsets', 'serperior').learnset.dracometeor = ['9L1']; this.modData('Learnsets', 'serperior').learnset.overheat = ['9L1']; this.modData('Learnsets', 'serperior').learnset.makeitrain = ['9L1']; this.modData('Learnsets', 'rampardos').learnset.accelerock = ['9L1']; this.modData('Learnsets', 'bibarel').learnset.bellydrum = ['9L1']; this.modData('Learnsets', 'bibarel').learnset.storedpower = ['9L1']; this.modData('Learnsets', 'bibarel').learnset.powertrip = ['9L1']; this.modData('Learnsets', 'golisopod').learnset.bellydrum = ['9L1']; this.modData('Learnsets', 'skuntank').learnset.shitpulse = ['9L1']; this.modData('Learnsets', 'dusknoir').learnset = { explosion: ['9L1'] }; for (const move of this.moves.all()) { if (move.flags['bite']) { this.modData('Learnsets', 'bruxish').learnset[move.id] = ['9L1']; } } for (const moveid in this.data.Learnsets['incineroar'].learnset) { if (this.moves.get(moveid).type === 'Dark') { delete this.modData('Learnsets', 'incineroar').learnset[moveid]; } } }, actions: { runMegaEvo(pokemon: Pokemon) { const speciesid = pokemon.canMegaEvo || pokemon.canUltraBurst; if (!speciesid) return false; pokemon.formeChange(speciesid, pokemon.getItem(), true); // Limit one mega evolution pokemon.canMegaEvo = null; this.battle.runEvent('AfterMega', pokemon); return true; }, switchIn(pokemon, pos, sourceEffect = null, isDrag) { if (!pokemon || pokemon.isActive) { this.battle.hint("A switch failed because the Pokémon trying to switch in is already in."); return false; } const side = pokemon.side; if (pos >= side.active.length) { throw new Error(`Invalid switch position ${pos} / ${side.active.length}`); } const oldActive = side.active[pos]; const unfaintedActive = oldActive?.hp ? oldActive : null; if (unfaintedActive) { oldActive.beingCalledBack = true; let switchCopyFlag: 'copyvolatile' | 'shedtail' | boolean = false; if (sourceEffect && typeof (sourceEffect as Move).selfSwitch === 'string') { switchCopyFlag = (sourceEffect as Move).selfSwitch!; } if (!oldActive.skipBeforeSwitchOutEventFlag && !isDrag) { this.battle.runEvent('BeforeSwitchOut', oldActive); if (this.battle.gen >= 5) { this.battle.eachEvent('Update'); } } oldActive.skipBeforeSwitchOutEventFlag = false; if (!this.battle.runEvent('SwitchOut', oldActive)) { // Warning: DO NOT interrupt a switch-out if you just want to trap a pokemon. // To trap a pokemon and prevent it from switching out, (e.g. Mean Look, Magnet Pull) // use the 'trapped' flag instead. // Note: Nothing in the real games can interrupt a switch-out (except Pursuit KOing, // which is handled elsewhere); this is just for custom formats. return false; } if (!oldActive.hp) { // a pokemon fainted from Pursuit before it could switch return 'pursuitfaint'; } // will definitely switch out at this point this.battle.singleEvent('End', oldActive.getAbility(), oldActive.abilityState, oldActive); this.battle.singleEvent('End', oldActive.getItem(), oldActive.itemState, oldActive); // if a pokemon is forced out by Whirlwind/etc or Eject Button/Pack, it can't use its chosen move this.battle.queue.cancelAction(oldActive); let newMove = null; if (this.battle.gen === 4 && sourceEffect) { newMove = oldActive.lastMove; } if (switchCopyFlag) { pokemon.copyVolatileFrom(oldActive, switchCopyFlag); } if (newMove) pokemon.lastMove = newMove; oldActive.clearVolatile(); } if (oldActive) { oldActive.isActive = false; oldActive.isStarted = false; oldActive.usedItemThisTurn = false; oldActive.statsRaisedThisTurn = false; oldActive.statsLoweredThisTurn = false; oldActive.position = pokemon.position; if (oldActive.fainted) oldActive.status = ''; if (this.battle.gen <= 4) { pokemon.lastItem = oldActive.lastItem; oldActive.lastItem = ''; } pokemon.position = pos; side.pokemon[pokemon.position] = pokemon; side.pokemon[oldActive.position] = oldActive; } pokemon.isActive = true; side.active[pos] = pokemon; pokemon.activeTurns = 0; pokemon.activeMoveActions = 0; for (const moveSlot of pokemon.moveSlots) { moveSlot.used = false; } pokemon.abilityState = this.battle.initEffectState({ id: pokemon.ability, target: pokemon }); pokemon.itemState = this.battle.initEffectState({ id: pokemon.item, target: pokemon }); this.battle.runEvent('BeforeSwitchIn', pokemon); if (sourceEffect) { this.battle.add(isDrag ? 'drag' : 'switch', pokemon, pokemon.getFullDetails, `[from] ${sourceEffect}`); } else { this.battle.add(isDrag ? 'drag' : 'switch', pokemon, pokemon.getFullDetails); } if (isDrag && this.battle.gen === 2) pokemon.draggedIn = this.battle.turn; pokemon.previouslySwitchedIn++; if (isDrag && this.battle.gen >= 5) { // runSwitch happens immediately so that Mold Breaker can make hazards bypass Clear Body and Levitate this.runSwitch(pokemon); } else { this.battle.queue.insertChoice({ choice: 'runSwitch', pokemon }); } if (pokemon.hasType('Flying')) { this.battle.field.addPseudoWeather('Tailwind', pokemon); } return true; }, useMoveInner(moveOrMoveName, pokemon, options) { let target = options?.target; let sourceEffect = options?.sourceEffect; const zMove = options?.zMove; const maxMove = options?.maxMove; if (!sourceEffect && this.battle.effect.id) sourceEffect = this.battle.effect; if (sourceEffect && ['instruct', 'custapberry'].includes(sourceEffect.id)) sourceEffect = null; let move = this.dex.getActiveMove(moveOrMoveName); pokemon.lastMoveUsed = move; if (move.id === 'weatherball' && zMove) { // Z-Weather Ball only changes types if it's used directly, // not if it's called by Z-Sleep Talk or something. this.battle.singleEvent('ModifyType', move, null, pokemon, target, move, move); if (move.type !== 'Normal') sourceEffect = move; } if (zMove || (move.category !== 'Status' && sourceEffect && (sourceEffect as ActiveMove).isZ)) { move = this.getActiveZMove(move, pokemon); } if (maxMove && move.category !== 'Status') { // Max move outcome is dependent on the move type after type modifications from ability and the move itself this.battle.singleEvent('ModifyType', move, null, pokemon, target, move, move); this.battle.runEvent('ModifyType', pokemon, target, move, move); } if (maxMove || (move.category !== 'Status' && sourceEffect && (sourceEffect as ActiveMove).isMax)) { move = this.getActiveMaxMove(move, pokemon); } if (this.battle.activeMove) { move.priority = this.battle.activeMove.priority; if (!move.hasBounced) move.pranksterBoosted = this.battle.activeMove.pranksterBoosted; } const baseTarget = move.target; let targetRelayVar = { target }; targetRelayVar = this.battle.runEvent('ModifyTarget', pokemon, target, move, targetRelayVar, true); if (targetRelayVar.target !== undefined) target = targetRelayVar.target; if (target === undefined) target = this.battle.getRandomTarget(pokemon, move); if (move.target === 'self' || move.target === 'allies') { target = pokemon; } if (sourceEffect) { move.sourceEffect = sourceEffect.id; move.ignoreAbility = (sourceEffect as ActiveMove).ignoreAbility; } let moveResult = false; this.battle.setActiveMove(move, pokemon, target); this.battle.singleEvent('ModifyType', move, null, pokemon, target, move, move); this.battle.singleEvent('ModifyMove', move, null, pokemon, target, move, move); if (baseTarget !== move.target) { // Target changed in ModifyMove, so we must adjust it here // Adjust before the next event so the correct target is passed to the // event target = this.battle.getRandomTarget(pokemon, move); } move = this.battle.runEvent('ModifyType', pokemon, target, move, move); move = this.battle.runEvent('ModifyMove', pokemon, target, move, move); if (baseTarget !== move.target) { // Adjust again target = this.battle.getRandomTarget(pokemon, move); } if (!move || pokemon.fainted) { return false; } let attrs = ''; let movename = move.name; if (move.id === 'hiddenpower') movename = 'Hidden Power'; if (sourceEffect) attrs += `|[from] ${sourceEffect.fullname}`; if (zMove && move.isZ === true) { attrs = `|[anim]${movename}${attrs}`; movename = `Z-${movename}`; } this.battle.addMove('move', pokemon, movename, `${target}${attrs}`); if (zMove) this.runZPower(move, pokemon); if (!target) { this.battle.attrLastMove('[notarget]'); this.battle.add(this.battle.gen >= 5 ? '-fail' : '-notarget', pokemon); return false; } const { targets, pressureTargets } = pokemon.getMoveTargets(move, target); if (targets.length) { target = targets[targets.length - 1]; // in case of redirection } const callerMoveForPressure = sourceEffect && (sourceEffect as ActiveMove).pp ? sourceEffect as ActiveMove : null; if (!sourceEffect || callerMoveForPressure || sourceEffect.id === 'pursuit') { let extraPP = 0; for (const source of pressureTargets) { const ppDrop = this.battle.runEvent('DeductPP', source, pokemon, move); if (ppDrop !== true) { extraPP += ppDrop || 0; } } if (extraPP > 0) { pokemon.deductPP(callerMoveForPressure || moveOrMoveName, extraPP); } } let tryMoveResult = this.battle.singleEvent('TryMove', move, null, pokemon, target, move); if (tryMoveResult) { tryMoveResult = this.battle.runEvent('TryMove', pokemon, target, move); } if (!tryMoveResult) { move.mindBlownRecoil = false; return tryMoveResult; } this.battle.singleEvent('UseMoveMessage', move, null, pokemon, target, move); if (move.ignoreImmunity === undefined) { move.ignoreImmunity = (move.category === 'Status'); } if (this.battle.gen !== 4 && move.selfdestruct === 'always') { this.battle.faint(pokemon, pokemon, move); } let damage: number | false | undefined | '' = false; if (move.target === 'all' || move.target === 'foeSide' || move.target === 'allySide' || move.target === 'allyTeam') { damage = this.tryMoveHit(targets, pokemon, move); if (damage === this.battle.NOT_FAIL) pokemon.moveThisTurnResult = null; if (damage || damage === 0 || damage === undefined) moveResult = true; } else { if (!targets.length) { this.battle.attrLastMove('[notarget]'); this.battle.add(this.battle.gen >= 5 ? '-fail' : '-notarget', pokemon); return false; } if (this.battle.gen === 4 && move.selfdestruct === 'always') { this.battle.faint(pokemon, pokemon, move); } moveResult = this.trySpreadMoveHit(targets, pokemon, move); } if (move.selfBoost && moveResult) this.moveHit(pokemon, pokemon, move, move.selfBoost, false, true); if (!pokemon.hp) { this.battle.faint(pokemon, pokemon, move); } if (!moveResult) { this.battle.singleEvent('MoveFail', move, null, target, pokemon, move); return false; } if (!(move.hasSheerForce && pokemon.hasAbility('sheerforce')) && !move.flags['futuremove']) { const originalHp = pokemon.hp; this.battle.singleEvent('AfterMoveSecondarySelf', move, null, pokemon, target, move); this.battle.runEvent('AfterMoveSecondarySelf', pokemon, target, move); if (pokemon && pokemon !== target && move.category !== 'Status') { if (pokemon.hp <= pokemon.maxhp / 2 && originalHp > pokemon.maxhp / 2) { this.battle.runEvent('EmergencyExit', pokemon, pokemon); } } } return true; }, canTerastallize(pokemon: Pokemon) { if (this.dex.gen !== 9) { return null; } return pokemon.teraType; }, canMegaEvo(pokemon) { const species = pokemon.baseSpecies; const altForme = species.otherFormes && this.dex.species.get(species.otherFormes[0]); const item = pokemon.getItem(); // Mega Rayquaza if ((this.battle.gen <= 7 || this.battle.ruleTable.has('+pokemontag:past') || this.battle.ruleTable.has('+pokemontag:future')) && altForme?.isMega && altForme?.requiredMove && pokemon.baseMoves.includes(this.battle.toID(altForme.requiredMove)) && !item.zMove) { return altForme.name; } if (species.baseSpecies === 'Magearna' && !species.isMega) { return species.name.includes('Original') ? 'Magearna-Original-Mega' : 'Magearna-Mega'; } if (!item.megaStone) return null; return item.megaStone[species.name]; }, modifyDamage(baseDamage, pokemon, target, move, suppressMessages = false) { const tr = this.battle.trunc; if (!move.type) move.type = '???'; const type = move.type; baseDamage += 2; if (move.spreadHit) { // multi-target modifier (doubles only) const spreadModifier = this.battle.gameType === 'freeforall' ? 0.5 : 0.75; this.battle.debug(`Spread modifier: ${spreadModifier}`); baseDamage = this.battle.modify(baseDamage, spreadModifier); } else if (move.multihitType === 'parentalbond' && move.hit > 1) { // Parental Bond modifier const bondModifier = this.battle.gen > 6 ? 0.25 : 0.5; this.battle.debug(`Parental Bond modifier: ${bondModifier}`); baseDamage = this.battle.modify(baseDamage, bondModifier); } // weather modifier baseDamage = this.battle.priorityEvent('WeatherModifyDamage', pokemon, target, move, baseDamage); // crit - not a modifier const isCrit = target.getMoveHitData(move).crit; if (isCrit) { baseDamage = tr(baseDamage * (move.critModifier || (this.battle.gen >= 6 ? 1.5 : 2))); } // random factor - also not a modifier baseDamage = this.battle.randomizer(baseDamage); // STAB // The "???" type never gets STAB // Not even if you Roost in Gen 4 and somehow manage to use // Struggle in the same turn. // (On second thought, it might be easier to get a MissingNo.) if (type !== '???') { let stab: number | [number, number] = 1; const pokeTypes = pokemon.getTypes(false, true); const isPrimarySTAB = move.forceSTAB || (pokemon.hasType(type) && pokeTypes[0] === type); const isSecondarySTAB = move.forceSTAB || (pokemon.hasType(type) && pokeTypes.length > 1 && pokeTypes[1] === type); const isSTAB = move.forceSTAB || pokemon.hasType(type) || pokemon.getTypes(false, true).includes(type); if (isPrimarySTAB) { stab = 1.7; } if (isSecondarySTAB) { stab = 1.2; } // The Stellar tera type makes this incredibly confusing // If the move's type does not match one of the user's base types, // the Stellar tera type applies a one-time 1.2x damage boost for that type. // // If the move's type does match one of the user's base types, // then the Stellar tera type applies a one-time 2x STAB boost for that type, // and then goes back to using the regular 1.5x STAB boost for those types. if (pokemon.terastallized === 'Stellar') { if (!pokemon.stellarBoostedTypes.includes(type) || move.stellarBoosted) { stab = isSTAB ? 2.3 : [4915, 4096]; move.stellarBoosted = true; if (pokemon.species.name !== 'Terapagos-Stellar') { pokemon.stellarBoostedTypes.push(type); } } } else { if (pokemon.terastallized === type && pokeTypes.includes(type)) { stab = 2.3; } stab = this.battle.runEvent('ModifySTAB', pokemon, target, move, stab); } baseDamage = this.battle.modify(baseDamage, stab); } // types let typeMod = target.runEffectiveness(move); typeMod = this.battle.clampIntRange(typeMod, -6, 6); target.getMoveHitData(move).typeMod = typeMod; if (typeMod > 0) { if (!suppressMessages) this.battle.add('-supereffective', target); for (let i = 0; i < typeMod; i++) { baseDamage *= 2; } } if (typeMod < 0) { if (!suppressMessages) this.battle.add('-resisted', target); for (let i = 0; i > typeMod; i--) { baseDamage = tr(baseDamage / 2); } } if (isCrit && !suppressMessages) this.battle.add('-crit', target); if (pokemon.status === 'brn' && move.category === 'Physical' && !pokemon.hasAbility('guts')) { if (this.battle.gen < 6 || move.id !== 'facade') { baseDamage = this.battle.modify(baseDamage, 0.5); } } if (pokemon.status === 'psn' && move.category === 'Special') { if (this.battle.gen < 6 || move.id !== 'facade') { baseDamage = this.battle.modify(baseDamage, 0.5); } } // Generation 5, but nothing later, sets damage to 1 before the final damage modifiers if (this.battle.gen === 5 && !baseDamage) baseDamage = 1; // Final modifier. Modifiers that modify damage after min damage check, such as Life Orb. baseDamage = this.battle.runEvent('ModifyDamage', pokemon, target, move, baseDamage); const bypassProtect = target.getMoveHitData(move).bypassProtect; if (bypassProtect) { baseDamage = this.battle.modify(baseDamage, 0.25); if (bypassProtect !== true && bypassProtect.effectType === 'Ability') { this.battle.add('-ability', pokemon, bypassProtect.name); } this.battle.add('-zbroken', target); } // Generation 6-7 moves the check for minimum 1 damage after the final modifier... if (this.battle.gen !== 5 && !baseDamage) return 1; // ...but 16-bit truncation happens even later, and can truncate to 0 return tr(baseDamage, 16); }, getDamage(source, target, move, suppressMessages = false) { if (typeof move === 'string') move = this.dex.getActiveMove(move); if (typeof move === 'number') { const basePower = move; move = new Dex.Move({ basePower, type: '???', category: 'Physical', willCrit: false, }) as ActiveMove; move.hit = 0; } if (!target.runImmunity(move, !suppressMessages)) { return false; } if (move.ohko) return this.battle.gen === 3 ? target.hp : target.maxhp; if (move.damageCallback) return move.damageCallback.call(this.battle, source, target); if (move.damage === 'level') { return source.level; } else if (move.damage) { return move.damage; } let category = this.battle.getCategory(move); let basePower: number | false | null = move.basePower; if (move.basePowerCallback) { basePower = move.basePowerCallback.call(this.battle, source, target, move); } if (!basePower) return basePower === 0 ? undefined : basePower; basePower = this.battle.clampIntRange(basePower, 1); if (move.type === 'Electric' && move.category === 'Physical') { basePower += 15; category = 'Special'; } let critMult; let critRatio = this.battle.runEvent('ModifyCritRatio', source, target, move, move.critRatio || 0); if (this.battle.gen <= 5) { critRatio = this.battle.clampIntRange(critRatio, 0, 5); critMult = [0, 16, 8, 4, 3, 2]; } else { critRatio = this.battle.clampIntRange(critRatio, 0, 4); if (this.battle.gen === 6) { critMult = [0, 16, 8, 2, 1]; } else { critMult = [0, 24, 8, 2, 1]; } } const moveHit = target.getMoveHitData(move); moveHit.crit = move.willCrit || false; if (move.willCrit === undefined) { if (critRatio) { moveHit.crit = this.battle.randomChance(1, critMult[critRatio]); } } if (moveHit.crit) { moveHit.crit = this.battle.runEvent('CriticalHit', target, null, move); } // happens after crit calculation basePower = this.battle.runEvent('BasePower', source, target, move, basePower, true); if (!basePower) return 0; basePower = this.battle.clampIntRange(basePower, 1); // Hacked Max Moves have 0 base power, even if you Dynamax if ((!source.volatiles['dynamax'] && move.isMax) || (move.isMax && this.dex.moves.get(move.baseMove).isMax)) { basePower = 0; } const dexMove = this.dex.moves.get(move.id); if (source.terastallized && (source.terastallized === 'Stellar' ? !source.stellarBoostedTypes.includes(move.type) : source.hasType(move.type)) && basePower < 60 && dexMove.priority <= 0 && !dexMove.multihit && // Hard move.basePower check for moves like Dragon Energy that have variable BP !((move.basePower === 0 || move.basePower === 150) && move.basePowerCallback) ) { basePower = 60; } const level = source.level; const attacker = move.overrideOffensivePokemon === 'target' ? target : source; const defender = move.overrideDefensivePokemon === 'source' ? source : target; const isPhysical = move.category === 'Physical'; let attackStat: StatIDExceptHP = move.overrideOffensiveStat || (isPhysical ? 'atk' : 'spa'); const defenseStat: StatIDExceptHP = move.overrideDefensiveStat || (isPhysical ? 'def' : 'spd'); const statTable = { atk: 'Atk', def: 'Def', spa: 'SpA', spd: 'SpD', spe: 'Spe' }; let atkBoosts = attacker.boosts[attackStat]; let defBoosts = defender.boosts[defenseStat]; let ignoreNegativeOffensive = !!move.ignoreNegativeOffensive; let ignorePositiveDefensive = !!move.ignorePositiveDefensive; if (moveHit.crit) { ignoreNegativeOffensive = true; ignorePositiveDefensive = true; } const ignoreOffensive = !!(move.ignoreOffensive || (ignoreNegativeOffensive && atkBoosts < 0)); const ignoreDefensive = !!(move.ignoreDefensive || (ignorePositiveDefensive && defBoosts > 0)); if (ignoreOffensive) { this.battle.debug('Negating (sp)atk boost/penalty.'); atkBoosts = 0; } if (ignoreDefensive) { this.battle.debug('Negating (sp)def boost/penalty.'); defBoosts = 0; } let attack = attacker.calculateStat(attackStat, atkBoosts, 1, source); let defense = defender.calculateStat(defenseStat, defBoosts, 1, target); attackStat = (category === 'Physical' ? 'atk' : 'spa'); // Apply Stat Modifiers attack = this.battle.runEvent('Modify' + statTable[attackStat], source, target, move, attack); defense = this.battle.runEvent('Modify' + statTable[defenseStat], target, source, move, defense); if (this.battle.gen <= 4 && ['explosion', 'selfdestruct'].includes(move.id) && defenseStat === 'def') { defense = this.battle.clampIntRange(Math.floor(defense / 2), 1); } const tr = this.battle.trunc; // int(int(int(2 * L / 5 + 2) * A * P / D) / 50); const baseDamage = tr(tr(tr(tr(2 * level / 5 + 2) * basePower * attack) / defense) / 50); // Calculate damage modifiers separately (order differs between generations) return this.modifyDamage(baseDamage, source, target, move, suppressMessages); }, }, };