mirror of
https://github.com/smogon/pokemon-showdown-client.git
synced 2026-03-21 09:35:44 -05:00
Update to ESLint 9 (#2326)
Some checks are pending
Node.js CI / build (22.x) (push) Waiting to run
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:
parent
3d4de88706
commit
a10821ab8b
|
|
@ -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
|
||||
210
.eslintrc.js
210
.eslintrc.js
|
|
@ -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"]
|
||||
*/
|
||||
}
|
||||
};
|
||||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
|
|
@ -16,7 +16,7 @@ jobs:
|
|||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x]
|
||||
node-version: [22.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
|
|
|||
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"],
|
||||
}),
|
||||
};
|
||||
|
|
@ -15,8 +15,8 @@ if (!fs.existsSync('caches/pokemon-showdown')) {
|
|||
}
|
||||
|
||||
process.stdout.write("Syncing data from Git repository... ");
|
||||
child_process.execSync('git pull', {cwd: 'caches/pokemon-showdown'});
|
||||
child_process.execSync('npm run build', {cwd: 'caches/pokemon-showdown'});
|
||||
child_process.execSync('git pull', { cwd: 'caches/pokemon-showdown' });
|
||||
child_process.execSync('npm run build', { cwd: 'caches/pokemon-showdown' });
|
||||
console.log("DONE");
|
||||
|
||||
const Dex = require('../caches/pokemon-showdown/dist/sim/dex').Dex;
|
||||
|
|
@ -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';
|
||||
|
|
@ -442,7 +447,7 @@ process.stdout.write("Building `data/teambuilder-tables.js`... ");
|
|||
if (species.isNonstandard && ['LGPE', 'CAP', 'Future'].includes(species.isNonstandard)) return 'Illegal';
|
||||
return species.tags.includes('Mythical') ? 'Mythical' :
|
||||
species.tags.includes('Restricted Legendary') ? 'Restricted Legendary' :
|
||||
species.nfe ? (species.prevo ? 'NFE' : 'LC') : 'Regular';
|
||||
species.nfe ? (species.prevo ? 'NFE' : 'LC') : 'Regular';
|
||||
}
|
||||
if (species.tier === 'CAP' || species.tier === 'CAP NFE' || species.tier === 'CAP LC') {
|
||||
return species.tier;
|
||||
|
|
@ -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'];
|
||||
|
||||
|
|
|
|||
|
|
@ -53,11 +53,11 @@ function updateLearnsets(callback) {
|
|||
);
|
||||
}
|
||||
|
||||
newLearnsetsG6[speciesid] = {learnset: newLearnset};
|
||||
newLearnsetsG6[speciesid] = { learnset: newLearnset };
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,207 +18,204 @@ const sourceMap = require('source-map');
|
|||
const VERBOSE = false;
|
||||
|
||||
function outputFileSync(filePath, res, opts) {
|
||||
fs.mkdirSync(path.dirname(filePath), {recursive: true});
|
||||
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
||||
|
||||
// we've requested explicit sourcemaps to be written to disk
|
||||
if (
|
||||
res.map &&
|
||||
opts.sourceMaps &&
|
||||
opts.sourceMaps !== "inline"
|
||||
) {
|
||||
const mapLoc = filePath + ".map";
|
||||
res.code += "\n//# sourceMappingURL=" + path.basename(mapLoc);
|
||||
res.map.file = path.basename(filePath);
|
||||
fs.writeFileSync(mapLoc, JSON.stringify(res.map));
|
||||
}
|
||||
|
||||
fs.writeFileSync(filePath, res.code);
|
||||
}
|
||||
|
||||
function slash(path) {
|
||||
const isExtendedLengthPath = /^\\\\\?\\/.test(path);
|
||||
const hasNonAscii = /[^\u0000-\u0080]+/.test(path);
|
||||
|
||||
if (isExtendedLengthPath || hasNonAscii) {
|
||||
return path;
|
||||
// we've requested explicit sourcemaps to be written to disk
|
||||
if (
|
||||
res.map &&
|
||||
opts.sourceMaps &&
|
||||
opts.sourceMaps !== "inline"
|
||||
) {
|
||||
const mapLoc = filePath + ".map";
|
||||
res.code += "\n//# sourceMappingURL=" + path.basename(mapLoc);
|
||||
res.map.file = path.basename(filePath);
|
||||
fs.writeFileSync(mapLoc, JSON.stringify(res.map));
|
||||
}
|
||||
|
||||
return path.replace(/\\/g, '/');
|
||||
fs.writeFileSync(filePath, res.code);
|
||||
}
|
||||
|
||||
function slash(filePath) {
|
||||
const isExtendedLengthPath = /^\\\\\?\\/.test(filePath);
|
||||
// eslint-disable-next-line no-control-regex
|
||||
const hasNonAscii = /[^\u0000-\u0080]+/.test(filePath);
|
||||
|
||||
if (isExtendedLengthPath || hasNonAscii) {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
return filePath.replace(/\\/g, '/');
|
||||
}
|
||||
|
||||
async function combineResults(fileResults, sourceMapOptions, opts) {
|
||||
let map = null;
|
||||
if (fileResults.some(result => result?.map)) {
|
||||
map = new sourceMap.SourceMapGenerator(sourceMapOptions);
|
||||
}
|
||||
let map = null;
|
||||
if (fileResults.some(result => result?.map)) {
|
||||
map = new sourceMap.SourceMapGenerator(sourceMapOptions);
|
||||
}
|
||||
|
||||
let code = "";
|
||||
let offset = 0;
|
||||
let code = "";
|
||||
let offset = 0;
|
||||
|
||||
for (const result of fileResults) {
|
||||
if (!result) continue;
|
||||
for (const result of fileResults) {
|
||||
if (!result) continue;
|
||||
|
||||
code += result.code + "\n";
|
||||
code += result.code + "\n";
|
||||
|
||||
if (result.map) {
|
||||
const consumer = await new sourceMap.SourceMapConsumer(result.map);
|
||||
const sources = new Set();
|
||||
if (result.map) {
|
||||
const consumer = await new sourceMap.SourceMapConsumer(result.map);
|
||||
const sources = new Set();
|
||||
|
||||
consumer.eachMapping(function (mapping) {
|
||||
if (mapping.source != null) sources.add(mapping.source);
|
||||
consumer.eachMapping(mapping => {
|
||||
if (mapping.source != null) sources.add(mapping.source);
|
||||
|
||||
map.addMapping({
|
||||
generated: {
|
||||
line: mapping.generatedLine + offset,
|
||||
column: mapping.generatedColumn,
|
||||
},
|
||||
source: mapping.source,
|
||||
original:
|
||||
mapping.source == null
|
||||
? null
|
||||
: {
|
||||
line: mapping.originalLine,
|
||||
column: mapping.originalColumn,
|
||||
},
|
||||
});
|
||||
});
|
||||
map.addMapping({
|
||||
generated: {
|
||||
line: mapping.generatedLine + offset,
|
||||
column: mapping.generatedColumn,
|
||||
},
|
||||
source: mapping.source,
|
||||
original:
|
||||
mapping.source == null ?
|
||||
null : {
|
||||
line: mapping.originalLine,
|
||||
column: mapping.originalColumn,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
for (const source of sources) {
|
||||
const content = consumer.sourceContentFor(source, true);
|
||||
if (content !== null) {
|
||||
map.setSourceContent(source, content);
|
||||
}
|
||||
}
|
||||
for (const source of sources) {
|
||||
const content = consumer.sourceContentFor(source, true);
|
||||
if (content !== null) {
|
||||
map.setSourceContent(source, content);
|
||||
}
|
||||
}
|
||||
|
||||
offset = code.split("\n").length - 1;
|
||||
}
|
||||
}
|
||||
offset = code.split("\n").length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.sourceMaps === "inline") {
|
||||
const json = JSON.stringify(map);
|
||||
const base64 = Buffer.from(json, 'utf8').toString('base64');
|
||||
code += "\n//# sourceMappingURL=data:application/json;charset=utf-8;base64," + base64;
|
||||
}
|
||||
if (opts.sourceMaps === "inline") {
|
||||
const json = JSON.stringify(map);
|
||||
const base64 = Buffer.from(json, 'utf8').toString('base64');
|
||||
code += "\n//# sourceMappingURL=data:application/json;charset=utf-8;base64," + base64;
|
||||
}
|
||||
|
||||
return {
|
||||
map: map,
|
||||
code: code,
|
||||
};
|
||||
return { map, code };
|
||||
}
|
||||
|
||||
function noRebuildNeeded(src, dest) {
|
||||
try {
|
||||
const srcStat = fs.statSync(src, {throwIfNoEntry: false});
|
||||
if (!srcStat) return true;
|
||||
const destStat = fs.statSync(dest);
|
||||
if (srcStat.ctimeMs < destStat.ctimeMs) return true;
|
||||
} catch (e) {}
|
||||
try {
|
||||
const srcStat = fs.statSync(src, { throwIfNoEntry: false });
|
||||
if (!srcStat) return true;
|
||||
const destStat = fs.statSync(dest);
|
||||
if (srcStat.ctimeMs < destStat.ctimeMs) return true;
|
||||
} catch {}
|
||||
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
function compileToDir(srcDir, destDir, opts = {}) {
|
||||
const incremental = opts.incremental;
|
||||
delete opts.incremental;
|
||||
const incremental = opts.incremental;
|
||||
delete opts.incremental;
|
||||
|
||||
function handleFile(src, base) {
|
||||
let relative = path.relative(base, src);
|
||||
function handleFile(src, base) {
|
||||
let relative = path.relative(base, src);
|
||||
|
||||
if (!relative.endsWith('.ts') && !relative.endsWith('.tsx')) {
|
||||
return 0;
|
||||
}
|
||||
if (relative.endsWith('.d.ts')) return 0;
|
||||
if (!relative.endsWith('.ts') && !relative.endsWith('.tsx')) {
|
||||
return 0;
|
||||
}
|
||||
if (relative.endsWith('.d.ts')) return 0;
|
||||
|
||||
relative = relative.slice(0, relative.endsWith('.tsx') ? -4 : -3) + '.js';
|
||||
relative = relative.slice(0, relative.endsWith('.tsx') ? -4 : -3) + '.js';
|
||||
|
||||
const dest = path.join(destDir, relative);
|
||||
const dest = path.join(destDir, relative);
|
||||
|
||||
if (incremental && noRebuildNeeded(src, dest)) return 0;
|
||||
if (incremental && noRebuildNeeded(src, dest)) return 0;
|
||||
|
||||
const res = babel.transformFileSync(src, {
|
||||
...opts,
|
||||
sourceFileName: slash(path.relative(dest + "/..", src)),
|
||||
});
|
||||
const res = babel.transformFileSync(src, {
|
||||
...opts,
|
||||
sourceFileName: slash(path.relative(dest + "/..", src)),
|
||||
});
|
||||
|
||||
if (!res) return 0;
|
||||
if (!res) return 0;
|
||||
|
||||
outputFileSync(dest, res, opts);
|
||||
fs.chmodSync(dest, fs.statSync(src).mode);
|
||||
outputFileSync(dest, res, opts);
|
||||
fs.chmodSync(dest, fs.statSync(src).mode);
|
||||
|
||||
if (VERBOSE) {
|
||||
console.log(src + " -> " + dest);
|
||||
}
|
||||
if (VERBOSE) {
|
||||
console.log(src + " -> " + dest);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
function handle(src, base) {
|
||||
const stat = fs.statSync(src, {throwIfNoEntry: false});
|
||||
function handle(src, base) {
|
||||
const stat = fs.statSync(src, { throwIfNoEntry: false });
|
||||
|
||||
if (!stat) return 0;
|
||||
if (!stat) return 0;
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
if (!base) base = src;
|
||||
if (stat.isDirectory()) {
|
||||
if (!base) base = src;
|
||||
|
||||
let count = 0;
|
||||
let count = 0;
|
||||
|
||||
const files = fs.readdirSync(src);
|
||||
for (const filename of files) {
|
||||
if (filename.startsWith('.')) continue;
|
||||
const files = fs.readdirSync(src);
|
||||
for (const filename of files) {
|
||||
if (filename.startsWith('.')) continue;
|
||||
|
||||
const srcFile = path.join(src, filename);
|
||||
const srcFile = path.join(src, filename);
|
||||
|
||||
count += handle(srcFile, base);
|
||||
}
|
||||
count += handle(srcFile, base);
|
||||
}
|
||||
|
||||
return count;
|
||||
} else {
|
||||
if (!base) base = path.dirname(src);
|
||||
return handleFile(src, base);
|
||||
}
|
||||
}
|
||||
return count;
|
||||
} else {
|
||||
if (!base) base = path.dirname(src);
|
||||
return handleFile(src, base);
|
||||
}
|
||||
}
|
||||
|
||||
let total = 0;
|
||||
fs.mkdirSync(destDir, {recursive: true});
|
||||
const srcDirs = typeof srcDir === 'string' ? [srcDir] : srcDir;
|
||||
for (const dir of srcDirs) total += handle(dir);
|
||||
if (incremental) opts.incremental = true; // incredibly dumb hack to preserve the option
|
||||
return total;
|
||||
let total = 0;
|
||||
fs.mkdirSync(destDir, { recursive: true });
|
||||
const srcDirs = typeof srcDir === 'string' ? [srcDir] : srcDir;
|
||||
for (const dir of srcDirs) total += handle(dir);
|
||||
if (incremental) opts.incremental = true; // incredibly dumb hack to preserve the option
|
||||
return total;
|
||||
}
|
||||
|
||||
function compileToFile(srcFile, destFile, opts) {
|
||||
const incremental = opts.incremental;
|
||||
delete opts.incremental;
|
||||
const incremental = opts.incremental;
|
||||
delete opts.incremental;
|
||||
|
||||
const srcFiles = typeof srcFile === 'string' ? [srcFile] : srcFile;
|
||||
const srcFiles = typeof srcFile === 'string' ? [srcFile] : srcFile;
|
||||
|
||||
if (incremental && srcFiles.every(src => noRebuildNeeded(src, destFile))) {
|
||||
opts.incremental = true; // incredibly dumb hack to preserve the option
|
||||
return 0;
|
||||
}
|
||||
if (incremental && srcFiles.every(src => noRebuildNeeded(src, destFile))) {
|
||||
opts.incremental = true; // incredibly dumb hack to preserve the option
|
||||
return 0;
|
||||
}
|
||||
|
||||
const results = [];
|
||||
const results = [];
|
||||
|
||||
for (const src of srcFiles) {
|
||||
if (!fs.existsSync(src)) continue;
|
||||
for (const src of srcFiles) {
|
||||
if (!fs.existsSync(src)) continue;
|
||||
|
||||
const res = babel.transformFileSync(src, opts);
|
||||
const res = babel.transformFileSync(src, opts);
|
||||
|
||||
if (res) results.push(res);
|
||||
if (res) results.push(res);
|
||||
|
||||
if (VERBOSE) console.log(src + " ->");
|
||||
}
|
||||
if (VERBOSE) console.log(src + " ->");
|
||||
}
|
||||
|
||||
combineResults(results, {
|
||||
file: path.basename(destFile),
|
||||
sourceRoot: opts.sourceRoot,
|
||||
}, opts).then(combined => {
|
||||
outputFileSync(destFile, combined, opts);
|
||||
});
|
||||
combineResults(results, {
|
||||
file: path.basename(destFile),
|
||||
sourceRoot: opts.sourceRoot,
|
||||
}, opts).then(combined => {
|
||||
outputFileSync(destFile, combined, opts);
|
||||
});
|
||||
|
||||
if (VERBOSE) console.log("-> " + destFile);
|
||||
if (incremental) opts.incremental = true; // incredibly dumb hack to preserve the option
|
||||
return results.length;
|
||||
if (VERBOSE) console.log("-> " + destFile);
|
||||
if (incremental) opts.incremental = true; // incredibly dumb hack to preserve the option
|
||||
return results.length;
|
||||
}
|
||||
|
||||
exports.compileToDir = compileToDir;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
435
eslint-ps-standard.mjs
Normal 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
134
eslint.config.mjs
Normal 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,
|
||||
}],
|
||||
},
|
||||
},
|
||||
]);
|
||||
5172
package-lock.json
generated
5172
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
14
package.json
14
package.json
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
this.battle.subscribe(function () { self.updateControls(); });
|
||||
|
||||
this.users = {};
|
||||
this.userCount = {users: 0};
|
||||
this.userCount = { users: 0 };
|
||||
this.$userList = this.$('.userlist');
|
||||
this.userList = new UserList({
|
||||
el: this.$userList,
|
||||
|
|
@ -76,7 +76,7 @@
|
|||
},
|
||||
requestLeave: function (e) {
|
||||
if ((this.side || this.requireForfeit) && this.battle && !this.battleEnded && !this.expired && !this.battle.forfeitPending) {
|
||||
app.addPopup(ForfeitPopup, {room: this, sourceEl: e && e.currentTarget, gameType: 'battle'});
|
||||
app.addPopup(ForfeitPopup, { room: this, sourceEl: e && e.currentTarget, gameType: 'battle' });
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
|
@ -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);
|
||||
|
|
@ -410,8 +411,8 @@
|
|||
};
|
||||
|
||||
if (this.request.forceSwitch !== true) {
|
||||
var faintedLength = _.filter(this.request.forceSwitch, function (fainted) {return fainted;}).length;
|
||||
var freedomDegrees = faintedLength - _.filter(switchables.slice(this.battle.pokemonControlled), function (mon) {return !mon.fainted;}).length;
|
||||
var faintedLength = _.filter(this.request.forceSwitch, function (fainted) { return fainted; }).length;
|
||||
var freedomDegrees = faintedLength - _.filter(switchables.slice(this.battle.pokemonControlled), function (mon) { return !mon.fainted; }).length;
|
||||
this.choice.freedomDegrees = Math.max(freedomDegrees, 0);
|
||||
this.choice.canSwitch = faintedLength - this.choice.freedomDegrees;
|
||||
}
|
||||
|
|
@ -542,7 +543,7 @@
|
|||
this.$('.timerbutton').replaceWith(this.getTimerHTML());
|
||||
},
|
||||
openTimer: function () {
|
||||
app.addPopup(TimerPopup, {room: this});
|
||||
app.addPopup(TimerPopup, { room: this });
|
||||
},
|
||||
updateMoveControls: function (type) {
|
||||
var switchables = this.request && this.request.side ? this.battle.myPokemon : [];
|
||||
|
|
@ -577,7 +578,7 @@
|
|||
var canTerastallize = curActive.canTerastallize || switchables[pos].canTerastallize;
|
||||
if (canZMove && typeof canZMove[0] === 'string') {
|
||||
canZMove = _.map(canZMove, function (move) {
|
||||
return {move: move, target: Dex.moves.get(move).target};
|
||||
return { move: move, target: Dex.moves.get(move).target };
|
||||
});
|
||||
}
|
||||
if (gigantamax) gigantamax = Dex.moves.get(gigantamax);
|
||||
|
|
@ -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 = '';
|
||||
|
|
@ -1106,7 +1107,7 @@
|
|||
request.requestType = 'wait';
|
||||
}
|
||||
|
||||
this.choice = choiceText ? {waiting: true} : null;
|
||||
this.choice = choiceText ? { waiting: true } : null;
|
||||
this.finalDecision = this.finalDecisionMove = this.finalDecisionSwitch = false;
|
||||
this.request = request;
|
||||
if (request.side) {
|
||||
|
|
@ -1184,7 +1185,7 @@
|
|||
this.send('/savereplay');
|
||||
},
|
||||
openBattleOptions: function () {
|
||||
app.addPopup(BattleOptionsPopup, {battle: this.battle, room: this});
|
||||
app.addPopup(BattleOptionsPopup, { battle: this.battle, room: this });
|
||||
},
|
||||
clickReplayDownloadButton: function (e) {
|
||||
var filename = (this.battle.tier || 'Battle').replace(/[^A-Za-z0-9]/g, '');
|
||||
|
|
@ -1277,7 +1278,7 @@
|
|||
var isTerastal = !!(this.$('input[name=terastallize]')[0] || '').checked;
|
||||
|
||||
var target = e.getAttribute('data-target');
|
||||
var choosableTargets = {normal: 1, any: 1, adjacentAlly: 1, adjacentAllyOrSelf: 1, adjacentFoe: 1};
|
||||
var choosableTargets = { normal: 1, any: 1, adjacentAlly: 1, adjacentAllyOrSelf: 1, adjacentFoe: 1 };
|
||||
if (this.battle.gameType === 'freeforall') delete choosableTargets['adjacentAllyOrSelf'];
|
||||
|
||||
this.choice.choices.push('move ' + pos + (isMega ? ' mega' : '') + (isMegaX ? ' megax' : isMegaY ? ' megay' : '') + (isZMove ? ' zmove' : '') + (isUltraBurst ? ' ultra' : '') + (isDynamax ? ' dynamax' : '') + (isTerastal ? ' terastallize' : ''));
|
||||
|
|
@ -1328,8 +1329,8 @@
|
|||
}
|
||||
|
||||
// After choosing the position to which a pokemon will switch in (Doubles/Triples end-game).
|
||||
if (!this.request || this.request.requestType !== 'switch') return false; //??
|
||||
if (this.choice.canSwitch > _.filter(this.choice.choices, function (choice) {return choice;}).length) {
|
||||
if (!this.request || this.request.requestType !== 'switch') return false; // ??
|
||||
if (this.choice.canSwitch > _.filter(this.choice.choices, function (choice) { return choice; }).length) {
|
||||
// More switches are pending.
|
||||
this.choice.type = 'switch2';
|
||||
this.updateControlsForPlayer();
|
||||
|
|
@ -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');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -88,30 +88,30 @@
|
|||
|
||||
$wrapper.html(
|
||||
'<div class="tournament-title">' +
|
||||
'<span class="tournament-format"></span> <span class="tournament-generator"></span> Tournament' +
|
||||
'<div class="tournament-status"></div>' +
|
||||
'<div class="tournament-toggle">Toggle</div>' +
|
||||
' <span class="tournament-format"></span> <span class="tournament-generator"></span> Tournament' +
|
||||
' <div class="tournament-status"></div>' +
|
||||
' <div class="tournament-toggle">Toggle</div>' +
|
||||
'</div>' +
|
||||
'<div class="tournament-box">' +
|
||||
'<div class="tournament-bracket"></div>' +
|
||||
'<div class="tournament-tools">' +
|
||||
'<div class="tournament-team"></div>' +
|
||||
'<button class="button tournament-join">Join</button><button class="button tournament-validate"><i class="fa fa-check"></i> Validate</button> <button class="button tournament-leave">Leave</button>' +
|
||||
'<div class="tournament-nomatches">Waiting for battles to become available...</div>' +
|
||||
'<div class="tournament-challenge">' +
|
||||
'<div class="tournament-challenge-user"></div>' +
|
||||
'<button class="button tournament-challenge-challenge"><strong>Ready!</strong></button><span class="tournament-challenge-user-menu"></span>' +
|
||||
'</div>' +
|
||||
'<div class="tournament-challengeby"></div>' +
|
||||
'<div class="tournament-challenging">' +
|
||||
'<div class="tournament-challenging-message"></div>' +
|
||||
'<button class="button tournament-challenge-cancel">Cancel</button>' +
|
||||
'</div>' +
|
||||
'<div class="tournament-challenged">' +
|
||||
'<div class="tournament-challenged-message"></div>' +
|
||||
'<button class="button tournament-challenge-accept"><strong>Ready!</strong></button>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
' <div class="tournament-bracket"></div>' +
|
||||
' <div class="tournament-tools">' +
|
||||
' <div class="tournament-team"></div>' +
|
||||
' <button class="button tournament-join">Join</button><button class="button tournament-validate"><i class="fa fa-check"></i> Validate</button> <button class="button tournament-leave">Leave</button>' +
|
||||
' <div class="tournament-nomatches">Waiting for battles to become available...</div>' +
|
||||
' <div class="tournament-challenge">' +
|
||||
' <div class="tournament-challenge-user"></div>' +
|
||||
' <button class="button tournament-challenge-challenge"><strong>Ready!</strong></button><span class="tournament-challenge-user-menu"></span>' +
|
||||
' </div>' +
|
||||
' <div class="tournament-challengeby"></div>' +
|
||||
' <div class="tournament-challenging">' +
|
||||
' <div class="tournament-challenging-message"></div>' +
|
||||
' <button class="button tournament-challenge-cancel">Cancel</button>' +
|
||||
' </div>' +
|
||||
' <div class="tournament-challenged">' +
|
||||
' <div class="tournament-challenged-message"></div>' +
|
||||
' <button class="button tournament-challenge-accept"><strong>Ready!</strong></button>' +
|
||||
' </div>' +
|
||||
' </div>' +
|
||||
'</div>');
|
||||
|
||||
this.$title = $wrapper.find('.tournament-title');
|
||||
|
|
@ -609,7 +609,7 @@
|
|||
};
|
||||
|
||||
var nodesByDepth = [];
|
||||
var stack = [{node: data.rootNode, depth: 0}];
|
||||
var stack = [{ node: data.rootNode, depth: 0 }];
|
||||
while (stack.length > 0) {
|
||||
var frame = stack.pop();
|
||||
|
||||
|
|
@ -619,7 +619,7 @@
|
|||
|
||||
if (!frame.node.children) frame.node.children = [];
|
||||
frame.node.children.forEach(function (child) {
|
||||
stack.push({node: child, depth: frame.depth + 1});
|
||||
stack.push({ node: child, depth: frame.depth + 1 });
|
||||
});
|
||||
}
|
||||
var maxDepth = nodesByDepth.length;
|
||||
|
|
@ -653,10 +653,10 @@
|
|||
|
||||
var link = d3.svg.diagonal()
|
||||
.source(function (link) {
|
||||
return {x: link.source.x, y: link.source.y + nodeSize.realWidth / 2};
|
||||
return { x: link.source.x, y: link.source.y + nodeSize.realWidth / 2 };
|
||||
})
|
||||
.target(function (link) {
|
||||
return {x: link.target.x, y: link.target.y - nodeSize.realWidth / 2};
|
||||
return { x: link.target.x, y: link.target.y - nodeSize.realWidth / 2 };
|
||||
})
|
||||
.projection(function (link) {
|
||||
return [size.width - link.y, link.x];
|
||||
|
|
@ -793,20 +793,20 @@
|
|||
};
|
||||
TournamentBox.prototype.showBracketPopup = function (data, isStandalone) {
|
||||
if (isStandalone)
|
||||
app.addPopup(BracketPopup, {$bracket: this.generateBracket(data)});
|
||||
app.addPopup(BracketPopup, { $bracket: this.generateBracket(data) });
|
||||
else
|
||||
this.bracketPopup = app.addPopup(BracketPopup, {parent: this, $bracket: this.generateBracket(data)});
|
||||
this.bracketPopup = app.addPopup(BracketPopup, { parent: this, $bracket: this.generateBracket(data) });
|
||||
};
|
||||
|
||||
TournamentBox.prototype.renderChallengeUsers = function () {
|
||||
return ' <button class="button" value="' + toID(this.info.challenges[0]) + '" name="tournamentButton" data-type="challengeUser">Change opponent</button>';
|
||||
};
|
||||
TournamentBox.prototype.challengeUser = function (user, button) {
|
||||
app.addPopup(UserPopup, {user: user, users: this.info.challenges, sourceEl: button});
|
||||
app.addPopup(UserPopup, { user: user, users: this.info.challenges, sourceEl: button });
|
||||
};
|
||||
|
||||
TournamentBox.prototype.teamSelect = function (team, button) {
|
||||
app.addPopup(TeamPopup, {team: team, format: this.info.teambuilderFormat, sourceEl: button, room: this.room.id, isMoreTeams: false, folderToggleOn: true, folderNotExpanded: []});
|
||||
app.addPopup(TeamPopup, { team: team, format: this.info.teambuilderFormat, sourceEl: button, room: this.room.id, isMoreTeams: false, folderToggleOn: true, folderNotExpanded: [] });
|
||||
};
|
||||
|
||||
return TournamentBox;
|
||||
|
|
|
|||
|
|
@ -215,7 +215,7 @@
|
|||
var name = $(e.currentTarget).data('name') || $(e.currentTarget).text();
|
||||
var away = $(e.currentTarget).data('away') || false;
|
||||
var status = $(e.currentTarget).data('status');
|
||||
app.addPopup(UserPopup, {roomGroup: roomGroup, name: name, away: away, status: status, sourceEl: e.currentTarget, position: position});
|
||||
app.addPopup(UserPopup, { roomGroup: roomGroup, name: name, away: away, status: status, sourceEl: e.currentTarget, position: position });
|
||||
},
|
||||
openPM: function (e) {
|
||||
e.preventDefault();
|
||||
|
|
@ -236,10 +236,10 @@
|
|||
app.addPopup(AvatarsPopup);
|
||||
},
|
||||
openSounds: function () {
|
||||
app.addPopup(SoundsPopup, {type: 'semimodal'});
|
||||
app.addPopup(SoundsPopup, { type: 'semimodal' });
|
||||
},
|
||||
openOptions: function () {
|
||||
app.addPopup(OptionsPopup, {type: 'semimodal'});
|
||||
app.addPopup(OptionsPopup, { type: 'semimodal' });
|
||||
},
|
||||
|
||||
// highlight
|
||||
|
|
@ -247,7 +247,7 @@
|
|||
getHighlight: function (message) {
|
||||
var highlights = Dex.prefs('highlights') || {};
|
||||
if (Array.isArray(highlights)) {
|
||||
highlights = {global: highlights};
|
||||
highlights = { global: highlights };
|
||||
// Migrate from the old highlight system
|
||||
Storage.prefs('highlights', highlights);
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -502,7 +502,7 @@
|
|||
var self = this;
|
||||
var challenge = function (targets) {
|
||||
target = toID(targets[0]);
|
||||
self.challengeData = {userid: target, format: targets.length > 1 ? targets.slice(1).join(',') : '', team: ''};
|
||||
self.challengeData = { userid: target, format: targets.length > 1 ? targets.slice(1).join(',') : '', team: '' };
|
||||
app.on('response:userdetails', self.challengeUserdetails, self);
|
||||
app.send('/cmd userdetails ' + target);
|
||||
};
|
||||
|
|
@ -575,7 +575,7 @@
|
|||
case 'open':
|
||||
if (this.checkBroadcast(cmd, text)) return false;
|
||||
var openUser = function (target) {
|
||||
app.addPopup(UserPopup, {name: target});
|
||||
app.addPopup(UserPopup, { name: target });
|
||||
};
|
||||
target = toName(target);
|
||||
if (!target) {
|
||||
|
|
@ -710,7 +710,7 @@
|
|||
if (!userid) {
|
||||
var newsId = $(this).data('newsid');
|
||||
if (newsId) {
|
||||
$.cookie('showdown_readnews', '' + newsId, {expires: 365});
|
||||
$.cookie('showdown_readnews', '' + newsId, { expires: 365 });
|
||||
}
|
||||
$(this).remove();
|
||||
return;
|
||||
|
|
@ -772,7 +772,7 @@
|
|||
}
|
||||
this.add('Join/leave messages on room ' + room + ': ALWAYS ON');
|
||||
} else {
|
||||
serverShowjoins = {global: 1};
|
||||
serverShowjoins = { global: 1 };
|
||||
this.add('Join/leave messages: ALWAYS ON');
|
||||
}
|
||||
showjoins[Config.server.id] = serverShowjoins;
|
||||
|
|
@ -791,7 +791,7 @@
|
|||
}
|
||||
this.add('Join/leave messages on room ' + room + ': AUTOMATIC');
|
||||
} else {
|
||||
serverShowjoins = {global: 0};
|
||||
serverShowjoins = { global: 0 };
|
||||
this.add('Join/leave messages: AUTOMATIC');
|
||||
}
|
||||
showjoins[Config.server.id] = serverShowjoins;
|
||||
|
|
@ -1373,10 +1373,10 @@
|
|||
},
|
||||
requestLeave: function (e) {
|
||||
if (app.rooms[''].games && app.rooms[''].games[this.id]) {
|
||||
app.addPopup(ForfeitPopup, {room: this, sourceEl: e && e.currentTarget, gameType: (this.id.substring(0, 5) === 'help-' ? 'help' : 'game')});
|
||||
app.addPopup(ForfeitPopup, { room: this, sourceEl: e && e.currentTarget, gameType: (this.id.substring(0, 5) === 'help-' ? 'help' : 'game') });
|
||||
return false;
|
||||
} else if (Dex.prefs('leavePopupRoom')) {
|
||||
app.addPopup(ForfeitPopup, {room: this, sourceEl: e && e.currentTarget, gameType: 'room'});
|
||||
app.addPopup(ForfeitPopup, { room: this, sourceEl: e && e.currentTarget, gameType: 'room' });
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
|
@ -1385,7 +1385,7 @@
|
|||
this.add(data);
|
||||
},
|
||||
getUserGroup: function (userid) {
|
||||
return (app.rooms[this.id].users[userid] || {group: ' '}).group;
|
||||
return (app.rooms[this.id].users[userid] || { group: ' ' }).group;
|
||||
},
|
||||
add: function (log) {
|
||||
if (typeof log === 'string') log = log.split('\n');
|
||||
|
|
@ -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);
|
||||
},
|
||||
|
|
@ -1960,7 +1959,7 @@
|
|||
text += '<button class="userbutton username" data-roomgroup="' + BattleLog.escapeHTML(user.group) + '" data-name="' + BattleLog.escapeHTML(user.name) + '"';
|
||||
text += (user.away ? ' data-away=true' : '') + (user.status ? ' data-status="' + BattleLog.escapeHTML(user.status) + '"' : '') + '>';
|
||||
var group = user.group;
|
||||
var details = Config.groups[group] || {type: 'user'};
|
||||
var details = Config.groups[group] || { type: 'user' };
|
||||
var color = user.away ? 'color:#888;' : BattleLog.hashColor(userid);
|
||||
text += '<em class="group' + (details.group === 2 ? ' staffgroup' : '') + '">' + BattleLog.escapeHTML(group) + '</em>';
|
||||
if (details.type === 'leadership') {
|
||||
|
|
@ -1992,16 +1991,16 @@
|
|||
comparator: function (a, b) {
|
||||
if (a === b) return 0;
|
||||
|
||||
var aUser = this.room.users[a] || {group: Config.defaultGroup, away: false};
|
||||
var bUser = this.room.users[b] || {group: Config.defaultGroup, away: false};
|
||||
var aUser = this.room.users[a] || { group: Config.defaultGroup, away: false };
|
||||
var bUser = this.room.users[b] || { group: Config.defaultGroup, away: false };
|
||||
|
||||
var aRank = (
|
||||
Config.groups[aUser.group || ' '] ||
|
||||
{order: (Config.defaultOrder || 10006.5)}
|
||||
{ order: (Config.defaultOrder || 10006.5) }
|
||||
).order;
|
||||
var bRank = (
|
||||
Config.groups[bUser.group || ' '] ||
|
||||
{order: (Config.defaultOrder || 10006.5)}
|
||||
{ order: (Config.defaultOrder || 10006.5) }
|
||||
).order;
|
||||
|
||||
if (aRank !== bRank) return aRank - bRank;
|
||||
|
|
|
|||
|
|
@ -92,8 +92,8 @@
|
|||
clickUsername: function (e) {
|
||||
e.stopPropagation();
|
||||
var name = $(e.currentTarget).data('name') || $(e.currentTarget).text();
|
||||
app.addPopup(UserPopup, {name: name, sourceEl: e.currentTarget});
|
||||
},
|
||||
app.addPopup(UserPopup, { name: name, sourceEl: e.currentTarget });
|
||||
}
|
||||
});
|
||||
|
||||
this.LadderRoom = HTMLRoom.extend({
|
||||
|
|
@ -124,7 +124,7 @@
|
|||
update: function () {
|
||||
if (!this.curFormat) {
|
||||
var buf = '<div class="ladder pad"><p>See a user\'s ranking with <a class="button" href="//' + Config.routes.users + '/" target="_blank">User lookup</a></p>' +
|
||||
//'<p><strong style="color:red">I\'m really really sorry, but as a warning: we\'re going to reset the ladder again soon to fix some more ladder bugs.</strong></p>' +
|
||||
// '<p><strong style="color:red">I\'m really really sorry, but as a warning: we\'re going to reset the ladder again soon to fix some more ladder bugs.</strong></p>' +
|
||||
'<p>(btw if you couldn\'t tell the ladder screens aren\'t done yet; they\'ll look nicer than this once I\'m done.)</p>' +
|
||||
'<p><button name="selectFormat" value="help" class="button"><i class="fa fa-info-circle"></i> How the ladder works</button></p><ul>';
|
||||
if (!window.BattleFormats) {
|
||||
|
|
@ -196,7 +196,7 @@
|
|||
this.update();
|
||||
}
|
||||
}, {
|
||||
COIL_B: {},
|
||||
COIL_B: {}
|
||||
});
|
||||
|
||||
}).call(this, jQuery);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
'click .closebutton': 'closePM',
|
||||
'click .minimizebutton': 'minimizePM',
|
||||
'click .pm-challenge': 'clickPMButtonBarChallenge',
|
||||
'click .pm-userOptions':'clickPMButtonBarUserOptions',
|
||||
'click .pm-userOptions': 'clickPMButtonBarUserOptions',
|
||||
'click .pm-window': 'clickPMBackground',
|
||||
'dblclick .pm-window h3': 'dblClickPMHeader',
|
||||
'focus textarea': 'onFocusPM',
|
||||
|
|
@ -347,7 +347,7 @@
|
|||
$pmWindow = $(e.currentTarget).closest('.pm-window');
|
||||
var newsId = $pmWindow.data('newsid');
|
||||
if (newsId) {
|
||||
$.cookie('showdown_readnews', '' + newsId, {expires: 365});
|
||||
$.cookie('showdown_readnews', '' + newsId, { expires: 365 });
|
||||
}
|
||||
$pmWindow.remove();
|
||||
return;
|
||||
|
|
@ -414,7 +414,7 @@
|
|||
clickUsername: function (e) {
|
||||
e.stopPropagation();
|
||||
var name = $(e.currentTarget).data('name') || $(e.currentTarget).text();
|
||||
app.addPopup(UserPopup, {name: name, sourceEl: e.currentTarget});
|
||||
app.addPopup(UserPopup, { name: name, sourceEl: e.currentTarget });
|
||||
},
|
||||
clickPMButtonBarChallenge: function (e) {
|
||||
var name = $(e.currentTarget).closest('.pm-window').data('name');
|
||||
|
|
@ -426,7 +426,7 @@
|
|||
e.stopPropagation();
|
||||
var name = $(e.currentTarget).closest('.pm-window').data('name');
|
||||
var userid = toID($(e.currentTarget).closest('.pm-window').data('name'));
|
||||
app.addPopup(UserOptions, {name: name, userid: userid, sourceEl: e.currentTarget});
|
||||
app.addPopup(UserOptions, { name: name, userid: userid, sourceEl: e.currentTarget });
|
||||
},
|
||||
focusPM: function (name) {
|
||||
this.openPM(name).prependTo(this.$pmBox).find('textarea[name=message]').focus();
|
||||
|
|
@ -668,10 +668,10 @@
|
|||
app.addPopup(AvatarsPopup);
|
||||
},
|
||||
openSounds: function () {
|
||||
app.addPopup(SoundsPopup, {type: 'semimodal'});
|
||||
app.addPopup(SoundsPopup, { type: 'semimodal' });
|
||||
},
|
||||
openOptions: function () {
|
||||
app.addPopup(OptionsPopup, {type: 'semimodal'});
|
||||
app.addPopup(OptionsPopup, { type: 'semimodal' });
|
||||
},
|
||||
|
||||
// challenges and searching
|
||||
|
|
@ -1002,7 +1002,7 @@
|
|||
}
|
||||
},
|
||||
format: function (format, button) {
|
||||
if (window.BattleFormats) app.addPopup(FormatPopup, {format: format, sourceEl: button});
|
||||
if (window.BattleFormats) app.addPopup(FormatPopup, { format: format, sourceEl: button });
|
||||
},
|
||||
adjustPrivacy: function (disallowSpectators) {
|
||||
Storage.prefs('disallowspectators', disallowSpectators);
|
||||
|
|
@ -1012,7 +1012,7 @@
|
|||
},
|
||||
team: function (team, button) {
|
||||
var format = $(button).closest('form').find('button[name=format]').val();
|
||||
app.addPopup(TeamPopup, {team: team, format: format, sourceEl: button, folderToggleOn: true, folderNotExpanded: []});
|
||||
app.addPopup(TeamPopup, { team: team, format: format, sourceEl: button, folderToggleOn: true, folderNotExpanded: [] });
|
||||
},
|
||||
|
||||
// format/team selection
|
||||
|
|
@ -1142,7 +1142,7 @@
|
|||
app.addPopupPrompt("Username", "Open", function (target) {
|
||||
if (!target) return;
|
||||
if (toID(target) === 'zarel') {
|
||||
app.addPopup(Popup, {htmlMessage: "Zarel is very busy; please don't contact him this way. If you're looking for help, try <a href=\"/help\">joining the Help room</a>?"});
|
||||
app.addPopup(Popup, { htmlMessage: "Zarel is very busy; please don't contact him this way. If you're looking for help, try <a href=\"/help\">joining the Help room</a>?" });
|
||||
return;
|
||||
}
|
||||
if (target === '~') {
|
||||
|
|
@ -1150,7 +1150,7 @@
|
|||
app.rooms[''].focusPM('~');
|
||||
return;
|
||||
}
|
||||
app.addPopup(UserPopup, {name: target});
|
||||
app.addPopup(UserPopup, { name: target });
|
||||
});
|
||||
}
|
||||
}, {
|
||||
|
|
@ -1202,14 +1202,14 @@
|
|||
case 'data-move':
|
||||
return '[outdated message type not supported]';
|
||||
case 'text':
|
||||
return {message: '<div class="chat">' + BattleLog.parseMessage(target) + '</div>', noNotify: true};
|
||||
return { message: '<div class="chat">' + BattleLog.parseMessage(target) + '</div>', noNotify: true };
|
||||
case 'error':
|
||||
return '<div class="chat message-error">' + BattleLog.escapeHTML(target) + '</div>';
|
||||
case 'html':
|
||||
if (!name) {
|
||||
return {message: '<div class="chat' + hlClass + '">' + timestamp + '<em>' + BattleLog.sanitizeHTML(target) + '</em></div>', noNotify: isNotPM};
|
||||
return { message: '<div class="chat' + hlClass + '">' + timestamp + '<em>' + BattleLog.sanitizeHTML(target) + '</em></div>', noNotify: isNotPM };
|
||||
}
|
||||
return {message: '<div class="chat chatmessage-' + toID(name) + hlClass + mineClass + '">' + timestamp + '<strong style="' + color + '">' + clickableName + ':</strong> <em>' + BattleLog.sanitizeHTML(target) + '</em></div>', noNotify: isNotPM};
|
||||
return { message: '<div class="chat chatmessage-' + toID(name) + hlClass + mineClass + '">' + timestamp + '<strong style="' + color + '">' + clickableName + ':</strong> <em>' + BattleLog.sanitizeHTML(target) + '</em></div>', noNotify: isNotPM };
|
||||
case 'uhtml':
|
||||
case 'uhtmlchange':
|
||||
var parts = target.split(',');
|
||||
|
|
@ -1225,13 +1225,13 @@
|
|||
$elements.remove();
|
||||
$chatElem.append('<div class="chat uhtml-' + toID(parts[0]) + ' chatmessage-' + toID(name) + '">' + BattleLog.sanitizeHTML(html) + '</div>');
|
||||
}
|
||||
return {message: '', noNotify: isNotPM};
|
||||
return { message: '', noNotify: isNotPM };
|
||||
case 'raw':
|
||||
return {message: '<div class="chat chatmessage-' + toID(name) + '">' + BattleLog.sanitizeHTML(target) + '</div>', noNotify: isNotPM};
|
||||
return { message: '<div class="chat chatmessage-' + toID(name) + '">' + BattleLog.sanitizeHTML(target) + '</div>', noNotify: isNotPM };
|
||||
case 'nonotify':
|
||||
return {message: '<div class="chat">' + timestamp + BattleLog.sanitizeHTML(target) + '</div>', noNotify: true};
|
||||
return { message: '<div class="chat">' + timestamp + BattleLog.sanitizeHTML(target) + '</div>', noNotify: true };
|
||||
case 'challenge':
|
||||
return {challenge: target};
|
||||
return { challenge: target };
|
||||
default:
|
||||
// Not a command or unsupported. Parsed as a normal chat message.
|
||||
if (!name) {
|
||||
|
|
@ -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,11 +1578,11 @@
|
|||
}
|
||||
},
|
||||
events: {
|
||||
'click input[type=checkbox]': 'foldersToggle',
|
||||
'click input[type=checkbox]': 'foldersToggle'
|
||||
},
|
||||
moreTeams: function () {
|
||||
this.close();
|
||||
app.addPopup(TeamPopup, {team: this.team, format: this.format, sourceEl: this.sourceEl, room: this.room, isMoreTeams: true, folderToggleOn: this.folderToggleOn, folderNotExpanded: this.folderNotExpanded});
|
||||
app.addPopup(TeamPopup, { team: this.team, format: this.format, sourceEl: this.sourceEl, room: this.room, isMoreTeams: true, folderToggleOn: this.folderToggleOn, folderNotExpanded: this.folderNotExpanded });
|
||||
},
|
||||
teambuilder: function () {
|
||||
var teamFormat = this.teamFormat;
|
||||
|
|
@ -1599,19 +1599,18 @@
|
|||
if (folder === key) {
|
||||
keyExists = true;
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (!keyExists) {
|
||||
folderNotExpanded.push(key);
|
||||
}
|
||||
this.close();
|
||||
app.addPopup(TeamPopup, {team: this.team, format: this.format, sourceEl: this.sourceEl, room: this.room, isMoreTeams: this.isMoreTeams, folderToggleOn: this.folderToggleOn, folderNotExpanded: folderNotExpanded});
|
||||
app.addPopup(TeamPopup, { team: this.team, format: this.format, sourceEl: this.sourceEl, room: this.room, isMoreTeams: this.isMoreTeams, folderToggleOn: this.folderToggleOn, folderNotExpanded: folderNotExpanded });
|
||||
},
|
||||
foldersToggle: function () {
|
||||
this.close();
|
||||
app.addPopup(TeamPopup, {team: this.team, format: this.format, sourceEl: this.sourceEl, room: this.room, isMoreTeams: this.isMoreTeams, folderToggleOn: !this.folderToggleOn, folderNotExpanded: this.folderNotExpanded});
|
||||
app.addPopup(TeamPopup, { team: this.team, format: this.format, sourceEl: this.sourceEl, room: this.room, isMoreTeams: this.isMoreTeams, folderToggleOn: !this.folderToggleOn, folderNotExpanded: this.folderNotExpanded });
|
||||
},
|
||||
selectTeam: function (i) {
|
||||
i = +i;
|
||||
|
|
|
|||
|
|
@ -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 () {
|
||||
|
|
@ -196,10 +198,10 @@
|
|||
app.addPopupPrompt("Username", "Open", function (target) {
|
||||
if (!target) return;
|
||||
if (toID(target) === 'zarel') {
|
||||
app.addPopup(Popup, {htmlMessage: "Zarel is very busy; please don't contact him this way. If you're looking for help, try <a href=\"/help\">joining the Help room</a>?"});
|
||||
app.addPopup(Popup, { htmlMessage: "Zarel is very busy; please don't contact him this way. If you're looking for help, try <a href=\"/help\">joining the Help room</a>?" });
|
||||
return;
|
||||
}
|
||||
app.addPopup(UserPopup, {name: target});
|
||||
app.addPopup(UserPopup, { name: target });
|
||||
});
|
||||
},
|
||||
refresh: function () {
|
||||
|
|
@ -243,9 +245,9 @@
|
|||
return;
|
||||
}
|
||||
var self = this;
|
||||
app.addPopup(FormatPopup, {format: format, sourceEl: button, selectType: 'watch', onselect: function (newFormat) {
|
||||
app.addPopup(FormatPopup, { format: format, sourceEl: button, selectType: 'watch', onselect: function (newFormat) {
|
||||
self.changeFormat(newFormat);
|
||||
}});
|
||||
} });
|
||||
},
|
||||
changeFormat: function (format) {
|
||||
this.format = format;
|
||||
|
|
|
|||
|
|
@ -52,8 +52,8 @@
|
|||
'change .detailsform input': 'detailsChange',
|
||||
'change .detailsform select': 'detailsChange',
|
||||
'submit .detailsform': 'detailsChange',
|
||||
'click .changeform' : 'altForm',
|
||||
'click .altform' : 'altForm',
|
||||
'click .changeform': 'altForm',
|
||||
'click .altform': 'altForm',
|
||||
|
||||
// stats
|
||||
'keyup .statform input.numform': 'statChange',
|
||||
|
|
@ -544,7 +544,7 @@
|
|||
this.teamScrollPos = 0;
|
||||
}
|
||||
|
||||
//reset focus to searchbar
|
||||
// reset focus to searchbar
|
||||
var teamSearchBar = this.$("#teamSearchBar");
|
||||
var strLength = teamSearchBar.val().length;
|
||||
if (strLength) {
|
||||
|
|
@ -555,7 +555,6 @@
|
|||
updatePersistence: function (state) {
|
||||
if (state) {
|
||||
this.$('.storage-warning').html('');
|
||||
return;
|
||||
}
|
||||
},
|
||||
greeting: function (answer, button) {
|
||||
|
|
@ -640,9 +639,9 @@
|
|||
if (format === '+') {
|
||||
e.stopImmediatePropagation();
|
||||
var self = this;
|
||||
app.addPopup(FormatPopup, {format: '', sourceEl: e.currentTarget, selectType: 'teambuilder', onselect: function (newFormat) {
|
||||
app.addPopup(FormatPopup, { format: '', sourceEl: e.currentTarget, selectType: 'teambuilder', onselect: function (newFormat) {
|
||||
self.selectFolder(newFormat);
|
||||
}});
|
||||
} });
|
||||
return;
|
||||
}
|
||||
if (format === '++') {
|
||||
|
|
@ -651,7 +650,7 @@
|
|||
// app.addPopupPrompt("Folder name:", "Create folder", function (newFormat) {
|
||||
// self.selectFolder(newFormat + '/');
|
||||
// });
|
||||
app.addPopup(PromptPopup, {message: "Folder name:", button: "Create folder", sourceEl: e.currentTarget, callback: function (name) {
|
||||
app.addPopup(PromptPopup, { message: "Folder name:", button: "Create folder", sourceEl: e.currentTarget, callback: function (name) {
|
||||
name = $.trim(name);
|
||||
if (name.indexOf('/') >= 0 || name.indexOf('\\') >= 0) {
|
||||
app.addPopupMessage("Names can't contain slashes, since they're used as a folder separator.");
|
||||
|
|
@ -663,7 +662,7 @@
|
|||
}
|
||||
if (!name) return;
|
||||
self.selectFolder(name + '/');
|
||||
}});
|
||||
} });
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
|
|
@ -678,7 +677,7 @@
|
|||
if (this.curFolder.slice(-1) !== '/') return;
|
||||
var oldFolder = this.curFolder.slice(0, -1);
|
||||
var self = this;
|
||||
app.addPopup(PromptPopup, {message: "Folder name:", button: "Rename folder", value: oldFolder, callback: function (name) {
|
||||
app.addPopup(PromptPopup, { message: "Folder name:", button: "Rename folder", value: oldFolder, callback: function (name) {
|
||||
name = $.trim(name);
|
||||
if (name.indexOf('/') >= 0 || name.indexOf('\\') >= 0) {
|
||||
app.addPopupMessage("Names can't contain slashes, since they're used as a folder separator.");
|
||||
|
|
@ -698,10 +697,10 @@
|
|||
}
|
||||
if (!window.nodewebkit) Storage.saveTeams();
|
||||
self.selectFolder(name + '/');
|
||||
}});
|
||||
} });
|
||||
},
|
||||
promptDeleteFolder: function () {
|
||||
app.addPopup(DeleteFolderPopup, {folder: this.curFolder, room: this});
|
||||
app.addPopup(DeleteFolderPopup, { folder: this.curFolder, room: this });
|
||||
},
|
||||
deleteFolder: function (format, addName) {
|
||||
if (format.slice(-1) !== '/') return;
|
||||
|
|
@ -1397,7 +1396,7 @@
|
|||
evBuf += '<small>−</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')) {
|
||||
|
|
@ -1595,9 +1594,9 @@
|
|||
return;
|
||||
}
|
||||
var self = this;
|
||||
app.addPopup(FormatPopup, {format: format, sourceEl: button, selectType: 'teambuilder', onselect: function (newFormat) {
|
||||
app.addPopup(FormatPopup, { format: format, sourceEl: button, selectType: 'teambuilder', onselect: function (newFormat) {
|
||||
self.changeFormat(newFormat);
|
||||
}});
|
||||
} });
|
||||
},
|
||||
changeFormat: function (format) {
|
||||
this.curTeam.format = format;
|
||||
|
|
@ -1683,7 +1682,7 @@
|
|||
clipboardExpanded: false,
|
||||
clipboardExpand: function () {
|
||||
var $clipboard = $('.teambuilder-clipboard-data');
|
||||
$clipboard.animate({height: this.clipboardCount() * 34}, 500, function () {
|
||||
$clipboard.animate({ height: this.clipboardCount() * 34 }, 500, function () {
|
||||
setTimeout(function () { $clipboard.focus(); }, 100);
|
||||
});
|
||||
|
||||
|
|
@ -1693,7 +1692,7 @@
|
|||
},
|
||||
clipboardShrink: function () {
|
||||
var $clipboard = $('.teambuilder-clipboard-data');
|
||||
$clipboard.animate({height: 32}, 500);
|
||||
$clipboard.animate({ height: 32 }, 500);
|
||||
|
||||
setTimeout(function () {
|
||||
this.clipboardExpanded = false;
|
||||
|
|
@ -1725,7 +1724,7 @@
|
|||
if (this.clipboardCount() === 1) {
|
||||
var $clipboard = $('.teambuilder-clipboard-container').css('opacity', 0);
|
||||
$clipboard.slideDown(250, function () {
|
||||
$clipboard.animate({opacity: 1}, 250);
|
||||
$clipboard.animate({ opacity: 1 }, 250);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
|
@ -1734,7 +1733,7 @@
|
|||
|
||||
var self = this;
|
||||
var $clipboard = $('.teambuilder-clipboard-container');
|
||||
$clipboard.animate({opacity: 0}, 250, function () {
|
||||
$clipboard.animate({ opacity: 0 }, 250, function () {
|
||||
$clipboard.slideUp(250, function () {
|
||||
self.clipboardUpdate();
|
||||
});
|
||||
|
|
@ -2016,7 +2015,7 @@
|
|||
if (!set.species) {
|
||||
buf += '<button disabled class="addpokemon" aria-label="Add Pokémon"><i class="fa fa-plus"></i></button> ';
|
||||
isAdd = true;
|
||||
} else if (i == this.curSetLoc) {
|
||||
} 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> ';
|
||||
|
|
@ -2048,7 +2047,7 @@
|
|||
var set = this.curSet;
|
||||
if (!set) return;
|
||||
|
||||
var stats = {hp:'', atk:'', def:'', spa:'', spd:'', spe:''};
|
||||
var stats = { hp: '', atk: '', def: '', spa: '', spd: '', spe: '' };
|
||||
|
||||
var supportsEVs = !this.curTeam.format.includes('letsgo');
|
||||
|
||||
|
|
@ -2066,7 +2065,7 @@
|
|||
evBuf += '<small>−</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;
|
||||
|
|
@ -2311,7 +2310,7 @@
|
|||
return;
|
||||
}
|
||||
|
||||
var stats = {hp:'', atk:'', def:'', spa:'', spd:'', spe:''};
|
||||
var stats = { hp: '', atk: '', def: '', spa: '', spd: '', spe: '' };
|
||||
if (this.curTeam.gen === 1) delete stats.spd;
|
||||
if (!set) return;
|
||||
var nature = BattleNatures[set.nature || 'Serious'];
|
||||
|
|
@ -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;
|
||||
|
|
@ -2709,7 +2708,7 @@
|
|||
} else {
|
||||
var hpTypeX = 0;
|
||||
var i = 1;
|
||||
var stats = {hp: 31, atk: 31, def: 31, spe: 31, spa: 31, spd: 31};
|
||||
var stats = { hp: 31, atk: 31, def: 31, spe: 31, spa: 31, spd: 31 };
|
||||
for (var s in stats) {
|
||||
if (set.ivs[s] === undefined) set.ivs[s] = 31;
|
||||
hpTypeX += i * (set.ivs[s] % 2);
|
||||
|
|
@ -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) {
|
||||
|
|
@ -2848,7 +2847,7 @@
|
|||
if (this.curTeam.gen > 1) {
|
||||
buf += '<div class="formrow"><label class="formlabel">Gender:</label><div>';
|
||||
if (species.gender && !isHackmons) {
|
||||
var genderTable = {'M': "Male", 'F': "Female", 'N': "Genderless"};
|
||||
var genderTable = { 'M': "Male", 'F': "Female", 'N': "Genderless" };
|
||||
buf += genderTable[species.gender];
|
||||
} else {
|
||||
buf += '<label class="checkbox inline"><input type="radio" name="gender" value="M"' + (set.gender === 'M' ? ' checked' : '') + ' /> Male</label> ';
|
||||
|
|
@ -3050,7 +3049,7 @@
|
|||
i = +$(e.currentTarget).closest('li').attr('value');
|
||||
set = this.curSetList[i];
|
||||
}
|
||||
app.addPopup(AltFormPopup, {curSet: set, index: i, room: this});
|
||||
app.addPopup(AltFormPopup, { curSet: set, index: i, room: this });
|
||||
},
|
||||
|
||||
/*********************************************************
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -3294,7 +3290,7 @@
|
|||
set.item = 'Starf Berry';
|
||||
set.ability = 'Harvest';
|
||||
set.moves = ['Substitute', 'Horn Leech', 'Earthquake', 'Phantom Force'];
|
||||
set.evs = {hp: 36, atk: 252, def: 0, spa: 0, spd: 0, spe: 220};
|
||||
set.evs = { hp: 36, atk: 252, def: 0, spa: 0, spd: 0, spe: 220 };
|
||||
set.ivs = {};
|
||||
set.nature = 'Jolly';
|
||||
this.updateSetTop();
|
||||
|
|
@ -3328,7 +3324,7 @@
|
|||
set.item = 'Leftovers';
|
||||
set.ability = 'Battle Armor';
|
||||
set.moves = ['Acupressure', 'Knock Off', 'Rest', 'Sleep Talk'];
|
||||
set.evs = {hp: 248, atk: 0, def: 96, spa: 0, spd: 108, spe: 56};
|
||||
set.evs = { hp: 248, atk: 0, def: 96, spa: 0, spd: 108, spe: 56 };
|
||||
set.ivs = {};
|
||||
set.nature = 'Impish';
|
||||
this.updateSetTop();
|
||||
|
|
@ -3428,7 +3424,7 @@
|
|||
if (!this.canHyperTrain(set)) {
|
||||
var hpType = moveName.substr(13);
|
||||
|
||||
set.ivs = {hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31};
|
||||
set.ivs = { hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31 };
|
||||
if (this.curTeam.gen > 2) {
|
||||
var HPivs = this.curTeam.dex.types.get(hpType).HPivs;
|
||||
for (var i in HPivs) {
|
||||
|
|
@ -3736,7 +3732,7 @@
|
|||
buf += '<div style="clear:both"></div>';
|
||||
buf += '</div>';
|
||||
|
||||
this.$el.html(buf).css({'max-width': (4 + spriteSize) * 7});
|
||||
this.$el.html(buf).css({ 'max-width': (4 + spriteSize) * 7 });
|
||||
},
|
||||
setForm: function (form) {
|
||||
var species = Dex.species.get(this.curSet.species);
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@
|
|||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
var name = $(e.currentTarget).data('name');
|
||||
app.addPopup(UserPopup, {name: name, sourceEl: e.currentTarget});
|
||||
app.addPopup(UserPopup, { name: name, sourceEl: e.currentTarget });
|
||||
},
|
||||
toggleMute: function () {
|
||||
var muted = !Dex.prefs('mute');
|
||||
|
|
@ -224,7 +224,7 @@
|
|||
e.preventDefault();
|
||||
var $target = $(e.currentTarget);
|
||||
if ($target.hasClass('minilogo')) {
|
||||
app.addPopup(TabListPopup, {sourceEl: e.currentTarget});
|
||||
app.addPopup(TabListPopup, { sourceEl: e.currentTarget });
|
||||
return;
|
||||
}
|
||||
var id = $target.attr('href');
|
||||
|
|
@ -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) {
|
||||
|
|
@ -877,7 +877,7 @@
|
|||
popup.$('.cur').removeClass('cur');
|
||||
Storage.bg.set(e.target.result, 'custom');
|
||||
} else {
|
||||
app.addPopup(ConfirmBackgroundPopup, {bgUrl: e.target.result});
|
||||
app.addPopup(ConfirmBackgroundPopup, { bgUrl: e.target.result });
|
||||
}
|
||||
};
|
||||
reader.readAsDataURL(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();
|
||||
|
|
@ -178,8 +179,8 @@ function toId() {
|
|||
Storage.prefs('serversettings', self.get('settings'));
|
||||
});
|
||||
|
||||
var replaceList = {'A': 'AⱯȺ', 'B': 'BƂƁɃ', 'C': 'CꜾȻ', 'D': 'DĐƋƊƉꝹ', 'E': 'EƐƎ', 'F': 'FƑꝻ', 'G': 'GꞠꝽꝾ', 'H': 'HĦⱧⱵꞍ', 'I': 'IƗ', 'J': 'JɈ', 'K': 'KꞢ', 'L': 'LꝆꞀ', 'M': 'MⱮƜ', 'N': 'NȠƝꞐꞤ', 'O': 'OǪǬØǾƆƟꝊꝌ', 'P': 'PƤⱣꝐꝒꝔ', 'Q': 'QꝖꝘɊ', 'R': 'RɌⱤꝚꞦꞂ', 'S': 'SẞꞨꞄ', 'T': 'TŦƬƮȾꞆ', 'U': 'UɄ', 'V': 'VƲꝞɅ', 'W': 'WⱲ', 'X': 'X', 'Y': 'YɎỾ', 'Z': 'ZƵȤⱿⱫꝢ', 'a': 'aąⱥɐ', 'b': 'bƀƃɓ', 'c': 'cȼꜿↄ', 'd': 'dđƌɖɗꝺ', 'e': 'eɇɛǝ', 'f': 'fḟƒꝼ', 'g': 'gɠꞡᵹꝿ', 'h': 'hħⱨⱶɥ', 'i': 'iɨı', 'j': 'jɉ', 'k': 'kƙⱪꝁꝃꝅꞣ', 'l': 'lſłƚɫⱡꝉꞁꝇ', 'm': 'mɱɯ', 'n': 'nƞɲʼnꞑꞥ', 'o': 'oǫǭøǿɔꝋꝍɵ', 'p': 'pƥᵽꝑꝓꝕ', 'q': 'qɋꝗꝙ', 'r': 'rɍɽꝛꞧꞃ', 's': 'sꞩꞅẛ', 't': 'tŧƭʈⱦꞇ', 'u': 'uưừứữửựųṷṵʉ', 'v': 'vʋꝟʌ', 'w': 'wⱳ', 'x': 'x', 'y': 'yɏỿ', 'z': 'zƶȥɀⱬꝣ', 'AA': 'Ꜳ', 'AE': 'ÆǼǢ', 'AO': 'Ꜵ', 'AU': 'Ꜷ', 'AV': 'ꜸꜺ', 'AY': 'Ꜽ', 'DZ': 'DZDŽ', 'Dz': 'DzDž', 'LJ': 'LJ', 'Lj': 'Lj', 'NJ': 'NJ', 'Nj': 'Nj', 'OI': 'Ƣ', 'OO': 'Ꝏ', 'OU': 'Ȣ', 'TZ': 'Ꜩ', 'VY': 'Ꝡ', 'aa': 'ꜳ', 'ae': 'æǽǣ', 'ao': 'ꜵ', 'au': 'ꜷ', 'av': 'ꜹꜻ', 'ay': 'ꜽ', 'dz': 'dzdž', 'hv': 'ƕ', 'lj': 'lj', 'nj': 'nj', 'oi': 'ƣ', 'ou': 'ȣ', 'oo': 'ꝏ', 'ss': 'ß', 'tz': 'ꜩ', 'vy': 'ꝡ'};
|
||||
var normalizeList = {'A': 'ÀÁÂẦẤẪẨÃĀĂẰẮẴẲȦǠÄǞẢÅǺǍȀȂẠẬẶḀĄ', 'B': 'ḂḄḆ', 'C': 'ĆĈĊČÇḈƇ', 'D': 'ḊĎḌḐḒḎ', 'E': 'ÈÉÊỀẾỄỂẼĒḔḖĔĖËẺĚȄȆẸỆȨḜĘḘḚ', 'F': 'Ḟ', 'G': 'ǴĜḠĞĠǦĢǤƓ', 'H': 'ĤḢḦȞḤḨḪ', 'I': 'ÌÍÎĨĪĬİÏḮỈǏȈȊỊĮḬ', 'J': 'Ĵ', 'K': 'ḰǨḲĶḴƘⱩꝀꝂꝄ', 'L': 'ĿĹĽḶḸĻḼḺŁȽⱢⱠꝈ', 'M': 'ḾṀṂ', 'N': 'ǸŃÑṄŇṆŅṊṈ', 'O': 'ÒÓÔỒỐỖỔÕṌȬṎŌṐṒŎȮȰÖȪỎŐǑȌȎƠỜỚỠỞỢỌỘ', 'P': 'ṔṖ', 'Q': '', 'R': 'ŔṘŘȐȒṚṜŖṞ', 'S': 'ŚṤŜṠŠṦṢṨȘŞⱾ', 'T': 'ṪŤṬȚŢṰṮ', 'U': 'ÙÚÛŨṸŪṺŬÜǛǗǕǙỦŮŰǓȔȖƯỪỨỮỬỰỤṲŲṶṴ', 'V': 'ṼṾ', 'W': 'ẀẂŴẆẄẈ', 'X': 'ẊẌ', 'Y': 'ỲÝŶỸȲẎŸỶỴƳ', 'Z': 'ŹẐŻŽẒẔ', 'a': 'ẚàáâầấẫẩãāăằắẵẳȧǡäǟảåǻǎȁȃạậặḁ', 'b': 'ḃḅḇ', 'c': 'ćĉċčçḉƈ', 'd': 'ḋďḍḑḓḏ', 'e': 'èéêềếễểẽēḕḗĕėëẻěȅȇẹệȩḝęḙḛ', 'f': '', 'g': 'ǵĝḡğġǧģǥ', 'h': 'ĥḣḧȟḥḩḫẖ', 'i': 'ìíîĩīĭïḯỉǐȉȋịįḭ', 'j': 'ĵǰ', 'k': 'ḱǩḳķḵ', 'l': 'ŀĺľḷḹļḽḻ', 'm': 'ḿṁṃ', 'n': 'ǹńñṅňṇņṋṉ', 'o': 'òóôồốỗổõṍȭṏōṑṓŏȯȱöȫỏőǒȍȏơờớỡởợọộ', 'p': 'ṕṗ', 'q': '', 'r': 'ŕṙřȑȓṛṝŗṟ', 's': 'śṥŝṡšṧṣṩșşȿ', 't': 'ṫẗťṭțţṱṯ', 'u': 'ùúûũṹūṻŭüǜǘǖǚủůűǔȕȗụṳ', 'v': 'ṽṿ', 'w': 'ẁẃŵẇẅẘẉ', 'x': 'ẋẍ', 'y': 'ỳýŷỹȳẏÿỷẙỵƴ', 'z': 'źẑżžẓẕ'};
|
||||
var replaceList = { 'A': 'AⱯȺ', 'B': 'BƂƁɃ', 'C': 'CꜾȻ', 'D': 'DĐƋƊƉꝹ', 'E': 'EƐƎ', 'F': 'FƑꝻ', 'G': 'GꞠꝽꝾ', 'H': 'HĦⱧⱵꞍ', 'I': 'IƗ', 'J': 'JɈ', 'K': 'KꞢ', 'L': 'LꝆꞀ', 'M': 'MⱮƜ', 'N': 'NȠƝꞐꞤ', 'O': 'OǪǬØǾƆƟꝊꝌ', 'P': 'PƤⱣꝐꝒꝔ', 'Q': 'QꝖꝘɊ', 'R': 'RɌⱤꝚꞦꞂ', 'S': 'SẞꞨꞄ', 'T': 'TŦƬƮȾꞆ', 'U': 'UɄ', 'V': 'VƲꝞɅ', 'W': 'WⱲ', 'X': 'X', 'Y': 'YɎỾ', 'Z': 'ZƵȤⱿⱫꝢ', 'a': 'aąⱥɐ', 'b': 'bƀƃɓ', 'c': 'cȼꜿↄ', 'd': 'dđƌɖɗꝺ', 'e': 'eɇɛǝ', 'f': 'fḟƒꝼ', 'g': 'gɠꞡᵹꝿ', 'h': 'hħⱨⱶɥ', 'i': 'iɨı', 'j': 'jɉ', 'k': 'kƙⱪꝁꝃꝅꞣ', 'l': 'lſłƚɫⱡꝉꞁꝇ', 'm': 'mɱɯ', 'n': 'nƞɲʼnꞑꞥ', 'o': 'oǫǭøǿɔꝋꝍɵ', 'p': 'pƥᵽꝑꝓꝕ', 'q': 'qɋꝗꝙ', 'r': 'rɍɽꝛꞧꞃ', 's': 'sꞩꞅẛ', 't': 'tŧƭʈⱦꞇ', 'u': 'uưừứữửựųṷṵʉ', 'v': 'vʋꝟʌ', 'w': 'wⱳ', 'x': 'x', 'y': 'yɏỿ', 'z': 'zƶȥɀⱬꝣ', 'AA': 'Ꜳ', 'AE': 'ÆǼǢ', 'AO': 'Ꜵ', 'AU': 'Ꜷ', 'AV': 'ꜸꜺ', 'AY': 'Ꜽ', 'DZ': 'DZDŽ', 'Dz': 'DzDž', 'LJ': 'LJ', 'Lj': 'Lj', 'NJ': 'NJ', 'Nj': 'Nj', 'OI': 'Ƣ', 'OO': 'Ꝏ', 'OU': 'Ȣ', 'TZ': 'Ꜩ', 'VY': 'Ꝡ', 'aa': 'ꜳ', 'ae': 'æǽǣ', 'ao': 'ꜵ', 'au': 'ꜷ', 'av': 'ꜹꜻ', 'ay': 'ꜽ', 'dz': 'dzdž', 'hv': 'ƕ', 'lj': 'lj', 'nj': 'nj', 'oi': 'ƣ', 'ou': 'ȣ', 'oo': 'ꝏ', 'ss': 'ß', 'tz': 'ꜩ', 'vy': 'ꝡ' };
|
||||
var normalizeList = { 'A': 'ÀÁÂẦẤẪẨÃĀĂẰẮẴẲȦǠÄǞẢÅǺǍȀȂẠẬẶḀĄ', 'B': 'ḂḄḆ', 'C': 'ĆĈĊČÇḈƇ', 'D': 'ḊĎḌḐḒḎ', 'E': 'ÈÉÊỀẾỄỂẼĒḔḖĔĖËẺĚȄȆẸỆȨḜĘḘḚ', 'F': 'Ḟ', 'G': 'ǴĜḠĞĠǦĢǤƓ', 'H': 'ĤḢḦȞḤḨḪ', 'I': 'ÌÍÎĨĪĬİÏḮỈǏȈȊỊĮḬ', 'J': 'Ĵ', 'K': 'ḰǨḲĶḴƘⱩꝀꝂꝄ', 'L': 'ĿĹĽḶḸĻḼḺŁȽⱢⱠꝈ', 'M': 'ḾṀṂ', 'N': 'ǸŃÑṄŇṆŅṊṈ', 'O': 'ÒÓÔỒỐỖỔÕṌȬṎŌṐṒŎȮȰÖȪỎŐǑȌȎƠỜỚỠỞỢỌỘ', 'P': 'ṔṖ', 'Q': '', 'R': 'ŔṘŘȐȒṚṜŖṞ', 'S': 'ŚṤŜṠŠṦṢṨȘŞⱾ', 'T': 'ṪŤṬȚŢṰṮ', 'U': 'ÙÚÛŨṸŪṺŬÜǛǗǕǙỦŮŰǓȔȖƯỪỨỮỬỰỤṲŲṶṴ', 'V': 'ṼṾ', 'W': 'ẀẂŴẆẄẈ', 'X': 'ẊẌ', 'Y': 'ỲÝŶỸȲẎŸỶỴƳ', 'Z': 'ŹẐŻŽẒẔ', 'a': 'ẚàáâầấẫẩãāăằắẵẳȧǡäǟảåǻǎȁȃạậặḁ', 'b': 'ḃḅḇ', 'c': 'ćĉċčçḉƈ', 'd': 'ḋďḍḑḓḏ', 'e': 'èéêềếễểẽēḕḗĕėëẻěȅȇẹệȩḝęḙḛ', 'f': '', 'g': 'ǵĝḡğġǧģǥ', 'h': 'ĥḣḧȟḥḩḫẖ', 'i': 'ìíîĩīĭïḯỉǐȉȋịįḭ', 'j': 'ĵǰ', 'k': 'ḱǩḳķḵ', 'l': 'ŀĺľḷḹļḽḻ', 'm': 'ḿṁṃ', 'n': 'ǹńñṅňṇņṋṉ', 'o': 'òóôồốỗổõṍȭṏōṑṓŏȯȱöȫỏőǒȍȏơờớỡởợọộ', 'p': 'ṕṗ', 'q': '', 'r': 'ŕṙřȑȓṛṝŗṟ', 's': 'śṥŝṡšṧṣṩșşȿ', 't': 'ṫẗťṭțţṱṯ', 'u': 'ùúûũṹūṻŭüǜǘǖǚủůűǔȕȗụṳ', 'v': 'ṽṿ', 'w': 'ẁẃŵẇẅẘẉ', 'x': 'ẋẍ', 'y': 'ỳýŷỹȳẏÿỷẙỵƴ', 'z': 'źẑżžẓẕ' };
|
||||
for (var i in replaceList) {
|
||||
replaceList[i] = new RegExp('[' + replaceList[i] + ']', 'g');
|
||||
}
|
||||
|
|
@ -409,7 +410,7 @@ function toId() {
|
|||
// this.down = true;
|
||||
|
||||
this.addRoom('');
|
||||
this.topbar = new Topbar({el: $('#header')});
|
||||
this.topbar = new Topbar({ el: $('#header') });
|
||||
if (this.down) {
|
||||
this.isDisconnected = true;
|
||||
// } else if (location.origin === 'http://smogtours.psim.us') {
|
||||
|
|
@ -427,7 +428,7 @@ function toId() {
|
|||
Storage.whenPrefsLoaded(function () {
|
||||
if (!Config.server.registered) {
|
||||
app.send('/autojoin');
|
||||
Backbone.history.start({pushState: !Config.testclient});
|
||||
Backbone.history.start({ pushState: !Config.testclient });
|
||||
return;
|
||||
}
|
||||
// Support legacy tournament setting and migrate to new pref
|
||||
|
|
@ -459,7 +460,7 @@ function toId() {
|
|||
if (Object.keys(settings).length) app.user.set('settings', settings);
|
||||
// HTML5 history throws exceptions when running on file://
|
||||
var useHistory = !Config.testclient && (location.pathname.slice(-5) !== '.html');
|
||||
Backbone.history.start({pushState: useHistory});
|
||||
Backbone.history.start({ pushState: useHistory });
|
||||
app.ignore = app.loadIgnore();
|
||||
});
|
||||
}
|
||||
|
|
@ -539,21 +540,21 @@ function toId() {
|
|||
$('.battle-log-add').html('<small>You are disconnected and cannot chat.</small>');
|
||||
|
||||
self.reconnectPending = (message || true);
|
||||
if (!self.popups.length) self.addPopup(ReconnectPopup, {message: message});
|
||||
if (!self.popups.length) self.addPopup(ReconnectPopup, { message: message });
|
||||
});
|
||||
|
||||
this.on('init:connectionerror', function () {
|
||||
self.isDisconnected = true;
|
||||
self.rooms[''].updateFormats();
|
||||
self.addPopup(ReconnectPopup, {cantconnect: true});
|
||||
self.addPopup(ReconnectPopup, { cantconnect: true });
|
||||
});
|
||||
|
||||
this.user.on('login:invalidname', function (name, reason) {
|
||||
self.addPopup(LoginPopup, {name: name, reason: reason});
|
||||
self.addPopup(LoginPopup, { name: name, reason: reason });
|
||||
});
|
||||
|
||||
this.user.on('login:authrequired', function (name, special) {
|
||||
self.addPopup(LoginPasswordPopup, {username: name, special: special});
|
||||
self.addPopup(LoginPasswordPopup, { username: name, special: special });
|
||||
});
|
||||
|
||||
this.on('loggedin', function () {
|
||||
|
|
@ -790,7 +791,7 @@ function toId() {
|
|||
}
|
||||
return new SockJS(
|
||||
protocol + '://' + Config.server.host + ':' + Config.server.port + Config.sockjsprefix,
|
||||
[], {timeout: 5 * 60 * 1000}
|
||||
[], { timeout: 5 * 60 * 1000 }
|
||||
);
|
||||
} catch (err) {
|
||||
// The most common case this happens is if an HTTPS connection fails,
|
||||
|
|
@ -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) {
|
||||
|
|
@ -1041,7 +1042,7 @@ function toId() {
|
|||
var replayid = roomid.slice(7);
|
||||
if (Config.server.id !== 'showdown') replayid = Config.server.id + '-' + replayid;
|
||||
var replayLink = 'https://' + Config.routes.replays + '/' + replayid;
|
||||
$.ajax(replayLink + '.json', {dataType: 'json'}).done(function (replay) {
|
||||
$.ajax(replayLink + '.json', { dataType: 'json' }).done(function (replay) {
|
||||
if (replay) {
|
||||
var title = replay.players[0] + ' vs. ' + replay.players[1];
|
||||
app.receive('>battle-' + replayid + '\n|init|battle\n|title|' + title + '\n' + replay.log);
|
||||
|
|
@ -1169,7 +1170,7 @@ function toId() {
|
|||
break;
|
||||
|
||||
case 'nametaken':
|
||||
app.addPopup(LoginPopup, {name: parts[1] || '', error: parts[2] || ''});
|
||||
app.addPopup(LoginPopup, { name: parts[1] || '', error: parts[2] || '' });
|
||||
break;
|
||||
|
||||
case 'queryresponse':
|
||||
|
|
@ -1258,7 +1259,7 @@ function toId() {
|
|||
case 'chat':
|
||||
if (parts[1] === '~') {
|
||||
if (parts[2].substr(0, 6) === '/warn ') {
|
||||
app.addPopup(RulesPopup, {warning: parts[2].substr(6)});
|
||||
app.addPopup(RulesPopup, { warning: parts[2].substr(6) });
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -1322,8 +1323,8 @@ function toId() {
|
|||
var column = 0;
|
||||
var columnChanged = false;
|
||||
|
||||
window.NonBattleGames = {rps: 'Rock Paper Scissors'};
|
||||
for (var i = 3; i <= 9; i = i + 2) {
|
||||
window.NonBattleGames = { rps: 'Rock Paper Scissors' };
|
||||
for (var i = 3; i <= 9; i += 2) {
|
||||
window.NonBattleGames['bestof' + i] = 'Best-of-' + i;
|
||||
}
|
||||
window.BattleFormats = {};
|
||||
|
|
@ -1473,7 +1474,7 @@ function toId() {
|
|||
if (silent) return;
|
||||
var sData = data.split(':');
|
||||
if (sData[0] === 'success') {
|
||||
app.addPopup(ReplayUploadedPopup, {id: sData[1] || id});
|
||||
app.addPopup(ReplayUploadedPopup, { id: sData[1] || id });
|
||||
} else if (data === 'hash mismatch') {
|
||||
app.addPopupMessage("Someone else is already uploading a replay of this battle. Try again in five seconds.");
|
||||
} else if (data === 'not found') {
|
||||
|
|
@ -1603,7 +1604,7 @@ function toId() {
|
|||
*/
|
||||
unjoinRoom: function (id, reason) {
|
||||
this.removeRoom(id, true);
|
||||
if (this.curRoom) this.navigate(this.curRoom.id, {replace: true});
|
||||
if (this.curRoom) this.navigate(this.curRoom.id, { replace: true });
|
||||
this.updateAutojoin();
|
||||
},
|
||||
tryJoinRoom: function (id) {
|
||||
|
|
@ -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
|
||||
|
|
@ -2068,7 +2066,7 @@ function toId() {
|
|||
} else {
|
||||
if (Config.server.id !== 'showdown') {
|
||||
// Switch to the autojoin object to handle multiple servers
|
||||
curAutojoin = {showdown: curAutojoin};
|
||||
curAutojoin = { showdown: curAutojoin };
|
||||
if (!autojoins.length) return;
|
||||
curAutojoin[Config.server.id] = autojoins.join(',');
|
||||
} else {
|
||||
|
|
@ -2142,19 +2140,19 @@ function toId() {
|
|||
addPopupMessage: function (message) {
|
||||
// shorthand for adding a popup message
|
||||
// this is the equivalent of alert(message)
|
||||
app.addPopup(Popup, {message: message});
|
||||
app.addPopup(Popup, { message: message });
|
||||
},
|
||||
addPopupPrompt: function (message, buttonOrCallback, callback) {
|
||||
var button = (callback ? buttonOrCallback : 'OK');
|
||||
callback = (!callback ? buttonOrCallback : callback);
|
||||
app.addPopup(PromptPopup, {message: message, button: button, callback: callback});
|
||||
app.addPopup(PromptPopup, { message: message, button: button, callback: callback });
|
||||
},
|
||||
closePopup: function (id) {
|
||||
if (this.popups.length) {
|
||||
var popup = this.popups.pop();
|
||||
if (popup.lastFocusedEl && popup.lastFocusedEl.focus) popup.lastFocusedEl.focus();
|
||||
popup.remove();
|
||||
if (this.reconnectPending) this.addPopup(ReconnectPopup, {message: this.reconnectPending});
|
||||
if (this.reconnectPending) this.addPopup(ReconnectPopup, { message: this.reconnectPending });
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -2229,9 +2227,9 @@ function toId() {
|
|||
*/
|
||||
selectformat: function (value, target) {
|
||||
var format = value || 'gen9randombattle';
|
||||
app.addPopup(FormatPopup, {format: format, sourceEl: target, selectType: 'watch', onselect: function (newFormat) {
|
||||
app.addPopup(FormatPopup, { format: format, sourceEl: target, selectType: 'watch', onselect: function (newFormat) {
|
||||
target.value = newFormat;
|
||||
}});
|
||||
} });
|
||||
},
|
||||
|
||||
copyText: function (value, target) {
|
||||
|
|
@ -2256,14 +2254,14 @@ function toId() {
|
|||
this.leftWidth = 0;
|
||||
switch (position) {
|
||||
case 'left':
|
||||
this.$el.css({left: 0, width: leftWidth, right: 'auto'});
|
||||
this.$el.css({ left: 0, width: leftWidth, right: 'auto' });
|
||||
break;
|
||||
case 'right':
|
||||
this.$el.css({left: leftWidth + 1, width: 'auto', right: 0});
|
||||
this.$el.css({ left: leftWidth + 1, width: 'auto', right: 0 });
|
||||
this.leftWidth = leftWidth;
|
||||
break;
|
||||
case 'full':
|
||||
this.$el.css({left: 0, width: 'auto', right: 0});
|
||||
this.$el.css({ left: 0, width: 'auto', right: 0 });
|
||||
break;
|
||||
}
|
||||
this.$el.show();
|
||||
|
|
@ -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
|
||||
|
|
@ -2330,9 +2328,9 @@ function toId() {
|
|||
};
|
||||
if (Dex.prefs('temporarynotifications')) {
|
||||
if (notification.cancel) {
|
||||
setTimeout(function () {notification.cancel();}, 5000);
|
||||
setTimeout(function () { notification.cancel(); }, 5000);
|
||||
} else if (notification.close) {
|
||||
setTimeout(function () {notification.close();}, 5000);
|
||||
setTimeout(function () { notification.close(); }, 5000);
|
||||
}
|
||||
}
|
||||
if (once) notification.psAutoclose = true;
|
||||
|
|
@ -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
|
||||
});
|
||||
}
|
||||
}, {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 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 () {
|
||||
|
|
|
|||
|
|
@ -209,7 +209,7 @@
|
|||
var ability = this.engine.dex.abilities.get(id);
|
||||
return this.renderAbilityRow(ability, matchStart, matchLength, errorMessage, attrs);
|
||||
case 'type':
|
||||
var type = {name: id[0].toUpperCase() + id.substr(1)};
|
||||
var type = { name: id[0].toUpperCase() + id.substr(1) };
|
||||
return this.renderTypeRow(type, matchStart, matchLength, errorMessage);
|
||||
case 'egggroup':
|
||||
// very hardcode
|
||||
|
|
@ -223,7 +223,7 @@
|
|||
} else {
|
||||
egName = id[0].toUpperCase() + id.substr(1);
|
||||
}
|
||||
var egggroup = {name: egName};
|
||||
var egggroup = { name: egName };
|
||||
return this.renderEggGroupRow(egggroup, matchStart, matchLength, errorMessage);
|
||||
case 'tier':
|
||||
// very hardcode
|
||||
|
|
@ -246,14 +246,14 @@
|
|||
publ: "PUBL",
|
||||
zubl: "ZUBL"
|
||||
};
|
||||
var tier = {name: tierTable[id]};
|
||||
var tier = { name: tierTable[id] };
|
||||
return this.renderTierRow(tier, matchStart, matchLength, errorMessage);
|
||||
case 'category':
|
||||
var category = {name: id[0].toUpperCase() + id.substr(1), id: id};
|
||||
var category = { name: id[0].toUpperCase() + id.substr(1), id: id };
|
||||
return this.renderCategoryRow(category, matchStart, matchLength, errorMessage);
|
||||
case 'article':
|
||||
var articleTitle = (window.BattleArticleTitles && BattleArticleTitles[id]) || (id[0].toUpperCase() + id.substr(1));
|
||||
var article = {name: articleTitle, id: id};
|
||||
var article = { name: articleTitle, id: id };
|
||||
return this.renderArticleRow(article, matchStart, matchLength, errorMessage);
|
||||
}
|
||||
return 'Error: not found';
|
||||
|
|
|
|||
|
|
@ -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,16 +183,15 @@ 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) {
|
||||
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
|
||||
case g: h = (b - r) / d + 2; break;
|
||||
case b: h = (r - g) / d + 4; break;
|
||||
}
|
||||
h /= 6;
|
||||
}
|
||||
var d = max - min;
|
||||
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||||
switch (max) {
|
||||
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
|
||||
case g: h = (b - r) / d + 2; break;
|
||||
case b: h = (r - g) / d + 4; break;
|
||||
}
|
||||
h /= 6;
|
||||
return '' + (h * 360) + ',' + (s * 100) + '%';
|
||||
}
|
||||
};
|
||||
|
|
@ -304,7 +303,7 @@ var updatePrefs = function () {
|
|||
var oldShowjoins = Storage.prefs('showjoins');
|
||||
if (oldShowjoins !== undefined && typeof oldShowjoins !== 'object') {
|
||||
var showjoins = {};
|
||||
var serverShowjoins = {global: (oldShowjoins ? 1 : 0)};
|
||||
var serverShowjoins = { global: (oldShowjoins ? 1 : 0) };
|
||||
var showroomjoins = Storage.prefs('showroomjoins');
|
||||
for (var roomid in showroomjoins) {
|
||||
serverShowjoins[roomid] = (showroomjoins[roomid] ? 1 : 0);
|
||||
|
|
@ -538,7 +537,7 @@ Storage.initTestClient = function () {
|
|||
data.sid = sid;
|
||||
get(uri, data, callback, type);
|
||||
} else {
|
||||
app.addPopup(ProxyPopup, {uri: uri, callback: callback});
|
||||
app.addPopup(ProxyPopup, { uri: uri, callback: callback });
|
||||
}
|
||||
};
|
||||
var post = $.post;
|
||||
|
|
@ -546,7 +545,7 @@ Storage.initTestClient = function () {
|
|||
if (type === 'html') {
|
||||
uri += '&testclient';
|
||||
}
|
||||
if (uri[0] === '/') { //relative URI
|
||||
if (uri[0] === '/') { // relative URI
|
||||
uri = Dex.resourcePrefix + uri.substr(1);
|
||||
}
|
||||
|
||||
|
|
@ -560,7 +559,7 @@ Storage.initTestClient = function () {
|
|||
src += '<input type=hidden name="' + i + '" value="' + BattleLog.escapeHTML(data[i]) + '">';
|
||||
}
|
||||
src += '<input type=submit value="Please click this button first."></form></body></html>';
|
||||
app.addPopup(ProxyPopup, {uri: "data:text/html;charset=UTF-8," + encodeURIComponent(src), callback: callback});
|
||||
app.addPopup(ProxyPopup, { uri: "data:text/html;charset=UTF-8," + encodeURIComponent(src), callback: callback });
|
||||
}
|
||||
};
|
||||
Storage.whenPrefsLoaded.load();
|
||||
|
|
@ -622,7 +621,7 @@ Storage.compareTeams = function (serverTeam, localTeam) {
|
|||
};
|
||||
|
||||
Storage.loadRemoteTeams = function (after) {
|
||||
$.get(app.user.getActionPHP(), {act: 'getteams'}, Storage.safeJSON(function (data) {
|
||||
$.get(app.user.getActionPHP(), { act: 'getteams' }, Storage.safeJSON(function (data) {
|
||||
if (data.actionerror) {
|
||||
return app.addPopupMessage('Error loading uploaded teams: ' + data.actionerror);
|
||||
}
|
||||
|
|
@ -652,7 +651,7 @@ Storage.loadRemoteTeams = function (after) {
|
|||
// team comes down from loginserver as comma-separated list of mons
|
||||
// to save bandwidth
|
||||
var mons = team.team.split(',').map(function (mon) {
|
||||
return {species: mon};
|
||||
return { species: mon };
|
||||
});
|
||||
team.team = Storage.packTeam(mons);
|
||||
Storage.teams.unshift(team);
|
||||
|
|
@ -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 += '|';
|
||||
|
|
@ -938,7 +937,7 @@ Storage.fastUnpackTeam = function (buf) {
|
|||
spe: Number(evs[5]) || 0
|
||||
};
|
||||
} else if (evstring === '0') {
|
||||
set.evs = {hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0};
|
||||
set.evs = { hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0 };
|
||||
}
|
||||
}
|
||||
i = j + 1;
|
||||
|
|
@ -1025,7 +1024,7 @@ Storage.unpackTeam = function (buf) {
|
|||
j = buf.indexOf('|', i);
|
||||
var ability = Dex.abilities.get(buf.substring(i, j)).name;
|
||||
var species = Dex.species.get(set.species);
|
||||
set.ability = (species.abilities && ability in {'':1, 0:1, 1:1, H:1} ? species.abilities[ability || '0'] : ability);
|
||||
set.ability = (species.abilities && ability in { '': 1, 0: 1, 1: 1, H: 1 } ? species.abilities[ability || '0'] : ability);
|
||||
i = j + 1;
|
||||
|
||||
// moves
|
||||
|
|
@ -1056,7 +1055,7 @@ Storage.unpackTeam = function (buf) {
|
|||
spe: Number(evs[5]) || 0
|
||||
};
|
||||
} else if (evstring === '0') {
|
||||
set.evs = {hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0};
|
||||
set.evs = { hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0 };
|
||||
}
|
||||
}
|
||||
i = j + 1;
|
||||
|
|
@ -1239,7 +1238,7 @@ Storage.importTeam = function (buffer, teams) {
|
|||
curSet = null;
|
||||
teams.push(Storage.unpackLine(line));
|
||||
} else if (!curSet) {
|
||||
curSet = {name: '', species: '', gender: ''};
|
||||
curSet = { name: '', species: '', gender: '' };
|
||||
team.push(curSet);
|
||||
var atIndex = line.lastIndexOf(' @ ');
|
||||
if (atIndex !== -1) {
|
||||
|
|
@ -1296,7 +1295,7 @@ Storage.importTeam = function (buffer, teams) {
|
|||
} else if (line.substr(0, 5) === 'EVs: ') {
|
||||
line = line.substr(5);
|
||||
var evLines = line.split('/');
|
||||
curSet.evs = {hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0};
|
||||
curSet.evs = { hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0 };
|
||||
for (var j = 0; j < evLines.length; j++) {
|
||||
var evLine = $.trim(evLines[j]);
|
||||
var spaceIndex = evLine.indexOf(' ');
|
||||
|
|
@ -1309,7 +1308,7 @@ Storage.importTeam = function (buffer, teams) {
|
|||
} else if (line.substr(0, 5) === 'IVs: ') {
|
||||
line = line.substr(5);
|
||||
var ivLines = line.split(' / ');
|
||||
curSet.ivs = {hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31};
|
||||
curSet.ivs = { hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31 };
|
||||
for (var j = 0; j < ivLines.length; j++) {
|
||||
var ivLine = ivLines[j];
|
||||
var spaceIndex = ivLine.indexOf(' ');
|
||||
|
|
@ -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;
|
||||
|
|
@ -1509,7 +1508,7 @@ Storage.initDirectory = function () {
|
|||
var self = this;
|
||||
|
||||
var dir = process.env.HOME || process.env.USERPROFILE || process.env.HOMEPATH;
|
||||
if (!(dir.charAt(dir.length - 1) in {'/': 1, '\\': 1})) dir += '/';
|
||||
if (!(dir.charAt(dir.length - 1) in { '/': 1, '\\': 1 })) dir += '/';
|
||||
fs.stat(dir + 'Documents', function (err, stats) {
|
||||
if (err || !stats.isDirectory()) {
|
||||
fs.stat(dir + 'My Documents', function (err, stats) {
|
||||
|
|
@ -1860,7 +1859,7 @@ Storage.nwLogChat = function (roomid, line) {
|
|||
var timestamp = '[' + hours + ':' + minutes + '] ';
|
||||
|
||||
if (!this.chatLogStreams[roomid]) {
|
||||
this.chatLogStreams[roomid] = fs.createWriteStream(this.dir + 'Logs/' + chatLogFdMonth + '/' + roomid + '.txt', {flags: 'a'});
|
||||
this.chatLogStreams[roomid] = fs.createWriteStream(this.dir + 'Logs/' + chatLogFdMonth + '/' + roomid + '.txt', { flags: 'a' });
|
||||
this.chatLogStreams[roomid].write('\n\n\nLog starting ' + now + '\n\n');
|
||||
}
|
||||
this.chatLogStreams[roomid].write(timestamp + line + '\n');
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -11,15 +11,15 @@
|
|||
* @license MIT
|
||||
*/
|
||||
|
||||
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 {Dex, toID, type ID, type SpriteData} from './battle-dex';
|
||||
import {BattleNatures} from './battle-dex-data';
|
||||
import {BattleTooltips} from './battle-tooltips';
|
||||
import {BattleTextParser, type Args, type KWArgs} from './battle-text-parser';
|
||||
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 { 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';
|
||||
import { BattleTextParser, type Args, type KWArgs } from './battle-text-parser';
|
||||
|
||||
/*
|
||||
|
||||
|
|
@ -74,14 +74,14 @@ export class BattleScene implements BattleSceneStub {
|
|||
$tooltips: JQuery = null!;
|
||||
tooltips: BattleTooltips;
|
||||
|
||||
sideConditions: [{[id: string]: Sprite[]}, {[id: string]: Sprite[]}] = [{}, {}];
|
||||
sideConditions: [{ [id: string]: Sprite[] }, { [id: string]: Sprite[] }] = [{}, {}];
|
||||
|
||||
preloadDone = 0;
|
||||
preloadNeeded = 0;
|
||||
bgm: BattleBGM | null = null;
|
||||
backdropImage: string = '';
|
||||
backdropImage = '';
|
||||
bgmNum = 0;
|
||||
preloadCache: {[url: string]: HTMLImageElement} = {};
|
||||
preloadCache: { [url: string]: HTMLImageElement } = {};
|
||||
|
||||
messagebarOpen = false;
|
||||
customControls = false;
|
||||
|
|
@ -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;
|
||||
|
|
@ -279,18 +279,18 @@ export class BattleScene implements BattleSceneStub {
|
|||
if (!end.scale && end.scale !== 0 && start.scale) end.scale = start.scale;
|
||||
if (!end.xscale && end.xscale !== 0 && start.xscale) end.xscale = start.xscale;
|
||||
if (!end.yscale && end.yscale !== 0 && start.yscale) end.yscale = start.yscale;
|
||||
end = {...start, ...end};
|
||||
end = { ...start, ...end };
|
||||
|
||||
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();
|
||||
|
||||
if (start.time) {
|
||||
$effect.css({...startpos, opacity: 0});
|
||||
$effect.css({ ...startpos, opacity: 0 });
|
||||
$effect.delay(start.time).animate({
|
||||
opacity: startpos.opacity,
|
||||
}, 1);
|
||||
|
|
@ -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})`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -596,7 +596,7 @@ export class BattleScene implements BattleSceneStub {
|
|||
let name = pokemon.side?.isFar &&
|
||||
(this.battle.ignoreOpponent || this.battle.ignoreNicks) ? pokemon.speciesForme : pokemon.name;
|
||||
if (name !== pokemon.speciesForme) {
|
||||
name += ' (' + pokemon.speciesForme + ')';
|
||||
name += ' (' + pokemon.speciesForme + ')';
|
||||
}
|
||||
if (pokemon === pokemon.side.active[0]) {
|
||||
name += ' (active)';
|
||||
|
|
@ -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];
|
||||
|
|
@ -802,17 +802,17 @@ export class BattleScene implements BattleSceneStub {
|
|||
const tooltips = this.battle.gameType === 'freeforall' ? {
|
||||
// FFA battles are visually rendered as triple battle with the center slots empty
|
||||
// so we swap the 2nd and 3rd tooltips on each side
|
||||
p2b: {top: 70, left: 250, width: 80, height: 100, tooltip: 'activepokemon|1|1'},
|
||||
p2a: {top: 90, left: 390, width: 100, height: 100, tooltip: 'activepokemon|1|0'},
|
||||
p1a: {top: 200, left: 130, width: 120, height: 160, tooltip: 'activepokemon|0|0'},
|
||||
p1b: {top: 200, left: 350, width: 150, height: 160, tooltip: 'activepokemon|0|1'},
|
||||
p2b: { top: 70, left: 250, width: 80, height: 100, tooltip: 'activepokemon|1|1' },
|
||||
p2a: { top: 90, left: 390, width: 100, height: 100, tooltip: 'activepokemon|1|0' },
|
||||
p1a: { top: 200, left: 130, width: 120, height: 160, tooltip: 'activepokemon|0|0' },
|
||||
p1b: { top: 200, left: 350, width: 150, height: 160, tooltip: 'activepokemon|0|1' },
|
||||
} : {
|
||||
p2c: {top: 70, left: 250, width: 80, height: 100, tooltip: 'activepokemon|1|2'},
|
||||
p2b: {top: 85, left: 320, width: 90, height: 100, tooltip: 'activepokemon|1|1'},
|
||||
p2a: {top: 90, left: 390, width: 100, height: 100, tooltip: 'activepokemon|1|0'},
|
||||
p1a: {top: 200, left: 130, width: 120, height: 160, tooltip: 'activepokemon|0|0'},
|
||||
p1b: {top: 200, left: 250, width: 150, height: 160, tooltip: 'activepokemon|0|1'},
|
||||
p1c: {top: 200, left: 350, width: 150, height: 160, tooltip: 'activepokemon|0|2'},
|
||||
p2c: { top: 70, left: 250, width: 80, height: 100, tooltip: 'activepokemon|1|2' },
|
||||
p2b: { top: 85, left: 320, width: 90, height: 100, tooltip: 'activepokemon|1|1' },
|
||||
p2a: { top: 90, left: 390, width: 100, height: 100, tooltip: 'activepokemon|1|0' },
|
||||
p1a: { top: 200, left: 130, width: 120, height: 160, tooltip: 'activepokemon|0|0' },
|
||||
p1b: { top: 200, left: 250, width: 150, height: 160, tooltip: 'activepokemon|0|1' },
|
||||
p1c: { top: 200, left: 350, width: 150, height: 160, tooltip: 'activepokemon|0|2' },
|
||||
};
|
||||
for (const id in tooltips) {
|
||||
let layout = tooltips[id as 'p1a'];
|
||||
|
|
@ -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 '';
|
||||
|
|
@ -951,7 +951,7 @@ export class BattleScene implements BattleSceneStub {
|
|||
let weatherhtml = ``;
|
||||
|
||||
if (this.battle.weather) {
|
||||
const weatherNameTable: {[id: string]: string} = {
|
||||
const weatherNameTable: { [id: string]: string } = {
|
||||
sunnyday: 'Sun',
|
||||
desolateland: 'Intense Sun',
|
||||
raindance: 'Rain',
|
||||
|
|
@ -1030,7 +1030,7 @@ export class BattleScene implements BattleSceneStub {
|
|||
}, this.curWeather ? 300 : 100, () => {
|
||||
this.$weather.html('<em>' + weatherhtml + '</em>');
|
||||
this.$weather.attr('class', weather ? 'weather ' + weather + 'weather' : 'weather');
|
||||
this.$weather.animate({opacity: isIntense || !weather ? 0.9 : 0.5}, 300);
|
||||
this.$weather.animate({ opacity: isIntense || !weather ? 0.9 : 0.5 }, 300);
|
||||
});
|
||||
this.curWeather = weather;
|
||||
} else {
|
||||
|
|
@ -1043,7 +1043,7 @@ export class BattleScene implements BattleSceneStub {
|
|||
opacity: 0,
|
||||
}, this.curTerrain ? 400 : 1, () => {
|
||||
this.$terrain.attr('class', terrain ? 'weather ' + terrain + 'weather' : 'weather');
|
||||
this.$terrain.animate({top: 0, opacity: 1}, 400);
|
||||
this.$terrain.animate({ top: 0, opacity: 1 }, 400);
|
||||
});
|
||||
this.curTerrain = terrain;
|
||||
}
|
||||
|
|
@ -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' : '−' + damage, 'bad');
|
||||
this.resultAnim(pokemon, this.battle.hardcoreMode ? 'Damage' : `−${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,
|
||||
|
|
@ -1685,7 +1685,7 @@ export class BattleScene implements BattleSceneStub {
|
|||
}
|
||||
this.battle = null!;
|
||||
}
|
||||
static getHPColor(pokemon: {hp: number, maxhp: number}) {
|
||||
static getHPColor(pokemon: { hp: number, maxhp: number }) {
|
||||
let ratio = pokemon.hp / pokemon.maxhp;
|
||||
if (ratio > 0.5) return 'g';
|
||||
if (ratio > 0.2) return 'y';
|
||||
|
|
@ -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,17 +1774,17 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
export class PokemonSprite extends Sprite {
|
||||
// HTML strings are constructed from this table and stored back in it to cache them
|
||||
protected static statusTable: {[id: string]: [string, 'good' | 'bad' | 'neutral'] | null | string} = {
|
||||
protected static statusTable: { [id: string]: [string, 'good' | 'bad' | 'neutral'] | null | string } = {
|
||||
formechange: null,
|
||||
typechange: null,
|
||||
typeadd: null,
|
||||
|
|
@ -1921,7 +1921,7 @@ export class PokemonSprite extends Sprite {
|
|||
left = 0;
|
||||
top = 0;
|
||||
|
||||
effects: {[id: string]: Sprite[]} = {};
|
||||
effects: { [id: string]: Sprite[] } = {};
|
||||
|
||||
constructor(spriteData: SpriteData | null, pos: InitScenePos, scene: BattleScene, isFrontSprite: boolean) {
|
||||
super(spriteData, pos, scene);
|
||||
|
|
@ -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) {
|
||||
|
|
@ -2043,14 +2043,14 @@ export class PokemonSprite extends Sprite {
|
|||
}, this.subsp!), 500);
|
||||
|
||||
this.$sub = null;
|
||||
this.anim({time: 500});
|
||||
this.anim({ time: 500 });
|
||||
if (this.scene.animating) this.scene.waitFor(this.$el);
|
||||
}
|
||||
beforeMove() {
|
||||
if (!this.scene.animating) return false;
|
||||
if (!this.isSubActive) return false;
|
||||
this.isSubActive = false;
|
||||
this.anim({time: 300});
|
||||
this.anim({ time: 300 });
|
||||
this.$sub!.animate(this.scene.pos({
|
||||
x: this.leftof(-50),
|
||||
y: this.y,
|
||||
|
|
@ -2082,7 +2082,7 @@ export class PokemonSprite extends Sprite {
|
|||
z: this.behind(30),
|
||||
opacity: 0.3,
|
||||
}, this.sp), 300);
|
||||
this.anim({time: 300});
|
||||
this.anim({ time: 300 });
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
|
@ -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, ' ')}</span> `;
|
||||
return (PokemonSprite.statusTable[id] = `<span class="${effect[1]}">${effect[0].replace(/ /g, ' ')}</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;
|
||||
|
|
@ -2920,9 +2919,9 @@ interface AnimData {
|
|||
prepareAnim?(scene: BattleScene, args: PokemonSprite[]): void;
|
||||
residualAnim?(scene: BattleScene, args: PokemonSprite[]): void;
|
||||
}
|
||||
export type AnimTable = {[k: string]: AnimData};
|
||||
export type AnimTable = { [k: string]: AnimData };
|
||||
|
||||
const BattleEffects: {[k: string]: SpriteData} = {
|
||||
const BattleEffects: { [k: string]: SpriteData } = {
|
||||
wisp: {
|
||||
url: 'wisp.png',
|
||||
w: 100, h: 100,
|
||||
|
|
@ -4266,16 +4265,16 @@ export const BattleOtherAnims: AnimTable = {
|
|||
},
|
||||
shake: {
|
||||
anim(scene, [attacker]) {
|
||||
attacker.anim({x: attacker.x - 10, time: 200});
|
||||
attacker.anim({x: attacker.x + 10, time: 300});
|
||||
attacker.anim({x: attacker.x, time: 200});
|
||||
attacker.anim({ x: attacker.x - 10, time: 200 });
|
||||
attacker.anim({ x: attacker.x + 10, time: 300 });
|
||||
attacker.anim({ x: attacker.x, time: 200 });
|
||||
},
|
||||
},
|
||||
dance: {
|
||||
anim(scene, [attacker]) {
|
||||
attacker.anim({x: attacker.x - 10});
|
||||
attacker.anim({x: attacker.x + 10});
|
||||
attacker.anim({x: attacker.x});
|
||||
attacker.anim({ x: attacker.x - 10 });
|
||||
attacker.anim({ x: attacker.x + 10 });
|
||||
attacker.anim({ x: attacker.x });
|
||||
},
|
||||
},
|
||||
consume: {
|
||||
|
|
@ -6072,11 +6071,11 @@ export const BattleStatusAnims: AnimTable = {
|
|||
anim(scene, [attacker]) {
|
||||
scene.backgroundEffect('#000000', 700, 0.2);
|
||||
attacker.delay(300);
|
||||
attacker.anim({x: attacker.x - 5, time: 50});
|
||||
attacker.anim({x: attacker.x + 5, time: 50});
|
||||
attacker.anim({x: attacker.x - 5, time: 50});
|
||||
attacker.anim({x: attacker.x + 5, time: 50});
|
||||
attacker.anim({x: attacker.x, time: 50});
|
||||
attacker.anim({ x: attacker.x - 5, time: 50 });
|
||||
attacker.anim({ x: attacker.x + 5, time: 50 });
|
||||
attacker.anim({ x: attacker.x - 5, time: 50 });
|
||||
attacker.anim({ x: attacker.x + 5, time: 50 });
|
||||
attacker.anim({ x: attacker.x, time: 50 });
|
||||
|
||||
scene.showEffect(attacker.sp, {
|
||||
x: attacker.x,
|
||||
|
|
@ -6172,4 +6171,4 @@ export const BattleStatusAnims: AnimTable = {
|
|||
},
|
||||
},
|
||||
};
|
||||
BattleStatusAnims['focuspunch'] = {anim: BattleStatusAnims['flinch'].anim};
|
||||
BattleStatusAnims['focuspunch'] = { anim: BattleStatusAnims['flinch'].anim };
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@
|
|||
* @license MIT
|
||||
*/
|
||||
|
||||
import type {Battle, ServerPokemon} from "./battle";
|
||||
import {Dex, toID, type ID} from "./battle-dex";
|
||||
import type { Battle, ServerPokemon } from "./battle";
|
||||
import { Dex, toID, type ID } from "./battle-dex";
|
||||
|
||||
export interface BattleRequestSideInfo {
|
||||
name: string;
|
||||
|
|
@ -281,7 +281,7 @@ export class BattleChoiceBuilder {
|
|||
|
||||
const index = this.choices.length;
|
||||
|
||||
if (choice === 'shift') return {choiceType: 'shift'};
|
||||
if (choice === 'shift') return { choiceType: 'shift' };
|
||||
|
||||
if (choice.startsWith('move ')) {
|
||||
if (request.requestType !== 'move') {
|
||||
|
|
@ -397,7 +397,7 @@ export class BattleChoiceBuilder {
|
|||
const choiceid = toID(choice);
|
||||
let matchLevel = 0;
|
||||
let match = 0;
|
||||
for (let i = 0 ; i < request.side.pokemon.length; i++) {
|
||||
for (let i = 0; i < request.side.pokemon.length; i++) {
|
||||
const serverPokemon = request.side.pokemon[i];
|
||||
let curMatchLevel = 0;
|
||||
if (choice === serverPokemon.name) {
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,19 +14,19 @@
|
|||
* @license MIT
|
||||
*/
|
||||
|
||||
import {Dex, toID} from "./battle-dex";
|
||||
import { Dex, toID } from "./battle-dex";
|
||||
|
||||
/**
|
||||
* String that contains only lowercase alphanumeric characters.
|
||||
*/
|
||||
export type ID = string & {__isID: true};
|
||||
export type ID = string & { __isID: true };
|
||||
|
||||
export interface Nature {
|
||||
plus?: StatNameExceptHP;
|
||||
minus?: StatNameExceptHP;
|
||||
}
|
||||
|
||||
export const BattleNatures: {[k in NatureName]: Nature} = {
|
||||
export const BattleNatures: { [k in NatureName]: Nature } = {
|
||||
Adamant: {
|
||||
plus: 'atk',
|
||||
minus: 'spa',
|
||||
|
|
@ -113,7 +113,7 @@ export const BattleNatures: {[k in NatureName]: Nature} = {
|
|||
minus: 'atk',
|
||||
},
|
||||
};
|
||||
export const BattleStatIDs: {[k: string]: StatName | undefined} = {
|
||||
export const BattleStatIDs: { [k: string]: StatName | undefined } = {
|
||||
HP: 'hp',
|
||||
hp: 'hp',
|
||||
Atk: 'atk',
|
||||
|
|
@ -148,7 +148,7 @@ export const BattleBaseSpeciesChart = [
|
|||
"unown", "burmy", "shellos", "gastrodon", "deerling", "sawsbuck", "vivillon", "flabebe", "floette", "florges", "furfrou", "minior", "alcremie", "tatsugiri", "pokestarufo", "pokestarbrycenman", "pokestarmt", "pokestarmt2", "pokestartransport", "pokestargiant", "pokestarhumanoid", "pokestarmonster", "pokestarf00", "pokestarf002", "pokestarspirit", "pokestarblackdoor", "pokestarwhitedoor", "pokestarblackbelt",
|
||||
] as ID[];
|
||||
|
||||
export const BattlePokemonIconIndexes: {[id: string]: number} = {
|
||||
export const BattlePokemonIconIndexes: { [id: string]: number } = {
|
||||
// alt forms
|
||||
egg: 1032 + 1,
|
||||
pikachubelle: 1032 + 2,
|
||||
|
|
@ -628,7 +628,7 @@ export const BattlePokemonIconIndexes: {[id: string]: number} = {
|
|||
draggalong: 1512 + 77,
|
||||
};
|
||||
|
||||
export const BattlePokemonIconIndexesLeft: {[id: string]: number} = {
|
||||
export const BattlePokemonIconIndexesLeft: { [id: string]: number } = {
|
||||
pikachubelle: 1404 + 0,
|
||||
pikachupopstar: 1404 + 1,
|
||||
clefairy: 1404 + 2,
|
||||
|
|
@ -738,7 +738,7 @@ export const BattlePokemonIconIndexesLeft: {[id: string]: number} = {
|
|||
blacephalon: 1404 + 105,
|
||||
};
|
||||
|
||||
export const BattleAvatarNumbers: {[k: string]: string} = {
|
||||
export const BattleAvatarNumbers: { [k: string]: string } = {
|
||||
1: 'lucas',
|
||||
2: 'dawn',
|
||||
3: 'youngster-gen4dp',
|
||||
|
|
@ -1240,10 +1240,10 @@ export class Move implements Effect {
|
|||
readonly zMove?: {
|
||||
basePower?: number,
|
||||
effect?: string,
|
||||
boost?: {[stat in StatName]?: number},
|
||||
boost?: { [stat in StatName]?: number },
|
||||
};
|
||||
readonly isMax: boolean | string;
|
||||
readonly maxMove: {basePower: number};
|
||||
readonly maxMove: { basePower: number };
|
||||
readonly ohko: true | 'Ice' | null;
|
||||
readonly recoil: number[] | null;
|
||||
readonly heal: number[] | null;
|
||||
|
|
@ -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) {
|
||||
|
|
@ -1291,43 +1291,43 @@ export class Move implements Effect {
|
|||
this.secondaries = data.secondaries || (data.secondary ? [data.secondary] : null);
|
||||
|
||||
this.isMax = data.isMax || false;
|
||||
this.maxMove = data.maxMove || {basePower: 0};
|
||||
this.maxMove = data.maxMove || { basePower: 0 };
|
||||
if (this.category !== 'Status' && !this.maxMove?.basePower) {
|
||||
if (this.isZ || this.isMax) {
|
||||
this.maxMove = {basePower: 1};
|
||||
this.maxMove = { basePower: 1 };
|
||||
} else if (!this.basePower) {
|
||||
this.maxMove = {basePower: 100};
|
||||
this.maxMove = { basePower: 100 };
|
||||
} else if (['Fighting', 'Poison'].includes(this.type)) {
|
||||
if (this.basePower >= 150) {
|
||||
this.maxMove = {basePower: 100};
|
||||
this.maxMove = { basePower: 100 };
|
||||
} else if (this.basePower >= 110) {
|
||||
this.maxMove = {basePower: 95};
|
||||
this.maxMove = { basePower: 95 };
|
||||
} else if (this.basePower >= 75) {
|
||||
this.maxMove = {basePower: 90};
|
||||
this.maxMove = { basePower: 90 };
|
||||
} else if (this.basePower >= 65) {
|
||||
this.maxMove = {basePower: 85};
|
||||
this.maxMove = { basePower: 85 };
|
||||
} else if (this.basePower >= 55) {
|
||||
this.maxMove = {basePower: 80};
|
||||
this.maxMove = { basePower: 80 };
|
||||
} else if (this.basePower >= 45) {
|
||||
this.maxMove = {basePower: 75};
|
||||
} else {
|
||||
this.maxMove = {basePower: 70};
|
||||
this.maxMove = { basePower: 75 };
|
||||
} else {
|
||||
this.maxMove = { basePower: 70 };
|
||||
}
|
||||
} else {
|
||||
if (this.basePower >= 150) {
|
||||
this.maxMove = {basePower: 150};
|
||||
this.maxMove = { basePower: 150 };
|
||||
} else if (this.basePower >= 110) {
|
||||
this.maxMove = {basePower: 140};
|
||||
this.maxMove = { basePower: 140 };
|
||||
} else if (this.basePower >= 75) {
|
||||
this.maxMove = {basePower: 130};
|
||||
this.maxMove = { basePower: 130 };
|
||||
} else if (this.basePower >= 65) {
|
||||
this.maxMove = {basePower: 120};
|
||||
this.maxMove = { basePower: 120 };
|
||||
} else if (this.basePower >= 55) {
|
||||
this.maxMove = {basePower: 110};
|
||||
this.maxMove = { basePower: 110 };
|
||||
} else if (this.basePower >= 45) {
|
||||
this.maxMove = {basePower: 100};
|
||||
} else {
|
||||
this.maxMove = {basePower: 90};
|
||||
this.maxMove = { basePower: 100 };
|
||||
} else {
|
||||
this.maxMove = { basePower: 90 };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}>;
|
||||
|
|
@ -1482,21 +1482,21 @@ export class Species implements Effect {
|
|||
readonly heightm: number;
|
||||
readonly gender: GenderName;
|
||||
readonly color: string;
|
||||
readonly genderRatio: Readonly<{M: number, F: number}> | null;
|
||||
readonly eggGroups: ReadonlyArray<string>;
|
||||
readonly tags: ReadonlyArray<string>;
|
||||
readonly genderRatio: Readonly<{ M: number, F: number }> | null;
|
||||
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,15 +1521,15 @@ 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;
|
||||
this.types = data.types || ['???'];
|
||||
this.abilities = data.abilities || {0: "No Ability"};
|
||||
this.baseStats = data.baseStats || {hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0};
|
||||
this.abilities = data.abilities || { 0: "No Ability" };
|
||||
this.baseStats = data.baseStats || { hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0 };
|
||||
this.bst = this.baseStats.hp + this.baseStats.atk + this.baseStats.def +
|
||||
this.baseStats.spa + this.baseStats.spd + this.baseStats.spe;
|
||||
this.weightkg = data.weightkg || 0;
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
|
@ -552,8 +552,8 @@ abstract class BattleTypedSearch<T extends SearchType> {
|
|||
set: Dex.PokemonSet | null = null;
|
||||
|
||||
protected formatType: 'doubles' | 'bdsp' | 'bdspdoubles' | 'bw1' | 'letsgo' | 'metronome' | 'natdex' | 'nfe' |
|
||||
'ssdlc1' | 'ssdlc1doubles' | 'predlc' | 'predlcdoubles' | 'predlcnatdex' | 'svdlc1' | 'svdlc1doubles' |
|
||||
'svdlc1natdex' | 'stadium' | 'lc' | null = null;
|
||||
'ssdlc1' | 'ssdlc1doubles' | 'predlc' | 'predlcdoubles' | 'predlcnatdex' | 'svdlc1' | 'svdlc1doubles' |
|
||||
'svdlc1natdex' | 'stadium' | 'lc' | null = null;
|
||||
|
||||
/**
|
||||
* Cached copy of what the results list would be with only base filters
|
||||
|
|
@ -565,7 +565,7 @@ abstract class BattleTypedSearch<T extends SearchType> {
|
|||
* is wondering why a specific result isn't showing up.
|
||||
*/
|
||||
baseIllegalResults: SearchRow[] | null = null;
|
||||
illegalReasons: {[id: string]: string} | null = null;
|
||||
illegalReasons: { [id: string]: string } | null = null;
|
||||
results: SearchRow[] | null = null;
|
||||
|
||||
protected readonly sortRow: SearchRow | null = null;
|
||||
|
|
@ -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') {
|
||||
|
|
@ -687,7 +687,7 @@ abstract class BattleTypedSearch<T extends SearchType> {
|
|||
}
|
||||
|
||||
if (!this.baseIllegalResults) {
|
||||
const legalityFilter: {[id: string]: 1} = {};
|
||||
const legalityFilter: { [id: string]: 1 } = {};
|
||||
for (const [resultType, value] of this.baseResults) {
|
||||
if (resultType === this.searchType) legalityFilter[value] = 1;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -825,7 +825,7 @@ abstract class BattleTypedSearch<T extends SearchType> {
|
|||
if (learnset && (moveid in learnset) && (!this.format.startsWith('tradebacks') ? learnset[moveid].includes(genChar) :
|
||||
learnset[moveid].includes(genChar) || (learnset[moveid].includes(`${gen + 1}`) && move.gen === gen)) &&
|
||||
(!eggMovesOnly || (learnset[moveid].includes('e') && this.dex.gen === 9))
|
||||
) {
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
learnsetid = this.nextLearnsetid(learnsetid, speciesid, true);
|
||||
|
|
@ -849,14 +849,14 @@ abstract class BattleTypedSearch<T extends SearchType> {
|
|||
this.formatType === 'ssdlc1doubles' ? 'gen8dlc1doubles' :
|
||||
this.formatType === 'predlc' ? 'gen9predlc' :
|
||||
this.formatType === 'predlcdoubles' ? 'gen9predlcdoubles' :
|
||||
this.formatType === 'predlcnatdex' ? 'gen9predlcnatdex' :
|
||||
this.formatType === 'predlcnatdex' ? 'gen9predlcnatdex' :
|
||||
this.formatType === 'svdlc1' ? 'gen9dlc1' :
|
||||
this.formatType === 'svdlc1doubles' ? 'gen9dlc1doubles' :
|
||||
this.formatType === 'svdlc1natdex' ? 'gen9dlc1natdex' :
|
||||
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);
|
||||
|
|
@ -884,7 +884,7 @@ abstract class BattleTypedSearch<T extends SearchType> {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
abstract getTable(): {[id: string]: any};
|
||||
abstract getTable(): { [id: string]: any };
|
||||
abstract getDefaultResults(): SearchRow[];
|
||||
abstract getBaseResults(): SearchRow[];
|
||||
abstract filter(input: SearchRow, filters: string[][]): boolean;
|
||||
|
|
@ -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) {
|
||||
|
|
@ -1019,7 +1019,7 @@ class BattlePokemonSearch extends BattleTypedSearch<'pokemon'> {
|
|||
table.tiers = null;
|
||||
}
|
||||
let tierSet: SearchRow[] = table.tierSet;
|
||||
let slices: {[k: string]: number} = table.formatSlices;
|
||||
let slices: { [k: string]: number } = table.formatSlices;
|
||||
if (format === 'ubers' || format === 'uber' || format === 'ubersuu' || format === 'nationaldexdoubles') {
|
||||
tierSet = tierSet.slice(slices.Uber);
|
||||
} else if (isVGCOrBS || (isHackmons && dex.gen === 9 && !this.formatType)) {
|
||||
|
|
@ -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'];
|
||||
|
|
@ -1649,7 +1651,7 @@ class BattleMoveSearch extends BattleTypedSearch<'move'> {
|
|||
for (let moveid in learnset) {
|
||||
let learnsetEntry = learnset[moveid];
|
||||
const move = dex.moves.get(moveid);
|
||||
const minGenCode: {[gen: number]: string} = {6: 'p', 7: 'q', 8: 'g', 9: 'a'};
|
||||
const minGenCode: { [gen: number]: string } = { 6: 'p', 7: 'q', 8: 'g', 9: 'a' };
|
||||
if (regionBornLegality && !learnsetEntry.includes(minGenCode[dex.gen])) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -1819,7 +1821,7 @@ class BattleMoveSearch extends BattleTypedSearch<'move'> {
|
|||
const sortOrder = reverseSort ? -1 : 1;
|
||||
switch (sortCol) {
|
||||
case 'power':
|
||||
let powerTable: {[id: string]: number | undefined} = {
|
||||
let powerTable: { [id: string]: number | undefined } = {
|
||||
return: 102, frustration: 102, spitup: 300, trumpcard: 200, naturalgift: 80, grassknot: 120,
|
||||
lowkick: 120, gyroball: 150, electroball: 150, flail: 200, reversal: 200, present: 120,
|
||||
wringout: 120, crushgrip: 120, heatcrash: 120, heavyslam: 120, fling: 130, magnitude: 150,
|
||||
|
|
@ -1861,7 +1863,7 @@ class BattleMoveSearch extends BattleTypedSearch<'move'> {
|
|||
|
||||
class BattleCategorySearch extends BattleTypedSearch<'category'> {
|
||||
getTable() {
|
||||
return {physical: 1, special: 1, status: 1};
|
||||
return { physical: 1, special: 1, status: 1 };
|
||||
}
|
||||
getDefaultResults(): SearchRow[] {
|
||||
return [
|
||||
|
|
|
|||
|
|
@ -18,16 +18,15 @@
|
|||
* @license MIT
|
||||
*/
|
||||
|
||||
import {Pokemon, type ServerPokemon} from "./battle";
|
||||
import { Pokemon, type ServerPokemon } from "./battle";
|
||||
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;
|
||||
|
|
@ -46,7 +45,7 @@ export declare namespace Dex {
|
|||
export type GenderName = DexData.GenderName;
|
||||
export type NatureName = DexData.NatureName;
|
||||
export type MoveTarget = DexData.MoveTarget;
|
||||
export type StatsTable = {hp: number, atk: number, def: number, spa: number, spd: number, spe: number};
|
||||
export type StatsTable = { hp: number, atk: number, def: number, spa: number, spd: number, spe: number };
|
||||
/**
|
||||
* Dex.PokemonSet can be sparse, in which case that entry should be
|
||||
* inferred from the rest of the set, according to sensible
|
||||
|
|
@ -87,10 +86,11 @@ export declare namespace Dex {
|
|||
teraType?: string;
|
||||
}
|
||||
}
|
||||
export type {ID};
|
||||
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,14 +109,14 @@ 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) {
|
||||
return toID(text);
|
||||
}
|
||||
|
||||
type Comparable = number | string | boolean | Comparable[] | {reverse: Comparable};
|
||||
type Comparable = number | string | boolean | Comparable[] | { reverse: Comparable };
|
||||
export const PSUtils = new class {
|
||||
/**
|
||||
* Like string.split(delimiter), but only recognizes the first `limit`
|
||||
|
|
@ -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);
|
||||
|
|
@ -174,9 +173,9 @@ export const PSUtils = new class {
|
|||
return 0;
|
||||
}
|
||||
if (a.reverse) {
|
||||
return PSUtils.compare((b as {reverse: string}).reverse, 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;
|
||||
|
||||
|
|
@ -266,8 +265,8 @@ export const Dex = new class implements ModdedDex {
|
|||
return `${protocol}//${window.Config ? Config.routes.client : 'play.pokemonshowdown.com'}/fx/`;
|
||||
})();
|
||||
|
||||
loadedSpriteData = {xy: 1, bw: 0};
|
||||
moddedDexes: {[mod: string]: ModdedDex} = {};
|
||||
loadedSpriteData = { xy: 1, bw: 0 };
|
||||
moddedDexes: { [mod: string]: ModdedDex } = {};
|
||||
|
||||
mod(modid: ID): ModdedDex {
|
||||
if (modid === 'gen9') return this;
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -379,7 +378,7 @@ export const Dex = new class implements ModdedDex {
|
|||
};
|
||||
}
|
||||
|
||||
if (!data) data = {exists: false};
|
||||
if (!data) data = { exists: false };
|
||||
let move = new Move(id, name, data);
|
||||
window.BattleMovedex[id] = move;
|
||||
return move;
|
||||
|
|
@ -407,7 +406,7 @@ export const Dex = new class implements ModdedDex {
|
|||
if (!window.BattleItems) window.BattleItems = {};
|
||||
let data = window.BattleItems[id];
|
||||
if (data && typeof data.exists === 'boolean') return data;
|
||||
if (!data) data = {exists: false};
|
||||
if (!data) data = { exists: false };
|
||||
let item = new Item(id, name, data);
|
||||
window.BattleItems[id] = item;
|
||||
return item;
|
||||
|
|
@ -429,7 +428,7 @@ export const Dex = new class implements ModdedDex {
|
|||
if (!window.BattleAbilities) window.BattleAbilities = {};
|
||||
let data = window.BattleAbilities[id];
|
||||
if (data && typeof data.exists === 'boolean') return data;
|
||||
if (!data) data = {exists: false};
|
||||
if (!data) data = { exists: false };
|
||||
let ability = new Ability(id, name, data);
|
||||
window.BattleAbilities[id] = ability;
|
||||
return ability;
|
||||
|
|
@ -465,8 +464,8 @@ export const Dex = new class implements ModdedDex {
|
|||
if (data && typeof data.exists === 'boolean') {
|
||||
species = data;
|
||||
} else {
|
||||
if (!data) data = {exists: false};
|
||||
if (!data.tier && id.slice(-5) === 'totem') {
|
||||
if (!data) data = { exists: false };
|
||||
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;
|
||||
|
|
@ -557,7 +555,7 @@ export const Dex = new class implements ModdedDex {
|
|||
noScale?: boolean,
|
||||
mod?: string,
|
||||
dynamax?: boolean,
|
||||
} = {gen: 6}) {
|
||||
} = { gen: 6 }) {
|
||||
const mechanicsGen = options.gen || 6;
|
||||
let isDynamax = !!options.dynamax;
|
||||
if (pokemon instanceof Pokemon) {
|
||||
|
|
@ -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) {
|
||||
|
|
@ -945,7 +944,7 @@ export class ModdedDex {
|
|||
}
|
||||
if (this.cache.Moves.hasOwnProperty(id)) return this.cache.Moves[id];
|
||||
|
||||
let data = {...Dex.moves.get(name)};
|
||||
let data = { ...Dex.moves.get(name) };
|
||||
|
||||
for (let i = Dex.gen - 1; i >= this.gen; i--) {
|
||||
const table = window.BattleTeambuilderTable[`gen${i}`];
|
||||
|
|
@ -978,7 +977,7 @@ export class ModdedDex {
|
|||
}
|
||||
if (this.cache.Items.hasOwnProperty(id)) return this.cache.Items[id];
|
||||
|
||||
let data = {...Dex.items.get(name)};
|
||||
let data = { ...Dex.items.get(name) };
|
||||
|
||||
for (let i = Dex.gen - 1; i >= this.gen; i--) {
|
||||
const table = window.BattleTeambuilderTable[`gen${i}`];
|
||||
|
|
@ -1008,7 +1007,7 @@ export class ModdedDex {
|
|||
}
|
||||
if (this.cache.Abilities.hasOwnProperty(id)) return this.cache.Abilities[id];
|
||||
|
||||
let data = {...Dex.abilities.get(name)};
|
||||
let data = { ...Dex.abilities.get(name) };
|
||||
|
||||
for (let i = Dex.gen - 1; i >= this.gen; i--) {
|
||||
const table = window.BattleTeambuilderTable[`gen${i}`];
|
||||
|
|
@ -1038,7 +1037,7 @@ export class ModdedDex {
|
|||
}
|
||||
if (this.cache.Species.hasOwnProperty(id)) return this.cache.Species[id];
|
||||
|
||||
let data = {...Dex.species.get(name)};
|
||||
let data = { ...Dex.species.get(name) };
|
||||
|
||||
for (let i = Dex.gen - 1; i >= this.gen; i--) {
|
||||
const table = window.BattleTeambuilderTable[`gen${i}`];
|
||||
|
|
@ -1053,12 +1052,12 @@ export class ModdedDex {
|
|||
}
|
||||
}
|
||||
if (this.gen < 3 || this.modid === 'gen7letsgo') {
|
||||
data.abilities = {0: "No Ability"};
|
||||
data.abilities = { 0: "No Ability" };
|
||||
}
|
||||
|
||||
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,22 +1073,22 @@ 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];
|
||||
|
||||
let data = {...Dex.types.get(name)};
|
||||
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
|
||||
break;
|
||||
}
|
||||
if (id in table.overrideTypeChart) {
|
||||
data = {...data, ...table.overrideTypeChart[id]};
|
||||
data = { ...data, ...table.overrideTypeChart[id] };
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -1174,7 +1173,7 @@ export const Teams = new class {
|
|||
spe: Number(evs[5]) || 0,
|
||||
};
|
||||
} else if (evstring === '0') {
|
||||
set.evs = {hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0};
|
||||
set.evs = { hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0 };
|
||||
}
|
||||
}
|
||||
i = j + 1;
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,10 +13,10 @@
|
|||
* @license MIT
|
||||
*/
|
||||
|
||||
import type {Battle} from './battle';
|
||||
import type {BattleScene} from './battle-animations';
|
||||
import {Dex, Teams, toID, toRoomid, toUserid, type ID} from './battle-dex';
|
||||
import {BattleTextParser, type Args, type KWArgs} from './battle-text-parser';
|
||||
import type { Battle } from './battle';
|
||||
import type { BattleScene } from './battle-animations';
|
||||
import { Dex, Teams, toID, toRoomid, toUserid, type ID } from './battle-dex';
|
||||
import { BattleTextParser, type Args, type KWArgs } from './battle-text-parser';
|
||||
|
||||
// Caja
|
||||
declare const html4: any;
|
||||
|
|
@ -25,7 +25,7 @@ declare const html: any;
|
|||
// defined in battle-log-misc
|
||||
declare function MD5(input: string): string;
|
||||
declare function formatText(input: string, isTrusted?: boolean): string;
|
||||
export {MD5, formatText};
|
||||
export { MD5, formatText };
|
||||
|
||||
export class BattleLog {
|
||||
elem: HTMLDivElement;
|
||||
|
|
@ -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;
|
||||
});
|
||||
|
|
@ -550,7 +552,7 @@ export class BattleLog {
|
|||
return str.replace(/"/g, '"').replace(/>/g, '>').replace(/</g, '<').replace(/&/g, '&');
|
||||
}
|
||||
|
||||
static colorCache: {[userid: string]: string} = {};
|
||||
static colorCache: { [userid: string]: string } = {};
|
||||
|
||||
/** @deprecated */
|
||||
static hashColor(name: ID) {
|
||||
|
|
@ -569,12 +571,12 @@ export class BattleLog {
|
|||
let S = parseInt(hash.substr(0, 4), 16) % 50 + 40; // 40 to 89
|
||||
let L = Math.floor(parseInt(hash.substr(8, 4), 16) % 20 + 30); // 30 to 49
|
||||
|
||||
let {R, G, B} = this.HSLToRGB(H, S, L);
|
||||
let { R, G, B } = this.HSLToRGB(H, S, L);
|
||||
let lum = R * R * R * 0.2126 + G * G * G * 0.7152 + B * B * B * 0.0722; // 0.013 (dark blue) to 0.737 (yellow)
|
||||
|
||||
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));
|
||||
|
|
@ -584,7 +586,7 @@ export class BattleLog {
|
|||
|
||||
L += HLmod;
|
||||
|
||||
let {R: r, G: g, B: b} = this.HSLToRGB(H, S, L);
|
||||
let { R: r, G: g, B: b } = this.HSLToRGB(H, S, L);
|
||||
const toHex = (x: number) => {
|
||||
const hex = Math.round(x * 255).toString(16);
|
||||
return hex.length === 1 ? '0' + hex : hex;
|
||||
|
|
@ -612,14 +614,15 @@ export class BattleLog {
|
|||
let R = R1 + m;
|
||||
let G = G1 + m;
|
||||
let B = B1 + m;
|
||||
return {R, G, B};
|
||||
return { R, G, B };
|
||||
}
|
||||
|
||||
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,8 +939,8 @@ export class BattleLog {
|
|||
if (Number(height) < 200) {
|
||||
height = window.innerWidth >= 400 ? '225' : '200';
|
||||
}
|
||||
const videoId = /(?:\?v=|\/embed\/)([A-Za-z0-9_\-]+)/.exec(src)?.[1];
|
||||
if (!videoId) return {tagName: 'img', attribs: ['alt', `invalid src for <youtube>`]};
|
||||
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];
|
||||
this.players.push(null);
|
||||
|
|
@ -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') {
|
||||
|
|
@ -1026,7 +1029,7 @@ export class BattleLog {
|
|||
setAttrib('rel', 'noopener');
|
||||
}
|
||||
}
|
||||
return {tagName, attribs};
|
||||
return { tagName, attribs };
|
||||
};
|
||||
}
|
||||
static localizeTime(full: string, date: string, time: string, timezone?: string) {
|
||||
|
|
@ -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(() => {
|
||||
|
|
@ -1164,7 +1167,7 @@ export class BattleLog {
|
|||
// This allows pretty much anything about the replay viewer to be
|
||||
// updated as desired.
|
||||
|
||||
static createReplayFile(room: {battle: Battle, id?: string, fragment?: string}) {
|
||||
static createReplayFile(room: { battle: Battle, id?: string, fragment?: string }) {
|
||||
let battle = room.battle;
|
||||
let replayid = room.id;
|
||||
if (replayid) {
|
||||
|
|
@ -1208,10 +1211,12 @@ export class BattleLog {
|
|||
return buf;
|
||||
}
|
||||
|
||||
static createReplayFileHref(room: {battle: Battle, id?: string, fragment?: string}) {
|
||||
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))));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,36 +1,36 @@
|
|||
import type {Pokemon, Side} from './battle';
|
||||
import type {ScenePos, PokemonSprite} from './battle-animations';
|
||||
import type {BattleLog} from './battle-log';
|
||||
import type {ID} from './battle-dex';
|
||||
import type {Args, KWArgs} from './battle-text-parser';
|
||||
import type { Pokemon, Side } from './battle';
|
||||
import type { ScenePos, PokemonSprite } from './battle-animations';
|
||||
import type { BattleLog } from './battle-log';
|
||||
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;
|
||||
log: BattleLog = {add: (args: Args, kwargs?: KWArgs) => {}} as any;
|
||||
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 { }
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@
|
|||
*/
|
||||
|
||||
import preact from "../js/lib/preact";
|
||||
import {Dex, type ID} from "./battle-dex";
|
||||
import type {DexSearch, SearchRow} from "./battle-dex-search";
|
||||
import { Dex, type ID } from "./battle-dex";
|
||||
import type { DexSearch, SearchRow } from "./battle-dex-search";
|
||||
|
||||
export class PSSearchResults extends preact.Component<{search: DexSearch}> {
|
||||
export class PSSearchResults extends preact.Component<{ search: DexSearch }> {
|
||||
readonly URL_ROOT = `//${Config.routes.dex}/`;
|
||||
|
||||
renderPokemonSortRow() {
|
||||
|
|
@ -57,7 +57,23 @@ 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">
|
||||
<span style={Dex.getPokemonIcon(pokemon.id)}></span>
|
||||
</span>
|
||||
|
||||
<span class="col pokemonnamecol">{this.renderName(pokemon.name, matchStart, matchEnd, tagStart)}</span>
|
||||
|
||||
{errorMessage}
|
||||
</a>
|
||||
</li>;
|
||||
}
|
||||
|
||||
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">
|
||||
|
|
@ -66,47 +82,43 @@ export class PSSearchResults extends preact.Component<{search: DexSearch}> {
|
|||
|
||||
<span class="col pokemonnamecol">{this.renderName(pokemon.name, matchStart, matchEnd, tagStart)}</span>
|
||||
|
||||
{errorMessage}
|
||||
</a></li>;
|
||||
}
|
||||
<span class="col typecol">
|
||||
{pokemon.types.map(type =>
|
||||
<img src={`${Dex.resourcePrefix}sprites/types/${type}.png`} alt={type} height="14" width="32" class="pixelated" />
|
||||
)}
|
||||
</span>
|
||||
|
||||
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">
|
||||
<span style={Dex.getPokemonIcon(pokemon.id)}></span>
|
||||
</span>
|
||||
|
||||
<span class="col pokemonnamecol">{this.renderName(pokemon.name, matchStart, matchEnd, tagStart)}</span>
|
||||
|
||||
<span class="col typecol">
|
||||
{pokemon.types.map(type =>
|
||||
<img src={`${Dex.resourcePrefix}sprites/types/${type}.png`} alt={type} height="14" width="32" class="pixelated" />
|
||||
{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>
|
||||
) : (
|
||||
<span class="col abilitycol"></span>
|
||||
)
|
||||
)}
|
||||
</span>
|
||||
|
||||
{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>
|
||||
:
|
||||
<span class="col abilitycol"></span>
|
||||
)}
|
||||
|
||||
<span class="col statcol"><em>HP</em><br />{stats.hp}</span>
|
||||
<span class="col statcol"><em>Atk</em><br />{stats.atk}</span>
|
||||
<span class="col statcol"><em>Def</em><br />{stats.def}</span>
|
||||
{search.dex.gen > 2 && <span class="col statcol"><em>SpA</em><br />{stats.spa}</span>}
|
||||
{search.dex.gen > 2 && <span class="col statcol"><em>SpD</em><br />{stats.spd}</span>}
|
||||
{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>;
|
||||
<span class="col statcol"><em>HP</em><br />{stats.hp}</span>
|
||||
<span class="col statcol"><em>Atk</em><br />{stats.atk}</span>
|
||||
<span class="col statcol"><em>Def</em><br />{stats.def}</span>
|
||||
{search.dex.gen > 2 && <span class="col statcol"><em>SpA</em><br />{stats.spa}</span>}
|
||||
{search.dex.gen > 2 && <span class="col statcol"><em>SpD</em><br />{stats.spd}</span>}
|
||||
{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>;
|
||||
}
|
||||
|
||||
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}`}>
|
||||
<span class="col namecol">{this.renderName(ability.name, matchStart, matchEnd)}</span>
|
||||
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}
|
||||
|
||||
{!errorMessage && <span class="col abilitydesccol">{ability.shortDesc}</span>}
|
||||
</a></li>;
|
||||
{!errorMessage && <span class="col abilitydesccol">{ability.shortDesc}</span>}
|
||||
</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,25 +250,24 @@ 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}`}>
|
||||
<span class="col namecol">{this.renderName(name, matchStart, matchEnd)}</span>
|
||||
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">
|
||||
<img src={`${Dex.resourcePrefix}sprites/categories/${name}.png`} alt={name} height="14" width="32" class="pixelated" />
|
||||
</span>
|
||||
<span class="col typecol">
|
||||
<img src={`${Dex.resourcePrefix}sprites/categories/${name}.png`} alt={name} height="14" width="32" class="pixelated" />
|
||||
</span>
|
||||
|
||||
{errorMessage}
|
||||
</a></li>;
|
||||
{errorMessage}
|
||||
</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,19 +291,20 @@ 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}`}>
|
||||
<span class="col namecol">{this.renderName(name, matchStart, matchEnd)}</span>
|
||||
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>
|
||||
<span class="col movedesccol">(egg group)</span>
|
||||
|
||||
{errorMessage}
|
||||
</a></li>;
|
||||
{errorMessage}
|
||||
</a>
|
||||
</li>;
|
||||
}
|
||||
|
||||
renderTierRow(id: ID, matchStart: number, matchEnd: number, errorMessage?: preact.ComponentChildren) {
|
||||
const search = this.props.search;
|
||||
// very hardcode
|
||||
const tierTable: {[id: string]: string} = {
|
||||
const tierTable: { [id: string]: string } = {
|
||||
uber: "Uber",
|
||||
caplc: "CAP LC",
|
||||
capnfe: "CAP NFE",
|
||||
|
|
@ -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>;
|
||||
}
|
||||
|
||||
|
|
@ -326,7 +344,7 @@ export class PSSearchResults extends preact.Component<{search: DexSearch}> {
|
|||
.replace(/<em>/g, '<em>').replace(/<\/em>/g, '</em>')
|
||||
.replace(/<strong>/g, '<strong>').replace(/<\/strong>/g, '</strong>');
|
||||
return <li class="result">
|
||||
<p dangerouslySetInnerHTML={{__html: sanitizedHTML}}></p>
|
||||
<p dangerouslySetInnerHTML={{ __html: sanitizedHTML }}></p>
|
||||
</li>;
|
||||
case 'header':
|
||||
return <li class="result"><h3>{id}</h3></li>;
|
||||
|
|
@ -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);
|
||||
case 'tier':
|
||||
return this.renderTierRow(id as ID, matchStart, matchEnd, errorMessage);
|
||||
return this.renderEggGroupRow(id, matchStart, matchEnd, errorMessage);
|
||||
case 'tier':
|
||||
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)
|
||||
)}
|
||||
{
|
||||
// TODO: implement windowing
|
||||
// for now, just show first twenty results
|
||||
search.results?.slice(0, 20).map(result => this.renderRow(result))
|
||||
}
|
||||
</ul>;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import {PS} from "./client-main";
|
||||
import { PS } from "./client-main";
|
||||
|
||||
export class BattleBGM {
|
||||
/**
|
||||
|
|
@ -102,7 +102,7 @@ export class BattleBGM {
|
|||
}
|
||||
|
||||
export const BattleSound = new class {
|
||||
soundCache: {[url: string]: HTMLAudioElement | undefined} = {};
|
||||
soundCache: { [url: string]: HTMLAudioElement | undefined } = {};
|
||||
|
||||
bgm: BattleBGM[] = [];
|
||||
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@
|
|||
* @license MIT
|
||||
*/
|
||||
|
||||
import {toID, type ID} from "./battle-dex";
|
||||
import { toID, type ID } from "./battle-dex";
|
||||
|
||||
export type Args = [string, ...string[]];
|
||||
export type KWArgs = {[kw: string]: string};
|
||||
export type KWArgs = { [kw: string]: string };
|
||||
export type SideID = 'p1' | 'p2' | 'p3' | 'p4';
|
||||
|
||||
export class BattleTextParser {
|
||||
|
|
@ -64,22 +64,22 @@ export class BattleTextParser {
|
|||
return line.slice(1).split('|') as [string, ...string[]];
|
||||
}
|
||||
|
||||
static parseBattleLine(line: string): {args: Args, kwArgs: KWArgs} {
|
||||
static parseBattleLine(line: string): { args: Args, kwArgs: KWArgs } {
|
||||
let args = this.parseLine(line, true);
|
||||
if (args) return {args, kwArgs: {}};
|
||||
if (args) return { args, kwArgs: {} };
|
||||
|
||||
args = line.slice(1).split('|') as [string, ...string[]];
|
||||
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
|
||||
kwArgs[lastArg.slice(1, bracketPos)] = lastArg.slice(bracketPos + 1).trim() || '.';
|
||||
args.pop();
|
||||
}
|
||||
return BattleTextParser.upgradeArgs({args, kwArgs});
|
||||
return BattleTextParser.upgradeArgs({ args, kwArgs });
|
||||
}
|
||||
|
||||
static parseNameParts(text: string) {
|
||||
|
|
@ -102,7 +102,7 @@ export class BattleTextParser {
|
|||
status = status.slice(1);
|
||||
}
|
||||
}
|
||||
return {group, name, away, status};
|
||||
return { group, name, away, status };
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -110,19 +110,19 @@ export class BattleTextParser {
|
|||
* them to modern versions. Used to keep battle.ts itself cleaner. Not
|
||||
* guaranteed to mutate or not mutate its inputs.
|
||||
*/
|
||||
static upgradeArgs({args, kwArgs}: {args: Args, kwArgs: KWArgs}): {args: Args, kwArgs: KWArgs} {
|
||||
static upgradeArgs({ args, kwArgs }: { args: Args, kwArgs: KWArgs }): { args: Args, kwArgs: KWArgs } {
|
||||
switch (args[0]) {
|
||||
case '-activate': {
|
||||
if (kwArgs.item || kwArgs.move || kwArgs.number || kwArgs.ability) return {args, kwArgs};
|
||||
if (kwArgs.item || kwArgs.move || kwArgs.number || kwArgs.ability) return { args, kwArgs };
|
||||
let [, pokemon, effect, arg3, arg4] = args;
|
||||
let target = kwArgs.of;
|
||||
const id = BattleTextParser.effectId(effect);
|
||||
|
||||
if (kwArgs.block) return {args: ['-fail', pokemon], kwArgs};
|
||||
if (kwArgs.block) return { args: ['-fail', pokemon], kwArgs };
|
||||
|
||||
if (id === 'wonderguard') return {args: ['-immune', pokemon], kwArgs: {from: 'ability:Wonder Guard'}};
|
||||
if (id === 'wonderguard') return { args: ['-immune', pokemon], kwArgs: { from: 'ability:Wonder Guard' } };
|
||||
|
||||
if (id === 'beatup' && kwArgs.of) return {args, kwArgs: {name: kwArgs.of}};
|
||||
if (id === 'beatup' && kwArgs.of) return { args, kwArgs: { name: kwArgs.of } };
|
||||
|
||||
if ([
|
||||
'ingrain', 'quickguard', 'wideguard', 'craftyshield', 'matblock', 'protect', 'mist', 'safeguard',
|
||||
|
|
@ -131,22 +131,22 @@ export class BattleTextParser {
|
|||
].includes(id)) {
|
||||
if (target) {
|
||||
kwArgs.of = pokemon;
|
||||
return {args: ['-block', target, effect, arg3], kwArgs};
|
||||
return { args: ['-block', target, effect, arg3], kwArgs };
|
||||
}
|
||||
return {args: ['-block', pokemon, effect, arg3], kwArgs};
|
||||
return { args: ['-block', pokemon, effect, arg3], kwArgs };
|
||||
}
|
||||
|
||||
if (id === 'charge') {
|
||||
return {args: ['-singlemove', pokemon, effect], kwArgs: {of: target}};
|
||||
return { args: ['-singlemove', pokemon, effect], kwArgs: { of: target } };
|
||||
}
|
||||
if ([
|
||||
'bind', 'wrap', 'clamp', 'whirlpool', 'firespin', 'magmastorm', 'sandtomb', 'infestation', 'snaptrap', 'thundercage', 'trapped',
|
||||
].includes(id)) {
|
||||
return {args: ['-start', pokemon, effect], kwArgs: {of: target}};
|
||||
return { args: ['-start', pokemon, effect], kwArgs: { of: target } };
|
||||
}
|
||||
|
||||
if (id === 'fairylock') {
|
||||
return {args: ['-fieldactivate', effect], kwArgs: {}};
|
||||
return { args: ['-fieldactivate', effect], kwArgs: {} };
|
||||
}
|
||||
|
||||
if (id === 'symbiosis' || id === 'poltergeist') {
|
||||
|
|
@ -168,7 +168,7 @@ export class BattleTextParser {
|
|||
|
||||
case '-fail': {
|
||||
if (kwArgs.from === 'ability: Flower Veil') {
|
||||
return {args: ['-block', kwArgs.of, 'ability: Flower Veil'], kwArgs: {of: args[1]}};
|
||||
return { args: ['-block', kwArgs.of, 'ability: Flower Veil'], kwArgs: { of: args[1] } };
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -187,7 +187,7 @@ export class BattleTextParser {
|
|||
let [, pokemon, effect, move] = args;
|
||||
if (['ability: Damp', 'ability: Dazzling', 'ability: Queenly Majesty', 'ability: Armor Tail'].includes(effect)) {
|
||||
args[0] = '-block';
|
||||
return {args: ['-block', pokemon, effect, move, kwArgs.of], kwArgs: {}};
|
||||
return { args: ['-block', pokemon, effect, move, kwArgs.of], kwArgs: {} };
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -211,15 +211,15 @@ export class BattleTextParser {
|
|||
case '-nothing':
|
||||
// OLD: |-nothing
|
||||
// NEW: |-activate||move:Splash
|
||||
return {args: ['-activate', '', 'move:Splash'], kwArgs};
|
||||
return { args: ['-activate', '', 'move:Splash'], kwArgs };
|
||||
}
|
||||
return {args, kwArgs};
|
||||
return { args, kwArgs };
|
||||
}
|
||||
|
||||
extractMessage(buf: string) {
|
||||
let out = '';
|
||||
for (const line of buf.split('\n')) {
|
||||
const {args, kwArgs} = BattleTextParser.parseBattleLine(line);
|
||||
const { args, kwArgs } = BattleTextParser.parseBattleLine(line);
|
||||
out += this.parseArgs(args, kwArgs) || '';
|
||||
}
|
||||
return out;
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,12 +8,12 @@
|
|||
* @license MIT
|
||||
*/
|
||||
|
||||
import {Pokemon, type Battle, type ServerPokemon} from "./battle";
|
||||
import {Dex, ModdedDex, toID, type ID} from "./battle-dex";
|
||||
import type {BattleScene} from "./battle-animations";
|
||||
import {BattleLog} from "./battle-log";
|
||||
import {BattleNatures} from "./battle-dex-data";
|
||||
import {BattleTextParser} from "./battle-text-parser";
|
||||
import { Pokemon, type Battle, type ServerPokemon } from "./battle";
|
||||
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";
|
||||
import { BattleTextParser } from "./battle-text-parser";
|
||||
|
||||
class ModifiableValue {
|
||||
value = 0;
|
||||
|
|
@ -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);
|
||||
|
|
@ -359,9 +359,9 @@ export class BattleTooltips {
|
|||
// let side = this.battle.mySide.ally;
|
||||
let activeIndex = parseInt(args[1], 10);
|
||||
let pokemon = null;
|
||||
/*if (activeIndex < side.pokemon.length) {
|
||||
/* if (activeIndex < side.pokemon.length) {
|
||||
pokemon = side.pokemon[activeIndex] || side.ally ? side.ally.pokemon[activeIndex] : null;
|
||||
}*/
|
||||
} */
|
||||
let serverPokemon = this.battle.myAllyPokemon ? this.battle.myAllyPokemon[activeIndex] : null;
|
||||
buf = this.showPokemonTooltip(pokemon, serverPokemon);
|
||||
break;
|
||||
|
|
@ -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;
|
||||
|
|
@ -461,7 +461,7 @@ export class BattleTooltips {
|
|||
BattleTooltips.hideTooltip();
|
||||
}
|
||||
|
||||
static zMoveEffects: {[zEffect: string]: string} = {
|
||||
static zMoveEffects: { [zEffect: string]: string } = {
|
||||
'clearnegativeboost': "Restores negative stat stages to 0",
|
||||
'crit2': "Crit ratio +2",
|
||||
'heal': "Restores HP 100%",
|
||||
|
|
@ -476,15 +476,14 @@ 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;
|
||||
}
|
||||
|
||||
static zMoveTable: {[type in Dex.TypeName]: string} = {
|
||||
static zMoveTable: { [type in Dex.TypeName]: string } = {
|
||||
Poison: "Acid Downpour",
|
||||
Fighting: "All-Out Pummeling",
|
||||
Dark: "Black Hole Eclipse",
|
||||
|
|
@ -507,7 +506,7 @@ export class BattleTooltips {
|
|||
"???": "",
|
||||
};
|
||||
|
||||
static maxMoveTable: {[type in Dex.TypeName]: string} = {
|
||||
static maxMoveTable: { [type in Dex.TypeName]: string } = {
|
||||
Poison: "Max Ooze",
|
||||
Fighting: "Max Knuckle",
|
||||
Dark: "Max Darkness",
|
||||
|
|
@ -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 −' + (-move.priority) + ')</em>.</p><p>';
|
||||
text += `Nearly always moves last <em>(priority −${-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) {
|
||||
|
|
@ -995,7 +1000,7 @@ export class BattleTooltips {
|
|||
}
|
||||
|
||||
calculateModifiedStats(clientPokemon: Pokemon | null, serverPokemon: ServerPokemon, statStagesOnly?: boolean) {
|
||||
let stats = {...serverPokemon.stats};
|
||||
let stats = { ...serverPokemon.stats };
|
||||
let pokemon = clientPokemon || serverPokemon;
|
||||
const isPowerTrick = clientPokemon?.volatiles['powertrick'];
|
||||
for (const statName of Dex.statNamesExceptHP) {
|
||||
|
|
@ -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') ||
|
||||
|
|
@ -1188,9 +1195,9 @@ export class BattleTooltips {
|
|||
const isNFE = this.battle.dex.species.get(serverPokemon.speciesForme).evos?.some(evo => {
|
||||
const evoSpecies = this.battle.dex.species.get(evo);
|
||||
return !evoSpecies.isNonstandard ||
|
||||
evoSpecies.isNonstandard === this.battle.dex.species.get(serverPokemon.speciesForme)?.isNonstandard ||
|
||||
// Pokemon with Hisui evolutions
|
||||
evoSpecies.isNonstandard === "Unobtainable";
|
||||
evoSpecies.isNonstandard === this.battle.dex.species.get(serverPokemon.speciesForme)?.isNonstandard ||
|
||||
// Pokemon with Hisui evolutions
|
||||
evoSpecies.isNonstandard === "Unobtainable";
|
||||
});
|
||||
if (item === 'eviolite' && (isNFE || this.battle.dex.species.get(serverPokemon.speciesForme).id === 'dipplin')) {
|
||||
stats.def = Math.floor(stats.def * 1.5);
|
||||
|
|
@ -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 + ' </small>';
|
||||
buf += '' + stats[statName];
|
||||
buf += `${BattleText[statLabel].statShortName} </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 + ' </small>';
|
||||
buf += `${BattleText[statLabel].statShortName} </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">•</span>' : '•';
|
||||
const bullet = moveName.startsWith('*') || move.isZ ? '<span style="color:#888">•</span>' : '•';
|
||||
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;
|
||||
}
|
||||
|
|
@ -1648,7 +1655,7 @@ export class BattleTooltips {
|
|||
// There aren't any max moves with the sound flag, but if there were, Liquid Voice would make them water type
|
||||
const isSound = !!(
|
||||
forMaxMove ?
|
||||
this.getMaxMoveFromType(moveType, forMaxMove !== true && forMaxMove || undefined) : move
|
||||
this.getMaxMoveFromType(moveType, forMaxMove !== true && forMaxMove || undefined) : move
|
||||
).flags['sound'];
|
||||
if (isSound && value.abilityModify(0, 'Liquid Voice')) {
|
||||
moveType = 'Water';
|
||||
|
|
@ -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');
|
||||
|
|
@ -2305,14 +2312,14 @@ export class BattleTooltips {
|
|||
return value;
|
||||
}
|
||||
|
||||
static incenseTypes: {[itemName: string]: Dex.TypeName} = {
|
||||
static incenseTypes: { [itemName: string]: Dex.TypeName } = {
|
||||
'Odd Incense': 'Psychic',
|
||||
'Rock Incense': 'Rock',
|
||||
'Rose Incense': 'Grass',
|
||||
'Sea Incense': 'Water',
|
||||
'Wave Incense': 'Water',
|
||||
};
|
||||
static itemTypes: {[itemName: string]: Dex.TypeName} = {
|
||||
static itemTypes: { [itemName: string]: Dex.TypeName } = {
|
||||
'Black Belt': 'Fighting',
|
||||
'Black Glasses': 'Dark',
|
||||
'Charcoal': 'Fire',
|
||||
|
|
@ -2332,7 +2339,7 @@ export class BattleTooltips {
|
|||
'Spell Tag': 'Ghost',
|
||||
'Twisted Spoon': 'Psychic',
|
||||
};
|
||||
static orbUsers: {[speciesForme: string]: string[]} = {
|
||||
static orbUsers: { [speciesForme: string]: string[] } = {
|
||||
'Latias': ['Soul Dew'],
|
||||
'Latios': ['Soul Dew'],
|
||||
'Dialga': ['Adamant Crystal', 'Adamant Orb'],
|
||||
|
|
@ -2340,7 +2347,7 @@ export class BattleTooltips {
|
|||
'Giratina': ['Griseous Core', 'Griseous Orb'],
|
||||
'Venomicon': ['Vile Vial'],
|
||||
};
|
||||
static orbTypes: {[itemName: string]: Dex.TypeName[]} = {
|
||||
static orbTypes: { [itemName: string]: Dex.TypeName[] } = {
|
||||
'Soul Dew': ['Psychic', 'Dragon'],
|
||||
'Adamant Crystal': ['Steel', 'Dragon'],
|
||||
'Adamant Orb': ['Steel', 'Dragon'],
|
||||
|
|
@ -2399,12 +2406,14 @@ 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')) {
|
||||
value.itemModify(1.2);
|
||||
return value;
|
||||
}
|
||||
(speciesForme.startsWith('Ogerpon-Cornerstone') && itemName === 'Cornerstone Mask')
|
||||
) {
|
||||
value.itemModify(1.2);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
// Gems
|
||||
|
|
@ -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;
|
||||
|
|
@ -2446,7 +2455,7 @@ export class BattleTooltips {
|
|||
return ally.effectiveAbility(serverPokemon);
|
||||
}
|
||||
getPokemonAbilityData(clientPokemon: Pokemon | null, serverPokemon: ServerPokemon | null | undefined) {
|
||||
const abilityData: {ability: string, baseAbility: string, possibilities: string[]} = {
|
||||
const abilityData: { ability: string, baseAbility: string, possibilities: string[] } = {
|
||||
ability: '', baseAbility: '', possibilities: [],
|
||||
};
|
||||
if (clientPokemon) {
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2537,13 +2544,13 @@ class BattleStatGuesser {
|
|||
guess(set: Dex.PokemonSet) {
|
||||
let role = this.guessRole(set);
|
||||
let comboEVs = this.guessEVs(set, role);
|
||||
let evs = {hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0};
|
||||
let evs = { hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0 };
|
||||
for (let stat in evs) {
|
||||
evs[stat as Dex.StatName] = comboEVs[stat as Dex.StatName] || 0;
|
||||
}
|
||||
let plusStat = comboEVs.plusStat || '';
|
||||
let minusStat = comboEVs.minusStat || '';
|
||||
return {role, evs, plusStat, minusStat, moveCount: this.moveCount, hasMove: this.hasMove};
|
||||
return { role, evs, plusStat, minusStat, moveCount: this.moveCount, hasMove: this.hasMove };
|
||||
}
|
||||
guessRole(set: Dex.PokemonSet) {
|
||||
if (!set) return '?';
|
||||
|
|
@ -2569,7 +2576,7 @@ class BattleStatGuesser {
|
|||
'specialBulk': 0,
|
||||
'physicalBulk': 0,
|
||||
};
|
||||
let hasMove: {[moveid: string]: 1} = {};
|
||||
let hasMove: { [moveid: string]: 1 } = {};
|
||||
let itemid = toID(set.item);
|
||||
let item = this.dex.items.get(itemid);
|
||||
let abilityid = toID(set.ability);
|
||||
|
|
@ -2835,7 +2842,7 @@ class BattleStatGuesser {
|
|||
diff -= change;
|
||||
}
|
||||
if (diff <= 0) return evTotal;
|
||||
let evPriority = {def: 1, spd: 1, hp: 1, atk: 1, spa: 1, spe: 1};
|
||||
let evPriority = { def: 1, spd: 1, hp: 1, atk: 1, spa: 1, spe: 1 };
|
||||
let prioStat: Dex.StatName;
|
||||
for (prioStat in evPriority) {
|
||||
if (prioStat === stat) continue;
|
||||
|
|
@ -2857,7 +2864,7 @@ class BattleStatGuesser {
|
|||
}
|
||||
guessEVs(
|
||||
set: Dex.PokemonSet, role: string
|
||||
): Partial<Dex.StatsTable> & {plusStat?: Dex.StatName | '', minusStat?: Dex.StatName | ''} {
|
||||
): Partial<Dex.StatsTable> & { plusStat?: Dex.StatName | '', minusStat?: Dex.StatName | '' } {
|
||||
if (!set) return {};
|
||||
if (role === '?') return {};
|
||||
let species = this.dex.species.get(set.species || set.name!);
|
||||
|
|
@ -2866,13 +2873,13 @@ class BattleStatGuesser {
|
|||
let hasMove = this.hasMove;
|
||||
let moveCount = this.moveCount;
|
||||
|
||||
let evs: Dex.StatsTable & {plusStat?: Dex.StatName | '', minusStat?: Dex.StatName | ''} = {
|
||||
let evs: Dex.StatsTable & { plusStat?: Dex.StatName | '', minusStat?: Dex.StatName | '' } = {
|
||||
hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0,
|
||||
};
|
||||
let plusStat: Dex.StatName | '' = '';
|
||||
let minusStat: Dex.StatName | '' = '';
|
||||
|
||||
let statChart: {[role: string]: [Dex.StatName, Dex.StatName]} = {
|
||||
let statChart: { [role: string]: [Dex.StatName, Dex.StatName] } = {
|
||||
'Bulky Band': ['atk', 'hp'],
|
||||
'Fast Band': ['spe', 'atk'],
|
||||
'Bulky Specs': ['spa', 'hp'],
|
||||
|
|
@ -2906,7 +2913,7 @@ class BattleStatGuesser {
|
|||
|
||||
if (this.supportsAVs) {
|
||||
// Let's Go, AVs enabled
|
||||
evs = {hp: 200, atk: 200, def: 200, spa: 200, spd: 200, spe: 200};
|
||||
evs = { hp: 200, atk: 200, def: 200, spa: 200, spd: 200, spe: 200 };
|
||||
if (!moveCount['PhysicalAttack']) evs.atk = 0;
|
||||
if (!moveCount['SpecialAttack']) evs.spa = 0;
|
||||
if (hasMove['gyroball'] || hasMove['trickroom']) evs.spe = 0;
|
||||
|
|
@ -2915,7 +2922,7 @@ class BattleStatGuesser {
|
|||
// no change
|
||||
} else if (this.ignoreEVLimits) {
|
||||
// Gen 1-2, hackable EVs (like Hackmons)
|
||||
evs = {hp: 252, atk: 252, def: 252, spa: 252, spd: 252, spe: 252};
|
||||
evs = { hp: 252, atk: 252, def: 252, spa: 252, spd: 252, spe: 252 };
|
||||
if (!moveCount['PhysicalAttack']) evs.atk = 0;
|
||||
if (!moveCount['SpecialAttack'] && this.dex.gen > 1) evs.spa = 0;
|
||||
if (hasMove['gyroball'] || hasMove['trickroom']) evs.spe = 0;
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
@ -3160,7 +3166,7 @@ function BattleStatOptimizer(set: Dex.PokemonSet, formatid: ID) {
|
|||
return ev;
|
||||
};
|
||||
|
||||
const origSpread = {evs: set.evs, ...origNature};
|
||||
const origSpread = { evs: set.evs, ...origNature };
|
||||
let origLeftoverEVs = 508;
|
||||
for (const stat of Dex.statNames) {
|
||||
origLeftoverEVs -= origSpread.evs?.[stat] || 0;
|
||||
|
|
@ -3185,12 +3191,12 @@ function BattleStatOptimizer(set: Dex.PokemonSet, formatid: ID) {
|
|||
if (!minusTooLow) {
|
||||
for (const stat of Dex.statNamesExceptHP) {
|
||||
if (origStats[stat] < origStats[bestMinus]) {
|
||||
const minEVs = getMinEVs(stat, {minus: stat});
|
||||
const minEVs = getMinEVs(stat, { minus: stat });
|
||||
if (minEVs > 252) continue;
|
||||
// This number can go negative at this point, but we'll make up for it later (and check to make sure)
|
||||
savedEVs = (origSpread.evs[stat] || 0) - minEVs;
|
||||
if (origNature.minus) {
|
||||
savedEVs += (origSpread.evs[origNature.minus] || 0) - getMinEVs(origNature.minus, {minus: stat});
|
||||
savedEVs += (origSpread.evs[origNature.minus] || 0) - getMinEVs(origNature.minus, { minus: stat });
|
||||
}
|
||||
bestMinus = stat;
|
||||
bestMinusMinEVs = minEVs;
|
||||
|
|
@ -3201,17 +3207,17 @@ function BattleStatOptimizer(set: Dex.PokemonSet, formatid: ID) {
|
|||
for (const stat of Dex.statNamesExceptHP) {
|
||||
// Don't move the plus to an uninvested stat
|
||||
if (stat !== origNature.plus && origSpread.evs[stat] && stat !== bestMinus) {
|
||||
const minEVs = getMinEVs(stat, {plus: stat});
|
||||
const minEVs = getMinEVs(stat, { plus: stat });
|
||||
let plusEVsSaved = (origNature.minus === stat ? getMinEVs(stat, {}) : origSpread.evs[stat] || 0) - minEVs;
|
||||
if (bestPlus && bestPlus !== bestMinus) {
|
||||
plusEVsSaved += bestPlusMinEVs! - getMinEVs(bestPlus, {plus: stat, minus: bestMinus});
|
||||
plusEVsSaved += bestPlusMinEVs! - getMinEVs(bestPlus, { plus: stat, minus: bestMinus });
|
||||
}
|
||||
if (plusEVsSaved > 0 && savedEVs + plusEVsSaved > 0) {
|
||||
savedEVs += plusEVsSaved;
|
||||
bestPlus = stat;
|
||||
bestPlusMinEVs = minEVs;
|
||||
} else if (plusEVsSaved === 0 && (bestPlus || savedEVs > 0) || plusEVsSaved > 0 && savedEVs + plusEVsSaved === 0) {
|
||||
if (!bestPlus || getStat(stat, getMinEVs(stat, {plus: stat}), {plus: stat}) > origStats[stat]) {
|
||||
if (!bestPlus || getStat(stat, getMinEVs(stat, { plus: stat }), { plus: stat }) > origStats[stat]) {
|
||||
savedEVs += plusEVsSaved;
|
||||
bestPlus = stat;
|
||||
bestPlusMinEVs = minEVs;
|
||||
|
|
@ -3226,7 +3232,7 @@ function BattleStatOptimizer(set: Dex.PokemonSet, formatid: ID) {
|
|||
evs: Partial<Dex.StatsTable>,
|
||||
plus?: Dex.StatNameExceptHP,
|
||||
minus?: Dex.StatNameExceptHP,
|
||||
} = {evs: {...origSpread.evs}, plus: bestPlus, minus: bestMinus};
|
||||
} = { evs: { ...origSpread.evs }, plus: bestPlus, minus: bestMinus };
|
||||
if (bestPlus !== origNature.plus || bestMinus !== origNature.minus) {
|
||||
newSpread.evs[bestPlus] = bestPlusMinEVs!;
|
||||
newSpread.evs[bestMinus] = bestMinusMinEVs!;
|
||||
|
|
@ -3239,7 +3245,7 @@ function BattleStatOptimizer(set: Dex.PokemonSet, formatid: ID) {
|
|||
for (const stat of Dex.statNames) {
|
||||
if (!newSpread.evs[stat]) delete newSpread.evs[stat];
|
||||
}
|
||||
return {...newSpread, savedEVs};
|
||||
return { ...newSpread, savedEVs };
|
||||
} else if (!plusTooHigh && !minusTooLow) {
|
||||
if (Math.floor(getStat(bestPlus, bestMinusMinEVs!, newSpread) / 11) <= Math.ceil(origStats[bestMinus] / 9)) {
|
||||
// We're not gaining more points from our plus than we're losing to our minus
|
||||
|
|
@ -3254,7 +3260,7 @@ function BattleStatOptimizer(set: Dex.PokemonSet, formatid: ID) {
|
|||
for (const stat of Dex.statNames) {
|
||||
if (!newSpread.evs[stat]) delete newSpread.evs[stat];
|
||||
}
|
||||
return {...newSpread, savedEVs};
|
||||
return { ...newSpread, savedEVs };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,16 +28,16 @@
|
|||
*/
|
||||
|
||||
// import $ from 'jquery';
|
||||
import {BattleSceneStub} from './battle-scene-stub';
|
||||
import {BattleLog} from './battle-log';
|
||||
import {BattleScene, 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';
|
||||
import { BattleSceneStub } from './battle-scene-stub';
|
||||
import { BattleLog } from './battle-log';
|
||||
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 EffectState = any[] & { 0: ID };
|
||||
export type WeatherState = [name: string, minTimeLeft: number, maxTimeLeft: number];
|
||||
export type HPColor = 'r' | 'y' | 'g';
|
||||
|
||||
export class Pokemon implements PokemonDetails, PokemonHealth {
|
||||
|
|
@ -93,20 +93,20 @@ export class Pokemon implements PokemonDetails, PokemonHealth {
|
|||
itemEffect = '';
|
||||
prevItem = '';
|
||||
prevItemEffect = '';
|
||||
terastallized: string | '' = '';
|
||||
terastallized = '';
|
||||
teraType = '';
|
||||
|
||||
boosts: {[stat: string]: number} = {};
|
||||
boosts: { [stat: string]: number } = {};
|
||||
status: Dex.StatusName | 'tox' | '' | '???' = '';
|
||||
statusStage = 0;
|
||||
volatiles: {[effectid: string]: EffectState} = {};
|
||||
turnstatuses: {[effectid: string]: EffectState} = {};
|
||||
movestatuses: {[effectid: string]: EffectState} = {};
|
||||
volatiles: { [effectid: string]: EffectState } = {};
|
||||
turnstatuses: { [effectid: string]: EffectState } = {};
|
||||
movestatuses: { [effectid: string]: EffectState } = {};
|
||||
lastMove = '';
|
||||
|
||||
/** [[moveName, ppUsed]] */
|
||||
moveTrack: [string, number][] = [];
|
||||
statusData = {sleepTurns: 0, toxicTurns: 0};
|
||||
statusData = { sleepTurns: 0, toxicTurns: 0 };
|
||||
timesAttacked = 0;
|
||||
|
||||
sprite: PokemonSprite;
|
||||
|
|
@ -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) {
|
||||
|
|
@ -765,7 +766,7 @@ export class Side {
|
|||
}
|
||||
if (this.pokemon.length > this.totalPokemon || this.battle.speciesClause) {
|
||||
// check for Illusion
|
||||
let existingTable: {[searchid: string]: number} = {};
|
||||
let existingTable: { [searchid: string]: number } = {};
|
||||
let toRemove = -1;
|
||||
for (let poke1i = 0; poke1i < this.pokemon.length; poke1i++) {
|
||||
let poke1 = this.pokemon[poke1i];
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -871,7 +872,7 @@ export class Side {
|
|||
pokemon.hpcolor = oldpokemon.hpcolor;
|
||||
pokemon.status = oldpokemon.status;
|
||||
pokemon.copyVolatileFrom(oldpokemon, true);
|
||||
pokemon.statusData = {...oldpokemon.statusData};
|
||||
pokemon.statusData = { ...oldpokemon.statusData };
|
||||
if (oldpokemon.terastallized) {
|
||||
pokemon.terastallized = oldpokemon.terastallized;
|
||||
pokemon.teraType = oldpokemon.terastallized;
|
||||
|
|
@ -901,7 +902,7 @@ export class Side {
|
|||
pokemon.removeVolatile('formechange' as ID);
|
||||
}
|
||||
if (!['batonpass', 'zbatonpass', 'shedtail', 'teleport'].includes(effect.id)) {
|
||||
this.battle.log(['switchout', pokemon.ident], {from: effect.id});
|
||||
this.battle.log(['switchout', pokemon.ident], { from: effect.id });
|
||||
}
|
||||
pokemon.statusData.toxicTurns = 0;
|
||||
if (this.battle.gen === 5) pokemon.statusData.sleepTurns = 0;
|
||||
|
|
@ -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,25 +1434,24 @@ 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) {
|
||||
if (side1.sideConditions[id] && side2.sideConditions[id]) {
|
||||
[side1.sideConditions[id], side2.sideConditions[id]] = [
|
||||
side2.sideConditions[id], side1.sideConditions[id],
|
||||
];
|
||||
this.scene.addSideCondition(side1.n, id as ID);
|
||||
this.scene.addSideCondition(side2.n, id as ID);
|
||||
} else if (side1.sideConditions[id] && !side2.sideConditions[id]) {
|
||||
side2.sideConditions[id] = side1.sideConditions[id];
|
||||
this.scene.addSideCondition(side2.n, id as ID);
|
||||
side1.removeSideCondition(id);
|
||||
} else if (side2.sideConditions[id] && !side1.sideConditions[id]) {
|
||||
side1.sideConditions[id] = side2.sideConditions[id];
|
||||
this.scene.addSideCondition(side1.n, id as ID);
|
||||
side2.removeSideCondition(id);
|
||||
}
|
||||
}
|
||||
let side1 = this.sides[0];
|
||||
let side2 = this.sides[1];
|
||||
for (const id of sideConditions) {
|
||||
if (side1.sideConditions[id] && side2.sideConditions[id]) {
|
||||
[side1.sideConditions[id], side2.sideConditions[id]] = [
|
||||
side2.sideConditions[id], side1.sideConditions[id],
|
||||
];
|
||||
this.scene.addSideCondition(side1.n, id as ID);
|
||||
this.scene.addSideCondition(side2.n, id as ID);
|
||||
} else if (side1.sideConditions[id] && !side2.sideConditions[id]) {
|
||||
side2.sideConditions[id] = side1.sideConditions[id];
|
||||
this.scene.addSideCondition(side2.n, id as ID);
|
||||
side1.removeSideCondition(id);
|
||||
} else if (side2.sideConditions[id] && !side1.sideConditions[id]) {
|
||||
side1.sideConditions[id] = side2.sideConditions[id];
|
||||
this.scene.addSideCondition(side1.n, id as ID);
|
||||
side2.removeSideCondition(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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';
|
||||
}
|
||||
|
|
@ -1780,7 +1779,7 @@ export class Battle {
|
|||
break;
|
||||
case 'revivalblessing':
|
||||
this.scene.runResidualAnim('wish' as ID, poke);
|
||||
const {siden} = this.parsePokemonId(args[1]);
|
||||
const { siden } = this.parsePokemonId(args[1]);
|
||||
const side = this.sides[siden];
|
||||
poke.fainted = false;
|
||||
poke.status = '';
|
||||
|
|
@ -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);
|
||||
|
|
@ -2483,7 +2481,7 @@ export class Battle {
|
|||
this.activateAbility(poke, effect);
|
||||
}
|
||||
|
||||
poke.boosts = {...tpoke.boosts};
|
||||
poke.boosts = { ...tpoke.boosts };
|
||||
poke.copyTypesFrom(tpoke, true);
|
||||
poke.ability = tpoke.ability;
|
||||
poke.timesAttacked = tpoke.timesAttacked;
|
||||
|
|
@ -2742,7 +2740,7 @@ export class Battle {
|
|||
break;
|
||||
case 'skydrop':
|
||||
if (kwArgs.interrupt) {
|
||||
this.scene.anim(poke, {time: 100});
|
||||
this.scene.anim(poke, { time: 100 });
|
||||
}
|
||||
break;
|
||||
case 'confusion':
|
||||
|
|
@ -2953,7 +2951,7 @@ export class Battle {
|
|||
case 'gravity':
|
||||
poke.removeVolatile('magnetrise' as ID);
|
||||
poke.removeVolatile('telekinesis' as ID);
|
||||
this.scene.anim(poke, {time: 100});
|
||||
this.scene.anim(poke, { time: 100 });
|
||||
break;
|
||||
case 'skillswap': case 'wanderingspirit':
|
||||
if (this.gen <= 4) break;
|
||||
|
|
@ -3139,7 +3137,8 @@ export class Battle {
|
|||
default: {
|
||||
throw new Error(`Unrecognized minor action: ${args[0]}`);
|
||||
break;
|
||||
}}
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
parseSpriteData(name) {
|
||||
|
|
@ -3256,17 +3255,17 @@ export class Battle {
|
|||
siden = parseInt(name.charAt(1), 10) - 1;
|
||||
name = name.slice(4);
|
||||
} else if (/^p[1-9][a-f]: /.test(name)) {
|
||||
const slotChart: {[k: string]: number} = {a: 0, b: 1, c: 2, d: 3, e: 4, f: 5};
|
||||
const slotChart: { [k: string]: number } = { a: 0, b: 1, c: 2, d: 3, e: 4, f: 5 };
|
||||
siden = parseInt(name.charAt(1), 10) - 1;
|
||||
slot = slotChart[name.charAt(2)];
|
||||
name = name.slice(5);
|
||||
pokemonid = `p${siden + 1}: ${name}`;
|
||||
}
|
||||
return {name, siden, slot, pokemonid};
|
||||
return { name, siden, slot, pokemonid };
|
||||
}
|
||||
getSwitchedPokemon(pokemonid: string, details: string) {
|
||||
if (pokemonid === '??') throw new Error(`pokemonid not passed`);
|
||||
const {name, siden, slot, pokemonid: parsedPokemonid} = this.parsePokemonId(pokemonid);
|
||||
const { name, siden, slot, pokemonid: parsedPokemonid } = this.parsePokemonId(pokemonid);
|
||||
pokemonid = parsedPokemonid;
|
||||
|
||||
const searchid = `${pokemonid}|${details}`;
|
||||
|
|
@ -3300,12 +3299,12 @@ export class Battle {
|
|||
return pokemon;
|
||||
}
|
||||
rememberTeamPreviewPokemon(sideid: string, details: string) {
|
||||
const {siden} = this.parsePokemonId(sideid);
|
||||
const { siden } = this.parsePokemonId(sideid);
|
||||
|
||||
return this.sides[siden].addPokemon('', '', details);
|
||||
}
|
||||
findCorrespondingPokemon(serverPokemon: {ident: string, details: string}) {
|
||||
const {siden} = this.parsePokemonId(serverPokemon.ident);
|
||||
findCorrespondingPokemon(serverPokemon: { ident: string, details: string }) {
|
||||
const { siden } = this.parsePokemonId(serverPokemon.ident);
|
||||
const searchid = `${serverPokemon.ident}|${serverPokemon.details}`;
|
||||
for (const pokemon of this.sides[siden].pokemon) {
|
||||
if (pokemon.searchid === searchid) {
|
||||
|
|
@ -3318,7 +3317,7 @@ export class Battle {
|
|||
if (!pokemonid || pokemonid === '??' || pokemonid === 'null' || pokemonid === 'false') {
|
||||
return null;
|
||||
}
|
||||
const {siden, slot, pokemonid: parsedPokemonid} = this.parsePokemonId(pokemonid);
|
||||
const { siden, slot, pokemonid: parsedPokemonid } = this.parsePokemonId(pokemonid);
|
||||
pokemonid = parsedPokemonid;
|
||||
|
||||
/** if true, don't match an active pokemon */
|
||||
|
|
@ -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') {
|
||||
|
|
@ -3607,7 +3606,7 @@ export class Battle {
|
|||
break;
|
||||
}
|
||||
case 'updatepoke': {
|
||||
const {siden} = this.parsePokemonId(args[1]);
|
||||
const { siden } = this.parsePokemonId(args[1]);
|
||||
const side = this.sides[siden];
|
||||
for (let i = 0; i < side.pokemon.length; i++) {
|
||||
const pokemon = side.pokemon[i];
|
||||
|
|
@ -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) {
|
||||
|
|
@ -3756,7 +3756,7 @@ export class Battle {
|
|||
return;
|
||||
}
|
||||
if (!str) return;
|
||||
const {args, kwArgs} = BattleTextParser.parseBattleLine(str);
|
||||
const { args, kwArgs } = BattleTextParser.parseBattleLine(str);
|
||||
|
||||
if (this.scene.maybeCloseMessagebar(args, kwArgs)) {
|
||||
this.currentStep--;
|
||||
|
|
@ -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) === '|-') {
|
||||
({args: nextArgs, kwArgs: nextKwargs} = BattleTextParser.parseBattleLine(nextLine));
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,10 @@
|
|||
* @license MIT
|
||||
*/
|
||||
|
||||
import {PS} from "./client-main";
|
||||
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;
|
||||
|
|
@ -20,7 +21,7 @@ export class PSConnection {
|
|||
const server = PS.server;
|
||||
const port = server.protocol === 'https' ? '' : ':' + server.port;
|
||||
const url = server.protocol + '://' + server.host + port + server.prefix;
|
||||
const socket = this.socket = new SockJS(url, [], {timeout: 5 * 60 * 1000});
|
||||
const socket = this.socket = new SockJS(url, [], { timeout: 5 * 60 * 1000 });
|
||||
socket.onopen = () => {
|
||||
console.log('\u2705 (CONNECTED)');
|
||||
this.connected = true;
|
||||
|
|
@ -60,17 +61,15 @@ export class PSConnection {
|
|||
PS.connection = new PSConnection();
|
||||
|
||||
export const PSLoginServer = new class {
|
||||
query(data: PostData): Promise<{[k: string]: any} | null> {
|
||||
query(data: PostData): Promise<{ [k: string]: any } | null> {
|
||||
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(
|
||||
return Net(url).get({ method: data ? 'POST' : 'GET', body: data }).then(
|
||||
res => res ? JSON.parse(res.slice(1)) : null
|
||||
).catch(
|
||||
() => null
|
||||
|
|
@ -96,7 +95,7 @@ class HttpError extends Error {
|
|||
this.body = body;
|
||||
try {
|
||||
(Error as any).captureStackTrace(this, HttpError);
|
||||
} catch (err) {}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
class NetRequest {
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
|
|
@ -179,7 +115,7 @@ declare const ColorThief: any;
|
|||
const PSBackground = new class extends PSStreamModel {
|
||||
id = '';
|
||||
curId = '';
|
||||
attrib: {url: string, title: string, artist: string} | null = null;
|
||||
attrib: { url: string, title: string, artist: string } | null = null;
|
||||
changeCount = 0;
|
||||
menuColors: string[] | null = null;
|
||||
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -9,13 +9,13 @@
|
|||
* @license AGPLv3
|
||||
*/
|
||||
|
||||
import { PSConnection, PSLoginServer } from './client-connection';
|
||||
import {PSModel, PSStreamModel} from './client-core';
|
||||
import type {PSRouter} from './panels';
|
||||
import type {ChatRoom} from './panel-chat';
|
||||
import type {MainMenuRoom} from './panel-mainmenu';
|
||||
import {toID, type ID} from './battle-dex';
|
||||
import {BattleTextParser, type Args} from './battle-text-parser';
|
||||
import { type PSConnection, PSLoginServer } from './client-connection';
|
||||
import { PSModel, PSStreamModel } from './client-core';
|
||||
import type { PSRouter } from './panels';
|
||||
import type { ChatRoom } from './panel-chat';
|
||||
import type { MainMenuRoom } from './panel-mainmenu';
|
||||
import { toID, type ID } from './battle-dex';
|
||||
import { BattleTextParser, type Args } from './battle-text-parser';
|
||||
|
||||
/**********************************************************************
|
||||
* Prefs
|
||||
|
|
@ -24,9 +24,9 @@ import {BattleTextParser, type Args} from './battle-text-parser';
|
|||
/**
|
||||
* String that contains only lowercase alphanumeric characters.
|
||||
*/
|
||||
export type RoomID = string & {__isRoomID: true};
|
||||
export type RoomID = string & { __isRoomID: true };
|
||||
|
||||
const PSPrefsDefaults: {[key: string]: any} = {};
|
||||
const PSPrefsDefaults: { [key: string]: any } = {};
|
||||
|
||||
/**
|
||||
* Tracks user preferences, stored in localStorage. Contains most local
|
||||
|
|
@ -54,7 +54,7 @@ class PSPrefs extends PSStreamModel<string | null> {
|
|||
* table. Uses 1 and 0 instead of true/false for JSON packing
|
||||
* reasons.
|
||||
*/
|
||||
showjoins: {[serverid: string]: {[roomid: string]: 1 | 0}} | null = null;
|
||||
showjoins: { [serverid: string]: { [roomid: string]: 1 | 0 } } | null = null;
|
||||
/**
|
||||
* true = one panel, false = two panels, left and right
|
||||
*/
|
||||
|
|
@ -66,7 +66,7 @@ class PSPrefs extends PSStreamModel<string | null> {
|
|||
notifvolume = 50;
|
||||
|
||||
storageEngine: 'localStorage' | 'iframeLocalStorage' | '' = '';
|
||||
storage: {[k: string]: any} = {};
|
||||
storage: { [k: string]: any } = {};
|
||||
readonly origin = `https://${Config.routes.client}`;
|
||||
constructor() {
|
||||
super();
|
||||
|
|
@ -116,9 +116,9 @@ class PSPrefs extends PSStreamModel<string | null> {
|
|||
fixPrefs(newPrefs: any) {
|
||||
const oldShowjoins = newPrefs['showjoins'];
|
||||
if (oldShowjoins !== undefined && typeof oldShowjoins !== 'object') {
|
||||
const showjoins: {[serverid: string]: {[roomid: string]: 1 | 0}} = {};
|
||||
const serverShowjoins: {[roomid: string]: 1 | 0} = {global: (oldShowjoins ? 1 : 0)};
|
||||
const showroomjoins = newPrefs['showroomjoins'] as {[roomid: string]: boolean};
|
||||
const showjoins: { [serverid: string]: { [roomid: string]: 1 | 0 } } = {};
|
||||
const serverShowjoins: { [roomid: string]: 1 | 0 } = { global: (oldShowjoins ? 1 : 0) };
|
||||
const showroomjoins = newPrefs['showroomjoins'] as { [roomid: string]: boolean };
|
||||
for (const roomid in showroomjoins) {
|
||||
serverShowjoins[roomid] = (showroomjoins[roomid] ? 1 : 0);
|
||||
}
|
||||
|
|
@ -172,7 +172,7 @@ class PSTeams extends PSStreamModel<'team' | 'format'> {
|
|||
/** false if it uses the ladder in the website */
|
||||
usesLocalLadder = false;
|
||||
list: Team[] = [];
|
||||
byKey: {[key: string]: Team | undefined} = {};
|
||||
byKey: { [key: string]: Team | undefined } = {};
|
||||
deletedTeams: [Team, number][] = [];
|
||||
constructor() {
|
||||
super();
|
||||
|
|
@ -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,
|
||||
|
|
@ -290,7 +289,7 @@ class PSUser extends PSModel {
|
|||
avatar = "1";
|
||||
setName(fullName: string, named: boolean, avatar: string) {
|
||||
const loggingIn = (!this.named && named);
|
||||
const {name, group} = BattleTextParser.parseNameParts(fullName);
|
||||
const { name, group } = BattleTextParser.parseNameParts(fullName);
|
||||
this.name = name;
|
||||
this.group = group;
|
||||
this.userid = toID(name);
|
||||
|
|
@ -340,7 +339,7 @@ class PSServer {
|
|||
registered = Config.defaultserver.registered;
|
||||
prefix = '/showdown';
|
||||
protocol: 'http' | 'https' = Config.defaultserver.httpport ? 'https' : 'http';
|
||||
groups: {[symbol: string]: PSGroup} = {
|
||||
groups: { [symbol: string]: PSGroup } = {
|
||||
'~': {
|
||||
name: "Administrator (~)",
|
||||
type: 'leadership',
|
||||
|
|
@ -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;
|
||||
|
|
@ -491,7 +490,7 @@ export class PSRoom extends PSStreamModel<Args | null> implements RoomOptions {
|
|||
if (options.rightPopup) this.rightPopup = true;
|
||||
if (options.connected) this.connected = true;
|
||||
}
|
||||
notify(options: {title: string, body?: string, noAutoDismiss?: boolean, id?: string}) {
|
||||
notify(options: { title: string, body?: string, noAutoDismiss?: boolean, id?: string }) {
|
||||
if (options.noAutoDismiss && !options.id) {
|
||||
throw new Error(`Must specify id for manual dismissing`);
|
||||
}
|
||||
|
|
@ -528,7 +527,7 @@ export class PSRoom extends PSStreamModel<Args | null> implements RoomOptions {
|
|||
break;
|
||||
} case 'tempnotify': {
|
||||
const [, id, title, body, toHighlight] = args;
|
||||
this.notify({title, body, id});
|
||||
this.notify({ title, body, id });
|
||||
break;
|
||||
} case 'tempnotifyoff': {
|
||||
const [, id] = args;
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -580,7 +581,7 @@ class PlaceholderRoom extends PSRoom {
|
|||
* PS
|
||||
*********************************************************************/
|
||||
|
||||
type RoomType = {Model?: typeof PSRoom, Component: any, title?: string};
|
||||
type RoomType = { Model?: typeof PSRoom, Component: any, title?: string };
|
||||
|
||||
/**
|
||||
* This model updates:
|
||||
|
|
@ -609,7 +610,7 @@ export const PS = new class extends PSModel {
|
|||
|
||||
router: PSRouter = null!;
|
||||
|
||||
rooms: {[roomid: string]: PSRoom | undefined} = {};
|
||||
rooms: { [roomid: string]: PSRoom | undefined } = {};
|
||||
roomTypes: {
|
||||
[type: string]: RoomType | undefined,
|
||||
} = {};
|
||||
|
|
@ -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;
|
||||
|
|
@ -688,7 +689,7 @@ export const PS = new class extends PSModel {
|
|||
* for security reasons it's impossible to know what they are until
|
||||
* they're dropped.
|
||||
*/
|
||||
dragging: {type: 'room', roomid: RoomID} | null = null;
|
||||
dragging: { type: 'room', roomid: RoomID } | null = null;
|
||||
|
||||
/** Tracks whether or not to display the "Use arrow keys" hint */
|
||||
arrowKeysUsed = 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;
|
||||
|
|
@ -1174,7 +1176,7 @@ export const PS = new class extends PSModel {
|
|||
}
|
||||
join(roomid: RoomID, side?: PSRoomLocation | null, noFocus?: boolean) {
|
||||
if (this.room.id === roomid) return;
|
||||
this.addRoom({id: roomid, side}, noFocus);
|
||||
this.addRoom({ id: roomid, side }, noFocus);
|
||||
this.update();
|
||||
}
|
||||
leave(roomid: RoomID) {
|
||||
|
|
|
|||
34
play.pokemonshowdown.com/src/globals.d.ts
vendored
34
play.pokemonshowdown.com/src/globals.d.ts
vendored
|
|
@ -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;
|
||||
type AnyObject = {[k: string]: any};
|
||||
declare var app: {user: AnyObject, rooms: AnyObject, ignore?: AnyObject};
|
||||
declare const Config: any;
|
||||
declare const Replays: any;
|
||||
declare const exports: any;
|
||||
type AnyObject = { [k: string]: any };
|
||||
declare const app: { user: AnyObject, rooms: AnyObject, ignore?: AnyObject };
|
||||
|
||||
interface Window {
|
||||
[k: string]: any;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
const MAX_UNDO_HISTORY = 100;
|
||||
export type MiniEditPlugin = new (editor: MiniEdit) => unknown;
|
||||
export type MiniEditSelection = {start: number, end: number} | null;
|
||||
export type MiniEditSelection = { start: number, end: number } | null;
|
||||
export class MiniEdit {
|
||||
static plugins: MiniEditPlugin[] = [];
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -90,7 +90,7 @@ export class MiniEdit {
|
|||
const selection = this.getSelection()!;
|
||||
const oldContent = this.getValue();
|
||||
const newText = oldContent.slice(0, selection.start) + text + oldContent.slice(selection.end);
|
||||
this.setValue(newText, {start: selection.start + text.length, end: selection.start + text.length});
|
||||
this.setValue(newText, { start: selection.start + text.length, end: selection.start + text.length });
|
||||
}
|
||||
|
||||
getSelection(): MiniEditSelection {
|
||||
|
|
@ -116,7 +116,7 @@ export class MiniEdit {
|
|||
});
|
||||
}
|
||||
|
||||
return (start === null || end === null) ? null : {start, end};
|
||||
return (start === null || end === null) ? null : { start, end };
|
||||
}
|
||||
|
||||
setSelection(sel: MiniEditSelection): void {
|
||||
|
|
@ -149,7 +149,7 @@ export class MiniEdit {
|
|||
}
|
||||
}
|
||||
select(): void {
|
||||
this.setSelection({start: 0, end: this.getValue().length});
|
||||
this.setSelection({ start: 0, end: this.getValue().length });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -174,11 +174,11 @@ export class MiniEditUndoPlugin {
|
|||
editor: MiniEdit;
|
||||
undoIndex: number | null = null;
|
||||
ignoreInput = false;
|
||||
history: {text: string, selection: MiniEditSelection}[] = [];
|
||||
history: { text: string, selection: MiniEditSelection }[] = [];
|
||||
|
||||
constructor(editor: MiniEdit) {
|
||||
this.editor = editor;
|
||||
this.history.push({text: editor.getValue(), selection: {start: 0, end: 0}});
|
||||
this.history.push({ text: editor.getValue(), selection: { start: 0, end: 0 } });
|
||||
|
||||
this.editor.pushHistory = this.onPushHistory;
|
||||
editor.element.addEventListener('keydown', this.onKeyDown);
|
||||
|
|
@ -197,7 +197,7 @@ export class MiniEditUndoPlugin {
|
|||
this.undoIndex = null;
|
||||
}
|
||||
|
||||
this.history.push({text, selection});
|
||||
this.history.push({ text, selection });
|
||||
|
||||
if (this.history.length > MAX_UNDO_HISTORY) this.history.shift();
|
||||
};
|
||||
|
|
@ -227,7 +227,7 @@ export class MiniEditUndoPlugin {
|
|||
return;
|
||||
}
|
||||
|
||||
const {text, selection} = this.history[this.undoIndex];
|
||||
const { text, selection } = this.history[this.undoIndex];
|
||||
this.ignoreInput = true;
|
||||
this.editor.setValue(text, selection);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,18 +6,18 @@
|
|||
*/
|
||||
|
||||
import preact from "../js/lib/preact";
|
||||
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 {BattleScene} from "./battle-animations";
|
||||
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, 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";
|
||||
import type { Args } from "./battle-text-parser";
|
||||
|
||||
type BattleDesc = {
|
||||
id: RoomID,
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -184,7 +197,7 @@ class BattleDiv extends preact.Component {
|
|||
}
|
||||
|
||||
function MoveButton(props: {
|
||||
children: string, cmd: string, moveData: {pp: number, maxpp: number}, type: Dex.TypeName, tooltip: string,
|
||||
children: string, cmd: string, moveData: { pp: number, maxpp: number }, type: Dex.TypeName, tooltip: string,
|
||||
}) {
|
||||
return <button name="cmd" value={props.cmd} class={`type-${props.type} has-tooltip`} data-tooltip={props.tooltip}>
|
||||
{props.children}<br />
|
||||
|
|
@ -198,7 +211,7 @@ function PokemonButton(props: {
|
|||
if (!pokemon) {
|
||||
return <button
|
||||
name="cmd" value={props.cmd} class={`${props.disabled ? 'disabled ' : ''}has-tooltip`}
|
||||
style={{opacity: props.disabled === 'fade' ? 0.5 : 1}} data-tooltip={props.tooltip}
|
||||
style={{ opacity: props.disabled === 'fade' ? 0.5 : 1 }} data-tooltip={props.tooltip}
|
||||
>
|
||||
(empty slot)
|
||||
</button>;
|
||||
|
|
@ -213,14 +226,14 @@ function PokemonButton(props: {
|
|||
|
||||
return <button
|
||||
name="cmd" value={props.cmd} class={`${props.disabled ? 'disabled ' : ''}has-tooltip`}
|
||||
style={{opacity: props.disabled === 'fade' ? 0.5 : 1}} data-tooltip={props.tooltip}
|
||||
style={{ opacity: props.disabled === 'fade' ? 0.5 : 1 }} data-tooltip={props.tooltip}
|
||||
>
|
||||
<span class="picon" style={Dex.getPokemonIcon(pokemon)}></span>
|
||||
{pokemon.name}
|
||||
{
|
||||
!props.noHPBar && !pokemon.fainted &&
|
||||
<span class={hpColorClass}>
|
||||
<span style={{width: Math.round(pokemon.hp * 92 / pokemon.maxhp) || 1}}></span>
|
||||
<span style={{ width: Math.round(pokemon.hp * 92 / pokemon.maxhp) || 1 }}></span>
|
||||
</span>
|
||||
}
|
||||
{!props.noHPBar && pokemon.status && <span class={`status ${pokemon.status}`}></span>}
|
||||
|
|
@ -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>;
|
||||
}
|
||||
|
|
@ -392,7 +415,7 @@ class BattlePanel extends PSRoomPanel<BattleRoom> {
|
|||
return <button disabled> </button>;
|
||||
}
|
||||
const tooltip = `zmove|${moveData.name}|${pokemonIndex}`;
|
||||
return <MoveButton cmd={`/move ${i + 1} zmove`} type={move.type} tooltip={tooltip} moveData={{pp: 1, maxpp: 1}}>
|
||||
return <MoveButton cmd={`/move ${i + 1} zmove`} type={move.type} tooltip={tooltip} moveData={{ pp: 1, maxpp: 1 }}>
|
||||
{zMoveData.name}
|
||||
</MoveButton>;
|
||||
});
|
||||
|
|
@ -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? "]
|
||||
:
|
||||
" 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} />
|
||||
|
|
|
|||
|
|
@ -6,20 +6,20 @@
|
|||
*/
|
||||
|
||||
import preact from "../js/lib/preact";
|
||||
import type {PSSubscription} from "./client-core";
|
||||
import {PS, PSRoom, type RoomOptions, type RoomID, type Team} from "./client-main";
|
||||
import {PSPanelWrapper, PSRoomPanel} from "./panels";
|
||||
import {TeamForm} from "./panel-mainmenu";
|
||||
import {BattleLog} from "./battle-log";
|
||||
import type {Battle} from "./battle";
|
||||
import {MiniEdit} from "./miniedit";
|
||||
import {PSUtils, toID, type ID} from "./battle-dex";
|
||||
import type { PSSubscription } from "./client-core";
|
||||
import { PS, PSRoom, type RoomOptions, type RoomID, type Team } from "./client-main";
|
||||
import { PSPanelWrapper, PSRoomPanel } from "./panels";
|
||||
import { TeamForm } from "./panel-mainmenu";
|
||||
import { BattleLog } from "./battle-log";
|
||||
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';
|
||||
users: {[userid: string]: string} = {};
|
||||
users: { [userid: string]: string } = {};
|
||||
userCount = 0;
|
||||
override readonly canConnect = true;
|
||||
|
||||
|
|
@ -92,7 +92,8 @@ export class ChatRoom extends PSRoom {
|
|||
this.challengedFormat = null;
|
||||
this.update(null);
|
||||
return false;
|
||||
}}
|
||||
}
|
||||
}
|
||||
return super.handleMessage(line);
|
||||
}
|
||||
openChallenge() {
|
||||
|
|
@ -219,7 +220,7 @@ export class ChatTextEntry extends preact.Component<{
|
|||
getValue() {
|
||||
return this.miniedit ? this.miniedit.getValue() : this.textbox.value;
|
||||
}
|
||||
setValue(value: string, selection?: {start: number, end: number}) {
|
||||
setValue(value: string, selection?: { start: number, end: number }) {
|
||||
if (this.miniedit) {
|
||||
this.miniedit.setValue(value, selection);
|
||||
} else {
|
||||
|
|
@ -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
|
||||
|
|
@ -285,19 +286,19 @@ export class ChatTextEntry extends preact.Component<{
|
|||
}
|
||||
getSelection() {
|
||||
return this.miniedit ?
|
||||
(this.miniedit.getSelection() || {start: 0, end: 0}) :
|
||||
{start: this.textbox.selectionStart, end: this.textbox.selectionEnd};
|
||||
(this.miniedit.getSelection() || { start: 0, end: 0 }) :
|
||||
{ start: this.textbox.selectionStart, end: this.textbox.selectionEnd };
|
||||
}
|
||||
setSelection(start: number, end: number) {
|
||||
if (this.miniedit) {
|
||||
this.miniedit.setSelection({start, end});
|
||||
this.miniedit.setSelection({ start, end });
|
||||
} else {
|
||||
this.textbox.setSelectionRange?.(start, end);
|
||||
}
|
||||
}
|
||||
toggleFormatChar(formatChar: string) {
|
||||
let value = this.getValue();
|
||||
let {start, end} = this.getSelection();
|
||||
let { start, end } = this.getSelection();
|
||||
|
||||
// make sure start and end aren't midway through the syntax
|
||||
if (value.charAt(start) === formatChar && value.charAt(start - 1) === formatChar &&
|
||||
|
|
@ -333,23 +334,23 @@ export class ChatTextEntry extends preact.Component<{
|
|||
end -= 2;
|
||||
}
|
||||
|
||||
this.setValue(value, {start, end});
|
||||
this.setValue(value, { start, end });
|
||||
return true;
|
||||
}
|
||||
override render() {
|
||||
const OLD_TEXTBOX = false;
|
||||
return <div
|
||||
class="chat-log-add hasuserlist" onClick={this.focusIfNoSelection} style={{left: this.props.left || 0}}
|
||||
class="chat-log-add hasuserlist" onClick={this.focusIfNoSelection} style={{ left: this.props.left || 0 }}
|
||||
>
|
||||
<form class="chatbox">
|
||||
<label style={{color: BattleLog.usernameColor(PS.user.userid)}}>{PS.user.name}:</label>
|
||||
<label style={{ color: BattleLog.usernameColor(PS.user.userid) }}>{PS.user.name}:</label>
|
||||
{OLD_TEXTBOX ? <textarea
|
||||
class={this.props.room.connected ? 'textbox' : 'textbox disabled'}
|
||||
autofocus
|
||||
rows={1}
|
||||
onInput={this.update}
|
||||
onKeyDown={this.onKeyDown}
|
||||
style={{resize: 'none', width: '100%', height: '16px', padding: '2px 3px 1px 3px'}}
|
||||
style={{ resize: 'none', width: '100%', height: '16px', padding: '2px 3px 1px 3px' }}
|
||||
placeholder={PS.focusPreview(this.props.room)}
|
||||
/> : <ChatTextBox
|
||||
class={this.props.room.connected ? 'textbox' : 'textbox disabled'}
|
||||
|
|
@ -360,7 +361,7 @@ export class ChatTextEntry extends preact.Component<{
|
|||
}
|
||||
}
|
||||
|
||||
class ChatTextBox extends preact.Component<{placeholder: string, class: string}> {
|
||||
class ChatTextBox extends preact.Component<{ placeholder: string, class: string }> {
|
||||
override shouldComponentUpdate() {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -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 = () => {
|
||||
|
|
@ -452,13 +453,13 @@ class ChatPanel extends PSRoomPanel<ChatRoom> {
|
|||
}
|
||||
}
|
||||
|
||||
export class ChatUserList extends preact.Component<{room: ChatRoom, left?: number, minimized?: boolean}> {
|
||||
export class ChatUserList extends preact.Component<{ room: ChatRoom, left?: number, minimized?: boolean }> {
|
||||
subscription: PSSubscription | null = null;
|
||||
override state = {
|
||||
expanded: false,
|
||||
};
|
||||
toggleExpanded = () => {
|
||||
this.setState({expanded: !this.state.expanded});
|
||||
this.setState({ expanded: !this.state.expanded });
|
||||
};
|
||||
override componentDidMount() {
|
||||
this.subscription = this.props.room.subscribe(msg => {
|
||||
|
|
@ -474,11 +475,14 @@ 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);
|
||||
const group = PS.server.groups[groupSymbol] || {type: 'user', order: 0};
|
||||
const group = PS.server.groups[groupSymbol] || { type: 'user', order: 0 };
|
||||
let color;
|
||||
if (name.endsWith('@!')) {
|
||||
name = name.slice(0, -2);
|
||||
|
|
@ -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' ?
|
||||
<strong><em style={{color}}>{name.substr(1)}</em></strong>
|
||||
: group.type === 'staff' ?
|
||||
<strong style={{color}}>{name.substr(1)}</strong>
|
||||
:
|
||||
<span style={{color}}>{name.substr(1)}</span>
|
||||
}
|
||||
{group.type === 'leadership' ? (
|
||||
<strong><em style={{ color }}>{name.substr(1)}</em></strong>
|
||||
) : 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>;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,13 +7,14 @@
|
|||
* @license AGPLv3
|
||||
*/
|
||||
|
||||
import {PS, PSRoom, type RoomOptions} from "./client-main";
|
||||
import {PSPanelWrapper, PSRoomPanel} from "./panels";
|
||||
import { PS, PSRoom, type RoomOptions } from "./client-main";
|
||||
import { PSPanelWrapper, PSRoomPanel } from "./panels";
|
||||
|
||||
// Example room with panel
|
||||
|
||||
class ExampleRoom extends PSRoom {
|
||||
override readonly classType: string = 'example';
|
||||
// eslint-disable-next-line no-useless-constructor
|
||||
constructor(options: RoomOptions) {
|
||||
super(options);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,19 +8,19 @@
|
|||
*/
|
||||
|
||||
import preact from "../js/lib/preact";
|
||||
import {PS, PSRoom} from "./client-main";
|
||||
import {Net} from "./client-connection";
|
||||
import {PSPanelWrapper, PSRoomPanel, SanitizedHTML} from "./panels";
|
||||
import {BattleLog} from "./battle-log";
|
||||
import {toID} from "./battle-dex";
|
||||
import { PS, PSRoom } from "./client-main";
|
||||
import { Net } from "./client-connection";
|
||||
import { PSPanelWrapper, PSRoomPanel, SanitizedHTML } from "./panels";
|
||||
import { BattleLog } from "./battle-log";
|
||||
import { toID } from "./battle-dex";
|
||||
|
||||
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({
|
||||
|
|
@ -71,8 +71,8 @@ export class LadderRoom extends PSRoom {
|
|||
};
|
||||
}
|
||||
|
||||
function LadderFormat(props: {room: LadderRoom}) {
|
||||
const {room} = props;
|
||||
function LadderFormat(props: { room: LadderRoom }) {
|
||||
const { room } = props;
|
||||
const {
|
||||
format, searchValue, lastSearch, loading, error, ladderData,
|
||||
setSearchValue, setLastSearch, requestLadderData,
|
||||
|
|
@ -124,18 +124,18 @@ function LadderFormat(props: {room: LadderRoom}) {
|
|||
}
|
||||
return <>
|
||||
<p>
|
||||
<button class="button" data-href="ladder" data-target="replace" >
|
||||
<button class="button" data-href="ladder" data-target="replace">
|
||||
<i class="fa fa-refresh"></i> Refresh
|
||||
</button>
|
||||
<RenderSearch/>
|
||||
<RenderSearch />
|
||||
</p>
|
||||
<RenderHeader/>
|
||||
<RenderHeader />
|
||||
<SanitizedHTML>{ladderData}</SanitizedHTML>
|
||||
</>;
|
||||
};
|
||||
return <div class="ladder pad">
|
||||
<p>
|
||||
<button class="button" data-href="ladder" data-target="replace">
|
||||
<button class="button" data-href="ladder" data-target="replace">
|
||||
<i class="fa fa-chevron-left"></i> Format List
|
||||
</button>
|
||||
</p>
|
||||
|
|
@ -145,7 +145,7 @@ function LadderFormat(props: {room: LadderRoom}) {
|
|||
|
||||
class LadderPanel extends PSRoomPanel<LadderRoom> {
|
||||
override componentDidMount() {
|
||||
const {room} = this.props;
|
||||
const { room } = this.props;
|
||||
// Request ladder data either on mount or after BattleFormats are loaded
|
||||
if (BattleFormats && room.format !== undefined) room.requestLadderData();
|
||||
this.subscriptions.push(
|
||||
|
|
@ -170,8 +170,8 @@ class LadderPanel extends PSRoomPanel<LadderRoom> {
|
|||
})
|
||||
);
|
||||
}
|
||||
static Notice = (props: {notice: string | undefined}) => {
|
||||
const {notice} = props;
|
||||
static Notice = (props: { notice: string | undefined }) => {
|
||||
const { notice } = props;
|
||||
if (notice) {
|
||||
return (
|
||||
<p>
|
||||
|
|
@ -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)) {
|
||||
|
|
@ -217,8 +217,8 @@ class LadderPanel extends PSRoomPanel<LadderRoom> {
|
|||
}
|
||||
return <>{sections}</>;
|
||||
};
|
||||
static ShowFormatList = (props: {room: LadderRoom}) => {
|
||||
const {room} = props;
|
||||
static ShowFormatList = (props: { room: LadderRoom }) => {
|
||||
const { room } = props;
|
||||
return <>
|
||||
<p>
|
||||
<a class="button" href={`/${Config.routes.users}/`} target="_blank">
|
||||
|
|
@ -235,7 +235,7 @@ class LadderPanel extends PSRoomPanel<LadderRoom> {
|
|||
</>;
|
||||
};
|
||||
override render() {
|
||||
const {room} = this.props;
|
||||
const { room } = this.props;
|
||||
return <PSPanelWrapper room={room} scrollable>
|
||||
<div class="ladder pad">
|
||||
{room.format === undefined && (
|
||||
|
|
|
|||
|
|
@ -6,17 +6,17 @@
|
|||
*/
|
||||
|
||||
import preact from "../js/lib/preact";
|
||||
import {PSLoginServer} from "./client-connection";
|
||||
import {PS, PSRoom, type RoomID, type Team} from "./client-main";
|
||||
import {PSPanelWrapper, PSRoomPanel} from "./panels";
|
||||
import type {BattlesRoom} from "./panel-battle";
|
||||
import type {ChatRoom} from "./panel-chat";
|
||||
import type {LadderRoom} from "./panel-ladder";
|
||||
import type {RoomsRoom} from "./panel-rooms";
|
||||
import {TeamBox} from "./panel-teamdropdown";
|
||||
import type {UserRoom} from "./panel-topbar";
|
||||
import {Dex, toID, type ID} from "./battle-dex";
|
||||
import type {Args} from "./battle-text-parser";
|
||||
import { PSLoginServer } from "./client-connection";
|
||||
import { PS, PSRoom, type RoomID, type Team } from "./client-main";
|
||||
import { PSPanelWrapper, PSRoomPanel } from "./panels";
|
||||
import type { BattlesRoom } from "./panel-battle";
|
||||
import type { ChatRoom } from "./panel-chat";
|
||||
import type { LadderRoom } from "./panel-ladder";
|
||||
import type { RoomsRoom } from "./panel-rooms";
|
||||
import { TeamBox } from "./panel-teamdropdown";
|
||||
import type { UserRoom } from "./panel-topbar";
|
||||
import { Dex, toID, type ID } from "./battle-dex";
|
||||
import type { Args } from "./battle-text-parser";
|
||||
|
||||
export type RoomInfo = {
|
||||
title: string, desc?: string, userCount?: number, section?: string, spotlight?: string, subRooms?: string[],
|
||||
|
|
@ -24,14 +24,16 @@ export type RoomInfo = {
|
|||
|
||||
export class MainMenuRoom extends PSRoom {
|
||||
override readonly classType: string = 'mainmenu';
|
||||
userdetailsCache: {[userid: string]: {
|
||||
userid: ID,
|
||||
avatar?: string | number,
|
||||
status?: string,
|
||||
group?: string,
|
||||
customgroup?: string,
|
||||
rooms?: {[roomid: string]: {isPrivate?: true, p1?: string, p2?: 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);
|
||||
}
|
||||
|
|
@ -109,9 +112,9 @@ 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;
|
||||
window.NonBattleGames = { rps: 'Rock Paper Scissors' };
|
||||
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();
|
||||
}
|
||||
|
|
@ -214,7 +217,7 @@ export class MainMenuRoom extends PSRoom {
|
|||
}
|
||||
|
||||
// Match base formats to their variants, if they are unavailable in the server.
|
||||
let multivariantFormats: {[id: string]: 1} = {};
|
||||
let multivariantFormats: { [id: string]: 1 } = {};
|
||||
for (let id in BattleFormats) {
|
||||
let teambuilderFormat = BattleFormats[BattleFormats[id].teambuilderFormat!];
|
||||
if (!teambuilderFormat || multivariantFormats[teambuilderFormat.id]) continue;
|
||||
|
|
@ -300,21 +303,21 @@ export class MainMenuRoom extends PSRoom {
|
|||
class NewsPanel extends PSRoomPanel {
|
||||
override render() {
|
||||
return <PSPanelWrapper room={this.props.room} scrollable>
|
||||
<div class="mini-window-body" dangerouslySetInnerHTML={{__html: PS.newsHTML}}></div>
|
||||
<div class="mini-window-body" dangerouslySetInnerHTML={{ __html: PS.newsHTML }}></div>
|
||||
</PSPanelWrapper>;
|
||||
}
|
||||
}
|
||||
|
||||
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');
|
||||
};
|
||||
handleDragStart = (e: DragEvent) => {
|
||||
const roomid = (e.currentTarget as HTMLElement).getAttribute('data-roomid') as RoomID;
|
||||
PS.dragging = {type: 'room', roomid};
|
||||
PS.dragging = { type: 'room', roomid };
|
||||
};
|
||||
renderMiniRoom(room: PSRoom) {
|
||||
const roomType = PS.roomTypes[room.type];
|
||||
|
|
@ -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,13 +344,13 @@ 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émon Showdown is offline due to a DDoS attack!</strong></p>
|
||||
:
|
||||
) : (
|
||||
<p class="error"><strong>Pokémon Showdown is offline due to technical difficulties!</strong></p>
|
||||
}
|
||||
)}
|
||||
<p>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<img width="96" height="96" src={`//${Config.routes.client}/sprites/gen5/teddiursa.png`} alt="" />
|
||||
</div>
|
||||
Bear with us as we freak out.
|
||||
|
|
@ -391,13 +396,13 @@ class MainMenuPanel extends PSRoomPanel<MainMenuRoom> {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rightmenu" style={{display: PS.leftRoomWidth ? 'none' : 'block'}}>
|
||||
<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">
|
||||
|
|
@ -415,7 +420,7 @@ class MainMenuPanel extends PSRoomPanel<MainMenuRoom> {
|
|||
}
|
||||
}
|
||||
|
||||
export class FormatDropdown extends preact.Component<{format?: string, onChange?: JSX.EventHandler<Event>}> {
|
||||
export class FormatDropdown extends preact.Component<{ format?: string, onChange?: JSX.EventHandler<Event> }> {
|
||||
declare base?: HTMLButtonElement;
|
||||
format = '[Gen 7] Random Battle';
|
||||
change = (e: Event) => {
|
||||
|
|
@ -427,7 +432,7 @@ export class FormatDropdown extends preact.Component<{format?: string, onChange?
|
|||
render() {
|
||||
if (this.props.format) {
|
||||
return <button
|
||||
name="format" value={this.props.format} class="select formatselect preselected" disabled
|
||||
name="format" value={this.props.format} class="select formatselect preselected" disabled
|
||||
>{this.props.format}</button>;
|
||||
}
|
||||
return <button
|
||||
|
|
@ -439,7 +444,7 @@ export class FormatDropdown extends preact.Component<{format?: string, onChange?
|
|||
}
|
||||
}
|
||||
|
||||
class TeamDropdown extends preact.Component<{format: string}> {
|
||||
class TeamDropdown extends preact.Component<{ format: string }> {
|
||||
teamFormat = '';
|
||||
teamKey = '';
|
||||
change = () => {
|
||||
|
|
@ -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>
|
||||
|
|
@ -489,14 +494,14 @@ export class TeamForm extends preact.Component<{
|
|||
children: preact.ComponentChildren, class?: string, format?: string,
|
||||
onSubmit: null | ((e: Event, format: string, team?: Team) => void),
|
||||
}> {
|
||||
override state = {format: '[Gen 7] Random Battle'};
|
||||
override state = { format: '[Gen 7] Random Battle' };
|
||||
changeFormat = (e: Event) => {
|
||||
this.setState({format: (e.target as HTMLButtonElement).value});
|
||||
this.setState({ format: (e.target as HTMLButtonElement).value });
|
||||
};
|
||||
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);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,17 +7,17 @@
|
|||
* @license MIT
|
||||
*/
|
||||
|
||||
import {PS, PSRoom, type RoomOptions} from "./client-main";
|
||||
import {PSPanelWrapper, PSRoomPanel, SanitizedHTML} from "./panels";
|
||||
import {BattleLog} from "./battle-log";
|
||||
import type {Args} from "./battle-text-parser";
|
||||
import { PS, PSRoom, type RoomOptions } from "./client-main";
|
||||
import { PSPanelWrapper, PSRoomPanel, SanitizedHTML } from "./panels";
|
||||
import { BattleLog } from "./battle-log";
|
||||
import type { Args } from "./battle-text-parser";
|
||||
|
||||
class PageRoom extends PSRoom {
|
||||
override readonly classType: string = 'html';
|
||||
readonly page?: string = this.id.split("-")[1];
|
||||
override readonly canConnect = true;
|
||||
|
||||
loading: boolean = true;
|
||||
loading = true;
|
||||
htmlData?: string;
|
||||
|
||||
setHTMLData = (htmlData?: string) => {
|
||||
|
|
@ -39,8 +39,7 @@ class PageRoom extends PSRoom {
|
|||
}
|
||||
}
|
||||
|
||||
function PageLadderHelp(props: {room: PageRoom}) {
|
||||
const {room} = props;
|
||||
function PageLadderHelp(props: { room: PageRoom }) {
|
||||
return <div class="ladder pad">
|
||||
<p>
|
||||
<button name="selectFormat" data-href="ladder" data-target="replace">
|
||||
|
|
@ -74,13 +73,13 @@ function PageLadderHelp(props: {room: PageRoom}) {
|
|||
}
|
||||
|
||||
class PagePanel extends PSRoomPanel<PageRoom> {
|
||||
clientRooms: {[key: string]: JSX.Element} = {'ladderhelp': <PageLadderHelp room={this.props.room}/>};
|
||||
clientRooms: { [key: string]: JSX.Element } = { 'ladderhelp': <PageLadderHelp room={this.props.room} /> };
|
||||
|
||||
/**
|
||||
* @return true to prevent line from being sent to server
|
||||
*/
|
||||
override receiveLine(args: Args) {
|
||||
const {room} = this.props;
|
||||
const { room } = this.props;
|
||||
switch (args[0]) {
|
||||
case 'title':
|
||||
room.title = args[1];
|
||||
|
|
@ -88,7 +87,7 @@ class PagePanel extends PSRoomPanel<PageRoom> {
|
|||
return true;
|
||||
case 'tempnotify': {
|
||||
const [, id, title, body] = args;
|
||||
room.notify({title, body, id});
|
||||
room.notify({ title, body, id });
|
||||
return true;
|
||||
}
|
||||
case 'tempnotifyoff': {
|
||||
|
|
@ -114,7 +113,7 @@ class PagePanel extends PSRoomPanel<PageRoom> {
|
|||
}
|
||||
}
|
||||
override render() {
|
||||
const {room} = this.props;
|
||||
const { room } = this.props;
|
||||
let renderPage;
|
||||
if (room.page !== undefined && this.clientRooms[room.page]) {
|
||||
renderPage = this.clientRooms[room.page];
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@
|
|||
* @license AGPLv3
|
||||
*/
|
||||
|
||||
import {PS, PSRoom, type RoomID, type RoomOptions} from "./client-main";
|
||||
import {PSPanelWrapper, PSRoomPanel} from "./panels";
|
||||
import type {RoomInfo} from "./panel-mainmenu";
|
||||
import {toID} from "./battle-dex";
|
||||
import { PS, PSRoom, type RoomID, type RoomOptions } from "./client-main";
|
||||
import { PSPanelWrapper, PSRoomPanel } from "./panels";
|
||||
import type { RoomInfo } from "./panel-mainmenu";
|
||||
import { toID } from "./battle-dex";
|
||||
|
||||
export class RoomsRoom extends PSRoom {
|
||||
override readonly classType: string = 'rooms';
|
||||
|
|
@ -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);
|
||||
|
|
@ -86,7 +86,7 @@ class RoomsPanel extends PSRoomPanel {
|
|||
room.title.replace(/[^A-Z0-9]+/g, '').toLowerCase().startsWith(searchid)
|
||||
);
|
||||
|
||||
const hidden = !exactMatch ? [{title: this.search, desc: "(Private room?)"}] : [];
|
||||
const hidden = !exactMatch ? [{ title: this.search, desc: "(Private room?)" }] : [];
|
||||
|
||||
const autoFill = this.lastKeyCode !== 127 && this.lastKeyCode >= 32;
|
||||
if (autoFill) {
|
||||
|
|
@ -103,15 +103,15 @@ 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);
|
||||
}
|
||||
|
||||
return {start, abbr, hidden};
|
||||
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">
|
||||
|
|
|
|||
|
|
@ -6,18 +6,18 @@
|
|||
*/
|
||||
|
||||
import preact from "../js/lib/preact";
|
||||
import {PS, PSRoom, type Team} from "./client-main";
|
||||
import {PSPanelWrapper, PSRoomPanel} from "./panels";
|
||||
import {PSTeambuilder} from "./panel-teamdropdown";
|
||||
import {Dex, toID, type ID} from "./battle-dex";
|
||||
import {DexSearch} from "./battle-dex-search";
|
||||
import {PSSearchResults} from "./battle-searchresults";
|
||||
import { PS, PSRoom, type Team } from "./client-main";
|
||||
import { PSPanelWrapper, PSRoomPanel } from "./panels";
|
||||
import { PSTeambuilder } from "./panel-teamdropdown";
|
||||
import { Dex, toID, type ID } from "./battle-dex";
|
||||
import { DexSearch } from "./battle-dex-search";
|
||||
import { PSSearchResults } from "./battle-searchresults";
|
||||
|
||||
class TeamRoom extends PSRoom {
|
||||
team: Team | null = null;
|
||||
}
|
||||
|
||||
class TeamTextbox extends preact.Component<{team: Team}> {
|
||||
class TeamTextbox extends preact.Component<{ team: Team }> {
|
||||
setInfo: {
|
||||
species: string,
|
||||
bottomY: number,
|
||||
|
|
@ -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 &&
|
||||
<div class="teaminnertextbox" style={{top: this.activeOffsetY - 1}}></div>
|
||||
}
|
||||
{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}}>
|
||||
<button class="button closesearch" onClick={this.closeMenu}><i class="fa fa-times"></i> Close</button>
|
||||
<PSSearchResults search={this.search} />
|
||||
</div>}
|
||||
{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>;
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@
|
|||
* @license AGPLv3
|
||||
*/
|
||||
|
||||
import {PS, PSRoom, type Team} from "./client-main";
|
||||
import {PSPanelWrapper, PSRoomPanel} from "./panels";
|
||||
import {TeamBox, TeamFolder} from "./panel-teamdropdown";
|
||||
import {PSUtils, type ID} from "./battle-dex";
|
||||
import { PS, PSRoom, type Team } from "./client-main";
|
||||
import { PSPanelWrapper, PSRoomPanel } from "./panels";
|
||||
import { TeamBox, TeamFolder } from "./panel-teamdropdown";
|
||||
import { PSUtils, type ID } from "./battle-dex";
|
||||
|
||||
class TeambuilderRoom extends PSRoom {
|
||||
readonly DEFAULT_FORMAT = 'gen8' as ID;
|
||||
|
|
@ -49,7 +49,8 @@ class TeambuilderRoom extends PSRoom {
|
|||
PS.teams.undelete();
|
||||
this.update(null);
|
||||
return true;
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
// unrecognized command
|
||||
alert(`Unrecognized command: ${line}`);
|
||||
|
|
@ -110,7 +111,7 @@ class TeambuilderPanel extends PSRoomPanel<TeambuilderRoom> {
|
|||
// (This is why folders you create will automatically disappear
|
||||
// if you leave them without adding anything to them.)
|
||||
|
||||
const folderTable: {[folder: string]: 1 | undefined} = {'': 1};
|
||||
const folderTable: { [folder: string]: 1 | undefined } = { '': 1 };
|
||||
const folders: string[] = [];
|
||||
for (const team of PS.teams.list) {
|
||||
const folder = team.folder;
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@
|
|||
* @license AGPLv3
|
||||
*/
|
||||
|
||||
import {PS, type Team} from "./client-main";
|
||||
import {PSPanelWrapper, PSRoomPanel} from "./panels";
|
||||
import {Dex, toID, type ID} from "./battle-dex";
|
||||
import { PS, type Team } from "./client-main";
|
||||
import { PSPanelWrapper, PSRoomPanel } from "./panels";
|
||||
import { Dex, toID, type ID } from "./battle-dex";
|
||||
import { BattleStatIDs, BattleStatNames } from "./battle-dex-data";
|
||||
|
||||
export class PSTeambuilder {
|
||||
|
|
@ -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 : ''}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -123,7 +112,7 @@ export class PSTeambuilder {
|
|||
for (const setBuf of buf.split(`]`)) {
|
||||
const parts = setBuf.split(`|`);
|
||||
if (parts.length < 11) continue;
|
||||
let set: Dex.PokemonSet = {species: '', moves: []};
|
||||
let set: Dex.PokemonSet = { species: '', moves: [] };
|
||||
team.push(set);
|
||||
|
||||
// name
|
||||
|
|
@ -137,13 +126,12 @@ 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;
|
||||
species.abilities[parts[3] as '0' || '0'] || (parts[3] === '' ? '' : '!!!ERROR!!!') :
|
||||
Dex.abilities.get(parts[3]).name;
|
||||
|
||||
// moves
|
||||
set.moves = parts[4].split(',').map(moveid =>
|
||||
|
|
@ -167,7 +155,7 @@ export class PSTeambuilder {
|
|||
spe: Number(evs[5]) || 0,
|
||||
};
|
||||
} else if (parts[6] === '0') {
|
||||
set.evs = {hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0};
|
||||
set.evs = { hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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 {
|
||||
|
|
@ -368,7 +356,7 @@ export class PSTeambuilder {
|
|||
} else if (line.startsWith('EVs: ')) {
|
||||
line = line.slice(5);
|
||||
let evLines = line.split('/');
|
||||
set.evs = {hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0};
|
||||
set.evs = { hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0 };
|
||||
for (let evLine of evLines) {
|
||||
evLine = evLine.trim();
|
||||
let spaceIndex = evLine.indexOf(' ');
|
||||
|
|
@ -381,7 +369,7 @@ export class PSTeambuilder {
|
|||
} else if (line.startsWith('IVs: ')) {
|
||||
line = line.slice(5);
|
||||
let ivLines = line.split(' / ');
|
||||
set.ivs = {hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31};
|
||||
set.ivs = { hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31 };
|
||||
for (let ivLine of ivLines) {
|
||||
ivLine = ivLine.trim();
|
||||
let spaceIndex = ivLine.indexOf(' ');
|
||||
|
|
@ -392,19 +380,19 @@ 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;
|
||||
line = 'Hidden Power ' + hpType;
|
||||
if (!set.ivs && Dex.types.isName(hpType)) {
|
||||
set.ivs = {hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31};
|
||||
set.ivs = { hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31 };
|
||||
const hpIVs = Dex.types.get(hpType).HPivs || {};
|
||||
for (let stat in hpIVs) {
|
||||
set.ivs[stat as Dex.StatName] = hpIVs[stat as Dex.StatName]!;
|
||||
|
|
@ -546,7 +534,7 @@ export class PSTeambuilder {
|
|||
}
|
||||
}
|
||||
|
||||
export function TeamFolder(props: {cur?: boolean, value: string, children: preact.ComponentChildren}) {
|
||||
export function TeamFolder(props: { cur?: boolean, value: string, children: preact.ComponentChildren }) {
|
||||
// folders are <div>s rather than <button>s because in theory it has
|
||||
// less weird interactions with HTML5 drag-and-drop
|
||||
if (props.cur) {
|
||||
|
|
@ -560,7 +548,7 @@ export function TeamFolder(props: {cur?: boolean, value: string, children: preac
|
|||
</div>;
|
||||
}
|
||||
|
||||
export function TeamBox(props: {team: Team | null, noLink?: boolean, button?: boolean}) {
|
||||
export function TeamBox(props: { team: Team | null, noLink?: boolean, button?: boolean }) {
|
||||
const team = props.team;
|
||||
let contents;
|
||||
if (team) {
|
||||
|
|
@ -659,7 +647,7 @@ class TeamDropdownPanel extends PSRoomPanel {
|
|||
if (availableWidth > 636) width = 613;
|
||||
if (availableWidth > 945) width = 919;
|
||||
|
||||
let teamBuckets: {[folder: string]: Team[]} = {};
|
||||
let teamBuckets: { [folder: string]: Team[] } = {};
|
||||
for (const team of teams) {
|
||||
const list = teamBuckets[team.folder] || (teamBuckets[team.folder] = []);
|
||||
list.push(team);
|
||||
|
|
@ -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}>
|
||||
<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}>
|
||||
{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}
|
||||
>
|
||||
<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>);
|
||||
|
|
@ -747,9 +749,7 @@ export interface FormatData {
|
|||
effectType: 'Format';
|
||||
}
|
||||
|
||||
declare const BattleFormats: {[id: string]: FormatData};
|
||||
/** id:name */
|
||||
declare const NonBattleGames: {[id: string]: string};
|
||||
declare const BattleFormats: { [id: string]: FormatData };
|
||||
|
||||
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;
|
||||
|
|
@ -806,7 +807,7 @@ class FormatDropdownPanel extends PSRoomPanel {
|
|||
|
||||
let curSection = '';
|
||||
let curColumnNum = 0;
|
||||
let curColumn: (FormatData | {id: null, section: string})[] = [];
|
||||
let curColumn: (FormatData | { id: null, section: string })[] = [];
|
||||
const columns = [curColumn];
|
||||
for (const format of formats) {
|
||||
if (format.column !== curColumnNum) {
|
||||
|
|
@ -819,7 +820,7 @@ class FormatDropdownPanel extends PSRoomPanel {
|
|||
if (format.section !== curSection) {
|
||||
curSection = format.section;
|
||||
if (curSection) {
|
||||
curColumn.push({id: null, section: curSection});
|
||||
curColumn.push({ id: null, section: curSection });
|
||||
}
|
||||
}
|
||||
curColumn.push(format);
|
||||
|
|
|
|||
|
|
@ -10,16 +10,16 @@
|
|||
*/
|
||||
|
||||
import preact from "../js/lib/preact";
|
||||
import {PS, PSRoom, type RoomOptions, type RoomID} from "./client-main";
|
||||
import {PSMain, PSPanelWrapper, PSRoomPanel} from "./panels";
|
||||
import type {Battle} from "./battle";
|
||||
import {Dex, toRoomid, toUserid, type ID} from "./battle-dex";
|
||||
import {BattleLog} from "./battle-log";
|
||||
import { PS, PSRoom, type RoomOptions, type RoomID } from "./client-main";
|
||||
import { PSMain, PSPanelWrapper, PSRoomPanel } from "./panels";
|
||||
import type { Battle } from "./battle";
|
||||
import { Dex, toRoomid, toUserid, type ID } from "./battle-dex";
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -75,7 +76,7 @@ export class PSHeader extends preact.Component<{style: {}}> {
|
|||
const roomid = PS.router.extractRoomID((e.currentTarget as HTMLAnchorElement).href);
|
||||
if (!roomid) return; // should never happen
|
||||
|
||||
PS.dragging = {type: 'room', roomid};
|
||||
PS.dragging = { type: 'room', roomid };
|
||||
};
|
||||
dragOnto(fromRoom: RoomID, toRoomList: 'leftRoomList' | 'rightRoomList' | 'miniRoomList', toIndex: number) {
|
||||
// one day you will be able to rearrange mainmenu and rooms, but not today
|
||||
|
|
@ -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>;
|
||||
|
|
@ -222,7 +223,7 @@ export class PSHeader extends preact.Component<{style: {}}> {
|
|||
if (!PS.user.named) {
|
||||
return <a class="button" href="login">Choose name</a>;
|
||||
}
|
||||
const userColor = window.BattleLog && {color: BattleLog.usernameColor(PS.user.userid)};
|
||||
const userColor = window.BattleLog && { color: BattleLog.usernameColor(PS.user.userid) };
|
||||
return <span class="username" data-name={PS.user.name} style={userColor}>
|
||||
<i class="fa fa-user" style="color:#779EC5"></i> <span class="usernametext">{PS.user.name}</span>
|
||||
</span>;
|
||||
|
|
@ -244,7 +245,7 @@ export class PSHeader extends preact.Component<{style: {}}> {
|
|||
<ul>
|
||||
{PS.leftRoomList.slice(1).map(roomid => this.renderRoomTab(roomid))}
|
||||
</ul>
|
||||
<ul class="siderooms" style={{float: 'none', marginLeft: PS.leftRoomWidth - 144}}>
|
||||
<ul class="siderooms" style={{ float: 'none', marginLeft: PS.leftRoomWidth - 144 }}>
|
||||
{PS.rightRoomList.map(roomid => this.renderRoomTab(roomid))}
|
||||
</ul>
|
||||
</div></div>
|
||||
|
|
@ -285,19 +286,19 @@ export class UserRoom extends PSRoom {
|
|||
class UserPanel extends PSRoomPanel<UserRoom> {
|
||||
override render() {
|
||||
const room = this.props.room;
|
||||
const user = PS.mainmenu.userdetailsCache[room.userid] || {userid: room.userid, avatar: '[loading]'};
|
||||
const user = PS.mainmenu.userdetailsCache[room.userid] || { userid: room.userid, avatar: '[loading]' };
|
||||
const name = room.name.slice(1);
|
||||
|
||||
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>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,13 +10,13 @@
|
|||
*/
|
||||
|
||||
import preact from "../js/lib/preact";
|
||||
import {toID} from "./battle-dex";
|
||||
import {BattleLog} from "./battle-log";
|
||||
import { toID } from "./battle-dex";
|
||||
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 {PSHeader} from "./panel-topbar";
|
||||
import { BattleTooltips } from "./battle-tooltips";
|
||||
import type { PSSubscription } from "./client-core";
|
||||
import { PS, type PSRoom, type RoomID } from "./client-main";
|
||||
import { PSHeader } from "./panel-topbar";
|
||||
|
||||
export class PSRouter {
|
||||
roomid = '' as RoomID;
|
||||
|
|
@ -127,7 +127,7 @@ export class PSRouter {
|
|||
}
|
||||
PS.router = new PSRouter();
|
||||
|
||||
export class PSRoomPanel<T extends PSRoom = PSRoom> extends preact.Component<{room: T}> {
|
||||
export class PSRoomPanel<T extends PSRoom = PSRoom> extends preact.Component<{ room: T }> {
|
||||
subscriptions: PSSubscription[] = [];
|
||||
override componentDidMount() {
|
||||
if (PS.room === this.props.room) this.focus();
|
||||
|
|
@ -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,21 +375,21 @@ 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) {
|
||||
let pos: PanelPosition | null = null;
|
||||
if (PS.leftRoomWidth === 0) {
|
||||
// one panel visible
|
||||
if (room === PS.activePanel) pos = {top: 56};
|
||||
if (room === PS.activePanel) pos = { top: 56 };
|
||||
} else {
|
||||
// both panels visible
|
||||
if (room === PS.leftRoom) pos = {top: 56, right: PS.leftRoomWidth};
|
||||
if (room === PS.rightRoom) pos = {top: 56, left: PS.leftRoomWidth};
|
||||
if (room === PS.leftRoom) pos = { top: 56, right: PS.leftRoomWidth };
|
||||
if (room === PS.rightRoom) pos = { top: 56, left: PS.leftRoomWidth };
|
||||
}
|
||||
|
||||
if (!pos) return {display: 'none'};
|
||||
if (!pos) return { display: 'none' };
|
||||
|
||||
let top: number | null = (pos.top || 0);
|
||||
let height: number | null = null;
|
||||
|
|
@ -422,7 +423,7 @@ export class PSMain extends preact.Component {
|
|||
}
|
||||
static getPopupStyle(room: PSRoom, width?: number | 'auto'): any {
|
||||
if (room.location === 'modal-popup' || !room.parentElem) {
|
||||
return {width: width || 480};
|
||||
return { width: width || 480 };
|
||||
}
|
||||
if (!room.width || !room.height) {
|
||||
return {
|
||||
|
|
@ -513,15 +514,15 @@ export class PSMain extends preact.Component {
|
|||
}
|
||||
}
|
||||
return <div class="ps-frame">
|
||||
<PSHeader style={{top: 0, left: 0, right: 0, height: '50px'}} />
|
||||
<PSHeader style={{ top: 0, left: 0, right: 0, height: '50px' }} />
|
||||
{rooms}
|
||||
{PS.popups.map(roomid => this.renderPopup(PS.rooms[roomid]!))}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
type PanelPosition = {top?: number, bottom?: number, left?: number, right?: number} | null;
|
||||
type PanelPosition = { top?: number, bottom?: number, left?: number, right?: number } | null;
|
||||
|
||||
export function SanitizedHTML(props: {children: string}) {
|
||||
return <div dangerouslySetInnerHTML={{__html: BattleLog.sanitizeHTML(props.children)}}/>;
|
||||
export function SanitizedHTML(props: { children: string }) {
|
||||
return <div dangerouslySetInnerHTML={{ __html: BattleLog.sanitizeHTML(props.children) }} />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -1,41 +1,42 @@
|
|||
/** @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 { 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
|
||||
window.top.__vm_add = window.top.__vm_add || [];
|
||||
// @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.
|
||||
// this is a x-browser way to make sure content has loaded.
|
||||
|
||||
(function (success) {
|
||||
if (window.document.readyState !== "loading") {
|
||||
success();
|
||||
} else {
|
||||
window.document.addEventListener("DOMContentLoaded", function () {
|
||||
success();
|
||||
});
|
||||
}
|
||||
})(function () {
|
||||
var placement = document.createElement("div");
|
||||
placement.setAttribute("class", "vm-placement");
|
||||
if (window.innerWidth > 1000) {
|
||||
//load desktop placement
|
||||
placement.setAttribute("data-id", "6452680c0b35755a3f09b59b");
|
||||
} else {
|
||||
//load mobile placement
|
||||
placement.setAttribute("data-id", "645268557bc7b571c2f06f62");
|
||||
}
|
||||
document.querySelector("#" + id)!.appendChild(placement);
|
||||
// @ts-expect-error
|
||||
window.top.__vm_add.push(placement);
|
||||
});
|
||||
(success => {
|
||||
if (window.document.readyState !== "loading") {
|
||||
success();
|
||||
} else {
|
||||
window.document.addEventListener("DOMContentLoaded", () => {
|
||||
success();
|
||||
});
|
||||
}
|
||||
})(() => {
|
||||
const placement = document.createElement("div");
|
||||
placement.setAttribute("class", "vm-placement");
|
||||
if (window.innerWidth > 1000) {
|
||||
// load desktop placement
|
||||
placement.setAttribute("data-id", "6452680c0b35755a3f09b59b");
|
||||
} else {
|
||||
// load mobile placement
|
||||
placement.setAttribute("data-id", "645268557bc7b571c2f06f62");
|
||||
}
|
||||
document.querySelector("#" + id)!.appendChild(placement);
|
||||
// @ts-expect-error no clue how to declare this one
|
||||
window.top.__vm_add.push(placement);
|
||||
});
|
||||
}
|
||||
|
||||
export class BattleDiv extends preact.Component {
|
||||
|
|
@ -43,7 +44,7 @@ export class BattleDiv extends preact.Component {
|
|||
return false;
|
||||
}
|
||||
override render() {
|
||||
return <div class="battle" style={{position: 'relative'}}></div>;
|
||||
return <div class="battle" style={{ position: 'relative' }}></div>;
|
||||
}
|
||||
}
|
||||
class BattleLogDiv extends preact.Component {
|
||||
|
|
@ -55,466 +56,479 @@ 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;
|
||||
} | null | undefined = undefined;
|
||||
resultError = '';
|
||||
battle: Battle | null;
|
||||
/** debug purposes */
|
||||
lastUsedKeyCode = '0';
|
||||
turnView: boolean | string = false;
|
||||
autofocusTurnView: 'select' | 'end' | null = null;
|
||||
override componentDidMount() {
|
||||
this.loadBattle(this.props.id);
|
||||
showAd('LeaderboardBTF');
|
||||
window.onkeydown = this.keyPressed;
|
||||
}
|
||||
override componentWillReceiveProps(nextProps) {
|
||||
if (this.stripQuery(this.props.id) !== this.stripQuery(nextProps.id)) {
|
||||
this.loadBattle(nextProps.id);
|
||||
}
|
||||
}
|
||||
stripQuery(id: string) {
|
||||
return id.includes('?') ? id.slice(0, id.indexOf('?')) : id;
|
||||
}
|
||||
loadBattle(id: string) {
|
||||
if (this.battle) this.battle.destroy();
|
||||
this.battle = null;
|
||||
this.result = undefined;
|
||||
this.resultError = '';
|
||||
this.forceUpdate();
|
||||
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,
|
||||
} | null | undefined = undefined;
|
||||
resultError = '';
|
||||
battle!: Battle | null;
|
||||
/** debug purposes */
|
||||
lastUsedKeyCode = '0';
|
||||
turnView: boolean | string = false;
|
||||
autofocusTurnView: 'select' | 'end' | null = null;
|
||||
override componentDidMount() {
|
||||
this.loadBattle(this.props.id);
|
||||
showAd('LeaderboardBTF');
|
||||
window.onkeydown = this.keyPressed;
|
||||
}
|
||||
override componentWillReceiveProps(nextProps: this['props']) {
|
||||
if (this.stripQuery(this.props.id) !== this.stripQuery(nextProps.id)) {
|
||||
this.loadBattle(nextProps.id);
|
||||
}
|
||||
}
|
||||
stripQuery(id: string) {
|
||||
return id.includes('?') ? id.slice(0, id.indexOf('?')) : id;
|
||||
}
|
||||
loadBattle(id: string) {
|
||||
if (this.battle) this.battle.destroy();
|
||||
this.battle = null;
|
||||
this.result = undefined;
|
||||
this.resultError = '';
|
||||
this.forceUpdate();
|
||||
|
||||
const elem = document.getElementById(`replaydata-${id}`);
|
||||
const logElem = document.getElementById(`replaylog-${id}`);
|
||||
if (elem) {
|
||||
// we actually do need to wait for that update to finish so
|
||||
// loadResult definitely has access to $frame and $logFrame
|
||||
setTimeout(() => this.loadResult(elem.innerText, id, logElem?.innerText.replace(/<\\\//g, '</')), 1);
|
||||
return;
|
||||
}
|
||||
const elem = document.getElementById(`replaydata-${id}`);
|
||||
const logElem = document.getElementById(`replaylog-${id}`);
|
||||
if (elem) {
|
||||
// we actually do need to wait for that update to finish so
|
||||
// loadResult definitely has access to $frame and $logFrame
|
||||
setTimeout(() => this.loadResult(elem.innerText, id, logElem?.innerText.replace(/<\\\//g, '</')), 1);
|
||||
return;
|
||||
}
|
||||
|
||||
Net(`/${this.stripQuery(id)}.json`).get().then(result => {
|
||||
this.loadResult(result, id);
|
||||
}).catch(err => {
|
||||
this.loadResult(err.statusCode === 404 ? '' : String(err?.body || ''), id);
|
||||
});
|
||||
}
|
||||
loadResult(result: string, id: string, log = '') {
|
||||
try {
|
||||
const replay: NonNullable<BattlePanel['result']> = JSON.parse(result);
|
||||
replay.log ||= log;
|
||||
this.result = replay;
|
||||
const $base = $(this.base!);
|
||||
this.battle = new Battle({
|
||||
id: replay.id,
|
||||
$frame: $base.find('.battle'),
|
||||
$logFrame: $base.find('.battle-log'),
|
||||
log: replay.log.split('\n'),
|
||||
isReplay: true,
|
||||
paused: true,
|
||||
autoresize: true,
|
||||
});
|
||||
// for ease of debugging
|
||||
(window as any).battle = this.battle;
|
||||
this.battle.subscribe(_ => {
|
||||
this.forceUpdate();
|
||||
});
|
||||
const query = Net.decodeQuery(id);
|
||||
if ('p2' in query) {
|
||||
this.battle.switchViewpoint();
|
||||
}
|
||||
if (query.turn || query.t) {
|
||||
this.battle.seekTurn(parseInt(query.turn || query.t, 10));
|
||||
}
|
||||
} catch (err) {
|
||||
this.result = null;
|
||||
this.resultError = result.startsWith('{') ? err.toString() : result;
|
||||
}
|
||||
this.forceUpdate();
|
||||
}
|
||||
override componentWillUnmount(): void {
|
||||
this.battle?.destroy();
|
||||
(window as any).battle = null;
|
||||
window.onkeydown = null;
|
||||
}
|
||||
override componentDidUpdate(): void {
|
||||
if (this.autofocusTurnView === 'select') {
|
||||
this.base?.querySelector<HTMLInputElement>('input[name=turn]')?.select();
|
||||
this.autofocusTurnView = null;
|
||||
}
|
||||
if (this.autofocusTurnView === 'end') {
|
||||
const turnbox = this.base?.querySelector<HTMLInputElement>('input[name=turn]');
|
||||
turnbox?.setSelectionRange(2, 2);
|
||||
turnbox?.focus();
|
||||
this.autofocusTurnView = null;
|
||||
}
|
||||
}
|
||||
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
|
||||
if (e.target?.tagName === 'INPUT' || e.target?.tagName === 'SELECT') return;
|
||||
switch (e.keyCode) {
|
||||
case 75: // k
|
||||
if (this.battle?.atQueueEnd) {
|
||||
this.replay();
|
||||
} else if (this.battle?.paused) {
|
||||
this.play();
|
||||
} else {
|
||||
this.pause();
|
||||
}
|
||||
break;
|
||||
case 74: // j
|
||||
if (e.shiftKey) this.firstTurn();
|
||||
else this.prevTurn();
|
||||
break;
|
||||
case 76: // l
|
||||
if (e.shiftKey) this.lastTurn();
|
||||
else this.nextTurn();
|
||||
break;
|
||||
case 188: // , (<)
|
||||
if (e.shiftKey) this.stepSpeed(-1);
|
||||
break;
|
||||
case 190: // . (>)
|
||||
if (e.shiftKey) this.stepSpeed(1);
|
||||
break;
|
||||
case 191: // / (?)
|
||||
if (e.shiftKey) {
|
||||
alert(
|
||||
'k = play/pause\n' +
|
||||
'j = previous turn\n' +
|
||||
'l = next turn\n' +
|
||||
'J = first turn\n' +
|
||||
'L = last turn\n' +
|
||||
'm = mute\n' +
|
||||
'< = slower\n' +
|
||||
'> = faster\n' +
|
||||
'1-9 = skip to turn\n' +
|
||||
'? = keyboard shortcuts (this)\n'
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 48: case 49: case 50: case 51: case 52: case 53: case 54: case 55: case 56: case 57: // 0-9
|
||||
case 96: case 97: case 98: case 99: case 100: case 101: case 102: case 103: case 104: case 105: // numpad 0-9
|
||||
this.turnView = String.fromCharCode(e.keyCode - (e.keyCode >= 96 ? 48 : 0));
|
||||
if (this.turnView === '0') this.turnView = '10';
|
||||
this.autofocusTurnView = 'end';
|
||||
e.preventDefault();
|
||||
this.forceUpdate();
|
||||
break;
|
||||
case 77: // m
|
||||
this.toggleMute();
|
||||
break;
|
||||
}
|
||||
this.forceUpdate();
|
||||
};
|
||||
play = () => {
|
||||
this.battle?.play();
|
||||
};
|
||||
replay = () => {
|
||||
this.battle?.reset();
|
||||
this.battle?.play();
|
||||
this.forceUpdate();
|
||||
};
|
||||
pause = () => {
|
||||
this.battle?.pause();
|
||||
};
|
||||
nextTurn = () => {
|
||||
this.battle?.seekBy(1);
|
||||
};
|
||||
prevTurn = () => {
|
||||
this.battle?.seekBy(-1);
|
||||
};
|
||||
firstTurn = () => {
|
||||
this.battle?.seekTurn(0);
|
||||
};
|
||||
lastTurn = () => {
|
||||
this.battle?.seekTurn(Infinity);
|
||||
};
|
||||
goToTurn = (e) => {
|
||||
Net(`/${this.stripQuery(id)}.json`).get().then(result => {
|
||||
this.loadResult(result, id);
|
||||
}).catch(err => {
|
||||
this.loadResult(err.statusCode === 404 ? '' : String(err?.body || ''), id);
|
||||
});
|
||||
}
|
||||
loadResult(result: string, id: string, log = '') {
|
||||
try {
|
||||
const replay: NonNullable<BattlePanel['result']> = JSON.parse(result);
|
||||
replay.log ||= log;
|
||||
this.result = replay;
|
||||
const $base = $(this.base!);
|
||||
this.battle = new Battle({
|
||||
id: replay.id as ID,
|
||||
$frame: $base.find('.battle'),
|
||||
$logFrame: $base.find('.battle-log'),
|
||||
log: replay.log.split('\n'),
|
||||
isReplay: true,
|
||||
paused: true,
|
||||
autoresize: true,
|
||||
});
|
||||
// for ease of debugging
|
||||
(window as any).battle = this.battle;
|
||||
this.battle.subscribe(_ => {
|
||||
this.forceUpdate();
|
||||
});
|
||||
const query = Net.decodeQuery(id);
|
||||
if ('p2' in query) {
|
||||
this.battle.switchViewpoint();
|
||||
}
|
||||
if (query.turn || query.t) {
|
||||
this.battle.seekTurn(parseInt(query.turn || query.t, 10));
|
||||
}
|
||||
} catch (err: any) {
|
||||
this.result = null;
|
||||
this.resultError = result.startsWith('{') ? err.toString() : result;
|
||||
}
|
||||
this.forceUpdate();
|
||||
}
|
||||
override componentWillUnmount(): void {
|
||||
this.battle?.destroy();
|
||||
(window as any).battle = null;
|
||||
window.onkeydown = null;
|
||||
}
|
||||
override componentDidUpdate(): void {
|
||||
if (this.autofocusTurnView === 'select') {
|
||||
this.base?.querySelector<HTMLInputElement>('input[name=turn]')?.select();
|
||||
this.autofocusTurnView = null;
|
||||
}
|
||||
if (this.autofocusTurnView === 'end') {
|
||||
const turnbox = this.base?.querySelector<HTMLInputElement>('input[name=turn]');
|
||||
turnbox?.setSelectionRange(2, 2);
|
||||
turnbox?.focus();
|
||||
this.autofocusTurnView = null;
|
||||
}
|
||||
}
|
||||
keyPressed = (e: KeyboardEvent) => {
|
||||
this.lastUsedKeyCode = `${e.keyCode}`;
|
||||
if (e.ctrlKey || e.metaKey || e.altKey) return;
|
||||
if (e.keyCode === 27 && this.turnView) { // Esc
|
||||
this.closeTurn();
|
||||
return;
|
||||
}
|
||||
// @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
|
||||
if (this.battle?.atQueueEnd) {
|
||||
this.replay();
|
||||
} else if (this.battle?.paused) {
|
||||
this.play();
|
||||
} else {
|
||||
this.pause();
|
||||
}
|
||||
break;
|
||||
case 74: // j
|
||||
if (e.shiftKey) this.firstTurn();
|
||||
else this.prevTurn();
|
||||
break;
|
||||
case 76: // l
|
||||
if (e.shiftKey) this.lastTurn();
|
||||
else this.nextTurn();
|
||||
break;
|
||||
case 188: // , (<)
|
||||
if (e.shiftKey) this.stepSpeed(-1);
|
||||
break;
|
||||
case 190: // . (>)
|
||||
if (e.shiftKey) this.stepSpeed(1);
|
||||
break;
|
||||
case 191: // / (?)
|
||||
if (e.shiftKey) {
|
||||
alert(
|
||||
'k = play/pause\n' +
|
||||
'j = previous turn\n' +
|
||||
'l = next turn\n' +
|
||||
'J = first turn\n' +
|
||||
'L = last turn\n' +
|
||||
'm = mute\n' +
|
||||
'< = slower\n' +
|
||||
'> = faster\n' +
|
||||
'1-9 = skip to turn\n' +
|
||||
'? = keyboard shortcuts (this)\n'
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 48: case 49: case 50: case 51: case 52: case 53: case 54: case 55: case 56: case 57: // 0-9
|
||||
case 96: case 97: case 98: case 99: case 100: case 101: case 102: case 103: case 104: case 105: // numpad 0-9
|
||||
this.turnView = String.fromCharCode(e.keyCode - (e.keyCode >= 96 ? 48 : 0));
|
||||
if (this.turnView === '0') this.turnView = '10';
|
||||
this.autofocusTurnView = 'end';
|
||||
e.preventDefault();
|
||||
this.forceUpdate();
|
||||
break;
|
||||
case 77: // m
|
||||
this.toggleMute();
|
||||
break;
|
||||
}
|
||||
this.forceUpdate();
|
||||
};
|
||||
play = () => {
|
||||
this.battle?.play();
|
||||
};
|
||||
replay = () => {
|
||||
this.battle?.reset();
|
||||
this.battle?.play();
|
||||
this.forceUpdate();
|
||||
};
|
||||
pause = () => {
|
||||
this.battle?.pause();
|
||||
};
|
||||
nextTurn = () => {
|
||||
this.battle?.seekBy(1);
|
||||
};
|
||||
prevTurn = () => {
|
||||
this.battle?.seekBy(-1);
|
||||
};
|
||||
firstTurn = () => {
|
||||
this.battle?.seekTurn(0);
|
||||
};
|
||||
lastTurn = () => {
|
||||
this.battle?.seekTurn(Infinity);
|
||||
};
|
||||
goToTurn = (e: Event) => {
|
||||
const turn = this.base?.querySelector<HTMLInputElement>('input[name=turn]')?.value;
|
||||
if (!turn?.trim()) return this.closeTurn(e);
|
||||
let turnNum = Number(turn);
|
||||
if (turn === 'e' || turn === 'end' || turn === 'f' || turn === 'finish') turnNum = Infinity;
|
||||
if (isNaN(turnNum) || turnNum < 0) alert("Invalid turn");
|
||||
this.battle?.seekTurn(turnNum);
|
||||
this.closeTurn(e);
|
||||
};
|
||||
switchViewpoint = () => {
|
||||
this.battle?.switchViewpoint();
|
||||
if (this.battle?.viewpointSwitched) {
|
||||
PSRouter.replace(this.stripQuery(this.props.id) + '?p2');
|
||||
} else {
|
||||
PSRouter.replace(this.stripQuery(this.props.id));
|
||||
}
|
||||
};
|
||||
this.closeTurn(e);
|
||||
};
|
||||
switchViewpoint = () => {
|
||||
this.battle?.switchViewpoint();
|
||||
if (this.battle?.viewpointSwitched) {
|
||||
PSRouter.replace(this.stripQuery(this.props.id) + '?p2');
|
||||
} else {
|
||||
PSRouter.replace(this.stripQuery(this.props.id));
|
||||
}
|
||||
};
|
||||
clickDownload = (e: MouseEvent) => {
|
||||
if (!this.battle) {
|
||||
// should never happen
|
||||
alert("Wait for the battle to finish loading before downloading.");
|
||||
return;
|
||||
}
|
||||
if (!this.battle) {
|
||||
// should never happen
|
||||
alert("Wait for the battle to finish loading before downloading.");
|
||||
return;
|
||||
}
|
||||
let filename = (this.battle.tier || 'Battle').replace(/[^A-Za-z0-9]/g, '');
|
||||
|
||||
// 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);
|
||||
|
||||
const a = e.currentTarget as HTMLAnchorElement;
|
||||
a.href = BattleLog.createReplayFileHref({battle: this.battle});
|
||||
const a = e.currentTarget as HTMLAnchorElement;
|
||||
a.href = BattleLog.createReplayFileHref({ battle: this.battle });
|
||||
a.download = filename + '.html';
|
||||
|
||||
e.stopPropagation();
|
||||
};
|
||||
getSpeed() {
|
||||
if (!this.battle) return 'normal';
|
||||
if (this.battle.messageFadeTime <= 40) {
|
||||
return 'hyperfast';
|
||||
} else if (this.battle.messageFadeTime <= 50) {
|
||||
return 'fast';
|
||||
} else if (this.battle.messageFadeTime >= 500) {
|
||||
return 'slow';
|
||||
} else if (this.battle.messageFadeTime >= 1000) {
|
||||
return 'reallyslow';
|
||||
}
|
||||
return 'normal';
|
||||
}
|
||||
changeSpeed = (e: Event | {target: HTMLSelectElement}) => {
|
||||
const speed = (e.target as HTMLSelectElement).value;
|
||||
const fadeTable = {
|
||||
hyperfast: 40,
|
||||
fast: 50,
|
||||
normal: 300,
|
||||
slow: 500,
|
||||
reallyslow: 1000
|
||||
};
|
||||
const delayTable = {
|
||||
hyperfast: 1,
|
||||
fast: 1,
|
||||
normal: 1,
|
||||
slow: 1000,
|
||||
reallyslow: 3000
|
||||
};
|
||||
if (!this.battle) return;
|
||||
this.battle.messageShownTime = delayTable[speed];
|
||||
this.battle.messageFadeTime = fadeTable[speed];
|
||||
this.battle.scene.updateAcceleration();
|
||||
};
|
||||
stepSpeed(delta: number) {
|
||||
const target = this.base?.querySelector<HTMLSelectElement>('select[name=speed]');
|
||||
if (!target) return; // should never happen
|
||||
const values = ['reallyslow', 'slow', 'normal', 'fast', 'hyperfast'];
|
||||
const newValue = values[values.indexOf(target.value) + delta];
|
||||
if (newValue) {
|
||||
target.value = newValue;
|
||||
this.changeSpeed({target});
|
||||
}
|
||||
}
|
||||
toggleMute() {
|
||||
this.battle?.setMute(!BattleSound.muted);
|
||||
this.forceUpdate();
|
||||
}
|
||||
changeSound = (e: Event) => {
|
||||
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);
|
||||
this.forceUpdate();
|
||||
};
|
||||
changeDarkMode = (e: Event) => {
|
||||
const darkmode = (e.target as HTMLSelectElement).value as 'dark';
|
||||
PSReplays.darkMode = darkmode;
|
||||
PSReplays.updateDarkMode();
|
||||
this.forceUpdate();
|
||||
};
|
||||
openTurn = (e: Event) => {
|
||||
this.turnView = `${this.battle?.turn}` || true;
|
||||
this.autofocusTurnView = 'select';
|
||||
e.preventDefault();
|
||||
this.forceUpdate();
|
||||
};
|
||||
closeTurn = (e?: Event) => {
|
||||
this.turnView = false;
|
||||
e?.preventDefault();
|
||||
this.forceUpdate();
|
||||
};
|
||||
renderError(position: any) {
|
||||
if (this.resultError) {
|
||||
return <div class={PSRouter.showingLeft() ? 'mainbar has-sidebar' : 'mainbar'} style={position}><section class="section">
|
||||
<h1>Error</h1>
|
||||
<p>
|
||||
{this.resultError}
|
||||
</p>
|
||||
</section></div>;
|
||||
}
|
||||
getSpeed() {
|
||||
if (!this.battle) return 'normal';
|
||||
if (this.battle.messageFadeTime <= 40) {
|
||||
return 'hyperfast';
|
||||
} else if (this.battle.messageFadeTime <= 50) {
|
||||
return 'fast';
|
||||
} else if (this.battle.messageFadeTime >= 500) {
|
||||
return 'slow';
|
||||
} else if (this.battle.messageFadeTime >= 1000) {
|
||||
return 'reallyslow';
|
||||
}
|
||||
return 'normal';
|
||||
}
|
||||
changeSpeed = (e: Event | { target: HTMLSelectElement }) => {
|
||||
const speed = (e.target as HTMLSelectElement).value;
|
||||
const fadeTable = {
|
||||
hyperfast: 40,
|
||||
fast: 50,
|
||||
normal: 300,
|
||||
slow: 500,
|
||||
reallyslow: 1000,
|
||||
};
|
||||
const delayTable = {
|
||||
hyperfast: 1,
|
||||
fast: 1,
|
||||
normal: 1,
|
||||
slow: 1000,
|
||||
reallyslow: 3000,
|
||||
};
|
||||
if (!this.battle) return;
|
||||
this.battle.messageShownTime = delayTable[speed as 'fast'];
|
||||
this.battle.messageFadeTime = fadeTable[speed as 'fast'];
|
||||
this.battle.scene.updateAcceleration();
|
||||
};
|
||||
stepSpeed(delta: number) {
|
||||
const target = this.base?.querySelector<HTMLSelectElement>('select[name=speed]');
|
||||
if (!target) return; // should never happen
|
||||
const values = ['reallyslow', 'slow', 'normal', 'fast', 'hyperfast'];
|
||||
const newValue = values[values.indexOf(target.value) + delta];
|
||||
if (newValue) {
|
||||
target.value = newValue;
|
||||
this.changeSpeed({ target });
|
||||
}
|
||||
}
|
||||
toggleMute() {
|
||||
this.battle?.setMute(!BattleSound.muted);
|
||||
this.forceUpdate();
|
||||
}
|
||||
changeSound = (e: Event) => {
|
||||
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.88125800126558);
|
||||
this.forceUpdate();
|
||||
};
|
||||
changeDarkMode = (e: Event) => {
|
||||
const darkmode = (e.target as HTMLSelectElement).value as 'dark';
|
||||
PSReplays.darkMode = darkmode;
|
||||
PSReplays.updateDarkMode();
|
||||
this.forceUpdate();
|
||||
};
|
||||
openTurn = (e: Event) => {
|
||||
this.turnView = `${this.battle?.turn || ''}` || true;
|
||||
this.autofocusTurnView = 'select';
|
||||
e.preventDefault();
|
||||
this.forceUpdate();
|
||||
};
|
||||
closeTurn = (e?: Event) => {
|
||||
this.turnView = false;
|
||||
e?.preventDefault();
|
||||
this.forceUpdate();
|
||||
};
|
||||
renderError(position: any) {
|
||||
if (this.resultError) {
|
||||
return <div class={PSRouter.showingLeft() ? 'mainbar has-sidebar' : 'mainbar'} style={position}>
|
||||
<section class="section">
|
||||
<h1>Error</h1>
|
||||
<p>
|
||||
{this.resultError}
|
||||
</p>
|
||||
</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'}}>
|
||||
<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'}} />
|
||||
<img src="//play.pokemonshowdown.com/sprites/gen5ani/unown-t.gif" alt="" style={{imageRendering: 'pixelated'}} />
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="//play.pokemonshowdown.com/sprites/gen5ani/unown-f.gif" alt="" style={{imageRendering: 'pixelated'}} />
|
||||
<img src="//play.pokemonshowdown.com/sprites/gen5ani/unown-o.gif" alt="" style={{imageRendering: 'pixelated'}} />
|
||||
<img src="//play.pokemonshowdown.com/sprites/gen5ani/unown-u.gif" alt="" style={{imageRendering: 'pixelated'}} />
|
||||
<img src="//play.pokemonshowdown.com/sprites/gen5ani/unown-n.gif" alt="" style={{imageRendering: 'pixelated'}} />
|
||||
<img src="//play.pokemonshowdown.com/sprites/gen5ani/unown-d.gif" alt="" style={{imageRendering: 'pixelated'}} />
|
||||
</div>
|
||||
</section><section class="section">
|
||||
<h1>Not Found</h1>
|
||||
// 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' }}>
|
||||
<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' }} />
|
||||
<img src="//play.pokemonshowdown.com/sprites/gen5ani/unown-t.gif" alt="" style={{ imageRendering: 'pixelated' }} />
|
||||
</div>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<img src="//play.pokemonshowdown.com/sprites/gen5ani/unown-f.gif" alt="" style={{ imageRendering: 'pixelated' }} />
|
||||
<img src="//play.pokemonshowdown.com/sprites/gen5ani/unown-o.gif" alt="" style={{ imageRendering: 'pixelated' }} />
|
||||
<img src="//play.pokemonshowdown.com/sprites/gen5ani/unown-u.gif" alt="" style={{ imageRendering: 'pixelated' }} />
|
||||
<img src="//play.pokemonshowdown.com/sprites/gen5ani/unown-n.gif" alt="" style={{ imageRendering: 'pixelated' }} />
|
||||
<img src="//play.pokemonshowdown.com/sprites/gen5ani/unown-d.gif" alt="" style={{ imageRendering: 'pixelated' }} />
|
||||
</div>
|
||||
</section><section class="section">
|
||||
<h1>Not Found</h1>
|
||||
<p>
|
||||
The battle you're looking for has expired. Battles expire after 15 minutes of inactivity unless they're saved.
|
||||
</p>
|
||||
<p>
|
||||
In the future, remember to click <strong>Upload and share replay</strong> to save a replay permanently.
|
||||
</p>
|
||||
</section>
|
||||
</div>;
|
||||
}
|
||||
renderControls() {
|
||||
const atEnd = !this.battle || this.battle.atQueueEnd;
|
||||
const atStart = !this.battle?.started;
|
||||
|
||||
if (this.turnView) {
|
||||
const value = this.turnView === true ? undefined : this.turnView;
|
||||
this.turnView = true;
|
||||
return <div class="replay-controls"><section class="section">
|
||||
<form onSubmit={this.goToTurn}>
|
||||
Turn? <input name="turn" autofocus value={value} inputMode="numeric" class="textbox" size={5} /> {}
|
||||
<button type="submit" class="button"><strong>Go</strong></button> {}
|
||||
<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.
|
||||
</p>
|
||||
</section></div>;
|
||||
}
|
||||
|
||||
return <div class="replay-controls">
|
||||
<p>
|
||||
The battle you're looking for has expired. Battles expire after 15 minutes of inactivity unless they're saved.
|
||||
{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 ? (
|
||||
<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}
|
||||
>
|
||||
<i class="fa fa-step-backward"></i><br />Prev turn
|
||||
</button>
|
||||
<button class="button button-last" disabled={atEnd} style={{ marginRight: '2px' }} onClick={this.nextTurn}>
|
||||
<i class="fa fa-step-forward"></i><br />Skip turn
|
||||
</button>
|
||||
<button class="button button-last" disabled={atEnd} onClick={this.lastTurn}>
|
||||
<i class="fa fa-fast-forward"></i><br />Skip to end
|
||||
</button> {}
|
||||
<button class="button" onClick={this.openTurn}>
|
||||
<i class="fa fa-repeat"></i> Go to turn...
|
||||
</button>
|
||||
</p>
|
||||
<p>
|
||||
In the future, remember to click <strong>Upload and share replay</strong> to save a replay permanently.
|
||||
<label class="optgroup">
|
||||
Speed:<br />
|
||||
<select name="speed" class="button" onChange={this.changeSpeed} value={this.getSpeed()}>
|
||||
<option value="hyperfast">Hyperfast</option>
|
||||
<option value="fast">Fast</option>
|
||||
<option value="normal">Normal</option>
|
||||
<option value="slow">Slow</option>
|
||||
<option value="reallyslow">Really slow</option>
|
||||
</select>
|
||||
</label> {}
|
||||
<label class="optgroup">
|
||||
Sound:<br />
|
||||
<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>
|
||||
</select>
|
||||
</label> {}
|
||||
<label class="optgroup">
|
||||
Dark mode:<br />
|
||||
<select name="darkmode" class="button" onChange={this.changeDarkMode} value={PSReplays.darkMode}>
|
||||
<option value="auto">Automatic</option>
|
||||
<option value="dark">Dark</option>
|
||||
<option value="light">Light</option>
|
||||
</select>
|
||||
</label> {}
|
||||
<label class="optgroup">
|
||||
Viewpoint:<br />
|
||||
<button onClick={this.switchViewpoint} name="viewpoint" class={this.battle ? 'button' : 'button disabled'}>
|
||||
{(this.battle?.viewpointSwitched ? this.result?.players[1] : this.result?.players[0] || "Player")} {}
|
||||
<i class="fa fa-random" aria-label="Switch viewpoint"></i>
|
||||
</button>
|
||||
</label>
|
||||
</p>
|
||||
</section></div>;
|
||||
}
|
||||
renderControls() {
|
||||
const atEnd = !this.battle || this.battle.atQueueEnd;
|
||||
const atStart = !this.battle?.started;
|
||||
{this.result ? <h1>
|
||||
<strong>{this.result.format}</strong>: {this.result.players.join(' vs. ')}
|
||||
</h1> : <h1>
|
||||
<em>Loading...</em>
|
||||
</h1>}
|
||||
{this.result ? <p>
|
||||
<a class="button" href="#" onClick={this.clickDownload} style={{ float: 'right' }}>
|
||||
<i class="fa fa-download" aria-hidden></i> Download
|
||||
</a>
|
||||
{this.result.uploadtime ? new Date(this.result.uploadtime * 1000).toDateString() : "Unknown upload date"}
|
||||
{this.result.rating ? [` | `, <em>Rating:</em>, ` ${this.result.rating}`] : ''}
|
||||
{/* {} <code>{this.keyCode}</code> */}
|
||||
</p> : <p> </p>}
|
||||
{!PSRouter.showingLeft() && <p>
|
||||
<a href={PSRouter.leftLoc || '.'} class="button"><i class="fa fa-caret-left"></i> More replays</a>
|
||||
</p>}
|
||||
</div>;
|
||||
}
|
||||
override render() {
|
||||
let position: any = {};
|
||||
if (PSRouter.showingLeft()) {
|
||||
if (PSRouter.stickyRight) {
|
||||
position = { position: 'sticky', top: '0' };
|
||||
} else {
|
||||
position = { position: 'sticky', bottom: '0' };
|
||||
}
|
||||
}
|
||||
|
||||
if (this.turnView) {
|
||||
const value = this.turnView === true ? undefined : this.turnView;
|
||||
this.turnView = true;
|
||||
return <div class="replay-controls"><section class="section">
|
||||
<form onSubmit={this.goToTurn}>
|
||||
Turn? <input name="turn" autofocus value={value} inputMode="numeric" class="textbox" size={5} /> {}
|
||||
<button type="submit" class="button"><strong>Go</strong></button> {}
|
||||
<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.
|
||||
</p>
|
||||
</section></div>;
|
||||
}
|
||||
if (this.result === null) return this.renderError(position);
|
||||
|
||||
return <div class="replay-controls">
|
||||
<p>
|
||||
{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) ?
|
||||
<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}>
|
||||
<i class="fa fa-step-backward"></i><br />Prev turn
|
||||
</button>
|
||||
<button class="button button-last" disabled={atEnd} style={{marginRight:'2px'}} onClick={this.nextTurn}>
|
||||
<i class="fa fa-step-forward"></i><br />Skip turn
|
||||
</button>
|
||||
<button class="button button-last" disabled={atEnd} onClick={this.lastTurn}>
|
||||
<i class="fa fa-fast-forward"></i><br />Skip to end
|
||||
</button> {}
|
||||
<button class="button" onClick={this.openTurn}>
|
||||
<i class="fa fa-repeat"></i> Go to turn...
|
||||
</button>
|
||||
</p>
|
||||
<p>
|
||||
<label class="optgroup">
|
||||
Speed:<br />
|
||||
<select name="speed" class="button" onChange={this.changeSpeed} value={this.getSpeed()}>
|
||||
<option value="hyperfast">Hyperfast</option>
|
||||
<option value="fast">Fast</option>
|
||||
<option value="normal">Normal</option>
|
||||
<option value="slow">Slow</option>
|
||||
<option value="reallyslow">Really slow</option>
|
||||
</select>
|
||||
</label> {}
|
||||
<label class="optgroup">
|
||||
Sound:<br />
|
||||
<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>
|
||||
</select>
|
||||
</label> {}
|
||||
<label class="optgroup">
|
||||
Dark mode:<br />
|
||||
<select name="darkmode" class="button" onChange={this.changeDarkMode} value={PSReplays.darkMode}>
|
||||
<option value="auto">Automatic</option>
|
||||
<option value="dark">Dark</option>
|
||||
<option value="light">Light</option>
|
||||
</select>
|
||||
</label> {}
|
||||
<label class="optgroup">
|
||||
Viewpoint:<br />
|
||||
<button onClick={this.switchViewpoint} name="viewpoint" class={this.battle ? 'button' : 'button disabled'}>
|
||||
{(this.battle?.viewpointSwitched ? this.result?.players[1] : this.result?.players[0] || "Player")} {}
|
||||
<i class="fa fa-random" aria-label="Switch viewpoint"></i>
|
||||
</button>
|
||||
</label>
|
||||
</p>
|
||||
{this.result ? <h1>
|
||||
<strong>{this.result.format}</strong>: {this.result.players.join(' vs. ')}
|
||||
</h1> : <h1>
|
||||
<em>Loading...</em>
|
||||
</h1>}
|
||||
{this.result ? <p>
|
||||
<a class="button" href="#" onClick={this.clickDownload} style={{float: 'right'}}>
|
||||
<i class="fa fa-download" aria-hidden></i> Download
|
||||
</a>
|
||||
{this.result.uploadtime ? new Date(this.result.uploadtime * 1000).toDateString() : "Unknown upload date"}
|
||||
{this.result.rating ? [` | `, <em>Rating:</em>, ` ${this.result.rating}`] : ''}
|
||||
{/* {} <code>{this.keyCode}</code> */}
|
||||
</p> : <p> </p>}
|
||||
{!PSRouter.showingLeft() && <p>
|
||||
<a href={PSRouter.leftLoc || '.'} class="button"><i class="fa fa-caret-left"></i> More replays</a>
|
||||
</p>}
|
||||
</div>;
|
||||
}
|
||||
override render() {
|
||||
let position: any = {};
|
||||
if (PSRouter.showingLeft()) {
|
||||
if (PSRouter.stickyRight) {
|
||||
position = {position: 'sticky', top: '0'};
|
||||
} else {
|
||||
position = {position: 'sticky', bottom: '0'};
|
||||
}
|
||||
}
|
||||
|
||||
if (this.result === null) return this.renderError(position);
|
||||
|
||||
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>;
|
||||
}
|
||||
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>;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,484 +1,513 @@
|
|||
/** @jsx preact.h */
|
||||
import preact from 'preact';
|
||||
import {Net, PSModel} from './utils';
|
||||
import {BattlePanel} from './replays-battle';
|
||||
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;
|
||||
declare const Config: any;
|
||||
|
||||
interface ReplayResult {
|
||||
uploadtime: number;
|
||||
id: string;
|
||||
format: string;
|
||||
players: string[];
|
||||
password?: string;
|
||||
private?: number;
|
||||
rating?: number;
|
||||
uploadtime: number;
|
||||
id: string;
|
||||
format: string;
|
||||
players: string[];
|
||||
password?: string;
|
||||
private?: number;
|
||||
rating?: number;
|
||||
}
|
||||
|
||||
class SearchPanel extends preact.Component<{id: string}> {
|
||||
results: ReplayResult[] | null = null;
|
||||
resultError: string | null = null;
|
||||
format = '';
|
||||
user = '';
|
||||
isPrivate = false;
|
||||
byRating = false;
|
||||
page = 1;
|
||||
loggedInUser: string | null = null;
|
||||
loggedInUserIsSysop = false;
|
||||
sort = 'date';
|
||||
override componentDidMount() {
|
||||
Net('/check-login.php').get().then(result => {
|
||||
if (result.charAt(0) !== ']') return;
|
||||
const [userid, sysop] = result.slice(1).split(',');
|
||||
this.loggedInUser = userid;
|
||||
this.loggedInUserIsSysop = !!sysop;
|
||||
this.forceUpdate();
|
||||
});
|
||||
this.updateSearch(Net.decodeQuery(this.props.id));
|
||||
}
|
||||
override componentDidUpdate(previousProps: this['props']) {
|
||||
if (this.props.id === previousProps.id) return;
|
||||
const query = Net.decodeQuery(this.props.id);
|
||||
const page = parseInt(query.page || '1');
|
||||
const byRating = (query.sort === 'rating');
|
||||
if (page !== this.page || byRating !== this.byRating) this.updateSearch(query);
|
||||
}
|
||||
updateSearch(query: {[k: string]: string}) {
|
||||
const user = query.user || '';
|
||||
const format = query.format || '';
|
||||
const page = parseInt(query.page || '1');
|
||||
const isPrivate = !!query.private;
|
||||
this.byRating = (query.sort === 'rating');
|
||||
this.search(user, format, isPrivate, page);
|
||||
}
|
||||
parseResponse(response: string, isPrivate?: boolean) {
|
||||
this.results = null;
|
||||
this.resultError = null;
|
||||
class SearchPanel extends preact.Component<{ id: string }> {
|
||||
results: ReplayResult[] | null = null;
|
||||
resultError: string | null = null;
|
||||
format = '';
|
||||
user = '';
|
||||
isPrivate = false;
|
||||
byRating = false;
|
||||
page = 1;
|
||||
loggedInUser: string | null = null;
|
||||
loggedInUserIsSysop = false;
|
||||
sort = 'date';
|
||||
override componentDidMount() {
|
||||
Net('/check-login.php').get().then(result => {
|
||||
if (!result.startsWith(']')) return;
|
||||
const [userid, sysop] = result.slice(1).split(',');
|
||||
this.loggedInUser = userid;
|
||||
this.loggedInUserIsSysop = !!sysop;
|
||||
this.forceUpdate();
|
||||
});
|
||||
this.updateSearch(Net.decodeQuery(this.props.id));
|
||||
}
|
||||
override componentDidUpdate(previousProps: this['props']) {
|
||||
if (this.props.id === previousProps.id) return;
|
||||
const query = Net.decodeQuery(this.props.id);
|
||||
const page = parseInt(query.page || '1');
|
||||
const byRating = (query.sort === 'rating');
|
||||
if (page !== this.page || byRating !== this.byRating) this.updateSearch(query);
|
||||
}
|
||||
updateSearch(query: { [k: string]: string }) {
|
||||
const user = query.user || '';
|
||||
const format = query.format || '';
|
||||
const page = parseInt(query.page || '1');
|
||||
const isPrivate = !!query.private;
|
||||
this.byRating = (query.sort === 'rating');
|
||||
this.search(user, format, isPrivate, page);
|
||||
}
|
||||
parseResponse(response: string, isPrivate?: boolean) {
|
||||
this.results = null;
|
||||
this.resultError = null;
|
||||
|
||||
if (isPrivate) {
|
||||
if (response.charAt(0) !== ']') {
|
||||
this.resultError = `Unrecognized response: ${response}`;
|
||||
return;
|
||||
}
|
||||
response = response.slice(1);
|
||||
}
|
||||
const results = JSON.parse(response);
|
||||
if (!Array.isArray(results)) {
|
||||
this.resultError = results.actionerror || `Unrecognized response: ${response}`;
|
||||
return;
|
||||
}
|
||||
this.results = results;
|
||||
}
|
||||
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;
|
||||
if (isPrivate) {
|
||||
if (!response.startsWith(']')) {
|
||||
this.resultError = `Unrecognized response: ${response}`;
|
||||
return;
|
||||
}
|
||||
response = response.slice(1);
|
||||
}
|
||||
const results = JSON.parse(response);
|
||||
if (!Array.isArray(results)) {
|
||||
this.resultError = results.actionerror || `Unrecognized response: ${response}`;
|
||||
return;
|
||||
}
|
||||
this.results = results;
|
||||
}
|
||||
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;
|
||||
|
||||
if (!format && !user) return this.recent();
|
||||
this.user = user;
|
||||
this.format = format;
|
||||
this.isPrivate = !!isPrivate;
|
||||
this.page = page;
|
||||
this.results = null;
|
||||
this.resultError = null;
|
||||
if (user || !format) this.byRating = false;
|
||||
if (!format && !user) return this.recent();
|
||||
this.user = user;
|
||||
this.format = format;
|
||||
this.isPrivate = !!isPrivate;
|
||||
this.page = page;
|
||||
this.results = null;
|
||||
this.resultError = null;
|
||||
if (user || !format) this.byRating = false;
|
||||
|
||||
if (!format && !user) {
|
||||
PSRouter.replace('')
|
||||
} else {
|
||||
PSRouter.replace('?' + Net.encodeQuery({
|
||||
user: user || undefined,
|
||||
format: format || undefined,
|
||||
private: isPrivate ? '1' : undefined,
|
||||
page: page === 1 ? undefined : page,
|
||||
sort: this.byRating ? 'rating' : undefined,
|
||||
}));
|
||||
}
|
||||
this.forceUpdate();
|
||||
Net(`/api/replays/${isPrivate ? 'searchprivate' : 'search'}`).get({
|
||||
query: {
|
||||
username: this.user,
|
||||
format: this.format,
|
||||
page,
|
||||
sort: this.byRating ? 'rating' : undefined,
|
||||
},
|
||||
}).then(response => {
|
||||
if (this.format !== format || this.user !== user) return;
|
||||
this.parseResponse(response, true);
|
||||
this.forceUpdate();
|
||||
}).catch(error => {
|
||||
if (this.format !== '' || this.user !== '') return;
|
||||
this.resultError = '' + error;
|
||||
this.forceUpdate();
|
||||
});
|
||||
}
|
||||
modLink(overrides: {page?: number, sort?: string}) {
|
||||
const newPage = (overrides.page !== undefined ? this.page + overrides.page : 1);
|
||||
return './?' + Net.encodeQuery({
|
||||
user: this.user || undefined,
|
||||
format: this.format || undefined,
|
||||
private: this.isPrivate ? '1' : undefined,
|
||||
page: newPage === 1 ? undefined : newPage,
|
||||
sort: (overrides.sort ? overrides.sort === 'rating' : this.byRating) ? 'rating' : undefined,
|
||||
});
|
||||
}
|
||||
recent() {
|
||||
this.format = '';
|
||||
this.user = '';
|
||||
this.results = null;
|
||||
this.forceUpdate();
|
||||
Net('/api/replays/recent').get().then(response => {
|
||||
if (this.format !== '' || this.user !== '') return;
|
||||
this.parseResponse(response, true);
|
||||
this.forceUpdate();
|
||||
}).catch(error => {
|
||||
if (this.format !== '' || this.user !== '') return;
|
||||
this.resultError = '' + error;
|
||||
this.forceUpdate();
|
||||
});
|
||||
}
|
||||
submitForm = (e: Event) => {
|
||||
e.preventDefault();
|
||||
const format = this.base!.querySelector<HTMLInputElement>('input[name=format]')?.value || '';
|
||||
const user = this.base!.querySelector<HTMLInputElement>('input[name=user]')?.value || '';
|
||||
const isPrivate = !this.base!.querySelector<HTMLInputElement>('input[name=private]')?.checked;
|
||||
this.search(user, format, isPrivate);
|
||||
};
|
||||
cancelForm = (e: Event) => {
|
||||
e.preventDefault();
|
||||
this.search('', '');
|
||||
};
|
||||
searchLoggedIn = (e: Event) => {
|
||||
if (!this.loggedInUser) return; // shouldn't happen
|
||||
(this.base!.querySelector('input[name=user]') as HTMLInputElement).value = this.loggedInUser;
|
||||
this.submitForm(e);
|
||||
};
|
||||
url(replay: ReplayResult) {
|
||||
const viewpointSwitched = (toID(replay.players[1]) === toID(this.user));
|
||||
return replay.id + (replay.password ? `-${replay.password}pw` : '') + (viewpointSwitched ? '?p2' : '');
|
||||
}
|
||||
formatid(replay: ReplayResult) {
|
||||
let formatid = replay.format;
|
||||
if (!formatid.startsWith('gen') || !/[0-9]/.test(formatid.charAt(3))) {
|
||||
// 2013 Oct 14, two days after X and Y were released; good enough
|
||||
// estimate for when we renamed `ou` to `gen5ou`.
|
||||
formatid = (replay.uploadtime > 1381734000 ? 'gen6' : 'gen5') + formatid;
|
||||
}
|
||||
if (!/^gen[0-9]+-/.test(formatid)) {
|
||||
formatid = formatid.slice(0, 4) + '-' + formatid.slice(4);
|
||||
}
|
||||
return formatid;
|
||||
}
|
||||
override render() {
|
||||
const activelySearching = !!(this.format || this.user);
|
||||
const hasNextPageLink = (this.results?.length || 0) > 50;
|
||||
const results = hasNextPageLink ? this.results!.slice(0, 50) : this.results;
|
||||
const searchResults = <ul class="linklist">
|
||||
{(this.resultError && <li>
|
||||
<strong class="message-error">{this.resultError}</strong>
|
||||
</li>) ||
|
||||
(!results && <li>
|
||||
<em>Loading...</em>
|
||||
</li>) ||
|
||||
(results?.map(result => <li>
|
||||
<a href={this.url(result)} class="blocklink">
|
||||
<small>{result.format}{result.rating ? ` (Rating: ${result.rating})` : ''}<br /></small>
|
||||
{!!result.private && <i class="fa fa-lock"></i>} {}
|
||||
<strong>{result.players[0]}</strong> vs. <strong>{result.players[1]}</strong>
|
||||
</a>
|
||||
</li>))}
|
||||
</ul>;
|
||||
return <div class={PSRouter.showingRight() ? 'sidebar' : ''}>
|
||||
<section class="section first-section">
|
||||
<h1>Search replays</h1>
|
||||
<form onSubmit={this.submitForm}>
|
||||
<p>
|
||||
<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>}
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label>Format:<br />
|
||||
<input type="search" class="textbox" name="format" placeholder="(blank = any format)" size={30} /></label>
|
||||
</p>
|
||||
<p>
|
||||
<label class="checkbox inline"><input type="radio" name="private" value="" /> Public</label> {}
|
||||
<label class="checkbox inline"><input type="radio" name="private" value="1" /> Private (your own replays only)</label>
|
||||
</p>
|
||||
<p>
|
||||
<button type="submit" class="button"><i class="fa fa-search" aria-hidden></i> <strong>Search</strong></button> {}
|
||||
{activelySearching && <button class="button" onClick={this.cancelForm}>Cancel</button>}
|
||||
</p>
|
||||
{activelySearching && <h1 aria-label="Results"></h1>}
|
||||
{activelySearching && this.format && !this.user && <p>
|
||||
Sort by: {}
|
||||
<a href={this.modLink({sort: 'date'})} class={`button button-first${this.byRating ? '' : ' disabled'}`}>
|
||||
Date
|
||||
</a>
|
||||
<a href={this.modLink({sort: 'rating'})} class={`button button-last${this.byRating ? ' disabled' : ''}`}>
|
||||
Rating
|
||||
</a>
|
||||
</p>}
|
||||
{activelySearching && this.page > 1 && <p class="pagelink">
|
||||
<a href={this.modLink({page: -1})} class="button"><i class="fa fa-caret-up"></i><br />Page {this.page - 1}</a>
|
||||
</p>}
|
||||
{activelySearching && searchResults}
|
||||
{activelySearching && (this.results?.length || 0) > 50 && <p class="pagelink">
|
||||
<a href={this.modLink({page: 1})} class="button">Page {this.page + 1}<br /><i class="fa fa-caret-down"></i></a>
|
||||
</p>}
|
||||
</form>
|
||||
</section>
|
||||
{!activelySearching && <FeaturedReplays />}
|
||||
{!activelySearching && <section class="section">
|
||||
<h1>Recent replays</h1>
|
||||
<ul class="linklist">
|
||||
{searchResults}
|
||||
</ul>
|
||||
</section>}
|
||||
</div>;
|
||||
}
|
||||
if (!format && !user) {
|
||||
PSRouter.replace('');
|
||||
} else {
|
||||
PSRouter.replace('?' + Net.encodeQuery({
|
||||
user: user || undefined,
|
||||
format: format || undefined,
|
||||
private: isPrivate ? '1' : undefined,
|
||||
page: page === 1 ? undefined : page,
|
||||
sort: this.byRating ? 'rating' : undefined,
|
||||
}));
|
||||
}
|
||||
this.forceUpdate();
|
||||
Net(`/api/replays/${isPrivate ? 'searchprivate' : 'search'}`).get({
|
||||
query: {
|
||||
username: this.user,
|
||||
format: this.format,
|
||||
page,
|
||||
sort: this.byRating ? 'rating' : undefined,
|
||||
},
|
||||
}).then(response => {
|
||||
if (this.format !== format || this.user !== user) return;
|
||||
this.parseResponse(response, true);
|
||||
this.forceUpdate();
|
||||
}).catch(error => {
|
||||
if (this.format !== '' || this.user !== '') return;
|
||||
this.resultError = '' + error;
|
||||
this.forceUpdate();
|
||||
});
|
||||
}
|
||||
modLink(overrides: { page?: number, sort?: string }) {
|
||||
const newPage = (overrides.page !== undefined ? this.page + overrides.page : 1);
|
||||
return './?' + Net.encodeQuery({
|
||||
user: this.user || undefined,
|
||||
format: this.format || undefined,
|
||||
private: this.isPrivate ? '1' : undefined,
|
||||
page: newPage === 1 ? undefined : newPage,
|
||||
sort: (overrides.sort ? overrides.sort === 'rating' : this.byRating) ? 'rating' : undefined,
|
||||
});
|
||||
}
|
||||
recent() {
|
||||
this.format = '';
|
||||
this.user = '';
|
||||
this.results = null;
|
||||
this.forceUpdate();
|
||||
Net('/api/replays/recent').get().then(response => {
|
||||
if (this.format !== '' || this.user !== '') return;
|
||||
this.parseResponse(response, true);
|
||||
this.forceUpdate();
|
||||
}).catch(error => {
|
||||
if (this.format !== '' || this.user !== '') return;
|
||||
this.resultError = '' + error;
|
||||
this.forceUpdate();
|
||||
});
|
||||
}
|
||||
submitForm = (e: Event) => {
|
||||
e.preventDefault();
|
||||
const format = this.base!.querySelector<HTMLInputElement>('input[name=format]')?.value || '';
|
||||
const user = this.base!.querySelector<HTMLInputElement>('input[name=user]')?.value || '';
|
||||
const isPrivate = !this.base!.querySelector<HTMLInputElement>('input[name=private]')?.checked;
|
||||
this.search(user, format, isPrivate);
|
||||
};
|
||||
cancelForm = (e: Event) => {
|
||||
e.preventDefault();
|
||||
this.search('', '');
|
||||
};
|
||||
searchLoggedIn = (e: Event) => {
|
||||
if (!this.loggedInUser) return; // shouldn't happen
|
||||
this.base!.querySelector<HTMLInputElement>('input[name=user]')!.value = this.loggedInUser;
|
||||
this.submitForm(e);
|
||||
};
|
||||
url(replay: ReplayResult) {
|
||||
const viewpointSwitched = (toID(replay.players[1]) === toID(this.user));
|
||||
return replay.id + (replay.password ? `-${replay.password}pw` : '') + (viewpointSwitched ? '?p2' : '');
|
||||
}
|
||||
formatid(replay: ReplayResult) {
|
||||
let formatid = replay.format;
|
||||
if (!formatid.startsWith('gen') || !/[0-9]/.test(formatid.charAt(3))) {
|
||||
// 2013 Oct 14, two days after X and Y were released; good enough
|
||||
// estimate for when we renamed `ou` to `gen5ou`.
|
||||
formatid = (replay.uploadtime > 1381734000 ? 'gen6' : 'gen5') + formatid;
|
||||
}
|
||||
if (!/^gen[0-9]+-/.test(formatid)) {
|
||||
formatid = formatid.slice(0, 4) + '-' + formatid.slice(4);
|
||||
}
|
||||
return formatid;
|
||||
}
|
||||
override render() {
|
||||
const activelySearching = !!(this.format || this.user);
|
||||
const hasNextPageLink = (this.results?.length || 0) > 50;
|
||||
const results = hasNextPageLink ? this.results!.slice(0, 50) : this.results;
|
||||
const searchResults = <ul class="linklist">
|
||||
{(this.resultError && <li>
|
||||
<strong class="message-error">{this.resultError}</strong>
|
||||
</li>) ||
|
||||
(!results && <li>
|
||||
<em>Loading...</em>
|
||||
</li>) ||
|
||||
(results?.map(result => <li>
|
||||
<a href={this.url(result)} class="blocklink">
|
||||
<small>{result.format}{result.rating ? ` (Rating: ${result.rating})` : ''}<br /></small>
|
||||
{!!result.private && <i class="fa fa-lock"></i>} {}
|
||||
<strong>{result.players[0]}</strong> vs. <strong>{result.players[1]}</strong>
|
||||
</a>
|
||||
</li>))}
|
||||
</ul>;
|
||||
return <div class={PSRouter.showingRight() ? 'sidebar' : ''}>
|
||||
<section class="section first-section">
|
||||
<h1>Search replays</h1>
|
||||
<form onSubmit={this.submitForm}>
|
||||
<p>
|
||||
<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>}
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label>Format:<br />
|
||||
<input type="search" class="textbox" name="format" placeholder="(blank = any format)" size={30} /></label>
|
||||
</p>
|
||||
<p>
|
||||
<label class="checkbox inline"><input type="radio" name="private" value="" /> Public</label> {}
|
||||
<label class="checkbox inline"><input type="radio" name="private" value="1" /> Private (your own replays only)</label>
|
||||
</p>
|
||||
<p>
|
||||
<button type="submit" class="button"><i class="fa fa-search" aria-hidden></i> <strong>Search</strong></button> {}
|
||||
{activelySearching && <button class="button" onClick={this.cancelForm}>Cancel</button>}
|
||||
</p>
|
||||
{activelySearching && <h1 aria-label="Results"></h1>}
|
||||
{activelySearching && this.format && !this.user && <p>
|
||||
Sort by: {}
|
||||
<a href={this.modLink({ sort: 'date' })} class={`button button-first${this.byRating ? '' : ' disabled'}`}>
|
||||
Date
|
||||
</a>
|
||||
<a href={this.modLink({ sort: 'rating' })} class={`button button-last${this.byRating ? ' disabled' : ''}`}>
|
||||
Rating
|
||||
</a>
|
||||
</p>}
|
||||
{activelySearching && this.page > 1 && <p class="pagelink">
|
||||
<a href={this.modLink({ page: -1 })} class="button"><i class="fa fa-caret-up"></i><br />Page {this.page - 1}</a>
|
||||
</p>}
|
||||
{activelySearching && searchResults}
|
||||
{activelySearching && (this.results?.length || 0) > 50 && <p class="pagelink">
|
||||
<a href={this.modLink({ page: 1 })} class="button">Page {this.page + 1}<br /><i class="fa fa-caret-down"></i></a>
|
||||
</p>}
|
||||
</form>
|
||||
</section>
|
||||
{!activelySearching && <FeaturedReplays />}
|
||||
{!activelySearching && <section class="section">
|
||||
<h1>Recent replays</h1>
|
||||
<ul class="linklist">
|
||||
{searchResults}
|
||||
</ul>
|
||||
</section>}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
class FeaturedReplays extends preact.Component {
|
||||
moreFun = false;
|
||||
moreCompetitive = false;
|
||||
showMoreFun = (e: Event) => {
|
||||
e.preventDefault();
|
||||
this.moreFun = true;
|
||||
this.forceUpdate();
|
||||
};
|
||||
showMoreCompetitive = (e: Event) => {
|
||||
e.preventDefault();
|
||||
this.moreCompetitive = true;
|
||||
this.forceUpdate();
|
||||
};
|
||||
override render() {
|
||||
return <section class="section">
|
||||
<h1>Featured replays</h1>
|
||||
<ul class="linklist">
|
||||
<li><h2>Fun</h2></li>
|
||||
<li><a href="oumonotype-82345404" class="blocklink">
|
||||
<small>[gen6-oumonotype]<br /></small>
|
||||
<strong>kdarewolf</strong> vs. <strong>Onox</strong>
|
||||
<small><br />Protean + prediction</small>
|
||||
</a></li>
|
||||
<li><a href="anythinggoes-218380995?p2" class="blocklink">
|
||||
<small>[gen6-anythinggoes]<br /></small>
|
||||
<strong>Anta2</strong> vs. <strong>dscottnew</strong>
|
||||
<small><br />Cheek Pouch</small>
|
||||
</a></li>
|
||||
<li><a href="uberssuspecttest-147833524" class="blocklink">
|
||||
<small>[gen6-ubers]<br /></small>
|
||||
<strong>Metal Brellow</strong> vs. <strong>zig100</strong>
|
||||
<small><br />Topsy-Turvy</small>
|
||||
</a></li>
|
||||
{!this.moreFun && <li style={{paddingLeft: '8px'}}>
|
||||
<button class="button" onClick={this.showMoreFun}>More <i class="fa fa-caret-right" aria-hidden></i></button>
|
||||
</li>}
|
||||
{this.moreFun && <li><a href="smogondoubles-75588440?p2" class="blocklink">
|
||||
<small>[gen6-smogondoubles]<br /></small>
|
||||
<strong>jamace6</strong> vs. <strong>DubsWelder</strong>
|
||||
<small><br />Garchomp sweeps 11 pokemon</small>
|
||||
</a></li>}
|
||||
{this.moreFun && <li><a href="ou-20651579?p2" class="blocklink">
|
||||
<small>[gen5-ou]<br /></small>
|
||||
<strong>RainSeven07</strong> vs. <strong>my body is regi</strong>
|
||||
<small><br />An entire team based on Assist V-create</small>
|
||||
</a></li>}
|
||||
{this.moreFun && <li><a href="balancedhackmons7322360?p2" class="blocklink">
|
||||
<small>[gen5-balancedhackmons]<br /></small>
|
||||
<strong>a ver</strong> vs. <strong>Shuckie</strong>
|
||||
<small><br />To a ver's frustration, PP stall is viable in Balanced Hackmons</small>
|
||||
</a></li>}
|
||||
<h2>Competitive</h2>
|
||||
<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>
|
||||
</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>
|
||||
</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>
|
||||
</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>
|
||||
</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>
|
||||
</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>
|
||||
</li>}
|
||||
{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>
|
||||
</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>
|
||||
</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>
|
||||
</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>
|
||||
</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>
|
||||
</a></li>}
|
||||
</ul>
|
||||
</section>;
|
||||
}
|
||||
moreFun = false;
|
||||
moreCompetitive = false;
|
||||
showMoreFun = (e: Event) => {
|
||||
e.preventDefault();
|
||||
this.moreFun = true;
|
||||
this.forceUpdate();
|
||||
};
|
||||
showMoreCompetitive = (e: Event) => {
|
||||
e.preventDefault();
|
||||
this.moreCompetitive = true;
|
||||
this.forceUpdate();
|
||||
};
|
||||
override render() {
|
||||
return <section class="section">
|
||||
<h1>Featured replays</h1>
|
||||
<ul class="linklist">
|
||||
<li><h2>Fun</h2></li>
|
||||
<li><a href="oumonotype-82345404" class="blocklink">
|
||||
<small>[gen6-oumonotype]<br /></small>
|
||||
<strong>kdarewolf</strong> vs. <strong>Onox</strong>
|
||||
<small><br />Protean + prediction</small>
|
||||
</a></li>
|
||||
<li><a href="anythinggoes-218380995?p2" class="blocklink">
|
||||
<small>[gen6-anythinggoes]<br /></small>
|
||||
<strong>Anta2</strong> vs. <strong>dscottnew</strong>
|
||||
<small><br />Cheek Pouch</small>
|
||||
</a></li>
|
||||
<li><a href="uberssuspecttest-147833524" class="blocklink">
|
||||
<small>[gen6-ubers]<br /></small>
|
||||
<strong>Metal Brellow</strong> vs. <strong>zig100</strong>
|
||||
<small><br />Topsy-Turvy</small>
|
||||
</a></li>
|
||||
{!this.moreFun && <li style={{ paddingLeft: '8px' }}>
|
||||
<button class="button" onClick={this.showMoreFun}>More <i class="fa fa-caret-right" aria-hidden></i></button>
|
||||
</li>}
|
||||
{this.moreFun && <li><a href="smogondoubles-75588440?p2" class="blocklink">
|
||||
<small>[gen6-smogondoubles]<br /></small>
|
||||
<strong>jamace6</strong> vs. <strong>DubsWelder</strong>
|
||||
<small><br />Garchomp sweeps 11 pokemon</small>
|
||||
</a></li>}
|
||||
{this.moreFun && <li><a href="ou-20651579?p2" class="blocklink">
|
||||
<small>[gen5-ou]<br /></small>
|
||||
<strong>RainSeven07</strong> vs. <strong>my body is regi</strong>
|
||||
<small><br />An entire team based on Assist V-create</small>
|
||||
</a></li>}
|
||||
{this.moreFun && <li><a href="balancedhackmons7322360?p2" class="blocklink">
|
||||
<small>[gen5-balancedhackmons]<br /></small>
|
||||
<strong>a ver</strong> vs. <strong>Shuckie</strong>
|
||||
<small><br />To a ver's frustration, PP stall is viable in Balanced Hackmons</small>
|
||||
</a></li>}
|
||||
<h2>Competitive</h2>
|
||||
<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>
|
||||
</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>
|
||||
</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>
|
||||
</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>
|
||||
</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>
|
||||
</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>
|
||||
</li>}
|
||||
{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>
|
||||
</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>
|
||||
</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>
|
||||
</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>
|
||||
</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>
|
||||
</a></li>}
|
||||
</ul>
|
||||
</section>;
|
||||
}
|
||||
}
|
||||
|
||||
export const PSRouter = new class extends PSModel {
|
||||
baseLoc: string;
|
||||
leftLoc: string | null = null;
|
||||
rightLoc: string | null = null;
|
||||
forceSinglePanel = false;
|
||||
stickyRight = true;
|
||||
constructor() {
|
||||
super();
|
||||
const baseLocSlashIndex = document.location.href.lastIndexOf('/');
|
||||
this.baseLoc = document.location.href.slice(0, baseLocSlashIndex + 1);
|
||||
this.go(document.location.href);
|
||||
this.setSinglePanel(true);
|
||||
if (window.history) window.addEventListener('popstate', e => {
|
||||
PSRouter.popState(e);
|
||||
this.update();
|
||||
});
|
||||
window.onresize = () => {
|
||||
PSRouter.setSinglePanel();
|
||||
};
|
||||
}
|
||||
showingLeft() {
|
||||
return this.leftLoc !== null && (!this.forceSinglePanel || this.rightLoc === null);
|
||||
}
|
||||
showingRight() {
|
||||
return this.rightLoc !== null;
|
||||
}
|
||||
setSinglePanel(init?: boolean) {
|
||||
const singlePanel = window.innerWidth < 1300;
|
||||
const stickyRight = (window.innerHeight > 614);
|
||||
if (this.forceSinglePanel !== singlePanel || this.stickyRight !== stickyRight) {
|
||||
this.forceSinglePanel = singlePanel;
|
||||
this.stickyRight = stickyRight;
|
||||
if (!init) this.update();
|
||||
}
|
||||
}
|
||||
push(href: string): boolean {
|
||||
if (!href.startsWith(this.baseLoc)) return false;
|
||||
baseLoc: string;
|
||||
leftLoc: string | null = null;
|
||||
rightLoc: string | null = null;
|
||||
forceSinglePanel = false;
|
||||
stickyRight = true;
|
||||
constructor() {
|
||||
super();
|
||||
const baseLocSlashIndex = document.location.href.lastIndexOf('/');
|
||||
this.baseLoc = document.location.href.slice(0, baseLocSlashIndex + 1);
|
||||
this.go(document.location.href);
|
||||
this.setSinglePanel(true);
|
||||
if (window.history) window.addEventListener('popstate', e => {
|
||||
PSRouter.popState(e);
|
||||
this.update();
|
||||
});
|
||||
window.onresize = () => {
|
||||
PSRouter.setSinglePanel();
|
||||
};
|
||||
}
|
||||
showingLeft() {
|
||||
return this.leftLoc !== null && (!this.forceSinglePanel || this.rightLoc === null);
|
||||
}
|
||||
showingRight() {
|
||||
return this.rightLoc !== null;
|
||||
}
|
||||
setSinglePanel(init?: boolean) {
|
||||
const singlePanel = window.innerWidth < 1300;
|
||||
const stickyRight = (window.innerHeight > 614);
|
||||
if (this.forceSinglePanel !== singlePanel || this.stickyRight !== stickyRight) {
|
||||
this.forceSinglePanel = singlePanel;
|
||||
this.stickyRight = stickyRight;
|
||||
if (!init) this.update();
|
||||
}
|
||||
}
|
||||
push(href: string): boolean {
|
||||
if (!href.startsWith(this.baseLoc)) return false;
|
||||
|
||||
if (this.go(href)) {
|
||||
window.history?.pushState([this.leftLoc, this.rightLoc], '', href);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/** returns whether the URL should change */
|
||||
go(href: string): boolean {
|
||||
if (!href.startsWith(this.baseLoc)) return false;
|
||||
if (this.go(href)) {
|
||||
window.history?.pushState([this.leftLoc, this.rightLoc], '', href);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/** returns whether the URL should change */
|
||||
go(href: string): boolean {
|
||||
if (!href.startsWith(this.baseLoc)) return false;
|
||||
|
||||
const loc = href.slice(this.baseLoc.length);
|
||||
if (!loc || loc.startsWith('?')) {
|
||||
this.leftLoc = loc;
|
||||
if (this.forceSinglePanel) {
|
||||
this.rightLoc = null;
|
||||
} else {
|
||||
return this.rightLoc === null;
|
||||
}
|
||||
} else {
|
||||
this.rightLoc = loc;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
replace(loc: string) {
|
||||
const href = this.baseLoc + loc;
|
||||
if (this.go(href)) {
|
||||
window.history?.replaceState([this.leftLoc, this.rightLoc], '', href);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
popState(e: PopStateEvent) {
|
||||
if (Array.isArray(e.state)) {
|
||||
const [leftLoc, rightLoc] = e.state;
|
||||
this.leftLoc = leftLoc;
|
||||
this.rightLoc = rightLoc;
|
||||
if (this.forceSinglePanel) this.leftLoc = null;
|
||||
} else {
|
||||
this.leftLoc = null;
|
||||
this.rightLoc = null;
|
||||
this.go(document.location.href);
|
||||
}
|
||||
this.update();
|
||||
}
|
||||
const loc = href.slice(this.baseLoc.length);
|
||||
if (!loc || loc.startsWith('?')) {
|
||||
this.leftLoc = loc;
|
||||
if (this.forceSinglePanel) {
|
||||
this.rightLoc = null;
|
||||
} else {
|
||||
return this.rightLoc === null;
|
||||
}
|
||||
} else {
|
||||
this.rightLoc = loc;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
replace(loc: string) {
|
||||
const href = this.baseLoc + loc;
|
||||
if (this.go(href)) {
|
||||
window.history?.replaceState([this.leftLoc, this.rightLoc], '', href);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
popState(e: PopStateEvent) {
|
||||
if (Array.isArray(e.state)) {
|
||||
const [leftLoc, rightLoc] = e.state;
|
||||
this.leftLoc = leftLoc;
|
||||
this.rightLoc = rightLoc;
|
||||
if (this.forceSinglePanel) this.leftLoc = null;
|
||||
} else {
|
||||
this.leftLoc = null;
|
||||
this.rightLoc = null;
|
||||
this.go(document.location.href);
|
||||
}
|
||||
this.update();
|
||||
}
|
||||
};
|
||||
|
||||
export class PSReplays extends preact.Component {
|
||||
static darkMode: 'dark' | 'light' | 'auto' = 'auto';
|
||||
static updateDarkMode() {
|
||||
let dark = this.darkMode === 'dark' ? 'dark' : '';
|
||||
if (this.darkMode === 'auto') {
|
||||
dark = window.matchMedia?.('(prefers-color-scheme: dark)').matches ? 'dark' : '';
|
||||
}
|
||||
document.documentElement.className = dark;
|
||||
}
|
||||
override componentDidMount() {
|
||||
PSRouter.subscribe(() => this.forceUpdate());
|
||||
if (window.history) {
|
||||
this.base!.addEventListener('click', e => {
|
||||
let el = e.target as HTMLElement;
|
||||
for (; el; el = el.parentNode as HTMLElement) {
|
||||
if (el.tagName === 'A' && PSRouter.push((el as HTMLAnchorElement).href)) {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
this.forceUpdate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// load custom colors from loginserver
|
||||
Net(`https://${Config.routes.client}/config/colors.json`).get().then(response => {
|
||||
const data = JSON.parse(response);
|
||||
Object.assign(Config.customcolors, data);
|
||||
});
|
||||
}
|
||||
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}>
|
||||
{PSRouter.showingLeft() && <SearchPanel id={PSRouter.leftLoc!} />}
|
||||
{PSRouter.showingRight() && <BattlePanel id={PSRouter.rightLoc!} />}
|
||||
<div style={{clear: 'both'}}></div>
|
||||
</div>;
|
||||
}
|
||||
static darkMode: 'dark' | 'light' | 'auto' = 'auto';
|
||||
static updateDarkMode() {
|
||||
let dark = this.darkMode === 'dark' ? 'dark' : '';
|
||||
if (this.darkMode === 'auto') {
|
||||
dark = window.matchMedia?.('(prefers-color-scheme: dark)').matches ? 'dark' : '';
|
||||
}
|
||||
document.documentElement.className = dark;
|
||||
}
|
||||
override componentDidMount() {
|
||||
PSRouter.subscribe(() => this.forceUpdate());
|
||||
if (window.history) {
|
||||
this.base!.addEventListener('click', e => {
|
||||
let el = e.target as HTMLElement;
|
||||
for (; el; el = el.parentNode as HTMLElement) {
|
||||
if (el.tagName === 'A' && PSRouter.push((el as HTMLAnchorElement).href)) {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
this.forceUpdate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// load custom colors from loginserver
|
||||
Net(`https://${Config.routes.client}/config/colors.json`).get().then(response => {
|
||||
const data = JSON.parse(response);
|
||||
Object.assign(Config.customcolors, data);
|
||||
});
|
||||
}
|
||||
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}
|
||||
>
|
||||
{PSRouter.showingLeft() && <SearchPanel id={PSRouter.leftLoc!} />}
|
||||
{PSRouter.showingRight() && <BattlePanel id={PSRouter.rightLoc!} />}
|
||||
<div style={{ clear: 'both' }}></div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
preact.render(<PSReplays />, document.getElementById('main')!);
|
||||
|
||||
if (window.matchMedia?.('(prefers-color-scheme: dark)').matches) {
|
||||
document.documentElement.className = 'dark';
|
||||
document.documentElement.className = 'dark';
|
||||
}
|
||||
window.matchMedia?.('(prefers-color-scheme: dark)').addEventListener('change', event => {
|
||||
if (PSReplays.darkMode === 'auto') document.documentElement.className = event.matches ? "dark" : "";
|
||||
if (PSReplays.darkMode === 'auto') document.documentElement.className = event.matches ? "dark" : "";
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
@ -100,8 +100,8 @@ Net.encodeQuery = function (data: string | PostData) {
|
|||
}
|
||||
return urlencodedData;
|
||||
};
|
||||
Net.decodeQuery = function (query: string): {[key: string]: string} {
|
||||
let out = {};
|
||||
Net.decodeQuery = function (query: string): { [key: string]: string } {
|
||||
let out: { [key: string]: string } = {};
|
||||
const questionIndex = query.indexOf('?');
|
||||
if (questionIndex >= 0) query = query.slice(questionIndex + 1);
|
||||
for (const queryPart of query.split('&')) {
|
||||
|
|
|
|||
|
|
@ -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/*"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
58
tslint.json
58
tslint.json
|
|
@ -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": []
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user