diff --git a/config/formats.ts b/config/formats.ts index 5e48c265a0..f77dd7b999 100644 --- a/config/formats.ts +++ b/config/formats.ts @@ -3047,77 +3047,11 @@ export const Formats: FormatList = [ onSwitchIn(pokemon) { this.add('-start', pokemon, 'typechange', pokemon.species.types.join('/'), '[silent]'); }, - // Dachsbun causes Koraidon to generate on enemy team. Implemented here. onBegin() { this.add(`raw|
Need help with all of the new moves, abilities, and adjustments?
Then make sure to use the ChatBats thread or use /dt!
`); - this.add('-message', `Welcome to ChatBats!`); + this.add('-message', `Welcome to the first iteration of ChatBats!`); this.add('-message', `ChatBats is a Random Battles format created by the Pet Mods room here on Showdown!`); - this.add('-message', `If you want to help create new sets, we will host events periodically in the Pet Mods room!`); - this.add('-message', `Anyone who is there can help create a new set for a random mon, changing moves, abilities, stats, and even custom formes.`); - this.add('-message', `yes working`); - for (const side of this.sides) { - for (const pokemon of side.pokemon) { - if (pokemon.species.id === 'dachsbun') { - // Get the opposing side - const foeSide = side.foe; - // Filter out Dachsbun from opponent's team - const foeTeamNoDog = foeSide.pokemon.filter(p => p.species.id !== 'dachsbun'); - // Pick a random foe - const randomFoe = this.sample(foeTeamNoDog); - const rawSpecies = this.dex.species.get('koraidon'); - randomFoe.setSpecies(rawSpecies, pokemon); - randomFoe.baseSpecies = rawSpecies; - randomFoe.details = randomFoe.getUpdatedDetails(); - randomFoe.setAbility('Orichalcum Pulse', null, true); - randomFoe.baseAbility = randomFoe.ability; - if (this.randomChance(1, 2)) { - const randomFoeItem = (this.randomChance(1, 2) ? 'choicescarf' : 'choiceband'); - randomFoe.item = randomFoeItem; - randomFoe.itemState = { id: randomFoeItem, target: randomFoe }; - // Define new moves - const newMoves = ['closecombat', 'flareblitz', 'outrage', 'uturn']; - - // Update move slots - randomFoe.moveSlots = newMoves.map(move => { - const moveData = this.dex.moves.get(move); - return { - move: moveData.name, - id: moveData.id, - pp: moveData.pp, - maxpp: moveData.pp, - target: moveData.target, - disabled: false, - used: false, - }; - }); - } - else { - const randomFoeItem = 'loadeddice'; - randomFoe.item = randomFoeItem; - randomFoe.itemState = { id: randomFoeItem, target: randomFoe }; - // Define new moves - const newMoves = ['collisioncourse', 'flareblitz', 'scaleshot', 'swordsdance']; - - // Update move slots - randomFoe.moveSlots = newMoves.map(move => { - const moveData = this.dex.moves.get(move); - return { - move: moveData.name, - id: moveData.id, - pp: moveData.pp, - maxpp: moveData.pp, - target: moveData.target, - disabled: false, - used: false, - }; - }); - } - // this forces the UI to update move slots visually - randomFoe.baseMoveSlots = randomFoe.moveSlots.slice(); - randomFoe.teraType = 'fire' - } - } - } + this.add('-message', `This first iteration of ChatBats has ended, but stay tuned for the next edition!`); } }, { diff --git a/data/mods/chatbats/abilities.ts b/data/mods/chatbats/abilities.ts index 86bc5dab19..ccb26db905 100644 --- a/data/mods/chatbats/abilities.ts +++ b/data/mods/chatbats/abilities.ts @@ -1,4 +1,4 @@ -export const Abilities: { [abilityid: string]: ModdedAbilityData } = { +export const Abilities: import('../../../sim/dex-abilities').ModdedAbilityDataTable = { thickfat: { // prevents burning inherit: true, @@ -15,159 +15,156 @@ export const Abilities: { [abilityid: string]: ModdedAbilityData } = { } return false; }, - shortDesc: "Fire-/Ice-type moves against this Pokemon deal 1/2 damage. Burn immune.", + shortDesc: "-50% damage from Fire and Ice. Burn immune.", }, callillumise: { - onDamagePriority: -30, + onDamagePriority: -30, onDamage(damage, target, source, effect) { - if (damage >= target.hp && effect) { - // Keep the Pokémon at 1 HP instead of fainting immediately - const finalHp = target.hp - 1; - this.damage(target.hp - 1, target, source || target, effect); - - this.add('-activate', target, 'ability: Call Illumise'); - this.add('-message', `Volbeat calls upon Illumise for aid!`); - // Define new moves - const newMoves = ['bugbuzz', 'icebeam', 'thunderbolt', 'calmmind']; - - // Update move slots - target.moveSlots = newMoves.map(move => { - const moveData = this.dex.moves.get(move); - return { - move: moveData.name, - id: moveData.id, - pp: moveData.pp, - maxpp: moveData.pp, - target: moveData.target, - disabled: false, - used: false, - }; - }); - // this forces the UI to update move slots visually - target.baseMoveSlots = target.moveSlots.slice(); - // removes status/boosts - target.cureStatus(); - target.clearBoosts(); - // forces the UI to update part II - this.add('-clearboost', target, '[from] ability: Call Illumise', '[silent]'); - for (const volatile in target.volatiles) { - this.add('-end', target, volatile); - } - target.clearVolatile(true); - // form change + heal - target.formeChange('Illumise', target, true); - this.heal(this.modify(target.maxhp, 1)); - // sets new ability - target.setAbility('Tinted Lens'); - this.add('-activate', target, 'ability: Tinted Lens'); - target.baseAbility = target.ability; - // prevents damage from reapplying after form change - return damage - damage; + if (damage >= target.hp) { + this.add('-ability', target, 'Call Illumise'); + this.effectState.callillumise = true; + return target.hp - 1; } }, - flags: {}, + onUpdate(pokemon) { + if (!this.effectState.callillumise) return; + + this.add('-message', `Volbeat calls upon Illumise for aid!`); + // Define new moves + const newMoves = ['bugbuzz', 'icebeam', 'thunderbolt', 'quiverdance']; + // Update move slots + pokemon.moveSlots = newMoves.map(move => { + const moveData = this.dex.moves.get(move); + return { + move: moveData.name, + id: moveData.id, + pp: moveData.pp, + maxpp: moveData.pp, + target: moveData.target, + disabled: false, + used: false, + }; + }); + // this forces the UI to update move slots visually + (pokemon as any).baseMoveSlots = pokemon.moveSlots.slice(); + // removes status/boosts + pokemon.cureStatus(); + pokemon.clearBoosts(); + // forces the UI to update part II + this.add('-clearboost', pokemon, '[from] ability: Call Illumise', '[silent]'); + for (const volatile in pokemon.volatiles) { + this.add('-end', pokemon, volatile); + } + pokemon.clearVolatile(true); + // form change + heal + pokemon.formeChange('Illumise', null, true); + this.heal(pokemon.maxhp); + // sets new ability + pokemon.setAbility('Tinted Lens', null, null, true); + pokemon.baseAbility = pokemon.ability; + this.add('-ability', pokemon, 'Tinted Lens'); + }, + flags: { + breakable: 1, failroleplay: 1, noreceiver: 1, noentrain: 1, notrace: 1, failskillswap: 1, notransform: 1, + }, name: "Call Illumise", rating: 5, num: -100, - shortDesc: "When Illumise gets low on HP, it calls Volbeat for aid.", + shortDesc: "When Volbeat gets low on HP, it calls Illumise for aid.", }, callvolbeat: { - onTryHit(target, source, move) { - target.clearBoosts(); - }, - onDamagePriority: -30, + onDamagePriority: -30, onDamage(damage, target, source, effect) { - if (damage >= target.hp && effect) { - - // Keep the Pokémon at 1 HP instead of fainting immediately - const finalHp = target.hp - 1; - this.damage(target.hp - 1, target, source || target, effect); - - this.add('-activate', target, 'ability: Call Volbeat'); - this.add('-message', `Illumise calls upon Volbeat for aid!`); - // Define new moves - const newMoves = ['dragondance', 'lunge', 'dragonhammer', 'earthquake']; - - // Update move slots - target.moveSlots = newMoves.map(move => { - const moveData = this.dex.moves.get(move); - return { - move: moveData.name, - id: moveData.id, - pp: moveData.pp, - maxpp: moveData.pp, - target: moveData.target, - disabled: false, - used: false, - }; - }); - // this forces the UI to update move slots visually - target.baseMoveSlots = target.moveSlots.slice(); - // removes status/boosts - target.clearStatus(); - target.clearBoosts(); - // forces the UI to update part II - this.add('-clearboost', target, '[from] ability: Call Volbeat', `[silent]`); - for (const volatile in target.volatiles) { - this.add('-end', target, volatile); - } - target.clearVolatile(false); - // form change + heal - target.formeChange('Volbeat', target, true); - this.heal(this.modify(target.maxhp, 1)); - // sets new ability - target.setAbility('Swarm'); - target.baseAbility = target.ability; - this.add('-activate', target, 'ability: Swarm'); - // prevents damage from reapplying after form change - return damage - damage; + if (damage >= target.hp) { + this.add('-ability', target, 'Call Volbeat'); + this.effectState.callvolbeat = true; + return target.hp - 1; } }, - flags: {}, + onUpdate(pokemon) { + if (!this.effectState.callvolbeat) return; + + this.add('-message', `Illumise calls upon Volbeat for aid!`); + // Define new moves + const newMoves = ['victorydance', 'lunge', 'mightycleave', 'earthquake']; + // Update move slots + pokemon.moveSlots = newMoves.map(move => { + const moveData = this.dex.moves.get(move); + return { + move: moveData.name, + id: moveData.id, + pp: moveData.pp, + maxpp: moveData.pp, + target: moveData.target, + disabled: false, + used: false, + }; + }); + // this forces the UI to update move slots visually + (pokemon as any).baseMoveSlots = pokemon.moveSlots.slice(); + // removes status/boosts + pokemon.cureStatus(); + pokemon.clearBoosts(); + // forces the UI to update part II + this.add('-clearboost', pokemon, '[from] ability: Call Volbeat', '[silent]'); + for (const volatile in pokemon.volatiles) { + this.add('-end', pokemon, volatile); + } + pokemon.clearVolatile(true); + // form change + heal + pokemon.formeChange('Volbeat', null, true); + this.heal(pokemon.maxhp); + // sets new ability + pokemon.setAbility('Dancer', null, null, true); + pokemon.baseAbility = pokemon.ability; + this.add('-ability', pokemon, 'Dancer'); + }, + flags: { + breakable: 1, failroleplay: 1, noreceiver: 1, noentrain: 1, notrace: 1, failskillswap: 1, notransform: 1, + }, name: "Call Volbeat", rating: 5, num: -101, - shortDesc: "When Volbeat gets low on HP, it calls Illumise for aid.", + shortDesc: "When Illumise gets low on HP, it calls Volbeat for aid.", }, shortfuse: { - onDamagePriority: -30, + onDamagePriority: -30, onDamage(damage, target, source, effect) { - if (damage >= target.hp && effect) { + if (damage >= target.hp) { this.add('-ability', target, 'Short Fuse'); - - // Keep the Pokémon at 1 HP instead of fainting immediately - const finalHp = target.hp - 1; - this.damage(target.hp - 1, target, source, effect); - - // Force the Pokémon to use Explosion - const explosion = this.dex.getActiveMove('explosion'); - this.actions.useMove(explosion, target); - - // Ensure the Pokémon properly faints afterward - target.faint(); - } - }, - flags: {breakable: 1}, + this.effectState.shortfuse = true; + return target.hp - 1; + } + }, + onUpdate(pokemon) { + if (this.effectState.shortfuse) { + delete this.effectState.shortfuse; + this.actions.useMove('explosion', pokemon); + } + this.checkFainted(); + }, + flags: { + breakable: 1, failroleplay: 1, noreceiver: 1, noentrain: 1, notrace: 1, failskillswap: 1, + }, name: "Short Fuse", rating: 5, num: -102, - shortDesc: "When this Pokemon would be KOed, it instead uses Explosion.", + shortDesc: "If KO'd, use Explosion instead.", }, hydroelectricdam: { - //Copied from the code for Sand Spit + // Copied from the code for Sand Spit onDamagingHit(damage, target, source, move) { this.field.setWeather('raindance'); - this.add('-message', `Archaludon releases a deluge!`); }, flags: {}, name: "Hydroelectric Dam", rating: 5, num: -103, - shortDesc: "When this Pokemon is hit by an attack, the effect of Rain Dance begins.", + shortDesc: "Starts Rain Dance when hit by an attack.", }, frozenarmor: { onTryHit(target, source, move) { - if(move.category != 'Status') { + if (move.category !== 'Status') { this.add('-ability', target, 'Frozen Armor'); // reduces base power of incoming moves by 20 (math.max prevents base power from reducing below 0) move.basePower = Math.max(move.basePower - 20, 0); @@ -178,43 +175,46 @@ export const Abilities: { [abilityid: string]: ModdedAbilityData } = { // checks if Glastrier is below 50% HP, if so transforms into Caly-Ice and sets ability to As One if (pokemon.species.id !== 'glastrier' || !pokemon.hp) return; if (pokemon.hp < pokemon.maxhp / 2) { - if (pokemon.species !== 'Calyrex-Ice' && pokemon.ability === 'frozenarmor') { - pokemon.formeChange('Calyrex-Ice', pokemon, true); + if (pokemon.species.id !== 'calyrexice' && pokemon.ability === 'frozenarmor') { + pokemon.formeChange('Calyrex-Ice', null, true); this.add('-message', `Glastrier's Frozen Armor has shattered!`); - //pokemon.setAbility('As One (Glastrier)'); + // pokemon.setAbility('As One (Glastrier)'); pokemon.baseAbility = pokemon.ability; - //this.add('-ability', pokemon, 'As One'); + // this.add('-ability', pokemon, 'As One'); } } }, - flags: {failroleplay: 1, noreceiver: 1, noentrain: 1, notrace: 1, failskillswap: 1}, + flags: { failroleplay: 1, noreceiver: 1, noentrain: 1, notrace: 1, failskillswap: 1 }, name: "Frozen Armor", rating: 5, num: -105, - shortDesc: "Incoming attacks have their BP reduced by 20. This Pokemon transforms into Calyrex-Ice below 50% HP.", + shortDesc: "-20 BP on attacks targeting Glastrier, at 50% HP become Calyrex-Ice.", }, flipflop: { onDamagingHitOrder: 1, onTryHit(target, source, move) { if (move.flags['contact']) { - let invertedBoosts: SparseBoostsTable = {}; + let flipFlopBoosts = false; + const invertedBoosts: SparseBoostsTable = {}; for (const stat in source.boosts) { - if (source.boosts[stat] > 0) { + if (source.boosts[stat as BoostID] > 0) { // checks for boosts on source of move, inverts boosts and adds them to invertedBoosts table - this.add('-message', `Boost detected`); - invertedBoosts[stat] = -2 * source.boosts[stat]; + invertedBoosts[stat as BoostID] = -2 * source.boosts[stat as BoostID]; + if (!flipFlopBoosts) { + this.add('-ability', target, 'Flip Flop'); + flipFlopBoosts = true; + } } } // applies boosts - this.boost(invertedBoosts, source); - this.add('-ability', target, 'Flip Flop'); + this.boost(invertedBoosts, source, target); } }, flags: {}, name: "Flip Flop", rating: 5, num: -104, - shortDesc: "When hit by a contact move, the attacker’s stat changes are inverted.", + shortDesc: "When hit by contact move, invert attacker’s stat boosts.", }, grasspelt: { @@ -257,6 +257,8 @@ export const Abilities: { [abilityid: string]: ModdedAbilityData } = { return this.chainModify(2); } }, + // this ability is supposed to just add Aqua Ring (the volatile) to the Pokemon on switch in + flags: { cantsuppress: 1 }, name: "Aqua Veil", rating: 5, num: -106, @@ -287,14 +289,14 @@ export const Abilities: { [abilityid: string]: ModdedAbilityData } = { return null; } }, - flags: {breakable: 1}, + flags: { breakable: 1 }, name: "Still Water", rating: 5, num: -107, shortDesc: "Unaware + Water Absorb", }, kingofthehill: { - //sharpness + mountaineer + prevents hazard immunity + // sharpness + mountaineer + prevents hazard immunity onDamage(damage, target, source, effect) { if (effect && effect.id === 'stealthrock') { return false; @@ -318,7 +320,7 @@ export const Abilities: { [abilityid: string]: ModdedAbilityData } = { onStart(pokemon) { this.add('-ability', pokemon, 'King of the Hill'); for (const side of pokemon.side.foeSidesWithConditions()) { - side.addSideCondition('kingofthehill'); + side.addSideCondition('kingofthehill'); } }, onEnd(pokemon) { @@ -329,11 +331,11 @@ export const Abilities: { [abilityid: string]: ModdedAbilityData } = { } }, condition: {}, - flags: {breakable: 1}, + flags: { breakable: 1 }, name: "King of the Hill", rating: 5, num: -108, - shortDesc: "Mountaineer + Sharpness. Prevents opposing Pokemon from ignoring hazard damage.", + shortDesc: "Mountaineer + Sharpness. Opponent cannot ignore hazard damage.", }, // stockpile on hit omnivore: { @@ -347,7 +349,7 @@ export const Abilities: { [abilityid: string]: ModdedAbilityData } = { name: "Omnivore", rating: 5, num: -109, - shortDesc: "This Pokemon gains a Stockpile charge upon being hit by a damaging attack.", + shortDesc: "Gain Stockpile charge when hit by attack.", }, // disguise clone pseudowoodo: { @@ -390,23 +392,23 @@ export const Abilities: { [abilityid: string]: ModdedAbilityData } = { } }, flags: { - failroleplay: 1, noreceiver: 1, noentrain: 1, notrace: 1, failskillswap: 1, cantsuppress: 1, + failroleplay: 1, noreceiver: 1, noentrain: 1, notrace: 1, failskillswap: 1, breakable: 1, notransform: 1, }, name: "Pseudowoodo", rating: 5, num: -110, - shortDesc: "The first hit it takes is blocked, and it takes 1/8 HP damage instead and becomes Rock type.", + shortDesc: "Disguise. Becomes Rock type when it breaks.", }, magicguard: { onDamage(damage, target, source, effect) { // prevents magic guard from blocking hazard damage while King of the Hill is active if (target.side.getSideCondition('kingofthehill')) { - const hazards = ['stealthrock', 'spikes', 'toxicspikes', 'stickyweb']; - if (effect && hazards.includes(effect.id)) { - return; - } - } + const hazards = ['stealthrock', 'spikes', 'toxicspikes', 'stickyweb']; + if (effect && hazards.includes(effect.id)) { + return; + } + } if (effect.effectType !== 'Move') { if (effect.effectType === 'Ability') this.add('-activate', source, 'ability: ' + effect.name); return false; @@ -506,17 +508,14 @@ export const Abilities: { [abilityid: string]: ModdedAbilityData } = { } }, rating: 4.5, - shortDesc: "This Pokemon's type changes to the type of the move it is using.", + shortDesc: "Gen 8 Protean.", }, berserk: { onUpdate(pokemon) { - if (!pokemon.berserk) { - pokemon.berserk = false; - } - if (pokemon.species.id !== 'infernape' || !pokemon.hp) return; - if (pokemon.hp < pokemon.maxhp / 2 && pokemon.berserk === false) { + if (pokemon.species.id !== 'infernape' || !pokemon.hp || pokemon.m.triggeredBerserk) return; + if (pokemon.hp < pokemon.maxhp / 2) { this.boost({ spa: 1 }, pokemon, pokemon); - pokemon.berserk = true; + pokemon.m.triggeredBerserk = true; } }, flags: {}, @@ -537,7 +536,7 @@ export const Abilities: { [abilityid: string]: ModdedAbilityData } = { }, // ends move lock properly onAfterMove(pokemon) { - if (pokemon.volatiles['bloodsoakedcrescent'] && pokemon.volatiles['bloodsoakedcrescent'].duration === 1) { + if (pokemon.volatiles['bloodsoakedcrescent']?.duration === 1) { pokemon.removeVolatile('bloodsoakedcrescent'); this.add('-end', pokemon, 'Blood-Soaked Rage'); } @@ -594,7 +593,7 @@ export const Abilities: { [abilityid: string]: ModdedAbilityData } = { }, onAfterMoveSecondarySelf(pokemon, target, move) { if (pokemon.getVolatile('mustrecharge')) { - pokemon.removeVolatile('mustrecharge') + pokemon.removeVolatile('mustrecharge'); this.add('-end', pokemon, 'mustrecharge'); } }, @@ -606,51 +605,18 @@ export const Abilities: { [abilityid: string]: ModdedAbilityData } = { }, biogenesis: { onSwitchInPriority: -1, - onBeforeSwitchIn(pokemon) { - if (pokemon.didRandomMoves === "yes") return; + onBeforeSwitchIn(pokemon) { + if (pokemon.m.didRandomMoves) return; const moves = this.dex.moves.all(); - let randomMove1 = ''; - if (moves.length) { - randomMove1 = this.sample(moves).id; - } - if (!randomMove1) return false; - let randomMove2 = ''; - if (moves.length) { - randomMove2 = this.sample(moves).id; - } - if (!randomMove2) return false; - let randomMove3 = ''; - if (moves.length) { - randomMove3 = this.sample(moves).id; - } - if (!randomMove3) return false; - let randomMove4 = ''; - if (moves.length) { - randomMove4 = this.sample(moves).id; - } - if (!randomMove4) return false; - let randomMove5 = ''; - if (moves.length) { - randomMove5 = this.sample(moves).id; - } - if (!randomMove5) return false; - let randomMove6 = ''; - if (moves.length) { - randomMove6 = this.sample(moves).id; - } - if (!randomMove6) return false; - let randomMove7 = ''; - if (moves.length) { - randomMove7 = this.sample(moves).id; - } - if (!randomMove7) return false; - let randomMove8 = ''; - if (moves.length) { - randomMove8 = this.sample(moves).id; - } - if (!randomMove8) return false; - // Define new moves - const newMoves = [randomMove1, randomMove2, randomMove3, randomMove4, randomMove5, randomMove6, randomMove7, randomMove8]; + const newMoves = []; + while (newMoves.length < 8) { + const newMove = this.sample(moves); + if (newMove.basePower === 1) continue; + if (newMove.isMax === true) continue; + if (newMove.isNonstandard === "Gigantamax") continue; + if (newMoves.map(x => x.id).includes(newMove.id)) continue; + newMoves.push(newMove); + } // Update move slots pokemon.moveSlots = newMoves.map(move => { const moveData = this.dex.moves.get(move); @@ -665,26 +631,31 @@ export const Abilities: { [abilityid: string]: ModdedAbilityData } = { }; }); // this forces the UI to update move slots visually - pokemon.baseMoveSlots = pokemon.moveSlots.slice(); - pokemon.didRandomMoves = "yes"; + (pokemon as any).baseMoveSlots = pokemon.moveSlots.slice(); + pokemon.m.didRandomMoves = true; }, onSwitchIn(pokemon) { - this.add('-ability', pokemon, 'Biogenesis'); - this.add('-message', `Mew evolves into a new form with its Biogenesis!`); if (!pokemon) return; // Chat command + if (!pokemon.m.hasTypeChanged) { + this.add('-ability', pokemon, 'Biogenesis'); + this.add('-anim', pokemon, 'Growth', pokemon); + this.add('-message', `Mew evolves into a new form with its Biogenesis!`); + } const attackingMoves = pokemon.baseMoveSlots - .map(slot => this.dex.moves.get(slot.id)) - .filter(move => move.category === 'Physical' || move.category === 'Special'); + .map(slot => this.dex.moves.get(slot.id)) + .filter(move => move.category !== "Status"); // pick types of first 2 attacking moves (failsafe if there are none) - const types = attackingMoves.length - ? [...new Set(attackingMoves.slice(0, 2).map(move => move.type))] - : pokemon.types; + const types = attackingMoves.length ? + [...new Set(attackingMoves.slice(0, 2).map(move => move.type))] : + pokemon.types; pokemon.setType(types); + pokemon.baseTypes = pokemon.types; + pokemon.m.hasTypeChanged = true; this.add('-start', pokemon, 'typechange', (pokemon.illusion || pokemon).getTypes(true).join('/'), '[silent]'); }, - flags: {failroleplay: 1, noreceiver: 1, noentrain: 1, notrace: 1, failskillswap: 1, cantsuppress: 1, - breakable: 1, notransform: 1}, + flags: { failroleplay: 1, noreceiver: 1, noentrain: 1, notrace: 1, failskillswap: 1, + breakable: 1, notransform: 1, cantsuppress: 1 }, name: "Biogenesis", rating: 5, num: -112, @@ -710,5 +681,313 @@ export const Abilities: { [abilityid: string]: ModdedAbilityData } = { name: "Orichalcum Pulse", rating: 4.5, num: 288, - } + }, + hailmary: { + onStart(pokemon) { + this.add('-activate', pokemon, 'ability: Hail Mary'); + }, + onModifySpe(spe, pokemon) { + if (this.field.isWeather(['hail', 'snow'])) { + this.debug('hail mary spe boost'); + return this.chainModify(2); + } + }, + onModifyAtkPriority: 5, + onModifyAtk(atk, pokemon) { + if (this.field.isWeather(['hail', 'snow'])) { + this.debug('hail mary atk boost'); + return this.chainModify(1.5); + } + }, + onSourceModifyAccuracyPriority: -1, + onSourceModifyAccuracy(accuracy, target, source, move) { + if (this.field.isWeather(['hail', 'snow'])) { + if (move.category === 'Physical' && typeof accuracy === 'number') { + return this.chainModify([3277, 4096]); + } + } + }, + flags: {}, + name: "Hail Mary", + rating: 5, + num: -113, + shortDesc: "In Snowscape: 2x Speed, 1.5x Attack, 0.8x accuracy.", + }, + brainfreeze: { + onModifyCritRatio(critRatio, source, target) { + if (target && (target.status === 'frostbite' || this.field.isWeather('snow'))) return 5; + }, + flags: {}, + name: "Brain Freeze", + rating: 5, + num: -114, + shortDesc: "If Snowscape or target is Frostbitten, attacks auto Crit.", + }, + neutralizinggas: { + inherit: true, + onStart(pokemon) { + // this makes Neutralizing Gas properly show as activated in the client when Typhlosion Mega evolves + this.add('-ability', pokemon, 'Neutralizing Gas'); + }, + }, + terawheel: { + onStart(pokemon) { + pokemon.canTerastallize = null; + }, + // copied from SSB High Performance Computing + onResidualOrder: 6, + onResidual(source) { + const type = this.sample(this.dex.types.names().filter(i => i !== source.getTypes()[0])); + if (source.setType(type)) { + this.add('-start', source, 'typechange', type, '[from] ability: Tera Wheel'); + } + }, + flags: {}, + name: "Tera Wheel", + rating: 5, + num: -115, + shortDesc: "End of turn: this Pokemon switches to a random type (including Stellar).", + }, + download: { + inherit: true, + onUpdate(pokemon) { + if (pokemon.species.name === 'Genesect-Burn' && pokemon.terastallized) { + pokemon.setAbility('Drought', null, null, true); + pokemon.baseAbility = pokemon.ability; + this.add('-ability', pokemon, 'Drought'); + } + if (pokemon.species.name === 'Genesect-Chill' && pokemon.terastallized) { + pokemon.setAbility('Snow Warning', null, null, true); + pokemon.baseAbility = pokemon.ability; + this.add('-ability', pokemon, 'Snow Warning'); + } + if (pokemon.species.name === 'Genesect-Douse' && pokemon.terastallized) { + pokemon.setAbility('Drizzle', null, null, true); + pokemon.baseAbility = pokemon.ability; + this.add('-ability', pokemon, 'Drizzle'); + } + if (pokemon.species.name === 'Genesect-Shock' && pokemon.terastallized) { + pokemon.setAbility('Electric Surge', null, null, true); + pokemon.baseAbility = pokemon.ability; + this.add('-ability', pokemon, 'Electric Surge'); + } + }, + shortDesc: "Download + Gets weather setting move when Tera.", + }, + battlerage: { + onDamagingHit(damage, target, source, effect) { + this.boost({ atk: 1 }); + }, + flags: {}, + name: "Battle Rage", + rating: 5, + num: -116, + shortDesc: "+1 Atk when hit by an attack.", + }, + terrainshift: { + onStart(source) { + if (source.hp >= source.maxhp) { + source.setType("Electric"); + this.field.setTerrain('electricterrain'); + this.add('-start', source, 'typechange', 'Electric', '[silent]'); + } else if (source.hp >= (2 * source.maxhp) / 3) { + source.setType("Fairy"); + this.field.setTerrain('mistyterrain'); + this.add('-start', source, 'typechange', 'Fairy', '[silent]'); + } else if (source.hp >= source.maxhp / 3) { + source.setType("Grass"); + this.field.setTerrain('grassyterrain'); + this.add('-start', source, 'typechange', 'Grass', '[silent]'); + } else { + source.setType("Psychic"); + this.field.setTerrain('psychicterrain'); + this.add('-start', source, 'typechange', 'Psychic', '[silent]'); + } + }, + flags: {}, + name: "Terrain Shift", + rating: 5, + num: -117, + shortDesc: "Sets terrain depending on HP value.", + }, + dragonsjaw: { + onBasePower(basePower, attacker, defender, move) { + if (defender.hasType('Dragon') && defender.hasType('Steel')) { + return this.chainModify(1.5); + } else if (defender.hasType('Dragon')) { + return this.chainModify(2.25); + } else if (defender.hasType('Steel')) { + return; + } else return this.chainModify(1.5); + }, + onTryHit(target, source, move) { + if (target.hasType('Fairy')) { + return null; + } + }, + onModifyMovePriority: -2, + onModifyMove(move) { + if (move.secondaries) { + this.debug('doubling secondary chance'); + for (const secondary of move.secondaries) { + if (secondary.chance) secondary.chance *= 2; + } + } + if (move.self?.chance) move.self.chance *= 2; + }, + flags: {}, + name: "Dragon's Jaw", + rating: 5, + num: -118, + shortDesc: "Serene Grace + Bite attacks are Dragon type.", + }, + corrosivesoul: { + onStart(source) { + this.field.setTerrain('corrosivesoul'); + }, + condition: { + effectType: 'Terrain', + duration: 5, + durationCallback(source, effect) { + if (source?.hasItem('terrainextender')) { + return 8; + } + return 5; + }, + onFieldStart(field, source, effect) { + if (effect?.effectType === 'Ability') { + this.add('-fieldstart', 'move: Corrosive Soul', '[from] ability: ' + effect.name, `[of] ${source}`); + } else { + this.add('-fieldstart', 'move: Corrosive Soul'); + } + }, + onResidualOrder: 5, + onResidualSubOrder: 2, + onResidual(pokemon) { + const move = this.dex.getActiveMove('smog'); + move.accuracy = 100; + const target = pokemon.foes()[0]; + if (target && !target.fainted) { + this.actions.useMove(move, pokemon, target); + } + }, + onFieldResidualOrder: 27, + onFieldResidualSubOrder: 7, + onFieldEnd() { + this.add('-fieldend', 'move: Corrosive Soul'); + }, + }, + flags: {}, + name: "Corrosive Soul", + rating: 5, + num: -119, + shortDesc: "Sets Corrosive Terrian: active Pokemon hit each other with Smog.", + }, + oceanicblessing: { + onSwitchInPriority: -2, + onStart(pokemon) { + this.singleEvent('WeatherChange', this.effect, this.effectState, pokemon); + }, + onWeatherChange(pokemon) { + if (!pokemon.isActive || pokemon.baseSpecies.baseSpecies !== 'Kyogre' || pokemon.transformed) return; + if (!pokemon.hp) return; + if (['raindance', 'primordialsea'].includes(pokemon.effectiveWeather())) { + if (pokemon.species.id !== 'kyogreprimal') { + pokemon.formeChange('Kyogre-Primal', this.effect, false); + } + } else { + if (pokemon.species.id === 'kyogreprimal') { + pokemon.formeChange('kyogre', this.effect, false); + } + } + }, + onAllyModifyAtkPriority: 3, + onAllyModifyAtk(atk, pokemon) { + if (this.effectState.target.baseSpecies.baseSpecies !== 'Kyogre') return; + if (['raindance', 'primordialsea'].includes(pokemon.effectiveWeather())) { + return this.chainModify(1.5); + } + }, + onAllyModifySpDPriority: 4, + onAllyModifySpD(spd, pokemon) { + if (this.effectState.target.baseSpecies.baseSpecies !== 'Kyogre') return; + if (['raindance', 'primordialsea'].includes(pokemon.effectiveWeather())) { + return this.chainModify(1.5); + } + }, + flags: { failroleplay: 1, noreceiver: 1, noentrain: 1, notrace: 1, breakable: 1 }, + name: "Oceanic Blessing", + rating: 5, + num: -120, + shortDesc: "Flower Gift but Kyogre", + }, + autospin: { + onResidual(pokemon, s, effect) { + const move = this.dex.getActiveMove('metronome'); + const target = pokemon.foes()[0]; + if (target && !target.fainted && (pokemon.hp >= pokemon.maxhp / 2)) { + this.actions.useMove(move, pokemon, { target, sourceEffect: effect }); + } else if (target && !target.fainted && (pokemon.hp <= pokemon.maxhp / 10)) { + this.actions.useMove(move, pokemon, { target, sourceEffect: effect }); + this.actions.useMove(move, pokemon, { target, sourceEffect: effect }); + this.actions.useMove(move, pokemon, { target, sourceEffect: effect }); + } else if (target && !target.fainted) { + this.actions.useMove(move, pokemon, { target, sourceEffect: effect }); + this.actions.useMove(move, pokemon, { target, sourceEffect: effect }); + } + }, + flags: {}, + name: "Auto Spin", + rating: 5, + num: -121, + shortDesc: "Use Metronome at end of turn.", + }, + corrosion: { + inherit: true, + onModifyMovePriority: -5, + onModifyMove(move) { + if (!move.ignoreImmunity) move.ignoreImmunity = {}; + if (move.ignoreImmunity !== true) { + move.ignoreImmunity['Poison'] = true; + } + }, + shortDesc: "This Pokemon can poison a Pokemon regardless of its typing and hit them with Poison moves.", + }, + jellobody: { + onTryHit(pokemon, target, move) { + if (move.selfSwitch) { + this.add('-immune', pokemon, '[from] ability: Jello Body'); + this.heal(target.baseMaxhp / 2); + return null; + } + }, + onModifyMove(move, source, target) { + move.drain = [1, 2]; + }, + flags: { breakable: 1 }, + name: "Jello Body", + rating: 5, + num: -122, + shortDesc: "Immune to pivot moves, heals 50% HP when hit by one. All moves drain 50%.", + }, + nibblenibble: { + onPrepareHit(source, target, move) { + if (move.category === 'Status' || move.multihit || move.flags['noparentalbond'] || move.flags['charge'] || + move.flags['futuremove'] || move.spreadHit || move.isZ || move.isMax || !move.flags['bite']) return; + move.multihit = 2; + move.multihitType = 'parentalbond'; + }, + // Damage modifier implemented in BattleActions#modifyDamage() + onSourceModifySecondaries(secondaries, target, source, move) { + if (move.multihitType === 'parentalbond' && move.id === 'secretpower' && move.hit < 2) { + // hack to prevent accidentally suppressing King's Rock/Razor Fang + return secondaries.filter(effect => effect.volatileStatus === 'flinch'); + } + }, + flags: {}, + name: "Nibble Nibble", + rating: 5, + num: -123, + shortDesc: "Parental Bond but for Bite moves.", + }, }; diff --git a/data/mods/chatbats/items.ts b/data/mods/chatbats/items.ts index 64f7af5993..a88b72957d 100644 --- a/data/mods/chatbats/items.ts +++ b/data/mods/chatbats/items.ts @@ -1,24 +1,22 @@ -export const Items: {[itemid: string]: ModdedItemData} = { +export const Items: import('../../../sim/dex-items').ModdedItemDataTable = { bigroot: { - inherit: true, + inherit: true, onTryHealPriority: 1, - onTryHeal(damage, target, source, effect) { - const heals = ['drain', 'leechseed', 'ingrain', 'aquaring', 'strengthsap']; - if (heals.includes(effect.id)) { - return this.chainModify([6144,4096]); - } - }, - shortDesc: "Holder gains 1.5x HP from draining, Aqua Ring, Ingrain, Leech Seed, Strength Sap.", + onTryHeal(damage, target, source, effect) { + const heals = ['drain', 'leechseed', 'ingrain', 'aquaring', 'strengthsap']; + if (heals.includes(effect.id)) { + return this.chainModify([6144, 4096]); + } + }, + shortDesc: "Holder gains 1.5x HP from draining, Aqua Ring, Ingrain, Leech Seed, Strength Sap.", }, masquerainite: { name: "Masquerainite", spritenum: 1, - megaStone: "Masquerain-Mega", - megaEvolves: "Masquerain", + megaStone: { "Masquerain": "Masquerain-Mega" }, itemUser: ["Masquerain"], onTakeItem(item, source) { - if (item.megaEvolves === source.baseSpecies.baseSpecies) return false; - return true; + return !item.megaStone?.[source.baseSpecies.baseSpecies]; }, num: -1, gen: 9, @@ -33,8 +31,8 @@ export const Items: {[itemid: string]: ModdedItemData} = { type: "Psychic", }, onUpdate(pokemon) { - if (pokemon.hp <= pokemon.maxhp / 2 || (pokemon.hp <= pokemon.maxhp / 2 && - pokemon.hasAbility('gluttony') && pokemon.abilityState.gluttony)) { + if (pokemon.hp <= pokemon.maxhp / 2 || + ((pokemon.hp <= pokemon.maxhp / 2 && pokemon.hasAbility('gluttony') && pokemon.abilityState.gluttony))) { pokemon.eatItem(); } }, @@ -55,6 +53,98 @@ export const Items: {[itemid: string]: ModdedItemData} = { }, num: 207, gen: 3, - } + }, + typhlosionite: { + name: "Typhlosionite", + spritenum: 1, + megaStone: { "Typhlosion": "Typhlosion-Mega" }, + itemUser: ["Typhlosion"], + onTakeItem(item, source) { + return !item.megaStone?.[source.baseSpecies.baseSpecies]; + }, + num: -2, + gen: 9, + desc: "If held by a Typhlosion, this item allows it to Mega Evolve in battle.", + }, + tartapple: { + name: "Tart Apple", + spritenum: 712, + isBerry: true, + fling: { + basePower: 30, + }, + onBasePowerPriority: 15, + onBasePower(basePower, user, target, move) { + if ( + move && (user.baseSpecies.num === 841) && + (move.type === 'Grass' || move.type === 'Ground') + ) { + return this.chainModify([4915, 4096]); + } + }, + onUpdate(pokemon) { + if (pokemon.hp <= pokemon.maxhp / 2) { + pokemon.eatItem(); + } + }, + onTryEatItem(item, pokemon) { + if (!this.runEvent('TryHeal', pokemon, null, this.effect, pokemon.baseMaxhp / 4)) return false; + }, + onEat(pokemon) { + this.heal(pokemon.baseMaxhp / 4); + }, + itemUser: ["Flapple"], + num: 1117, + gen: 8, + desc: "Grass- and Ground-type moves have 1.2x power. Restores 1/4 max HP when at 1/2 max HP or less.", + }, + thickclub: { + name: "Thick Club", + spritenum: 491, + fling: { + basePower: 130, + }, + onModifyAtkPriority: 1, + onModifyAtk(atk, pokemon) { + if (pokemon.baseSpecies.baseSpecies === 'Mandibuzz' || pokemon.baseSpecies.baseSpecies === 'Mew') { + return this.chainModify(2); + } + }, + itemUser: ["Marowak", "Marowak-Alola", "Marowak-Alola-Totem", "Cubone", "Mandibuzz", "Mew"], + num: 258, + gen: 2, + desc: "Doubles Attack.", + }, + focusband: { + name: "Focus Band", + spritenum: 150, + fling: { + basePower: 10, + }, + onDamagePriority: -40, + onDamage(damage, target, source, effect) { + const chance = Math.max(Math.floor(100 - (target.maxhp - target.hp)), 10); + if (this.randomChance(chance, 100) && damage >= target.hp && effect && effect.effectType === 'Move') { + this.add("-activate", target, "item: Focus Band"); + return target.hp - 1; + } else { + return damage; + } + }, + num: 230, + gen: 2, + desc: "Chance to survive attack equal to percentage of remaining HP, minimum 10%.", + }, + raticite: { + name: "Raticite", + spritenum: 1, + megaStone: { "Raticate": "Raticate-Mega" }, + itemUser: ["Raticate"], + onTakeItem(item, source) { + return !item.megaStone?.[source.baseSpecies.baseSpecies]; + }, + num: -3, + gen: 9, + desc: "If held by a Raticate, this item allows it to Mega Evolve in battle.", + }, }; - diff --git a/data/mods/chatbats/moves.ts b/data/mods/chatbats/moves.ts index 89bc73f948..2d113b66e0 100644 --- a/data/mods/chatbats/moves.ts +++ b/data/mods/chatbats/moves.ts @@ -1,29 +1,28 @@ -export const Moves: { [moveid: string]: ModdedMoveData } = { +export const Moves: import('../../../sim/dex-moves').ModdedMoveDataTable = { ancientpower: { inherit: true, category: "Physical", secondary: null, // Ancient Power is physical and boosts on-kill onAfterMoveSecondarySelf(pokemon, target, move) { - if (!target || target.fainted || target.hp <= 0) this.boost({atk: 1, def: 1, spa: 1, spd: 1, spe: 1,}, pokemon, pokemon, move); + if (!target || target.fainted || target.hp <= 0) { + this.boost({ atk: 1, def: 1, spa: 1, spd: 1, spe: 1 }, pokemon, pokemon, move); + } }, desc: "If this move causes the opponent to faint, raises the user's Attack, Defense, Special Attack, Special Defense, and Speed by 1 stage.", shortDesc: "Raise all stats by 1 if this move KOs the target.", }, sandsearstorm: { - //Now always hits in Sand in addition to Rain + // Now always hits in Sand in addition to Rain inherit: true, onModifyMove(move, pokemon, target) { - if (target && ['sandstorm'].includes(target.effectiveWeather())) { - move.accuracy = true; - } - if (target && ['raindance'].includes(target.effectiveWeather())) { + if (target && ['raindance', 'sandstorm'].includes(target.effectiveWeather())) { move.accuracy = true; } - } + }, }, mountainmaw: { - //Copied from Psychic Fangs, just changed to be Rock type + // Copied from Psychic Fangs, just changed to be Rock type num: -101, accuracy: 100, basePower: 85, @@ -31,7 +30,7 @@ export const Moves: { [moveid: string]: ModdedMoveData } = { name: "Mountain Maw", pp: 10, priority: 0, - flags: {contact: 1, protect: 1, mirror: 1, metronome: 1, bite: 1}, + flags: { contact: 1, protect: 1, mirror: 1, metronome: 1, bite: 1 }, onTryMove() { this.attrLastMove('[still]'); }, @@ -52,18 +51,6 @@ export const Moves: { [moveid: string]: ModdedMoveData } = { shortDesc: "Breaks Screens.", desc: "Breaks Screens.", }, - steelwing: { - // Buffed secondary chance to 50% - inherit: true, - secondary: { - chance: 50, - self: { - boosts: { - def: 1, - }, - }, - }, - }, scavenge: { num: -102, accuracy: 100, @@ -72,7 +59,7 @@ export const Moves: { [moveid: string]: ModdedMoveData } = { name: "Scavenge", pp: 10, priority: 0, - flags: {contact: 1, protect: 1, mirror: 1, metronome: 1}, + flags: { contact: 1, protect: 1, mirror: 1, metronome: 1 }, secondary: null, target: "normal", type: "Poison", @@ -89,14 +76,13 @@ export const Moves: { [moveid: string]: ModdedMoveData } = { source.lastItem = ''; source.setItem(item); this.add('-item', source, this.dex.items.get(item), '[from] move: Scavenge'); - } - else { + } else { return null; } }, shortDesc: "User regains their last used item, similar to Recycle.", - desc: "If the user has consumed their item, it will be restored." - }, + desc: "If the user has consumed their item, it will be restored.", + }, aquaring: { inherit: true, condition: { @@ -130,7 +116,7 @@ export const Moves: { [moveid: string]: ModdedMoveData } = { } }, }, - shortDesc: "User Water power 2x, takes 0.5x Fire damage. Recover 1/16 max HP per turn.", + shortDesc: "2x Water power, 0.5x Fire damage, heal 1/16 HP per turn.", desc: "User recovers 1/16 max HP per turn. While this is active, this Pokemon's Water power is 2x and Fire power against it is halved.", }, @@ -145,58 +131,50 @@ export const Moves: { [moveid: string]: ModdedMoveData } = { type: "Normal", effectType: "Move", flags: { contact: 1, protect: 1, mirror: 1, metronome: 1 }, - shortDesc: "Changes type to the most effective against the target (Water, Fighting, Fire, or Normal).", + shortDesc: "Type swaps to most effective (Water, Fighting, Fire, or Normal).", desc: "Changes the move's and user's forme to the most effective against the target (Water, Fighting, Fire, or Normal).", beforeMoveCallback(source, target, move) { - const typeEffectiveness = { - Normal: this.dex.getEffectiveness('Normal', target), - Water: this.dex.getEffectiveness('Water', target), - Fighting: this.dex.getEffectiveness('Fighting', target), - Fire: this.dex.getEffectiveness('Fire', target), - }; - - let bestType = 'Normal'; - let maxEffectiveness = -Infinity; - // gets most effective type against target (defaults to normal) - for (const type in typeEffectiveness) { - if (typeEffectiveness[type] > maxEffectiveness) { - maxEffectiveness = typeEffectiveness[type]; - bestType = type; - } - } - // changes form to match most effective type - if (bestType === 'Water') { - source.formeChange('Tauros-Paldea-Aqua'); - source.setAbility('Adaptability'); - this.add('-ability', source, 'Adaptability'); - } else if (bestType === 'Fighting') { - source.formeChange('Tauros-Paldea-Combat'); - source.setAbility('Adaptability'); - this.add('-ability', source, 'Adaptability'); - } else if (bestType === 'Fire') { - source.formeChange('Tauros-Paldea-Blaze'); - source.setAbility('Adaptability'); - this.add('-ability', source, 'Adaptability'); - } else { - source.formeChange('Tauros'); - source.setAbility('Adaptability'); + if (target) { + const typeEffectiveness: { [k: string]: number } = { + Normal: this.dex.getEffectiveness('Normal', target), + Water: this.dex.getEffectiveness('Water', target), + Fighting: this.dex.getEffectiveness('Fighting', target), + Fire: this.dex.getEffectiveness('Fire', target), + }; + let bestType = 'Normal'; + let type: keyof typeof typeEffectiveness; + let maxEffectiveness = -Infinity; + // gets most effective type against target (defaults to normal) + for (type in typeEffectiveness) { + if (typeEffectiveness[type] > maxEffectiveness) { + maxEffectiveness = typeEffectiveness[type]; + bestType = type; + } + } + // changes form to match most effective type + let forme = ''; + switch (bestType) { + case 'Water': forme = '-Paldea-Aqua'; break; + case 'Fighting': forme = '-Paldea-Combat'; break; + case 'Fire': forme = '-Paldea-Blaze'; break; + } + source.formeChange('Tauros' + forme); this.add('-ability', source, 'Adaptability'); - } - - source.m.ragingBullMoveType = bestType; + source.m.ragingBullMoveType = bestType; + } }, - // animation was remnant of Techno Blast code being copied, decided to keep because funny + // animation was remnant of Techno Blast code being copied, decided to keep because funny onPrepareHit(target, source, move) { - this.add('-anim', source, 'Techno Blast', target); + this.add('-anim', source, 'Techno Blast', target); }, - // sets type properly (failsafe) + // sets type properly (failsafe) onModifyType(move, pokemon, target) { - if (pokemon.m.ragingBullMoveType) { + if (pokemon.m.ragingBullMoveType) { move.type = pokemon.m.ragingBullMoveType; - } + } }, target: "normal", - }, + }, iciclestorm: { num: -1044, accuracy: 95, @@ -205,7 +183,7 @@ export const Moves: { [moveid: string]: ModdedMoveData } = { name: "Icicle Storm", pp: 15, priority: 0, - flags: { contact: 1, protect: 1, mirror: 1, metronome: 1 }, + flags: { protect: 1, mirror: 1, metronome: 1 }, onTryMove() { this.attrLastMove('[still]'); }, @@ -217,7 +195,7 @@ export const Moves: { [moveid: string]: ModdedMoveData } = { self: { onHit(source) { this.field.setWeather('snow'); - } + }, }, secondary: null, target: "normal", @@ -227,13 +205,13 @@ export const Moves: { [moveid: string]: ModdedMoveData } = { shortDesc: "Sets Snow.", }, springtidestorm: { - //Now always hits in Sand in addition to Rain + // Now always hits in Sand in addition to Rain inherit: true, onModifyMove(move, pokemon, target) { - if (target && ['raindance'].includes(target.effectiveWeather())) { + if (target && ['sandstorm', 'raindance'].includes(target.effectiveWeather())) { move.accuracy = true; } - } + }, }, // Poison type now spitup: { @@ -263,7 +241,7 @@ export const Moves: { [moveid: string]: ModdedMoveData } = { const special = Math.floor(Math.floor(Math.floor(Math.floor(2 * pokemon.level / 5 + 2) * 90 * spa) / spd) / 50); if (physical < special || (physical === special && this.randomChance(1, 2))) { move.category = 'Special'; - move.flags.contact = 0; + move.flags.contact = undefined; } }, onPrepareHit(target, source) { @@ -306,7 +284,7 @@ export const Moves: { [moveid: string]: ModdedMoveData } = { secondary: null, target: "normal", type: "Water", - zMove: { boost: { atk: 1 } }, + zMove: { boost: { spe: 1 } }, contestType: "Beautiful", desc: "Encore + Rain Dance", shortDesc: "Encore + Rain Dance", @@ -324,27 +302,25 @@ export const Moves: { [moveid: string]: ModdedMoveData } = { // checks for water move usage from opponent onModifyPriority(priority, source) { // gets current foe in singles - const foe = source.side.foe.active[0]; - if (!foe || foe.fainted) { + const foe = source.foes()[0]; + if (!foe || foe.fainted) { return priority; } // gets attack of foe this turn - const action = this.queue.willMove(foe); - if (!action || action.choice !== 'move') { + const action = this.queue.willMove(foe); + if (!action || action.choice !== 'move') { return priority; } - const move = action.move; - if (move?.type === 'Water') { - return priority + 1; - } - else { + const move = action.move; + if (move?.type === 'Water') { + return priority + 1; + } else { return priority; } - return priority; }, // modifies base power onBasePower(basePower, source, target) { - const foe = source.side.foe.active[0]; + const foe = source.foes()[0]; if (!foe || foe.fainted) { return basePower; } @@ -356,11 +332,9 @@ export const Moves: { [moveid: string]: ModdedMoveData } = { if (move?.type === 'Water') { this.add('-message', `Sudowoodo draws power from the water!`); return basePower + 70; - } - else { + } else { return basePower; } - return basePower; }, onTryMove() { this.attrLastMove('[still]'); @@ -373,11 +347,11 @@ export const Moves: { [moveid: string]: ModdedMoveData } = { target: "normal", type: "Rock", contestType: "Beautiful", - shortDesc: "+1 Priority and 2x power if target is using a Water move.", + shortDesc: "+1 Priority and 2x power if target uses Water move.", desc: "If the target is using a Water type move, this move will always move first and gains double power.", }, ironstrike: { - //implemented via changes to Stealth Rocks and Spikes + // implemented via changes to Stealth Rocks and Spikes num: -107, accuracy: 100, basePower: 50, @@ -399,41 +373,42 @@ export const Moves: { [moveid: string]: ModdedMoveData } = { shortDesc: "Inflicts damage from hazards on target's side.", desc: "Target takes damage from all entry hazards on their side of the field, unless they are immune.", }, + thunderkick: { + num: -1192, + name: "Thunder Kick", + type: "Electric", + basePower: 50, + accuracy: 100, + category: "Physical", + priority: 0, + pp: 5, + target: "normal", + contestType: "Beautiful", + onTryMove() { + this.attrLastMove('[still]'); + }, + onPrepareHit(t, s, m) { + this.add('-anim', s, 'High Jump Kick', t); + this.add('-anim', s, 'Thunder', t); + }, + flags: { contact: 1, protect: 1 }, + }, thunderouskick: { inherit: true, secondary: null, onHit(target, source, move) { // random # 0 or 1 - const randomNum = Math.round(Math.random()); + const randomNum = this.random(2); // 50% chance to drop def - if (randomNum === 0) { - if (target.boosts.def !== -6) { - this.boost({def: -1}, target, source, move); - } - } - // if not def drop, does thunder kick - else { - this.add('-message', `${source.name} follows up with a Thunder Kick!`); - // defines Thunder Kick - const thunderKick = { - name: "Thunder Kick", - type: "Electric", - basePower: 50, - accuracy: 100, - category: "Physical", - priority: 0, - onTryMove() { - this.attrLastMove('[still]'); - }, - onPrepareHit(target, source, move) { - this.add('-anim', source, 'High Jump Kick', target); - this.add('-anim', source, 'Thunder', target); - }, - flags: {contact: true, protect: true}, - }; + if (randomNum === 0) { + if (target.boosts.def !== -6) { + this.boost({ def: -1 }, target, source, move); + } + } else { + this.add('-message', `${source.name} follows up with a Thunder Kick!`); // uses Thunder Kick - this.actions.useMove(thunderKick, source, target); - } + this.actions.useMove('thunderkick', source, target); + } }, desc: "50% chance to reduce Defense by 1, 50% chance to inflict an additional 50 BP Electric type damage.", shortDesc: "50% -1 Defense, 50% extra 50 BP Electric damage.", @@ -455,7 +430,8 @@ export const Moves: { [moveid: string]: ModdedMoveData } = { }, onSwitchIn(pokemon) { // hardcode to prevent hazard damage during Order Up switches + enforce hazard damage while King of the Hill is active - if ((pokemon.hasItem('heavydutyboots') && !pokemon.side.getSideCondition('kingofthehill')) || pokemon.side.getSideCondition('orderup')) return; + if ((pokemon.hasItem('heavydutyboots') && !pokemon.side.getSideCondition('kingofthehill')) || + pokemon.side.getSideCondition('orderup')) return; const typeMod = this.clampIntRange(pokemon.runEffectiveness(this.dex.getActiveMove('stealthrock')), -6, 6); this.damage(pokemon.maxhp * (2 ** typeMod) / 8); }, @@ -497,7 +473,8 @@ export const Moves: { [moveid: string]: ModdedMoveData } = { }, onSwitchIn(pokemon) { // Order Up + king of the hill functionality - if (((!pokemon.isGrounded() || pokemon.hasItem('heavydutyboots')) && !pokemon.side.getSideCondition('kingofthehill')) || pokemon.side.getSideCondition('orderup')) return; + if (((!pokemon.isGrounded() || pokemon.hasItem('heavydutyboots')) && !pokemon.side.getSideCondition('kingofthehill')) || + pokemon.side.getSideCondition('orderup')) return; const damageAmounts = [0, 3, 4, 6]; // 1/8, 1/6, 1/4 this.damage(damageAmounts[this.effectState.layers] * pokemon.maxhp / 24); }, @@ -516,7 +493,7 @@ export const Moves: { [moveid: string]: ModdedMoveData } = { zMove: { boost: { def: 1 } }, contestType: "Clever", }, - orderup: { + orderup: { num: 856, accuracy: 100, basePower: 80, @@ -530,53 +507,50 @@ export const Moves: { [moveid: string]: ModdedMoveData } = { onSwitchInPriority: -1, onSwitchIn(pokemon) { // when Dondozo switches back in after eating, it gains boost - if (pokemon.baseSpecies.baseSpecies == 'Dondozo') { + if (pokemon.baseSpecies.baseSpecies === 'Dondozo') { // reapplies volatiles and stat boosts - if (pokemon.storedVolatiles) { - for (const volatile in pokemon.storedVolatiles) { - pokemon.addVolatile(volatile); + if ((pokemon as any).storedVolatiles) { + for (const volatile in (pokemon as any).storedVolatiles) { + pokemon.addVolatile(volatile); } } - if (pokemon.storedBoosts) { - for (const stat in pokemon.storedBoosts) { - const change = pokemon.storedBoosts[stat]; - if (change !== 0) { - this.boost({[stat]: change}, pokemon); - } - } + if ((pokemon as any).storedBoosts) { + for (const stat in (pokemon as any).storedBoosts) { + const change = (pokemon as any).storedBoosts[stat as BoostID]; + if (change !== 0) { + this.boost({ [stat]: change }, pokemon); + } + } } this.add('-message', `Dondozo enjoyed its meal!`); // applies boost based on eaten mon stats if (this.effectState.eatenBoost === 'atk' || this.effectState.eatenBoost === 'spa') { this.boost({ atk: 3 }, pokemon); + } else if (this.effectState.eatenBoost === 'def' || this.effectState.eatenBoost === 'spd') { + this.boost({ def: 2, spd: 2 }, pokemon); + } else { + this.boost({ spe: 3 }, pokemon); } - else if (this.effectState.eatenBoost === 'def' || this.effectState.eatenBoost === 'spd') { - this.boost({ def: 2, spd: 2}, pokemon); - } - else { - this.boost({ spe: 3}, pokemon); - } - // adds volatile ordered, which prevents the order up effect from occuring again until Dondozo switches out + // adds volatile ordered, which prevents the order up effect from occurring again until Dondozo switches out pokemon.addVolatile('ordered'); // removes the side condition pokemon.side.removeSideCondition('orderup'); - } - // after Dondozo switches out, this happens to the next pokemon that is switched in - else { + } else { + // after Dondozo switches out, this happens to the next pokemon that is switched in const meal = pokemon; // faints the eaten mon pokemon.faint(); // finds highest stat of eaten mon, stored in effectState eatenBoost const stats = ['atk', 'def', 'spa', 'spd', 'spe']; - let highestStat = stats[0]; - let maxStatValue = meal.storedStats[highestStat]; + let highestStat = stats[0]; + let maxStatValue = meal.storedStats[highestStat as StatIDExceptHP]; - for (const stat of stats) { - if (meal.storedStats[stat] > maxStatValue) { - highestStat = stat; - maxStatValue = meal.storedStats[stat]; - } - } + for (const stat of stats) { + if (meal.storedStats[stat as StatIDExceptHP] > maxStatValue) { + highestStat = stat; + maxStatValue = meal.storedStats[stat as StatIDExceptHP]; + } + } this.effectState.eatenBoost = highestStat; } }, @@ -586,22 +560,23 @@ export const Moves: { [moveid: string]: ModdedMoveData } = { if (!dondozo) return; // forces Dondozo in after the eaten mon faints this.queue.insertChoice({ - choice: 'switch', - pokemon: pokemon, + choice: 'switch', + pokemon, target: dondozo, - }); + }); this.checkFainted(); - } + }, }, // when order up hits, first checks for volatile ordered to ensure that Order Up has not already been used, then starts orderup side condition and switches Dondozo out onHit(target, source, move) { if (source.volatiles['ordered']) return; + if (source.species.id === 'mew') return; source.side.addSideCondition('orderup'); // stores stat changes and volatiles to reapply after switch - source.storedBoosts = { ...source.boosts }; - source.storedVolatiles = {}; + (source as any).storedBoosts = { ...source.boosts }; + (source as any).storedVolatiles = {}; for (const volatile in source.volatiles) { - source.storedVolatiles[volatile] = source.volatiles[volatile]; + (source as any).storedVolatiles[volatile] = source.volatiles[volatile]; } if (source.side.getSideCondition('orderup')) { this.add('-ability', source, 'Order Up'); @@ -614,7 +589,7 @@ export const Moves: { [moveid: string]: ModdedMoveData } = { target: "normal", type: "Dragon", desc: "Dondozo eats a mon on the user's team, KOing it. Dondozo then gains a stat boost depending on the eaten mon's highest stat: +3 Attack for Atk/SpA, +2 Def/+2 SpD for Def/SpD, and +3 Speed for Speed.", - shortDesc: "Dondozo orders up a meal. Dondozo gains stat boosts based on the highest stat of the Pokemon it eats.", + shortDesc: "Dondozo KOs an ally mon. Gain stat boost in ally's highest stat.", }, toxicspikes: { // prevents Dondozo from being affected by Toxic Spikes during Order Up switching @@ -637,7 +612,11 @@ export const Moves: { [moveid: string]: ModdedMoveData } = { this.add('-sideend', pokemon.side, 'move: Toxic Spikes', `[of] ${pokemon}`); pokemon.side.removeSideCondition('toxicspikes'); // hardcode for King of the Hill and Order Up - } else if (pokemon.hasType('Steel') || (pokemon.hasItem('heavydutyboots') && !pokemon.side.getSideCondition('kingofthehill')) || pokemon.side.getSideCondition('orderup')) { + } else if ( + pokemon.hasType('Steel') || + (pokemon.hasItem('heavydutyboots') && !pokemon.side.getSideCondition('kingofthehill')) || + pokemon.side.getSideCondition('orderup') + ) { // do nothing } else if (this.effectState.layers >= 2) { pokemon.trySetStatus('tox', pokemon.side.foe.active[0]); @@ -655,7 +634,8 @@ export const Moves: { [moveid: string]: ModdedMoveData } = { }, onSwitchIn(pokemon) { // king of the hill - if ((!pokemon.isGrounded() || pokemon.hasItem('heavydutyboots')) && !pokemon.side.getSideCondition('kingofthehill')) return; + if ((!pokemon.isGrounded() || pokemon.hasItem('heavydutyboots')) && + !pokemon.side.getSideCondition('kingofthehill')) return; this.add('-activate', pokemon, 'move: Sticky Web'); this.boost({ spe: -1 }, pokemon, pokemon.side.foe.active[0], this.dex.getActiveMove('stickyweb')); }, @@ -701,10 +681,9 @@ export const Moves: { [moveid: string]: ModdedMoveData } = { }, onAfterMoveSecondarySelf(pokemon, target, move) { if (!target || target.fainted || target.hp <= 0) { - this.boost({ atk: 1, }, pokemon, pokemon, move); - } - else { - this.boost({ atk: -1, }, pokemon, pokemon, move); + this.boost({ atk: 1 }, pokemon, pokemon, move); + } else { + this.boost({ atk: -1 }, pokemon, pokemon, move); } }, flags: { contact: 1, protect: 1, mirror: 1, metronome: 1 }, @@ -716,142 +695,113 @@ export const Moves: { [moveid: string]: ModdedMoveData } = { shortDesc: "On KO: +1 Atk. Otherwise -1 Atk.", }, wickedblow: { - num: 817, - accuracy: 100, - basePower: 75, - category: "Physical", - name: "Wicked Blow", - pp: 5, - priority: 0, + inherit: true, beforeMoveCallback(source, target, move) { - this.effectState.surgingStrikesAlreadyUsed = 0; - this.add('-anim', source, 'Techno Blast', target); - const typeEffectiveness = { - Water: this.dex.getEffectiveness('Water', target), - Dark: this.dex.getEffectiveness('Dark', target), - }; - - let bestType = 'Water'; - let maxEffectiveness = -Infinity; - // gets most effective type against target (defaults to the current type) - for (const type in typeEffectiveness) { - if (typeEffectiveness[type] > maxEffectiveness) { - maxEffectiveness = typeEffectiveness[type]; - bestType = type; - } - } - // changes form to match most effective type - if (bestType === 'Dark') { - this.add('-message', `Urshifu takes pity on its foe and transforms into a weaker type!`); - source.formeChange('Urshifu-Rapid-Strike', source, true); - source.setAbility('Sniper'); - this.add('-ability', source, 'Sniper'); - const oldMove = 'wickedblow'; - const newMove = 'surgingstrikes'; - - const oldMoveId = this.toID(oldMove); - const newMoveData = this.dex.moves.get(newMove); - - for (const slot of source.moveSlots) { - if (slot.id === oldMoveId) { - slot.move = newMoveData.name; - slot.id = newMoveData.id; - slot.pp = newMoveData.pp; - slot.maxpp = newMoveData.pp; - slot.target = newMoveData.target; - slot.disabled = false; - slot.used = false; - break; + if (target) { + this.effectState.surgingStrikesAlreadyUsed = 0; + this.add('-anim', source, 'Techno Blast', target); + const typeEffectiveness: { [k: string]: number } = { + Water: this.dex.getEffectiveness('Water', target), + Dark: this.dex.getEffectiveness('Dark', target), + }; + let type: keyof typeof typeEffectiveness; + let bestType = 'Water'; + let maxEffectiveness = -Infinity; + // gets most effective type against target (defaults to the current type) + for (type in typeEffectiveness) { + if (typeEffectiveness[type] > maxEffectiveness) { + maxEffectiveness = typeEffectiveness[type]; + bestType = type; + } + } + // changes form to match most effective type + if (bestType === 'Dark') { + this.add('-message', `Urshifu takes pity on its foe and transforms into a weaker type!`); + source.formeChange('Urshifu-Rapid-Strike', null, true); + source.setAbility('Sniper'); + this.add('-ability', source, 'Sniper'); + const oldMove = 'wickedblow'; + const newMove = 'surgingstrikes'; + const oldMoveId = this.toID(oldMove); + const newMoveData = this.dex.moves.get(newMove); + const oldMoveIdx = source.moveSlots.findIndex(x => x.id === oldMoveId); + if (oldMoveIdx >= 0) { + source.moveSlots[oldMoveIdx] = source.baseMoveSlots[oldMoveIdx] = { + move: newMoveData.name, + id: newMoveData.id, + pp: newMoveData.pp, + maxpp: newMoveData.pp, + target: newMoveData.target, + disabled: false, + used: false, + }; } + this.actions.useMove('surgingstrikes', source, target); + this.effectState.surgingStrikesAlreadyUsed = 1; } - source.baseMoveSlots = source.moveSlots.slice(); - this.actions.useMove('surgingstrikes', source, target); - this.effectState.surgingStrikesAlreadyUsed = 1; - } - + } }, onTry(source, target, move) { if (this.effectState.surgingStrikesAlreadyUsed === 1) { return null; } }, - flags: { contact: 1, protect: 1, mirror: 1, punch: 1 }, - willCrit: true, - secondary: null, - target: "normal", - type: "Dark", desc: "This move will transform into Rapid Strike Urshifu/Surging Strikes if it would be less effective against the target.", shortDesc: "Becomes Surging Strikes if it would be less effective.", }, surgingstrikes: { - num: 818, - accuracy: 100, - basePower: 25, - category: "Physical", - name: "Surging Strikes", - pp: 5, - priority: 0, + inherit: true, beforeMoveCallback(source, target, move) { - this.effectState.wickedBlowAlreadyUsed = 0; - this.add('-anim', source, 'Techno Blast', target); - const typeEffectiveness = { - Dark: this.dex.getEffectiveness('Dark', target), - Water: this.dex.getEffectiveness('Water', target), - }; - - let bestType = 'Dark'; - let maxEffectiveness = -Infinity; - // gets most effective type against target (defaults to the current type) - for (const type in typeEffectiveness) { - if (typeEffectiveness[type] > maxEffectiveness) { - maxEffectiveness = typeEffectiveness[type]; - bestType = type; - } - } - // changes form to match most effective type - if (bestType === 'Water') { - this.add('-message', `Urshifu takes pity on its foe and transforms into a weaker type!`); - source.formeChange('Urshifu', source, true); - source.setAbility('Sniper'); - this.add('-ability', source, 'Sniper'); - const oldMove = 'surgingstrikes'; - const newMove = 'wickedblow'; - - const oldMoveId = this.toID(oldMove); - const newMoveData = this.dex.moves.get(newMove); - - for (const slot of source.moveSlots) { - if (slot.id === oldMoveId) { - slot.move = newMoveData.name; - slot.id = newMoveData.id; - slot.pp = newMoveData.pp; - slot.maxpp = newMoveData.pp; - slot.target = newMoveData.target; - slot.disabled = false; - slot.used = false; - break; + if (source.species.id === 'araquanid') return; + if (target) { + this.effectState.wickedBlowAlreadyUsed = 0; + this.add('-anim', source, 'Techno Blast', target); + const typeEffectiveness: { [k: string]: number } = { + Dark: this.dex.getEffectiveness('Dark', target), + Water: this.dex.getEffectiveness('Water', target), + }; + let type: keyof typeof typeEffectiveness; + let bestType = 'Dark'; + let maxEffectiveness = -Infinity; + // gets most effective type against target (defaults to the current type) + for (type in typeEffectiveness) { + if (typeEffectiveness[type] > maxEffectiveness) { + maxEffectiveness = typeEffectiveness[type]; + bestType = type; + } + } + // changes form to match most effective type + if (bestType === 'Water') { + this.add('-message', `Urshifu takes pity on its foe and transforms into a weaker type!`); + source.formeChange('Urshifu', null, true); + source.setAbility('Sniper'); + this.add('-ability', source, 'Sniper'); + const newMove = 'wickedblow'; + const oldMoveId: ID = 'surgingstrikes' as ID; + const newMoveData = this.dex.moves.get(newMove); + const oldMoveIdx = source.moveSlots.findIndex(x => x.id === oldMoveId); + if (oldMoveIdx >= 0) { + source.moveSlots[oldMoveIdx] = source.baseMoveSlots[oldMoveIdx] = { + move: newMoveData.name, + id: newMoveData.id, + pp: newMoveData.pp, + maxpp: newMoveData.pp, + target: newMoveData.target, + disabled: false, + used: false, + }; } + this.actions.useMove('wickedblow', source, target); + this.effectState.wickedBlowAlreadyUsed = 1; } - source.baseMoveSlots = source.moveSlots.slice(); - this.actions.useMove('wickedblow', source, target); - this.effectState.wickedBlowAlreadyUsed = 1; - } - + } }, onTry(source, target, move) { if (this.effectState.wickedBlowAlreadyUsed === 1) { return null; } }, - flags: { contact: 1, protect: 1, mirror: 1, punch: 1 }, - willCrit: true, - multihit: 3, - secondary: null, - target: "normal", - type: "Water", - zMove: { basePower: 140 }, - maxMove: { basePower: 130 }, - desc: "This move will transform into Single Strike Urshifu/Wicked Blow if it would be less effective against the target.", + desc: "This move will transform into Single Strike Urshifu/Wicked Blow if it would be less effective against the target. Does not work with Araquanid.", shortDesc: "Becomes Wicked Blow if it would be less effective.", }, twister: { @@ -889,6 +839,912 @@ export const Moves: { [moveid: string]: ModdedMoveData } = { contestType: "Cool", desc: "Removes hazards, side conditions, and terrain. Lowers Evasion by 1.", shortDesc: "-1 evasion; ends user and target hazards/terrain.", - } + }, + magnetbomb: { + num: 443, + accuracy: true, + basePower: 90, + category: "Special", + name: "Magnet Bomb", + pp: 20, + priority: 0, + onHit(target, source, move) { + target.setType('Steel'); + this.add('-start', target, 'typechange', 'Steel'); + }, + flags: { protect: 1, mirror: 1, metronome: 1, bullet: 1 }, + secondary: null, + target: "normal", + type: "Steel", + contestType: "Cool", + desc: "Changes the target's type to Steel.", + shortDesc: "Changes the target's type to Steel.", + }, + triplekick: { + inherit: true, + basePower: 20, + basePowerCallback(pokemon, target, move) { + return 20 * move.hit; + }, + }, + freezingglare: { + inherit: true, + secondary: { + chance: 30, + onHit(target, source, move) { + if (!target.hasType('Ice')) { + target.trySetStatus('frostbite', source, move); + } + }, + }, + desc: "30% chance to inflict Frostbite.", + shortDesc: "30% chance to inflict Frostbite.", + }, + zippyzap: { + inherit: true, + category: "Special", + onTryMove() { + this.attrLastMove('[still]'); + }, + onPrepareHit(target, source) { + this.add('-anim', source, 'Extreme Speed', target); + this.add('-anim', source, 'Thunder', target); + }, + secondary: null, + desc: "Nearly always goes first.", + shortDesc: "Nearly always goes first.", + }, + burnout: { + num: -1004, + accuracy: 100, + basePower: 70, + category: "Special", + name: "Burn Out", + pp: 20, + priority: 0, + onTryMove() { + this.attrLastMove('[still]'); + }, + onPrepareHit(target, source) { + this.add('-anim', source, 'Fire Spin', target); + }, + onHit(target, source, move) { + if (source.species.id === 'jolteon' || source.species.id === 'vaporeon') { + this.add('-message', `Eevee uses its Fire Stone!`); + const currentHP = source.hp / source.maxhp; + source.formeChange('Flareon', null, true); + source.sethp(source.maxhp * currentHP); + this.add('-sethp', source, source.getHealth, '[from] move: Flip Turn', '[silent]'); + // target.setAbility('Eeveelution'); + // target.baseAbility = target.ability; + const newMoves = ['flipturn', 'voltswitch', 'sizzlyslide', 'facade']; + // Update move slots + // eslint-disable-next-line @typescript-eslint/no-shadow + source.moveSlots = newMoves.map(move => { + const moveData = this.dex.moves.get(move); + return { + move: moveData.name, + id: moveData.id, + pp: moveData.pp, + maxpp: moveData.pp, + target: moveData.target, + disabled: false, + used: false, + }; + }); + // this forces the UI to update move slots visually + (source as any).baseMoveSlots = source.moveSlots.slice(); + } + }, + flags: { contact: 1, protect: 1, mirror: 1, metronome: 1 }, + selfSwitch: true, + secondary: null, + target: "normal", + type: "Fire", + contestType: "Cute", + desc: "User switches out after damaging the target.", + shortDesc: "User switches out after damaging the target.", + }, + voltswitch: { + inherit: true, + onHit(target, source, move) { + if (source.species.id === 'flareon' || source.species.id === 'vaporeon') { + this.add('-message', `Eevee uses its Thunder Stone!`); + const currentHP = source.hp / source.maxhp; + source.formeChange('Jolteon', null, true); + source.sethp(source.maxhp * currentHP); + this.add('-sethp', source, source.getHealth, '[from] move: Flip Turn', '[silent]'); + // target.setAbility('Eeveelution'); + // target.baseAbility = target.ability; + const newMoves = ['flipturn', 'burnout', 'zippyzap', 'freezyfrost']; + // Update move slots + // eslint-disable-next-line @typescript-eslint/no-shadow + source.moveSlots = newMoves.map(move => { + const moveData = this.dex.moves.get(move); + return { + move: moveData.name, + id: moveData.id, + pp: moveData.pp, + maxpp: moveData.pp, + target: moveData.target, + disabled: false, + used: false, + }; + }); + // this forces the UI to update move slots visually + (source as any).baseMoveSlots = source.moveSlots.slice(); + } + }, + }, + flipturn: { + inherit: true, + onHit(target, source, move) { + if (source.species.id === 'jolteon' || source.species.id === 'flareon') { + this.add('-message', `Eevee uses its Water Stone!`); + const currentHP = source.hp / source.maxhp; + source.formeChange('Vaporeon', null, true); + source.sethp(source.maxhp * currentHP); + this.add('-sethp', source, source.getHealth, '[from] move: Flip Turn', '[silent]'); + // target.setAbility('Eeveelution'); + // target.baseAbility = target.ability; + const newMoves = ['voltswitch', 'burnout', 'recover', 'scald']; + // Update move slots + // eslint-disable-next-line @typescript-eslint/no-shadow + source.moveSlots = newMoves.map(move => { + const moveData = this.dex.moves.get(move); + return { + move: moveData.name, + id: moveData.id, + pp: moveData.pp, + maxpp: moveData.pp, + target: moveData.target, + disabled: false, + used: false, + }; + }); + // this forces the UI to update move slots visually + (source as any).baseMoveSlots = source.moveSlots.slice(); + } + }, + }, + sizzlyslide: { + inherit: true, + basePower: 80, + onTryMove() { + this.attrLastMove('[still]'); + }, + onPrepareHit(target, source) { + this.add('-anim', source, 'Flame Charge', target); + }, + }, + freezyfrost: { + inherit: true, + onTryMove() { + this.attrLastMove('[still]'); + }, + onPrepareHit(target, source) { + this.add('-anim', source, 'Blizzard', target); + }, + }, + bouncybubble: { + inherit: true, + onTryMove() { + this.attrLastMove('[still]'); + }, + onPrepareHit(target, source) { + this.add('-anim', source, 'Bubble Beam', target); + }, + }, + purify: { + inherit: true, + pp: 10, + flags: { reflectable: 1, heal: 1, metronome: 1 }, + onHit(target, source) { + const foe = source.side.foe.active[0]; + if (foe && !foe.fainted && foe.status) { + this.heal(Math.ceil(source.maxhp * 0.5), source); + } else { + this.heal(Math.ceil(source.maxhp * 0.25), source); + } + }, + target: "self", + desc: "Heals for 25% HP, or 50% if foe is statused.", + shortDesc: "Heals for 25% HP, or 50% if foe is statused.", + }, + saltcurse: { + num: -1006, + accuracy: 100, + basePower: 70, + basePowerCallback(pokemon, target, move) { + if (target.status === 'par') { + this.debug('BP doubled on paralyzed target'); + return move.basePower * 2; + } + return move.basePower; + }, + onEffectiveness(typeMod, target, type) { + if (type === 'Water') return 1; + if (type === 'Steel') return 1; + }, + onTryMove() { + this.attrLastMove('[still]'); + }, + onPrepareHit(target, source) { + this.add('-anim', source, 'Glare', target); + this.add('-anim', source, 'Ivy Cudgel Rock', target); + }, + category: "Physical", + name: "Salt Curse", + pp: 10, + priority: 0, + flags: { contact: 1, protect: 1, mirror: 1, metronome: 1 }, + secondary: null, + target: "normal", + type: "Rock", + contestType: "Tough", + desc: "Double power if target is Paralyzed. Super-effective against Water and Steel.", + shortDesc: "2x BP if target is Paralyzed, Water type, or Steel type.", + }, + flyby: { + num: -1006, + accuracy: 100, + basePower: 70, + category: "Special", + name: "Fly-by", + pp: 20, + priority: 0, + onTryMove() { + this.attrLastMove('[still]'); + }, + onPrepareHit(target, source) { + this.add('-anim', source, 'Dual Wingbeat', target); + }, + flags: { protect: 1, mirror: 1, metronome: 1 }, + selfSwitch: true, + secondary: { + chance: 50, + boosts: { + atk: -1, + }, + }, + target: "normal", + type: "Flying", + contestType: "Cute", + desc: "User switches out. Target: -1 Attack.", + shortDesc: "User switches out. Target: -1 Attack.", + }, + silktrap: { + inherit: true, + condition: { + duration: 1, + onStart(target) { + this.add('-singleturn', target, 'Protect'); + }, + onTryHitPriority: 3, + onTryHit(target, source, move) { + if (!move.flags['protect'] || move.category === 'Status') { + if (move.isZ || move.isMax) target.getMoveHitData(move).zBrokeProtect = true; + return; + } + if (move.smartTarget) { + move.smartTarget = false; + } else { + this.add('-activate', target, 'move: Protect'); + } + const lockedmove = source.getVolatile('lockedmove'); + if (lockedmove) { + // Outrage counter is reset + if (source.volatiles['lockedmove'].duration === 2) { + delete source.volatiles['lockedmove']; + } + } + if (this.checkMoveMakesContact(move, source, target)) { + source.side.addSideCondition('stickyweb'); + } + return this.NOT_FAIL; + }, + onHit(target, source, move) { + if (move.isZOrMaxPowered && this.checkMoveMakesContact(move, source, target)) { + source.side.addSideCondition('stickyweb'); + } + }, + }, + desc: "Protect. If contact: set Sticky Web.", + shortDesc: "Protect. If contact: set Sticky Web.", + }, + heatsink: { + num: -1007, + accuracy: 100, + basePower: 80, + onTryMove() { + this.attrLastMove('[still]'); + }, + onPrepareHit(target, source) { + this.add('-anim', source, 'Fire Spin', target); + this.add('-anim', source, 'Bitter Blade', target); + }, + onModifyMove(move, source, target) { + if (target?.status === 'brn') { + move.drain = [3, 4]; + } + }, + category: "Special", + name: "Heat Sink", + pp: 20, + priority: 0, + flags: { protect: 1, mirror: 1, metronome: 1 }, + drain: [1, 2], + secondary: null, + target: "normal", + type: "Fire", + zMove: { basePower: 160 }, + contestType: "Clever", + desc: "50% drain. 75% drain instead if target is Burned.", + shortDesc: "50% drain. 75% drain if target is Burned.", + }, + terastarstorm: { + inherit: true, + onModifyType(move, pokemon) { + const types = pokemon.getTypes(); + let type = types[0]; + if (type === 'Bird') type = '???'; + if (type === '???' && types[1]) type = types[1]; + move.type = type; + if (pokemon.species.name === 'Terapagos-Stellar') { + move.type = 'Stellar'; + if (pokemon.getStat('atk', false, true) > pokemon.getStat('spa', false, true)) { + move.category = 'Physical'; + } + } + }, + desc: "Type varies based on the user's primary type.", + shortDesc: "Type varies based on the user's primary type.", + }, + grabapple: { + num: -1008, + accuracy: 100, + basePower: 100, + category: "Physical", + name: "Grab Apple", + pp: 10, + priority: 0, + flags: { contact: 1, protect: 1, mirror: 1, metronome: 1 }, + secondary: null, + target: "normal", + type: "Grass", + contestType: "Tough", + onTryMove() { + this.attrLastMove('[still]'); + }, + onPrepareHit(target, source) { + this.add('-anim', source, 'Grav Apple', target); + this.add('-anim', source, 'Thief', target); + }, + onAfterMove(source) { + if (source.lastItem) { + const item = source.lastItem; + source.lastItem = ''; + source.setItem(item); + this.add('-item', source, this.dex.items.get(item), '[from] move: Grab Apple'); + } else { + return null; + } + }, + shortDesc: "User regains their last used item, similar to Recycle.", + desc: "If the user has consumed their item, it will be restored.", + }, + sashimishuffle: { + num: -1009, + accuracy: true, + basePower: 0, + category: "Status", + name: "Sashimi Shuffle", + pp: 5, + priority: 0, + flags: { metronome: 1 }, + onTryMove() { + this.attrLastMove('[still]'); + }, + onPrepareHit(target, source) { + this.add('-anim', source, 'Order Up', target); + this.add('-anim', source, 'Order Up', target); + }, + onHit(target) { + if (!this.canSwitch(target.side) || target.volatiles['commanded']) { + this.attrLastMove('[still]'); + this.add('-fail', target); + return this.NOT_FAIL; + } + }, + self: { + onHit(source) { + source.skipBeforeSwitchOutEventFlag = true; + }, + }, + selfSwitch: true, + slotCondition: 'sashimishuffle', + condition: { + onSwitchIn(target) { + this.singleEvent('Swap', this.effect, this.effectState, target); + }, + onSwap(target) { + if (!target.fainted) { + target.heal(target.maxhp / 3); + this.add('-heal', target, target.getHealth, '[from] move: Sashimi Shuffle'); + target.side.removeSlotCondition(target, 'sashimishuffle'); + } + }, + }, + secondary: null, + target: "self", + type: "Normal", + zMove: { effect: 'clearnegativeboost' }, + contestType: "Cute", + shortDesc: "User switches. Next Pokemon heals 1/3 HP.", + desc: "User switches. Next Pokemon heals 1/3 HP.", + }, + technoblast: { + inherit: true, + basePowerCallback(pokemon, target, move) { + if (this.field.isWeather('snow')) { + return move.basePower * 1.3; + } else return move.basePower; + }, + }, + crowverload: { + num: -1010, + accuracy: 100, + basePower: 12, + category: "Physical", + name: "Crowverload", + pp: 10, + priority: -4, + onTryMove() { + this.attrLastMove('[still]'); + }, + onPrepareHit(target, source) { + this.add('-anim', source, 'Glare', target); + this.add('-anim', source, 'X-Scissor', target); + }, + onTryHit(source, target, move) { + if (source.volatiles['substitute']) { + this.add('-fail', source, 'move: Crowverload'); + return this.NOT_FAIL; + } + }, + onAfterMove(source, target, move) { + this.actions.useMove('substitute', source, { }); + // source.addVolatile['substitute']; + // this.damage(Math.ceil(source.maxhp / 4)); + }, + flags: { protect: 1, mirror: 1, metronome: 1 }, + multihit: [10, 10], + secondary: null, + target: "normal", + type: "Dark", + zMove: { basePower: 140 }, + maxMove: { basePower: 130 }, + contestType: "Tough", + shortDesc: "Hits 10 times. User creates a Substitute.", + desc: "Hits 10 times. User creates a Substitute.", + }, + naturesfury: { + num: -1011, + accuracy: true, + basePower: 0, + category: "Status", + name: "Nature's Fury", + pp: 20, + priority: 0, + flags: { failencore: 1, nosleeptalk: 1, noassist: 1, failcopycat: 1, failmimic: 1, failinstruct: 1 }, + onModifyPriority(priority, source, target, move) { + if (this.field.isTerrain('electricterrain')) { + return priority + 1; + } else if (this.field.isTerrain('grassyterrain')) { + return priority + 1; + } else if (this.field.isTerrain('mistyterrain')) { + return priority + 1; + } else if (this.field.isTerrain('psychicterrain')) { + return priority; + } else { + return priority + 2; + } + }, + onTryMove() { + this.attrLastMove('[still]'); + }, + onTryHit(target, pokemon) { + let move = 'extremespeed'; + if (this.field.isTerrain('electricterrain')) { + move = 'lightningleap'; + } else if (this.field.isTerrain('grassyterrain')) { + move = 'grassyglide'; + } else if (this.field.isTerrain('mistyterrain')) { + move = 'mistymarch'; + } else if (this.field.isTerrain('psychicterrain')) { + move = 'wackywhack'; + } + this.actions.useMove(move, pokemon, target); + return null; + }, + callsMove: true, + secondary: null, + target: "normal", + type: "Normal", + contestType: "Beautiful", + shortDesc: "Move used depends on Terrain.", + desc: "Move used depends on Terrain.", + }, + mistymarch: { + num: -1012, + accuracy: 100, + basePower: 55, + category: "Physical", + name: "Misty March", + pp: 20, + priority: 0, + flags: { contact: 1, protect: 1, mirror: 1, metronome: 1 }, + onTryMove() { + this.attrLastMove('[still]'); + }, + onPrepareHit(target, source) { + this.add('-anim', source, 'Play Rough', target); + }, + onModifyPriority(priority, source, target, move) { + if (this.field.isTerrain('mistyterrain') && source.isGrounded()) { + return priority + 1; + } + }, + secondary: null, + target: "normal", + type: "Fairy", + contestType: "Cool", + shortDesc: "User on Misty Terrain: +1 priority.", + desc: "User on Misty Terrain: +1 priority.", + }, + lightningleap: { + num: -1013, + accuracy: 100, + basePower: 55, + category: "Physical", + name: "Lightning Leap", + pp: 20, + priority: 0, + flags: { contact: 1, protect: 1, mirror: 1, metronome: 1 }, + onTryMove() { + this.attrLastMove('[still]'); + }, + onPrepareHit(target, source) { + this.add('-anim', source, 'Volt Tackle', target); + }, + onModifyPriority(priority, source, target, move) { + if (this.field.isTerrain('electricterrain') && source.isGrounded()) { + return priority + 1; + } + }, + secondary: null, + target: "normal", + type: "Electric", + contestType: "Cool", + shortDesc: "User on Electric Terrain: +1 priority.", + desc: "User on Electric Terrain: +1 priority.", + }, + wackywhack: { + num: -1014, + accuracy: 100, + basePower: 80, + category: "Physical", + name: "Wacky Whack", + pp: 20, + priority: 0, + flags: { contact: 1, protect: 1, mirror: 1, metronome: 1 }, + onTryMove() { + this.attrLastMove('[still]'); + }, + onPrepareHit(target, source) { + this.add('-anim', source, 'Zed Headbutt', target); + this.add('-anim', source, 'Thief', target); + }, + onAfterMoveSecondarySelf(source, target, move) { + if (this.field.isTerrain('psychicterrain') && source.isGrounded()) { + this.boost({ spe: 1 }, source, source); + } + }, + secondary: null, + target: "normal", + type: "Psychic", + contestType: "Cool", + shortDesc: "User in Psychic Terrain: +1 Speed.", + desc: "User in Psychic Terrain: +1 Speed.", + }, + bonemerang: { + inherit: true, + onAfterMove(source) { + const item = source.lastItem || 'thickclub'; + source.lastItem = ''; + source.setItem(item); + this.add('-item', source, this.dex.items.get(item), '[from] move: Bonemerang'); + }, + shortDesc: "Returns last used item. Default Thick Club.", + desc: "Returns last used item. Defaults to Thick Club if none.", + }, + electricterrain: { + inherit: true, + condition: { + effectType: 'Terrain', + duration: 5, + durationCallback(source, effect) { + if (source?.hasItem('terrainextender')) { + return 8; + } + return 5; + }, + onSetStatus(status, target, source, effect) { + if (status.id === 'slp' && target.isGrounded() && !target.isSemiInvulnerable()) { + if (effect.id === 'yawn' || (effect.effectType === 'Move' && !effect.secondaries)) { + this.add('-activate', target, 'move: Electric Terrain'); + } + return false; + } + }, + onTryAddVolatile(status, target) { + if (!target.isGrounded() || target.isSemiInvulnerable()) return; + if (status.id === 'yawn') { + this.add('-activate', target, 'move: Electric Terrain'); + return null; + } + }, + onBasePowerPriority: 6, + onBasePower(basePower, attacker, defender, move) { + if (move.type === 'Electric' && attacker.isGrounded() && !attacker.isSemiInvulnerable()) { + this.debug('electric terrain boost'); + return this.chainModify([5325, 4096]); + } + }, + onFieldStart(field, source, effect) { + if (effect?.effectType === 'Ability') { + this.add('-fieldstart', 'move: Electric Terrain', '[from] ability: ' + effect.name, `[of] ${source}`); + } else { + this.add('-fieldstart', 'move: Electric Terrain'); + } + }, + onDisableMove(pokemon) { + for (const moveSlot of pokemon.moveSlots) { + if (this.dex.moves.get(moveSlot.id).flags['heal']) { + pokemon.disableMove(moveSlot.id); + } + } + }, + onBeforeMovePriority: 6, + onBeforeMove(pokemon, target, move) { + if (move.flags['heal'] && !move.isZ && !move.isMax) { + this.add('cant', pokemon, 'move: Electric Terrain', move); + return false; + } + }, + onModifyMove(move, pokemon, target) { + if (move.flags['heal'] && !move.isZ && !move.isMax) { + this.add('cant', pokemon, 'move: Electric Terrain', move); + return false; + } + }, + onFieldResidualOrder: 27, + onFieldResidualSubOrder: 7, + onFieldEnd() { + this.add('-fieldend', 'move: Electric Terrain'); + }, + }, + shortDesc: "5 turns. Grounded: +Electric power, can't sleep, can't heal.", + desc: "5 turns. Grounded: +Electric power, can't sleep, can't use healing moves.", + }, + mistyterrain: { + inherit: true, + condition: { + effectType: 'Terrain', + duration: 5, + durationCallback(source, effect) { + if (source?.hasItem('terrainextender')) { + return 8; + } + return 5; + }, + onSetStatus(status, target, source, effect) { + if (!target.isGrounded() || target.isSemiInvulnerable()) return; + if (effect && ((effect as Move).status || effect.id === 'yawn')) { + this.add('-activate', target, 'move: Misty Terrain'); + } + return false; + }, + onTryAddVolatile(status, target, source, effect) { + if (!target.isGrounded() || target.isSemiInvulnerable()) return; + if (status.id === 'confusion') { + if (effect.effectType === 'Move' && !effect.secondaries) this.add('-activate', target, 'move: Misty Terrain'); + return null; + } + }, + onBasePowerPriority: 6, + onBasePower(basePower, attacker, defender, move) { + if (move.type === 'Dragon' && defender.isGrounded() && !defender.isSemiInvulnerable()) { + this.debug('misty terrain weaken'); + return this.chainModify(0.5); + } + if (move.type === 'Fairy' && attacker.isGrounded() && !attacker.isSemiInvulnerable()) { + this.debug('misty terrain boost'); + return this.chainModify([5325, 4096]); + } + }, + onFieldStart(field, source, effect) { + if (effect?.effectType === 'Ability') { + this.add('-fieldstart', 'move: Misty Terrain', '[from] ability: ' + effect.name, `[of] ${source}`); + } else { + this.add('-fieldstart', 'move: Misty Terrain'); + } + }, + onFieldResidualOrder: 27, + onFieldResidualSubOrder: 7, + onFieldEnd() { + this.add('-fieldend', 'Misty Terrain'); + }, + }, + shortDesc: "5 turns. Can't status,-Dragon power vs grounded, +Fairy power.", + desc: "5 turns. Can't status,-Dragon power vs grounded, +Fairy power.", + }, + lootbox: { + num: -1015, + accuracy: 100, + basePower: 0, + category: "Physical", + name: "Loot Box", + pp: 15, + priority: 0, + flags: { protect: 1, mirror: 1, metronome: 1 }, + onModifyMove(move, pokemon, target) { + const rand = this.random(8); + if (rand < 2) { + move.basePower = 0; + } else if (rand < 4) { + move.basePower = 60; + } else if (rand < 6) { + move.basePower = 120; + } else { + move.basePower = 150; + } + }, + onTryMove() { + this.attrLastMove('[still]'); + }, + onPrepareHit(target, source) { + this.add('-anim', source, 'Explosion', target); + this.add('-anim', source, 'Mind Blown', target); + }, + secondary: null, + target: "normal", + type: "Normal", + contestType: "Cute", + shortDesc: "Present but better.", + }, + sinisterarrows: { + num: -1016, + accuracy: 100, + basePower: 50, + category: "Physical", + name: "Sinister Arrows", + pp: 10, + priority: 0, + flags: { allyanim: 1, metronome: 1, futuremove: 1 }, + ignoreImmunity: true, + onTry(source, target) { + if (!target.side.addSideCondition('sinisterarrows')) return false; + Object.assign(target.side.sideConditions['sinisterarrows'], { + move: 'sinisterarrows', + source, + moveData: { + id: 'sinisterarrows', + name: "Sinister Arrows", + accuracy: 100, + basePower: 50, + category: "Physical", + priority: 0, + flags: { allyanim: 1, metronome: 1, futuremove: 1 }, + ignoreImmunity: false, + effectType: 'Move', + type: 'Ghost', + }, + }); + this.add('-start', source, 'move: Sinister Arrows'); + return this.NOT_FAIL; + }, + onTryMove(target, source, move) { + this.add('-anim', source, 'Curse', target); + this.add('-anim', source, 'Spite', target); + }, + condition: { + duration: 4, + onStart(target) { + this.effectState.targetSlot = target.getSlot(); + this.effectState.endingTurn = (this.turn - 1) + 3; + }, + onResidualOrder: 5, + onResidualSubOrder: 2, + onResidual(target) { + const data = this.effectState; + // time's up; time to hit! :D + const move = this.dex.moves.get(data.move); + if (target.fainted || target === data.source) { + this.hint(`${move.name} did not hit because the target is ${(target.fainted ? 'fainted' : 'the user')}.`); + return; + } + if (!this.getOverflowedTurnCount()) return; + //target.removeVolatile('Protect'); + //target.removeVolatile('Endure'); + if (data.source.hasAbility('infiltrator') && this.gen >= 6) { + data.moveData.infiltrates = true; + } + if (data.source.hasAbility('normalize') && this.gen >= 6) { + data.moveData.type = 'Normal'; + } + const hitMove = new this.dex.Move(data.moveData) as ActiveMove; + this.actions.runMove(hitMove, data.source, this.effectState.targetSlot); + this.hint(`${move.name} hits.`); + if (data.source.isActive && data.source.hasItem('lifeorb') && this.gen >= 5) { + this.singleEvent('AfterMoveSecondarySelf', data.source.getItem(), data.source.itemState, data.source, target, data.source.getItem()); + } + this.activeMove = null; + this.checkWin(); + }, + }, + secondary: null, + target: "normal", + type: "Ghost", + contestType: "Clever", + shortDesc: "Hits for 4 turns, even if user switches out.", + }, + mortalspin: { + inherit: true, + category: "Special", + }, + lastbreakfast: { + num: -1020, + accuracy: 100, + basePower: 80, + category: "Physical", + name: "Last Breakfast", + pp: 15, + priority: 0, + flags: { protect: 1, mirror: 1, metronome: 1, contact: 1, bite: 1 }, + onHit(target, source, move) { + const numberBerries = 0 + 1 * Number(source.side.totalFainted); + for (let i = 0; i < numberBerries; i++) { + const possibleBerries = ['aguavberry', 'apicotberry', 'enigmaberry', 'figyberry', 'ganlonberry', 'iapapaberry', + 'keeberry', 'lansatberry', 'leppaberry', 'liechiberry', 'lumberry', 'magoberry', + 'marangaberry', 'micleberry', + 'oranberry', 'petayaberry', 'salacberry', 'sitrusberry', 'starfberry', 'wikiberry', + 'aspearberry', 'cheriberry', 'chestoberry', 'lumberry', 'pechaberry', 'rawstberry', 'persimberry']; + const chosenBerry = this.sample(possibleBerries); + const berry = this.dex.items.get(chosenBerry); + if (source.hp && berry.isBerry) { + if (this.singleEvent('Eat', berry, null, source, source, move)) { + this.runEvent('EatItem', source, source, move, berry); + } + if (berry.onEat) source.ateBerry = true; + } + } + }, + onTryMove() { + this.attrLastMove('[still]'); + }, + onPrepareHit(target, source) { + this.add('-anim', source, 'Curse', target); + this.add('-anim', source, 'Bug Bite', target); + }, + secondary: null, + target: "normal", + type: "Ghost", + contestType: "Cute", + shortDesc: "Eats X random berries, where X is fainted teammates.", + }, + superfang: { + inherit: true, + flags: { contact: 1, protect: 1, mirror: 1, metronome: 1, bite: 1 }, + }, }; - diff --git a/data/mods/chatbats/pokedex.ts b/data/mods/chatbats/pokedex.ts index 34a1d0757e..6d4f878feb 100644 --- a/data/mods/chatbats/pokedex.ts +++ b/data/mods/chatbats/pokedex.ts @@ -1,20 +1,18 @@ -import { inherits } from "util"; - -export const Pokedex: { [k: string]: ModdedSpeciesData } = { +export const Pokedex: import('../../../sim/dex-species').ModdedSpeciesDataTable = { volcarona: { inherit: true, - abilities: {0: "Fluffy"}, + abilities: { 0: "Fluffy" }, }, golemalola: { - inherit: true, + inherit: true, }, lurantis: { inherit: true, - baseStats: {hp: 85, atk: 105, def: 90, spa: 95, spd: 90, spe: 75}, + baseStats: { hp: 85, atk: 105, def: 90, spa: 95, spd: 90, spe: 75 }, }, ironcrown: { inherit: true, - abilities: {0: "Queenly Majesty", H: "Battle Armor"}, + abilities: { 0: "Queenly Majesty", H: "Battle Armor" }, }, mamoswine: { inherit: true, @@ -24,83 +22,83 @@ export const Pokedex: { [k: string]: ModdedSpeciesData } = { }, carbink: { inherit: true, - abilities: {0: "Magic Bounce"}, + abilities: { 0: "Magic Bounce" }, }, moltres: { inherit: true, - abilities: {0: "Magic Guard"}, + abilities: { 0: "Magic Guard" }, }, kommoo: { inherit: true, - baseStats: {hp: 75, atk: 100, def: 125, spa: 110, spd: 105, spe: 85}, - abilities: {0: "Punk Rock"}, + baseStats: { hp: 75, atk: 100, def: 125, spa: 110, spd: 105, spe: 85 }, + abilities: { 0: "Punk Rock" }, }, illumise: { inherit: true, - abilities: {0: "Call Volbeat"}, + abilities: { 0: "Call Volbeat" }, }, volbeat: { inherit: true, - abilities: {0: "Call Illumise"}, + abilities: { 0: "Call Illumise" }, }, abomasnow: { inherit: true, }, abomasnowmega: { inherit: true, - baseStats: {hp: 90, atk: 132, def: 105, spa: 92, spd: 105, spe: 70}, - abilities: {0: "Slush Rush"}, + baseStats: { hp: 90, atk: 132, def: 105, spa: 92, spd: 105, spe: 70 }, + abilities: { 0: "Slush Rush" }, }, dugtrio: { inherit: true, }, altaria: { inherit: true, - abilities: {0: "Fluffy"}, + abilities: { 0: "Fluffy" }, }, altariamega: { inherit: true, }, tyranitar: { inherit: true, - abilities: {0: "Sand Stream", H: "Sharpness"}, + abilities: { 0: "Sand Stream", H: "Sharpness" }, }, tyranitarmega: { inherit: true, - baseStats: {hp: 100, atk: 114, def: 150, spa: 155, spd: 110, spe: 71}, + baseStats: { hp: 100, atk: 114, def: 150, spa: 155, spd: 110, spe: 71 }, types: ["Rock", "Dragon"], }, mimikyu: { inherit: true, - baseStats: {hp: 65, atk: 110, def: 80, spa: 50, spd: 105, spe: 96}, + baseStats: { hp: 65, atk: 110, def: 80, spa: 50, spd: 105, spe: 96 }, }, mimikyubusted: { inherit: true, - abilities: {0: "Perish Body"}, - baseStats: {hp: 65, atk: 90, def: 80, spa: 50, spd: 105, spe: 116}, + abilities: { 0: "Perish Body" }, + baseStats: { hp: 65, atk: 90, def: 80, spa: 50, spd: 105, spe: 116 }, }, mesprit: { inherit: true, - abilities: {0: "Liquid Voice"}, + abilities: { 0: "Liquid Voice" }, types: ["Psychic", "Water"], }, electrode: { inherit: true, - abilities: {0: "Short Fuse"}, + abilities: { 0: "Short Fuse" }, types: ["Electric", "Normal"], }, taurospaldeacombat: { inherit: true, - abilities: {0: "Adaptability"}, + abilities: { 0: "Adaptability" }, }, chiyu: { inherit: true, - abilities: {0: "Water Absorb"}, - baseStats: {hp: 55, atk: 135, def: 80, spa: 80, spd: 120, spe: 100}, + abilities: { 0: "Water Absorb" }, + baseStats: { hp: 55, atk: 135, def: 80, spa: 80, spd: 120, spe: 100 }, }, wochien: { inherit: true, - abilities: {0: "Liquid Ooze"}, + abilities: { 0: "Liquid Ooze" }, types: ["Grass", "Water"], }, staraptor: { @@ -109,89 +107,90 @@ export const Pokedex: { [k: string]: ModdedSpeciesData } = { }, archaludon: { inherit: true, - abilities: {0: "Hydroelectric Dam", 1: "Stamina"}, + abilities: { 0: "Hydroelectric Dam", 1: "Stamina" }, }, malamar: { inherit: true, - abilities: {0: "Flip Flop"}, - baseStats: {hp: 86, atk: 92, def: 88, spa: 88, spd: 75, spe: 73}, + abilities: { 0: "Flip Flop" }, + baseStats: { hp: 86, atk: 92, def: 88, spa: 88, spd: 75, spe: 73 }, }, empoleon: { inherit: true, - abilities: {0: "Rough Skin"}, - baseStats: {hp: 84, atk: 111, def: 88, spa: 86, spd: 101, spe: 60}, + abilities: { 0: "Sharpness" }, + types: ["Water", "Steel", "Flying"], }, glastrier: { inherit: true, - abilities: {0: "Frozen Armor"}, + abilities: { 0: "Frozen Armor" }, }, calyrexice: { inherit: true, - baseStats: {hp: 100, atk: 165, def: 130, spa: 85, spd: 110, spe: 90}, + baseStats: { hp: 100, atk: 165, def: 130, spa: 85, spd: 110, spe: 90 }, }, regieleki: { inherit: true, - abilities: {0: "Galvanize"}, + abilities: { 0: "Galvanize" }, }, lycanrocmidnight: { inherit: true, - abilities: {0: "Technician"}, + abilities: { 0: "Technician" }, }, lycanroc: { inherit: true, - abilities: {0: "Drought"}, + abilities: { 0: "Drought" }, }, lycanrocdusk: { inherit: true, - abilities: {0: "Strong Jaw"}, + abilities: { 0: "Strong Jaw" }, }, dodrio: { inherit: true, - abilities: {0: "Speed Boost"}, + abilities: { 0: "Speed Boost" }, types: ["Flying", "Fighting"], }, whiscash: { inherit: true, - abilities: {0: "Regenerator"}, - baseStats: {hp: 110, atk: 78, def: 88, spa: 76, spd: 86, spe: 60}, + abilities: { 0: "Regenerator" }, + baseStats: { hp: 110, atk: 78, def: 88, spa: 76, spd: 86, spe: 60 }, }, hippowdon: { inherit: true, - abilities: {0: "Earth Eater"}, + abilities: { 0: "Earth Eater" }, }, cramorant: { inherit: true, - baseStats: {hp: 90, atk: 85, def: 75, spa: 85, spd: 95, spe: 85}, + baseStats: { hp: 90, atk: 85, def: 75, spa: 85, spd: 95, spe: 85 }, }, cramorantgulping: { inherit: true, - baseStats: {hp: 90, atk: 85, def: 75, spa: 85, spd: 95, spe: 85}, - abilities: {0: "Storm Drain"}, + baseStats: { hp: 90, atk: 85, def: 75, spa: 85, spd: 95, spe: 85 }, + abilities: { 0: "Storm Drain" }, }, cramorantgorging: { inherit: true, - baseStats: {hp: 90, atk: 85, def: 75, spa: 85, spd: 95, spe: 85}, - abilities: {0: "Lightning Rod"}, + baseStats: { hp: 90, atk: 85, def: 75, spa: 85, spd: 95, spe: 85 }, + abilities: { 0: "Lightning Rod" }, }, grafaiai: { inherit: true, - baseStats: {hp: 83, atk: 95, def: 65, spa: 80, spd: 72, spe: 110}, + baseStats: { hp: 83, atk: 95, def: 65, spa: 80, spd: 72, spe: 110 }, }, tatsugiri: { inherit: true, - abilities: {0: "Dry Skin"}, + abilities: { 0: "Regenerator" }, + baseStats: { hp: 78, atk: 50, def: 70, spa: 120, spd: 95, spe: 82 }, }, kyurem: { inherit: true, - abilities: {0: "Skill Link"}, + abilities: { 0: "Skill Link" }, }, roaringmoon: { inherit: true, - abilities: {0: "Shadow Shield"}, + abilities: { 0: "Shadow Shield" }, }, milotic: { inherit: true, - abilities: {0: "Aqua Veil"}, + abilities: { 0: "Aqua Veil" }, types: ["Water", "Fairy"], }, gogoat: { @@ -200,11 +199,11 @@ export const Pokedex: { [k: string]: ModdedSpeciesData } = { }, clodsire: { inherit: true, - abilities: {0: "Still Water"}, + abilities: { 0: "Still Water" }, }, masquerain: { inherit: true, - abilities: {0: "Intimidate"}, + abilities: { 0: "Intimidate" }, }, masquerainmega: { num: -999, @@ -223,48 +222,46 @@ export const Pokedex: { [k: string]: ModdedSpeciesData } = { }, kyuremblack: { inherit: true, - abilities: {0: "Teravolt"}, + abilities: { 0: "Teravolt" }, types: ["Dragon", "Ice", "Electric"], }, ironthorns: { inherit: true, - abilities: {0: "Iron Barbs"}, + abilities: { 0: "Iron Barbs" }, }, dudunsparce: { inherit: true, - abilities: {0: "Earth Eater"}, + abilities: { 0: "Earth Eater" }, types: ["Normal", "Ground"], }, dudunsparcethreesegment: { inherit: true, - abilities: {0: "Earth Eater"}, + abilities: { 0: "Earth Eater" }, types: ["Normal", "Ground"], }, chienpao: { inherit: true, - abilities: {0: "Tablets of Ruin"}, + abilities: { 0: "Tablets of Ruin" }, }, pelipper: { inherit: true, }, kleavor: { inherit: true, - abilities: {0: "King of the Hill"}, + abilities: { 0: "King of the Hill" }, baseStats: { hp: 120, atk: 135, def: 95, spa: 45, spd: 75, spe: 85 }, }, araquanid: { inherit: true, - baseStats: { hp: 2, atk: 140, def: 92, spa: 50, spd: 132, spe: 42 }, - maxHP: 16, }, avalugghisui: { inherit: true, - abilities: {0: "Multiscale"}, + abilities: { 0: "Multiscale" }, baseStats: { hp: 95, atk: 127, def: 184, spa: 68, spd: 72, spe: 76 }, }, swalot: { inherit: true, - abilities: {0: "Omnivore"}, + abilities: { 0: "Omnivore" }, }, zapdosgalar: { inherit: true, @@ -275,7 +272,7 @@ export const Pokedex: { [k: string]: ModdedSpeciesData } = { }, sudowoodo: { inherit: true, - abilities: {0: "Pseudowoodo"}, + abilities: { 0: "Pseudowoodo" }, types: ["Grass"], baseForme: "Grass", otherFormes: ["Sudowoodo-Rock"], @@ -307,11 +304,11 @@ export const Pokedex: { [k: string]: ModdedSpeciesData } = { }, infernape: { inherit: true, - abilities: {0: "Berserk"}, + abilities: { 0: "Berserk" }, }, salamence: { inherit: true, - abilities: {0: "Aerilate"}, + abilities: { 0: "Aerilate" }, }, salamencemega: { num: 373, @@ -329,11 +326,11 @@ export const Pokedex: { [k: string]: ModdedSpeciesData } = { }, urshifu: { inherit: true, - abilities: {0: "Sniper"}, + abilities: { 0: "Sniper" }, }, urshifurapidstrike: { inherit: true, - abilities: {0: "Sniper"}, + abilities: { 0: "Sniper" }, }, stonjourner: { inherit: true, @@ -341,11 +338,10 @@ export const Pokedex: { [k: string]: ModdedSpeciesData } = { veluza: { inherit: true, types: ["Water", "Ghost"], - baseStats: { hp: 90, atk: 102, def: 123, spa: 78, spd: 115, spe: 70 }, }, ogerponhearthflame: { inherit: true, - abilities: {0: "Intimidate"}, + abilities: { 0: "Intimidate" }, }, dachsbun: { inherit: true, @@ -355,6 +351,164 @@ export const Pokedex: { [k: string]: ModdedSpeciesData } = { }, mew: { inherit: true, - abilities: {0: "Biogenesis"}, - } + abilities: { 0: "Biogenesis" }, + }, + magneton: { + inherit: true, + }, + delibird: { + inherit: true, + abilities: { 0: "Hail Mary" }, + baseStats: { hp: 45, atk: 90, def: 45, spa: 65, spd: 45, spe: 136 }, + }, + articunogalar: { + inherit: true, + abilities: { 0: "Brain Freeze" }, + }, + vaporeon: { + inherit: true, + abilities: { 0: "Marvel Scale" }, + }, + jolteon: { + inherit: true, + abilities: { 0: "Quick Feet" }, + }, + flareon: { + inherit: true, + abilities: { 0: "Guts" }, + baseStats: { hp: 65, atk: 130, def: 65, spa: 60, spd: 110, spe: 95 }, + }, + garganacl: { + inherit: true, + }, + swanna: { + inherit: true, + abilities: { 0: "Serene Grace" }, + baseStats: { hp: 75, atk: 117, def: 93, spa: 117, spd: 93, spe: 128 }, + }, + typhlosion: { + inherit: true, + abilities: { 0: "Magic Guard" }, + }, + typhlosionmega: { + num: -998, + name: "Typhlosion-Mega", + baseSpecies: "Typhlosion", + forme: "Mega", + types: ["Fire", "Water"], + genderRatio: { M: 0.5, F: 0.5 }, + baseStats: { hp: 78, atk: 103, def: 98, spa: 140, spd: 115, spe: 100 }, + abilities: { 0: "Neutralizing Gas" }, + heightm: 1.7, + weightkg: 84.5, + color: "Blue", + eggGroups: ["Field"], + requiredItem: "Typhlosionite", + }, + terapagos: { + inherit: true, + }, + terapagosterastal: { + inherit: true, + abilities: { 0: "Tera Wheel" }, + }, + terapagosstellar: { + inherit: true, + types: ["Stellar"], + }, + flapple: { + inherit: true, + abilities: { 0: "Ripen" }, + types: ["Grass", "Ground"], + }, + genesect: { + inherit: true, + abilities: { 0: "Download" }, + }, + honchkrow: { + inherit: true, + abilities: { 0: "Supreme Overlord" }, + baseStats: { hp: 100, atk: 125, def: 52, spa: 125, spd: 52, spe: 71 }, + }, + primeape: { + inherit: true, + abilities: { 0: "Battle Rage" }, + }, + rillaboom: { + inherit: true, + abilities: { 0: "Terrain Shift" }, + }, + mandibuzz: { + inherit: true, + abilities: { 0: "Weak Armor" }, + types: ["Dark", "Ground"], + }, + feraligatr: { + inherit: true, + }, + feraligatrmega: { + num: -988, + name: "Feraligatr-Mega", + baseSpecies: "Feraligatr", + forme: "Mega", + types: ["Dragon"], + genderRatio: { M: 0.875, F: 0.125 }, + baseStats: { hp: 85, atk: 145, def: 120, spa: 99, spd: 103, spe: 78 }, + abilities: { 0: "Dragon's Jaw" }, + heightm: 2.3, + weightkg: 108.8, + color: "Blue", + eggGroups: ["Monster", "Water 1"], + requiredItem: "Feraligite", + gen: 9, + }, + salazzle: { + inherit: true, + abilities: { 0: "Corrosive Soul" }, + }, + kyogre: { + inherit: true, + abilities: { 0: "Oceanic Blessing" }, + }, + azelf: { + inherit: true, + abilities: { 0: "Auto Spin" }, + types: ["Psychic", "Normal"], + }, + decidueye: { + inherit: true, + abilities: { 0: "Overgrow", 1: "Sniper" }, + }, + ogerponcornerstone: { + inherit: true, + abilities: { 0: "Solid Rock" }, + }, + glimmora: { + inherit: true, + abilities: { 0: "Corrosion" }, + }, + wobbuffet: { + inherit: true, + abilities: { 0: "Jello Body" }, + }, + raticate: { + inherit: true, + abilities: { 0: "Hustle" }, + baseStats: { hp: 90, atk: 81, def: 60, spa: 50, spd: 70, spe: 97 }, + }, + raticatemega: { + num: -977, + name: "Raticate-Mega", + baseSpecies: "Raticate", + forme: "Mega", + types: ["Normal", "Ghost"], + genderRatio: { M: 0.5, F: 0.5 }, + baseStats: { hp: 90, atk: 105, def: 60, spa: 50, spd: 70, spe: 173 }, + abilities: { 0: "Nibble Nibble" }, + heightm: 0.7, + weightkg: 5, + color: "Black", + eggGroups: ["Field"], + requiredItem: "Raticite", + }, }; diff --git a/data/mods/chatbats/scripts.ts b/data/mods/chatbats/scripts.ts index ea4243f647..207d563386 100644 --- a/data/mods/chatbats/scripts.ts +++ b/data/mods/chatbats/scripts.ts @@ -1,9 +1,5 @@ export const Scripts: ModdedBattleScriptsData = { gen: 9, - teambuilderConfig: { - excludeStandardTiers: true, - customTiers: ["ChatBats"], - }, init() { this.modData('Learnsets', 'lurantis').learnset.icehammer = ['9L1']; @@ -21,12 +17,12 @@ export const Scripts: ModdedBattleScriptsData = { this.modData('Learnsets', 'kommoo').learnset.aurasphere = ['9L1']; - this.modData('Learnsets', 'illumise').learnset.calmmind = ['9L1']; + this.modData('Learnsets', 'illumise').learnset.quiverdance = ['9L1']; this.modData('Learnsets', 'illumise').learnset.thunderbolt = ['9L1']; this.modData('Learnsets', 'illumise').learnset.icebeam = ['9L1']; - this.modData('Learnsets', 'volbeat').learnset.dragondance = ['9L1']; - this.modData('Learnsets', 'volbeat').learnset.dragonhammer = ['9L1']; + this.modData('Learnsets', 'volbeat').learnset.victorydance = ['9L1']; + this.modData('Learnsets', 'volbeat').learnset.mightycleave = ['9L1']; this.modData('Learnsets', 'volbeat').learnset.earthquake = ['9L1']; this.modData('Learnsets', 'abomasnow').learnset.glaciallance = ['9L1']; @@ -38,7 +34,6 @@ export const Scripts: ModdedBattleScriptsData = { this.modData('Learnsets', 'dugtrio').learnset.acrobatics = ['9L1']; this.modData('Learnsets', 'altaria').learnset.beakblast = ['9L1']; - this.modData('Learnsets', 'altaria').learnset.healbell = ['9L1']; this.modData('Learnsets', 'altaria').learnset.return = ['9L1']; this.modData('Learnsets', 'altaria').learnset.explosion = ['9L1']; @@ -62,7 +57,7 @@ export const Scripts: ModdedBattleScriptsData = { this.modData('Learnsets', 'mesprit').learnset.cosmicpower = ['9L1']; this.modData('Learnsets', 'mesprit').learnset.aquaring = ['9L1']; this.modData('Learnsets', 'mesprit').learnset.freezedry = ['9L1']; - + this.modData('Learnsets', 'electrode').learnset.encore = ['9L1']; this.modData('Learnsets', 'electrode').learnset.rapidspin = ['9L1']; @@ -94,7 +89,10 @@ export const Scripts: ModdedBattleScriptsData = { this.modData('Learnsets', 'malamar').learnset.sweetkiss = ['9L1']; this.modData('Learnsets', 'malamar').learnset.spiritbreak = ['9L1']; - this.modData('Learnsets', 'empoleon').learnset.aquacutter = ['9L1']; + this.modData('Learnsets', 'empoleon').learnset.nastyplot = ['9L1']; + this.modData('Learnsets', 'empoleon').learnset.watershuriken = ['9L1']; + this.modData('Learnsets', 'empoleon').learnset.tachyoncutter = ['9L1']; + this.modData('Learnsets', 'empoleon').learnset.secretsword = ['9L1']; this.modData('Learnsets', 'regieleki').learnset.blazingtorque = ['9L1']; this.modData('Learnsets', 'regieleki').learnset.soak = ['9L1']; @@ -111,8 +109,6 @@ export const Scripts: ModdedBattleScriptsData = { this.modData('Learnsets', 'lycanrocdusk').learnset.icefang = ['9L1']; this.modData('Learnsets', 'dodrio').learnset.triplearrows = ['9L1']; - this.modData('Learnsets', 'dodrio').learnset.tripledive = ['9L1']; - this.modData('Learnsets', 'dodrio').learnset.hyperdrill = ['9L1']; this.modData('Learnsets', 'dodrio').learnset.obstruct = ['9L1']; this.modData('Learnsets', 'whiscash').learnset.toxic = ['9L1']; @@ -133,7 +129,6 @@ export const Scripts: ModdedBattleScriptsData = { this.modData('Learnsets', 'roaringmoon').learnset.glaiverush = ['9L1']; this.modData('Learnsets', 'milotic').learnset.bouncybubble = ['9L1']; - this.modData('Learnsets', 'milotic').learnset.toxic = ['9L1']; this.modData('Learnsets', 'milotic').learnset.moonblast = ['9L1']; this.modData('Learnsets', 'gogoat').learnset.headsmash = ['9L1']; @@ -161,11 +156,11 @@ export const Scripts: ModdedBattleScriptsData = { this.modData('Learnsets', 'araquanid').learnset.surgingstrikes = ['9L1']; this.modData('Learnsets', 'araquanid').learnset.flipturn = ['9L1']; - this.modData('Learnsets', 'araquanid').learnset.jetpunch = ['9L1']; + this.modData('Learnsets', 'araquanid').learnset.silktrap = ['9L1']; this.modData('Learnsets', 'araquanid').learnset.firstimpression = ['9L1']; this.modData('Learnsets', 'avalugghisui').learnset.mountainmaw = ['9L1']; - + this.modData('Learnsets', 'swalot').learnset.earthpower = ['9L1']; this.modData('Learnsets', 'zapdosgalar').learnset.wildcharge = ['9L1']; @@ -187,7 +182,7 @@ export const Scripts: ModdedBattleScriptsData = { this.modData('Learnsets', 'golurk').learnset.headlongrush = ['9L1']; this.modData('Learnsets', 'meowscarada').learnset.encore = ['9L1']; - this.modData('Learnsets', 'meowscarada').learnset.spectralthief = ['9L1']; + this.modData('Learnsets', 'meowscarada').learnset.spectralthief = ['9L1']; this.modData('Learnsets', 'infernape').learnset.alloutassault = ['9L1']; this.modData('Learnsets', 'infernape').learnset.mindblown = ['9L1']; @@ -206,11 +201,9 @@ export const Scripts: ModdedBattleScriptsData = { this.modData('Learnsets', 'stonjourner').learnset.rockwrecker = ['9L1']; this.modData('Learnsets', 'stonjourner').learnset.meteorassault = ['9L1']; this.modData('Learnsets', 'stonjourner').learnset.skyattack = ['9L1']; - this.modData('Learnsets', 'stonjourner').learnset.victorydance = ['9L1']; this.modData('Learnsets', 'stonjourner').learnset.solarblade = ['9L1']; this.modData('Learnsets', 'veluza').learnset.ragefist = ['9L1']; - this.modData('Learnsets', 'veluza').learnset.magicaltorque = ['9L1']; this.modData('Learnsets', 'ogerpon').learnset.leafblade = ['9L1']; this.modData('Learnsets', 'ogerpon').learnset.crabhammer = ['9L1']; @@ -219,5 +212,93 @@ export const Scripts: ModdedBattleScriptsData = { this.modData('Learnsets', 'dachsbun').learnset.nuzzle = ['9L1']; this.modData('Learnsets', 'dachsbun').learnset.spiritbreak = ['9L1']; this.modData('Learnsets', 'dachsbun').learnset.morningsun = ['9L1']; + + this.modData('Learnsets', 'magneton').learnset.magnetbomb = ['9L1']; + + this.modData('Learnsets', 'delibird').learnset.iciclestorm = ['9L1']; + + this.modData('Learnsets', 'hitmontop').learnset.bulletseed = ['9L1']; + + this.modData('Learnsets', 'articunogalar').learnset.aeroblast = ['9L1']; + this.modData('Learnsets', 'articunogalar').learnset.oblivionwing = ['9L1']; + this.modData('Learnsets', 'articunogalar').learnset.aurasphere = ['9L1']; + + this.modData('Learnsets', 'vaporeon').learnset.voltswitch = ['9L1']; + this.modData('Learnsets', 'vaporeon').learnset.burnout = ['9L1']; + + this.modData('Learnsets', 'garganacl').learnset.thunderwave = ['9L1']; + this.modData('Learnsets', 'garganacl').learnset.saltcurse = ['9L1']; + this.modData('Learnsets', 'garganacl').learnset.purify = ['9L1']; + + this.modData('Learnsets', 'swanna').learnset.bleakwindstorm = ['9L1']; + this.modData('Learnsets', 'swanna').learnset.steameruption = ['9L1']; + this.modData('Learnsets', 'swanna').learnset.flyby = ['9L1']; + + this.modData('Learnsets', 'typhlosion').learnset.heatsink = ['9L1']; + this.modData('Learnsets', 'typhlosion').learnset.steameruption = ['9L1']; + this.modData('Learnsets', 'typhlosion').learnset.matchagotcha = ['9L1']; + this.modData('Learnsets', 'typhlosion').learnset.calmmind = ['9L1']; + this.modData('Learnsets', 'typhlosion').learnset.morningsun = ['9L1']; + + this.modData('Learnsets', 'terapagos').learnset.nastyplot = ['9L1']; + + this.modData('Learnsets', 'tatsugiri').learnset.switcheroo = ['9L1']; + this.modData('Learnsets', 'tatsugiri').learnset.sashimishuffle = ['9L1']; + this.modData('Learnsets', 'tatsugiri').learnset.twister = ['9L1']; + this.modData('Learnsets', 'tatsugiri').learnset.waterspout = ['9L1']; + + this.modData('Learnsets', 'flapple').learnset.earthquake = ['9L1']; + this.modData('Learnsets', 'flapple').learnset.grabapple = ['9L1']; + this.modData('Learnsets', 'flapple').learnset.flareblitz = ['9L1']; + + this.modData('Learnsets', 'genesect').learnset.earthquake = ['9L1']; + this.modData('Learnsets', 'genesect').learnset.sunsteelstrike = ['9L1']; + this.modData('Learnsets', 'genesect').learnset.behemothblade = ['9L1']; + this.modData('Learnsets', 'genesect').learnset.makeitrain = ['9L1']; + this.modData('Learnsets', 'genesect').learnset.tachyoncutter = ['9L1']; + + this.modData('Learnsets', 'honchkrow').learnset.crowverload = ['9L1']; + this.modData('Learnsets', 'honchkrow').learnset.oblivionwing = ['9L1']; + this.modData('Learnsets', 'honchkrow').learnset.closecombat = ['9L1']; + + this.modData('Learnsets', 'primeape').learnset.knockoff = ['9L1']; + this.modData('Learnsets', 'primeape').learnset.ironhead = ['9L1']; + + this.modData('Learnsets', 'rillaboom').learnset.naturesfury = ['9L1']; + this.modData('Learnsets', 'rillaboom').learnset.landswrath = ['9L1']; + + this.modData('Learnsets', 'mandibuzz').learnset.fling = ['9L1']; + this.modData('Learnsets', 'mandibuzz').learnset.scavenge = ['9L1']; + this.modData('Learnsets', 'mandibuzz').learnset.bonemerang = ['9L1']; + + this.modData('Learnsets', 'feraligatr').learnset.firefang = ['9L1']; + this.modData('Learnsets', 'feraligatr').learnset.thunderfang = ['9L1']; + this.modData('Learnsets', 'feraligatr').learnset.poisonfang = ['9L1']; + + this.modData('Learnsets', 'salazzle').learnset.magmastorm = ['9L1']; + this.modData('Learnsets', 'salazzle').learnset.malignantchain = ['9L1']; + this.modData('Learnsets', 'salazzle').learnset.psychicnoise = ['9L1']; + this.modData('Learnsets', 'salazzle').learnset.banefulbunker = ['9L1']; + + this.modData('Learnsets', 'kyogre').learnset.hurricane = ['9L1']; + this.modData('Learnsets', 'kyogre').learnset.tidalsurge = ['9L1']; + + this.modData('Learnsets', 'azelf').learnset.rapidspin = ['9L1']; + this.modData('Learnsets', 'azelf').learnset.lootbox = ['9L1']; + this.modData('Learnsets', 'azelf').learnset.acupressure = ['9L1']; + + this.modData('Learnsets', 'decidueye').learnset.sinisterarrows = ['9L1']; + + this.modData('Learnsets', 'ogerpon').learnset.sappyseed = ['9L1']; + this.modData('Learnsets', 'ogerpon').learnset.thousandwaves = ['9L1']; + + this.modData('Learnsets', 'glimmora').learnset.icebeam = ['9L1']; + this.modData('Learnsets', 'glimmora').learnset.malignantchain = ['9L1']; + + this.modData('Learnsets', 'wobbuffet').learnset.nightshade = ['9L1']; + this.modData('Learnsets', 'wobbuffet').learnset.guillotine = ['9L1']; + this.modData('Learnsets', 'wobbuffet').learnset.shedtail = ['9L1']; + + this.modData('Learnsets', 'raticate').learnset.lastbreakfast = ['9L1']; }, }; diff --git a/data/random-battles/chatbats/random-sets.json b/data/random-battles/chatbats/random-sets.json index 65ccf6cac3..615908c2c4 100644 --- a/data/random-battles/chatbats/random-sets.json +++ b/data/random-battles/chatbats/random-sets.json @@ -1,6 +1,6 @@ { "volcarona": { - "level": 80, + "level": 84, "sets": [ { "role": "Bulky Support", @@ -11,7 +11,7 @@ ] }, "golemalola": { - "level": 92, + "level": 94, "sets": [ { "role": "Fast Attacker", @@ -33,7 +33,7 @@ ] }, "ironcrown": { - "level": 80, + "level": 78, "sets": [ { "role": "Bulky Attacker", @@ -72,7 +72,7 @@ ] }, "carbink": { - "level": 90, + "level": 84, "sets": [ { "role": "Bulky Setup", @@ -89,18 +89,18 @@ ] }, "moltres": { - "level": 82, + "level": 78, "sets": [ { "role": "Bulky Support", "movepool": ["Brave Bird", "Flare Blitz", "Wood Hammer", "Wave Crash", "Defog", "U-Turn"], "abilities": ["Magic Guard"], - "teraTypes": ["Ground"] + "teraTypes": ["Grass"] } ] }, "kommoo": { - "level": 70, + "level": 72, "sets": [ { "role": "Setup Sweeper", @@ -111,29 +111,29 @@ ] }, "illumise": { - "level": 94, + "level": 90, "sets": [ { "role": "Bulky Setup", - "movepool": ["Bug Buzz", "Calm Mind", "Ice Beam", "Thunderbolt"], + "movepool": ["Bug Buzz", "Quiver Dance", "Ice Beam", "Thunderbolt"], "abilities": ["Call Volbeat"], "teraTypes": ["Electric"] } ] }, "volbeat": { - "level": 88, + "level": 90, "sets": [ { "role": "Setup Sweeper", - "movepool": ["Dragon Dance", "Lunge", "Dragon Hammer", "Earthquake"], + "movepool": ["Victory Dance", "Lunge", "Mighty Cleave", "Earthquake"], "abilities": ["Call Illumise"], - "teraTypes": ["Dragon"] + "teraTypes": ["Rock"] } ] }, "abomasnow": { - "level": 82, + "level": 86, "sets": [ { "role": "Bulky Support", @@ -165,20 +165,9 @@ }, { "role": "Fast Attacker", - "movepool": ["Sucker Punch", "Mighty Cleave", "Earthquake", "Tera Blast", "Stealth Rock"], + "movepool": ["Sucker Punch", "Mighty Cleave", "Earthquake", "Tera Blast"], "abilities": ["Arena Trap"], - "teraTypes": ["Flying", "Ground"] - } - ] - }, - "altaria": { - "level": 86, - "sets": [ - { - "role": "Bulky Support", - "movepool": ["Beak Blast", "Roost", "Heal Bell", "Defog", "Earthquake"], - "abilities": ["Fluffy"], - "teraTypes": ["Steel"] + "teraTypes": ["Flying"] } ] }, @@ -187,14 +176,20 @@ "sets": [ { "role": "Setup Sweeper", - "movepool": ["Beak Blast", "Roost", "Return", "Dragon Dance", "Earthquake", "Draco Meteor", "Explosion"], + "movepool": ["Return", "Dragon Dance", "Earthquake", "Roost"], + "abilities": ["Fluffy"], + "teraTypes": ["Normal"] + }, + { + "role": "Bulky Attacker", + "movepool": ["Beak Blast", "Roost", "Return", "Draco Meteor", "Explosion"], "abilities": ["Fluffy"], "teraTypes": ["Normal"] } ] }, "tyranitar": { - "level": 84, + "level": 86, "sets": [ { "role": "Fast Attacker", @@ -216,7 +211,7 @@ ] }, "mimikyu": { - "level": 88, + "level": 86, "sets": [ { "role": "Fast Support", @@ -227,11 +222,11 @@ ] }, "mesprit": { - "level": 80, + "level": 82, "sets": [ { "role": "Setup Sweeper", - "movepool": ["Agility", "Psychic Noise", "Psychic", "Stored Power", "Freeze-Dry", "Torch Song"], + "movepool": ["Agility", "Psychic", "Stored Power", "Freeze-Dry", "Torch Song"], "abilities": ["Liquid Voice"], "teraTypes": ["Poison", "Water"] }, @@ -255,7 +250,7 @@ ] }, "taurospaldeacombat": { - "level": 80, + "level": 78, "sets": [ { "role": "Wallbreaker", @@ -292,14 +287,14 @@ "sets": [ { "role": "Fast Attacker", - "movepool": ["Brave Bird", "Jump Kick", "U-turn", "Head Smash", "Wave Crash", "Flare Blitz", "Tailwind"], + "movepool": ["Brave Bird", "Jump Kick", "U-turn", "Head Smash", "Wave Crash", "Flare Blitz"], "abilities": ["Reckless"], "teraTypes": ["Fighting"] } ] }, "archaludon": { - "level": 80, + "level": 78, "sets": [ { "role": "AV Pivot", @@ -316,7 +311,7 @@ ] }, "malamar": { - "level": 92, + "level": 94, "sets": [ { "role": "Bulky Support", @@ -327,18 +322,24 @@ ] }, "empoleon": { - "level": 84, + "level": 82, "sets": [ { - "role": "Bulky Setup", - "movepool": ["Roost", "Aqua Cutter", "Steel Wing", "Swords Dance"], - "abilities": ["Rough Skin"], - "teraTypes": ["Flying"] + "role": "Setup Sweeper", + "movepool": ["Nasty Plot", "Tachyon Cutter", "Air Slash", "Secret Sword", "Roost"], + "abilities": ["Sharpness"], + "teraTypes": ["Steel", "Ground"] + }, + { + "role": "Setup Sweeper", + "movepool": ["Nasty Plot", "Tachyon Cutter", "Air Slash", "Water Shuriken"], + "abilities": ["Sharpness"], + "teraTypes": ["Steel", "Ground"] } ] }, "glastrier": { - "level": 84, + "level": 82, "sets": [ { "role": "Bulky Setup", @@ -359,14 +360,14 @@ "sets": [ { "role": "Fast Attacker", - "movepool": ["Explosion", "Extreme Speed", "Blazing Torque", "Volt Switch", "Rapid Spin", "Soak"], + "movepool": ["Explosion", "Extreme Speed", "Volt Switch", "Rapid Spin", "Soak"], "abilities": ["Galvanize"], "teraTypes": ["Electric"] } ] }, "lycanrocmidnight": { - "level": 90, + "level": 88, "sets": [ { "role": "Setup Sweeper", @@ -377,7 +378,7 @@ ] }, "lycanroc": { - "level": 88, + "level": 92, "sets": [ { "role": "Fast Attacker", @@ -388,7 +389,7 @@ ] }, "lycanrocdusk": { - "level": 82, + "level": 86, "sets": [ { "role": "Fast Attacker", @@ -403,7 +404,13 @@ "sets": [ { "role": "Wallbreaker", - "movepool": ["Triple Arrows", "Triple Dive", "Hyper Drill", "Obstruct"], + "movepool": ["Triple Arrows", "Brave Bird", "Knock Off", "Obstruct"], + "abilities": ["Speed Boost"], + "teraTypes": ["Fighting"] + }, + { + "role": "Wallbreaker", + "movepool": ["Triple Arrows", "Drill Peck", "Knock Off", "Obstruct"], "abilities": ["Speed Boost"], "teraTypes": ["Fighting"] } @@ -421,7 +428,7 @@ ] }, "hippowdon": { - "level": 84, + "level": 86, "sets": [ { "role": "Bulky Support", @@ -454,18 +461,18 @@ ] }, "tatsugiri": { - "level": 86, + "level": 84, "sets": [ { - "role": "Fast Attacker", - "movepool": ["Hydro Pump", "Draco Meteor", "Rain Dance", "Nasty Plot", "Rapid Spin"], - "abilities": ["Dry Skin"], + "role": "Fast Support", + "movepool": ["Switcheroo", "Sashimi Shuffle", "Twister", "Water Spout"], + "abilities": ["Regenerator"], "teraTypes": ["Water"] } ] }, "kyurem": { - "level": 80, + "level": 78, "sets": [ { "role": "Setup Sweeper", @@ -476,7 +483,7 @@ ] }, "roaringmoon": { - "level": 78, + "level": 80, "sets": [ { "role": "Fast Attacker", @@ -487,18 +494,18 @@ ] }, "milotic": { - "level": 82, + "level": 80, "sets": [ { "role": "Bulky Support", - "movepool": ["Bouncy Bubble", "Ice Beam", "Recover", "Haze", "Toxic", "Dragon Tail", "Substitute", "Moonblast"], + "movepool": ["Bouncy Bubble", "Recover", "Haze", "Moonblast"], "abilities": ["Aqua Veil"], "teraTypes": ["Grass"] } ] }, "gogoat": { - "level": 86, + "level": 90, "sets": [ { "role": "Bulky Attacker", @@ -542,7 +549,7 @@ ] }, "kyuremblack": { - "level": 76, + "level": 80, "sets": [ { "role": "Wallbreaker", @@ -553,7 +560,7 @@ ] }, "ironthorns": { - "level": 88, + "level": 92, "sets": [ { "role": "Bulky Support", @@ -564,7 +571,7 @@ ] }, "dudunsparce": { - "level": 84, + "level": 86, "sets": [ { "role": "Bulky Support", @@ -575,11 +582,11 @@ ] }, "chienpao": { - "level": 78, + "level": 82, "sets": [ { "role": "Bulky Setup", - "movepool": ["Bulk Up", "Icicle Storm", "Swords Dance", "Sacred Sword", "Throat Chop"], + "movepool": ["Icicle Storm", "Swords Dance", "Sacred Sword", "Throat Chop"], "abilities": ["Tablets of Ruin"], "teraTypes": ["Fighting"] } @@ -590,7 +597,7 @@ "sets": [ { "role": "Fast Attacker", - "movepool": ["Weather Ball", "Bleakwind Storm", "Sandsear Storm", "Wildbolt Storm", "Springtide Storm"], + "movepool": ["Weather Ball", "Bleakwind Storm", "Sandsear Storm", "Wildbolt Storm", "Springtide Storm", "U-turn"], "abilities": ["Drizzle"], "teraTypes": ["Ground", "Electric", "Fairy"] }, @@ -603,7 +610,7 @@ ] }, "kleavor": { - "level": 82, + "level": 80, "sets": [ { "role": "AV Pivot", @@ -620,18 +627,18 @@ ] }, "araquanid": { - "level": 86, + "level": 88, "sets": [ { - "role": "Fast Attacker", - "movepool": ["Jet Punch", "Surging Strikes", "Flip Turn", "First Impression", "Sticky Web"], + "role": "Bulky Support", + "movepool": ["Silk Trap", "Surging Strikes", "Flip Turn", "First Impression"], "abilities": ["Water Bubble"], "teraTypes": ["Water"] } ] }, "avalugghisui": { - "level": 84, + "level": 82, "sets": [ { "role": "Bulky Support", @@ -653,7 +660,7 @@ ] }, "zapdosgalar": { - "level": 84, + "level": 82, "sets": [ { "role": "Fast Attacker", @@ -664,7 +671,7 @@ ] }, "phione": { - "level": 88, + "level": 90, "sets": [ { "role": "Bulky Setup", @@ -675,14 +682,8 @@ ] }, "sudowoodo": { - "level": 88, + "level": 90, "sets": [ - { - "role": "Bulky Attacker", - "movepool": ["Bonsai Bounce", "Synthesis", "Curse", "Stealth Rock", "Earthquake", "Wood Hammer", "Head Smash"], - "abilities": ["Pseudowoodo"], - "teraTypes": ["Rock", "Grass"] - }, { "role": "Wallbreaker", "movepool": ["Bonsai Bounce", "Earthquake", "Wood Hammer", "Head Smash"], @@ -692,7 +693,7 @@ ] }, "dondozo": { - "level": 84, + "level": 86, "sets": [ { "role": "Bulky Setup", @@ -709,7 +710,7 @@ ] }, "golurk": { - "level": 86, + "level": 88, "sets": [ { "role": "Bulky Setup", @@ -720,7 +721,7 @@ ] }, "meowscarada": { - "level": 84, + "level": 80, "sets": [ { "role": "Fast Support", @@ -753,7 +754,7 @@ ] }, "salamencemega": { - "level": 78, + "level": 80, "sets": [ { "role": "Fast Attacker", @@ -764,7 +765,7 @@ ] }, "urshifu": { - "level": 84, + "level": 80, "sets": [ { "role": "AV Pivot", @@ -781,7 +782,7 @@ ] }, "urshifurapidstrike": { - "level": 84, + "level": 80, "sets": [ { "role": "AV Pivot", @@ -802,7 +803,7 @@ "sets": [ { "role": "Setup Sweeper", - "movepool": ["Rock Wrecker", "Giga Impact", "Meteor Assault", "Sky Attack", "Solar Blade", "Victory Dance"], + "movepool": ["Rock Wrecker", "Giga Impact", "Meteor Assault", "Sky Attack", "Solar Blade"], "abilities": ["Power Spot"], "teraTypes": ["Rock", "Grass", "Fighting"] } @@ -813,14 +814,14 @@ "sets": [ { "role": "Setup Sweeper", - "movepool": ["Waterfall", "Rage Fist", "Fillet Away", "Stored Power", "Magical Torque", "Hydro Pump"], + "movepool": ["Waterfall", "Rage Fist", "Fillet Away", "Stored Power"], "abilities": ["Mold Breaker"], - "teraTypes": ["Water", "Ghost", "Fairy", "Psychic", "Dark"] + "teraTypes": ["Water", "Ghost", "Psychic", "Dark"] } ] }, "ogerponhearthflame": { - "level": 84, + "level": 80, "sets": [ { "role": "Wallbreaker", @@ -831,7 +832,7 @@ ] }, "dachsbun": { - "level": 86, + "level": 84, "sets": [ { "role": "Bulky Support", @@ -851,5 +852,337 @@ "teraTypes": ["Stellar"] } ] + }, + "magneton": { + "level": 90, + "sets": [ + { + "role": "Bulky Attacker", + "movepool": ["Magnet Rise", "Thunderbolt", "Magnet Bomb", "Volt Switch", "Thunder Wave"], + "abilities": ["Magnet Pull"], + "teraTypes": ["Ghost", "Water"] + } + ] + }, + "delibird": { + "level": 84, + "sets": [ + { + "role": "Fast Attacker", + "movepool": ["Icicle Storm", "Brave Bird", "Aerial Ace", "Drill Run"], + "abilities": ["Hail Mary"], + "teraTypes": ["Ice", "Ground"] + } + ] + }, + "hitmontop": { + "level": 88, + "sets": [ + { + "role": "Bulky Attacker", + "movepool": ["Triple Axel", "Triple Kick", "Bulk Up", "Rapid Spin", "Bullet Seed"], + "abilities": ["Technician"], + "teraTypes": ["Ice"] + } + ] + }, + "articunogalar": { + "level": 84, + "sets": [ + { + "role": "Setup Sweeper", + "movepool": ["Freezing Glare", "Aeroblast", "Roost", "Calm Mind"], + "abilities": ["Brain Freeze"], + "teraTypes": ["Steel"] + }, + { + "role": "Fast Attacker", + "movepool": ["Freezing Glare", "Oblivion Wing", "Aura Sphere", "U-turn"], + "abilities": ["Brain Freeze"], + "teraTypes": ["Fighting", "Psychic"] + } + ] + }, + "vaporeon": { + "level": 88, + "sets": [ + { + "role": "Fast Support", + "movepool": ["Volt Switch", "Burn Out", "Recover", "Scald"], + "abilities": ["Marvel Scale"], + "teraTypes": ["Stellar"] + } + ] + }, + "garganacl": { + "level": 84, + "sets": [ + { + "role": "Bulky Support", + "movepool": ["Thunder Wave", "Salt Curse", "Purify", "Earthquake"], + "abilities": ["Purifying Salt"], + "teraTypes": ["Ghost"] + } + ] + }, + "swanna": { + "level": 82, + "sets": [ + { + "role": "Fast Attacker", + "movepool": ["Steam Eruption", "Bleakwind Storm", "Roost", "Defog", "Fly-by"], + "abilities": ["Serene Grace"], + "teraTypes": ["Ground"] + } + ] + }, + "typhlosionmega": { + "level": 82, + "sets": [ + { + "role": "Fast Attacker", + "movepool": ["Eruption", "Steam Eruption", "Matcha Gotcha", "Morning Sun"], + "abilities": ["Magic Guard"], + "teraTypes": ["Normal"] + }, + { + "role": "Bulky Setup", + "movepool": ["Heat Sink", "Steam Eruption", "Calm Mind", "Morning Sun"], + "abilities": ["Magic Guard"], + "teraTypes": ["Normal"] + } + ] + }, + "terapagos": { + "level": 82, + "sets": [ + { + "role": "Bulky Setup", + "movepool": ["Rest", "Sleep Talk", "Calm Mind", "Tera Starstorm"], + "abilities": ["Tera Shift"], + "teraTypes": ["Normal"] + }, + { + "role": "Setup Sweeper", + "movepool": ["Rapid Spin", "Nasty Plot", "Protect", "Tera Starstorm"], + "abilities": ["Tera Shift"], + "teraTypes": ["Normal"] + } + ] + }, + "flapple": { + "level": 84, + "sets": [ + { + "role": "Setup Sweeper", + "movepool": ["Dragon Dance", "Earthquake", "Grab Apple", "Flare Blitz"], + "abilities": ["Ripen"], + "teraTypes": ["Fire"] + } + ] + }, + "genesectburn": { + "level": 82, + "sets": [ + { + "role": "Wallbreaker", + "movepool": ["Techno Blast", "Earthquake", "Sunsteel Strike", "U-turn"], + "abilities": ["Download"], + "teraTypes": ["Fire"] + } + ] + }, + "genesectchill": { + "level": 84, + "sets": [ + { + "role": "Wallbreaker", + "movepool": ["Techno Blast", "Earthquake", "Behemoth Blade", "U-turn"], + "abilities": ["Download"], + "teraTypes": ["Ice"] + } + ] + }, + "genesectdouse": { + "level": 82, + "sets": [ + { + "role": "Wallbreaker", + "movepool": ["Techno Blast", "Thunder", "Make It Rain", "U-turn"], + "abilities": ["Download"], + "teraTypes": ["Water"] + } + ] + }, + "genesectshock": { + "level": 80, + "sets": [ + { + "role": "Wallbreaker", + "movepool": ["Techno Blast", "Ice Beam", "Tachyon Cutter", "U-turn"], + "abilities": ["Download"], + "teraTypes": ["Electric"] + } + ] + }, + "honchkrow": { + "level": 84, + "sets": [ + { + "role": "Bulky Attacker", + "movepool": ["Crowverload", "Sucker Punch", "Oblivion Wing", "U-turn"], + "abilities": ["Supreme Overlord"], + "teraTypes": ["Dark"] + }, + { + "role": "Bulky Attacker", + "movepool": ["Crowverload", "Sucker Punch", "Oblivion Wing", "Close Combat"], + "abilities": ["Supreme Overlord"], + "teraTypes": ["Fighting"] + } + ] + }, + "primeape": { + "level": 84, + "sets": [ + { + "role": "Bulky Attacker", + "movepool": ["Drain Punch", "Taunt", "Knock Off", "Iron Head", "Earthquake"], + "abilities": ["Battle Rage"], + "teraTypes": ["Steel"] + } + ] + }, + "rillaboom": { + "level": 84, + "sets": [ + { + "role": "Fast Attacker", + "movepool": ["Nature's Fury", "Wood Hammer", "Land's Wrath", "U-turn"], + "abilities": ["Terrain Shift"], + "teraTypes": ["Stellar"] + } + ] + }, + "mandibuzz": { + "level": 84, + "sets": [ + { + "role": "Wallbreaker", + "movepool": ["Fling", "Scavenge", "Bonemerang", "Roost"], + "abilities": ["Weak Armor"], + "teraTypes": ["Poison"] + } + ] + }, + "feraligatr": { + "level": 84, + "sets": [ + { + "role": "Setup Sweeper", + "movepool": ["Dragon Dance", "Thunder Fang", "Crunch", "Liquidation"], + "abilities": ["Sheer Force"], + "teraTypes": ["Dark"] + } + ] + }, + "feraligatrmega": { + "level": 84, + "sets": [ + { + "role": "Setup Sweeper", + "movepool": ["Dragon Dance", "Thunder Fang", "Poison Fang", "Crunch", "Fire Fang"], + "abilities": ["Sheer Force"], + "teraTypes": ["Normal"] + } + ] + }, + "salazzle": { + "level": 84, + "sets": [ + { + "role": "Wallbreaker", + "movepool": ["Magma Storm", "Malignant Chain", "Venoshock", "Psychic Noise", "Baneful Bunker"], + "abilities": ["Corrosive Soul"], + "teraTypes": ["Flying"] + } + ] + }, + "kyogre": { + "level": 80, + "sets": [ + { + "role": "Bulky Attacker", + "movepool": ["Origin Pulse", "Thunder", "Ice Beam", "Hurricane", "Tidal Surge"], + "abilities": ["Oceanic Blessing"], + "teraTypes": ["Water"] + } + ] + }, + "azelf": { + "level": 84, + "sets": [ + { + "role": "Fast Attacker", + "movepool": ["Rapid Spin", "Metronome", "Loot Box", "Acupressure"], + "abilities": ["Auto Spin"], + "teraTypes": ["Stellar"] + } + ] + }, + "decidueye": { + "level": 84, + "sets": [ + { + "role": "Fast Support", + "movepool": ["Sinister Arrows", "Leaf Blade", "Defog", "Roost", "Spirit Shackle", "U-turn"], + "abilities": ["Overgrow", "Sniper"], + "teraTypes": ["Steel", "Dark", "Ghost"] + } + ] + }, + "ogerponcornerstone": { + "level": 84, + "sets": [ + { + "role": "Bulky Attacker", + "movepool": ["Sappy Seed", "Ivy Cudgel", "Synthesis", "Spiky Shield", "Thousand Waves"], + "abilities": ["Solid Rock"], + "teraTypes": ["Rock"] + } + ] + }, + "glimmora": { + "level": 84, + "sets": [ + { + "role": "Fast Attacker", + "movepool": ["Mortal Spin", "Meteor Beam", "Power Gem", "Ice Beam", "Malignant Chain"], + "abilities": ["Corrosion"], + "teraTypes": ["Grass"] + } + ] + }, + "wobbuffet": { + "level": 84, + "sets": [ + { + "role": "Bulky Support", + "movepool": ["Counter", "Mirror Coat", "Shed Tail", "Encore", "Guillotine", "Night Shade"], + "abilities": ["Jello Body"], + "teraTypes": ["Dark", "Steel"] + } + ] + }, + "raticatemega": { + "level": 84, + "sets": [ + { + "role": "Fast Attacker", + "movepool": ["Super Fang", "Hyper Fang", "Last Breakfast", "U-turn"], + "abilities": ["Hustle"], + "teraTypes": ["Normal"] + } + ] } } diff --git a/data/random-battles/chatbats/teams.ts b/data/random-battles/chatbats/teams.ts index e81f994eee..fa7f264372 100644 --- a/data/random-battles/chatbats/teams.ts +++ b/data/random-battles/chatbats/teams.ts @@ -1,85 +1,17 @@ -import {Dex, toID} from '../../../sim/dex'; -import {Utils} from '../../../lib'; -import {PRNG, PRNGSeed} from '../../../sim/prng'; -import {RuleTable} from '../../../sim/dex-formats'; -import {Tags} from '../../tags'; - -export interface TeamData { - typeCount: {[k: string]: number}; - typeComboCount: {[k: string]: number}; - baseFormes: {[k: string]: number}; - megaCount?: number; - zCount?: number; - wantsTeraCount?: number; - has: {[k: string]: number}; - forceResult: boolean; - weaknesses: {[k: string]: number}; - resistances: {[k: string]: number}; - weather?: string; - eeveeLimCount?: number; - gigantamax?: boolean; -} -export interface BattleFactorySpecies { - flags: {limEevee?: 1}; - sets: BattleFactorySet[]; -} -interface BattleFactorySet { - species: string; - item: string; - ability: string; - nature: string; - moves: string[]; - evs?: Partial; - ivs?: Partial; -} -interface BSSFactorySet { - species: string; - weight: number; - item: string[]; - ability: string; - nature: string; - moves: string[][]; - teraType: string[]; - gender?: string; - wantsTera?: boolean; - evs: number[]; - ivs?: number[]; -} -export class MoveCounter extends Utils.Multiset { - damagingMoves: Set; - - constructor() { - super(); - this.damagingMoves = new Set(); - } -} - -type MoveEnforcementChecker = ( - movePool: string[], moves: Set, abilities: string[], types: string[], - counter: MoveCounter, species: Species, teamDetails: RandomTeamsTypes.TeamDetails, - isLead: boolean, isDoubles: boolean, teraType: string, role: RandomTeamsTypes.Role, -) => boolean; +import { RandomTeams, type MoveCounter } from "../gen9/teams"; // Moves that restore HP: const RECOVERY_MOVES = [ 'healorder', 'milkdrink', 'moonlight', 'morningsun', 'recover', 'roost', 'shoreup', 'slackoff', 'softboiled', 'strengthsap', 'synthesis', ]; -// Moves that drop stats: -const CONTRARY_MOVES = [ - 'armorcannon', 'closecombat', 'leafstorm', 'makeitrain', 'overheat', 'spinout', 'superpower', 'vcreate', -]; // Moves that boost Attack: const PHYSICAL_SETUP = [ 'bellydrum', 'bulkup', 'coil', 'curse', 'dragondance', 'honeclaws', 'howl', 'meditate', 'poweruppunch', 'swordsdance', 'tidyup', 'victorydance', + 'filletaway', ]; // Moves which boost Special Attack: const SPECIAL_SETUP = [ - 'calmmind', 'chargebeam', 'geomancy', 'nastyplot', 'quiverdance', 'tailglow', 'takeheart', 'torchsong', -]; -// Moves that boost Attack AND Special Attack: -// Dragon Dance is here to force Altaria to get it -const MIXED_SETUP = [ - 'clangoroussoul', 'growth', 'happyhour', 'holdhands', 'noretreat', 'shellsmash', 'workup', 'dragondance', 'filletaway', + 'calmmind', 'chargebeam', 'geomancy', 'nastyplot', 'quiverdance', 'tailglow', 'takeheart', 'torchsong', 'filletaway', ]; // Some moves that only boost Speed: const SPEED_SETUP = [ @@ -89,19 +21,11 @@ const SPEED_SETUP = [ const SETUP = [ 'acidarmor', 'agility', 'autotomize', 'bellydrum', 'bulkup', 'calmmind', 'clangoroussoul', 'coil', 'cosmicpower', 'curse', 'dragondance', 'filletaway', 'flamecharge', 'growth', 'honeclaws', 'howl', 'irondefense', 'meditate', 'nastyplot', 'noretreat', 'poweruppunch', 'quiverdance', - 'rockpolish', 'shellsmash', 'shiftgear', 'swordsdance', 'tailglow', 'takeheart', 'tidyup', 'trailblaze', 'trick room', 'workup', 'victorydance', + 'rockpolish', 'shellsmash', 'shiftgear', 'swordsdance', 'tailglow', 'takeheart', 'tidyup', 'trailblaze', 'trickroom', 'workup', 'victorydance', ]; const SPEED_CONTROL = [ 'electroweb', 'glare', 'icywind', 'lowsweep', 'quash', 'stringshot', 'tailwind', 'thunderwave', 'trickroom', ]; -// Moves that shouldn't be the only STAB moves: -const NO_STAB = [ - 'accelerock', 'aquajet', 'bounce', 'breakingswipe', 'bulletpunch', 'chatter', 'chloroblast', 'circlethrow', 'clearsmog', 'covet', - 'dragontail', 'doomdesire', 'electroweb', 'eruption', 'explosion', 'fakeout', 'feint', 'flamecharge', 'flipturn', 'futuresight', - 'grassyglide', 'iceshard', 'icywind', 'incinerate', 'infestation', 'machpunch', 'meteorbeam', 'mortalspin', 'nuzzle', 'pluck', 'pursuit', - 'quickattack', 'rapidspin', 'reversal', 'selfdestruct', 'shadowsneak', 'skydrop', 'snarl', 'strugglebug', 'suckerpunch', 'uturn', - 'vacuumwave', 'voltswitch', 'watershuriken', 'waterspout', -]; // Hazard-setting moves const HAZARDS = [ 'spikes', 'stealthrock', 'stickyweb', 'toxicspikes', @@ -137,308 +61,8 @@ const NO_LEAD_POKEMON = [ const DOUBLES_NO_LEAD_POKEMON = [ 'Basculegion', 'Houndstone', 'Iron Bundle', 'Roaring Moon', 'Zacian', 'Zamazenta', ]; - -const DEFENSIVE_TERA_BLAST_USERS = [ - 'alcremie', 'bellossom', 'comfey', 'fezandipiti', 'florges', -]; - -function sereneGraceBenefits(move: Move) { - return move.secondary?.chance && move.secondary.chance > 20 && move.secondary.chance < 100; -} - -export class RandomTeams { - dex: ModdedDex; - gen: number; - factoryTier: string; - format: Format; - prng: PRNG; - noStab: string[]; - readonly maxTeamSize: number; - readonly adjustLevel: number | null; - readonly maxMoveCount: number; - readonly forceMonotype: string | undefined; - readonly forceTeraType: string | undefined; - - /** - * Checkers for move enforcement based on types or other factors - * - * returns true to try to force the move type, false otherwise. - */ - moveEnforcementCheckers: {[k: string]: MoveEnforcementChecker}; - - constructor(format: Format | string, prng: PRNG | PRNGSeed | null) { - format = Dex.formats.get(format); - this.dex = Dex.forFormat(format); - this.gen = this.dex.gen; - this.noStab = NO_STAB; - - const ruleTable = Dex.formats.getRuleTable(format); - this.maxTeamSize = ruleTable.maxTeamSize; - this.adjustLevel = ruleTable.adjustLevel; - this.maxMoveCount = ruleTable.maxMoveCount; - const forceMonotype = ruleTable.valueRules.get('forcemonotype'); - this.forceMonotype = forceMonotype && this.dex.types.get(forceMonotype).exists ? - this.dex.types.get(forceMonotype).name : undefined; - const forceTeraType = ruleTable.valueRules.get('forceteratype'); - this.forceTeraType = forceTeraType && this.dex.types.get(forceTeraType).exists ? - this.dex.types.get(forceTeraType).name : undefined; - - this.factoryTier = ''; - this.format = format; - this.prng = prng && !Array.isArray(prng) ? prng : new PRNG(prng); - - this.moveEnforcementCheckers = { - Bug: (movePool, moves, abilities, types, counter) => ( - movePool.includes('megahorn') || movePool.includes('xscissor') || - (!counter.get('Bug') && (types.includes('Electric') || types.includes('Psychic'))) - ), - Dark: ( - movePool, moves, abilities, types, counter, species, teamDetails, isLead, isDoubles, teraType, role - ) => { - if ( - counter.get('Dark') < 2 && PRIORITY_POKEMON.includes(species.id) && role === 'Wallbreaker' - ) return true; - return !counter.get('Dark'); - }, - Dragon: (movePool, moves, abilities, types, counter) => !counter.get('Dragon'), - Electric: (movePool, moves, abilities, types, counter) => !counter.get('Electric'), - Fairy: (movePool, moves, abilities, types, counter) => !counter.get('Fairy'), - Fighting: (movePool, moves, abilities, types, counter) => !counter.get('Fighting'), - Fire: (movePool, moves, abilities, types, counter, species) => !counter.get('Fire'), - Flying: (movePool, moves, abilities, types, counter) => !counter.get('Flying'), - Ghost: (movePool, moves, abilities, types, counter) => !counter.get('Ghost'), - Grass: (movePool, moves, abilities, types, counter, species) => ( - !counter.get('Grass') && ( - movePool.includes('leafstorm') || species.baseStats.atk >= 100 || - types.includes('Electric') || abilities.includes('Seed Sower') - ) - ), - Ground: (movePool, moves, abilities, types, counter) => !counter.get('Ground'), - Ice: (movePool, moves, abilities, types, counter) => ( - movePool.includes('freezedry') || movePool.includes('blizzard') || !counter.get('Ice') - ), - Normal: (movePool, moves, types, counter) => (movePool.includes('boomburst') || movePool.includes('hypervoice')), - Poison: (movePool, moves, abilities, types, counter) => { - if (types.includes('Ground')) return false; - return !counter.get('Poison'); - }, - Psychic: (movePool, moves, abilities, types, counter, species, teamDetails, isLead, isDoubles) => { - if (counter.get('Psychic')) return false; - if (movePool.includes('calmmind') || abilities.includes('Strong Jaw')) return true; - if (isDoubles && movePool.includes('psychicfangs')) return true; - return abilities.includes('Psychic Surge') || ['Bug', 'Electric', 'Fighting', 'Fire', 'Grass', 'Poison'].some(m => types.includes(m)); - }, - Rock: (movePool, moves, abilities, types, counter, species) => !counter.get('Rock') && species.baseStats.atk >= 80, - Steel: (movePool, moves, abilities, types, counter, species, teamDetails, isLead, isDoubles) => ( - !counter.get('Steel') && - (isDoubles || species.baseStats.atk >= 90 || movePool.includes('gigatonhammer') || movePool.includes('makeitrain')) - ), - Water: (movePool, moves, abilities, types, counter) => (!counter.get('Water') && !types.includes('Ground')), - }; - } - - setSeed(prng?: PRNG | PRNGSeed) { - this.prng = prng && !Array.isArray(prng) ? prng : new PRNG(prng); - } - - getTeam(options?: PlayerOptions | null): PokemonSet[] { - const generatorName = ( - typeof this.format.team === 'string' && this.format.team.startsWith('random') - ) ? this.format.team + 'Team' : ''; - // @ts-ignore - return this[generatorName || 'randomTeam'](options); - } - - randomChance(numerator: number, denominator: number) { - return this.prng.randomChance(numerator, denominator); - } - - sample(items: readonly T[]): T { - return this.prng.sample(items); - } - - sampleIfArray(item: T | T[]): T { - if (Array.isArray(item)) { - return this.sample(item); - } - return item; - } - - random(m?: number, n?: number) { - return this.prng.next(m, n); - } - - /** - * Remove an element from an unsorted array significantly faster - * than .splice - */ - fastPop(list: any[], index: number) { - // If an array doesn't need to be in order, replacing the - // element at the given index with the removed element - // is much, much faster than using list.splice(index, 1). - const length = list.length; - if (index < 0 || index >= list.length) { - // sanity check - throw new Error(`Index ${index} out of bounds for given array`); - } - - const element = list[index]; - list[index] = list[length - 1]; - list.pop(); - return element; - } - - /** - * Remove a random element from an unsorted array and return it. - * Uses the battle's RNG if in a battle. - */ - sampleNoReplace(list: any[]) { - const length = list.length; - if (length === 0) return null; - const index = this.random(length); - return this.fastPop(list, index); - } - - /** - * Removes n random elements from an unsorted array and returns them. - * If n is less than the array's length, randomly removes and returns all the elements - * in the array (so the returned array could have length < n). - */ - multipleSamplesNoReplace(list: T[], n: number): T[] { - const samples = []; - while (samples.length < n && list.length) { - samples.push(this.sampleNoReplace(list)); - } - - return samples; - } - - /** - * Check if user has directly tried to ban/unban/restrict things in a custom battle. - * Doesn't count bans nested inside other formats/rules. - */ - private hasDirectCustomBanlistChanges() { - if (this.format.banlist.length || this.format.restricted.length || this.format.unbanlist.length) return true; - if (!this.format.customRules) return false; - for (const rule of this.format.customRules) { - for (const banlistOperator of ['-', '+', '*']) { - if (rule.startsWith(banlistOperator)) return true; - } - } - return false; - } - - /** - * Inform user when custom bans are unsupported in a team generator. - */ - protected enforceNoDirectCustomBanlistChanges() { - if (this.hasDirectCustomBanlistChanges()) { - throw new Error(`Custom bans are not currently supported in ${this.format.name}.`); - } - } - - /** - * Inform user when complex bans are unsupported in a team generator. - */ - protected enforceNoDirectComplexBans() { - if (!this.format.customRules) return false; - for (const rule of this.format.customRules) { - if (rule.includes('+') && !rule.startsWith('+')) { - throw new Error(`Complex bans are not currently supported in ${this.format.name}.`); - } - } - } - - /** - * Validate set element pool size is sufficient to support size requirements after simple bans. - */ - private enforceCustomPoolSizeNoComplexBans( - effectTypeName: string, - basicEffectPool: BasicEffect[], - requiredCount: number, - requiredCountExplanation: string - ) { - if (basicEffectPool.length >= requiredCount) return; - throw new Error(`Legal ${effectTypeName} count is insufficient to support ${requiredCountExplanation} (${basicEffectPool.length} / ${requiredCount}).`); - } - - queryMoves( - moves: Set | null, - species: Species, - teraType: string, - abilities: string[], - ): MoveCounter { - // This is primarily a helper function for random setbuilder functions. - const counter = new MoveCounter(); - const types = species.types; - if (!moves?.size) return counter; - - const categories = {Physical: 0, Special: 0, Status: 0}; - - // Iterate through all moves we've chosen so far and keep track of what they do: - for (const moveid of moves) { - const move = this.dex.moves.get(moveid); - - const moveType = this.getMoveType(move, species, abilities, teraType); - if (move.damage || move.damageCallback) { - // Moves that do a set amount of damage: - counter.add('damage'); - counter.damagingMoves.add(move); - } else { - // Are Physical/Special/Status moves: - categories[move.category]++; - } - // Moves that have a low base power: - if (moveid === 'lowkick' || (move.basePower && move.basePower <= 60 && moveid !== 'rapidspin')) { - counter.add('technician'); - } - // Moves that hit up to 5 times: - if (move.multihit && Array.isArray(move.multihit) && move.multihit[1] === 5) counter.add('skilllink'); - if (move.recoil || move.hasCrashDamage) counter.add('recoil'); - if (move.drain) counter.add('drain'); - // Moves which have a base power: - if (move.basePower || move.basePowerCallback) { - if (!this.noStab.includes(moveid) || PRIORITY_POKEMON.includes(species.id) && move.priority > 0) { - counter.add(moveType); - if (types.includes(moveType)) counter.add('stab'); - if (teraType === moveType) counter.add('stabtera'); - counter.damagingMoves.add(move); - } - if (move.flags['bite']) counter.add('strongjaw'); - if (move.flags['punch']) counter.add('ironfist'); - if (move.flags['sound']) counter.add('sound'); - if (move.priority > 0 || (moveid === 'grassyglide' && abilities.includes('Grassy Surge'))) { - counter.add('priority'); - } - } - // Moves with secondary effects: - if (move.secondary || move.hasSheerForce) { - counter.add('sheerforce'); - if (sereneGraceBenefits(move)) { - counter.add('serenegrace'); - } - } - // Moves with low accuracy: - if (move.accuracy && move.accuracy !== true && move.accuracy < 90) counter.add('inaccurate'); - - // Moves that change stats: - if (RECOVERY_MOVES.includes(moveid)) counter.add('recovery'); - if (CONTRARY_MOVES.includes(moveid)) counter.add('contrary'); - if (PHYSICAL_SETUP.includes(moveid)) counter.add('physicalsetup'); - if (SPECIAL_SETUP.includes(moveid)) counter.add('specialsetup'); - if (MIXED_SETUP.includes(moveid)) counter.add('mixedsetup'); - if (SPEED_SETUP.includes(moveid)) counter.add('speedsetup'); - if (SETUP.includes(moveid)) counter.add('setup'); - if (HAZARDS.includes(moveid)) counter.add('hazards'); - } - - counter.set('Physical', Math.floor(categories['Physical'])); - counter.set('Special', Math.floor(categories['Special'])); - counter.set('Status', categories['Status']); - return counter; - } - - cullMovePool( +export class RandomChatBatsTeams extends RandomTeams { + override cullMovePool( types: string[], moves: Set, abilities: string[], @@ -477,9 +101,7 @@ export class RandomTeams { } // Develop additional move lists - const statusMoves = this.dex.moves.all() - .filter(move => move.category === 'Status') - .map(move => move.id); + const statusMoves = this.cachedStatusMoves; // Team-based move culls if (teamDetails.screens && movePool.length >= this.maxMoveCount + 2) { @@ -557,7 +179,6 @@ export class RandomTeams { ['dragondance', 'dracometeor'], // These attacks are redundant with each other - //[['psychic', 'psychicnoise'], ['psyshock', 'psychicnoise']], ['surf', 'hydropump'], ['liquidation', 'wavecrash'], ['aquajet', 'flipturn'], @@ -583,7 +204,6 @@ export class RandomTeams { ['taunt', 'disable'], [['thunderwave', 'toxic'], ['thunderwave', 'willowisp']], [['thunderwave', 'toxic', 'willowisp'], 'toxicspikes'], - ]; for (const pair of incompatiblePairs) this.incompatibleMoves(moves, movePool, pair[0], pair[1]); @@ -608,93 +228,19 @@ export class RandomTeams { if (species.id === 'archaludon') this.incompatibleMoves(moves, movePool, 'scald', 'hydropump'); if (species.id === 'abomasnowmega') this.incompatibleMoves(moves, movePool, 'iceshard', 'snowscape'); if (species.id === 'regieleki') this.incompatibleMoves(moves, movePool, 'blazingtorque', 'soak'); - if (species.id === 'tatsugiri') this.incompatibleMoves(moves, movePool, 'nastyplot', 'rapidspin'); if (species.id === 'golurk') this.incompatibleMoves(moves, movePool, 'icepunch', 'dynamicpunch'); - if (species.id === 'veluza') this.incompatibleMoves(moves, movePool, 'waterfall', 'hydropump'); if (species.id === 'ogerponhearthflame') this.incompatibleMoves(moves, movePool, 'crabhammer', 'stoneedge'); - + if (species.id === 'hitmontop') this.incompatibleMoves(moves, movePool, 'bulkup', 'rapidspin'); + if (species.id === 'mesprit') this.incompatibleMoves(moves, movePool, 'psychic', 'storedpower'); + if (species.id === 'primeape') this.incompatibleMoves(moves, movePool, 'knockoff', 'earthquake'); + if (species.id === 'feraligatrmega') this.incompatibleMoves(moves, movePool, 'thunderfang', 'poisonfang'); + if (species.id === 'salazzle') this.incompatibleMoves(moves, movePool, 'malignantchain', 'venoshock'); + if (species.id === 'glimmora') this.incompatibleMoves(moves, movePool, 'powergem', 'meteorbeam'); + if (species.id === 'wobbuffet') this.incompatibleMoves(moves, movePool, 'shedtail', 'encore'); + if (species.id === 'wobbuffet') this.incompatibleMoves(moves, movePool, 'nightshade', 'guillotine'); } - // Checks for and removes incompatible moves, starting with the first move in movesA. - incompatibleMoves( - moves: Set, - movePool: string[], - movesA: string | string[], - movesB: string | string[], - ): void { - const moveArrayA = (Array.isArray(movesA)) ? movesA : [movesA]; - const moveArrayB = (Array.isArray(movesB)) ? movesB : [movesB]; - if (moves.size + movePool.length <= this.maxMoveCount) return; - for (const moveid1 of moves) { - if (moveArrayB.includes(moveid1)) { - for (const moveid2 of moveArrayA) { - if (moveid1 !== moveid2 && movePool.includes(moveid2)) { - this.fastPop(movePool, movePool.indexOf(moveid2)); - if (moves.size + movePool.length <= this.maxMoveCount) return; - } - } - } - if (moveArrayA.includes(moveid1)) { - for (const moveid2 of moveArrayB) { - if (moveid1 !== moveid2 && movePool.includes(moveid2)) { - this.fastPop(movePool, movePool.indexOf(moveid2)); - if (moves.size + movePool.length <= this.maxMoveCount) return; - } - } - } - } - } - - // Adds a move to the moveset, returns the MoveCounter - addMove( - move: string, - moves: Set, - types: string[], - abilities: string[], - teamDetails: RandomTeamsTypes.TeamDetails, - species: Species, - isLead: boolean, - isDoubles: boolean, - movePool: string[], - teraType: string, - role: RandomTeamsTypes.Role, - ): MoveCounter { - moves.add(move); - this.fastPop(movePool, movePool.indexOf(move)); - const counter = this.queryMoves(moves, species, teraType, abilities); - this.cullMovePool(types, moves, abilities, counter, movePool, teamDetails, species, isLead, isDoubles, teraType, role); - return counter; - } - - // Returns the type of a given move for STAB/coverage enforcement purposes - getMoveType(move: Move, species: Species, abilities: string[], teraType: string): string { - if (move.id === 'terablast') return teraType; - if (['judgment', 'revelationdance'].includes(move.id)) return species.types[0]; - - if (move.name === "Raging Bull" && species.name.startsWith("Tauros-Paldea")) { - if (species.name.endsWith("Combat")) return "Fighting"; - if (species.name.endsWith("Blaze")) return "Fire"; - if (species.name.endsWith("Aqua")) return "Water"; - } - - if (move.name === "Ivy Cudgel" && species.name.startsWith("Ogerpon")) { - if (species.name.endsWith("Wellspring")) return "Water"; - if (species.name.endsWith("Hearthflame")) return "Fire"; - if (species.name.endsWith("Cornerstone")) return "Rock"; - } - - const moveType = move.type; - if (moveType === 'Normal') { - if (abilities.includes('Aerilate')) return 'Flying'; - if (abilities.includes('Galvanize')) return 'Electric'; - if (abilities.includes('Pixilate')) return 'Fairy'; - if (abilities.includes('Refrigerate')) return 'Ice'; - } - return moveType; - } - - // Generate random moveset for a given species, role, tera type. - randomMoveset( + override randomMoveset( types: string[], abilities: string[], teamDetails: RandomTeamsTypes.TeamDetails, @@ -745,36 +291,12 @@ export class RandomTeams { movePool, teraType, role); } } - // 33% chance to force Dragon Dance on Mega Altaria, since it otherwise never gets it due to teambuilder shenanigans - if (species.id === 'altariamega') { - if (movePool.includes('dragondance')) { - if (this.randomChance(1, 3)) { - counter = this.addMove('dragondance', moves, types, abilities, teamDetails, species, isLead, isDoubles, - movePool, teraType, role); - } - } - } - // enforces a sound move on Mesprit with Throat Spray - if (species.id === 'mesprit') { - if (movePool.includes('psychicnoise')) { - if (this.randomChance(1, 2)) { - counter = this.addMove('psychicnoise', moves, types, abilities, teamDetails, species, isLead, isDoubles, - movePool, teraType, role); - } - else { - counter = this.addMove('torchsong', moves, types, abilities, teamDetails, species, isLead, isDoubles, - movePool, teraType, role); - } - } - } // enforces both primary stabs on Infernape - if (species.id === 'infernape') { - if (movePool.includes('mindblown')) { - counter = this.addMove('mindblown', moves, types, abilities, teamDetails, species, isLead, isDoubles, - movePool, teraType, role); - counter = this.addMove('alloutassault', moves, types, abilities, teamDetails, species, isLead, isDoubles, - movePool, teraType, role); - } + if (species.id === 'infernape' && movePool.includes('mindblown')) { + counter = this.addMove('mindblown', moves, types, abilities, teamDetails, species, isLead, isDoubles, + movePool, teraType, role); + counter = this.addMove('alloutassault', moves, types, abilities, teamDetails, species, isLead, isDoubles, + movePool, teraType, role); } // Enforce Facade if Guts is a possible ability @@ -818,11 +340,10 @@ export class RandomTeams { } // Enforce Flip Turn on pure Water-type Wallbreakers - if (types.length === 1 && types.includes('Water') && role === 'Wallbreaker') { - if (movePool.includes('flipturn')) { - counter = this.addMove('flipturn', moves, types, abilities, teamDetails, species, isLead, isDoubles, - movePool, teraType, role); - } + if (types.length === 1 && types.includes('Water') && + role === 'Wallbreaker' && movePool.includes('flipturn')) { + counter = this.addMove('flipturn', moves, types, abilities, teamDetails, species, isLead, isDoubles, + movePool, teraType, role); } // Enforce Spore on Smeargle @@ -884,7 +405,7 @@ export class RandomTeams { // Enforce a single STAB for Moltres if (species.id === 'moltres') { - let typeToEnforce = (this.randomChance(1,2)) ? 'Fire' : 'Flying'; + const typeToEnforce = this.randomChance(1, 2) ? 'Fire' : 'Flying'; const stabMoves = []; for (const moveid of movePool) { @@ -926,11 +447,11 @@ export class RandomTeams { } // Enforce Tera STAB - if (!counter.get('stabtera') && !['Bulky Support', 'Doubles Support'].includes(role)) { + // prevents Meowscarada from being enforced stab moves (since it has Protean and doesn't care) + if (!counter.get('stabtera') && !['Bulky Support', 'Doubles Support'].includes(role) && + !abilities.includes('Protean')) { const stabMoves = []; for (const moveid of movePool) { - // prevents Meowscarada from being enforced stab moves (since it has Protean and doesn't care) - if (species.id === 'meowscarada') break; const move = this.dex.moves.get(moveid); const moveType = this.getMoveType(move, species, abilities, teraType); if (!this.noStab.includes(moveid) && (move.basePower || move.basePowerCallback) && teraType === moveType) { @@ -945,11 +466,10 @@ export class RandomTeams { } // If no STAB move was added, add a STAB move - if (!counter.get('stab')) { + // prevents Meowscarada from being enforced stab moves (since it has Protean and doesn't care) + if (!counter.get('stab') && !abilities.includes('Protean')) { const stabMoves = []; for (const moveid of movePool) { - // prevents Meowscarada from being enforced stab moves (since it has Protean and doesn't care) - if (species.id === 'meowscarada') break; const move = this.dex.moves.get(moveid); const moveType = this.getMoveType(move, species, abilities, teraType); if (!this.noStab.includes(moveid) && (move.basePower || move.basePowerCallback) && types.includes(moveType)) { @@ -1031,7 +551,7 @@ export class RandomTeams { if (!['AV Pivot', 'Fast Support', 'Bulky Support', 'Bulky Protect', 'Doubles Support'].includes(role)) { if (counter.damagingMoves.size === 1) { // Find the type of the current attacking move - const currentAttackType = counter.damagingMoves.values().next().value.type; + const currentAttackType = counter.damagingMoves.values().next().value!.type; // Choose an attacking move that is of different type to the current single attack const coverageMoves = []; for (const moveid of movePool) { @@ -1077,108 +597,7 @@ export class RandomTeams { return moves; } - shouldCullAbility( - ability: string, - types: string[], - moves: Set, - abilities: string[], - counter: MoveCounter, - teamDetails: RandomTeamsTypes.TeamDetails, - species: Species, - isLead: boolean, - isDoubles: boolean, - teraType: string, - role: RandomTeamsTypes.Role, - ): boolean { - switch (ability) { - // Abilities which are primarily useful for certain moves or with team support - case 'Chlorophyll': case 'Solar Power': - return !teamDetails.sun; - case 'Defiant': - return (species.id === 'thundurus' && !!counter.get('Status')); - case 'Hydration': case 'Swift Swim': - return !teamDetails.rain; - case 'Iron Fist': case 'Skill Link': - return !counter.get(toID(ability)); - case 'Overgrow': - return !counter.get('Grass'); - case 'Prankster': - return !counter.get('Status'); - case 'Sand Force': case 'Sand Rush': - return !teamDetails.sand; - case 'Slush Rush': - return !teamDetails.snow; - case 'Swarm': - return !counter.get('Bug'); - case 'Torrent': - return (!counter.get('Water') && !moves.has('flipturn')); - } - - return false; - } - - - getAbility( - types: string[], - moves: Set, - abilities: string[], - counter: MoveCounter, - teamDetails: RandomTeamsTypes.TeamDetails, - species: Species, - isLead: boolean, - isDoubles: boolean, - teraType: string, - role: RandomTeamsTypes.Role, - ): string { - if (abilities.length <= 1) return abilities[0]; - - // Hard-code abilities here - if (species.id === 'drifblim') return moves.has('defog') ? 'Aftermath' : 'Unburden'; - if (species.id === 'hitmonchan' && counter.get('ironfist')) return 'Iron Fist'; - if ((species.id === 'thundurus' || species.id === 'tornadus') && !counter.get('Physical')) return 'Prankster'; - if (species.id === 'swampert' && (counter.get('Water') || moves.has('flipturn'))) return 'Torrent'; - if (species.id === 'toucannon' && counter.get('skilllink')) return 'Skill Link'; - if (abilities.includes('Slush Rush') && moves.has('snowscape')) return 'Slush Rush'; - if (species.id === 'golduck' && teamDetails.rain) return 'Swift Swim'; - - // ffa abilities that differ from doubles - if (this.format.gameType === 'freeforall') { - if (species.id === 'bellossom') return 'Chlorophyll'; - if (species.id === 'sinistcha') return 'Heatproof'; - if (species.id === 'oranguru') return 'Inner Focus'; - if (species.id === 'duraludon') return 'Light Metal'; - if (species.id === 'clefairy') return 'Magic Guard'; - if (species.id === 'blissey') return 'Natural Cure'; - if (species.id === 'barraskewda') return 'Swift Swim'; - if (abilities.includes('Pressure') && abilities.includes('Telepathy')) return 'Pressure'; - } - - const abilityAllowed: string[] = []; - // Obtain a list of abilities that are allowed (not culled) - for (const ability of abilities) { - if (!this.shouldCullAbility( - ability, types, moves, abilities, counter, teamDetails, species, isLead, isDoubles, teraType, role - )) { - abilityAllowed.push(ability); - } - } - - // Pick a random allowed ability - if (abilityAllowed.length >= 1) return this.sample(abilityAllowed); - - // If all abilities are rejected, prioritize weather abilities over non-weather abilities - if (!abilityAllowed.length) { - const weatherAbilities = abilities.filter( - a => ['Chlorophyll', 'Hydration', 'Sand Force', 'Sand Rush', 'Slush Rush', 'Solar Power', 'Swift Swim'].includes(a) - ); - if (weatherAbilities.length) return this.sample(weatherAbilities); - } - - // Pick a random ability - return this.sample(abilities); - } - - getPriorityItem( + override getPriorityItem( ability: string, types: string[], moves: Set, @@ -1213,7 +632,7 @@ export class RandomTeams { // PMCM hardcodes if (species.id === 'volcarona') return 'Heavy-Duty Boots'; if (species.id === 'golemalola') return 'Life Orb'; - if (species.id === 'ironcrown') return this.sample(['Chesto Berry', 'Leftovers']); + if (species.id === 'ironcrown') return moves.has('rest') ? 'Chesto Berry' : 'Leftovers'; if (species.id === 'lurantis') return this.sample(['Life Orb', 'Leftovers']); if (species.id === 'carbink') return 'Leftovers'; if (species.id === 'moltres') return 'Life Orb'; @@ -1223,7 +642,6 @@ export class RandomTeams { if (species.id === 'abomasnow') return 'Light Clay'; if (species.id === 'dugtrio' && moves.has("swordsdance")) return 'Focus Sash'; if (species.id === 'dugtrio') return 'Choice Band'; - if (species.id === 'altaria') return 'Heavy-Duty Boots'; if (species.id === 'tyranitar') return 'Choice Scarf'; if (species.id === 'mimikyu') return 'Red Card'; if (species.id === 'mesprit' && moves.has("aquaring")) return 'Leftovers'; @@ -1237,18 +655,19 @@ export class RandomTeams { if (species.id === 'archaludon' && ability === 'Hydroelectric Dam') return 'Assault Vest'; if (species.id === 'archaludon' && ability === 'Stamina') return 'Leftovers'; if (species.id === 'malamar') return this.sample(['Mirror Herb', 'Leftovers']); - if (species.id === 'empoleon') return 'Rocky Helmet'; + if (species.id === 'empoleon') return moves.has('watershuriken') ? 'Loaded Dice' : 'Leftovers'; if (species.id === 'glastrier' && moves.has('swordsdance')) return 'Heavy-Duty Boots'; if (species.id === 'glastrier') return 'Assault Vest'; if (species.id === 'lycanrocmidnight') return 'Loaded Dice'; if (species.id === 'lycanroc') return this.sample(['Leftovers', 'Heavy-Duty Boots']); if (species.id === 'lycanrocdusk') return 'Expert Belt'; - if (species.id === 'dodrio') return 'Life Orb'; + if (species.id === 'dodrio' && moves.has('drillpeck')) return 'Life Orb'; + if (species.id === 'dodrio' && moves.has('bravebird')) return 'Heavy-Duty Boots'; if (species.id === 'whiscash') return 'Rocky Helmet'; if (species.id === 'hippowdon') return this.sample(['Leftovers', 'Rocky Helmet']); if (species.id === 'cramorant') return 'Heavy-Duty Boots'; if (species.id === 'grafaiai') return this.sample(['Red Card', 'Mirror Herb']); - if (species.id === 'tatsugiri') return 'Mystic Water'; + if (species.id === 'tatsugiri') return 'Choice Scarf'; if (species.id === 'kyurem') return 'Heavy-Duty Boots'; if (species.id === 'roaringmoon') return 'Heavy-Duty Boots'; if (species.id === 'milotic') return 'Rocky Helmet'; @@ -1268,8 +687,6 @@ export class RandomTeams { if (species.id === 'swalot') return 'Leftovers'; if (species.id === 'zapdosgalar') return this.sample(['Choice Scarf', 'Expert Belt']); if (species.id === 'phione') return 'Leftovers'; - if (species.id === 'sudowoodo' && moves.has('synthesis')) return this.sample(['Red Card', 'Leftovers']); - if (species.id === 'sudowoodo' && moves.has('curse')) return this.sample(['Red Card', 'Leftovers']); if (species.id === 'sudowoodo') return 'Choice Band'; if (species.id === 'dondozo') return 'Leftovers'; if (species.id === 'golurk') return this.sample(['Life Orb', 'Punching Glove', 'Colbur Berry']); @@ -1278,294 +695,41 @@ export class RandomTeams { if (species.id === 'urshifu') return this.sample(['Life Orb', 'Protective Pads']); if (species.id === 'urshifurapidstrike') return this.sample(['Life Orb', 'Protective Pads']); if (species.id === 'salamence') return this.sample(['Life Orb', 'Heavy-Duty Boots', 'Sky Plate']); - if (species.id === 'stonjourner') return 'Life Orb'; + if (species.id === 'stonjourner') return 'Choice Scarf'; if (species.id === 'veluza') return 'Sitrus Berry'; if (species.id === 'ogerponhearthflame') return 'Hearthflame Mask'; if (species.id === 'dachsbun') return 'Rocky Helmet'; if (species.id === 'mew') return 'Starf Berry'; - - if ( - species.id === 'froslass' || moves.has('populationbomb') || - (ability === 'Hustle' && counter.get('setup') && !isDoubles && this.randomChance(1, 2)) - ) return 'Wide Lens'; - if (moves.has('clangoroussoul') || (species.id === 'toxtricity' && moves.has('shiftgear'))) return 'Throat Spray'; - if ( - (species.baseSpecies === 'Magearna' && role === 'Tera Blast user') || - species.id === 'necrozmaduskmane' || (species.id === 'calyrexice' && isDoubles) - ) return 'Weakness Policy'; - if (['dragonenergy', 'lastrespects', 'waterspout'].some(m => moves.has(m))) return 'Choice Scarf'; - if ( - !isDoubles && (ability === 'Imposter' || (species.id === 'magnezone' && role === 'Fast Attacker')) - ) return 'Choice Scarf'; - if (species.id === 'rampardos' && (role === 'Fast Attacker' || isDoubles)) return 'Choice Scarf'; - if (species.id === 'palkia' && counter.get('Special') < 4) return 'Lustrous Orb'; - if ( - moves.has('courtchange') || - !isDoubles && (species.id === 'luvdisc' || (species.id === 'terapagos' && !moves.has('rest'))) - ) return 'Heavy-Duty Boots'; - if (moves.has('bellydrum') && moves.has('substitute')) return 'Salac Berry'; - if ( - ['Cheek Pouch', 'Cud Chew', 'Harvest', 'Ripen'].some(m => ability === m) || - moves.has('bellydrum') || moves.has('filletaway') - ) { - return 'Sitrus Berry'; - } - if (['healingwish', 'switcheroo', 'trick'].some(m => moves.has(m))) { - if ( - species.baseStats.spe >= 60 && species.baseStats.spe <= 108 && - role !== 'Wallbreaker' && role !== 'Doubles Wallbreaker' && !counter.get('priority') - ) { - return 'Choice Scarf'; - } else { - return (counter.get('Physical') > counter.get('Special')) ? 'Choice Band' : 'Choice Specs'; - } - } - if (counter.get('Status') && (species.name === 'Latias' || species.name === 'Latios')) return 'Soul Dew'; - if (species.id === 'scyther' && !isDoubles) return (isLead && !moves.has('uturn')) ? 'Eviolite' : 'Heavy-Duty Boots'; - if (species.nfe) return 'Eviolite'; - if (ability === 'Poison Heal') return 'Toxic Orb'; - if ((ability === 'Guts' || moves.has('facade')) && !moves.has('sleeptalk')) { - return (types.includes('Fire') || ability === 'Toxic Boost') ? 'Toxic Orb' : 'Flame Orb'; - } - if (ability === 'Magic Guard' || (ability === 'Sheer Force' && counter.get('sheerforce'))) return 'Life Orb'; - if (ability === 'Anger Shell') return this.sample(['Rindo Berry', 'Passho Berry', 'Scope Lens', 'Sitrus Berry']); - if (moves.has('dragondance') && isDoubles) return 'Clear Amulet'; - if (counter.get('skilllink') && ability !== 'Skill Link' && species.id !== 'breloom') return 'Loaded Dice'; - if (ability === 'Unburden') { - return (moves.has('closecombat') || moves.has('leafstorm')) ? 'White Herb' : 'Sitrus Berry'; - } - if (moves.has('shellsmash') && ability !== 'Weak Armor') return 'White Herb'; - if (moves.has('meteorbeam') || (moves.has('electroshot') && !teamDetails.rain)) return 'Power Herb'; - if (ability === 'Sniper') return 'Scope Lens'; - if (moves.has('acrobatics') && ability !== 'Protosynthesis') return ''; - if (moves.has('auroraveil') || moves.has('dustveil') || moves.has('lightscreen') && moves.has('reflect')) return 'Light Clay'; - if (ability === 'Gluttony') return `${this.sample(['Aguav', 'Figy', 'Iapapa', 'Mago', 'Wiki'])} Berry`; - if ( - moves.has('rest') && !moves.has('sleeptalk') && - ability !== 'Natural Cure' && ability !== 'Shed Skin' - ) { - return 'Chesto Berry'; - } - if ( - species.id !== 'yanmega' && ability !== 'Mountaineer' && - this.dex.getEffectiveness('Rock', species) >= 2 && (!types.includes('Flying') || !isDoubles) - ) return 'Heavy-Duty Boots'; - } - - /** Item generation specific to Random Doubles */ - getDoublesItem( - ability: string, - types: string[], - moves: Set, - counter: MoveCounter, - teamDetails: RandomTeamsTypes.TeamDetails, - species: Species, - isLead: boolean, - teraType: string, - role: RandomTeamsTypes.Role, - ): string { - const scarfReqs = ( - !counter.get('priority') && ability !== 'Speed Boost' && role !== 'Doubles Wallbreaker' && - species.baseStats.spe >= 60 && species.baseStats.spe <= 108 && - this.randomChance(1, 2) - ); - const offensiveRole = ( - ['Doubles Fast Attacker', 'Doubles Wallbreaker', 'Doubles Setup Sweeper', 'Offensive Protect'].some(m => role === m) - ); - - if (species.id === 'ursalunabloodmoon' || (moves.has('doubleedge') && moves.has('fakeout'))) return 'Silk Scarf'; - if ( - moves.has('flipturn') && moves.has('protect') && (moves.has('aquajet') || (moves.has('jetpunch'))) - ) return 'Mystic Water'; - if (counter.get('speedsetup') && role === 'Doubles Bulky Setup') return 'Weakness Policy'; - if (species.id === 'toxapex') return 'Binding Band'; - if (moves.has('blizzard') && ability !== 'Snow Warning' && !teamDetails.snow) return 'Blunder Policy'; - - if (role === 'Choice Item user') { - if (scarfReqs || (counter.get('Physical') < 4 && counter.get('Special') < 3 && !moves.has('memento'))) { - return 'Choice Scarf'; - } - return (counter.get('Physical') >= 3) ? 'Choice Band' : 'Choice Specs'; - } - if (counter.get('Physical') >= 4 && - ['fakeout', 'feint', 'firstimpression', 'rapidspin', 'suckerpunch'].every(m => !moves.has(m)) && - (moves.has('flipturn') || moves.has('uturn') || role === 'Doubles Wallbreaker') - ) { - return (scarfReqs) ? 'Choice Scarf' : 'Choice Band'; - } - if ( - ((counter.get('Special') >= 4 && (moves.has('voltswitch') || role === 'Doubles Wallbreaker')) || ( - counter.get('Special') >= 3 && (moves.has('uturn') || moves.has('flipturn')) - )) && !moves.has('electroweb') - ) { - return (scarfReqs) ? 'Choice Scarf' : 'Choice Specs'; - } - if ( - (role === 'Bulky Protect' && counter.get('setup')) || moves.has('substitute') || moves.has('irondefense') || - species.id === 'eternatus' || species.id === 'regigigas' - ) return 'Leftovers'; - if (species.id === 'sylveon') return 'Pixie Plate'; - if (ability === 'Intimidate' && this.dex.getEffectiveness('Rock', species) >= 1) return 'Heavy-Duty Boots'; - if ( - (offensiveRole || (role === 'Tera Blast user' && (species.baseStats.spe >= 80 || moves.has('trickroom')))) && - (!moves.has('fakeout') || species.id === 'ambipom') && !moves.has('incinerate') && - (!moves.has('uturn') || types.includes('Bug') || ability === 'Libero') && - ((!moves.has('icywind') && !moves.has('electroweb')) || species.id === 'ironbundle') - ) { - return ( - (ability === 'Quark Drive' || ability === 'Protosynthesis') && !isLead && species.id !== 'ironvaliant' && - ['dracometeor', 'firstimpression', 'uturn', 'voltswitch'].every(m => !moves.has(m)) - ) ? 'Booster Energy' : 'Life Orb'; - } - if (isLead && (species.id === 'glimmora' || - (['Doubles Fast Attacker', 'Doubles Wallbreaker', 'Offensive Protect'].includes(role) && - species.baseStats.hp + species.baseStats.def + species.baseStats.spd <= 230)) - ) return 'Focus Sash'; - if ( - ['Doubles Fast Attacker', 'Doubles Wallbreaker', 'Offensive Protect'].includes(role) && - moves.has('fakeout') || moves.has('incinerate') - ) { - return (this.dex.getEffectiveness('Rock', species) >= 1) ? 'Heavy-Duty Boots' : 'Clear Amulet'; - } - if (!counter.get('Status')) return 'Assault Vest'; - return 'Sitrus Berry'; - } - - getItem( - ability: string, - types: string[], - moves: Set, - counter: MoveCounter, - teamDetails: RandomTeamsTypes.TeamDetails, - species: Species, - isLead: boolean, - teraType: string, - role: RandomTeamsTypes.Role, - ): string { - if (types.includes('Normal') && moves.has('fakeout')) return 'Silk Scarf'; - if ( - species.id !== 'jirachi' && (counter.get('Physical') >= 4) && - ['dragontail', 'fakeout', 'firstimpression', 'flamecharge', 'rapidspin'].every(m => !moves.has(m)) - ) { - const scarfReqs = ( - role !== 'Wallbreaker' && - (species.baseStats.atk >= 100 || ability === 'Huge Power' || ability === 'Pure Power') && - species.baseStats.spe >= 60 && species.baseStats.spe <= 108 && - ability !== 'Speed Boost' && !counter.get('priority') && !moves.has('aquastep') - ); - return (scarfReqs && this.randomChance(1, 2)) ? 'Choice Scarf' : 'Choice Band'; - } - if ( - (counter.get('Special') >= 4) || - (counter.get('Special') >= 3 && ['flipturn', 'uturn'].some(m => moves.has(m))) - ) { - const scarfReqs = ( - role !== 'Wallbreaker' && - species.baseStats.spa >= 100 && - species.baseStats.spe >= 60 && species.baseStats.spe <= 108 && - ability !== 'Speed Boost' && ability !== 'Tinted Lens' && !moves.has('uturn') && !counter.get('priority') - ); - return (scarfReqs && this.randomChance(1, 2)) ? 'Choice Scarf' : 'Choice Specs'; - } - if (counter.get('speedsetup') && role === 'Bulky Setup') return 'Weakness Policy'; - if ( - !counter.get('Status') && - !['Fast Attacker', 'Wallbreaker', 'Tera Blast user'].includes(role) - && species.id !== 'furumo') { - return 'Assault Vest'; - } - if (species.id === 'golem') return (counter.get('speedsetup')) ? 'Weakness Policy' : 'Custap Berry'; - if (moves.has('substitute')) return 'Leftovers'; - if (moves.has('stickyweb') && species.id !== 'araquanid' && isLead) return 'Focus Sash'; - if (this.dex.getEffectiveness('Rock', species) >= 1) return 'Heavy-Duty Boots'; - if ( - (moves.has('chillyreception') || ( - role === 'Fast Support' && - [...PIVOT_MOVES, 'defog', 'mortalspin', 'rapidspin'].some(m => moves.has(m)) && - !types.includes('Flying') && ability !== 'Levitate' - )) - ) return 'Heavy-Duty Boots'; - - // Low Priority - if ( - ability === 'Rough Skin' || ( - ability === 'Regenerator' && (role === 'Bulky Support' || role === 'Bulky Attacker') && - (species.baseStats.hp + species.baseStats.def) >= 180 && this.randomChance(1, 2) - ) || ( - ability !== 'Regenerator' && !counter.get('setup') && counter.get('recovery') && - this.dex.getEffectiveness('Fighting', species) < 1 && - (species.baseStats.hp + species.baseStats.def) > 200 && this.randomChance(1, 2) - ) - ) return 'Rocky Helmet'; - if (moves.has('outrage') && counter.get('setup')) return 'Lum Berry'; - if (moves.has('protect') && ability !== 'Speed Boost') return 'Leftovers'; - if ( - role === 'Fast Support' && isLead && !counter.get('recovery') && !counter.get('recoil') && - (counter.get('hazards') || counter.get('setup')) && - (species.baseStats.hp + species.baseStats.def + species.baseStats.spd) < 258 - ) return 'Focus Sash'; - if ( - !counter.get('setup') && ability !== 'Levitate' && this.dex.getEffectiveness('Ground', species) >= 2 - ) return 'Air Balloon'; - if (['Bulky Attacker', 'Bulky Support', 'Bulky Setup'].some(m => role === (m))) return 'Leftovers'; - if (species.id === 'pawmot' && moves.has('nuzzle')) return 'Leppa Berry'; - if (role === 'Fast Support' || role === 'Fast Bulky Setup') { - return (counter.get('Physical') + counter.get('Special') >= 3 && !moves.has('nuzzle')) ? 'Life Orb' : 'Leftovers'; - } - if (role === 'Tera Blast user' && DEFENSIVE_TERA_BLAST_USERS.includes(species.id)) return 'Leftovers'; - if ( - ['flamecharge', 'rapidspin', 'trailblaze'].every(m => !moves.has(m)) && - ['Fast Attacker', 'Setup Sweeper', 'Tera Blast user', 'Wallbreaker'].some(m => role === (m)) - ) return 'Life Orb'; - return 'Leftovers'; - } - - getLevel( - species: Species, - isDoubles: boolean, - ): number { - if (this.adjustLevel) return this.adjustLevel; - // doubles levelling - if (isDoubles && this.randomDoublesSets[species.id]["level"]) return this.randomDoublesSets[species.id]["level"]!; - if (!isDoubles && this.randomSets[species.id]["level"]) return this.randomSets[species.id]["level"]!; - // Default to tier-based levelling - const tier = species.tier; - const tierScale: Partial> = { - Uber: 76, - OU: 80, - UUBL: 81, - UU: 82, - RUBL: 83, - RU: 84, - NUBL: 85, - NU: 86, - PUBL: 87, - PU: 88, "(PU)": 88, NFE: 88, - }; - return tierScale[tier] || 80; - } - - getForme(species: Species): string { - if (typeof species.battleOnly === 'string') { - // Only change the forme. The species has custom moves, and may have different typing and requirements. - return species.battleOnly; - } - if (species.cosmeticFormes) return this.sample([species.name].concat(species.cosmeticFormes)); - - // Consolidate mostly-cosmetic formes, at least for the purposes of Random Battles - if (['Dudunsparce', 'Magearna', 'Maushold', 'Polteageist', 'Sinistcha', 'Zarude'].includes(species.baseSpecies)) { - return this.sample([species.name].concat(species.otherFormes!)); - } - if (species.baseSpecies === 'Basculin') return 'Basculin' + this.sample(['', '-Blue-Striped']); - if (species.baseSpecies === 'Pikachu') { - return 'Pikachu' + this.sample( - ['', '-Original', '-Hoenn', '-Sinnoh', '-Unova', '-Kalos', '-Alola', '-Partner', '-World'] - ); - } - return species.name; + if (species.id === 'magneton') return this.sample(['Air Balloon', 'Chople Berry']); + if (species.id === 'delibird') return 'Heavy-Duty Boots'; + if (species.id === 'hitmontop') return this.sample(['Protective Pads', 'Wide Lens']); + if (species.id === 'articunogalar' && moves.has('roost')) return 'Heavy-Duty Boots'; + if (species.id === 'articunogalar' && moves.has('aurasphere')) return 'Choice Specs'; + if (species.id === 'vaporeon') return 'Flame Orb'; + if (species.id === 'garganacl') return 'Poisonium Z'; + if (species.id === 'swanna') return 'Heavy-Duty Boots'; + if (species.id === 'terapagos') return 'Leftovers'; + if (species.id === 'flapple') return 'Tart Apple'; + if (species.id === 'genesectburn' && moves.has('sunsteelstrike')) return 'Burn Drive'; + if (species.id === 'genesectchill' && moves.has('behemothblade')) return 'Chill Drive'; + if (species.id === 'genesectdouse' && moves.has('makeitrain')) return 'Douse Drive'; + if (species.id === 'genesectshock' && moves.has('tachyoncutter')) return 'Shock Drive'; + if (species.id === 'honchkrow') return 'Heavy-Duty Boots'; + if (species.id === 'primeape') return 'Eviolite'; + if (species.id === 'rillaboom') return 'Heavy-Duty Boots'; + if (species.id === 'mandibuzz') return 'Thick Club'; + if (species.id === 'feraligatr') return 'Life Orb'; + if (species.id === 'salazzle') return 'Heavy-Duty Boots'; + if (species.id === 'kyogre') return 'Waterium Z'; + if (species.id === 'azelf') return 'Focus Band'; + if (species.id === 'decidueye') return this.sample(['Life Orb', 'Heavy-Duty Boots', "Leftovers"]); + if (species.id === 'ogerponcornerstone') return 'Cornerstone Mask'; + if (species.id === 'glimmora' && moves.has('meteorbeam')) return 'Power Herb'; + if (species.id === 'glimmora') return 'Air Balloon'; + if (species.id === 'wobbuffet') return 'Covert Cloak'; } - randomSet( + override randomSet( s: string | Species, teamDetails: RandomTeamsTypes.TeamDetails = {}, isLead = false, @@ -1573,7 +737,7 @@ export class RandomTeams { ): RandomTeamsTypes.RandomSet { const species = this.dex.species.get(s); const forme = this.getForme(species); - const sets = this[`random${isDoubles ? 'Doubles' : ''}Sets`][species.id]["sets"]; + const sets = this.randomSets[species.id]["sets"]; const possibleSets: RandomTeamsTypes.RandomSetData[] = []; const ruleTable = this.dex.formats.getRuleTable(this.format); @@ -1603,8 +767,8 @@ export class RandomTeams { let ability = ''; let item = undefined; - const evs = {hp: 85, atk: 85, def: 85, spa: 85, spd: 85, spe: 85}; - const ivs = {hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31}; + const evs = { hp: 85, atk: 85, def: 85, spa: 85, spd: 85, spe: 85 }; + const ivs = { hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31 }; const types = species.types; const abilities = set.abilities!; @@ -1668,7 +832,8 @@ export class RandomTeams { return move.category !== 'Physical' || move.id === 'bodypress' || move.id === 'foulplay'; }); // prevents Illumise (who can turn into Volbeat with Physical moves) from having 0 Atk EVs - if (noAttackStatMoves && !moves.has('transform') && this.format.mod !== 'partnersincrime' && species.id !== 'illumise') { + if (noAttackStatMoves && !moves.has('transform') && this.format.mod !== 'partnersincrime' && + species.id !== 'illumise') { evs.atk = 0; ivs.atk = 0; } @@ -1687,7 +852,7 @@ export class RandomTeams { return { name: species.baseSpecies, species: forme, - gender: species.baseSpecies === 'Greninja' ? 'M' : species.gender, + gender: species.baseSpecies === 'Greninja' ? 'M' : (species.gender || (this.random(2) ? 'F' : 'M')), shiny: this.randomChance(1, 1024), level, moves: shuffledMoves, @@ -1700,48 +865,12 @@ export class RandomTeams { }; } - getPokemonPool( - type: string, - pokemonToExclude: RandomTeamsTypes.RandomSet[] = [], - isMonotype = false, - pokemonList: string[] - ): [{[k: string]: string[]}, string[]] { - const exclude = pokemonToExclude.map(p => toID(p.species)); - const pokemonPool: {[k: string]: string[]} = {}; - const baseSpeciesPool = []; - for (const pokemon of pokemonList) { - let species = this.dex.species.get(pokemon); - if (exclude.includes(species.id)) continue; - if (isMonotype) { - if (!species.types.includes(type)) continue; - if (typeof species.battleOnly === 'string') { - species = this.dex.species.get(species.battleOnly); - if (!species.types.includes(type)) continue; - } - } + override randomSets: { [species: string]: RandomTeamsTypes.RandomSpeciesData } = require('./random-sets.json'); - if (species.baseSpecies in pokemonPool) { - pokemonPool[species.baseSpecies].push(pokemon); - } else { - pokemonPool[species.baseSpecies] = [pokemon]; - } - } - // Include base species 1x if 1-3 formes, 2x if 4-6 formes, 3x if 7+ formes - for (const baseSpecies of Object.keys(pokemonPool)) { - // Squawkabilly has 4 formes, but only 2 functionally different formes, so only include it 1x - const weight = (baseSpecies === 'Squawkabilly') ? 1 : Math.min(Math.ceil(pokemonPool[baseSpecies].length / 3), 3); - for (let i = 0; i < weight; i++) baseSpeciesPool.push(baseSpecies); - } - return [pokemonPool, baseSpeciesPool]; - } - - randomSets: {[species: string]: RandomTeamsTypes.RandomSpeciesData} = require('./random-sets.json'); - //randomDoublesSets: {[species: string]: RandomTeamsTypes.RandomSpeciesData} = require('./random-doubles-sets.json'); - - randomTeam() { + randomChatBatsTeam() { this.enforceNoDirectCustomBanlistChanges(); - const seed = this.prng.seed; + const seed = this.prng.getSeed(); const ruleTable = this.dex.formats.getRuleTable(this.format); const pokemon: RandomTeamsTypes.RandomSet[] = []; @@ -1752,33 +881,33 @@ export class RandomTeams { const type = this.forceMonotype || this.sample(typePool); // PotD stuff - const usePotD = global.Config && Config.potd && ruleTable.has('potd'); - const potd = usePotD ? this.dex.species.get(Config.potd) : null; + // const usePotD = global.Config && Config.potd && ruleTable.has('potd'); + // const potd = usePotD ? this.dex.species.get(Config.potd) : null; - const baseFormes: {[k: string]: number} = {}; + const baseFormes: { [k: string]: number } = {}; let hasMega = false; - const typeCount: {[k: string]: number} = {}; - const typeComboCount: {[k: string]: number} = {}; - const typeWeaknesses: {[k: string]: number} = {}; - const typeDoubleWeaknesses: {[k: string]: number} = {}; + const typeCount: { [k: string]: number } = {}; + const typeComboCount: { [k: string]: number } = {}; + const typeWeaknesses: { [k: string]: number } = {}; + const typeDoubleWeaknesses: { [k: string]: number } = {}; const teamDetails: RandomTeamsTypes.TeamDetails = {}; let numMaxLevelPokemon = 0; - const pokemonList = isDoubles ? Object.keys(this.randomDoublesSets) : Object.keys(this.randomSets); + const pokemonList = Object.keys(this.randomSets); const [pokemonPool, baseSpeciesPool] = this.getPokemonPool(type, pokemon, isMonotype, pokemonList); let leadsRemaining = this.format.gameType === 'doubles' ? 2 : 1; while (baseSpeciesPool.length && pokemon.length < this.maxTeamSize) { const baseSpecies = this.sampleNoReplace(baseSpeciesPool); - + if (hasMega && (baseSpecies === "Typhlosion" || baseSpecies === "Altaria" || baseSpecies === "Raticate")) continue; const currentSpeciesPool: Species[] = []; // Check if the base species has a mega forme available - let canMega = false; - for (const poke of pokemonPool[baseSpecies]) { - const species = this.dex.species.get(poke); - if (!hasMega && species.isMega) canMega = true; - } + // let canMega = false; + // for (const poke of pokemonPool[baseSpecies]) { + // const species = this.dex.species.get(poke); + // if (!hasMega && species.isMega) canMega = true; + // } for (const poke of pokemonPool[baseSpecies]) { const species = this.dex.species.get(poke); // Prevent multiple megas @@ -1788,13 +917,13 @@ export class RandomTeams { // if (canMega && !species.isMega && species.id !== 'abomasnow') continue; currentSpeciesPool.push(species); } - // change let to const when not testing - let species = this.sample(currentSpeciesPool); + // change const to let when enforcing certain mons for testing + const species = this.sample(currentSpeciesPool); - //let species = this.dex.species.get(this.sample(pokemonPool[baseSpecies])); + // let species = this.dex.species.get(this.sample(pokemonPool[baseSpecies])); if (!species.exists) continue; - + // Limit to one of each species (Species Clause) if (baseFormes[species.baseSpecies]) continue; @@ -1824,7 +953,7 @@ export class RandomTeams { // Limit two of any type // ADJUSTING TO 6 -- ADJUST BACK AFTER MORE POKEMON HAVE BEEN ADDED for (const typeName of types) { - if (typeCount[typeName] >= 2 /*6*/ * limitFactor) { + if (typeCount[typeName] >= 2 /* 6 */ * limitFactor) { skip = true; break; } @@ -1837,14 +966,14 @@ export class RandomTeams { // it's weak to the type if (this.dex.getEffectiveness(typeName, species) > 0) { if (!typeWeaknesses[typeName]) typeWeaknesses[typeName] = 0; - if (typeWeaknesses[typeName] >= 3 /*6*/ * limitFactor) { + if (typeWeaknesses[typeName] >= 3 /* 6 */ * limitFactor) { skip = true; break; } } if (this.dex.getEffectiveness(typeName, species) > 1) { if (!typeDoubleWeaknesses[typeName]) typeDoubleWeaknesses[typeName] = 0; - if (typeDoubleWeaknesses[typeName] >= 1 /*6*/ * limitFactor) { + if (typeDoubleWeaknesses[typeName] >= 1 /* 6 */ * Number(limitFactor)) { skip = true; break; } @@ -1859,14 +988,14 @@ export class RandomTeams { Object.values(species.abilities).filter(a => ['Dry Skin', 'Fluffy'].includes(a)).length ) { if (!typeWeaknesses['Fire']) typeWeaknesses['Fire'] = 0; - if (typeWeaknesses['Fire'] >= 3 /*6*/ * limitFactor) continue; + if (typeWeaknesses['Fire'] >= 3 /* 6 */ * limitFactor) continue; } // Limit four weak to Freeze-Dry // ADJUSTING TO 6 -- ADJUST BACK AFTER MORE POKEMON HAVE BEEN ADDED if (weakToFreezeDry) { if (!typeWeaknesses['Freeze-Dry']) typeWeaknesses['Freeze-Dry'] = 0; - if (typeWeaknesses['Freeze-Dry'] >= 4 /*6*/ * limitFactor) continue; + if (typeWeaknesses['Freeze-Dry'] >= 4 /* 6 */ * limitFactor) continue; } // Limit one level 100 Pokemon @@ -1879,13 +1008,11 @@ export class RandomTeams { if (!this.forceMonotype && isMonotype && (typeComboCount[typeCombo] >= 3 * limitFactor)) continue; // The Pokemon of the Day - //if (potd?.exists && (pokemon.length === 1 || this.maxTeamSize === 1)) species = potd; - - // Code to enforce a mon on teams for testing - //if (pokemon.length === 1 || this.maxTeamSize === 1) species = 'dachsbun'; - //if (pokemon.length === 2 || this.maxTeamSize === 1) species = 'ogerponhearthflame'; - if (pokemon.length === 3 || this.maxTeamSize === 1) species = 'mew'; - + // if (potd?.exists && (pokemon.length === 1 || this.maxTeamSize === 1)) species = potd; + + // testing code + // if (pokemon.length === 0 || this.maxTeamSize === 1) species = this.dex.species.get('Raticate-Mega'); + let set: RandomTeamsTypes.RandomSet; if (leadsRemaining) { @@ -1979,809 +1106,6 @@ export class RandomTeams { return pokemon; } - - randomCCTeam(): RandomTeamsTypes.RandomSet[] { - this.enforceNoDirectCustomBanlistChanges(); - - const dex = this.dex; - const team = []; - - const natures = this.dex.natures.all(); - const items = this.dex.items.all(); - - const randomN = this.randomNPokemon(this.maxTeamSize, this.forceMonotype, undefined, undefined, true); - - for (let forme of randomN) { - let species = dex.species.get(forme); - if (species.isNonstandard) species = dex.species.get(species.baseSpecies); - - // Random legal item - let item = ''; - let isIllegalItem; - let isBadItem; - if (this.gen >= 2) { - do { - item = this.sample(items).name; - isIllegalItem = this.dex.items.get(item).gen > this.gen || this.dex.items.get(item).isNonstandard; - isBadItem = item.startsWith("TR") || this.dex.items.get(item).isPokeball; - } while (isIllegalItem || (isBadItem && this.randomChance(19, 20))); - } - - // Make sure forme is legal - if (species.battleOnly) { - if (typeof species.battleOnly === 'string') { - species = dex.species.get(species.battleOnly); - } else { - species = dex.species.get(this.sample(species.battleOnly)); - } - forme = species.name; - } else if (species.requiredItems && !species.requiredItems.some(req => toID(req) === item)) { - if (!species.changesFrom) throw new Error(`${species.name} needs a changesFrom value`); - species = dex.species.get(species.changesFrom); - forme = species.name; - } - - // Make sure that a base forme does not hold any forme-modifier items. - let itemData = this.dex.items.get(item); - if (itemData.forcedForme && forme === this.dex.species.get(itemData.forcedForme).baseSpecies) { - do { - itemData = this.sample(items); - item = itemData.name; - } while ( - itemData.gen > this.gen || - itemData.isNonstandard || - (itemData.forcedForme && forme === this.dex.species.get(itemData.forcedForme).baseSpecies) - ); - } - - // Random legal ability - const abilities = Object.values(species.abilities).filter(a => this.dex.abilities.get(a).gen <= this.gen); - const ability: string = this.gen <= 2 ? 'No Ability' : this.sample(abilities); - - // Four random unique moves from the movepool - let pool = ['struggle']; - if (forme === 'Smeargle') { - pool = this.dex.moves.all() - .filter(move => !(move.isNonstandard || move.isZ || move.isMax || move.realMove)) - .map(m => m.id); - } else { - pool = [...this.dex.species.getMovePool(species.id)]; - } - - const moves = this.multipleSamplesNoReplace(pool, this.maxMoveCount); - - // Random EVs - const evs: StatsTable = {hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0}; - const s: StatID[] = ["hp", "atk", "def", "spa", "spd", "spe"]; - let evpool = 510; - do { - const x = this.sample(s); - const y = this.random(Math.min(256 - evs[x], evpool + 1)); - evs[x] += y; - evpool -= y; - } while (evpool > 0); - - // Random IVs - const ivs = { - hp: this.random(32), - atk: this.random(32), - def: this.random(32), - spa: this.random(32), - spd: this.random(32), - spe: this.random(32), - }; - - // Random nature - const nature = this.sample(natures).name; - - // Level balance--calculate directly from stats rather than using some silly lookup table - const mbstmin = 1307; // Sunkern has the lowest modified base stat total, and that total is 807 - - let stats = species.baseStats; - // If Wishiwashi, use the school-forme's much higher stats - if (species.baseSpecies === 'Wishiwashi') stats = Dex.species.get('wishiwashischool').baseStats; - // If Terapagos, use Terastal-forme's stats - if (species.baseSpecies === 'Terapagos') stats = Dex.species.get('terapagosterastal').baseStats; - - // Modified base stat total assumes 31 IVs, 85 EVs in every stat - let mbst = (stats["hp"] * 2 + 31 + 21 + 100) + 10; - mbst += (stats["atk"] * 2 + 31 + 21 + 100) + 5; - mbst += (stats["def"] * 2 + 31 + 21 + 100) + 5; - mbst += (stats["spa"] * 2 + 31 + 21 + 100) + 5; - mbst += (stats["spd"] * 2 + 31 + 21 + 100) + 5; - mbst += (stats["spe"] * 2 + 31 + 21 + 100) + 5; - - let level; - if (this.adjustLevel) { - level = this.adjustLevel; - } else { - level = Math.floor(100 * mbstmin / mbst); // Initial level guess will underestimate - - while (level < 100) { - mbst = Math.floor((stats["hp"] * 2 + 31 + 21 + 100) * level / 100 + 10); - // Since damage is roughly proportional to level - mbst += Math.floor(((stats["atk"] * 2 + 31 + 21 + 100) * level / 100 + 5) * level / 100); - mbst += Math.floor((stats["def"] * 2 + 31 + 21 + 100) * level / 100 + 5); - mbst += Math.floor(((stats["spa"] * 2 + 31 + 21 + 100) * level / 100 + 5) * level / 100); - mbst += Math.floor((stats["spd"] * 2 + 31 + 21 + 100) * level / 100 + 5); - mbst += Math.floor((stats["spe"] * 2 + 31 + 21 + 100) * level / 100 + 5); - - if (mbst >= mbstmin) break; - level++; - } - } - - // Random happiness - const happiness = this.random(256); - - // Random shininess - const shiny = this.randomChance(1, 1024); - - const set: RandomTeamsTypes.RandomSet = { - name: species.baseSpecies, - species: species.name, - gender: species.gender, - item, - ability, - moves, - evs, - ivs, - nature, - level, - happiness, - shiny, - }; - if (this.gen === 9) { - // Tera type - if (this.forceTeraType) { - set.teraType = this.forceTeraType; - } else { - set.teraType = this.sample(this.dex.types.all()).name; - } - } - team.push(set); - } - - return team; - } - - randomNPokemon(n: number, requiredType?: string, minSourceGen?: number, ruleTable?: RuleTable, requireMoves = false) { - // Picks `n` random pokemon--no repeats, even among formes - // Also need to either normalize for formes or select formes at random - // Unreleased are okay but no CAP - if (requiredType && !this.dex.types.get(requiredType).exists) { - throw new Error(`"${requiredType}" is not a valid type.`); - } - - const isNotCustom = !ruleTable; - - const pool: number[] = []; - let speciesPool: Species[] = []; - if (isNotCustom) { - speciesPool = [...this.dex.species.all()]; - for (const species of speciesPool) { - if (species.isNonstandard && species.isNonstandard !== 'Unobtainable') continue; - if (requireMoves) { - const hasMovesInCurrentGen = this.dex.species.getMovePool(species.id).size; - if (!hasMovesInCurrentGen) continue; - } - if (requiredType && !species.types.includes(requiredType)) continue; - if (minSourceGen && species.gen < minSourceGen) continue; - const num = species.num; - if (num <= 0 || pool.includes(num)) continue; - pool.push(num); - } - } else { - const EXISTENCE_TAG = ['past', 'future', 'lgpe', 'unobtainable', 'cap', 'custom', 'nonexistent']; - const nonexistentBanReason = ruleTable.check('nonexistent'); - // Assume tierSpecies does not differ from species here (mega formes can be used without their stone, etc) - for (const species of this.dex.species.all()) { - if (requiredType && !species.types.includes(requiredType)) continue; - - let banReason = ruleTable.check('pokemon:' + species.id); - if (banReason) continue; - if (banReason !== '') { - if (species.isMega && ruleTable.check('pokemontag:mega')) continue; - - banReason = ruleTable.check('basepokemon:' + toID(species.baseSpecies)); - if (banReason) continue; - if (banReason !== '' || this.dex.species.get(species.baseSpecies).isNonstandard !== species.isNonstandard) { - const nonexistentCheck = Tags.nonexistent.genericFilter!(species) && nonexistentBanReason; - let tagWhitelisted = false; - let tagBlacklisted = false; - for (const ruleid of ruleTable.tagRules) { - if (ruleid.startsWith('*')) continue; - const tagid = ruleid.slice(12) as ID; - const tag = Tags[tagid]; - if ((tag.speciesFilter || tag.genericFilter)!(species)) { - const existenceTag = EXISTENCE_TAG.includes(tagid); - if (ruleid.startsWith('+')) { - if (!existenceTag && nonexistentCheck) continue; - tagWhitelisted = true; - break; - } - tagBlacklisted = true; - break; - } - } - if (tagBlacklisted) continue; - if (!tagWhitelisted) { - if (ruleTable.check('pokemontag:allpokemon')) continue; - } - } - } - speciesPool.push(species); - const num = species.num; - if (pool.includes(num)) continue; - pool.push(num); - } - } - - const hasDexNumber: {[k: string]: number} = {}; - for (let i = 0; i < n; i++) { - const num = this.sampleNoReplace(pool); - hasDexNumber[num] = i; - } - - const formes: string[][] = []; - for (const species of speciesPool) { - if (!(species.num in hasDexNumber)) continue; - if (isNotCustom && (species.gen > this.gen || - (species.isNonstandard && species.isNonstandard !== 'Unobtainable'))) continue; - if (requiredType && !species.types.includes(requiredType)) continue; - if (!formes[hasDexNumber[species.num]]) formes[hasDexNumber[species.num]] = []; - formes[hasDexNumber[species.num]].push(species.name); - } - - if (formes.length < n) { - throw new Error(`Legal Pokemon forme count insufficient to support Max Team Size: (${formes.length} / ${n}).`); - } - - const nPokemon = []; - for (let i = 0; i < n; i++) { - if (!formes[i].length) { - throw new Error(`Invalid pokemon gen ${this.gen}: ${JSON.stringify(formes)} numbers ${JSON.stringify(hasDexNumber)}`); - } - nPokemon.push(this.sample(formes[i])); - } - return nPokemon; - } - - randomHCTeam(): PokemonSet[] { - const hasCustomBans = this.hasDirectCustomBanlistChanges(); - const ruleTable = this.dex.formats.getRuleTable(this.format); - const hasNonexistentBan = hasCustomBans && ruleTable.check('nonexistent'); - const hasNonexistentWhitelist = hasCustomBans && (hasNonexistentBan === ''); - - if (hasCustomBans) { - this.enforceNoDirectComplexBans(); - } - - // Item Pool - const doItemsExist = this.gen > 1; - let itemPool: Item[] = []; - if (doItemsExist) { - if (!hasCustomBans) { - itemPool = [...this.dex.items.all()].filter(item => (item.gen <= this.gen && !item.isNonstandard)); - } else { - const hasAllItemsBan = ruleTable.check('pokemontag:allitems'); - for (const item of this.dex.items.all()) { - let banReason = ruleTable.check('item:' + item.id); - if (banReason) continue; - if (banReason !== '' && item.id) { - if (hasAllItemsBan) continue; - if (item.isNonstandard) { - banReason = ruleTable.check('pokemontag:' + toID(item.isNonstandard)); - if (banReason) continue; - if (banReason !== '' && item.isNonstandard !== 'Unobtainable') { - if (hasNonexistentBan) continue; - if (!hasNonexistentWhitelist) continue; - } - } - } - itemPool.push(item); - } - if (ruleTable.check('item:noitem')) { - this.enforceCustomPoolSizeNoComplexBans('item', itemPool, this.maxTeamSize, 'Max Team Size'); - } - } - } - - // Ability Pool - const doAbilitiesExist = (this.gen > 2) && (this.dex.currentMod !== 'gen7letsgo'); - let abilityPool: Ability[] = []; - if (doAbilitiesExist) { - if (!hasCustomBans) { - abilityPool = [...this.dex.abilities.all()].filter(ability => (ability.gen <= this.gen && !ability.isNonstandard)); - } else { - const hasAllAbilitiesBan = ruleTable.check('pokemontag:allabilities'); - for (const ability of this.dex.abilities.all()) { - let banReason = ruleTable.check('ability:' + ability.id); - if (banReason) continue; - if (banReason !== '') { - if (hasAllAbilitiesBan) continue; - if (ability.isNonstandard) { - banReason = ruleTable.check('pokemontag:' + toID(ability.isNonstandard)); - if (banReason) continue; - if (banReason !== '') { - if (hasNonexistentBan) continue; - if (!hasNonexistentWhitelist) continue; - } - } - } - abilityPool.push(ability); - } - if (ruleTable.check('ability:noability')) { - this.enforceCustomPoolSizeNoComplexBans('ability', abilityPool, this.maxTeamSize, 'Max Team Size'); - } - } - } - - // Move Pool - const setMoveCount = ruleTable.maxMoveCount; - let movePool: Move[] = []; - if (!hasCustomBans) { - movePool = [...this.dex.moves.all()].filter(move => - (move.gen <= this.gen && !move.isNonstandard)); - } else { - const hasAllMovesBan = ruleTable.check('pokemontag:allmoves'); - for (const move of this.dex.moves.all()) { - let banReason = ruleTable.check('move:' + move.id); - if (banReason) continue; - if (banReason !== '') { - if (hasAllMovesBan) continue; - if (move.isNonstandard) { - banReason = ruleTable.check('pokemontag:' + toID(move.isNonstandard)); - if (banReason) continue; - if (banReason !== '' && move.isNonstandard !== 'Unobtainable') { - if (hasNonexistentBan) continue; - if (!hasNonexistentWhitelist) continue; - } - } - } - movePool.push(move); - } - this.enforceCustomPoolSizeNoComplexBans('move', movePool, this.maxTeamSize * setMoveCount, 'Max Team Size * Max Move Count'); - } - - // Nature Pool - const doNaturesExist = this.gen > 2; - let naturePool: Nature[] = []; - if (doNaturesExist) { - if (!hasCustomBans) { - naturePool = [...this.dex.natures.all()]; - } else { - const hasAllNaturesBan = ruleTable.check('pokemontag:allnatures'); - for (const nature of this.dex.natures.all()) { - let banReason = ruleTable.check('nature:' + nature.id); - if (banReason) continue; - if (banReason !== '' && nature.id) { - if (hasAllNaturesBan) continue; - if (nature.isNonstandard) { - banReason = ruleTable.check('pokemontag:' + toID(nature.isNonstandard)); - if (banReason) continue; - if (banReason !== '' && nature.isNonstandard !== 'Unobtainable') { - if (hasNonexistentBan) continue; - if (!hasNonexistentWhitelist) continue; - } - } - } - naturePool.push(nature); - } - // There is no 'nature:nonature' rule so do not constrain pool size - } - } - - const randomN = this.randomNPokemon(this.maxTeamSize, this.forceMonotype, undefined, - hasCustomBans ? ruleTable : undefined); - - const team = []; - for (const forme of randomN) { - // Choose forme - const species = this.dex.species.get(forme); - - // Random unique item - let item = ''; - let itemData; - let isBadItem; - if (doItemsExist) { - // We discard TRs and Balls with 95% probability because of their otherwise overwhelming presence - do { - itemData = this.sampleNoReplace(itemPool); - item = itemData?.name; - isBadItem = item.startsWith("TR") || itemData.isPokeball; - } while (isBadItem && this.randomChance(19, 20) && itemPool.length > this.maxTeamSize); - } - - // Random unique ability - let ability = 'No Ability'; - let abilityData; - if (doAbilitiesExist) { - abilityData = this.sampleNoReplace(abilityPool); - ability = abilityData?.name; - } - - // Random unique moves - const m = []; - do { - const move = this.sampleNoReplace(movePool); - m.push(move.id); - } while (m.length < setMoveCount); - - // Random EVs - const evs = {hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0}; - if (this.gen === 6) { - let evpool = 510; - do { - const x = this.sample(Dex.stats.ids()); - const y = this.random(Math.min(256 - evs[x], evpool + 1)); - evs[x] += y; - evpool -= y; - } while (evpool > 0); - } else { - for (const x of Dex.stats.ids()) { - evs[x] = this.random(256); - } - } - - // Random IVs - const ivs: StatsTable = { - hp: this.random(32), - atk: this.random(32), - def: this.random(32), - spa: this.random(32), - spd: this.random(32), - spe: this.random(32), - }; - - // Random nature - let nature = ''; - if (doNaturesExist && (naturePool.length > 0)) { - nature = this.sample(naturePool).name; - } - - // Level balance - const mbstmin = 1307; - const stats = species.baseStats; - let mbst = (stats['hp'] * 2 + 31 + 21 + 100) + 10; - mbst += (stats['atk'] * 2 + 31 + 21 + 100) + 5; - mbst += (stats['def'] * 2 + 31 + 21 + 100) + 5; - mbst += (stats['spa'] * 2 + 31 + 21 + 100) + 5; - mbst += (stats['spd'] * 2 + 31 + 21 + 100) + 5; - mbst += (stats['spe'] * 2 + 31 + 21 + 100) + 5; - - let level; - if (this.adjustLevel) { - level = this.adjustLevel; - } else { - level = Math.floor(100 * mbstmin / mbst); - while (level < 100) { - mbst = Math.floor((stats['hp'] * 2 + 31 + 21 + 100) * level / 100 + 10); - mbst += Math.floor(((stats['atk'] * 2 + 31 + 21 + 100) * level / 100 + 5) * level / 100); - mbst += Math.floor((stats['def'] * 2 + 31 + 21 + 100) * level / 100 + 5); - mbst += Math.floor(((stats['spa'] * 2 + 31 + 21 + 100) * level / 100 + 5) * level / 100); - mbst += Math.floor((stats['spd'] * 2 + 31 + 21 + 100) * level / 100 + 5); - mbst += Math.floor((stats['spe'] * 2 + 31 + 21 + 100) * level / 100 + 5); - if (mbst >= mbstmin) break; - level++; - } - } - - // Random happiness - const happiness = this.random(256); - - // Random shininess - const shiny = this.randomChance(1, 1024); - - const set: PokemonSet = { - name: species.baseSpecies, - species: species.name, - gender: species.gender, - item, - ability, - moves: m, - evs, - ivs, - nature, - level, - happiness, - shiny, - }; - if (this.gen === 9) { - // Random Tera type - if (this.forceTeraType) { - set.teraType = this.forceTeraType; - } else { - set.teraType = this.sample(this.dex.types.all()).name; - } - } - team.push(set); - } - - return team; - } - - //randomBSSFactorySets: AnyObject = require("./bss-factory-sets.json"); - - randomBSSFactorySet( - species: Species, teamData: RandomTeamsTypes.FactoryTeamDetails - ): RandomTeamsTypes.RandomFactorySet | null { - const id = toID(species.name); - const setList = this.randomBSSFactorySets[id].sets; - - const movesMax: {[k: string]: number} = { - batonpass: 1, - stealthrock: 1, - toxicspikes: 1, - trickroom: 1, - auroraveil: 1, - }; - const weatherAbilities = ['drizzle', 'drought', 'snowwarning', 'sandstream']; - const terrainAbilities: {[k: string]: string} = { - electricsurge: "electric", - psychicsurge: "psychic", - grassysurge: "grassy", - seedsower: "grassy", - mistysurge: "misty", - }; - const terrainItemsRequire: {[k: string]: string} = { - electricseed: "electric", - psychicseed: "psychic", - grassyseed: "grassy", - mistyseed: "misty", - }; - - const maxWantsTera = 2; - - // Build a pool of eligible sets, given the team partners - // Also keep track of sets with moves the team requires - const effectivePool: { - set: BSSFactorySet, moveVariants?: number[], itemVariants?: number, abilityVariants?: number, - }[] = []; - - for (const curSet of setList) { - let reject = false; - - // limit to 2 dedicated tera users per team - if (curSet.wantsTera && teamData.wantsTeraCount && teamData.wantsTeraCount >= maxWantsTera) { - continue; - } - - // reject 2+ weather setters - if (teamData.weather && weatherAbilities.includes(curSet.ability)) { - continue; - } - - if (terrainAbilities[curSet.ability]) { - if (!teamData.terrain) teamData.terrain = []; - teamData.terrain.push(terrainAbilities[curSet.ability]); - } - - for (const item of curSet.item) { - if (terrainItemsRequire[item] && !teamData.terrain?.includes(terrainItemsRequire[item])) { - reject = true; // reject any sets with a seed item possible and no terrain setter to activate it - break; - } - } - - const curSetMoveVariants = []; - for (const move of curSet.moves) { - const variantIndex = this.random(move.length); - const moveId = toID(move[variantIndex]); - if (movesMax[moveId] && teamData.has[moveId] >= movesMax[moveId]) { - reject = true; - break; - } - curSetMoveVariants.push(variantIndex); - } - if (reject) continue; - const set = {set: curSet, moveVariants: curSetMoveVariants}; - effectivePool.push(set); - } - - if (!effectivePool.length) { - if (!teamData.forceResult) return null; - for (const curSet of setList) { - effectivePool.push({set: curSet}); - } - } - - // Sets have individual weight, choose one with weighted random selection - - let setData = this.sample(effectivePool); // Init with unweighted random set as fallback - - const total = effectivePool.reduce((a, b) => a + b.set.weight, 0); - const setRand = this.random(total); - - let cur = 0; - for (const set of effectivePool) { - cur += set.set.weight; - if (cur > setRand) { - setData = set; // Bingo! - break; - } - } - - const moves = []; - for (const [i, moveSlot] of setData.set.moves.entries()) { - moves.push(setData.moveVariants ? moveSlot[setData.moveVariants[i]] : this.sample(moveSlot)); - } - - return { - name: setData.set.species || species.baseSpecies, - species: setData.set.species, - teraType: (this.sampleIfArray(setData.set.teraType)), - gender: setData.set.gender || species.gender || (this.randomChance(1, 2) ? "M" : "F"), - item: this.sampleIfArray(setData.set.item) || "", - ability: this.sampleIfArray(setData.set.ability), - shiny: this.randomChance(1, 1024), - level: 50, - happiness: 255, - evs: {hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0, ...setData.set.evs}, - ivs: {hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31, ...setData.set.ivs}, - nature: setData.set.nature || "Serious", - moves, - wantsTera: setData.set.wantsTera, - }; - } - - - randomBSSFactoryTeam(side: PlayerOptions, depth = 0): RandomTeamsTypes.RandomFactorySet[] { - this.enforceNoDirectCustomBanlistChanges(); - - const forceResult = depth >= 4; - - const pokemon = []; - - const pokemonPool = Object.keys(this.randomBSSFactorySets); - - const teamData: TeamData = { - typeCount: {}, - typeComboCount: {}, - baseFormes: {}, - has: {}, - wantsTeraCount: 0, - forceResult: forceResult, - weaknesses: {}, - resistances: {}, - }; - const weatherAbilitiesSet: {[k: string]: string} = { - drizzle: "raindance", - drought: "sunnyday", - snowwarning: "hail", - sandstream: "sandstorm", - }; - const resistanceAbilities: {[k: string]: string[]} = { - waterabsorb: ["Water"], - flashfire: ["Fire"], - lightningrod: ["Electric"], - voltabsorb: ["Electric"], - thickfat: ["Ice", "Fire"], - levitate: ["Ground"], - }; - const limitFactor = Math.ceil(this.maxTeamSize / 6); - /** - * Weighted random shuffle - * Uses the fact that for two uniform variables x1 and x2, x1^(1/w1) is larger than x2^(1/w2) - * with probability equal to w1/(w1+w2), which is what we want. See e.g. here https://arxiv.org/pdf/1012.0256.pdf, - * original paper is behind a paywall. - */ - const shuffledSpecies = []; - for (const speciesName of pokemonPool) { - const sortObject = { - speciesName, - score: Math.pow(this.prng.next(), 1 / this.randomBSSFactorySets[speciesName].weight), - }; - shuffledSpecies.push(sortObject); - } - shuffledSpecies.sort((a, b) => a.score - b.score); - - while (shuffledSpecies.length && pokemon.length < this.maxTeamSize) { - // repeated popping from weighted shuffle is equivalent to repeated weighted sampling without replacement - const species = this.dex.species.get(shuffledSpecies.pop()!.speciesName); - if (!species.exists) continue; - - if (this.forceMonotype && !species.types.includes(this.forceMonotype)) continue; - - // Limit to one of each species (Species Clause) - if (teamData.baseFormes[species.baseSpecies]) continue; - - // Limit 2 of any type (most of the time) - const types = species.types; - let skip = false; - if (!this.forceMonotype) { - for (const type of types) { - if (teamData.typeCount[type] >= 2 * limitFactor && this.randomChance(4, 5)) { - skip = true; - break; - } - } - } - if (skip) continue; - - const set = this.randomBSSFactorySet(species, teamData); - if (!set) continue; - - // Limit 1 of any type combination - let typeCombo = types.slice().sort().join(); - if (set.ability === "Drought" || set.ability === "Drizzle") { - // Drought and Drizzle don't count towards the type combo limit - typeCombo = set.ability; - } - if (!this.forceMonotype && teamData.typeComboCount[typeCombo] >= limitFactor) continue; - - const itemData = this.dex.items.get(set.item); - if (teamData.has[itemData.id]) continue; // Item Clause - - // Okay, the set passes, add it to our team - pokemon.push(set); - - // Now that our Pokemon has passed all checks, we can update team data: - for (const type of types) { - if (type in teamData.typeCount) { - teamData.typeCount[type]++; - } else { - teamData.typeCount[type] = 1; - } - } - if (typeCombo in teamData.typeComboCount) { - teamData.typeComboCount[typeCombo]++; - } else { - teamData.typeComboCount[typeCombo] = 1; - } - - teamData.baseFormes[species.baseSpecies] = 1; - - teamData.has[itemData.id] = 1; - - if (set.wantsTera) { - if (!teamData.wantsTeraCount) teamData.wantsTeraCount = 0; - teamData.wantsTeraCount++; - } - - const abilityState = this.dex.abilities.get(set.ability); - if (abilityState.id in weatherAbilitiesSet) { - teamData.weather = weatherAbilitiesSet[abilityState.id]; - } - - for (const move of set.moves) { - const moveId = toID(move); - if (moveId in teamData.has) { - teamData.has[moveId]++; - } else { - teamData.has[moveId] = 1; - } - } - - for (const typeName of this.dex.types.names()) { - // Cover any major weakness (3+) with at least one resistance - if (teamData.resistances[typeName] >= 1) continue; - if (resistanceAbilities[abilityState.id]?.includes(typeName) || !this.dex.getImmunity(typeName, types)) { - // Heuristic: assume that Pokémon with these abilities don't have (too) negative typing. - teamData.resistances[typeName] = (teamData.resistances[typeName] || 0) + 1; - if (teamData.resistances[typeName] >= 1) teamData.weaknesses[typeName] = 0; - continue; - } - const typeMod = this.dex.getEffectiveness(typeName, types); - if (typeMod < 0) { - teamData.resistances[typeName] = (teamData.resistances[typeName] || 0) + 1; - if (teamData.resistances[typeName] >= 1) teamData.weaknesses[typeName] = 0; - } else if (typeMod > 0) { - teamData.weaknesses[typeName] = (teamData.weaknesses[typeName] || 0) + 1; - } - } - } - if (!teamData.forceResult && pokemon.length < this.maxTeamSize) return this.randomBSSFactoryTeam(side, ++depth); - - // Quality control we cannot afford for monotype - if (!teamData.forceResult && !this.forceMonotype) { - for (const type in teamData.weaknesses) { - if (teamData.weaknesses[type] >= 3 * limitFactor) return this.randomBSSFactoryTeam(side, ++depth); - } - } - - return pokemon; - } } -export default RandomTeams; +export default RandomChatBatsTeams; diff --git a/sim/battle.ts b/sim/battle.ts index 3a464b7d92..6f9fa58f56 100644 --- a/sim/battle.ts +++ b/sim/battle.ts @@ -3137,6 +3137,10 @@ export class Battle { return this.sides[parseInt(sideid[1]) - 1]; } + getOverflowedTurnCount(): number { + return this.gen >= 8 ? (this.turn - 1) % 256 : this.turn - 1; + } + destroy() { // deallocate ourself