Merge branch 'held-item-refactor' of https://github.com/Wlowscha/pokerogue into held-item-refactor
12
.github/test-filters.yml
vendored
@ -1,7 +1,8 @@
|
|||||||
all:
|
all:
|
||||||
- "src/**"
|
# Negations syntax from https://github.com/dorny/paths-filter/issues/184#issuecomment-2786521554
|
||||||
- "test/**"
|
- "src/**/!(*.{md,py,sh,gitkeep,gitignore})"
|
||||||
- "public/**"
|
- "test/**/!(*.{md,py,sh,gitkeep,gitignore})"
|
||||||
|
- "public/**/!(*.{md,py,sh,gitkeep,gitignore})"
|
||||||
# Workflows that can impact tests
|
# Workflows that can impact tests
|
||||||
- ".github/workflows/test*.yml"
|
- ".github/workflows/test*.yml"
|
||||||
- ".github/test-filters.yml"
|
- ".github/test-filters.yml"
|
||||||
@ -12,8 +13,3 @@ all:
|
|||||||
- "tsconfig*.json" # tsconfig.json tweaking can impact compilation
|
- "tsconfig*.json" # tsconfig.json tweaking can impact compilation
|
||||||
- "global.d.ts"
|
- "global.d.ts"
|
||||||
- ".env*"
|
- ".env*"
|
||||||
# Blanket negations for files that cannot impact tests
|
|
||||||
- "!**/*.py" # No .py files
|
|
||||||
- "!**/*.sh" # No .sh files
|
|
||||||
- "!**/*.md" # No .md files
|
|
||||||
- "!**/.git*" # .gitkeep and family
|
|
||||||
|
9
.github/workflows/test-shard-template.yml
vendored
@ -19,19 +19,20 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
name: Shard ${{ inputs.shard }} of ${{ inputs.totalShards }}
|
# We can't use dynmically named jobs until https://github.com/orgs/community/discussions/13261 is implemented
|
||||||
|
name: Shard
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: ${{ !inputs.skip }}
|
if: ${{ !inputs.skip }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out Git repository
|
- name: Check out Git repository
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@v4.2.2
|
||||||
with:
|
with:
|
||||||
submodules: 'recursive'
|
submodules: "recursive"
|
||||||
- name: Set up Node.js
|
- name: Set up Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version-file: '.nvmrc'
|
node-version-file: ".nvmrc"
|
||||||
cache: 'npm'
|
cache: "npm"
|
||||||
- name: Install Node.js dependencies
|
- name: Install Node.js dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
|
7
.github/workflows/tests.yml
vendored
@ -25,6 +25,7 @@ jobs:
|
|||||||
- name: checkout
|
- name: checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36
|
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36
|
||||||
|
id: filter
|
||||||
with:
|
with:
|
||||||
filters: .github/test-filters.yml
|
filters: .github/test-filters.yml
|
||||||
|
|
||||||
@ -33,10 +34,10 @@ jobs:
|
|||||||
needs: check-path-change-filter
|
needs: check-path-change-filter
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
shard: [1, 2, 3, 4, 5]
|
||||||
uses: ./.github/workflows/test-shard-template.yml
|
uses: ./.github/workflows/test-shard-template.yml
|
||||||
with:
|
with:
|
||||||
project: main
|
project: main
|
||||||
shard: ${{ matrix.shard }}
|
shard: ${{ matrix.shard }}
|
||||||
totalShards: 10
|
totalShards: 5
|
||||||
skip: ${{ needs.check-path-change-filter.outputs.all == 'false'}}
|
skip: ${{ needs.check-path-change-filter.outputs.all != 'true'}}
|
||||||
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
@ -1,34 +1,36 @@
|
|||||||
{
|
{
|
||||||
"1": {
|
"1": {
|
||||||
"319452": "831a1f",
|
|
||||||
"4a7310": "982443",
|
|
||||||
"7ba563": "b44040",
|
|
||||||
"bdef84": "ec8c8c",
|
|
||||||
"8cbd63": "c54b4b",
|
"8cbd63": "c54b4b",
|
||||||
"215200": "710f2e",
|
"a5d670": "df5252",
|
||||||
|
"4aa552": "9f2f2c",
|
||||||
"a5d674": "e16363",
|
"a5d674": "e16363",
|
||||||
"196b21": "891222",
|
"7aa953": "c54b4b",
|
||||||
|
"7ba563": "b44040",
|
||||||
|
"215200": "710f2e",
|
||||||
"f7ce00": "7aa1df",
|
"f7ce00": "7aa1df",
|
||||||
"525252": "123a5a",
|
"525252": "123a5a",
|
||||||
"63b56b": "b2332f",
|
|
||||||
"a5d673": "df5252",
|
|
||||||
"8c6b3a": "448bc3",
|
"8c6b3a": "448bc3",
|
||||||
"4aa552": "9f2f2c"
|
"bdef84": "ec8c8c",
|
||||||
|
"63b56b": "b2332f",
|
||||||
|
"319452": "831a1f",
|
||||||
|
"196b21": "891222",
|
||||||
|
"4a7310": "982443"
|
||||||
},
|
},
|
||||||
"2": {
|
"2": {
|
||||||
"319452": "b08d72",
|
|
||||||
"4a7310": "4f3956",
|
|
||||||
"7ba563": "704e7e",
|
|
||||||
"bdef84": "a779ba",
|
|
||||||
"8cbd63": "e3d7a6",
|
"8cbd63": "e3d7a6",
|
||||||
"215200": "583823",
|
"a5d670": "d7cda7",
|
||||||
|
"4aa552": "c5a77f",
|
||||||
"a5d674": "8c669b",
|
"a5d674": "8c669b",
|
||||||
"196b21": "78582c",
|
"7aa953": "704e7e",
|
||||||
|
"7ba563": "704e7e",
|
||||||
|
"215200": "583823",
|
||||||
"f7ce00": "f2aacd",
|
"f7ce00": "f2aacd",
|
||||||
"525252": "a53b6f",
|
"525252": "a53b6f",
|
||||||
"63b56b": "cfc191",
|
|
||||||
"a5d673": "d7cda7",
|
|
||||||
"8c6b3a": "df87bb",
|
"8c6b3a": "df87bb",
|
||||||
"4aa552": "c5a77f"
|
"bdef84": "a779ba",
|
||||||
|
"63b56b": "cfc191",
|
||||||
|
"319452": "b08d72",
|
||||||
|
"196b21": "78582c",
|
||||||
|
"4a7310": "4f3956"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,28 +1,28 @@
|
|||||||
{
|
{
|
||||||
"1": {
|
"1": {
|
||||||
|
"196b21": "831a1f",
|
||||||
|
"7ba563": "b44040",
|
||||||
|
"215201": "630d28",
|
||||||
|
"215200": "710f2f",
|
||||||
|
"a5d674": "df5252",
|
||||||
|
"8cbd63": "c54b4b",
|
||||||
|
"63b56b": "b2332f",
|
||||||
|
"a5d670": "e16363",
|
||||||
"319452": "831a1f",
|
"319452": "831a1f",
|
||||||
"4aa552": "9f2f2c",
|
"4aa552": "9f2f2c",
|
||||||
"7ba563": "b44040",
|
"4a7310": "982443"
|
||||||
"8cbd63": "c54b4b",
|
|
||||||
"215200": "710f2f",
|
|
||||||
"196b21": "831a1f",
|
|
||||||
"a5d674": "df5252",
|
|
||||||
"4a7310": "982443",
|
|
||||||
"a5d673": "e16363",
|
|
||||||
"63b56b": "b2332f",
|
|
||||||
"215201": "630d28"
|
|
||||||
},
|
},
|
||||||
"2": {
|
"2": {
|
||||||
|
"196b21": "b08d72",
|
||||||
|
"7ba563": "704e7e",
|
||||||
|
"215201": "583823",
|
||||||
|
"215200": "3f3249",
|
||||||
|
"a5d674": "d7cda7",
|
||||||
|
"8cbd63": "e3d7a6",
|
||||||
|
"63b56b": "cfc191",
|
||||||
|
"a5d670": "8c669b",
|
||||||
"319452": "b08d72",
|
"319452": "b08d72",
|
||||||
"4aa552": "c5a77f",
|
"4aa552": "c5a77f",
|
||||||
"7ba563": "704e7e",
|
"4a7310": "4f3956"
|
||||||
"8cbd63": "e3d7a6",
|
|
||||||
"215200": "3f3249",
|
|
||||||
"196b21": "b08d72",
|
|
||||||
"a5d674": "d7cda7",
|
|
||||||
"4a7310": "4f3956",
|
|
||||||
"a5d673": "8c669b",
|
|
||||||
"63b56b": "cfc191",
|
|
||||||
"215201": "583823"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,28 +1,28 @@
|
|||||||
{
|
{
|
||||||
"1": {
|
"1": {
|
||||||
|
"196b21": "780d4a",
|
||||||
|
"7ba563": "b44040",
|
||||||
|
"215201": "710f2e",
|
||||||
|
"215200": "710f2f",
|
||||||
|
"a5d674": "de5b6f",
|
||||||
|
"8cbd63": "bf3d64",
|
||||||
|
"63b56b": "9e2056",
|
||||||
|
"a5d670": "e16363",
|
||||||
"319452": "780d4a",
|
"319452": "780d4a",
|
||||||
"4aa552": "8a1652",
|
"4aa552": "8a1652",
|
||||||
"7ba563": "b44040",
|
"4a7310": "982443"
|
||||||
"8cbd63": "bf3d64",
|
|
||||||
"215200": "710f2f",
|
|
||||||
"196b21": "780d4a",
|
|
||||||
"a5d674": "de5b6f",
|
|
||||||
"4a7310": "982443",
|
|
||||||
"a5d673": "e16363",
|
|
||||||
"63b56b": "9e2056",
|
|
||||||
"215201": "710f2e"
|
|
||||||
},
|
},
|
||||||
"2": {
|
"2": {
|
||||||
|
"196b21": "b59c72",
|
||||||
|
"7ba563": "805a9c",
|
||||||
|
"215201": "694d37",
|
||||||
|
"215200": "41334d",
|
||||||
|
"a5d674": "f6f7df",
|
||||||
|
"8cbd63": "ebe9ca",
|
||||||
|
"63b56b": "e3ddb8",
|
||||||
|
"a5d670": "a473ba",
|
||||||
"319452": "b59c72",
|
"319452": "b59c72",
|
||||||
"4aa552": "c9b991",
|
"4aa552": "c9b991",
|
||||||
"7ba563": "805a9c",
|
"4a7310": "4f3956"
|
||||||
"8cbd63": "ebe9ca",
|
|
||||||
"215200": "41334d",
|
|
||||||
"196b21": "b59c72",
|
|
||||||
"a5d674": "f6f7df",
|
|
||||||
"4a7310": "4f3956",
|
|
||||||
"a5d673": "a473ba",
|
|
||||||
"63b56b": "e3ddb8",
|
|
||||||
"215201": "694d37"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,34 +1,36 @@
|
|||||||
{
|
{
|
||||||
"1": {
|
"1": {
|
||||||
"319452": "780d4a",
|
|
||||||
"4a7310": "982443",
|
|
||||||
"7ba563": "b44040",
|
|
||||||
"bdef84": "ec8c8c",
|
|
||||||
"8cbd63": "bf3d64",
|
"8cbd63": "bf3d64",
|
||||||
"215200": "710f2e",
|
"a5d670": "de5b6f",
|
||||||
|
"4aa552": "8a1652",
|
||||||
"a5d674": "e16363",
|
"a5d674": "e16363",
|
||||||
"196b21": "7d1157",
|
"7aa953": "bf3d64",
|
||||||
|
"7ba563": "b44040",
|
||||||
|
"215200": "710f2e",
|
||||||
"f7ce00": "5bcfc3",
|
"f7ce00": "5bcfc3",
|
||||||
"525252": "20668c",
|
"525252": "20668c",
|
||||||
"63b56b": "9e2056",
|
|
||||||
"a5d673": "de5b6f",
|
|
||||||
"8c6b3a": "33a3b0",
|
"8c6b3a": "33a3b0",
|
||||||
"4aa552": "8a1652"
|
"bdef84": "ec8c8c",
|
||||||
|
"63b56b": "9e2056",
|
||||||
|
"319452": "780d4a",
|
||||||
|
"196b21": "7d1157",
|
||||||
|
"4a7310": "982443"
|
||||||
},
|
},
|
||||||
"2": {
|
"2": {
|
||||||
"319452": "b59c72",
|
|
||||||
"4a7310": "4f3956",
|
|
||||||
"7ba563": "805a9c",
|
|
||||||
"bdef84": "c193cf",
|
|
||||||
"8cbd63": "f6f7df",
|
"8cbd63": "f6f7df",
|
||||||
"215200": "694d37",
|
"a5d670": "ebe9ca",
|
||||||
|
"4aa552": "c9b991",
|
||||||
"a5d674": "a473ba",
|
"a5d674": "a473ba",
|
||||||
"196b21": "9c805f",
|
"7aa953": "805a9c",
|
||||||
|
"7ba563": "805a9c",
|
||||||
|
"215200": "694d37",
|
||||||
"f7ce00": "f2aab6",
|
"f7ce00": "f2aab6",
|
||||||
"525252": "983364",
|
"525252": "983364",
|
||||||
"63b56b": "e3ddb8",
|
|
||||||
"a5d673": "ebe9ca",
|
|
||||||
"8c6b3a": "df879f",
|
"8c6b3a": "df879f",
|
||||||
"4aa552": "c9b991"
|
"bdef84": "c193cf",
|
||||||
|
"63b56b": "e3ddb8",
|
||||||
|
"319452": "b59c72",
|
||||||
|
"196b21": "9c805f",
|
||||||
|
"4a7310": "4f3956"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1 +1 @@
|
|||||||
Subproject commit 42cd5cf577f475c22bc82d55e7ca358eb4f3184f
|
Subproject commit e9ccbadb6eaa3b797f3dec919745befda2ec74bd
|
149
scripts/decrypt-save.js
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
import pkg from "crypto-js";
|
||||||
|
const { AES, enc } = pkg;
|
||||||
|
// biome-ignore lint: This is how you import fs from node
|
||||||
|
import * as fs from "node:fs";
|
||||||
|
|
||||||
|
const SAVE_KEY = "x0i2O7WRiANTqPmZ";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map of condensed keynames to their associated full names
|
||||||
|
* NOTE: Update this if `src/system/game-data#systemShortKeys` ever changes!
|
||||||
|
*/
|
||||||
|
const systemShortKeys = {
|
||||||
|
seenAttr: "$sa",
|
||||||
|
caughtAttr: "$ca",
|
||||||
|
natureAttr: "$na",
|
||||||
|
seenCount: "$s",
|
||||||
|
caughtCount: "$c",
|
||||||
|
hatchedCount: "$hc",
|
||||||
|
ivs: "$i",
|
||||||
|
moveset: "$m",
|
||||||
|
eggMoves: "$em",
|
||||||
|
candyCount: "$x",
|
||||||
|
friendship: "$f",
|
||||||
|
abilityAttr: "$a",
|
||||||
|
passiveAttr: "$pa",
|
||||||
|
valueReduction: "$vr",
|
||||||
|
classicWinCount: "$wc",
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace the shortened key names with their full names
|
||||||
|
* @param {string} dataStr - The string to convert
|
||||||
|
* @returns {string} The string with shortened keynames replaced with full names
|
||||||
|
*/
|
||||||
|
function convertSystemDataStr(dataStr) {
|
||||||
|
const fromKeys = Object.values(systemShortKeys);
|
||||||
|
const toKeys = Object.keys(systemShortKeys);
|
||||||
|
for (const k in fromKeys) {
|
||||||
|
dataStr = dataStr.replace(new RegExp(`${fromKeys[k].replace("$", "\\$")}`, "g"), toKeys[k]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypt a save
|
||||||
|
* @param {string} path - The path to the encrypted save file
|
||||||
|
* @returns {string} The decrypted save data
|
||||||
|
*/
|
||||||
|
function decryptSave(path) {
|
||||||
|
// Check if the file exists
|
||||||
|
if (!fs.existsSync(path)) {
|
||||||
|
console.error(`File not found: ${path}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
let fileData;
|
||||||
|
try {
|
||||||
|
fileData = fs.readFileSync(path, "utf8");
|
||||||
|
} catch (e) {
|
||||||
|
switch (e.code) {
|
||||||
|
case "ENOENT":
|
||||||
|
console.error(`File not found: ${path}`);
|
||||||
|
break;
|
||||||
|
case "EACCES":
|
||||||
|
console.error(`Could not open ${path}: Permission denied`);
|
||||||
|
break;
|
||||||
|
case "EISDIR":
|
||||||
|
console.error(`Unable to read ${path} as it is a directory`);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.error(`Error reading file: ${e.message}`);
|
||||||
|
}
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
return convertSystemDataStr(AES.decrypt(fileData, SAVE_KEY).toString(enc.Utf8));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Print the usage message and exits */
|
||||||
|
function printUsage() {
|
||||||
|
console.log(`
|
||||||
|
Usage: node decrypt-save.js <encrypted-file> [save-file]
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
file-path Path to the encrypted save file to decrypt.
|
||||||
|
save-file Path to where the decrypted data should be written. If not provided, the decrypted data will be printed to the console.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h, --help Show this help message and exit.
|
||||||
|
|
||||||
|
Description:
|
||||||
|
This script decrypts an encrypted pokerogue save file
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write `data` to `filePath`, gracefully communicating errors that arise
|
||||||
|
* @param {string} filePath
|
||||||
|
* @param {string} data
|
||||||
|
*/
|
||||||
|
function writeToFile(filePath, data) {
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(filePath, data);
|
||||||
|
} catch (e) {
|
||||||
|
switch (e.code) {
|
||||||
|
case "EACCES":
|
||||||
|
console.error(`Could not open ${filePath}: Permission denied`);
|
||||||
|
break;
|
||||||
|
case "EISDIR":
|
||||||
|
console.error(`Unable to write to ${filePath} as it is a directory`);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.error(`Error writing file: ${e.message}`);
|
||||||
|
}
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
let args = process.argv.slice(2);
|
||||||
|
// Get options
|
||||||
|
const options = args.filter(arg => arg.startsWith("-"));
|
||||||
|
// get args
|
||||||
|
args = args.filter(arg => !arg.startsWith("-"));
|
||||||
|
|
||||||
|
if (args.length === 0 || options.includes("-h") || options.includes("--help") || args.length > 2) {
|
||||||
|
printUsage();
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
// If the user provided a second argument, check if the file exists already and refuse to write to it.
|
||||||
|
if (args.length === 2) {
|
||||||
|
const destPath = args[1];
|
||||||
|
if (fs.existsSync(destPath)) {
|
||||||
|
console.error(`Refusing to overwrite ${destPath}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, commence decryption.
|
||||||
|
const decrypt = decryptSave(args[0]);
|
||||||
|
|
||||||
|
if (args.length === 1) {
|
||||||
|
process.stdout.write(decrypt);
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeToFile(destPath, decrypt);
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
@ -7425,7 +7425,12 @@ export function initAbilities() {
|
|||||||
.uncopiable()
|
.uncopiable()
|
||||||
.attr(NoTransformAbilityAbAttr),
|
.attr(NoTransformAbilityAbAttr),
|
||||||
new Ability(Abilities.GOOD_AS_GOLD, 9)
|
new Ability(Abilities.GOOD_AS_GOLD, 9)
|
||||||
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.category === MoveCategory.STATUS && ![ MoveTarget.ENEMY_SIDE, MoveTarget.BOTH_SIDES, MoveTarget.USER_SIDE ].includes(move.moveTarget))
|
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) =>
|
||||||
|
pokemon !== attacker
|
||||||
|
&& move.category === MoveCategory.STATUS
|
||||||
|
&& ![ MoveTarget.ENEMY_SIDE, MoveTarget.BOTH_SIDES, MoveTarget.USER_SIDE ].includes(move.moveTarget)
|
||||||
|
)
|
||||||
|
.edgeCase() // Heal Bell should not cure the status of a Pokemon with Good As Gold
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
new Ability(Abilities.VESSEL_OF_RUIN, 9)
|
new Ability(Abilities.VESSEL_OF_RUIN, 9)
|
||||||
.attr(FieldMultiplyStatAbAttr, Stat.SPATK, 0.75)
|
.attr(FieldMultiplyStatAbAttr, Stat.SPATK, 0.75)
|
||||||
|
@ -7523,7 +7523,7 @@ export class SuppressAbilitiesAttr extends MoveEffectAttr {
|
|||||||
|
|
||||||
/** Causes the effect to fail when the target's ability is unsupressable or already suppressed. */
|
/** Causes the effect to fail when the target's ability is unsupressable or already suppressed. */
|
||||||
getCondition(): MoveConditionFunc {
|
getCondition(): MoveConditionFunc {
|
||||||
return (user, target, move) => target.getAbility().isSuppressable && !target.summonData.abilitySuppressed;
|
return (_user, target, _move) => !target.summonData.abilitySuppressed && (target.getAbility().isSuppressable || (target.hasPassive() && target.getPassiveAbility().isSuppressable));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1697,8 +1697,8 @@ export function initSpecies() {
|
|||||||
new PokemonSpecies(Species.CHINCHOU, 2, false, false, false, "Angler Pokémon", PokemonType.WATER, PokemonType.ELECTRIC, 0.5, 12, Abilities.VOLT_ABSORB, Abilities.ILLUMINATE, Abilities.WATER_ABSORB, 330, 75, 38, 38, 56, 56, 67, 190, 50, 66, GrowthRate.SLOW, 50, false),
|
new PokemonSpecies(Species.CHINCHOU, 2, false, false, false, "Angler Pokémon", PokemonType.WATER, PokemonType.ELECTRIC, 0.5, 12, Abilities.VOLT_ABSORB, Abilities.ILLUMINATE, Abilities.WATER_ABSORB, 330, 75, 38, 38, 56, 56, 67, 190, 50, 66, GrowthRate.SLOW, 50, false),
|
||||||
new PokemonSpecies(Species.LANTURN, 2, false, false, false, "Light Pokémon", PokemonType.WATER, PokemonType.ELECTRIC, 1.2, 22.5, Abilities.VOLT_ABSORB, Abilities.ILLUMINATE, Abilities.WATER_ABSORB, 460, 125, 58, 58, 76, 76, 67, 75, 50, 161, GrowthRate.SLOW, 50, false),
|
new PokemonSpecies(Species.LANTURN, 2, false, false, false, "Light Pokémon", PokemonType.WATER, PokemonType.ELECTRIC, 1.2, 22.5, Abilities.VOLT_ABSORB, Abilities.ILLUMINATE, Abilities.WATER_ABSORB, 460, 125, 58, 58, 76, 76, 67, 75, 50, 161, GrowthRate.SLOW, 50, false),
|
||||||
new PokemonSpecies(Species.PICHU, 2, false, false, false, "Tiny Mouse Pokémon", PokemonType.ELECTRIC, null, 0.3, 2, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 205, 20, 40, 15, 35, 35, 60, 190, 70, 41, GrowthRate.MEDIUM_FAST, 50, false, false,
|
new PokemonSpecies(Species.PICHU, 2, false, false, false, "Tiny Mouse Pokémon", PokemonType.ELECTRIC, null, 0.3, 2, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 205, 20, 40, 15, 35, 35, 60, 190, 70, 41, GrowthRate.MEDIUM_FAST, 50, false, false,
|
||||||
new PokemonForm("Normal", "", PokemonType.ELECTRIC, null, 1.4, 61.5, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 205, 20, 40, 15, 35, 35, 60, 190, 70, 41, false, null, true),
|
new PokemonForm("Normal", "", PokemonType.ELECTRIC, null, 1.4, 2, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 205, 20, 40, 15, 35, 35, 60, 190, 70, 41, false, null, true),
|
||||||
new PokemonForm("Spiky-Eared", "spiky", PokemonType.ELECTRIC, null, 1.4, 61.5, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 205, 20, 40, 15, 35, 35, 60, 190, 70, 41, false, null, true),
|
new PokemonForm("Spiky-Eared", "spiky", PokemonType.ELECTRIC, null, 1.4, 2, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 205, 20, 40, 15, 35, 35, 60, 190, 70, 41, false, null, true),
|
||||||
),
|
),
|
||||||
new PokemonSpecies(Species.CLEFFA, 2, false, false, false, "Star Shape Pokémon", PokemonType.FAIRY, null, 0.3, 3, Abilities.CUTE_CHARM, Abilities.MAGIC_GUARD, Abilities.FRIEND_GUARD, 218, 50, 25, 28, 45, 55, 15, 150, 140, 44, GrowthRate.FAST, 25, false),
|
new PokemonSpecies(Species.CLEFFA, 2, false, false, false, "Star Shape Pokémon", PokemonType.FAIRY, null, 0.3, 3, Abilities.CUTE_CHARM, Abilities.MAGIC_GUARD, Abilities.FRIEND_GUARD, 218, 50, 25, 28, 45, 55, 15, 150, 140, 44, GrowthRate.FAST, 25, false),
|
||||||
new PokemonSpecies(Species.IGGLYBUFF, 2, false, false, false, "Balloon Pokémon", PokemonType.NORMAL, PokemonType.FAIRY, 0.3, 1, Abilities.CUTE_CHARM, Abilities.COMPETITIVE, Abilities.FRIEND_GUARD, 210, 90, 30, 15, 40, 20, 15, 170, 50, 42, GrowthRate.FAST, 25, false),
|
new PokemonSpecies(Species.IGGLYBUFF, 2, false, false, false, "Balloon Pokémon", PokemonType.NORMAL, PokemonType.FAIRY, 0.3, 1, Abilities.CUTE_CHARM, Abilities.COMPETITIVE, Abilities.FRIEND_GUARD, 210, 90, 30, 15, 40, 20, 15, 170, 50, 42, GrowthRate.FAST, 25, false),
|
||||||
@ -3121,7 +3121,7 @@ export function initSpecies() {
|
|||||||
),
|
),
|
||||||
new PokemonSpecies(Species.WALKING_WAKE, 9, false, false, false, "Paradox Pokémon", PokemonType.WATER, PokemonType.DRAGON, 3.5, 280, Abilities.PROTOSYNTHESIS, Abilities.NONE, Abilities.NONE, 590, 99, 83, 91, 125, 83, 109, 10, 0, 295, GrowthRate.SLOW, null, false), //Custom Catchrate, matching Gouging Fire and Raging Bolt
|
new PokemonSpecies(Species.WALKING_WAKE, 9, false, false, false, "Paradox Pokémon", PokemonType.WATER, PokemonType.DRAGON, 3.5, 280, Abilities.PROTOSYNTHESIS, Abilities.NONE, Abilities.NONE, 590, 99, 83, 91, 125, 83, 109, 10, 0, 295, GrowthRate.SLOW, null, false), //Custom Catchrate, matching Gouging Fire and Raging Bolt
|
||||||
new PokemonSpecies(Species.IRON_LEAVES, 9, false, false, false, "Paradox Pokémon", PokemonType.GRASS, PokemonType.PSYCHIC, 1.5, 125, Abilities.QUARK_DRIVE, Abilities.NONE, Abilities.NONE, 590, 90, 130, 88, 70, 108, 104, 10, 0, 295, GrowthRate.SLOW, null, false), //Custom Catchrate, matching Iron Boulder and Iron Crown
|
new PokemonSpecies(Species.IRON_LEAVES, 9, false, false, false, "Paradox Pokémon", PokemonType.GRASS, PokemonType.PSYCHIC, 1.5, 125, Abilities.QUARK_DRIVE, Abilities.NONE, Abilities.NONE, 590, 90, 130, 88, 70, 108, 104, 10, 0, 295, GrowthRate.SLOW, null, false), //Custom Catchrate, matching Iron Boulder and Iron Crown
|
||||||
new PokemonSpecies(Species.DIPPLIN, 9, false, false, false, "Candy Apple Pokémon", PokemonType.GRASS, PokemonType.DRAGON, 0.4, 9.7, Abilities.SUPERSWEET_SYRUP, Abilities.GLUTTONY, Abilities.STICKY_HOLD, 485, 80, 80, 110, 95, 80, 40, 45, 50, 170, GrowthRate.ERRATIC, 50, false),
|
new PokemonSpecies(Species.DIPPLIN, 9, false, false, false, "Candy Apple Pokémon", PokemonType.GRASS, PokemonType.DRAGON, 0.4, 4.4, Abilities.SUPERSWEET_SYRUP, Abilities.GLUTTONY, Abilities.STICKY_HOLD, 485, 80, 80, 110, 95, 80, 40, 45, 50, 170, GrowthRate.ERRATIC, 50, false),
|
||||||
new PokemonSpecies(Species.POLTCHAGEIST, 9, false, false, false, "Matcha Pokémon", PokemonType.GRASS, PokemonType.GHOST, 0.1, 1.1, Abilities.HOSPITALITY, Abilities.NONE, Abilities.HEATPROOF, 308, 40, 45, 45, 74, 54, 50, 120, 50, 62, GrowthRate.SLOW, null, false, false,
|
new PokemonSpecies(Species.POLTCHAGEIST, 9, false, false, false, "Matcha Pokémon", PokemonType.GRASS, PokemonType.GHOST, 0.1, 1.1, Abilities.HOSPITALITY, Abilities.NONE, Abilities.HEATPROOF, 308, 40, 45, 45, 74, 54, 50, 120, 50, 62, GrowthRate.SLOW, null, false, false,
|
||||||
new PokemonForm("Counterfeit Form", "counterfeit", PokemonType.GRASS, PokemonType.GHOST, 0.1, 1.1, Abilities.HOSPITALITY, Abilities.NONE, Abilities.HEATPROOF, 308, 40, 45, 45, 74, 54, 50, 120, 50, 62, false, null, true),
|
new PokemonForm("Counterfeit Form", "counterfeit", PokemonType.GRASS, PokemonType.GHOST, 0.1, 1.1, Abilities.HOSPITALITY, Abilities.NONE, Abilities.HEATPROOF, 308, 40, 45, 45, 74, 54, 50, 120, 50, 62, false, null, true),
|
||||||
new PokemonForm("Artisan Form", "artisan", PokemonType.GRASS, PokemonType.GHOST, 0.1, 1.1, Abilities.HOSPITALITY, Abilities.NONE, Abilities.HEATPROOF, 308, 40, 45, 45, 74, 54, 50, 120, 50, 62, false, null, false, true),
|
new PokemonForm("Artisan Form", "artisan", PokemonType.GRASS, PokemonType.GHOST, 0.1, 1.1, Abilities.HOSPITALITY, Abilities.NONE, Abilities.HEATPROOF, 308, 40, 45, 45, 74, 54, 50, 120, 50, 62, false, null, false, true),
|
||||||
|
9
src/enums/drop-down-column.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export enum DropDownColumn {
|
||||||
|
GEN,
|
||||||
|
TYPES,
|
||||||
|
BIOME,
|
||||||
|
CAUGHT,
|
||||||
|
UNLOCKS,
|
||||||
|
MISC,
|
||||||
|
SORT
|
||||||
|
}
|
@ -5,7 +5,9 @@ import { globalScene } from "#app/global-scene";
|
|||||||
import type { Variant } from "#app/sprites/variant";
|
import type { Variant } from "#app/sprites/variant";
|
||||||
import { populateVariantColors, variantColorCache } from "#app/sprites/variant";
|
import { populateVariantColors, variantColorCache } from "#app/sprites/variant";
|
||||||
import { variantData } from "#app/sprites/variant";
|
import { variantData } from "#app/sprites/variant";
|
||||||
import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "#app/ui/battle-info";
|
import BattleInfo from "#app/ui/battle-info/battle-info";
|
||||||
|
import { EnemyBattleInfo } from "#app/ui/battle-info/enemy-battle-info";
|
||||||
|
import { PlayerBattleInfo } from "#app/ui/battle-info/player-battle-info";
|
||||||
import type Move from "#app/data/moves/move";
|
import type Move from "#app/data/moves/move";
|
||||||
import {
|
import {
|
||||||
HighCritAttr,
|
HighCritAttr,
|
||||||
@ -3352,22 +3354,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
return this.battleInfo.updateInfo(this, instant);
|
return this.battleInfo.updateInfo(this, instant);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Show or hide the type effectiveness multiplier window
|
|
||||||
* Passing undefined will hide the window
|
|
||||||
*/
|
|
||||||
updateEffectiveness(effectiveness?: string) {
|
|
||||||
this.battleInfo.updateEffectiveness(effectiveness);
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleStats(visible: boolean): void {
|
toggleStats(visible: boolean): void {
|
||||||
this.battleInfo.toggleStats(visible);
|
this.battleInfo.toggleStats(visible);
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleFlyout(visible: boolean): void {
|
|
||||||
this.battleInfo.toggleFlyout(visible);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds experience to this PlayerPokemon, subject to wave based level caps.
|
* Adds experience to this PlayerPokemon, subject to wave based level caps.
|
||||||
* @param exp The amount of experience to add
|
* @param exp The amount of experience to add
|
||||||
@ -5523,6 +5513,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class PlayerPokemon extends Pokemon {
|
export class PlayerPokemon extends Pokemon {
|
||||||
|
protected battleInfo: PlayerBattleInfo;
|
||||||
public compatibleTms: Moves[];
|
public compatibleTms: Moves[];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -6043,6 +6034,7 @@ export class PlayerPokemon extends Pokemon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class EnemyPokemon extends Pokemon {
|
export class EnemyPokemon extends Pokemon {
|
||||||
|
protected battleInfo: EnemyBattleInfo;
|
||||||
public trainerSlot: TrainerSlot;
|
public trainerSlot: TrainerSlot;
|
||||||
public aiType: AiType;
|
public aiType: AiType;
|
||||||
public bossSegments: number;
|
public bossSegments: number;
|
||||||
@ -6714,6 +6706,19 @@ export class EnemyPokemon extends Pokemon {
|
|||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show or hide the type effectiveness multiplier window
|
||||||
|
* Passing undefined will hide the window
|
||||||
|
*/
|
||||||
|
updateEffectiveness(effectiveness?: string) {
|
||||||
|
this.battleInfo.updateEffectiveness(effectiveness);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleFlyout(visible: boolean): void {
|
||||||
|
this.battleInfo.toggleFlyout(visible);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -7,7 +7,7 @@ import type PokemonSpecies from "./data/pokemon-species";
|
|||||||
import { allSpecies } from "./data/pokemon-species";
|
import { allSpecies } from "./data/pokemon-species";
|
||||||
import type { Arena } from "./field/arena";
|
import type { Arena } from "./field/arena";
|
||||||
import Overrides from "#app/overrides";
|
import Overrides from "#app/overrides";
|
||||||
import { randSeedInt, randSeedItem } from "#app/utils/common";
|
import { isNullOrUndefined, randSeedInt, randSeedItem } from "#app/utils/common";
|
||||||
import { Biome } from "#enums/biome";
|
import { Biome } from "#enums/biome";
|
||||||
import { Species } from "#enums/species";
|
import { Species } from "#enums/species";
|
||||||
import { Challenges } from "./enums/challenges";
|
import { Challenges } from "./enums/challenges";
|
||||||
@ -124,16 +124,20 @@ export class GameMode implements GameModeConfig {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns either:
|
* @returns either:
|
||||||
* - random biome for Daily mode
|
|
||||||
* - override from overrides.ts
|
* - override from overrides.ts
|
||||||
|
* - random biome for Daily mode
|
||||||
* - Town
|
* - Town
|
||||||
*/
|
*/
|
||||||
getStartingBiome(): Biome {
|
getStartingBiome(): Biome {
|
||||||
|
if (!isNullOrUndefined(Overrides.STARTING_BIOME_OVERRIDE)) {
|
||||||
|
return Overrides.STARTING_BIOME_OVERRIDE;
|
||||||
|
}
|
||||||
|
|
||||||
switch (this.modeId) {
|
switch (this.modeId) {
|
||||||
case GameModes.DAILY:
|
case GameModes.DAILY:
|
||||||
return getDailyStartingBiome();
|
return getDailyStartingBiome();
|
||||||
default:
|
default:
|
||||||
return Overrides.STARTING_BIOME_OVERRIDE || Biome.TOWN;
|
return Biome.TOWN;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ window.addEventListener("unhandledrejection", event => {
|
|||||||
const setPositionRelative = function (guideObject: Phaser.GameObjects.GameObject, x: number, y: number) {
|
const setPositionRelative = function (guideObject: Phaser.GameObjects.GameObject, x: number, y: number) {
|
||||||
const offsetX = guideObject.width * (-0.5 + (0.5 - guideObject.originX));
|
const offsetX = guideObject.width * (-0.5 + (0.5 - guideObject.originX));
|
||||||
const offsetY = guideObject.height * (-0.5 + (0.5 - guideObject.originY));
|
const offsetY = guideObject.height * (-0.5 + (0.5 - guideObject.originY));
|
||||||
this.setPosition(guideObject.x + offsetX + x, guideObject.y + offsetY + y);
|
return this.setPosition(guideObject.x + offsetX + x, guideObject.y + offsetY + y);
|
||||||
};
|
};
|
||||||
|
|
||||||
Phaser.GameObjects.Container.prototype.setPositionRelative = setPositionRelative;
|
Phaser.GameObjects.Container.prototype.setPositionRelative = setPositionRelative;
|
||||||
|
@ -73,7 +73,7 @@ class DefaultOverrides {
|
|||||||
*/
|
*/
|
||||||
readonly BATTLE_STYLE_OVERRIDE: BattleStyle | null = null;
|
readonly BATTLE_STYLE_OVERRIDE: BattleStyle | null = null;
|
||||||
readonly STARTING_WAVE_OVERRIDE: number = 0;
|
readonly STARTING_WAVE_OVERRIDE: number = 0;
|
||||||
readonly STARTING_BIOME_OVERRIDE: Biome = Biome.TOWN;
|
readonly STARTING_BIOME_OVERRIDE: Biome | null = null;
|
||||||
readonly ARENA_TINT_OVERRIDE: TimeOfDay | null = null;
|
readonly ARENA_TINT_OVERRIDE: TimeOfDay | null = null;
|
||||||
/** Multiplies XP gained by this value including 0. Set to null to ignore the override. */
|
/** Multiplies XP gained by this value including 0. Set to null to ignore the override. */
|
||||||
readonly XP_MULTIPLIER_OVERRIDE: number | null = null;
|
readonly XP_MULTIPLIER_OVERRIDE: number | null = null;
|
||||||
|
@ -125,6 +125,12 @@ export class SwitchSummonPhase extends SummonPhase {
|
|||||||
const switchedInPokemon: Pokemon | undefined = party[this.slotIndex];
|
const switchedInPokemon: Pokemon | undefined = party[this.slotIndex];
|
||||||
this.lastPokemon = this.getPokemon();
|
this.lastPokemon = this.getPokemon();
|
||||||
|
|
||||||
|
// Defensive programming: Overcome the bug where the summon data has somehow not been reset
|
||||||
|
// prior to switching in a new Pokemon.
|
||||||
|
// Force the switch to occur and load the assets for the new pokemon, ignoring override.
|
||||||
|
switchedInPokemon.resetSummonData();
|
||||||
|
switchedInPokemon.loadAssets(true);
|
||||||
|
|
||||||
applyPreSummonAbAttrs(PreSummonAbAttr, switchedInPokemon);
|
applyPreSummonAbAttrs(PreSummonAbAttr, switchedInPokemon);
|
||||||
applyPreSwitchOutAbAttrs(PreSwitchOutAbAttr, this.lastPokemon);
|
applyPreSwitchOutAbAttrs(PreSwitchOutAbAttr, this.lastPokemon);
|
||||||
if (!switchedInPokemon) {
|
if (!switchedInPokemon) {
|
||||||
@ -132,6 +138,7 @@ export class SwitchSummonPhase extends SummonPhase {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (this.switchType === SwitchType.BATON_PASS) {
|
if (this.switchType === SwitchType.BATON_PASS) {
|
||||||
// If switching via baton pass, update opposing tags coming from the prior pokemon
|
// If switching via baton pass, update opposing tags coming from the prior pokemon
|
||||||
(this.player ? globalScene.getEnemyField() : globalScene.getPlayerField()).forEach((enemyPokemon: Pokemon) =>
|
(this.player ? globalScene.getEnemyField() : globalScene.getPlayerField()).forEach((enemyPokemon: Pokemon) =>
|
||||||
|
12
src/typings/phaser/index.d.ts
vendored
@ -20,37 +20,37 @@ declare module "phaser" {
|
|||||||
/**
|
/**
|
||||||
* Sets this object's position relative to another object with a given offset
|
* Sets this object's position relative to another object with a given offset
|
||||||
*/
|
*/
|
||||||
setPositionRelative(guideObject: any, x: number, y: number): void;
|
setPositionRelative(guideObject: any, x: number, y: number): this;
|
||||||
}
|
}
|
||||||
interface Sprite {
|
interface Sprite {
|
||||||
/**
|
/**
|
||||||
* Sets this object's position relative to another object with a given offset
|
* Sets this object's position relative to another object with a given offset
|
||||||
*/
|
*/
|
||||||
setPositionRelative(guideObject: any, x: number, y: number): void;
|
setPositionRelative(guideObject: any, x: number, y: number): this;
|
||||||
}
|
}
|
||||||
interface Image {
|
interface Image {
|
||||||
/**
|
/**
|
||||||
* Sets this object's position relative to another object with a given offset
|
* Sets this object's position relative to another object with a given offset
|
||||||
*/
|
*/
|
||||||
setPositionRelative(guideObject: any, x: number, y: number): void;
|
setPositionRelative(guideObject: any, x: number, y: number): this;
|
||||||
}
|
}
|
||||||
interface NineSlice {
|
interface NineSlice {
|
||||||
/**
|
/**
|
||||||
* Sets this object's position relative to another object with a given offset
|
* Sets this object's position relative to another object with a given offset
|
||||||
*/
|
*/
|
||||||
setPositionRelative(guideObject: any, x: number, y: number): void;
|
setPositionRelative(guideObject: any, x: number, y: number): this;
|
||||||
}
|
}
|
||||||
interface Text {
|
interface Text {
|
||||||
/**
|
/**
|
||||||
* Sets this object's position relative to another object with a given offset
|
* Sets this object's position relative to another object with a given offset
|
||||||
*/
|
*/
|
||||||
setPositionRelative(guideObject: any, x: number, y: number): void;
|
setPositionRelative(guideObject: any, x: number, y: number): this;
|
||||||
}
|
}
|
||||||
interface Rectangle {
|
interface Rectangle {
|
||||||
/**
|
/**
|
||||||
* Sets this object's position relative to another object with a given offset
|
* Sets this object's position relative to another object with a given offset
|
||||||
*/
|
*/
|
||||||
setPositionRelative(guideObject: any, x: number, y: number): void;
|
setPositionRelative(guideObject: any, x: number, y: number): this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,7 +161,7 @@ export class UiInputs {
|
|||||||
|
|
||||||
buttonInfo(pressed = true): void {
|
buttonInfo(pressed = true): void {
|
||||||
if (globalScene.showMovesetFlyout) {
|
if (globalScene.showMovesetFlyout) {
|
||||||
for (const p of globalScene.getField().filter(p => p?.isActive(true))) {
|
for (const p of globalScene.getEnemyField().filter(p => p?.isActive(true))) {
|
||||||
p.toggleFlyout(pressed);
|
p.toggleFlyout(pressed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type { default as Pokemon } from "../field/pokemon";
|
import type { EnemyPokemon, default as Pokemon } from "../field/pokemon";
|
||||||
import { addTextObject, TextStyle } from "./text";
|
import { addTextObject, TextStyle } from "./text";
|
||||||
import { fixedInt } from "#app/utils/common";
|
import { fixedInt } from "#app/utils/common";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
@ -126,7 +126,7 @@ export default class BattleFlyout extends Phaser.GameObjects.Container {
|
|||||||
* Links the given {@linkcode Pokemon} and subscribes to the {@linkcode BattleSceneEventType.MOVE_USED} event
|
* Links the given {@linkcode Pokemon} and subscribes to the {@linkcode BattleSceneEventType.MOVE_USED} event
|
||||||
* @param pokemon {@linkcode Pokemon} to link to this flyout
|
* @param pokemon {@linkcode Pokemon} to link to this flyout
|
||||||
*/
|
*/
|
||||||
initInfo(pokemon: Pokemon) {
|
initInfo(pokemon: EnemyPokemon) {
|
||||||
this.pokemon = pokemon;
|
this.pokemon = pokemon;
|
||||||
|
|
||||||
this.name = `Flyout ${getPokemonNameWithAffix(this.pokemon)}`;
|
this.name = `Flyout ${getPokemonNameWithAffix(this.pokemon)}`;
|
||||||
|
@ -1,986 +0,0 @@
|
|||||||
import type { EnemyPokemon, default as Pokemon } from "../field/pokemon";
|
|
||||||
import { getLevelTotalExp, getLevelRelExp } from "../data/exp";
|
|
||||||
import { getLocalizedSpriteKey, fixedInt } from "#app/utils/common";
|
|
||||||
import { addTextObject, TextStyle } from "./text";
|
|
||||||
import { getGenderSymbol, getGenderColor, Gender } from "../data/gender";
|
|
||||||
import { StatusEffect } from "#enums/status-effect";
|
|
||||||
import { globalScene } from "#app/global-scene";
|
|
||||||
import { getTypeRgb } from "#app/data/type";
|
|
||||||
import { PokemonType } from "#enums/pokemon-type";
|
|
||||||
import { getVariantTint } from "#app/sprites/variant";
|
|
||||||
import { Stat } from "#enums/stat";
|
|
||||||
import BattleFlyout from "./battle-flyout";
|
|
||||||
import { WindowVariant, addWindow } from "./ui-theme";
|
|
||||||
import i18next from "i18next";
|
|
||||||
import { ExpGainsSpeed } from "#app/enums/exp-gains-speed";
|
|
||||||
|
|
||||||
export default class BattleInfo extends Phaser.GameObjects.Container {
|
|
||||||
public static readonly EXP_GAINS_DURATION_BASE = 1650;
|
|
||||||
|
|
||||||
private baseY: number;
|
|
||||||
|
|
||||||
private player: boolean;
|
|
||||||
private mini: boolean;
|
|
||||||
private boss: boolean;
|
|
||||||
private bossSegments: number;
|
|
||||||
private offset: boolean;
|
|
||||||
private lastName: string | null;
|
|
||||||
private lastTeraType: PokemonType;
|
|
||||||
private lastStatus: StatusEffect;
|
|
||||||
private lastHp: number;
|
|
||||||
private lastMaxHp: number;
|
|
||||||
private lastHpFrame: string | null;
|
|
||||||
private lastExp: number;
|
|
||||||
private lastLevelExp: number;
|
|
||||||
private lastLevel: number;
|
|
||||||
private lastLevelCapped: boolean;
|
|
||||||
private lastStats: string;
|
|
||||||
|
|
||||||
private box: Phaser.GameObjects.Sprite;
|
|
||||||
private nameText: Phaser.GameObjects.Text;
|
|
||||||
private genderText: Phaser.GameObjects.Text;
|
|
||||||
private ownedIcon: Phaser.GameObjects.Sprite;
|
|
||||||
private championRibbon: Phaser.GameObjects.Sprite;
|
|
||||||
private teraIcon: Phaser.GameObjects.Sprite;
|
|
||||||
private shinyIcon: Phaser.GameObjects.Sprite;
|
|
||||||
private fusionShinyIcon: Phaser.GameObjects.Sprite;
|
|
||||||
private splicedIcon: Phaser.GameObjects.Sprite;
|
|
||||||
private statusIndicator: Phaser.GameObjects.Sprite;
|
|
||||||
private levelContainer: Phaser.GameObjects.Container;
|
|
||||||
private hpBar: Phaser.GameObjects.Image;
|
|
||||||
private hpBarSegmentDividers: Phaser.GameObjects.Rectangle[];
|
|
||||||
private levelNumbersContainer: Phaser.GameObjects.Container;
|
|
||||||
private hpNumbersContainer: Phaser.GameObjects.Container;
|
|
||||||
private type1Icon: Phaser.GameObjects.Sprite;
|
|
||||||
private type2Icon: Phaser.GameObjects.Sprite;
|
|
||||||
private type3Icon: Phaser.GameObjects.Sprite;
|
|
||||||
private expBar: Phaser.GameObjects.Image;
|
|
||||||
|
|
||||||
// #region Type effectiveness hint objects
|
|
||||||
private effectivenessContainer: Phaser.GameObjects.Container;
|
|
||||||
private effectivenessWindow: Phaser.GameObjects.NineSlice;
|
|
||||||
private effectivenessText: Phaser.GameObjects.Text;
|
|
||||||
private currentEffectiveness?: string;
|
|
||||||
// #endregion
|
|
||||||
|
|
||||||
public expMaskRect: Phaser.GameObjects.Graphics;
|
|
||||||
|
|
||||||
private statsContainer: Phaser.GameObjects.Container;
|
|
||||||
private statsBox: Phaser.GameObjects.Sprite;
|
|
||||||
private statValuesContainer: Phaser.GameObjects.Container;
|
|
||||||
private statNumbers: Phaser.GameObjects.Sprite[];
|
|
||||||
|
|
||||||
public flyoutMenu?: BattleFlyout;
|
|
||||||
|
|
||||||
private statOrder: Stat[];
|
|
||||||
private readonly statOrderPlayer = [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.ACC, Stat.EVA, Stat.SPD];
|
|
||||||
private readonly statOrderEnemy = [Stat.HP, Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.ACC, Stat.EVA, Stat.SPD];
|
|
||||||
|
|
||||||
constructor(x: number, y: number, player: boolean) {
|
|
||||||
super(globalScene, x, y);
|
|
||||||
this.baseY = y;
|
|
||||||
this.player = player;
|
|
||||||
this.mini = !player;
|
|
||||||
this.boss = false;
|
|
||||||
this.offset = false;
|
|
||||||
this.lastName = null;
|
|
||||||
this.lastTeraType = PokemonType.UNKNOWN;
|
|
||||||
this.lastStatus = StatusEffect.NONE;
|
|
||||||
this.lastHp = -1;
|
|
||||||
this.lastMaxHp = -1;
|
|
||||||
this.lastHpFrame = null;
|
|
||||||
this.lastExp = -1;
|
|
||||||
this.lastLevelExp = -1;
|
|
||||||
this.lastLevel = -1;
|
|
||||||
|
|
||||||
// Initially invisible and shown via Pokemon.showInfo
|
|
||||||
this.setVisible(false);
|
|
||||||
|
|
||||||
this.box = globalScene.add.sprite(0, 0, this.getTextureName());
|
|
||||||
this.box.setName("box");
|
|
||||||
this.box.setOrigin(1, 0.5);
|
|
||||||
this.add(this.box);
|
|
||||||
|
|
||||||
this.nameText = addTextObject(player ? -115 : -124, player ? -15.2 : -11.2, "", TextStyle.BATTLE_INFO);
|
|
||||||
this.nameText.setName("text_name");
|
|
||||||
this.nameText.setOrigin(0, 0);
|
|
||||||
this.add(this.nameText);
|
|
||||||
|
|
||||||
this.genderText = addTextObject(0, 0, "", TextStyle.BATTLE_INFO);
|
|
||||||
this.genderText.setName("text_gender");
|
|
||||||
this.genderText.setOrigin(0, 0);
|
|
||||||
this.genderText.setPositionRelative(this.nameText, 0, 2);
|
|
||||||
this.add(this.genderText);
|
|
||||||
|
|
||||||
if (!this.player) {
|
|
||||||
this.ownedIcon = globalScene.add.sprite(0, 0, "icon_owned");
|
|
||||||
this.ownedIcon.setName("icon_owned");
|
|
||||||
this.ownedIcon.setVisible(false);
|
|
||||||
this.ownedIcon.setOrigin(0, 0);
|
|
||||||
this.ownedIcon.setPositionRelative(this.nameText, 0, 11.75);
|
|
||||||
this.add(this.ownedIcon);
|
|
||||||
|
|
||||||
this.championRibbon = globalScene.add.sprite(0, 0, "champion_ribbon");
|
|
||||||
this.championRibbon.setName("icon_champion_ribbon");
|
|
||||||
this.championRibbon.setVisible(false);
|
|
||||||
this.championRibbon.setOrigin(0, 0);
|
|
||||||
this.championRibbon.setPositionRelative(this.nameText, 8, 11.75);
|
|
||||||
this.add(this.championRibbon);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.teraIcon = globalScene.add.sprite(0, 0, "icon_tera");
|
|
||||||
this.teraIcon.setName("icon_tera");
|
|
||||||
this.teraIcon.setVisible(false);
|
|
||||||
this.teraIcon.setOrigin(0, 0);
|
|
||||||
this.teraIcon.setScale(0.5);
|
|
||||||
this.teraIcon.setPositionRelative(this.nameText, 0, 2);
|
|
||||||
this.teraIcon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 12, 15), Phaser.Geom.Rectangle.Contains);
|
|
||||||
this.add(this.teraIcon);
|
|
||||||
|
|
||||||
this.shinyIcon = globalScene.add.sprite(0, 0, "shiny_star");
|
|
||||||
this.shinyIcon.setName("icon_shiny");
|
|
||||||
this.shinyIcon.setVisible(false);
|
|
||||||
this.shinyIcon.setOrigin(0, 0);
|
|
||||||
this.shinyIcon.setScale(0.5);
|
|
||||||
this.shinyIcon.setPositionRelative(this.nameText, 0, 2);
|
|
||||||
this.shinyIcon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 12, 15), Phaser.Geom.Rectangle.Contains);
|
|
||||||
this.add(this.shinyIcon);
|
|
||||||
|
|
||||||
this.fusionShinyIcon = globalScene.add.sprite(0, 0, "shiny_star_2");
|
|
||||||
this.fusionShinyIcon.setName("icon_fusion_shiny");
|
|
||||||
this.fusionShinyIcon.setVisible(false);
|
|
||||||
this.fusionShinyIcon.setOrigin(0, 0);
|
|
||||||
this.fusionShinyIcon.setScale(0.5);
|
|
||||||
this.fusionShinyIcon.setPosition(this.shinyIcon.x, this.shinyIcon.y);
|
|
||||||
this.add(this.fusionShinyIcon);
|
|
||||||
|
|
||||||
this.splicedIcon = globalScene.add.sprite(0, 0, "icon_spliced");
|
|
||||||
this.splicedIcon.setName("icon_spliced");
|
|
||||||
this.splicedIcon.setVisible(false);
|
|
||||||
this.splicedIcon.setOrigin(0, 0);
|
|
||||||
this.splicedIcon.setScale(0.5);
|
|
||||||
this.splicedIcon.setPositionRelative(this.nameText, 0, 2);
|
|
||||||
this.splicedIcon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 12, 15), Phaser.Geom.Rectangle.Contains);
|
|
||||||
this.add(this.splicedIcon);
|
|
||||||
|
|
||||||
this.statusIndicator = globalScene.add.sprite(0, 0, getLocalizedSpriteKey("statuses"));
|
|
||||||
this.statusIndicator.setName("icon_status");
|
|
||||||
this.statusIndicator.setVisible(false);
|
|
||||||
this.statusIndicator.setOrigin(0, 0);
|
|
||||||
this.statusIndicator.setPositionRelative(this.nameText, 0, 11.5);
|
|
||||||
this.add(this.statusIndicator);
|
|
||||||
|
|
||||||
this.levelContainer = globalScene.add.container(player ? -41 : -50, player ? -10 : -5);
|
|
||||||
this.levelContainer.setName("container_level");
|
|
||||||
this.add(this.levelContainer);
|
|
||||||
|
|
||||||
const levelOverlay = globalScene.add.image(0, 0, "overlay_lv");
|
|
||||||
this.levelContainer.add(levelOverlay);
|
|
||||||
|
|
||||||
this.hpBar = globalScene.add.image(player ? -61 : -71, player ? -1 : 4.5, "overlay_hp");
|
|
||||||
this.hpBar.setName("hp_bar");
|
|
||||||
this.hpBar.setOrigin(0);
|
|
||||||
this.add(this.hpBar);
|
|
||||||
|
|
||||||
this.hpBarSegmentDividers = [];
|
|
||||||
|
|
||||||
this.levelNumbersContainer = globalScene.add.container(9.5, globalScene.uiTheme ? 0 : -0.5);
|
|
||||||
this.levelNumbersContainer.setName("container_level");
|
|
||||||
this.levelContainer.add(this.levelNumbersContainer);
|
|
||||||
|
|
||||||
if (this.player) {
|
|
||||||
this.hpNumbersContainer = globalScene.add.container(-15, 10);
|
|
||||||
this.hpNumbersContainer.setName("container_hp");
|
|
||||||
this.add(this.hpNumbersContainer);
|
|
||||||
|
|
||||||
const expBar = globalScene.add.image(-98, 18, "overlay_exp");
|
|
||||||
expBar.setName("overlay_exp");
|
|
||||||
expBar.setOrigin(0);
|
|
||||||
this.add(expBar);
|
|
||||||
|
|
||||||
const expMaskRect = globalScene.make.graphics({});
|
|
||||||
expMaskRect.setScale(6);
|
|
||||||
expMaskRect.fillStyle(0xffffff);
|
|
||||||
expMaskRect.beginPath();
|
|
||||||
expMaskRect.fillRect(127, 126, 85, 2);
|
|
||||||
|
|
||||||
const expMask = expMaskRect.createGeometryMask();
|
|
||||||
|
|
||||||
expBar.setMask(expMask);
|
|
||||||
|
|
||||||
this.expBar = expBar;
|
|
||||||
this.expMaskRect = expMaskRect;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.statsContainer = globalScene.add.container(0, 0);
|
|
||||||
this.statsContainer.setName("container_stats");
|
|
||||||
this.statsContainer.setAlpha(0);
|
|
||||||
this.add(this.statsContainer);
|
|
||||||
|
|
||||||
this.statsBox = globalScene.add.sprite(0, 0, `${this.getTextureName()}_stats`);
|
|
||||||
this.statsBox.setName("box_stats");
|
|
||||||
this.statsBox.setOrigin(1, 0.5);
|
|
||||||
this.statsContainer.add(this.statsBox);
|
|
||||||
|
|
||||||
const statLabels: Phaser.GameObjects.Sprite[] = [];
|
|
||||||
this.statNumbers = [];
|
|
||||||
|
|
||||||
this.statValuesContainer = globalScene.add.container(0, 0);
|
|
||||||
this.statsContainer.add(this.statValuesContainer);
|
|
||||||
|
|
||||||
// this gives us a different starting location from the left of the label and padding between stats for a player vs enemy
|
|
||||||
// since the player won't have HP to show, it doesn't need to change from the current version
|
|
||||||
const startingX = this.player ? -this.statsBox.width + 8 : -this.statsBox.width + 5;
|
|
||||||
const paddingX = this.player ? 4 : 2;
|
|
||||||
const statOverflow = this.player ? 1 : 0;
|
|
||||||
this.statOrder = this.player ? this.statOrderPlayer : this.statOrderEnemy; // this tells us whether or not to use the player or enemy battle stat order
|
|
||||||
|
|
||||||
this.statOrder.map((s, i) => {
|
|
||||||
// we do a check for i > statOverflow to see when the stat labels go onto the next column
|
|
||||||
// For enemies, we have HP (i=0) by itself then a new column, so we check for i > 0
|
|
||||||
// For players, we don't have HP, so we start with i = 0 and i = 1 for our first column, and so need to check for i > 1
|
|
||||||
const statX =
|
|
||||||
i > statOverflow
|
|
||||||
? this.statNumbers[Math.max(i - 2, 0)].x + this.statNumbers[Math.max(i - 2, 0)].width + paddingX
|
|
||||||
: startingX; // we have the Math.max(i - 2, 0) in there so for i===1 to not return a negative number; since this is now based on anything >0 instead of >1, we need to allow for i-2 < 0
|
|
||||||
|
|
||||||
const baseY = -this.statsBox.height / 2 + 4; // this is the baseline for the y-axis
|
|
||||||
let statY: number; // this will be the y-axis placement for the labels
|
|
||||||
if (this.statOrder[i] === Stat.SPD || this.statOrder[i] === Stat.HP) {
|
|
||||||
statY = baseY + 5;
|
|
||||||
} else {
|
|
||||||
statY = baseY + (!!(i % 2) === this.player ? 10 : 0); // we compare i % 2 against this.player to tell us where to place the label; because this.battleStatOrder for enemies has HP, this.battleStatOrder[1]=ATK, but for players this.battleStatOrder[0]=ATK, so this comparing i % 2 to this.player fixes this issue for us
|
|
||||||
}
|
|
||||||
|
|
||||||
const statLabel = globalScene.add.sprite(statX, statY, "pbinfo_stat", Stat[s]);
|
|
||||||
statLabel.setName("icon_stat_label_" + i.toString());
|
|
||||||
statLabel.setOrigin(0, 0);
|
|
||||||
statLabels.push(statLabel);
|
|
||||||
this.statValuesContainer.add(statLabel);
|
|
||||||
|
|
||||||
const statNumber = globalScene.add.sprite(
|
|
||||||
statX + statLabel.width,
|
|
||||||
statY,
|
|
||||||
"pbinfo_stat_numbers",
|
|
||||||
this.statOrder[i] !== Stat.HP ? "3" : "empty",
|
|
||||||
);
|
|
||||||
statNumber.setName("icon_stat_number_" + i.toString());
|
|
||||||
statNumber.setOrigin(0, 0);
|
|
||||||
this.statNumbers.push(statNumber);
|
|
||||||
this.statValuesContainer.add(statNumber);
|
|
||||||
|
|
||||||
if (this.statOrder[i] === Stat.HP) {
|
|
||||||
statLabel.setVisible(false);
|
|
||||||
statNumber.setVisible(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!this.player) {
|
|
||||||
this.flyoutMenu = new BattleFlyout(this.player);
|
|
||||||
this.add(this.flyoutMenu);
|
|
||||||
|
|
||||||
this.moveBelow<Phaser.GameObjects.GameObject>(this.flyoutMenu, this.box);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.type1Icon = globalScene.add.sprite(
|
|
||||||
player ? -139 : -15,
|
|
||||||
player ? -17 : -15.5,
|
|
||||||
`pbinfo_${player ? "player" : "enemy"}_type1`,
|
|
||||||
);
|
|
||||||
this.type1Icon.setName("icon_type_1");
|
|
||||||
this.type1Icon.setOrigin(0, 0);
|
|
||||||
this.add(this.type1Icon);
|
|
||||||
|
|
||||||
this.type2Icon = globalScene.add.sprite(
|
|
||||||
player ? -139 : -15,
|
|
||||||
player ? -1 : -2.5,
|
|
||||||
`pbinfo_${player ? "player" : "enemy"}_type2`,
|
|
||||||
);
|
|
||||||
this.type2Icon.setName("icon_type_2");
|
|
||||||
this.type2Icon.setOrigin(0, 0);
|
|
||||||
this.add(this.type2Icon);
|
|
||||||
|
|
||||||
this.type3Icon = globalScene.add.sprite(
|
|
||||||
player ? -154 : 0,
|
|
||||||
player ? -17 : -15.5,
|
|
||||||
`pbinfo_${player ? "player" : "enemy"}_type`,
|
|
||||||
);
|
|
||||||
this.type3Icon.setName("icon_type_3");
|
|
||||||
this.type3Icon.setOrigin(0, 0);
|
|
||||||
this.add(this.type3Icon);
|
|
||||||
|
|
||||||
if (!this.player) {
|
|
||||||
this.effectivenessContainer = globalScene.add.container(0, 0);
|
|
||||||
this.effectivenessContainer.setPositionRelative(this.type1Icon, 22, 4);
|
|
||||||
this.effectivenessContainer.setVisible(false);
|
|
||||||
this.add(this.effectivenessContainer);
|
|
||||||
|
|
||||||
this.effectivenessText = addTextObject(5, 4.5, "", TextStyle.BATTLE_INFO);
|
|
||||||
this.effectivenessWindow = addWindow(0, 0, 0, 20, undefined, false, undefined, undefined, WindowVariant.XTHIN);
|
|
||||||
|
|
||||||
this.effectivenessContainer.add(this.effectivenessWindow);
|
|
||||||
this.effectivenessContainer.add(this.effectivenessText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getStatsValueContainer(): Phaser.GameObjects.Container {
|
|
||||||
return this.statValuesContainer;
|
|
||||||
}
|
|
||||||
|
|
||||||
initInfo(pokemon: Pokemon) {
|
|
||||||
this.updateNameText(pokemon);
|
|
||||||
const nameTextWidth = this.nameText.displayWidth;
|
|
||||||
|
|
||||||
this.name = pokemon.getNameToRender();
|
|
||||||
this.box.name = pokemon.getNameToRender();
|
|
||||||
|
|
||||||
this.flyoutMenu?.initInfo(pokemon);
|
|
||||||
|
|
||||||
this.genderText.setText(getGenderSymbol(pokemon.gender));
|
|
||||||
this.genderText.setColor(getGenderColor(pokemon.gender));
|
|
||||||
this.genderText.setPositionRelative(this.nameText, nameTextWidth, 0);
|
|
||||||
|
|
||||||
this.lastTeraType = pokemon.getTeraType();
|
|
||||||
|
|
||||||
this.teraIcon.setPositionRelative(this.nameText, nameTextWidth + this.genderText.displayWidth + 1, 2);
|
|
||||||
this.teraIcon.setVisible(pokemon.isTerastallized);
|
|
||||||
this.teraIcon.on("pointerover", () => {
|
|
||||||
if (pokemon.isTerastallized) {
|
|
||||||
globalScene.ui.showTooltip(
|
|
||||||
"",
|
|
||||||
i18next.t("fightUiHandler:teraHover", {
|
|
||||||
type: i18next.t(`pokemonInfo:Type.${PokemonType[this.lastTeraType]}`),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.teraIcon.on("pointerout", () => globalScene.ui.hideTooltip());
|
|
||||||
|
|
||||||
const isFusion = pokemon.isFusion(true);
|
|
||||||
|
|
||||||
this.splicedIcon.setPositionRelative(
|
|
||||||
this.nameText,
|
|
||||||
nameTextWidth + this.genderText.displayWidth + 1 + (this.teraIcon.visible ? this.teraIcon.displayWidth + 1 : 0),
|
|
||||||
2.5,
|
|
||||||
);
|
|
||||||
this.splicedIcon.setVisible(isFusion);
|
|
||||||
if (this.splicedIcon.visible) {
|
|
||||||
this.splicedIcon.on("pointerover", () =>
|
|
||||||
globalScene.ui.showTooltip(
|
|
||||||
"",
|
|
||||||
`${pokemon.species.getName(pokemon.formIndex)}/${pokemon.fusionSpecies?.getName(pokemon.fusionFormIndex)}`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
this.splicedIcon.on("pointerout", () => globalScene.ui.hideTooltip());
|
|
||||||
}
|
|
||||||
|
|
||||||
const doubleShiny = isFusion && pokemon.shiny && pokemon.fusionShiny;
|
|
||||||
const baseVariant = !doubleShiny ? pokemon.getVariant(true) : pokemon.variant;
|
|
||||||
|
|
||||||
this.shinyIcon.setPositionRelative(
|
|
||||||
this.nameText,
|
|
||||||
nameTextWidth +
|
|
||||||
this.genderText.displayWidth +
|
|
||||||
1 +
|
|
||||||
(this.teraIcon.visible ? this.teraIcon.displayWidth + 1 : 0) +
|
|
||||||
(this.splicedIcon.visible ? this.splicedIcon.displayWidth + 1 : 0),
|
|
||||||
2.5,
|
|
||||||
);
|
|
||||||
this.shinyIcon.setTexture(`shiny_star${doubleShiny ? "_1" : ""}`);
|
|
||||||
this.shinyIcon.setVisible(pokemon.isShiny());
|
|
||||||
this.shinyIcon.setTint(getVariantTint(baseVariant));
|
|
||||||
if (this.shinyIcon.visible) {
|
|
||||||
const shinyDescriptor =
|
|
||||||
doubleShiny || baseVariant
|
|
||||||
? `${baseVariant === 2 ? i18next.t("common:epicShiny") : baseVariant === 1 ? i18next.t("common:rareShiny") : i18next.t("common:commonShiny")}${doubleShiny ? `/${pokemon.fusionVariant === 2 ? i18next.t("common:epicShiny") : pokemon.fusionVariant === 1 ? i18next.t("common:rareShiny") : i18next.t("common:commonShiny")}` : ""}`
|
|
||||||
: "";
|
|
||||||
this.shinyIcon.on("pointerover", () =>
|
|
||||||
globalScene.ui.showTooltip(
|
|
||||||
"",
|
|
||||||
`${i18next.t("common:shinyOnHover")}${shinyDescriptor ? ` (${shinyDescriptor})` : ""}`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
this.shinyIcon.on("pointerout", () => globalScene.ui.hideTooltip());
|
|
||||||
}
|
|
||||||
|
|
||||||
this.fusionShinyIcon.setPosition(this.shinyIcon.x, this.shinyIcon.y);
|
|
||||||
this.fusionShinyIcon.setVisible(doubleShiny);
|
|
||||||
if (isFusion) {
|
|
||||||
this.fusionShinyIcon.setTint(getVariantTint(pokemon.fusionVariant));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.player) {
|
|
||||||
if (this.nameText.visible) {
|
|
||||||
this.nameText.on("pointerover", () =>
|
|
||||||
globalScene.ui.showTooltip(
|
|
||||||
"",
|
|
||||||
i18next.t("battleInfo:generation", {
|
|
||||||
generation: i18next.t(`starterSelectUiHandler:gen${pokemon.species.generation}`),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
this.nameText.on("pointerout", () => globalScene.ui.hideTooltip());
|
|
||||||
}
|
|
||||||
|
|
||||||
const dexEntry = globalScene.gameData.dexData[pokemon.species.speciesId];
|
|
||||||
this.ownedIcon.setVisible(!!dexEntry.caughtAttr);
|
|
||||||
const opponentPokemonDexAttr = pokemon.getDexAttr();
|
|
||||||
if (globalScene.gameMode.isClassic) {
|
|
||||||
if (
|
|
||||||
globalScene.gameData.starterData[pokemon.species.getRootSpeciesId()].classicWinCount > 0 &&
|
|
||||||
globalScene.gameData.starterData[pokemon.species.getRootSpeciesId(true)].classicWinCount > 0
|
|
||||||
) {
|
|
||||||
this.championRibbon.setVisible(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if Player owns all genders and forms of the Pokemon
|
|
||||||
const missingDexAttrs = (dexEntry.caughtAttr & opponentPokemonDexAttr) < opponentPokemonDexAttr;
|
|
||||||
|
|
||||||
const ownedAbilityAttrs = globalScene.gameData.starterData[pokemon.species.getRootSpeciesId()].abilityAttr;
|
|
||||||
|
|
||||||
// Check if the player owns ability for the root form
|
|
||||||
const playerOwnsThisAbility = pokemon.checkIfPlayerHasAbilityOfStarter(ownedAbilityAttrs);
|
|
||||||
|
|
||||||
if (missingDexAttrs || !playerOwnsThisAbility) {
|
|
||||||
this.ownedIcon.setTint(0x808080);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.boss) {
|
|
||||||
this.updateBossSegmentDividers(pokemon as EnemyPokemon);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.hpBar.setScale(pokemon.getHpRatio(true), 1);
|
|
||||||
this.lastHpFrame = this.hpBar.scaleX > 0.5 ? "high" : this.hpBar.scaleX > 0.25 ? "medium" : "low";
|
|
||||||
this.hpBar.setFrame(this.lastHpFrame);
|
|
||||||
if (this.player) {
|
|
||||||
this.setHpNumbers(pokemon.hp, pokemon.getMaxHp());
|
|
||||||
}
|
|
||||||
this.lastHp = pokemon.hp;
|
|
||||||
this.lastMaxHp = pokemon.getMaxHp();
|
|
||||||
|
|
||||||
this.setLevel(pokemon.level);
|
|
||||||
this.lastLevel = pokemon.level;
|
|
||||||
|
|
||||||
this.shinyIcon.setVisible(pokemon.isShiny());
|
|
||||||
|
|
||||||
const types = pokemon.getTypes(true, false, undefined, true);
|
|
||||||
this.type1Icon.setTexture(`pbinfo_${this.player ? "player" : "enemy"}_type${types.length > 1 ? "1" : ""}`);
|
|
||||||
this.type1Icon.setFrame(PokemonType[types[0]].toLowerCase());
|
|
||||||
this.type2Icon.setVisible(types.length > 1);
|
|
||||||
this.type3Icon.setVisible(types.length > 2);
|
|
||||||
if (types.length > 1) {
|
|
||||||
this.type2Icon.setFrame(PokemonType[types[1]].toLowerCase());
|
|
||||||
}
|
|
||||||
if (types.length > 2) {
|
|
||||||
this.type3Icon.setFrame(PokemonType[types[2]].toLowerCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.player) {
|
|
||||||
this.expMaskRect.x = (pokemon.levelExp / getLevelTotalExp(pokemon.level, pokemon.species.growthRate)) * 510;
|
|
||||||
this.lastExp = pokemon.exp;
|
|
||||||
this.lastLevelExp = pokemon.levelExp;
|
|
||||||
|
|
||||||
this.statValuesContainer.setPosition(8, 7);
|
|
||||||
}
|
|
||||||
|
|
||||||
const stats = this.statOrder.map(() => 0);
|
|
||||||
|
|
||||||
this.lastStats = stats.join("");
|
|
||||||
this.updateStats(stats);
|
|
||||||
}
|
|
||||||
|
|
||||||
getTextureName(): string {
|
|
||||||
return `pbinfo_${this.player ? "player" : "enemy"}${!this.player && this.boss ? "_boss" : this.mini ? "_mini" : ""}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
setMini(mini: boolean): void {
|
|
||||||
if (this.mini === mini) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.mini = mini;
|
|
||||||
|
|
||||||
this.box.setTexture(this.getTextureName());
|
|
||||||
this.statsBox.setTexture(`${this.getTextureName()}_stats`);
|
|
||||||
|
|
||||||
if (this.player) {
|
|
||||||
this.y -= 12 * (mini ? 1 : -1);
|
|
||||||
this.baseY = this.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
const offsetElements = [
|
|
||||||
this.nameText,
|
|
||||||
this.genderText,
|
|
||||||
this.teraIcon,
|
|
||||||
this.splicedIcon,
|
|
||||||
this.shinyIcon,
|
|
||||||
this.statusIndicator,
|
|
||||||
this.levelContainer,
|
|
||||||
];
|
|
||||||
offsetElements.forEach(el => (el.y += 1.5 * (mini ? -1 : 1)));
|
|
||||||
|
|
||||||
[this.type1Icon, this.type2Icon, this.type3Icon].forEach(el => {
|
|
||||||
el.x += 4 * (mini ? 1 : -1);
|
|
||||||
el.y += -8 * (mini ? 1 : -1);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.statValuesContainer.x += 2 * (mini ? 1 : -1);
|
|
||||||
this.statValuesContainer.y += -7 * (mini ? 1 : -1);
|
|
||||||
|
|
||||||
const toggledElements = [this.hpNumbersContainer, this.expBar];
|
|
||||||
toggledElements.forEach(el => el.setVisible(!mini));
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleStats(visible: boolean): void {
|
|
||||||
globalScene.tweens.add({
|
|
||||||
targets: this.statsContainer,
|
|
||||||
duration: fixedInt(125),
|
|
||||||
ease: "Sine.easeInOut",
|
|
||||||
alpha: visible ? 1 : 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
updateBossSegments(pokemon: EnemyPokemon): void {
|
|
||||||
const boss = !!pokemon.bossSegments;
|
|
||||||
|
|
||||||
if (boss !== this.boss) {
|
|
||||||
this.boss = boss;
|
|
||||||
|
|
||||||
[
|
|
||||||
this.nameText,
|
|
||||||
this.genderText,
|
|
||||||
this.teraIcon,
|
|
||||||
this.splicedIcon,
|
|
||||||
this.shinyIcon,
|
|
||||||
this.ownedIcon,
|
|
||||||
this.championRibbon,
|
|
||||||
this.statusIndicator,
|
|
||||||
this.statValuesContainer,
|
|
||||||
].map(e => (e.x += 48 * (boss ? -1 : 1)));
|
|
||||||
this.hpBar.x += 38 * (boss ? -1 : 1);
|
|
||||||
this.hpBar.y += 2 * (this.boss ? -1 : 1);
|
|
||||||
this.levelContainer.x += 2 * (boss ? -1 : 1);
|
|
||||||
this.hpBar.setTexture(`overlay_hp${boss ? "_boss" : ""}`);
|
|
||||||
this.box.setTexture(this.getTextureName());
|
|
||||||
this.statsBox.setTexture(`${this.getTextureName()}_stats`);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.bossSegments = boss ? pokemon.bossSegments : 0;
|
|
||||||
this.updateBossSegmentDividers(pokemon);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateBossSegmentDividers(pokemon: EnemyPokemon): void {
|
|
||||||
while (this.hpBarSegmentDividers.length) {
|
|
||||||
this.hpBarSegmentDividers.pop()?.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.boss && this.bossSegments > 1) {
|
|
||||||
const uiTheme = globalScene.uiTheme;
|
|
||||||
const maxHp = pokemon.getMaxHp();
|
|
||||||
for (let s = 1; s < this.bossSegments; s++) {
|
|
||||||
const dividerX = (Math.round((maxHp / this.bossSegments) * s) / maxHp) * this.hpBar.width;
|
|
||||||
const divider = globalScene.add.rectangle(
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
this.hpBar.height - (uiTheme ? 0 : 1),
|
|
||||||
pokemon.bossSegmentIndex >= s ? 0xffffff : 0x404040,
|
|
||||||
);
|
|
||||||
divider.setOrigin(0.5, 0);
|
|
||||||
divider.setName("hpBar_divider_" + s.toString());
|
|
||||||
this.add(divider);
|
|
||||||
this.moveBelow(divider as Phaser.GameObjects.GameObject, this.statsContainer);
|
|
||||||
|
|
||||||
divider.setPositionRelative(this.hpBar, dividerX, uiTheme ? 0 : 1);
|
|
||||||
this.hpBarSegmentDividers.push(divider);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setOffset(offset: boolean): void {
|
|
||||||
if (this.offset === offset) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.offset = offset;
|
|
||||||
|
|
||||||
this.x += 10 * (this.offset === this.player ? 1 : -1);
|
|
||||||
this.y += 27 * (this.offset ? 1 : -1);
|
|
||||||
this.baseY = this.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateInfo(pokemon: Pokemon, instant?: boolean): Promise<void> {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
if (!globalScene) {
|
|
||||||
return resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
const gender = pokemon.summonData.illusion?.gender ?? pokemon.gender;
|
|
||||||
|
|
||||||
this.genderText.setText(getGenderSymbol(gender));
|
|
||||||
this.genderText.setColor(getGenderColor(gender));
|
|
||||||
|
|
||||||
const nameUpdated = this.lastName !== pokemon.getNameToRender();
|
|
||||||
|
|
||||||
if (nameUpdated) {
|
|
||||||
this.updateNameText(pokemon);
|
|
||||||
this.genderText.setPositionRelative(this.nameText, this.nameText.displayWidth, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
const teraType = pokemon.isTerastallized ? pokemon.getTeraType() : PokemonType.UNKNOWN;
|
|
||||||
const teraTypeUpdated = this.lastTeraType !== teraType;
|
|
||||||
|
|
||||||
if (teraTypeUpdated) {
|
|
||||||
this.teraIcon.setVisible(teraType !== PokemonType.UNKNOWN);
|
|
||||||
this.teraIcon.setPositionRelative(
|
|
||||||
this.nameText,
|
|
||||||
this.nameText.displayWidth + this.genderText.displayWidth + 1,
|
|
||||||
2,
|
|
||||||
);
|
|
||||||
this.teraIcon.setTintFill(Phaser.Display.Color.GetColor(...getTypeRgb(teraType)));
|
|
||||||
this.lastTeraType = teraType;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isFusion = pokemon.isFusion(true);
|
|
||||||
|
|
||||||
if (nameUpdated || teraTypeUpdated) {
|
|
||||||
this.splicedIcon.setVisible(isFusion);
|
|
||||||
|
|
||||||
this.teraIcon.setPositionRelative(
|
|
||||||
this.nameText,
|
|
||||||
this.nameText.displayWidth + this.genderText.displayWidth + 1,
|
|
||||||
2,
|
|
||||||
);
|
|
||||||
this.splicedIcon.setPositionRelative(
|
|
||||||
this.nameText,
|
|
||||||
this.nameText.displayWidth +
|
|
||||||
this.genderText.displayWidth +
|
|
||||||
1 +
|
|
||||||
(this.teraIcon.visible ? this.teraIcon.displayWidth + 1 : 0),
|
|
||||||
1.5,
|
|
||||||
);
|
|
||||||
this.shinyIcon.setPositionRelative(
|
|
||||||
this.nameText,
|
|
||||||
this.nameText.displayWidth +
|
|
||||||
this.genderText.displayWidth +
|
|
||||||
1 +
|
|
||||||
(this.teraIcon.visible ? this.teraIcon.displayWidth + 1 : 0) +
|
|
||||||
(this.splicedIcon.visible ? this.splicedIcon.displayWidth + 1 : 0),
|
|
||||||
2.5,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.lastStatus !== (pokemon.status?.effect || StatusEffect.NONE)) {
|
|
||||||
this.lastStatus = pokemon.status?.effect || StatusEffect.NONE;
|
|
||||||
|
|
||||||
if (this.lastStatus !== StatusEffect.NONE) {
|
|
||||||
this.statusIndicator.setFrame(StatusEffect[this.lastStatus].toLowerCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
const offsetX = !this.player ? (this.ownedIcon.visible ? 8 : 0) + (this.championRibbon.visible ? 8 : 0) : 0;
|
|
||||||
this.statusIndicator.setPositionRelative(this.nameText, offsetX, 11.5);
|
|
||||||
|
|
||||||
this.statusIndicator.setVisible(!!this.lastStatus);
|
|
||||||
}
|
|
||||||
|
|
||||||
const types = pokemon.getTypes(true, false, undefined, true);
|
|
||||||
this.type1Icon.setTexture(`pbinfo_${this.player ? "player" : "enemy"}_type${types.length > 1 ? "1" : ""}`);
|
|
||||||
this.type1Icon.setFrame(PokemonType[types[0]].toLowerCase());
|
|
||||||
this.type2Icon.setVisible(types.length > 1);
|
|
||||||
this.type3Icon.setVisible(types.length > 2);
|
|
||||||
if (types.length > 1) {
|
|
||||||
this.type2Icon.setFrame(PokemonType[types[1]].toLowerCase());
|
|
||||||
}
|
|
||||||
if (types.length > 2) {
|
|
||||||
this.type3Icon.setFrame(PokemonType[types[2]].toLowerCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateHpFrame = () => {
|
|
||||||
const hpFrame = this.hpBar.scaleX > 0.5 ? "high" : this.hpBar.scaleX > 0.25 ? "medium" : "low";
|
|
||||||
if (hpFrame !== this.lastHpFrame) {
|
|
||||||
this.hpBar.setFrame(hpFrame);
|
|
||||||
this.lastHpFrame = hpFrame;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const updatePokemonHp = () => {
|
|
||||||
let duration = !instant ? Phaser.Math.Clamp(Math.abs(this.lastHp - pokemon.hp) * 5, 250, 5000) : 0;
|
|
||||||
const speed = globalScene.hpBarSpeed;
|
|
||||||
if (speed) {
|
|
||||||
duration = speed >= 3 ? 0 : duration / Math.pow(2, speed);
|
|
||||||
}
|
|
||||||
globalScene.tweens.add({
|
|
||||||
targets: this.hpBar,
|
|
||||||
ease: "Sine.easeOut",
|
|
||||||
scaleX: pokemon.getHpRatio(true),
|
|
||||||
duration: duration,
|
|
||||||
onUpdate: () => {
|
|
||||||
if (this.player && this.lastHp !== pokemon.hp) {
|
|
||||||
const tweenHp = Math.ceil(this.hpBar.scaleX * pokemon.getMaxHp());
|
|
||||||
this.setHpNumbers(tweenHp, pokemon.getMaxHp());
|
|
||||||
this.lastHp = tweenHp;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateHpFrame();
|
|
||||||
},
|
|
||||||
onComplete: () => {
|
|
||||||
updateHpFrame();
|
|
||||||
// If, after tweening, the hp is different from the original (due to rounding), force the hp number display
|
|
||||||
// to update to the correct value.
|
|
||||||
if (this.player && this.lastHp !== pokemon.hp) {
|
|
||||||
this.setHpNumbers(pokemon.hp, pokemon.getMaxHp());
|
|
||||||
this.lastHp = pokemon.hp;
|
|
||||||
}
|
|
||||||
resolve();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (!this.player) {
|
|
||||||
this.lastHp = pokemon.hp;
|
|
||||||
}
|
|
||||||
this.lastMaxHp = pokemon.getMaxHp();
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.player) {
|
|
||||||
const isLevelCapped = pokemon.level >= globalScene.getMaxExpLevel();
|
|
||||||
|
|
||||||
if (this.lastExp !== pokemon.exp || this.lastLevel !== pokemon.level) {
|
|
||||||
const originalResolve = resolve;
|
|
||||||
const durationMultipler = Math.max(
|
|
||||||
Phaser.Tweens.Builders.GetEaseFunction("Cubic.easeIn")(
|
|
||||||
1 - Math.min(pokemon.level - this.lastLevel, 10) / 10,
|
|
||||||
),
|
|
||||||
0.1,
|
|
||||||
);
|
|
||||||
resolve = () => this.updatePokemonExp(pokemon, false, durationMultipler).then(() => originalResolve());
|
|
||||||
} else if (isLevelCapped !== this.lastLevelCapped) {
|
|
||||||
this.setLevel(pokemon.level);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.lastLevelCapped = isLevelCapped;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.lastHp !== pokemon.hp || this.lastMaxHp !== pokemon.getMaxHp()) {
|
|
||||||
return updatePokemonHp();
|
|
||||||
}
|
|
||||||
if (!this.player && this.lastLevel !== pokemon.level) {
|
|
||||||
this.setLevel(pokemon.level);
|
|
||||||
this.lastLevel = pokemon.level;
|
|
||||||
}
|
|
||||||
|
|
||||||
const stats = pokemon.getStatStages();
|
|
||||||
const statsStr = stats.join("");
|
|
||||||
|
|
||||||
if (this.lastStats !== statsStr) {
|
|
||||||
this.updateStats(stats);
|
|
||||||
this.lastStats = statsStr;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.shinyIcon.setVisible(pokemon.isShiny(true));
|
|
||||||
|
|
||||||
const doubleShiny = isFusion && pokemon.shiny && pokemon.fusionShiny;
|
|
||||||
const baseVariant = !doubleShiny ? pokemon.getVariant(true) : pokemon.variant;
|
|
||||||
this.shinyIcon.setTint(getVariantTint(baseVariant));
|
|
||||||
|
|
||||||
this.fusionShinyIcon.setVisible(doubleShiny);
|
|
||||||
if (isFusion) {
|
|
||||||
this.fusionShinyIcon.setTint(getVariantTint(pokemon.fusionVariant));
|
|
||||||
}
|
|
||||||
this.fusionShinyIcon.setPosition(this.shinyIcon.x, this.shinyIcon.y);
|
|
||||||
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
updateNameText(pokemon: Pokemon): void {
|
|
||||||
let displayName = pokemon.getNameToRender().replace(/[♂♀]/g, "");
|
|
||||||
let nameTextWidth: number;
|
|
||||||
|
|
||||||
const nameSizeTest = addTextObject(0, 0, displayName, TextStyle.BATTLE_INFO);
|
|
||||||
nameTextWidth = nameSizeTest.displayWidth;
|
|
||||||
|
|
||||||
const gender = pokemon.summonData.illusion?.gender ?? pokemon.gender;
|
|
||||||
while (
|
|
||||||
nameTextWidth >
|
|
||||||
(this.player || !this.boss ? 60 : 98) -
|
|
||||||
((gender !== Gender.GENDERLESS ? 6 : 0) +
|
|
||||||
(pokemon.fusionSpecies ? 8 : 0) +
|
|
||||||
(pokemon.isShiny() ? 8 : 0) +
|
|
||||||
(Math.min(pokemon.level.toString().length, 3) - 3) * 8)
|
|
||||||
) {
|
|
||||||
displayName = `${displayName.slice(0, displayName.endsWith(".") ? -2 : -1).trimEnd()}.`;
|
|
||||||
nameSizeTest.setText(displayName);
|
|
||||||
nameTextWidth = nameSizeTest.displayWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
nameSizeTest.destroy();
|
|
||||||
|
|
||||||
this.nameText.setText(displayName);
|
|
||||||
this.lastName = pokemon.getNameToRender();
|
|
||||||
|
|
||||||
if (this.nameText.visible) {
|
|
||||||
this.nameText.setInteractive(
|
|
||||||
new Phaser.Geom.Rectangle(0, 0, this.nameText.width, this.nameText.height),
|
|
||||||
Phaser.Geom.Rectangle.Contains,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updatePokemonExp(pokemon: Pokemon, instant?: boolean, levelDurationMultiplier = 1): Promise<void> {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
const levelUp = this.lastLevel < pokemon.level;
|
|
||||||
const relLevelExp = getLevelRelExp(this.lastLevel + 1, pokemon.species.growthRate);
|
|
||||||
const levelExp = levelUp ? relLevelExp : pokemon.levelExp;
|
|
||||||
let ratio = relLevelExp ? levelExp / relLevelExp : 0;
|
|
||||||
if (this.lastLevel >= globalScene.getMaxExpLevel(true)) {
|
|
||||||
if (levelUp) {
|
|
||||||
ratio = 1;
|
|
||||||
} else {
|
|
||||||
ratio = 0;
|
|
||||||
}
|
|
||||||
instant = true;
|
|
||||||
}
|
|
||||||
const durationMultiplier = Phaser.Tweens.Builders.GetEaseFunction("Sine.easeIn")(
|
|
||||||
1 - Math.max(this.lastLevel - 100, 0) / 150,
|
|
||||||
);
|
|
||||||
let duration =
|
|
||||||
this.visible && !instant
|
|
||||||
? ((levelExp - this.lastLevelExp) / relLevelExp) *
|
|
||||||
BattleInfo.EXP_GAINS_DURATION_BASE *
|
|
||||||
durationMultiplier *
|
|
||||||
levelDurationMultiplier
|
|
||||||
: 0;
|
|
||||||
const speed = globalScene.expGainsSpeed;
|
|
||||||
if (speed && speed >= ExpGainsSpeed.DEFAULT) {
|
|
||||||
duration = speed >= ExpGainsSpeed.SKIP ? ExpGainsSpeed.DEFAULT : duration / Math.pow(2, speed);
|
|
||||||
}
|
|
||||||
if (ratio === 1) {
|
|
||||||
this.lastLevelExp = 0;
|
|
||||||
this.lastLevel++;
|
|
||||||
} else {
|
|
||||||
this.lastExp = pokemon.exp;
|
|
||||||
this.lastLevelExp = pokemon.levelExp;
|
|
||||||
}
|
|
||||||
if (duration) {
|
|
||||||
globalScene.playSound("se/exp");
|
|
||||||
}
|
|
||||||
globalScene.tweens.add({
|
|
||||||
targets: this.expMaskRect,
|
|
||||||
ease: "Sine.easeIn",
|
|
||||||
x: ratio * 510,
|
|
||||||
duration: duration,
|
|
||||||
onComplete: () => {
|
|
||||||
if (!globalScene) {
|
|
||||||
return resolve();
|
|
||||||
}
|
|
||||||
if (duration) {
|
|
||||||
globalScene.sound.stopByKey("se/exp");
|
|
||||||
}
|
|
||||||
if (ratio === 1) {
|
|
||||||
globalScene.playSound("se/level_up");
|
|
||||||
this.setLevel(this.lastLevel);
|
|
||||||
globalScene.time.delayedCall(500 * levelDurationMultiplier, () => {
|
|
||||||
this.expMaskRect.x = 0;
|
|
||||||
this.updateInfo(pokemon, instant).then(() => resolve());
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
resolve();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setLevel(level: number): void {
|
|
||||||
const isCapped = level >= globalScene.getMaxExpLevel();
|
|
||||||
this.levelNumbersContainer.removeAll(true);
|
|
||||||
const levelStr = level.toString();
|
|
||||||
for (let i = 0; i < levelStr.length; i++) {
|
|
||||||
this.levelNumbersContainer.add(
|
|
||||||
globalScene.add.image(i * 8, 0, `numbers${isCapped && this.player ? "_red" : ""}`, levelStr[i]),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
this.levelContainer.setX((this.player ? -41 : -50) - 8 * Math.max(levelStr.length - 3, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
setHpNumbers(hp: number, maxHp: number): void {
|
|
||||||
if (!this.player || !globalScene) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.hpNumbersContainer.removeAll(true);
|
|
||||||
const hpStr = hp.toString();
|
|
||||||
const maxHpStr = maxHp.toString();
|
|
||||||
let offset = 0;
|
|
||||||
for (let i = maxHpStr.length - 1; i >= 0; i--) {
|
|
||||||
this.hpNumbersContainer.add(globalScene.add.image(offset++ * -8, 0, "numbers", maxHpStr[i]));
|
|
||||||
}
|
|
||||||
this.hpNumbersContainer.add(globalScene.add.image(offset++ * -8, 0, "numbers", "/"));
|
|
||||||
for (let i = hpStr.length - 1; i >= 0; i--) {
|
|
||||||
this.hpNumbersContainer.add(globalScene.add.image(offset++ * -8, 0, "numbers", hpStr[i]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateStats(stats: number[]): void {
|
|
||||||
this.statOrder.map((s, i) => {
|
|
||||||
if (s !== Stat.HP) {
|
|
||||||
this.statNumbers[i].setFrame(stats[s - 1].toString());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request the flyoutMenu to toggle if available and hides or shows the effectiveness window where necessary
|
|
||||||
*/
|
|
||||||
toggleFlyout(visible: boolean): void {
|
|
||||||
this.flyoutMenu?.toggleFlyout(visible);
|
|
||||||
|
|
||||||
if (visible) {
|
|
||||||
this.effectivenessContainer?.setVisible(false);
|
|
||||||
} else {
|
|
||||||
this.updateEffectiveness(this.currentEffectiveness);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show or hide the type effectiveness multiplier window
|
|
||||||
* Passing undefined will hide the window
|
|
||||||
*/
|
|
||||||
updateEffectiveness(effectiveness?: string) {
|
|
||||||
if (this.player) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.currentEffectiveness = effectiveness;
|
|
||||||
|
|
||||||
if (!globalScene.typeHints || effectiveness === undefined || this.flyoutMenu?.flyoutVisible) {
|
|
||||||
this.effectivenessContainer.setVisible(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.effectivenessText.setText(effectiveness);
|
|
||||||
this.effectivenessWindow.width = 10 + this.effectivenessText.displayWidth;
|
|
||||||
this.effectivenessContainer.setVisible(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
getBaseY(): number {
|
|
||||||
return this.baseY;
|
|
||||||
}
|
|
||||||
|
|
||||||
resetY(): void {
|
|
||||||
this.y = this.baseY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PlayerBattleInfo extends BattleInfo {
|
|
||||||
constructor() {
|
|
||||||
super(Math.floor(globalScene.game.canvas.width / 6) - 10, -72, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class EnemyBattleInfo extends BattleInfo {
|
|
||||||
constructor() {
|
|
||||||
super(140, -141, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
setMini(_mini: boolean): void {} // Always mini
|
|
||||||
}
|
|
688
src/ui/battle-info/battle-info.ts
Normal file
@ -0,0 +1,688 @@
|
|||||||
|
import type { default as Pokemon } from "../../field/pokemon";
|
||||||
|
import { getLocalizedSpriteKey, fixedInt, getShinyDescriptor } from "#app/utils/common";
|
||||||
|
import { addTextObject, TextStyle } from "../text";
|
||||||
|
import { getGenderSymbol, getGenderColor, Gender } from "../../data/gender";
|
||||||
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
|
import { globalScene } from "#app/global-scene";
|
||||||
|
import { getTypeRgb } from "#app/data/type";
|
||||||
|
import { PokemonType } from "#enums/pokemon-type";
|
||||||
|
import { getVariantTint } from "#app/sprites/variant";
|
||||||
|
import { Stat } from "#enums/stat";
|
||||||
|
import i18next from "i18next";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameters influencing the position of elements within the battle info container
|
||||||
|
*/
|
||||||
|
export type BattleInfoParamList = {
|
||||||
|
/** X offset for the name text*/
|
||||||
|
nameTextX: number;
|
||||||
|
/** Y offset for the name text */
|
||||||
|
nameTextY: number;
|
||||||
|
/** X offset for the level container */
|
||||||
|
levelContainerX: number;
|
||||||
|
/** Y offset for the level container */
|
||||||
|
levelContainerY: number;
|
||||||
|
/** X offset for the hp bar */
|
||||||
|
hpBarX: number;
|
||||||
|
/** Y offset for the hp bar */
|
||||||
|
hpBarY: number;
|
||||||
|
/** Parameters for the stat box container */
|
||||||
|
statBox: {
|
||||||
|
/** The starting offset from the left of the label for the entries in the stat box */
|
||||||
|
xOffset: number;
|
||||||
|
/** The X padding between each number column */
|
||||||
|
paddingX: number;
|
||||||
|
/** The index of the stat entries at which paddingX is used instead of startingX */
|
||||||
|
statOverflow: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default abstract class BattleInfo extends Phaser.GameObjects.Container {
|
||||||
|
public static readonly EXP_GAINS_DURATION_BASE = 1650;
|
||||||
|
|
||||||
|
protected baseY: number;
|
||||||
|
protected baseLvContainerX: number;
|
||||||
|
|
||||||
|
protected player: boolean;
|
||||||
|
protected mini: boolean;
|
||||||
|
protected boss: boolean;
|
||||||
|
protected bossSegments: number;
|
||||||
|
protected offset: boolean;
|
||||||
|
protected lastName: string | null;
|
||||||
|
protected lastTeraType: PokemonType;
|
||||||
|
protected lastStatus: StatusEffect;
|
||||||
|
protected lastHp: number;
|
||||||
|
protected lastMaxHp: number;
|
||||||
|
protected lastHpFrame: string | null;
|
||||||
|
protected lastExp: number;
|
||||||
|
protected lastLevelExp: number;
|
||||||
|
protected lastLevel: number;
|
||||||
|
protected lastLevelCapped: boolean;
|
||||||
|
protected lastStats: string;
|
||||||
|
|
||||||
|
protected box: Phaser.GameObjects.Sprite;
|
||||||
|
protected nameText: Phaser.GameObjects.Text;
|
||||||
|
protected genderText: Phaser.GameObjects.Text;
|
||||||
|
protected teraIcon: Phaser.GameObjects.Sprite;
|
||||||
|
protected shinyIcon: Phaser.GameObjects.Sprite;
|
||||||
|
protected fusionShinyIcon: Phaser.GameObjects.Sprite;
|
||||||
|
protected splicedIcon: Phaser.GameObjects.Sprite;
|
||||||
|
protected statusIndicator: Phaser.GameObjects.Sprite;
|
||||||
|
protected levelContainer: Phaser.GameObjects.Container;
|
||||||
|
protected hpBar: Phaser.GameObjects.Image;
|
||||||
|
protected levelNumbersContainer: Phaser.GameObjects.Container;
|
||||||
|
protected type1Icon: Phaser.GameObjects.Sprite;
|
||||||
|
protected type2Icon: Phaser.GameObjects.Sprite;
|
||||||
|
protected type3Icon: Phaser.GameObjects.Sprite;
|
||||||
|
protected expBar: Phaser.GameObjects.Image;
|
||||||
|
|
||||||
|
public expMaskRect: Phaser.GameObjects.Graphics;
|
||||||
|
|
||||||
|
protected statsContainer: Phaser.GameObjects.Container;
|
||||||
|
protected statsBox: Phaser.GameObjects.Sprite;
|
||||||
|
protected statValuesContainer: Phaser.GameObjects.Container;
|
||||||
|
protected statNumbers: Phaser.GameObjects.Sprite[];
|
||||||
|
|
||||||
|
get statOrder(): Stat[] {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Helper method used by the constructor to create the tera and shiny icons next to the name */
|
||||||
|
private constructIcons() {
|
||||||
|
const hitArea = new Phaser.Geom.Rectangle(0, 0, 12, 15);
|
||||||
|
const hitCallback = Phaser.Geom.Rectangle.Contains;
|
||||||
|
|
||||||
|
this.teraIcon = globalScene.add
|
||||||
|
.sprite(0, 0, "icon_tera")
|
||||||
|
.setName("icon_tera")
|
||||||
|
.setVisible(false)
|
||||||
|
.setOrigin(0)
|
||||||
|
.setScale(0.5)
|
||||||
|
.setInteractive(hitArea, hitCallback)
|
||||||
|
.setPositionRelative(this.nameText, 0, 2);
|
||||||
|
|
||||||
|
this.shinyIcon = globalScene.add
|
||||||
|
.sprite(0, 0, "shiny_star")
|
||||||
|
.setName("icon_shiny")
|
||||||
|
.setVisible(false)
|
||||||
|
.setOrigin(0)
|
||||||
|
.setScale(0.5)
|
||||||
|
.setInteractive(hitArea, hitCallback)
|
||||||
|
.setPositionRelative(this.nameText, 0, 2);
|
||||||
|
|
||||||
|
this.fusionShinyIcon = globalScene.add
|
||||||
|
.sprite(0, 0, "shiny_star_2")
|
||||||
|
.setName("icon_fusion_shiny")
|
||||||
|
.setVisible(false)
|
||||||
|
.setOrigin(0)
|
||||||
|
.setScale(0.5)
|
||||||
|
.copyPosition(this.shinyIcon);
|
||||||
|
|
||||||
|
this.splicedIcon = globalScene.add
|
||||||
|
.sprite(0, 0, "icon_spliced")
|
||||||
|
.setName("icon_spliced")
|
||||||
|
.setVisible(false)
|
||||||
|
.setOrigin(0)
|
||||||
|
.setScale(0.5)
|
||||||
|
.setInteractive(hitArea, hitCallback)
|
||||||
|
.setPositionRelative(this.nameText, 0, 2);
|
||||||
|
|
||||||
|
this.add([this.teraIcon, this.shinyIcon, this.fusionShinyIcon, this.splicedIcon]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submethod of the constructor that creates and adds the stats container to the battle info
|
||||||
|
*/
|
||||||
|
protected constructStatContainer({ xOffset, paddingX, statOverflow }: BattleInfoParamList["statBox"]): void {
|
||||||
|
this.statsContainer = globalScene.add.container(0, 0).setName("container_stats").setAlpha(0);
|
||||||
|
this.add(this.statsContainer);
|
||||||
|
|
||||||
|
this.statsBox = globalScene.add
|
||||||
|
.sprite(0, 0, `${this.getTextureName()}_stats`)
|
||||||
|
.setName("box_stats")
|
||||||
|
.setOrigin(1, 0.5);
|
||||||
|
this.statsContainer.add(this.statsBox);
|
||||||
|
|
||||||
|
const statLabels: Phaser.GameObjects.Sprite[] = [];
|
||||||
|
this.statNumbers = [];
|
||||||
|
|
||||||
|
this.statValuesContainer = globalScene.add.container();
|
||||||
|
this.statsContainer.add(this.statValuesContainer);
|
||||||
|
|
||||||
|
const startingX = -this.statsBox.width + xOffset;
|
||||||
|
|
||||||
|
// this gives us a different starting location from the left of the label and padding between stats for a player vs enemy
|
||||||
|
// since the player won't have HP to show, it doesn't need to change from the current version
|
||||||
|
|
||||||
|
for (const [i, s] of this.statOrder.entries()) {
|
||||||
|
const isHp = s === Stat.HP;
|
||||||
|
// we do a check for i > statOverflow to see when the stat labels go onto the next column
|
||||||
|
// For enemies, we have HP (i=0) by itself then a new column, so we check for i > 0
|
||||||
|
// For players, we don't have HP, so we start with i = 0 and i = 1 for our first column, and so need to check for i > 1
|
||||||
|
const statX =
|
||||||
|
i > statOverflow
|
||||||
|
? this.statNumbers[Math.max(i - 2, 0)].x + this.statNumbers[Math.max(i - 2, 0)].width + paddingX
|
||||||
|
: startingX; // we have the Math.max(i - 2, 0) in there so for i===1 to not return a negative number; since this is now based on anything >0 instead of >1, we need to allow for i-2 < 0
|
||||||
|
|
||||||
|
let statY = -this.statsBox.height / 2 + 4; // this is the baseline for the y-axis
|
||||||
|
if (isHp || s === Stat.SPD) {
|
||||||
|
statY += 5;
|
||||||
|
} else if (this.player === !!(i % 2)) {
|
||||||
|
// we compare i % 2 against this.player to tell us where to place the label
|
||||||
|
// because this.battleStatOrder for enemies has HP, this.battleStatOrder[1]=ATK, but for players
|
||||||
|
// this.battleStatOrder[0]=ATK, so this comparing i % 2 to this.player fixes this issue for us
|
||||||
|
statY += 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
const statLabel = globalScene.add
|
||||||
|
.sprite(statX, statY, "pbinfo_stat", Stat[s])
|
||||||
|
.setName("icon_stat_label_" + i.toString())
|
||||||
|
.setOrigin(0);
|
||||||
|
statLabels.push(statLabel);
|
||||||
|
this.statValuesContainer.add(statLabel);
|
||||||
|
|
||||||
|
const statNumber = globalScene.add
|
||||||
|
.sprite(statX + statLabel.width, statY, "pbinfo_stat_numbers", !isHp ? "3" : "empty")
|
||||||
|
.setName("icon_stat_number_" + i.toString())
|
||||||
|
.setOrigin(0);
|
||||||
|
this.statNumbers.push(statNumber);
|
||||||
|
this.statValuesContainer.add(statNumber);
|
||||||
|
|
||||||
|
if (isHp) {
|
||||||
|
statLabel.setVisible(false);
|
||||||
|
statNumber.setVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submethod of the constructor that creates and adds the pokemon type icons to the battle info
|
||||||
|
*/
|
||||||
|
protected abstract constructTypeIcons(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param x - The x position of the battle info container
|
||||||
|
* @param y - The y position of the battle info container
|
||||||
|
* @param player - Whether this battle info belongs to a player or an enemy
|
||||||
|
* @param posParams - The parameters influencing the position of elements within the battle info container
|
||||||
|
*/
|
||||||
|
constructor(x: number, y: number, player: boolean, posParams: BattleInfoParamList) {
|
||||||
|
super(globalScene, x, y);
|
||||||
|
this.baseY = y;
|
||||||
|
this.player = player;
|
||||||
|
this.mini = !player;
|
||||||
|
this.boss = false;
|
||||||
|
this.offset = false;
|
||||||
|
this.lastName = null;
|
||||||
|
this.lastTeraType = PokemonType.UNKNOWN;
|
||||||
|
this.lastStatus = StatusEffect.NONE;
|
||||||
|
this.lastHp = -1;
|
||||||
|
this.lastMaxHp = -1;
|
||||||
|
this.lastHpFrame = null;
|
||||||
|
this.lastExp = -1;
|
||||||
|
this.lastLevelExp = -1;
|
||||||
|
this.lastLevel = -1;
|
||||||
|
this.baseLvContainerX = posParams.levelContainerX;
|
||||||
|
|
||||||
|
// Initially invisible and shown via Pokemon.showInfo
|
||||||
|
this.setVisible(false);
|
||||||
|
|
||||||
|
this.box = globalScene.add.sprite(0, 0, this.getTextureName()).setName("box").setOrigin(1, 0.5);
|
||||||
|
this.add(this.box);
|
||||||
|
|
||||||
|
this.nameText = addTextObject(posParams.nameTextX, posParams.nameTextY, "", TextStyle.BATTLE_INFO)
|
||||||
|
.setName("text_name")
|
||||||
|
.setOrigin(0);
|
||||||
|
this.add(this.nameText);
|
||||||
|
|
||||||
|
this.genderText = addTextObject(0, 0, "", TextStyle.BATTLE_INFO)
|
||||||
|
.setName("text_gender")
|
||||||
|
.setOrigin(0)
|
||||||
|
.setPositionRelative(this.nameText, 0, 2);
|
||||||
|
this.add(this.genderText);
|
||||||
|
|
||||||
|
this.constructIcons();
|
||||||
|
|
||||||
|
this.statusIndicator = globalScene.add
|
||||||
|
.sprite(0, 0, getLocalizedSpriteKey("statuses"))
|
||||||
|
.setName("icon_status")
|
||||||
|
.setVisible(false)
|
||||||
|
.setOrigin(0)
|
||||||
|
.setPositionRelative(this.nameText, 0, 11.5);
|
||||||
|
this.add(this.statusIndicator);
|
||||||
|
|
||||||
|
this.levelContainer = globalScene.add
|
||||||
|
.container(posParams.levelContainerX, posParams.levelContainerY)
|
||||||
|
.setName("container_level");
|
||||||
|
this.add(this.levelContainer);
|
||||||
|
|
||||||
|
const levelOverlay = globalScene.add.image(0, 0, "overlay_lv");
|
||||||
|
this.levelContainer.add(levelOverlay);
|
||||||
|
|
||||||
|
this.hpBar = globalScene.add.image(posParams.hpBarX, posParams.hpBarY, "overlay_hp").setName("hp_bar").setOrigin(0);
|
||||||
|
this.add(this.hpBar);
|
||||||
|
|
||||||
|
this.levelNumbersContainer = globalScene.add
|
||||||
|
.container(9.5, globalScene.uiTheme ? 0 : -0.5)
|
||||||
|
.setName("container_level");
|
||||||
|
this.levelContainer.add(this.levelNumbersContainer);
|
||||||
|
|
||||||
|
this.constructStatContainer(posParams.statBox);
|
||||||
|
|
||||||
|
this.constructTypeIcons();
|
||||||
|
}
|
||||||
|
|
||||||
|
getStatsValueContainer(): Phaser.GameObjects.Container {
|
||||||
|
return this.statValuesContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
//#region Initialization methods
|
||||||
|
|
||||||
|
initSplicedIcon(pokemon: Pokemon, baseWidth: number) {
|
||||||
|
this.splicedIcon.setPositionRelative(
|
||||||
|
this.nameText,
|
||||||
|
baseWidth + this.genderText.displayWidth + 1 + (this.teraIcon.visible ? this.teraIcon.displayWidth + 1 : 0),
|
||||||
|
2.5,
|
||||||
|
);
|
||||||
|
this.splicedIcon.setVisible(pokemon.isFusion(true));
|
||||||
|
if (!this.splicedIcon.visible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.splicedIcon
|
||||||
|
.on("pointerover", () =>
|
||||||
|
globalScene.ui.showTooltip(
|
||||||
|
"",
|
||||||
|
`${pokemon.species.getName(pokemon.formIndex)}/${pokemon.fusionSpecies?.getName(pokemon.fusionFormIndex)}`,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.on("pointerout", () => globalScene.ui.hideTooltip());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by {@linkcode initInfo} to initialize the shiny icon
|
||||||
|
* @param pokemon - The pokemon object attached to this battle info
|
||||||
|
* @param baseXOffset - The x offset to use for the shiny icon
|
||||||
|
* @param doubleShiny - Whether the pokemon is shiny and its fusion species is also shiny
|
||||||
|
*/
|
||||||
|
protected initShinyIcon(pokemon: Pokemon, xOffset: number, doubleShiny: boolean) {
|
||||||
|
const baseVariant = !doubleShiny ? pokemon.getVariant(true) : pokemon.variant;
|
||||||
|
|
||||||
|
this.shinyIcon.setPositionRelative(
|
||||||
|
this.nameText,
|
||||||
|
xOffset +
|
||||||
|
this.genderText.displayWidth +
|
||||||
|
1 +
|
||||||
|
(this.teraIcon.visible ? this.teraIcon.displayWidth + 1 : 0) +
|
||||||
|
(this.splicedIcon.visible ? this.splicedIcon.displayWidth + 1 : 0),
|
||||||
|
2.5,
|
||||||
|
);
|
||||||
|
this.shinyIcon
|
||||||
|
.setTexture(`shiny_star${doubleShiny ? "_1" : ""}`)
|
||||||
|
.setVisible(pokemon.isShiny())
|
||||||
|
.setTint(getVariantTint(baseVariant));
|
||||||
|
|
||||||
|
if (!this.shinyIcon.visible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let shinyDescriptor = "";
|
||||||
|
if (doubleShiny || baseVariant) {
|
||||||
|
shinyDescriptor = " (" + getShinyDescriptor(baseVariant);
|
||||||
|
if (doubleShiny) {
|
||||||
|
shinyDescriptor += "/" + getShinyDescriptor(pokemon.fusionVariant);
|
||||||
|
}
|
||||||
|
shinyDescriptor += ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
this.shinyIcon
|
||||||
|
.on("pointerover", () => globalScene.ui.showTooltip("", i18next.t("common:shinyOnHover") + shinyDescriptor))
|
||||||
|
.on("pointerout", () => globalScene.ui.hideTooltip());
|
||||||
|
}
|
||||||
|
|
||||||
|
initInfo(pokemon: Pokemon) {
|
||||||
|
this.updateNameText(pokemon);
|
||||||
|
const nameTextWidth = this.nameText.displayWidth;
|
||||||
|
|
||||||
|
this.name = pokemon.getNameToRender();
|
||||||
|
this.box.name = pokemon.getNameToRender();
|
||||||
|
|
||||||
|
this.genderText
|
||||||
|
.setText(getGenderSymbol(pokemon.gender))
|
||||||
|
.setColor(getGenderColor(pokemon.gender))
|
||||||
|
.setPositionRelative(this.nameText, nameTextWidth, 0);
|
||||||
|
|
||||||
|
this.lastTeraType = pokemon.getTeraType();
|
||||||
|
|
||||||
|
this.teraIcon
|
||||||
|
.setVisible(pokemon.isTerastallized)
|
||||||
|
.on("pointerover", () => {
|
||||||
|
if (pokemon.isTerastallized) {
|
||||||
|
globalScene.ui.showTooltip(
|
||||||
|
"",
|
||||||
|
i18next.t("fightUiHandler:teraHover", {
|
||||||
|
type: i18next.t(`pokemonInfo:Type.${PokemonType[this.lastTeraType]}`),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on("pointerout", () => globalScene.ui.hideTooltip())
|
||||||
|
.setPositionRelative(this.nameText, nameTextWidth + this.genderText.displayWidth + 1, 2);
|
||||||
|
|
||||||
|
const isFusion = pokemon.isFusion(true);
|
||||||
|
this.initSplicedIcon(pokemon, nameTextWidth);
|
||||||
|
|
||||||
|
const doubleShiny = isFusion && pokemon.shiny && pokemon.fusionShiny;
|
||||||
|
this.initShinyIcon(pokemon, nameTextWidth, doubleShiny);
|
||||||
|
|
||||||
|
this.fusionShinyIcon.setVisible(doubleShiny).copyPosition(this.shinyIcon);
|
||||||
|
if (isFusion) {
|
||||||
|
this.fusionShinyIcon.setTint(getVariantTint(pokemon.fusionVariant));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hpBar.setScale(pokemon.getHpRatio(true), 1);
|
||||||
|
this.lastHpFrame = this.hpBar.scaleX > 0.5 ? "high" : this.hpBar.scaleX > 0.25 ? "medium" : "low";
|
||||||
|
this.hpBar.setFrame(this.lastHpFrame);
|
||||||
|
this.lastHp = pokemon.hp;
|
||||||
|
this.lastMaxHp = pokemon.getMaxHp();
|
||||||
|
|
||||||
|
this.setLevel(pokemon.level);
|
||||||
|
this.lastLevel = pokemon.level;
|
||||||
|
|
||||||
|
this.shinyIcon.setVisible(pokemon.isShiny());
|
||||||
|
|
||||||
|
this.setTypes(pokemon.getTypes(true, false, undefined, true));
|
||||||
|
|
||||||
|
const stats = this.statOrder.map(() => 0);
|
||||||
|
|
||||||
|
this.lastStats = stats.join("");
|
||||||
|
this.updateStats(stats);
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the texture name of the battle info box
|
||||||
|
*/
|
||||||
|
abstract getTextureName(): string;
|
||||||
|
|
||||||
|
setMini(_mini: boolean): void {}
|
||||||
|
|
||||||
|
toggleStats(visible: boolean): void {
|
||||||
|
globalScene.tweens.add({
|
||||||
|
targets: this.statsContainer,
|
||||||
|
duration: fixedInt(125),
|
||||||
|
ease: "Sine.easeInOut",
|
||||||
|
alpha: visible ? 1 : 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setOffset(offset: boolean): void {
|
||||||
|
if (this.offset === offset) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.offset = offset;
|
||||||
|
|
||||||
|
this.x += 10 * (this.offset === this.player ? 1 : -1);
|
||||||
|
this.y += 27 * (this.offset ? 1 : -1);
|
||||||
|
this.baseY = this.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
//#region Update methods and helpers
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the status icon to match the pokemon's current status
|
||||||
|
* @param pokemon - The pokemon object attached to this battle info
|
||||||
|
* @param xOffset - The offset from the name text
|
||||||
|
*/
|
||||||
|
updateStatusIcon(pokemon: Pokemon, xOffset = 0) {
|
||||||
|
if (this.lastStatus !== (pokemon.status?.effect || StatusEffect.NONE)) {
|
||||||
|
this.lastStatus = pokemon.status?.effect || StatusEffect.NONE;
|
||||||
|
|
||||||
|
if (this.lastStatus !== StatusEffect.NONE) {
|
||||||
|
this.statusIndicator.setFrame(StatusEffect[this.lastStatus].toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.statusIndicator.setVisible(!!this.lastStatus).setPositionRelative(this.nameText, xOffset, 11.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Update the pokemon name inside the container */
|
||||||
|
protected updateName(name: string): boolean {
|
||||||
|
if (this.lastName === name) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.nameText.setText(name).setPositionRelative(this.box, -this.nameText.displayWidth, 0);
|
||||||
|
this.lastName = name;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updateTeraType(ty: PokemonType): boolean {
|
||||||
|
if (this.lastTeraType === ty) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.teraIcon
|
||||||
|
.setVisible(ty !== PokemonType.UNKNOWN)
|
||||||
|
.setTintFill(Phaser.Display.Color.GetColor(...getTypeRgb(ty)))
|
||||||
|
.setPositionRelative(this.nameText, this.nameText.displayWidth + this.genderText.displayWidth + 1, 2);
|
||||||
|
this.lastTeraType = ty;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the type icons to match the pokemon's types
|
||||||
|
*/
|
||||||
|
setTypes(types: PokemonType[]): void {
|
||||||
|
this.type1Icon
|
||||||
|
.setTexture(`pbinfo_${this.player ? "player" : "enemy"}_type${types.length > 1 ? "1" : ""}`)
|
||||||
|
.setFrame(PokemonType[types[0]].toLowerCase());
|
||||||
|
this.type2Icon.setVisible(types.length > 1);
|
||||||
|
this.type3Icon.setVisible(types.length > 2);
|
||||||
|
if (types.length > 1) {
|
||||||
|
this.type2Icon.setFrame(PokemonType[types[1]].toLowerCase());
|
||||||
|
}
|
||||||
|
if (types.length > 2) {
|
||||||
|
this.type3Icon.setFrame(PokemonType[types[2]].toLowerCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by {@linkcode updateInfo} to update the position of the tera, spliced, and shiny icons
|
||||||
|
* @param isFusion - Whether the pokemon is a fusion or not
|
||||||
|
*/
|
||||||
|
protected updateIconDisplay(isFusion: boolean): void {
|
||||||
|
this.teraIcon.setPositionRelative(this.nameText, this.nameText.displayWidth + this.genderText.displayWidth + 1, 2);
|
||||||
|
this.splicedIcon
|
||||||
|
.setVisible(isFusion)
|
||||||
|
.setPositionRelative(
|
||||||
|
this.nameText,
|
||||||
|
this.nameText.displayWidth +
|
||||||
|
this.genderText.displayWidth +
|
||||||
|
1 +
|
||||||
|
(this.teraIcon.visible ? this.teraIcon.displayWidth + 1 : 0),
|
||||||
|
1.5,
|
||||||
|
);
|
||||||
|
this.shinyIcon.setPositionRelative(
|
||||||
|
this.nameText,
|
||||||
|
this.nameText.displayWidth +
|
||||||
|
this.genderText.displayWidth +
|
||||||
|
1 +
|
||||||
|
(this.teraIcon.visible ? this.teraIcon.displayWidth + 1 : 0) +
|
||||||
|
(this.splicedIcon.visible ? this.splicedIcon.displayWidth + 1 : 0),
|
||||||
|
2.5,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//#region Hp Bar Display handling
|
||||||
|
/**
|
||||||
|
* Called every time the hp frame is updated by the tween
|
||||||
|
* @param pokemon - The pokemon object attached to this battle info
|
||||||
|
*/
|
||||||
|
protected updateHpFrame(): void {
|
||||||
|
const hpFrame = this.hpBar.scaleX > 0.5 ? "high" : this.hpBar.scaleX > 0.25 ? "medium" : "low";
|
||||||
|
if (hpFrame !== this.lastHpFrame) {
|
||||||
|
this.hpBar.setFrame(hpFrame);
|
||||||
|
this.lastHpFrame = hpFrame;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by every frame in the hp animation tween created in {@linkcode updatePokemonHp}
|
||||||
|
* @param _pokemon - The pokemon the battle-info bar belongs to
|
||||||
|
*/
|
||||||
|
protected onHpTweenUpdate(_pokemon: Pokemon): void {
|
||||||
|
this.updateHpFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Update the pokemonHp bar */
|
||||||
|
protected updatePokemonHp(pokemon: Pokemon, resolve: (r: void | PromiseLike<void>) => void, instant?: boolean): void {
|
||||||
|
let duration = !instant ? Phaser.Math.Clamp(Math.abs(this.lastHp - pokemon.hp) * 5, 250, 5000) : 0;
|
||||||
|
const speed = globalScene.hpBarSpeed;
|
||||||
|
if (speed) {
|
||||||
|
duration = speed >= 3 ? 0 : duration / Math.pow(2, speed);
|
||||||
|
}
|
||||||
|
globalScene.tweens.add({
|
||||||
|
targets: this.hpBar,
|
||||||
|
ease: "Sine.easeOut",
|
||||||
|
scaleX: pokemon.getHpRatio(true),
|
||||||
|
duration: duration,
|
||||||
|
onUpdate: () => {
|
||||||
|
this.onHpTweenUpdate(pokemon);
|
||||||
|
},
|
||||||
|
onComplete: () => {
|
||||||
|
this.updateHpFrame();
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.lastMaxHp = pokemon.getMaxHp();
|
||||||
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
async updateInfo(pokemon: Pokemon, instant?: boolean): Promise<void> {
|
||||||
|
let resolve: (r: void | PromiseLike<void>) => void = () => {};
|
||||||
|
const promise = new Promise<void>(r => (resolve = r));
|
||||||
|
if (!globalScene) {
|
||||||
|
return resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
const gender: Gender = pokemon.summonData?.illusion?.gender ?? pokemon.gender;
|
||||||
|
|
||||||
|
this.genderText.setText(getGenderSymbol(gender)).setColor(getGenderColor(gender));
|
||||||
|
|
||||||
|
const nameUpdated = this.updateName(pokemon.getNameToRender());
|
||||||
|
|
||||||
|
const teraTypeUpdated = this.updateTeraType(pokemon.isTerastallized ? pokemon.getTeraType() : PokemonType.UNKNOWN);
|
||||||
|
|
||||||
|
const isFusion = pokemon.isFusion(true);
|
||||||
|
|
||||||
|
if (nameUpdated || teraTypeUpdated) {
|
||||||
|
this.updateIconDisplay(isFusion);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateStatusIcon(pokemon);
|
||||||
|
|
||||||
|
if (this.lastHp !== pokemon.hp || this.lastMaxHp !== pokemon.getMaxHp()) {
|
||||||
|
return this.updatePokemonHp(pokemon, resolve, instant);
|
||||||
|
}
|
||||||
|
if (!this.player && this.lastLevel !== pokemon.level) {
|
||||||
|
this.setLevel(pokemon.level);
|
||||||
|
this.lastLevel = pokemon.level;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stats = pokemon.getStatStages();
|
||||||
|
const statsStr = stats.join("");
|
||||||
|
|
||||||
|
if (this.lastStats !== statsStr) {
|
||||||
|
this.updateStats(stats);
|
||||||
|
this.lastStats = statsStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.shinyIcon.setVisible(pokemon.isShiny(true));
|
||||||
|
|
||||||
|
const doubleShiny = isFusion && pokemon.shiny && pokemon.fusionShiny;
|
||||||
|
const baseVariant = !doubleShiny ? pokemon.getVariant(true) : pokemon.variant;
|
||||||
|
this.shinyIcon.setTint(getVariantTint(baseVariant));
|
||||||
|
|
||||||
|
this.fusionShinyIcon.setVisible(doubleShiny).setPosition(this.shinyIcon.x, this.shinyIcon.y);
|
||||||
|
if (isFusion) {
|
||||||
|
this.fusionShinyIcon.setTint(getVariantTint(pokemon.fusionVariant));
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
await promise;
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
updateNameText(pokemon: Pokemon): void {
|
||||||
|
let displayName = pokemon.getNameToRender().replace(/[♂♀]/g, "");
|
||||||
|
let nameTextWidth: number;
|
||||||
|
|
||||||
|
const nameSizeTest = addTextObject(0, 0, displayName, TextStyle.BATTLE_INFO);
|
||||||
|
nameTextWidth = nameSizeTest.displayWidth;
|
||||||
|
|
||||||
|
const gender = pokemon.summonData.illusion?.gender ?? pokemon.gender;
|
||||||
|
while (
|
||||||
|
nameTextWidth >
|
||||||
|
(this.player || !this.boss ? 60 : 98) -
|
||||||
|
((gender !== Gender.GENDERLESS ? 6 : 0) +
|
||||||
|
(pokemon.fusionSpecies ? 8 : 0) +
|
||||||
|
(pokemon.isShiny() ? 8 : 0) +
|
||||||
|
(Math.min(pokemon.level.toString().length, 3) - 3) * 8)
|
||||||
|
) {
|
||||||
|
displayName = `${displayName.slice(0, displayName.endsWith(".") ? -2 : -1).trimEnd()}.`;
|
||||||
|
nameSizeTest.setText(displayName);
|
||||||
|
nameTextWidth = nameSizeTest.displayWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
nameSizeTest.destroy();
|
||||||
|
|
||||||
|
this.nameText.setText(displayName);
|
||||||
|
this.lastName = pokemon.getNameToRender();
|
||||||
|
|
||||||
|
if (this.nameText.visible) {
|
||||||
|
this.nameText.setInteractive(
|
||||||
|
new Phaser.Geom.Rectangle(0, 0, this.nameText.width, this.nameText.height),
|
||||||
|
Phaser.Geom.Rectangle.Contains,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the level numbers container to display the provided level
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* The numbers in the pokemon's level uses images for each number rather than a text object with a special font.
|
||||||
|
* This method sets the images for each digit of the level number and then positions the level container based
|
||||||
|
* on the number of digits.
|
||||||
|
*
|
||||||
|
* @param level - The level to display
|
||||||
|
* @param textureKey - The texture key for the level numbers
|
||||||
|
*/
|
||||||
|
setLevel(level: number, textureKey: "numbers" | "numbers_red" = "numbers"): void {
|
||||||
|
this.levelNumbersContainer.removeAll(true);
|
||||||
|
const levelStr = level.toString();
|
||||||
|
for (let i = 0; i < levelStr.length; i++) {
|
||||||
|
this.levelNumbersContainer.add(globalScene.add.image(i * 8, 0, textureKey, levelStr[i]));
|
||||||
|
}
|
||||||
|
this.levelContainer.setX(this.baseLvContainerX - 8 * Math.max(levelStr.length - 3, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStats(stats: number[]): void {
|
||||||
|
for (const [i, s] of this.statOrder.entries()) {
|
||||||
|
if (s !== Stat.HP) {
|
||||||
|
this.statNumbers[i].setFrame(stats[s - 1].toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getBaseY(): number {
|
||||||
|
return this.baseY;
|
||||||
|
}
|
||||||
|
|
||||||
|
resetY(): void {
|
||||||
|
this.y = this.baseY;
|
||||||
|
}
|
||||||
|
}
|
235
src/ui/battle-info/enemy-battle-info.ts
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
import { globalScene } from "#app/global-scene";
|
||||||
|
import BattleFlyout from "../battle-flyout";
|
||||||
|
import { addTextObject, TextStyle } from "#app/ui/text";
|
||||||
|
import { addWindow, WindowVariant } from "#app/ui/ui-theme";
|
||||||
|
import { Stat } from "#enums/stat";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import type { EnemyPokemon } from "#app/field/pokemon";
|
||||||
|
import type { GameObjects } from "phaser";
|
||||||
|
import BattleInfo from "./battle-info";
|
||||||
|
import type { BattleInfoParamList } from "./battle-info";
|
||||||
|
|
||||||
|
export class EnemyBattleInfo extends BattleInfo {
|
||||||
|
protected player: false = false;
|
||||||
|
protected championRibbon: Phaser.GameObjects.Sprite;
|
||||||
|
protected ownedIcon: Phaser.GameObjects.Sprite;
|
||||||
|
protected flyoutMenu: BattleFlyout;
|
||||||
|
|
||||||
|
protected hpBarSegmentDividers: GameObjects.Rectangle[] = [];
|
||||||
|
|
||||||
|
// #region Type effectiveness hint objects
|
||||||
|
protected effectivenessContainer: Phaser.GameObjects.Container;
|
||||||
|
protected effectivenessWindow: Phaser.GameObjects.NineSlice;
|
||||||
|
protected effectivenessText: Phaser.GameObjects.Text;
|
||||||
|
protected currentEffectiveness?: string;
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
override get statOrder(): Stat[] {
|
||||||
|
return [Stat.HP, Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.ACC, Stat.EVA, Stat.SPD];
|
||||||
|
}
|
||||||
|
|
||||||
|
override getTextureName(): string {
|
||||||
|
return this.boss ? "pbinfo_enemy_boss" : "pbinfo_enemy_mini";
|
||||||
|
}
|
||||||
|
|
||||||
|
override constructTypeIcons(): void {
|
||||||
|
this.type1Icon = globalScene.add.sprite(-15, -15.5, "pbinfo_enemy_type1").setName("icon_type_1").setOrigin(0);
|
||||||
|
this.type2Icon = globalScene.add.sprite(-15, -2.5, "pbinfo_enemy_type2").setName("icon_type_2").setOrigin(0);
|
||||||
|
this.type3Icon = globalScene.add.sprite(0, 15.5, "pbinfo_enemy_type3").setName("icon_type_3").setOrigin(0);
|
||||||
|
this.add([this.type1Icon, this.type2Icon, this.type3Icon]);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
const posParams: BattleInfoParamList = {
|
||||||
|
nameTextX: -124,
|
||||||
|
nameTextY: -11.2,
|
||||||
|
levelContainerX: -50,
|
||||||
|
levelContainerY: -5,
|
||||||
|
hpBarX: -71,
|
||||||
|
hpBarY: 4.5,
|
||||||
|
statBox: {
|
||||||
|
xOffset: 5,
|
||||||
|
paddingX: 2,
|
||||||
|
statOverflow: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
super(140, -141, false, posParams);
|
||||||
|
|
||||||
|
this.ownedIcon = globalScene.add
|
||||||
|
.sprite(0, 0, "icon_owned")
|
||||||
|
.setName("icon_owned")
|
||||||
|
.setVisible(false)
|
||||||
|
.setOrigin(0, 0)
|
||||||
|
.setPositionRelative(this.nameText, 0, 11.75);
|
||||||
|
|
||||||
|
this.championRibbon = globalScene.add
|
||||||
|
.sprite(0, 0, "champion_ribbon")
|
||||||
|
.setName("icon_champion_ribbon")
|
||||||
|
.setVisible(false)
|
||||||
|
.setOrigin(0, 0)
|
||||||
|
.setPositionRelative(this.nameText, 8, 11.75);
|
||||||
|
// Ensure these two icons are positioned below the stats container
|
||||||
|
this.addAt([this.ownedIcon, this.championRibbon], this.getIndex(this.statsContainer));
|
||||||
|
|
||||||
|
this.flyoutMenu = new BattleFlyout(this.player);
|
||||||
|
this.add(this.flyoutMenu);
|
||||||
|
|
||||||
|
this.moveBelow<Phaser.GameObjects.GameObject>(this.flyoutMenu, this.box);
|
||||||
|
|
||||||
|
this.effectivenessContainer = globalScene.add
|
||||||
|
.container(0, 0)
|
||||||
|
.setVisible(false)
|
||||||
|
.setPositionRelative(this.type1Icon, 22, 4);
|
||||||
|
this.add(this.effectivenessContainer);
|
||||||
|
|
||||||
|
this.effectivenessText = addTextObject(5, 4.5, "", TextStyle.BATTLE_INFO);
|
||||||
|
this.effectivenessWindow = addWindow(0, 0, 0, 20, undefined, false, undefined, undefined, WindowVariant.XTHIN);
|
||||||
|
|
||||||
|
this.effectivenessContainer.add([this.effectivenessWindow, this.effectivenessText]);
|
||||||
|
}
|
||||||
|
|
||||||
|
override initInfo(pokemon: EnemyPokemon): void {
|
||||||
|
this.flyoutMenu.initInfo(pokemon);
|
||||||
|
super.initInfo(pokemon);
|
||||||
|
|
||||||
|
if (this.nameText.visible) {
|
||||||
|
this.nameText
|
||||||
|
.on("pointerover", () =>
|
||||||
|
globalScene.ui.showTooltip(
|
||||||
|
"",
|
||||||
|
i18next.t("battleInfo:generation", {
|
||||||
|
generation: i18next.t(`starterSelectUiHandler:gen${pokemon.species.generation}`),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.on("pointerout", () => globalScene.ui.hideTooltip());
|
||||||
|
}
|
||||||
|
|
||||||
|
const dexEntry = globalScene.gameData.dexData[pokemon.species.speciesId];
|
||||||
|
this.ownedIcon.setVisible(!!dexEntry.caughtAttr);
|
||||||
|
const opponentPokemonDexAttr = pokemon.getDexAttr();
|
||||||
|
if (
|
||||||
|
globalScene.gameMode.isClassic &&
|
||||||
|
globalScene.gameData.starterData[pokemon.species.getRootSpeciesId()].classicWinCount > 0 &&
|
||||||
|
globalScene.gameData.starterData[pokemon.species.getRootSpeciesId(true)].classicWinCount > 0
|
||||||
|
) {
|
||||||
|
this.championRibbon.setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if Player owns all genders and forms of the Pokemon
|
||||||
|
const missingDexAttrs = (dexEntry.caughtAttr & opponentPokemonDexAttr) < opponentPokemonDexAttr;
|
||||||
|
|
||||||
|
const ownedAbilityAttrs = globalScene.gameData.starterData[pokemon.species.getRootSpeciesId()].abilityAttr;
|
||||||
|
|
||||||
|
// Check if the player owns ability for the root form
|
||||||
|
const playerOwnsThisAbility = pokemon.checkIfPlayerHasAbilityOfStarter(ownedAbilityAttrs);
|
||||||
|
|
||||||
|
if (missingDexAttrs || !playerOwnsThisAbility) {
|
||||||
|
this.ownedIcon.setTint(0x808080);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.boss) {
|
||||||
|
this.updateBossSegmentDividers(pokemon as EnemyPokemon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show or hide the type effectiveness multiplier window
|
||||||
|
* Passing undefined will hide the window
|
||||||
|
*/
|
||||||
|
updateEffectiveness(effectiveness?: string) {
|
||||||
|
this.currentEffectiveness = effectiveness;
|
||||||
|
|
||||||
|
if (!globalScene.typeHints || effectiveness === undefined || this.flyoutMenu.flyoutVisible) {
|
||||||
|
this.effectivenessContainer.setVisible(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.effectivenessText.setText(effectiveness);
|
||||||
|
this.effectivenessWindow.width = 10 + this.effectivenessText.displayWidth;
|
||||||
|
this.effectivenessContainer.setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request the flyoutMenu to toggle if available and hides or shows the effectiveness window where necessary
|
||||||
|
*/
|
||||||
|
toggleFlyout(visible: boolean): void {
|
||||||
|
this.flyoutMenu.toggleFlyout(visible);
|
||||||
|
|
||||||
|
if (visible) {
|
||||||
|
this.effectivenessContainer.setVisible(false);
|
||||||
|
} else {
|
||||||
|
this.updateEffectiveness(this.currentEffectiveness);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateBossSegments(pokemon: EnemyPokemon): void {
|
||||||
|
const boss = !!pokemon.bossSegments;
|
||||||
|
|
||||||
|
if (boss !== this.boss) {
|
||||||
|
this.boss = boss;
|
||||||
|
|
||||||
|
[
|
||||||
|
this.nameText,
|
||||||
|
this.genderText,
|
||||||
|
this.teraIcon,
|
||||||
|
this.splicedIcon,
|
||||||
|
this.shinyIcon,
|
||||||
|
this.ownedIcon,
|
||||||
|
this.championRibbon,
|
||||||
|
this.statusIndicator,
|
||||||
|
this.levelContainer,
|
||||||
|
this.statValuesContainer,
|
||||||
|
].map(e => (e.x += 48 * (boss ? -1 : 1)));
|
||||||
|
this.hpBar.x += 38 * (boss ? -1 : 1);
|
||||||
|
this.hpBar.y += 2 * (this.boss ? -1 : 1);
|
||||||
|
this.hpBar.setTexture(`overlay_hp${boss ? "_boss" : ""}`);
|
||||||
|
this.box.setTexture(this.getTextureName());
|
||||||
|
this.statsBox.setTexture(`${this.getTextureName()}_stats`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.bossSegments = boss ? pokemon.bossSegments : 0;
|
||||||
|
this.updateBossSegmentDividers(pokemon);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateBossSegmentDividers(pokemon: EnemyPokemon): void {
|
||||||
|
while (this.hpBarSegmentDividers.length) {
|
||||||
|
this.hpBarSegmentDividers.pop()?.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.boss && this.bossSegments > 1) {
|
||||||
|
const uiTheme = globalScene.uiTheme;
|
||||||
|
const maxHp = pokemon.getMaxHp();
|
||||||
|
for (let s = 1; s < this.bossSegments; s++) {
|
||||||
|
const dividerX = (Math.round((maxHp / this.bossSegments) * s) / maxHp) * this.hpBar.width;
|
||||||
|
const divider = globalScene.add.rectangle(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
this.hpBar.height - (uiTheme ? 0 : 1),
|
||||||
|
pokemon.bossSegmentIndex >= s ? 0xffffff : 0x404040,
|
||||||
|
);
|
||||||
|
divider.setOrigin(0.5, 0).setName("hpBar_divider_" + s.toString());
|
||||||
|
this.add(divider);
|
||||||
|
this.moveBelow(divider as Phaser.GameObjects.GameObject, this.statsContainer);
|
||||||
|
|
||||||
|
divider.setPositionRelative(this.hpBar, dividerX, uiTheme ? 0 : 1);
|
||||||
|
this.hpBarSegmentDividers.push(divider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override updateStatusIcon(pokemon: EnemyPokemon): void {
|
||||||
|
super.updateStatusIcon(pokemon, (this.ownedIcon.visible ? 8 : 0) + (this.championRibbon.visible ? 8 : 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override updatePokemonHp(
|
||||||
|
pokemon: EnemyPokemon,
|
||||||
|
resolve: (r: void | PromiseLike<void>) => void,
|
||||||
|
instant?: boolean,
|
||||||
|
): void {
|
||||||
|
super.updatePokemonHp(pokemon, resolve, instant);
|
||||||
|
this.lastHp = pokemon.hp;
|
||||||
|
}
|
||||||
|
}
|
242
src/ui/battle-info/player-battle-info.ts
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
import { getLevelRelExp, getLevelTotalExp } from "#app/data/exp";
|
||||||
|
import type { PlayerPokemon } from "#app/field/pokemon";
|
||||||
|
import { globalScene } from "#app/global-scene";
|
||||||
|
import { ExpGainsSpeed } from "#enums/exp-gains-speed";
|
||||||
|
import { Stat } from "#enums/stat";
|
||||||
|
import BattleInfo from "./battle-info";
|
||||||
|
import type { BattleInfoParamList } from "./battle-info";
|
||||||
|
|
||||||
|
export class PlayerBattleInfo extends BattleInfo {
|
||||||
|
protected player: true = true;
|
||||||
|
protected hpNumbersContainer: Phaser.GameObjects.Container;
|
||||||
|
|
||||||
|
override get statOrder(): Stat[] {
|
||||||
|
return [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.ACC, Stat.EVA, Stat.SPD];
|
||||||
|
}
|
||||||
|
|
||||||
|
override getTextureName(): string {
|
||||||
|
return this.mini ? "pbinfo_player_mini" : "pbinfo_player";
|
||||||
|
}
|
||||||
|
|
||||||
|
override constructTypeIcons(): void {
|
||||||
|
this.type1Icon = globalScene.add.sprite(-139, -17, "pbinfo_player_type1").setName("icon_type_1").setOrigin(0);
|
||||||
|
this.type2Icon = globalScene.add.sprite(-139, -1, "pbinfo_player_type2").setName("icon_type_2").setOrigin(0);
|
||||||
|
this.type3Icon = globalScene.add.sprite(-154, -17, "pbinfo_player_type3").setName("icon_type_3").setOrigin(0);
|
||||||
|
this.add([this.type1Icon, this.type2Icon, this.type3Icon]);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
const posParams: BattleInfoParamList = {
|
||||||
|
nameTextX: -115,
|
||||||
|
nameTextY: -15.2,
|
||||||
|
levelContainerX: -41,
|
||||||
|
levelContainerY: -10,
|
||||||
|
hpBarX: -61,
|
||||||
|
hpBarY: -1,
|
||||||
|
statBox: {
|
||||||
|
xOffset: 8,
|
||||||
|
paddingX: 4,
|
||||||
|
statOverflow: 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
super(Math.floor(globalScene.game.canvas.width / 6) - 10, -72, true, posParams);
|
||||||
|
|
||||||
|
this.hpNumbersContainer = globalScene.add.container(-15, 10).setName("container_hp");
|
||||||
|
|
||||||
|
// hp number container must be beneath the stat container for overlay to display properly
|
||||||
|
this.addAt(this.hpNumbersContainer, this.getIndex(this.statsContainer));
|
||||||
|
|
||||||
|
const expBar = globalScene.add.image(-98, 18, "overlay_exp").setName("overlay_exp").setOrigin(0);
|
||||||
|
this.add(expBar);
|
||||||
|
|
||||||
|
const expMaskRect = globalScene.make
|
||||||
|
.graphics({})
|
||||||
|
.setScale(6)
|
||||||
|
.fillStyle(0xffffff)
|
||||||
|
.beginPath()
|
||||||
|
.fillRect(127, 126, 85, 2);
|
||||||
|
|
||||||
|
const expMask = expMaskRect.createGeometryMask();
|
||||||
|
|
||||||
|
expBar.setMask(expMask);
|
||||||
|
|
||||||
|
this.expBar = expBar;
|
||||||
|
this.expMaskRect = expMaskRect;
|
||||||
|
}
|
||||||
|
|
||||||
|
override initInfo(pokemon: PlayerPokemon): void {
|
||||||
|
super.initInfo(pokemon);
|
||||||
|
this.setHpNumbers(pokemon.hp, pokemon.getMaxHp());
|
||||||
|
this.expMaskRect.x = (pokemon.levelExp / getLevelTotalExp(pokemon.level, pokemon.species.growthRate)) * 510;
|
||||||
|
this.lastExp = pokemon.exp;
|
||||||
|
this.lastLevelExp = pokemon.levelExp;
|
||||||
|
|
||||||
|
this.statValuesContainer.setPosition(8, 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
override setMini(mini: boolean): void {
|
||||||
|
if (this.mini === mini) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.mini = mini;
|
||||||
|
|
||||||
|
this.box.setTexture(this.getTextureName());
|
||||||
|
this.statsBox.setTexture(`${this.getTextureName()}_stats`);
|
||||||
|
|
||||||
|
if (this.player) {
|
||||||
|
this.y -= 12 * (mini ? 1 : -1);
|
||||||
|
this.baseY = this.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
const offsetElements = [
|
||||||
|
this.nameText,
|
||||||
|
this.genderText,
|
||||||
|
this.teraIcon,
|
||||||
|
this.splicedIcon,
|
||||||
|
this.shinyIcon,
|
||||||
|
this.statusIndicator,
|
||||||
|
this.levelContainer,
|
||||||
|
];
|
||||||
|
offsetElements.forEach(el => (el.y += 1.5 * (mini ? -1 : 1)));
|
||||||
|
|
||||||
|
[this.type1Icon, this.type2Icon, this.type3Icon].forEach(el => {
|
||||||
|
el.x += 4 * (mini ? 1 : -1);
|
||||||
|
el.y += -8 * (mini ? 1 : -1);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.statValuesContainer.x += 2 * (mini ? 1 : -1);
|
||||||
|
this.statValuesContainer.y += -7 * (mini ? 1 : -1);
|
||||||
|
|
||||||
|
const toggledElements = [this.hpNumbersContainer, this.expBar];
|
||||||
|
toggledElements.forEach(el => el.setVisible(!mini));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the Hp Number text (that is the "HP/Max HP" text that appears below the player's health bar)
|
||||||
|
* while the health bar is tweening.
|
||||||
|
* @param pokemon - The Pokemon the health bar belongs to.
|
||||||
|
*/
|
||||||
|
protected override onHpTweenUpdate(pokemon: PlayerPokemon): void {
|
||||||
|
const tweenHp = Math.ceil(this.hpBar.scaleX * pokemon.getMaxHp());
|
||||||
|
this.setHpNumbers(tweenHp, pokemon.getMaxHp());
|
||||||
|
this.lastHp = tweenHp;
|
||||||
|
this.updateHpFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePokemonExp(pokemon: PlayerPokemon, instant?: boolean, levelDurationMultiplier = 1): Promise<void> {
|
||||||
|
const levelUp = this.lastLevel < pokemon.level;
|
||||||
|
const relLevelExp = getLevelRelExp(this.lastLevel + 1, pokemon.species.growthRate);
|
||||||
|
const levelExp = levelUp ? relLevelExp : pokemon.levelExp;
|
||||||
|
let ratio = relLevelExp ? levelExp / relLevelExp : 0;
|
||||||
|
if (this.lastLevel >= globalScene.getMaxExpLevel(true)) {
|
||||||
|
ratio = levelUp ? 1 : 0;
|
||||||
|
instant = true;
|
||||||
|
}
|
||||||
|
const durationMultiplier = Phaser.Tweens.Builders.GetEaseFunction("Sine.easeIn")(
|
||||||
|
1 - Math.max(this.lastLevel - 100, 0) / 150,
|
||||||
|
);
|
||||||
|
let duration =
|
||||||
|
this.visible && !instant
|
||||||
|
? ((levelExp - this.lastLevelExp) / relLevelExp) *
|
||||||
|
BattleInfo.EXP_GAINS_DURATION_BASE *
|
||||||
|
durationMultiplier *
|
||||||
|
levelDurationMultiplier
|
||||||
|
: 0;
|
||||||
|
const speed = globalScene.expGainsSpeed;
|
||||||
|
if (speed && speed >= ExpGainsSpeed.DEFAULT) {
|
||||||
|
duration = speed >= ExpGainsSpeed.SKIP ? ExpGainsSpeed.DEFAULT : duration / Math.pow(2, speed);
|
||||||
|
}
|
||||||
|
if (ratio === 1) {
|
||||||
|
this.lastLevelExp = 0;
|
||||||
|
this.lastLevel++;
|
||||||
|
} else {
|
||||||
|
this.lastExp = pokemon.exp;
|
||||||
|
this.lastLevelExp = pokemon.levelExp;
|
||||||
|
}
|
||||||
|
if (duration) {
|
||||||
|
globalScene.playSound("se/exp");
|
||||||
|
}
|
||||||
|
return new Promise(resolve => {
|
||||||
|
globalScene.tweens.add({
|
||||||
|
targets: this.expMaskRect,
|
||||||
|
ease: "Sine.easeIn",
|
||||||
|
x: ratio * 510,
|
||||||
|
duration: duration,
|
||||||
|
onComplete: () => {
|
||||||
|
if (!globalScene) {
|
||||||
|
return resolve();
|
||||||
|
}
|
||||||
|
if (duration) {
|
||||||
|
globalScene.sound.stopByKey("se/exp");
|
||||||
|
}
|
||||||
|
if (ratio === 1) {
|
||||||
|
globalScene.playSound("se/level_up");
|
||||||
|
this.setLevel(this.lastLevel);
|
||||||
|
globalScene.time.delayedCall(500 * levelDurationMultiplier, () => {
|
||||||
|
this.expMaskRect.x = 0;
|
||||||
|
this.updateInfo(pokemon, instant).then(() => resolve());
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the info on the info bar.
|
||||||
|
*
|
||||||
|
* In addition to performing all the steps of {@linkcode BattleInfo.updateInfo},
|
||||||
|
* it also updates the EXP Bar
|
||||||
|
*/
|
||||||
|
override async updateInfo(pokemon: PlayerPokemon, instant?: boolean): Promise<void> {
|
||||||
|
await super.updateInfo(pokemon, instant);
|
||||||
|
const isLevelCapped = pokemon.level >= globalScene.getMaxExpLevel();
|
||||||
|
const oldLevelCapped = this.lastLevelCapped;
|
||||||
|
this.lastLevelCapped = isLevelCapped;
|
||||||
|
|
||||||
|
if (this.lastExp !== pokemon.exp || this.lastLevel !== pokemon.level) {
|
||||||
|
const durationMultipler = Math.max(
|
||||||
|
Phaser.Tweens.Builders.GetEaseFunction("Cubic.easeIn")(1 - Math.min(pokemon.level - this.lastLevel, 10) / 10),
|
||||||
|
0.1,
|
||||||
|
);
|
||||||
|
await this.updatePokemonExp(pokemon, false, durationMultipler);
|
||||||
|
} else if (isLevelCapped !== oldLevelCapped) {
|
||||||
|
this.setLevel(pokemon.level);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the HP numbers text, that is the "HP/Max HP" text that appears below the player's health bar.
|
||||||
|
* @param hp - The current HP of the player.
|
||||||
|
* @param maxHp - The maximum HP of the player.
|
||||||
|
*/
|
||||||
|
setHpNumbers(hp: number, maxHp: number): void {
|
||||||
|
if (!globalScene) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.hpNumbersContainer.removeAll(true);
|
||||||
|
const hpStr = hp.toString();
|
||||||
|
const maxHpStr = maxHp.toString();
|
||||||
|
let offset = 0;
|
||||||
|
for (let i = maxHpStr.length - 1; i >= 0; i--) {
|
||||||
|
this.hpNumbersContainer.add(globalScene.add.image(offset++ * -8, 0, "numbers", maxHpStr[i]));
|
||||||
|
}
|
||||||
|
this.hpNumbersContainer.add(globalScene.add.image(offset++ * -8, 0, "numbers", "/"));
|
||||||
|
for (let i = hpStr.length - 1; i >= 0; i--) {
|
||||||
|
this.hpNumbersContainer.add(globalScene.add.image(offset++ * -8, 0, "numbers", hpStr[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the level numbers container to display the provided level
|
||||||
|
*
|
||||||
|
* Overrides the default implementation to handle displaying level capped numbers in red.
|
||||||
|
* @param level - The level to display
|
||||||
|
*/
|
||||||
|
override setLevel(level: number): void {
|
||||||
|
super.setLevel(level, level >= globalScene.getMaxExpLevel() ? "numbers_red" : "numbers");
|
||||||
|
}
|
||||||
|
}
|
@ -43,14 +43,13 @@ export default class EggCounterContainer extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
this.add(this.eggCountWindow);
|
this.add(this.eggCountWindow);
|
||||||
|
|
||||||
const eggSprite = globalScene.add.sprite(19, 18, "egg", "egg_0");
|
const eggSprite = globalScene.add.sprite(19, 18, "egg", "egg_0").setScale(0.32);
|
||||||
eggSprite.setScale(0.32);
|
|
||||||
|
|
||||||
this.eggCountText = addTextObject(28, 13, `${this.eggCount}`, TextStyle.MESSAGE, { fontSize: "66px" });
|
this.eggCountText = addTextObject(28, 13, `${this.eggCount}`, TextStyle.MESSAGE, { fontSize: "66px" }).setName(
|
||||||
this.eggCountText.setName("text-egg-count");
|
"text-egg-count",
|
||||||
|
);
|
||||||
|
|
||||||
this.add(eggSprite);
|
this.add([eggSprite, this.eggCountText]);
|
||||||
this.add(this.eggCountText);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -22,18 +22,12 @@ export default class EvolutionSceneHandler extends MessageUiHandler {
|
|||||||
const ui = this.getUi();
|
const ui = this.getUi();
|
||||||
|
|
||||||
this.evolutionContainer = globalScene.add.container(0, -globalScene.game.canvas.height / 6);
|
this.evolutionContainer = globalScene.add.container(0, -globalScene.game.canvas.height / 6);
|
||||||
ui.add(this.evolutionContainer);
|
|
||||||
|
|
||||||
const messageBg = globalScene.add.sprite(0, 0, "bg", globalScene.windowType);
|
const messageBg = globalScene.add.sprite(0, 0, "bg", globalScene.windowType).setOrigin(0, 1).setVisible(false);
|
||||||
messageBg.setOrigin(0, 1);
|
|
||||||
messageBg.setVisible(false);
|
|
||||||
ui.add(messageBg);
|
|
||||||
|
|
||||||
this.messageBg = messageBg;
|
this.messageBg = messageBg;
|
||||||
|
|
||||||
this.messageContainer = globalScene.add.container(12, -39);
|
this.messageContainer = globalScene.add.container(12, -39).setVisible(false);
|
||||||
this.messageContainer.setVisible(false);
|
|
||||||
ui.add(this.messageContainer);
|
|
||||||
|
|
||||||
const message = addTextObject(0, 0, "", TextStyle.MESSAGE, {
|
const message = addTextObject(0, 0, "", TextStyle.MESSAGE, {
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
@ -43,6 +37,8 @@ export default class EvolutionSceneHandler extends MessageUiHandler {
|
|||||||
});
|
});
|
||||||
this.messageContainer.add(message);
|
this.messageContainer.add(message);
|
||||||
|
|
||||||
|
ui.add([this.evolutionContainer, this.messageBg, this.messageContainer]);
|
||||||
|
|
||||||
this.message = message;
|
this.message = message;
|
||||||
|
|
||||||
this.initPromptSprite(this.messageContainer);
|
this.initPromptSprite(this.messageContainer);
|
||||||
@ -52,10 +48,8 @@ export default class EvolutionSceneHandler extends MessageUiHandler {
|
|||||||
super.show(_args);
|
super.show(_args);
|
||||||
|
|
||||||
globalScene.ui.bringToTop(this.evolutionContainer);
|
globalScene.ui.bringToTop(this.evolutionContainer);
|
||||||
globalScene.ui.bringToTop(this.messageBg);
|
globalScene.ui.bringToTop(this.messageBg.setVisible(true));
|
||||||
globalScene.ui.bringToTop(this.messageContainer);
|
globalScene.ui.bringToTop(this.messageContainer.setVisible(true));
|
||||||
this.messageBg.setVisible(true);
|
|
||||||
this.messageContainer.setVisible(true);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import { getLocalizedSpriteKey, fixedInt, padInt } from "#app/utils/common";
|
|||||||
import { MoveCategory } from "#enums/MoveCategory";
|
import { MoveCategory } from "#enums/MoveCategory";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import { Button } from "#enums/buttons";
|
import { Button } from "#enums/buttons";
|
||||||
import type { PokemonMove } from "#app/field/pokemon";
|
import type { EnemyPokemon, PokemonMove } from "#app/field/pokemon";
|
||||||
import type Pokemon from "#app/field/pokemon";
|
import type Pokemon from "#app/field/pokemon";
|
||||||
import type { CommandPhase } from "#app/phases/command-phase";
|
import type { CommandPhase } from "#app/phases/command-phase";
|
||||||
import MoveInfoOverlay from "./move-info-overlay";
|
import MoveInfoOverlay from "./move-info-overlay";
|
||||||
@ -279,7 +279,7 @@ export default class FightUiHandler extends UiHandler implements InfoToggle {
|
|||||||
this.moveInfoOverlay.show(pokemonMove.getMove());
|
this.moveInfoOverlay.show(pokemonMove.getMove());
|
||||||
|
|
||||||
pokemon.getOpponents().forEach(opponent => {
|
pokemon.getOpponents().forEach(opponent => {
|
||||||
opponent.updateEffectiveness(this.getEffectivenessText(pokemon, opponent, pokemonMove));
|
(opponent as EnemyPokemon).updateEffectiveness(this.getEffectivenessText(pokemon, opponent, pokemonMove));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -391,7 +391,7 @@ export default class FightUiHandler extends UiHandler implements InfoToggle {
|
|||||||
|
|
||||||
const opponents = (globalScene.getCurrentPhase() as CommandPhase).getPokemon().getOpponents();
|
const opponents = (globalScene.getCurrentPhase() as CommandPhase).getPokemon().getOpponents();
|
||||||
opponents.forEach(opponent => {
|
opponents.forEach(opponent => {
|
||||||
opponent.updateEffectiveness(undefined);
|
(opponent as EnemyPokemon).updateEffectiveness();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,16 +5,7 @@ import { addTextObject, getTextColor, TextStyle } from "./text";
|
|||||||
import type { UiTheme } from "#enums/ui-theme";
|
import type { UiTheme } from "#enums/ui-theme";
|
||||||
import { addWindow, WindowVariant } from "./ui-theme";
|
import { addWindow, WindowVariant } from "./ui-theme";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
|
import type { DropDownColumn } from "#enums/drop-down-column";
|
||||||
export enum DropDownColumn {
|
|
||||||
GEN,
|
|
||||||
TYPES,
|
|
||||||
BIOME,
|
|
||||||
CAUGHT,
|
|
||||||
UNLOCKS,
|
|
||||||
MISC,
|
|
||||||
SORT,
|
|
||||||
}
|
|
||||||
|
|
||||||
export class FilterBar extends Phaser.GameObjects.Container {
|
export class FilterBar extends Phaser.GameObjects.Container {
|
||||||
private window: Phaser.GameObjects.NineSlice;
|
private window: Phaser.GameObjects.NineSlice;
|
||||||
@ -49,13 +40,9 @@ export class FilterBar extends Phaser.GameObjects.Container {
|
|||||||
this.cursorOffset = cursorOffset;
|
this.cursorOffset = cursorOffset;
|
||||||
|
|
||||||
this.window = addWindow(0, 0, width, height, false, false, undefined, undefined, WindowVariant.THIN);
|
this.window = addWindow(0, 0, width, height, false, false, undefined, undefined, WindowVariant.THIN);
|
||||||
this.add(this.window);
|
|
||||||
|
|
||||||
this.cursorObj = globalScene.add.image(1, 1, "cursor");
|
this.cursorObj = globalScene.add.image(1, 1, "cursor").setScale(0.5).setVisible(false).setOrigin(0);
|
||||||
this.cursorObj.setScale(0.5);
|
this.add([this.window, this.cursorObj]);
|
||||||
this.cursorObj.setVisible(false);
|
|
||||||
this.cursorObj.setOrigin(0, 0);
|
|
||||||
this.add(this.cursorObj);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -23,7 +23,8 @@ import type { Species } from "#enums/species";
|
|||||||
import { Button } from "#enums/buttons";
|
import { Button } from "#enums/buttons";
|
||||||
import { DropDown, DropDownLabel, DropDownOption, DropDownState, DropDownType, SortCriteria } from "#app/ui/dropdown";
|
import { DropDown, DropDownLabel, DropDownOption, DropDownState, DropDownType, SortCriteria } from "#app/ui/dropdown";
|
||||||
import { PokedexMonContainer } from "#app/ui/pokedex-mon-container";
|
import { PokedexMonContainer } from "#app/ui/pokedex-mon-container";
|
||||||
import { DropDownColumn, FilterBar } from "#app/ui/filter-bar";
|
import { FilterBar } from "#app/ui/filter-bar";
|
||||||
|
import { DropDownColumn } from "#enums/drop-down-column";
|
||||||
import { ScrollBar } from "#app/ui/scroll-bar";
|
import { ScrollBar } from "#app/ui/scroll-bar";
|
||||||
import { Abilities } from "#enums/abilities";
|
import { Abilities } from "#enums/abilities";
|
||||||
import {
|
import {
|
||||||
|
@ -8,7 +8,7 @@ import type Pokemon from "../field/pokemon";
|
|||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import type { DexEntry, StarterDataEntry } from "../system/game-data";
|
import type { DexEntry, StarterDataEntry } from "../system/game-data";
|
||||||
import { DexAttr } from "../system/game-data";
|
import { DexAttr } from "../system/game-data";
|
||||||
import { fixedInt } from "#app/utils/common";
|
import { fixedInt, getShinyDescriptor } from "#app/utils/common";
|
||||||
import ConfirmUiHandler from "./confirm-ui-handler";
|
import ConfirmUiHandler from "./confirm-ui-handler";
|
||||||
import { StatsContainer } from "./stats-container";
|
import { StatsContainer } from "./stats-container";
|
||||||
import { TextStyle, addBBCodeTextObject, addTextObject, getTextColor } from "./text";
|
import { TextStyle, addBBCodeTextObject, addTextObject, getTextColor } from "./text";
|
||||||
@ -343,18 +343,19 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container {
|
|||||||
this.pokemonShinyIcon.setVisible(pokemon.isShiny());
|
this.pokemonShinyIcon.setVisible(pokemon.isShiny());
|
||||||
this.pokemonShinyIcon.setTint(getVariantTint(baseVariant));
|
this.pokemonShinyIcon.setTint(getVariantTint(baseVariant));
|
||||||
if (this.pokemonShinyIcon.visible) {
|
if (this.pokemonShinyIcon.visible) {
|
||||||
const shinyDescriptor =
|
let shinyDescriptor = "";
|
||||||
doubleShiny || baseVariant
|
if (doubleShiny || baseVariant) {
|
||||||
? `${baseVariant === 2 ? i18next.t("common:epicShiny") : baseVariant === 1 ? i18next.t("common:rareShiny") : i18next.t("common:commonShiny")}${doubleShiny ? `/${pokemon.fusionVariant === 2 ? i18next.t("common:epicShiny") : pokemon.fusionVariant === 1 ? i18next.t("common:rareShiny") : i18next.t("common:commonShiny")}` : ""}`
|
shinyDescriptor = " (" + getShinyDescriptor(baseVariant);
|
||||||
: "";
|
if (doubleShiny) {
|
||||||
this.pokemonShinyIcon.on("pointerover", () =>
|
shinyDescriptor += "/" + getShinyDescriptor(pokemon.fusionVariant);
|
||||||
globalScene.ui.showTooltip(
|
}
|
||||||
"",
|
shinyDescriptor += ")";
|
||||||
`${i18next.t("common:shinyOnHover")}${shinyDescriptor ? ` (${shinyDescriptor})` : ""}`,
|
}
|
||||||
true,
|
this.pokemonShinyIcon
|
||||||
),
|
.on("pointerover", () =>
|
||||||
);
|
globalScene.ui.showTooltip("", i18next.t("common:shinyOnHover") + shinyDescriptor, true),
|
||||||
this.pokemonShinyIcon.on("pointerout", () => globalScene.ui.hideTooltip());
|
)
|
||||||
|
.on("pointerout", () => globalScene.ui.hideTooltip());
|
||||||
|
|
||||||
const newShiny = BigInt(1 << (pokemon.shiny ? 1 : 0));
|
const newShiny = BigInt(1 << (pokemon.shiny ? 1 : 0));
|
||||||
const newVariant = BigInt(1 << (pokemon.variant + 4));
|
const newVariant = BigInt(1 << (pokemon.variant + 4));
|
||||||
|
@ -53,7 +53,8 @@ import { Button } from "#enums/buttons";
|
|||||||
import { EggSourceType } from "#enums/egg-source-types";
|
import { EggSourceType } from "#enums/egg-source-types";
|
||||||
import { DropDown, DropDownLabel, DropDownOption, DropDownState, DropDownType, SortCriteria } from "#app/ui/dropdown";
|
import { DropDown, DropDownLabel, DropDownOption, DropDownState, DropDownType, SortCriteria } from "#app/ui/dropdown";
|
||||||
import { StarterContainer } from "#app/ui/starter-container";
|
import { StarterContainer } from "#app/ui/starter-container";
|
||||||
import { DropDownColumn, FilterBar } from "#app/ui/filter-bar";
|
import { FilterBar } from "#app/ui/filter-bar";
|
||||||
|
import { DropDownColumn } from "#enums/drop-down-column";
|
||||||
import { ScrollBar } from "#app/ui/scroll-bar";
|
import { ScrollBar } from "#app/ui/scroll-bar";
|
||||||
import { SelectChallengePhase } from "#app/phases/select-challenge-phase";
|
import { SelectChallengePhase } from "#app/phases/select-challenge-phase";
|
||||||
import { EncounterPhase } from "#app/phases/encounter-phase";
|
import { EncounterPhase } from "#app/phases/encounter-phase";
|
||||||
@ -108,17 +109,21 @@ const languageSettings: { [key: string]: LanguageSetting } = {
|
|||||||
instructionTextSize: "38px",
|
instructionTextSize: "38px",
|
||||||
},
|
},
|
||||||
de: {
|
de: {
|
||||||
starterInfoTextSize: "48px",
|
starterInfoTextSize: "54px",
|
||||||
instructionTextSize: "35px",
|
instructionTextSize: "35px",
|
||||||
starterInfoXPos: 33,
|
starterInfoXPos: 35,
|
||||||
},
|
},
|
||||||
"es-ES": {
|
"es-ES": {
|
||||||
starterInfoTextSize: "52px",
|
starterInfoTextSize: "50px",
|
||||||
instructionTextSize: "35px",
|
instructionTextSize: "38px",
|
||||||
|
starterInfoYOffset: 0.5,
|
||||||
|
starterInfoXPos: 38,
|
||||||
},
|
},
|
||||||
"es-MX": {
|
"es-MX": {
|
||||||
starterInfoTextSize: "52px",
|
starterInfoTextSize: "50px",
|
||||||
instructionTextSize: "35px",
|
instructionTextSize: "38px",
|
||||||
|
starterInfoYOffset: 0.5,
|
||||||
|
starterInfoXPos: 38,
|
||||||
},
|
},
|
||||||
fr: {
|
fr: {
|
||||||
starterInfoTextSize: "54px",
|
starterInfoTextSize: "54px",
|
||||||
@ -128,21 +133,16 @@ const languageSettings: { [key: string]: LanguageSetting } = {
|
|||||||
starterInfoTextSize: "56px",
|
starterInfoTextSize: "56px",
|
||||||
instructionTextSize: "38px",
|
instructionTextSize: "38px",
|
||||||
},
|
},
|
||||||
pt_BR: {
|
"pt-BR": {
|
||||||
starterInfoTextSize: "47px",
|
starterInfoTextSize: "48px",
|
||||||
instructionTextSize: "38px",
|
instructionTextSize: "42px",
|
||||||
|
starterInfoYOffset: 0.5,
|
||||||
starterInfoXPos: 33,
|
starterInfoXPos: 33,
|
||||||
},
|
},
|
||||||
zh: {
|
zh: {
|
||||||
starterInfoTextSize: "47px",
|
starterInfoTextSize: "56px",
|
||||||
instructionTextSize: "38px",
|
instructionTextSize: "36px",
|
||||||
starterInfoYOffset: 1,
|
starterInfoXPos: 26,
|
||||||
starterInfoXPos: 24,
|
|
||||||
},
|
|
||||||
pt: {
|
|
||||||
starterInfoTextSize: "48px",
|
|
||||||
instructionTextSize: "42px",
|
|
||||||
starterInfoXPos: 33,
|
|
||||||
},
|
},
|
||||||
ko: {
|
ko: {
|
||||||
starterInfoTextSize: "60px",
|
starterInfoTextSize: "60px",
|
||||||
@ -156,9 +156,11 @@ const languageSettings: { [key: string]: LanguageSetting } = {
|
|||||||
starterInfoYOffset: 0.5,
|
starterInfoYOffset: 0.5,
|
||||||
starterInfoXPos: 33,
|
starterInfoXPos: 33,
|
||||||
},
|
},
|
||||||
"ca-ES": {
|
ca: {
|
||||||
starterInfoTextSize: "52px",
|
starterInfoTextSize: "48px",
|
||||||
instructionTextSize: "38px",
|
instructionTextSize: "38px",
|
||||||
|
starterInfoYOffset: 0.5,
|
||||||
|
starterInfoXPos: 29,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
isNullOrUndefined,
|
isNullOrUndefined,
|
||||||
toReadableString,
|
toReadableString,
|
||||||
formatStat,
|
formatStat,
|
||||||
|
getShinyDescriptor,
|
||||||
} from "#app/utils/common";
|
} from "#app/utils/common";
|
||||||
import type { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
|
import type { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
|
||||||
import { getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters";
|
import { getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters";
|
||||||
@ -445,18 +446,19 @@ export default class SummaryUiHandler extends UiHandler {
|
|||||||
this.shinyIcon.setVisible(this.pokemon.isShiny(false));
|
this.shinyIcon.setVisible(this.pokemon.isShiny(false));
|
||||||
this.shinyIcon.setTint(getVariantTint(baseVariant));
|
this.shinyIcon.setTint(getVariantTint(baseVariant));
|
||||||
if (this.shinyIcon.visible) {
|
if (this.shinyIcon.visible) {
|
||||||
const shinyDescriptor =
|
let shinyDescriptor = "";
|
||||||
doubleShiny || baseVariant
|
if (doubleShiny || baseVariant) {
|
||||||
? `${baseVariant === 2 ? i18next.t("common:epicShiny") : baseVariant === 1 ? i18next.t("common:rareShiny") : i18next.t("common:commonShiny")}${doubleShiny ? `/${this.pokemon.fusionVariant === 2 ? i18next.t("common:epicShiny") : this.pokemon.fusionVariant === 1 ? i18next.t("common:rareShiny") : i18next.t("common:commonShiny")}` : ""}`
|
shinyDescriptor = " (" + getShinyDescriptor(baseVariant);
|
||||||
: "";
|
if (doubleShiny) {
|
||||||
this.shinyIcon.on("pointerover", () =>
|
shinyDescriptor += "/" + getShinyDescriptor(this.pokemon.fusionVariant);
|
||||||
globalScene.ui.showTooltip(
|
}
|
||||||
"",
|
shinyDescriptor += ")";
|
||||||
`${i18next.t("common:shinyOnHover")}${shinyDescriptor ? ` (${shinyDescriptor})` : ""}`,
|
}
|
||||||
true,
|
this.shinyIcon
|
||||||
),
|
.on("pointerover", () =>
|
||||||
);
|
globalScene.ui.showTooltip("", i18next.t("common:shinyOnHover") + shinyDescriptor, true),
|
||||||
this.shinyIcon.on("pointerout", () => globalScene.ui.hideTooltip());
|
)
|
||||||
|
.on("pointerout", () => globalScene.ui.hideTooltip());
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fusionShinyIcon.setPosition(this.shinyIcon.x, this.shinyIcon.y);
|
this.fusionShinyIcon.setPosition(this.shinyIcon.x, this.shinyIcon.y);
|
||||||
|
@ -2,6 +2,7 @@ import { MoneyFormat } from "#enums/money-format";
|
|||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import { pokerogueApi } from "#app/plugins/api/pokerogue-api";
|
import { pokerogueApi } from "#app/plugins/api/pokerogue-api";
|
||||||
|
import type { Variant } from "#app/sprites/variant";
|
||||||
|
|
||||||
export type nil = null | undefined;
|
export type nil = null | undefined;
|
||||||
|
|
||||||
@ -576,3 +577,18 @@ export function animationFileName(move: Moves): string {
|
|||||||
export function camelCaseToKebabCase(str: string): string {
|
export function camelCaseToKebabCase(str: string): string {
|
||||||
return str.replace(/[A-Z]+(?![a-z])|[A-Z]/g, (s, o) => (o ? "-" : "") + s.toLowerCase());
|
return str.replace(/[A-Z]+(?![a-z])|[A-Z]/g, (s, o) => (o ? "-" : "") + s.toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Get the localized shiny descriptor for the provided variant
|
||||||
|
* @param variant - The variant to get the shiny descriptor for
|
||||||
|
* @returns The localized shiny descriptor
|
||||||
|
*/
|
||||||
|
export function getShinyDescriptor(variant: Variant): string {
|
||||||
|
switch (variant) {
|
||||||
|
case 2:
|
||||||
|
return i18next.t("common:epicShiny");
|
||||||
|
case 1:
|
||||||
|
return i18next.t("common:rareShiny");
|
||||||
|
case 0:
|
||||||
|
return i18next.t("common:commonShiny");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { BattlerIndex } from "#app/battle";
|
import { BattlerIndex } from "#app/battle";
|
||||||
import { allAbilities } from "#app/data/data-lists";
|
|
||||||
import { ArenaTagSide } from "#app/data/arena-tag";
|
import { ArenaTagSide } from "#app/data/arena-tag";
|
||||||
|
import { allAbilities } from "#app/data/data-lists";
|
||||||
import { ArenaTagType } from "#app/enums/arena-tag-type";
|
import { ArenaTagType } from "#app/enums/arena-tag-type";
|
||||||
import { BattlerTagType } from "#app/enums/battler-tag-type";
|
import { BattlerTagType } from "#app/enums/battler-tag-type";
|
||||||
import { Stat } from "#app/enums/stat";
|
import { Stat } from "#app/enums/stat";
|
||||||
@ -107,35 +107,33 @@ describe("Abilities - Good As Gold", () => {
|
|||||||
expect(game.scene.getPlayerField()[1].getTag(BattlerTagType.HELPING_HAND)).toBeUndefined();
|
expect(game.scene.getPlayerField()[1].getTag(BattlerTagType.HELPING_HAND)).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should block the ally's heal bell, but only if the good as gold user is on the field", async () => {
|
// TODO: re-enable when heal bell is fixed
|
||||||
game.override.battleStyle("double");
|
it.todo("should block the ally's heal bell, but only if the good as gold user is on the field", async () => {
|
||||||
game.override.moveset([Moves.HEAL_BELL, Moves.SPLASH]);
|
game.override.battleStyle("double").statusEffect(StatusEffect.BURN);
|
||||||
game.override.statusEffect(StatusEffect.BURN);
|
await game.classicMode.startBattle([Species.MILOTIC, Species.FEEBAS, Species.ABRA]);
|
||||||
await game.classicMode.startBattle([Species.MAGIKARP, Species.FEEBAS, Species.ABRA]);
|
const [milotic, feebas, abra] = game.scene.getPlayerParty();
|
||||||
const [good_as_gold, ball_fetch] = game.scene.getPlayerField();
|
game.field.mockAbility(milotic, Abilities.GOOD_AS_GOLD);
|
||||||
|
game.field.mockAbility(feebas, Abilities.BALL_FETCH);
|
||||||
// Force second pokemon to have ball fetch to isolate to a single mon.
|
game.field.mockAbility(abra, Abilities.BALL_FETCH);
|
||||||
vi.spyOn(ball_fetch, "getAbility").mockReturnValue(allAbilities[Abilities.BALL_FETCH]);
|
|
||||||
|
|
||||||
// turn 1
|
// turn 1
|
||||||
game.move.select(Moves.SPLASH, 0);
|
game.move.use(Moves.SPLASH, 0);
|
||||||
game.move.select(Moves.HEAL_BELL, 1);
|
game.move.use(Moves.HEAL_BELL, 1);
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
expect(good_as_gold.status?.effect).toBe(StatusEffect.BURN);
|
expect(milotic.status?.effect).toBe(StatusEffect.BURN);
|
||||||
|
|
||||||
game.doSwitchPokemon(2);
|
game.doSwitchPokemon(2);
|
||||||
game.move.select(Moves.HEAL_BELL, 0);
|
game.move.use(Moves.HEAL_BELL, 1);
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
expect(good_as_gold.status?.effect).toBeUndefined();
|
expect(milotic.status?.effect).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not block field targeted effects like rain dance", async () => {
|
it("should not block field targeted effects like rain dance", async () => {
|
||||||
game.override.battleStyle("single");
|
game.override.battleStyle("single");
|
||||||
game.override.enemyMoveset([Moves.RAIN_DANCE]);
|
game.override.enemyMoveset([Moves.RAIN_DANCE]);
|
||||||
game.override.weather(WeatherType.NONE);
|
|
||||||
await game.classicMode.startBattle([Species.MAGIKARP]);
|
await game.classicMode.startBattle([Species.MAGIKARP]);
|
||||||
|
|
||||||
game.move.select(Moves.SPLASH, 0);
|
game.move.use(Moves.SPLASH, 0);
|
||||||
await game.phaseInterceptor.to("BerryPhase");
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.RAIN);
|
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.RAIN);
|
||||||
|
@ -29,20 +29,18 @@ describe("Moves - Alluring Voice", () => {
|
|||||||
.disableCrits()
|
.disableCrits()
|
||||||
.enemySpecies(Species.MAGIKARP)
|
.enemySpecies(Species.MAGIKARP)
|
||||||
.enemyAbility(Abilities.ICE_SCALES)
|
.enemyAbility(Abilities.ICE_SCALES)
|
||||||
.enemyMoveset([Moves.HOWL])
|
.enemyMoveset(Moves.HOWL)
|
||||||
.startingLevel(10)
|
.startingLevel(10)
|
||||||
.enemyLevel(10)
|
.enemyLevel(10)
|
||||||
.starterSpecies(Species.FEEBAS)
|
.ability(Abilities.BALL_FETCH);
|
||||||
.ability(Abilities.BALL_FETCH)
|
|
||||||
.moveset([Moves.ALLURING_VOICE]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should confuse the opponent if their stat stages were raised", async () => {
|
it("should confuse the opponent if their stat stages were raised", async () => {
|
||||||
await game.classicMode.startBattle();
|
await game.classicMode.startBattle([Species.FEEBAS]);
|
||||||
|
|
||||||
const enemy = game.scene.getEnemyPokemon()!;
|
const enemy = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
game.move.select(Moves.ALLURING_VOICE);
|
game.move.use(Moves.ALLURING_VOICE);
|
||||||
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
||||||
await game.phaseInterceptor.to(BerryPhase);
|
await game.phaseInterceptor.to(BerryPhase);
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { MoveResult } from "#app/field/pokemon";
|
||||||
import { Abilities } from "#enums/abilities";
|
import { Abilities } from "#enums/abilities";
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
import { Species } from "#enums/species";
|
import { Species } from "#enums/species";
|
||||||
@ -22,21 +23,23 @@ describe("Moves - Chloroblast", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
game = new GameManager(phaserGame);
|
game = new GameManager(phaserGame);
|
||||||
game.override
|
game.override
|
||||||
.moveset([Moves.CHLOROBLAST])
|
|
||||||
.ability(Abilities.BALL_FETCH)
|
.ability(Abilities.BALL_FETCH)
|
||||||
.battleStyle("single")
|
.battleStyle("single")
|
||||||
.disableCrits()
|
.disableCrits()
|
||||||
.enemySpecies(Species.MAGIKARP)
|
.enemySpecies(Species.MAGIKARP)
|
||||||
.enemyAbility(Abilities.BALL_FETCH)
|
.enemyAbility(Abilities.BALL_FETCH);
|
||||||
.enemyMoveset(Moves.PROTECT);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not deal recoil damage if the opponent uses protect", async () => {
|
it("should not deal recoil damage if the opponent uses protect", async () => {
|
||||||
await game.classicMode.startBattle([Species.FEEBAS]);
|
await game.classicMode.startBattle([Species.FEEBAS]);
|
||||||
|
|
||||||
game.move.select(Moves.CHLOROBLAST);
|
game.move.use(Moves.CHLOROBLAST);
|
||||||
await game.phaseInterceptor.to("BerryPhase");
|
await game.move.forceEnemyMove(Moves.PROTECT);
|
||||||
|
await game.toEndOfTurn();
|
||||||
|
|
||||||
expect(game.scene.getPlayerPokemon()!.isFullHp()).toBe(true);
|
const player = game.field.getPlayerPokemon();
|
||||||
|
|
||||||
|
expect(player.isFullHp()).toBe(true);
|
||||||
|
expect(player.getLastXMoves()[0]).toMatchObject({ result: MoveResult.MISS, move: Moves.CHLOROBLAST });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -25,7 +25,7 @@ describe("Moves - Gastro Acid", () => {
|
|||||||
game.override.battleStyle("double");
|
game.override.battleStyle("double");
|
||||||
game.override.startingLevel(1);
|
game.override.startingLevel(1);
|
||||||
game.override.enemyLevel(100);
|
game.override.enemyLevel(100);
|
||||||
game.override.ability(Abilities.NONE);
|
game.override.ability(Abilities.BALL_FETCH);
|
||||||
game.override.moveset([Moves.GASTRO_ACID, Moves.WATER_GUN, Moves.SPLASH, Moves.CORE_ENFORCER]);
|
game.override.moveset([Moves.GASTRO_ACID, Moves.WATER_GUN, Moves.SPLASH, Moves.CORE_ENFORCER]);
|
||||||
game.override.enemySpecies(Species.BIDOOF);
|
game.override.enemySpecies(Species.BIDOOF);
|
||||||
game.override.enemyMoveset(Moves.SPLASH);
|
game.override.enemyMoveset(Moves.SPLASH);
|
||||||
@ -40,7 +40,7 @@ describe("Moves - Gastro Acid", () => {
|
|||||||
* - player mon 1 should have dealt damage, player mon 2 should have not
|
* - player mon 1 should have dealt damage, player mon 2 should have not
|
||||||
*/
|
*/
|
||||||
|
|
||||||
await game.startBattle();
|
await game.classicMode.startBattle();
|
||||||
|
|
||||||
game.move.select(Moves.GASTRO_ACID, 0, BattlerIndex.ENEMY);
|
game.move.select(Moves.GASTRO_ACID, 0, BattlerIndex.ENEMY);
|
||||||
game.move.select(Moves.SPLASH, 1);
|
game.move.select(Moves.SPLASH, 1);
|
||||||
@ -63,7 +63,7 @@ describe("Moves - Gastro Acid", () => {
|
|||||||
it("fails if used on an enemy with an already-suppressed ability", async () => {
|
it("fails if used on an enemy with an already-suppressed ability", async () => {
|
||||||
game.override.battleStyle("single");
|
game.override.battleStyle("single");
|
||||||
|
|
||||||
await game.startBattle();
|
await game.classicMode.startBattle();
|
||||||
|
|
||||||
game.move.select(Moves.CORE_ENFORCER);
|
game.move.select(Moves.CORE_ENFORCER);
|
||||||
// Force player to be slower to enable Core Enforcer to proc its suppression effect
|
// Force player to be slower to enable Core Enforcer to proc its suppression effect
|
||||||
@ -77,4 +77,27 @@ describe("Moves - Gastro Acid", () => {
|
|||||||
|
|
||||||
expect(game.scene.getPlayerPokemon()!.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
|
expect(game.scene.getPlayerPokemon()!.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should suppress the passive of a target even if its main ability is unsuppressable and not suppress main abli", async () => {
|
||||||
|
game.override
|
||||||
|
.enemyAbility(Abilities.COMATOSE)
|
||||||
|
.enemyPassiveAbility(Abilities.WATER_ABSORB)
|
||||||
|
.moveset([Moves.SPLASH, Moves.GASTRO_ACID, Moves.WATER_GUN]);
|
||||||
|
await game.classicMode.startBattle([Species.MAGIKARP]);
|
||||||
|
|
||||||
|
const enemyPokemon = game.scene.getEnemyPokemon();
|
||||||
|
|
||||||
|
game.move.select(Moves.GASTRO_ACID);
|
||||||
|
await game.toNextTurn();
|
||||||
|
expect(enemyPokemon?.summonData.abilitySuppressed).toBe(true);
|
||||||
|
|
||||||
|
game.move.select(Moves.WATER_GUN);
|
||||||
|
await game.toNextTurn();
|
||||||
|
expect(enemyPokemon?.getHpRatio()).toBeLessThan(1);
|
||||||
|
|
||||||
|
game.move.select(Moves.SPORE);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(enemyPokemon?.status?.effect).toBeFalsy();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
import { BattlerIndex } from "#app/battle";
|
||||||
import Phaser from "phaser";
|
import { MoveResult, PokemonMove } from "#app/field/pokemon";
|
||||||
import GameManager from "#test/testUtils/gameManager";
|
import { BerryPhase } from "#app/phases/berry-phase";
|
||||||
|
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
||||||
import { Abilities } from "#enums/abilities";
|
import { Abilities } from "#enums/abilities";
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
import { Species } from "#enums/species";
|
|
||||||
import { BerryPhase } from "#app/phases/berry-phase";
|
|
||||||
import { MoveResult, PokemonMove } from "#app/field/pokemon";
|
|
||||||
import { PokemonType } from "#enums/pokemon-type";
|
import { PokemonType } from "#enums/pokemon-type";
|
||||||
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
import { Species } from "#enums/species";
|
||||||
import { StatusEffect } from "#enums/status-effect";
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
import { BattlerIndex } from "#app/battle";
|
import GameManager from "#test/testUtils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
describe("Moves - Powder", () => {
|
describe("Moves - Powder", () => {
|
||||||
let phaserGame: Phaser.Game;
|
let phaserGame: Phaser.Game;
|
||||||
@ -161,7 +161,6 @@ describe("Moves - Powder", () => {
|
|||||||
game.move.select(Moves.ROAR, 0, BattlerIndex.ENEMY_2);
|
game.move.select(Moves.ROAR, 0, BattlerIndex.ENEMY_2);
|
||||||
game.move.select(Moves.SPLASH, 1);
|
game.move.select(Moves.SPLASH, 1);
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
await game.toNextTurn(); // Requires game.toNextTurn() twice due to double battle
|
|
||||||
|
|
||||||
// Turn 2: Enemy should activate Powder twice: From using Ember, and from copying Fiery Dance via Dancer
|
// Turn 2: Enemy should activate Powder twice: From using Ember, and from copying Fiery Dance via Dancer
|
||||||
playerPokemon.hp = playerPokemon.getMaxHp();
|
playerPokemon.hp = playerPokemon.getMaxHp();
|
||||||
|
@ -5,6 +5,7 @@ import { getMoveTargets } from "#app/data/moves/move";
|
|||||||
import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
|
import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
|
||||||
import Trainer from "#app/field/trainer";
|
import Trainer from "#app/field/trainer";
|
||||||
import { GameModes, getGameMode } from "#app/game-mode";
|
import { GameModes, getGameMode } from "#app/game-mode";
|
||||||
|
import { globalScene } from "#app/global-scene";
|
||||||
import { ModifierTypeOption, modifierTypes } from "#app/modifier/modifier-type";
|
import { ModifierTypeOption, modifierTypes } from "#app/modifier/modifier-type";
|
||||||
import overrides from "#app/overrides";
|
import overrides from "#app/overrides";
|
||||||
import { CheckSwitchPhase } from "#app/phases/check-switch-phase";
|
import { CheckSwitchPhase } from "#app/phases/check-switch-phase";
|
||||||
@ -22,15 +23,13 @@ import { TitlePhase } from "#app/phases/title-phase";
|
|||||||
import { TurnEndPhase } from "#app/phases/turn-end-phase";
|
import { TurnEndPhase } from "#app/phases/turn-end-phase";
|
||||||
import { TurnInitPhase } from "#app/phases/turn-init-phase";
|
import { TurnInitPhase } from "#app/phases/turn-init-phase";
|
||||||
import { TurnStartPhase } from "#app/phases/turn-start-phase";
|
import { TurnStartPhase } from "#app/phases/turn-start-phase";
|
||||||
import ErrorInterceptor from "#test/testUtils/errorInterceptor";
|
|
||||||
import type InputsHandler from "#test/testUtils/inputsHandler";
|
|
||||||
import type BallUiHandler from "#app/ui/ball-ui-handler";
|
import type BallUiHandler from "#app/ui/ball-ui-handler";
|
||||||
import type BattleMessageUiHandler from "#app/ui/battle-message-ui-handler";
|
import type BattleMessageUiHandler from "#app/ui/battle-message-ui-handler";
|
||||||
import type CommandUiHandler from "#app/ui/command-ui-handler";
|
import type CommandUiHandler from "#app/ui/command-ui-handler";
|
||||||
import type ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
|
import type ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
|
||||||
import type PartyUiHandler from "#app/ui/party-ui-handler";
|
import type PartyUiHandler from "#app/ui/party-ui-handler";
|
||||||
|
import type StarterSelectUiHandler from "#app/ui/starter-select-ui-handler";
|
||||||
import type TargetSelectUiHandler from "#app/ui/target-select-ui-handler";
|
import type TargetSelectUiHandler from "#app/ui/target-select-ui-handler";
|
||||||
import { UiMode } from "#enums/ui-mode";
|
|
||||||
import { isNullOrUndefined } from "#app/utils/common";
|
import { isNullOrUndefined } from "#app/utils/common";
|
||||||
import { BattleStyle } from "#enums/battle-style";
|
import { BattleStyle } from "#enums/battle-style";
|
||||||
import { Button } from "#enums/buttons";
|
import { Button } from "#enums/buttons";
|
||||||
@ -40,24 +39,26 @@ import type { Moves } from "#enums/moves";
|
|||||||
import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
import { PlayerGender } from "#enums/player-gender";
|
import { PlayerGender } from "#enums/player-gender";
|
||||||
import type { Species } from "#enums/species";
|
import type { Species } from "#enums/species";
|
||||||
|
import { UiMode } from "#enums/ui-mode";
|
||||||
|
import ErrorInterceptor from "#test/testUtils/errorInterceptor";
|
||||||
import { generateStarter, waitUntil } from "#test/testUtils/gameManagerUtils";
|
import { generateStarter, waitUntil } from "#test/testUtils/gameManagerUtils";
|
||||||
import GameWrapper from "#test/testUtils/gameWrapper";
|
import GameWrapper from "#test/testUtils/gameWrapper";
|
||||||
import { ChallengeModeHelper } from "#test/testUtils/helpers/challengeModeHelper";
|
import { ChallengeModeHelper } from "#test/testUtils/helpers/challengeModeHelper";
|
||||||
import { ClassicModeHelper } from "#test/testUtils/helpers/classicModeHelper";
|
import { ClassicModeHelper } from "#test/testUtils/helpers/classicModeHelper";
|
||||||
import { DailyModeHelper } from "#test/testUtils/helpers/dailyModeHelper";
|
import { DailyModeHelper } from "#test/testUtils/helpers/dailyModeHelper";
|
||||||
|
import { FieldHelper } from "#test/testUtils/helpers/field-helper";
|
||||||
import { ModifierHelper } from "#test/testUtils/helpers/modifiersHelper";
|
import { ModifierHelper } from "#test/testUtils/helpers/modifiersHelper";
|
||||||
import { MoveHelper } from "#test/testUtils/helpers/moveHelper";
|
import { MoveHelper } from "#test/testUtils/helpers/moveHelper";
|
||||||
import { OverridesHelper } from "#test/testUtils/helpers/overridesHelper";
|
import { OverridesHelper } from "#test/testUtils/helpers/overridesHelper";
|
||||||
import { ReloadHelper } from "#test/testUtils/helpers/reloadHelper";
|
import { ReloadHelper } from "#test/testUtils/helpers/reloadHelper";
|
||||||
import { SettingsHelper } from "#test/testUtils/helpers/settingsHelper";
|
import { SettingsHelper } from "#test/testUtils/helpers/settingsHelper";
|
||||||
|
import type InputsHandler from "#test/testUtils/inputsHandler";
|
||||||
|
import { MockFetch } from "#test/testUtils/mocks/mockFetch";
|
||||||
import PhaseInterceptor from "#test/testUtils/phaseInterceptor";
|
import PhaseInterceptor from "#test/testUtils/phaseInterceptor";
|
||||||
import TextInterceptor from "#test/testUtils/TextInterceptor";
|
import TextInterceptor from "#test/testUtils/TextInterceptor";
|
||||||
import { AES, enc } from "crypto-js";
|
import { AES, enc } from "crypto-js";
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import { expect, vi } from "vitest";
|
import { expect, vi } from "vitest";
|
||||||
import { globalScene } from "#app/global-scene";
|
|
||||||
import type StarterSelectUiHandler from "#app/ui/starter-select-ui-handler";
|
|
||||||
import { MockFetch } from "#test/testUtils/mocks/mockFetch";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class to manage the game state and transitions between phases.
|
* Class to manage the game state and transitions between phases.
|
||||||
@ -76,6 +77,7 @@ export default class GameManager {
|
|||||||
public readonly settings: SettingsHelper;
|
public readonly settings: SettingsHelper;
|
||||||
public readonly reload: ReloadHelper;
|
public readonly reload: ReloadHelper;
|
||||||
public readonly modifiers: ModifierHelper;
|
public readonly modifiers: ModifierHelper;
|
||||||
|
public readonly field: FieldHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance of GameManager.
|
* Creates an instance of GameManager.
|
||||||
@ -123,6 +125,7 @@ export default class GameManager {
|
|||||||
this.settings = new SettingsHelper(this);
|
this.settings = new SettingsHelper(this);
|
||||||
this.reload = new ReloadHelper(this);
|
this.reload = new ReloadHelper(this);
|
||||||
this.modifiers = new ModifierHelper(this);
|
this.modifiers = new ModifierHelper(this);
|
||||||
|
this.field = new FieldHelper(this);
|
||||||
this.override.sanitizeOverrides();
|
this.override.sanitizeOverrides();
|
||||||
|
|
||||||
// Disables Mystery Encounters on all tests (can be overridden at test level)
|
// Disables Mystery Encounters on all tests (can be overridden at test level)
|
||||||
@ -383,6 +386,7 @@ export default class GameManager {
|
|||||||
* @param moveId - The {@linkcode Moves | move} the enemy will use
|
* @param moveId - The {@linkcode Moves | move} the enemy will use
|
||||||
* @param target - The {@linkcode BattlerIndex} of the target against which the enemy will use the given move;
|
* @param target - The {@linkcode BattlerIndex} of the target against which the enemy will use the given move;
|
||||||
* will use normal target selection priorities if omitted.
|
* will use normal target selection priorities if omitted.
|
||||||
|
* @deprecated Use {@linkcode MoveHelper.forceEnemyMove} or {@linkcode MoveHelper.selectEnemyMove}
|
||||||
*/
|
*/
|
||||||
async forceEnemyMove(moveId: Moves, target?: BattlerIndex) {
|
async forceEnemyMove(moveId: Moves, target?: BattlerIndex) {
|
||||||
// Wait for the next EnemyCommandPhase to start
|
// Wait for the next EnemyCommandPhase to start
|
||||||
@ -417,9 +421,15 @@ export default class GameManager {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Transition to the next upcoming {@linkcode CommandPhase} */
|
/** Transition to the first {@linkcode CommandPhase} of the next turn. */
|
||||||
async toNextTurn() {
|
async toNextTurn() {
|
||||||
await this.phaseInterceptor.to(CommandPhase);
|
await this.phaseInterceptor.to("TurnInitPhase");
|
||||||
|
await this.phaseInterceptor.to("CommandPhase");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Transition to the {@linkcode TurnEndPhase | end of the current turn}. */
|
||||||
|
async toEndOfTurn() {
|
||||||
|
await this.phaseInterceptor.to("TurnEndPhase");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
87
test/testUtils/helpers/field-helper.ts
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
// -- start tsdoc imports --
|
||||||
|
// biome-ignore lint/correctness/noUnusedImports: TSDoc import
|
||||||
|
import type { globalScene } from "#app/global-scene";
|
||||||
|
// -- end tsdoc imports --
|
||||||
|
|
||||||
|
import type { BattlerIndex } from "#app/battle";
|
||||||
|
import type { Ability } from "#app/data/abilities/ability-class";
|
||||||
|
import { allAbilities } from "#app/data/data-lists";
|
||||||
|
import type Pokemon from "#app/field/pokemon";
|
||||||
|
import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
|
||||||
|
import type { Abilities } from "#enums/abilities";
|
||||||
|
import type { PokemonType } from "#enums/pokemon-type";
|
||||||
|
import { Stat } from "#enums/stat";
|
||||||
|
import { GameManagerHelper } from "#test/testUtils/helpers/gameManagerHelper";
|
||||||
|
import { expect, type MockInstance, vi } from "vitest";
|
||||||
|
|
||||||
|
/** Helper to manage pokemon */
|
||||||
|
export class FieldHelper extends GameManagerHelper {
|
||||||
|
/**
|
||||||
|
* Passthrough for {@linkcode globalScene.getPlayerPokemon} that adds an `undefined` check for
|
||||||
|
* the Pokemon so that the return type for the function doesn't have `undefined`.
|
||||||
|
* This removes the need to add a `!` like when calling `game.scene.getPlayerPokemon()!`.
|
||||||
|
* @param includeSwitching Whether a pokemon that is currently switching out is valid, default `true`
|
||||||
|
* @returns The first {@linkcode PlayerPokemon} that is {@linkcode globalScene.getPlayerField on the field}
|
||||||
|
* and {@linkcode PlayerPokemon.isActive is active}
|
||||||
|
* (aka {@linkcode PlayerPokemon.isAllowedInBattle is allowed in battle}).
|
||||||
|
*/
|
||||||
|
public getPlayerPokemon(includeSwitching = true): PlayerPokemon {
|
||||||
|
const pokemon = this.game.scene.getPlayerPokemon(includeSwitching);
|
||||||
|
expect(pokemon).toBeDefined();
|
||||||
|
return pokemon!;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Passthrough for {@linkcode globalScene.getEnemyPokemon} that adds an `undefined` check for
|
||||||
|
* the Pokemon so that the return type for the function doesn't have `undefined`.
|
||||||
|
* This removes the need to add a `!` like when calling `game.scene.getEnemyPokemon()!`.
|
||||||
|
* @param includeSwitching Whether a pokemon that is currently switching out is valid, default `true`
|
||||||
|
* @returns The first {@linkcode EnemyPokemon} that is {@linkcode globalScene.getEnemyField on the field}
|
||||||
|
* and {@linkcode EnemyPokemon.isActive is active}
|
||||||
|
* (aka {@linkcode EnemyPokemon.isAllowedInBattle is allowed in battle}).
|
||||||
|
*/
|
||||||
|
public getEnemyPokemon(includeSwitching = true): EnemyPokemon {
|
||||||
|
const pokemon = this.game.scene.getEnemyPokemon(includeSwitching);
|
||||||
|
expect(pokemon).toBeDefined();
|
||||||
|
return pokemon!;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns The {@linkcode BattlerIndex | indexes} of Pokemon on the field in order of decreasing Speed.
|
||||||
|
* Speed ties are returned in increasing order of index.
|
||||||
|
*
|
||||||
|
* Note: Trick Room does not modify the speed of Pokemon on the field.
|
||||||
|
*/
|
||||||
|
public getSpeedOrder(): BattlerIndex[] {
|
||||||
|
return this.game.scene
|
||||||
|
.getField(true)
|
||||||
|
.sort((pA, pB) => pB.getEffectiveStat(Stat.SPD) - pA.getEffectiveStat(Stat.SPD))
|
||||||
|
.map(p => p.getBattlerIndex());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mocks a pokemon's ability, overriding its existing ability (takes precedence over global overrides)
|
||||||
|
* @param pokemon - The pokemon to mock the ability of
|
||||||
|
* @param ability - The ability to be mocked
|
||||||
|
* @returns A {@linkcode MockInstance} object
|
||||||
|
* @see {@linkcode vi.spyOn}
|
||||||
|
* @see https://vitest.dev/api/mock#mockreturnvalue
|
||||||
|
*/
|
||||||
|
public mockAbility(pokemon: Pokemon, ability: Abilities): MockInstance<(baseOnly?: boolean) => Ability> {
|
||||||
|
return vi.spyOn(pokemon, "getAbility").mockReturnValue(allAbilities[ability]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forces a pokemon to be terastallized. Defaults to the pokemon's primary type if not specified.
|
||||||
|
*
|
||||||
|
* This function only mocks the Pokemon's tera-related variables; it does NOT activate any tera-related abilities.
|
||||||
|
*
|
||||||
|
* @param pokemon - The pokemon to terastallize.
|
||||||
|
* @param teraType - (optional) The {@linkcode PokemonType} to terastallize it as.
|
||||||
|
*/
|
||||||
|
public forceTera(pokemon: Pokemon, teraType?: PokemonType): void {
|
||||||
|
vi.spyOn(pokemon, "isTerastallized", "get").mockReturnValue(true);
|
||||||
|
teraType ??= pokemon.getSpeciesForm(true).type1;
|
||||||
|
vi.spyOn(pokemon, "teraType", "get").mockReturnValue(teraType);
|
||||||
|
}
|
||||||
|
}
|
@ -1,14 +1,16 @@
|
|||||||
import type { BattlerIndex } from "#app/battle";
|
import type { BattlerIndex } from "#app/battle";
|
||||||
|
import { getMoveTargets } from "#app/data/moves/move";
|
||||||
import { Button } from "#app/enums/buttons";
|
import { Button } from "#app/enums/buttons";
|
||||||
import type Pokemon from "#app/field/pokemon";
|
import type Pokemon from "#app/field/pokemon";
|
||||||
import { PokemonMove } from "#app/field/pokemon";
|
import { PokemonMove } from "#app/field/pokemon";
|
||||||
import Overrides from "#app/overrides";
|
import Overrides from "#app/overrides";
|
||||||
import type { CommandPhase } from "#app/phases/command-phase";
|
import type { CommandPhase } from "#app/phases/command-phase";
|
||||||
|
import type { EnemyCommandPhase } from "#app/phases/enemy-command-phase";
|
||||||
import { LearnMovePhase } from "#app/phases/learn-move-phase";
|
import { LearnMovePhase } from "#app/phases/learn-move-phase";
|
||||||
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
||||||
import { Command } from "#app/ui/command-ui-handler";
|
import { Command } from "#app/ui/command-ui-handler";
|
||||||
import { UiMode } from "#enums/ui-mode";
|
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
|
import { UiMode } from "#enums/ui-mode";
|
||||||
import { getMovePosition } from "#test/testUtils/gameManagerUtils";
|
import { getMovePosition } from "#test/testUtils/gameManagerUtils";
|
||||||
import { GameManagerHelper } from "#test/testUtils/helpers/gameManagerHelper";
|
import { GameManagerHelper } from "#test/testUtils/helpers/gameManagerHelper";
|
||||||
import { vi } from "vitest";
|
import { vi } from "vitest";
|
||||||
@ -92,6 +94,35 @@ export class MoveHelper extends GameManagerHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modifies a player pokemon's moveset to contain only the selected move and then
|
||||||
|
* selects it to be used during the next {@linkcode CommandPhase}.
|
||||||
|
*
|
||||||
|
* Warning: Will disable the player moveset override if it is enabled!
|
||||||
|
*
|
||||||
|
* Note: If you need to check for changes in the player's moveset as part of the test, it may be
|
||||||
|
* best to use {@linkcode changeMoveset} and {@linkcode select} instead.
|
||||||
|
* @param moveId - the move to use
|
||||||
|
* @param pkmIndex - the pokemon index. Relevant for double-battles only (defaults to 0)
|
||||||
|
* @param targetIndex - (optional) The {@linkcode BattlerIndex} of the Pokemon to target for single-target moves, or `null` if a manual call to `selectTarget()` is required
|
||||||
|
* @param useTera - If `true`, the Pokemon also chooses to Terastallize. This does not require a Tera Orb. Default: `false`.
|
||||||
|
*/
|
||||||
|
public use(moveId: Moves, pkmIndex: 0 | 1 = 0, targetIndex?: BattlerIndex | null, useTera = false): void {
|
||||||
|
if ([Overrides.MOVESET_OVERRIDE].flat().length > 0) {
|
||||||
|
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([]);
|
||||||
|
console.warn("Warning: `use` overwrites the Pokemon's moveset and disables the player moveset override!");
|
||||||
|
}
|
||||||
|
|
||||||
|
const pokemon = this.game.scene.getPlayerField()[pkmIndex];
|
||||||
|
pokemon.moveset = [new PokemonMove(moveId)];
|
||||||
|
|
||||||
|
if (useTera) {
|
||||||
|
this.selectWithTera(moveId, pkmIndex, targetIndex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.select(moveId, pkmIndex, targetIndex);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Forces the Paralysis or Freeze status to activate on the next move by temporarily mocking {@linkcode Overrides.STATUS_ACTIVATION_OVERRIDE},
|
* Forces the Paralysis or Freeze status to activate on the next move by temporarily mocking {@linkcode Overrides.STATUS_ACTIVATION_OVERRIDE},
|
||||||
* advancing to the next `MovePhase`, and then resetting the override to `null`
|
* advancing to the next `MovePhase`, and then resetting the override to `null`
|
||||||
@ -132,6 +163,77 @@ export class MoveHelper extends GameManagerHelper {
|
|||||||
console.log(`Pokemon ${pokemon.species.name}'s moveset manually set to ${movesetStr} (=[${moveset.join(", ")}])!`);
|
console.log(`Pokemon ${pokemon.species.name}'s moveset manually set to ${movesetStr} (=[${moveset.join(", ")}])!`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forces the next enemy selecting a move to use the given move in its moveset
|
||||||
|
* against the given target (if applicable).
|
||||||
|
* @param moveId The {@linkcode MoveId | move} the enemy will use
|
||||||
|
* @param target (Optional) the {@linkcode BattlerIndex | target} which the enemy will use the given move against
|
||||||
|
*/
|
||||||
|
public async selectEnemyMove(moveId: Moves, target?: BattlerIndex) {
|
||||||
|
// Wait for the next EnemyCommandPhase to start
|
||||||
|
await this.game.phaseInterceptor.to("EnemyCommandPhase", false);
|
||||||
|
const enemy =
|
||||||
|
this.game.scene.getEnemyField()[(this.game.scene.getCurrentPhase() as EnemyCommandPhase).getFieldIndex()];
|
||||||
|
const legalTargets = getMoveTargets(enemy, moveId);
|
||||||
|
|
||||||
|
vi.spyOn(enemy, "getNextMove").mockReturnValueOnce({
|
||||||
|
move: moveId,
|
||||||
|
targets:
|
||||||
|
target !== undefined && !legalTargets.multiple && legalTargets.targets.includes(target)
|
||||||
|
? [target]
|
||||||
|
: enemy.getNextTargets(moveId),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the EnemyCommandPhase to completion.
|
||||||
|
* This allows this function to be called consecutively to
|
||||||
|
* force a move for each enemy in a double battle.
|
||||||
|
*/
|
||||||
|
await this.game.phaseInterceptor.to("EnemyCommandPhase");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forces the next enemy selecting a move to use the given move against the given target (if applicable).
|
||||||
|
*
|
||||||
|
* Warning: Overwrites the pokemon's moveset and disables the moveset override!
|
||||||
|
*
|
||||||
|
* Note: If you need to check for changes in the enemy's moveset as part of the test, it may be
|
||||||
|
* best to use {@linkcode changeMoveset} and {@linkcode selectEnemyMove} instead.
|
||||||
|
* @param moveId The {@linkcode MoveId | move} the enemy will use
|
||||||
|
* @param target (Optional) the {@linkcode BattlerIndex | target} which the enemy will use the given move against
|
||||||
|
*/
|
||||||
|
public async forceEnemyMove(moveId: Moves, target?: BattlerIndex) {
|
||||||
|
// Wait for the next EnemyCommandPhase to start
|
||||||
|
await this.game.phaseInterceptor.to("EnemyCommandPhase", false);
|
||||||
|
|
||||||
|
const enemy =
|
||||||
|
this.game.scene.getEnemyField()[(this.game.scene.getCurrentPhase() as EnemyCommandPhase).getFieldIndex()];
|
||||||
|
|
||||||
|
if ([Overrides.OPP_MOVESET_OVERRIDE].flat().length > 0) {
|
||||||
|
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([]);
|
||||||
|
console.warn(
|
||||||
|
"Warning: `forceEnemyMove` overwrites the Pokemon's moveset and disables the enemy moveset override!",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
enemy.moveset = [new PokemonMove(moveId)];
|
||||||
|
const legalTargets = getMoveTargets(enemy, moveId);
|
||||||
|
|
||||||
|
vi.spyOn(enemy, "getNextMove").mockReturnValueOnce({
|
||||||
|
move: moveId,
|
||||||
|
targets:
|
||||||
|
target !== undefined && !legalTargets.multiple && legalTargets.targets.includes(target)
|
||||||
|
? [target]
|
||||||
|
: enemy.getNextTargets(moveId),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the EnemyCommandPhase to completion.
|
||||||
|
* This allows this function to be called consecutively to
|
||||||
|
* force a move for each enemy in a double battle.
|
||||||
|
*/
|
||||||
|
await this.game.phaseInterceptor.to("EnemyCommandPhase");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simulates learning a move for a player pokemon.
|
* Simulates learning a move for a player pokemon.
|
||||||
* @param move The {@linkcode Moves} being learnt
|
* @param move The {@linkcode Moves} being learnt
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
export interface MockGameObject {
|
export interface MockGameObject {
|
||||||
name: string;
|
name: string;
|
||||||
|
active: boolean;
|
||||||
|
destroy?(): void;
|
||||||
|
setActive(active: boolean): this;
|
||||||
|
setName(name: string): this;
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,20 @@ import type { MockGameObject } from "./mockGameObject";
|
|||||||
/** Mocks video-related stuff */
|
/** Mocks video-related stuff */
|
||||||
export class MockVideoGameObject implements MockGameObject {
|
export class MockVideoGameObject implements MockGameObject {
|
||||||
public name: string;
|
public name: string;
|
||||||
|
public active = true;
|
||||||
|
|
||||||
public play = () => null;
|
public play = () => null;
|
||||||
public stop = () => this;
|
public stop = () => this;
|
||||||
public setOrigin = () => null;
|
public setOrigin = () => this;
|
||||||
public setScale = () => null;
|
public setScale = () => this;
|
||||||
public setVisible = () => null;
|
public setVisible = () => this;
|
||||||
public setLoop = () => null;
|
public setLoop = () => this;
|
||||||
|
public setName = (name: string) => {
|
||||||
|
this.name = name;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
public setActive = (active: boolean) => {
|
||||||
|
this.active = active;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -2,199 +2,257 @@ import type MockTextureManager from "#test/testUtils/mocks/mockTextureManager";
|
|||||||
import type { MockGameObject } from "../mockGameObject";
|
import type { MockGameObject } from "../mockGameObject";
|
||||||
|
|
||||||
export default class MockContainer implements MockGameObject {
|
export default class MockContainer implements MockGameObject {
|
||||||
protected x;
|
protected x: number;
|
||||||
protected y;
|
protected y: number;
|
||||||
protected scene;
|
protected scene;
|
||||||
protected width;
|
protected width: number;
|
||||||
protected height;
|
protected height: number;
|
||||||
protected visible;
|
protected visible: boolean;
|
||||||
private alpha;
|
private alpha: number;
|
||||||
private style;
|
private style;
|
||||||
public frame;
|
public frame;
|
||||||
protected textureManager;
|
protected textureManager;
|
||||||
public list: MockGameObject[] = [];
|
public list: MockGameObject[] = [];
|
||||||
public name: string;
|
public name: string;
|
||||||
|
public active = true;
|
||||||
|
|
||||||
constructor(textureManager: MockTextureManager, x, y) {
|
constructor(textureManager: MockTextureManager, x: number, y: number) {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
this.frame = {};
|
this.frame = {};
|
||||||
this.textureManager = textureManager;
|
this.textureManager = textureManager;
|
||||||
}
|
}
|
||||||
setVisible(visible) {
|
setVisible(visible: boolean): this {
|
||||||
this.visible = visible;
|
this.visible = visible;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
once(_event, _callback, _source) {}
|
once(_event, _callback, _source): this {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
off(_event, _callback, _source) {}
|
off(_event, _callback, _source): this {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
removeFromDisplayList() {
|
removeFromDisplayList(): this {
|
||||||
// same as remove or destroy
|
// same as remove or destroy
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
removeBetween(_startIndex, _endIndex, _destroyChild) {
|
removeBetween(_startIndex, _endIndex, _destroyChild): this {
|
||||||
// Removes multiple children across an index range
|
// Removes multiple children across an index range
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
addedToScene() {
|
addedToScene() {
|
||||||
// This callback is invoked when this Game Object is added to a Scene.
|
// This callback is invoked when this Game Object is added to a Scene.
|
||||||
}
|
}
|
||||||
|
|
||||||
setSize(_width, _height) {
|
setSize(_width: number, _height: number): this {
|
||||||
// Sets the size of this Game Object.
|
// Sets the size of this Game Object.
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setMask() {
|
setMask(): this {
|
||||||
/// Sets the mask that this Game Object will use to render with.
|
/// Sets the mask that this Game Object will use to render with.
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setPositionRelative(_source, _x, _y) {
|
setPositionRelative(_source, _x, _y): this {
|
||||||
/// Sets the position of this Game Object to be a relative position from the source Game Object.
|
/// Sets the position of this Game Object to be a relative position from the source Game Object.
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setInteractive = () => null;
|
setInteractive(): this {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
setOrigin(x, y) {
|
setOrigin(x = 0.5, y = x): this {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setAlpha(alpha) {
|
setAlpha(alpha = 1): this {
|
||||||
this.alpha = alpha;
|
this.alpha = alpha;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setFrame(_frame, _updateSize?: boolean, _updateOrigin?: boolean) {
|
setFrame(_frame, _updateSize?: boolean, _updateOrigin?: boolean): this {
|
||||||
// Sets the frame this Game Object will use to render with.
|
// Sets the frame this Game Object will use to render with.
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setScale(_scale) {
|
setScale(_x = 1, _y = _x): this {
|
||||||
// Sets the scale of this Game Object.
|
// Sets the scale of this Game Object.
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setPosition(x, y) {
|
setPosition(x = 0, y = x, _z = 0, _w = 0): this {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setX(x) {
|
setX(x = 0): this {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setY(y) {
|
setY(y = 0): this {
|
||||||
this.y = y;
|
this.y = y;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this.list = [];
|
this.list = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
setShadow(_shadowXpos, _shadowYpos, _shadowColor) {
|
setShadow(_shadowXpos, _shadowYpos, _shadowColor): this {
|
||||||
// Sets the shadow settings for this Game Object.
|
// Sets the shadow settings for this Game Object.
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setLineSpacing(_lineSpacing) {
|
setLineSpacing(_lineSpacing): this {
|
||||||
// Sets the line spacing value of this Game Object.
|
// Sets the line spacing value of this Game Object.
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setText(_text) {
|
setText(_text): this {
|
||||||
// Sets the text this Game Object will display.
|
// Sets the text this Game Object will display.
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setAngle(_angle) {
|
setAngle(_angle): this {
|
||||||
// Sets the angle of this Game Object.
|
// Sets the angle of this Game Object.
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setShadowOffset(_offsetX, _offsetY) {
|
setShadowOffset(_offsetX, _offsetY): this {
|
||||||
// Sets the shadow offset values.
|
// Sets the shadow offset values.
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setWordWrapWidth(_width) {
|
setWordWrapWidth(_width) {
|
||||||
// Sets the width (in pixels) to use for wrapping lines.
|
// Sets the width (in pixels) to use for wrapping lines.
|
||||||
}
|
}
|
||||||
|
|
||||||
setFontSize(_fontSize) {
|
setFontSize(_fontSize): this {
|
||||||
// Sets the font size of this Game Object.
|
// Sets the font size of this Game Object.
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
getBounds() {
|
getBounds() {
|
||||||
return { width: this.width, height: this.height };
|
return { width: this.width, height: this.height };
|
||||||
}
|
}
|
||||||
|
|
||||||
setColor(_color) {
|
setColor(_color): this {
|
||||||
// Sets the tint of this Game Object.
|
// Sets the tint of this Game Object.
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setShadowColor(_color) {
|
setShadowColor(_color): this {
|
||||||
// Sets the shadow color.
|
// Sets the shadow color.
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setTint(_color) {
|
setTint(_color: this) {
|
||||||
// Sets the tint of this Game Object.
|
// Sets the tint of this Game Object.
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setStrokeStyle(_thickness, _color) {
|
setStrokeStyle(_thickness, _color): this {
|
||||||
// Sets the stroke style for the graphics.
|
// Sets the stroke style for the graphics.
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setDepth(_depth) {
|
setDepth(_depth): this {
|
||||||
// Sets the depth of this Game Object.
|
// Sets the depth of this Game Object.\
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setTexture(_texture) {
|
setTexture(_texture): this {
|
||||||
// Sets the texture this Game Object will use to render with.
|
// Sets the texture this Game Object will use to render with.\
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
clearTint() {
|
clearTint(): this {
|
||||||
// Clears any previously set tint.
|
// Clears any previously set tint.\
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
sendToBack() {
|
sendToBack(): this {
|
||||||
// Sends this Game Object to the back of its parent's display list.
|
// Sends this Game Object to the back of its parent's display list.\
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
moveTo(_obj) {
|
moveTo(_obj): this {
|
||||||
// Moves this Game Object to the given index in the list.
|
// Moves this Game Object to the given index in the list.\
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
moveAbove(_obj) {
|
moveAbove(_obj): this {
|
||||||
// Moves this Game Object to be above the given Game Object in the display list.
|
// Moves this Game Object to be above the given Game Object in the display list.
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
moveBelow(_obj) {
|
moveBelow(_obj): this {
|
||||||
// Moves this Game Object to be below the given Game Object in the display list.
|
// Moves this Game Object to be below the given Game Object in the display list.
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setName(name: string) {
|
setName(name: string): this {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
bringToTop(_obj) {
|
bringToTop(_obj): this {
|
||||||
// Brings this Game Object to the top of its parents display list.
|
// Brings this Game Object to the top of its parents display list.
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
on(_event, _callback, _source) {}
|
on(_event, _callback, _source): this {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
add(obj) {
|
add(obj: MockGameObject | MockGameObject[]): this {
|
||||||
// Adds a child to this Game Object.
|
if (Array.isArray(obj)) {
|
||||||
|
this.list.push(...obj);
|
||||||
|
} else {
|
||||||
this.list.push(obj);
|
this.list.push(obj);
|
||||||
}
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
removeAll() {
|
removeAll(): this {
|
||||||
// Removes all Game Objects from this Container.
|
// Removes all Game Objects from this Container.
|
||||||
this.list = [];
|
this.list = [];
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
addAt(obj, index) {
|
addAt(obj: MockGameObject | MockGameObject[], index = 0): this {
|
||||||
// Adds a Game Object to this Container at the given index.
|
// Adds a Game Object to this Container at the given index.
|
||||||
this.list.splice(index, 0, obj);
|
if (!Array.isArray(obj)) {
|
||||||
|
obj = [obj];
|
||||||
|
}
|
||||||
|
this.list.splice(index, 0, ...obj);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(obj) {
|
remove(obj: MockGameObject | MockGameObject[], destroyChild = false): this {
|
||||||
const index = this.list.indexOf(obj);
|
if (!Array.isArray(obj)) {
|
||||||
|
obj = [obj];
|
||||||
|
}
|
||||||
|
for (const item of obj) {
|
||||||
|
const index = this.list.indexOf(item);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
this.list.splice(index, 1);
|
this.list.splice(index, 1);
|
||||||
}
|
}
|
||||||
|
if (destroyChild) {
|
||||||
|
item.destroy?.();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
getIndex(obj) {
|
getIndex(obj) {
|
||||||
@ -210,15 +268,48 @@ export default class MockContainer implements MockGameObject {
|
|||||||
return this.list;
|
return this.list;
|
||||||
}
|
}
|
||||||
|
|
||||||
getByName(key: string) {
|
getByName(key: string): MockGameObject | null {
|
||||||
return this.list.find(v => v.name === key) ?? new MockContainer(this.textureManager, 0, 0);
|
return this.list.find(v => v.name === key) ?? new MockContainer(this.textureManager, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
disableInteractive = () => null;
|
disableInteractive(): this {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
each(method) {
|
// biome-ignore lint/complexity/noBannedTypes: This matches the signature of the method it mocks
|
||||||
|
each(callback: Function, context?: object, ...args: any[]): this {
|
||||||
|
if (context !== undefined) {
|
||||||
|
callback = callback.bind(context);
|
||||||
|
}
|
||||||
|
for (const item of this.list.slice()) {
|
||||||
|
callback(item, ...args);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// biome-ignore lint/complexity/noBannedTypes: This matches the signature of the method it mocks
|
||||||
|
iterate(callback: Function, context?: object, ...args: any[]): this {
|
||||||
|
if (context !== undefined) {
|
||||||
|
callback = callback.bind(context);
|
||||||
|
}
|
||||||
for (const item of this.list) {
|
for (const item of this.list) {
|
||||||
method(item);
|
callback(item, ...args);
|
||||||
}
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
copyPosition(source: { x?: number; y?: number }): this {
|
||||||
|
if (source.x !== undefined) {
|
||||||
|
this.x = source.x;
|
||||||
|
}
|
||||||
|
if (source.y !== undefined) {
|
||||||
|
this.y = source.y;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
setActive(active: boolean): this {
|
||||||
|
this.active = active;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,61 +4,81 @@ export default class MockGraphics implements MockGameObject {
|
|||||||
private scene;
|
private scene;
|
||||||
public list: MockGameObject[] = [];
|
public list: MockGameObject[] = [];
|
||||||
public name: string;
|
public name: string;
|
||||||
|
public active = true;
|
||||||
constructor(textureManager, _config) {
|
constructor(textureManager, _config) {
|
||||||
this.scene = textureManager.scene;
|
this.scene = textureManager.scene;
|
||||||
}
|
}
|
||||||
|
|
||||||
fillStyle(_color) {
|
fillStyle(_color): this {
|
||||||
// Sets the fill style to be used by the fill methods.
|
// Sets the fill style to be used by the fill methods.
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
beginPath() {
|
beginPath(): this {
|
||||||
// Starts a new path by emptying the list of sub-paths. Call this method when you want to create a new path.
|
// Starts a new path by emptying the list of sub-paths. Call this method when you want to create a new path.
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
fillRect(_x, _y, _width, _height) {
|
fillRect(_x, _y, _width, _height): this {
|
||||||
// Adds a rectangle shape to the path which is filled when you call fill().
|
// Adds a rectangle shape to the path which is filled when you call fill().
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
createGeometryMask() {
|
createGeometryMask(): this {
|
||||||
// Creates a geometry mask.
|
// Creates a geometry mask.
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setOrigin(_x, _y) {}
|
setOrigin(_x, _y): this {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
setAlpha(_alpha) {}
|
setAlpha(_alpha): this {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
setVisible(_visible) {}
|
setVisible(_visible): this {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
setName(_name) {}
|
setName(_name) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
once(_event, _callback, _source) {}
|
once(_event, _callback, _source) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
removeFromDisplayList() {
|
removeFromDisplayList(): this {
|
||||||
// same as remove or destroy
|
// same as remove or destroy
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
addedToScene() {
|
addedToScene() {
|
||||||
// This callback is invoked when this Game Object is added to a Scene.
|
// This callback is invoked when this Game Object is added to a Scene.
|
||||||
}
|
}
|
||||||
|
|
||||||
setPositionRelative(_source, _x, _y) {
|
setPositionRelative(_source, _x, _y): this {
|
||||||
/// Sets the position of this Game Object to be a relative position from the source Game Object.
|
/// Sets the position of this Game Object to be a relative position from the source Game Object.
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this.list = [];
|
this.list = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
setScale(_scale) {
|
setScale(_scale): this {
|
||||||
// Sets the scale of this Game Object.
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
off(_event, _callback, _source) {}
|
off(_event, _callback, _source): this {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
add(obj) {
|
add(obj): this {
|
||||||
// Adds a child to this Game Object.
|
// Adds a child to this Game Object.
|
||||||
this.list.push(obj);
|
this.list.push(obj);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
removeAll() {
|
removeAll() {
|
||||||
@ -90,4 +110,13 @@ export default class MockGraphics implements MockGameObject {
|
|||||||
getAll() {
|
getAll() {
|
||||||
return this.list;
|
return this.list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
copyPosition(_source): this {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
setActive(active: boolean): this {
|
||||||
|
this.active = active;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,56 +5,76 @@ export default class MockRectangle implements MockGameObject {
|
|||||||
private scene;
|
private scene;
|
||||||
public list: MockGameObject[] = [];
|
public list: MockGameObject[] = [];
|
||||||
public name: string;
|
public name: string;
|
||||||
|
public active = true;
|
||||||
|
|
||||||
constructor(textureManager, _x, _y, _width, _height, fillColor) {
|
constructor(textureManager, _x, _y, _width, _height, fillColor) {
|
||||||
this.fillColor = fillColor;
|
this.fillColor = fillColor;
|
||||||
this.scene = textureManager.scene;
|
this.scene = textureManager.scene;
|
||||||
}
|
}
|
||||||
setOrigin(_x, _y) {}
|
setOrigin(_x, _y): this {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
setAlpha(_alpha) {}
|
setAlpha(_alpha): this {
|
||||||
setVisible(_visible) {}
|
return this;
|
||||||
|
}
|
||||||
|
setVisible(_visible): this {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
setName(_name) {}
|
setName(_name): this {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
once(_event, _callback, _source) {}
|
once(_event, _callback, _source): this {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
removeFromDisplayList() {
|
removeFromDisplayList(): this {
|
||||||
// same as remove or destroy
|
// same as remove or destroy
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
addedToScene() {
|
addedToScene() {
|
||||||
// This callback is invoked when this Game Object is added to a Scene.
|
// This callback is invoked when this Game Object is added to a Scene.
|
||||||
}
|
}
|
||||||
|
|
||||||
setPositionRelative(_source, _x, _y) {
|
setPositionRelative(_source, _x, _y): this {
|
||||||
/// Sets the position of this Game Object to be a relative position from the source Game Object.
|
/// Sets the position of this Game Object to be a relative position from the source Game Object.
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this.list = [];
|
this.list = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
add(obj) {
|
add(obj: MockGameObject | MockGameObject[]): this {
|
||||||
// Adds a child to this Game Object.
|
// Adds a child to this Game Object.
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
this.list.push(...obj);
|
||||||
|
} else {
|
||||||
this.list.push(obj);
|
this.list.push(obj);
|
||||||
}
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
removeAll() {
|
removeAll() {
|
||||||
// Removes all Game Objects from this Container.
|
// Removes all Game Objects from this Container.
|
||||||
this.list = [];
|
this.list = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
addAt(obj, index) {
|
addAt(obj, index): this {
|
||||||
// Adds a Game Object to this Container at the given index.
|
// Adds a Game Object to this Container at the given index.
|
||||||
this.list.splice(index, 0, obj);
|
this.list.splice(index, 0, obj);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(obj) {
|
remove(obj): this {
|
||||||
const index = this.list.indexOf(obj);
|
const index = this.list.indexOf(obj);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
this.list.splice(index, 1);
|
this.list.splice(index, 1);
|
||||||
}
|
}
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
getIndex(obj) {
|
getIndex(obj) {
|
||||||
@ -69,9 +89,17 @@ export default class MockRectangle implements MockGameObject {
|
|||||||
getAll() {
|
getAll() {
|
||||||
return this.list;
|
return this.list;
|
||||||
}
|
}
|
||||||
setScale(_scale) {
|
setScale(_scale): this {
|
||||||
// return this.phaserText.setScale(scale);
|
// return this.phaserText.setScale(scale);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
off() {}
|
off(): this {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
setActive(active: boolean): this {
|
||||||
|
this.active = active;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import type { MockGameObject } from "../mockGameObject";
|
import type { MockGameObject } from "../mockGameObject";
|
||||||
import Sprite = Phaser.GameObjects.Sprite;
|
|
||||||
import Frame = Phaser.Textures.Frame;
|
import Frame = Phaser.Textures.Frame;
|
||||||
|
|
||||||
export default class MockSprite implements MockGameObject {
|
export default class MockSprite implements MockGameObject {
|
||||||
@ -14,6 +13,7 @@ export default class MockSprite implements MockGameObject {
|
|||||||
public anims;
|
public anims;
|
||||||
public list: MockGameObject[] = [];
|
public list: MockGameObject[] = [];
|
||||||
public name: string;
|
public name: string;
|
||||||
|
public active = true;
|
||||||
constructor(textureManager, x, y, texture) {
|
constructor(textureManager, x, y, texture) {
|
||||||
this.textureManager = textureManager;
|
this.textureManager = textureManager;
|
||||||
this.scene = textureManager.scene;
|
this.scene = textureManager.scene;
|
||||||
@ -21,7 +21,9 @@ export default class MockSprite implements MockGameObject {
|
|||||||
Phaser.GameObjects.Sprite.prototype.setInteractive = this.setInteractive;
|
Phaser.GameObjects.Sprite.prototype.setInteractive = this.setInteractive;
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
Phaser.GameObjects.Sprite.prototype.setTexture = this.setTexture;
|
Phaser.GameObjects.Sprite.prototype.setTexture = this.setTexture;
|
||||||
|
// @ts-ignore
|
||||||
Phaser.GameObjects.Sprite.prototype.setSizeToFrame = this.setSizeToFrame;
|
Phaser.GameObjects.Sprite.prototype.setSizeToFrame = this.setSizeToFrame;
|
||||||
|
// @ts-ignore
|
||||||
Phaser.GameObjects.Sprite.prototype.setFrame = this.setFrame;
|
Phaser.GameObjects.Sprite.prototype.setFrame = this.setFrame;
|
||||||
// Phaser.GameObjects.Sprite.prototype.disable = this.disable;
|
// Phaser.GameObjects.Sprite.prototype.disable = this.disable;
|
||||||
|
|
||||||
@ -37,46 +39,55 @@ export default class MockSprite implements MockGameObject {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
setTexture(_key: string, _frame?: string | number) {
|
setTexture(_key: string, _frame?: string | number): this {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setSizeToFrame(_frame?: boolean | Frame): Sprite {
|
setSizeToFrame(_frame?: boolean | Frame): this {
|
||||||
return {} as Sprite;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setPipeline(obj) {
|
setPipeline(obj): this {
|
||||||
// Sets the pipeline of this Game Object.
|
// Sets the pipeline of this Game Object.
|
||||||
return this.phaserSprite.setPipeline(obj);
|
this.phaserSprite.setPipeline(obj);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
off(_event, _callback, _source) {}
|
off(_event, _callback, _source): this {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
setTintFill(color) {
|
setTintFill(color): this {
|
||||||
// Sets the tint fill color.
|
// Sets the tint fill color.
|
||||||
return this.phaserSprite.setTintFill(color);
|
this.phaserSprite.setTintFill(color);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setScale(scale) {
|
setScale(scale = 1): this {
|
||||||
return this.phaserSprite.setScale(scale);
|
this.phaserSprite.setScale(scale);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setOrigin(x, y) {
|
setOrigin(x = 0.5, y = x): this {
|
||||||
return this.phaserSprite.setOrigin(x, y);
|
this.phaserSprite.setOrigin(x, y);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setSize(width, height) {
|
setSize(width, height): this {
|
||||||
// Sets the size of this Game Object.
|
// Sets the size of this Game Object.
|
||||||
return this.phaserSprite.setSize(width, height);
|
this.phaserSprite.setSize(width, height);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
once(event, callback, source) {
|
once(event, callback, source): this {
|
||||||
return this.phaserSprite.once(event, callback, source);
|
this.phaserSprite.once(event, callback, source);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
removeFromDisplayList() {
|
removeFromDisplayList(): this {
|
||||||
// same as remove or destroy
|
// same as remove or destroy
|
||||||
return this.phaserSprite.removeFromDisplayList();
|
this.phaserSprite.removeFromDisplayList();
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
addedToScene() {
|
addedToScene() {
|
||||||
@ -84,114 +95,140 @@ export default class MockSprite implements MockGameObject {
|
|||||||
return this.phaserSprite.addedToScene();
|
return this.phaserSprite.addedToScene();
|
||||||
}
|
}
|
||||||
|
|
||||||
setVisible(visible) {
|
setVisible(visible): this {
|
||||||
return this.phaserSprite.setVisible(visible);
|
this.phaserSprite.setVisible(visible);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setPosition(x, y) {
|
setPosition(x, y): this {
|
||||||
return this.phaserSprite.setPosition(x, y);
|
this.phaserSprite.setPosition(x, y);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setRotation(radians) {
|
setRotation(radians): this {
|
||||||
return this.phaserSprite.setRotation(radians);
|
this.phaserSprite.setRotation(radians);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop(): this {
|
||||||
return this.phaserSprite.stop();
|
this.phaserSprite.stop();
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setInteractive = () => null;
|
setInteractive(): this {
|
||||||
|
return this;
|
||||||
on(event, callback, source) {
|
|
||||||
return this.phaserSprite.on(event, callback, source);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setAlpha(alpha) {
|
on(event, callback, source): this {
|
||||||
return this.phaserSprite.setAlpha(alpha);
|
this.phaserSprite.on(event, callback, source);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setTint(color) {
|
setAlpha(alpha): this {
|
||||||
|
this.phaserSprite.setAlpha(alpha);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTint(color): this {
|
||||||
// Sets the tint of this Game Object.
|
// Sets the tint of this Game Object.
|
||||||
return this.phaserSprite.setTint(color);
|
this.phaserSprite.setTint(color);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setFrame(frame, _updateSize?: boolean, _updateOrigin?: boolean) {
|
setFrame(frame, _updateSize?: boolean, _updateOrigin?: boolean): this {
|
||||||
// Sets the frame this Game Object will use to render with.
|
// Sets the frame this Game Object will use to render with.
|
||||||
this.frame = frame;
|
this.frame = frame;
|
||||||
return frame;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setPositionRelative(source, x, y) {
|
setPositionRelative(source, x, y): this {
|
||||||
/// Sets the position of this Game Object to be a relative position from the source Game Object.
|
/// Sets the position of this Game Object to be a relative position from the source Game Object.
|
||||||
return this.phaserSprite.setPositionRelative(source, x, y);
|
this.phaserSprite.setPositionRelative(source, x, y);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setY(y) {
|
setY(y: number): this {
|
||||||
return this.phaserSprite.setY(y);
|
this.phaserSprite.setY(y);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setCrop(x, y, width, height) {
|
setCrop(x: number, y: number, width: number, height: number): this {
|
||||||
// Sets the crop size of this Game Object.
|
// Sets the crop size of this Game Object.
|
||||||
return this.phaserSprite.setCrop(x, y, width, height);
|
this.phaserSprite.setCrop(x, y, width, height);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
clearTint() {
|
clearTint(): this {
|
||||||
// Clears any previously set tint.
|
// Clears any previously set tint.
|
||||||
return this.phaserSprite.clearTint();
|
this.phaserSprite.clearTint();
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
disableInteractive() {
|
disableInteractive(): this {
|
||||||
// Disables Interactive features of this Game Object.
|
// Disables Interactive features of this Game Object.
|
||||||
return null;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
apply() {
|
apply() {
|
||||||
return this.phaserSprite.apply();
|
this.phaserSprite.apply();
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
play() {
|
play(): this {
|
||||||
// return this.phaserSprite.play();
|
// return this.phaserSprite.play();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setPipelineData(key, value) {
|
setPipelineData(key: string, value: any): this {
|
||||||
this.pipelineData[key] = value;
|
this.pipelineData[key] = value;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
return this.phaserSprite.destroy();
|
return this.phaserSprite.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
setName(name) {
|
setName(name: string): this {
|
||||||
return this.phaserSprite.setName(name);
|
this.phaserSprite.setName(name);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setAngle(angle) {
|
setAngle(angle): this {
|
||||||
return this.phaserSprite.setAngle(angle);
|
this.phaserSprite.setAngle(angle);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setMask() {}
|
setMask(): this {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
add(obj) {
|
add(obj: MockGameObject | MockGameObject[]): this {
|
||||||
// Adds a child to this Game Object.
|
// Adds a child to this Game Object.
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
this.list.push(...obj);
|
||||||
|
} else {
|
||||||
this.list.push(obj);
|
this.list.push(obj);
|
||||||
}
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
removeAll() {
|
removeAll() {
|
||||||
// Removes all Game Objects from this Container.
|
// Removes all Game Objects from this Container.
|
||||||
this.list = [];
|
this.list = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
addAt(obj, index) {
|
addAt(obj, index): this {
|
||||||
// Adds a Game Object to this Container at the given index.
|
// Adds a Game Object to this Container at the given index.
|
||||||
this.list.splice(index, 0, obj);
|
this.list.splice(index, 0, obj);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(obj) {
|
remove(obj): this {
|
||||||
const index = this.list.indexOf(obj);
|
const index = this.list.indexOf(obj);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
this.list.splice(index, 1);
|
this.list.splice(index, 1);
|
||||||
}
|
}
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
getIndex(obj) {
|
getIndex(obj) {
|
||||||
@ -206,4 +243,14 @@ export default class MockSprite implements MockGameObject {
|
|||||||
getAll() {
|
getAll() {
|
||||||
return this.list;
|
return this.list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
copyPosition(obj): this {
|
||||||
|
this.phaserSprite.copyPosition(obj);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
setActive(active: boolean): this {
|
||||||
|
this.phaserSprite.setActive(active);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ export default class MockText implements MockGameObject {
|
|||||||
public text = "";
|
public text = "";
|
||||||
public name: string;
|
public name: string;
|
||||||
public color?: string;
|
public color?: string;
|
||||||
|
public active = true;
|
||||||
|
|
||||||
constructor(textureManager, _x, _y, _content, _styleOptions) {
|
constructor(textureManager, _x, _y, _content, _styleOptions) {
|
||||||
this.scene = textureManager.scene;
|
this.scene = textureManager.scene;
|
||||||
@ -107,42 +108,51 @@ export default class MockText implements MockGameObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setScale(_scale) {
|
setScale(_scale): this {
|
||||||
// return this.phaserText.setScale(scale);
|
// return this.phaserText.setScale(scale);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setShadow(_shadowXpos, _shadowYpos, _shadowColor) {
|
setShadow(_shadowXpos, _shadowYpos, _shadowColor): this {
|
||||||
// Sets the shadow settings for this Game Object.
|
// Sets the shadow settings for this Game Object.
|
||||||
// return this.phaserText.setShadow(shadowXpos, shadowYpos, shadowColor);
|
// return this.phaserText.setShadow(shadowXpos, shadowYpos, shadowColor);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setLineSpacing(_lineSpacing) {
|
setLineSpacing(_lineSpacing): this {
|
||||||
// Sets the line spacing value of this Game Object.
|
// Sets the line spacing value of this Game Object.
|
||||||
// return this.phaserText.setLineSpacing(lineSpacing);
|
// return this.phaserText.setLineSpacing(lineSpacing);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setOrigin(_x, _y) {
|
setOrigin(_x, _y): this {
|
||||||
// return this.phaserText.setOrigin(x, y);
|
// return this.phaserText.setOrigin(x, y);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
once(_event, _callback, _source) {
|
once(_event, _callback, _source): this {
|
||||||
// return this.phaserText.once(event, callback, source);
|
// return this.phaserText.once(event, callback, source);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
off(_event, _callback, _obj) {}
|
off(_event, _callback, _obj) {}
|
||||||
|
|
||||||
removedFromScene() {}
|
removedFromScene() {}
|
||||||
|
|
||||||
addToDisplayList() {}
|
addToDisplayList(): this {
|
||||||
|
return this;
|
||||||
setStroke(_color, _thickness) {
|
|
||||||
// Sets the stroke color and thickness.
|
|
||||||
// return this.phaserText.setStroke(color, thickness);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
removeFromDisplayList() {
|
setStroke(_color, _thickness): this {
|
||||||
|
// Sets the stroke color and thickness.
|
||||||
|
// return this.phaserText.setStroke(color, thickness);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
removeFromDisplayList(): this {
|
||||||
// same as remove or destroy
|
// same as remove or destroy
|
||||||
// return this.phaserText.removeFromDisplayList();
|
// return this.phaserText.removeFromDisplayList();
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
addedToScene() {
|
addedToScene() {
|
||||||
@ -150,16 +160,18 @@ export default class MockText implements MockGameObject {
|
|||||||
// return this.phaserText.addedToScene();
|
// return this.phaserText.addedToScene();
|
||||||
}
|
}
|
||||||
|
|
||||||
setVisible(_visible) {
|
setVisible(_visible): this {
|
||||||
// return this.phaserText.setVisible(visible);
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setY(_y) {
|
setY(_y): this {
|
||||||
// return this.phaserText.setY(y);
|
// return this.phaserText.setY(y);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setX(_x) {
|
setX(_x): this {
|
||||||
// return this.phaserText.setX(x);
|
// return this.phaserText.setX(x);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -169,37 +181,45 @@ export default class MockText implements MockGameObject {
|
|||||||
* @param z The z position of this Game Object. Default 0.
|
* @param z The z position of this Game Object. Default 0.
|
||||||
* @param w The w position of this Game Object. Default 0.
|
* @param w The w position of this Game Object. Default 0.
|
||||||
*/
|
*/
|
||||||
setPosition(_x?: number, _y?: number, _z?: number, _w?: number) {}
|
setPosition(_x?: number, _y?: number, _z?: number, _w?: number): this {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
setText(text) {
|
setText(text): this {
|
||||||
// Sets the text this Game Object will display.
|
// Sets the text this Game Object will display.
|
||||||
// return this.phaserText.setText\(text);
|
// return this.phaserText.setText\(text);
|
||||||
this.text = text;
|
this.text = text;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setAngle(_angle) {
|
setAngle(_angle): this {
|
||||||
// Sets the angle of this Game Object.
|
// Sets the angle of this Game Object.
|
||||||
// return this.phaserText.setAngle(angle);
|
// return this.phaserText.setAngle(angle);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setPositionRelative(_source, _x, _y) {
|
setPositionRelative(_source, _x, _y): this {
|
||||||
/// Sets the position of this Game Object to be a relative position from the source Game Object.
|
/// Sets the position of this Game Object to be a relative position from the source Game Object.
|
||||||
// return this.phaserText.setPositionRelative(source, x, y);
|
// return this.phaserText.setPositionRelative(source, x, y);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setShadowOffset(_offsetX, _offsetY) {
|
setShadowOffset(_offsetX, _offsetY): this {
|
||||||
// Sets the shadow offset values.
|
// Sets the shadow offset values.
|
||||||
// return this.phaserText.setShadowOffset(offsetX, offsetY);
|
// return this.phaserText.setShadowOffset(offsetX, offsetY);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setWordWrapWidth(width) {
|
setWordWrapWidth(width): this {
|
||||||
// Sets the width (in pixels) to use for wrapping lines.
|
// Sets the width (in pixels) to use for wrapping lines.
|
||||||
this.wordWrapWidth = width;
|
this.wordWrapWidth = width;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setFontSize(_fontSize) {
|
setFontSize(_fontSize): this {
|
||||||
// Sets the font size of this Game Object.
|
// Sets the font size of this Game Object.
|
||||||
// return this.phaserText.setFontSize(fontSize);
|
// return this.phaserText.setFontSize(fontSize);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
getBounds() {
|
getBounds() {
|
||||||
@ -209,25 +229,31 @@ export default class MockText implements MockGameObject {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
setColor(color: string) {
|
setColor(color: string): this {
|
||||||
this.color = color;
|
this.color = color;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setInteractive = () => null;
|
setInteractive(): this {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
setShadowColor(_color) {
|
setShadowColor(_color): this {
|
||||||
// Sets the shadow color.
|
// Sets the shadow color.
|
||||||
// return this.phaserText.setShadowColor(color);
|
// return this.phaserText.setShadowColor(color);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setTint(_color) {
|
setTint(_color): this {
|
||||||
// Sets the tint of this Game Object.
|
// Sets the tint of this Game Object.
|
||||||
// return this.phaserText.setTint(color);
|
// return this.phaserText.setTint(color);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setStrokeStyle(_thickness, _color) {
|
setStrokeStyle(_thickness, _color): this {
|
||||||
// Sets the stroke style for the graphics.
|
// Sets the stroke style for the graphics.
|
||||||
// return this.phaserText.setStrokeStyle(thickness, color);
|
// return this.phaserText.setStrokeStyle(thickness, color);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
@ -235,20 +261,24 @@ export default class MockText implements MockGameObject {
|
|||||||
this.list = [];
|
this.list = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
setAlpha(_alpha) {
|
setAlpha(_alpha): this {
|
||||||
// return this.phaserText.setAlpha(alpha);
|
// return this.phaserText.setAlpha(alpha);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setName(name: string) {
|
setName(name: string): this {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setAlign(_align) {
|
setAlign(_align): this {
|
||||||
// return this.phaserText.setAlign(align);
|
// return this.phaserText.setAlign(align);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setMask() {
|
setMask(): this {
|
||||||
/// Sets the mask that this Game Object will use to render with.
|
/// Sets the mask that this Game Object will use to render with.
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
getBottomLeft() {
|
getBottomLeft() {
|
||||||
@ -265,37 +295,43 @@ export default class MockText implements MockGameObject {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
disableInteractive() {
|
disableInteractive(): this {
|
||||||
// Disables interaction with this Game Object.
|
// Disables interaction with this Game Object.
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
clearTint() {
|
clearTint(): this {
|
||||||
// Clears tint on this Game Object.
|
// Clears tint on this Game Object.
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
add(obj) {
|
add(obj): this {
|
||||||
// Adds a child to this Game Object.
|
// Adds a child to this Game Object.
|
||||||
this.list.push(obj);
|
this.list.push(obj);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
removeAll() {
|
removeAll(): this {
|
||||||
// Removes all Game Objects from this Container.
|
// Removes all Game Objects from this Container.
|
||||||
this.list = [];
|
this.list = [];
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
addAt(obj, index) {
|
addAt(obj, index): this {
|
||||||
// Adds a Game Object to this Container at the given index.
|
// Adds a Game Object to this Container at the given index.
|
||||||
this.list.splice(index, 0, obj);
|
this.list.splice(index, 0, obj);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(obj) {
|
remove(obj): this {
|
||||||
const index = this.list.indexOf(obj);
|
const index = this.list.indexOf(obj);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
this.list.splice(index, 1);
|
this.list.splice(index, 1);
|
||||||
}
|
}
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
getIndex(obj) {
|
getIndex(obj): number {
|
||||||
const index = this.list.indexOf(obj);
|
const index = this.list.indexOf(obj);
|
||||||
return index || -1;
|
return index || -1;
|
||||||
}
|
}
|
||||||
@ -317,5 +353,10 @@ export default class MockText implements MockGameObject {
|
|||||||
return this.runWordWrap(this.text).split("\n");
|
return this.runWordWrap(this.text).split("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// biome-ignore lint/complexity/noBannedTypes: This matches the signature of the class this mocks
|
||||||
on(_event: string | symbol, _fn: Function, _context?: any) {}
|
on(_event: string | symbol, _fn: Function, _context?: any) {}
|
||||||
|
|
||||||
|
setActive(_active: boolean): this {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ export default class MockTexture implements MockGameObject {
|
|||||||
public frames: object;
|
public frames: object;
|
||||||
public firstFrame: string;
|
public firstFrame: string;
|
||||||
public name: string;
|
public name: string;
|
||||||
|
public active: boolean;
|
||||||
|
|
||||||
constructor(manager, key: string, source) {
|
constructor(manager, key: string, source) {
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
@ -39,4 +40,14 @@ export default class MockTexture implements MockGameObject {
|
|||||||
getSourceImage() {
|
getSourceImage() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setActive(active: boolean): this {
|
||||||
|
this.active = active;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
setName(name: string): this {
|
||||||
|
this.name = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,10 +70,10 @@ export function initTestFile() {
|
|||||||
* @param x The relative x position
|
* @param x The relative x position
|
||||||
* @param y The relative y position
|
* @param y The relative y position
|
||||||
*/
|
*/
|
||||||
const setPositionRelative = function (guideObject: any, x: number, y: number) {
|
const setPositionRelative = function (guideObject: any, x: number, y: number): any {
|
||||||
const offsetX = guideObject.width * (-0.5 + (0.5 - guideObject.originX));
|
const offsetX = guideObject.width * (-0.5 + (0.5 - guideObject.originX));
|
||||||
const offsetY = guideObject.height * (-0.5 + (0.5 - guideObject.originY));
|
const offsetY = guideObject.height * (-0.5 + (0.5 - guideObject.originY));
|
||||||
this.setPosition(guideObject.x + offsetX + x, guideObject.y + offsetY + y);
|
return this.setPosition(guideObject.x + offsetX + x, guideObject.y + offsetY + y);
|
||||||
};
|
};
|
||||||
|
|
||||||
Phaser.GameObjects.Container.prototype.setPositionRelative = setPositionRelative;
|
Phaser.GameObjects.Container.prototype.setPositionRelative = setPositionRelative;
|
||||||
|
@ -8,7 +8,7 @@ import { Abilities } from "#enums/abilities";
|
|||||||
import { Species } from "#enums/species";
|
import { Species } from "#enums/species";
|
||||||
import { allSpecies, getPokemonSpecies, type PokemonForm } from "#app/data/pokemon-species";
|
import { allSpecies, getPokemonSpecies, type PokemonForm } from "#app/data/pokemon-species";
|
||||||
import { Button } from "#enums/buttons";
|
import { Button } from "#enums/buttons";
|
||||||
import { DropDownColumn } from "#app/ui/filter-bar";
|
import { DropDownColumn } from "#enums/drop-down-column";
|
||||||
import type PokemonSpecies from "#app/data/pokemon-species";
|
import type PokemonSpecies from "#app/data/pokemon-species";
|
||||||
import { PokemonType } from "#enums/pokemon-type";
|
import { PokemonType } from "#enums/pokemon-type";
|
||||||
import { UiMode } from "#enums/ui-mode";
|
import { UiMode } from "#enums/ui-mode";
|
||||||
|