Compare commits

...

12 Commits

Author SHA1 Message Date
Madmadness65
8a0b5c52d3 [Bugfix] Fix Rival battles not playing correct music
The Rival fights were playing the incorrect battle music on the Mixed music preference. This has been fixed.
2024-06-10 16:53:14 -05:00
Dmitriy K
ee4d130ebb
[Test(refactor)]: Refactor tests game manager (#2062)
* refactor tests game manager

* add setPosition to Mock Text class
2024-06-10 23:35:24 +02:00
whatanoob
942c119814
[Bug] Fix toxic damage increment not resetting upon switching (#1844)
* Update phases.ts

Add reset toxic turn counter in PostSummonPhase

* Update phases.ts

Fix linting
2024-06-10 16:33:34 -04:00
SquillWall
a97933fe4c
[Bug] Fix inputs being annoying in the corner of the screen again (#2054) 2024-06-10 16:19:06 -04:00
Adrian T
bcfeaf0639
[Bug] add move effectiveness text color check to ignore ability (#2042) 2024-06-10 13:40:00 -04:00
José Ricardo Fleury Oliveira
36f3cc6b47
[Localization] Localizing "Loading..." text (#1822)
* localized "Loading..." texts

* fixes
2024-06-10 13:33:48 -04:00
Dakurei
c2375a0f5d
[Enhancement] Fix - Adjusts the color of the remaining PPs correctly to the theme (#2045)
+ Choose colors adapted to provide a contrast that facilitates reading (ratio of 3.0 or more)
    Tool used: https://webaim.org/resources/contrastchecker/
  + Refactoring of the condition to avoid unnecessarily assigning variable several times depending on the contents of 'ppPercentLeft'
2024-06-10 13:30:02 -04:00
SquillWall
f672ecc8ed
[Bug] resolve issue with input text persisting in top left of game screen in certain cases (#1982)
* resolve issue with input text persisting in top left of game screen in certain cases

* Clean up ui logic for fix
2024-06-10 11:41:01 -05:00
José Ricardo Fleury Oliveira
29d9ac7038
[Localization][pt] move pokemon-info-container label 1 pixel to the right (#2041) 2024-06-10 10:55:10 -04:00
Adrian T
7d55c165df
[QoL] add dynamic default pp color (#2039) 2024-06-10 10:46:08 -04:00
Greenlamp2
e29c08ba1f
[Test] Add Tests for Zen Mode Ability (#1978)
* added tests for zen mode - change form in battle

* added tests to run some battle against trainer/rival & boss

* added a test with a method to kill a pokemon

* added an override in the clock mocked to reduce the time of fainting pokemon and thus reducing test time from 5s to less than 1s

* added some more tests + doAttack, doKillOpponents, toNextWave, toNextTurn helper

* added some more tests + doAttack, doKillOpponents, toNextWave, toNextTurn helper + fix some tests
2024-06-10 10:10:23 -04:00
Madmadness65
32d222c9cd
Implement Arceus Plates and Silvally Memories (#1195)
* Implement Arceus Plates and Silvally Memories

* German localization for items

* Run linter on changes, add items to zh_TW localization

* linter cleanup

* Finish localization for other languages

Names for items sourced from Bulbapedia. Names for the items in Portuguese could not be reliably sourced, so they were as directly translated as possible.

* Add custom Blank Memory item

Currently does nothing, but the idea is that it would be used if the all-in-one form change item idea is followed through.

* Update item atlas

* Use move.type instead of type.value

The type effectiveness now works again.

* Update comments

* Fix type changing affecting only Silvally

* Condense switch cases down to a single line

As per Brain Frog's suggestion.
2024-06-10 09:58:34 -04:00
71 changed files with 7895 additions and 6519 deletions

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 405 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

View File

Before

Width:  |  Height:  |  Size: 396 B

After

Width:  |  Height:  |  Size: 396 B

View File

Before

Width:  |  Height:  |  Size: 411 B

After

Width:  |  Height:  |  Size: 411 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

View File

Before

Width:  |  Height:  |  Size: 378 B

After

Width:  |  Height:  |  Size: 378 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

View File

Before

Width:  |  Height:  |  Size: 397 B

After

Width:  |  Height:  |  Size: 397 B

View File

Before

Width:  |  Height:  |  Size: 386 B

After

Width:  |  Height:  |  Size: 386 B

View File

Before

Width:  |  Height:  |  Size: 391 B

After

Width:  |  Height:  |  Size: 391 B

View File

Before

Width:  |  Height:  |  Size: 390 B

After

Width:  |  Height:  |  Size: 390 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

View File

Before

Width:  |  Height:  |  Size: 381 B

After

Width:  |  Height:  |  Size: 381 B

View File

Before

Width:  |  Height:  |  Size: 393 B

After

Width:  |  Height:  |  Size: 393 B

View File

Before

Width:  |  Height:  |  Size: 385 B

After

Width:  |  Height:  |  Size: 385 B

View File

Before

Width:  |  Height:  |  Size: 383 B

After

Width:  |  Height:  |  Size: 383 B

View File

Before

Width:  |  Height:  |  Size: 375 B

After

Width:  |  Height:  |  Size: 375 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 334 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 B

View File

Before

Width:  |  Height:  |  Size: 385 B

After

Width:  |  Height:  |  Size: 385 B

View File

Before

Width:  |  Height:  |  Size: 390 B

After

Width:  |  Height:  |  Size: 390 B

View File

Before

Width:  |  Height:  |  Size: 383 B

After

Width:  |  Height:  |  Size: 383 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

View File

Before

Width:  |  Height:  |  Size: 387 B

After

Width:  |  Height:  |  Size: 387 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

View File

Before

Width:  |  Height:  |  Size: 381 B

After

Width:  |  Height:  |  Size: 381 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

View File

@ -4043,8 +4043,7 @@ export function initAbilities() {
.attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr)
.attr(UnsuppressableAbilityAbAttr)
.attr(NoFusionAbilityAbAttr)
.unimplemented(),
.attr(NoFusionAbilityAbAttr),
new Ability(Abilities.FLOWER_GIFT, 4)
.conditionalAttr(getWeatherCondition(WeatherType.SUNNY || WeatherType.HARSH_SUN), BattleStatMultiplierAbAttr, BattleStat.ATK, 1.5)
.conditionalAttr(getWeatherCondition(WeatherType.SUNNY || WeatherType.HARSH_SUN), BattleStatMultiplierAbAttr, BattleStat.SPDEF, 1.5)
@ -4396,8 +4395,7 @@ export function initAbilities() {
.attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr)
.attr(UnsuppressableAbilityAbAttr)
.attr(NoFusionAbilityAbAttr)
.unimplemented(),
.attr(NoFusionAbilityAbAttr),
new Ability(Abilities.ELECTRIC_SURGE, 7)
.attr(PostSummonTerrainChangeAbAttr, TerrainType.ELECTRIC)
.attr(PostBiomeChangeTerrainChangeAbAttr, TerrainType.ELECTRIC),

View File

@ -3328,6 +3328,19 @@ export class VariableMoveTypeAttr extends MoveAttr {
}
}
export class FormChangeItemTypeAttr extends VariableMoveTypeAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.ARCEUS) || [user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.SILVALLY)) {
const form = user.species.speciesId === Species.ARCEUS || user.species.speciesId === Species.SILVALLY ? user.formIndex : user.fusionSpecies.formIndex;
move.type = Type[Type[form]];
return true;
}
return false;
}
}
export class TechnoBlastTypeAttr extends VariableMoveTypeAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.GENESECT)) {
@ -6612,7 +6625,7 @@ export function initMoves() {
.attr(ConfuseAttr)
.soundBased(),
new AttackMove(Moves.JUDGMENT, Type.NORMAL, MoveCategory.SPECIAL, 100, 100, 10, -1, 0, 4)
.partial(),
.attr(FormChangeItemTypeAttr),
new AttackMove(Moves.BUG_BITE, Type.BUG, MoveCategory.PHYSICAL, 60, 100, 20, -1, 0, 4)
.attr(StealEatBerryAttr),
new AttackMove(Moves.CHARGE_BEAM, Type.ELECTRIC, MoveCategory.SPECIAL, 50, 90, 10, 70, 0, 4)
@ -7382,7 +7395,7 @@ export function initMoves() {
new AttackMove(Moves.NATURES_MADNESS, Type.FAIRY, MoveCategory.SPECIAL, -1, 90, 10, -1, 0, 7)
.attr(TargetHalfHpDamageAttr),
new AttackMove(Moves.MULTI_ATTACK, Type.NORMAL, MoveCategory.PHYSICAL, 120, 100, 10, -1, 0, 7)
.partial(),
.attr(FormChangeItemTypeAttr),
/* Unused */
new AttackMove(Moves.TEN_MILLION_VOLT_THUNDERBOLT, Type.ELECTRIC, MoveCategory.SPECIAL, 195, -1, 1, -1, 0, 7)
.partial()

View File

@ -86,7 +86,45 @@ export enum FormChangeItem {
SHOCK_DRIVE,
BURN_DRIVE,
CHILL_DRIVE,
DOUSE_DRIVE
DOUSE_DRIVE,
FIST_PLATE = 100,
SKY_PLATE,
TOXIC_PLATE,
EARTH_PLATE,
STONE_PLATE,
INSECT_PLATE,
SPOOKY_PLATE,
IRON_PLATE,
FLAME_PLATE,
SPLASH_PLATE,
MEADOW_PLATE,
ZAP_PLATE,
MIND_PLATE,
ICICLE_PLATE,
DRACO_PLATE,
DREAD_PLATE,
PIXIE_PLATE,
BLANK_PLATE, // TODO: Find a potential use for this
LEGEND_PLATE, // TODO: Find a potential use for this
FIGHTING_MEMORY,
FLYING_MEMORY,
POISON_MEMORY,
GROUND_MEMORY,
ROCK_MEMORY,
BUG_MEMORY,
GHOST_MEMORY,
STEEL_MEMORY,
FIRE_MEMORY,
WATER_MEMORY,
GRASS_MEMORY,
ELECTRIC_MEMORY,
PSYCHIC_MEMORY,
ICE_MEMORY,
DRAGON_MEMORY,
DARK_MEMORY,
FAIRY_MEMORY,
BLANK_MEMORY // TODO: Find a potential use for this
}
export type SpeciesFormChangeConditionPredicate = (p: Pokemon) => boolean;
@ -533,6 +571,25 @@ export const pokemonFormChanges: PokemonFormChanges = {
new SpeciesFormChange(Species.SHAYMIN, "sky", "land", new SpeciesFormChangeTimeOfDayTrigger(TimeOfDay.DAWN, TimeOfDay.NIGHT)),
new SpeciesFormChange(Species.SHAYMIN, "sky", "land", new SpeciesFormChangeStatusEffectTrigger(StatusEffect.FREEZE))
],
[Species.ARCEUS]: [
new SpeciesFormChange(Species.ARCEUS, "normal", "fighting", new SpeciesFormChangeItemTrigger(FormChangeItem.FIST_PLATE)),
new SpeciesFormChange(Species.ARCEUS, "normal", "flying", new SpeciesFormChangeItemTrigger(FormChangeItem.SKY_PLATE)),
new SpeciesFormChange(Species.ARCEUS, "normal", "poison", new SpeciesFormChangeItemTrigger(FormChangeItem.TOXIC_PLATE)),
new SpeciesFormChange(Species.ARCEUS, "normal", "ground", new SpeciesFormChangeItemTrigger(FormChangeItem.EARTH_PLATE)),
new SpeciesFormChange(Species.ARCEUS, "normal", "rock", new SpeciesFormChangeItemTrigger(FormChangeItem.STONE_PLATE)),
new SpeciesFormChange(Species.ARCEUS, "normal", "bug", new SpeciesFormChangeItemTrigger(FormChangeItem.INSECT_PLATE)),
new SpeciesFormChange(Species.ARCEUS, "normal", "ghost", new SpeciesFormChangeItemTrigger(FormChangeItem.SPOOKY_PLATE)),
new SpeciesFormChange(Species.ARCEUS, "normal", "steel", new SpeciesFormChangeItemTrigger(FormChangeItem.IRON_PLATE)),
new SpeciesFormChange(Species.ARCEUS, "normal", "fire", new SpeciesFormChangeItemTrigger(FormChangeItem.FLAME_PLATE)),
new SpeciesFormChange(Species.ARCEUS, "normal", "water", new SpeciesFormChangeItemTrigger(FormChangeItem.SPLASH_PLATE)),
new SpeciesFormChange(Species.ARCEUS, "normal", "grass", new SpeciesFormChangeItemTrigger(FormChangeItem.MEADOW_PLATE)),
new SpeciesFormChange(Species.ARCEUS, "normal", "electric", new SpeciesFormChangeItemTrigger(FormChangeItem.ZAP_PLATE)),
new SpeciesFormChange(Species.ARCEUS, "normal", "psychic", new SpeciesFormChangeItemTrigger(FormChangeItem.MIND_PLATE)),
new SpeciesFormChange(Species.ARCEUS, "normal", "ice", new SpeciesFormChangeItemTrigger(FormChangeItem.ICICLE_PLATE)),
new SpeciesFormChange(Species.ARCEUS, "normal", "dragon", new SpeciesFormChangeItemTrigger(FormChangeItem.DRACO_PLATE)),
new SpeciesFormChange(Species.ARCEUS, "normal", "dark", new SpeciesFormChangeItemTrigger(FormChangeItem.DREAD_PLATE)),
new SpeciesFormChange(Species.ARCEUS, "normal", "fairy", new SpeciesFormChangeItemTrigger(FormChangeItem.PIXIE_PLATE))
],
[Species.DARMANITAN]: [
new SpeciesFormChange(Species.DARMANITAN, "", "zen", new SpeciesFormChangeManualTrigger(), true),
new SpeciesFormChange(Species.DARMANITAN, "zen", "", new SpeciesFormChangeManualTrigger(), true)
@ -597,6 +654,25 @@ export const pokemonFormChanges: PokemonFormChanges = {
new SpeciesFormChange(Species.WISHIWASHI, "", "school", new SpeciesFormChangeManualTrigger(), true),
new SpeciesFormChange(Species.WISHIWASHI, "school", "", new SpeciesFormChangeManualTrigger(), true)
],
[Species.SILVALLY]: [
new SpeciesFormChange(Species.SILVALLY, "normal", "fighting", new SpeciesFormChangeItemTrigger(FormChangeItem.FIGHTING_MEMORY)),
new SpeciesFormChange(Species.SILVALLY, "normal", "flying", new SpeciesFormChangeItemTrigger(FormChangeItem.FLYING_MEMORY)),
new SpeciesFormChange(Species.SILVALLY, "normal", "poison", new SpeciesFormChangeItemTrigger(FormChangeItem.POISON_MEMORY)),
new SpeciesFormChange(Species.SILVALLY, "normal", "ground", new SpeciesFormChangeItemTrigger(FormChangeItem.GROUND_MEMORY)),
new SpeciesFormChange(Species.SILVALLY, "normal", "rock", new SpeciesFormChangeItemTrigger(FormChangeItem.ROCK_MEMORY)),
new SpeciesFormChange(Species.SILVALLY, "normal", "bug", new SpeciesFormChangeItemTrigger(FormChangeItem.BUG_MEMORY)),
new SpeciesFormChange(Species.SILVALLY, "normal", "ghost", new SpeciesFormChangeItemTrigger(FormChangeItem.GHOST_MEMORY)),
new SpeciesFormChange(Species.SILVALLY, "normal", "steel", new SpeciesFormChangeItemTrigger(FormChangeItem.STEEL_MEMORY)),
new SpeciesFormChange(Species.SILVALLY, "normal", "fire", new SpeciesFormChangeItemTrigger(FormChangeItem.FIRE_MEMORY)),
new SpeciesFormChange(Species.SILVALLY, "normal", "water", new SpeciesFormChangeItemTrigger(FormChangeItem.WATER_MEMORY)),
new SpeciesFormChange(Species.SILVALLY, "normal", "grass", new SpeciesFormChangeItemTrigger(FormChangeItem.GRASS_MEMORY)),
new SpeciesFormChange(Species.SILVALLY, "normal", "electric", new SpeciesFormChangeItemTrigger(FormChangeItem.ELECTRIC_MEMORY)),
new SpeciesFormChange(Species.SILVALLY, "normal", "psychic", new SpeciesFormChangeItemTrigger(FormChangeItem.PSYCHIC_MEMORY)),
new SpeciesFormChange(Species.SILVALLY, "normal", "ice", new SpeciesFormChangeItemTrigger(FormChangeItem.ICE_MEMORY)),
new SpeciesFormChange(Species.SILVALLY, "normal", "dragon", new SpeciesFormChangeItemTrigger(FormChangeItem.DRAGON_MEMORY)),
new SpeciesFormChange(Species.SILVALLY, "normal", "dark", new SpeciesFormChangeItemTrigger(FormChangeItem.DARK_MEMORY)),
new SpeciesFormChange(Species.SILVALLY, "normal", "fairy", new SpeciesFormChangeItemTrigger(FormChangeItem.FAIRY_MEMORY))
],
[Species.MINIOR]: [
new SpeciesFormChange(Species.MINIOR, "red-meteor", "red", new SpeciesFormChangeManualTrigger(), true),
new SpeciesFormChange(Species.MINIOR, "red", "red-meteor", new SpeciesFormChangeManualTrigger(), true),

View File

@ -1445,21 +1445,21 @@ export const trainerConfigs: TrainerConfigs = {
p.generateAndPopulateMoveset();
})),
[TrainerType.RIVAL]: new TrainerConfig((t = TrainerType.RIVAL)).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setStaticParty().setEncounterBgm(TrainerType.RIVAL).setBattleBgm("battle_rival").setPartyTemplates(trainerPartyTemplates.RIVAL)
[TrainerType.RIVAL]: new TrainerConfig((t = TrainerType.RIVAL)).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setStaticParty().setEncounterBgm(TrainerType.RIVAL).setBattleBgm("battle_rival").setMixedBattleBgm("battle_rival").setPartyTemplates(trainerPartyTemplates.RIVAL)
.setModifierRewardFuncs(() => modifierTypes.SUPER_EXP_CHARM, () => modifierTypes.EXP_SHARE)
.setPartyMemberFunc(0, getRandomPartyMemberFunc([Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE, Species.CHIKORITA, Species.CYNDAQUIL, Species.TOTODILE, Species.TREECKO, Species.TORCHIC, Species.MUDKIP, Species.TURTWIG, Species.CHIMCHAR, Species.PIPLUP, Species.SNIVY, Species.TEPIG, Species.OSHAWOTT, Species.CHESPIN, Species.FENNEKIN, Species.FROAKIE, Species.ROWLET, Species.LITTEN, Species.POPPLIO, Species.GROOKEY, Species.SCORBUNNY, Species.SOBBLE, Species.SPRIGATITO, Species.FUECOCO, Species.QUAXLY], TrainerSlot.TRAINER, true))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([Species.PIDGEY, Species.HOOTHOOT, Species.TAILLOW, Species.STARLY, Species.PIDOVE, Species.FLETCHLING, Species.PIKIPEK, Species.ROOKIDEE, Species.WATTREL], TrainerSlot.TRAINER, true)),
[TrainerType.RIVAL_2]: new TrainerConfig(++t).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setStaticParty().setMoneyMultiplier(1.25).setEncounterBgm(TrainerType.RIVAL).setBattleBgm("battle_rival").setPartyTemplates(trainerPartyTemplates.RIVAL_2)
[TrainerType.RIVAL_2]: new TrainerConfig(++t).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setStaticParty().setMoneyMultiplier(1.25).setEncounterBgm(TrainerType.RIVAL).setBattleBgm("battle_rival").setMixedBattleBgm("battle_rival").setPartyTemplates(trainerPartyTemplates.RIVAL_2)
.setModifierRewardFuncs(() => modifierTypes.EXP_SHARE)
.setPartyMemberFunc(0, getRandomPartyMemberFunc([Species.IVYSAUR, Species.CHARMELEON, Species.WARTORTLE, Species.BAYLEEF, Species.QUILAVA, Species.CROCONAW, Species.GROVYLE, Species.COMBUSKEN, Species.MARSHTOMP, Species.GROTLE, Species.MONFERNO, Species.PRINPLUP, Species.SERVINE, Species.PIGNITE, Species.DEWOTT, Species.QUILLADIN, Species.BRAIXEN, Species.FROGADIER, Species.DARTRIX, Species.TORRACAT, Species.BRIONNE, Species.THWACKEY, Species.RABOOT, Species.DRIZZILE, Species.FLORAGATO, Species.CROCALOR, Species.QUAXWELL], TrainerSlot.TRAINER, true))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([Species.PIDGEOTTO, Species.HOOTHOOT, Species.TAILLOW, Species.STARAVIA, Species.TRANQUILL, Species.FLETCHINDER, Species.TRUMBEAK, Species.CORVISQUIRE, Species.WATTREL], TrainerSlot.TRAINER, true))
.setPartyMemberFunc(2, getSpeciesFilterRandomPartyMemberFunc((species: PokemonSpecies) => !pokemonEvolutions.hasOwnProperty(species.speciesId) && !pokemonPrevolutions.hasOwnProperty(species.speciesId) && species.baseTotal >= 450)),
[TrainerType.RIVAL_3]: new TrainerConfig(++t).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setStaticParty().setMoneyMultiplier(1.5).setEncounterBgm(TrainerType.RIVAL).setBattleBgm("battle_rival").setPartyTemplates(trainerPartyTemplates.RIVAL_3)
[TrainerType.RIVAL_3]: new TrainerConfig(++t).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setStaticParty().setMoneyMultiplier(1.5).setEncounterBgm(TrainerType.RIVAL).setBattleBgm("battle_rival").setMixedBattleBgm("battle_rival").setPartyTemplates(trainerPartyTemplates.RIVAL_3)
.setPartyMemberFunc(0, getRandomPartyMemberFunc([Species.VENUSAUR, Species.CHARIZARD, Species.BLASTOISE, Species.MEGANIUM, Species.TYPHLOSION, Species.FERALIGATR, Species.SCEPTILE, Species.BLAZIKEN, Species.SWAMPERT, Species.TORTERRA, Species.INFERNAPE, Species.EMPOLEON, Species.SERPERIOR, Species.EMBOAR, Species.SAMUROTT, Species.CHESNAUGHT, Species.DELPHOX, Species.GRENINJA, Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA, Species.RILLABOOM, Species.CINDERACE, Species.INTELEON, Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL], TrainerSlot.TRAINER, true))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([Species.PIDGEOT, Species.NOCTOWL, Species.SWELLOW, Species.STARAPTOR, Species.UNFEZANT, Species.TALONFLAME, Species.TOUCANNON, Species.CORVIKNIGHT, Species.KILOWATTREL], TrainerSlot.TRAINER, true))
.setPartyMemberFunc(2, getSpeciesFilterRandomPartyMemberFunc((species: PokemonSpecies) => !pokemonEvolutions.hasOwnProperty(species.speciesId) && !pokemonPrevolutions.hasOwnProperty(species.speciesId) && species.baseTotal >= 450))
.setSpeciesFilter(species => species.baseTotal >= 540),
[TrainerType.RIVAL_4]: new TrainerConfig(++t).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setBoss().setStaticParty().setMoneyMultiplier(1.75).setEncounterBgm(TrainerType.RIVAL).setBattleBgm("battle_rival_2").setPartyTemplates(trainerPartyTemplates.RIVAL_4)
[TrainerType.RIVAL_4]: new TrainerConfig(++t).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setBoss().setStaticParty().setMoneyMultiplier(1.75).setEncounterBgm(TrainerType.RIVAL).setBattleBgm("battle_rival_2").setMixedBattleBgm("battle_rival_2").setPartyTemplates(trainerPartyTemplates.RIVAL_4)
.setPartyMemberFunc(0, getRandomPartyMemberFunc([Species.VENUSAUR, Species.CHARIZARD, Species.BLASTOISE, Species.MEGANIUM, Species.TYPHLOSION, Species.FERALIGATR, Species.SCEPTILE, Species.BLAZIKEN, Species.SWAMPERT, Species.TORTERRA, Species.INFERNAPE, Species.EMPOLEON, Species.SERPERIOR, Species.EMBOAR, Species.SAMUROTT, Species.CHESNAUGHT, Species.DELPHOX, Species.GRENINJA, Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA, Species.RILLABOOM, Species.CINDERACE, Species.INTELEON, Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL], TrainerSlot.TRAINER, true))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([Species.PIDGEOT, Species.NOCTOWL, Species.SWELLOW, Species.STARAPTOR, Species.UNFEZANT, Species.TALONFLAME, Species.TOUCANNON, Species.CORVIKNIGHT, Species.KILOWATTREL], TrainerSlot.TRAINER, true))
.setPartyMemberFunc(2, getSpeciesFilterRandomPartyMemberFunc((species: PokemonSpecies) => !pokemonEvolutions.hasOwnProperty(species.speciesId) && !pokemonPrevolutions.hasOwnProperty(species.speciesId) && species.baseTotal >= 450))
@ -1468,7 +1468,7 @@ export const trainerConfigs: TrainerConfigs = {
const starter = party[0];
return [modifierTypes.TERA_SHARD().generateType(null, [starter.species.type1]).withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(starter) as PersistentModifier];
}),
[TrainerType.RIVAL_5]: new TrainerConfig(++t).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setBoss().setStaticParty().setMoneyMultiplier(2.25).setEncounterBgm(TrainerType.RIVAL).setBattleBgm("battle_rival_3").setPartyTemplates(trainerPartyTemplates.RIVAL_5)
[TrainerType.RIVAL_5]: new TrainerConfig(++t).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setBoss().setStaticParty().setMoneyMultiplier(2.25).setEncounterBgm(TrainerType.RIVAL).setBattleBgm("battle_rival_3").setMixedBattleBgm("battle_rival_3").setPartyTemplates(trainerPartyTemplates.RIVAL_5)
.setPartyMemberFunc(0, getRandomPartyMemberFunc([Species.VENUSAUR, Species.CHARIZARD, Species.BLASTOISE, Species.MEGANIUM, Species.TYPHLOSION, Species.FERALIGATR, Species.SCEPTILE, Species.BLAZIKEN, Species.SWAMPERT, Species.TORTERRA, Species.INFERNAPE, Species.EMPOLEON, Species.SERPERIOR, Species.EMBOAR, Species.SAMUROTT, Species.CHESNAUGHT, Species.DELPHOX, Species.GRENINJA, Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA, Species.RILLABOOM, Species.CINDERACE, Species.INTELEON, Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL], TrainerSlot.TRAINER, true,
p => p.setBoss(true, 2)))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([Species.PIDGEOT, Species.NOCTOWL, Species.SWELLOW, Species.STARAPTOR, Species.UNFEZANT, Species.TALONFLAME, Species.TOUCANNON, Species.CORVIKNIGHT, Species.KILOWATTREL], TrainerSlot.TRAINER, true))
@ -1484,7 +1484,7 @@ export const trainerConfigs: TrainerConfigs = {
const starter = party[0];
return [modifierTypes.TERA_SHARD().generateType(null, [starter.species.type1]).withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(starter) as PersistentModifier];
}),
[TrainerType.RIVAL_6]: new TrainerConfig(++t).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setBoss().setStaticParty().setMoneyMultiplier(3).setEncounterBgm("final").setBattleBgm("battle_rival_3").setPartyTemplates(trainerPartyTemplates.RIVAL_6)
[TrainerType.RIVAL_6]: new TrainerConfig(++t).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setBoss().setStaticParty().setMoneyMultiplier(3).setEncounterBgm("final").setBattleBgm("battle_rival_3").setMixedBattleBgm("battle_rival_3").setPartyTemplates(trainerPartyTemplates.RIVAL_6)
.setPartyMemberFunc(0, getRandomPartyMemberFunc([Species.VENUSAUR, Species.CHARIZARD, Species.BLASTOISE, Species.MEGANIUM, Species.TYPHLOSION, Species.FERALIGATR, Species.SCEPTILE, Species.BLAZIKEN, Species.SWAMPERT, Species.TORTERRA, Species.INFERNAPE, Species.EMPOLEON, Species.SERPERIOR, Species.EMBOAR, Species.SAMUROTT, Species.CHESNAUGHT, Species.DELPHOX, Species.GRENINJA, Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA, Species.RILLABOOM, Species.CINDERACE, Species.INTELEON, Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL], TrainerSlot.TRAINER, true,
p => {
p.setBoss(true, 3);

View File

@ -1070,6 +1070,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
/**
* Calculates the effectiveness of a move against the Pokémon.
*
* @param source - The Pokémon using the move.
* @param move - The move being used.
* @returns The type damage multiplier or undefined if it's a status move
*/
getMoveEffectiveness(source: Pokemon, move: PokemonMove): TypeDamageMultiplier | undefined {
@ -1077,19 +1081,27 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return undefined;
}
return this.getAttackMoveEffectiveness(source, move);
return this.getAttackMoveEffectiveness(source, move, true);
}
getAttackMoveEffectiveness(source: Pokemon, pokemonMove: PokemonMove): TypeDamageMultiplier {
/**
* Calculates the effectiveness of an attack move against the Pokémon.
*
* @param source - The attacking Pokémon.
* @param pokemonMove - The move being used by the attacking Pokémon.
* @param ignoreAbility - Whether to check for abilities that might affect type effectiveness or immunity.
* @returns The type damage multiplier, indicating the effectiveness of the move
*/
getAttackMoveEffectiveness(source: Pokemon, pokemonMove: PokemonMove, ignoreAbility: boolean = false): TypeDamageMultiplier {
const move = pokemonMove.getMove();
const typeless = move.hasAttr(TypelessAttr);
const typeMultiplier = new Utils.NumberHolder(this.getAttackTypeEffectiveness(move.type, source));
const cancelled = new Utils.BooleanHolder(false);
applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier);
if (!typeless) {
if (!typeless && !ignoreAbility) {
applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, move, cancelled, typeMultiplier, true);
}
if (!cancelled.value) {
if (!cancelled.value && !ignoreAbility) {
applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, move, cancelled, typeMultiplier, true);
}
return (!cancelled.value ? typeMultiplier.value : 0) as TypeDamageMultiplier;

View File

@ -389,5 +389,43 @@ export const modifierType: ModifierTypeTranslationEntries = {
"BURN_DRIVE": "Flammenmodul",
"CHILL_DRIVE": "Gefriermodul",
"DOUSE_DRIVE": "Aquamodul",
"FIST_PLATE": "Fausttafel",
"SKY_PLATE": "Wolkentafel",
"TOXIC_PLATE": "Gifttafel",
"EARTH_PLATE": "Erdtafel",
"STONE_PLATE": "Steintafel",
"INSECT_PLATE": "Käfertafel",
"SPOOKY_PLATE": "Spuktafel",
"IRON_PLATE": "Eisentafel",
"FLAME_PLATE": "Feuertafel",
"SPLASH_PLATE": "Wassertafel",
"MEADOW_PLATE": "Wiesentafel",
"ZAP_PLATE": "Blitztafel",
"MIND_PLATE": "Hirntafel",
"ICICLE_PLATE": "Frosttafel",
"DRACO_PLATE": "Dracotafel",
"DREAD_PLATE": "Furchttafel",
"PIXIE_PLATE": "Feentafel",
"BLANK_PLATE": "Neutraltafel",
"LEGEND_PLATE": "Legendentafel",
"FIGHTING_MEMORY": "Kampf-Disc",
"FLYING_MEMORY": "Flug-Disc",
"POISON_MEMORY": "Gift-Disc",
"GROUND_MEMORY": "Boden-Disc",
"ROCK_MEMORY": "Gesteins-Disc",
"BUG_MEMORY": "Käfer-Disc",
"GHOST_MEMORY": "Geister-Disc",
"STEEL_MEMORY": "Stahl-Disc",
"FIRE_MEMORY": "Feuer-Disc",
"WATER_MEMORY": "Wasser-Disc",
"GRASS_MEMORY": "Pflanzen-Disc",
"ELECTRIC_MEMORY": "Elektro-Disc",
"PSYCHIC_MEMORY": "Psycho-Disc",
"ICE_MEMORY": "Eis-Disc",
"DRAGON_MEMORY": "Drachen-Disc",
"DARK_MEMORY": "Unlicht-Disc",
"FAIRY_MEMORY": "Feen-Disc",
"BLANK_MEMORY": "Leere-Disc",
},
} as const;

View File

@ -388,5 +388,43 @@ export const modifierType: ModifierTypeTranslationEntries = {
"BURN_DRIVE": "Burn Drive",
"CHILL_DRIVE": "Chill Drive",
"DOUSE_DRIVE": "Douse Drive",
"FIST_PLATE": "Fist Plate",
"SKY_PLATE": "Sky Plate",
"TOXIC_PLATE": "Toxic Plate",
"EARTH_PLATE": "Earth Plate",
"STONE_PLATE": "Stone Plate",
"INSECT_PLATE": "Insect Plate",
"SPOOKY_PLATE": "Spooky Plate",
"IRON_PLATE": "Iron Plate",
"FLAME_PLATE": "Flame Plate",
"SPLASH_PLATE": "Splash Plate",
"MEADOW_PLATE": "Meadow Plate",
"ZAP_PLATE": "Zap Plate",
"MIND_PLATE": "Mind Plate",
"ICICLE_PLATE": "Icicle Plate",
"DRACO_PLATE": "Draco Plate",
"DREAD_PLATE": "Dread Plate",
"PIXIE_PLATE": "Pixie Plate",
"BLANK_PLATE": "Blank Plate",
"LEGEND_PLATE": "Legend Plate",
"FIGHTING_MEMORY": "Fighting Memory",
"FLYING_MEMORY": "Flying Memory",
"POISON_MEMORY": "Poison Memory",
"GROUND_MEMORY": "Ground Memory",
"ROCK_MEMORY": "Rock Memory",
"BUG_MEMORY": "Bug Memory",
"GHOST_MEMORY": "Ghost Memory",
"STEEL_MEMORY": "Steel Memory",
"FIRE_MEMORY": "Fire Memory",
"WATER_MEMORY": "Water Memory",
"GRASS_MEMORY": "Grass Memory",
"ELECTRIC_MEMORY": "Electric Memory",
"PSYCHIC_MEMORY": "Psychic Memory",
"ICE_MEMORY": "Ice Memory",
"DRAGON_MEMORY": "Dragon Memory",
"DARK_MEMORY": "Dark Memory",
"FAIRY_MEMORY": "Fairy Memory",
"BLANK_MEMORY": "Blank Memory",
},
} as const;

View File

@ -388,5 +388,43 @@ export const modifierType: ModifierTypeTranslationEntries = {
"BURN_DRIVE": "PiroROM",
"CHILL_DRIVE": "CrioROM",
"DOUSE_DRIVE": "HidroROM",
"FIST_PLATE": "Tabla Fuerte",
"SKY_PLATE": "Tabla Cielo",
"TOXIC_PLATE": "Tabla Tóxica",
"EARTH_PLATE": "Tabla Terrax",
"STONE_PLATE": "Tabla Pétrea",
"INSECT_PLATE": "Tabla Bicho",
"SPOOKY_PLATE": "Tabla Terror",
"IRON_PLATE": "Tabla Acero",
"FLAME_PLATE": "Tabla Llama",
"SPLASH_PLATE": "Tabla Linfa",
"MEADOW_PLATE": "Tabla Pradal",
"ZAP_PLATE": "Tabla Trueno",
"MIND_PLATE": "Tabla Mental",
"ICICLE_PLATE": "Tabla Helada",
"DRACO_PLATE": "Tabla Draco",
"DREAD_PLATE": "Tabla Oscura",
"PIXIE_PLATE": "Tabla Duende",
"BLANK_PLATE": "Tabla Neutra",
"LEGEND_PLATE": "Tabla Legendaria",
"FIGHTING_MEMORY": "Disco Lucha",
"FLYING_MEMORY": "Disco Volador",
"POISON_MEMORY": "Disco Veneno",
"GROUND_MEMORY": "Disco Tierra",
"ROCK_MEMORY": "Disco Roca",
"BUG_MEMORY": "Disco Bicho",
"GHOST_MEMORY": "Disco Fantasma",
"STEEL_MEMORY": "Disco Acero",
"FIRE_MEMORY": "Disco Fuego",
"WATER_MEMORY": "Disco Agua",
"GRASS_MEMORY": "Disco Planta",
"ELECTRIC_MEMORY": "Disco Eléctrico",
"PSYCHIC_MEMORY": "Disco Psíquico",
"ICE_MEMORY": "Disco Hielo",
"DRAGON_MEMORY": "Disco Dragón",
"DARK_MEMORY": "Disco Siniestro",
"FAIRY_MEMORY": "Disco Hada",
"BLANK_MEMORY": "Disco Blanco",
},
} as const;

