Merge branch 'beta' into pokemon-info-box-formName-enhancement

This commit is contained in:
flx-sta 2024-09-17 19:28:26 -07:00 committed by GitHub
commit 5f562ba4b9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 156 additions and 36 deletions

View File

@ -55,7 +55,7 @@ Check out [Github Issues](https://github.com/pagefaultgames/pokerogue/issues) to
- Pokémon Sword/Shield
- Pokémon Legends: Arceus
- Pokémon Scarlet/Violet
- Firel (Custom Laboratory, Metropolis, Seabed, and Space biome music)
- Firel (Custom Ice Cave, Laboratory, Metropolis, Plains, Power Plant, Seabed, Space, and Volcano biome music)
- Lmz (Custom Jungle biome music)
- Andr06 (Custom Slum and Sea biome music)

View File

@ -26,10 +26,36 @@ body {
#app {
display: flex;
justify-content: center;
align-items: center;
}
#app > div:first-child {
transform-origin: top !important;
transform-origin: center !important;
}
/*
Supports automatic vertical centering as suggested in PR#1114, but only via CSS
Condition factorized to deduce CSS rules:
true if (isLandscape && !isMobile() && !hasTouchscreen() || (hasTouchscreen() && !isTouchControlsEnabled))
*/
/* isLandscape && !isMobile() && !hasTouchscreen() */
@media (orientation: landscape) and (pointer: fine) {
#app {
align-items: center;
}
}
@media (pointer: coarse) {
/* hasTouchscreen() && !isTouchControlsEnabled */
body:has(> #touchControls[class=visible]) #app {
align-items: start;
}
body:has(> #touchControls[class=visible]) #app > div:first-child {
transform-origin: top !important;
}
}
#layout:fullscreen #dpad, #layout:fullscreen {

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -2625,7 +2625,11 @@ export class PreStatStageChangeAbAttr extends AbAttr {
}
}
/**
* Protect one or all {@linkcode BattleStat} from reductions caused by other Pokémon's moves and Abilities
*/
export class ProtectStatAbAttr extends PreStatStageChangeAbAttr {
/** {@linkcode BattleStat} to protect or `undefined` if **all** {@linkcode BattleStat} are protected */
private protectedStat?: BattleStat;
constructor(protectedStat?: BattleStat) {
@ -2634,7 +2638,17 @@ export class ProtectStatAbAttr extends PreStatStageChangeAbAttr {
this.protectedStat = protectedStat;
}
applyPreStatStageChange(_pokemon: Pokemon, _passive: boolean, simulated: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, _args: any[]): boolean {
/**
* Apply the {@linkcode ProtectedStatAbAttr} to an interaction
* @param _pokemon
* @param _passive
* @param simulated
* @param stat the {@linkcode BattleStat} being affected
* @param cancelled The {@linkcode Utils.BooleanHolder} that will be set to true if the stat is protected
* @param _args
* @returns true if the stat is protected, false otherwise
*/
applyPreStatStageChange(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, _args: any[]): boolean {
if (Utils.isNullOrUndefined(this.protectedStat) || stat === this.protectedStat) {
cancelled.value = true;
return true;
@ -3757,7 +3771,7 @@ export class StatStageChangeMultiplierAbAttr extends AbAttr {
this.multiplier = multiplier;
}
apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
(args[0] as Utils.IntegerHolder).value *= this.multiplier;
return true;

View File

@ -488,14 +488,14 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise<void> {
} else {
moveAnims.set(move, null);
const defaultMoveAnim = allMoves[move] instanceof AttackMove ? Moves.TACKLE : allMoves[move] instanceof SelfStatusMove ? Moves.FOCUS_ENERGY : Moves.TAIL_WHIP;
const moveName = Moves[move].toLowerCase().replace(/\_/g, "-");
const fetchAnimAndResolve = (move: Moves) => {
scene.cachedFetch(`./battle-anims/${moveName}.json`)
scene.cachedFetch(`./battle-anims/${Utils.animationFileName(move)}.json`)
.then(response => {
const contentType = response.headers.get("content-type");
if (!response.ok || contentType?.indexOf("application/json") === -1) {
console.error(`Could not load animation file for move '${moveName}'`, response.status, response.statusText);
populateMoveAnim(move, moveAnims.get(defaultMoveAnim));
useDefaultAnim(move, defaultMoveAnim);
logMissingMoveAnim(move, response.status, response.statusText);
return resolve();
}
return response.json();
@ -515,6 +515,11 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise<void> {
} else {
resolve();
}
})
.catch(error => {
useDefaultAnim(move, defaultMoveAnim);
logMissingMoveAnim(move, error);
return resolve();
});
};
fetchAnimAndResolve(move);
@ -522,6 +527,29 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise<void> {
});
}
/**
* Populates the default animation for the given move.
*
* @param move the move to populate an animation for
* @param defaultMoveAnim the move to use as the default animation
*/
function useDefaultAnim(move: Moves, defaultMoveAnim: Moves) {
populateMoveAnim(move, moveAnims.get(defaultMoveAnim));
}
/**
* Helper method for printing a warning to the console when a move animation is missing.
*
* @param move the move to populate an animation for
* @param optionalParams parameters to add to the error logging
*
* @remarks use {@linkcode useDefaultAnim} to use a default animation
*/
function logMissingMoveAnim(move: Moves, ...optionalParams: any[]) {
const moveName = Utils.animationFileName(move);
console.warn(`Could not load animation file for move '${moveName}'`, ...optionalParams);
}
/**
* Fetches animation configs to be used in a Mystery Encounter
* @param scene

View File

@ -746,7 +746,7 @@ export class Arena {
case Biome.TOWN:
return 7.288;
case Biome.PLAINS:
return 7.693;
return 17.485;
case Biome.GRASS:
return 1.995;
case Biome.TALL_GRASS:
@ -774,13 +774,13 @@ export class Arena {
case Biome.DESERT:
return 1.143;
case Biome.ICE_CAVE:
return 15.010;
return 0.000;
case Biome.MEADOW:
return 3.891;
case Biome.POWER_PLANT:
return 2.810;
return 9.447;
case Biome.VOLCANO:
return 5.116;
return 17.637;
case Biome.GRAVEYARD:
return 3.232;
case Biome.DOJO:

View File

@ -1273,13 +1273,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* @param attrType {@linkcode AbAttr} The ability attribute to check for.
* @param canApply {@linkcode Boolean} If false, it doesn't check whether the ability is currently active
* @param ignoreOverride {@linkcode Boolean} If true, it ignores ability changing effects
* @returns {AbAttr[]} A list of all the ability attributes on this ability.
* @returns A list of all the ability attributes on this ability.
*/
getAbilityAttrs(attrType: { new(...args: any[]): AbAttr }, canApply: boolean = true, ignoreOverride?: boolean): AbAttr[] {
const abilityAttrs: AbAttr[] = [];
getAbilityAttrs<T extends AbAttr = AbAttr>(attrType: { new(...args: any[]): T }, canApply: boolean = true, ignoreOverride?: boolean): T[] {
const abilityAttrs: T[] = [];
if (!canApply || this.canApplyAbility()) {
abilityAttrs.push(...this.getAbility(ignoreOverride).getAttrs(attrType));
abilityAttrs.push(...this.getAbility(ignoreOverride).getAttrs<T>(attrType));
}
if (!canApply || this.canApplyAbility(true)) {

View File

@ -108,7 +108,7 @@
"forest": "PMD EoS Dusk Forest",
"grass": "PMD EoS Apple Woods",
"graveyard": "PMD EoS Mystifying Forest",
"ice_cave": "PMD EoS Vast Ice Mountain",
"ice_cave": "Firel - -60F",
"island": "PMD EoS Craggy Coast",
"jungle": "Lmz - Jungle",
"laboratory": "Firel - Laboratory",
@ -116,8 +116,8 @@
"meadow": "PMD EoS Sky Peak Forest",
"metropolis": "Firel - Metropolis",
"mountain": "PMD EoS Mt. Horn",
"plains": "PMD EoS Sky Peak Prairie",
"power_plant": "PMD EoS Far Amp Plains",
"plains": "Firel - Route 888",
"power_plant": "Firel - The Klink",
"ruins": "PMD EoS Deep Sealed Ruin",
"sea": "Andr06 - Marine Mystique",
"seabed": "Firel - Seabed",
@ -128,7 +128,7 @@
"tall_grass": "PMD EoS Foggy Forest",
"temple": "PMD EoS Aegis Cave",
"town": "PMD EoS Random Dungeon Theme 3",
"volcano": "PMD EoS Steam Cave",
"volcano": "Firel - Twisturn Volcano",
"wasteland": "PMD EoS Hidden Highland",
"encounter_ace_trainer": "BW Trainers' Eyes Meet (Ace Trainer)",
"encounter_backpacker": "BW Trainers' Eyes Meet (Backpacker)",

View File

@ -13,8 +13,10 @@
"ALL": "전부",
"PASS_BATON": "배턴터치한다",
"UNPAUSE_EVOLUTION": "진화 재개",
"PAUSE_EVOLUTION": "진화 중지",
"REVIVE": "되살린다",
"RENAME": "닉네임 바꾸기",
"SELECT": "선택한다",
"choosePokemon": "포켓몬을 선택하세요.",
"doWhatWithThisPokemon": "포켓몬을 어떻게 하겠습니까?",
"noEnergy": "{{pokemonName}}[[는]] 싸울 수 있는\n기력이 남아 있지 않습니다!",
@ -23,6 +25,7 @@
"tooManyItems": "{{pokemonName}}[[는]] 지닌 도구의 수가\n너무 많습니다",
"anyEffect": "써도 효과가 없다.",
"unpausedEvolutions": "{{pokemonName}}의 진화가 재개되었다.",
"pausedEvolutions": "{{pokemonName}}[[가]] 진화하지 않도록 했다.",
"unspliceConfirmation": "{{pokemonName}}로부터 {{fusionName}}의 융합을 해제하시겠습니까?\n{{fusionName}}는 사라지게 됩니다.",
"wasReverted": "{{fusionName}}은 {{pokemonName}}의 모습으로 돌아갔습니다!",
"releaseConfirmation": "{{pokemonName}}[[를]]\n정말 놓아주겠습니까?",

View File

@ -11,6 +11,10 @@
"expGainsSpeed": "경험치 획득 속도",
"expPartyDisplay": "파티 경험치 표시",
"skipSeenDialogues": "본 대화 생략",
"eggSkip": "알 스킵",
"never": "안 함",
"always": "항상",
"ask": "확인하기",
"battleStyle": "시합 룰",
"enableRetries": "재도전 허용",
"hideIvs": "개체값탐지기 효과 끄기",

View File

@ -6,7 +6,7 @@ import Pokemon from "#app/field/pokemon";
import { getPokemonNameWithAffix } from "#app/messages";
import { ResetNegativeStatStageModifier } from "#app/modifier/modifier";
import { handleTutorial, Tutorial } from "#app/tutorial";
import * as Utils from "#app/utils";
import { NumberHolder, BooleanHolder } from "#app/utils";
import i18next from "i18next";
import { PokemonPhase } from "./pokemon-phase";
import { Stat, type BattleStat, getStatKey, getStatStageChangeDescriptionKey } from "#enums/stat";
@ -42,17 +42,23 @@ export class StatStageChangePhase extends PokemonPhase {
return this.end();
}
const stages = new NumberHolder(this.stages);
if (!this.ignoreAbilities) {
applyAbAttrs(StatStageChangeMultiplierAbAttr, pokemon, null, false, stages);
}
let simulate = false;
const filteredStats = this.stats.filter(stat => {
const cancelled = new Utils.BooleanHolder(false);
const cancelled = new BooleanHolder(false);
if (!this.selfTarget && this.stages < 0) {
if (!this.selfTarget && stages.value < 0) {
// TODO: Include simulate boolean when tag applications can be simulated
this.scene.arena.applyTagsForSide(MistTag, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, cancelled);
}
if (!cancelled.value && !this.selfTarget && this.stages < 0) {
if (!cancelled.value && !this.selfTarget && stages.value < 0) {
applyPreStatStageChangeAbAttrs(ProtectStatAbAttr, pokemon, stat, cancelled, simulate);
}
@ -64,12 +70,6 @@ export class StatStageChangePhase extends PokemonPhase {
return !cancelled.value;
});
const stages = new Utils.IntegerHolder(this.stages);
if (!this.ignoreAbilities) {
applyAbAttrs(StatStageChangeMultiplierAbAttr, pokemon, null, false, stages);
}
const relLevels = filteredStats.map(s => (stages.value >= 1 ? Math.min(pokemon.getStatStage(s) + stages.value, 6) : Math.max(pokemon.getStatStage(s) + stages.value, -6)) - pokemon.getStatStage(s));
this.onChange && this.onChange(this.getPokemon(), filteredStats, relLevels);

View File

@ -31,7 +31,7 @@ describe("Abilities - Contrary", () => {
});
it("should invert stat changes when applied", async() => {
await game.startBattle([
await game.classicMode.startBattle([
Species.SLOWBRO
]);
@ -39,4 +39,39 @@ describe("Abilities - Contrary", () => {
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
}, 20000);
describe("With Clear Body", () => {
it("should apply positive effects", async () => {
game.override
.enemyPassiveAbility(Abilities.CLEAR_BODY)
.moveset([Moves.TAIL_WHIP]);
await game.classicMode.startBattle([Species.SLOWBRO]);
const enemyPokemon = game.scene.getEnemyPokemon()!;
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
game.move.select(Moves.TAIL_WHIP);
await game.phaseInterceptor.to("TurnEndPhase");
expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(1);
});
it("should block negative effects", async () => {
game.override
.enemyPassiveAbility(Abilities.CLEAR_BODY)
.enemyMoveset([Moves.HOWL, Moves.HOWL, Moves.HOWL, Moves.HOWL])
.moveset([Moves.SPLASH]);
await game.classicMode.startBattle([Species.SLOWBRO]);
const enemyPokemon = game.scene.getEnemyPokemon()!;
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to("TurnEndPhase");
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
});
});
});

View File

@ -162,7 +162,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
this.splicedIcon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 12, 15), Phaser.Geom.Rectangle.Contains);
this.add(this.splicedIcon);
this.statusIndicator = this.scene.add.sprite(0, 0, `statuses_${i18next.resolvedLanguage}`);
this.statusIndicator = this.scene.add.sprite(0, 0, Utils.getLocalizedSpriteKey("statuses"));
this.statusIndicator.setName("icon_status");
this.statusIndicator.setVisible(false);
this.statusIndicator.setOrigin(0, 0);

View File

@ -91,7 +91,7 @@ export default class MoveInfoOverlay extends Phaser.GameObjects.Container implem
valuesBg.setOrigin(0, 0);
this.val.add(valuesBg);
this.typ = this.scene.add.sprite(25, EFF_HEIGHT - 35, `types${Utils.verifyLang(i18next.language) ? `_${i18next.language}` : ""}`, "unknown");
this.typ = this.scene.add.sprite(25, EFF_HEIGHT - 35, Utils.getLocalizedSpriteKey("types"), "unknown");
this.typ.setScale(0.8);
this.val.add(this.typ);
@ -138,7 +138,7 @@ export default class MoveInfoOverlay extends Phaser.GameObjects.Container implem
this.pow.setText(move.power >= 0 ? move.power.toString() : "---");
this.acc.setText(move.accuracy >= 0 ? move.accuracy.toString() : "---");
this.pp.setText(move.pp >= 0 ? move.pp.toString() : "---");
this.typ.setTexture(`types${Utils.verifyLang(i18next.language) ? `_${i18next.language}` : ""}`, Type[move.type].toLowerCase());
this.typ.setTexture(Utils.getLocalizedSpriteKey("types"), Type[move.type].toLowerCase());
this.cat.setFrame(MoveCategory[move.category].toLowerCase());
this.desc.setText(move?.effect || "");

View File

@ -1272,7 +1272,7 @@ class PartySlot extends Phaser.GameObjects.Container {
}
if (this.pokemon.status) {
const statusIndicator = this.scene.add.sprite(0, 0, `statuses_${i18next.resolvedLanguage}`);
const statusIndicator = this.scene.add.sprite(0, 0, Utils.getLocalizedSpriteKey("statuses"));
statusIndicator.setFrame(StatusEffect[this.pokemon.status?.effect].toLowerCase());
statusIndicator.setOrigin(0, 0);
statusIndicator.setPositionRelative(slotLevelLabel, this.slotIndex >= battlerCount ? 43 : 55, 0);

View File

@ -214,7 +214,7 @@ export default class SummaryUiHandler extends UiHandler {
this.statusContainer.add(statusLabel);
this.status = this.scene.add.sprite(91, 4, `statuses_${i18next.resolvedLanguage}`);
this.status = this.scene.add.sprite(91, 4, Utils.getLocalizedSpriteKey("statuses"));
this.status.setOrigin(0.5, 0);
this.statusContainer.add(this.status);

View File

@ -1,4 +1,5 @@
import { MoneyFormat } from "#enums/money-format";
import { Moves } from "#enums/moves";
import i18next from "i18next";
export const MissingTextureKey = "__MISSING";
@ -628,3 +629,12 @@ export function getLocalizedSpriteKey(baseKey: string) {
export function isBetween(num: number, min: number, max: number): boolean {
return num >= min && num <= max;
}
/**
* Helper method to return the animation filename for a given move
*
* @param move the move for which the animation filename is needed
*/
export function animationFileName(move: Moves): string {
return Moves[move].toLowerCase().replace(/\_/g, "-");
}