Update to ESLint 9 (#2326)
Some checks are pending
Node.js CI / build (22.x) (push) Waiting to run

This finally removes the tslint dependency and switches to eslint.

There are a lot of other changes here, too, to bring the codebase up to
server standards. TSLint never had much in the way of indentation
enforcement.

Not very happy about eslint splitting itself up over 6 dependencies,
or its documentation over three websites, nor how poorly documented the
new flat config is, but I mean, eslint's gonna eslint. Customizing
would be even harder if we tried to use Biome or something. They mostly
seem to go full Prettier.

Also here are some changes to our style rules. In particular:

- Curly brackets (for objects etc) now have spaces inside them. Sorry
  for the huge change. ESLint doesn't support our old style, and most
  projects use Prettier style, so we might as well match them in this way.
  See https://github.com/eslint-stylistic/eslint-stylistic/issues/415

- String + number concatenation is no longer allowed (except in ES3
  code). We otherwise now consistently use template strings for this.
This commit is contained in:
Guangcong Luo 2025-02-25 20:05:32 -08:00 committed by GitHub
parent 3d4de88706
commit a10821ab8b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
68 changed files with 6892 additions and 5302 deletions

View File

@ -1,16 +0,0 @@
node_modules/
/play.pokemonshowdown.com/data/*
/play.pokemonshowdown.com/js/*
!/play.pokemonshowdown.com/js/client-battle.js
!/play.pokemonshowdown.com/js/client-chat-tournament.js
!/play.pokemonshowdown.com/js/client-chat.js
!/play.pokemonshowdown.com/js/client-ladder.js
!/play.pokemonshowdown.com/js/client-mainmenu.js
!/play.pokemonshowdown.com/js/client-rooms.js
!/play.pokemonshowdown.com/js/client-teambuilder.js
!/play.pokemonshowdown.com/js/client-topbar.js
!/play.pokemonshowdown.com/js/client.js
!/play.pokemonshowdown.com/js/replay-embed.template.js
!/play.pokemonshowdown.com/js/search.js
!/play.pokemonshowdown.com/js/storage.js

View File

@ -1,210 +0,0 @@
'use strict';
module.exports = {
"root": true,
"parserOptions": {
"ecmaVersion": 3,
"sourceType": "script"
},
"env": {
"node": true,
"browser": true
},
"globals": {
// Libraries
"_": false, "$": false, "Backbone": false, "d3": false, "html": false, "html4": false, "jQuery": false, "SockJS": false, "ColorThief": false,
// Environment-specific
"fs": false, "gui": false, "ga": false, "macgap": false, "nwWindow": false, "webkitNotifications": false, "nw": false,
// Battle stuff
"Battle": true, "Pokemon": true, "BattleSound": true, "BattleTooltips": true, "BattleLog": true,
"BattleAbilities": false, "BattleAliases": false, "BattleBackdrops": false, "BattleBackdropsFive": false, "BattleBackdropsFour": false, "BattleBackdropsThree": false, "BattleEffects": false,
"BattleFormats": false, "BattleFormatsData": false, "BattleLearnsets": false, "BattleItems": false, "BattleMoveAnims": false, "BattleMovedex": false, "BattleNatures": false,
"BattleOtherAnims": false, "BattlePokedex": false,"BattlePokemonSprites": false, "BattlePokemonSpritesBW": false, "BattleSearchCountIndex": false, "BattleSearchIndex": false, "BattleArticleTitles": false,
"BattleSearchIndexOffset": false, "BattleSearchIndexType": false, "BattleStatIDs": false, "BattleStatNames": false, "BattleStatusAnims": false, "BattleStatuses": false, "BattleTeambuilderTable": false,
"ModifiableValue": false, "BattleStatGuesser": false, "BattleStatOptimizer": false, "BattleText": true, "BattleTextAFD": false, "BattleTextNotAFD": false,
"BattleTextParser": false,
// Generic global variables
"Config": false, "BattleSearch": false, "Storage": false, "Dex": false, "DexSearch": false,
"app": false, "toID": false, "toRoomid": false, "toUserid": false, "toName": false, "PSUtils": false, "MD5": false,
"ChatHistory": false, "Topbar": false, "UserList": false,
// Rooms
"Room": false, "BattleRoom": false, "ChatRoom": false, "ConsoleRoom": false, "HTMLRoom": false, "LadderRoom": false, "MainMenuRoom": false, "RoomsRoom": false, "BattlesRoom": false, "TeambuilderRoom": false,
// Tons of popups
"Popup": false, "ForfeitPopup": false, "BracketPopup": false, "LoginPasswordPopup": false, "UserPopup": false, "UserOptionsPopup": false, "UserOptions": false, "TeamPopup": false,
"AvatarsPopup": false, "CreditsPopup": false, "FormatPopup": false, "FormattingPopup": false, "LoginPopup": false,
"MovePopup": false, "SoundsPopup": false, "OptionsPopup": false, "PromptPopup": false, "ProxyPopup": false, "ReconnectPopup": false,
"RegisterPopup": false, "ReplayUploadedPopup": false, "RulesPopup": false, "TabListPopup": false, "TournamentBox": false,
"CustomBackgroundPopup": false,
// Test client
"POKEMON_SHOWDOWN_TESTCLIENT_KEY": false
},
"extends": "eslint:recommended",
"rules": {
"no-cond-assign": ["error", "except-parens"],
"no-console": "off",
"no-constant-condition": "off",
"no-control-regex": "off",
"no-empty": ["error", {"allowEmptyCatch": true}],
"no-inner-declarations": ["error", "functions"],
"no-redeclare": "off",
"valid-jsdoc": "off",
// TODO: actually fix useless escapes
"no-useless-escape": "off",
"array-callback-return": "error",
"complexity": "off",
"consistent-return": "off",
"default-case": "off",
"dot-location": ["error", "property"],
"dot-notation": "off",
"eqeqeq": "off",
"guard-for-in": "off",
"no-caller": "error",
"no-case-declarations": "off",
"no-div-regex": "error",
"no-else-return": "off",
"no-labels": ["error", {"allowLoop": true, "allowSwitch": true}],
"no-eval": "off",
"no-implied-eval": "error",
"no-extend-native": "error",
"no-extra-bind": "warn",
"no-extra-label": "error",
"no-extra-parens": "off",
"no-implicit-coercion": "off",
"no-invalid-this": "off",
"no-iterator": "error",
"no-lone-blocks": "off",
"no-loop-func": "off",
"no-magic-numbers": "off",
"no-multi-spaces": "warn",
"no-multi-str": "error",
"no-new-func": "error",
"no-new-wrappers": "error",
"no-octal-escape": "error",
"no-param-reassign": "off",
"no-proto": "error",
"no-prototype-builtins": "error",
"no-return-assign": ["error", "except-parens"],
"no-self-compare": "error",
"no-sequences": "error",
"no-throw-literal": "error",
"no-unmodified-loop-condition": "error",
"no-unused-expressions": "error",
"no-useless-call": "error",
"no-useless-concat": "off",
"no-void": "off",
"no-warning-comments": "off",
"no-with": "error",
"radix": ["error", "always"],
"vars-on-top": "off",
"wrap-iife": ["error", "inside"],
"yoda": "off",
"init-declarations": "off",
"no-catch-shadow": "off",
"no-label-var": "error",
"no-restricted-globals": ["error", "Proxy", "Reflect", "Symbol", "WeakSet"],
"no-shadow-restricted-names": "error",
"no-shadow": "off",
"no-undef-init": "off",
"no-undef": ["error", {"typeof": true}],
"no-undefined": "off",
"no-unused-vars": "off",
"no-mixed-requires": "error",
"no-new-require": "error",
"no-path-concat": "off",
"no-process-env": "off",
"no-process-exit": "off",
"no-restricted-modules": ["error", "moment", "request", "sugar"],
"no-sync": "off",
"array-bracket-spacing": ["error", "never"],
"block-spacing": "off",
"brace-style": ["error", "1tbs", {"allowSingleLine": true}],
"camelcase": "off",
"comma-spacing": ["error", {"before": false, "after": true}],
"comma-style": ["error", "last"],
"computed-property-spacing": ["error", "never"],
"consistent-this": "off",
"func-names": "off",
"func-style": "off",
"id-length": "off",
"id-match": "off",
"indent": ["error", "tab"],
"key-spacing": "off",
"lines-around-comment": "off",
"max-nested-callbacks": "off",
"max-statements-per-line": "off",
"new-parens": "error",
"newline-after-var": "off",
"newline-before-return": "off",
"no-array-constructor": "error",
"no-continue": "off",
"no-inline-comments": "off",
"no-lonely-if": "off",
"no-mixed-spaces-and-tabs": ["error", "smart-tabs"],
"no-multiple-empty-lines": ["error", {"max": 2, "maxEOF": 1}],
"no-negated-condition": "off",
"no-nested-ternary": "off",
"no-new-object": "error",
"no-spaced-func": "error",
"no-ternary": "off",
"no-trailing-spaces": ["error", {"ignoreComments": false}],
"no-underscore-dangle": "off",
"no-unneeded-ternary": "error",
"object-curly-spacing": ["error", "never"],
"one-var": "off",
"operator-assignment": "off",
"operator-linebreak": ["error", "after"],
"quote-props": "off",
"quotes": "off",
"require-jsdoc": "off",
"semi-spacing": ["error", {"before": false, "after": true}],
"semi": ["error", "always"],
"sort-vars": "off",
"keyword-spacing": ["error", {"before": true, "after": true}],
"space-before-blocks": ["error", "always"],
"space-before-function-paren": ["error", {"anonymous": "always", "named": "never"}],
"space-in-parens": ["error", "never"],
"space-infix-ops": "error",
"space-unary-ops": ["error", {"words": true, "nonwords": false}],
"wrap-regex": "off",
"arrow-parens": ["error", "as-needed"],
"arrow-spacing": ["error", {"before": true, "after": true}],
"no-confusing-arrow": "off",
"no-useless-computed-key": "error",
"no-useless-rename": "error",
"prefer-arrow-callback": "off",
"rest-spread-spacing": ["error", "never"],
"template-curly-spacing": ["error", "never"],
"no-restricted-syntax": ["error", "TaggedTemplateExpression", "ObjectPattern", "ArrayPattern"],
// Rules enabled in the server code, but disabled here
/*
"block-scoped-var": "error",
"callback-return": [2, ["callback", "cb", "done"]],
"comma-dangle": ["error", "always-multiline"],
"curly": ["error", "multi-line", "consistent"],
"eqeqeq": "error",
"no-constant-condition": "error",
"no-floating-decimal": "error",
"no-new": "error",
"no-redeclare": "error",
"no-unused-vars": ["warn", {"args": "none"}],
"no-use-before-define": ["error", {"functions": false, "classes": false}],
"no-var": "error",
"new-cap": ["error", {"newIsCap": true, "capIsNew: false}],
"padded-blocks": ["error", "never"],
"strict": ["error", "global"]
*/
}
};

View File

@ -16,7 +16,7 @@ jobs:
strategy:
matrix:
node-version: [14.x]
node-version: [22.x]
steps:
- uses: actions/checkout@v2

View File

@ -2,6 +2,5 @@
"editor.formatOnSave": false,
"showdown.server": "", // e.g., "?~~localhost:8000"
"showdown.clientUrl": "http://localhost:8080",
"tslint.configFile": "tslint.json",
"typescript.tsdk": "node_modules/typescript/lib"
}

View File

@ -1,44 +0,0 @@
'use strict';
const baseRules = Object.assign({}, require('./../.eslintrc.js').rules);
module.exports = {
"root": true,
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "script",
"ecmaFeatures": {
"globalReturn": true
}
},
"env": {
"es6": true,
"node": true
},
"rules": Object.assign(baseRules, {
"comma-dangle": [2, "always-multiline"],
"eqeqeq": 2,
"no-floating-decimal": 2,
"no-new": 2,
"no-redeclare": 2,
"radix": [1, "as-needed"],
"strict": [2, "global"],
"no-unused-vars": [1, {"args": "none"}],
"no-use-before-define": [2, {"functions": false, "classes": false}],
"new-cap": [2, {"newIsCap": true, "capIsNew": false}],
"padded-blocks": [2, "never"],
"arrow-parens": [2, "as-needed"],
"arrow-spacing": [2, {"before": true, "after": true}],
"constructor-super": 2,
"no-class-assign": 2,
"no-confusing-arrow": 0,
"no-const-assign": 2,
"no-dupe-class-members": 2,
"no-restricted-syntax": "off",
"no-this-before-super": 2,
"no-var": 2,
"require-yield": 2,
"template-curly-spacing": [2, "never"],
}),
};

View File