View File

@ -388,5 +388,43 @@ export const modifierType: ModifierTypeTranslationEntries = {
"BURN_DRIVE": "Module Pyro",
"CHILL_DRIVE": "Module Cryo",
"DOUSE_DRIVE": "Module Aqua",
"FIST_PLATE": "Plaque Poing",
"SKY_PLATE": "Plaque Ciel",
"TOXIC_PLATE": "Plaque Toxicité",
"EARTH_PLATE": "Plaque Terre",
"STONE_PLATE": "Plaque Roc",
"INSECT_PLATE": "Plaque Insecte",
"SPOOKY_PLATE": "Plaque Fantôme",
"IRON_PLATE": "Plaque Fer",
"FLAME_PLATE": "Plaque Flamme",
"SPLASH_PLATE": "Plaque Hydro",
"MEADOW_PLATE": "Plaque Herbe",
"ZAP_PLATE": "Plaque Volt",
"MIND_PLATE": "Plaque Esprit",
"ICICLE_PLATE": "Plaque Glace",
"DRACO_PLATE": "Plaque Draco",
"DREAD_PLATE": "Plaque Ombre",
"PIXIE_PLATE": "Plaque Pixie",
"BLANK_PLATE": "Plaque Renouveau",
"LEGEND_PLATE": "Plaque Légende",
"FIGHTING_MEMORY": "ROM Combat",
"FLYING_MEMORY": "ROM Vol",
"POISON_MEMORY": "ROM Poison",
"GROUND_MEMORY": "ROM Sol",
"ROCK_MEMORY": "ROM Roche",
"BUG_MEMORY": "ROM Insecte",
"GHOST_MEMORY": "ROM Spectre",
"STEEL_MEMORY": "ROM Acier",
"FIRE_MEMORY": "ROM Feu",
"WATER_MEMORY": "ROM Eau",
"GRASS_MEMORY": "ROM Plante",
"ELECTRIC_MEMORY": "ROM Électrik",
"PSYCHIC_MEMORY": "ROM Psy",
"ICE_MEMORY": "ROM Glace",
"DRAGON_MEMORY": "ROM Dragon",
"DARK_MEMORY": "ROM Ténèbres",
"FAIRY_MEMORY": "ROM Fée",
"BLANK_MEMORY": "ROM Vierge",
},
} as const;

