diff --git a/.eslintrc.js b/.eslintrc.js index f3aa66b17..dd4833c54 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -28,6 +28,7 @@ module.exports = { // Generic global variables "Config": false, "BattleSearch": false, "Storage": false, "Dex": false, "DexSearch": false, + "ModConfig": false, "ModSprites": false, "app": false, "toID": false, "toRoomid": false, "toUserid": false, "toName": false, "PSUtils": false, "MD5": false, "ChatHistory": false, "Topbar": false, "UserList": false, diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b1ea8233f..fea7815ce 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,9 +5,9 @@ name: Node.js CI on: push: - branches: [ master ] + branches: [ main ] pull_request: - branches: [ master ] + branches: [ main ] jobs: build: @@ -16,15 +16,24 @@ jobs: strategy: matrix: - node-version: [14.x] + node-version: [18.x] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} - uses: Zarel/setup-node@patch-1 + uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} - run: npm install - - run: npm run test + - run: npm run build --if-present + - run: node build full + - name: Build DH2 + run: npm run build --if-present + - name: start DH2 server instance + run: | + node caches/DH2/pokemon-showdown & + while ! curl -s http://localhost:8000 -o /dev/null; do + sleep 60 + done env: CI: true diff --git a/.gitignore b/.gitignore index 5a210bb73..258c56f94 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,16 @@ npm-debug.log /play.pokemonshowdown.com/js/miniedit.js /play.pokemonshowdown.com/ads.txt +/website/.well-known/ +/website/.pages-cached/ +/website/files/ +/website/images/ +/website/ads.txt +/DH2/* +/DH2/ +/website/replays/index.html +/website/replays/js/ +node_modules /pokemonshowdown.com/.well-known/ /pokemonshowdown.com/.pages-cached/ /pokemonshowdown.com/files/ diff --git a/build-tools/build-indexes b/build-tools/build-indexes old mode 100755 new mode 100644 index aa13c390f..3d7719068 --- a/build-tools/build-indexes +++ b/build-tools/build-indexes @@ -4,32 +4,57 @@ const fs = require("fs"); const path = require('path'); const child_process = require("child_process"); +const server_repo = require("./server-repo"); const rootDir = path.resolve(__dirname, '..'); process.chdir(rootDir); -if (!fs.existsSync('caches/pokemon-showdown')) { - child_process.execSync('git clone https://github.com/smogon/pokemon-showdown.git', { - cwd: 'caches', +const debug = true; + +process.stdout.write("Syncing data from Git repository... "); +if (!fs.existsSync('./caches/DH2')) { + child_process.execSync('git clone ' + server_repo + ' caches/DH2', { }); + child_process.execSync("git pull " + server_repo, {cwd: 'caches/DH2'}); } -process.stdout.write("Syncing data from Git repository... "); -child_process.execSync('git pull', {cwd: 'caches/pokemon-showdown'}); -child_process.execSync('npm run build', {cwd: 'caches/pokemon-showdown'}); +child_process.execSync('npm run build', {cwd: 'caches/DH2'}); + console.log("DONE"); -const Dex = require('../caches/pokemon-showdown/dist/sim/dex').Dex; +const Dex = require('../caches/DH2/dist/sim/dex').Dex; const toID = Dex.toID; +const ModConfigData = require('../config/mod-config').ModConfigData; +const ModConfig = ModConfigData.ClientMods; +var Formats = require('../caches/DH2/dist/config/formats.js').Formats; + +for (const modid in Dex.dexes) { + try { + if ((/gen\d/.test(modid) && modid.length === 4) || modid === 'base') continue; + let teambuilderConfig = Dex.dexes[modid].data.Scripts.teambuilderConfig; + teambuilderConfig = teambuilderConfig ? teambuilderConfig : {}; + if(teambuilderConfig.moveIsNotUseless) teambuilderConfig.moveIsNotUseless = JSON.stringify(teambuilderConfig.moveIsNotUseless.toString()); + ModConfig[modid] = ModConfig[modid] ? ModConfig[modid] : {}; + ModConfig[modid] = Object.assign(ModConfig[modid], teambuilderConfig); + } catch(err) { + // delete ModConfig[modid]; + // delete BattleTeambuilderTable[modid]; + if (debug) { + console.log("WARNING: Failed to load config data for " + modid); + console.log("This was the error:"); + console.log(err); + } + } +} process.stdout.write("Loading gen 6 data... "); Dex.includeData(); console.log("DONE"); function es3stringify(obj) { - const buf = JSON.stringify(obj); - return buf.replace(/\"([A-Za-z][A-Za-z0-9]*)\"\:/g, (fullMatch, key) => ( - ['return', 'new', 'delete'].includes(key) ? fullMatch : `${key}:` - )); + let buf = JSON.stringify(obj); + buf = buf.replace(/\"([A-Za-z][A-Za-z0-9]*)\"\:/g, '$1:'); + buf = buf.replace(/return\:/g, '"return":').replace(/new\:/g, '"new":').replace(/delete\:/g, '"delete":'); + return buf; } function requireNoCache(pathSpec) { @@ -37,12 +62,42 @@ function requireNoCache(pathSpec) { return require(pathSpec); } +process.stdout.write("Building `data/search-index.js`... "); +buildSearchIndex(); +console.log("DONE"); + +process.stdout.write("Building `data/formats.js`... "); +buildFormats(); +console.log("DONE"); + +process.stdout.write("Building `data/teambuilder-tables.js`... "); +buildTeambuilderTables(); +console.log("DONE"); + +process.stdout.write("Building `data/pokedex.js`... "); +buildPokedex(); +console.log("DONE"); + +process.stdout.write("Building `data/moves,items,abilities,typechart,learnsets.js`..."); +buildMoves(); +buildItems(); +buildAbilities(); +buildTypechart(); +buildLearnsets(); +console.log("DONE"); + +process.stdout.write("Building aliases, formats-data, mod-sprites, text.js..."); +buildAliases(); +buildFormatsData(); +buildModSprites(); +buildText(); +console.log("DONE"); + /********************************************************* * Build search-index.js *********************************************************/ -{ - process.stdout.write("Building `data/search-index.js`... "); +function buildSearchIndex() { let index = []; @@ -78,7 +133,9 @@ function requireNoCache(pathSpec) { index.push('moves article'); // generate aliases + const usedIDs = []; function generateAlias(id, name, type) { + usedIDs.push(id); let i = name.lastIndexOf(' '); if (i < 0) i = name.lastIndexOf('-'); if (name.endsWith('-Mega-X') || name.endsWith('-Mega-Y')) { @@ -224,6 +281,31 @@ function requireNoCache(pathSpec) { generateAlias(id, name, 'ability'); } + const infoTable = {Pokedex: 'pokemon', Moves: 'move', Items: 'item', Abilities: 'ability', TypeChart: 'type'}; + for (const modid in ModConfig) { + for (const key in infoTable) { + try { + const modDex = Dex.mod(modid); + if (!modDex || !modDex.data || !modDex.data[key]) continue; + for (let id in modDex.data[key]) { + if (!modDex.data[key][id]) continue; + if (!usedIDs.includes(toID(id)) && id in Dex.data.TypeChart === false) { + index.push(toID(id) + ' ' + infoTable[key]); + usedIDs.push(toID(id)); + const name = modDex.data[key][id].name; + if (name) generateAlias(id, name, infoTable[key]); + } + } + } catch(err) { + if (debug) { + console.log("WARNING: Failed to load search data for " + modid); + console.log("This was the error:"); + console.log(err); + } + } + } + } + index.sort(); // manually rearrange @@ -303,15 +385,11 @@ function requireNoCache(pathSpec) { fs.writeFileSync('play.pokemonshowdown.com/data/search-index.js', buf); } -console.log("DONE"); - /********************************************************* * Build teambuilder-tables.js *********************************************************/ -process.stdout.write("Building `data/teambuilder-tables.js`... "); - -{ +function buildTeambuilderTables() { const BattleTeambuilderTable = {}; let buf = '// DO NOT EDIT - automatically built with build-tools/build-indexes\n\n'; @@ -424,6 +502,8 @@ process.stdout.write("Building `data/teambuilder-tables.js`... "); } if (isVGC) { if (species.isNonstandard && species.isNonstandard !== 'Gigantamax') return 'Illegal'; + // these are breaking certain mods, disable them for now. + if (baseSpecies.tags.includes('Mythical')) return 'Mythical'; if (baseSpecies.tags.includes('Restricted Legendary')) return 'Restricted Legendary'; if (species.tier === 'NFE') return 'NFE'; @@ -650,8 +730,6 @@ process.stdout.write("Building `data/teambuilder-tables.js`... "); const specificItems = [['header', "Pokémon-specific items"]]; const poorItems = [['header', "Usually useless items"]]; const badItems = [['header', "Useless items"]]; - const unreleasedItems = []; - if (genNum === 6) unreleasedItems.push(['header', "Unreleased"]); for (const id of itemList) { const item = Dex.mod(gen).items.get(id); if (item.gen > genNum) { @@ -838,10 +916,7 @@ process.stdout.write("Building `data/teambuilder-tables.js`... "); default: if ( item.name.endsWith(" Ball") || item.name.endsWith(" Fossil") || item.name.startsWith("Fossilized ") || - item.name.endsWith(" Sweet") || item.name.endsWith(" Apple") - ) { - badItems.push(id); - } else if (item.name.startsWith("TR")) { + item.name.endsWith(" Sweet") || item.name.endsWith(" Apple") || item.name.startsWith("TR")) { badItems.push(id); } else if (item.name.endsWith(" Gem") && item.name !== "Normal Gem") { if (genNum >= 6) { @@ -851,18 +926,29 @@ process.stdout.write("Building `data/teambuilder-tables.js`... "); } else { goodItems.push(id); } - } else if (item.name.endsWith(" Drive")) { - specificItems.push(id); - } else if (item.name.endsWith(" Memory")) { - specificItems.push(id); - } else if (item.name.startsWith("Rusted")) { - specificItems.push(id); - } else if (item.itemUser) { - specificItems.push(id); - } else if (item.megaStone) { + } else if (item.name.endsWith(" Drive") || item.name.endsWith(" Memory") + || item.name.startsWith("Rusted") || item.itemUser|| item.megaStone) { specificItems.push(id); } else { - goodItems.push(id); + switch (item.rating) { + case 3: + greatItems.push(id); + break; + case 2: + goodItems.push(id); + break; + // outclassed items + case 1: + poorItems.push(id); + break; + // Fling-only + case 0: + badItems.push(id); + break; + default: + goodItems.push(id); + break; + } } } } @@ -871,7 +957,6 @@ process.stdout.write("Building `data/teambuilder-tables.js`... "); items.push(...specificItems); items.push(...poorItems); items.push(...badItems); - items.push(...unreleasedItems); } // @@ -881,6 +966,49 @@ process.stdout.write("Building `data/teambuilder-tables.js`... "); const gen3HMs = new Set(['cut', 'fly', 'surf', 'strength', 'flash', 'rocksmash', 'waterfall', 'dive']); const gen4HMs = new Set(['cut', 'fly', 'surf', 'strength', 'rocksmash', 'waterfall', 'rockclimb']); + function getLearnsetStr(moveid, learnMoveArr, pokemonid = "", modid = "") { + let learnsetStr = ''; + if (!learnMoveArr || !learnMoveArr.map) { + return ""; + } + const gens = learnMoveArr.map(x => Number(x[0])); + const minGen = Math.min(...gens); + const vcOnly = (minGen === 7 && learnMoveArr.every(x => x[0] !== '7' || x === '7V') || + minGen === 8 && learnMoveArr.every(x => x[0] !== '8' || x === '8V') || + minGen === 9 && learnMoveArr.every(x => x[0] !== '9' || x === '9V')); + + if (minGen <= 4 && minGen > 2 && (gen3HMs.has(moveid) || gen4HMs.has(moveid))) { + let legalGens = ''; + let available = false; + + if (minGen === 3) { + legalGens += '3'; + available = true; + } + if (available) available = !gen3HMs.has(moveid); + + if (available || gens.includes(4)) { + legalGens += '4'; + available = true; + } + if (available) available = !gen4HMs.has(moveid); + + let minUpperGen = available ? 5 : Math.min( + ...gens.filter(gen => gen > 4) + ); + legalGens += '0123456789'.slice(minUpperGen); + learnsetStr = legalGens; + } else { + learnsetStr = '0123456789'.slice(minGen); + } + + if (gens.indexOf(6) >= 0) learnsetStr += 'p'; + if (gens.indexOf(7) >= 0 && !vcOnly) learnsetStr += 'q'; + if (gens.indexOf(8) >= 0 && !vcOnly) learnsetStr += 'g'; + if (gens.indexOf(9) >= 0 && !vcOnly) learnsetStr += 'a'; + return learnsetStr; + } + const learnsets = {}; BattleTeambuilderTable.learnsets = learnsets; for (const id in Dex.data.Learnsets) { @@ -1114,9 +1242,9 @@ process.stdout.write("Building `data/teambuilder-tables.js`... "); // Client relevant data that should be overriden by past gens and mods const overrideSpeciesKeys = ['abilities', 'baseStats', 'cosmeticFormes', 'isNonstandard', 'requiredItems', 'types', 'unreleasedHidden']; - const overrideMoveKeys = ['accuracy', 'basePower', 'category', 'desc', 'flags', 'isNonstandard', 'pp', 'priority', 'shortDesc', 'target', 'type']; + const overrideMoveKeys = ['accuracy', 'basePower', 'category', 'desc', 'flags', 'isNonstandard', 'noSketch', 'pp', 'priority', 'shortDesc', 'target', 'type', 'viable']; const overrideAbilityKeys = ['desc', 'flags', 'isNonstandard', 'rating', 'shortDesc']; - const overrideItemKeys = ['desc', 'fling', 'isNonstandard', 'naturalGift', 'shortDesc']; + const overrideItemKeys = ['desc', 'fling', 'isNonstandard', 'rating', 'shortDesc']; // // Past gen table @@ -1143,16 +1271,53 @@ process.stdout.write("Building `data/teambuilder-tables.js`... "); } } - const overrideMoveData = {}; - BattleTeambuilderTable[gen].overrideMoveData = overrideMoveData; + const overrideDexInfo = {}; + BattleTeambuilderTable[gen].overrideDexInfo = overrideDexInfo; + for (const id in genData.Pokedex) { + const modEntry = genData.Pokedex[id]; + const baseEntry = Dex.data.Pokedex[id]; + if (typeof baseEntry === 'undefined') { + overrideDexInfo[id] = {}; + overrideDexInfo[id] = modEntry; + overrideDexInfo[id].exists = true; + continue; + } + for (const key in modEntry) { + const modString = JSON.stringify(modEntry[key]); + const baseString = JSON.stringify(baseEntry[key]); + if (modString !== baseString) { + if (!overrideDexInfo[id]) overrideDexInfo[id] = {}; + if (modString === undefined) overrideDexInfo[id][key] = null; + else try { + overrideDexInfo[id][key] = JSON.parse(modString); + } catch (e) { + // Vivillon-Fancy coded with intentional undefined fields in the source, so we'll escape it + if (id === 'vivillonfancy') continue; + console.log(gen + " " + id + " " + key + " parsed an invalid value: " + modString); + continue; + } + } + } + } + const overrideMoveInfo = {}; + BattleTeambuilderTable[gen].overrideMoveInfo = overrideMoveInfo; for (const id in genData.Moves) { - const curEntry = genDex.moves.get(id); - const nextEntry = nextGenDex.moves.get(id); - for (const key of overrideMoveKeys) { - if (key === 'category' && genNum <= 3) continue; - if (JSON.stringify(curEntry[key]) !== JSON.stringify(nextEntry[key])) { - if (!overrideMoveData[id]) overrideMoveData[id] = {}; - overrideMoveData[id][key] = curEntry[key]; + const modEntry = genData.Moves[id]; + const baseEntry = Dex.data.Moves[id]; + if (typeof baseEntry === 'undefined') { + overrideMoveInfo[id] = {}; + overrideMoveInfo[id] = modEntry; + overrideMoveInfo[id].exists = true; + continue; + } + for (const key in modEntry) { + if (!overrideMoveKeys.includes(key)) continue; + const modString = JSON.stringify(modEntry[key]); + const baseString = JSON.stringify(baseEntry[key]); + if (modString !== baseString) { + if (!overrideMoveInfo[id]) overrideMoveInfo[id] = {}; + //if (modString === undefined) overrideMoveInfo[id][key] = null; + overrideMoveInfo[id][key] = JSON.parse(modString); } } } @@ -1176,9 +1341,13 @@ process.stdout.write("Building `data/teambuilder-tables.js`... "); const curEntry = genDex.items.get(id); const nextEntry = nextGenDex.items.get(id); for (const key of overrideItemKeys) { - if (JSON.stringify(curEntry[key]) !== JSON.stringify(nextEntry[key])) { + const curString = JSON.stringify(curEntry[key]); + const nextString = JSON.stringify(nextEntry[key]); + if (curString !== nextString) { if (!overrideItemData[id]) overrideItemData[id] = {}; - overrideItemData[id][key] = curEntry[key]; + if (curString === undefined) overrideItemData[id][key] = null; + else overrideItemData[id][key] = JSON.parse(curString); + if(key === 'desc' && !curEntry['shortDesc']) overrideItemData[id]['shortDesc'] = JSON.parse(curString); } } } @@ -1190,7 +1359,7 @@ process.stdout.write("Building `data/teambuilder-tables.js`... "); for (const id in nextGenData.TypeChart) { const curEntry = genData.TypeChart[id]; const nextEntry = nextGenData.TypeChart[id]; - if (curEntry.isNonstandard) { + if (!curEntry) { removeType[id] = true; continue; } @@ -1199,9 +1368,384 @@ process.stdout.write("Building `data/teambuilder-tables.js`... "); } } } + const sliceTiers = ["OU", "AG", "Uber", "UU", "(UU)", "RU", "NU", "(NU)", "PU", "(PU)", "ZUBL", "ZU", "NFE", "LC", "DOU", "DUU", "(DUU)", "New", "Legal", "Regular", "Restricted Legendary", "CAP LC"]; + function buildTiers(modid, tierTable, tiers, customTiers, formatSlices, tierOrder) { + for (const tier of tierOrder) { + if (ModConfig[modid].excludeStandardTiers) break; + if (sliceTiers.includes(tier)) { + let usedTier = tier; + if (usedTier === "(UU)") usedTier = "RU"; + if (usedTier === "(NU)") usedTier = "PU"; + if (usedTier === "(PU)") usedTier = "ZU"; + if (usedTier === "(DUU)") usedTier = "DNU"; + formatSlices[usedTier] = tiers.length; + } + if (!tierTable[tier]) continue; + tiers.push(['header', tier]); + tiers.push(...tierTable[tier]); + } + for (const tier of ModConfig[modid].customTiers) { + if (!tierTable[tier]) continue; + customTiers.push(['header', tier]); + customTiers.push(...tierTable[tier]); + } + } + for (const modConfigId of Object.keys(ModConfig)) { + if (BattleTeambuilderTable[modConfigId]) continue; + try { // catch loading errors so the whole thing doesn't break if one mod does + // Gather data about formats + const modDex = Dex.mod(modConfigId); + const modData = modDex.data; + if (modData.Scripts.teambuilderConfig) ModConfig[modConfigId] = modData.Scripts.teambuilderConfig; + let modGen = modDex.gen; + const petModFormats = {}; + ModConfig[modConfigId].formats = petModFormats; + // Find formats using this mod + let hasSinglesFormats = false; + let hasDoublesFormats = false; + for (const i in Dex.formats.formatsListCache) { + // const format = Dex.formats.get(i); + // const formatMod = toID(format.mod); + const formatCacheMod = toID(Dex.formats.formatsListCache[i].mod); + const formatCacheName = toID(Dex.formats.formatsListCache[i].name); + if (formatCacheMod !== modConfigId) continue; + // petModFormats[formatCacheMod] = Formats.find(m => m.mod === modConfigId); + petModFormats[formatCacheName] = Formats.find(m => toID(m.name) === formatCacheName); + const format = petModFormats[formatCacheName]; + let maxGen = + formatCacheName.substr(0, 3) === 'gen' && parseInt(formatCacheMod.substr(3, 1)) < 10 ? parseInt(formatCacheMod.substr(3, 1)) : 9; + if(format.gameType === 'doubles') hasDoublesFormats = true; + else hasSinglesFormats = true; + // format.name = formatCacheMod; + if (!format.banlist) format.banlist = []; + if (!format.unbanlist) format.unbanlist = []; + if (!format.teambuilderFormat) format.teambuilderFormat = ''; + format.bans = []; + format.unbans = []; + if (!!format.banlist) { + for (const name of format.banlist) { + let id = toID(name); + if(name.endsWith('-Base')) id = id.substr(0, id.length - 4); + if (id in Dex.data.Pokedex || id in modData.Pokedex) format.bans.push(id); + const formes = modData.Pokedex[id]?.otherFormes; + if(formes && toID(name) === id){ //id wasn't modified for base forme ban, therefore all formes are banned + for(const forme of formes){ + const formeid = toID(forme); + if (formeid in Dex.data.Pokedex || formeid in modData.Pokedex) format.bans.push(formeid); + } + } + } + if (!!format.banlist && format.banlist.includes("All Pokemon")) { + format.bans.push('All Pokemon'); + for (const name of format.unbanlist) { + let id = toID(name); + if(name.endsWith('-Base')) id = id.substr(0, id.length - 4); + if (id in Dex.data.Pokedex || id in modData.Pokedex) format.unbans.push(id); + const formes = modData.Pokedex[id]?.otherFormes; + if(formes && toID(name) === id){ //id wasn't modified for base forme ban, therefore all formes are banned + for(const forme of formes){ + const formeid = toID(forme); + if (formeid in Dex.data.Pokedex || formeid in modData.Pokedex) format.unbans.push(formeid); + } + } + } + } + } + if (formatCacheMod.startsWith('gen') && maxGen < Number(formatCacheMod.charAt(3))) maxGen = Number(formatCacheMod.charAt(3)); + if (maxGen !== 9) modGen = maxGen; + } + // Find any nonstandard tiers used by this mod + const standardTiers = ['uber', 'ou', 'uubl', 'uu', 'rubl', 'ru', 'nubl', 'nu', 'publ', + 'pu', 'zu', 'zubl', 'nfe', 'lcuber', 'lc', 'cap', 'caplc', 'capnfe', 'ag', 'duber', + 'dou', 'dbl', 'duu', 'dnu', 'illegal', 'unreleased']; + if (!ModConfig[modConfigId].customTiers) ModConfig[modConfigId].customTiers = []; + if (!ModConfig[modConfigId].customDoublesTiers) ModConfig[modConfigId].customDoublesTiers = []; + for (const speciesid in modData.FormatsData) { + if (!modData.FormatsData[speciesid]) { + if(debug && !(speciesid in Dex.data.Pokedex)) console.log('Warning: ' + speciesid + ' does not have tiering data in ' + modConfigId); + continue; + } + const tier = modData.FormatsData[speciesid].tier; + const doublesTier = modData.FormatsData[speciesid].doublesTier; + if (tier === undefined && doublesTier === undefined) continue; + if (!standardTiers.includes(toID(tier)) && + !ModConfig[modConfigId].customTiers.includes(tier)) { + ModConfig[modConfigId].customTiers.push(tier); + } + if (!standardTiers.includes(toID(doublesTier)) && + !ModConfig[modConfigId].customDoublesTiers.includes(doublesTier)) { + ModConfig[modConfigId].customDoublesTiers.push(doublesTier); + } + } + BattleTeambuilderTable[modConfigId] = {}; + //tiers + const tierTable = {}; + const pokemon = Object.keys(modData.Pokedex); + pokemon.sort(); + const overrideTier = {}; + if(hasSinglesFormats) { + BattleTeambuilderTable[modConfigId].overrideTier = overrideTier; + } + const doublesOverrideTier = {}; + if (hasDoublesFormats) { + BattleTeambuilderTable[modConfigId].doubles = {}; + BattleTeambuilderTable[modConfigId].doubles.overrideTier = doublesOverrideTier; + } + for (const id of pokemon) { + const species = modDex.species.get(id); + const tier = species.tier; + overrideTier[species.id] = tier; + if (species.forme && JSON.stringify(modData.Pokedex[species.id]) === JSON.stringify(Dex.data.Pokedex[species.id]) && !ModConfig.showAllFormes) { // hardcode from main + if ( + [ + 'Aegislash', 'Castform', 'Cherrim', 'Cramorant', 'Eiscue', 'Meloetta', 'Mimikyu', 'Minior', 'Morpeko', 'Wishiwashi', + ].includes(species.baseSpecies) || species.forme.includes('Totem') || species.forme.includes('Zen') + ) { + continue; + } + } + if (!tierTable[tier]) tierTable[tier] = []; + tierTable[tier].push(id); + if (hasDoublesFormats) { + const doublesTier = species.doublesTier; + doublesOverrideTier[species.id] = species.doublesTier; + if (!tierTable[doublesTier]) tierTable[doublesTier] = []; + if (tier !== doublesTier) tierTable[doublesTier].push(id); + } + } + for (const tier in tierTable) { + for (const formatid in petModFormats) { + const banlist = petModFormats[formatid].banlist; + if (!!banlist.includes(tier) || !!banlist.includes(toID(tier))) { + for (const i in tierTable[tier]) { + const speciesid = tierTable[tier][i]; + if (!petModFormats[formatid].banlist.includes(speciesid)) petModFormats[formatid].bans.push(speciesid); + } + } + } + } + const tiers = []; + BattleTeambuilderTable[modConfigId].tiers = tiers; + const doublesTiers = []; + if (hasDoublesFormats) BattleTeambuilderTable[modConfigId].doubles.tiers = doublesTiers; + const customTiers = []; + BattleTeambuilderTable[modConfigId].customTiers = customTiers; + const customDoublesTiers = []; + if (hasDoublesFormats) BattleTeambuilderTable[modConfigId].doubles.customTiers = customDoublesTiers; + const formatSlices = {}; + BattleTeambuilderTable[modConfigId].formatSlices = formatSlices; + const doublesFormatSlices = {}; + if (hasDoublesFormats) BattleTeambuilderTable[modConfigId].doubles.formatSlices = doublesFormatSlices; + const tierOrder = ["OU", "AG", "Uber", "(Uber)", "OU", "(OU)", "UUBL", "UU", "RUBL", "RU", "NUBL", "NU", "PUBL", "PU", "(PU)", "ZUBL", "ZU", "New", "NFE", "LC Uber", "LC", "Unreleased"]; + const tierOrderDoubles = ["DUber", "(DUber)", "DOU", "DBL", "(DOU)", "DUU", "(DUU)", "New", "NFE", "LC Uber", "LC"]; + if (hasSinglesFormats) buildTiers(modConfigId, tierTable, tiers, customTiers, formatSlices, tierOrder); + if (hasDoublesFormats) buildTiers(modConfigId, tierTable, doublesTiers, customDoublesTiers, doublesFormatSlices, tierOrderDoubles); + //pokemon stats + const overrideDexInfo = {}; + BattleTeambuilderTable[modConfigId].overrideDexInfo = overrideDexInfo; + for (const id in modData.Pokedex) { + const modEntry = modData.Pokedex[id]; + const baseEntry = Dex.data.Pokedex[id]; + if (typeof baseEntry === 'undefined') { + overrideDexInfo[id] = {}; + overrideDexInfo[id] = modEntry; + overrideDexInfo[id].exists = true; + continue; + } + for (const key in modEntry) { + const modString = JSON.stringify(modEntry[key]); + const baseString = JSON.stringify(baseEntry[key]); + if (modString !== baseString) { + if (!overrideDexInfo[id]) overrideDexInfo[id] = {}; + if (modString === undefined) overrideDexInfo[id][key] = undefined; + else overrideDexInfo[id][key] = JSON.parse(modString); + } + } + } + //learnsets + const overrideLearnsets = {}; + BattleTeambuilderTable[modConfigId].overrideLearnsets = overrideLearnsets; + for (const id in modDex.data.Learnsets) { + const learnset = modDex.data.Learnsets[id].learnset; + const baseLearnset = Dex.data.Learnsets[id] ? Dex.data.Learnsets[id].learnset : {}; + if (!learnset) continue; + if (!learnsets[id]) learnsets[id] = {}; + for (const moveid in learnset) { + const newLearnsetEntry = getLearnsetStr(moveid, learnset[moveid], id, modConfigId); + let baseLearnsetEntry = learnsets[id][moveid]; + if (modGen <= 2 && G2Learnsets[id]) baseLearnsetEntry = G2Learnsets[id][moveid]; + const baseLsetNoEarlyGen = baseLearnsetEntry ? baseLearnsetEntry.replace(1, '').replace(2, '') : ''; + if (newLearnsetEntry !== baseLearnsetEntry && newLearnsetEntry !== baseLsetNoEarlyGen) { + if (!overrideLearnsets[id]) overrideLearnsets[id] = {}; + overrideLearnsets[id][moveid] = newLearnsetEntry; + } + } + for (const moveid in baseLearnset) { + if (baseLearnset[moveid] && !learnset[moveid]) { + if (!overrideLearnsets[id]) overrideLearnsets[id] = {}; + overrideLearnsets[id][moveid] = 'r'; + } + } + } + //items + let items = []; + const fullItemName = {}; + BattleTeambuilderTable[modConfigId].fullItemName = fullItemName; + BattleTeambuilderTable[modConfigId].items = items; + const overrideItemInfo = {}; + BattleTeambuilderTable[modConfigId].overrideItemData = overrideItemInfo; + + const greatItems = []; + const goodItems = []; + const specificItems = []; + const poorItems = []; + const badItems = []; + for (const id in modData.Items) { + const modEntry = modData.Items[id]; + const baseEntry = Dex.data.Items[id]; + if (typeof baseEntry === 'undefined') { + overrideItemInfo[id] = {}; + overrideItemInfo[id] = modEntry; + overrideItemInfo[id].exists = true; + fullItemName[id] = modData.Items[id].name; + } else { + if(baseEntry.gen > modGen) continue; + for (const key in modEntry) { + if (!overrideItemKeys.includes(key)) continue; + const modString = JSON.stringify(modEntry[key]); + const baseString = JSON.stringify(baseEntry[key]); + if (modString !== baseString) { + if (!overrideItemInfo[id]) overrideItemInfo[id] = {}; + overrideItemInfo[id][key] = JSON.parse(modString); + if(key === 'desc' && !modEntry['shortDesc']) overrideItemInfo[id]['shortDesc'] = JSON.parse(modString); + } + } + } + + if (modEntry.itemUser || modEntry.megaStone || id === 'boosterenergy') { + specificItems.push(id); + continue; + } + if (modEntry.isPokeball || modEntry.name.startsWith("TR")) { + badItems.push(id); + continue; + } + switch (modEntry.rating) { + case 3: + greatItems.push(id); + break; + case 2: + goodItems.push(id); + break; + // outclassed items + case 1: + poorItems.push(id); + break; + // Fling-only + case 0: + badItems.push(id); + break; + // Allows mods to manually set Pokemon-specific items + case -1: + specificItems.push(id); + break; + default: + goodItems.push(id); + } + } + greatItems.sort(); + greatItems.unshift(['header', "Popular items"]); + items.push(...greatItems); + goodItems.sort(); + goodItems.unshift(['header', "Items"]); + items.push(...goodItems); + specificItems.sort(); + specificItems.unshift(['header', "Pokémon-specific items"]); + items.push(...specificItems); + poorItems.sort(); + poorItems.unshift(['header', "Usually useless items"]); + items.push(...poorItems); + badItems.sort(); + badItems.unshift(['header', "Useless items"]); + items.push(...badItems); + //moves + const overrideMoveInfo = {}; + BattleTeambuilderTable[modConfigId].overrideMoveInfo = overrideMoveInfo; + for (const id in modData.Moves) { + const modEntry = modData.Moves[id]; + const baseEntry = Dex.data.Moves[id]; + const genEntry = modGen !== 9 ? Dex.mod('gen'+modGen).data.Moves[id] : null; + if (typeof baseEntry === 'undefined') { + overrideMoveInfo[id] = {}; + overrideMoveInfo[id] = modEntry; + overrideMoveInfo[id].exists = true; + continue; + } + let moddedMoveIsOldGen = true; + for (const key in modEntry) { + if (!overrideMoveKeys.includes(key)) continue; + const modString = JSON.stringify(modEntry[key]); + const baseString = JSON.stringify(baseEntry[key]); + if (modString !== baseString) { + if (genEntry && moddedMoveIsOldGen) { + const genString = JSON.stringify(genEntry[key]); + if (genString !== modString) { + moddedMoveIsOldGen = false; + } + } + if (!overrideMoveInfo[id]) overrideMoveInfo[id] = {}; + if (modString === undefined) overrideMoveInfo[id][key] = undefined; + else overrideMoveInfo[id][key] = JSON.parse(modString); + } + } + if (overrideMoveInfo[id] && moddedMoveIsOldGen) overrideMoveInfo[id].modMoveFromOldGen = true; + } + for(const id in Dex.data.Moves){ //Weed out nulled moves + const modEntry = modData.Moves[id]; + if(!modEntry){ + overrideMoveInfo[id] = {}; + overrideMoveInfo[id].isNonstandard = "Unobtainable"; + overrideMoveInfo[id].exists = false; + continue; + } + } + //abilities + const fullAbilityName = {}; + BattleTeambuilderTable[modConfigId].fullAbilityName = fullAbilityName; + const overrideAbilityDesc = {}; + BattleTeambuilderTable[modConfigId].overrideAbilityDesc = overrideAbilityDesc; + for (const id in modData.Abilities) { + const modEntry = modData.Abilities[id]; + const baseEntry = Dex.data.Abilities[id]; + const fakeAbility = (typeof baseEntry === 'undefined'); + if (fakeAbility) fullAbilityName[id] = modData.Abilities[id].name; + if (fakeAbility || (modEntry.shortDesc || modEntry.desc) !== (baseEntry.shortDesc || baseEntry.desc)) { + overrideAbilityDesc[id] = (modEntry.shortDesc || modEntry.desc); + } + } + //type chart + const overrideTypeChart = {}; + BattleTeambuilderTable[modConfigId].overrideTypeChart = overrideTypeChart; + for (const id in modData.TypeChart) { + const modEntry = modData.TypeChart[id]; + const baseEntry = Dex.data.TypeChart[id]; + if (JSON.stringify(modEntry) !== JSON.stringify(baseEntry)) { + overrideTypeChart[id] = modEntry; + } + } + } catch (err) { + delete ModConfig[modConfigId]; + delete BattleTeambuilderTable[modConfigId]; + if (debug) { + console.log("WARNING: Failed to load " + modConfigId); + console.log("This was the error:"); + console.log(err); + } + } + } // - // Mods + // (not pet) Mods // for (const mod of ['gen5bw1', 'gen7letsgo', 'gen8bdsp', 'gen9ssb']) { @@ -1222,7 +1766,7 @@ process.stdout.write("Building `data/teambuilder-tables.js`... "); } } - const overrideMoveData = {}; + var overrideMoveData = {}; BattleTeambuilderTable[mod].overrideMoveData = overrideMoveData; for (const id in modData.Moves) { const modEntry = modDex.moves.get(id); @@ -1236,7 +1780,7 @@ process.stdout.write("Building `data/teambuilder-tables.js`... "); } } - const overrideAbilityData = {}; + var overrideAbilityData = {}; BattleTeambuilderTable[mod].overrideAbilityData = overrideAbilityData; for (const id in modData.Abilities) { const modEntry = modDex.abilities.get(id); @@ -1250,7 +1794,7 @@ process.stdout.write("Building `data/teambuilder-tables.js`... "); } const overrideItemData = {}; - BattleTeambuilderTable[mod].overrideItemData = overrideItemData; + BattleTeambuilderTable[mod].overrideItemInfo = overrideItemData; for (const id in modData.Items) { const modEntry = modDex.items.get(id); const parentEntry = parentDex.items.get(id); @@ -1263,21 +1807,65 @@ process.stdout.write("Building `data/teambuilder-tables.js`... "); } } - buf += `exports.BattleTeambuilderTable = JSON.parse('${JSON.stringify(BattleTeambuilderTable).replace(/['\\]/g, "\\$&")}');\n\n`; + /* + + This block of code is a bit wild, I might not be able to help with it too much. + + Essentially it breaks down everything in BattleTeambuilderTable into blocks of 5, + casts them to a JSON object and then un-JSONs them by deleting the curly braces + that surround them. It =manually parses the edges of the entire JSON file so that the whole + thing is treated as a single JSON object with sub-objects to be loaded by the teambuilder. + + */ + + const tableKeys = Object.keys(BattleTeambuilderTable); + const blockSize = 5; + let blockIndex = 0; + + console.log("writing BattleTeambuilderTable..."); + const tableDir = 'play.pokemonshowdown.com/data/teambuilder-tables.js'; + fs.writeFileSync(tableDir, '// DO NOT EDIT - automatically built with build-tools/build-indexes\n\n'); + fs.appendFileSync(tableDir, 'exports.BattleTeambuilderTable = JSON.parse(\'{') + + // ChatGPT suggested to put BattleTeambuilderTable into blocks to prevent memory overload. + while (blockIndex < tableKeys.length) { + const block = tableKeys.slice(blockIndex, blockIndex + blockSize); + const blockTable = {}; + block.forEach(key => { + blockTable[key] = BattleTeambuilderTable[key]; + }); + var jsonString = JSON.stringify(blockTable).replace(/['\\]/g, "\\$&"); + jsonString = jsonString.substring(1, jsonString.length - 1); + fs.appendFileSync(tableDir, jsonString); + blockIndex += blockSize; + if (blockIndex < tableKeys.length) fs.appendFileSync(tableDir, ','); + } + fs.appendFileSync(tableDir, '}\');\n\n'); + console.log("DONE"); + + console.log("writing compressed data/teambuilder-tables.js.gz..."); + + var zlib = require('zlib'); + var gzip = zlib.createGzip(); + var tbtJs = fs.createReadStream(tableDir); + var tbtGz = fs.createWriteStream(tableDir + '.gz'); + tbtJs.pipe(gzip).pipe(tbtGz); + + console.log("DONE"); - fs.writeFileSync('play.pokemonshowdown.com/data/teambuilder-tables.js', buf); + console.log("writing ModConfig..."); + fs.writeFileSync('play.pokemonshowdown.com/data/mod-config.js', 'exports.ModConfig = ' + JSON.stringify(ModConfig) + ';\n\n'); + console.log("DONE"); } -console.log("DONE"); + /********************************************************* * Build pokedex.js *********************************************************/ -process.stdout.write("Building `data/pokedex.js`... "); - -{ - const Pokedex = requireNoCache('../caches/pokemon-showdown/dist/data/pokedex.js').Pokedex; +function buildPokedex() { + const Pokedex = requireNoCache('../caches/DH2/dist/data/pokedex.js').Pokedex; for (const id in Pokedex) { const entry = Pokedex[id]; if (Dex.data.FormatsData[id]) { @@ -1293,16 +1881,12 @@ process.stdout.write("Building `data/pokedex.js`... "); fs.writeFileSync('play.pokemonshowdown.com/data/pokedex.json', JSON.stringify(Pokedex)); } -console.log("DONE"); - /********************************************************* * Build moves.js *********************************************************/ -process.stdout.write("Building `data/moves,items,abilities,typechart,learnsets.js`..."); - -{ - const Moves = requireNoCache('../caches/pokemon-showdown/dist/data/moves.js').Moves; +function buildMoves() { + const Moves = requireNoCache('../caches/DH2/dist/data/moves.js').Moves; for (const id in Moves) { const move = Dex.moves.get(Moves[id].name); if (move.desc) Moves[id].desc = move.desc; @@ -1318,8 +1902,8 @@ process.stdout.write("Building `data/moves,items,abilities,typechart,learnsets.j * Build items.js *********************************************************/ -{ - const Items = requireNoCache('../caches/pokemon-showdown/dist/data/items.js').Items; +function buildItems() { + const Items = requireNoCache('../caches/DH2/dist/data/items.js').Items; for (const id in Items) { const item = Dex.items.get(Items[id].name); if (item.desc) Items[id].desc = item.desc; @@ -1333,8 +1917,8 @@ process.stdout.write("Building `data/moves,items,abilities,typechart,learnsets.j * Build abilities.js *********************************************************/ -{ - const Abilities = requireNoCache('../caches/pokemon-showdown/dist/data/abilities.js').Abilities; +function buildAbilities() { + const Abilities = requireNoCache('../caches/DH2/dist/data/abilities.js').Abilities; for (const id in Abilities) { const ability = Dex.abilities.get(Abilities[id].name); if (ability.desc) Abilities[id].desc = ability.desc; @@ -1348,8 +1932,8 @@ process.stdout.write("Building `data/moves,items,abilities,typechart,learnsets.j * Build typechart.js *********************************************************/ -{ - const TypeChart = requireNoCache('../caches/pokemon-showdown/dist/data/typechart.js').TypeChart; +function buildTypechart() { + const TypeChart = requireNoCache('../caches/DH2/dist/data/typechart.js').TypeChart; const buf = 'exports.BattleTypeChart = ' + es3stringify(TypeChart) + ';'; fs.writeFileSync('play.pokemonshowdown.com/data/typechart.js', buf); } @@ -1358,8 +1942,8 @@ process.stdout.write("Building `data/moves,items,abilities,typechart,learnsets.j * Build aliases.js *********************************************************/ -{ - const Aliases = requireNoCache('../caches/pokemon-showdown/dist/data/aliases.js').Aliases; +function buildAliases() { + const Aliases = requireNoCache('../caches/DH2/dist/data/aliases.js').Aliases; const buf = 'exports.BattleAliases = ' + es3stringify(Aliases) + ';'; fs.writeFileSync('play.pokemonshowdown.com/data/aliases.js', buf); } @@ -1368,8 +1952,8 @@ process.stdout.write("Building `data/moves,items,abilities,typechart,learnsets.j * Build formats-data.js *********************************************************/ -{ - const FormatsData = requireNoCache('../caches/pokemon-showdown/dist/data/formats-data.js').FormatsData; +function buildFormatsData() { + const FormatsData = requireNoCache('../caches/DH2/dist/data/formats-data.js').FormatsData; const buf = 'exports.BattleFormatsData = ' + es3stringify(FormatsData) + ';'; fs.writeFileSync('play.pokemonshowdown.com/data/formats-data.js', buf); } @@ -1378,8 +1962,8 @@ process.stdout.write("Building `data/moves,items,abilities,typechart,learnsets.j * Build formats.js *********************************************************/ -{ - const Formats = requireNoCache('../caches/pokemon-showdown/dist/config/formats.js').Formats; +function buildFormats() { + Formats = requireNoCache('../caches/DH2/dist/config/formats.js').Formats; const buf = 'exports.Formats = ' + es3stringify(Formats) + ';'; fs.writeFileSync('play.pokemonshowdown.com/data/formats.js', buf); } @@ -1388,18 +1972,46 @@ process.stdout.write("Building `data/moves,items,abilities,typechart,learnsets.j * Build learnsets.js *********************************************************/ -{ - const Learnsets = requireNoCache('../caches/pokemon-showdown/dist/data/learnsets.js').Learnsets; +function buildLearnsets() { + const Learnsets = requireNoCache('../caches/DH2/dist/data/learnsets.js').Learnsets; const buf = 'exports.BattleLearnsets = ' + es3stringify(Learnsets) + ';'; fs.writeFileSync('play.pokemonshowdown.com/data/learnsets.js', buf); fs.writeFileSync('play.pokemonshowdown.com/data/learnsets.json', JSON.stringify(Learnsets)); } +/********************************************************* + * Build mod-sprites.js + *********************************************************/ + +function buildModSprites() { + const modSprites = {}; + const modDir = fs.readdirSync('caches/DH2/data/mods/'); + for (const i in modDir) { + const modName = modDir[i]; + const subFolders = ['anifront', 'anifront-shiny','aniback','aniback-shiny','front', 'front-shiny', 'back', 'back-shiny', 'icons', 'types', 'items', 'cries']; + for (const j in subFolders) { + const subF = subFolders[j]; + const spritePath = 'caches/DH2/data/mods/' + modName + (subF === 'cries' ? '/audio/cries' : ('/sprites/' + subF)); + const spriteDir = fs.existsSync(spritePath) ? fs.readdirSync(spritePath) : ''; + for (const sprI in spriteDir) { + let id = spriteDir[sprI]; + const ext = id.split(".")[1]; + id = toID(id.slice(0, id.length - 4)); + modSprites[id] ||= {}; + modSprites[id][modName] ||= []; + modSprites[id][modName].push(subF); + } + } + } + const buf = 'exports.ModSprites = ' + es3stringify(modSprites) + ';'; + fs.writeFileSync('play.pokemonshowdown.com/data/mod-sprites.js', buf); +} + /********************************************************* * Build text.js *********************************************************/ -{ +function buildText() { const textData = Dex.loadTextData(); const Text = textData.Default; @@ -1425,5 +2037,3 @@ process.stdout.write("Building `data/moves,items,abilities,typechart,learnsets.j const buf = 'exports.BattleText = ' + es3stringify(Text) + ';'; fs.writeFileSync('play.pokemonshowdown.com/data/text.js', buf); } - -console.log("DONE"); diff --git a/build-tools/build-learnsets b/build-tools/build-learnsets index 1e149dbe8..652015232 100755 --- a/build-tools/build-learnsets +++ b/build-tools/build-learnsets @@ -18,7 +18,7 @@ const thisFile = __filename; const thisDir = __dirname; const rootDir = path.resolve(thisDir, '../play.pokemonshowdown.com'); -const Dex = require('../caches/pokemon-showdown/dist/sim/dex').Dex; +const Dex = require('../caches/DH2/dist/sim/dex').Dex; const toID = Dex.toID; function updateLearnsets(callback) { diff --git a/build-tools/build-minidex b/build-tools/build-minidex index dd4cfce04..7db1f0052 100755 --- a/build-tools/build-minidex +++ b/build-tools/build-minidex @@ -6,7 +6,7 @@ const path = require("path"); process.chdir(path.resolve(__dirname, '../play.pokemonshowdown.com')); const imageSize = require('image-size'); -const Dex = require('./../caches/pokemon-showdown/dist/sim/dex').Dex; +const Dex = require('./../caches/DH2/dist/sim/dex').Dex; const toID = Dex.toID; process.stdout.write("Updating animated sprite dimensions... "); diff --git a/build-tools/build-sets b/build-tools/build-sets new file mode 100644 index 000000000..a9f44e00c --- /dev/null +++ b/build-tools/build-sets @@ -0,0 +1,25 @@ +#!/usr/bin/env node +'use strict'; + +const fs = require("fs"); +const child_process = require('child_process'); +const path = require("path"); + +process.stdout.write("Importing sets from @smogon/sets... "); + +const shell = cmd => child_process.execSync(cmd, {stdio: 'inherit', cwd: path.resolve(__dirname, '..')}); +shell(`npm install --no-audit --no-save @smogon/sets`); + +const src = path.resolve(__dirname, '../node_modules/@smogon/sets'); +const dest = path.resolve(__dirname, '../data/sets'); + +try { + fs.mkdirSync(dest); +} catch (err) { + if (err.code !== 'EEXIST') throw err; +} + +for (const file of fs.readdirSync(src)) { + if (!file.endsWith('.json')) continue; + fs.copyFileSync(`${src}/${file}`, `${dest}/${file}`); +} \ No newline at end of file diff --git a/build-tools/server-repo b/build-tools/server-repo new file mode 100644 index 000000000..9d9d89a81 --- /dev/null +++ b/build-tools/server-repo @@ -0,0 +1,2 @@ +const server_repo = "https://github.com/scoopapa/DH2.git"; +module.exports = server_repo; \ No newline at end of file diff --git a/build-tools/update b/build-tools/update old mode 100755 new mode 100644 index 23d52a894..fb02e0f77 --- a/build-tools/update +++ b/build-tools/update @@ -55,6 +55,7 @@ Config.routes = { dex: '${routes.dex}', replays: '${routes.replays}', users: '${routes.users}', + psmain: '${routes.psmain}', }; ${AUTOCONFIG_END}`; @@ -178,32 +179,7 @@ crossprotocolContents = crossprotocolContents.replace(URL_REGEX, addCachebuster) let replayEmbedContents = fs.readFileSync('play.pokemonshowdown.com/js/replay-embed.template.js', {encoding: 'utf8'}); replayEmbedContents = replayEmbedContents.replace(/play\.pokemonshowdown\.com/g, routes.client); -// add news, only if it's actually likely to exist -process.stdout.write("and news... "); -let stdout = ''; -let newsid = 0; -let news = '[failed to retrieve news]'; -try { - stdout = child_process.execSync('php ' + path.resolve(thisDir, 'news-embed.php')); -} catch (e) { - console.log("git hook failed to retrieve news (exec command failed):\n" + (e.error + e.stderr + e.stdout)); -} -try { - if (stdout) [newsid, news] = JSON.parse(stdout); -} catch (e) { - console.log("git hook failed to retrieve news (parsing JSON failed):\n" + e.stack); -} - -indexContents = indexContents.replace(/<!-- newsid -->/g, newsid); -indexContents = indexContents.replace(/<!-- news -->/g, news); - -let indexContents2 = ''; -try { - let indexContentsOld = indexContents; - indexContents = indexContents.replace(/<!-- head custom -->/g, '' + fs.readFileSync('config/head-custom.html')); - indexContents2 = indexContentsOld.replace(/<!-- head custom -->/g, '' + fs.readFileSync('config/head-custom-test.html')); - indexContents2 = indexContents2.replace(/src="\/\/play.pokemonshowdown.com\/config\/config.js\?[a-z0-9]*"/, 'src="//play.pokemonshowdown.com/config/config-test.js?4"'); -} catch (e) {} +// Nobody cares about the news. Cheers. fs.writeFileSync('play.pokemonshowdown.com/index.html', indexContents); if (indexContents2) { diff --git a/config/config.js b/config/config.js new file mode 100644 index 000000000..8d59db77c --- /dev/null +++ b/config/config.js @@ -0,0 +1,46 @@ +var Config = Config || {}; + +/* version */ Config.version = "0"; + +Config.bannedHosts = ['cool.jit.su', 'pokeball-nixonserver.rhcloud.com']; + +Config.whitelist = [ + 'wikipedia.org', + + // The full list is maintained outside of this repository so changes to it + // don't clutter the commit log. Feel free to copy our list for your own + // purposes; it's here: https://play.pokemonshowdown.com/config/config.js + + // If you would like to change our list, simply message Zarel on Smogon or + // Discord. +]; + +// `defaultserver` specifies the server to use when the domain name in the +// address bar is `Config.routes.client`. +Config.defaultserver = { + id: 'dragonheaven', + host: '191.101.232.116', + port: 8000, + httpport: 80, + altport: 80, + registered: true +}; + +Config.roomsFirstOpenScript = function () { +}; + +Config.customcolors = { + 'zarel': 'aeo' +}; +/*** Begin automatically generated configuration ***/ +Config.version = "0.11.2"; + +Config.routes = { + root: 'petmodsdh.com', + client: 'localhost', + dex: 'dex.pokemonshowdown.com', + replays: 'replay.pokemonshowdown.com', + users: 'pokemonshowdown.com/users', + psmain: 'pokemonshowdown.com', +}; +/*** End automatically generated configuration ***/ diff --git a/config/mod-config.js b/config/mod-config.js new file mode 100644 index 000000000..f519c4d7c --- /dev/null +++ b/config/mod-config.js @@ -0,0 +1,39 @@ +/* +optional data: +customTiers - these are auto-detected by the script, but you can set them here to ensure they show up in the right order +excludeStandardTiers - set to true if you want only your custom tiers to show up for the format +*/ +const ModConfigData = { + ClientMods: { + // cleanslate: { + // excludeStandardTiers: true, + // }, + // cleanslatemicro: { + // excludeStandardTiers: true, + // }, + // csts: { + // customTiers: ['CS1', 'CSM', 'CS2'], + // excludeStandardTiers: true, + // }, + // roulettemons: { + // excludeStandardTiers: true, + // }, + // ccapm2020: { + // excludeStandardTiers: true, + // }, + // fealpha: { + // excludeStandardTiers: true, + // }, + // feuu: { + // excludeStandardTiers: true, + // }, + // prism: { + // ignoreEVLimits: true, + // spriteGen: 2, + // }, + // smashmodsmelee: { + // excludeStandardTiers: true, + // }, + }, +}; +exports.ModConfigData = ModConfigData; diff --git a/config/routes.json b/config/routes.json index 69553f29a..7376f4a27 100644 --- a/config/routes.json +++ b/config/routes.json @@ -1,7 +1,8 @@ { - "root": "pokemonshowdown.com", - "client": "play.pokemonshowdown.com", + "root": "191.101.232.116", + "client": "localhost", "dex": "dex.pokemonshowdown.com", "replays": "replay.pokemonshowdown.com", - "users": "pokemonshowdown.com/users" -} + "users": "pokemonshowdown.com/users", + "psmain": "pokemonshowdown.com" +} \ No newline at end of file diff --git a/config/testclient-key.js b/config/testclient-key.js new file mode 100644 index 000000000..cd6630d03 --- /dev/null +++ b/config/testclient-key.js @@ -0,0 +1 @@ +const POKEMON_SHOWDOWN_TESTCLIENT_KEY = 'DH%20Client%2C60930894%2C2adcb851619e8a7f6475b9e41609257005'; diff --git a/package.json b/package.json index 05c95aaf2..6ecf9010b 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,12 @@ "dependencies": { "@babel/core": "^7.21.3", "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-logical-assignment-operators": "^7.20.7", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", + "@babel/plugin-proposal-object-rest-spread": "^7.20.7", + "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", + "@babel/plugin-proposal-optional-chaining": "^7.21.0", + "@babel/plugin-transform-logical-assignment-operators": "^7.22.5", "@babel/plugin-transform-react-jsx": "^7.21.0", "@babel/preset-env": "^7.20.2", "@babel/preset-typescript": "^7.21.0", diff --git a/play.pokemonshowdown.com/config/testclient-key.js b/play.pokemonshowdown.com/config/testclient-key.js new file mode 100644 index 000000000..cd6630d03 --- /dev/null +++ b/play.pokemonshowdown.com/config/testclient-key.js @@ -0,0 +1 @@ +const POKEMON_SHOWDOWN_TESTCLIENT_KEY = 'DH%20Client%2C60930894%2C2adcb851619e8a7f6475b9e41609257005'; diff --git a/play.pokemonshowdown.com/index.template.html b/play.pokemonshowdown.com/index.template.html index 5258ad886..d670c9af4 100644 --- a/play.pokemonshowdown.com/index.template.html +++ b/play.pokemonshowdown.com/index.template.html @@ -1,136 +1,134 @@ <!DOCTYPE html> -<!-- - ............. - ,................... - ,..................======== - ....~=##############=======+ - ...##################=======. - ..=######+..., +##=======,.. - ..=###### ., ..... +=====+,.... - ###########~,.. ====== - .....##############~=====+....., pokemonshowdown.com - ..........###########====...... - ............#######,==...... - =###.,........+#####+ ....... - ####################~......, - #################+======, - ++++++++++++ =======+ - -Viewing source? We're open source! Check us out on GitHub! -https://github.com/Zarel/Pokemon-Showdown -https://github.com/Zarel/Pokemon-Showdown-Client (you are here) - -Also visit us in the Dev chatroom: -https://psim.us/dev - ---> -<meta charset="UTF-8" /> -<meta id="viewport" name="viewport" content="width=device-width" /> -<title>Showdown!</title> -<meta http-equiv="X-UA-Compatible" content="IE=Edge" /> -<link rel="shortcut icon" href="//play.pokemonshowdown.com/favicon.ico" id="dynamic-favicon" /> -<link rel="icon" sizes="256x256" href="//play.pokemonshowdown.com/favicon-256.png" /> -<link rel="stylesheet" href="//play.pokemonshowdown.com/style/battle.css?" /> -<link rel="stylesheet" href="//play.pokemonshowdown.com/style/client.css?" /> -<link rel="stylesheet" href="//play.pokemonshowdown.com/style/sim-types.css?" /> -<link rel="stylesheet" href="//play.pokemonshowdown.com/style/utilichart.css?" /> -<link rel="stylesheet" href="//play.pokemonshowdown.com/style/font-awesome.css?" /> -<meta name="apple-mobile-web-app-capable" content="yes" /> -<link rel="manifest" href="/manifest.json" /> -<!--[if lte IE 8]><script>document.location.replace('http://pokemonshowdown.com/autodownload/win');</script><![endif]--> - -<!-- head custom --> - -<div id="header" class="header"> - <img class="logo" src="//play.pokemonshowdown.com/pokemonshowdownbeta.png" srcset="//play.pokemonshowdown.com/pokemonshowdownbeta@2x.png 2x" alt="Pokémon Showdown! (beta)" width="146" height="44" /><div class="maintabbarbottom"></div> -</div> -<div class="ps-room scrollable" id="mainmenu"><div class="mainmenuwrapper"> - <div class="leftmenu"> - <div class="activitymenu"> - <div class="pmbox"> - <div class="pm-window news-embed" data-newsid="<!-- newsid -->"> - <h3><button class="closebutton" tabindex="-1"><i class="fa fa-times-circle"></i></button><button class="minimizebutton" tabindex="-1"><i class="fa fa-minus-circle"></i></button>News</h3> - <div class="pm-log" style="max-height:none"> - <!-- news --> +<html> + <head> + <meta charset="UTF-8" /> + <meta id="viewport" name="viewport" content="width=device-width" /> + <title>Showdown Pet Mods</title> + <link rel="shortcut icon" href="favicon.ico" id="dynamic-favicon" /> + <link rel="stylesheet" href="style/battle.css" /> + <link rel="stylesheet" href="style/client.css" /> + <link rel="stylesheet" href="style/sim-types.css" /> + <link rel="stylesheet" href="style/utilichart.css" /> + <link rel="stylesheet" href="style/font-awesome.css" /> + <meta name="robots" content="noindex" /> + <meta http-equiv="X-UA-Compatible" content="IE=Edge" /> + <script src="config/config.js"></script> + <script> + function loadRemoteData(src) { + var scriptEl = document.createElement('script'); + scriptEl.src = src.replace(/.*\/?data\//g, 'https://play.pokemonshowdown.com/data/'); + document.head.appendChild(scriptEl); + } + Config.testclient = true; + (function() { + if (location.search !== '') { + var m = /\?~~(([^:\/]*)(:[0-9]*)?)/.exec(location.search); + if (m) { + Config.server = { + id: m[1], + host: m[2], + port: (m[3] && parseInt(m[3].substr(1))) || 8000 + }; + } else { + alert('Unrecognised query string syntax: ' + location.search); + } + } + })(); + </script> + <!--[if lte IE 8]><script> + Config.oldie = true; + </script><![endif]--> + </head> + <body> + <div id="header" class="header"> + <img class="logo" src="pokemonshowdownbeta.png" alt="Pokémon Showdown! (beta)" width="146" height="44" /><div class="maintabbarbottom"></div> + </div> + <div class="ps-room scrollable" id="mainmenu"><div class="mainmenuwrapper"> + <div class="leftmenu"> + <div class="activitymenu"> + <div class="pmbox"> + <div class="pm-window news-embed"> + <h3><button class="closebutton" tabindex="-1" aria-label="Close"><i class="fa fa-times-circle"></i></button><button class="minimizebutton" tabindex="-1" aria-label="Minimize"><i class="fa fa-minus-circle"></i></button>Latest News</h3> + <div class="pm-log" style="max-height:none"> + <div class="newsentry"><h4>Pet Mods Client</h4><p>Welcome to the Pet Mods client! We have teambuilder support for mods here!</p><strong></strong></div> + </div> + </div> </div> </div> + <div class="mainmenu"> + <div id="loading-message" class="mainmessage">Initializing... <noscript>FAILED<br /><br />Pokémon Showdown requires JavaScript.</noscript></div> + </div> </div> - </div> - <div class="mainmenu"> - <div id="loading-message" class="mainmessage">Initializing... <noscript>FAILED<br /><br />Pokémon Showdown requires JavaScript.</noscript></div> - </div> - </div> - <div class="rightmenu"> - </div> - <div class="mainmenufooter"> - <div class="bgcredit"></div> - <small><a href="//dex.pokemonshowdown.com/" target="_blank">Pokédex</a> | <a href="//replay.pokemonshowdown.com/" target="_blank">Replays</a> | <a href="//pokemonshowdown.com/rules" target="_blank">Rules</a> | <a href="//pokemonshowdown.com/credits" target="_blank">Credits</a> | <a href="http://smogon.com/forums/" target="_blank">Forum</a> | <a href="//pokemonshowdown.com/privacy" target="_blank">Privacy policy</a></small> - </div> -</div></div> -<script> - var LM = document.getElementById('loading-message'); - LM.innerHTML += ' DONE<br />Loading libraries...'; -</script> -<script nomodule src="//play.pokemonshowdown.com/js/lib/ps-polyfill.js"></script> -<script src="//play.pokemonshowdown.com/config/config.js?"></script> -<script src="//play.pokemonshowdown.com/js/lib/jquery-2.2.4.min.js"></script> -<script src="//play.pokemonshowdown.com/js/lib/jquery-cookie.js"></script> -<script src="//play.pokemonshowdown.com/js/lib/autoresize.jquery.min.js?"></script> -<script src="//play.pokemonshowdown.com/js/battle-sound.js?"></script> -<script src="//play.pokemonshowdown.com/js/lib/html-css-sanitizer-minified.js?"></script> -<script src="//play.pokemonshowdown.com/js/lib/lodash.core.js?"></script> -<script src="//play.pokemonshowdown.com/js/lib/backbone.js?"></script> -<script src="//play.pokemonshowdown.com/js/lib/d3.v3.min.js"></script> + <div class="rightmenu"> + </div> + <div class="mainmenufooter"> + <small><a href="//pokemonshowdown.com/" target="_blank"><strong>Pokémon Showdown</strong></a> | <a href="http://smogon.com/" target="_blank"><strong>Smogon</strong></a><br><a href="//pokemonshowdown.com/dex/" target="_blank">Pokédex</a> | <a href="//pokemonshowdown.com/replay/" target="_blank">Replays</a> | <a href="//pokemonshowdown.com/rules" target="_blank">Rules</a></small> | <small><a href="//pokemonshowdown.com/forums/" target="_blank">Forum</a></small> + </div> + </div></div> + <script> + document.getElementById('loading-message').innerHTML += ' DONE<br />Loading libraries...'; + </script> + <script nomodule src="/js/lib/ps-polyfill.js"></script> + <script src="js/lib/jquery-2.2.4.min.js"></script> + <script src="js/lib/jquery-cookie.js"></script> + <script src="js/lib/autoresize.jquery.min.js"></script> + <script src="js/battle-sound.js"></script> + <script src="../config/testclient-key.js"></script> + <script src="js/lib/html-css-sanitizer-minified.js"></script> + <script src="js/lib/lodash.core.js"></script> + <script src="js/lib/backbone.js"></script> + <script src="js/lib/d3.v3.min.js"></script> + + <script> + document.getElementById('loading-message').innerHTML += ' DONE<br />Loading data...'; + window.exports = window; + </script> -<script> - LM.innerHTML += ' DONE<br />Loading data...'; -</script> + <script src="js/battledata.js" onerror="alert('You must build the client with `node build` before using testclient.html')"></script> + <script src="data/text.js" onerror="loadRemoteData(this.src)"></script> + <script src="data/pokedex-mini.js" onerror="loadRemoteData(this.src)"></script> + <script src="data/pokedex-mini-bw.js" onerror="loadRemoteData(this.src)"></script> + <script src="data/typechart.js" onerror="loadRemoteData(this.src)"></script> + <script src="js/battle.js"></script> + <script src="js/lib/sockjs-1.4.0-nwjsfix.min.js"></script> + <script src="js/lib/color-thief.min.js"></script> -<script src="//play.pokemonshowdown.com/js/battledata.js?"></script> -<script src="//play.pokemonshowdown.com/js/storage.js?"></script> -<script src="//play.pokemonshowdown.com/data/pokedex-mini.js?"></script> -<script src="//play.pokemonshowdown.com/data/typechart.js?"></script> -<script src="//play.pokemonshowdown.com/js/battle.js?"></script> -<script src="//play.pokemonshowdown.com/js/lib/sockjs-1.4.0-nwjsfix.min.js"></script> -<script src="//play.pokemonshowdown.com/js/lib/color-thief.min.js"></script> + <script> + document.getElementById('loading-message').innerHTML += ' DONE<br />Loading client...'; + </script> -<script> - LM.innerHTML += ' DONE<br />Loading client...'; -</script> + <script src="js/client.js"></script> + <script src="js/client-topbar.js"></script> + <script src="js/client-mainmenu.js"></script> + <script src="js/client-teambuilder.js"></script> + <script src="js/client-ladder.js"></script> + <script src="js/client-chat.js"></script> + <script src="js/client-chat-tournament.js"></script> + <script src="js/battle-tooltips.js"></script> + <script src="js/client-battle.js"></script> + <script src="js/client-rooms.js"></script> + <script src="js/storage.js"></script> + <script src="data/graphics.js" onerror="loadRemoteData(this.src)"></script> -<script src="//play.pokemonshowdown.com/js/client.js?"></script> -<script src="//play.pokemonshowdown.com/js/client-topbar.js?"></script> -<script src="//play.pokemonshowdown.com/js/client-mainmenu.js?"></script> -<script src="//play.pokemonshowdown.com/js/client-teambuilder.js?"></script> -<script src="//play.pokemonshowdown.com/js/client-ladder.js?"></script> -<script src="//play.pokemonshowdown.com/js/client-chat.js?"></script> -<script src="//play.pokemonshowdown.com/js/client-chat-tournament.js?"></script> -<script src="//play.pokemonshowdown.com/js/battle-tooltips.js?"></script> -<script src="//play.pokemonshowdown.com/js/client-battle.js?"></script> -<script src="//play.pokemonshowdown.com/js/client-rooms.js?"></script> -<script src="//play.pokemonshowdown.com/data/graphics.js?"></script> + <script src="data/pokedex.js" onerror="loadRemoteData(this.src)"></script> + <script src="data/moves.js" onerror="loadRemoteData(this.src)"></script> + <script src="data/items.js" onerror="loadRemoteData(this.src)"></script> + <script src="data/abilities.js" onerror="loadRemoteData(this.src)"></script> -<script> - // framebust - see https://owasp.org/www-pdf-archive/OWASP_AppSec_Research_2010_Busting_Frame_Busting_by_Rydstedt.pdf - // should be robust against reflective XSS filters and navigation interception - var app; - if (self === top) { - app = new App(); - } else { - LM.innerHTML += ' IN FRAME<br />Please visit Showdown directly.'; - top.location = self.location; - } -</script> + <script src="data/search-index.js" onerror="loadRemoteData(this.src)"></script> + <script src="data/teambuilder-tables.js" onerror="loadRemoteData(this.src)"></script> + <script src="data/mod-sprites.js" onerror="loadRemoteData(this.src)"></script> + <script src="data/mod-config.js" onerror="loadRemoteData(this.src)"></script> + <script src="js/battle-dex-search.js"></script> + <script src="js/search.js"></script> -<script src="//play.pokemonshowdown.com/data/pokedex.js?"></script> -<script src="//play.pokemonshowdown.com/data/moves.js?"></script> -<script src="//play.pokemonshowdown.com/data/items.js?"></script> -<script src="//play.pokemonshowdown.com/data/abilities.js?"></script> + <script src="data/aliases.js" async="async" onerror="loadRemoteData(this.src)"></script> -<script src="//play.pokemonshowdown.com/data/search-index.js?"></script> -<script src="//play.pokemonshowdown.com/data/teambuilder-tables.js?"></script> -<script src="//play.pokemonshowdown.com/js/battle-dex-search.js?"></script> -<script src="//play.pokemonshowdown.com/js/search.js?"></script> + <script> + window.onload = () => { + window.app = new App(); + } + </script> -<script src="//play.pokemonshowdown.com/data/aliases.js?" async></script> -<script src="//play.pokemonshowdown.com/js/clean-cookies.php" async></script> + </body> +</html> \ No newline at end of file diff --git a/play.pokemonshowdown.com/js/client-battle.js b/play.pokemonshowdown.com/js/client-battle.js index 431390403..eb68c87ad 100644 --- a/play.pokemonshowdown.com/js/client-battle.js +++ b/play.pokemonshowdown.com/js/client-battle.js @@ -617,7 +617,7 @@ var tooltipArgs = 'activepokemon|1|' + i; var disabled = false; - if (moveTarget === 'adjacentAlly' || moveTarget === 'adjacentAllyOrSelf') { + if (moveTarget === 'adjacentAlly' || moveTarget === 'adjacentAllyOrSelf' || moveTarget === 'anyAlly') { disabled = true; } else if (moveTarget === 'normal' || moveTarget === 'adjacentFoe') { if (Math.abs(farSlot - i) > 1) disabled = true; @@ -628,7 +628,7 @@ } else if (!pokemon || pokemon.fainted) { targetMenus[0] += '<button name="chooseMoveTarget" value="' + (i + 1) + '"><span class="picon" style="' + Dex.getPokemonIcon('missingno') + '"></span></button> '; } else { - targetMenus[0] += '<button name="chooseMoveTarget" value="' + (i + 1) + '" class="has-tooltip" data-tooltip="' + BattleLog.escapeHTML(tooltipArgs) + '"><span class="picon" style="' + Dex.getPokemonIcon(pokemon) + '"></span>' + (this.battle.ignoreOpponent || this.battle.ignoreNicks ? pokemon.speciesForme : BattleLog.escapeHTML(pokemon.name)) + '<span class="' + pokemon.getHPColorClass() + '"><span style="width:' + (Math.round(pokemon.hp * 92 / pokemon.maxhp) || 1) + 'px"></span></span>' + (pokemon.status ? '<span class="status ' + pokemon.status + '"></span>' : '') + '</button> '; + targetMenus[0] += '<button name="chooseMoveTarget" value="' + (i + 1) + '" class="has-tooltip" data-tooltip="' + BattleLog.escapeHTML(tooltipArgs) + '"><span class="picon" style="' + Dex.getPokemonIcon(pokemon, false, this.battle.mod || '') + '"></span>' + (this.battle.ignoreOpponent || this.battle.ignoreNicks ? pokemon.speciesForme : BattleLog.escapeHTML(pokemon.name)) + '<span class="' + pokemon.getHPColorClass() + '"><span style="width:' + (Math.round(pokemon.hp * 92 / pokemon.maxhp) || 1) + 'px"></span></span>' + (pokemon.status ? '<span class="status ' + pokemon.status + '"></span>' : '') + '</button> '; } } for (var i = 0; i < nearActive.length; i++) { @@ -641,14 +641,14 @@ } else if (moveTarget === 'normal' || moveTarget === 'adjacentAlly' || moveTarget === 'adjacentAllyOrSelf') { if (Math.abs(activePos - i) > 1) disabled = true; } - if (moveTarget !== 'adjacentAllyOrSelf' && activePos == i) disabled = true; + if (moveTarget !== 'adjacentAllyOrSelf' && moveTarget !== 'anyAlly' && activePos == i) disabled = true; if (disabled) { targetMenus[1] += '<button disabled style="visibility:hidden"></button> '; } else if (!pokemon || pokemon.fainted) { targetMenus[1] += '<button name="chooseMoveTarget" value="' + (-(i + 1)) + '"><span class="picon" style="' + Dex.getPokemonIcon('missingno') + '"></span></button> '; } else { - targetMenus[1] += '<button name="chooseMoveTarget" value="' + (-(i + 1)) + '" class="has-tooltip" data-tooltip="' + BattleLog.escapeHTML(tooltipArgs) + '"><span class="picon" style="' + Dex.getPokemonIcon(pokemon) + '"></span>' + BattleLog.escapeHTML(pokemon.name) + '<span class="' + pokemon.getHPColorClass() + '"><span style="width:' + (Math.round(pokemon.hp * 92 / pokemon.maxhp) || 1) + 'px"></span></span>' + (pokemon.status ? '<span class="status ' + pokemon.status + '"></span>' : '') + '</button> '; + targetMenus[1] += '<button name="chooseMoveTarget" value="' + (-(i + 1)) + '" class="has-tooltip" data-tooltip="' + BattleLog.escapeHTML(tooltipArgs) + '"><span class="picon" style="' + Dex.getPokemonIcon(pokemon, false, this.battle.mod || '') + '"></span>' + BattleLog.escapeHTML(pokemon.name) + '<span class="' + pokemon.getHPColorClass() + '"><span style="width:' + (Math.round(pokemon.hp * 92 / pokemon.maxhp) || 1) + 'px"></span></span>' + (pokemon.status ? '<span class="status ' + pokemon.status + '"></span>' : '') + '</button> '; } } @@ -795,9 +795,9 @@ pokemon.name = pokemon.ident.substr(4); var tooltipArgs = 'switchpokemon|' + i; if (pokemon.fainted || i < this.battle.pokemonControlled || this.choice.switchFlags[i] || trapped) { - party += '<button class="disabled has-tooltip" name="chooseDisabled" value="' + BattleLog.escapeHTML(pokemon.name) + (pokemon.fainted ? ',fainted' : trapped ? ',trapped' : i < this.battle.nearSide.active.length ? ',active' : '') + '" data-tooltip="' + BattleLog.escapeHTML(tooltipArgs) + '"><span class="picon" style="' + Dex.getPokemonIcon(pokemon) + '"></span>' + BattleLog.escapeHTML(pokemon.name) + (pokemon.hp ? '<span class="' + pokemon.getHPColorClass() + '"><span style="width:' + (Math.round(pokemon.hp * 92 / pokemon.maxhp) || 1) + 'px"></span></span>' + (pokemon.status ? '<span class="status ' + pokemon.status + '"></span>' : '') : '') + '</button> '; + party += '<button class="disabled has-tooltip" name="chooseDisabled" value="' + BattleLog.escapeHTML(pokemon.name) + (pokemon.fainted ? ',fainted' : trapped ? ',trapped' : i < this.battle.nearSide.active.length ? ',active' : '') + '" data-tooltip="' + BattleLog.escapeHTML(tooltipArgs) + '"><span class="picon" style="' + Dex.getPokemonIcon(pokemon, false, this.battle.mod || '') + '"></span>' + BattleLog.escapeHTML(pokemon.name) + (pokemon.hp ? '<span class="' + pokemon.getHPColorClass() + '"><span style="width:' + (Math.round(pokemon.hp * 92 / pokemon.maxhp) || 1) + 'px"></span></span>' + (pokemon.status ? '<span class="status ' + pokemon.status + '"></span>' : '') : '') + '</button> '; } else { - party += '<button name="chooseSwitch" value="' + i + '" class="has-tooltip" data-tooltip="' + BattleLog.escapeHTML(tooltipArgs) + '"><span class="picon" style="' + Dex.getPokemonIcon(pokemon) + '"></span>' + BattleLog.escapeHTML(pokemon.name) + '<span class="' + pokemon.getHPColorClass() + '"><span style="width:' + (Math.round(pokemon.hp * 92 / pokemon.maxhp) || 1) + 'px"></span></span>' + (pokemon.status ? '<span class="status ' + pokemon.status + '"></span>' : '') + '</button> '; + party += '<button name="chooseSwitch" value="' + i + '" class="has-tooltip" data-tooltip="' + BattleLog.escapeHTML(tooltipArgs) + '"><span class="picon" style="' + Dex.getPokemonIcon(pokemon, false, this.battle.mod || '') + '"></span>' + BattleLog.escapeHTML(pokemon.name) + '<span class="' + pokemon.getHPColorClass() + '"><span style="width:' + (Math.round(pokemon.hp * 92 / pokemon.maxhp) || 1) + 'px"></span></span>' + (pokemon.status ? '<span class="status ' + pokemon.status + '"></span>' : '') + '</button> '; } } if (this.battle.mySide.ally) party += this.displayAllyParty(); @@ -811,7 +811,7 @@ var pokemon = allyParty[i]; pokemon.name = pokemon.ident.substr(4); var tooltipArgs = 'allypokemon|' + i; - party += '<button class="disabled has-tooltip" name="chooseDisabled" value="' + BattleLog.escapeHTML(pokemon.name) + ',notMine' + '" data-tooltip="' + BattleLog.escapeHTML(tooltipArgs) + '"><span class="picon" style="' + Dex.getPokemonIcon(pokemon) + '"></span>' + BattleLog.escapeHTML(pokemon.name) + (pokemon.hp ? '<span class="' + pokemon.getHPColorClass() + '"><span style="width:' + (Math.round(pokemon.hp * 92 / pokemon.maxhp) || 1) + 'px"></span></span>' + (pokemon.status ? '<span class="status ' + pokemon.status + '"></span>' : '') : '') + '</button> '; + party += '<button class="disabled has-tooltip" name="chooseDisabled" value="' + BattleLog.escapeHTML(pokemon.name) + ',notMine' + '" data-tooltip="' + BattleLog.escapeHTML(tooltipArgs) + '"><span class="picon" style="' + Dex.getPokemonIcon(pokemon, false, this.battle.mod || '') + '"></span>' + BattleLog.escapeHTML(pokemon.name) + (pokemon.hp ? '<span class="' + pokemon.getHPColorClass() + '"><span style="width:' + (Math.round(pokemon.hp * 92 / pokemon.maxhp) || 1) + 'px"></span></span>' + (pokemon.status ? '<span class="status ' + pokemon.status + '"></span>' : '') : '') + '</button> '; } return party; }, @@ -852,11 +852,11 @@ var pokemon = this.battle.myPokemon[i]; var tooltipArgs = 'switchpokemon|' + i; if (pokemon && !pokemon.fainted || this.choice.switchOutFlags[i]) { - controls += '<button disabled class="has-tooltip" data-tooltip="' + BattleLog.escapeHTML(tooltipArgs) + '"><span class="picon" style="' + Dex.getPokemonIcon(pokemon) + '"></span>' + BattleLog.escapeHTML(pokemon.name) + (!pokemon.fainted ? '<span class="' + pokemon.getHPColorClass() + '"><span style="width:' + (Math.round(pokemon.hp * 92 / pokemon.maxhp) || 1) + 'px"></span></span>' + (pokemon.status ? '<span class="status ' + pokemon.status + '"></span>' : '') : '') + '</button> '; + controls += '<button disabled class="has-tooltip" data-tooltip="' + BattleLog.escapeHTML(tooltipArgs) + '"><span class="picon" style="' + Dex.getPokemonIcon(pokemon, false, this.battle.mod || '') + '"></span>' + BattleLog.escapeHTML(pokemon.name) + (!pokemon.fainted ? '<span class="' + pokemon.getHPColorClass() + '"><span style="width:' + (Math.round(pokemon.hp * 92 / pokemon.maxhp) || 1) + 'px"></span></span>' + (pokemon.status ? '<span class="status ' + pokemon.status + '"></span>' : '') : '') + '</button> '; } else if (!pokemon) { controls += '<button disabled></button> '; } else { - controls += '<button name="chooseSwitchTarget" value="' + i + '" class="has-tooltip" data-tooltip="' + BattleLog.escapeHTML(tooltipArgs) + '"><span class="picon" style="' + Dex.getPokemonIcon(pokemon) + '"></span>' + BattleLog.escapeHTML(pokemon.name) + '<span class="' + pokemon.getHPColorClass() + '"><span style="width:' + (Math.round(pokemon.hp * 92 / pokemon.maxhp) || 1) + 'px"></span></span>' + (pokemon.status ? '<span class="status ' + pokemon.status + '"></span>' : '') + '</button> '; + controls += '<button name="chooseSwitchTarget" value="' + i + '" class="has-tooltip" data-tooltip="' + BattleLog.escapeHTML(tooltipArgs) + '"><span class="picon" style="' + Dex.getPokemonIcon(pokemon, false, this.battle.mod || '') + '"></span>' + BattleLog.escapeHTML(pokemon.name) + '<span class="' + pokemon.getHPColorClass() + '"><span style="width:' + (Math.round(pokemon.hp * 92 / pokemon.maxhp) || 1) + 'px"></span></span>' + (pokemon.status ? '<span class="status ' + pokemon.status + '"></span>' : '') + '</button> '; } } controls += '</div>'; @@ -892,7 +892,7 @@ switchMenu += '<button name="chooseSwitch" value="' + i + '" class="has-tooltip" data-tooltip="' + BattleLog.escapeHTML(tooltipArgs) + '">'; } } - switchMenu += '<span class="picon" style="' + Dex.getPokemonIcon(pokemon) + '"></span>' + BattleLog.escapeHTML(pokemon.name) + (!pokemon.fainted ? '<span class="' + pokemon.getHPColorClass() + '"><span style="width:' + (Math.round(pokemon.hp * 92 / pokemon.maxhp) || 1) + 'px"></span></span>' + (pokemon.status ? '<span class="status ' + pokemon.status + '"></span>' : '') : '') + '</button> '; + switchMenu += '<span class="picon" style="' + Dex.getPokemonIcon(pokemon, false, this.battle.mod || '') + '"></span>' + BattleLog.escapeHTML(pokemon.name) + (!pokemon.fainted ? '<span class="' + pokemon.getHPColorClass() + '"><span style="width:' + (Math.round(pokemon.hp * 92 / pokemon.maxhp) || 1) + 'px"></span></span>' + (pokemon.status ? '<span class="status ' + pokemon.status + '"></span>' : '') : '') + '</button> '; } var controls = ( @@ -927,9 +927,9 @@ var pokemon = switchables[oIndex]; var tooltipArgs = 'switchpokemon|' + oIndex; if (i < this.choice.done) { - switchMenu += '<button disabled class="has-tooltip" data-tooltip="' + BattleLog.escapeHTML(tooltipArgs) + '"><span class="picon" style="' + Dex.getPokemonIcon(pokemon) + '"></span>' + BattleLog.escapeHTML(pokemon.name) + '</button> '; + switchMenu += '<button disabled class="has-tooltip" data-tooltip="' + BattleLog.escapeHTML(tooltipArgs) + '"><span class="picon" style="' + Dex.getPokemonIcon(pokemon, false, this.battle.mod || '') + '"></span>' + BattleLog.escapeHTML(pokemon.name) + '</button> '; } else { - switchMenu += '<button name="chooseTeamPreview" value="' + i + '" class="has-tooltip" data-tooltip="' + BattleLog.escapeHTML(tooltipArgs) + '"><span class="picon" style="' + Dex.getPokemonIcon(pokemon) + '"></span>' + BattleLog.escapeHTML(pokemon.name) + '</button> '; + switchMenu += '<button name="chooseTeamPreview" value="' + i + '" class="has-tooltip" data-tooltip="' + BattleLog.escapeHTML(tooltipArgs) + '"><span class="picon" style="' + Dex.getPokemonIcon(pokemon, false, this.battle.mod || '') + '"></span>' + BattleLog.escapeHTML(pokemon.name) + '</button> '; } } @@ -1272,9 +1272,10 @@ var isTerastal = !!(this.$('input[name=terastallize]')[0] || '').checked; var target = e.getAttribute('data-target'); - var choosableTargets = {normal: 1, any: 1, adjacentAlly: 1, adjacentAllyOrSelf: 1, adjacentFoe: 1}; + var choosableTargets = {normal: 1, any: 1, adjacentAlly: 1, adjacentAllyOrSelf: 1, anyAlly: 1, adjacentFoe: 1}; if (this.battle.gameType === 'freeforall') delete choosableTargets['adjacentAllyOrSelf']; + this.choice.choices.push('move ' + pos + (isMega ? ' mega' : '') + (isMegaX ? ' megax' : isMegaY ? ' megay' : '') + (isZMove ? ' zmove' : '') + (isUltraBurst ? ' ultra' : '') + (isDynamax ? ' dynamax' : '') + (isTerastal ? ' terastallize' : '')); if (nearActive.length > 1 && target in choosableTargets) { this.choice.type = 'movetarget'; diff --git a/play.pokemonshowdown.com/js/client-teambuilder.js b/play.pokemonshowdown.com/js/client-teambuilder.js index fd2593e4f..853cd2fde 100644 --- a/play.pokemonshowdown.com/js/client-teambuilder.js +++ b/play.pokemonshowdown.com/js/client-teambuilder.js @@ -30,6 +30,9 @@ if (this.curTeam.format.includes('bdsp')) { this.curTeam.dex = Dex.mod('gen8bdsp'); } + if (this.curTeam.mod) { + this.curTeam.dex = Dex.mod(this.curTeam.mod); + } Storage.activeSetList = this.curSetList; } }, @@ -127,6 +130,7 @@ curTeamLoc: 0, curSet: null, curSetLoc: 0, + dex: Dex, // curFolder will have '/' at the end if it's a folder, but // it will be alphanumeric (so guaranteed no '/') if it's a @@ -146,16 +150,20 @@ update: function () { teams = Storage.teams; if (this.curTeam) { + // console.log(JSON.stringify(this.curTeam, null, 4)) if (this.curTeam.format && !this.formatResources[this.curTeam.format]) { this.tryLoadFormatResource(this.curTeam.format); } + if (this.curTeam.loaded === false || (this.curTeam.teamid && !this.curTeam.loaded)) { this.loadTeam(); return this.updateTeamView(); } this.ignoreEVLimits = (this.curTeam.gen < 3 || ((this.curTeam.format.includes('hackmons') || this.curTeam.format.endsWith('bh')) && this.curTeam.gen !== 6) || - this.curTeam.format.includes('metronomebattle')); + this.curTeam.format.includes('metronomebattle') || (this.curTeam.mod && ModConfig[this.curTeam.mod].ignoreEVLimits)); + this.dex = this.curTeam.mod ? Dex.mod(this.curTeam.mod) : Dex; // keeping just in case for now + this.curTeam.dex = this.curTeam.mod ? Dex.mod(this.curTeam.mod) : Dex; if (this.curSet) { return this.updateSetView(); } @@ -750,12 +758,21 @@ this.curTeam.iconCache = '!'; this.curTeam.gen = this.getGen(this.curTeam.format); this.curTeam.dex = Dex.forGen(this.curTeam.gen); + var ClientMods = ModConfig; + for (var modid in (ClientMods)) { + for (var formatid in ClientMods[modid].formats) { + if (formatid === this.curTeam.format) this.curTeam.mod = modid; + } + } if (this.curTeam.format.includes('letsgo')) { this.curTeam.dex = Dex.mod('gen7letsgo'); } if (this.curTeam.format.includes('bdsp')) { this.curTeam.dex = Dex.mod('gen8bdsp'); } + if (this.curTeam.mod) { + this.curTeam.dex = Dex.mod(this.curTeam.mod); + } Storage.activeSetList = this.curSetList = Storage.unpackTeam(this.curTeam.team); this.curTeamIndex = i; this.update(); @@ -1284,7 +1301,7 @@ var isBDSP = this.curTeam.format.includes('bdsp'); var isNatDex = this.curTeam.format.includes('nationaldex') || this.curTeam.format.includes('natdex'); var buf = '<li value="' + i + '">'; - if (!set.species) { + if (!set.species || !species) { if (this.deletedSet) { buf += '<div class="setmenu setmenu-left"><button name="undeleteSet" class="button"><i class="fa fa-undo"></i> Undo Delete</button></div>'; } @@ -1297,7 +1314,7 @@ buf += '<div class="setchart-nickname">'; buf += '<label>Nickname</label><input type="text" name="nickname" class="textbox" value="' + BattleLog.escapeHTML(set.name || '') + '" placeholder="' + BattleLog.escapeHTML(species.baseSpecies) + '" />'; buf += '</div>'; - buf += '<div class="setchart" style="' + Dex.getTeambuilderSprite(set, this.curTeam.gen) + ';">'; + buf += '<div class="setchart" style="' + Dex.getTeambuilderSprite(set, this.curTeam.gen, this.curTeam.mod) + ';">'; // icon buf += '<div class="setcol setcol-icon">'; @@ -1356,14 +1373,19 @@ var itemicon = '<span class="itemicon"></span>'; if (set.item) { var item = this.curTeam.dex.items.get(set.item); - itemicon = '<span class="itemicon" style="' + Dex.getItemIcon(item) + '"></span>'; + itemicon = '<span class="itemicon" style="' + Dex.getItemIcon(item, this.curTeam.mod) + '"></span>'; } buf += itemicon; buf += '</div>'; buf += '<div class="setcell setcell-typeicons">'; var types = species.types; + var table = (this.curTeam.gen < 7 ? BattleTeambuilderTable['gen' + this.curTeam.gen] : null); + if ( + table && table.overrideDexInfo && species.id in table.overrideDexInfo && + table.overrideDexInfo[species.id].types + ) types = table.overrideDexInfo[species.id].types; if (types) { - for (var i = 0; i < types.length; i++) buf += Dex.getTypeIcon(types[i]); + for (var i = 0; i < types.length; i++) buf += Dex.getTypeIcon(types[i], null, this.curTeam.mod); } buf += '</div></div>'; @@ -1385,7 +1407,7 @@ buf += '<div class="setcol setcol-stats"><div class="setrow"><label>Stats</label><button class="textbox setstats" name="stats">'; buf += '<span class="statrow statrow-head"><label></label> <span class="statgraph"></span> <em>' + (!isLetsGo ? 'EV' : 'AV') + '</em></span>'; var stats = {}; - var defaultEV = (this.curTeam.gen > 2 ? 0 : 252); + var defaultEV = ((this.curTeam.gen > 2 && !this.ignoreEVLimits) ? 0 : 252); for (var j in BattleStatNames) { if (j === 'spd' && this.curTeam.gen === 1) continue; stats[j] = this.getStat(j, set); @@ -1471,6 +1493,7 @@ } }, addPokemon: function () { + console.log("add pokemon"); if (!this.curTeam) return; var team = this.curSetList; if (!team.length || team[team.length - 1].species) { @@ -1603,12 +1626,22 @@ this.curTeam.format = format; this.curTeam.gen = this.getGen(this.curTeam.format); this.curTeam.dex = Dex.forGen(this.curTeam.gen); + this.curTeam.mod = 0; if (this.curTeam.format.includes('letsgo')) { this.curTeam.dex = Dex.mod('gen7letsgo'); } if (this.curTeam.format.includes('bdsp')) { this.curTeam.dex = Dex.mod('gen8bdsp'); } + var ClientMods = ModConfig; + for (var modid in (ClientMods)) { + for (var formatid in ClientMods[modid].formats) { + if (formatid === this.curTeam.format) this.curTeam.mod = modid; + } + } + if (this.curTeam.mod) { + this.curTeam.dex = Dex.mod(this.curTeam.mod); + } this.save(); if (this.curTeam.gen === 5 && !Dex.loadedSpriteData['bw']) Dex.loadSpriteData('bw'); this.update(); @@ -1653,10 +1686,10 @@ var buf = ''; for (var i = 0; i < this.clipboardCount(); i++) { var res = this.clipboard[i]; - var species = Dex.species.get(res.species); + var species = this.curTeam.dex.species.get(res.species); buf += '<div class="result" data-id="' + i + '">'; - buf += '<div class="section"><span class="icon" style="' + Dex.getPokemonIcon(species.name) + '"></span>'; + buf += '<div class="section"><span class="icon" style="' + Dex.getPokemonIcon(species.name, false, this.curTeam.mod) + '"></span>'; buf += '<span class="species">' + (species.name === species.baseSpecies ? BattleLog.escapeHTML(species.name) : (BattleLog.escapeHTML(species.baseSpecies) + '-<small>' + BattleLog.escapeHTML(species.name.substr(species.baseSpecies.length + 1)) + '</small>')) + '</span></div>'; buf += '<div class="section"><span class="ability-item">' + (BattleLog.escapeHTML(res.ability) || '<i>No ability</i>') + '<br />' + (BattleLog.escapeHTML(res.item) || '<i>No item</i>') + '</span></div>'; buf += '<div class="section no-border">'; @@ -1766,7 +1799,7 @@ .focus() .select(); - this.getSmogonSets(); + // this.getSmogonSets(); }, getSmogonSets: function () { this.$('.teambuilder-pokemon-import .teambuilder-import-smogon-sets').empty(); @@ -2012,14 +2045,14 @@ } for (var i = start; i < end; i++) { var set = this.curSetList[i]; - var pokemonicon = '<span class="picon pokemonicon-' + i + '" style="' + Dex.getPokemonIcon(set) + '"></span>'; + var pokemonicon = '<span class="picon pokemonicon-' + i + '" style="' + Dex.getPokemonIcon(set, false, this.curTeam.mod) + '"></span>'; if (!set.species) { buf += '<button disabled class="addpokemon" aria-label="Add Pokémon"><i class="fa fa-plus"></i></button> '; isAdd = true; } else if (i == this.curSetLoc) { buf += '<button disabled class="pokemon">' + pokemonicon + BattleLog.escapeHTML(set.name || this.curTeam.dex.species.get(set.species).baseSpecies || '<i class="fa fa-plus"></i>') + '</button> '; } else { - buf += '<button name="selectPokemon" value="' + i + '" class="pokemon">' + pokemonicon + BattleLog.escapeHTML(set.name || this.curTeam.dex.species.get(set.species).baseSpecies) + '</button> '; + buf += '<button name="selectPokemon" value="' + i + '" class="pokemon">' + pokemonicon + BattleLog.escapeHTML(set.name || this.curTeam.dex.species.get(set.species, undefined, "From Render Teambar 2").baseSpecies) + '</button> '; } } if (this.curSetList.length < this.curTeam.capacity && !isAdd) { @@ -2031,13 +2064,13 @@ var set = this.curSet; if (!set) return; - this.$('.setchart').attr('style', Dex.getTeambuilderSprite(set, this.curTeam.gen)); + this.$('.setchart').attr('style', Dex.getTeambuilderSprite(set, this.curTeam.gen, this.curTeam.mod)); - this.$('.pokemonicon-' + this.curSetLoc).css('background', Dex.getPokemonIcon(set).substr(11)); + this.$('.pokemonicon-' + this.curSetLoc).css('background', Dex.getPokemonIcon(set, false, this.curTeam.mod).substr(11)); var item = this.curTeam.dex.items.get(set.item); if (item.id) { - this.$('.setcol-details .itemicon').css('background', Dex.getItemIcon(item).substr(11)); + this.$('.setcol-details .itemicon').css('background', Dex.getItemIcon(set, false, this.curTeam.mod).substr(11)); } else { this.$('.setcol-details .itemicon').css('background', 'none'); } @@ -2271,12 +2304,12 @@ var buf = ''; var set = this.curSet; var species = this.curTeam.dex.species.get(this.curSet.species); - + if (this.curTeam.mod) species = this.curTeam.dex.species.get(this.curSet.species,undefined, "from updateStatForm 2"); var baseStats = species.baseStats; buf += '<div class="resultheader"><h3>EVs</h3></div>'; buf += '<div class="statform">'; - var guess = new BattleStatGuesser(this.curTeam.format).guess(set); + var guess = new BattleStatGuesser(this.curTeam.format, this.curTeam.mod).guess(set); var role = guess.role; var guessedEVs = guess.evs; @@ -2319,10 +2352,11 @@ var supportsEVs = !this.curTeam.format.includes('letsgo'); // var supportsAVs = !supportsEVs && this.curTeam.format.endsWith('norestrictions'); - var defaultEV = this.curTeam.gen <= 2 ? 252 : 0; + var defaultEV = (this.curTeam.gen > 2 && !this.ignoreEVLimits) ? 0 : 252; var maxEV = supportsEVs ? 252 : 200; var stepEV = supportsEVs ? 4 : 1; + // label column buf += '<div class="col labelcol"><div></div>'; buf += '<div><label>HP</label></div><div><label>Attack</label></div><div><label>Defense</label></div><div>'; @@ -2838,7 +2872,7 @@ var isBDSP = this.curTeam.format.includes('bdsp'); var isNatDex = this.curTeam.format.includes('nationaldex') || this.curTeam.format.includes('natdex'); var isHackmons = this.curTeam.format.includes('hackmons') || this.curTeam.format.endsWith('bh'); - var species = this.curTeam.dex.species.get(set.species); + var species = this.curTeam.dex.species.get(set.species, undefined, "from updateDetailsForm"); if (!set) return; buf += '<div class="resultheader"><h3>Details</h3></div>'; buf += '<form class="detailsform">'; @@ -2939,7 +2973,7 @@ e.stopPropagation(); var set = this.curSet; if (!set) return; - var species = this.curTeam.dex.species.get(set.species); + var species = this.curTeam.dex.species.get(set.species, undefined, "from detailsChange"); var isLetsGo = this.curTeam.format.includes('letsgo'); var isBDSP = this.curTeam.format.includes('bdsp'); var isNatDex = this.curTeam.format.includes('nationaldex') || this.curTeam.format.includes('natdex'); @@ -3216,7 +3250,7 @@ } else if (id in BattleMovedex && format && format.endsWith("trademarked")) { val = BattleMovedex[id].name; } else { - val = (id in BattleAbilities ? BattleAbilities[id].name : ''); + val = (id in BattleAbilities ? this.curTeam.dex.abilities.get(e.currentTarget.value).name : ''); } break; case 'item': @@ -3225,14 +3259,14 @@ } else if (id in BattleAbilities && format && format.endsWith("multibility")) { val = BattleAbilities[id].name; } else { - val = (id in BattleItems ? BattleItems[id].name : ''); + val = (id in BattleItems ? this.curTeam.dex.items.get(e.currentTarget.value).name : ''); } break; case 'move1': case 'move2': case 'move3': case 'move4': if (id in BattlePokedex && format && format.endsWith("pokemoves")) { val = BattlePokedex[id].name; } else { - val = (id in BattleMovedex ? BattleMovedex[id].name : ''); + val = (id in BattleMovedex ? this.curTeam.dex.moves.get(e.currentTarget.value).name : ''); } break; } @@ -3426,18 +3460,15 @@ if (resetSpeed) minSpe = false; if (moveName.substr(0, 13) === 'Hidden Power ') { if (!this.canHyperTrain(set)) { - var hpType = moveName.substr(13); - + var hpType = moveName.substr(13).toLowerCase(); set.ivs = {hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31}; if (this.curTeam.gen > 2) { - var HPivs = this.curTeam.dex.types.get(hpType).HPivs; - for (var i in HPivs) { - set.ivs[i] = HPivs[i]; + for (var i in exports.BattleTypeChart[hpType].HPivs) { + set.ivs[i] = exports.BattleTypeChart[hpType].HPivs[i]; } } else { - var HPdvs = this.curTeam.dex.types.get(hpType).HPdvs; - for (var i in HPdvs) { - set.ivs[i] = HPdvs[i] * 2; + for (var i in exports.BattleTypeChart[hpType].HPdvs) { + set.ivs[i] = exports.BattleTypeChart[hpType].HPdvs[i] * 2; } var atkDV = Math.floor(set.ivs.atk / 2); var defDV = Math.floor(set.ivs.def / 2); @@ -3470,7 +3501,10 @@ for (var i = 0; i < moves.length; ++i) { if (!moves[i]) continue; if (moves[i].substr(0, 13) === 'Hidden Power ') hasHiddenPower = true; - var move = this.curTeam.dex.moves.get(moves[i]); + //var move = this.curTeam.dex.moves.get(moves[i]); + var move = 0; + if (this.curTeam.mod) move = Dex.mod(this.curTeam.mod).moves.get(moves[i]); + else move = Dex.forGen(this.curTeam.gen).moves.get(moves[i]); if (move.id === 'transform') { hasHiddenPower = true; // A Pokemon with Transform can copy another Pokemon that knows Hidden Power @@ -3525,8 +3559,10 @@ }, setPokemon: function (val, selectNext) { var set = this.curSet; - var species = this.curTeam.dex.species.get(val); - if (!species.exists || set.species === species.name) { + var species; + if (this.curTeam.mod) species = Dex.mod(this.curTeam.mod).species.get(val, undefined, "from setPokemon 1"); + else species = Dex.forGen(this.curTeam.gen).species.get(val,undefined, "from setPokemon 2"); + if (!species || !species.exists || set.species === species.name) { if (selectNext) this.$('input[name=item]').select(); return; } @@ -3562,12 +3598,11 @@ if (set.gigantamax) delete set.gigantamax; if (set.teraType) delete set.teraType; if (!(this.curTeam.format.includes('hackmons') || this.curTeam.format.endsWith('bh')) && species.requiredItems.length === 1) { - set.item = species.requiredItems[0]; + set.item = species.requiredItems[0] || ''; } else { set.item = ''; } set.ability = species.abilities['0']; - set.moves = []; set.evs = {}; set.ivs = {}; @@ -3600,8 +3635,10 @@ // do this after setting set.evs because it's assumed to exist // after getStat is run - var species = this.curTeam.dex.species.get(set.species); - if (!species.exists) return 0; + var species; + if (this.curTeam.mod) species = Dex.mod(this.curTeam.mod).species.get(set.species,undefined, "from getStat1"); + else species = Dex.forGen(this.curTeam.gen).species.get(set.species,undefined, "from getStat 2"); + if (!species || !species.exists) return 0; if (!set.level) set.level = 100; if (typeof set.ivs[stat] === 'undefined') set.ivs[stat] = 31; @@ -3704,47 +3741,65 @@ this.room = data.room; this.curSet = data.curSet; this.chartIndex = data.index; + const mod = (this.room.curTeam && this.room.curTeam.mod) || ""; var species = this.room.curTeam.dex.species.get(this.curSet.species); var baseid = toID(species.baseSpecies); var forms = [baseid].concat(species.cosmeticFormes.map(toID)); - var spriteDir = Dex.resourcePrefix + 'sprites/'; + + let modSprite = Dex.getSpriteMod(mod, baseid, 'front', species.exists !== false) + || Dex.getSpriteMod(mod, species.id, 'front', species.exists !== false); + let resourcePrefix; + let d; + if (modSprite) { + resourcePrefix = Dex.modResourcePrefix + modSprite + '/'; + d = ""; + } else { + resourcePrefix = Dex.resourcePrefix; + d = "-"; + } + + var spriteDir = resourcePrefix + 'sprites/'; var spriteSize = 96; var spriteDim = 'width: 96px; height: 96px;'; var gen = Math.max(this.room.curTeam.gen, species.gen); - var dir = gen > 5 ? 'dex' : 'gen' + gen; - if (Dex.prefs('nopastgens')) gen = 'dex'; - if (Dex.prefs('bwgfx') && dir === 'dex') gen = 'gen5'; - spriteDir += dir; + var dir; + if (modSprite) { + dir = "front"; + } else { + if (Dex.prefs('bwgfx')) dir = 'gen5'; + else if (Dex.prefs('nopastgens') || gen > 5) dir = 'dex'; + else dir = 'gen' + gen; + } + var spriteDir = resourcePrefix + 'sprites/' + dir; if (dir === 'dex') { spriteSize = 120; spriteDim = 'width: 120px; height: 120px;'; } - var buf = ''; - buf += '<p>Pick a variant or <button name="close" class="button">Cancel</button></p>'; - buf += '<div class="formlist">'; + var buf = '<p>Pick a variant or <button name="close" class="button">Cancel</button></p><div class="formlist">'; var formCount = forms.length; for (var i = 0; i < formCount; i++) { var formid = forms[i].substring(baseid.length); var form = (formid ? formid[0].toUpperCase() + formid.slice(1) : ''); buf += '<button name="setForm" value="' + form + '" style="'; - buf += 'background-image: url(' + spriteDir + '/' + baseid + (form ? '-' + formid : '') + '.png); ' + spriteDim + '" class="option'; - buf += (form === (species.forme || '') ? ' cur' : '') + '"></button>'; + buf += 'background-image: url(' + spriteDir + '/' + baseid + (form ? d + formid : '') + '.png); ' + spriteDim + '" class="option'; + if (form === (species.forme || '')) buf += ' cur'; + buf += '"></button>'; } - buf += '<div style="clear:both"></div>'; - buf += '</div>'; + buf += '<div style="clear:both"></div></div>'; this.$el.html(buf).css({'max-width': (4 + spriteSize) * 7}); }, setForm: function (form) { - var species = Dex.species.get(this.curSet.species); - if (form && form !== species.form) { - this.curSet.species = Dex.species.get(species.baseSpecies + form).name; - } else if (!form) { + var species = this.room.curTeam.dex.species.get(this.curSet.species); + if (!form) { this.curSet.species = species.baseSpecies; } + else if (form !== species.form) { + this.curSet.species = this.room.curTeam.dex.species.get(species.baseSpecies + form).name; + } this.close(); if (this.room.curSet) { this.room.updatePokemonSprite(); diff --git a/play.pokemonshowdown.com/js/client.js b/play.pokemonshowdown.com/js/client.js index 8caa89362..24b6318c1 100644 --- a/play.pokemonshowdown.com/js/client.js +++ b/play.pokemonshowdown.com/js/client.js @@ -218,7 +218,7 @@ function toId() { getActionPHP: function () { var ret = '/~~' + Config.server.id + '/action.php'; if (Config.testclient) { - ret = 'https://' + Config.routes.client + ret; + ret = 'https://play.pokemonshowdown.com/action.php'; } return (this.getActionPHP = function () { return ret; @@ -2081,7 +2081,7 @@ function toId() { playNotificationSound: function () { if (window.BattleSound && !Dex.prefs('mute')) { - BattleSound.playSound('audio/notification.wav', Dex.prefs('notifvolume')); + BattleSound.playSound('https://' + Config.routes.psmain + '/audio/notification.wav', Dex.prefs('notifvolume')); } }, diff --git a/play.pokemonshowdown.com/js/replay-embed.template.js b/play.pokemonshowdown.com/js/replay-embed.template.js index 6b372e5ed..6aa3a1709 100644 --- a/play.pokemonshowdown.com/js/replay-embed.template.js +++ b/play.pokemonshowdown.com/js/replay-embed.template.js @@ -28,27 +28,30 @@ function requireScript(url) { document.head.appendChild(scriptEl); } -linkStyle('https://play.pokemonshowdown.com/style/font-awesome.css?'); -linkStyle('https://play.pokemonshowdown.com/style/battle.css?a7'); -linkStyle('https://play.pokemonshowdown.com/style/replay.css?a7'); -linkStyle('https://play.pokemonshowdown.com/style/utilichart.css?a7'); - -requireScript('https://play.pokemonshowdown.com/js/lib/ps-polyfill.js'); -requireScript('https://play.pokemonshowdown.com/config/config.js?a7'); -requireScript('https://play.pokemonshowdown.com/js/lib/jquery-1.11.0.min.js'); -requireScript('https://play.pokemonshowdown.com/js/lib/html-sanitizer-minified.js'); -requireScript('https://play.pokemonshowdown.com/js/battle-sound.js'); -requireScript('https://play.pokemonshowdown.com/js/battledata.js?a7'); -requireScript('https://play.pokemonshowdown.com/data/pokedex-mini.js?a7'); -requireScript('https://play.pokemonshowdown.com/data/pokedex-mini-bw.js?a7'); -requireScript('https://play.pokemonshowdown.com/data/graphics.js?a7'); -requireScript('https://play.pokemonshowdown.com/data/pokedex.js?a7'); -requireScript('https://play.pokemonshowdown.com/data/moves.js?a7'); -requireScript('https://play.pokemonshowdown.com/data/abilities.js?a7'); -requireScript('https://play.pokemonshowdown.com/data/items.js?a7'); -requireScript('https://play.pokemonshowdown.com/data/teambuilder-tables.js?a7'); -requireScript('https://play.pokemonshowdown.com/js/battle-tooltips.js?a7'); -requireScript('https://play.pokemonshowdown.com/js/battle.js?a7'); +linkStyle('http://petmodsdh.com/style/font-awesome.css?'); +linkStyle('http://petmodsdh.com/style/battle.css?a7'); +linkStyle('http://petmodsdh.com/style/replay.css?a7'); +linkStyle('http://petmodsdh.com/style/utilichart.css?a7'); + +requireScript('http://petmodsdh.com/js/lib/ps-polyfill.js'); +requireScript('http://petmodsdh.com/config/config.js?a7'); +requireScript('http://petmodsdh.com/js/lib/jquery-1.11.0.min.js'); +requireScript('http://petmodsdh.com/js/lib/lodash.compat.js'); +requireScript('http://petmodsdh.com/js/lib/html-sanitizer-minified.js'); +requireScript('http://petmodsdh.com/js/battle-sound.js'); +requireScript('http://petmodsdh.com/js/battledata.js?a7'); +requireScript('http://petmodsdh.com/data/pokedex-mini.js?a7'); +requireScript('http://petmodsdh.com/data/pokedex-mini-bw.js?a7'); +requireScript('http://petmodsdh.com/data/graphics.js?a7'); +requireScript('http://petmodsdh.com/data/pokedex.js?a7'); +requireScript('http://petmodsdh.com/data/moves.js?a7'); +requireScript('http://petmodsdh.com/data/abilities.js?a7'); +requireScript('http://petmodsdh.com/data/items.js?a7'); +requireScript('http://petmodsdh.com/data/teambuilder-tables.js?a7'); +requireScript('http://petmodsdh.com/data/mod-sprites.js?a7'); +requireScript('http://petmodsdh.com/data/mod-config.js?a7'); +requireScript('http://petmodsdh.com/js/battle-tooltips.js?a7'); +requireScript('http://petmodsdh.com/js/battle.js?a7'); var Replays = { battle: null, diff --git a/play.pokemonshowdown.com/js/search.js b/play.pokemonshowdown.com/js/search.js index 5fafa1ba3..8b8293481 100644 --- a/play.pokemonshowdown.com/js/search.js +++ b/play.pokemonshowdown.com/js/search.js @@ -108,9 +108,8 @@ var buf = '<p>Filters: '; for (var i = 0; i < this.filters.length; i++) { var text = this.filters[i][1]; - if (this.filters[i][0] === 'move') text = Dex.moves.get(text).name; - if (this.filters[i][0] === 'pokemon') text = Dex.species.get(text).name; - buf += '<button class="filter" value="' + BattleLog.escapeHTML(this.filters[i].join(':')) + '">' + text + ' <i class="fa fa-times-circle"></i></button> '; + if (this.filters[i][0] === 'move') text = this.engine.dex.species.get(text).name; + if (this.filters[i][0] === 'pokemon') text = this.engine.dex.moves.get(text).name; buf += '<button class="filter" value="' + BattleLog.escapeHTML(this.filters[i].join(':')) + '">' + text + ' <i class="fa fa-times-circle"></i></button> '; } if (!q) buf += '<small style="color: #888">(backspace = delete filter)</small>'; return buf + '</p>'; @@ -197,7 +196,7 @@ case 'sortmove': return this.renderMoveSortRow(); case 'pokemon': - var pokemon = this.engine.dex.species.get(id); + var pokemon = this.engine.dex.species.get(id, (id, undefined, "from renderRow")); return this.renderPokemonRow(pokemon, matchStart, matchLength, errorMessage, attrs); case 'move': var move = this.engine.dex.moves.get(id); @@ -303,7 +302,7 @@ // icon buf += '<span class="col iconcol">'; - buf += '<span style="' + Dex.getPokemonIcon(pokemon.name) + '"></span>'; + buf += '<span style="' + Dex.getPokemonIcon(pokemon.name, false, this.engine.dex.modid) + '"></span>'; buf += '</span> '; // name @@ -338,13 +337,12 @@ buf += '<span class="col typecol">'; var types = pokemon.types; for (var i = 0; i < types.length; i++) { - buf += Dex.getTypeIcon(types[i]); + buf += Dex.getTypeIcon(types[i], null, this.mod); } buf += '</span> '; - // abilities if (gen >= 3) { - var abilities = Dex.forGen(gen).species.get(id).abilities; + var abilities = pokemon.abilities; if (gen >= 5) { if (abilities['1']) { buf += '<span class="col twoabilitycol">' + abilities['0'] + '<br />' + @@ -407,7 +405,7 @@ // icon buf += '<span class="col iconcol">'; - buf += '<span style="' + Dex.getPokemonIcon(pokemon.name) + '"></span>'; + buf += '<span style="' + Dex.getPokemonIcon(pokemon.name, false, this.mod) + '"></span>'; buf += '</span> '; // name @@ -475,7 +473,7 @@ // icon buf += '<span class="col itemiconcol">'; - buf += '<span style="' + Dex.getItemIcon(item) + '"></span>'; + buf += '<span style="' + Dex.getItemIcon(item, null, this.mod) + '"></span>'; buf += '</span> '; // name @@ -559,7 +557,7 @@ // type buf += '<span class="col typecol">'; - buf += Dex.getTypeIcon(move.type); + buf += Dex.getTypeIcon(move.type, null, this.engine.dex.modid); buf += Dex.getCategoryIcon(move.category); buf += '</span> '; @@ -571,6 +569,9 @@ buf += '<span class="col pplabelcol"><em>PP</em><br />' + pp + '</span> '; // desc + if (this.engine.dex.gen < 9 && !BattleTeambuilderTable[this.engine.dex.modid]?.overrideMoveInfo?.[id]?.shortDesc) { + move.shortDesc = Dex.mod("gen" + this.engine.dex.gen).moves.get(id).shortDesc; // this does not correctly give the gen 1 description, but if it did this would be a fix + } buf += '<span class="col movedesccol">' + BattleLog.escapeHTML(move.shortDesc) + '</span> '; buf += '</a></li>'; @@ -596,7 +597,7 @@ // type buf += '<span class="col typecol">'; - buf += Dex.getTypeIcon(move.type); + buf += Dex.getTypeIcon(move.type, null, this.engine.dex.modid); buf += Dex.getCategoryIcon(move.category); buf += '</span> '; diff --git a/play.pokemonshowdown.com/js/storage.js b/play.pokemonshowdown.com/js/storage.js index 8ca36a79b..58b7be737 100644 --- a/play.pokemonshowdown.com/js/storage.js +++ b/play.pokemonshowdown.com/js/storage.js @@ -888,7 +888,15 @@ Storage.fastUnpackTeam = function (buf) { while (true) { var set = {}; team.push(set); - + + var thisDex = Dex; + for (var teamid in this.teams) { + var teamData = this.teams[teamid]; + if (teamData.team === buf && teamData.mod) { + thisDex = Dex.mod(teamData.mod); + } + } + // name j = buf.indexOf('|', i); set.name = buf.substring(i, j); @@ -907,7 +915,7 @@ Storage.fastUnpackTeam = function (buf) { // ability j = buf.indexOf('|', i); var ability = buf.substring(i, j); - var species = Dex.species.get(set.species); + var species = thisDex.species.get(set.species); if (species.baseSpecies === 'Zygarde' && ability === 'H') ability = 'Power Construct'; set.ability = (species.abilities && ['', '0', '1', 'H', 'S'].includes(ability) ? species.abilities[ability] || '!!!ERROR!!!' : ability); i = j + 1; @@ -999,6 +1007,14 @@ Storage.fastUnpackTeam = function (buf) { Storage.unpackTeam = function (buf) { if (!buf) return []; + var thisDex = Dex; + for (var teamid in this.teams) { + var teamData = this.teams[teamid]; + if (teamData.team === buf && teamData.mod) { + thisDex = Dex.mod(teamData.mod); + } + } + var team = []; var i = 0, j = 0; @@ -1013,25 +1029,25 @@ Storage.unpackTeam = function (buf) { // species j = buf.indexOf('|', i); - set.species = Dex.species.get(buf.substring(i, j)).name || set.name; + set.species = thisDex.species.get(buf.substring(i, j)).name || set.name; i = j + 1; // item j = buf.indexOf('|', i); - set.item = Dex.items.get(buf.substring(i, j)).name; + set.item = thisDex.items.get(buf.substring(i, j)).name; i = j + 1; // ability j = buf.indexOf('|', i); - var ability = Dex.abilities.get(buf.substring(i, j)).name; - var species = Dex.species.get(set.species); + var ability = thisDex.abilities.get(buf.substring(i, j)).name; + var species = thisDex.species.get(set.species); set.ability = (species.abilities && ability in {'':1, 0:1, 1:1, H:1} ? species.abilities[ability || '0'] : ability); i = j + 1; // moves j = buf.indexOf('|', i); set.moves = buf.substring(i, j).split(',').map(function (moveid) { - return Dex.moves.get(moveid).name; + return thisDex.moves.get(moveid).name; }); i = j + 1; @@ -1140,15 +1156,31 @@ Storage.packedTeamNames = function (buf) { return team; }; -Storage.packedTeamIcons = function (buf) { +Storage.packedTeamIcons = function (buf, mod) { if (!buf) return '<em>(empty team)</em>'; return this.packedTeamNames(buf).map(function (species) { - return '<span class="picon" style="' + Dex.getPokemonIcon(species) + ';float:left;overflow:visible"><span style="font-size:0px">' + toID(species) + '</span></span>'; + return '<span class="picon" style="' + Dex.getPokemonIcon(species, false, mod) + ';float:left;overflow:visible"><span style="font-size:0px">' + toID(species) + '</span></span>'; }).join(''); }; Storage.getTeamIcons = function (team) { + let formatmod = ''; + const format = team.format; + //Bruteforcing through our list of mods to check if one has our team's format + //(For empty teams this isn't necessary) + if (team.team) { + for (const mod in window.ModConfig) { + const modformats = window.ModConfig[mod].formats; + for (const formatid in modformats) { + if (format === formatid) { + formatmod = mod; + break; + } + } + if (formatmod) break; + } + } if (team.iconCache === '!') { // an icon cache of '!' means that not only are the icons not cached, // but the packed team isn't guaranteed to be updated to the latest @@ -1160,12 +1192,12 @@ Storage.getTeamIcons = function (team) { // a packed team. team.team = Storage.packTeam(Storage.activeSetList); if ('teambuilder' in app.rooms) { - return Storage.packedTeamIcons(team.team); + return Storage.packedTeamIcons(team.team, formatmod); } Storage.activeSetList = null; - team.iconCache = Storage.packedTeamIcons(team.team); + team.iconCache = Storage.packedTeamIcons(team.team, formatmod); } else if (!team.iconCache) { - team.iconCache = Storage.packedTeamIcons(team.team); + team.iconCache = Storage.packedTeamIcons(team.team, formatmod); } return team.iconCache; }; @@ -1197,6 +1229,7 @@ Storage.importTeam = function (buffer, teams) { } else if (text.length === 1 || (text.length === 2 && !text[1])) { return Storage.unpackTeam(text[0]); } + const mod = (window.room.curTeam && window.room.curTeam.mod) ? window.room.curTeam.mod : ""; for (var i = 0; i < text.length; i++) { var line = $.trim(text[i]); if (line === '' || line === '---') { @@ -1258,11 +1291,29 @@ Storage.importTeam = function (buffer, teams) { var parenIndex = line.lastIndexOf(' ('); if (line.substr(line.length - 1) === ')' && parenIndex !== -1) { line = line.substr(0, line.length - 1); - curSet.species = Dex.species.get(line.substr(parenIndex + 2)).name; + var thisDex = Dex.species.get(line.substr(parenIndex + 2)).exists ? Dex : null; + if (!thisDex) { + for (var modid in (ModConfig)) { + if (Dex.mod(modid).species.get(line.substr(parenIndex + 2)).exists) { + thisDex = Dex.mod(modid); + } + } + } + curSet.species = thisDex.species.get(line.substr(parenIndex + 2)).name; line = line.substr(0, parenIndex); curSet.name = line; } else { - curSet.species = Dex.species.get(line).name; + var thisDex = Dex.species.get(line).exists ? Dex : null; + if (!thisDex) { + for (var modid in (ModConfig)) { + if (Dex.mod(modid).species.get(line).exists) { + thisDex = Dex.mod(modid); + } + } + } + console.log(curSet); + console.log(line); + curSet.species = thisDex.species.get(line).name; curSet.name = ''; } } else if (line.substr(0, 7) === 'Trait: ') { diff --git a/play.pokemonshowdown.com/src/battle-animations-moves.ts b/play.pokemonshowdown.com/src/battle-animations-moves.ts index f80971cb8..c6172b929 100644 --- a/play.pokemonshowdown.com/src/battle-animations-moves.ts +++ b/play.pokemonshowdown.com/src/battle-animations-moves.ts @@ -711,7 +711,7 @@ export const BattleMoveAnims: AnimTable = { time: 1550, }, 'decel'); } - scene.backgroundEffect(`url('https://${Config.routes.client}/fx/weather-hail.png')`, 750, 1, 800); + scene.backgroundEffect(`url('https://${Config.routes.psmain}/fx/weather-hail.png')`, 750, 1, 800); }, }, sandstorm: { @@ -1533,7 +1533,7 @@ export const BattleMoveAnims: AnimTable = { orderup: { anim(scene, [attacker, defender]) { const tatsugiriSprite = { - url: `https://${Config.routes.client}/sprites/gen5/tatsugiri${['-droopy', '-stretchy', ''][Math.floor(Math.random() * 3)]}.png`, + url: `https://${Config.routes.psmain}/sprites/gen5/tatsugiri${['-droopy', '-stretchy', ''][Math.floor(Math.random() * 3)]}.png`, w: 96, h: 96, }; @@ -3610,7 +3610,7 @@ export const BattleMoveAnims: AnimTable = { }, morningsun: { anim(scene, [attacker, defender]) { - scene.backgroundEffect(`url('https://${Config.routes.client}/fx/weather-sunnyday.jpg')`, 700, 0.5); + scene.backgroundEffect(`url('https://${Config.routes.psmain}/fx/weather-sunnyday.jpg')`, 700, 0.5); scene.showEffect('wisp', { x: attacker.x + 40, y: attacker.y - 40, @@ -3746,7 +3746,7 @@ export const BattleMoveAnims: AnimTable = { }, cosmicpower: { anim(scene, [attacker]) { - scene.backgroundEffect(`url('https://${Config.routes.client}/fx/bg-space.jpg')`, 600, 0.6); + scene.backgroundEffect(`url('https://${Config.routes.psmain}/fx/bg-space.jpg')`, 600, 0.6); scene.showEffect('wisp', { x: attacker.x + 40, y: attacker.y - 40, @@ -5586,7 +5586,7 @@ export const BattleMoveAnims: AnimTable = { }, seismictoss: { anim(scene, [attacker, defender]) { - scene.backgroundEffect(`url('https://${Config.routes.client}/fx/bg-space.jpg')`, 500, 0.6, 300); + scene.backgroundEffect(`url('https://${Config.routes.psmain}/fx/bg-space.jpg')`, 500, 0.6, 300); scene.showEffect('wisp', { x: defender.x, y: defender.y + 10, @@ -8418,7 +8418,7 @@ export const BattleMoveAnims: AnimTable = { }, meteormash: { anim(scene, [attacker, defender]) { - scene.backgroundEffect(`url('https://${Config.routes.client}/fx/bg-space.jpg')`, 1000, 0.4); + scene.backgroundEffect(`url('https://${Config.routes.psmain}/fx/bg-space.jpg')`, 1000, 0.4); scene.showEffect(attacker.sp, { x: attacker.leftof(20), y: attacker.y, @@ -18832,7 +18832,7 @@ export const BattleMoveAnims: AnimTable = { }, psystrike: { anim(scene, [attacker, defender]) { - scene.backgroundEffect(`url('https://${Config.routes.client}/fx/weather-psychicterrain.png')`, 950, 0.6); + scene.backgroundEffect(`url('https://${Config.routes.psmain}/fx/weather-psychicterrain.png')`, 950, 0.6); scene.showEffect('poisonwisp', { x: defender.x - 100, y: defender.y, @@ -19999,7 +19999,7 @@ export const BattleMoveAnims: AnimTable = { }, wish: { anim(scene, [attacker]) { - scene.backgroundEffect(`url('https://${Config.routes.client}/fx/bg-space.jpg')`, 600, 0.4); + scene.backgroundEffect(`url('https://${Config.routes.psmain}/fx/bg-space.jpg')`, 600, 0.4); scene.showEffect('wisp', { x: attacker.x, @@ -20013,7 +20013,7 @@ export const BattleMoveAnims: AnimTable = { }, 'accel'); }, residualAnim(scene, [attacker]) { - scene.backgroundEffect(`url('https://${Config.routes.client}/fx/bg-space.jpg')`, 600, 0.4); + scene.backgroundEffect(`url('https://${Config.routes.psmain}/fx/bg-space.jpg')`, 600, 0.4); scene.showEffect('wisp', { x: attacker.x, @@ -21262,7 +21262,7 @@ export const BattleMoveAnims: AnimTable = { }, dracometeor: { anim(scene, [attacker, defender]) { - scene.backgroundEffect(`url('https://${Config.routes.client}/fx/bg-space.jpg')`, 1100, 0.8); + scene.backgroundEffect(`url('https://${Config.routes.psmain}/fx/bg-space.jpg')`, 1100, 0.8); scene.showEffect('flareball', { x: defender.leftof(-200), y: defender.y + 175, @@ -22767,7 +22767,7 @@ export const BattleMoveAnims: AnimTable = { let ystep = (defender.x - 200 - attacker.x) / 5; let zstep = (defender.z - attacker.z) / 5; - scene.backgroundEffect(`url('https://${Config.routes.client}/fx/weather-sunnyday.jpg')`, 900, 0.5); + scene.backgroundEffect(`url('https://${Config.routes.psmain}/fx/weather-sunnyday.jpg')`, 900, 0.5); for (let i = 0; i < 5; i++) { scene.showEffect('energyball', { @@ -23126,7 +23126,7 @@ export const BattleMoveAnims: AnimTable = { let ystep = 20; let zstep = 0; - scene.backgroundEffect(`url('https://${Config.routes.client}/fx/weather-sunnyday.jpg')`, 900, 0.5); + scene.backgroundEffect(`url('https://${Config.routes.psmain}/fx/weather-sunnyday.jpg')`, 900, 0.5); scene.showEffect('sword', { x: attacker.leftof(10), @@ -23411,7 +23411,7 @@ export const BattleMoveAnims: AnimTable = { let ystep = (defender.x - 200 - attacker.x) / 5; let zstep = (defender.z - attacker.z) / 5; - scene.backgroundEffect(`url('https://${Config.routes.client}/fx/weather-sandstorm.png')`, 900, 0.5); + scene.backgroundEffect(`url('https://${Config.routes.psmain}/fx/weather-sandstorm.png')`, 900, 0.5); for (let i = 0; i < 5; i++) { scene.showEffect('mudwisp', { @@ -23611,7 +23611,7 @@ export const BattleMoveAnims: AnimTable = { }, sheercold: { // Reminder: Improve this later anim(scene, [attacker, defender]) { - scene.backgroundEffect(`url('https://${Config.routes.client}/sprites/gen6bgs/bg-icecave.jpg')`, 1000, 0.6); + scene.backgroundEffect(`url('https://${Config.routes.psmain}/sprites/gen6bgs/bg-icecave.jpg')`, 1000, 0.6); scene.showEffect('icicle', { x: defender.x, y: defender.y, @@ -23629,7 +23629,7 @@ export const BattleMoveAnims: AnimTable = { }, glaciallance: { anim(scene, [attacker, ...defenders]) { - scene.backgroundEffect(`url('https://${Config.routes.client}/sprites/gen6bgs/bg-icecave.jpg')`, 1000, 0.6); + scene.backgroundEffect(`url('https://${Config.routes.psmain}/sprites/gen6bgs/bg-icecave.jpg')`, 1000, 0.6); for (const defender of defenders) { scene.showEffect('icicle', { x: defender.x, @@ -25944,7 +25944,7 @@ export const BattleMoveAnims: AnimTable = { }, dragonascent: { anim(scene, [attacker, defender]) { - scene.backgroundEffect(`url('https://${Config.routes.client}/fx/bg-space.jpg')`, 1000, 0.7); + scene.backgroundEffect(`url('https://${Config.routes.psmain}/fx/bg-space.jpg')`, 1000, 0.7); scene.showEffect('iceball', { x: attacker.leftof(-25), y: attacker.y + 250, @@ -29435,7 +29435,7 @@ export const BattleMoveAnims: AnimTable = { }, plasmafists: { anim(scene, [attacker, defender]) { - scene.backgroundEffect(`url('https://${Config.routes.client}/sprites/gen6bgs/bg-earthycave.jpg')`, 2000, 1); + scene.backgroundEffect(`url('https://${Config.routes.psmain}/sprites/gen6bgs/bg-earthycave.jpg')`, 2000, 1); scene.backgroundEffect('#000000', 1000, 0.6); scene.backgroundEffect('#FFFFFF', 300, 0.6, 1000); scene.showEffect('electroball', { @@ -29678,7 +29678,7 @@ export const BattleMoveAnims: AnimTable = { }, collisioncourse: { anim(scene, [attacker, defender]) { - scene.backgroundEffect(`url('https://${Config.routes.client}/fx/weather-sunnyday.jpg')`, 1300, 0.5); + scene.backgroundEffect(`url('https://${Config.routes.psmain}/fx/weather-sunnyday.jpg')`, 1300, 0.5); scene.showEffect(attacker.sp, { x: attacker.x, y: attacker.y, @@ -29824,7 +29824,7 @@ export const BattleMoveAnims: AnimTable = { }, electrodrift: { anim(scene, [attacker, defender]) { - scene.backgroundEffect(`url('https://${Config.routes.client}/fx/weather-electricterrain.png')`, 1300, 0.5); + scene.backgroundEffect(`url('https://${Config.routes.psmain}/fx/weather-electricterrain.png')`, 1300, 0.5); scene.showEffect(attacker.sp, { x: attacker.x, y: attacker.y, @@ -33847,7 +33847,7 @@ export const BattleMoveAnims: AnimTable = { oceanicoperetta: { anim(scene, [attacker, defender]) { scene.backgroundEffect('linear-gradient(#000000 20%, #0000DD)', 2700, 0.4); - scene.backgroundEffect(`url('https://${Config.routes.client}/fx/weather-raindance.jpg')`, 700, 0.2, 2000); + scene.backgroundEffect(`url('https://${Config.routes.psmain}/fx/weather-raindance.jpg')`, 700, 0.2, 2000); scene.showEffect('iceball', { x: attacker.x, y: attacker.y + 120, @@ -34170,7 +34170,7 @@ export const BattleMoveAnims: AnimTable = { }, splinteredstormshards: { anim(scene, [attacker, defender]) { - scene.backgroundEffect(`url('https://${Config.routes.client}/sprites/gen6bgs/bg-earthycave.jpg')`, 2700, 0.8, 300); + scene.backgroundEffect(`url('https://${Config.routes.psmain}/sprites/gen6bgs/bg-earthycave.jpg')`, 2700, 0.8, 300); scene.backgroundEffect('linear-gradient(#FFC720 15%, #421800)', 2700, 0.7); scene.backgroundEffect('#ffffff', 400, 0.6, 2500); scene.showEffect('rock3', { @@ -34893,7 +34893,7 @@ export const BattleMoveAnims: AnimTable = { } const defender = defenders[1] || defenders[0]; scene.backgroundEffect('#000000', 300, 0.9); - scene.backgroundEffect(`url('https://${Config.routes.client}/sprites/gen6bgs/bg-earthycave.jpg')`, 2000, 0.7, 300); + scene.backgroundEffect(`url('https://${Config.routes.psmain}/sprites/gen6bgs/bg-earthycave.jpg')`, 2000, 0.7, 300); scene.backgroundEffect('linear-gradient(#FB5C1E 20%, #3F1D0F', 2000, 0.6, 300); scene.backgroundEffect('#FFFFFF', 1000, 0.9, 2200); scene.showEffect('shine', { @@ -35565,8 +35565,8 @@ export const BattleMoveAnims: AnimTable = { let ystep = (defender.x - 200 - attacker.x) / 5; let zstep = (defender.z - attacker.z) / 5; - scene.backgroundEffect(`url('https://${Config.routes.client}/fx/weather-trickroom.png')`, 700, 1); - scene.backgroundEffect(`url('https://${Config.routes.client}/fx/bg-space.jpg')`, 2500, 1, 700); + scene.backgroundEffect(`url('https://${Config.routes.psmain}/fx/weather-trickroom.png')`, 700, 1); + scene.backgroundEffect(`url('https://${Config.routes.psmain}/fx/bg-space.jpg')`, 2500, 1, 700); scene.backgroundEffect('#FFFFFF', 1500, 1, 2500); scene.showEffect('flareball', { diff --git a/play.pokemonshowdown.com/src/battle-animations.ts b/play.pokemonshowdown.com/src/battle-animations.ts index cbc70486a..75cae9eca 100644 --- a/play.pokemonshowdown.com/src/battle-animations.ts +++ b/play.pokemonshowdown.com/src/battle-animations.ts @@ -115,7 +115,7 @@ export class BattleScene implements BattleSceneStub { let numericId = 0; if (battle.id) { numericId = parseInt(battle.id.slice(battle.id.lastIndexOf('-') + 1), 10); - if (this.battle.id.includes('digimon')) this.mod = 'digimon'; + if (this.battle.id.includes('digimon')) this.battle.mod = 'digimon'; } if (!numericId) { numericId = Math.floor(Math.random() * 1000000); @@ -676,14 +676,15 @@ export class BattleScene implements BattleSceneStub { pokemonhtml += `<span class="picon" style="` + Dex.getPokemonIcon('zoroark') + `" title="Unrevealed Illusion user" aria-label="Unrevealed Illusion user"></span>`; } else if (!poke) { pokemonhtml += `<span class="picon" style="` + Dex.getPokemonIcon('pokeball') + `" title="Not revealed" aria-label="Not revealed"></span>`; - } else if (!poke.ident && this.battle.teamPreviewCount && this.battle.teamPreviewCount < side.pokemon.length) { - // in VGC (bring 6 pick 4) and other pick-less-than-you-bring formats, this is - // a pokemon that's been brought but not necessarily picked - const details = this.getDetailsText(poke); - pokemonhtml += `<span${tooltipCode} style="` + Dex.getPokemonIcon(poke, !side.isFar) + `;opacity:0.6" aria-label="${details}"></span>`; } else { + pokemonhtml += `<span${tooltipCode} style="` + Dex.getPokemonIcon(poke, !side.isFar, this.battle.mod); const details = this.getDetailsText(poke); - pokemonhtml += `<span${tooltipCode} style="` + Dex.getPokemonIcon(poke, !side.isFar) + `" aria-label="${details}"></span>`; + if (!poke.ident && this.battle.teamPreviewCount && this.battle.teamPreviewCount < side.pokemon.length) { + // in VGC (bring 6 pick 4) and other pick-less-than-you-bring formats, this is + // a pokemon that's been brought but not necessarily picked + pokemonhtml += `;opacity:0.6`; + } + pokemonhtml += `" aria-label="${details}"></span>`; } if (i % 3 === 2) pokemonhtml += `</div><div class="teamicons">`; } @@ -842,7 +843,7 @@ export class BattleScene implements BattleSceneStub { let spriteData = Dex.getSpriteData(pokemon, !!spriteIndex, { gen: this.gen, noScale: true, - mod: this.mod, + mod: this.battle.mod, }); let y = 0; let x = 0; @@ -856,8 +857,11 @@ export class BattleScene implements BattleSceneStub { if (textBuf) textBuf += ' / '; textBuf += pokemon.speciesForme; let url = spriteData.url; + var placeholderSprite = spriteData.isFrontSprite // Pet Mods placeholder sprites + ? "https://play.pokemonshowdown.com/sprites/gen5/substitute.png" + : "https://play.pokemonshowdown.com/sprites/gen5-back/substitute.png"; // if (this.paused) url.replace('/xyani', '/xy').replace('.gif', '.png'); - buf += '<img src="' + url + '" width="' + spriteData.w + '" height="' + spriteData.h + '" style="position:absolute;top:' + Math.floor(y - spriteData.h / 2) + 'px;left:' + Math.floor(x - spriteData.w / 2) + 'px" />'; + buf += '<img src="' + url + '" width="' + spriteData.w + '" height="' + spriteData.h + '" style="position:absolute;top:' + Math.floor(y - spriteData.h / 2) + 'px;left:' + Math.floor(x - spriteData.w / 2) + 'px" onerror="this.src=\'' + placeholderSprite + '\'"/>'; buf2 += '<div style="position:absolute;top:' + (y + 45) + 'px;left:' + (x - 40) + 'px;width:80px;font-size:10px;text-align:center;color:#FFF;">'; const gender = pokemon.gender; if (gender === 'M' || gender === 'F') { @@ -1090,7 +1094,7 @@ export class BattleScene implements BattleSceneStub { addPokemonSprite(pokemon: Pokemon) { const sprite = new PokemonSprite(Dex.getSpriteData(pokemon, pokemon.side.isFar, { gen: this.gen, - mod: this.mod, + mod: this.battle.mod, }), { x: pokemon.side.x, y: pokemon.side.y, @@ -1395,7 +1399,7 @@ export class BattleScene implements BattleSceneStub { typeAnim(pokemon: Pokemon, types: string) { const result = BattleLog.escapeHTML(types).split('/').map(type => - '<img src="' + Dex.resourcePrefix + 'sprites/types/' + encodeURIComponent(type) + '.png" alt="' + type + '" class="pixelated" />' + Dex.getTypeIcon(encodeURIComponent(type),null,this.battle.mod) ).join(' '); this.resultAnim(pokemon, result, 'neutral'); } @@ -1541,6 +1545,21 @@ export class BattleScene implements BattleSceneStub { return pokemon.sprite.afterMove(); } + updateSpritesForSide(side: Side) { + side.missedPokemon?.sprite?.destroy(); + + side.missedPokemon = { + sprite: new PokemonSprite(null, { + x: side.leftof(-100), + y: side.y, + z: side.z, + opacity: 0, + }, this, side.isFar), + } as any; + + side.missedPokemon.sprite.isMissedPokemon = true; + } + // Misc ///////////////////////////////////////////////////////////////////// @@ -1989,7 +2008,7 @@ export class PokemonSprite extends Sprite { if (this.$sub) return; const subsp = Dex.getSpriteData('substitute', this.isFrontSprite, { gen: this.scene.gen, - mod: this.scene.mod, + mod: this.scene.battle.mod, }); this.subsp = subsp; this.$sub = $('<img src="' + subsp.url + '" style="display:block;opacity:0;position:absolute"' + (subsp.pixelated ? ' class="pixelated"' : '') + ' />'); @@ -2104,7 +2123,7 @@ export class PokemonSprite extends Sprite { if (!this.oldsp) this.oldsp = this.sp; this.sp = Dex.getSpriteData(pokemon, this.isFrontSprite, { gen: this.scene.gen, - mod: this.scene.mod, + mod: this.scene.battle.mod, }); } else if (this.oldsp) { this.sp = this.oldsp; @@ -2509,7 +2528,7 @@ export class PokemonSprite extends Sprite { if (!this.scene.animating && !isPermanent) return; let sp = Dex.getSpriteData(pokemon, this.isFrontSprite, { gen: this.scene.gen, - mod: this.scene.mod, + mod: this.scene.battle.mod, }); let oldsp = this.sp; if (isPermanent) { @@ -2517,7 +2536,7 @@ export class PokemonSprite extends Sprite { // if a permanent forme change happens while dynamaxed, we need an undynamaxed sprite to go back to this.oldsp = Dex.getSpriteData(pokemon, this.isFrontSprite, { gen: this.scene.gen, - mod: this.scene.mod, + mod: this.scene.battle.mod, dynamax: false, }); } else { @@ -2818,12 +2837,12 @@ export class PokemonSprite extends Sprite { } else if (pokemon.volatiles.typechange && pokemon.volatiles.typechange[1]) { const types = pokemon.volatiles.typechange[1].split('/'); for (const type of types) { - status += '<img src="' + Dex.resourcePrefix + 'sprites/types/' + encodeURIComponent(type) + '.png" alt="' + type + '" class="pixelated" /> '; + status += Dex.getTypeIcon(encodeURIComponent(type),null,this.scene.battle.mod); } } if (pokemon.volatiles.typeadd) { const type = pokemon.volatiles.typeadd[1]; - status += '+<img src="' + Dex.resourcePrefix + 'sprites/types/' + type + '.png" alt="' + type + '" class="pixelated" /> '; + status += Dex.getTypeIcon(type,null,this.scene.battle.mod); } for (const stat in pokemon.boosts) { if (pokemon.boosts[stat]) { diff --git a/play.pokemonshowdown.com/src/battle-choices.ts b/play.pokemonshowdown.com/src/battle-choices.ts index e44fd75a8..1d59b585f 100644 --- a/play.pokemonshowdown.com/src/battle-choices.ts +++ b/play.pokemonshowdown.com/src/battle-choices.ts @@ -189,7 +189,7 @@ class BattleChoiceBuilder { } if (choice.choiceType === 'move') { if (!choice.targetLoc && this.requestLength() > 1) { - const choosableTargets = ['normal', 'any', 'adjacentAlly', 'adjacentAllyOrSelf', 'adjacentFoe']; + const choosableTargets = ['normal', 'any', 'adjacentAlly', 'adjacentAllyOrSelf', 'anyAlly', 'adjacentFoe']; if (choosableTargets.includes(this.getChosenMove(choice, this.index()).target)) { this.current.move = choice.move; this.current.mega = choice.mega; diff --git a/play.pokemonshowdown.com/src/battle-dex-data.ts b/play.pokemonshowdown.com/src/battle-dex-data.ts index 4761bc499..4afbdf5e0 100644 --- a/play.pokemonshowdown.com/src/battle-dex-data.ts +++ b/play.pokemonshowdown.com/src/battle-dex-data.ts @@ -1091,6 +1091,7 @@ class Item implements Effect { readonly id: ID; readonly name: string; readonly gen: number; + readonly isNonstandard: string; readonly exists: boolean; readonly num: number; @@ -1118,6 +1119,7 @@ class Item implements Effect { this.name = Dex.sanitizeName(name); this.id = id; this.gen = data.gen || 0; + this.isNonstandard = data.isNonstandard || undefined; this.exists = ('exists' in data ? !!data.exists : true); this.num = data.num || 0; @@ -1204,7 +1206,7 @@ interface MoveFlags { wind?: 1 | 0; } -type MoveTarget = 'normal' | 'any' | 'adjacentAlly' | 'adjacentFoe' | 'adjacentAllyOrSelf' | // single-target +type MoveTarget = 'normal' | 'any' | 'adjacentAlly' | 'adjacentFoe' | 'adjacentAllyOrSelf' | 'anyAlly' | // single-target 'self' | 'randomNormal' | // single-target, automatic 'allAdjacent' | 'allAdjacentFoes' | // spread 'allySide' | 'foeSide' | 'all'; // side and field @@ -1231,6 +1233,7 @@ class Move implements Effect { readonly desc: string; readonly shortDesc: string; readonly isNonstandard: string | null; + readonly viable: boolean | null; readonly isZ: ID; readonly zMove?: { basePower?: number, @@ -1239,7 +1242,7 @@ class Move implements Effect { }; readonly isMax: boolean | string; readonly maxMove: {basePower: number}; - readonly ohko: true | 'Ice' | null; + readonly ohko: true | TypeName | null; readonly recoil: number[] | null; readonly heal: number[] | null; readonly multihit: number[] | number | null; @@ -1273,6 +1276,7 @@ class Move implements Effect { this.desc = data.desc; this.shortDesc = data.shortDesc; this.isNonstandard = data.isNonstandard || null; + this.viable = ('viable' in data ? !!data.viable : null); this.isZ = data.isZ || ''; this.zMove = data.zMove || {}; this.ohko = data.ohko || null; diff --git a/play.pokemonshowdown.com/src/battle-dex-search.ts b/play.pokemonshowdown.com/src/battle-dex-search.ts index a9ff39557..2f2caab56 100644 --- a/play.pokemonshowdown.com/src/battle-dex-search.ts +++ b/play.pokemonshowdown.com/src/battle-dex-search.ts @@ -26,6 +26,7 @@ declare const BattleSearchIndex: [ID, SearchType, number?, number?][]; declare const BattleSearchIndexOffset: any; declare const BattleTeambuilderTable: any; + /** * Backend for search UIs. */ @@ -80,8 +81,11 @@ class DexSearch { */ filters: SearchFilter[] | null = null; + constructor(searchType: SearchType | '' = '', formatid = '' as ID, species = '' as ID) { this.setType(searchType, formatid, species); + if (window.room.curTeam.mod) this.dex = Dex.mod(window.room.curTeam.mod); + } getTypedSearch(searchType: SearchType | '', format = '' as ID, speciesOrSet: ID | PokemonSet = '' as ID) { @@ -402,6 +406,29 @@ class DexSearch { topbufIndex = 2; } + // determine if the element comes from the current mod + const table = BattleTeambuilderTable[window.room.curTeam.mod]; + if ( + typeIndex === 1 && (!BattlePokedex[id] || BattlePokedex[id].exists === false) && + (!table || !table.overrideDexInfo || id in table.overrideDexInfo === false) + ) continue; + else if ( + typeIndex === 5 && (!BattleItems[id] || BattleItems[id].exists === false) && + (!table || !table.overrideItemData || id in table.overrideItemData === false) + ) continue; + else if ( + typeIndex === 4 && (!BattleMovedex[id] || BattleMovedex[id].exists === false) && + (!table || !table.overrideMoveInfo || id in table.overrideMoveInfo === false) + ) continue; + else if ( + typeIndex === 6 && (!BattleAbilities[id] || BattleAbilities[id].exists === false) && + (!table || !table.overrideAbilityDesc || id in table.overrideAbilityDesc === false) + ) continue; + else if ( + typeIndex === 2 && id.replace(id.charAt(0), id.charAt(0).toUpperCase()) in window.BattleTypeChart === false && + (!table || id.replace(id.charAt(0), id.charAt(0).toUpperCase()) in table.overrideTypeChart === false) + ) continue; + if (illegal && typeIndex === searchTypeIndex) { // Always show illegal results under legal results. // This is done by putting legal results (and the type header) @@ -454,6 +481,28 @@ class DexSearch { let buf: SearchRow[] = []; let illegalBuf: SearchRow[] = []; let illegal = this.typedSearch?.illegalReasons; + // Change object to look in if using a mod + let pokedex = BattlePokedex; + let moveDex = BattleMovedex; + if (window.room.curTeam.mod) { + pokedex = {}; + moveDex = {}; + const table = BattleTeambuilderTable[window.room.curTeam.mod]; + for (const id in table.overrideDexInfo) { + pokedex[id] = { + types: table.overrideDexInfo[id].types, + abilities: table.overrideDexInfo[id].abilities, + }; + } + for (const id in table.overrideMoveInfo) { + moveDex[id] = { + type: table.overrideMoveInfo.type, + category: table.overrideMoveInfo.category, + }; + } + pokedex = {...pokedex, ...BattlePokedex}; + moveDex = {...moveDex, ...BattleMovedex}; + } if (searchType === 'pokemon') { switch (fType) { case 'type': @@ -538,6 +587,12 @@ abstract class BattleTypedSearch<T extends SearchType> { * "Doubles" and "Let's Go" from the name. */ format = '' as ID; + /** + * + * mod formats can set the format variable to a standard format, so modFormat + * keeps track of the original format in such a case + */ + modFormat = '' as ID; /** * `species` is the second of two base filters. It constrains results to * things that species can use, and affects the default sort. @@ -548,6 +603,7 @@ abstract class BattleTypedSearch<T extends SearchType> { * (Abilities/items can affect what moves are sorted as usable.) */ set: PokemonSet | null = null; + mod = ''; protected formatType: 'doubles' | 'bdsp' | 'bdspdoubles' | 'bw1' | 'letsgo' | 'metronome' | 'natdex' | 'nfe' | 'ssdlc1' | 'ssdlc1doubles' | 'predlc' | 'predlcdoubles' | 'predlcnatdex' | 'svdlc1' | 'svdlc1doubles' | @@ -573,11 +629,38 @@ abstract class BattleTypedSearch<T extends SearchType> { this.baseResults = null; this.baseIllegalResults = null; - + this.modFormat = format; + let gen = 9; + const ClientMods = window.ModConfig; if (format.slice(0, 3) === 'gen') { const gen = (Number(format.charAt(3)) || 6); - format = (format.slice(4) || 'customgame') as ID; + // format = (format.slice(4) || 'customgame') as ID; this.dex = Dex.forGen(gen); + let mod = ''; + let overrideFormat = ''; + let modFormatType = ''; + for (const modid in (ClientMods)) { + for (const formatid in ClientMods[modid].formats) { + if (formatid === format || format.slice(4) === formatid) { + if (format.slice(4) === formatid) this.modFormat = formatid; + mod = modid; + const formatTable = ClientMods[modid].formats[formatid]; + if (mod && formatTable.teambuilderFormat) overrideFormat = toID(formatTable.teambuilderFormat); + if (mod && formatTable.formatType) modFormatType = toID(formatTable.formatType); + break; + } + } + } + if (mod) { + this.dex = Dex.mod(mod as ID); + this.dex.gen = gen; + this.mod = mod; + } else { + this.dex = Dex.forGen(gen); + } + if (overrideFormat) format = overrideFormat as ID; + else format = (format.slice(4) || 'customgame') as ID; + if (modFormatType) this.formatType = modFormatType as 'doubles' | 'letsgo' | 'metronome' | 'natdex' | 'nfe' | 'dlc1' | 'dlc1doubles' | null; } else if (!format) { this.dex = Dex; } @@ -820,10 +903,28 @@ abstract class BattleTypedSearch<T extends SearchType> { if (this.formatType === 'bw1') table = table['gen5bw1']; let learnset = table.learnsets[learnsetid]; const eggMovesOnly = this.eggMovesOnly(learnsetid, speciesid); - if (learnset && (moveid in learnset) && (!this.format.startsWith('tradebacks') ? learnset[moveid].includes(genChar) : - learnset[moveid].includes(genChar) || (learnset[moveid].includes(`${gen + 1}`) && move.gen === gen)) && + if (this.mod) { + const overrideLearnsets = BattleTeambuilderTable[this.mod].overrideLearnsets; + if (overrideLearnsets[learnsetid]) { + if (!learnset) learnset = overrideLearnsets[learnsetid]; //Didn't have learnset and mod gave it one + learnset = JSON.parse(JSON.stringify(learnset)); + for (const learnedMove in overrideLearnsets[learnsetid]) learnset[learnedMove] = overrideLearnsets[learnsetid][learnedMove]; + } + } + try { + if (!Object.keys(learnset).length) { //Doesn't have learnset but one is loaded; some other mod gave it one + learnsetid = toID(this.dex.species.get(learnsetid).baseSpecies); + } + } catch (e) { + console.log("Error: Unable to load learnset data for " + learnsetid + " in " + this.mod); + } + + // Modified this function to account for pet mods with tradebacks enabled + const tradebacksMod = ['gen1expansionpack', 'gen1burgundy']; + if (learnset && (moveid in learnset) && (!(this.format.startsWith('tradebacks') || tradebacksMod.includes(this.mod)) ? learnset[moveid].includes(genChar) : + (learnset[moveid].includes(genChar) || (learnset[moveid].includes(`${gen + 1}`) && move.gen === gen)) && (!eggMovesOnly || (learnset[moveid].includes('e') && this.dex.gen === 9)) - ) { + ) {= return true; } learnsetid = this.nextLearnsetid(learnsetid, speciesid, true); @@ -834,7 +935,9 @@ abstract class BattleTypedSearch<T extends SearchType> { if (this.formatType === 'metronome') { return pokemon.num >= 0 ? String(pokemon.num) : pokemon.tier; } + const modFormatTable = this.mod ? window.ModConfig[this.mod].formats[this.modFormat] : {}; let table = window.BattleTeambuilderTable; + if (this.mod) table = modFormatTable.gameType !== 'doubles' ? BattleTeambuilderTable[this.mod] : BattleTeambuilderTable[this.mod].doubles; const gen = this.dex.gen; const tableKey = this.formatType === 'doubles' ? `gen${gen}doubles` : this.formatType === 'letsgo' ? 'gen7letsgo' : @@ -892,7 +995,8 @@ abstract class BattleTypedSearch<T extends SearchType> { class BattlePokemonSearch extends BattleTypedSearch<'pokemon'> { sortRow: SearchRow = ['sortpokemon', '']; getTable() { - return BattlePokedex; + if (!this.mod) return BattlePokedex; + else return {...BattleTeambuilderTable[this.mod].overrideDexInfo, ...BattlePokedex}; } getDefaultResults(): SearchRow[] { let results: SearchRow[] = []; @@ -946,9 +1050,11 @@ class BattlePokemonSearch extends BattleTypedSearch<'pokemon'> { const isHackmons = format.includes('hackmons') || format.endsWith('bh'); let isDoublesOrBS = isVGCOrBS || this.formatType?.includes('doubles'); const dex = this.dex; - + const modFormatTable = this.mod ? window.ModConfig[this.mod].formats[this.modFormat] : {}; let table = BattleTeambuilderTable; - if ((format.endsWith('cap') || format.endsWith('caplc')) && dex.gen < 9) { + if (this.mod) { + table = modFormatTable.gameType !== 'doubles' ? BattleTeambuilderTable[this.mod] : BattleTeambuilderTable[this.mod].doubles; + } else if ((format.endsWith('cap') || format.endsWith('caplc')) && dex.gen < 9) { table = table['gen' + dex.gen]; } else if (isVGCOrBS) { table = table['gen' + dex.gen + 'vgc']; @@ -1103,11 +1209,45 @@ class BattlePokemonSearch extends BattleTypedSearch<'pokemon'> { }); } } + if (this.mod && !table.customTierSet) { + table.customTierSet = table.customTiers.map((r: any) => { + if (typeof r === 'string') return ['pokemon', r]; + return [r[0], r[1]]; + }); + table.customTiers = null; + } + let customTierSet: SearchRow[] = table.customTierSet; + if (customTierSet) { + tierSet = customTierSet.concat(tierSet); + if (modFormatTable.bans.length > 0 && !modFormatTable.bans.includes("All Pokemon")) { + tierSet = tierSet.filter(([type, id]) => { + let banned = modFormatTable.bans; + return !(banned.includes(id)); + }); + } else if (modFormatTable.unbans.length > 0 && modFormatTable.bans.includes("All Pokemon")) { + tierSet = tierSet.filter(([type, id]) => { + let unbanned = modFormatTable.unbans; + return (unbanned.includes(id) || type === 'header'); + }); + } + let headerCount = 0; + let lastHeader = ''; + const emptyHeaders: string[] = []; + for (const i in tierSet) { + headerCount = tierSet[i][0] === 'header' ? headerCount + 1 : 0; + if (headerCount > 1) emptyHeaders.push(lastHeader); + if (headerCount > 0) lastHeader = tierSet[i][1]; + } + if (headerCount === 1) emptyHeaders.push(lastHeader); + tierSet = tierSet.filter(([type, id]) => { + return (type !== 'header' || !emptyHeaders.includes(id)); + }); + } if (format === 'zu' && dex.gen === 5 && table.gen5zuBans) { tierSet = tierSet.filter(([type, id]) => { if (id in table.gen5zuBans) return false; return true; - }); + } } // Filter out Gmax Pokemon from standard tier selection @@ -1147,14 +1287,27 @@ class BattlePokemonSearch extends BattleTypedSearch<'pokemon'> { } sort(results: SearchRow[], sortCol: string, reverseSort?: boolean) { const sortOrder = reverseSort ? -1 : 1; + const table = !this.mod ? '' : BattleTeambuilderTable[this.mod].overrideDexInfo; if (['hp', 'atk', 'def', 'spa', 'spd', 'spe'].includes(sortCol)) { return results.sort(([rowType1, id1], [rowType2, id2]) => { + let pokedex1 = BattlePokedex; + let pokedex2 = BattlePokedex; + if (this.mod) { + if (table[id1] && table[id1].baseStats) pokedex1 = table; + if (table[id2] && table[id2].baseStats) pokedex2 = table; + } const stat1 = this.dex.species.get(id1).baseStats[sortCol as StatName]; const stat2 = this.dex.species.get(id2).baseStats[sortCol as StatName]; return (stat2 - stat1) * sortOrder; }); } else if (sortCol === 'bst') { return results.sort(([rowType1, id1], [rowType2, id2]) => { + let pokedex1 = BattlePokedex; + let pokedex2 = BattlePokedex; + if (this.mod) { + if (table[id1] && table[id1].baseStats) pokedex1 = table; + if (table[id2] && table[id2].baseStats) pokedex2 = table; + } const base1 = this.dex.species.get(id1).baseStats; const base2 = this.dex.species.get(id2).baseStats; let bst1 = base1.hp + base1.atk + base1.def + base1.spa + base1.spd + base1.spe; @@ -1178,8 +1331,9 @@ class BattlePokemonSearch extends BattleTypedSearch<'pokemon'> { class BattleAbilitySearch extends BattleTypedSearch<'ability'> { getTable() { - return BattleAbilities; - } + if (!this.mod) return BattleAbilities; + else return {...BattleTeambuilderTable[this.mod].fullAbilityName, ...BattleAbilities}; + } getDefaultResults(): SearchRow[] { const results: SearchRow[] = []; for (let id in BattleAbilities) { @@ -1265,11 +1419,14 @@ class BattleAbilitySearch extends BattleTypedSearch<'ability'> { class BattleItemSearch extends BattleTypedSearch<'item'> { getTable() { - return BattleItems; + if (!this.mod) return BattleItems; + else return {...BattleTeambuilderTable[this.mod].overrideItemData, ...BattleItems}; } getDefaultResults(): SearchRow[] { let table = BattleTeambuilderTable; - if (this.formatType?.startsWith('bdsp')) { + if (this.mod) { + table = table[this.mod]; + } else if (this.formatType?.startsWith('bdsp')) { table = table['gen8bdsp']; } else if (this.formatType === 'bw1') { table = table['gen5bw1']; @@ -1292,19 +1449,30 @@ class BattleItemSearch extends BattleTypedSearch<'item'> { return table.itemSet; } getBaseResults(): SearchRow[] { - if (!this.species) return this.getDefaultResults(); - const speciesName = this.dex.species.get(this.species).name; const results = this.getDefaultResults(); + const species = this.dex.species.get(this.species); const speciesSpecific: SearchRow[] = []; - for (const row of results) { + for (let i = results.length - 1; i > 0; i--) { + const row = results[i]; if (row[0] !== 'item') continue; - if (this.dex.items.get(row[1]).itemUser?.includes(speciesName)) { + const id = row[1]; + let item = this.dex.items.get(id); + if (!item.exists || item.isNonstandard) { + if (item.isNonstandard !== "Past" || this.formatType !== 'natdex') { + results.splice(i, 1); + continue; + } + } + if (item.itemUser?.includes(species.name)) { + speciesSpecific.push(row); + } + if(id === 'boosterenergy' && species.tags?.includes('Paradox')) { speciesSpecific.push(row); } } if (speciesSpecific.length) { return [ - ['header', "Specific to " + speciesName], + ['header', "Specific to " + species.name], ...speciesSpecific, ...results, ]; @@ -1332,12 +1500,13 @@ class BattleItemSearch extends BattleTypedSearch<'item'> { class BattleMoveSearch extends BattleTypedSearch<'move'> { sortRow: SearchRow = ['sortmove', '']; getTable() { - return BattleMovedex; + if (!this.mod) return BattleMovedex; + else return {...BattleTeambuilderTable[this.mod].overrideMoveInfo, ...BattleMovedex}; } getDefaultResults(): SearchRow[] { let results: SearchRow[] = []; results.push(['header', "Moves"]); - for (let id in BattleMovedex) { + for (let id in this.getTable()) { switch (id) { case 'paleowave': results.push(['header', "CAP moves"]); @@ -1354,6 +1523,12 @@ class BattleMoveSearch extends BattleTypedSearch<'move'> { let abilityid: ID = set ? toID(set.ability) : '' as ID; const itemid: ID = set ? toID(set.item) : '' as ID; + + // Check if mod declared forced viability + if (this.mod && id in BattleTeambuilderTable[this.mod].overrideMoveInfo) { + if(BattleTeambuilderTable[this.mod].overrideMoveInfo[id].viable === true) return true; + if(BattleTeambuilderTable[this.mod].overrideMoveInfo[id].viable === false) return false; + } if (dex.gen === 1) { // Usually not useless for Gen 1 @@ -1405,7 +1580,9 @@ class BattleMoveSearch extends BattleTypedSearch<'move'> { if (itemid === 'pidgeotite') abilityid = 'noguard' as ID; if (itemid === 'blastoisinite') abilityid = 'megalauncher' as ID; - if (itemid === 'aerodactylite') abilityid = 'toughclaws' as ID; + if (itemid === 'heracronite') abilityid = 'skilllink' as ID; + if (itemid === 'cameruptite') abilityid = 'sheerforce' as ID; + if (itemid === 'aerodactylite' || itemid === 'charizardmegax') abilityid = 'toughclaws' as ID; if (itemid === 'glalitite') abilityid = 'refrigerate' as ID; switch (id) { @@ -1455,17 +1632,15 @@ class BattleMoveSearch extends BattleTypedSearch<'move'> { case 'hex': return !moves.includes('infernalparade'); case 'hiddenpowerelectric': - return (dex.gen < 4 && !moves.includes('thunderpunch')) && !moves.includes('thunderbolt'); + return !(moves.includes('thunderbolt') || (dex.gen < 4 && moves.includes('thunderpunch'))); case 'hiddenpowerfighting': - return (dex.gen < 4 && !moves.includes('brickbreak')) && !moves.includes('aurasphere') && !moves.includes('focusblast'); + return !(moves.includes('aurasphere') || moves.includes('focusblast') || (dex.gen < 4 && moves.includes('brickbreak'))); case 'hiddenpowerfire': - return (dex.gen < 4 && !moves.includes('firepunch')) && !moves.includes('flamethrower') && - !moves.includes('mysticalfire') && !moves.includes('burningjealousy'); + return !(moves.includes('flamethrower') || moves.includes('mysticalfire') || (dex.gen < 4 && moves.includes('firepunch'))); case 'hiddenpowergrass': - return !moves.includes('energyball') && !moves.includes('grassknot') && !moves.includes('gigadrain'); + return !(moves.includes('energyball') || moves.includes('grassknot') || moves.includes('gigadrain')); case 'hiddenpowerice': - return !moves.includes('icebeam') && (dex.gen < 4 && !moves.includes('icepunch')) || - (dex.gen > 5 && !moves.includes('aurorabeam') && !moves.includes('glaciate')); + return !(moves.includes('icebeam') || (dex.gen > 5 && (moves.includes('aurorabeam') || moves.includes('glaciate'))) || (dex.gen < 4 && moves.includes('icepunch'))); case 'hiddenpowerflying': return dex.gen < 4 && !moves.includes('drillpeck'); case 'hiddenpowerbug': @@ -1508,7 +1683,7 @@ class BattleMoveSearch extends BattleTypedSearch<'move'> { case 'petaldance': return abilityid === 'owntempo'; case 'phantomforce': - return (!moves.includes('poltergeist') && !moves.includes('shadowclaw')) || this.formatType === 'doubles'; + return !(moves.includes('shadowforce') || moves.includes('poltergeist') || moves.includes('shadowclaw')) || this.formatType === 'doubles'; case 'poisonfang': return species.types.includes('Poison') && !moves.includes('gunkshot') && !moves.includes('poisonjab'); case 'relicsong': @@ -1562,6 +1737,7 @@ class BattleMoveSearch extends BattleTypedSearch<'move'> { const move = dex.moves.get(id); if (!move.exists) return true; + if (!BattleMovedex[id].exists) return true; //Flag custom moves as viable by default if ((move.status === 'slp' || id === 'yawn') && dex.gen === 9 && !this.formatType) { return false; } @@ -1582,6 +1758,9 @@ class BattleMoveSearch extends BattleTypedSearch<'move'> { if (move.flags['slicing'] && abilityid === 'sharpness') { return true; } + if (move.basePower < 75 && !(abilityid === 'technician' && move.basePower <= 60 && move.basePower >= 50)) { + return BattleMoveSearch.GOOD_WEAK_MOVES.includes(id); + } return !BattleMoveSearch.BAD_STRONG_MOVES.includes(id); } static readonly GOOD_STATUS_MOVES = [ @@ -1602,11 +1781,24 @@ class BattleMoveSearch extends BattleTypedSearch<'move'> { let species = dex.species.get(this.species); const format = this.format; const isHackmons = (format.includes('hackmons') || format.endsWith('bh')); - const isSTABmons = (format.includes('stabmons') || format === 'staaabmons'); - const isTradebacks = format.includes('tradebacks'); + const isSTABmons = (format.includes('stabmons') || format.includes('stylemons')|| format === 'staaabmons'); + const isTradebacks = (format.includes('tradebacks') || this.mod === 'gen1expansionpack' || this.mod === 'gen1burgundy'); const regionBornLegality = dex.gen >= 6 && (/^battle(spot|stadium|festival)/.test(format) || format.startsWith('bss') || format.startsWith('vgc') || (dex.gen === 9 && this.formatType !== 'natdex')); + + let hasOwnUsefulCheck = false; + switch(typeof window.ModConfig[this.mod]?.moveIsNotUseless){ + case 'string': + hasOwnUsefulCheck = true; + const usefulCheck = JSON.parse(window.ModConfig[this.mod].moveIsNotUseless); + const checkParameters = usefulCheck.substring(usefulCheck.indexOf('(')+1,usefulCheck.indexOf(')')).split(','); + window.ModConfig[this.mod].moveIsNotUseless = new Function(...checkParameters, usefulCheck.substring(usefulCheck.indexOf('{'))); + break; + case 'function': + hasOwnUsefulCheck = true; + break; + } let learnsetid = this.firstLearnsetid(species.id); let moves: string[] = []; @@ -1622,7 +1814,19 @@ class BattleMoveSearch extends BattleTypedSearch<'move'> { if (this.formatType?.startsWith('svdlc1')) lsetTable = lsetTable['gen9dlc1']; while (learnsetid) { let learnset = lsetTable.learnsets[learnsetid]; + if (this.mod) { + const overrideLearnsets = BattleTeambuilderTable[this.mod].overrideLearnsets; + if (overrideLearnsets[learnsetid]) { + if(!learnset) learnset = overrideLearnsets[learnsetid]; //Didn't have learnset and mod gave it one + learnset = JSON.parse(JSON.stringify(learnset)); + for (const moveid in overrideLearnsets[learnsetid]) learnset[moveid] = overrideLearnsets[learnsetid][moveid]; + } + } if (learnset) { + if (!Object.keys(learnset).length) { //Doesn't have learnset but one is loaded; some other mod gave it one + learnsetid = toID(this.dex.species.get(learnsetid).baseSpecies); + continue; + } for (let moveid in learnset) { let learnsetEntry = learnset[moveid]; const move = dex.moves.get(moveid); @@ -1677,22 +1881,22 @@ class BattleMoveSearch extends BattleTypedSearch<'move'> { } if (sketch || isHackmons) { if (isHackmons) moves = []; - for (let id in BattleMovedex) { + for (let id in this.getTable()) { if (!format.startsWith('cap') && (id === 'paleowave' || id === 'shadowstrike')) continue; - const move = dex.moves.get(id); - if (move.gen > dex.gen) continue; + let move = dex.moves.get(id); + if (!move.exists || moves.includes(id) || move.gen > dex.gen) continue; if (sketch) { if (move.flags['nosketch'] || move.isMax || move.isZ) continue; if (move.isNonstandard && move.isNonstandard !== 'Past') continue; if (move.isNonstandard === 'Past' && this.formatType !== 'natdex') continue; - sketchMoves.push(move.id); + sketchMoves.push(id); } else { if (!(dex.gen < 8 || this.formatType === 'natdex') && move.isZ) continue; if (typeof move.isMax === 'string') continue; if (move.isMax && dex.gen > 8) continue; if (move.isNonstandard === 'Past' && this.formatType !== 'natdex') continue; if (move.isNonstandard === 'LGPE' && this.formatType !== 'letsgo') continue; - moves.push(move.id); + moves.push(id); } } } @@ -1750,7 +1954,11 @@ class BattleMoveSearch extends BattleTypedSearch<'move'> { let usableMoves: SearchRow[] = []; let uselessMoves: SearchRow[] = []; for (const id of moves) { - const isUsable = this.moveIsNotUseless(id as ID, species, moves, this.set); + let isUsable = this.moveIsNotUseless(id as ID, species, moves, this.set); + if (hasOwnUsefulCheck) { + const modIsUsable = window.ModConfig[this.mod].moveIsNotUseless.apply(window.ModConfig[this.mod], [id as ID, species, moves, this.set, this.dex]); + if (typeof modIsUsable === 'boolean' && modIsUsable !== isUsable) isUsable = modIsUsable; + } if (isUsable) { if (!usableMoves.length) usableMoves.push(['header', "Moves"]); usableMoves.push(['move', id as ID]); @@ -1764,7 +1972,11 @@ class BattleMoveSearch extends BattleTypedSearch<'move'> { uselessMoves.push(['header', "Useless sketched moves"]); } for (const id of sketchMoves) { - const isUsable = this.moveIsNotUseless(id as ID, species, sketchMoves, this.set); + let isUsable = this.moveIsNotUseless(id as ID, species, sketchMoves, this.set); + if (hasOwnUsefulCheck) { + const modIsUsable = window.ModConfig[this.mod].moveIsNotUseless.apply(window.ModConfig[this.mod], [id as ID, species, moves, this.set, this.dex]); + if (typeof modIsUsable === 'boolean' && modIsUsable !== isUsable) isUsable = modIsUsable; + } if (isUsable) { usableMoves.push(['move', id as ID]); } else { @@ -1860,7 +2072,8 @@ class BattleCategorySearch extends BattleTypedSearch<'category'> { class BattleTypeSearch extends BattleTypedSearch<'type'> { getTable() { - return window.BattleTypeChart; + if (!this.mod) return window.BattleTypeChart; + else return {...BattleTeambuilderTable[this.mod].overrideTypeChart, ...window.BattleTypeChart}; } getDefaultResults(): SearchRow[] { const results: SearchRow[] = []; diff --git a/play.pokemonshowdown.com/src/battle-dex.ts b/play.pokemonshowdown.com/src/battle-dex.ts index e45aacc0f..2c11241dc 100644 --- a/play.pokemonshowdown.com/src/battle-dex.ts +++ b/play.pokemonshowdown.com/src/battle-dex.ts @@ -179,15 +179,19 @@ const Dex = new class implements ModdedDex { pokeballs: string[] | null = null; + //TODO we might want to move this to something like data/petmods + readonly modResourcePrefix = 'https://raw.githubusercontent.com/scoopapa/dh2/master/data/mods/'; + + resourcePrefix = (() => { let prefix = ''; if (window.document?.location?.protocol !== 'http:') prefix = 'https:'; - return `${prefix}//${window.Config ? Config.routes.client : 'play.pokemonshowdown.com'}/`; + return `${prefix}//${'play.pokemonshowdown.com'}/`; })(); fxPrefix = (() => { const protocol = (window.document?.location?.protocol !== 'http:') ? 'https:' : ''; - return `${protocol}//${window.Config ? Config.routes.client : 'play.pokemonshowdown.com'}/fx/`; + return `${protocol}//${'play.pokemonshowdown.com'}/fx/`; })(); loadedSpriteData = {xy: 1, bw: 0}; @@ -302,8 +306,8 @@ const Dex = new class implements ModdedDex { basePower: Number(id.slice(11)), }; } - if (!data) data = {exists: false}; + let move = new Move(id, name, data); window.BattleMovedex[id] = move; return move; @@ -315,6 +319,16 @@ const Dex = new class implements ModdedDex { 'Fire', 'Water', 'Grass', 'Electric', 'Ice', 'Psychic', 'Dark', 'Dragon', ].includes(type) ? 'Special' : 'Physical'; } + getKEPCategory(type: string) { + return [ + 'Fire', 'Water', 'Grass', 'Electric', 'Ice', 'Psychic', 'Dark', 'Dragon', 'Fairy' + ].includes(type) ? 'Special' : 'Physical'; + } + getCSICategory(type: string) { + return [ + 'Fire', 'Water', 'Grass', 'Electric', 'Ice', 'Psychic', 'Dark', 'Dragon', 'Cosmic' + ].includes(type) ? 'Special' : 'Physical'; + } items = { get: (nameOrItem: string | Item | null | undefined): Item => { @@ -353,7 +367,6 @@ const Dex = new class implements ModdedDex { if (!window.BattleAbilities) window.BattleAbilities = {}; let data = window.BattleAbilities[id]; if (data && typeof data.exists === 'boolean') return data; - if (!data) data = {exists: false}; let ability = new Ability(id, name, data); window.BattleAbilities[id] = ability; return ability; @@ -361,7 +374,7 @@ const Dex = new class implements ModdedDex { }; species = { - get: (nameOrSpecies: string | Species | null | undefined): Species => { + get: (nameOrSpecies: string | Species | null | undefined, modded = false, debug = ""): Species => { if (nameOrSpecies && typeof nameOrSpecies !== 'string') { // TODO: don't accept Species' here return nameOrSpecies; @@ -384,7 +397,6 @@ const Dex = new class implements ModdedDex { } if (!window.BattlePokedex) window.BattlePokedex = {}; let data = window.BattlePokedex[id]; - let species: Species; if (data && typeof data.exists === 'boolean') { species = data; @@ -460,6 +472,22 @@ const Dex = new class implements ModdedDex { } return false; } + // getSpriteMod is used to find the correct mod folder for the sprite url to use + // id is the name of the pokemon, type, or item. folder refers to "front", or "back-shiny" etc. overrideStandard is false for custom elements and true for canon elements + getSpriteMod(optionsMod: string, spriteId: string, filepath: string, overrideStandard: boolean = false) { + if (!window.ModSprites[spriteId]) return ''; + if ((!optionsMod || !window.ModSprites[spriteId][optionsMod]) && !overrideStandard) { // for custom elements only, it will use sprites from another mod if the mod provided doesn't have one + for (const modName in window.ModSprites[spriteId]) { + if (window.ModSprites[spriteId][modName].includes(filepath)) return modName; + if (window.ModSprites[spriteId][modName].includes('ani' + filepath)) return modName; + } + } + if (optionsMod && window.ModSprites[spriteId][optionsMod]) { + if (window.ModSprites[spriteId][optionsMod].includes('ani' + filepath)) return optionsMod; + if (window.ModSprites[spriteId][optionsMod].includes(filepath)) return optionsMod; + } + return ''; // must be a real Pokemon or not have custom sprite data + } loadSpriteData(gen: 'xy' | 'bw') { if (this.loadedSpriteData[gen]) return; @@ -473,16 +501,18 @@ const Dex = new class implements ModdedDex { el.src = path + 'data/pokedex-mini-bw.js' + qs; document.getElementsByTagName('body')[0].appendChild(el); } + getSpriteData(pokemon: Pokemon | Species | string, isFront: boolean, options: { gen?: number, shiny?: boolean, gender?: GenderName, afd?: boolean, noScale?: boolean, - mod?: string, + mod: string, dynamax?: boolean, - } = {gen: 6}) { - const mechanicsGen = options.gen || 6; + } = {gen: 6, mod: ''}) { + let mechanicsGen = options.gen || 6; + if (options.mod && window.ModConfig[options.mod].spriteGen) mechanicsGen = window.ModConfig[options.mod].spriteGen; let isDynamax = !!options.dynamax; if (pokemon instanceof Pokemon) { if (pokemon.volatiles.transform) { @@ -502,6 +532,19 @@ const Dex = new class implements ModdedDex { } pokemon = pokemon.getSpeciesForme() + (isGigantamax ? '-Gmax' : ''); } + const modSpecies = Dex.species.get(pokemon); + let resourcePrefix = Dex.resourcePrefix; + let spriteDir = 'sprites/'; + let hasCustomSprite = false; + let modSpriteId = toID(modSpecies.spriteid); + options.mod = this.getSpriteMod(options.mod, modSpriteId, isFront ? 'front' : 'back', modSpecies.exists); + if (options.mod) { + resourcePrefix = Dex.modResourcePrefix; + spriteDir = `${options.mod}/sprites/`; + hasCustomSprite = true; + if (this.getSpriteMod(options.mod, modSpriteId, (isFront ? 'front' : 'back') + '-shiny', modSpecies.exists) === '') options.shiny = false; + } + const species = Dex.species.get(pokemon); // Gmax sprites are already extremely large, so we don't need to double. if (species.name.endsWith('-Gmax')) isDynamax = false; @@ -510,7 +553,7 @@ const Dex = new class implements ModdedDex { w: 96, h: 96, y: 0, - url: Dex.resourcePrefix + 'sprites/', + url: resourcePrefix + spriteDir, pixelated: true, isFrontSprite: false, cryurl: '', @@ -527,7 +570,7 @@ const Dex = new class implements ModdedDex { dir = '-back'; facing = 'back'; } - + if (hasCustomSprite) dir = isFront ? 'front' : 'back'; // Decide which gen sprites to use. // // There are several different generations we care about here: @@ -621,13 +664,29 @@ const Dex = new class implements ModdedDex { } // Mod Cries - if (options.mod) { - spriteData.cryurl = `sprites/${options.mod}/audio/${toID(species.baseSpecies)}`; - spriteData.cryurl += '.mp3'; + if (options.mod === 'digimon') { + spriteData.cryurl = `sprites/${options.mod}/audio/${toID(species.baseSpecies)}.mp3`; + } + //If we already have a cry url we load from the main server, otherwise we try to search for the presence of a custom cry + if (!(spriteData.cryurl &&= 'https://' + Config.routes.psmain + '/' + spriteData.cryurl)) { + //For whatever reason if there is a cry but no true sprite data then options.mod becomes '' regardless of mod + //TODO: Possibly fix that? I wouldn't prioritize it though + if (window.ModSprites[modSpriteId]?.[options.mod]?.includes('cries')) { + spriteData.cryurl = resourcePrefix + options.mod + '/audio/cries/' + speciesid + '.mp3'; + } else { //We couldn't find a cry + spriteData.cryurl = ''; + } + } + + let hasCustomAnim = false; + if (hasCustomSprite && window.ModSprites[modSpriteId][options.mod].includes('ani' + facing)){ + hasCustomAnim = true; + animationData[facing] = {}; + animationData[facing].w = 192; + animationData[facing].h = 192; } - if (animationData[facing + 'f'] && options.gender === 'F') facing += 'f'; - let allowAnim = !Dex.prefs('noanim') && !Dex.prefs('nogif'); + let allowAnim = (!hasCustomSprite || (hasCustomSprite && hasCustomAnim)) && !Dex.prefs('noanim') && !Dex.prefs('nogif'); if (allowAnim && spriteData.gen >= 6) spriteData.pixelated = false; if (allowAnim && animationData[facing] && spriteData.gen >= 5) { if (facing.slice(-1) === 'f') name += '-f'; @@ -636,15 +695,23 @@ const Dex = new class implements ModdedDex { spriteData.w = animationData[facing].w; spriteData.h = animationData[facing].h; spriteData.url += dir + '/' + name + '.gif'; + console.log(animationData[facing]); } else { // There is no entry or enough data in pokedex-mini.js // Handle these in case-by-case basis; either using BW sprites or matching the played gen. - dir = (baseDir || 'gen5') + dir; + if (!hasCustomSprite) dir = (baseDir || 'gen5') + dir; // Gender differences don't exist prior to Gen 4, // so there are no sprites for it - if (spriteData.gen >= 4 && miscData['frontf'] && options.gender === 'F') { - name += '-f'; + if (spriteData.gen >= 4 && options.gender === 'F') { + //Is it a realmon with a gender difference? + if (miscData['frontf']) { + name += '-f'; + } + //If it's a custom sprite, does it have separate sprites for male and female? + else if (window.ModSprites[modSpriteId] && window.ModSprites[modSpriteId + 'f']) { + name += 'f'; + } } spriteData.url += dir + '/' + name + '.png'; @@ -674,7 +741,14 @@ const Dex = new class implements ModdedDex { spriteData.h *= 1.5; spriteData.y += -11; } - + if (window.BattlePokemonSprites) { + if (!window.ModSprites[modSpriteId] && !window.BattlePokemonSprites[modSpriteId] && pokemon !== 'substitute') { + spriteData = Dex.getSpriteData('substitute', spriteData.isFrontSprite, { + gen: options.gen, + mod: options.mod, + }); + } + } return spriteData; } @@ -705,7 +779,7 @@ const Dex = new class implements ModdedDex { return num; } - getPokemonIcon(pokemon: string | Pokemon | ServerPokemon | PokemonSet | null, facingLeft?: boolean) { + getPokemonIcon(pokemon: string | Pokemon | ServerPokemon | PokemonSet | null, facingLeft?: boolean, mod: string = '') { if (pokemon === 'pokeball') { return `background:transparent url(${Dex.resourcePrefix}sprites/pokemonicons-pokeball-sheet.png) no-repeat scroll -0px 4px`; } else if (pokemon === 'pokeball-statused') { @@ -732,16 +806,32 @@ const Dex = new class implements ModdedDex { let top = Math.floor(num / 12) * 30; let left = (num % 12) * 40; let fainted = ((pokemon as Pokemon | ServerPokemon)?.fainted ? `;opacity:.3;filter:grayscale(100%) brightness(.5)` : ``); + Dex.species.get(id); + let species = window.BattlePokedexAltForms && window.BattlePokedexAltForms[id] ? window.BattlePokedexAltForms[id] : Dex.species.get(id); + mod = this.getSpriteMod(mod, id, 'icons', species.exists !== false); + if (mod) return `background:transparent url(${this.modResourcePrefix}${mod}/sprites/icons/${id}.png) no-repeat scroll -0px -0px${fainted}`; return `background:transparent url(${Dex.resourcePrefix}sprites/pokemonicons-sheet.png?v16) no-repeat scroll -${left}px -${top}px${fainted}`; + } - getTeambuilderSpriteData(pokemon: any, gen: number = 0): TeambuilderSpriteData { + getTeambuilderSpriteData(pokemon: any, gen: number = 0, mod: string = ''): TeambuilderSpriteData { let id = toID(pokemon.species); let spriteid = pokemon.spriteid; - let species = Dex.species.get(pokemon.species); + let species = window.BattlePokedexAltForms && window.BattlePokedexAltForms[id] ? window.BattlePokedexAltForms[id] : Dex.species.get(pokemon.species);; if (pokemon.species && !spriteid) { spriteid = species.spriteid || toID(pokemon.species); } + if (mod && window.ModConfig[mod].spriteGen) gen = window.ModConfig[mod].spriteGen; + mod = this.getSpriteMod(mod, id, 'front', species.exists !== false); + if (mod) { + return { + spriteDir: `${mod}/sprites/front`, + spriteid, + shiny: (this.getSpriteMod(mod, id, 'front-shiny', species.exists !== false) !== null && pokemon.shiny), + x: 10, + y: 5, + }; + } if (species.exists === false) return { spriteDir: 'sprites/gen5', spriteid: '0', x: 10, y: 5 }; if (window.Config?.server?.afd || Dex.prefs('afd')) { return { @@ -791,16 +881,20 @@ const Dex = new class implements ModdedDex { return spriteData; } - getTeambuilderSprite(pokemon: any, gen: number = 0) { + getTeambuilderSprite(pokemon: any, gen: number = 0, mod: string = '') { if (!pokemon) return ''; - const data = this.getTeambuilderSpriteData(pokemon, gen); + const data = this.getTeambuilderSpriteData(pokemon, gen, mod); const shiny = (data.shiny ? '-shiny' : ''); - return 'background-image:url(' + Dex.resourcePrefix + data.spriteDir + shiny + '/' + data.spriteid + '.png);background-position:' + data.x + 'px ' + data.y + 'px;background-repeat:no-repeat'; + let resourcePrefix = Dex.resourcePrefix; + if (data.spriteDir.includes('front')) resourcePrefix = Dex.modResourcePrefix; + return 'background-image:url(' + resourcePrefix + data.spriteDir + shiny + '/' + data.spriteid + '.png);background-position:' + data.x + 'px ' + data.y + 'px;background-repeat:no-repeat'; } - getItemIcon(item: any) { + getItemIcon(item: any, mod: string = '') { let num = 0; if (typeof item === 'string' && exports.BattleItems) item = exports.BattleItems[toID(item)]; + mod = this.getSpriteMod(mod, item.id, 'items'); + if (mod) return `background:transparent url(${this.modResourcePrefix}${mod}/sprites/items/${item.id}.png) no-repeat`; if (item?.spritenum) num = item.spritenum; let top = Math.floor(num / 16) * 24; @@ -808,11 +902,16 @@ const Dex = new class implements ModdedDex { return 'background:transparent url(' + Dex.resourcePrefix + 'sprites/itemicons-sheet.png?v1) no-repeat scroll -' + left + 'px -' + top + 'px'; } - getTypeIcon(type: string | null, b?: boolean) { // b is just for utilichart.js + getTypeIcon(type: string | null, b?: boolean, mod: string = '') { // b is just for utilichart.js type = this.types.get(type).name; if (!type) type = '???'; let sanitizedType = type.replace(/\?/g, '%3f'); - return `<img src="${Dex.resourcePrefix}sprites/types/${sanitizedType}.png" alt="${type}" height="14" width="32" class="pixelated${b ? ' b' : ''}" />`; + mod = this.getSpriteMod(mod, toID(type), 'types'); + if (mod && (type !== '???')) { + return `<img src="${this.modResourcePrefix}${mod}/sprites/types/${toID(type)}.png" alt="${type}" class="pixelated${b ? ' b' : ''}" />`; + } else { + return `<img src="${Dex.resourcePrefix}sprites/types/${sanitizedType}.png" alt="${type}" height="14" width="32" class="pixelated${b ? ' b' : ''}" />`; + } } getCategoryIcon(category: string | null) { @@ -844,7 +943,7 @@ const Dex = new class implements ModdedDex { }; class ModdedDex { - readonly gen: number; + gen: number; readonly modid: ID; readonly cache = { Moves: {} as any as {[k: string]: Move}, @@ -856,9 +955,9 @@ class ModdedDex { pokeballs: string[] | null = null; constructor(modid: ID) { this.modid = modid; - const gen = parseInt(modid.substr(3, 1), 10); - if (!modid.startsWith('gen') || !gen) throw new Error("Unsupported modid"); - this.gen = gen; + const gen = parseInt(modid.slice(3), 10); + if (!modid.startsWith('gen') || !gen) this.gen = 9; + else this.gen = gen; } moves = { get: (name: string): Move => { @@ -867,26 +966,30 @@ class ModdedDex { name = BattleAliases[id]; id = toID(name); } - if (this.cache.Moves.hasOwnProperty(id)) return this.cache.Moves[id]; + // if (this.cache.Moves.hasOwnProperty(id)) return this.cache.Moves[id]; let data = {...Dex.moves.get(name)}; - for (let i = Dex.gen - 1; i >= this.gen; i--) { - const table = window.BattleTeambuilderTable[`gen${i}`]; - if (id in table.overrideMoveData) { - Object.assign(data, table.overrideMoveData[id]); - } - } - if (this.modid !== `gen${this.gen}`) { - const table = window.BattleTeambuilderTable[this.modid]; - if (id in table.overrideMoveData) { - Object.assign(data, table.overrideMoveData[id]); + const table = window.BattleTeambuilderTable[this.modid]; + if (table.overrideMoveInfo[id]) { + for (const key in table.overrideMoveInfo[id]) { + data = {...Dex.moves.get(name), ...table.overrideMoveInfo[id]}; } } if (this.gen <= 3 && data.category !== 'Status') { - data.category = Dex.getGen3Category(data.type); + switch(this.modid) { + case 'gen1expansionpack': + case 'gen2expansionpack': + data.category = Dex.getKEPCategory(data.type); + break; + case 'gen2crystalseviiislands': + data.category = Dex.getCSICategory(data.type); + break; + default: + data.category = Dex.getGen3Category(data.type); + break; + } } - const move = new Move(id, name, data); this.cache.Moves[id] = move; return move; @@ -903,18 +1006,19 @@ class ModdedDex { if (this.cache.Items.hasOwnProperty(id)) return this.cache.Items[id]; let data = {...Dex.items.get(name)}; - + const table = window.BattleTeambuilderTable[this.modid]; + if (table.fullItemName && id in table.fullItemName) { + data.name = table.fullItemName[id]; + data.exists = true; + } for (let i = Dex.gen - 1; i >= this.gen; i--) { - const table = window.BattleTeambuilderTable[`gen${i}`]; - if (id in table.overrideItemData) { - Object.assign(data, table.overrideItemData[id]); + const genTable = window.BattleTeambuilderTable[`gen${i}`]; + if (id in genTable.overrideItemData) { + Object.assign(data, genTable.overrideItemData[id]); } } - if (this.modid !== `gen${this.gen}`) { - const table = window.BattleTeambuilderTable[this.modid]; - if (id in table.overrideItemData) { - Object.assign(data, table.overrideItemData[id]); - } + if (this.modid !== `gen${this.gen}`) && id in table.overrideItemData) { + Object.assign(data, table.overrideItemData[id]); } const item = new Item(id, name, data); @@ -933,7 +1037,7 @@ class ModdedDex { if (this.cache.Abilities.hasOwnProperty(id)) return this.cache.Abilities[id]; let data = {...Dex.abilities.get(name)}; - + for (let i = Dex.gen - 1; i >= this.gen; i--) { const table = window.BattleTeambuilderTable[`gen${i}`]; if (id in table.overrideAbilityData) { @@ -942,11 +1046,17 @@ class ModdedDex { } if (this.modid !== `gen${this.gen}`) { const table = window.BattleTeambuilderTable[this.modid]; - if (id in table.overrideAbilityData) { + if (table.overrideAbilityData && id in table.overrideAbilityData) { Object.assign(data, table.overrideAbilityData[id]); } + if (table.overrideAbilityDesc && id in table.overrideAbilityDesc) { + data.shortDesc = table.overrideAbilityDesc[id]; + } + if (table.fullAbilityName && id in table.fullAbilityName) { + data.name = table.fullAbilityName[id]; + data.exists = true; + } } - const ability = new Ability(id, name, data); this.cache.Abilities[id] = ability; return ability; @@ -954,42 +1064,76 @@ class ModdedDex { }; species = { - get: (name: string): Species => { + get: (name: string, hasData = true, debug = ""): Species => { + if (name.id) name = name.id; let id = toID(name); + let formid = id; if (window.BattleAliases && id in BattleAliases) { name = BattleAliases[id]; id = toID(name); } - if (this.cache.Species.hasOwnProperty(id)) return this.cache.Species[id]; - - let data = {...Dex.species.get(name)}; - - for (let i = Dex.gen - 1; i >= this.gen; i--) { - const table = window.BattleTeambuilderTable[`gen${i}`]; - if (id in table.overrideSpeciesData) { - Object.assign(data, table.overrideSpeciesData[id]); + + if (name.includes('-')) this.species.get(name.split('-')[0]); + const table = window.BattleTeambuilderTable[this.modid]; + if (!table.BattlePokedexAltForms) table.BattlePokedexAltForms = {}; + if (formid in table.BattlePokedexAltForms) { + return table.BattlePokedexAltForms[formid]; + } + if (!table.BattleBaseSpeciesChart) table.BattleBaseSpeciesChart = []; + if (window.BattleAliases && id in BattleAliases && !table.overrideDexInfo[id]) { + name = BattleAliases[id]; + id = toID(name); + } else if (table.overrideDexInfo && !(id in table.overrideDexInfo) && table.BattleBaseSpeciesChart) { + for (const baseSpeciesId of table.BattleBaseSpeciesChart) { + if (formid.startsWith(baseSpeciesId)) { + id = baseSpeciesId; + break; + } } } - if (this.modid !== `gen${this.gen}`) { - const table = window.BattleTeambuilderTable[this.modid]; - if (id in table.overrideSpeciesData) { - Object.assign(data, table.overrideSpeciesData[id]); + // if (this.cache.Species.hasOwnProperty(id)) return this.cache.Species[id]; + var data; + if (hasData) { + data = {...Dex.species.get(name, true, "from moddedDex: getSpecies 1")}; + if (table.overrideDexInfo && table.overrideDexInfo[id]) { + data = {...Dex.species.get(name, true, "from moddedDex: getSpecies 2"), ...table.overrideDexInfo[id]}; + } + } else { + if (table.overrideDexInfo && table.overrideDexInfo[id]) { + data = {...table.overrideDexInfo[id]}; } } + if (this.gen < 3 || this.modid === 'gen7letsgo') { - data.abilities = {0: "No Ability"}; + data.abilities = {0: "None"}; } - - const table = window.BattleTeambuilderTable[this.modid]; - if (id in table.overrideTier) data.tier = table.overrideTier[id]; + + if (table.overrideTier && id in table.overrideTier) data.tier = table.overrideTier[id]; + if (table.doubles?.overrideTier && id in table.doubles.overrideTier) data.doublesTier = table.doubles.overrideTier[id]; if (!data.tier && id.slice(-5) === 'totem') { data.tier = this.species.get(id.slice(0, -5)).tier; } if (!data.tier && data.baseSpecies && toID(data.baseSpecies) !== id) { data.tier = this.species.get(data.baseSpecies).tier; } + if (data.cosmeticFormes) { + if (!table.BattleBaseSpeciesChart.includes(id)) table.BattleBaseSpeciesChart.push(id); + for (const forme of data.cosmeticFormes) { + if (toID(forme) === formid) { + data = new Species(formid, name, { + ...data, + name: forme, + forme: forme.slice(data.name.length + 1), + baseForme: "", + baseSpecies: data.name, + otherFormes: null, + }); + table.BattlePokedexAltForms[formid] = data; + break; + } + } + } if (data.gen > this.gen) data.tier = 'Illegal'; - const species = new Species(id, name, data); this.cache.Species[id] = species; return species; @@ -1001,7 +1145,7 @@ class ModdedDex { const id = toID(name) as ID; name = id.substr(0, 1).toUpperCase() + id.substr(1); - if (this.cache.Types.hasOwnProperty(id)) return this.cache.Types[id]; + // if (this.cache.Types.hasOwnProperty(id)) return this.cache.Types[id]; let data = {...Dex.types.get(name)}; diff --git a/play.pokemonshowdown.com/src/battle-log-misc.js b/play.pokemonshowdown.com/src/battle-log-misc.js index 898c3b99c..f8f6ea98c 100644 --- a/play.pokemonshowdown.com/src/battle-log-misc.js +++ b/play.pokemonshowdown.com/src/battle-log-misc.js @@ -27,3 +27,18 @@ d=k(d,e,b,c,g[f+15],14,3634488961),c=k(c,d,e,b,g[f+4],20,3889429448),b=k(b,c,d,e e=l(e,b,c,d,g[f+4],11,1272893353),d=l(d,e,b,c,g[f+7],16,4139469664),c=l(c,d,e,b,g[f+10],23,3200236656),b=l(b,c,d,e,g[f+13],4,681279174),e=l(e,b,c,d,g[f+0],11,3936430074),d=l(d,e,b,c,g[f+3],16,3572445317),c=l(c,d,e,b,g[f+6],23,76029189),b=l(b,c,d,e,g[f+9],4,3654602809),e=l(e,b,c,d,g[f+12],11,3873151461),d=l(d,e,b,c,g[f+15],16,530742520),c=l(c,d,e,b,g[f+2],23,3299628645),b=m(b,c,d,e,g[f+0],6,4096336452),e=m(e,b,c,d,g[f+7],10,1126891415),d=m(d,e,b,c,g[f+14],15,2878612391),c=m(c,d,e,b,g[f+5],21,4237533241), b=m(b,c,d,e,g[f+12],6,1700485571),e=m(e,b,c,d,g[f+3],10,2399980690),d=m(d,e,b,c,g[f+10],15,4293915773),c=m(c,d,e,b,g[f+1],21,2240044497),b=m(b,c,d,e,g[f+8],6,1873313359),e=m(e,b,c,d,g[f+15],10,4264355552),d=m(d,e,b,c,g[f+6],15,2734768916),c=m(c,d,e,b,g[f+13],21,1309151649),b=m(b,c,d,e,g[f+4],6,4149444226),e=m(e,b,c,d,g[f+11],10,3174756917),d=m(d,e,b,c,g[f+2],15,718787259),c=m(c,d,e,b,g[f+9],21,3951481745),b=i(b,o),c=i(c,p),d=i(d,q),e=i(e,r);return(n(b)+n(c)+n(d)+n(e)).toLowerCase()}; /* eslint-enable */ + +// text formatter, transpiled from server chat-formatter.js +var formatText = (function(){function g(d,a){a=void 0===a?!1:a;d=(""+d).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'");this.f=d=d.replace(h,function(a){if(/^[a-z0-9.]+@/ig.test(a))var c="mailto:"+a;else if(c=a.replace(/^([a-z]*[^a-z:])/g,"http://$1"),"https://docs.google.com/"===a.substr(0,24)||"docs.google.com/"===a.substr(0,16)){"https"===a.slice(0,5)&&(a=a.slice(8));if("?usp=sharing"===a.substr(-12)||"&usp=sharing"===a.substr(-12))a=a.slice(0,-12); +"#gid=0"===a.substr(-6)&&(a=a.slice(0,-6));var b=a.lastIndexOf("/");18<a.length-b&&(b=a.length);22<b-4&&(a=a.slice(0,19)+'<small class="message-overflow">'+a.slice(19,b-4)+"</small>"+a.slice(b-4))}return'<a href="'+c+'" rel="noopener" target="_blank">'+a+"</a>"});this.b=[];this.stack=[];this.isTrusted=a;this.offset=0}var h=/(?:(?:(?:https?:\/\/|\bwww[.])[a-z0-9-]+(?:[.][a-z0-9-]+)*|\b[a-z0-9-]+(?:[.][a-z0-9-]+)*[.](?:com?|org|net|edu|info|us|jp|[a-z]{2,3}(?=[:/])))(?:[:][0-9]+)?\b(?:\/(?:(?:[^\s()&<>]|&|"|[(](?:[^\s()<>&]|&)*[)])*(?:[^\s`()[\]{}'".,!?;:&<>*`^~\\]|[(](?:[^\s()<>&]|&)*[)]))?)?|[a-z0-9.]+\b@[a-z0-9-]+(?:[.][a-z0-9-]+)*[.][a-z]{2,3})(?![^ ]*>)/ig; +g.prototype.slice=function(d,a){return this.f.slice(d,a)};g.prototype.a=function(d){return this.f.charAt(d)};g.prototype.i=function(d,a,b){this.c(a);this.stack.push([d,this.b.length]);this.b.push(this.slice(a,b));this.offset=b};g.prototype.c=function(d){d!==this.offset&&(this.b.push(this.slice(this.offset,d)),this.offset=d)};g.prototype.m=function(d){for(var a=-1,b=this.stack.length-1;0<=b;b--){var c=this.stack[b];if("("===c[0]){a=b;break}if("spoiler"!==c[0])break}if(-1!==a){for(this.c(d);this.stack.length> +a;)this.h(d);this.offset=d}};g.prototype.o=function(d,a,b){for(var c=-1,e=this.stack.length-1;0<=e;e--)if(this.stack[e][0]===d){c=e;break}if(-1===c)return!1;for(this.c(a);this.stack.length>c+1;)this.h(a);a=this.stack.pop()[1];c="";switch(d){case "_":c="i";break;case "*":c="b";break;case "~":c="s";break;case "^":c="sup";break;case "\\":c="sub"}c&&(this.b[a]="<"+c+">",this.b.push("</"+c+">"),this.offset=b);return!0};g.prototype.h=function(d){var a=this.stack.pop();if(a)switch(this.c(d),a[0]){case "spoiler":this.b.push("</span>"); +this.b[a[1]]='<span class="spoiler">';break;case ">":this.b.push("</span>"),this.b[a[1]]='<span class="greentext">'}};g.prototype.j=function(d){for(;this.stack.length;)this.h(d);this.c(d)};g.prototype.l=function(d){d=d.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'");return encodeURIComponent(d)};g.prototype.g=function(d,a){switch(d){case "`":for(var b=0,c=a;"`"===this.a(c);)b++,c++;for(var e=0;c<this.f.length;){var f=this.a(c);if("\n"=== +f)break;if("`"===f)e++;else{if(e===b)break;e=0}c++}if(e!==b)break;this.c(a);this.b.push("<code>");e=a+b;b=c-b;e+1>=b||(" "===this.a(e)&&" "===this.a(b-1)?(e++,b--):" "===this.a(e)&&"`"===this.a(e+1)?e++:" "===this.a(b-1)&&"`"===this.a(b-2)&&b--);this.b.push(this.slice(e,b));this.b.push("</code>");this.offset=c;break;case "[":if("[["!==this.slice(a,a+2))break;c=a+2;for(f=e=-1;c<this.f.length;){b=this.a(c);if("]"===b||"\n"===b)break;":"===b&&0>e&&(e=c);"&"===b&&"<"===this.slice(c,c+4)&&(f=c);c++}if("]]"!== +this.slice(c,c+2))break;var g=c;b="";0<=f&&">"===this.slice(c-4,c)&&(b=this.slice(f+4,c-4),g=f," "===this.a(g-1)&&g--,b=encodeURI(b.replace(/^([a-z]*[^a-z:])/g,"http://$1")));f=this.slice(a+2,g).replace(/<\/?a(?: [^>]+)?>/g,"");b&&!this.isTrusted&&(g=b.replace(/^https?:\/\//,"").replace(/^www\./,"").replace(/\/$/,""),f+="<small> <"+g+"></small>",b+='" rel="noopener');if(0<e)switch(e=this.slice(a+2,e).toLowerCase(),e){case "w":case "wiki":f=f.slice(" "===f.charAt(e.length+1)?e.length+2:e.length+ +1);b="//en.wikipedia.org/w/index.php?title=Special:Search&search="+this.l(f);f="wiki: "+f;break;case "pokemon":case "item":f=f.slice(" "===f.charAt(e.length+1)?e.length+2:e.length+1),g=this.isTrusted?"<psicon "+e+'="'+f+'"/>':"["+f+"]",b=e,"item"===e&&(b+="s"),b="//dex.pokemonshowdown.com/"+b+"/"+toID(f),f=g}b||(b="//www.google.com/search?ie=UTF-8&btnI&q="+this.l(f));this.c(a);this.b.push('<a href="'+b+'" target="_blank">'+f+"</a>");this.offset=c+2;break;case "<":if("<<"!==this.slice(a,a+8))break; +for(c=a+8;/[a-z0-9-]/.test(this.a(c));)c++;if(">>"!==this.slice(c,c+8))break;this.c(a);b=this.slice(a+8,c);this.b.push('«<a href="/'+b+'" target="_blank">'+b+"</a>»");this.offset=c+8;break;case "a":for(c=a+1;"/"!==this.a(c)||"a"!==this.a(c+1)||">"!==this.a(c+2);)c++;this.c(c+3)}};g.prototype.get=function(){for(var d=this.offset,a=d;a<this.f.length;a++){var b=this.a(a);switch(b){case "_":case "*":case "~":case "^":case "\\":if(this.a(a+1)===b&&this.a(a+2)!==b&&(" "!==this.a(a-1)&& +this.o(b,a,a+2)||" "!==this.a(a+2)&&this.i(b,a,a+2),a<this.offset)){a=this.offset-1;break}for(;this.a(a+1)===b;)a++;break;case "(":this.stack.push(["(",-1]);break;case ")":this.m(a);a<this.offset&&(a=this.offset-1);break;case "`":"`"===this.a(a+1)&&this.g("`",a);if(a<this.offset){a=this.offset-1;break}for(;"`"===this.a(a+1);)a++;break;case "[":this.g("[",a);if(a<this.offset){a=this.offset-1;break}for(;"["===this.a(a+1);)a++;break;case ":":if(7>a)break;if("spoiler:"===this.slice(a-7,a+1).toLowerCase()|| +"spoilers:"===this.slice(a-8,a+1).toLowerCase())" "===this.a(a+1)&&a++,this.i("spoiler",a+1,a+1);break;case "&":a===d&&">"===this.slice(a,a+4)?"._/=:;".includes(this.a(a+4))||["w<","w>"].includes(this.slice(a+4,a+9))||this.i(">",a,a):this.g("<",a);if(a<this.offset){a=this.offset-1;break}for(;"lt;&"===this.slice(a+1,a+5);)a+=4;break;case "<":this.g("a",a);a<this.offset&&(a=this.offset-1);break;case "\r":case "\n":this.j(a),d=a+1}}this.j(this.f.length);return this.b.join("")};return function(d, +a){return(new g(d,void 0===a?!1:a)).get()}})(); +/* eslint-enable */ \ No newline at end of file diff --git a/play.pokemonshowdown.com/src/battle-log.ts b/play.pokemonshowdown.com/src/battle-log.ts index c078c16a4..cb38f0cfb 100644 --- a/play.pokemonshowdown.com/src/battle-log.ts +++ b/play.pokemonshowdown.com/src/battle-log.ts @@ -763,8 +763,7 @@ export class BattleLog { return false; }, getURI(uri: string) { - return `http://${Config.routes.root}/interstice?uri=${encodeURIComponent(uri)}`; - }, + return `http://${Config.routes.psmain}/interstice?uri=${encodeURIComponent(uri)}`; }, }; })(); @@ -1194,7 +1193,7 @@ export class BattleLog { buf += '<div class="battle-log battle-log-inline"><div class="inner">' + battle.scene.log.elem.innerHTML + '</div></div>\n'; buf += '</div>\n'; buf += '<script>\n'; - buf += `let daily = Math.floor(Date.now()/1000/60/60/24);document.write('<script src="https://${Config.routes.client}/js/replay-embed.js?version'+daily+'"></'+'script>');\n`; + buf += `let daily = Math.floor(Date.now()/1000/60/60/24);document.write('<script src="http://191.101.232.116//js/replay-embed.js?version'+daily+'"></'+'script>');\n`; buf += '</script>\n'; return buf; } diff --git a/play.pokemonshowdown.com/src/battle-scene-stub.ts b/play.pokemonshowdown.com/src/battle-scene-stub.ts index 7fc99892f..db64678e9 100644 --- a/play.pokemonshowdown.com/src/battle-scene-stub.ts +++ b/play.pokemonshowdown.com/src/battle-scene-stub.ts @@ -75,6 +75,7 @@ export class BattleSceneStub { anim(pokemon: Pokemon, end: ScenePos, transition?: string) { } beforeMove(pokemon: Pokemon) { } afterMove(pokemon: Pokemon) { } + updateSpritesForSide(side: Side) { } } if (typeof require === 'function') { diff --git a/play.pokemonshowdown.com/src/battle-sound.ts b/play.pokemonshowdown.com/src/battle-sound.ts index 5307e73f9..52c079471 100644 --- a/play.pokemonshowdown.com/src/battle-sound.ts +++ b/play.pokemonshowdown.com/src/battle-sound.ts @@ -115,7 +115,7 @@ export const BattleSound = new class { if (this.soundCache[url]) return this.soundCache[url]; try { const sound = document.createElement('audio'); - sound.src = 'https://' + Config.routes.client + '/' + url; + sound.src = url; sound.volume = this.effectVolume / 100; this.soundCache[url] = sound; return sound; @@ -142,7 +142,7 @@ export const BattleSound = new class { this.deleteBgm(replaceBGM); } - const bgm = new BattleBGM(url, loopstart, loopend); + const bgm = new BattleBGM('https://' + Config.routes.psmain + '/' + url, loopstart, loopend); this.bgm.push(bgm); return bgm; } diff --git a/play.pokemonshowdown.com/src/battle-tooltips.ts b/play.pokemonshowdown.com/src/battle-tooltips.ts index ebeb17136..1f3df4ff5 100644 --- a/play.pokemonshowdown.com/src/battle-tooltips.ts +++ b/play.pokemonshowdown.com/src/battle-tooltips.ts @@ -203,12 +203,10 @@ class BattleTooltips { $elem.on('touchstart', '.has-tooltip', e => { e.preventDefault(); this.holdLockTooltipEvent(e); - if (!BattleTooltips.parentElem) { - // should never happen, but in case there's a bug in the tooltip handler - BattleTooltips.parentElem = e.currentTarget; + if (e.currentTarget === BattleTooltips.parentElem && BattleTooltips.parentElem!.tagName === 'BUTTON') { + $(BattleTooltips.parentElem!).addClass('pressed'); + BattleTooltips.isPressed = true; } - $(BattleTooltips.parentElem!).addClass('pressed'); - BattleTooltips.isPressed = true; }); $elem.on('touchend', '.has-tooltip', e => { e.preventDefault(); @@ -1068,6 +1066,19 @@ class BattleTooltips { stats.atk *= 2; } } + //Vaporemons + if (this.battle.tier.includes("VaporeMons")) { + if (item === 'mantisclaw') { + if (speciesName === 'Scyther') { + speedModifiers.push(1.5); + } else if (speciesName === 'Scizor') { + stats.def = Math.floor(stats.def * 1.3); + stats.spd = Math.floor(stats.spd * 1.3); + } else if (speciesName === 'Kleavor') { + stats.atk = Math.floor(stats.atk * 1.5); + } + } + } if (speciesName === 'Ditto' && !(clientPokemon && 'transform' in clientPokemon.volatiles)) { if (item === 'quickpowder') { @@ -1165,6 +1176,17 @@ class BattleTooltips { } else { stats[statName] = Math.floor(stats[statName] * 1.3); } + } + if (this.battle.tier.includes("VaporeMons")) {//Vaporemons + if (clientPokemon.volatiles['protomosis' + statName] || clientPokemon.volatiles['photondrive' + statName] || + clientPokemon.volatiles['protocrysalis' + statName] || clientPokemon.volatiles['neurondrive' + statName] || + clientPokemon.volatiles['protostasis' + statName] || clientPokemon.volatiles['runedrive' + statName]) { + if (statName === 'spe') { + speedModifiers.push(1.5); + } else { + stats[statName] = Math.floor(stats[statName] * 1.3); + } + } } } } @@ -1176,6 +1198,25 @@ class BattleTooltips { speedModifiers.push(1.5); } } + if (this.battle.tier.includes("VaporeMons")) {//Vaporemons + if (item === 'tuffytuff' && (['igglybuff','jigglypuff','wigglytuff'].includes( + this.battle.dex.species.get(serverPokemon.speciesForme).id))) { + stats.def *= 2; + stats.spd *= 2; + } + else if (item === 'mithrilarmor') { + stats.def = Math.floor(stats.def * 1.2); + } + else if (item === 'snowglobe' && this.pokemonHasType(pokemon, 'Ice')) { + stats.def = Math.floor(stats.def * 1.5); + } + else if (item === 'sandclock' && this.pokemonHasType(pokemon, 'Rock')) { + stats.spd = Math.floor(stats.spd * 1.5); + } + else if (item === 'desertrose' && species === 'Florges' && this.battle.weather === 'sandstorm') { + stats.spd = Math.floor(stats.spd * 1.5); + } + } const isNFE = this.battle.dex.species.get(serverPokemon.speciesForme).evos?.some(evo => { const evoSpecies = this.battle.dex.species.get(evo); return !evoSpecies.isNonstandard || @@ -1188,7 +1229,11 @@ class BattleTooltips { stats.spd = Math.floor(stats.spd * 1.5); } if (ability === 'grasspelt' && this.battle.hasPseudoWeather('Grassy Terrain')) { - stats.def = Math.floor(stats.def * 1.5); + if (this.battle.tier.includes("VaporeMons")) {//Vaporemons + stats.def = Math.floor(stats.def * 1.3333); + } else { + stats.def = Math.floor(stats.def * 1.5); + } } if (this.battle.hasPseudoWeather('Electric Terrain')) { if (ability === 'surgesurfer') { @@ -1667,15 +1712,15 @@ class BattleTooltips { } if (move.id === 'weatherball' && value.weatherModify(0)) { if (this.battle.weather === 'stormsurge') moveType = 'Water'; - if (this.battle.weather === 'deserteddunes') moveType = 'Rock'; + else if (this.battle.weather === 'deserteddunes') moveType = 'Rock'; } if (move.id === 'o' || move.id === 'worriednoises') { moveType = pokemonTypes[0]; } - if (move.id === 'dillydally') { + else if (move.id === 'dillydally') { moveType = pokemonTypes[pokemonTypes.length - 1]; } - if (move.id === 'magicalfocus') { + else if (move.id === 'magicalfocus') { if (this.battle.turn % 3 === 1) { moveType = 'Fire'; } else if (this.battle.turn % 3 === 2) { @@ -1684,10 +1729,10 @@ class BattleTooltips { moveType = 'Ice'; } } - if (move.id === 'hydrostatics' && pokemon.terastallized) { + else if (move.id === 'hydrostatics' && pokemon.terastallized) { moveType = 'Water'; } - if (move.id === 'asongoficeandfire' && pokemon.getSpeciesForme() === 'Volcarona') moveType = 'Ice'; + else if (move.id === 'asongoficeandfire' && pokemon.getSpeciesForme() === 'Volcarona') moveType = 'Ice'; if (this.battle.abilityActive('dynamictyping')) { moveType = '???'; } @@ -1698,6 +1743,10 @@ class BattleTooltips { } } } + /*else if (this.battle.tier.includes("VaporeMons") && move.id === 'terablast' && itemName === 'Tera Shard') { + const stats = this.calculateModifiedStats(pokemon, serverPokemon, true); + if (stats.atk > stats.spa) category = 'Physical'; + }*/ return [moveType, category]; } @@ -1873,6 +1922,9 @@ class BattleTooltips { if (move.id === 'terablast' && pokemon.terastallized === 'Stellar') { value.set(100, 'Tera Stellar boost'); } + /*if (['terablast'].includes(move.id) && this.battle.tier.includes("VaporeMons") && itemName === 'Tera Shard') { + value.set(100, 'Tera Shard boost'); + }*/ if (move.id === 'brine' && target && target.hp * 2 <= target.maxhp) { value.modify(2, 'Brine + target below half HP'); } @@ -2046,7 +2098,15 @@ class BattleTooltips { } } // Base power based on times hit - if (move.id === 'ragefist') { + if (this.battle.tier.includes("VaporeMons")) {//Vaporemons + if (move.id === 'ragefist' || move.id === 'ragingfury') { + value.set(Math.min(200, 50 + 50 * pokemon.timesAttacked), + pokemon.timesAttacked > 0 + ? `Hit ${pokemon.timesAttacked} time${pokemon.timesAttacked > 1 ? 's' : ''}` + : undefined); + } + } + if (move.id === 'ragefist' && !this.battle.tier.includes("VaporeMons")) { value.set(Math.min(350, 50 + 50 * pokemon.timesAttacked), pokemon.timesAttacked > 0 ? `Hit ${pokemon.timesAttacked} time${pokemon.timesAttacked > 1 ? 's' : ''}` @@ -2073,7 +2133,10 @@ class BattleTooltips { if (['psn', 'tox'].includes(pokemon.status) && move.category === 'Physical') { value.abilityModify(1.5, "Toxic Boost"); } - if (['Rock', 'Ground', 'Steel'].includes(moveType) && this.battle.weather === 'sandstorm') { + if (['Rock', 'Ground', 'Steel'].includes(moveType) && this.battle.weather === 'sandstorm' && !this.battle.tier.includes("VaporeMons")) { + if (value.tryAbility("Sand Force")) value.weatherModify(1.3, "Sandstorm", "Sand Force"); + } + if (this.battle.weather === 'sandstorm' && this.battle.tier.includes("VaporeMons")) { if (value.tryAbility("Sand Force")) value.weatherModify(1.3, "Sandstorm", "Sand Force"); } if (move.secondaries) { @@ -2139,7 +2202,11 @@ class BattleTooltips { } else if (allyAbility === 'Power Spot' && ally !== pokemon) { value.modify(1.3, 'Power Spot'); } else if (allyAbility === 'Steely Spirit' && moveType === 'Steel') { - value.modify(1.5, 'Steely Spirit'); + if (this.battle.tier.includes("VaporeMons")) { + value.modify(2, 'Steely Spirit'); + } else { + value.modify(1.5, 'Steely Spirit'); + } } } for (const foe of pokemon.side.foe.active) { @@ -2405,10 +2472,36 @@ class BattleTooltips { if (itemName === 'Muscle Band' && move.category === 'Physical' || itemName === 'Wise Glasses' && move.category === 'Special' || - itemName === 'Punching Glove' && move.flags['punch']) { + itemName === 'Punching Glove' && move.flags['punch'] && !this.battle.tier.includes("VaporeMons")) { value.itemModify(1.1); } - + //Vaporemons + if (this.battle.tier.includes("VaporeMons")) { + if (itemName === 'Protective Pads' && (move.recoil || move.hasCrashDamage) || + itemName === 'Quick Claw' && move.priority > 0.1 || + itemName === 'Razor Fang' && move.flags['bite'] || + itemName === 'Razor Claw' && move.flags['slicing'] || + itemName === 'Big Root' && move.flags['heal'] || + itemName === 'Punching Glove' && move.flags['punch']) { + value.itemModify(1.3); + } else if (itemName === 'Baseball Bat' && move.flags['contact']) { + value.itemModify(1.25); + } else if (itemName === 'Tie-Dye Band') { + if (this.pokemonHasType(pokemon, moveType)) { + value.itemModify(0.67); + } else { + value.itemModify(1.3); + } + } else if (itemName === 'Hero\' Bubble' && moveType === 'Water' && speciesName === 'Palafin') { + value.itemModify(2); + }/* else if ( + (speciesName === 'Charizard' && itemName === 'Wellspring Mask') || + (speciesName.startsWith('Ogerpon-Hearthflame') && itemName === 'Hearthflame Mask') || + (speciesName.startsWith('Ogerpon-Cornerstone') && itemName === 'Cornerstone Mask')) { + value.itemModify(1.2); + return value; + }*/ + } return value; } getPokemonTypes(pokemon: Pokemon | ServerPokemon, preterastallized = false): ReadonlyArray<TypeName> { @@ -2553,9 +2646,9 @@ class BattleStatGuesser { supportsEVs: boolean; supportsAVs: boolean; - constructor(formatid: ID) { + constructor(formatid: ID, modid: ID) { this.formatid = formatid; - this.dex = formatid ? Dex.mod(formatid.slice(0, 4) as ID) : Dex; + this.dex = modid ? Dex.mod(modid) : formatid ? Dex.mod(formatid.slice(0, 4) as ID) : Dex; this.ignoreEVLimits = ( this.dex.gen < 3 || ((this.formatid.endsWith('hackmons') || this.formatid.endsWith('bh')) && this.dex.gen !== 6) || @@ -3295,4 +3388,4 @@ if (typeof require === 'function') { // in Node (global as any).BattleStatGuesser = BattleStatGuesser; (global as any).BattleStatOptimizer = BattleStatOptimizer; -} +} \ No newline at end of file diff --git a/play.pokemonshowdown.com/src/battle.ts b/play.pokemonshowdown.com/src/battle.ts index 4a9b73e9a..b0e8d27a2 100644 --- a/play.pokemonshowdown.com/src/battle.ts +++ b/play.pokemonshowdown.com/src/battle.ts @@ -658,9 +658,14 @@ export class Side { } reset() { this.clearPokemon(); + this.updateSprites(); this.sideConditions = {}; this.faintCounter = 0; } + updateSprites() { + this.z = (this.isFar ? 200 : 0); + this.battle.scene.updateSpritesForSide(this); + } setAvatar(avatar: string) { this.avatar = avatar; } @@ -1087,6 +1092,7 @@ export class Battle { myAllyPokemon: ServerPokemon[] | null = null; lastMove = ''; + mod = '' as ID; gen = 8; dex: ModdedDex = Dex; teamPreviewCount = 0; @@ -1147,6 +1153,19 @@ export class Battle { throw new Error(`You must specify $frame and $logFrame simultaneously`); } + const format = this.id.slice(this.id.indexOf('-') + 1, this.id.lastIndexOf('-')); + for (const mod in window.ModConfig) { + for (const formatid in window.ModConfig[mod].formats) { + if (format === formatid) { + this.mod = mod as ID; + this.dex = Dex.mod(mod as ID); + break; + } + } + if (this.mod) break; + } + if (this.id.includes('digimon')) this.mod = 'digimon' as ID; + this.paused = !!options.paused; this.started = !this.paused; this.debug = !!options.debug; @@ -1510,7 +1529,7 @@ export class Battle { if ( !target && this.gameType === 'singles' && - !['self', 'allies', 'allySide', 'adjacentAlly', 'adjacentAllyOrSelf', 'allyTeam'].includes(moveTarget) + !['self', 'allies', 'allySide', 'adjacentAlly', 'adjacentAllyOrSelf', 'anyAlly', 'allyTeam'].includes(moveTarget) ) { // Hardcode for moves without a target in singles foeTargets.push(pokemon.side.foe.active[0]); @@ -3536,9 +3555,9 @@ export class Battle { side.setName(args[2]); if (args[3]) side.setAvatar(args[3]); if (args[4]) side.rating = args[4]; + this.scene.updateSidebar(side); if (this.joinButtons) this.scene.hideJoinButtons(); this.log(args); - this.scene.updateSidebar(side); break; } case 'badge': { @@ -3683,7 +3702,7 @@ export class Battle { } case 'gen': { this.gen = parseInt(args[1], 10); - this.dex = Dex.forGen(this.gen); + this.dex = this.mod ? Dex.mod(this.mod) : Dex.forGen(this.gen); this.scene.updateGen(); this.log(args); break; diff --git a/play.pokemonshowdown.com/src/client-main.ts b/play.pokemonshowdown.com/src/client-main.ts index 8e33e27e2..44a5338c2 100644 --- a/play.pokemonshowdown.com/src/client-main.ts +++ b/play.pokemonshowdown.com/src/client-main.ts @@ -274,9 +274,9 @@ class PSTeams extends PSStreamModel<'team' | 'format'> { *********************************************************************/ class PSUser extends PSModel { - name = ""; + name = "Guest"; group = ''; - userid = "" as ID; + userid = "guest" as ID; named = false; registered = false; avatar = "1"; @@ -296,22 +296,22 @@ class PSUser extends PSModel { } } } - logOut() { - PSLoginServer.query({ - act: 'logout', - userid: this.userid, - }); - PS.send('|/logout'); - PS.connection?.disconnect(); - - alert("You have been logged out and disconnected.\n\nIf you wanted to change your name while staying connected, use the 'Change Name' button or the '/nick' command."); - this.name = ""; - this.group = ''; - this.userid = "" as ID; - this.named = false; - this.registered = false; - this.update(); - } + // logOut() { + // PSLoginServer.query({ + // act: 'logout', + // userid: this.userid, + // }); + // PS.send('|/logout'); + // PS.connection?.disconnect(); + + // alert("You have been logged out and disconnected.\n\nIf you wanted to change your name while staying connected, use the 'Change Name' button or the '/nick' command."); + // this.name = ""; + // this.group = ''; + // this.userid = "" as ID; + // this.named = false; + // this.registered = false; + // this.update(); + // } } /********************************************************************** @@ -535,15 +535,6 @@ class PSRoom extends PSStreamModel<Args | null> implements RoomOptions { }} } handleMessage(line: string) { - if (!line.startsWith('/') || line.startsWith('//')) return false; - const spaceIndex = line.indexOf(' '); - const cmd = spaceIndex >= 0 ? line.slice(1, spaceIndex) : line.slice(1); - // const target = spaceIndex >= 0 ? line.slice(spaceIndex + 1) : ''; - switch (cmd) { - case 'logout': { - PS.user.logOut(); - return true; - }} return false; } send(msg: string, direct?: boolean) { @@ -671,17 +662,6 @@ const PS = new class extends PSModel { leftRoomWidth = 0; mainmenu: MainMenuRoom = null!; - /** - * The drag-and-drop API is incredibly dumb and doesn't let us know - * what's being dragged until the `drop` event, so we track it here. - * - * Note that `PS.dragging` will be null if the drag was initiated - * outside PS (e.g. dragging a team from File Explorer to PS), and - * for security reasons it's impossible to know what they are until - * they're dropped. - */ - dragging: {type: 'room', roomid: RoomID} | null = null; - /** Tracks whether or not to display the "Use arrow keys" hint */ arrowKeysUsed = false; @@ -843,11 +823,7 @@ const PS = new class extends PSModel { const roomid = fullMsg.slice(0, pipeIndex) as RoomID; const msg = fullMsg.slice(pipeIndex + 1); console.log('\u25b6\ufe0f ' + (roomid ? '[' + roomid + '] ' : '') + '%c' + msg, "color: #776677"); - if (!this.connection) { - alert(`You are not connected and cannot send ${msg}.`); - return; - } - this.connection.send(fullMsg); + this.connection!.send(fullMsg); } isVisible(room: PSRoom) { if (this.leftRoomWidth === 0) { @@ -905,7 +881,7 @@ const PS = new class extends PSModel { case 'news': options.type = options.id; break; - case 'battle-': case 'user-': case 'team-': case 'ladder-': + case 'battle-': case 'user-': case 'team-': options.type = options.id.slice(0, hyphenIndex); break; case 'view-': diff --git a/play.pokemonshowdown.com/src/panel-chat.tsx b/play.pokemonshowdown.com/src/panel-chat.tsx index 482885ce6..f9164a097 100644 --- a/play.pokemonshowdown.com/src/panel-chat.tsx +++ b/play.pokemonshowdown.com/src/panel-chat.tsx @@ -85,7 +85,7 @@ class ChatRoom extends PSRoom { this.update(null); return false; }} - return super.handleMessage(line); + return false; } openChallenge() { if (!this.pmTarget) { diff --git a/play.pokemonshowdown.com/src/panel-mainmenu.tsx b/play.pokemonshowdown.com/src/panel-mainmenu.tsx index 66f7ff982..30fbe5fa5 100644 --- a/play.pokemonshowdown.com/src/panel-mainmenu.tsx +++ b/play.pokemonshowdown.com/src/panel-mainmenu.tsx @@ -5,9 +5,7 @@ * @license AGPLv3 */ -type RoomInfo = { - title: string, desc?: string, userCount?: number, section?: string, spotlight?: string, subRooms?: string[], -}; +type RoomInfo = {title: string, desc?: string, userCount?: number, subRooms?: string[]}; class MainMenuRoom extends PSRoom { readonly classType: string = 'mainmenu'; @@ -16,7 +14,7 @@ class MainMenuRoom extends PSRoom { avatar?: string | number, status?: string, group?: string, - customgroup?: string, + // customgroup?: string, rooms?: {[roomid: string]: {isPrivate?: true, p1?: string, p2?: string}}, }} = {}; roomsCache: { @@ -24,6 +22,8 @@ class MainMenuRoom extends PSRoom { userCount?: number, chat?: RoomInfo[], sectionTitles?: string[], + official?: RoomInfo[], + pspl?: RoomInfo[], } = {}; receiveLine(args: Args) { const [cmd] = args; @@ -247,16 +247,6 @@ class MainMenuRoom extends PSRoom { if (userRoom) userRoom.update(null); break; case 'rooms': - if (response.pspl) { - for (const roomInfo of response.pspl) roomInfo.spotlight = "Spotlight"; - response.chat = [...response.pspl, ...response.chat]; - response.pspl = null; - } - if (response.official) { - for (const roomInfo of response.official) roomInfo.section = "Official"; - response.chat = [...response.official, ...response.chat]; - response.official = null; - } this.roomsCache = response; const roomsRoom = PS.rooms[`rooms`] as RoomsRoom; if (roomsRoom) roomsRoom.update(null); @@ -274,12 +264,6 @@ class MainMenuRoom extends PSRoom { battlesRoom.update(null); } break; - case 'laddertop': - const ladderRoomEntries = Object.entries(PS.rooms).filter(entry => entry[0].startsWith('ladder')); - for (const [, ladderRoom] of ladderRoomEntries) { - (ladderRoom as LadderRoom).update(response); - } - break; } } } @@ -299,10 +283,10 @@ class MainMenuPanel extends PSRoomPanel<MainMenuRoom> { submit = (e: Event) => { alert('todo: implement'); }; - handleDragStart = (e: DragEvent) => { - const roomid = (e.currentTarget as HTMLElement).getAttribute('data-roomid') as RoomID; - PS.dragging = {type: 'room', roomid}; - }; + // handleDragStart = (e: DragEvent) => { + // const roomid = (e.currentTarget as HTMLElement).getAttribute('data-roomid') as RoomID; + // PS.dragging = {type: 'room', roomid}; + // }; renderMiniRoom(room: PSRoom) { const roomType = PS.roomTypes[room.type]; const Panel = roomType ? roomType.Component : PSRoomPanel; @@ -313,7 +297,7 @@ class MainMenuPanel extends PSRoomPanel<MainMenuRoom> { const room = PS.rooms[roomid]!; return <div class="pmbox"> <div class="mini-window"> - <h3 draggable onDragStart={this.handleDragStart} data-roomid={roomid}> + <h3> <button class="closebutton" name="closeRoom" value={roomid} aria-label="Close" tabIndex={-1}><i class="fa fa-times-circle"></i></button> <button class="minimizebutton" tabIndex={-1}><i class="fa fa-minus-circle"></i></button> {room.title} @@ -323,41 +307,60 @@ class MainMenuPanel extends PSRoomPanel<MainMenuRoom> { </div>; }); } - renderSearchButton() { - if (PS.down) { - return <div class="menugroup" style="background: rgba(10,10,10,.6)"> - {PS.down === 'ddos' ? - <p class="error"><strong>Pokémon Showdown is offline due to a DDoS attack!</strong></p> - : - <p class="error"><strong>Pokémon Showdown is offline due to technical difficulties!</strong></p> - } - <p> - <div style={{textAlign: 'center'}}> - <img width="96" height="96" src={`//${Config.routes.client}/sprites/gen5/teddiursa.png`} alt="" /> - </div> - Bear with us as we freak out. - </p> - <p>(We'll be back up in a few hours.)</p> - </div>; - } + // renderSearchButton() { + // if (PS.down) { + // return <div class="menugroup" style="background: rgba(10,10,10,.6)"> + // {PS.down === 'ddos' ? + // <p class="error"><strong>Pokémon Showdown is offline due to a DDoS attack!</strong></p> + // : + // <p class="error"><strong>Pokémon Showdown is offline due to technical difficulties!</strong></p> + // } + // <p> + // <div style={{textAlign: 'center'}}> + // <img width="96" height="96" src={`//${Config.routes.client}/sprites/gen5/teddiursa.png`} alt="" /> + // </div> + // Bear with us as we freak out. + // </p> + // <p>(We'll be back up in a few hours.)</p> + // </div>; + // } - if (!PS.user.userid || PS.isOffline) { - return <TeamForm class="menugroup" onSubmit={this.submit}> - <button class="mainmenu1 big button disabled" name="search"> - <em>{PS.isOffline ? "Disconnected" : "Connecting..."}</em> - </button> - </TeamForm>; - } + // if (!PS.user.userid || PS.isOffline) { + // return <TeamForm class="menugroup" onSubmit={this.submit}> + // <button class="mainmenu1 big button disabled" name="search"> + // <em>{PS.isOffline ? "Disconnected" : "Connecting..."}</em> + // </button> + // </TeamForm>; + // } - return <TeamForm class="menugroup" onSubmit={this.submit}> - <button class="mainmenu1 big button" name="search"> + // return <TeamForm class="menugroup" onSubmit={this.submit}> + // <button class="mainmenu1 big button" name="search"> + // <strong>Battle!</strong><br /> + // <small>Find a random opponent</small> + // </button> + // </TeamForm>; + // } + render() { + const onlineButton = ' button' + (PS.isOffline ? ' disabled' : ''); + const searchButton = (PS.down ? <div class="menugroup" style="background: rgba(10,10,10,.6)"> + {PS.down === 'ddos' ? + <p class="error"><strong>Pokémon Showdown is offline due to a DDoS attack!</strong></p> + : + <p class="error"><strong>Pokémon Showdown is offline due to technical difficulties!</strong></p> + } + <p> + <div style={{textAlign: 'center'}}> + <img width="96" height="96" src={`//${Config.routes.client}/sprites/gen5/teddiursa.png`} alt="" /> + </div> + Bear with us as we freak out. + </p> + <p>(We'll be back up in a few hours.)</p> + </div> : <TeamForm class="menugroup" onSubmit={this.submit}> + <button class={"mainmenu1 big" + onlineButton} name="search"> <strong>Battle!</strong><br /> <small>Find a random opponent</small> </button> - </TeamForm>; - } - render() { - const onlineButton = ' button' + (PS.isOffline ? ' disabled' : ''); + </TeamForm>); return <PSPanelWrapper room={this.props.room} scrollable> <div class="mainmenuwrapper"> <div class="leftmenu"> @@ -365,7 +368,7 @@ class MainMenuPanel extends PSRoomPanel<MainMenuRoom> { {this.renderMiniRooms()} </div> <div class="mainmenu"> - {this.renderSearchButton()} + {searchButton} <div class="menugroup"> <p><button class="mainmenu2 button" name="joinRoom" value="teambuilder">Teambuilder</button></p> diff --git a/play.pokemonshowdown.com/src/panel-rooms.tsx b/play.pokemonshowdown.com/src/panel-rooms.tsx index 74ec5d177..677af0e51 100644 --- a/play.pokemonshowdown.com/src/panel-rooms.tsx +++ b/play.pokemonshowdown.com/src/panel-rooms.tsx @@ -126,6 +126,8 @@ class RoomsPanel extends PSRoomPanel { } else { roomList = [ this.renderRoomList("Chat rooms", rooms.chat), + this.renderRoomList("Official chat rooms", rooms.official), + this.renderRoomList("PSPL winner", rooms.pspl), ]; } diff --git a/play.pokemonshowdown.com/src/panel-teambuilder-team.tsx b/play.pokemonshowdown.com/src/panel-teambuilder-team.tsx index 47dce8ab8..64fa73652 100644 --- a/play.pokemonshowdown.com/src/panel-teambuilder-team.tsx +++ b/play.pokemonshowdown.com/src/panel-teambuilder-team.tsx @@ -186,6 +186,10 @@ class TeamTextbox extends preact.Component<{team: Team}> { } class TeamPanel extends PSRoomPanel<TeamRoom> { + backToList = () => { + PS.removeRoom(this.props.room); + PS.join('teambuilder' as RoomID); + }; rename = (e: Event) => { const textbox = e.currentTarget as HTMLInputElement; const room = this.props.room; @@ -198,8 +202,7 @@ class TeamPanel extends PSRoomPanel<TeamRoom> { const team = PS.teams.byKey[room.id.slice(5)]; if (!team) { return <PSPanelWrapper room={room}> - <button class="button" data-href="teambuilder" data-target="replace"> - <i class="fa fa-chevron-left"></i> List + <button class="button" onClick={this.backToList}> <i class="fa fa-chevron-left"></i> List </button> <p class="error"> Team doesn't exist @@ -210,8 +213,7 @@ class TeamPanel extends PSRoomPanel<TeamRoom> { if (!room.team) room.team = team; return <PSPanelWrapper room={room} scrollable> <div class="pad"> - <button class="button" data-href="teambuilder" data-target="replace"> - <i class="fa fa-chevron-left"></i> List + <button class="button" onClick={this.backToList}> <i class="fa fa-chevron-left"></i> List </button> <label class="label teamname"> Team name: diff --git a/play.pokemonshowdown.com/src/panel-teamdropdown.tsx b/play.pokemonshowdown.com/src/panel-teamdropdown.tsx index 0794f16ca..29c59afc1 100644 --- a/play.pokemonshowdown.com/src/panel-teamdropdown.tsx +++ b/play.pokemonshowdown.com/src/panel-teamdropdown.tsx @@ -398,7 +398,7 @@ class PSTeambuilder { if (line.startsWith('Hidden Power [')) { const hpType = line.slice(14, -1) as TypeName; line = 'Hidden Power ' + hpType; - if (!set.ivs && Dex.types.isName(hpType)) { + if (!set.ivs && window.BattleTypeChart && window.BattleTypeChart[hpType]) { set.ivs = {hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31}; const hpIVs = Dex.types.get(hpType).HPivs || {}; for (let stat in hpIVs) { diff --git a/play.pokemonshowdown.com/src/panels.tsx b/play.pokemonshowdown.com/src/panels.tsx index 3bffff1b1..fddda1a3a 100644 --- a/play.pokemonshowdown.com/src/panels.tsx +++ b/play.pokemonshowdown.com/src/panels.tsx @@ -3,7 +3,7 @@ * * Main view - sets up the frame, and the generic panels. * - * Also sets up most global event listeners. + * Also sets up global event listeners. * * @author Guangcong Luo <guangcongluo@gmail.com> * @license AGPLv3 @@ -20,34 +20,6 @@ class PSRouter { this.subscribeHash(); } } - extractRoomID(url: string) { - if (url.startsWith(document.location.origin)) { - url = url.slice(document.location.origin.length); - } else { - if (url.startsWith('http://')) { - url = url.slice(7); - } else if (url.startsWith('https://')) { - url = url.slice(8); - } - if (url.startsWith(document.location.host)) { - url = url.slice(document.location.host.length); - } else if (PS.server.id === 'showdown' && url.startsWith('play.pokemonshowdown.com')) { - url = url.slice(24); - } else if (PS.server.id === 'showdown' && url.startsWith('psim.us')) { - url = url.slice(7); - } else if (url.startsWith('replay.pokemonshowdown.com')) { - url = url.slice(26).replace('/', '/battle-'); - } - } - if (url.startsWith('/')) url = url.slice(1); - - if (!/^[a-z0-9-]*$/.test(url)) return null; - - const redirects = /^(appeals?|rooms?suggestions?|suggestions?|adminrequests?|bugs?|bugreports?|rules?|faq|credits?|privacy|contact|dex|insecure)$/; - if (redirects.test(url)) return null; - - return url as RoomID; - } subscribeHash() { if (location.hash) { const currentRoomid = location.hash.slice(1); @@ -224,14 +196,8 @@ class PSMain extends preact.Component { return; } if (elem.tagName === 'A' || elem.getAttribute('data-href')) { - const href = elem.getAttribute('data-href') || (elem as HTMLAnchorElement).href; - const roomid = PS.router.extractRoomID(href); - + const roomid = this.roomidFromLink(elem as HTMLAnchorElement); if (roomid !== null) { - if (elem.getAttribute('data-target') === 'replace') { - const room = this.getRoom(elem); - if (room) PS.leave(room.id); - } PS.addRoom({ id: roomid, parentElem: elem, @@ -246,19 +212,8 @@ class PSMain extends preact.Component { if (this.handleButtonClick(elem as HTMLButtonElement)) { e.preventDefault(); e.stopImmediatePropagation(); - return; - } else if (!elem.getAttribute('type')) { - // the spec says that buttons with no `type` attribute should be - // submit buttons, but this is a bad default so we're going - // to just assume they're not - - // don't return, to allow <a><button> to make links that look - // like buttons - e.preventDefault(); - } else { - // presumably a different part of the app is handling this button - return; } + return; } if (elem.id.startsWith('room-')) { clickedRoom = PS.rooms[elem.id.slice(5)]; @@ -307,18 +262,9 @@ class PSMain extends preact.Component { } }); - const colorSchemeQuery = window.matchMedia?.('(prefers-color-scheme: dark)'); - if (colorSchemeQuery?.media !== 'not all') { - colorSchemeQuery.addEventListener('change', function (cs) { - if (PS.prefs.theme === 'system') document.body.className = cs.matches ? 'dark' : ''; - }); - } - PS.prefs.subscribeAndRun(key => { - if (!key || key === 'theme') { - const dark = PS.prefs.theme === 'dark' || - (PS.prefs.theme === 'system' && colorSchemeQuery && colorSchemeQuery.matches); - document.body.className = dark ? 'dark' : ''; + if (!key || key === 'dark') { + document.body.className = PS.prefs.dark ? 'dark' : ''; } }); } @@ -351,6 +297,29 @@ class PSMain extends preact.Component { } return false; } + roomidFromLink(elem: HTMLAnchorElement) { + let href = elem.getAttribute('data-href'); + if (href) { + // yes that's what we needed + } else if (PS.server.id === 'showdown') { + if (elem.host && elem.host !== Config.routes.client && elem.host !== 'psim.us') { + return null; + } + href = elem.pathname; + } else { + if (elem.host !== location.host) { + return null; + } + href = elem.pathname; + } + const roomid = href.slice(1); + if (!/^[a-z0-9-]*$/.test(roomid)) { + return null; // not a roomid + } + const redirects = /^(appeals?|rooms?suggestions?|suggestions?|adminrequests?|bugs?|bugreports?|rules?|faq|credits?|news|privacy|contact|dex|insecure)$/; + if (redirects.test(roomid)) return null; + return roomid as RoomID; + } static containingRoomid(elem: HTMLElement) { let curElem: HTMLElement | null = elem; while (curElem) { @@ -511,8 +480,4 @@ class PSMain extends preact.Component { } } -type PanelPosition = {top?: number, bottom?: number, left?: number, right?: number} | null; - -function SanitizedHTML(props: {children: string}) { - return <div dangerouslySetInnerHTML={{__html: BattleLog.sanitizeHTML(props.children)}}/>; -} +type PanelPosition = {top?: number, bottom?: number, left?: number, right?: number} | null; \ No newline at end of file diff --git a/play.pokemonshowdown.com/style/STYLING.html b/play.pokemonshowdown.com/style/STYLING.html index c3fcbaecf..4e9249f1f 100644 --- a/play.pokemonshowdown.com/style/STYLING.html +++ b/play.pokemonshowdown.com/style/STYLING.html @@ -172,13 +172,13 @@ <h2>Buttons and links</h2> </p> <div class="light-container"> - Play <a href="https://pokemonshowdown.com/">Showdown!</a> + Play <a href="https://pokemonshowdown.com/">Showdown Pet Mods</a> </div> <div class="dark-container dark"> - Play <a href="https://pokemonshowdown.com/">Showdown!</a> + Play <a href="https://pokemonshowdown.com/">Showdown Pet Mods</a> </div> <div class="dark-container code dark"> - Play <a href="https://pokemonshowdown.com/">Showdown!</a> + Play <a href="https://pokemonshowdown.com/">Showdown Pet Mods</a> </div> <div class="clear"></div> diff --git a/play.pokemonshowdown.com/style/client.css b/play.pokemonshowdown.com/style/client.css index 80ed47b03..08ec18629 100644 --- a/play.pokemonshowdown.com/style/client.css +++ b/play.pokemonshowdown.com/style/client.css @@ -28,32 +28,235 @@ body { .pad p { margin: 9px 0; } +.label { + font-size: 9pt; + font-weight: bold; + display: block; +} +.optlabel { + font-size: 9pt; + display: block; +} .label strong { font-size: 11pt; display: block; } +.label .textbox { + display: block; +} +.textbox { + border: 1px solid #AAAAAA; + border-radius: 3px; + padding: 2px 3px; + font-family: Verdana, Helvetica, Arial, sans-serif; + font-size: 9pt; + + box-shadow: inset 0px 1px 2px #CCCCCC, 1px 1px 0 rgba(255,255,255,.6); + background: #F8FBFD; + color: black; +} +.textbox:hover { + border-color: #474747; + box-shadow: inset 0px 1px 2px #D2D2D2, 1px 1px 0 rgba(255,255,255,.6); + background: #FFFFFF; +} +.textbox:focus, +.button:focus { + outline: 0 none; + border-color: #004488; + box-shadow: inset 0px 1px 2px #D2D2D2, 0px 0px 5px #2266AA; +} +.textbox:focus { + background: #FFFFFF; +} +.textbox.disabled, .textbox:disabled { + background: #CCCCCC; + color: #555555; +} .buttonbar { margin-top: 1em; text-align: center; } +hr { + border-top: 1px solid #999999; + border-bottom: 0; + border-left: 0; + border-right: 0; +} + select { font-size: 9pt; font-family: Verdana, Helvetica, Arial, sans-serif; } -.dark .tabbar a.button { - box-shadow: inset 0.5px 1px 1px rgba(255, 255, 255, 0.5); +/* .dark a.button { + color: #222222; + text-shadow: 0 1px 0 white; + border: solid 1px #AAAAAA; + background: #e3e3e3; + background: linear-gradient(to bottom, #f6f6f6, #e3e3e3); +} +.dark a.button:hover { + background: #cfcfcf; + background: linear-gradient(to bottom, #f2f2f2, #c2c2c2); + border-color: #606060; +} +.dark a.button:active { + background: linear-gradient(to bottom, #cfcfcf, #f2f2f2); +} */ + +.dark .button.notifying { + background: #6BACC5; + border-color: #2C9CC1; +} + +.dark .button.notifying.subtle { + border-color: #000000; + background: #b2d7f7; + color: black; +} + +.dark .button.notifying:hover { + background: #6BACC5; + border-color: #FFFFFF; +} + +.dark a.button:visited { + color: #F9F9F9; +} +.dark a.button.subtle-notifying { + color: #61A3BB; +} + +.dark .textbox { + border-color: #888888; + + box-shadow: none; + background: #282B2D; + color: #DDD; } -.dark .tabbar a.button:active, -.dark .tabbar a.button.cur:active { +.dark .textbox:hover { + border-color: #BBBBBB; box-shadow: none; + background: #222222; +} +.dark .textbox:focus, +.dark .button:focus { + outline: 0 none; + border-color: #BBBBBB; + box-shadow: 0px 0px 4px #88CCFF, 0px 0px 4px #88CCFF; +} +.dark .textbox:focus { + background: #111111; +} +/* .dark .roomtab.button.closable, +.dark .roomtab.button { + background: #3A3A3A; + border-color: #A9A9A9; + color: #F9F9F9; + text-shadow: 1px 1px 0 #111; +} +.dark .roomtab.button:hover { + background: #606060; + border-color: #EEEEEE; +} +.dark .roomtab.button.cur.closable, +.dark .roomtab.button.cur { + background: #636363; + border-color: #A9A9A9; + color: #F9F9F9; + text-shadow: 1px 1px 0 #111; +} */ +.dark .tabbar a.button.subtle-notifying { + color: #6BACC5; +} +.dark .tabbar a.button.notifying { + background: #6BACC5; + border-color: #2C9CC1; + color: #000; + text-shadow: none; } .dark .maintabbarbottom { + background: #636363; + border-color: #A9A9A9; +} + +/* .dark button { + background: #777777; + box-shadow: none; + border: 1px solid #EEEEEE; + color: #EEEEEE; + border-radius: 5px; +} +*/ +.dark .folderButton, +.dark .tabbar a.button, +.dark .popupmenu .folderButton, +.dark .popupmenu .folderButtonOpen, +.dark .popupmenu button.button { + background: #484848; + box-shadow: inset 0 3px 4px rgba(255, 255, 255, 0.25); + border-color: #A9A9A9; + text-shadow: none; + color: #F9F9F9; +} + +.dark .popupmenu button.folderButtonOver { + background: linear-gradient(to bottom, #2a2a2a, #313131); +} + +.dark .popupmenu button.folderButtonOpen:hover { + color: #F9F9F9; +} + +.dark .popupmenu button.folderButtonOpen:hover, +.dark .folderButton:hover, +.dark .popupmenu button.folderButton:hover, +.dark .popupmenu button.button:hover, +.dark .tabbar a.button:hover { + background: #606060; + border-color: #EEEEEE; +} + +.dark .folderButton:hover, +.dark .popupmenu button.folderButton:hover, +.dark .popupmenu button.button:hover { + color: #F9F9F9; +} + +.dark .tabbar a.button.notifying:hover { + background: #92C2D3; +} +.dark .tabbar a.button:active { + background: #3A3A3A; + border-color: #EEEEEE; + box-shadow: none; +} +.dark .button.cur, +.dark .button.cur:hover, +.dark .tabbar a.button.cur, +.dark .tabbar a.button.cur:hover { + background: #636363; + border-color: #A9A9A9; + box-shadow: none; + color: #F9F9F9; +} +.dark .button.disabled, +.dark .button.disabled:hover, +.dark .button.disabled:active { background: #555555; - border-color: #5A5A5A; - border-top-color: #34373b; + border-color: #555555; + box-shadow: none; + color: #999999; } +/* +.dark .closebutton, +.dark .minimizebutton, +.dark button.subtle { + background: none; + border: none; +} */ /********************************************************* * Header @@ -111,7 +314,7 @@ select { display: block; list-style: none; margin: 0; - padding: 2px 0 0 0; + padding: 0 0 0 0; height: 37px; text-align: left; @@ -132,7 +335,6 @@ select { background: #f8f8f8; border: solid 1px #AAAAAA; border-left: 0; - border-right: 0; margin: -1px 0 0 0; -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.2); -moz-box-shadow: 0 1px 2px rgba(0,0,0,.2); @@ -163,15 +365,63 @@ select { border-radius: 0; } -.popupmenu .option { - width: 204px; +.popupmenu button.button { + outline: none; + cursor: pointer; + text-align: center; + text-decoration: none; + border-radius: 5px; + -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.2), inset 0 -1px 2px rgba(255,255,255,1); + -moz-box-shadow: 0 1px 2px rgba(0,0,0,.2), inset 0 -1px 2px rgba(255,255,255,1); + box-shadow: 0 1px 2px rgba(0,0,0,.2), inset 0 -1px 2px rgba(255,255,255,1); + border-radius: 5px; + font-family: Verdana, Helvetica, Arial, sans-serif; + display: inline-block; + + /* default colors */ + color: #222222; + text-shadow: 0 1px 0 white; + border: solid 1px #AAAAAA; + background: #e3e3e3; + background: linear-gradient(to bottom, #f6f6f6, #e3e3e3); + + font-size: 9pt; + padding: 3px 8px; } + .popupmenu i { display: inline-block; width: 16px; color: #777777; } +.popupmenu button.folderButton, +.popupmenu button.folderButtonOpen { + width: 172px; + outline: none; + cursor: pointer; + text-decoration: none; + border-radius: 5px; + -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.2), inset 0 -1px 2px rgba(255,255,255,1); + -moz-box-shadow: 0 1px 2px rgba(0,0,0,.2), inset 0 -1px 2px rgba(255,255,255,1); + box-shadow: 0 1px 2px rgba(0,0,0,.2), inset 0 -1px 2px rgba(255,255,255,1); + border-radius: 5px; + font-family: Verdana, Helvetica, Arial, sans-serif; + display: inline-block; + /* default colors */ + color: #222222; + text-shadow: 0 1px 0 white; + border: solid 1px #AAAAAA; + background: #e3e3e3; + background: linear-gradient(to bottom, #f6f6f6, #e3e3e3); + font-size: 9pt; + padding: 3px 8px; +} + +.popupmenu button.folderButtonOpen { + background: linear-gradient(to bottom, #e4e4e4, #d3d3d3); +} + .button.small, .button.small { font-size: 8pt; @@ -182,6 +432,16 @@ select { font-size: 12pt; padding: 5px 10px; } +.popupmenu button.folderButton:hover, +.popupmenu button.folderButtonOpen:hover, +.popupmenu button.button:hover { + background: #cfcfcf; + background: linear-gradient(to bottom, #f2f2f2, #c2c2c2); + border-color: #606060; +} +.popupmenu button.button:active { + background: linear-gradient(to bottom, #cfcfcf, #f2f2f2); +} .mainmenuwrapper .menugroup .button.disabled, .mainmenuwrapper .menugroup .button.disabled:hover, @@ -196,6 +456,31 @@ select { box-shadow: 0 1px 2px rgba(0,0,0,.1); } +.button.notifying { + border-color: #AA8866; + background: #e3c3a3; +} +.button.notifying:hover { + border-color: #604020; + background: #cfaf8f; +} +.button.notifying.subtle { + border-color: #000000; + background: #b2d7f7; + color: black; +} +.button.cur, +.folderButton.cur, +.button.cur:hover { + color: #777777; + background: #f8f8f8; + box-shadow: none; + border-color: #AAAAAA; + cursor: default; +} +.button.subtle-notifying { + color: #AA6600; +} .button.subtle-notifying .fa-comment-o:before, .button.notifying .fa-comment-o:before { content: "\f0e6"; @@ -211,6 +496,7 @@ i.subtle:hover { filter: alpha(opacity=100); } + .tabbar li, .tabbar ul { display: block; @@ -231,11 +517,8 @@ i.subtle:hover { margin: 0 -1px 0 0; top: 1px; border-radius: 0; - box-shadow: inset 0 -1px 2px rgba(255,255,255,1); - font-size: 11px; -} -.tabbar a.button.cur { box-shadow: none; + font-size: 11px; } .tabbar a.button i { display: block; @@ -260,9 +543,7 @@ i.subtle:hover { margin-right: -6px; } .tabbar a.button:hover, -.tablist a.button:hover, -.tabbar a.button:focus, -.tablist a.button:focus { +.tablist a.button:hover { z-index: 10; } .tabbar a.button.cur, @@ -311,9 +592,6 @@ i.subtle:hover { .closebutton:hover { color: #BB2222; } -.dark .closebutton:hover { - color: #EE7777; -} span.header-username:hover { color: #333333; } @@ -321,10 +599,6 @@ span.header-username:hover { .pm-window h3:hover .minimizebutton { color: #333333; } -.dark .minimizebutton:hover, -.dark .pm-window h3:hover .minimizebutton { - color: #CCCCCC; -} .pm-window h3 .closebutton:hover + .minimizebutton { color: #999999 !important; } @@ -336,12 +610,10 @@ span.header-username:hover { color: #661111; } .pm-minimized .minimizebutton .fa-minus-circle:before { - /** replace the minus with a plus when PM is minimized */ content: "\f055"; } .tablist li, .tablist ul { - /** tablist is the menu that's displayed when the window is too wide for all the tabs */ list-style: none; margin: 0; padding: 0; @@ -419,6 +691,7 @@ span.header-username:hover { .scrollable { overflow: auto; -webkit-overflow-scrolling: touch; + overflow-scrolling: touch; } .ps-room.ps-room-light { background: rgba(242,247,250,.85); @@ -440,6 +713,7 @@ span.header-username:hover { overflow: auto; -webkit-overflow-scrolling: touch; + overflow-scrolling: touch; z-index: 20; } .ps-popup { @@ -460,9 +734,7 @@ span.header-username:hover { margin: 80px auto 20px auto; max-width: 320px; } -.ps-popup p { - margin: 4px 0; -} +.ps-popup p, .ps-popup h3 { margin: 7px 0; } @@ -510,6 +782,21 @@ p.or:after { .popupmenu li:first-child h3 { margin-top: 0; } +.popupmenu button { + display: block; + font-size: 8pt; + font-family: Verdana, Helvetica, Arial, sans-serif; + margin: 0 0 0 6px; + padding: 2px 3px; + border: 1px solid transparent; + border-radius: 2px; + background: transparent; + color: black; + width: 204px; + text-align: left; + + box-sizing: border-box; +} @media (max-height:590px) { .popupmenu h3 { margin-top: 2px; @@ -524,7 +811,18 @@ p.or:after { } } -.popupmenu .button { +.popupmenu button.sel { + border-color: #AAAAAA; + white-space: nowrap; + overflow: hidden; +} +.popupmenu button:hover, +.popupmenu button.sel:hover { + border-color: #888888; + background: #D5D5D5; + color: black; +} +.popupmenu button.button { margin: 2px auto 5px; width: 184px; } @@ -703,21 +1001,14 @@ p.or:after { border-color: #AA8866; background: #E3C3A3; } -.dark .pm-window h3.pm-notifying { - background: #417589; - color: #BBBBBB; -} .pm-window h3.pm-notifying:hover { border-color: #604020; background: #CFAF8F; } -.dark .pm-window h3.pm-notifying:hover { - background: #417589; -} .pm-window h3 .closebutton, .pm-window h3 .minimizebutton { float: right; - margin: -2px -3px; + margin: -3px -3px; width: 22px; height: 22px; } @@ -737,6 +1028,7 @@ p.or:after { overflow: auto; -webkit-overflow-scrolling: touch; + overflow-scrolling: touch; word-wrap: break-word; } .pm-buttonbar { @@ -763,18 +1055,6 @@ p.or:after { background: #f8f8f8; color: #222222; } -.dark .pm-window h3 { - background: rgba(50,50,50,.8); - color: #AAA; -} -.dark .pm-window h3:hover { - color: #CCC; -} -.dark .pm-window.focused h3, -.dark .pm-window.focused h3:hover { - background: #222; - color: #EEE; -} .challenge { margin-top: -1px; background: #fcd2b3; @@ -1018,6 +1298,32 @@ p.or:after { max-width: 480px; text-align: left; } +.roomlist a.ilink { + display: block; + margin: 2px 7px 4px 7px; + padding: 1px 4px 2px 4px; + border: 1px solid #BBCCDD; + background: rgba(248, 251, 253, 0.5); + + border-radius: 4px; + text-decoration: none; + color: #336699; + text-shadow: #ffffff 0px -1px 0; + cursor: pointer; + font-size: 10pt; + + overflow: hidden; + white-space: nowrap; +} +.roomlist a.ilink small { + font-size: 8pt; +} +.roomlist a.ilink:hover { + border-color: #778899; + background: #E5EAED; + color: #224466; + text-decoration: none; +} .roomlist .subrooms { font-size: 8pt; padding-left: 20px; @@ -1026,7 +1332,7 @@ p.or:after { .roomlist .subrooms i.fa-level-up { margin-right: 5px; } -.roomlist .subrooms a.blocklink { +.roomlist .subrooms a.ilink { display: inline-block; margin: 0; vertical-align: middle; @@ -1050,6 +1356,7 @@ p.or:after { overflow: auto; overflow-x: hidden; -webkit-overflow-scrolling: touch; + overflow-scrolling: touch; word-wrap: break-word; } .chat-log-add { @@ -1219,7 +1526,6 @@ a.ilink.yours { overflow: hidden; transition: max-height 0.15s; -webkit-transition: max-height 0.15s; - touch-action: none; } .tournament-bracket { @@ -1227,19 +1533,15 @@ a.ilink.yours { padding: 10px; overflow: hidden; font-size: 8pt; - touch-action: none; } - .tournament-bracket-overflowing { height: 200px; padding: 0; position: relative; left: 0; top: 0; - touch-action: none; } - .tournament-popout-link { position: absolute; bottom: 0.5em; @@ -1427,6 +1729,7 @@ a.ilink.yours { overflow: auto; -webkit-overflow-scrolling: touch; + overflow-scrolling: touch; } .pm-buttonbar { height: 21px; @@ -1441,9 +1744,6 @@ a.ilink.yours { border-right: 1px solid #AAAAAA; border-bottom: 1px solid #AAAAAA; } -.dark .userlist, .dark .pm-buttonbar button, .dark .chat-log-add, .dark .pm-window h3, .dark .pm-log, .dark .newsentry { - border-color: #5A5A5A; -} .userlist-minimized { height: 21px; bottom: auto; @@ -1480,7 +1780,8 @@ a.ilink.yours { text-align: left; } .userlist li { - height: 20px; + border-bottom: 1px solid #CCCCCC; + height: 19px; font: 10pt Verdana, sans-serif; white-space: nowrap; } @@ -1503,7 +1804,7 @@ a.ilink.yours { border: 0; padding: 1px 0; margin: 0; - height: 20px; + height: 19px; width: 100%; white-space: nowrap; font: 9pt Verdana, sans-serif; @@ -2568,6 +2869,8 @@ a.ilink.yours { color: #CC3311; border-color: #CC3311; } +.setchart-nickname input { +} .setchart .setcell-details input { width: 216px; } @@ -2691,6 +2994,7 @@ a.ilink.yours { overflow-y: scroll; -webkit-overflow-scrolling: touch; + overflow-scrolling: touch; } @media (max-height:410px) { .teambuilder-results { @@ -2727,6 +3031,7 @@ a.ilink.yours { overflow: auto; -webkit-overflow-scrolling: touch; + overflow-scrolling: touch; } .teamwrapper.scaled { width: 640px; @@ -2949,6 +3254,11 @@ a.ilink.yours { float: left; margin: 2px; padding: 2px; + + border: 1px solid transparent; + border-radius: 4px; + box-shadow: none; + background: transparent; } .avatarlist button { width: 80px; @@ -2961,8 +3271,20 @@ a.ilink.yours { height: 90px; background: url(../fx/client-bgsheet.png) no-repeat scroll 0px 0px; } -.formlist button { - background-repeat: no-repeat; +.avatarlist button.cur, +.formlist button.cur, +.bglist button.cur { + border-color: #999999; +} +.avatarlist button:hover, +.avatarlist button.cur:hover, +.formlist button:hover, +.formlist button.cur:hover, +.bglist button:hover, +.bglist button.cur:hover { + border: 1px solid #8899AA; + background-color: #F1F4F9; + box-shadow: 1px 1px 1px #D5D5D5; } .effect-volume, @@ -3003,13 +3325,11 @@ a.ilink.yours { .dark .pm-log-add { background: rgba(0,0,0,.70); color: #DDD; - border-color: #5A5A5A; } .dark .pm-log { background: rgba(0,0,0,.85); color: #DDD; - backdrop-filter: blur(4px); } .dark .userlist-maximized { @@ -3046,7 +3366,6 @@ a.ilink.yours { .dark .chat-log { background: rgba(0,0,0,.5); color: #DDD; - backdrop-filter: blur(4px); } .dark .userbar .username { @@ -3059,15 +3378,29 @@ a.ilink.yours { .dark .ps-popup { background: #0D151E; color: #DDD; - border-color: #34373b; + border-color: #888; - box-shadow: 2px 2px 3px rgba(0,0,0,.5), inset 0.5px 1px 1px rgba(255, 255, 255, 0.5); + box-shadow: 2px 2px 3px rgba(0,0,0,.2); } .dark .usergroup { color: #BBB; } +.dark .popupmenu button, +.dark .bglist button, +.dark .avatarlist button { + color: #AAA; + box-shadow: none; +} +.dark .popupmenu button:hover, +.dark .popupmenu button.sel:hover, +.dark .bglist button:hover, +.dark .avatarlist button:hover { + border-color: #AAAAAA; + background-color: #AAAAAA; + color: black; +} .dark .changeform i { border-color: #bbb; color: #bbb; @@ -3139,7 +3472,7 @@ a.ilink.yours { } .dark .highlighted { - background: rgba(120,220,255,0.25); + background: rgba(120,220,255,0.28); } .dark .message-pm { color: #00af00; @@ -3149,10 +3482,9 @@ a.ilink.yours { } .dark a.ilink { color: #4488EE; - border-color: #526c87; } .dark .chat.mine { - background: rgba(255,255,255,0.08); + background: rgba(255,255,255,0.05); } /* teambuilder */ @@ -3270,9 +3602,50 @@ a.ilink.yours { color: #BBB; } -/* for testclient */ +/* rooms */ +.dark .roomlist a.ilink { + box-shadow: none; + text-shadow: none; +} + +.dark .roomlist a.ilink { + border-color: #7799BB; + background: rgba(30, 40, 50, .5); + color: #7799BB; +} +.dark .roomlist a.ilink:hover { + border-color: #AACCEE; + background: rgba(30, 40, 50, 1); + color: #AACCEE; +} + +/* misc */ .dark iframe.textbox, .dark iframe.textbox:hover, .dark iframe.textbox:focus { background: #DDDDDD; } + +/********************************************************* + * <blink> + *********************************************************/ + +@-webkit-keyframes blinker { + from { opacity: 1.0; } + to { opacity: 0.0; } +} +@keyframes blinker { + from { opacity: 1.0; } + to { opacity: 0.0; } +} +blink { + -webkit-animation-name: blinker; + -webkit-animation-iteration-count: infinite; + -webkit-animation-timing-function: cubic-bezier(1.0,0,0,1.0); + -webkit-animation-duration: 1s; + animation-name: blinker; + animation-iteration-count: infinite; + animation-timing-function: cubic-bezier(1.0,0,0,1.0); + animation-duration: 1s; + text-decoration: none; +} diff --git a/play.pokemonshowdown.com/testclient-beta.html b/play.pokemonshowdown.com/testclient-beta.html index 6069704a8..65627275d 100644 --- a/play.pokemonshowdown.com/testclient-beta.html +++ b/play.pokemonshowdown.com/testclient-beta.html @@ -131,6 +131,8 @@ <h3><button class="closebutton" tabindex="-1" aria-label="Close"><i class="fa fa <script src="data/abilities.js"></script> <script src="data/search-index.js"></script> <script src="data/teambuilder-tables.js"></script> + <script src="data/mod-sprites.js" onerror="loadRemoteData(this.src)"></script> + <script src="data/mod-config.js" onerror="loadRemoteData(this.src)"></script> <script src="js/panel-teamdropdown.js"></script> <script src="js/panel-teambuilder.js?"></script> <script src="js/battle-dex-search.js?"></script> diff --git a/play.pokemonshowdown.com/testclient.html b/play.pokemonshowdown.com/testclient.html index 17d828fd5..d670c9af4 100644 --- a/play.pokemonshowdown.com/testclient.html +++ b/play.pokemonshowdown.com/testclient.html @@ -3,7 +3,7 @@ <head> <meta charset="UTF-8" /> <meta id="viewport" name="viewport" content="width=device-width" /> - <title>Showdown!</title> + <title>Showdown Pet Mods</title> <link rel="shortcut icon" href="favicon.ico" id="dynamic-favicon" /> <link rel="stylesheet" href="style/battle.css" /> <link rel="stylesheet" href="style/client.css" /> @@ -12,7 +12,7 @@ <link rel="stylesheet" href="style/font-awesome.css" /> <meta name="robots" content="noindex" /> <meta http-equiv="X-UA-Compatible" content="IE=Edge" /> - <script src="https://play.pokemonshowdown.com/config/config.js"></script> + <script src="config/config.js"></script> <script> function loadRemoteData(src) { var scriptEl = document.createElement('script'); @@ -50,7 +50,7 @@ <div class="pm-window news-embed"> <h3><button class="closebutton" tabindex="-1" aria-label="Close"><i class="fa fa-times-circle"></i></button><button class="minimizebutton" tabindex="-1" aria-label="Minimize"><i class="fa fa-minus-circle"></i></button>Latest News</h3> <div class="pm-log" style="max-height:none"> - <div class="newsentry"><h4>Test client</h4><p>Welcome to the test client! You can test client changes here!</p><p>—<strong>Zarel</strong> <small class="date">on Sep 25, 2015</small></p></div> + <div class="newsentry"><h4>Pet Mods Client</h4><p>Welcome to the Pet Mods client! We have teambuilder support for mods here!</p><strong></strong></div> </div> </div> </div> @@ -84,8 +84,6 @@ <h3><button class="closebutton" tabindex="-1" aria-label="Close"><i class="fa fa window.exports = window; </script> - <!-- fallback for if you've never done a full build --> - <script src="js/server/chat-formatter.js" onerror="loadRemoteData(this.src)"></script> <script src="js/battledata.js" onerror="alert('You must build the client with `node build` before using testclient.html')"></script> <script src="data/text.js" onerror="loadRemoteData(this.src)"></script> <script src="data/pokedex-mini.js" onerror="loadRemoteData(this.src)"></script> @@ -119,6 +117,8 @@ <h3><button class="closebutton" tabindex="-1" aria-label="Close"><i class="fa fa <script src="data/search-index.js" onerror="loadRemoteData(this.src)"></script> <script src="data/teambuilder-tables.js" onerror="loadRemoteData(this.src)"></script> + <script src="data/mod-sprites.js" onerror="loadRemoteData(this.src)"></script> + <script src="data/mod-config.js" onerror="loadRemoteData(this.src)"></script> <script src="js/battle-dex-search.js"></script> <script src="js/search.js"></script> @@ -131,4 +131,4 @@ <h3><button class="closebutton" tabindex="-1" aria-label="Close"><i class="fa fa </script> </body> -</html> +</html> \ No newline at end of file diff --git a/pokemonshowdown.com/js/ladder.js b/pokemonshowdown.com/js/ladder.js index 48fae6671..08a2f8b09 100644 --- a/pokemonshowdown.com/js/ladder.js +++ b/pokemonshowdown.com/js/ladder.js @@ -26,17 +26,7 @@ var LadderPanel = Panels.StaticPanel.extend({ events: { 'change select[name=standing]': 'changeStanding', 'click button[name=openReset]': 'openReset', - 'click button[name=cancelReset]': 'cancelReset', - 'click button[name=copyUrl]': 'copyUrl' - }, - copyUrl: function (e) { - var button = e.currentTarget; - var textbox = button.parentElement.querySelector('textarea'); - textbox.select(); - document.execCommand("copy"); - button.textContent = 'Copied!'; - e.preventDefault(); - e.stopImmediatePropagation(); + 'click button[name=cancelReset]': 'cancelReset' }, openReset: function (e) { e.preventDefault(); @@ -80,4 +70,4 @@ var App = Panels.App.extend({ } }); -var app = new App(); +var app = new App(); \ No newline at end of file diff --git a/pokemonshowdown.com/js/panels.js b/pokemonshowdown.com/js/panels.js index e12f19a98..700d8305a 100755 --- a/pokemonshowdown.com/js/panels.js +++ b/pokemonshowdown.com/js/panels.js @@ -834,8 +834,7 @@ if (!Function.prototype.bind) { updateBackButton: function() { if (this.sourcePanel) { if (this.sourcePanel.shortTitle) { - this.$('.pfx-backbutton').html(this.app.backButtonPrefix+this.sourcePanel.shortTitle.replace(/</g, '<')); - } + this.$('.pfx-backbutton').html(this.app.backButtonPrefix+this.sourcePanel.shortTitle); } this.$('.pfx-backbutton').attr('href', this.app.root+this.sourcePanel.fragment); } },