@ -27,7 +27,7 @@ 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 buf.replace(/"([A-Za-z][A-Za-z0-9]*)":/g, (fullMatch, key) => (
['return', 'new', 'delete'].includes(key) ? fullMatch : `${key}:`
));
}
@ -52,10 +52,14 @@ function requireNoCache(pathSpec) {
index = index.concat(Object.keys(Dex.data.Abilities).map(x => x + ' ability'));
index = index.concat(Object.keys(Dex.data.TypeChart).map(x => toID(x) + ' type'));
index = index.concat(['physical', 'special', 'status'].map(x => toID(x) + ' category'));
index = index.concat(['monster', 'water1', 'bug', 'flying', 'field', 'fairy', 'grass', 'humanlike', 'water3', 'mineral', 'amorphous', 'water2', 'ditto', 'dragon', 'undiscovered'].map(x => toID(x) + ' egggroup'));
index = index.concat(['ou', 'uu', 'ru', 'nu', 'pu', 'zu', 'lc', 'nfe', 'uber', 'uubl', 'rubl', 'nubl', 'publ', 'zubl', 'cap', 'caplc', 'capnfe'].map(x => toID(x) + ' tier'));
index = index.concat([
'monster', 'water1', 'bug', 'flying', 'field', 'fairy', 'grass', 'humanlike', 'water3', 'mineral', 'amorphous', 'water2', 'ditto', 'dragon', 'undiscovered',
].map(x => toID(x) + ' egggroup'));
index = index.concat([
'ou', 'uu', 'ru', 'nu', 'pu', 'zu', 'lc', 'nfe', 'uber', 'uubl', 'rubl', 'nubl', 'publ', 'zubl', 'cap', 'caplc', 'capnfe',
].map(x => toID(x) + ' tier'));
let BattleArticleTitles = {};
const BattleArticleTitles = {};
try {
for (const file of fs.readdirSync('../dex.pokemonshowdown.com/articles/')) {
@ -71,7 +75,7 @@ function requireNoCache(pathSpec) {
index.push('' + id + ' article');
}
}
} catch (e) {
} catch {
console.log('\n(WARNING: NO ARTICLES)');
}
index.push('pokemon article');
@ -79,8 +83,8 @@ function requireNoCache(pathSpec) {
// generate aliases
function generateAlias(id, name, type) {
let i = name.lastIndexOf(' ');
if (i < 0) i = name.lastIndexOf('-');
let offset = name.lastIndexOf(' ');
if (offset < 0) offset = name.lastIndexOf('-');
if (name.endsWith('-Mega-X') || name.endsWith('-Mega-Y')) {
index.push('mega' + toID(name.slice(0, -7) + name.slice(-1)) + ' ' + type + ' ' + id + ' 0');
index.push('m' + toID(name.slice(0, -7) + name.slice(-1)) + ' ' + type + ' ' + id + ' 0');
@ -96,86 +100,86 @@ function requireNoCache(pathSpec) {
index.push('alolan' + toID(name.slice(0, -6)) + ' ' + type + ' ' + id + ' 0');
return;
}
let oldI = i;
if (name === 'Alakazam') i = 5;
if (name === 'Arctovish') i = 5;
if (name === 'Arctozolt') i = 5;
if (name === 'Articuno') i = 5;
if (name === 'Breloom') i = 3;
if (name === 'Bronzong') i = 4;
if (name === 'Celebi') i = 4;
if (name === 'Charizard') i = 5;
if (name === 'Donphan') i = 3;
if (name === 'Dracovish') i = 5;
if (name === 'Dracozolt') i = 5;
if (name === 'Dragapult') i = 5;
if (name === 'Dusclops') i = 3;
if (name === 'Electabuzz') i = 6;
if (name === 'Exeggutor') i = 2;
if (name === 'Garchomp') i = 3;
if (name === 'Hariyama') i = 4;
if (name === 'Magearna') i = 2;
if (name === 'Magnezone') i = 5;
if (name === 'Mamoswine') i = 4;
if (name === 'Moltres') i = 3;
if (name === 'Nidoking') i = 4;
if (name === 'Nidoqueen') i = 4;
if (name === 'Nidorina') i = 4;
if (name === 'Nidorino') i = 4;
if (name === 'Regice') i = 3;
if (name === 'Regidrago') i = 4;
if (name === 'Regieleki') i = 4;
if (name === 'Regigigas') i = 4;
if (name === 'Regirock') i = 4;
if (name === 'Registeel') i = 4;
if (name === 'Slowbro') i = 4;
if (name === 'Slowking') i = 4;
if (name === 'Starmie') i = 4;
if (name === 'Tyranitar') i = 6;
if (name === 'Zapdos') i = 3;
const oldOffset = offset;
if (name === 'Alakazam') offset = 5;
if (name === 'Arctovish') offset = 5;
if (name === 'Arctozolt') offset = 5;
if (name === 'Articuno') offset = 5;
if (name === 'Breloom') offset = 3;
if (name === 'Bronzong') offset = 4;
if (name === 'Celebi') offset = 4;
if (name === 'Charizard') offset = 5;
if (name === 'Donphan') offset = 3;
if (name === 'Dracovish') offset = 5;
if (name === 'Dracozolt') offset = 5;
if (name === 'Dragapult') offset = 5;
if (name === 'Dusclops') offset = 3;
if (name === 'Electabuzz') offset = 6;
if (name === 'Exeggutor') offset = 2;
if (name === 'Garchomp') offset = 3;
if (name === 'Hariyama') offset = 4;
if (name === 'Magearna') offset = 2;
if (name === 'Magnezone') offset = 5;
if (name === 'Mamoswine') offset = 4;
if (name === 'Moltres') offset = 3;
if (name === 'Nidoking') offset = 4;
if (name === 'Nidoqueen') offset = 4;
if (name === 'Nidorina') offset = 4;
if (name === 'Nidorino') offset = 4;
if (name === 'Regice') offset = 3;
if (name === 'Regidrago') offset = 4;
if (name === 'Regieleki') offset = 4;
if (name === 'Regigigas') offset = 4;
if (name === 'Regirock') offset = 4;
if (name === 'Registeel') offset = 4;
if (name === 'Slowbro') offset = 4;
if (name === 'Slowking') offset = 4;
if (name === 'Starmie') offset = 4;
if (name === 'Tyranitar') offset = 6;
if (name === 'Zapdos') offset = 3;
if (name === 'Acupressure') i = 3;
if (name === 'Aromatherapy') i = 5;
if (name === 'Boomburst') i = 4;
if (name === 'Crabhammer') i = 4;
if (name === 'Discharge') i = 3;
if (name === 'Earthquake') i = 5;
if (name === 'Extrasensory') i = 5;
if (name === 'Flamethrower') i = 5;
if (name === 'Headbutt') i = 4;
if (name === 'Moonblast') i = 4;
if (name === 'Moonlight') i = 4;
if (name === 'Overheat') i = 4;
if (name === 'Outrage') i = 3;
if (name === 'Octazooka') i = 4;
if (name === 'Payback') i = 3;
if (name === 'Psyshock') i = 3;
if (name === 'Psywave') i = 3;
if (name === 'Rototiller') i = 4;
if (name === 'Rollout') i = 4;
if (name === 'Safeguard') i = 4;
if (name === 'Sandstorm') i = 4;
if (name === 'Smokescreen') i = 5;
if (name === 'Stockpile') i = 5;
if (name === 'Steamroller') i = 5;
if (name === 'Superpower') i = 5;
if (name === 'Supersonic') i = 5;
if (name === 'Synchronoise') i = 7;
if (name === 'Tailwind') i = 4;
if (name === 'Telekinesis') i = 4;
if (name === 'Teleport') i = 4;
if (name === 'Thunderbolt') i = 7;
if (name === 'Twineedle') i = 3;
if (name === 'Uproar') i = 2;
if (name === 'Venoshock') i = 4;
if (name === 'Whirlpool') i = 5;
if (name === 'Whirlwind') i = 5;
if (name === 'Acupressure') offset = 3;
if (name === 'Aromatherapy') offset = 5;
if (name === 'Boomburst') offset = 4;
if (name === 'Crabhammer') offset = 4;
if (name === 'Discharge') offset = 3;
if (name === 'Earthquake') offset = 5;
if (name === 'Extrasensory') offset = 5;
if (name === 'Flamethrower') offset = 5;
if (name === 'Headbutt') offset = 4;
if (name === 'Moonblast') offset = 4;
if (name === 'Moonlight') offset = 4;
if (name === 'Overheat') offset = 4;
if (name === 'Outrage') offset = 3;
if (name === 'Octazooka') offset = 4;
if (name === 'Payback') offset = 3;
if (name === 'Psyshock') offset = 3;
if (name === 'Psywave') offset = 3;
if (name === 'Rototiller') offset = 4;
if (name === 'Rollout') offset = 4;
if (name === 'Safeguard') offset = 4;
if (name === 'Sandstorm') offset = 4;
if (name === 'Smokescreen') offset = 5;
if (name === 'Stockpile') offset = 5;
if (name === 'Steamroller') offset = 5;
if (name === 'Superpower') offset = 5;
if (name === 'Supersonic') offset = 5;
if (name === 'Synchronoise') offset = 7;
if (name === 'Tailwind') offset = 4;
if (name === 'Telekinesis') offset = 4;
if (name === 'Teleport') offset = 4;
if (name === 'Thunderbolt') offset = 7;
if (name === 'Twineedle') offset = 3;
if (name === 'Uproar') offset = 2;
if (name === 'Venoshock') offset = 4;
if (name === 'Whirlpool') offset = 5;
if (name === 'Whirlwind') offset = 5;
let acronym;
if (oldI < 0 && i > 0) {
acronym = toID(name.charAt(0) + name.slice(i));
if (oldOffset < 0 && offset > 0) {
acronym = toID(name.charAt(0) + name.slice(offset));
}
if (i < 0) return;
index.push('' + toID(name.slice(i)) + ' ' + type + ' ' + id + ' ' + toID(name.slice(0, i)).length);
if (offset < 0) return;
index.push('' + toID(name.slice(offset)) + ' ' + type + ' ' + id + ' ' + toID(name.slice(0, offset)).length);
if (name.startsWith('Hidden Power ')) {
acronym = 'hp' + toID(name.substr(13));
index.push('' + acronym + ' ' + type + ' ' + id + ' 0');
@ -200,8 +204,8 @@ function requireNoCache(pathSpec) {
index.push('cuno ' + type + ' ' + id + ' 4');
}
let i2 = name.lastIndexOf(' ', i - 1);
if (i2 < 0) i2 = name.lastIndexOf('-', i - 1);
let i2 = name.lastIndexOf(' ', offset - 1);
if (i2 < 0) i2 = name.lastIndexOf('-', offset - 1);
if (name === 'Zen Headbutt') i2 = 8;
if (i2 >= 0) {
index.push('' + toID(name.slice(i2)) + ' ' + type + ' ' + id + ' ' + toID(name.slice(0, i2)).length);
@ -248,8 +252,7 @@ function requireNoCache(pathSpec) {
index[index.indexOf('ditto pokemon')] = 'ditto egggroup';
index[index.indexOf('ditto egggroup')] = 'ditto pokemon';
let BattleSearchIndex = index.map(x => {
const BattleSearchIndex = index.map(x => {
x = x.split(' ');
if (x.length > 3) {
x[3] = Number(x[3]);
@ -258,7 +261,7 @@ function requireNoCache(pathSpec) {
return x;
});
let BattleSearchIndexOffset = BattleSearchIndex.map((entry, i) => {
const BattleSearchIndexOffset = BattleSearchIndex.map(entry => {
const id = entry[0];
let name = '';
switch (entry[1]) {
@ -281,13 +284,15 @@ function requireNoCache(pathSpec) {
return '';
});
let BattleSearchCountIndex = {};
const BattleSearchCountIndex = {};
for (const type in Dex.data.TypeChart) {
BattleSearchCountIndex[type + ' move'] = Object.keys(Dex.data.Moves).filter(id => (Dex.data.Moves[id].type === type)).length;
BattleSearchCountIndex[type + ' move'] = Object.keys(Dex.data.Moves)
.filter(id => (Dex.data.Moves[id].type === type)).length;
}
for (const type in Dex.data.TypeChart) {
BattleSearchCountIndex[type + ' pokemon'] = Object.keys(Dex.data.Pokedex).filter(id => (Dex.data.Pokedex[id].types.indexOf(type) >= 0)).length;
BattleSearchCountIndex[type + ' pokemon'] = Object.keys(Dex.data.Pokedex)
.filter(id => (Dex.data.Pokedex[id].types.indexOf(type) >= 0)).length;
}
let buf = '// DO NOT EDIT - automatically built with build-tools/build-indexes\n\n';
@ -369,7 +374,7 @@ process.stdout.write("Building `data/teambuilder-tables.js`... ");
const species = Dex.mod(gen).species.get(id);
const baseSpecies = Dex.mod(gen).species.get(species.baseSpecies);
if (species.gen > genNum) continue;
const tier = (() => {
const speciesTier = (() => {
if (isMetBattle) {
let tier = species.tier;
if (species.isNonstandard) {
@ -416,7 +421,7 @@ process.stdout.write("Building `data/teambuilder-tables.js`... ");
return tier;
}
if (isLetsGo) {
let validNum = (baseSpecies.num <= 151 && species.num >= 1) || [808, 809].includes(baseSpecies.num);
const validNum = (baseSpecies.num <= 151 && species.num >= 1) || [808, 809].includes(baseSpecies.num);
if (!validNum) return 'Illegal';
if (species.forme && !['Alola', 'Mega', 'Mega-X', 'Mega-Y', 'Starter'].includes(species.forme)) return 'Illegal';
if (species.name === 'Pikachu-Alola') return 'Illegal';
@ -455,7 +460,7 @@ process.stdout.write("Building `data/teambuilder-tables.js`... ");
}
return species.tier;
})();
overrideTier[species.id] = tier;
overrideTier[species.id] = speciesTier;
if (species.forme) {
if (
[
@ -467,8 +472,8 @@ process.stdout.write("Building `data/teambuilder-tables.js`... ");
}
}
if (!tierTable[tier]) tierTable[tier] = [];
tierTable[tier].push(id);
if (!tierTable[speciesTier]) tierTable[speciesTier] = [];
tierTable[speciesTier].push(id);
if (genNum === 9) {
const ubersUU = Dex.formats.get(gen + 'ubersuu');
@ -610,11 +615,16 @@ process.stdout.write("Building `data/teambuilder-tables.js`... ");
if (gen === 'gen4') {
return ["CAP", "CAP NFE", "CAP LC", "AG", "Uber", "OU", "(OU)", "UUBL", "UU", "NUBL", "NU", "NFE", "LC"];
}
return ["CAP", "CAP NFE", "CAP LC", "AG", "Uber", "(Uber)", "OU", "(OU)", "UUBL", "UU", "RUBL", "RU", "NUBL", "NU", "PUBL", "PU", "ZUBL", "ZU", "New", "NFE", "LC", "Unreleased"];
return [
"CAP", "CAP NFE", "CAP LC", "AG", "Uber", "(Uber)", "OU", "(OU)", "UUBL", "UU", "RUBL", "RU", "NUBL", "NU", "PUBL", "PU", "ZUBL", "ZU", "New", "NFE", "LC", "Unreleased",
];
})();
for (const tier of tierOrder) {
if (tier in {OU:1, AG:1, Uber:1, UU:1, RU:1, NU:1, PU:1, ZU: 1, NFE:1, LC:1, DOU:1, DUU:1, "(DUU)":1, New:1, Legal:1, Regular:1, "Restricted Legendary":1, "CAP LC":1}) {
if (tier in {
OU: 1, AG: 1, Uber: 1, UU: 1, RU: 1, NU: 1, PU: 1, ZU: 1, NFE: 1, LC: 1, DOU: 1, DUU: 1,
"(DUU)": 1, New: 1, Legal: 1, Regular: 1, "Restricted Legendary": 1, "CAP LC": 1,
}) {
let usedTier = tier;
if (usedTier === "(DUU)") usedTier = "DNU";
formatSlices[usedTier] = tiers.length;
@ -907,7 +917,7 @@ process.stdout.write("Building `data/teambuilder-tables.js`... ");
}
if (available) available = !gen4HMs.has(moveid);
let minUpperGen = available ? 5 : Math.min(
const minUpperGen = available ? 5 : Math.min(
...gens.filter(gen => gen > 4)
);
legalGens += '0123456789'.slice(minUpperGen);
@ -1001,7 +1011,7 @@ process.stdout.write("Building `data/teambuilder-tables.js`... ");
}
if (available) available = !gen4HMs.has(moveid);
let minUpperGen = available ? 5 : Math.min(
const minUpperGen = available ? 5 : Math.min(
...gens.filter(gen => gen > 4)
);
legalGens += '012345678'.slice(minUpperGen);
@ -1044,7 +1054,7 @@ process.stdout.write("Building `data/teambuilder-tables.js`... ");
}
if (available) available = !gen4HMs.has(moveid);
let minUpperGen = available ? 5 : Math.min(
const minUpperGen = available ? 5 : Math.min(
...gens.filter(gen => gen > 4)
);
legalGens += '0123456789'.slice(minUpperGen);
@ -1090,7 +1100,7 @@ process.stdout.write("Building `data/teambuilder-tables.js`... ");
}
if (available) available = !gen4HMs.has(moveid);
let minUpperGen = available ? 5 : Math.min(
const minUpperGen = available ? 5 : Math.min(
...gens.filter(gen => gen > 4)
);
legalGens += '0123456789'.slice(minUpperGen);
@ -1113,8 +1123,12 @@ 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 overrideSpeciesKeys = [
'abilities', 'baseStats', 'cosmeticFormes', 'isNonstandard', 'requiredItems', 'types', 'unreleasedHidden',
];
const overrideMoveKeys = [
'accuracy', 'basePower', 'category', 'desc', 'flags', 'isNonstandard', 'pp', 'priority', 'shortDesc', 'target', 'type',
];
const overrideAbilityKeys = ['desc', 'flags', 'isNonstandard', 'rating', 'shortDesc'];
const overrideItemKeys = ['desc', 'fling', 'isNonstandard', 'naturalGift', 'shortDesc'];

View File

@ -57,7 +57,7 @@ function updateLearnsets(callback) {
}
const buf = [];
const pokemonList = Object.keys(Pokedex).map(speciesId => Pokedex[speciesId]).sort(function (a, b) {
const pokemonList = Object.keys(Pokedex).map(speciesId => Pokedex[speciesId]).sort((a, b) => {
// Missingno. goes first (zeroth); afterwards, CAP in descending dex order (increasingly negative)
// Finally, standard Pokémon in ascending dex order
if (a.num <= 0 && b.num > 0) return -1;
@ -89,11 +89,9 @@ try {
// It doesn't exist currently, but it will by the end of the script execution.
// Any other error is unacceptable and will throw.
}
try {
{
updateStats = fs.statSync(thisFile);
updateMTime = updateStats.mtime.getTime();
} catch (err) {
throw err; // !!
}
// update learnsets-g6
@ -105,7 +103,7 @@ let learnsetsG6ToUpdate = true;
try {
learnsetsStats = fs.statSync(path.join(rootDir, 'data', 'learnsets.js'));
} catch (err) {
} catch {
// Couldn't find learnsets.js, but that's not the end of the world: skip to next task.
console.error("Couldn't find `data/learnsets.js`. Task aborted.");
learnsetsG6ToUpdate = false;
@ -124,10 +122,13 @@ if (learnsetsG6ToUpdate) {
}
}
if (learnsetsG6ToUpdate && (!indexStats || !learnsetsG6Stats || indexMTime < updateMTime || indexMTime < learnsetsStats.mtime.getTime() || indexMTime < learnsetsG6Stats.mtime.getTime())) {
if (learnsetsG6ToUpdate && (
!indexStats || !learnsetsG6Stats || indexMTime < updateMTime || indexMTime < learnsetsStats.mtime.getTime() ||
indexMTime < learnsetsG6Stats.mtime.getTime()
)) {
// Only merge learnsets.js with learnsets-g6.js if any of those files, or this one, have been modified recently (or if we don't know what "recently" means)
updateLearnsets(function (err) {
updateLearnsets(err => {
if (err) {
let stack = err.stack || '';
stack = "File `data/learnsets-g6` failed to update.\n" + stack;

View File

@ -21,7 +21,6 @@ exports.BattlePokemonSprites = {
substitute:{exists:false, front:{w:34, h:39}, back:{w:37, h:38}},
`;
let g5buf = `/*
DO NOT EDIT
@ -31,26 +30,26 @@ THIS FILE IS AUTOGENERATED BY ./build-tools/build-minidex
exports.BattlePokemonSpritesBW = {
`;
function sizeObj(path) {
function sizeObj(objPath) {
try {
let size = imageSize(path);
const size = imageSize(objPath);
return {
w: size.width,
h: size.height,
};
} catch (e) {}
} catch {}
}
function updateSizes() {
for (let baseid in Dex.data.Pokedex) {
let species = Dex.species.get(baseid);
for (let formeName of [''].concat(species.cosmeticFormes || [])) {
for (const baseid in Dex.data.Pokedex) {
const species = Dex.species.get(baseid);
for (const formeName of [''].concat(species.cosmeticFormes || [])) {
let spriteid = species.spriteid;
if (formeName) spriteid += '-' + toID(formeName).slice(species.id.length);
let id = toID(spriteid);
const id = toID(spriteid);
{
let row = {num: species.num};
const row = { num: species.num };
const frontSize = sizeObj('sprites/ani/' + spriteid + '.gif');
if (frontSize) row.front = frontSize;
const frontSizeF = sizeObj('sprites/ani/' + spriteid + '-f.gif');
@ -65,7 +64,7 @@ function updateSizes() {
}
{
let g5row = {num: species.num};
const g5row = { num: species.num };
const frontSize = sizeObj('sprites/gen5ani/' + spriteid + '.gif');
if (frontSize) g5row.front = frontSize;
const frontSizeF = sizeObj('sprites/gen5ani/' + spriteid + '-f.gif');
@ -99,6 +98,6 @@ if (fs.existsSync('sprites/ani/')) {
try {
fs.unlinkSync('data/pokedex-mini.js');
fs.unlinkSync('data/pokedex-mini-bw.js');
} catch (e) {}
} catch {}
console.log('SKIPPED');
}

View File

@ -35,15 +35,16 @@ function outputFileSync(filePath, res, opts) {
fs.writeFileSync(filePath, res.code);
}
function slash(path) {
const isExtendedLengthPath = /^\\\\\?\\/.test(path);
const hasNonAscii = /[^\u0000-\u0080]+/.test(path);
function slash(filePath) {
const isExtendedLengthPath = /^\\\\\?\\/.test(filePath);
// eslint-disable-next-line no-control-regex
const hasNonAscii = /[^\u0000-\u0080]+/.test(filePath);
if (isExtendedLengthPath || hasNonAscii) {
return path;
return filePath;
}
return path.replace(/\\/g, '/');
return filePath.replace(/\\/g, '/');
}
async function combineResults(fileResults, sourceMapOptions, opts) {
@ -64,7 +65,7 @@ async function combineResults(fileResults, sourceMapOptions, opts) {
const consumer = await new sourceMap.SourceMapConsumer(result.map);
const sources = new Set();
consumer.eachMapping(function (mapping) {
consumer.eachMapping(mapping => {
if (mapping.source != null) sources.add(mapping.source);
map.addMapping({
@ -74,9 +75,8 @@ async function combineResults(fileResults, sourceMapOptions, opts) {
},
source: mapping.source,
original:
mapping.source == null
? null
: {
mapping.source == null ?
null : {
line: mapping.originalLine,
column: mapping.originalColumn,
},
@ -100,10 +100,7 @@ async function combineResults(fileResults, sourceMapOptions, opts) {
code += "\n//# sourceMappingURL=data:application/json;charset=utf-8;base64," + base64;
}
return {
map: map,
code: code,
};
return { map, code };
}
function noRebuildNeeded(src, dest) {
@ -112,7 +109,7 @@ function noRebuildNeeded(src, dest) {
if (!srcStat) return true;
const destStat = fs.statSync(dest);
if (srcStat.ctimeMs < destStat.ctimeMs) return true;
} catch (e) {}
} catch {}
return false;
}

View File

@ -3,7 +3,3 @@ cd "$(dirname "$0")"
cd ..
cp config/config.js config/config.js.old
cp config/head-custom.html config/head-custom.html.old
mv -f config/config-test.js config/config.js
mv -f config/head-custom-test.html config/head-custom.html
mv -i config/config.js.old config/config-test.js
mv -i config/head-custom.html.old config/head-custom-test.html

View File

@ -20,9 +20,10 @@ process.chdir(rootDir);
const AUTOCONFIG_START = '/*** Begin automatically generated configuration ***/';
const AUTOCONFIG_END = '/*** End automatically generated configuration ***/';
const UTF8 = { encoding: 'utf8' };
function escapeRegex(string) {
return string.replace(/[\/\*\.]/g, '\\$&');
return string.replace(/[/*.]/g, '\\$&');
}
/*********************************************************
@ -42,7 +43,7 @@ try {
});
const origin = ('' + commit).trim();
version += ` (${head.slice(0, 8)}${head !== origin ? `/${origin.slice(0, 8)}` : ''})`;
} catch (e) {}
} catch {}
const routes = JSON.parse(fs.readFileSync('config/routes.json'));
const autoconfigRegex = new RegExp(`${escapeRegex(AUTOCONFIG_START)}[^]+${escapeRegex(AUTOCONFIG_END)}`);
@ -59,7 +60,7 @@ Config.routes = {
${AUTOCONFIG_END}`;
// remove old automatically generated configuration and add the new one
let configBuf = fs.readFileSync('config/config.js', {encoding: 'utf8'});
let configBuf = fs.readFileSync('config/config.js', UTF8);
if (autoconfigRegex.test(configBuf)) {
configBuf = configBuf.replace(autoconfigRegex, autoconfig);
} else {
@ -74,11 +75,12 @@ console.log("DONE");
process.stdout.write("Compiling TS files... ");
let compileStartTime = process.hrtime();
const compileStartTime = process.hrtime();
let compiledFiles = 0;
// Babel can't find babelrc if we try to compile stuff in caches/pokemon-showdown/ fsr
let compileOpts = Object.assign(eval('(' + fs.readFileSync('.babelrc') + ')'), {
// eslint-disable-next-line no-eval
const compileOpts = Object.assign(eval('(' + fs.readFileSync('.babelrc') + ')'), {
babelrc: false,
incremental: true,
ignore: ['play.pokemonshowdown.com/src/battle-animations.js', 'play.pokemonshowdown.com/src/battle-animations-moves.js'],
@ -95,7 +97,7 @@ if (process.argv[2] === 'full') {
fs.statSync('play.pokemonshowdown.com/data/graphics.js');
// graphics.js exists, recompile it
delete compileOpts.ignore;
} catch (e) {}
} catch {}
}
compiledFiles += compiler.compileToDir(`play.pokemonshowdown.com/src`, `play.pokemonshowdown.com/js`, compileOpts);
@ -148,18 +150,18 @@ function addCachebuster(_, attr, url, urlQuery) {
let hash = Math.random(); // just in case creating the hash fails
try {
const filename = url.slice(1).replace('/' + routes.client + '/', '');
const fstr = fs.readFileSync(filename, {encoding: 'utf8'});
const fstr = fs.readFileSync(filename, UTF8);
hash = crypto.createHash('md5').update(fstr).digest('hex').substr(0, 8);
} catch (e) {}
} catch {}
return attr + '="' + url + '?' + hash + '"';
} else {
// hardcoded to Replays rn; TODO: generalize
let hash;
try {
const fstr = fs.readFileSync('replay.pokemonshowdown.com/' + url, {encoding: 'utf8'});
const fstr = fs.readFileSync('replay.pokemonshowdown.com/' + url, UTF8);
hash = crypto.createHash('md5').update(fstr).digest('hex').substr(0, 8);
} catch (e) {}
} catch {}
return attr + '="' + url + '?' + (hash || 'v1') + '"';
}
@ -169,13 +171,13 @@ function addCachebuster(_, attr, url, urlQuery) {
}
// add hashes to js and css files and rewrite URLs
let indexContents = fs.readFileSync('play.pokemonshowdown.com/index.template.html', {encoding: 'utf8'});
let indexContents = fs.readFileSync('play.pokemonshowdown.com/index.template.html', UTF8);
indexContents = indexContents.replace(URL_REGEX, addCachebuster);
let preactIndexContents = fs.readFileSync('play.pokemonshowdown.com/preactalpha.template.html', {encoding: 'utf8'});
let preactIndexContents = fs.readFileSync('play.pokemonshowdown.com/preactalpha.template.html', UTF8);
preactIndexContents = preactIndexContents.replace(URL_REGEX, addCachebuster);
let crossprotocolContents = fs.readFileSync('play.pokemonshowdown.com/crossprotocol.template.html', {encoding: 'utf8'});
let crossprotocolContents = fs.readFileSync('play.pokemonshowdown.com/crossprotocol.template.html', UTF8);
crossprotocolContents = crossprotocolContents.replace(URL_REGEX, addCachebuster);
let replayEmbedContents = fs.readFileSync('play.pokemonshowdown.com/js/replay-embed.template.js', {encoding: 'utf8'});
let replayEmbedContents = fs.readFileSync('play.pokemonshowdown.com/js/replay-embed.template.js', UTF8);
replayEmbedContents = replayEmbedContents.replace(/play\.pokemonshowdown\.com/g, routes.client);
// add news, only if it's actually likely to exist
@ -199,11 +201,11 @@ indexContents = indexContents.replace(/<!-- news -->/g, news);
let indexContents2 = '';
try {
let indexContentsOld = indexContents;
const 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) {}
indexContents2 = indexContentsOld
.replace(/<!-- head custom -->/g, '' + fs.readFileSync('config/head-custom-test.html'));
} catch {}
fs.writeFileSync('play.pokemonshowdown.com/index.html', indexContents);
if (indexContents2) {
@ -213,7 +215,7 @@ fs.writeFileSync('play.pokemonshowdown.com/preactalpha.html', preactIndexContent
fs.writeFileSync('play.pokemonshowdown.com/crossprotocol.html', crossprotocolContents);
fs.writeFileSync('play.pokemonshowdown.com/js/replay-embed.js', replayEmbedContents);
let replaysContents = fs.readFileSync('replay.pokemonshowdown.com/index.template.php', {encoding: 'utf8'});
let replaysContents = fs.readFileSync('replay.pokemonshowdown.com/index.template.php', UTF8);
replaysContents = replaysContents.replace(URL_REGEX, addCachebuster);
fs.writeFileSync('replay.pokemonshowdown.com/index.php', replaysContents);

View File

@ -6,4 +6,4 @@ This directory is for caches. Everything here should be safe to delete.
Things cached here:
- `pokemon-showdown` a checkout of the server repo, used in the build process (mostly for stuff in `play.pokemonshowdown.com/data/`)
- `eslint-*.json` eslint cache files
- `eslintcache.json` eslint cache files

View File

@ -5,7 +5,7 @@ var Config = Config || {};
Config.bannedHosts = ['cool.jit.su', 'pokeball-nixonserver.rhcloud.com'];
Config.whitelist = [
'wikipedia.org',
'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

435
eslint-ps-standard.mjs Normal file
View File

@ -0,0 +1,435 @@
/**
* Pokemon Showdown standard style
*
* This is Showdown's shared ESLint configuration. Each project overrides
* at least a little of it here and there, but these are the rules we use
* unless there's a good reason otherwise.
*/
// @ts-check
import eslint from '@eslint/js';
import globals from 'globals';
import tseslint from 'typescript-eslint';
import stylistic from '@stylistic/eslint-plugin';
/** @typedef {import('typescript-eslint').Config} ConfigFile */
/** @typedef {Awaited<ConfigFile>[number]} Config */
/** @typedef {NonNullable<Config['rules']>} Rules */
export { eslint, globals, tseslint, stylistic };
/** @type {Config} */
export const plugin = {
plugins: {
'@stylistic': stylistic,
'@typescript-eslint': tseslint.plugin,
},
};
/** @type {typeof tseslint.config} */
export const configure = (...args) => [
plugin,
...tseslint.config(...args),
];
/** @type {NonNullable<Config['rules']>} */
export const defaultRules = {
...stylistic.configs.customize({
braceStyle: '1tbs',
indent: 'tab',
semi: true,
jsx: true,
// ...
}).rules,
// TODO rules to revisit
// =====================
// nice to have but we mostly know && || precedence so not urgent to fix
"@stylistic/no-mixed-operators": "off",
// test only (should never be committed, but useful when testing)
// ==============================================================
// do we want unused args/destructures to start with _? unsure
"no-unused-vars": ["warn", {
args: "all",
argsIgnorePattern: ".",
caughtErrors: "all",
destructuredArrayIgnorePattern: ".",
ignoreRestSiblings: true,
}],
// "no-unused-vars": ["warn", {
// args: "all",
// argsIgnorePattern: "^_",
// caughtErrors: "all",
// destructuredArrayIgnorePattern: "^_",
// ignoreRestSiblings: true
// }],
"@stylistic/max-len": ["warn", {
"code": 120, "tabWidth": 0,
// DO NOT EDIT DIRECTLY: see bottom of file for source
"ignorePattern": "^\\s*(?:\\/\\/ \\s*)?(?:(?:export )?(?:let |const |readonly )?[a-zA-Z0-9_$.]+(?: \\+?=>? )|[a-zA-Z0-9$]+: \\[?|(?:return |throw )?(?:new )?(?:[a-zA-Z0-9$.]+\\()?)?(?:Utils\\.html|(?:this\\.)?(?:room\\.)?tr|\\$\\()?['\"`/]",
}],
"prefer-const": ["warn", { "destructuring": "all" }],
// PS code (code specific to PS)
// =============================
"@stylistic/new-parens": "off", // used for the `new class {...}` pattern
"no-prototype-builtins": "off",
// defaults too strict
// ===================
"no-empty": ["error", { "allowEmptyCatch": true }],
"no-case-declarations": "off",
// probably bugs
// =============
"array-callback-return": "error",
"no-constructor-return": "error",
"no-dupe-class-members": "error",
"no-extend-native": "error",
"no-extra-bind": "warn",
"no-extra-label": "warn",
"no-eval": "error",
"no-implied-eval": "error",
"no-inner-declarations": ["error", "functions"],
"no-iterator": "error",
"no-fallthrough": ["error", { allowEmptyCase: true, reportUnusedFallthroughComment: true }],
"no-promise-executor-return": ["error", { allowVoid: true }],
"no-return-assign": "error",
"no-self-compare": "error",
"no-sequences": "error",
"no-shadow": "error",
"no-template-curly-in-string": "error",
"no-throw-literal": "warn",
"no-unmodified-loop-condition": "error",
// best way to read first key of object
// "no-unreachable-loop": "error",
// ternary is used to convert callbacks to Promises
// tagged templates are used for the SQL library
"no-unused-expressions": ["error", { allowTernary: true, allowTaggedTemplates: true, enforceForJSX: true }],
"no-useless-call": "error",
// "no-useless-assignment": "error",
"require-atomic-updates": "error",
// syntax style (local syntactical, usually autofixable formatting decisions)
// ===========================================================================
"@stylistic/member-delimiter-style": ["error", {
multiline: { delimiter: "comma", requireLast: true },
singleline: { delimiter: "comma", requireLast: false },
overrides: { interface: {
multiline: { delimiter: "semi", requireLast: true },
singleline: { delimiter: "semi", requireLast: false },
} },
}],
"default-case-last": "error",
"eqeqeq": ["error", "always", { null: "ignore" }],
"no-array-constructor": "error",
"no-duplicate-imports": "error",
"no-implicit-coercion": ["error", { allow: ["!!", "+"] }],
"no-multi-str": "error",
"no-object-constructor": "error",
"no-proto": "error",
"no-unneeded-ternary": "error",
"no-useless-computed-key": "error",
"no-useless-constructor": "error",
"no-useless-rename": "error",
"no-useless-return": "error",
"no-var": "error",
"object-shorthand": ["error", "always"],
"operator-assignment": ["error", "always"],
"prefer-arrow-callback": "error",
"prefer-exponentiation-operator": "error",
"prefer-numeric-literals": "error",
"prefer-object-has-own": "error",
"prefer-object-spread": "error",
"prefer-promise-reject-errors": "error",
"prefer-regex-literals": "error",
"prefer-rest-params": "error",
"prefer-spread": "error",
"radix": ["error", "as-needed"],
// syntax style, overriding base
// =============================
"@stylistic/quotes": "off",
"@stylistic/quote-props": "off",
"@stylistic/function-call-spacing": "error",
"@stylistic/arrow-parens": ["error", "as-needed"],
"@stylistic/comma-dangle": ["error", {
"arrays": "always-multiline",
"objects": "always-multiline",
"imports": "always-multiline",
"exports": "always-multiline",
"functions": "never",
"importAttributes": "always-multiline",
"dynamicImports": "always-multiline",
"enums": "always-multiline",
"generics": "always-multiline",
"tuples": "always-multiline",
}],
"@stylistic/jsx-wrap-multilines": "off",
"@stylistic/jsx-closing-bracket-location": ["error", "line-aligned"],
// "@stylistic/jsx-closing-tag-location": ["error", "line-aligned"],
"@stylistic/jsx-closing-tag-location": "off",
"@stylistic/jsx-one-expression-per-line": "off",
"@stylistic/jsx-max-props-per-line": "off",
"@stylistic/jsx-function-call-newline": "off",
"no-restricted-syntax": ["error",
{ selector: "CallExpression[callee.name='Symbol']", message: "Annoying to serialize, just use a string" },
],
// whitespace
// ==========
"@stylistic/block-spacing": "error",
"@stylistic/operator-linebreak": ["error", "after"],
"@stylistic/max-statements-per-line": ["error", { max: 3, ignoredNodes: ['BreakStatement'] }],
"@stylistic/lines-between-class-members": "off",
"@stylistic/multiline-ternary": "off",
"@stylistic/object-curly-spacing": ["error", "always"],
"@stylistic/indent": ["error", "tab", { "flatTernaryExpressions": true }],
};
/** @type {NonNullable<Config['rules']>} */
export const defaultRulesTS = {
...defaultRules,
// TODO: revisit
// we should do this someday but it'd have to be a gradual manual process
// "@typescript-eslint/explicit-module-boundary-types": "off",
// like above but slightly harder, so do that one first
// "@typescript-eslint/explicit-function-return-type": "off",
// probably we should settle on a standard someday
// "@typescript-eslint/member-ordering": "off",
// "@typescript-eslint/no-extraneous-class": "error",
// maybe we should consider this
"@typescript-eslint/consistent-indexed-object-style": "off",
// typescript-eslint specific
// ==========================
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": defaultRules["no-unused-vars"],
"no-shadow": "off",
"@typescript-eslint/no-shadow": defaultRules["no-shadow"],
"no-dupe-class-members": "off",
"@typescript-eslint/no-dupe-class-members": defaultRules["no-dupe-class-members"],
"no-unused-expressions": "off",
"@typescript-eslint/no-unused-expressions": defaultRules["no-unused-expressions"],
// defaults too strict
// ===================
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-non-null-assertion": "off",
// probably bugs
// =============
"@typescript-eslint/no-empty-object-type": "error",
"@typescript-eslint/no-extra-non-null-assertion": "error",
"@typescript-eslint/no-misused-new": "error",
// no way to get it to be less strict unfortunately
// "@typescript-eslint/no-misused-spread": "error",
"@typescript-eslint/no-non-null-asserted-optional-chain": "error",
// naming style
// ============
"@typescript-eslint/naming-convention": ["error", {
"selector": ["class", "interface", "typeAlias"],
"format": ["PascalCase"],
}],
// syntax style (local syntactical, usually autofixable formatting decisions)
// ===========================================================================
"@typescript-eslint/no-namespace": ["error", { allowDeclarations: true }],
"@typescript-eslint/prefer-namespace-keyword": "error",
"@typescript-eslint/adjacent-overload-signatures": "error",
"@typescript-eslint/array-type": "error",
"@typescript-eslint/consistent-type-assertions": ["error", { "assertionStyle": "as" }],
"@typescript-eslint/consistent-type-definitions": "off",
"@typescript-eslint/consistent-type-imports": ["error", { fixStyle: "inline-type-imports" }],
"@typescript-eslint/explicit-member-accessibility": ["error", { "accessibility": "no-public" }],
"@typescript-eslint/parameter-properties": "error",
// `source` and `target` are frequently used as variables that may point to `this`
// or to another `Pokemon` object, depending on how the given method is invoked
"@typescript-eslint/no-this-alias": ["error", { "allowedNames": ["source", "target"] }],
// unfortunately this has lots of false positives without strict array/object property access
// "@typescript-eslint/no-unnecessary-boolean-literal-compare": "error",
"@typescript-eslint/prefer-as-const": "error",
"@typescript-eslint/prefer-for-of": "error",
"@typescript-eslint/prefer-function-type": "error",
"@typescript-eslint/prefer-return-this-type": "error",
"@typescript-eslint/triple-slash-reference": "error",
"@typescript-eslint/unified-signatures": "error",
};
/** @type {NonNullable<Config['rules']>} */
export const defaultRulesTSChecked = {
...defaultRulesTS,
// style
// =====
"@typescript-eslint/no-unnecessary-type-arguments": "error",
"@typescript-eslint/restrict-plus-operands": ["error", {
allowBoolean: false, allowNullish: false, allowNumberAndString: false, allowRegExp: false,
}],
"@typescript-eslint/restrict-template-expressions": ["error", {
allow: [{ name: ['Error', 'URL', 'URLSearchParams'], from: 'lib' }],
allowBoolean: false, allowNever: false, allowNullish: false, allowRegExp: false,
}],
// we use `any`
// ============
"@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/no-unsafe-call": "off",
"@typescript-eslint/no-unsafe-member-access": "off",
"@typescript-eslint/no-unsafe-return": "off",
"@typescript-eslint/no-unsafe-argument": "off",
// yes-types syntax style, overriding base
// =======================================
"@typescript-eslint/prefer-includes": "error",
"@typescript-eslint/prefer-nullish-coalescing": "off",
"@typescript-eslint/dot-notation": "off",
"@typescript-eslint/no-confusing-non-null-assertion": "off",
};
/** @type {NonNullable<Config['rules']>} */
export const defaultRulesES3 = {
...defaultRules,
// required in ES3
// ================
"no-var": "off",
"object-shorthand": ["error", "never"],
"prefer-arrow-callback": "off",
"prefer-exponentiation-operator": "off",
"prefer-object-has-own": "off",
"prefer-object-spread": "off",
"prefer-rest-params": "off",
"prefer-spread": "off",
"radix": "off",
"@stylistic/comma-dangle": "error",
"no-unused-vars": ["warn", {
args: "all",
argsIgnorePattern: ".",
caughtErrors: "all",
caughtErrorsIgnorePattern: "^e(rr)?$",
destructuredArrayIgnorePattern: ".",
ignoreRestSiblings: true,
}],
"no-restricted-syntax": ["error",
{ selector: "TaggedTemplateExpression", message: "Hard to compile down to ES3" },
{ selector: "CallExpression[callee.name='Symbol']", message: "Annoying to serialize, just use a string" },
],
// with no block scoping, coming up with original variable names is too hard
"no-redeclare": "off",
// treat var as let
// unfortunately doesn't actually let me redeclare
// "block-scoped-var": "error",
"no-caller": "error",
"no-invalid-this": "error",
"no-new-wrappers": "error",
// Map/Set can be polyfilled but it's nontrivial and it's easier just to use bare objects
"no-restricted-globals": ["error", "Proxy", "Reflect", "Symbol", "WeakSet", "WeakMap", "Set", "Map"],
"unicode-bom": "error",
};
/**
* Actually very different from defaultRulesES3, because we don't have to
* worry about syntax that's easy to transpile to ES3 (which is basically
* all syntax).
* @type {NonNullable<Config['rules']>}
*/
export const defaultRulesES3TSChecked = {
...defaultRulesTSChecked,
"radix": "off",
"no-restricted-globals": ["error", "Proxy", "Reflect", "Symbol", "WeakSet", "WeakMap", "Set", "Map"],
"no-restricted-syntax": ["error", "TaggedTemplateExpression", "YieldExpression", "AwaitExpression", "BigIntLiteral"],
};
/**
* @param {Config[]} configs
* @returns {Config}
*/
function extractPlugin(configs) {
return configs.find(config => !config.rules) ||
(() => { throw new Error('No plugin found'); })();
}
/**
* @param {Config[]} configs
* @returns {Rules}
*/
function extractRules(configs) {
const rules = {};
for (const config of configs.filter(c => c.rules)) {
Object.assign(rules, config.rules);
}
return rules;
}
const tseslintPlugin = extractPlugin(tseslint.configs.stylisticTypeChecked);
/** @type {{[k: string]: Config[]}} */
export const configs = {
js: [{
rules: {
...eslint.configs.recommended.rules,
...defaultRules,
},
}],
ts: [tseslintPlugin, {
rules: {
...eslint.configs.recommended.rules,
...extractRules(tseslint.configs.recommendedTypeChecked),
...extractRules(tseslint.configs.stylisticTypeChecked),
...defaultRulesTSChecked,
},
}],
es3: [{
rules: {
...eslint.configs.recommended.rules,
...defaultRulesES3,
},
}],
es3ts: [tseslintPlugin, {
rules: {
...eslint.configs.recommended.rules,
...extractRules(tseslint.configs.recommendedTypeChecked),
...extractRules(tseslint.configs.stylisticTypeChecked),
...defaultRulesES3TSChecked,
},
}],
};
/*
SOURCE FOR IGNOREPATTERN (compile with https://regexfree.k55.io/ )
# indentation
^\s*
# possibly commented out
(\/\/\ \s*)?
(
# define a variable, append to a variable, or define a single-arg arrow function
(export\ )? (let\ |const\ |readonly\ )? [a-zA-Z0-9_$.]+ (\ \+?=>?\ )
|
# define a property (oversize arrays are only allowed in properties)
[a-zA-Z0-9$]+:\ \[?
|
# optionally return or throw
(return\ |throw\ )?
# call a function or constructor
(new\ )?([a-zA-Z0-9$.]+\()?
)?
(
Utils\.html
|
(this\.)?(room\.)?tr
|
\$\(
)?
# start of string or regex
['"`\/]
*/

134
eslint.config.mjs Normal file
View File

@ -0,0 +1,134 @@
// @ts-check
import { configs, configure, globals } from './eslint-ps-standard.mjs';
export default configure([
{
ignores: [
'caches/**',
'play.pokemonshowdown.com/config/config-test.js',
'play.pokemonshowdown.com/src/battle-log-misc.js',
'play.pokemonshowdown.com/js/replay-embed.js',
],
},
{
name: "JavaScript for browsers (ES3)",
files: [
'play.pokemonshowdown.com/js/client-battle.js',
'play.pokemonshowdown.com/js/client-chat-tournament.js',
'play.pokemonshowdown.com/js/client-chat.js',
'play.pokemonshowdown.com/js/client-ladder.js',
'play.pokemonshowdown.com/js/client-mainmenu.js',
'play.pokemonshowdown.com/js/client-rooms.js',
'play.pokemonshowdown.com/js/client-teambuilder.js',
'play.pokemonshowdown.com/js/client-topbar.js',
'play.pokemonshowdown.com/js/client.js',
'play.pokemonshowdown.com/js/replay-embed.template.js',
'play.pokemonshowdown.com/js/search.js',
'play.pokemonshowdown.com/js/storage.js',
'config/config-example.js',
],
extends: [configs.es3],
languageOptions: {
ecmaVersion: 3,
sourceType: "script",
globals: {
...globals.builtin,
...globals.browser,
...globals.node,
// Libraries
"_": false, "$": false, "Backbone": false, "d3": false, "html": false, "html4": false, "jQuery": false, "SockJS": false, "ColorThief": false,
// Environment-specific
"fs": false, "gui": false, "ga": false, "macgap": false, "nwWindow": false, "webkitNotifications": false, "nw": false,
// Battle stuff
"Battle": true, "Pokemon": true, "BattleSound": true, "BattleTooltips": true, "BattleLog": true,
"BattleAbilities": false, "BattleAliases": false, "BattleBackdrops": false, "BattleBackdropsFive": false, "BattleBackdropsFour": false, "BattleBackdropsThree": false, "BattleEffects": false,
"BattleFormats": false, "BattleFormatsData": false, "BattleLearnsets": false, "BattleItems": false, "BattleMoveAnims": false, "BattleMovedex": false, "BattleNatures": false,
"BattleOtherAnims": false, "BattlePokedex": false, "BattlePokemonSprites": false, "BattlePokemonSpritesBW": false, "BattleSearchCountIndex": false, "BattleSearchIndex": false, "BattleArticleTitles": false,
"BattleSearchIndexOffset": false, "BattleSearchIndexType": false, "BattleStatIDs": false, "BattleStatNames": false, "BattleStatusAnims": false, "BattleStatuses": false, "BattleTeambuilderTable": false,
"ModifiableValue": false, "BattleStatGuesser": false, "BattleStatOptimizer": false, "BattleText": true, "BattleTextAFD": false, "BattleTextNotAFD": false,
"BattleTextParser": false,
// Generic global variables
"Config": false, "BattleSearch": false, "Storage": false, "Dex": false, "DexSearch": false,
"app": false, "toID": false, "toRoomid": false, "toUserid": false, "toName": false, "PSUtils": false, "MD5": false,
"ChatHistory": false, "Topbar": false, "UserList": false,
// Rooms
"Room": false, "BattleRoom": false, "ChatRoom": false, "ConsoleRoom": false, "HTMLRoom": false, "LadderRoom": false, "MainMenuRoom": false, "RoomsRoom": false, "BattlesRoom": false, "TeambuilderRoom": false,
// Tons of popups
"Popup": false, "ForfeitPopup": false, "BracketPopup": false, "LoginPasswordPopup": false, "UserPopup": false, "UserOptionsPopup": false, "UserOptions": false, "TeamPopup": false,
"AvatarsPopup": false, "CreditsPopup": false, "FormatPopup": false, "FormattingPopup": false, "LoginPopup": false,
"MovePopup": false, "SoundsPopup": false, "OptionsPopup": false, "PromptPopup": false, "ProxyPopup": false, "ReconnectPopup": false,
"RegisterPopup": false, "ReplayUploadedPopup": false, "RulesPopup": false, "TabListPopup": false, "TournamentBox": false,
"CustomBackgroundPopup": false,
// Test client
"POKEMON_SHOWDOWN_TESTCLIENT_KEY": false,
},
},
rules: {
"@stylistic/max-len": "off",
// we use these for the big IIFEs that wrap entire files
"@stylistic/padded-blocks": "off",
// TODO: actually fix useless escapes
"no-useless-escape": "off",
"no-shadow-restricted-names": "error",
"no-shadow": "off",
},
},
{
name: "JavaScript for Node",
files: [
'*.mjs', // look mom I'm linting myself!
'build-tools/*.js',
'build-tools/update',
'build-tools/build-*',
],
extends: [configs.js],
languageOptions: {
globals: {
...globals.builtin,
...globals.node,
},
},
rules: {
},
},
{
name: "TypeScript",
files: [
'play.pokemonshowdown.com/src/*.ts',
'play.pokemonshowdown.com/src/*.tsx',
'replay.pokemonshowdown.com/src/*.ts',
'replay.pokemonshowdown.com/src/*.tsx',
],
extends: [configs.es3ts],
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
rules: {
// temporary
"prefer-const": "off",
// we use these for grouping
"@stylistic/padded-blocks": "off",
// too many of these on client
"@typescript-eslint/no-floating-promises": "off",
// we use these for animations
"@typescript-eslint/unbound-method": "off",
"@typescript-eslint/restrict-template-expressions": ["error", {
allow: [
{ name: ['Error', 'URL', 'URLSearchParams'], from: 'lib' },
{ name: ['ModifiableValue'], from: 'file' },
],
allowBoolean: false, allowNever: false, allowNullish: false, allowRegExp: false,
}],
},
},
]);

5166
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -9,9 +9,9 @@
"url": "https://github.com/Zarel/Pokemon-Showdown-Client.git"
},
"scripts": {
"lint": "eslint --config=.eslintrc.js --cache --cache-file=caches/eslint-base.json play.pokemonshowdown.com/js/ && eslint --config=build-tools/.eslintrc.js --cache --cache-file=caches/eslint-build.json build-tools/update build-tools/build-indexes && tslint --project .",
"test": "npm run lint && tsc && node build && mocha test/*.js",
"fix": "eslint --config=.eslintrc.js --fix js/ && eslint --config=build-tools/.eslintrc.js --fix build-tools/update build-tools/build-indexes",
"lint": "eslint --cache --cache-location caches/eslintcache.json",
"test": "node build && tsc && eslint --cache --cache-location caches/eslintcache.json --max-warnings 0 && mocha test/*.js",
"fix": "eslint --cache --cache-location caches/eslintcache.json --fix",
"build": "node build",
"build-full": "node build full"
},
@ -26,14 +26,16 @@
"image-size": "^0.7.5"
},
"devDependencies": {
"@stylistic/eslint-plugin": "^4.0.1",
"@types/jquery": "^3.5.3",
"@types/mocha": "^5.2.6",
"eslint": "^5.16.0",
"eslint": "^9.20.1",
"globals": "^16.0.0",
"mocha": "^6.0.2",
"preact": "^8.3.1",
"source-map": "^0.7.3",
"tslint": "^5.20.1",
"typescript": "^5.7.3"
"typescript": "^5.7.3",
"typescript-eslint": "^8.24.1"
},
"private": true
}

View File

@ -217,7 +217,8 @@
this.battle.stepQueue.push('|' + args.join('|'));
break;
}
} else if (logLine.substr(0, 7) === '|title|') { // eslint-disable-line no-empty
} else if (logLine.substr(0, 7) === '|title|') {
// empty
} else if (logLine.substr(0, 5) === '|win|' || logLine === '|tie') {
this.battleEnded = true;
this.battle.stepQueue.push(logLine);
@ -641,7 +642,7 @@
} 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' && activePos === i) disabled = true;
if (disabled) {
targetMenus[1] += '<button disabled style="visibility:hidden"></button> ';
@ -840,7 +841,7 @@
}
var switchables = this.request && this.request.side ? this.battle.myPokemon : [];
var nearActive = this.battle.nearSide.active;
// var nearActive = this.battle.nearSide.active;
var isReviving = !!switchables[pos].reviving;
var requestTitle = '';
@ -1414,8 +1415,10 @@
}
} else if (this.request.requestType === 'move') {
var requestDetails = this.request && this.request.side ? this.battle.myPokemon : [];
while (choices.length < this.battle.pokemonControlled &&
(!nearActive[choices.length] || requestDetails[choices.length].commanding)) {
while (
choices.length < this.battle.pokemonControlled &&
(!nearActive[choices.length] || requestDetails[choices.length].commanding)
) {
choices.push('pass');
}

View File

@ -362,7 +362,7 @@
var currentLine = prefix.substr(prefix.lastIndexOf('\n') + 1);
var shouldSearchCommands = !cmds || (cmds.length ? !!cmds.length && !cmds.filter(function (x) {
return x.startsWith(currentLine);
}).length : prefix != this.tabComplete.prefix);
}).length : prefix !== this.tabComplete.prefix);
var isCommandSearch = (currentLine.startsWith('/') && !currentLine.startsWith('//')) || currentLine.startsWith('!');
var resultsExist = this.tabComplete.lastSearch === text && this.tabComplete.commands;
if (isCommandSearch && shouldSearchCommands && !resultsExist) {
@ -417,7 +417,7 @@
return bidx - aidx;
}
return -1; // a comes first
} else if (bidx != -1) {
} else if (bidx !== -1) {
return 1; // b comes first
}
return (a[0] < b[0]) ? -1 : 1; // alphabetical order
@ -1500,7 +1500,6 @@
this.addJoinLeave('rename', row[1], row[2], true);
break;
case 'users':
this.parseUserList(row[1]);
break;
@ -1738,9 +1737,9 @@
break;
}
if (j > 0) {
if (j == 1 && list.length == 2) {
if (j === 1 && list.length === 2) {
message += ' and ';
} else if (j == list.length - 1) {
} else if (j === list.length - 1) {
message += ', and ';
} else {
message += ', ';
@ -1900,7 +1899,7 @@
buf += this.getNoNamedUsersOnline();
}
if (this.room.userCount.guests) {
buf += '<li id="' + this.room.id + '-userlist-guests" style="text-align:center;padding:2px 0"><small>(<span id="' + this.room.id + '-usercount-guests">' + this.room.userCount.guests + '</span> guest' + (this.room.userCount.guests == 1 ? '' : 's') + ')</small></li>';
buf += '<li id="' + this.room.id + '-userlist-guests" style="text-align:center;padding:2px 0"><small>(<span id="' + this.room.id + '-usercount-guests">' + this.room.userCount.guests + '</span> guest' + (this.room.userCount.guests === 1 ? '' : 's') + ')</small></li>';
}
this.$el.html(buf);
},

View File

@ -93,7 +93,7 @@
e.stopPropagation();
var name = $(e.currentTarget).data('name') || $(e.currentTarget).text();
app.addPopup(UserPopup, { name: name, sourceEl: e.currentTarget });
},
}
});
this.LadderRoom = HTMLRoom.extend({
@ -196,7 +196,7 @@
this.update();
}
}, {
COIL_B: {},
COIL_B: {}
});
}).call(this, jQuery);

View File

@ -1246,7 +1246,7 @@
events: {
'keyup input[name=search]': 'updateSearch',
'click details': 'updateOpen',
'click i.fa': 'updateStar',
'click i.fa': 'updateStar'
},
initialize: function (data) {
this.data = data;
@ -1258,7 +1258,7 @@
"S/V Singles": true, "S/V Doubles": true, "Unofficial Metagames": true, "National Dex": true, "OM of the Month": true,
"Other Metagames": true, "Randomized Format Spotlight": true, "RoA Spotlight": true,
// For AFD
"Random Meta of the Decade": true,
"Random Meta of the Decade": true
};
}
if (!this.starred) this.starred = Storage.prefs('starredformats') || {};
@ -1387,7 +1387,7 @@
if (!format.isTeambuilderFormat) return false;
} else {
if (format.effectType !== 'Format' || format.battleFormat) return false;
if (this.selectType != 'watch' && !format[this.selectType + 'Show']) return false;
if (this.selectType !== 'watch' && !format[this.selectType + 'Show']) return false;
}
return true;
},
@ -1531,7 +1531,7 @@
} else {
bufs[curBuf] += '<li><button name="selectFolder" class="button" value="(No Folder)"><i class="fa fa-folder" style="margin-right: 7px; margin-left: 4px;"></i>(No Folder)</button></li>';
count++;
if (count % bufBoundary === 0 && count != 0 && curBuf < 4) curBuf++;
if (count % bufBoundary === 0 && count !== 0 && curBuf < 4) curBuf++;
}
if (!isNoFolder) {
for (var i = 0; i < teams.length; i++) {
@ -1578,7 +1578,7 @@
}
},
events: {
'click input[type=checkbox]': 'foldersToggle',
'click input[type=checkbox]': 'foldersToggle'
},
moreTeams: function () {
this.close();
@ -1599,9 +1599,8 @@
if (folder === key) {
keyExists = true;
return false;
} else {
return true;
}
return true;
});
if (!keyExists) {
folderNotExpanded.push(key);

View File

@ -117,8 +117,8 @@
if (rooms.userCount) {
var userCount = Number(rooms.userCount);
var battleCount = Number(rooms.battleCount);
var leftSide = '<button class="button" name="finduser" title="Find an online user"><span class="pixelated usercount" title="Meloetta is PS\'s mascot! The Aria forme is about using its voice, and represents our chatrooms." ></span><strong>' + userCount + '</strong> ' + (userCount == 1 ? 'user' : 'users') + ' online</button> ';
var rightSide = '<button class="button" name="roomlist" title="Watch an active battle"><span class="pixelated battlecount" title="Meloetta is PS\'s mascot! The Pirouette forme is Fighting-type, and represents our battles." ></span><strong>' + battleCount + '</strong> active ' + (battleCount == 1 ? 'battle' : 'battles') + '</button>';
var leftSide = '<button class="button" name="finduser" title="Find an online user"><span class="pixelated usercount" title="Meloetta is PS\'s mascot! The Aria forme is about using its voice, and represents our chatrooms." ></span><strong>' + userCount + '</strong> ' + (userCount === 1 ? 'user' : 'users') + ' online</button> ';
var rightSide = '<button class="button" name="roomlist" title="Watch an active battle"><span class="pixelated battlecount" title="Meloetta is PS\'s mascot! The Pirouette forme is Fighting-type, and represents our battles." ></span><strong>' + battleCount + '</strong> active ' + (battleCount === 1 ? 'battle' : 'battles') + '</button>';
this.$('.roomlisttop').html('<div class="roomcounters">' + leftSide + '</td><td>' + rightSide + '</div>');
}
@ -174,9 +174,11 @@
);
this.$('.roomlist').last().html(
(otherRooms.length ?
'<h2 class="rooms-chatrooms">Chat rooms</h2>' + otherRooms.sort(this.compareRooms).map(this.renderRoomBtn).join("") : '') +
'<h2 class="rooms-chatrooms">Chat rooms</h2>' + otherRooms.sort(this.compareRooms).map(this.renderRoomBtn).join("") : ''
) +
(hiddenRooms.length && this.showMoreRooms ?
'<h2 class="rooms-chatrooms">Hidden rooms</h2>' + hiddenRooms.sort(this.compareRooms).map(this.renderRoomBtn).join("") : '')
'<h2 class="rooms-chatrooms">Hidden rooms</h2>' + hiddenRooms.sort(this.compareRooms).map(this.renderRoomBtn).join("") : ''
)
);
},
roomlist: function () {

View File

@ -555,7 +555,6 @@
updatePersistence: function (state) {
if (state) {
this.$('.storage-warning').html('');
return;
}
},
greeting: function (answer, button) {
@ -1397,7 +1396,7 @@
evBuf += '<small>&minus;</small>';
}
var width = stats[j] * 75 / 504;
if (j == 'hp') width = stats[j] * 75 / 704;
if (j === 'hp') width = stats[j] * 75 / 704;
if (width > 75) width = 75;
var color = Math.floor(stats[j] * 180 / 714);
if (color > 360) color = 360;
@ -1430,7 +1429,7 @@
if (format) self.changeFormat(format.id);
notes.shift();
}
var teamNotes = notes.join('\n'); // Not implemented yet
// var teamNotes = notes.join('\n'); // Not implemented yet
var title = data.title;
if (title && !title.startsWith('Untitled')) {
@ -2016,7 +2015,7 @@
if (!set.species) {
buf += '<button disabled class="addpokemon" aria-label="Add Pok&eacute;mon"><i class="fa fa-plus"></i></button> ';
isAdd = true;
} else if (i == this.curSetLoc) {
} 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> ';
@ -2066,7 +2065,7 @@
evBuf += '<small>&minus;</small>';
}
var width = stats[stat] * 75 / 504;
if (stat == 'hp') width = stats[stat] * 75 / 704;
if (stat === 'hp') width = stats[stat] * 75 / 704;
if (width > 75) width = 75;
var color = Math.floor(stats[stat] * 180 / 714);
if (color > 360) color = 360;
@ -2089,7 +2088,7 @@
for (var stat in stats) {
if (stat === 'spd' && this.curTeam.gen === 1) continue;
var width = stats[stat] * 180 / 504;
if (stat == 'hp') width = stats[stat] * 180 / 704;
if (stat === 'hp') width = stats[stat] * 180 / 704;
if (width > 179) width = 179;
var color = Math.floor(stats[stat] * 180 / 714);
if (color > 360) color = 360;
@ -2344,7 +2343,7 @@
for (var i in stats) {
stats[i] = this.getStat(i);
var width = stats[i] * 180 / 504;
if (i == 'hp') width = Math.floor(stats[i] * 180 / 704);
if (i === 'hp') width = Math.floor(stats[i] * 180 / 704);
if (width > 179) width = 179;
var color = Math.floor(stats[i] * 180 / 714);
if (color > 360) color = 360;
@ -2735,7 +2734,7 @@
var supportsEVs = !this.curTeam.format.includes('letsgo');
var supportsAVs = !supportsEVs && this.curTeam.format.endsWith('norestrictions');
if (supportsEVs) {
while (val > 0 && this.getStat(stat, set, val - 4) == result) val -= 4;
while (val > 0 && this.getStat(stat, set, val - 4) === result) val -= 4;
}
if (supportsEVs && !this.ignoreEVLimits && set.evs) {
@ -3125,7 +3124,6 @@
var entry = $firstResult.data('entry');
var val = entry.slice(entry.indexOf("|") + 1);
this.chartSet(val, true);
return;
} else if (e.keyCode === 38) { // up
e.preventDefault();
e.stopPropagation();
@ -3153,7 +3151,6 @@
} else if (e.keyCode === 27 || e.keyCode === 8) { // esc, backspace
if (!e.currentTarget.value && this.search.removeFilter()) {
this.search.find('');
return;
}
} else if (e.keyCode === 188) {
var $firstResult = this.$chart.find('a').first();
@ -3163,7 +3160,6 @@
e.stopPropagation();
$(e.currentTarget).val('').select();
this.search.find('');
return;
}
}
},

View File

@ -543,7 +543,7 @@
"हिंदी": 'hindi',
"日本語": 'japanese',
"简体中文": 'simplifiedchinese',
"中文": 'traditionalchinese',
"中文": 'traditionalchinese'
};
buf += '<p><label class="optlabel">Language: <select name="language" class="button">';
for (var name in possibleLanguages) {

View File

@ -1,3 +1,4 @@
/* exported toId */
function toId() {
// toId has been renamed toID
alert("You have an old extension/script for Pokemon Showdown which is incompatible with this client. It needs to be removed or updated.");
@ -20,7 +21,7 @@ function toId() {
}
$(document).on('keydown', function (e) {
if (e.keyCode == 27) { // Esc
if (e.keyCode === 27) { // Esc
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
@ -942,7 +943,7 @@ function toId() {
this.loadingTeam = true;
$.get(app.user.getActionPHP(), {
act: 'getteam',
teamid: team.teamid,
teamid: team.teamid
}, Storage.safeJSON(function (data) {
app.loadingTeam = false;
if (data.actionerror) {
@ -1323,7 +1324,7 @@ function toId() {
var columnChanged = false;
window.NonBattleGames = { rps: 'Rock Paper Scissors' };
for (var i = 3; i <= 9; i = i + 2) {
for (var i = 3; i <= 9; i += 2) {
window.NonBattleGames['bestof' + i] = 'Best-of-' + i;
}
window.BattleFormats = {};
@ -1724,7 +1725,6 @@ function toId() {
}
room.focus(null, focusTextbox);
return;
},
focusRoomLeft: function (id) {
var room = this.rooms[id];
@ -1750,7 +1750,6 @@ function toId() {
if (this.curRoom.id === id) this.navigate(id);
room.focus(null, true);
return;
},
focusRoomRight: function (id) {
var room = this.rooms[id];
@ -1774,7 +1773,6 @@ function toId() {
// if (this.curRoom.id === id) this.navigate(id);
room.focus(null, true);
return;
},
/**
* This is the function for handling the two-panel layout
@ -2298,14 +2296,14 @@ function toId() {
notifications: null,
subtleNotification: false,
notify: function (title, body, tag, once) {
if (once && app.focused && (this === app.curRoom || this == app.curSideRoom)) return;
if (once && app.focused && (this === app.curRoom || this === app.curSideRoom)) return;
if (!tag) tag = 'message';
var needsTabbarUpdate = false;
if (!this.notifications) {
this.notifications = {};
needsTabbarUpdate = true;
}
if (app.focused && (this === app.curRoom || this == app.curSideRoom)) {
if (app.focused && (this === app.curRoom || this === app.curSideRoom)) {
this.notifications[tag] = {};
} else if (window.nodewebkit && !nwWindow.setBadgeLabel) {
// old desktop client
@ -2359,7 +2357,7 @@ function toId() {
}
},
subtleNotifyOnce: function () {
if (app.focused && (this === app.curRoom || this == app.curSideRoom)) return;
if (app.focused && (this === app.curRoom || this === app.curSideRoom)) return;
if (this.notifications || this.subtleNotification) return;
this.subtleNotification = true;
this.notificationClass = ' subtle-notifying';
@ -2629,7 +2627,7 @@ function toId() {
} else {
app.addPopupMessage("You are already registered!");
}
},
}
});
var PromptPopup = this.PromptPopup = Popup.extend({
@ -2872,7 +2870,7 @@ function toId() {
app.addPopup(UserOptionsPopup, {
name: this.data.name,
userid: this.data.userid,
friended: this.data.friended,
friended: this.data.friended
});
}
}, {

View File

@ -49,8 +49,8 @@ if (!Array.prototype.filter) {
}
// ES2016, predates nomodule
if (!Array.prototype.includes) {
Array.prototype.includes = function includes(thing) {
return this.indexOf(thing) !== -1;
Array.prototype.includes = function includes(thing, offset) {
return this.indexOf(thing, offset) !== -1;
};
}
// ES5
@ -61,8 +61,8 @@ if (!Array.isArray) {
}
// ES6
if (!String.prototype.includes) {
String.prototype.includes = function includes(thing) {
return this.indexOf(thing) !== -1;
String.prototype.includes = function includes(thing, offset) {
return this.indexOf(thing, offset) !== -1;
};
}
// ES6

View File

@ -79,7 +79,7 @@ var Replays = {
log: log.split('\n'),
isReplay: true,
paused: true,
autoresize: true,
autoresize: true
});
this.$('.replay-controls-2').html('<div class="chooser leftchooser speedchooser"> <em>Speed:</em> <div><button value="hyperfast">Hyperfast</button><button value="fast">Fast</button><button value="normal" class="sel">Normal</button><button value="slow">Slow</button><button value="reallyslow">Really Slow</button></div> </div> <div class="chooser colorchooser"> <em>Color&nbsp;scheme:</em> <div><button class="sel" value="light">Light</button><button value="dark">Dark</button></div> </div> <div class="chooser soundchooser" style="display:none"> <em>Music:</em> <div><button class="sel" value="on">On</button><button value="off">Off</button></div> </div>');
@ -202,7 +202,7 @@ var Replays = {
},
switchViewpoint: function () {
this.battle.switchViewpoint();
},
}
};
window.onload = function () {

View File

@ -15,7 +15,7 @@ Storage.initialize = function () {
Storage.safeJSON = function (callback) {
return function (data) {
if (data.length < 1) return;
if (data[0] == ']') data = data.substr(1);
if (data[0] === ']') data = data.substr(1);
return callback(JSON.parse(data));
};
};
@ -183,7 +183,7 @@ Storage.bg = {
var l = (max + min) / 2;
if (max === min) {
return '0, 0%';
} else {
}
var d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
@ -192,7 +192,6 @@ Storage.bg = {
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return '' + (h * 360) + ',' + (s * 100) + '%';
}
};
@ -854,7 +853,7 @@ Storage.packTeam = function (team) {
}
// level
if (set.level && set.level != 100) {
if (set.level && set.level !== 100) {
buf += '|' + set.level;
} else {
buf += '|';
@ -1397,7 +1396,7 @@ Storage.exportTeam = function (team, gen, hidestats) {
if (curSet.ability) {
text += 'Ability: ' + curSet.ability + " \n";
}
if (curSet.level && curSet.level != 100) {
if (curSet.level && curSet.level !== 100) {
text += 'Level: ' + curSet.level + " \n";
}
if (curSet.shiny) {
@ -1472,7 +1471,7 @@ Storage.exportTeam = function (team, gen, hidestats) {
}
if (!defaultIvs) {
for (var stat in BattleStatNames) {
if (typeof curSet.ivs[stat] === 'undefined' || isNaN(curSet.ivs[stat]) || curSet.ivs[stat] == 31) continue;
if (typeof curSet.ivs[stat] === 'undefined' || isNaN(curSet.ivs[stat]) || curSet.ivs[stat] === 31) continue;
if (first) {
text += 'IVs: ';
first = false;

View File

@ -51,9 +51,9 @@
linkStyle("style/battle-search.css");
linkStyle("/style/font-awesome.css");
</script>
<script nomodule defer src="/js/lib/ps-polyfill.js"></script>
<script defer src="/config/config.js?"></script>
<script defer src="/js/client-core.js?"></script>
<script nomodule defer src="/js/lib/ps-polyfill.js"></script>
<script defer src="/js/battle-dex.js?"></script>
<script defer src="/js/battle-text-parser.js?"></script>

View File

@ -14015,7 +14015,6 @@ export const BattleMoveAnims: AnimTable = {
opacity: 0.4,
time: 200 * i + 200,
}, 'linear', 'fade');
}
},
},

View File

@ -15,7 +15,7 @@ import type {Battle, Pokemon, Side, WeatherState} from './battle';
import type { BattleSceneStub } from './battle-scene-stub';
import { BattleMoveAnims } from './battle-animations-moves';
import { BattleLog } from './battle-log';
import {BattleBGM, BattleSound} from './battle-sound';
import { type BattleBGM, BattleSound } from './battle-sound';
import { Dex, toID, type ID, type SpriteData } from './battle-dex';
import { BattleNatures } from './battle-dex-data';
import { BattleTooltips } from './battle-tooltips';
@ -79,7 +79,7 @@ export class BattleScene implements BattleSceneStub {
preloadDone = 0;
preloadNeeded = 0;
bgm: BattleBGM | null = null;
backdropImage: string = '';
backdropImage = '';
bgmNum = 0;
preloadCache: { [url: string]: HTMLImageElement } = {};
@ -271,7 +271,7 @@ export class BattleScene implements BattleSceneStub {
effect: string | SpriteData, start: ScenePos, end: ScenePos,
transition: string, after?: string, additionalCss?: JQuery.PlainObject
) {
if (typeof effect === 'string') effect = BattleEffects[effect] as SpriteData;
if (typeof effect === 'string') effect = BattleEffects[effect];
if (!start.time) start.time = 0;
if (!end.time) end.time = start.time + 500;
start.time += this.timeOffset;
@ -284,7 +284,7 @@ export class BattleScene implements BattleSceneStub {
let startpos = this.pos(start, effect);
let endpos = this.posT(end, effect, transition, start);
let $effect = $('<img src="' + effect.url + '" style="display:block;position:absolute" />');
let $effect = $(`<img src="${effect.url!}" style="display:block;position:absolute" />`);
this.$fx.append($effect);
if (additionalCss) $effect.css(additionalCss);
$effect = this.$fx.children().last();
@ -349,10 +349,10 @@ export class BattleScene implements BattleSceneStub {
let left = 210;
let top = 245;
let scale = (obj.gen === 5
? 2.0 - ((loc.z!) / 200)
: 1.5 - 0.5 * ((loc.z!) / 200));
if (scale < .1) scale = .1;
let scale = (obj.gen === 5 ?
2.0 - ((loc.z!) / 200) :
1.5 - 0.5 * ((loc.z!) / 200));
if (scale < 0.1) scale = 0.1;
left += (410 - 190) * ((loc.z!) / 200);
top += (135 - 245) * ((loc.z!) / 200);
@ -476,7 +476,7 @@ export class BattleScene implements BattleSceneStub {
}, this.battle.messageFadeTime / this.acceleration);
}
}
if (this.battle.hardcoreMode && message.slice(0, 8) === '<small>(') {
if (this.battle.hardcoreMode && message.startsWith('<small>(')) {
message = '';
}
if (message && this.animating) {
@ -580,15 +580,15 @@ export class BattleScene implements BattleSceneStub {
} else {
if (gen <= 1) bg = 'fx/bg-gen1.png?';
else if (gen <= 2) bg = 'fx/bg-gen2.png?';
else if (gen <= 3) bg = 'fx/' + BattleBackdropsThree[this.numericId % BattleBackdropsThree.length] + '?';
else if (gen <= 4) bg = 'fx/' + BattleBackdropsFour[this.numericId % BattleBackdropsFour.length];
else if (gen <= 5) bg = 'fx/' + BattleBackdropsFive[this.numericId % BattleBackdropsFive.length];
else bg = 'sprites/gen6bgs/' + BattleBackdrops[this.numericId % BattleBackdrops.length];
else if (gen <= 3) bg = `fx/${BattleBackdropsThree[this.numericId % BattleBackdropsThree.length]}?`;
else if (gen <= 4) bg = `fx/${BattleBackdropsFour[this.numericId % BattleBackdropsFour.length]}`;
else if (gen <= 5) bg = `fx/${BattleBackdropsFive[this.numericId % BattleBackdropsFive.length]}`;
else bg = `sprites/gen6bgs/${BattleBackdrops[this.numericId % BattleBackdrops.length]}`;
}
this.backdropImage = bg;
if (this.$bg) {
this.$bg.css('background-image', 'url(' + Dex.resourcePrefix + '' + this.backdropImage + ')');
this.$bg.css('background-image', `url(${Dex.resourcePrefix}${this.backdropImage})`);
}
}
@ -665,29 +665,29 @@ export class BattleScene implements BattleSceneStub {
for (let i = 0; i < sidebarIcons.length; i++) {
const [iconType, pokeIndex] = sidebarIcons[i];
const poke = pokeIndex !== null ? side.pokemon[pokeIndex] : null;
const tooltipCode = ` class="picon has-tooltip" data-tooltip="pokemon|${side.n}|${pokeIndex}${iconType === 'pokemon-illusion' ? '|illusion' : ''}"`;
const tooltipCode = ` class="picon has-tooltip" data-tooltip="pokemon|${side.n}|${pokeIndex!}${iconType === 'pokemon-illusion' ? '|illusion' : ''}"`;
if (iconType === 'empty') {
pokemonhtml += `<span class="picon" style="` + Dex.getPokemonIcon('pokeball-none') + `"></span>`;
pokemonhtml += `<span class="picon" style="${Dex.getPokemonIcon('pokeball-none')}"></span>`;
} else if (noShow) {
if (poke?.fainted) {
pokemonhtml += `<span${tooltipCode} style="` + Dex.getPokemonIcon('pokeball-fainted') + `" aria-label="Fainted"></span>`;
pokemonhtml += `<span${tooltipCode} style="${Dex.getPokemonIcon('pokeball-fainted')}" aria-label="Fainted"></span>`;
} else if (poke?.status) {
pokemonhtml += `<span${tooltipCode} style="` + Dex.getPokemonIcon('pokeball-statused') + `" aria-label="Statused"></span>`;
pokemonhtml += `<span${tooltipCode} style="${Dex.getPokemonIcon('pokeball-statused')}" aria-label="Statused"></span>`;
} else {
pokemonhtml += `<span${tooltipCode} style="` + Dex.getPokemonIcon('pokeball') + `" aria-label="Non-statused"></span>`;
pokemonhtml += `<span${tooltipCode} style="${Dex.getPokemonIcon('pokeball')}" aria-label="Non-statused"></span>`;
}
} else if (iconType === 'pseudo-zoroark') {
pokemonhtml += `<span class="picon" style="` + Dex.getPokemonIcon('zoroark') + `" title="Unrevealed Illusion user" aria-label="Unrevealed Illusion user"></span>`;
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>`;
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>`;
pokemonhtml += `<span${tooltipCode} style="${Dex.getPokemonIcon(poke, !side.isFar)};opacity:0.6" aria-label="${details}"></span>`;
} else {
const details = this.getDetailsText(poke);
pokemonhtml += `<span${tooltipCode} style="` + Dex.getPokemonIcon(poke, !side.isFar) + `" aria-label="${details}"></span>`;
pokemonhtml += `<span${tooltipCode} style="${Dex.getPokemonIcon(poke, !side.isFar)}" aria-label="${details}"></span>`;
}
if (i % 3 === 2) pokemonhtml += `</div><div class="teamicons">`;
}
@ -737,7 +737,7 @@ export class BattleScene implements BattleSceneStub {
const side = this.battle.nearSide;
if (side.ally) {
const side2 = side.ally!;
const side2 = side.ally;
this.$leftbar.html(this.getSidebarHTML(side, 'near2') + this.getSidebarHTML(side2, 'near'));
} else if (this.battle.sides.length > 2) { // FFA
const side2 = this.battle.sides[side.n === 0 ? 3 : 2];
@ -750,7 +750,7 @@ export class BattleScene implements BattleSceneStub {
const side = this.battle.farSide;
if (side.ally) {
const side2 = side.ally!;
const side2 = side.ally;
this.$rightbar.html(this.getSidebarHTML(side, 'far2') + this.getSidebarHTML(side2, 'far'));
} else if (this.battle.sides.length > 2) { // FFA
const side2 = this.battle.sides[side.n === 0 ? 3 : 2];
@ -861,26 +861,26 @@ export class BattleScene implements BattleSceneStub {
textBuf += pokemon.speciesForme;
let url = spriteData.url;
// 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" />';
buf2 += '<div style="position:absolute;top:' + (y + 45) + 'px;left:' + (x - 40) + 'px;width:80px;font-size:10px;text-align:center;color:#FFF;">';
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" />`;
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') {
buf2 += `<img src="${Dex.fxPrefix}gender-${gender.toLowerCase()}.png" alt="${gender}" width="7" height="10" class="pixelated" style="margin-bottom:-1px" /> `;
}
if (pokemon.level !== 100) {
buf2 += '<span style="text-shadow:#000 1px 1px 0,#000 1px -1px 0,#000 -1px 1px 0,#000 -1px -1px 0"><small>L</small>' + pokemon.level + '</span>';
buf2 += `<span style="text-shadow:#000 1px 1px 0,#000 1px -1px 0,#000 -1px 1px 0,#000 -1px -1px 0"><small>L</small>${pokemon.level}</span>`;
}
if (pokemon.item === '(mail)') {
buf2 += ' <img src="' + Dex.resourcePrefix + 'fx/mail.png" width="8" height="10" alt="F" style="margin-bottom:-1px" />';
buf2 += ` <img src="${Dex.resourcePrefix}fx/mail.png" width="8" height="10" alt="F" style="margin-bottom:-1px" />`;
} else if (pokemon.item) {
buf2 += ' <img src="' + Dex.resourcePrefix + 'fx/item.png" width="8" height="10" alt="F" style="margin-bottom:-1px" />';
buf2 += ` <img src="${Dex.resourcePrefix}fx/item.png" width="8" height="10" alt="F" style="margin-bottom:-1px" />`;
}
buf2 += '</div>';
}
side.totalPokemon = side.pokemon.length;
if (textBuf) {
this.log.addDiv('chat battle-history',
'<strong>' + BattleLog.escapeHTML(side.name) + '\'s team:</strong> <em style="color:#445566;display:block;">' + BattleLog.escapeHTML(textBuf) + '</em>'
`<strong>${BattleLog.escapeHTML(side.name)}'s team:</strong> <em style="color:#445566;display:block;">${BattleLog.escapeHTML(textBuf)}</em>`
);
}
this.$sprites[spriteIndex].html(buf + buf2);
@ -916,21 +916,21 @@ export class BattleScene implements BattleSceneStub {
}
pseudoWeatherLeft(pWeather: WeatherState) {
let buf = '<br />' + Dex.moves.get(pWeather[0]).name;
let buf = `<br />${Dex.moves.get(pWeather[0]).name}`;
if (!pWeather[1] && pWeather[2]) {
pWeather[1] = pWeather[2];
pWeather[2] = 0;
}
if (this.battle.gen < 7 && this.battle.hardcoreMode) return buf;
if (pWeather[2]) {
return buf + ' <small>(' + pWeather[1] + ' or ' + pWeather[2] + ' turns)</small>';
return `${buf} <small>(${pWeather[1]} or ${pWeather[2]} turns)</small>`;
}
if (pWeather[1]) {
return buf + ' <small>(' + pWeather[1] + ' turn' + (pWeather[1] === 1 ? '' : 's') + ')</small>';
return `${buf} <small>(${pWeather[1]} turn${pWeather[1] === 1 ? '' : 's'})</small>`;
}
return buf; // weather not found
}
sideConditionLeft(cond: [string, number, number, number], isFoe: boolean, all?: boolean) {
sideConditionLeft(cond: Side['sideConditions'][string], isFoe: boolean, all?: boolean) {
if (!cond[2] && !cond[3] && !all) return '';
let buf = `<br />${isFoe && !all ? "Foe's " : ""}${Dex.moves.get(cond[0]).name}`;
if (this.battle.gen < 7 && this.battle.hardcoreMode) return buf;
@ -941,9 +941,9 @@ export class BattleScene implements BattleSceneStub {
cond[3] = 0;
}
if (!cond[3]) {
return buf + ' <small>(' + cond[2] + ' turn' + (cond[2] === 1 ? '' : 's') + ')</small>';
return `${buf} <small>(${cond[2]} turn${cond[2] === 1 ? '' : 's'})</small>`;
}
return buf + ' <small>(' + cond[2] + ' or ' + cond[3] + ' turns)</small>';
return `${buf} <small>(${cond[2]} or ${cond[3]} turns)</small>`;
}
weatherLeft() {
if (this.battle.gen < 7 && this.battle.hardcoreMode) return '';
@ -1053,7 +1053,7 @@ export class BattleScene implements BattleSceneStub {
this.$turn.html('');
return;
}
this.$turn.html('<div class="turn has-tooltip" data-tooltip="field" data-ownheight="1">Turn ' + this.battle.turn + '</div>');
this.$turn.html(`<div class="turn has-tooltip" data-tooltip="field" data-ownheight="1">Turn ${this.battle.turn}</div>`);
}
incrementTurn() {
if (!this.animating) return;
@ -1061,7 +1061,7 @@ export class BattleScene implements BattleSceneStub {
const turn = this.battle.turn;
if (turn <= 0) return;
const $prevTurn = this.$turn.children();
const $newTurn = $('<div class="turn has-tooltip" data-tooltip="field" data-ownheight="1">Turn ' + turn + '</div>');
const $newTurn = $(`<div class="turn has-tooltip" data-tooltip="field" data-ownheight="1">Turn ${turn}</div>`);
$newTurn.css({
opacity: 0,
left: 160,
@ -1071,7 +1071,7 @@ export class BattleScene implements BattleSceneStub {
opacity: 1,
left: 110,
}, 500).animate({
opacity: .4,
opacity: 0.4,
}, 1500);
$prevTurn.animate({
opacity: 0,
@ -1130,7 +1130,7 @@ export class BattleScene implements BattleSceneStub {
yscale: 0,
opacity: 0.1,
}, this);
this.$spritesFront[spriteIndex].append(auroraveil.$el!);
this.$spritesFront[spriteIndex].append(auroraveil.$el);
this.sideConditions[siden][id] = [auroraveil];
auroraveil.anim({
opacity: 0.7,
@ -1150,7 +1150,7 @@ export class BattleScene implements BattleSceneStub {
yscale: 0,
opacity: 0.1,
}, this);
this.$spritesFront[spriteIndex].append(reflect.$el!);
this.$spritesFront[spriteIndex].append(reflect.$el);
this.sideConditions[siden][id] = [reflect];
reflect.anim({
opacity: 0.7,
@ -1170,7 +1170,7 @@ export class BattleScene implements BattleSceneStub {
yscale: 0,
opacity: 0.1,
}, this);
this.$spritesFront[spriteIndex].append(safeguard.$el!);
this.$spritesFront[spriteIndex].append(safeguard.$el);
this.sideConditions[siden][id] = [safeguard];
safeguard.anim({
opacity: 0.7,
@ -1190,7 +1190,7 @@ export class BattleScene implements BattleSceneStub {
yscale: 0,
opacity: 0.1,
}, this);
this.$spritesFront[spriteIndex].append(lightscreen.$el!);
this.$spritesFront[spriteIndex].append(lightscreen.$el);
this.sideConditions[siden][id] = [lightscreen];
lightscreen.anim({
opacity: 0.7,
@ -1210,7 +1210,7 @@ export class BattleScene implements BattleSceneStub {
yscale: 0,
opacity: 0.1,
}, this);
this.$spritesFront[spriteIndex].append(mist.$el!);
this.$spritesFront[spriteIndex].append(mist.$el);
this.sideConditions[siden][id] = [mist];
mist.anim({
opacity: 0.7,
@ -1257,10 +1257,10 @@ export class BattleScene implements BattleSceneStub {
scale: 0.2,
}, this);
this.$spritesFront[spriteIndex].append(rock1.$el!);
this.$spritesFront[spriteIndex].append(rock2.$el!);
this.$spritesFront[spriteIndex].append(rock3.$el!);
this.$spritesFront[spriteIndex].append(rock4.$el!);
this.$spritesFront[spriteIndex].append(rock1.$el);
this.$spritesFront[spriteIndex].append(rock2.$el);
this.$spritesFront[spriteIndex].append(rock3.$el);
this.$spritesFront[spriteIndex].append(rock4.$el);
this.sideConditions[siden][id] = [rock1, rock2, rock3, rock4];
break;
case 'gmaxsteelsurge':
@ -1289,9 +1289,9 @@ export class BattleScene implements BattleSceneStub {
scale: 0.8,
}, this);
this.$spritesFront[spriteIndex].append(surge1.$el!);
this.$spritesFront[spriteIndex].append(surge2.$el!);
this.$spritesFront[spriteIndex].append(surge3.$el!);
this.$spritesFront[spriteIndex].append(surge1.$el);
this.$spritesFront[spriteIndex].append(surge2.$el);
this.$spritesFront[spriteIndex].append(surge3.$el);
this.sideConditions[siden][id] = [surge1, surge2, surge3];
break;
case 'spikes':
@ -1309,7 +1309,7 @@ export class BattleScene implements BattleSceneStub {
z: side.z,
scale: 0.3,
}, this);
this.$spritesFront[spriteIndex].append(spike1.$el!);
this.$spritesFront[spriteIndex].append(spike1.$el);
spikeArray.push(spike1);
}
if (spikeArray.length < 2 && levels >= 2) {
@ -1318,9 +1318,9 @@ export class BattleScene implements BattleSceneStub {
x: x + 30,
y: y - 45,
z: side.z,
scale: .3,
scale: 0.3,
}, this);
this.$spritesFront[spriteIndex].append(spike2.$el!);
this.$spritesFront[spriteIndex].append(spike2.$el);
spikeArray.push(spike2);
}
if (spikeArray.length < 3 && levels >= 3) {
@ -1329,9 +1329,9 @@ export class BattleScene implements BattleSceneStub {
x: x + 50,
y: y - 40,
z: side.z,
scale: .3,
scale: 0.3,
}, this);
this.$spritesFront[spriteIndex].append(spike3.$el!);
this.$spritesFront[spriteIndex].append(spike3.$el);
spikeArray.push(spike3);
}
break;
@ -1350,7 +1350,7 @@ export class BattleScene implements BattleSceneStub {
z: side.z,
scale: 0.3,
}, this);
this.$spritesFront[spriteIndex].append(tspike1.$el!);
this.$spritesFront[spriteIndex].append(tspike1.$el);
tspikeArray.push(tspike1);
}
if (tspikeArray.length < 2 && tspikeLevels >= 2) {
@ -1359,9 +1359,9 @@ export class BattleScene implements BattleSceneStub {
x: x - 15,
y: y - 35,
z: side.z,
scale: .3,
scale: 0.3,
}, this);
this.$spritesFront[spriteIndex].append(tspike2.$el!);
this.$spritesFront[spriteIndex].append(tspike2.$el);
tspikeArray.push(tspike2);
}
break;
@ -1374,7 +1374,7 @@ export class BattleScene implements BattleSceneStub {
opacity: 0.4,
scale: 0.7,
}, this);
this.$spritesFront[spriteIndex].append(web.$el!);
this.$spritesFront[spriteIndex].append(web.$el);
this.sideConditions[siden][id] = [web];
break;
}
@ -1399,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" />'
`<img src="${Dex.resourcePrefix}sprites/types/${encodeURIComponent(type)}.png" alt="${type}" class="pixelated" />`
).join(' ');
this.resultAnim(pokemon, result, 'neutral');
}
@ -1425,7 +1425,7 @@ export class BattleScene implements BattleSceneStub {
}
abilityActivateAnim(pokemon: Pokemon, result: string) {
if (!this.animating) return;
this.$fx.append('<div class="result abilityresult"><strong>' + result + '</strong></div>');
this.$fx.append(`<div class="result abilityresult"><strong>${result}</strong></div>`);
let $effect = this.$fx.children().last();
$effect.delay(this.timeOffset).css({
display: 'block',
@ -1459,7 +1459,7 @@ export class BattleScene implements BattleSceneStub {
}
if (damage === '100%' && pokemon.hp > 0) damage = '99%';
this.resultAnim(pokemon, this.battle.hardcoreMode ? 'Damage' : '&minus;' + damage, 'bad');
this.resultAnim(pokemon, this.battle.hardcoreMode ? 'Damage' : `&minus;${damage}`, 'bad');
$hp.animate({
width: w,
@ -1482,7 +1482,7 @@ export class BattleScene implements BattleSceneStub {
callback = () => { $hp.removeClass('hp-red'); };
}
this.resultAnim(pokemon, this.battle.hardcoreMode ? 'Heal' : '+' + damage, 'good');
this.resultAnim(pokemon, this.battle.hardcoreMode ? 'Heal' : `+${damage}`, 'good');
$hp.animate({
width: w,
@ -1732,7 +1732,7 @@ export class Sprite {
if (spriteData) {
sp = spriteData;
let rawHTML = sp.rawHTML ||
'<img src="' + sp.url + '" style="display:none;position:absolute"' + (sp.pixelated ? ' class="pixelated"' : '') + ' />';
`<img src="${sp.url!}" style="display:none;position:absolute"${sp.pixelated ? ' class="pixelated"' : ''} />`;
this.$el = $(rawHTML);
} else {
sp = {
@ -1746,7 +1746,7 @@ export class Sprite {
this.x = pos.x;
this.y = pos.y;
this.z = pos.z;
if (pos.opacity !== 0 && spriteData) this.$el!.css(scene.pos(pos, sp));
if (pos.opacity !== 0 && spriteData) this.$el.css(scene.pos(pos, sp));
if (!spriteData) {
this.delay = function () { return this; };
@ -1760,7 +1760,7 @@ export class Sprite {
this.scene = null!;
}
delay(time: number) {
this.$el!.delay(time);
this.$el.delay(time);
return this;
}
anim(end: ScenePos, transition?: string) {
@ -1774,10 +1774,10 @@ export class Sprite {
...end,
};
if (end.time === 0) {
this.$el!.css(this.scene.pos(end, this.sp));
this.$el.css(this.scene.pos(end, this.sp));
return this;
}
this.$el!.animate(this.scene.posT(end, this.sp, transition, this), end.time!);
this.$el.animate(this.scene.posT(end, this.sp, transition, this), end.time!);
return this;
}
}
@ -1985,7 +1985,7 @@ export class PokemonSprite extends Sprite {
x: this.x,
y: this.y,
z: (this.isSubActive ? this.behind(30) : this.z),
opacity: (this.$sub ? .3 : 1),
opacity: (this.$sub ? 0.3 : 1),
}, sp));
}
animSub(instant?: boolean, noAnim?: boolean) {
@ -2128,7 +2128,7 @@ export class PokemonSprite extends Sprite {
if (this.$el) {
this.$el.stop(true, false);
this.$el.remove();
const $newEl = $('<img src="' + this.sp.url + '" style="display:none;position:absolute"' + (this.sp.pixelated ? ' class="pixelated"' : '') + ' />');
const $newEl = $(`<img src="${this.sp.url!}" style="display:none;position:absolute"${this.sp.pixelated ? ' class="pixelated"' : ''} />`);
this.$el = $newEl;
}
@ -2164,7 +2164,7 @@ export class PokemonSprite extends Sprite {
x: this.x,
y: this.y,
z: this.behind(30),
opacity: .3,
opacity: 0.3,
}, this.sp));
this.$sub.css(this.scene.pos({
x: this.x,
@ -2278,7 +2278,7 @@ export class PokemonSprite extends Sprite {
x: this.x,
y: this.y + 30,
z: this.behind(50),
scale: .7,
scale: 0.7,
}, {
opacity: 1,
x: this.x,
@ -2409,7 +2409,7 @@ export class PokemonSprite extends Sprite {
left: this.statbarLeft - (this.isFrontSprite ? -100 : 100),
opacity: 0,
}, 300 / this.scene.acceleration, () => {
$statbar!.remove();
$statbar.remove();
});
}
}
@ -2447,7 +2447,7 @@ export class PokemonSprite extends Sprite {
x: this.x,
y: this.y - 40,
z: this.z,
scale: .7,
scale: 0.7,
time: 300 / this.scene.acceleration,
}, {
opacity: 0,
@ -2466,7 +2466,7 @@ export class PokemonSprite extends Sprite {
left: this.statbarLeft + (this.isFrontSprite ? 50 : -50),
opacity: 0,
}, 300 / this.scene.acceleration, () => {
$statbar!.remove();
$statbar.remove();
});
}
}
@ -2500,7 +2500,7 @@ export class PokemonSprite extends Sprite {
$statbar.animate({
opacity: 0,
}, 300, () => {
$statbar!.remove();
$statbar.remove();
});
}
}
@ -2617,7 +2617,7 @@ export class PokemonSprite extends Sprite {
opacity: 1,
time: 100,
}).anim({
opacity: .4,
opacity: 0.4,
time: 300,
});
}
@ -2636,32 +2636,32 @@ export class PokemonSprite extends Sprite {
x: this.x - 30,
y: this.y - 40,
z: this.z,
scale: .2,
opacity: .6,
scale: 0.2,
opacity: 0.6,
};
const pos2 = {
display: 'block',
x: this.x + 40,
y: this.y - 35,
z: this.z,
scale: .2,
opacity: .6,
scale: 0.2,
opacity: 0.6,
};
const pos3 = {
display: 'block',
x: this.x + 20,
y: this.y - 25,
z: this.z,
scale: .2,
opacity: .6,
scale: 0.2,
opacity: 0.6,
};
const leechseed1 = new Sprite(BattleEffects.energyball, pos1, this.scene);
const leechseed2 = new Sprite(BattleEffects.energyball, pos2, this.scene);
const leechseed3 = new Sprite(BattleEffects.energyball, pos3, this.scene);
this.scene.$spritesFront[spriten].append(leechseed1.$el!);
this.scene.$spritesFront[spriten].append(leechseed2.$el!);
this.scene.$spritesFront[spriten].append(leechseed3.$el!);
this.scene.$spritesFront[spriten].append(leechseed1.$el);
this.scene.$spritesFront[spriten].append(leechseed2.$el);
this.scene.$spritesFront[spriten].append(leechseed3.$el);
this.effects['leechseed'] = [leechseed1, leechseed2, leechseed3];
} else if (id === 'protect' || id === 'magiccoat') {
const protect = new Sprite(BattleEffects.protect, {
@ -2671,15 +2671,15 @@ export class PokemonSprite extends Sprite {
z: this.behind(-15),
xscale: 1,
yscale: 0,
opacity: .1,
opacity: 0.1,
}, this.scene);
this.scene.$spritesFront[spriten].append(protect.$el!);
this.scene.$spritesFront[spriten].append(protect.$el);
this.effects[id] = [protect];
protect.anim({
opacity: .9,
opacity: 0.9,
time: instant ? 0 : 400,
}).anim({
opacity: .4,
opacity: 0.4,
time: instant ? 0 : 300,
});
}
@ -2702,7 +2702,7 @@ export class PokemonSprite extends Sprite {
dogarsCheck(pokemon: Pokemon) {
if (pokemon.side.isFar) return;
if (pokemon.speciesForme === 'Koffing' && pokemon.name.match(/dogars/i)) {
if (pokemon.speciesForme === 'Koffing' && (/dogars/i.exec(pokemon.name))) {
this.scene.setBgm(-1);
} else if (this.scene.bgmNum === -1) {
this.scene.rollBgm();
@ -2734,7 +2734,7 @@ export class PokemonSprite extends Sprite {
buf += (pokemon.level === 100 ? `` : ` <small>L${pokemon.level}</small>`);
let symbol = '';
if (pokemon.speciesForme.indexOf('-Mega') >= 0) symbol = 'mega';
if (pokemon.speciesForme.includes('-Mega')) symbol = 'mega';
else if (pokemon.speciesForme === 'Kyogre-Primal') symbol = 'alpha';
else if (pokemon.speciesForme === 'Groudon-Primal') symbol = 'omega';
if (symbol) {
@ -2852,7 +2852,7 @@ export class PokemonSprite extends Sprite {
private static getEffectTag(id: string) {
let effect = PokemonSprite.statusTable[id];
if (typeof effect === 'string') return effect;
if (effect === null) return PokemonSprite.statusTable[id] = '';
if (effect === null) return (PokemonSprite.statusTable[id] = '');
if (effect === undefined) {
let label = `[[${id}]]`;
if (Dex.species.get(id).exists) {
@ -2868,7 +2868,7 @@ export class PokemonSprite extends Sprite {
}
effect = [label, 'neutral'];
}
return PokemonSprite.statusTable[id] = `<span class="${effect[1]}">${effect[0].replace(/ /g, '&nbsp;')}</span> `;
return (PokemonSprite.statusTable[id] = `<span class="${effect[1]}">${effect[0].replace(/ /g, '&nbsp;')}</span> `);
}
updateHPText(pokemon: Pokemon) {
@ -2879,11 +2879,11 @@ export class PokemonSprite extends Sprite {
$hptext.hide();
$hptextborder.hide();
} else if (this.scene.battle.hardcoreMode || this.scene.battle.reportExactHP) {
$hptext.html(pokemon.hp + '/');
$hptext.html(`${pokemon.hp}/`);
$hptext.show();
$hptextborder.show();
} else {
$hptext.html(pokemon.hpWidth(100) + '%');
$hptext.html(`${pokemon.hpWidth(100)}%`);
$hptext.show();
$hptextborder.show();
}
@ -2897,7 +2897,6 @@ export class PokemonSprite extends Sprite {
// slp: -webkit-filter: grayscale(100%);
// frz: -webkit-filter: sepia(100%) hue-rotate(154deg) saturate(759%) brightness(23%);
// @ts-ignore
Object.assign($.easing, {
ballisticUp(x: number, t: number, b: number, c: number, d: number) {
return -3 * x * x + 4 * x;

View File

@ -429,7 +429,7 @@ export class BattleChoiceBuilder {
throw new Error(`Couldn't find Pokémon "${choice}" to switch to!`);
}
if (target.fainted) {
throw new Error(`${target} is fainted and cannot battle!`);
throw new Error(`${target.name} is fainted and cannot battle!`);
}
return current;
}

View File

@ -1252,7 +1252,7 @@ export class Move implements Effect {
readonly basePowerCallback: boolean;
readonly noPPBoosts: boolean;
readonly status: string;
readonly secondaries: ReadonlyArray<any> | null;
readonly secondaries: readonly any[] | null;
readonly num: number;
constructor(id: ID, name: string, data: any) {
@ -1468,7 +1468,7 @@ export class Species implements Effect {
// basic data
readonly num: number;
readonly types: ReadonlyArray<TypeName>;
readonly types: readonly TypeName[];
readonly abilities: Readonly<{
0: string, 1?: string, H?: string, S?: string,
}>;
@ -1483,20 +1483,20 @@ export class Species implements Effect {
readonly gender: GenderName;
readonly color: string;
readonly genderRatio: Readonly<{ M: number, F: number }> | null;
readonly eggGroups: ReadonlyArray<string>;
readonly tags: ReadonlyArray<string>;
readonly eggGroups: readonly string[];
readonly tags: readonly string[];
// format data
readonly otherFormes: ReadonlyArray<string> | null;
readonly cosmeticFormes: ReadonlyArray<string> | null;
readonly evos: ReadonlyArray<string> | null;
readonly otherFormes: readonly string[] | null;
readonly cosmeticFormes: readonly string[] | null;
readonly evos: readonly string[] | null;
readonly prevo: string;
readonly evoType: 'trade' | 'useItem' | 'levelMove' | 'levelExtra' | 'levelFriendship' | 'levelHold' | 'other' | '';
readonly evoLevel: number;
readonly evoMove: string;
readonly evoItem: string;
readonly evoCondition: string;
readonly requiredItems: ReadonlyArray<string>;
readonly requiredItems: readonly string[];
readonly tier: string;
readonly isTotem: boolean;
readonly isMega: boolean;
@ -1521,9 +1521,9 @@ export class Species implements Effect {
const baseId = toID(this.baseSpecies);
this.formeid = (baseId === this.id ? '' : '-' + toID(this.forme));
this.spriteid = baseId + this.formeid;
if (this.spriteid.slice(-5) === 'totem') this.spriteid = this.spriteid.slice(0, -5);
if (this.spriteid.endsWith('totem')) this.spriteid = this.spriteid.slice(0, -5);
if (this.spriteid === 'greninja-bond') this.spriteid = 'greninja';
if (this.spriteid.slice(-1) === '-') this.spriteid = this.spriteid.slice(0, -1);
if (this.spriteid.endsWith('-')) this.spriteid = this.spriteid.slice(0, -1);
this.baseForme = data.baseForme || '';
this.num = data.num || 0;

View File

@ -11,7 +11,7 @@
* @license MIT
*/
import {Dex, ModdedDex, toID, type ID} from "./battle-dex";
import { Dex, type ModdedDex, toID, type ID } from "./battle-dex";
type SearchType = (
'pokemon' | 'type' | 'tier' | 'move' | 'item' | 'ability' | 'egggroup' | 'category' | 'article'
@ -217,7 +217,7 @@ export class DexSearch {
/** searching for "Psychic type" will make the type come up over the move */
let qFilterType: 'type' | '' = '';
if (query.slice(-4) === 'type') {
if (query.endsWith('type')) {
if (query.slice(0, -4) in window.BattleTypeChart) {
query = query.slice(0, -4);
qFilterType = 'type';
@ -265,7 +265,7 @@ export class DexSearch {
// the alias text before any other passes.
let queryAlias;
if (query in BattleAliases) {
if (['sub', 'tr'].includes(query) || toID(BattleAliases[query]).slice(0, query.length) !== query) {
if (['sub', 'tr'].includes(query) || !toID(BattleAliases[query]).startsWith(query)) {
queryAlias = toID(BattleAliases[query]);
let aliasPassType: SearchPassType = (queryAlias === 'hiddenpower' ? 'exact' : 'normal');
searchPasses.unshift([aliasPassType, DexSearch.getClosest(queryAlias), queryAlias]);
@ -576,7 +576,7 @@ abstract class BattleTypedSearch<T extends SearchType> {
this.baseResults = null;
this.baseIllegalResults = null;
if (format.slice(0, 3) === 'gen') {
if (format.startsWith('gen')) {
const gen = (Number(format.charAt(3)) || 6);
format = (format.slice(4) || 'customgame') as ID;
this.dex = Dex.forGen(gen);
@ -668,10 +668,10 @@ abstract class BattleTypedSearch<T extends SearchType> {
if (typeof speciesOrSet === 'string') {
if (speciesOrSet) this.species = speciesOrSet;
} else {
this.set = speciesOrSet as Dex.PokemonSet;
this.set = speciesOrSet;
this.species = toID(this.set.species);
}
if (!searchType || !this.set) return;
// if (!searchType || !this.set) return;
}
getResults(filters?: SearchFilter[] | null, sortCol?: string | null, reverseSort?: boolean): SearchRow[] {
if (sortCol === 'type') {
@ -742,7 +742,7 @@ abstract class BattleTypedSearch<T extends SearchType> {
if (this.sortRow) {
results = [this.sortRow, ...results];
}
if (illegalResults && illegalResults.length) {
if (illegalResults?.length) {
results = [...results, ['header', "Illegal results"], ...illegalResults];
}
return results;
@ -856,7 +856,7 @@ abstract class BattleTypedSearch<T extends SearchType> {
this.formatType === 'natdex' ? `gen${gen}natdex` :
this.formatType === 'stadium' ? `gen${gen}stadium${gen > 1 ? gen : ''}` :
`gen${gen}`;
if (table && table[tableKey]) {
if (table?.[tableKey]) {
table = table[tableKey];
}
if (!table) return pokemon.tier;
@ -865,7 +865,7 @@ abstract class BattleTypedSearch<T extends SearchType> {
if (id in table.overrideTier) {
return table.overrideTier[id];
}
if (id.slice(-5) === 'totem' && id.slice(0, -5) in table.overrideTier) {
if (id.endsWith('totem') && id.slice(0, -5) in table.overrideTier) {
return table.overrideTier[id.slice(0, -5)];
}
id = toID(pokemon.baseSpecies);
@ -951,13 +951,13 @@ class BattlePokemonSearch extends BattleTypedSearch<'pokemon'> {
let table = BattleTeambuilderTable;
if ((format.endsWith('cap') || format.endsWith('caplc')) && dex.gen < 9) {
table = table['gen' + dex.gen];
table = table[`gen${dex.gen}`];
} else if (isVGCOrBS) {
table = table['gen' + dex.gen + 'vgc'];
table = table[`gen${dex.gen}vgc`];
} else if (dex.gen === 9 && isHackmons && !this.formatType) {
table = table['bh'];
} else if (
table['gen' + dex.gen + 'doubles'] && dex.gen > 4 &&
table[`gen${dex.gen}doubles`] && dex.gen > 4 &&
this.formatType !== 'letsgo' && this.formatType !== 'bdspdoubles' &&
this.formatType !== 'ssdlc1doubles' && this.formatType !== 'predlcdoubles' &&
this.formatType !== 'svdlc1doubles' && !this.formatType?.includes('natdex') &&
@ -967,10 +967,10 @@ class BattlePokemonSearch extends BattleTypedSearch<'pokemon'> {
format === 'partnersincrime'
)
) {
table = table['gen' + dex.gen + 'doubles'];
table = table[`gen${dex.gen}doubles`];
isDoublesOrBS = true;
} else if (dex.gen < 9 && !this.formatType) {
table = table['gen' + dex.gen];
table = table[`gen${dex.gen}`];
} else if (this.formatType?.startsWith('bdsp')) {
table = table['gen8' + this.formatType];
} else if (this.formatType === 'letsgo') {
@ -978,13 +978,13 @@ class BattlePokemonSearch extends BattleTypedSearch<'pokemon'> {
} else if (this.formatType === 'bw1') {
table = table['gen5bw1'];
} else if (this.formatType === 'natdex') {
table = table['gen' + dex.gen + 'natdex'];
table = table[`gen${dex.gen}natdex`];
} else if (this.formatType === 'metronome') {
table = table['gen' + dex.gen + 'metronome'];
table = table[`gen${dex.gen}metronome`];
} else if (this.formatType === 'nfe') {
table = table['gen' + dex.gen + 'nfe'];
table = table[`gen${dex.gen}nfe`];
} else if (this.formatType === 'lc') {
table = table['gen' + dex.gen + 'lc'];
table = table[`gen${dex.gen}lc`];
} else if (this.formatType?.startsWith('ssdlc1')) {
if (this.formatType.includes('doubles')) {
table = table['gen8dlc1doubles'];
@ -1008,7 +1008,7 @@ class BattlePokemonSearch extends BattleTypedSearch<'pokemon'> {
table = table['gen9dlc1'];
}
} else if (this.formatType === 'stadium') {
table = table['gen' + dex.gen + 'stadium' + (dex.gen > 1 ? dex.gen : '')];
table = table[`gen${dex.gen}stadium${dex.gen > 1 ? dex.gen : ''}`];
}
if (!table.tierSet) {
@ -1049,7 +1049,9 @@ class BattlePokemonSearch extends BattleTypedSearch<'pokemon'> {
else if (format === 'pu') tierSet = tierSet.slice(slices.PU || slices.NU);
else if (format === 'zu' && dex.gen === 5) tierSet = tierSet.slice(slices.PU || slices.NU);
else if (format === 'zu') tierSet = tierSet.slice(slices.ZU || slices.PU || slices.NU);
else if (format === 'lc' || format === 'lcuu' || format.startsWith('lc') || (format !== 'caplc' && format.endsWith('lc'))) tierSet = tierSet.slice(slices.LC);
else if (
format === 'lc' || format === 'lcuu' || format.startsWith('lc') || (format !== 'caplc' && format.endsWith('lc'))
) tierSet = tierSet.slice(slices.LC);
else if (format === 'cap' || format.endsWith('cap')) {
tierSet = tierSet.slice(0, slices.AG || slices.Uber).concat(tierSet.slice(slices.OU));
} else if (format === 'caplc') {
@ -1276,11 +1278,11 @@ class BattleItemSearch extends BattleTypedSearch<'item'> {
} else if (this.formatType === 'bw1') {
table = table['gen5bw1'];
} else if (this.formatType === 'natdex') {
table = table['gen' + this.dex.gen + 'natdex'];
table = table[`gen${this.dex.gen}natdex`];
} else if (this.formatType === 'metronome') {
table = table['gen' + this.dex.gen + 'metronome'];
table = table[`gen${this.dex.gen}metronome`];
} else if (this.dex.gen < 9) {
table = table['gen' + this.dex.gen];
table = table[`gen${this.dex.gen}`];
}
if (!table.itemSet) {
table.itemSet = table.items.map((r: any) => {
@ -1635,7 +1637,7 @@ class BattleMoveSearch extends BattleTypedSearch<'move'> {
let moves: string[] = [];
let sketchMoves: string[] = [];
let sketch = false;
let gen = '' + dex.gen;
let gen = `${dex.gen}`;
let lsetTable = BattleTeambuilderTable;
if (this.formatType?.startsWith('bdsp')) lsetTable = lsetTable['gen8bdsp'];
if (this.formatType === 'letsgo') lsetTable = lsetTable['gen7letsgo'];
@ -1661,7 +1663,7 @@ class BattleMoveSearch extends BattleTypedSearch<'move'> {
}
if (
!learnsetEntry.includes(gen) &&
(!isTradebacks ? true : !(move.gen <= dex.gen && learnsetEntry.includes('' + (dex.gen + 1))))
(!isTradebacks ? true : !(move.gen <= dex.gen && learnsetEntry.includes(`${dex.gen + 1}`)))
) {
continue;
}

View File

@ -23,11 +23,10 @@ import {
BattleAvatarNumbers, BattleBaseSpeciesChart, BattlePokemonIconIndexes, BattlePokemonIconIndexesLeft, BattleStatNames,
Ability, Item, Move, Species, PureEffect, type ID, type Type,
} from "./battle-dex-data";
// tslint:disable-next-line
import * as DexData from "./battle-dex-data";
import type * as DexData from "./battle-dex-data";
export declare namespace Dex {
/* tslint:disable:no-shadowed-variable */
/* eslint-disable @typescript-eslint/no-shadow */
export type Ability = DexData.Ability;
export type Item = DexData.Item;
export type Move = DexData.Move;
@ -37,7 +36,7 @@ export declare namespace Dex {
export type PureEffect = DexData.PureEffect;
export type Effect = DexData.Effect;
export type ID = DexData.ID;
/* tslint:enable:no-shadowed-variable */
/* eslint-enable @typescript-eslint/no-shadow */
export type StatName = DexData.StatName;
export type StatNameExceptHP = DexData.StatNameExceptHP;
export type BoostStatName = DexData.BoostStatName;
@ -91,6 +90,7 @@ export type {ID};
declare const require: any;
declare const global: any;
declare const process: any;
if (typeof window === 'undefined') {
// Node
@ -100,8 +100,7 @@ if (typeof window === 'undefined') {
window.exports = window;
}
// @ts-ignore
window.nodewebkit = !!(typeof process !== 'undefined' && process.versions && process.versions['node-webkit']);
window.nodewebkit = !!(typeof process !== 'undefined' && process.versions?.['node-webkit']);
export function toID(text: any) {
if (text?.id) {
@ -110,7 +109,7 @@ export function toID(text: any) {
text = text.userid;
}
if (typeof text !== 'string' && typeof text !== 'number') return '' as ID;
return ('' + text).toLowerCase().replace(/[^a-z0-9]+/g, '') as ID;
return `${text}`.toLowerCase().replace(/[^a-z0-9]+/g, '') as ID;
}
export function toUserid(text: any) {
@ -129,7 +128,7 @@ export const PSUtils = new class {
*
* Returns an array of length exactly limit + 1.
*/
splitFirst(str: string, delimiter: string, limit: number = 1) {
splitFirst(str: string, delimiter: string, limit = 1) {
let splitStr: string[] = [];
while (splitStr.length < limit) {
let delimiterIndex = str.indexOf(delimiter);
@ -176,7 +175,7 @@ export const PSUtils = new class {
if (a.reverse) {
return PSUtils.compare((b as { reverse: string }).reverse, a.reverse);
}
throw new Error(`Passed value ${a} is not comparable`);
throw new Error(`Passed value ${a as any} is not comparable`);
}
/**
* Sorts an array according to the callback's output on its elements.
@ -206,7 +205,7 @@ export function toRoomid(roomid: string) {
export function toName(name: any) {
if (typeof name !== 'string' && typeof name !== 'number') return '';
name = ('' + name).replace(/[\|\s\[\]\,\u202e]+/g, ' ').trim();
name = `${name}`.replace(/[|\s[\],\u202e]+/g, ' ').trim();
if (name.length > 18) name = name.substr(0, 18).trim();
// remove zalgo
@ -250,8 +249,8 @@ export const Dex = new class implements ModdedDex {
readonly modid = 'gen9' as ID;
readonly cache = null!;
readonly statNames: ReadonlyArray<Dex.StatName> = ['hp', 'atk', 'def', 'spa', 'spd', 'spe'];
readonly statNamesExceptHP: ReadonlyArray<Dex.StatNameExceptHP> = ['atk', 'def', 'spa', 'spd', 'spe'];
readonly statNames: readonly Dex.StatName[] = ['hp', 'atk', 'def', 'spa', 'spd', 'spe'];
readonly statNamesExceptHP: readonly Dex.StatNameExceptHP[] = ['atk', 'def', 'spa', 'spd', 'spe'];
pokeballs: string[] | null = null;
@ -287,14 +286,14 @@ export const Dex = new class implements ModdedDex {
if (window.BattleAvatarNumbers && avatar in BattleAvatarNumbers) {
avatar = BattleAvatarNumbers[avatar];
}
if (avatar.charAt(0) === '#') {
if (avatar.startsWith('#')) {
return Dex.resourcePrefix + 'sprites/trainers-custom/' + toID(avatar.substr(1)) + '.png';
}
if (avatar.includes('.') && window.Config?.server?.registered) {
// custom avatar served by the server
let protocol = (Config.server.port === 443) ? 'https' : 'http';
return protocol + '://' + Config.server.host + ':' + Config.server.port +
'/avatars/' + encodeURIComponent(avatar).replace(/\%3F/g, '?');
'/avatars/' + encodeURIComponent(avatar).replace(/%3F/g, '?');
}
return Dex.resourcePrefix + 'sprites/trainers/' + Dex.sanitizeName(avatar || 'unknown') + '.png';
}
@ -318,14 +317,14 @@ export const Dex = new class implements ModdedDex {
}
prefs(prop: string) {
// @ts-ignore
// @ts-expect-error this is what I get for calling it Storage...
return window.Storage?.prefs?.(prop);
}
getShortName(name: string) {
let shortName = name.replace(/[^A-Za-z0-9]+$/, '');
if (shortName.indexOf('(') >= 0) {
shortName += name.slice(shortName.length).replace(/[^\(\)]+/g, '').replace(/\(\)/g, '');
if (shortName.includes('(')) {
shortName += name.slice(shortName.length).replace(/[^()]+/g, '').replace(/\(\)/g, '');
}
return shortName;
}
@ -466,7 +465,7 @@ export const Dex = new class implements ModdedDex {
species = data;
} else {
if (!data) data = { exists: false };
if (!data.tier && id.slice(-5) === 'totem') {
if (!data.tier && id.endsWith('totem')) {
data.tier = this.species.get(id.slice(0, -5)).tier;
}
if (!data.tier && data.baseSpecies && toID(data.baseSpecies) !== id) {
@ -503,7 +502,7 @@ export const Dex = new class implements ModdedDex {
if (!type || typeof type === 'string') {
const id = toID(type) as string;
const name = id.substr(0, 1).toUpperCase() + id.substr(1);
type = (window.BattleTypeChart && window.BattleTypeChart[id]) || {};
type = window.BattleTypeChart?.[id] || {};
if (type.damageTaken) type.exists = true;
if (!type.id) type.id = id;
if (!type.name) type.name = name;
@ -525,14 +524,13 @@ export const Dex = new class implements ModdedDex {
isName: (name: string | null): boolean => {
const id = toID(name);
if (name !== id.substr(0, 1).toUpperCase() + id.substr(1)) return false;
return (window.BattleTypeChart || {}).hasOwnProperty(id);
return window.BattleTypeChart?.hasOwnProperty(id);
},
};
hasAbility(species: Species, ability: string) {
for (const i in species.abilities) {
// @ts-ignore
if (ability === species.abilities[i]) return true;
if (ability === species.abilities[i as '0']) return true;
}
return false;
}
@ -543,7 +541,7 @@ export const Dex = new class implements ModdedDex {
let path = $('script[src*="pokedex-mini.js"]').attr('src') || '';
let qs = '?' + (path.split('?')[1] || '');
path = (path.match(/.+?(?=data\/pokedex-mini\.js)/) || [])[0] || '';
path = ((/.+?(?=data\/pokedex-mini\.js)/.exec(path)) || [])[0] || '';
let el = document.createElement('script');
el.src = path + 'data/pokedex-mini-bw.js' + qs;
@ -706,7 +704,7 @@ export const Dex = new class implements ModdedDex {
let allowAnim = !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';
if (facing.endsWith('f')) name += '-f';
dir = baseDir + 'ani' + dir;
spriteData.w = animationData[facing].w;
@ -794,31 +792,32 @@ export const Dex = new class implements ModdedDex {
let id = toID(pokemon);
if (!pokemon || typeof pokemon === 'string') pokemon = null;
// @ts-ignore
// @ts-expect-error safe, but too lazy to cast
if (pokemon?.speciesForme) id = toID(pokemon.speciesForme);
// @ts-ignore
// @ts-expect-error safe, but too lazy to cast
if (pokemon?.species) id = toID(pokemon.species);
// @ts-ignore
// @ts-expect-error safe, but too lazy to cast
if (pokemon?.volatiles?.formechange && !pokemon.volatiles.transform) {
// @ts-ignore
// @ts-expect-error safe, but too lazy to cast
id = toID(pokemon.volatiles.formechange[1]);
}
let num = this.getPokemonIconNum(id, pokemon?.gender === 'F', facingLeft);
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)` : ``);
let fainted = ((pokemon as Pokemon | ServerPokemon)?.fainted ?
`;opacity:.3;filter:grayscale(100%) brightness(.5)` : ``);
return `background:transparent url(${Dex.resourcePrefix}sprites/pokemonicons-sheet.png?v18) no-repeat scroll -${left}px -${top}px${fainted}`;
}
getTeambuilderSpriteData(pokemon: any, gen: number = 0): TeambuilderSpriteData {
getTeambuilderSpriteData(pokemon: any, gen = 0): TeambuilderSpriteData {
let id = toID(pokemon.species);
let spriteid = pokemon.spriteid;
let species = Dex.species.get(pokemon.species);
if (pokemon.species && !spriteid) {
spriteid = species.spriteid || toID(pokemon.species);
}
if (species.exists === false) return { spriteDir: 'sprites/gen5', spriteid: '0', x: 10, y: 5 };
if (!species.exists) return { spriteDir: 'sprites/gen5', spriteid: '0', x: 10, y: 5 };
if (window.Config?.server?.afd || Dex.prefs('afd')) {
return {
spriteid,
@ -867,11 +866,11 @@ export const Dex = new class implements ModdedDex {
return spriteData;
}
getTeambuilderSprite(pokemon: any, gen: number = 0) {
getTeambuilderSprite(pokemon: any, gen = 0) {
if (!pokemon) return '';
const data = this.getTeambuilderSpriteData(pokemon, gen);
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';
return `background-image:url(${Dex.resourcePrefix}${data.spriteDir}${shiny}/${data.spriteid}.png);background-position:${data.x}px ${data.y}px;background-repeat:no-repeat`;
}
getItemIcon(item: any) {
@ -881,7 +880,7 @@ export const Dex = new class implements ModdedDex {
let top = Math.floor(num / 16) * 24;
let left = (num % 16) * 24;
return 'background:transparent url(' + Dex.resourcePrefix + 'sprites/itemicons-sheet.png?v1) no-repeat scroll -' + left + 'px -' + top + 'px';
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
@ -911,7 +910,7 @@ export const Dex = new class implements ModdedDex {
if (this.pokeballs) return this.pokeballs;
this.pokeballs = [];
if (!window.BattleItems) window.BattleItems = {};
for (const data of Object.values(window.BattleItems) as AnyObject[]) {
for (const data of Object.values<AnyObject>(window.BattleItems)) {
if (!data.isPokeball) continue;
this.pokeballs.push(data.name);
}
@ -923,11 +922,11 @@ export class ModdedDex {
readonly gen: number;
readonly modid: ID;
readonly cache = {
Moves: {} as any as {[k: string]: Move},
Items: {} as any as {[k: string]: Item},
Abilities: {} as any as {[k: string]: Ability},
Species: {} as any as {[k: string]: Species},
Types: {} as any as {[k: string]: Dex.Effect},
Moves: {} as { [k: string]: Move },
Items: {} as { [k: string]: Item },
Abilities: {} as { [k: string]: Ability },
Species: {} as { [k: string]: Species },
Types: {} as { [k: string]: Dex.Effect },
};
pokeballs: string[] | null = null;
constructor(modid: ID) {
@ -1058,7 +1057,7 @@ export class ModdedDex {
const table = window.BattleTeambuilderTable[this.modid];
if (id in table.overrideTier) data.tier = table.overrideTier[id];
if (!data.tier && id.slice(-5) === 'totem') {
if (!data.tier && id.endsWith('totem')) {
data.tier = this.species.get(id.slice(0, -5)).tier;
}
if (!data.tier && data.baseSpecies && toID(data.baseSpecies) !== id) {
@ -1074,7 +1073,7 @@ export class ModdedDex {
types = {
get: (name: string): Dex.Effect => {
const id = toID(name) as ID;
const id = toID(name);
name = id.substr(0, 1).toUpperCase() + id.substr(1);
if (this.cache.Types.hasOwnProperty(id)) return this.cache.Types[id];
@ -1082,7 +1081,7 @@ export class ModdedDex {
let data = { ...Dex.types.get(name) };
for (let i = 7; i >= this.gen; i--) {
const table = window.BattleTeambuilderTable['gen' + i];
const table = window.BattleTeambuilderTable[`gen${i}`];
if (id in table.removeType) {
data.exists = false;
// don't bother correcting its attributes given it doesn't exist
@ -1102,7 +1101,7 @@ export class ModdedDex {
if (this.pokeballs) return this.pokeballs;
this.pokeballs = [];
if (!window.BattleItems) window.BattleItems = {};
for (const data of Object.values(window.BattleItems) as AnyObject[]) {
for (const data of Object.values<AnyObject>(window.BattleItems)) {
if (data.gen && data.gen > this.gen) continue;
if (!data.isPokeball) continue;
this.pokeballs.push(data.name);
@ -1148,9 +1147,9 @@ export const Teams = new class {
// moves
j = buf.indexOf('|', i);
set.moves = buf.substring(i, j).split(',').map(function (moveid) {
return Dex.moves.get(moveid).name;
});
set.moves = buf.substring(i, j).split(',').map(
moveid => Dex.moves.get(moveid).name
);
i = j + 1;
// nature
@ -1234,49 +1233,49 @@ export const Teams = new class {
export(team: Dex.PokemonSet[] | string, gen: number, hidestats = false) {
if (!team) return '';
if (typeof team === 'string') {
if (team.indexOf('\n') >= 0) return team;
if (team.includes('\n')) return team;
team = this.unpack(team);
}
let text = '';
for (const curSet of team) {
if (curSet.name && curSet.name !== curSet.species) {
text += '' + curSet.name + ' (' + curSet.species + ')';
text += `${curSet.name} (${curSet.species})`;
} else {
text += '' + curSet.species;
text += `${curSet.species}`;
}
if (curSet.gender === 'M') text += ' (M)';
if (curSet.gender === 'F') text += ' (F)';
if (curSet.item) {
text += ' @ ' + curSet.item;
text += ` @ ${curSet.item}`;
}
text += " \n";
if (curSet.ability) {
text += 'Ability: ' + curSet.ability + " \n";
text += `Ability: ${curSet.ability} \n`;
}
if (curSet.level && curSet.level !== 100) {
text += 'Level: ' + curSet.level + " \n";
text += `Level: ${curSet.level} \n`;
}
if (curSet.shiny) {
text += 'Shiny: Yes \n';
}
if (typeof curSet.happiness === 'number' && curSet.happiness !== 255 && !isNaN(curSet.happiness)) {
text += 'Happiness: ' + curSet.happiness + " \n";
text += `Happiness: ${curSet.happiness} \n`;
}
if (curSet.pokeball) {
text += 'Pokeball: ' + curSet.pokeball + " \n";
text += `Pokeball: ${curSet.pokeball} \n`;
}
if (curSet.hpType) {
text += 'Hidden Power: ' + curSet.hpType + " \n";
text += `Hidden Power: ${curSet.hpType} \n`;
}
if (typeof curSet.dynamaxLevel === 'number' && curSet.dynamaxLevel !== 10 && !isNaN(curSet.dynamaxLevel)) {
text += 'Dynamax Level: ' + curSet.dynamaxLevel + " \n";
text += `Dynamax Level: ${curSet.dynamaxLevel} \n`;
}
if (curSet.gigantamax) {
text += 'Gigantamax: Yes \n';
}
if (gen === 9) {
const species = Dex.species.get(curSet.species);
text += 'Tera Type: ' + (species.forceTeraType || curSet.teraType || species.types[0]) + " \n";
text += `Tera Type: ${species.forceTeraType || curSet.teraType || species.types[0]} \n`;
}
if (!hidestats) {
let first = true;
@ -1290,14 +1289,14 @@ export const Teams = new class {
} else {
text += ' / ';
}
text += '' + curSet.evs[j] + ' ' + BattleStatNames[j];
text += `${curSet.evs[j]!} ${BattleStatNames[j]}`;
}
}
if (!first) {
text += " \n";
}
if (curSet.nature) {
text += '' + curSet.nature + ' Nature' + " \n";
text += `${curSet.nature} Nature \n`;
}
first = true;
if (curSet.ivs) {
@ -1338,7 +1337,7 @@ export const Teams = new class {
} else {
text += ' / ';
}
text += '' + curSet.ivs[stat] + ' ' + BattleStatNames[stat];
text += `${curSet.ivs[stat]} ${BattleStatNames[stat]}`;
}
}
}
@ -1348,11 +1347,11 @@ export const Teams = new class {
}
if (curSet.moves) {
for (let move of curSet.moves) {
if (move.substr(0, 13) === 'Hidden Power ') {
move = move.substr(0, 13) + '[' + move.substr(13) + ']';
if (move.startsWith('Hidden Power ')) {
move = `${move.slice(0, 13)}[${move.slice(13)}]`;
}
if (move) {
text += '- ' + move + " \n";
text += `- ${move} \n`;
}
}
}
@ -1364,6 +1363,6 @@ export const Teams = new class {
if (typeof require === 'function') {
// in Node
(global as any).Dex = Dex;
(global as any).toID = toID;
global.Dex = Dex;
global.toID = toID;
}

View File

@ -113,7 +113,7 @@ export class BattleLog {
if (
battle.seeking === Infinity ?
battle.currentStep < battle.stepQueue.length - 2000 :
battle.turn < battle.seeking! - 100
battle.turn < battle.seeking - 100
) {
this.addSeekEarlierButton();
return;
@ -154,7 +154,7 @@ export class BattleLog {
const user = BattleTextParser.parseNameParts(args[1]);
if (battle?.ignoreSpects && ' +'.includes(user.group)) return;
const formattedUser = user.group + user.name;
const isJoin = (args[0].charAt(0) === 'j');
const isJoin = (args[0].startsWith('j'));
if (!this.joinLeave) {
this.joinLeave = {
joins: [],
@ -271,9 +271,11 @@ export class BattleLog {
const exportedTeam = team.map(set => {
let buf = Teams.export([set], battle.gen).replace(/\n/g, '<br />');
if (set.name && set.name !== set.species) {
buf = buf.replace(set.name, BattleLog.sanitizeHTML(`<span class="picon" style="${Dex.getPokemonIcon(set.species)}"></span><br />${set.name}`));
buf = buf.replace(set.name, BattleLog.sanitizeHTML(
`<span class="picon" style="${Dex.getPokemonIcon(set.species)}"></span><br />${set.name}`));
} else {
buf = buf.replace(set.species, `<span class="picon" style="${Dex.getPokemonIcon(set.species)}"></span><br />${set.species}`);
buf = buf.replace(set.species,
`<span class="picon" style="${Dex.getPokemonIcon(set.species)}"></span><br />${set.species}`);
}
if (set.item) {
buf = buf.replace(set.item, `${set.item} <span class="itemicon" style="${Dex.getItemIcon(set.item)}"></span>`);
@ -380,7 +382,7 @@ export class BattleLog {
const messages = message.split('\n').map(line => {
line = BattleLog.escapeHTML(line);
line = line.replace(/\*\*(.*)\*\*/, '<strong>$1</strong>');
line = line.replace(/\|\|([^\|]*)\|\|([^\|]*)\|\|/, '<abbr title="$1">$2</abbr>');
line = line.replace(/\|\|([^|]*)\|\|([^|]*)\|\|/, '<abbr title="$1">$2</abbr>');
if (line.startsWith(' ')) line = '<small>' + line.trim() + '</small>';
return line;
});
@ -574,7 +576,7 @@ export class BattleLog {
let HLmod = (lum - 0.2) * -150; // -80 (yellow) to 28 (dark blue)
if (HLmod > 18) HLmod = (HLmod - 18) * 2.5;
else if (HLmod < 0) HLmod = (HLmod - 0) / 3;
else if (HLmod < 0) HLmod /= 3;
else HLmod = 0;
// let mod = ';border-right: ' + Math.abs(HLmod) + 'px solid ' + (HLmod > 0 ? 'red' : '#0088FF');
let Hdist = Math.min(Math.abs(180 - H), Math.abs(240 - H));
@ -616,10 +618,11 @@ export class BattleLog {
}
static prefs(name: string) {
// @ts-ignore
// @ts-expect-error optional, for old client
if (window.Storage?.prefs) return Storage.prefs(name);
// @ts-ignore
// @ts-expect-error optional, for Preact client
if (window.PS) return PS.prefs[name];
// may be neither, for e.g. Replays
return undefined;
}
@ -641,7 +644,7 @@ export class BattleLog {
let cmd = '';
let target = '';
if (message.charAt(0) === '/') {
if (message.startsWith('/')) {
if (message.charAt(1) === '/') {
message = message.slice(1);
} else {
@ -753,11 +756,11 @@ export class BattleLog {
static interstice = (() => {
const whitelist: string[] = Config.whitelist;
const patterns = whitelist.map(entry => new RegExp(
`^(https?:)?//([A-Za-z0-9-]*\\.)?${entry.replace(/\./g, '\\.')}(/.*)?`,
'i'));
`^(https?:)?//([A-Za-z0-9-]*\\.)?${entry.replace(/\./g, '\\.')}(/.*)?`, 'i'
));
return {
isWhitelisted(uri: string) {
if (uri[0] === '/' && uri[1] !== '/') {
if (uri.startsWith('/') && uri[1] !== '/') {
// domain-relative URIs are safe
return true;
}
@ -904,7 +907,7 @@ export class BattleLog {
return {
tagName: 'iframe',
attribs: [
'src', `https://player.twitch.tv/?channel=${channelId}&parent=${location.hostname}&autoplay=false`,
'src', `https://player.twitch.tv/?channel=${channelId!}&parent=${location.hostname}&autoplay=false`,
'allowfullscreen', 'true', 'height', `${height}`, 'width', `${width}`,
],
};
@ -912,7 +915,7 @@ export class BattleLog {
// <username> is a custom element that handles namecolors
tagName = 'strong';
const color = this.usernameColor(toID(getAttrib('name')));
const style = getAttrib('style');
const style = getAttrib('style') || '';
setAttrib('style', `${style};color:${color}`);
} else if (tagName === 'spotify') {
// <iframe src="https://open.spotify.com/embed/track/6aSYnCIwcLpnDXngGKAEzZ" width="300" height="380" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe>
@ -921,7 +924,7 @@ export class BattleLog {
return {
tagName: 'iframe',
attribs: ['src', `https://open.spotify.com/embed/track/${songId}`, 'width', '300', 'height', '380', 'frameborder', '0', 'allowtransparency', 'true', 'allow', 'encrypted-media'],
attribs: ['src', `https://open.spotify.com/embed/track/${songId!}`, 'width', '300', 'height', '380', 'frameborder', '0', 'allowtransparency', 'true', 'allow', 'encrypted-media'],
};
} else if (tagName === 'youtube') {
// <iframe width="320" height="180" src="https://www.youtube.com/embed/dQw4w9WgXcQ" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
@ -936,7 +939,7 @@ export class BattleLog {
if (Number(height) < 200) {
height = window.innerWidth >= 400 ? '225' : '200';
}
const videoId = /(?:\?v=|\/embed\/)([A-Za-z0-9_\-]+)/.exec(src)?.[1];
const videoId = /(?:\?v=|\/embed\/)([A-Za-z0-9_-]+)/.exec(src)?.[1];
if (!videoId) return { tagName: 'img', attribs: ['alt', `invalid src for <youtube>`] };
const time = /(?:\?|&)(?:t|start)=([0-9]+)/.exec(src)?.[1];
@ -950,7 +953,7 @@ export class BattleLog {
'width', width, 'height', height,
'src', `https://www.youtube.com/embed/${videoId}?enablejsapi=1&playsinline=1${time ? `&start=${time}` : ''}`,
'frameborder', '0', 'allow', 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture', 'allowfullscreen', 'allowfullscreen',
'time', (time || 0) + "",
'time', `${time || 0}`,
],
};
} else if (tagName === 'formatselect') {
@ -1081,7 +1084,8 @@ export class BattleLog {
// allows T, however it's more practical to also allow spaces.
return sanitized.replace(
/<time>\s*([+-]?\d{4,}-\d{2}-\d{2})[T ](\d{2}:\d{2}(?::\d{2}(?:\.\d{3})?)?)(Z|[+-]\d{2}:\d{2})?\s*<\/time>/ig,
this.localizeTime);
this.localizeTime
);
}
static initYoutubePlayer(idx: number) {
@ -1106,7 +1110,6 @@ export class BattleLog {
player.seekTo(time);
}
this.players[idx - 1] = player;
};
// wait for html element to be in DOM
this.ensureYoutube().then(() => {
@ -1211,7 +1214,9 @@ export class BattleLog {
static createReplayFileHref(room: { battle: Battle, id?: string, fragment?: string }) {
// unescape(encodeURIComponent()) is necessary because btoa doesn't support Unicode
const replayFile = BattleLog.createReplayFile(room);
if (!replayFile) return 'javascript:alert("You will need to click Download again once the replay file is at the end.");void 0';
if (!replayFile) {
return 'javascript:alert("You will need to click Download again once the replay file is at the end.");void 0';
}
return 'data:text/plain;base64,' + encodeURIComponent(btoa(unescape(encodeURIComponent(replayFile))));
}
}

View File

@ -5,32 +5,32 @@ import type {ID} from './battle-dex';
import type { Args, KWArgs } from './battle-text-parser';
export class BattleSceneStub {
animating: boolean = false;
acceleration: number = NaN;
gen: number = NaN;
activeCount: number = NaN;
numericId: number = NaN;
timeOffset: number = NaN;
interruptionCount: number = NaN;
messagebarOpen: boolean = false;
animating = false;
acceleration = NaN;
gen = NaN;
activeCount = NaN;
numericId = NaN;
timeOffset = NaN;
interruptionCount = NaN;
messagebarOpen = false;
log: BattleLog = { add: (args: Args, kwargs?: KWArgs) => {} } as any;
$frame?: JQuery;
abilityActivateAnim(pokemon: Pokemon, result: string): void { }
addPokemonSprite(pokemon: Pokemon): PokemonSprite { return null!; }
addSideCondition(siden: number, id: ID, instant?: boolean | undefined): void { }
addSideCondition(siden: number, id: ID, instant?: boolean): void { }
animationOff(): void { }
animationOn(): void { }
maybeCloseMessagebar(args: Args, kwArgs: KWArgs): boolean { return false; }
closeMessagebar(): boolean { return false; }
damageAnim(pokemon: Pokemon, damage: string | number): void { }
destroy(): void { }
finishAnimations(): JQuery.Promise<JQuery<HTMLElement>, any, any> | undefined { return void(0); }
finishAnimations(): JQuery.Promise<JQuery> | undefined { return undefined; }
healAnim(pokemon: Pokemon, damage: string | number): void { }
hideJoinButtons(): void { }
incrementTurn(): void { }
updateAcceleration(): void { }
message(message: string, hiddenMessage?: string | undefined): void { }
message(message: string, hiddenMessage?: string): void { }
pause(): void { }
setMute(muted: boolean): void { }
preemptCatchup(): void { }
@ -55,7 +55,7 @@ export class BattleSceneStub {
updateSidebar(side: Side): void { }
updateSidebars(): void { }
updateStatbars(): void { }
updateWeather(instant?: boolean | undefined): void { }
updateWeather(instant?: boolean): void { }
upkeepWeather(): void { }
wait(time: number): void { }
setFrameHTML(html: any): void { }

View File

@ -57,7 +57,8 @@ export class PSSearchResults extends preact.Component<{search: DexSearch}> {
if (search.dex.gen < 2) bst -= stats['spd'];
if (errorMessage) {
return <li class="result"><a href={`${this.URL_ROOT}pokemon/${id}`} data-target="push" data-entry={`pokemon|${pokemon.name}`}>
return <li class="result">
<a href={`${this.URL_ROOT}pokemon/${id}`} data-target="push" data-entry={`pokemon|${pokemon.name}`}>
<span class="col numcol">{search.getTier(pokemon)}</span>
<span class="col iconcol">
@ -67,10 +68,12 @@ export class PSSearchResults extends preact.Component<{search: DexSearch}> {
<span class="col pokemonnamecol">{this.renderName(pokemon.name, matchStart, matchEnd, tagStart)}</span>
{errorMessage}
</a></li>;
</a>
</li>;
}
return <li class="result"><a href={`${this.URL_ROOT}pokemon/${id}`} data-target="push" data-entry={`pokemon|${pokemon.name}`}>
return <li class="result">
<a href={`${this.URL_ROOT}pokemon/${id}`} data-target="push" data-entry={`pokemon|${pokemon.name}`}>
<span class="col numcol">{search.getTier(pokemon)}</span>
<span class="col iconcol">
@ -85,17 +88,25 @@ export class PSSearchResults extends preact.Component<{search: DexSearch}> {
)}
</span>
{search.dex.gen >= 3 && (pokemon.abilities['1'] ?
{search.dex.gen >= 3 && (
pokemon.abilities['1'] ? (
<span class="col twoabilitycol">{pokemon.abilities['0']}<br />{pokemon.abilities['1']}</span>
:
) : (
<span class="col abilitycol">{pokemon.abilities['0']}</span>
)
)}
{search.dex.gen >= 5 && (pokemon.abilities['S'] ?
<span class={`col twoabilitycol${pokemon.unreleasedHidden ? ' unreleasedhacol' : ''}`}>{pokemon.abilities['H'] || ''}<br />{pokemon.abilities['S']}</span>
: pokemon.abilities['H'] ?
<span class={`col abilitycol${pokemon.unreleasedHidden ? ' unreleasedhacol' : ''}`}>{pokemon.abilities['H']}</span>
:
{search.dex.gen >= 5 && (
pokemon.abilities['S'] ? (
<span class={`col twoabilitycol${pokemon.unreleasedHidden ? ' unreleasedhacol' : ''}`}>
{pokemon.abilities['H'] || ''}<br />{pokemon.abilities['S']}
</span>
) : pokemon.abilities['H'] ? (
<span class={`col abilitycol${pokemon.unreleasedHidden ? ' unreleasedhacol' : ''}`}>
{pokemon.abilities['H']}
</span>
) : (
<span class="col abilitycol"></span>
)
)}
<span class="col statcol"><em>HP</em><br />{stats.hp}</span>
@ -106,7 +117,8 @@ export class PSSearchResults extends preact.Component<{search: DexSearch}> {
{search.dex.gen < 2 && <span class="col statcol"><em>Spc</em><br />{stats.spa}</span>}
<span class="col statcol"><em>Spe</em><br />{stats.spe}</span>
<span class="col bstcol"><em>BST<br />{bst}</em></span>
</a></li>;
</a>
</li>;
}
renderName(name: string, matchStart: number, matchEnd: number, tagStart?: number) {
@ -166,13 +178,15 @@ export class PSSearchResults extends preact.Component<{search: DexSearch}> {
const ability = search.dex.abilities.get(id);
if (!ability) return <li class="result">Unrecognized ability</li>;
return <li class="result"><a href={`${this.URL_ROOT}abilitys/${id}`} data-target="push" data-entry={`ability|${ability.name}`}>
return <li class="result">
<a href={`${this.URL_ROOT}abilitys/${id}`} data-target="push" data-entry={`ability|${ability.name}`}>
<span class="col namecol">{this.renderName(ability.name, matchStart, matchEnd)}</span>
{errorMessage}
{!errorMessage && <span class="col abilitydesccol">{ability.shortDesc}</span>}
</a></li>;
</a>
</li>;
}
renderMoveRow(id: ID, matchStart: number, matchEnd: number, errorMessage?: preact.ComponentChildren) {
@ -196,8 +210,14 @@ export class PSSearchResults extends preact.Component<{search: DexSearch}> {
<span class="col movenamecol">{this.renderName(move.name, matchStart, matchEnd, tagStart)}</span>
<span class="col typecol">
<img src={`${Dex.resourcePrefix}sprites/types/${move.type}.png`} alt={move.type} height="14" width="32" class="pixelated" />
<img src={`${Dex.resourcePrefix}sprites/categories/${move.category}.png`} alt={move.category} height="14" width="32" class="pixelated" />
<img
src={`${Dex.resourcePrefix}sprites/types/${move.type}.png`}
alt={move.type} height="14" width="32" class="pixelated"
/>
<img
src={`${Dex.resourcePrefix}sprites/categories/${move.category}.png`}
alt={move.category} height="14" width="32" class="pixelated"
/>
</span>
<span class="col labelcol">
@ -216,7 +236,6 @@ export class PSSearchResults extends preact.Component<{search: DexSearch}> {
}
renderTypeRow(id: ID, matchStart: number, matchEnd: number, errorMessage?: preact.ComponentChildren) {
const search = this.props.search;
const name = id.charAt(0).toUpperCase() + id.slice(1);
return <li class="result"><a href={`${this.URL_ROOT}types/${id}`} data-target="push" data-entry={`type|${name}`}>
@ -231,10 +250,10 @@ export class PSSearchResults extends preact.Component<{search: DexSearch}> {
}
renderCategoryRow(id: ID, matchStart: number, matchEnd: number, errorMessage?: preact.ComponentChildren) {
const search = this.props.search;
const name = id.charAt(0).toUpperCase() + id.slice(1);
return <li class="result"><a href={`${this.URL_ROOT}categories/${id}`} data-target="push" data-entry={`category|${name}`}>
return <li class="result">
<a href={`${this.URL_ROOT}categories/${id}`} data-target="push" data-entry={`category|${name}`}>
<span class="col namecol">{this.renderName(name, matchStart, matchEnd)}</span>
<span class="col typecol">
@ -242,14 +261,13 @@ export class PSSearchResults extends preact.Component<{search: DexSearch}> {
</span>
{errorMessage}
</a></li>;
</a>
</li>;
}
renderArticleRow(id: ID, matchStart: number, matchEnd: number, errorMessage?: preact.ComponentChildren) {
const search = this.props.search;
const isSearchType = (id === 'pokemon' || id === 'moves');
const name = (window.BattleArticleTitles && window.BattleArticleTitles[id]) ||
(id.charAt(0).toUpperCase() + id.substr(1));
const name = window.BattleArticleTitles?.[id] || (id.charAt(0).toUpperCase() + id.substr(1));
return <li class="result"><a href={`${this.URL_ROOT}articles/${id}`} data-target="push" data-entry={`article|${name}`}>
<span class="col namecol">{this.renderName(name, matchStart, matchEnd)}</span>
@ -261,7 +279,6 @@ export class PSSearchResults extends preact.Component<{search: DexSearch}> {
}
renderEggGroupRow(id: ID, matchStart: number, matchEnd: number, errorMessage?: preact.ComponentChildren) {
const search = this.props.search;
// very hardcode
let name: string | undefined;
if (id === 'humanlike') name = 'Human-Like';
@ -274,17 +291,18 @@ export class PSSearchResults extends preact.Component<{search: DexSearch}> {
name = id.charAt(0).toUpperCase() + id.slice(1);
}
return <li class="result"><a href={`${this.URL_ROOT}egggroups/${id}`} data-target="push" data-entry={`egggroup|${name}`}>
return <li class="result">
<a href={`${this.URL_ROOT}egggroups/${id}`} data-target="push" data-entry={`egggroup|${name}`}>
<span class="col namecol">{this.renderName(name, matchStart, matchEnd)}</span>
<span class="col movedesccol">(egg group)</span>
{errorMessage}
</a></li>;
</a>
</li>;
}
renderTierRow(id: ID, matchStart: number, matchEnd: number, errorMessage?: preact.ComponentChildren) {
const search = this.props.search;
// very hardcode
const tierTable: { [id: string]: string } = {
uber: "Uber",
@ -314,9 +332,9 @@ export class PSSearchResults extends preact.Component<{search: DexSearch}> {
let errorMessage: preact.ComponentChild = null;
let label;
if ((label = search.filterLabel(type))) { // tslint:disable-line
if ((label = search.filterLabel(type))) {
errorMessage = <span class="col filtercol"><em>{label}</em></span>;
} else if ((label = search.illegalLabel(id as ID))) { // tslint:disable-line
} else if ((label = search.illegalLabel(id as ID))) {
errorMessage = <span class="col illegalcol"><em>{label}</em></span>;
}
@ -335,23 +353,23 @@ export class PSSearchResults extends preact.Component<{search: DexSearch}> {
case 'sortmove':
return this.renderMoveSortRow();
case 'pokemon':
return this.renderPokemonRow(id as ID, matchStart, matchEnd, errorMessage);
return this.renderPokemonRow(id, matchStart, matchEnd, errorMessage);
case 'move':
return this.renderMoveRow(id as ID, matchStart, matchEnd, errorMessage);
return this.renderMoveRow(id, matchStart, matchEnd, errorMessage);
case 'item':
return this.renderItemRow(id as ID, matchStart, matchEnd, errorMessage);
return this.renderItemRow(id, matchStart, matchEnd, errorMessage);
case 'ability':
return this.renderAbilityRow(id as ID, matchStart, matchEnd, errorMessage);
return this.renderAbilityRow(id, matchStart, matchEnd, errorMessage);
case 'type':
return this.renderTypeRow(id as ID, matchStart, matchEnd, errorMessage);
return this.renderTypeRow(id, matchStart, matchEnd, errorMessage);
case 'egggroup':
return this.renderEggGroupRow(id as ID, matchStart, matchEnd, errorMessage);
return this.renderEggGroupRow(id, matchStart, matchEnd, errorMessage);
case 'tier':
return this.renderTierRow(id as ID, matchStart, matchEnd, errorMessage);
return this.renderTierRow(id, matchStart, matchEnd, errorMessage);
case 'category':
return this.renderCategoryRow(id as ID, matchStart, matchEnd, errorMessage);
return this.renderCategoryRow(id, matchStart, matchEnd, errorMessage);
case 'article':
return this.renderArticleRow(id as ID, matchStart, matchEnd, errorMessage);
return this.renderArticleRow(id, matchStart, matchEnd, errorMessage);
}
return <li>Error: not found</li>;
}
@ -368,12 +386,11 @@ export class PSSearchResults extends preact.Component<{search: DexSearch}> {
)}
{!search.query && <small style="color: #888">(backspace = delete filter)</small>}
</p>}
{search.results &&
{
// TODO: implement windowing
// for now, just show first twenty results
search.results.slice(0, 20).map(result =>
this.renderRow(result)
)}
search.results?.slice(0, 20).map(result => this.renderRow(result))
}
</ul>;
}
}

View File

@ -171,7 +171,7 @@ export const BattleSound = new class {
loudnessPercentToAmplitudePercent(loudnessPercent: number) {
// 10 dB is perceived as approximately twice as loud
let decibels = 10 * Math.log(loudnessPercent / 100) / Math.log(2);
return Math.pow(10, decibels / 20) * 100;
return 10 ** (decibels / 20) * 100;
}
setBgmVolume(bgmVolume: number) {
this.bgmVolume = this.loudnessPercentToAmplitudePercent(bgmVolume);

View File

@ -72,7 +72,7 @@ export class BattleTextParser {
const kwArgs: KWArgs = {};
while (args.length > 1) {
const lastArg = args[args.length - 1];
if (lastArg.charAt(0) !== '[') break;
if (!lastArg.startsWith('[')) break;
const bracketPos = lastArg.indexOf(']');
if (bracketPos <= 0) break;
// default to '.' so it evaluates to boolean true
@ -229,13 +229,13 @@ export class BattleTextParser {
if (this.lowercaseRegExp === undefined) {
const prefixes = ['pokemon', 'opposingPokemon', 'team', 'opposingTeam', 'party', 'opposingParty'].map(templateId => {
const template = BattleText.default[templateId];
if (template.charAt(0) === template.charAt(0).toUpperCase()) return '';
if (template.startsWith(template.charAt(0).toUpperCase())) return '';
const bracketIndex = template.indexOf('[');
if (bracketIndex >= 0) return template.slice(0, bracketIndex);
return template;
}).filter(prefix => prefix);
if (prefixes.length) {
let buf = `((?:^|\n)(?: | \\\(|\\\[)?)(` +
let buf = `((?:^|\n)(?: | \\(|\\[)?)(` +
prefixes.map(BattleTextParser.escapeRegExp).join('|') +
`)`;
this.lowercaseRegExp = new RegExp(buf, 'g');
@ -302,7 +302,7 @@ export class BattleTextParser {
return '';
}
team(side: string, isFar: boolean = false) {
team(side: string, isFar = false) {
side = side.slice(0, 2);
if (side === this.perspective || side === BattleTextParser.allyID(side as SideID)) {
return !isFar ? BattleText.default.team : BattleText.default.opposingTeam;
@ -358,7 +358,7 @@ export class BattleTextParser {
let id = BattleTextParser.effectId(namespace);
if (BattleText[id] && type in BattleText[id]) {
if (BattleText[id][type].charAt(1) === '.') type = BattleText[id][type].slice(2) as ID;
if (BattleText[id][type].charAt(0) === '#') id = BattleText[id][type].slice(1) as ID;
if (BattleText[id][type].startsWith('#')) id = BattleText[id][type].slice(1) as ID;
if (!BattleText[id][type]) return '';
return BattleText[id][type] + '\n';
}
@ -380,7 +380,7 @@ export class BattleTextParser {
static stat(stat: string) {
const entry = BattleText[stat || "stats"];
if (!entry || !entry.statName) return `???stat:${stat}???`;
if (!entry?.statName) return `???stat:${stat}???`;
return entry.statName;
}
@ -417,7 +417,7 @@ export class BattleTextParser {
return 'postMajor';
}
}
return (cmd.charAt(0) === '-' ? 'postMajor' : '');
return (cmd.startsWith('-') ? 'postMajor' : '');
}
sectionBreak(args: Args, kwArgs: KWArgs) {
@ -601,7 +601,8 @@ export class BattleTextParser {
let id = BattleTextParser.effectId(effect);
if (id === 'typechange') {
const template = this.template('typeChange', kwArgs.from);
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[TYPE]', arg3).replace('[SOURCE]', this.pokemon(kwArgs.of));
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[TYPE]', arg3)
.replace('[SOURCE]', this.pokemon(kwArgs.of));
}
if (id === 'typeadd') {
const template = this.template('typeAdd', kwArgs.from);
@ -629,13 +630,14 @@ export class BattleTextParser {
if (kwArgs.damage) templateId = 'activate';
if (kwArgs.block) templateId = 'block';
if (kwArgs.upkeep) templateId = 'upkeep';
if (id === 'mist' && this.gen <= 2) templateId = 'startGen' + this.gen;
if (id === 'mist' && this.gen <= 2) templateId = `startGen${this.gen}`;
if (id === 'reflect' || id === 'lightscreen') templateId = 'startGen1';
if (templateId === 'start' && kwArgs.from?.startsWith('item:')) {
templateId += 'FromItem';
}
const template = this.template(templateId, kwArgs.from, effect);
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[EFFECT]', this.effect(effect)).replace('[MOVE]', arg3).replace('[SOURCE]', this.pokemon(kwArgs.of)).replace('[ITEM]', this.effect(kwArgs.from));
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[EFFECT]', this.effect(effect))
.replace('[MOVE]', arg3).replace('[SOURCE]', this.pokemon(kwArgs.of)).replace('[ITEM]', this.effect(kwArgs.from));
}
case '-end': {
@ -652,7 +654,8 @@ export class BattleTextParser {
template = this.template('endFromItem', effect);
}
if (!template) template = this.template(templateId, effect);
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[EFFECT]', this.effect(effect)).replace('[SOURCE]', this.pokemon(kwArgs.of)).replace('[ITEM]', this.effect(kwArgs.from));
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[EFFECT]', this.effect(effect))
.replace('[SOURCE]', this.pokemon(kwArgs.of)).replace('[ITEM]', this.effect(kwArgs.from));
}
case '-ability': {
@ -671,7 +674,8 @@ export class BattleTextParser {
if (kwArgs.from) {
line1 = this.maybeAbility(kwArgs.from, pokemon) + line1;
const template = this.template('changeAbility', kwArgs.from);
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[ABILITY]', this.effect(ability)).replace('[SOURCE]', this.pokemon(kwArgs.of));
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[ABILITY]', this.effect(ability))
.replace('[SOURCE]', this.pokemon(kwArgs.of));
}
const id = BattleTextParser.effectId(ability);
if (id === 'unnerve') {
@ -702,12 +706,14 @@ export class BattleTextParser {
const line1 = this.maybeAbility(kwArgs.from, kwArgs.of || pokemon);
if (['thief', 'covet', 'bestow', 'magician', 'pickpocket'].includes(id)) {
const template = this.template('takeItem', kwArgs.from);
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[ITEM]', this.effect(item)).replace('[SOURCE]', this.pokemon(target || kwArgs.of));
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[ITEM]', this.effect(item))
.replace('[SOURCE]', this.pokemon(target || kwArgs.of));
}
if (id === 'frisk') {
const hasTarget = kwArgs.of && pokemon && kwArgs.of !== pokemon;
const template = this.template(hasTarget ? 'activate' : 'activateNoTarget', "Frisk");
return line1 + template.replace('[POKEMON]', this.pokemon(kwArgs.of)).replace('[ITEM]', this.effect(item)).replace('[TARGET]', this.pokemon(pokemon));
return line1 + template.replace('[POKEMON]', this.pokemon(kwArgs.of)).replace('[ITEM]', this.effect(item))
.replace('[TARGET]', this.pokemon(pokemon));
}
if (kwArgs.from) {
const template = this.template('addItem', kwArgs.from);
@ -727,7 +733,8 @@ export class BattleTextParser {
const id = BattleTextParser.effectId(kwArgs.from);
if (id === 'gem') {
const template = this.template('useGem', item);
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[ITEM]', this.effect(item)).replace('[MOVE]', kwArgs.move);
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[ITEM]', this.effect(item))
.replace('[MOVE]', kwArgs.move);
}
if (id === 'stealeat') {
const template = this.template('removeItem', "Bug Bite");
@ -735,7 +742,8 @@ export class BattleTextParser {
}
if (kwArgs.from) {
const template = this.template('removeItem', kwArgs.from);
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[ITEM]', this.effect(item)).replace('[SOURCE]', this.pokemon(kwArgs.of));
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[ITEM]', this.effect(item))
.replace('[SOURCE]', this.pokemon(kwArgs.of));
}
if (kwArgs.weaken) {
const template = this.template('activateWeaken');
@ -792,7 +800,8 @@ export class BattleTextParser {
}
let template = this.template('start', effect, 'NODEFAULT');
if (!template) template = this.template('start').replace('[EFFECT]', this.effect(effect));
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[SOURCE]', this.pokemon(kwArgs.of)).replace('[TEAM]', this.team(pokemon.slice(0, 2)));
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[SOURCE]', this.pokemon(kwArgs.of))
.replace('[TEAM]', this.team(pokemon.slice(0, 2)));
}
case '-sidestart': {
@ -922,9 +931,11 @@ export class BattleTextParser {
line1 += this.ability(kwArgs.ability2, target);
}
if (kwArgs.move || kwArgs.number || kwArgs.item || kwArgs.name) {
template = template.replace('[MOVE]', kwArgs.move).replace('[NUMBER]', kwArgs.number).replace('[ITEM]', kwArgs.item).replace('[NAME]', kwArgs.name);
template = template.replace('[MOVE]', kwArgs.move).replace('[NUMBER]', kwArgs.number)
.replace('[ITEM]', kwArgs.item).replace('[NAME]', kwArgs.name);
}
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[TARGET]', this.pokemon(target)).replace('[SOURCE]', this.pokemon(kwArgs.of));
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[TARGET]', this.pokemon(target))
.replace('[SOURCE]', this.pokemon(kwArgs.of));
}
case '-prepare': {
@ -948,7 +959,8 @@ export class BattleTextParser {
}
if (kwArgs.from.startsWith('item:')) {
template = this.template(kwArgs.of ? 'damageFromPokemon' : 'damageFromItem');
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[ITEM]', this.effect(kwArgs.from)).replace('[SOURCE]', this.pokemon(kwArgs.of));
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[ITEM]', this.effect(kwArgs.from))
.replace('[SOURCE]', this.pokemon(kwArgs.of));
}
if (kwArgs.partiallytrapped || id === 'bind' || id === 'wrap') {
template = this.template('damageFromPartialTrapping');
@ -964,7 +976,8 @@ export class BattleTextParser {
let template = this.template('heal', kwArgs.from, 'NODEFAULT');
const line1 = this.maybeAbility(kwArgs.from, kwArgs.of || pokemon);
if (template) {
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[SOURCE]', this.pokemon(kwArgs.of)).replace('[NICKNAME]', kwArgs.wisher);
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[SOURCE]', this.pokemon(kwArgs.of))
.replace('[NICKNAME]', kwArgs.wisher);
}
if (kwArgs.from && !kwArgs.from.startsWith('ability:')) {
@ -989,7 +1002,8 @@ export class BattleTextParser {
templateId += (kwArgs.multiple ? 'MultipleFromZEffect' : 'FromZEffect');
} else if (amount && kwArgs.from?.startsWith('item:')) {
const template = this.template(templateId + 'FromItem', kwArgs.from);
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[STAT]', BattleTextParser.stat(stat)).replace('[ITEM]', this.effect(kwArgs.from));
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[STAT]', BattleTextParser.stat(stat))
.replace('[ITEM]', this.effect(kwArgs.from));
}
const template = this.template(templateId, kwArgs.from);
return line1 + template.replace(/\[POKEMON\]/g, this.pokemon(pokemon)).replace('[STAT]', BattleTextParser.stat(stat));
@ -1055,9 +1069,10 @@ export class BattleTextParser {
const line1 = this.maybeAbility(effect, kwArgs.of || pokemon);
let id = BattleTextParser.effectId(effect);
let templateId = 'block';
if (id === 'mist' && this.gen <= 2) templateId = 'blockGen' + this.gen;
if (id === 'mist' && this.gen <= 2) templateId = `blockGen${this.gen}`;
const template = this.template(templateId, effect);
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[SOURCE]', this.pokemon(attacker || kwArgs.of)).replace('[MOVE]', move);
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon))
.replace('[SOURCE]', this.pokemon(attacker || kwArgs.of)).replace('[MOVE]', move);
}
case '-fail': {
@ -1199,5 +1214,5 @@ declare const require: any;
declare const global: any;
if (typeof require === 'function') {
// in Node
(global as any).BattleTextParser = BattleTextParser;
global.BattleTextParser = BattleTextParser;
}

View File

@ -9,7 +9,7 @@
*/
import { Pokemon, type Battle, type ServerPokemon } from "./battle";
import {Dex, ModdedDex, toID, type ID} from "./battle-dex";
import { Dex, type ModdedDex, toID, type ID } from "./battle-dex";
import type { BattleScene } from "./battle-animations";
import { BattleLog } from "./battle-log";
import { BattleNatures } from "./battle-dex-data";
@ -197,7 +197,7 @@ export class BattleTooltips {
if (!BattleTooltips.isLocked) BattleTooltips.hideTooltip();
}
listen(elem: HTMLElement | JQuery<HTMLElement>) {
listen(elem: HTMLElement | JQuery) {
const $elem = $(elem);
$elem.on('mouseover', '.has-tooltip', this.showTooltipEvent);
$elem.on('click', '.has-tooltip', this.clickTooltipEvent);
@ -373,7 +373,7 @@ export class BattleTooltips {
default:
// "throws" an error without crashing
Promise.resolve(new Error(`unrecognized type`));
buf = `<p class="message-error" style="white-space: pre-wrap">${new Error(`unrecognized type`).stack}</p>`;
buf = `<p class="message-error" style="white-space: pre-wrap">${new Error(`unrecognized type`).stack!}</p>`;
}
this.placeTooltip(buf, elem, ownHeight, type);
@ -412,7 +412,7 @@ export class BattleTooltips {
try {
const selection = window.getSelection()!;
if (selection.type === 'Range') return;
} catch (err) {}
} catch {}
BattleTooltips.hideTooltip();
});
} else {
@ -422,7 +422,7 @@ export class BattleTooltips {
left: x,
top: y,
});
innerHTML = `<div class="tooltipinner"><div class="tooltip tooltip-${type}">${innerHTML}</div></div>`;
innerHTML = `<div class="tooltipinner"><div class="tooltip tooltip-${type!}">${innerHTML}</div></div>`;
$wrapper.html(innerHTML).appendTo(document.body);
BattleTooltips.elem = $wrapper.find('.tooltip')[0] as HTMLDivElement;
BattleTooltips.isLocked = false;
@ -476,9 +476,8 @@ export class BattleTooltips {
}
let boostText = '';
if (move.zMove!.boost) {
let boosts = Object.keys(move.zMove!.boost) as Dex.StatName[];
boostText = boosts.map(stat =>
BattleTextParser.stat(stat) + ' +' + move.zMove!.boost![stat]
boostText = Object.entries(move.zMove!.boost).map(([stat, boost]) =>
`${BattleTextParser.stat(stat)} +${boost}`
).join(', ');
}
return boostText;
@ -623,7 +622,7 @@ export class BattleTooltips {
});
}
text += '<h2>' + move.name + '<br />';
text += `<h2>${move.name}<br />`;
text += Dex.getTypeIcon(moveType);
text += ` ${Dex.getCategoryIcon(category)}</h2>`;
@ -635,16 +634,16 @@ export class BattleTooltips {
// Otherwise, it is just shown as in singles.
// The trick is that we need to calculate it first for each Pokémon to see if it changes.
let prevBasePower: string | null = null;
let basePower: string = '';
let basePower = '';
let difference = false;
let basePowers = [];
for (const active of foeActive) {
if (!active) continue;
value = this.getMoveBasePower(move, moveType, value, active);
basePower = '' + value;
basePower = `${value}`;
if (prevBasePower === null) prevBasePower = basePower;
if (prevBasePower !== basePower) difference = true;
basePowers.push('Base power vs ' + active.name + ': ' + basePower);
basePowers.push(`Base power vs ${active.name}: ${basePower}`);
}
if (difference) {
text += '<p>' + basePowers.join('<br />') + '</p>';
@ -655,7 +654,7 @@ export class BattleTooltips {
if (!showingMultipleBasePowers && category !== 'Status') {
let activeTarget = foeActive[0] || foeActive[1] || foeActive[2];
value = this.getMoveBasePower(move, moveType, value, activeTarget);
text += '<p>Base power: ' + value + '</p>';
text += `<p>Base power: ${value}</p>`;
}
let accuracy = this.getMoveAccuracy(move, value);
@ -683,22 +682,22 @@ export class BattleTooltips {
calls = 'Swift';
}
let calledMove = this.battle.dex.moves.get(calls);
text += 'Calls ' + Dex.getTypeIcon(this.getMoveType(calledMove, value)[0]) + ' ' + calledMove.name;
text += `Calls ${Dex.getTypeIcon(this.getMoveType(calledMove, value)[0])} ${calledMove.name}`;
}
text += '<p>Accuracy: ' + accuracy + '</p>';
if (zEffect) text += '<p>Z-Effect: ' + zEffect + '</p>';
text += `<p>Accuracy: ${accuracy}</p>`;
if (zEffect) text += `<p>Z-Effect: ${zEffect}</p>`;
if (this.battle.hardcoreMode) {
text += '<p class="tooltip-section">' + move.shortDesc + '</p>';
text += `<p class="tooltip-section">${move.shortDesc}</p>`;
} else {
text += '<p class="tooltip-section">';
if (move.priority > 1) {
text += 'Nearly always moves first <em>(priority +' + move.priority + ')</em>.</p><p>';
text += `Nearly always moves first <em>(priority +${move.priority})</em>.</p><p>`;
} else if (move.priority <= -1) {
text += 'Nearly always moves last <em>(priority &minus;' + (-move.priority) + ')</em>.</p><p>';
text += `Nearly always moves last <em>(priority &minus;${-move.priority})</em>.</p><p>`;
} else if (move.priority === 1) {
text += 'Usually moves first <em>(priority +' + move.priority + ')</em>.</p><p>';
text += `Usually moves first <em>(priority +${move.priority})</em>.</p><p>`;
} else {
if (move.id === 'grassyglide' && this.battle.hasPseudoWeather('Grassy Terrain')) {
text += 'Usually moves first <em>(priority +1)</em>.</p><p>';
@ -803,7 +802,7 @@ export class BattleTooltips {
let name = BattleLog.escapeHTML(pokemon.name);
if (pokemon.speciesForme !== pokemon.name) {
name += ' <small>(' + BattleLog.escapeHTML(pokemon.speciesForme) + ')</small>';
name += ` <small>(${BattleLog.escapeHTML(pokemon.speciesForme)})</small>`;
}
let levelBuf = (pokemon.level !== 100 ? ` <small>L${pokemon.level}</small>` : ``);
@ -843,25 +842,31 @@ export class BattleTooltips {
text += '<p><small>HP:</small> (fainted)</p>';
} else if (this.battle.hardcoreMode) {
if (serverPokemon) {
text += '<p><small>HP:</small> ' + serverPokemon.hp + '/' + serverPokemon.maxhp + (pokemon.status ? ' <span class="status ' + pokemon.status + '">' + pokemon.status.toUpperCase() + '</span>' : '') + '</p>';
const status = pokemon.status ? ` <span class="status ${pokemon.status}">${pokemon.status.toUpperCase()}</span>` : '';
text += `<p><small>HP:</small> ${serverPokemon.hp}/${serverPokemon.maxhp}${status}</p>`;
}
} else {
let exacthp = '';
if (serverPokemon) {
exacthp = ' (' + serverPokemon.hp + '/' + serverPokemon.maxhp + ')';
exacthp = ` (${serverPokemon.hp}/${serverPokemon.maxhp})`;
} else if (pokemon.maxhp === 48) {
exacthp = ' <small>(' + pokemon.hp + '/' + pokemon.maxhp + ' pixels)</small>';
exacthp = ` <small>(${pokemon.hp}/${pokemon.maxhp} pixels)</small>`;
}
text += '<p><small>HP:</small> ' + Pokemon.getHPText(pokemon, this.battle.reportExactHP) + exacthp + (pokemon.status ? ' <span class="status ' + pokemon.status + '">' + pokemon.status.toUpperCase() + '</span>' : '');
const status = pokemon.status ? ` <span class="status ${pokemon.status}">${pokemon.status.toUpperCase()}</span>` : '';
text += `<p><small>HP:</small> ${Pokemon.getHPText(pokemon, this.battle.reportExactHP)}${exacthp}${status}`;
if (clientPokemon) {
if (pokemon.status === 'tox') {
if (pokemon.ability === 'Poison Heal' || pokemon.ability === 'Magic Guard') {
text += ' <small>Would take if ability removed: ' + Math.floor(100 / 16 * Math.min(clientPokemon.statusData.toxicTurns + 1, 15)) + '%</small>';
text += ` <small>Would take if ability removed: ${Math.floor(
100 / 16 * Math.min(clientPokemon.statusData.toxicTurns + 1, 15)
)}%</small>`;
} else {
text += ' Next damage: ' + Math.floor(100 / (clientPokemon.volatiles['dynamax'] ? 32 : 16) * Math.min(clientPokemon.statusData.toxicTurns + 1, 15)) + '%';
text += ` Next damage: ${Math.floor(
100 / (clientPokemon.volatiles['dynamax'] ? 32 : 16) * Math.min(clientPokemon.statusData.toxicTurns + 1, 15)
)}%`;
}
} else if (pokemon.status === 'slp') {
text += ' Turns asleep: ' + clientPokemon.statusData.sleepTurns;
text += ` Turns asleep: ${clientPokemon.statusData.sleepTurns}`;
}
}
text += '</p>';
@ -940,7 +945,7 @@ export class BattleTooltips {
text += `${this.getPPUseText(row)}<br />`;
}
if (clientPokemon.moveTrack.filter(([moveName]) => {
if (moveName.charAt(0) === '*') return false;
if (moveName.startsWith('*')) return false;
const move = this.battle.dex.moves.get(moveName);
return !move.isZ && !move.isMax && move.name !== 'Mimic';
}).length > 4) {
@ -1049,7 +1054,9 @@ export class BattleTooltips {
}
let item = toID(serverPokemon.item);
let speedHalvingEVItems = ['machobrace', 'poweranklet', 'powerband', 'powerbelt', 'powerbracer', 'powerlens', 'powerweight'];
let speedHalvingEVItems = [
'machobrace', 'poweranklet', 'powerband', 'powerbelt', 'powerbracer', 'powerlens', 'powerweight',
];
if (
(ability === 'klutz' && !speedHalvingEVItems.includes(item)) ||
this.battle.hasPseudoWeather('Magic Room') ||
@ -1362,7 +1369,7 @@ export class BattleTooltips {
chainedSpeedModifier *= modifier;
}
// Chained modifiers round down on 0.5
stats.spe = stats.spe * chainedSpeedModifier;
stats.spe *= chainedSpeedModifier;
stats.spe = stats.spe % 1 > 0.5 ? Math.ceil(stats.spe) : Math.floor(stats.spe);
if (pokemon.status === 'par' && ability !== 'quickfeet') {
@ -1381,7 +1388,7 @@ export class BattleTooltips {
if (!serverPokemon || isTransformed) {
if (!clientPokemon) throw new Error('Must pass either clientPokemon or serverPokemon');
let [min, max] = this.getSpeedRange(clientPokemon);
return '<p><small>Spe</small> ' + min + ' to ' + max + ' <small>(before items/abilities/modifiers)</small></p>';
return `<p><small>Spe</small> ${min} to ${max} <small>(before items/abilities/modifiers)</small></p>`;
}
const stats = serverPokemon.stats;
const modifiedStats = this.calculateModifiedStats(clientPokemon, serverPokemon);
@ -1394,8 +1401,8 @@ export class BattleTooltips {
if (this.battle.gen === 1 && statName === 'spd') continue;
let statLabel = this.battle.gen === 1 && statName === 'spa' ? 'spc' : statName;
buf += statName === 'atk' ? '<small>' : '<small> / ';
buf += '' + BattleText[statLabel].statShortName + '&nbsp;</small>';
buf += '' + stats[statName];
buf += `${BattleText[statLabel].statShortName}&nbsp;</small>`;
buf += `${stats[statName]}`;
if (modifiedStats[statName] !== stats[statName]) hasModifiedStat = true;
}
buf += '</p>';
@ -1410,13 +1417,13 @@ export class BattleTooltips {
if (this.battle.gen === 1 && statName === 'spd') continue;
let statLabel = this.battle.gen === 1 && statName === 'spa' ? 'spc' : statName;
buf += statName === 'atk' ? '<small>' : '<small> / ';
buf += '' + BattleText[statLabel].statShortName + '&nbsp;</small>';
buf += `${BattleText[statLabel].statShortName}&nbsp;</small>`;
if (modifiedStats[statName] === stats[statName]) {
buf += '' + modifiedStats[statName];
buf += `${modifiedStats[statName]}`;
} else if (modifiedStats[statName] < stats[statName]) {
buf += '<strong class="stat-lowered">' + modifiedStats[statName] + '</strong>';
buf += `<strong class="stat-lowered">${modifiedStats[statName]}</strong>`;
} else {
buf += '<strong class="stat-boosted">' + modifiedStats[statName] + '</strong>';
buf += `<strong class="stat-boosted">${modifiedStats[statName]}</strong>`;
}
}
buf += '</p>';
@ -1427,7 +1434,7 @@ export class BattleTooltips {
let [moveName, ppUsed] = moveTrackRow;
let move;
let maxpp;
if (moveName.charAt(0) === '*') {
if (moveName.startsWith('*')) {
// Transformed move
move = this.battle.dex.moves.get(moveName.substr(1));
maxpp = 5;
@ -1436,11 +1443,11 @@ export class BattleTooltips {
maxpp = (move.pp === 1 || move.noPPBoosts ? move.pp : move.pp * 8 / 5);
if (this.battle.gen < 3) maxpp = Math.min(61, maxpp);
}
const bullet = moveName.charAt(0) === '*' || move.isZ ? '<span style="color:#888">&#8226;</span>' : '&#8226;';
const bullet = moveName.startsWith('*') || move.isZ ? '<span style="color:#888">&#8226;</span>' : '&#8226;';
if (ppUsed === Infinity) {
return `${bullet} ${move.name} <small>(0/${maxpp})</small>`;
}
if (ppUsed || moveName.charAt(0) === '*') {
if (ppUsed || moveName.startsWith('*')) {
return `${bullet} ${move.name} <small>(${maxpp - ppUsed}/${maxpp})</small>`;
}
return `${bullet} ${move.name} ${showKnown ? ' <small>(revealed)</small>' : ''}`;
@ -1448,7 +1455,7 @@ export class BattleTooltips {
ppUsed(move: Dex.Move, pokemon: Pokemon) {
for (let [moveName, ppUsed] of pokemon.moveTrack) {
if (moveName.charAt(0) === '*') moveName = moveName.substr(1);
if (moveName.startsWith('*')) moveName = moveName.substr(1);
if (move.name === moveName) return ppUsed;
}
return 0;
@ -1472,7 +1479,7 @@ export class BattleTooltips {
if (rules['Frantic Fusions Mod']) {
const fusionSpecies = this.battle.dex.species.get(pokemon.name);
if (fusionSpecies.exists && fusionSpecies.name !== species.name) {
baseSpe = baseSpe + tr(fusionSpecies.baseStats.spe / 4);
baseSpe += tr(fusionSpecies.baseStats.spe / 4);
if (baseSpe < 1) baseSpe = 1;
if (baseSpe > 255) baseSpe = 255;
}
@ -1716,7 +1723,7 @@ export class BattleTooltips {
getMoveAccuracy(move: Dex.Move, value: ModifiableValue, target?: Pokemon) {
value.reset(move.accuracy === true ? 0 : move.accuracy, true);
let pokemon = value.pokemon!;
let pokemon = value.pokemon;
// Sure-hit accuracy
if (move.id === 'toxic' && this.battle.gen >= 6 && this.pokemonHasType(pokemon, 'Poison')) {
value.set(0, "Poison type");
@ -1859,7 +1866,7 @@ export class BattleTooltips {
// Takes into account the target for some moves.
// If it is unsure of the actual base power, it gives an estimate.
getMoveBasePower(move: Dex.Move, moveType: Dex.TypeName, value: ModifiableValue, target: Pokemon | null = null) {
const pokemon = value.pokemon!;
const pokemon = value.pokemon;
const serverPokemon = value.serverPokemon;
// apply modifiers for moves that depend on the actual stats
@ -2059,9 +2066,9 @@ export class BattleTooltips {
// Base power based on times hit
if (move.id === 'ragefist') {
value.set(Math.min(350, 50 + 50 * pokemon.timesAttacked),
pokemon.timesAttacked > 0
? `Hit ${pokemon.timesAttacked} time${pokemon.timesAttacked > 1 ? 's' : ''}`
: undefined);
pokemon.timesAttacked > 0 ?
`Hit ${pokemon.timesAttacked} time${pokemon.timesAttacked > 1 ? 's' : ''}` :
undefined);
}
if (!value.value) return value;
@ -2236,16 +2243,16 @@ export class BattleTooltips {
if (this.battle.tier.includes('Super Staff Bros')) {
if (move.id === 'bodycount') {
value.set(50 + 50 * pokemon.side.faintCounter,
pokemon.side.faintCounter > 0
? `${pokemon.side.faintCounter} teammate${pokemon.side.faintCounter > 1 ? 's' : ''} KOed`
: undefined);
pokemon.side.faintCounter > 0 ?
`${pokemon.side.faintCounter} teammate${pokemon.side.faintCounter > 1 ? 's' : ''} KOed` :
undefined);
}
// Base power based on times hit
if (move.id === 'vengefulmood') {
value.set(Math.min(140, 60 + 20 * pokemon.timesAttacked),
pokemon.timesAttacked > 0
? `Hit ${pokemon.timesAttacked} time${pokemon.timesAttacked > 1 ? 's' : ''}`
: undefined);
pokemon.timesAttacked > 0 ?
`Hit ${pokemon.timesAttacked} time${pokemon.timesAttacked > 1 ? 's' : ''}` :
undefined);
}
if (move.id === 'alting' && pokemon.shiny) {
value.set(69, 'Shiny');
@ -2399,9 +2406,11 @@ export class BattleTooltips {
}
if (speciesName === 'Ogerpon') {
const speciesForme = value.pokemon.getSpeciesForme();
if ((speciesForme.startsWith('Ogerpon-Wellspring') && itemName === 'Wellspring Mask') ||
if (
(speciesForme.startsWith('Ogerpon-Wellspring') && itemName === 'Wellspring Mask') ||
(speciesForme.startsWith('Ogerpon-Hearthflame') && itemName === 'Hearthflame Mask') ||
(speciesForme.startsWith('Ogerpon-Cornerstone') && itemName === 'Cornerstone Mask')) {
(speciesForme.startsWith('Ogerpon-Cornerstone') && itemName === 'Cornerstone Mask')
) {
value.itemModify(1.2);
return value;
}
@ -2422,14 +2431,14 @@ export class BattleTooltips {
return value;
}
getPokemonTypes(pokemon: Pokemon | ServerPokemon, preterastallized = false): ReadonlyArray<Dex.TypeName> {
getPokemonTypes(pokemon: Pokemon | ServerPokemon, preterastallized = false): readonly Dex.TypeName[] {
if (!(pokemon as Pokemon).getTypes) {
return this.battle.dex.species.get(pokemon.speciesForme).types;
}
return (pokemon as Pokemon).getTypeList(undefined, preterastallized);
}
pokemonHasType(pokemon: Pokemon | ServerPokemon, type: Dex.TypeName, types?: ReadonlyArray<Dex.TypeName>) {
pokemonHasType(pokemon: Pokemon | ServerPokemon, type: Dex.TypeName, types?: readonly Dex.TypeName[]) {
if (!types) types = this.getPokemonTypes(pokemon);
for (const curType of types) {
if (curType === type) return true;
@ -2459,16 +2468,14 @@ export class BattleTooltips {
const speciesForme = clientPokemon.getSpeciesForme() || serverPokemon?.speciesForme || '';
const species = this.battle.dex.species.get(speciesForme);
if (species.exists && species.abilities) {
abilityData.possibilities = [species.abilities['0']];
if (species.abilities['1']) abilityData.possibilities.push(species.abilities['1']);
if (species.abilities['H']) abilityData.possibilities.push(species.abilities['H']);
if (species.abilities['S']) abilityData.possibilities.push(species.abilities['S']);
abilityData.possibilities = Object.values(species.abilities);
if (this.battle.rules['Frantic Fusions Mod']) {
const fusionSpecies = this.battle.dex.species.get(clientPokemon.name);
if (fusionSpecies.exists && fusionSpecies.name !== species.name) {
abilityData.possibilities = Array.from(
new Set(abilityData.possibilities.concat(Object.values(fusionSpecies.abilities)))
);
for (const newAbility of Object.values(fusionSpecies.abilities)) {
if (abilityData.possibilities.includes(newAbility)) continue;
abilityData.possibilities.push(newAbility);
}
}
}
}
@ -2946,14 +2953,14 @@ class BattleStatGuesser {
let SRresistances = ['Ground', 'Steel', 'Fighting'];
let SRweak = 0;
if (set.ability !== 'Magic Guard' && set.ability !== 'Mountaineer') {
if (SRweaknesses.indexOf(species.types[0]) >= 0) {
if (SRweaknesses.includes(species.types[0])) {
SRweak++;
} else if (SRresistances.indexOf(species.types[0]) >= 0) {
} else if (SRresistances.includes(species.types[0])) {
SRweak--;
}
if (SRweaknesses.indexOf(species.types[1]) >= 0) {
if (SRweaknesses.includes(species.types[1])) {
SRweak++;
} else if (SRresistances.indexOf(species.types[1]) >= 0) {
} else if (SRresistances.includes(species.types[1])) {
SRweak--;
}
}
@ -2965,10 +2972,10 @@ class BattleStatGuesser {
hpDivisibility = 4;
} else if (set.item === 'Leftovers' || set.item === 'Black Sludge') {
hpDivisibility = 0;
} else if (hasMove['bellydrum'] && (set.item || '').slice(-5) === 'Berry') {
} else if (hasMove['bellydrum'] && (set.item || '').endsWith('Berry')) {
hpDivisibility = 2;
hpShouldBeDivisible = true;
} else if (hasMove['substitute'] && (set.item || '').slice(-5) === 'Berry') {
} else if (hasMove['substitute'] && (set.item || '').endsWith('Berry')) {
hpDivisibility = 4;
hpShouldBeDivisible = true;
} else if (SRweak >= 2 || hasMove['bellydrum']) {
@ -3044,7 +3051,6 @@ class BattleStatGuesser {
if (ev) evs['spe'] = ev;
}
}
}
if (hasMove['gyroball'] || hasMove['trickroom']) {
@ -3085,11 +3091,11 @@ class BattleStatGuesser {
let baseStat = species.baseStats[stat];
let iv = (set.ivs && set.ivs[stat]);
let iv = set.ivs?.[stat];
if (typeof iv !== 'number') iv = 31;
if (this.dex.gen <= 2) iv &= 30;
let ev = (set.evs && set.evs[stat]);
let ev = set.evs?.[stat];
if (typeof ev !== 'number') ev = (this.dex.gen > 2 ? 0 : 252);
if (evOverride !== undefined) ev = evOverride;
@ -3266,6 +3272,6 @@ declare const require: any;
declare const global: any;
if (typeof require === 'function') {
// in Node
(global as any).BattleStatGuesser = BattleStatGuesser;
(global as any).BattleStatOptimizer = BattleStatOptimizer;
global.BattleStatGuesser = BattleStatGuesser;
global.BattleStatOptimizer = BattleStatOptimizer;
}

View File

@ -30,14 +30,14 @@
// import $ from 'jquery';
import { BattleSceneStub } from './battle-scene-stub';
import { BattleLog } from './battle-log';
import {BattleScene, PokemonSprite, BattleStatusAnims} from './battle-animations';
import { BattleScene, type PokemonSprite, BattleStatusAnims } from './battle-animations';
import { Dex, Teams, toID, toUserid, type ID, type ModdedDex } from './battle-dex';
import { BattleTextParser, type Args, type KWArgs, type SideID } from './battle-text-parser';
declare const app: { user: AnyObject, rooms: AnyObject, ignore?: AnyObject } | undefined;
/** [id, element?, ...misc] */
export type EffectState = any[] & { 0: ID };
/** [name, minTimeLeft, maxTimeLeft] */
export type WeatherState = [string, number, number];
export type WeatherState = [name: string, minTimeLeft: number, maxTimeLeft: number];
export type HPColor = 'r' | 'y' | 'g';
export class Pokemon implements PokemonDetails, PokemonHealth {
@ -93,7 +93,7 @@ export class Pokemon implements PokemonDetails, PokemonHealth {
itemEffect = '';
prevItem = '';
prevItemEffect = '';
terastallized: string | '' = '';
terastallized = '';
teraType = '';
boosts: { [stat: string]: number } = {};
@ -174,7 +174,7 @@ export class Pokemon implements PokemonDetails, PokemonHealth {
if (range[0] === range[1]) {
let percentage = Math.abs(range[0] * 100);
if (Math.floor(percentage) === percentage) {
return percentage + '%';
return `${percentage}%`;
}
return percentage.toFixed(precision) + '%';
}
@ -187,7 +187,7 @@ export class Pokemon implements PokemonDetails, PokemonHealth {
lower = (range[0] * 100).toFixed(precision);
upper = (range[1] * 100).toFixed(precision);
}
return '' + lower + separator + upper + '%';
return `${lower}${separator}${upper}%`;
}
// Returns [min, max] damage dealt as a proportion of total HP from 0 to 1
getDamageRange(damage: any): [number, number] {
@ -216,7 +216,7 @@ export class Pokemon implements PokemonDetails, PokemonHealth {
healthParse(hpstring: string, parsedamage?: boolean, heal?: boolean):
[number, number, number] | [number, number, number, number, HPColor] | null {
// returns [delta, denominator, percent(, oldnum, oldcolor)] or null
if (!hpstring || !hpstring.length) return null;
if (!hpstring?.length) return null;
let parenIndex = hpstring.lastIndexOf('(');
if (parenIndex >= 0) {
// old style damage and health reporting
@ -266,7 +266,7 @@ export class Pokemon implements PokemonDetails, PokemonHealth {
if (!details) return false;
if (details === this.details) return true;
if (this.searchid) return false;
if (details.indexOf(', shiny') >= 0) {
if (details.includes(', shiny')) {
if (this.checkDetails(details.replace(', shiny', ''))) return true;
}
// the actual forme was hidden on Team Preview
@ -340,7 +340,7 @@ export class Pokemon implements PokemonDetails, PokemonHealth {
rememberMove(moveName: string, pp = 1, recursionSource?: string) {
if (recursionSource === this.ident) return;
moveName = Dex.moves.get(moveName).name;
if (moveName.charAt(0) === '*') return;
if (moveName.startsWith('*')) return;
if (moveName === 'Struggle') return;
if (this.volatiles.transform) {
// make sure there is no infinite recursion if both Pokemon are transformed into each other
@ -422,7 +422,7 @@ export class Pokemon implements PokemonDetails, PokemonHealth {
this.boosts = {};
this.clearVolatiles();
for (let i = 0; i < this.moveTrack.length; i++) {
if (this.moveTrack[i][0].charAt(0) === '*') {
if (this.moveTrack[i][0].startsWith('*')) {
this.moveTrack.splice(i, 1);
i--;
}
@ -477,8 +477,8 @@ export class Pokemon implements PokemonDetails, PokemonHealth {
this.removeVolatile('typeadd' as ID);
}
}
getTypes(serverPokemon?: ServerPokemon, preterastallized = false): [ReadonlyArray<Dex.TypeName>, Dex.TypeName | ''] {
let types: ReadonlyArray<Dex.TypeName>;
getTypes(serverPokemon?: ServerPokemon, preterastallized = false): [readonly Dex.TypeName[], Dex.TypeName | ''] {
let types: readonly Dex.TypeName[];
if (!preterastallized && this.terastallized && this.terastallized !== 'Stellar') {
types = [this.terastallized as Dex.TypeName];
} else if (this.volatiles.typechange) {
@ -588,8 +588,8 @@ export class Pokemon implements PokemonDetails, PokemonHealth {
return Pokemon.getHPText(this, this.side.battle.reportExactHP, precision);
}
static getHPText(pokemon: PokemonHealth, exactHP: boolean, precision = 1) {
if (exactHP) return pokemon.hp + '/' + pokemon.maxhp;
if (pokemon.maxhp === 100) return pokemon.hp + '%';
if (exactHP) return `${pokemon.hp}/${pokemon.maxhp}`;
if (pokemon.maxhp === 100) return `${pokemon.hp}%`;
if (pokemon.maxhp !== 48) return (100 * pokemon.hp / pokemon.maxhp).toFixed(precision) + '%';
let range = Pokemon.getPixelRange(pokemon.hp, pokemon.hpcolor);
return Pokemon.getFormattedRange(range, precision, '');
@ -610,9 +610,9 @@ export class Side {
isFar: boolean;
foe: Side = null!;
ally: Side | null = null;
avatar: string = 'unknown';
avatar = 'unknown';
badges: string[] = [];
rating: string = '';
rating = '';
totalPokemon = 6;
x = 0;
y = 0;
@ -625,8 +625,9 @@ export class Side {
lastPokemon = null as Pokemon | null;
pokemon = [] as Pokemon[];
/** [effectName, levels, minDuration, maxDuration] */
sideConditions: {[id: string]: [string, number, number, number]} = {};
sideConditions: {
[id: string]: [effectName: string, levels: number, minDuration: number, maxDuration: number],
} = {};
faintCounter = 0;
constructor(battle: Battle, n: number) {
@ -777,9 +778,9 @@ export class Side {
toRemove = poke2i;
} else if (poke === poke2) {
toRemove = poke1i;
} else if (this.active.indexOf(poke1) >= 0) {
} else if (this.active.includes(poke1)) {
toRemove = poke2i;
} else if (this.active.indexOf(poke2) >= 0) {
} else if (this.active.includes(poke2)) {
toRemove = poke1i;
} else if (poke1.fainted && !poke2.fainted) {
toRemove = poke2i;
@ -797,7 +798,7 @@ export class Side {
for (const curPoke of this.pokemon) {
if (curPoke === poke) continue;
if (curPoke.fainted) continue;
if (this.active.indexOf(curPoke) >= 0) continue;
if (this.active.includes(curPoke)) continue;
if (curPoke.speciesForme === 'Zoroark' || curPoke.speciesForme === 'Zorua' || curPoke.ability === 'Illusion') {
illusionFound = curPoke;
break;
@ -811,7 +812,7 @@ export class Side {
for (const curPoke of this.pokemon) {
if (curPoke === poke) continue;
if (curPoke.fainted) continue;
if (this.active.indexOf(curPoke) >= 0) continue;
if (this.active.includes(curPoke)) continue;
illusionFound = curPoke;
break;
}
@ -1099,7 +1100,7 @@ export class Battle {
gameType: 'singles' | 'doubles' | 'triples' | 'multi' | 'freeforall' = 'singles';
compatMode = true;
rated: string | boolean = false;
rules: {[ruleName: string]: 1 | 0} = {};
rules: { [ruleName: string]: 1 | undefined } = {};
isBlitz = false;
reportExactHP = false;
endLastTurnPending = false;
@ -1131,8 +1132,8 @@ export class Battle {
paused: boolean;
constructor(options: {
$frame?: JQuery<HTMLElement>,
$logFrame?: JQuery<HTMLElement>,
$frame?: JQuery,
$logFrame?: JQuery,
id?: ID,
log?: string[] | string,
paused?: boolean,
@ -1184,9 +1185,9 @@ export class Battle {
}
if (width && width < 640) {
const scale = (width / 640);
this.scene.$frame?.css('transform', 'scale(' + scale + ')');
this.scene.$frame?.css('transform', `scale(${scale})`);
this.scene.$frame?.css('transform-origin', 'top left');
this.scene.$frame?.css('margin-bottom', '' + (360 * scale - 360) + 'px');
this.scene.$frame?.css('margin-bottom', `${360 * scale - 360}px`);
// this.$foeHint.css('transform', 'scale(' + scale + ')');
} else {
this.scene.$frame?.css('transform', 'none');
@ -1433,7 +1434,7 @@ export class Battle {
if (this.gameType === 'freeforall') {
// TODO: Add FFA support
return;
} else {
}
let side1 = this.sides[0];
let side2 = this.sides[1];
for (const id of sideConditions) {
@ -1454,7 +1455,6 @@ export class Battle {
}
}
}
}
updateTurnCounters() {
for (const pWeather of this.pseudoWeather) {
if (pWeather[1]) pWeather[1]--;
@ -1498,7 +1498,7 @@ export class Battle {
pokemon.item = move.isZ;
let item = Dex.items.get(move.isZ);
if (item.zMoveFrom) moveName = item.zMoveFrom;
} else if (move.name.slice(0, 2) === 'Z-') {
} else if (move.name.startsWith('Z-')) {
moveName = moveName.slice(2);
move = Dex.moves.get(moveName);
if (window.BattleItems) {
@ -1733,8 +1733,7 @@ export class Battle {
}
let damageinfo = '' + Pokemon.getFormattedRange(range, damage[1] === 100 ? 0 : 1, '\u2013');
if (damage[1] !== 100) {
let hover = '' + ((damage[0] < 0) ? '\u2212' : '') +
Math.abs(damage[0]) + '/' + damage[1];
let hover = `${(damage[0] < 0) ? '\u2212' : ''}${Math.abs(damage[0])}/${damage[1]}`;
if (damage[1] === 48) { // this is a hack
hover += ' pixels';
}
@ -2216,7 +2215,6 @@ export class Battle {
}
this.log(args, kwArgs);
break;
}
case '-cureteam': { // For old gens when the whole team was always cured
let poke = this.getPokemon(args[1])!;
@ -2240,7 +2238,7 @@ export class Battle {
if (possibleTargets.length === 1) {
poke = possibleTargets[0]!;
} else {
this.activateAbility(ofpoke!, "Frisk");
this.activateAbility(ofpoke, "Frisk");
this.log(args, kwArgs);
break;
}
@ -2263,7 +2261,7 @@ export class Battle {
this.scene.resultAnim(poke, item.name, 'neutral');
break;
case 'frisk':
this.activateAbility(ofpoke!, "Frisk");
this.activateAbility(ofpoke, "Frisk");
if (poke && poke !== ofpoke) { // used for gen 6
poke.itemEffect = 'frisked';
this.scene.resultAnim(poke, item.name, 'neutral');
@ -2448,7 +2446,7 @@ export class Battle {
let commaIndex = newSpeciesForme.indexOf(',');
if (commaIndex !== -1) {
let level = newSpeciesForme.substr(commaIndex + 1).trim();
if (level.charAt(0) === 'L') {
if (level.startsWith('L')) {
poke.level = parseInt(level.substr(1), 10);
}
newSpeciesForme = args[2].substr(0, commaIndex);
@ -3139,7 +3137,8 @@ export class Battle {
default: {
throw new Error(`Unrecognized minor action: ${args[0]}`);
break;
}}
}
}
}
/*
parseSpriteData(name) {
@ -3400,10 +3399,10 @@ export class Battle {
}
case 'tier': {
this.tier = args[1];
if (this.tier.slice(-13) === 'Random Battle') {
if (this.tier.endsWith('Random Battle')) {
this.speciesClause = true;
}
if (this.tier.slice(-8) === ' (Blitz)') {
if (this.tier.endsWith(' (Blitz)')) {
this.messageFadeTime = 40;
this.isBlitz = true;
}
@ -3480,26 +3479,26 @@ export class Battle {
}
case 'inactive': {
if (!this.kickingInactive) this.kickingInactive = true;
if (args[1].slice(0, 11) === "Time left: ") {
if (args[1].startsWith("Time left: ")) {
let [time, totalTime, graceTime] = args[1].split(' | ');
this.kickingInactive = parseInt(time.slice(11), 10) || true;
this.totalTimeLeft = parseInt(totalTime, 10);
this.graceTimeLeft = parseInt(graceTime || '', 10) || 0;
if (this.totalTimeLeft === this.kickingInactive) this.totalTimeLeft = 0;
return;
} else if (args[1].slice(0, 9) === "You have ") {
} else if (args[1].startsWith("You have ")) {
// this is ugly but parseInt is documented to work this way
// so I'm going to be lazy and not chop off the rest of the
// sentence
this.kickingInactive = parseInt(args[1].slice(9), 10) || true;
return;
} else if (args[1].slice(-14) === ' seconds left.') {
} else if (args[1].endsWith(' seconds left.')) {
let hasIndex = args[1].indexOf(' has ');
let userid = window.app?.user?.get('userid');
if (toID(args[1].slice(0, hasIndex)) === userid) {
this.kickingInactive = parseInt(args[1].slice(hasIndex + 5), 10) || true;
}
} else if (args[1].slice(-27) === ' 15 seconds left this turn.') {
} else if (args[1].endsWith(' 15 seconds left this turn.')) {
if (this.isBlitz) return;
}
this.log(args, undefined, preempt);
@ -3598,7 +3597,7 @@ export class Battle {
break;
}
case 'poke': {
let pokemon = this.rememberTeamPreviewPokemon(args[1], args[2])!;
let pokemon = this.rememberTeamPreviewPokemon(args[1], args[2]);
if (args[3] === 'mail') {
pokemon.item = '(mail)';
} else if (args[3] === 'item') {
@ -3629,8 +3628,8 @@ export class Battle {
const side = this.getSide(args[1]);
side.clearPokemon();
for (const set of team) {
const details = set.species + (!set.level || set.level === 100 ? '' : ', L' + set.level) +
(!set.gender || set.gender === 'N' ? '' : ', ' + set.gender) + (set.shiny ? ', shiny' : '');
const details = set.species + (!set.level || set.level === 100 ? '' : `, L${set.level}`) +
(!set.gender || set.gender === 'N' ? '' : `, ${set.gender}`) + (set.shiny ? ', shiny' : '');
const pokemon = side.addPokemon('', '', details);
if (set.item) pokemon.item = set.item;
if (set.ability) pokemon.rememberAbility(set.ability);
@ -3644,14 +3643,14 @@ export class Battle {
}
case 'switch': case 'drag': case 'replace': {
this.endLastTurn();
let poke = this.getSwitchedPokemon(args[1], args[2])!;
let poke = this.getSwitchedPokemon(args[1], args[2]);
let slot = poke.slot;
poke.healthParse(args[3]);
poke.removeVolatile('itemremoved' as ID);
poke.terastallized = args[2].match(/tera:([a-z]+)$/i)?.[1] || '';
poke.terastallized = (/tera:([a-z]+)$/i.exec(args[2]))?.[1] || '';
if (args[0] === 'switch') {
if (poke.side.active[slot]) {
poke.side.switchOut(poke.side.active[slot]!, kwArgs);
poke.side.switchOut(poke.side.active[slot], kwArgs);
}
poke.side.switchIn(poke, kwArgs);
} else if (args[0] === 'replace') {
@ -3746,7 +3745,8 @@ export class Battle {
default: {
this.log(args, kwArgs, preempt);
break;
}}
}
}
}
run(str: string, preempt?: boolean) {
@ -3768,19 +3768,19 @@ export class Battle {
let nextArgs: Args = [''];
let nextKwargs: KWArgs = {};
const nextLine = this.stepQueue[this.currentStep + 1] || '';
if (nextLine.slice(0, 2) === '|-') {
if (nextLine.startsWith('|-')) {
({ args: nextArgs, kwArgs: nextKwargs } = BattleTextParser.parseBattleLine(nextLine));
}
if (this.debug) {
if (args[0].charAt(0) === '-' || args[0] === 'detailschange') {
if (args[0].startsWith('-') || args[0] === 'detailschange') {
this.runMinor(args, kwArgs, nextArgs, nextKwargs);
} else {
this.runMajor(args, kwArgs, preempt);
}
} else {
try {
if (args[0].charAt(0) === '-' || args[0] === 'detailschange') {
if (args[0].startsWith('-') || args[0] === 'detailschange') {
this.runMinor(args, kwArgs, nextArgs, nextKwargs);
} else {
this.runMajor(args, kwArgs, preempt);
@ -3893,7 +3893,8 @@ export class Battle {
let interruptionCount: number;
do {
this.waitForAnimations = true;
// modified in this.run() but idk how to tell TS that
this.waitForAnimations = true as this['waitForAnimations'];
if (this.currentStep >= this.stepQueue.length) {
this.atQueueEnd = true;
if (!this.ended && this.isReplay) this.prematureEnd();
@ -3954,6 +3955,6 @@ declare const require: any;
declare const global: any;
if (typeof require === 'function') {
// in Node
(global as any).Battle = Battle;
(global as any).Pokemon = Pokemon;
global.Battle = Battle;
global.Pokemon = Pokemon;
}

View File

@ -7,7 +7,8 @@
import { PS } from "./client-main";
declare var SockJS: any;
declare const SockJS: any;
declare const POKEMON_SHOWDOWN_TESTCLIENT_KEY: string | undefined;
export class PSConnection {
socket: any = null;
@ -64,10 +65,8 @@ export const PSLoginServer = new class {
let url = '/~~' + PS.server.id + '/action.php';
if (location.pathname.endsWith('.html')) {
url = 'https://' + Config.routes.client + url;
// @ts-ignore
if (typeof POKEMON_SHOWDOWN_TESTCLIENT_KEY === 'string') {
// @ts-ignore
data.sid = POKEMON_SHOWDOWN_TESTCLIENT_KEY.replace(/\%2C/g, ',');
data.sid = POKEMON_SHOWDOWN_TESTCLIENT_KEY.replace(/%2C/g, ',');
}
}
return Net(url).get({ method: data ? 'POST' : 'GET', body: data }).then(
@ -96,7 +95,7 @@ class HttpError extends Error {
this.body = body;
try {
(Error as any).captureStackTrace(this, HttpError);
} catch (err) {}
} catch {}
}
}
class NetRequest {

View File

@ -13,76 +13,12 @@
* @license AGPLv3
*/
/**********************************************************************
* Polyfills
*********************************************************************/
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function (searchElement, fromIndex) {
for (let i = (fromIndex || 0); i < this.length; i++) {
if (this[i] === searchElement) return i;
}
return -1;
};
}
if (!Array.prototype.includes) {
Array.prototype.includes = function (thing) {
return this.indexOf(thing) !== -1;
};
}
if (!String.prototype.includes) {
String.prototype.includes = function (thing) {
return this.indexOf(thing) !== -1;
};
}
if (!String.prototype.startsWith) {
String.prototype.startsWith = function (thing) {
return this.slice(0, thing.length) === thing;
};
}
if (!String.prototype.endsWith) {
String.prototype.endsWith = function (thing) {
return this.slice(-thing.length) === thing;
};
}
if (!String.prototype.trim) {
String.prototype.trim = function () {
return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
};
}
if (!Object.assign) {
Object.assign = function (thing: any, rest: any) {
for (let i = 1; i < arguments.length; i++) {
let source = arguments[i];
for (let k in source) {
thing[k] = source[k];
}
}
return thing;
};
}
if (!Object.create) {
Object.create = function (proto: any) {
function F() {}
F.prototype = proto;
return new (F as any)();
};
}
if (!window.console) {
// in IE8, the console object is only defined when devtools is open
// I don't actually know if this will cause problems when you open devtools,
// but that's something I can figure out if I ever bother testing in IE8
(window as any).console = {
log() {},
};
}
/**********************************************************************
* PS Models
*********************************************************************/
// PS's model classes are defined here
const PSURL = `${document.location!.protocol !== 'http:' ? 'https:' : ''}//${Config.routes.client}/`;
const PSURL = `${document.location.protocol !== 'http:' ? 'https:' : ''}//${Config.routes.client}/`;
export class PSSubscription {
observable: PSModel | PSStreamModel<any>;
@ -318,7 +254,7 @@ const PSBackground = new class extends PSStreamModel {
"",
];
}
if (!menuColors && bgUrl.charAt(0) === '#') {
if (!menuColors && bgUrl.startsWith('#')) {
const r = parseInt(bgUrl.slice(1, 3), 16) / 255;
const g = parseInt(bgUrl.slice(3, 5), 16) / 255;
const b = parseInt(bgUrl.slice(5, 7), 16) / 255;
@ -396,7 +332,7 @@ PSBackground.subscribe(bgUrl => {
if (bgUrl !== null) {
let background;
if (bgUrl.charAt(0) === '#') {
if (bgUrl.startsWith('#')) {
background = bgUrl;
} else if (PSBackground.curId !== 'custom') {
background = `#546bac url(${bgUrl}) no-repeat left center fixed`;
@ -424,7 +360,7 @@ PSBackground.subscribe(bgUrl => {
buttonStyleElem = new HTMLStyleElement();
buttonStyleElem.id = 'mainmenubuttoncolors';
buttonStyleElem.textContent = cssBuf;
document.head!.appendChild(buttonStyleElem);
document.head.appendChild(buttonStyleElem);
}
} else {
buttonStyleElem.textContent = cssBuf;

View File

@ -9,7 +9,7 @@
* @license AGPLv3
*/
import { PSConnection, PSLoginServer } from './client-connection';
import { type PSConnection, PSLoginServer } from './client-connection';
import { PSModel, PSStreamModel } from './client-core';
import type { PSRouter } from './panels';
import type { ChatRoom } from './panel-chat';
@ -204,7 +204,7 @@ class PSTeams extends PSStreamModel<'team' | 'format'> {
return;
}
if (buffer.charAt(0) === '[' && !buffer.trim().includes('\n')) {
if (buffer.startsWith('[') && !buffer.trim().includes('\n')) {
this.unpackOldBuffer(buffer);
return;
}
@ -243,7 +243,6 @@ class PSTeams extends PSStreamModel<'team' | 'format'> {
unpackOldBuffer(buffer: string) {
alert(`Your team storage format is too old for PS. You'll need to upgrade it at https://${Config.routes.client}/recoverteams.html`);
this.list = [];
return;
}
packAll(teams: Team[]) {
return teams.map(team => (
@ -264,7 +263,7 @@ class PSTeams extends PSStreamModel<'team' | 'format'> {
let slashIndex = line.lastIndexOf('/', pipeIndex);
if (slashIndex < 0) slashIndex = bracketIndex; // line.slice(slashIndex + 1, pipeIndex) will be ''
let format = bracketIndex > 0 ? line.slice(0, bracketIndex) : 'gen7';
if (format.slice(0, 3) !== 'gen') format = 'gen6' + format;
if (!format.startsWith('gen')) format = 'gen6' + format;
const name = line.slice(slashIndex + 1, pipeIndex);
return {
name,
@ -458,14 +457,14 @@ export class PSRoom extends PSStreamModel<Args | null> implements RoomOptions {
* In particular, this is `true` after sending `/join`, and `false`
* after sending `/leave`, even before the server responds.
*/
connected: boolean = false;
connected = false;
/**
* Can this room even be connected to at all?
* `true` = pass messages from the server to subscribers
* `false` = throw an error if we receive messages from the server
*/
readonly canConnect: boolean = false;
connectWhenLoggedIn: boolean = false;
connectWhenLoggedIn = false;
onParentEvent: ((eventId: 'focus' | 'keydown', e?: Event) => false | void) | null = null;
width = 0;
@ -540,7 +539,8 @@ export class PSRoom extends PSStreamModel<Args | null> implements RoomOptions {
} else {
throw new Error(`This room is not designed to receive messages`);
}
}}
}
}
}
handleMessage(line: string) {
if (!line.startsWith('/') || line.startsWith('//')) return false;
@ -551,7 +551,8 @@ export class PSRoom extends PSStreamModel<Args | null> implements RoomOptions {
case 'logout': {
PS.user.logOut();
return true;
}}
}
}
return false;
}
send(msg: string, direct?: boolean) {
@ -570,7 +571,7 @@ export class PSRoom extends PSStreamModel<Args | null> implements RoomOptions {
class PlaceholderRoom extends PSRoom {
queue = [] as Args[];
override readonly classType: 'placeholder' = 'placeholder';
override readonly classType = 'placeholder';
override receiveLine(args: Args) {
this.queue.push(args);
}
@ -667,7 +668,7 @@ export const PS = new class extends PSModel {
* the Rooms panel and clicking "Hide")
*
* Will NOT be true if only one panel fits onto the screen at the
* moment, but resizing will display multiple panels  for that,
* moment, but resizing will display multiple panels for that,
* check `PS.leftRoomWidth === 0`
*/
onePanelMode = false;
@ -841,7 +842,8 @@ export const PS = new class extends PSModel {
}
this.update();
continue;
}}
}
}
if (room) room.receiveLine(args);
}
if (room) room.update(isInit ? [`initdone`] : null);
@ -1062,7 +1064,7 @@ export const PS = new class extends PSModel {
options.id = `pm-${options.id.slice(10)}` as RoomID;
options.challengeMenuOpen = true;
}
if (options.id.startsWith('pm-') && options.id.indexOf('-', 3) < 0) {
if (options.id.startsWith('pm-') && !options.id.includes('-', 3)) {
const userid1 = PS.user.userid;
const userid2 = options.id.slice(3);
options.id = `pm-${[userid1, userid2].sort().join('-')}` as RoomID;

View File

@ -1,26 +1,28 @@
/* eslint-disable @typescript-eslint/consistent-type-imports */
// dex data
///////////
declare var BattleText: {[id: string]: {[templateName: string]: string}};
declare var BattleFormats: {[id: string]: import('./panel-teamdropdown').FormatData};
declare var BattlePokedex: any;
declare var BattleMovedex: any;
declare var BattleAbilities: any;
declare var BattleItems: any;
declare var BattleAliases: any;
declare var BattleStatuses: any;
declare var BattlePokemonSprites: any;
declare var BattlePokemonSpritesBW: any;
declare var NonBattleGames: {[id: string]: string};
declare const BattleText: { [id: string]: { [templateName: string]: string } };
declare const BattleFormats: { [id: string]: import('./panel-teamdropdown').FormatData };
declare const BattlePokedex: any;
declare const BattleMovedex: any;
declare const BattleAbilities: any;
declare const BattleItems: any;
declare const BattleAliases: any;
declare const BattleStatuses: any;
declare const BattlePokemonSprites: any;
declare const BattlePokemonSpritesBW: any;
declare const NonBattleGames: { [id: string]: string };
// PS globals
/////////////
declare var Config: any;
declare var Replays: any;
declare var exports: any;
declare const Config: any;
declare const Replays: any;
declare const exports: any;
type AnyObject = { [k: string]: any };
declare var app: {user: AnyObject, rooms: AnyObject, ignore?: AnyObject};
declare const app: { user: AnyObject, rooms: AnyObject, ignore?: AnyObject };
interface Window {
[k: string]: any;

View File

@ -31,7 +31,6 @@ export class MiniEdit {
* it doesn't already exist and the user types a newline at the end
* of the text, it wouldn't appear.
*/
// tslint:disable-next-line
_setContent: (text: string) => void;
pushHistory?: (text: string, selection: MiniEditSelection) => void;
onKeyDown = (ev: KeyboardEvent) => {
@ -41,7 +40,9 @@ export class MiniEdit {
}
};
constructor(el: HTMLElement, options: {setContent: MiniEdit['_setContent'], onKeyDown?: (ev: KeyboardEvent) => void}) {
constructor(
el: HTMLElement, options: { setContent: MiniEdit['_setContent'], onKeyDown?: (ev: KeyboardEvent) => void }
) {
this.element = el;
this._setContent = options.setContent;
@ -55,7 +56,6 @@ export class MiniEdit {
});
this.element.addEventListener('keydown', this.onKeyDown);
// tslint:disable-next-line
for (const Plugin of MiniEdit.plugins) new Plugin(this);
}
@ -79,7 +79,7 @@ export class MiniEdit {
this.pushHistory?.(text, selection);
}
getValue(): string {
let text = this.element.textContent || '';
const text = this.element.textContent || '';
if (text.endsWith('\n')) return text.slice(0, -1);
return text;
}

View File

@ -10,12 +10,12 @@ import {PS, PSRoom, type RoomOptions, type RoomID} from "./client-main";
import { PSPanelWrapper, PSRoomPanel } from "./panels";
import { ChatLog, ChatRoom, ChatTextEntry, ChatUserList } from "./panel-chat";
import { FormatDropdown } from "./panel-mainmenu";
import {Battle, Pokemon, type ServerPokemon} from "./battle";
import { Battle, type Pokemon, type ServerPokemon } from "./battle";
import { BattleScene } from "./battle-animations";
import { Dex, toID } from "./battle-dex";
import {
BattleChoiceBuilder, type BattleMoveRequest, type BattleRequest, type BattleRequestSideInfo,
type BattleSwitchRequest, type BattleTeamRequest
type BattleSwitchRequest, type BattleTeamRequest,
} from "./battle-choices";
import type { Args } from "./battle-text-parser";
@ -69,29 +69,41 @@ class BattlesPanel extends PSRoomPanel<BattlesRoom> {
override render() {
const room = this.props.room;
return <PSPanelWrapper room={room} scrollable><div class="pad">
<button class="button" style="float:right;font-size:10pt;margin-top:3px" name="close"><i class="fa fa-times"></i> Close</button>
<button class="button" style="float:right;font-size:10pt;margin-top:3px" name="close">
<i class="fa fa-times"></i> Close
</button>
<div class="roomlist">
<p>
<button class="button" name="refresh" onClick={this.refresh}><i class="fa fa-refresh"></i> Refresh</button> <span style={Dex.getPokemonIcon('meloetta-pirouette') + ';display:inline-block;vertical-align:middle'} class="picon" title="Meloetta is PS's mascot! The Pirouette forme is Fighting-type, and represents our battles."></span>
<button class="button" name="refresh" onClick={this.refresh}><i class="fa fa-refresh"></i> Refresh</button> {}
<span
style={Dex.getPokemonIcon('meloetta-pirouette') + ';display:inline-block;vertical-align:middle'} class="picon"
title="Meloetta is PS's mascot! The Pirouette forme is Fighting-type, and represents our battles."
></span>
</p>
<p>
<label class="label">Format:</label><FormatDropdown onChange={this.changeFormat} />
</p>
{/* <label>Minimum Elo: <select name="elofilter"><option value="none">None</option><option value="1100">1100</option><option value="1300">1300</option><option value="1500">1500</option><option value="1700">1700</option><option value="1900">1900</option></select></label>
{/* <label>
Minimum Elo: <select name="elofilter">
<option value="none">None</option><option value="1100">1100</option><option value="1300">1300</option>
<option value="1500">1500</option><option value="1700">1700</option><option value="1900">1900</option>
</select>
</label>
<form class="search">
<p>
<input type="text" name="prefixsearch" class="textbox" placeholder="Username prefix"/><button type="submit" class="button">Search</button>
<input type="text" name="prefixsearch" class="textbox" placeholder="Username prefix"/>
<button type="submit" class="button">Search</button>
</p>
</form> */}
<div class="list">{!room.battles ?
<div class="list">{!room.battles ? (
<p>Loading...</p>
: !room.battles.length ?
) : !room.battles.length ? (
<p>No battles are going on</p>
:
) : (
room.battles.map(battle => this.renderBattleLink(battle))
}</div>
)}</div>
</div>
</div></PSPanelWrapper>;
}
@ -129,7 +141,7 @@ class BattleRoom extends ChatRoom {
return true;
} case 'ffto': case 'fastfowardto': {
let turnNum = Number(target);
if (target.charAt(0) === '+' || turnNum < 0) {
if (target.startsWith('+') || turnNum < 0) {
turnNum += this.battle.turn;
if (turnNum < 0) turnNum = 0;
} else if (target === 'end') {
@ -169,7 +181,8 @@ class BattleRoom extends ChatRoom {
if (this.choices.isDone()) this.send(`/choose ${this.choices.toString()}`, true);
this.update(null);
return true;
}}
}
}
return super.handleMessage(line);
}
}
@ -343,20 +356,30 @@ class BattlePanel extends PSRoomPanel<BattleRoom> {
const atEnd = room.battle.atQueueEnd;
return <div class="controls">
<p>
{atEnd ?
{atEnd ? (
<button class="button disabled" name="cmd" value="/play"><i class="fa fa-play"></i><br />Play</button>
: room.battle.paused ?
) : room.battle.paused ? (
<button class="button" name="cmd" value="/play"><i class="fa fa-play"></i><br />Play</button>
:
) : (
<button class="button" name="cmd" value="/pause"><i class="fa fa-pause"></i><br />Pause</button>
} {}
<button class="button" name="cmd" value="/ffto -1"><i class="fa fa-step-backward"></i><br />Last turn</button>
<button class={"button" + (atEnd ? " disabled" : "")} name="cmd" value="/ffto +1"><i class="fa fa-step-forward"></i><br />Skip turn</button> {}
<button class="button" name="cmd" value="/ffto 0"><i class="fa fa-undo"></i><br />First turn</button>
<button class={"button" + (atEnd ? " disabled" : "")} name="cmd" value="/ffto end"><i class="fa fa-fast-forward"></i><br />Skip to end</button>
)} {}
<button class="button" name="cmd" value="/ffto -1">
<i class="fa fa-step-backward"></i><br />Last turn
</button>
<button class={"button" + (atEnd ? " disabled" : "")} name="cmd" value="/ffto +1">
<i class="fa fa-step-forward"></i><br />Skip turn
</button> {}
<button class="button" name="cmd" value="/ffto 0">
<i class="fa fa-undo"></i><br />First turn
</button>
<button class={"button" + (atEnd ? " disabled" : "")} name="cmd" value="/ffto end">
<i class="fa fa-fast-forward"></i><br />Skip to end
</button>
</p>
<p>
<button class="button" name="cmd" value="/switchsides"><i class="fa fa-random"></i> Switch sides</button>
<button class="button" name="cmd" value="/switchsides">
<i class="fa fa-random"></i> Switch sides
</button>
</p>
</div>;
}
@ -475,7 +498,7 @@ class BattlePanel extends PSRoomPanel<BattleRoom> {
<div class="switchmenu">
{team.map((serverPokemon, i) => {
return <PokemonButton
pokemon={serverPokemon} cmd={``} noHPBar disabled={true} tooltip={`switchpokemon|${i}`}
pokemon={serverPokemon} cmd="" noHPBar disabled tooltip={`switchpokemon|${i}`}
/>;
})}
</div>
@ -660,15 +683,17 @@ class BattlePanel extends PSRoomPanel<BattleRoom> {
return <div class="controls">
<div class="whatdo">
<button name="openTimer" class="button disabled timerbutton"><i class="fa fa-hourglass-start"></i> Timer</button>
{choices.alreadySwitchingIn.length > 0 ?
{choices.alreadySwitchingIn.length > 0 ? (
[<button name="cmd" value="/cancel" class="button"><i class="fa fa-chevron-left"></i> Back</button>,
" What about the rest of your team? "]
:
) : (
"How will you start the battle? "
}
)}
</div>
<div class="switchcontrols">
<h3 class="switchselect">Choose {choices.alreadySwitchingIn.length <= 0 ? `lead` : `slot ${choices.alreadySwitchingIn.length + 1}`}</h3>
<h3 class="switchselect">
Choose {choices.alreadySwitchingIn.length <= 0 ? `lead` : `slot ${choices.alreadySwitchingIn.length + 1}`}
</h3>
<div class="switchmenu">
{this.renderTeamControls(request, choices)}
<div style="clear:left"></div>
@ -681,7 +706,8 @@ class BattlePanel extends PSRoomPanel<BattleRoom> {
</div>
</div>
</div>;
}}
}
}
return null;
}
override render() {
@ -689,7 +715,9 @@ class BattlePanel extends PSRoomPanel<BattleRoom> {
return <PSPanelWrapper room={room}>
<BattleDiv></BattleDiv>
<ChatLog class="battle-log hasuserlist" room={this.props.room} onClick={this.focusIfNoSelection} left={640} noSubscription>
<ChatLog
class="battle-log hasuserlist" room={this.props.room} onClick={this.focusIfNoSelection} left={640} noSubscription
>
{}
</ChatLog>
<ChatTextEntry room={this.props.room} onMessage={this.send} onKey={this.onKey} left={640} />

View File

@ -15,7 +15,7 @@ import type {Battle} from "./battle";
import { MiniEdit } from "./miniedit";
import { PSUtils, toID, type ID } from "./battle-dex";
declare const formatText: any;
declare const formatText: any; // from js/server/chat-formatter.js
export class ChatRoom extends PSRoom {
override readonly classType: 'chat' | 'battle' = 'chat';
@ -92,7 +92,8 @@ export class ChatRoom extends PSRoom {
this.challengedFormat = null;
this.update(null);
return false;
}}
}
}
return super.handleMessage(line);
}
openChallenge() {
@ -256,7 +257,7 @@ export class ChatTextEntry extends preact.Component<{
}
handleKey(ev: KeyboardEvent) {
const cmdKey = ((ev.metaKey ? 1 : 0) + (ev.ctrlKey ? 1 : 0) === 1) && !ev.altKey && !ev.shiftKey;
const anyModifier = ev.ctrlKey || ev.altKey || ev.metaKey || ev.shiftKey;
// const anyModifier = ev.ctrlKey || ev.altKey || ev.metaKey || ev.shiftKey;
if (ev.keyCode === 13 && !ev.shiftKey) { // Enter key
return this.submit();
} else if (ev.keyCode === 13 && this.miniedit) { // enter
@ -380,7 +381,7 @@ class ChatPanel extends PSRoomPanel<ChatRoom> {
// `display: none` element, but contentEditable boxes are pickier.
// Waiting for a 0 timeout turns out to be enough.
setTimeout(() => {
(this.base!.querySelector('textarea, pre.textbox') as HTMLElement).focus();
this.base!.querySelector<HTMLElement>('textarea, pre.textbox')!.focus();
}, 0);
}
focusIfNoSelection = () => {
@ -474,7 +475,10 @@ export class ChatUserList extends preact.Component<{room: ChatRoom, left?: numbe
PSUtils.sortBy(userList, ([id, name]) => (
[PS.server.getGroup(name.charAt(0)).order, !name.endsWith('@!'), id]
));
return <ul class={'userlist' + (this.props.minimized ? (this.state.expanded ? ' userlist-maximized' : ' userlist-minimized') : '')} style={{left: this.props.left || 0}}>
return <ul
class={'userlist' + (this.props.minimized ? (this.state.expanded ? ' userlist-maximized' : ' userlist-minimized') : '')}
style={{ left: this.props.left || 0 }}
>
<li class="userlist-count" onClick={this.toggleExpanded}><small>{room.userCount} users</small></li>
{userList.map(([userid, name]) => {
const groupSymbol = name.charAt(0);
@ -490,13 +494,13 @@ export class ChatUserList extends preact.Component<{room: ChatRoom, left?: numbe
<em class={`group${['leadership', 'staff'].includes(group.type!) ? ' staffgroup' : ''}`}>
{groupSymbol}
</em>
{group.type === 'leadership' ?
{group.type === 'leadership' ? (
<strong><em style={{ color }}>{name.substr(1)}</em></strong>
: group.type === 'staff' ?
) : group.type === 'staff' ? (
<strong style={{ color }}>{name.substr(1)}</strong>
:
) : (
<span style={{ color }}>{name.substr(1)}</span>
}
)}
</button></li>;
})}
</ul>;
@ -505,7 +509,7 @@ export class ChatUserList extends preact.Component<{room: ChatRoom, left?: numbe
export class ChatLog extends preact.Component<{
class: string, room: ChatRoom, onClick?: (e: Event) => void, children?: preact.ComponentChildren,
left?: number, top?: number, noSubscription?: boolean;
left?: number, top?: number, noSubscription?: boolean,
}> {
log: BattleLog | null = null;
subscription: PSSubscription | null = null;
@ -575,9 +579,10 @@ export class ChatLog extends preact.Component<{
}
}
render() {
return <div class={this.props.class} role="log" onClick={this.props.onClick} style={{
left: this.props.left || 0, top: this.props.top || 0,
}}></div>;
return <div
class={this.props.class} role="log" onClick={this.props.onClick}
style={{ left: this.props.left || 0, top: this.props.top || 0 }}
></div>;
}
}

View File

@ -14,6 +14,7 @@ import {PSPanelWrapper, PSRoomPanel} from "./panels";
class ExampleRoom extends PSRoom {
override readonly classType: string = 'example';
// eslint-disable-next-line no-useless-constructor
constructor(options: RoomOptions) {
super(options);
}

View File

@ -18,9 +18,9 @@ export class LadderRoom extends PSRoom {
override readonly classType: string = 'ladder';
readonly format?: string = this.id.split('-')[1];
notice?: string;
searchValue: string = '';
lastSearch: string = '';
loading: boolean = false;
searchValue = '';
lastSearch = '';
loading = false;
error?: string;
ladderData?: string;
@ -53,7 +53,7 @@ export class LadderRoom extends PSRoom {
requestLadderData = (searchValue?: string) => {
const { teams } = PS;
if (teams.usesLocalLadder) {
this.send(`/cmd laddertop ${this.format} ${toID(this.searchValue)}`);
this.send(`/cmd laddertop ${this.format!} ${toID(this.searchValue)}`);
} else if (this.format !== undefined) {
Net('/ladder.php')
.get({
@ -185,7 +185,7 @@ class LadderPanel extends PSRoomPanel<LadderRoom> {
if (!BattleFormats) {
return <p>Loading...</p>;
}
let currentSection: string = "";
let currentSection = "";
let sections: JSX.Element[] = [];
let formats: JSX.Element[] = [];
for (const [key, format] of Object.entries(BattleFormats)) {

View File

@ -24,14 +24,16 @@ export type RoomInfo = {
export class MainMenuRoom extends PSRoom {
override readonly classType: string = 'mainmenu';
userdetailsCache: {[userid: string]: {
userdetailsCache: {
[userid: string]: {
userid: ID,
avatar?: string | number,
status?: string,
group?: string,
customgroup?: string,
rooms?: { [roomid: string]: { isPrivate?: true, p1?: string, p2?: string } },
}} = {};
},
} = {};
roomsCache: {
battleCount?: number,
userCount?: number,
@ -75,7 +77,8 @@ export class MainMenuRoom extends PSRoom {
const [, message] = args;
alert(message.replace(/\|\|/g, '\n'));
return;
}}
}
}
const lobby = PS.rooms['lobby'];
if (lobby) lobby.receiveLine(args);
}
@ -110,8 +113,8 @@ export class MainMenuRoom extends PSRoom {
let column = 0;
window.NonBattleGames = { rps: 'Rock Paper Scissors' };
for (let i = 3; i <= 9; i = i + 2) {
window.NonBattleGames['bestof' + i] = 'Best-of-' + i;
for (let i = 3; i <= 9; i += 2) {
window.NonBattleGames[`bestof${i}`] = `Best-of-${i}`;
}
window.BattleFormats = {};
for (let j = 1; j < formatsList.length; j++) {
@ -121,7 +124,7 @@ export class MainMenuRoom extends PSRoom {
isSection = false;
} else if (entry === ',LL') {
PS.teams.usesLocalLadder = true;
} else if (entry === '' || (entry.charAt(0) === ',' && !isNaN(Number(entry.slice(1))))) {
} else if (entry === '' || (entry.startsWith(',') && !isNaN(Number(entry.slice(1))))) {
isSection = true;
if (entry) {
@ -158,16 +161,16 @@ export class MainMenuRoom extends PSRoom {
}
}
let id = toID(name);
let isTeambuilderFormat = !team && name.slice(-11) !== 'Custom Game';
let isTeambuilderFormat = !team && !name.endsWith('Custom Game');
let teambuilderFormat = '' as ID;
let teambuilderFormatName = '';
if (isTeambuilderFormat) {
teambuilderFormatName = name;
if (id.slice(0, 3) !== 'gen') {
if (!id.startsWith('gen')) {
teambuilderFormatName = '[Gen 6] ' + name;
}
let parenPos = teambuilderFormatName.indexOf('(');
if (parenPos > 0 && name.slice(-1) === ')') {
if (parenPos > 0 && name.endsWith(')')) {
// variation of existing tier
teambuilderFormatName = teambuilderFormatName.slice(0, parenPos).trim();
}
@ -307,7 +310,7 @@ class NewsPanel extends PSRoomPanel {
class MainMenuPanel extends PSRoomPanel<MainMenuRoom> {
override focus() {
(this.base!.querySelector('button.big') as HTMLButtonElement).focus();
this.base!.querySelector<HTMLButtonElement>('button.big')!.focus();
}
submit = (e: Event) => {
alert('todo: implement');
@ -327,7 +330,9 @@ class MainMenuPanel extends PSRoomPanel<MainMenuRoom> {
return <div class="pmbox">
<div class="mini-window">
<h3 draggable onDragStart={this.handleDragStart} data-roomid={roomid}>
<button class="closebutton" name="closeRoom" value={roomid} aria-label="Close" tabIndex={-1}><i class="fa fa-times-circle"></i></button>
<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}
</h3>
@ -339,11 +344,11 @@ class MainMenuPanel extends PSRoomPanel<MainMenuRoom> {
renderSearchButton() {
if (PS.down) {
return <div class="menugroup" style="background: rgba(10,10,10,.6)">
{PS.down === 'ddos' ?
{PS.down === 'ddos' ? (
<p class="error"><strong>Pok&eacute;mon Showdown is offline due to a DDoS attack!</strong></p>
:
) : (
<p class="error"><strong>Pok&eacute;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="" />
@ -393,11 +398,11 @@ class MainMenuPanel extends PSRoomPanel<MainMenuRoom> {
</div>
<div class="rightmenu" style={{ display: PS.leftRoomWidth ? 'none' : 'block' }}>
<div class="menugroup">
{PS.server.id === 'showdown' ?
{PS.server.id === 'showdown' ? (
<p><button class={"mainmenu1" + onlineButton} name="joinRoom" value="rooms">Join chat</button></p>
:
) : (
<p><button class={"mainmenu1" + onlineButton} name="joinRoom" value="lobby">Join lobby chat</button></p>
}
)}
</div>
</div>
<div class="mainmenufooter">
@ -456,7 +461,7 @@ class TeamDropdown extends preact.Component<{format: string}> {
render() {
const teamFormat = PS.teams.teambuilderFormat(this.props.format);
const formatData = window.BattleFormats?.[teamFormat];
if (formatData && formatData.team) {
if (formatData?.team) {
return <button class="select teamselect preselected" name="team" value="random" disabled>
<div class="team">
<strong>Random team</strong>
@ -495,8 +500,8 @@ export class TeamForm extends preact.Component<{
};
submit = (e: Event) => {
e.preventDefault();
const format = (this.base!.querySelector('button[name=format]') as HTMLButtonElement).value;
const teamKey = (this.base!.querySelector('button[name=team]') as HTMLButtonElement).value;
const format = this.base!.querySelector<HTMLButtonElement>('button[name=format]')!.value;
const teamKey = this.base!.querySelector<HTMLButtonElement>('button[name=team]')!.value;
const team = teamKey ? PS.teams.byKey[teamKey] : undefined;
if (this.props.onSubmit) this.props.onSubmit(e, format, team);
};

View File

@ -17,7 +17,7 @@ class PageRoom extends PSRoom {
readonly page?: string = this.id.split("-")[1];
override readonly canConnect = true;
loading: boolean = true;
loading = true;
htmlData?: string;
setHTMLData = (htmlData?: string) => {
@ -40,7 +40,6 @@ class PageRoom extends PSRoom {
}
function PageLadderHelp(props: { room: PageRoom }) {
const {room} = props;
return <div class="ladder pad">
<p>
<button name="selectFormat" data-href="ladder" data-target="replace">

View File

@ -36,7 +36,7 @@ class RoomsPanel extends PSRoomPanel {
PS.update();
};
changeSearch = (e: Event) => {
const target = (e.currentTarget as HTMLInputElement);
const target = e.currentTarget as HTMLInputElement;
if (target.selectionStart !== target.selectionEnd) return;
this.search = target.value;
this.forceUpdate();
@ -44,7 +44,7 @@ class RoomsPanel extends PSRoomPanel {
keyDownSearch = (e: KeyboardEvent) => {
this.lastKeyCode = e.keyCode;
if (e.keyCode === 13) {
const target = (e.currentTarget as HTMLInputElement);
const target = e.currentTarget as HTMLInputElement;
let value = target.value;
const arrowIndex = value.indexOf(' \u21d2 ');
if (arrowIndex >= 0) value = value.slice(arrowIndex + 3);
@ -103,7 +103,7 @@ class RoomsPanel extends PSRoomPanel {
autoFillValue = ' \u21d2 ' + firstTitle;
}
const oldSearch = this.search;
const searchElem = this.base!.querySelector('input[type=search]') as HTMLInputElement;
const searchElem = this.base!.querySelector<HTMLInputElement>('input[type=search]')!;
searchElem.value = oldSearch + autoFillValue;
searchElem.setSelectionRange(oldSearch.length, oldSearch.length + autoFillValue.length);
}
@ -111,7 +111,7 @@ class RoomsPanel extends PSRoomPanel {
return { start, abbr, hidden };
}
override focus() {
(this.base!.querySelector('input[type=search]') as HTMLInputElement).focus();
this.base!.querySelector<HTMLInputElement>('input[type=search]')!.focus();
}
override render() {
if (this.hidden && PS.isVisible(this.props.room)) this.hidden = false;
@ -166,7 +166,7 @@ class RoomsPanel extends PSRoomPanel {
</div></PSPanelWrapper>;
}
renderRoomList(title: string, rooms?: RoomInfo[]) {
if (!rooms || !rooms.length) return null;
if (!rooms?.length) return null;
// Descending order
const sortedRooms = rooms.sort((a, b) => (b.userCount || 0) - (a.userCount || 0));
return <div class="roomlist">

View File

@ -158,7 +158,9 @@ class TeamTextbox extends preact.Component<{team: Team}> {
}
render() {
return <div class="teameditor">
<textarea class="textbox teamtextbox" onInput={this.input} onSelect={this.select} onClick={this.select} onKeyUp={this.select} />
<textarea
class="textbox teamtextbox" onInput={this.input} onSelect={this.select} onClick={this.select} onKeyUp={this.select}
/>
<textarea
class="textbox teamtextbox heighttester" style="visibility:hidden" tabIndex={-1} aria-hidden={true}
/>
@ -177,18 +179,22 @@ class TeamTextbox extends preact.Component<{team: Team}> {
const left = (num % 12) * 40;
const iconStyle = `background:transparent url(${Dex.resourcePrefix}sprites/pokemonicons-sheet.png) no-repeat scroll -${left}px -${top}px`;
return <span class="picon" style={
`top:${prevOffset + 1}px;left:50px;position:absolute;${iconStyle}`
}></span>;
return <span
class="picon" style={`top:${prevOffset + 1}px;left:50px;position:absolute;${iconStyle}`}
></span>;
})}
{this.activeOffsetY >= 0 &&
{this.activeOffsetY >= 0 && (
<div class="teaminnertextbox" style={{ top: this.activeOffsetY - 1 }}></div>
}
)}
</div>
{this.activeType && <div class="searchresults" style={{top: this.activeSetIndex >= 0 ? this.setInfo[this.activeSetIndex].bottomY - 12 : 0}}>
{this.activeType && (
<div
class="searchresults" style={{ top: this.activeSetIndex >= 0 ? this.setInfo[this.activeSetIndex].bottomY - 12 : 0 }}
>
<button class="button closesearch" onClick={this.closeMenu}><i class="fa fa-times"></i> Close</button>
<PSSearchResults search={this.search} />
</div>}
</div>
)}
</div>;
}
}
@ -223,7 +229,9 @@ class TeamPanel extends PSRoomPanel<TeamRoom> {
</button>
<label class="label teamname">
Team name:
<input class="textbox" type="text" value={team.name} onInput={this.rename} onChange={this.rename} onKeyUp={this.rename} />
<input
class="textbox" type="text" value={team.name} onInput={this.rename} onChange={this.rename} onKeyUp={this.rename}
/>
</label>
<TeamTextbox team={team} />
</div>

View File

@ -49,7 +49,8 @@ class TeambuilderRoom extends PSRoom {
PS.teams.undelete();
this.update(null);
return true;
}}
}
}
// unrecognized command
alert(`Unrecognized command: ${line}`);
@ -169,9 +170,9 @@ class TeambuilderPanel extends PSRoomPanel<TeambuilderRoom> {
const folderOpenIcon = room.curFolder === format ? 'fa-folder-open' : 'fa-folder';
if (gen === 0) {
renderedFolders.push(<TeamFolder cur={room.curFolder === format} value={format}>
<i class={
`fa ${folderOpenIcon}${format === '/' ? '-o' : ''}`
}></i>
<i
class={`fa ${folderOpenIcon}${format === '/' ? '-o' : ''}`}
></i>
{format.slice(0, -1) || '(uncategorized)'}
</TeamFolder>);
continue;
@ -212,7 +213,7 @@ class TeambuilderPanel extends PSRoomPanel<TeambuilderRoom> {
let filterFolder: string | null = null;
let filterFormat: string | null = null;
if (room.curFolder) {
if (room.curFolder.slice(-1) === '/') {
if (room.curFolder.endsWith('/')) {
filterFolder = room.curFolder.slice(0, -1);
teams = teams.filter(team => !team || team.folder === filterFolder);
} else {
@ -226,7 +227,7 @@ class TeambuilderPanel extends PSRoomPanel<TeambuilderRoom> {
{this.renderFolderList()}
</div>
<div class="teampane">
{filterFolder ?
{filterFolder ? (
<h2>
<i class="fa fa-folder-open"></i> {filterFolder} {}
<button class="button small" style="margin-left:5px" name="renameFolder">
@ -236,13 +237,13 @@ class TeambuilderPanel extends PSRoomPanel<TeambuilderRoom> {
<i class="fa fa-times"></i> Remove
</button>
</h2>
: filterFolder === '' ?
) : filterFolder === '' ? (
<h2><i class="fa fa-folder-open-o"></i> Teams not in any folders</h2>
: filterFormat ?
) : filterFormat ? (
<h2><i class="fa fa-folder-open-o"></i> {filterFormat} <small>({teams.length})</small></h2>
:
) : (
<h2>All Teams <small>({teams.length})</small></h2>
}
)}
<p>
<button name="cmd" value="/newteam" class="button big"><i class="fa fa-plus-circle"></i> New Team</button>
</p>
@ -254,7 +255,7 @@ class TeambuilderPanel extends PSRoomPanel<TeambuilderRoom> {
</li>
) : (
<li key="undelete">
<button name="cmd" value={`/undeleteteam`}><i class="fa fa-undo"></i> Undo delete</button>
<button name="cmd" value="/undeleteteam"><i class="fa fa-undo"></i> Undo delete</button>
</li>
))}
</ul>

View File

@ -24,14 +24,14 @@ export class PSTeambuilder {
// species
let id = toID(set.species);
buf += '|' + (toID(set.name || set.species) === id ? '' : id);
buf += `|${toID(set.name || set.species) === id ? '' : id}`;
// item
buf += '|' + toID(set.item);
buf += `|${toID(set.item)}`;
// ability
id = toID(set.ability);
buf += '|' + (id || '-');
buf += `|${id || '-'}`;
// moves
buf += '|';
@ -39,7 +39,7 @@ export class PSTeambuilder {
for (let j = 0; j < set.moves.length; j++) {
let moveid = toID(set.moves[j]);
if (j && !moveid) continue;
buf += (j ? ',' : '') + moveid;
buf += `${j ? ',' : ''}${moveid}`;
if (moveid.substr(0, 11) === 'hiddenpower' && moveid.length > 11) {
hasHP = moveid.slice(11);
}
@ -47,35 +47,24 @@ export class PSTeambuilder {
}
// nature
buf += '|' + (set.nature || '');
buf += `|${set.nature || ''}`;
// evs
if (set.evs) {
buf += '|' + (set.evs['hp'] || '') + ',' +
(set.evs['atk'] || '') + ',' +
(set.evs['def'] || '') + ',' +
(set.evs['spa'] || '') + ',' +
(set.evs['spd'] || '') + ',' +
(set.evs['spe'] || '');
buf += `|${set.evs['hp'] || ''},${set.evs['atk'] || ''},${set.evs['def'] || ''},` +
`${set.evs['spa'] || ''},${set.evs['spd'] || ''},${set.evs['spe'] || ''}`;
} else {
buf += '|';
}
// gender
if (set.gender) {
buf += '|' + set.gender;
} else {
buf += '|';
}
buf += `|${set.gender || ''}`;
// ivs
if (set.ivs) {
buf += '|' + (set.ivs['hp'] === 31 ? '' : set.ivs['hp']) + ',' +
(set.ivs['atk'] === 31 ? '' : set.ivs['atk']) + ',' +
(set.ivs['def'] === 31 ? '' : set.ivs['def']) + ',' +
(set.ivs['spa'] === 31 ? '' : set.ivs['spa']) + ',' +
(set.ivs['spd'] === 31 ? '' : set.ivs['spd']) + ',' +
(set.ivs['spe'] === 31 ? '' : set.ivs['spe']);
buf += `|${set.ivs['hp'] === 31 ? '' : set.ivs['hp']},${set.ivs['atk'] === 31 ? '' : set.ivs['atk']},` +
`${set.ivs['def'] === 31 ? '' : set.ivs['def']},${set.ivs['spa'] === 31 ? '' : set.ivs['spa']},` +
`${set.ivs['spd'] === 31 ? '' : set.ivs['spd']},${set.ivs['spe'] === 31 ? '' : set.ivs['spe']}`;
} else {
buf += '|';
}
@ -89,14 +78,14 @@ export class PSTeambuilder {
// level
if (set.level) {
buf += '|' + set.level;
buf += `|${set.level}`;
} else {
buf += '|';
}
// happiness
if (set.happiness !== undefined) {
buf += '|' + set.happiness;
buf += `|${set.happiness}`;
} else {
buf += '|';
}
@ -105,10 +94,10 @@ export class PSTeambuilder {
set.pokeball || (set.hpType && toID(set.hpType) !== hasHP) || set.gigantamax ||
(set.dynamaxLevel !== undefined && set.dynamaxLevel !== 10)
) {
buf += ',' + (set.hpType || '');
buf += ',' + toID(set.pokeball);
buf += ',' + (set.gigantamax ? 'G' : '');
buf += ',' + (set.dynamaxLevel !== undefined && set.dynamaxLevel !== 10 ? set.dynamaxLevel : '');
buf += `,${set.hpType || ''}`;
buf += `,${toID(set.pokeball)}`;
buf += `,${set.gigantamax ? 'G' : ''}`;
buf += `,${set.dynamaxLevel !== undefined && set.dynamaxLevel !== 10 ? set.dynamaxLevel : ''}`;
}
}
@ -137,10 +126,9 @@ export class PSTeambuilder {
// ability
const species = Dex.species.get(set.species);
set.ability = parts[3] === '-' ?
'' :
(species.baseSpecies === 'Zygarde' && parts[3] === 'H') ?
'Power Construct' :
set.ability =
parts[3] === '-' ? '' :
(species.baseSpecies === 'Zygarde' && parts[3] === 'H') ? 'Power Construct' :
['', '0', '1', 'H', 'S'].includes(parts[3]) ?
species.abilities[parts[3] as '0' || '0'] || (parts[3] === '' ? '' : '!!!ERROR!!!') :
Dex.abilities.get(parts[3]).name;
@ -333,7 +321,7 @@ export class PSTeambuilder {
line = line.slice(0, -4);
}
let parenIndex = line.lastIndexOf(' (');
if (line.charAt(line.length - 1) === ')' && parenIndex !== -1) {
if (line.endsWith(')') && parenIndex !== -1) {
set.species = Dex.species.get(line.slice(parenIndex + 2, -1)).name;
set.name = line.slice(0, parenIndex);
} else {
@ -392,13 +380,13 @@ export class PSTeambuilder {
if (isNaN(statval)) statval = 31;
set.ivs[statid] = statval;
}
} else if (line.match(/^[A-Za-z]+ (N|n)ature/)) {
} else if (/^[A-Za-z]+ (N|n)ature/.exec(line)) {
let natureIndex = line.indexOf(' Nature');
if (natureIndex === -1) natureIndex = line.indexOf(' nature');
if (natureIndex === -1) return;
line = line.substr(0, natureIndex);
if (line !== 'undefined') set.nature = line as Dex.NatureName;
} else if (line.charAt(0) === '-' || line.charAt(0) === '~') {
} else if (line.startsWith('-') || line.startsWith('~')) {
line = line.slice(line.charAt(1) === ' ' ? 2 : 1);
if (line.startsWith('Hidden Power [')) {
const hpType = line.slice(14, -1) as Dex.TypeName;
@ -676,13 +664,27 @@ class TeamDropdownPanel extends PSRoomPanel {
const hasOtherGens = genList.length > 1 || genList[0] !== baseGen;
teamList.push(<p>
{baseFormat.length > 4 && <button class={'button' + (baseFormat === this.format ? ' disabled' : '')} onClick={this.setFormat} name="format" value={baseFormat}>
{baseFormat.length > 4 && (
<button
class={'button' + (baseFormat === this.format ? ' disabled' : '')}
onClick={this.setFormat} name="format" value={baseFormat}
>
<i class="fa fa-folder-o"></i> [{baseFormat.slice(0, 4)}] {baseFormat.slice(4)}
</button>} <button class={'button' + (baseGen === this.format ? ' disabled' : '')} onClick={this.setFormat} name="format" value={baseGen}>
</button>
)} {}
<button
class={'button' + (baseGen === this.format ? ' disabled' : '')} onClick={this.setFormat} name="format" value={baseGen}
>
<i class="fa fa-folder-o"></i> [{baseGen}] <em>(uncategorized)</em>
</button> <button class={'button' + (baseGen === this.gen ? ' disabled' : '')} onClick={this.setFormat} name="gen" value={baseGen}>
</button> {}
<button
class={'button' + (baseGen === this.gen ? ' disabled' : '')} onClick={this.setFormat} name="gen" value={baseGen}
>
<i class="fa fa-folder-o"></i> [{baseGen}] <em>(all)</em>
</button> {hasOtherGens && !this.gen && <button class="button" onClick={this.setFormat} name="gen" value={baseGen}>Other gens</button>}
</button> {}
{hasOtherGens && !this.gen && (
<button class="button" onClick={this.setFormat} name="gen" value={baseGen}>Other gens</button>
)}
</p>);
if (hasOtherGens && this.gen) {
@ -716,7 +718,7 @@ class TeamDropdownPanel extends PSRoomPanel {
</h2>);
}
teamList.push(<ul class="teamdropdown" onClick={this.click}>
{teamBuckets[folder].map(team => <li key={team.key} style={"display:inline-block"}>
{teamBuckets[folder].map(team => <li key={team.key} style={{ display: 'inline-block' }}>
<TeamBox team={team} button />
</li>)}
</ul>);
@ -748,8 +750,6 @@ export interface FormatData {
}
declare const BattleFormats: { [id: string]: FormatData };
/** id:name */
declare const NonBattleGames: {[id: string]: string};
class FormatDropdownPanel extends PSRoomPanel {
gen = '';
@ -778,6 +778,7 @@ class FormatDropdownPanel extends PSRoomPanel {
let formatsLoaded = !!window.BattleFormats;
if (formatsLoaded) {
formatsLoaded = false;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
for (let i in window.BattleFormats) {
formatsLoaded = true;
break;

View File

@ -19,7 +19,7 @@ import {BattleLog} from "./battle-log";
window.addEventListener('drop', e => {
console.log('drop ' + e.dataTransfer!.dropEffect);
const target = e.target as HTMLElement;
if (/^text/.test((target as HTMLInputElement).type)) {
if ((target as HTMLInputElement).type.startsWith("text")) {
PS.dragging = null;
return; // Ignore text fields
}
@ -40,7 +40,7 @@ window.addEventListener('dragover', e => {
e.preventDefault();
});
export class PSHeader extends preact.Component<{style: {}}> {
export class PSHeader extends preact.Component<{ style: object }> {
handleDragEnter = (e: DragEvent) => {
console.log('dragenter ' + e.dataTransfer!.dropEffect);
e.preventDefault();
@ -63,6 +63,7 @@ export class PSHeader extends preact.Component<{style: {}}> {
if (rightIndex >= 0) {
this.dragOnto(draggingRoom, 'rightRoomList', rightIndex);
} else {
// eslint-disable-next-line no-useless-return
return;
}
}
@ -186,7 +187,7 @@ export class PSHeader extends preact.Component<{style: {}}> {
break;
case 'html':
default:
if (title.charAt(0) === '[') {
if (title.startsWith('[')) {
let closeBracketIndex = title.indexOf(']');
if (closeBracketIndex > 0) {
icon = <i class="text">{title.slice(1, closeBracketIndex)}</i>;
@ -291,13 +292,13 @@ class UserPanel extends PSRoomPanel<UserRoom> {
const group = PS.server.getGroup(room.name);
let groupName: preact.ComponentChild = group.name || null;
if (group.type === 'punishment') {
groupName = <span style='color:#777777'>{groupName}</span>;
groupName = <span style="color:#777777">{groupName}</span>;
}
const globalGroup = PS.server.getGroup(user.group);
let globalGroupName: preact.ComponentChild = globalGroup.name && `Global ${globalGroup.name}` || null;
if (globalGroup.type === 'punishment') {
globalGroupName = <span style='color:#777777'>{globalGroupName}</span>;
globalGroupName = <span style="color:#777777">{globalGroupName}</span>;
}
if (globalGroup.name === group.name) groupName = null;
@ -319,7 +320,8 @@ class UserPanel extends PSRoomPanel<UserRoom> {
const p1 = curRoom.p1!.substr(1);
const p2 = curRoom.p2!.substr(1);
const ownBattle = (PS.user.userid === toUserid(p1) || PS.user.userid === toUserid(p2));
const roomLink = <a href={`/${roomid}`} class={'ilink' + (ownBattle || roomid in PS.rooms ? ' yours' : '')}
const roomLink = <a
href={`/${roomid}`} class={'ilink' + (ownBattle || roomid in PS.rooms ? ' yours' : '')}
title={`${p1 || '?'} v. ${p2 || '?'}`}
>{roomrank}{roomid.substr(7)}</a>;
if (curRoom.isPrivate) {
@ -365,28 +367,31 @@ class UserPanel extends PSRoomPanel<UserRoom> {
{user.avatar !== '[loading]' &&
<img
class={'trainersprite' + (room.isSelf ? ' yours' : '')}
src={Dex.resolveAvatar('' + (user.avatar || 'unknown'))}
/>
}
<strong><a href={`//${Config.routes.users}/${user.userid}`} target="_blank" style={away ? {color: '#888888'} : null}>{name}</a></strong><br />
src={Dex.resolveAvatar(`${user.avatar || 'unknown'}`)}
/>}
<strong><a
href={`//${Config.routes.users}/${user.userid}`} target="_blank" style={away ? { color: '#888888' } : null}
>
{name}
</a></strong><br />
{status && <div class="userstatus">{status}</div>}
{groupName && <div class="usergroup roomgroup">{groupName}</div>}
{globalGroupName && <div class="usergroup globalgroup">{globalGroupName}</div>}
{user.customgroup && <div class="usergroup globalgroup">{user.customgroup}</div>}
{roomsList}
</div>
{isSelf || !PS.user.named ?
{isSelf || !PS.user.named ? (
<p class="buttonbar">
<button class="button" disabled>Challenge</button> {}
<button class="button" disabled>Chat</button>
</p>
:
) : (
<p class="buttonbar">
<button class="button" data-href={`/challenge-${user.userid}`}>Challenge</button> {}
<button class="button" data-href={`/pm-${user.userid}`}>Chat</button> {}
<button class="button disabled" name="userOptions">{'\u2026'}</button>
</p>
}
)}
{isSelf && <hr />}
{isSelf && <p class="buttonbar" style="text-align: right">
<button class="button disabled" name="login"><i class="fa fa-pencil"></i> Change name</button> {}
@ -423,7 +428,9 @@ class VolumePanel extends PSRoomPanel {
return <PSPanelWrapper room={room}>
<h3>Volume</h3>
<p class="volume">
<label class="optlabel">Effects: <span class="value">{!PS.prefs.mute && PS.prefs.effectvolume ? `${PS.prefs.effectvolume}%` : `muted`}</span></label>
<label class="optlabel">
Effects: <span class="value">{!PS.prefs.mute && PS.prefs.effectvolume ? `${PS.prefs.effectvolume}%` : `muted`}</span>
</label>
{PS.prefs.mute ?
<em>(muted)</em> :
<input
@ -432,7 +439,9 @@ class VolumePanel extends PSRoomPanel {
/>}
</p>
<p class="volume">
<label class="optlabel">Music: <span class="value">{!PS.prefs.mute && PS.prefs.musicvolume ? `${PS.prefs.musicvolume}%` : `muted`}</span></label>
<label class="optlabel">
Music: <span class="value">{!PS.prefs.mute && PS.prefs.musicvolume ? `${PS.prefs.musicvolume}%` : `muted`}</span>
</label>
{PS.prefs.mute ?
<em>(muted)</em> :
<input
@ -441,7 +450,10 @@ class VolumePanel extends PSRoomPanel {
/>}
</p>
<p class="volume">
<label class="optlabel">Notifications: <span class="value">{!PS.prefs.mute && PS.prefs.notifvolume ? `${PS.prefs.notifvolume}%` : `muted`}</span></label>
<label class="optlabel">
Notifications: {}
<span class="value">{!PS.prefs.mute && PS.prefs.notifvolume ? `${PS.prefs.notifvolume}%` : `muted`}</span>
</label>
{PS.prefs.mute ?
<em>(muted)</em> :
<input
@ -450,7 +462,9 @@ class VolumePanel extends PSRoomPanel {
/>}
</p>
<p>
<label class="checkbox"><input type="checkbox" name="mute" checked={PS.prefs.mute} onChange={this.setMute} /> Mute all</label>
<label class="checkbox">
<input type="checkbox" name="mute" checked={PS.prefs.mute} onChange={this.setMute} /> Mute all
</label>
</p>
</PSPanelWrapper>;
}

View File

@ -15,7 +15,7 @@ import {BattleLog} from "./battle-log";
import type { Args } from "./battle-text-parser";
import { BattleTooltips } from "./battle-tooltips";
import type { PSSubscription } from "./client-core";
import {PS, PSRoom, type RoomID} from "./client-main";
import { PS, type PSRoom, type RoomID } from "./client-main";
import { PSHeader } from "./panel-topbar";
export class PSRouter {
@ -277,6 +277,7 @@ export class PSMain extends preact.Component {
}
if (PS.room !== clickedRoom) {
if (clickedRoom) PS.room = clickedRoom;
// eslint-disable-next-line no-unmodified-loop-condition
while (PS.popups.length && (!clickedRoom || clickedRoom.id !== PS.popups[PS.popups.length - 1])) {
PS.closePopup();
}
@ -318,7 +319,7 @@ export class PSMain extends preact.Component {
const colorSchemeQuery = window.matchMedia?.('(prefers-color-scheme: dark)');
if (colorSchemeQuery?.media !== 'not all') {
colorSchemeQuery.addEventListener('change', function (cs) {
colorSchemeQuery.addEventListener('change', cs => {
if (PS.prefs.theme === 'system') document.body.className = cs.matches ? 'dark' : '';
});
}
@ -326,7 +327,7 @@ export class PSMain extends preact.Component {
PS.prefs.subscribeAndRun(key => {
if (!key || key === 'theme') {
const dark = PS.prefs.theme === 'dark' ||
(PS.prefs.theme === 'system' && colorSchemeQuery && colorSchemeQuery.matches);
(PS.prefs.theme === 'system' && colorSchemeQuery?.matches);
document.body.className = dark ? 'dark' : '';
}
});
@ -374,7 +375,7 @@ export class PSMain extends preact.Component {
try {
const selection = window.getSelection()!;
if (selection.type === 'Range') return false;
} catch (err) {}
} catch {}
BattleTooltips.hideTooltip();
}
static posStyle(room: PSRoom) {

View File

@ -93,9 +93,9 @@
}
})();
</script>
<script nomodule src="/js/lib/ps-polyfill.js"></script>
<script src="../config/testclient-key.js"></script>
<script src="js/client-core.js"></script>
<script nomodule src="/js/lib/ps-polyfill.js"></script>
<script src="js/battle-dex.js"></script>
<script src="js/battle-text-parser.js"></script>

View File

@ -27,7 +27,7 @@ var ReplaySidebarPanel = Panels.StaticPanel.extend({
if (!page) page = 2;
var user = elem.dataset.user;
var format = elem.dataset.format;
var private = !!elem.dataset.private;
var priv = !!elem.dataset.private;
var self = this;
elem.innerHTML = 'Loading...<br /><i class="fa fa-caret-down"></i>'
$.get('/search', Object.assign({
@ -35,7 +35,7 @@ var ReplaySidebarPanel = Panels.StaticPanel.extend({
format: format,
page: page,
output: 'html'
}, private ? {private: 1} : {}), function (data) {
}, priv ? {private: 1} : {}), function (data) {
self.$('ul.linklist').append(data);
// var $nextOffset = self.$('input.offset');
// var val = $nextOffset.val();

View File

@ -1,29 +1,30 @@
/** @jsx preact.h */
import preact from 'preact';
import preact from '../../play.pokemonshowdown.com/js/lib/preact';
import $ from 'jquery';
import { Net } from './utils';
import { PSRouter, PSReplays } from './replays';
import { Battle } from '../../play.pokemonshowdown.com/src/battle';
import { BattleLog } from '../../play.pokemonshowdown.com/src/battle-log';
import { BattleSound } from '../../play.pokemonshowdown.com/src/battle-sound';
import type { ID } from '../../play.pokemonshowdown.com/src/battle-dex';
declare function toID(input: string): string;
function showAd(id: string) {
// @ts-expect-error
// @ts-expect-error no clue how to declare this one
window.top.__vm_add = window.top.__vm_add || [];
// this is a x-browser way to make sure content has loaded.
(function (success) {
(success => {
if (window.document.readyState !== "loading") {
success();
} else {
window.document.addEventListener("DOMContentLoaded", function () {
window.document.addEventListener("DOMContentLoaded", () => {
success();
});
}
})(function () {
var placement = document.createElement("div");
})(() => {
const placement = document.createElement("div");
placement.setAttribute("class", "vm-placement");
if (window.innerWidth > 1000) {
// load desktop placement
@ -33,7 +34,7 @@ function showAd(id: string) {
placement.setAttribute("data-id", "645268557bc7b571c2f06f62");
}
document.querySelector("#" + id)!.appendChild(placement);
// @ts-expect-error
// @ts-expect-error no clue how to declare this one
window.top.__vm_add.push(placement);
});
}
@ -57,18 +58,18 @@ class BattleLogDiv extends preact.Component {
export class BattlePanel extends preact.Component<{ id: string }> {
result: {
uploadtime: number;
id: string;
format: string;
players: string[];
log: string;
views: number;
rating: number;
private: number;
password: string;
uploadtime: number,
id: string,
format: string,
players: string[],
log: string,
views: number,
rating: number,
private: number,
password: string,
} | null | undefined = undefined;
resultError = '';
battle: Battle | null;
battle!: Battle | null;
/** debug purposes */
lastUsedKeyCode = '0';
turnView: boolean | string = false;
@ -78,7 +79,7 @@ export class BattlePanel extends preact.Component<{id: string}> {
showAd('LeaderboardBTF');
window.onkeydown = this.keyPressed;
}
override componentWillReceiveProps(nextProps) {
override componentWillReceiveProps(nextProps: this['props']) {
if (this.stripQuery(this.props.id) !== this.stripQuery(nextProps.id)) {
this.loadBattle(nextProps.id);
}
@ -115,7 +116,7 @@ export class BattlePanel extends preact.Component<{id: string}> {
this.result = replay;
const $base = $(this.base!);
this.battle = new Battle({
id: replay.id,
id: replay.id as ID,
$frame: $base.find('.battle'),
$logFrame: $base.find('.battle-log'),
log: replay.log.split('\n'),
@ -135,7 +136,7 @@ export class BattlePanel extends preact.Component<{id: string}> {
if (query.turn || query.t) {
this.battle.seekTurn(parseInt(query.turn || query.t, 10));
}
} catch (err) {
} catch (err: any) {
this.result = null;
this.resultError = result.startsWith('{') ? err.toString() : result;
}
@ -159,14 +160,13 @@ export class BattlePanel extends preact.Component<{id: string}> {
}
}
keyPressed = (e: KeyboardEvent) => {
// @ts-ignore
this.lastUsedKeyCode = `${e.keyCode}`;
if (e.ctrlKey || e.metaKey || e.altKey) return;
if (e.keyCode === 27 && this.turnView) { // Esc
this.closeTurn();
return;
}
// @ts-ignore
// @ts-expect-error really wish they let me assert that the target is an HTMLElement
if (e.target?.tagName === 'INPUT' || e.target?.tagName === 'SELECT') return;
switch (e.keyCode) {
case 75: // k
@ -245,7 +245,7 @@ export class BattlePanel extends preact.Component<{id: string}> {
lastTurn = () => {
this.battle?.seekTurn(Infinity);
};
goToTurn = (e) => {
goToTurn = (e: Event) => {
const turn = this.base?.querySelector<HTMLInputElement>('input[name=turn]')?.value;
if (!turn?.trim()) return this.closeTurn(e);
let turnNum = Number(turn);
@ -273,9 +273,9 @@ export class BattlePanel extends preact.Component<{id: string}> {
// ladies and gentlemen, JavaScript dates
const timestamp = (this.result?.uploadtime || 0) * 1000;
const date = new Date(timestamp);
filename += '-' + date.getFullYear();
filename += (date.getMonth() >= 9 ? '-' : '-0') + (date.getMonth() + 1);
filename += (date.getDate() >= 10 ? '-' : '-0') + date.getDate();
filename += `-${date.getFullYear()}`;
filename += `${date.getMonth() >= 9 ? '-' : '-0'}${date.getMonth() + 1}`;
filename += `${date.getDate() >= 10 ? '-' : '-0'}${date.getDate()}`;
filename += '-' + toID(this.battle.p1.name);
filename += '-' + toID(this.battle.p2.name);
@ -306,18 +306,18 @@ export class BattlePanel extends preact.Component<{id: string}> {
fast: 50,
normal: 300,
slow: 500,
reallyslow: 1000
reallyslow: 1000,
};
const delayTable = {
hyperfast: 1,
fast: 1,
normal: 1,
slow: 1000,
reallyslow: 3000
reallyslow: 3000,
};
if (!this.battle) return;
this.battle.messageShownTime = delayTable[speed];
this.battle.messageFadeTime = fadeTable[speed];
this.battle.messageShownTime = delayTable[speed as 'fast'];
this.battle.messageFadeTime = fadeTable[speed as 'fast'];
this.battle.scene.updateAcceleration();
};
stepSpeed(delta: number) {
@ -338,7 +338,7 @@ export class BattlePanel extends preact.Component<{id: string}> {
const muted = (e.target as HTMLSelectElement).value;
this.battle?.setMute(muted === 'off');
// Wolfram Alpha says that default volume is 100 e^(-(2 log^2(2))/log(10)) which is around 65.881
BattleSound.setBgmVolume(muted === 'musicoff' ? 0 : 65.881258001265573);
BattleSound.setBgmVolume(muted === 'musicoff' ? 0 : 65.88125800126558);
this.forceUpdate();
};
changeDarkMode = (e: Event) => {
@ -348,7 +348,7 @@ export class BattlePanel extends preact.Component<{id: string}> {
this.forceUpdate();
};
openTurn = (e: Event) => {
this.turnView = `${this.battle?.turn}` || true;
this.turnView = `${this.battle?.turn || ''}` || true;
this.autofocusTurnView = 'select';
e.preventDefault();
this.forceUpdate();
@ -360,19 +360,22 @@ export class BattlePanel extends preact.Component<{id: string}> {
};
renderError(position: any) {
if (this.resultError) {
return <div class={PSRouter.showingLeft() ? 'mainbar has-sidebar' : 'mainbar'} style={position}><section class="section">
return <div class={PSRouter.showingLeft() ? 'mainbar has-sidebar' : 'mainbar'} style={position}>
<section class="section">
<h1>Error</h1>
<p>
{this.resultError}
</p>
</section></div>;
</section>
</div>;
}
// In theory, this should almost never happen, because Replays will
// never link to a nonexistent replay, but this might happen if e.g.
// a replay gets deleted or made private after you searched for it
// but before you clicked it.
return <div class={PSRouter.showingLeft() ? 'mainbar has-sidebar' : 'mainbar'} style={position}><section class="section" style={{maxWidth: '200px'}}>
return <div class={PSRouter.showingLeft() ? 'mainbar has-sidebar' : 'mainbar'} style={position}>
<section class="section" style={{ maxWidth: '200px' }}>
<div style={{ textAlign: 'center' }}>
<img src="//play.pokemonshowdown.com/sprites/gen5ani/unown-n.gif" alt="" style={{ imageRendering: 'pixelated' }} />
<img src="//play.pokemonshowdown.com/sprites/gen5ani/unown-o.gif" alt="" style={{ imageRendering: 'pixelated' }} />
@ -393,7 +396,8 @@ export class BattlePanel extends preact.Component<{id: string}> {
<p>
In the future, remember to click <strong>Upload and share replay</strong> to save a replay permanently.
</p>
</section></div>;
</section>
</div>;
}
renderControls() {
const atEnd = !this.battle || this.battle.atQueueEnd;
@ -409,30 +413,35 @@ export class BattlePanel extends preact.Component<{id: string}> {
<button type="button" class="button" onClick={this.closeTurn}>Cancel</button>
</form>
<p>
<em>Pro tip:</em> You don't need to click "Skip to turn" if you have a keyboard, just start typing the turn number and press <kbd>Enter</kbd>. For more shortcuts, press <kbd>Shift</kbd>+<kbd>/</kbd> when a text box isn't focused.
<em>Pro tip:</em> You don't need to click "Skip to turn" if you have a keyboard, just start typing
the turn number and press <kbd>Enter</kbd>. For more shortcuts, press <kbd>Shift</kbd>+<kbd>/</kbd>
when a text box isn't focused.
</p>
</section></div>;
}
return <div class="replay-controls">
<p>
{atEnd && this.battle ?
{atEnd && this.battle ? (
<button onClick={this.replay} class="button" style={{ width: '5em', marginRight: '3px' }}>
<i class="fa fa-undo"></i><br />Replay
</button>
: (!this.battle || this.battle.paused) ?
) : !this.battle || this.battle.paused ? (
<button onClick={this.play} class="button" disabled={!this.battle} style={{ width: '5em', marginRight: '3px' }}>
<i class="fa fa-play"></i><br /><strong>Play</strong>
</button>
:
) : (
<button onClick={this.pause} class="button" style={{ width: '5em', marginRight: '3px' }}>
<i class="fa fa-pause"></i><br /><strong>Pause</strong>
</button>
} {}
)} {}
<button class="button button-first" disabled={atStart} onClick={this.firstTurn}>
<i class="fa fa-fast-backward"></i><br />First turn
</button>
<button class="button button-first" disabled={atStart} style={{marginLeft:'1px',position:'relative',zIndex:'1'}} onClick={this.prevTurn}>
<button
class="button button-first" disabled={atStart} style={{ marginLeft: '1px', position: 'relative', zIndex: '1' }}
onClick={this.prevTurn}
>
<i class="fa fa-step-backward"></i><br />Prev turn
</button>
<button class="button button-last" disabled={atEnd} style={{ marginRight: '2px' }} onClick={this.nextTurn}>
@ -458,7 +467,10 @@ export class BattlePanel extends preact.Component<{id: string}> {
</label> {}
<label class="optgroup">
Sound:<br />
<select name="sound" class="button" onChange={this.changeSound} value={BattleSound.muted ? 'off' : BattleSound.bgmVolume ? 'on' : 'musicoff'}>
<select
name="sound" class="button" onChange={this.changeSound}
value={BattleSound.muted ? 'off' : BattleSound.bgmVolume ? 'on' : 'musicoff'}
>
<option value="on">On</option>
<option value="musicoff">Music Off</option>
<option value="off">Muted</option>
@ -510,11 +522,13 @@ export class BattlePanel extends preact.Component<{id: string}> {
if (this.result === null) return this.renderError(position);
return <div class={PSRouter.showingLeft() ? 'mainbar has-sidebar' : 'mainbar'} style={position}><div style={{position: 'relative'}}>
return <div class={PSRouter.showingLeft() ? 'mainbar has-sidebar' : 'mainbar'} style={position}>
<div style={{ position: 'relative' }}>
<BattleDiv />
<BattleLogDiv />
{this.renderControls()}
<div id="LeaderboardBTF"></div>
</div></div>;
</div>
</div>;
}
}

View File

@ -1,5 +1,5 @@
/** @jsx preact.h */
import preact from 'preact';
import preact from '../../play.pokemonshowdown.com/js/lib/preact';
import { Net, PSModel } from './utils';
import { BattlePanel } from './replays-battle';
declare function toID(input: string): string;
@ -28,7 +28,7 @@ class SearchPanel extends preact.Component<{id: string}> {
sort = 'date';
override componentDidMount() {
Net('/check-login.php').get().then(result => {
if (result.charAt(0) !== ']') return;
if (!result.startsWith(']')) return;
const [userid, sysop] = result.slice(1).split(',');
this.loggedInUser = userid;
this.loggedInUserIsSysop = !!sysop;
@ -56,7 +56,7 @@ class SearchPanel extends preact.Component<{id: string}> {
this.resultError = null;
if (isPrivate) {
if (response.charAt(0) !== ']') {
if (!response.startsWith(']')) {
this.resultError = `Unrecognized response: ${response}`;
return;
}
@ -72,7 +72,7 @@ class SearchPanel extends preact.Component<{id: string}> {
search(user: string, format: string, isPrivate?: boolean, page = 1) {
this.base!.querySelector<HTMLInputElement>('input[name=user]')!.value = user;
this.base!.querySelector<HTMLInputElement>('input[name=format]')!.value = format;
this.base!.querySelectorAll<HTMLInputElement>('input[name=private]')[isPrivate ? 1 : 0]!.checked = true;
this.base!.querySelectorAll<HTMLInputElement>('input[name=private]')[isPrivate ? 1 : 0].checked = true;
if (!format && !user) return this.recent();
this.user = user;
@ -84,7 +84,7 @@ class SearchPanel extends preact.Component<{id: string}> {
if (user || !format) this.byRating = false;
if (!format && !user) {
PSRouter.replace('')
PSRouter.replace('');
} else {
PSRouter.replace('?' + Net.encodeQuery({
user: user || undefined,
@ -150,7 +150,7 @@ class SearchPanel extends preact.Component<{id: string}> {
};
searchLoggedIn = (e: Event) => {
if (!this.loggedInUser) return; // shouldn't happen
(this.base!.querySelector('input[name=user]') as HTMLInputElement).value = this.loggedInUser;
this.base!.querySelector<HTMLInputElement>('input[name=user]')!.value = this.loggedInUser;
this.submitForm(e);
};
url(replay: ReplayResult) {
@ -196,7 +196,8 @@ class SearchPanel extends preact.Component<{id: string}> {
<label>
Username:<br />
<input type="search" class="textbox" name="user" placeholder="(blank = any user)" size={20} /> {}
{this.loggedInUser && <button type="button" class="button" onClick={this.searchLoggedIn}>{this.loggedInUser}'s replays</button>}
{this.loggedInUser &&
<button type="button" class="button" onClick={this.searchLoggedIn}>{this.loggedInUser}'s replays</button>}
</label>
</p>
<p>
@ -296,27 +297,39 @@ class FeaturedReplays extends preact.Component {
<li><a href="doublesou-232753081" class="blocklink">
<small>[gen6-doublesou]<br /></small>
<strong>Electrolyte</strong> vs. <strong>finally</strong>
<small><br />finally steals Electrolyte's spot in the finals of the Doubles Winter Seasonal by outplaying Toxic Aegislash.</small>
<small><br />
finally steals Electrolyte's spot in the finals of the Doubles Winter Seasonal by outplaying Toxic Aegislash.
</small>
</a></li>
<li><a href="smogtours-gen5ou-59402" class="blocklink">
<small>[gen5-ou]<br /></small>
<strong>Reymedy</strong> vs. <strong>Leftiez</strong>
<small><br />Reymedy's superior grasp over BW OU lead to his claim of victory over Leftiez in the No Johns Tournament.</small>
<small><br />
Reymedy's superior grasp over BW OU lead to his claim of victory over Leftiez in the No Johns Tournament.
</small>
</a></li>
<li><a href="smogtours-gen3ou-56583" class="blocklink">
<small>[gen3-ou]<br /></small>
<strong>pokebasket</strong> vs. <strong>Alf'</strong>
<small><br />pokebasket proved Blissey isn't really one to take a Focus Punch well in his victory match over Alf' in the Fuck Trappers ADV OU tournament.</small>
<small><br />
pokebasket proved Blissey isn't really one to take a Focus Punch well in his victory match over Alf' in the
Fuck Trappers ADV OU tournament.
</small>
</a></li>
<li><a href="smogtours-ou-55891" class="blocklink">
<small>[gen6-ou]<br /></small>
<strong>Marshall.Law</strong> vs. <strong>Malekith</strong>
<small><br />In a "match full of reverses", Marshall.Law takes on Malekith in the finals of It's No Use.</small>
<small><br />
In a "match full of reverses", Marshall.Law takes on Malekith in the finals of It's No Use.
</small>
</a></li>
<li><a href="smogtours-ubers-54583" class="blocklink">
<small>[gen6-custom]<br /></small>
<strong>hard</strong> vs. <strong>panamaxis</strong>
<small><br />Dark horse panamaxis proves his worth as the rightful winner of The Walkthrough Tournament in this exciting final versus hard.</small>
<small><br />
Dark horse panamaxis proves his worth as the rightful winner of The Walkthrough Tournament in this exciting
final versus hard.
</small>
</a></li>
{!this.moreCompetitive && <li style={{ paddingLeft: '8px' }}>
<button class="button" onClick={this.showMoreCompetitive}>More <i class="fa fa-caret-right" aria-hidden></i></button>
@ -324,27 +337,41 @@ class FeaturedReplays extends preact.Component {
{this.moreCompetitive && <li><a href="smogtours-ubers-34646" class="blocklink">
<small>[gen6-ubers]<br /></small>
<strong>steelphoenix</strong> vs. <strong>Jibaku</strong>
<small><br />In this SPL Week 4 battle, Jibaku's clever plays with Mega Sableye keep the momentum mostly in his favor.</small>
<small><br />
In this SPL Week 4 battle, Jibaku's clever plays with Mega Sableye keep the momentum mostly in his favor.
</small>
</a></li>}
{this.moreCompetitive && <li><a href="smogtours-uu-36860" class="blocklink">
<small>[gen6-uu]<br /></small>
<strong>IronBullet93</strong> vs. <strong>Laurel</strong>
<small><br />Laurel outplays IronBullet's Substitute Tyrantrum with the sly use of a Shuca Berry Cobalion, but luck was inevitably the deciding factor in this SPL Week 6 match.</small>
<small><br />
Laurel outplays IronBullet's Substitute Tyrantrum with the sly use of a Shuca Berry Cobalion, but luck was
inevitably the deciding factor in this SPL Week 6 match.
</small>
</a></li>}
{this.moreCompetitive && <li><a href="smogtours-gen5ou-36900" class="blocklink">
<small>[gen5-ou]<br /></small>
<strong>Lowgock</strong> vs. <strong>Meridian</strong>
<small><br />This SPL Week 6 match features impressive plays, from Jirachi sacrificing itself to paralysis to avoid a burn to some clever late-game switches.</small>
<small><br />
This SPL Week 6 match features impressive plays, from Jirachi sacrificing itself to paralysis to avoid a
burn to some clever late-game switches.
</small>
</a></li>}
{this.moreCompetitive && <li><a href="smogtours-gen4ou-36782" class="blocklink">
<small>[gen4-ou]<br /></small>
<strong>Heist</strong> vs. <strong>liberty32</strong>
<small><br />Starting out as an entry hazard-filled stallfest, this close match is eventually decided by liberty32's efficient use of Aerodactyl.</small>
<small><br />
Starting out as an entry hazard-filled stallfest, this close match is eventually decided by liberty32's
efficient use of Aerodactyl.
</small>
</a></li>}
{this.moreCompetitive && <li><a href="randombattle-213274483" class="blocklink">
<small>[gen6-randombattle]<br /></small>
<strong>The Immortal</strong> vs. <strong>Amphinobite</strong>
<small><br />Substitute Lugia and Rotom-Fan take advantage of Slowking's utility and large HP stat, respectively, in this high ladder match.</small>
<small><br />
Substitute Lugia and Rotom-Fan take advantage of Slowking's utility and large HP stat, respectively,
in this high ladder match.
</small>
</a></li>}
</ul>
</section>;
@ -466,7 +493,9 @@ export class PSReplays extends preact.Component {
override render() {
const position = PSRouter.showingLeft() && PSRouter.showingRight() && !PSRouter.stickyRight ?
{ display: 'flex', flexDirection: 'column', justifyContent: 'flex-end' } : {};
return <div class={'bar-wrapper' + (PSRouter.showingLeft() && PSRouter.showingRight() ? ' has-sidebar' : '')} style={position}>
return <div
class={'bar-wrapper' + (PSRouter.showingLeft() && PSRouter.showingRight() ? ' has-sidebar' : '')} style={position}
>
{PSRouter.showingLeft() && <SearchPanel id={PSRouter.leftLoc!} />}
{PSRouter.showingRight() && <BattlePanel id={PSRouter.rightLoc!} />}
<div style={{ clear: 'both' }}></div>

View File

@ -20,7 +20,7 @@ export class HttpError extends Error {
this.body = body;
try {
(Error as any).captureStackTrace(this, HttpError);
} catch (err) {}
} catch {}
}
}
export class NetRequest {
@ -101,7 +101,7 @@ Net.encodeQuery = function (data: string | PostData) {
return urlencodedData;
};
Net.decodeQuery = function (query: string): { [key: string]: string } {
let out = {};
let out: { [key: string]: string } = {};
const questionIndex = query.indexOf('?');
if (questionIndex >= 0) query = query.slice(questionIndex + 1);
for (const queryPart of query.split('&')) {

View File

@ -15,6 +15,7 @@
"types": [],
"include": [
"./play.pokemonshowdown.com/js/lib/preact.d.ts",
"./play.pokemonshowdown.com/src/*"
"./play.pokemonshowdown.com/src/*",
"./replay.pokemonshowdown.com/src/*"
]
}

View File

@ -1,58 +0,0 @@
{
"extends": "tslint:latest",
"defaultSeverity": "error",
"jsRules": {},
"rules": {
"array-type": [true, "array"],
"arrow-parens": [true, "ban-single-arg-parens"],
"curly": [true, "ignore-same-line"],
"indent": [true, "tabs", 2],
"quotemark": false,
"member-access": [true, "no-public"],
"no-empty": [true, "allow-empty-catch", "allow-empty-functions"],
"no-string-literal": false,
"interface-over-type-literal": false,
// TODO: turn on
"prefer-const": false,
"max-line-length": {
"options": {
"limit": 120,
"ignore-pattern": "template\\.replace\\(('\\[|/)|[a-z0-9]=|^\\s*(// \\s*)?((let |const )?[a-zA-Z0-9$.]+ \\+?= (\\$\\()?|(return |throw )?(new )?([a-zA-Z0-9$.]+\\()?)?['\"`/]"
}
},
"interface-name": false,
"forin": false,
// maybe one day
"member-ordering": false,
"max-classes-per-file": false,
// they look weird in `new class {...}`
"new-parens": false,
"no-bitwise": false,
"no-console": false,
"no-namespace": [true, "allow-declarations"],
"only-arrow-functions": false,
"prefer-conditional-expression": false,
"no-shadowed-variable": [true, {"temporalDeadZone": false}],
"no-switch-case-fall-through": true,
"no-unnecessary-initializer": false,
"object-literal-sort-keys": false,
"object-literal-key-quotes": false,
"ordered-imports": false,
"trailing-comma": [
true,
{
"multiline": {
"objects": "always",
"arrays": "always",
"functions": "never",
"typeLiterals": "always"
},
"singleline": "never",
"esSpecCompliant": true
}
],
"semicolon": [true, "always", "strict-bound-class-methods"],
"space-before-function-paren": [true, {"anonymous": "always", "named": "never", "asyncArrow": "always"}]
},
"rulesDirectory": []
}