View File

@ -388,5 +388,43 @@ export const modifierType: ModifierTypeTranslationEntries = {
"BURN_DRIVE": "Piromodulo",
"CHILL_DRIVE": "Gelomodulo",
"DOUSE_DRIVE": "Idromodulo",
"FIST_PLATE": "Lastrapugno",
"SKY_PLATE": "Lastracielo",
"TOXIC_PLATE": "Lastrafiele",
"EARTH_PLATE": "Lastrageo",
"STONE_PLATE": "Lastrapietra",
"INSECT_PLATE": "Lastrabaco",
"SPOOKY_PLATE": "Lastratetra",
"IRON_PLATE": "Lastraferro",
"FLAME_PLATE": "Lastrarogo",
"SPLASH_PLATE": "Lastraidro",
"MEADOW_PLATE": "Lastraprato",
"ZAP_PLATE": "Lastrasaetta",
"MIND_PLATE": "Lastramente",
"ICICLE_PLATE": "Lastragelo",
"DRACO_PLATE": "Lastradrakon",
"DREAD_PLATE": "Lastratimore",
"PIXIE_PLATE": "Lastraspiritello",
"BLANK_PLATE": "Lastraripristino",
"LEGEND_PLATE": "Lastraleggenda",
"FIGHTING_MEMORY": "ROM Lotta",
"FLYING_MEMORY": "ROM Volante",
"POISON_MEMORY": "ROM Veleno",
"GROUND_MEMORY": "ROM Terra",
"ROCK_MEMORY": "ROM Roccia",
"BUG_MEMORY": "ROM Coleottero",
"GHOST_MEMORY": "ROM Spettro",
"STEEL_MEMORY": "ROM Acciaio",
"FIRE_MEMORY": "ROM Fuoco",
"WATER_MEMORY": "ROM Acqua",
"GRASS_MEMORY": "ROM Erba",
"ELECTRIC_MEMORY": "ROM Elettro",
"PSYCHIC_MEMORY": "ROM Psico",
"ICE_MEMORY": "ROM Ghiaccio",
"DRAGON_MEMORY": "ROM Drago",
"DARK_MEMORY": "ROM Buio",
"FAIRY_MEMORY": "ROM Folletto",
"BLANK_MEMORY": "ROM Vuota",
},
} as const;

