Main -> Beta (#3635)
* Fixed issue with falsy issue within condition to get a stat for IV scanner * add fix setting code to prevent form/variant bug when default form/variant setting is wrong. in addition, that fix code include gender fix, so i revert old gender fix. update wrong log message. * [Hotfix] Fix Memory Mushroom not showing relearner moves (#3619) * Fix Memory Mushroom not showing relearner moves * Fix rollout test * Rewrite player faint logic in FaintPhase (#3614) * 867 runerigus sprite (#3629) cropped static frames, fixed cropped sprite set runerigus exp to use the shiny exp's animation verified all hex colors are unchanged - fixed ultra necrozma exp front variant swapped arrays. - xatu female eye color fix * [Bug] Preventing the MBH from being stolen in Endless (#3630) * Endless MBH Fix * add import * Revert "add import" This reverts commit814a4059c2
. * Revert "Endless MBH Fix" This reverts commit8eb4481301
. * removed newline --------- Co-authored-by: Frederico Santos <frederico.f.santos@tecnico.ulisboa.pt> Co-authored-by: frutescens <info@laptop> * [Bug] Fix type-hints for immunity (#3620) * enable mock containers to be found by name * enable mock text to be found by name * add test coverage for type-hints Only for "immunity" and "status moves" * fix wrong message key of curse(ghost type) (#3631) Co-authored-by: Frederico Santos <frederico.f.santos@tecnico.ulisboa.pt> * [Hotfix] Steal-able Mini Black Hole Pt 2 (#3632) * Still have no idea where Eternatus is given the MBH.... * typedocs --------- Co-authored-by: frutescens <info@laptop> * [Hotfix] Abilities that prevent ATK drops no longer stop other stat drops (#3624) * Abilities that prevent ATK drops no longer stop other stat drops * Apply suggestions from code review Co-authored-by: Mumble <kimjoanne@protonmail.com> * Add `isNullOrUndefined()` utility function --------- * Grip Claw now shows the proper pokemon nickname (#3634) Co-authored-by: frutescens <info@laptop> --------- Co-authored-by: Opaque02 <66582645+Opaque02@users.noreply.github.com> Co-authored-by: KimJeongSun <leo@atlaslabs.ai> Co-authored-by: Frederico Santos <frederico.f.santos@tecnico.ulisboa.pt> Co-authored-by: innerthunder <168692175+innerthunder@users.noreply.github.com> Co-authored-by: cam <lrlrliwoo@gmail.com> Co-authored-by: Mumble <kimjoanne@protonmail.com> Co-authored-by: frutescens <info@laptop> Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> Co-authored-by: Enoch <enoch.jwsong@gmail.com> Co-authored-by: Mumble <171087428+frutescens@users.noreply.github.com>
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 800 B |
Before Width: | Height: | Size: 743 B After Width: | Height: | Size: 793 B |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 976 B After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
|
@ -1,21 +1,5 @@
|
||||||
{
|
{
|
||||||
"1": {
|
"1": {
|
||||||
"b0a080": "e552ec",
|
|
||||||
"f8f8e8": "ffe2ed",
|
|
||||||
"9b8259": "b021c5",
|
|
||||||
"e5e4c2": "ffb9f9",
|
|
||||||
"000000": "000000",
|
|
||||||
"bc9b4e": "900090",
|
|
||||||
"f8f8d0": "ff8ae9",
|
|
||||||
"e8e088": "ff49e7",
|
|
||||||
"d0b868": "d10cc7",
|
|
||||||
"7d673b": "510059",
|
|
||||||
"282828": "282828",
|
|
||||||
"f84040": "f84040",
|
|
||||||
"f88888": "1ae2e6",
|
|
||||||
"c81010": "00c2d2"
|
|
||||||
},
|
|
||||||
"2": {
|
|
||||||
"b0a080": "d96b23",
|
"b0a080": "d96b23",
|
||||||
"f8f8e8": "ffe1b8",
|
"f8f8e8": "ffe1b8",
|
||||||
"9b8259": "b43c06",
|
"9b8259": "b43c06",
|
||||||
|
@ -30,5 +14,21 @@
|
||||||
"f84040": "f84040",
|
"f84040": "f84040",
|
||||||
"f88888": "f88888",
|
"f88888": "f88888",
|
||||||
"c81010": "c81010"
|
"c81010": "c81010"
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"b0a080": "e552ec",
|
||||||
|
"f8f8e8": "ffe2ed",
|
||||||
|
"9b8259": "b021c5",
|
||||||
|
"e5e4c2": "ffb9f9",
|
||||||
|
"000000": "000000",
|
||||||
|
"bc9b4e": "900090",
|
||||||
|
"f8f8d0": "ff8ae9",
|
||||||
|
"e8e088": "ff49e7",
|
||||||
|
"d0b868": "d10cc7",
|
||||||
|
"7d673b": "510059",
|
||||||
|
"282828": "282828",
|
||||||
|
"f84040": "f84040",
|
||||||
|
"f88888": "1ae2e6",
|
||||||
|
"c81010": "00c2d2"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -2421,7 +2421,6 @@ export default class BattleScene extends SceneBase {
|
||||||
getEnemyModifierTypesForWave(difficultyWaveIndex, count, [ enemyPokemon ], this.currentBattle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD, upgradeChance)
|
getEnemyModifierTypesForWave(difficultyWaveIndex, count, [ enemyPokemon ], this.currentBattle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD, upgradeChance)
|
||||||
.map(mt => mt.newModifier(enemyPokemon).add(this.enemyModifiers, false, this));
|
.map(mt => mt.newModifier(enemyPokemon).add(this.enemyModifiers, false, this));
|
||||||
});
|
});
|
||||||
|
|
||||||
this.updateModifiers(false).then(() => resolve());
|
this.updateModifiers(false).then(() => resolve());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -2395,16 +2395,16 @@ export class PreStatChangeAbAttr extends AbAttr {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ProtectStatAbAttr extends PreStatChangeAbAttr {
|
export class ProtectStatAbAttr extends PreStatChangeAbAttr {
|
||||||
private protectedStat: BattleStat | null;
|
private protectedStat?: BattleStat;
|
||||||
|
|
||||||
constructor(protectedStat?: BattleStat) {
|
constructor(protectedStat?: BattleStat) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.protectedStat = protectedStat ?? null;
|
this.protectedStat = protectedStat;
|
||||||
}
|
}
|
||||||
|
|
||||||
applyPreStatChange(pokemon: Pokemon, passive: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[]): boolean {
|
applyPreStatChange(pokemon: Pokemon, passive: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[]): boolean {
|
||||||
if (!this.protectedStat || stat === this.protectedStat) {
|
if (Utils.isNullOrUndefined(this.protectedStat) || stat === this.protectedStat) {
|
||||||
cancelled.value = true;
|
cancelled.value = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4441,7 +4441,7 @@ export class CurseAttr extends MoveEffectAttr {
|
||||||
const curseRecoilDamage = Math.max(1, Math.floor(user.getMaxHp() / 2));
|
const curseRecoilDamage = Math.max(1, Math.floor(user.getMaxHp() / 2));
|
||||||
user.damageAndUpdate(curseRecoilDamage, HitResult.OTHER, false, true, true);
|
user.damageAndUpdate(curseRecoilDamage, HitResult.OTHER, false, true, true);
|
||||||
user.scene.queueMessage(
|
user.scene.queueMessage(
|
||||||
i18next.t("battle:cursedOnAdd", {
|
i18next.t("battlerTags:cursedOnAdd", {
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(user),
|
pokemonNameWithAffix: getPokemonNameWithAffix(user),
|
||||||
pokemonName: getPokemonNameWithAffix(target)
|
pokemonName: getPokemonNameWithAffix(target)
|
||||||
})
|
})
|
||||||
|
|
|
@ -921,7 +921,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||||
* by how many learnable moves there are for the {@linkcode Pokemon}.
|
* by how many learnable moves there are for the {@linkcode Pokemon}.
|
||||||
*/
|
*/
|
||||||
getLearnableLevelMoves(): Moves[] {
|
getLearnableLevelMoves(): Moves[] {
|
||||||
let levelMoves = this.getLevelMoves(1, true).map(lm => lm[1]);
|
let levelMoves = this.getLevelMoves(1, true, false, true).map(lm => lm[1]);
|
||||||
if (this.metBiome === -1 && !this.scene.gameMode.isFreshStartChallenge() && !this.scene.gameMode.isDaily) {
|
if (this.metBiome === -1 && !this.scene.gameMode.isFreshStartChallenge() && !this.scene.gameMode.isDaily) {
|
||||||
levelMoves = this.getUnlockedEggMoves().concat(levelMoves);
|
levelMoves = this.getUnlockedEggMoves().concat(levelMoves);
|
||||||
}
|
}
|
||||||
|
@ -1210,11 +1210,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||||
*
|
*
|
||||||
* @param source - The Pokémon using the move.
|
* @param source - The Pokémon using the move.
|
||||||
* @param move - The move being used.
|
* @param move - The move being used.
|
||||||
* @returns The type damage multiplier or undefined if it's a status move
|
* @returns The type damage multiplier or 1 if it's a status move
|
||||||
*/
|
*/
|
||||||
getMoveEffectiveness(source: Pokemon, move: PokemonMove): TypeDamageMultiplier | undefined {
|
getMoveEffectiveness(source: Pokemon, move: PokemonMove): TypeDamageMultiplier {
|
||||||
if (move.getMove().category === MoveCategory.STATUS) {
|
if (move.getMove().category === MoveCategory.STATUS) {
|
||||||
return undefined;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.getAttackMoveEffectiveness(source, move, !this.battleData?.abilityRevealed);
|
return this.getAttackMoveEffectiveness(source, move, !this.battleData?.abilityRevealed);
|
||||||
|
|
|
@ -2414,7 +2414,7 @@ export class ContactHeldItemTransferChanceModifier extends HeldItemTransferModif
|
||||||
}
|
}
|
||||||
|
|
||||||
getTransferMessage(pokemon: Pokemon, targetPokemon: Pokemon, item: ModifierTypes.ModifierType): string {
|
getTransferMessage(pokemon: Pokemon, targetPokemon: Pokemon, item: ModifierTypes.ModifierType): string {
|
||||||
return i18next.t("modifier:contactHeldItemTransferApply", { pokemonNameWithAffix: getPokemonNameWithAffix(targetPokemon), itemName: item.name, pokemonName: pokemon.name, typeName: this.type.name });
|
return i18next.t("modifier:contactHeldItemTransferApply", { pokemonNameWithAffix: getPokemonNameWithAffix(targetPokemon), itemName: item.name, pokemonName: getPokemonNameWithAffix(pokemon), typeName: this.type.name });
|
||||||
}
|
}
|
||||||
|
|
||||||
getMaxHeldItemCount(pokemon: Pokemon): integer {
|
getMaxHeldItemCount(pokemon: Pokemon): integer {
|
||||||
|
|
|
@ -878,6 +878,10 @@ export class EncounterPhase extends BattlePhase {
|
||||||
} else if (!(battle.waveIndex % 1000)) {
|
} else if (!(battle.waveIndex % 1000)) {
|
||||||
enemyPokemon.formIndex = 1;
|
enemyPokemon.formIndex = 1;
|
||||||
enemyPokemon.updateScale();
|
enemyPokemon.updateScale();
|
||||||
|
const bossMBH = this.scene.findModifier(m => m instanceof TurnHeldItemTransferModifier && m.pokemonId === enemyPokemon.id, false) as TurnHeldItemTransferModifier;
|
||||||
|
this.scene.removeModifier(bossMBH!);
|
||||||
|
bossMBH?.setTransferrableFalse();
|
||||||
|
this.scene.addEnemyModifier(bossMBH!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4033,13 +4037,24 @@ export class FaintPhase extends PokemonPhase {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.player) {
|
if (this.player) {
|
||||||
const nonFaintedLegalPartyMembers = this.scene.getParty().filter(p => p.isAllowedInBattle());
|
/** The total number of Pokemon in the player's party that can legally fight */
|
||||||
const nonFaintedPartyMemberCount = nonFaintedLegalPartyMembers.length;
|
const legalPlayerPokemon = this.scene.getParty().filter(p => p.isAllowedInBattle());
|
||||||
if (!nonFaintedPartyMemberCount) {
|
/** The total number of legal player Pokemon that aren't currently on the field */
|
||||||
|
const legalPlayerPartyPokemon = legalPlayerPokemon.filter(p => !p.isActive(true));
|
||||||
|
if (!legalPlayerPokemon.length) {
|
||||||
|
/** If the player doesn't have any legal Pokemon, end the game */
|
||||||
this.scene.unshiftPhase(new GameOverPhase(this.scene));
|
this.scene.unshiftPhase(new GameOverPhase(this.scene));
|
||||||
} else if (nonFaintedPartyMemberCount === 1 && this.scene.currentBattle.double) {
|
} else if (this.scene.currentBattle.double && legalPlayerPokemon.length === 1 && legalPlayerPartyPokemon.length === 0) {
|
||||||
|
/**
|
||||||
|
* If the player has exactly one Pokemon in total at this point in a double battle, and that Pokemon
|
||||||
|
* is already on the field, unshift a phase that moves that Pokemon to center position.
|
||||||
|
*/
|
||||||
this.scene.unshiftPhase(new ToggleDoublePositionPhase(this.scene, true));
|
this.scene.unshiftPhase(new ToggleDoublePositionPhase(this.scene, true));
|
||||||
} else if (nonFaintedPartyMemberCount >= this.scene.currentBattle.getBattlerCount()) {
|
} else if (legalPlayerPartyPokemon.length > 0) {
|
||||||
|
/**
|
||||||
|
* If previous conditions weren't met, and the player has at least 1 legal Pokemon off the field,
|
||||||
|
* push a phase that prompts the player to summon a Pokemon from their party.
|
||||||
|
*/
|
||||||
this.scene.pushPhase(new SwitchPhase(this.scene, this.fieldIndex, true, false));
|
this.scene.pushPhase(new SwitchPhase(this.scene, this.fieldIndex, true, false));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { BattleStat } from "#app/data/battle-stat";
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import GameManager from "#test/utils/gameManager";
|
||||||
|
import { getMovePosition } from "#test/utils/gameManagerUtils";
|
||||||
|
import { SPLASH_ONLY } from "#test/utils/testUtils";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
describe("Abilities - Hyper Cutter", () => {
|
||||||
|
let phaserGame: Phaser.Game;
|
||||||
|
let game: GameManager;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
phaserGame = new Phaser.Game({
|
||||||
|
type: Phaser.HEADLESS,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
game.phaseInterceptor.restoreOg();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
game = new GameManager(phaserGame);
|
||||||
|
game.override
|
||||||
|
.battleType("single")
|
||||||
|
.moveset([Moves.SAND_ATTACK, Moves.NOBLE_ROAR, Moves.DEFOG, Moves.OCTOLOCK])
|
||||||
|
.ability(Abilities.BALL_FETCH)
|
||||||
|
.enemySpecies(Species.SHUCKLE)
|
||||||
|
.enemyAbility(Abilities.HYPER_CUTTER)
|
||||||
|
.enemyMoveset(SPLASH_ONLY);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reference Link: https://bulbapedia.bulbagarden.net/wiki/Hyper_Cutter_(Ability)
|
||||||
|
|
||||||
|
it("only prevents ATK drops", async () => {
|
||||||
|
await game.startBattle();
|
||||||
|
|
||||||
|
const enemy = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
|
game.doAttack(getMovePosition(game.scene, 0, Moves.OCTOLOCK));
|
||||||
|
await game.toNextTurn();
|
||||||
|
game.doAttack(getMovePosition(game.scene, 0, Moves.DEFOG));
|
||||||
|
await game.toNextTurn();
|
||||||
|
game.doAttack(getMovePosition(game.scene, 0, Moves.NOBLE_ROAR));
|
||||||
|
await game.toNextTurn();
|
||||||
|
game.doAttack(getMovePosition(game.scene, 0, Moves.SAND_ATTACK));
|
||||||
|
await game.toNextTurn();
|
||||||
|
game.override.moveset([Moves.STRING_SHOT]);
|
||||||
|
game.doAttack(getMovePosition(game.scene, 0, Moves.STRING_SHOT));
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(enemy.summonData.battleStats[BattleStat.ATK]).toEqual(0);
|
||||||
|
[BattleStat.ACC, BattleStat.DEF, BattleStat.EVA, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD].forEach((stat: number) => expect(enemy.summonData.battleStats[stat]).toBeLessThan(0));
|
||||||
|
});
|
||||||
|
});
|
|
@ -12,6 +12,7 @@ import { SPLASH_ONLY } from "#test/utils/testUtils";
|
||||||
describe("Moves - Rollout", () => {
|
describe("Moves - Rollout", () => {
|
||||||
let phaserGame: Phaser.Game;
|
let phaserGame: Phaser.Game;
|
||||||
let game: GameManager;
|
let game: GameManager;
|
||||||
|
const TIMEOUT = 20 * 1000;
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
phaserGame = new Phaser.Game({
|
phaserGame = new Phaser.Game({
|
||||||
|
@ -77,5 +78,5 @@ describe("Moves - Rollout", () => {
|
||||||
// reset
|
// reset
|
||||||
expect(turn6Dmg).toBeGreaterThanOrEqual(turn1Dmg - variance);
|
expect(turn6Dmg).toBeGreaterThanOrEqual(turn1Dmg - variance);
|
||||||
expect(turn6Dmg).toBeLessThanOrEqual(turn1Dmg + variance);
|
expect(turn6Dmg).toBeLessThanOrEqual(turn1Dmg + variance);
|
||||||
});
|
}, TIMEOUT);
|
||||||
});
|
});
|
||||||
|
|
|
@ -87,7 +87,6 @@ describe("UI - Transfer Items", () => {
|
||||||
handler.processInput(Button.ACTION); // select Pokemon
|
handler.processInput(Button.ACTION); // select Pokemon
|
||||||
|
|
||||||
expect(handler.optionsContainer.list.some((option) => (option as BBCodeText).text?.includes("Transfer"))).toBe(true);
|
expect(handler.optionsContainer.list.some((option) => (option as BBCodeText).text?.includes("Transfer"))).toBe(true);
|
||||||
|
|
||||||
game.phaseInterceptor.unlock();
|
game.phaseInterceptor.unlock();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
import { Button } from "#app/enums/buttons.js";
|
||||||
|
import { Moves } from "#app/enums/moves";
|
||||||
|
import { Species } from "#app/enums/species";
|
||||||
|
import { CommandPhase } from "#app/phases";
|
||||||
|
import FightUiHandler from "#app/ui/fight-ui-handler.js";
|
||||||
|
import { Mode } from "#app/ui/ui.js";
|
||||||
|
import GameManager from "#test/utils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
import MockText from "../utils/mocks/mocksContainer/mockText";
|
||||||
|
import { SPLASH_ONLY } from "../utils/testUtils";
|
||||||
|
|
||||||
|
describe("UI - Type Hints", () => {
|
||||||
|
let phaserGame: Phaser.Game;
|
||||||
|
let game: GameManager;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
phaserGame = new Phaser.Game({
|
||||||
|
type: Phaser.HEADLESS,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
game.phaseInterceptor.restoreOg();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
game = new GameManager(phaserGame);
|
||||||
|
game.settings.typeHints(true); //activate type hints
|
||||||
|
game.override.battleType("single").startingLevel(100).startingWave(1).enemyMoveset(SPLASH_ONLY);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("check immunity color", async () => {
|
||||||
|
game.override
|
||||||
|
.battleType("single")
|
||||||
|
.startingLevel(100)
|
||||||
|
.startingWave(1)
|
||||||
|
.enemySpecies(Species.FLORGES)
|
||||||
|
.enemyMoveset(SPLASH_ONLY)
|
||||||
|
.moveset([Moves.DRAGON_CLAW]);
|
||||||
|
game.settings.typeHints(true); //activate type hints
|
||||||
|
|
||||||
|
await game.startBattle([Species.RAYQUAZA]);
|
||||||
|
|
||||||
|
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
|
||||||
|
const { ui } = game.scene;
|
||||||
|
const handler = ui.getHandler<FightUiHandler>();
|
||||||
|
handler.processInput(Button.ACTION); // select "Fight"
|
||||||
|
game.phaseInterceptor.unlock();
|
||||||
|
});
|
||||||
|
|
||||||
|
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
|
||||||
|
const { ui } = game.scene;
|
||||||
|
const movesContainer = ui.getByName<Phaser.GameObjects.Container>(FightUiHandler.MOVES_CONTAINER_NAME);
|
||||||
|
const dragonClawText = movesContainer
|
||||||
|
.getAll<Phaser.GameObjects.Text>()
|
||||||
|
.find((text) => text.text === "Dragon Claw")! as unknown as MockText;
|
||||||
|
|
||||||
|
expect.soft(dragonClawText.color).toBe("#929292");
|
||||||
|
ui.getHandler().processInput(Button.ACTION);
|
||||||
|
});
|
||||||
|
await game.phaseInterceptor.to(CommandPhase);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("check status move color", async () => {
|
||||||
|
game.override.enemySpecies(Species.FLORGES).moveset([Moves.GROWL]);
|
||||||
|
|
||||||
|
await game.startBattle([Species.RAYQUAZA]);
|
||||||
|
|
||||||
|
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
|
||||||
|
const { ui } = game.scene;
|
||||||
|
const handler = ui.getHandler<FightUiHandler>();
|
||||||
|
handler.processInput(Button.ACTION); // select "Fight"
|
||||||
|
game.phaseInterceptor.unlock();
|
||||||
|
});
|
||||||
|
|
||||||
|
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
|
||||||
|
const { ui } = game.scene;
|
||||||
|
const movesContainer = ui.getByName<Phaser.GameObjects.Container>(FightUiHandler.MOVES_CONTAINER_NAME);
|
||||||
|
const growlText = movesContainer
|
||||||
|
.getAll<Phaser.GameObjects.Text>()
|
||||||
|
.find((text) => text.text === "Growl")! as unknown as MockText;
|
||||||
|
|
||||||
|
expect.soft(growlText.color).toBe(undefined);
|
||||||
|
ui.getHandler().processInput(Button.ACTION);
|
||||||
|
});
|
||||||
|
await game.phaseInterceptor.to(CommandPhase);
|
||||||
|
});
|
||||||
|
});
|
|
@ -30,6 +30,7 @@ import { MoveHelper } from "./helpers/moveHelper";
|
||||||
import { vi } from "vitest";
|
import { vi } from "vitest";
|
||||||
import { ClassicModeHelper } from "./helpers/classicModeHelper";
|
import { ClassicModeHelper } from "./helpers/classicModeHelper";
|
||||||
import { DailyModeHelper } from "./helpers/dailyModeHelper";
|
import { DailyModeHelper } from "./helpers/dailyModeHelper";
|
||||||
|
import { SettingsHelper } from "./helpers/settingsHelper";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class to manage the game state and transitions between phases.
|
* Class to manage the game state and transitions between phases.
|
||||||
|
@ -44,6 +45,7 @@ export default class GameManager {
|
||||||
public readonly move: MoveHelper;
|
public readonly move: MoveHelper;
|
||||||
public readonly classicMode: ClassicModeHelper;
|
public readonly classicMode: ClassicModeHelper;
|
||||||
public readonly dailyMode: DailyModeHelper;
|
public readonly dailyMode: DailyModeHelper;
|
||||||
|
public readonly settings: SettingsHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance of GameManager.
|
* Creates an instance of GameManager.
|
||||||
|
@ -63,6 +65,7 @@ export default class GameManager {
|
||||||
this.move = new MoveHelper(this);
|
this.move = new MoveHelper(this);
|
||||||
this.classicMode = new ClassicModeHelper(this);
|
this.classicMode = new ClassicModeHelper(this);
|
||||||
this.dailyMode = new DailyModeHelper(this);
|
this.dailyMode = new DailyModeHelper(this);
|
||||||
|
this.settings = new SettingsHelper(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { GameManagerHelper } from "./gameManagerHelper";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to handle settings for tests
|
||||||
|
*/
|
||||||
|
export class SettingsHelper extends GameManagerHelper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable/Enable type hints settings
|
||||||
|
* @param enable true to enabled, false to disabled
|
||||||
|
*/
|
||||||
|
typeHints(enable: boolean) {
|
||||||
|
this.game.scene.typeHints = enable;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import MockTextureManager from "#test/utils/mocks/mockTextureManager";
|
import MockTextureManager from "#test/utils/mocks/mockTextureManager";
|
||||||
|
import { vi } from "vitest";
|
||||||
import { MockGameObject } from "../mockGameObject";
|
import { MockGameObject } from "../mockGameObject";
|
||||||
|
|
||||||
export default class MockContainer implements MockGameObject {
|
export default class MockContainer implements MockGameObject {
|
||||||
|
@ -13,6 +14,7 @@ export default class MockContainer implements MockGameObject {
|
||||||
public frame;
|
public frame;
|
||||||
protected textureManager;
|
protected textureManager;
|
||||||
public list: MockGameObject[] = [];
|
public list: MockGameObject[] = [];
|
||||||
|
private name?: string;
|
||||||
|
|
||||||
constructor(textureManager: MockTextureManager, x, y) {
|
constructor(textureManager: MockTextureManager, x, y) {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
|
@ -159,9 +161,10 @@ export default class MockContainer implements MockGameObject {
|
||||||
// Moves this Game Object to be below the given Game Object in the display list.
|
// Moves this Game Object to be below the given Game Object in the display list.
|
||||||
}
|
}
|
||||||
|
|
||||||
setName(name) {
|
setName = vi.fn((name: string) => {
|
||||||
|
this.name = name;
|
||||||
// return this.phaserSprite.setName(name);
|
// return this.phaserSprite.setName(name);
|
||||||
}
|
});
|
||||||
|
|
||||||
bringToTop(obj) {
|
bringToTop(obj) {
|
||||||
// Brings this Game Object to the top of its parents display list.
|
// Brings this Game Object to the top of its parents display list.
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import UI from "#app/ui/ui";
|
import UI from "#app/ui/ui";
|
||||||
|
import { vi } from "vitest";
|
||||||
import { MockGameObject } from "../mockGameObject";
|
import { MockGameObject } from "../mockGameObject";
|
||||||
|
|
||||||
export default class MockText implements MockGameObject {
|
export default class MockText implements MockGameObject {
|
||||||
|
@ -10,6 +11,8 @@ export default class MockText implements MockGameObject {
|
||||||
public list: MockGameObject[] = [];
|
public list: MockGameObject[] = [];
|
||||||
public style;
|
public style;
|
||||||
public text = "";
|
public text = "";
|
||||||
|
private name?: string;
|
||||||
|
public color?: string;
|
||||||
|
|
||||||
constructor(textureManager, x, y, content, styleOptions) {
|
constructor(textureManager, x, y, content, styleOptions) {
|
||||||
this.scene = textureManager.scene;
|
this.scene = textureManager.scene;
|
||||||
|
@ -190,10 +193,9 @@ export default class MockText implements MockGameObject {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
setColor(color) {
|
setColor = vi.fn((color: string) => {
|
||||||
// Sets the tint of this Game Object.
|
this.color = color;
|
||||||
// return this.phaserText.setColor(color);
|
});
|
||||||
}
|
|
||||||
|
|
||||||
setShadowColor(color) {
|
setShadowColor(color) {
|
||||||
// Sets the shadow color.
|
// Sets the shadow color.
|
||||||
|
@ -219,9 +221,9 @@ export default class MockText implements MockGameObject {
|
||||||
// return this.phaserText.setAlpha(alpha);
|
// return this.phaserText.setAlpha(alpha);
|
||||||
}
|
}
|
||||||
|
|
||||||
setName(name) {
|
setName = vi.fn((name: string) => {
|
||||||
// return this.phaserText.setName(name);
|
this.name = name;
|
||||||
}
|
});
|
||||||
|
|
||||||
setAlign(align) {
|
setAlign(align) {
|
||||||
// return this.phaserText.setAlign(align);
|
// return this.phaserText.setAlign(align);
|
||||||
|
|
|
@ -226,7 +226,7 @@ export default class BattleMessageUiHandler extends MessageUiHandler {
|
||||||
highestIv = ivs[s];
|
highestIv = ivs[s];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (shownStat) {
|
if (shownStat !== null && shownStat !== undefined) {
|
||||||
shownStats.push(shownStat);
|
shownStats.push(shownStat);
|
||||||
statsPool.splice(statsPool.indexOf(shownStat), 1);
|
statsPool.splice(statsPool.indexOf(shownStat), 1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,8 @@ import {Button} from "#enums/buttons";
|
||||||
import Pokemon, { PokemonMove } from "#app/field/pokemon.js";
|
import Pokemon, { PokemonMove } from "#app/field/pokemon.js";
|
||||||
|
|
||||||
export default class FightUiHandler extends UiHandler {
|
export default class FightUiHandler extends UiHandler {
|
||||||
|
public static readonly MOVES_CONTAINER_NAME = "moves";
|
||||||
|
|
||||||
private movesContainer: Phaser.GameObjects.Container;
|
private movesContainer: Phaser.GameObjects.Container;
|
||||||
private moveInfoContainer: Phaser.GameObjects.Container;
|
private moveInfoContainer: Phaser.GameObjects.Container;
|
||||||
private typeIcon: Phaser.GameObjects.Sprite;
|
private typeIcon: Phaser.GameObjects.Sprite;
|
||||||
|
@ -35,7 +37,7 @@ export default class FightUiHandler extends UiHandler {
|
||||||
const ui = this.getUi();
|
const ui = this.getUi();
|
||||||
|
|
||||||
this.movesContainer = this.scene.add.container(18, -38.7);
|
this.movesContainer = this.scene.add.container(18, -38.7);
|
||||||
this.movesContainer.setName("moves");
|
this.movesContainer.setName(FightUiHandler.MOVES_CONTAINER_NAME);
|
||||||
ui.add(this.movesContainer);
|
ui.add(this.movesContainer);
|
||||||
|
|
||||||
this.moveInfoContainer = this.scene.add.container(1, 0);
|
this.moveInfoContainer = this.scene.add.container(1, 0);
|
||||||
|
@ -271,11 +273,10 @@ export default class FightUiHandler extends UiHandler {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const moveColors = opponents.map((opponent) => {
|
const moveColors = opponents
|
||||||
return opponent.getMoveEffectiveness(pokemon, pokemonMove);
|
.map((opponent) => opponent.getMoveEffectiveness(pokemon, pokemonMove))
|
||||||
}).filter((eff) => !!eff).sort((a, b) => b - a).map((effectiveness) => {
|
.sort((a, b) => b - a)
|
||||||
return getTypeDamageMultiplierColor(effectiveness, "offense");
|
.map((effectiveness) => getTypeDamageMultiplierColor(effectiveness ?? 0, "offense"));
|
||||||
});
|
|
||||||
|
|
||||||
return moveColors[0];
|
return moveColors[0];
|
||||||
}
|
}
|
||||||
|
|
|
@ -2916,14 +2916,18 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||||
const isCaught = this.scene.gameData.dexData[species.speciesId]?.caughtAttr || BigInt(0);
|
const isCaught = this.scene.gameData.dexData[species.speciesId]?.caughtAttr || BigInt(0);
|
||||||
const isVariant3Caught = !!(isCaught & DexAttr.VARIANT_3);
|
const isVariant3Caught = !!(isCaught & DexAttr.VARIANT_3);
|
||||||
const isVariant2Caught = !!(isCaught & DexAttr.VARIANT_2);
|
const isVariant2Caught = !!(isCaught & DexAttr.VARIANT_2);
|
||||||
|
const isDefaultVariantCaught = !!(isCaught & DexAttr.DEFAULT_VARIANT);
|
||||||
const isVariantCaught = !!(isCaught & DexAttr.SHINY);
|
const isVariantCaught = !!(isCaught & DexAttr.SHINY);
|
||||||
const isMaleCaught = !!(isCaught & DexAttr.MALE);
|
const isMaleCaught = !!(isCaught & DexAttr.MALE);
|
||||||
const isFemaleCaught = !!(isCaught & DexAttr.FEMALE);
|
const isFemaleCaught = !!(isCaught & DexAttr.FEMALE);
|
||||||
|
|
||||||
|
const starterAttributes = this.starterPreferences[species.speciesId];
|
||||||
|
|
||||||
|
const props = this.scene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId));
|
||||||
|
const defaultAbilityIndex = this.scene.gameData.getStarterSpeciesDefaultAbilityIndex(species);
|
||||||
|
const defaultNature = this.scene.gameData.getSpeciesDefaultNature(species);
|
||||||
|
|
||||||
if (!dexEntry.caughtAttr) {
|
if (!dexEntry.caughtAttr) {
|
||||||
const props = this.scene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId));
|
|
||||||
const defaultAbilityIndex = this.scene.gameData.getStarterSpeciesDefaultAbilityIndex(species);
|
|
||||||
const defaultNature = this.scene.gameData.getSpeciesDefaultNature(species);
|
|
||||||
if (shiny === undefined || shiny !== props.shiny) {
|
if (shiny === undefined || shiny !== props.shiny) {
|
||||||
shiny = props.shiny;
|
shiny = props.shiny;
|
||||||
}
|
}
|
||||||
|
@ -2942,6 +2946,83 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||||
if (natureIndex === undefined || natureIndex !== defaultNature) {
|
if (natureIndex === undefined || natureIndex !== defaultNature) {
|
||||||
natureIndex = defaultNature;
|
natureIndex = defaultNature;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// compare current shiny, formIndex, female, variant, abilityIndex, natureIndex with the caught ones
|
||||||
|
// if the current ones are not caught, we need to find the next caught ones
|
||||||
|
if (shiny) {
|
||||||
|
if (!(isVariantCaught || isVariant2Caught || isVariant3Caught)) {
|
||||||
|
shiny = false;
|
||||||
|
starterAttributes.shiny = false;
|
||||||
|
variant = 0;
|
||||||
|
starterAttributes.variant = 0;
|
||||||
|
} else {
|
||||||
|
shiny = true;
|
||||||
|
starterAttributes.shiny = true;
|
||||||
|
if (variant === 0 && !isDefaultVariantCaught) {
|
||||||
|
if (isVariant2Caught) {
|
||||||
|
variant = 1;
|
||||||
|
starterAttributes.variant = 1;
|
||||||
|
} else if (isVariant3Caught) {
|
||||||
|
variant = 2;
|
||||||
|
starterAttributes.variant = 2;
|
||||||
|
} else {
|
||||||
|
variant = 0;
|
||||||
|
starterAttributes.variant = 0;
|
||||||
|
}
|
||||||
|
} else if (variant === 1 && !isVariant2Caught) {
|
||||||
|
if (isVariantCaught) {
|
||||||
|
variant = 0;
|
||||||
|
starterAttributes.variant = 0;
|
||||||
|
} else if (isVariant3Caught) {
|
||||||
|
variant = 2;
|
||||||
|
starterAttributes.variant = 2;
|
||||||
|
} else {
|
||||||
|
variant = 0;
|
||||||
|
starterAttributes.variant = 0;
|
||||||
|
}
|
||||||
|
} else if (variant === 2 && !isVariant3Caught) {
|
||||||
|
if (isVariantCaught) {
|
||||||
|
variant = 0;
|
||||||
|
starterAttributes.variant = 0;
|
||||||
|
} else if (isVariant2Caught) {
|
||||||
|
variant = 1;
|
||||||
|
starterAttributes.variant = 1;
|
||||||
|
} else {
|
||||||
|
variant = 0;
|
||||||
|
starterAttributes.variant = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (female) {
|
||||||
|
if (!isFemaleCaught) {
|
||||||
|
female = false;
|
||||||
|
starterAttributes.female = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!isMaleCaught) {
|
||||||
|
female = true;
|
||||||
|
starterAttributes.female = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (species.forms) {
|
||||||
|
const formCount = species.forms.length;
|
||||||
|
let newFormIndex = formIndex??0;
|
||||||
|
if (species.forms[newFormIndex]) {
|
||||||
|
const isValidForm = species.forms[newFormIndex].isStarterSelectable && dexEntry.caughtAttr & this.scene.gameData.getFormAttr(newFormIndex);
|
||||||
|
if (!isValidForm) {
|
||||||
|
do {
|
||||||
|
newFormIndex = (newFormIndex + 1) % formCount;
|
||||||
|
if (species.forms[newFormIndex].isStarterSelectable && dexEntry.caughtAttr & this.scene.gameData.getFormAttr(newFormIndex)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while (newFormIndex !== props.formIndex);
|
||||||
|
formIndex = newFormIndex;
|
||||||
|
starterAttributes.form = formIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.shinyOverlay.setVisible(shiny ?? false); // TODO: is false the correct default?
|
this.shinyOverlay.setVisible(shiny ?? false); // TODO: is false the correct default?
|
||||||
|
@ -2993,12 +3074,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dexEntry.caughtAttr && species.malePercent !== null) {
|
if (dexEntry.caughtAttr && species.malePercent !== null) {
|
||||||
let gender: Gender;
|
const gender = !female ? Gender.MALE : Gender.FEMALE;
|
||||||
if ((female && isFemaleCaught) || (!female && !isMaleCaught)) {
|
|
||||||
gender = Gender.FEMALE;
|
|
||||||
} else {
|
|
||||||
gender = Gender.MALE;
|
|
||||||
}
|
|
||||||
this.pokemonGenderText.setText(getGenderSymbol(gender));
|
this.pokemonGenderText.setText(getGenderSymbol(gender));
|
||||||
this.pokemonGenderText.setColor(getGenderColor(gender));
|
this.pokemonGenderText.setColor(getGenderColor(gender));
|
||||||
this.pokemonGenderText.setShadowColor(getGenderColor(gender, true));
|
this.pokemonGenderText.setShadowColor(getGenderColor(gender, true));
|
||||||
|
@ -3479,7 +3555,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||||
|
|
||||||
checkIconId(icon: Phaser.GameObjects.Sprite, species: PokemonSpecies, female: boolean, formIndex: number, shiny: boolean, variant: number) {
|
checkIconId(icon: Phaser.GameObjects.Sprite, species: PokemonSpecies, female: boolean, formIndex: number, shiny: boolean, variant: number) {
|
||||||
if (icon.frame.name !== species.getIconId(female, formIndex, shiny, variant)) {
|
if (icon.frame.name !== species.getIconId(female, formIndex, shiny, variant)) {
|
||||||
console.log(`${species.name}'s variant icon does not exist. Replacing with default.`);
|
console.log(`${species.name}'s icon ${icon.frame.name} does not match getIconId with female: ${female}, formIndex: ${formIndex}, shiny: ${shiny}, variant: ${variant}`);
|
||||||
icon.setTexture(species.getIconAtlasKey(formIndex, false, variant));
|
icon.setTexture(species.getIconAtlasKey(formIndex, false, variant));
|
||||||
icon.setFrame(species.getIconId(female, formIndex, false, variant));
|
icon.setFrame(species.getIconId(female, formIndex, false, variant));
|
||||||
}
|
}
|
||||||
|
|
|
@ -552,3 +552,11 @@ export function capitalizeString(str: string, sep: string, lowerFirstChar: boole
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if an object is null or undefined
|
||||||
|
* @param object
|
||||||
|
*/
|
||||||
|
export function isNullOrUndefined(object: any): boolean {
|
||||||
|
return null === object || undefined === object;
|
||||||
|
}
|
||||||
|
|