Super Staff Bros 4 (#7793)

Collaborative Project lead by @xInstruct with many contributors.

See the Special Thanks section of https://www.smogon.com/articles/super-staff-bros-4
This commit is contained in:
Instruct
2020-12-24 11:21:02 -05:00
committed by GitHub
parent 2398a915fe
commit 5b441c2013
15 changed files with 11390 additions and 7533 deletions

View File

@@ -1,6 +1,6 @@
config/formats.ts @TheImmortal
data/mods/*/random-teams.ts @TheImmortal
data/mods/ssb/ @HoeenCoder
data/mods/ssb/ @HoeenCoder @xInstruct
data/random-teams.ts @TheImmortal
data/text/ @Marty-D
databases/ @monsanto

2
build
View File

@@ -96,7 +96,7 @@ sucrase('./lib', './.lib-dist');
if (sucrase('./server', './.server-dist')) {
replace('.server-dist', [
{regex: /(require\(.*?)(lib|sim)/g, replace: `$1.$2-dist`},
{regex: /(require\(.*?)(data|lib|sim)/g, replace: `$1.$2-dist`},
]);
}

View File

@@ -1355,6 +1355,44 @@ export const Formats: FormatList = [
team: 'random',
ruleset: ['Obtainable', 'Same Type Clause', 'HP Percentage Mod', 'Cancel Mod', 'Sleep Clause Mod'],
},
{
name: "[Gen 8] Super Staff Bros 4",
desc: "The fourth iteration of Super Staff Bros is here! Battle with a random team of pokemon created by the sim staff.",
threads: [
`&bullet; <a href="https://www.smogon.com/articles/super-staff-bros-4">Introduction &amp; Roster</a>`,
],
mod: 'ssb',
team: 'randomStaffBros',
ruleset: ['Dynamax Clause', 'HP Percentage Mod', 'Cancel Mod', 'Sleep Clause Mod'],
onBegin() { // TODO look into making an event to put this right after turn|1
// https://discordapp.com/channels/630837856075513856/630845310033330206/716126469528485909
// Requires client change
this.add(`raw|<div class='broadcast-green'><b>Wondering what all these custom moves, abilities, and items do?<br />Check out the <a href="https://www.smogon.com/articles/super-staff-bros-4" target="_blank">Super Staff Bros 4 Guide</a> or use /ssb to find out!</b></div>`);
this.add('message', [
'THE BATTLE FOR SURVIVAL BEGINS!', 'WHO WILL SURVIVE?', 'GET READY TO KEEP UP!', 'GET READY!', 'DARE TO BELIEVE YOU CAN SURVIVE!', 'THERE CAN BE ONLY ONE WINNER!', 'GET READY FOR THE FIGHT OF YOUR LIFE!', 'WHO WILL PREVAIL?', 'ONLY ONE TEAM WILL BE LEFT STANDING!', 'BATTLE WITHOUT LIMITS!',
][this.random(10)]);
this.add('message', 'FIGHT!');
},
onSwitchInPriority: 100,
onSwitchIn(pokemon) {
let name: string = this.toID(pokemon.illusion ? pokemon.illusion.name : pokemon.name);
if (this.dex.getSpecies(name).exists || this.dex.getMove(name).exists || this.dex.getAbility(name).exists) {
// Certain pokemon have volatiles named after their id
// To prevent overwriting those, and to prevent accidentaly leaking
// that a pokemon is on a team through the onStart even triggering
// at the start of a match, users with pokemon names will need their
// statuses to end in "user".
name = name + 'user';
}
// Add the mon's status effect to it as a volatile.
const status = this.dex.getEffect(name);
if (status?.exists) {
pokemon.addVolatile(name, pokemon);
}
},
},
{
name: "[Gen 8] Challenge Cup 1v1",
@@ -1463,40 +1501,6 @@ export const Formats: FormatList = [
searchShow: false,
ruleset: ['HP Percentage Mod', 'Cancel Mod'],
},
{
name: "[Gen 7] Super Staff Bros Brawl",
desc: "Super Staff Bros returns for another round! Battle with a random team of pokemon created by the sim staff.",
threads: [
`&bullet; <a href="https://www.smogon.com/articles/super-staff-bros-brawl">Introduction &amp; Roster</a>`,
],
mod: 'ssb',
team: 'randomStaffBros',
ruleset: ['HP Percentage Mod', 'Cancel Mod', 'Sleep Clause Mod'],
onBegin() {
this.add('raw|SUPER STAFF BROS <b>BRAWL</b>!!');
this.add('message', 'GET READY FOR THE NEXT BATTLE!');
if (this.teamGenerator.allXfix) this.add(`c|~HoeenHero|Oops I dropped my bag of xfix sets sorry!`);
this.add(`raw|<div class='broadcast-green'><b>Wondering what all these custom moves, abilities, and items do?<br />Check out the <a href="https://www.smogon.com/articles/super-staff-bros-brawl" target="_blank">Super Staff Bros Brawl Guide</a> and find out!</b></div>`);
},
onSwitchInPriority: 100,
onSwitchIn(pokemon) {
let name: string = this.toID(pokemon.illusion ? pokemon.illusion.name : pokemon.name);
if (this.dex.getSpecies(name).exists || name === 'rage') {
// Certain pokemon have volatiles named after their id
// To prevent overwriting those, and to prevent accidentaly leaking
// that a pokemon is on a team through the onStart even triggering
// at the start of a match, users with pokemon names will need their
// statuses to end in "user".
name = name + 'user';
}
// Add the mon's status effect to it as a volatile.
const status = this.dex.getEffect(name);
if (status?.exists) {
pokemon.addVolatile(name, pokemon);
}
},
},
{
name: "[Gen 7 Let's Go] Random Battle",

View File

@@ -32,7 +32,8 @@ export const Aliases: {[alias: string]: string} = {
monorandom: "[Gen 8] Monotype Random Battle",
bf: "[Gen 7] Battle Factory",
bssf: "[Gen 7] BSS Factory",
ssb: "[Gen 7] Super Staff Bros Brawl",
ssb: "[Gen 8] Super Staff Bros 4",
ssb4: "[Gen 8] Super Staff Bros 4",
lgrandom: "[Gen 7] Let's Go Random Battle",
gen6bf: "[Gen 6] Battle Factory",
gen7mono: "[Gen 7] Monotype",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,147 +1,69 @@
export const Items: {[k: string]: ModdedItemData} = {
// Aeonic
noseiumz: {
name: "Noseium Z",
// Alpha
caioniumz: {
name: "Caionium Z",
isNonstandard: "Custom",
onTakeItem: false,
zMove: "Shitpost",
zMoveFrom: "Fissure",
itemUser: ["Nosepass"],
gen: 7,
desc: "If held by a Nosepass with Fissure, it can use Shitpost.",
zMove: "Blistering Ice Age",
zMoveFrom: "Blizzard",
itemUser: ["Aurorus"],
gen: 8,
desc: "If held by an Aurorus with Blizzard, it can use Blistering Ice Age.",
},
// E4 Flint
magmarizer: {
inherit: true,
megaStone: "Steelix-Mega",
megaEvolves: "Steelix",
onTakeItem(item, source) {
if (item.megaEvolves === source.baseSpecies.baseSpecies) return false;
return true;
// A Quag To The Past
quagniumz: {
name: "Quagnium Z",
isNonstandard: "Custom",
onTakeItem: false,
zMove: "Bounty Place",
zMoveFrom: "Scorching Sands",
itemUser: ["Quagsire"],
gen: 8,
desc: "If held by a Quagsire with Scorching Sands, it can use Bounty Place.",
},
// Kalalokki
kalalokkiumz: {
name: "Kalalokkium Z",
isNonstandard: "Custom",
onTakeItem: false,
zMove: "Gaelstrom",
zMoveFrom: "Blackbird",
itemUser: ["Wingull"],
gen: 8,
desc: "If held by a Wingull with Blackbird, it can use Gaelstrom.",
},
// Robb576
modium6z: {
name: "Modium-6 Z",
isNonstandard: "Custom",
onTakeItem: false,
zMove: "Integer Overflow",
zMoveFrom: "Photon Geyser",
itemUser: ["Necrozma-Ultra"],
gen: 8,
desc: "If held by a Robb576 with Photon Geyser, it can use Integer Overflow.",
},
// xJoelituh
rarebone: {
onModifyAtkPriority: 2,
onModifyAtk() {
return this.chainModify(1.5);
},
fling: undefined, // Cannot be flung now since its a mega stone
desc: "If held by a Steelix, this item allows it to Mega Evolve in battle.",
},
// FOMG
astleyiumz: {
name: "Astleyium Z",
isNonstandard: "Custom",
onTakeItem: false,
zMove: "Rickrollout",
zMoveFrom: "Rock Slide",
itemUser: ["Golem"],
gen: 7,
desc: "If held by a Golem with Rock Slide, it can use Rickrollout.",
},
// inactive
dusknoiriumz: {
name: "Dusknoirium Z",
isNonstandard: "Custom",
onTakeItem: false,
zMove: "Petrifying Gaze",
zMoveFrom: "Mean Look",
itemUser: ["Dusknoir"],
gen: 7,
desc: "If held by a Dusknoir with Mean Look, it can use Petrifying Gaze.",
},
// Kris
thunderstone: {
inherit: true,
// @ts-ignore
megaStone: ["Rotom-Wash", "Rotom-Mow", "Rotom-Heat", "Rotom-Frost", "Rotom-Fan"],
megaEvolves: "Rotom",
onTakeItem(item, source) {
if (item.megaEvolves === source.baseSpecies.baseSpecies) return false;
return true;
onModifyDefPriority: 2,
onModifyDef() {
return this.chainModify(1.5);
},
fling: undefined, // Cannot be flung now since its a mega stone
desc: "If held by a Rotom, this item allows it to Mega Evolve in battle.",
shortDesc: "If held by a Rotom, this item allows it to Mega Evolve in battle.",
},
// MajorBowman
victiniumz: {
name: "Victinium Z",
isNonstandard: "Custom",
onTakeItem: false,
zMove: "Blaze of Glory",
zMoveFrom: "V-create",
itemUser: ["Victini"],
gen: 7,
desc: "If held by a Victini with V-create, it can use Blaze of Glory.",
},
// Pohjis
marowakiumz: {
name: "Marowakium Z",
isNonstandard: "Custom",
onTakeItem: false,
zMove: "Great Eqake",
zMoveFrom: "Earthquake",
itemUser: ["Marowak"],
gen: 7,
desc: "If held by a Marowak with Earthquake, it can use Great Eqake.",
},
// SamJo
thicciniumz: {
name: "Thiccinium Z",
isNonstandard: "Custom",
onTakeItem: false,
zMove: "Extra T h i c c",
zMoveFrom: "Thicc",
itemUser: ["Mamoswine"],
gen: 7,
desc: "If held by a Mamoswine with Thicc, it can use Extra T h i c c.",
},
// Schiavetto
mariahcariumz: {
name: "Mariahcarium Z",
isNonstandard: "Custom",
onTakeItem: false,
zMove: "Plurshift",
zMoveFrom: "Poison Jab",
itemUser: ["Scolipede"],
gen: 7,
desc: "If held by a Scolipede with Poison Jab, it can use Plurshift.",
},
// Snaquaza
fakeclaimiumz: {
name: "Fakeclaimium Z",
isNonstandard: "Custom",
onTakeItem: false,
zMove: "Fake Claim",
// @ts-ignore hack for Snaquaza's Z move.
zMoveFrom: ["Brave Bird", "Superpower", "Sucker Punch", "Flamethrower", "Ice Beam", "Thunderbolt"],
itemUser: ["Honchkrow"],
gen: 7,
desc: "If held by a Honchkrow, it can use Fake Claim.",
},
// The Immortal
buzzniumz: {
name: "Buzznium Z",
isNonstandard: "Custom",
onTakeItem: false,
zMove: "Ultra Succ",
zMoveFrom: "Drain Punch",
itemUser: ["Buzzwole"],
gen: 7,
desc: "If held by a Buzzwole with Drain Punch, it can use Ultra Succ.",
},
// Teclis
darkrainiumz: {
name: "Darkrainium Z",
isNonstandard: "Custom",
onTakeItem: false,
zMove: "Absolute Configuration",
zMoveFrom: "Dark Void",
itemUser: ["Darkrai"],
gen: 7,
desc: "If held by a Darkrai with Dark Void, it can use Absolute Configuration.",
},
// XpRienzo
charcoal: {
inherit: true,
zMove: "Bleh Flame",
zMoveFrom: "Blue Flare",
itemUser: ["Reshiram"],
desc: "Fire-type attacks have 1.2x power. Reshiram with Blue Flare can use Bleh Flame.",
onModifySpDPriority: 2,
onModifySpD() {
return this.chainModify(1.5);
},
gen: 4,
name: "Rare Bone",
desc: "1.5x to Attack, Defense, and Special Defense.",
shortDesc: "1.5x to Attack, Defense, and Special Defense.",
},
};

File diff suppressed because it is too large Load Diff

View File

@@ -6,186 +6,254 @@ export const Pokedex: {[k: string]: ModdedSpeciesData} = {
baseStats: {hp: 100, atk: 100, def: 100, spa: 100, spd: 100, spe: 100}, // the base stats for the pokemon
},
*/
// ACakeWearingAHat
dunsparce: {
// Abdelrahman
cameruptmega: {
inherit: true,
types: ['Normal', 'Ghost'],
abilities: {0: "Water Absorb"},
},
// Akiamara
croagunk: {
// Aelita
zygardecomplete: {
inherit: true,
baseStats: {hp: 83, atk: 106, def: 65, spa: 86, spd: 65, spe: 85},
abilities: {0: "Scyphozoa"},
},
// Arrested
blastoisemega: {
// aegii
aegislash: {
inherit: true,
abilities: {0: "Shell Shocker"},
abilities: {0: "Set the Stage"},
},
// Birdy
oricorio: {
aegislashblade: {
inherit: true,
abilities: {0: "Arabesque"},
abilities: {0: "Set the Stage"},
},
oricoriopau: {
// Aeonic
nosepass: {
inherit: true,
abilities: {0: "Arabesque"},
baseStats: {hp: 70, atk: 85, def: 135, spa: 45, spd: 90, spe: 70},
},
oricoriopompom: {
// Aethernum
lotad: {
inherit: true,
abilities: {0: "Arabesque"},
baseStats: {hp: 40, atk: 70, def: 70, spa: 80, spd: 90, spe: 70},
},
oricoriosensu: {
// Annika
mewtwomegay: {
inherit: true,
abilities: {0: "Arabesque"},
abilities: {0: "Overprotective"},
},
// E4 Flint
steelixmega: {
// A Quag To The Past
quagsire: {
inherit: true,
abilities: {0: 'Stark Mountain'},
types: ['Steel', 'Ground', 'Fire'],
baseStats: {hp: 95, atk: 65, def: 85, spa: 65, spd: 85, spe: 35},
},
// Eien
mew: {
// Billo
cosmog: {
inherit: true,
abilities: {0: 'Psychic Surge'},
baseStats: {hp: 86, atk: 58, def: 62, spa: 87, spd: 62, spe: 74},
},
// dogknees
furret: {
inherit: true,
types: ["Normal", "Ghost"],
},
// Elgino
mimikyubusted: {
celebi: {
inherit: true,
abilities: {0: 'Gib love pls'},
types: ["Grass", "Fairy"],
},
// eternally
ducklett: {
// EpicNikolai
garchompmega: {
inherit: true,
// Doubled def, spa, spd and spe
baseStats: {hp: 62, atk: 44, def: 100, spa: 88, spd: 100, spe: 110},
abilities: {0: "Dragon Heart"},
types: ["Dragon", "Fire"],
},
// fart
kartana: {
// Felucia
uxie: {
inherit: true,
types: ['Fairy', 'Steel'],
types: ["Psychic", "Normal"],
},
// frostyicelad
laprasgmax: {
inherit: true,
heightm: 2.5,
weightkg: 220,
},
// GMars
minior: {
inherit: true,
abilities: {0: 'mysteryshell'},
abilities: {0: "Capsule Armor"},
},
miniorviolet: {
inherit: true,
abilities: {0: "Capsule Armor"},
},
miniorindigo: {
inherit: true,
abilities: {0: "Capsule Armor"},
},
miniorblue: {
inherit: true,
abilities: {0: "Capsule Armor"},
},
miniorgreen: {
inherit: true,
abilities: {0: "Capsule Armor"},
},
minioryellow: {
inherit: true,
abilities: {0: "Capsule Armor"},
},
miniororange: {
inherit: true,
abilities: {0: "Capsule Armor"},
},
miniormeteor: {
inherit: true,
abilities: {0: 'mysteryshell'},
abilities: {0: "Capsule Armor"},
},
// grimAuxiliatrix
aggronmega: {
// Hydro
pichu: {
inherit: true,
abilities: {0: 'Intimidate'},
types: ["Electric", "Water"],
baseStats: {hp: 70, atk: 80, def: 55, spa: 75, spd: 75, spe: 100},
},
// guishark
sharpedomega: {
// Inactive
gyaradosmega: {
inherit: true,
abilities: {0: 'gz guishark'},
abilities: {0: "Dragon's Fury"},
},
// Jho
toxtricity: {
inherit: true,
abilities: {0: "Punk Rock"},
},
toxtricitylowkey: {
inherit: true,
abilities: {0: "Venomize"},
},
// Kaiju Bunny
gliscor: {
lopunnymega: {
inherit: true,
abilities: {0: 'Poison Heal'}, // Ability change is permanent
abilities: {0: "Second Wind"},
types: ["Normal", "Fairy"],
},
// Kris
rotomfan: {
unown: {
inherit: true,
abilities: {0: "Adaptability"},
baseStats: {hp: 50, atk: 65, def: 127, spa: 145, spd: 127, spe: 106},
baseStats: {hp: 100, atk: 100, def: 100, spa: 100, spd: 100, spe: 100},
// For reverting back to an Unown forme
abilities: {0: "Protean"},
},
rotomfrost: {
// Lamp
lampent: {
inherit: true,
abilities: {0: "Adaptability"},
baseStats: {hp: 50, atk: 65, def: 127, spa: 145, spd: 127, spe: 106},
baseStats: {hp: 60, atk: 80, def: 100, spa: 135, spd: 100, spe: 95},
},
rotomheat: {
// Meicoo
venusaurmega: {
inherit: true,
abilities: {0: "Adaptability"},
baseStats: {hp: 50, atk: 65, def: 127, spa: 145, spd: 127, spe: 106},
abilities: {0: "Unaware"},
},
rotommow: {
// nui
jigglypuff: {
inherit: true,
abilities: {0: "Adaptability"},
baseStats: {hp: 50, atk: 65, def: 127, spa: 145, spd: 127, spe: 106},
},
rotomwash: {
inherit: true,
abilities: {0: "Adaptability"},
baseStats: {hp: 50, atk: 65, def: 127, spa: 145, spd: 127, spe: 106},
},
// Level 51
porygon2: {
inherit: true,
abilities: {0: 'Stamina'},
},
// MacChaeger
mantyke: {
inherit: true,
baseStats: {hp: 90, atk: 40, def: 100, spa: 120, spd: 240, spe: 100},
},
// martha
dianciemega: {
inherit: true,
abilities: {0: 'Pixilate'},
},
// Morfent
banettemega: {
inherit: true,
abilities: {0: 'Comatose'},
},
// OM
flareon: {
inherit: true,
types: ['Fire', 'Fairy'],
baseStats: {hp: 115, atk: 128, def: 62, spa: 128, spd: 78, spe: 62},
},
// Overneat
absolmega: {
inherit: true,
abilities: {0: 'Filter'},
types: ['Dark', 'Fairy'],
abilities: {0: "Fluffy"},
types: ["Dark", "Fairy"],
},
// Pirate Princess
gulpin: {
// PartMan
chandelure: {
inherit: true,
baseStats: {hp: 70, atk: 43, def: 53, spa: 86, spd: 53, spe: 40},
},
// pre
deoxys: {
inherit: true,
abilities: {0: 'Optimize'},
},
deoxysattack: {
inherit: true,
abilities: {0: 'Optimize'},
},
deoxysdefense: {
inherit: true,
abilities: {0: 'Optimize'},
},
deoxysspeed: {
inherit: true,
abilities: {0: 'Optimize'},
abilities: {0: "Hecatomb"},
},
// Psynergy
blazikenmega: {
rayquaza: {
inherit: true,
abilities: {0: 'Wrath'},
abilities: {0: "Supernova"},
},
rayquazamega: {
inherit: true,
abilities: {0: "Supernova"},
requiredMove: "Clear Breath",
},
// Robb576
necrozmadawnwings: {
inherit: true,
abilities: {0: "The Numbers Game"},
},
necrozmaduskmane: {
inherit: true,
abilities: {0: "The Numbers Game"},
},
necrozmaultra: {
inherit: true,
abilities: {0: "The Numbers Game"},
},
// Strucni
aggronmega: {
inherit: true,
abilities: {0: "Overasked Clause"},
},
// Finland
alcremie: {
inherit: true,
abilities: {0: "Winding Song"},
},
// vivalospride's interaction with Coconut's move
darumaka: {
inherit: true,
evos: ["Darmanitan", "Darmanitan-Zen"],
},
darmanitanzen: {
inherit: true,
prevo: "Darumaka",
},
// yuki
pikachucosplay: {
inherit: true,
baseStats: {hp: 60, atk: 85, def: 50, spa: 95, spd: 85, spe: 110},
abilities: {0: "Combat Training"},
},
pikachuphd: {
inherit: true,
baseStats: {hp: 60, atk: 85, def: 50, spa: 95, spd: 85, spe: 110},
abilities: {0: "Triage"},
},
// Rach
pikachulibre: {
inherit: true,
types: ['Electric', 'Fighting'],
baseStats: {hp: 60, atk: 85, def: 50, spa: 95, spd: 85, spe: 110},
abilities: {0: "White Smoke"},
},
// Sunny
sceptilemega: {
pikachupopstar: {
inherit: true,
abilities: {0: 'Contrary'},
baseStats: {hp: 60, atk: 85, def: 50, spa: 95, spd: 85, spe: 110},
abilities: {0: "Dancer"},
},
pikachurockstar: {
inherit: true,
baseStats: {hp: 60, atk: 85, def: 50, spa: 95, spd: 85, spe: 110},
abilities: {0: "Punk Rock"},
},
pikachubelle: {
inherit: true,
baseStats: {hp: 60, atk: 85, def: 50, spa: 95, spd: 85, spe: 110},
abilities: {0: "Tangled Feet"},
},
// Zalm
weedle: {
inherit: true,
baseStats: {hp: 85, atk: 100, def: 90, spa: 20, spd: 90, spe: 90},
baseStats: {hp: 100, atk: 35, def: 100, spa: 90, spd: 90, spe: 100},
},
// Zarel
meloetta: {
inherit: true,
abilities: {0: "Dancer"},
},
meloettapirouette: {
inherit: true,
abilities: {0: "Serene Grace"},
},
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -4,11 +4,17 @@
*/
import {FS} from "../../lib/fs";
import {SSBSet, ssbSets} from "../../data/mods/ssb/random-teams";
import {Utils} from "../../lib/utils";
const GEN_NAMES: {[k: string]: string} = {
gen1: '[Gen 1]', gen2: '[Gen 2]', gen3: '[Gen 3]', gen4: '[Gen 4]', gen5: '[Gen 5]', gen6: '[Gen 6]', gen7: '[Gen 7]',
};
const STAT_NAMES: {[k: string]: string} = {
hp: "HP", atk: "Atk", def: "Def", spa: "SpA", spd: "SpD", spe: "Spe",
};
const TIERS: {[k: string]: string} = {
uber: "Uber", ubers: "Uber",
ou: "OU", uu: "UU", ru: "RU", nu: "NU", pu: "PU",
@@ -95,9 +101,6 @@ function battleFactorySets(species: string | Species, tier: string | null, gen =
);
if (!Object.keys(statsFile).length) return null;
let buf = ``;
const statNames: {[k: string]: string} = {
hp: "HP", atk: "Atk", def: "Def", spa: "SpA", spd: "SpD", spe: "Spe",
};
if (!isBSS) {
if (!tier) return {e: `Please provide a valid tier.`};
if (!(toID(tier) in TIERS)) return {e: `That tier isn't supported.`};
@@ -123,7 +126,7 @@ function battleFactorySets(species: string | Species, tier: string | null, gen =
let ev: string;
for (ev in set.evs) {
if (set.evs[ev] === 0) continue;
evs.push(`${set.evs[ev]} ${statNames[ev]}`);
evs.push(`${set.evs[ev]} ${STAT_NAMES[ev]}`);
}
buf += `${evs.join(" / ")}</li>`;
}
@@ -134,7 +137,7 @@ function battleFactorySets(species: string | Species, tier: string | null, gen =
let iv: string;
for (iv in set.ivs) {
if (set.ivs[iv] === 31) continue;
ivs.push(`${set.ivs[iv]} ${statNames[iv]}`);
ivs.push(`${set.ivs[iv]} ${STAT_NAMES[iv]}`);
}
buf += `${ivs.join(" / ")}</li>`;
}
@@ -163,7 +166,7 @@ function battleFactorySets(species: string | Species, tier: string | null, gen =
let ev: string;
for (ev in set.evs) {
if (set.evs[ev] === 0) continue;
evs.push(`${set.evs[ev]} ${statNames[ev]}`);
evs.push(`${set.evs[ev]} ${STAT_NAMES[ev]}`);
}
buf += `${evs.join(" / ")}</li>`;
}
@@ -174,7 +177,7 @@ function battleFactorySets(species: string | Species, tier: string | null, gen =
let iv: string;
for (iv in set.ivs) {
if (set.ivs[iv] === 31) continue;
ivs.push(`${set.ivs[iv]} ${statNames[iv]}`);
ivs.push(`${set.ivs[iv]} ${STAT_NAMES[iv]}`);
}
buf += `${ivs.join(" / ")}</li>`;
}
@@ -204,9 +207,6 @@ function CAP1v1Sets(species: string | Species) {
return {e: `${species.name} doesn't have any sets in [Gen 8] CAP 1v1.`};
}
let buf = `<span style="color:#999999;">Sets for ${species.name} in [Gen 8] CAP 1v1:</span><br />`;
const statNames: {[k: string]: string} = {
hp: "HP", atk: "Atk", def: "Def", spa: "SpA", spd: "SpD", spe: "Spe",
};
for (const [i, set] of statsFile[species.name].entries()) {
buf += `<details><summary>Set ${i + 1}</summary>`;
buf += `<ul style="list-style-type:none;">`;
@@ -221,7 +221,7 @@ function CAP1v1Sets(species: string | Species) {
let ev: string;
for (ev in set.evs) {
if (set.evs[ev] === 0) continue;
evs.push(`${set.evs[ev]} ${statNames[ev]}`);
evs.push(`${set.evs[ev]} ${STAT_NAMES[ev]}`);
}
buf += `${evs.join(" / ")}</li>`;
}
@@ -232,7 +232,7 @@ function CAP1v1Sets(species: string | Species) {
let iv: string;
for (iv in set.ivs) {
if (set.ivs[iv] === 31) continue;
ivs.push(`${set.ivs[iv]} ${statNames[iv]}`);
ivs.push(`${set.ivs[iv]} ${STAT_NAMES[iv]}`);
}
buf += `${ivs.join(" / ")}</li>`;
}
@@ -244,6 +244,375 @@ function CAP1v1Sets(species: string | Species) {
return buf;
}
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})` : ``} @ ${Array.isArray(set.item) ? set.item.map(x => dex.getItem(x).name).join(' / ') : dex.getItem(set.item).name}</li>`;
buf += `<li>Ability: ${Array.isArray(set.ability) ? set.ability.map(x => dex.getAbility(x).name).join(' / ') : dex.getAbility(set.ability).name}</li>`;
if (set.shiny) buf += `<li>Shiny: ${typeof set.shiny === 'number' ? `Sometimes` : `Yes`}</li>`;
if (set.evs) {
const evs: string[] = [];
let ev: StatName;
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.join(" / ") : formatNature(set.nature)} Nature</li>`;
}
if (set.ivs) {
const ivs: string[] = [];
let iv: StatName;
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.getMove(x).name).join(" / ") : dex.getMove(moveid).name}</li>`;
}
const italicize = !baseDex.getMove(set.signatureMove).exists;
buf += `<li>- ${italicize ? `<i>` : ``}${dex.getMove(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) || 'CAP',
};
if (sigMove.isNonstandard === "Past" && dex.gen >= 8) details["&#10007; Past Gens Only"] = "";
if (sigMove.secondary || sigMove.secondaries) details["&#10003; Secondary effect"] = "";
if (sigMove.flags['contact']) details["&#10003; Contact"] = "";
if (sigMove.flags['sound']) details["&#10003; Sound"] = "";
if (sigMove.flags['bullet']) details["&#10003; Bullet"] = "";
if (sigMove.flags['pulse']) details["&#10003; Pulse"] = "";
if (!sigMove.flags['protect'] && !/(ally|self)/i.test(sigMove.target)) details["&#10003; Bypasses Protect"] = "";
if (sigMove.flags['authentic']) details["&#10003; Bypasses Substitutes"] = "";
if (sigMove.flags['defrost']) details["&#10003; Thaws user"] = "";
if (sigMove.flags['bite']) details["&#10003; Bite"] = "";
if (sigMove.flags['punch']) details["&#10003; Punch"] = "";
if (sigMove.flags['powder']) details["&#10003; Powder"] = "";
if (sigMove.flags['reflectable']) details["&#10003; Bounceable"] = "";
if (sigMove.flags['charge']) details["&#10003; Two-turn move"] = "";
if (sigMove.flags['recharge']) details["&#10003; Has recharge turn"] = "";
if (sigMove.flags['gravity']) details["&#10007; Suppressed by Gravity"] = "";
if (sigMove.flags['dance']) details["&#10003; Dance 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;
const stats: {[k in BoostName]: string} = {
atk: 'Attack', def: 'Defense', spa: 'Sp. Atk', spd: 'Sp. Def', spe: 'Speed', accuracy: 'Accuracy', evasion: 'Evasiveness',
};
let h: BoostName;
for (h in boost) {
details["Z-Effect"] += ` ${stats[h]} +${boost[h]}`;
}
} else if (sigMove.isZ && typeof sigMove.isZ === 'string') {
details["&#10003; Z-Move"] = "";
const zCrystal = dex.getItem(sigMove.isZ);
details["Z-Crystal"] = zCrystal.name;
if (zCrystal.itemUser) {
details["User"] = zCrystal.itemUser.join(", ");
details["Required Move"] = dex.getItem(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.keys(details).map(detail => {
if (details[detail] === '') return detail;
return `<font color="#686868">${detail}:</font> ${details[detail]}`;
}).join("&nbsp;|&ThickSpace;")}</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.getItem(set.item);
const sigItem = dex.getItem(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.keys(details).map(detail => {
if (details[detail] === '') return detail;
return `<font color="#686868">${detail}:</font> ${details[detail]}`;
}).join("&nbsp;|&ThickSpace;")}</font>`;
}
}
return buf;
}
function generateSSBAbilityInfo(set: SSBSet, dex: ModdedDex, baseDex: ModdedDex) {
let buf = ``;
if (!Array.isArray(set.ability) && !baseDex.getAbility(set.ability).exists) {
const sigAbil = Dex.deepClone(dex.getAbility(set.ability));
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) || 'CAP',
};
buf += `<font size="1">${Object.keys(details).map(detail => {
if (details[detail] === '') return detail;
return `<font color="#686868">${detail}:</font> ${details[detail]}`;
}).join("&nbsp;|&ThickSpace;")}</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.getSpecies(species);
const newSpecies = dex.getSpecies(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.getSpecies(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.keys(details).map(detail => {
if (details[detail] === '') return detail;
return `<font color="#686868">${detail}:</font> ${details[detail]}`;
}).join("&nbsp;|&ThickSpace;")}</font>`;
}
return buf;
}
function generateSSBInnateInfo(name: string, dex: ModdedDex, baseDex: ModdedDex) {
let buf = ``;
// Special casing for users whose usernames are already existing, i.e. Perish Song
let effect = dex.getEffect(name + 'user');
let longDesc = ``;
const baseAbility = Dex.deepClone(baseDex.getAbility('noability'));
// @ts-ignore hack to record the name of the innate abilities without using name
if (effect.exists && effect.innateName && (effect.desc || effect.shortDesc)) {
// @ts-ignore hack
baseAbility.name = effect.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;
}
} else {
effect = dex.getEffect(name);
// @ts-ignore hack
if (effect.exists && effect.innateName && (effect.desc || effect.shortDesc)) {
// @ts-ignore hack
baseAbility.name = effect.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: '8'};
buf += `<font size="1">${Object.keys(details).map(detail => {
if (details[detail] === '') return detail;
return `<font color="#686868">${detail}:</font> ${details[detail]}`;
}).join("&nbsp;|&ThickSpace;")}</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('gen8superstaffbros4');
if (!Object.keys(ssbSets).map(toID).includes(toID(target))) {
return {e: `Error: ${target.trim()} doesn't have a [Gen 8] Super Staff Bros 4 set.`};
}
let displayName = '';
const names = [];
for (const member in ssbSets) {
if (toID(member).startsWith(toID(target))) names.push(member);
if (toID(member) === toID(target)) displayName = member;
}
let buf = '';
for (const name of names) {
if (buf) buf += `<hr>`;
const set = ssbSets[name];
const mutatedSpecies = dex.getSpecies(set.species);
if (!set.skip) {
buf += Utils.html`<h1><psicon pokemon="${mutatedSpecies.id}">${displayName === 'yuki' ? name : displayName}</h1>`;
} else {
buf += `<details><summary><psicon pokemon="${set.species}"><strong>${name.split('-').slice(1).join('-') + ' forme'}</strong></summary>`;
}
buf += generateSSBSet(set, dex, baseDex);
const item = dex.getItem(set.item as string);
if (!set.skip || set.signatureMove !== ssbSets[set.skip].signatureMove) {
const sigMove = baseDex.getMove(set.signatureMove).exists && !Array.isArray(set.item) &&
typeof item.zMove === 'string' ?
dex.getMove(item.zMove) : dex.getMove(set.signatureMove);
buf += generateSSBMoveInfo(sigMove, dex);
if (sigMove.id === 'blackbird') buf += generateSSBMoveInfo(dex.getMove('gaelstrom'), dex);
}
buf += generateSSBItemInfo(set, dex, baseDex);
buf += generateSSBAbilityInfo(set, dex, baseDex);
buf += generateSSBInnateInfo(name, dex, baseDex);
buf += generateSSBPokemonInfo(set.species, dex, baseDex);
if (!Array.isArray(set.item) && item.megaStone) {
buf += generateSSBPokemonInfo(item.megaStone, dex, baseDex);
// Psynergy, Struchni, and Raj.shoot have itemless Mega Evolutions
} else if (['Aggron', 'Rayquaza'].includes(set.species)) {
buf += generateSSBPokemonInfo(`${set.species}-Mega`, dex, baseDex);
} else if (set.species === 'Charizard') {
buf += generateSSBPokemonInfo('Charizard-Mega-X', dex, baseDex);
}
if (set.skip) buf += `</details>`;
}
return buf;
}
export const commands: ChatCommands = {
randbats: 'randombattles',
randombattles(target, room, user) {
@@ -428,4 +797,17 @@ export const commands: ChatCommands = {
cap1v1help: [
`/cap1v1 [pokemon] - Displays a Pok\u00e9mon's CAP 1v1 sets.`,
],
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. Brawl set and custom features.`,
],
};

View File

@@ -1533,7 +1533,7 @@ export class Battle {
this.makeRequest('move');
}
private maybeTriggerEndlessBattleClause(
maybeTriggerEndlessBattleClause(
trappedBySide: boolean[], stalenessBySide: ('internal' | 'external' | undefined)[]
) {
if (this.turn <= 100 || !this.ruleTable.has('endlessbattleclause')) return;

View File

@@ -240,7 +240,7 @@ export interface MoveData extends EffectData, MoveEventMethods, HitEffect {
baseMove?: string;
}
export type ModdedMoveData = MoveData | Partial<Omit<MoveData, 'name'>> & {inherit: true};
export type ModdedMoveData = MoveData | Partial<Omit<MoveData, 'name'>> & {inherit: true, gen?: number};
export interface Move extends Readonly<BasicEffect & MoveData> {
readonly effectType: 'Move';

View File

@@ -372,6 +372,7 @@ interface ModdedBattleScriptsData extends Partial<BattleScriptsData> {
this: Battle, baseDamage: number, pokemon: Pokemon, target: Pokemon, move: ActiveMove, suppressMessages?: boolean
) => void;
natureModify?: (this: Battle, stats: StatsTable, set: PokemonSet) => StatsTable;
nextTurn?: (this: Battle) => void;
runMove?: (
this: Battle, moveOrMoveName: Move | string, pokemon: Pokemon, targetLoc: number, sourceEffect?: Effect | null,
zMove?: string, externalMove?: boolean, maxMove?: string, originalTarget?: Pokemon