View File

@ -425,5 +425,6 @@ export const modifierType: ModifierTypeTranslationEntries = {
"DRAGON_MEMORY": "드래곤메모리",
"DARK_MEMORY": "다크메모리",
"FAIRY_MEMORY": "페어리메모리",
"BLANK_MEMORY": "빈메모리",
},
} as const;

View File

@ -388,5 +388,43 @@ export const modifierType: ModifierTypeTranslationEntries = {
"BURN_DRIVE": "IgneDisco",
"CHILL_DRIVE": "CrioDisco",
"DOUSE_DRIVE": "HidroDisco",
"FIST_PLATE": "Placa de Punho",
"SKY_PLATE": "Placa do Céu",
"TOXIC_PLATE": "Placa Tóxica",
"EARTH_PLATE": "Placa Terrestre",
"STONE_PLATE": "Placa de Pedra",
"INSECT_PLATE": "Placa de Insetos",
"SPOOKY_PLATE": "Placa Assustadora",
"IRON_PLATE": "Placa de Ferro",
"FLAME_PLATE": "Placa da Chama",
"SPLASH_PLATE": "Placa de Respingo",
"MEADOW_PLATE": "Placa de Prado",
"ZAP_PLATE": "Placa Elétrica",
"MIND_PLATE": "Placa Mental",
"ICICLE_PLATE": "Placa de Gelo",
"DRACO_PLATE": "Placa de Draco",
"DREAD_PLATE": "Placa do Pavor",
"PIXIE_PLATE": "Placa Duende",
"BLANK_PLATE": "Placa em Branco",
"LEGEND_PLATE": "Placa de Legenda",
"FIGHTING_MEMORY": "Memória de Lutador",
"FLYING_MEMORY": "Memória Voadora",
"POISON_MEMORY": "Memória Venenosa",
"GROUND_MEMORY": "Memória Terrestre",
"ROCK_MEMORY": "Memória da Rocha",
"BUG_MEMORY": "Memória de Insetos",
"GHOST_MEMORY": "Memória Fantasma",
"STEEL_MEMORY": "Memória de Aço",
"FIRE_MEMORY": "Memória de Fogo",
"WATER_MEMORY": "Memória da Água",
"GRASS_MEMORY": "Memória de Planta",
"ELECTRIC_MEMORY": "Memória Elétrica",
"PSYCHIC_MEMORY": "Memória Psíquica",
"ICE_MEMORY": "Memória de Gelo",
"DRAGON_MEMORY": "Memória do Dragão",
"DARK_MEMORY": "Memória Negra",
"FAIRY_MEMORY": "Memória de Fada",
"BLANK_MEMORY": "Memória Vazia",
},
} as const;

View File

@ -388,5 +388,43 @@ export const modifierType: ModifierTypeTranslationEntries = {
"BURN_DRIVE": "火焰卡带",
"CHILL_DRIVE": "冰冻卡带",
"DOUSE_DRIVE": "水流卡带",
"FIST_PLATE": "拳頭石板",
"SKY_PLATE": "藍天石板",
"TOXIC_PLATE": "劇毒石板",
"EARTH_PLATE": "大地石板",
"STONE_PLATE": "岩石石板",
"INSECT_PLATE": "玉蟲石板",
"SPOOKY_PLATE": "妖怪石板",
"IRON_PLATE": "鋼鐵石板",
"FLAME_PLATE": "火球石板",
"SPLASH_PLATE": "水滴石板",
"MEADOW_PLATE": "碧綠石板",
"ZAP_PLATE": "雷電石板",
"MIND_PLATE": "神奇石板",
"ICICLE_PLATE": "冰柱石板",
"DRACO_PLATE": "龍之石板",
"DREAD_PLATE": "惡顏石板",
"PIXIE_PLATE": "妖精石板",
"BLANK_PLATE": "淨空石板",
"LEGEND_PLATE": "傳說石板",
"FIGHTING_MEMORY": "戰鬥記憶碟",
"FLYING_MEMORY": "飛翔記憶碟",
"POISON_MEMORY": "毒記憶碟",
"GROUND_MEMORY": "大地記憶碟",
"ROCK_MEMORY": "岩石記憶碟",
"BUG_MEMORY": "蟲子記憶碟",
"GHOST_MEMORY": "幽靈記憶碟",
"STEEL_MEMORY": "鋼鐵記憶碟",
"FIRE_MEMORY": "火焰記憶碟",
"WATER_MEMORY": "清水記憶碟",
"GRASS_MEMORY": "青草記憶碟",
"ELECTRIC_MEMORY": "電子記憶碟",
"PSYCHIC_MEMORY": "精神記憶碟",
"ICE_MEMORY": "冰雪記憶碟",
"DRAGON_MEMORY": "龍記憶碟",
"DARK_MEMORY": "黑暗記憶碟",
"FAIRY_MEMORY": "妖精記憶碟",
"BLANK_MEMORY": "空白記憶碟",
},
} as const;

