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 commit 814a4059c2.

* Revert "Endless MBH Fix"

This reverts commit 8eb4481301.

* 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>
This commit is contained in:
NightKev 2024-08-18 18:18:43 -07:00 committed by GitHub
parent d61c8f2870
commit 098811c006
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 1154 additions and 823 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 800 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 743 B

After

Width:  |  Height:  |  Size: 793 B

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 976 B

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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