mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-07-03 23:12:20 +02:00
Merge branch 'beta' into main
This commit is contained in:
commit
7f2d8b0155
384
.dependency-cruiser.cjs
Normal file
384
.dependency-cruiser.cjs
Normal file
@ -0,0 +1,384 @@
|
|||||||
|
/** @type {import('dependency-cruiser').IConfiguration} */
|
||||||
|
module.exports = {
|
||||||
|
forbidden: [
|
||||||
|
{
|
||||||
|
name: 'no-circular-at-runtime',
|
||||||
|
severity: 'warn',
|
||||||
|
comment:
|
||||||
|
'This dependency is part of a circular relationship. You might want to revise ' +
|
||||||
|
'your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ',
|
||||||
|
from: {},
|
||||||
|
to: {
|
||||||
|
circular: true,
|
||||||
|
viaOnly: {
|
||||||
|
dependencyTypesNot: [
|
||||||
|
'type-only'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'no-orphans',
|
||||||
|
comment:
|
||||||
|
"This is an orphan module - it's likely not used (anymore?). Either use it or " +
|
||||||
|
"remove it. If it's logical this module is an orphan (i.e. it's a config file), " +
|
||||||
|
"add an exception for it in your dependency-cruiser configuration. By default " +
|
||||||
|
"this rule does not scrutinize dot-files (e.g. .eslintrc.js), TypeScript declaration " +
|
||||||
|
"files (.d.ts), tsconfig.json and some of the babel and webpack configs.",
|
||||||
|
severity: 'warn',
|
||||||
|
from: {
|
||||||
|
orphan: true,
|
||||||
|
pathNot: [
|
||||||
|
'(^|/)[.][^/]+[.](?:js|cjs|mjs|ts|cts|mts|json)$', // dot files
|
||||||
|
'[.]d[.]ts$', // TypeScript declaration files
|
||||||
|
'(^|/)tsconfig[.]json$', // TypeScript config
|
||||||
|
'(^|/)(?:babel|webpack)[.]config[.](?:js|cjs|mjs|ts|cts|mts|json)$' // other configs
|
||||||
|
]
|
||||||
|
},
|
||||||
|
to: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'no-deprecated-core',
|
||||||
|
comment:
|
||||||
|
'A module depends on a node core module that has been deprecated. Find an alternative - these are ' +
|
||||||
|
"bound to exist - node doesn't deprecate lightly.",
|
||||||
|
severity: 'warn',
|
||||||
|
from: {},
|
||||||
|
to: {
|
||||||
|
dependencyTypes: [
|
||||||
|
'core'
|
||||||
|
],
|
||||||
|
path: [
|
||||||
|
'^v8/tools/codemap$',
|
||||||
|
'^v8/tools/consarray$',
|
||||||
|
'^v8/tools/csvparser$',
|
||||||
|
'^v8/tools/logreader$',
|
||||||
|
'^v8/tools/profile_view$',
|
||||||
|
'^v8/tools/profile$',
|
||||||
|
'^v8/tools/SourceMap$',
|
||||||
|
'^v8/tools/splaytree$',
|
||||||
|
'^v8/tools/tickprocessor-driver$',
|
||||||
|
'^v8/tools/tickprocessor$',
|
||||||
|
'^node-inspect/lib/_inspect$',
|
||||||
|
'^node-inspect/lib/internal/inspect_client$',
|
||||||
|
'^node-inspect/lib/internal/inspect_repl$',
|
||||||
|
'^async_hooks$',
|
||||||
|
'^punycode$',
|
||||||
|
'^domain$',
|
||||||
|
'^constants$',
|
||||||
|
'^sys$',
|
||||||
|
'^_linklist$',
|
||||||
|
'^_stream_wrap$'
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'not-to-deprecated',
|
||||||
|
comment:
|
||||||
|
'This module uses a (version of an) npm module that has been deprecated. Either upgrade to a later ' +
|
||||||
|
'version of that module, or find an alternative. Deprecated modules are a security risk.',
|
||||||
|
severity: 'warn',
|
||||||
|
from: {},
|
||||||
|
to: {
|
||||||
|
dependencyTypes: [
|
||||||
|
'deprecated'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'no-non-package-json',
|
||||||
|
severity: 'error',
|
||||||
|
comment:
|
||||||
|
"This module depends on an npm package that isn't in the 'dependencies' section of your package.json. " +
|
||||||
|
"That's problematic as the package either (1) won't be available on live (2 - worse) will be " +
|
||||||
|
"available on live with an non-guaranteed version. Fix it by adding the package to the dependencies " +
|
||||||
|
"in your package.json.",
|
||||||
|
from: {},
|
||||||
|
to: {
|
||||||
|
dependencyTypes: [
|
||||||
|
'npm-no-pkg',
|
||||||
|
'npm-unknown'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'not-to-unresolvable',
|
||||||
|
comment:
|
||||||
|
"This module depends on a module that cannot be found ('resolved to disk'). If it's an npm " +
|
||||||
|
'module: add it to your package.json. In all other cases you likely already know what to do.',
|
||||||
|
severity: 'error',
|
||||||
|
from: {},
|
||||||
|
to: {
|
||||||
|
couldNotResolve: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'no-duplicate-dep-types',
|
||||||
|
comment:
|
||||||
|
"Likely this module depends on an external ('npm') package that occurs more than once " +
|
||||||
|
"in your package.json i.e. bot as a devDependencies and in dependencies. This will cause " +
|
||||||
|
"maintenance problems later on.",
|
||||||
|
severity: 'warn',
|
||||||
|
from: {},
|
||||||
|
to: {
|
||||||
|
moreThanOneDependencyType: true,
|
||||||
|
// as it's pretty common to have a type import be a type only import
|
||||||
|
// _and_ (e.g.) a devDependency - don't consider type-only dependency
|
||||||
|
// types for this rule
|
||||||
|
dependencyTypesNot: ["type-only"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/* rules you might want to tweak for your specific situation: */
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'not-to-spec',
|
||||||
|
comment:
|
||||||
|
'This module depends on a spec (test) file. The sole responsibility of a spec file is to test code. ' +
|
||||||
|
"If there's something in a spec that's of use to other modules, it doesn't have that single " +
|
||||||
|
'responsibility anymore. Factor it out into (e.g.) a separate utility/ helper or a mock.',
|
||||||
|
severity: 'error',
|
||||||
|
from: {},
|
||||||
|
to: {
|
||||||
|
path: '[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'not-to-dev-dep',
|
||||||
|
severity: 'error',
|
||||||
|
comment:
|
||||||
|
"This module depends on an npm package from the 'devDependencies' section of your " +
|
||||||
|
'package.json. It looks like something that ships to production, though. To prevent problems ' +
|
||||||
|
"with npm packages that aren't there on production declare it (only!) in the 'dependencies'" +
|
||||||
|
'section of your package.json. If this module is development only - add it to the ' +
|
||||||
|
'from.pathNot re of the not-to-dev-dep rule in the dependency-cruiser configuration',
|
||||||
|
from: {
|
||||||
|
path: '^(src)',
|
||||||
|
pathNot: [
|
||||||
|
'[.](?:spec|test|setup|script)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$',
|
||||||
|
'src/test'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
to: {
|
||||||
|
dependencyTypes: [
|
||||||
|
'npm-dev',
|
||||||
|
],
|
||||||
|
// type only dependencies are not a problem as they don't end up in the
|
||||||
|
// production code or are ignored by the runtime.
|
||||||
|
dependencyTypesNot: [
|
||||||
|
'type-only'
|
||||||
|
],
|
||||||
|
pathNot: [
|
||||||
|
'node_modules/@types/'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'optional-deps-used',
|
||||||
|
severity: 'info',
|
||||||
|
comment:
|
||||||
|
"This module depends on an npm package that is declared as an optional dependency " +
|
||||||
|
"in your package.json. As this makes sense in limited situations only, it's flagged here. " +
|
||||||
|
"If you're using an optional dependency here by design - add an exception to your" +
|
||||||
|
"dependency-cruiser configuration.",
|
||||||
|
from: {},
|
||||||
|
to: {
|
||||||
|
dependencyTypes: [
|
||||||
|
'npm-optional'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'peer-deps-used',
|
||||||
|
comment:
|
||||||
|
"This module depends on an npm package that is declared as a peer dependency " +
|
||||||
|
"in your package.json. This makes sense if your package is e.g. a plugin, but in " +
|
||||||
|
"other cases - maybe not so much. If the use of a peer dependency is intentional " +
|
||||||
|
"add an exception to your dependency-cruiser configuration.",
|
||||||
|
severity: 'warn',
|
||||||
|
from: {},
|
||||||
|
to: {
|
||||||
|
dependencyTypes: [
|
||||||
|
'npm-peer'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
options: {
|
||||||
|
|
||||||
|
/* Which modules not to follow further when encountered */
|
||||||
|
doNotFollow: {
|
||||||
|
/* path: an array of regular expressions in strings to match against */
|
||||||
|
path: ['node_modules']
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Which modules to exclude */
|
||||||
|
// exclude : {
|
||||||
|
// /* path: an array of regular expressions in strings to match against */
|
||||||
|
// path: '',
|
||||||
|
// },
|
||||||
|
|
||||||
|
/* Which modules to exclusively include (array of regular expressions in strings)
|
||||||
|
dependency-cruiser will skip everything not matching this pattern
|
||||||
|
*/
|
||||||
|
// includeOnly : [''],
|
||||||
|
|
||||||
|
/* List of module systems to cruise.
|
||||||
|
When left out dependency-cruiser will fall back to the list of _all_
|
||||||
|
module systems it knows of. It's the default because it's the safe option
|
||||||
|
It might come at a performance penalty, though.
|
||||||
|
moduleSystems: ['amd', 'cjs', 'es6', 'tsd']
|
||||||
|
|
||||||
|
As in practice only commonjs ('cjs') and ecmascript modules ('es6')
|
||||||
|
are widely used, you can limit the moduleSystems to those.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// moduleSystems: ['cjs', 'es6'],
|
||||||
|
|
||||||
|
/* prefix for links in html and svg output (e.g. 'https://github.com/you/yourrepo/blob/main/'
|
||||||
|
to open it on your online repo or `vscode://file/${process.cwd()}/` to
|
||||||
|
open it in visual studio code),
|
||||||
|
*/
|
||||||
|
// prefix: `vscode://file/${process.cwd()}/`,
|
||||||
|
|
||||||
|
/* false (the default): ignore dependencies that only exist before typescript-to-javascript compilation
|
||||||
|
true: also detect dependencies that only exist before typescript-to-javascript compilation
|
||||||
|
"specify": for each dependency identify whether it only exists before compilation or also after
|
||||||
|
*/
|
||||||
|
// tsPreCompilationDeps: false,
|
||||||
|
|
||||||
|
/* list of extensions to scan that aren't javascript or compile-to-javascript.
|
||||||
|
Empty by default. Only put extensions in here that you want to take into
|
||||||
|
account that are _not_ parsable.
|
||||||
|
*/
|
||||||
|
// extraExtensionsToScan: [".json", ".jpg", ".png", ".svg", ".webp"],
|
||||||
|
|
||||||
|
/* if true combines the package.jsons found from the module up to the base
|
||||||
|
folder the cruise is initiated from. Useful for how (some) mono-repos
|
||||||
|
manage dependencies & dependency definitions.
|
||||||
|
*/
|
||||||
|
// combinedDependencies: false,
|
||||||
|
|
||||||
|
/* if true leave symlinks untouched, otherwise use the realpath */
|
||||||
|
// preserveSymlinks: false,
|
||||||
|
|
||||||
|
/* TypeScript project file ('tsconfig.json') to use for
|
||||||
|
(1) compilation and
|
||||||
|
(2) resolution (e.g. with the paths property)
|
||||||
|
|
||||||
|
The (optional) fileName attribute specifies which file to take (relative to
|
||||||
|
dependency-cruiser's current working directory). When not provided
|
||||||
|
defaults to './tsconfig.json'.
|
||||||
|
*/
|
||||||
|
tsConfig: {
|
||||||
|
fileName: 'tsconfig.json'
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Webpack configuration to use to get resolve options from.
|
||||||
|
|
||||||
|
The (optional) fileName attribute specifies which file to take (relative
|
||||||
|
to dependency-cruiser's current working directory. When not provided defaults
|
||||||
|
to './webpack.conf.js'.
|
||||||
|
|
||||||
|
The (optional) `env` and `arguments` attributes contain the parameters
|
||||||
|
to be passed if your webpack config is a function and takes them (see
|
||||||
|
webpack documentation for details)
|
||||||
|
*/
|
||||||
|
// webpackConfig: {
|
||||||
|
// fileName: 'webpack.config.js',
|
||||||
|
// env: {},
|
||||||
|
// arguments: {}
|
||||||
|
// },
|
||||||
|
|
||||||
|
/* Babel config ('.babelrc', '.babelrc.json', '.babelrc.json5', ...) to use
|
||||||
|
for compilation
|
||||||
|
*/
|
||||||
|
// babelConfig: {
|
||||||
|
// fileName: '.babelrc',
|
||||||
|
// },
|
||||||
|
|
||||||
|
/* List of strings you have in use in addition to cjs/ es6 requires
|
||||||
|
& imports to declare module dependencies. Use this e.g. if you've
|
||||||
|
re-declared require, use a require-wrapper or use window.require as
|
||||||
|
a hack.
|
||||||
|
*/
|
||||||
|
// exoticRequireStrings: [],
|
||||||
|
|
||||||
|
/* options to pass on to enhanced-resolve, the package dependency-cruiser
|
||||||
|
uses to resolve module references to disk. The values below should be
|
||||||
|
suitable for most situations
|
||||||
|
|
||||||
|
If you use webpack: you can also set these in webpack.conf.js. The set
|
||||||
|
there will override the ones specified here.
|
||||||
|
*/
|
||||||
|
enhancedResolveOptions: {
|
||||||
|
/* What to consider as an 'exports' field in package.jsons */
|
||||||
|
exportsFields: ["exports"],
|
||||||
|
/* List of conditions to check for in the exports field.
|
||||||
|
Only works when the 'exportsFields' array is non-empty.
|
||||||
|
*/
|
||||||
|
conditionNames: ["import", "require", "node", "default", "types"],
|
||||||
|
/*
|
||||||
|
The extensions, by default are the same as the ones dependency-cruiser
|
||||||
|
can access (run `npx depcruise --info` to see which ones that are in
|
||||||
|
_your_ environment). If that list is larger than you need you can pass
|
||||||
|
the extensions you actually use (e.g. [".js", ".jsx"]). This can speed
|
||||||
|
up module resolution, which is the most expensive step.
|
||||||
|
*/
|
||||||
|
// extensions: [".js", ".jsx", ".ts", ".tsx", ".d.ts"],
|
||||||
|
/* What to consider a 'main' field in package.json */
|
||||||
|
mainFields: ["module", "main", "types", "typings"],
|
||||||
|
/*
|
||||||
|
A list of alias fields in package.jsons
|
||||||
|
See [this specification](https://github.com/defunctzombie/package-browser-field-spec) and
|
||||||
|
the webpack [resolve.alias](https://webpack.js.org/configuration/resolve/#resolvealiasfields)
|
||||||
|
documentation
|
||||||
|
|
||||||
|
Defaults to an empty array (= don't use alias fields).
|
||||||
|
*/
|
||||||
|
// aliasFields: ["browser"],
|
||||||
|
},
|
||||||
|
reporterOptions: {
|
||||||
|
dot: {
|
||||||
|
/* pattern of modules that can be consolidated in the detailed
|
||||||
|
graphical dependency graph. The default pattern in this configuration
|
||||||
|
collapses everything in node_modules to one folder deep so you see
|
||||||
|
the external modules, but their innards.
|
||||||
|
*/
|
||||||
|
collapsePattern: 'node_modules/(?:@[^/]+/[^/]+|[^/]+)',
|
||||||
|
|
||||||
|
/* Options to tweak the appearance of your graph.See
|
||||||
|
https://github.com/sverweij/dependency-cruiser/blob/main/doc/options-reference.md#reporteroptions
|
||||||
|
for details and some examples. If you don't specify a theme
|
||||||
|
dependency-cruiser falls back to a built-in one.
|
||||||
|
*/
|
||||||
|
// theme: {
|
||||||
|
// graph: {
|
||||||
|
// /* splines: "ortho" gives straight lines, but is slow on big graphs
|
||||||
|
// splines: "true" gives bezier curves (fast, not as nice as ortho)
|
||||||
|
// */
|
||||||
|
// splines: "true"
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
archi: {
|
||||||
|
/* pattern of modules that can be consolidated in the high level
|
||||||
|
graphical dependency graph. If you use the high level graphical
|
||||||
|
dependency graph reporter (`archi`) you probably want to tweak
|
||||||
|
this collapsePattern to your situation.
|
||||||
|
*/
|
||||||
|
collapsePattern: '^(?:packages|src|lib(s?)|app(s?)|bin|test(s?)|spec(s?))/[^/]+|node_modules/(?:@[^/]+/[^/]+|[^/]+)',
|
||||||
|
|
||||||
|
/* Options to tweak the appearance of your graph. If you don't specify a
|
||||||
|
theme for 'archi' dependency-cruiser will use the one specified in the
|
||||||
|
dot section above and otherwise use the default one.
|
||||||
|
*/
|
||||||
|
// theme: { },
|
||||||
|
},
|
||||||
|
"text": {
|
||||||
|
"highlightFocused": true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// generated: dependency-cruiser@16.3.3 on 2024-06-13T23:26:36.169Z
|
3
.env
3
.env
@ -1,3 +1,6 @@
|
|||||||
VITE_BYPASS_LOGIN=0
|
VITE_BYPASS_LOGIN=0
|
||||||
VITE_BYPASS_TUTORIAL=0
|
VITE_BYPASS_TUTORIAL=0
|
||||||
VITE_SERVER_URL=http://localhost:8001
|
VITE_SERVER_URL=http://localhost:8001
|
||||||
|
VITE_DISCORD_CLIENT_ID=1248062921129459756
|
||||||
|
VITE_GOOGLE_CLIENT_ID=955345393540-2k6lfftf0fdnb0krqmpthjnqavfvvf73.apps.googleusercontent.com
|
||||||
|
VITE_I18N_DEBUG=0
|
||||||
|
6
.env.beta
Normal file
6
.env.beta
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
VITE_BYPASS_LOGIN=0
|
||||||
|
VITE_BYPASS_TUTORIAL=0
|
||||||
|
VITE_SERVER_URL=https://api.beta.pokerogue.net
|
||||||
|
VITE_DISCORD_CLIENT_ID=1248062921129459756
|
||||||
|
VITE_GOOGLE_CLIENT_ID=955345393540-2k6lfftf0fdnb0krqmpthjnqavfvvf73.apps.googleusercontent.com
|
||||||
|
VITE_I18N_DEBUG=1
|
@ -1,3 +1,7 @@
|
|||||||
VITE_BYPASS_LOGIN=1
|
VITE_BYPASS_LOGIN=1
|
||||||
VITE_BYPASS_TUTORIAL=0
|
VITE_BYPASS_TUTORIAL=0
|
||||||
VITE_SERVER_URL=http://localhost:8001
|
VITE_SERVER_URL=http://localhost:8001
|
||||||
|
VITE_DISCORD_CLIENT_ID=1234567890
|
||||||
|
VITE_GOOGLE_CLIENT_ID=1234567890
|
||||||
|
VITE_I18N_DEBUG=1
|
||||||
|
VITE_PORT=8000
|
||||||
|
6
.env.production
Normal file
6
.env.production
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
VITE_BYPASS_LOGIN=0
|
||||||
|
VITE_BYPASS_TUTORIAL=0
|
||||||
|
VITE_SERVER_URL=https://api.pokerogue.net
|
||||||
|
VITE_DISCORD_CLIENT_ID=1248062921129459756
|
||||||
|
VITE_GOOGLE_CLIENT_ID=955345393540-2k6lfftf0fdnb0krqmpthjnqavfvvf73.apps.googleusercontent.com
|
||||||
|
VITE_I18N_DEBUG=0
|
45
.github/CODEOWNERS
vendored
Normal file
45
.github/CODEOWNERS
vendored
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# Order is important; the last matching pattern takes the most precedence.
|
||||||
|
|
||||||
|
# everything (whole code-base) - Junior Devs
|
||||||
|
* @pagefaultgames/junior-dev-team
|
||||||
|
|
||||||
|
# github actions/templates etc. - Dev Leads
|
||||||
|
/.github @pagefaultgames/dev-leads
|
||||||
|
|
||||||
|
# --- Translations ---
|
||||||
|
|
||||||
|
# all translations - Translation Leads
|
||||||
|
/src/locales @pagefaultgames/translation-leads
|
||||||
|
|
||||||
|
# Catalan (Spain/Spanish)
|
||||||
|
/src/locales/ca_ES @pagefaultgames/catalan-translation-team
|
||||||
|
|
||||||
|
# German
|
||||||
|
/src/locales/de @pagefaultgames/german-translation-team
|
||||||
|
|
||||||
|
# English
|
||||||
|
/src/locales/en @pagefaultgames/english-translation-team
|
||||||
|
|
||||||
|
# Spanish
|
||||||
|
/src/locales/es @pagefaultgames/spanish-translation-team
|
||||||
|
|
||||||
|
# French
|
||||||
|
/src/locales/fr @pagefaultgames/french-translation-team
|
||||||
|
|
||||||
|
# Italian
|
||||||
|
/src/locales/it @pagefaultgames/italian-translation-team
|
||||||
|
|
||||||
|
# Japenese
|
||||||
|
/src/locales/ja @pagefaultgames/japanese-translation-team
|
||||||
|
|
||||||
|
# Korean
|
||||||
|
/src/locales/ko @pagefaultgames/korean-translation-team
|
||||||
|
|
||||||
|
# Brasilian (Brasil/Portuguese)
|
||||||
|
/src/locales/pt_BR @pagefaultgames/portuguese_br-translation-team
|
||||||
|
|
||||||
|
# Chinese (simplified)
|
||||||
|
/src/locales/zh_CN @pagefaultgames/chinese_simplified-translation-team
|
||||||
|
|
||||||
|
# Chinese (traditional)
|
||||||
|
/src/locales/zh_TW @pagefaultgames/chinese_traditional-translation-team
|
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
github: patapancakes
|
30
.github/ISSUE_TEMPLATE/bug_report.md
vendored
30
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,30 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Create a report to help us improve
|
|
||||||
title: "[BUG]"
|
|
||||||
labels: Bug
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Describe the bug**
|
|
||||||
<!-- A clear and concise description of what the bug is. -->
|
|
||||||
|
|
||||||
**To Reproduce**
|
|
||||||
<!-- Steps to reproduce the behavior: -->
|
|
||||||
1. Go to '...'
|
|
||||||
2. Click on '....'
|
|
||||||
3. Scroll down to '....'
|
|
||||||
4. See error
|
|
||||||
|
|
||||||
**Expected behavior**
|
|
||||||
<!-- A clear and concise description of what you expected to happen. If it is an existing move or ability -->
|
|
||||||
|
|
||||||
**Screenshots / Videos**
|
|
||||||
<!-- If applicable, add screenshots or videos to help explain your problem. -->
|
|
||||||
|
|
||||||
**Device**
|
|
||||||
<!-- What browser are you playing in, are you on a PC/Mac or Phone? -->
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
<!-- Add any other context about the problem here. -->
|
|
115
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
115
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
name: Bug Report
|
||||||
|
description: Create a report to help us improve
|
||||||
|
title: "[Bug] "
|
||||||
|
labels: ["Bug"]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for taking the time to fill out this bug report!
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: Describe the bug
|
||||||
|
description: A clear and concise description of what the bug is.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
---
|
||||||
|
- type: textarea
|
||||||
|
id: session-file
|
||||||
|
attributes:
|
||||||
|
label: Session export file
|
||||||
|
description: Open Menu → ManageData → Export Session → Select slot. The file should now be in your `/Downloads` directory. Change the file extension type from `.prsv` to `.txt` (How to [Windows](https://www.guidingtech.com/how-to-change-file-type-on-windows/) | [Mac](https://support.apple.com/guide/mac-help/show-or-hide-filename-extensions-on-mac-mchlp2304/mac) | [iOS](https://www.guidingtech.com/change-file-type-extension-on-iphone/)).
|
||||||
|
placeholder: Focus me and then drop your file here (or use the upload button at the bottom)
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: textarea
|
||||||
|
id: data-file
|
||||||
|
attributes:
|
||||||
|
label: User data export file
|
||||||
|
description: Open Menu → ManageData → Export Data. The file should now be in your `/Downloads` directory. Change the file extension type from `.prsv` to `.txt` (How to [Windows](https://www.guidingtech.com/how-to-change-file-type-on-windows/) | [Mac](https://support.apple.com/guide/mac-help/show-or-hide-filename-extensions-on-mac-mchlp2304/mac) | [iOS](https://www.guidingtech.com/change-file-type-extension-on-iphone/)).
|
||||||
|
placeholder: Focus me and then drop your file here (or use the upload button at the bottom)
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
---
|
||||||
|
- type: textarea
|
||||||
|
id: expected-behavior
|
||||||
|
attributes:
|
||||||
|
label: Expected behavior
|
||||||
|
description: A clear and concise description of what you expected to happen. If it is an existing move or ability
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
---
|
||||||
|
- type: textarea
|
||||||
|
id: media
|
||||||
|
attributes:
|
||||||
|
label: Screenshots / Videos
|
||||||
|
description: If applicable, add screenshots or videos to help explain your problem.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
---
|
||||||
|
- type: dropdown
|
||||||
|
id: os
|
||||||
|
attributes:
|
||||||
|
label: What OS did you observe the bug on?
|
||||||
|
multiple: true
|
||||||
|
options:
|
||||||
|
- PC/Windows
|
||||||
|
- Mac/OSX
|
||||||
|
- Linux
|
||||||
|
- iOS
|
||||||
|
- Android
|
||||||
|
- Other
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: os-other
|
||||||
|
attributes:
|
||||||
|
label: If other please specify
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
---
|
||||||
|
- type: dropdown
|
||||||
|
id: browser
|
||||||
|
attributes:
|
||||||
|
label: Which browser do you use?
|
||||||
|
multiple: true
|
||||||
|
options:
|
||||||
|
- Chrome
|
||||||
|
- Firefox
|
||||||
|
- Safari
|
||||||
|
- Edge
|
||||||
|
- Opera
|
||||||
|
- Other
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: browser-other
|
||||||
|
attributes:
|
||||||
|
label: If other please specify
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
---
|
||||||
|
- type: textarea
|
||||||
|
id: additional-context
|
||||||
|
attributes:
|
||||||
|
label: Additional context
|
||||||
|
description: Add any other context about the problem here.
|
18
.github/ISSUE_TEMPLATE/feature_request.md
vendored
18
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -1,18 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Suggest an idea for this project
|
|
||||||
title: "[Feature]"
|
|
||||||
labels: enhancement
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
|
||||||
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
|
||||||
|
|
||||||
**Describe the Feature**
|
|
||||||
<!-- A clear and concise description of what you want to happen. -->
|
|
||||||
<!-- Add a link to the feature if it is an existing move/ability/etc -->
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
<!-- Add any other context or screenshots about the feature request here. -->
|
|
37
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
37
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
name: Feature Request
|
||||||
|
description: Suggest an idea for this project
|
||||||
|
title: "[Feature] "
|
||||||
|
labels: ["Enhancement"]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for taking the time to fill out this feature request!
|
||||||
|
- type: textarea
|
||||||
|
id: relation
|
||||||
|
attributes:
|
||||||
|
label: Is your feature request related to a problem? Please describe.
|
||||||
|
description: Clear and concise description of what the problem is.
|
||||||
|
placeholder: E.g. "I'm always frustrated when [...]"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
---
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: Describe the Feature
|
||||||
|
description: A clear and concise description of what you want to happen. Add a link to the feature if it is an existing move/ability/etc.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
---
|
||||||
|
- type: textarea
|
||||||
|
id: additional-context
|
||||||
|
attributes:
|
||||||
|
label: Additional context
|
||||||
|
description: Add any other context or screenshots about the feature request here.
|
9
.github/pull_request_template.md
vendored
9
.github/pull_request_template.md
vendored
@ -2,15 +2,15 @@
|
|||||||
<!-- Make sure that this PR is not overlapping with someone else's work -->
|
<!-- Make sure that this PR is not overlapping with someone else's work -->
|
||||||
<!-- Please try to keep the PR self-contained (and small) -->
|
<!-- Please try to keep the PR self-contained (and small) -->
|
||||||
|
|
||||||
## What are the changes?
|
## What are the changes the user will see?
|
||||||
<!-- Summarize what are the changes from a user perspective on the application -->
|
<!-- Summarize what are the changes from a user perspective on the application -->
|
||||||
|
|
||||||
## Why am I doing these changes?
|
## Why am I making these changes?
|
||||||
<!-- Explain why you decided to introduce these changes -->
|
<!-- Explain why you decided to introduce these changes -->
|
||||||
<!-- Does it come from an issue or another PR? Please link it -->
|
<!-- Does it come from an issue or another PR? Please link it -->
|
||||||
<!-- Explain why you believe this can enhance user experience -->
|
<!-- Explain why you believe this can enhance user experience -->
|
||||||
|
|
||||||
## What did change?
|
## What are the changes from a developer perspective?
|
||||||
<!-- Explicitly state what are the changes introduced by the PR -->
|
<!-- Explicitly state what are the changes introduced by the PR -->
|
||||||
<!-- You can make use of a comparison between what was the state before and after your PR changes -->
|
<!-- You can make use of a comparison between what was the state before and after your PR changes -->
|
||||||
|
|
||||||
@ -25,9 +25,12 @@
|
|||||||
<!-- Do the reviewer need to do something special in order to test your change? -->
|
<!-- Do the reviewer need to do something special in order to test your change? -->
|
||||||
|
|
||||||
## Checklist
|
## Checklist
|
||||||
|
- [ ] **I'm using `beta` as my base branch**
|
||||||
- [ ] There is no overlap with another PR?
|
- [ ] There is no overlap with another PR?
|
||||||
- [ ] The PR is self-contained and cannot be split into smaller PRs?
|
- [ ] The PR is self-contained and cannot be split into smaller PRs?
|
||||||
- [ ] Have I provided a clear explanation of the changes?
|
- [ ] Have I provided a clear explanation of the changes?
|
||||||
|
- [ ] Have I considered writing automated tests for the issue?
|
||||||
|
- [ ] If I have text, did I add make it translatable and added a key in the English language?
|
||||||
- [ ] Have I tested the changes (manually)?
|
- [ ] Have I tested the changes (manually)?
|
||||||
- [ ] Are all unit tests still passing? (`npm run test`)
|
- [ ] Are all unit tests still passing? (`npm run test`)
|
||||||
- [ ] Are the changes visual?
|
- [ ] Are the changes visual?
|
||||||
|
32
.github/workflows/deploy-beta.yml
vendored
Normal file
32
.github/workflows/deploy-beta.yml
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
name: Deploy Beta
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- beta
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
if: github.repository == 'pagefaultgames/pokerogue'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "20"
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
- name: Build
|
||||||
|
run: npm run build:beta
|
||||||
|
env:
|
||||||
|
NODE_ENV: production
|
||||||
|
- name: Set up SSH
|
||||||
|
run: |
|
||||||
|
mkdir ~/.ssh
|
||||||
|
echo "${{ secrets.BETA_SSH_PUBLIC_KEY }}" > ~/.ssh/id_ed25519.pub
|
||||||
|
echo "${{ secrets.BETA_SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
|
||||||
|
chmod 600 ~/.ssh/*
|
||||||
|
ssh-keyscan -H ${{ secrets.BETA_SSH_HOST }} >> ~/.ssh/known_hosts
|
||||||
|
- name: Deploy build on server
|
||||||
|
run: |
|
||||||
|
rsync --del --no-times --checksum -vrm dist/* ${{ secrets.BETA_SSH_USER }}@${{ secrets.BETA_SSH_HOST }}:${{ secrets.BETA_DESTINATION_DIR }}
|
2
.github/workflows/eslint.yml
vendored
2
.github/workflows/eslint.yml
vendored
@ -6,9 +6,11 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main # Trigger on push events to the main branch
|
- main # Trigger on push events to the main branch
|
||||||
|
- beta # Trigger on push events to the beta branch
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- main # Trigger on pull request events targeting the main branch
|
- main # Trigger on pull request events targeting the main branch
|
||||||
|
- beta # Trigger on pull request events targeting the beta branch
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
run-linters: # Define a job named "run-linters"
|
run-linters: # Define a job named "run-linters"
|
||||||
|
4
.github/workflows/github-pages.yml
vendored
4
.github/workflows/github-pages.yml
vendored
@ -7,6 +7,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
- beta
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pages:
|
pages:
|
||||||
@ -47,13 +48,12 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cd pokerogue_docs
|
cd pokerogue_docs
|
||||||
npm ci
|
npm ci
|
||||||
npm install typedoc --save-dev
|
|
||||||
|
|
||||||
- name: Generate Typedoc docs
|
- name: Generate Typedoc docs
|
||||||
working-directory: ${{env.api-dir}}
|
working-directory: ${{env.api-dir}}
|
||||||
run: |
|
run: |
|
||||||
cd pokerogue_docs
|
cd pokerogue_docs
|
||||||
npx typedoc --out /tmp/docs --githubPages false --entryPoints ./src/
|
npm run docs -- --out /tmp/docs --githubPages false --entryPoints ./src/
|
||||||
|
|
||||||
- name: Commit & Push docs
|
- name: Commit & Push docs
|
||||||
if: github.event_name == 'push'
|
if: github.event_name == 'push'
|
||||||
|
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@ -6,9 +6,11 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main # Trigger on push events to the main branch
|
- main # Trigger on push events to the main branch
|
||||||
|
- beta # Trigger on push events to the beta branch
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- main # Trigger on pull request events targeting the main branch
|
- main # Trigger on pull request events targeting the main branch
|
||||||
|
- beta # Trigger on pull request events targeting the beta branch
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
run-tests: # Define a job named "run-tests"
|
run-tests: # Define a job named "run-tests"
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -39,3 +39,5 @@ coverage
|
|||||||
# Local Documentation
|
# Local Documentation
|
||||||
/typedoc
|
/typedoc
|
||||||
|
|
||||||
|
/dependency-graph.svg
|
||||||
|
/.vs
|
||||||
|
@ -53,9 +53,11 @@ Check out [Github Issues](https://github.com/pagefaultgames/pokerogue/issues) to
|
|||||||
- Pokémon Sun/Moon
|
- Pokémon Sun/Moon
|
||||||
- Pokémon Ultra Sun/Ultra Moon
|
- Pokémon Ultra Sun/Ultra Moon
|
||||||
- Pokémon Sword/Shield
|
- Pokémon Sword/Shield
|
||||||
|
- Pokémon Legends: Arceus
|
||||||
- Pokémon Scarlet/Violet
|
- Pokémon Scarlet/Violet
|
||||||
- Firel (Custom Metropolis and Laboratory biome music)
|
- Firel (Custom Laboratory, Metropolis, Seabed, and Space biome music)
|
||||||
- Lmz (Custom Jungle biome music)
|
- Lmz (Custom Jungle biome music)
|
||||||
|
- Andr06 (Custom Slum and Sea biome music)
|
||||||
|
|
||||||
### 🎵 Sound Effects
|
### 🎵 Sound Effects
|
||||||
- Pokémon Emerald
|
- Pokémon Emerald
|
||||||
@ -80,6 +82,7 @@ Check out [Github Issues](https://github.com/pagefaultgames/pokerogue/issues) to
|
|||||||
- kyledove
|
- kyledove
|
||||||
- Brumirage
|
- Brumirage
|
||||||
- pkmn_realidea (Paid Commissions)
|
- pkmn_realidea (Paid Commissions)
|
||||||
|
- IceJkai
|
||||||
|
|
||||||
### 🎨 Trainer Portraits
|
### 🎨 Trainer Portraits
|
||||||
- pkmn_realidea (Paid Commissions)
|
- pkmn_realidea (Paid Commissions)
|
||||||
@ -111,6 +114,7 @@ Check out [Github Issues](https://github.com/pagefaultgames/pokerogue/issues) to
|
|||||||
- mangalos810
|
- mangalos810
|
||||||
- Involuntary-Twitch
|
- Involuntary-Twitch
|
||||||
- selstar
|
- selstar
|
||||||
|
- koda_want_to_sleep
|
||||||
|
|
||||||
### 🎨 Move Animations
|
### 🎨 Move Animations
|
||||||
- Pokémon Reborn
|
- Pokémon Reborn
|
||||||
|
13
dependency-graph.js
Normal file
13
dependency-graph.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { Graphviz } from "@hpcc-js/wasm/graphviz";
|
||||||
|
|
||||||
|
const graphviz = await Graphviz.load();
|
||||||
|
|
||||||
|
const inputFile = [];
|
||||||
|
for await (const chunk of process.stdin) {
|
||||||
|
inputFile.push(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = Buffer.concat(inputFile).toString("utf-8");
|
||||||
|
|
||||||
|
const svg = graphviz.dot(file, "svg");
|
||||||
|
process.stdout.write(svg);
|
224
docs/enemy-ai.md
Normal file
224
docs/enemy-ai.md
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
# EnemyCommandPhase: How Enemy Pokémon Decide What to Do
|
||||||
|
|
||||||
|
## Step 1: Should the Enemy Pokémon Switch?
|
||||||
|
|
||||||
|
When battling an enemy Trainer, the first decision the enemy needs to make is whether or not to switch an active Pokémon with another Pokémon in their party. This decision is primarily made by comparing **matchup scores** between each Pokémon in the enemy's party.
|
||||||
|
|
||||||
|
### Calculating Matchup Scores
|
||||||
|
|
||||||
|
The core function for matchup score calculation can be found in `src/field/pokemon.ts`, within the `Pokemon` class:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
getMatchupScore(pokemon: Pokemon): number;
|
||||||
|
```
|
||||||
|
|
||||||
|
This computes the source Pokémon's matchup score against the Pokémon passed by argument using the formula
|
||||||
|
|
||||||
|
$$\text{MUScore} = (\text{atkScore}+\text{defScore}) * \text{hpDiffRatio} $$
|
||||||
|
|
||||||
|
where
|
||||||
|
- $\text{atkScore}$ is the combined effectiveness of the source Pokémon's types against the opposing Pokémon's defensive typing: $\prod_{\text{types}} \text{typeEffectiveness}(\text{type}, \text{oppPokemon})$. $\text{typeEffectiveness}$ is 1 when the type deals neutral damage to the opposing Pokémon's defensive typing, 2 when the type deals super effective damage, and so on. $atkScore$ is also increased by 25 percent if the source Pokémon has a higher Speed stat than the opposing Pokémon.
|
||||||
|
- $\text{defScore}$ is the inverse of the opposing Pokémon's $\text{atkScore}$ against the source Pokémon's defensive typing, or $(\prod_{\text{types}} \text{typeEffectiveness}(\text{type}, \text{sourcePokemon}))^{-1}$. Unlike $\text{atkScore}$, $\text{defScore}$ is capped at a maximum score of 4.
|
||||||
|
- $\text{hpDiffRatio}= \text{sourceHpRatio}-\text{oppHpRatio}+1$. This is further multiplied by 1.5 if the source Pokémon has a higher Speed stat than the opposing Pokémon; however, $\text{hpDiffRatio}$ cannot be higher than 1.
|
||||||
|
|
||||||
|
The maximum possible matchup score a Pokémon could have against a single opponent is $(16+16)\times 2=64$, which occurs when
|
||||||
|
- the Pokémon hits its opponent for 4x super effective damage with both of its types.
|
||||||
|
- the Pokémon is immune to or resists both of the opponent's types by 4x.
|
||||||
|
- the Pokémon is at max HP while the opponent's HP ratio is near zero.
|
||||||
|
|
||||||
|
In most situations, though, a Pokémon's matchup score against an opponent will be at most 16, which is equivalent to having two super effective types and resisting both of the opponent's types with the same HP ratios as before.
|
||||||
|
|
||||||
|
The minimum possible matchup score a Pokémon could have against a single opponent is near zero, which occurs when the Pokémon's HP ratio is near zero while the opponent is at max HP. However, a Pokémon's matchup score can also be very low when its type(s) are 4x weak to and/or resisted by its opponent's types.
|
||||||
|
|
||||||
|
### Determining Switches in EnemyCommandPhase
|
||||||
|
|
||||||
|
The `EnemyCommandPhase` follows this process to determine whether or not an enemy Pokémon should switch on each turn during a Trainer battle.
|
||||||
|
|
||||||
|
1. If the Pokémon has a move already queued (e.g. they are recharging after using Hyper Beam), or they are trapped (e.g. by Bind or Arena Trap), skip to resolving a `FIGHT` command (see next section).
|
||||||
|
2. For each Pokémon in the enemy's party, [compute their matchup scores](#calculating-matchup-scores) against the active player Pokémon. If there are two active player Pokémon in the battle, add their matchup scores together.
|
||||||
|
3. Take the party member with the highest matchup score and apply a multiplier to the score that reduces the score based on how frequently the enemy trainer has switched Pokémon in the current battle.
|
||||||
|
- The multiplier scales off of a counter that increments when the enemy trainer chooses to switch a Pokémon and decrements when they choose to use a move.
|
||||||
|
4. Compare the result of Step 3 with the active enemy Pokémon's matchup score. If the party member's matchup score is at least three times that of the active Pokémon, switch to that party member.
|
||||||
|
- "Boss" trainers only require the party member's matchup score to be at least two times that of the active Pokémon, so they are more likely to switch than other trainers. The full list of boss trainers in the game is as follows:
|
||||||
|
- All gym leaders, Elite 4 members, and Champions
|
||||||
|
- All Evil Team leaders
|
||||||
|
- The last three Rival Fights (on waves 95, 145, and 195)
|
||||||
|
5. If the enemy decided to switch, send a switch `turnCommand` and end this `EnemyCommandPhase`; otherwise, move on to resolving a `FIGHT` enemy command.
|
||||||
|
|
||||||
|
## Step 2: Selecting a Move
|
||||||
|
|
||||||
|
At this point, the enemy (a wild or trainer Pokémon) has decided against switching and instead will use a move from its moveset. However, it still needs to figure out which move to use and, if applicable, which target to use the move against. The logic for determining an enemy's next move and target is contained within two methods: `EnemyPokemon.getNextMove()` and `EnemyPokemon.getNextTargets()` in `src/field/pokemon.ts`.
|
||||||
|
|
||||||
|
### Choosing a Move with `getNextMove()`
|
||||||
|
|
||||||
|
In `getNextMove()`, the enemy Pokémon chooses a move to use in the following steps:
|
||||||
|
1. If the Pokémon has a move in its Move Queue (e.g. the second turn of a charging move), and the queued move is still usable, use that move against the given target.
|
||||||
|
2. Filter out any moves it can't use within its moveset. The remaining moves make up the enemy's **move pool** for the turn.
|
||||||
|
1. A move can be unusable if it has no PP left or it has been disabled by another move or effect
|
||||||
|
2. If the enemy's move pool is empty, use Struggle.
|
||||||
|
3. Calculate the **move score** of each move in the enemy's move pool.
|
||||||
|
1. A move's move score is equivalent to the move's maximum **target score** among all of the move's possible targets on the field ([more on this later](#calculating-move-and-target-scores)).
|
||||||
|
2. A move's move score is set to -20 if at least one of these conditions are met:
|
||||||
|
- The move is unimplemented (or, more precisely, the move's name ends with " (N)").
|
||||||
|
- Conditions for the move to succeed are not met (unless the move is Sucker Punch, Upper Hand, or Thunderclap, as those moves' conditions can't be resolved before the turn starts).
|
||||||
|
- The move's target scores are 0 or `NaN` for each target. In this case, the game assumes the target score calculation for that move is unimplemented.
|
||||||
|
4. Sort the move pool in descending order of move scores.
|
||||||
|
5. From here, the enemy's move selection varies based on its `aiType`. If the enemy is a Boss Pokémon or has a Trainer, it uses the `SMART` AI type; otherwise, it uses the `SMART_RANDOM` AI type.
|
||||||
|
1. Let $m_i$ be the *i*-th move in the sorted move pool $M$:
|
||||||
|
- If `aiType === SMART_RANDOM`, the enemy has a 5/8 chance of selecting $m_0$ and a 3/8 chance of advancing to the next best move $m_1$, where it then repeats this roll. This process stops when a move is selected or the last move in the move pool is reached.
|
||||||
|
- If `aiType === SMART`, a similar loop is used to decide between selecting the move $m_i$ and advancing to the next iteration with the move $m_{i+1}$. However, instead of using a flat probability, the following conditions need to be met to advance from selecting $m_i$ to $m_{i+1}$:
|
||||||
|
- $\text{sign}(s_i) = \text{sign}(s_{i+1})$, where $s_i$ is the move score of $m_i$.
|
||||||
|
- $\text{randInt}(0, 100) < \text{round}(\frac{s_{i+1}}{s_i}\times 50)$. In other words: if the scores of $m_i$ and $m_{i+1}$ have the same sign, the chance to advance to the next iteration with $m_{i+1}$ is proportional to how close the scores are to each other. The probability to advance to the next iteration is at most 50 percent (when $s_i$ and $s_{i+1}$ are equal).
|
||||||
|
6. The enemy will use the move selected in Step 5 against the target(s) with the highest [**target selection score (TSS)**](#choosing-targets-with-getnexttargets)
|
||||||
|
|
||||||
|
### Calculating Move and Target Scores
|
||||||
|
|
||||||
|
As part of the move selection process, the enemy Pokémon must compute a **target score (TS)** for each legal target for each move in its move pool. The base target score for all moves is a combination of the move's **user benefit score (UBS)** and **target benefit score (TBS)**.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
A move's UBS and TBS are computed with the respective functions in the `Move` class:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer;
|
||||||
|
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer;
|
||||||
|
```
|
||||||
|
|
||||||
|
Logically, these functions are very similar – they add up their respective benefit scores from each of the move's attributes (as determined by `attr.getUserBenefitScore`, and `attr.getTargetBenefitScore`, respectively) and return the total benefit score. However, there are two key functional differences in how the UBS and TBS of a move are handled:
|
||||||
|
1. In addition to influencing move selection, a move's TBS also influences target selection for that move, whereas UBS has no influence.
|
||||||
|
2. When evaluating the target score of a move against an opposing Pokémon, the move's TBS is multiplied by -1, whereas the move's UBS does not change. For this reason, move attributes return negative values for their TBS to reward using the move against an enemy.
|
||||||
|
|
||||||
|
#### Calculating Target Benefit Score (TBS) for Attack Moves
|
||||||
|
|
||||||
|
In addition to the base score from `Move.getTargetBenefitScore()`, attack moves calculate an `attackScore` which influences the move's TBS based on the following properties:
|
||||||
|
- The move's power (after the move's `VariablePowerAttrs` are applied)
|
||||||
|
- The move's type effectiveness against the target (note that this also accounts for type immunities from abilities such as Levitate and field effects such as Strong Winds).
|
||||||
|
- The move's category (Physical/Special), and whether the user has a higher Attack or Special Attack stat.
|
||||||
|
|
||||||
|
More specifically, the following steps are taken to compute the move's `attackScore`:
|
||||||
|
1. Compute a multiplier based on the move's type effectiveness:
|
||||||
|
|
||||||
|
%7D%5C%5C-2&&%5Ctext%7Botherwise%7D%5C%5C%5Cend%7Bmatrix%7D%5Cright.)
|
||||||
|
2. Compute a multiplier based on the move's category and the user's offensive stats:
|
||||||
|
1. Compute the user's offensive stat ratio:
|
||||||
|
|
||||||
|

|
||||||
|
2. Compute the stat-based multiplier:
|
||||||
|
|
||||||
|

|
||||||
|
3. Calculate the move's `attackScore`:
|
||||||
|
|
||||||
|
$\text{attackScore} = (\text{typeMult}\times \text{statMult})+\lfloor \frac{\text{power}}{5} \rfloor$
|
||||||
|
|
||||||
|
The maximum total multiplier in `attackScore` ($\text{typeMult}\times \text{statMult}$) is 4, which occurs for attacks that are super effective against the target and are categorically aligned with the user's offensive stats (e.g. the move is physical, and the user has much higher Attack than Sp. Atk). The minimum total multiplier of -4 occurs (somewhat confusingly) for attacks that are not super effective but are categorically aligned with the user's offensive stats.
|
||||||
|
|
||||||
|
The attack move's total TBS, then, is $\text{TBS}=\text{baseScore}-\text{attackScore}$, where $\text{baseScore}$ is the result of `Move.getTargetBenefitScore()`.
|
||||||
|
|
||||||
|
#### Calculating Target Score (TS) for Attack Moves
|
||||||
|
|
||||||
|
The final step to calculate an attack move's target score (TS) is to multiply the base target score by the move's type effectiveness and STAB (if it applies):
|
||||||
|
- If the target is an enemy, the corresponding TS is multiplied by the move's type effectiveness against the enemy (e.g. 2 if the move is super effective), then by 1.5 if the move shares a type with the user.
|
||||||
|
- If the target is an ally, the TS is divided by these factors instead.
|
||||||
|
- If $\text{TS}=0$ after these multipliers are applied, the TS is set to -20 for the current target.
|
||||||
|
|
||||||
|
### Choosing Targets with `getNextTargets()`
|
||||||
|
|
||||||
|
The enemy's target selection for single-target moves works in a very similar way to its move selection. Each potential target is given a **target selection score (TSS)** which is based on the move's [target benefit score](#calculating-move-and-target-scores) for that target:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Once the TSS is calculated for each target, the target is selected as follows:
|
||||||
|
1. Sort the targets (indexes) in decreasing order of their target selection scores (or weights). Let $t_i$ be the index of the *i*-th target in the sorted list, and let $w_i$ be that target's corresponding TSS.
|
||||||
|
2. Normalize the weights. Let $w_n$ be the lowest-weighted target in the sorted list, then:
|
||||||
|
|
||||||
|

|
||||||
|
3. Remove all weights from the list such that $W_i < \frac{W_0}{2}$
|
||||||
|
4. Generate a random integer $R=\text{rand}(0, W_{\text{total}})$ where $W_{\text{total}}$ is the sum of all the remaining weights after Step 3.
|
||||||
|
5. For each target $(t_i, W_i)$,
|
||||||
|
1. if $R \le \sum_{j=0}^{i} W_i$, or if $t_i$ is the last target in the list, **return** $t_i$
|
||||||
|
2. otherwise, advance to the next target $t_{i+1}$ and repeat this check.
|
||||||
|
|
||||||
|
Once the target is selected, the enemy has successfully determined its next action for the turn, and its corresponding `EnemyCommandPhase` ends. From here, the `TurnStartPhase` processes the enemy's commands alongside the player's commands and begins to resolve the turn.
|
||||||
|
|
||||||
|
## An Example in Battle
|
||||||
|
|
||||||
|
Suppose you enter a single battle against an enemy trainer with the following Pokémon in their party:
|
||||||
|
|
||||||
|
1. An [Excadrill](https://bulbapedia.bulbagarden.net/wiki/Excadrill_(Pok%C3%A9mon)) with the Ability Sand Force and the following moveset
|
||||||
|
1. Earthquake
|
||||||
|
2. Iron Head
|
||||||
|
3. Crush Claw
|
||||||
|
4. Swords Dance
|
||||||
|
2. A [Heatmor](https://bulbapedia.bulbagarden.net/wiki/Heatmor_(Pok%C3%A9mon)) with the Ability Flash Fire and the following moveset
|
||||||
|
1. Fire Lash
|
||||||
|
2. Inferno
|
||||||
|
3. Hone Claws
|
||||||
|
4. Shadow Claw
|
||||||
|
|
||||||
|
The enemy trainer leads with their Heatmor, and you lead with a [Dachsbun](https://bulbapedia.bulbagarden.net/wiki/Dachsbun_(Pok%C3%A9mon)) with the Ability Well-Baked Body. We'll cover the enemy's behavior over the next two turns.
|
||||||
|
|
||||||
|
### Turn 1
|
||||||
|
|
||||||
|
To determine whether the enemy should switch Pokémon, it first calculates each party member's matchup scores against the player's Dachsbun:
|
||||||
|
|
||||||
|
$$\text{MUScore} = (\text{atkScore}+\text{defScore}) * \text{hpDiffRatio} $$
|
||||||
|
- Defensively, Heatmor's Fire typing resists Dachsbun's Fairy typing, so its `defScore` is 2. However, because of Dachsbun's Fire immunity granted by Well-Baked Body, Heatmor's `atkScore` against Dachsbun is 0. With both Pokémon at maximum HP, Heatmor's total matchup score is 2.
|
||||||
|
- Excadrill's Steel typing also resists Fairy, so its `defScore` is also 2. In this case, though, Steel is also super effective against Fairy, so Excadrill's base `atkScore` is 2. If Excadrill outspeeds Dachsbun (possibly due to it having a +Spd nature or holding a Carbos), its `atkScore` is further increased to 2.5. Since both Pokémon are at maximum HP, Excadrill's total matchup score is 4 (or 4.5 if it outspeeds).
|
||||||
|
|
||||||
|
Based on the enemy party's matchup scores, whether or not the trainer switches out Heatmor for Excadrill depends on the trainer's type. The difference in matchup scores is enough to cause a switch to Excadrill for boss trainers (e.g. gym leaders) but not for regular trainers. For this example, we'll assume the trainer is a boss and, therefore, decides to switch to Excadrill on this turn.
|
||||||
|
|
||||||
|
### Turn 2
|
||||||
|
|
||||||
|
Now that the enemy Pokémon with the best matchup score is on the field (assuming it survives Dachsbun's attack on the last turn), the enemy will now decide to have Excadrill use one of its moves. Assuming all of its moves are usable, we'll go through the target score calculations for each move:
|
||||||
|
|
||||||
|
- **Earthquake**: In a single battle, this move is just a 100-power Ground-type physical attack with no additional effects. With no additional benefit score from attributes, the move's base target score against the player's Dachsbun is just the `attackScore` from `AttackMove.getTargetBenefitScore()`. In this case, Earthquake's `attackScore` is given by
|
||||||
|
|
||||||
|
$\text{attackScore}=(\text{typeMult}\times \text{statMult}) + \lfloor \frac{\text{power}}{5} \rfloor = -2\times 2 + 20 = 16$
|
||||||
|
|
||||||
|
Here, `typeMult` is -2 because the move is not super effective, and `statMult` is 2 because Excadrill's Attack is significantly higher than its Sp. Atk. Accounting for STAB thanks to Excadrill's typing, the final target score for this move is **24**
|
||||||
|
|
||||||
|
- **Iron Head**: This move is an 80-power Steel-type physical attack with an additional chance to cause the target to flinch. With these properties, Iron Head has a user benefit score of 0 and a target benefit score given by
|
||||||
|
|
||||||
|
$\text{TBS}=\text{getTargetBenefitScore(FlinchAttr)}-\text{attackScore}$
|
||||||
|
|
||||||
|
Under its current implementation, the target benefit score of `FlinchAttr` is -5. Calculating the move's `attackScore`, we get:
|
||||||
|
|
||||||
|
$\text{attackScore}=(\text{typeMult}\times \text{statMult}) + \lfloor \frac{\text{power}}{5} \rfloor = 2\times 2 + 16 = 20$
|
||||||
|
|
||||||
|
Note that `typeMult` in this case is 2 because Iron Head is super effective (or better) against Dachsbun. With the move's UBS at 0, the base target score calculation against Dachsbun simplifies to
|
||||||
|
|
||||||
|
$\text{TS}=-\text{TBS}=-(-5-20)=25$
|
||||||
|
|
||||||
|
We then need to apply a 2x multiplier for the move's type effectiveness and a 1.5x multiplier since STAB applies. After applying these multipliers, the final score for this move is **75**.
|
||||||
|
|
||||||
|
- **Swords Dance**: As a non-attacking move, this move's benefit score is derived entirely from the sum of its attributes' benefit scores. Swords Dance's `StatChangeAttr` has a user benefit score of 0 and a target benefit score that, in this case, simplifies to
|
||||||
|
|
||||||
|
$\text{TBS}=4\times \text{levels} + (-2\times \text{sign(levels)})$
|
||||||
|
|
||||||
|
where `levels` is the number of stat stages added by the attribute (in this case, +2). The final score for this move is **6** (Note: because this move is self-targeted, we don't flip the sign of TBS when computing the target score).
|
||||||
|
|
||||||
|
- **Crush Claw**: This move is a 75-power Normal-type physical attack with a 50 percent chance to lower the target's Defense by one stage. The additional effect is implemented by the same `StatChangeAttr` as Swords Dance, so we can use the same formulas from before to compute the total TBS and base target score.
|
||||||
|
|
||||||
|
$\text{TBS}=\text{getTargetBenefitScore(StatChangeAttr)}-\text{attackScore}$
|
||||||
|
|
||||||
|
$\text{TBS}=(-4 + 2)-(-2\times 2 + \lfloor \frac{75}{5} \rfloor)=-2-11=-13$
|
||||||
|
|
||||||
|
$\text{TS}=-\text{TBS}=13$
|
||||||
|
|
||||||
|
This move is neutral against Dachsbun and isn't boosted by STAB from Excadrill, so we don't need to apply any extra multipliers. The final score for this move is **13**.
|
||||||
|
|
||||||
|
We now have a sorted move pool in decreasing order of move scores:
|
||||||
|
1. Iron Head (**75**)
|
||||||
|
2. Earthquake (**24**)
|
||||||
|
3. Crush Claw (**13**)
|
||||||
|
4. Swords Dance (**6**)
|
||||||
|
|
||||||
|
Since no other score is at least half that of Iron Head's score, the enemy AI automatically chooses to use Iron Head against Dachsbun at this point.
|
||||||
|
|
||||||
|
## Guidelines for Implementing Benefit Scores
|
||||||
|
|
||||||
|
When implementing a new move attribute, it's important to override `MoveAttr`'s `getUserBenefitScore` and `getTargetBenefitScore` functions to ensure that the enemy AI can accurately determine when and how to use moves with that attribute. Here are a few basic specifications you should adhere to when implementing benefit scores for a new attribute:
|
||||||
|
- A move's **user benefit score (UBS)** incentivizes (or discourages) the move's usage in general. A positive UBS gives the move more incentive to be used, while a negative UBS gives the move less incentive.
|
||||||
|
- A move's **target benefit score (TBS)** incentivizes (or discourages) the move's usage on a specific target. A positive TBS indicates the move is better used on the user or its allies, while a negative TBS indicates the move is better used on enemies.
|
||||||
|
- **The total benefit score (UBS + TBS) of a move should never be 0.** The move selection algorithm assumes the move's benefit score is unimplemented if the total score is 0 and penalizes the move's usage as a result. With status moves especially, it's important to have some form of implementation among the move's attributes to avoid this scenario.
|
||||||
|
- **Score functions that use formulas should include comments.** If your attribute requires complex logic or formulas to calculate benefit scores, please add comments to explain how the logic works and its intended effect on the enemy's decision making.
|
@ -1,6 +1,7 @@
|
|||||||
import tseslint from '@typescript-eslint/eslint-plugin';
|
import tseslint from '@typescript-eslint/eslint-plugin';
|
||||||
|
import stylisticTs from '@stylistic/eslint-plugin-ts'
|
||||||
import parser from '@typescript-eslint/parser';
|
import parser from '@typescript-eslint/parser';
|
||||||
import imports from 'eslint-plugin-import';
|
// import imports from 'eslint-plugin-import'; // Disabled due to not being compatible with eslint v9
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
@ -10,7 +11,8 @@ export default [
|
|||||||
parser: parser
|
parser: parser
|
||||||
},
|
},
|
||||||
plugins: {
|
plugins: {
|
||||||
imports: imports.configs.recommended,
|
// imports: imports.configs.recommended // Disabled due to not being compatible with eslint v9
|
||||||
|
'@stylistic/ts': stylisticTs,
|
||||||
'@typescript-eslint': tseslint
|
'@typescript-eslint': tseslint
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
@ -25,18 +27,19 @@ export default [
|
|||||||
"ignoreRestSiblings": true // Allows unused variables that are part of a rest property in object destructuring. Useful for excluding certain properties from an object while using the rest.
|
"ignoreRestSiblings": true // Allows unused variables that are part of a rest property in object destructuring. Useful for excluding certain properties from an object while using the rest.
|
||||||
}],
|
}],
|
||||||
"eol-last": ["error", "always"], // Enforces at least one newline at the end of files
|
"eol-last": ["error", "always"], // Enforces at least one newline at the end of files
|
||||||
"@typescript-eslint/semi": ["error", "always"], // Requires semicolons for TypeScript-specific syntax
|
"@stylistic/ts/semi": ["error", "always"], // Requires semicolons for TypeScript-specific syntax
|
||||||
"semi": "off", // Disables the general semi rule for TypeScript files
|
"semi": "off", // Disables the general semi rule for TypeScript files
|
||||||
"no-extra-semi": ["error"], // Disallows unnecessary semicolons for TypeScript-specific syntax
|
"no-extra-semi": ["error"], // Disallows unnecessary semicolons for TypeScript-specific syntax
|
||||||
"brace-style": "off", // Note: you must disable the base rule as it can report incorrect errors
|
"brace-style": "off", // Note: you must disable the base rule as it can report incorrect errors
|
||||||
"curly": ["error", "all"], // Enforces the use of curly braces for all control statements
|
"curly": ["error", "all"], // Enforces the use of curly braces for all control statements
|
||||||
"@typescript-eslint/brace-style": ["error", "1tbs"],
|
"@stylistic/ts/brace-style": ["error", "1tbs"],
|
||||||
"no-trailing-spaces": ["error", { // Disallows trailing whitespace at the end of lines
|
"no-trailing-spaces": ["error", { // Disallows trailing whitespace at the end of lines
|
||||||
"skipBlankLines": false, // Enforces the rule even on blank lines
|
"skipBlankLines": false, // Enforces the rule even on blank lines
|
||||||
"ignoreComments": false // Enforces the rule on lines containing comments
|
"ignoreComments": false // Enforces the rule on lines containing comments
|
||||||
}],
|
}],
|
||||||
"space-before-blocks": ["error", "always"], // Enforces a space before blocks
|
"space-before-blocks": ["error", "always"], // Enforces a space before blocks
|
||||||
"keyword-spacing": ["error", { "before": true, "after": true }] // Enforces spacing before and after keywords
|
"keyword-spacing": ["error", { "before": true, "after": true }], // Enforces spacing before and after keywords
|
||||||
|
"comma-spacing": ["error", { "before": false, "after": true }] // Enforces spacing after comma
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
344
index.css
344
index.css
@ -1,16 +1,8 @@
|
|||||||
|
/* Global */
|
||||||
:root {
|
:root {
|
||||||
--color-base: hsl(0, 0%, 55%);
|
--color-base: hsl(0, 0%, 55%);
|
||||||
--color-light: hsl(0, 0%, 90%);
|
--color-light: hsl(0, 0%, 90%);
|
||||||
--color-dark: hsl(0, 0%, 10%);
|
--color-dark: hsl(0, 0%, 10%);
|
||||||
--controls-size: 10vh;
|
|
||||||
--text-shadow-size: 0.65vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (orientation: landscape) {
|
|
||||||
:root {
|
|
||||||
--controls-size: 20vh;
|
|
||||||
--text-shadow-size: 1.3vh;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
@ -19,9 +11,27 @@ html {
|
|||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
display:flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
background: #484050;
|
background: #484050;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (display-mode: fullscreen) {
|
||||||
|
body {
|
||||||
|
background: #000000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#links {
|
||||||
|
width: 90%;
|
||||||
|
text-align: center;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -31,28 +41,181 @@ body {
|
|||||||
transform-origin: top !important;
|
transform-origin: top !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#layout:fullscreen #dpad, #layout:fullscreen {
|
||||||
|
bottom: 6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:-internal-autofill-selected {
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Need adjust input font-size */
|
||||||
|
input {
|
||||||
|
font-size: 3.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
input:-internal-autofill-selected {
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Touch Controls: */
|
||||||
|
|
||||||
|
#touchControls {
|
||||||
|
--text-shadow-size: 0.65vh;
|
||||||
|
--controls-size: 10vh;
|
||||||
|
--touch-control-opacity: 0.6;
|
||||||
|
|
||||||
|
--controls-padding: 1rem;
|
||||||
|
|
||||||
|
--controls-size-with-padding: calc(var(--controls-size) + var(--controls-padding));
|
||||||
|
--controls-size-with-wide-padding: calc(var(--controls-size) *1.2 + var(--controls-padding));
|
||||||
|
--control-group-extra-size: calc(var(--controls-size) * 0.8);
|
||||||
|
--control-group-extra-wide-size: calc(var(--controls-size) * 1.2);
|
||||||
|
|
||||||
|
--control-group-extra-2-offset: calc(var(--controls-size-with-padding) + (var(--controls-size) - var(--control-group-extra-size)) / 2);
|
||||||
|
--control-group-extra-1-offset: calc(var(--controls-padding) + (var(--controls-size) - var(--control-group-extra-size)) / 2);
|
||||||
|
|
||||||
|
--small-control-size: calc(var(--controls-size) / 3);
|
||||||
|
--rect-control-size: calc(var(--controls-size) * 0.74);
|
||||||
|
|
||||||
|
font-family: 'emerald';
|
||||||
|
font-size: var(--controls-size);
|
||||||
|
text-shadow: var(--color-dark) var(--text-shadow-size) var(--text-shadow-size);
|
||||||
|
color: var(--color-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (orientation: landscape) {
|
||||||
|
#touchControls {
|
||||||
|
--controls-size: 20vh;
|
||||||
|
--text-shadow-size: 1.3vh;
|
||||||
|
--small-button-offset: 4vh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#touchControls:not(.visible) {
|
#touchControls:not(.visible) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#dpad, #apad {
|
#touchControls .active {
|
||||||
|
opacity: var(--touch-control-opacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-group {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 1rem;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: center;
|
||||||
|
width: var(--controls-size);
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-group-dpad {
|
||||||
|
width: calc(2 * var(--controls-size));
|
||||||
|
height: calc(2 * var(--controls-size));
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-group-extra {
|
||||||
|
width: var(--control-group-extra-size);
|
||||||
|
height: var(--control-group-extra-size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide buttons on specific UIs */
|
||||||
|
|
||||||
|
/* Show #apadPreviousTab and #apadNextTab only in settings, except in touch configuration panel */
|
||||||
|
#touchControls:not([data-ui-mode^='SETTINGS']) #apadPreviousTab,
|
||||||
|
#touchControls:not([data-ui-mode^='SETTINGS']) #apadNextTab,
|
||||||
|
#touchControls:is(.config-mode) #apadPreviousTab,
|
||||||
|
#touchControls:is(.config-mode) #apadNextTab {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show #apadInfo only in battle */
|
||||||
|
#touchControls:not([data-ui-mode='COMMAND']):not([data-ui-mode='FIGHT']):not([data-ui-mode='BALL']):not([data-ui-mode='TARGET_SELECT']) #apadInfo {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show #apadStats only in battle and shop */
|
||||||
|
#touchControls:not([data-ui-mode='COMMAND']):not([data-ui-mode='FIGHT']):not([data-ui-mode='BALL']):not([data-ui-mode='TARGET_SELECT']):not([data-ui-mode='MODIFIER_SELECT']) #apadStats {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show cycle buttons only on STARTER_SELECT and on touch configuration panel */
|
||||||
|
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT']) #apadOpenFilters,
|
||||||
|
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT']) #apadCycleForm,
|
||||||
|
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT']) #apadCycleShiny,
|
||||||
|
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT']) #apadCycleNature,
|
||||||
|
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT']) #apadCycleAbility,
|
||||||
|
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT']) #apadCycleGender,
|
||||||
|
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT']) #apadCycleVariant {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Configuration toolbar */
|
||||||
|
|
||||||
|
#configToolbar {
|
||||||
|
width: 100%;
|
||||||
|
position: fixed;
|
||||||
|
top: 1rem;
|
||||||
|
left: 0;
|
||||||
|
z-index: 9;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#configToolbar .column {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10%;
|
||||||
|
padding: 0 var(--controls-padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
#configToolbar .button-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#configToolbar .info-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#configToolbar .button {
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
|
background-color: var(--color-base);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 10%;
|
||||||
|
height: var(--small-control-size);
|
||||||
|
font-size: var(--small-control-size);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
text-shadow: var(--color-dark) calc(var(--text-shadow-size) / 3) calc(var(--text-shadow-size) / 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (orientation: portrait) {
|
#configToolbar .button:active {
|
||||||
#dpad, #apad {
|
opacity: var(--touch-control-opacity)
|
||||||
bottom: calc(1rem + env(safe-area-inset-bottom));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#configToolbar .orientation-label {
|
||||||
|
font-size: var(--small-control-size);
|
||||||
|
text-shadow: var(--color-dark) calc(var(--text-shadow-size) / 3) calc(var(--text-shadow-size) / 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* dpad */
|
||||||
#dpad {
|
#dpad {
|
||||||
left: 1rem;
|
z-index: 3;
|
||||||
}
|
opacity: 0.8;
|
||||||
|
|
||||||
#apad {
|
|
||||||
right: 1rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#dpad svg {
|
#dpad svg {
|
||||||
@ -61,114 +224,84 @@ body {
|
|||||||
fill: var(--color-base);
|
fill: var(--color-base);
|
||||||
}
|
}
|
||||||
|
|
||||||
#dpad svg rect {
|
/* apad buttons */
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
#apad > * {
|
.apad-button {
|
||||||
width: var(--controls-size);
|
|
||||||
height: var(--controls-size);
|
|
||||||
}
|
|
||||||
|
|
||||||
#apad .apadBtn {
|
|
||||||
width: var(--controls-size);
|
|
||||||
height: var(--controls-size);
|
|
||||||
background-color: var(--color-base);
|
background-color: var(--color-base);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: var(--controls-size);
|
||||||
|
height: var(--controls-size);
|
||||||
|
opacity: 0.8;
|
||||||
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#apad .apadLabel {
|
.apad-small {
|
||||||
font-family: 'emerald';
|
width: var(--small-control-size);
|
||||||
font-size: var(--controls-size);
|
height: var(--small-control-size);
|
||||||
text-shadow: var(--color-dark) var(--text-shadow-size) var(--text-shadow-size);
|
}
|
||||||
color: var(--color-light);
|
|
||||||
|
.apad-label {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
height: 100%;
|
||||||
|
margin-right: -2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#apad .apadLabelSmall {
|
.apad-small > .apad-label {
|
||||||
font-size: calc(var(--controls-size) / 3);
|
font-size: var(--small-control-size);
|
||||||
text-shadow: var(--color-dark) calc(var(--text-shadow-size) / 3) calc(var(--text-shadow-size) / 3);
|
text-shadow: var(--color-dark) calc(var(--text-shadow-size) / 3) calc(var(--text-shadow-size) / 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
#apad #apadLabelAction, #apad #apadLabelCancel {
|
.apad-rectangle {
|
||||||
margin-left: calc(var(--controls-size) / 3);
|
|
||||||
line-height: 0.9;
|
|
||||||
}
|
|
||||||
|
|
||||||
#apad > :nth-child(2) {
|
|
||||||
position: relative;
|
|
||||||
right: var(--controls-size);
|
|
||||||
}
|
|
||||||
|
|
||||||
#apad .apadRectBtn {
|
|
||||||
position: relative;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding-right: 10%;
|
width: var(--rect-control-size);
|
||||||
border-radius: 10%;
|
height: var(--small-control-size);
|
||||||
bottom: calc(var(--controls-size) * 0.05);
|
|
||||||
width: calc(var(--controls-size) * 0.6);
|
|
||||||
height: calc(var(--controls-size) * 0.3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#apad .apadSqBtn {
|
.apad-square {
|
||||||
border-radius: 10%;
|
width: var(--small-control-size);
|
||||||
width: calc(var(--controls-size) * 0.3);
|
height: var(--small-control-size);
|
||||||
height: calc(var(--controls-size) * 0.3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#apad .apadBtnContainer {
|
.apad-circle {
|
||||||
position: relative;
|
width: var(--controls-size);
|
||||||
display: flex;
|
height: var(--controls-size);
|
||||||
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#apad .apadRectBtnContainer {
|
/* Defaults:*/
|
||||||
flex-wrap: wrap;
|
|
||||||
margin-top: calc(var(--controls-size) * -0.8);
|
#control-group-dpad {
|
||||||
left: calc(var(--controls-size) * 0.175);
|
left: var(--controls-padding);
|
||||||
height: calc(var(--controls-size) * 0.8);
|
bottom: var(--controls-padding);
|
||||||
}
|
}
|
||||||
|
|
||||||
#apad .apadSqBtnContainer {
|
#control-group-action {
|
||||||
flex-wrap: wrap;
|
right: var(--controls-padding);
|
||||||
justify-content: space-evenly;
|
bottom: var(--controls-size-with-padding);
|
||||||
align-items: center;
|
|
||||||
margin-bottom: calc(var(--controls-size) * -0.8);
|
|
||||||
top: calc(var(--controls-size) * -0.9);
|
|
||||||
width: calc(var(--controls-size) * 0.8);
|
|
||||||
height: calc(var(--controls-size) * 0.8);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#apad .apadRectBtnContainer > #apadMenu {
|
#control-group-cancel {
|
||||||
align-self: flex-end;
|
right: var(--controls-size-with-wide-padding);
|
||||||
|
bottom: var(--controls-padding);
|
||||||
}
|
}
|
||||||
|
|
||||||
#apad .apadRectBtnContainer > .apadSqBtn:not(:first-child) {
|
#control-group-extra-1 {
|
||||||
margin-left: 10%;
|
right: var(--control-group-extra-1-offset);
|
||||||
|
bottom: var(--control-group-extra-1-offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
#touchControls:not([data-ui-mode='STARTER_SELECT']):not([data-ui-mode='SETTINGS']):not([data-ui-mode='SETTINGS_DISPLAY']):not([data-ui-mode='SETTINGS_AUDIO']):not([data-ui-mode='SETTINGS_GAMEPAD']):not([data-ui-mode='SETTINGS_KEYBOARD']) #apad .apadRectBtnContainer > .apadSqBtn:not(.apadBattle),
|
#control-group-extra-2 {
|
||||||
#touchControls:not([data-ui-mode='STARTER_SELECT']):not([data-ui-mode='SETTINGS']):not([data-ui-mode='SETTINGS_DISPLAY']):not([data-ui-mode='SETTINGS_AUDIO']):not([data-ui-mode='SETTINGS_GAMEPAD']):not([data-ui-mode='SETTINGS_KEYBOARD']) #apad .apadSqBtnContainer > .apadSqBtn:not(.apadBattle)
|
width: var(--control-group-extra-wide-size);
|
||||||
{
|
right: var(--control-group-extra-2-offset);
|
||||||
display: none;
|
bottom: var(--control-group-extra-2-offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
#touchControls:not([data-ui-mode='COMMAND']):not([data-ui-mode='FIGHT']):not([data-ui-mode='BALL']):not([data-ui-mode='TARGET_SELECT']):not([data-ui-mode='MODIFIER_SELECT']) #apad .apadBattle {
|
/* Layout */
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#apad .apadRectBtnContainer + .apadSqBtnContainer {
|
|
||||||
top: calc(var(--controls-size) * -1.9);
|
|
||||||
left: calc(var(--controls-size) * -0.9);
|
|
||||||
}
|
|
||||||
|
|
||||||
#apad .apadBtnContainer .apadLabel {
|
|
||||||
margin-left: calc(var(--controls-size) / 12);
|
|
||||||
line-height: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
#dpad path:not(.active), #apad .apadBtn:not(.active) {
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
#layout:fullscreen #dpad, #layout:fullscreen #apad {
|
#layout:fullscreen #dpad, #layout:fullscreen #apad {
|
||||||
bottom: 6rem;
|
bottom: 6rem;
|
||||||
@ -190,6 +323,17 @@ input:-internal-autofill-selected {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#tnc-links {
|
||||||
|
font-size: xx-small;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #328cea;
|
||||||
|
margin-right: 4px;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Firefox old*/
|
/* Firefox old*/
|
||||||
@-moz-keyframes blink {
|
@-moz-keyframes blink {
|
||||||
0% {
|
0% {
|
||||||
|
134
index.html
134
index.html
@ -4,12 +4,24 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<title>PokéRogue</title>
|
<title>PokéRogue</title>
|
||||||
|
<meta name="title" content="PokéRogue" />
|
||||||
<meta name="description" content="A Pokémon fangame heavily inspired by the roguelite genre. Battle endlessly while gathering stacking items, exploring many different biomes, and reaching Pokémon stats you never thought possible." />
|
<meta name="description" content="A Pokémon fangame heavily inspired by the roguelite genre. Battle endlessly while gathering stacking items, exploring many different biomes, and reaching Pokémon stats you never thought possible." />
|
||||||
<meta name="theme-color" content="#da3838" />
|
<meta name="theme-color" content="#da3838" />
|
||||||
|
<meta name="keywords" content="pokerogue, pokemon, roguelite" />
|
||||||
|
<meta name="news_keywords" content="pokerogue, pokemon, roguelite" />
|
||||||
|
<meta name="distribution" content="Global">
|
||||||
|
<meta http-equiv="audience" content="General">
|
||||||
<meta property="og:title" content="PokéRogue" />
|
<meta property="og:title" content="PokéRogue" />
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
<meta property="og:description" content="A Pokémon fangame heavily inspired by the roguelite genre. Battle endlessly while gathering stacking items, exploring many different biomes, and reaching Pokémon stats you never thought possible." />
|
<meta property="og:description" content="A Pokémon fangame heavily inspired by the roguelite genre. Battle endlessly while gathering stacking items, exploring many different biomes, and reaching Pokémon stats you never thought possible." />
|
||||||
<meta property="og:image" content="https://pokerogue.net/logo512.png" />
|
<meta property="og:image" content="https://pokerogue.net/logo512.png" />
|
||||||
|
<meta property="og:url" content="https://pokerogue.net" />
|
||||||
|
<meta property="og:site_name" content="PokéRogue" />
|
||||||
|
<meta property="twitter:title" content="PokéRogue" />
|
||||||
|
<meta property="twitter:description" content="A Pokémon fangame heavily inspired by the roguelite genre. Battle endlessly while gathering stacking items, exploring many different biomes, and reaching Pokémon stats you never thought possible." />
|
||||||
|
<meta property="twitter:image" content="https://pokerogue.net/logo512.png" />
|
||||||
|
<meta property="twitter:card" content="summary" />
|
||||||
|
<meta property="twitter:url" content="https://pokerogue.net" />
|
||||||
<link rel="apple-touch-icon" href="./logo512.png" />
|
<link rel="apple-touch-icon" href="./logo512.png" />
|
||||||
<link rel="shortcut icon" type="image/png" href="./logo512.png" />
|
<link rel="shortcut icon" type="image/png" href="./logo512.png" />
|
||||||
<link rel="canonical" href="https://pokerogue.net" />
|
<link rel="canonical" href="https://pokerogue.net" />
|
||||||
@ -19,11 +31,6 @@
|
|||||||
font-family: 'emerald';
|
font-family: 'emerald';
|
||||||
src: url('./fonts/pokemon-emerald-pro.ttf') format('truetype');
|
src: url('./fonts/pokemon-emerald-pro.ttf') format('truetype');
|
||||||
}
|
}
|
||||||
@font-face {
|
|
||||||
font-family: 'unifont';
|
|
||||||
src: url('./fonts/unifont-15.1.05.otf') format('opentype');
|
|
||||||
size-adjust: 70%;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'pkmnems';
|
font-family: 'pkmnems';
|
||||||
@ -32,6 +39,7 @@
|
|||||||
</style>
|
</style>
|
||||||
<link rel="stylesheet" type="text/css" href="./index.css" />
|
<link rel="stylesheet" type="text/css" href="./index.css" />
|
||||||
<link rel="manifest" href="./manifest.webmanifest">
|
<link rel="manifest" href="./manifest.webmanifest">
|
||||||
|
<script type="text/javascript" src="https://app.termly.io/resource-blocker/c5dbfa2f-9723-4c0f-a84b-2895124e851f?autoBlock=on"></script>
|
||||||
<script>
|
<script>
|
||||||
if ("serviceWorker" in navigator) {
|
if ("serviceWorker" in navigator) {
|
||||||
window.addEventListener("load", function () {
|
window.addEventListener("load", function () {
|
||||||
@ -56,59 +64,95 @@
|
|||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<div id="touchControls">
|
<div id="touchControls">
|
||||||
<div id="dpad" class="unselectable">
|
<div class="left">
|
||||||
|
<div id="control-group-dpad" class="control-group control-group-dpad">
|
||||||
|
<div id="dpad" data-control-key="DPAD">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 72 72">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 72 72">
|
||||||
<path id="dpadUp" data-key="UP" d="M48,5.8C48,2.5,45.4,0,42,0H29.9C26.6,0,24,2.4,24,5.8V24h24V5.8z" />
|
<path id="dpadUp" data-key="UP"
|
||||||
<path id="dpadRight" data-key="RIGHT" d="M66.2,24H48v24h18.2c3.3,0,5.8-2.7,5.8-6V29.9C72,26.5,69.5,24,66.2,24z" />
|
d="M48,5.8C48,2.5,45.4,0,42,0H29.9C26.6,0,24,2.4,24,5.8V24h24V5.8z" />
|
||||||
<path id="dpadDown" data-key="DOWN" d="M24,66.3c0,3.3,2.6,5.7,5.9,5.7H42c3.3,0,6-2.4,6-5.7V48H24V66.3z" />
|
<path id="dpadRight" data-key="RIGHT"
|
||||||
<path id="dpadLeft" data-key="LEFT" d="M5.7,24C2.4,24,0,26.5,0,29.9V42c0,3.3,2.3,6,5.7,6H24V24H5.7z" />
|
d="M66.2,24H48v24h18.2c3.3,0,5.8-2.7,5.8-6V29.9C72,26.5,69.5,24,66.2,24z" />
|
||||||
|
<path id="dpadDown" data-key="DOWN"
|
||||||
|
d="M24,66.3c0,3.3,2.6,5.7,5.9,5.7H42c3.3,0,6-2.4,6-5.7V48H24V66.3z" />
|
||||||
|
<path id="dpadLeft" data-key="LEFT"
|
||||||
|
d="M5.7,24C2.4,24,0,26.5,0,29.9V42c0,3.3,2.3,6,5.7,6H24V24H5.7z" />
|
||||||
<rect id="dpadCenter" x="24" y="24" width="24" height="24" />
|
<rect id="dpadCenter" x="24" y="24" width="24" height="24" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="apad" class="unselectable">
|
<div class="right">
|
||||||
<div id="apadAction" class="apadCircBtn apadBtn" data-key="ACTION">
|
<div id="control-group-action" class="control-group">
|
||||||
<text id="apadLabelAction" class="apadLabel">A</text>
|
<div id="apadAction" class="apad-button apad-circle" data-key="ACTION">
|
||||||
</div>
|
<span class="apad-label">A</span>
|
||||||
<div id="apadCancel" class="apadCircBtn apadBtn" data-key="CANCEL">
|
|
||||||
<text id="apadLabelCancel" class="apadLabel">B</text>
|
|
||||||
</div>
|
|
||||||
<div class="apadBtnContainer apadRectBtnContainer">
|
|
||||||
<div id="apadCycleShiny" class="apadSqBtn apadBtn" data-key="CYCLE_SHINY">
|
|
||||||
<text class="apadLabel apadLabelSmall">R</text>
|
|
||||||
</div>
|
|
||||||
<div id="apadCycleVariant" class="apadSqBtn apadBtn" data-key="V">
|
|
||||||
<text class="apadLabel apadLabelSmall">V</text>
|
|
||||||
</div>
|
|
||||||
<div id="apadStats" class="apadRectBtn apadBtn apadBattle" data-key="STATS">
|
|
||||||
<text class="apadLabel apadLabelSmall">C</text>
|
|
||||||
</div>
|
|
||||||
<div id="apadMenu" class="apadRectBtn apadBtn" data-key="MENU">
|
|
||||||
<text class="apadLabel apadLabelSmall">Menu</text>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="apadBtnContainer apadSqBtnContainer">
|
|
||||||
<div id="apadCycleForm" class="apadSqBtn apadBtn" data-key="CYCLE_FORM">
|
<div id="control-group-cancel" class="control-group">
|
||||||
<text class="apadLabel apadLabelSmall">F</text>
|
<div id="apadCancel" class="apad-button apad-circle" data-key="CANCEL">
|
||||||
</div>
|
<span class="apad-label">B</span>
|
||||||
<div id="apadCycleGender" class="apadSqBtn apadBtn" data-key="CYCLE_GENDER">
|
|
||||||
<text class="apadLabel apadLabelSmall">G</text>
|
|
||||||
</div>
|
|
||||||
<div id="apadCycleAbility" class="apadSqBtn apadBtn" data-key="CYCLE_ABILITY">
|
|
||||||
<text class="apadLabel apadLabelSmall">E</text>
|
|
||||||
</div>
|
|
||||||
<div id="apadCycleNature" class="apadSqBtn apadBtn" data-key="CYCLE_NATURE">
|
|
||||||
<text class="apadLabel apadLabelSmall">N</text>
|
|
||||||
</div>
|
|
||||||
<div id="apadInfo" class="apadRectBtn apadBtn apadBattle" data-key="V">
|
|
||||||
<text class="apadLabel apadLabelSmall">V</text>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="control-group-extra-1" class="control-group control-group-extra">
|
||||||
|
<!-- buttons to navigate settings tabs -->
|
||||||
|
<div id="apadPreviousTab" class="apad-button apad-square apad-small" data-key="CYCLE_FORM">
|
||||||
|
<span class="apad-label">F</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="apadNextTab" class="apad-button apad-square apad-small" data-key="CYCLE_SHINY">
|
||||||
|
<span class="apad-label">R</span>
|
||||||
|
</div>
|
||||||
|
<!-- buttons to open filter menu in starter select -->
|
||||||
|
<div id="apadOpenFilters" class="apad-button apad-rectangle apad-small" data-key="STATS">
|
||||||
|
<span class="apad-label">C</span>
|
||||||
|
</div>
|
||||||
|
<!-- main menu button -->
|
||||||
|
<div id="apadMenu" class="apad-button apad-rectangle apad-small" data-key="MENU">
|
||||||
|
<span class="apad-label">Menu</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="control-group-extra-2" class="control-group control-group-extra">
|
||||||
|
<!-- buttons to cycle through pokemon characteristics in starter select -->
|
||||||
|
<div id="apadCycleForm" class="apad-button apad-square apad-small" data-key="CYCLE_FORM">
|
||||||
|
<span class="apad-label">F</span>
|
||||||
|
</div>
|
||||||
|
<div id="apadCycleGender" class="apad-button apad-square apad-small" data-key="CYCLE_GENDER">
|
||||||
|
<span class="apad-label">G</span>
|
||||||
|
</div>
|
||||||
|
<div id="apadCycleShiny" class="apad-button apad-square apad-small" data-key="CYCLE_SHINY">
|
||||||
|
<span class="apad-label">R</span>
|
||||||
|
</div>
|
||||||
|
<div id="apadCycleAbility" class="apad-button apad-square apad-small" data-key="CYCLE_ABILITY">
|
||||||
|
<span class="apad-label">E</span>
|
||||||
|
</div>
|
||||||
|
<div id="apadCycleNature" class="apad-button apad-square apad-small" data-key="CYCLE_NATURE">
|
||||||
|
<span class="apad-label">N</span>
|
||||||
|
</div>
|
||||||
|
<div id="apadCycleVariant" class="apad-button apad-square apad-small" data-key="V">
|
||||||
|
<span class="apad-label">V</span>
|
||||||
|
</div>
|
||||||
|
<!-- buttons to display battle-specific information -->
|
||||||
|
<div id="apadInfo" class="apad-button apad-rectangle apad-small" data-key="V">
|
||||||
|
<span class="apad-label">V</span>
|
||||||
|
</div>
|
||||||
|
<div id="apadStats" class="apad-button apad-rectangle apad-small" data-key="STATS">
|
||||||
|
<span class="apad-label">C</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="tnc-links">
|
||||||
|
<a href="#" class="termly-display-preferences" style="display: none;" target="_blank" rel="noreferrer noopener">Consent Preferences</a>
|
||||||
|
<a href="https://app.termly.io/policy-viewer/policy.html?policyUUID=bc96778b-3f04-4d25-bafc-0deba53e8bec" target="_blank" rel="noreferrer noopener">Privacy Policy</a>
|
||||||
|
<a href="https://app.termly.io/policy-viewer/policy.html?policyUUID=8b523c05-7ec2-4646-9534-5bd61b386e2a" target="_blank" rel="noreferrer noopener">Cookie Disclaimer</a>
|
||||||
|
<a href="https://app.termly.io/policy-viewer/policy.html?policyUUID=b01e092a-9721-477f-8356-45576702ff9e" target="_blank" rel="noreferrer noopener">Terms & Conditions</a>
|
||||||
|
<a href="https://app.termly.io/policy-viewer/policy.html?policyUUID=3b5d1928-3f5b-4ee1-b8df-2d6c276b0bcc" target="_blank" rel="noreferrer noopener">Acceptable Use Policy</a>
|
||||||
</div>
|
</div>
|
||||||
<script type="module" src="./src/main.ts"></script>
|
<script type="module" src="./src/main.ts"></script>
|
||||||
<script src="./src/touch-controls.ts" type="module"></script>
|
<script src="./src/touch-controls.ts" type="module"></script>
|
||||||
<script src="./src/debug.js" type="module"></script>
|
<script src="./src/debug.js" type="module"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
6386
package-lock.json
generated
6386
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
47
package.json
47
package.json
@ -7,36 +7,39 @@
|
|||||||
"start": "vite",
|
"start": "vite",
|
||||||
"start:dev": "vite --mode development",
|
"start:dev": "vite --mode development",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
|
"build:beta": "vite build --mode beta",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"test": "vitest run",
|
"test": "vitest run --project pre && vitest run --project main",
|
||||||
"test:cov": "vitest run --coverage",
|
"test:cov": "vitest run --project pre && vitest run --project main --coverage",
|
||||||
"test:watch": "vitest watch --coverage",
|
"test:watch": "vitest run --project pre && vitest watch --project main --coverage",
|
||||||
"test:silent": "vitest run --silent",
|
"test:silent": "vitest run --project pre && vitest run --project main --silent",
|
||||||
|
"typecheck": "tsc --noEmit",
|
||||||
"eslint": "eslint --fix .",
|
"eslint": "eslint --fix .",
|
||||||
"eslint-ci": "eslint .",
|
"eslint-ci": "eslint .",
|
||||||
"docs": "typedoc"
|
"docs": "typedoc",
|
||||||
|
"depcruise": "depcruise src",
|
||||||
|
"depcruise:graph": "depcruise src --output-type dot | node dependency-graph.js > dependency-graph.svg"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.3.0",
|
"@eslint/js": "^9.3.0",
|
||||||
|
"@hpcc-js/wasm": "^2.18.0",
|
||||||
|
"@stylistic/eslint-plugin-ts": "^2.6.0-beta.0",
|
||||||
|
"@types/jsdom": "^21.1.7",
|
||||||
"@types/node": "^20.12.13",
|
"@types/node": "^20.12.13",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.10.0",
|
"@typescript-eslint/eslint-plugin": "^8.0.0-alpha.54",
|
||||||
"@typescript-eslint/parser": "^7.10.0",
|
"@typescript-eslint/parser": "^8.0.0-alpha.54",
|
||||||
"@vitest/coverage-istanbul": "^1.4.0",
|
"@vitest/coverage-istanbul": "^2.0.4",
|
||||||
"axios": "^1.6.2",
|
"dependency-cruiser": "^16.3.10",
|
||||||
"axios-cache-interceptor": "^1.3.2",
|
"eslint": "^9.7.0",
|
||||||
"eslint": "^8.57.0",
|
|
||||||
"eslint-plugin-import": "^2.29.1",
|
|
||||||
"jsdom": "^24.0.0",
|
"jsdom": "^24.0.0",
|
||||||
"json-beautify": "^1.1.1",
|
|
||||||
"lefthook": "^1.6.12",
|
"lefthook": "^1.6.12",
|
||||||
"phaser3spectorjs": "^0.0.8",
|
"phaser3spectorjs": "^0.0.8",
|
||||||
"pokenode-ts": "^1.20.0",
|
"typedoc": "^0.26.4",
|
||||||
"typedoc": "^0.25.13",
|
"typescript": "^5.5.3",
|
||||||
"typescript": "^5.4.5",
|
"typescript-eslint": "^8.0.0-alpha.54",
|
||||||
"typescript-eslint": "^7.10.0",
|
"vite": "^5.3.5",
|
||||||
"vite": "^4.5.0",
|
"vite-tsconfig-paths": "^4.3.2",
|
||||||
"vite-plugin-fs": "^0.4.4",
|
"vitest": "^2.0.4",
|
||||||
"vitest": "^1.4.0",
|
|
||||||
"vitest-canvas-mock": "^0.3.3"
|
"vitest-canvas-mock": "^0.3.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -53,7 +56,9 @@
|
|||||||
"node": ">=20.0.0"
|
"node": ">=20.0.0"
|
||||||
},
|
},
|
||||||
"imports": {
|
"imports": {
|
||||||
|
"#enums/*": "./enums/*",
|
||||||
"#app": "./src/main.js",
|
"#app": "./src/main.js",
|
||||||
"#app/*": "./src/*"
|
"#app/*": "./src/*",
|
||||||
|
"#test/*": "./src/test/*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user