View File

@ -442,5 +442,43 @@ export const modifierType: ModifierTypeTranslationEntries = {
BURN_DRIVE: "火焰卡帶",
CHILL_DRIVE: "冰凍卡帶",
DOUSE_DRIVE: "水流卡帶",
"FIST_PLATE": "拳頭石板",
"SKY_PLATE": "藍天石板",
"TOXIC_PLATE": "劇毒石板",
"EARTH_PLATE": "大地石板",
"STONE_PLATE": "岩石石板",
"INSECT_PLATE": "玉蟲石板",
"SPOOKY_PLATE": "妖怪石板",
"IRON_PLATE": "鋼鐵石板",
"FLAME_PLATE": "火球石板",
"SPLASH_PLATE": "水滴石板",
"MEADOW_PLATE": "碧綠石板",
"ZAP_PLATE": "雷電石板",
"MIND_PLATE": "神奇石板",
"ICICLE_PLATE": "冰柱石板",
"DRACO_PLATE": "龍之石板",
"DREAD_PLATE": "惡顏石板",
"PIXIE_PLATE": "妖精石板",
"BLANK_PLATE": "淨空石板",
"LEGEND_PLATE": "傳說石板",
"FIGHTING_MEMORY": "戰鬥記憶碟",
"FLYING_MEMORY": "飛翔記憶碟",
"POISON_MEMORY": "毒記憶碟",
"GROUND_MEMORY": "大地記憶碟",
"ROCK_MEMORY": "岩石記憶碟",
"BUG_MEMORY": "蟲子記憶碟",
"GHOST_MEMORY": "幽靈記憶碟",
"STEEL_MEMORY": "鋼鐵記憶碟",
"FIRE_MEMORY": "火焰記憶碟",
"WATER_MEMORY": "清水記憶碟",
"GRASS_MEMORY": "青草記憶碟",
"ELECTRIC_MEMORY": "電子記憶碟",
"PSYCHIC_MEMORY": "精神記憶碟",
"ICE_MEMORY": "冰雪記憶碟",
"DRAGON_MEMORY": "龍記憶碟",
"DARK_MEMORY": "黑暗記憶碟",
"FAIRY_MEMORY": "妖精記憶碟",
"BLANK_MEMORY": "空白記憶碟",
},
} as const;

View File

@ -1079,6 +1079,10 @@ export class NextEncounterPhase extends EncounterPhase {
super(scene);
}
start() {
super.start();
}
doEncounter(): void {
this.scene.playBgm(undefined, true);
@ -1158,6 +1162,9 @@ export class PostSummonPhase extends PokemonPhase {
const pokemon = this.getPokemon();
if (pokemon.status?.effect === StatusEffect.TOXIC) {
pokemon.status.turnCount = 0;
}
this.scene.arena.applyTags(ArenaTrapTag, pokemon);
applyPostSummonAbAttrs(PostSummonAbAttr, pokemon).then(() => this.end());
}
@ -1497,6 +1504,10 @@ export class SwitchSummonPhase extends SummonPhase {
this.batonPass = batonPass;
}
start(): void {
super.start();
}
preSummon(): void {
if (!this.player) {
if (this.slotIndex === -1) {

View File

@ -0,0 +1,142 @@
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 {Abilities} from "#app/data/enums/abilities";
import {Species} from "#app/data/enums/species";
import {
CommandPhase,
DamagePhase,
EnemyCommandPhase,
MessagePhase,
PostSummonPhase,
SwitchPhase,
SwitchSummonPhase,
TurnEndPhase, TurnInitPhase,
TurnStartPhase,
} from "#app/phases";
import {Mode} from "#app/ui/ui";
import {Stat} from "#app/data/pokemon-stat";
import {Moves} from "#app/data/enums/moves";
import {getMovePosition} from "#app/test/utils/gameManagerUtils";
import {Command} from "#app/ui/command-ui-handler";
import {QuietFormChangePhase} from "#app/form-change-phase";
describe("Abilities - Zen mode", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
const moveToUse = Moves.SPLASH;
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION);
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.ZEN_MODE);
vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(100);
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE,Moves.TACKLE,Moves.TACKLE,Moves.TACKLE]);
});
it("ZEN MODE - not enough damage to change form", async() => {
const moveToUse = Moves.SPLASH;
await game.startBattle([
Species.DARMANITAN,
]);
game.scene.getParty()[0].stats[Stat.SPD] = 1;
game.scene.getParty()[0].stats[Stat.HP] = 100;
game.scene.getParty()[0].hp = 100;
expect(game.scene.getParty()[0].formIndex).toBe(0);
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(DamagePhase, false);
// await game.phaseInterceptor.runFrom(DamagePhase).to(DamagePhase, false);
const damagePhase = game.scene.getCurrentPhase() as DamagePhase;
damagePhase.updateAmount(40);
await game.phaseInterceptor.runFrom(DamagePhase).to(TurnEndPhase, false);
expect(game.scene.getParty()[0].hp).toBeLessThan(100);
expect(game.scene.getParty()[0].formIndex).toBe(0);
}, 20000);
it("ZEN MODE - enough damage to change form", async() => {
const moveToUse = Moves.SPLASH;
await game.startBattle([
Species.DARMANITAN,
]);
game.scene.getParty()[0].stats[Stat.SPD] = 1;
game.scene.getParty()[0].stats[Stat.HP] = 1000;
game.scene.getParty()[0].hp = 100;
expect(game.scene.getParty()[0].formIndex).toBe(0);
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(QuietFormChangePhase);
await game.phaseInterceptor.to(TurnInitPhase, false);
expect(game.scene.getParty()[0].hp).not.toBe(100);
expect(game.scene.getParty()[0].formIndex).not.toBe(0);
}, 20000);
it("ZEN MODE - kill pokemon while on zen mode", async() => {
const moveToUse = Moves.SPLASH;
await game.startBattle([
Species.DARMANITAN,
Species.CHARIZARD,
]);
game.scene.getParty()[0].stats[Stat.SPD] = 1;
game.scene.getParty()[0].stats[Stat.HP] = 1000;
game.scene.getParty()[0].hp = 100;
expect(game.scene.getParty()[0].formIndex).toBe(0);
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(DamagePhase, false);
// await game.phaseInterceptor.runFrom(DamagePhase).to(DamagePhase, false);
const damagePhase = game.scene.getCurrentPhase() as DamagePhase;
damagePhase.updateAmount(80);
await game.phaseInterceptor.runFrom(DamagePhase).to(QuietFormChangePhase);
expect(game.scene.getParty()[0].hp).not.toBe(100);
expect(game.scene.getParty()[0].formIndex).not.toBe(0);
await game.killPokemon(game.scene.getParty()[0]);
expect(game.scene.getParty()[0].isFainted()).toBe(true);
await game.phaseInterceptor.run(MessagePhase);
await game.phaseInterceptor.run(EnemyCommandPhase);
await game.phaseInterceptor.run(TurnStartPhase);
game.onNextPrompt("SwitchPhase", Mode.PARTY, () => {
game.scene.unshiftPhase(new SwitchSummonPhase(game.scene, 0, 1, false, false));
game.scene.ui.setMode(Mode.MESSAGE);
});
game.onNextPrompt("SwitchPhase", Mode.MESSAGE, () => {
game.endPhase();
});
await game.phaseInterceptor.run(SwitchPhase);
await game.phaseInterceptor.to(PostSummonPhase);
expect(game.scene.getParty()[1].formIndex).toBe(1);
}, 20000);
});

View File

@ -6,7 +6,7 @@ import {Species} from "#app/data/enums/species";
import * as overrides from "../../overrides";
import {Command} from "#app/ui/command-ui-handler";
import {
CommandPhase,
CommandPhase, DamagePhase,
EncounterPhase,
EnemyCommandPhase,
LoginPhase,
@ -15,7 +15,7 @@ import {
SelectStarterPhase,
SummonPhase,
TitlePhase,
TurnInitPhase,
TurnInitPhase, VictoryPhase,
} from "#app/phases";
import {Moves} from "#app/data/enums/moves";
import GameManager from "#app/test/utils/gameManager";
@ -106,9 +106,7 @@ describe("Test Battle Phase", () => {
const movePosition = getMovePosition(game.scene, 0, Moves.TACKLE);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(SelectModifierPhase);
expect(game.scene.ui?.getMode()).toBe(Mode.MODIFIER_SELECT);
expect(game.scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(SelectModifierPhase, false);
}, 20000);
it("do attack wave 3 - single battle - regular - NO OHKO with opponent using non damage attack", async() => {
@ -128,7 +126,7 @@ describe("Test Battle Phase", () => {
const movePosition = getMovePosition(game.scene, 0, Moves.TACKLE);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnInitPhase);
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnInitPhase, false);
}, 20000);
it("load 100% data file", async() => {
@ -259,5 +257,71 @@ describe("Test Battle Phase", () => {
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
}, 20000);
it("kill opponent pokemon", async() => {
const moveToUse = Moves.SPLASH;
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "STARTER_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MEWTWO);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION);
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.ZEN_MODE);
vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(2000);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(3);
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE,Moves.TACKLE,Moves.TACKLE,Moves.TACKLE]);
await game.startBattle([
Species.DARMANITAN,
Species.CHARIZARD,
]);
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.to(DamagePhase, false);
await game.killPokemon(game.scene.currentBattle.enemyParty[0]);
expect(game.scene.currentBattle.enemyParty[0].isFainted()).toBe(true);
await game.phaseInterceptor.to(VictoryPhase, false);
}, 200000);
it("to next turn", async() => {
const moveToUse = Moves.SPLASH;
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "STARTER_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MEWTWO);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION);
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.ZEN_MODE);
vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(2000);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(3);
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE,Moves.TACKLE,Moves.TACKLE,Moves.TACKLE]);
await game.startBattle();
const turn = game.scene.currentBattle.turn;
game.doAttack(0);
await game.toNextTurn();
expect(game.scene.currentBattle.turn).toBeGreaterThan(turn);
}, 20000);
it("to next wave with pokemon killed, single", async() => {
const moveToUse = Moves.SPLASH;
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "STARTER_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MEWTWO);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION);
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.ZEN_MODE);
vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(2000);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(3);
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE,Moves.TACKLE,Moves.TACKLE,Moves.TACKLE]);
await game.startBattle();
const waveIndex = game.scene.currentBattle.waveIndex;
game.doAttack(0);
await game.doKillOpponents();
await game.toNextWave();
expect(game.scene.currentBattle.waveIndex).toBeGreaterThan(waveIndex);
}, 20000);
});

View File

