Spaces:
Paused
Paused
| import { type SSBSet, ssbSets } from "../../../data/mods/gen9ssb/random-teams"; | |
| import { Utils } from "../../../lib"; | |
| import { formatNature, STAT_NAMES } from "."; | |
| function generateSSBSet(set: SSBSet, dex: ModdedDex, baseDex: ModdedDex) { | |
| if (set.skip) { | |
| const baseSet = toID(Object.values(ssbSets[set.skip]).join()); | |
| const skipSet = toID(Object.values(set).join()).slice(0, -toID(set.skip).length); | |
| if (baseSet === skipSet) return ``; | |
| } | |
| let buf = ``; | |
| buf += `<details><summary>Set</summary>`; | |
| buf += `<ul style="list-style-type:none;"><li>${set.species}${set.gender !== '' ? ` (${set.gender})` : ``}`; | |
| buf += `${set.item ? ' @ ' : ''}${Array.isArray(set.item) ? set.item.map(x => dex.items.get(x).name).join(' / ') : dex.items.get(set.item).name}</li>`; | |
| buf += `<li>Ability: ${Array.isArray(set.ability) ? set.ability.map(x => dex.abilities.get(x).name).join(' / ') : dex.abilities.get(set.ability).name}</li>`; | |
| if (set.teraType) { | |
| buf += `<li>Tera Type: ${Array.isArray(set.teraType) ? set.teraType.map(x => dex.types.get(x).name).join(' / ') : set.teraType === 'Any' ? 'Any' : dex.types.get(set.teraType).name}</li>`; | |
| } | |
| if (set.shiny) buf += `<li>Shiny: ${typeof set.shiny === 'number' ? `1 in ${set.shiny} chance` : `Yes`}</li>`; | |
| if (set.evs) { | |
| const evs: string[] = []; | |
| let ev: StatID; | |
| for (ev in set.evs) { | |
| if (set.evs[ev] === 0) continue; | |
| evs.push(`${set.evs[ev]} ${STAT_NAMES[ev]}`); | |
| } | |
| buf += `<li>EVs: ${evs.join(" / ")}</li>`; | |
| } | |
| if (set.nature) { | |
| buf += `<li>${Array.isArray(set.nature) ? set.nature.map(x => formatNature(x)).join(" / ") : formatNature(set.nature)} Nature</li>`; | |
| } | |
| if (set.ivs) { | |
| const ivs: string[] = []; | |
| let iv: StatID; | |
| for (iv in set.ivs) { | |
| if (set.ivs[iv] === 31) continue; | |
| ivs.push(`${set.ivs[iv]} ${STAT_NAMES[iv]}`); | |
| } | |
| buf += `<li>IVs: ${ivs.join(" / ")}</li>`; | |
| } | |
| for (const moveid of set.moves) { | |
| buf += `<li>- ${Array.isArray(moveid) ? moveid.map(x => dex.moves.get(x).name).join(" / ") : dex.moves.get(moveid).name}</li>`; | |
| } | |
| const italicize = !baseDex.moves.get(set.signatureMove).exists; | |
| buf += `<li>- ${italicize ? `<i>` : ``}${dex.moves.get(set.signatureMove).name}${italicize ? `</i>` : ``}</li>`; | |
| buf += `</ul>`; | |
| buf += `</details>`; | |
| return buf; | |
| } | |
| function generateSSBMoveInfo(sigMove: Move, dex: ModdedDex) { | |
| let buf = ``; | |
| if (sigMove.shortDesc || sigMove.desc) { | |
| buf += `<hr />`; | |
| buf += Chat.getDataMoveHTML(sigMove); | |
| const details: { [k: string]: string } = { | |
| Priority: String(sigMove.priority), | |
| Gen: String(sigMove.gen || 9), | |
| }; | |
| if (sigMove.isNonstandard === "Past" && dex.gen >= 8) details["✗ Past Gens Only"] = ""; | |
| if (sigMove.secondary || sigMove.secondaries || sigMove.hasSheerForce) details["✓ Boosted by Sheer Force"] = ""; | |
| if (sigMove.flags['contact'] && dex.gen >= 3) details["✓ Contact"] = ""; | |
| if (sigMove.flags['sound'] && dex.gen >= 3) details["✓ Sound"] = ""; | |
| if (sigMove.flags['bullet'] && dex.gen >= 6) details["✓ Bullet"] = ""; | |
| if (sigMove.flags['pulse'] && dex.gen >= 6) details["✓ Pulse"] = ""; | |
| if (!sigMove.flags['protect'] && sigMove.target !== 'self') details["✓ Bypasses Protect"] = ""; | |
| if (sigMove.flags['bypasssub']) details["✓ Bypasses Substitutes"] = ""; | |
| if (sigMove.flags['defrost']) details["✓ Thaws user"] = ""; | |
| if (sigMove.flags['bite'] && dex.gen >= 6) details["✓ Bite"] = ""; | |
| if (sigMove.flags['punch'] && dex.gen >= 4) details["✓ Punch"] = ""; | |
| if (sigMove.flags['powder'] && dex.gen >= 6) details["✓ Powder"] = ""; | |
| if (sigMove.flags['reflectable'] && dex.gen >= 3) details["✓ Bounceable"] = ""; | |
| if (sigMove.flags['charge']) details["✓ Two-turn move"] = ""; | |
| if (sigMove.flags['recharge']) details["✓ Has recharge turn"] = ""; | |
| if (sigMove.flags['gravity'] && dex.gen >= 4) details["✗ Suppressed by Gravity"] = ""; | |
| if (sigMove.flags['dance'] && dex.gen >= 7) details["✓ Dance move"] = ""; | |
| if (sigMove.flags['slicing'] && dex.gen >= 9) details["✓ Slicing move"] = ""; | |
| if (sigMove.flags['wind'] && dex.gen >= 9) details["✓ Wind move"] = ""; | |
| if (sigMove.zMove?.basePower) { | |
| details["Z-Power"] = String(sigMove.zMove.basePower); | |
| } else if (sigMove.zMove?.effect) { | |
| const zEffects: { [k: string]: string } = { | |
| clearnegativeboost: "Restores negative stat stages to 0", | |
| crit2: "Crit ratio +2", | |
| heal: "Restores HP 100%", | |
| curse: "Restores HP 100% if user is Ghost type, otherwise Attack +1", | |
| redirect: "Redirects opposing attacks to user", | |
| healreplacement: "Restores replacement's HP 100%", | |
| }; | |
| details["Z-Effect"] = zEffects[sigMove.zMove.effect]; | |
| } else if (sigMove.zMove?.boost) { | |
| details["Z-Effect"] = ""; | |
| const boost = sigMove.zMove.boost; | |
| for (const h in boost) { | |
| details["Z-Effect"] += ` ${Dex.stats.mediumNames[h as 'atk']} +${boost[h as 'atk']}`; | |
| } | |
| } else if (sigMove.isZ && typeof sigMove.isZ === 'string') { | |
| details["✓ Z-Move"] = ""; | |
| const zCrystal = dex.items.get(sigMove.isZ); | |
| details["Z-Crystal"] = zCrystal.name; | |
| if (zCrystal.itemUser) { | |
| details["User"] = zCrystal.itemUser.join(", "); | |
| details["Required Move"] = dex.items.get(sigMove.isZ).zMoveFrom!; | |
| } | |
| } else { | |
| details["Z-Effect"] = "None"; | |
| } | |
| const targetTypes: { [k: string]: string } = { | |
| normal: "One Adjacent Pok\u00e9mon", | |
| self: "User", | |
| adjacentAlly: "One Ally", | |
| adjacentAllyOrSelf: "User or Ally", | |
| adjacentFoe: "One Adjacent Opposing Pok\u00e9mon", | |
| allAdjacentFoes: "All Adjacent Opponents", | |
| foeSide: "Opposing Side", | |
| allySide: "User's Side", | |
| allyTeam: "User's Side", | |
| allAdjacent: "All Adjacent Pok\u00e9mon", | |
| any: "Any Pok\u00e9mon", | |
| all: "All Pok\u00e9mon", | |
| scripted: "Chosen Automatically", | |
| randomNormal: "Random Adjacent Opposing Pok\u00e9mon", | |
| allies: "User and Allies", | |
| }; | |
| details["Target"] = targetTypes[sigMove.target] || "Unknown"; | |
| if (sigMove.isNonstandard === 'Unobtainable') { | |
| details[`Unobtainable in Gen ${dex.gen}`] = ""; | |
| } | |
| buf += `<font size="1">${Object.entries(details).map(([detail, value]) => ( | |
| value === '' ? detail : `<font color="#686868">${detail}:</font> ${value}` | |
| )).join(" |  ")}</font>`; | |
| if (sigMove.desc && sigMove.desc !== sigMove.shortDesc) { | |
| buf += `<details><summary><strong>In-Depth Description</strong></summary>${sigMove.desc}</details>`; | |
| } | |
| } | |
| return buf; | |
| } | |
| function generateSSBItemInfo(set: SSBSet, dex: ModdedDex, baseDex: ModdedDex) { | |
| let buf = ``; | |
| if (!Array.isArray(set.item)) { | |
| const baseItem = baseDex.items.get(set.item); | |
| const sigItem = dex.items.get(set.item); | |
| if (!baseItem.exists || (baseItem.desc || baseItem.shortDesc) !== (sigItem.desc || sigItem.shortDesc)) { | |
| buf += `<hr />`; | |
| buf += Chat.getDataItemHTML(sigItem); | |
| const details: { [k: string]: string } = { | |
| Gen: String(sigItem.gen), | |
| }; | |
| if (dex.gen >= 4) { | |
| if (sigItem.fling) { | |
| details["Fling Base Power"] = String(sigItem.fling.basePower); | |
| if (sigItem.fling.status) details["Fling Effect"] = sigItem.fling.status; | |
| if (sigItem.fling.volatileStatus) details["Fling Effect"] = sigItem.fling.volatileStatus; | |
| if (sigItem.isBerry) details["Fling Effect"] = "Activates the Berry's effect on the target."; | |
| if (sigItem.id === 'whiteherb') details["Fling Effect"] = "Restores the target's negative stat stages to 0."; | |
| if (sigItem.id === 'mentalherb') { | |
| const flingEffect = "Removes the effects of Attract, Disable, Encore, Heal Block, Taunt, and Torment from the target."; | |
| details["Fling Effect"] = flingEffect; | |
| } | |
| } else { | |
| details["Fling"] = "This item cannot be used with Fling."; | |
| } | |
| } | |
| if (sigItem.naturalGift && dex.gen >= 3) { | |
| details["Natural Gift Type"] = sigItem.naturalGift.type; | |
| details["Natural Gift Base Power"] = String(sigItem.naturalGift.basePower); | |
| } | |
| if (sigItem.isNonstandard && sigItem.isNonstandard !== "Custom") { | |
| details[`Unobtainable in Gen ${dex.gen}`] = ""; | |
| } | |
| buf += `<font size="1">${Object.entries(details).map(([detail, value]) => ( | |
| value === '' ? detail : `<font color="#686868">${detail}:</font> ${value}` | |
| )).join(" |  ")}</font>`; | |
| } | |
| } | |
| return buf; | |
| } | |
| function generateSSBAbilityInfo(set: SSBSet, dex: ModdedDex, baseDex: ModdedDex) { | |
| let buf = ``; | |
| const customMegaAbilities = ['Sableye', 'Ampharos']; | |
| if (!Array.isArray(set.ability) && | |
| (customMegaAbilities.includes(set.species) || !baseDex.abilities.get(set.ability).exists)) { | |
| let sigAbil = baseDex.deepClone(dex.abilities.get(set.ability)); | |
| if (customMegaAbilities.includes(set.species)) { | |
| const megaAbil = dex.species.get(`${set.species}-Mega`).abilities[0]; | |
| sigAbil = baseDex.deepClone(dex.abilities.get(megaAbil)); | |
| } | |
| if (!sigAbil.desc && !sigAbil.shortDesc) { | |
| sigAbil.desc = `This ability doesn't have a description. Try contacting the SSB dev team.`; | |
| } | |
| buf += `<hr />`; | |
| buf += Chat.getDataAbilityHTML(sigAbil); | |
| const details: { [k: string]: string } = { | |
| Gen: String(sigAbil.gen || 9) || 'CAP', | |
| }; | |
| if (sigAbil.flags['cantsuppress']) details["✓ Not affected by Gastro Acid"] = ""; | |
| if (sigAbil.flags['breakable']) details["✓ Ignored by Mold Breaker"] = ""; | |
| buf += `<font size="1">${Object.entries(details).map(([detail, value]) => ( | |
| value === '' ? detail : `<font color="#686868">${detail}:</font> ${value}` | |
| )).join(" |  ")}</font>`; | |
| if (sigAbil.desc && sigAbil.shortDesc && sigAbil.desc !== sigAbil.shortDesc) { | |
| buf += `<details><summary><strong>In-Depth Description</strong></summary>${sigAbil.desc}</details>`; | |
| } | |
| } | |
| return buf; | |
| } | |
| function generateSSBPokemonInfo(species: string, dex: ModdedDex, baseDex: ModdedDex) { | |
| let buf = ``; | |
| const origSpecies = baseDex.species.get(species); | |
| const newSpecies = dex.species.get(species); | |
| if ( | |
| newSpecies.types.join('/') !== origSpecies.types.join('/') || | |
| Object.values(newSpecies.abilities).join('/') !== Object.values(origSpecies.abilities).join('/') || | |
| Object.values(newSpecies.baseStats).join('/') !== Object.values(origSpecies.baseStats).join('/') | |
| ) { | |
| buf += `<hr />`; | |
| buf += Chat.getDataPokemonHTML(newSpecies, dex.gen, 'SSB'); | |
| let weighthit = 20; | |
| if (newSpecies.weighthg >= 2000) { | |
| weighthit = 120; | |
| } else if (newSpecies.weighthg >= 1000) { | |
| weighthit = 100; | |
| } else if (newSpecies.weighthg >= 500) { | |
| weighthit = 80; | |
| } else if (newSpecies.weighthg >= 250) { | |
| weighthit = 60; | |
| } else if (newSpecies.weighthg >= 100) { | |
| weighthit = 40; | |
| } | |
| const details: { [k: string]: string } = { | |
| "Dex#": String(newSpecies.num), | |
| Gen: String(newSpecies.gen) || 'CAP', | |
| Height: `${newSpecies.heightm} m`, | |
| }; | |
| details["Weight"] = `${newSpecies.weighthg / 10} kg <em>(${weighthit} BP)</em>`; | |
| if (newSpecies.color && dex.gen >= 5) details["Dex Colour"] = newSpecies.color; | |
| if (newSpecies.eggGroups && dex.gen >= 2) details["Egg Group(s)"] = newSpecies.eggGroups.join(", "); | |
| const evos: string[] = []; | |
| for (const evoName of newSpecies.evos) { | |
| const evo = dex.species.get(evoName); | |
| if (evo.gen <= dex.gen) { | |
| const condition = evo.evoCondition ? ` ${evo.evoCondition}` : ``; | |
| switch (evo.evoType) { | |
| case 'levelExtra': | |
| evos.push(`${evo.name} (level-up${condition})`); | |
| break; | |
| case 'levelFriendship': | |
| evos.push(`${evo.name} (level-up with high Friendship${condition})`); | |
| break; | |
| case 'levelHold': | |
| evos.push(`${evo.name} (level-up holding ${evo.evoItem}${condition})`); | |
| break; | |
| case 'useItem': | |
| evos.push(`${evo.name} (${evo.evoItem})`); | |
| break; | |
| case 'levelMove': | |
| evos.push(`${evo.name} (level-up with ${evo.evoMove}${condition})`); | |
| break; | |
| case 'other': | |
| evos.push(`${evo.name} (${evo.evoCondition})`); | |
| break; | |
| case 'trade': | |
| evos.push(`${evo.name} (trade${evo.evoItem ? ` holding ${evo.evoItem}` : condition})`); | |
| break; | |
| default: | |
| evos.push(`${evo.name} (${evo.evoLevel}${condition})`); | |
| } | |
| } | |
| } | |
| if (!evos.length) { | |
| details[`<font color="#686868">Does Not Evolve</font>`] = ""; | |
| } else { | |
| details["Evolution"] = evos.join(", "); | |
| } | |
| buf += `<font size="1">${Object.entries(details).map(([detail, value]) => ( | |
| value === '' ? detail : `<font color="#686868">${detail}:</font> ${value}` | |
| )).join(" |  ")}</font>`; | |
| } | |
| return buf; | |
| } | |
| function generateSSBInnateInfo(name: string, dex: ModdedDex, baseDex: ModdedDex) { | |
| let buf = ``; | |
| // Special casing for users whose usernames are already existing, i.e. dhelmise | |
| let effect = dex.conditions.get(name + 'user'); | |
| let longDesc = ``; | |
| const baseAbility = baseDex.deepClone(baseDex.abilities.get('noability')); | |
| if (effect.exists && (effect as any).innateName && (effect.desc || effect.shortDesc)) { | |
| baseAbility.name = (effect as any).innateName; | |
| if (!effect.desc && !effect.shortDesc) { | |
| baseAbility.desc = baseAbility.shortDesc = "This innate does not have a description."; | |
| } | |
| if (effect.desc) baseAbility.desc = effect.desc; | |
| if (effect.shortDesc) baseAbility.shortDesc = effect.shortDesc; | |
| buf += `<hr />Innate Ability:<br />${Chat.getDataAbilityHTML(baseAbility)}`; | |
| if (effect.desc && effect.shortDesc && effect.desc !== effect.shortDesc) { | |
| longDesc = effect.desc; | |
| } | |
| } else { | |
| effect = dex.deepClone(dex.conditions.get(name)); | |
| if (!effect.desc && !effect.shortDesc) { | |
| effect.desc = effect.shortDesc = "This innate does not have a description."; | |
| } | |
| if (effect.exists && (effect as any).innateName) { | |
| baseAbility.name = (effect as any).innateName; | |
| if (effect.desc) baseAbility.desc = effect.desc; | |
| if (effect.shortDesc) baseAbility.shortDesc = effect.shortDesc; | |
| buf += `<hr />Innate Ability:<br />${Chat.getDataAbilityHTML(baseAbility)}`; | |
| if (effect.desc && effect.shortDesc && effect.desc !== effect.shortDesc) { | |
| longDesc = effect.desc; | |
| } | |
| } | |
| } | |
| if (buf) { | |
| const details: { [k: string]: string } = { Gen: '9' }; | |
| buf += `<font size="1">${Object.entries(details).map(([detail, value]) => ( | |
| value === '' ? detail : `<font color="#686868">${detail}:</font> ${value}` | |
| )).join(" |  ")}</font>`; | |
| } | |
| if (longDesc) { | |
| buf += `<details><summary><strong>In-Depth Description</strong></summary>${longDesc}</details>`; | |
| } | |
| return buf; | |
| } | |
| function SSBSets(target: string) { | |
| const baseDex = Dex; | |
| const dex = Dex.forFormat('gen9superstaffbrosultimate'); | |
| if (!Object.keys(ssbSets).map(toID).includes(toID(target))) { | |
| return { e: `Error: ${target.trim()} doesn't have a [Gen 9] Super Staff Bros Ultimate set.` }; | |
| } | |
| let name = ''; | |
| for (const member in ssbSets) { | |
| if (toID(member) === toID(target)) name = member; | |
| } | |
| let buf = ''; | |
| const sets: string[] = []; | |
| for (const set in ssbSets) { | |
| if (!set.startsWith(name)) continue; | |
| if (!ssbSets[set].skip && set !== name) continue; | |
| sets.push(set); | |
| } | |
| for (const setName of sets) { | |
| const set = ssbSets[setName]; | |
| const mutatedSpecies = dex.species.get(set.species); | |
| if (!set.skip) { | |
| buf += Utils.html`<h1><psicon pokemon="${mutatedSpecies.id}">${setName}</h1>`; | |
| } else { | |
| buf += `<details><summary><psicon pokemon="${set.species}"><strong>${setName.split('-').slice(1).join('-') + ' forme'}</strong></summary>`; | |
| } | |
| buf += generateSSBSet(set, dex, baseDex); | |
| const item = dex.items.get(set.item as string); | |
| if (!set.skip || set.signatureMove !== ssbSets[set.skip].signatureMove) { | |
| const sigMove = baseDex.moves.get(set.signatureMove).exists && !Array.isArray(set.item) && | |
| typeof item.zMove === 'string' ? | |
| dex.moves.get(item.zMove) : dex.moves.get(set.signatureMove); | |
| buf += generateSSBMoveInfo(sigMove, dex); | |
| } | |
| buf += generateSSBItemInfo(set, dex, baseDex); | |
| buf += generateSSBAbilityInfo(set, dex, baseDex); | |
| buf += generateSSBInnateInfo(setName, dex, baseDex); | |
| buf += generateSSBPokemonInfo(set.species, dex, baseDex); | |
| if (!Array.isArray(set.item) && item.megaStone) { | |
| buf += generateSSBPokemonInfo(item.megaStone, dex, baseDex); | |
| // keys and Kennedy have an itemless forme change | |
| } else if (['Rayquaza'].includes(set.species)) { | |
| buf += generateSSBPokemonInfo(`${set.species}-Mega`, dex, baseDex); | |
| } else if (['Cinderace'].includes(set.species)) { | |
| buf += generateSSBPokemonInfo(`${set.species}-Gmax`, dex, baseDex); | |
| } | |
| if (set.skip) buf += `</details>`; | |
| } | |
| return buf; | |
| } | |
| export const disabledSets = Chat.oldPlugins.ssb?.disabledSets || []; | |
| function enforceDisabledSets() { | |
| for (const process of Rooms.PM.processes) { | |
| process.getProcess().send(`EVAL\n\nConfig.disabledssbsets = ${JSON.stringify(disabledSets)}`); | |
| } | |
| } | |
| enforceDisabledSets(); | |
| export const commands: Chat.ChatCommands = { | |
| ssb(target, room, user) { | |
| if (!this.runBroadcast()) return; | |
| if (!target) return this.parse(`/help ssb`); | |
| const set = SSBSets(target); | |
| if (typeof set !== 'string') { | |
| throw new Chat.ErrorMessage(set.e); | |
| } | |
| return this.sendReplyBox(set); | |
| }, | |
| ssbhelp: [ | |
| `/ssb [staff member] - Displays a staff member's Super Staff Bros. set and custom features.`, | |
| ], | |
| enablessbset: 'disablessbset', | |
| disablessbset(target, room, user, connection, cmd) { | |
| this.checkCan('rangeban'); | |
| target = toID(target); | |
| if (!Object.keys(ssbSets).map(toID).includes(target as ID)) { | |
| throw new Chat.ErrorMessage(`${target} has no SSB set.`); | |
| } | |
| const disableIdx = disabledSets.indexOf(target); | |
| if (cmd.startsWith('enable')) { | |
| if (disableIdx < 0) { | |
| throw new Chat.ErrorMessage(`${target}'s set is not disabled.`); | |
| } | |
| disabledSets.splice(disableIdx, 1); | |
| this.privateGlobalModAction(`${user.name} enabled ${target}'s SSB set.`); | |
| } else { | |
| if (disableIdx > -1) { | |
| throw new Chat.ErrorMessage(`That set is already disabled.`); | |
| } | |
| disabledSets.push(target); | |
| this.privateGlobalModAction(`${user.name} disabled the SSB set for ${target}`); | |
| } | |
| enforceDisabledSets(); | |
| }, | |
| }; | |