Compare commits

...

13 Commits

Author SHA1 Message Date
Ei
589d06c15e
[Bug] Fixed reviving while transformed not resetting the sprite (#2152)
* Fixed reviving while transformed not resetting the sprite

* Made reload assets in resetStatus optional
2024-06-12 18:08:15 -04:00
Bastien
da37069457
[Localization] [FR] Update Lass dialogue.ts (#2133)
* [Localization] [FR] Update dialogue.ts

* Changes and correct typo

* Correct typo

* Typo

* Typo

* type again

* correct typo
2024-06-12 16:05:23 -04:00
Adrian T
8e8b006c49
[Bug] add condition before applying IgnoreTypeImmunityAbAttr (#2150) 2024-06-12 16:02:28 -04:00
Dmitriy K
777a8ad9ea
update gameManager switchPokemon to match other menu utilities (#2147) 2024-06-12 20:49:07 +02:00
Zach Day
31406b35fd
[Move] Fix type immunities granted by abilities only applying to attacks (#2145)
* Fix type immunity given by abilities only applying to attacking moves

* Add tests for type immunity granted by abilities

* Use Sap Sipper as base for testing
2024-06-12 14:46:45 -04:00
Adrian T
23caea1766
[Bug] hotfix type hint (#2146) 2024-06-12 14:39:37 -04:00
Dakurei
85c77b7445
[UI] Fix the display size of inputs, which is MUCH too small, and should be dependent on screen size rather than a fixed value (#2143) 2024-06-12 13:49:19 -04:00
Adrian T
fe7ed72afa
[Enhancement] add revealed ability flag (#2105) 2024-06-12 13:30:10 -04:00
José Ricardo Fleury Oliveira
a2638e4a79
[Localization][pt] updated passive pop up (#2141) 2024-06-12 13:14:21 -04:00
flx-sta
ed495673f3
[Bug] prevent iOS input-auto zoom by adding font-size: 16px to inputs (#2137) 2024-06-12 13:11:30 -04:00
Bastien
4139e11b11
[Localization] [FR] Update swimmer dialogue.ts (#2134) 2024-06-12 13:04:02 -04:00
José Ricardo Fleury Oliveira
1b19e35e60
[Localization][pt] Updated trainers (#2048) 2024-06-12 12:56:22 -04:00
GoldTra
03a635610b
[Localization] Update Spanish abilty bar and fix fainted localization (#2136) 2024-06-12 11:43:33 -04:00
14 changed files with 215 additions and 48 deletions

View File

@ -31,6 +31,11 @@ body {
transform-origin: top !important;
}
/* Need adjust input font-size */
input {
font-size: 3.2rem;
}
#touchControls:not(.visible) {
display: none;
}

View File

@ -9,7 +9,7 @@ import { BattlerTag } from "./battler-tags";
import { BattlerTagType } from "./enums/battler-tag-type";
import { StatusEffect, getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "./status-effect";
import { Gender } from "./gender";
import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, StatusMoveTypeImmunityAttr, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, IncrementMovePriorityAttr } from "./move";
import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, IncrementMovePriorityAttr } from "./move";
import { ArenaTagSide, ArenaTrapTag } from "./arena-tag";
import { ArenaTagType } from "./enums/arena-tag-type";
import { Stat, getStatName } from "./pokemon-stat";
@ -357,6 +357,7 @@ export class TypeImmunityAbAttr extends PreDefendAbAttr {
}
/**
* Applies immunity if this ability grants immunity to the type of the given move.
* @param pokemon {@linkcode Pokemon} the defending Pokemon
* @param passive N/A
* @param attacker {@linkcode Pokemon} the attacking Pokemon
@ -366,7 +367,12 @@ export class TypeImmunityAbAttr extends PreDefendAbAttr {
* @param args [1] {@linkcode Utils.NumberHolder} type of move being defended against in case it has changed from default type
*/
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if ((move instanceof AttackMove || move.getAttrs(StatusMoveTypeImmunityAttr).find(attr => attr.immuneType === this.immuneType)) && move.type === this.immuneType) {
// Field moves should ignore immunity
if ([ MoveTarget.BOTH_SIDES, MoveTarget.ENEMY_SIDE, MoveTarget.USER_SIDE ].includes(move.moveTarget)) {
return false;
}
if (move.type === this.immuneType) {
(args[0] as Utils.NumberHolder).value = 0;
return true;
}

View File

@ -1111,7 +1111,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return undefined;
}
return this.getAttackMoveEffectiveness(source, move, true);
return this.getAttackMoveEffectiveness(source, move, !this.battleData?.abilityRevealed);
}
/**
@ -1146,7 +1146,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
let multiplier = types.map(defType => {
if (source) {
const ignoreImmunity = new Utils.BooleanHolder(false);
if (source.isActive(true) && source.hasAbilityWithAttr(IgnoreTypeImmunityAbAttr)) {
applyAbAttrs(IgnoreTypeImmunityAbAttr, source, ignoreImmunity, moveType, defType);
}
if (ignoreImmunity.value) {
return 1;
}
@ -2525,8 +2527,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* Resets the status of a pokemon.
* @param revive Whether revive should be cured; defaults to true.
* @param confusion Whether resetStatus should include confusion or not; defaults to false.
* @param reloadAssets Whether to reload the assets or not; defaults to false.
*/
resetStatus(revive: boolean = true, confusion: boolean = false): void {
resetStatus(revive: boolean = true, confusion: boolean = false, reloadAssets: boolean = false): void {
const lastStatus = this.status?.effect;
if (!revive && lastStatus === StatusEffect.FAINT) {
return;
@ -2543,6 +2546,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.lapseTag(BattlerTagType.CONFUSED);
}
}
if (reloadAssets) {
this.loadAssets(false).then(() => this.playAnim());
}
}
primeSummonData(summonDataPrimer: PokemonSummonData): void {
@ -3821,6 +3827,7 @@ export class PokemonBattleData {
public endured: boolean = false;
public berriesEaten: BerryType[] = [];
public abilitiesApplied: Abilities[] = [];
public abilityRevealed: boolean = false;
}
export class PokemonBattleSummonData {

View File

@ -61,5 +61,5 @@ export const battle: SimpleTranslationEntries = {
"useMove": "¡{{pokemonNameWithAffix}} usó {{moveName}}!",
"drainMessage": "¡{{pokemonName}} tuvo su\nenergía absorbida!",
"regainHealth": "¡{{pokemonName}} recuperó\nPS!",
"fainted": El {{pokemonNameWithAffix}} se debilitó!"
"fainted": {{pokemonNameWithAffix}} se debilitó!"
} as const;

View File

@ -4,6 +4,6 @@ export const fightUiHandler: SimpleTranslationEntries = {
"pp": "PP",
"power": "Potencia",
"accuracy": "Precisión",
"abilityFlyInText": " {{pokemonName}}'s {{passive}}{{abilityName}}",
"passive": "Passive ", // The space at the end is important
"abilityFlyInText": " {{passive}}{{pokemonName}}\n{{abilityName}}",
"passive": "Pasiva de ", // The space at the end is important
} as const;

View File

@ -36,26 +36,26 @@ export const PGMdialogue: DialogueTranslationEntries = {
},
"lass": {
"encounter": {
1: "Let's have a battle, shall we?",
2: "You look like a new trainer. Let's have a battle!",
3: "I don't recognize you. How about a battle?",
4: "Let's have a fun Pokémon battle!",
5: "I'll show you the ropes of how to really use Pokémon!",
6: "A serious battle starts from a serious beginning! Are you sure you're ready?",
7: "You're only young once. And you only get one shot at a given battle. Soon, you'll be nothing but a memory.",
8: "You'd better go easy on me, OK? Though I'll be seriously fighting!",
9: "School is boring. I've got nothing to do. Yawn. I'm only battling to kill the time."
1: "Affrontons-nous, d'accord ?",
2: "Tu as l'air d'un nouveau dresseur. Battons nous !",
3: "Je ne te reconnais pas. Que dirais-tu de te battre ?",
4: "Amusons-nous pendant ce combat Pokémon !",
5: "Je vais t'apprendre à te battre avec des Pokémon !",
6: "Un combat doit être pris au sérieux. Es-tu prêt à te battre ?",
7: "Tu ne seras pas jeune éternellement. Tu n'auras qu'une chance pendant un combat. Bientôt, tu ne seras plus qu'un souvenir.",
8: "Tu ferais mieux d'y aller doucement avec moi. Mais je vais me battre sérieusement !",
9: "Je m'ennuie à l'école. Je n'ai rien à faire. *Baille*. Je me bats juste pour passer le temps."
},
"victory": {
1: "That was impressive! I've got a lot to learn.",
2: "I didn't think you'd beat me that bad…",
3: "I hope we get to have a rematch some day.",
4: "That was pretty amazingly fun! You've totally exhausted me…",
5: "You actually taught me a lesson! You're pretty amazing!",
6: "Seriously, I lost. That is, like, seriously depressing, but you were seriously cool.",
7: "I don't need memories like this. Deleting memory…",
8: "Hey! I told you to go easy on me! Still, you're pretty cool when you're serious.",
9: "I'm actually getting tired of battling… There's gotta be something new to do…"
1: "Wow, c'était impressionnant. J'ai beaucoup à apprendre.",
2: "Je ne pensais pas que je perdrais comme ça…",
3: "J'espère que j'aurai ma revanche un jour.",
4: "C'était super amusant ! Ce combat m'a épuisé…",
5: "Tu m'as appris une belle leçon ! Tu es vraiment incroyable !",
6: "Vraiment ? J'ai perdu… C'est des choses qui arrivent, ça me déprime mais tu es vraiment très cool.",
7: "Je n'ai pas besoin de souvenirs comme ça. *Suppression de la mémoire…*",
8: "Hé ! Je t'avais dit d'y aller doucement avec moi ! Mais tu es vraiment trop cool quand tu te bats sérieusement.",
9: "J'en ai marre des combats Pokémon… Il doit y avoir de nouvelles choses à faire…"
}
},
"breeder": {
@ -118,14 +118,14 @@ export const PGMdialogue: DialogueTranslationEntries = {
},
"swimmer": {
"encounter": {
1: "Time to dive in!",
2: "Let's ride the waves of victory!",
3: "Ready to make a splash!",
1: "C'est l'heure de plonger dans le combat !",
2: "Je vais surfer sur les vagues de la victoire !",
3: "Je vais t'éclabousser de mon talent !",
},
"victory": {
1: "Drenched in defeat!",
2: "A wave of defeat!",
3: "Back to shore, I guess.",
1: "Tu m'as complètement séché",
2: "Il semblerait que j'ai surfé sur les vagues de la défaite...",
3: "Retour sur la terre ferme je suppose",
},
},
"backpacker": {

View File

@ -4,6 +4,6 @@ export const fightUiHandler: SimpleTranslationEntries = {
"pp": "PP",
"power": "Poder",
"accuracy": "Precisão",
"abilityFlyInText": " {{pokemonName}}'s {{passive}}{{abilityName}}",
"passive": "Passive ", // The space at the end is important
"abilityFlyInText": " {{passive}}{{pokemonName}}\n{{abilityName}}",
"passive": "Passiva de ", // The space at the end is important
} as const;

View File

@ -13,6 +13,12 @@ export const titles: SimpleTranslationEntries = {
"rival": "Rival",
"professor": "Professor",
"frontier_brain": "Cérebro da Fronteira",
"rocket_boss": "Chefe da Equipe Rocket",
"magma_boss": "Chefe da Equipe Magma",
"aqua_boss": "Chefe da Equipe Aqua",
"galactic_boss": "Chefe da Equipe Galáctica",
"plasma_boss": "Chefe da Equipe Plasma",
"flare_boss": "Chefe da Equipe Flare",
// Maybe if we add the evil teams we can add "Team Rocket" and "Team Aqua" etc. here as well as "Team Rocket Boss" and "Team Aqua Admin" etc.
} as const;
@ -119,6 +125,18 @@ export const trainerClasses: SimpleTranslationEntries = {
"worker_female": "Operária",
"workers": "Operários",
"youngster": "Jovem",
"rocket_grunt": "Recruta da Equipe Rocket",
"rocket_grunt_female": "Recruta da Equipe Rocket",
"magma_grunt": "Recruta da Equipe Magma",
"magma_grunt_female": "Recruta da Equipe Magma",
"aqua_grunt": "Recruta da Equipe Aqua",
"aqua_grunt_female": "Recruta da Equipe Aqua",
"galactic_grunt": "Recruta da Equipe Galáctica",
"galactic_grunt_female": "Recruta da Equipe Galáctica",
"plasma_grunt": "Recruta da Equipe Plasma",
"plasma_grunt_female": "Recruta da Equipe Plasma",
"flare_grunt": "Recruta da Equipe Flare",
"flare_grunt_female": "Recruta da Equipe Flare",
} as const;
// Names of special trainers like gym leaders, elite four, and the champion
@ -247,6 +265,11 @@ export const trainerNames: SimpleTranslationEntries = {
"leon": "Leon",
"rival": "Finn",
"rival_female": "Ivy",
"maxie": "Maxie",
"archie": "Archie",
"cyrus": "Cyrus",
"ghetsis": "Ghetsis",
"lysandre": "Lysandre",
// Double Names
"blue_red_double": "Blue & Red",

View File

@ -1091,8 +1091,7 @@ export class PokemonInstantReviveModifier extends PokemonHeldItemModifier {
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(),
Math.max(Math.floor(pokemon.getMaxHp() / 2), 1), getPokemonMessage(pokemon, ` was revived\nby its ${this.type.name}!`), false, false, true));
pokemon.resetStatus();
pokemon.resetStatus(true, false, true);
return true;
}

View File

@ -3122,7 +3122,12 @@ export class ShowAbilityPhase extends PokemonPhase {
start() {
super.start();
this.scene.abilityBar.showAbility(this.getPokemon(), this.passive);
const pokemon = this.getPokemon();
this.scene.abilityBar.showAbility(pokemon, this.passive);
if (pokemon.battleData) {
pokemon.battleData.abilityRevealed = true;
}
this.end();
}

View File

@ -56,7 +56,9 @@ describe("Abilities - Intimidate", () => {
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
await game.switchPokemon(1);
game.doSwitchPokemon(1);
await game.phaseInterceptor.run(CommandPhase);
await game.phaseInterceptor.to(CommandPhase);
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.POOCHYENA);
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
@ -81,7 +83,9 @@ describe("Abilities - Intimidate", () => {
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
await game.switchPokemon(1);
game.doSwitchPokemon(1);
await game.phaseInterceptor.run(CommandPhase);
await game.phaseInterceptor.to(CommandPhase);
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.POOCHYENA);
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
@ -106,7 +110,9 @@ describe("Abilities - Intimidate", () => {
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
await game.switchPokemon(1);
game.doSwitchPokemon(1);
await game.phaseInterceptor.run(CommandPhase);
await game.phaseInterceptor.to(CommandPhase);
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.POOCHYENA);
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;

View File

@ -0,0 +1,116 @@
import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
import Phaser from "phaser";
import GameManager from "#app/test/utils/gameManager";
import * as overrides from "#app/overrides";
import {Species} from "#app/data/enums/species";
import {
CommandPhase,
EnemyCommandPhase, TurnEndPhase,
} from "#app/phases";
import {Mode} from "#app/ui/ui";
import {Moves} from "#app/data/enums/moves";
import {getMovePosition} from "#app/test/utils/gameManagerUtils";
import {Command} from "#app/ui/command-ui-handler";
import { Abilities } from "#app/data/enums/abilities.js";
import { BattleStat } from "#app/data/battle-stat.js";
import { TerrainType } from "#app/data/terrain.js";
// See also: ArenaTypeAbAttr
describe("Abilities - Sap Sipper", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "NEVER_CRIT_OVERRIDE", "get").mockReturnValue(true);
});
it("raise attack 1 level and block effects when activated against a grass attack", async() => {
const moveToUse = Moves.LEAFAGE;
const enemyAbility = Abilities.SAP_SIPPER;
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH, Moves.NONE, Moves.NONE, Moves.NONE]);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.DUSKULL);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(enemyAbility);
await game.startBattle();
const startingOppHp = game.scene.currentBattle.enemyParty[0].hp;
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
const movePosition = getMovePosition(game.scene, 0, moveToUse);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnEndPhase);
expect(startingOppHp - game.scene.getEnemyParty()[0].hp).toBe(0);
expect(game.scene.getEnemyParty()[0].summonData.battleStats[BattleStat.ATK]).toBe(1);
});
it("raise attack 1 level and block effects when activated against a grass status move", async() => {
const moveToUse = Moves.SPORE;
const enemyAbility = Abilities.SAP_SIPPER;
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH, Moves.NONE, Moves.NONE, Moves.NONE]);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(enemyAbility);
await game.startBattle();
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
const movePosition = getMovePosition(game.scene, 0, moveToUse);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnEndPhase);
expect(game.scene.getEnemyParty()[0].status).toBeUndefined();
expect(game.scene.getEnemyParty()[0].summonData.battleStats[BattleStat.ATK]).toBe(1);
});
it("do not activate against status moves that target the field", async() => {
const moveToUse = Moves.GRASSY_TERRAIN;
const enemyAbility = Abilities.SAP_SIPPER;
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH, Moves.NONE, Moves.NONE, Moves.NONE]);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(enemyAbility);
await game.startBattle();
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
const movePosition = getMovePosition(game.scene, 0, moveToUse);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnEndPhase);
expect(game.scene.arena.terrain).toBeDefined();
expect(game.scene.arena.terrain.terrainType).toBe(TerrainType.GRASSY);
expect(game.scene.getEnemyParty()[0].summonData.battleStats[BattleStat.ATK]).toBe(0);
});
});

View File

@ -74,11 +74,11 @@ describe("Moves - Spikes", () => {
await game.phaseInterceptor.to(CommandPhase, false);
const initialHp = game.scene.getParty()[0].hp;
await game.switchPokemon(1, false);
game.doSwitchPokemon(1);
await game.phaseInterceptor.run(CommandPhase);
await game.phaseInterceptor.to(CommandPhase, false);
await game.switchPokemon(1, false);
game.doSwitchPokemon(1);
await game.phaseInterceptor.run(CommandPhase);
await game.phaseInterceptor.to(CommandPhase, false);

View File

@ -290,16 +290,16 @@ export default class GameManager {
});
}
async switchPokemon(pokemonIndex: number, toNext: boolean = true) {
/**
* Switch pokemon and transition to the enemy command phase
* @param pokemonIndex the index of the pokemon in your party to switch to
*/
doSwitchPokemon(pokemonIndex: number) {
this.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
this.scene.ui.setMode(Mode.PARTY, PartyUiMode.SWITCH, (this.scene.getCurrentPhase() as CommandPhase).getPokemon().getFieldIndex(), null, PartyUiHandler.FilterNonFainted);
});
this.onNextPrompt("CommandPhase", Mode.PARTY, () => {
(this.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.POKEMON, pokemonIndex, false);
});
if (toNext) {
await this.phaseInterceptor.run(CommandPhase);
await this.phaseInterceptor.to(CommandPhase);
}
}
}