@ -1,4 +1,4 @@
import {afterEach, beforeAll, beforeEach, describe, it, vi} from "vitest";
import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
import GameManager from "#app/test/utils/gameManager";
import Phaser from "phaser";
import * as overrides from "#app/overrides";
@ -22,18 +22,24 @@ describe("Test Battle Phase", () => {
beforeEach(() => {
game = new GameManager(phaserGame);
});
it("should start phase", async() => {
const moveToUse = Moves.SPLASH;
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "STARTER_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MEWTWO);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION);
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.ZEN_MODE);
vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(2000);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(3);
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE,Moves.TACKLE,Moves.TACKLE,Moves.TACKLE]);
});
it.skip("to next turn", async() => {
await game.startBattle();
}, 100000);
const turn = game.scene.currentBattle.turn;
game.doAttack(0);
await game.toNextTurn();
expect(game.scene.currentBattle.turn).toBeGreaterThan(turn);
}, 20000);
});

View File

@ -0,0 +1,137 @@
import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
import {Mode} from "#app/ui/ui";
import {Species} from "#app/data/enums/species";
import * as overrides from "../../overrides";
import {
CommandPhase,
} from "#app/phases";
import {Moves} from "#app/data/enums/moves";
import GameManager from "#app/test/utils/gameManager";
import Phaser from "phaser";
import {Abilities} from "#app/data/enums/abilities";
describe("Test Battle Phase", () => {
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, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA);
vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(2000);
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION);
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
});
it("startBattle 2vs1 boss", async() => {
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(10);
await game.startBattle([
Species.BLASTOISE,
Species.CHARIZARD,
]);
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
}, 20000);
it("startBattle 2vs2 boss", async() => {
vi.spyOn(overrides, "DOUBLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(10);
await game.startBattle([
Species.BLASTOISE,
Species.CHARIZARD,
]);
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
}, 20000);
it("startBattle 2vs2 trainer", async() => {
vi.spyOn(overrides, "DOUBLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(5);
await game.startBattle([
Species.BLASTOISE,
Species.CHARIZARD,
]);
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
}, 20000);
it("startBattle 2vs1 trainer", async() => {
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(5);
await game.startBattle([
Species.BLASTOISE,
Species.CHARIZARD,
]);
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
}, 20000);
it("startBattle 2vs1 rival", async() => {
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(8);
await game.startBattle([
Species.BLASTOISE,
Species.CHARIZARD,
]);
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
}, 20000);
it("startBattle 2vs2 rival", async() => {
vi.spyOn(overrides, "DOUBLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(8);
await game.startBattle([
Species.BLASTOISE,
Species.CHARIZARD,
]);
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
}, 20000);
it("startBattle 1vs1 trainer", async() => {
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(5);
await game.startBattle([
Species.BLASTOISE,
]);
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
}, 20000);
it("startBattle 2vs2 trainer", async() => {
vi.spyOn(overrides, "DOUBLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(5);
await game.startBattle([
Species.BLASTOISE,
Species.CHARIZARD,
]);
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
}, 20000);
it("startBattle 4vs2 trainer", async() => {
vi.spyOn(overrides, "DOUBLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(5);
await game.startBattle([
Species.BLASTOISE,
Species.CHARIZARD,
Species.DARKRAI,
Species.GABITE,
]);
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
}, 20000);
});

View File

@ -11,6 +11,6 @@ export default class TextInterceptor {
}
getLatestMessage(): string {
return this.logs[this.logs.length - 1];
return this.logs.pop();
}
}

View File

@ -4,18 +4,18 @@ import {generateStarter, waitUntil} from "#app/test/utils/gameManagerUtils";
import {
CommandPhase,
EncounterPhase,
LoginPhase,
PostSummonPhase,
FaintPhase,
LoginPhase, NewBattlePhase,
SelectGenderPhase,
SelectStarterPhase,
TitlePhase,
TitlePhase, TurnInitPhase,
} from "#app/phases";
import BattleScene from "#app/battle-scene.js";
import PhaseInterceptor from "#app/test/utils/phaseInterceptor";
import TextInterceptor from "#app/test/utils/TextInterceptor";
import {GameModes, getGameMode} from "#app/game-mode";
import fs from "fs";
import { AES, enc } from "crypto-js";
import {AES, enc} from "crypto-js";
import {updateUserInfo} from "#app/account";
import {Species} from "#app/data/enums/species";
import {PlayerGender} from "#app/data/enums/player-gender";
@ -23,6 +23,11 @@ import {GameDataType} from "#app/data/enums/game-data-type";
import InputsHandler from "#app/test/utils/inputsHandler";
import {ExpNotification} from "#app/enums/exp-notification";
import ErrorInterceptor from "#app/test/utils/errorInterceptor";
import {EnemyPokemon, PlayerPokemon} from "#app/field/pokemon";
import {MockClock} from "#app/test/utils/mocks/mockClock";
import {Command} from "#app/ui/command-ui-handler";
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
import {Button} from "#app/enums/buttons";
/**
* Class to manage the game state and transitions between phases.
@ -84,31 +89,31 @@ export default class GameManager {
* @param callback - The callback to execute.
* @param expireFn - Optional function to determine if the prompt has expired.
*/
onNextPrompt(phaseTarget: string, mode: Mode, callback: () => void, expireFn?: () => void) {
this.phaseInterceptor.addToNextPrompt(phaseTarget, mode, callback, expireFn);
onNextPrompt(phaseTarget: string, mode: Mode, callback: () => void, expireFn?: () => void, awaitingActionInput: boolean = false) {
this.phaseInterceptor.addToNextPrompt(phaseTarget, mode, callback, expireFn, awaitingActionInput);
}
/**
* Runs the game to the title phase.
* @returns A promise that resolves when the title phase is reached.
*/
runToTitle(): Promise<void> {
return new Promise(async(resolve, reject) => {
await this.phaseInterceptor.run(LoginPhase).catch((e) => reject(e));
this.onNextPrompt("SelectGenderPhase", Mode.OPTION_SELECT, () => {
this.scene.gameData.gender = PlayerGender.MALE;
this.endPhase();
}, () => this.isCurrentPhase(TitlePhase));
await this.phaseInterceptor.run(SelectGenderPhase, () => this.isCurrentPhase(TitlePhase)).catch((e) => reject(e));
await this.phaseInterceptor.run(TitlePhase).catch((e) => reject(e));
this.scene.gameSpeed = 5;
this.scene.moveAnimations = false;
this.scene.showLevelUpStats = false;
this.scene.expGainsSpeed = 3;
this.scene.expParty = ExpNotification.SKIP;
this.scene.hpBarSpeed = 3;
resolve();
});
async runToTitle(): Promise<void> {
await this.phaseInterceptor.run(LoginPhase);
this.onNextPrompt("SelectGenderPhase", Mode.OPTION_SELECT, () => {
this.scene.gameData.gender = PlayerGender.MALE;
this.endPhase();
}, () => this.isCurrentPhase(TitlePhase));
await this.phaseInterceptor.run(SelectGenderPhase, () => this.isCurrentPhase(TitlePhase));
await this.phaseInterceptor.run(TitlePhase);
this.scene.gameSpeed = 5;
this.scene.moveAnimations = false;
this.scene.showLevelUpStats = false;
this.scene.expGainsSpeed = 3;
this.scene.expParty = ExpNotification.SKIP;
this.scene.hpBarSpeed = 3;
}
/**
@ -116,41 +121,91 @@ export default class GameManager {
* @param species - Optional array of species to summon.
* @returns A promise that resolves when the summon phase is reached.
*/
runToSummon(species?: Species[]): Promise<void> {
return new Promise(async(resolve, reject) => {
await this.runToTitle().catch((e) => reject(e));
this.onNextPrompt("TitlePhase", Mode.TITLE, () => {
this.scene.gameMode = getGameMode(GameModes.CLASSIC);
const starters = generateStarter(this.scene, species);
const selectStarterPhase = new SelectStarterPhase(this.scene);
this.scene.pushPhase(new EncounterPhase(this.scene, false));
selectStarterPhase.initBattle(starters);
});
await this.phaseInterceptor.run(EncounterPhase).catch((e) => reject(e));
resolve();
async runToSummon(species?: Species[]) {
await this.runToTitle();
this.onNextPrompt("TitlePhase", Mode.TITLE, () => {
this.scene.gameMode = getGameMode(GameModes.CLASSIC);
const starters = generateStarter(this.scene, species);
const selectStarterPhase = new SelectStarterPhase(this.scene);
this.scene.pushPhase(new EncounterPhase(this.scene, false));
selectStarterPhase.initBattle(starters);
});
await this.phaseInterceptor.run(EncounterPhase);
}
/**
* Starts a battle.
* Transitions to the start of a battle.
* @param species - Optional array of species to start the battle with.
* @returns A promise that resolves when the battle is started.
*/
startBattle(species?: Species[]): Promise<void> {
return new Promise(async(resolve, reject) => {
await this.runToSummon(species).catch((e) => reject(e));
this.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => {
this.setMode(Mode.MESSAGE);
this.endPhase();
}, () => this.isCurrentPhase(CommandPhase));
this.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => {
this.setMode(Mode.MESSAGE);
this.endPhase();
}, () => this.isCurrentPhase(CommandPhase));
await this.phaseInterceptor.runFrom(PostSummonPhase).to(CommandPhase).catch((e) => reject(e));
console.log("==================[New Turn]==================");
return resolve();
async startBattle(species?: Species[]) {
await this.runToSummon(species);
this.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => {
this.setMode(Mode.MESSAGE);
this.endPhase();
}, () => this.isCurrentPhase(CommandPhase) || this.isCurrentPhase(TurnInitPhase));
this.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => {
this.setMode(Mode.MESSAGE);
this.endPhase();
}, () => this.isCurrentPhase(CommandPhase) || this.isCurrentPhase(TurnInitPhase));
await this.phaseInterceptor.to(CommandPhase);
console.log("==================[New Turn]==================");
}
/**
* Emulate a player attack
* @param movePosition the index of the move in the pokemon's moveset array
*/
doAttack(movePosition: integer) {
this.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
this.scene.ui.setMode(Mode.FIGHT, (this.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
this.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
(this.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
}
/** Faint all opponents currently on the field */
async doKillOpponents() {
await this.killPokemon(this.scene.currentBattle.enemyParty[0]);
if (this.scene.currentBattle.double) {
await this.killPokemon(this.scene.currentBattle.enemyParty[1]);
}
}
/** Emulate selecting a modifier (item) */
doSelectModifier() {
this.onNextPrompt("SelectModifierPhase", Mode.MODIFIER_SELECT, () => {
const handler = this.scene.ui.getHandler() as ModifierSelectUiHandler;
handler.processInput(Button.CANCEL);
}, () => this.isCurrentPhase(CommandPhase) || this.isCurrentPhase(NewBattlePhase), true);
this.onNextPrompt("SelectModifierPhase", Mode.CONFIRM, () => {
const handler = this.scene.ui.getHandler() as ModifierSelectUiHandler;
handler.processInput(Button.ACTION);
}, () => this.isCurrentPhase(CommandPhase) || this.isCurrentPhase(NewBattlePhase));
}
/** Transition to the next upcoming {@linkcode CommandPhase} */
async toNextTurn() {
await this.phaseInterceptor.to(CommandPhase);
}
/** Emulate selecting a modifier (item) and transition to the next upcoming {@linkcode CommandPhase} */
async toNextWave() {
this.doSelectModifier();
this.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => {
this.setMode(Mode.MESSAGE);
this.endPhase();
}, () => this.isCurrentPhase(TurnInitPhase));
await this.toNextTurn();
}
/**
@ -213,4 +268,15 @@ export default class GameManager {
}
return updateUserInfo();
}
async killPokemon(pokemon: PlayerPokemon | EnemyPokemon) {
(this.scene.time as MockClock).overrideDelay = 0.01;
return new Promise<void>(async(resolve, reject) => {
pokemon.hp = 0;
this.scene.pushPhase(new FaintPhase(this.scene, pokemon.getBattlerIndex(), true));
await this.phaseInterceptor.to(FaintPhase).catch((e) => reject(e));
(this.scene.time as MockClock).overrideDelay = undefined;
resolve();
});
}
}

View File

@ -120,7 +120,7 @@ export default class GameWrapper {
pause: () => null,
setRate: () => null,
add: () => this.scene.sound,
get: () => this.scene.sound,
get: () => ({...this.scene.sound, totalDuration: 0}),
getAllPlaying: () => [],
manager: {
game: this.game,
@ -131,6 +131,13 @@ export default class GameWrapper {
key: "",
};
this.scene.cameras = {
main: {
setPostPipeline: () => null,
removePostPipeline: () => null,
},
}
this.scene.tweens = {
add: (data) => {
if (data.onComplete) {

View File

@ -2,8 +2,10 @@ import Clock = Phaser.Time.Clock;
export class MockClock extends Clock {
public overrideDelay: number;
constructor(scene) {
super(scene);
this.overrideDelay = undefined;
setInterval(() => {
/*
To simulate frame update
@ -14,4 +16,9 @@ export class MockClock extends Clock {
this.update(this.systems.game.loop.time, 100);
}, 100);
}
addEvent(config: Phaser.Time.TimerEvent | Phaser.Types.Time.TimerEventConfig): Phaser.Time.TimerEvent {
const cfg = { ...config, delay: this.overrideDelay || config.delay};
return super.addEvent(cfg);
}
}

View File

@ -143,6 +143,7 @@ export default class MockSprite {
play() {
// return this.phaserSprite.play();
return this;
}
setPipelineData(key, value) {

View File

@ -8,6 +8,7 @@ export default class MockText {
private textureManager;
public list = [];
public style;
constructor(textureManager, x, y, content, styleOptions) {
this.scene = textureManager.scene;
this.textureManager = textureManager;
@ -138,6 +139,15 @@ export default class MockText {
// return this.phaserText.setX(x);
}
/**
* Sets the position of this Game Object.
* @param x The x position of this Game Object. Default 0.
* @param y The y position of this Game Object. If not set it will use the `x` value. Default x.
* @param z The z position of this Game Object. Default 0.
* @param w The w position of this Game Object. Default 0.
*/
setPosition(x?: number, y?: number, z?: number, w?: number) { }
setText(text) {
// Sets the text this Game Object will display.
// return this.phaserText.setText(text);

View File

@ -1,17 +1,43 @@
import {
BattleEndPhase,
BerryPhase,
CheckSwitchPhase, CommandPhase, DamagePhase, EggLapsePhase,
EncounterPhase, EnemyCommandPhase, FaintPhase,
LoginPhase, MessagePhase, MoveEffectPhase, MoveEndPhase, MovePhase, NewBattlePhase, NextEncounterPhase,
CheckSwitchPhase,
CommandPhase,
DamagePhase,
EggLapsePhase,
EncounterPhase,
EnemyCommandPhase,
FaintPhase,
LoginPhase,
MessagePhase,
MoveEffectPhase,
MoveEndPhase,
MovePhase,
NewBattlePhase,
NextEncounterPhase,
PostSummonPhase,
SelectGenderPhase, SelectModifierPhase,
SelectStarterPhase, SelectTargetPhase, ShinySparklePhase, ShowAbilityPhase, StatChangePhase, SummonPhase,
TitlePhase, ToggleDoublePositionPhase, TurnEndPhase, TurnInitPhase, TurnStartPhase, UnavailablePhase, VictoryPhase
SelectGenderPhase,
SelectModifierPhase,
SelectStarterPhase,
SelectTargetPhase,
ShinySparklePhase,
ShowAbilityPhase,
StatChangePhase,
SummonPhase,
SwitchPhase,
SwitchSummonPhase,
TitlePhase,
ToggleDoublePositionPhase,
TurnEndPhase,
TurnInitPhase,
TurnStartPhase,
UnavailablePhase,
VictoryPhase
} from "#app/phases";
import UI, {Mode} from "#app/ui/ui";
import {Phase} from "#app/phase";
import ErrorInterceptor from "#app/test/utils/errorInterceptor";
import {QuietFormChangePhase} from "#app/form-change-phase";
export default class PhaseInterceptor {
public scene;
@ -63,10 +89,13 @@ export default class PhaseInterceptor {
[ShinySparklePhase, this.startPhase],
[SelectTargetPhase, this.startPhase],
[UnavailablePhase, this.startPhase],
[QuietFormChangePhase, this.startPhase],
[SwitchPhase, this.startPhase],
[SwitchSummonPhase, this.startPhase],
];
private endBySetMode = [
TitlePhase, SelectGenderPhase, CommandPhase, SelectModifierPhase
TitlePhase, SelectGenderPhase, CommandPhase
];
/**
@ -78,8 +107,8 @@ export default class PhaseInterceptor {
this.log = [];
this.onHold = [];
this.prompts = [];
this.startPromptHandler();
this.initPhases();
this.startPromptHander();
}
rejectAll(error) {
@ -109,8 +138,10 @@ export default class PhaseInterceptor {
async to(phaseTo, runTarget: boolean = true): Promise<void> {
return new Promise(async (resolve, reject) => {
ErrorInterceptor.getInstance().add(this);
await this.run(this.phaseFrom).catch((e) => reject(e));
this.phaseFrom = null;
if (this.phaseFrom) {
await this.run(this.phaseFrom).catch((e) => reject(e));
this.phaseFrom = null;
}
const targetName = typeof phaseTo === "string" ? phaseTo : phaseTo.name;
this.intervalRun = setInterval(async() => {
const currentPhase = this.onHold?.length && this.onHold[0];
@ -238,7 +269,6 @@ export default class PhaseInterceptor {
*/
superEndPhase() {
const instance = this.scene.getCurrentPhase();
console.log(`%c INTERCEPTED Super End Phase ${instance.constructor.name}`, "color:red;");
this.originalSuperEnd.apply(instance);
this.inProgress?.callback();
this.inProgress = undefined;
@ -253,6 +283,9 @@ export default class PhaseInterceptor {
const instance = this.scene.ui;
console.log("setMode", mode, args);
const ret = this.originalSetMode.apply(instance, [mode, ...args]);
if (!this.phases[currentPhase.constructor.name]) {
throw new Error(`missing ${currentPhase.constructor.name} in phaseInterceptior PHASES list`);
}
if (this.phases[currentPhase.constructor.name].endBySetMode) {
this.inProgress?.callback();
this.inProgress = undefined;
@ -263,16 +296,17 @@ export default class PhaseInterceptor {
/**
* Method to start the prompt handler.
*/
startPromptHander() {
startPromptHandler() {
this.promptInterval = setInterval(() => {
if (this.prompts.length) {
const actionForNextPrompt = this.prompts[0];
const expireFn = actionForNextPrompt.expireFn && actionForNextPrompt.expireFn();
const currentMode = this.scene.ui.getMode();
const currentPhase = this.scene.getCurrentPhase().constructor.name;
const currentHandler = this.scene.ui.getHandler();
if (expireFn) {
this.prompts.shift();
} else if (currentMode === actionForNextPrompt.mode && currentPhase === actionForNextPrompt.phaseTarget) {
} else if (currentMode === actionForNextPrompt.mode && currentPhase === actionForNextPrompt.phaseTarget && currentHandler.active && (!actionForNextPrompt.awaitingActionInput || (actionForNextPrompt.awaitingActionInput && currentHandler.awaitingActionInput))) {
this.prompts.shift().callback();
}
}
@ -286,12 +320,13 @@ export default class PhaseInterceptor {
* @param callback - The callback function to execute.
* @param expireFn - The function to determine if the prompt has expired.
*/
addToNextPrompt(phaseTarget: string, mode: Mode, callback: () => void, expireFn: () => void) {
addToNextPrompt(phaseTarget: string, mode: Mode, callback: () => void, expireFn: () => void, awaitingActionInput: boolean = false) {
this.prompts.push({
phaseTarget,
mode,
callback,
expireFn
expireFn,
awaitingActionInput
});
}

View File

@ -179,21 +179,25 @@ export default class FightUiHandler extends UiHandler {
const pp = maxPP - pokemonMove.ppUsed;
this.ppText.setText(`${Utils.padInt(pp, 2, " ")}/${Utils.padInt(maxPP, 2, " ")}`);
const ppPercentLeft = pp / maxPP;
let ppColor = "white";
if (ppPercentLeft <= 0.5) {
ppColor = "yellow";
}
if (ppPercentLeft <= 0.25) {
ppColor = "orange";
}
if (pp === 0) {
ppColor = "red";
}
this.ppText.setColor(ppColor);
this.powerText.setText(`${power >= 0 ? power : "---"}`);
this.accuracyText.setText(`${accuracy >= 0 ? accuracy : "---"}`);
const ppPercentLeft = pp / maxPP;
//** Determines TextStyle according to percentage of PP remaining */
let ppColorStyle = TextStyle.MOVE_PP_FULL;
if (ppPercentLeft > 0.25 && ppPercentLeft <= 0.5) {
ppColorStyle = TextStyle.MOVE_PP_HALF_FULL;
} else if (ppPercentLeft > 0 && ppPercentLeft <= 0.25) {
ppColorStyle = TextStyle.MOVE_PP_NEAR_EMPTY;
} else if (ppPercentLeft === 0) {
ppColorStyle = TextStyle.MOVE_PP_EMPTY;
}
//** Changes the text color and shadow according to the determined TextStyle */
this.ppText.setColor(this.getTextColor(ppColorStyle, false));
this.ppText.setShadowColor(this.getTextColor(ppColorStyle, true));
pokemon.getOpponents().forEach((opponent) => {
opponent.updateEffectiveness(this.getEffectivenessText(pokemon, opponent, pokemonMove));
});

View File

@ -1,3 +1,4 @@
import i18next from "i18next";
import BattleScene from "../battle-scene";
import { ModalUiHandler } from "./modal-ui-handler";
import { addTextObject, TextStyle } from "./text";
@ -31,7 +32,7 @@ export default class LoadingModalUiHandler extends ModalUiHandler {
setup(): void {
super.setup();
const label = addTextObject(this.scene, this.getWidth() / 2, this.getHeight() / 2, "Loading…", TextStyle.WINDOW);
const label = addTextObject(this.scene, this.getWidth() / 2, this.getHeight() / 2, i18next.t("menu:loading"), TextStyle.WINDOW);
label.setOrigin(0.5, 0.5);
this.modalContainer.add(label);

View File

@ -6,12 +6,12 @@ import { getNatureName } from "../data/nature";
import { Type } from "../data/type";
import Pokemon from "../field/pokemon";
import i18next from "../plugins/i18n";
import { DexAttr } from "../system/game-data";
import * as Utils from "../utils";
import ConfirmUiHandler from "./confirm-ui-handler";
import { StatsContainer } from "./stats-container";
import { TextStyle, addBBCodeTextObject, addTextObject, getTextColor } from "./text";
import { addWindow } from "./ui-theme";
import { DexAttr } from "../system/game-data";
interface LanguageSetting {
infoContainerTextSize: string;
@ -40,7 +40,7 @@ const languageSettings: { [key: string]: LanguageSetting } = {
},
"pt": {
infoContainerTextSize: "60px",
infoContainerLabelXPos: -16,
infoContainerLabelXPos: -15,
infoContainerTextXPos: -12,
},
};

View File

@ -193,8 +193,21 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
private pokemonHatchedIcon : Phaser.GameObjects.Sprite;
private pokemonHatchedCountText: Phaser.GameObjects.Text;
private genOptionsText: Phaser.GameObjects.Text;
private instructionsContainer: Phaser.GameObjects.Container;
private instructionsText: Phaser.GameObjects.Text;
private shinyIconElement: Phaser.GameObjects.Sprite;
private formIconElement: Phaser.GameObjects.Sprite;
private abilityIconElement: Phaser.GameObjects.Sprite;
private genderIconElement: Phaser.GameObjects.Sprite;
private natureIconElement: Phaser.GameObjects.Sprite;
private variantIconElement: Phaser.GameObjects.Sprite;
private shinyLabel: Phaser.GameObjects.Text;
private formLabel: Phaser.GameObjects.Text;
private genderLabel: Phaser.GameObjects.Text;
private abilityLabel: Phaser.GameObjects.Text;
private natureLabel: Phaser.GameObjects.Text;
private variantLabel: Phaser.GameObjects.Text;
private starterSelectMessageBox: Phaser.GameObjects.NineSlice;
private starterSelectMessageBoxContainer: Phaser.GameObjects.Container;
private statsContainer: StatsContainer;
@ -656,10 +669,45 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.starterSelectContainer.add(this.pokemonEggMovesContainer);
// The font size should be set per language
const instructionTextSize = textSettings.instructionTextSize;
this.instructionsContainer = this.scene.add.container(4, 156);
this.instructionsContainer.setVisible(true);
this.starterSelectContainer.add(this.instructionsContainer);
// instruction rows that will be pushed into the container dynamically based on need
this.shinyIconElement = this.scene.add.sprite(this.instructionRowX, this.instructionRowY, "keyboard", "R.png");
this.shinyIconElement.setScale(0.675);
this.shinyIconElement.setOrigin(0.0, 0.0);
this.shinyLabel = addTextObject(this.scene, this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY, i18next.t("starterSelectUiHandler:cycleShiny"), TextStyle.PARTY, { fontSize: instructionTextSize });
this.formIconElement = this.scene.add.sprite(this.instructionRowX, this.instructionRowY, "keyboard", "F.png");
this.formIconElement.setScale(0.675);
this.formIconElement.setOrigin(0.0, 0.0);
this.formLabel = addTextObject(this.scene, this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY, i18next.t("starterSelectUiHandler:cycleForm"), TextStyle.PARTY, { fontSize: instructionTextSize });
this.genderIconElement = this.scene.add.sprite(this.instructionRowX, this.instructionRowY, "keyboard", "G.png");
this.genderIconElement.setScale(0.675);
this.genderIconElement.setOrigin(0.0, 0.0);
this.genderLabel = addTextObject(this.scene, this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY, i18next.t("starterSelectUiHandler:cycleGender"), TextStyle.PARTY, { fontSize: instructionTextSize });
this.abilityIconElement = this.scene.add.sprite(this.instructionRowX, this.instructionRowY, "keyboard", "E.png");
this.abilityIconElement.setScale(0.675);
this.abilityIconElement.setOrigin(0.0, 0.0);
this.abilityLabel = addTextObject(this.scene, this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY, i18next.t("starterSelectUiHandler:cycleAbility"), TextStyle.PARTY, { fontSize: instructionTextSize });
this.natureIconElement = this.scene.add.sprite(this.instructionRowX, this.instructionRowY, "keyboard", "N.png");
this.natureIconElement.setScale(0.675);
this.natureIconElement.setOrigin(0.0, 0.0);
this.natureLabel = addTextObject(this.scene, this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY, i18next.t("starterSelectUiHandler:cycleNature"), TextStyle.PARTY, { fontSize: instructionTextSize });
this.variantIconElement = this.scene.add.sprite(this.instructionRowX, this.instructionRowY, "keyboard", "V.png");
this.variantIconElement.setScale(0.675);
this.variantIconElement.setOrigin(0.0, 0.0);
this.variantLabel = addTextObject(this.scene, this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY, i18next.t("starterSelectUiHandler:cycleVariant"), TextStyle.PARTY, { fontSize: instructionTextSize });
this.hideInstructions();
this.starterSelectMessageBoxContainer = this.scene.add.container(0, this.scene.game.canvas.height / 6);
this.starterSelectMessageBoxContainer.setVisible(false);
this.starterSelectContainer.add(this.starterSelectMessageBoxContainer);
@ -1494,7 +1542,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.setSpeciesDetails(this.lastSpecies, undefined, undefined, undefined, undefined, undefined, undefined, false);
}
createButtonFromIconText(iconSetting, gamepadType, translatedText, instructionTextSize): void {
updateButtonIcon(iconSetting, gamepadType, iconElement, controlLabel): void {
let iconPath;
// touch controls cannot be rebound as is, and are just emulating a keyboard event.
// Additionally, since keyboard controls can be rebound (and will be displayed when they are), we need to have special handling for the touch controls
@ -1525,10 +1573,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
} else {
iconPath = this.scene.inputController?.getIconForLatestInputRecorded(iconSetting);
}
const iconElement = this.scene.add.sprite(this.instructionRowX, this.instructionRowY, gamepadType, iconPath);
iconElement.setScale(0.675);
iconElement.setOrigin(0.0, 0.0);
const controlLabel = addTextObject(this.scene, this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY, translatedText, TextStyle.PARTY, { fontSize: instructionTextSize });
iconElement.setTexture(gamepadType, iconPath);
iconElement.setPosition(this.instructionRowX, this.instructionRowY);
controlLabel.setPosition(this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY);
iconElement.setVisible(true);
controlLabel.setVisible(true);
this.instructionsContainer.add([iconElement, controlLabel]);
this.instructionRowY += 8;
if (this.instructionRowY >= 24) {
@ -1538,12 +1587,9 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
}
updateInstructions(): void {
const currentLanguage = i18next.resolvedLanguage;
const langSettingKey = Object.keys(languageSettings).find(lang => currentLanguage.includes(lang));
const textSettings = languageSettings[langSettingKey];
const instructionTextSize = textSettings.instructionTextSize;
this.instructionRowX = 0;
this.instructionRowY = 0;
this.hideInstructions();
this.instructionsContainer.removeAll();
let gamepadType;
if (this.scene.inputMethod === "gamepad") {
@ -1554,22 +1600,22 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
if (this.speciesStarterDexEntry?.caughtAttr) {
if (this.canCycleShiny) {
this.createButtonFromIconText(SettingKeyboard.Button_Cycle_Shiny, gamepadType, i18next.t("starterSelectUiHandler:cycleShiny"), instructionTextSize);
this.updateButtonIcon(SettingKeyboard.Button_Cycle_Shiny, gamepadType, this.shinyIconElement, this.shinyLabel);
}
if (this.canCycleForm) {
this.createButtonFromIconText(SettingKeyboard.Button_Cycle_Form, gamepadType, i18next.t("starterSelectUiHandler:cycleForm"), instructionTextSize);
this.updateButtonIcon(SettingKeyboard.Button_Cycle_Form, gamepadType, this.formIconElement, this.formLabel);
}
if (this.canCycleGender) {
this.createButtonFromIconText(SettingKeyboard.Button_Cycle_Gender, gamepadType, i18next.t("starterSelectUiHandler:cycleGender"), instructionTextSize);
this.updateButtonIcon(SettingKeyboard.Button_Cycle_Gender, gamepadType, this.genderIconElement, this.genderLabel);
}
if (this.canCycleAbility) {
this.createButtonFromIconText(SettingKeyboard.Button_Cycle_Ability, gamepadType, i18next.t("starterSelectUiHandler:cycleAbility"), instructionTextSize);
this.updateButtonIcon(SettingKeyboard.Button_Cycle_Ability, gamepadType, this.abilityIconElement, this.abilityLabel);
}
if (this.canCycleNature) {
this.createButtonFromIconText(SettingKeyboard.Button_Cycle_Nature, gamepadType, i18next.t("starterSelectUiHandler:cycleNature"), instructionTextSize);
this.updateButtonIcon(SettingKeyboard.Button_Cycle_Nature, gamepadType, this.natureIconElement, this.natureLabel);
}
if (this.canCycleVariant) {
this.createButtonFromIconText(SettingKeyboard.Button_Cycle_Variant, gamepadType, i18next.t("starterSelectUiHandler:cycleVariant"), instructionTextSize);
this.updateButtonIcon(SettingKeyboard.Button_Cycle_Variant, gamepadType, this.variantIconElement, this.variantLabel);
}
}
}
@ -2341,9 +2387,25 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
super.clearText();
}
hideInstructions(): void {
this.shinyIconElement.setVisible(false);
this.shinyLabel.setVisible(false);
this.formIconElement.setVisible(false);
this.formLabel.setVisible(false);
this.genderIconElement.setVisible(false);
this.genderLabel.setVisible(false);
this.abilityIconElement.setVisible(false);
this.abilityLabel.setVisible(false);
this.natureIconElement.setVisible(false);
this.natureLabel.setVisible(false);
this.variantIconElement.setVisible(false);
this.variantLabel.setVisible(false);
}
clear(): void {
super.clear();
this.cursor = -1;
this.hideInstructions();
this.starterSelectContainer.setVisible(false);
this.blockInput = false;

View File

@ -29,7 +29,11 @@ export enum TextStyle {
SETTINGS_LOCKED,
TOOLTIP_TITLE,
TOOLTIP_CONTENT,
MOVE_INFO_CONTENT
MOVE_INFO_CONTENT,
MOVE_PP_FULL,
MOVE_PP_HALF_FULL,
MOVE_PP_NEAR_EMPTY,
MOVE_PP_EMPTY
}
interface LanguageSetting {
@ -204,11 +208,27 @@ export function getTextColor(textStyle: TextStyle, shadow?: boolean, uiTheme: Ui
return !shadow ? "#f8f8f8" : "#6b5a73";
case TextStyle.WINDOW:
case TextStyle.MOVE_INFO_CONTENT:
case TextStyle.MOVE_PP_FULL:
case TextStyle.TOOLTIP_CONTENT:
if (uiTheme) {
return !shadow ? "#484848" : "#d0d0c8";
}
return !shadow ? "#f8f8f8" : "#6b5a73";
case TextStyle.MOVE_PP_HALF_FULL:
if (uiTheme) {
return !shadow ? "#a68e17" : "#ebd773";
}
return !shadow ? "#ccbe00" : "#6e672c";
case TextStyle.MOVE_PP_NEAR_EMPTY:
if (uiTheme) {
return !shadow ? "#d64b00" : "#f7b18b";
}
return !shadow ? "#d64b00" : "#69402a";
case TextStyle.MOVE_PP_EMPTY:
if (uiTheme) {
return !shadow ? "#e13d3d" : "#fca2a2";
}
return !shadow ? "#e13d3d" : "#632929";
case TextStyle.WINDOW_ALT:
return !shadow ? "#484848" : "#d0d0c8";
case TextStyle.BATTLE_INFO: