diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000000..219c096336b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +# .dockerignore +node_modules +*.log +*.md +.gitignore +Dockerfile +.env \ No newline at end of file diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index f2e17898334..fea857355a0 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -55,15 +55,15 @@ jobs: - 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 change. - - name: Overwrite RELEASE file + # 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" - echo "Release v${{ github.event.inputs.versionName }}" > RELEASE - git add RELEASE - git commit -m "Stage release v${{ github.event.inputs.versionName }}" - + 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 diff --git a/.github/workflows/deploy-beta.yml b/.github/workflows/deploy-beta.yml index 341999dcd45..5abba4488be 100644 --- a/.github/workflows/deploy-beta.yml +++ b/.github/workflows/deploy-beta.yml @@ -22,8 +22,6 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 - with: - version: 10 - uses: actions/setup-node@v4 with: diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 528906196e5..1f2c1259dd1 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -20,8 +20,6 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 - with: - version: 10 - uses: actions/setup-node@v4 with: diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index edecae64f95..e1314c2cbd3 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -30,8 +30,6 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 - with: - version: 10 - name: Set up Node uses: actions/setup-node@v4 diff --git a/.github/workflows/test-shard-template.yml b/.github/workflows/test-shard-template.yml index 79aea56bbd0..6f4728863b4 100644 --- a/.github/workflows/test-shard-template.yml +++ b/.github/workflows/test-shard-template.yml @@ -32,8 +32,6 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 - with: - version: 10 - name: Set up Node.js uses: actions/setup-node@v4 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 04ab7ff4faa..c24b648c490 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -80,7 +80,8 @@ 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) +- [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! diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000000..ddb865b4831 --- /dev/null +++ b/Dockerfile @@ -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"] diff --git a/RELEASE b/RELEASE deleted file mode 100644 index a1a9f30b0e8..00000000000 --- a/RELEASE +++ /dev/null @@ -1 +0,0 @@ -Release v1.10.0 diff --git a/biome.jsonc b/biome.jsonc index e1aac032597..2433ba52010 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.2.3/schema.json", + "$schema": "https://biomejs.dev/schemas/2.2.4/schema.json", "vcs": { "enabled": true, "clientKind": "git", @@ -98,7 +98,9 @@ "useTrimStartEnd": "error", "useReadonlyClassProperties": { "level": "info", // TODO: Graduate to error eventually - "options": { "checkAllProperties": true } + // NOTE: "checkAllProperties" has an immature implementation that + // causes many false positives across files. Enable if/when maturity improves + "options": { "checkAllProperties": false } }, "useConsistentObjectDefinitions": { "level": "error", @@ -209,11 +211,15 @@ "nursery": { "noUselessUndefined": "error", "useMaxParams": { - "level": "warn", // TODO: Change to "error"... eventually... - "options": { "max": 4 } // A lot of stuff has a few params, but + "level": "info", // TODO: Change to "error"... eventually... + "options": { "max": 7 } }, "noShadow": "warn", // TODO: refactor and make "error" - "noNonNullAssertedOptionalChain": "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" } } }, @@ -248,16 +254,9 @@ }, // Overrides to prevent unused import removal inside `overrides.ts`, enums & `.d.ts` files (for TSDoc linkcodes), - // as well as inside script boilerplate files. + // as well as inside script boilerplate files (whose imports will _presumably_ be used in the generated file). { - // TODO: Rename existing boilerplates in the folder and remove this last alias - "includes": [ - "**/src/overrides.ts", - "**/src/enums/**/*", - "**/*.d.ts", - "scripts/**/*.boilerplate.ts", - "**/boilerplates/*.ts" - ], + "includes": ["**/src/overrides.ts", "**/src/enums/**/*", "**/*.d.ts", "scripts/**/*.boilerplate.ts"], "linter": { "rules": { "correctness": { diff --git a/docs/podman.md b/docs/podman.md new file mode 100644 index 00000000000..dea52131e92 --- /dev/null +++ b/docs/podman.md @@ -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 +` \ No newline at end of file diff --git a/package.json b/package.json index e5da00019cb..27b1fc4e290 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,10 @@ "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 --no-isolate", "test:cov": "vitest run --coverage --no-isolate", @@ -31,7 +33,7 @@ "update-locales:remote": "git submodule update --progress --init --recursive --force --remote" }, "devDependencies": { - "@biomejs/biome": "2.2.3", + "@biomejs/biome": "2.2.4", "@ls-lint/ls-lint": "2.3.1", "@types/crypto-js": "^4.2.0", "@types/jsdom": "^21.1.7", @@ -46,12 +48,12 @@ "lefthook": "^1.12.2", "msw": "^2.10.4", "phaser3spectorjs": "^0.0.8", - "typedoc": "^0.28.12", + "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.8.3", - "vite": "^7.0.6", + "typescript": "^5.9.2", + "vite": "^7.0.7", "vite-tsconfig-paths": "^5.1.4", "vitest": "^3.2.4", "vitest-canvas-mock": "^0.3.3" @@ -71,5 +73,6 @@ }, "engines": { "node": ">=22.0.0" - } + }, + "packageManager": "pnpm@10.16.1" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5bbe08bf0c6..46608772338 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,7 +19,7 @@ importers: version: 4.2.0 i18next: specifier: ^24.2.3 - version: 24.2.3(typescript@5.8.3) + version: 24.2.3(typescript@5.9.2) i18next-browser-languagedetector: specifier: ^8.2.0 version: 8.2.0 @@ -28,7 +28,7 @@ importers: version: 3.0.2 i18next-korean-postposition-processor: specifier: ^1.0.0 - version: 1.0.0(i18next@24.2.3(typescript@5.8.3)) + version: 1.0.0(i18next@24.2.3(typescript@5.9.2)) json-stable-stringify: specifier: ^1.3.0 version: 1.3.0 @@ -43,8 +43,8 @@ importers: version: 1.80.16(graphology-types@0.24.8) devDependencies: '@biomejs/biome': - specifier: 2.2.3 - version: 2.2.3 + specifier: 2.2.4 + version: 2.2.4 '@ls-lint/ls-lint': specifier: 2.3.1 version: 2.3.1 @@ -59,7 +59,7 @@ importers: version: 22.16.5 '@vitest/coverage-istanbul': specifier: ^3.2.4 - version: 3.2.4(vitest@3.2.4(@types/node@22.16.5)(jsdom@26.1.0)(msw@2.10.4(@types/node@22.16.5)(typescript@5.8.3))(yaml@2.8.1)) + version: 3.2.4(vitest@3.2.4(@types/node@22.16.5)(jsdom@26.1.0)(msw@2.10.4(@types/node@22.16.5)(typescript@5.9.2))(yaml@2.8.1)) '@vitest/expect': specifier: ^3.2.4 version: 3.2.4 @@ -83,37 +83,37 @@ importers: version: 1.12.2 msw: specifier: ^2.10.4 - version: 2.10.4(@types/node@22.16.5)(typescript@5.8.3) + version: 2.10.4(@types/node@22.16.5)(typescript@5.9.2) phaser3spectorjs: specifier: ^0.0.8 version: 0.0.8 typedoc: - specifier: ^0.28.12 - version: 0.28.12(typescript@5.8.3) + specifier: 0.28.7 + version: 0.28.7(typescript@5.9.2) typedoc-github-theme: specifier: ^0.3.1 - version: 0.3.1(typedoc@0.28.12(typescript@5.8.3)) + version: 0.3.1(typedoc@0.28.7(typescript@5.9.2)) typedoc-plugin-coverage: specifier: ^4.0.1 - version: 4.0.1(typedoc@0.28.12(typescript@5.8.3)) + version: 4.0.1(typedoc@0.28.7(typescript@5.9.2)) typedoc-plugin-mdn-links: specifier: ^5.0.9 - version: 5.0.9(typedoc@0.28.12(typescript@5.8.3)) + version: 5.0.9(typedoc@0.28.7(typescript@5.9.2)) typescript: - specifier: ^5.8.3 - version: 5.8.3 + specifier: ^5.9.2 + version: 5.9.2 vite: - specifier: ^7.0.6 - version: 7.0.6(@types/node@22.16.5)(yaml@2.8.1) + specifier: ^7.0.7 + version: 7.0.7(@types/node@22.16.5)(yaml@2.8.1) vite-tsconfig-paths: specifier: ^5.1.4 - version: 5.1.4(typescript@5.8.3)(vite@7.0.6(@types/node@22.16.5)(yaml@2.8.1)) + version: 5.1.4(typescript@5.9.2)(vite@7.0.7(@types/node@22.16.5)(yaml@2.8.1)) vitest: specifier: ^3.2.4 - version: 3.2.4(@types/node@22.16.5)(jsdom@26.1.0)(msw@2.10.4(@types/node@22.16.5)(typescript@5.8.3))(yaml@2.8.1) + version: 3.2.4(@types/node@22.16.5)(jsdom@26.1.0)(msw@2.10.4(@types/node@22.16.5)(typescript@5.9.2))(yaml@2.8.1) vitest-canvas-mock: specifier: ^0.3.3 - version: 0.3.3(vitest@3.2.4(@types/node@22.16.5)(jsdom@26.1.0)(msw@2.10.4(@types/node@22.16.5)(typescript@5.8.3))(yaml@2.8.1)) + version: 0.3.3(vitest@3.2.4(@types/node@22.16.5)(jsdom@26.1.0)(msw@2.10.4(@types/node@22.16.5)(typescript@5.9.2))(yaml@2.8.1)) packages: @@ -195,55 +195,55 @@ packages: resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} engines: {node: '>=6.9.0'} - '@biomejs/biome@2.2.3': - resolution: {integrity: sha512-9w0uMTvPrIdvUrxazZ42Ib7t8Y2yoGLKLdNne93RLICmaHw7mcLv4PPb5LvZLJF3141gQHiCColOh/v6VWlWmg==} + '@biomejs/biome@2.2.4': + resolution: {integrity: sha512-TBHU5bUy/Ok6m8c0y3pZiuO/BZoY/OcGxoLlrfQof5s8ISVwbVBdFINPQZyFfKwil8XibYWb7JMwnT8wT4WVPg==} engines: {node: '>=14.21.3'} hasBin: true - '@biomejs/cli-darwin-arm64@2.2.3': - resolution: {integrity: sha512-OrqQVBpadB5eqzinXN4+Q6honBz+tTlKVCsbEuEpljK8ASSItzIRZUA02mTikl3H/1nO2BMPFiJ0nkEZNy3B1w==} + '@biomejs/cli-darwin-arm64@2.2.4': + resolution: {integrity: sha512-RJe2uiyaloN4hne4d2+qVj3d3gFJFbmrr5PYtkkjei1O9c+BjGXgpUPVbi8Pl8syumhzJjFsSIYkcLt2VlVLMA==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [darwin] - '@biomejs/cli-darwin-x64@2.2.3': - resolution: {integrity: sha512-OCdBpb1TmyfsTgBAM1kPMXyYKTohQ48WpiN9tkt9xvU6gKVKHY4oVwteBebiOqyfyzCNaSiuKIPjmHjUZ2ZNMg==} + '@biomejs/cli-darwin-x64@2.2.4': + resolution: {integrity: sha512-cFsdB4ePanVWfTnPVaUX+yr8qV8ifxjBKMkZwN7gKb20qXPxd/PmwqUH8mY5wnM9+U0QwM76CxFyBRJhC9tQwg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [darwin] - '@biomejs/cli-linux-arm64-musl@2.2.3': - resolution: {integrity: sha512-q3w9jJ6JFPZPeqyvwwPeaiS/6NEszZ+pXKF+IczNo8Xj6fsii45a4gEEicKyKIytalV+s829ACZujQlXAiVLBQ==} + '@biomejs/cli-linux-arm64-musl@2.2.4': + resolution: {integrity: sha512-7TNPkMQEWfjvJDaZRSkDCPT/2r5ESFPKx+TEev+I2BXDGIjfCZk2+b88FOhnJNHtksbOZv8ZWnxrA5gyTYhSsQ==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-arm64@2.2.3': - resolution: {integrity: sha512-g/Uta2DqYpECxG+vUmTAmUKlVhnGEcY7DXWgKP8ruLRa8Si1QHsWknPY3B/wCo0KgYiFIOAZ9hjsHfNb9L85+g==} + '@biomejs/cli-linux-arm64@2.2.4': + resolution: {integrity: sha512-M/Iz48p4NAzMXOuH+tsn5BvG/Jb07KOMTdSVwJpicmhN309BeEyRyQX+n1XDF0JVSlu28+hiTQ2L4rZPvu7nMw==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-x64-musl@2.2.3': - resolution: {integrity: sha512-y76Dn4vkP1sMRGPFlNc+OTETBhGPJ90jY3il6jAfur8XWrYBQV3swZ1Jo0R2g+JpOeeoA0cOwM7mJG6svDz79w==} + '@biomejs/cli-linux-x64-musl@2.2.4': + resolution: {integrity: sha512-m41nFDS0ksXK2gwXL6W6yZTYPMH0LughqbsxInSKetoH6morVj43szqKx79Iudkp8WRT5SxSh7qVb8KCUiewGg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-linux-x64@2.2.3': - resolution: {integrity: sha512-LEtyYL1fJsvw35CxrbQ0gZoxOG3oZsAjzfRdvRBRHxOpQ91Q5doRVjvWW/wepgSdgk5hlaNzfeqpyGmfSD0Eyw==} + '@biomejs/cli-linux-x64@2.2.4': + resolution: {integrity: sha512-orr3nnf2Dpb2ssl6aihQtvcKtLySLta4E2UcXdp7+RTa7mfJjBgIsbS0B9GC8gVu0hjOu021aU8b3/I1tn+pVQ==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-win32-arm64@2.2.3': - resolution: {integrity: sha512-Ms9zFYzjcJK7LV+AOMYnjN3pV3xL8Prxf9aWdDVL74onLn5kcvZ1ZMQswE5XHtnd/r/0bnUd928Rpbs14BzVmA==} + '@biomejs/cli-win32-arm64@2.2.4': + resolution: {integrity: sha512-NXnfTeKHDFUWfxAefa57DiGmu9VyKi0cDqFpdI+1hJWQjGJhJutHPX0b5m+eXvTKOaf+brU+P0JrQAZMb5yYaQ==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [win32] - '@biomejs/cli-win32-x64@2.2.3': - resolution: {integrity: sha512-gvCpewE7mBwBIpqk1YrUqNR4mCiyJm6UI3YWQQXkedSSEwzRdodRpaKhbdbHw1/hmTWOVXQ+Eih5Qctf4TCVOQ==} + '@biomejs/cli-win32-x64@2.2.4': + resolution: {integrity: sha512-3Y4V4zVRarVh/B/eSHczR4LYoSVyv3Dfuvm3cWs5w/HScccS0+Wt/lHOcDTRYeHjQmMYVC3rIRWqyN2EI52+zg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [win32] @@ -285,158 +285,158 @@ packages: resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} engines: {node: '>=18'} - '@esbuild/aix-ppc64@0.25.8': - resolution: {integrity: sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==} + '@esbuild/aix-ppc64@0.25.9': + resolution: {integrity: sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.25.8': - resolution: {integrity: sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==} + '@esbuild/android-arm64@0.25.9': + resolution: {integrity: sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.25.8': - resolution: {integrity: sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==} + '@esbuild/android-arm@0.25.9': + resolution: {integrity: sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.25.8': - resolution: {integrity: sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==} + '@esbuild/android-x64@0.25.9': + resolution: {integrity: sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.25.8': - resolution: {integrity: sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==} + '@esbuild/darwin-arm64@0.25.9': + resolution: {integrity: sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.25.8': - resolution: {integrity: sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==} + '@esbuild/darwin-x64@0.25.9': + resolution: {integrity: sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.25.8': - resolution: {integrity: sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==} + '@esbuild/freebsd-arm64@0.25.9': + resolution: {integrity: sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.8': - resolution: {integrity: sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==} + '@esbuild/freebsd-x64@0.25.9': + resolution: {integrity: sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.25.8': - resolution: {integrity: sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==} + '@esbuild/linux-arm64@0.25.9': + resolution: {integrity: sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.25.8': - resolution: {integrity: sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==} + '@esbuild/linux-arm@0.25.9': + resolution: {integrity: sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.25.8': - resolution: {integrity: sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==} + '@esbuild/linux-ia32@0.25.9': + resolution: {integrity: sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.25.8': - resolution: {integrity: sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==} + '@esbuild/linux-loong64@0.25.9': + resolution: {integrity: sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.25.8': - resolution: {integrity: sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==} + '@esbuild/linux-mips64el@0.25.9': + resolution: {integrity: sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.25.8': - resolution: {integrity: sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==} + '@esbuild/linux-ppc64@0.25.9': + resolution: {integrity: sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.25.8': - resolution: {integrity: sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==} + '@esbuild/linux-riscv64@0.25.9': + resolution: {integrity: sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.25.8': - resolution: {integrity: sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==} + '@esbuild/linux-s390x@0.25.9': + resolution: {integrity: sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.25.8': - resolution: {integrity: sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==} + '@esbuild/linux-x64@0.25.9': + resolution: {integrity: sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.25.8': - resolution: {integrity: sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==} + '@esbuild/netbsd-arm64@0.25.9': + resolution: {integrity: sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.8': - resolution: {integrity: sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==} + '@esbuild/netbsd-x64@0.25.9': + resolution: {integrity: sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.25.8': - resolution: {integrity: sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==} + '@esbuild/openbsd-arm64@0.25.9': + resolution: {integrity: sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.8': - resolution: {integrity: sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==} + '@esbuild/openbsd-x64@0.25.9': + resolution: {integrity: sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.25.8': - resolution: {integrity: sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==} + '@esbuild/openharmony-arm64@0.25.9': + resolution: {integrity: sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.25.8': - resolution: {integrity: sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==} + '@esbuild/sunos-x64@0.25.9': + resolution: {integrity: sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.25.8': - resolution: {integrity: sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==} + '@esbuild/win32-arm64@0.25.9': + resolution: {integrity: sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.25.8': - resolution: {integrity: sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==} + '@esbuild/win32-ia32@0.25.9': + resolution: {integrity: sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.25.8': - resolution: {integrity: sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==} + '@esbuild/win32-x64@0.25.9': + resolution: {integrity: sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -612,103 +612,108 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@rollup/rollup-android-arm-eabi@4.46.1': - resolution: {integrity: sha512-oENme6QxtLCqjChRUUo3S6X8hjCXnWmJWnedD7VbGML5GUtaOtAyx+fEEXnBXVf0CBZApMQU0Idwi0FmyxzQhw==} + '@rollup/rollup-android-arm-eabi@4.50.1': + resolution: {integrity: sha512-HJXwzoZN4eYTdD8bVV22DN8gsPCAj3V20NHKOs8ezfXanGpmVPR7kalUHd+Y31IJp9stdB87VKPFbsGY3H/2ag==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.46.1': - resolution: {integrity: sha512-OikvNT3qYTl9+4qQ9Bpn6+XHM+ogtFadRLuT2EXiFQMiNkXFLQfNVppi5o28wvYdHL2s3fM0D/MZJ8UkNFZWsw==} + '@rollup/rollup-android-arm64@4.50.1': + resolution: {integrity: sha512-PZlsJVcjHfcH53mOImyt3bc97Ep3FJDXRpk9sMdGX0qgLmY0EIWxCag6EigerGhLVuL8lDVYNnSo8qnTElO4xw==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.46.1': - resolution: {integrity: sha512-EFYNNGij2WllnzljQDQnlFTXzSJw87cpAs4TVBAWLdkvic5Uh5tISrIL6NRcxoh/b2EFBG/TK8hgRrGx94zD4A==} + '@rollup/rollup-darwin-arm64@4.50.1': + resolution: {integrity: sha512-xc6i2AuWh++oGi4ylOFPmzJOEeAa2lJeGUGb4MudOtgfyyjr4UPNK+eEWTPLvmPJIY/pgw6ssFIox23SyrkkJw==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.46.1': - resolution: {integrity: sha512-ZaNH06O1KeTug9WI2+GRBE5Ujt9kZw4a1+OIwnBHal92I8PxSsl5KpsrPvthRynkhMck4XPdvY0z26Cym/b7oA==} + '@rollup/rollup-darwin-x64@4.50.1': + resolution: {integrity: sha512-2ofU89lEpDYhdLAbRdeyz/kX3Y2lpYc6ShRnDjY35bZhd2ipuDMDi6ZTQ9NIag94K28nFMofdnKeHR7BT0CATw==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.46.1': - resolution: {integrity: sha512-n4SLVebZP8uUlJ2r04+g2U/xFeiQlw09Me5UFqny8HGbARl503LNH5CqFTb5U5jNxTouhRjai6qPT0CR5c/Iig==} + '@rollup/rollup-freebsd-arm64@4.50.1': + resolution: {integrity: sha512-wOsE6H2u6PxsHY/BeFHA4VGQN3KUJFZp7QJBmDYI983fgxq5Th8FDkVuERb2l9vDMs1D5XhOrhBrnqcEY6l8ZA==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.46.1': - resolution: {integrity: sha512-8vu9c02F16heTqpvo3yeiu7Vi1REDEC/yES/dIfq3tSXe6mLndiwvYr3AAvd1tMNUqE9yeGYa5w7PRbI5QUV+w==} + '@rollup/rollup-freebsd-x64@4.50.1': + resolution: {integrity: sha512-A/xeqaHTlKbQggxCqispFAcNjycpUEHP52mwMQZUNqDUJFFYtPHCXS1VAG29uMlDzIVr+i00tSFWFLivMcoIBQ==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.46.1': - resolution: {integrity: sha512-K4ncpWl7sQuyp6rWiGUvb6Q18ba8mzM0rjWJ5JgYKlIXAau1db7hZnR0ldJvqKWWJDxqzSLwGUhA4jp+KqgDtQ==} + '@rollup/rollup-linux-arm-gnueabihf@4.50.1': + resolution: {integrity: sha512-54v4okehwl5TaSIkpp97rAHGp7t3ghinRd/vyC1iXqXMfjYUTm7TfYmCzXDoHUPTTf36L8pr0E7YsD3CfB3ZDg==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.46.1': - resolution: {integrity: sha512-YykPnXsjUjmXE6j6k2QBBGAn1YsJUix7pYaPLK3RVE0bQL2jfdbfykPxfF8AgBlqtYbfEnYHmLXNa6QETjdOjQ==} + '@rollup/rollup-linux-arm-musleabihf@4.50.1': + resolution: {integrity: sha512-p/LaFyajPN/0PUHjv8TNyxLiA7RwmDoVY3flXHPSzqrGcIp/c2FjwPPP5++u87DGHtw+5kSH5bCJz0mvXngYxw==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.46.1': - resolution: {integrity: sha512-kKvqBGbZ8i9pCGW3a1FH3HNIVg49dXXTsChGFsHGXQaVJPLA4f/O+XmTxfklhccxdF5FefUn2hvkoGJH0ScWOA==} + '@rollup/rollup-linux-arm64-gnu@4.50.1': + resolution: {integrity: sha512-2AbMhFFkTo6Ptna1zO7kAXXDLi7H9fGTbVaIq2AAYO7yzcAsuTNWPHhb2aTA6GPiP+JXh85Y8CiS54iZoj4opw==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.46.1': - resolution: {integrity: sha512-zzX5nTw1N1plmqC9RGC9vZHFuiM7ZP7oSWQGqpbmfjK7p947D518cVK1/MQudsBdcD84t6k70WNczJOct6+hdg==} + '@rollup/rollup-linux-arm64-musl@4.50.1': + resolution: {integrity: sha512-Cgef+5aZwuvesQNw9eX7g19FfKX5/pQRIyhoXLCiBOrWopjo7ycfB292TX9MDcDijiuIJlx1IzJz3IoCPfqs9w==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.46.1': - resolution: {integrity: sha512-O8CwgSBo6ewPpktFfSDgB6SJN9XDcPSvuwxfejiddbIC/hn9Tg6Ai0f0eYDf3XvB/+PIWzOQL+7+TZoB8p9Yuw==} + '@rollup/rollup-linux-loongarch64-gnu@4.50.1': + resolution: {integrity: sha512-RPhTwWMzpYYrHrJAS7CmpdtHNKtt2Ueo+BlLBjfZEhYBhK00OsEqM08/7f+eohiF6poe0YRDDd8nAvwtE/Y62Q==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-ppc64-gnu@4.46.1': - resolution: {integrity: sha512-JnCfFVEKeq6G3h3z8e60kAp8Rd7QVnWCtPm7cxx+5OtP80g/3nmPtfdCXbVl063e3KsRnGSKDHUQMydmzc/wBA==} + '@rollup/rollup-linux-ppc64-gnu@4.50.1': + resolution: {integrity: sha512-eSGMVQw9iekut62O7eBdbiccRguuDgiPMsw++BVUg+1K7WjZXHOg/YOT9SWMzPZA+w98G+Fa1VqJgHZOHHnY0Q==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.46.1': - resolution: {integrity: sha512-dVxuDqS237eQXkbYzQQfdf/njgeNw6LZuVyEdUaWwRpKHhsLI+y4H/NJV8xJGU19vnOJCVwaBFgr936FHOnJsQ==} + '@rollup/rollup-linux-riscv64-gnu@4.50.1': + resolution: {integrity: sha512-S208ojx8a4ciIPrLgazF6AgdcNJzQE4+S9rsmOmDJkusvctii+ZvEuIC4v/xFqzbuP8yDjn73oBlNDgF6YGSXQ==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.46.1': - resolution: {integrity: sha512-CvvgNl2hrZrTR9jXK1ye0Go0HQRT6ohQdDfWR47/KFKiLd5oN5T14jRdUVGF4tnsN8y9oSfMOqH6RuHh+ck8+w==} + '@rollup/rollup-linux-riscv64-musl@4.50.1': + resolution: {integrity: sha512-3Ag8Ls1ggqkGUvSZWYcdgFwriy2lWo+0QlYgEFra/5JGtAd6C5Hw59oojx1DeqcA2Wds2ayRgvJ4qxVTzCHgzg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.46.1': - resolution: {integrity: sha512-x7ANt2VOg2565oGHJ6rIuuAon+A8sfe1IeUx25IKqi49OjSr/K3awoNqr9gCwGEJo9OuXlOn+H2p1VJKx1psxA==} + '@rollup/rollup-linux-s390x-gnu@4.50.1': + resolution: {integrity: sha512-t9YrKfaxCYe7l7ldFERE1BRg/4TATxIg+YieHQ966jwvo7ddHJxPj9cNFWLAzhkVsbBvNA4qTbPVNsZKBO4NSg==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.46.1': - resolution: {integrity: sha512-9OADZYryz/7E8/qt0vnaHQgmia2Y0wrjSSn1V/uL+zw/i7NUhxbX4cHXdEQ7dnJgzYDS81d8+tf6nbIdRFZQoQ==} + '@rollup/rollup-linux-x64-gnu@4.50.1': + resolution: {integrity: sha512-MCgtFB2+SVNuQmmjHf+wfI4CMxy3Tk8XjA5Z//A0AKD7QXUYFMQcns91K6dEHBvZPCnhJSyDWLApk40Iq/H3tA==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.46.1': - resolution: {integrity: sha512-NuvSCbXEKY+NGWHyivzbjSVJi68Xfq1VnIvGmsuXs6TCtveeoDRKutI5vf2ntmNnVq64Q4zInet0UDQ+yMB6tA==} + '@rollup/rollup-linux-x64-musl@4.50.1': + resolution: {integrity: sha512-nEvqG+0jeRmqaUMuwzlfMKwcIVffy/9KGbAGyoa26iu6eSngAYQ512bMXuqqPrlTyfqdlB9FVINs93j534UJrg==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.46.1': - resolution: {integrity: sha512-mWz+6FSRb82xuUMMV1X3NGiaPFqbLN9aIueHleTZCc46cJvwTlvIh7reQLk4p97dv0nddyewBhwzryBHH7wtPw==} + '@rollup/rollup-openharmony-arm64@4.50.1': + resolution: {integrity: sha512-RDsLm+phmT3MJd9SNxA9MNuEAO/J2fhW8GXk62G/B4G7sLVumNFbRwDL6v5NrESb48k+QMqdGbHgEtfU0LCpbA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.50.1': + resolution: {integrity: sha512-hpZB/TImk2FlAFAIsoElM3tLzq57uxnGYwplg6WDyAxbYczSi8O2eQ+H2Lx74504rwKtZ3N2g4bCUkiamzS6TQ==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.46.1': - resolution: {integrity: sha512-7Thzy9TMXDw9AU4f4vsLNBxh7/VOKuXi73VH3d/kHGr0tZ3x/ewgL9uC7ojUKmH1/zvmZe2tLapYcZllk3SO8Q==} + '@rollup/rollup-win32-ia32-msvc@4.50.1': + resolution: {integrity: sha512-SXjv8JlbzKM0fTJidX4eVsH+Wmnp0/WcD8gJxIZyR6Gay5Qcsmdbi9zVtnbkGPG8v2vMR1AD06lGWy5FLMcG7A==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.46.1': - resolution: {integrity: sha512-7GVB4luhFmGUNXXJhH2jJwZCFB3pIOixv2E3s17GQHBFUOQaISlt7aGcQgqvCaDSxTZJUzlK/QJ1FN8S94MrzQ==} + '@rollup/rollup-win32-x64-msvc@4.50.1': + resolution: {integrity: sha512-StxAO/8ts62KZVRAm4JZYq9+NqNsV7RvimNK+YM7ry//zebEH6meuugqW/P5OFUCjyQgui+9fUxT6d5NShvMvA==} cpu: [x64] os: [win32] @@ -1022,8 +1027,8 @@ packages: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} - esbuild@0.25.8: - resolution: {integrity: sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==} + esbuild@0.25.9: + resolution: {integrity: sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==} engines: {node: '>=18'} hasBin: true @@ -1058,8 +1063,9 @@ packages: fast-uri@3.0.6: resolution: {integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==} - fdir@6.4.6: - resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} peerDependencies: picomatch: ^3 || ^4 peerDependenciesMeta: @@ -1597,8 +1603,8 @@ packages: engines: {node: '>= 0.4'} hasBin: true - rollup@4.46.1: - resolution: {integrity: sha512-33xGNBsDJAkzt0PvninskHlWnTIPgDtTwhg0U38CUoNP/7H6wI2Cz6dUeoNPbjdTdsYTGuiFFASuUOWovH0SyQ==} + rollup@4.50.1: + resolution: {integrity: sha512-78E9voJHwnXQMiQdiqswVLZwJIzdBKJ1GdI5Zx6XwoFKUIk09/sSrr+05QFzvYb8q6Y9pPV45zzDuYa3907TZA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -1734,6 +1740,10 @@ packages: resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} engines: {node: '>=12.0.0'} + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + tinypool@1.1.1: resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} engines: {node: ^18.0.0 || >=20.0.0} @@ -1818,15 +1828,15 @@ packages: peerDependencies: typedoc: 0.27.x || 0.28.x - typedoc@0.28.12: - resolution: {integrity: sha512-H5ODu4f7N+myG4MfuSp2Vh6wV+WLoZaEYxKPt2y8hmmqNEMVrH69DAjjdmYivF4tP/C2jrIZCZhPalZlTU/ipA==} + typedoc@0.28.7: + resolution: {integrity: sha512-lpz0Oxl6aidFkmS90VQDQjk/Qf2iw0IUvFqirdONBdj7jPSN9mGXhy66BcGNDxx5ZMyKKiBVAREvPEzT6Uxipw==} engines: {node: '>= 18', pnpm: '>= 10'} hasBin: true peerDependencies: - typescript: 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x + typescript: 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x - typescript@5.8.3: - resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + typescript@5.9.2: + resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} engines: {node: '>=14.17'} hasBin: true @@ -1865,8 +1875,8 @@ packages: vite: optional: true - vite@7.0.6: - resolution: {integrity: sha512-MHFiOENNBd+Bd9uvc8GEsIzdkn1JxMmEeYX35tI3fv0sJBUTfW5tQsoaOwuY4KhBI09A3dUJ/DXf2yxPVPUceg==} + vite@7.0.7: + resolution: {integrity: sha512-hc6LujN/EkJHmxeiDJMs0qBontZ1cdBvvoCbWhVjzUFTU329VRyOC46gHNSA8NcOC5yzCeXpwI40tieI3DEZqg==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -2154,39 +2164,39 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 - '@biomejs/biome@2.2.3': + '@biomejs/biome@2.2.4': optionalDependencies: - '@biomejs/cli-darwin-arm64': 2.2.3 - '@biomejs/cli-darwin-x64': 2.2.3 - '@biomejs/cli-linux-arm64': 2.2.3 - '@biomejs/cli-linux-arm64-musl': 2.2.3 - '@biomejs/cli-linux-x64': 2.2.3 - '@biomejs/cli-linux-x64-musl': 2.2.3 - '@biomejs/cli-win32-arm64': 2.2.3 - '@biomejs/cli-win32-x64': 2.2.3 + '@biomejs/cli-darwin-arm64': 2.2.4 + '@biomejs/cli-darwin-x64': 2.2.4 + '@biomejs/cli-linux-arm64': 2.2.4 + '@biomejs/cli-linux-arm64-musl': 2.2.4 + '@biomejs/cli-linux-x64': 2.2.4 + '@biomejs/cli-linux-x64-musl': 2.2.4 + '@biomejs/cli-win32-arm64': 2.2.4 + '@biomejs/cli-win32-x64': 2.2.4 - '@biomejs/cli-darwin-arm64@2.2.3': + '@biomejs/cli-darwin-arm64@2.2.4': optional: true - '@biomejs/cli-darwin-x64@2.2.3': + '@biomejs/cli-darwin-x64@2.2.4': optional: true - '@biomejs/cli-linux-arm64-musl@2.2.3': + '@biomejs/cli-linux-arm64-musl@2.2.4': optional: true - '@biomejs/cli-linux-arm64@2.2.3': + '@biomejs/cli-linux-arm64@2.2.4': optional: true - '@biomejs/cli-linux-x64-musl@2.2.3': + '@biomejs/cli-linux-x64-musl@2.2.4': optional: true - '@biomejs/cli-linux-x64@2.2.3': + '@biomejs/cli-linux-x64@2.2.4': optional: true - '@biomejs/cli-win32-arm64@2.2.3': + '@biomejs/cli-win32-arm64@2.2.4': optional: true - '@biomejs/cli-win32-x64@2.2.3': + '@biomejs/cli-win32-x64@2.2.4': optional: true '@bundled-es-modules/cookie@2.0.1': @@ -2222,82 +2232,82 @@ snapshots: '@csstools/css-tokenizer@3.0.4': {} - '@esbuild/aix-ppc64@0.25.8': + '@esbuild/aix-ppc64@0.25.9': optional: true - '@esbuild/android-arm64@0.25.8': + '@esbuild/android-arm64@0.25.9': optional: true - '@esbuild/android-arm@0.25.8': + '@esbuild/android-arm@0.25.9': optional: true - '@esbuild/android-x64@0.25.8': + '@esbuild/android-x64@0.25.9': optional: true - '@esbuild/darwin-arm64@0.25.8': + '@esbuild/darwin-arm64@0.25.9': optional: true - '@esbuild/darwin-x64@0.25.8': + '@esbuild/darwin-x64@0.25.9': optional: true - '@esbuild/freebsd-arm64@0.25.8': + '@esbuild/freebsd-arm64@0.25.9': optional: true - '@esbuild/freebsd-x64@0.25.8': + '@esbuild/freebsd-x64@0.25.9': optional: true - '@esbuild/linux-arm64@0.25.8': + '@esbuild/linux-arm64@0.25.9': optional: true - '@esbuild/linux-arm@0.25.8': + '@esbuild/linux-arm@0.25.9': optional: true - '@esbuild/linux-ia32@0.25.8': + '@esbuild/linux-ia32@0.25.9': optional: true - '@esbuild/linux-loong64@0.25.8': + '@esbuild/linux-loong64@0.25.9': optional: true - '@esbuild/linux-mips64el@0.25.8': + '@esbuild/linux-mips64el@0.25.9': optional: true - '@esbuild/linux-ppc64@0.25.8': + '@esbuild/linux-ppc64@0.25.9': optional: true - '@esbuild/linux-riscv64@0.25.8': + '@esbuild/linux-riscv64@0.25.9': optional: true - '@esbuild/linux-s390x@0.25.8': + '@esbuild/linux-s390x@0.25.9': optional: true - '@esbuild/linux-x64@0.25.8': + '@esbuild/linux-x64@0.25.9': optional: true - '@esbuild/netbsd-arm64@0.25.8': + '@esbuild/netbsd-arm64@0.25.9': optional: true - '@esbuild/netbsd-x64@0.25.8': + '@esbuild/netbsd-x64@0.25.9': optional: true - '@esbuild/openbsd-arm64@0.25.8': + '@esbuild/openbsd-arm64@0.25.9': optional: true - '@esbuild/openbsd-x64@0.25.8': + '@esbuild/openbsd-x64@0.25.9': optional: true - '@esbuild/openharmony-arm64@0.25.8': + '@esbuild/openharmony-arm64@0.25.9': optional: true - '@esbuild/sunos-x64@0.25.8': + '@esbuild/sunos-x64@0.25.9': optional: true - '@esbuild/win32-arm64@0.25.8': + '@esbuild/win32-arm64@0.25.9': optional: true - '@esbuild/win32-ia32@0.25.8': + '@esbuild/win32-ia32@0.25.9': optional: true - '@esbuild/win32-x64@0.25.8': + '@esbuild/win32-x64@0.25.9': optional: true '@gerrit0/mini-shiki@3.12.2': @@ -2474,64 +2484,67 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@rollup/rollup-android-arm-eabi@4.46.1': + '@rollup/rollup-android-arm-eabi@4.50.1': optional: true - '@rollup/rollup-android-arm64@4.46.1': + '@rollup/rollup-android-arm64@4.50.1': optional: true - '@rollup/rollup-darwin-arm64@4.46.1': + '@rollup/rollup-darwin-arm64@4.50.1': optional: true - '@rollup/rollup-darwin-x64@4.46.1': + '@rollup/rollup-darwin-x64@4.50.1': optional: true - '@rollup/rollup-freebsd-arm64@4.46.1': + '@rollup/rollup-freebsd-arm64@4.50.1': optional: true - '@rollup/rollup-freebsd-x64@4.46.1': + '@rollup/rollup-freebsd-x64@4.50.1': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.46.1': + '@rollup/rollup-linux-arm-gnueabihf@4.50.1': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.46.1': + '@rollup/rollup-linux-arm-musleabihf@4.50.1': optional: true - '@rollup/rollup-linux-arm64-gnu@4.46.1': + '@rollup/rollup-linux-arm64-gnu@4.50.1': optional: true - '@rollup/rollup-linux-arm64-musl@4.46.1': + '@rollup/rollup-linux-arm64-musl@4.50.1': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.46.1': + '@rollup/rollup-linux-loongarch64-gnu@4.50.1': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.46.1': + '@rollup/rollup-linux-ppc64-gnu@4.50.1': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.46.1': + '@rollup/rollup-linux-riscv64-gnu@4.50.1': optional: true - '@rollup/rollup-linux-riscv64-musl@4.46.1': + '@rollup/rollup-linux-riscv64-musl@4.50.1': optional: true - '@rollup/rollup-linux-s390x-gnu@4.46.1': + '@rollup/rollup-linux-s390x-gnu@4.50.1': optional: true - '@rollup/rollup-linux-x64-gnu@4.46.1': + '@rollup/rollup-linux-x64-gnu@4.50.1': optional: true - '@rollup/rollup-linux-x64-musl@4.46.1': + '@rollup/rollup-linux-x64-musl@4.50.1': optional: true - '@rollup/rollup-win32-arm64-msvc@4.46.1': + '@rollup/rollup-openharmony-arm64@4.50.1': optional: true - '@rollup/rollup-win32-ia32-msvc@4.46.1': + '@rollup/rollup-win32-arm64-msvc@4.50.1': optional: true - '@rollup/rollup-win32-x64-msvc@4.46.1': + '@rollup/rollup-win32-ia32-msvc@4.50.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.50.1': optional: true '@shikijs/engine-oniguruma@3.12.2': @@ -2586,7 +2599,7 @@ snapshots: '@types/unist@3.0.3': {} - '@vitest/coverage-istanbul@3.2.4(vitest@3.2.4(@types/node@22.16.5)(jsdom@26.1.0)(msw@2.10.4(@types/node@22.16.5)(typescript@5.8.3))(yaml@2.8.1))': + '@vitest/coverage-istanbul@3.2.4(vitest@3.2.4(@types/node@22.16.5)(jsdom@26.1.0)(msw@2.10.4(@types/node@22.16.5)(typescript@5.9.2))(yaml@2.8.1))': dependencies: '@istanbuljs/schema': 0.1.3 debug: 4.4.1 @@ -2598,7 +2611,7 @@ snapshots: magicast: 0.3.5 test-exclude: 7.0.1 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/node@22.16.5)(jsdom@26.1.0)(msw@2.10.4(@types/node@22.16.5)(typescript@5.8.3))(yaml@2.8.1) + vitest: 3.2.4(@types/node@22.16.5)(jsdom@26.1.0)(msw@2.10.4(@types/node@22.16.5)(typescript@5.9.2))(yaml@2.8.1) transitivePeerDependencies: - supports-color @@ -2610,14 +2623,14 @@ snapshots: chai: 5.2.1 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(msw@2.10.4(@types/node@22.16.5)(typescript@5.8.3))(vite@7.0.6(@types/node@22.16.5)(yaml@2.8.1))': + '@vitest/mocker@3.2.4(msw@2.10.4(@types/node@22.16.5)(typescript@5.9.2))(vite@7.0.7(@types/node@22.16.5)(yaml@2.8.1))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - msw: 2.10.4(@types/node@22.16.5)(typescript@5.8.3) - vite: 7.0.6(@types/node@22.16.5)(yaml@2.8.1) + msw: 2.10.4(@types/node@22.16.5)(typescript@5.9.2) + vite: 7.0.7(@types/node@22.16.5)(yaml@2.8.1) '@vitest/pretty-format@3.2.4': dependencies: @@ -2868,34 +2881,34 @@ snapshots: dependencies: es-errors: 1.3.0 - esbuild@0.25.8: + esbuild@0.25.9: optionalDependencies: - '@esbuild/aix-ppc64': 0.25.8 - '@esbuild/android-arm': 0.25.8 - '@esbuild/android-arm64': 0.25.8 - '@esbuild/android-x64': 0.25.8 - '@esbuild/darwin-arm64': 0.25.8 - '@esbuild/darwin-x64': 0.25.8 - '@esbuild/freebsd-arm64': 0.25.8 - '@esbuild/freebsd-x64': 0.25.8 - '@esbuild/linux-arm': 0.25.8 - '@esbuild/linux-arm64': 0.25.8 - '@esbuild/linux-ia32': 0.25.8 - '@esbuild/linux-loong64': 0.25.8 - '@esbuild/linux-mips64el': 0.25.8 - '@esbuild/linux-ppc64': 0.25.8 - '@esbuild/linux-riscv64': 0.25.8 - '@esbuild/linux-s390x': 0.25.8 - '@esbuild/linux-x64': 0.25.8 - '@esbuild/netbsd-arm64': 0.25.8 - '@esbuild/netbsd-x64': 0.25.8 - '@esbuild/openbsd-arm64': 0.25.8 - '@esbuild/openbsd-x64': 0.25.8 - '@esbuild/openharmony-arm64': 0.25.8 - '@esbuild/sunos-x64': 0.25.8 - '@esbuild/win32-arm64': 0.25.8 - '@esbuild/win32-ia32': 0.25.8 - '@esbuild/win32-x64': 0.25.8 + '@esbuild/aix-ppc64': 0.25.9 + '@esbuild/android-arm': 0.25.9 + '@esbuild/android-arm64': 0.25.9 + '@esbuild/android-x64': 0.25.9 + '@esbuild/darwin-arm64': 0.25.9 + '@esbuild/darwin-x64': 0.25.9 + '@esbuild/freebsd-arm64': 0.25.9 + '@esbuild/freebsd-x64': 0.25.9 + '@esbuild/linux-arm': 0.25.9 + '@esbuild/linux-arm64': 0.25.9 + '@esbuild/linux-ia32': 0.25.9 + '@esbuild/linux-loong64': 0.25.9 + '@esbuild/linux-mips64el': 0.25.9 + '@esbuild/linux-ppc64': 0.25.9 + '@esbuild/linux-riscv64': 0.25.9 + '@esbuild/linux-s390x': 0.25.9 + '@esbuild/linux-x64': 0.25.9 + '@esbuild/netbsd-arm64': 0.25.9 + '@esbuild/netbsd-x64': 0.25.9 + '@esbuild/openbsd-arm64': 0.25.9 + '@esbuild/openbsd-x64': 0.25.9 + '@esbuild/openharmony-arm64': 0.25.9 + '@esbuild/sunos-x64': 0.25.9 + '@esbuild/win32-arm64': 0.25.9 + '@esbuild/win32-ia32': 0.25.9 + '@esbuild/win32-x64': 0.25.9 escalade@3.2.0: {} @@ -2921,7 +2934,7 @@ snapshots: fast-uri@3.0.6: {} - fdir@6.4.6(picomatch@4.0.3): + fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 @@ -3040,19 +3053,19 @@ snapshots: transitivePeerDependencies: - encoding - i18next-korean-postposition-processor@1.0.0(i18next@24.2.3(typescript@5.8.3)): + i18next-korean-postposition-processor@1.0.0(i18next@24.2.3(typescript@5.9.2)): dependencies: - i18next: 24.2.3(typescript@5.8.3) + i18next: 24.2.3(typescript@5.9.2) i18next@22.5.1: dependencies: '@babel/runtime': 7.28.2 - i18next@24.2.3(typescript@5.8.3): + i18next@24.2.3(typescript@5.9.2): dependencies: '@babel/runtime': 7.28.2 optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.2 iconv-lite@0.4.24: dependencies: @@ -3319,7 +3332,7 @@ snapshots: ms@2.1.3: {} - msw@2.10.4(@types/node@22.16.5)(typescript@5.8.3): + msw@2.10.4(@types/node@22.16.5)(typescript@5.9.2): dependencies: '@bundled-es-modules/cookie': 2.0.1 '@bundled-es-modules/statuses': 1.0.1 @@ -3340,7 +3353,7 @@ snapshots: type-fest: 4.41.0 yargs: 17.7.2 optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.2 transitivePeerDependencies: - '@types/node' @@ -3467,30 +3480,31 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - rollup@4.46.1: + rollup@4.50.1: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.46.1 - '@rollup/rollup-android-arm64': 4.46.1 - '@rollup/rollup-darwin-arm64': 4.46.1 - '@rollup/rollup-darwin-x64': 4.46.1 - '@rollup/rollup-freebsd-arm64': 4.46.1 - '@rollup/rollup-freebsd-x64': 4.46.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.46.1 - '@rollup/rollup-linux-arm-musleabihf': 4.46.1 - '@rollup/rollup-linux-arm64-gnu': 4.46.1 - '@rollup/rollup-linux-arm64-musl': 4.46.1 - '@rollup/rollup-linux-loongarch64-gnu': 4.46.1 - '@rollup/rollup-linux-ppc64-gnu': 4.46.1 - '@rollup/rollup-linux-riscv64-gnu': 4.46.1 - '@rollup/rollup-linux-riscv64-musl': 4.46.1 - '@rollup/rollup-linux-s390x-gnu': 4.46.1 - '@rollup/rollup-linux-x64-gnu': 4.46.1 - '@rollup/rollup-linux-x64-musl': 4.46.1 - '@rollup/rollup-win32-arm64-msvc': 4.46.1 - '@rollup/rollup-win32-ia32-msvc': 4.46.1 - '@rollup/rollup-win32-x64-msvc': 4.46.1 + '@rollup/rollup-android-arm-eabi': 4.50.1 + '@rollup/rollup-android-arm64': 4.50.1 + '@rollup/rollup-darwin-arm64': 4.50.1 + '@rollup/rollup-darwin-x64': 4.50.1 + '@rollup/rollup-freebsd-arm64': 4.50.1 + '@rollup/rollup-freebsd-x64': 4.50.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.50.1 + '@rollup/rollup-linux-arm-musleabihf': 4.50.1 + '@rollup/rollup-linux-arm64-gnu': 4.50.1 + '@rollup/rollup-linux-arm64-musl': 4.50.1 + '@rollup/rollup-linux-loongarch64-gnu': 4.50.1 + '@rollup/rollup-linux-ppc64-gnu': 4.50.1 + '@rollup/rollup-linux-riscv64-gnu': 4.50.1 + '@rollup/rollup-linux-riscv64-musl': 4.50.1 + '@rollup/rollup-linux-s390x-gnu': 4.50.1 + '@rollup/rollup-linux-x64-gnu': 4.50.1 + '@rollup/rollup-linux-x64-musl': 4.50.1 + '@rollup/rollup-openharmony-arm64': 4.50.1 + '@rollup/rollup-win32-arm64-msvc': 4.50.1 + '@rollup/rollup-win32-ia32-msvc': 4.50.1 + '@rollup/rollup-win32-x64-msvc': 4.50.1 fsevents: 2.3.3 rrweb-cssom@0.8.0: {} @@ -3604,7 +3618,12 @@ snapshots: tinyglobby@0.2.14: dependencies: - fdir: 6.4.6(picomatch@4.0.3) + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 tinypool@1.1.1: {} @@ -3640,9 +3659,9 @@ snapshots: dependencies: punycode: 2.3.1 - tsconfck@3.1.6(typescript@5.8.3): + tsconfck@3.1.6(typescript@5.9.2): optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.2 tsconfig-paths-webpack-plugin@4.2.0: dependencies: @@ -3663,28 +3682,28 @@ snapshots: type-fest@4.41.0: {} - typedoc-github-theme@0.3.1(typedoc@0.28.12(typescript@5.8.3)): + typedoc-github-theme@0.3.1(typedoc@0.28.7(typescript@5.9.2)): dependencies: - typedoc: 0.28.12(typescript@5.8.3) + typedoc: 0.28.7(typescript@5.9.2) - typedoc-plugin-coverage@4.0.1(typedoc@0.28.12(typescript@5.8.3)): + typedoc-plugin-coverage@4.0.1(typedoc@0.28.7(typescript@5.9.2)): dependencies: - typedoc: 0.28.12(typescript@5.8.3) + typedoc: 0.28.7(typescript@5.9.2) - typedoc-plugin-mdn-links@5.0.9(typedoc@0.28.12(typescript@5.8.3)): + typedoc-plugin-mdn-links@5.0.9(typedoc@0.28.7(typescript@5.9.2)): dependencies: - typedoc: 0.28.12(typescript@5.8.3) + typedoc: 0.28.7(typescript@5.9.2) - typedoc@0.28.12(typescript@5.8.3): + typedoc@0.28.7(typescript@5.9.2): dependencies: '@gerrit0/mini-shiki': 3.12.2 lunr: 2.3.9 markdown-it: 14.1.0 minimatch: 9.0.5 - typescript: 5.8.3 + typescript: 5.9.2 yaml: 2.8.1 - typescript@5.8.3: {} + typescript@5.9.2: {} uc.micro@2.1.0: {} @@ -3711,7 +3730,7 @@ snapshots: debug: 4.4.1 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.0.6(@types/node@22.16.5)(yaml@2.8.1) + vite: 7.0.7(@types/node@22.16.5)(yaml@2.8.1) transitivePeerDependencies: - '@types/node' - jiti @@ -3726,40 +3745,40 @@ snapshots: - tsx - yaml - vite-tsconfig-paths@5.1.4(typescript@5.8.3)(vite@7.0.6(@types/node@22.16.5)(yaml@2.8.1)): + vite-tsconfig-paths@5.1.4(typescript@5.9.2)(vite@7.0.7(@types/node@22.16.5)(yaml@2.8.1)): dependencies: debug: 4.4.1 globrex: 0.1.2 - tsconfck: 3.1.6(typescript@5.8.3) + tsconfck: 3.1.6(typescript@5.9.2) optionalDependencies: - vite: 7.0.6(@types/node@22.16.5)(yaml@2.8.1) + vite: 7.0.7(@types/node@22.16.5)(yaml@2.8.1) transitivePeerDependencies: - supports-color - typescript - vite@7.0.6(@types/node@22.16.5)(yaml@2.8.1): + vite@7.0.7(@types/node@22.16.5)(yaml@2.8.1): dependencies: - esbuild: 0.25.8 - fdir: 6.4.6(picomatch@4.0.3) + esbuild: 0.25.9 + fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.46.1 - tinyglobby: 0.2.14 + rollup: 4.50.1 + tinyglobby: 0.2.15 optionalDependencies: '@types/node': 22.16.5 fsevents: 2.3.3 yaml: 2.8.1 - vitest-canvas-mock@0.3.3(vitest@3.2.4(@types/node@22.16.5)(jsdom@26.1.0)(msw@2.10.4(@types/node@22.16.5)(typescript@5.8.3))(yaml@2.8.1)): + vitest-canvas-mock@0.3.3(vitest@3.2.4(@types/node@22.16.5)(jsdom@26.1.0)(msw@2.10.4(@types/node@22.16.5)(typescript@5.9.2))(yaml@2.8.1)): dependencies: jest-canvas-mock: 2.5.2 - vitest: 3.2.4(@types/node@22.16.5)(jsdom@26.1.0)(msw@2.10.4(@types/node@22.16.5)(typescript@5.8.3))(yaml@2.8.1) + vitest: 3.2.4(@types/node@22.16.5)(jsdom@26.1.0)(msw@2.10.4(@types/node@22.16.5)(typescript@5.9.2))(yaml@2.8.1) - vitest@3.2.4(@types/node@22.16.5)(jsdom@26.1.0)(msw@2.10.4(@types/node@22.16.5)(typescript@5.8.3))(yaml@2.8.1): + vitest@3.2.4(@types/node@22.16.5)(jsdom@26.1.0)(msw@2.10.4(@types/node@22.16.5)(typescript@5.9.2))(yaml@2.8.1): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(msw@2.10.4(@types/node@22.16.5)(typescript@5.8.3))(vite@7.0.6(@types/node@22.16.5)(yaml@2.8.1)) + '@vitest/mocker': 3.2.4(msw@2.10.4(@types/node@22.16.5)(typescript@5.9.2))(vite@7.0.7(@types/node@22.16.5)(yaml@2.8.1)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -3777,7 +3796,7 @@ snapshots: tinyglobby: 0.2.14 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.0.6(@types/node@22.16.5)(yaml@2.8.1) + vite: 7.0.7(@types/node@22.16.5)(yaml@2.8.1) vite-node: 3.2.4(@types/node@22.16.5)(yaml@2.8.1) why-is-node-running: 2.3.0 optionalDependencies: diff --git a/public/locales b/public/locales index 090bfefaf7e..74de730a642 160000 --- a/public/locales +++ b/public/locales @@ -1 +1 @@ -Subproject commit 090bfefaf7e9d4efcbca61fa78a9cdf5d701830b +Subproject commit 74de730a64272c8e9ca0a4cdcf3426cbf1b0aeda diff --git a/scripts/create-test/boilerplates/default.ts b/scripts/create-test/boilerplates/default.boilerplate.ts similarity index 94% rename from scripts/create-test/boilerplates/default.ts rename to scripts/create-test/boilerplates/default.boilerplate.ts index e644e740594..7b633cf8276 100644 --- a/scripts/create-test/boilerplates/default.ts +++ b/scripts/create-test/boilerplates/default.boilerplate.ts @@ -47,6 +47,6 @@ describe("{{description}}", () => { await game.toEndOfTurn(); expect(feebas).toHaveUsedMove({ move: MoveId.SPLASH, result: MoveResult.SUCCESS }); - expect(game.textInterceptor.logs).toContain(i18next.t("moveTriggers:splash")); + expect(game).toHaveShownMessage(i18next.t("moveTriggers:splash")); }); }); diff --git a/scripts/create-test/create-test.js b/scripts/create-test/create-test.js index 5e395783da7..df065657346 100644 --- a/scripts/create-test/create-test.js +++ b/scripts/create-test/create-test.js @@ -102,9 +102,9 @@ async function promptFileName(selectedType) { function getBoilerplatePath(choiceType) { switch (choiceType) { // case "Reward": - // return path.join(__dirname, "boilerplates/reward.ts"); + // return path.join(__dirname, "boilerplates/reward.boilerplate.ts"); default: - return path.join(__dirname, "boilerplates/default.ts"); + return path.join(__dirname, "boilerplates/default.boilerplate.ts"); } } diff --git a/src/@types/api/pokerogue-daily-api.ts b/src/@types/api/pokerogue-daily-api.ts index 862ff2f51a3..838af2a2a34 100644 --- a/src/@types/api/pokerogue-daily-api.ts +++ b/src/@types/api/pokerogue-daily-api.ts @@ -1,4 +1,4 @@ -import type { ScoreboardCategory } from "#ui/containers/daily-run-scoreboard"; +import type { ScoreboardCategory } from "#ui/daily-run-scoreboard"; export interface GetDailyRankingsRequest { category: ScoreboardCategory; diff --git a/src/ai/ai-moveset-gen.ts b/src/ai/ai-moveset-gen.ts new file mode 100644 index 00000000000..f392ca46d3f --- /dev/null +++ b/src/ai/ai-moveset-gen.ts @@ -0,0 +1,770 @@ +import { globalScene } from "#app/global-scene"; +import { speciesEggMoves } from "#balance/egg-moves"; +import { + BASE_LEVEL_WEIGHT_OFFSET, + BASE_WEIGHT_MULTIPLIER, + BOSS_EXTRA_WEIGHT_MULTIPLIER, + COMMON_TIER_TM_LEVEL_REQUIREMENT, + COMMON_TM_MOVESET_WEIGHT, + EGG_MOVE_LEVEL_REQUIREMENT, + EGG_MOVE_TO_LEVEL_WEIGHT, + EGG_MOVE_WEIGHT_MAX, + EVOLUTION_MOVE_WEIGHT, + GREAT_TIER_TM_LEVEL_REQUIREMENT, + GREAT_TM_MOVESET_WEIGHT, + getMaxEggMoveCount, + getMaxTmCount, + RARE_EGG_MOVE_LEVEL_REQUIREMENT, + STAB_BLACKLIST, + ULTRA_TIER_TM_LEVEL_REQUIREMENT, + ULTRA_TM_MOVESET_WEIGHT, +} from "#balance/moveset-generation"; +import { EVOLVE_MOVE, RELEARN_MOVE } from "#balance/pokemon-level-moves"; +import { speciesTmMoves, tmPoolTiers } from "#balance/tms"; +import { allMoves } from "#data/data-lists"; +import { ModifierTier } from "#enums/modifier-tier"; +import { MoveCategory } from "#enums/move-category"; +import type { MoveId } from "#enums/move-id"; +import { PokemonType } from "#enums/pokemon-type"; +import type { SpeciesId } from "#enums/species-id"; +import { Stat } from "#enums/stat"; +import type { EnemyPokemon, Pokemon } from "#field/pokemon"; +import { PokemonMove } from "#moves/pokemon-move"; +import { NumberHolder, randSeedInt } from "#utils/common"; +import { isBeta } from "#utils/utility-vars"; + +/** + * Compute and assign a weight to the level-up moves currently available to the Pokémon + * + * @param pokemon - The Pokémon to generate a level-based move pool for + * @returns A map of move IDs to their computed weights + * + * @remarks + * A move's weight is determined by its level, as follows: + * 1. If the level is an {@linkcode EVOLVE_MOVE} move, weight is 60 + * 2. If it is level 1 with 80+ BP, it is considered a "move reminder" move and + * weight is 60 + * 3. If the Pokémon has a trainer and the move is a {@linkcode RELEARN_MOVE}, + * weight is 60 + * 4. Otherwise, weight is the earliest level the move can be learned + 20 + */ +function getAndWeightLevelMoves(pokemon: Pokemon): Map { + const movePool = new Map(); + let allLevelMoves: [number, MoveId][]; + // TODO: Investigate why there needs to be error handling here + try { + allLevelMoves = pokemon.getLevelMoves(1, true, true, pokemon.hasTrainer()); + } catch (e) { + console.warn("Error encountered trying to generate moveset for %s: %s", pokemon.species.name, e); + return movePool; + } + + const level = pokemon.level; + const hasTrainer = pokemon.hasTrainer(); + + for (const levelMove of allLevelMoves) { + const [learnLevel, id] = levelMove; + if (level < learnLevel) { + break; + } + const move = allMoves[id]; + // Skip unimplemented moves or moves that are already in the pool + if (move.name.endsWith(" (N)") || movePool.has(id)) { + continue; + } + + let weight = learnLevel + BASE_LEVEL_WEIGHT_OFFSET; + switch (learnLevel) { + case EVOLVE_MOVE: + weight = EVOLUTION_MOVE_WEIGHT; + break; + // Assume level 1 moves with 80+ BP are "move reminder" moves and bump their weight. Trainers use actual relearn moves. + case 1: + if (move.power >= 80) { + weight = 60; + } + break; + case RELEARN_MOVE: + if (hasTrainer) { + weight = 60; + } + } + + movePool.set(id, weight); + } + + return movePool; +} + +/** + * Determine which TM tiers a Pokémon can learn based on its level + * @param level - The level of the Pokémon + * @returns A tuple indicating whether the Pokémon can learn common, great, and ultra tier TMs + */ +function getAllowedTmTiers(level: number): [common: boolean, great: boolean, ultra: boolean] { + return [ + level >= COMMON_TIER_TM_LEVEL_REQUIREMENT, + level >= GREAT_TIER_TM_LEVEL_REQUIREMENT, + level >= ULTRA_TIER_TM_LEVEL_REQUIREMENT, + ]; +} + +/** + * Get the TMs that a species can learn based on its ID and formKey + * @param speciesId - The species ID of the Pokémon + * @param level - The level of the Pokémon + * @param formKey - The form key of the Pokémon + * @param levelPool - The current level-based move pool, to avoid duplicates + * @param tmPool - The TM move pool to add to, which will be modified in place + * @param allowedTiers - The tiers of TMs the Pokémon is allowed to learn + * + * @privateRemarks + * Split out from `getAndWeightTmMoves` to allow fusion species to add their TMs + * without duplicating code. + */ +function getTmPoolForSpecies( + speciesId: number, + level: number, + formKey: string, + levelPool: ReadonlyMap, + eggPool: ReadonlyMap, + tmPool: Map, + allowedTiers = getAllowedTmTiers(level), +): void { + const [allowCommon, allowGreat, allowUltra] = allowedTiers; + const tms = speciesTmMoves[speciesId]; + // Species with no learnable TMs (e.g. Ditto) don't have entries in the `speciesTmMoves` object, + // so this is needed to avoid iterating over `undefined` + if (tms == null) { + return; + } + + let moveId: MoveId; + for (const tm of tms) { + if (Array.isArray(tm)) { + if (tm[0] !== formKey) { + continue; + } + moveId = tm[1]; + } else { + moveId = tm; + } + + if (levelPool.has(moveId) || eggPool.has(moveId) || tmPool.has(moveId)) { + continue; + } + switch (tmPoolTiers[moveId]) { + case ModifierTier.COMMON: + allowCommon && tmPool.set(moveId, COMMON_TM_MOVESET_WEIGHT); + break; + case ModifierTier.GREAT: + allowGreat && tmPool.set(moveId, GREAT_TM_MOVESET_WEIGHT); + break; + case ModifierTier.ULTRA: + allowUltra && tmPool.set(moveId, ULTRA_TM_MOVESET_WEIGHT); + break; + } + } +} + +/** + * Compute and assign a weight to the TM moves currently available to the Pokémon + * @param pokemon - The Pokémon to generate a TM-based move pool for + * @param currentSet - The current movepool, to avoid duplicates + * @param tmPool - The TM move pool to add to, which will be modified in place + * @returns A map of move IDs to their computed weights + * + * @remarks + * Only trainer pokemon can learn TM moves, and there are restrictions + * as to how many and which TMs are available based on the level of the Pokémon. + * 1. Before level 25, no TM moves are available + * 2. Between levels 25 and 40, only COMMON tier TMs are available, + */ +function getAndWeightTmMoves( + pokemon: Pokemon, + currentPool: ReadonlyMap, + eggPool: ReadonlyMap, + tmPool: Map, +): void { + const level = pokemon.level; + const allowedTiers = getAllowedTmTiers(level); + if (!allowedTiers.includes(true)) { + return; + } + + const form = pokemon.species.forms[pokemon.formIndex]?.formKey ?? ""; + getTmPoolForSpecies(pokemon.species.speciesId, level, form, currentPool, eggPool, tmPool, allowedTiers); + const fusionFormKey = pokemon.getFusionFormKey(); + const fusionSpecies = pokemon.fusionSpecies?.speciesId; + if (fusionSpecies != null && fusionFormKey != null && fusionFormKey !== "") { + getTmPoolForSpecies(fusionSpecies, level, fusionFormKey, currentPool, eggPool, tmPool, allowedTiers); + } +} + +/** + * Get the weight multiplier for an egg move + * @param levelPool - Map of level up moves to their weights + * @param level - The level of the Pokémon + * @param forRare - Whether this is for a rare egg move + * @param isBoss - Whether the Pokémon having the egg move generated is a boss Pokémon + */ +export function getEggMoveWeight( + // biome-ignore-start lint/correctness/noUnusedFunctionParameters: Saved to allow this algorithm to be tweaked easily without adjusting signatures + levelPool: ReadonlyMap, + level: number, + forRare: boolean, + isBoss: boolean, + // biome-ignore-end lint/correctness/noUnusedFunctionParameters: Endrange +): number { + const levelUpWeightedEggMoveWeight = Math.round(Math.max(...levelPool.values()) * EGG_MOVE_TO_LEVEL_WEIGHT); + // Rare egg moves are always weighted at 5/6 the weight of normal egg moves + return Math.min(levelUpWeightedEggMoveWeight, EGG_MOVE_WEIGHT_MAX) * (forRare ? 5 / 6 : 1); +} + +/** + * Submethod of {@linkcode getAndWeightEggMoves} that adds egg moves for a specific species to the egg move pool + * + * @param rootSpeciesId - The ID of the root species for which to generate the egg move pool. + * @param levelPool - A readonly map of move IDs to their levels, representing moves already learned by leveling up. + * @param eggPool - A map to be populated with egg move IDs and their corresponding weights. + * @param eggMoveWeight - The default weight to assign to regular egg moves. + * @param excludeRare - If true, excludes rare egg moves + * @param rareEggMoveWeight - The weight to assign to rare egg moves; default 0 + * + * @privateRemarks + * Split from `getAndWeightEggMoves` to allow fusion species to add their egg moves without duplicating code. + * + * @remarks + * - Moves present in `levelPool` are excluded from the egg pool. + * - If `excludeRare` is true, rare egg moves (at index 3) are skipped. + * - Rare egg moves are assigned `rareEggMoveWeight`, while others receive `eggMoveWeight`. + */ +function getEggPoolForSpecies( + rootSpeciesId: SpeciesId, + levelPool: ReadonlyMap, + eggPool: Map, + eggMoveWeight: number, + excludeRare: boolean, + rareEggMoveWeight = 0, +): void { + const eggMoves = speciesEggMoves[rootSpeciesId]; + if (eggMoves == null) { + return; + } + for (const [idx, moveId] of eggMoves.entries()) { + if (levelPool.has(moveId) || (idx === 3 && excludeRare)) { + continue; + } + eggPool.set(Math.max(moveId, eggPool.get(moveId) ?? 0), idx === 3 ? rareEggMoveWeight : eggMoveWeight); + } +} + +/** + * Compute and assign a weight to the egg moves currently available to the Pokémon + * @param pokemon - The Pokémon to generate egg moves for + * @param levelPool - The map of level-based moves to their weights + * @param eggPool - A map of move IDs to their weights for egg moves that will be modified in place + * + * @remarks + * This function checks if the Pokémon meets the requirements to learn egg moves, + * and if allowed, calculates the weights for regular and rare egg moves using the provided pools. + */ +function getAndWeightEggMoves( + pokemon: Pokemon, + levelPool: ReadonlyMap, + eggPool: Map, +): void { + const level = pokemon.level; + if (level < EGG_MOVE_LEVEL_REQUIREMENT || !globalScene.currentBattle?.trainer?.config.allowEggMoves) { + return; + } + const isBoss = pokemon.isBoss(); + const excludeRare = isBoss || level < RARE_EGG_MOVE_LEVEL_REQUIREMENT; + const eggMoveWeight = getEggMoveWeight(levelPool, level, false, isBoss); + let rareEggMoveWeight: number | undefined; + if (!excludeRare) { + rareEggMoveWeight = getEggMoveWeight(levelPool, level, true, isBoss); + } + getEggPoolForSpecies( + pokemon.species.getRootSpeciesId(), + levelPool, + eggPool, + eggMoveWeight, + excludeRare, + rareEggMoveWeight, + ); + + const fusionSpecies = pokemon.fusionSpecies?.getRootSpeciesId(); + if (fusionSpecies != null) { + getEggPoolForSpecies(fusionSpecies, levelPool, eggPool, eggMoveWeight, excludeRare, rareEggMoveWeight); + } +} + +/** + * Filter a move pool, removing moves that are not allowed based on conditions + * @param pool - The move pool to filter + * @param isBoss - Whether the Pokémon is a boss + * @param hasTrainer - Whether the Pokémon has a trainer + */ +function filterMovePool(pool: Map, isBoss: boolean, hasTrainer: boolean): void { + for (const [moveId, weight] of pool) { + if (weight <= 0) { + pool.delete(moveId); + continue; + } + const move = allMoves[moveId]; + // Forbid unimplemented moves + if (move.name.endsWith(" (N)")) { + pool.delete(moveId); + continue; + } + // Bosses never get self ko moves or Pain Split + if (isBoss && (move.hasAttr("SacrificialAttr") || move.hasAttr("HpSplitAttr"))) { + pool.delete(moveId); + } + + // No one gets Memento or Final Gambit + if (move.hasAttr("SacrificialAttrOnHit")) { + pool.delete(moveId); + continue; + } + + // Trainers never get OHKO moves + if (hasTrainer && move.hasAttr("OneHitKOAttr")) { + pool.delete(moveId); + } + } +} + +/** + * Perform Trainer-specific adjustments to move weights in a move pool + * @param pool - The move pool to adjust + */ +function adjustWeightsForTrainer(pool: Map): void { + for (const [moveId, weight] of pool.entries()) { + const move = allMoves[moveId]; + let adjustedWeight = weight; + // Half the weight of self KO moves on trainers + adjustedWeight *= move.hasAttr("SacrificialAttr") ? 0.5 : 1; + + // Trainers get a weight bump to stat buffing moves + adjustedWeight *= move.getAttrs("StatStageChangeAttr").some(a => a.stages > 1 && a.selfTarget) ? 1.25 : 1; + + // Trainers get a weight decrease to multiturn moves + adjustedWeight *= !!move.isChargingMove() || !!move.hasAttr("RechargeAttr") ? 0.7 : 1; + if (adjustedWeight !== weight) { + pool.set(moveId, adjustedWeight); + } + } +} + +/** + * Adjust weights of damaging moves in a move pool based on their power and category + * + * @param pool - The move pool to adjust + * @param pokemon - The Pokémon for which the moveset is being generated + * @param willTera - Whether the Pokémon is expected to Tera (i.e., has instant Tera on a Trainer Pokémon); default `false` + * @remarks + * Caps max power at 90 to avoid something like hyper beam ruining the stats. + * pokemon is a pretty soft weighting factor, although it is scaled with the weight multiplier. + */ +function adjustDamageMoveWeights(pool: Map, pokemon: Pokemon, willTera = false): void { + // begin max power at 40 to avoid inflating weights too much when there are only low power moves + let maxPower = 40; + for (const moveId of pool.keys()) { + const move = allMoves[moveId]; + maxPower = Math.max(maxPower, move.calculateEffectivePower()); + if (maxPower >= 90) { + maxPower = 90; + break; + } + } + + const atk = pokemon.getStat(Stat.ATK); + const spAtk = pokemon.getStat(Stat.SPATK); + const lowerStat = Math.min(atk, spAtk); + const higherStat = Math.max(atk, spAtk); + const worseCategory = atk > spAtk ? MoveCategory.SPECIAL : MoveCategory.PHYSICAL; + const statRatio = lowerStat / higherStat; + const adjustmentRatio = Math.min(Math.pow(statRatio, 3) * 1.3, 1); + + for (const [moveId, weight] of pool) { + const move = allMoves[moveId]; + let adjustedWeight = weight; + if (move.category === MoveCategory.STATUS) { + continue; + } + // Scale weight based on their ratio to the highest power move, capping at 50% reduction + adjustedWeight *= Math.max(Math.min(move.calculateEffectivePower() / maxPower, 1), 0.5); + + // Scale weight based the stat it uses to deal damage, based on the ratio between said stat + // and the higher stat + if (move.hasAttr("DefAtkAttr")) { + const def = pokemon.getStat(Stat.DEF); + const defRatio = def / higherStat; + const defAdjustRatio = Math.min(Math.pow(defRatio, 3) * 1.3, 1.1); + adjustedWeight *= defAdjustRatio; + } else if ( + move.category === worseCategory + && !move.hasAttr("PhotonGeyserCategoryAttr") + && !move.hasAttr("ShellSideArmCategoryAttr") + && !(move.hasAttr("TeraMoveCategoryAttr") && willTera) + ) { + // Raw multiply each move's category by the stat it uses to deal damage + // moves that always use the higher offensive stat are left unadjusted + adjustedWeight *= adjustmentRatio; + } + + if (adjustedWeight !== weight) { + pool.set(moveId, adjustedWeight); + } + } +} + +/** + * Calculate the total weight of all moves in a move pool + * @param pool - The move pool to calculate the total weight for + * @returns The total weight of all moves in the pool + */ +function calculateTotalPoolWeight(pool: Map): number { + let totalWeight = 0; + for (const weight of pool.values()) { + totalWeight += weight; + } + return totalWeight; +} + +/** + * Filter a pool and return a new array of moves that pass the predicate + * @param pool - The move pool to filter + * @param predicate - The predicate function to determine if a move should be included + * @param totalWeight - An output parameter to hold the total weight of the filtered pool. Its value is reset to 0 if provided. + * @returns An array of move ID and weight tuples that pass the predicate + */ +function filterPool( + pool: ReadonlyMap, + predicate: (moveId: MoveId) => boolean, + totalWeight?: NumberHolder, +): [id: MoveId, weight: number][] { + let hasTotalWeight = false; + if (totalWeight != null) { + totalWeight.value = 0; + hasTotalWeight = true; + } + const newPool: [id: MoveId, weight: number][] = []; + for (const [moveId, weight] of pool) { + if (predicate(moveId)) { + newPool.push([moveId, weight]); + if (hasTotalWeight) { + // Bang is safe here because we set `hasTotalWeight` in the if check above + totalWeight!.value += weight; + } + } + } + + return newPool; +} + +/** + * Forcibly add a STAB move to the Pokémon's moveset from the provided pools + * + * @remarks + * If no STAB move is available, add any damaging move. + * If no damaging move is available, no move is added + * @param pool - The master move pool + * @param tmPool - The TM move pool + * @param eggPool - The egg move pool + * @param pokemon - The Pokémon for which the moveset is being generated + * @param tmCount - A holder for the count of TM moves selected + * @param eggMoveCount - A holder for the count of egg moves selected + * @param willTera - Whether the Pokémon is expected to Tera (i.e., has instant Tera on a Trainer Pokémon); default `false` + * @param forceAnyDamageIfNoStab - If true, will force any damaging move if no STAB move is available + */ +// biome-ignore lint/nursery/useMaxParams: This is a complex function that needs all these parameters +function forceStabMove( + pool: Map, + tmPool: Map, + eggPool: Map, + pokemon: Pokemon, + tmCount: NumberHolder, + eggMoveCount: NumberHolder, + willTera = false, + forceAnyDamageIfNoStab = false, +): void { + // All Pokemon force a STAB move first + const totalWeight = new NumberHolder(0); + const stabMovePool = filterPool( + pool, + moveId => { + const move = allMoves[moveId]; + return ( + move.category !== MoveCategory.STATUS + && (pokemon.isOfType(move.type) + || (willTera && move.hasAttr("TeraBlastTypeAttr") && pokemon.getTeraType() !== PokemonType.STELLAR)) + && !STAB_BLACKLIST.has(moveId) + ); + }, + totalWeight, + ); + + const chosenPool = + stabMovePool.length > 0 || !forceAnyDamageIfNoStab + ? stabMovePool + : filterPool( + pool, + m => allMoves[m[0]].category !== MoveCategory.STATUS && !STAB_BLACKLIST.has(m[0]), + totalWeight, + ); + + if (chosenPool.length > 0) { + let rand = randSeedInt(totalWeight.value); + let index = 0; + while (rand > chosenPool[index][1]) { + rand -= chosenPool[index++][1]; + } + const selectedId = chosenPool[index][0]; + pool.delete(selectedId); + if (tmPool.has(selectedId)) { + tmPool.delete(selectedId); + tmCount.value++; + } else if (eggPool.has(selectedId)) { + eggPool.delete(selectedId); + eggMoveCount.value++; + } + pokemon.moveset.push(new PokemonMove(selectedId)); + } +} + +/** + * Adjust weights in the remaining move pool based on existing moves in the Pokémon's moveset + * + * @remarks + * Submethod for step 5 of moveset generation + * @param pool - The move pool to filter + * @param pokemon - The Pokémon for which the moveset is being generated + */ +function filterRemainingTrainerMovePool(pool: [id: MoveId, weight: number][], pokemon: Pokemon) { + // Sqrt the weight of any damaging moves with overlapping types. pokemon is about a 0.05 - 0.1 multiplier. + // Other damaging moves 2x weight if 0-1 damaging moves, 0.5x if 2, 0.125x if 3. These weights get 20x if STAB. + // Status moves remain unchanged on weight, pokemon encourages 1-2 + for (const [idx, [moveId, weight]] of pool.entries()) { + let ret: number; + if ( + pokemon.moveset.some( + mo => mo.getMove().category !== MoveCategory.STATUS && mo.getMove().type === allMoves[moveId].type, + ) + ) { + ret = Math.ceil(Math.sqrt(weight)); + } else if (allMoves[moveId].category !== MoveCategory.STATUS) { + ret = Math.ceil( + (weight / Math.max(Math.pow(4, pokemon.moveset.filter(mo => (mo.getMove().power ?? 0) > 1).length) / 8, 0.5)) + * (pokemon.isOfType(allMoves[moveId].type) && !STAB_BLACKLIST.has(moveId) ? 20 : 1), + ); + } else { + ret = weight; + } + pool[idx] = [moveId, ret]; + } +} + +/** + * Fill in the remaining slots in the Pokémon's moveset from the provided pools + * @param pokemon - The Pokémon for which the moveset is being generated + * @param tmPool - The TM move pool + * @param eggMovePool - The egg move pool + * @param tmCount - A holder for the count of moves that have been added to the moveset from TMs + * @param eggMoveCount - A holder for the count of moves that have been added to the moveset from egg moves + * @param baseWeights - The base weights of all moves in the master pool + * @param remainingPool - The remaining move pool to select from + */ +function fillInRemainingMovesetSlots( + pokemon: Pokemon, + tmPool: Map, + eggMovePool: Map, + tmCount: NumberHolder, + eggMoveCount: NumberHolder, + baseWeights: Map, + remainingPool: [id: MoveId, weight: number][], +): void { + const tmCap = getMaxTmCount(pokemon.level); + const eggCap = getMaxEggMoveCount(pokemon.level); + const remainingPoolWeight = new NumberHolder(0); + while (remainingPool.length > pokemon.moveset.length && pokemon.moveset.length < 4) { + const nonLevelMoveCount = tmCount.value + eggMoveCount.value; + remainingPool = filterPool( + baseWeights, + (m: MoveId) => + !pokemon.moveset.some( + mo => + m === mo.moveId || (allMoves[m]?.hasAttr("SacrificialAttr") && mo.getMove()?.hasAttr("SacrificialAttr")), // Only one self-KO move allowed + ) + && (nonLevelMoveCount < tmCap || !tmPool.has(m)) + && (nonLevelMoveCount < eggCap || !eggMovePool.has(m)), + remainingPoolWeight, + ); + if (pokemon.hasTrainer()) { + filterRemainingTrainerMovePool(remainingPool, pokemon); + } + const totalWeight = remainingPool.reduce((v, m) => v + m[1], 0); + let rand = randSeedInt(totalWeight); + let index = 0; + while (rand > remainingPool[index][1]) { + rand -= remainingPool[index++][1]; + } + const selectedMoveId = remainingPool[index][0]; + baseWeights.delete(selectedMoveId); + if (tmPool.has(selectedMoveId)) { + tmCount.value++; + tmPool.delete(selectedMoveId); + } else if (eggMovePool.has(selectedMoveId)) { + eggMoveCount.value++; + eggMovePool.delete(selectedMoveId); + } + pokemon.moveset.push(new PokemonMove(selectedMoveId)); + } +} + +/** + * Debugging function to log computed move weights for a Pokémon + * @param pokemon - The Pokémon for which the move weights were computed + * @param pool - The move pool containing move IDs and their weights + * @param note - Short note to include in the log for context + */ +function debugMoveWeights(pokemon: Pokemon, pool: Map, note: string): void { + if ((isBeta || import.meta.env.DEV) && import.meta.env.NODE_ENV !== "test") { + const moveNameToWeightMap = new Map(); + const sortedByValue = Array.from(pool.entries()).sort((a, b) => b[1] - a[1]); + for (const [moveId, weight] of sortedByValue) { + moveNameToWeightMap.set(allMoves[moveId].name, weight); + } + console.log("%cComputed move weights [%s] for %s", "color: blue", note, pokemon.name, moveNameToWeightMap); + } +} + +/** + * Generate a moveset for a given Pokémon based on its level, types, stats, and whether it is wild or a trainer's Pokémon. + * @param pokemon - The Pokémon to generate a moveset for + * @returns A reference to the Pokémon's moveset array + */ +export function generateMoveset(pokemon: Pokemon): void { + pokemon.moveset = []; + // Step 1: Generate the pools from various sources: level up, egg moves, and TMs + const learnPool = getAndWeightLevelMoves(pokemon); + debugMoveWeights(pokemon, learnPool, "Initial Level Moves"); + const hasTrainer = pokemon.hasTrainer(); + const tmPool = new Map(); + const eggMovePool = new Map(); + + if (hasTrainer) { + getAndWeightEggMoves(pokemon, learnPool, eggMovePool); + eggMovePool.size > 0 && debugMoveWeights(pokemon, eggMovePool, "Initial Egg Moves"); + getAndWeightTmMoves(pokemon, learnPool, eggMovePool, tmPool); + tmPool.size > 0 && debugMoveWeights(pokemon, tmPool, "Initial Tm Moves"); + } + + // Now, combine pools into one master pool. + // The pools are kept around so we know where the move was sourced from + const movePool = new Map([...tmPool.entries(), ...eggMovePool.entries(), ...learnPool.entries()]); + + // Step 2: Filter out forbidden moves + const isBoss = pokemon.isBoss(); + filterMovePool(movePool, isBoss, hasTrainer); + + // Step 3: Adjust weights for trainers + if (hasTrainer) { + adjustWeightsForTrainer(movePool); + } + + /** Determine whether this pokemon will instantly tera */ + const willTera = + hasTrainer + && globalScene.currentBattle?.trainer?.config.trainerAI.instantTeras.includes( + // The cast to EnemyPokemon is safe; includes will just return false if the property doesn't exist + (pokemon as EnemyPokemon).initialTeamIndex, + ); + + adjustDamageMoveWeights(movePool, pokemon, willTera); + + /** The higher this is, the greater the impact of weight. At `0` all moves are equal weight. */ + let weightMultiplier = BASE_WEIGHT_MULTIPLIER; + if (isBoss) { + weightMultiplier += BOSS_EXTRA_WEIGHT_MULTIPLIER; + } + + const baseWeights = new Map(movePool); + for (const [moveId, weight] of baseWeights) { + if (weight <= 0) { + baseWeights.delete(moveId); + continue; + } + baseWeights.set(moveId, Math.ceil(Math.pow(weight, weightMultiplier) * 100)); + } + + const tmCount = new NumberHolder(0); + const eggMoveCount = new NumberHolder(0); + + debugMoveWeights(pokemon, baseWeights, "Pre STAB Move"); + + // Step 4: Force a STAB move if possible + forceStabMove(movePool, tmPool, eggMovePool, pokemon, tmCount, eggMoveCount, willTera); + // Note: To force a secondary stab, call this a second time, and pass `false` for the last parameter + // Would also tweak the function to not consider moves already in the moveset + // e.g. forceStabMove(..., false); + + // Step 5: Fill in remaining slots + fillInRemainingMovesetSlots( + pokemon, + tmPool, + eggMovePool, + tmCount, + eggMoveCount, + baseWeights, + filterPool(baseWeights, (m: MoveId) => !pokemon.moveset.some(mo => m[0] === mo.moveId)), + ); +} + +/** + * Exports for internal testing purposes. + * ⚠️ These *must not* be used outside of tests, as they will not be defined. + * @internal + */ +export const __INTERNAL_TEST_EXPORTS: { + getAndWeightLevelMoves: typeof getAndWeightLevelMoves; + getAllowedTmTiers: typeof getAllowedTmTiers; + getTmPoolForSpecies: typeof getTmPoolForSpecies; + getAndWeightTmMoves: typeof getAndWeightTmMoves; + getEggMoveWeight: typeof getEggMoveWeight; + getEggPoolForSpecies: typeof getEggPoolForSpecies; + getAndWeightEggMoves: typeof getAndWeightEggMoves; + filterMovePool: typeof filterMovePool; + adjustWeightsForTrainer: typeof adjustWeightsForTrainer; + adjustDamageMoveWeights: typeof adjustDamageMoveWeights; + calculateTotalPoolWeight: typeof calculateTotalPoolWeight; + filterPool: typeof filterPool; + forceStabMove: typeof forceStabMove; + filterRemainingTrainerMovePool: typeof filterRemainingTrainerMovePool; + fillInRemainingMovesetSlots: typeof fillInRemainingMovesetSlots; +} = {} as any; + +// We can't use `import.meta.vitest` here, because this would not be set +// until the tests themselves begin to run, which is after imports +// So we rely on NODE_ENV being test instead +if (import.meta.env.NODE_ENV === "test") { + Object.assign(__INTERNAL_TEST_EXPORTS, { + getAndWeightLevelMoves, + getAllowedTmTiers, + getTmPoolForSpecies, + getAndWeightTmMoves, + getEggMoveWeight, + getEggPoolForSpecies, + getAndWeightEggMoves, + filterMovePool, + adjustWeightsForTrainer, + adjustDamageMoveWeights, + calculateTotalPoolWeight, + filterPool, + forceStabMove, + filterRemainingTrainerMovePool, + fillInRemainingMovesetSlots, + }); +} diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 50868e94c47..cbda368782e 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -121,13 +121,13 @@ import { vouchers } from "#system/voucher"; import { trainerConfigs } from "#trainers/trainer-config"; import type { HeldModifierConfig } from "#types/held-modifier-config"; import type { Localizable } from "#types/locales"; -import { AbilityBar } from "#ui/containers/ability-bar"; -import { ArenaFlyout } from "#ui/containers/arena-flyout"; -import { CandyBar } from "#ui/containers/candy-bar"; -import { CharSprite } from "#ui/containers/char-sprite"; -import { PartyExpBar } from "#ui/containers/party-exp-bar"; -import { PokeballTray } from "#ui/containers/pokeball-tray"; -import { PokemonInfoContainer } from "#ui/containers/pokemon-info-container"; +import { AbilityBar } from "#ui/ability-bar"; +import { ArenaFlyout } from "#ui/arena-flyout"; +import { CandyBar } from "#ui/candy-bar"; +import { CharSprite } from "#ui/char-sprite"; +import { PartyExpBar } from "#ui/party-exp-bar"; +import { PokeballTray } from "#ui/pokeball-tray"; +import { PokemonInfoContainer } from "#ui/pokemon-info-container"; import { addTextObject, getTextColor } from "#ui/text"; import { UI } from "#ui/ui"; import { addUiThemeOverrides } from "#ui/ui-theme"; @@ -138,7 +138,6 @@ import { formatMoney, getIvsFromId, isBetween, - isNullOrUndefined, NumberHolder, randomString, randSeedInt, @@ -859,20 +858,21 @@ export class BattleScene extends SceneBase { } /** - * Return the {@linkcode Pokemon} associated with a given ID. - * @param pokemonId - The ID whose Pokemon will be retrieved. - * @returns The {@linkcode Pokemon} associated with the given id. - * Returns `null` if the ID is `undefined` or not present in either party. - * @todo Change the `null` to `undefined` and update callers' signatures - - * this is weird and causes a lot of random jank + * Return the {@linkcode Pokemon} associated with the given ID. + * @param pokemonId - The PID whose Pokemon will be retrieved + * @returns The `Pokemon` associated with the given ID, + * or `undefined` if none is found in either team's party. + * @see {@linkcode Pokemon.id} + * @todo `pokemonId` should not allow `undefined` */ - getPokemonById(pokemonId: number | undefined): Pokemon | null { - if (isNullOrUndefined(pokemonId)) { - return null; + public getPokemonById(pokemonId: number | undefined): Pokemon | undefined { + if (pokemonId == null) { + // biome-ignore lint/nursery/noUselessUndefined: More explicit + return undefined; } const party = (this.getPlayerParty() as Pokemon[]).concat(this.getEnemyParty()); - return party.find(p => p.id === pokemonId) ?? null; + return party.find(p => p.id === pokemonId); } addPlayerPokemon( @@ -1319,7 +1319,7 @@ export class BattleScene extends SceneBase { if ( !this.gameMode.hasTrainers || Overrides.BATTLE_TYPE_OVERRIDE === BattleType.WILD - || (Overrides.DISABLE_STANDARD_TRAINERS_OVERRIDE && isNullOrUndefined(trainerData)) + || (Overrides.DISABLE_STANDARD_TRAINERS_OVERRIDE && trainerData == null) ) { newBattleType = BattleType.WILD; } else { @@ -1332,13 +1332,12 @@ export class BattleScene extends SceneBase { if (newBattleType === BattleType.TRAINER) { const trainerType = Overrides.RANDOM_TRAINER_OVERRIDE?.trainerType ?? this.arena.randomTrainerType(newWaveIndex); + const hasDouble = trainerConfigs[trainerType].hasDouble; let doubleTrainer = false; if (trainerConfigs[trainerType].doubleOnly) { doubleTrainer = true; - } else if (trainerConfigs[trainerType].hasDouble) { - doubleTrainer = - Overrides.RANDOM_TRAINER_OVERRIDE?.alwaysDouble - || !randSeedInt(this.getDoubleBattleChance(newWaveIndex, playerField)); + } else if (hasDouble) { + doubleTrainer = !randSeedInt(this.getDoubleBattleChance(newWaveIndex, playerField)); // Add a check that special trainers can't be double except for tate and liza - they should use the normal double chance if ( trainerConfigs[trainerType].trainerTypeDouble @@ -1347,11 +1346,19 @@ export class BattleScene extends SceneBase { doubleTrainer = false; } } - const variant = doubleTrainer - ? TrainerVariant.DOUBLE - : randSeedInt(2) - ? TrainerVariant.FEMALE - : TrainerVariant.DEFAULT; + + // Forcing a double battle on wave 1 causes a bug where only one enemy is sent out, + // making it impossible to complete the fight without a reload + const overrideVariant = + Overrides.RANDOM_TRAINER_OVERRIDE?.trainerVariant === TrainerVariant.DOUBLE + && (!hasDouble || newWaveIndex <= 1) + ? TrainerVariant.DEFAULT + : Overrides.RANDOM_TRAINER_OVERRIDE?.trainerVariant; + + const variant = + overrideVariant + ?? (doubleTrainer ? TrainerVariant.DOUBLE : randSeedInt(2) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT); + newTrainer = trainerData !== undefined ? trainerData.toTrainer() : new Trainer(trainerType, variant); this.field.add(newTrainer); } @@ -1383,7 +1390,7 @@ export class BattleScene extends SceneBase { newDouble = false; } - if (!isNullOrUndefined(Overrides.BATTLE_STYLE_OVERRIDE)) { + if (Overrides.BATTLE_STYLE_OVERRIDE != null) { let doubleOverrideForWave: "single" | "double" | null = null; switch (Overrides.BATTLE_STYLE_OVERRIDE) { @@ -1572,7 +1579,7 @@ export class BattleScene extends SceneBase { // Give trainers with specialty types an appropriately-typed form for Wormadam, Rotom, Arceus, Oricorio, Silvally, or Paldean Tauros. !isEggPhase && this.currentBattle?.battleType === BattleType.TRAINER - && !isNullOrUndefined(this.currentBattle.trainer) + && this.currentBattle.trainer != null && this.currentBattle.trainer.config.hasSpecialtyType() ) { if (species.speciesId === SpeciesId.WORMADAM) { @@ -2692,7 +2699,7 @@ export class BattleScene extends SceneBase { } } else if (modifier instanceof FusePokemonModifier) { args.push(this.getPokemonById(modifier.fusePokemonId) as PlayerPokemon); - } else if (modifier instanceof RememberMoveModifier && !isNullOrUndefined(cost)) { + } else if (modifier instanceof RememberMoveModifier && cost != null) { args.push(cost); } @@ -3007,7 +3014,7 @@ export class BattleScene extends SceneBase { } if ( modifier instanceof PokemonHeldItemModifier - && !isNullOrUndefined(modifier.getSpecies()) + && modifier.getSpecies() != null && !this.getPokemonById(modifier.pokemonId)?.hasSpecies(modifier.getSpecies()!) ) { modifiers.splice(m--, 1); @@ -3573,7 +3580,7 @@ export class BattleScene extends SceneBase { // Loading override or session encounter let encounter: MysteryEncounter | null; if ( - !isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_OVERRIDE) + Overrides.MYSTERY_ENCOUNTER_OVERRIDE != null && allMysteryEncounters.hasOwnProperty(Overrides.MYSTERY_ENCOUNTER_OVERRIDE) ) { encounter = allMysteryEncounters[Overrides.MYSTERY_ENCOUNTER_OVERRIDE]; @@ -3584,7 +3591,7 @@ export class BattleScene extends SceneBase { encounter = allMysteryEncounters[encounterType ?? -1]; return encounter; } else { - encounter = !isNullOrUndefined(encounterType) ? allMysteryEncounters[encounterType] : null; + encounter = encounterType != null ? allMysteryEncounters[encounterType] : null; } // Check for queued encounters first @@ -3643,7 +3650,7 @@ export class BattleScene extends SceneBase { ? MysteryEncounterTier.ULTRA : MysteryEncounterTier.ROGUE; - if (!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_TIER_OVERRIDE)) { + if (Overrides.MYSTERY_ENCOUNTER_TIER_OVERRIDE != null) { tier = Overrides.MYSTERY_ENCOUNTER_TIER_OVERRIDE; } diff --git a/src/constants/colors.ts b/src/constants/colors.ts index 717c5fa5f0d..a2400ef5f90 100644 --- a/src/constants/colors.ts +++ b/src/constants/colors.ts @@ -1,7 +1,8 @@ /** - * @module + * * A big file storing colors used in logging. * Minified by Terser during production builds, so has no overhead. + * @module */ // Colors used in prod diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index e12f5454a34..d8460984c99 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -67,7 +67,6 @@ import type { Constructor } from "#utils/common"; import { BooleanHolder, coerceArray, - isNullOrUndefined, NumberHolder, randSeedFloat, randSeedInt, @@ -1040,7 +1039,7 @@ export class PostDefendStatStageChangeAbAttr extends PostDefendAbAttr { if (this.allOthers) { const ally = pokemon.getAlly(); - const otherPokemon = !isNullOrUndefined(ally) ? pokemon.getOpponents().concat([ally]) : pokemon.getOpponents(); + const otherPokemon = ally != null ? pokemon.getOpponents().concat([ally]) : pokemon.getOpponents(); for (const other of otherPokemon) { globalScene.phaseManager.unshiftNew( "StatStageChangePhase", @@ -1470,7 +1469,7 @@ export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr { override canApply({ move, opponent: attacker, pokemon }: PostMoveInteractionAbAttrParams): boolean { return ( - isNullOrUndefined(attacker.getTag(BattlerTagType.DISABLED)) + attacker.getTag(BattlerTagType.DISABLED) == null && move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon }) && (this.chance === -1 || pokemon.randBattleSeedInt(100) < this.chance) ); @@ -2807,7 +2806,7 @@ export class PostSummonAllyHealAbAttr extends PostSummonAbAttr { override apply({ pokemon, simulated }: AbAttrBaseParams): void { const target = pokemon.getAlly(); - if (!simulated && !isNullOrUndefined(target)) { + if (!simulated && target != null) { globalScene.phaseManager.unshiftNew( "PokemonHealPhase", target.getBattlerIndex(), @@ -2838,7 +2837,7 @@ export class PostSummonClearAllyStatStagesAbAttr extends PostSummonAbAttr { override apply({ pokemon, simulated }: AbAttrBaseParams): void { const target = pokemon.getAlly(); - if (!simulated && !isNullOrUndefined(target)) { + if (!simulated && target != null) { for (const s of BATTLE_STATS) { target.setStatStage(s, 0); } @@ -2957,13 +2956,13 @@ export class PostSummonHealStatusAbAttr extends PostSummonRemoveEffectAbAttr { public override canApply({ pokemon }: AbAttrBaseParams): boolean { const status = pokemon.status?.effect; - return !isNullOrUndefined(status) && (this.immuneEffects.length === 0 || this.immuneEffects.includes(status)); + return status != null && (this.immuneEffects.length === 0 || this.immuneEffects.includes(status)); } public override apply({ pokemon }: AbAttrBaseParams): void { // TODO: should probably check against simulated... const status = pokemon.status?.effect; - if (!isNullOrUndefined(status)) { + if (status != null) { this.statusHealed = status; pokemon.resetStatus(false); pokemon.updateInfo(); @@ -3099,7 +3098,7 @@ export class PostSummonCopyAllyStatsAbAttr extends PostSummonAbAttr { } const ally = pokemon.getAlly(); - return !(isNullOrUndefined(ally) || ally.getStatStages().every(s => s === 0)); + return !(ally == null || ally.getStatStages().every(s => s === 0)); } override apply({ pokemon, simulated }: AbAttrBaseParams): void { @@ -3107,7 +3106,7 @@ export class PostSummonCopyAllyStatsAbAttr extends PostSummonAbAttr { return; } const ally = pokemon.getAlly(); - if (!isNullOrUndefined(ally)) { + if (ally != null) { for (const s of BATTLE_STATS) { pokemon.setStatStage(s, ally.getStatStage(s)); } @@ -3237,7 +3236,7 @@ export class CommanderAbAttr extends AbAttr { const ally = pokemon.getAlly(); return ( globalScene.currentBattle?.double - && !isNullOrUndefined(ally) + && ally != null && ally.species.speciesId === SpeciesId.DONDOZO && !(ally.isFainted() || ally.getTag(BattlerTagType.COMMANDED)) ); @@ -3281,7 +3280,7 @@ export class PreSwitchOutResetStatusAbAttr extends PreSwitchOutAbAttr { } override canApply({ pokemon }: AbAttrBaseParams): boolean { - return !isNullOrUndefined(pokemon.status); + return pokemon.status != null; } override apply({ pokemon, simulated }: AbAttrBaseParams): void { @@ -3561,7 +3560,7 @@ export class ProtectStatAbAttr extends PreStatStageChangeAbAttr { } override canApply({ stat, cancelled }: PreStatStageChangeAbAttrParams): boolean { - return !cancelled.value && (isNullOrUndefined(this.protectedStat) || stat === this.protectedStat); + return !cancelled.value && (this.protectedStat == null || stat === this.protectedStat); } /** @@ -3797,11 +3796,7 @@ export class ConditionalUserFieldProtectStatAbAttr extends PreStatStageChangeAbA if (!target) { return false; } - return ( - !cancelled.value - && (isNullOrUndefined(this.protectedStat) || stat === this.protectedStat) - && this.condition(target) - ); + return !cancelled.value && (this.protectedStat == null || stat === this.protectedStat) && this.condition(target); } /** @@ -4558,7 +4553,7 @@ export class PostTurnStatusHealAbAttr extends PostTurnAbAttr { } override canApply({ pokemon }: AbAttrBaseParams): boolean { - return !isNullOrUndefined(pokemon.status) && this.effects.includes(pokemon.status.effect) && !pokemon.isFullHp(); + return pokemon.status != null && this.effects.includes(pokemon.status.effect) && !pokemon.isFullHp(); } override apply({ simulated, passive, pokemon }: AbAttrBaseParams): void { @@ -4893,7 +4888,7 @@ export class PostTurnHurtIfSleepingAbAttr extends PostTurnAbAttr { */ export class FetchBallAbAttr extends PostTurnAbAttr { override canApply({ simulated, pokemon }: AbAttrBaseParams): boolean { - return !simulated && !isNullOrUndefined(globalScene.currentBattle.lastUsedPokeball) && !!pokemon.isPlayer; + return !simulated && globalScene.currentBattle.lastUsedPokeball != null && !!pokemon.isPlayer; } /** @@ -6258,7 +6253,7 @@ class ForceSwitchOutHelper { true, 500, ); - if (globalScene.currentBattle.double && !isNullOrUndefined(allyPokemon)) { + if (globalScene.currentBattle.double && allyPokemon != null) { globalScene.redirectPokemonMoves(switchOutTarget, allyPokemon); } } @@ -7110,7 +7105,7 @@ export function initAbilities() { .attr(PostDefendMoveDisableAbAttr, 30) .bypassFaint(), new Ability(AbilityId.HEALER, 5) - .conditionalAttr(pokemon => !isNullOrUndefined(pokemon.getAlly()) && randSeedInt(10) < 3, PostTurnResetStatusAbAttr, true), + .conditionalAttr(pokemon => pokemon.getAlly() != null && randSeedInt(10) < 3, PostTurnResetStatusAbAttr, true), new Ability(AbilityId.FRIEND_GUARD, 5) .attr(AlliedFieldDamageReductionAbAttr, 0.75) .ignorable(), diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index f6207adb9aa..9764abeb5fc 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -1,40 +1,4 @@ -/** biome-ignore-start lint/correctness/noUnusedImports: TSDoc imports */ -import type { BattlerTag } from "#app/data/battler-tags"; -/** biome-ignore-end lint/correctness/noUnusedImports: TSDoc imports */ - -import { applyAbAttrs, applyOnGainAbAttrs, applyOnLoseAbAttrs } from "#abilities/apply-ab-attrs"; -import { globalScene } from "#app/global-scene"; -import { getPokemonNameWithAffix } from "#app/messages"; -import { CommonBattleAnim } from "#data/battle-anims"; -import { allMoves } from "#data/data-lists"; -import { AbilityId } from "#enums/ability-id"; -import { ArenaTagSide } from "#enums/arena-tag-side"; -import { ArenaTagType } from "#enums/arena-tag-type"; -import { BattlerTagType } from "#enums/battler-tag-type"; -import { HitResult } from "#enums/hit-result"; -import { CommonAnim } from "#enums/move-anims-common"; -import { MoveCategory } from "#enums/move-category"; -import { MoveId } from "#enums/move-id"; -import { MoveTarget } from "#enums/move-target"; -import { PokemonType } from "#enums/pokemon-type"; -import { Stat } from "#enums/stat"; -import { StatusEffect } from "#enums/status-effect"; -import type { Arena } from "#field/arena"; -import type { Pokemon } from "#field/pokemon"; -import { isSpreadMove } from "#moves/move-utils"; -import type { - ArenaScreenTagType, - ArenaTagData, - EntryHazardTagType, - RoomArenaTagType, - SerializableArenaTagType, -} from "#types/arena-tags"; -import type { Mutable } from "#types/type-helpers"; -import { BooleanHolder, type NumberHolder, toDmgValue } from "#utils/common"; -import i18next from "i18next"; - /** - * @module * ArenaTags are are meant for effects that are tied to the arena (as opposed to a specific pokemon). * Examples include (but are not limited to) * - Cross-turn effects that persist even if the user/target switches out, such as Happy Hour @@ -77,8 +41,44 @@ import i18next from "i18next"; * ``` * Notes * - If the class has any subclasses, then the second form of `loadTag` *must* be used. + * @module */ +/** biome-ignore-start lint/correctness/noUnusedImports: TSDoc imports */ +import type { BattlerTag } from "#app/data/battler-tags"; +/** biome-ignore-end lint/correctness/noUnusedImports: TSDoc imports */ + +import { applyAbAttrs, applyOnGainAbAttrs, applyOnLoseAbAttrs } from "#abilities/apply-ab-attrs"; +import { globalScene } from "#app/global-scene"; +import { getPokemonNameWithAffix } from "#app/messages"; +import { CommonBattleAnim } from "#data/battle-anims"; +import { allMoves } from "#data/data-lists"; +import { AbilityId } from "#enums/ability-id"; +import { ArenaTagSide } from "#enums/arena-tag-side"; +import { ArenaTagType } from "#enums/arena-tag-type"; +import { BattlerTagType } from "#enums/battler-tag-type"; +import { HitResult } from "#enums/hit-result"; +import { CommonAnim } from "#enums/move-anims-common"; +import { MoveCategory } from "#enums/move-category"; +import { MoveId } from "#enums/move-id"; +import { MoveTarget } from "#enums/move-target"; +import { PokemonType } from "#enums/pokemon-type"; +import { Stat } from "#enums/stat"; +import { StatusEffect } from "#enums/status-effect"; +import type { Arena } from "#field/arena"; +import type { Pokemon } from "#field/pokemon"; +import { isSpreadMove } from "#moves/move-utils"; +import type { + ArenaScreenTagType, + ArenaTagData, + EntryHazardTagType, + RoomArenaTagType, + SerializableArenaTagType, +} from "#types/arena-tags"; +import type { Mutable } from "#types/type-helpers"; +import { BooleanHolder, type NumberHolder, toDmgValue } from "#utils/common"; +import i18next from "i18next"; + /** Interface containing the serializable fields of ArenaTagData. */ interface BaseArenaTag { /** @@ -197,9 +197,10 @@ export abstract class ArenaTag implements BaseArenaTag { /** * Apply effects when this Tag overlaps by creating a new instance while one is already present. - * @param _source - The {@linkcode Pokemon} having added the tag, or `null` if no pokemon did + * @param _source - The {@linkcode Pokemon} having added the tag, or `undefined` if no pokemon did + * @todo Rather than passing this `undefined`, maybe just... don't pass the tags? */ - public onOverlap(_source: Pokemon | null): void {} + public onOverlap(_source: Pokemon | undefined): void {} /** * Reduce this {@linkcode ArenaTag}'s duration and apply any end-of-turn effects @@ -229,17 +230,17 @@ export abstract class ArenaTag implements BaseArenaTag { } /** - * Helper function that retrieves the source Pokemon + * Helper function that retrieves the source Pokemon. * @returns - The source {@linkcode Pokemon} for this tag. * Returns `undefined` if `this.sourceId` is `undefined` */ protected getSourcePokemon(): Pokemon | undefined { - return globalScene.getPokemonById(this.sourceId) ?? undefined; + return globalScene.getPokemonById(this.sourceId); } /** - * Helper function that retrieves the Pokemon affected - * @returns list of PlayerPokemon or EnemyPokemon on the field + * Helper function that retrieves the Pokemon affected. + * @returns An array containing all {@linkcode Pokemon} affected by this Tag. */ protected getAffectedPokemon(): Pokemon[] { switch (this.side) { @@ -1467,7 +1468,7 @@ export class SuppressAbilitiesTag extends SerializableArenaTag { return "arenaTag:neutralizingGasOnRemove"; } - private playActivationMessage(pokemon: Pokemon | null) { + private playActivationMessage(pokemon: Pokemon | undefined): void { if (pokemon) { globalScene.phaseManager.queueMessage( i18next.t("arenaTag:neutralizingGasOnAdd", { @@ -1499,7 +1500,7 @@ export class SuppressAbilitiesTag extends SerializableArenaTag { } } - public override onOverlap(source: Pokemon | null): void { + public override onOverlap(source: Pokemon | undefined): void { (this as Mutable).sourceCount++; this.playActivationMessage(source); } diff --git a/src/data/balance/moveset-generation.ts b/src/data/balance/moveset-generation.ts new file mode 100644 index 00000000000..90a602ca97e --- /dev/null +++ b/src/data/balance/moveset-generation.ts @@ -0,0 +1,235 @@ +/* + * SPDX-Copyright-Text: 2025 Pagefault Games + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +/** + * # Balance: Moveset Generation Configuration + * + * This module contains configuration constants and functions that control + * the limitations and rules around moveset generation for generated Pokémon. + * + * + * ### Move Weights + * + * The various move weight constants in this module control how likely + * certain categories of moves are to appear in a generated Pokémon's + * moveset. Higher weights make a move more likely to be chosen. + * The constants here specify the *base* weight for a move when first computed. + * These weights are post-processed (and then scaled up such that weights have a larger impact, + * for instance, on boss Pokémon) before being used in the actual moveset generation. + * + * Post Processing of weights includes, but is not limited to: + * - Adjusting weights of status moves + * - Adjusting weights based on the move's power relative to the highest power available + * - Adjusting weights based on the stat the move uses to calculate damage relative to the higher stat + * + * + * All weights go through additional post-processing based on + * their expected power (accuracy * damage * expected number of hits) + * + * @module + */ + +import { MoveId } from "#enums/move-id"; + + +//#region Constants +/** + * The minimum level for a Pokémon to generate with a move it can only learn + * from a common tier TM + */ +export const COMMON_TIER_TM_LEVEL_REQUIREMENT = 25; +/** + * The minimum level for a Pokémon to generate with a move it can only learn + * from a great tier TM + */ +export const GREAT_TIER_TM_LEVEL_REQUIREMENT = 40; +/** + * The minimum level for a Pokémon to generate with a move it can only learn + * from an ultra tier TM + */ +export const ULTRA_TIER_TM_LEVEL_REQUIREMENT = 55; + +/** Below this level, Pokémon will be unable to generate with any egg moves */ +export const EGG_MOVE_LEVEL_REQUIREMENT = 60; +/** Below this level, Pokémon will be unable to generate with rare egg moves */ +export const RARE_EGG_MOVE_LEVEL_REQUIREMENT = 170; + +// Note: Not exported, only for use with `getMaxTmCount +/** Below this level, Pokémon will be unable to generate with any TMs */ +const ONE_TM_THRESHOLD = 25; +/** Below this level, Pokémon will generate with at most 1 TM */ +const TWO_TM_THRESHOLD = 41; +/** Below this level, Pokémon will generate with at most two TMs */ +const THREE_TM_THRESHOLD = 71; +/** Below this level, Pokémon will generate with at most three TMs */ +const FOUR_TM_THRESHOLD = 101; + +/** Below this level, Pokémon will be unable to generate any egg moves */ +const ONE_EGG_MOVE_THRESHOLD = 80; +/** Below this level, Pokémon will generate with at most 1 egg moves */ +const TWO_EGG_MOVE_THRESHOLD = 121; +/** Below this level, Pokémon will generate with at most 2 egg moves */ +const THREE_EGG_MOVE_THRESHOLD = 161; +/** Above this level, Pokémon will generate with at most 3 egg moves */ +const FOUR_EGG_MOVE_THRESHOLD = 201; + + +/** The weight given to TMs in the common tier during moveset generation */ +export const COMMON_TM_MOVESET_WEIGHT = 12; +/** The weight given to TMs in the great tier during moveset generation */ +export const GREAT_TM_MOVESET_WEIGHT = 14; +/** The weight given to TMs in the ultra tier during moveset generation */ +export const ULTRA_TM_MOVESET_WEIGHT = 18; + +/** + * The base weight offset for level moves + * + * @remarks + * The relative likelihood of moves learned at different levels is determined by + * the ratio of their weights, + * or, the formula: + * `(levelB + BASE_LEVEL_WEIGHT_OFFSET) / (levelA + BASE_LEVEL_WEIGHT_OFFSET)` + * + * For example, consider move A and B that are learned at levels 1 and 60, respectively, + * but have no other differences (same power, accuracy, category, etc). + * The following table demonstrates the likelihood of move B being chosen over move A. + * + * | Offset | Likelihood | + * |--------|------------| + * | 0 | 60x | + * | 1 | 30x | + * | 5 | 10.8x | + * | 20 | 3.8x | + * | 60 | 2x | + * + * Note that increasing this without adjusting the other weights will decrease the likelihood of non-level moves + * + * For a complete picture, see {@link https://www.desmos.com/calculator/wgln4dxigl} + */ +export const BASE_LEVEL_WEIGHT_OFFSET = 20; + +/** + * The maximum weight an egg move can ever have + * @remarks + * Egg moves have their weights adjusted based on the maximum weight of the Pokémon's + * level-up moves. Rare Egg moves are always 5/6th of the computed egg move weight. + * Boss pokemon are not allowed to spawn with rare egg moves. + * @see {@linkcode EGG_MOVE_TO_LEVEL_WEIGHT} + */ +export const EGG_MOVE_WEIGHT_MAX = 60; +/** + * The percentage of the Pokémon's highest weighted level move to the weight an + * egg move can generate with + */ +export const EGG_MOVE_TO_LEVEL_WEIGHT = 0.85; +/** The weight given to evolution moves */ +export const EVOLUTION_MOVE_WEIGHT = 70; +/** The weight given to relearn moves */ +export const RELEARN_MOVE_WEIGHT = 60; + +/** The base weight multiplier to use + * + * The higher the number, the more impact weights have on the final move selection. + * i.e. if set to 0, all moves have equal chance of being selected regardless of their weight. + */ +export const BASE_WEIGHT_MULTIPLIER = 1.6; + +/** The additional weight added onto {@linkcode BASE_WEIGHT_MULTIPLIER} for boss Pokémon */ +export const BOSS_EXTRA_WEIGHT_MULTIPLIER = 0.4; + + + +/** + * Set of moves that should be blacklisted from the forced STAB during moveset generation + * + * @remarks + * During moveset generation, trainer pokemon attempt to force their pokemon to generate with STAB + * moves in their movesets. Moves in this list not be considered to be "STAB" moves for this purpose. + * This does *not* prevent them from appearing in the moveset, but they will never + * be selected as a forced STAB move. + */ +export const STAB_BLACKLIST: ReadonlySet = new Set([ + MoveId.BEAT_UP, + MoveId.BELCH, + MoveId.BIDE, + MoveId.COMEUPPANCE, + MoveId.COUNTER, + MoveId.DOOM_DESIRE, + MoveId.DRAGON_RAGE, + MoveId.DREAM_EATER, + MoveId.ENDEAVOR, + MoveId.EXPLOSION, + MoveId.FAKE_OUT, + MoveId.FIRST_IMPRESSION, + MoveId.FISSURE, + MoveId.FLING, + MoveId.FOCUS_PUNCH, + MoveId.FUTURE_SIGHT, + MoveId.GUILLOTINE, + MoveId.HOLD_BACK, + MoveId.HORN_DRILL, + MoveId.LAST_RESORT, + MoveId.METAL_BURST, + MoveId.MIRROR_COAT, + MoveId.MISTY_EXPLOSION, + MoveId.NATURAL_GIFT, + MoveId.NATURES_MADNESS, + MoveId.NIGHT_SHADE, + MoveId.PSYWAVE, + MoveId.RUINATION, + MoveId.SELF_DESTRUCT, + MoveId.SHEER_COLD, + MoveId.SHELL_TRAP, + MoveId.SKY_DROP, + MoveId.SNORE, + MoveId.SONIC_BOOM, + MoveId.SPIT_UP, + MoveId.STEEL_BEAM, + MoveId.STEEL_ROLLER, + MoveId.SUPER_FANG, + MoveId.SYNCHRONOISE, + MoveId.UPPER_HAND, +]); + +//#endregion Constants + +/** + * Get the maximum number of TMs a Pokémon is allowed to learn based on + * its level + * @param level - The level of the Pokémon + * @returns The number of TMs the Pokémon can learn at this level + */ +export function getMaxTmCount(level: number) { + if (level < ONE_TM_THRESHOLD) { + return 0; + } + if (level < TWO_TM_THRESHOLD) { + return 1; + } + if (level < THREE_TM_THRESHOLD) { + return 2; + } + if (level < FOUR_TM_THRESHOLD) { + return 3; + } + return 4; +} + + +export function getMaxEggMoveCount(level: number): number { + if (level < ONE_EGG_MOVE_THRESHOLD) { + return 0; + } + if (level < TWO_EGG_MOVE_THRESHOLD) { + return 1; + } + if (level < THREE_EGG_MOVE_THRESHOLD) { + return 2; + } + if (level < FOUR_EGG_MOVE_THRESHOLD) { + return 3; + } + return 4; +} diff --git a/src/data/balance/pokemon-evolutions.ts b/src/data/balance/pokemon-evolutions.ts index d364dc036b1..0c2fa4e78fa 100644 --- a/src/data/balance/pokemon-evolutions.ts +++ b/src/data/balance/pokemon-evolutions.ts @@ -14,7 +14,7 @@ import { TimeOfDay } from "#enums/time-of-day"; import { WeatherType } from "#enums/weather-type"; import type { Pokemon } from "#field/pokemon"; import type { SpeciesStatBoosterItem, SpeciesStatBoosterModifierType } from "#modifiers/modifier-type"; -import { coerceArray, isNullOrUndefined, randSeedInt } from "#utils/common"; +import { coerceArray, randSeedInt } from "#utils/common"; import { getPokemonSpecies } from "#utils/pokemon-utils"; import { toCamelCase } from "#utils/strings"; import i18next from "i18next"; @@ -53,6 +53,7 @@ export enum EvolutionItem { PRISM_SCALE, RAZOR_CLAW, RAZOR_FANG, + OVAL_STONE, REAPER_CLOTH, ELECTIRIZER, MAGMARIZER, @@ -128,7 +129,7 @@ export class SpeciesEvolutionCondition { } public get description(): string[] { - if (!isNullOrUndefined(this.desc)) { + if (this.desc != null) { return this.desc; } this.desc = this.data.map(cond => { @@ -161,11 +162,11 @@ export class SpeciesEvolutionCondition { case EvoCondKey.HELD_ITEM: return i18next.t(`pokemonEvolutions:heldItem.${toCamelCase(cond.itemKey)}`); } - }).filter(s => !isNullOrUndefined(s)); // Filter out stringless conditions + }).filter(s => s != null); // Filter out stringless conditions return this.desc; } - public conditionsFulfilled(pokemon: Pokemon): boolean { + public conditionsFulfilled(pokemon: Pokemon, forFusion = false): boolean { console.log(this.data); return this.data.every(cond => { switch (cond.key) { @@ -185,7 +186,7 @@ export class SpeciesEvolutionCondition { m.getStackCount() + pokemon.getPersistentTreasureCount() >= cond.value ); case EvoCondKey.GENDER: - return pokemon.gender === cond.gender; + return cond.gender === (forFusion ? pokemon.fusionGender : pokemon.gender); case EvoCondKey.SHEDINJA: // Shedinja cannot be evolved into directly return false; case EvoCondKey.BIOME: @@ -233,7 +234,7 @@ export class SpeciesFormEvolution { this.evoFormKey = evoFormKey; this.level = level; this.item = item || EvolutionItem.NONE; - if (!isNullOrUndefined(condition)) { + if (condition != null) { this.condition = new SpeciesEvolutionCondition(...coerceArray(condition)); } this.wildDelay = wildDelay ?? SpeciesWildEvolutionDelay.NONE; @@ -291,8 +292,8 @@ export class SpeciesFormEvolution { return ( pokemon.level >= this.level && // Check form key, using the fusion's form key if we're checking the fusion - (isNullOrUndefined(this.preFormKey) || (forFusion ? pokemon.getFusionFormKey() : pokemon.getFormKey()) === this.preFormKey) && - (isNullOrUndefined(this.condition) || this.condition.conditionsFulfilled(pokemon)) && + (this.preFormKey == null || (forFusion ? pokemon.getFusionFormKey() : pokemon.getFormKey()) === this.preFormKey) && + (this.condition == null || this.condition.conditionsFulfilled(pokemon, forFusion)) && ((item ?? EvolutionItem.NONE) === (this.item ?? EvolutionItem.NONE)) ); } @@ -305,11 +306,11 @@ export class SpeciesFormEvolution { */ public isValidItemEvolution(pokemon: Pokemon, forFusion = false): boolean { return ( - !isNullOrUndefined(this.item) && + this.item != null && pokemon.level >= this.level && // Check form key, using the fusion's form key if we're checking the fusion - (isNullOrUndefined(this.preFormKey) || (forFusion ? pokemon.getFusionFormKey() : pokemon.getFormKey()) === this.preFormKey) && - (isNullOrUndefined(this.condition) || this.condition.conditionsFulfilled(pokemon)) + (this.preFormKey == null || (forFusion ? pokemon.getFusionFormKey() : pokemon.getFormKey()) === this.preFormKey) && + (this.condition == null || this.condition.conditionsFulfilled(pokemon)) ); } @@ -1496,10 +1497,13 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesFormEvolution(SpeciesId.DUDUNSPARCE, "", "two-segment", 32, null, {key: EvoCondKey.MOVE, move: MoveId.HYPER_DRILL}, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.GLIGAR]: [ - new SpeciesEvolution(SpeciesId.GLISCOR, 1, EvolutionItem.RAZOR_FANG, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]} /* Razor fang at night*/, SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(SpeciesId.GLISCOR, 1, EvolutionItem.RAZOR_FANG, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.SNEASEL]: [ - new SpeciesEvolution(SpeciesId.WEAVILE, 1, EvolutionItem.RAZOR_CLAW, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]} /* Razor claw at night*/, SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(SpeciesId.WEAVILE, 1, EvolutionItem.RAZOR_CLAW, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}, SpeciesWildEvolutionDelay.VERY_LONG) + ], + [SpeciesId.HAPPINY]: [ + new SpeciesEvolution(SpeciesId.CHANSEY, 1, EvolutionItem.OVAL_STONE, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]}, SpeciesWildEvolutionDelay.SHORT) ], [SpeciesId.URSARING]: [ new SpeciesEvolution(SpeciesId.URSALUNA, 1, EvolutionItem.PEAT_BLOCK, null, SpeciesWildEvolutionDelay.VERY_LONG) //Ursaring does not evolve into Bloodmoon Ursaluna @@ -1760,7 +1764,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.CROBAT, 1, null, {key: EvoCondKey.FRIENDSHIP, value: 120}, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.CHANSEY]: [ - new SpeciesEvolution(SpeciesId.BLISSEY, 1, null, {key: EvoCondKey.FRIENDSHIP, value: 200}, SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(SpeciesId.BLISSEY, 1, null, {key: EvoCondKey.FRIENDSHIP, value: 180}, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.PICHU]: [ new SpeciesFormEvolution(SpeciesId.PIKACHU, "spiky", "partner", 1, null, {key: EvoCondKey.FRIENDSHIP, value: 90}, SpeciesWildEvolutionDelay.SHORT), @@ -1787,9 +1791,6 @@ export const pokemonEvolutions: PokemonEvolutions = { [SpeciesId.CHINGLING]: [ new SpeciesEvolution(SpeciesId.CHIMECHO, 1, null, [{key: EvoCondKey.FRIENDSHIP, value: 90}, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}], SpeciesWildEvolutionDelay.MEDIUM) ], - [SpeciesId.HAPPINY]: [ - new SpeciesEvolution(SpeciesId.CHANSEY, 1, null, {key: EvoCondKey.FRIENDSHIP, value: 160}, SpeciesWildEvolutionDelay.SHORT) - ], [SpeciesId.MUNCHLAX]: [ new SpeciesEvolution(SpeciesId.SNORLAX, 1, null, {key: EvoCondKey.FRIENDSHIP, value: 120}, SpeciesWildEvolutionDelay.LONG) ], diff --git a/src/data/battle-anims.ts b/src/data/battle-anims.ts index 1dcb7d7eebf..573a1730796 100644 --- a/src/data/battle-anims.ts +++ b/src/data/battle-anims.ts @@ -7,7 +7,7 @@ import { AnimBlendType, AnimFocus, AnimFrameTarget, ChargeAnim, CommonAnim } fro import { MoveFlags } from "#enums/move-flags"; import { MoveId } from "#enums/move-id"; import type { Pokemon } from "#field/pokemon"; -import { coerceArray, getFrameMs, isNullOrUndefined, type nil } from "#utils/common"; +import { coerceArray, getFrameMs, type nil } from "#utils/common"; import { getEnumKeys, getEnumValues } from "#utils/enums"; import { toKebabCase } from "#utils/strings"; import Phaser from "phaser"; @@ -388,7 +388,7 @@ class AnimTimedAddBgEvent extends AnimTimedBgEvent { moveAnim.bgSprite.setAlpha(this.opacity / 255); globalScene.field.add(moveAnim.bgSprite); const fieldPokemon = globalScene.getEnemyPokemon(false) ?? globalScene.getPlayerPokemon(false); - if (!isNullOrUndefined(priority)) { + if (priority != null) { globalScene.field.moveTo(moveAnim.bgSprite as Phaser.GameObjects.GameObject, priority); } else if (fieldPokemon?.isOnField()) { globalScene.field.moveBelow(moveAnim.bgSprite as Phaser.GameObjects.GameObject, fieldPokemon); @@ -524,7 +524,7 @@ export async function initEncounterAnims(encounterAnim: EncounterAnim | Encounte const encounterAnimNames = getEnumKeys(EncounterAnim); const encounterAnimFetches: Promise>[] = []; for (const anim of anims) { - if (encounterAnims.has(anim) && !isNullOrUndefined(encounterAnims.get(anim))) { + if (encounterAnims.has(anim) && encounterAnims.get(anim) != null) { continue; } encounterAnimFetches.push( @@ -1240,7 +1240,7 @@ export abstract class BattleAnim { const graphicIndex = graphicFrameCount++; const moveSprite = sprites[graphicIndex]; - if (!isNullOrUndefined(frame.priority)) { + if (frame.priority != null) { const setSpritePriority = (priority: number) => { if (existingFieldSprites.length > priority) { // Move to specified priority index diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index c495cdaa604..80a30516903 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -1,3 +1,42 @@ +/** + * BattlerTags are used to represent semi-persistent effects that can be attached to a Pokemon. + * Note that before serialization, a new tag object is created, and then `loadTag` is called on the + * tag with the object that was serialized. + * + * This means it is straightforward to avoid serializing fields. + * Fields that are not set in the constructor and not set in `loadTag` will thus not be serialized. + * + * Any battler tag that can persist across sessions must extend SerializableBattlerTag in its class definition signature. + * Only tags that persist across waves (meaning their effect can last >1 turn) should be considered + * serializable. + * + * Serializable battler tags have strict requirements for their fields. + * Properties that are not necessary to reconstruct the tag must not be serialized. This can be avoided + * by using a private property. If access to the property is needed outside of the class, then + * a getter (and potentially, a setter) should be used instead. + * + * If a property that is intended to be private must be serialized, then it should instead + * be declared as a public readonly propety. Then, in the `loadTag` method (or any method inside the class that needs to adjust the property) + * use `(this as Mutable).propertyName = value;` + * These rules ensure that Typescript is aware of the shape of the serialized version of the class. + * + * If any new serializable fields *are* added, then the class *must* override the + * `loadTag` method to set the new fields. Its signature *must* match the example below: + * ``` + * class ExampleTag extends SerializableBattlerTag { + * // Example, if we add 2 new fields that should be serialized: + * public a: string; + * public b: number; + * // Then we must also define a loadTag method with one of the following signatures + * public override loadTag(source: BaseBattlerTag & Pick(source: BaseBattlerTag & Pick): void; + * } + * ``` + * Notes + * - If the class has any subclasses, then the second form of `loadTag` *must* be used. + * @module + */ + import { applyAbAttrs } from "#abilities/apply-ab-attrs"; import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; @@ -49,48 +88,9 @@ import type { TypeBoostTagType, } from "#types/battler-tags"; import type { Mutable } from "#types/type-helpers"; -import { BooleanHolder, coerceArray, getFrameMs, isNullOrUndefined, NumberHolder, toDmgValue } from "#utils/common"; +import { BooleanHolder, coerceArray, getFrameMs, NumberHolder, toDmgValue } from "#utils/common"; import { toCamelCase } from "#utils/strings"; -/** - * @module - * BattlerTags are used to represent semi-persistent effects that can be attached to a Pokemon. - * Note that before serialization, a new tag object is created, and then `loadTag` is called on the - * tag with the object that was serialized. - * - * This means it is straightforward to avoid serializing fields. - * Fields that are not set in the constructor and not set in `loadTag` will thus not be serialized. - * - * Any battler tag that can persist across sessions must extend SerializableBattlerTag in its class definition signature. - * Only tags that persist across waves (meaning their effect can last >1 turn) should be considered - * serializable. - * - * Serializable battler tags have strict requirements for their fields. - * Properties that are not necessary to reconstruct the tag must not be serialized. This can be avoided - * by using a private property. If access to the property is needed outside of the class, then - * a getter (and potentially, a setter) should be used instead. - * - * If a property that is intended to be private must be serialized, then it should instead - * be declared as a public readonly propety. Then, in the `loadTag` method (or any method inside the class that needs to adjust the property) - * use `(this as Mutable).propertyName = value;` - * These rules ensure that Typescript is aware of the shape of the serialized version of the class. - * - * If any new serializable fields *are* added, then the class *must* override the - * `loadTag` method to set the new fields. Its signature *must* match the example below: - * ``` - * class ExampleTag extends SerializableBattlerTag { - * // Example, if we add 2 new fields that should be serialized: - * public a: string; - * public b: number; - * // Then we must also define a loadTag method with one of the following signatures - * public override loadTag(source: BaseBattlerTag & Pick(source: BaseBattlerTag & Pick): void; - * } - * ``` - * Notes - * - If the class has any subclasses, then the second form of `loadTag` *must* be used. - */ - /** Interface containing the serializable fields of BattlerTag */ interface BaseBattlerTag { /** The tag's remaining duration */ @@ -198,7 +198,7 @@ export class BattlerTag implements BaseBattlerTag { * Helper function that retrieves the source Pokemon object * @returns The source {@linkcode Pokemon}, or `null` if none is found */ - public getSourcePokemon(): Pokemon | null { + public getSourcePokemon(): Pokemon | undefined { return globalScene.getPokemonById(this.sourceId); } } @@ -378,7 +378,7 @@ export class DisabledTag extends MoveRestrictionBattlerTag { // Disable fails against struggle or an empty move history // TODO: Confirm if this is redundant given Disable/Cursed Body's disable conditions const move = pokemon.getLastNonVirtualMove(); - if (isNullOrUndefined(move) || move.move === MoveId.STRUGGLE) { + if (move == null || move.move === MoveId.STRUGGLE) { return; } @@ -451,7 +451,7 @@ export class GorillaTacticsTag extends MoveRestrictionBattlerTag { override canAdd(pokemon: Pokemon): boolean { // Choice items ignore struggle, so Gorilla Tactics should too const lastSelectedMove = pokemon.getLastNonVirtualMove(); - return !isNullOrUndefined(lastSelectedMove) && lastSelectedMove.move !== MoveId.STRUGGLE; + return lastSelectedMove != null && lastSelectedMove.move !== MoveId.STRUGGLE; } /** @@ -968,7 +968,7 @@ export class InfatuatedTag extends SerializableBattlerTag { phaseManager.queueMessage( i18next.t("battlerTags:infatuatedLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - sourcePokemonName: getPokemonNameWithAffix(globalScene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct? + sourcePokemonName: getPokemonNameWithAffix(this.getSourcePokemon()), }), ); phaseManager.unshiftNew("CommonAnimPhase", pokemon.getBattlerIndex(), undefined, CommonAnim.ATTRACT); @@ -1305,7 +1305,7 @@ export class EncoreTag extends MoveRestrictionBattlerTag { override lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { if (lapseType === BattlerTagLapseType.CUSTOM) { const encoredMove = pokemon.getMoveset().find(m => m.moveId === this.moveId); - return !isNullOrUndefined(encoredMove) && encoredMove.getPpRatio() > 0; + return encoredMove != null && encoredMove.getPpRatio() > 0; } return super.lapse(pokemon, lapseType); } diff --git a/src/data/challenge.ts b/src/data/challenge.ts index 64a88bc8be2..65faa900af7 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -1,5 +1,6 @@ import type { FixedBattleConfig } from "#app/battle"; import { getRandomTrainerFunc } from "#app/battle"; +import { globalScene } from "#app/global-scene"; import { defaultStarterSpeciesAndEvolutions } from "#balance/pokemon-evolutions"; import { speciesStarterCosts } from "#balance/starters"; import type { PokemonSpecies } from "#data/pokemon-species"; @@ -12,6 +13,7 @@ import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves"; import { ModifierTier } from "#enums/modifier-tier"; import { MoveId } from "#enums/move-id"; import type { MoveSourceType } from "#enums/move-source-type"; +import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Nature } from "#enums/nature"; import { PokemonType } from "#enums/pokemon-type"; import { SpeciesId } from "#enums/species-id"; @@ -1076,7 +1078,12 @@ export class LimitedCatchChallenge extends Challenge { override applyPokemonAddToParty(pokemon: EnemyPokemon, status: BooleanHolder): boolean { if (status.value) { - status.value = pokemon.metWave % 10 === 1; + const isTeleporter = + globalScene.currentBattle.mysteryEncounter?.encounterType === MysteryEncounterType.TELEPORTING_HIJINKS + && globalScene.currentBattle.mysteryEncounter.selectedOption + !== globalScene.currentBattle.mysteryEncounter.options[2]; // don't allow catch when not choosing biome change option + const isFirstWave = pokemon.metWave % 10 === 1; + status.value = isTeleporter || isFirstWave; return true; } return false; diff --git a/src/data/daily-run.ts b/src/data/daily-run.ts index a0d3358ecb0..5f49b9adbb0 100644 --- a/src/data/daily-run.ts +++ b/src/data/daily-run.ts @@ -6,8 +6,8 @@ import { PokemonSpecies } from "#data/pokemon-species"; import { BiomeId } from "#enums/biome-id"; import { PartyMemberStrength } from "#enums/party-member-strength"; import { SpeciesId } from "#enums/species-id"; -import type { Starter } from "#ui/handlers/starter-select-ui-handler"; -import { isNullOrUndefined, randSeedGauss, randSeedInt, randSeedItem } from "#utils/common"; +import type { Starter } from "#ui/starter-select-ui-handler"; +import { randSeedGauss, randSeedInt, randSeedItem } from "#utils/common"; import { getEnumValues } from "#utils/enums"; import { getPokemonSpecies, getPokemonSpeciesForm } from "#utils/pokemon-utils"; @@ -32,7 +32,7 @@ export function getDailyRunStarters(seed: string): Starter[] { const startingLevel = globalScene.gameMode.getStartingLevel(); const eventStarters = getDailyEventSeedStarters(seed); - if (!isNullOrUndefined(eventStarters)) { + if (eventStarters != null) { starters.push(...eventStarters); return; } @@ -127,7 +127,7 @@ const dailyBiomeWeights: BiomeWeights = { export function getDailyStartingBiome(): BiomeId { const eventBiome = getDailyEventSeedBiome(globalScene.seed); - if (!isNullOrUndefined(eventBiome)) { + if (eventBiome != null) { return eventBiome; } diff --git a/src/data/moves/move-utils.ts b/src/data/moves/move-utils.ts index eedeea53087..1fe0880317b 100644 --- a/src/data/moves/move-utils.ts +++ b/src/data/moves/move-utils.ts @@ -7,7 +7,7 @@ import { PokemonType } from "#enums/pokemon-type"; import type { Pokemon } from "#field/pokemon"; import { applyMoveAttrs } from "#moves/apply-attrs"; import type { Move, MoveTargetSet, UserMoveConditionFunc } from "#moves/move"; -import { isNullOrUndefined, NumberHolder } from "#utils/common"; +import { NumberHolder } from "#utils/common"; /** * Return whether the move targets the field @@ -78,7 +78,7 @@ export function getMoveTargets(user: Pokemon, move: MoveId, replaceTarget?: Move case MoveTarget.OTHER: case MoveTarget.ALL_NEAR_OTHERS: case MoveTarget.ALL_OTHERS: - set = !isNullOrUndefined(ally) ? opponents.concat([ally]) : opponents; + set = ally != null ? opponents.concat([ally]) : opponents; multiple = moveTarget === MoveTarget.ALL_NEAR_OTHERS || moveTarget === MoveTarget.ALL_OTHERS; break; case MoveTarget.NEAR_ENEMY: @@ -95,22 +95,22 @@ export function getMoveTargets(user: Pokemon, move: MoveId, replaceTarget?: Move return { targets: [-1 as BattlerIndex], multiple: false }; case MoveTarget.NEAR_ALLY: case MoveTarget.ALLY: - set = !isNullOrUndefined(ally) ? [ally] : []; + set = ally != null ? [ally] : []; break; case MoveTarget.USER_OR_NEAR_ALLY: case MoveTarget.USER_AND_ALLIES: case MoveTarget.USER_SIDE: - set = !isNullOrUndefined(ally) ? [user, ally] : [user]; + set = ally != null ? [user, ally] : [user]; multiple = moveTarget !== MoveTarget.USER_OR_NEAR_ALLY; break; case MoveTarget.ALL: case MoveTarget.BOTH_SIDES: - set = (!isNullOrUndefined(ally) ? [user, ally] : [user]).concat(opponents); + set = (ally != null ? [user, ally] : [user]).concat(opponents); multiple = true; break; case MoveTarget.CURSE: { - const extraTargets = !isNullOrUndefined(ally) ? [ally] : []; + const extraTargets = ally != null ? [ally] : []; set = user.getTypes(true).includes(PokemonType.GHOST) ? opponents.concat(extraTargets) : [user]; } break; diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 652c86e530f..2a3d8e58bad 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -88,7 +88,7 @@ import type { AttackMoveResult } from "#types/attack-move-result"; import type { Localizable } from "#types/locales"; import type { ChargingMove, MoveAttrMap, MoveAttrString, MoveClassMap, MoveKindString, MoveMessageFunc } from "#types/move-types"; import type { TurnMove } from "#types/turn-move"; -import { BooleanHolder, coerceArray, type Constructor, isNullOrUndefined, NumberHolder, randSeedFloat, randSeedInt, randSeedItem, toDmgValue } from "#utils/common"; +import { BooleanHolder, coerceArray, type Constructor, NumberHolder, randSeedFloat, randSeedInt, randSeedItem, toDmgValue } from "#utils/common"; import { getEnumValues } from "#utils/enums"; import { toCamelCase, toTitleCase } from "#utils/strings"; import i18next from "i18next"; @@ -835,7 +835,7 @@ export abstract class Move implements Localizable { applyAbAttrs("VariableMovePowerAbAttr", abAttrParams); const ally = source.getAlly(); - if (!isNullOrUndefined(ally)) { + if (ally != null) { applyAbAttrs("AllyMoveCategoryPowerBoostAbAttr", {...abAttrParams, pokemon: ally}); } @@ -965,7 +965,7 @@ export abstract class Move implements Localizable { // ...and cannot enhance Pollen Puff when targeting an ally. const ally = user.getAlly(); - const exceptPollenPuffAlly: boolean = this.id === MoveId.POLLEN_PUFF && !isNullOrUndefined(ally) && targets.includes(ally.getBattlerIndex()) + const exceptPollenPuffAlly: boolean = this.id === MoveId.POLLEN_PUFF && ally != null && targets.includes(ally.getBattlerIndex()) return (!restrictSpread || !isMultiTarget) && !this.isChargingMove() @@ -2114,7 +2114,7 @@ export class FlameBurstAttr extends MoveEffectAttr { const targetAlly = target.getAlly(); const cancelled = new BooleanHolder(false); - if (!isNullOrUndefined(targetAlly)) { + if (targetAlly != null) { applyAbAttrs("BlockNonDirectDamageAbAttr", {pokemon: targetAlly, cancelled}); } @@ -2127,7 +2127,7 @@ export class FlameBurstAttr extends MoveEffectAttr { } getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { - return !isNullOrUndefined(target.getAlly()) ? -5 : 0; + return target.getAlly() != null ? -5 : 0; } } @@ -3156,7 +3156,7 @@ export class WeatherInstantChargeAttr extends InstantChargeAttr { super((user, move) => { const currentWeather = globalScene.arena.weather; - if (isNullOrUndefined(currentWeather?.weatherType)) { + if (currentWeather?.weatherType == null) { return false; } else { return !currentWeather?.isEffectSuppressed() @@ -6293,7 +6293,7 @@ export class RevivalBlessingAttr extends MoveEffectAttr { pokemon.heal(Math.min(toDmgValue(0.5 * pokemon.getMaxHp()), pokemon.getMaxHp())); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:revivalBlessing", { pokemonName: getPokemonNameWithAffix(pokemon) }), 0, true); const allyPokemon = user.getAlly(); - if (globalScene.currentBattle.double && globalScene.getEnemyParty().length > 1 && !isNullOrUndefined(allyPokemon)) { + if (globalScene.currentBattle.double && globalScene.getEnemyParty().length > 1 && allyPokemon != null) { // Handle cases where revived pokemon needs to get switched in on same turn if (allyPokemon.isFainted() || allyPokemon === pokemon) { // Enemy switch phase should be removed and replaced with the revived pkmn switching in @@ -6462,7 +6462,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:fled", { pokemonName: getPokemonNameWithAffix(switchOutTarget) }), null, true, 500); // in double battles redirect potential moves off fled pokemon - if (globalScene.currentBattle.double && !isNullOrUndefined(allyPokemon)) { + if (globalScene.currentBattle.double && allyPokemon != null) { globalScene.redirectPokemonMoves(switchOutTarget, allyPokemon); } } @@ -7122,7 +7122,7 @@ export class CopyMoveAttr extends CallMoveAttr { getCondition(): MoveConditionFunc { return (_user, target, _move) => { const lastMove = this.mirrorMove ? target.getLastNonVirtualMove(false, false)?.move : globalScene.currentBattle.lastMove; - return !isNullOrUndefined(lastMove) && !this.invalidMoves.has(lastMove); + return lastMove != null && !this.invalidMoves.has(lastMove); }; } } @@ -7169,7 +7169,7 @@ export class RepeatMoveAttr extends MoveEffectAttr { && firstTarget !== target.getAlly() ) { const ally = firstTarget.getAlly(); - if (!isNullOrUndefined(ally) && ally.isActive()) { + if (ally != null && ally.isActive()) { moveTargets = [ ally.getBattlerIndex() ]; } } @@ -7476,7 +7476,7 @@ export class SketchAttr extends MoveEffectAttr { } const targetMove = target.getLastNonVirtualMove(); - return !isNullOrUndefined(targetMove) + return targetMove != null && !invalidSketchMoves.has(targetMove.move) && user.getMoveset().every(m => m.moveId !== targetMove.move) }; @@ -7533,7 +7533,7 @@ export class AbilityCopyAttr extends MoveEffectAttr { user.setTempAbility(target.getAbility()); const ally = user.getAlly(); - if (this.copyToPartner && globalScene.currentBattle?.double && !isNullOrUndefined(ally) && ally.hp) { // TODO is this the best way to check that the ally is active? + if (this.copyToPartner && globalScene.currentBattle?.double && ally != null && ally.hp) { // TODO is this the best way to check that the ally is active? globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:copiedTargetAbility", { pokemonName: getPokemonNameWithAffix(ally), targetName: getPokemonNameWithAffix(target), abilityName: allAbilities[target.getAbility().id].name })); ally.setTempAbility(target.getAbility()); } @@ -8054,7 +8054,7 @@ const failIfGhostTypeCondition: MoveConditionFunc = (user: Pokemon, target: Poke const failIfNoTargetHeldItemsCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => target.getHeldItems().filter(i => i.isTransferable)?.length > 0; const attackedByItemMessageFunc = (user: Pokemon, target: Pokemon, move: Move) => { - if (isNullOrUndefined(target)) { // Fix bug when used against targets that have both fainted + if (target == null) { // Fix bug when used against targets that have both fainted return ""; } const heldItems = target.getHeldItems().filter(i => i.isTransferable); @@ -8611,7 +8611,7 @@ export function initMoves() { .attr(AddBattlerTagAttr, BattlerTagType.DISABLED, false, true) .condition((_user, target, _move) => { const lastNonVirtualMove = target.getLastNonVirtualMove(); - return !isNullOrUndefined(lastNonVirtualMove) && lastNonVirtualMove.move !== MoveId.STRUGGLE; + return lastNonVirtualMove != null && lastNonVirtualMove.move !== MoveId.STRUGGLE; }) .ignoresSubstitute() .reflectable(), @@ -9955,7 +9955,7 @@ export function initMoves() { .condition(failOnGravityCondition) .condition((_user, target, _move) => ![ SpeciesId.DIGLETT, SpeciesId.DUGTRIO, SpeciesId.ALOLA_DIGLETT, SpeciesId.ALOLA_DUGTRIO, SpeciesId.SANDYGAST, SpeciesId.PALOSSAND, SpeciesId.WIGLETT, SpeciesId.WUGTRIO ].includes(target.species.speciesId)) .condition((_user, target, _move) => !(target.species.speciesId === SpeciesId.GENGAR && target.getFormKey() === "mega")) - .condition((_user, target, _move) => isNullOrUndefined(target.getTag(BattlerTagType.INGRAIN)) && isNullOrUndefined(target.getTag(BattlerTagType.IGNORE_FLYING))) + .condition((_user, target, _move) => target.getTag(BattlerTagType.INGRAIN) == null && target.getTag(BattlerTagType.IGNORE_FLYING) == null) .attr(AddBattlerTagAttr, BattlerTagType.TELEKINESIS, false, true, 3) .attr(AddBattlerTagAttr, BattlerTagType.FLOATING, false, true, 3) .reflectable(), diff --git a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts index 5462c0eb336..00e98048ada 100644 --- a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts +++ b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts @@ -46,9 +46,9 @@ import { } from "#mystery-encounters/mystery-encounter-requirements"; import { getRandomPartyMemberFunc, trainerConfigs } from "#trainers/trainer-config"; import { TrainerPartyCompoundTemplate, TrainerPartyTemplate } from "#trainers/trainer-party-template"; -import { MoveInfoOverlay } from "#ui/containers/move-info-overlay"; -import type { OptionSelectItem } from "#ui/handlers/abstract-option-select-ui-handler"; -import { isNullOrUndefined, randSeedInt, randSeedShuffle } from "#utils/common"; +import type { OptionSelectItem } from "#ui/abstract-option-select-ui-handler"; +import { MoveInfoOverlay } from "#ui/move-info-overlay"; +import { randSeedInt, randSeedShuffle } from "#utils/common"; import i18next from "i18next"; /** the i18n namespace for the encounter */ @@ -571,7 +571,7 @@ function getTrainerConfigForWave(waveIndex: number) { .setPartyMemberFunc( 4, getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => { - if (!isNullOrUndefined(pool3Mon.formIndex)) { + if (pool3Mon.formIndex != null) { p.formIndex = pool3Mon.formIndex; p.generateAndPopulateMoveset(); p.generateName(); @@ -603,7 +603,7 @@ function getTrainerConfigForWave(waveIndex: number) { .setPartyMemberFunc( 3, getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => { - if (!isNullOrUndefined(pool3Mon.formIndex)) { + if (pool3Mon.formIndex != null) { p.formIndex = pool3Mon.formIndex; p.generateAndPopulateMoveset(); p.generateName(); @@ -613,7 +613,7 @@ function getTrainerConfigForWave(waveIndex: number) { .setPartyMemberFunc( 4, getRandomPartyMemberFunc([pool3Mon2.species], TrainerSlot.TRAINER, true, p => { - if (!isNullOrUndefined(pool3Mon2.formIndex)) { + if (pool3Mon2.formIndex != null) { p.formIndex = pool3Mon2.formIndex; p.generateAndPopulateMoveset(); p.generateName(); @@ -648,7 +648,7 @@ function getTrainerConfigForWave(waveIndex: number) { .setPartyMemberFunc( 3, getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => { - if (!isNullOrUndefined(pool3Mon.formIndex)) { + if (pool3Mon.formIndex != null) { p.formIndex = pool3Mon.formIndex; p.generateAndPopulateMoveset(); p.generateName(); @@ -687,7 +687,7 @@ function getTrainerConfigForWave(waveIndex: number) { .setPartyMemberFunc( 2, getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => { - if (!isNullOrUndefined(pool3Mon.formIndex)) { + if (pool3Mon.formIndex != null) { p.formIndex = pool3Mon.formIndex; p.generateAndPopulateMoveset(); p.generateName(); @@ -697,7 +697,7 @@ function getTrainerConfigForWave(waveIndex: number) { .setPartyMemberFunc( 3, getRandomPartyMemberFunc([pool3Mon2.species], TrainerSlot.TRAINER, true, p => { - if (!isNullOrUndefined(pool3Mon2.formIndex)) { + if (pool3Mon2.formIndex != null) { p.formIndex = pool3Mon2.formIndex; p.generateAndPopulateMoveset(); p.generateName(); diff --git a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts index 42907455e22..24ea7167864 100644 --- a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts +++ b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts @@ -45,7 +45,7 @@ import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encounter-option"; import { trainerConfigs } from "#trainers/trainer-config"; import { TrainerPartyCompoundTemplate, TrainerPartyTemplate } from "#trainers/trainer-party-template"; -import type { OptionSelectConfig } from "#ui/handlers/abstract-option-select-ui-handler"; +import type { OptionSelectConfig } from "#ui/abstract-option-select-ui-handler"; import { randSeedInt, randSeedShuffle } from "#utils/common"; import { getPokemonSpecies } from "#utils/pokemon-utils"; import i18next from "i18next"; diff --git a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts index 598f9d496a2..33512ff0760 100644 --- a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts +++ b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts @@ -37,7 +37,7 @@ import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encou import { MoveRequirement } from "#mystery-encounters/mystery-encounter-requirements"; import { DANCING_MOVES } from "#mystery-encounters/requirement-groups"; import { PokemonData } from "#system/pokemon-data"; -import type { OptionSelectItem } from "#ui/handlers/abstract-option-select-ui-handler"; +import type { OptionSelectItem } from "#ui/abstract-option-select-ui-handler"; import { getPokemonSpecies } from "#utils/pokemon-utils"; import i18next from "i18next"; diff --git a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts index 65d22bfc6de..426eafb5e67 100644 --- a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts +++ b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts @@ -15,7 +15,7 @@ import { getRandomPlayerPokemon, getRandomSpeciesByStarterCost } from "#mystery- import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encounter-option"; -import { isNullOrUndefined, randSeedInt } from "#utils/common"; +import { randSeedInt } from "#utils/common"; import { getPokemonSpecies } from "#utils/pokemon-utils"; /** i18n namespace for encounter */ @@ -192,7 +192,7 @@ export const DarkDealEncounter: MysteryEncounter = MysteryEncounterBuilder.withE }; }), }; - if (!isNullOrUndefined(bossSpecies.forms) && bossSpecies.forms.length > 0) { + if (bossSpecies.forms != null && bossSpecies.forms.length > 0) { pokemonConfig.formIndex = 0; } const config: EnemyPartyConfig = { diff --git a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts index 79cccd91b26..8cd4c8bee66 100644 --- a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts +++ b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts @@ -33,7 +33,7 @@ import { MoneyRequirement, } from "#mystery-encounters/mystery-encounter-requirements"; import i18next from "#plugins/i18n"; -import type { OptionSelectItem } from "#ui/handlers/abstract-option-select-ui-handler"; +import type { OptionSelectItem } from "#ui/abstract-option-select-ui-handler"; import { randSeedItem } from "#utils/common"; import { getPokemonSpecies } from "#utils/pokemon-utils"; diff --git a/src/data/mystery-encounters/encounters/field-trip-encounter.ts b/src/data/mystery-encounters/encounters/field-trip-encounter.ts index 30c4026fcad..81d9bce3a76 100644 --- a/src/data/mystery-encounters/encounters/field-trip-encounter.ts +++ b/src/data/mystery-encounters/encounters/field-trip-encounter.ts @@ -18,7 +18,7 @@ import { import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encounter-option"; -import type { OptionSelectItem } from "#ui/handlers/abstract-option-select-ui-handler"; +import type { OptionSelectItem } from "#ui/abstract-option-select-ui-handler"; import i18next from "i18next"; /** i18n namespace for the encounter */ diff --git a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts index 1cc31eaa21f..0f37a1fae94 100644 --- a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts +++ b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts @@ -45,7 +45,7 @@ import { TypeRequirement, } from "#mystery-encounters/mystery-encounter-requirements"; import { FIRE_RESISTANT_ABILITIES } from "#mystery-encounters/requirement-groups"; -import { isNullOrUndefined, randSeedInt } from "#utils/common"; +import { randSeedInt } from "#utils/common"; import { getPokemonSpecies } from "#utils/pokemon-utils"; /** the i18n namespace for the encounter */ @@ -238,7 +238,7 @@ export const FieryFalloutEncounter: MysteryEncounter = MysteryEncounterBuilder.w // Burn random member const burnable = nonFireTypes.filter( - p => isNullOrUndefined(p.status) || isNullOrUndefined(p.status.effect) || p.status.effect === StatusEffect.NONE, + p => p.status == null || p.status.effect == null || p.status.effect === StatusEffect.NONE, ); if (burnable?.length > 0) { const roll = randSeedInt(burnable.length); diff --git a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts index 7dbbe24fb69..e2166e99f6a 100644 --- a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts +++ b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts @@ -42,8 +42,8 @@ import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encou import { PartySizeRequirement } from "#mystery-encounters/mystery-encounter-requirements"; import { PokemonData } from "#system/pokemon-data"; import { MusicPreference } from "#system/settings"; -import type { OptionSelectItem } from "#ui/handlers/abstract-option-select-ui-handler"; -import { isNullOrUndefined, NumberHolder, randInt, randSeedInt, randSeedItem, randSeedShuffle } from "#utils/common"; +import type { OptionSelectItem } from "#ui/abstract-option-select-ui-handler"; +import { NumberHolder, randInt, randSeedInt, randSeedItem, randSeedShuffle } from "#utils/common"; import { getEnumKeys } from "#utils/enums"; import { getRandomLocaleEntry } from "#utils/i18n"; import { getPokemonSpecies } from "#utils/pokemon-utils"; @@ -537,7 +537,7 @@ function generateTradeOption(alreadyUsedSpecies: PokemonSpecies[], originalBst?: bstCap = originalBst + 100; bstMin = originalBst - 100; } - while (isNullOrUndefined(newSpecies)) { + while (newSpecies == null) { // Get all non-legendary species that fall within the Bst range requirements let validSpecies = allSpecies.filter(s => { const isLegendaryOrMythical = s.legendary || s.subLegendary || s.mythical; @@ -550,7 +550,7 @@ function generateTradeOption(alreadyUsedSpecies: PokemonSpecies[], originalBst?: if (validSpecies?.length > 20) { validSpecies = randSeedShuffle(validSpecies); newSpecies = validSpecies.pop(); - while (isNullOrUndefined(newSpecies) || alreadyUsedSpecies.includes(newSpecies)) { + while (newSpecies == null || alreadyUsedSpecies.includes(newSpecies)) { newSpecies = validSpecies.pop(); } } else { diff --git a/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts b/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts index 292c866c0ee..51efa0c7586 100644 --- a/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts @@ -28,7 +28,7 @@ import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encounter-option"; import { MoneyRequirement } from "#mystery-encounters/mystery-encounter-requirements"; import { PokemonData } from "#system/pokemon-data"; -import { isNullOrUndefined, randSeedInt, randSeedItem } from "#utils/common"; +import { randSeedInt, randSeedItem } from "#utils/common"; import { getPokemonSpecies } from "#utils/pokemon-utils"; /** the i18n namespace for this encounter */ @@ -81,7 +81,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter = MysteryEncounterBui let tries = 0; // Reroll any species that don't have HAs - while ((isNullOrUndefined(species.abilityHidden) || species.abilityHidden === AbilityId.NONE) && tries < 5) { + while ((species.abilityHidden == null || species.abilityHidden === AbilityId.NONE) && tries < 5) { species = getSalesmanSpeciesOffer(); tries++; } @@ -110,7 +110,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter = MysteryEncounterBui */ if ( r === 0 - || ((isNullOrUndefined(species.abilityHidden) || species.abilityHidden === AbilityId.NONE) + || ((species.abilityHidden == null || species.abilityHidden === AbilityId.NONE) && validEventEncounters.length === 0) ) { // If you roll 1%, give shiny Magikarp with random variant @@ -118,7 +118,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter = MysteryEncounterBui pokemon = new PlayerPokemon(species, 5, 2, undefined, undefined, true); } else if ( validEventEncounters.length > 0 - && (r <= EVENT_THRESHOLD || isNullOrUndefined(species.abilityHidden) || species.abilityHidden === AbilityId.NONE) + && (r <= EVENT_THRESHOLD || species.abilityHidden == null || species.abilityHidden === AbilityId.NONE) ) { tries = 0; do { diff --git a/src/data/mystery-encounters/encounters/training-session-encounter.ts b/src/data/mystery-encounters/encounters/training-session-encounter.ts index 796840c431f..1f3778a5d2c 100644 --- a/src/data/mystery-encounters/encounters/training-session-encounter.ts +++ b/src/data/mystery-encounters/encounters/training-session-encounter.ts @@ -27,8 +27,8 @@ import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encounter-option"; import { PokemonData } from "#system/pokemon-data"; import type { HeldModifierConfig } from "#types/held-modifier-config"; -import type { OptionSelectItem } from "#ui/handlers/abstract-option-select-ui-handler"; -import { isNullOrUndefined, randSeedShuffle } from "#utils/common"; +import type { OptionSelectItem } from "#ui/abstract-option-select-ui-handler"; +import { randSeedShuffle } from "#utils/common"; import { getEnumValues } from "#utils/enums"; import i18next from "i18next"; @@ -324,7 +324,7 @@ export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilde // Only update the fusion's dex data if the Pokemon is already caught in dex (ignore rentals) const rootFusionSpecies = playerPokemon.fusionSpecies?.getRootSpeciesId(); if ( - !isNullOrUndefined(rootFusionSpecies) + rootFusionSpecies != null && speciesStarterCosts.hasOwnProperty(rootFusionSpecies) && !!globalScene.gameData.dexData[rootFusionSpecies].caughtAttr ) { diff --git a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts index 7bbc4a57757..cd61a6852f7 100644 --- a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts +++ b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts @@ -32,7 +32,7 @@ import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encou import { MoveRequirement, PersistentModifierRequirement } from "#mystery-encounters/mystery-encounter-requirements"; import { CHARMING_MOVES } from "#mystery-encounters/requirement-groups"; import { PokemonData } from "#system/pokemon-data"; -import { isNullOrUndefined, randSeedInt } from "#utils/common"; +import { randSeedInt } from "#utils/common"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/uncommonBreed"; @@ -167,7 +167,7 @@ export const UncommonBreedEncounter: MysteryEncounter = MysteryEncounterBuilder. const encounter = globalScene.currentBattle.mysteryEncounter!; const eggMove = encounter.misc.eggMove; - if (!isNullOrUndefined(eggMove)) { + if (eggMove != null) { // Check what type of move the egg move is to determine target const pokemonMove = new PokemonMove(eggMove); const move = pokemonMove.getMove(); diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index 1fcbd2961d1..abd81fb92ea 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -41,7 +41,7 @@ import { PokemonData } from "#system/pokemon-data"; import { trainerConfigs } from "#trainers/trainer-config"; import { TrainerPartyTemplate } from "#trainers/trainer-party-template"; import type { HeldModifierConfig } from "#types/held-modifier-config"; -import { isNullOrUndefined, NumberHolder, randSeedInt, randSeedShuffle } from "#utils/common"; +import { NumberHolder, randSeedInt, randSeedShuffle } from "#utils/common"; import { getPokemonSpecies } from "#utils/pokemon-utils"; /** i18n namespace for encounter */ @@ -634,7 +634,7 @@ function getTransformedSpecies( alreadyUsedSpecies: PokemonSpecies[], ): PokemonSpecies { let newSpecies: PokemonSpecies | undefined; - while (isNullOrUndefined(newSpecies)) { + while (newSpecies == null) { const bstCap = originalBst + bstSearchRange[1]; const bstMin = Math.max(originalBst + bstSearchRange[0], 0); @@ -655,7 +655,7 @@ function getTransformedSpecies( if (validSpecies?.length > 20) { validSpecies = randSeedShuffle(validSpecies); newSpecies = validSpecies.pop(); - while (isNullOrUndefined(newSpecies) || alreadyUsedSpecies.includes(newSpecies)) { + while (newSpecies == null || alreadyUsedSpecies.includes(newSpecies)) { newSpecies = validSpecies.pop(); } } else { @@ -771,12 +771,12 @@ async function addEggMoveToNewPokemonMoveset( if (eggMoves) { const eggMoveIndices = randSeedShuffle([0, 1, 2, 3]); let randomEggMoveIndex = eggMoveIndices.pop(); - let randomEggMove = !isNullOrUndefined(randomEggMoveIndex) ? eggMoves[randomEggMoveIndex] : null; + let randomEggMove = randomEggMoveIndex != null ? eggMoves[randomEggMoveIndex] : null; let retries = 0; while (retries < 3 && (!randomEggMove || newPokemon.moveset.some(m => m.moveId === randomEggMove))) { // If Pokemon already knows this move, roll for another egg move randomEggMoveIndex = eggMoveIndices.pop(); - randomEggMove = !isNullOrUndefined(randomEggMoveIndex) ? eggMoves[randomEggMoveIndex] : null; + randomEggMove = randomEggMoveIndex != null ? eggMoves[randomEggMoveIndex] : null; retries++; } @@ -791,11 +791,7 @@ async function addEggMoveToNewPokemonMoveset( } // For pokemon that the player owns (including ones just caught), unlock the egg move - if ( - !forBattle - && !isNullOrUndefined(randomEggMoveIndex) - && !!globalScene.gameData.dexData[speciesRootForm].caughtAttr - ) { + if (!forBattle && randomEggMoveIndex != null && !!globalScene.gameData.dexData[speciesRootForm].caughtAttr) { await globalScene.gameData.setEggMoveUnlocked(getPokemonSpecies(speciesRootForm), randomEggMoveIndex, true); } } diff --git a/src/data/mystery-encounters/mystery-encounter-option.ts b/src/data/mystery-encounters/mystery-encounter-option.ts index fc7bb15d343..1b3b260414d 100644 --- a/src/data/mystery-encounters/mystery-encounter-option.ts +++ b/src/data/mystery-encounters/mystery-encounter-option.ts @@ -12,7 +12,7 @@ import { MoneyRequirement, TypeRequirement, } from "#mystery-encounters/mystery-encounter-requirements"; -import { isNullOrUndefined, randSeedInt } from "#utils/common"; +import { randSeedInt } from "#utils/common"; // biome-ignore lint/suspicious/noConfusingVoidType: void unions in callbacks are OK export type OptionPhaseCallback = () => Promise; @@ -62,7 +62,7 @@ export class MysteryEncounterOption implements IMysteryEncounterOption { onPostOptionPhase?: OptionPhaseCallback; constructor(option: IMysteryEncounterOption | null) { - if (!isNullOrUndefined(option)) { + if (option != null) { Object.assign(this, option); } this.hasDexProgress = this.hasDexProgress ?? false; diff --git a/src/data/mystery-encounters/mystery-encounter-requirements.ts b/src/data/mystery-encounters/mystery-encounter-requirements.ts index f20d513419e..85906044b77 100644 --- a/src/data/mystery-encounters/mystery-encounter-requirements.ts +++ b/src/data/mystery-encounters/mystery-encounter-requirements.ts @@ -15,7 +15,7 @@ import { WeatherType } from "#enums/weather-type"; import type { PlayerPokemon } from "#field/pokemon"; import { AttackTypeBoosterModifier } from "#modifiers/modifier"; import type { AttackTypeBoosterModifierType } from "#modifiers/modifier-type"; -import { coerceArray, isNullOrUndefined } from "#utils/common"; +import { coerceArray } from "#utils/common"; export interface EncounterRequirement { meetsRequirement(): boolean; // Boolean to see if a requirement is met @@ -219,7 +219,7 @@ export class WaveRangeRequirement extends EncounterSceneRequirement { } override meetsRequirement(): boolean { - if (!isNullOrUndefined(this.waveRange) && this.waveRange[0] <= this.waveRange[1]) { + if (this.waveRange != null && this.waveRange[0] <= this.waveRange[1]) { const waveIndex = globalScene.currentBattle.waveIndex; if ( (waveIndex >= 0 && this.waveRange[0] >= 0 && this.waveRange[0] > waveIndex) @@ -275,11 +275,7 @@ export class TimeOfDayRequirement extends EncounterSceneRequirement { override meetsRequirement(): boolean { const timeOfDay = globalScene.arena?.getTimeOfDay(); - return !( - !isNullOrUndefined(timeOfDay) - && this.requiredTimeOfDay?.length > 0 - && !this.requiredTimeOfDay.includes(timeOfDay) - ); + return !(timeOfDay != null && this.requiredTimeOfDay?.length > 0 && !this.requiredTimeOfDay.includes(timeOfDay)); } override getDialogueToken(_pokemon?: PlayerPokemon): [string, string] { @@ -298,7 +294,7 @@ export class WeatherRequirement extends EncounterSceneRequirement { override meetsRequirement(): boolean { const currentWeather = globalScene.arena.weather?.weatherType; return !( - !isNullOrUndefined(currentWeather) + currentWeather != null && this.requiredWeather?.length > 0 && !this.requiredWeather.includes(currentWeather!) ); @@ -307,7 +303,7 @@ export class WeatherRequirement extends EncounterSceneRequirement { override getDialogueToken(_pokemon?: PlayerPokemon): [string, string] { const currentWeather = globalScene.arena.weather?.weatherType; let token = ""; - if (!isNullOrUndefined(currentWeather)) { + if (currentWeather != null) { token = WeatherType[currentWeather].replace("_", " ").toLocaleLowerCase(); } return ["weather", token]; @@ -331,7 +327,7 @@ export class PartySizeRequirement extends EncounterSceneRequirement { } override meetsRequirement(): boolean { - if (!isNullOrUndefined(this.partySizeRange) && this.partySizeRange[0] <= this.partySizeRange[1]) { + if (this.partySizeRange != null && this.partySizeRange[0] <= this.partySizeRange[1]) { const partySize = this.excludeDisallowedPokemon ? globalScene.getPokemonAllowedInBattle().length : globalScene.getPlayerParty().length; @@ -363,7 +359,7 @@ export class PersistentModifierRequirement extends EncounterSceneRequirement { override meetsRequirement(): boolean { const partyPokemon = globalScene.getPlayerParty(); - if (isNullOrUndefined(partyPokemon) || this.requiredHeldItemModifiers?.length < 0) { + if (partyPokemon == null || this.requiredHeldItemModifiers?.length < 0) { return false; } let modifierCount = 0; @@ -396,7 +392,7 @@ export class MoneyRequirement extends EncounterSceneRequirement { override meetsRequirement(): boolean { const money = globalScene.money; - if (isNullOrUndefined(money)) { + if (money == null) { return false; } @@ -429,7 +425,7 @@ export class SpeciesRequirement extends EncounterPokemonRequirement { override meetsRequirement(): boolean { const partyPokemon = globalScene.getPlayerParty(); - if (isNullOrUndefined(partyPokemon) || this.requiredSpecies?.length < 0) { + if (partyPokemon == null || this.requiredSpecies?.length < 0) { return false; } return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon; @@ -469,7 +465,7 @@ export class NatureRequirement extends EncounterPokemonRequirement { override meetsRequirement(): boolean { const partyPokemon = globalScene.getPlayerParty(); - if (isNullOrUndefined(partyPokemon) || this.requiredNature?.length < 0) { + if (partyPokemon == null || this.requiredNature?.length < 0) { return false; } return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon; @@ -484,7 +480,7 @@ export class NatureRequirement extends EncounterPokemonRequirement { } override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { - if (!isNullOrUndefined(pokemon?.nature) && this.requiredNature.includes(pokemon.nature)) { + if (pokemon?.nature != null && this.requiredNature.includes(pokemon.nature)) { return ["nature", Nature[pokemon.nature]]; } return ["nature", ""]; @@ -508,7 +504,7 @@ export class TypeRequirement extends EncounterPokemonRequirement { override meetsRequirement(): boolean { let partyPokemon = globalScene.getPlayerParty(); - if (isNullOrUndefined(partyPokemon)) { + if (partyPokemon == null) { return false; } @@ -561,7 +557,7 @@ export class MoveRequirement extends EncounterPokemonRequirement { override meetsRequirement(): boolean { const partyPokemon = globalScene.getPlayerParty(); - if (isNullOrUndefined(partyPokemon) || this.requiredMoves?.length < 0) { + if (partyPokemon == null || this.requiredMoves?.length < 0) { return false; } return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon; @@ -612,7 +608,7 @@ export class CompatibleMoveRequirement extends EncounterPokemonRequirement { override meetsRequirement(): boolean { const partyPokemon = globalScene.getPlayerParty(); - if (isNullOrUndefined(partyPokemon) || this.requiredMoves?.length < 0) { + if (partyPokemon == null || this.requiredMoves?.length < 0) { return false; } return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon; @@ -668,7 +664,7 @@ export class AbilityRequirement extends EncounterPokemonRequirement { override meetsRequirement(): boolean { const partyPokemon = globalScene.getPlayerParty(); - if (isNullOrUndefined(partyPokemon) || this.requiredAbilities?.length < 0) { + if (partyPokemon == null || this.requiredAbilities?.length < 0) { return false; } return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon; @@ -692,7 +688,7 @@ export class AbilityRequirement extends EncounterPokemonRequirement { override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { const matchingAbility = this.requiredAbilities.find(a => pokemon?.hasAbility(a, false)); - if (!isNullOrUndefined(matchingAbility)) { + if (matchingAbility != null) { return ["ability", allAbilities[matchingAbility].name]; } return ["ability", ""]; @@ -713,7 +709,7 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement { override meetsRequirement(): boolean { const partyPokemon = globalScene.getPlayerParty(); - if (isNullOrUndefined(partyPokemon) || this.requiredStatusEffect?.length < 0) { + if (partyPokemon == null || this.requiredStatusEffect?.length < 0) { return false; } const x = this.queryParty(partyPokemon).length >= this.minNumberOfPokemon; @@ -727,11 +723,7 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement { return this.requiredStatusEffect.some(statusEffect => { if (statusEffect === StatusEffect.NONE) { // StatusEffect.NONE also checks for null or undefined status - return ( - isNullOrUndefined(pokemon.status) - || isNullOrUndefined(pokemon.status.effect) - || pokemon.status.effect === statusEffect - ); + return pokemon.status == null || pokemon.status.effect == null || pokemon.status.effect === statusEffect; } return pokemon.status?.effect === statusEffect; }); @@ -742,11 +734,7 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement { return !this.requiredStatusEffect.some(statusEffect => { if (statusEffect === StatusEffect.NONE) { // StatusEffect.NONE also checks for null or undefined status - return ( - isNullOrUndefined(pokemon.status) - || isNullOrUndefined(pokemon.status.effect) - || pokemon.status.effect === statusEffect - ); + return pokemon.status == null || pokemon.status.effect == null || pokemon.status.effect === statusEffect; } return pokemon.status?.effect === statusEffect; }); @@ -756,9 +744,7 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement { override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { const reqStatus = this.requiredStatusEffect.filter(a => { if (a === StatusEffect.NONE) { - return ( - isNullOrUndefined(pokemon?.status) || isNullOrUndefined(pokemon.status.effect) || pokemon.status.effect === a - ); + return pokemon?.status == null || pokemon.status.effect == null || pokemon.status.effect === a; } return pokemon!.status?.effect === a; }); @@ -788,7 +774,7 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen override meetsRequirement(): boolean { const partyPokemon = globalScene.getPlayerParty(); - if (isNullOrUndefined(partyPokemon) || this.requiredFormChangeItem?.length < 0) { + if (partyPokemon == null || this.requiredFormChangeItem?.length < 0) { return false; } return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon; @@ -847,7 +833,7 @@ export class HeldItemRequirement extends EncounterPokemonRequirement { override meetsRequirement(): boolean { const partyPokemon = globalScene.getPlayerParty(); - if (isNullOrUndefined(partyPokemon)) { + if (partyPokemon == null) { return false; } return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon; @@ -911,7 +897,7 @@ export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRe override meetsRequirement(): boolean { const partyPokemon = globalScene.getPlayerParty(); - if (isNullOrUndefined(partyPokemon)) { + if (partyPokemon == null) { return false; } return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon; @@ -978,7 +964,7 @@ export class LevelRequirement extends EncounterPokemonRequirement { override meetsRequirement(): boolean { // Party Pokemon inside required level range - if (!isNullOrUndefined(this.requiredLevelRange) && this.requiredLevelRange[0] <= this.requiredLevelRange[1]) { + if (this.requiredLevelRange != null && this.requiredLevelRange[0] <= this.requiredLevelRange[1]) { const partyPokemon = globalScene.getPlayerParty(); const pokemonInRange = this.queryParty(partyPokemon); if (pokemonInRange.length < this.minNumberOfPokemon) { @@ -1019,10 +1005,7 @@ export class FriendshipRequirement extends EncounterPokemonRequirement { override meetsRequirement(): boolean { // Party Pokemon inside required friendship range - if ( - !isNullOrUndefined(this.requiredFriendshipRange) - && this.requiredFriendshipRange[0] <= this.requiredFriendshipRange[1] - ) { + if (this.requiredFriendshipRange != null && this.requiredFriendshipRange[0] <= this.requiredFriendshipRange[1]) { const partyPokemon = globalScene.getPlayerParty(); const pokemonInRange = this.queryParty(partyPokemon); if (pokemonInRange.length < this.minNumberOfPokemon) { @@ -1071,7 +1054,7 @@ export class HealthRatioRequirement extends EncounterPokemonRequirement { override meetsRequirement(): boolean { // Party Pokemon's health inside required health range - if (!isNullOrUndefined(this.requiredHealthRange) && this.requiredHealthRange[0] <= this.requiredHealthRange[1]) { + if (this.requiredHealthRange != null && this.requiredHealthRange[0] <= this.requiredHealthRange[1]) { const partyPokemon = globalScene.getPlayerParty(); const pokemonInRange = this.queryParty(partyPokemon); if (pokemonInRange.length < this.minNumberOfPokemon) { @@ -1098,7 +1081,7 @@ export class HealthRatioRequirement extends EncounterPokemonRequirement { override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { const hpRatio = pokemon?.getHpRatio(); - if (!isNullOrUndefined(hpRatio)) { + if (hpRatio != null) { return ["healthRatio", Math.floor(hpRatio * 100).toString() + "%"]; } return ["healthRatio", ""]; @@ -1119,7 +1102,7 @@ export class WeightRequirement extends EncounterPokemonRequirement { override meetsRequirement(): boolean { // Party Pokemon's weight inside required weight range - if (!isNullOrUndefined(this.requiredWeightRange) && this.requiredWeightRange[0] <= this.requiredWeightRange[1]) { + if (this.requiredWeightRange != null && this.requiredWeightRange[0] <= this.requiredWeightRange[1]) { const partyPokemon = globalScene.getPlayerParty(); const pokemonInRange = this.queryParty(partyPokemon); if (pokemonInRange.length < this.minNumberOfPokemon) { diff --git a/src/data/mystery-encounters/mystery-encounter-save-data.ts b/src/data/mystery-encounters/mystery-encounter-save-data.ts index f04abccba5f..71cd2517a95 100644 --- a/src/data/mystery-encounters/mystery-encounter-save-data.ts +++ b/src/data/mystery-encounters/mystery-encounter-save-data.ts @@ -1,7 +1,6 @@ import { BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT } from "#app/constants"; import type { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; -import { isNullOrUndefined } from "#utils/common"; export class SeenEncounterData { type: MysteryEncounterType; @@ -28,7 +27,7 @@ export class MysteryEncounterSaveData { queuedEncounters: QueuedEncounter[] = []; constructor(data?: MysteryEncounterSaveData) { - if (!isNullOrUndefined(data)) { + if (data != null) { Object.assign(this, data); } diff --git a/src/data/mystery-encounters/mystery-encounter.ts b/src/data/mystery-encounters/mystery-encounter.ts index 273e14248e6..f18660b5d71 100644 --- a/src/data/mystery-encounters/mystery-encounter.ts +++ b/src/data/mystery-encounters/mystery-encounter.ts @@ -25,7 +25,7 @@ import { StatusEffectRequirement, WaveRangeRequirement, } from "#mystery-encounters/mystery-encounter-requirements"; -import { coerceArray, isNullOrUndefined, randSeedInt } from "#utils/common"; +import { coerceArray, randSeedInt } from "#utils/common"; import { capitalizeFirstLetter } from "#utils/strings"; export interface EncounterStartOfBattleEffect { @@ -275,7 +275,7 @@ export class MysteryEncounter implements IMysteryEncounter { private seedOffset?: any; constructor(encounter: IMysteryEncounter | null) { - if (!isNullOrUndefined(encounter)) { + if (encounter != null) { Object.assign(this, encounter); } this.encounterTier = this.encounterTier ?? MysteryEncounterTier.COMMON; diff --git a/src/data/mystery-encounters/requirements/can-learn-move-requirement.ts b/src/data/mystery-encounters/requirements/can-learn-move-requirement.ts index 26602b8ae31..a5810406ef9 100644 --- a/src/data/mystery-encounters/requirements/can-learn-move-requirement.ts +++ b/src/data/mystery-encounters/requirements/can-learn-move-requirement.ts @@ -3,7 +3,7 @@ import type { MoveId } from "#enums/move-id"; import type { PlayerPokemon } from "#field/pokemon"; import { PokemonMove } from "#moves/pokemon-move"; import { EncounterPokemonRequirement } from "#mystery-encounters/mystery-encounter-requirements"; -import { coerceArray, isNullOrUndefined } from "#utils/common"; +import { coerceArray } from "#utils/common"; /** * {@linkcode CanLearnMoveRequirement} options @@ -44,7 +44,7 @@ export class CanLearnMoveRequirement extends EncounterPokemonRequirement { .getPlayerParty() .filter(pkm => (this.includeFainted ? pkm.isAllowedInChallenge() : pkm.isAllowedInBattle())); - if (isNullOrUndefined(partyPokemon) || this.requiredMoves?.length < 0) { + if (partyPokemon == null || this.requiredMoves?.length < 0) { return false; } diff --git a/src/data/mystery-encounters/utils/encounter-dialogue-utils.ts b/src/data/mystery-encounters/utils/encounter-dialogue-utils.ts index 1a9b008f9e9..be681f731e8 100644 --- a/src/data/mystery-encounters/utils/encounter-dialogue-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-dialogue-utils.ts @@ -1,7 +1,6 @@ import { globalScene } from "#app/global-scene"; import type { TextStyle } from "#enums/text-style"; import { getTextWithColors } from "#ui/text"; -import { isNullOrUndefined } from "#utils/common"; import i18next from "i18next"; /** @@ -11,7 +10,7 @@ import i18next from "i18next"; * @param primaryStyle Can define a text style to be applied to the entire string. Must be defined for BBCodeText styles to be applied correctly */ export function getEncounterText(keyOrString?: string, primaryStyle?: TextStyle): string | null { - if (isNullOrUndefined(keyOrString)) { + if (keyOrString == null) { return null; } diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index 0903624d903..86cd3fa3a32 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -46,10 +46,10 @@ import type { PokemonData } from "#system/pokemon-data"; import type { TrainerConfig } from "#trainers/trainer-config"; import { trainerConfigs } from "#trainers/trainer-config"; import type { HeldModifierConfig } from "#types/held-modifier-config"; -import type { OptionSelectConfig, OptionSelectItem } from "#ui/handlers/abstract-option-select-ui-handler"; -import type { PartyOption, PokemonSelectFilter } from "#ui/handlers/party-ui-handler"; -import { PartyUiMode } from "#ui/handlers/party-ui-handler"; -import { coerceArray, isNullOrUndefined, randomString, randSeedInt, randSeedItem } from "#utils/common"; +import type { OptionSelectConfig, OptionSelectItem } from "#ui/abstract-option-select-ui-handler"; +import type { PartyOption, PokemonSelectFilter } from "#ui/party-ui-handler"; +import { PartyUiMode } from "#ui/party-ui-handler"; +import { coerceArray, randomString, randSeedInt, randSeedItem } from "#utils/common"; import { getPokemonSpecies } from "#utils/pokemon-utils"; import i18next from "i18next"; @@ -143,7 +143,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig): const trainerType = partyConfig?.trainerType; const partyTrainerConfig = partyConfig?.trainerConfig; let trainerConfig: TrainerConfig; - if (!isNullOrUndefined(trainerType) || partyTrainerConfig) { + if (trainerType != null || partyTrainerConfig) { globalScene.currentBattle.mysteryEncounter!.encounterMode = MysteryEncounterMode.TRAINER_BATTLE; if (globalScene.currentBattle.trainer) { globalScene.currentBattle.trainer.setVisible(false); @@ -154,7 +154,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig): const doubleTrainer = trainerConfig.doubleOnly || (trainerConfig.hasDouble && !!partyConfig.doubleBattle); doubleBattle = doubleTrainer; - const trainerFemale = isNullOrUndefined(partyConfig.female) ? !!randSeedInt(2) : partyConfig.female; + const trainerFemale = partyConfig.female == null ? !!randSeedInt(2) : partyConfig.female; const newTrainer = new Trainer( trainerConfig.trainerType, doubleTrainer ? TrainerVariant.DOUBLE : trainerFemale ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT, @@ -202,7 +202,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig): let dataSource: PokemonData | undefined; let isBoss = false; if (!loaded) { - if ((!isNullOrUndefined(trainerType) || trainerConfig) && battle.trainer) { + if ((trainerType != null || trainerConfig) && battle.trainer) { // Allows overriding a trainer's pokemon to use specific species/data if (partyConfig?.pokemonConfigs && e < partyConfig.pokemonConfigs.length) { const config = partyConfig.pokemonConfigs[e]; @@ -258,7 +258,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig): enemyPokemon.resetSummonData(); } - if ((!loaded && isNullOrUndefined(partyConfig.countAsSeen)) || partyConfig.countAsSeen) { + if ((!loaded && partyConfig.countAsSeen == null) || partyConfig.countAsSeen) { globalScene.gameData.setPokemonSeen(enemyPokemon, true, !!(trainerType || trainerConfig)); } @@ -266,7 +266,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig): const config = partyConfig.pokemonConfigs[e]; // Set form - if (!isNullOrUndefined(config.nickname)) { + if (config.nickname != null) { enemyPokemon.nickname = btoa(unescape(encodeURIComponent(config.nickname))); } @@ -276,22 +276,22 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig): } // Set form - if (!isNullOrUndefined(config.formIndex)) { + if (config.formIndex != null) { enemyPokemon.formIndex = config.formIndex; } // Set shiny - if (!isNullOrUndefined(config.shiny)) { + if (config.shiny != null) { enemyPokemon.shiny = config.shiny; } // Set Variant - if (enemyPokemon.shiny && !isNullOrUndefined(config.variant)) { + if (enemyPokemon.shiny && config.variant != null) { enemyPokemon.variant = config.variant; } // Set custom mystery encounter data fields (such as sprite scale, custom abilities, types, etc.) - if (!isNullOrUndefined(config.customPokemonData)) { + if (config.customPokemonData != null) { enemyPokemon.customPokemonData = config.customPokemonData; } @@ -300,7 +300,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig): let segments = config.bossSegments ?? globalScene.getEncounterBossSegments(globalScene.currentBattle.waveIndex, level, enemySpecies, true); - if (!isNullOrUndefined(config.bossSegmentModifier)) { + if (config.bossSegmentModifier != null) { segments += config.bossSegmentModifier; } enemyPokemon.setBoss(true, segments); @@ -335,18 +335,18 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig): } // Set ability - if (!isNullOrUndefined(config.abilityIndex)) { + if (config.abilityIndex != null) { enemyPokemon.abilityIndex = config.abilityIndex; } // Set gender - if (!isNullOrUndefined(config.gender)) { + if (config.gender != null) { enemyPokemon.gender = config.gender!; enemyPokemon.summonData.gender = config.gender; } // Set AI type - if (!isNullOrUndefined(config.aiType)) { + if (config.aiType != null) { enemyPokemon.aiType = config.aiType; } diff --git a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts index 0d07300d00d..01d4659d379 100644 --- a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts @@ -31,11 +31,11 @@ import { showEncounterText, } from "#mystery-encounters/encounter-dialogue-utils"; import { achvs } from "#system/achv"; -import type { PartyOption } from "#ui/handlers/party-ui-handler"; -import { PartyUiMode } from "#ui/handlers/party-ui-handler"; -import { SummaryUiMode } from "#ui/handlers/summary-ui-handler"; +import type { PartyOption } from "#ui/party-ui-handler"; +import { PartyUiMode } from "#ui/party-ui-handler"; +import { SummaryUiMode } from "#ui/summary-ui-handler"; import { applyChallenges } from "#utils/challenge-utils"; -import { BooleanHolder, isNullOrUndefined, randSeedInt } from "#utils/common"; +import { BooleanHolder, randSeedInt } from "#utils/common"; import { getPokemonSpecies } from "#utils/pokemon-utils"; import i18next from "i18next"; @@ -276,7 +276,7 @@ export function getRandomSpeciesByStarterCost( if (types && types.length > 0) { filteredSpecies = filteredSpecies.filter( - s => types.includes(s[0].type1) || (!isNullOrUndefined(s[0].type2) && types.includes(s[0].type2)), + s => types.includes(s[0].type1) || (s[0].type2 != null && types.includes(s[0].type2)), ); } diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index 2d76c2c0400..7c00bf5dff7 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -28,7 +28,7 @@ import type { Variant, VariantSet } from "#sprites/variant"; import { populateVariantColorCache, variantColorCache, variantData } from "#sprites/variant"; import type { Localizable } from "#types/locales"; import type { StarterMoveset } from "#types/save-data"; -import { isNullOrUndefined, randSeedFloat, randSeedGauss, randSeedInt } from "#utils/common"; +import { randSeedFloat, randSeedGauss, randSeedInt } from "#utils/common"; import { getPokemonSpecies } from "#utils/pokemon-utils"; import { toCamelCase, toPascalCase } from "#utils/strings"; import { argbFromRgba, QuantizerCelebi, rgbaFromArgb } from "@material/material-color-utilities"; @@ -197,7 +197,7 @@ export abstract class PokemonSpeciesForm { * @returns The id of the ability */ getPassiveAbility(formIndex?: number): AbilityId { - if (isNullOrUndefined(formIndex)) { + if (formIndex == null) { formIndex = this.formIndex; } let starterSpeciesId = this.speciesId; @@ -551,7 +551,7 @@ export abstract class PokemonSpeciesForm { const spriteKey = this.getSpriteKey(female, formIndex, shiny, variant, back); globalScene.loadPokemonAtlas(spriteKey, this.getSpriteAtlasPath(female, formIndex, shiny, variant, back)); globalScene.load.audio(this.getCryKey(formIndex), `audio/${this.getCryKey(formIndex)}.m4a`); - if (!isNullOrUndefined(variant)) { + if (variant != null) { await this.loadVariantColors(spriteKey, female, variant, back, formIndex); } return new Promise(resolve => { @@ -579,7 +579,7 @@ export abstract class PokemonSpeciesForm { const spritePath = this.getSpriteAtlasPath(female, formIndex, shiny, variant, back) .replace("variant/", "") .replace(/_[1-3]$/, ""); - if (!isNullOrUndefined(variant)) { + if (variant != null) { loadPokemonVariantAssets(spriteKey, spritePath, variant).then(() => resolve()); } }); @@ -791,7 +791,7 @@ export class PokemonSpecies extends PokemonSpeciesForm implements Localizable { * @returns A randomly rolled gender based on this Species' {@linkcode malePercent}. */ generateGender(): Gender { - if (isNullOrUndefined(this.malePercent)) { + if (this.malePercent == null) { return Gender.GENDERLESS; } diff --git a/src/data/pokemon/pokemon-data.ts b/src/data/pokemon/pokemon-data.ts index 87ffbbab4cd..4fbb70bccb2 100644 --- a/src/data/pokemon/pokemon-data.ts +++ b/src/data/pokemon/pokemon-data.ts @@ -16,7 +16,6 @@ import type { AttackMoveResult } from "#types/attack-move-result"; import type { IllusionData } from "#types/illusion-data"; import type { TurnMove } from "#types/turn-move"; import type { CoerceNullPropertiesToUndefined } from "#types/type-helpers"; -import { isNullOrUndefined } from "#utils/common"; import { getPokemonSpeciesForm } from "#utils/pokemon-utils"; /** @@ -64,14 +63,14 @@ function deserializePokemonSpeciesForm(value: SerializedSpeciesForm | PokemonSpe // @ts-expect-error: We may be deserializing a PokemonSpeciesForm, but we catch later on let { id, formIdx } = value; - if (isNullOrUndefined(id) || isNullOrUndefined(formIdx)) { + if (id == null || formIdx == null) { // @ts-expect-error: Typescript doesn't know that in block, `value` must be a PokemonSpeciesForm id = value.speciesId; // @ts-expect-error: Same as above (plus we are accessing a protected property) formIdx = value._formIndex; } // If for some reason either of these fields are null/undefined, we cannot reconstruct the species form - if (isNullOrUndefined(id) || isNullOrUndefined(formIdx)) { + if (id == null || formIdx == null) { return null; } return getPokemonSpeciesForm(id, formIdx); @@ -151,13 +150,13 @@ export class PokemonSummonData { public moveHistory: TurnMove[] = []; constructor(source?: PokemonSummonData | SerializedPokemonSummonData) { - if (isNullOrUndefined(source)) { + if (source == null) { return; } // TODO: Rework this into an actual generic function for use elsewhere for (const [key, value] of Object.entries(source)) { - if (isNullOrUndefined(value) && this.hasOwnProperty(key)) { + if (value == null && this.hasOwnProperty(key)) { continue; } @@ -171,7 +170,7 @@ export class PokemonSummonData { const illusionData = { ...value, }; - if (!isNullOrUndefined(illusionData.fusionSpecies)) { + if (illusionData.fusionSpecies != null) { switch (typeof illusionData.fusionSpecies) { case "object": illusionData.fusionSpecies = allSpecies[illusionData.fusionSpecies.speciesId]; @@ -224,18 +223,18 @@ export class PokemonSummonData { CoerceNullPropertiesToUndefined, "speciesForm" | "fusionSpeciesForm" | "illusion" >), - speciesForm: isNullOrUndefined(speciesForm) - ? undefined - : { id: speciesForm.speciesId, formIdx: speciesForm.formIndex }, - fusionSpeciesForm: isNullOrUndefined(fusionSpeciesForm) - ? undefined - : { id: fusionSpeciesForm.speciesId, formIdx: fusionSpeciesForm.formIndex }, - illusion: isNullOrUndefined(illusion) - ? undefined - : { - ...(this.illusion as Omit), - fusionSpecies: illusionSpeciesForm?.speciesId, - }, + speciesForm: speciesForm == null ? undefined : { id: speciesForm.speciesId, formIdx: speciesForm.formIndex }, + fusionSpeciesForm: + fusionSpeciesForm == null + ? undefined + : { id: fusionSpeciesForm.speciesId, formIdx: fusionSpeciesForm.formIndex }, + illusion: + illusion == null + ? undefined + : { + ...(this.illusion as Omit), + fusionSpecies: illusionSpeciesForm?.speciesId, + }, }; // Replace `null` with `undefined`, as `undefined` never gets serialized for (const [key, value] of Object.entries(t)) { @@ -278,7 +277,7 @@ export class PokemonBattleData { public berriesEaten: BerryType[] = []; constructor(source?: PokemonBattleData | Partial) { - if (!isNullOrUndefined(source)) { + if (source != null) { this.hitCount = source.hitCount ?? 0; this.hasEatenBerry = source.hasEatenBerry ?? false; this.berriesEaten = source.berriesEaten ?? []; diff --git a/src/data/splash-messages.ts b/src/data/splash-messages.ts index 21e7e5d05e6..a7791af78ac 100644 --- a/src/data/splash-messages.ts +++ b/src/data/splash-messages.ts @@ -234,7 +234,7 @@ const seasonalSplashMessages: Season[] = [ "valentines.happyValentines", "valentines.fullOfLove", "valentines.applinForYou", - "valentines.thePowerOfLoveIsThreeThirtyBST", + "valentines.thePowerOfLoveIsThreeThirtyBst", "valentines.haveAHeartScale", "valentines.i<3You", ], @@ -265,7 +265,7 @@ const seasonalSplashMessages: Season[] = [ "aprilFools.whoIsFinn", "aprilFools.watchOutForShadowPokemon", "aprilFools.nowWithDarkTypeLuxray", - "aprilFools.onlyOnPokerogueNetAGAIN", + "aprilFools.onlyOnPokerogueNetAgain", "aprilFools.noFreeVouchers", "aprilFools.altffourAchievementPoints", "aprilFools.rokePogue", diff --git a/src/data/trainers/trainer-config.ts b/src/data/trainers/trainer-config.ts index 9d891444829..b5786d1f0a2 100644 --- a/src/data/trainers/trainer-config.ts +++ b/src/data/trainers/trainer-config.ts @@ -3,6 +3,8 @@ import { globalScene } from "#app/global-scene"; import { pokemonEvolutions, pokemonPrevolutions } from "#balance/pokemon-evolutions"; import { signatureSpecies } from "#balance/signature-species"; import { tmSpecies } from "#balance/tms"; +// biome-ignore lint/correctness/noUnusedImports: Used in a tsdoc comment +import type { RARE_EGG_MOVE_LEVEL_REQUIREMENT } from "#data/balance/moveset-generation"; import { modifierTypes } from "#data/data-lists"; import { doubleBattleDialogue } from "#data/double-battle-dialogue"; import { Gender } from "#data/gender"; @@ -41,7 +43,8 @@ import type { TrainerConfigs, TrainerTierPools, } from "#types/trainer-funcs"; -import { coerceArray, isNullOrUndefined, randSeedInt, randSeedIntRange, randSeedItem } from "#utils/common"; +import type { Mutable } from "#types/type-helpers"; +import { coerceArray, randSeedInt, randSeedIntRange, randSeedItem } from "#utils/common"; import { getPokemonSpecies } from "#utils/pokemon-utils"; import { toCamelCase, toTitleCase } from "#utils/strings"; import i18next from "i18next"; @@ -119,6 +122,15 @@ export class TrainerConfig { public hasVoucher = false; public trainerAI: TrainerAI; + /** + * Whether this trainer's Pokémon are allowed to generate with egg moves + * @defaultValue `false` + * + * @see {@linkcode setEggMovesAllowed} + * @see {@linkcode RARE_EGG_MOVE_LEVEL_THRESHOLD} + */ + public readonly allowEggMoves: boolean = false; + public encounterMessages: string[] = []; public victoryMessages: string[] = []; public defeatMessages: string[] = []; @@ -387,8 +399,27 @@ export class TrainerConfig { return this; } - setBoss(): TrainerConfig { + /** + * Allow this trainer's Pokémon to have egg moves when generating their movesets. + * + * @remarks + * It is redundant to call this if {@linkcode setBoss} is also called on the configuration. + * @returns `this` for method chaining + * @see {@linkcode allowEggMoves} + */ + public setEggMovesAllowed(): this { + (this as Mutable).allowEggMoves = true; + return this; + } + + /** + * Set this trainer as a boss trainer + * @returns `this` for method chaining + * @see {@linkcode isBoss} + */ + public setBoss(): TrainerConfig { this.isBoss = true; + (this as Mutable).allowEggMoves = true; return this; } @@ -474,7 +505,7 @@ export class TrainerConfig { .fill(null) .map((_, i) => i) .filter(i => shedinjaCanTera || party[i].species.speciesId !== SpeciesId.SHEDINJA); // Shedinja can only Tera on Bug specialty type (or no specialty type) - const setPartySlot = !isNullOrUndefined(slot) ? Phaser.Math.Wrap(slot, 0, party.length) : -1; // If we have a tera slot defined, wrap it to party size. + const setPartySlot = slot != null ? Phaser.Math.Wrap(slot, 0, party.length) : -1; // If we have a tera slot defined, wrap it to party size. for (let t = 0; t < Math.min(count(), party.length); t++) { const randomIndex = partyMemberIndexes.indexOf(setPartySlot) > -1 ? setPartySlot : randSeedItem(partyMemberIndexes); @@ -537,7 +568,7 @@ export class TrainerConfig { initI18n(); } - if (!isNullOrUndefined(specialtyType)) { + if (specialtyType != null) { this.setSpecialtyType(specialtyType); } @@ -612,7 +643,7 @@ export class TrainerConfig { signatureSpecies.forEach((speciesPool, s) => { this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(coerceArray(speciesPool))); }); - if (!isNullOrUndefined(specialtyType)) { + if (specialtyType != null) { this.setSpeciesFilter(p => p.isOfType(specialtyType)); this.setSpecialtyType(specialtyType); } @@ -717,7 +748,7 @@ export class TrainerConfig { }); // Set species filter and specialty type if provided, otherwise filter by base total. - if (!isNullOrUndefined(specialtyType)) { + if (specialtyType != null) { this.setSpeciesFilter(p => p.isOfType(specialtyType) && p.baseTotal >= ELITE_FOUR_MINIMUM_BST); this.setSpecialtyType(specialtyType); } else { @@ -895,7 +926,7 @@ export class TrainerConfig { * @returns `true` if `specialtyType` is defined and not {@link PokemonType.UNKNOWN} */ hasSpecialtyType(): boolean { - return !isNullOrUndefined(this.specialtyType) && this.specialtyType !== PokemonType.UNKNOWN; + return this.specialtyType != null && this.specialtyType !== PokemonType.UNKNOWN; } /** @@ -2942,7 +2973,7 @@ export const trainerConfigs: TrainerConfigs = { getRandomPartyMemberFunc([SpeciesId.SLOWBRO, SpeciesId.GALAR_SLOWBRO], TrainerSlot.TRAINER, true, p => { // Tera Ice Slowbro/G-Slowbro p.generateAndPopulateMoveset(); - if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.ICE_BEAM)) { + if (!p.moveset.some(move => move != null && move.moveId === MoveId.ICE_BEAM)) { // Check if Ice Beam is in the moveset, if not, replace the third move with Ice Beam. p.moveset[2] = new PokemonMove(MoveId.ICE_BEAM); } @@ -2967,7 +2998,7 @@ export const trainerConfigs: TrainerConfigs = { getRandomPartyMemberFunc([SpeciesId.STEELIX], TrainerSlot.TRAINER, true, p => { // Tera Fighting Steelix p.generateAndPopulateMoveset(); - if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.BODY_PRESS)) { + if (!p.moveset.some(move => move != null && move.moveId === MoveId.BODY_PRESS)) { // Check if Body Press is in the moveset, if not, replace the third move with Body Press. p.moveset[2] = new PokemonMove(MoveId.BODY_PRESS); } @@ -2992,7 +3023,7 @@ export const trainerConfigs: TrainerConfigs = { getRandomPartyMemberFunc([SpeciesId.ARBOK, SpeciesId.WEEZING], TrainerSlot.TRAINER, true, p => { // Tera Ghost Arbok/Weezing p.generateAndPopulateMoveset(); - if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.TERA_BLAST)) { + if (!p.moveset.some(move => move != null && move.moveId === MoveId.TERA_BLAST)) { // Check if Tera Blast is in the moveset, if not, replace the third move with Tera Blast. p.moveset[2] = new PokemonMove(MoveId.TERA_BLAST); } @@ -3018,7 +3049,7 @@ export const trainerConfigs: TrainerConfigs = { getRandomPartyMemberFunc([SpeciesId.GYARADOS, SpeciesId.AERODACTYL], TrainerSlot.TRAINER, true, p => { // Tera Dragon Gyarados/Aerodactyl p.generateAndPopulateMoveset(); - if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.TERA_BLAST)) { + if (!p.moveset.some(move => move != null && move.moveId === MoveId.TERA_BLAST)) { // Check if Tera Blast is in the moveset, if not, replace the third move with Tera Blast. p.moveset[2] = new PokemonMove(MoveId.TERA_BLAST); } @@ -3079,7 +3110,7 @@ export const trainerConfigs: TrainerConfigs = { getRandomPartyMemberFunc([SpeciesId.GENGAR], TrainerSlot.TRAINER, true, p => { // Tera Dark Gengar p.generateAndPopulateMoveset(); - if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.DARK_PULSE)) { + if (!p.moveset.some(move => move != null && move.moveId === MoveId.DARK_PULSE)) { // Check if Dark Pulse is in the moveset, if not, replace the third move with Dark Pulse. p.moveset[2] = new PokemonMove(MoveId.DARK_PULSE); } @@ -3163,7 +3194,7 @@ export const trainerConfigs: TrainerConfigs = { getRandomPartyMemberFunc([SpeciesId.DHELMISE], TrainerSlot.TRAINER, true, p => { // Tera Dragon Dhelmise p.generateAndPopulateMoveset(); - if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.TERA_BLAST)) { + if (!p.moveset.some(move => move != null && move.moveId === MoveId.TERA_BLAST)) { // Check if Tera Blast is in the moveset, if not, replace the third move with Tera Blast. p.moveset[2] = new PokemonMove(MoveId.TERA_BLAST); } @@ -3193,7 +3224,7 @@ export const trainerConfigs: TrainerConfigs = { p.setBoss(true, 2); p.abilityIndex = 1; // Sniper p.generateAndPopulateMoveset(); - if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.X_SCISSOR)) { + if (!p.moveset.some(move => move != null && move.moveId === MoveId.X_SCISSOR)) { // Check if X-Scissor is in the moveset, if not, replace the third move with X-Scissor. p.moveset[2] = new PokemonMove(MoveId.X_SCISSOR); } @@ -3232,7 +3263,7 @@ export const trainerConfigs: TrainerConfigs = { getRandomPartyMemberFunc([SpeciesId.STEELIX, SpeciesId.LOPUNNY], TrainerSlot.TRAINER, true, p => { // Tera Fire Steelix/Lopunny p.generateAndPopulateMoveset(); - if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.TERA_BLAST)) { + if (!p.moveset.some(move => move != null && move.moveId === MoveId.TERA_BLAST)) { // Check if Tera Blast is in the moveset, if not, replace the third move with Tera Blast. p.moveset[2] = new PokemonMove(MoveId.TERA_BLAST); } @@ -3375,7 +3406,7 @@ export const trainerConfigs: TrainerConfigs = { getRandomPartyMemberFunc([SpeciesId.CERULEDGE], TrainerSlot.TRAINER, true, p => { // Tera Steel Ceruledge p.generateAndPopulateMoveset(); - if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.IRON_HEAD)) { + if (!p.moveset.some(move => move != null && move.moveId === MoveId.IRON_HEAD)) { // Check if Iron Head is in the moveset, if not, replace the third move with Iron Head. p.moveset[2] = new PokemonMove(MoveId.IRON_HEAD); } @@ -3413,7 +3444,7 @@ export const trainerConfigs: TrainerConfigs = { getRandomPartyMemberFunc([SpeciesId.INCINEROAR], TrainerSlot.TRAINER, true, p => { // Tera Fighting Incineroar p.generateAndPopulateMoveset(); - if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.CROSS_CHOP)) { + if (!p.moveset.some(move => move != null && move.moveId === MoveId.CROSS_CHOP)) { // Check if Cross Chop is in the moveset, if not, replace the third move with Cross Chop. p.moveset[2] = new PokemonMove(MoveId.CROSS_CHOP); } @@ -3486,7 +3517,7 @@ export const trainerConfigs: TrainerConfigs = { getRandomPartyMemberFunc([SpeciesId.DECIDUEYE], TrainerSlot.TRAINER, true, p => { // Tera Flying Decidueye p.generateAndPopulateMoveset(); - if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.BRAVE_BIRD)) { + if (!p.moveset.some(move => move != null && move.moveId === MoveId.BRAVE_BIRD)) { // Check if Brave Bird is in the moveset, if not, replace the third move with Brave Bird. p.moveset[2] = new PokemonMove(MoveId.BRAVE_BIRD); } @@ -3511,7 +3542,7 @@ export const trainerConfigs: TrainerConfigs = { getRandomPartyMemberFunc([SpeciesId.TOXICROAK], TrainerSlot.TRAINER, true, p => { // Tera Dark Toxicroak p.generateAndPopulateMoveset(); - if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.SUCKER_PUNCH)) { + if (!p.moveset.some(move => move != null && move.moveId === MoveId.SUCKER_PUNCH)) { // Check if Sucker Punch is in the moveset, if not, replace the third move with Sucker Punch. p.moveset[2] = new PokemonMove(MoveId.SUCKER_PUNCH); } @@ -3536,7 +3567,7 @@ export const trainerConfigs: TrainerConfigs = { getRandomPartyMemberFunc([SpeciesId.EISCUE], TrainerSlot.TRAINER, true, p => { // Tera Water Eiscue p.generateAndPopulateMoveset(); - if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.LIQUIDATION)) { + if (!p.moveset.some(move => move != null && move.moveId === MoveId.LIQUIDATION)) { // Check if Liquidation is in the moveset, if not, replace the third move with Liquidation. p.moveset[2] = new PokemonMove(MoveId.LIQUIDATION); } @@ -3598,7 +3629,7 @@ export const trainerConfigs: TrainerConfigs = { // Tera Dragon Torkoal p.abilityIndex = 1; // Drought p.generateAndPopulateMoveset(); - if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.TERA_BLAST)) { + if (!p.moveset.some(move => move != null && move.moveId === MoveId.TERA_BLAST)) { // Check if Tera Blast is in the moveset, if not, replace the third move with Tera Blast. p.moveset[2] = new PokemonMove(MoveId.TERA_BLAST); } @@ -3695,7 +3726,7 @@ export const trainerConfigs: TrainerConfigs = { getRandomPartyMemberFunc([SpeciesId.EXEGGUTOR], TrainerSlot.TRAINER, true, p => { // Tera Fire Exeggutor p.generateAndPopulateMoveset(); - if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.TERA_BLAST)) { + if (!p.moveset.some(move => move != null && move.moveId === MoveId.TERA_BLAST)) { // Check if Tera Blast is in the moveset, if not, replace the third move with Tera Blast. p.moveset[2] = new PokemonMove(MoveId.TERA_BLAST); } @@ -3705,7 +3736,7 @@ export const trainerConfigs: TrainerConfigs = { 3, getRandomPartyMemberFunc([SpeciesId.TALONFLAME], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); - if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.SUNNY_DAY)) { + if (!p.moveset.some(move => move != null && move.moveId === MoveId.SUNNY_DAY)) { // Check if Sunny Day is in the moveset, if not, replace the third move with Sunny Day. p.moveset[2] = new PokemonMove(MoveId.SUNNY_DAY); } @@ -3728,7 +3759,7 @@ export const trainerConfigs: TrainerConfigs = { getRandomPartyMemberFunc([SpeciesId.REUNICLUS], TrainerSlot.TRAINER, true, p => { // Tera Steel Reuniclus p.generateAndPopulateMoveset(); - if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.FLASH_CANNON)) { + if (!p.moveset.some(move => move != null && move.moveId === MoveId.FLASH_CANNON)) { // Check if Flash Cannon is in the moveset, if not, replace the third move with Flash Cannon. p.moveset[2] = new PokemonMove(MoveId.FLASH_CANNON); } @@ -3756,7 +3787,7 @@ export const trainerConfigs: TrainerConfigs = { // Tera Fairy Excadrill p.setBoss(true, 2); p.generateAndPopulateMoveset(); - if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.TERA_BLAST)) { + if (!p.moveset.some(move => move != null && move.moveId === MoveId.TERA_BLAST)) { // Check if Tera Blast is in the moveset, if not, replace the third move with Tera Blast. p.moveset[2] = new PokemonMove(MoveId.TERA_BLAST); } @@ -3771,7 +3802,7 @@ export const trainerConfigs: TrainerConfigs = { getRandomPartyMemberFunc([SpeciesId.SCEPTILE], TrainerSlot.TRAINER, true, p => { // Tera Dragon Sceptile p.generateAndPopulateMoveset(); - if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.DUAL_CHOP)) { + if (!p.moveset.some(move => move != null && move.moveId === MoveId.DUAL_CHOP)) { // Check if Dual Chop is in the moveset, if not, replace the third move with Dual Chop. p.moveset[2] = new PokemonMove(MoveId.DUAL_CHOP); } @@ -3841,7 +3872,7 @@ export const trainerConfigs: TrainerConfigs = { p.formIndex = 1; // Partner Pikachu p.gender = Gender.MALE; p.generateAndPopulateMoveset(); - if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.VOLT_TACKLE)) { + if (!p.moveset.some(move => move != null && move.moveId === MoveId.VOLT_TACKLE)) { // Check if Volt Tackle is in the moveset, if not, replace the first move with Volt Tackle. p.moveset[0] = new PokemonMove(MoveId.VOLT_TACKLE); } @@ -4072,7 +4103,7 @@ export const trainerConfigs: TrainerConfigs = { getRandomPartyMemberFunc([SpeciesId.KELDEO], TrainerSlot.TRAINER, true, p => { p.pokeball = PokeballType.ROGUE_BALL; p.generateAndPopulateMoveset(); - if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.SECRET_SWORD)) { + if (!p.moveset.some(move => move != null && move.moveId === MoveId.SECRET_SWORD)) { // Check if Secret Sword is in the moveset, if not, replace the third move with Secret Sword. p.moveset[2] = new PokemonMove(MoveId.SECRET_SWORD); } @@ -4401,7 +4432,7 @@ export const trainerConfigs: TrainerConfigs = { 5, getRandomPartyMemberFunc([SpeciesId.KINGAMBIT], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); - if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.TERA_BLAST)) { + if (!p.moveset.some(move => move != null && move.moveId === MoveId.TERA_BLAST)) { // Check if Tera Blast is in the moveset, if not, replace the third move with Tera Blast. p.moveset[2] = new PokemonMove(MoveId.TERA_BLAST); } @@ -4480,7 +4511,7 @@ export const trainerConfigs: TrainerConfigs = { 4, getRandomPartyMemberFunc([SpeciesId.TERAPAGOS], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); - if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.TERA_STARSTORM)) { + if (!p.moveset.some(move => move != null && move.moveId === MoveId.TERA_STARSTORM)) { // Check if Tera Starstorm is in the moveset, if not, replace the first move with Tera Starstorm. p.moveset[0] = new PokemonMove(MoveId.TERA_STARSTORM); } @@ -4494,7 +4525,7 @@ export const trainerConfigs: TrainerConfigs = { p.setBoss(true, 2); p.teraType = PokemonType.FIGHTING; p.generateAndPopulateMoveset(); - if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.TERA_BLAST)) { + if (!p.moveset.some(move => move != null && move.moveId === MoveId.TERA_BLAST)) { // Check if Tera Blast is in the moveset, if not, replace the third move with Tera Blast. p.moveset[2] = new PokemonMove(MoveId.TERA_BLAST); } @@ -5054,7 +5085,7 @@ export const trainerConfigs: TrainerConfigs = { 2, getRandomPartyMemberFunc([SpeciesId.HONCHKROW], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); - if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.SUCKER_PUNCH)) { + if (!p.moveset.some(move => move != null && move.moveId === MoveId.SUCKER_PUNCH)) { // Check if Sucker Punch is in the moveset, if not, replace the third move with Sucker Punch. p.moveset[2] = new PokemonMove(MoveId.SUCKER_PUNCH); } @@ -5517,7 +5548,7 @@ export const trainerConfigs: TrainerConfigs = { p.formIndex = randSeedInt(18); // Random Silvally Form p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ROGUE_BALL; - if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.MULTI_ATTACK)) { + if (!p.moveset.some(move => move != null && move.moveId === MoveId.MULTI_ATTACK)) { // Check if Multi Attack is in the moveset, if not, replace the first move with Multi Attack. p.moveset[0] = new PokemonMove(MoveId.MULTI_ATTACK); } @@ -5590,7 +5621,7 @@ export const trainerConfigs: TrainerConfigs = { getRandomPartyMemberFunc([SpeciesId.GOLISOPOD], TrainerSlot.TRAINER, true, p => { p.setBoss(true, 2); p.generateAndPopulateMoveset(); - if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.FIRST_IMPRESSION)) { + if (!p.moveset.some(move => move != null && move.moveId === MoveId.FIRST_IMPRESSION)) { // Check if First Impression is in the moveset, if not, replace the third move with First Impression. p.moveset[2] = new PokemonMove(MoveId.FIRST_IMPRESSION); p.gender = Gender.MALE; @@ -5607,7 +5638,7 @@ export const trainerConfigs: TrainerConfigs = { getRandomPartyMemberFunc([SpeciesId.GOLISOPOD], TrainerSlot.TRAINER, true, p => { p.setBoss(true, 2); p.generateAndPopulateMoveset(); - if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.FIRST_IMPRESSION)) { + if (!p.moveset.some(move => move != null && move.moveId === MoveId.FIRST_IMPRESSION)) { // Check if First Impression is in the moveset, if not, replace the third move with First Impression. p.moveset[2] = new PokemonMove(MoveId.FIRST_IMPRESSION); p.abilityIndex = 2; // Anticipation @@ -5643,7 +5674,7 @@ export const trainerConfigs: TrainerConfigs = { p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ROGUE_BALL; p.formIndex = randSeedInt(4, 1); // Shock, Burn, Chill, or Douse Drive - if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.TECHNO_BLAST)) { + if (!p.moveset.some(move => move != null && move.moveId === MoveId.TECHNO_BLAST)) { // Check if Techno Blast is in the moveset, if not, replace the third move with Techno Blast. p.moveset[2] = new PokemonMove(MoveId.TECHNO_BLAST); } @@ -5778,7 +5809,7 @@ export const trainerConfigs: TrainerConfigs = { p.setBoss(true, 2); p.abilityIndex = 2; // Pixilate p.generateAndPopulateMoveset(); - if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.HYPER_VOICE)) { + if (!p.moveset.some(move => move != null && move.moveId === MoveId.HYPER_VOICE)) { // Check if Hyper Voice is in the moveset, if not, replace the second move with Hyper Voice. p.moveset[1] = new PokemonMove(MoveId.HYPER_VOICE); p.gender = Gender.FEMALE; @@ -5807,7 +5838,7 @@ export const trainerConfigs: TrainerConfigs = { p.setBoss(true, 2); p.abilityIndex = 2; // Pixilate p.generateAndPopulateMoveset(); - if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.HYPER_VOICE)) { + if (!p.moveset.some(move => move != null && move.moveId === MoveId.HYPER_VOICE)) { // Check if Hyper Voice is in the moveset, if not, replace the second move with Hyper Voice. p.moveset[1] = new PokemonMove(MoveId.HYPER_VOICE); p.gender = Gender.FEMALE; diff --git a/src/field/arena.ts b/src/field/arena.ts index 6584984d13b..6d09eee4bca 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -36,7 +36,7 @@ import type { Pokemon } from "#field/pokemon"; import { FieldEffectModifier } from "#modifiers/modifier"; import type { Move } from "#moves/move"; import type { AbstractConstructor } from "#types/type-helpers"; -import { type Constructor, isNullOrUndefined, NumberHolder, randSeedInt } from "#utils/common"; +import { type Constructor, NumberHolder, randSeedInt } from "#utils/common"; import { getPokemonSpecies } from "#utils/pokemon-utils"; export class Arena { @@ -339,7 +339,7 @@ export class Arena { const weatherDuration = new NumberHolder(0); - if (!isNullOrUndefined(user)) { + if (user != null) { weatherDuration.value = 5; globalScene.applyModifier(FieldEffectModifier, user.isPlayer(), user, weatherDuration); } @@ -420,7 +420,7 @@ export class Arena { const terrainDuration = new NumberHolder(0); - if (!isNullOrUndefined(user)) { + if (user != null) { terrainDuration.value = 5; globalScene.applyModifier(FieldEffectModifier, user.isPlayer(), user, terrainDuration); } diff --git a/src/field/mystery-encounter-intro.ts b/src/field/mystery-encounter-intro.ts index cf0a0f30529..4b4a234251a 100644 --- a/src/field/mystery-encounter-intro.ts +++ b/src/field/mystery-encounter-intro.ts @@ -5,7 +5,6 @@ import { getSpriteKeysFromSpecies } from "#mystery-encounters/encounter-pokemon- import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter"; import { loadPokemonVariantAssets } from "#sprites/pokemon-sprite"; import type { Variant } from "#sprites/variant"; -import { isNullOrUndefined } from "#utils/common"; import type { GameObjects } from "phaser"; type PlayAnimationConfig = Phaser.Types.Animations.PlayAnimationConfig; @@ -98,7 +97,7 @@ export class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Container { ...config, }; - if (!isNullOrUndefined(result.species)) { + if (result.species != null) { const keys = getSpriteKeysFromSpecies(result.species, undefined, undefined, result.isShiny, result.variant); result.spriteKey = keys.spriteKey; result.fileRoot = keys.fileRoot; @@ -205,12 +204,12 @@ export class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Container { n++; } - if (!isNullOrUndefined(pokemonShinySparkle)) { + if (pokemonShinySparkle != null) { // Offset the sparkle to match the Pokemon's position pokemonShinySparkle.setPosition(sprite.x, sprite.y); } - if (!isNullOrUndefined(alpha)) { + if (alpha != null) { sprite.setAlpha(alpha); tintSprite.setAlpha(alpha); } @@ -234,7 +233,7 @@ export class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Container { this.spriteConfigs.forEach(config => { if (config.isPokemon) { globalScene.loadPokemonAtlas(config.spriteKey, config.fileRoot); - if (config.isShiny && !isNullOrUndefined(config.variant)) { + if (config.isShiny && config.variant != null) { shinyPromises.push(loadPokemonVariantAssets(config.spriteKey, config.fileRoot, config.variant)); } } else if (config.isItem) { diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 30bbff65945..8ee3d993319 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1,5 +1,6 @@ import type { Ability, PreAttackModifyDamageAbAttrParams } from "#abilities/ability"; import { applyAbAttrs, applyOnGainAbAttrs, applyOnLoseAbAttrs } from "#abilities/apply-ab-attrs"; +import { generateMoveset } from "#app/ai/ai-moveset-gen"; import type { AnySound, BattleScene } from "#app/battle-scene"; import { PLAYER_PARTY_MAX_SIZE, RARE_CANDY_FRIENDSHIP_CAP } from "#app/constants"; import { timedEventManager } from "#app/global-event-manager"; @@ -18,7 +19,7 @@ import type { LevelMoves } from "#balance/pokemon-level-moves"; import { EVOLVE_MOVE, RELEARN_MOVE } from "#balance/pokemon-level-moves"; import { BASE_HIDDEN_ABILITY_CHANCE, BASE_SHINY_CHANCE, SHINY_EPIC_CHANCE, SHINY_VARIANT_CHANCE } from "#balance/rates"; import { getStarterValueFriendshipCap, speciesStarterCosts } from "#balance/starters"; -import { reverseCompatibleTms, tmPoolTiers, tmSpecies } from "#balance/tms"; +import { reverseCompatibleTms, tmSpecies } from "#balance/tms"; import type { SuppressAbilitiesTag } from "#data/arena-tag"; import { NoCritTag, WeakenMoveScreenTag } from "#data/arena-tag"; import { @@ -81,7 +82,6 @@ import { DexAttr } from "#enums/dex-attr"; import { FieldPosition } from "#enums/field-position"; import { HitResult } from "#enums/hit-result"; import { LearnMoveSituation } from "#enums/learn-move-situation"; -import { ModifierTier } from "#enums/modifier-tier"; import { MoveCategory } from "#enums/move-category"; import { MoveFlags } from "#enums/move-flags"; import { MoveId } from "#enums/move-id"; @@ -147,8 +147,8 @@ import type { StarterDataEntry, StarterMoveset } from "#types/save-data"; import type { TurnMove } from "#types/turn-move"; import { BattleInfo } from "#ui/battle-info"; import { EnemyBattleInfo } from "#ui/enemy-battle-info"; -import type { PartyOption } from "#ui/handlers/party-ui-handler"; -import { PartyUiHandler, PartyUiMode } from "#ui/handlers/party-ui-handler"; +import type { PartyOption } from "#ui/party-ui-handler"; +import { PartyUiHandler, PartyUiMode } from "#ui/party-ui-handler"; import { PlayerBattleInfo } from "#ui/player-battle-info"; import { applyChallenges } from "#utils/challenge-utils"; import { @@ -159,7 +159,6 @@ import { fixedInt, getIvsFromId, isBetween, - isNullOrUndefined, NumberHolder, randSeedFloat, randSeedInt, @@ -886,7 +885,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { return this.fallbackVariantColor(cacheKey, spritePath, useExpSprite, battleSpritePath, error); }) .then(c => { - if (!isNullOrUndefined(c)) { + if (c != null) { variantColorCache[cacheKey] = c; } }); @@ -1475,7 +1474,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { } const ally = this.getAlly(); - if (!isNullOrUndefined(ally)) { + if (ally != null) { applyAbAttrs("AllyStatMultiplierAbAttr", { pokemon: ally, stat, @@ -1658,7 +1657,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { if (useIllusion && this.summonData.illusion) { return this.summonData.illusion.gender; } - if (!ignoreOverride && !isNullOrUndefined(this.summonData.gender)) { + if (!ignoreOverride && this.summonData.gender != null) { return this.summonData.gender; } return this.gender; @@ -1674,7 +1673,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { if (useIllusion && this.summonData.illusion?.fusionGender) { return this.summonData.illusion.fusionGender; } - if (!ignoreOverride && !isNullOrUndefined(this.summonData.fusionGender)) { + if (!ignoreOverride && this.summonData.fusionGender != null) { return this.summonData.fusionGender; } return this.fusionGender; @@ -1793,7 +1792,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { * @returns Whether this Pokemon has this species as either its base or fusion counterpart. */ hasSpecies(species: SpeciesId, formKey?: string): boolean { - if (isNullOrUndefined(formKey)) { + if (formKey == null) { return this.species.speciesId === species || this.fusionSpecies?.speciesId === species; } @@ -1941,7 +1940,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { secondType = fusionType1; } - if (secondType === PokemonType.UNKNOWN && isNullOrUndefined(fusionType2)) { + if (secondType === PokemonType.UNKNOWN && fusionType2 == null) { // If second pokemon was monotype and shared its primary type secondType = customTypes @@ -2024,12 +2023,12 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { return allAbilities[Overrides.ENEMY_ABILITY_OVERRIDE]; } if (this.isFusion()) { - if (!isNullOrUndefined(this.fusionCustomPokemonData?.ability) && this.fusionCustomPokemonData.ability !== -1) { + if (this.fusionCustomPokemonData?.ability != null && this.fusionCustomPokemonData.ability !== -1) { return allAbilities[this.fusionCustomPokemonData.ability]; } return allAbilities[this.getFusionSpeciesForm(ignoreOverride).getAbility(this.fusionAbilityIndex)]; } - if (!isNullOrUndefined(this.customPokemonData.ability) && this.customPokemonData.ability !== -1) { + if (this.customPokemonData.ability != null && this.customPokemonData.ability !== -1) { return allAbilities[this.customPokemonData.ability]; } let abilityId = this.getSpeciesForm(ignoreOverride).getAbility(this.abilityIndex); @@ -2053,7 +2052,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { if (Overrides.ENEMY_PASSIVE_ABILITY_OVERRIDE && this.isEnemy()) { return allAbilities[Overrides.ENEMY_PASSIVE_ABILITY_OVERRIDE]; } - if (!isNullOrUndefined(this.customPokemonData.passive) && this.customPokemonData.passive !== -1) { + if (this.customPokemonData.passive != null && this.customPokemonData.passive !== -1) { return allAbilities[this.customPokemonData.passive]; } @@ -2237,7 +2236,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { public getWeight(): number { const autotomizedTag = this.getTag(AutotomizedTag); let weightRemoved = 0; - if (!isNullOrUndefined(autotomizedTag)) { + if (autotomizedTag != null) { weightRemoved = 100 * autotomizedTag.autotomizeCount; } const minWeight = 0.1; @@ -2384,7 +2383,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { cancelled?: BooleanHolder, useIllusion = false, ): TypeDamageMultiplier { - if (!isNullOrUndefined(this.turnData?.moveEffectiveness)) { + if (this.turnData?.moveEffectiveness != null) { return this.turnData?.moveEffectiveness; } @@ -2636,7 +2635,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { e => new FusionSpeciesFormEvolution(this.species.speciesId, e), ); for (const fe of fusionEvolutions) { - if (fe.validate(this)) { + if (fe.validate(this, true)) { return fe; } } @@ -2775,7 +2774,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { * This causes problems when there are intentional duplicates (i.e. Smeargle with Sketch) */ if (levelMoves) { - this.getUniqueMoves(levelMoves, ret); + Pokemon.getUniqueMoves(levelMoves, ret); } return ret; @@ -2789,7 +2788,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { * @param levelMoves the input array to search for non-duplicates from * @param ret the output array to be pushed into. */ - private getUniqueMoves(levelMoves: LevelMoves, ret: LevelMoves): void { + private static getUniqueMoves(levelMoves: LevelMoves, ret: LevelMoves): void { const uniqueMoves: MoveId[] = []; for (const lm of levelMoves) { if (!uniqueMoves.find(m => m === lm[1])) { @@ -3047,294 +3046,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { /** Generates a semi-random moveset for a Pokemon */ public generateAndPopulateMoveset(): void { - this.moveset = []; - let movePool: [MoveId, number][] = []; - const allLevelMoves = this.getLevelMoves(1, true, true, this.hasTrainer()); - if (!allLevelMoves) { - console.warn("Error encountered trying to generate moveset for:", this.species.name); - return; - } - - for (const levelMove of allLevelMoves) { - if (this.level < levelMove[0]) { - break; - } - let weight = levelMove[0] + 20; - // Evolution Moves - if (levelMove[0] === EVOLVE_MOVE) { - weight = 70; - } - // Assume level 1 moves with 80+ BP are "move reminder" moves and bump their weight. Trainers use actual relearn moves. - if ( - (levelMove[0] === 1 && allMoves[levelMove[1]].power >= 80) - || (levelMove[0] === RELEARN_MOVE && this.hasTrainer()) - ) { - weight = 60; - } - if (!movePool.some(m => m[0] === levelMove[1]) && !allMoves[levelMove[1]].name.endsWith(" (N)")) { - movePool.push([levelMove[1], weight]); - } - } - - if (this.hasTrainer()) { - const tms = Object.keys(tmSpecies); - for (const tm of tms) { - const moveId = Number.parseInt(tm) as MoveId; - let compatible = false; - for (const p of tmSpecies[tm]) { - if (Array.isArray(p)) { - if ( - p[0] === this.species.speciesId - || (this.fusionSpecies - && p[0] === this.fusionSpecies.speciesId - && p.slice(1).indexOf(this.species.forms[this.formIndex]) > -1) - ) { - compatible = true; - break; - } - } else if (p === this.species.speciesId || (this.fusionSpecies && p === this.fusionSpecies.speciesId)) { - compatible = true; - break; - } - } - if (compatible && !movePool.some(m => m[0] === moveId) && !allMoves[moveId].name.endsWith(" (N)")) { - if (tmPoolTiers[moveId] === ModifierTier.COMMON && this.level >= 15) { - movePool.push([moveId, 24]); - } else if (tmPoolTiers[moveId] === ModifierTier.GREAT && this.level >= 30) { - movePool.push([moveId, 28]); - } else if (tmPoolTiers[moveId] === ModifierTier.ULTRA && this.level >= 50) { - movePool.push([moveId, 34]); - } - } - } - - // No egg moves below level 60 - if (this.level >= 60) { - for (let i = 0; i < 3; i++) { - const moveId = speciesEggMoves[this.species.getRootSpeciesId()][i]; - if (!movePool.some(m => m[0] === moveId) && !allMoves[moveId].name.endsWith(" (N)")) { - movePool.push([moveId, 60]); - } - } - const moveId = speciesEggMoves[this.species.getRootSpeciesId()][3]; - // No rare egg moves before e4 - if ( - this.level >= 170 - && !movePool.some(m => m[0] === moveId) - && !allMoves[moveId].name.endsWith(" (N)") - && !this.isBoss() - ) { - movePool.push([moveId, 50]); - } - if (this.fusionSpecies) { - for (let i = 0; i < 3; i++) { - const moveId = speciesEggMoves[this.fusionSpecies.getRootSpeciesId()][i]; - if (!movePool.some(m => m[0] === moveId) && !allMoves[moveId].name.endsWith(" (N)")) { - movePool.push([moveId, 60]); - } - } - const moveId = speciesEggMoves[this.fusionSpecies.getRootSpeciesId()][3]; - // No rare egg moves before e4 - if ( - this.level >= 170 - && !movePool.some(m => m[0] === moveId) - && !allMoves[moveId].name.endsWith(" (N)") - && !this.isBoss() - ) { - movePool.push([moveId, 50]); - } - } - } - } - - // Bosses never get self ko moves or Pain Split - if (this.isBoss()) { - movePool = movePool.filter( - m => !allMoves[m[0]].hasAttr("SacrificialAttr") && !allMoves[m[0]].hasAttr("HpSplitAttr"), - ); - } - // No one gets Memento or Final Gambit - movePool = movePool.filter(m => !allMoves[m[0]].hasAttr("SacrificialAttrOnHit")); - if (this.hasTrainer()) { - // Trainers never get OHKO moves - movePool = movePool.filter(m => !allMoves[m[0]].hasAttr("OneHitKOAttr")); - // Half the weight of self KO moves - movePool = movePool.map(m => [m[0], m[1] * (allMoves[m[0]].hasAttr("SacrificialAttr") ? 0.5 : 1)]); - // Trainers get a weight bump to stat buffing moves - movePool = movePool.map(m => [ - m[0], - m[1] * (allMoves[m[0]].getAttrs("StatStageChangeAttr").some(a => a.stages > 1 && a.selfTarget) ? 1.25 : 1), - ]); - // Trainers get a weight decrease to multiturn moves - movePool = movePool.map(m => [ - m[0], - m[1] * (!!allMoves[m[0]].isChargingMove() || !!allMoves[m[0]].hasAttr("RechargeAttr") ? 0.7 : 1), - ]); - } - - // Weight towards higher power moves, by reducing the power of moves below the highest power. - // Caps max power at 90 to avoid something like hyper beam ruining the stats. - // This is a pretty soft weighting factor, although it is scaled with the weight multiplier. - const maxPower = Math.min( - movePool.reduce((v, m) => Math.max(allMoves[m[0]].calculateEffectivePower(), v), 40), - 90, - ); - movePool = movePool.map(m => [ - m[0], - m[1] - * (allMoves[m[0]].category === MoveCategory.STATUS - ? 1 - : Math.max(Math.min(allMoves[m[0]].calculateEffectivePower() / maxPower, 1), 0.5)), - ]); - - // Weight damaging moves against the lower stat. This uses a non-linear relationship. - // If the higher stat is 1 - 1.09x higher, no change. At higher stat ~1.38x lower stat, off-stat moves have half weight. - // One third weight at ~1.58x higher, one quarter weight at ~1.73x higher, one fifth at ~1.87x, and one tenth at ~2.35x higher. - const atk = this.getStat(Stat.ATK); - const spAtk = this.getStat(Stat.SPATK); - const worseCategory: MoveCategory = atk > spAtk ? MoveCategory.SPECIAL : MoveCategory.PHYSICAL; - const statRatio = worseCategory === MoveCategory.PHYSICAL ? atk / spAtk : spAtk / atk; - movePool = movePool.map(m => [ - m[0], - m[1] * (allMoves[m[0]].category === worseCategory ? Math.min(Math.pow(statRatio, 3) * 1.3, 1) : 1), - ]); - - /** The higher this is the more the game weights towards higher level moves. At `0` all moves are equal weight. */ - let weightMultiplier = 1.6; - if (this.isBoss()) { - weightMultiplier += 0.4; - } - const baseWeights: [MoveId, number][] = movePool.map(m => [ - m[0], - Math.ceil(Math.pow(m[1], weightMultiplier) * 100), - ]); - - const STAB_BLACKLIST: ReadonlySet = new Set([ - MoveId.BEAT_UP, - MoveId.BELCH, - MoveId.BIDE, - MoveId.COMEUPPANCE, - MoveId.COUNTER, - MoveId.DOOM_DESIRE, - MoveId.DRAGON_RAGE, - MoveId.DREAM_EATER, - MoveId.ENDEAVOR, - MoveId.EXPLOSION, - MoveId.FAKE_OUT, - MoveId.FIRST_IMPRESSION, - MoveId.FISSURE, - MoveId.FLING, - MoveId.FOCUS_PUNCH, - MoveId.FUTURE_SIGHT, - MoveId.GUILLOTINE, - MoveId.HOLD_BACK, - MoveId.HORN_DRILL, - MoveId.LAST_RESORT, - MoveId.METAL_BURST, - MoveId.MIRROR_COAT, - MoveId.MISTY_EXPLOSION, - MoveId.NATURAL_GIFT, - MoveId.NATURES_MADNESS, - MoveId.NIGHT_SHADE, - MoveId.PSYWAVE, - MoveId.RUINATION, - MoveId.SELF_DESTRUCT, - MoveId.SHEER_COLD, - MoveId.SHELL_TRAP, - MoveId.SKY_DROP, - MoveId.SNORE, - MoveId.SONIC_BOOM, - MoveId.SPIT_UP, - MoveId.STEEL_BEAM, - MoveId.STEEL_ROLLER, - MoveId.SUPER_FANG, - MoveId.SYNCHRONOISE, - MoveId.UPPER_HAND, - ]); - - // All Pokemon force a STAB move first - const stabMovePool = baseWeights.filter( - m => - allMoves[m[0]].category !== MoveCategory.STATUS - && this.isOfType(allMoves[m[0]].type) - && !STAB_BLACKLIST.has(m[0]), - ); - - if (stabMovePool.length > 0) { - const totalWeight = stabMovePool.reduce((v, m) => v + m[1], 0); - let rand = randSeedInt(totalWeight); - let index = 0; - while (rand > stabMovePool[index][1]) { - rand -= stabMovePool[index++][1]; - } - this.moveset.push(new PokemonMove(stabMovePool[index][0])); - } else { - // If there are no damaging STAB moves, just force a random damaging move - const attackMovePool = baseWeights.filter( - m => allMoves[m[0]].category !== MoveCategory.STATUS && !STAB_BLACKLIST.has(m[0]), - ); - if (attackMovePool.length > 0) { - const totalWeight = attackMovePool.reduce((v, m) => v + m[1], 0); - let rand = randSeedInt(totalWeight); - let index = 0; - while (rand > attackMovePool[index][1]) { - rand -= attackMovePool[index++][1]; - } - this.moveset.push(new PokemonMove(attackMovePool[index][0], 0, 0)); - } - } - - while (baseWeights.length > this.moveset.length && this.moveset.length < 4) { - if (this.hasTrainer()) { - // Sqrt the weight of any damaging moves with overlapping types. This is about a 0.05 - 0.1 multiplier. - // Other damaging moves 2x weight if 0-1 damaging moves, 0.5x if 2, 0.125x if 3. These weights get 20x if STAB. - // Status moves remain unchanged on weight, this encourages 1-2 - movePool = baseWeights - .filter( - m => - !this.moveset.some( - mo => - m[0] === mo.moveId - || (allMoves[m[0]].hasAttr("SacrificialAttr") && mo.getMove().hasAttr("SacrificialAttr")), // Only one self-KO move allowed - ), - ) - .map(m => { - let ret: number; - if ( - this.moveset.some( - mo => mo.getMove().category !== MoveCategory.STATUS && mo.getMove().type === allMoves[m[0]].type, - ) - ) { - ret = Math.ceil(Math.sqrt(m[1])); - } else if (allMoves[m[0]].category !== MoveCategory.STATUS) { - ret = Math.ceil( - (m[1] / Math.max(Math.pow(4, this.moveset.filter(mo => (mo.getMove().power ?? 0) > 1).length) / 8, 0.5)) - * (this.isOfType(allMoves[m[0]].type) && !STAB_BLACKLIST.has(m[0]) ? 20 : 1), - ); - } else { - ret = m[1]; - } - return [m[0], ret]; - }); - } else { - // Non-trainer pokemon just use normal weights - movePool = baseWeights.filter( - m => - !this.moveset.some( - mo => - m[0] === mo.moveId - || (allMoves[m[0]].hasAttr("SacrificialAttr") && mo.getMove().hasAttr("SacrificialAttr")), // Only one self-KO move allowed - ), - ); - } - const totalWeight = movePool.reduce((v, m) => v + m[1], 0); - let rand = randSeedInt(totalWeight); - let index = 0; - while (rand > movePool[index][1]) { - rand -= movePool[index++][1]; - } - this.moveset.push(new PokemonMove(movePool[index][0])); - } + generateMoveset(this); // Trigger FormChange, except for enemy Pokemon during Mystery Encounters, to avoid crashes if ( @@ -3606,7 +3318,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { }); const ally = this.getAlly(); - if (!isNullOrUndefined(ally)) { + if (ally != null) { const ignore = this.hasAbilityWithAttr("MoveAbilityBypassAbAttr") || sourceMove.hasFlag(MoveFlags.IGNORE_ABILITIES); applyAbAttrs("AllyStatMultiplierAbAttr", { @@ -4015,7 +3727,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { const ally = this.getAlly(); /** Additionally apply friend guard damage reduction if ally has it. */ - if (globalScene.currentBattle.double && !isNullOrUndefined(ally) && ally.isActive(true)) { + if (globalScene.currentBattle.double && ally != null && ally.isActive(true)) { applyAbAttrs("AlliedFieldDamageReductionAbAttr", { ...abAttrParams, // Same parameters as before, except we are applying the ally's ability @@ -6391,13 +6103,13 @@ export class EnemyPokemon extends Pokemon { if ( speciesId in Overrides.ENEMY_FORM_OVERRIDES - && !isNullOrUndefined(Overrides.ENEMY_FORM_OVERRIDES[speciesId]) + && Overrides.ENEMY_FORM_OVERRIDES[speciesId] != null && this.species.forms[Overrides.ENEMY_FORM_OVERRIDES[speciesId]] ) { this.formIndex = Overrides.ENEMY_FORM_OVERRIDES[speciesId]; } else if (globalScene.gameMode.isDaily && globalScene.gameMode.isWaveFinal(globalScene.currentBattle.waveIndex)) { const eventBoss = getDailyEventSeedBoss(globalScene.seed); - if (!isNullOrUndefined(eventBoss)) { + if (eventBoss != null) { this.formIndex = eventBoss.formIndex; } } @@ -6933,12 +6645,20 @@ export class EnemyPokemon extends Pokemon { * @param segmentIndex index of the segment to get down to (0 = no shield left, 1 = 1 shield left, etc.) */ handleBossSegmentCleared(segmentIndex: number): void { + let doStatBoost = !this.hasTrainer(); + // TODO: Rewrite this bespoke logic to improve clarity while (this.bossSegmentIndex > 0 && segmentIndex - 1 < this.bossSegmentIndex) { + this.bossSegmentIndex--; + + // Continue, _not_ break here, to ensure that each segment is still broken + if (!doStatBoost) { + continue; + } + let boostedStat: EffectiveStat | undefined; // Filter out already maxed out stat stages and weigh the rest based on existing stats const leftoverStats = EFFECTIVE_STATS.filter((s: EffectiveStat) => this.getStatStage(s) < 6); const statWeights = leftoverStats.map((s: EffectiveStat) => this.getStat(s, false)); - let boostedStat: EffectiveStat | undefined; const statThresholds: number[] = []; let totalWeight = 0; @@ -6957,18 +6677,18 @@ export class EnemyPokemon extends Pokemon { } if (boostedStat === undefined) { - this.bossSegmentIndex--; - return; + doStatBoost = false; + continue; } let stages = 1; // increase the boost if the boss has at least 3 segments and we passed last shield - if (this.bossSegments >= 3 && this.bossSegmentIndex === 1) { + if (this.bossSegments >= 3 && this.bossSegmentIndex === 0) { stages++; } // increase the boost if the boss has at least 5 segments and we passed the second to last shield - if (this.bossSegments >= 5 && this.bossSegmentIndex === 2) { + if (this.bossSegments >= 5 && this.bossSegmentIndex === 1) { stages++; } @@ -6981,7 +6701,6 @@ export class EnemyPokemon extends Pokemon { true, true, ); - this.bossSegmentIndex--; } } diff --git a/src/game-mode.ts b/src/game-mode.ts index 9ea3adf59d3..e543e3c42ca 100644 --- a/src/game-mode.ts +++ b/src/game-mode.ts @@ -14,7 +14,7 @@ import { SpeciesId } from "#enums/species-id"; import type { Arena } from "#field/arena"; import { classicFixedBattles, type FixedBattleConfigs } from "#trainers/fixed-battle-configs"; import { applyChallenges } from "#utils/challenge-utils"; -import { BooleanHolder, isNullOrUndefined, randSeedInt, randSeedItem } from "#utils/common"; +import { BooleanHolder, randSeedInt, randSeedItem } from "#utils/common"; import { getPokemonSpecies } from "#utils/pokemon-utils"; import i18next from "i18next"; @@ -146,7 +146,7 @@ export class GameMode implements GameModeConfig { * - Town */ getStartingBiome(): BiomeId { - if (!isNullOrUndefined(Overrides.STARTING_BIOME_OVERRIDE)) { + if (Overrides.STARTING_BIOME_OVERRIDE != null) { return Overrides.STARTING_BIOME_OVERRIDE; } @@ -234,7 +234,7 @@ export class GameMode implements GameModeConfig { getOverrideSpecies(waveIndex: number): PokemonSpecies | null { if (this.isDaily && this.isWaveFinal(waveIndex)) { const eventBoss = getDailyEventSeedBoss(globalScene.seed); - if (!isNullOrUndefined(eventBoss)) { + if (eventBoss != null) { // Cannot set form index here, it will be overriden when adding it as enemy pokemon. return getPokemonSpecies(eventBoss.speciesId); } diff --git a/src/init/init.ts b/src/init/init.ts index b717664b654..8452278b3f1 100644 --- a/src/init/init.ts +++ b/src/init/init.ts @@ -11,7 +11,7 @@ import { initMoves } from "#moves/move"; import { initMysteryEncounters } from "#mystery-encounters/mystery-encounters"; import { initAchievements } from "#system/achv"; import { initVouchers } from "#system/voucher"; -import { initStatsKeys } from "#ui/handlers/game-stats-ui-handler"; +import { initStatsKeys } from "#ui/game-stats-ui-handler"; /** Initialize the game. */ export function initializeGame() { diff --git a/src/modifier/init-modifier-pools.ts b/src/modifier/init-modifier-pools.ts index ba12920407d..e6ec69eac7f 100644 --- a/src/modifier/init-modifier-pools.ts +++ b/src/modifier/init-modifier-pools.ts @@ -31,7 +31,6 @@ import { } from "#modifiers/modifier-pools"; import { WeightedModifierType } from "#modifiers/modifier-type"; import type { WeightedModifierTypeWeightFunc } from "#types/modifier-types"; -import { isNullOrUndefined } from "#utils/common"; /** * Initialize the wild modifier pool @@ -409,7 +408,7 @@ function initUltraModifierPool() { if (!isHoldingOrb) { const moveset = p .getMoveset(true) - .filter(m => !isNullOrUndefined(m)) + .filter(m => m != null) .map(m => m.moveId); const canSetStatus = p.canSetStatus(StatusEffect.TOXIC, true, true, null, true); @@ -455,7 +454,7 @@ function initUltraModifierPool() { if (!isHoldingOrb) { const moveset = p .getMoveset(true) - .filter(m => !isNullOrUndefined(m)) + .filter(m => m != null) .map(m => m.moveId); const canSetStatus = p.canSetStatus(StatusEffect.BURN, true, true, null, true); diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 155ada1c18d..d67011bc145 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -115,19 +115,11 @@ import { import type { PokemonMove } from "#moves/pokemon-move"; import { getVoucherTypeIcon, getVoucherTypeName, VoucherType } from "#system/voucher"; import type { ModifierTypeFunc, WeightedModifierTypeWeightFunc } from "#types/modifier-types"; -import type { PokemonMoveSelectFilter, PokemonSelectFilter } from "#ui/handlers/party-ui-handler"; -import { PartyUiHandler } from "#ui/handlers/party-ui-handler"; +import type { PokemonMoveSelectFilter, PokemonSelectFilter } from "#ui/party-ui-handler"; +import { PartyUiHandler } from "#ui/party-ui-handler"; import { getModifierTierTextTint } from "#ui/text"; import { applyChallenges } from "#utils/challenge-utils"; -import { - BooleanHolder, - formatMoney, - isNullOrUndefined, - NumberHolder, - padInt, - randSeedInt, - randSeedItem, -} from "#utils/common"; +import { BooleanHolder, formatMoney, NumberHolder, padInt, randSeedInt, randSeedItem } from "#utils/common"; import { getEnumKeys, getEnumValues } from "#utils/enums"; import { getModifierPoolForType, getModifierType } from "#utils/modifier-utils"; import { toCamelCase } from "#utils/strings"; @@ -263,7 +255,7 @@ export class ModifierType { this.tier = modifier.modifierType.tier; return this; } - if (isNullOrUndefined(defaultTier)) { + if (defaultTier == null) { // If weight is 0, keep track of the first tier where the item was found defaultTier = modifier.modifierType.tier; } @@ -2920,7 +2912,7 @@ export function getPartyLuckValue(party: Pokemon[]): number { globalScene.executeWithSeedOffset( () => { const eventLuck = getDailyEventSeedLuck(globalScene.seed); - if (!isNullOrUndefined(eventLuck)) { + if (eventLuck != null) { DailyLuck.value = eventLuck; return; } diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 1f470e592c2..b94c479e96e 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -42,7 +42,7 @@ import type { import type { VoucherType } from "#system/voucher"; import type { ModifierInstanceMap, ModifierString } from "#types/modifier-types"; import { addTextObject } from "#ui/text"; -import { BooleanHolder, hslToHex, isNullOrUndefined, NumberHolder, randSeedFloat, toDmgValue } from "#utils/common"; +import { BooleanHolder, hslToHex, NumberHolder, randSeedFloat, toDmgValue } from "#utils/common"; import { getModifierType } from "#utils/modifier-utils"; import i18next from "i18next"; @@ -728,7 +728,7 @@ export abstract class PokemonHeldItemModifier extends PersistentModifier { } getPokemon(): Pokemon | undefined { - return globalScene.getPokemonById(this.pokemonId) ?? undefined; + return globalScene.getPokemonById(this.pokemonId); } getScoreMultiplier(): number { @@ -2113,10 +2113,7 @@ export class PokemonHpRestoreModifier extends ConsumablePokemonModifier { * @returns `true` if the {@linkcode PokemonHpRestoreModifier} should be applied */ override shouldApply(playerPokemon?: PlayerPokemon, multiplier?: number): boolean { - return ( - super.shouldApply(playerPokemon) - && (this.fainted || (!isNullOrUndefined(multiplier) && typeof multiplier === "number")) - ); + return super.shouldApply(playerPokemon) && (this.fainted || (multiplier != null && typeof multiplier === "number")); } /** @@ -2753,10 +2750,10 @@ export class PokemonMultiHitModifier extends PokemonHeldItemModifier { return false; } - if (!isNullOrUndefined(count)) { + if (count != null) { return this.applyHitCountBoost(count); } - if (!isNullOrUndefined(damageMultiplier)) { + if (damageMultiplier != null) { return this.applyDamageModifier(pokemon, damageMultiplier); } diff --git a/src/overrides.ts b/src/overrides.ts index b8212ea8fd6..3f61196f0b4 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -1,4 +1,4 @@ -import { type PokeballCounts } from "#app/battle-scene"; +import type { PokeballCounts } from "#app/battle-scene"; import { EvolutionItem } from "#balance/pokemon-evolutions"; import { Gender } from "#data/gender"; import { AbilityId } from "#enums/ability-id"; @@ -18,10 +18,11 @@ import { Stat } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; import { TimeOfDay } from "#enums/time-of-day"; import { TrainerType } from "#enums/trainer-type"; +import { TrainerVariant } from "#enums/trainer-variant"; import { Unlockables } from "#enums/unlockables"; import { VariantTier } from "#enums/variant-tier"; import { WeatherType } from "#enums/weather-type"; -import { type ModifierOverride } from "#modifiers/modifier-type"; +import type { ModifierOverride } from "#modifiers/modifier-type"; import { Variant } from "#sprites/variant"; /** @@ -311,8 +312,12 @@ export type BattleStyle = "double" | "single" | "even-doubles" | "odd-doubles"; export type RandomTrainerOverride = { /** The Type of trainer to force */ trainerType: Exclude; - /* If the selected trainer type has a double version, it will always use its double version. */ - alwaysDouble?: boolean; + /** + * The {@linkcode TrainerVariant} to force. + * @remarks + * `TrainerVariant.DOUBLE` cannot be forced on the first wave of a game due to issues with trainer party generation. + */ + trainerVariant?: TrainerVariant; }; /** The type of the {@linkcode DefaultOverrides} class */ diff --git a/src/phase-manager.ts b/src/phase-manager.ts index 4bb7e0a4b37..3fbf68de60d 100644 --- a/src/phase-manager.ts +++ b/src/phase-manager.ts @@ -1,3 +1,12 @@ +/** + * Manager for phases used by battle scene. + * + * @remarks + * **This file must not be imported or used directly.** + * The manager is exclusively used by the Battle Scene and is NOT intended for external use. + * @module + */ + import { PHASE_START_COLOR } from "#app/constants/colors"; import { globalScene } from "#app/global-scene"; import type { Phase } from "#app/phase"; @@ -103,15 +112,6 @@ import { WeatherEffectPhase } from "#phases/weather-effect-phase"; import type { PhaseMap, PhaseString } from "#types/phase-types"; import { type Constructor, coerceArray } from "#utils/common"; -/** - * @module - * Manager for phases used by battle scene. - * - * @remarks - * **This file must not be imported or used directly.** - * The manager is exclusively used by the Battle Scene and is NOT intended for external use. - */ - /** * Object that holds all of the phase constructors. * This is used to create new phases dynamically using the `newPhase` method in the `PhaseManager`. diff --git a/src/phases/attempt-capture-phase.ts b/src/phases/attempt-capture-phase.ts index 81f85850e88..2eadae244d5 100644 --- a/src/phases/attempt-capture-phase.ts +++ b/src/phases/attempt-capture-phase.ts @@ -21,9 +21,9 @@ import type { EnemyPokemon } from "#field/pokemon"; import { PokemonHeldItemModifier } from "#modifiers/modifier"; import { PokemonPhase } from "#phases/pokemon-phase"; import { achvs } from "#system/achv"; -import type { PartyOption } from "#ui/handlers/party-ui-handler"; -import { PartyUiMode } from "#ui/handlers/party-ui-handler"; -import { SummaryUiMode } from "#ui/handlers/summary-ui-handler"; +import type { PartyOption } from "#ui/party-ui-handler"; +import { PartyUiMode } from "#ui/party-ui-handler"; +import { SummaryUiMode } from "#ui/summary-ui-handler"; import { applyChallenges } from "#utils/challenge-utils"; import { BooleanHolder } from "#utils/common"; import i18next from "i18next"; diff --git a/src/phases/command-phase.ts b/src/phases/command-phase.ts index b9867e22522..2bf845776ca 100644 --- a/src/phases/command-phase.ts +++ b/src/phases/command-phase.ts @@ -220,7 +220,7 @@ export class CommandPhase extends FieldPhase { if (!moveStatus.value) { cannotSelectKey = "battle:moveCannotUseChallenge"; } else if (move.getPpRatio() === 0) { - cannotSelectKey = "battle:moveNoPP"; + cannotSelectKey = "battle:moveNoPp"; } else if (move.getName().endsWith(" (N)")) { cannotSelectKey = "battle:moveNotImplemented"; } else if (user.isMoveRestricted(move.moveId, user)) { diff --git a/src/phases/egg-hatch-phase.ts b/src/phases/egg-hatch-phase.ts index bfd66bd02e4..946288c4fd8 100644 --- a/src/phases/egg-hatch-phase.ts +++ b/src/phases/egg-hatch-phase.ts @@ -9,9 +9,9 @@ import { doShinySparkleAnim } from "#field/anims"; import type { PlayerPokemon } from "#field/pokemon"; import type { EggLapsePhase } from "#phases/egg-lapse-phase"; import { achvs } from "#system/achv"; -import { EggCounterContainer } from "#ui/containers/egg-counter-container"; -import { PokemonInfoContainer } from "#ui/containers/pokemon-info-container"; -import type { EggHatchSceneUiHandler } from "#ui/handlers/egg-hatch-scene-ui-handler"; +import { EggCounterContainer } from "#ui/egg-counter-container"; +import type { EggHatchSceneUiHandler } from "#ui/egg-hatch-scene-ui-handler"; +import { PokemonInfoContainer } from "#ui/pokemon-info-container"; import { fixedInt, getFrameMs, randInt } from "#utils/common"; import i18next from "i18next"; import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; diff --git a/src/phases/evolution-phase.ts b/src/phases/evolution-phase.ts index 5943ed730ff..80750beeb68 100644 --- a/src/phases/evolution-phase.ts +++ b/src/phases/evolution-phase.ts @@ -10,7 +10,7 @@ import { LearnMoveSituation } from "#enums/learn-move-situation"; import { UiMode } from "#enums/ui-mode"; import { cos, sin } from "#field/anims"; import type { PlayerPokemon, Pokemon } from "#field/pokemon"; -import type { EvolutionSceneUiHandler } from "#ui/handlers/evolution-scene-ui-handler"; +import type { EvolutionSceneUiHandler } from "#ui/evolution-scene-ui-handler"; import { fixedInt, getFrameMs, randInt } from "#utils/common"; import i18next from "i18next"; import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; diff --git a/src/phases/faint-phase.ts b/src/phases/faint-phase.ts index 349dfcfa8e5..821d16c6546 100644 --- a/src/phases/faint-phase.ts +++ b/src/phases/faint-phase.ts @@ -17,7 +17,6 @@ import type { EnemyPokemon, PlayerPokemon, Pokemon } from "#field/pokemon"; import { PokemonInstantReviveModifier } from "#modifiers/modifier"; import { PokemonMove } from "#moves/pokemon-move"; import { PokemonPhase } from "#phases/pokemon-phase"; -import { isNullOrUndefined } from "#utils/common"; import i18next from "i18next"; export class FaintPhase extends PokemonPhase { @@ -187,7 +186,7 @@ export class FaintPhase extends PokemonPhase { // in double battles redirect potential moves off fainted pokemon const allyPokemon = pokemon.getAlly(); - if (globalScene.currentBattle.double && !isNullOrUndefined(allyPokemon)) { + if (globalScene.currentBattle.double && allyPokemon != null) { globalScene.redirectPokemonMoves(pokemon, allyPokemon); } diff --git a/src/phases/form-change-phase.ts b/src/phases/form-change-phase.ts index 7521adee6c9..4fb34079367 100644 --- a/src/phases/form-change-phase.ts +++ b/src/phases/form-change-phase.ts @@ -8,7 +8,7 @@ import { UiMode } from "#enums/ui-mode"; import type { PlayerPokemon, Pokemon } from "#field/pokemon"; import { EvolutionPhase } from "#phases/evolution-phase"; import { achvs } from "#system/achv"; -import type { PartyUiHandler } from "#ui/handlers/party-ui-handler"; +import type { PartyUiHandler } from "#ui/party-ui-handler"; import { fixedInt } from "#utils/common"; export class FormChangePhase extends EvolutionPhase { diff --git a/src/phases/learn-move-phase.ts b/src/phases/learn-move-phase.ts index e75d7c8f6f3..4fc38b08d16 100644 --- a/src/phases/learn-move-phase.ts +++ b/src/phases/learn-move-phase.ts @@ -10,8 +10,8 @@ import { UiMode } from "#enums/ui-mode"; import type { Pokemon } from "#field/pokemon"; import type { Move } from "#moves/move"; import { PlayerPartyMemberPokemonPhase } from "#phases/player-party-member-pokemon-phase"; -import { EvolutionSceneUiHandler } from "#ui/handlers/evolution-scene-ui-handler"; -import { SummaryUiMode } from "#ui/handlers/summary-ui-handler"; +import { EvolutionSceneUiHandler } from "#ui/evolution-scene-ui-handler"; +import { SummaryUiMode } from "#ui/summary-ui-handler"; import i18next from "i18next"; export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { diff --git a/src/phases/login-phase.ts b/src/phases/login-phase.ts index f310c60b0d4..d81b9b614f2 100644 --- a/src/phases/login-phase.ts +++ b/src/phases/login-phase.ts @@ -33,7 +33,7 @@ export class LoginPhase extends Phase { globalScene.ui.showText(i18next.t("menu:logInOrCreateAccount")); } - globalScene.playSound("menu_open"); + globalScene.playSound("ui/menu_open"); const loadData = () => { updateUserInfo().then(success => { @@ -53,7 +53,7 @@ export class LoginPhase extends Phase { loadData(); }, () => { - globalScene.playSound("menu_open"); + globalScene.playSound("ui/menu_open"); globalScene.ui.setMode(UiMode.REGISTRATION_FORM, { buttonActions: [ () => { diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 0600f7d5ecf..18e25b328f8 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -40,7 +40,7 @@ import { DamageAchv } from "#system/achv"; import type { DamageResult } from "#types/damage-result"; import type { TurnMove } from "#types/turn-move"; import type { nil } from "#utils/common"; -import { BooleanHolder, isNullOrUndefined, NumberHolder } from "#utils/common"; +import { BooleanHolder, NumberHolder } from "#utils/common"; import i18next from "i18next"; export type HitCheckEntry = [HitCheckResult, TypeDamageMultiplier]; @@ -645,13 +645,18 @@ export class MoveEffectPhase extends PokemonPhase { return move.getAttrs("HitsTagAttr").some(hta => hta.tagType === semiInvulnerableTag.tagType); } - /** @returns The {@linkcode Pokemon} using this phase's invoked move */ - public getUserPokemon(): Pokemon | null { + /** + * @todo Investigate why this doesn't use `BattlerIndex` + * @returns The {@linkcode Pokemon} using this phase's invoked move + */ + public getUserPokemon(): Pokemon | undefined { // TODO: Make this purely a battler index if (this.battlerIndex > BattlerIndex.ENEMY_2) { return globalScene.getPokemonById(this.battlerIndex); } - return (this.player ? globalScene.getPlayerField() : globalScene.getEnemyField())[this.fieldIndex]; + // TODO: Figure out why this uses `fieldIndex` instead of `BattlerIndex` + // TODO: Remove `?? undefined` once field pokemon getters are made sane + return (this.player ? globalScene.getPlayerField() : globalScene.getEnemyField())[this.fieldIndex] ?? undefined; } /** @@ -740,7 +745,7 @@ export class MoveEffectPhase extends PokemonPhase { (attr: MoveAttr) => attr.is("MoveEffectAttr") && attr.trigger === triggerType - && (isNullOrUndefined(selfTarget) || attr.selfTarget === selfTarget) + && (selfTarget == null || attr.selfTarget === selfTarget) && (!attr.firstHitOnly || this.firstHit) && (!attr.lastHitOnly || this.lastHit) && (!attr.firstTargetOnly || (firstTarget ?? true)), @@ -765,7 +770,7 @@ export class MoveEffectPhase extends PokemonPhase { */ protected applyMoveEffects(target: Pokemon, effectiveness: TypeDamageMultiplier, firstTarget: boolean): void { const user = this.getUserPokemon(); - if (isNullOrUndefined(user)) { + if (user == null) { return; } @@ -932,7 +937,7 @@ export class MoveEffectPhase extends PokemonPhase { msg = i18next.t("battle:hitResultNotVeryEffective"); break; case HitResult.ONE_HIT_KO: - msg = i18next.t("battle:hitResultOneHitKO"); + msg = i18next.t("battle:hitResultOneHitKo"); break; } if (msg) { diff --git a/src/phases/mystery-encounter-phases.ts b/src/phases/mystery-encounter-phases.ts index df670deaf26..4f50b40c965 100644 --- a/src/phases/mystery-encounter-phases.ts +++ b/src/phases/mystery-encounter-phases.ts @@ -14,7 +14,7 @@ import type { OptionSelectSettings } from "#mystery-encounters/encounter-phase-u import { transitionMysteryEncounterIntroVisuals } from "#mystery-encounters/encounter-phase-utils"; import type { MysteryEncounterOption, OptionPhaseCallback } from "#mystery-encounters/mystery-encounter-option"; import { SeenEncounterData } from "#mystery-encounters/mystery-encounter-save-data"; -import { isNullOrUndefined, randSeedItem } from "#utils/common"; +import { randSeedItem } from "#utils/common"; import i18next from "i18next"; /** @@ -93,7 +93,7 @@ export class MysteryEncounterPhase extends Phase { if (option.onPreOptionPhase) { globalScene.executeWithSeedOffset(async () => { return await option.onPreOptionPhase!().then(result => { - if (isNullOrUndefined(result) || result) { + if (result == null || result) { this.continueEncounter(); } }); @@ -578,7 +578,7 @@ export class PostMysteryEncounterPhase extends Phase { if (this.onPostOptionSelect) { globalScene.executeWithSeedOffset(async () => { return await this.onPostOptionSelect!().then(result => { - if (isNullOrUndefined(result) || result) { + if (result == null || result) { this.continueEncounter(); } }); diff --git a/src/phases/obtain-status-effect-phase.ts b/src/phases/obtain-status-effect-phase.ts index 4846130cf4d..b9f3e266d87 100644 --- a/src/phases/obtain-status-effect-phase.ts +++ b/src/phases/obtain-status-effect-phase.ts @@ -23,6 +23,7 @@ export class ObtainStatusEffectPhase extends PokemonPhase { * @param sourceText - The text to show for the source of the status effect, if any; default `null`. * @param statusMessage - A string containing text to be displayed upon status setting; * defaults to normal key for status if empty or omitted. + * @todo stop passing `null` to the phase */ constructor( battlerIndex: BattlerIndex, diff --git a/src/phases/pokemon-anim-phase.ts b/src/phases/pokemon-anim-phase.ts index 39e9c609aec..c45f201641c 100644 --- a/src/phases/pokemon-anim-phase.ts +++ b/src/phases/pokemon-anim-phase.ts @@ -4,7 +4,6 @@ import { PokemonAnimType } from "#enums/pokemon-anim-type"; import { SpeciesId } from "#enums/species-id"; import type { Pokemon } from "#field/pokemon"; import { BattlePhase } from "#phases/battle-phase"; -import { isNullOrUndefined } from "#utils/common"; export class PokemonAnimPhase extends BattlePhase { public readonly phaseName = "PokemonAnimPhase"; @@ -52,7 +51,7 @@ export class PokemonAnimPhase extends BattlePhase { private doSubstituteAddAnim(): void { const substitute = this.pokemon.getTag(SubstituteTag); - if (isNullOrUndefined(substitute)) { + if (substitute == null) { this.end(); return; } @@ -336,7 +335,7 @@ export class PokemonAnimPhase extends BattlePhase { // Note: unlike the other Commander animation, this is played through the // Dondozo instead of the Tatsugiri. const tatsugiri = this.pokemon.getAlly(); - if (isNullOrUndefined(tatsugiri)) { + if (tatsugiri == null) { console.warn("Aborting COMMANDER_REMOVE anim: Tatsugiri is undefined"); this.end(); return; diff --git a/src/phases/pokemon-phase.ts b/src/phases/pokemon-phase.ts index 1a1a7e2efa3..92b29889079 100644 --- a/src/phases/pokemon-phase.ts +++ b/src/phases/pokemon-phase.ts @@ -9,7 +9,9 @@ export abstract class PokemonPhase extends FieldPhase { * TODO: Make this either use IDs or `BattlerIndex`es, not a weird mix of both */ protected battlerIndex: BattlerIndex | number; + // TODO: Why is this needed? public player: boolean; + /** @todo Remove in favor of `battlerIndex` pleas for fuck's sake */ public fieldIndex: number; constructor(battlerIndex?: BattlerIndex | number) { @@ -32,10 +34,11 @@ export abstract class PokemonPhase extends FieldPhase { this.fieldIndex = battlerIndex % 2; } + // TODO: This should have `undefined` in its signature getPokemon(): Pokemon { if (this.battlerIndex > BattlerIndex.ENEMY_2) { - return globalScene.getPokemonById(this.battlerIndex)!; //TODO: is this bang correct? + return globalScene.getPokemonById(this.battlerIndex)!; } - return globalScene.getField()[this.battlerIndex]!; //TODO: is this bang correct? + return globalScene.getField()[this.battlerIndex]!; } } diff --git a/src/phases/revival-blessing-phase.ts b/src/phases/revival-blessing-phase.ts index a9dedf4c325..5d75f2c9b47 100644 --- a/src/phases/revival-blessing-phase.ts +++ b/src/phases/revival-blessing-phase.ts @@ -3,9 +3,9 @@ import { SwitchType } from "#enums/switch-type"; import { UiMode } from "#enums/ui-mode"; import type { PlayerPokemon } from "#field/pokemon"; import { BattlePhase } from "#phases/battle-phase"; -import type { PartyOption } from "#ui/handlers/party-ui-handler"; -import { PartyUiHandler, PartyUiMode } from "#ui/handlers/party-ui-handler"; -import { isNullOrUndefined, toDmgValue } from "#utils/common"; +import type { PartyOption } from "#ui/party-ui-handler"; +import { PartyUiHandler, PartyUiMode } from "#ui/party-ui-handler"; +import { toDmgValue } from "#utils/common"; import i18next from "i18next"; /** @@ -42,11 +42,7 @@ export class RevivalBlessingPhase extends BattlePhase { ); const allyPokemon = this.user.getAlly(); - if ( - globalScene.currentBattle.double - && globalScene.getPlayerParty().length > 1 - && !isNullOrUndefined(allyPokemon) - ) { + if (globalScene.currentBattle.double && globalScene.getPlayerParty().length > 1 && allyPokemon != null) { if (slotIndex <= 1) { // Revived ally pokemon globalScene.phaseManager.unshiftNew( diff --git a/src/phases/select-biome-phase.ts b/src/phases/select-biome-phase.ts index 3276c34306c..21c0cfade94 100644 --- a/src/phases/select-biome-phase.ts +++ b/src/phases/select-biome-phase.ts @@ -5,7 +5,7 @@ import { ChallengeType } from "#enums/challenge-type"; import { UiMode } from "#enums/ui-mode"; import { MapModifier, MoneyInterestModifier } from "#modifiers/modifier"; import { BattlePhase } from "#phases/battle-phase"; -import type { OptionSelectItem } from "#ui/handlers/abstract-option-select-ui-handler"; +import type { OptionSelectItem } from "#ui/abstract-option-select-ui-handler"; import { applyChallenges } from "#utils/challenge-utils"; import { BooleanHolder, randSeedInt } from "#utils/common"; diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index 51adeb21af0..2031fc5c5f1 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-modifier-phase.ts @@ -24,10 +24,10 @@ import { TmModifierType, } from "#modifiers/modifier-type"; import { BattlePhase } from "#phases/battle-phase"; -import type { ModifierSelectUiHandler } from "#ui/handlers/modifier-select-ui-handler"; -import { SHOP_OPTIONS_ROW_LIMIT } from "#ui/handlers/modifier-select-ui-handler"; -import { PartyOption, PartyUiHandler, PartyUiMode } from "#ui/handlers/party-ui-handler"; -import { isNullOrUndefined, NumberHolder } from "#utils/common"; +import type { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler"; +import { SHOP_OPTIONS_ROW_LIMIT } from "#ui/modifier-select-ui-handler"; +import { PartyOption, PartyUiHandler, PartyUiMode } from "#ui/party-ui-handler"; +import { NumberHolder } from "#utils/common"; import i18next from "i18next"; export type ModifierSelectCallback = (rowCursor: number, cursor: number) => boolean; @@ -429,7 +429,7 @@ export class SelectModifierPhase extends BattlePhase { } let multiplier = 1; - if (!isNullOrUndefined(this.customModifierSettings?.rerollMultiplier)) { + if (this.customModifierSettings?.rerollMultiplier != null) { if (this.customModifierSettings.rerollMultiplier < 0) { // Completely overrides reroll cost to -1 and early exits return -1; diff --git a/src/phases/select-starter-phase.ts b/src/phases/select-starter-phase.ts index 50c06f96a77..27e2150fd06 100644 --- a/src/phases/select-starter-phase.ts +++ b/src/phases/select-starter-phase.ts @@ -7,10 +7,9 @@ import { ChallengeType } from "#enums/challenge-type"; import type { SpeciesId } from "#enums/species-id"; import { UiMode } from "#enums/ui-mode"; import { overrideHeldItems, overrideModifiers } from "#modifiers/modifier"; -import { SaveSlotUiMode } from "#ui/handlers/save-slot-select-ui-handler"; -import type { Starter } from "#ui/handlers/starter-select-ui-handler"; +import { SaveSlotUiMode } from "#ui/save-slot-select-ui-handler"; +import type { Starter } from "#ui/starter-select-ui-handler"; import { applyChallenges } from "#utils/challenge-utils"; -import { isNullOrUndefined } from "#utils/common"; import { getPokemonSpecies } from "#utils/pokemon-utils"; import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; @@ -51,7 +50,7 @@ export class SelectStarterPhase extends Phase { let starterFormIndex = Math.min(starterProps.formIndex, Math.max(starter.species.forms.length - 1, 0)); if ( starter.species.speciesId in Overrides.STARTER_FORM_OVERRIDES - && !isNullOrUndefined(Overrides.STARTER_FORM_OVERRIDES[starter.species.speciesId]) + && Overrides.STARTER_FORM_OVERRIDES[starter.species.speciesId] != null && starter.species.forms[Overrides.STARTER_FORM_OVERRIDES[starter.species.speciesId]!] ) { starterFormIndex = Overrides.STARTER_FORM_OVERRIDES[starter.species.speciesId]!; @@ -89,7 +88,7 @@ export class SelectStarterPhase extends Phase { starterPokemon.nickname = starter.nickname; } - if (!isNullOrUndefined(starter.teraType)) { + if (starter.teraType != null) { starterPokemon.teraType = starter.teraType; } else { starterPokemon.teraType = starterPokemon.species.type1; diff --git a/src/phases/stat-stage-change-phase.ts b/src/phases/stat-stage-change-phase.ts index 6c15342ddeb..2731c037d5f 100644 --- a/src/phases/stat-stage-change-phase.ts +++ b/src/phases/stat-stage-change-phase.ts @@ -13,7 +13,7 @@ import type { Pokemon } from "#field/pokemon"; import { ResetNegativeStatStageModifier } from "#modifiers/modifier"; import { PokemonPhase } from "#phases/pokemon-phase"; import type { ConditionalUserFieldProtectStatAbAttrParams, PreStatStageChangeAbAttrParams } from "#types/ability-types"; -import { BooleanHolder, isNullOrUndefined, NumberHolder } from "#utils/common"; +import { BooleanHolder, NumberHolder } from "#utils/common"; import i18next from "i18next"; export type StatStageChangeCallback = ( @@ -153,7 +153,7 @@ export class StatStageChangePhase extends PokemonPhase { applyAbAttrs("ConditionalUserFieldProtectStatAbAttr", abAttrParams); // TODO: Consider skipping this call if `cancelled` is false. const ally = pokemon.getAlly(); - if (!isNullOrUndefined(ally)) { + if (ally != null) { applyAbAttrs("ConditionalUserFieldProtectStatAbAttr", { ...abAttrParams, pokemon: ally }); } diff --git a/src/phases/switch-phase.ts b/src/phases/switch-phase.ts index 8b03f5ec5ce..83a699b6b08 100644 --- a/src/phases/switch-phase.ts +++ b/src/phases/switch-phase.ts @@ -3,7 +3,7 @@ import { DynamicPhaseType } from "#enums/dynamic-phase-type"; import { SwitchType } from "#enums/switch-type"; import { UiMode } from "#enums/ui-mode"; import { BattlePhase } from "#phases/battle-phase"; -import { PartyOption, PartyUiHandler, PartyUiMode } from "#ui/handlers/party-ui-handler"; +import { PartyOption, PartyUiHandler, PartyUiMode } from "#ui/party-ui-handler"; /** * Opens the party selector UI and transitions into a {@linkcode SwitchSummonPhase} diff --git a/src/phases/title-phase.ts b/src/phases/title-phase.ts index 97f6bf0d837..414be4c820c 100644 --- a/src/phases/title-phase.ts +++ b/src/phases/title-phase.ts @@ -16,9 +16,9 @@ import type { Modifier } from "#modifiers/modifier"; import { getDailyRunStarterModifiers, regenerateModifierPoolThresholds } from "#modifiers/modifier-type"; import { vouchers } from "#system/voucher"; import type { SessionSaveData } from "#types/save-data"; -import type { OptionSelectConfig, OptionSelectItem } from "#ui/handlers/abstract-option-select-ui-handler"; -import { SaveSlotUiMode } from "#ui/handlers/save-slot-select-ui-handler"; -import { isLocal, isLocalServerConnected, isNullOrUndefined } from "#utils/common"; +import type { OptionSelectConfig, OptionSelectItem } from "#ui/abstract-option-select-ui-handler"; +import { SaveSlotUiMode } from "#ui/save-slot-select-ui-handler"; +import { isLocal, isLocalServerConnected } from "#utils/common"; import i18next from "i18next"; export class TitlePhase extends Phase { @@ -289,7 +289,7 @@ export class TitlePhase extends Phase { } else { // Grab first 10 chars of ISO date format (YYYY-MM-DD) and convert to base64 let seed: string = btoa(new Date().toISOString().substring(0, 10)); - if (!isNullOrUndefined(Overrides.DAILY_RUN_SEED_OVERRIDE)) { + if (Overrides.DAILY_RUN_SEED_OVERRIDE != null) { seed = Overrides.DAILY_RUN_SEED_OVERRIDE; } generateDaily(seed); diff --git a/src/plugins/api/pokerogue-daily-api.ts b/src/plugins/api/pokerogue-daily-api.ts index dfde4720730..5ea3846e60e 100644 --- a/src/plugins/api/pokerogue-daily-api.ts +++ b/src/plugins/api/pokerogue-daily-api.ts @@ -1,6 +1,6 @@ import { ApiBase } from "#api/api-base"; import type { GetDailyRankingsPageCountRequest, GetDailyRankingsRequest } from "#types/api/pokerogue-daily-api"; -import type { RankingEntry } from "#ui/containers/daily-run-scoreboard"; +import type { RankingEntry } from "#ui/daily-run-scoreboard"; /** * A wrapper for daily-run PokéRogue API requests. diff --git a/src/sprites/variant.ts b/src/sprites/variant.ts index 28d7ed13839..9d7a20bc058 100644 --- a/src/sprites/variant.ts +++ b/src/sprites/variant.ts @@ -2,7 +2,6 @@ import { globalScene } from "#app/global-scene"; import { VariantTier } from "#enums/variant-tier"; import type { Pokemon } from "#field/pokemon"; import { hasExpSprite } from "#sprites/sprite-utils"; -import { isNullOrUndefined } from "#utils/common"; export type Variant = 0 | 1 | 2; @@ -138,7 +137,7 @@ export async function populateVariantColorCache( return fallbackVariantColor(cacheKey, spritePath, useExpSprite, battleSpritePath, error); }) .then(c => { - if (!isNullOrUndefined(c)) { + if (c != null) { variantColorCache[cacheKey] = c; } }); diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 247f8c3660a..8c2a1219245 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -72,7 +72,7 @@ import type { VoucherCounts, VoucherUnlocks, } from "#types/save-data"; -import { RUN_HISTORY_LIMIT } from "#ui/handlers/run-history-ui-handler"; +import { RUN_HISTORY_LIMIT } from "#ui/run-history-ui-handler"; import { applyChallenges } from "#utils/challenge-utils"; import { executeIf, fixedInt, isLocal, NumberHolder, randInt, randSeedItem } from "#utils/common"; import { decrypt, encrypt } from "#utils/data"; diff --git a/src/system/ribbons/ribbon-methods.ts b/src/system/ribbons/ribbon-methods.ts index 138c0be7b51..f1aeb9fefc2 100644 --- a/src/system/ribbons/ribbon-methods.ts +++ b/src/system/ribbons/ribbon-methods.ts @@ -2,7 +2,6 @@ import { globalScene } from "#app/global-scene"; import { pokemonPrevolutions } from "#balance/pokemon-evolutions"; import type { SpeciesId } from "#enums/species-id"; import type { RibbonFlag } from "#system/ribbons/ribbon-data"; -import { isNullOrUndefined } from "#utils/common"; /** * Award one or more ribbons to a species and its pre-evolutions @@ -14,7 +13,7 @@ export function awardRibbonsToSpeciesLine(id: SpeciesId, ribbons: RibbonFlag): v const dexData = globalScene.gameData.dexData; dexData[id].ribbons.award(ribbons); // Mark all pre-evolutions of the Pokémon with the same ribbon flags. - for (let prevoId = pokemonPrevolutions[id]; !isNullOrUndefined(prevoId); prevoId = pokemonPrevolutions[prevoId]) { + for (let prevoId = pokemonPrevolutions[id]; prevoId != null; prevoId = pokemonPrevolutions[prevoId]) { dexData[prevoId].ribbons.award(ribbons); } } diff --git a/src/system/version-migration/versions/v1_0_4.ts b/src/system/version-migration/versions/v1_0_4.ts index 8229b9320d5..5342396d576 100644 --- a/src/system/version-migration/versions/v1_0_4.ts +++ b/src/system/version-migration/versions/v1_0_4.ts @@ -8,7 +8,6 @@ import type { SessionSaveData, SystemSaveData } from "#types/save-data"; import type { SessionSaveMigrator } from "#types/session-save-migrator"; import type { SettingsSaveMigrator } from "#types/settings-save-migrator"; import type { SystemSaveMigrator } from "#types/system-save-migrator"; -import { isNullOrUndefined } from "#utils/common"; /** * Migrate ability starter data if empty for caught species. @@ -82,7 +81,7 @@ const fixLegendaryStats: SystemSaveMigrator = { const fixStarterData: SystemSaveMigrator = { version: "1.0.4", migrate: (data: SystemSaveData): void => { - if (!isNullOrUndefined(data.starterData)) { + if (data.starterData != null) { for (const starterId of defaultStarterSpecies) { if (data.starterData[starterId]?.abilityAttr) { data.starterData[starterId].abilityAttr |= AbilityAttr.ABILITY_1; @@ -198,7 +197,7 @@ const migrateCustomPokemonData: SessionSaveMigrator = { pokemon["fusionMysteryEncounterPokemonData"] = null; } pokemon.customPokemonData = pokemon.customPokemonData ?? new CustomPokemonData(); - if (!isNullOrUndefined(pokemon["natureOverride"]) && pokemon["natureOverride"] >= 0) { + if (pokemon["natureOverride"] != null && pokemon["natureOverride"] >= 0) { pokemon.customPokemonData.nature = pokemon["natureOverride"]; pokemon["natureOverride"] = -1; } diff --git a/src/system/version-migration/versions/v1_7_0.ts b/src/system/version-migration/versions/v1_7_0.ts index 6d365cf31ac..e526ccd2c2b 100644 --- a/src/system/version-migration/versions/v1_7_0.ts +++ b/src/system/version-migration/versions/v1_7_0.ts @@ -3,7 +3,6 @@ import { DexAttr } from "#enums/dex-attr"; import type { SessionSaveData, SystemSaveData } from "#types/save-data"; import type { SessionSaveMigrator } from "#types/session-save-migrator"; import type { SystemSaveMigrator } from "#types/system-save-migrator"; -import { isNullOrUndefined } from "#utils/common"; import { getPokemonSpecies, getPokemonSpeciesForm } from "#utils/pokemon-utils"; /** @@ -68,13 +67,13 @@ const migrateTera: SessionSaveMigrator = { } data.party.forEach(p => { - if (isNullOrUndefined(p.teraType)) { + if (p.teraType == null) { p.teraType = getPokemonSpeciesForm(p.species, p.formIndex).type1; } }); data.enemyParty.forEach(p => { - if (isNullOrUndefined(p.teraType)) { + if (p.teraType == null) { p.teraType = getPokemonSpeciesForm(p.species, p.formIndex).type1; } }); diff --git a/src/timed-event-manager.ts b/src/timed-event-manager.ts index ed92a1c9ca5..7db89b2a0ef 100644 --- a/src/timed-event-manager.ts +++ b/src/timed-event-manager.ts @@ -9,7 +9,6 @@ import { TextStyle } from "#enums/text-style"; import { WeatherType } from "#enums/weather-type"; import { addTextObject } from "#ui/text"; import type { nil } from "#utils/common"; -import { isNullOrUndefined } from "#utils/common"; import i18next from "i18next"; export enum EventType { @@ -428,7 +427,7 @@ export class TimedEventManager { getEventBannerLangs(): string[] { const ret: string[] = []; - ret.push(...timedEvents.find(te => this.isActive(te) && !isNullOrUndefined(te.availableLangs))?.availableLangs!); + ret.push(...timedEvents.find(te => this.isActive(te) && te.availableLangs != null)?.availableLangs!); return ret; } @@ -437,7 +436,7 @@ export class TimedEventManager { timedEvents .filter(te => this.isActive(te)) .map(te => { - if (!isNullOrUndefined(te.eventEncounters)) { + if (te.eventEncounters != null) { ret.push(...te.eventEncounters); } }); @@ -452,7 +451,7 @@ export class TimedEventManager { let multiplier = CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER; const classicFriendshipEvents = timedEvents.filter(te => this.isActive(te)); for (const fe of classicFriendshipEvents) { - if (!isNullOrUndefined(fe.classicFriendshipMultiplier) && fe.classicFriendshipMultiplier > multiplier) { + if (fe.classicFriendshipMultiplier != null && fe.classicFriendshipMultiplier > multiplier) { multiplier = fe.classicFriendshipMultiplier; } } @@ -476,7 +475,7 @@ export class TimedEventManager { timedEvents .filter(te => this.isActive(te)) .map(te => { - if (!isNullOrUndefined(te.delibirdyBuff)) { + if (te.delibirdyBuff != null) { ret.push(...te.delibirdyBuff); } }); @@ -492,7 +491,7 @@ export class TimedEventManager { timedEvents .filter(te => this.isActive(te)) .map(te => { - if (!isNullOrUndefined(te.weather)) { + if (te.weather != null) { ret.push(...te.weather); } }); @@ -504,7 +503,7 @@ export class TimedEventManager { timedEvents .filter(te => this.isActive(te)) .map(te => { - if (!isNullOrUndefined(te.mysteryEncounterTierChanges)) { + if (te.mysteryEncounterTierChanges != null) { ret.push(...te.mysteryEncounterTierChanges); } }); @@ -514,7 +513,7 @@ export class TimedEventManager { getEventMysteryEncountersDisabled(): MysteryEncounterType[] { const ret: MysteryEncounterType[] = []; timedEvents - .filter(te => this.isActive(te) && !isNullOrUndefined(te.mysteryEncounterTierChanges)) + .filter(te => this.isActive(te) && te.mysteryEncounterTierChanges != null) .map(te => { te.mysteryEncounterTierChanges?.map(metc => { if (metc.disable) { @@ -531,7 +530,7 @@ export class TimedEventManager { ): MysteryEncounterTier { let ret = normal; timedEvents - .filter(te => this.isActive(te) && !isNullOrUndefined(te.mysteryEncounterTierChanges)) + .filter(te => this.isActive(te) && te.mysteryEncounterTierChanges != null) .map(te => { te.mysteryEncounterTierChanges?.map(metc => { if (metc.mysteryEncounter === encounterType) { @@ -544,7 +543,7 @@ export class TimedEventManager { getEventLuckBoost(): number { let ret = 0; - const luckEvents = timedEvents.filter(te => this.isActive(te) && !isNullOrUndefined(te.luckBoost)); + const luckEvents = timedEvents.filter(te => this.isActive(te) && te.luckBoost != null); for (const le of luckEvents) { ret += le.luckBoost!; } @@ -556,7 +555,7 @@ export class TimedEventManager { timedEvents .filter(te => this.isActive(te)) .map(te => { - if (!isNullOrUndefined(te.luckBoostedSpecies)) { + if (te.luckBoostedSpecies != null) { ret.push(...te.luckBoostedSpecies.filter(s => !ret.includes(s))); } }); @@ -576,7 +575,7 @@ export class TimedEventManager { getFixedBattleEventRewards(wave: number): string[] { const ret: string[] = []; timedEvents - .filter(te => this.isActive(te) && !isNullOrUndefined(te.classicWaveRewards)) + .filter(te => this.isActive(te) && te.classicWaveRewards != null) .map(te => { ret.push(...te.classicWaveRewards!.filter(cwr => cwr.wave === wave).map(cwr => cwr.type)); }); @@ -586,7 +585,7 @@ export class TimedEventManager { // Gets the extra shiny chance for trainers due to event (odds/65536) getClassicTrainerShinyChance(): number { let ret = 0; - const tsEvents = timedEvents.filter(te => this.isActive(te) && !isNullOrUndefined(te.trainerShinyChance)); + const tsEvents = timedEvents.filter(te => this.isActive(te) && te.trainerShinyChance != null); tsEvents.map(t => (ret += t.trainerShinyChance!)); return ret; } @@ -594,7 +593,7 @@ export class TimedEventManager { getEventBgmReplacement(bgm: string): string { let ret = bgm; timedEvents.map(te => { - if (this.isActive(te) && !isNullOrUndefined(te.music)) { + if (this.isActive(te) && te.music != null) { te.music.map(mr => { if (mr[0] === bgm) { console.log(`it is ${te.name} so instead of ${mr[0]} we play ${mr[1]}`); diff --git a/src/tutorial.ts b/src/tutorial.ts index 5ab0be116f8..018d0927da0 100644 --- a/src/tutorial.ts +++ b/src/tutorial.ts @@ -1,8 +1,8 @@ import { globalScene } from "#app/global-scene"; import Overrides from "#app/overrides"; import { UiMode } from "#enums/ui-mode"; -import { AwaitableUiHandler } from "#ui/handlers/awaitable-ui-handler"; -import type { UiHandler } from "#ui/handlers/ui-handler"; +import { AwaitableUiHandler } from "#ui/awaitable-ui-handler"; +import type { UiHandler } from "#ui/ui-handler"; import i18next from "i18next"; export enum Tutorial { diff --git a/src/ui-inputs.ts b/src/ui-inputs.ts index 5c4a530b39a..fd7883d3136 100644 --- a/src/ui-inputs.ts +++ b/src/ui-inputs.ts @@ -3,16 +3,16 @@ import type { InputsController } from "#app/inputs-controller"; import { Button } from "#enums/buttons"; import { UiMode } from "#enums/ui-mode"; import { Setting, SettingKeys, settingIndex } from "#system/settings"; -import type { MessageUiHandler } from "#ui/handlers/message-ui-handler"; -import { PokedexPageUiHandler } from "#ui/handlers/pokedex-page-ui-handler"; -import { PokedexUiHandler } from "#ui/handlers/pokedex-ui-handler"; -import { RunInfoUiHandler } from "#ui/handlers/run-info-ui-handler"; -import { StarterSelectUiHandler } from "#ui/handlers/starter-select-ui-handler"; +import type { MessageUiHandler } from "#ui/message-ui-handler"; +import { PokedexPageUiHandler } from "#ui/pokedex-page-ui-handler"; +import { PokedexUiHandler } from "#ui/pokedex-ui-handler"; +import { RunInfoUiHandler } from "#ui/run-info-ui-handler"; import { SettingsAudioUiHandler } from "#ui/settings-audio-ui-handler"; import { SettingsDisplayUiHandler } from "#ui/settings-display-ui-handler"; import { SettingsGamepadUiHandler } from "#ui/settings-gamepad-ui-handler"; import { SettingsKeyboardUiHandler } from "#ui/settings-keyboard-ui-handler"; import { SettingsUiHandler } from "#ui/settings-ui-handler"; +import { StarterSelectUiHandler } from "#ui/starter-select-ui-handler"; import Phaser from "phaser"; type ActionKeys = Record void>; diff --git a/src/ui/battle-info/enemy-battle-info.ts b/src/ui/battle-info/enemy-battle-info.ts index ad72afedc38..1a16a1dd934 100644 --- a/src/ui/battle-info/enemy-battle-info.ts +++ b/src/ui/battle-info/enemy-battle-info.ts @@ -3,9 +3,9 @@ import { Stat } from "#enums/stat"; import { TextStyle } from "#enums/text-style"; import { UiTheme } from "#enums/ui-theme"; import type { EnemyPokemon } from "#field/pokemon"; +import { BattleFlyout } from "#ui/battle-flyout"; import type { BattleInfoParamList } from "#ui/battle-info"; import { BattleInfo } from "#ui/battle-info"; -import { BattleFlyout } from "#ui/containers/battle-flyout"; import { addTextObject } from "#ui/text"; import { addWindow, WindowVariant } from "#ui/ui-theme"; import { getLocalizedSpriteKey } from "#utils/common"; diff --git a/src/ui/containers/arena-flyout.ts b/src/ui/containers/arena-flyout.ts index 3555694760d..a73846de1ac 100644 --- a/src/ui/containers/arena-flyout.ts +++ b/src/ui/containers/arena-flyout.ts @@ -15,8 +15,8 @@ import { } from "#events/arena"; import type { TurnEndEvent } from "#events/battle-scene"; import { BattleSceneEventType } from "#events/battle-scene"; -import { TimeOfDayWidget } from "#ui/containers/time-of-day-widget"; import { addTextObject } from "#ui/text"; +import { TimeOfDayWidget } from "#ui/time-of-day-widget"; import { addWindow, WindowVariant } from "#ui/ui-theme"; import { fixedInt } from "#utils/common"; import { toCamelCase, toTitleCase } from "#utils/strings"; diff --git a/src/ui/containers/dropdown.ts b/src/ui/containers/dropdown.ts index bf589085d2e..2244aa0e5ce 100644 --- a/src/ui/containers/dropdown.ts +++ b/src/ui/containers/dropdown.ts @@ -1,6 +1,6 @@ import { globalScene } from "#app/global-scene"; import { TextStyle } from "#enums/text-style"; -import { ScrollBar } from "#ui/containers/scroll-bar"; +import { ScrollBar } from "#ui/scroll-bar"; import { addTextObject } from "#ui/text"; import { addWindow, WindowVariant } from "#ui/ui-theme"; import i18next from "i18next"; diff --git a/src/ui/containers/egg-counter-container.ts b/src/ui/containers/egg-counter-container.ts index d080dd66a68..385480fc91d 100644 --- a/src/ui/containers/egg-counter-container.ts +++ b/src/ui/containers/egg-counter-container.ts @@ -2,7 +2,7 @@ import { globalScene } from "#app/global-scene"; import { TextStyle } from "#enums/text-style"; import type { EggCountChangedEvent } from "#events/egg"; import { EggEventType } from "#events/egg"; -import type { EggHatchSceneUiHandler } from "#ui/handlers/egg-hatch-scene-ui-handler"; +import type { EggHatchSceneUiHandler } from "#ui/egg-hatch-scene-ui-handler"; import { addTextObject } from "#ui/text"; import { addWindow } from "#ui/ui-theme"; diff --git a/src/ui/containers/filter-bar.ts b/src/ui/containers/filter-bar.ts index bbca38c3f53..3f164c4fcbb 100644 --- a/src/ui/containers/filter-bar.ts +++ b/src/ui/containers/filter-bar.ts @@ -2,9 +2,9 @@ import { globalScene } from "#app/global-scene"; import type { DropDownColumn } from "#enums/drop-down-column"; import { TextStyle } from "#enums/text-style"; import type { UiTheme } from "#enums/ui-theme"; -import type { DropDown } from "#ui/containers/dropdown"; -import { DropDownType } from "#ui/containers/dropdown"; -import type { StarterContainer } from "#ui/containers/starter-container"; +import type { DropDown } from "#ui/dropdown"; +import { DropDownType } from "#ui/dropdown"; +import type { StarterContainer } from "#ui/starter-container"; import { addTextObject, getTextColor } from "#ui/text"; import { addWindow, WindowVariant } from "#ui/ui-theme"; diff --git a/src/ui/containers/filter-text.ts b/src/ui/containers/filter-text.ts index 0d15aca8530..8601a86defa 100644 --- a/src/ui/containers/filter-text.ts +++ b/src/ui/containers/filter-text.ts @@ -2,8 +2,8 @@ import { globalScene } from "#app/global-scene"; import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; import type { UiTheme } from "#enums/ui-theme"; -import type { StarterContainer } from "#ui/containers/starter-container"; -import type { AwaitableUiHandler } from "#ui/handlers/awaitable-ui-handler"; +import type { AwaitableUiHandler } from "#ui/awaitable-ui-handler"; +import type { StarterContainer } from "#ui/starter-container"; import { addTextObject, getTextColor } from "#ui/text"; import type { UI } from "#ui/ui"; import { addWindow, WindowVariant } from "#ui/ui-theme"; diff --git a/src/ui/containers/hatched-pokemon-container.ts b/src/ui/containers/hatched-pokemon-container.ts index 456ffd923a1..b1bc66c21ec 100644 --- a/src/ui/containers/hatched-pokemon-container.ts +++ b/src/ui/containers/hatched-pokemon-container.ts @@ -4,8 +4,8 @@ import { Gender } from "#data/gender"; import type { PokemonSpecies } from "#data/pokemon-species"; import { DexAttr } from "#enums/dex-attr"; import { getVariantTint } from "#sprites/variant"; -import type { PokemonIconAnimHelper } from "#ui/utils/pokemon-icon-anim-helper"; -import { PokemonIconAnimMode } from "#ui/utils/pokemon-icon-anim-helper"; +import type { PokemonIconAnimHelper } from "#ui/pokemon-icon-anim-helper"; +import { PokemonIconAnimMode } from "#ui/pokemon-icon-anim-helper"; /** * A container for a Pokemon's sprite and icons to get displayed in the egg summary screen diff --git a/src/ui/containers/pokedex-mon-container.ts b/src/ui/containers/pokedex-mon-container.ts index 15ef6c9b5c8..158f42dd42a 100644 --- a/src/ui/containers/pokedex-mon-container.ts +++ b/src/ui/containers/pokedex-mon-container.ts @@ -3,7 +3,6 @@ import type { PokemonSpecies } from "#data/pokemon-species"; import { TextStyle } from "#enums/text-style"; import type { Variant } from "#sprites/variant"; import { addTextObject } from "#ui/text"; -import { isNullOrUndefined } from "#utils/common"; interface SpeciesDetails { shiny?: boolean; @@ -177,16 +176,16 @@ export class PokedexMonContainer extends Phaser.GameObjects.Container { const defaultDexAttr = globalScene.gameData.getSpeciesDefaultDexAttr(species, false, true); const defaultProps = globalScene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr); - if (!isNullOrUndefined(formIndex)) { + if (formIndex != null) { defaultProps.formIndex = formIndex; } - if (!isNullOrUndefined(shiny)) { + if (shiny != null) { defaultProps.shiny = shiny; } - if (!isNullOrUndefined(variant)) { + if (variant != null) { defaultProps.variant = variant; } - if (!isNullOrUndefined(female)) { + if (female != null) { defaultProps.female = female; } diff --git a/src/ui/containers/pokemon-info-container.ts b/src/ui/containers/pokemon-info-container.ts index 6072b610430..3b6e5bc2fc1 100644 --- a/src/ui/containers/pokemon-info-container.ts +++ b/src/ui/containers/pokemon-info-container.ts @@ -8,7 +8,7 @@ import type { Pokemon } from "#field/pokemon"; import { getVariantTint } from "#sprites/variant"; import type { DexEntry } from "#types/dex-data"; import type { StarterDataEntry } from "#types/save-data"; -import { ConfirmUiHandler } from "#ui/handlers/confirm-ui-handler"; +import { ConfirmUiHandler } from "#ui/confirm-ui-handler"; import { addBBCodeTextObject, addTextObject, getTextColor } from "#ui/text"; import { addWindow } from "#ui/ui-theme"; import { fixedInt, getShinyDescriptor } from "#utils/common"; diff --git a/src/ui/handlers/abstract-binding-ui-handler.ts b/src/ui/handlers/abstract-binding-ui-handler.ts index d106ff6f914..6b747a10d2b 100644 --- a/src/ui/handlers/abstract-binding-ui-handler.ts +++ b/src/ui/handlers/abstract-binding-ui-handler.ts @@ -2,9 +2,9 @@ import { globalScene } from "#app/global-scene"; import { Button } from "#enums/buttons"; import { TextStyle } from "#enums/text-style"; import type { UiMode } from "#enums/ui-mode"; -import { UiHandler } from "#ui/handlers/ui-handler"; import { NavigationManager } from "#ui/navigation-menu"; import { addTextObject, getTextColor } from "#ui/text"; +import { UiHandler } from "#ui/ui-handler"; import { addWindow } from "#ui/ui-theme"; import i18next from "i18next"; diff --git a/src/ui/handlers/abstract-option-select-ui-handler.ts b/src/ui/handlers/abstract-option-select-ui-handler.ts index 1e102010e4a..b86d5c33a0d 100644 --- a/src/ui/handlers/abstract-option-select-ui-handler.ts +++ b/src/ui/handlers/abstract-option-select-ui-handler.ts @@ -2,8 +2,8 @@ import { globalScene } from "#app/global-scene"; import { Button } from "#enums/buttons"; import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; -import { UiHandler } from "#ui/handlers/ui-handler"; import { addBBCodeTextObject, getTextColor, getTextStyleOptions } from "#ui/text"; +import { UiHandler } from "#ui/ui-handler"; import { addWindow } from "#ui/ui-theme"; import { fixedInt, rgbHexToRgba } from "#utils/common"; import { argbFromRgba } from "@material/material-color-utilities"; diff --git a/src/ui/handlers/achvs-ui-handler.ts b/src/ui/handlers/achvs-ui-handler.ts index d7cf82e78ad..dea1829499e 100644 --- a/src/ui/handlers/achvs-ui-handler.ts +++ b/src/ui/handlers/achvs-ui-handler.ts @@ -8,8 +8,8 @@ import { achvs, getAchievementDescription } from "#system/achv"; import type { Voucher } from "#system/voucher"; import { getVoucherTypeIcon, getVoucherTypeName, vouchers } from "#system/voucher"; import type { AchvUnlocks, VoucherUnlocks } from "#types/save-data"; -import { ScrollBar } from "#ui/containers/scroll-bar"; -import { MessageUiHandler } from "#ui/handlers/message-ui-handler"; +import { MessageUiHandler } from "#ui/message-ui-handler"; +import { ScrollBar } from "#ui/scroll-bar"; import { addTextObject } from "#ui/text"; import { addWindow } from "#ui/ui-theme"; import i18next from "i18next"; diff --git a/src/ui/handlers/admin-ui-handler.ts b/src/ui/handlers/admin-ui-handler.ts index 9ca30e35313..38420c61010 100644 --- a/src/ui/handlers/admin-ui-handler.ts +++ b/src/ui/handlers/admin-ui-handler.ts @@ -3,9 +3,9 @@ import { globalScene } from "#app/global-scene"; import { Button } from "#enums/buttons"; import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; -import type { InputFieldConfig } from "#ui/handlers/form-modal-ui-handler"; -import { FormModalUiHandler } from "#ui/handlers/form-modal-ui-handler"; -import type { ModalConfig } from "#ui/handlers/modal-ui-handler"; +import type { InputFieldConfig } from "#ui/form-modal-ui-handler"; +import { FormModalUiHandler } from "#ui/form-modal-ui-handler"; +import type { ModalConfig } from "#ui/modal-ui-handler"; import { getTextColor } from "#ui/text"; import { toTitleCase } from "#utils/strings"; diff --git a/src/ui/handlers/autocomplete-ui-handler.ts b/src/ui/handlers/autocomplete-ui-handler.ts index 914fe23a123..337b17048dc 100644 --- a/src/ui/handlers/autocomplete-ui-handler.ts +++ b/src/ui/handlers/autocomplete-ui-handler.ts @@ -1,6 +1,6 @@ import { Button } from "#enums/buttons"; import { UiMode } from "#enums/ui-mode"; -import { AbstractOptionSelectUiHandler } from "#ui/handlers/abstract-option-select-ui-handler"; +import { AbstractOptionSelectUiHandler } from "#ui/abstract-option-select-ui-handler"; export class AutoCompleteUiHandler extends AbstractOptionSelectUiHandler { modalContainer: Phaser.GameObjects.Container; diff --git a/src/ui/handlers/awaitable-ui-handler.ts b/src/ui/handlers/awaitable-ui-handler.ts index 9dcd3377da2..e8513b4acc1 100644 --- a/src/ui/handlers/awaitable-ui-handler.ts +++ b/src/ui/handlers/awaitable-ui-handler.ts @@ -1,7 +1,7 @@ import { globalScene } from "#app/global-scene"; import { Button } from "#enums/buttons"; import type { UiMode } from "#enums/ui-mode"; -import { UiHandler } from "#ui/handlers/ui-handler"; +import { UiHandler } from "#ui/ui-handler"; export abstract class AwaitableUiHandler extends UiHandler { protected awaitingActionInput: boolean; diff --git a/src/ui/handlers/ball-ui-handler.ts b/src/ui/handlers/ball-ui-handler.ts index 3d1868c207e..3d8efca96b8 100644 --- a/src/ui/handlers/ball-ui-handler.ts +++ b/src/ui/handlers/ball-ui-handler.ts @@ -5,8 +5,8 @@ import { Command } from "#enums/command"; import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; import type { CommandPhase } from "#phases/command-phase"; -import { UiHandler } from "#ui/handlers/ui-handler"; import { addTextObject, getTextStyleOptions } from "#ui/text"; +import { UiHandler } from "#ui/ui-handler"; import { addWindow } from "#ui/ui-theme"; import i18next from "i18next"; diff --git a/src/ui/handlers/battle-message-ui-handler.ts b/src/ui/handlers/battle-message-ui-handler.ts index 6912109c7e7..f845f22a730 100644 --- a/src/ui/handlers/battle-message-ui-handler.ts +++ b/src/ui/handlers/battle-message-ui-handler.ts @@ -3,7 +3,7 @@ import { Button } from "#enums/buttons"; import { getStatKey, PERMANENT_STATS } from "#enums/stat"; import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; -import { MessageUiHandler } from "#ui/handlers/message-ui-handler"; +import { MessageUiHandler } from "#ui/message-ui-handler"; import { addBBCodeTextObject, addTextObject, getTextColor } from "#ui/text"; import { addWindow } from "#ui/ui-theme"; import i18next from "i18next"; diff --git a/src/ui/handlers/challenges-select-ui-handler.ts b/src/ui/handlers/challenges-select-ui-handler.ts index 5cc91285a74..62f9d578667 100644 --- a/src/ui/handlers/challenges-select-ui-handler.ts +++ b/src/ui/handlers/challenges-select-ui-handler.ts @@ -5,8 +5,8 @@ import { Challenges } from "#enums/challenges"; import { Color, ShadowColor } from "#enums/color"; import { TextStyle } from "#enums/text-style"; import type { UiMode } from "#enums/ui-mode"; -import { UiHandler } from "#ui/handlers/ui-handler"; import { addTextObject } from "#ui/text"; +import { UiHandler } from "#ui/ui-handler"; import { addWindow } from "#ui/ui-theme"; import { getLocalizedSpriteKey } from "#utils/common"; import i18next from "i18next"; diff --git a/src/ui/handlers/change-password-form-ui-handler.ts b/src/ui/handlers/change-password-form-ui-handler.ts index f4fdf349978..eccc67ffb04 100644 --- a/src/ui/handlers/change-password-form-ui-handler.ts +++ b/src/ui/handlers/change-password-form-ui-handler.ts @@ -1,9 +1,9 @@ import { globalScene } from "#app/global-scene"; import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; import { UiMode } from "#enums/ui-mode"; -import type { InputFieldConfig } from "#ui/handlers/form-modal-ui-handler"; -import { FormModalUiHandler } from "#ui/handlers/form-modal-ui-handler"; -import type { ModalConfig } from "#ui/handlers/modal-ui-handler"; +import type { InputFieldConfig } from "#ui/form-modal-ui-handler"; +import { FormModalUiHandler } from "#ui/form-modal-ui-handler"; +import type { ModalConfig } from "#ui/modal-ui-handler"; import i18next from "i18next"; export class ChangePasswordFormUiHandler extends FormModalUiHandler { diff --git a/src/ui/handlers/command-ui-handler.ts b/src/ui/handlers/command-ui-handler.ts index de5e51a4210..693fe0eefef 100644 --- a/src/ui/handlers/command-ui-handler.ts +++ b/src/ui/handlers/command-ui-handler.ts @@ -9,9 +9,9 @@ import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; import { TerastallizeAccessModifier } from "#modifiers/modifier"; import type { CommandPhase } from "#phases/command-phase"; -import { PartyUiHandler, PartyUiMode } from "#ui/handlers/party-ui-handler"; -import { UiHandler } from "#ui/handlers/ui-handler"; +import { PartyUiHandler, PartyUiMode } from "#ui/party-ui-handler"; import { addTextObject } from "#ui/text"; +import { UiHandler } from "#ui/ui-handler"; import i18next from "i18next"; export class CommandUiHandler extends UiHandler { diff --git a/src/ui/handlers/confirm-ui-handler.ts b/src/ui/handlers/confirm-ui-handler.ts index 77f1f182514..64a0bb7028a 100644 --- a/src/ui/handlers/confirm-ui-handler.ts +++ b/src/ui/handlers/confirm-ui-handler.ts @@ -1,8 +1,8 @@ import { globalScene } from "#app/global-scene"; import { Button } from "#enums/buttons"; import { UiMode } from "#enums/ui-mode"; -import type { OptionSelectConfig } from "#ui/handlers/abstract-option-select-ui-handler"; -import { AbstractOptionSelectUiHandler } from "#ui/handlers/abstract-option-select-ui-handler"; +import type { OptionSelectConfig } from "#ui/abstract-option-select-ui-handler"; +import { AbstractOptionSelectUiHandler } from "#ui/abstract-option-select-ui-handler"; import i18next from "i18next"; export class ConfirmUiHandler extends AbstractOptionSelectUiHandler { diff --git a/src/ui/handlers/egg-gacha-ui-handler.ts b/src/ui/handlers/egg-gacha-ui-handler.ts index bd96b4d9392..f24c8c04fdb 100644 --- a/src/ui/handlers/egg-gacha-ui-handler.ts +++ b/src/ui/handlers/egg-gacha-ui-handler.ts @@ -9,7 +9,7 @@ import { GachaType } from "#enums/gacha-types"; import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; import { getVoucherTypeIcon, VoucherType } from "#system/voucher"; -import { MessageUiHandler } from "#ui/handlers/message-ui-handler"; +import { MessageUiHandler } from "#ui/message-ui-handler"; import { addTextObject, getEggTierTextTint, getTextStyleOptions } from "#ui/text"; import { addWindow } from "#ui/ui-theme"; import { fixedInt, randSeedShuffle } from "#utils/common"; @@ -96,7 +96,7 @@ export class EggGachaUiHandler extends MessageUiHandler { legendaryLabelY = 0; } - const gachaUpLabel = addTextObject(gachaX, gachaY, i18next.t("egg:legendaryUPGacha"), gachaTextStyle).setOrigin(0); + const gachaUpLabel = addTextObject(gachaX, gachaY, i18next.t("egg:legendaryUpGacha"), gachaTextStyle).setOrigin(0); gachaInfoContainer.add(gachaUpLabel); switch (gachaType as GachaType) { @@ -124,14 +124,14 @@ export class EggGachaUiHandler extends MessageUiHandler { gachaUpLabel.setAlign("center").setY(0); } - gachaUpLabel.setText(i18next.t("egg:moveUPGacha")).setX(0).setOrigin(0.5, 0); + gachaUpLabel.setText(i18next.t("egg:moveUpGacha")).setX(0).setOrigin(0.5, 0); break; case GachaType.SHINY: if (["de", "fr", "ko", "ru"].includes(currentLanguage)) { gachaUpLabel.setAlign("center").setY(0); } - gachaUpLabel.setText(i18next.t("egg:shinyUPGacha")).setX(0).setOrigin(0.5, 0); + gachaUpLabel.setText(i18next.t("egg:shinyUpGacha")).setX(0).setOrigin(0.5, 0); break; } diff --git a/src/ui/handlers/egg-hatch-scene-ui-handler.ts b/src/ui/handlers/egg-hatch-scene-ui-handler.ts index 2fff9980de3..d0827532e14 100644 --- a/src/ui/handlers/egg-hatch-scene-ui-handler.ts +++ b/src/ui/handlers/egg-hatch-scene-ui-handler.ts @@ -1,7 +1,7 @@ import { globalScene } from "#app/global-scene"; import { Button } from "#enums/buttons"; import { UiMode } from "#enums/ui-mode"; -import { UiHandler } from "#ui/handlers/ui-handler"; +import { UiHandler } from "#ui/ui-handler"; export class EggHatchSceneUiHandler extends UiHandler { public eggHatchContainer: Phaser.GameObjects.Container; diff --git a/src/ui/handlers/egg-list-ui-handler.ts b/src/ui/handlers/egg-list-ui-handler.ts index e4d1d5b10e9..3ec509261bb 100644 --- a/src/ui/handlers/egg-list-ui-handler.ts +++ b/src/ui/handlers/egg-list-ui-handler.ts @@ -2,12 +2,12 @@ import { globalScene } from "#app/global-scene"; import { Button } from "#enums/buttons"; import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; -import { ScrollBar } from "#ui/containers/scroll-bar"; -import { MessageUiHandler } from "#ui/handlers/message-ui-handler"; +import { MessageUiHandler } from "#ui/message-ui-handler"; +import { PokemonIconAnimHelper, PokemonIconAnimMode } from "#ui/pokemon-icon-anim-helper"; +import { ScrollBar } from "#ui/scroll-bar"; +import { ScrollableGridHelper } from "#ui/scrollable-grid-helper"; import { addTextObject } from "#ui/text"; import { addWindow } from "#ui/ui-theme"; -import { PokemonIconAnimHelper, PokemonIconAnimMode } from "#ui/utils/pokemon-icon-anim-helper"; -import { ScrollableGridHelper } from "#ui/utils/scrollable-grid-helper"; import i18next from "i18next"; export class EggListUiHandler extends MessageUiHandler { diff --git a/src/ui/handlers/egg-summary-ui-handler.ts b/src/ui/handlers/egg-summary-ui-handler.ts index 1097615d83d..35dc9c5176f 100644 --- a/src/ui/handlers/egg-summary-ui-handler.ts +++ b/src/ui/handlers/egg-summary-ui-handler.ts @@ -3,12 +3,12 @@ import { getEggTierForSpecies } from "#data/egg"; import type { EggHatchData } from "#data/egg-hatch-data"; import { Button } from "#enums/buttons"; import { UiMode } from "#enums/ui-mode"; -import { HatchedPokemonContainer } from "#ui/containers/hatched-pokemon-container"; -import { PokemonHatchInfoContainer } from "#ui/containers/pokemon-hatch-info-container"; -import { ScrollBar } from "#ui/containers/scroll-bar"; -import { MessageUiHandler } from "#ui/handlers/message-ui-handler"; -import { PokemonIconAnimHelper, PokemonIconAnimMode } from "#ui/utils/pokemon-icon-anim-helper"; -import { ScrollableGridHelper } from "#ui/utils/scrollable-grid-helper"; +import { HatchedPokemonContainer } from "#ui/hatched-pokemon-container"; +import { MessageUiHandler } from "#ui/message-ui-handler"; +import { PokemonHatchInfoContainer } from "#ui/pokemon-hatch-info-container"; +import { PokemonIconAnimHelper, PokemonIconAnimMode } from "#ui/pokemon-icon-anim-helper"; +import { ScrollBar } from "#ui/scroll-bar"; +import { ScrollableGridHelper } from "#ui/scrollable-grid-helper"; const iconContainerX = 112; const iconContainerY = 9; diff --git a/src/ui/handlers/evolution-scene-ui-handler.ts b/src/ui/handlers/evolution-scene-ui-handler.ts index a1783544a07..ba3d8f8f57f 100644 --- a/src/ui/handlers/evolution-scene-ui-handler.ts +++ b/src/ui/handlers/evolution-scene-ui-handler.ts @@ -2,7 +2,7 @@ import { globalScene } from "#app/global-scene"; import { Button } from "#enums/buttons"; import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; -import { MessageUiHandler } from "#ui/handlers/message-ui-handler"; +import { MessageUiHandler } from "#ui/message-ui-handler"; import { addTextObject } from "#ui/text"; export class EvolutionSceneUiHandler extends MessageUiHandler { diff --git a/src/ui/handlers/fight-ui-handler.ts b/src/ui/handlers/fight-ui-handler.ts index 9dd00a90b66..72b6949eb9c 100644 --- a/src/ui/handlers/fight-ui-handler.ts +++ b/src/ui/handlers/fight-ui-handler.ts @@ -12,9 +12,9 @@ import { UiMode } from "#enums/ui-mode"; import type { EnemyPokemon, Pokemon } from "#field/pokemon"; import type { PokemonMove } from "#moves/pokemon-move"; import type { CommandPhase } from "#phases/command-phase"; -import { MoveInfoOverlay } from "#ui/containers/move-info-overlay"; -import { UiHandler } from "#ui/handlers/ui-handler"; +import { MoveInfoOverlay } from "#ui/move-info-overlay"; import { addTextObject, getTextColor } from "#ui/text"; +import { UiHandler } from "#ui/ui-handler"; import { fixedInt, getLocalizedSpriteKey, padInt } from "#utils/common"; import i18next from "i18next"; diff --git a/src/ui/handlers/form-modal-ui-handler.ts b/src/ui/handlers/form-modal-ui-handler.ts index af1d8653df7..2efd39ca359 100644 --- a/src/ui/handlers/form-modal-ui-handler.ts +++ b/src/ui/handlers/form-modal-ui-handler.ts @@ -2,8 +2,8 @@ import { globalScene } from "#app/global-scene"; import { Button } from "#enums/buttons"; import { TextStyle } from "#enums/text-style"; import type { UiMode } from "#enums/ui-mode"; -import type { ModalConfig } from "#ui/handlers/modal-ui-handler"; -import { ModalUiHandler } from "#ui/handlers/modal-ui-handler"; +import type { ModalConfig } from "#ui/modal-ui-handler"; +import { ModalUiHandler } from "#ui/modal-ui-handler"; import { addTextInputObject, addTextObject, getTextColor } from "#ui/text"; import { addWindow, WindowVariant } from "#ui/ui-theme"; import { fixedInt } from "#utils/common"; diff --git a/src/ui/handlers/game-stats-ui-handler.ts b/src/ui/handlers/game-stats-ui-handler.ts index 9ffb7346b4d..24ff842a902 100644 --- a/src/ui/handlers/game-stats-ui-handler.ts +++ b/src/ui/handlers/game-stats-ui-handler.ts @@ -7,8 +7,8 @@ import { PlayerGender } from "#enums/player-gender"; import { TextStyle } from "#enums/text-style"; import { UiTheme } from "#enums/ui-theme"; import type { GameData } from "#system/game-data"; -import { UiHandler } from "#ui/handlers/ui-handler"; import { addTextObject } from "#ui/text"; +import { UiHandler } from "#ui/ui-handler"; import { addWindow } from "#ui/ui-theme"; import { formatFancyLargeNumber, getPlayTimeString } from "#utils/common"; import { toTitleCase } from "#utils/strings"; @@ -108,7 +108,7 @@ const displayStats: DisplayStats = { sourceFunc: gameData => gameData.gameStats.highestDamage.toString(), }, highestHeal: { - label_key: "highestHPHealed", + label_key: "highestHpHealed", sourceFunc: gameData => gameData.gameStats.highestHeal.toString(), }, pokemonSeen: { diff --git a/src/ui/handlers/loading-modal-ui-handler.ts b/src/ui/handlers/loading-modal-ui-handler.ts index 9b401e17f91..de00d911c47 100644 --- a/src/ui/handlers/loading-modal-ui-handler.ts +++ b/src/ui/handlers/loading-modal-ui-handler.ts @@ -1,6 +1,6 @@ import { TextStyle } from "#enums/text-style"; import type { UiMode } from "#enums/ui-mode"; -import { ModalUiHandler } from "#ui/handlers/modal-ui-handler"; +import { ModalUiHandler } from "#ui/modal-ui-handler"; import { addTextObject } from "#ui/text"; import i18next from "i18next"; diff --git a/src/ui/handlers/login-form-ui-handler.ts b/src/ui/handlers/login-form-ui-handler.ts index 0634ae36ba8..44c5b93131f 100644 --- a/src/ui/handlers/login-form-ui-handler.ts +++ b/src/ui/handlers/login-form-ui-handler.ts @@ -3,10 +3,10 @@ import { globalScene } from "#app/global-scene"; import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; import { languageOptions } from "#system/settings-language"; -import type { OptionSelectItem } from "#ui/handlers/abstract-option-select-ui-handler"; -import type { InputFieldConfig } from "#ui/handlers/form-modal-ui-handler"; -import { FormModalUiHandler } from "#ui/handlers/form-modal-ui-handler"; -import type { ModalConfig } from "#ui/handlers/modal-ui-handler"; +import type { OptionSelectItem } from "#ui/abstract-option-select-ui-handler"; +import type { InputFieldConfig } from "#ui/form-modal-ui-handler"; +import { FormModalUiHandler } from "#ui/form-modal-ui-handler"; +import type { ModalConfig } from "#ui/modal-ui-handler"; import { addTextObject } from "#ui/text"; import { addWindow } from "#ui/ui-theme"; import { fixedInt } from "#utils/common"; diff --git a/src/ui/handlers/menu-ui-handler.ts b/src/ui/handlers/menu-ui-handler.ts index df1908bae39..419f2489818 100644 --- a/src/ui/handlers/menu-ui-handler.ts +++ b/src/ui/handlers/menu-ui-handler.ts @@ -7,10 +7,10 @@ import { Button } from "#enums/buttons"; import { GameDataType } from "#enums/game-data-type"; import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; -import { BgmBar } from "#ui/containers/bgm-bar"; -import type { OptionSelectConfig, OptionSelectItem } from "#ui/handlers/abstract-option-select-ui-handler"; -import type { AwaitableUiHandler } from "#ui/handlers/awaitable-ui-handler"; -import { MessageUiHandler } from "#ui/handlers/message-ui-handler"; +import type { OptionSelectConfig, OptionSelectItem } from "#ui/abstract-option-select-ui-handler"; +import type { AwaitableUiHandler } from "#ui/awaitable-ui-handler"; +import { BgmBar } from "#ui/bgm-bar"; +import { MessageUiHandler } from "#ui/message-ui-handler"; import { addTextObject, getTextStyleOptions } from "#ui/text"; import { addWindow, WindowVariant } from "#ui/ui-theme"; import { fixedInt, isLocal, sessionIdKey } from "#utils/common"; diff --git a/src/ui/handlers/message-ui-handler.ts b/src/ui/handlers/message-ui-handler.ts index b8e3f983cca..1deaca78493 100644 --- a/src/ui/handlers/message-ui-handler.ts +++ b/src/ui/handlers/message-ui-handler.ts @@ -1,6 +1,6 @@ import { globalScene } from "#app/global-scene"; import type { UiMode } from "#enums/ui-mode"; -import { AwaitableUiHandler } from "#ui/handlers/awaitable-ui-handler"; +import { AwaitableUiHandler } from "#ui/awaitable-ui-handler"; import { getFrameMs } from "#utils/common"; export abstract class MessageUiHandler extends AwaitableUiHandler { diff --git a/src/ui/handlers/modal-ui-handler.ts b/src/ui/handlers/modal-ui-handler.ts index e6eecece9a2..c145363b244 100644 --- a/src/ui/handlers/modal-ui-handler.ts +++ b/src/ui/handlers/modal-ui-handler.ts @@ -2,8 +2,8 @@ import { globalScene } from "#app/global-scene"; import type { Button } from "#enums/buttons"; import { TextStyle } from "#enums/text-style"; import type { UiMode } from "#enums/ui-mode"; -import { UiHandler } from "#ui/handlers/ui-handler"; import { addTextObject } from "#ui/text"; +import { UiHandler } from "#ui/ui-handler"; import { addWindow, WindowVariant } from "#ui/ui-theme"; export interface ModalConfig { diff --git a/src/ui/handlers/modifier-select-ui-handler.ts b/src/ui/handlers/modifier-select-ui-handler.ts index a721bf9e7db..95bc30fb97c 100644 --- a/src/ui/handlers/modifier-select-ui-handler.ts +++ b/src/ui/handlers/modifier-select-ui-handler.ts @@ -11,8 +11,8 @@ import { UiMode } from "#enums/ui-mode"; import { HealShopCostModifier, LockModifierTiersModifier, PokemonHeldItemModifier } from "#modifiers/modifier"; import type { ModifierTypeOption } from "#modifiers/modifier-type"; import { getPlayerShopModifierTypeOptionsForWave, TmModifierType } from "#modifiers/modifier-type"; -import { MoveInfoOverlay } from "#ui/containers/move-info-overlay"; -import { AwaitableUiHandler } from "#ui/handlers/awaitable-ui-handler"; +import { AwaitableUiHandler } from "#ui/awaitable-ui-handler"; +import { MoveInfoOverlay } from "#ui/move-info-overlay"; import { addTextObject, getModifierTierTextTint, getTextColor, getTextStyleOptions } from "#ui/text"; import { formatMoney, NumberHolder } from "#utils/common"; import i18next from "i18next"; diff --git a/src/ui/handlers/mystery-encounter-ui-handler.ts b/src/ui/handlers/mystery-encounter-ui-handler.ts index 9bc6f0681ee..e4c9dfbfee3 100644 --- a/src/ui/handlers/mystery-encounter-ui-handler.ts +++ b/src/ui/handlers/mystery-encounter-ui-handler.ts @@ -9,11 +9,11 @@ import { getEncounterText } from "#mystery-encounters/encounter-dialogue-utils"; import type { OptionSelectSettings } from "#mystery-encounters/encounter-phase-utils"; import type { MysteryEncounterOption } from "#mystery-encounters/mystery-encounter-option"; import type { MysteryEncounterPhase } from "#phases/mystery-encounter-phases"; -import { PartyUiMode } from "#ui/handlers/party-ui-handler"; -import { UiHandler } from "#ui/handlers/ui-handler"; +import { PartyUiMode } from "#ui/party-ui-handler"; import { addBBCodeTextObject, getBBCodeFrag } from "#ui/text"; +import { UiHandler } from "#ui/ui-handler"; import { addWindow, WindowVariant } from "#ui/ui-theme"; -import { fixedInt, isNullOrUndefined } from "#utils/common"; +import { fixedInt } from "#utils/common"; import i18next from "i18next"; import type BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; @@ -95,12 +95,10 @@ export class MysteryEncounterUiHandler extends UiHandler { super.show(args); this.overrideSettings = (args[0] as OptionSelectSettings) ?? {}; - const showDescriptionContainer = isNullOrUndefined(this.overrideSettings?.hideDescription) - ? true - : !this.overrideSettings.hideDescription; - const slideInDescription = isNullOrUndefined(this.overrideSettings?.slideInDescription) - ? true - : this.overrideSettings.slideInDescription; + const showDescriptionContainer = + this.overrideSettings?.hideDescription == null ? true : !this.overrideSettings.hideDescription; + const slideInDescription = + this.overrideSettings?.slideInDescription == null ? true : this.overrideSettings.slideInDescription; const startingCursorIndex = this.overrideSettings?.startingCursorIndex ?? 0; this.cursorContainer.setVisible(true); @@ -567,7 +565,7 @@ export class MysteryEncounterUiHandler extends UiHandler { } this.tooltipContainer.setVisible(true); - if (isNullOrUndefined(cursor) || cursor > this.optionsContainer.length - 2) { + if (cursor == null || cursor > this.optionsContainer.length - 2) { // Ignore hovers on view party button // Hide dex progress if visible this.showHideDexProgress(false); diff --git a/src/ui/handlers/party-ui-handler.ts b/src/ui/handlers/party-ui-handler.ts index cc2bb093b07..d4014cc0288 100644 --- a/src/ui/handlers/party-ui-handler.ts +++ b/src/ui/handlers/party-ui-handler.ts @@ -21,11 +21,11 @@ import type { PokemonMove } from "#moves/pokemon-move"; import type { CommandPhase } from "#phases/command-phase"; import { getVariantTint } from "#sprites/variant"; import type { TurnMove } from "#types/turn-move"; -import { MoveInfoOverlay } from "#ui/containers/move-info-overlay"; -import { MessageUiHandler } from "#ui/handlers/message-ui-handler"; +import { MessageUiHandler } from "#ui/message-ui-handler"; +import { MoveInfoOverlay } from "#ui/move-info-overlay"; +import { PokemonIconAnimHelper, PokemonIconAnimMode } from "#ui/pokemon-icon-anim-helper"; import { addBBCodeTextObject, addTextObject, getTextColor } from "#ui/text"; import { addWindow } from "#ui/ui-theme"; -import { PokemonIconAnimHelper, PokemonIconAnimMode } from "#ui/utils/pokemon-icon-anim-helper"; import { applyChallenges } from "#utils/challenge-utils"; import { BooleanHolder, getLocalizedSpriteKey, randInt } from "#utils/common"; import { toCamelCase, toTitleCase } from "#utils/strings"; diff --git a/src/ui/handlers/pokedex-page-ui-handler.ts b/src/ui/handlers/pokedex-page-ui-handler.ts index 659c3488bc1..31e2998b850 100644 --- a/src/ui/handlers/pokedex-page-ui-handler.ts +++ b/src/ui/handlers/pokedex-page-ui-handler.ts @@ -46,15 +46,15 @@ import { getVariantIcon, getVariantTint } from "#sprites/variant"; import { SettingKeyboard } from "#system/settings-keyboard"; import type { DexEntry } from "#types/dex-data"; import type { StarterAttributes } from "#types/save-data"; -import { BaseStatsOverlay } from "#ui/containers/base-stats-overlay"; -import { MoveInfoOverlay } from "#ui/containers/move-info-overlay"; -import { PokedexInfoOverlay } from "#ui/containers/pokedex-info-overlay"; -import { StatsContainer } from "#ui/containers/stats-container"; -import type { OptionSelectItem } from "#ui/handlers/abstract-option-select-ui-handler"; -import { MessageUiHandler } from "#ui/handlers/message-ui-handler"; +import type { OptionSelectItem } from "#ui/abstract-option-select-ui-handler"; +import { BaseStatsOverlay } from "#ui/base-stats-overlay"; +import { MessageUiHandler } from "#ui/message-ui-handler"; +import { MoveInfoOverlay } from "#ui/move-info-overlay"; +import { PokedexInfoOverlay } from "#ui/pokedex-info-overlay"; +import { StatsContainer } from "#ui/stats-container"; import { addBBCodeTextObject, addTextObject, getTextColor, getTextStyleOptions } from "#ui/text"; import { addWindow } from "#ui/ui-theme"; -import { BooleanHolder, getLocalizedSpriteKey, isNullOrUndefined, padInt, rgbHexToRgba } from "#utils/common"; +import { BooleanHolder, getLocalizedSpriteKey, padInt, rgbHexToRgba } from "#utils/common"; import { getEnumValues } from "#utils/enums"; import { getPokemonSpecies, getPokemonSpeciesForm } from "#utils/pokemon-utils"; import { toCamelCase, toTitleCase } from "#utils/strings"; @@ -2424,11 +2424,7 @@ export class PokedexPageUiHandler extends MessageUiHandler { // We will only update the sprite if there is a change to form, shiny/variant // or gender for species with gender sprite differences const shouldUpdateSprite = - (species?.genderDiffs && !isNullOrUndefined(female)) - || !isNullOrUndefined(formIndex) - || !isNullOrUndefined(shiny) - || !isNullOrUndefined(variant) - || forceUpdate; + (species?.genderDiffs && female != null) || formIndex != null || shiny != null || variant != null || forceUpdate; if (this.activeTooltip === "CANDY") { if (this.species && this.pokemonCandyContainer.visible) { diff --git a/src/ui/handlers/pokedex-scan-ui-handler.ts b/src/ui/handlers/pokedex-scan-ui-handler.ts index bb3cec5bb56..18afd0598c2 100644 --- a/src/ui/handlers/pokedex-scan-ui-handler.ts +++ b/src/ui/handlers/pokedex-scan-ui-handler.ts @@ -1,12 +1,11 @@ import { allAbilities, allMoves, allSpecies } from "#data/data-lists"; import { UiMode } from "#enums/ui-mode"; import type { PlayerPokemon } from "#field/pokemon"; -import { FilterTextRow } from "#ui/containers/filter-text"; -import type { OptionSelectItem } from "#ui/handlers/abstract-option-select-ui-handler"; -import type { InputFieldConfig } from "#ui/handlers/form-modal-ui-handler"; -import { FormModalUiHandler } from "#ui/handlers/form-modal-ui-handler"; -import type { ModalConfig } from "#ui/handlers/modal-ui-handler"; -import { isNullOrUndefined } from "#utils/common"; +import type { OptionSelectItem } from "#ui/abstract-option-select-ui-handler"; +import { FilterTextRow } from "#ui/filter-text"; +import type { InputFieldConfig } from "#ui/form-modal-ui-handler"; +import { FormModalUiHandler } from "#ui/form-modal-ui-handler"; +import type { ModalConfig } from "#ui/modal-ui-handler"; import i18next from "i18next"; export class PokedexScanUiHandler extends FormModalUiHandler { @@ -132,7 +131,7 @@ export class PokedexScanUiHandler extends FormModalUiHandler { return { label: value, handler: () => { - if (!isNullOrUndefined(evt.data) || evt.inputType?.toLowerCase() === "deletecontentbackward") { + if (evt.data != null || evt.inputType?.toLowerCase() === "deletecontentbackward") { inputObject.setText(value); } ui.revertMode(); diff --git a/src/ui/handlers/pokedex-ui-handler.ts b/src/ui/handlers/pokedex-ui-handler.ts index 3e37ccba563..c6f9dbee448 100644 --- a/src/ui/handlers/pokedex-ui-handler.ts +++ b/src/ui/handlers/pokedex-ui-handler.ts @@ -34,23 +34,16 @@ import { getVariantIcon, getVariantTint } from "#sprites/variant"; import { SettingKeyboard } from "#system/settings-keyboard"; import type { DexEntry } from "#types/dex-data"; import type { DexAttrProps, StarterAttributes } from "#types/save-data"; -import { - DropDown, - DropDownLabel, - DropDownOption, - DropDownState, - DropDownType, - SortCriteria, -} from "#ui/containers/dropdown"; -import { FilterBar } from "#ui/containers/filter-bar"; -import { FilterText, FilterTextRow } from "#ui/containers/filter-text"; -import { PokedexMonContainer } from "#ui/containers/pokedex-mon-container"; -import { ScrollBar } from "#ui/containers/scroll-bar"; -import type { OptionSelectConfig } from "#ui/handlers/abstract-option-select-ui-handler"; -import { MessageUiHandler } from "#ui/handlers/message-ui-handler"; +import type { OptionSelectConfig } from "#ui/abstract-option-select-ui-handler"; +import { DropDown, DropDownLabel, DropDownOption, DropDownState, DropDownType, SortCriteria } from "#ui/dropdown"; +import { FilterBar } from "#ui/filter-bar"; +import { FilterText, FilterTextRow } from "#ui/filter-text"; +import { MessageUiHandler } from "#ui/message-ui-handler"; +import { PokedexMonContainer } from "#ui/pokedex-mon-container"; +import { PokemonIconAnimHelper, PokemonIconAnimMode } from "#ui/pokemon-icon-anim-helper"; +import { ScrollBar } from "#ui/scroll-bar"; import { addTextObject, getTextColor } from "#ui/text"; import { addWindow } from "#ui/ui-theme"; -import { PokemonIconAnimHelper, PokemonIconAnimMode } from "#ui/utils/pokemon-icon-anim-helper"; import { BooleanHolder, fixedInt, getLocalizedSpriteKey, padInt, randIntRange, rgbHexToRgba } from "#utils/common"; import type { StarterPreferences } from "#utils/data"; import { loadStarterPreferences } from "#utils/data"; diff --git a/src/ui/handlers/registration-form-ui-handler.ts b/src/ui/handlers/registration-form-ui-handler.ts index d424e44b455..2c8080d534d 100644 --- a/src/ui/handlers/registration-form-ui-handler.ts +++ b/src/ui/handlers/registration-form-ui-handler.ts @@ -2,9 +2,9 @@ import { pokerogueApi } from "#api/pokerogue-api"; import { globalScene } from "#app/global-scene"; import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; -import type { InputFieldConfig } from "#ui/handlers/form-modal-ui-handler"; -import { FormModalUiHandler } from "#ui/handlers/form-modal-ui-handler"; -import type { ModalConfig } from "#ui/handlers/modal-ui-handler"; +import type { InputFieldConfig } from "#ui/form-modal-ui-handler"; +import { FormModalUiHandler } from "#ui/form-modal-ui-handler"; +import type { ModalConfig } from "#ui/modal-ui-handler"; import { addTextObject } from "#ui/text"; import i18next from "i18next"; diff --git a/src/ui/handlers/rename-form-ui-handler.ts b/src/ui/handlers/rename-form-ui-handler.ts index f1d9ae3c981..9da5b0e8554 100644 --- a/src/ui/handlers/rename-form-ui-handler.ts +++ b/src/ui/handlers/rename-form-ui-handler.ts @@ -1,7 +1,7 @@ import type { PlayerPokemon } from "#field/pokemon"; -import type { InputFieldConfig } from "#ui/handlers/form-modal-ui-handler"; -import { FormModalUiHandler } from "#ui/handlers/form-modal-ui-handler"; -import type { ModalConfig } from "#ui/handlers/modal-ui-handler"; +import type { InputFieldConfig } from "#ui/form-modal-ui-handler"; +import { FormModalUiHandler } from "#ui/form-modal-ui-handler"; +import type { ModalConfig } from "#ui/modal-ui-handler"; import i18next from "i18next"; export class RenameFormUiHandler extends FormModalUiHandler { diff --git a/src/ui/handlers/run-history-ui-handler.ts b/src/ui/handlers/run-history-ui-handler.ts index baf1577a2b4..4dd73d4826b 100644 --- a/src/ui/handlers/run-history-ui-handler.ts +++ b/src/ui/handlers/run-history-ui-handler.ts @@ -8,8 +8,8 @@ import { TrainerVariant } from "#enums/trainer-variant"; import { UiMode } from "#enums/ui-mode"; import type { PokemonData } from "#system/pokemon-data"; import type { RunEntry } from "#types/save-data"; -import { MessageUiHandler } from "#ui/handlers/message-ui-handler"; -import { RunDisplayMode } from "#ui/handlers/run-info-ui-handler"; +import { MessageUiHandler } from "#ui/message-ui-handler"; +import { RunDisplayMode } from "#ui/run-info-ui-handler"; import { addTextObject } from "#ui/text"; import { addWindow } from "#ui/ui-theme"; import { fixedInt, formatLargeNumber } from "#utils/common"; diff --git a/src/ui/handlers/run-info-ui-handler.ts b/src/ui/handlers/run-info-ui-handler.ts index 93ace112148..556884194b1 100644 --- a/src/ui/handlers/run-info-ui-handler.ts +++ b/src/ui/handlers/run-info-ui-handler.ts @@ -22,8 +22,8 @@ import { getVariantTint } from "#sprites/variant"; import type { PokemonData } from "#system/pokemon-data"; import { SettingKeyboard } from "#system/settings-keyboard"; import type { SessionSaveData } from "#types/save-data"; -import { UiHandler } from "#ui/handlers/ui-handler"; import { addBBCodeTextObject, addTextObject, getTextColor } from "#ui/text"; +import { UiHandler } from "#ui/ui-handler"; import { addWindow } from "#ui/ui-theme"; import { formatFancyLargeNumber, formatLargeNumber, formatMoney, getPlayTimeString } from "#utils/common"; import { toCamelCase } from "#utils/strings"; diff --git a/src/ui/handlers/save-slot-select-ui-handler.ts b/src/ui/handlers/save-slot-select-ui-handler.ts index 5115ab23578..194971a005f 100644 --- a/src/ui/handlers/save-slot-select-ui-handler.ts +++ b/src/ui/handlers/save-slot-select-ui-handler.ts @@ -8,12 +8,12 @@ import { UiMode } from "#enums/ui-mode"; import * as Modifier from "#modifiers/modifier"; import type { PokemonData } from "#system/pokemon-data"; import type { SessionSaveData } from "#types/save-data"; -import type { OptionSelectConfig } from "#ui/handlers/abstract-option-select-ui-handler"; -import { MessageUiHandler } from "#ui/handlers/message-ui-handler"; -import { RunDisplayMode } from "#ui/handlers/run-info-ui-handler"; +import type { OptionSelectConfig } from "#ui/abstract-option-select-ui-handler"; +import { MessageUiHandler } from "#ui/message-ui-handler"; +import { RunDisplayMode } from "#ui/run-info-ui-handler"; import { addTextObject } from "#ui/text"; import { addWindow } from "#ui/ui-theme"; -import { fixedInt, formatLargeNumber, getPlayTimeString, isNullOrUndefined } from "#utils/common"; +import { fixedInt, formatLargeNumber, getPlayTimeString } from "#utils/common"; import i18next from "i18next"; const SESSION_SLOTS_COUNT = 5; @@ -405,7 +405,7 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler { } this.setArrowVisibility(hasData); } - if (!isNullOrUndefined(prevSlotIndex)) { + if (prevSlotIndex != null) { this.revertSessionSlot(prevSlotIndex); } diff --git a/src/ui/handlers/session-reload-modal-ui-handler.ts b/src/ui/handlers/session-reload-modal-ui-handler.ts index 33c18b1974a..1f5a205f990 100644 --- a/src/ui/handlers/session-reload-modal-ui-handler.ts +++ b/src/ui/handlers/session-reload-modal-ui-handler.ts @@ -1,7 +1,7 @@ import { TextStyle } from "#enums/text-style"; import type { UiMode } from "#enums/ui-mode"; -import type { ModalConfig } from "#ui/handlers/modal-ui-handler"; -import { ModalUiHandler } from "#ui/handlers/modal-ui-handler"; +import type { ModalConfig } from "#ui/modal-ui-handler"; +import { ModalUiHandler } from "#ui/modal-ui-handler"; import { addTextObject } from "#ui/text"; export class SessionReloadModalUiHandler extends ModalUiHandler { diff --git a/src/ui/handlers/starter-select-ui-handler.ts b/src/ui/handlers/starter-select-ui-handler.ts index 7c67ed13d4b..53e566864b1 100644 --- a/src/ui/handlers/starter-select-ui-handler.ts +++ b/src/ui/handlers/starter-select-ui-handler.ts @@ -50,30 +50,22 @@ import { RibbonData } from "#system/ribbons/ribbon-data"; import { SettingKeyboard } from "#system/settings-keyboard"; import type { DexEntry } from "#types/dex-data"; import type { DexAttrProps, StarterAttributes, StarterDataEntry, StarterMoveset } from "#types/save-data"; -import { - DropDown, - DropDownLabel, - DropDownOption, - DropDownState, - DropDownType, - SortCriteria, -} from "#ui/containers/dropdown"; -import { FilterBar } from "#ui/containers/filter-bar"; -import { MoveInfoOverlay } from "#ui/containers/move-info-overlay"; -import { ScrollBar } from "#ui/containers/scroll-bar"; -import { StarterContainer } from "#ui/containers/starter-container"; -import { StatsContainer } from "#ui/containers/stats-container"; -import type { OptionSelectItem } from "#ui/handlers/abstract-option-select-ui-handler"; -import { MessageUiHandler } from "#ui/handlers/message-ui-handler"; +import type { OptionSelectItem } from "#ui/abstract-option-select-ui-handler"; +import { DropDown, DropDownLabel, DropDownOption, DropDownState, DropDownType, SortCriteria } from "#ui/dropdown"; +import { FilterBar } from "#ui/filter-bar"; +import { MessageUiHandler } from "#ui/message-ui-handler"; +import { MoveInfoOverlay } from "#ui/move-info-overlay"; +import { PokemonIconAnimHelper, PokemonIconAnimMode } from "#ui/pokemon-icon-anim-helper"; +import { ScrollBar } from "#ui/scroll-bar"; +import { StarterContainer } from "#ui/starter-container"; +import { StatsContainer } from "#ui/stats-container"; import { addBBCodeTextObject, addTextObject, getTextColor } from "#ui/text"; import { addWindow } from "#ui/ui-theme"; -import { PokemonIconAnimHelper, PokemonIconAnimMode } from "#ui/utils/pokemon-icon-anim-helper"; import { applyChallenges, checkStarterValidForChallenge } from "#utils/challenge-utils"; import { BooleanHolder, fixedInt, getLocalizedSpriteKey, - isNullOrUndefined, NumberHolder, padInt, randIntRange, @@ -2555,7 +2547,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { case Button.CYCLE_TERA: if (this.canCycleTera) { const speciesForm = getPokemonSpeciesForm(this.lastSpecies.speciesId, starterAttributes.form ?? 0); - if (speciesForm.type1 === this.teraCursor && !isNullOrUndefined(speciesForm.type2)) { + if (speciesForm.type1 === this.teraCursor && speciesForm.type2 != null) { starterAttributes.tera = speciesForm.type2; originalStarterAttributes.tera = starterAttributes.tera; this.setSpeciesDetails(this.lastSpecies, { @@ -2797,7 +2789,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { */ switchMoveHandler(targetIndex: number, newMove: MoveId, previousMove: MoveId) { const starterMoveset = this.starterMoveset; - if (isNullOrUndefined(starterMoveset)) { + if (starterMoveset == null) { console.warn("Trying to update a non-existing moveset"); return; } @@ -3694,7 +3686,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { ); } - if (!isNullOrUndefined(props.formIndex)) { + if (props.formIndex != null) { // If switching forms while the pokemon is in the team, update its moveset this.updateSelectedStarterMoveset(species.speciesId); } @@ -3816,10 +3808,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { // We will only update the sprite if there is a change to form, shiny/variant // or gender for species with gender sprite differences const shouldUpdateSprite = - (species?.genderDiffs && !isNullOrUndefined(female)) - || !isNullOrUndefined(formIndex) - || !isNullOrUndefined(shiny) - || !isNullOrUndefined(variant); + (species?.genderDiffs && female != null) || formIndex != null || shiny != null || variant != null; const isFreshStartChallenge = globalScene.gameMode.hasChallenge(Challenges.FRESH_START); @@ -3857,7 +3846,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { ); // TODO: is this bang correct? this.abilityCursor = abilityIndex !== undefined ? abilityIndex : (abilityIndex = oldAbilityIndex); this.natureCursor = natureIndex !== undefined ? natureIndex : (natureIndex = oldNatureIndex); - this.teraCursor = !isNullOrUndefined(teraType) ? teraType : (teraType = oldTeraType); + this.teraCursor = teraType != null ? teraType : (teraType = oldTeraType); const [isInParty, partyIndex]: [boolean, number] = this.isInParty(species); // we use this to firstly check if the pokemon is in the party, and if so, to get the party index in order to update the icon image if (isInParty) { this.updatePartyIcon(species, partyIndex); @@ -3998,7 +3987,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { this.canCycleTera = !this.statsMode && this.allowTera - && !isNullOrUndefined(getPokemonSpeciesForm(species.speciesId, formIndex ?? 0).type2) + && getPokemonSpeciesForm(species.speciesId, formIndex ?? 0).type2 != null && !globalScene.gameMode.hasChallenge(Challenges.FRESH_START); } @@ -4599,7 +4588,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { this.canCycleTera = !this.statsMode && this.allowTera - && !isNullOrUndefined(getPokemonSpeciesForm(this.lastSpecies.speciesId, formIndex ?? 0).type2) + && getPokemonSpeciesForm(this.lastSpecies.speciesId, formIndex ?? 0).type2 != null && !globalScene.gameMode.hasChallenge(Challenges.FRESH_START); this.updateInstructions(); } diff --git a/src/ui/handlers/summary-ui-handler.ts b/src/ui/handlers/summary-ui-handler.ts index e73c5bae431..c9c8229ebfd 100644 --- a/src/ui/handlers/summary-ui-handler.ts +++ b/src/ui/handlers/summary-ui-handler.ts @@ -25,17 +25,9 @@ import type { PokemonMove } from "#moves/pokemon-move"; import type { Variant } from "#sprites/variant"; import { getVariantTint } from "#sprites/variant"; import { achvs } from "#system/achv"; -import { UiHandler } from "#ui/handlers/ui-handler"; import { addBBCodeTextObject, addTextObject, getBBCodeFrag, getTextColor } from "#ui/text"; -import { - fixedInt, - formatStat, - getLocalizedSpriteKey, - getShinyDescriptor, - isNullOrUndefined, - padInt, - rgbHexToRgba, -} from "#utils/common"; +import { UiHandler } from "#ui/ui-handler"; +import { fixedInt, formatStat, getLocalizedSpriteKey, getShinyDescriptor, padInt, rgbHexToRgba } from "#utils/common"; import { getEnumValues } from "#utils/enums"; import { toCamelCase, toTitleCase } from "#utils/strings"; import { argbFromRgba } from "@material/material-color-utilities"; @@ -895,10 +887,7 @@ export class SummaryUiHandler extends UiHandler { profileContainer.add(luckText); } - if ( - globalScene.gameData.achvUnlocks.hasOwnProperty(achvs.TERASTALLIZE.id) - && !isNullOrUndefined(this.pokemon) - ) { + if (globalScene.gameData.achvUnlocks.hasOwnProperty(achvs.TERASTALLIZE.id) && this.pokemon != null) { const teraIcon = globalScene.add.sprite(123, 26, "button_tera"); teraIcon.setName("terastallize-icon"); teraIcon.setFrame(PokemonType[this.pokemon.getTeraType()].toLowerCase()); diff --git a/src/ui/handlers/target-select-ui-handler.ts b/src/ui/handlers/target-select-ui-handler.ts index 777a6734383..bd81278c661 100644 --- a/src/ui/handlers/target-select-ui-handler.ts +++ b/src/ui/handlers/target-select-ui-handler.ts @@ -7,8 +7,8 @@ import { UiMode } from "#enums/ui-mode"; import type { Pokemon } from "#field/pokemon"; import type { ModifierBar } from "#modifiers/modifier"; import { getMoveTargets } from "#moves/move-utils"; -import { UiHandler } from "#ui/handlers/ui-handler"; -import { fixedInt, isNullOrUndefined } from "#utils/common"; +import { UiHandler } from "#ui/ui-handler"; +import { fixedInt } from "#utils/common"; export type TargetSelectCallback = (targets: BattlerIndex[]) => void; @@ -71,7 +71,7 @@ export class TargetSelectUiHandler extends UiHandler { */ resetCursor(cursorN: number, user: Pokemon): void { if ( - !isNullOrUndefined(cursorN) + cursorN != null && ([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2].includes(cursorN) || user.tempSummonData.waveTurnCount === 1) ) { // Reset cursor on the first turn of a fight or if an ally was targeted last turn @@ -90,13 +90,10 @@ export class TargetSelectUiHandler extends UiHandler { this.targetSelectCallback(button === Button.ACTION ? targetIndexes : []); success = true; if (this.fieldIndex === BattlerIndex.PLAYER) { - if (isNullOrUndefined(this.cursor0) || this.cursor0 !== this.cursor) { + if (this.cursor0 == null || this.cursor0 !== this.cursor) { this.cursor0 = this.cursor; } - } else if ( - this.fieldIndex === BattlerIndex.PLAYER_2 - && (isNullOrUndefined(this.cursor1) || this.cursor1 !== this.cursor) - ) { + } else if (this.fieldIndex === BattlerIndex.PLAYER_2 && (this.cursor1 == null || this.cursor1 !== this.cursor)) { this.cursor1 = this.cursor; } } else if (this.isMultipleTargets) { diff --git a/src/ui/handlers/test-dialogue-ui-handler.ts b/src/ui/handlers/test-dialogue-ui-handler.ts index d72de64ef70..b33e6726547 100644 --- a/src/ui/handlers/test-dialogue-ui-handler.ts +++ b/src/ui/handlers/test-dialogue-ui-handler.ts @@ -1,10 +1,9 @@ import { UiMode } from "#enums/ui-mode"; import type { PlayerPokemon } from "#field/pokemon"; -import type { OptionSelectItem } from "#ui/handlers/abstract-option-select-ui-handler"; -import type { InputFieldConfig } from "#ui/handlers/form-modal-ui-handler"; -import { FormModalUiHandler } from "#ui/handlers/form-modal-ui-handler"; -import type { ModalConfig } from "#ui/handlers/modal-ui-handler"; -import { isNullOrUndefined } from "#utils/common"; +import type { OptionSelectItem } from "#ui/abstract-option-select-ui-handler"; +import type { InputFieldConfig } from "#ui/form-modal-ui-handler"; +import { FormModalUiHandler } from "#ui/form-modal-ui-handler"; +import type { ModalConfig } from "#ui/modal-ui-handler"; import i18next from "i18next"; export class TestDialogueUiHandler extends FormModalUiHandler { @@ -18,7 +17,7 @@ export class TestDialogueUiHandler extends FormModalUiHandler { .map((t, i) => { const value = Object.values(object)[i]; - if (typeof value === "object" && !isNullOrUndefined(value)) { + if (typeof value === "object" && value != null) { // we check for not null or undefined here because if the language json file has a null key, the typeof will still be an object, but that object will be null, causing issues // If the value is an object, execute the same process // si el valor es un objeto ejecuta el mismo proceso @@ -27,7 +26,7 @@ export class TestDialogueUiHandler extends FormModalUiHandler { t => t.length > 0, ); } - if (typeof value === "string" || isNullOrUndefined(value)) { + if (typeof value === "string" || value == null) { // we check for null or undefined here as per above - the typeof is still an object but the value is null so we need to exit out of this and pass the null key // Return in the format expected by i18next @@ -109,7 +108,7 @@ export class TestDialogueUiHandler extends FormModalUiHandler { handler: () => { // this is here to make sure that if you try to backspace then enter, the last known evt.data (backspace) is picked up // this is because evt.data is null for backspace, so without this, the autocomplete windows just closes - if (!isNullOrUndefined(evt.data) || evt.inputType?.toLowerCase() === "deletecontentbackward") { + if (evt.data != null || evt.inputType?.toLowerCase() === "deletecontentbackward") { const separatedArray = inputObject.text.split(" "); separatedArray[separatedArray.length - 1] = value; inputObject.setText(separatedArray.join(" ")); diff --git a/src/ui/handlers/unavailable-modal-ui-handler.ts b/src/ui/handlers/unavailable-modal-ui-handler.ts index 5b885bfff77..7ba77dcac23 100644 --- a/src/ui/handlers/unavailable-modal-ui-handler.ts +++ b/src/ui/handlers/unavailable-modal-ui-handler.ts @@ -2,8 +2,8 @@ import { updateUserInfo } from "#app/account"; import { globalScene } from "#app/global-scene"; import { TextStyle } from "#enums/text-style"; import type { UiMode } from "#enums/ui-mode"; -import type { ModalConfig } from "#ui/handlers/modal-ui-handler"; -import { ModalUiHandler } from "#ui/handlers/modal-ui-handler"; +import type { ModalConfig } from "#ui/modal-ui-handler"; +import { ModalUiHandler } from "#ui/modal-ui-handler"; import { addTextObject } from "#ui/text"; import { sessionIdKey } from "#utils/common"; import { removeCookie } from "#utils/cookies"; diff --git a/src/ui/settings/abstract-control-settings-ui-handler.ts b/src/ui/settings/abstract-control-settings-ui-handler.ts index 2d9f3e6a6bd..17812785d1e 100644 --- a/src/ui/settings/abstract-control-settings-ui-handler.ts +++ b/src/ui/settings/abstract-control-settings-ui-handler.ts @@ -5,10 +5,10 @@ import type { Device } from "#enums/devices"; import { TextStyle } from "#enums/text-style"; import type { UiMode } from "#enums/ui-mode"; import { getIconWithSettingName } from "#inputs/config-handler"; -import { ScrollBar } from "#ui/containers/scroll-bar"; -import { UiHandler } from "#ui/handlers/ui-handler"; import { NavigationManager, NavigationMenu } from "#ui/navigation-menu"; +import { ScrollBar } from "#ui/scroll-bar"; import { addTextObject, getTextColor } from "#ui/text"; +import { UiHandler } from "#ui/ui-handler"; import { addWindow } from "#ui/ui-theme"; import { toCamelCase } from "#utils/strings"; import i18next from "i18next"; diff --git a/src/ui/settings/abstract-settings-ui-handler.ts b/src/ui/settings/abstract-settings-ui-handler.ts index 876c18eb697..e22c28116f5 100644 --- a/src/ui/settings/abstract-settings-ui-handler.ts +++ b/src/ui/settings/abstract-settings-ui-handler.ts @@ -6,9 +6,9 @@ import type { SettingType } from "#system/settings"; import { Setting, SettingKeys } from "#system/settings"; import type { AnyFn } from "#types/type-helpers"; import type { InputsIcons } from "#ui/abstract-control-settings-ui-handler"; -import { ScrollBar } from "#ui/containers/scroll-bar"; -import { MessageUiHandler } from "#ui/handlers/message-ui-handler"; +import { MessageUiHandler } from "#ui/message-ui-handler"; import { NavigationManager, NavigationMenu } from "#ui/navigation-menu"; +import { ScrollBar } from "#ui/scroll-bar"; import { addTextObject, getTextColor } from "#ui/text"; import { addWindow } from "#ui/ui-theme"; import i18next from "i18next"; diff --git a/src/ui/settings/gamepad-binding-ui-handler.ts b/src/ui/settings/gamepad-binding-ui-handler.ts index 4fdc4abdbfe..93923aeb57d 100644 --- a/src/ui/settings/gamepad-binding-ui-handler.ts +++ b/src/ui/settings/gamepad-binding-ui-handler.ts @@ -3,7 +3,7 @@ import { Device } from "#enums/devices"; import { TextStyle } from "#enums/text-style"; import type { UiMode } from "#enums/ui-mode"; import { getIconWithSettingName, getKeyWithKeycode } from "#inputs/config-handler"; -import { AbstractBindingUiHandler } from "#ui/handlers/abstract-binding-ui-handler"; +import { AbstractBindingUiHandler } from "#ui/abstract-binding-ui-handler"; import { addTextObject } from "#ui/text"; import i18next from "i18next"; diff --git a/src/ui/settings/keyboard-binding-ui-handler.ts b/src/ui/settings/keyboard-binding-ui-handler.ts index b1fd153461f..b339ac16188 100644 --- a/src/ui/settings/keyboard-binding-ui-handler.ts +++ b/src/ui/settings/keyboard-binding-ui-handler.ts @@ -3,7 +3,7 @@ import { Device } from "#enums/devices"; import { TextStyle } from "#enums/text-style"; import type { UiMode } from "#enums/ui-mode"; import { getKeyWithKeycode } from "#inputs/config-handler"; -import { AbstractBindingUiHandler } from "#ui/handlers/abstract-binding-ui-handler"; +import { AbstractBindingUiHandler } from "#ui/abstract-binding-ui-handler"; import { addTextObject } from "#ui/text"; import i18next from "i18next"; diff --git a/src/ui/settings/option-select-ui-handler.ts b/src/ui/settings/option-select-ui-handler.ts index 235f16e7f09..c989c768244 100644 --- a/src/ui/settings/option-select-ui-handler.ts +++ b/src/ui/settings/option-select-ui-handler.ts @@ -1,5 +1,5 @@ import { UiMode } from "#enums/ui-mode"; -import { AbstractOptionSelectUiHandler } from "#ui/handlers/abstract-option-select-ui-handler"; +import { AbstractOptionSelectUiHandler } from "#ui/abstract-option-select-ui-handler"; export class OptionSelectUiHandler extends AbstractOptionSelectUiHandler { constructor(mode: UiMode = UiMode.OPTION_SELECT) { diff --git a/src/ui/ui.ts b/src/ui/ui.ts index c7d33f66605..76b07d7bfa5 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -4,59 +4,59 @@ import { Device } from "#enums/devices"; import { PlayerGender } from "#enums/player-gender"; import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; -import { AchvBar } from "#ui/containers/achv-bar"; -import type { BgmBar } from "#ui/containers/bgm-bar"; -import { SavingIconContainer } from "#ui/containers/saving-icon-handler"; +import { AchvBar } from "#ui/achv-bar"; +import { AchvsUiHandler } from "#ui/achvs-ui-handler"; +import { AutoCompleteUiHandler } from "#ui/autocomplete-ui-handler"; +import { AwaitableUiHandler } from "#ui/awaitable-ui-handler"; +import { BallUiHandler } from "#ui/ball-ui-handler"; +import { BattleMessageUiHandler } from "#ui/battle-message-ui-handler"; +import type { BgmBar } from "#ui/bgm-bar"; +import { GameChallengesUiHandler } from "#ui/challenges-select-ui-handler"; +import { ChangePasswordFormUiHandler } from "#ui/change-password-form-ui-handler"; +import { CommandUiHandler } from "#ui/command-ui-handler"; +import { ConfirmUiHandler } from "#ui/confirm-ui-handler"; +import { EggGachaUiHandler } from "#ui/egg-gacha-ui-handler"; +import { EggHatchSceneUiHandler } from "#ui/egg-hatch-scene-ui-handler"; +import { EggListUiHandler } from "#ui/egg-list-ui-handler"; +import { EggSummaryUiHandler } from "#ui/egg-summary-ui-handler"; +import { EvolutionSceneUiHandler } from "#ui/evolution-scene-ui-handler"; +import { FightUiHandler } from "#ui/fight-ui-handler"; +import { GameStatsUiHandler } from "#ui/game-stats-ui-handler"; import { GamepadBindingUiHandler } from "#ui/gamepad-binding-ui-handler"; -import { AchvsUiHandler } from "#ui/handlers/achvs-ui-handler"; -import { AutoCompleteUiHandler } from "#ui/handlers/autocomplete-ui-handler"; -import { AwaitableUiHandler } from "#ui/handlers/awaitable-ui-handler"; -import { BallUiHandler } from "#ui/handlers/ball-ui-handler"; -import { BattleMessageUiHandler } from "#ui/handlers/battle-message-ui-handler"; -import { GameChallengesUiHandler } from "#ui/handlers/challenges-select-ui-handler"; -import { ChangePasswordFormUiHandler } from "#ui/handlers/change-password-form-ui-handler"; -import { CommandUiHandler } from "#ui/handlers/command-ui-handler"; -import { ConfirmUiHandler } from "#ui/handlers/confirm-ui-handler"; -import { EggGachaUiHandler } from "#ui/handlers/egg-gacha-ui-handler"; -import { EggHatchSceneUiHandler } from "#ui/handlers/egg-hatch-scene-ui-handler"; -import { EggListUiHandler } from "#ui/handlers/egg-list-ui-handler"; -import { EggSummaryUiHandler } from "#ui/handlers/egg-summary-ui-handler"; -import { EvolutionSceneUiHandler } from "#ui/handlers/evolution-scene-ui-handler"; -import { FightUiHandler } from "#ui/handlers/fight-ui-handler"; -import { GameStatsUiHandler } from "#ui/handlers/game-stats-ui-handler"; -import { LoadingModalUiHandler } from "#ui/handlers/loading-modal-ui-handler"; -import { LoginFormUiHandler } from "#ui/handlers/login-form-ui-handler"; -import { MenuUiHandler } from "#ui/handlers/menu-ui-handler"; -import { MessageUiHandler } from "#ui/handlers/message-ui-handler"; -import { ModifierSelectUiHandler } from "#ui/handlers/modifier-select-ui-handler"; -import { MysteryEncounterUiHandler } from "#ui/handlers/mystery-encounter-ui-handler"; -import { PartyUiHandler } from "#ui/handlers/party-ui-handler"; -import { PokedexPageUiHandler } from "#ui/handlers/pokedex-page-ui-handler"; -import { PokedexScanUiHandler } from "#ui/handlers/pokedex-scan-ui-handler"; -import { PokedexUiHandler } from "#ui/handlers/pokedex-ui-handler"; -import { RegistrationFormUiHandler } from "#ui/handlers/registration-form-ui-handler"; -import { RenameFormUiHandler } from "#ui/handlers/rename-form-ui-handler"; -import { RunHistoryUiHandler } from "#ui/handlers/run-history-ui-handler"; -import { RunInfoUiHandler } from "#ui/handlers/run-info-ui-handler"; -import { SaveSlotSelectUiHandler } from "#ui/handlers/save-slot-select-ui-handler"; -import { SessionReloadModalUiHandler } from "#ui/handlers/session-reload-modal-ui-handler"; -import { StarterSelectUiHandler } from "#ui/handlers/starter-select-ui-handler"; -import { SummaryUiHandler } from "#ui/handlers/summary-ui-handler"; -import { TargetSelectUiHandler } from "#ui/handlers/target-select-ui-handler"; -import { TestDialogueUiHandler } from "#ui/handlers/test-dialogue-ui-handler"; -import { TitleUiHandler } from "#ui/handlers/title-ui-handler"; -import type { UiHandler } from "#ui/handlers/ui-handler"; -import { UnavailableModalUiHandler } from "#ui/handlers/unavailable-modal-ui-handler"; import { KeyboardBindingUiHandler } from "#ui/keyboard-binding-ui-handler"; +import { LoadingModalUiHandler } from "#ui/loading-modal-ui-handler"; +import { LoginFormUiHandler } from "#ui/login-form-ui-handler"; +import { MenuUiHandler } from "#ui/menu-ui-handler"; +import { MessageUiHandler } from "#ui/message-ui-handler"; +import { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler"; +import { MysteryEncounterUiHandler } from "#ui/mystery-encounter-ui-handler"; import { NavigationManager } from "#ui/navigation-menu"; import { OptionSelectUiHandler } from "#ui/option-select-ui-handler"; +import { PartyUiHandler } from "#ui/party-ui-handler"; +import { PokedexPageUiHandler } from "#ui/pokedex-page-ui-handler"; +import { PokedexScanUiHandler } from "#ui/pokedex-scan-ui-handler"; +import { PokedexUiHandler } from "#ui/pokedex-ui-handler"; +import { RegistrationFormUiHandler } from "#ui/registration-form-ui-handler"; +import { RenameFormUiHandler } from "#ui/rename-form-ui-handler"; +import { RunHistoryUiHandler } from "#ui/run-history-ui-handler"; +import { RunInfoUiHandler } from "#ui/run-info-ui-handler"; +import { SaveSlotSelectUiHandler } from "#ui/save-slot-select-ui-handler"; +import { SavingIconContainer } from "#ui/saving-icon-handler"; +import { SessionReloadModalUiHandler } from "#ui/session-reload-modal-ui-handler"; import { SettingsAudioUiHandler } from "#ui/settings-audio-ui-handler"; import { SettingsDisplayUiHandler } from "#ui/settings-display-ui-handler"; import { SettingsGamepadUiHandler } from "#ui/settings-gamepad-ui-handler"; import { SettingsKeyboardUiHandler } from "#ui/settings-keyboard-ui-handler"; import { SettingsUiHandler } from "#ui/settings-ui-handler"; +import { StarterSelectUiHandler } from "#ui/starter-select-ui-handler"; +import { SummaryUiHandler } from "#ui/summary-ui-handler"; +import { TargetSelectUiHandler } from "#ui/target-select-ui-handler"; +import { TestDialogueUiHandler } from "#ui/test-dialogue-ui-handler"; import { addTextObject } from "#ui/text"; +import { TitleUiHandler } from "#ui/title-ui-handler"; +import type { UiHandler } from "#ui/ui-handler"; import { addWindow } from "#ui/ui-theme"; +import { UnavailableModalUiHandler } from "#ui/unavailable-modal-ui-handler"; import { executeIf } from "#utils/common"; import i18next from "i18next"; import { AdminUiHandler } from "./handlers/admin-ui-handler"; diff --git a/src/ui/utils/scrollable-grid-helper.ts b/src/ui/utils/scrollable-grid-helper.ts index 675352ff6fb..74ddfdfa412 100644 --- a/src/ui/utils/scrollable-grid-helper.ts +++ b/src/ui/utils/scrollable-grid-helper.ts @@ -1,6 +1,6 @@ import { Button } from "#enums/buttons"; -import type { ScrollBar } from "#ui/containers/scroll-bar"; -import type { UiHandler } from "#ui/handlers/ui-handler"; +import type { ScrollBar } from "#ui/scroll-bar"; +import type { UiHandler } from "#ui/ui-handler"; type UpdateGridCallbackFunction = () => void; type UpdateDetailsCallbackFunction = (index: number) => void; diff --git a/src/utils/anim-utils.ts b/src/utils/anim-utils.ts new file mode 100644 index 00000000000..f1a06552d38 --- /dev/null +++ b/src/utils/anim-utils.ts @@ -0,0 +1,26 @@ +import { globalScene } from "#app/global-scene"; +import type { SceneBase } from "#app/scene-base"; + +/** + * Plays a Tween animation, resolving once the animation completes. + * @param config - The config for a single Tween + * @param scene - The {@linkcode SceneBase} on which the Tween plays; default {@linkcode globalScene} + * @returns A Promise that resolves once the Tween has been played. + * + * @privateRemarks + * The `config` input should not include an `onComplete` field as that callback is + * used to resolve the Promise containing the Tween animation. + * However, `config`'s type cannot be changed to something like `Omit` + * due to how the type for `TweenBuilderConfig` is defined. + */ +export async function playTween( + config: Phaser.Types.Tweens.TweenBuilderConfig, + scene: SceneBase = globalScene, +): Promise { + await new Promise(resolve => + scene.tweens.add({ + ...config, + onComplete: resolve, + }), + ); +} diff --git a/src/utils/common.ts b/src/utils/common.ts index 2734b075a53..f0166b1e74c 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -458,15 +458,6 @@ export function truncateString(str: string, maxLength = 10) { return str; } -/** - * Report whether a given value is nullish (`null`/`undefined`). - * @param val - The value whose nullishness is being checked - * @returns `true` if `val` is either `null` or `undefined` - */ -export function isNullOrUndefined(val: any): val is null | undefined { - return val === null || val === undefined; -} - /** * This function is used in the context of a Pokémon battle game to calculate the actual integer damage value from a float result. * Many damage calculation formulas involve various parameters and result in float values. diff --git a/src/utils/cookies.ts b/src/utils/cookies.ts index e82895d1fac..e16d9d78556 100644 --- a/src/utils/cookies.ts +++ b/src/utils/cookies.ts @@ -23,11 +23,13 @@ export function getCookie(cName: string): string { } const name = `${cName}=`; const ca = document.cookie.split(";"); - // Check all cookies in the document and see if any of them match, grabbing the first one whose value lines up - for (const c of ca) { - const cTrimmed = c.trim(); - if (cTrimmed.startsWith(name)) { - return c.slice(name.length, c.length); + for (let c of ca) { + // ⚠️ DO NOT REPLACE THIS WITH C = C.TRIM() - IT BREAKS IN NON-CHROMIUM BROWSERS ⚠️ + while (c.charAt(0) === " ") { + c = c.substring(1); + } + if (c.indexOf(name) === 0) { + return c.substring(name.length, c.length); } } return ""; diff --git a/src/vite.env.d.ts b/src/vite.env.d.ts index 68159908730..3192b81afd3 100644 --- a/src/vite.env.d.ts +++ b/src/vite.env.d.ts @@ -9,8 +9,9 @@ interface ImportMetaEnv { readonly VITE_DISCORD_CLIENT_ID?: string; readonly VITE_GOOGLE_CLIENT_ID?: string; readonly VITE_I18N_DEBUG?: string; + readonly NODE_ENV?: string; } -interface ImportMeta { +declare interface ImportMeta { readonly env: ImportMetaEnv; } diff --git a/test/abilities/arena-trap.test.ts b/test/abilities/arena-trap.test.ts index 5b426fd4f47..8f5d820a145 100644 --- a/test/abilities/arena-trap.test.ts +++ b/test/abilities/arena-trap.test.ts @@ -6,7 +6,7 @@ import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import { UiMode } from "#enums/ui-mode"; import { GameManager } from "#test/test-utils/game-manager"; -import type { PartyUiHandler } from "#ui/handlers/party-ui-handler"; +import type { PartyUiHandler } from "#ui/party-ui-handler"; import i18next from "i18next"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; diff --git a/test/abilities/healer.test.ts b/test/abilities/healer.test.ts index 43280ff8271..fb28cd891ac 100644 --- a/test/abilities/healer.test.ts +++ b/test/abilities/healer.test.ts @@ -6,7 +6,6 @@ import { SpeciesId } from "#enums/species-id"; import { StatusEffect } from "#enums/status-effect"; import type { Pokemon } from "#field/pokemon"; import { GameManager } from "#test/test-utils/game-manager"; -import { isNullOrUndefined } from "#utils/common"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; @@ -37,7 +36,7 @@ describe("Abilities - Healer", () => { // Mock healer to have a 100% chance of healing its ally vi.spyOn(allAbilities[AbilityId.HEALER].getAttrs("PostTurnResetStatusAbAttr")[0], "getCondition").mockReturnValue( - (pokemon: Pokemon) => !isNullOrUndefined(pokemon.getAlly()), + (pokemon: Pokemon) => pokemon.getAlly() != null, ); }); diff --git a/test/ai/ai-moveset-gen.test.ts b/test/ai/ai-moveset-gen.test.ts new file mode 100644 index 00000000000..6d927926131 --- /dev/null +++ b/test/ai/ai-moveset-gen.test.ts @@ -0,0 +1,285 @@ +import { __INTERNAL_TEST_EXPORTS } from "#app/ai/ai-moveset-gen"; +import { + COMMON_TIER_TM_LEVEL_REQUIREMENT, + GREAT_TIER_TM_LEVEL_REQUIREMENT, + ULTRA_TIER_TM_LEVEL_REQUIREMENT, +} from "#balance/moveset-generation"; +import { allMoves, allSpecies } from "#data/data-lists"; +import { MoveId } from "#enums/move-id"; +import { SpeciesId } from "#enums/species-id"; +import { TrainerSlot } from "#enums/trainer-slot"; +import { EnemyPokemon } from "#field/pokemon"; +import { GameManager } from "#test/test-utils/game-manager"; +import { NumberHolder } from "#utils/common"; +import { afterEach } from "node:test"; +import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; + +/** + * Parameters for {@linkcode createTestablePokemon} + */ +interface MockPokemonParams { + /** The level to set the Pokémon to */ + level: number; + /** + * Whether the pokemon is a boss or not. + * @defaultValue `false` + */ + boss?: boolean; + /** + * The trainer slot to assign to the pokemon, if any. + * @defaultValue `TrainerSlot.NONE` + */ + trainerSlot?: TrainerSlot; + /** + * The form index to assign to the pokemon, if any. + * This *must* be one of the valid form indices for the species, or the test will break. + * @defaultValue `0` + */ + formIndex?: number; +} + +/** + * Construct an `EnemyPokemon` that can be used for testing + * @param species - The species ID of the pokemon to create + * @returns The newly created `EnemyPokemon`. + * @todo Move this to a dedicated unit test util folder if more tests come to rely on it + */ +function createTestablePokemon( + species: SpeciesId, + { level, trainerSlot = TrainerSlot.NONE, boss = false, formIndex = 0 }: MockPokemonParams, +): EnemyPokemon { + const pokemon = new EnemyPokemon(allSpecies[species], level, trainerSlot, boss); + if (formIndex !== 0) { + const formIndexLength = allSpecies[species]?.forms.length; + const name = allSpecies[species]?.name; + expect(formIndex, `${name} does not have a form with index ${formIndex}`).toBeLessThan(formIndexLength); + pokemon.formIndex = formIndex; + } + + return pokemon; +} + +describe("Unit Tests - ai-moveset-gen.ts", () => { + describe("filterPool", () => { + const { filterPool } = __INTERNAL_TEST_EXPORTS; + it("clones a pool when there are no predicates", () => { + const pool = new Map([ + [MoveId.TACKLE, 1], + [MoveId.FLAMETHROWER, 2], + ]); + + const filtered = filterPool(pool, () => true); + const expected = [ + [MoveId.TACKLE, 1], + [MoveId.FLAMETHROWER, 2], + ]; + expect(filtered).toEqual(expected); + }); + + it("does not modify the original pool", () => { + const pool = new Map([ + [MoveId.TACKLE, 1], + [MoveId.FLAMETHROWER, 2], + ]); + const original = new Map(pool); + + filterPool(pool, moveId => moveId !== MoveId.TACKLE); + expect(pool).toEqual(original); + }); + + it("filters out moves that do not match the predicate", () => { + const pool = new Map([ + [MoveId.TACKLE, 1], + [MoveId.FLAMETHROWER, 2], + [MoveId.SPLASH, 3], + ]); + const filtered = filterPool(pool, moveId => moveId !== MoveId.SPLASH); + expect(filtered).toEqual([ + [MoveId.TACKLE, 1], + [MoveId.FLAMETHROWER, 2], + ]); + }); + + it("returns an empty array if no moves match the predicate", () => { + const pool = new Map([ + [MoveId.TACKLE, 1], + [MoveId.FLAMETHROWER, 2], + ]); + const filtered = filterPool(pool, () => false); + expect(filtered).toEqual([]); + }); + + it("calculates totalWeight correctly when provided", () => { + const pool = new Map([ + [MoveId.TACKLE, 1], + [MoveId.FLAMETHROWER, 2], + [MoveId.SPLASH, 3], + ]); + const totalWeight = new NumberHolder(0); + const filtered = filterPool(pool, moveId => moveId !== MoveId.SPLASH, totalWeight); + expect(filtered).toEqual([ + [MoveId.TACKLE, 1], + [MoveId.FLAMETHROWER, 2], + ]); + expect(totalWeight.value).toBe(3); + }); + + it("Clears totalWeight when provided", () => { + const pool = new Map([ + [MoveId.TACKLE, 1], + [MoveId.FLAMETHROWER, 2], + ]); + const totalWeight = new NumberHolder(42); + const filtered = filterPool(pool, () => false, totalWeight); + expect(filtered).toEqual([]); + expect(totalWeight.value).toBe(0); + }); + }); + + describe("getAllowedTmTiers", () => { + const { getAllowedTmTiers } = __INTERNAL_TEST_EXPORTS; + + it.each([ + { tierName: "common", resIdx: 0, level: COMMON_TIER_TM_LEVEL_REQUIREMENT - 1 }, + { tierName: "great", resIdx: 1, level: GREAT_TIER_TM_LEVEL_REQUIREMENT - 1 }, + { tierName: "ultra", resIdx: 2, level: ULTRA_TIER_TM_LEVEL_REQUIREMENT - 1 }, + ])("should prevent $name TMs when below level $level", ({ level, resIdx }) => { + expect(getAllowedTmTiers(level)[resIdx]).toBe(false); + }); + + it.each([ + { tierName: "common", resIdx: 0, level: COMMON_TIER_TM_LEVEL_REQUIREMENT }, + { tierName: "great", resIdx: 1, level: GREAT_TIER_TM_LEVEL_REQUIREMENT }, + { tierName: "ultra", resIdx: 2, level: ULTRA_TIER_TM_LEVEL_REQUIREMENT }, + ])("should allow $name TMs when at level $level", ({ level, resIdx }) => { + expect(getAllowedTmTiers(level)[resIdx]).toBe(true); + }); + }); + + // Unit tests for methods that require a game context + describe("", () => { + //#region boilerplate + let phaserGame: Phaser.Game; + let game: GameManager; + /**A pokemon object that will be cleaned up after every test */ + let pokemon: EnemyPokemon | null = null; + + beforeAll(async () => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + // Game manager can be reused between tests as we are not really modifying the global state + // So there is no need to put this in a beforeEach with cleanup in afterEach. + game = new GameManager(phaserGame); + }); + + afterEach(() => { + pokemon?.destroy(); + }); + // Sanitize the interceptor after running the suite to ensure other tests are not affected + afterAll(() => { + game.phaseInterceptor.restoreOg(); + }); + //#endregion boilerplate + + function createCharmander(_ = pokemon): asserts _ is EnemyPokemon { + pokemon?.destroy(); + pokemon = createTestablePokemon(SpeciesId.CHARMANDER, { level: 10 }); + expect(pokemon).toBeInstanceOf(EnemyPokemon); + } + describe("getAndWeightLevelMoves", () => { + const { getAndWeightLevelMoves } = __INTERNAL_TEST_EXPORTS; + + it("returns an empty map if getLevelMoves throws", async () => { + createCharmander(pokemon); + vi.spyOn(pokemon, "getLevelMoves").mockImplementation(() => { + throw new Error("fail"); + }); + // Suppress the warning from the test output + const warnMock = vi.spyOn(console, "warn").mockImplementationOnce(() => {}); + + const result = getAndWeightLevelMoves(pokemon); + expect(warnMock).toHaveBeenCalled(); + expect(result.size).toBe(0); + }); + + it("skips unimplemented moves", () => { + createCharmander(pokemon); + vi.spyOn(pokemon, "getLevelMoves").mockReturnValue([ + [1, MoveId.TACKLE], + [5, MoveId.GROWL], + ]); + vi.spyOn(allMoves[MoveId.TACKLE], "name", "get").mockReturnValue("Tackle (N)"); + const result = getAndWeightLevelMoves(pokemon); + expect(result.has(MoveId.TACKLE)).toBe(false); + expect(result.has(MoveId.GROWL)).toBe(true); + }); + + it("skips moves already in the pool", () => { + createCharmander(pokemon); + vi.spyOn(pokemon, "getLevelMoves").mockReturnValue([ + [1, MoveId.TACKLE], + [5, MoveId.TACKLE], + ]); + + const result = getAndWeightLevelMoves(pokemon); + expect(result.get(MoveId.TACKLE)).toBe(21); + }); + + it("weights moves based on level", () => { + createCharmander(pokemon); + vi.spyOn(pokemon, "getLevelMoves").mockReturnValue([ + [1, MoveId.TACKLE], + [5, MoveId.GROWL], + [9, MoveId.EMBER], + ]); + + const result = getAndWeightLevelMoves(pokemon); + expect(result.get(MoveId.TACKLE)).toBe(21); + expect(result.get(MoveId.GROWL)).toBe(25); + expect(result.get(MoveId.EMBER)).toBe(29); + }); + }); + }); +}); + +describe("Regression Tests - ai-moveset-gen.ts", () => { + //#region boilerplate + let phaserGame: Phaser.Game; + let game: GameManager; + /**A pokemon object that will be cleaned up after every test */ + let pokemon: EnemyPokemon | null = null; + + beforeAll(async () => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + // Game manager can be reused between tests as we are not really modifying the global state + // So there is no need to put this in a beforeEach with cleanup in afterEach. + game = new GameManager(phaserGame); + }); + + afterEach(() => { + pokemon?.destroy(); + }); + + afterAll(() => { + game.phaseInterceptor.restoreOg(); + }); + //#endregion boilerplate + + describe("getTmPoolForSpecies", () => { + const { getTmPoolForSpecies } = __INTERNAL_TEST_EXPORTS; + + it("should not crash when generating a moveset for Pokemon without TM moves", () => { + pokemon = createTestablePokemon(SpeciesId.DITTO, { level: 50 }); + expect(() => + getTmPoolForSpecies(SpeciesId.DITTO, ULTRA_TIER_TM_LEVEL_REQUIREMENT, "", new Map(), new Map(), new Map(), [ + true, + true, + true, + ]), + ).not.toThrow(); + }); + }); +}); diff --git a/test/battler-tags/substitute.test.ts b/test/battler-tags/substitute.test.ts index 7ae60ad1408..a2ff539d2a8 100644 --- a/test/battler-tags/substitute.test.ts +++ b/test/battler-tags/substitute.test.ts @@ -49,7 +49,7 @@ describe("BattlerTag - SubstituteTag", () => { vi.spyOn(messages, "getPokemonNameWithAffix").mockReturnValue(""); vi.spyOn(mockPokemon.scene as BattleScene, "getPokemonById").mockImplementation(pokemonId => - mockPokemon.id === pokemonId ? mockPokemon : null, + mockPokemon.id === pokemonId ? mockPokemon : undefined, ); }); diff --git a/test/challenges/hardcore.test.ts b/test/challenges/hardcore.test.ts index a52d7102868..0f4ab1b9f02 100644 --- a/test/challenges/hardcore.test.ts +++ b/test/challenges/hardcore.test.ts @@ -8,7 +8,7 @@ import { SpeciesId } from "#enums/species-id"; import { StatusEffect } from "#enums/status-effect"; import { UiMode } from "#enums/ui-mode"; import { GameManager } from "#test/test-utils/game-manager"; -import { ModifierSelectUiHandler } from "#ui/handlers/modifier-select-ui-handler"; +import { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; diff --git a/test/challenges/limited-support.test.ts b/test/challenges/limited-support.test.ts index ba8930943dd..35413220550 100644 --- a/test/challenges/limited-support.test.ts +++ b/test/challenges/limited-support.test.ts @@ -5,7 +5,7 @@ import { SpeciesId } from "#enums/species-id"; import { UiMode } from "#enums/ui-mode"; import { ExpBoosterModifier } from "#modifiers/modifier"; import { GameManager } from "#test/test-utils/game-manager"; -import { ModifierSelectUiHandler } from "#ui/handlers/modifier-select-ui-handler"; +import { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; diff --git a/test/daily-mode.test.ts b/test/daily-mode.test.ts index fae12a0b5d7..34a8da80478 100644 --- a/test/daily-mode.test.ts +++ b/test/daily-mode.test.ts @@ -5,7 +5,7 @@ import { SpeciesId } from "#enums/species-id"; import { UiMode } from "#enums/ui-mode"; import { MapModifier } from "#modifiers/modifier"; import { GameManager } from "#test/test-utils/game-manager"; -import { ModifierSelectUiHandler } from "#ui/handlers/modifier-select-ui-handler"; +import { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; describe("Daily Mode", () => { diff --git a/test/eggs/egg.test.ts b/test/eggs/egg.test.ts index 8b47e68f402..001adf83b37 100644 --- a/test/eggs/egg.test.ts +++ b/test/eggs/egg.test.ts @@ -202,7 +202,7 @@ describe("Egg Generation Tests", () => { const scene = game.scene; const eggMoveIndex = new Egg({ scene }).eggMoveIndex; - const result = !Utils.isNullOrUndefined(eggMoveIndex) && eggMoveIndex >= 0 && eggMoveIndex <= 3; + const result = eggMoveIndex != null && eggMoveIndex >= 0 && eggMoveIndex <= 3; expect(result).toBe(true); }); diff --git a/test/imports.test.ts b/test/imports.test.ts deleted file mode 100644 index aeaa763c05e..00000000000 --- a/test/imports.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { initStatsKeys } from "#ui/handlers/game-stats-ui-handler"; -import { describe, expect, it } from "vitest"; - -async function importModule() { - try { - initStatsKeys(); - const { PokemonMove } = await import("#app/data/moves/pokemon-move"); - const { SpeciesId: Species } = await import("#enums/species-id"); - return { - PokemonMove, - Species, - }; - // Dynamically import the module - } catch (error) { - // Log the error stack trace - console.error("Error during import:", error.stack); - // Rethrow the error to ensure the test fails - throw error; - } -} - -describe("tests to debug the import, with trace", () => { - it("import PokemonMove module", async () => { - const module = await importModule(); - // Example assertion - expect(module.PokemonMove).toBeDefined(); - }); - - it("import Species module", async () => { - const module = await importModule(); - // Example assertion - expect(module.Species).toBeDefined(); - }); -}); diff --git a/test/items/dire-hit.test.ts b/test/items/dire-hit.test.ts index d704a94f3a8..6d4bc7524eb 100644 --- a/test/items/dire-hit.test.ts +++ b/test/items/dire-hit.test.ts @@ -10,7 +10,7 @@ import { NewBattlePhase } from "#phases/new-battle-phase"; import { TurnEndPhase } from "#phases/turn-end-phase"; import { TurnInitPhase } from "#phases/turn-init-phase"; import { GameManager } from "#test/test-utils/game-manager"; -import type { ModifierSelectUiHandler } from "#ui/handlers/modifier-select-ui-handler"; +import type { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; diff --git a/test/items/double-battle-chance-booster.test.ts b/test/items/double-battle-chance-booster.test.ts index 9985d4b3a55..2c12b34eba3 100644 --- a/test/items/double-battle-chance-booster.test.ts +++ b/test/items/double-battle-chance-booster.test.ts @@ -5,7 +5,7 @@ import { SpeciesId } from "#enums/species-id"; import { UiMode } from "#enums/ui-mode"; import { DoubleBattleChanceBoosterModifier } from "#modifiers/modifier"; import { GameManager } from "#test/test-utils/game-manager"; -import type { ModifierSelectUiHandler } from "#ui/handlers/modifier-select-ui-handler"; +import type { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; diff --git a/test/items/temp-stat-stage-booster.test.ts b/test/items/temp-stat-stage-booster.test.ts index 499f1d630b0..05ea5a03eae 100644 --- a/test/items/temp-stat-stage-booster.test.ts +++ b/test/items/temp-stat-stage-booster.test.ts @@ -7,7 +7,7 @@ import { BATTLE_STATS, Stat } from "#enums/stat"; import { UiMode } from "#enums/ui-mode"; import { TempStatStageBoosterModifier } from "#modifiers/modifier"; import { GameManager } from "#test/test-utils/game-manager"; -import type { ModifierSelectUiHandler } from "#ui/handlers/modifier-select-ui-handler"; +import type { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; diff --git a/test/moves/whirlwind.test.ts b/test/moves/whirlwind.test.ts index 61c05a30322..ac112f01ea3 100644 --- a/test/moves/whirlwind.test.ts +++ b/test/moves/whirlwind.test.ts @@ -10,6 +10,7 @@ import { PokemonType } from "#enums/pokemon-type"; import { SpeciesId } from "#enums/species-id"; import { StatusEffect } from "#enums/status-effect"; import { TrainerType } from "#enums/trainer-type"; +import { TrainerVariant } from "#enums/trainer-variant"; import { GameManager } from "#test/test-utils/game-manager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; @@ -193,7 +194,7 @@ describe("Moves - Whirlwind", () => { .battleType(BattleType.TRAINER) .randomTrainer({ trainerType: TrainerType.BREEDER, - alwaysDouble: true, + trainerVariant: TrainerVariant.DOUBLE, }) .enemyMoveset([MoveId.SPLASH, MoveId.LUNAR_DANCE]) .moveset([MoveId.WHIRLWIND, MoveId.SPLASH]); diff --git a/test/mystery-encounter/encounter-test-utils.ts b/test/mystery-encounter/encounter-test-utils.ts index fcf27b2c6fb..4aad0e000d9 100644 --- a/test/mystery-encounter/encounter-test-utils.ts +++ b/test/mystery-encounter/encounter-test-utils.ts @@ -13,11 +13,10 @@ import { } from "#phases/mystery-encounter-phases"; import { VictoryPhase } from "#phases/victory-phase"; import type { GameManager } from "#test/test-utils/game-manager"; -import type { MessageUiHandler } from "#ui/handlers/message-ui-handler"; -import type { MysteryEncounterUiHandler } from "#ui/handlers/mystery-encounter-ui-handler"; -import type { PartyUiHandler } from "#ui/handlers/party-ui-handler"; +import type { MessageUiHandler } from "#ui/message-ui-handler"; +import type { MysteryEncounterUiHandler } from "#ui/mystery-encounter-ui-handler"; import type { OptionSelectUiHandler } from "#ui/option-select-ui-handler"; -import { isNullOrUndefined } from "#utils/common"; +import type { PartyUiHandler } from "#ui/party-ui-handler"; import { expect, vi } from "vitest"; /** @@ -147,7 +146,7 @@ export async function runSelectMysteryEncounterOption( break; } - if (!isNullOrUndefined(secondaryOptionSelect?.pokemonNo)) { + if (secondaryOptionSelect?.pokemonNo != null) { await handleSecondaryOptionSelect(game, secondaryOptionSelect.pokemonNo, secondaryOptionSelect.optionNo); } else { uiHandler.processInput(Button.ACTION); @@ -174,7 +173,7 @@ async function handleSecondaryOptionSelect(game: GameManager, pokemonNo: number, partyUiHandler.processInput(Button.ACTION); // If there is a second choice to make after selecting a Pokemon - if (!isNullOrUndefined(optionNo)) { + if (optionNo != null) { // Wait for Summary menu to close and second options to spawn const secondOptionUiHandler = game.scene.ui.handlers[UiMode.OPTION_SELECT] as OptionSelectUiHandler; vi.spyOn(secondOptionUiHandler, "show"); diff --git a/test/mystery-encounter/encounters/berries-abound-encounter.test.ts b/test/mystery-encounter/encounters/berries-abound-encounter.test.ts index 44585d4d795..5e9dffa1332 100644 --- a/test/mystery-encounter/encounters/berries-abound-encounter.test.ts +++ b/test/mystery-encounter/encounters/berries-abound-encounter.test.ts @@ -17,7 +17,7 @@ import { } from "#test/mystery-encounter/encounter-test-utils"; import { GameManager } from "#test/test-utils/game-manager"; import { initSceneWithoutEncounterPhase } from "#test/test-utils/game-manager-utils"; -import { ModifierSelectUiHandler } from "#ui/handlers/modifier-select-ui-handler"; +import { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; const namespace = "mysteryEncounters/berriesAbound"; diff --git a/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts b/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts index a3357b00b89..723516174fb 100644 --- a/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts +++ b/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts @@ -20,7 +20,7 @@ import { } from "#test/mystery-encounter/encounter-test-utils"; import { GameManager } from "#test/test-utils/game-manager"; import { initSceneWithoutEncounterPhase } from "#test/test-utils/game-manager-utils"; -import { ModifierSelectUiHandler } from "#ui/handlers/modifier-select-ui-handler"; +import { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; const namespace = "mysteryEncounters/bugTypeSuperfan"; diff --git a/test/mystery-encounter/encounters/clowning-around-encounter.test.ts b/test/mystery-encounter/encounters/clowning-around-encounter.test.ts index d199a331943..e7ec6e43392 100644 --- a/test/mystery-encounter/encounters/clowning-around-encounter.test.ts +++ b/test/mystery-encounter/encounters/clowning-around-encounter.test.ts @@ -29,8 +29,8 @@ import { } from "#test/mystery-encounter/encounter-test-utils"; import { GameManager } from "#test/test-utils/game-manager"; import { initSceneWithoutEncounterPhase } from "#test/test-utils/game-manager-utils"; -import type { PartyUiHandler } from "#ui/handlers/party-ui-handler"; import type { OptionSelectUiHandler } from "#ui/option-select-ui-handler"; +import type { PartyUiHandler } from "#ui/party-ui-handler"; import { getPokemonSpecies } from "#utils/pokemon-utils"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; diff --git a/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts b/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts index bf42e6d4df3..81a2fc7463c 100644 --- a/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts +++ b/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts @@ -18,7 +18,7 @@ import { skipBattleRunMysteryEncounterRewardsPhase, } from "#test/mystery-encounter/encounter-test-utils"; import { GameManager } from "#test/test-utils/game-manager"; -import { ModifierSelectUiHandler } from "#ui/handlers/modifier-select-ui-handler"; +import { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; const namespace = "mysteryEncounters/dancingLessons"; diff --git a/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts b/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts index d6c566c4de6..c270429b394 100644 --- a/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts +++ b/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts @@ -11,7 +11,7 @@ import * as MysteryEncounters from "#mystery-encounters/mystery-encounters"; import { CIVILIZATION_ENCOUNTER_BIOMES } from "#mystery-encounters/mystery-encounters"; import { runMysteryEncounterToEnd } from "#test/mystery-encounter/encounter-test-utils"; import { GameManager } from "#test/test-utils/game-manager"; -import { ModifierSelectUiHandler } from "#ui/handlers/modifier-select-ui-handler"; +import { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; const namespace = "mysteryEncounters/departmentStoreSale"; diff --git a/test/mystery-encounter/encounters/field-trip-encounter.test.ts b/test/mystery-encounter/encounters/field-trip-encounter.test.ts index b8e6e36cf17..fd3e20012b1 100644 --- a/test/mystery-encounter/encounters/field-trip-encounter.test.ts +++ b/test/mystery-encounter/encounters/field-trip-encounter.test.ts @@ -12,7 +12,7 @@ import * as MysteryEncounters from "#mystery-encounters/mystery-encounters"; import { SelectModifierPhase } from "#phases/select-modifier-phase"; import { runMysteryEncounterToEnd } from "#test/mystery-encounter/encounter-test-utils"; import { GameManager } from "#test/test-utils/game-manager"; -import { ModifierSelectUiHandler } from "#ui/handlers/modifier-select-ui-handler"; +import { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler"; import i18next from "i18next"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; diff --git a/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts b/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts index 34c0f635000..81dbad16e01 100644 --- a/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts +++ b/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts @@ -17,7 +17,7 @@ import { } from "#test/mystery-encounter/encounter-test-utils"; import { GameManager } from "#test/test-utils/game-manager"; import { initSceneWithoutEncounterPhase } from "#test/test-utils/game-manager-utils"; -import { ModifierSelectUiHandler } from "#ui/handlers/modifier-select-ui-handler"; +import { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; const namespace = "mysteryEncounters/fightOrFlight"; diff --git a/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts b/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts index 8a058bad5fe..bc1a2893627 100644 --- a/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts +++ b/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts @@ -22,7 +22,7 @@ import { } from "#test/mystery-encounter/encounter-test-utils"; import { GameManager } from "#test/test-utils/game-manager"; import { initSceneWithoutEncounterPhase } from "#test/test-utils/game-manager-utils"; -import { ModifierSelectUiHandler } from "#ui/handlers/modifier-select-ui-handler"; +import { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; const namespace = "mysteryEncounters/funAndGames"; diff --git a/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts b/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts index 90bc41fde2b..c8e934168bc 100644 --- a/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts +++ b/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts @@ -15,7 +15,7 @@ import * as MysteryEncounters from "#mystery-encounters/mystery-encounters"; import { CIVILIZATION_ENCOUNTER_BIOMES } from "#mystery-encounters/mystery-encounters"; import { runMysteryEncounterToEnd } from "#test/mystery-encounter/encounter-test-utils"; import { GameManager } from "#test/test-utils/game-manager"; -import { ModifierSelectUiHandler } from "#ui/handlers/modifier-select-ui-handler"; +import { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler"; import * as Utils from "#utils/common"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; diff --git a/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts b/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts index ed8b6bffbe9..a8d91f8f101 100644 --- a/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts +++ b/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts @@ -20,7 +20,7 @@ import { GameManager } from "#test/test-utils/game-manager"; import { initSceneWithoutEncounterPhase } from "#test/test-utils/game-manager-utils"; import { TrainerConfig } from "#trainers/trainer-config"; import { TrainerPartyCompoundTemplate, TrainerPartyTemplate } from "#trainers/trainer-party-template"; -import { ModifierSelectUiHandler } from "#ui/handlers/modifier-select-ui-handler"; +import { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; const namespace = "mysteryEncounters/mysteriousChallengers"; diff --git a/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts b/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts index a096ea5ff6e..c88d77a8cf5 100644 --- a/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts +++ b/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts @@ -16,7 +16,7 @@ import { } from "#test/mystery-encounter/encounter-test-utils"; import { GameManager } from "#test/test-utils/game-manager"; import { initSceneWithoutEncounterPhase } from "#test/test-utils/game-manager-utils"; -import { ModifierSelectUiHandler } from "#ui/handlers/modifier-select-ui-handler"; +import { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler"; import i18next from "i18next"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; diff --git a/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts b/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts index 5df2d2ab358..06c4c3c1cee 100644 --- a/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts +++ b/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts @@ -24,7 +24,7 @@ import { } from "#test/mystery-encounter/encounter-test-utils"; import { GameManager } from "#test/test-utils/game-manager"; import { initSceneWithoutEncounterPhase } from "#test/test-utils/game-manager-utils"; -import { ModifierSelectUiHandler } from "#ui/handlers/modifier-select-ui-handler"; +import { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler"; import { getPokemonSpecies } from "#utils/pokemon-utils"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; diff --git a/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts b/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts index 9b009879522..814e2ee07fb 100644 --- a/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts +++ b/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts @@ -20,7 +20,7 @@ import { VictoryPhase } from "#phases/victory-phase"; import { runMysteryEncounterToEnd } from "#test/mystery-encounter/encounter-test-utils"; import { GameManager } from "#test/test-utils/game-manager"; import { initSceneWithoutEncounterPhase } from "#test/test-utils/game-manager-utils"; -import { ModifierSelectUiHandler } from "#ui/handlers/modifier-select-ui-handler"; +import { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler"; import { getPokemonSpecies } from "#utils/pokemon-utils"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; diff --git a/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts b/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts index 6f6c01c7322..91a88712e9b 100644 --- a/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts +++ b/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts @@ -27,7 +27,7 @@ import { } from "#test/mystery-encounter/encounter-test-utils"; import { GameManager } from "#test/test-utils/game-manager"; import { initSceneWithoutEncounterPhase } from "#test/test-utils/game-manager-utils"; -import { ModifierSelectUiHandler } from "#ui/handlers/modifier-select-ui-handler"; +import { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler"; import * as Utils from "#utils/common"; import { getPokemonSpecies } from "#utils/pokemon-utils"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; diff --git a/test/mystery-encounter/encounters/weird-dream-encounter.test.ts b/test/mystery-encounter/encounters/weird-dream-encounter.test.ts index 3640ed3809f..f00ef834624 100644 --- a/test/mystery-encounter/encounters/weird-dream-encounter.test.ts +++ b/test/mystery-encounter/encounters/weird-dream-encounter.test.ts @@ -16,7 +16,7 @@ import { } from "#test/mystery-encounter/encounter-test-utils"; import { GameManager } from "#test/test-utils/game-manager"; import { initSceneWithoutEncounterPhase } from "#test/test-utils/game-manager-utils"; -import { ModifierSelectUiHandler } from "#ui/handlers/modifier-select-ui-handler"; +import { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; const namespace = "mysteryEncounters/weirdDream"; diff --git a/test/phases/mystery-encounter-phase.test.ts b/test/phases/mystery-encounter-phase.test.ts index 8dfbd509a05..30ab977dbc6 100644 --- a/test/phases/mystery-encounter-phase.test.ts +++ b/test/phases/mystery-encounter-phase.test.ts @@ -5,8 +5,8 @@ import { SpeciesId } from "#enums/species-id"; import { UiMode } from "#enums/ui-mode"; import { MysteryEncounterOptionSelectedPhase } from "#phases/mystery-encounter-phases"; import { GameManager } from "#test/test-utils/game-manager"; -import type { MessageUiHandler } from "#ui/handlers/message-ui-handler"; -import type { MysteryEncounterUiHandler } from "#ui/handlers/mystery-encounter-ui-handler"; +import type { MessageUiHandler } from "#ui/message-ui-handler"; +import type { MysteryEncounterUiHandler } from "#ui/mystery-encounter-ui-handler"; import i18next from "i18next"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; diff --git a/test/phases/select-modifier-phase.test.ts b/test/phases/select-modifier-phase.test.ts index f6446c6e7be..c6f07088819 100644 --- a/test/phases/select-modifier-phase.test.ts +++ b/test/phases/select-modifier-phase.test.ts @@ -12,7 +12,7 @@ import { ModifierTypeOption } from "#modifiers/modifier-type"; import { SelectModifierPhase } from "#phases/select-modifier-phase"; import { GameManager } from "#test/test-utils/game-manager"; import { initSceneWithoutEncounterPhase } from "#test/test-utils/game-manager-utils"; -import { ModifierSelectUiHandler } from "#ui/handlers/modifier-select-ui-handler"; +import { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler"; import { shiftCharCodes } from "#utils/common"; import { getPokemonSpecies } from "#utils/pokemon-utils"; import Phaser from "phaser"; diff --git a/test/plugins/api/pokerogue-daily-api.test.ts b/test/plugins/api/pokerogue-daily-api.test.ts index b45896e6a2c..ef5dfddada5 100644 --- a/test/plugins/api/pokerogue-daily-api.test.ts +++ b/test/plugins/api/pokerogue-daily-api.test.ts @@ -2,7 +2,7 @@ import { PokerogueDailyApi } from "#api/pokerogue-daily-api"; import { initServerForApiTests } from "#test/test-utils/test-file-initialization"; import { getApiBaseUrl } from "#test/test-utils/test-utils"; import type { GetDailyRankingsPageCountRequest, GetDailyRankingsRequest } from "#types/api/pokerogue-daily-api"; -import { type RankingEntry, ScoreboardCategory } from "#ui/containers/daily-run-scoreboard"; +import { type RankingEntry, ScoreboardCategory } from "#ui/daily-run-scoreboard"; import { HttpResponse, http } from "msw"; import type { SetupServerApi } from "msw/node"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; diff --git a/test/test-utils/game-manager-utils.ts b/test/test-utils/game-manager-utils.ts index 7b12251819f..26b7ccf1020 100644 --- a/test/test-utils/game-manager-utils.ts +++ b/test/test-utils/game-manager-utils.ts @@ -9,7 +9,7 @@ import type { MoveId } from "#enums/move-id"; import type { SpeciesId } from "#enums/species-id"; import { PlayerPokemon } from "#field/pokemon"; import type { StarterMoveset } from "#types/save-data"; -import type { Starter } from "#ui/handlers/starter-select-ui-handler"; +import type { Starter } from "#ui/starter-select-ui-handler"; import { getPokemonSpecies, getPokemonSpeciesForm } from "#utils/pokemon-utils"; /** Function to convert Blob to string */ diff --git a/test/test-utils/game-manager.ts b/test/test-utils/game-manager.ts index 6b7c805df74..f9db964ad26 100644 --- a/test/test-utils/game-manager.ts +++ b/test/test-utils/game-manager.ts @@ -45,14 +45,13 @@ import { MockFetch } from "#test/test-utils/mocks/mock-fetch"; import { PhaseInterceptor } from "#test/test-utils/phase-interceptor"; import { TextInterceptor } from "#test/test-utils/text-interceptor"; import type { PhaseClass, PhaseString } from "#types/phase-types"; -import type { BallUiHandler } from "#ui/handlers/ball-ui-handler"; -import type { BattleMessageUiHandler } from "#ui/handlers/battle-message-ui-handler"; -import type { CommandUiHandler } from "#ui/handlers/command-ui-handler"; -import type { ModifierSelectUiHandler } from "#ui/handlers/modifier-select-ui-handler"; -import type { PartyUiHandler } from "#ui/handlers/party-ui-handler"; -import type { StarterSelectUiHandler } from "#ui/handlers/starter-select-ui-handler"; -import type { TargetSelectUiHandler } from "#ui/handlers/target-select-ui-handler"; -import { isNullOrUndefined } from "#utils/common"; +import type { BallUiHandler } from "#ui/ball-ui-handler"; +import type { BattleMessageUiHandler } from "#ui/battle-message-ui-handler"; +import type { CommandUiHandler } from "#ui/command-ui-handler"; +import type { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler"; +import type { PartyUiHandler } from "#ui/party-ui-handler"; +import type { StarterSelectUiHandler } from "#ui/starter-select-ui-handler"; +import type { TargetSelectUiHandler } from "#ui/target-select-ui-handler"; import fs from "node:fs"; import { AES, enc } from "crypto-js"; import { expect, vi } from "vitest"; @@ -240,7 +239,7 @@ export class GameManager { * @returns A Promise that resolves when the EncounterPhase ends. */ async runToMysteryEncounter(encounterType?: MysteryEncounterType, species?: SpeciesId[]) { - if (!isNullOrUndefined(encounterType)) { + if (encounterType != null) { this.override.disableTrainerWaves(); this.override.mysteryEncounter(encounterType); } @@ -272,7 +271,7 @@ export class GameManager { ); await this.phaseInterceptor.to("EncounterPhase"); - if (!isNullOrUndefined(encounterType)) { + if (encounterType != null) { expect(this.scene.currentBattle?.mysteryEncounter?.encounterType).toBe(encounterType); } } diff --git a/test/test-utils/game-wrapper.ts b/test/test-utils/game-wrapper.ts index 166f4b8de7a..d18ea9301ea 100644 --- a/test/test-utils/game-wrapper.ts +++ b/test/test-utils/game-wrapper.ts @@ -12,7 +12,7 @@ import { MockLoader } from "#test/test-utils/mocks/mock-loader"; import { MockTextureManager } from "#test/test-utils/mocks/mock-texture-manager"; import { MockTimedEventManager } from "#test/test-utils/mocks/mock-timed-event-manager"; import { MockContainer } from "#test/test-utils/mocks/mocks-container/mock-container"; -import { PokedexMonContainer } from "#ui/containers/pokedex-mon-container"; +import { PokedexMonContainer } from "#ui/pokedex-mon-container"; import fs from "node:fs"; import Phaser from "phaser"; import { vi } from "vitest"; diff --git a/test/test-utils/helpers/daily-mode-helper.ts b/test/test-utils/helpers/daily-mode-helper.ts index 365c5df3627..ca882eaf548 100644 --- a/test/test-utils/helpers/daily-mode-helper.ts +++ b/test/test-utils/helpers/daily-mode-helper.ts @@ -7,7 +7,7 @@ import { EncounterPhase } from "#phases/encounter-phase"; import { TitlePhase } from "#phases/title-phase"; import { TurnInitPhase } from "#phases/turn-init-phase"; import { GameManagerHelper } from "#test/test-utils/helpers/game-manager-helper"; -import type { SaveSlotSelectUiHandler } from "#ui/handlers/save-slot-select-ui-handler"; +import type { SaveSlotSelectUiHandler } from "#ui/save-slot-select-ui-handler"; /** * Helper to handle daily mode specifics diff --git a/test/test-utils/phase-interceptor.ts b/test/test-utils/phase-interceptor.ts index 4ac5e1150e5..6c7b5bf3033 100644 --- a/test/test-utils/phase-interceptor.ts +++ b/test/test-utils/phase-interceptor.ts @@ -67,7 +67,7 @@ import { UnlockPhase } from "#phases/unlock-phase"; import { VictoryPhase } from "#phases/victory-phase"; import { ErrorInterceptor } from "#test/test-utils/error-interceptor"; import type { PhaseClass, PhaseString } from "#types/phase-types"; -import type { AwaitableUiHandler } from "#ui/handlers/awaitable-ui-handler"; +import type { AwaitableUiHandler } from "#ui/awaitable-ui-handler"; import { UI } from "#ui/ui"; export interface PromptHandler { @@ -111,8 +111,8 @@ export class PhaseInterceptor { private intervalRun: NodeJS.Timeout; private prompts: PromptHandler[]; private inProgress?: InProgressStub; - private originalSetMode: UI["setMode"]; - private originalSuperEnd: Phase["end"]; + private originalSetMode: typeof UI.prototype.setMode; + private originalSuperEnd: typeof Phase.prototype.end; /** * List of phases with their corresponding start methods. diff --git a/test/test-utils/setup/test-end-log.ts b/test/test-utils/setup/test-end-log.ts index 9814ba8a45c..5be8299b124 100644 --- a/test/test-utils/setup/test-end-log.ts +++ b/test/test-utils/setup/test-end-log.ts @@ -1,16 +1,16 @@ +/** + * Code to add markers to the beginning and end of tests. + * Intended for use with {@linkcode CustomDefaultReporter}, and placed inside test hooks + * (rather than as part of the reporter) to ensure Vitest waits for the log messages to be printed. + * @module + */ + // biome-ignore lint/correctness/noUnusedImports: TSDoc import type CustomDefaultReporter from "#test/test-utils/reporters/custom-default-reporter"; import { basename, join, relative } from "path"; import chalk from "chalk"; import type { RunnerTask, RunnerTaskResult, RunnerTestCase } from "vitest"; -/** - * @module - * Code to add markers to the beginning and end of tests. - * Intended for use with {@linkcode CustomDefaultReporter}, and placed inside test hooks - * (rather than as part of the reporter) to ensure Vitest waits for the log messages to be printed. - */ - /** A long string of "="s to partition off each test from one another. */ const TEST_END_BARRIER = chalk.bold.hex("#ff7c7cff")("=================="); diff --git a/test/ui/item-manage-button.test.ts b/test/ui/item-manage-button.test.ts index b5c24776e7b..c28cd9e802e 100644 --- a/test/ui/item-manage-button.test.ts +++ b/test/ui/item-manage-button.test.ts @@ -5,8 +5,8 @@ import { SpeciesId } from "#enums/species-id"; import { UiMode } from "#enums/ui-mode"; import type { Pokemon } from "#field/pokemon"; import { GameManager } from "#test/test-utils/game-manager"; -import type { ModifierSelectUiHandler } from "#ui/handlers/modifier-select-ui-handler"; -import { type PartyUiHandler, PartyUiMode } from "#ui/handlers/party-ui-handler"; +import type { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler"; +import { type PartyUiHandler, PartyUiMode } from "#ui/party-ui-handler"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; diff --git a/test/ui/pokedex.test.ts b/test/ui/pokedex.test.ts index 34539094232..6b84b253260 100644 --- a/test/ui/pokedex.test.ts +++ b/test/ui/pokedex.test.ts @@ -8,9 +8,9 @@ import { SpeciesId } from "#enums/species-id"; import { UiMode } from "#enums/ui-mode"; import { GameManager } from "#test/test-utils/game-manager"; import type { StarterAttributes } from "#types/save-data"; -import { FilterTextRow } from "#ui/containers/filter-text"; -import { PokedexPageUiHandler } from "#ui/handlers/pokedex-page-ui-handler"; -import { PokedexUiHandler } from "#ui/handlers/pokedex-ui-handler"; +import { FilterTextRow } from "#ui/filter-text"; +import { PokedexPageUiHandler } from "#ui/pokedex-page-ui-handler"; +import { PokedexUiHandler } from "#ui/pokedex-ui-handler"; import { getPokemonSpecies } from "#utils/pokemon-utils"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; diff --git a/test/ui/starter-select.test.ts b/test/ui/starter-select.test.ts index 2f575b72a5c..397f3d6086f 100644 --- a/test/ui/starter-select.test.ts +++ b/test/ui/starter-select.test.ts @@ -8,10 +8,10 @@ import { SpeciesId } from "#enums/species-id"; import { UiMode } from "#enums/ui-mode"; import type { TitlePhase } from "#phases/title-phase"; import { GameManager } from "#test/test-utils/game-manager"; -import type { OptionSelectItem } from "#ui/handlers/abstract-option-select-ui-handler"; -import type { SaveSlotSelectUiHandler } from "#ui/handlers/save-slot-select-ui-handler"; -import type { StarterSelectUiHandler } from "#ui/handlers/starter-select-ui-handler"; +import type { OptionSelectItem } from "#ui/abstract-option-select-ui-handler"; import type { OptionSelectUiHandler } from "#ui/option-select-ui-handler"; +import type { SaveSlotSelectUiHandler } from "#ui/save-slot-select-ui-handler"; +import type { StarterSelectUiHandler } from "#ui/starter-select-ui-handler"; import i18next from "i18next"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; diff --git a/test/ui/transfer-item-options.test.ts b/test/ui/transfer-item-options.test.ts index 7e9c1b5e36b..901aa261f50 100644 --- a/test/ui/transfer-item-options.test.ts +++ b/test/ui/transfer-item-options.test.ts @@ -4,8 +4,8 @@ import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import { UiMode } from "#enums/ui-mode"; import { GameManager } from "#test/test-utils/game-manager"; -import { type PartyUiHandler, PartyUiMode } from "#ui/handlers/party-ui-handler"; -import type { RenameFormUiHandler } from "#ui/handlers/rename-form-ui-handler"; +import { type PartyUiHandler, PartyUiMode } from "#ui/party-ui-handler"; +import type { RenameFormUiHandler } from "#ui/rename-form-ui-handler"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; diff --git a/test/ui/transfer-item.test.ts b/test/ui/transfer-item.test.ts index 67a21b0656d..c04c16623e3 100644 --- a/test/ui/transfer-item.test.ts +++ b/test/ui/transfer-item.test.ts @@ -4,8 +4,8 @@ import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import { UiMode } from "#enums/ui-mode"; import { GameManager } from "#test/test-utils/game-manager"; -import { ModifierSelectUiHandler } from "#ui/handlers/modifier-select-ui-handler"; -import { PartyUiHandler, PartyUiMode } from "#ui/handlers/party-ui-handler"; +import { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler"; +import { PartyUiHandler, PartyUiMode } from "#ui/party-ui-handler"; import Phaser from "phaser"; import type BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; diff --git a/test/ui/type-hints.test.ts b/test/ui/type-hints.test.ts index 56891e22c2a..b5fe0d9585a 100644 --- a/test/ui/type-hints.test.ts +++ b/test/ui/type-hints.test.ts @@ -4,7 +4,7 @@ import { SpeciesId } from "#enums/species-id"; import { UiMode } from "#enums/ui-mode"; import { GameManager } from "#test/test-utils/game-manager"; import type { MockText } from "#test/test-utils/mocks/mocks-container/mock-text"; -import { FightUiHandler } from "#ui/handlers/fight-ui-handler"; +import { FightUiHandler } from "#ui/fight-ui-handler"; import i18next from "i18next"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; diff --git a/tsconfig.json b/tsconfig.json index 7bf82eaaca0..131a0503ee3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -50,7 +50,14 @@ ], "#trainers/*": ["./src/data/trainers/*.ts"], "#types/*": ["./src/@types/helpers/*.ts", "./src/@types/*.ts", "./src/typings/phaser/*.ts"], - "#ui/*": ["./src/ui/battle-info/*.ts", "./src/ui/settings/*.ts", "./src/ui/*.ts"], + "#ui/*": [ + "./src/ui/battle-info/*.ts", + "./src/ui/containers/*.ts", + "./src/ui/handlers/*.ts", + "./src/ui/settings/*.ts", + "./src/ui/utils/*.ts", + "./src/ui/*.ts" + ], "#utils/*": ["./src/utils/*.ts"], "#data/*": ["./src/data/pokemon-forms/*.ts", "./src/data/pokemon/*.ts", "./src/data/*.ts"], "#test/*": ["./test/*.ts"], diff --git a/typedoc.config.js b/typedoc.config.js index fabe9f1c8c5..d9e880743ca 100644 --- a/typedoc.config.js +++ b/typedoc.config.js @@ -43,7 +43,7 @@ const config = { readme: "./README.md", coverageLabel: "Documented", coverageSvgWidth: 120, // Increased from 104 baseline due to adding 2 extra letters - favicon: "./public/images/logo.png", + favicon: "./favicon.ico", theme: "typedoc-github-theme", customFooterHtml: "

Copyright Pagefault Games 2025

", customFooterHtmlDisableWrapper: true,