Merge pull request #4170 from ben-lear/mystery-encounters-feedback
Mystery encounters feedback
30
.github/workflows/test-shard-template.yml
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
name: Test Template
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
project:
|
||||
required: true
|
||||
type: string
|
||||
shard:
|
||||
required: true
|
||||
type: number
|
||||
totalShards:
|
||||
required: true
|
||||
type: number
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Shard ${{ inputs.shard }} of ${{ inputs.totalShards }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- name: Install Node.js dependencies
|
||||
run: npm ci
|
||||
- name: Run tests
|
||||
run: npx vitest --project ${{ inputs.project }} --shard=${{ inputs.shard }}/${{ inputs.totalShards }} ${{ !runner.debug && '--silent' || '' }}
|
98
.github/workflows/tests.yml
vendored
@ -15,91 +15,33 @@ on:
|
||||
types: [checks_requested]
|
||||
|
||||
jobs:
|
||||
run-misc-tests: # Define a job named "run-tests"
|
||||
name: Run misc tests # Human-readable name for the job
|
||||
runs-on: ubuntu-latest # Specify the latest Ubuntu runner for the job
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository # Step to check out the repository
|
||||
uses: actions/checkout@v4 # Use the checkout action version 4
|
||||
|
||||
- name: Set up Node.js # Step to set up Node.js environment
|
||||
uses: actions/setup-node@v4 # Use the setup-node action version 4
|
||||
with:
|
||||
node-version: 20 # Specify Node.js version 20
|
||||
|
||||
- name: Install Node.js dependencies # Step to install Node.js dependencies
|
||||
run: npm ci # Use 'npm ci' to install dependencies
|
||||
|
||||
- name: pre-test # pre-test to check overrides
|
||||
run: npx vitest run --project pre
|
||||
- name: test misc
|
||||
run: npx vitest --project misc
|
||||
|
||||
run-abilities-tests:
|
||||
name: Run abilities tests
|
||||
runs-on: ubuntu-latest
|
||||
pre-test:
|
||||
name: Run Pre-test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: tests-action
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- name: Install Node.js dependencies
|
||||
working-directory: tests-action
|
||||
run: npm ci
|
||||
- name: pre-test
|
||||
run: npx vitest run --project pre
|
||||
- name: test abilities
|
||||
run: npx vitest --project abilities
|
||||
- name: Run Pre-test
|
||||
working-directory: tests-action
|
||||
run: npx vitest run --project pre ${{ !runner.debug && '--silent' || '' }}
|
||||
|
||||
run-items-tests:
|
||||
name: Run items tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- name: Install Node.js dependencies
|
||||
run: npm ci
|
||||
- name: pre-test
|
||||
run: npx vitest run --project pre
|
||||
- name: test items
|
||||
run: npx vitest --project items
|
||||
|
||||
run-moves-tests:
|
||||
name: Run moves tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- name: Install Node.js dependencies
|
||||
run: npm ci
|
||||
- name: pre-test
|
||||
run: npx vitest run --project pre
|
||||
- name: test moves
|
||||
run: npx vitest --project moves
|
||||
|
||||
run-battle-tests:
|
||||
name: Run battle tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- name: Install Node.js dependencies
|
||||
run: npm ci
|
||||
- name: pre-test
|
||||
run: npx vitest run --project pre
|
||||
- name: test battle
|
||||
run: npx vitest --project battle
|
||||
run-tests:
|
||||
name: Run Tests
|
||||
needs: [pre-test]
|
||||
strategy:
|
||||
matrix:
|
||||
shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
uses: ./.github/workflows/test-shard-template.yml
|
||||
with:
|
||||
project: main
|
||||
shard: ${{ matrix.shard }}
|
||||
totalShards: 10
|
@ -1,7 +1,7 @@
|
||||
import tseslint from '@typescript-eslint/eslint-plugin';
|
||||
import stylisticTs from '@stylistic/eslint-plugin-ts'
|
||||
import parser from '@typescript-eslint/parser';
|
||||
// import imports from 'eslint-plugin-import'; // Disabled due to not being compatible with eslint v9
|
||||
import importX from 'eslint-plugin-import-x';
|
||||
|
||||
export default [
|
||||
{
|
||||
@ -11,7 +11,7 @@ export default [
|
||||
parser: parser
|
||||
},
|
||||
plugins: {
|
||||
// imports: imports.configs.recommended // Disabled due to not being compatible with eslint v9
|
||||
"import-x": importX,
|
||||
'@stylistic/ts': stylisticTs,
|
||||
'@typescript-eslint': tseslint
|
||||
},
|
||||
@ -39,7 +39,8 @@ export default [
|
||||
}],
|
||||
"space-before-blocks": ["error", "always"], // Enforces a space before blocks
|
||||
"keyword-spacing": ["error", { "before": true, "after": true }], // Enforces spacing before and after keywords
|
||||
"comma-spacing": ["error", { "before": false, "after": true }] // Enforces spacing after comma
|
||||
"comma-spacing": ["error", { "before": false, "after": true }], // Enforces spacing after comma
|
||||
"import-x/extensions": ["error", "never", { "json": "always" }], // Enforces no extension for imports unless json
|
||||
}
|
||||
}
|
||||
]
|
||||
|
200
package-lock.json
generated
@ -28,6 +28,7 @@
|
||||
"@vitest/coverage-istanbul": "^2.0.4",
|
||||
"dependency-cruiser": "^16.3.10",
|
||||
"eslint": "^9.7.0",
|
||||
"eslint-plugin-import-x": "^4.2.1",
|
||||
"jsdom": "^24.0.0",
|
||||
"lefthook": "^1.6.12",
|
||||
"phaser3spectorjs": "^0.0.8",
|
||||
@ -2505,6 +2506,19 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/doctrine": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
|
||||
"integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"esutils": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eastasianwidth": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||
@ -2687,6 +2701,155 @@
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-import-resolver-node": {
|
||||
"version": "0.3.9",
|
||||
"resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz",
|
||||
"integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "^3.2.7",
|
||||
"is-core-module": "^2.13.0",
|
||||
"resolve": "^1.22.4"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-import-resolver-node/node_modules/debug": {
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
|
||||
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-import-x": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.2.1.tgz",
|
||||
"integrity": "sha512-WWi2GedccIJa0zXxx3WDnTgouGQTtdYK1nhXMwywbqqAgB0Ov+p1pYBsWh3VaB0bvBOwLse6OfVII7jZD9xo5Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/utils": "^8.1.0",
|
||||
"debug": "^4.3.4",
|
||||
"doctrine": "^3.0.0",
|
||||
"eslint-import-resolver-node": "^0.3.9",
|
||||
"get-tsconfig": "^4.7.3",
|
||||
"is-glob": "^4.0.3",
|
||||
"minimatch": "^9.0.3",
|
||||
"semver": "^7.6.3",
|
||||
"stable-hash": "^0.0.4",
|
||||
"tslib": "^2.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-import-x/node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.5.0.tgz",
|
||||
"integrity": "sha512-06JOQ9Qgj33yvBEx6tpC8ecP9o860rsR22hWMEd12WcTRrfaFgHr2RB/CA/B+7BMhHkXT4chg2MyboGdFGawYg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.5.0",
|
||||
"@typescript-eslint/visitor-keys": "8.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-import-x/node_modules/@typescript-eslint/types": {
|
||||
"version": "8.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.5.0.tgz",
|
||||
"integrity": "sha512-qjkormnQS5wF9pjSi6q60bKUHH44j2APxfh9TQRXK8wbYVeDYYdYJGIROL87LGZZ2gz3Rbmjc736qyL8deVtdw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-import-x/node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.5.0.tgz",
|
||||
"integrity": "sha512-vEG2Sf9P8BPQ+d0pxdfndw3xIXaoSjliG0/Ejk7UggByZPKXmJmw3GW5jV2gHNQNawBUyfahoSiCFVov0Ruf7Q==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.5.0",
|
||||
"@typescript-eslint/visitor-keys": "8.5.0",
|
||||
"debug": "^4.3.4",
|
||||
"fast-glob": "^3.3.2",
|
||||
"is-glob": "^4.0.3",
|
||||
"minimatch": "^9.0.4",
|
||||
"semver": "^7.6.0",
|
||||
"ts-api-utils": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-import-x/node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.5.0.tgz",
|
||||
"integrity": "sha512-6yyGYVL0e+VzGYp60wvkBHiqDWOpT63pdMV2CVG4LVDd5uR6q1qQN/7LafBZtAtNIn/mqXjsSeS5ggv/P0iECw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.4.0",
|
||||
"@typescript-eslint/scope-manager": "8.5.0",
|
||||
"@typescript-eslint/types": "8.5.0",
|
||||
"@typescript-eslint/typescript-estree": "8.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-import-x/node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.5.0.tgz",
|
||||
"integrity": "sha512-yTPqMnbAZJNy2Xq2XU8AdtOW9tJIr+UQb64aXB9f3B1498Zx9JorVgFJcZpEc9UBuCCrdzKID2RGAMkYcDtZOw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.5.0",
|
||||
"eslint-visitor-keys": "^3.4.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-scope": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz",
|
||||
@ -3143,6 +3306,19 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/get-tsconfig": {
|
||||
"version": "4.8.0",
|
||||
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.0.tgz",
|
||||
"integrity": "sha512-Pgba6TExTZ0FJAn1qkJAjIeKoDJ3CsI2ChuLohJnZl/tTU8MVrq3b+2t5UOPfRa4RMsorClBjJALkJUMjG1PAw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"resolve-pkg-maps": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/glob-parent": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||
@ -4854,6 +5030,16 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-pkg-maps": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
|
||||
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/reusify": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
|
||||
@ -5069,6 +5255,13 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/stable-hash": {
|
||||
"version": "0.0.4",
|
||||
"resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.4.tgz",
|
||||
"integrity": "sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/stackback": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
|
||||
@ -5460,6 +5653,13 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
|
||||
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
|
||||
"dev": true,
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/type-check": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||
|
@ -32,6 +32,7 @@
|
||||
"@vitest/coverage-istanbul": "^2.0.4",
|
||||
"dependency-cruiser": "^16.3.10",
|
||||
"eslint": "^9.7.0",
|
||||
"eslint-plugin-import-x": "^4.2.1",
|
||||
"jsdom": "^24.0.0",
|
||||
"lefthook": "^1.6.12",
|
||||
"phaser3spectorjs": "^0.0.8",
|
||||
|
BIN
public/images/events/egg-update_de.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
public/images/events/egg-update_en.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
public/images/events/egg-update_es.png
Normal file
After Width: | Height: | Size: 198 KiB |
BIN
public/images/events/egg-update_fr.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
public/images/events/egg-update_it.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
public/images/events/egg-update_ja.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
public/images/events/egg-update_ko.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
public/images/events/egg-update_pt-BR.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
public/images/events/egg-update_zh-CN.png
Normal file
After Width: | Height: | Size: 51 KiB |
@ -2287,8 +2287,14 @@ export default class BattleScene extends SceneBase {
|
||||
return true;
|
||||
}
|
||||
|
||||
findPhase(phaseFilter: (phase: Phase) => boolean): Phase | undefined {
|
||||
return this.phaseQueue.find(phaseFilter);
|
||||
/**
|
||||
* Find a specific {@linkcode Phase} in the phase queue.
|
||||
*
|
||||
* @param phaseFilter filter function to use to find the wanted phase
|
||||
* @returns the found phase or undefined if none found
|
||||
*/
|
||||
findPhase<P extends Phase = Phase>(phaseFilter: (phase: P) => boolean): P | undefined {
|
||||
return this.phaseQueue.find(phaseFilter) as P;
|
||||
}
|
||||
|
||||
tryReplacePhase(phaseFilter: (phase: Phase) => boolean, phase: Phase): boolean {
|
||||
@ -2875,20 +2881,20 @@ export default class BattleScene extends SceneBase {
|
||||
const keys: string[] = [];
|
||||
const playerParty = this.getParty();
|
||||
playerParty.forEach(p => {
|
||||
keys.push("pkmn__" + p.species.getSpriteId(p.gender === Gender.FEMALE, p.species.formIndex, p.shiny, p.variant));
|
||||
keys.push("pkmn__" + p.species.getSpriteId(p.gender === Gender.FEMALE, p.species.formIndex, p.shiny, p.variant, true));
|
||||
keys.push("cry/" + p.species.getCryKey(p.species.formIndex));
|
||||
if (p.fusionSpecies && p.getSpeciesForm() !== p.getFusionSpeciesForm()) {
|
||||
keys.push("cry/"+p.getFusionSpeciesForm().getCryKey(p.fusionSpecies.formIndex));
|
||||
keys.push(p.getSpriteKey(true));
|
||||
keys.push(p.getBattleSpriteKey(true, true));
|
||||
keys.push("cry/" + p.species.getCryKey(p.formIndex));
|
||||
if (p.fusionSpecies) {
|
||||
keys.push("cry/"+p.fusionSpecies.getCryKey(p.fusionFormIndex));
|
||||
}
|
||||
});
|
||||
// enemyParty has to be operated on separately from playerParty because playerPokemon =/= enemyPokemon
|
||||
const enemyParty = this.getEnemyParty();
|
||||
enemyParty.forEach(p => {
|
||||
keys.push(p.species.getSpriteKey(p.gender === Gender.FEMALE, p.species.formIndex, p.shiny, p.variant));
|
||||
keys.push("cry/" + p.species.getCryKey(p.species.formIndex));
|
||||
if (p.fusionSpecies && p.getSpeciesForm() !== p.getFusionSpeciesForm()) {
|
||||
keys.push("cry/"+p.getFusionSpeciesForm().getCryKey(p.fusionSpecies.formIndex));
|
||||
keys.push(p.getSpriteKey(true));
|
||||
keys.push("cry/" + p.species.getCryKey(p.formIndex));
|
||||
if (p.fusionSpecies) {
|
||||
keys.push("cry/"+p.fusionSpecies.getCryKey(p.fusionFormIndex));
|
||||
}
|
||||
});
|
||||
return keys;
|
||||
|
@ -1689,8 +1689,7 @@ export const trainerTypeDialogue: TrainerTypeDialogue = {
|
||||
"dialogue:roark.victory.1",
|
||||
"dialogue:roark.victory.2",
|
||||
"dialogue:roark.victory.3",
|
||||
"dialogue:roark.victory.4",
|
||||
"dialogue:roark.victory.5"
|
||||
"dialogue:roark.victory.4"
|
||||
],
|
||||
defeat: [
|
||||
"dialogue:roark.defeat.1",
|
||||
|
@ -229,7 +229,7 @@ export class Egg {
|
||||
|
||||
let pokemonSpecies = getPokemonSpecies(this._species);
|
||||
// Special condition to have Phione eggs also have a chance of generating Manaphy
|
||||
if (this._species === Species.PHIONE) {
|
||||
if (this._species === Species.PHIONE && this._sourceType === EggSourceType.SAME_SPECIES_EGG) {
|
||||
pokemonSpecies = getPokemonSpecies(Utils.randSeedInt(MANAPHY_EGG_MANAPHY_RATE) ? Species.PHIONE : Species.MANAPHY);
|
||||
}
|
||||
|
||||
@ -335,7 +335,8 @@ export class Egg {
|
||||
break;
|
||||
}
|
||||
|
||||
return Utils.randSeedInt(baseChance * Math.pow(2, 3 - this.tier)) ? Utils.randSeedInt(3) : 3;
|
||||
const tierMultiplier = this.isManaphyEgg() ? 2 : Math.pow(2, 3 - this.tier);
|
||||
return Utils.randSeedInt(baseChance * tierMultiplier) ? Utils.randSeedInt(3) : 3;
|
||||
}
|
||||
|
||||
private getEggTierDefaultHatchWaves(eggTier?: EggTier): number {
|
||||
@ -370,7 +371,12 @@ export class Egg {
|
||||
* the species that was the legendary focus at the time
|
||||
*/
|
||||
if (this.isManaphyEgg()) {
|
||||
const rand = Utils.randSeedInt(MANAPHY_EGG_MANAPHY_RATE);
|
||||
/**
|
||||
* Adding a technicality to make unit tests easier: By making this check pass
|
||||
* when Utils.randSeedInt(8) = 1, and by making the generatePlayerPokemon() species
|
||||
* check pass when Utils.randSeedInt(8) = 0, we can tell them apart during tests.
|
||||
*/
|
||||
const rand = (Utils.randSeedInt(MANAPHY_EGG_MANAPHY_RATE) !== 1);
|
||||
return rand ? Species.PHIONE : Species.MANAPHY;
|
||||
} else if (this.tier === EggTier.MASTER
|
||||
&& this._sourceType === EggSourceType.GACHA_LEGENDARY) {
|
||||
|
@ -6272,12 +6272,42 @@ export class VariableTargetAttr extends MoveAttr {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attribute for {@linkcode Moves.AFTER_YOU}
|
||||
*
|
||||
* [After You - Move | Bulbapedia](https://bulbapedia.bulbagarden.net/wiki/After_You_(move))
|
||||
*/
|
||||
export class AfterYouAttr extends MoveEffectAttr {
|
||||
/**
|
||||
* Allows the target of this move to act right after the user.
|
||||
*
|
||||
* @param user {@linkcode Pokemon} that is using the move.
|
||||
* @param target {@linkcode Pokemon} that will move right after this move is used.
|
||||
* @param move {@linkcode Move} {@linkcode Moves.AFTER_YOU}
|
||||
* @param _args N/A
|
||||
* @returns true
|
||||
*/
|
||||
override apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean {
|
||||
user.scene.queueMessage(i18next.t("moveTriggers:afterYou", {targetName: getPokemonNameWithAffix(target)}));
|
||||
|
||||
//Will find next acting phase of the targeted pokémon, delete it and queue it next on successful delete.
|
||||
const nextAttackPhase = target.scene.findPhase<MovePhase>((phase) => phase.pokemon === target);
|
||||
if (nextAttackPhase && target.scene.tryRemovePhase((phase: MovePhase) => phase.pokemon === target)) {
|
||||
target.scene.prependToPhase(new MovePhase(target.scene, target, [...nextAttackPhase.targets], nextAttackPhase.move), MovePhase);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const failOnGravityCondition: MoveConditionFunc = (user, target, move) => !user.scene.arena.getTag(ArenaTagType.GRAVITY);
|
||||
|
||||
const failOnBossCondition: MoveConditionFunc = (user, target, move) => !target.isBossImmune();
|
||||
|
||||
const failOnMaxCondition: MoveConditionFunc = (user, target, move) => !target.isMax();
|
||||
|
||||
const failIfSingleBattle: MoveConditionFunc = (user, target, move) => user.scene.currentBattle.double;
|
||||
|
||||
const failIfDampCondition: MoveConditionFunc = (user, target, move) => {
|
||||
const cancelled = new Utils.BooleanHolder(false);
|
||||
user.scene.getField(true).map(p=>applyAbAttrs(FieldPreventExplosiveMovesAbAttr, p, cancelled));
|
||||
@ -7925,7 +7955,10 @@ export function initMoves() {
|
||||
.attr(AbilityGiveAttr),
|
||||
new StatusMove(Moves.AFTER_YOU, Type.NORMAL, -1, 15, -1, 0, 5)
|
||||
.ignoresProtect()
|
||||
.unimplemented(),
|
||||
.target(MoveTarget.NEAR_OTHER)
|
||||
.condition(failIfSingleBattle)
|
||||
.condition((user, target, move) => !target.turnData.acted)
|
||||
.attr(AfterYouAttr),
|
||||
new AttackMove(Moves.ROUND, Type.NORMAL, MoveCategory.SPECIAL, 60, 100, 15, -1, 0, 5)
|
||||
.soundBased()
|
||||
.partial(),
|
||||
|
@ -486,7 +486,7 @@ function doBerrySpritePile(scene: BattleScene, isEat: boolean = false) {
|
||||
});
|
||||
}
|
||||
|
||||
function doBerryBounce(scene: BattleScene, berrySprites: Phaser.GameObjects.Sprite[], yd: number, baseBounceDuration: integer) {
|
||||
function doBerryBounce(scene: BattleScene, berrySprites: Phaser.GameObjects.Sprite[], yd: number, baseBounceDuration: number) {
|
||||
let bouncePower = 1;
|
||||
let bounceYOffset = yd;
|
||||
|
||||
|
@ -582,7 +582,7 @@ function getTrainerConfigForWave(waveIndex: number) {
|
||||
}
|
||||
|
||||
function getRandomPartyMemberFunc(speciesPool: Species[], trainerSlot: TrainerSlot = TrainerSlot.TRAINER, ignoreEvolution: boolean = false, postProcess?: (enemyPokemon: EnemyPokemon) => void) {
|
||||
return (scene: BattleScene, level: integer, strength: PartyMemberStrength) => {
|
||||
return (scene: BattleScene, level: number, strength: PartyMemberStrength) => {
|
||||
let species = Utils.randSeedItem(speciesPool);
|
||||
if (!ignoreEvolution) {
|
||||
species = getPokemonSpecies(species).getTrainerSpeciesForLevel(level, true, strength);
|
||||
|
@ -382,7 +382,7 @@ async function handleSwapAbility(scene: BattleScene) {
|
||||
}
|
||||
|
||||
function displayYesNoOptions(scene: BattleScene, resolve) {
|
||||
showEncounterText(scene, `${namespace}.option.1.ability_prompt`, 500, false);
|
||||
showEncounterText(scene, `${namespace}.option.1.ability_prompt`, null, 500, false);
|
||||
const fullOptions = [
|
||||
{
|
||||
label: i18next.t("menu:yes"),
|
||||
@ -429,7 +429,7 @@ function onYesAbilitySwap(scene: BattleScene, resolve) {
|
||||
selectPokemonForOption(scene, onPokemonSelected, onPokemonNotSelected);
|
||||
}
|
||||
|
||||
function generateItemsOfTier(scene: BattleScene, pokemon: PlayerPokemon, numItems: integer, tier: ModifierTier | "Berries") {
|
||||
function generateItemsOfTier(scene: BattleScene, pokemon: PlayerPokemon, numItems: number, tier: ModifierTier | "Berries") {
|
||||
// These pools have to be defined at runtime so that modifierTypes exist
|
||||
// Pools have instances of the modifier type equal to the max stacks that modifier can be applied to any one pokemon
|
||||
// This is to prevent "over-generating" a random item of a certain type during item swaps
|
||||
|
@ -122,7 +122,7 @@ export const DelibirdyEncounter: MysteryEncounter =
|
||||
const shellBell = generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
|
||||
await applyModifierTypeToPlayerPokemon(scene, scene.getParty()[0], shellBell);
|
||||
scene.playSound("item_fanfare");
|
||||
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), undefined, true);
|
||||
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true);
|
||||
} else {
|
||||
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.ABILITY_CHARM));
|
||||
}
|
||||
@ -197,7 +197,7 @@ export const DelibirdyEncounter: MysteryEncounter =
|
||||
const shellBell = generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
|
||||
await applyModifierTypeToPlayerPokemon(scene, scene.getParty()[0], shellBell);
|
||||
scene.playSound("item_fanfare");
|
||||
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), undefined, true);
|
||||
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true);
|
||||
} else {
|
||||
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.CANDY_JAR));
|
||||
}
|
||||
@ -210,7 +210,7 @@ export const DelibirdyEncounter: MysteryEncounter =
|
||||
const shellBell = generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
|
||||
await applyModifierTypeToPlayerPokemon(scene, scene.getParty()[0], shellBell);
|
||||
scene.playSound("item_fanfare");
|
||||
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), undefined, true);
|
||||
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true);
|
||||
} else {
|
||||
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.HEALING_CHARM));
|
||||
}
|
||||
@ -290,7 +290,7 @@ export const DelibirdyEncounter: MysteryEncounter =
|
||||
const shellBell = generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
|
||||
await applyModifierTypeToPlayerPokemon(scene, scene.getParty()[0], shellBell);
|
||||
scene.playSound("item_fanfare");
|
||||
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), undefined, true);
|
||||
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true);
|
||||
} else {
|
||||
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.BERRY_POUCH));
|
||||
}
|
||||
|
@ -157,7 +157,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
|
||||
const formName = tradePokemon.species.forms?.[pokemon.formIndex]?.formName;
|
||||
const line1 = i18next.t("pokemonInfoContainer:ability") + " " + tradePokemon.getAbility().name + (tradePokemon.getGender() !== Gender.GENDERLESS ? " | " + i18next.t("pokemonInfoContainer:gender") + " " + getGenderSymbol(tradePokemon.getGender()) : "");
|
||||
const line2 = i18next.t("pokemonInfoContainer:nature") + " " + getNatureName(tradePokemon.getNature()) + (formName ? " | " + i18next.t("pokemonInfoContainer:form") + " " + formName : "");
|
||||
scene.ui.showText(`${line1}\n${line2}`, 0);
|
||||
showEncounterText(scene, `${line1}\n${line2}`, 0);
|
||||
},
|
||||
};
|
||||
return option;
|
||||
@ -195,7 +195,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
|
||||
// Show the trade animation
|
||||
await showTradeBackground(scene);
|
||||
await doPokemonTradeSequence(scene, tradedPokemon, newPlayerPokemon);
|
||||
await showEncounterText(scene, `${namespace}.trade_received`, 0, true, 4000);
|
||||
await showEncounterText(scene, `${namespace}.trade_received`, null, 0, true, 4000);
|
||||
scene.playBgm("mystery_encounter_gts");
|
||||
await hideTradeBackground(scene);
|
||||
tradedPokemon.destroy();
|
||||
@ -278,7 +278,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
|
||||
// Show the trade animation
|
||||
await showTradeBackground(scene);
|
||||
await doPokemonTradeSequence(scene, tradedPokemon, newPlayerPokemon);
|
||||
await showEncounterText(scene, `${namespace}.trade_received`, 0, true, 4000);
|
||||
await showEncounterText(scene, `${namespace}.trade_received`, null, 0, true, 4000);
|
||||
scene.playBgm("mystery_encounter_gts");
|
||||
await hideTradeBackground(scene);
|
||||
tradedPokemon.destroy();
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { getPokemonSpecies } from "#app/data/pokemon-species.js";
|
||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import { Moves } from "#app/enums/moves";
|
||||
import { Species } from "#app/enums/species.js";
|
||||
import { Species } from "#app/enums/species";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
|
||||
|
@ -2,7 +2,7 @@ import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-enco
|
||||
import { EnemyPartyConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { getHighestLevelPlayerPokemon, koPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
||||
import { randSeedInt } from "#app/utils.js";
|
||||
import { randSeedInt } from "#app/utils";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
|
||||
|
@ -175,9 +175,9 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
|
||||
// 80% chance to increase flee stage +1
|
||||
const fleeChangeResult = tryChangeFleeStage(scene, 1, 8);
|
||||
if (!fleeChangeResult) {
|
||||
await showEncounterText(scene, getEncounterText(scene, `${namespace}.safari.busy_eating`) ?? "", 1000, false );
|
||||
await showEncounterText(scene, getEncounterText(scene, `${namespace}.safari.busy_eating`) ?? "", null, 1000, false );
|
||||
} else {
|
||||
await showEncounterText(scene, getEncounterText(scene, `${namespace}.safari.eating`) ?? "", 1000, false);
|
||||
await showEncounterText(scene, getEncounterText(scene, `${namespace}.safari.eating`) ?? "", null, 1000, false);
|
||||
}
|
||||
|
||||
await doEndTurn(scene, 1);
|
||||
@ -204,9 +204,9 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
|
||||
// 80% chance to decrease catch stage -1
|
||||
const catchChangeResult = tryChangeCatchStage(scene, -1, 8);
|
||||
if (!catchChangeResult) {
|
||||
await showEncounterText(scene, getEncounterText(scene, `${namespace}.safari.beside_itself_angry`) ?? "", 1000, false );
|
||||
await showEncounterText(scene, getEncounterText(scene, `${namespace}.safari.beside_itself_angry`) ?? "", null, 1000, false );
|
||||
} else {
|
||||
await showEncounterText(scene, getEncounterText(scene, `${namespace}.safari.angry`) ?? "", 1000, false );
|
||||
await showEncounterText(scene, getEncounterText(scene, `${namespace}.safari.angry`) ?? "", null, 1000, false );
|
||||
}
|
||||
|
||||
await doEndTurn(scene, 2);
|
||||
@ -291,7 +291,7 @@ async function summonSafariPokemon(scene: BattleScene) {
|
||||
scene.unshiftPhase(new SummonPhase(scene, 0, false));
|
||||
|
||||
encounter.setDialogueToken("pokemonName", getPokemonNameWithAffix(pokemon));
|
||||
showEncounterText(scene, getEncounterText(scene, "battle:singleWildAppeared") ?? "", 1500, false)
|
||||
showEncounterText(scene, getEncounterText(scene, "battle:singleWildAppeared") ?? "", null, 1500, false)
|
||||
.then(() => {
|
||||
const ivScannerModifier = scene.findModifier(m => m instanceof IvScannerModifier);
|
||||
if (ivScannerModifier) {
|
||||
|
@ -156,7 +156,7 @@ export const TheStrongStuffEncounter: MysteryEncounter =
|
||||
|
||||
encounter.setDialogueToken("reductionValue", HIGH_BST_REDUCTION_VALUE.toString());
|
||||
encounter.setDialogueToken("increaseValue", BST_INCREASE_VALUE.toString());
|
||||
await showEncounterText(scene, `${namespace}.option.1.selected_2`, undefined, true);
|
||||
await showEncounterText(scene, `${namespace}.option.1.selected_2`, null, undefined, true);
|
||||
|
||||
setEncounterRewards(scene, { fillRemaining: true });
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
|
@ -13,7 +13,7 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
|
||||
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
|
||||
import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { getEncounterText, queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import HeldModifierConfig from "#app/interfaces/held-modifier-config";
|
||||
@ -286,27 +286,33 @@ export const TrainingSessionEncounter: MysteryEncounter =
|
||||
? pokemon.getFusionSpeciesForm()
|
||||
: pokemon.getSpeciesForm();
|
||||
const abilityCount = speciesForm.getAbilityCount();
|
||||
const abilities = new Array(abilityCount)
|
||||
const abilities: Ability[] = new Array(abilityCount)
|
||||
.fill(null)
|
||||
.map((val, i) => allAbilities[speciesForm.getAbility(i)]);
|
||||
return abilities.map((ability: Ability, index) => {
|
||||
const option: OptionSelectItem = {
|
||||
label: ability.name,
|
||||
handler: () => {
|
||||
// Pokemon and ability selected
|
||||
encounter.setDialogueToken("ability", ability.name);
|
||||
encounter.misc = {
|
||||
playerPokemon: pokemon,
|
||||
abilityIndex: index,
|
||||
};
|
||||
return true;
|
||||
},
|
||||
onHover: () => {
|
||||
scene.ui.showText(ability.description);
|
||||
},
|
||||
};
|
||||
return option;
|
||||
|
||||
const optionSelectItems: OptionSelectItem[] = [];
|
||||
abilities.forEach((ability: Ability, index) => {
|
||||
if (!optionSelectItems.some(o => o.label === ability.name)) {
|
||||
const option: OptionSelectItem = {
|
||||
label: ability.name,
|
||||
handler: () => {
|
||||
// Pokemon and ability selected
|
||||
encounter.setDialogueToken("ability", ability.name);
|
||||
encounter.misc = {
|
||||
playerPokemon: pokemon,
|
||||
abilityIndex: index,
|
||||
};
|
||||
return true;
|
||||
},
|
||||
onHover: () => {
|
||||
showEncounterText(scene, ability.description, 0);
|
||||
},
|
||||
};
|
||||
optionSelectItems.push(option);
|
||||
}
|
||||
});
|
||||
|
||||
return optionSelectItems;
|
||||
};
|
||||
|
||||
// Only Pokemon that are not KOed/legal can be trained
|
||||
|
@ -179,7 +179,7 @@ async function tryApplyDigRewardItems(scene: BattleScene) {
|
||||
}
|
||||
|
||||
scene.playSound("item_fanfare");
|
||||
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: "2 " + leftovers.name }), undefined, true);
|
||||
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: "2 " + leftovers.name }), null, undefined, true);
|
||||
|
||||
// First Shell bell
|
||||
for (const pokemon of party) {
|
||||
@ -206,7 +206,7 @@ async function tryApplyDigRewardItems(scene: BattleScene) {
|
||||
}
|
||||
|
||||
scene.playSound("item_fanfare");
|
||||
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: "2 " + shellBell.name }), undefined, true);
|
||||
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: "2 " + shellBell.name }), null, undefined, true);
|
||||
}
|
||||
|
||||
async function doGarbageDig(scene: BattleScene) {
|
||||
|
@ -22,7 +22,17 @@ export interface EncounterRequirement {
|
||||
}
|
||||
|
||||
export abstract class EncounterSceneRequirement implements EncounterRequirement {
|
||||
/**
|
||||
* Returns whether the EncounterSceneRequirement's... requirements, are met by the given scene
|
||||
* @param partyPokemon
|
||||
*/
|
||||
abstract meetsRequirement(scene: BattleScene): boolean;
|
||||
/**
|
||||
* Returns a dialogue token key/value pair for a given Requirement.
|
||||
* Should be overridden by child Requirement classes.
|
||||
* @param scene
|
||||
* @param pokemon
|
||||
*/
|
||||
abstract getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string];
|
||||
}
|
||||
|
||||
@ -34,7 +44,7 @@ export class CombinationSceneRequirement extends EncounterSceneRequirement {
|
||||
this.orRequirements = orRequirements;
|
||||
}
|
||||
|
||||
meetsRequirement(scene: BattleScene): boolean {
|
||||
override meetsRequirement(scene: BattleScene): boolean {
|
||||
for (const req of this.orRequirements) {
|
||||
if (req.meetsRequirement(scene)) {
|
||||
return true;
|
||||
@ -58,6 +68,10 @@ export abstract class EncounterPokemonRequirement implements EncounterRequiremen
|
||||
public minNumberOfPokemon: number;
|
||||
public invertQuery: boolean;
|
||||
|
||||
/**
|
||||
* Returns whether the EncounterPokemonRequirement's... requirements, are met by the given scene
|
||||
* @param partyPokemon
|
||||
*/
|
||||
abstract meetsRequirement(scene: BattleScene): boolean;
|
||||
|
||||
/**
|
||||
@ -66,6 +80,12 @@ export abstract class EncounterPokemonRequirement implements EncounterRequiremen
|
||||
*/
|
||||
abstract queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[];
|
||||
|
||||
/**
|
||||
* Returns a dialogue token key/value pair for a given Requirement.
|
||||
* Should be overridden by child Requirement classes.
|
||||
* @param scene
|
||||
* @param pokemon
|
||||
*/
|
||||
abstract getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string];
|
||||
}
|
||||
|
||||
|
@ -281,11 +281,17 @@ export default class MysteryEncounter implements IMysteryEncounter {
|
||||
return sceneReq && secReqs && priReqs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a specific player pokemon meets all given primary EncounterPokemonRequirements
|
||||
* Used automatically as part of {@linkcode meetsRequirements}, but can also be used to manually check certain Pokemon where needed
|
||||
* @param scene
|
||||
* @param pokemon
|
||||
*/
|
||||
pokemonMeetsPrimaryRequirements(scene: BattleScene, pokemon: Pokemon) {
|
||||
return !this.primaryPokemonRequirements.some(req => !req.queryParty(scene.getParty()).map(p => p.id).includes(pokemon.id));
|
||||
}
|
||||
|
||||
meetsPrimaryRequirementAndPrimaryPokemonSelected(scene: BattleScene): boolean {
|
||||
private meetsPrimaryRequirementAndPrimaryPokemonSelected(scene: BattleScene): boolean {
|
||||
if (!this.primaryPokemonRequirements || this.primaryPokemonRequirements.length === 0) {
|
||||
const activeMon = scene.getParty().filter(p => p.isActive(true));
|
||||
if (activeMon.length > 0) {
|
||||
@ -342,7 +348,7 @@ export default class MysteryEncounter implements IMysteryEncounter {
|
||||
}
|
||||
}
|
||||
|
||||
meetsSecondaryRequirementAndSecondaryPokemonSelected(scene: BattleScene): boolean {
|
||||
private meetsSecondaryRequirementAndSecondaryPokemonSelected(scene: BattleScene): boolean {
|
||||
if (!this.secondaryPokemonRequirements || this.secondaryPokemonRequirements.length === 0) {
|
||||
this.secondaryPokemon = [];
|
||||
return true;
|
||||
@ -446,6 +452,14 @@ export default class MysteryEncounter implements IMysteryEncounter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to cache a dialogue token for the encounter.
|
||||
* Tokens will be auto-injected via the `{{key}}` pattern with `value`,
|
||||
* when using the {@link showEncounterText} and {@link showEncounterDialogue} helper functions.
|
||||
*
|
||||
* @param key
|
||||
* @param value
|
||||
*/
|
||||
setDialogueToken(key: string, value: string): void {
|
||||
this.dialogueTokens[key] = value;
|
||||
}
|
||||
|
@ -4,6 +4,14 @@ import { UiTheme } from "#enums/ui-theme";
|
||||
import { isNullOrUndefined } from "#app/utils";
|
||||
import i18next from "i18next";
|
||||
|
||||
/**
|
||||
* Will inject all relevant dialogue tokens that exist in the {@link BattleScene.currentBattle.mysteryEncounter.dialogueTokens}, into i18n text.
|
||||
* Also adds BBCodeText fragments for colored text, if applicable
|
||||
* @param scene
|
||||
* @param keyOrString
|
||||
* @param primaryStyle - can define a text style to be applied to the entire string. Must be defined for BBCodeText styles to be applied correctly
|
||||
* @param uiTheme
|
||||
*/
|
||||
export function getEncounterText(scene: BattleScene, keyOrString?: string, primaryStyle?: TextStyle, uiTheme: UiTheme = UiTheme.DEFAULT): string | null {
|
||||
if (isNullOrUndefined(keyOrString)) {
|
||||
return null;
|
||||
@ -20,6 +28,11 @@ export function getEncounterText(scene: BattleScene, keyOrString?: string, prima
|
||||
return textString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to inject {@link BattleScene.currentBattle.mysteryEncounter.dialogueTokens} into a given content string
|
||||
* @param scene
|
||||
* @param keyOrString
|
||||
*/
|
||||
function getTextWithDialogueTokens(scene: BattleScene, keyOrString?: string): string | null {
|
||||
if (isNullOrUndefined(keyOrString)) {
|
||||
return null;
|
||||
@ -51,14 +64,15 @@ export function queueEncounterMessage(scene: BattleScene, contentKey: string): v
|
||||
* Will display a message in UI with injected encounter data tokens
|
||||
* @param scene
|
||||
* @param contentKey
|
||||
* @param delay
|
||||
* @param prompt
|
||||
* @param callbackDelay
|
||||
* @param promptDelay
|
||||
*/
|
||||
export function showEncounterText(scene: BattleScene, contentKey: string, callbackDelay: number = 0, prompt: boolean = true, promptDelay: number | null = null): Promise<void> {
|
||||
export function showEncounterText(scene: BattleScene, contentKey: string, delay: number | null = null, callbackDelay: number = 0, prompt: boolean = true, promptDelay: number | null = null): Promise<void> {
|
||||
return new Promise<void>(resolve => {
|
||||
const text: string | null = getEncounterText(scene, contentKey);
|
||||
scene.ui.showText(text ?? "", null, () => resolve(), callbackDelay, prompt, promptDelay);
|
||||
scene.ui.showText(text ?? "", delay, () => resolve(), callbackDelay, prompt, promptDelay);
|
||||
});
|
||||
}
|
||||
|
||||
@ -66,13 +80,14 @@ export function showEncounterText(scene: BattleScene, contentKey: string, callba
|
||||
* Will display a dialogue (with speaker title) in UI with injected encounter data tokens
|
||||
* @param scene
|
||||
* @param textContentKey
|
||||
* @param delay
|
||||
* @param speakerContentKey
|
||||
* @param callbackDelay
|
||||
*/
|
||||
export function showEncounterDialogue(scene: BattleScene, textContentKey: string, speakerContentKey: string, callbackDelay: number = 0): Promise<void> {
|
||||
export function showEncounterDialogue(scene: BattleScene, textContentKey: string, speakerContentKey: string, delay: number | null = null, callbackDelay: number = 0): Promise<void> {
|
||||
return new Promise<void>(resolve => {
|
||||
const text: string | null = getEncounterText(scene, textContentKey);
|
||||
const speaker: string | null = getEncounterText(scene, speakerContentKey);
|
||||
scene.ui.showDialogue(text ?? "", speaker ?? "", null, () => resolve(), callbackDelay);
|
||||
scene.ui.showDialogue(text ?? "", speaker ?? "", delay, () => resolve(), callbackDelay);
|
||||
});
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ export interface EnemyPokemonConfig {
|
||||
passive?: boolean;
|
||||
moveSet?: Moves[];
|
||||
nature?: Nature;
|
||||
ivs?: [integer, integer, integer, integer, integer, integer];
|
||||
ivs?: [number, number, number, number, number, number];
|
||||
shiny?: boolean;
|
||||
/** Can set just the status, or pass a timer on the status turns */
|
||||
status?: StatusEffect | [StatusEffect, number];
|
||||
@ -422,7 +422,7 @@ export function selectPokemonForOption(scene: BattleScene, onPokemonSelected: (p
|
||||
const modeToSetOnExit = scene.ui.getMode();
|
||||
|
||||
// Open party screen to choose pokemon
|
||||
scene.ui.setMode(Mode.PARTY, PartyUiMode.SELECT, -1, (slotIndex: integer, option: PartyOption) => {
|
||||
scene.ui.setMode(Mode.PARTY, PartyUiMode.SELECT, -1, (slotIndex: number, option: PartyOption) => {
|
||||
if (slotIndex < scene.getParty().length) {
|
||||
scene.ui.setMode(modeToSetOnExit).then(() => {
|
||||
const pokemon = scene.getParty()[slotIndex];
|
||||
@ -456,7 +456,7 @@ export function selectPokemonForOption(scene: BattleScene, onPokemonSelected: (p
|
||||
return true;
|
||||
},
|
||||
onHover: () => {
|
||||
scene.ui.showText(i18next.t("mysteryEncounterMessages:cancel_option"));
|
||||
showEncounterText(scene, i18next.t("mysteryEncounterMessages:cancel_option"), 0);
|
||||
}
|
||||
});
|
||||
|
||||
@ -534,7 +534,7 @@ export function selectOptionThenPokemon(scene: BattleScene, options: OptionSelec
|
||||
|
||||
const selectPokemonAfterOption = (selectedOptionIndex: number) => {
|
||||
// Open party screen to choose a Pokemon
|
||||
scene.ui.setMode(Mode.PARTY, PartyUiMode.SELECT, -1, (slotIndex: integer, option: PartyOption) => {
|
||||
scene.ui.setMode(Mode.PARTY, PartyUiMode.SELECT, -1, (slotIndex: number, option: PartyOption) => {
|
||||
if (slotIndex < scene.getParty().length) {
|
||||
// Pokemon and option selected
|
||||
scene.ui.setMode(modeToSetOnExit).then(() => {
|
||||
@ -852,7 +852,7 @@ export function calculateMEAggregateStats(scene: BattleScene, baseSpawnWeight: n
|
||||
if (Array.isArray(biomeLinks[currentBiome])) {
|
||||
let biomes: Biome[];
|
||||
scene.executeWithSeedOffset(() => {
|
||||
biomes = (biomeLinks[currentBiome] as (Biome | [Biome, integer])[])
|
||||
biomes = (biomeLinks[currentBiome] as (Biome | [Biome, number])[])
|
||||
.filter(b => {
|
||||
return !Array.isArray(b) || !Utils.randSeedInt(b[1]);
|
||||
})
|
||||
|
@ -20,12 +20,24 @@ import { Gender } from "#app/data/gender";
|
||||
import { PermanentStat } from "#enums/stat";
|
||||
import { VictoryPhase } from "#app/phases/victory-phase";
|
||||
|
||||
export function getSpriteKeysFromSpecies(species: Species, female?: boolean, formIndex?: integer, shiny?: boolean, variant?: integer): { spriteKey: string, fileRoot: string } {
|
||||
/**
|
||||
* Gets the sprite key and file root for a given PokemonSpecies (accounts for gender, shiny, variants, forms, and experimental)
|
||||
* @param species
|
||||
* @param female
|
||||
* @param formIndex
|
||||
* @param shiny
|
||||
* @param variant
|
||||
*/
|
||||
export function getSpriteKeysFromSpecies(species: Species, female?: boolean, formIndex?: number, shiny?: boolean, variant?: number): { spriteKey: string, fileRoot: string } {
|
||||
const spriteKey = getPokemonSpecies(species).getSpriteKey(female ?? false, formIndex ?? 0, shiny ?? false, variant ?? 0);
|
||||
const fileRoot = getPokemonSpecies(species).getSpriteAtlasPath(female ?? false, formIndex ?? 0, shiny ?? false, variant ?? 0);
|
||||
return { spriteKey, fileRoot };
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the sprite key and file root for a given Pokemon (accounts for gender, shiny, variants, forms, and experimental)
|
||||
* @param pokemon
|
||||
*/
|
||||
export function getSpriteKeysFromPokemon(pokemon: Pokemon): { spriteKey: string, fileRoot: string } {
|
||||
const spriteKey = pokemon.getSpeciesForm().getSpriteKey(pokemon.getGender() === Gender.FEMALE, pokemon.formIndex, pokemon.shiny, pokemon.variant);
|
||||
const fileRoot = pokemon.getSpeciesForm().getSpriteAtlasPath(pokemon.getGender() === Gender.FEMALE, pokemon.formIndex, pokemon.shiny, pokemon.variant);
|
||||
@ -442,6 +454,14 @@ export function trainerThrowPokeball(scene: BattleScene, pokemon: EnemyPokemon,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates pokeball opening and messages when an attempted catch fails
|
||||
* @param scene
|
||||
* @param pokemon
|
||||
* @param originalY
|
||||
* @param pokeball
|
||||
* @param pokeballType
|
||||
*/
|
||||
function failCatch(scene: BattleScene, pokemon: EnemyPokemon, originalY: number, pokeball: Phaser.GameObjects.Sprite, pokeballType: PokeballType) {
|
||||
return new Promise<void>(resolve => {
|
||||
scene.playSound("se/pb_rel");
|
||||
@ -541,7 +561,7 @@ export async function catchPokemon(scene: BattleScene, pokemon: EnemyPokemon, po
|
||||
scene.ui.showText(i18next.t("battle:partyFull", { pokemonName: pokemon.getNameToRender() }), null, () => {
|
||||
scene.pokemonInfoContainer.makeRoomForConfirmUi();
|
||||
scene.ui.setMode(Mode.CONFIRM, () => {
|
||||
scene.ui.setMode(Mode.PARTY, PartyUiMode.RELEASE, 0, (slotIndex: integer, _option: PartyOption) => {
|
||||
scene.ui.setMode(Mode.PARTY, PartyUiMode.RELEASE, 0, (slotIndex: number, _option: PartyOption) => {
|
||||
scene.ui.setMode(Mode.MESSAGE).then(() => {
|
||||
if (slotIndex < 6) {
|
||||
addToParty();
|
||||
@ -573,6 +593,11 @@ export async function catchPokemon(scene: BattleScene, pokemon: EnemyPokemon, po
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates pokeball disappearing then destroys the object
|
||||
* @param scene
|
||||
* @param pokeball
|
||||
*/
|
||||
function removePb(scene: BattleScene, pokeball: Phaser.GameObjects.Sprite) {
|
||||
if (pokeball) {
|
||||
scene.tweens.add({
|
||||
@ -588,6 +613,11 @@ function removePb(scene: BattleScene, pokeball: Phaser.GameObjects.Sprite) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates a wild pokemon "fleeing", including sfx and messaging
|
||||
* @param scene
|
||||
* @param pokemon
|
||||
*/
|
||||
export async function doPokemonFlee(scene: BattleScene, pokemon: EnemyPokemon): Promise<void> {
|
||||
await new Promise<void>(resolve => {
|
||||
scene.playSound("se/flee");
|
||||
@ -603,7 +633,7 @@ export async function doPokemonFlee(scene: BattleScene, pokemon: EnemyPokemon):
|
||||
onComplete: () => {
|
||||
pokemon.setVisible(false);
|
||||
scene.field.remove(pokemon, true);
|
||||
showEncounterText(scene, i18next.t("battle:pokemonFled", { pokemonName: pokemon.getNameToRender() }), 600, false)
|
||||
showEncounterText(scene, i18next.t("battle:pokemonFled", { pokemonName: pokemon.getNameToRender() }), null, 600, false)
|
||||
.then(() => {
|
||||
resolve();
|
||||
});
|
||||
@ -612,6 +642,11 @@ export async function doPokemonFlee(scene: BattleScene, pokemon: EnemyPokemon):
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the player fleeing from a wild pokemon, including sfx and messaging
|
||||
* @param scene
|
||||
* @param pokemon
|
||||
*/
|
||||
export function doPlayerFlee(scene: BattleScene, pokemon: EnemyPokemon): Promise<void> {
|
||||
return new Promise<void>(resolve => {
|
||||
// Ease pokemon out
|
||||
@ -626,7 +661,7 @@ export function doPlayerFlee(scene: BattleScene, pokemon: EnemyPokemon): Promise
|
||||
onComplete: () => {
|
||||
pokemon.setVisible(false);
|
||||
scene.field.remove(pokemon, true);
|
||||
showEncounterText(scene, i18next.t("battle:playerFled", { pokemonName: pokemon.getNameToRender() }), 600, false)
|
||||
showEncounterText(scene, i18next.t("battle:playerFled", { pokemonName: pokemon.getNameToRender() }), null, 600, false)
|
||||
.then(() => {
|
||||
resolve();
|
||||
});
|
||||
@ -635,7 +670,9 @@ export function doPlayerFlee(scene: BattleScene, pokemon: EnemyPokemon): Promise
|
||||
});
|
||||
}
|
||||
|
||||
// Bug Species and their corresponding weights
|
||||
/**
|
||||
* Bug Species and their corresponding weights
|
||||
*/
|
||||
const GOLDEN_BUG_NET_SPECIES_POOL: [Species, number][] = [
|
||||
[Species.SCYTHER, 40],
|
||||
[Species.SCIZOR, 40],
|
||||
@ -666,7 +703,10 @@ const GOLDEN_BUG_NET_SPECIES_POOL: [Species, number][] = [
|
||||
[Species.PHEROMOSA, 1],
|
||||
];
|
||||
|
||||
export function getGoldenBugNetSpecies(scene: BattleScene, waveIndex: integer, level: integer): PokemonSpecies {
|
||||
/**
|
||||
* Will randomly return one of the species from GOLDEN_BUG_NET_SPECIES_POOL, based on their weights
|
||||
*/
|
||||
export function getGoldenBugNetSpecies(): PokemonSpecies {
|
||||
const totalWeight = GOLDEN_BUG_NET_SPECIES_POOL.reduce((a, b) => a + b[1], 0);
|
||||
const roll = randSeedInt(totalWeight);
|
||||
|
||||
|
@ -103,7 +103,7 @@ export function doPokemonTransformationSequence(scene: BattleScene, previousPoke
|
||||
scene.time.delayedCall(1000, () => {
|
||||
pokemonEvoTintSprite.setScale(0.25);
|
||||
pokemonEvoTintSprite.setVisible(true);
|
||||
doCycle(scene, 2, 6, pokemonTintSprite, pokemonEvoTintSprite).then(success => {
|
||||
doCycle(scene, 2, 6, pokemonTintSprite, pokemonEvoTintSprite).then(() => {
|
||||
pokemonEvoSprite.setVisible(true);
|
||||
doCircleInward(scene, transformationBaseBg, transformationContainer, xOffset, yOffset);
|
||||
|
||||
@ -143,7 +143,15 @@ export function doPokemonTransformationSequence(scene: BattleScene, previousPoke
|
||||
});
|
||||
}
|
||||
|
||||
function doSpiralUpward(scene: BattleScene, transformationBaseBg, transformationContainer, xOffset: number, yOffset: number) {
|
||||
/**
|
||||
* Animates particles that "spiral" upwards at start of transform animation
|
||||
* @param scene
|
||||
* @param transformationBaseBg
|
||||
* @param transformationContainer
|
||||
* @param xOffset
|
||||
* @param yOffset
|
||||
*/
|
||||
function doSpiralUpward(scene: BattleScene, transformationBaseBg: Phaser.GameObjects.Image, transformationContainer: Phaser.GameObjects.Container, xOffset: number, yOffset: number) {
|
||||
let f = 0;
|
||||
|
||||
scene.tweens.addCounter({
|
||||
@ -162,7 +170,15 @@ function doSpiralUpward(scene: BattleScene, transformationBaseBg, transformation
|
||||
});
|
||||
}
|
||||
|
||||
function doArcDownward(scene: BattleScene, transformationBaseBg, transformationContainer, xOffset: number, yOffset: number) {
|
||||
/**
|
||||
* Animates particles that arc downwards after the upwards spiral
|
||||
* @param scene
|
||||
* @param transformationBaseBg
|
||||
* @param transformationContainer
|
||||
* @param xOffset
|
||||
* @param yOffset
|
||||
*/
|
||||
function doArcDownward(scene: BattleScene, transformationBaseBg: Phaser.GameObjects.Image, transformationContainer: Phaser.GameObjects.Container, xOffset: number, yOffset: number) {
|
||||
let f = 0;
|
||||
|
||||
scene.tweens.addCounter({
|
||||
@ -181,7 +197,15 @@ function doArcDownward(scene: BattleScene, transformationBaseBg, transformationC
|
||||
});
|
||||
}
|
||||
|
||||
function doCycle(scene: BattleScene, l: number, lastCycle: integer, pokemonTintSprite, pokemonEvoTintSprite): Promise<boolean> {
|
||||
/**
|
||||
* Animates the transformation between the old pokemon form and new pokemon form
|
||||
* @param scene
|
||||
* @param l
|
||||
* @param lastCycle
|
||||
* @param pokemonTintSprite
|
||||
* @param pokemonEvoTintSprite
|
||||
*/
|
||||
function doCycle(scene: BattleScene, l: number, lastCycle: number, pokemonTintSprite: Phaser.GameObjects.Sprite, pokemonEvoTintSprite: Phaser.GameObjects.Sprite): Promise<boolean> {
|
||||
return new Promise(resolve => {
|
||||
const isLastCycle = l === lastCycle;
|
||||
scene.tweens.add({
|
||||
@ -209,7 +233,15 @@ function doCycle(scene: BattleScene, l: number, lastCycle: integer, pokemonTintS
|
||||
});
|
||||
}
|
||||
|
||||
function doCircleInward(scene: BattleScene, transformationBaseBg, transformationContainer, xOffset: number, yOffset: number) {
|
||||
/**
|
||||
* Animates particles in a circle pattern
|
||||
* @param scene
|
||||
* @param transformationBaseBg
|
||||
* @param transformationContainer
|
||||
* @param xOffset
|
||||
* @param yOffset
|
||||
*/
|
||||
function doCircleInward(scene: BattleScene, transformationBaseBg: Phaser.GameObjects.Image, transformationContainer: Phaser.GameObjects.Container, xOffset: number, yOffset: number) {
|
||||
let f = 0;
|
||||
|
||||
scene.tweens.addCounter({
|
||||
@ -230,7 +262,16 @@ function doCircleInward(scene: BattleScene, transformationBaseBg, transformation
|
||||
});
|
||||
}
|
||||
|
||||
function doSpiralUpwardParticle(scene: BattleScene, trigIndex: integer, transformationBaseBg, transformationContainer, xOffset: number, yOffset: number) {
|
||||
/**
|
||||
* Helper function for {@link doSpiralUpward}, handles a single particle
|
||||
* @param scene
|
||||
* @param trigIndex
|
||||
* @param transformationBaseBg
|
||||
* @param transformationContainer
|
||||
* @param xOffset
|
||||
* @param yOffset
|
||||
*/
|
||||
function doSpiralUpwardParticle(scene: BattleScene, trigIndex: number, transformationBaseBg: Phaser.GameObjects.Image, transformationContainer: Phaser.GameObjects.Container, xOffset: number, yOffset: number) {
|
||||
const initialX = transformationBaseBg.displayWidth / 2 + xOffset;
|
||||
const particle = scene.add.image(initialX, 0, "evo_sparkle");
|
||||
transformationContainer.add(particle);
|
||||
@ -266,7 +307,16 @@ function doSpiralUpwardParticle(scene: BattleScene, trigIndex: integer, transfor
|
||||
updateParticle();
|
||||
}
|
||||
|
||||
function doArcDownParticle(scene: BattleScene, trigIndex: integer, transformationBaseBg, transformationContainer, xOffset: number, yOffset: number) {
|
||||
/**
|
||||
* Helper function for {@link doArcDownward}, handles a single particle
|
||||
* @param scene
|
||||
* @param trigIndex
|
||||
* @param transformationBaseBg
|
||||
* @param transformationContainer
|
||||
* @param xOffset
|
||||
* @param yOffset
|
||||
*/
|
||||
function doArcDownParticle(scene: BattleScene, trigIndex: number, transformationBaseBg: Phaser.GameObjects.Image, transformationContainer: Phaser.GameObjects.Container, xOffset: number, yOffset: number) {
|
||||
const initialX = transformationBaseBg.displayWidth / 2 + xOffset;
|
||||
const particle = scene.add.image(initialX, 0, "evo_sparkle");
|
||||
particle.setScale(0.5);
|
||||
@ -299,7 +349,17 @@ function doArcDownParticle(scene: BattleScene, trigIndex: integer, transformatio
|
||||
updateParticle();
|
||||
}
|
||||
|
||||
function doCircleInwardParticle(scene: BattleScene, trigIndex: integer, speed: integer, transformationBaseBg, transformationContainer, xOffset: number, yOffset: number) {
|
||||
/**
|
||||
* Helper function for @{link doCircleInward}, handles a single particle
|
||||
* @param scene
|
||||
* @param trigIndex
|
||||
* @param speed
|
||||
* @param transformationBaseBg
|
||||
* @param transformationContainer
|
||||
* @param xOffset
|
||||
* @param yOffset
|
||||
*/
|
||||
function doCircleInwardParticle(scene: BattleScene, trigIndex: number, speed: number, transformationBaseBg: Phaser.GameObjects.Image, transformationContainer: Phaser.GameObjects.Container, xOffset: number, yOffset: number) {
|
||||
const initialX = transformationBaseBg.displayWidth / 2 + xOffset;
|
||||
const initialY = transformationBaseBg.displayHeight / 2 + yOffset;
|
||||
const particle = scene.add.image(initialX, initialY, "evo_sparkle");
|
||||
|
@ -247,7 +247,7 @@ export abstract class PokemonSpeciesForm {
|
||||
* Gets the BST for the species
|
||||
* @returns The species' BST.
|
||||
*/
|
||||
getBaseStatTotal(): integer {
|
||||
getBaseStatTotal(): number {
|
||||
return this.baseStats.reduce((i, n) => n + i);
|
||||
}
|
||||
|
||||
@ -256,11 +256,11 @@ export abstract class PokemonSpeciesForm {
|
||||
* @param stat The desired stat.
|
||||
* @returns The species' base stat amount.
|
||||
*/
|
||||
getBaseStat(stat: Stat): integer {
|
||||
getBaseStat(stat: Stat): number {
|
||||
return this.baseStats[stat];
|
||||
}
|
||||
|
||||
getBaseExp(): integer {
|
||||
getBaseExp(): number {
|
||||
let ret = this.baseExp;
|
||||
switch (this.getFormSpriteKey()) {
|
||||
case SpeciesFormKey.MEGA:
|
||||
|
@ -994,6 +994,9 @@ export class TrainerConfig {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a copy of a trainer config so that it can be modified without affecting the {@link trainerConfigs} source map
|
||||
*/
|
||||
copy(): TrainerConfig {
|
||||
let copy = new TrainerConfig(this.trainerType);
|
||||
copy = this.trainerTypeDouble ? copy.setDoubleTrainerType(this.trainerTypeDouble) : copy;
|
||||
|
@ -88,12 +88,14 @@ export class Weather {
|
||||
return 1;
|
||||
}
|
||||
|
||||
isMoveWeatherCancelled(move: Move): boolean {
|
||||
isMoveWeatherCancelled(user: Pokemon, move: Move): boolean {
|
||||
const moveType = user.getMoveType(move);
|
||||
|
||||
switch (this.weatherType) {
|
||||
case WeatherType.HARSH_SUN:
|
||||
return move instanceof AttackMove && move.type === Type.WATER;
|
||||
return move instanceof AttackMove && moveType === Type.WATER;
|
||||
case WeatherType.HEAVY_RAIN:
|
||||
return move instanceof AttackMove && move.type === Type.FIRE;
|
||||
return move instanceof AttackMove && moveType === Type.FIRE;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -1,9 +1,12 @@
|
||||
export enum MysteryEncounterMode {
|
||||
/** MysteryEncounter will always begin in this mode, but will always swap modes when an option is selected */
|
||||
DEFAULT,
|
||||
/** If the MysteryEncounter battle is a trainer type battle */
|
||||
TRAINER_BATTLE,
|
||||
/** If the MysteryEncounter battle is a wild type battle */
|
||||
WILD_BATTLE,
|
||||
/** Enables special boss music during encounter */
|
||||
BOSS_BATTLE,
|
||||
/** If there is no battle in the MysteryEncounter or option selected */
|
||||
NO_BATTLE
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ export enum MysteryEncounterType {
|
||||
SHADY_VITAMIN_DEALER,
|
||||
FIELD_TRIP,
|
||||
SAFARI_ZONE,
|
||||
LOST_AT_SEA, // Might be generalized later on
|
||||
LOST_AT_SEA,
|
||||
FIERY_FALLOUT,
|
||||
THE_STRONG_STUFF,
|
||||
THE_POKEMON_SALESMAN,
|
||||
|
@ -391,8 +391,8 @@ export class Arena {
|
||||
return true;
|
||||
}
|
||||
|
||||
isMoveWeatherCancelled(move: Move) {
|
||||
return this.weather && !this.weather.isEffectSuppressed(this.scene) && this.weather.isMoveWeatherCancelled(move);
|
||||
isMoveWeatherCancelled(user: Pokemon, move: Move) {
|
||||
return this.weather && !this.weather.isEffectSuppressed(this.scene) && this.weather.isMoveWeatherCancelled(user, move);
|
||||
}
|
||||
|
||||
isMoveTerrainCancelled(user: Pokemon, targets: BattlerIndex[], move: Move) {
|
||||
|
@ -175,6 +175,9 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the assets that were defined on construction (async)
|
||||
*/
|
||||
loadAssets(): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
if (!this.spriteConfigs) {
|
||||
@ -226,6 +229,9 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the initial frames and tint of sprites after load
|
||||
*/
|
||||
initSprite(): void {
|
||||
if (!this.spriteConfigs) {
|
||||
return;
|
||||
@ -282,6 +288,9 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* For sprites with animation and that do not have animation disabled, will begin frame animation
|
||||
*/
|
||||
playAnim(): void {
|
||||
if (!this.spriteConfigs) {
|
||||
return;
|
||||
@ -318,6 +327,9 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all non-tint sprites (these are the "real" unmodified sprites)
|
||||
*/
|
||||
getSprites(): Phaser.GameObjects.Sprite[] {
|
||||
if (!this.spriteConfigs) {
|
||||
return [];
|
||||
@ -330,6 +342,9 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all tint sprites (duplicate sprites that have different alpha and fill values)
|
||||
*/
|
||||
getTintSprites(): Phaser.GameObjects.Sprite[] {
|
||||
if (!this.spriteConfigs) {
|
||||
return [];
|
||||
@ -343,7 +358,15 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
||||
return ret;
|
||||
}
|
||||
|
||||
tint(sprite, color: number, alpha?: number, duration?: integer, ease?: string): void {
|
||||
/**
|
||||
* Tints a single sprite
|
||||
* @param sprite
|
||||
* @param color
|
||||
* @param alpha
|
||||
* @param duration
|
||||
* @param ease
|
||||
*/
|
||||
private tint(sprite, color: number, alpha?: number, duration?: integer, ease?: string): void {
|
||||
// const tintSprites = this.getTintSprites();
|
||||
sprite.setTintFill(color);
|
||||
sprite.setVisible(true);
|
||||
@ -362,6 +385,13 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tints all sprites
|
||||
* @param color
|
||||
* @param alpha
|
||||
* @param duration
|
||||
* @param ease
|
||||
*/
|
||||
tintAll(color: number, alpha?: number, duration?: integer, ease?: string): void {
|
||||
const tintSprites = this.getTintSprites();
|
||||
tintSprites.map(tintSprite => {
|
||||
@ -369,7 +399,13 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
||||
});
|
||||
}
|
||||
|
||||
untint(sprite, duration: integer, ease?: string): void {
|
||||
/**
|
||||
* Untints a single sprite over a duration
|
||||
* @param sprite
|
||||
* @param duration
|
||||
* @param ease
|
||||
*/
|
||||
private untint(sprite, duration: integer, ease?: string): void {
|
||||
if (duration) {
|
||||
this.scene.tweens.add({
|
||||
targets: sprite,
|
||||
@ -387,6 +423,12 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Untints all sprites
|
||||
* @param sprite
|
||||
* @param duration
|
||||
* @param ease
|
||||
*/
|
||||
untintAll(duration: integer, ease?: string): void {
|
||||
const tintSprites = this.getTintSprites();
|
||||
tintSprites.map(tintSprite => {
|
||||
@ -406,6 +448,9 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface is required so as not to override {@link Phaser.GameObjects.Container.scene}
|
||||
*/
|
||||
export default interface MysteryEncounterIntroVisuals {
|
||||
scene: BattleScene
|
||||
}
|
||||
|
@ -2875,7 +2875,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
return this.fusionFaintCry(callback);
|
||||
}
|
||||
|
||||
const key = `cry/${this.getSpeciesForm().getCryKey(this.formIndex)}`;
|
||||
const key = `cry/${this.species.getCryKey(this.formIndex)}`;
|
||||
//eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
let i = 0;
|
||||
let rate = 0.85;
|
||||
@ -2933,7 +2933,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
}
|
||||
|
||||
private fusionFaintCry(callback: Function): void {
|
||||
const key = `cry/${this.getSpeciesForm().getCryKey(this.formIndex)}`;
|
||||
const key = `cry/${this.species.getCryKey(this.formIndex)}`;
|
||||
let i = 0;
|
||||
let rate = 0.85;
|
||||
const cry = this.scene.playSound(key, { rate: rate }) as AnySound;
|
||||
@ -2941,7 +2941,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
const tintSprite = this.getTintSprite();
|
||||
let duration = cry.totalDuration * 1000;
|
||||
|
||||
const fusionCryKey = `cry/${this.getFusionSpeciesForm().getCryKey(this.fusionFormIndex)}`;
|
||||
const fusionCryKey = `cry/${this.fusionSpecies?.getCryKey(this.fusionFormIndex)}`;
|
||||
let fusionCry = this.scene.playSound(fusionCryKey, { rate: rate }) as AnySound;
|
||||
fusionCry.stop();
|
||||
duration = Math.min(duration, fusionCry.totalDuration * 1000);
|
||||
@ -3628,7 +3628,6 @@ export default interface Pokemon {
|
||||
|
||||
export class PlayerPokemon extends Pokemon {
|
||||
public compatibleTms: Moves[];
|
||||
public usedTms: Moves[];
|
||||
|
||||
constructor(scene: BattleScene, species: PokemonSpecies, level: integer, abilityIndex?: integer, formIndex?: integer, gender?: Gender, shiny?: boolean, variant?: Variant, ivs?: integer[], nature?: Nature, dataSource?: Pokemon | PokemonData) {
|
||||
super(scene, 106, 148, species, level, abilityIndex, formIndex, gender, shiny, variant, ivs, nature, dataSource);
|
||||
@ -3652,7 +3651,6 @@ export class PlayerPokemon extends Pokemon {
|
||||
}
|
||||
}
|
||||
this.generateCompatibleTms();
|
||||
this.usedTms = [];
|
||||
}
|
||||
|
||||
initBattleInfo(): void {
|
||||
|
@ -9,7 +9,6 @@ import * as Utils from "./utils";
|
||||
import { Biome } from "#enums/biome";
|
||||
import { Species } from "#enums/species";
|
||||
import { Challenges } from "./enums/challenges";
|
||||
import MAX_SAFE_INTEGER = Phaser.Math.MAX_SAFE_INTEGER;
|
||||
|
||||
export enum GameModes {
|
||||
CLASSIC,
|
||||
@ -62,7 +61,7 @@ export class GameMode implements GameModeConfig {
|
||||
}
|
||||
this.battleConfig = battleConfig || {};
|
||||
this.minMysteryEncounterWave = this.minMysteryEncounterWave ?? 0;
|
||||
this.maxMysteryEncounterWave = this.maxMysteryEncounterWave ?? MAX_SAFE_INTEGER;
|
||||
this.maxMysteryEncounterWave = this.maxMysteryEncounterWave ?? Number.MAX_SAFE_INTEGER;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -7,15 +7,15 @@ import { WindowVariant, getWindowVariantSuffix } from "./ui/ui-theme";
|
||||
import { isMobile } from "./touch-controls";
|
||||
import * as Utils from "./utils";
|
||||
import { initI18n } from "./plugins/i18n";
|
||||
import {initPokemonPrevolutions} from "#app/data/pokemon-evolutions";
|
||||
import {initBiomes} from "#app/data/biomes";
|
||||
import {initEggMoves} from "#app/data/egg-moves";
|
||||
import {initPokemonForms} from "#app/data/pokemon-forms";
|
||||
import {initSpecies} from "#app/data/pokemon-species";
|
||||
import {initMoves} from "#app/data/move";
|
||||
import {initAbilities} from "#app/data/ability";
|
||||
import {initAchievements} from "#app/system/achv";
|
||||
import {initTrainerTypeDialogue} from "#app/data/dialogue";
|
||||
import { initPokemonPrevolutions } from "#app/data/pokemon-evolutions";
|
||||
import { initBiomes } from "#app/data/biomes";
|
||||
import { initEggMoves } from "#app/data/egg-moves";
|
||||
import { initPokemonForms } from "#app/data/pokemon-forms";
|
||||
import { initSpecies } from "#app/data/pokemon-species";
|
||||
import { initMoves } from "#app/data/move";
|
||||
import { initAbilities } from "#app/data/ability";
|
||||
import { initAchievements } from "#app/system/achv";
|
||||
import { initTrainerTypeDialogue } from "#app/data/dialogue";
|
||||
import { initChallenges } from "./data/challenge";
|
||||
import i18next from "i18next";
|
||||
import { initStatsKeys } from "./ui/game-stats-ui-handler";
|
||||
@ -251,9 +251,9 @@ export class LoadingScene extends SceneBase {
|
||||
}
|
||||
const availableLangs = ["en", "de", "it", "fr", "ja", "ko", "es", "pt-BR", "zh-CN"];
|
||||
if (lang && availableLangs.includes(lang)) {
|
||||
this.loadImage("september-update-"+lang, "events");
|
||||
this.loadImage("egg-update_"+lang, "events");
|
||||
} else {
|
||||
this.loadImage("september-update-en", "events");
|
||||
this.loadImage("egg-update_en", "events");
|
||||
}
|
||||
|
||||
this.loadAtlas("statuses", "");
|
||||
|
@ -66,5 +66,6 @@
|
||||
"revivalBlessing": "{{pokemonName}} ist wieder fit und kampfbereit!",
|
||||
"swapArenaTags": "{{pokemonName}} hat die Effekte, die auf den beiden Seiten des Kampffeldes wirken, miteinander getauscht!",
|
||||
"exposedMove": "{{pokemonName}} erkennt {{targetPokemonName}}!",
|
||||
"safeguard": "{{targetName}} wird durch Bodyguard geschützt!"
|
||||
"safeguard": "{{targetName}} wird durch Bodyguard geschützt!",
|
||||
"afterYou": "{{targetName}} lässt sich auf Galanterie ein!"
|
||||
}
|
||||
|
@ -53,9 +53,48 @@ import terrain from "./terrain.json";
|
||||
import modifierSelectUiHandler from "./modifier-select-ui-handler.json";
|
||||
import moveTriggers from "./move-trigger.json";
|
||||
import runHistory from "./run-history.json";
|
||||
import { mysteryEncounter } from "#app/locales/en/mystery-encounter";
|
||||
import mysteryEncounterMessages from "./mystery-encounter-messages.json";
|
||||
import lostAtSea from "./mystery-encounters/lost-at-sea-dialogue.json";
|
||||
import mysteriousChest from "#app/locales/en/mystery-encounters/mysterious-chest-dialogue.json";
|
||||
import mysteriousChallengers from "#app/locales/en/mystery-encounters/mysterious-challengers-dialogue.json";
|
||||
import darkDeal from "#app/locales/en/mystery-encounters/dark-deal-dialogue.json";
|
||||
import departmentStoreSale from "#app/locales/en/mystery-encounters/department-store-sale-dialogue.json";
|
||||
import fieldTrip from "#app/locales/en/mystery-encounters/field-trip-dialogue.json";
|
||||
import fieryFallout from "#app/locales/en/mystery-encounters/fiery-fallout-dialogue.json";
|
||||
import fightOrFlight from "#app/locales/en/mystery-encounters/fight-or-flight-dialogue.json";
|
||||
import safariZone from "#app/locales/en/mystery-encounters/safari-zone-dialogue.json";
|
||||
import shadyVitaminDealer from "#app/locales/en/mystery-encounters/shady-vitamin-dealer-dialogue.json";
|
||||
import slumberingSnorlax from "#app/locales/en/mystery-encounters/slumbering-snorlax-dialogue.json";
|
||||
import trainingSession from "#app/locales/en/mystery-encounters/training-session-dialogue.json";
|
||||
import theStrongStuff from "#app/locales/en/mystery-encounters/the-strong-stuff-dialogue.json";
|
||||
import pokemonSalesman from "#app/locales/en/mystery-encounters/the-pokemon-salesman-dialogue.json";
|
||||
import offerYouCantRefuse from "#app/locales/en/mystery-encounters/an-offer-you-cant-refuse-dialogue.json";
|
||||
import delibirdy from "#app/locales/en/mystery-encounters/delibirdy-dialogue.json";
|
||||
import absoluteAvarice from "#app/locales/en/mystery-encounters/absolute-avarice-dialogue.json";
|
||||
import aTrainersTest from "#app/locales/en/mystery-encounters/a-trainers-test-dialogue.json";
|
||||
import trashToTreasure from "#app/locales/en/mystery-encounters/trash-to-treasure-dialogue.json";
|
||||
import berriesAbound from "#app/locales/en/mystery-encounters/berries-abound-dialogue.json";
|
||||
import clowningAround from "#app/locales/en/mystery-encounters/clowning-around-dialogue.json";
|
||||
import partTimer from "#app/locales/en/mystery-encounters/part-timer-dialogue.json";
|
||||
import dancingLessons from "#app/locales/en/mystery-encounters/dancing-lessons-dialogue.json";
|
||||
import weirdDream from "#app/locales/en/mystery-encounters/weird-dream-dialogue.json";
|
||||
import theWinstrateChallenge from "#app/locales/en/mystery-encounters/the-winstrate-challenge-dialogue.json";
|
||||
import teleportingHijinks from "#app/locales/en/mystery-encounters/teleporting-hijinks-dialogue.json";
|
||||
import bugTypeSuperfan from "#app/locales/en/mystery-encounters/bug-type-superfan-dialogue.json";
|
||||
import funAndGames from "#app/locales/en/mystery-encounters/fun-and-games-dialogue.json";
|
||||
import uncommonBreed from "#app/locales/en/mystery-encounters/uncommon-breed-dialogue.json";
|
||||
import globalTradeSystem from "#app/locales/en/mystery-encounters/global-trade-system-dialogue.json";
|
||||
|
||||
/**
|
||||
* Dialogue/Text token injection patterns that can be used:
|
||||
* - `$` will be treated as a new line for Message and Dialogue strings.
|
||||
* - `@d{<number>}` will add a time delay to text animation for Message and Dialogue strings.
|
||||
* - `@s{<sound_effect_key>}` will play a specified sound effect for Message and Dialogue strings.
|
||||
* - `@f{<number>}` will fade the screen to black for the given duration, then fade back in for Message and Dialogue strings.
|
||||
* - `{{<token>}}` (MYSTERY ENCOUNTERS ONLY) will auto-inject the matching dialogue token value that is stored in {@link IMysteryEncounter.dialogueTokens}.
|
||||
* - (see [i18next interpolations](https://www.i18next.com/translation-function/interpolation)) for more details.
|
||||
* - `@[<TextStyle>]{<text>}` (STATIC TEXT ONLY, NOT USEABLE WITH {@link UI.showText()} OR {@link UI.showDialogue()}) will auto-color the given text to a specified {@link TextStyle} (e.g. `TextStyle.SUMMARY_GREEN`).
|
||||
*/
|
||||
export const enConfig = {
|
||||
ability,
|
||||
abilityTriggers,
|
||||
@ -112,6 +151,39 @@ export const enConfig = {
|
||||
modifierSelectUiHandler,
|
||||
moveTriggers,
|
||||
runHistory,
|
||||
mysteryEncounter: mysteryEncounter,
|
||||
mysteryEncounter: {
|
||||
// DO NOT REMOVE
|
||||
"unit_test_dialogue": "{{test}}{{test}} {{test{{test}}}} {{test1}} {{test\}} {{test\\}} {{test\\\}} {test}}",
|
||||
mysteriousChallengers,
|
||||
mysteriousChest,
|
||||
darkDeal,
|
||||
fightOrFlight,
|
||||
slumberingSnorlax,
|
||||
trainingSession,
|
||||
departmentStoreSale,
|
||||
shadyVitaminDealer,
|
||||
fieldTrip,
|
||||
safariZone,
|
||||
lostAtSea,
|
||||
fieryFallout,
|
||||
theStrongStuff,
|
||||
pokemonSalesman,
|
||||
offerYouCantRefuse,
|
||||
delibirdy,
|
||||
absoluteAvarice,
|
||||
aTrainersTest,
|
||||
trashToTreasure,
|
||||
berriesAbound,
|
||||
clowningAround,
|
||||
partTimer,
|
||||
dancingLessons,
|
||||
weirdDream,
|
||||
theWinstrateChallenge,
|
||||
teleportingHijinks,
|
||||
bugTypeSuperfan,
|
||||
funAndGames,
|
||||
uncommonBreed,
|
||||
globalTradeSystem
|
||||
},
|
||||
mysteryEncounterMessages
|
||||
};
|
||||
|
@ -67,5 +67,6 @@
|
||||
"revivalBlessing": "{{pokemonName}} was revived!",
|
||||
"swapArenaTags": "{{pokemonName}} swapped the battle effects affecting each side of the field!",
|
||||
"exposedMove": "{{pokemonName}} identified\n{{targetPokemonName}}!",
|
||||
"safeguard": "{{targetName}} is protected by Safeguard!"
|
||||
}
|
||||
"safeguard": "{{targetName}} is protected by Safeguard!",
|
||||
"afterYou": "{{pokemonName}} took the kind offer!"
|
||||
}
|
||||
|
@ -1,81 +0,0 @@
|
||||
import lostAtSea from "./mystery-encounters/lost-at-sea-dialogue.json";
|
||||
import mysteriousChest from "#app/locales/en/mystery-encounters/mysterious-chest-dialogue.json";
|
||||
import mysteriousChallengers from "#app/locales/en/mystery-encounters/mysterious-challengers-dialogue.json";
|
||||
import darkDeal from "#app/locales/en/mystery-encounters/dark-deal-dialogue.json";
|
||||
import departmentStoreSale from "#app/locales/en/mystery-encounters/department-store-sale-dialogue.json";
|
||||
import fieldTrip from "#app/locales/en/mystery-encounters/field-trip-dialogue.json";
|
||||
import fieryFallout from "#app/locales/en/mystery-encounters/fiery-fallout-dialogue.json";
|
||||
import fightOrFlight from "#app/locales/en/mystery-encounters/fight-or-flight-dialogue.json";
|
||||
import safariZone from "#app/locales/en/mystery-encounters/safari-zone-dialogue.json";
|
||||
import shadyVitaminDealer from "#app/locales/en/mystery-encounters/shady-vitamin-dealer-dialogue.json";
|
||||
import slumberingSnorlax from "#app/locales/en/mystery-encounters/slumbering-snorlax-dialogue.json";
|
||||
import trainingSession from "#app/locales/en/mystery-encounters/training-session-dialogue.json";
|
||||
import theStrongStuff from "#app/locales/en/mystery-encounters/the-strong-stuff-dialogue.json";
|
||||
import pokemonSalesman from "#app/locales/en/mystery-encounters/the-pokemon-salesman-dialogue.json";
|
||||
import offerYouCantRefuse from "#app/locales/en/mystery-encounters/an-offer-you-cant-refuse-dialogue.json";
|
||||
import delibirdy from "#app/locales/en/mystery-encounters/delibirdy-dialogue.json";
|
||||
import absoluteAvarice from "#app/locales/en/mystery-encounters/absolute-avarice-dialogue.json";
|
||||
import aTrainersTest from "#app/locales/en/mystery-encounters/a-trainers-test-dialogue.json";
|
||||
import trashToTreasure from "#app/locales/en/mystery-encounters/trash-to-treasure-dialogue.json";
|
||||
import berriesAbound from "#app/locales/en/mystery-encounters/berries-abound-dialogue.json";
|
||||
import clowningAround from "#app/locales/en/mystery-encounters/clowning-around-dialogue.json";
|
||||
import partTimer from "#app/locales/en/mystery-encounters/part-timer-dialogue.json";
|
||||
import dancingLessons from "#app/locales/en/mystery-encounters/dancing-lessons-dialogue.json";
|
||||
import weirdDream from "#app/locales/en/mystery-encounters/weird-dream-dialogue.json";
|
||||
import theWinstrateChallenge from "#app/locales/en/mystery-encounters/the-winstrate-challenge-dialogue.json";
|
||||
import teleportingHijinks from "#app/locales/en/mystery-encounters/teleporting-hijinks-dialogue.json";
|
||||
import bugTypeSuperfan from "#app/locales/en/mystery-encounters/bug-type-superfan-dialogue.json";
|
||||
import funAndGames from "#app/locales/en/mystery-encounters/fun-and-games-dialogue.json";
|
||||
import uncommonBreed from "#app/locales/en/mystery-encounters/uncommon-breed-dialogue.json";
|
||||
import globalTradeSystem from "#app/locales/en/mystery-encounters/global-trade-system-dialogue.json";
|
||||
|
||||
/**
|
||||
* Injection patterns that can be used:
|
||||
* - `$` will be treated as a new line for Message and Dialogue strings.
|
||||
* - `@d{<number>}` will add a time delay to text animation for Message and Dialogue strings.
|
||||
* - `@s{<sound_effect_key>}` will play a specified sound effect for Message and Dialogue strings.
|
||||
* - `@f{<number>}` will fade the screen to black for the given duration, then fade back in for Message and Dialogue strings.
|
||||
* - `{{<token>}}` will auto-inject the matching dialogue token value that is stored in {@link IMysteryEncounter.dialogueTokens}.
|
||||
* - (see [i18next interpolations](https://www.i18next.com/translation-function/interpolation)) for more details.
|
||||
* - `@[<TextStyle>]{<text>}` will auto-color the given text to a specified {@link TextStyle} (e.g. `TextStyle.SUMMARY_GREEN`).
|
||||
*
|
||||
* For Option tooltips ({@link OptionTextDisplay.buttonTooltip}):
|
||||
* - Any tooltip that starts with `(+)` or `(-)` at the beginning of a newline will auto-color to green/blue respectively.
|
||||
* - Note, this only occurs for option tooltips, nowhere else.
|
||||
* - Other types of `(...)` tooltips will have to specify the text color manually by using the `@[SUMMARY_GREEN]{<text>}` pattern.
|
||||
*/
|
||||
export const mysteryEncounter = {
|
||||
// DO NOT REMOVE
|
||||
"unit_test_dialogue": "{{test}}{{test}} {{test{{test}}}} {{test1}} {{test\}} {{test\\}} {{test\\\}} {test}}",
|
||||
|
||||
mysteriousChallengers,
|
||||
mysteriousChest,
|
||||
darkDeal,
|
||||
fightOrFlight,
|
||||
slumberingSnorlax,
|
||||
trainingSession,
|
||||
departmentStoreSale,
|
||||
shadyVitaminDealer,
|
||||
fieldTrip,
|
||||
safariZone,
|
||||
lostAtSea,
|
||||
fieryFallout,
|
||||
theStrongStuff,
|
||||
pokemonSalesman,
|
||||
offerYouCantRefuse,
|
||||
delibirdy,
|
||||
absoluteAvarice,
|
||||
aTrainersTest,
|
||||
trashToTreasure,
|
||||
berriesAbound,
|
||||
clowningAround,
|
||||
partTimer,
|
||||
dancingLessons,
|
||||
weirdDream,
|
||||
theWinstrateChallenge,
|
||||
teleportingHijinks,
|
||||
bugTypeSuperfan,
|
||||
funAndGames,
|
||||
uncommonBreed,
|
||||
globalTradeSystem
|
||||
} as const;
|
@ -166,7 +166,7 @@ export interface GeneratedPersistentModifierType {
|
||||
getPregenArgs(): any[];
|
||||
}
|
||||
|
||||
export class AddPokeballModifierType extends ModifierType {
|
||||
class AddPokeballModifierType extends ModifierType {
|
||||
private pokeballType: PokeballType;
|
||||
private count: integer;
|
||||
|
||||
@ -647,6 +647,9 @@ export class BaseStatBoosterModifierType extends PokemonHeldItemModifierType imp
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuckle Juice item
|
||||
*/
|
||||
export class PokemonBaseStatTotalModifierType extends PokemonHeldItemModifierType implements GeneratedPersistentModifierType {
|
||||
private readonly statModifier: integer;
|
||||
|
||||
@ -655,7 +658,7 @@ export class PokemonBaseStatTotalModifierType extends PokemonHeldItemModifierTyp
|
||||
this.statModifier = statModifier;
|
||||
}
|
||||
|
||||
getDescription(scene: BattleScene): string {
|
||||
override getDescription(scene: BattleScene): string {
|
||||
return i18next.t("modifierType:ModifierType.PokemonBaseStatTotalModifierType.description", {
|
||||
increaseDecrease: i18next.t(this.statModifier >= 0 ? "modifierType:ModifierType.PokemonBaseStatTotalModifierType.extra.increase" : "modifierType:ModifierType.PokemonBaseStatTotalModifierType.extra.decrease"),
|
||||
blessCurse: i18next.t(this.statModifier >= 0 ? "modifierType:ModifierType.PokemonBaseStatTotalModifierType.extra.blessed" : "modifierType:ModifierType.PokemonBaseStatTotalModifierType.extra.cursed"),
|
||||
@ -663,11 +666,14 @@ export class PokemonBaseStatTotalModifierType extends PokemonHeldItemModifierTyp
|
||||
});
|
||||
}
|
||||
|
||||
getPregenArgs(): any[] {
|
||||
public getPregenArgs(): any[] {
|
||||
return [ this.statModifier ];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Old Gateau item
|
||||
*/
|
||||
export class PokemonBaseStatFlatModifierType extends PokemonHeldItemModifierType implements GeneratedPersistentModifierType {
|
||||
private readonly statModifier: integer;
|
||||
private readonly stats: Stat[];
|
||||
@ -678,14 +684,14 @@ export class PokemonBaseStatFlatModifierType extends PokemonHeldItemModifierType
|
||||
this.stats = stats;
|
||||
}
|
||||
|
||||
getDescription(scene: BattleScene): string {
|
||||
override getDescription(scene: BattleScene): string {
|
||||
return i18next.t("modifierType:ModifierType.PokemonBaseStatFlatModifierType.description", {
|
||||
stats: this.stats.map(stat => i18next.t(getStatKey(stat))).join("/"),
|
||||
statValue: this.statModifier,
|
||||
});
|
||||
}
|
||||
|
||||
getPregenArgs(): any[] {
|
||||
public getPregenArgs(): any[] {
|
||||
return [ this.statModifier, this.stats ];
|
||||
}
|
||||
}
|
||||
@ -1607,6 +1613,7 @@ const modifierPool: ModifierPool = {
|
||||
new WeightedModifierType(modifierTypes.TEMP_STAT_STAGE_BOOSTER, 4),
|
||||
new WeightedModifierType(modifierTypes.BERRY, 2),
|
||||
new WeightedModifierType(modifierTypes.TM_COMMON, 2),
|
||||
new WeightedModifierType(modifierTypes.VOUCHER, (party: Pokemon[], rerollCount: integer) => !party[0].scene.gameMode.isDaily ? Math.max(1 - rerollCount, 0) : 0, 1),
|
||||
].map(m => {
|
||||
m.setTier(ModifierTier.COMMON); return m;
|
||||
}),
|
||||
@ -1677,7 +1684,7 @@ const modifierPool: ModifierPool = {
|
||||
new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 3),
|
||||
new WeightedModifierType(modifierTypes.TERA_SHARD, 1),
|
||||
new WeightedModifierType(modifierTypes.DNA_SPLICERS, (party: Pokemon[]) => party[0].scene.gameMode.isSplicedOnly && party.filter(p => !p.fusionSpecies).length > 1 ? 4 : 0),
|
||||
new WeightedModifierType(modifierTypes.VOUCHER, (party: Pokemon[], rerollCount: integer) => !party[0].scene.gameMode.isDaily ? Math.max(1 - rerollCount, 0) : 0, 1),
|
||||
new WeightedModifierType(modifierTypes.VOUCHER, (party: Pokemon[], rerollCount: integer) => !party[0].scene.gameMode.isDaily ? Math.max(3 - rerollCount * 3, 0) : 0, 3),
|
||||
].map(m => {
|
||||
m.setTier(ModifierTier.GREAT); return m;
|
||||
}),
|
||||
@ -1758,7 +1765,7 @@ const modifierPool: ModifierPool = {
|
||||
new WeightedModifierType(modifierTypes.RARE_FORM_CHANGE_ITEM, (party: Pokemon[]) => Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 50), 4) * 6, 24),
|
||||
new WeightedModifierType(modifierTypes.MEGA_BRACELET, (party: Pokemon[]) => Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 50), 4) * 9, 36),
|
||||
new WeightedModifierType(modifierTypes.DYNAMAX_BAND, (party: Pokemon[]) => Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 50), 4) * 9, 36),
|
||||
new WeightedModifierType(modifierTypes.VOUCHER_PLUS, (party: Pokemon[], rerollCount: integer) => !party[0].scene.gameMode.isDaily ? Math.max(3 - rerollCount * 1, 0) : 0, 3),
|
||||
new WeightedModifierType(modifierTypes.VOUCHER_PLUS, (party: Pokemon[], rerollCount: integer) => !party[0].scene.gameMode.isDaily ? Math.max(9 - rerollCount * 3, 0) : 0, 9),
|
||||
].map(m => {
|
||||
m.setTier(ModifierTier.ROGUE); return m;
|
||||
}),
|
||||
@ -1767,7 +1774,7 @@ const modifierPool: ModifierPool = {
|
||||
new WeightedModifierType(modifierTypes.SHINY_CHARM, 14),
|
||||
new WeightedModifierType(modifierTypes.HEALING_CHARM, 18),
|
||||
new WeightedModifierType(modifierTypes.MULTI_LENS, 18),
|
||||
new WeightedModifierType(modifierTypes.VOUCHER_PREMIUM, (party: Pokemon[], rerollCount: integer) => !party[0].scene.gameMode.isDaily && !party[0].scene.gameMode.isEndless && !party[0].scene.gameMode.isSplicedOnly ? Math.max(5 - rerollCount * 2, 0) : 0, 5),
|
||||
new WeightedModifierType(modifierTypes.VOUCHER_PREMIUM, (party: Pokemon[], rerollCount: integer) => !party[0].scene.gameMode.isDaily && !party[0].scene.gameMode.isEndless && !party[0].scene.gameMode.isSplicedOnly ? Math.max(15 - rerollCount * 5, 0) : 0, 15),
|
||||
new WeightedModifierType(modifierTypes.DNA_SPLICERS, (party: Pokemon[]) => !party[0].scene.gameMode.isSplicedOnly && party.filter(p => !p.fusionSpecies).length > 1 ? 24 : 0, 24),
|
||||
new WeightedModifierType(modifierTypes.MINI_BLACK_HOLE, (party: Pokemon[]) => (!party[0].scene.gameMode.isFreshStartChallenge() && party[0].scene.gameData.unlocks[Unlockables.MINI_BLACK_HOLE]) ? 1 : 0, 1),
|
||||
].map(m => {
|
||||
@ -2118,6 +2125,14 @@ export function getPlayerModifierTypeOptions(count: integer, party: PlayerPokemo
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will generate a ModifierType from the ModifierPoolType.PLAYER pool, attempting to retry duplicated items up to retryCount
|
||||
* @param existingOptions - currently generated options
|
||||
* @param retryCount - how many times to retry before allowing a dupe item
|
||||
* @param party - current player party, used to calculate items in the pool
|
||||
* @param tier - If specified will generate item of tier
|
||||
* @param allowLuckUpgrades - allow items to upgrade tiers (the little animation that plays and is affected by luck)
|
||||
*/
|
||||
function getModifierTypeOptionWithRetry(existingOptions: ModifierTypeOption[], retryCount: integer, party: PlayerPokemon[], tier?: ModifierTier, allowLuckUpgrades?: boolean): ModifierTypeOption {
|
||||
allowLuckUpgrades = allowLuckUpgrades ?? true;
|
||||
let candidate = getNewModifierTypeOption(party, ModifierPoolType.PLAYER, tier, undefined, 0, allowLuckUpgrades);
|
||||
@ -2253,6 +2268,15 @@ export function getDailyRunStarterModifiers(party: PlayerPokemon[]): Modifiers.P
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a ModifierType from the specified pool
|
||||
* @param party - party of the trainer using the item
|
||||
* @param poolType - PLAYER/WILD/TRAINER
|
||||
* @param tier - If specified, will override the initial tier of an item (can still upgrade with luck)
|
||||
* @param upgradeCount - If defined, means that this is a new ModifierType being generated to override another via luck upgrade. Used for recursive logic
|
||||
* @param retryCount - Max allowed tries before the next tier down is checked for a valid ModifierType
|
||||
* @param allowLuckUpgrades - Default true. If false, will not allow ModifierType to randomly upgrade to next tier
|
||||
*/
|
||||
function getNewModifierTypeOption(party: Pokemon[], poolType: ModifierPoolType, tier?: ModifierTier, upgradeCount?: integer, retryCount: integer = 0, allowLuckUpgrades: boolean = true): ModifierTypeOption | null {
|
||||
const player = !poolType;
|
||||
const pool = getModifierPoolForType(poolType);
|
||||
|
@ -367,6 +367,10 @@ export abstract class LapsingPersistentModifier extends PersistentModifier {
|
||||
return container;
|
||||
}
|
||||
|
||||
getIconStackText(_scene: BattleScene, _virtual?: boolean): Phaser.GameObjects.BitmapText | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
getBattleCount(): number {
|
||||
return this.battleCount;
|
||||
}
|
||||
@ -384,7 +388,8 @@ export abstract class LapsingPersistentModifier extends PersistentModifier {
|
||||
}
|
||||
|
||||
getMaxStackCount(_scene: BattleScene, _forThreshold?: boolean): number {
|
||||
return 1;
|
||||
// Must be an abitrary number greater than 1
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
@ -787,7 +792,7 @@ export class TerastallizeModifier extends LapsingPokemonHeldItemModifier {
|
||||
/**
|
||||
* Modifier used for held items, specifically vitamins like Carbos, Hp Up, etc., that
|
||||
* increase the value of a given {@linkcode PermanentStat}.
|
||||
* @extends LapsingPersistentModifier
|
||||
* @extends PokemonHeldItemModifier
|
||||
* @see {@linkcode apply}
|
||||
*/
|
||||
export class BaseStatModifier extends PokemonHeldItemModifier {
|
||||
@ -833,6 +838,9 @@ export class BaseStatModifier extends PokemonHeldItemModifier {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently used by Shuckle Juice item
|
||||
*/
|
||||
export class PokemonBaseStatTotalModifier extends PokemonHeldItemModifier {
|
||||
private statModifier: integer;
|
||||
readonly isTransferrable: boolean = false;
|
||||
@ -842,23 +850,23 @@ export class PokemonBaseStatTotalModifier extends PokemonHeldItemModifier {
|
||||
this.statModifier = statModifier;
|
||||
}
|
||||
|
||||
matchType(modifier: Modifier): boolean {
|
||||
override matchType(modifier: Modifier): boolean {
|
||||
return modifier instanceof PokemonBaseStatTotalModifier;
|
||||
}
|
||||
|
||||
clone(): PersistentModifier {
|
||||
override clone(): PersistentModifier {
|
||||
return new PokemonBaseStatTotalModifier(this.type as ModifierTypes.PokemonBaseStatTotalModifierType, this.pokemonId, this.statModifier, this.stackCount);
|
||||
}
|
||||
|
||||
getArgs(): any[] {
|
||||
override getArgs(): any[] {
|
||||
return super.getArgs().concat(this.statModifier);
|
||||
}
|
||||
|
||||
shouldApply(args: any[]): boolean {
|
||||
override shouldApply(args: any[]): boolean {
|
||||
return super.shouldApply(args) && args.length === 2 && args[1] instanceof Array;
|
||||
}
|
||||
|
||||
apply(args: any[]): boolean {
|
||||
override apply(args: any[]): boolean {
|
||||
// Modifies the passed in baseStats[] array
|
||||
args[1].forEach((v, i) => {
|
||||
// HP is affected by half as much as other stats
|
||||
@ -869,15 +877,18 @@ export class PokemonBaseStatTotalModifier extends PokemonHeldItemModifier {
|
||||
return true;
|
||||
}
|
||||
|
||||
getScoreMultiplier(): number {
|
||||
override getScoreMultiplier(): number {
|
||||
return 1.2;
|
||||
}
|
||||
|
||||
getMaxHeldItemCount(pokemon: Pokemon): integer {
|
||||
override getMaxHeldItemCount(pokemon: Pokemon): integer {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently used by Old Gateau item
|
||||
*/
|
||||
export class PokemonBaseStatFlatModifier extends PokemonHeldItemModifier {
|
||||
private statModifier: integer;
|
||||
private stats: Stat[];
|
||||
@ -890,23 +901,23 @@ export class PokemonBaseStatFlatModifier extends PokemonHeldItemModifier {
|
||||
this.stats = stats;
|
||||
}
|
||||
|
||||
matchType(modifier: Modifier): boolean {
|
||||
override matchType(modifier: Modifier): boolean {
|
||||
return modifier instanceof PokemonBaseStatFlatModifier;
|
||||
}
|
||||
|
||||
clone(): PersistentModifier {
|
||||
override clone(): PersistentModifier {
|
||||
return new PokemonBaseStatFlatModifier(this.type, this.pokemonId, this.statModifier, this.stats, this.stackCount);
|
||||
}
|
||||
|
||||
getArgs(): any[] {
|
||||
override getArgs(): any[] {
|
||||
return super.getArgs().concat(this.statModifier, this.stats);
|
||||
}
|
||||
|
||||
shouldApply(args: any[]): boolean {
|
||||
override shouldApply(args: any[]): boolean {
|
||||
return super.shouldApply(args) && args.length === 2 && args[1] instanceof Array;
|
||||
}
|
||||
|
||||
apply(args: any[]): boolean {
|
||||
override apply(args: any[]): boolean {
|
||||
// Modifies the passed in baseStats[] array by a flat value, only if the stat is specified in this.stats
|
||||
args[1].forEach((v, i) => {
|
||||
if (this.stats.includes(i)) {
|
||||
@ -918,15 +929,18 @@ export class PokemonBaseStatFlatModifier extends PokemonHeldItemModifier {
|
||||
return true;
|
||||
}
|
||||
|
||||
getScoreMultiplier(): number {
|
||||
override getScoreMultiplier(): number {
|
||||
return 1.1;
|
||||
}
|
||||
|
||||
getMaxHeldItemCount(pokemon: Pokemon): integer {
|
||||
override getMaxHeldItemCount(pokemon: Pokemon): integer {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently used by Macho Brace item
|
||||
*/
|
||||
export class PokemonIncrementingStatModifier extends PokemonHeldItemModifier {
|
||||
readonly isTransferrable: boolean = false;
|
||||
|
||||
@ -2500,6 +2514,9 @@ export class LockModifierTiersModifier extends PersistentModifier {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Black Sludge item
|
||||
*/
|
||||
export class HealShopCostModifier extends PersistentModifier {
|
||||
constructor(type: ModifierType, stackCount?: integer) {
|
||||
super(type, stackCount);
|
||||
|
@ -448,6 +448,7 @@ export class EggHatchPhase extends Phase {
|
||||
*/
|
||||
generatePokemon(): PlayerPokemon {
|
||||
this.eggHatchData = this.eggLapsePhase.generatePokemon(this.egg);
|
||||
this.eggMoveIndex = this.eggHatchData.eggMoveIndex;
|
||||
return this.eggHatchData.pokemon;
|
||||
}
|
||||
}
|
||||
|
@ -43,8 +43,9 @@ export class EggSummaryPhase extends Phase {
|
||||
}
|
||||
|
||||
end() {
|
||||
this.eggHatchHandler.clear();
|
||||
this.scene.ui.setModeForceTransition(Mode.MESSAGE).then(() => {});
|
||||
super.end();
|
||||
this.scene.time.delayedCall(250, () => this.scene.setModifiersVisible(true));
|
||||
this.scene.ui.setModeForceTransition(Mode.MESSAGE).then(() => {
|
||||
super.end();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ export class EncounterPhase extends BattlePhase {
|
||||
let enemySpecies = this.scene.randomSpecies(battle.waveIndex, level, true);
|
||||
// If player has golden bug net, rolls 10% chance to replace with species from the golden bug net bug pool
|
||||
if (!!this.scene.findModifier(m => m instanceof BoostBugSpawnModifier) && randSeedInt(10) === 0) {
|
||||
enemySpecies = getGoldenBugNetSpecies(this.scene, battle.waveIndex, level);
|
||||
enemySpecies = getGoldenBugNetSpecies();
|
||||
}
|
||||
battle.enemyParty[e] = this.scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.NONE, !!this.scene.getEncounterBossSegments(battle.waveIndex, level, enemySpecies));
|
||||
if (this.scene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) {
|
||||
@ -433,7 +433,7 @@ export class EncounterPhase extends BattlePhase {
|
||||
}
|
||||
});
|
||||
|
||||
if (this.scene.currentBattle.battleType !== BattleType.TRAINER && this.scene.currentBattle.battleType !== BattleType.MYSTERY_ENCOUNTER) {
|
||||
if (![BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER].includes(this.scene.currentBattle.battleType)) {
|
||||
enemyField.map(p => this.scene.pushConditionalPhase(new PostSummonPhase(this.scene, p.getBattlerIndex()), () => {
|
||||
// if there is not a player party, we can't continue
|
||||
if (!this.scene.getParty()?.length) {
|
||||
|
@ -108,7 +108,7 @@ export class FaintPhase extends PokemonPhase {
|
||||
}
|
||||
} else {
|
||||
this.scene.unshiftPhase(new VictoryPhase(this.scene, this.battlerIndex));
|
||||
if (this.scene.currentBattle.battleType === BattleType.TRAINER || this.scene.currentBattle.battleType === BattleType.MYSTERY_ENCOUNTER) {
|
||||
if ([BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER].includes(this.scene.currentBattle.battleType)) {
|
||||
const hasReservePartyMember = !!this.scene.getEnemyParty().filter(p => p.isActive() && !p.isOnField() && p.trainerSlot === (pokemon as EnemyPokemon).trainerSlot).length;
|
||||
if (hasReservePartyMember) {
|
||||
this.scene.pushPhase(new SwitchSummonPhase(this.scene, this.fieldIndex, -1, false, false, false));
|
||||
|
@ -137,6 +137,9 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase {
|
||||
*/
|
||||
async learnMove(index: number, move: Move, pokemon: Pokemon, textMessage?: string) {
|
||||
if (this.fromTM) {
|
||||
if (!pokemon.usedTMs) {
|
||||
pokemon.usedTMs = [];
|
||||
}
|
||||
pokemon.usedTMs.push(this.moveId);
|
||||
}
|
||||
pokemon.setMove(index, this.moveId);
|
||||
|
@ -204,7 +204,7 @@ export class MovePhase extends BattlePhase {
|
||||
let success = this.move.getMove().applyConditions(this.pokemon, targets[0], this.move.getMove());
|
||||
const cancelled = new Utils.BooleanHolder(false);
|
||||
let failedText = this.move.getMove().getFailedText(this.pokemon, targets[0], this.move.getMove(), cancelled);
|
||||
if (success && this.scene.arena.isMoveWeatherCancelled(this.move.getMove())) {
|
||||
if (success && this.scene.arena.isMoveWeatherCancelled(this.pokemon, this.move.getMove())) {
|
||||
success = false;
|
||||
} else if (success && this.scene.arena.isMoveTerrainCancelled(this.pokemon, this.targets, this.move.getMove())) {
|
||||
success = false;
|
||||
|
@ -7,7 +7,6 @@ import MysteryEncounterOption, { OptionPhaseCallback } from "../data/mystery-enc
|
||||
import { getCharVariantFromDialogue } from "../data/dialogue";
|
||||
import { TrainerSlot } from "../data/trainer-config";
|
||||
import { BattleSpec } from "#enums/battle-spec";
|
||||
import { Tutorial, handleTutorial } from "../tutorial";
|
||||
import { IvScannerModifier } from "../modifier/modifier";
|
||||
import * as Utils from "../utils";
|
||||
import { isNullOrUndefined } from "../utils";
|
||||
@ -50,6 +49,9 @@ export class MysteryEncounterPhase extends Phase {
|
||||
this.optionSelectSettings = optionSelectSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates seed offset, sets seen encounter session data, sets UI mode
|
||||
*/
|
||||
start() {
|
||||
super.start();
|
||||
|
||||
@ -70,6 +72,11 @@ export class MysteryEncounterPhase extends Phase {
|
||||
this.scene.ui.setMode(Mode.MYSTERY_ENCOUNTER, this.optionSelectSettings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers after a player selects an option for the encounter
|
||||
* @param option
|
||||
* @param index
|
||||
*/
|
||||
handleOptionSelect(option: MysteryEncounterOption, index: number): boolean {
|
||||
// Set option selected flag
|
||||
this.scene.currentBattle.mysteryEncounter!.selectedOption = option;
|
||||
@ -106,6 +113,9 @@ export class MysteryEncounterPhase extends Phase {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues MysteryEncounterOptionSelectedPhase, displays option.selected dialogue and ends phase
|
||||
*/
|
||||
continueEncounter() {
|
||||
const endDialogueAndContinueEncounter = () => {
|
||||
this.scene.pushPhase(new MysteryEncounterOptionSelectedPhase(this.scene));
|
||||
@ -141,10 +151,9 @@ export class MysteryEncounterPhase extends Phase {
|
||||
}
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends phase
|
||||
*/
|
||||
end() {
|
||||
this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end());
|
||||
}
|
||||
@ -196,6 +205,9 @@ export class MysteryEncounterBattleStartCleanupPhase extends Phase {
|
||||
super(scene);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up TURN_END tags, any PostTurnEffectPhases, checks for Pokemon switches, then continues
|
||||
*/
|
||||
start() {
|
||||
super.start();
|
||||
|
||||
@ -231,7 +243,7 @@ export class MysteryEncounterBattleStartCleanupPhase extends Phase {
|
||||
this.scene.unshiftPhase(new ToggleDoublePositionPhase(this.scene, true));
|
||||
}
|
||||
|
||||
super.end();
|
||||
this.end();
|
||||
}
|
||||
}
|
||||
|
||||
@ -250,13 +262,21 @@ export class MysteryEncounterBattlePhase extends Phase {
|
||||
this.disableSwitch = disableSwitch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a ME battle
|
||||
*/
|
||||
start() {
|
||||
super.start();
|
||||
|
||||
this.doMysteryEncounterBattle(this.scene);
|
||||
}
|
||||
|
||||
getBattleMessage(scene: BattleScene): string {
|
||||
/**
|
||||
* Gets intro battle message for new battle
|
||||
* @param scene
|
||||
* @private
|
||||
*/
|
||||
private getBattleMessage(scene: BattleScene): string {
|
||||
const enemyField = scene.getEnemyField();
|
||||
const encounterMode = scene.currentBattle.mysteryEncounter!.encounterMode;
|
||||
|
||||
@ -278,7 +298,12 @@ export class MysteryEncounterBattlePhase extends Phase {
|
||||
: i18next.t("battle:multiWildAppeared", { pokemonName1: enemyField[0].name, pokemonName2: enemyField[1].name });
|
||||
}
|
||||
|
||||
doMysteryEncounterBattle(scene: BattleScene) {
|
||||
/**
|
||||
* Queues SummonPhases for the new battle, and handles trainer animations/dialogue if Trainer battle
|
||||
* @param scene
|
||||
* @private
|
||||
*/
|
||||
private doMysteryEncounterBattle(scene: BattleScene) {
|
||||
const encounterMode = scene.currentBattle.mysteryEncounter!.encounterMode;
|
||||
if (encounterMode === MysteryEncounterMode.WILD_BATTLE || encounterMode === MysteryEncounterMode.BOSS_BATTLE) {
|
||||
// Summons the wild/boss Pokemon
|
||||
@ -342,7 +367,12 @@ export class MysteryEncounterBattlePhase extends Phase {
|
||||
}
|
||||
}
|
||||
|
||||
endBattleSetup(scene: BattleScene) {
|
||||
/**
|
||||
* Initiate SummonPhases, scanner phases, PostSummon phases, etc.
|
||||
* @param scene
|
||||
* @private
|
||||
*/
|
||||
private endBattleSetup(scene: BattleScene) {
|
||||
const enemyField = scene.getEnemyField();
|
||||
const encounterMode = scene.currentBattle.mysteryEncounter!.encounterMode;
|
||||
|
||||
@ -385,11 +415,14 @@ export class MysteryEncounterBattlePhase extends Phase {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove?
|
||||
handleTutorial(this.scene, Tutorial.Access_Menu).then(() => super.end());
|
||||
this.end();
|
||||
}
|
||||
|
||||
showEnemyTrainer(): void {
|
||||
/**
|
||||
* Ease in enemy trainer
|
||||
* @private
|
||||
*/
|
||||
private showEnemyTrainer(): void {
|
||||
// Show enemy trainer
|
||||
const trainer = this.scene.currentBattle.trainer;
|
||||
if (!trainer) {
|
||||
@ -413,7 +446,7 @@ export class MysteryEncounterBattlePhase extends Phase {
|
||||
});
|
||||
}
|
||||
|
||||
hideEnemyTrainer(): void {
|
||||
private hideEnemyTrainer(): void {
|
||||
this.scene.tweens.add({
|
||||
targets: this.scene.currentBattle.trainer,
|
||||
x: "+=16",
|
||||
@ -444,6 +477,9 @@ export class MysteryEncounterRewardsPhase extends Phase {
|
||||
this.addHealPhase = addHealPhase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs {@link MysteryEncounter.doContinueEncounter} and ends phase, OR {@link MysteryEncounter.onRewards} then continues encounter
|
||||
*/
|
||||
start() {
|
||||
super.start();
|
||||
const encounter = this.scene.currentBattle.mysteryEncounter!;
|
||||
@ -466,6 +502,9 @@ export class MysteryEncounterRewardsPhase extends Phase {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues encounter EXP and rewards phases, PostMysteryEncounterPhase, and ends phase
|
||||
*/
|
||||
doEncounterRewardsAndContinue() {
|
||||
const encounter = this.scene.currentBattle.mysteryEncounter!;
|
||||
|
||||
@ -501,6 +540,9 @@ export class PostMysteryEncounterPhase extends Phase {
|
||||
this.onPostOptionSelect = this.scene.currentBattle.mysteryEncounter?.selectedOption?.onPostOptionPhase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs {@link MysteryEncounter.onPostOptionSelect} then continues encounter
|
||||
*/
|
||||
start() {
|
||||
super.start();
|
||||
|
||||
@ -518,6 +560,9 @@ export class PostMysteryEncounterPhase extends Phase {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues NewBattlePhase, plays outro dialogue and ends phase
|
||||
*/
|
||||
continueEncounter() {
|
||||
const endPhase = () => {
|
||||
this.scene.pushPhase(new NewBattlePhase(this.scene));
|
||||
|
@ -1,4 +1,4 @@
|
||||
import BattleScene from "#app/battle-scene.js";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import { Phase } from "#app/phase";
|
||||
|
||||
export class PartyExpPhase extends Phase {
|
||||
|
@ -65,7 +65,7 @@ export class QuietFormChangePhase extends BattlePhase {
|
||||
pokemonFormTintSprite.setVisible(false);
|
||||
pokemonFormTintSprite.setTintFill(0xFFFFFF);
|
||||
|
||||
this.scene.playSound("PRSFX- Transform");
|
||||
this.scene.playSound("battle_anims/PRSFX- Transform");
|
||||
|
||||
this.scene.tweens.add({
|
||||
targets: pokemonTintSprite,
|
||||
|
@ -232,7 +232,7 @@ export class SummonPhase extends PartyMemberPokemonPhase {
|
||||
|
||||
pokemon.resetTurnData();
|
||||
|
||||
if (!this.loaded || this.scene.currentBattle.battleType === BattleType.TRAINER || this.scene.currentBattle.battleType === BattleType.MYSTERY_ENCOUNTER || (this.scene.currentBattle.waveIndex % 10) === 1) {
|
||||
if (!this.loaded || [BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER].includes(this.scene.currentBattle.battleType) || (this.scene.currentBattle.waveIndex % 10) === 1) {
|
||||
this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true);
|
||||
this.queuePostSummon();
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ export class TrainerVictoryPhase extends BattlePhase {
|
||||
const trainerType = this.scene.currentBattle.trainer?.config.trainerType!; // TODO: is this bang correct?
|
||||
if (vouchers.hasOwnProperty(TrainerType[trainerType])) {
|
||||
if (!this.scene.validateVoucher(vouchers[TrainerType[trainerType]]) && this.scene.currentBattle.trainer?.config.isBoss) {
|
||||
this.scene.unshiftPhase(new ModifierRewardPhase(this.scene, [modifierTypes.VOUCHER, modifierTypes.VOUCHER, modifierTypes.VOUCHER_PLUS, modifierTypes.VOUCHER_PREMIUM][vouchers[TrainerType[trainerType]].voucherType]));
|
||||
this.scene.unshiftPhase(new ModifierRewardPhase(this.scene, [modifierTypes.VOUCHER_PLUS, modifierTypes.VOUCHER_PLUS, modifierTypes.VOUCHER_PLUS, modifierTypes.VOUCHER_PREMIUM][vouchers[TrainerType[trainerType]].voucherType]));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import { applyPreWeatherEffectAbAttrs, SuppressWeatherEffectAbAttr, PreWeatherDamageAbAttr, applyAbAttrs, BlockNonDirectDamageAbAttr, applyPostWeatherLapseAbAttrs, PostWeatherLapseAbAttr } from "#app/data/ability.js";
|
||||
import { applyPreWeatherEffectAbAttrs, SuppressWeatherEffectAbAttr, PreWeatherDamageAbAttr, applyAbAttrs, BlockNonDirectDamageAbAttr, applyPostWeatherLapseAbAttrs, PostWeatherLapseAbAttr } from "#app/data/ability";
|
||||
import { CommonAnim } from "#app/data/battle-anims";
|
||||
import { Weather, getWeatherDamageMessage, getWeatherLapseMessage } from "#app/data/weather";
|
||||
import { BattlerTagType } from "#app/enums/battler-tag-type";
|
||||
|
@ -7,7 +7,7 @@ import * as Utils from "../utils";
|
||||
import { PlayerGender } from "#enums/player-gender";
|
||||
import { Challenge, FreshStartChallenge, SingleGenerationChallenge, SingleTypeChallenge, InverseBattleChallenge } from "#app/data/challenge";
|
||||
import { ConditionFn } from "#app/@types/common";
|
||||
import { Stat, getShortenedStatKey } from "#app/enums/stat";
|
||||
import { Stat, getShortenedStatKey } from "#app/enums/stat";
|
||||
import { Challenges } from "#app/enums/challenges";
|
||||
|
||||
export enum AchvTier {
|
||||
@ -197,7 +197,7 @@ export function getAchievementDescription(localizationKey: string): string {
|
||||
case "100_RIBBONS":
|
||||
return i18next.t("achv:RibbonAchv.description", {context: genderStr, "ribbonAmount": achvs._100_RIBBONS.ribbonAmount.toLocaleString("en-US")});
|
||||
case "TRANSFER_MAX_STAT_STAGE":
|
||||
return i18next.t("achv:TRANSFER_MAX_BATTLE_STAT.description", { context: genderStr });
|
||||
return i18next.t("achv:TRANSFER_MAX_STAT_STAGE.description", { context: genderStr });
|
||||
case "MAX_FRIENDSHIP":
|
||||
return i18next.t("achv:MAX_FRIENDSHIP.description", { context: genderStr });
|
||||
case "MEGA_EVOLVE":
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { allSpecies } from "#app/data/pokemon-species.js";
|
||||
import { allSpecies } from "#app/data/pokemon-species";
|
||||
import { AbilityAttr, defaultStarterSpecies, DexAttr, SessionSaveData, SystemSaveData } from "./game-data";
|
||||
import { SettingKeys } from "./settings/settings";
|
||||
|
||||
@ -31,7 +31,7 @@ export function applySessionDataPatches(data: SessionSaveData) {
|
||||
|
||||
// From [ stat, battlesLeft ] to [ stat, maxBattles, battleCount ]
|
||||
m.args = [ newStat, 5, m.args[1] ];
|
||||
} else if (m.className === "DoubleBattleChanceBoosterModifier") {
|
||||
} else if (m.className === "DoubleBattleChanceBoosterModifier" && m.args.length === 1) {
|
||||
let maxBattles: number;
|
||||
switch (m.typeId) {
|
||||
case "MAX_LURE":
|
||||
@ -53,6 +53,8 @@ export function applySessionDataPatches(data: SessionSaveData) {
|
||||
data.enemyModifiers.forEach((m) => {
|
||||
if (m.className === "PokemonBaseStatModifier") {
|
||||
m.className = "BaseStatModifier";
|
||||
} else if (m.className === "PokemonResetNegativeStatStageModifier") {
|
||||
m.className = "ResetNegativeStatStageModifier";
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -74,7 +76,7 @@ export function applySystemDataPatches(data: SystemSaveData) {
|
||||
if (data.starterData) {
|
||||
// Migrate ability starter data if empty for caught species
|
||||
Object.keys(data.starterData).forEach(sd => {
|
||||
if (data.dexData[sd].caughtAttr && !data.starterData[sd].abilityAttr) {
|
||||
if (data.dexData[sd]?.caughtAttr && (data.starterData[sd] && !data.starterData[sd].abilityAttr)) {
|
||||
data.starterData[sd].abilityAttr = 1;
|
||||
}
|
||||
});
|
||||
@ -102,9 +104,11 @@ export function applySystemDataPatches(data: SystemSaveData) {
|
||||
// --- PATCHES ---
|
||||
|
||||
// Fix Starter Data
|
||||
if (data.gameVersion) {
|
||||
for (const starterId of defaultStarterSpecies) {
|
||||
for (const starterId of defaultStarterSpecies) {
|
||||
if (data.starterData[starterId]?.abilityAttr) {
|
||||
data.starterData[starterId].abilityAttr |= AbilityAttr.ABILITY_1;
|
||||
}
|
||||
if (data.dexData[starterId]?.caughtAttr) {
|
||||
data.dexData[starterId].caughtAttr |= DexAttr.FEMALE;
|
||||
}
|
||||
}
|
||||
|
118
src/test/eggs/manaphy-egg.test.ts
Normal file
@ -0,0 +1,118 @@
|
||||
import { Egg } from "#app/data/egg";
|
||||
import { EggSourceType } from "#app/enums/egg-source-types";
|
||||
import { EggTier } from "#app/enums/egg-type";
|
||||
import { Species } from "#enums/species";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
describe("Manaphy Eggs", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
const EGG_HATCH_COUNT: integer = 48;
|
||||
let rngSweepProgress: number = 0;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
game = new GameManager(phaserGame);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await game.importData("src/test/utils/saves/everything.prsv");
|
||||
|
||||
/**
|
||||
* In our tests, we will perform an "RNG sweep" by letting rngSweepProgress
|
||||
* increase uniformly from 0 to 1 in order to get a uniform sample of the
|
||||
* possible RNG outcomes. This will let us quickly and consistently find
|
||||
* the probability of each RNG outcome.
|
||||
*/
|
||||
vi.spyOn(Phaser.Math.RND, "realInRange").mockImplementation((min: number, max: number) => {
|
||||
return rngSweepProgress * (max - min) + min;
|
||||
});
|
||||
});
|
||||
|
||||
it("should have correct Manaphy rates and Rare Egg Move rates, from the egg gacha", () => {
|
||||
const scene = game.scene;
|
||||
|
||||
let manaphyCount = 0;
|
||||
let phioneCount = 0;
|
||||
let rareEggMoveCount = 0;
|
||||
for (let i = 0; i < EGG_HATCH_COUNT; i++) {
|
||||
rngSweepProgress = (2 * i + 1) / (2 * EGG_HATCH_COUNT);
|
||||
|
||||
const newEgg = new Egg({ scene, tier: EggTier.COMMON, sourceType: EggSourceType.GACHA_SHINY, id: 204 });
|
||||
const newHatch = newEgg.generatePlayerPokemon(scene);
|
||||
if (newHatch.species.speciesId === Species.MANAPHY) {
|
||||
manaphyCount++;
|
||||
} else if (newHatch.species.speciesId === Species.PHIONE) {
|
||||
phioneCount++;
|
||||
}
|
||||
if (newEgg.eggMoveIndex === 3) {
|
||||
rareEggMoveCount++;
|
||||
}
|
||||
}
|
||||
|
||||
expect(manaphyCount + phioneCount).toBe(EGG_HATCH_COUNT);
|
||||
expect(manaphyCount).toBe(1/8 * EGG_HATCH_COUNT);
|
||||
expect(rareEggMoveCount).toBe(1/12 * EGG_HATCH_COUNT);
|
||||
});
|
||||
|
||||
it("should have correct Manaphy rates and Rare Egg Move rates, from Phione species eggs", () => {
|
||||
const scene = game.scene;
|
||||
|
||||
let manaphyCount = 0;
|
||||
let phioneCount = 0;
|
||||
let rareEggMoveCount = 0;
|
||||
for (let i = 0; i < EGG_HATCH_COUNT; i++) {
|
||||
rngSweepProgress = (2 * i + 1) / (2 * EGG_HATCH_COUNT);
|
||||
|
||||
const newEgg = new Egg({ scene, species: Species.PHIONE, sourceType: EggSourceType.SAME_SPECIES_EGG });
|
||||
const newHatch = newEgg.generatePlayerPokemon(scene);
|
||||
if (newHatch.species.speciesId === Species.MANAPHY) {
|
||||
manaphyCount++;
|
||||
} else if (newHatch.species.speciesId === Species.PHIONE) {
|
||||
phioneCount++;
|
||||
}
|
||||
if (newEgg.eggMoveIndex === 3) {
|
||||
rareEggMoveCount++;
|
||||
}
|
||||
}
|
||||
|
||||
expect(manaphyCount + phioneCount).toBe(EGG_HATCH_COUNT);
|
||||
expect(manaphyCount).toBe(1/8 * EGG_HATCH_COUNT);
|
||||
expect(rareEggMoveCount).toBe(1/6 * EGG_HATCH_COUNT);
|
||||
});
|
||||
|
||||
it("should have correct Manaphy rates and Rare Egg Move rates, from Manaphy species eggs", () => {
|
||||
const scene = game.scene;
|
||||
|
||||
let manaphyCount = 0;
|
||||
let phioneCount = 0;
|
||||
let rareEggMoveCount = 0;
|
||||
for (let i = 0; i < EGG_HATCH_COUNT; i++) {
|
||||
rngSweepProgress = (2 * i + 1) / (2 * EGG_HATCH_COUNT);
|
||||
|
||||
const newEgg = new Egg({ scene, species: Species.MANAPHY, sourceType: EggSourceType.SAME_SPECIES_EGG });
|
||||
const newHatch = newEgg.generatePlayerPokemon(scene);
|
||||
if (newHatch.species.speciesId === Species.MANAPHY) {
|
||||
manaphyCount++;
|
||||
} else if (newHatch.species.speciesId === Species.PHIONE) {
|
||||
phioneCount++;
|
||||
}
|
||||
if (newEgg.eggMoveIndex === 3) {
|
||||
rareEggMoveCount++;
|
||||
}
|
||||
}
|
||||
|
||||
expect(phioneCount).toBe(0);
|
||||
expect(manaphyCount).toBe(EGG_HATCH_COUNT);
|
||||
expect(rareEggMoveCount).toBe(1/6 * EGG_HATCH_COUNT);
|
||||
});
|
||||
});
|
@ -1,13 +1,13 @@
|
||||
import { Moves } from "#app/enums/moves.js";
|
||||
import { Species } from "#app/enums/species.js";
|
||||
import { Moves } from "#app/enums/moves";
|
||||
import { Species } from "#app/enums/species";
|
||||
import { DoubleBattleChanceBoosterModifier } from "#app/modifier/modifier";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
import { ShopCursorTarget } from "#app/enums/shop-cursor-target.js";
|
||||
import { Mode } from "#app/ui/ui.js";
|
||||
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler.js";
|
||||
import { Button } from "#app/enums/buttons.js";
|
||||
import { ShopCursorTarget } from "#app/enums/shop-cursor-target";
|
||||
import { Mode } from "#app/ui/ui";
|
||||
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
|
||||
import { Button } from "#app/enums/buttons";
|
||||
|
||||
describe("Items - Double Battle Chance Boosters", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
|
65
src/test/moves/after_you.test.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { Abilities } from "#app/enums/abilities";
|
||||
import { MoveResult } from "#app/field/pokemon";
|
||||
import { MovePhase } from "#app/phases/move-phase";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
|
||||
const TIMEOUT = 20 * 1000;
|
||||
|
||||
describe("Moves - After You", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.battleType("double")
|
||||
.enemyLevel(5)
|
||||
.enemySpecies(Species.PIKACHU)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyMoveset(Moves.SPLASH)
|
||||
.ability(Abilities.BALL_FETCH)
|
||||
.moveset([Moves.AFTER_YOU, Moves.SPLASH]);
|
||||
});
|
||||
|
||||
it("makes the target move immediately after the user", async () => {
|
||||
await game.classicMode.startBattle([Species.REGIELEKI, Species.SHUCKLE]);
|
||||
|
||||
game.move.select(Moves.AFTER_YOU, 0, BattlerIndex.PLAYER_2);
|
||||
game.move.select(Moves.SPLASH, 1);
|
||||
|
||||
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||
await game.phaseInterceptor.to(MovePhase, false);
|
||||
const phase = game.scene.getCurrentPhase() as MovePhase;
|
||||
expect(phase.pokemon).toBe(game.scene.getPlayerField()[1]);
|
||||
await game.phaseInterceptor.to("MoveEndPhase");
|
||||
}, TIMEOUT);
|
||||
|
||||
it("fails if target already moved", async () => {
|
||||
game.override.enemySpecies(Species.SHUCKLE);
|
||||
await game.classicMode.startBattle([Species.REGIELEKI, Species.PIKACHU]);
|
||||
|
||||
game.move.select(Moves.SPLASH);
|
||||
game.move.select(Moves.AFTER_YOU, 1, BattlerIndex.PLAYER);
|
||||
|
||||
await game.phaseInterceptor.to("MoveEndPhase");
|
||||
await game.phaseInterceptor.to("MoveEndPhase");
|
||||
await game.phaseInterceptor.to(MovePhase);
|
||||
|
||||
expect(game.scene.getPlayerField()[1].getLastXMoves(1)[0].result).toBe(MoveResult.FAIL);
|
||||
}, TIMEOUT);
|
||||
});
|
@ -1,7 +1,7 @@
|
||||
import { LostAtSeaEncounter } from "#app/data/mystery-encounters/encounters/lost-at-sea-encounter";
|
||||
import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters";
|
||||
import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { getPokemonSpecies } from "#app/data/pokemon-species.js";
|
||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import { Biome } from "#app/enums/biome";
|
||||
import { Moves } from "#app/enums/moves";
|
||||
import { MysteryEncounterType } from "#app/enums/mystery-encounter-type";
|
||||
|
@ -94,6 +94,9 @@ export default class GameManager {
|
||||
this.challengeMode = new ChallengeModeHelper(this);
|
||||
this.settings = new SettingsHelper(this);
|
||||
this.reload = new ReloadHelper(this);
|
||||
|
||||
// Disables Mystery Encounters on all tests (can be overridden at test level)
|
||||
this.override.mysteryEncounterChance(0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -96,7 +96,7 @@ export function getMovePosition(scene: BattleScene, pokemonIndex: 0 | 1, move: M
|
||||
* @param scene
|
||||
* @param species
|
||||
*/
|
||||
export function initSceneWithoutEncounterPhase(scene, species?: Species[]) {
|
||||
export function initSceneWithoutEncounterPhase(scene: BattleScene, species?: Species[]) {
|
||||
const starters = generateStarter(scene, species);
|
||||
starters.forEach((starter) => {
|
||||
const starterProps = scene.gameData.getSpeciesDexAttrProps(starter.species, starter.dexAttr);
|
||||
@ -104,7 +104,7 @@ export function initSceneWithoutEncounterPhase(scene, species?: Species[]) {
|
||||
const starterGender = Gender.MALE;
|
||||
const starterIvs = scene.gameData.dexData[starter.species.speciesId].ivs.slice(0);
|
||||
const starterPokemon = scene.addPlayerPokemon(starter.species, scene.gameMode.getStartingLevel(), starter.abilityIndex, starterFormIndex, starterGender, starterProps.shiny, starterProps.variant, starterIvs, starter.nature);
|
||||
starterPokemon.tryPopulateMoveset(starter.moveset);
|
||||
starter.moveset && starterPokemon.tryPopulateMoveset(starter.moveset);
|
||||
scene.getParty().push(starterPokemon);
|
||||
});
|
||||
|
||||
|
@ -8,7 +8,7 @@ import * as GameMode from "#app/game-mode";
|
||||
import { GameModes, getGameMode } from "#app/game-mode";
|
||||
import { ModifierOverride } from "#app/modifier/modifier-type";
|
||||
import Overrides from "#app/overrides";
|
||||
import { MockInstance, vi } from "vitest";
|
||||
import { vi } from "vitest";
|
||||
import { GameManagerHelper } from "./gameManagerHelper";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
@ -333,9 +333,9 @@ export class OverridesHelper extends GameManagerHelper {
|
||||
mysteryEncounterChance(percentage: number) {
|
||||
const maxRate: number = 256; // 100%
|
||||
const rate = maxRate * (percentage / 100);
|
||||
const spy = vi.spyOn(Overrides, "MYSTERY_ENCOUNTER_RATE_OVERRIDE", "get").mockReturnValue(rate);
|
||||
vi.spyOn(Overrides, "MYSTERY_ENCOUNTER_RATE_OVERRIDE", "get").mockReturnValue(rate);
|
||||
this.log(`Mystery encounter chance set to ${percentage}% (=${rate})!`);
|
||||
return spy;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -343,10 +343,10 @@ export class OverridesHelper extends GameManagerHelper {
|
||||
* @returns spy instance
|
||||
* @param tier
|
||||
*/
|
||||
mysteryEncounterTier(tier: MysteryEncounterTier): MockInstance {
|
||||
const spy = vi.spyOn(Overrides, "MYSTERY_ENCOUNTER_TIER_OVERRIDE", "get").mockReturnValue(tier);
|
||||
mysteryEncounterTier(tier: MysteryEncounterTier) {
|
||||
vi.spyOn(Overrides, "MYSTERY_ENCOUNTER_TIER_OVERRIDE", "get").mockReturnValue(tier);
|
||||
this.log(`Mystery encounter tier set to ${tier}!`);
|
||||
return spy;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -355,9 +355,9 @@ export class OverridesHelper extends GameManagerHelper {
|
||||
* @returns spy instance
|
||||
*/
|
||||
mysteryEncounter(encounterType: MysteryEncounterType) {
|
||||
const spy = vi.spyOn(Overrides, "MYSTERY_ENCOUNTER_OVERRIDE", "get").mockReturnValue(encounterType);
|
||||
vi.spyOn(Overrides, "MYSTERY_ENCOUNTER_OVERRIDE", "get").mockReturnValue(encounterType);
|
||||
this.log(`Mystery encounter override set to ${encounterType}!`);
|
||||
return spy;
|
||||
return this;
|
||||
}
|
||||
|
||||
private log(...params: any[]) {
|
||||
|
@ -14,7 +14,7 @@ export default class MockContainer implements MockGameObject {
|
||||
public frame;
|
||||
protected textureManager;
|
||||
public list: MockGameObject[] = [];
|
||||
name: string;
|
||||
public name: string;
|
||||
|
||||
constructor(textureManager: MockTextureManager, x, y) {
|
||||
this.x = x;
|
||||
@ -36,9 +36,7 @@ export default class MockContainer implements MockGameObject {
|
||||
// same as remove or destroy
|
||||
}
|
||||
|
||||
removeBetween(startIndex, endIndex, destroyChild) {
|
||||
// Removes multiple children across an index range
|
||||
}
|
||||
removeBetween = vi.fn((startIndex, endIndex, destroyChild) => {});
|
||||
|
||||
addedToScene() {
|
||||
// This callback is invoked when this Game Object is added to a Scene.
|
||||
@ -156,9 +154,7 @@ export default class MockContainer implements MockGameObject {
|
||||
// Sends this Game Object to the back of its parent's display list.
|
||||
}
|
||||
|
||||
moveTo(obj) {
|
||||
// Moves this Game Object to the given index in the list.
|
||||
}
|
||||
moveTo = vi.fn((obj) => {});
|
||||
|
||||
moveAbove(obj) {
|
||||
// Moves this Game Object to be above the given Game Object in the display list.
|
||||
@ -215,9 +211,9 @@ export default class MockContainer implements MockGameObject {
|
||||
return this.list;
|
||||
}
|
||||
|
||||
getByName(key: string) {
|
||||
getByName = vi.fn((key: string) => {
|
||||
return this.list.find(v => v.name === key) ?? new MockContainer(this.textureManager, 0, 0);
|
||||
}
|
||||
});
|
||||
|
||||
disableInteractive = vi.fn();
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import { MockGameObject } from "../mockGameObject";
|
||||
export default class MockGraphics implements MockGameObject {
|
||||
private scene;
|
||||
public list: MockGameObject[] = [];
|
||||
name: string;
|
||||
public name: string;
|
||||
constructor(textureManager, config) {
|
||||
this.scene = textureManager.scene;
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ export default class MockRectangle implements MockGameObject {
|
||||
private fillColor;
|
||||
private scene;
|
||||
public list: MockGameObject[] = [];
|
||||
name: string;
|
||||
public name: string;
|
||||
|
||||
constructor(textureManager, x, y, width, height, fillColor) {
|
||||
this.fillColor = fillColor;
|
||||
|
@ -15,7 +15,7 @@ export default class MockSprite implements MockGameObject {
|
||||
public scene;
|
||||
public anims;
|
||||
public list: MockGameObject[] = [];
|
||||
name: string;
|
||||
public name: string;
|
||||
constructor(textureManager, x, y, texture) {
|
||||
this.textureManager = textureManager;
|
||||
this.scene = textureManager.scene;
|
||||
|
@ -11,7 +11,7 @@ export default class MockText implements MockGameObject {
|
||||
public list: MockGameObject[] = [];
|
||||
public style;
|
||||
public text = "";
|
||||
name: string;
|
||||
public name: string;
|
||||
public color?: string;
|
||||
|
||||
constructor(textureManager, x, y, content, styleOptions) {
|
||||
@ -80,19 +80,19 @@ export default class MockText implements MockGameObject {
|
||||
return result;
|
||||
}
|
||||
|
||||
showText(text, delay, callback, callbackDelay, prompt, promptDelay) {
|
||||
showText = vi.fn((text: string, delay?: integer | null, callback?: Function | null, callbackDelay?: integer | null, prompt?: boolean | null, promptDelay?: integer | null) => {
|
||||
this.scene.messageWrapper.showText(text, delay, callback, callbackDelay, prompt, promptDelay);
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
showDialogue(text, name, delay, callback, callbackDelay, promptDelay) {
|
||||
this.scene.messageWrapper.showDialogue(text, name, delay, callback, callbackDelay, promptDelay);
|
||||
showDialogue = vi.fn((keyOrText: string, name: string | undefined, delay: integer | null = 0, callback: Function, callbackDelay?: integer, promptDelay?: integer) => {
|
||||
this.scene.messageWrapper.showDialogue(keyOrText, name, delay, callback, callbackDelay, promptDelay);
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setScale(scale) {
|
||||
// return this.phaserText.setScale(scale);
|
||||
@ -257,13 +257,9 @@ export default class MockText implements MockGameObject {
|
||||
};
|
||||
}
|
||||
|
||||
disableInteractive() {
|
||||
// Disables interaction with this Game Object.
|
||||
}
|
||||
disableInteractive = vi.fn();
|
||||
|
||||
clearTint() {
|
||||
// Clears tint on this Game Object.
|
||||
}
|
||||
clearTint = vi.fn();
|
||||
|
||||
add(obj) {
|
||||
// Adds a child to this Game Object.
|
||||
|
@ -12,7 +12,7 @@ export default class MockTexture implements MockGameObject {
|
||||
public source;
|
||||
public frames: object;
|
||||
public firstFrame: string;
|
||||
name: string;
|
||||
public name: string;
|
||||
|
||||
constructor(manager, key: string, source) {
|
||||
this.manager = manager;
|
||||
|
@ -1,129 +0,0 @@
|
||||
import { Weather, WeatherType } from "#app/data/weather";
|
||||
import { Biome } from "#app/enums/biome";
|
||||
import { MockInstance, vi } from "vitest";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import * as GameMode from "#app/game-mode";
|
||||
import { GameModes, getGameMode } from "#app/game-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import Overrides from "#app/overrides";
|
||||
import { ModifierOverride } from "#app/modifier/modifier-type";
|
||||
|
||||
/**
|
||||
* Helper to handle overrides in tests
|
||||
*/
|
||||
export class OverridesHelper {
|
||||
game: GameManager;
|
||||
constructor(game: GameManager) {
|
||||
this.game = game;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the encounter chance for a mystery encounter.
|
||||
* @param percentage the encounter chance in %
|
||||
* @returns spy instance
|
||||
*/
|
||||
mysteryEncounterChance(percentage: number) {
|
||||
const maxRate: number = 256; // 100%
|
||||
const rate = maxRate * (percentage / 100);
|
||||
const spy = vi.spyOn(Overrides, "MYSTERY_ENCOUNTER_RATE_OVERRIDE", "get").mockReturnValue(rate);
|
||||
this.log(`Mystery encounter chance set to ${percentage}% (=${rate})!`);
|
||||
return spy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the encounter chance for a mystery encounter.
|
||||
* @returns spy instance
|
||||
* @param tier
|
||||
*/
|
||||
mysteryEncounterTier(tier: MysteryEncounterTier): MockInstance {
|
||||
const spy = vi.spyOn(Overrides, "MYSTERY_ENCOUNTER_TIER_OVERRIDE", "get").mockReturnValue(tier);
|
||||
this.log(`Mystery encounter tier set to ${tier}!`);
|
||||
return spy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the encounter that spawns for the scene
|
||||
* @param encounterType
|
||||
* @returns spy instance
|
||||
*/
|
||||
mysteryEncounter(encounterType: MysteryEncounterType) {
|
||||
const spy = vi.spyOn(Overrides, "MYSTERY_ENCOUNTER_OVERRIDE", "get").mockReturnValue(encounterType);
|
||||
this.log(`Mystery encounter override set to ${encounterType}!`);
|
||||
return spy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the starting biome
|
||||
* @warning Any event listeners that are attached to [NewArenaEvent](events\battle-scene.ts) may need to be handled down the line
|
||||
* @param biome the biome to set
|
||||
*/
|
||||
startingBiome(biome: Biome) {
|
||||
this.game.scene.newArena(biome);
|
||||
this.log(`Starting biome set to ${Biome[biome]} (=${biome})!`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the starting wave (index)
|
||||
* @param wave the wave (index) to set. Classic: `1`-`200`
|
||||
* @returns spy instance
|
||||
*/
|
||||
startingWave(wave: number) {
|
||||
const spy = vi.spyOn(Overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(wave);
|
||||
this.log(`Starting wave set to ${wave}!`);
|
||||
return spy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override each wave to have or not have standard trainer battles
|
||||
* @returns spy instance
|
||||
* @param disable - true
|
||||
*/
|
||||
disableTrainerWaves(disable: boolean): MockInstance {
|
||||
const realFn = getGameMode;
|
||||
const spy = vi.spyOn(GameMode, "getGameMode").mockImplementation((gameMode: GameModes) => {
|
||||
const mode = realFn(gameMode);
|
||||
mode.hasTrainers = !disable;
|
||||
return mode;
|
||||
});
|
||||
this.log(`Standard trainer waves are ${disable? "disabled" : "enabled"}!`);
|
||||
return spy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the weather (type)
|
||||
* @param type weather type to set
|
||||
* @returns spy instance
|
||||
*/
|
||||
weather(type: WeatherType) {
|
||||
const spy = vi.spyOn(Overrides, "WEATHER_OVERRIDE", "get").mockReturnValue(type);
|
||||
this.log(`Weather set to ${Weather[type]} (=${type})!`);
|
||||
return spy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the seed
|
||||
* @param seed the seed to set
|
||||
* @returns spy instance
|
||||
*/
|
||||
seed(seed: string) {
|
||||
const spy = vi.spyOn(this.game.scene, "resetSeed").mockImplementation(() => {
|
||||
this.game.scene.waveSeed = seed;
|
||||
Phaser.Math.RND.sow([seed]);
|
||||
this.game.scene.rngCounter = 0;
|
||||
});
|
||||
this.game.scene.resetSeed();
|
||||
this.log(`Seed set to "${seed}"!`);
|
||||
return spy;
|
||||
}
|
||||
|
||||
starterHeldItems(modifiers: ModifierOverride[]) {
|
||||
const spy = vi.spyOn(Overrides, "STARTING_MODIFIER_OVERRIDE", "get").mockReturnValue(modifiers);
|
||||
this.log(`Starting modifiers set to ${modifiers.map(m => JSON.stringify(m)).join(", ")}!`);
|
||||
return spy;
|
||||
}
|
||||
|
||||
private log(...params: any[]) {
|
||||
console.log("Overrides:", ...params);
|
||||
}
|
||||
}
|
@ -50,16 +50,15 @@ import {
|
||||
MysteryEncounterRewardsPhase,
|
||||
PostMysteryEncounterPhase
|
||||
} from "#app/phases/mystery-encounter-phases";
|
||||
import { LearnMovePhase } from "#app/phases/learn-move-phase";
|
||||
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
|
||||
import { PartyExpPhase } from "#app/phases/party-exp-phase";
|
||||
|
||||
export interface PromptHandler {
|
||||
phaseTarget?;
|
||||
mode?;
|
||||
callback?;
|
||||
expireFn?;
|
||||
awaitingActionInput?;
|
||||
phaseTarget?: string;
|
||||
mode?: Mode;
|
||||
callback?: () => void;
|
||||
expireFn?: () => void;
|
||||
awaitingActionInput?: boolean;
|
||||
}
|
||||
|
||||
export default class PhaseInterceptor {
|
||||
@ -369,7 +368,10 @@ export default class PhaseInterceptor {
|
||||
if (expireFn) {
|
||||
this.prompts.shift();
|
||||
} else if (currentMode === actionForNextPrompt.mode && currentPhase === actionForNextPrompt.phaseTarget && currentHandler.active && (!actionForNextPrompt.awaitingActionInput || (actionForNextPrompt.awaitingActionInput && currentHandler.awaitingActionInput))) {
|
||||
this.prompts.shift()?.callback();
|
||||
const prompt = this.prompts.shift();
|
||||
if (prompt?.callback) {
|
||||
prompt.callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -1,4 +1,3 @@
|
||||
import "#test/fontFace.setup";
|
||||
import "vitest-canvas-mock";
|
||||
|
||||
import { initLoggedInUser } from "#app/account";
|
||||
@ -13,8 +12,7 @@ import { initAchievements } from "#app/system/achv";
|
||||
import { initVouchers } from "#app/system/voucher";
|
||||
import { initStatsKeys } from "#app/ui/game-stats-ui-handler";
|
||||
import { initMysteryEncounters } from "#app/data/mystery-encounters/mystery-encounters";
|
||||
import { beforeAll, beforeEach, vi } from "vitest";
|
||||
import Overrides from "#app/overrides";
|
||||
import { beforeAll, vi } from "vitest";
|
||||
|
||||
/** Mock the override import to always return default values, ignoring any custom overrides. */
|
||||
vi.mock("#app/overrides", async (importOriginal) => {
|
||||
@ -49,8 +47,3 @@ beforeAll(() => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Disables Mystery Encounters on all tests (can be overridden at test level)
|
||||
beforeEach( () => {
|
||||
vi.spyOn(Overrides, "MYSTERY_ENCOUNTER_RATE_OVERRIDE", "get").mockReturnValue(0);
|
||||
});
|
||||
|
@ -25,14 +25,14 @@ interface TimedEvent extends EventBanner {
|
||||
|
||||
const timedEvents: TimedEvent[] = [
|
||||
{
|
||||
name: "September Update",
|
||||
name: "Egg Skip Update",
|
||||
eventType: EventType.GENERIC,
|
||||
startDate: new Date(Date.UTC(2024, 7, 28, 0)),
|
||||
endDate: new Date(Date.UTC(2024, 8, 15, 0)),
|
||||
bannerKey: "september-update",
|
||||
startDate: new Date(Date.UTC(2024, 8, 8, 0)),
|
||||
endDate: new Date(Date.UTC(2024, 8, 12, 0)),
|
||||
bannerKey: "egg-update",
|
||||
xPosition: 19,
|
||||
yPosition: 115,
|
||||
scale: 0.30,
|
||||
yPosition: 120,
|
||||
scale: 0.21,
|
||||
availableLangs: ["en", "de", "it", "fr", "ja", "ko", "es", "pt-BR", "zh-CN"]
|
||||
}
|
||||
];
|
||||
@ -94,9 +94,9 @@ export class TimedEventDisplay extends Phaser.GameObjects.Container {
|
||||
let key = this.event.bannerKey;
|
||||
if (lang && this.event.availableLangs && this.event.availableLangs.length > 0) {
|
||||
if (this.event.availableLangs.includes(lang)) {
|
||||
key += "-"+lang;
|
||||
key += "_"+lang;
|
||||
} else {
|
||||
key += "-en";
|
||||
key += "_en";
|
||||
}
|
||||
}
|
||||
console.log(this.event.bannerKey);
|
||||
|
@ -29,8 +29,10 @@ export default class EggSummaryUiHandler extends MessageUiHandler {
|
||||
private summaryContainer: Phaser.GameObjects.Container;
|
||||
/** container for the mini pokemon sprites */
|
||||
private pokemonIconSpritesContainer: Phaser.GameObjects.Container;
|
||||
/** container for the icons displayed alongside the mini icons (e.g. shiny, HA capsule) */
|
||||
/** container for the icons displayed on top of the mini pokemon sprites (e.g. shiny, HA capsule) */
|
||||
private pokemonIconsContainer: Phaser.GameObjects.Container;
|
||||
/** container for the elements displayed behind the mini pokemon sprites (e.g. egg rarity bg) */
|
||||
private pokemonBackgroundContainer: Phaser.GameObjects.Container;
|
||||
/** hatch info container that displays the current pokemon / hatch (main element on left hand side) */
|
||||
private infoContainer: PokemonHatchInfoContainer;
|
||||
/** handles jumping animations for the pokemon sprite icons */
|
||||
@ -71,15 +73,17 @@ export default class EggSummaryUiHandler extends MessageUiHandler {
|
||||
this.eggHatchBg.setOrigin(0, 0);
|
||||
this.eggHatchContainer.add(this.eggHatchBg);
|
||||
|
||||
this.pokemonIconsContainer = this.scene.add.container(iconContainerX, iconContainerY);
|
||||
this.pokemonIconSpritesContainer = this.scene.add.container(iconContainerX, iconContainerY);
|
||||
this.summaryContainer.add(this.pokemonIconsContainer);
|
||||
this.summaryContainer.add(this.pokemonIconSpritesContainer);
|
||||
|
||||
this.cursorObj = this.scene.add.image(0, 0, "select_cursor");
|
||||
this.cursorObj.setOrigin(0, 0);
|
||||
this.summaryContainer.add(this.cursorObj);
|
||||
|
||||
this.pokemonIconSpritesContainer = this.scene.add.container(iconContainerX, iconContainerY);
|
||||
this.pokemonIconsContainer = this.scene.add.container(iconContainerX, iconContainerY);
|
||||
this.pokemonBackgroundContainer = this.scene.add.container(iconContainerX, iconContainerY);
|
||||
this.summaryContainer.add(this.pokemonBackgroundContainer);
|
||||
this.summaryContainer.add(this.pokemonIconSpritesContainer);
|
||||
this.summaryContainer.add(this.pokemonIconsContainer);
|
||||
|
||||
this.infoContainer = new PokemonHatchInfoContainer(this.scene, this.summaryContainer);
|
||||
this.infoContainer.setup();
|
||||
this.infoContainer.changeToEggSummaryLayout();
|
||||
@ -95,8 +99,10 @@ export default class EggSummaryUiHandler extends MessageUiHandler {
|
||||
this.summaryContainer.setVisible(false);
|
||||
this.pokemonIconSpritesContainer.removeAll(true);
|
||||
this.pokemonIconsContainer.removeAll(true);
|
||||
this.pokemonBackgroundContainer.removeAll(true);
|
||||
this.eggHatchBg.setVisible(false);
|
||||
this.getUi().hideTooltip();
|
||||
|
||||
// Note: Questions on garbage collection go to @frutescens
|
||||
const activeKeys = this.scene.getActiveKeys();
|
||||
// Removing unnecessary sprites from animation manager
|
||||
@ -117,7 +123,6 @@ export default class EggSummaryUiHandler extends MessageUiHandler {
|
||||
this.eggHatchData.length = 0;
|
||||
// Removes Pokemon icons in EggSummaryUiHandler
|
||||
this.iconAnimHandler.removeAll();
|
||||
console.log("Egg Summary Handler cleared");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -164,25 +169,25 @@ export default class EggSummaryUiHandler extends MessageUiHandler {
|
||||
const offset = 2;
|
||||
const rightSideX = 12;
|
||||
|
||||
const bg = this.scene.add.image(x+2, y+5, "passive_bg");
|
||||
bg.setOrigin(0, 0);
|
||||
bg.setScale(0.75);
|
||||
bg.setVisible(true);
|
||||
this.pokemonIconsContainer.add(bg);
|
||||
const rarityBg = this.scene.add.image(x + 2, y + 5, "passive_bg");
|
||||
rarityBg.setOrigin(0, 0);
|
||||
rarityBg.setScale(0.75);
|
||||
rarityBg.setVisible(true);
|
||||
this.pokemonBackgroundContainer.add(rarityBg);
|
||||
|
||||
// set tint for passive bg
|
||||
switch (getEggTierForSpecies(displayPokemon.species)) {
|
||||
case EggTier.COMMON:
|
||||
bg.setVisible(false);
|
||||
rarityBg.setVisible(false);
|
||||
break;
|
||||
case EggTier.GREAT:
|
||||
bg.setTint(0xabafff);
|
||||
rarityBg.setTint(0xabafff);
|
||||
break;
|
||||
case EggTier.ULTRA:
|
||||
bg.setTint(0xffffaa);
|
||||
rarityBg.setTint(0xffffaa);
|
||||
break;
|
||||
case EggTier.MASTER:
|
||||
bg.setTint(0xdfffaf);
|
||||
rarityBg.setTint(0xdfffaf);
|
||||
break;
|
||||
}
|
||||
const species = displayPokemon.species;
|
||||
@ -192,35 +197,31 @@ export default class EggSummaryUiHandler extends MessageUiHandler {
|
||||
const isShiny = displayPokemon.shiny;
|
||||
|
||||
// set pokemon icon (and replace with base sprite if there is a mismatch)
|
||||
const icon = this.scene.add.sprite(x - offset, y + offset, species.getIconAtlasKey(formIndex, isShiny, variant));
|
||||
icon.setScale(0.5);
|
||||
icon.setOrigin(0, 0);
|
||||
icon.setFrame(species.getIconId(female, formIndex, isShiny, variant));
|
||||
const pokemonIcon = this.scene.add.sprite(x - offset, y + offset, species.getIconAtlasKey(formIndex, isShiny, variant));
|
||||
pokemonIcon.setScale(0.5);
|
||||
pokemonIcon.setOrigin(0, 0);
|
||||
pokemonIcon.setFrame(species.getIconId(female, formIndex, isShiny, variant));
|
||||
|
||||
if (icon.frame.name !== species.getIconId(female, formIndex, isShiny, variant)) {
|
||||
if (pokemonIcon.frame.name !== species.getIconId(female, formIndex, isShiny, variant)) {
|
||||
console.log(`${species.name}'s variant icon does not exist. Replacing with default.`);
|
||||
icon.setTexture(species.getIconAtlasKey(formIndex, false, variant));
|
||||
icon.setFrame(species.getIconId(female, formIndex, false, variant));
|
||||
pokemonIcon.setTexture(species.getIconAtlasKey(formIndex, false, variant));
|
||||
pokemonIcon.setFrame(species.getIconId(female, formIndex, false, variant));
|
||||
}
|
||||
this.pokemonIconSpritesContainer.add(icon);
|
||||
this.iconAnimHandler.addOrUpdate(icon, PokemonIconAnimMode.NONE);
|
||||
this.pokemonIconSpritesContainer.add(pokemonIcon);
|
||||
|
||||
const shiny = this.scene.add.image(x + rightSideX, y + offset * 2, "shiny_star_small");
|
||||
shiny.setScale(0.5);
|
||||
shiny.setVisible(displayPokemon.shiny);
|
||||
shiny.setTint(getVariantTint(displayPokemon.variant));
|
||||
this.pokemonIconsContainer.add(shiny);
|
||||
const shinyIcon = this.scene.add.image(x + rightSideX, y + offset, "shiny_star_small");
|
||||
shinyIcon.setOrigin(0, 0);
|
||||
shinyIcon.setScale(0.5);
|
||||
shinyIcon.setVisible(displayPokemon.shiny);
|
||||
shinyIcon.setTint(getVariantTint(displayPokemon.variant));
|
||||
this.pokemonIconsContainer.add(shinyIcon);
|
||||
|
||||
const ha = this.scene.add.image(x + rightSideX, y + 7, "ha_capsule");
|
||||
ha.setScale(0.5);
|
||||
ha.setVisible((displayPokemon.hasAbility(displayPokemon.species.abilityHidden)));
|
||||
this.pokemonIconsContainer.add(ha);
|
||||
const haIcon = this.scene.add.image(x + rightSideX, y + offset * 4, "ha_capsule");
|
||||
haIcon.setOrigin(0, 0);
|
||||
haIcon.setScale(0.5);
|
||||
haIcon.setVisible(displayPokemon.abilityIndex === 2);
|
||||
this.pokemonIconsContainer.add(haIcon);
|
||||
|
||||
const pb = this.scene.add.image(x + rightSideX, y + offset * 7, "icon_owned");
|
||||
pb.setOrigin(0, 0);
|
||||
pb.setScale(0.5);
|
||||
|
||||
// add animation for new unlocks (new catch or new shiny or new form)
|
||||
const dexEntry = value.dexEntryBeforeUpdate;
|
||||
const caughtAttr = dexEntry.caughtAttr;
|
||||
const newShiny = BigInt(1 << (displayPokemon.shiny ? 1 : 0));
|
||||
@ -228,17 +229,24 @@ export default class EggSummaryUiHandler extends MessageUiHandler {
|
||||
const newShinyOrVariant = ((newShiny & caughtAttr) === BigInt(0)) || ((newVariant & caughtAttr) === BigInt(0));
|
||||
const newForm = (BigInt(1 << displayPokemon.formIndex) * DexAttr.DEFAULT_FORM & caughtAttr) === BigInt(0);
|
||||
|
||||
pb.setVisible(!caughtAttr || newForm);
|
||||
if (!caughtAttr || newShinyOrVariant || newForm) {
|
||||
this.iconAnimHandler.addOrUpdate(icon, PokemonIconAnimMode.PASSIVE);
|
||||
}
|
||||
this.pokemonIconsContainer.add(pb);
|
||||
const pokeballIcon = this.scene.add.image(x + rightSideX, y + offset * 7, "icon_owned");
|
||||
pokeballIcon.setOrigin(0, 0);
|
||||
pokeballIcon.setScale(0.5);
|
||||
pokeballIcon.setVisible(!caughtAttr || newForm);
|
||||
this.pokemonIconsContainer.add(pokeballIcon);
|
||||
|
||||
const em = this.scene.add.image(x, y + offset, "icon_egg_move");
|
||||
em.setOrigin(0, 0);
|
||||
em.setScale(0.5);
|
||||
em.setVisible(value.eggMoveUnlocked);
|
||||
this.pokemonIconsContainer.add(em);
|
||||
const eggMoveIcon = this.scene.add.image(x, y + offset, "icon_egg_move");
|
||||
eggMoveIcon.setOrigin(0, 0);
|
||||
eggMoveIcon.setScale(0.5);
|
||||
eggMoveIcon.setVisible(value.eggMoveUnlocked);
|
||||
this.pokemonIconsContainer.add(eggMoveIcon);
|
||||
|
||||
// add animation to the Pokemon sprite for new unlocks (new catch, new shiny or new form)
|
||||
if (!caughtAttr || newShinyOrVariant || newForm) {
|
||||
this.iconAnimHandler.addOrUpdate(pokemonIcon, PokemonIconAnimMode.PASSIVE);
|
||||
} else {
|
||||
this.iconAnimHandler.addOrUpdate(pokemonIcon, PokemonIconAnimMode.NONE);
|
||||
}
|
||||
});
|
||||
|
||||
this.setCursor(0);
|
||||
@ -256,7 +264,6 @@ export default class EggSummaryUiHandler extends MessageUiHandler {
|
||||
if (phase instanceof EggSummaryPhase) {
|
||||
phase.end();
|
||||
}
|
||||
ui.revertMode();
|
||||
success = true;
|
||||
} else {
|
||||
const count = this.eggHatchData.length;
|
||||
|
@ -122,7 +122,8 @@ export default class FightUiHandler extends UiHandler {
|
||||
}
|
||||
} else {
|
||||
// Cannot back out of fight menu if skipToFightInput is enabled
|
||||
if (this.scene.currentBattle.battleType !== BattleType.MYSTERY_ENCOUNTER || !this.scene.currentBattle.mysteryEncounter?.skipToFightInput) {
|
||||
const { battleType, mysteryEncounter } = this.scene.currentBattle;
|
||||
if (battleType !== BattleType.MYSTERY_ENCOUNTER || !mysteryEncounter?.skipToFightInput) {
|
||||
ui.setMode(Mode.COMMAND, this.fieldIndex);
|
||||
success = true;
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ export abstract class FormModalUiHandler extends ModalUiHandler {
|
||||
const inputBg = addWindow(this.scene, 0, 0, 80, 16, false, false, 0, 0, WindowVariant.XTHIN);
|
||||
|
||||
const isPassword = field.includes(i18next.t("menu:password")) || field.includes(i18next.t("menu:confirmPassword"));
|
||||
const input = addTextInputObject(this.scene, 4, -2, 440, 116, TextStyle.TOOLTIP_CONTENT, { type: isPassword ? "password" : "text", maxLength: isPassword ? 64 : 18 });
|
||||
const input = addTextInputObject(this.scene, 4, -2, 440, 116, TextStyle.TOOLTIP_CONTENT, { type: isPassword ? "password" : "text", maxLength: isPassword ? 64 : 20 });
|
||||
input.setOrigin(0, 0);
|
||||
|
||||
inputContainer.add(inputBg);
|
||||
|
@ -29,6 +29,8 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler {
|
||||
if (delay === null || delay === undefined) {
|
||||
delay = 20;
|
||||
}
|
||||
|
||||
// Pattern matching regex that checks for @c{}, @f{}, @s{}, and @f{} patterns within message text and parses them to their respective behaviors.
|
||||
const charVarMap = new Map<integer, string>();
|
||||
const delayMap = new Map<integer, integer>();
|
||||
const soundMap = new Map<integer, string>();
|
||||
|
@ -22,7 +22,8 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
||||
private cursorObj?: Phaser.GameObjects.Image;
|
||||
|
||||
private optionsContainer: Phaser.GameObjects.Container;
|
||||
private optionScrollTweens: (Phaser.Tweens.Tween | null)[] = [null, null, null, null];
|
||||
// Length = max number of allowable options (4)
|
||||
private optionScrollTweens: (Phaser.Tweens.Tween | null)[] = new Array(4).fill(null);
|
||||
|
||||
private tooltipWindow: Phaser.GameObjects.NineSlice;
|
||||
private tooltipContainer: Phaser.GameObjects.Container;
|
||||
@ -49,7 +50,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
||||
super(scene, Mode.MYSTERY_ENCOUNTER);
|
||||
}
|
||||
|
||||
setup() {
|
||||
override setup() {
|
||||
const ui = this.getUi();
|
||||
|
||||
this.cursorContainer = this.scene.add.container(18, -38.7);
|
||||
@ -89,7 +90,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
||||
this.dexProgressContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, 24, 28), Phaser.Geom.Rectangle.Contains);
|
||||
}
|
||||
|
||||
show(args: any[]): boolean {
|
||||
override show(args: any[]): boolean {
|
||||
super.show(args);
|
||||
|
||||
this.overrideSettings = args[0] as OptionSelectSettings ?? {};
|
||||
@ -119,7 +120,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
||||
return true;
|
||||
}
|
||||
|
||||
processInput(button: Button): boolean {
|
||||
override processInput(button: Button): boolean {
|
||||
const ui = this.getUi();
|
||||
|
||||
let success = false;
|
||||
@ -179,7 +180,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
||||
return success;
|
||||
}
|
||||
|
||||
handleTwoOptionMoveInput(button: Button): boolean {
|
||||
private handleTwoOptionMoveInput(button: Button): boolean {
|
||||
let success = false;
|
||||
const cursor = this.getCursor();
|
||||
switch (button) {
|
||||
@ -208,7 +209,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
||||
return success;
|
||||
}
|
||||
|
||||
handleThreeOptionMoveInput(button: Button): boolean {
|
||||
private handleThreeOptionMoveInput(button: Button): boolean {
|
||||
let success = false;
|
||||
const cursor = this.getCursor();
|
||||
switch (button) {
|
||||
@ -245,7 +246,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
||||
return success;
|
||||
}
|
||||
|
||||
handleFourOptionMoveInput(button: Button): boolean {
|
||||
private handleFourOptionMoveInput(button: Button): boolean {
|
||||
let success = false;
|
||||
const cursor = this.getCursor();
|
||||
switch (button) {
|
||||
@ -282,6 +283,10 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* When ME UI first displays, the option buttons will be disabled temporarily to prevent player accidentally clicking through hastily
|
||||
* This method is automatically called after a short delay but can also be called manually
|
||||
*/
|
||||
unblockInput() {
|
||||
if (this.blockInput) {
|
||||
this.blockInput = false;
|
||||
@ -295,11 +300,11 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
getCursor(): integer {
|
||||
override getCursor(): integer {
|
||||
return this.cursor ? this.cursor : 0;
|
||||
}
|
||||
|
||||
setCursor(cursor: integer): boolean {
|
||||
override setCursor(cursor: integer): boolean {
|
||||
const prevCursor = this.getCursor();
|
||||
const changed = prevCursor !== cursor;
|
||||
if (changed) {
|
||||
@ -480,7 +485,11 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
displayOptionTooltip() {
|
||||
/**
|
||||
* Updates and displays the tooltip for a given option
|
||||
* The tooltip will auto wrap and scroll if it is too long
|
||||
*/
|
||||
private displayOptionTooltip() {
|
||||
const cursor = this.getCursor();
|
||||
// Clear tooltip box
|
||||
if (this.tooltipContainer.length > 1) {
|
||||
@ -553,7 +562,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
override clear(): void {
|
||||
super.clear();
|
||||
this.overrideSettings = undefined;
|
||||
this.optionsContainer.setVisible(false);
|
||||
@ -567,7 +576,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
||||
this.eraseCursor();
|
||||
}
|
||||
|
||||
eraseCursor(): void {
|
||||
private eraseCursor(): void {
|
||||
if (this.cursorObj) {
|
||||
this.cursorObj.destroy();
|
||||
}
|
||||
@ -575,10 +584,10 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Will show or hide the Dex progress icon for an option that has dex progress
|
||||
* @param show - if true does show, if false does hide
|
||||
*/
|
||||
showHideDexProgress(show: boolean) {
|
||||
private showHideDexProgress(show: boolean) {
|
||||
if (show && !this.showDexProgress) {
|
||||
this.showDexProgress = true;
|
||||
this.scene.tweens.killTweensOf(this.dexProgressContainer);
|
||||
|
@ -531,7 +531,6 @@ export default class PartyUiHandler extends MessageUiHandler {
|
||||
return this.processInput(Button.CANCEL);
|
||||
} else if (option === PartyOption.SELECT) {
|
||||
ui.playSelect();
|
||||
// ui.setModeWithoutClear(Mode.SUMMARY, pokemon).then(() => this.clearOptions());
|
||||
return true;
|
||||
}
|
||||
} else if (button === Button.CANCEL) {
|
||||
|
@ -262,7 +262,7 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container {
|
||||
this.pokemonFormText.disableInteractive();
|
||||
}
|
||||
|
||||
const abilityTextStyle = pokemon.abilityIndex === (pokemon.species.ability2 ? 2 : 1) ? TextStyle.MONEY : TextStyle.WINDOW;
|
||||
const abilityTextStyle = pokemon.abilityIndex === 2 ? TextStyle.MONEY : TextStyle.WINDOW;
|
||||
this.pokemonAbilityText.setText(pokemon.getAbility(true).name);
|
||||
this.pokemonAbilityText.setColor(getTextColor(abilityTextStyle, false, this.scene.uiTheme));
|
||||
this.pokemonAbilityText.setShadowColor(getTextColor(abilityTextStyle, true, this.scene.uiTheme));
|
||||
|
@ -21,6 +21,7 @@ import { getVariantTint } from "#app/data/variant";
|
||||
import * as Modifier from "../modifier/modifier";
|
||||
import { Species } from "#enums/species";
|
||||
import { PlayerGender } from "#enums/player-gender";
|
||||
import { SettingKeyboard } from "#app/system/settings/settings-keyboard";
|
||||
|
||||
/**
|
||||
* RunInfoUiMode indicates possible overlays of RunInfoUiHandler.
|
||||
@ -151,7 +152,13 @@ export default class RunInfoUiHandler extends UiHandler {
|
||||
const headerBgCoords = headerBg.getTopRight();
|
||||
const abilityButtonContainer = this.scene.add.container(0, 0);
|
||||
const abilityButtonText = addTextObject(this.scene, 8, 0, i18next.t("runHistory:viewHeldItems"), TextStyle.WINDOW, {fontSize:"34px"});
|
||||
const abilityButtonElement = new Phaser.GameObjects.Sprite(this.scene, 0, 2, "keyboard", "E.png");
|
||||
const gamepadType = this.getUi().getGamepadType();
|
||||
let abilityButtonElement: Phaser.GameObjects.Sprite;
|
||||
if (gamepadType === "touch") {
|
||||
abilityButtonElement = new Phaser.GameObjects.Sprite(this.scene, 0, 2, "keyboard", "E.png");
|
||||
} else {
|
||||
abilityButtonElement = new Phaser.GameObjects.Sprite(this.scene, 0, 2, gamepadType, this.scene.inputController?.getIconForLatestInputRecorded(SettingKeyboard.Button_Cycle_Ability));
|
||||
}
|
||||
abilityButtonContainer.add([abilityButtonText, abilityButtonElement]);
|
||||
abilityButtonContainer.setPosition(headerBgCoords.x - abilityButtonText.displayWidth - abilityButtonElement.displayWidth - 8, 10);
|
||||
this.runContainer.add(abilityButtonContainer);
|
||||
@ -180,11 +187,19 @@ export default class RunInfoUiHandler extends UiHandler {
|
||||
if (this.isVictory) {
|
||||
const hallofFameInstructionContainer = this.scene.add.container(0, 0);
|
||||
const shinyButtonText = addTextObject(this.scene, 8, 0, i18next.t("runHistory:viewHallOfFame"), TextStyle.WINDOW, {fontSize:"65px"});
|
||||
const shinyButtonElement = new Phaser.GameObjects.Sprite(this.scene, 0, 4, "keyboard", "R.png");
|
||||
const formButtonText = addTextObject(this.scene, 8, 12, i18next.t("runHistory:viewEndingSplash"), TextStyle.WINDOW, {fontSize:"65px"});
|
||||
const gamepadType = this.getUi().getGamepadType();
|
||||
let shinyButtonElement: Phaser.GameObjects.Sprite;
|
||||
let formButtonElement: Phaser.GameObjects.Sprite;
|
||||
if (gamepadType === "touch") {
|
||||
shinyButtonElement = new Phaser.GameObjects.Sprite(this.scene, 0, 4, "keyboard", "R.png");
|
||||
formButtonElement = new Phaser.GameObjects.Sprite(this.scene, 0, 16, "keyboard", "F.png");
|
||||
} else {
|
||||
shinyButtonElement = new Phaser.GameObjects.Sprite(this.scene, 0, 4, gamepadType, this.scene.inputController?.getIconForLatestInputRecorded(SettingKeyboard.Button_Cycle_Shiny));
|
||||
formButtonElement = new Phaser.GameObjects.Sprite(this.scene, 0, 16, gamepadType, this.scene.inputController?.getIconForLatestInputRecorded(SettingKeyboard.Button_Cycle_Form));
|
||||
}
|
||||
hallofFameInstructionContainer.add([shinyButtonText, shinyButtonElement]);
|
||||
|
||||
const formButtonText = addTextObject(this.scene, 8, 12, i18next.t("runHistory:viewEndingSplash"), TextStyle.WINDOW, {fontSize:"65px"});
|
||||
const formButtonElement = new Phaser.GameObjects.Sprite(this.scene, 0, 16, "keyboard", "F.png");
|
||||
hallofFameInstructionContainer.add([formButtonText, formButtonElement]);
|
||||
|
||||
hallofFameInstructionContainer.setPosition(12, 25);
|
||||
|
@ -1220,6 +1220,19 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the display of candy upgrade icons or animations for the given StarterContainer
|
||||
* @param starterContainer the container for the Pokemon to update
|
||||
*/
|
||||
updateCandyUpgradeDisplay(starterContainer: StarterContainer) {
|
||||
if (this.isUpgradeIconEnabled() ) {
|
||||
this.setUpgradeIcon(starterContainer);
|
||||
}
|
||||
if (this.isUpgradeAnimationEnabled()) {
|
||||
this.setUpgradeAnimation(starterContainer.icon, this.lastSpecies, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes an {@linkcode CandyUpgradeNotificationChangedEvent} sent when the corresponding setting changes
|
||||
* @param event {@linkcode Event} sent by the callback
|
||||
@ -1624,7 +1637,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
}
|
||||
});
|
||||
}
|
||||
const candyCount = starterData.candyCount;
|
||||
|
||||
const passiveAttr = starterData.passiveAttr;
|
||||
if (passiveAttr & PassiveAttr.UNLOCKED) { // this is for enabling and disabling the passive
|
||||
if (!(passiveAttr & PassiveAttr.ENABLED)) {
|
||||
@ -1705,8 +1718,13 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
const showUseCandies = () => { // this lets you use your candies
|
||||
|
||||
// Purchases with Candy
|
||||
const candyCount = starterData.candyCount;
|
||||
const showUseCandies = () => {
|
||||
const options: any[] = []; // TODO: add proper type
|
||||
|
||||
// Unlock passive option
|
||||
if (!(passiveAttr & PassiveAttr.UNLOCKED)) {
|
||||
const passiveCost = getPassiveCandyCount(speciesStarters[this.lastSpecies.speciesId]);
|
||||
options.push({
|
||||
@ -1724,18 +1742,12 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
}
|
||||
});
|
||||
ui.setMode(Mode.STARTER_SELECT);
|
||||
this.setSpeciesDetails(this.lastSpecies, undefined, undefined, undefined, undefined, undefined, undefined);
|
||||
this.setSpeciesDetails(this.lastSpecies);
|
||||
this.scene.playSound("se/buy");
|
||||
|
||||
// if starterContainer exists, update the passive background
|
||||
// update the passive background and icon/animation for available upgrade
|
||||
if (starterContainer) {
|
||||
// Update the candy upgrade display
|
||||
if (this.isUpgradeIconEnabled() ) {
|
||||
this.setUpgradeIcon(starterContainer);
|
||||
}
|
||||
if (this.isUpgradeAnimationEnabled()) {
|
||||
this.setUpgradeAnimation(starterContainer.icon, this.lastSpecies, true);
|
||||
}
|
||||
|
||||
this.updateCandyUpgradeDisplay(starterContainer);
|
||||
starterContainer.starterPassiveBgs.setVisible(!!this.scene.gameData.starterData[this.lastSpecies.speciesId].passiveAttr);
|
||||
}
|
||||
return true;
|
||||
@ -1746,6 +1758,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
itemArgs: starterColors[this.lastSpecies.speciesId]
|
||||
});
|
||||
}
|
||||
|
||||
// Reduce cost option
|
||||
const valueReduction = starterData.valueReduction;
|
||||
if (valueReduction < valueReductionMax) {
|
||||
const reductionCost = getValueReductionCandyCounts(speciesStarters[this.lastSpecies.speciesId])[valueReduction];
|
||||
@ -1767,19 +1781,10 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
ui.setMode(Mode.STARTER_SELECT);
|
||||
this.scene.playSound("se/buy");
|
||||
|
||||
// if starterContainer exists, update the value reduction background
|
||||
// update the value label and icon/animation for available upgrade
|
||||
if (starterContainer) {
|
||||
this.updateStarterValueLabel(starterContainer);
|
||||
|
||||
// If the notification setting is set to 'On', update the candy upgrade display
|
||||
if (this.scene.candyUpgradeNotification === 2) {
|
||||
if (this.isUpgradeIconEnabled() ) {
|
||||
this.setUpgradeIcon(starterContainer);
|
||||
}
|
||||
if (this.isUpgradeAnimationEnabled()) {
|
||||
this.setUpgradeAnimation(starterContainer.icon, this.lastSpecies, true);
|
||||
}
|
||||
}
|
||||
this.updateCandyUpgradeDisplay(starterContainer);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -1812,6 +1817,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
ui.setMode(Mode.STARTER_SELECT);
|
||||
this.scene.playSound("se/buy");
|
||||
|
||||
// update the icon/animation for available upgrade
|
||||
if (starterContainer) {
|
||||
this.updateCandyUpgradeDisplay(starterContainer);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
17
src/ui/ui.ts
@ -52,6 +52,7 @@ import RunInfoUiHandler from "./run-info-ui-handler";
|
||||
import EggSummaryUiHandler from "./egg-summary-ui-handler";
|
||||
import TestDialogueUiHandler from "#app/ui/test-dialogue-ui-handler";
|
||||
import AutoCompleteUiHandler from "./autocomplete-ui-handler";
|
||||
import { Device } from "#enums/devices";
|
||||
import MysteryEncounterUiHandler from "./mystery-encounter-ui-handler";
|
||||
|
||||
export enum Mode {
|
||||
@ -582,4 +583,20 @@ export default class UI extends Phaser.GameObjects.Container {
|
||||
public getModeChain(): Mode[] {
|
||||
return this.modeChain;
|
||||
}
|
||||
|
||||
/**
|
||||
* getGamepadType - returns the type of gamepad being used
|
||||
* inputMethod could be "keyboard" or "touch" or "gamepad"
|
||||
* if inputMethod is "keyboard" or "touch", then the inputMethod is returned
|
||||
* if inputMethod is "gamepad", then the gamepad type is returned it could be "xbox" or "dualshock"
|
||||
* @returns gamepad type
|
||||
*/
|
||||
public getGamepadType(): string {
|
||||
const scene = this.scene as BattleScene;
|
||||
if (scene.inputMethod === "gamepad") {
|
||||
return scene.inputController.getConfig(scene.inputController.selectedDevice[Device.GAMEPAD]).padType;
|
||||
} else {
|
||||
return scene.inputMethod;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -587,6 +587,10 @@ export function isNullOrUndefined(object: any): boolean {
|
||||
return null === object || undefined === object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Capitalizes the first letter of a string
|
||||
* @param str
|
||||
*/
|
||||
export function capitalizeFirstLetter(str: string) {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
|
@ -1,46 +1,42 @@
|
||||
import { defineProject, UserWorkspaceConfig } from 'vitest/config';
|
||||
import { defaultConfig } from './vite.config';
|
||||
|
||||
export const defaultProjectTestConfig: UserWorkspaceConfig["test"] = {
|
||||
setupFiles: ['./src/test/vitest.setup.ts'],
|
||||
server: {
|
||||
deps: {
|
||||
inline: ['vitest-canvas-mock'],
|
||||
//@ts-ignore
|
||||
optimizer: {
|
||||
web: {
|
||||
include: ['vitest-canvas-mock'],
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
environment: 'jsdom' as const,
|
||||
environmentOptions: {
|
||||
jsdom: {
|
||||
resources: 'usable',
|
||||
},
|
||||
},
|
||||
threads: false,
|
||||
trace: true,
|
||||
restoreMocks: true,
|
||||
watch: false,
|
||||
coverage: {
|
||||
provider: 'istanbul' as const,
|
||||
reportsDirectory: 'coverage' as const,
|
||||
reporters: ['text-summary', 'html'],
|
||||
},
|
||||
}
|
||||
import { defineProject } from "vitest/config";
|
||||
import { defaultConfig } from "./vite.config";
|
||||
|
||||
export default defineProject(({ mode }) => ({
|
||||
...defaultConfig,
|
||||
test: {
|
||||
...defaultProjectTestConfig,
|
||||
name: "main",
|
||||
include: ["./src/test/**/*.{test,spec}.ts"],
|
||||
exclude: ["./src/test/pre.test.ts"],
|
||||
},
|
||||
esbuild: {
|
||||
pure: mode === 'production' ? [ 'console.log' ] : [],
|
||||
keepNames: true,
|
||||
},
|
||||
}))
|
||||
...defaultConfig,
|
||||
test: {
|
||||
setupFiles: ["./src/test/fontFace.setup.ts", "./src/test/vitest.setup.ts"],
|
||||
server: {
|
||||
deps: {
|
||||
inline: ["vitest-canvas-mock"],
|
||||
//@ts-ignore
|
||||
optimizer: {
|
||||
web: {
|
||||
include: ["vitest-canvas-mock"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
environment: "jsdom" as const,
|
||||
environmentOptions: {
|
||||
jsdom: {
|
||||
resources: "usable",
|
||||
},
|
||||
},
|
||||
threads: false,
|
||||
trace: true,
|
||||
restoreMocks: true,
|
||||
watch: false,
|
||||
coverage: {
|
||||
provider: "istanbul" as const,
|
||||
reportsDirectory: "coverage" as const,
|
||||
reporters: ["text-summary", "html"],
|
||||
},
|
||||
name: "main",
|
||||
include: ["./src/test/**/*.{test,spec}.ts"],
|
||||
exclude: ["./src/test/pre.test.ts"],
|
||||
},
|
||||
esbuild: {
|
||||
pure: mode === "production" ? ["console.log"] : [],
|
||||
keepNames: true,
|
||||
},
|
||||
}));
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { defineWorkspace } from "vitest/config";
|
||||
import { defaultConfig } from "./vite.config";
|
||||
import { defaultProjectTestConfig } from "./vitest.config";
|
||||
|
||||
export default defineWorkspace([
|
||||
{
|
||||
@ -11,58 +10,5 @@ export default defineWorkspace([
|
||||
environment: "jsdom",
|
||||
},
|
||||
},
|
||||
{
|
||||
...defaultConfig,
|
||||
test: {
|
||||
...defaultProjectTestConfig,
|
||||
name: "misc",
|
||||
include: [
|
||||
"src/test/achievements/**/*.{test,spec}.ts",
|
||||
"src/test/arena/**/*.{test,spec}.ts",
|
||||
"src/test/battlerTags/**/*.{test,spec}.ts",
|
||||
"src/test/eggs/**/*.{test,spec}.ts",
|
||||
"src/test/field/**/*.{test,spec}.ts",
|
||||
"src/test/inputs/**/*.{test,spec}.ts",
|
||||
"src/test/localization/**/*.{test,spec}.ts",
|
||||
"src/test/phases/**/*.{test,spec}.ts",
|
||||
"src/test/settingMenu/**/*.{test,spec}.ts",
|
||||
"src/test/sprites/**/*.{test,spec}.ts",
|
||||
"src/test/ui/**/*.{test,spec}.ts",
|
||||
"src/test/*.{test,spec}.ts",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
...defaultConfig,
|
||||
test: {
|
||||
...defaultProjectTestConfig,
|
||||
name: "abilities",
|
||||
include: ["src/test/abilities/**/*.{test,spec}.ts"],
|
||||
},
|
||||
},
|
||||
{
|
||||
...defaultConfig,
|
||||
test: {
|
||||
...defaultProjectTestConfig,
|
||||
name: "battle",
|
||||
include: ["src/test/battle/**/*.{test,spec}.ts"],
|
||||
},
|
||||
},
|
||||
{
|
||||
...defaultConfig,
|
||||
test: {
|
||||
...defaultProjectTestConfig,
|
||||
name: "items",
|
||||
include: ["src/test/items/**/*.{test,spec}.ts"],
|
||||
},
|
||||
},
|
||||
{
|
||||
...defaultConfig,
|
||||
test: {
|
||||
...defaultProjectTestConfig,
|
||||
name: "moves",
|
||||
include: ["src/test/moves/**/*.{test,spec}.ts"],
|
||||
},
|
||||
},
|
||||
"./vitest.config.ts",
|
||||
]);
|
||||
|