Merge branch 'pagefaultgames:beta' into beta

This commit is contained in:
Chapybara-jp 2024-09-11 01:05:30 +02:00 committed by GitHub
commit db39ef336b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 521 additions and 264 deletions

View 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' || '' }}

View File

@ -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

View File

@ -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
View File

@ -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",

View File

@ -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",

View File

@ -222,7 +222,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);
}
@ -326,7 +326,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 {
@ -361,7 +362,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) {

View File

@ -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,

View File

@ -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";

View File

@ -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";

View 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);
});
});

View File

@ -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;

View File

@ -1,4 +1,3 @@
import "#test/fontFace.setup";
import "vitest-canvas-mock";
import { initLoggedInUser } from "#app/account";

View File

@ -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,6 +99,7 @@ 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
@ -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);

View File

@ -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));

View File

@ -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;

View File

@ -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,
},
}));

View File

@ -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",
]);