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:
|
||||
- "src/**"
|
||||
- "test/**"
|
||||
- "public/**"
|
||||
# Negations syntax from https://github.com/dorny/paths-filter/issues/184#issuecomment-2786521554
|
||||
- "src/**/!(*.{md,py,sh,gitkeep,gitignore})"
|
||||
- "test/**/!(*.{md,py,sh,gitkeep,gitignore})"
|
||||
- "public/**/!(*.{md,py,sh,gitkeep,gitignore})"
|
||||
# Workflows that can impact tests
|
||||
- ".github/workflows/test*.yml"
|
||||
- ".github/test-filters.yml"
|
||||
@ -12,8 +13,3 @@ all:
|
||||
- "tsconfig*.json" # tsconfig.json tweaking can impact compilation
|
||||
- "global.d.ts"
|
||||
- ".env*"
|
||||
# Blanket negations for files that cannot impact tests
|
||||
- "!**/*.py" # No .py files
|
||||
- "!**/*.sh" # No .sh files
|
||||
- "!**/*.md" # No .md files
|
||||
- "!**/.git*" # .gitkeep and family
|
||||
|
9
.github/workflows/test-shard-template.yml
vendored
@ -19,19 +19,20 @@ on:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Shard ${{ inputs.shard }} of ${{ inputs.totalShards }}
|
||||
# We can't use dynmically named jobs until https://github.com/orgs/community/discussions/13261 is implemented
|
||||
name: Shard
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ !inputs.skip }}
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v4.2.2
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
submodules: "recursive"
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
node-version-file: ".nvmrc"
|
||||
cache: "npm"
|
||||
- name: Install Node.js dependencies
|
||||
run: npm ci
|
||||
- name: Run tests
|
||||
|
7
.github/workflows/tests.yml
vendored
@ -25,6 +25,7 @@ jobs:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36
|
||||
id: filter
|
||||
with:
|
||||
filters: .github/test-filters.yml
|
||||
|
||||
@ -33,10 +34,10 @@ jobs:
|
||||
needs: check-path-change-filter
|
||||
strategy:
|
||||
matrix:
|
||||
shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
shard: [1, 2, 3, 4, 5]
|
||||
uses: ./.github/workflows/test-shard-template.yml
|
||||
with:
|
||||
project: main
|
||||
shard: ${{ matrix.shard }}
|
||||
totalShards: 10
|
||||
skip: ${{ needs.check-path-change-filter.outputs.all == 'false'}}
|
||||
totalShards: 5
|
||||
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": {
|
||||
"319452": "831a1f",
|
||||
"4a7310": "982443",
|
||||
"7ba563": "b44040",
|
||||
"bdef84": "ec8c8c",
|
||||
"8cbd63": "c54b4b",
|
||||
"215200": "710f2e",
|
||||
"a5d670": "df5252",
|
||||
"4aa552": "9f2f2c",
|
||||
"a5d674": "e16363",
|
||||
"196b21": "891222",
|
||||
"7aa953": "c54b4b",
|
||||
"7ba563": "b44040",
|
||||
"215200": "710f2e",
|
||||
"f7ce00": "7aa1df",
|
||||
"525252": "123a5a",
|
||||
"63b56b": "b2332f",
|
||||
"a5d673": "df5252",
|
||||
"8c6b3a": "448bc3",
|
||||
"4aa552": "9f2f2c"
|
||||
"bdef84": "ec8c8c",
|
||||
"63b56b": "b2332f",
|
||||
"319452": "831a1f",
|
||||
"196b21": "891222",
|
||||
"4a7310": "982443"
|
||||
},
|
||||
"2": {
|
||||
"319452": "b08d72",
|
||||
"4a7310": "4f3956",
|
||||
"7ba563": "704e7e",
|
||||
"bdef84": "a779ba",
|
||||
"8cbd63": "e3d7a6",
|
||||
"215200": "583823",
|
||||
"a5d670": "d7cda7",
|
||||
"4aa552": "c5a77f",
|
||||
"a5d674": "8c669b",
|
||||
"196b21": "78582c",
|
||||
"7aa953": "704e7e",
|
||||
"7ba563": "704e7e",
|
||||
"215200": "583823",
|
||||
"f7ce00": "f2aacd",
|
||||
"525252": "a53b6f",
|
||||
"63b56b": "cfc191",
|
||||
"a5d673": "d7cda7",
|
||||
"8c6b3a": "df87bb",
|
||||
"4aa552": "c5a77f"
|
||||
"bdef84": "a779ba",
|
||||
"63b56b": "cfc191",
|
||||
"319452": "b08d72",
|
||||
"196b21": "78582c",
|
||||
"4a7310": "4f3956"
|
||||
}
|
||||
}
|
@ -1,28 +1,28 @@
|
||||
{
|
||||
"1": {
|
||||
"196b21": "831a1f",
|
||||
"7ba563": "b44040",
|
||||
"215201": "630d28",
|
||||
"215200": "710f2f",
|
||||
"a5d674": "df5252",
|
||||
"8cbd63": "c54b4b",
|
||||
"63b56b": "b2332f",
|
||||
"a5d670": "e16363",
|
||||
"319452": "831a1f",
|
||||
"4aa552": "9f2f2c",
|
||||
"7ba563": "b44040",
|
||||
"8cbd63": "c54b4b",
|
||||
"215200": "710f2f",
|
||||
"196b21": "831a1f",
|
||||
"a5d674": "df5252",
|
||||
"4a7310": "982443",
|
||||
"a5d673": "e16363",
|
||||
"63b56b": "b2332f",
|
||||
"215201": "630d28"
|
||||
"4a7310": "982443"
|
||||
},
|
||||
"2": {
|
||||
"196b21": "b08d72",
|
||||
"7ba563": "704e7e",
|
||||
"215201": "583823",
|
||||
"215200": "3f3249",
|
||||
"a5d674": "d7cda7",
|
||||
"8cbd63": "e3d7a6",
|
||||
"63b56b": "cfc191",
|
||||
"a5d670": "8c669b",
|
||||
"319452": "b08d72",
|
||||
"4aa552": "c5a77f",
|
||||
"7ba563": "704e7e",
|
||||
"8cbd63": "e3d7a6",
|
||||
"215200": "3f3249",
|
||||
"196b21": "b08d72",
|
||||
"a5d674": "d7cda7",
|
||||
"4a7310": "4f3956",
|
||||
"a5d673": "8c669b",
|
||||
"63b56b": "cfc191",
|
||||
"215201": "583823"
|
||||
"4a7310": "4f3956"
|
||||
}
|
||||
}
|
@ -1,28 +1,28 @@
|
||||
{
|
||||
"1": {
|
||||
"196b21": "780d4a",
|
||||
"7ba563": "b44040",
|
||||
"215201": "710f2e",
|
||||
"215200": "710f2f",
|
||||
"a5d674": "de5b6f",
|
||||
"8cbd63": "bf3d64",
|
||||
"63b56b": "9e2056",
|
||||
"a5d670": "e16363",
|
||||
"319452": "780d4a",
|
||||
"4aa552": "8a1652",
|
||||
"7ba563": "b44040",
|
||||
"8cbd63": "bf3d64",
|
||||
"215200": "710f2f",
|
||||
"196b21": "780d4a",
|
||||
"a5d674": "de5b6f",
|
||||
"4a7310": "982443",
|
||||
"a5d673": "e16363",
|
||||
"63b56b": "9e2056",
|
||||
"215201": "710f2e"
|
||||
"4a7310": "982443"
|
||||
},
|
||||
"2": {
|
||||
"196b21": "b59c72",
|
||||
"7ba563": "805a9c",
|
||||
"215201": "694d37",
|
||||
"215200": "41334d",
|
||||
"a5d674": "f6f7df",
|
||||
"8cbd63": "ebe9ca",
|
||||
"63b56b": "e3ddb8",
|
||||
"a5d670": "a473ba",
|
||||
"319452": "b59c72",
|
||||
"4aa552": "c9b991",
|
||||
"7ba563": "805a9c",
|
||||
"8cbd63": "ebe9ca",
|
||||
"215200": "41334d",
|
||||
"196b21": "b59c72",
|
||||
"a5d674": "f6f7df",
|
||||
"4a7310": "4f3956",
|
||||
"a5d673": "a473ba",
|
||||
"63b56b": "e3ddb8",
|
||||
"215201": "694d37"
|
||||
"4a7310": "4f3956"
|
||||
}
|
||||
}
|
@ -1,34 +1,36 @@
|
||||
{
|
||||
"1": {
|
||||
"319452": "780d4a",
|
||||
"4a7310": "982443",
|
||||
"7ba563": "b44040",
|
||||
"bdef84": "ec8c8c",
|
||||
"8cbd63": "bf3d64",
|
||||
"215200": "710f2e",
|
||||
"a5d670": "de5b6f",
|
||||
"4aa552": "8a1652",
|
||||
"a5d674": "e16363",
|
||||
"196b21": "7d1157",
|
||||
"7aa953": "bf3d64",
|
||||
"7ba563": "b44040",
|
||||
"215200": "710f2e",
|
||||
"f7ce00": "5bcfc3",
|
||||
"525252": "20668c",
|
||||
"63b56b": "9e2056",
|
||||
"a5d673": "de5b6f",
|
||||
"8c6b3a": "33a3b0",
|
||||
"4aa552": "8a1652"
|
||||
"bdef84": "ec8c8c",
|
||||
"63b56b": "9e2056",
|
||||
"319452": "780d4a",
|
||||
"196b21": "7d1157",
|
||||
"4a7310": "982443"
|
||||
},
|
||||
"2": {
|
||||
"319452": "b59c72",
|
||||
"4a7310": "4f3956",
|
||||
"7ba563": "805a9c",
|
||||
"bdef84": "c193cf",
|
||||
"8cbd63": "f6f7df",
|
||||
"215200": "694d37",
|
||||
"a5d670": "ebe9ca",
|
||||
"4aa552": "c9b991",
|
||||
"a5d674": "a473ba",
|
||||
"196b21": "9c805f",
|
||||
"7aa953": "805a9c",
|
||||
"7ba563": "805a9c",
|
||||
"215200": "694d37",
|
||||
"f7ce00": "f2aab6",
|
||||
"525252": "983364",
|
||||
"63b56b": "e3ddb8",
|
||||
"a5d673": "ebe9ca",
|
||||
"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()
|
||||
.attr(NoTransformAbilityAbAttr),
|
||||
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(),
|
||||
new Ability(Abilities.VESSEL_OF_RUIN, 9)
|
||||
.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. */
|
||||
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.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 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("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("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, 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.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.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 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),
|
||||
|
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 { populateVariantColors, variantColorCache } 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 {
|
||||
HighCritAttr,
|
||||
@ -3352,22 +3354,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
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 {
|
||||
this.battleInfo.toggleStats(visible);
|
||||
}
|
||||
|
||||
toggleFlyout(visible: boolean): void {
|
||||
this.battleInfo.toggleFlyout(visible);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds experience to this PlayerPokemon, subject to wave based level caps.
|
||||
* @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 {
|
||||
protected battleInfo: PlayerBattleInfo;
|
||||
public compatibleTms: Moves[];
|
||||
|
||||
constructor(
|
||||
@ -6043,6 +6034,7 @@ export class PlayerPokemon extends Pokemon {
|
||||
}
|
||||
|
||||
export class EnemyPokemon extends Pokemon {
|
||||
protected battleInfo: EnemyBattleInfo;
|
||||
public trainerSlot: TrainerSlot;
|
||||
public aiType: AiType;
|
||||
public bossSegments: number;
|
||||
@ -6714,6 +6706,19 @@ export class EnemyPokemon extends Pokemon {
|
||||
|
||||
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 type { Arena } from "./field/arena";
|
||||
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 { Species } from "#enums/species";
|
||||
import { Challenges } from "./enums/challenges";
|
||||
@ -124,16 +124,20 @@ export class GameMode implements GameModeConfig {
|
||||
|
||||
/**
|
||||
* @returns either:
|
||||
* - random biome for Daily mode
|
||||
* - override from overrides.ts
|
||||
* - random biome for Daily mode
|
||||
* - Town
|
||||
*/
|
||||
getStartingBiome(): Biome {
|
||||
if (!isNullOrUndefined(Overrides.STARTING_BIOME_OVERRIDE)) {
|
||||
return Overrides.STARTING_BIOME_OVERRIDE;
|
||||
}
|
||||
|
||||
switch (this.modeId) {
|
||||
case GameModes.DAILY:
|
||||
return getDailyStartingBiome();
|
||||
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 offsetX = guideObject.width * (-0.5 + (0.5 - guideObject.originX));
|
||||
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;
|
||||
|
@ -73,7 +73,7 @@ class DefaultOverrides {
|
||||
*/
|
||||
readonly BATTLE_STYLE_OVERRIDE: BattleStyle | null = null;
|
||||
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;
|
||||
/** Multiplies XP gained by this value including 0. Set to null to ignore the override. */
|
||||
readonly XP_MULTIPLIER_OVERRIDE: number | null = null;
|
||||
|
@ -125,6 +125,12 @@ export class SwitchSummonPhase extends SummonPhase {
|
||||
const switchedInPokemon: Pokemon | undefined = party[this.slotIndex];
|
||||
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);
|
||||
applyPreSwitchOutAbAttrs(PreSwitchOutAbAttr, this.lastPokemon);
|
||||
if (!switchedInPokemon) {
|
||||
@ -132,6 +138,7 @@ export class SwitchSummonPhase extends SummonPhase {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (this.switchType === SwitchType.BATON_PASS) {
|
||||
// If switching via baton pass, update opposing tags coming from the prior 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
|
||||
*/
|
||||
setPositionRelative(guideObject: any, x: number, y: number): void;
|
||||
setPositionRelative(guideObject: any, x: number, y: number): this;
|
||||
}
|
||||
interface Sprite {
|
||||
/**
|
||||
* 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 {
|
||||
/**
|
||||
* 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 {
|
||||
/**
|
||||
* 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 {
|
||||
/**
|
||||
* 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 {
|
||||
/**
|
||||
* 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 {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -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 { fixedInt } from "#app/utils/common";
|
||||
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
|
||||
* @param pokemon {@linkcode Pokemon} to link to this flyout
|
||||
*/
|
||||
initInfo(pokemon: Pokemon) {
|
||||
initInfo(pokemon: EnemyPokemon) {
|
||||
this.pokemon = 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);
|
||||
|
||||
const eggSprite = globalScene.add.sprite(19, 18, "egg", "egg_0");
|
||||
eggSprite.setScale(0.32);
|
||||
const eggSprite = globalScene.add.sprite(19, 18, "egg", "egg_0").setScale(0.32);
|
||||
|
||||
this.eggCountText = addTextObject(28, 13, `${this.eggCount}`, TextStyle.MESSAGE, { fontSize: "66px" });
|
||||
this.eggCountText.setName("text-egg-count");
|
||||
this.eggCountText = addTextObject(28, 13, `${this.eggCount}`, TextStyle.MESSAGE, { fontSize: "66px" }).setName(
|
||||
"text-egg-count",
|
||||
);
|
||||
|
||||
this.add(eggSprite);
|
||||
this.add(this.eggCountText);
|
||||
this.add([eggSprite, this.eggCountText]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -22,18 +22,12 @@ export default class EvolutionSceneHandler extends MessageUiHandler {
|
||||
const ui = this.getUi();
|
||||
|
||||
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);
|
||||
messageBg.setOrigin(0, 1);
|
||||
messageBg.setVisible(false);
|
||||
ui.add(messageBg);
|
||||
const messageBg = globalScene.add.sprite(0, 0, "bg", globalScene.windowType).setOrigin(0, 1).setVisible(false);
|
||||
|
||||
this.messageBg = messageBg;
|
||||
|
||||
this.messageContainer = globalScene.add.container(12, -39);
|
||||
this.messageContainer.setVisible(false);
|
||||
ui.add(this.messageContainer);
|
||||
this.messageContainer = globalScene.add.container(12, -39).setVisible(false);
|
||||
|
||||
const message = addTextObject(0, 0, "", TextStyle.MESSAGE, {
|
||||
maxLines: 2,
|
||||
@ -43,6 +37,8 @@ export default class EvolutionSceneHandler extends MessageUiHandler {
|
||||
});
|
||||
this.messageContainer.add(message);
|
||||
|
||||
ui.add([this.evolutionContainer, this.messageBg, this.messageContainer]);
|
||||
|
||||
this.message = message;
|
||||
|
||||
this.initPromptSprite(this.messageContainer);
|
||||
@ -52,10 +48,8 @@ export default class EvolutionSceneHandler extends MessageUiHandler {
|
||||
super.show(_args);
|
||||
|
||||
globalScene.ui.bringToTop(this.evolutionContainer);
|
||||
globalScene.ui.bringToTop(this.messageBg);
|
||||
globalScene.ui.bringToTop(this.messageContainer);
|
||||
this.messageBg.setVisible(true);
|
||||
this.messageContainer.setVisible(true);
|
||||
globalScene.ui.bringToTop(this.messageBg.setVisible(true));
|
||||
globalScene.ui.bringToTop(this.messageContainer.setVisible(true));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import { getLocalizedSpriteKey, fixedInt, padInt } from "#app/utils/common";
|
||||
import { MoveCategory } from "#enums/MoveCategory";
|
||||
import i18next from "i18next";
|
||||
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 { CommandPhase } from "#app/phases/command-phase";
|
||||
import MoveInfoOverlay from "./move-info-overlay";
|
||||
@ -279,7 +279,7 @@ export default class FightUiHandler extends UiHandler implements InfoToggle {
|
||||
this.moveInfoOverlay.show(pokemonMove.getMove());
|
||||
|
||||
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();
|
||||
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 { addWindow, WindowVariant } from "./ui-theme";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
|
||||
export enum DropDownColumn {
|
||||
GEN,
|
||||
TYPES,
|
||||
BIOME,
|
||||
CAUGHT,
|
||||
UNLOCKS,
|
||||
MISC,
|
||||
SORT,
|
||||
}
|
||||
import type { DropDownColumn } from "#enums/drop-down-column";
|
||||
|
||||
export class FilterBar extends Phaser.GameObjects.Container {
|
||||
private window: Phaser.GameObjects.NineSlice;
|
||||
@ -49,13 +40,9 @@ export class FilterBar extends Phaser.GameObjects.Container {
|
||||
this.cursorOffset = cursorOffset;
|
||||
|
||||
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.setScale(0.5);
|
||||
this.cursorObj.setVisible(false);
|
||||
this.cursorObj.setOrigin(0, 0);
|
||||
this.add(this.cursorObj);
|
||||
this.cursorObj = globalScene.add.image(1, 1, "cursor").setScale(0.5).setVisible(false).setOrigin(0);
|
||||
this.add([this.window, this.cursorObj]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -23,7 +23,8 @@ import type { Species } from "#enums/species";
|
||||
import { Button } from "#enums/buttons";
|
||||
import { DropDown, DropDownLabel, DropDownOption, DropDownState, DropDownType, SortCriteria } from "#app/ui/dropdown";
|
||||
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 { Abilities } from "#enums/abilities";
|
||||
import {
|
||||
|
@ -8,7 +8,7 @@ import type Pokemon from "../field/pokemon";
|
||||
import i18next from "i18next";
|
||||
import type { DexEntry, StarterDataEntry } 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 { StatsContainer } from "./stats-container";
|
||||
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.setTint(getVariantTint(baseVariant));
|
||||
if (this.pokemonShinyIcon.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.pokemonShinyIcon.on("pointerover", () =>
|
||||
globalScene.ui.showTooltip(
|
||||
"",
|
||||
`${i18next.t("common:shinyOnHover")}${shinyDescriptor ? ` (${shinyDescriptor})` : ""}`,
|
||||
true,
|
||||
),
|
||||
);
|
||||
this.pokemonShinyIcon.on("pointerout", () => globalScene.ui.hideTooltip());
|
||||
let shinyDescriptor = "";
|
||||
if (doubleShiny || baseVariant) {
|
||||
shinyDescriptor = " (" + getShinyDescriptor(baseVariant);
|
||||
if (doubleShiny) {
|
||||
shinyDescriptor += "/" + getShinyDescriptor(pokemon.fusionVariant);
|
||||
}
|
||||
shinyDescriptor += ")";
|
||||
}
|
||||
this.pokemonShinyIcon
|
||||
.on("pointerover", () =>
|
||||
globalScene.ui.showTooltip("", i18next.t("common:shinyOnHover") + shinyDescriptor, true),
|
||||
)
|
||||
.on("pointerout", () => globalScene.ui.hideTooltip());
|
||||
|
||||
const newShiny = BigInt(1 << (pokemon.shiny ? 1 : 0));
|
||||
const newVariant = BigInt(1 << (pokemon.variant + 4));
|
||||
|
@ -53,7 +53,8 @@ import { Button } from "#enums/buttons";
|
||||
import { EggSourceType } from "#enums/egg-source-types";
|
||||
import { DropDown, DropDownLabel, DropDownOption, DropDownState, DropDownType, SortCriteria } from "#app/ui/dropdown";
|
||||
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 { SelectChallengePhase } from "#app/phases/select-challenge-phase";
|
||||
import { EncounterPhase } from "#app/phases/encounter-phase";
|
||||
@ -108,17 +109,21 @@ const languageSettings: { [key: string]: LanguageSetting } = {
|
||||
instructionTextSize: "38px",
|
||||
},
|
||||
de: {
|
||||
starterInfoTextSize: "48px",
|
||||
starterInfoTextSize: "54px",
|
||||
instructionTextSize: "35px",
|
||||
starterInfoXPos: 33,
|
||||
starterInfoXPos: 35,
|
||||
},
|
||||
"es-ES": {
|
||||
starterInfoTextSize: "52px",
|
||||
instructionTextSize: "35px",
|
||||
starterInfoTextSize: "50px",
|
||||
instructionTextSize: "38px",
|
||||
starterInfoYOffset: 0.5,
|
||||
starterInfoXPos: 38,
|
||||
},
|
||||
"es-MX": {
|
||||
starterInfoTextSize: "52px",
|
||||
instructionTextSize: "35px",
|
||||
starterInfoTextSize: "50px",
|
||||
instructionTextSize: "38px",
|
||||
starterInfoYOffset: 0.5,
|
||||
starterInfoXPos: 38,
|
||||
},
|
||||
fr: {
|
||||
starterInfoTextSize: "54px",
|
||||
@ -128,21 +133,16 @@ const languageSettings: { [key: string]: LanguageSetting } = {
|
||||
starterInfoTextSize: "56px",
|
||||
instructionTextSize: "38px",
|
||||
},
|
||||
pt_BR: {
|
||||
starterInfoTextSize: "47px",
|
||||
instructionTextSize: "38px",
|
||||
"pt-BR": {
|
||||
starterInfoTextSize: "48px",
|
||||
instructionTextSize: "42px",
|
||||
starterInfoYOffset: 0.5,
|
||||
starterInfoXPos: 33,
|
||||
},
|
||||
zh: {
|
||||
starterInfoTextSize: "47px",
|
||||
instructionTextSize: "38px",
|
||||
starterInfoYOffset: 1,
|
||||
starterInfoXPos: 24,
|
||||
},
|
||||
pt: {
|
||||
starterInfoTextSize: "48px",
|
||||
instructionTextSize: "42px",
|
||||
starterInfoXPos: 33,
|
||||
starterInfoTextSize: "56px",
|
||||
instructionTextSize: "36px",
|
||||
starterInfoXPos: 26,
|
||||
},
|
||||
ko: {
|
||||
starterInfoTextSize: "60px",
|
||||
@ -156,9 +156,11 @@ const languageSettings: { [key: string]: LanguageSetting } = {
|
||||
starterInfoYOffset: 0.5,
|
||||
starterInfoXPos: 33,
|
||||
},
|
||||
"ca-ES": {
|
||||
starterInfoTextSize: "52px",
|
||||
ca: {
|
||||
starterInfoTextSize: "48px",
|
||||
instructionTextSize: "38px",
|
||||
starterInfoYOffset: 0.5,
|
||||
starterInfoXPos: 29,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -11,6 +11,7 @@ import {
|
||||
isNullOrUndefined,
|
||||
toReadableString,
|
||||
formatStat,
|
||||
getShinyDescriptor,
|
||||
} from "#app/utils/common";
|
||||
import type { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
|
||||
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.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 ? `/${this.pokemon.fusionVariant === 2 ? i18next.t("common:epicShiny") : this.pokemon.fusionVariant === 1 ? i18next.t("common:rareShiny") : i18next.t("common:commonShiny")}` : ""}`
|
||||
: "";
|
||||
this.shinyIcon.on("pointerover", () =>
|
||||
globalScene.ui.showTooltip(
|
||||
"",
|
||||
`${i18next.t("common:shinyOnHover")}${shinyDescriptor ? ` (${shinyDescriptor})` : ""}`,
|
||||
true,
|
||||
),
|
||||
);
|
||||
this.shinyIcon.on("pointerout", () => globalScene.ui.hideTooltip());
|
||||
let shinyDescriptor = "";
|
||||
if (doubleShiny || baseVariant) {
|
||||
shinyDescriptor = " (" + getShinyDescriptor(baseVariant);
|
||||
if (doubleShiny) {
|
||||
shinyDescriptor += "/" + getShinyDescriptor(this.pokemon.fusionVariant);
|
||||
}
|
||||
shinyDescriptor += ")";
|
||||
}
|
||||
this.shinyIcon
|
||||
.on("pointerover", () =>
|
||||
globalScene.ui.showTooltip("", i18next.t("common:shinyOnHover") + shinyDescriptor, true),
|
||||
)
|
||||
.on("pointerout", () => globalScene.ui.hideTooltip());
|
||||
}
|
||||
|
||||
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 i18next from "i18next";
|
||||
import { pokerogueApi } from "#app/plugins/api/pokerogue-api";
|
||||
import type { Variant } from "#app/sprites/variant";
|
||||
|
||||
export type nil = null | undefined;
|
||||
|
||||
@ -576,3 +577,18 @@ export function animationFileName(move: Moves): string {
|
||||
export function camelCaseToKebabCase(str: string): string {
|
||||
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 { allAbilities } from "#app/data/data-lists";
|
||||
import { ArenaTagSide } from "#app/data/arena-tag";
|
||||
import { allAbilities } from "#app/data/data-lists";
|
||||
import { ArenaTagType } from "#app/enums/arena-tag-type";
|
||||
import { BattlerTagType } from "#app/enums/battler-tag-type";
|
||||
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();
|
||||
});
|
||||
|
||||
it("should block the ally's heal bell, but only if the good as gold user is on the field", async () => {
|
||||
game.override.battleStyle("double");
|
||||
game.override.moveset([Moves.HEAL_BELL, Moves.SPLASH]);
|
||||
game.override.statusEffect(StatusEffect.BURN);
|
||||
await game.classicMode.startBattle([Species.MAGIKARP, Species.FEEBAS, Species.ABRA]);
|
||||
const [good_as_gold, ball_fetch] = game.scene.getPlayerField();
|
||||
|
||||
// Force second pokemon to have ball fetch to isolate to a single mon.
|
||||
vi.spyOn(ball_fetch, "getAbility").mockReturnValue(allAbilities[Abilities.BALL_FETCH]);
|
||||
// TODO: re-enable when heal bell is fixed
|
||||
it.todo("should block the ally's heal bell, but only if the good as gold user is on the field", async () => {
|
||||
game.override.battleStyle("double").statusEffect(StatusEffect.BURN);
|
||||
await game.classicMode.startBattle([Species.MILOTIC, Species.FEEBAS, Species.ABRA]);
|
||||
const [milotic, feebas, abra] = game.scene.getPlayerParty();
|
||||
game.field.mockAbility(milotic, Abilities.GOOD_AS_GOLD);
|
||||
game.field.mockAbility(feebas, Abilities.BALL_FETCH);
|
||||
game.field.mockAbility(abra, Abilities.BALL_FETCH);
|
||||
|
||||
// turn 1
|
||||
game.move.select(Moves.SPLASH, 0);
|
||||
game.move.select(Moves.HEAL_BELL, 1);
|
||||
game.move.use(Moves.SPLASH, 0);
|
||||
game.move.use(Moves.HEAL_BELL, 1);
|
||||
await game.toNextTurn();
|
||||
expect(good_as_gold.status?.effect).toBe(StatusEffect.BURN);
|
||||
expect(milotic.status?.effect).toBe(StatusEffect.BURN);
|
||||
|
||||
game.doSwitchPokemon(2);
|
||||
game.move.select(Moves.HEAL_BELL, 0);
|
||||
game.move.use(Moves.HEAL_BELL, 1);
|
||||
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 () => {
|
||||
game.override.battleStyle("single");
|
||||
game.override.enemyMoveset([Moves.RAIN_DANCE]);
|
||||
game.override.weather(WeatherType.NONE);
|
||||
await game.classicMode.startBattle([Species.MAGIKARP]);
|
||||
|
||||
game.move.select(Moves.SPLASH, 0);
|
||||
game.move.use(Moves.SPLASH, 0);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
|
||||
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.RAIN);
|
||||
|
@ -29,20 +29,18 @@ describe("Moves - Alluring Voice", () => {
|
||||
.disableCrits()
|
||||
.enemySpecies(Species.MAGIKARP)
|
||||
.enemyAbility(Abilities.ICE_SCALES)
|
||||
.enemyMoveset([Moves.HOWL])
|
||||
.enemyMoveset(Moves.HOWL)
|
||||
.startingLevel(10)
|
||||
.enemyLevel(10)
|
||||
.starterSpecies(Species.FEEBAS)
|
||||
.ability(Abilities.BALL_FETCH)
|
||||
.moveset([Moves.ALLURING_VOICE]);
|
||||
.ability(Abilities.BALL_FETCH);
|
||||
});
|
||||
|
||||
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()!;
|
||||
|
||||
game.move.select(Moves.ALLURING_VOICE);
|
||||
game.move.use(Moves.ALLURING_VOICE);
|
||||
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
||||
await game.phaseInterceptor.to(BerryPhase);
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { MoveResult } from "#app/field/pokemon";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
@ -22,21 +23,23 @@ describe("Moves - Chloroblast", () => {
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.moveset([Moves.CHLOROBLAST])
|
||||
.ability(Abilities.BALL_FETCH)
|
||||
.battleStyle("single")
|
||||
.disableCrits()
|
||||
.enemySpecies(Species.MAGIKARP)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyMoveset(Moves.PROTECT);
|
||||
.enemyAbility(Abilities.BALL_FETCH);
|
||||
});
|
||||
|
||||
it("should not deal recoil damage if the opponent uses protect", async () => {
|
||||
await game.classicMode.startBattle([Species.FEEBAS]);
|
||||
|
||||
game.move.select(Moves.CHLOROBLAST);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
game.move.use(Moves.CHLOROBLAST);
|
||||
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.startingLevel(1);
|
||||
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.enemySpecies(Species.BIDOOF);
|
||||
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
|
||||
*/
|
||||
|
||||
await game.startBattle();
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
game.move.select(Moves.GASTRO_ACID, 0, BattlerIndex.ENEMY);
|
||||
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 () => {
|
||||
game.override.battleStyle("single");
|
||||
|
||||
await game.startBattle();
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
game.move.select(Moves.CORE_ENFORCER);
|
||||
// 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);
|
||||
});
|
||||
|
||||
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 Phaser from "phaser";
|
||||
import GameManager from "#test/testUtils/gameManager";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { MoveResult, PokemonMove } from "#app/field/pokemon";
|
||||
import { BerryPhase } from "#app/phases/berry-phase";
|
||||
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
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 { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
||||
import { Species } from "#enums/species";
|
||||
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", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
@ -161,7 +161,6 @@ describe("Moves - Powder", () => {
|
||||
game.move.select(Moves.ROAR, 0, BattlerIndex.ENEMY_2);
|
||||
game.move.select(Moves.SPLASH, 1);
|
||||
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
|
||||
playerPokemon.hp = playerPokemon.getMaxHp();
|
||||
|
@ -5,6 +5,7 @@ import { getMoveTargets } from "#app/data/moves/move";
|
||||
import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
|
||||
import Trainer from "#app/field/trainer";
|
||||
import { GameModes, getGameMode } from "#app/game-mode";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { ModifierTypeOption, modifierTypes } from "#app/modifier/modifier-type";
|
||||
import overrides from "#app/overrides";
|
||||
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 { TurnInitPhase } from "#app/phases/turn-init-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 BattleMessageUiHandler from "#app/ui/battle-message-ui-handler";
|
||||
import type CommandUiHandler from "#app/ui/command-ui-handler";
|
||||
import type ModifierSelectUiHandler from "#app/ui/modifier-select-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 { UiMode } from "#enums/ui-mode";
|
||||
import { isNullOrUndefined } from "#app/utils/common";
|
||||
import { BattleStyle } from "#enums/battle-style";
|
||||
import { Button } from "#enums/buttons";
|
||||
@ -40,24 +39,26 @@ import type { Moves } from "#enums/moves";
|
||||
import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { PlayerGender } from "#enums/player-gender";
|
||||
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 GameWrapper from "#test/testUtils/gameWrapper";
|
||||
import { ChallengeModeHelper } from "#test/testUtils/helpers/challengeModeHelper";
|
||||
import { ClassicModeHelper } from "#test/testUtils/helpers/classicModeHelper";
|
||||
import { DailyModeHelper } from "#test/testUtils/helpers/dailyModeHelper";
|
||||
import { FieldHelper } from "#test/testUtils/helpers/field-helper";
|
||||
import { ModifierHelper } from "#test/testUtils/helpers/modifiersHelper";
|
||||
import { MoveHelper } from "#test/testUtils/helpers/moveHelper";
|
||||
import { OverridesHelper } from "#test/testUtils/helpers/overridesHelper";
|
||||
import { ReloadHelper } from "#test/testUtils/helpers/reloadHelper";
|
||||
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 TextInterceptor from "#test/testUtils/TextInterceptor";
|
||||
import { AES, enc } from "crypto-js";
|
||||
import fs from "node:fs";
|
||||
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.
|
||||
@ -76,6 +77,7 @@ export default class GameManager {
|
||||
public readonly settings: SettingsHelper;
|
||||
public readonly reload: ReloadHelper;
|
||||
public readonly modifiers: ModifierHelper;
|
||||
public readonly field: FieldHelper;
|
||||
|
||||
/**
|
||||
* Creates an instance of GameManager.
|
||||
@ -123,6 +125,7 @@ export default class GameManager {
|
||||
this.settings = new SettingsHelper(this);
|
||||
this.reload = new ReloadHelper(this);
|
||||
this.modifiers = new ModifierHelper(this);
|
||||
this.field = new FieldHelper(this);
|
||||
this.override.sanitizeOverrides();
|
||||
|
||||
// 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 target - The {@linkcode BattlerIndex} of the target against which the enemy will use the given move;
|
||||
* will use normal target selection priorities if omitted.
|
||||
* @deprecated Use {@linkcode MoveHelper.forceEnemyMove} or {@linkcode MoveHelper.selectEnemyMove}
|
||||
*/
|
||||
async forceEnemyMove(moveId: Moves, target?: BattlerIndex) {
|
||||
// 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() {
|
||||
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 { getMoveTargets } from "#app/data/moves/move";
|
||||
import { Button } from "#app/enums/buttons";
|
||||
import type Pokemon from "#app/field/pokemon";
|
||||
import { PokemonMove } from "#app/field/pokemon";
|
||||
import Overrides from "#app/overrides";
|
||||
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 { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
||||
import { Command } from "#app/ui/command-ui-handler";
|
||||
import { UiMode } from "#enums/ui-mode";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { UiMode } from "#enums/ui-mode";
|
||||
import { getMovePosition } from "#test/testUtils/gameManagerUtils";
|
||||
import { GameManagerHelper } from "#test/testUtils/helpers/gameManagerHelper";
|
||||
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},
|
||||
* 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(", ")}])!`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @param move The {@linkcode Moves} being learnt
|
||||
|
@ -1,3 +1,7 @@
|
||||
export interface MockGameObject {
|
||||
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 */
|
||||
export class MockVideoGameObject implements MockGameObject {
|
||||
public name: string;
|
||||
public active = true;
|
||||
|
||||
public play = () => null;
|
||||
public stop = () => this;
|
||||
public setOrigin = () => null;
|
||||
public setScale = () => null;
|
||||
public setVisible = () => null;
|
||||
public setLoop = () => null;
|
||||
public setOrigin = () => this;
|
||||
public setScale = () => this;
|
||||
public setVisible = () => this;
|
||||
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";
|
||||
|
||||
export default class MockContainer implements MockGameObject {
|
||||
protected x;
|
||||
protected y;
|
||||
protected x: number;
|
||||
protected y: number;
|
||||
protected scene;
|
||||
protected width;
|
||||
protected height;
|
||||
protected visible;
|
||||
private alpha;
|
||||
protected width: number;
|
||||
protected height: number;
|
||||
protected visible: boolean;
|
||||
private alpha: number;
|
||||
private style;
|
||||
public frame;
|
||||
protected textureManager;
|
||||
public list: MockGameObject[] = [];
|
||||
public name: string;
|
||||
public active = true;
|
||||
|
||||
constructor(textureManager: MockTextureManager, x, y) {
|
||||
constructor(textureManager: MockTextureManager, x: number, y: number) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.frame = {};
|
||||
this.textureManager = textureManager;
|
||||
}
|
||||
setVisible(visible) {
|
||||
setVisible(visible: boolean): this {
|
||||
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
|
||||
return this;
|
||||
}
|
||||
|
||||
removeBetween(_startIndex, _endIndex, _destroyChild) {
|
||||
removeBetween(_startIndex, _endIndex, _destroyChild): this {
|
||||
// Removes multiple children across an index range
|
||||
return this;
|
||||
}
|
||||
|
||||
addedToScene() {
|
||||
// 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.
|
||||
return this;
|
||||
}
|
||||
|
||||
setMask() {
|
||||
setMask(): this {
|
||||
/// 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.
|
||||
return this;
|
||||
}
|
||||
|
||||
setInteractive = () => null;
|
||||
setInteractive(): this {
|
||||
return this;
|
||||
}
|
||||
|
||||
setOrigin(x, y) {
|
||||
setOrigin(x = 0.5, y = x): this {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
return this;
|
||||
}
|
||||
|
||||
setAlpha(alpha) {
|
||||
setAlpha(alpha = 1): this {
|
||||
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.
|
||||
return this;
|
||||
}
|
||||
|
||||
setScale(_scale) {
|
||||
setScale(_x = 1, _y = _x): this {
|
||||
// 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.y = y;
|
||||
return this;
|
||||
}
|
||||
|
||||
setX(x) {
|
||||
setX(x = 0): this {
|
||||
this.x = x;
|
||||
return this;
|
||||
}
|
||||
|
||||
setY(y) {
|
||||
setY(y = 0): this {
|
||||
this.y = y;
|
||||
return this;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.list = [];
|
||||
}
|
||||
|
||||
setShadow(_shadowXpos, _shadowYpos, _shadowColor) {
|
||||
setShadow(_shadowXpos, _shadowYpos, _shadowColor): this {
|
||||
// Sets the shadow settings for this Game Object.
|
||||
return this;
|
||||
}
|
||||
|
||||
setLineSpacing(_lineSpacing) {
|
||||
setLineSpacing(_lineSpacing): this {
|
||||
// Sets the line spacing value of this Game Object.
|
||||
return this;
|
||||
}
|
||||
|
||||
setText(_text) {
|
||||
setText(_text): this {
|
||||
// Sets the text this Game Object will display.
|
||||
return this;
|
||||
}
|
||||
|
||||
setAngle(_angle) {
|
||||
setAngle(_angle): this {
|
||||
// Sets the angle of this Game Object.
|
||||
return this;
|
||||
}
|
||||
|
||||
setShadowOffset(_offsetX, _offsetY) {
|
||||
setShadowOffset(_offsetX, _offsetY): this {
|
||||
// Sets the shadow offset values.
|
||||
return this;
|
||||
}
|
||||
|
||||
setWordWrapWidth(_width) {
|
||||
// Sets the width (in pixels) to use for wrapping lines.
|
||||
}
|
||||
|
||||
setFontSize(_fontSize) {
|
||||
setFontSize(_fontSize): this {
|
||||
// Sets the font size of this Game Object.
|
||||
return this;
|
||||
}
|
||||
getBounds() {
|
||||
return { width: this.width, height: this.height };
|
||||
}
|
||||
|
||||
setColor(_color) {
|
||||
setColor(_color): this {
|
||||
// Sets the tint of this Game Object.
|
||||
return this;
|
||||
}
|
||||
|
||||
setShadowColor(_color) {
|
||||
setShadowColor(_color): this {
|
||||
// Sets the shadow color.
|
||||
return this;
|
||||
}
|
||||
|
||||
setTint(_color) {
|
||||
setTint(_color: this) {
|
||||
// Sets the tint of this Game Object.
|
||||
return this;
|
||||
}
|
||||
|
||||
setStrokeStyle(_thickness, _color) {
|
||||
setStrokeStyle(_thickness, _color): this {
|
||||
// Sets the stroke style for the graphics.
|
||||
return this;
|
||||
}
|
||||
|
||||
setDepth(_depth) {
|
||||
// Sets the depth of this Game Object.
|
||||
setDepth(_depth): this {
|
||||
// Sets the depth of this Game Object.\
|
||||
return this;
|
||||
}
|
||||
|
||||
setTexture(_texture) {
|
||||
// Sets the texture this Game Object will use to render with.
|
||||
setTexture(_texture): this {
|
||||
// Sets the texture this Game Object will use to render with.\
|
||||
return this;
|
||||
}
|
||||
|
||||
clearTint() {
|
||||
// Clears any previously set tint.
|
||||
clearTint(): this {
|
||||
// Clears any previously set tint.\
|
||||
return this;
|
||||
}
|
||||
|
||||
sendToBack() {
|
||||
// Sends this Game Object to the back of its parent's display list.
|
||||
sendToBack(): this {
|
||||
// Sends this Game Object to the back of its parent's display list.\
|
||||
return this;
|
||||
}
|
||||
|
||||
moveTo(_obj) {
|
||||
// Moves this Game Object to the given index in the list.
|
||||
moveTo(_obj): this {
|
||||
// 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.
|
||||
return this;
|
||||
}
|
||||
|
||||
moveBelow(_obj) {
|
||||
moveBelow(_obj): this {
|
||||
// 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;
|
||||
return this;
|
||||
}
|
||||
|
||||
bringToTop(_obj) {
|
||||
bringToTop(_obj): this {
|
||||
// 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) {
|
||||
// Adds a child to this Game Object.
|
||||
add(obj: MockGameObject | MockGameObject[]): this {
|
||||
if (Array.isArray(obj)) {
|
||||
this.list.push(...obj);
|
||||
} else {
|
||||
this.list.push(obj);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
removeAll() {
|
||||
removeAll(): this {
|
||||
// Removes all Game Objects from this Container.
|
||||
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.
|
||||
this.list.splice(index, 0, obj);
|
||||
if (!Array.isArray(obj)) {
|
||||
obj = [obj];
|
||||
}
|
||||
this.list.splice(index, 0, ...obj);
|
||||
return this;
|
||||
}
|
||||
|
||||
remove(obj) {
|
||||
const index = this.list.indexOf(obj);
|
||||
remove(obj: MockGameObject | MockGameObject[], destroyChild = false): this {
|
||||
if (!Array.isArray(obj)) {
|
||||
obj = [obj];
|
||||
}
|
||||
for (const item of obj) {
|
||||
const index = this.list.indexOf(item);
|
||||
if (index !== -1) {
|
||||
this.list.splice(index, 1);
|
||||
}
|
||||
if (destroyChild) {
|
||||
item.destroy?.();
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
getIndex(obj) {
|
||||
@ -210,15 +268,48 @@ export default class MockContainer implements MockGameObject {
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
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;
|
||||
public list: MockGameObject[] = [];
|
||||
public name: string;
|
||||
public active = true;
|
||||
constructor(textureManager, _config) {
|
||||
this.scene = textureManager.scene;
|
||||
}
|
||||
|
||||
fillStyle(_color) {
|
||||
fillStyle(_color): this {
|
||||
// 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.
|
||||
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().
|
||||
return this;
|
||||
}
|
||||
|
||||
createGeometryMask() {
|
||||
createGeometryMask(): this {
|
||||
// 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
|
||||
return this;
|
||||
}
|
||||
|
||||
addedToScene() {
|
||||
// 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.
|
||||
return this;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.list = [];
|
||||
}
|
||||
|
||||
setScale(_scale) {
|
||||
// Sets the scale of this Game Object.
|
||||
setScale(_scale): this {
|
||||
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.
|
||||
this.list.push(obj);
|
||||
return this;
|
||||
}
|
||||
|
||||
removeAll() {
|
||||
@ -90,4 +110,13 @@ export default class MockGraphics implements MockGameObject {
|
||||
getAll() {
|
||||
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;
|
||||
public list: MockGameObject[] = [];
|
||||
public name: string;
|
||||
public active = true;
|
||||
|
||||
constructor(textureManager, _x, _y, _width, _height, fillColor) {
|
||||
this.fillColor = fillColor;
|
||||
this.scene = textureManager.scene;
|
||||
}
|
||||
setOrigin(_x, _y) {}
|
||||
setOrigin(_x, _y): this {
|
||||
return this;
|
||||
}
|
||||
|
||||
setAlpha(_alpha) {}
|
||||
setVisible(_visible) {}
|
||||
setAlpha(_alpha): this {
|
||||
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
|
||||
return this;
|
||||
}
|
||||
|
||||
addedToScene() {
|
||||
// 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.
|
||||
return this;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.list = [];
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
removeAll() {
|
||||
// Removes all Game Objects from this Container.
|
||||
this.list = [];
|
||||
}
|
||||
|
||||
addAt(obj, index) {
|
||||
addAt(obj, index): this {
|
||||
// Adds a Game Object to this Container at the given index.
|
||||
this.list.splice(index, 0, obj);
|
||||
return this;
|
||||
}
|
||||
|
||||
remove(obj) {
|
||||
remove(obj): this {
|
||||
const index = this.list.indexOf(obj);
|
||||
if (index !== -1) {
|
||||
this.list.splice(index, 1);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
getIndex(obj) {
|
||||
@ -69,9 +89,17 @@ export default class MockRectangle implements MockGameObject {
|
||||
getAll() {
|
||||
return this.list;
|
||||
}
|
||||
setScale(_scale) {
|
||||
setScale(_scale): this {
|
||||
// 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 type { MockGameObject } from "../mockGameObject";
|
||||
import Sprite = Phaser.GameObjects.Sprite;
|
||||
import Frame = Phaser.Textures.Frame;
|
||||
|
||||
export default class MockSprite implements MockGameObject {
|
||||
@ -14,6 +13,7 @@ export default class MockSprite implements MockGameObject {
|
||||
public anims;
|
||||
public list: MockGameObject[] = [];
|
||||
public name: string;
|
||||
public active = true;
|
||||
constructor(textureManager, x, y, texture) {
|
||||
this.textureManager = textureManager;
|
||||
this.scene = textureManager.scene;
|
||||
@ -21,7 +21,9 @@ export default class MockSprite implements MockGameObject {
|
||||
Phaser.GameObjects.Sprite.prototype.setInteractive = this.setInteractive;
|
||||
// @ts-ignore
|
||||
Phaser.GameObjects.Sprite.prototype.setTexture = this.setTexture;
|
||||
// @ts-ignore
|
||||
Phaser.GameObjects.Sprite.prototype.setSizeToFrame = this.setSizeToFrame;
|
||||
// @ts-ignore
|
||||
Phaser.GameObjects.Sprite.prototype.setFrame = this.setFrame;
|
||||
// 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;
|
||||
}
|
||||
|
||||
setSizeToFrame(_frame?: boolean | Frame): Sprite {
|
||||
return {} as Sprite;
|
||||
setSizeToFrame(_frame?: boolean | Frame): this {
|
||||
return this;
|
||||
}
|
||||
|
||||
setPipeline(obj) {
|
||||
setPipeline(obj): this {
|
||||
// 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.
|
||||
return this.phaserSprite.setTintFill(color);
|
||||
this.phaserSprite.setTintFill(color);
|
||||
return this;
|
||||
}
|
||||
|
||||
setScale(scale) {
|
||||
return this.phaserSprite.setScale(scale);
|
||||
setScale(scale = 1): this {
|
||||
this.phaserSprite.setScale(scale);
|
||||
return this;
|
||||
}
|
||||
|
||||
setOrigin(x, y) {
|
||||
return this.phaserSprite.setOrigin(x, y);
|
||||
setOrigin(x = 0.5, y = x): this {
|
||||
this.phaserSprite.setOrigin(x, y);
|
||||
return this;
|
||||
}
|
||||
|
||||
setSize(width, height) {
|
||||
setSize(width, height): this {
|
||||
// Sets the size of this Game Object.
|
||||
return this.phaserSprite.setSize(width, height);
|
||||
this.phaserSprite.setSize(width, height);
|
||||
return this;
|
||||
}
|
||||
|
||||
once(event, callback, source) {
|
||||
return this.phaserSprite.once(event, callback, source);
|
||||
once(event, callback, source): this {
|
||||
this.phaserSprite.once(event, callback, source);
|
||||
return this;
|
||||
}
|
||||
|
||||
removeFromDisplayList() {
|
||||
removeFromDisplayList(): this {
|
||||
// same as remove or destroy
|
||||
return this.phaserSprite.removeFromDisplayList();
|
||||
this.phaserSprite.removeFromDisplayList();
|
||||
return this;
|
||||
}
|
||||
|
||||
addedToScene() {
|
||||
@ -84,114 +95,140 @@ export default class MockSprite implements MockGameObject {
|
||||
return this.phaserSprite.addedToScene();
|
||||
}
|
||||
|
||||
setVisible(visible) {
|
||||
return this.phaserSprite.setVisible(visible);
|
||||
setVisible(visible): this {
|
||||
this.phaserSprite.setVisible(visible);
|
||||
return this;
|
||||
}
|
||||
|
||||
setPosition(x, y) {
|
||||
return this.phaserSprite.setPosition(x, y);
|
||||
setPosition(x, y): this {
|
||||
this.phaserSprite.setPosition(x, y);
|
||||
return this;
|
||||
}
|
||||
|
||||
setRotation(radians) {
|
||||
return this.phaserSprite.setRotation(radians);
|
||||
setRotation(radians): this {
|
||||
this.phaserSprite.setRotation(radians);
|
||||
return this;
|
||||
}
|
||||
|
||||
stop() {
|
||||
return this.phaserSprite.stop();
|
||||
stop(): this {
|
||||
this.phaserSprite.stop();
|
||||
return this;
|
||||
}
|
||||
|
||||
setInteractive = () => null;
|
||||
|
||||
on(event, callback, source) {
|
||||
return this.phaserSprite.on(event, callback, source);
|
||||
setInteractive(): this {
|
||||
return this;
|
||||
}
|
||||
|
||||
setAlpha(alpha) {
|
||||
return this.phaserSprite.setAlpha(alpha);
|
||||
on(event, callback, source): this {
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
return this.phaserSprite.setPositionRelative(source, x, y);
|
||||
this.phaserSprite.setPositionRelative(source, x, y);
|
||||
return this;
|
||||
}
|
||||
|
||||
setY(y) {
|
||||
return this.phaserSprite.setY(y);
|
||||
setY(y: number): this {
|
||||
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.
|
||||
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.
|
||||
return this.phaserSprite.clearTint();
|
||||
this.phaserSprite.clearTint();
|
||||
return this;
|
||||
}
|
||||
|
||||
disableInteractive() {
|
||||
disableInteractive(): this {
|
||||
// Disables Interactive features of this Game Object.
|
||||
return null;
|
||||
return this;
|
||||
}
|
||||
|
||||
apply() {
|
||||
return this.phaserSprite.apply();
|
||||
this.phaserSprite.apply();
|
||||
return this;
|
||||
}
|
||||
|
||||
play() {
|
||||
play(): this {
|
||||
// return this.phaserSprite.play();
|
||||
return this;
|
||||
}
|
||||
|
||||
setPipelineData(key, value) {
|
||||
setPipelineData(key: string, value: any): this {
|
||||
this.pipelineData[key] = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
return this.phaserSprite.destroy();
|
||||
}
|
||||
|
||||
setName(name) {
|
||||
return this.phaserSprite.setName(name);
|
||||
setName(name: string): this {
|
||||
this.phaserSprite.setName(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
setAngle(angle) {
|
||||
return this.phaserSprite.setAngle(angle);
|
||||
setAngle(angle): this {
|
||||
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.
|
||||
if (Array.isArray(obj)) {
|
||||
this.list.push(...obj);
|
||||
} else {
|
||||
this.list.push(obj);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
removeAll() {
|
||||
// Removes all Game Objects from this Container.
|
||||
this.list = [];
|
||||
}
|
||||
|
||||
addAt(obj, index) {
|
||||
addAt(obj, index): this {
|
||||
// Adds a Game Object to this Container at the given index.
|
||||
this.list.splice(index, 0, obj);
|
||||
return this;
|
||||
}
|
||||
|
||||
remove(obj) {
|
||||
remove(obj): this {
|
||||
const index = this.list.indexOf(obj);
|
||||
if (index !== -1) {
|
||||
this.list.splice(index, 1);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
getIndex(obj) {
|
||||
@ -206,4 +243,14 @@ export default class MockSprite implements MockGameObject {
|
||||
getAll() {
|
||||
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 name: string;
|
||||
public color?: string;
|
||||
public active = true;
|
||||
|
||||
constructor(textureManager, _x, _y, _content, _styleOptions) {
|
||||
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;
|
||||
}
|
||||
|
||||
setShadow(_shadowXpos, _shadowYpos, _shadowColor) {
|
||||
setShadow(_shadowXpos, _shadowYpos, _shadowColor): this {
|
||||
// Sets the shadow settings for this Game Object.
|
||||
// return this.phaserText.setShadow(shadowXpos, shadowYpos, shadowColor);
|
||||
return this;
|
||||
}
|
||||
|
||||
setLineSpacing(_lineSpacing) {
|
||||
setLineSpacing(_lineSpacing): this {
|
||||
// Sets the line spacing value of this Game Object.
|
||||
// return this.phaserText.setLineSpacing(lineSpacing);
|
||||
return this;
|
||||
}
|
||||
|
||||
setOrigin(_x, _y) {
|
||||
setOrigin(_x, _y): this {
|
||||
// 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;
|
||||
}
|
||||
|
||||
off(_event, _callback, _obj) {}
|
||||
|
||||
removedFromScene() {}
|
||||
|
||||
addToDisplayList() {}
|
||||
|
||||
setStroke(_color, _thickness) {
|
||||
// Sets the stroke color and thickness.
|
||||
// return this.phaserText.setStroke(color, thickness);
|
||||
addToDisplayList(): this {
|
||||
return this;
|
||||
}
|
||||
|
||||
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
|
||||
// return this.phaserText.removeFromDisplayList();
|
||||
return this;
|
||||
}
|
||||
|
||||
addedToScene() {
|
||||
@ -150,16 +160,18 @@ export default class MockText implements MockGameObject {
|
||||
// return this.phaserText.addedToScene();
|
||||
}
|
||||
|
||||
setVisible(_visible) {
|
||||
// return this.phaserText.setVisible(visible);
|
||||
setVisible(_visible): this {
|
||||
return this;
|
||||
}
|
||||
|
||||
setY(_y) {
|
||||
setY(_y): this {
|
||||
// return this.phaserText.setY(y);
|
||||
return this;
|
||||
}
|
||||
|
||||
setX(_x) {
|
||||
setX(_x): this {
|
||||
// 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 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.
|
||||
// return this.phaserText.setText\(text);
|
||||
this.text = text;
|
||||
return this;
|
||||
}
|
||||
|
||||
setAngle(_angle) {
|
||||
setAngle(_angle): this {
|
||||
// Sets the angle of this Game Object.
|
||||
// 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.
|
||||
// return this.phaserText.setPositionRelative(source, x, y);
|
||||
return this;
|
||||
}
|
||||
|
||||
setShadowOffset(_offsetX, _offsetY) {
|
||||
setShadowOffset(_offsetX, _offsetY): this {
|
||||
// Sets the shadow offset values.
|
||||
// return this.phaserText.setShadowOffset(offsetX, offsetY);
|
||||
return this;
|
||||
}
|
||||
|
||||
setWordWrapWidth(width) {
|
||||
setWordWrapWidth(width): this {
|
||||
// Sets the width (in pixels) to use for wrapping lines.
|
||||
this.wordWrapWidth = width;
|
||||
return this;
|
||||
}
|
||||
|
||||
setFontSize(_fontSize) {
|
||||
setFontSize(_fontSize): this {
|
||||
// Sets the font size of this Game Object.
|
||||
// return this.phaserText.setFontSize(fontSize);
|
||||
return this;
|
||||
}
|
||||
|
||||
getBounds() {
|
||||
@ -209,25 +229,31 @@ export default class MockText implements MockGameObject {
|
||||
};
|
||||
}
|
||||
|
||||
setColor(color: string) {
|
||||
setColor(color: string): this {
|
||||
this.color = color;
|
||||
return this;
|
||||
}
|
||||
|
||||
setInteractive = () => null;
|
||||
setInteractive(): this {
|
||||
return this;
|
||||
}
|
||||
|
||||
setShadowColor(_color) {
|
||||
setShadowColor(_color): this {
|
||||
// Sets the shadow color.
|
||||
// return this.phaserText.setShadowColor(color);
|
||||
return this;
|
||||
}
|
||||
|
||||
setTint(_color) {
|
||||
setTint(_color): this {
|
||||
// Sets the tint of this Game Object.
|
||||
// return this.phaserText.setTint(color);
|
||||
return this;
|
||||
}
|
||||
|
||||
setStrokeStyle(_thickness, _color) {
|
||||
setStrokeStyle(_thickness, _color): this {
|
||||
// Sets the stroke style for the graphics.
|
||||
// return this.phaserText.setStrokeStyle(thickness, color);
|
||||
return this;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
@ -235,20 +261,24 @@ export default class MockText implements MockGameObject {
|
||||
this.list = [];
|
||||
}
|
||||
|
||||
setAlpha(_alpha) {
|
||||
setAlpha(_alpha): this {
|
||||
// return this.phaserText.setAlpha(alpha);
|
||||
return this;
|
||||
}
|
||||
|
||||
setName(name: string) {
|
||||
setName(name: string): this {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
setAlign(_align) {
|
||||
setAlign(_align): this {
|
||||
// return this.phaserText.setAlign(align);
|
||||
return this;
|
||||
}
|
||||
|
||||
setMask() {
|
||||
setMask(): this {
|
||||
/// Sets the mask that this Game Object will use to render with.
|
||||
return this;
|
||||
}
|
||||
|
||||
getBottomLeft() {
|
||||
@ -265,37 +295,43 @@ export default class MockText implements MockGameObject {
|
||||
};
|
||||
}
|
||||
|
||||
disableInteractive() {
|
||||
disableInteractive(): this {
|
||||
// Disables interaction with this Game Object.
|
||||
return this;
|
||||
}
|
||||
|
||||
clearTint() {
|
||||
clearTint(): this {
|
||||
// Clears tint on this Game Object.
|
||||
return this;
|
||||
}
|
||||
|
||||
add(obj) {
|
||||
add(obj): this {
|
||||
// Adds a child to this Game Object.
|
||||
this.list.push(obj);
|
||||
return this;
|
||||
}
|
||||
|
||||
removeAll() {
|
||||
removeAll(): this {
|
||||
// Removes all Game Objects from this Container.
|
||||
this.list = [];
|
||||
return this;
|
||||
}
|
||||
|
||||
addAt(obj, index) {
|
||||
addAt(obj, index): this {
|
||||
// Adds a Game Object to this Container at the given index.
|
||||
this.list.splice(index, 0, obj);
|
||||
return this;
|
||||
}
|
||||
|
||||
remove(obj) {
|
||||
remove(obj): this {
|
||||
const index = this.list.indexOf(obj);
|
||||
if (index !== -1) {
|
||||
this.list.splice(index, 1);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
getIndex(obj) {
|
||||
getIndex(obj): number {
|
||||
const index = this.list.indexOf(obj);
|
||||
return index || -1;
|
||||
}
|
||||
@ -317,5 +353,10 @@ export default class MockText implements MockGameObject {
|
||||
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) {}
|
||||
|
||||
setActive(_active: boolean): this {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ export default class MockTexture implements MockGameObject {
|
||||
public frames: object;
|
||||
public firstFrame: string;
|
||||
public name: string;
|
||||
public active: boolean;
|
||||
|
||||
constructor(manager, key: string, source) {
|
||||
this.manager = manager;
|
||||
@ -39,4 +40,14 @@ export default class MockTexture implements MockGameObject {
|
||||
getSourceImage() {
|
||||
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 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 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;
|
||||
|
@ -8,7 +8,7 @@ import { Abilities } from "#enums/abilities";
|
||||
import { Species } from "#enums/species";
|
||||
import { allSpecies, getPokemonSpecies, type PokemonForm } from "#app/data/pokemon-species";
|
||||
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 { PokemonType } from "#enums/pokemon-type";
|
||||
import { UiMode } from "#enums/ui-mode";
|
||||
|