chore: handle merge conflicts

This commit is contained in:
Sirz Benjie 2025-09-16 09:38:02 -05:00
commit 079fe97109
No known key found for this signature in database
GPG Key ID: 4A524B4D196C759E
13326 changed files with 378918 additions and 331570 deletions

View File

@ -2,11 +2,32 @@
module.exports = {
forbidden: [
{
name: "no-circular-at-runtime",
severity: "warn",
name: "no-non-type-@type-exports",
severity: "error",
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) ",
"Files in @types should not export anything but types and interfaces. The folder is intended to house imports that are removed at runtime, and thus should not contain anything with a bearing on runtime code.",
from: {},
to: {
path: "(^|/)src/@types",
dependencyTypesNot: ["type-only"],
},
},
{
name: "only-type-imports",
severity: "error",
comment: "Files in 'enums/' and '@types/' must only use type imports.",
from: {
path: ["(^|/)src/@types", "(^|/)src/enums"],
},
to: {
dependencyTypesNot: ["type-only"],
},
},
{
name: "no-circular-at-runtime",
severity: "error",
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,
@ -18,12 +39,8 @@ module.exports = {
{
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",
"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: "error",
from: {
orphan: true,
pathNot: [
@ -31,6 +48,7 @@ module.exports = {
"[.]d[.]ts$", // TypeScript declaration files
"(^|/)tsconfig[.]json$", // TypeScript config
"(^|/)(?:babel|webpack)[.]config[.](?:js|cjs|mjs|ts|cts|mts|json)$", // other configs
"(^|/)test/.+[.]setup[.]ts", // Vitest setup files
],
},
to: {},
@ -38,9 +56,8 @@ module.exports = {
{
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",
"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: "error",
from: {},
to: {
dependencyTypes: ["core"],
@ -71,9 +88,8 @@ module.exports = {
{
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",
"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: "error",
from: {},
to: {
dependencyTypes: ["deprecated"],
@ -83,10 +99,7 @@ module.exports = {
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.",
"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"],
@ -95,8 +108,7 @@ module.exports = {
{
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.",
"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: {
@ -106,10 +118,8 @@ module.exports = {
{
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",
"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: "error",
from: {},
to: {
moreThanOneDependencyType: true,
@ -120,14 +130,12 @@ module.exports = {
},
},
/* rules you might want to tweak for your specific situation: */
// 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.",
"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: {
@ -138,11 +146,7 @@ module.exports = {
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",
"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)$", "./test"],
@ -159,10 +163,7 @@ module.exports = {
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.",
"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 yourdependency-cruiser configuration.",
from: {},
to: {
dependencyTypes: ["npm-optional"],
@ -171,11 +172,8 @@ module.exports = {
{
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",
"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: "error",
from: {},
to: {
dependencyTypes: ["npm-peer"],
@ -183,6 +181,7 @@ module.exports = {
},
],
options: {
exclude: ["src/plugins/vite/*", "src/vite.env.d.ts"],
/* Which modules not to follow further when encountered */
doNotFollow: {
/* path: an array of regular expressions in strings to match against */
@ -205,7 +204,7 @@ module.exports = {
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.
*/
@ -213,7 +212,7 @@ module.exports = {
// 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
to open it on your online repo or `vscode://file/${process.cwd()}/` to
open it in visual studio code),
*/
// prefix: `vscode://file/${process.cwd()}/`,
@ -222,7 +221,7 @@ module.exports = {
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,
tsPreCompilationDeps: true,
/* 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
@ -258,7 +257,7 @@ module.exports = {
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
to be passed if your webpack config is a function and takes them (see
webpack documentation for details)
*/
// webpackConfig: {
@ -297,7 +296,7 @@ module.exports = {
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
can access (run `pnpm exec 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.
@ -309,8 +308,8 @@ module.exports = {
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
documentation
Defaults to an empty array (= don't use alias fields).
*/
// aliasFields: ["browser"],

View File

@ -0,0 +1,61 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node
{
"name": "Node.js & TypeScript",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm",
"features": {
"ghcr.io/devcontainers/features/github-cli:1": {
"installDirectlyFromGitHubRelease": true,
"version": "latest"
},
"ghcr.io/devcontainers-extra/features/pnpm:2": {
"version": "latest"
}
},
"customizations": {
"vscode": {
"settings": {
// # Formatter configs
"editor.defaultFormatter": "biomejs.biome",
"editor.tabSize": 2,
"editor.insertSpaces": true,
"editor.codeActionsOnSave": {
"source.addMissingImports.ts": "always",
"source.removeUnusedImports": "always",
"source.fixAll.biome": "always",
"source.organizeImports.biome": "always"
},
"biome.suggestInstallingGlobally": false,
// # JS/TS setting overrides
"javascript.preferences.importModuleSpecifier": "non-relative",
"javascript.preferences.importModuleSpecifierEnding": "index",
"javascript.preferGoToSourceDefinition": true,
"javascript.updateImportsOnFileMove.enabled": "always",
"typescript.preferences.importModuleSpecifier": "non-relative",
"typescript.preferences.importModuleSpecifierEnding": "index",
"typescript.preferGoToSourceDefinition": true,
"typescript.updateImportsOnFileMove.enabled": "always",
"typescript.tsserver.experimental.enableProjectDiagnostics": true,
// # Miscellaneous
"npm.packageManager": "pnpm",
"npm.scriptRunner": "pnpm",
"vitest.cliArguments": "--no-isolate"
},
"extensions": [
"biomejs.biome",
"YoavBls.pretty-ts-errors",
"vitest.explorer",
"adpyke.codesnap", // Bind to a hotkey (ctrl+\, etc) for best results
"aaron-bond.better-comments",
"MuTsunTsai.jsdoc-link"
]
}
},
"postCreateCommand": "pnpm install",
"forwardPorts": [8000]
}

7
.dockerignore Normal file
View File

@ -0,0 +1,7 @@
# .dockerignore
node_modules
*.log
*.md
.gitignore
Dockerfile
.env

View File

@ -3,7 +3,7 @@
# top-most EditorConfig file
root = true
[src/*.{js,ts}]
[**/*.{js,ts,json,jsonc}]
indent_style = space
indent_size = 2
end_of_line = lf

View File

@ -1,6 +1,7 @@
VITE_BYPASS_LOGIN=1
VITE_BYPASS_TUTORIAL=0
VITE_SERVER_URL=http://localhost:8001
# IDs for discord/google auth go unused due to VITE_BYPASS_LOGIN
VITE_DISCORD_CLIENT_ID=1234567890
VITE_GOOGLE_CLIENT_ID=1234567890
VITE_I18N_DEBUG=0

14
.github/CODEOWNERS vendored
View File

@ -3,12 +3,9 @@
# everything (whole code-base) - Junior Devs
* @pagefaultgames/junior-dev-team
# github actions/templates etc. - Dev Leads
/.github @pagefaultgames/senior-dev-team
# Art Team
/public/**/*.png @pagefaultgames/art-team
/public/**/*.json @pagefaultgames/art-team
/public/**/*.json @pagefaultgames/art-team
/public/images @pagefaultgames/art-team
/public/battle-anims @pagefaultgames/art-team
@ -19,4 +16,11 @@
/public/audio @pagefaultgames/composer-team
# Balance Files; contain actual code logic and must also be owned by dev team
/src/data/balance @pagefaultgames/balance-team @pagefaultgames/junior-dev-team
/src/data/balance @pagefaultgames/balance-team @pagefaultgames/junior-dev-team
/src/data/trainers @pagefaultgames/balance-team @pagefaultgames/junior-dev-team
# GitHub actions/templates etc. - Senior Devs
# Should be defined last in the file to make sure these always override all other definitions
/.github @pagefaultgames/senior-dev-team
package.json @pagefaultgames/senior-dev-team
pnpm-lock.yaml @pagefaultgames/senior-dev-team

View File

@ -2,25 +2,28 @@
<!-- Feel free to look at other PRs for examples -->
<!--
Make sure the title includes categorization (choose the one that best fits):
- [Bug]: If the PR is primarily a bug fix
- [Move]: If a move has new or changed functionality
- [Ability]: If an ability has new or changed functionality
- [Item]: For new or modified items
- [Mystery]: For new or modified Mystery Encounters
- [Test]: If the PR is primarily adding or modifying tests
- [UI/UX]: If the PR is changing UI/UX elements
- [Audio]: If the PR is adding or changing music/sfx
- [Sprite]: If the PR is adding or changing sprites
- [Balance]: If the PR is related to game balance
- [Challenge]: If the PR is adding or modifying challenges
- [Bug]: If the PR is primarily a bug fix
- [Move]: If a move has new or changed functionality
- [Ability]: If an ability has new or changed functionality
- [Item]: For new or modified items
- [Mystery]: For new or modified Mystery Encounters
- [Test]: If the PR is primarily adding or modifying tests
- [UI/UX]: If the PR is changing UI/UX elements
- [Audio]: If the PR is adding or changing music/sfx
- [Sprite]: If the PR is adding or changing sprites
- [Balance]: If the PR is related to game balance
- [Challenge]: If the PR is adding or modifying challenges
- [Refactor]: If the PR is primarily rewriting existing code
- [Docs]: If the PR is just adding or modifying documentation (such as tsdocs/code comments)
- [GitHub]: For changes to GitHub workflows/templates/etc
- [Misc]: If no other category fits the PR
- [Dev]: If the PR is primarily changing something pertaining to development (lefthook hooks, linter rules, etc.)
- [i18n]: If the PR is primarily adding/changing locale keys or key usage (may come with an associated locales PR)
- [Docs]: If the PR is adding or modifying documentation (such as tsdocs/code comments)
- [GitHub]: For changes to GitHub workflows/templates/etc
- [Misc]: If no other category fits the PR
-->
<!--
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 the user will see?
@ -65,12 +68,12 @@ Do the reviewers need to do something special in order to test your changes?
- [ ] The PR is self-contained and cannot be split into smaller PRs?
- [ ] Have I provided a clear explanation of the changes?
- [ ] Have I tested the changes manually?
- [ ] Are all unit tests still passing? (`npm run test`)
- [ ] Have I created new automated tests (`npm run create-test`) or updated existing tests related to the PR's changes?
- [ ] Are all unit tests still passing? (`pnpm test:silent`)
- [ ] Have I created new automated tests (`pnpm test:create`) or updated existing tests related to the PR's changes?
- [ ] Have I provided screenshots/videos of the changes (if applicable)?
- [ ] Have I made sure that any UI change works for both UI themes (default and legacy)?
Are there any localization additions or changes? If so:
- [ ] Has a locales PR been created on the [locales](https://github.com/pagefaultgames/pokerogue-locales) repo?
- [ ] If so, please leave a link to it here:
- [ ] Has the translation team been contacted for proofreading/translation?
- [ ] Has the translation team been contacted for proofreading/translation?

15
.github/test-filters.yml vendored Normal file
View File

@ -0,0 +1,15 @@
all:
# Negations syntax from https://github.com/dorny/paths-filter/issues/184#issuecomment-2786521554
- "src/**/!(*.{md,py,sh,gitkeep,gitignore})"
- "test/**/!(*.{md,py,sh,gitkeep,gitignore})"
- "public/**/!(*.{md,py,sh,gitkeep,gitignore})"
# Workflows that can impact tests
- ".github/workflows/test*.yml"
- ".github/test-filters.yml"
# top-level files
- "package*.json"
- ".nvrmc" # Updates to node version can break tests
- "vite*" # vite.config.ts, vite.vitest.config.ts, vitest.workspace.ts
- "tsconfig*.json" # tsconfig.json tweaking can impact compilation
- "global.d.ts"
- ".env*"

88
.github/workflows/create-release.yml vendored Normal file
View File

@ -0,0 +1,88 @@
name: Create Release Branch
on:
workflow_dispatch:
inputs:
versionName:
description: "Name of version (i.e. 1.9.0)"
type: string
required: true
confirmVersion:
type: string
required: true
description: "Confirm version name"
# explicitly specify the necessary scopes
permissions:
pull-requests: write
actions: write
contents: write
jobs:
create-release:
if: github.repository == 'pagefaultgames/pokerogue' && (vars.BETA_DEPLOY_BRANCH == '' || ! startsWith(vars.BETA_DEPLOY_BRANCH, 'release'))
timeout-minutes: 10
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed for github cli commands
runs-on: ubuntu-latest
steps:
- name: Validate provided version
# Ensure version matches confirmation and conforms to expected pattern.
run: |
if [[ "${{ github.event.inputs.versionName }}" != "${{ github.event.inputs.confirmVersion }}" ]]; then
echo "Version name does not match confirmation. Exiting."
exit 1
fi
if [[ ! "${{ github.event.inputs.versionName }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Version name must follow the format X.Y.Z where X, Y, and Z are all numbers. Exiting..."
exit 1
fi
shell: bash
- uses: actions/create-github-app-token@v2
id: app-token
with:
app-id: ${{ secrets.PAGEFAULT_APP_ID }}
private-key: ${{ secrets.PAGEFAULT_APP_PRIVATE_KEY }}
- name: Check out code
uses: actions/checkout@v4
with:
submodules: "recursive"
# Always base off of beta branch, regardless of the branch the workflow was triggered from.
ref: beta
token: ${{ steps.app-token.outputs.token }}
- name: Create release branch
run: git checkout -b release
# In order to be able to open a PR into beta, we need the branch to have at least one commit.
# The first commit is _usually_ just bumping the version number, so we can kill 2 birds with 1 stone here
- name: Bump release version
run: |
git config --local user.name "github-actions[bot]"
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
pnpm --no-git-tag-version version ${{ github.events.inputs.versionName }}
git commit -am "Stage release for v${{ github.events.inputs.versionName }}"
- name: Push new branch
run: git push origin release
# The repository variable is used by the deploy-beta workflow to determine whether to deploy from beta or release.
- name: Set repository variable
run: GITHUB_TOKEN="${{ steps.app-token.outputs.token }}" gh variable set BETA_DEPLOY_BRANCH --body "release"
- name: Create pull request to main
run: |
gh pr create --base main \
--head release \
--title "Release v${{ github.event.inputs.versionName }} to main" \
--body "This PR is for the release of v${{ github.event.inputs.versionName }}, and was created automatically by the GitHub Actions workflow invoked by ${{ github.actor }}" \
--draft
- name: Create pull request to beta
run: |
gh pr create --base beta \
--head release \
--title "Release v${{ github.event.inputs.versionName }} to beta" \
--body "This PR is for the release of v${{ github.event.inputs.versionName }}, and was created automatically by the GitHub Actions workflow invoked by ${{ github.actor }}" \
--draft

View File

@ -4,24 +4,37 @@ on:
push:
branches:
- beta
- release
workflow_run:
types: completed
workflows: ["Post Release Deleted"]
jobs:
deploy:
if: github.repository == 'pagefaultgames/pokerogue'
if: github.repository == 'pagefaultgames/pokerogue' && github.ref_name == (vars.BETA_DEPLOY_BRANCH || 'beta')
timeout-minutes: 10
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: 'recursive'
submodules: "recursive"
ref: ${{ vars.BETA_DEPLOY_BRANCH || 'beta'}}
- name: Install pnpm
uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
node-version-file: ".nvmrc"
- name: Install dependencies
run: npm ci
run: pnpm i
- name: Build
run: npm run build:beta
run: pnpm build:beta
env:
NODE_ENV: production
- name: Set up SSH
run: |
mkdir ~/.ssh
@ -29,6 +42,7 @@ jobs:
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 }}
run: |
rsync --del --no-times --checksum -vrm dist/* ${{ secrets.BETA_SSH_USER }}@${{ secrets.BETA_SSH_HOST }}:${{ secrets.BETA_DESTINATION_DIR }}

View File

@ -11,20 +11,28 @@ on:
jobs:
deploy:
if: github.repository == 'pagefaultgames/pokerogue'
timeout-minutes: 10
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: 'recursive'
- name: Install pnpm
uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
- name: Install dependencies
run: npm ci
run: pnpm i
- name: Build
run: npm run build
run: pnpm build
env:
NODE_ENV: production
- name: Set up SSH
if: github.event_name == 'push' && github.ref_name == 'main'
run: |
@ -33,11 +41,13 @@ jobs:
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/*
ssh-keyscan -H ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts
- name: Deploy build on server
if: github.event_name == 'push' && github.ref_name == 'main'
run: |
run: |
rsync --del --no-times --checksum -vrm dist/* ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}:${{ secrets.DESTINATION_DIR }}
ssh -t ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} "~/prmanifest --inpath ${{ secrets.DESTINATION_DIR }} --outpath ${{ secrets.DESTINATION_DIR }}/manifest.json"
- name: Purge Cloudflare Cache
if: github.event_name == 'push' && github.ref_name == 'main'
id: purge-cache

View File

@ -4,10 +4,15 @@ on:
push:
branches:
- main
- beta
- release
- 'hotfix*'
pull_request:
branches:
- main
- beta
- release
- 'hotfix*'
merge_group:
types: [checks_requested]
@ -15,26 +20,32 @@ jobs:
pages:
name: Github Pages
if: github.repository == 'pagefaultgames/pokerogue'
timeout-minutes: 10
runs-on: ubuntu-latest
env:
api-dir: ./
strategy:
fail-fast: false
docs-dir: ./pokerogue_docs
# Only push docs when running on pushes to main/beta
DRY_RUN: ${{github.event_name != 'push' || (github.ref_name != 'beta' && github.ref_name != 'main')}}
steps:
- name: Checkout repository for Typedoc
uses: actions/checkout@v4
with:
submodules: 'recursive'
path: pokerogue_docs
sparse-checkout: |
/*
!/public/
/public/images/pokemon/variant/_exp_masterlist.json
/public/images/pokemon/variant/_masterlist.json
/public/images/logo.png
sparse-checkout-cone-mode: false
- name: Install OS package
run: |
sudo apt update
sudo apt install -y git openssh-client
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 10
- name: Setup Node 22.14.1
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version-file: "pokerogue_docs/.nvmrc"
@ -47,26 +58,24 @@ jobs:
ref: gh-pages
- name: Install Node.js dependencies
working-directory: ${{env.api-dir}}
run: |
cd pokerogue_docs
npm ci
working-directory: ${{env.docs-dir}}
run: pnpm i
- name: Generate Typedoc docs
working-directory: ${{env.api-dir}}
run: |
cd pokerogue_docs
npm run docs -- --out /tmp/docs --githubPages false --entryPoints ./src/
working-directory: ${{env.docs-dir}}
env:
REF_NAME: ${{github.ref_name}}
DRY_RUN: ${{env.DRY_RUN}}
run: pnpm typedoc
- name: Commit & Push docs
if: github.event_name == 'push'
# env vars are stored as strings instead of booleans (hence why an explicit check is required)
if: ${{ env.DRY_RUN == 'false'}}
run: |
cd pokerogue_gh
git config user.email "github-actions[bot]@users.noreply.github.com"
git config user.name "github-actions[bot]"
mkdir -p $GITHUB_REF_NAME
rm -rf $GITHUB_REF_NAME/*
cp -r /tmp/docs/. $GITHUB_REF_NAME
rsync -rd --delete /tmp/docs/ $GITHUB_REF_NAME
git add $GITHUB_REF_NAME
git commit --allow-empty -m "[skip ci] Deploy docs"
git push
git commit -m "[skip ci] Deploy docs"
git push

109
.github/workflows/linting.yml vendored Normal file
View File

@ -0,0 +1,109 @@
name: Linting
on:
push:
branches:
- main
- beta
- release
- 'hotfix*'
pull_request:
branches:
- main
- beta
- release
- 'hotfix*'
merge_group:
types: [checks_requested]
jobs:
run-linters:
name: Run all linters
timeout-minutes: 10
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@v4
with:
submodules: "recursive"
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
cache: "pnpm"
- name: Install Node modules
run: pnpm i
# Lint files with Biome-Lint - https://biomejs.dev/linter/
- name: Run Biome-Lint
run: pnpm biome-ci
id: biome_lint
continue-on-error: true
# Validate dependencies with dependency-cruiser - https://github.com/sverweij/dependency-cruiser
- name: Run Dependency-Cruise
run: pnpm depcruise
id: depcruise
continue-on-error: true
# Validate types with tsc - https://www.typescriptlang.org/docs/handbook/compiler-options.html#using-the-cli
- name: Run Typecheck
run: pnpm typecheck
id: typecheck
continue-on-error: true
# The exact same thing
- name: Run Typecheck (scripts)
run: pnpm typecheck:scripts
id: typecheck-scripts
continue-on-error: true
- name: Evaluate for Errors
env:
BIOME_LINT_OUTCOME: ${{ steps.biome_lint.outcome }}
DEPCRUISE_OUTCOME: ${{ steps.depcruise.outcome }}
TYPECHECK_OUTCOME: ${{ steps.typecheck.outcome }}
TYPECHECK_SCRIPTS_OUTCOME: ${{ steps.typecheck-scripts.outcome }}
run: |
# Check for Errors
# Make text red.
red () {
printf "\e[31m%s\e[0m" "$1"
}
# Make text green.
green () {
printf "\e[32m%s\e[0m" "$1"
}
print_result() {
local name=$1
local outcome=$2
if [ "$outcome" == "success" ]; then
printf "$(green "✅ $name: $outcome")\n"
else
printf "$(red "❌ $name: $outcome")\n"
fi
}
print_result "Biome" "$BIOME_LINT_OUTCOME"
print_result "Depcruise" "$DEPCRUISE_OUTCOME"
print_result "Typecheck" "$TYPECHECK_OUTCOME"
print_result "Typecheck scripts" "$TYPECHECK_SCRIPTS_OUTCOME"
if [[ "$BIOME_LINT_OUTCOME" != "success" || \
"$DEPCRUISE_OUTCOME" != "success" || \
"$TYPECHECK_OUTCOME" != "success" || \
"$TYPECHECK_SCRIPTS_OUTCOME" != "success" ]]; then
printf "$(red "❌ One or more checks failed!")\n" >&2
exit 1
fi
printf "$(green "✅ All checks passed!")\n"

View File

@ -0,0 +1,13 @@
name: Post Release Deleted
on:
delete:
jobs:
# Set the BETA_DEPLOY_BRANCH variable to beta when a release branch is deleted
update-release-var:
if: github.repository == 'pagefaultgames/pokerogue' && github.event.ref_type == 'branch' && github.event.ref == 'release'
timeout-minutes: 5
runs-on: ubuntu-latest
steps:
- name: Set BETA_DEPLOY_BRANCH to beta
run: GITHUB_TOKEN="${{ secrets.RW_VARS_PAT }}" gh variable set BETA_DEPLOY_BRANCH --body "beta" --repo "pagefaultgames/pokerogue"

View File

@ -1,41 +0,0 @@
name: Biome Code Quality
on:
# Trigger the workflow on push or pull request,
# but only for the main branch
push:
branches:
- main # Trigger on push events to the main branch
- beta # Trigger on push events to the beta branch
pull_request:
branches:
- main # Trigger on pull request events targeting the main branch
- beta # Trigger on pull request events targeting the beta branch
merge_group:
types: [checks_requested]
jobs:
run-linters: # Define a job named "run-linters"
name: Run linters # Human-readable name for the job
runs-on: ubuntu-latest # Specify the latest Ubuntu runner for the job
steps:
- name: Check out Git repository # Step to check out the repository
uses: actions/checkout@v4 # Use the checkout action version 4
with:
submodules: 'recursive'
- name: Set up Node.js # Step to set up Node.js environment
uses: actions/setup-node@v4 # Use the setup-node action version 4
with:
node-version-file: '.nvmrc'
cache: 'npm'
- name: Install Node.js dependencies # Step to install Node.js dependencies
run: npm ci # Use 'npm ci' to install dependencies
- name: eslint # Step to run linters
run: npm run eslint-ci
- name: Lint with Biome # Step to run linters
run: npm run biome-ci

View File

@ -12,22 +12,35 @@ on:
totalShards:
required: true
type: number
skip:
required: true
type: boolean
default: false
jobs:
test:
name: Shard ${{ inputs.shard }} of ${{ inputs.totalShards }}
# We can't use dynmically named jobs until https://github.com/orgs/community/discussions/13261 is implemented
name: Shard
timeout-minutes: 10
runs-on: ubuntu-latest
if: ${{ !inputs.skip }}
steps:
- name: Check out Git repository
uses: actions/checkout@v4.2.2
with:
submodules: 'recursive'
submodules: "recursive"
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'
node-version-file: ".nvmrc"
cache: "pnpm"
- name: Install Node.js dependencies
run: npm ci
run: pnpm i
- name: Run tests
run: npx vitest --project ${{ inputs.project }} --no-isolate --shard=${{ inputs.shard }}/${{ inputs.totalShards }} ${{ !runner.debug && '--silent' || '' }}
run: pnpm test:silent --shard=${{ inputs.shard }}/${{ inputs.totalShards }}

View File

@ -1,67 +1,54 @@
name: Tests
on:
# Trigger the workflow on push or pull request,
# but only for the main branch
push:
branches:
- main # Trigger on push events to the main branch
- beta # Trigger on push events to the beta branch
# go upvote https://github.com/actions/runner/issues/1182 and yell at microsoft until they fix this or ditch yml for workflows
paths:
# src and test files
- "src/**"
- "test/**"
- "public/**"
# Workflows that can impact tests
- ".github/workflows/test*.yml"
# top-level files
- "package*.json"
- ".nvrmc" # Updates to node version can break tests
- "vite.*.ts" # vite.config.ts, vite.vitest.config.ts, vitest.workspace.ts
- "tsconfig*.json" # tsconfig.json tweaking can impact compilation
- "global.d.ts"
- ".env.*"
# Blanket negations for files that cannot impact tests
- "!**/*.py" # No .py files
- "!**/*.sh" # No .sh files
- "!**/*.md" # No .md files
- "!**/.git*" # .gitkeep and family
- main
- beta
- release
- 'hotfix*'
pull_request:
branches:
- main # Trigger on pull request events targeting the main branch
- beta # Trigger on pull request events targeting the beta branch
paths: # go upvote https://github.com/actions/runner/issues/1182 and yell at microsoft because until then we have to duplicate this
# src and test files
- "src/**"
- "test/**"
- "public/**"
# Workflows that can impact tests
- ".github/workflows/test*.yml"
# top-level files
- "package*.json"
- ".nvrmc" # Updates to node version can break tests
- "vite*" # vite.config.ts, vite.vitest.config.ts, vitest.workspace.ts
- "tsconfig*.json" # tsconfig.json tweaking can impact compilation
- "global.d.ts"
- ".env.*"
# Blanket negations for files that cannot impact tests
- "!**/*.py" # No .py files
- "!**/*.sh" # No .sh files
- "!**/*.md" # No .md files
- "!**/.git*" # .gitkeep and family
- main
- beta
- release
- 'hotfix*'
merge_group:
types: [checks_requested]
workflow_dispatch:
jobs:
check-path-change-filter:
timeout-minutes: 5
runs-on: ubuntu-latest
permissions:
pull-requests: read
outputs:
all: ${{ steps.filter.outputs.all }}
steps:
- name: checkout
uses: actions/checkout@v4
with:
sparse-checkout: |
.github/test-filters.yml
sparse-checkout-cone-mode: false
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36
id: filter
with:
filters: .github/test-filters.yml
run-tests:
name: Run Tests
needs: check-path-change-filter
strategy:
# don't stop upon 1 shard failing
fail-fast: false
matrix:
shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
shard: [1, 2, 3, 4, 5]
uses: ./.github/workflows/test-shard-template.yml
with:
project: main
shard: ${{ matrix.shard }}
totalShards: 10
totalShards: 5
skip: ${{ needs.check-path-change-filter.outputs.all != 'true'}}

5
.gitignore vendored
View File

@ -11,9 +11,12 @@ node_modules
dist
dist-ssr
*.local
build
# Editor directories and files
# Editor directories and files (excluding `extensions.json` for devcontainer)
*.code-workspace
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo

29
.ls-lint.yml Normal file
View File

@ -0,0 +1,29 @@
# Base settings to use
# Note that the `_cfg` key isn't part of ls-lint's configuration, it's just a YAML anchor for reuse.
_cfg: &cfg
.ps1: kebab-case
.ts: kebab-case
.js: kebab-case
.*.ts: kebab-case
.*.js: kebab-case
.dir: kebab-case
.py: snake_case # python files should always use snake_case
ls:
<<: *cfg
src: &src
<<: *cfg
.dir: kebab-case | regex:@types
.js: exists:0
src/system/version-migration/versions:
.ts: snake_case
<<: *cfg
test: *src
ignore:
- node_modules
- .vscode
- .github
- .git
- public
- dist
- .devcontainer

13
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,13 @@
{
"recommendations": [
"biomejs.biome",
"YoavBls.pretty-ts-errors",
"vitest.explorer",
// This stuff isn't mandatory - it's just nice to have :)
"adpyke.codesnap", // Bind to a hotkey (ctrl+\, etc) for best results
"aaron-bond.better-comments",
"MuTsunTsai.jsdoc-link"
]
}

128
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,128 @@
# Contributing to PokéRogue
Thank you for taking the time to contribute, every little bit helps. This project is entirely open-source and unmonetized - community contributions are what keep it alive!
Please make sure you understand everything relevant to your changes from the [Table of Contents](#-table-of-contents), and absolutely *feel free to reach out in the **#dev-corner** channel on [Discord](https://discord.gg/pokerogue)*.
We are here to help and the better you understand what you're working on, the easier it will be for it to find its way into the game.
## 📄 Table of Contents
- [Development Basics](#-development-basics)
- [Environment Setup](#-environment-setup)
- [Getting Started](#-getting-started)
- [Documentation](#-documentation)
- [Testing Your Changes](#-testing-your-changes)
- [Development Save File (Unlock Everything)](#-development-save-file)
## 🛠️ Development Basics
PokéRogue is built with [Typescript](https://www.typescriptlang.org/docs/handbook/intro.html), using the [Phaser](https://github.com/phaserjs/phaser) game framework.
If you have the motivation and experience with Typescript/Javascript (or are willing to learn), you can contribute by forking the repository and making pull requests with contributions.
## 💻 Environment Setup
### Codespaces/Devcontainer Environment
Arguably the easiest way to get started is by using the prepared development environment.
We have a `.devcontainer/devcontainer.json` file, meaning we are compatible with:
- [![Open in GitHub Codespaces][codespaces-badge]][codespaces-link], or
- the [Visual Studio Code Remote - Containers][devcontainer-ext] extension.
This Linux environment comes with all required dependencies needed to start working on the project.
[codespaces-badge]: <https://github.com/codespaces/badge.svg>
[codespaces-link]: <https://github.com/codespaces/new?hide_repo_select=true&repo=620476224&ref=beta>
[devcontainer-ext]: <https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers>
### Local Development
#### Prerequisites
- node: >=22.14.0 - [manage with pnpm](https://pnpm.io/cli/env) | [manage with fnm](https://github.com/Schniz/fnm) | [manage with nvm](https://github.com/nvm-sh/nvm) | [manage with volta.sh](https://volta.sh/)
- pnpm: 10.x - [how to install](https://pnpm.io/installation) (not recommended to install via `npm` on Windows native) | [alternate method - volta.sh](https://volta.sh/)
- The repository [forked](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo) and [cloned](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) locally on your device
#### Running Locally
1. Run `pnpm install` from the repository root
- *if you run into any errors, reach out in the **#dev-corner** channel on Discord*
2. Run `pnpm start:dev` to locally run the project at `localhost:8000`
## 🚀 Getting Started
A great way to develop an understanding of how the project works is to look at test cases (located in [the `test` folder](./test/)).
Tests show you both how things are supposed to work and the expected "flow" to get from point A to point B in battles.
*This is a big project and you will be confused at times - never be afraid to reach out and ask questions in **#dev-corner***!
### Where to Look
Once you have your feet under you, check out the [Issues](https://github.com/pagefaultgames/pokerogue/issues) page to see how you can help us!
Most issues are bugs and are labeled with their area, such as `Move`, `Ability`, `UI/UX`, etc. There are also priority labels:
- `P0`: Completely gamebreaking (very rare)
- `P1`: Major - Game crash
- `P2`: Minor - Incorrect (but non-crashing) move/ability/interaction
- `P3`: No gameplay impact - typo, minor graphical error, etc.
Also under issues, you can take a look at the [List of Partial / Unimplemented Moves and Abilities](https://github.com/pagefaultgames/pokerogue/issues/3503) and the [Bug Board](https://github.com/orgs/pagefaultgames/projects/3). The latter is essentially the same as the issues page, so take your pick.
You are free to comment on any issue so that you may be assigned to it and we can avoid multiple people working on the same thing.
## 📚 Documentation
You can find the auto-generated documentation [here](https://pagefaultgames.github.io/pokerogue/main/index.html).
Additionally, the [docs folder](./docs) contains a variety of in-depth documents and guides useful for aspiring contributors. \
Notable topics include:
- [Commenting your code](./docs/comments.md)
- [Linting & Formatting](./docs/linting.md)
- [Localization](./docs/localization.md)
- [Enemy AI move selection](./docs/enemy-ai.md)
- [Running with Podman](./docs/podman.md)
Again, if you have unanswered questions please feel free to ask!
## 🧪 Testing Your Changes
You've just made a change - how can you check if it works? You have two areas to hit:
### 1 - Manual Testing
> This will likely be your first stop. After making a change, you'll want to spin the game up and make sure everything is as you expect. To do this, you will need a way to manipulate the game to produce the situation you're looking to test.
[src/overrides.ts](../src/overrides.ts) contains overrides for most values you'll need to change for testing, controlled through the `overrides` object.
For example, here is how you could test a scenario where the player Pokemon has the ability Drought and the enemy Pokemon has the move Water Gun:
```typescript
const overrides = {
ABILITY_OVERRIDE: AbilityId.DROUGHT,
ENEMY_MOVESET_OVERRIDE: MoveId.WATER_GUN,
} satisfies Partial<InstanceType<typeof DefaultOverrides>>;
```
Read through `src/overrides.ts` file to find the override that fits your needs - there are a lot of them!
If the situation you're trying to test can't be created using existing overrides (or with the [Dev Save](#-development-save-file)), reach out in **#dev-corner**.
You can get help testing your specific changes, and you might have found a new override that needs to be created!
### 2 - Automatic Testing
> PokéRogue uses [Vitest](https://vitest.dev/) for automatic testing. Checking out the existing tests in the [test](./test/) folder is a great way to understand how this works, and to get familiar with the project as a whole.
To make sure your changes didn't break any existing test cases, run `pnpm test:silent` in your terminal. You can also provide an argument to the command: to run only the Dancer (ability) tests, you could write `pnpm test:silent dancer`.
- __Note that passing all test cases does *not* guarantee that everything is working properly__. The project does not have complete regression testing.
Most non-trivial changes (*especially bug fixes*) should come along with new test cases.
- To make a new test file, run `pnpm test:create` and follow the prompts. If the move/ability/etc. you're modifying already has tests, simply add new cases to the end of the file. As mentioned before, the easiest way to get familiar with the system and understand how to write your own tests is simply to read the existing tests, particularly ones similar to the tests you intend to write.
- Ensure that new tests:
- Are deterministic. In other words, the test should never pass or fail when it shouldn't due to randomness. This involves primarily ensuring that abilities and moves are never randomly selected.
- As much as possible, are unit tests. If you have made two distinct changes, they should be tested in two separate cases.
- Test edge cases. A good strategy is to think of edge cases beforehand and create tests for them using `it.todo`. Once the edge case has been handled, you can remove the `todo` marker.
## 😈 Development Save File
> Some issues may require you to have unlocks on your save file which go beyond normal overrides. For this reason, the repository contains a [save file](../test/test-utils/saves/everything.psrv) with _everything_ unlocked (even ones not legitimately obtainable, like unimplemented variant shinies).
1. Start the game up locally and navigate to `Menu -> Manage Data -> Import Data`
2. Select [everything.prsv](test/test-utils/saves/everything.prsv) (`test/test-utils/saves/everything.prsv`) and confirm.

View File

@ -24,9 +24,10 @@
- Pokémon Sword/Shield
- Pokémon Legends: Arceus
- Pokémon Scarlet/Violet
- Firel (Custom Graveyard, Ice Cave, Laboratory, Metropolis, Plains, Power Plant, Seabed, Space, and Volcano biome music)
- Firel (Custom Graveyard, Ice Cave, Laboratory, Metropolis, Plains, Power Plant, Seabed, Space, Volcano, and Desert biome music)
- Lmz (Custom Ancient Ruins, Jungle, and Lake biome music)
- Andr06 (Custom Forest, Slum and Sea biome music)
- Andr06 (Custom Forest, Slum, Sea, and Fairy Cave biome music)
- Leavannite (Custom Wasteland biome music)
- _tresnoir
- unveiler
@ -40,6 +41,7 @@
## Backgrounds
- Squip (Paid Commissions)
- Contributions by Someonealive-QN
- Contributions by redactedinlight
## UI
- GAMEFREAK

47
Dockerfile Normal file
View File

@ -0,0 +1,47 @@
# syntax=docker/dockerfile:1
ARG NODE_VERSION=22.14
ARG OS=alpine
FROM node:${NODE_VERSION}-${OS}
# Create non-root user
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# Install git (for potential runtime needs)
RUN apk add --no-cache git
# Set working directory
WORKDIR /app
# Enable and prepare pnpm
RUN corepack enable && corepack prepare pnpm@10.14.0 --activate
COPY . .
# Copy package files
COPY package.json pnpm-lock.yaml ./
# Install all dependencies
RUN --mount=type=cache,target=/home/appuser/.pnpm-store \
pnpm install --frozen-lockfile && \
rm -rf /home/appuser/.pnpm-store/*
# Change ownership
RUN chown -R appuser:appgroup /app
# Switch to non-root user
USER appuser
# Set environment variables
ENV VITE_BYPASS_LOGIN=1 \
VITE_BYPASS_TUTORIAL=0 \
NEXT_TELEMETRY_DISABLED=1 \
PNP_HOME=/home/appuser/.shrc \
NODE_ENV=development \
PORT=8000
# Expose port
EXPOSE $PORT
# Start the app in development mode
CMD ["pnpm", "run", "start:podman"]

View File

@ -1,53 +1,18 @@
<picture><img src="./public/images/logo.png" width="300" alt="PokéRogue"></picture>
<div align="center"><picture><img src="./public/images/logo.png" width="300" alt="PokéRogue"></picture>
[![Discord Static Badge](https://img.shields.io/badge/Community_Discord-blurple?style=flat&logo=discord&logoSize=auto&labelColor=white&color=5865F2)](https://discord.gg/pokerogue)
[![Docs Coverage Static Badge](https://pagefaultgames.github.io/pokerogue/beta/coverage.svg)](https://pagefaultgames.github.io/pokerogue/beta)
[![Testing Badge](https://github.com/pagefaultgames/pokerogue/actions/workflows/tests.yml/badge.svg)](https://github.com/pagefaultgames/pokerogue/actions/workflows/tests.yml)
[![License: GNU AGPL v3](https://img.shields.io/badge/License-AGPL_v3-blue.svg)](https://www.gnu.org/licenses/agpl-3.0)</div>
PokéRogue is a browser based Pokémon fangame heavily inspired by the roguelite genre. Battle endlessly while gathering stacking items, exploring many different biomes, fighting trainers, bosses, and more!
# Contributing
## 🛠️ Development
If you have the motivation and experience with Typescript/Javascript (or are willing to learn) please feel free to fork the repository and make pull requests with contributions. If you don't know what to work on but want to help, reference the below **To-Do** section or the **#feature-vote** channel in the discord.
### 💻 Environment Setup
#### Prerequisites
- node: 22.14.0
- npm: [how to install](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
#### Running Locally
1. Clone the repo and in the root directory run `npm install`
- *if you run into any errors, reach out in the **#dev-corner** channel in discord*
2. Run `npm run start:dev` to locally run the project in `localhost:8000`
#### Linting
We're using Biome as our common linter and formatter. It will run automatically during the pre-commit hook but if you would like to manually run it, use the `npm run biome` script. To view the complete rules, check out the [biome.jsonc](./biome.jsonc) file.
### 📚 Documentation
You can find the auto-generated documentation [here](https://pagefaultgames.github.io/pokerogue/main/index.html).
For information on enemy AI, check out the [enemy-ai.md](./docs/enemy-ai.md) file.
For detailed guidelines on documenting your code, refer to the [comments.md](./docs/comments.md) file.
### ❔ FAQ
**How do I test a new _______?**
- In the `src/overrides.ts` file there are overrides for most values you'll need to change for testing
**How do I retrieve the translations?**
- The translations were moved to the [dedicated translation repository](https://github.com/pagefaultgames/pokerogue-locales) and are now applied as a submodule in this project.
- The command to retrieve the translations is `git submodule update --init --recursive`. If you still struggle to get it working, please reach out to #dev-corner channel in Discord.
## 🪧 To Do
Check out [Github Issues](https://github.com/pagefaultgames/pokerogue/issues) to see how can you help us!
See [CONTRIBUTING.md](./CONTRIBUTING.md), this includes instructions on how to set up the game locally.
# 📝 Credits
>
> If this project contains assets you have produced and you do not see your name, **please** reach out, either [here on GitHub](https://github.com/pagefaultgames/pokerogue/issues/new) or via [Discord](https://discord.gg/pokerogue).
Thank you to all the wonderful people that have contributed to the PokéRogue project! You can find the credits [here](./CREDITS.md).

View File

@ -1,7 +1,7 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"$schema": "https://biomejs.dev/schemas/2.2.4/schema.json",
"vcs": {
"enabled": false,
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true,
"defaultBranch": "beta"
@ -10,99 +10,267 @@
"enabled": true,
"useEditorconfig": true,
"indentStyle": "space",
"ignore": ["src/enums/*", "src/data/balance/*"],
"includes": ["**", "!**/src/data/balance/**"],
"lineWidth": 120
},
"files": {
"ignoreUnknown": true,
// Adding folders to the ignore list is GREAT for performance because it prevents biome from descending into them
// and having to verify whether each individual file is ignored
"ignore": [
"**/*.d.ts",
"dist/*",
"build/*",
"coverage/*",
"public/*",
".github/*",
"node_modules/*",
".vscode/*",
"*.css", // TODO?
"*.html", // TODO?
"src/overrides.ts",
// TODO: these files are too big and complex, ignore them until their respective refactors
"src/data/moves/move.ts",
"src/data/abilities/ability.ts",
"src/field/pokemon.ts",
// this file is just too big:
"src/data/balance/tms.ts"
"includes": [
"**",
"!**/dist",
"!**/coverage",
"!**/public",
"!**/.github",
"!**/node_modules",
"!**/typedoc",
// TODO: lint css and html?
"!**/*.css",
"!**/*.html",
// TODO: enable linting this file
"!**/src/data/moves/move.ts",
// this file is too big
"!**/src/data/balance/tms.ts"
]
},
"organizeImports": { "enabled": false },
"assist": {
"actions": {
"source": {
"organizeImports": {
"level": "on",
"options": {
"groups": [":ALIAS:", ":NODE:", ":PACKAGE_WITH_PROTOCOL:", ":PACKAGE:", ":PATH:"]
}
}
}
}
},
// TODO: Remove unneeded `options` blocks once biome's JSON schema is fixed to not require them
"linter": {
"ignore": [
"src/phases/move-effect-phase.ts" // TODO: unignore after move-effect-phase refactor
],
"enabled": true,
"rules": {
"recommended": true,
"correctness": {
"noUndeclaredVariables": "off",
"noUndeclaredVariables": "error",
"noUnusedVariables": "error",
"noSwitchDeclarations": "warn", // TODO: refactor and make this an error
"noVoidTypeReturn": "warn", // TODO: Refactor and make this an error
"noUnusedImports": "error"
"noSwitchDeclarations": "error",
"noVoidTypeReturn": "error",
"noUnusedImports": {
"level": "error",
"fix": "safe",
"options": {}
},
"noUnusedFunctionParameters": "error",
"noUnusedLabels": "error",
"noPrivateImports": "error",
"useSingleJsDocAsterisk": "error",
"useJsonImportAttributes": "off" // "Import attributes are only supported when the '--module' option is set to 'esnext', 'node18', 'nodenext', or 'preserve'. ts(2823)"
},
"style": {
"noVar": "error",
"useEnumInitializers": "off",
"useBlockStatements": "error",
"useExplicitLengthCheck": {
"level": "error",
"fix": "safe",
"options": {}
},
"useAtIndex": "error",
"noNegationElse": {
"level": "info", // TODO: Promote to error eventually
"fix": "unsafe", // duplicates else blocks
"options": {}
},
// TODO: Fix all instances of this and promote to `error` - this and enums are the 2 things
// barring us from `esModuleInterop`
"noParameterProperties": "warn",
"useConsistentBuiltinInstantiation": {
"level": "error",
"fix": "safe",
"options": {}
},
"noDefaultExport": "warn", // TODO: Fix `overrides.ts` and enable
"noShoutyConstants": "error",
"useThrowNewError": {
"level": "error",
"fix": "safe",
"options": {}
},
"useThrowOnlyError": "error",
"useTrimStartEnd": "error",
"useReadonlyClassProperties": {
"level": "info", // TODO: Graduate to error eventually
// NOTE: "checkAllProperties" has an immature implementation that
// causes many false positives across files. Enable if/when maturity improves
"options": { "checkAllProperties": false }
},
"useConsistentObjectDefinitions": {
"level": "error",
"options": { "syntax": "shorthand" }
},
"useCollapsedIf": "error",
"useCollapsedElseIf": "error",
"noSubstr": "error",
"noYodaExpression": "error",
"useForOf": "error",
"useEnumInitializers": "off", // large enums like MoveId/SpeciesId would make this cumbersome
"useBlockStatements": {
"level": "error",
"fix": "safe",
"options": {}
},
"useConst": "error",
"useImportType": "error",
"noNonNullAssertion": "off", // TODO: Turn this on ASAP and fix all non-null assertions
"noNonNullAssertion": "off", // TODO: Turn this on ASAP and fix all non-null assertions in non-test files
"noParameterAssign": "off",
"useExponentiationOperator": "off",
"useDefaultParameterLast": "off", // TODO: Fix spots in the codebase where this flag would be triggered, and then enable
"useSingleVarDeclarator": "off",
"useExponentiationOperator": "off", // Too typo-prone and easy to mixup with standard multiplication (* vs **)
"useDefaultParameterLast": {
// TODO: Fix spots in the codebase where this flag would be triggered
// and then set to "error" and re-enable the fixer
"level": "warn",
"fix": "none",
"options": {}
},
"useSingleVarDeclarator": {
"level": "error",
"fix": "safe",
"options": {}
},
"useNodejsImportProtocol": "off",
"useTemplate": "off", // string concatenation is faster: https://stackoverflow.com/questions/29055518/are-es6-template-literals-faster-than-string-concatenation
"noNamespaceImport": "error"
"useAsConstAssertion": "error",
"noUnusedTemplateLiteral": "error",
"useNumberNamespace": "error",
"noInferrableTypes": "error",
"noUselessElse": "error",
"noRestrictedTypes": {
"level": "error",
"options": {
"types": {
"integer": {
"message": "This is an alias for 'number' that can provide false impressions of what values can actually be contained in this variable. Use 'number' instead.",
"use": "number"
}
}
}
},
// TODO: Wait until the rule gets options for ignoring doc comments and/or different parameter names,
// and THEN enable it codebase-wide
"useUnifiedTypeSignatures": {
"level": "info",
"fix": "none",
"options": {}
},
"useGroupedAccessorPairs": "error",
"useObjectSpread": "error",
"useNumericSeparators": "off" // TODO: Consider enabling?
},
"suspicious": {
"useErrorMessage": "error",
"noEvolvingTypes": "warn", // TODO: Review and enable ASAP - this is VERY VERY BAD
"useNumberToFixedDigitsArgument": "error",
"useGuardForIn": "warn", // TODO: Review and enable ASAP - this is EVEN FRICKING WORSE
"noDoubleEquals": "error",
// While this would be a nice rule to enable, the current structure of the codebase makes this infeasible
// due to being used for move/ability `args` params and save data-related code.
// This can likely be enabled for all non-utils files once these are eventually reworked, but until then we leave it off.
"noExplicitAny": "off",
"noAssignInExpressions": "off",
"noPrototypeBuiltins": "off",
"noFallthroughSwitchClause": "off",
"noImplicitAnyLet": "info", // TODO: Refactor and make this an error
"noRedeclare": "off", // TODO: Refactor and make this an error
"noGlobalIsNan": "off",
"noAsyncPromiseExecutor": "warn" // TODO: Refactor and make this an error
"noPrototypeBuiltins": "off", // TODO: enable this
"noFallthroughSwitchClause": "error", // Prevents accidental automatic fallthroughs in switch cases (use disable comment if needed)
"noImplicitAnyLet": "warn", // TODO: Refactor and make this an error
"noRedeclare": "info", // TODO: Refactor and make this an error
"noGlobalIsNan": "error",
"noAsyncPromiseExecutor": "warn", // TODO: Refactor and make this an error
"noVar": "error",
"noDocumentCookie": "off", // Firefox has minimal support for the "Cookie Store API"
"noConstantBinaryExpressions": "error",
"noTsIgnore": "error",
"useIterableCallbackReturn": "warn" // TODO: Refactor and change to error
},
"complexity": {
"noExcessiveCognitiveComplexity": "warn",
"useLiteralKeys": "off",
"useWhile": "error",
"noVoid": "warn", // TODO: Review and enable ASAP - this is also bad
"noUselessStringConcat": "error",
"noExcessiveCognitiveComplexity": "info", // TODO: Refactor and make this an error
"useLiteralKeys": "off", // TODO: enable?
"noForEach": "off", // Foreach vs for of is not that simple.
"noUselessSwitchCase": "off", // Explicit > Implicit
"noUselessConstructor": "warn", // TODO: Refactor and make this an error
"noBannedTypes": "warn" // TODO: Refactor and make this an error
"noUselessConstructor": "error",
"noBannedTypes": "warn", // TODO: Refactor and make this an error
"noThisInStatic": "error",
"noUselessThisAlias": "error",
"noUselessTernary": "error",
"useIndexOf": "error"
},
"performance": {
"noNamespaceImport": "error",
"noDelete": "error",
"noBarrelFile": "error"
},
"nursery": {
"noUselessUndefined": "error",
"useMaxParams": {
"level": "info", // TODO: Change to "error"... eventually...
"options": { "max": 7 }
},
"noShadow": "warn", // TODO: refactor and make "error"
"noNonNullAssertedOptionalChain": "warn", // TODO: refactor and make "error"
"noDuplicateDependencies": "error",
"noImportCycles": "error",
// TODO: Change to error once promises are used properly
"noMisusedPromises": "info"
}
}
},
"javascript": {
"formatter": { "quoteStyle": "double", "arrowParentheses": "asNeeded" }
"formatter": {
"quoteStyle": "double",
"arrowParentheses": "asNeeded",
"operatorLinebreak": "before"
},
"globals": ["Phaser"],
"parser": {
"jsxEverywhere": false
}
},
"overrides": [
{
"include": ["test/**/*.test.ts"],
"javascript": { "globals": [] },
"includes": ["**/test/**/*.test.ts"],
"linter": {
"rules": {
"performance": {
"noDelete": "off"
"noDelete": "off", // TODO: evaluate if this is necessary for the test(s) to function
"noNamespaceImport": "off" // this is required for `vi.spyOn` to work in some tests
},
"style": {
"noNamespaceImport": "off"
"noNonNullAssertion": "off" // tedious in some tests
},
"nursery": {
"noFloatingPromises": "error"
}
}
}
},
// Overrides to prevent unused import removal inside `overrides.ts`, enums & `.d.ts` files (for TSDoc linkcodes),
// as well as inside script boilerplate files (whose imports will _presumably_ be used in the generated file).
{
"includes": ["**/src/overrides.ts", "**/src/enums/**/*", "**/*.d.ts", "scripts/**/*.boilerplate.ts"],
"linter": {
"rules": {
"correctness": {
"noUnusedImports": "off"
}
}
}
},
{
"includes": ["**/src/overrides.ts"],
"linter": {
"rules": {
"style": {
"useImportType": "off"
}
}
}

View File

@ -1,172 +0,0 @@
/**
* This script creates a test boilerplate file in the appropriate
* directory based on the type selected.
* @example npm run create-test
*/
import fs from "fs";
import inquirer from "inquirer";
import path from "path";
import { fileURLToPath } from "url";
// Get the directory name of the current module file
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const typeChoices = ["Move", "Ability", "Item", "Mystery Encounter"];
/**
* Prompts the user to select a type via list.
* @returns {Promise<{selectedOption: string}>} the selected type
*/
async function promptTestType() {
const typeAnswer = await inquirer.prompt([
{
type: "list",
name: "selectedOption",
message: "What type of test would you like to create:",
choices: [...typeChoices, "EXIT"],
},
]);
if (typeAnswer.selectedOption === "EXIT") {
console.log("Exiting...");
return process.exit();
}
if (!typeChoices.includes(typeAnswer.selectedOption)) {
console.error(`Please provide a valid type (${typeChoices.join(", ")})!`);
return await promptTestType();
}
return typeAnswer;
}
/**
* Prompts the user to provide a file name.
* @param {string} selectedType
* @returns {Promise<{userInput: string}>} the selected file name
*/
async function promptFileName(selectedType) {
const fileNameAnswer = await inquirer.prompt([
{
type: "input",
name: "userInput",
message: `Please provide the name of the ${selectedType}:`,
},
]);
if (!fileNameAnswer.userInput || fileNameAnswer.userInput.trim().length === 0) {
console.error("Please provide a valid file name!");
return await promptFileName(selectedType);
}
return fileNameAnswer;
}
/**
* Runs the interactive create-test "CLI"
* @returns {Promise<void>}
*/
async function runInteractive() {
const typeAnswer = await promptTestType();
const fileNameAnswer = await promptFileName(typeAnswer.selectedOption);
const type = typeAnswer.selectedOption.toLowerCase();
// Convert fileName from kebab-case or camelCase to snake_case
const fileName = fileNameAnswer.userInput
.replace(/-+/g, "_") // Convert kebab-case (dashes) to underscores
.replace(/([a-z])([A-Z])/g, "$1_$2") // Convert camelCase to snake_case
.replace(/\s+/g, "_") // Replace spaces with underscores
.toLowerCase(); // Ensure all lowercase
// Format the description for the test case
const formattedName = fileName.replace(/_/g, " ").replace(/\b\w/g, char => char.toUpperCase());
// Determine the directory based on the type
let dir;
let description;
switch (type) {
case "move":
dir = path.join(__dirname, "test", "moves");
description = `Moves - ${formattedName}`;
break;
case "ability":
dir = path.join(__dirname, "test", "abilities");
description = `Abilities - ${formattedName}`;
break;
case "item":
dir = path.join(__dirname, "test", "items");
description = `Items - ${formattedName}`;
break;
case "mystery encounter":
dir = path.join(__dirname, "test", "mystery-encounter", "encounters");
description = `Mystery Encounter - ${formattedName}`;
break;
default:
console.error(`Invalid type. Please use one of the following: ${typeChoices.join(", ")}.`);
process.exit(1);
}
// Define the content template
const content = `import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import GameManager from "#test/testUtils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
describe("${description}", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.moveset([ Moves.SPLASH ])
.ability(Abilities.BALL_FETCH)
.battleType("single")
.disableCrits()
.enemySpecies(Species.MAGIKARP)
.enemyAbility(Abilities.BALL_FETCH)
.enemyMoveset(Moves.SPLASH);
});
it("should do X", async () => {
await game.classicMode.startBattle([ Species.FEEBAS ]);
game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to("BerryPhase");
expect(true).toBe(true);
});
});
`;
// Ensure the directory exists
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
// Create the file with the given name
const filePath = path.join(dir, `${fileName}.test.ts`);
if (fs.existsSync(filePath)) {
console.error(`File "${fileName}.test.ts" already exists.`);
process.exit(1);
}
// Write the template content to the file
fs.writeFileSync(filePath, content, "utf8");
console.log(`File created at: ${filePath}`);
}
runInteractive();

View File

@ -1,13 +0,0 @@
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);

View File

@ -1,64 +1,107 @@
## How do I comment my code?
# Commenting code
### While we're not enforcing a strict standard, there are some things to keep in mind:
People spend more time reading code than writing it (sometimes substantially more so). As such, comments and documentation are **vital** for any large codebase like this.
## General Guidelines
While we're not enforcing a strict standard, here are some things to keep in mind:
- Make comments meaningful
- Comments should be explaining why a line or block of code exists and what the reason behind it is
- Comments should not be repeating chunks of code or explaining what 'true' and 'false' means in typescript
- Comments should **NOT** repeat _what_ code _does_[^1] or explain concepts obvious to someone with a basic understanding of the language at hand. Instead, focus on explaining _why_ a line or block of code exists.
- Anyone with basic reading comprehension and a good IDE can figure out what code does; gaining a _post hoc_ understanding of the _reasons_ behind its existence takes a lot more digging, effort and bloodshed.
- Keep comments readable
- A comment's verbosity should roughly scale with the complexity of its subject matter. Some people naturally write shorter or longer comments as a personal style, but summarizing a 300 line function with "does a thing" is about as good as writing nothing. Conversely, writing a paragraph-level response where a basic one-liner would suffice is no less undesirable.
- Long comments should ideally be broken into multiple lines at around the 100-120 character mark. This isn't _mandatory_, but avoids unnecessary scrolling in terminals and IDEs.
- Make sure comments exist on Functions, Classes, Methods, and Properties
- This may be the most important things to comment. When someone goes to use a function/class/method/etc., having a comment reduces the need to flip back and forth between files to figure out how something works. Peek Definition is great until you're three nested functions deep.
- The best example of this is JSDoc-style comments as seen below:
- When formatted this way, the comment gets shown by intellisense in VS Code or similar IDEs just by hovering over the text!
- Functions also show each the comment for parameter as you type them, making keeping track of what each one does in lengthy functions much more clear
```js
/**
* Changes the type-based weather modifier if this move's power would be reduced by it
* @param user {@linkcode Pokemon} using this move
* @param target {@linkcode Pokemon} target of this move
* @param move {@linkcode Move} being used
* @param args [0] {@linkcode Utils.NumberHolder} for arenaAttackTypeMultiplier
* @returns true if the function succeeds
*/
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
}
- These may be the most important things to comment. When someone goes to use a function/class/method/etc., having a comment reduces the need to flip back and forth between files to figure out what XYZ does. Peek Definition is great until you're three nested levels deep.
/** Set to true when experimental animated sprites from Gen6+ are used */
public experimentalSprites: boolean = false;
[^1]: With exceptions for extremely long, convoluted or unintuitive methods (though an over-dependency on said comments is likely a symptom of poorly structured code).
# TSDoc
The codebase makes extensive use of [TSDoc](https://tsdoc.org), which is a TypeScript-specific version of [JSDoc](https://jsdoc.app/about-getting-started)
that uses similar syntax and attaches to functions, classes, etc.
When formatted correctly, these comments are shown within VS Code or similar IDEs just by hovering over the function or object.
- Functions also show the comment for each parameter as you type them, making keeping track of arguments inside lengthy functions much more clear.
They can also be used to generate a commentated overview of the codebase. There is a GitHub action that automatically updates [this docs site](https://pagefaultgames.github.io/pokerogue/main/index.html)
and you can generate it locally as well via `pnpm run docs` which will generate into the `typedoc/` directory.
## Syntax
For an example of how TSDoc comments work, here are some TSDoc comments taken from `src/data/moves/move.ts`:
```ts
/**
* Cures the user's party of non-volatile status conditions, ie. Heal Bell, Aromatherapy
* @extends MoveAttr
* @see {@linkcode apply}
* Attribute to put in a {@link https://bulbapedia.bulbagarden.net/wiki/Substitute_(doll) | Substitute Doll} for the user.
*/
export class DontHealThePartyPlsAttr extends MoveAttr {
export class AddSubstituteAttr extends MoveEffectAttr {
/** The ratio of the user's max HP that is required to apply this effect */
private hpCost: number;
/** Whether the damage taken should be rounded up (Shed Tail rounds up) */
private roundUp: boolean;
constructor(hpCost: number, roundUp: boolean) {
// code removed
}
/**
* Removes 1/4 of the user's maximum HP (rounded down) to create a substitute for the user
* @param user - The {@linkcode Pokemon} that used the move.
* @param target - n/a
* @param move - The {@linkcode Move} with this attribute.
* @param args - n/a
* @returns `true` if the attribute successfully applies, `false` otherwise
*/
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
// code removed
}
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
// code removed
}
getCondition(): MoveConditionFunc {
// code removed
}
/**
* Get the substitute-specific failure message if one should be displayed.
* @param user - The pokemon using the move.
* @returns The substitute-specific failure message if the conditions apply, otherwise `undefined`
*/
getFailedText(user: Pokemon, _target: Pokemon, _move: Move): string | undefined {
// code removed
}
}
```
You'll notice this contains an `{@linkcode Object}` tag for each parameter. This provides an easy type denomination and hyperlink to that type using VS Code's Intellisense. `@linkcode` is used instead of `@link` so that the text appears in monospace which is more obviously a `type` rather than a random hyperlink.
If you're interested in going more in depth, you can find a reference guide for how comments like these work [here](https://jsdoc.app)
Looking at the example given, you'll notice this contains an `{@linkcode XYZ}` tag in some of the parameters. This provides a clickable hyperlink to that type or object in most modern IDEs. (`@linkcode` is used here instead of `@link` so that the text appears in monospace which is more obviously a `type` rather than a random hyperlink.) \
Also note the dashes (` - `) between the parameter names and descriptions - these are **mandatory** under the TSDoc spec[^2].
If you're interested in going more in depth, you can find a reference guide for how comments like these work [on the TSDoc website](https://tsdoc.org).
The [playground page](https://tsdoc.org/play/) there can also be used for live testing of examples.
[^2]: Incidentally, this is also the only place dashes are explicitly _required_.
### What not to do:
- Don't leave comments for code you don't understand
- Incorrect information is worse than no information. If you aren't sure how something works, don't make something up to explain it. Ask for help instead.
- Incorrect information is worse than no information. If you aren't sure how something works, don't make something up to explain it - ask for help and/or mark it as TODO.
- Don't over-comment
- Not everything needs an explanation. Try to summarize blocks of code instead of singular lines where possible. Single line comments should call out specific oddities.
- Not everything needs a comment. Try to summarize blocks of code instead of singular lines where possible, always preferring giving a reason over stating a fact. Single line comments should call out specific oddities or features.
## How do Abilities and Moves differ from other classes?
While other classes should be fully documented, Abilities and Moves heavily incoperate inheritance (i.e. the `extends` keyword). Because of this, much of the functionality in these classes is duplicated or only slightly changed between classes.
### With this in mind, there's a few more things to keep in mind for these:
- Do not document any parameters if the function mirrors the one they extend.
- Keep this in mind for functions that are not the `apply` function as they are usually sparce and mostly reused
- The class itself must be documented
- This must include the `@extends BaseClass` and `@see {@linkcode apply}` tags
- Keep this in mind for functions that are not the `apply` function as they are usually sparse and mostly reused
- Class member variables must be documented
- You can use a single line documentation comment for these `/** i.e. a comment like this */`
- `args` parameters must be documented if used
- This should look something like this when there are multiple:
- This should look something vaguely like this when there are multiple:
```ts
/**
...
* @param args [0] {@linkcode Utils.NumberHolder} of arenaAttackTypeMultiplier
* [1] {@linkcode Utils.BooleanHolder} of cancelled
* [2] {@linkcode Utils.BooleanHolder} of rWeDoneYet
* @param args -
* `[0]` The {@linkcode Move} being used
* `[1]` A {@linkcode BooleanHolder} used to track XYZ
* `[2]` {@linkcode BooleanHolder} `paramC` - paramC description here
...
*/
```
```

View File

@ -37,12 +37,12 @@ The `EnemyCommandPhase` follows this process to determine whether or not an enem
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.
- 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)
- "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
@ -54,28 +54,35 @@ At this point, the enemy (a wild or trainer Pokémon) has decided against switch
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.
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 "&nbsp;(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.
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 until after 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).
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)**.
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 is a combination of the move's **user benefit score (UBS)** and **target benefit score (TBS)**, representing how much the move helps or hinders the user and/or its target(s).
![equation](https://latex.codecogs.com/png.image?%5Cinline%20%5Cdpi%7B100%7D%5Cbg%7Bwhite%7D%5Ctext%7BTS%7D=%5Ctext%7BUBS%7D&plus;%5Ctext%7BTBS%7D%5Ctimes%5Cleft%5C%7B%5Cbegin%7Bmatrix%7D-1&%5Ctext%7Bif%20target%20is%20an%20opponent%7D%5C%5C1&%5Ctext%7Botherwise%7D%5C%5C%5Cend%7Bmatrix%7D%5Cright.)
$$
\text{TS} = \text{UBS} + \left( \text{TBS} \times
\begin{cases}
-1 & \text{if target is an opponent} \\
1 & \text{otherwise}
\end{cases}
\right)
$$
A move's UBS and TBS are computed with the respective functions in the `Move` class:
@ -96,19 +103,38 @@ In addition to the base score from `Move.getTargetBenefitScore()`, attack moves
- 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:
1. Compute a multiplier based on the move's type effectiveness:
![typeMultEqn](https://latex.codecogs.com/png.image?%5Cdpi%7B110%7D%5Cbg%7Bwhite%7D%5Ctext%7BtypeMult%7D=%5Cleft%5C%7B%5Cbegin%7Bmatrix%7D2&&%5Ctext%7Bif%20move%20is%20super%20effective(or%20better)%7D%5C%5C-2&&%5Ctext%7Botherwise%7D%5C%5C%5Cend%7Bmatrix%7D%5Cright.)
$$
\text{typeMult} =
\begin{cases}
2 & \text{if move is super effective (or better)} \\
-2 & \text{otherwise}
\end{cases}
$$
2. Compute a multiplier based on the move's category and the user's offensive stats:
1. Compute the user's offensive stat ratio:
![statRatioEqn](https://latex.codecogs.com/png.image?%5Cinline%20%5Cdpi%7B100%7D%5Cbg%7Bwhite%7D%5Ctext%7BstatRatio%7D=%5Cleft%5C%7B%5Cbegin%7Bmatrix%7D%5Cfrac%7B%5Ctext%7BuserSpAtk%7D%7D%7B%5Ctext%7BuserAtk%7D%7D&%5Ctext%7Bif%20move%20is%20physical%7D%5C%5C%5Cfrac%7B%5Ctext%7BuserAtk%7D%7D%7B%5Ctext%7BuserSpAtk%7D%7D&%5Ctext%7Botherwise%7D%5C%5C%5Cend%7Bmatrix%7D%5Cright.)
2. Compute the stat-based multiplier:
1. Compute the user's offensive stat ratio:
![statMultEqn](https://latex.codecogs.com/png.image?%5Cinline%20%5Cdpi%7B100%7D%5Cbg%7Bwhite%7D%5Ctext%7BstatMult%7D=%5Cleft%5C%7B%5Cbegin%7Bmatrix%7D2&%5Ctext%7Bif%20statRatio%7D%5Cle%200.75%5C%5C1.5&%5Ctext%7Bif%5C;%7D0.75%5Cle%5Ctext%7BstatRatio%7D%5Cle%200.875%5C%5C1&%5Ctext%7Botherwise%7D%5C%5C%5Cend%7Bmatrix%7D%5Cright.)
$$
\text{statRatio} =
\begin{cases}
\frac{\text{userSpAtk}}{\text{userAtk}} & \text{if move is physical} \\
\frac{\text{userAtk}}{\text{userSpAtk}} & \text{otherwise}
\end{cases}
$$
2. Compute the stat-based multiplier:
$$
\text{statMult} =
\begin{cases}
2 & \text{if statRatio} \leq 0.75 \\
1.5 & \text{if } 0.75 \leq \text{statRatio} \leq 0.875 \\
1 & \text{otherwise}
\end{cases}
$$
3. Calculate the move's `attackScore`:
$\text{attackScore} = (\text{typeMult}\times \text{statMult})+\lfloor \frac{\text{power}}{5} \rfloor$
$\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.
@ -125,18 +151,31 @@ The final step to calculate an attack move's target score (TS) is to multiply th
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:
![TSSEqn](https://latex.codecogs.com/png.image?%5Cinline%20%5Cdpi%7B100%7D%5Cbg%7Bwhite%7D%5Ctext%7BTSS%7D=%5Ctext%7BTBS%7D%5Ctimes%5Cleft%5C%7B%5Cbegin%7Bmatrix%7D-1&%5Ctext%7Bif%20target%20is%20an%20opponent%7D%5C%5C1&%5Ctext%7Botherwise%7D%5C%5C%5Cend%7Bmatrix%7D%5Cright.)
$$
\text{TSS} = \text{TBS} \times
\begin{cases}
-1 & \text{if target is an opponent} \\
1 & \text{otherwise}
\end{cases}
$$
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:
![normWeightEqn](https://latex.codecogs.com/png.image?%5Cinline%20%5Cdpi%7B100%7D%5Cbg%7Bwhite%7DW_i=%5Cleft%5C%7B%5Cbegin%7Bmatrix%7Dw_i&plus;%7Cw_n%7C&%5Ctext%7Bif%5C;%7Dw_n%5C;%5Ctext%7Bis%20negative%7D%5C%5Cw_i&%5Ctext%7Botherwise%7D%5C%5C%5Cend%7Bmatrix%7D%5Cright.)
$$
W_i =
\begin{cases}
w_i + |w_n| & \text{if } w_n \text{ is negative} \\
w_i & \text{otherwise}
\end{cases}
$$
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.
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.
@ -145,15 +184,15 @@ Once the target is selected, the enemy has successfully determined its next acti
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
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
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.
@ -172,13 +211,13 @@ Based on the enemy party's matchup scores, whether or not the trainer switches o
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:
@ -198,7 +237,7 @@ Now that the enemy Pokémon with the best matchup score is on the field (assumin
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 `StatStageChangeAttr` 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(StatStageChangeAttr)}-\text{attackScore}$
$\text{TBS}=(-4 + 2)-(-2\times 2 + \lfloor \frac{75}{5} \rfloor)=-2-11=-13$

View File

@ -1,40 +1,57 @@
# ESLint
## Key Features
# Linting & Formatting
1. **Automation**:
- A pre-commit hook has been added to automatically run ESLint on the added or modified files, ensuring code quality before commits.
Writing clean, readable code is important, and linters and formatters are an integral part of ensuring code quality and readability. \
It is for this reason we are using [Biome](https://biomejs.dev), an opinionated linter/formatter (akin to Prettier) with a heavy focus on speed and performance.
2. **Manual Usage**:
- If you prefer not to use the pre-commit hook, you can manually run ESLint to automatically fix issues using the command:
```sh
npx eslint --fix . or npm run eslint
```
- Running this command will lint all files in the repository.
### Installation
You probably installed Biome already without noticing it - it's included inside `package.json` and should've been downloaded when you ran `pnpm install` after cloning the repo. If you haven't done that yet, go do that first.
3. **GitHub Action**:
- A GitHub Action has been added to automatically run ESLint on every push and pull request, ensuring code quality in the CI/CD pipeline.
# Using Biome
## Summary of ESLint Rules
For the most part, Biome attempts to stay "out of your hair", letting you write code while enforcing a consistent formatting standard and only notifying for errors it can't automatically fix. \
On the other hand, if Biome complains about a piece of code, **there's probably a good reason why**. Disable comments should be used sparingly or when readabilty demands it - your first instinct should be to fix the code in question, not disable the rule.
1. **General Rules**:
- **Equality**: Use `===` and `!==` instead of `==` and `!=` (`eqeqeq`).
- **Indentation**: Enforce 2-space indentation (`indent`).
- **Quotes**: Use doublequotes for strings (`quotes`).
- **Variable Declarations**:
- Disallow `var`; use `let` or `const` (`no-var`).
- Prefer `const` for variables that are never reassigned (`prefer-const`).
- **Unused Variables**: Allow unused function parameters but enforce error for other unused variables (`@typescript-eslint/no-unused-vars`).
- **End of Line**: Ensure at least one newline at the end of files (`eol-last`).
- **Curly Braces**: Enforce the use of curly braces for all control statements (`curly`).
- **Brace Style**: Use one true brace style (`1tbs`) for TypeScript-specific syntax (`@typescript-eslint/brace-style`).
## Editor Integration
Biome has integration with many popular code editors. See [these](https://biomejs.dev/guides/editors/first-party-extensions/) [pages](https://biomejs.dev/guides/editors/third-party-extensions/) for information about enabling Biome in your editor of choice.
2. **TypeScript-Specific Rules**:
- **Semicolons**:
- Enforce semicolons for TypeScript-specific syntax (`@typescript-eslint/semi`).
- Disallow unnecessary semicolons (`@typescript-eslint/no-extra-semi`).
## Automated Runs
Generally speaking, most users shouldn't need to run Biome directly; in addition to editor integration, a [pre-commit hook](../lefthook.yml) will automatically format and lint all staged files before each commit.
## Benefits
> ![WARNING]
> You will **not** be able to commit code if any staged files contain `error`-level linting problems. \
> If you, for whatever reason, _absolutely need_ to bypass Lefthook for a given commit,
> pass the `--no-verify` flag to `git commit`.
- **Consistency**: Ensures consistent coding style across the project.
- **Code Quality**: Helps catch potential errors and improve overall code quality.
- **Readability**: Makes the codebase easier to read and maintain.
We also have a [Github Action](../.github/workflows/linting.yml) to verify code quality each time a PR is updated, preventing bad code from inadvertently making its way upstream. \
These are effectively the same commands as run by Lefthook, merely on a project-wide scale.
## Running Biome via CLI
To run you Biome on your files manually, you have 2 main options:
1. Run the scripts included in `package.json` (`pnpm biome` and `pnpm biome:all`). \
These have sensible defaults for command-line options, but do not allow altering certain flags (as some cannot be specified twice in the same command)
2. Execute the Biome executable manually from the command line like so:
```sh
pnpm exec biome check --[flags]
```
This allows customizing non-overridable flags like `--diagnostic-level` on a more granular level, but requires slightly more verbosity and specifying more options.
A full list of flags and options can be found on [their website](https://biomejs.dev/reference/cli/), but here's a few useful ones to keep in mind:
- `--write` will cause Biome to write all "safe" fixes and formatting changes directly to your files (rather than just complaining and erroring out).
- `--changed` and `--staged` will limit checking to all changed or staged files respectively. Biome sources this info from the relevant version control system (in this case `git`).
- `diagnostic-level=XXX` will only show diagnostics with at least the given severity level (`info/warn/error`). Useful to only focus on errors causing a failed workflow run or similar.
## Linting Rules
We primarily use Biome's [recommended ruleset](https://biomejs.dev/linter/rules/) for linting JS/TS files, with some customizations to better suit our project's needs[^1].
Some things to consider:
- We have disabled rules that prioritize style over performance, such as `useTemplate`.
- Some rules are currently marked as warnings (`warn`) to allow for gradual refactoring without blocking development. **Do not write new code that triggers these rules!**
- The linter is configured to ignore specific files and folders (such as excessively large files or ones in need of refactoring) to improve performance and focus on actionable areas.
Any questions about linting rules can be brought up in the `#dev-corner` channel in the community Discord.
[^1]: A complete list of rules can be found in the [`biome.jsonc`](../biome.jsonc) file in the project root. Many rules are accompanied by comments explaining the reasons for their inclusion (or lack thereof).

147
docs/localization.md Normal file
View File

@ -0,0 +1,147 @@
# Localization 101
PokéRogue's localization team puts immense effort into making the game accessible around the world, supporting over 12 different languages at the time of writing this document. \
As a developer, it's important to help maintain global accessibility by effectively coordinating with the Translation Team on any new features or enhancements.
This document aims to cover everything you need to know to help keep the integration process for localization smooth and simple.
# Prerequisites
Before you continue, this document assumes:
- You have already forked the repository and set up a development environment according to [CONTRIBUTING.md](../CONTRIBUTING.md).
- You have a basic level of familiarity with Git commands and GitHub repositories.
- You have joined the [community Discord](https://discord.gg/pokerogue) and have access to `#dev-corner` and related channels via **[#select-roles](https://discord.com/channels/1125469663833370665/1194825607738052621)**.
This is the easiest way to keep in touch with both the Translation Team and other like-minded contributors!
# About the `pokerogue-locales` submodule
PokéRogue's translations are managed under a separate dedicated repository, [`pokerogue-locales`](https://github.com/pagefaultgames/pokerogue-locales/).
This repository is integrated into the main one as a [git submodule](https://git-scm.com/book/en/v2/Git-Tools-Submodules) within the `public/locales` folder.
## What Is a Submodule?
In essence, a submodule is a way for one repository (i.e. `pokerogue`) to use another repository (i.e. `pokerogue-locales`) internally.
The parent repo (the "superproject") houses a cloned version of the 2nd repository (the "submodule") inside it, making locales effectively a "repository within a repository", so to speak.
>[!TIP]
> Many popular IDEs have integrated `git` support with special handling around submodules:
>
> ![Image showing Visual Studio Code's `git` integration in the File Explorer. A blue "S" in the top right hand corner indicates the `public/locales` folder is a submodule.](https://github.com/user-attachments/assets/bd42d354-c65b-4cbe-8873-23d760dc1714 "What the `public/locales` submodule looks like in VS Code's File Explorer")
>
> ![Image showing Visual Studio Code's Source Control tab. A separate dropdown can be seen for each individual submodule.](https://github.com/user-attachments/assets/8b4d3f64-aec1-4474-91df-03dc1252a2fa "Making commits on submodules without even changing directories!")
## Fetching Changes from Submodules
The following command will initialize your branch's locales repository and update its HEAD:
```bash
pnpm update-locales
```
> [!TIP]
> This command is run _automatically_ after cloning, merging or changing branches, so you should rarely have to run it manually.
> [!IMPORTANT]
> If you EVER run into issues with the `locales` submodule, try deleting the `.git/modules/public` and `public/locales` folders before re-initializing it again.
## How Are Translations Integrated?
This project uses the [i18next library](https://www.i18next.com/) to integrate translations from `public/locales` into the source code.
The basic process for fetching translated text goes roughly as follows:
1. The source code fetches text by a given key.
```ts
globalScene.phaseManager.queueMessage(
i18next.t("fileName:keyName", { arg1: "Hello", arg2: "an example", ... })
);
```
2. The game looks up the key in the corresponding JSON file for the user's language.
```jsonc
// from "en/file-name.json"...
{
"keyName": "{{arg1}}! This is {{arg2}} of translated text!"
}
```
If the key doesn't exist for the given language, the game will default to an appropriate fallback (usually the corresponding English key).
3. The game shows the translated text to the user.
```ts
"Hello! This is an example of translated text!"
```
# Submitting Locales Changes
If you have a feature or enhancement that requires additions or changes to in-game text, you will need to make a fork of the `pokerogue-locales` repo and submit your text changes as a pull request _in addition_ to your pull request to the main project. \
Since these two PRs aren't _technically_ linked, it's important to coordinate with the Translation Team to ensure that both PRs are integrated safely into the project.
> [!CAUTION]
> **DO NOT HARDCODE PLAYER-FACING TEXT INTO THE CODE!**
## Making Changes
One perk of submodules is you don't actually _need_ to clone the locales repository to start contributing - `git` already does that for you on initialization.
Given `pokerogue-locales` is a full-fledged `git` repository _inside_ `pokerogue`, making changes is roughly the same as normal, merely using `public/locales` as your root directory.
> [!WARNING]
> Make sure to checkout or rebase onto `upstream/main` (`pnpm update-locales:remote`) **BEFORE** creating a locales PR!
> The checked-out commit is based on the superproject's SHA-1 by default, so hastily making changes may see you basing your commits on last week's `HEAD`.
## Requirements for Adding Translated Text
When a new feature or enhancement requires adding a new locales key **without changing text in existing keys**, we have the following workflow with regards to localization:
1. You (the developer) make a pull request to the main repository for your new feature.
If this feature requires new text, the text should be integrated into the code with a new `i18next` key pointing to where you plan to add it into the locales repository.
2. You then make another pull request — this time to the `pokerogue-locales` repository — adding a new entry with text for each key you added to your main PR.
- You must add the corresponding **English keys** while making the PR; the Translation Team can take care of the rest[^2].
- For any feature pulled from the mainline Pokémon games (e.g. a Move or Ability implementation), it's best practice to include a source link for any added text. \
[Poké Corpus](https://abcboy101.github.io/poke-corpus/) is a great resource for finding text from the mainline games; otherwise, a video/picture showing the text being displayed should suffice.
- You should also [notify the current Head of Translation](#notifying-translation) to ensure a fast response.
3. Your locales should use the following format:
- File names should be in `kebab-case`. Example: `trainer-names.json`
- Key names should be in `camelCase`. Example: `aceTrainer`
- If you make use of i18next's inbuilt [context support](https://www.i18next.com/translation-function/context), you need to use `snake_case` for the context key. Example: `aceTrainer_male`
4. At this point, you may begin [testing locales integration in your main PR](#documenting-locales-changes).
5. The Translation Team will approve the locales PR (after corrections, if necessary), then merge it into `pokerogue-locales`.
6. The Dev Team will approve your main PR for your feature, then merge it into PokéRogue's beta environment.
[^2]: For those wondering, the reason for choosing English specifically is due to it being the master language set in Pontoon (the program used by the Translation Team to perform locale updates).
If a key is present in any language _except_ the master language, it won't appear anywhere else in the translation tool, rendering missing English keys quite a hassle.
### Requirements for Modifying Translated Text
PRs that modify existing text have different risks with respect to coordination between development and translation, so their requirements are slightly different:
- As above, you set up 2 PRs: one for the feature itself in the main repo, and another for the associated locales changes in the locale repo.
- Now, however, you need to have your main PR be approved by the Dev Team **before** your corresponding locale changes are merged in.
- After your main PR is approved, you may update the submodule and post video evidence of integration into the **locales PR**.
- A Lead or Senior Translator from the Translation Team will then approve your main PR (if all is well), clearing your feature for merging into `beta`.
## Documenting Locales Changes
After making a PR involving any outwards-facing behavior (but _especially_ locales-related ones), it's generally considered good practice to attach proof of those changes working in-game.
The basic procedure is roughly as follows:
1. Update your locales submodule to point to **the branch you used to make the locales PR**. \
Many IDEs with `git` integration support doing this from the GUI, \
or you can simply do it via command-line:
```bash
cd public/locales
git checkout your-branch-name-here
```
2. Set some of the [in-game overrides](../CONTRIBUTING.md#1---manual-testing) inside `overrides.ts` to values corresponding to the interactions being tested.
3. Start a local dev server (`pnpm start:dev`) and open localhost in your browser.
4. Take screenshots or record a video of the locales changes being displayed in-game using the software of your choice[^2].
[^2]: For those lacking a screen capture device, [OBS Studio](https://obsproject.com) is a popular open-source option.
> [!NOTE]
> For those aiming to film their changes, bear in mind that GitHub has a hard **10mB limit** on uploaded media content.
> If your video is too large, consider making it shorter or downscaling the quality.
## Notifying Translation
Put simply, stating that a PR exists makes it much easier to review and merge.
The easiest way to do this is by **pinging the current Head of Translation** in the [community Discord](https://discord.gg/pokerogue) (ideally in `#dev-corner` or similar).
<!-- Remember to update this everytime the head of translation changes! -->
> [!IMPORTANT]
> The current Head of Translation is: \
> ** @lugiadrien **
# Closing Remarks
If you have any questions about the developer process for localization, don't hesitate to ask!
Feel free to contact us on Discord - the Dev Team and Translation Team will be happy to answer any questions.

27
docs/podman.md Normal file
View File

@ -0,0 +1,27 @@
# Using Podman
## Requirements
* `podman >=5.x`
## Steps
1. `podman build -t pokerogue -f Dockerfile .`
2. `podman create --name temp-pokerogue localhost/pokerogue`
3. `podman cp temp-pokerogue:/app/node_modules ./`
4. `podman cp temp-pokerogue:/app/public/locales ./public/`
5. `podman rm temp-pokerogue`
6. `podman run --rm -p 8000:8000 -v $(pwd):/app:Z --userns=keep-id -u $(id -u):$(id -g) localhost/pokerogue`
7. Visit `http://localhost:8000/`
Note:
1. Steps 2,3,4 are required because mounting working directory without installed `node_modules/` and assets locally will be empty,
this way we prevent it by copying them from the container itself to local directory
2. `podman run` may take a couple of minutes to mount the working directory
### Running tests inside container
`podman run --rm -p 8000:8000 -v $(pwd):/app:Z --userns=keep-id -u $(id -u):$(id -g) localhost/pokerogue pnpm test:silent
`

View File

@ -1,43 +0,0 @@
import tseslint from "@typescript-eslint/eslint-plugin";
import stylisticTs from "@stylistic/eslint-plugin-ts";
import parser from "@typescript-eslint/parser";
import importX from "eslint-plugin-import-x";
export default [
{
name: "eslint-config",
files: ["src/**/*.{ts,tsx,js,jsx}", "test/**/*.{ts,tsx,js,jsx}"],
ignores: ["dist/*", "build/*", "coverage/*", "public/*", ".github/*", "node_modules/*", ".vscode/*"],
languageOptions: {
parser: parser,
},
plugins: {
"import-x": importX,
"@stylistic/ts": stylisticTs,
"@typescript-eslint": tseslint,
},
rules: {
"prefer-const": "error", // Enforces the use of `const` for variables that are never reassigned
"no-undef": "off", // Disables the rule that disallows the use of undeclared variables (TypeScript handles this)
"no-extra-semi": ["error"], // Disallows unnecessary semicolons for TypeScript-specific syntax
"import-x/extensions": ["error", "never", { json: "always" }], // Enforces no extension for imports unless json
},
},
{
name: "eslint-tests",
files: ["test/**/**.test.ts"],
languageOptions: {
parser: parser,
parserOptions: {
project: ["./tsconfig.json"],
},
},
plugins: {
"@typescript-eslint": tseslint,
},
rules: {
"@typescript-eslint/no-floating-promises": "error", // Require Promise-like statements to be handled appropriately. - https://typescript-eslint.io/rules/no-floating-promises/
"@typescript-eslint/no-misused-promises": "error", // Disallow Promises in places not designed to handle them. - https://typescript-eslint.io/rules/no-misused-promises/
},
},
];

25
global.d.ts vendored
View File

@ -1,14 +1,31 @@
import type { AnyFn } from "#types/type-helpers";
import type { SetupServerApi } from "msw/node";
export {};
declare global {
/**
* Only used in testing.
* Can technically be undefined/null but for ease of use we are going to assume it is always defined.
* Used to load i18n files exclusively.
*
* To set up your own server in a test see `game_data.test.ts`
*
* To set up your own server in a test see `game-data.test.ts`
*/
var server: SetupServerApi;
// Overloads for `Function.apply` and `Function.call` to add type safety on matching argument types
interface Function {
apply<T extends AnyFn>(this: T, thisArg: ThisParameterType<T>, argArray: Parameters<T>): ReturnType<T>;
call<T extends AnyFn>(this: T, thisArg: ThisParameterType<T>, ...argArray: Parameters<T>): ReturnType<T>;
}
}
// Global augments for `typedoc` to prevent TS from erroring when editing the config JS file
declare module "typedoc" {
export interface TypeDocOptionMap {
coverageLabel: string;
coverageColor: string;
coverageOutputPath: string;
coverageOutputType: "svg" | "json" | "all";
coverageSvgWidth: number;
}
}

View File

@ -145,6 +145,5 @@
</div>
<script type="module" src="./src/main.ts"></script>
<script src="./src/touch-controls.ts" type="module"></script>
<script src="./src/debug.js" type="module"></script>
</body>
</html>

View File

@ -1,15 +1,24 @@
pre-commit:
parallel: true
skip:
- merge
- rebase
commands:
biome-lint:
glob: "*.{js,jsx,ts,tsx}"
run: npx @biomejs/biome check --write --reporter=summary {staged_files} --no-errors-on-unmatched
# Disable colors as certain IDEs don't support it in the output pane.
# Summary mode looks decent in plain ASCII anyhow
run: pnpm exec biome check --write --colors=off --reporter=summary --staged --no-errors-on-unmatched --diagnostic-level=error
stage_fixed: true
skip:
- merge
- rebase
ls-lint:
run: pnpm exec ls-lint
post-merge:
commands:
update-submodules:
run: git submodule update --init --recursive
run: pnpm update-locales
post-checkout:
commands:
update-submodules:
# cf https://git-scm.com/docs/githooks#_post_checkout:
# The 3rd argument is 1 for branch checkouts and 0 for file checkouts.
run: if test {3} -eq "1"; then pnpm update-locales; fi

7993
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,72 +1,78 @@
{
"name": "pokemon-rogue-battle",
"private": true,
"version": "1.8.4",
"version": "1.10.7",
"type": "module",
"scripts": {
"start": "vite",
"start:prod": "vite --mode production",
"start:beta": "vite --mode beta",
"start:dev": "vite --mode development",
"start:podman": "vite --mode development --host 0.0.0.0 --port $PORT",
"build": "vite build",
"build:beta": "vite build --mode beta",
"build:dev": "vite build --mode development",
"preview": "vite preview",
"test": "vitest run",
"test": "vitest run --no-isolate",
"test:cov": "vitest run --coverage --no-isolate",
"test:watch": "vitest watch --coverage --no-isolate",
"test:silent": "vitest run --silent --no-isolate",
"test:silent": "vitest run --silent='passed-only' --no-isolate",
"test:create": "node scripts/create-test/create-test.js",
"eggMoves:parse": "node scripts/parse-egg-moves/main.js",
"scrape-trainers": "node scripts/scrape-trainer-names/main.js",
"typecheck": "tsc --noEmit",
"eslint": "eslint --fix .",
"eslint-ci": "eslint .",
"biome": "biome check --write --changed --no-errors-on-unmatched",
"biome-ci": "biome ci --diagnostic-level=error --reporter=github --changed --no-errors-on-unmatched",
"docs": "typedoc",
"depcruise": "depcruise src",
"depcruise:graph": "depcruise src --output-type dot | node dependency-graph.js > dependency-graph.svg",
"create-test": "node ./create-test-boilerplate.js",
"postinstall": "npx lefthook install && npx lefthook run post-merge",
"update-version:patch": "npm version patch --force --no-git-tag-version",
"update-version:minor": "npm version minor --force --no-git-tag-version",
"typecheck:scripts": "tsc -p scripts/jsconfig.json",
"biome": "biome check --write --changed --no-errors-on-unmatched --diagnostic-level=error",
"biome:all": "biome check --write --no-errors-on-unmatched --diagnostic-level=error",
"biome-ci": "biome ci --diagnostic-level=error --reporter=github --no-errors-on-unmatched",
"typedoc": "typedoc",
"depcruise": "depcruise src test",
"postinstall": "lefthook install; git submodule update --init --recursive",
"update-version:patch": "pnpm version patch --force --no-git-tag-version",
"update-version:minor": "pnpm version minor --force --no-git-tag-version",
"update-locales": "git submodule update --progress --init --recursive",
"update-locales:remote": "git submodule update --progress --init --recursive --force --remote"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@eslint/js": "^9.23.0",
"@hpcc-js/wasm": "^2.22.4",
"@stylistic/eslint-plugin-ts": "^4.1.0",
"@biomejs/biome": "2.2.4",
"@ls-lint/ls-lint": "2.3.1",
"@types/crypto-js": "^4.2.0",
"@types/jsdom": "^21.1.7",
"@types/node": "^22.13.14",
"@typescript-eslint/eslint-plugin": "^8.28.0",
"@typescript-eslint/parser": "^8.28.0",
"@vitest/coverage-istanbul": "^3.0.9",
"dependency-cruiser": "^16.3.10",
"eslint": "^9.23.0",
"eslint-plugin-import-x": "^4.9.4",
"inquirer": "^12.4.2",
"jsdom": "^26.0.0",
"lefthook": "^1.11.5",
"msw": "^2.7.3",
"@types/node": "^22.16.5",
"@vitest/coverage-istanbul": "^3.2.4",
"@vitest/expect": "^3.2.4",
"@vitest/utils": "^3.2.4",
"chalk": "^5.4.1",
"dependency-cruiser": "^16.10.4",
"inquirer": "^12.8.2",
"jsdom": "^26.1.0",
"lefthook": "^1.12.2",
"msw": "^2.10.4",
"phaser3spectorjs": "^0.0.8",
"typedoc": "^0.28.1",
"typescript": "^5.8.2",
"typescript-eslint": "^8.28.0",
"vite": "^6.2.0",
"typedoc": "0.28.7",
"typedoc-github-theme": "^0.3.1",
"typedoc-plugin-coverage": "^4.0.1",
"typedoc-plugin-mdn-links": "^5.0.9",
"typescript": "^5.9.2",
"vite": "^7.0.7",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.0.9",
"vitest": "^3.2.4",
"vitest-canvas-mock": "^0.3.3"
},
"dependencies": {
"@material/material-color-utilities": "^0.2.7",
"compare-versions": "^6.1.1",
"crypto-js": "^4.2.0",
"i18next": "^24.2.2",
"i18next-browser-languagedetector": "^8.0.4",
"i18next": "^24.2.3",
"i18next-browser-languagedetector": "^8.2.0",
"i18next-http-backend": "^3.0.2",
"i18next-korean-postposition-processor": "^1.0.0",
"json-stable-stringify": "^1.2.0",
"json-stable-stringify": "^1.3.0",
"jszip": "^3.10.1",
"phaser": "^3.70.0",
"phaser3-rex-plugins": "^1.80.14"
"phaser": "^3.90.0",
"phaser3-rex-plugins": "^1.80.16"
},
"engines": {
"node": ">=22.0.0"
}
},
"packageManager": "pnpm@10.16.1"
}

3898
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

6
pnpm-workspace.yaml Normal file
View File

@ -0,0 +1,6 @@
onlyBuiltDependencies:
- esbuild
- msw
- lefthook
shellEmulator: true

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -542,6 +542,79 @@
"volume": 100,
"pitch": 55,
"eventType": "AnimTimedSoundEvent"
},
{
"frameIndex": 0,
"resourceName": "PRAS- Sandstorm",
"bgX": -50,
"bgY": 0,
"opacity": 0,
"duration": 5,
"eventType": "AnimTimedAddBgEvent"
},
{
"frameIndex": 0,
"resourceName": "",
"bgX": -50,
"bgY": 0,
"opacity": 96,
"duration": 3,
"eventType": "AnimTimedUpdateBgEvent"
}
],
"3": [
{
"frameIndex": 3,
"resourceName": "",
"bgX": -25,
"bgY": 0,
"opacity": 128,
"duration": 3,
"eventType": "AnimTimedUpdateBgEvent"
}
],
"6": [
{
"frameIndex": 6,
"resourceName": "",
"bgX": 0,
"bgY": 0,
"opacity": 192,
"duration": 3,
"eventType": "AnimTimedUpdateBgEvent"
}
],
"9": [
{
"frameIndex": 9,
"resourceName": "",
"bgX": 25,
"bgY": 0,
"opacity": 128,
"duration": 3,
"eventType": "AnimTimedUpdateBgEvent"
}
],
"12": [
{
"frameIndex": 12,
"resourceName": "",
"bgX": 50,
"bgY": 0,
"opacity": 96,
"duration": 3,
"eventType": "AnimTimedUpdateBgEvent"
}
],
"15": [
{
"frameIndex": 15,
"resourceName": "",
"bgX": 50,
"bgY": 0,
"opacity": 0,
"duration": 3,
"eventType": "AnimTimedUpdateBgEvent"
}
]
},

View File

@ -1102,6 +1102,112 @@
"volume": 100,
"pitch": 136,
"eventType": "AnimTimedSoundEvent"
},
{
"frameIndex": 0,
"resourceName": "PRAS- Strong Winds",
"bgX": 0,
"bgY": 0,
"opacity": 0,
"duration": 4,
"eventType": "AnimTimedAddBgEvent"
},
{
"frameIndex": 0,
"resourceName": "",
"bgX": 0,
"bgY": 0,
"opacity": 32,
"duration": 4,
"eventType": "AnimTimedUpdateBgEvent"
}
],
"4": [
{
"frameIndex": 4,
"resourceName": "",
"bgX": 10,
"bgY": 0,
"opacity": 64,
"duration": 4,
"eventType": "AnimTimedUpdateBgEvent"
}
],
"8": [
{
"frameIndex": 8,
"resourceName": "",
"bgX": 20,
"bgY": 0,
"opacity": 128,
"duration": 4,
"eventType": "AnimTimedUpdateBgEvent"
}
],
"12": [
{
"frameIndex": 12,
"resourceName": "",
"bgX": 30,
"bgY": 0,
"opacity": 128,
"duration": 4,
"eventType": "AnimTimedUpdateBgEvent"
}
],
"16": [
{
"frameIndex": 16,
"resourceName": "",
"bgX": 40,
"bgY": 0,
"opacity": 128,
"duration": 4,
"eventType": "AnimTimedUpdateBgEvent"
}
],
"20": [
{
"frameIndex": 20,
"resourceName": "",
"bgX": 50,
"bgY": 0,
"opacity": 128,
"duration": 4,
"eventType": "AnimTimedUpdateBgEvent"
}
],
"24": [
{
"frameIndex": 24,
"resourceName": "",
"bgX": 60,
"bgY": 0,
"opacity": 64,
"duration": 4,
"eventType": "AnimTimedUpdateBgEvent"
}
],
"28": [
{
"frameIndex": 28,
"resourceName": "",
"bgX": 70,
"bgY": 0,
"opacity": 32,
"duration": 4,
"eventType": "AnimTimedUpdateBgEvent"
}
],
"32": [
{
"frameIndex": 32,
"resourceName": "",
"bgX": 80,
"bgY": 0,
"opacity": 0,
"duration": 3,
"eventType": "AnimTimedUpdateBgEvent"
}
]
},

View File

@ -1,6 +1,6 @@
{
"id": 94,
"graphic": "PRAS- PsychicBG",
"graphic": "PRAS- Psychic BG",
"frames": [
[
{

View File

@ -179,8 +179,6 @@
"483-origin",
"484-origin",
"484-origin",
"487-origin",
"487-origin",
"531-mega",
"531-mega",
"569-gigantamax",
@ -335,8 +333,6 @@
"671-yellow",
"6713",
"6713",
"672",
"672",
"6724",
"6724",
"673",
@ -379,10 +375,6 @@
"690",
"691",
"691",
"692",
"692",
"693",
"693",
"695",
"695",
"696",
@ -505,10 +497,6 @@
"751",
"752",
"752",
"753",
"753",
"754",
"754",
"755",
"755",
"756",
@ -537,10 +525,6 @@
"767",
"768",
"768",
"769",
"769",
"770",
"770",
"771",
"771",
"772",
@ -597,6 +581,20 @@
"774-yellow",
"774",
"774",
"774-blue-meteor",
"774-blue-meteor",
"774-green-meteor",
"774-green-meteor",
"774-indigo-meteor",
"774-indigo-meteor",
"774-orange-meteor",
"774-orange-meteor",
"774-red-meteor",
"774-red-meteor",
"774-violet-meteor",
"774-violet-meteor",
"774-yellow-meteor",
"774-yellow-meteor",
"775",
"775",
"776",
@ -613,12 +611,6 @@
"780",
"781",
"781",
"782",
"782",
"783",
"783",
"784",
"784",
"785",
"785",
"786",
@ -755,10 +747,6 @@
"841",
"842",
"842",
"843",
"843",
"844",
"844",
"845-gorging",
"845-gorging",
"845-gulping",
@ -897,10 +885,6 @@
"900",
"901",
"901",
"902-female",
"902-female",
"902",
"902",
"903",
"903",
"904",
@ -1299,8 +1283,6 @@
"483b-origin",
"484b-origin",
"484b-origin",
"487b-origin",
"487b-origin",
"531b-mega",
"531b-mega",
"569b-gigantamax",
@ -1455,8 +1437,6 @@
"671b-yellow",
"6713b",
"6713b",
"672b",
"672b",
"6724b",
"6724b",
"673b",
@ -1499,10 +1479,6 @@
"690b",
"691b",
"691b",
"692b",
"692b",
"693b",
"693b",
"695b",
"695b",
"696b",
@ -1625,10 +1601,6 @@
"751b",
"752b",
"752b",
"753b",
"753b",
"754b",
"754b",
"755b",
"755b",
"756b",
@ -1657,10 +1629,6 @@
"767b",
"768b",
"768b",
"769b",
"769b",
"770b",
"770b",
"771b",
"771b",
"772b",
@ -1715,6 +1683,20 @@
"774b-violet",
"774b-yellow",
"774b-yellow",
"774b-blue-meteor",
"774b-blue-meteor",
"774b-green-meteor",
"774b-green-meteor",
"774b-indigo-meteor",
"774b-indigo-meteor",
"774b-orange-meteor",
"774b-orange-meteor",
"774b-red-meteor",
"774b-red-meteor",
"774b-violet-meteor",
"774b-violet-meteor",
"774b-yellow-meteor",
"774b-yellow-meteor",
"774b",
"774b",
"775b",
@ -1733,12 +1715,6 @@
"780b",
"781b",
"781b",
"782b",
"782b",
"783b",
"783b",
"784b",
"784b",
"785b",
"785b",
"786b",
@ -1875,10 +1851,6 @@
"841b",
"842b",
"842b",
"843b",
"843b",
"844b",
"844b",
"845b-gorging",
"845b-gorging",
"845b-gulping",
@ -2017,10 +1989,6 @@
"900b",
"901b",
"901b",
"902b-female",
"902b-female",
"902b",
"902b",
"903b",
"903b",
"904b",
@ -2419,8 +2387,6 @@
"483sb-origin",
"484sb-origin",
"484sb-origin",
"487sb-origin",
"487sb-origin",
"531sb-mega",
"531sb-mega",
"569sb-gigantamax",
@ -2575,8 +2541,6 @@
"671sb-yellow",
"6713sb",
"6713sb",
"672sb",
"672sb",
"6724sb",
"6724sb",
"673sb",
@ -2619,10 +2583,6 @@
"690sb",
"691sb",
"691sb",
"692sb",
"692sb",
"693sb",
"693sb",
"695sb",
"695sb",
"696sb",
@ -2745,10 +2705,6 @@
"751sb",
"752sb",
"752sb",
"753sb",
"753sb",
"754sb",
"754sb",
"755sb",
"755sb",
"756sb",
@ -2777,10 +2733,6 @@
"767sb",
"768sb",
"768sb",
"769sb",
"769sb",
"770sb",
"770sb",
"771sb",
"771sb",
"772sb",
@ -2835,6 +2787,20 @@
"774sb-violet",
"774sb-yellow",
"774sb-yellow",
"774sb-blue-meteor",
"774sb-blue-meteor",
"774sb-green-meteor",
"774sb-green-meteor",
"774sb-indigo-meteor",
"774sb-indigo-meteor",
"774sb-orange-meteor",
"774sb-orange-meteor",
"774sb-red-meteor",
"774sb-red-meteor",
"774sb-violet-meteor",
"774sb-violet-meteor",
"774sb-yellow-meteor",
"774sb-yellow-meteor",
"774sb",
"774sb",
"775sb",
@ -2853,12 +2819,6 @@
"780sb",
"781sb",
"781sb",
"782sb",
"782sb",
"783sb",
"783sb",
"784sb",
"784sb",
"785sb",
"785sb",
"786sb",
@ -2995,10 +2955,6 @@
"841sb",
"842sb",
"842sb",
"843sb",
"843sb",
"844sb",
"844sb",
"845sb-gorging",
"845sb-gorging",
"845sb-gulping",
@ -3137,10 +3093,6 @@
"900sb",
"901sb",
"901sb",
"902sb-female",
"902sb-female",
"902sb",
"902sb",
"903sb",
"903sb",
"904sb",
@ -3544,8 +3496,6 @@
"483s-origin",
"484s-origin",
"484s-origin",
"487s-origin",
"487s-origin",
"531s-mega",
"531s-mega",
"569s-gigantamax",
@ -3700,8 +3650,6 @@
"671s-yellow",
"6713s",
"6713s",
"672s",
"672s",
"6724s",
"6724s",
"673s",
@ -3744,10 +3692,6 @@
"690s",
"691s",
"691s",
"692s",
"692s",
"693s",
"693s",
"695s",
"695s",
"696s",
@ -3870,10 +3814,6 @@
"751s",
"752s",
"752s",
"753s",
"753s",
"754s",
"754s",
"755s",
"755s",
"756s",
@ -3902,10 +3842,6 @@
"767s",
"768s",
"768s",
"769s",
"769s",
"770s",
"770s",
"771s",
"771s",
"772s",
@ -3960,6 +3896,20 @@
"774s-violet",
"774s-yellow",
"774s-yellow",
"774s-blue-meteor",
"774s-blue-meteor",
"774s-green-meteor",
"774s-green-meteor",
"774s-indigo-meteor",
"774s-indigo-meteor",
"774s-orange-meteor",
"774s-orange-meteor",
"774s-red-meteor",
"774s-red-meteor",
"774s-violet-meteor",
"774s-violet-meteor",
"774s-yellow-meteor",
"774s-yellow-meteor",
"774s",
"774s",
"775s",
@ -3978,12 +3928,6 @@
"780s",
"781s",
"781s",
"782s",
"782s",
"783s",
"783s",
"784s",
"784s",
"785s",
"785s",
"786s",
@ -4120,10 +4064,6 @@
"841s",
"842s",
"842s",
"843s",
"843s",
"844s",
"844s",
"845s-gorging",
"845s-gorging",
"845s-gulping",
@ -4262,10 +4202,6 @@
"900s",
"901s",
"901s",
"902s-female",
"902s-female",
"902s",
"902s",
"903s",
"903s",
"904s",
@ -4601,8 +4537,6 @@
"730",
"747",
"748",
"753",
"754",
"755",
"756",
"761",

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 607 B

After

Width:  |  Height:  |  Size: 333 B

BIN
public/fonts/pokemon-bw.ttf Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 637 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 654 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 773 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 474 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 558 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 772 B

After

Width:  |  Height:  |  Size: 264 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 552 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 667 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 767 B

After

Width:  |  Height:  |  Size: 302 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 764 B

After

Width:  |  Height:  |  Size: 318 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1008 B

After

Width:  |  Height:  |  Size: 384 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 789 B

After

Width:  |  Height:  |  Size: 264 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 852 B

After

Width:  |  Height:  |  Size: 390 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 701 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 779 B

After

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 489 B

After

Width:  |  Height:  |  Size: 195 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 694 B

After

Width:  |  Height:  |  Size: 285 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 789 B

After

Width:  |  Height:  |  Size: 267 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 943 B

After

Width:  |  Height:  |  Size: 420 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 635 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 909 B

After

Width:  |  Height:  |  Size: 369 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 706 B

After

Width:  |  Height:  |  Size: 276 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 766 B

After

Width:  |  Height:  |  Size: 309 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 781 B

After

Width:  |  Height:  |  Size: 264 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 703 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 652 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 422 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 868 B

After

Width:  |  Height:  |  Size: 352 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 501 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 788 B

After

Width:  |  Height:  |  Size: 266 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 624 B

After

Width:  |  Height:  |  Size: 242 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 479 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 610 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 617 B

After

Width:  |  Height:  |  Size: 252 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 729 B

After

Width:  |  Height:  |  Size: 287 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 667 B

After

Width:  |  Height:  |  Size: 260 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 784 B

After

Width:  |  Height:  |  Size: 264 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 777 B

After

Width:  |  Height:  |  Size: 351 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 554 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 785 B

After

Width:  |  Height:  |  Size: 264 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 670 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 470 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Some files were not shown because too many files have changed in this diff Show More