Compare commits

..

17 Commits

Author SHA1 Message Date
Matthew
9c97e37c27
initVouchers correctly (#1940) 2024-06-07 23:50:07 -04:00
Madmadness65
bf5b6e3f6b
[Enhancement] Add more legendary battle music; add music preference setting (#1753)
* Add more legendary battle music; add music preference setting

* Replace BDSP tracks

"Souless", they say. Naysayers, I say.

* Update for settings menu refactor

* Remove unnecessary Reshiram/Zekrom music calls

The default legendary battle music is the Reshiram & Zekrom battle theme anyways, so removing these lines make it functionally identical.

* Update for settings menu refactor AGAIN
2024-06-07 21:52:14 -05:00
sodam
aa7c354be8
[Localization] #1761 Korean trainer dialogue (Gym leader in Johto region with Elite-4) (#1878)
* [Localization] #1761 Korean trainer dialogue(Gym leader in Johto with Elite-4)

* [Localization] #1761 Korean trainer dialogue(Gym leader in Johto with Elite-4)

* fixed word (by korean grammer)

* made chuck's dialogue clear

Co-authored-by: returntoice <dieandbecome@gmail.com>

* made lorelei's dialogue clear

Co-authored-by: returntoice <dieandbecome@gmail.com>

* fixed the spacing at dialogue.ts

Co-authored-by: returntoice <dieandbecome@gmail.com>

* fixed the spacing at dialogue.ts (with GINK-SS)

* Modified Karen's dialouge more characteristic

---------

Co-authored-by: returntoice <dieandbecome@gmail.com>
2024-06-07 22:32:30 -04:00
NightKev
ff7429f240
[Bug] Update Sweet Scent to reduce evasion by 2 (#1910)
cf https://bulbapedia.bulbagarden.net/wiki/Sweet_Scent_(move)
2024-06-07 21:42:17 -04:00
Matthew
3022aabc32
Cleanup format, show correct value for money rewards (#1926) 2024-06-08 02:09:12 +01:00
Matthew Olker
7422ebdb28 Walker asked me to commit to main 2024-06-07 19:43:21 -04:00
Greenlamp2
1a149bfa04
[Testing] Flexible Testing Wrapper for Phaser-Based Battle-Scenes (#1908)
* refactor executed code while importing and initializing all of these in loading-scene

* reset to main

* fix server url

* added rule no-trailing-spaces

* made progress

* test somme data from a session save is working

* trying to launch a battle

* added fetch wrapper to load data locally

* trying to mockAllSettled

* pushPhase & shiftPhase

* check integrity of exported session

* set toke + loggedInUser in tests

* progress on starting new battle

* tring to test phase but it's async

* mocking fetch

* working mock fetch

* need to handle pile of data

* attempt to use real phaser classes

* reorder overrides

* refactored to use some real classes from phaser

* removed useless things

* started to work on some container mock

* finished the mockContainer time to add some logic

* some more mock containers

* removed addMethods since there is the mock classes now

* commented issues

* attempt to create mockTextureManager

* fix tests

* mockSprite & mockText

* yes but not really

* yes but not really

* fix tutorial callback

* reached mode title

* added achievement tests

* fix test achievements with current state of mock

* correct sequence loading for BattleScene with mockLoader !

* deep dive into next step

* working wait until starter selection screen

* added newGame method into wrapper

* expect to save_slot

* trying to manage pokemon sprite for getAll without success yet

* added test for egg output

* fixed egg test for June

* fix tests + locate next issue to fix

* we are in battle baby

* added new game in one-line

* export is working but export only what's in the fetch

* fix start game as guest

* refactored how we start a battle + cleanup

* overrided mewtwo but issue with currentBattle

* refactor: rename InitAchievements to initAchievements

* added missing mock method

* override level and pokemon forms working as intended

* bringToTop Obj

* remove launch battle in achivement test

* fix getIndex when same pokemon

* can run all tests

* first attack, faint, and shop modifiers, MockClock

* on method for container

* added doAttack one-liner

* one-line export data

* removed throw error

* feat: Make `scenes` property of `GameWrapper` class public

The `scenes` property of the `GameWrapper` class was changed from private to public. This change allows external access to the `scenes` map, which is used to store Phaser scenes. This modification was made to enable easier manipulation and interaction with the scenes in the game.

* correction

* removed CanvasRenderer

* added a param to remove console.log and added a param to preven scene create call

* fix encounter wave 30 when it's a trainer

* test double-battle

* test fight without KO

* test double fight no ko

* fix crashing texture + added Text wrapper to log fight

* fix tests on boss - trainer - rival

* chore: Refactor BattleScene initialization and add new phases

Refactor the BattleScene initialization code to remove unnecessary delay and improve performance. Also, add new phases for the title and unavailable states to enhance the game experience.

* rework of Game tests

* skipFn is working

* added onNextPrompt and restore Og Start

* better newGame

* added skipFN in remove

* not yet working test but updated interceptors

* do attack work but not on PostSummonPhase phase when there is mention of silcoon and wurmple

* error located, it's just a double fight, i was not there yet

* single OHKO & double no OHKO

* added expirationFn into next prompt

* all tests are passing

* working test on non damaging move from opponent

* cleaned a bit

* removed phaser initialisation on every tests

* renamed test file

* added load system data

* added some ability support

* added onKill & onSummon abilities test

* removed useless test + cleanup

* removed useless test + cleanup

* fixed tests after merge main

* added itemHeld endTurn trigger test (toxic orb)

* added runFrom..To

* added mustRun to assert currentPhase

* added no-miss move to test things

* cleaner restore mock

* fix test

* fix moxie test + game speed

* improve test speed

* added onOurself and onOpponent mvoe test

* added onDamage test for tackle

* removed timeout in intervals to run tests faster

* cleanup

* added never crit override + separate file per test + remove randomness in randBattleSeedInt

* move folders

* better org

* renamed itemHeld folder to items

* fix deploy.yml

* cleanup

* simplified the gameManager start battle and allow single pokemon in party

* remove the need of mode development

* added input handler to test inputs + remove time from phaser into inputController

* added keyboard support

* added fakeMobile support

* added details

* removed a console.log + added logUp

* move test to folder

* fixed canvas issue

* added starter select tests

* added some more test on starter-select

* added battle-order tests

* added battle-order tests

* fixing Phaser RNG

* ordering stats for better reading

* fix tests for main

* adapt battle-order test to be more readable

* fix merge

* fix some errors and silent all errors from gameWrapper since it's not possible to avoid them

* fix mocks to manage childs & stuffs

* added some docs

* fix achievement test

* removed an unused file

* separate misc tests to clean battle.test file

* added a basic french lokalization test

* added i18n where it needs to be used only

* revers extracted method

* removed unused method

* removed handler fetch since we do not test anything server related

* fix test with handlers removed

* added intrepid sword test

* fix enum exp party

---------

Co-authored-by: Frederico Santos <frederico.f.santos@tecnico.ulisboa.pt>
2024-06-07 18:33:45 -04:00
Madmadness65
3d87e86e58 Fix minor typo in Surskit's German name 2024-06-07 16:57:26 -05:00
Jannik Tappert
ece7c9f2d5
Added missing german dialogue for sailor and firebreather (#1883) 2024-06-07 22:51:49 +01:00
AJ Fontaine
f3898dbabd
[Bug] Fix female trainer names on male Backpackers/Hikers (#1919)
* Fix female trainer names on male Backpackers/Hikers

* Remove duplicate Backpacker Ruth
2024-06-07 17:44:35 -04:00
AJ Fontaine
eb058d7eb7
[Bug] Fix Harvest not checking stack limits for berries (#1920) 2024-06-07 17:42:48 -04:00
Madmadness65
83c11a0865 Allow Partner Pikachu & Eevee to Gigantamax
Note that they will revert back to regular Pikachus and Eevees if deactivated due to an existing bug with the form changes.
2024-06-07 16:21:18 -05:00
Matthew Olker
63d98fe70e Hotfix: types breaking gh-pages action 2024-06-07 17:05:44 -04:00
td76099
0a17c2495a
Bugfix: Abilities check final move type instead of default move type (#1440)
* Added check for move changing type before determining if defending is immune to it because of an ability

* Remove duplicate Ability change class and added getType() function under a move

* Reworking how moves get passed into hit calc

* Fixing exceptions and overreaching changes

* reverting forwarn and dancing move back to original since they are not being changed

* fixing some small move related bugs

* Fixing file permissions after testing

* Fixing move type not resetting after individual MoveEffectPhase

* Fixing move.ts permissions (again)

* Addressing some MR feedback and adding some documentation for PokemonMove class
2024-06-07 16:57:57 -04:00
sodam
636cb9c8f2
[Localization] #1761 Korean trainer dialogue (some unnamed trainers) (#1911)
* localized to korean (some unnamed trainers)

* fixed dialogue's meaning clear

Co-authored-by: returntoice <dieandbecome@gmail.com>

* fixed spacing right

Co-authored-by: Sangmin Lee <66083363+GINK-SS@users.noreply.github.com>

---------

Co-authored-by: returntoice <dieandbecome@gmail.com>
Co-authored-by: Sangmin Lee <66083363+GINK-SS@users.noreply.github.com>
2024-06-07 21:52:31 +01:00
prime
f5f98ec537
[Bug] fixed wrong stacking of move info overlay issues (#1888)
* fixed wrongly stacking overlay issues

- starter selection
 - IVs are now behind the overlay
 - the overlay should clear when exiting it via controller (requires tests as i don't have a controller)
- TM
 - will prevent C/Shift from showing the overlay until the next item selection, when selecting a TM as item reward.
 - will prevent C/Shift from showing the overlay when canceling item selection

* removed reference to previously deleted resource

* fixed override
2024-06-07 16:45:49 -04:00
Jannik Tappert
97dde2d1f3
[QoL] Added https and server url is read from the env now (#1764)
* Added https and server url is read from the env now

* Added the new key to the vite.env.d.ts
2024-06-07 21:43:32 +01:00
108 changed files with 5133 additions and 610 deletions

3
.env
View File

@ -1,2 +1,3 @@
VITE_BYPASS_LOGIN=0
VITE_BYPASS_TUTORIAL=0
VITE_BYPASS_TUTORIAL=0
VITE_SERVER_URL=http://localhost:8001

View File

@ -1,2 +1,3 @@
VITE_BYPASS_LOGIN=1
VITE_BYPASS_TUTORIAL=0
VITE_BYPASS_TUTORIAL=0
VITE_SERVER_URL=http://localhost:8001

View File

@ -46,7 +46,14 @@ Check out [Github Issues](https://github.com/pagefaultgames/pokerogue/issues) to
- Keisuke Ito
- Arata Iiyoshi
- Atsuhiro Ishizuna
- Pokémon HeartGold/SoulSilver
- Pokémon Black/White 2
- Pokémon X/Y
- Pokémon Omega Ruby/Alpha Sapphire
- Pokémon Sun/Moon
- Pokémon Ultra Sun/Ultra Moon
- Pokémon Sword/Shield
- Pokémon Scarlet/Violet
- Firel (Custom Metropolis and Laboratory biome music)
- Lmz (Custom Jungle biome music)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -126,6 +126,7 @@ export default class BattleScene extends SceneBase {
public uiTheme: UiTheme = UiTheme.DEFAULT;
public windowType: integer = 0;
public experimentalSprites: boolean = false;
public musicPreference: integer = 0;
public moveAnimations: boolean = true;
public expGainsSpeed: integer = 0;
public skipSeenDialogues: boolean = false;
@ -159,7 +160,7 @@ export default class BattleScene extends SceneBase {
public gameData: GameData;
public sessionSlotId: integer;
private phaseQueue: Phase[];
public phaseQueue: Phase[];
private phaseQueuePrepend: Phase[];
private phaseQueuePrependSpliceIndex: integer;
private nextCommandPhaseQueue: Phase[];
@ -201,7 +202,7 @@ export default class BattleScene extends SceneBase {
public arenaFlyout: ArenaFlyout;
private fieldOverlay: Phaser.GameObjects.Rectangle;
private modifiers: PersistentModifier[];
public modifiers: PersistentModifier[];
private enemyModifiers: PersistentModifier[];
public uiContainer: Phaser.GameObjects.Container;
public ui: UI;
@ -300,7 +301,8 @@ export default class BattleScene extends SceneBase {
this.fieldSpritePipeline = new FieldSpritePipeline(this.game);
(this.renderer as Phaser.Renderer.WebGL.WebGLRenderer).pipelines.add("FieldSprite", this.fieldSpritePipeline);
this.time.delayedCall(20, () => this.launchBattle());
this.launchBattle();
}
update() {
@ -947,7 +949,8 @@ export default class BattleScene extends SceneBase {
}
newBattle(waveIndex?: integer, battleType?: BattleType, trainerData?: TrainerData, double?: boolean): Battle {
const newWaveIndex = waveIndex || ((this.currentBattle?.waveIndex || (startingWave - 1)) + 1);
const _startingWave = Overrides.STARTING_WAVE_OVERRIDE || startingWave;
const newWaveIndex = waveIndex || ((this.currentBattle?.waveIndex || (_startingWave - 1)) + 1);
let newDouble: boolean;
let newBattleType: BattleType;
let newTrainer: Trainer;
@ -1007,6 +1010,9 @@ export default class BattleScene extends SceneBase {
if (Overrides.DOUBLE_BATTLE_OVERRIDE) {
newDouble = true;
}
if (Overrides.SINGLE_BATTLE_OVERRIDE) {
newDouble = false;
}
const lastBattle = this.currentBattle;
@ -1376,8 +1382,7 @@ export default class BattleScene extends SceneBase {
if (this.money === undefined) {
return;
}
const formattedMoney =
this.moneyFormat === MoneyFormat.ABBREVIATED ? Utils.formatFancyLargeNumber(this.money, 3) : this.money.toLocaleString();
const formattedMoney = Utils.formatMoney(this.moneyFormat, this.money);
this.moneyText.setText(`${formattedMoney}`);
this.fieldUI.moveAbove(this.moneyText, this.luckText);
if (forceVisible) {
@ -1694,14 +1699,74 @@ export default class BattleScene extends SceneBase {
return 13.122;
case "battle_unova_gym":
return 19.145;
case "battle_legendary_regis": //B2W2 Legendary Titan Battle
case "battle_legendary_kanto": //XY Kanto Legendary Battle
return 32.966;
case "battle_legendary_raikou": //HGSS Raikou Battle
return 12.632;
case "battle_legendary_entei": //HGSS Entei Battle
return 2.905;
case "battle_legendary_suicune": //HGSS Suicune Battle
return 12.636;
case "battle_legendary_lugia": //HGSS Lugia Battle
return 19.770;
case "battle_legendary_ho_oh": //HGSS Ho-oh Battle
return 17.668;
case "battle_legendary_regis_g5": //B2W2 Legendary Titan Battle
return 49.500;
case "battle_legendary_regis_g6": //ORAS Legendary Titan Battle
return 21.130;
case "battle_legendary_gro_kyo": //ORAS Groudon & Kyogre Battle
return 10.547;
case "battle_legendary_rayquaza": //ORAS Rayquaza Battle
return 10.495;
case "battle_legendary_deoxys": //ORAS Deoxys Battle
return 13.333;
case "battle_legendary_lake_trio": //ORAS Lake Guardians Battle
return 16.887;
case "battle_legendary_sinnoh": //ORAS Sinnoh Legendary Battle
return 22.770;
case "battle_legendary_dia_pal": //ORAS Dialga & Palkia Battle
return 16.009;
case "battle_legendary_giratina": //ORAS Giratina Battle
return 10.451;
case "battle_legendary_arceus": //HGSS Arceus Battle
return 9.595;
case "battle_legendary_unova": //BW Unova Legendary Battle
return 13.855;
case "battle_legendary_kyurem": //BW Kyurem Battle
return 18.314;
case "battle_legendary_res_zek": //BW Reshiram & Zekrom Battle
return 18.329;
case "battle_legendary_xern_yvel": //XY Xerneas & Yveltal Battle
return 26.468;
case "battle_legendary_tapu": //SM Tapu Battle
return 0.000;
case "battle_legendary_sol_lun": //SM Solgaleo & Lunala Battle
return 6.525;
case "battle_legendary_ub": //SM Ultra Beast Battle
return 9.818;
case "battle_legendary_dusk_dawn": //USUM Dusk Mane & Dawn Wings Necrozma Battle
return 5.211;
case "battle_legendary_ultra_nec": //USUM Ultra Necrozma Battle
return 10.344;
case "battle_legendary_zac_zam": //SWSH Zacian & Zamazenta Battle
return 11.424;
case "battle_legendary_glas_spec": //SWSH Glastrier & Spectrier Battle
return 12.503;
case "battle_legendary_calyrex": //SWSH Calyrex Battle
return 50.641;
case "battle_legendary_birds_galar": //SWSH Galarian Legendary Birds Battle
return 0.175;
case "battle_legendary_ruinous": //SV Treasures of Ruin Battle
return 6.333;
case "battle_legendary_loyal_three": //SV Loyal Three Battle
return 6.500;
case "battle_legendary_ogerpon": //SV Ogerpon Battle
return 14.335;
case "battle_legendary_terapagos": //SV Terapagos Battle
return 24.377;
case "battle_legendary_pecharunt": //SV Pecharunt Battle
return 6.508;
case "battle_rival":
return 13.689;
case "battle_rival_2":

View File

@ -209,22 +209,116 @@ export default class Battle {
return "battle_final_encounter";
}
if (pokemon.species.legendary || pokemon.species.subLegendary || pokemon.species.mythical) {
if (pokemon.species.speciesId === Species.REGIROCK || pokemon.species.speciesId === Species.REGICE || pokemon.species.speciesId === Species.REGISTEEL || pokemon.species.speciesId === Species.REGIGIGAS || pokemon.species.speciesId === Species.REGIELEKI || pokemon.species.speciesId === Species.REGIDRAGO) {
return "battle_legendary_regis";
}
if (pokemon.species.speciesId === Species.COBALION || pokemon.species.speciesId === Species.TERRAKION || pokemon.species.speciesId === Species.VIRIZION || pokemon.species.speciesId === Species.TORNADUS || pokemon.species.speciesId === Species.THUNDURUS || pokemon.species.speciesId === Species.LANDORUS || pokemon.species.speciesId === Species.KELDEO || pokemon.species.speciesId === Species.MELOETTA || pokemon.species.speciesId === Species.GENESECT) {
if (scene.musicPreference === 0) {
if (pokemon.species.speciesId === Species.REGIROCK || pokemon.species.speciesId === Species.REGICE || pokemon.species.speciesId === Species.REGISTEEL || pokemon.species.speciesId === Species.REGIGIGAS || pokemon.species.speciesId === Species.REGIELEKI || pokemon.species.speciesId === Species.REGIDRAGO) {
return "battle_legendary_regis_g5";
}
if (pokemon.species.speciesId === Species.COBALION || pokemon.species.speciesId === Species.TERRAKION || pokemon.species.speciesId === Species.VIRIZION || pokemon.species.speciesId === Species.TORNADUS || pokemon.species.speciesId === Species.THUNDURUS || pokemon.species.speciesId === Species.LANDORUS || pokemon.species.speciesId === Species.KELDEO || pokemon.species.speciesId === Species.MELOETTA || pokemon.species.speciesId === Species.GENESECT) {
return "battle_legendary_unova";
}
if (pokemon.species.speciesId === Species.KYUREM) {
return "battle_legendary_kyurem";
}
if (pokemon.species.legendary) {
return "battle_legendary_res_zek";
}
return "battle_legendary_unova";
} else {
if (pokemon.species.speciesId === Species.ARTICUNO || pokemon.species.speciesId === Species.ZAPDOS || pokemon.species.speciesId === Species.MOLTRES || pokemon.species.speciesId === Species.MEWTWO || pokemon.species.speciesId === Species.MEW) {
return "battle_legendary_kanto";
}
if (pokemon.species.speciesId === Species.RAIKOU) {
return "battle_legendary_raikou";
}
if (pokemon.species.speciesId === Species.ENTEI) {
return "battle_legendary_entei";
}
if (pokemon.species.speciesId === Species.SUICUNE) {
return "battle_legendary_suicune";
}
if (pokemon.species.speciesId === Species.LUGIA) {
return "battle_legendary_lugia";
}
if (pokemon.species.speciesId === Species.HO_OH) {
return "battle_legendary_ho_oh";
}
if (pokemon.species.speciesId === Species.REGIROCK || pokemon.species.speciesId === Species.REGICE || pokemon.species.speciesId === Species.REGISTEEL || pokemon.species.speciesId === Species.REGIGIGAS || pokemon.species.speciesId === Species.REGIELEKI || pokemon.species.speciesId === Species.REGIDRAGO) {
return "battle_legendary_regis_g6";
}
if (pokemon.species.speciesId === Species.GROUDON || pokemon.species.speciesId === Species.KYOGRE) {
return "battle_legendary_gro_kyo";
}
if (pokemon.species.speciesId === Species.RAYQUAZA) {
return "battle_legendary_rayquaza";
}
if (pokemon.species.speciesId === Species.DEOXYS) {
return "battle_legendary_deoxys";
}
if (pokemon.species.speciesId === Species.UXIE || pokemon.species.speciesId === Species.MESPRIT || pokemon.species.speciesId === Species.AZELF) {
return "battle_legendary_lake_trio";
}
if (pokemon.species.speciesId === Species.HEATRAN || pokemon.species.speciesId === Species.CRESSELIA || pokemon.species.speciesId === Species.DARKRAI || pokemon.species.speciesId === Species.SHAYMIN) {
return "battle_legendary_sinnoh";
}
if (pokemon.species.speciesId === Species.DIALGA || pokemon.species.speciesId === Species.PALKIA) {
return "battle_legendary_dia_pal";
}
if (pokemon.species.speciesId === Species.GIRATINA) {
return "battle_legendary_giratina";
}
if (pokemon.species.speciesId === Species.ARCEUS) {
return "battle_legendary_arceus";
}
if (pokemon.species.speciesId === Species.COBALION || pokemon.species.speciesId === Species.TERRAKION || pokemon.species.speciesId === Species.VIRIZION || pokemon.species.speciesId === Species.TORNADUS || pokemon.species.speciesId === Species.THUNDURUS || pokemon.species.speciesId === Species.LANDORUS || pokemon.species.speciesId === Species.KELDEO || pokemon.species.speciesId === Species.MELOETTA || pokemon.species.speciesId === Species.GENESECT) {
return "battle_legendary_unova";
}
if (pokemon.species.speciesId === Species.KYUREM) {
return "battle_legendary_kyurem";
}
if (pokemon.species.speciesId === Species.XERNEAS || pokemon.species.speciesId === Species.YVELTAL || pokemon.species.speciesId === Species.ZYGARDE) {
return "battle_legendary_xern_yvel";
}
if (pokemon.species.speciesId === Species.TAPU_KOKO || pokemon.species.speciesId === Species.TAPU_LELE || pokemon.species.speciesId === Species.TAPU_BULU || pokemon.species.speciesId === Species.TAPU_FINI) {
return "battle_legendary_tapu";
}
if (pokemon.species.speciesId === Species.COSMOG || pokemon.species.speciesId === Species.COSMOEM || pokemon.species.speciesId === Species.SOLGALEO || pokemon.species.speciesId === Species.LUNALA || pokemon.species.speciesId === Species.NECROZMA) {
return "battle_legendary_sol_lun";
}
if (pokemon.species.speciesId === Species.NIHILEGO || pokemon.species.speciesId === Species.BUZZWOLE || pokemon.species.speciesId === Species.PHEROMOSA || pokemon.species.speciesId === Species.XURKITREE || pokemon.species.speciesId === Species.CELESTEELA || pokemon.species.speciesId === Species.KARTANA || pokemon.species.speciesId === Species.GUZZLORD || pokemon.species.speciesId === Species.POIPOLE || pokemon.species.speciesId === Species.NAGANADEL || pokemon.species.speciesId === Species.STAKATAKA || pokemon.species.speciesId === Species.BLACEPHALON) {
return "battle_legendary_ub";
}
if (pokemon.species.speciesId === Species.ZACIAN || pokemon.species.speciesId === Species.ZAMAZENTA) {
return "battle_legendary_zac_zam";
}
if (pokemon.species.speciesId === Species.GLASTRIER || pokemon.species.speciesId === Species.SPECTRIER) {
return "battle_legendary_glas_spec";
}
if (pokemon.species.speciesId === Species.CALYREX) {
return "battle_legendary_calyrex";
}
if (pokemon.species.speciesId === Species.GALAR_ARTICUNO || pokemon.species.speciesId === Species.GALAR_ZAPDOS || pokemon.species.speciesId === Species.GALAR_MOLTRES) {
return "battle_legendary_birds_galar";
}
if (pokemon.species.speciesId === Species.WO_CHIEN || pokemon.species.speciesId === Species.CHIEN_PAO || pokemon.species.speciesId === Species.TING_LU || pokemon.species.speciesId === Species.CHI_YU) {
return "battle_legendary_ruinous";
}
if (pokemon.species.speciesId === Species.OKIDOGI || pokemon.species.speciesId === Species.MUNKIDORI || pokemon.species.speciesId === Species.FEZANDIPITI) {
return "battle_legendary_loyal_three";
}
if (pokemon.species.speciesId === Species.OGERPON) {
return "battle_legendary_ogerpon";
}
if (pokemon.species.speciesId === Species.TERAPAGOS) {
return "battle_legendary_terapagos";
}
if (pokemon.species.speciesId === Species.PECHARUNT) {
return "battle_legendary_pecharunt";
}
if (pokemon.species.legendary) {
return "battle_legendary_res_zek";
}
return "battle_legendary_unova";
}
if (pokemon.species.speciesId === Species.RESHIRAM || pokemon.species.speciesId === Species.ZEKROM) {
return "battle_legendary_res_zek";
}
if (pokemon.species.speciesId === Species.KYUREM) {
return "battle_legendary_kyurem";
}
if (pokemon.species.legendary) {
return "battle_legendary_res_zek";
}
return "battle_legendary_unova";
}
}

View File

@ -235,10 +235,10 @@ export class PostBattleInitStatChangeAbAttr extends PostBattleInitAbAttr {
}
}
type PreDefendAbAttrCondition = (pokemon: Pokemon, attacker: Pokemon, move: PokemonMove) => boolean;
type PreDefendAbAttrCondition = (pokemon: Pokemon, attacker: Pokemon, move: Move) => boolean;
export class PreDefendAbAttr extends AbAttr {
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise<boolean> {
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise<boolean> {
return false;
}
}
@ -252,7 +252,7 @@ export class PreDefendFormChangeAbAttr extends PreDefendAbAttr {
this.formFunc = formFunc;
}
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean {
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
const formIndex = this.formFunc(pokemon);
if (formIndex !== pokemon.formIndex) {
pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false);
@ -263,7 +263,7 @@ export class PreDefendFormChangeAbAttr extends PreDefendAbAttr {
}
}
export class PreDefendFullHpEndureAbAttr extends PreDefendAbAttr {
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean {
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (pokemon.hp === pokemon.getMaxHp() &&
pokemon.getMaxHp() > 1 && //Checks if pokemon has wonder_guard (which forces 1hp)
(args[0] as Utils.NumberHolder).value >= pokemon.hp) { //Damage >= hp
@ -308,8 +308,8 @@ export class ReceivedMoveDamageMultiplierAbAttr extends PreDefendAbAttr {
this.powerMultiplier = powerMultiplier;
}
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (this.condition(pokemon, attacker, move.getMove())) {
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (this.condition(pokemon, attacker, move)) {
(args[0] as Utils.NumberHolder).value *= this.powerMultiplier;
return true;
}
@ -329,8 +329,8 @@ export class PreDefendMovePowerToOneAbAttr extends ReceivedMoveDamageMultiplierA
super(condition, 1);
}
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (this.condition(pokemon, attacker, move.getMove())) {
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (this.condition(pokemon, attacker, move)) {
(args[0] as Utils.NumberHolder).value = 1;
return true;
}
@ -339,6 +339,12 @@ export class PreDefendMovePowerToOneAbAttr extends ReceivedMoveDamageMultiplierA
}
}
/**
* Determines whether a Pokemon is immune to a move because of an ability.
* @extends PreDefendAbAttr
* @see {@linkcode applyPreDefend}
* @see {@linkcode getCondition}
*/
export class TypeImmunityAbAttr extends PreDefendAbAttr {
private immuneType: Type;
private condition: AbAttrCondition;
@ -350,8 +356,17 @@ export class TypeImmunityAbAttr extends PreDefendAbAttr {
this.condition = condition;
}
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if ((move.getMove() instanceof AttackMove || move.getMove().getAttrs(StatusMoveTypeImmunityAttr).find(attr => attr.immuneType === this.immuneType)) && move.getMove().type === this.immuneType) {
/**
* @param pokemon {@linkcode Pokemon} the defending Pokemon
* @param passive N/A
* @param attacker {@linkcode Pokemon} the attacking Pokemon
* @param move {@linkcode Move} the attacking move
* @param cancelled N/A
* @param args [0] {@linkcode Utils.NumberHolder} gets set to 0 if move is immuned by an ability.
* @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) {
(args[0] as Utils.NumberHolder).value = 0;
return true;
}
@ -369,7 +384,7 @@ export class TypeImmunityHealAbAttr extends TypeImmunityAbAttr {
super(immuneType);
}
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean {
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
const ret = super.applyPreDefend(pokemon, passive, attacker, move, cancelled, args);
if (ret) {
@ -399,7 +414,7 @@ class TypeImmunityStatChangeAbAttr extends TypeImmunityAbAttr {
this.levels = levels;
}
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean {
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
const ret = super.applyPreDefend(pokemon, passive, attacker, move, cancelled, args);
if (ret) {
@ -425,7 +440,7 @@ class TypeImmunityAddBattlerTagAbAttr extends TypeImmunityAbAttr {
this.turnCount = turnCount;
}
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean {
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
const ret = super.applyPreDefend(pokemon, passive, attacker, move, cancelled, args);
if (ret) {
@ -445,8 +460,8 @@ export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr {
super(null, condition);
}
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (move.getMove() instanceof AttackMove && pokemon.getAttackTypeEffectiveness(move.getMove().type, attacker) < 2) {
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (move instanceof AttackMove && pokemon.getAttackTypeEffectiveness(move.type, attacker) < 2) {
cancelled.value = true;
(args[0] as Utils.NumberHolder).value = 0;
return true;
@ -461,15 +476,15 @@ export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr {
}
export class PostDefendAbAttr extends AbAttr {
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean | Promise<boolean> {
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean | Promise<boolean> {
return false;
}
}
export class PostDefendDisguiseAbAttr extends PostDefendAbAttr {
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
if (pokemon.formIndex === 0 && pokemon.battleData.hitCount !== 0 && (move.getMove().category === MoveCategory.SPECIAL || move.getMove().category === MoveCategory.PHYSICAL)) {
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (pokemon.formIndex === 0 && pokemon.battleData.hitCount !== 0 && (move.category === MoveCategory.SPECIAL || move.category === MoveCategory.PHYSICAL)) {
const recoilDamage = Math.ceil((pokemon.getMaxHp() / 8) - attacker.turnData.damageDealt);
if (!recoilDamage) {
@ -494,7 +509,7 @@ export class PostDefendFormChangeAbAttr extends PostDefendAbAttr {
this.formFunc = formFunc;
}
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
const formIndex = this.formFunc(pokemon);
if (formIndex !== pokemon.formIndex) {
pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false);
@ -506,16 +521,16 @@ export class PostDefendFormChangeAbAttr extends PostDefendAbAttr {
}
export class FieldPriorityMoveImmunityAbAttr extends PreDefendAbAttr {
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean {
const attackPriority = new Utils.IntegerHolder(move.getMove().priority);
applyMoveAttrs(IncrementMovePriorityAttr,attacker,null,move.getMove(),attackPriority);
applyAbAttrs(IncrementMovePriorityAbAttr, attacker, null, move.getMove(), attackPriority);
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
const attackPriority = new Utils.IntegerHolder(move.priority);
applyMoveAttrs(IncrementMovePriorityAttr,attacker,null,move,attackPriority);
applyAbAttrs(IncrementMovePriorityAbAttr, attacker, null, move, attackPriority);
if (move.getMove().moveTarget===MoveTarget.USER || move.getMove().moveTarget===MoveTarget.NEAR_ALLY) {
if (move.moveTarget===MoveTarget.USER || move.moveTarget===MoveTarget.NEAR_ALLY) {
return false;
}
if (attackPriority.value > 0 && !move.getMove().isMultiTarget()) {
if (attackPriority.value > 0 && !move.isMultiTarget()) {
cancelled.value = true;
return true;
}
@ -539,7 +554,7 @@ export class MoveImmunityAbAttr extends PreDefendAbAttr {
this.immuneCondition = immuneCondition;
}
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean {
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (this.immuneCondition(pokemon, attacker, move)) {
cancelled.value = true;
return true;
@ -563,7 +578,7 @@ export class MoveImmunityStatChangeAbAttr extends MoveImmunityAbAttr {
this.levels = levels;
}
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean {
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
const ret = super.applyPreDefend(pokemon, passive, attacker, move, cancelled, args);
if (ret) {
const simulated = args.length > 1 && args[1];
@ -593,8 +608,8 @@ export class ReverseDrainAbAttr extends PostDefendAbAttr {
* @args N/A
* @returns true if healing should be reversed on a healing move, false otherwise.
*/
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
if (move.getMove().hasAttr(HitHealAttr)) {
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (move.hasAttr(HitHealAttr)) {
pokemon.scene.queueMessage(getPokemonMessage(attacker, " sucked up the liquid ooze!"));
return true;
}
@ -619,8 +634,8 @@ export class PostDefendStatChangeAbAttr extends PostDefendAbAttr {
this.allOthers = allOthers;
}
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
if (this.condition(pokemon, attacker, move.getMove())) {
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (this.condition(pokemon, attacker, move)) {
if (this.allOthers) {
const otherPokemon = pokemon.getAlly() ? pokemon.getOpponents().concat([ pokemon.getAlly() ]) : pokemon.getOpponents();
for (const other of otherPokemon) {
@ -653,10 +668,10 @@ export class PostDefendHpGatedStatChangeAbAttr extends PostDefendAbAttr {
this.selfTarget = selfTarget;
}
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
const hpGateFlat: integer = Math.ceil(pokemon.getMaxHp() * this.hpGate);
const lastAttackReceived = pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1];
if (this.condition(pokemon, attacker, move.getMove()) && (pokemon.hp <= hpGateFlat && (pokemon.hp + lastAttackReceived.damage) > hpGateFlat)) {
if (this.condition(pokemon, attacker, move) && (pokemon.hp <= hpGateFlat && (pokemon.hp + lastAttackReceived.damage) > hpGateFlat)) {
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, (this.selfTarget ? pokemon : attacker).getBattlerIndex(), true, this.stats, this.levels));
return true;
}
@ -676,8 +691,8 @@ export class PostDefendApplyArenaTrapTagAbAttr extends PostDefendAbAttr {
this.tagType = tagType;
}
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
if (this.condition(pokemon, attacker, move.getMove())) {
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (this.condition(pokemon, attacker, move)) {
const tag = pokemon.scene.arena.getTag(this.tagType) as ArenaTrapTag;
if (!pokemon.scene.arena.getTag(this.tagType) || tag.layers < tag.maxLayers) {
pokemon.scene.arena.addTag(this.tagType, 0, undefined, pokemon.id, pokemon.isPlayer() ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER);
@ -698,11 +713,11 @@ export class PostDefendApplyBattlerTagAbAttr extends PostDefendAbAttr {
this.tagType = tagType;
}
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
if (this.condition(pokemon, attacker, move.getMove())) {
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (this.condition(pokemon, attacker, move)) {
if (!pokemon.getTag(this.tagType)) {
pokemon.addTag(this.tagType, undefined, undefined, pokemon.id);
pokemon.scene.queueMessage(i18next.t("abilityTriggers:windPowerCharged", { pokemonName: pokemon.name, moveName: move.getName() }));
pokemon.scene.queueMessage(i18next.t("abilityTriggers:windPowerCharged", { pokemonName: pokemon.name, moveName: move.name }));
}
return true;
}
@ -711,9 +726,9 @@ export class PostDefendApplyBattlerTagAbAttr extends PostDefendAbAttr {
}
export class PostDefendTypeChangeAbAttr extends PostDefendAbAttr {
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (hitResult < HitResult.NO_EFFECT) {
const type = move.getMove().type;
const type = move.type;
const pokemonTypes = pokemon.getTypes(true);
if (pokemonTypes.length !== 1 || pokemonTypes[0] !== type) {
pokemon.summonData.types = [ type ];
@ -738,7 +753,7 @@ export class PostDefendTerrainChangeAbAttr extends PostDefendAbAttr {
this.terrainType = terrainType;
}
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (hitResult < HitResult.NO_EFFECT) {
return pokemon.scene.arena.trySetTerrain(this.terrainType, true);
}
@ -758,8 +773,8 @@ export class PostDefendContactApplyStatusEffectAbAttr extends PostDefendAbAttr {
this.effects = effects;
}
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
if (move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.status && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance)) {
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.status && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance)) {
const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)];
return attacker.trySetStatus(effect, true, pokemon);
}
@ -773,7 +788,7 @@ export class EffectSporeAbAttr extends PostDefendContactApplyStatusEffectAbAttr
super(10, StatusEffect.POISON, StatusEffect.PARALYSIS, StatusEffect.SLEEP);
}
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (attacker.hasAbility(Abilities.OVERCOAT) || attacker.isOfType(Type.GRASS)) {
return false;
}
@ -794,9 +809,9 @@ export class PostDefendContactApplyTagChanceAbAttr extends PostDefendAbAttr {
this.turnCount = turnCount;
}
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
if (move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && pokemon.randSeedInt(100) < this.chance) {
return attacker.addTag(this.tagType, this.turnCount, move.moveId, attacker.id);
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && pokemon.randSeedInt(100) < this.chance) {
return attacker.addTag(this.tagType, this.turnCount, move.id, attacker.id);
}
return false;
@ -814,7 +829,7 @@ export class PostDefendCritStatChangeAbAttr extends PostDefendAbAttr {
this.levels = levels;
}
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ this.stat ], this.levels));
return true;
@ -834,8 +849,8 @@ export class PostDefendContactDamageAbAttr extends PostDefendAbAttr {
this.damageRatio = damageRatio;
}
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
if (move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) {
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) {
attacker.damageAndUpdate(Math.ceil(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER);
attacker.turnData.damageTaken += Math.ceil(attacker.getMaxHp() * (1 / this.damageRatio));
return true;
@ -864,8 +879,8 @@ export class PostDefendPerishSongAbAttr extends PostDefendAbAttr {
this.turns = turns;
}
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
if (move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) {
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) {
if (pokemon.getTag(BattlerTagType.PERISH_SONG) || attacker.getTag(BattlerTagType.PERISH_SONG)) {
return false;
} else {
@ -891,7 +906,7 @@ export class PostDefendWeatherChangeAbAttr extends PostDefendAbAttr {
this.weatherType = weatherType;
}
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (!pokemon.scene.arena.weather?.isImmutable()) {
return pokemon.scene.arena.trySetWeather(this.weatherType, true);
}
@ -905,8 +920,8 @@ export class PostDefendAbilitySwapAbAttr extends PostDefendAbAttr {
super();
}
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
if (move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnswappableAbilityAbAttr)) {
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnswappableAbilityAbAttr)) {
const tempAbilityId = attacker.getAbility().id;
attacker.summonData.ability = pokemon.getAbility().id;
pokemon.summonData.ability = tempAbilityId;
@ -929,8 +944,8 @@ export class PostDefendAbilityGiveAbAttr extends PostDefendAbAttr {
this.ability = ability;
}
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
if (move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnsuppressableAbilityAbAttr) && !attacker.getAbility().hasAttr(PostDefendAbilityGiveAbAttr)) {
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnsuppressableAbilityAbAttr) && !attacker.getAbility().hasAttr(PostDefendAbilityGiveAbAttr)) {
attacker.summonData.ability = this.ability;
return true;
@ -947,7 +962,7 @@ export class PostDefendAbilityGiveAbAttr extends PostDefendAbAttr {
export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr {
private chance: integer;
private attacker: Pokemon;
private move: PokemonMove;
private move: Move;
constructor(chance: integer) {
super();
@ -955,13 +970,13 @@ export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr {
this.chance = chance;
}
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (!attacker.summonData.disabledMove) {
if (move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance) && !attacker.isMax()) {
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance) && !attacker.isMax()) {
this.attacker = attacker;
this.move = move;
attacker.summonData.disabledMove = move.moveId;
attacker.summonData.disabledMove = move.id;
attacker.summonData.disabledTurns = 4;
return true;
}
@ -970,7 +985,7 @@ export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr {
}
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
return getPokemonMessage(this.attacker, `'s ${this.move.getName()}\nwas disabled!`);
return getPokemonMessage(this.attacker, `'s ${this.move.name}\nwas disabled!`);
}
}
@ -998,49 +1013,18 @@ export class PostStatChangeStatChangeAbAttr extends PostStatChangeAbAttr {
}
export class PreAttackAbAttr extends AbAttr {
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, args: any[]): boolean | Promise<boolean> {
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean | Promise<boolean> {
return false;
}
}
export class VariableMovePowerAbAttr extends PreAttackAbAttr {
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, args: any[]): boolean {
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean {
//const power = args[0] as Utils.NumberHolder;
return false;
}
}
export class VariableMoveTypeAbAttr extends AbAttr {
apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
//const power = args[0] as Utils.IntegerHolder;
return false;
}
}
export class MoveTypeChangePowerMultiplierAbAttr extends VariableMoveTypeAbAttr {
private matchType: Type;
private newType: Type;
private powerMultiplier: number;
constructor(matchType: Type, newType: Type, powerMultiplier: number) {
super(true);
this.matchType = matchType;
this.newType = newType;
this.powerMultiplier = powerMultiplier;
}
apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
const type = (args[0] as Utils.IntegerHolder);
if (type.value === this.matchType) {
type.value = this.newType;
(args[1] as Utils.NumberHolder).value *= this.powerMultiplier;
return true;
}
return false;
}
}
export class FieldPreventExplosiveMovesAbAttr extends AbAttr {
apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise<boolean> {
cancelled.value = true;
@ -1060,11 +1044,10 @@ export class MoveTypeChangeAttr extends PreAttackAbAttr {
this.condition = condition;
}
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, args: any[]): boolean {
if (this.condition(pokemon, defender, move.getMove())) {
const type = (args[0] as Utils.IntegerHolder);
type.value = this.newType;
(args[1] as Utils.NumberHolder).value *= this.powerMultiplier;
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean {
if (this.condition(pokemon, defender, move)) {
move.type = this.newType;
(args[0] as Utils.NumberHolder).value *= this.powerMultiplier;
return true;
}
@ -1097,8 +1080,8 @@ export class DamageBoostAbAttr extends PreAttackAbAttr {
* @param args Utils.NumberHolder as damage
* @returns true if the function succeeds
*/
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, args: any[]): boolean {
if (this.condition(pokemon, defender, move.getMove())) {
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean {
if (this.condition(pokemon, defender, move)) {
const power = args[0] as Utils.NumberHolder;
power.value = Math.floor(power.value * this.damageMultiplier);
return true;
@ -1118,8 +1101,8 @@ export class MovePowerBoostAbAttr extends VariableMovePowerAbAttr {
this.powerMultiplier = powerMultiplier;
}
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, args: any[]): boolean {
if (this.condition(pokemon, defender, move.getMove())) {
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean {
if (this.condition(pokemon, defender, move)) {
(args[0] as Utils.NumberHolder).value *= this.powerMultiplier;
return true;
@ -1165,8 +1148,8 @@ export class VariableMovePowerBoostAbAttr extends VariableMovePowerAbAttr {
/**
* @override
*/
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, args: any[]): boolean {
const multiplier = this.mult(pokemon, defender, move.getMove());
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move, args: any[]): boolean {
const multiplier = this.mult(pokemon, defender, move);
if (multiplier !== 1) {
(args[0] as Utils.NumberHolder).value *= multiplier;
return true;
@ -1177,7 +1160,7 @@ export class VariableMovePowerBoostAbAttr extends VariableMovePowerAbAttr {
}
export class FieldVariableMovePowerAbAttr extends AbAttr {
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, args: any[]): boolean {
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean {
//const power = args[0] as Utils.NumberHolder;
return false;
}
@ -1193,8 +1176,8 @@ export class FieldMovePowerBoostAbAttr extends FieldVariableMovePowerAbAttr {
this.powerMultiplier = powerMultiplier;
}
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, args: any[]): boolean {
if (this.condition(pokemon, defender, move.getMove())) {
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean {
if (this.condition(pokemon, defender, move)) {
(args[0] as Utils.NumberHolder).value *= this.powerMultiplier;
return true;
@ -1235,7 +1218,7 @@ export class BattleStatMultiplierAbAttr extends AbAttr {
}
export class PostAttackAbAttr extends AbAttr {
applyPostAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean | Promise<boolean> {
applyPostAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean | Promise<boolean> {
return false;
}
}
@ -1249,9 +1232,9 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr {
this.condition = condition;
}
applyPostAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): Promise<boolean> {
applyPostAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): Promise<boolean> {
return new Promise<boolean>(resolve => {
if (hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, defender, move.getMove()))) {
if (hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, defender, move))) {
const heldItems = this.getTargetHeldItems(defender).filter(i => i.getTransferrable(false));
if (heldItems.length) {
const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)];
@ -1287,8 +1270,8 @@ export class PostAttackApplyStatusEffectAbAttr extends PostAttackAbAttr {
this.effects = effects;
}
applyPostAttack(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
if (pokemon !== attacker && (!this.contactRequired || move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) && pokemon.randSeedInt(100) < this.chance && !pokemon.status) {
applyPostAttack(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (pokemon !== attacker && (!this.contactRequired || move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) && pokemon.randSeedInt(100) < this.chance && !pokemon.status) {
const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)];
return attacker.trySetStatus(effect, true, pokemon);
}
@ -1305,11 +1288,11 @@ export class PostAttackContactApplyStatusEffectAbAttr extends PostAttackApplySta
export class PostAttackApplyBattlerTagAbAttr extends PostAttackAbAttr {
private contactRequired: boolean;
private chance: (user: Pokemon, target: Pokemon, move: PokemonMove) => integer;
private chance: (user: Pokemon, target: Pokemon, move: Move) => integer;
private effects: BattlerTagType[];
constructor(contactRequired: boolean, chance: (user: Pokemon, target: Pokemon, move: PokemonMove) => integer, ...effects: BattlerTagType[]) {
constructor(contactRequired: boolean, chance: (user: Pokemon, target: Pokemon, move: Move) => integer, ...effects: BattlerTagType[]) {
super();
this.contactRequired = contactRequired;
@ -1317,8 +1300,8 @@ export class PostAttackApplyBattlerTagAbAttr extends PostAttackAbAttr {
this.effects = effects;
}
applyPostAttack(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
if (pokemon !== attacker && (!this.contactRequired || move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) && pokemon.randSeedInt(100) < this.chance(attacker, pokemon, move) && !pokemon.status) {
applyPostAttack(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (pokemon !== attacker && (!this.contactRequired || move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) && pokemon.randSeedInt(100) < this.chance(attacker, pokemon, move) && !pokemon.status) {
const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)];
@ -1338,9 +1321,9 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr {
this.condition = condition;
}
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): Promise<boolean> {
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): Promise<boolean> {
return new Promise<boolean>(resolve => {
if (hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, attacker, move.getMove()))) {
if (hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, attacker, move))) {
const heldItems = this.getTargetHeldItems(attacker).filter(i => i.getTransferrable(false));
if (heldItems.length) {
const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)];
@ -2002,9 +1985,9 @@ export class ConfusionOnStatusEffectAbAttr extends PostAttackAbAttr {
* @param args [0] {@linkcode StatusEffect} applied by move
* @returns true if defender is confused
*/
applyPostAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
applyPostAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (this.effects.indexOf(args[0]) > -1 && !defender.isFainted()) {
return defender.addTag(BattlerTagType.CONFUSED, pokemon.randSeedInt(3,2), move.moveId, defender.id);
return defender.addTag(BattlerTagType.CONFUSED, pokemon.randSeedInt(3,2), move.id, defender.id);
}
return false;
}
@ -2607,7 +2590,7 @@ export class PostTurnLootAbAttr extends PostTurnAbAttr {
if (!berryModifier) {
pokemon.scene.addModifier(new BerryModifier(chosenBerry, pokemon.id, chosenBerryType, 1));
} else {
} else if (berryModifier.stackCount < berryModifier.getMaxHeldItemCount(pokemon)) {
berryModifier.stackCount++;
}
@ -3028,7 +3011,7 @@ export class PostBattleLootAbAttr extends PostBattleAbAttr {
}
export class PostFaintAbAttr extends AbAttr {
applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
return false;
}
}
@ -3047,7 +3030,7 @@ export class PostFaintClearWeatherAbAttr extends PostFaintAbAttr {
* @param args N/A
* @returns {boolean} Returns true if the weather clears, otherwise false.
*/
applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
const weatherType = pokemon.scene.arena.weather.weatherType;
let turnOffWeather = false;
@ -3091,8 +3074,8 @@ export class PostFaintContactDamageAbAttr extends PostFaintAbAttr {
this.damageRatio = damageRatio;
}
applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
if (move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) {
applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) {
const cancelled = new Utils.BooleanHolder(false);
pokemon.scene.getField(true).map(p=>applyAbAttrs(FieldPreventExplosiveMovesAbAttr, p, cancelled));
if (cancelled.value) {
@ -3119,7 +3102,7 @@ export class PostFaintHPDamageAbAttr extends PostFaintAbAttr {
super ();
}
applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
const damage = pokemon.turnData.attacksReceived[0].damage;
attacker.damageAndUpdate((damage), HitResult.OTHER);
attacker.turnData.damageTaken += damage;
@ -3456,7 +3439,7 @@ export class IceFaceMoveImmunityAbAttr extends MoveImmunityAbAttr {
* @param {any[]} args - Additional arguments.
* @returns {boolean} - Whether the immunity was applied.
*/
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean {
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
const isImmune = super.applyPreDefend(pokemon, passive, attacker, move, cancelled, args);
if (isImmune) {
@ -3566,13 +3549,13 @@ export function applyPostBattleInitAbAttrs(attrType: { new(...args: any[]): Post
}
export function applyPreDefendAbAttrs(attrType: { new(...args: any[]): PreDefendAbAttr },
pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, ...args: any[]): Promise<void> {
pokemon: Pokemon, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, ...args: any[]): Promise<void> {
const simulated = args.length > 1 && args[1];
return applyAbAttrsInternal<PreDefendAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreDefend(pokemon, passive, attacker, move, cancelled, args), args, false, false, simulated);
}
export function applyPostDefendAbAttrs(attrType: { new(...args: any[]): PostDefendAbAttr },
pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, ...args: any[]): Promise<void> {
pokemon: Pokemon, attacker: Pokemon, move: Move, hitResult: HitResult, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PostDefendAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostDefend(pokemon, passive, attacker, move, hitResult, args), args);
}
@ -3587,12 +3570,12 @@ export function applyBattleStatMultiplierAbAttrs(attrType: { new(...args: any[])
}
export function applyPreAttackAbAttrs(attrType: { new(...args: any[]): PreAttackAbAttr },
pokemon: Pokemon, defender: Pokemon, move: PokemonMove, ...args: any[]): Promise<void> {
pokemon: Pokemon, defender: Pokemon, move: Move, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PreAttackAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreAttack(pokemon, passive, defender, move, args), args);
}
export function applyPostAttackAbAttrs(attrType: { new(...args: any[]): PostAttackAbAttr },
pokemon: Pokemon, defender: Pokemon, move: PokemonMove, hitResult: HitResult, ...args: any[]): Promise<void> {
pokemon: Pokemon, defender: Pokemon, move: Move, hitResult: HitResult, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PostAttackAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostAttack(pokemon, passive, defender, move, hitResult, args), args);
}
@ -3673,7 +3656,7 @@ export function applyPostBattleAbAttrs(attrType: { new(...args: any[]): PostBatt
}
export function applyPostFaintAbAttrs(attrType: { new(...args: any[]): PostFaintAbAttr },
pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, ...args: any[]): Promise<void> {
pokemon: Pokemon, attacker: Pokemon, move: Move, hitResult: HitResult, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PostFaintAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostFaint(pokemon, passive, attacker, move, hitResult, args), args);
}
@ -3692,7 +3675,7 @@ export const allAbilities = [ new Ability(Abilities.NONE, 3) ];
export function initAbilities() {
allAbilities.push(
new Ability(Abilities.STENCH, 3)
.attr(PostAttackApplyBattlerTagAbAttr, false, (user, target, move) => (move.getMove().category !== MoveCategory.STATUS && !move.getMove().hasAttr(FlinchAttr)) ? 10 : 0, BattlerTagType.FLINCHED),
.attr(PostAttackApplyBattlerTagAbAttr, false, (user, target, move) => (move.category !== MoveCategory.STATUS && !move.hasAttr(FlinchAttr)) ? 10 : 0, BattlerTagType.FLINCHED),
new Ability(Abilities.DRIZZLE, 3)
.attr(PostSummonWeatherChangeAbAttr, WeatherType.RAIN)
.attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.RAIN),
@ -3829,7 +3812,7 @@ export function initAbilities() {
return false;
}),
new Ability(Abilities.SOUNDPROOF, 3)
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.getMove().hasFlag(MoveFlags.SOUND_BASED))
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.hasFlag(MoveFlags.SOUND_BASED))
.ignorable(),
new Ability(Abilities.RAIN_DISH, 3)
.attr(PostWeatherLapseHealAbAttr, 1, WeatherType.RAIN, WeatherType.HEAVY_RAIN)
@ -4115,13 +4098,13 @@ export function initAbilities() {
)
.partial(),
new Ability(Abilities.TELEPATHY, 5)
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon.getAlly() === attacker && move.getMove() instanceof AttackMove)
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon.getAlly() === attacker && move instanceof AttackMove)
.ignorable(),
new Ability(Abilities.MOODY, 5)
.attr(MoodyAbAttr),
new Ability(Abilities.OVERCOAT, 5)
.attr(BlockWeatherDamageAttr)
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.getMove().hasFlag(MoveFlags.POWDER_MOVE))
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.hasFlag(MoveFlags.POWDER_MOVE))
.ignorable(),
new Ability(Abilities.POISON_TOUCH, 5)
.attr(PostAttackContactApplyStatusEffectAbAttr, 30, StatusEffect.POISON),
@ -4210,14 +4193,14 @@ export function initAbilities() {
new Ability(Abilities.MAGICIAN, 6)
.attr(PostAttackStealHeldItemAbAttr),
new Ability(Abilities.BULLETPROOF, 6)
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.getMove().hasFlag(MoveFlags.BALLBOMB_MOVE))
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.hasFlag(MoveFlags.BALLBOMB_MOVE))
.ignorable(),
new Ability(Abilities.COMPETITIVE, 6)
.attr(PostStatChangeStatChangeAbAttr, (target, statsChanged, levels) => levels < 0, [BattleStat.SPATK], 2),
new Ability(Abilities.STRONG_JAW, 6)
.attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.BITING_MOVE), 1.5),
new Ability(Abilities.REFRIGERATE, 6)
.attr(MoveTypeChangePowerMultiplierAbAttr, Type.NORMAL, Type.ICE, 1.2),
.attr(MoveTypeChangeAttr, Type.ICE, 1.2, (user, target, move) => move.type === Type.NORMAL),
new Ability(Abilities.SWEET_VEIL, 6)
.attr(StatusEffectImmunityAbAttr, StatusEffect.SLEEP)
.attr(BattlerTagImmunityAbAttr, BattlerTagType.DROWSY)
@ -4240,11 +4223,11 @@ export function initAbilities() {
new Ability(Abilities.TOUGH_CLAWS, 6)
.attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), 1.3),
new Ability(Abilities.PIXILATE, 6)
.attr(MoveTypeChangePowerMultiplierAbAttr, Type.NORMAL, Type.FAIRY, 1.2),
.attr(MoveTypeChangeAttr, Type.FAIRY, 1.2, (user, target, move) => move.type === Type.NORMAL),
new Ability(Abilities.GOOEY, 6)
.attr(PostDefendStatChangeAbAttr, (target, user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), BattleStat.SPD, -1, false),
new Ability(Abilities.AERILATE, 6)
.attr(MoveTypeChangePowerMultiplierAbAttr, Type.NORMAL, Type.FLYING, 1.2),
.attr(MoveTypeChangeAttr, Type.FLYING, 1.2, (user, target, move) => move.type === Type.NORMAL),
new Ability(Abilities.PARENTAL_BOND, 6)
.unimplemented(),
new Ability(Abilities.DARK_AURA, 6)
@ -4314,7 +4297,7 @@ export function initAbilities() {
new Ability(Abilities.TRIAGE, 7)
.attr(IncrementMovePriorityAbAttr, (pokemon, move) => move.hasFlag(MoveFlags.TRIAGE_MOVE), 3),
new Ability(Abilities.GALVANIZE, 7)
.attr(MoveTypeChangePowerMultiplierAbAttr, Type.NORMAL, Type.ELECTRIC, 1.2),
.attr(MoveTypeChangeAttr, Type.ELECTRIC, 1.2, (user, target, move) => move.type === Type.NORMAL),
new Ability(Abilities.SURGE_SURFER, 7)
.conditionalAttr(getTerrainCondition(TerrainType.ELECTRIC), BattleStatMultiplierAbAttr, BattleStat.SPD, 2),
new Ability(Abilities.SCHOOLING, 7)
@ -4482,7 +4465,7 @@ export function initAbilities() {
.conditionalAttr(getWeatherCondition(WeatherType.HAIL, WeatherType.SNOW), PostSummonAddBattlerTagAbAttr, BattlerTagType.ICE_FACE, 0)
// When weather changes to HAIL or SNOW while pokemon is fielded, add BattlerTagType.ICE_FACE
.attr(PostWeatherChangeAddBattlerTagAttr, BattlerTagType.ICE_FACE, 0, WeatherType.HAIL, WeatherType.SNOW)
.attr(IceFaceMoveImmunityAbAttr, (target, user, move) => move.getMove().category === MoveCategory.PHYSICAL && !!target.getTag(BattlerTagType.ICE_FACE))
.attr(IceFaceMoveImmunityAbAttr, (target, user, move) => move.category === MoveCategory.PHYSICAL && !!target.getTag(BattlerTagType.ICE_FACE))
.ignorable(),
new Ability(Abilities.POWER_SPOT, 8)
.unimplemented(),
@ -4567,7 +4550,7 @@ export function initAbilities() {
.attr(TypeImmunityStatChangeAbAttr, Type.FIRE, BattleStat.DEF, 2)
.ignorable(),
new Ability(Abilities.WIND_RIDER, 9)
.attr(MoveImmunityStatChangeAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.getMove().hasFlag(MoveFlags.WIND_MOVE) && move.getMove().category !== MoveCategory.STATUS, BattleStat.ATK, 1)
.attr(MoveImmunityStatChangeAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.hasFlag(MoveFlags.WIND_MOVE) && move.category !== MoveCategory.STATUS, BattleStat.ATK, 1)
.attr(PostSummonStatChangeOnArenaAbAttr, ArenaTagType.TAILWIND)
.ignorable(),
new Ability(Abilities.GUARD_DOG, 9)
@ -4607,7 +4590,7 @@ export function initAbilities() {
.attr(NoTransformAbilityAbAttr)
.partial(), // While setting the tag, the getbattlestat should ignore all modifiers to stats except stat stages
new Ability(Abilities.GOOD_AS_GOLD, 9)
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.getMove().category === MoveCategory.STATUS)
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.category === MoveCategory.STATUS)
.ignorable()
.partial(),
new Ability(Abilities.VESSEL_OF_RUIN, 9)

View File

@ -99,6 +99,7 @@ export default class Move implements Localizable {
public id: Moves;
public name: string;
public type: Type;
public defaultType: Type;
public category: MoveCategory;
public moveTarget: MoveTarget;
public power: integer;
@ -118,6 +119,7 @@ export default class Move implements Localizable {
this.nameAppend = "";
this.type = type;
this.defaultType = type;
this.category = category;
this.moveTarget = defaultMoveTarget;
this.power = power;
@ -1629,7 +1631,7 @@ export class StatusEffectAttr extends MoveEffectAttr {
}
if ((!pokemon.status || (pokemon.status.effect === this.effect && move.chance < 0))
&& pokemon.trySetStatus(this.effect, true, user, this.cureTurn)) {
applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, new PokemonMove(move.id), null,this.effect);
applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, move, null,this.effect);
return true;
}
}
@ -3323,23 +3325,22 @@ 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)) {
const form = user.species.speciesId === Species.GENESECT ? user.formIndex : user.fusionSpecies.formIndex;
const type = (args[0] as Utils.IntegerHolder);
switch (form) {
case 1: // Shock Drive
type.value = Type.ELECTRIC;
move.type = Type.ELECTRIC;
break;
case 2: // Burn Drive
type.value = Type.FIRE;
move.type = Type.FIRE;
break;
case 3: // Chill Drive
type.value = Type.ICE;
move.type = Type.ICE;
break;
case 4: // Douse Drive
type.value = Type.WATER;
move.type = Type.WATER;
break;
default:
type.value = Type.NORMAL;
move.type = Type.NORMAL;
break;
}
return true;
@ -3353,14 +3354,13 @@ export class AuraWheelTypeAttr extends VariableMoveTypeAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.MORPEKO)) {
const form = user.species.speciesId === Species.MORPEKO ? user.formIndex : user.fusionSpecies.formIndex;
const type = (args[0] as Utils.IntegerHolder);
switch (form) {
case 1: // Hangry Mode
type.value = Type.DARK;
move.type = Type.DARK;
break;
default: // Full Belly Mode
type.value = Type.ELECTRIC;
move.type = Type.ELECTRIC;
break;
}
return true;
@ -3374,17 +3374,16 @@ export class RagingBullTypeAttr extends VariableMoveTypeAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.PALDEA_TAUROS)) {
const form = user.species.speciesId === Species.PALDEA_TAUROS ? user.formIndex : user.fusionSpecies.formIndex;
const type = (args[0] as Utils.IntegerHolder);
switch (form) {
case 1: // Blaze breed
type.value = Type.FIRE;
move.type = Type.FIRE;
break;
case 2: // Aqua breed
type.value = Type.WATER;
move.type = Type.WATER;
break;
default:
type.value = Type.FIGHTING;
move.type = Type.FIGHTING;
break;
}
return true;
@ -3398,32 +3397,31 @@ export class IvyCudgelTypeAttr extends VariableMoveTypeAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.OGERPON)) {
const form = user.species.speciesId === Species.OGERPON ? user.formIndex : user.fusionSpecies.formIndex;
const type = (args[0] as Utils.IntegerHolder);
switch (form) {
case 1: // Wellspring Mask
type.value = Type.WATER;
move.type = Type.WATER;
break;
case 2: // Hearthflame Mask
type.value = Type.FIRE;
move.type = Type.FIRE;
break;
case 3: // Cornerstone Mask
type.value = Type.ROCK;
move.type = Type.ROCK;
break;
case 4: // Teal Mask Tera
type.value = Type.GRASS;
move.type = Type.GRASS;
break;
case 5: // Wellspring Mask Tera
type.value = Type.WATER;
move.type = Type.WATER;
break;
case 6: // Hearthflame Mask Tera
type.value = Type.FIRE;
move.type = Type.FIRE;
break;
case 7: // Cornerstone Mask Tera
type.value = Type.ROCK;
move.type = Type.ROCK;
break;
default:
type.value = Type.GRASS;
move.type = Type.GRASS;
break;
}
return true;
@ -3436,23 +3434,21 @@ export class IvyCudgelTypeAttr extends VariableMoveTypeAttr {
export class WeatherBallTypeAttr extends VariableMoveTypeAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if (!user.scene.arena.weather?.isEffectSuppressed(user.scene)) {
const type = (args[0] as Utils.IntegerHolder);
switch (user.scene.arena.weather?.weatherType) {
case WeatherType.SUNNY:
case WeatherType.HARSH_SUN:
type.value = Type.FIRE;
move.type = Type.FIRE;
break;
case WeatherType.RAIN:
case WeatherType.HEAVY_RAIN:
type.value = Type.WATER;
move.type = Type.WATER;
break;
case WeatherType.SANDSTORM:
type.value = Type.ROCK;
move.type = Type.ROCK;
break;
case WeatherType.HAIL:
case WeatherType.SNOW:
type.value = Type.ICE;
move.type = Type.ICE;
break;
default:
return false;
@ -3484,20 +3480,18 @@ export class TerrainPulseTypeAttr extends VariableMoveTypeAttr {
}
const currentTerrain = user.scene.arena.getTerrainType();
const type = (args[0] as Utils.IntegerHolder);
switch (currentTerrain) {
case TerrainType.MISTY:
type.value = Type.FAIRY;
move.type = Type.FAIRY;
break;
case TerrainType.ELECTRIC:
type.value = Type.ELECTRIC;
move.type = Type.ELECTRIC;
break;
case TerrainType.GRASSY:
type.value = Type.GRASS;
move.type = Type.GRASS;
break;
case TerrainType.PSYCHIC:
type.value = Type.PSYCHIC;
move.type = Type.PSYCHIC;
break;
default:
return false;
@ -3508,8 +3502,6 @@ export class TerrainPulseTypeAttr extends VariableMoveTypeAttr {
export class HiddenPowerTypeAttr extends VariableMoveTypeAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const type = (args[0] as Utils.IntegerHolder);
const iv_val = Math.floor(((user.ivs[Stat.HP] & 1)
+(user.ivs[Stat.ATK] & 1) * 2
+(user.ivs[Stat.DEF] & 1) * 4
@ -3517,7 +3509,7 @@ export class HiddenPowerTypeAttr extends VariableMoveTypeAttr {
+(user.ivs[Stat.SPATK] & 1) * 16
+(user.ivs[Stat.SPDEF] & 1) * 32) * 15/63);
type.value = [
move.type = [
Type.FIGHTING, Type.FLYING, Type.POISON, Type.GROUND,
Type.ROCK, Type.BUG, Type.GHOST, Type.STEEL,
Type.FIRE, Type.WATER, Type.GRASS, Type.ELECTRIC,
@ -3529,16 +3521,14 @@ export class HiddenPowerTypeAttr extends VariableMoveTypeAttr {
export class MatchUserTypeAttr extends VariableMoveTypeAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const type = (args[0] as Utils.IntegerHolder);
const userTypes = user.getTypes(true);
if (userTypes.includes(Type.STELLAR)) { // will not change to stellar type
const nonTeraTypes = user.getTypes();
type.value = nonTeraTypes[0];
move.type = nonTeraTypes[0];
return true;
} else if (userTypes.length > 0) {
type.value = userTypes[0];
move.type = userTypes[0];
return true;
} else {
return false;
@ -4345,7 +4335,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
// Check if the move category is not STATUS or if the switch out condition is not met
if (!this.getSwitchOutCondition()(user, target, move)) {
//Apply effects before switch out i.e. poison point, flame body, etc
applyPostDefendAbAttrs(PostDefendContactApplyStatusEffectAbAttr, target, user, new PokemonMove(move.id), null);
applyPostDefendAbAttrs(PostDefendContactApplyStatusEffectAbAttr, target, user, move, null);
return resolve(false);
}
@ -6015,7 +6005,7 @@ export function initMoves() {
], true)
.attr(RemoveArenaTrapAttr),
new StatusMove(Moves.SWEET_SCENT, Type.NORMAL, 100, 20, -1, 0, 2)
.attr(StatChangeAttr, BattleStat.EVA, -1)
.attr(StatChangeAttr, BattleStat.EVA, -2)
.target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.IRON_TAIL, Type.STEEL, MoveCategory.PHYSICAL, 100, 75, 15, 30, 0, 2)
.attr(StatChangeAttr, BattleStat.DEF, -1),

View File

@ -363,7 +363,8 @@ export const pokemonFormChanges: PokemonFormChanges = {
new SpeciesFormChange(Species.PIDGEOT, "", SpeciesFormKey.MEGA, new SpeciesFormChangeItemTrigger(FormChangeItem.PIDGEOTITE))
],
[Species.PIKACHU]: [
new SpeciesFormChange(Species.PIKACHU, "", SpeciesFormKey.GIGANTAMAX, new SpeciesFormChangeItemTrigger(FormChangeItem.MAX_MUSHROOMS))
new SpeciesFormChange(Species.PIKACHU, "", SpeciesFormKey.GIGANTAMAX, new SpeciesFormChangeItemTrigger(FormChangeItem.MAX_MUSHROOMS)),
new SpeciesFormChange(Species.PIKACHU, "partner", SpeciesFormKey.GIGANTAMAX, new SpeciesFormChangeItemTrigger(FormChangeItem.MAX_MUSHROOMS))
],
[Species.MEOWTH]: [
new SpeciesFormChange(Species.MEOWTH, "", SpeciesFormKey.GIGANTAMAX, new SpeciesFormChangeItemTrigger(FormChangeItem.MAX_MUSHROOMS))
@ -397,7 +398,8 @@ export const pokemonFormChanges: PokemonFormChanges = {
new SpeciesFormChange(Species.LAPRAS, "", SpeciesFormKey.GIGANTAMAX, new SpeciesFormChangeItemTrigger(FormChangeItem.MAX_MUSHROOMS))
],
[Species.EEVEE]: [
new SpeciesFormChange(Species.EEVEE, "", SpeciesFormKey.GIGANTAMAX, new SpeciesFormChangeItemTrigger(FormChangeItem.MAX_MUSHROOMS))
new SpeciesFormChange(Species.EEVEE, "", SpeciesFormKey.GIGANTAMAX, new SpeciesFormChangeItemTrigger(FormChangeItem.MAX_MUSHROOMS)),
new SpeciesFormChange(Species.EEVEE, "partner", SpeciesFormKey.GIGANTAMAX, new SpeciesFormChangeItemTrigger(FormChangeItem.MAX_MUSHROOMS))
],
[Species.SNORLAX]: [
new SpeciesFormChange(Species.SNORLAX, "", SpeciesFormKey.GIGANTAMAX, new SpeciesFormChangeItemTrigger(FormChangeItem.MAX_MUSHROOMS))

View File

@ -78,7 +78,7 @@ export const trainerNamePools = {
[TrainerType.ACE_TRAINER]: [["Aaron","Allen","Blake","Brian","Gaven","Jake","Kevin","Mike","Nick","Paul","Ryan","Sean","Darin","Albert","Berke","Clyde","Edgar","George","Leroy","Owen","Parker","Randall","Ruben","Samuel","Vincent","Warren","Wilton","Zane","Alfred","Braxton","Felix","Gerald","Jonathan","Leonel","Marcel","Mitchell","Quincy","Roderick","Colby","Rolando","Yuji","Abel","Anton","Arthur","Cesar","Dalton","Dennis","Ernest","Garrett","Graham","Henry","Isaiah","Jonah","Jose","Keenan","Micah","Omar","Quinn","Rodolfo","Saul","Sergio","Skylar","Stefan","Zachery","Alton","Arabella","Bonita","Cal","Cody","French","Kobe","Paulo","Shaye","Austin","Beckett","Charlie","Corky","David","Dwayne","Elmer","Jesse","Jared","Johan","Jordan","Kipp","Lou","Terry","Tom","Webster","Billy","Doyle","Enzio","Geoff","Grant","Kelsey","Miguel","Pierce","Ray","Santino","Shel","Adelbert","Bence","Emil","Evan","Mathis","Maxim","Neil","Rico","Robbie","Theo","Viktor","Benedict","Cornelius","Hisato","Leopold","Neville","Vito","Chase","Cole","Hiroshi","Jackson","Jim","Kekoa","Makana","Yuki","Elwood","Seth","Alvin","Arjun","Arnold","Cameron","Carl","Carlton","Christopher","Dave","Dax","Dominic","Edmund","Finn","Fred","Garret","Grayson","Jace","Jaxson","Jay","Jirard","Johnson","Kayden","Kite","Louis","Mac","Marty","Percy","Raymond","Ronnie","Satch","Tim","Zach","Conner","Vince","Bedro","Boda","Botan","Daras","Dury","Herton","Rewn","Stum","Tock","Trilo","Berki","Cruik","Dazon","Desid","Dillot","Farfin","Forgon","Hebel","Morfon","Moril","Shadd","Vanhub","Bardo","Carben","Degin","Gorps","Klept","Lask","Malex","Mopar","Niled","Noxon","Teslor","Tetil"],["Beth","Carol","Cybil","Emma","Fran","Gwen","Irene","Jenn","Joyce","Kate","Kelly","Lois","Lola","Megan","Quinn","Reena","Cara","Alexa","Brooke","Caroline","Elaine","Hope","Jennifer","Jody","Julie","Lori","Mary","Michelle","Shannon","Wendy","Alexia","Alicia","Athena","Carolina","Cristin","Darcy","Dianne","Halle","Jazmyn","Katelynn","Keira","Marley","Allyson","Kathleen","Naomi","Alyssa","Ariana","Brandi","Breanna","Brenda","Brenna","Catherine","Clarice","Dana","Deanna","Destiny","Jamie","Jasmin","Kassandra","Laura","Maria","Mariah","Maya","Meagan","Mikayla","Monique","Natasha","Olivia","Sandra","Savannah","Sydney","Moira","Piper","Salma","Allison","Beverly","Cathy","Cheyenne","Clara","Dara","Eileen","Glinda","Junko","Lena","Lucille","Mariana","Olwen","Shanta","Stella","Angi","Belle","Chandra","Cora","Eve","Jacqueline","Jeanne","Juliet","Kathrine","Layla","Lucca","Melina","Miki","Nina","Sable","Shelly","Summer","Trish","Vicki","Alanza","Cordelia","Hilde","Imelda","Michele","Mireille","Claudia","Constance","Harriet","Honor","Melba","Portia","Alexis","Angela","Karla","Lindsey","Tori","Sheri","Jada","Kailee","Amanda","Annie","Kindra","Kyla","Sofia","Yvette","Becky","Flora","Gloria","Buna","Ferda","Lehan","Liqui","Lomen","Neira","Atilo","Detta","Gilly","Gosney","Levens","Moden","Rask","Rateis","Rosno","Tynan","Veron","Zoel","Cida","Dibsin","Dodin","Ebson","Equin","Flostin","Gabsen","Halsion","Hileon","Quelor","Rapeel","Roze","Tensin"]],
[TrainerType.ARTIST]: [["Ismael","William","Horton","Pierre","Zach","Gough","Salvador","Vincent","Duncan"],["Georgia"]],
[TrainerType.BACKERS]: [["Alf & Fred","Hawk & Dar","Joe & Ross","Les & Web","Masa & Yas","Stu & Art"],["Ai & Ciel","Ami & Eira","Cam & Abby","Fey & Sue","Kat & Phae","Kay & Ali","Ava & Aya","Cleo & Rio","May & Mal"]],
[TrainerType.BACKPACKER]: [["Alexander","Carlos","Herman","Jerome","Keane","Kelsey","Kiyo","Michael","Nate","Peter","Sam","Stephen","Talon","Terrance","Toru","Waylon","Boone","Clifford","Ivan","Kendall","Lowell","Randall","Reece","Roland","Shane","Walt","Farid","Heike","Joren","Lane","Roderick","Darnell","Deon","Emory","Graeme","Grayson","Ashley","Mikiko","Kiana","Perdy","Maria","Yuho","Peren","Barbara","Diane","Ruth","Aitor","Alex","Arturo","Asier","Jaime","Jonathan","Julio","Kevin","Kosuke","Lander","Markel","Mateo","Nil","Pau","Samuel"],["Anna","Corin","Elaine","Emi","Jill","Kumiko","Liz","Lois","Lora","Molly","Patty","Ruth","Vicki","Annie","Blossom","Clara","Eileen","Mae","Myra","Rachel","Tami"]],
[TrainerType.BACKPACKER]: [["Alexander","Carlos","Herman","Jerome","Keane","Kelsey","Kiyo","Michael","Nate","Peter","Sam","Stephen","Talon","Terrance","Toru","Waylon","Boone","Clifford","Ivan","Kendall","Lowell","Randall","Reece","Roland","Shane","Walt","Farid","Heike","Joren","Lane","Roderick","Darnell","Deon","Emory","Graeme","Grayson","Aitor","Alex","Arturo","Asier","Jaime","Jonathan","Julio","Kevin","Kosuke","Lander","Markel","Mateo","Nil","Pau","Samuel"],["Anna","Corin","Elaine","Emi","Jill","Kumiko","Liz","Lois","Lora","Molly","Patty","Ruth","Vicki","Annie","Blossom","Clara","Eileen","Mae","Myra","Rachel","Tami","Ashley","Mikiko","Kiana","Perdy","Maria","Yuho","Peren","Barbara","Diane"]],
[TrainerType.BAKER]: ["Chris","Jenn","Lilly"],
[TrainerType.BEAUTY]: ["Cassie","Julia","Olivia","Samantha","Valerie","Victoria","Bridget","Connie","Jessica","Johanna","Melissa","Sheila","Shirley","Tiffany","Namiko","Thalia","Grace","Lola","Lori","Maura","Tamia","Cyndy","Devon","Gabriella","Harley","Lindsay","Nicola","Callie","Charlotte","Kassandra","December","Fleming","Nikola","Aimee","Anais","Brigitte","Cassandra","Andrea","Brittney","Carolyn","Krystal","Alexis","Alice","Aina","Anya","Arianna","Aubrey","Beverly","Camille","Beauty","Evette","Hansol","Haruka","Jill","Jo","Lana","Lois","Lucy","Mai","Nickie","Nicole","Prita","Rose","Shelly","Suzy","Tessa","Anita","Alissa","Rita","Cudsy","Eloff","Miru","Minot","Nevah","Niven","Ogoin"],
[TrainerType.BIKER]: ["Charles","Dwayne","Glenn","Harris","Joel","Riley","Zeke","Alex","Billy","Ernest","Gerald","Hideo","Isaac","Jared","Jaren","Jaxon","Jordy","Lao","Lukas","Malik","Nikolas","Ricardo","Ruben","Virgil","William","Aiden","Dale","Dan","Jacob","Markey","Reese","Teddy","Theron","Jeremy","Morgann","Phillip","Philip","Stanley","Dillon"],
@ -93,7 +93,7 @@ export const trainerNamePools = {
[TrainerType.FISHERMAN]: ["Andre","Arnold","Barney","Chris","Edgar","Henry","Jonah","Justin","Kyle","Martin","Marvin","Ralph","Raymond","Scott","Stephen","Wilton","Tully","Andrew","Barny","Carter","Claude","Dale","Elliot","Eugene","Ivan","Ned","Nolan","Roger","Ronald","Wade","Wayne","Darian","Kai","Chip","Hank","Kaden","Tommy","Tylor","Alec","Brett","Cameron","Cody","Cole","Cory","Erick","George","Joseph","Juan","Kenneth","Luc","Miguel","Travis","Walter","Zachary","Josh","Gideon","Kyler","Liam","Murphy","Bruce","Damon","Devon","Hubert","Jones","Lydon","Mick","Pete","Sean","Sid","Vince","Bucky","Dean","Eustace","Kenzo","Leroy","Mack","Ryder","Ewan","Finn","Murray","Seward","Shad","Wharton","Finley","Fisher","Fisk","River","Sheaffer","Timin","Carl","Ernest","Hal","Herbert","Hisato","Mike","Vernon","Harriet","Marina","Chase"],
[TrainerType.GUITARIST]: ["Anna","Beverly","January","Tina","Alicia","Claudia","Julia","Lidia","Mireia","Noelia","Sara","Sheila","Tatiana"],
[TrainerType.HARLEQUIN]: ["Charley","Ian","Jack","Kerry","Louis","Pat","Paul","Rick","Anders","Clarence","Gary"],
[TrainerType.HIKER]: ["Anthony","Bailey","Benjamin","Daniel","Erik","Jim","Kenny","Leonard","Michael","Parry","Phillip","Russell","Sidney","Tim","Timothy","Alan","Brice","Clark","Eric","Lenny","Lucas","Mike","Trent","Devan","Eli","Marc","Sawyer","Allen","Daryl","Dudley","Earl","Franklin","Jeremy","Marcos","Nob","Oliver","Wayne","Alexander","Damon","Jonathan","Justin","Kevin","Lorenzo","Louis","Maurice","Nicholas","Reginald","Robert","Theodore","Bruce","Clarke","Devin","Dwight","Edwin","Eoin","Noland","Russel","Andy","Bret","Darrell","Gene","Hardy","Hugh","Jebediah","Jeremiah","Kit","Neil","Terrell","Don","Doug","Hunter","Jared","Jerome","Keith","Manuel","Markus","Otto","Shelby","Stephen","Teppei","Tobias","Wade","Zaiem","Aaron","Alain","Bergin","Bernard","Brent","Corwin","Craig","Delmon","Dunstan","Orestes","Ross","Davian","Calhoun","David","Gabriel","Ryan","Thomas","Travis","Zachary","Anuhea","Barnaby","Claus","Collin","Colson","Dexter","Dillan","Eugine","Farkas","Hisato","Julius","Kenji","Irwin","Lionel","Paul","Richter","Valentino","Donald","Douglas","Kevyn","Angela","Carla","Celia","Daniela","Estela","Fatima","Helena","Leire","Lucia","Luna","Manuela","Mar","Marina","Miyu","Nancy","Nerea","Paula","Rocio","Yanira","Chester"],
[TrainerType.HIKER]: ["Anthony","Bailey","Benjamin","Daniel","Erik","Jim","Kenny","Leonard","Michael","Parry","Phillip","Russell","Sidney","Tim","Timothy","Alan","Brice","Clark","Eric","Lenny","Lucas","Mike","Trent","Devan","Eli","Marc","Sawyer","Allen","Daryl","Dudley","Earl","Franklin","Jeremy","Marcos","Nob","Oliver","Wayne","Alexander","Damon","Jonathan","Justin","Kevin","Lorenzo","Louis","Maurice","Nicholas","Reginald","Robert","Theodore","Bruce","Clarke","Devin","Dwight","Edwin","Eoin","Noland","Russel","Andy","Bret","Darrell","Gene","Hardy","Hugh","Jebediah","Jeremiah","Kit","Neil","Terrell","Don","Doug","Hunter","Jared","Jerome","Keith","Manuel","Markus","Otto","Shelby","Stephen","Teppei","Tobias","Wade","Zaiem","Aaron","Alain","Bergin","Bernard","Brent","Corwin","Craig","Delmon","Dunstan","Orestes","Ross","Davian","Calhoun","David","Gabriel","Ryan","Thomas","Travis","Zachary","Anuhea","Barnaby","Claus","Collin","Colson","Dexter","Dillan","Eugine","Farkas","Hisato","Julius","Kenji","Irwin","Lionel","Paul","Richter","Valentino","Donald","Douglas","Kevyn","Chester"], //["Angela","Carla","Celia","Daniela","Estela","Fatima","Helena","Leire","Lucia","Luna","Manuela","Mar","Marina","Miyu","Nancy","Nerea","Paula","Rocio","Yanira"]
[TrainerType.HOOLIGANS]: ["Jim & Cas","Rob & Sal"],
[TrainerType.HOOPSTER]: ["Bobby","John","Lamarcus","Derrick","Nicolas"],
[TrainerType.INFIELDER]: ["Alex","Connor","Todd"],

View File

@ -4,7 +4,7 @@ import { Variant, VariantSet, variantColorCache } from "#app/data/variant";
import { variantData } from "#app/data/variant";
import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "../ui/battle-info";
import { Moves } from "../data/enums/moves";
import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, VariablePowerAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, MultiHitAttr, StatusMoveTypeImmunityAttr, MoveTarget, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatChangesAttr, SacrificialAttr, VariableMoveTypeAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, MoveFlags } from "../data/move";
import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, VariablePowerAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, MultiHitAttr, VariableMoveTypeAttr, StatusMoveTypeImmunityAttr, MoveTarget, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatChangesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, MoveFlags } from "../data/move";
import { default as PokemonSpecies, PokemonSpeciesForm, SpeciesFormKey, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm, getStarterValueFriendshipCap, speciesStarters, starterPassiveAbilities } from "../data/pokemon-species";
import * as Utils from "../utils";
import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from "../data/type";
@ -27,7 +27,7 @@ import { TempBattleStat } from "../data/temp-battle-stat";
import { ArenaTagSide, WeakenMoveScreenTag, WeakenMoveTypeTag } from "../data/arena-tag";
import { ArenaTagType } from "../data/enums/arena-tag-type";
import { Biome } from "../data/enums/biome";
import { Ability, AbAttr, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, FieldVariableMovePowerAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, MoveTypeChangeAttr, PreApplyBattlerTagAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, VariableMoveTypeAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr } from "../data/ability";
import { Ability, AbAttr, BattleStatMultiplierAbAttr, MoveTypeChangeAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, FieldVariableMovePowerAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, PreApplyBattlerTagAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr } from "../data/ability";
import { Abilities } from "#app/data/enums/abilities";
import PokemonData from "../system/pokemon-data";
import { BattlerIndex } from "../battle";
@ -1057,9 +1057,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return !this.isOfType(Type.FLYING, true) && !this.hasAbility(Abilities.LEVITATE);
}
getAttackMoveEffectiveness(source: Pokemon, move: PokemonMove): TypeDamageMultiplier {
const typeless = move.getMove().hasAttr(TypelessAttr);
const typeMultiplier = new Utils.NumberHolder(this.getAttackTypeEffectiveness(move.getMove().type, source));
getAttackMoveEffectiveness(source: Pokemon, pokemonMove: PokemonMove): 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);
if (!typeless) {
applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, move, cancelled, typeMultiplier, true);
@ -1620,9 +1621,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return (this.isPlayer() ? this.scene.getPlayerField() : this.scene.getEnemyField())[this.getFieldIndex() ? 0 : 1];
}
apply(source: Pokemon, battlerMove: PokemonMove): HitResult {
apply(source: Pokemon, move: Move): HitResult {
let result: HitResult;
const move = battlerMove.getMove();
const damage = new Utils.NumberHolder(0);
const defendingSidePlayField = this.isPlayer() ? this.scene.getPlayerField() : this.scene.getEnemyField();
@ -1630,19 +1630,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
applyMoveAttrs(VariableMoveCategoryAttr, source, this, move, variableCategory);
const moveCategory = variableCategory.value as MoveCategory;
const variableType = new Utils.IntegerHolder(move.type);
const typeChangeMovePowerMultiplier = new Utils.NumberHolder(1);
applyMoveAttrs(VariableMoveTypeAttr, source, this, move, variableType);
// 2nd argument is for MoveTypeChangePowerMultiplierAbAttr
applyAbAttrs(VariableMoveTypeAbAttr, source, null, variableType, typeChangeMovePowerMultiplier);
applyPreAttackAbAttrs(MoveTypeChangeAttr, source, this, battlerMove, variableType, typeChangeMovePowerMultiplier);
const type = variableType.value as Type;
applyMoveAttrs(VariableMoveTypeAttr, source, this, move);
applyPreAttackAbAttrs(MoveTypeChangeAttr, source, this, move, typeChangeMovePowerMultiplier);
const types = this.getTypes(true, true);
const cancelled = new Utils.BooleanHolder(false);
const typeless = move.hasAttr(TypelessAttr);
const typeMultiplier = new Utils.NumberHolder(!typeless && (moveCategory !== MoveCategory.STATUS || move.getAttrs(StatusMoveTypeImmunityAttr).find(attr => types.includes(attr.immuneType)))
? this.getAttackTypeEffectiveness(type, source)
? this.getAttackTypeEffectiveness(move.type, source)
: 1);
applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier);
if (typeless) {
@ -1667,44 +1663,44 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const isPhysical = moveCategory === MoveCategory.PHYSICAL;
const power = new Utils.NumberHolder(move.power);
const sourceTeraType = source.getTeraType();
if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === type && power.value < 60 && move.priority <= 0 && !move.hasAttr(MultiHitAttr) && !this.scene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) {
if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === move.type && power.value < 60 && move.priority <= 0 && !move.hasAttr(MultiHitAttr) && !this.scene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) {
power.value = 60;
}
applyPreAttackAbAttrs(VariableMovePowerAbAttr, source, this, battlerMove, power);
this.scene.getField(true).map(p => applyPreAttackAbAttrs(FieldVariableMovePowerAbAttr, this, source, battlerMove, power));
applyPreAttackAbAttrs(VariableMovePowerAbAttr, source, this, move, power);
this.scene.getField(true).map(p => applyPreAttackAbAttrs(FieldVariableMovePowerAbAttr, this, source, move, power));
applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, this, source, battlerMove, cancelled, power);
applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, this, source, move, cancelled, power);
power.value *= typeChangeMovePowerMultiplier.value;
if (!typeless) {
applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, battlerMove, cancelled, typeMultiplier);
applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, move, cancelled, typeMultiplier);
}
if (!cancelled.value) {
applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, battlerMove, cancelled, typeMultiplier);
defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, source, battlerMove, cancelled, typeMultiplier));
applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, move, cancelled, typeMultiplier);
defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, source, move, cancelled, typeMultiplier));
}
if (cancelled.value) {
result = HitResult.NO_EFFECT;
} else {
const typeBoost = source.findTag(t => t instanceof TypeBoostTag && (t as TypeBoostTag).boostedType === type) as TypeBoostTag;
const typeBoost = source.findTag(t => t instanceof TypeBoostTag && t.boostedType === move.type) as TypeBoostTag;
if (typeBoost) {
power.value *= typeBoost.boostValue;
if (typeBoost.oneUse) {
source.removeTag(typeBoost.tagType);
}
}
const arenaAttackTypeMultiplier = new Utils.NumberHolder(this.scene.arena.getAttackTypeMultiplier(type, source.isGrounded()));
const arenaAttackTypeMultiplier = new Utils.NumberHolder(this.scene.arena.getAttackTypeMultiplier(move.type, source.isGrounded()));
applyMoveAttrs(IgnoreWeatherTypeDebuffAttr, source, this, move, arenaAttackTypeMultiplier);
if (this.scene.arena.getTerrainType() === TerrainType.GRASSY && this.isGrounded() && type === Type.GROUND && move.moveTarget === MoveTarget.ALL_NEAR_OTHERS) {
if (this.scene.arena.getTerrainType() === TerrainType.GRASSY && this.isGrounded() && move.type === Type.GROUND && move.moveTarget === MoveTarget.ALL_NEAR_OTHERS) {
power.value /= 2;
}
applyMoveAttrs(VariablePowerAttr, source, this, move, power);
this.scene.applyModifiers(PokemonMultiHitModifier, source.isPlayer(), source, new Utils.IntegerHolder(0), power);
if (!typeless) {
this.scene.arena.applyTags(WeakenMoveTypeTag, type, power);
this.scene.applyModifiers(AttackTypeBoosterModifier, source.isPlayer(), source, type, power);
this.scene.arena.applyTags(WeakenMoveTypeTag, move.type, power);
this.scene.applyModifiers(AttackTypeBoosterModifier, source.isPlayer(), source, move.type, power);
}
if (source.getTag(HelpingHandTag)) {
power.value *= 1.5;
@ -1731,6 +1727,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
const critChance = [24, 8, 2, 1][Math.max(0, Math.min(critLevel.value, 3))];
isCritical = !source.getTag(BattlerTagType.NO_CRIT) && (critChance === 1 || !this.scene.randBattleSeedInt(critChance));
if (Overrides.NEVER_CRIT_OVERRIDE) {
isCritical = false;
}
}
if (isCritical) {
const blockCrit = new Utils.BooleanHolder(false);
@ -1749,11 +1748,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
const isTypeImmune = (typeMultiplier.value * arenaAttackTypeMultiplier.value) === 0;
const sourceTypes = source.getTypes();
const matchesSourceType = sourceTypes[0] === type || (sourceTypes.length > 1 && sourceTypes[1] === type);
const matchesSourceType = sourceTypes[0] === move.type || (sourceTypes.length > 1 && sourceTypes[1] === move.type);
const stabMultiplier = new Utils.NumberHolder(1);
if (sourceTeraType === Type.UNKNOWN && matchesSourceType) {
stabMultiplier.value += 0.5;
} else if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === type) {
} else if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === move.type) {
stabMultiplier.value += 0.5;
}
@ -1778,7 +1777,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
}
applyPreAttackAbAttrs(DamageBoostAbAttr, source, this, battlerMove, damage);
applyPreAttackAbAttrs(DamageBoostAbAttr, source, this, move, damage);
/**
* For each {@link HitsTagAttr} the move has, doubles the damage of the move if:
@ -1793,7 +1792,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
});
}
if (this.scene.arena.terrain?.terrainType === TerrainType.MISTY && this.isGrounded() && type === Type.DRAGON) {
if (this.scene.arena.terrain?.terrainType === TerrainType.MISTY && this.isGrounded() && move.type === Type.DRAGON) {
damage.value = Math.floor(damage.value / 2);
}
@ -1848,7 +1847,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const oneHitKo = result === HitResult.ONE_HIT_KO;
if (damage.value) {
if (this.getHpRatio() === 1) {
applyPreDefendAbAttrs(PreDefendFullHpEndureAbAttr, this, source, battlerMove, cancelled, damage);
applyPreDefendAbAttrs(PreDefendFullHpEndureAbAttr, this, source, move, cancelled, damage);
} else if (!this.isPlayer() && damage.value >= this.hp) {
this.scene.applyModifiers(EnemyEndureChanceModifier, false, this);
}
@ -1913,11 +1912,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
break;
case MoveCategory.STATUS:
if (!typeless) {
applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, battlerMove, cancelled, typeMultiplier);
applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, move, cancelled, typeMultiplier);
}
if (!cancelled.value) {
applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, battlerMove, cancelled, typeMultiplier);
defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, source, battlerMove, cancelled, typeMultiplier));
applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, move, cancelled, typeMultiplier);
defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, source, move, cancelled, typeMultiplier));
}
if (!typeMultiplier.value) {
this.scene.queueMessage(i18next.t("battle:hitResultNoEffect", { pokemonName: this.name }));
@ -3381,10 +3380,6 @@ export class EnemyPokemon extends Pokemon {
const pokemonMove = movePool[m];
const move = pokemonMove.getMove();
const variableType = new Utils.IntegerHolder(move.type);
applyAbAttrs(VariableMoveTypeAbAttr, this, null, variableType);
const moveType = variableType.value as Type;
let moveScore = moveScores[m];
const targetScores: integer[] = [];
@ -3402,12 +3397,12 @@ export class EnemyPokemon extends Pokemon {
const effectiveness = target.getAttackMoveEffectiveness(this, pokemonMove);
if (target.isPlayer() !== this.isPlayer()) {
targetScore *= effectiveness;
if (this.isOfType(moveType)) {
if (this.isOfType(move.type)) {
targetScore *= 1.5;
}
} else if (effectiveness) {
targetScore /= effectiveness;
if (this.isOfType(moveType)) {
if (this.isOfType(move.type)) {
targetScore /= 1.5;
}
}
@ -3789,6 +3784,19 @@ export enum HitResult {
export type DamageResult = HitResult.EFFECTIVE | HitResult.SUPER_EFFECTIVE | HitResult.NOT_VERY_EFFECTIVE | HitResult.ONE_HIT_KO | HitResult.OTHER;
/**
* Wrapper class for the {@linkcode Move} class for Pokemon to interact with.
* These are the moves assigned to a {@linkcode Pokemon} object.
* It links to {@linkcode Move} class via the move ID.
* Compared to {@linkcode Move}, this class also tracks if a move has received.
* PP Ups, amount of PP used, and things like that.
* @see {@linkcode isUsable} - checks if move is disabled, out of PP, or not implemented.
* @see {@linkcode getMove} - returns {@linkcode Move} object by looking it up via ID.
* @see {@linkcode usePp} - removes a point of PP from the move.
* @see {@linkcode getMovePp} - returns amount of PP a move currently has.
* @see {@linkcode getPpRatio} - returns the current PP amount / max PP amount.
* @see {@linkcode getName} - returns name of {@linkcode Move}.
**/
export class PokemonMove {
public moveId: Moves;
public ppUsed: integer;

View File

@ -94,7 +94,6 @@ export class InputsController {
private buttonLock: Button;
private interactions: Map<Button, Map<string, boolean>> = new Map();
private time: Phaser.Time.Clock;
private configs: Map<string, InterfaceConfig> = new Map();
public gamepadSupport: boolean = true;
@ -121,7 +120,6 @@ export class InputsController {
constructor(scene: BattleScene) {
this.scene = scene;
this.time = this.scene.time;
this.selectedDevice = {
[Device.GAMEPAD]: null,
[Device.KEYBOARD]: "default"
@ -246,6 +244,9 @@ export class InputsController {
* If an interaction is valid and should be processed, it emits an 'input_down' event with details of the interaction.
*/
update(): void {
if (this.pauseUpdate) {
return;
}
for (const b of Utils.getEnumValues(Button).reverse()) {
if (
this.interactions.hasOwnProperty(b) &&
@ -256,8 +257,7 @@ export class InputsController {
if (
(!this.gamepadSupport && this.interactions[b].source === "gamepad") ||
(this.interactions[b].source === "gamepad" && this.interactions[b].sourceName && this.interactions[b].sourceName !== this.selectedDevice[Device.GAMEPAD]) ||
(this.interactions[b].source === "keyboard" && this.interactions[b].sourceName && this.interactions[b].sourceName !== this.selectedDevice[Device.KEYBOARD]) ||
this.pauseUpdate
(this.interactions[b].source === "keyboard" && this.interactions[b].sourceName && this.interactions[b].sourceName !== this.selectedDevice[Device.KEYBOARD])
) {
// Deletes the last interaction for a button if gamepad is disabled.
this.delLastProcessedMovementTime(b as Button);
@ -548,7 +548,8 @@ export class InputsController {
if (!this.isButtonLocked(button)) {
return false;
}
if (this.time.now - this.interactions[button].pressTime >= repeatInputDelayMillis) {
const duration = Date.now() - this.interactions[button].pressTime;
if (duration >= repeatInputDelayMillis) {
return true;
}
}
@ -573,7 +574,7 @@ export class InputsController {
return;
}
this.setButtonLock(button);
this.interactions[button].pressTime = this.time.now;
this.interactions[button].pressTime = Date.now();
this.interactions[button].isPressed = true;
this.interactions[button].source = source;
this.interactions[button].sourceName = sourceName.toLowerCase();
@ -633,7 +634,7 @@ export class InputsController {
this.interactions[b].sourceName = null;
}
}
setTimeout(() => this.pauseUpdate = false, 500);
this.pauseUpdate = false;
}
/**

View File

@ -16,9 +16,11 @@ import {initPokemonForms} from "#app/data/pokemon-forms";
import {initSpecies} from "#app/data/pokemon-species";
import {initMoves} from "#app/data/move";
import {initAbilities} from "#app/data/ability";
import {initAchievements} from "#app/system/achv";
import {initTrainerTypeDialogue} from "#app/data/dialogue";
import i18next from "i18next";
import { initStatsKeys } from "./ui/game-stats-ui-handler";
import { initVouchers } from "./system/voucher";
export class LoadingScene extends SceneBase {
constructor() {
@ -135,7 +137,6 @@ export class LoadingScene extends SceneBase {
this.loadImage("summary_stats_overlay_exp", "ui");
this.loadImage("summary_moves", "ui");
this.loadImage("summary_moves_effect", "ui");
this.loadImage("summary_moves_effect_type", "ui");
this.loadImage("summary_moves_overlay_row", "ui");
this.loadImage("summary_moves_overlay_pp", "ui");
this.loadAtlas("summary_moves_cursor", "ui");
@ -329,6 +330,8 @@ export class LoadingScene extends SceneBase {
this.loadLoadingScreen();
initVouchers();
initAchievements();
initStatsKeys();
initPokemonPrevolutions();
initBiomes();

View File

@ -369,26 +369,26 @@ export const PGMdialogue: DialogueTranslationEntries = {
},
"firebreather": {
"encounter": {
1: "My flames shall devour you!",
2: "My soul is on fire. I'll show you how hot it burns!",
3: "Step right up and take a look!"
1: "Meine Flammen werden dich verschlingen!",
2: "Meine Seele hat Feuer gefangen. Ich werde dir zeigen, wie heiß sie brennt!",
3: "Komm näher und sieh dir meine Flammen an!"
},
"victory": {
1: "I burned down to ashes...",
2: "Yow! That's hot!",
3: "Ow! I scorched the tip of my nose!"
1: "Verbrannt bis zur Asche...",
2: "Yow! Das ist heiß!",
3: "Auuu! Ich habe mir die Nasenspitze verbrannt!"
},
},
"sailor": {
"encounter": {
1: "Matey, you're walking the plank if you lose!",
2: "Come on then! My sailor's pride is at stake!",
3: "Ahoy there! Are you seasick?"
1: "Matrose, du gehst über Bord, wenn du verlierst!",
2: "Komm schon! Mein Stolz als Seemann steht auf dem Spiel!",
3: "Ahoj! Bist du seekrank?"
},
"victory": {
1: "Argh! Beaten by a kid!",
2: "Your spirit sank me!",
3: "I think it's me that's seasick..."
1: "Argh! Von einem Kind besiegt!",
2: "Dein Geist hat mich versenkt!",
3: "Ich glaube, ich bin der der seekrank ist..."
},
},
"brock": {

View File

@ -283,7 +283,7 @@ export const pokemon: SimpleTranslationEntries = {
"ralts": "Trasla",
"kirlia": "Kirlia",
"gardevoir": "Gardevoir",
"surskit": "Geweiher",
"surskit": "Gehweiher",
"masquerain": "Maskeregen",
"shroomish": "Knilz",
"breloom": "Kapilz",

View File

@ -243,64 +243,64 @@ export const PGMdialogue: DialogueTranslationEntries = {
},
"scientist": {
"encounter": {
1: "My research will lead this world to peace and joy.",
1: "제 연구는 이 세상을 평화와 기쁨으로 이끌 겁니다.",
},
"victory": {
1: "I am a genius… I am not supposed to lose against someone like you…",
1: "전 천재니까… 당신 같은 사람에게 질 수 없는데…",
},
},
"school_kid": {
"encounter": {
1: "…Heehee. I'm confident in my calculations and analysis.",
2: "I'm gaining as much experience as I can because I want to be a Gym Leader someday."
1: "…헤헷. 계산과 분석에는 자신 있어.",
2: "언젠가 체육관 관장이 되고 싶어서, 최대한 많은 경험을 쌓고 있어."
},
"victory": {
1: "Ohhhh… Calculation and analysis are perhaps no match for chance…",
2: "Even difficult, trying experiences have their purpose, I suppose."
1: "으아아… 이번에는 아마 계산과 분석이 빗나간 것 같아…",
2: "내가 보기엔, 어렵고 힘든 경험도 나름의 의미가 있는 것 같아."
}
},
"artist": {
"encounter": {
1: "I used to be popular, but now I am all washed up.",
1: "예전엔 인기가 많았지만, 지금은 모두 사라졌다네.",
},
"victory": {
1: "As times change, values also change. I realized that too late.",
1: "시대가 변하면, 가치관도 변하지. 난 그걸 너무 늦게 깨달았어.",
},
},
"guitarist": {
"encounter": {
1: "Get ready to feel the rhythm of defeat as I strum my way to victory!",
1: "패배의 리듬을 느낄 준비는 됐겠지? 내가 승리할 거니까!",
},
"victory": {
1: "Silenced for now, but my melody of resilience will play on.",
1: "지금은 조용하지만, 회복의 멜로디를 연주할 거야.",
},
},
"worker": {
"encounter": {
1: "It bothers me that people always misunderstand me. I'm a lot more pure than everyone thinks.",
1: "사람들이 저를 오해하는 게 신경 쓰여요. 전 생각보다 훨씬 깨끗하답니다.",
},
"victory": {
1: "I really don't want my skin to burn, so I want to stay in the shade while I work.",
1: "피부가 타는 게 싫어서, 일하는 동안엔 그늘에 머물고 싶어요.",
},
},
"worker_female": {
"encounter": {
1: `It bothers me that people always misunderstand me.
$I'm a lot more pure than everyone thinks.`
1: `사람들이 나를 오해하는 게 신경 쓰여.
$나는 .`
},
"victory": {
1: "I really don't want my skin to burn, so I want to stay in the shade while I work."
1: "피부가 타는 게 싫어서, 일하는 동안엔 그늘에 머물고 싶어."
},
"defeat": {
1: "My body and mind aren't necessarily always in sync."
1: "생각처럼 몸이 잘 안따라주네."
}
},
"worker_double": {
"encounter": {
1: "I'll show you we can break you. We've been training in the field!",
1: "너를 무너뜨릴 수 있다는 것을 보여줄게. 우리는 실전 경험이 있거든!",
},
"victory": {
1: "How strange… How could this be… I shouldn't have been outmuscled.",
1: "이상하네… 어떻게 이럴 수 있지… 힘으로 압도할 수 없다니.",
},
},
"hex_maniac": {
@ -697,19 +697,19 @@ export const PGMdialogue: DialogueTranslationEntries = {
},
"falkner": {
"encounter": {
1: "I'll show you the real power of the magnificent bird Pokémon!",
2: "Winds, stay with me!",
3: "Dad! I hope you're watching me battle from above!"
1: "넓은 하늘을 화려하게 나는 새 포켓몬의 진정한 강함을 알게 해주겠다!",
2: "바람이여, 나에게 오라!",
3: "아버지, 내 시합을 하늘에서도 봐줘!"
},
"victory": {
1: "I understand… I'll bow out gracefully.",
2: "A defeat is a defeat. You are strong indeed.",
3: "…Shoot! Yeah, I lost."
1: "알았다… 미련없이 땅에 내려가지.",
2: "패배는 패배니까. 넌 정말 강하군.",
3: "…큭! 그래, 내가 졌다."
},
"defeat": {
1: "Dad! I won with your cherished bird Pokémon…",
2: "Bird Pokémon are the best after all!",
3: "Feels like I'm catching up to my dad!"
1: "아버지! 소중히 여기던 새 포켓몬으로 이겼어…",
2: "언제나 새 포켓몬이 최강이다!",
3: "아버지를 따라 잡은 기분이군!"
}
},
"nessa": {
@ -874,34 +874,34 @@ export const PGMdialogue: DialogueTranslationEntries = {
},
"morty": {
"encounter": {
1: `With a little more, I could see a future in which I meet the legendary Pokémon.
$You're going to help me reach that level!`,
2: `It's said that a rainbow-hued Pokémon will come down to appear before a truly powerful Trainer.
$I believed that tale, so I have secretly trained here all my life. As a result, I can now see what others cannot.
$I see a shadow of the person who will make the Pokémon appear.
$I believe that person is me! You're going to help me reach that level!`,
3: "Whether you choose to believe or not, mystic power does exist.",
4: "You can bear witness to the fruits of my training.",
5: "You must make your soul one with that of Pokémon. Can you do this?",
6: "Say, do you want to be part of my training?"
1: `조금만 더 노력하면, 내가 전설의 포켓몬을 만나는 미래가 보여.
$내가 !`,
2: `커다란 무지개색 포켓몬은 진정한 강함을 가진 트레이너 앞에 나타난다는 이야기가 있어.
$ 믿, . , .
$내겐 .
$ 믿! !`,
3: "네가 믿든 믿지 않든, 불가사의한 힘은 존재해.",
4: "넌 내 수련의 결실을 보게 될 거야.",
5: "포켓몬과 너의 영혼을 하나로 만들어야 해. 가능하겠어?",
6: "저기, 너 내 수행의 일부분이 되고 싶은거지?"
},
"victory": {
1: "I'm not good enough yet…",
2: `I see… Your journey has taken you to far-away places and you have witnessed much more than I.
$I envy you for that`,
3: "How is this possible…",
4: `I don't think our potentials are so different.
$But you seem to have something more than that So be it.`,
5: "Guess I need more training.",
6: "That's a shame."
1: "나는 아직 멀었구나…",
2: `그래… 여행으로 먼 곳을 돌아다니면서, 나보다 훨씬 많은 것을 봐왔구나.
$네가 `,
3: "이게 어떻게 가능한 거지…",
4: `우리의 잠재력은 그렇게 다르진 않은 것 같아.
$그치만 .`,
5: "수련이 더 필요하겠군.",
6: "안타깝게 됐네."
},
"defeat": {
1: "I moved… one step ahead again.",
2: "Fufufu…",
3: "Wh-what?! It can't be! Even that wasn't enough?",
4: "I feel like I just smashed through a really stubborn boulder!",
5: "Ahahahah!",
6: "I knew I would win!"
1: "내가…다시 한 발짝 앞섰어.",
2: "후후훗…",
3: "뭐-뭐야?! 이럴 수가! 그것도 부족해?",
4: "정말 단단한 바위를 뚫고 나온 기분인데!",
5: "아하하하하!",
6: "내가 이길 줄 알았어!"
}
},
"crispin": {
@ -1146,26 +1146,26 @@ export const PGMdialogue: DialogueTranslationEntries = {
},
"lorelei": {
"encounter": {
1: `No one can best me when it comes to icy Pokémon! Freezing moves are powerful!
$Your Pokémon will be at my mercy when they are frozen solid! Hahaha! Are you ready?`,
1: `얼음포켓몬을 내보내면 대적할 사람이 없지! 상대를 얼린다는 건 매우 강력한 공격이야.
$ ! ! ?`,
},
"victory": {
1: "How dare you!"
1: "어떻게 감히!"
},
"defeat": {
1: "There's nothing you can do once you're frozen."
1: "얼어붙은 넌 아무것도 할 수 없어."
}
},
"will": {
"encounter": {
1: `I have trained all around the world, making my psychic Pokémon powerful.
$I can only keep getting better! Losing is not an option!`,
1: `나는 전세계를 돌아다니며, 강한 에스퍼 포켓몬을 만들도록 수행해왔다.
$계속 ! !`,
},
"victory": {
1: "I… I can't… believe it…"
1: "이… 내가… 믿을수 없어…"
},
"defeat": {
1: "That was close. I wonder what it is that you lack."
1: "근소한 차이였다. 네게 부족한 것이 무엇인지 궁금하군."
}
},
"malva": {
@ -1216,35 +1216,35 @@ export const PGMdialogue: DialogueTranslationEntries = {
},
"bruno": {
"encounter": {
1: "We will grind you down with our superior power! Hoo hah!"
1: "우월한 힘으로 너를 부숴주지! 우! 하~앗!"
},
"victory": {
1: "Why? How could I lose?"
1: "하? 어떻게 내가 진 거지?"
},
"defeat": {
1: "You can challenge me all you like, but the results will never change!"
1: "얼마든지 내게 도전 할 수 있지만, 결과는 절대 바뀌지 않을 거다!"
}
},
"bugsy": {
"encounter": {
1: "I'm Bugsy! I never lose when it comes to bug Pokémon!"
1: "내 이름은 호일! 벌레 포켓몬에 대해서라면 누구에게도 지지 않아!"
},
"victory": {
1: "Whoa, amazing! You're an expert on Pokémon!\nMy research isn't complete yet. OK, you win."
1: "우와, 대단해! 넌 포켓몬 전문가구나!\n내 연구는 아직 안 끝났네. 응, 네가 이겼어."
},
"defeat": {
1: "Thanks! Thanks to our battle, I was also able to make progress in my research!"
1: "고마워! 방금 승부 덕분에, 내 연구도 진전을 이룬 것 같아!"
}
},
"koga": {
"encounter": {
1: "Fwahahahaha! Pokémon are not merely about brute force--you shall see soon enough!"
1: "후하하하! 포켓몬은 딘순히 강한 것만이 아니다--곧 알려주지!"
},
"victory": {
1: "Ah! You've proven your worth!"
1: "하! 스스로 증명해냈군!"
},
"defeat": {
1: "Have you learned to fear the techniques of the ninja?"
1: "인술을 피하는 방법을 배워보겠나?"
}
},
"bertha": {
@ -1319,13 +1319,13 @@ export const PGMdialogue: DialogueTranslationEntries = {
},
"agatha": {
"encounter": {
1: "Pokémon are for battling! I'll show you how a real Trainer battles!"
1: "포켓몬은 싸우게 하려고 있는 것이야! 진정한 싸움이라는 것을 보여주겠다!"
},
"victory": {
1: "Oh my! You're something special, child!"
1: "이런! 넌 무언가 특별하구나, 꼬마야!"
},
"defeat": {
1: "Bahaha. That's how a proper battle's done!"
1: "바하하하. 제대로 된 승부는 이렇게 하는거다!"
}
},
"flint": {
@ -1414,33 +1414,33 @@ export const PGMdialogue: DialogueTranslationEntries = {
},
"lance": {
"encounter": {
1: "I've been waiting for you. Allow me to test your skill.",
2: "I thought that you would be able to get this far. Let's get this started."
1: "널 기다리고 있었다. 그 실력을 시험해보겠어.",
2: "여기까지 올 수 있을거라고 생각했다. 슬슬 시작해볼까."
},
"victory": {
1: "You got me. You are magnificent!",
2: "I never expected another trainer to beat me… I'm surprised."
1: "날 따라잡았군. 훌륭해!",
2: "다른 트레이너가 날 이길 거라곤 생각 못했는데… 놀랍군."
},
"defeat": {
1: "That was close. Want to try again?",
2: "It's not that you are weak. Don't let it bother you."
1: "근소하군. 다시 해볼까?",
2: "네가 약해서가 아니다. 신경쓰지 말도록."
}
},
"karen": {
"encounter": {
1: "I am Karen. Would you care for a showdown with my Dark-type Pokémon?",
2: "I am unlike those you've already met.",
3: "You've assembled a charming team. Our battle should be a good one."
1: "난 카렌! 내 악 타입 포켓몬과의 승부를 원하니?",
2: "난 네가 이전에 만났던 트레이너들과는 달라.",
3: "강한 포켓몬, 약한 포켓몬, 그런 건 사람이 멋대로 정하는 것."
},
"victory": {
1: "No! I can't win. How did you become so strong?",
2: "I will not stray from my chosen path.",
3: "The Champion is looking forward to meeting you."
1: "좋아하는 마음이 전해진다면 포켓몬도 답할거야. 그렇게 강해지는 거지",
2: "난 내가 선택한 길을 걸어갈거야.",
3: "챔피언이 너를 기다리고 있어."
},
"defeat": {
1: "That's about what I expected.",
2: "Well, that was relatively entertaining.",
3: "Come visit me anytime."
1: "정말 강한 트레이너라면 좋아하는 포켓몬으로 이길 수 있도록 열심히 해야 해.",
2: "뭐, 비교적 재밌었어.",
3: "언제라도 다시 찾아와, 상대해줄게."
}
},
"milo": {
@ -1507,13 +1507,13 @@ export const PGMdialogue: DialogueTranslationEntries = {
},
"blue": {
"encounter": {
1: "You must be pretty good to get this far."
1: "여기까지 왔다니, 실력이 꽤 봐줄만 할 것 같은데."
},
"victory": {
1: "I've only lost to him and now to you… Him? Hee, hee…"
1: "그 녀석한테만 지는 줄 알았는데… 누구냐고? 하, 하…"
},
"defeat": {
1: "See? My power is what got me here."
1: "봤지? 여기까지 온 내 실력."
}
},
"piers": {
@ -1540,24 +1540,24 @@ export const PGMdialogue: DialogueTranslationEntries = {
},
"jasmine": {
"encounter": {
1: "Oh… Your Pokémon are impressive. I think I will enjoy this."
1: "와… 당신의 포켓몬은 인상적이네요. 재미있을 것 같아요."
},
"victory": {
1: "You are truly strong. I'll have to try much harder, too."
1: "당신은 정말 강하네요. 저도 더 열심히 노력해야겠어요."
},
"defeat": {
1: "I never expected to win."
1: "이길 줄은 몰랐어요."
}
},
"lance_champion": {
"encounter": {
1: "I am still the Champion. I won't hold anything back."
1: "여전히 난 챔피언이다. 더이상 주저할 게 없군."
},
"victory": {
1: "This is the emergence of a new Champion."
1: "새로운 챔피언의 등장이군."
},
"defeat": {
1: "I successfully defended my Championship."
1: "성공적으로 챔피언 자리를 지켜냈다."
}
},
"steven": {
@ -1652,24 +1652,24 @@ export const PGMdialogue: DialogueTranslationEntries = {
},
"whitney": {
"encounter": {
1: "Hey! Don't you think Pokémon are, like, super cute?"
1: "있지! 포켓몬들 말이야, 정말 너무 귀엽지?"
},
"victory": {
1: "Waaah! Waaah! You're so mean!"
1: "흑! 으아앙! 너무해!"
},
"defeat": {
1: "And that's that!"
1: "이걸로 끝!"
}
},
"chuck": {
"encounter": {
1: "Hah! You want to challenge me? Are you brave or just ignorant?"
1: "하! 나에게 도전하겠다고? 용감한 거냐, 아니면 그냥 무모한 거냐?"
},
"victory": {
1: "You're strong! Would you please make me your apprentice?"
1: "자네 강하군! 나를 제자로 삼아주겠나?"
},
"defeat": {
1: "There. Do you realize how much more powerful I am than you?"
1: "자. 내가 자네보다 얼마나 더 강력한지 깨달았겠지?"
}
},
"katy": {
@ -1685,24 +1685,24 @@ export const PGMdialogue: DialogueTranslationEntries = {
},
"pryce": {
"encounter": {
1: "Youth alone does not ensure victory! Experience is what counts."
1: "젊음만으로는 승리를 보장할 수 없다! 중요한 것은 경험이다."
},
"victory": {
1: "Outstanding! That was perfect. Try not to forget what you feel now."
1: "특출하군! 완벽해. 지금 이 느낌을 잊지 말도록."
},
"defeat": {
1: "Just as I envisioned."
1: "내가 예상했던 그대로군."
}
},
"clair": {
"encounter": {
1: "Do you know who I am? And you still dare to challenge me?"
1: "내가 누군지 알지? 그런데도 감히 내게 도전해?"
},
"victory": {
1: "I wonder how far you can get with your skill level. This should be fascinating."
1: "네 실력이 어디까지 올라갈 수 있는지 궁금하네. 아주 흥미진진하겠어."
},
"defeat": {
1: "That's that."
1: "끝이다."
}
},
"maylene": {

View File

@ -25,6 +25,7 @@ import i18next from "#app/plugins/i18n";
import { getModifierTierTextTint } from "#app/ui/text";
import { BattlerTagType } from "#app/data/enums/battler-tag-type.js";
import * as Overrides from "../overrides";
import { MoneyMultiplierModifier } from "./modifier";
const outputModifierData = false;
const useMaxWeightForOutput = false;
@ -631,9 +632,13 @@ export class MoneyRewardModifierType extends ModifierType {
}
getDescription(scene: BattleScene): string {
const moneyAmount = new Utils.IntegerHolder(scene.getWaveMoneyAmount(this.moneyMultiplier));
scene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount);
const formattedMoney = Utils.formatMoney(scene.moneyFormat, moneyAmount.value);
return i18next.t("modifierType:ModifierType.MoneyRewardModifierType.description", {
moneyMultiplier: i18next.t(this.moneyMultiplierDescriptorKey as any),
moneyAmount: scene.getWaveMoneyAmount(this.moneyMultiplier).toLocaleString("en-US"),
moneyAmount: formattedMoney,
});
}
}

View File

@ -29,6 +29,7 @@ import { modifierTypes } from "./modifier/modifier-type";
export const SEED_OVERRIDE: string = "";
export const WEATHER_OVERRIDE: WeatherType = WeatherType.NONE;
export const DOUBLE_BATTLE_OVERRIDE: boolean = false;
export const SINGLE_BATTLE_OVERRIDE: boolean = false;
export const STARTING_WAVE_OVERRIDE: integer = 0;
export const STARTING_BIOME_OVERRIDE: Biome = Biome.TOWN;
export const ARENA_TINT_OVERRIDE: TimeOfDay = null;
@ -110,6 +111,7 @@ export const OPP_MODIFIER_OVERRIDE: Array<ModifierOverride> = [];
export const STARTING_HELD_ITEMS_OVERRIDE: Array<ModifierOverride> = [];
export const OPP_HELD_ITEMS_OVERRIDE: Array<ModifierOverride> = [];
export const NEVER_CRIT_OVERRIDE: boolean = false;
/**
* An array of items by keys as defined in the "modifierTypes" object in the "modifier/modifier-type.ts" file.

View File

@ -149,7 +149,7 @@ export class LoginPhase extends Phase {
export class TitlePhase extends Phase {
private loaded: boolean;
private lastSessionData: SessionSaveData;
private gameMode: GameModes;
public gameMode: GameModes;
constructor(scene: BattleScene) {
super(scene);
@ -527,60 +527,63 @@ export class SelectStarterPhase extends Phase {
return this.end();
}
this.scene.sessionSlotId = slotId;
const party = this.scene.getParty();
const loadPokemonAssets: Promise<void>[] = [];
starters.forEach((starter: Starter, i: integer) => {
if (!i && Overrides.STARTER_SPECIES_OVERRIDE) {
starter.species = getPokemonSpecies(Overrides.STARTER_SPECIES_OVERRIDE as Species);
}
const starterProps = this.scene.gameData.getSpeciesDexAttrProps(starter.species, starter.dexAttr);
let starterFormIndex = Math.min(starterProps.formIndex, Math.max(starter.species.forms.length - 1, 0));
if (!i && Overrides.STARTER_SPECIES_OVERRIDE) {
starterFormIndex = Overrides.STARTER_FORM_OVERRIDE;
}
let starterGender = starter.species.malePercent !== null
? !starterProps.female ? Gender.MALE : Gender.FEMALE
: Gender.GENDERLESS;
if (Overrides.GENDER_OVERRIDE !== null) {
starterGender = Overrides.GENDER_OVERRIDE;
}
const starterIvs = this.scene.gameData.dexData[starter.species.speciesId].ivs.slice(0);
const starterPokemon = this.scene.addPlayerPokemon(starter.species, this.scene.gameMode.getStartingLevel(), starter.abilityIndex, starterFormIndex, starterGender, starterProps.shiny, starterProps.variant, starterIvs, starter.nature);
starterPokemon.tryPopulateMoveset(starter.moveset);
if (starter.passive) {
starterPokemon.passive = true;
}
starterPokemon.luck = this.scene.gameData.getDexAttrLuck(this.scene.gameData.dexData[starter.species.speciesId].caughtAttr);
if (starter.pokerus) {
starterPokemon.pokerus = true;
}
if (this.scene.gameMode.isSplicedOnly) {
starterPokemon.generateFusionSpecies(true);
}
starterPokemon.setVisible(false);
party.push(starterPokemon);
loadPokemonAssets.push(starterPokemon.loadAssets());
});
overrideModifiers(this.scene);
overrideHeldItems(this.scene, party[0]);
Promise.all(loadPokemonAssets).then(() => {
SoundFade.fadeOut(this.scene, this.scene.sound.get("menu"), 500, true);
this.scene.time.delayedCall(500, () => this.scene.playBgm());
if (this.scene.gameMode.isClassic) {
this.scene.gameData.gameStats.classicSessionsPlayed++;
} else {
this.scene.gameData.gameStats.endlessSessionsPlayed++;
}
this.scene.newBattle();
this.scene.arena.init();
this.scene.sessionPlayTime = 0;
this.scene.lastSavePlayTime = 0;
this.end();
});
this.initBattle(starters);
});
}, this.gameMode);
}
initBattle(starters: Starter[]) {
const party = this.scene.getParty();
const loadPokemonAssets: Promise<void>[] = [];
starters.forEach((starter: Starter, i: integer) => {
if (!i && Overrides.STARTER_SPECIES_OVERRIDE) {
starter.species = getPokemonSpecies(Overrides.STARTER_SPECIES_OVERRIDE as Species);
}
const starterProps = this.scene.gameData.getSpeciesDexAttrProps(starter.species, starter.dexAttr);
let starterFormIndex = Math.min(starterProps.formIndex, Math.max(starter.species.forms.length - 1, 0));
if (!i && Overrides.STARTER_SPECIES_OVERRIDE) {
starterFormIndex = Overrides.STARTER_FORM_OVERRIDE;
}
let starterGender = starter.species.malePercent !== null
? !starterProps.female ? Gender.MALE : Gender.FEMALE
: Gender.GENDERLESS;
if (Overrides.GENDER_OVERRIDE !== null) {
starterGender = Overrides.GENDER_OVERRIDE;
}
const starterIvs = this.scene.gameData.dexData[starter.species.speciesId].ivs.slice(0);
const starterPokemon = this.scene.addPlayerPokemon(starter.species, this.scene.gameMode.getStartingLevel(), starter.abilityIndex, starterFormIndex, starterGender, starterProps.shiny, starterProps.variant, starterIvs, starter.nature);
starterPokemon.tryPopulateMoveset(starter.moveset);
if (starter.passive) {
starterPokemon.passive = true;
}
starterPokemon.luck = this.scene.gameData.getDexAttrLuck(this.scene.gameData.dexData[starter.species.speciesId].caughtAttr);
if (starter.pokerus) {
starterPokemon.pokerus = true;
}
if (this.scene.gameMode.isSplicedOnly) {
starterPokemon.generateFusionSpecies(true);
}
starterPokemon.setVisible(false);
party.push(starterPokemon);
loadPokemonAssets.push(starterPokemon.loadAssets());
});
overrideModifiers(this.scene);
overrideHeldItems(this.scene, party[0]);
Promise.all(loadPokemonAssets).then(() => {
SoundFade.fadeOut(this.scene, this.scene.sound.get("menu"), 500, true);
this.scene.time.delayedCall(500, () => this.scene.playBgm());
if (this.scene.gameMode.isClassic) {
this.scene.gameData.gameStats.classicSessionsPlayed++;
} else {
this.scene.gameData.gameStats.endlessSessionsPlayed++;
}
this.scene.newBattle();
this.scene.arena.init();
this.scene.sessionPlayTime = 0;
this.scene.lastSavePlayTime = 0;
this.end();
});
}
}
export class BattlePhase extends Phase {
@ -2710,9 +2713,10 @@ export class MoveEffectPhase extends PokemonPhase {
}
const overridden = new Utils.BooleanHolder(false);
const move = this.move.getMove();
// Assume single target for override
applyMoveAttrs(OverrideMoveEffectAttr, user, this.getTarget(), this.move.getMove(), overridden, this.move.virtual).then(() => {
applyMoveAttrs(OverrideMoveEffectAttr, user, this.getTarget(), move, overridden, this.move.virtual).then(() => {
if (overridden.value) {
return this.end();
@ -2723,8 +2727,8 @@ export class MoveEffectPhase extends PokemonPhase {
if (user.turnData.hitsLeft === undefined) {
const hitCount = new Utils.IntegerHolder(1);
// Assume single target for multi hit
applyMoveAttrs(MultiHitAttr, user, this.getTarget(), this.move.getMove(), hitCount);
if (this.move.getMove() instanceof AttackMove && !this.move.getMove().hasAttr(FixedDamageAttr)) {
applyMoveAttrs(MultiHitAttr, user, this.getTarget(), move, hitCount);
if (move instanceof AttackMove && !move.hasAttr(FixedDamageAttr)) {
this.scene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, hitCount, new Utils.IntegerHolder(0));
}
user.turnData.hitsLeft = user.turnData.hitCount = hitCount.value;
@ -2735,13 +2739,13 @@ export class MoveEffectPhase extends PokemonPhase {
const targetHitChecks = Object.fromEntries(targets.map(p => [ p.getBattlerIndex(), this.hitCheck(p) ]));
const activeTargets = targets.map(t => t.isActive(true));
if (!activeTargets.length || (!this.move.getMove().hasAttr(VariableTargetAttr) && !this.move.getMove().isMultiTarget() && !targetHitChecks[this.targets[0]])) {
if (!activeTargets.length || (!move.hasAttr(VariableTargetAttr) && !move.isMultiTarget() && !targetHitChecks[this.targets[0]])) {
user.turnData.hitCount = 1;
user.turnData.hitsLeft = 1;
if (activeTargets.length) {
this.scene.queueMessage(getPokemonMessage(user, "'s\nattack missed!"));
moveHistoryEntry.result = MoveResult.MISS;
applyMoveAttrs(MissEffectAttr, user, null, this.move.getMove());
applyMoveAttrs(MissEffectAttr, user, null, move);
} else {
this.scene.queueMessage(i18next.t("battle:attackFailed"));
moveHistoryEntry.result = MoveResult.FAIL;
@ -2752,7 +2756,7 @@ export class MoveEffectPhase extends PokemonPhase {
const applyAttrs: Promise<void>[] = [];
// Move animation only needs one target
new MoveAnim(this.move.getMove().id as Moves, user, this.getTarget()?.getBattlerIndex()).play(this.scene, () => {
new MoveAnim(move.id as Moves, user, this.getTarget()?.getBattlerIndex()).play(this.scene, () => {
for (const target of targets) {
if (!targetHitChecks[target.getBattlerIndex()]) {
user.turnData.hitCount = 1;
@ -2761,31 +2765,31 @@ export class MoveEffectPhase extends PokemonPhase {
if (moveHistoryEntry.result === MoveResult.PENDING) {
moveHistoryEntry.result = MoveResult.MISS;
}
applyMoveAttrs(MissEffectAttr, user, null, this.move.getMove());
applyMoveAttrs(MissEffectAttr, user, null, move);
continue;
}
const isProtected = !this.move.getMove().hasFlag(MoveFlags.IGNORE_PROTECT) && target.findTags(t => t instanceof ProtectedTag).find(t => target.lapseTag(t.tagType));
const isProtected = !move.hasFlag(MoveFlags.IGNORE_PROTECT) && target.findTags(t => t instanceof ProtectedTag).find(t => target.lapseTag(t.tagType));
const firstHit = moveHistoryEntry.result !== MoveResult.SUCCESS;
moveHistoryEntry.result = MoveResult.SUCCESS;
const hitResult = !isProtected ? target.apply(user, this.move) : HitResult.NO_EFFECT;
const hitResult = !isProtected ? target.apply(user, move) : HitResult.NO_EFFECT;
this.scene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger);
applyAttrs.push(new Promise(resolve => {
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.PRE_APPLY && (!attr.firstHitOnly || firstHit),
user, target, this.move.getMove()).then(() => {
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.PRE_APPLY && (!attr.firstHitOnly || firstHit),
user, target, move).then(() => {
if (hitResult !== HitResult.FAIL) {
const chargeEffect = !!this.move.getMove().getAttrs(ChargeAttr).find(ca => ca.usedChargeEffect(user, this.getTarget(), this.move.getMove()));
const chargeEffect = !!move.getAttrs(ChargeAttr).find(ca => ca.usedChargeEffect(user, this.getTarget(), move));
// Charge attribute with charge effect takes all effect attributes and applies them to charge stage, so ignore them if this is present
Utils.executeIf(!chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_APPLY
&& (attr as MoveEffectAttr).selfTarget && (!attr.firstHitOnly || firstHit), user, target, this.move.getMove())).then(() => {
Utils.executeIf(!chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_APPLY
&& attr.selfTarget && (!attr.firstHitOnly || firstHit), user, target, move)).then(() => {
if (hitResult !== HitResult.NO_EFFECT) {
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_APPLY
&& !(attr as MoveEffectAttr).selfTarget && (!attr.firstHitOnly || firstHit), user, target, this.move.getMove()).then(() => {
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_APPLY
&& !attr.selfTarget && (!attr.firstHitOnly || firstHit), user, target, move).then(() => {
if (hitResult < HitResult.NO_EFFECT) {
const flinched = new Utils.BooleanHolder(false);
user.scene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched);
@ -2793,15 +2797,15 @@ export class MoveEffectPhase extends PokemonPhase {
target.addTag(BattlerTagType.FLINCHED, undefined, this.move.moveId, user.id);
}
}
Utils.executeIf(!isProtected && !chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.HIT && (!attr.firstHitOnly || firstHit),
user, target, this.move.getMove()).then(() => {
return Utils.executeIf(!target.isFainted() || target.canApplyAbility(), () => applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move, hitResult).then(() => {
if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) {
Utils.executeIf(!isProtected && !chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.HIT && (!attr.firstHitOnly || firstHit),
user, target, move).then(() => {
return Utils.executeIf(!target.isFainted() || target.canApplyAbility(), () => applyPostDefendAbAttrs(PostDefendAbAttr, target, user, move, hitResult).then(() => {
if (!user.isPlayer() && move instanceof AttackMove) {
user.scene.applyShuffledModifiers(this.scene, EnemyAttackStatusEffectChanceModifier, false, target);
}
})).then(() => {
applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move, hitResult).then(() => {
if (this.move.getMove() instanceof AttackMove) {
applyPostAttackAbAttrs(PostAttackAbAttr, user, target, move, hitResult).then(() => {
if (move instanceof AttackMove) {
this.scene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target.getFieldIndex());
}
resolve();
@ -2811,7 +2815,7 @@ export class MoveEffectPhase extends PokemonPhase {
).then(() => resolve());
});
} else {
applyMoveAttrs(NoEffectAttr, user, null, this.move.getMove()).then(() => resolve());
applyMoveAttrs(NoEffectAttr, user, null, move).then(() => resolve());
}
});
} else {
@ -2821,8 +2825,8 @@ export class MoveEffectPhase extends PokemonPhase {
}));
}
// Trigger effect which should only apply one time after all targeted effects have already applied
const postTarget = applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_TARGET,
user, null, this.move.getMove());
const postTarget = applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_TARGET,
user, null, move);
if (applyAttrs.length) { // If there is a pending asynchronous move effect, do this after
applyAttrs[applyAttrs.length - 1]?.then(() => postTarget);
@ -2836,6 +2840,8 @@ export class MoveEffectPhase extends PokemonPhase {
}
end() {
const move = this.move.getMove();
move.type = move.defaultType;
const user = this.getUserPokemon();
if (user) {
if (--user.turnData.hitsLeft >= 1 && this.getTarget()?.isActive()) {
@ -3542,7 +3548,7 @@ export class FaintPhase extends PokemonPhase {
if (pokemon.turnData?.attacksReceived?.length) {
const lastAttack = pokemon.turnData.attacksReceived[0];
applyPostFaintAbAttrs(PostFaintAbAttr, pokemon, this.scene.getPokemonById(lastAttack.sourceId), new PokemonMove(lastAttack.move), lastAttack.result);
applyPostFaintAbAttrs(PostFaintAbAttr, pokemon, this.scene.getPokemonById(lastAttack.sourceId), new PokemonMove(lastAttack.move).getMove(), lastAttack.result);
}
const alivePlayField = this.scene.getField(true);

View File

@ -40,6 +40,10 @@ export class Achv {
return i18next.t(`achv:${this.localizationKey}.name`);
}
getDescription(): string {
return this.description;
}
getIconImage(): string {
return this.iconImage;
}
@ -259,14 +263,12 @@ export const achvs = {
CLASSIC_VICTORY: new Achv("CLASSIC_VICTORY","", "CLASSIC_VICTORY.description", "relic_crown", 150),
};
{
(function() {
const achvKeys = Object.keys(achvs);
achvKeys.forEach((a: string, i: integer) => {
achvs[a].id = a;
if (achvs[a].hasParent) {
achvs[a].parentId = achvKeys[i - 1];
}
});
})();
export function initAchievements() {
const achvKeys = Object.keys(achvs);
achvKeys.forEach((a: string, i: integer) => {
achvs[a].id = a;
if (achvs[a].hasParent) {
achvs[a].parentId = achvKeys[i - 1];
}
});
}

View File

@ -60,13 +60,13 @@ export function getDataTypeKey(dataType: GameDataType, slotId: integer = 0): str
}
}
function encrypt(data: string, bypassLogin: boolean): string {
export function encrypt(data: string, bypassLogin: boolean): string {
return (bypassLogin
? (data: string) => btoa(data)
: (data: string) => AES.encrypt(data, saveKey))(data);
}
function decrypt(data: string, bypassLogin: boolean): string {
export function decrypt(data: string, bypassLogin: boolean): string {
return (bypassLogin
? (data: string) => atob(data)
: (data: string) => AES.decrypt(data, saveKey).toString(enc.Utf8))(data);
@ -493,7 +493,7 @@ export class GameData {
});
}
private parseSystemData(dataStr: string): SystemSaveData {
parseSystemData(dataStr: string): SystemSaveData {
return JSON.parse(dataStr, (k: string, v: any) => {
if (k === "gameStats") {
return new GameStats(v);
@ -512,7 +512,7 @@ export class GameData {
}) as SystemSaveData;
}
private convertSystemDataStr(dataStr: string, shorten: boolean = false): string {
convertSystemDataStr(dataStr: string, shorten: boolean = false): string {
if (!shorten) {
// Account for past key oversight
dataStr = dataStr.replace(/\$pAttr/g, "$pa");

View File

@ -66,7 +66,8 @@ export const SettingKeys = {
Player_Gender: "PLAYER_GENDER",
Master_Volume: "MASTER_VOLUME",
BGM_Volume: "BGM_VOLUME",
SE_Volume: "SE_VOLUME"
SE_Volume: "SE_VOLUME",
Music_Preference: "MUSIC_PREFERENCE"
};
/**
@ -287,6 +288,14 @@ export const Setting: Array<Setting> = [
options: VOLUME_OPTIONS,
default: 10,
type: SettingType.AUDIO
},
{
key: SettingKeys.Music_Preference,
label: "Music Preference",
options: ["Consistent", "Mixed"],
default: 0,
type: SettingType.AUDIO,
requireReload: true
}
];
@ -335,6 +344,9 @@ export function setSetting(scene: BattleScene, setting: string, value: integer):
scene.seVolume = value ? parseInt(Setting[index].options[value]) * 0.01 : 0;
scene.updateSoundVolume();
break;
case SettingKeys.Music_Preference:
scene.musicPreference = value;
break;
case SettingKeys.Damage_Numbers:
scene.damageNumbersMode = value;
break;

View File

@ -83,42 +83,40 @@ export const vouchers: Vouchers = {};
const voucherAchvs: Achv[] = [ achvs.CLASSIC_VICTORY ];
{
(function() {
import("../data/trainer-config").then(tc => {
const trainerConfigs = tc.trainerConfigs;
export function initVouchers() {
import("../data/trainer-config").then(tc => {
const trainerConfigs = tc.trainerConfigs;
for (const achv of voucherAchvs) {
const voucherType = achv.score >= 150
? VoucherType.GOLDEN
: achv.score >= 100
? VoucherType.PREMIUM
: achv.score >= 75
? VoucherType.PLUS
: VoucherType.REGULAR;
vouchers[achv.id] = new Voucher(voucherType, getAchievementDescription(achv.localizationKey));
}
for (const achv of voucherAchvs) {
const voucherType = achv.score >= 150
? VoucherType.GOLDEN
: achv.score >= 100
? VoucherType.PREMIUM
: achv.score >= 75
? VoucherType.PLUS
: VoucherType.REGULAR;
vouchers[achv.id] = new Voucher(voucherType, getAchievementDescription(achv.localizationKey));
}
const bossTrainerTypes = Object.keys(trainerConfigs)
.filter(tt => trainerConfigs[tt].isBoss && trainerConfigs[tt].getDerivedType() !== TrainerType.RIVAL);
const bossTrainerTypes = Object.keys(trainerConfigs)
.filter(tt => trainerConfigs[tt].isBoss && trainerConfigs[tt].getDerivedType() !== TrainerType.RIVAL);
for (const trainerType of bossTrainerTypes) {
const voucherType = trainerConfigs[trainerType].moneyMultiplier < 10
? VoucherType.PLUS
: VoucherType.PREMIUM;
const key = TrainerType[trainerType];
const trainerName = trainerConfigs[trainerType].name;
const trainer = trainerConfigs[trainerType];
const title = trainer.title ? ` (${trainer.title})` : "";
vouchers[key] = new Voucher(
voucherType,
`${i18next.t("voucher:defeatTrainer", { trainerName })} ${title}`,
);
}
const voucherKeys = Object.keys(vouchers);
for (const k of voucherKeys) {
vouchers[k].id = k;
}
});
})();
for (const trainerType of bossTrainerTypes) {
const voucherType = trainerConfigs[trainerType].moneyMultiplier < 10
? VoucherType.PLUS
: VoucherType.PREMIUM;
const key = TrainerType[trainerType];
const trainerName = trainerConfigs[trainerType].name;
const trainer = trainerConfigs[trainerType];
const title = trainer.title ? ` (${trainer.title})` : "";
vouchers[key] = new Voucher(
voucherType,
`${i18next.t("voucher:defeatTrainer", { trainerName })} ${title}`,
);
}
const voucherKeys = Object.keys(vouchers);
for (const k of voucherKeys) {
vouchers[k].id = k;
}
});
}

View File

@ -0,0 +1,83 @@
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 {
CheckSwitchPhase, CommandPhase, MessagePhase,
PostSummonPhase,
ShinySparklePhase,
ShowAbilityPhase,
StatChangePhase,
SummonPhase,
ToggleDoublePositionPhase, TurnInitPhase
} from "#app/phases";
import {Mode} from "#app/ui/ui";
import {BattleStat} from "#app/data/battle-stat";
describe("Abilities - Intimidate", () => {
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, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MIGHTYENA);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.INTIMIDATE);
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.INTIMIDATE);
});
it("INTIMIDATE", async() => {
await game.runToSummon([
Species.MIGHTYENA,
Species.MIGHTYENA,
]);
await game.phaseInterceptor.run(PostSummonPhase);
expect(game.scene.getParty()[0].summonData).not.toBeUndefined();
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(0);
await game.phaseInterceptor.run(ShowAbilityPhase);
await game.phaseInterceptor.run(StatChangePhase);
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
await game.phaseInterceptor.run(SummonPhase);
await game.phaseInterceptor.run(ShinySparklePhase, () => game.isCurrentPhase(ToggleDoublePositionPhase));
await game.phaseInterceptor.run(ToggleDoublePositionPhase);
game.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => {
game.setMode(Mode.MESSAGE);
game.endPhase();
});
await game.phaseInterceptor.run(CheckSwitchPhase);
await game.phaseInterceptor.run(PostSummonPhase);
let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.ATK]).toBe(0);
await game.phaseInterceptor.run(ShowAbilityPhase);
game.scene.moveAnimations = null; // Mandatory to avoid crash
await game.phaseInterceptor.run(StatChangePhase);
battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
await game.phaseInterceptor.run(MessagePhase);
await game.phaseInterceptor.run(TurnInitPhase);
await game.phaseInterceptor.run(CommandPhase);
}, 20000);
});

View File

@ -0,0 +1,65 @@
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 {
MessagePhase,
PostSummonPhase,
ShowAbilityPhase,
StatChangePhase,
ToggleDoublePositionPhase
} from "#app/phases";
import {BattleStat} from "#app/data/battle-stat";
describe("Abilities - Intrepid Sword", () => {
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, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.ZACIAN);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.INTREPID_SWORD);
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.INTREPID_SWORD);
});
it("INTREPID SWORD on player", async() => {
await game.runToSummon([
Species.ZACIAN,
]);
await game.phaseInterceptor.runFrom(PostSummonPhase).to(PostSummonPhase);
expect(game.scene.getParty()[0].summonData).not.toBeUndefined();
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(0);
await game.phaseInterceptor.mustRun(ShowAbilityPhase).catch((error) => expect(error).toBe(ShowAbilityPhase));
await game.phaseInterceptor.mustRun(StatChangePhase).catch((error) => expect(error).toBe(StatChangePhase));
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(1);
}, 20000);
it("INTREPID SWORD on opponent", async() => {
await game.runToSummon([
Species.ZACIAN,
]);
let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.ATK]).toBe(0);
await game.phaseInterceptor.runFrom(PostSummonPhase).to(ToggleDoublePositionPhase);
await game.phaseInterceptor.mustRun(StatChangePhase).catch((error) => expect(error).toBe(StatChangePhase));
await game.phaseInterceptor.whenAboutToRun(MessagePhase);
battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.ATK]).toBe(1);
}, 20000);
});

View File

@ -0,0 +1,67 @@
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,
EnemyCommandPhase,
VictoryPhase
} 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 {BattleStat} from "#app/data/battle-stat";
describe("Abilities - Moxie", () => {
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.AERIAL_ACE;
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.MOXIE);
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.MOXIE);
vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(2000);
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("MOXIE", async() => {
const moveToUse = Moves.AERIAL_ACE;
await game.startBattle([
Species.MIGHTYENA,
Species.MIGHTYENA,
]);
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[Stat.ATK]).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(VictoryPhase);
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(1);
}, 20000);
});

View File

@ -1,9 +0,0 @@
import { MoneyAchv } from "#app/system/achv";
import { describe, expect, it } from "vitest";
describe("check some Achievement related stuff", () => {
it ("should check Achievement creation", () => {
const ach = new MoneyAchv("", "Achievement", 1000, null, 100);
expect(ach.name).toBe("Achievement");
});
});

View File

@ -0,0 +1,274 @@
import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
import {MoneyAchv, Achv, AchvTier, RibbonAchv, DamageAchv, HealAchv, LevelAchv, ModifierAchv, achvs} from "#app/system/achv";
import BattleScene from "../../battle-scene";
import { IntegerHolder, NumberHolder } from "#app/utils.js";
import { TurnHeldItemTransferModifier } from "#app/modifier/modifier.js";
import Phaser from "phaser";
import GameManager from "#app/test/utils/gameManager";
import * as overrides from "#app/overrides";
describe("check some Achievement related stuff", () => {
it ("should check Achievement creation", () => {
const ach = new MoneyAchv("", "Achievement", 1000, null, 100);
expect(ach.name).toBe("Achievement");
});
});
describe("Achv", () => {
let achv: Achv;
beforeEach(() => {
achv = new Achv("", "Test Achievement", "This is a test achievement", "test_icon", 10);
});
it("should have the correct name", () => {
expect(achv.getDescription()).toBe("This is a test achievement");
});
it("should have the correct icon image", () => {
expect(achv.getIconImage()).toBe("test_icon");
});
it("should set the achievement as secret", () => {
achv.setSecret();
expect(achv.secret).toBe(true);
expect(achv.hasParent).toBe(false);
achv.setSecret(true);
expect(achv.secret).toBe(true);
expect(achv.hasParent).toBe(true);
achv.setSecret(false);
expect(achv.secret).toBe(true);
expect(achv.hasParent).toBe(false);
});
it("should return the correct tier based on the score", () => {
const achv1 = new Achv("", "Test Achievement 1", "Test Description", "test_icon", 10);
const achv2 = new Achv("", "Test Achievement 2", "Test Description", "test_icon", 25);
const achv3 = new Achv("", "Test Achievement 3", "Test Description", "test_icon", 50);
const achv4 = new Achv("", "Test Achievement 4", "Test Description", "test_icon", 75);
const achv5 = new Achv("", "Test Achievement 5", "Test Description", "test_icon", 100);
expect(achv1.getTier()).toBe(AchvTier.COMMON);
expect(achv2.getTier()).toBe(AchvTier.GREAT);
expect(achv3.getTier()).toBe(AchvTier.ULTRA);
expect(achv4.getTier()).toBe(AchvTier.ROGUE);
expect(achv5.getTier()).toBe(AchvTier.MASTER);
});
it("should validate the achievement based on the condition function", () => {
const conditionFunc = jest.fn((scene: BattleScene, args: any[]) => args[0] === 10);
const achv = new Achv("", "Test Achievement", "Test Description", "test_icon", 10, conditionFunc);
expect(achv.validate(new BattleScene(), [5])).toBe(false);
expect(achv.validate(new BattleScene(), [10])).toBe(true);
expect(conditionFunc).toHaveBeenCalledTimes(2);
});
});
describe("MoneyAchv", () => {
it("should create an instance of MoneyAchv", () => {
const moneyAchv = new MoneyAchv("", "Test Money Achievement", 10000, "money_icon", 10);
expect(moneyAchv).toBeInstanceOf(MoneyAchv);
expect(moneyAchv instanceof Achv).toBe(true);
});
it("should validate the achievement based on the money amount", () => {
const moneyAchv = new MoneyAchv("", "Test Money Achievement", 10000, "money_icon", 10);
const scene = new BattleScene();
scene.money = 5000;
expect(moneyAchv.validate(scene, [])).toBe(false);
scene.money = 15000;
expect(moneyAchv.validate(scene, [])).toBe(true);
});
});
describe("RibbonAchv", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
let scene: BattleScene;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
vi.spyOn(overrides, "STARTER_SPECIES_OVERRIDE", "get").mockReturnValue(0);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(0);
vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(0);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(0);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([]);
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([]);
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(false);
vi.spyOn(overrides, "DOUBLE_BATTLE_OVERRIDE", "get").mockReturnValue(false);
game = new GameManager(phaserGame);
scene = game.scene;
});
it("should create an instance of RibbonAchv", () => {
const ribbonAchv = new RibbonAchv("", "Test Ribbon Achievement", 10, "ribbon_icon", 10);
expect(ribbonAchv).toBeInstanceOf(RibbonAchv);
expect(ribbonAchv instanceof Achv).toBe(true);
});
it("should validate the achievement based on the ribbon amount", () => {
const ribbonAchv = new RibbonAchv("", "Test Ribbon Achievement", 10, "ribbon_icon", 10);
scene.gameData.gameStats.ribbonsOwned = 5;
expect(ribbonAchv.validate(scene, [])).toBe(false);
scene.gameData.gameStats.ribbonsOwned = 15;
expect(ribbonAchv.validate(scene, [])).toBe(true);
});
});
describe("DamageAchv", () => {
it("should create an instance of DamageAchv", () => {
const damageAchv = new DamageAchv("", "Test Damage Achievement", 250, "damage_icon", 10);
expect(damageAchv).toBeInstanceOf(DamageAchv);
expect(damageAchv instanceof Achv).toBe(true);
});
it("should validate the achievement based on the damage amount", () => {
const damageAchv = new DamageAchv("", "Test Damage Achievement", 250, "damage_icon", 10);
const scene = new BattleScene();
const numberHolder = new NumberHolder(200);
expect(damageAchv.validate(scene, [numberHolder])).toBe(false);
numberHolder.value = 300;
expect(damageAchv.validate(scene, [numberHolder])).toBe(true);
});
});
describe("HealAchv", () => {
it("should create an instance of HealAchv", () => {
const healAchv = new HealAchv("", "Test Heal Achievement", 250, "heal_icon", 10);
expect(healAchv).toBeInstanceOf(HealAchv);
expect(healAchv instanceof Achv).toBe(true);
});
it("should validate the achievement based on the heal amount", () => {
const healAchv = new HealAchv("", "Test Heal Achievement", 250, "heal_icon", 10);
const scene = new BattleScene();
const numberHolder = new NumberHolder(200);
expect(healAchv.validate(scene, [numberHolder])).toBe(false);
numberHolder.value = 300;
expect(healAchv.validate(scene, [numberHolder])).toBe(true);
});
});
describe("LevelAchv", () => {
it("should create an instance of LevelAchv", () => {
const levelAchv = new LevelAchv("", "Test Level Achievement", 100, "level_icon", 10);
expect(levelAchv).toBeInstanceOf(LevelAchv);
expect(levelAchv instanceof Achv).toBe(true);
});
it("should validate the achievement based on the level", () => {
const levelAchv = new LevelAchv("", "Test Level Achievement", 100, "level_icon", 10);
const scene = new BattleScene();
const integerHolder = new IntegerHolder(50);
expect(levelAchv.validate(scene, [integerHolder])).toBe(false);
integerHolder.value = 150;
expect(levelAchv.validate(scene, [integerHolder])).toBe(true);
});
});
describe("ModifierAchv", () => {
it("should create an instance of ModifierAchv", () => {
const modifierAchv = new ModifierAchv("", "Test Modifier Achievement", "Test Description", "modifier_icon", 10, () => true);
expect(modifierAchv).toBeInstanceOf(ModifierAchv);
expect(modifierAchv instanceof Achv).toBe(true);
});
it("should validate the achievement based on the modifier function", () => {
const modifierAchv = new ModifierAchv("", "Test Modifier Achievement", "Test Description", "modifier_icon", 10, () => true);
const scene = new BattleScene();
const modifier = new TurnHeldItemTransferModifier(null, 3, 1);
expect(modifierAchv.validate(scene, [modifier])).toBe(true);
});
});
describe("achvs", () => {
it("should contain the predefined achievements", () => {
expect(achvs._10K_MONEY).toBeInstanceOf(MoneyAchv);
expect(achvs._100K_MONEY).toBeInstanceOf(MoneyAchv);
expect(achvs._1M_MONEY).toBeInstanceOf(MoneyAchv);
expect(achvs._10M_MONEY).toBeInstanceOf(MoneyAchv);
expect(achvs._250_DMG).toBeInstanceOf(DamageAchv);
expect(achvs._1000_DMG).toBeInstanceOf(DamageAchv);
expect(achvs._2500_DMG).toBeInstanceOf(DamageAchv);
expect(achvs._10000_DMG).toBeInstanceOf(DamageAchv);
expect(achvs._250_HEAL).toBeInstanceOf(HealAchv);
expect(achvs._1000_HEAL).toBeInstanceOf(HealAchv);
expect(achvs._2500_HEAL).toBeInstanceOf(HealAchv);
expect(achvs._10000_HEAL).toBeInstanceOf(HealAchv);
expect(achvs.LV_100).toBeInstanceOf(LevelAchv);
expect(achvs.LV_250).toBeInstanceOf(LevelAchv);
expect(achvs.LV_1000).toBeInstanceOf(LevelAchv);
expect(achvs._10_RIBBONS).toBeInstanceOf(RibbonAchv);
expect(achvs._25_RIBBONS).toBeInstanceOf(RibbonAchv);
expect(achvs._50_RIBBONS).toBeInstanceOf(RibbonAchv);
expect(achvs._75_RIBBONS).toBeInstanceOf(RibbonAchv);
expect(achvs._100_RIBBONS).toBeInstanceOf(RibbonAchv);
expect(achvs.TRANSFER_MAX_BATTLE_STAT).toBeInstanceOf(Achv);
expect(achvs.MAX_FRIENDSHIP).toBeInstanceOf(Achv);
expect(achvs.MEGA_EVOLVE).toBeInstanceOf(Achv);
expect(achvs.GIGANTAMAX).toBeInstanceOf(Achv);
expect(achvs.TERASTALLIZE).toBeInstanceOf(Achv);
expect(achvs.STELLAR_TERASTALLIZE).toBeInstanceOf(Achv);
expect(achvs.SPLICE).toBeInstanceOf(Achv);
expect(achvs.MINI_BLACK_HOLE).toBeInstanceOf(ModifierAchv);
expect(achvs.CATCH_MYTHICAL).toBeInstanceOf(Achv);
expect(achvs.CATCH_SUB_LEGENDARY).toBeInstanceOf(Achv);
expect(achvs.CATCH_LEGENDARY).toBeInstanceOf(Achv);
expect(achvs.SEE_SHINY).toBeInstanceOf(Achv);
expect(achvs.SHINY_PARTY).toBeInstanceOf(Achv);
expect(achvs.HATCH_MYTHICAL).toBeInstanceOf(Achv);
expect(achvs.HATCH_SUB_LEGENDARY).toBeInstanceOf(Achv);
expect(achvs.HATCH_LEGENDARY).toBeInstanceOf(Achv);
expect(achvs.HATCH_SHINY).toBeInstanceOf(Achv);
expect(achvs.HIDDEN_ABILITY).toBeInstanceOf(Achv);
expect(achvs.PERFECT_IVS).toBeInstanceOf(Achv);
expect(achvs.CLASSIC_VICTORY).toBeInstanceOf(Achv);
});
it("should initialize the achievements with IDs and parent IDs", () => {
expect(achvs._10K_MONEY.id).toBe("_10K_MONEY");
expect(achvs._10K_MONEY.hasParent).toBe(undefined);
expect(achvs._100K_MONEY.id).toBe("_100K_MONEY");
expect(achvs._100K_MONEY.hasParent).toBe(true);
expect(achvs._100K_MONEY.parentId).toBe("_10K_MONEY");
expect(achvs._1M_MONEY.id).toBe("_1M_MONEY");
expect(achvs._1M_MONEY.hasParent).toBe(true);
expect(achvs._1M_MONEY.parentId).toBe("_100K_MONEY");
expect(achvs._10M_MONEY.id).toBe("_10M_MONEY");
expect(achvs._10M_MONEY.hasParent).toBe(true);
expect(achvs._10M_MONEY.parentId).toBe("_1M_MONEY");
expect(achvs.LV_100.id).toBe("LV_100");
expect(achvs.LV_100.hasParent).toBe(false);
expect(achvs.LV_250.id).toBe("LV_250");
expect(achvs.LV_250.hasParent).toBe(true);
expect(achvs.LV_250.parentId).toBe("LV_100");
expect(achvs.LV_1000.id).toBe("LV_1000");
expect(achvs.LV_1000.hasParent).toBe(true);
expect(achvs.LV_1000.parentId).toBe("LV_250");
});
});

View File

@ -0,0 +1,220 @@
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, EnemyCommandPhase,
TurnStartPhase
} from "#app/phases";
import {Mode} from "#app/ui/ui";
import {getMovePosition} from "#app/test/utils/gameManagerUtils";
import {Moves} from "#app/data/enums/moves";
import {Command} from "#app/ui/command-ui-handler";
import {Stat} from "#app/data/pokemon-stat";
import TargetSelectUiHandler from "#app/ui/target-select-ui-handler";
import {Button} from "#app/enums/buttons";
describe("Battle order", () => {
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, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MEWTWO);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.INSOMNIA);
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.INSOMNIA);
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]);
});
it("opponent faster than player 50 vs 150", async() => {
await game.startBattle([
Species.BULBASAUR,
]);
game.scene.getParty()[0].stats[Stat.SPD] = 50;
game.scene.currentBattle.enemyParty[0].stats[Stat.SPD] = 150;
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, Moves.TACKLE);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
await game.phaseInterceptor.run(EnemyCommandPhase);
await game.phaseInterceptor.whenAboutToRun(TurnStartPhase);
const phase = game.scene.getCurrentPhase() as TurnStartPhase;
const order = phase.getOrder();
expect(order[0]).toBe(2);
expect(order[1]).toBe(0);
}, 20000);
it("Player faster than opponent 150 vs 50", async() => {
await game.startBattle([
Species.BULBASAUR,
]);
game.scene.getParty()[0].stats[Stat.SPD] = 150;
game.scene.currentBattle.enemyParty[0].stats[Stat.SPD] = 50;
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, Moves.TACKLE);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
await game.phaseInterceptor.run(EnemyCommandPhase);
await game.phaseInterceptor.whenAboutToRun(TurnStartPhase);
const phase = game.scene.getCurrentPhase() as TurnStartPhase;
const order = phase.getOrder();
expect(order[0]).toBe(0);
expect(order[1]).toBe(2);
}, 20000);
it("double - both opponents faster than player 50/50 vs 150/150", async() => {
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(false);
vi.spyOn(overrides, "DOUBLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
await game.startBattle([
Species.BULBASAUR,
Species.BLASTOISE,
]);
game.scene.getParty()[0].stats[Stat.SPD] = 50;
game.scene.getParty()[1].stats[Stat.SPD] = 50;
game.scene.currentBattle.enemyParty[0].stats[Stat.SPD] = 150;
game.scene.currentBattle.enemyParty[1].stats[Stat.SPD] = 150;
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, Moves.TACKLE);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
game.onNextPrompt("SelectTargetPhase", Mode.TARGET_SELECT, () => {
const handler = game.scene.ui.getHandler() as TargetSelectUiHandler;
handler.processInput(Button.ACTION);
});
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, Moves.TACKLE);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
game.onNextPrompt("SelectTargetPhase", Mode.TARGET_SELECT, () => {
const handler = game.scene.ui.getHandler() as TargetSelectUiHandler;
handler.processInput(Button.ACTION);
});
await game.phaseInterceptor.runFrom(CommandPhase).to(EnemyCommandPhase);
await game.phaseInterceptor.run(EnemyCommandPhase);
await game.phaseInterceptor.whenAboutToRun(TurnStartPhase);
const phase = game.scene.getCurrentPhase() as TurnStartPhase;
const order = phase.getOrder();
expect(order.indexOf(0)).toBeGreaterThan(order.indexOf(2));
expect(order.indexOf(0)).toBeGreaterThan(order.indexOf(3));
expect(order.indexOf(1)).toBeGreaterThan(order.indexOf(2));
expect(order.indexOf(1)).toBeGreaterThan(order.indexOf(3));
}, 20000);
it("double - speed tie except 1 - 100/100 vs 100/150", async() => {
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(false);
vi.spyOn(overrides, "DOUBLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
await game.startBattle([
Species.BULBASAUR,
Species.BLASTOISE,
]);
game.scene.getParty()[0].stats[Stat.SPD] = 100;
game.scene.getParty()[1].stats[Stat.SPD] = 100;
game.scene.currentBattle.enemyParty[0].stats[Stat.SPD] = 100;
game.scene.currentBattle.enemyParty[1].stats[Stat.SPD] = 150;
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, Moves.TACKLE);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
game.onNextPrompt("SelectTargetPhase", Mode.TARGET_SELECT, () => {
const handler = game.scene.ui.getHandler() as TargetSelectUiHandler;
handler.processInput(Button.ACTION);
});
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, Moves.TACKLE);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
game.onNextPrompt("SelectTargetPhase", Mode.TARGET_SELECT, () => {
const handler = game.scene.ui.getHandler() as TargetSelectUiHandler;
handler.processInput(Button.ACTION);
});
await game.phaseInterceptor.runFrom(CommandPhase).to(EnemyCommandPhase);
await game.phaseInterceptor.run(EnemyCommandPhase);
await game.phaseInterceptor.whenAboutToRun(TurnStartPhase);
const phase = game.scene.getCurrentPhase() as TurnStartPhase;
const order = phase.getOrder();
expect(order.indexOf(3)).toBeLessThan(order.indexOf(0));
expect(order.indexOf(3)).toBeLessThan(order.indexOf(1));
expect(order.indexOf(3)).toBeLessThan(order.indexOf(2));
}, 20000);
it("double - speed tie 100/150 vs 100/150", async() => {
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(false);
vi.spyOn(overrides, "DOUBLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
await game.startBattle([
Species.BULBASAUR,
Species.BLASTOISE,
]);
game.scene.getParty()[0].stats[Stat.SPD] = 100;
game.scene.getParty()[1].stats[Stat.SPD] = 150;
game.scene.currentBattle.enemyParty[0].stats[Stat.SPD] = 100;
game.scene.currentBattle.enemyParty[1].stats[Stat.SPD] = 150;
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, Moves.TACKLE);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
game.onNextPrompt("SelectTargetPhase", Mode.TARGET_SELECT, () => {
const handler = game.scene.ui.getHandler() as TargetSelectUiHandler;
handler.processInput(Button.ACTION);
});
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, Moves.TACKLE);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
game.onNextPrompt("SelectTargetPhase", Mode.TARGET_SELECT, () => {
const handler = game.scene.ui.getHandler() as TargetSelectUiHandler;
handler.processInput(Button.ACTION);
});
await game.phaseInterceptor.runFrom(CommandPhase).to(EnemyCommandPhase);
await game.phaseInterceptor.run(EnemyCommandPhase);
await game.phaseInterceptor.whenAboutToRun(TurnStartPhase);
const phase = game.scene.getCurrentPhase() as TurnStartPhase;
const order = phase.getOrder();
expect(order.indexOf(1)).toBeLessThan(order.indexOf(0));
expect(order.indexOf(1)).toBeLessThan(order.indexOf(2));
expect(order.indexOf(3)).toBeLessThan(order.indexOf(0));
expect(order.indexOf(3)).toBeLessThan(order.indexOf(2));
}, 20000);
});

View File

@ -0,0 +1,248 @@
import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
import {generateStarter, getMovePosition, waitUntil,} from "#app/test/utils/gameManagerUtils";
import {Mode} from "#app/ui/ui";
import {GameModes} from "#app/game-mode";
import {Species} from "#app/data/enums/species";
import * as overrides from "../../overrides";
import {Command} from "#app/ui/command-ui-handler";
import {
BattleEndPhase,
BerryPhase,
CommandPhase,
DamagePhase,
EggLapsePhase,
EncounterPhase,
EnemyCommandPhase,
FaintPhase,
LoginPhase,
MessagePhase,
MoveEffectPhase,
MoveEndPhase,
MovePhase,
PostSummonPhase,
SelectGenderPhase,
SelectModifierPhase,
SelectStarterPhase,
StatChangePhase,
TitlePhase,
TurnEndPhase,
TurnInitPhase,
TurnStartPhase,
VictoryPhase,
} from "#app/phases";
import {Moves} from "#app/data/enums/moves";
import GameManager from "#app/test/utils/gameManager";
import Phaser from "phaser";
import {allSpecies} from "#app/data/pokemon-species";
import {PlayerGender} from "#app/data/enums/player-gender";
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);
});
it("test phase interceptor with remove", async() => {
await game.phaseInterceptor.run(LoginPhase);
await game.phaseInterceptor.run(LoginPhase, () => {
return game.phaseInterceptor.log.includes("LoginPhase");
});
game.scene.gameData.gender = PlayerGender.MALE;
await game.phaseInterceptor.remove(SelectGenderPhase, () => game.isCurrentPhase(TitlePhase));
await game.phaseInterceptor.run(TitlePhase);
await waitUntil(() => game.scene.ui?.getMode() === Mode.TITLE);
expect(game.scene.ui?.getMode()).toBe(Mode.TITLE);
}, 100000);
it("test phase interceptor with prompt", async() => {
await game.phaseInterceptor.run(LoginPhase);
game.onNextPrompt("SelectGenderPhase", Mode.OPTION_SELECT, () => {
game.scene.gameData.gender = PlayerGender.MALE;
game.endPhase();
});
await game.phaseInterceptor.run(SelectGenderPhase);
await game.phaseInterceptor.run(TitlePhase);
await game.waitMode(Mode.TITLE);
expect(game.scene.ui?.getMode()).toBe(Mode.TITLE);
expect(game.scene.gameData.gender).toBe(PlayerGender.MALE);
}, 100000);
it("test phase interceptor with prompt with preparation for a future prompt", async() => {
await game.phaseInterceptor.run(LoginPhase);
game.onNextPrompt("SelectGenderPhase", Mode.OPTION_SELECT, () => {
game.scene.gameData.gender = PlayerGender.MALE;
game.endPhase();
});
game.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => {
game.setMode(Mode.MESSAGE);
game.endPhase();
});
await game.phaseInterceptor.run(SelectGenderPhase);
await game.phaseInterceptor.run(TitlePhase);
await game.waitMode(Mode.TITLE);
expect(game.scene.ui?.getMode()).toBe(Mode.TITLE);
expect(game.scene.gameData.gender).toBe(PlayerGender.MALE);
}, 100000);
it("newGame one-liner", async() => {
await game.startBattle();
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
}, 100000);
it("do attack wave 3 - single battle - regular - OHKO", async() => {
vi.spyOn(overrides, "STARTER_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MEWTWO);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA);
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, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
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, Moves.TACKLE);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
await game.phaseInterceptor.run(EnemyCommandPhase);
await game.phaseInterceptor.run(TurnStartPhase);
await game.phaseInterceptor.run(MovePhase);
await game.phaseInterceptor.run(MessagePhase);
await game.phaseInterceptor.run(MoveEffectPhase);
await game.phaseInterceptor.run(DamagePhase);
await game.phaseInterceptor.run(MessagePhase, () => game.isCurrentPhase(FaintPhase));
await game.phaseInterceptor.run(FaintPhase);
await game.phaseInterceptor.run(MessagePhase);
await game.phaseInterceptor.run(VictoryPhase);
await game.phaseInterceptor.run(MoveEndPhase);
await game.phaseInterceptor.run(MovePhase);
await game.phaseInterceptor.run(BerryPhase);
await game.phaseInterceptor.run(TurnEndPhase);
await game.phaseInterceptor.run(BattleEndPhase);
await game.phaseInterceptor.run(EggLapsePhase);
await game.phaseInterceptor.run(SelectModifierPhase);
expect(game.scene.ui?.getMode()).toBe(Mode.MODIFIER_SELECT);
expect(game.scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
}, 100000);
it("do attack wave 3 - single battle - regular - NO OHKO with opponent using non damage attack", async() => {
vi.spyOn(overrides, "STARTER_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MEWTWO);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA);
vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(5);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(3);
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TAIL_WHIP]);
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
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, Moves.TACKLE);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
await game.phaseInterceptor.run(EnemyCommandPhase);
await game.phaseInterceptor.run(TurnStartPhase);
await game.phaseInterceptor.run(MovePhase);
await game.phaseInterceptor.run(MessagePhase);
await game.phaseInterceptor.run(MoveEffectPhase);
await game.phaseInterceptor.run(DamagePhase);
await game.phaseInterceptor.run(MessagePhase, () => game.isCurrentPhase(MoveEndPhase));
await game.phaseInterceptor.run(MoveEndPhase);
await game.phaseInterceptor.run(MovePhase);
await game.phaseInterceptor.run(MessagePhase, () => game.isCurrentPhase(MoveEffectPhase));
await game.phaseInterceptor.run(MoveEffectPhase);
game.scene.moveAnimations = null; // Mandatory to avoid the crash
await game.phaseInterceptor.run(StatChangePhase, () => game.isCurrentPhase(MessagePhase) || game.isCurrentPhase(MoveEndPhase) || game.isCurrentPhase(DamagePhase));
await game.phaseInterceptor.run(DamagePhase, () => game.isCurrentPhase(MessagePhase) || game.isCurrentPhase(MoveEndPhase));
await game.phaseInterceptor.run(MessagePhase, () => game.isCurrentPhase(MoveEndPhase));
await game.phaseInterceptor.run(MoveEndPhase);
await game.phaseInterceptor.run(BerryPhase);
await game.phaseInterceptor.run(MessagePhase, () => game.isCurrentPhase(TurnEndPhase));
await game.phaseInterceptor.run(TurnEndPhase);
await game.phaseInterceptor.run(TurnInitPhase);
await game.phaseInterceptor.run(CommandPhase);
await waitUntil(() => game.scene.ui?.getMode() === Mode.COMMAND);
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
}, 100000);
it("load 100% data file", async() => {
await game.importData("src/test/utils/saves/everything.prsv");
const caughtCount = Object.keys(game.scene.gameData.dexData).filter((key) => {
const species = game.scene.gameData.dexData[key];
return species.caughtAttr !== 0n;
}).length;
expect(caughtCount).toBe(Object.keys(allSpecies).length);
}, 50000);
it("start battle with selected team", async() => {
await game.startBattle([
Species.CHARIZARD,
Species.CHANSEY,
Species.MEW
]);
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.CHARIZARD);
expect(game.scene.getParty()[1].species.speciesId).toBe(Species.CHANSEY);
expect(game.scene.getParty()[2].species.speciesId).toBe(Species.MEW);
}, 50000);
it("assert next phase", async() => {
await game.phaseInterceptor.run(LoginPhase);
game.onNextPrompt("SelectGenderPhase", Mode.OPTION_SELECT, () => {
game.scene.gameData.gender = PlayerGender.MALE;
game.endPhase();
}, () => game.isCurrentPhase(TitlePhase));
await game.phaseInterceptor.mustRun(SelectGenderPhase).catch((error) => expect(error).toBe(SelectGenderPhase));
await game.phaseInterceptor.mustRun(TitlePhase).catch((error) => expect(error).toBe(TitlePhase));
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const starters = generateStarter(game.scene);
const selectStarterPhase = new SelectStarterPhase(game.scene, GameModes.CLASSIC);
game.scene.pushPhase(new EncounterPhase(game.scene, false));
selectStarterPhase.initBattle(starters);
});
await game.phaseInterceptor.mustRun(EncounterPhase).catch((error) => expect(error).toBe(EncounterPhase));
await game.phaseInterceptor.mustRun(PostSummonPhase).catch((error) => expect(error).toBe(PostSummonPhase));
}, 50000);
it("test remove random battle seed int", async() => {
for (let i=0; i<10; i++) {
const rand = game.scene.randBattleSeedInt(15);
expect(rand).toBe(14);
}
});
});

33
src/test/eggs/egg.test.ts Normal file
View File

@ -0,0 +1,33 @@
import {beforeAll, describe, expect, it} from "vitest";
import BattleScene from "../../battle-scene";
import { getLegendaryGachaSpeciesForTimestamp } from "#app/data/egg.js";
import { Species } from "#app/data/enums/species.js";
import Phaser from "phaser";
describe("getLegendaryGachaSpeciesForTimestamp", () => {
beforeAll(() => {
new Phaser.Game({
type: Phaser.HEADLESS,
});
});
it("should return Arceus for the 10th of June", () => {
const scene = new BattleScene();
const timestamp = new Date(2024, 5, 10, 15, 0, 0, 0).getTime();
const expectedSpecies = Species.ARCEUS;
const result = getLegendaryGachaSpeciesForTimestamp(scene, timestamp);
expect(result).toBe(expectedSpecies);
});
it("should return Arceus for the 10th of July", () => {
const scene = new BattleScene();
const timestamp = new Date(2024, 6, 10, 15, 0, 0, 0).getTime();
const expectedSpecies = Species.ARCEUS;
const result = getLegendaryGachaSpeciesForTimestamp(scene, timestamp);
expect(result).toBe(expectedSpecies);
});
});

View File

@ -0,0 +1,105 @@
import {afterEach, beforeAll, beforeEach, describe, expect, it} from "vitest";
import Phaser from "phaser";
import GameManager from "#app/test/utils/gameManager";
import pad_xbox360 from "#app/configs/inputs/pad_xbox360";
import cfg_keyboard_qwerty from "#app/configs/inputs/cfg_keyboard_qwerty";
import InputsHandler from "#app/test/utils/inputsHandler";
describe("Inputs", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
let originalDocument: Document;
beforeAll(() => {
originalDocument = window.document;
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
Object.defineProperty(window, "document", {
value: originalDocument,
configurable: true,
writable: true,
});
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.inputsHandler = new InputsHandler(game.scene);
});
it("Mobile - test touch holding for 1ms - 1 input", async () => {
await game.inputsHandler.pressTouch("dpadUp", 1);
expect(game.inputsHandler.log.length).toBe(1);
});
it("Mobile - test touch holding for 200ms - 1 input", async () => {
await game.inputsHandler.pressTouch("dpadUp", 200);
expect(game.inputsHandler.log.length).toBe(1);
});
it("Mobile - test touch holding for 300ms - 2 input", async () => {
await game.inputsHandler.pressTouch("dpadUp", 300);
expect(game.inputsHandler.log.length).toBe(2);
});
it("Mobile - test touch holding for 1000ms - 4 input", async () => {
await game.inputsHandler.pressTouch("dpadUp", 1000);
expect(game.inputsHandler.log.length).toBe(4);
});
it("keyboard - test input holding for 1ms - 1 input", async() => {
await game.inputsHandler.pressKeyboardKey(cfg_keyboard_qwerty.deviceMapping.KEY_ARROW_UP, 1);
expect(game.inputsHandler.log.length).toBe(1);
});
it("keyboard - test input holding for 200ms - 1 input", async() => {
await game.inputsHandler.pressKeyboardKey(cfg_keyboard_qwerty.deviceMapping.KEY_ARROW_UP, 200);
expect(game.inputsHandler.log.length).toBe(1);
});
it("keyboard - test input holding for 300ms - 2 input", async() => {
await game.inputsHandler.pressKeyboardKey(cfg_keyboard_qwerty.deviceMapping.KEY_ARROW_UP, 300);
expect(game.inputsHandler.log.length).toBe(2);
});
it("keyboard - test input holding for 1000ms - 4 input", async() => {
await game.inputsHandler.pressKeyboardKey(cfg_keyboard_qwerty.deviceMapping.KEY_ARROW_UP, 1000);
expect(game.inputsHandler.log.length).toBe(4);
});
it("keyboard - test input holding for 2000ms - 8 input", async() => {
await game.inputsHandler.pressKeyboardKey(cfg_keyboard_qwerty.deviceMapping.KEY_ARROW_UP, 2000);
expect(game.inputsHandler.log.length).toBe(8);
});
it("gamepad - test input holding for 1ms - 1 input", async() => {
await game.inputsHandler.pressGamepadButton(pad_xbox360.deviceMapping.RC_S, 1);
expect(game.inputsHandler.log.length).toBe(1);
});
it("gamepad - test input holding for 200ms - 1 input", async() => {
await game.inputsHandler.pressGamepadButton(pad_xbox360.deviceMapping.RC_S, 200);
expect(game.inputsHandler.log.length).toBe(1);
});
it("gamepad - test input holding for 300ms - 2 input", async() => {
await game.inputsHandler.pressGamepadButton(pad_xbox360.deviceMapping.RC_S, 300);
expect(game.inputsHandler.log.length).toBe(2);
});
it("gamepad - test input holding for 1000ms - 4 input", async() => {
await game.inputsHandler.pressGamepadButton(pad_xbox360.deviceMapping.RC_S, 1000);
expect(game.inputsHandler.log.length).toBe(4);
});
it("gamepad - test input holding for 2000ms - 8 input", async() => {
await game.inputsHandler.pressGamepadButton(pad_xbox360.deviceMapping.RC_S, 2000);
expect(game.inputsHandler.log.length).toBe(8);
});
});

View File

@ -0,0 +1,80 @@
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,
EnemyCommandPhase,
MessagePhase,
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 {StatusEffect} from "#app/data/status-effect";
describe("Items - Toxic orb", () => {
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.GROWTH;
const oppMoveToUse = Moves.TACKLE;
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA);
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.INSOMNIA);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.INSOMNIA);
vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(2000);
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([oppMoveToUse, oppMoveToUse, oppMoveToUse, oppMoveToUse]);
vi.spyOn(overrides, "STARTING_HELD_ITEMS_OVERRIDE", "get").mockReturnValue([{
name: "TOXIC_ORB",
}]);
});
it("TOXIC ORB", async() => {
const moveToUse = Moves.GROWTH;
await game.startBattle([
Species.MIGHTYENA,
Species.MIGHTYENA,
]);
expect(game.scene.modifiers[0].type.id).toBe("TOXIC_ORB");
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
// Select Attack
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
// Select Move Growth
const movePosition = getMovePosition(game.scene, 0, moveToUse);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
// will run the 13 phase from enemyCommandPhase to TurnEndPhase
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnEndPhase);
// Toxic orb should trigger here
await game.phaseInterceptor.run(MessagePhase);
const message = game.textInterceptor.getLatestMessage();
expect(message).toContain("was badly poisoned by Toxic Orb");
await game.phaseInterceptor.run(MessagePhase);
const message2 = game.textInterceptor.getLatestMessage();
expect(message2).toContain("is hurt");
expect(message2).toContain("by poison");
expect(game.scene.getParty()[0].status.effect).toBe(StatusEffect.TOXIC);
}, 20000);
});

View File

@ -0,0 +1,42 @@
import {afterEach, beforeAll, describe, expect, it} from "vitest";
import Phaser from "phaser";
import GameManager from "#app/test/utils/gameManager";
import {Species} from "#app/data/enums/species";
import i18next from "i18next";
import {initI18n} from "#app/plugins/i18n";
describe("Lokalization - french", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
initI18n();
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
it("test bulbasaur name english", async () => {
game = new GameManager(phaserGame);
await game.startBattle([
Species.BULBASAUR,
]);
expect(game.scene.getParty()[0].name).toBe("Bulbasaur");
}, 20000);
it("test bulbasaure name french", async () => {
const locale = "fr";
i18next.changeLanguage(locale);
localStorage.setItem("prLang", locale);
game = new GameManager(phaserGame);
await game.startBattle([
Species.BULBASAUR,
]);
expect(game.scene.getParty()[0].name).toBe("Bulbizarre");
}, 20000);
});

View File

@ -0,0 +1,69 @@
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,
EnemyCommandPhase,
TurnInitPhase,
} 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 {BattleStat} from "#app/data/battle-stat";
describe("Moves - Growth", () => {
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.GROWTH;
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.MOXIE);
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.INSOMNIA);
vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(2000);
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("GROWTH", async() => {
const moveToUse = Moves.GROWTH;
await game.startBattle([
Species.MIGHTYENA,
Species.MIGHTYENA,
]);
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[Stat.SPATK]).toBe(0);
const battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.SPATK]).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(TurnInitPhase);
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.SPATK]).toBe(1);
}, 20000);
});

View File

@ -0,0 +1,85 @@
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 {Stat} from "#app/data/pokemon-stat";
describe("Moves - Tackle", () => {
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.TACKLE;
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MAGIKARP);
vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(1);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(97);
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.GROWTH,Moves.GROWTH,Moves.GROWTH,Moves.GROWTH]);
vi.spyOn(overrides, "NEVER_CRIT_OVERRIDE", "get").mockReturnValue(true);
});
it("TACKLE against ghost", async() => {
const moveToUse = Moves.TACKLE;
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.GENGAR);
await game.startBattle([
Species.MIGHTYENA,
]);
const hpOpponent = 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);
const hpLost = hpOpponent - game.scene.currentBattle.enemyParty[0].hp;
expect(hpLost).toBe(0);
}, 20000);
it("TACKLE against not resistant", async() => {
const moveToUse = Moves.TACKLE;
await game.startBattle([
Species.MIGHTYENA,
]);
game.scene.currentBattle.enemyParty[0].stats[Stat.DEF] = 50;
game.scene.getParty()[0].stats[Stat.ATK] = 50;
const hpOpponent = 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);
const hpLost = hpOpponent - game.scene.currentBattle.enemyParty[0].hp;
expect(hpLost).toBeGreaterThan(0);
expect(hpLost).toBe(4);
}, 20000);
});

View File

@ -0,0 +1,66 @@
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,
EnemyCommandPhase,
TurnInitPhase,
} 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 {BattleStat} from "#app/data/battle-stat";
describe("Moves - Tail whip", () => {
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.TAIL_WHIP;
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.INSOMNIA);
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.INSOMNIA);
vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(2000);
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("TAIL_WHIP", async() => {
const moveToUse = Moves.TAIL_WHIP;
await game.startBattle([
Species.MIGHTYENA,
Species.MIGHTYENA,
]);
let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.DEF]).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(TurnInitPhase);
battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.DEF]).toBe(-1);
}, 20000);
});

View File

@ -1,5 +0,0 @@
import Phaser from "phaser";
export default new Phaser.Game({
type: Phaser.HEADLESS,
});

View File

@ -0,0 +1,51 @@
import BattleScene from "#app/battle-scene.js";
import { LoginPhase, TitlePhase, UnavailablePhase } from "#app/phases.js";
import { Mode } from "#app/ui/ui.js";
import {afterEach, beforeAll, beforeEach, describe, expect, it} from "vitest";
import Phaser from "phaser";
import GameManager from "#app/test/utils/gameManager";
describe("Phases", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
let scene: BattleScene;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
scene = game.scene;
});
describe("LoginPhase", () => {
it("should start the login phase", async () => {
const loginPhase = new LoginPhase(scene);
loginPhase.start();
expect(scene.ui.getMode()).to.equal(Mode.MESSAGE);
});
});
describe("TitlePhase", () => {
it("should start the title phase", async () => {
const titlePhase = new TitlePhase(scene);
titlePhase.start();
expect(scene.ui.getMode()).to.equal(Mode.MESSAGE);
});
});
describe("UnavailablePhase", () => {
it("should start the unavailable phase", async () => {
const unavailablePhase = new UnavailablePhase(scene);
unavailablePhase.start();
expect(scene.ui.getMode()).to.equal(Mode.UNAVAILABLE);
});
});
});

View File

@ -1,57 +0,0 @@
import {describe, expect, it} from "vitest";
import {getPokemonSpecies} from "#app/data/pokemon-species";
import {PokemonMove} from "#app/field/pokemon";
import {Species} from "#app/data/enums/species";
import {Moves} from "#app/data/enums/moves";
import PokemonData from "#app/system/pokemon-data";
describe("some tests related to PokemonData and Species", () => {
it("should create a species", () => {
const species = getPokemonSpecies(Species.MEW);
expect(species).not.toBeNull();
});
it("should create a pokemon", () => {
const pokemon = new PokemonData({
species: Species.MEW,
level: 1,
});
expect(pokemon).not.toBeNull();
expect(pokemon.level).toEqual(1);
expect(pokemon.species).toEqual(Species.MEW);
});
it("should generate a moveset", () => {
const pokemon = new PokemonData({
species: Species.MEW,
level: 1,
});
expect(pokemon.moveset[0].moveId).toBe(Moves.TACKLE);
expect(pokemon.moveset[1].moveId).toBe(Moves.GROWL);
});
it("should create an ennemypokemon", () => {
const ennemyPokemon = new PokemonData({
species: Species.MEWTWO,
level: 100,
});
expect(ennemyPokemon).not.toBeNull();
expect(ennemyPokemon.level).toEqual(100);
expect(ennemyPokemon.species).toEqual(Species.MEWTWO);
});
it("should create an ennemypokemon with specified moveset", () => {
const ennemyPokemon = new PokemonData({
species: Species.MEWTWO,
level: 100,
moveset: [
new PokemonMove(Moves.ACID),
new PokemonMove(Moves.ACROBATICS),
new PokemonMove(Moves.FOCUS_ENERGY),
]
});
expect(ennemyPokemon.moveset[0].moveId).toBe(Moves.ACID);
expect(ennemyPokemon.moveset[1].moveId).toBe(Moves.ACROBATICS);
expect(ennemyPokemon.moveset[2].moveId).toBe(Moves.FOCUS_ENERGY);
});
});

View File

@ -5,8 +5,8 @@ import {
getKeyWithKeycode,
getKeyWithSettingName,
} from "#app/configs/inputs/configHandler";
import {MenuManip} from "#app/test/helpers/menuManip";
import {InGameManip} from "#app/test/helpers/inGameManip";
import {MenuManip} from "#app/test/settingMenu/helpers/menuManip";
import {InGameManip} from "#app/test/settingMenu/helpers/inGameManip";
import {Device} from "#app/enums/devices";
import {InterfaceConfig} from "#app/inputs-controller";
import cfg_keyboard_qwerty from "#app/configs/inputs/cfg_keyboard_qwerty";

View File

@ -1,8 +1,8 @@
import {beforeAll, describe, expect, it} from "vitest";
import _masterlist from "../../public/images/pokemon/variant/_masterlist.json";
import _masterlist from "../../../public/images/pokemon/variant/_masterlist.json";
import fs from "fs";
import path from "path";
import {getAppRootDir} from "#app/test/testUtils";
import {getAppRootDir} from "#app/test/sprites/spritesUtils";
const deepCopy = (data) => {
return JSON.parse(JSON.stringify(data));

View File

@ -0,0 +1,612 @@
import {afterEach, beforeAll, beforeEach, describe, expect, it} from "vitest";
import Phaser from "phaser";
import GameManager from "#app/test/utils/gameManager";
import {Species} from "#app/data/enums/species";
import {
EncounterPhase,
SelectStarterPhase,
TitlePhase,
} from "#app/phases";
import {Mode} from "#app/ui/ui";
import {GameModes} from "#app/game-mode";
import StarterSelectUiHandler from "#app/ui/starter-select-ui-handler";
import {Button} from "#app/enums/buttons";
import OptionSelectUiHandler from "#app/ui/settings/option-select-ui-handler";
import SaveSlotSelectUiHandler from "#app/ui/save-slot-select-ui-handler";
import {OptionSelectItem} from "#app/ui/abstact-option-select-ui-handler";
import {Gender} from "#app/data/gender";
import {Nature} from "#app/data/nature";
import {Abilities} from "#app/data/enums/abilities";
import {allSpecies} from "#app/data/pokemon-species";
describe("UI - Starter select", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
});
it("Bulbasaur - shiny - variant 2 male", async() => {
await game.importData("src/test/utils/saves/everything.prsv");
const caughtCount = Object.keys(game.scene.gameData.dexData).filter((key) => {
const species = game.scene.gameData.dexData[key];
return species.caughtAttr !== 0n;
}).length;
expect(caughtCount).toBe(Object.keys(allSpecies).length);
await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const currentPhase = game.scene.getCurrentPhase() as TitlePhase;
currentPhase.gameMode = GameModes.CLASSIC;
currentPhase.end();
});
await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase));
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT);
handler.processInput(Button.ACTION);
});
let options: OptionSelectItem[];
let optionSelectUiHandler: OptionSelectUiHandler;
await new Promise<void>((resolve) => {
game.onNextPrompt("SelectStarterPhase", Mode.OPTION_SELECT, () => {
optionSelectUiHandler = game.scene.ui.getHandler() as OptionSelectUiHandler;
options = optionSelectUiHandler.getOptionsWithScroll();
resolve();
});
});
expect(options.some(option => option.label === "Add to Party")).toBe(true);
expect(options.some(option => option.label === "Toggle IVs")).toBe(true);
expect(options.some(option => option.label === "Manage Moves")).toBe(true);
expect(options.some(option => option.label === "Use Candies")).toBe(true);
expect(options.some(option => option.label === "Cancel")).toBe(true);
optionSelectUiHandler.processInput(Button.ACTION);
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.SUBMIT);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.BULBASAUR);
expect(game.scene.getParty()[0].shiny).toBe(true);
expect(game.scene.getParty()[0].variant).toBe(2);
expect(game.scene.getParty()[0].gender).toBe(Gender.MALE);
}, 20000);
it("Bulbasaur - shiny - variant 2 female hardy overgrow", async() => {
await game.importData("src/test/utils/saves/everything.prsv");
await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const currentPhase = game.scene.getCurrentPhase() as TitlePhase;
currentPhase.gameMode = GameModes.CLASSIC;
currentPhase.end();
});
await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase));
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT);
handler.processInput(Button.CYCLE_GENDER);
handler.processInput(Button.ACTION);
});
let options: OptionSelectItem[];
let optionSelectUiHandler: OptionSelectUiHandler;
await new Promise<void>((resolve) => {
game.onNextPrompt("SelectStarterPhase", Mode.OPTION_SELECT, () => {
optionSelectUiHandler = game.scene.ui.getHandler() as OptionSelectUiHandler;
options = optionSelectUiHandler.getOptionsWithScroll();
resolve();
});
});
expect(options.some(option => option.label === "Add to Party")).toBe(true);
expect(options.some(option => option.label === "Toggle IVs")).toBe(true);
expect(options.some(option => option.label === "Manage Moves")).toBe(true);
expect(options.some(option => option.label === "Use Candies")).toBe(true);
expect(options.some(option => option.label === "Cancel")).toBe(true);
optionSelectUiHandler.processInput(Button.ACTION);
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.SUBMIT);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.BULBASAUR);
expect(game.scene.getParty()[0].shiny).toBe(true);
expect(game.scene.getParty()[0].variant).toBe(2);
expect(game.scene.getParty()[0].nature).toBe(Nature.HARDY);
expect(game.scene.getParty()[0].getAbility().id).toBe(Abilities.OVERGROW);
}, 20000);
it("Bulbasaur - shiny - variant 2 female lonely cholorophyl", async() => {
await game.importData("src/test/utils/saves/everything.prsv");
await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const currentPhase = game.scene.getCurrentPhase() as TitlePhase;
currentPhase.gameMode = GameModes.CLASSIC;
currentPhase.end();
});
await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase));
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT);
handler.processInput(Button.CYCLE_GENDER);
handler.processInput(Button.CYCLE_NATURE);
handler.processInput(Button.CYCLE_ABILITY);
handler.processInput(Button.ACTION);
});
let options: OptionSelectItem[];
let optionSelectUiHandler: OptionSelectUiHandler;
await new Promise<void>((resolve) => {
game.onNextPrompt("SelectStarterPhase", Mode.OPTION_SELECT, () => {
optionSelectUiHandler = game.scene.ui.getHandler() as OptionSelectUiHandler;
options = optionSelectUiHandler.getOptionsWithScroll();
resolve();
});
});
expect(options.some(option => option.label === "Add to Party")).toBe(true);
expect(options.some(option => option.label === "Toggle IVs")).toBe(true);
expect(options.some(option => option.label === "Manage Moves")).toBe(true);
expect(options.some(option => option.label === "Use Candies")).toBe(true);
expect(options.some(option => option.label === "Cancel")).toBe(true);
optionSelectUiHandler.processInput(Button.ACTION);
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.SUBMIT);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.BULBASAUR);
expect(game.scene.getParty()[0].shiny).toBe(true);
expect(game.scene.getParty()[0].variant).toBe(2);
expect(game.scene.getParty()[0].nature).toBe(Nature.LONELY);
expect(game.scene.getParty()[0].getAbility().id).toBe(Abilities.CHLOROPHYLL);
}, 20000);
it("Bulbasaur - shiny - variant 2 female", async() => {
await game.importData("src/test/utils/saves/everything.prsv");
await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const currentPhase = game.scene.getCurrentPhase() as TitlePhase;
currentPhase.gameMode = GameModes.CLASSIC;
currentPhase.end();
});
await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase));
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT);
handler.processInput(Button.CYCLE_GENDER);
handler.processInput(Button.ACTION);
});
let options: OptionSelectItem[];
let optionSelectUiHandler: OptionSelectUiHandler;
await new Promise<void>((resolve) => {
game.onNextPrompt("SelectStarterPhase", Mode.OPTION_SELECT, () => {
optionSelectUiHandler = game.scene.ui.getHandler() as OptionSelectUiHandler;
options = optionSelectUiHandler.getOptionsWithScroll();
resolve();
});
});
expect(options.some(option => option.label === "Add to Party")).toBe(true);
expect(options.some(option => option.label === "Toggle IVs")).toBe(true);
expect(options.some(option => option.label === "Manage Moves")).toBe(true);
expect(options.some(option => option.label === "Use Candies")).toBe(true);
expect(options.some(option => option.label === "Cancel")).toBe(true);
optionSelectUiHandler.processInput(Button.ACTION);
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.SUBMIT);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.BULBASAUR);
expect(game.scene.getParty()[0].shiny).toBe(true);
expect(game.scene.getParty()[0].variant).toBe(2);
expect(game.scene.getParty()[0].gender).toBe(Gender.FEMALE);
}, 20000);
it("Bulbasaur - not shiny", async() => {
await game.importData("src/test/utils/saves/everything.prsv");
await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const currentPhase = game.scene.getCurrentPhase() as TitlePhase;
currentPhase.gameMode = GameModes.CLASSIC;
currentPhase.end();
});
await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase));
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT);
handler.processInput(Button.CYCLE_SHINY);
handler.processInput(Button.ACTION);
});
let options: OptionSelectItem[];
let optionSelectUiHandler: OptionSelectUiHandler;
await new Promise<void>((resolve) => {
game.onNextPrompt("SelectStarterPhase", Mode.OPTION_SELECT, () => {
optionSelectUiHandler = game.scene.ui.getHandler() as OptionSelectUiHandler;
options = optionSelectUiHandler.getOptionsWithScroll();
resolve();
});
});
expect(options.some(option => option.label === "Add to Party")).toBe(true);
expect(options.some(option => option.label === "Toggle IVs")).toBe(true);
expect(options.some(option => option.label === "Manage Moves")).toBe(true);
expect(options.some(option => option.label === "Use Candies")).toBe(true);
expect(options.some(option => option.label === "Cancel")).toBe(true);
optionSelectUiHandler.processInput(Button.ACTION);
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.SUBMIT);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.BULBASAUR);
expect(game.scene.getParty()[0].shiny).toBe(false);
expect(game.scene.getParty()[0].variant).toBe(0);
}, 20000);
it("Bulbasaur - shiny - variant 0", async() => {
await game.importData("src/test/utils/saves/everything.prsv");
await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const currentPhase = game.scene.getCurrentPhase() as TitlePhase;
currentPhase.gameMode = GameModes.CLASSIC;
currentPhase.end();
});
await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase));
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT);
handler.processInput(Button.V);
handler.processInput(Button.ACTION);
});
let options: OptionSelectItem[];
let optionSelectUiHandler: OptionSelectUiHandler;
await new Promise<void>((resolve) => {
game.onNextPrompt("SelectStarterPhase", Mode.OPTION_SELECT, () => {
optionSelectUiHandler = game.scene.ui.getHandler() as OptionSelectUiHandler;
options = optionSelectUiHandler.getOptionsWithScroll();
resolve();
});
});
expect(options.some(option => option.label === "Add to Party")).toBe(true);
expect(options.some(option => option.label === "Toggle IVs")).toBe(true);
expect(options.some(option => option.label === "Manage Moves")).toBe(true);
expect(options.some(option => option.label === "Use Candies")).toBe(true);
expect(options.some(option => option.label === "Cancel")).toBe(true);
optionSelectUiHandler.processInput(Button.ACTION);
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.SUBMIT);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.BULBASAUR);
expect(game.scene.getParty()[0].shiny).toBe(true);
expect(game.scene.getParty()[0].variant).toBe(0);
}, 20000);
it("Bulbasaur - shiny - variant 1", async() => {
await game.importData("src/test/utils/saves/everything.prsv");
await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const currentPhase = game.scene.getCurrentPhase() as TitlePhase;
currentPhase.gameMode = GameModes.CLASSIC;
currentPhase.end();
});
await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase));
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT);
handler.processInput(Button.V);
handler.processInput(Button.V);
handler.processInput(Button.ACTION);
});
let options: OptionSelectItem[];
let optionSelectUiHandler: OptionSelectUiHandler;
await new Promise<void>((resolve) => {
game.onNextPrompt("SelectStarterPhase", Mode.OPTION_SELECT, () => {
optionSelectUiHandler = game.scene.ui.getHandler() as OptionSelectUiHandler;
options = optionSelectUiHandler.getOptionsWithScroll();
resolve();
});
});
expect(options.some(option => option.label === "Add to Party")).toBe(true);
expect(options.some(option => option.label === "Toggle IVs")).toBe(true);
expect(options.some(option => option.label === "Manage Moves")).toBe(true);
expect(options.some(option => option.label === "Use Candies")).toBe(true);
expect(options.some(option => option.label === "Cancel")).toBe(true);
optionSelectUiHandler.processInput(Button.ACTION);
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.SUBMIT);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.BULBASAUR);
expect(game.scene.getParty()[0].shiny).toBe(true);
expect(game.scene.getParty()[0].variant).toBe(1);
}, 20000);
it("Bulbasaur - shiny - variant 1", async() => {
await game.importData("src/test/utils/saves/everything.prsv");
await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const currentPhase = game.scene.getCurrentPhase() as TitlePhase;
currentPhase.gameMode = GameModes.CLASSIC;
currentPhase.end();
});
await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase));
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT);
handler.processInput(Button.V);
handler.processInput(Button.V);
handler.processInput(Button.V);
handler.processInput(Button.ACTION);
});
let options: OptionSelectItem[];
let optionSelectUiHandler: OptionSelectUiHandler;
await new Promise<void>((resolve) => {
game.onNextPrompt("SelectStarterPhase", Mode.OPTION_SELECT, () => {
optionSelectUiHandler = game.scene.ui.getHandler() as OptionSelectUiHandler;
options = optionSelectUiHandler.getOptionsWithScroll();
resolve();
});
});
expect(options.some(option => option.label === "Add to Party")).toBe(true);
expect(options.some(option => option.label === "Toggle IVs")).toBe(true);
expect(options.some(option => option.label === "Manage Moves")).toBe(true);
expect(options.some(option => option.label === "Use Candies")).toBe(true);
expect(options.some(option => option.label === "Cancel")).toBe(true);
optionSelectUiHandler.processInput(Button.ACTION);
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.SUBMIT);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.BULBASAUR);
expect(game.scene.getParty()[0].shiny).toBe(true);
expect(game.scene.getParty()[0].variant).toBe(2);
}, 20000);
it("Check if first pokemon in party is caterpie from gen 1 and 1rd row, 3rd column ", async() => {
await game.importData("src/test/utils/saves/everything.prsv");
await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const currentPhase = game.scene.getCurrentPhase() as TitlePhase;
currentPhase.gameMode = GameModes.CLASSIC;
currentPhase.end();
});
await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase));
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT);
handler.processInput(Button.RIGHT);
handler.processInput(Button.RIGHT);
handler.processInput(Button.RIGHT);
handler.processInput(Button.ACTION);
});
let options: OptionSelectItem[];
let optionSelectUiHandler: OptionSelectUiHandler;
await new Promise<void>((resolve) => {
game.onNextPrompt("SelectStarterPhase", Mode.OPTION_SELECT, () => {
optionSelectUiHandler = game.scene.ui.getHandler() as OptionSelectUiHandler;
options = optionSelectUiHandler.getOptionsWithScroll();
resolve();
});
});
expect(options.some(option => option.label === "Add to Party")).toBe(true);
expect(options.some(option => option.label === "Toggle IVs")).toBe(true);
expect(options.some(option => option.label === "Manage Moves")).toBe(true);
expect(options.some(option => option.label === "Use Candies")).toBe(true);
expect(options.some(option => option.label === "Cancel")).toBe(true);
optionSelectUiHandler.processInput(Button.ACTION);
let starterSelectUiHandler: StarterSelectUiHandler;
await new Promise<void>((resolve) => {
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
starterSelectUiHandler = game.scene.ui.getHandler() as StarterSelectUiHandler;
starterSelectUiHandler.processInput(Button.SUBMIT);
resolve();
});
});
expect(starterSelectUiHandler.starterGens[0]).toBe(0);
expect(starterSelectUiHandler.starterCursors[0]).toBe(3);
expect(starterSelectUiHandler.cursorObj.x).toBe(132 + 4 * 18);
expect(starterSelectUiHandler.cursorObj.y).toBe(10);
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.CATERPIE);
}, 20000);
it("Check if first pokemon in party is nidoran_m from gen 1 and 2nd row, 4th column (cursor (9+4)-1) ", async() => {
await game.importData("src/test/utils/saves/everything.prsv");
await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const currentPhase = game.scene.getCurrentPhase() as TitlePhase;
currentPhase.gameMode = GameModes.CLASSIC;
currentPhase.end();
});
await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase));
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT);
handler.processInput(Button.RIGHT);
handler.processInput(Button.RIGHT);
handler.processInput(Button.RIGHT);
handler.processInput(Button.DOWN);
handler.processInput(Button.ACTION);
});
let options: OptionSelectItem[];
let optionSelectUiHandler: OptionSelectUiHandler;
await new Promise<void>((resolve) => {
game.onNextPrompt("SelectStarterPhase", Mode.OPTION_SELECT, () => {
optionSelectUiHandler = game.scene.ui.getHandler() as OptionSelectUiHandler;
options = optionSelectUiHandler.getOptionsWithScroll();
resolve();
});
});
expect(options.some(option => option.label === "Add to Party")).toBe(true);
expect(options.some(option => option.label === "Toggle IVs")).toBe(true);
expect(options.some(option => option.label === "Manage Moves")).toBe(true);
expect(options.some(option => option.label === "Use Candies")).toBe(true);
expect(options.some(option => option.label === "Cancel")).toBe(true);
optionSelectUiHandler.processInput(Button.ACTION);
let starterSelectUiHandler: StarterSelectUiHandler;
await new Promise<void>((resolve) => {
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
starterSelectUiHandler = game.scene.ui.getHandler() as StarterSelectUiHandler;
starterSelectUiHandler.processInput(Button.SUBMIT);
resolve();
});
});
expect(starterSelectUiHandler.starterGens[0]).toBe(0);
expect(starterSelectUiHandler.starterCursors[0]).toBe(12);
expect(starterSelectUiHandler.cursorObj.x).toBe(132 + 4 * 18);
expect(starterSelectUiHandler.cursorObj.y).toBe(28);
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.NIDORAN_M);
}, 20000);
});

View File

@ -0,0 +1,16 @@
export default class TextInterceptor {
private scene;
private logs = [];
constructor(scene) {
this.scene = scene;
scene.messageWrapper = this;
}
showText(text: string, delay?: integer, callback?: Function, callbackDelay?: integer, prompt?: boolean, promptDelay?: integer): void {
this.logs.push(text);
}
getLatestMessage(): string {
return this.logs[this.logs.length - 1];
}
}

View File

@ -0,0 +1,50 @@
<!DOCTYPE html><body>
<div id="touchControls">
<div id="dpad" class="unselectable">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 72 72">
<path id="dpadUp" data-key="UP" d="M48,5.8C48,2.5,45.4,0,42,0H29.9C26.6,0,24,2.4,24,5.8V24h24V5.8z" />
<path id="dpadRight" data-key="RIGHT" d="M66.2,24H48v24h18.2c3.3,0,5.8-2.7,5.8-6V29.9C72,26.5,69.5,24,66.2,24z" />
<path id="dpadDown" data-key="DOWN" d="M24,66.3c0,3.3,2.6,5.7,5.9,5.7H42c3.3,0,6-2.4,6-5.7V48H24V66.3z" />
<path id="dpadLeft" data-key="LEFT" d="M5.7,24C2.4,24,0,26.5,0,29.9V42c0,3.3,2.3,6,5.7,6H24V24H5.7z" />
<rect id="dpadCenter" x="24" y="24" width="24" height="24" />
</svg>
</div>
<div id="apad" class="unselectable">
<div id="apadAction" class="apadCircBtn apadBtn" data-key="ACTION">
<text id="apadLabelAction" class="apadLabel">A</text>
</div>
<div id="apadCancel" class="apadCircBtn apadBtn" data-key="CANCEL">
<text id="apadLabelCancel" class="apadLabel">B</text>
</div>
<div class="apadBtnContainer apadRectBtnContainer">
<div id="apadCycleShiny" class="apadSqBtn apadBtn" data-key="CYCLE_SHINY">
<text class="apadLabel apadLabelSmall">R</text>
</div>
<div id="apadCycleVariant" class="apadSqBtn apadBtn" data-key="CYCLE_VARIANT">
<text class="apadLabel apadLabelSmall">V</text>
</div>
<div id="apadStats" class="apadRectBtn apadBtn" data-key="STATS">
<text class="apadLabel apadLabelSmall">C</text>
</div>
<div id="apadMenu" class="apadRectBtn apadBtn" data-key="MENU">
<text class="apadLabel apadLabelSmall">Menu</text>
</div>
</div>
<div class="apadBtnContainer apadSqBtnContainer">
<div id="apadCycleForm" class="apadSqBtn apadBtn" data-key="CYCLE_FORM">
<text class="apadLabel apadLabelSmall">F</text>
</div>
<div id="apadCycleGender" class="apadSqBtn apadBtn" data-key="CYCLE_GENDER">
<text class="apadLabel apadLabelSmall">G</text>
</div>
<div id="apadCycleAbility" class="apadSqBtn apadBtn" data-key="CYCLE_ABILITY">
<text class="apadLabel apadLabelSmall">E</text>
</div>
<div id="apadCycleNature" class="apadSqBtn apadBtn" data-key="CYCLE_NATURE">
<text class="apadLabel apadLabelSmall">N</text>
</div>
</div>
</div>
</div>
</body>

View File

@ -0,0 +1,223 @@
import GameWrapper from "#app/test/utils/gameWrapper";
import {Mode} from "#app/ui/ui";
import {generateStarter, waitUntil} from "#app/test/utils/gameManagerUtils";
import {
CheckSwitchPhase,
CommandPhase,
EncounterPhase,
LoginPhase,
PostSummonPhase,
SelectGenderPhase,
SelectStarterPhase,
SummonPhase,
TitlePhase,
ToggleDoublePositionPhase,
} 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 {expect} from "vitest";
import {GameModes} from "#app/game-mode";
import fs from "fs";
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";
import {GameDataType} from "#app/data/enums/game-data-type";
import InputsHandler from "#app/test/utils/inputsHandler";
import {ExpNotification} from "#app/enums/exp-notification";
/**
* Class to manage the game state and transitions between phases.
*/
export default class GameManager {
public gameWrapper: GameWrapper;
public scene: BattleScene;
public phaseInterceptor: PhaseInterceptor;
public textInterceptor: TextInterceptor;
public inputsHandler: InputsHandler;
/**
* Creates an instance of GameManager.
* @param phaserGame - The Phaser game instance.
* @param bypassLogin - Whether to bypass the login phase.
*/
constructor(phaserGame: Phaser.Game, bypassLogin: boolean = true) {
BattleScene.prototype.randBattleSeedInt = (arg) => arg-1;
this.gameWrapper = new GameWrapper(phaserGame, bypassLogin);
this.scene = new BattleScene();
this.phaseInterceptor = new PhaseInterceptor(this.scene);
this.textInterceptor = new TextInterceptor(this.scene);
this.gameWrapper.setScene(this.scene);
}
/**
* Sets the game mode.
* @param mode - The mode to set.
*/
setMode(mode: Mode) {
this.scene.ui?.setMode(mode);
}
/**
* Waits until the specified mode is set.
* @param mode - The mode to wait for.
* @returns A promise that resolves when the mode is set.
*/
waitMode(mode: Mode): Promise<void> {
return new Promise(async (resolve) => {
await waitUntil(() => this.scene.ui?.getMode() === mode);
return resolve();
});
}
/**
* Ends the current phase.
*/
endPhase() {
this.scene.getCurrentPhase().end();
}
/**
* Adds an action to be executed on the next prompt.
* @param phaseTarget - The target phase.
* @param mode - The mode to wait for.
* @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);
}
/**
* 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) => {
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;
resolve();
});
}
/**
* Runs the game to the summon phase.
* @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) => {
await this.runToTitle();
this.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const starters = generateStarter(this.scene, species);
const selectStarterPhase = new SelectStarterPhase(this.scene, GameModes.CLASSIC);
this.scene.pushPhase(new EncounterPhase(this.scene, false));
selectStarterPhase.initBattle(starters);
});
await this.phaseInterceptor.run(EncounterPhase);
resolve();
});
}
/**
* Starts 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) => {
await this.runToSummon(species);
await this.phaseInterceptor.runFrom(PostSummonPhase).to(ToggleDoublePositionPhase);
await this.phaseInterceptor.run(SummonPhase, () => this.isCurrentPhase(CheckSwitchPhase) || this.isCurrentPhase(PostSummonPhase));
this.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => {
this.setMode(Mode.MESSAGE);
this.endPhase();
}, () => this.isCurrentPhase(PostSummonPhase));
this.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => {
this.setMode(Mode.MESSAGE);
this.endPhase();
}, () => this.isCurrentPhase(PostSummonPhase));
await this.phaseInterceptor.run(CheckSwitchPhase, () => this.isCurrentPhase(PostSummonPhase));
await this.phaseInterceptor.run(CheckSwitchPhase, () => this.isCurrentPhase(PostSummonPhase));
await this.phaseInterceptor.runFrom(PostSummonPhase).to(CommandPhase);
await waitUntil(() => this.scene.ui?.getMode() === Mode.COMMAND);
console.log("==================[New Turn]==================");
expect(this.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(this.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
return resolve();
});
}
/**
* Checks if the player has won the battle.
* @returns True if the player has won, otherwise false.
*/
isVictory() {
return this.scene.currentBattle.enemyParty.every(pokemon => pokemon.isFainted());
}
/**
* Checks if the current phase matches the target phase.
* @param phaseTarget - The target phase.
* @returns True if the current phase matches the target phase, otherwise false.
*/
isCurrentPhase(phaseTarget) {
const targetName = typeof phaseTarget === "string" ? phaseTarget : phaseTarget.name;
return this.scene.getCurrentPhase().constructor.name === targetName;
}
/**
* Checks if the current mode matches the target mode.
* @param mode - The target mode.
* @returns True if the current mode matches the target mode, otherwise false.
*/
isCurrentMode(mode: Mode) {
return this.scene.ui?.getMode() === mode;
}
/**
* Exports the save data to import it in a test game.
* @returns A promise that resolves with the exported save data.
*/
exportSaveToTest(): Promise<string> {
return new Promise(async (resolve) => {
await this.scene.gameData.saveAll(this.scene, true, true, true, true);
this.scene.reset(true);
await waitUntil(() => this.scene.ui?.getMode() === Mode.TITLE);
await this.scene.gameData.tryExportData(GameDataType.SESSION, 0);
await waitUntil(() => localStorage.hasOwnProperty("toExport"));
return resolve(localStorage.getItem("toExport"));
});
}
/**
* Imports game data from a file.
* @param path - The path to the data file.
* @returns A promise that resolves with a tuple containing a boolean indicating success and an integer status code.
*/
async importData(path): Promise<[boolean, integer]> {
const saveKey = "x0i2O7WRiANTqPmZ";
const dataRaw = fs.readFileSync(path, {encoding: "utf8", flag: "r"});
let dataStr = AES.decrypt(dataRaw, saveKey).toString(enc.Utf8);
dataStr = this.scene.gameData.convertSystemDataStr(dataStr);
const systemData = this.scene.gameData.parseSystemData(dataStr);
const valid = !!systemData.dexData && !!systemData.timestamp;
if (valid) {
await updateUserInfo();
await this.scene.gameData.initSystem(dataStr);
}
return updateUserInfo();
}
}

View File

@ -0,0 +1,87 @@
// Function to convert Blob to string
import {getDailyRunStarters} from "#app/data/daily-run";
import {Gender} from "#app/data/gender";
import {Species} from "#app/data/enums/species";
import {Starter} from "#app/ui/starter-select-ui-handler";
import {GameModes, gameModes} from "#app/game-mode";
import {getPokemonSpecies, getPokemonSpeciesForm} from "#app/data/pokemon-species";
import {PlayerPokemon} from "#app/field/pokemon";
export function blobToString(blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => {
resolve(reader.result);
};
reader.onerror = () => {
reject(new Error("Error reading Blob as string"));
};
reader.readAsText(blob);
});
}
export function holdOn(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
export function generateStarter(scene, species?: Species[]) {
const seed = "test";
const starters = getTestRunStarters(scene, seed, species);
const startingLevel = scene.gameMode.getStartingLevel();
for (const starter of starters) {
const starterProps = scene.gameData.getSpeciesDexAttrProps(starter.species, starter.dexAttr);
const starterFormIndex = Math.min(starterProps.formIndex, Math.max(starter.species.forms.length - 1, 0));
const starterGender = starter.species.malePercent !== null
? !starterProps.female ? Gender.MALE : Gender.FEMALE
: Gender.GENDERLESS;
const starterPokemon = scene.addPlayerPokemon(starter.species, startingLevel, starter.abilityIndex, starterFormIndex, starterGender, starterProps.shiny, starterProps.variant, undefined, starter.nature);
starter.moveset = starterPokemon.moveset;
}
return starters;
}
function getTestRunStarters(scene, seed, species) {
if (!species) {
return getDailyRunStarters(scene, seed);
}
const starters: Starter[] = [];
const startingLevel = gameModes[GameModes.CLASSIC].getStartingLevel();
for (const specie of species) {
const starterSpeciesForm = getPokemonSpeciesForm(specie, 0);
const starterSpecies = getPokemonSpecies(starterSpeciesForm.speciesId);
const pokemon = new PlayerPokemon(scene, starterSpecies, startingLevel, undefined, 0, undefined, undefined, undefined, undefined, undefined, undefined);
const starter: Starter = {
species: starterSpecies,
dexAttr: pokemon.getDexAttr(),
abilityIndex: pokemon.abilityIndex,
passive: false,
nature: pokemon.getNature(),
pokerus: pokemon.pokerus
};
starters.push(starter);
}
return starters;
}
export function waitUntil(truth) {
return new Promise(resolve => {
const interval = setInterval(() => {
if (truth()) {
clearInterval(interval);
resolve(true);
}
}, 1000);
});
}
export function getMovePosition(scene, pokemonIndex, moveIndex) {
const playerPokemon = scene.getPlayerField()[pokemonIndex];
const moveSet = playerPokemon.getMoveset();
const index = moveSet.findIndex((move) => move.moveId === moveIndex);
return index;
}

View File

@ -0,0 +1,252 @@
/* eslint-disable */
// @ts-nocheck
import * as main from "#app/main";
import fs from "fs";
import InputManager = Phaser.Input.InputManager;
import KeyboardManager = Phaser.Input.Keyboard.KeyboardManager;
import KeyboardPlugin = Phaser.Input.Keyboard.KeyboardPlugin;
import GamepadPlugin = Phaser.Input.Gamepad.GamepadPlugin;
import EventEmitter = Phaser.Events.EventEmitter;
import UpdateList = Phaser.GameObjects.UpdateList;
import MockGraphics from "#app/test/utils/mocks/mocksContainer/mockGraphics";
import MockTextureManager from "#app/test/utils/mocks/mockTextureManager";
import Phaser from "phaser";
import {blobToString} from "#app/test/utils/gameManagerUtils";
import {vi} from "vitest";
import mockLocalStorage from "#app/test/utils/mocks/mockLocalStorage";
import mockConsoleLog from "#app/test/utils/mocks/mockConsoleLog";
import MockLoader from "#app/test/utils/mocks/mockLoader";
import {MockFetch} from "#app/test/utils/mocks/mockFetch";
import * as Utils from "#app/utils";
import InputText from "phaser3-rex-plugins/plugins/inputtext";
import {MockClock} from "#app/test/utils/mocks/mockClock";
import BattleScene from "#app/battle-scene.js";
import {MoveAnim} from "#app/data/battle-anims";
import Pokemon from "#app/field/pokemon";
import * as battleScene from "#app/battle-scene";
Object.defineProperty(window, "localStorage", {
value: mockLocalStorage(),
});
Object.defineProperty(window, "console", {
value: mockConsoleLog(false),
});
InputText.prototype.setElement = () => null;
InputText.prototype.resize = () => null;
window.URL.createObjectURL = (blob: Blob) => {
blobToString(blob).then((data: string) => {
localStorage.setItem("toExport", data);
})
return null;
};
navigator.getGamepads = vi.fn().mockReturnValue([]);
global.fetch = vi.fn(MockFetch);
Utils.setCookie(Utils.sessionIdKey, 'fake_token');
window.matchMedia = () => ({
matches: false,
});
/**
* Sets this object's position relative to another object with a given offset
* @param guideObject {@linkcode Phaser.GameObjects.GameObject} to base the position off of
* @param x The relative x position
* @param y The relative y position
*/
const setPositionRelative = function (guideObject: any, x: number, y: number) {
const offsetX = guideObject.width * (-0.5 + (0.5 - guideObject.originX));
const offsetY = guideObject.height * (-0.5 + (0.5 - guideObject.originY));
this.setPosition(guideObject.x + offsetX + x, guideObject.y + offsetY + y);
};
Phaser.GameObjects.Container.prototype.setPositionRelative = setPositionRelative;
Phaser.GameObjects.Sprite.prototype.setPositionRelative = setPositionRelative;
Phaser.GameObjects.Image.prototype.setPositionRelative = setPositionRelative;
Phaser.GameObjects.NineSlice.prototype.setPositionRelative = setPositionRelative;
Phaser.GameObjects.Text.prototype.setPositionRelative = setPositionRelative;
Phaser.GameObjects.Rectangle.prototype.setPositionRelative = setPositionRelative;
export default class GameWrapper {
public game: Phaser.Game;
public scene: BattleScene;
constructor(phaserGame: Phaser.Game, bypassLogin: boolean) {
Phaser.Math.RND.sow([ 'test' ]);
vi.spyOn(Utils, "apiFetch", "get").mockReturnValue(fetch);
if (bypassLogin) {
vi.spyOn(battleScene, "bypassLogin", "get").mockReturnValue(true);
}
this.game = phaserGame;
MoveAnim.prototype.getAnim = () => ({
frames: {},
});
Pokemon.prototype.enableMask = () => null;
localStorage.clear();
}
setScene(scene: BattleScene) {
this.scene = scene;
this.injectMandatory();
this.scene.preload && this.scene.preload();
this.scene.create();
}
injectMandatory() {
this.game.config = {
seed: ["test"],
}
this.scene.game = this.game;
this.game.renderer = {
maxTextures: -1,
gl: {},
deleteTexture: () => null,
canvasToTexture: () => ({}),
createCanvasTexture: () => ({}),
pipelines: {
add: () => null,
},
};
this.scene.renderer = this.game.renderer;
this.scene.children = {
removeAll: () => null,
};
this.scene.sound = {
play: () => null,
pause: () => null,
setRate: () => null,
add: () => this.scene.sound,
get: () => this.scene.sound,
getAllPlaying: () => [],
manager: {
game: this.game,
},
setVolume: () => null,
stopByKey: () => null,
on: (evt, callback) => callback(),
key: "",
};
this.scene.tweens = {
add: (data) => {
if (data.onComplete) {
data.onComplete();
}
},
getTweensOf: () => ([]),
killTweensOf: () => ([]),
chain: () => null,
addCounter: (data) => {
if (data.onComplete) {
data.onComplete();
}
},
};
this.scene.anims = this.game.anims;
this.scene.cache = this.game.cache;
this.scene.plugins = this.game.plugins;
this.scene.registry = this.game.registry;
this.scene.scale = this.game.scale;
this.scene.textures = this.game.textures;
this.scene.events = this.game.events;
this.scene.manager = new InputManager(this.game, {});
this.scene.manager.keyboard = new KeyboardManager(this.scene);
this.scene.pluginEvents = new EventEmitter();
this.scene.domContainer = {} as HTMLDivElement;
this.scene.spritePipeline = {};
this.scene.fieldSpritePipeline = {};
this.scene.load = new MockLoader(this.scene);
this.scene.sys = {
queueDepthSort: () => null,
anims: this.game.anims,
game: this.game,
textures: {
addCanvas: () => ({
get: () => ({ // this.frame in Text.js
source: {},
setSize: () => null,
glTexture: () => ({
spectorMetadata: {},
}),
}),
})
},
cache: this.scene.load.cacheManager,
scale: this.game.scale,
// _scene.sys.scale = new ScaleManager(_scene);
// events: {
// on: () => null,
// },
events: new EventEmitter(),
settings: {
loader: {
key: 'battle',
}
},
input: this.game.input,
};
const mockTextureManager = new MockTextureManager(this.scene);
this.scene.add = mockTextureManager.add;
this.scene.sys.displayList = this.scene.add.displayList;
this.scene.sys.updateList = new UpdateList(this.scene);
this.scene.systems = this.scene.sys;
this.scene.input = this.game.input;
this.scene.scene = this.scene;
this.scene.input.keyboard = new KeyboardPlugin(this.scene);
this.scene.input.gamepad = new GamepadPlugin(this.scene);
this.scene.cachedFetch = (url, init) => {
return new Promise((resolve) => {
// need to remove that if later we want to test battle-anims
const newUrl = url.includes('./battle-anims/') ? prependPath('./battle-anims/tackle.json') : prependPath(url);
let raw;
try {
raw = fs.readFileSync(newUrl, {encoding: "utf8", flag: "r"});
} catch(e) {
return resolve(createFetchBadResponse({}));
}
const data = JSON.parse(raw);
const response = createFetchResponse(data);
return resolve(response);
});
};
this.scene.make = {
graphics: (config) => new MockGraphics(mockTextureManager, config),
rexTransitionImagePack: () => ({
transit: () => null,
}),
};
this.scene.time = new MockClock(this.scene);
}
}
function prependPath(originalPath) {
const prefix = "public";
if (originalPath.startsWith("./")) {
return originalPath.replace("./", `${prefix}/`);
}
return originalPath;
}
// Simulate fetch response
function createFetchResponse(data) {
return {
ok: true,
status: 200,
json: () => Promise.resolve(data),
text: () => Promise.resolve(JSON.stringify(data)),
};
}
// Simulate fetch response
function createFetchBadResponse(data) {
return {
ok: false,
status: 404,
json: () => Promise.resolve(data),
text: () => Promise.resolve(JSON.stringify(data)),
};
}

View File

@ -0,0 +1,115 @@
import BattleScene from "#app/battle-scene";
import Phaser from "phaser";
import {InputsController} from "#app/inputs-controller";
import pad_xbox360 from "#app/configs/inputs/pad_xbox360";
import {holdOn} from "#app/test/utils/gameManagerUtils";
import {initTouchControls} from "#app/touch-controls";
import { JSDOM } from "jsdom";
import fs from "fs";
export default class InputsHandler {
private scene: BattleScene;
private events: Phaser.Events.EventEmitter;
private inputController: InputsController;
public log = [];
public logUp = [];
private fakePad: Fakepad;
private fakeMobile: FakeMobile;
constructor(scene: BattleScene) {
this.scene = scene;
this.inputController = this.scene.inputController;
this.fakePad = new Fakepad(pad_xbox360);
this.fakeMobile = new FakeMobile();
this.scene.input.gamepad.gamepads.push(this.fakePad);
this.init();
}
pressTouch(button: string, duration: integer): Promise<void> {
return new Promise(async (resolve) => {
this.fakeMobile.touchDown(button);
await holdOn(duration);
this.fakeMobile.touchUp(button);
resolve();
});
}
pressGamepadButton(button: integer, duration: integer): Promise<void> {
return new Promise(async (resolve) => {
this.scene.input.gamepad.emit("down", this.fakePad, {index: button});
await holdOn(duration);
this.scene.input.gamepad.emit("up", this.fakePad, {index: button});
resolve();
});
}
pressKeyboardKey(key: integer, duration: integer): Promise<void> {
return new Promise(async (resolve) => {
this.scene.input.keyboard.emit("keydown", {keyCode: key});
await holdOn(duration);
this.scene.input.keyboard.emit("keyup", {keyCode: key});
resolve();
});
}
init(): void {
setInterval(() => {
this.inputController.update();
});
initTouchControls(this.inputController.events);
this.events = this.inputController.events;
this.scene.input.gamepad.emit("connected", this.fakePad);
this.listenInputs();
}
listenInputs(): void {
this.events.on("input_down", (event) => {
this.log.push({type: "input_down", button: event.button});
}, this);
this.events.on("input_up", (event) => {
this.logUp.push({type: "input_up", button: event.button});
}, this);
}
}
class Fakepad extends Phaser.Input.Gamepad.Gamepad {
public id: string;
public index: number;
constructor(pad) {
super(undefined, {...pad, buttons: pad.deviceMapping, axes: []});
this.id = "xbox_360_fakepad";
this.index = 0;
}
}
class FakeMobile {
constructor() {
const fakeMobilePage = fs.readFileSync("./src/test/utils/fakeMobile.html", {encoding: "utf8", flag: "r"});
const dom = new JSDOM(fakeMobilePage);
Object.defineProperty(window, "document", {
value: dom.window.document,
configurable: true,
});
}
touchDown(button: string) {
const node = document.querySelector(`[data-key][id='${button}']`);
if (!node) {
return;
}
const event = new Event("touchstart");
node.dispatchEvent(event);
}
touchUp(button: string) {
const node = document.querySelector(`[data-key][id='${button}']`);
if (!node) {
return;
}
const event = new Event("touchend");
node.dispatchEvent(event);
}
}

View File

@ -0,0 +1,79 @@
import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
import Phaser from "phaser";
import GameManager from "#app/test/utils/gameManager";
import {apiFetch} from "#app/utils";
import {waitUntil} from "#app/test/utils/gameManagerUtils";
describe("Test misc", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
});
it("test fetch mock async", async () => {
const spy = vi.fn();
await fetch("https://localhost:8080/account/info").then(response => {
expect(response.status).toBe(200);
expect(response.ok).toBe(true);
return response.json();
}).then(data => {
spy(); // Call the spy function
expect(data).toEqual({"username":"greenlamp","lastSessionSlot":0});
});
expect(spy).toHaveBeenCalled();
});
it("test apifetch mock async", async () => {
const spy = vi.fn();
await apiFetch("https://localhost:8080/account/info").then(response => {
expect(response.status).toBe(200);
expect(response.ok).toBe(true);
return response.json();
}).then(data => {
spy(); // Call the spy function
expect(data).toEqual({"username":"greenlamp","lastSessionSlot":0});
});
expect(spy).toHaveBeenCalled();
});
it("test fetch mock sync", async () => {
const response = await fetch("https://localhost:8080/account/info");
const data = await response.json();
expect(response.ok).toBe(true);
expect(response.status).toBe(200);
expect(data).toEqual({"username":"greenlamp","lastSessionSlot":0});
});
it("test apifetch mock sync", async () => {
const data = await game.scene.cachedFetch("./battle-anims/splishy-splash.json");
expect(data).not.toBeUndefined();
});
it("testing wait phase queue", async () => {
const fakeScene = {
phaseQueue: [1, 2, 3] // Initially not empty
};
setTimeout(() => {
fakeScene.phaseQueue = [];
}, 500);
const spy = vi.fn();
await waitUntil(() => fakeScene.phaseQueue.length === 0).then(result => {
expect(result).toBe(true);
spy(); // Call the spy function
});
expect(spy).toHaveBeenCalled();
});
});

View File

@ -0,0 +1,17 @@
import Clock = Phaser.Time.Clock;
export class MockClock extends Clock {
constructor(scene) {
super(scene);
setInterval(() => {
/*
To simulate frame update
eventEmitter.on(SceneEvents.PRE_UPDATE, this.preUpdate, this);
eventEmitter.on(SceneEvents.UPDATE, this.update, this);
*/
this.preUpdate(this.systems.game.loop.time, 100);
this.update(this.systems.game.loop.time, 100);
}, 100);
}
}

View File

@ -0,0 +1,82 @@
const MockConsoleLog = (_logDisabled= false, _phaseText=false) => {
let logs = [];
const logDisabled: boolean = _logDisabled;
const phaseText: boolean = _phaseText;
const originalLog = console.log;
const originalError = console.error;
const originalDebug = console.debug;
const originalWarn = console.warn;
const notified = [];
const blacklist = ["Phaser", "variant icon does not exist", "Texture \"%s\" not found"];
const whitelist = ["Phase"];
return ({
log(...args) {
const argsStr = this.getStr(args);
logs.push(argsStr);
if (logDisabled && (!phaseText)) {
return;
}
if ((phaseText && !whitelist.some((b) => argsStr.includes(b))) || blacklist.some((b) => argsStr.includes(b))) {
return;
}
originalLog(args);
},
error(...args) {
const argsStr = this.getStr(args);
logs.push(argsStr);
originalError(args); // Appelle le console.error originel
},
debug(...args) {
const argsStr = this.getStr(args);
logs.push(argsStr);
if (logDisabled && (!phaseText)) {
return;
}
if (!whitelist.some((b) => argsStr.includes(b)) || blacklist.some((b) => argsStr.includes(b))) {
return;
}
originalDebug(args);
},
warn(...args) {
const argsStr = this.getStr(args);
logs.push(args);
if (logDisabled && (!phaseText)) {
return;
}
if (!whitelist.some((b) => argsStr.includes(b)) || blacklist.some((b) => argsStr.includes(b))) {
return;
}
originalWarn(args);
},
notify(msg) {
originalLog(msg);
notified.push(msg);
},
getLogs() {
return logs;
},
clearLogs() {
logs = [];
},
getStr(...args) {
return args.map(arg => {
if (typeof arg === "object" && arg !== null) {
// Handle objects including arrays
return JSON.stringify(arg, (key, value) =>
typeof value === "bigint" ? value.toString() : value
);
} else if (typeof arg === "bigint") {
// Handle BigInt values
return arg.toString();
} else {
// Handle all other types
return arg.toString();
}
}).join(";");
},
});
};
export default MockConsoleLog;

View File

@ -0,0 +1,32 @@
export const MockFetch = (input, init) => {
const url = typeof input === "string" ? input : input.url;
let responseHandler;
let responseText;
const handlers = {
"account/info": {"username":"greenlamp","lastSessionSlot":0},
"savedata/session": {},
"savedata/system": {},
"savedata/updateall": "",
"daily/rankingpagecount": { data: 0 },
"game/titlestats": {"playerCount":0,"battleCount":5},
"daily/rankings": [],
};
for (const key of Object.keys(handlers)) {
if (url.includes(key)) {
responseHandler = async() => handlers[key];
responseText = async() => handlers[key] ? JSON.stringify(handlers[key]) : handlers[key];
break;
}
}
return Promise.resolve({
ok: true,
status: 200,
json: responseHandler,
text: responseText,
});
};

View File

@ -0,0 +1,42 @@
import CacheManager = Phaser.Cache.CacheManager;
export default class MockLoader {
public cacheManager;
constructor(scene) {
this.cacheManager = new CacheManager(scene);
}
once(event, callback) {
callback();
}
setBaseURL(url) {
return null;
}
video() {
return null;
}
spritesheet(key, url, frameConfig) {
}
audio(key, url) {
}
isLoading() {
return false;
}
start() {
}
image() {
}
atlas(key, textureUrl, atlasUrl) {
}
}

View File

@ -0,0 +1,27 @@
const mockLocalStorage = (() => {
let store = {} as Storage;
return {
getItem(key: string) {
return store[key];
},
setItem(key: string, value: string) {
store[key] = value;
},
hasOwnProperty(key: string) {
return store.hasOwnProperty(key);
},
removeItem(key: string) {
delete store[key];
},
clear() {
store = {} as Storage;
},
};
});
export default mockLocalStorage;

View File

@ -0,0 +1,85 @@
import MockContainer from "#app/test/utils/mocks/mocksContainer/mockContainer";
import MockSprite from "#app/test/utils/mocks/mocksContainer/mockSprite";
import MockRectangle from "#app/test/utils/mocks/mocksContainer/mockRectangle";
import MockNineslice from "#app/test/utils/mocks/mocksContainer/mockNineslice";
import MockImage from "#app/test/utils/mocks/mocksContainer/mockImage";
import MockText from "#app/test/utils/mocks/mocksContainer/mockText";
import MockPolygon from "#app/test/utils/mocks/mocksContainer/mockPolygon";
export default class MockTextureManager {
private textures: Map<string, any>;
private scene;
public add;
public displayList;
public list = [];
constructor(scene) {
this.scene = scene;
this.textures = new Map();
this.displayList = new Phaser.GameObjects.DisplayList(scene);
this.add = {
container: this.container.bind(this),
sprite: this.sprite.bind(this),
tileSprite: this.sprite.bind(this),
existing: this.existing.bind(this),
rectangle: this.rectangle.bind(this),
nineslice: this.nineslice.bind(this),
image: this.image.bind(this),
polygon: this.polygon.bind(this),
text: this.text.bind(this),
bitmapText: this.text.bind(this),
displayList: this.displayList,
};
}
container(x, y) {
const container = new MockContainer(this, x, y);
this.list.push(container);
return container;
}
sprite(x,y, texture) {
const sprite = new MockSprite(this, x, y, texture);
this.list.push(sprite);
return sprite;
}
existing(obj) {
// const whitelist = ["ArenaBase", "PlayerPokemon", "EnemyPokemon"];
// const key = obj.constructor.name;
// if (whitelist.includes(key) || obj.texture?.key?.includes("trainer_")) {
// this.containers.push(obj);
// }
}
rectangle(x, y, width, height, fillColor) {
const rectangle = new MockRectangle(this, x, y, width, height, fillColor);
this.list.push(rectangle);
return rectangle;
}
nineslice(x, y, texture, frame, width, height, leftWidth, rightWidth, topHeight, bottomHeight) {
const nineSlice = new MockNineslice(this, x, y, texture, frame, width, height, leftWidth, rightWidth, topHeight, bottomHeight);
this.list.push(nineSlice);
return nineSlice;
}
image(x, y, texture) {
const image = new MockImage(this, x, y, texture);
this.list.push(image);
return image;
}
text(x, y, content, styleOptions) {
const text = new MockText(this, x, y, content, styleOptions);
this.list.push(text);
return text;
}
polygon(x, y, content, fillColor, fillAlpha) {
const polygon = new MockPolygon(this, x, y, content, fillColor, fillAlpha);
this.list.push(polygon);
return polygon;
}
}

View File

@ -0,0 +1,207 @@
import MockTextureManager from "#app/test/utils/mocks/mockTextureManager";
export default class MockContainer {
protected x;
protected y;
protected scene;
protected width;
protected height;
protected visible;
private alpha;
private style;
public frame;
protected textureManager;
public list = [];
constructor(textureManager: MockTextureManager, x, y) {
this.x = x;
this.y = y;
this.frame = {};
this.textureManager = textureManager;
}
setVisible(visible) {
this.visible = visible;
}
once(event, callback, source) {
}
off(event, callback, source) {
}
removeFromDisplayList() {
// same as remove or destroy
}
addedToScene() {
// This callback is invoked when this Game Object is added to a Scene.
}
setSize(width, height) {
// Sets the size of this Game Object.
}
setMask() {
/// Sets the mask that this Game Object will use to render with.
}
setPositionRelative(source, x, y) {
/// Sets the position of this Game Object to be a relative position from the source Game Object.
}
setInteractive(hitArea?, callback?, dropZone?) {
/// Sets the InteractiveObject to be a drop zone for a drag and drop operation.
}
setOrigin(x, y) {
this.x = x;
this.y = y;
}
setAlpha(alpha) {
this.alpha = alpha;
}
setFrame(frame, updateSize?: boolean, updateOrigin?: boolean) {
// Sets the frame this Game Object will use to render with.
}
setScale(scale) {
// Sets the scale of this Game Object.
}
setPosition(x, y) {
this.x = x;
this.y = y;
}
setX(x) {
this.x = x;
}
setY(y) {
this.y = y;
}
destroy() {
this.list = [];
}
setShadow(shadowXpos, shadowYpos, shadowColor) {
// Sets the shadow settings for this Game Object.
}
setLineSpacing(lineSpacing) {
// Sets the line spacing value of this Game Object.
}
setText(text) {
// Sets the text this Game Object will display.
}
setAngle(angle) {
// Sets the angle of this Game Object.
}
setShadowOffset(offsetX, offsetY) {
// Sets the shadow offset values.
}
setWordWrapWidth(width) {
// Sets the width (in pixels) to use for wrapping lines.
}
setFontSize(fontSize) {
// Sets the font size of this Game Object.
}
getBounds() {
return { width: this.width, height: this.height };
}
setColor(color) {
// Sets the tint of this Game Object.
}
setShadowColor(color) {
// Sets the shadow color.
}
setTint(color) {
// Sets the tint of this Game Object.
}
setStrokeStyle(thickness, color) {
// Sets the stroke style for the graphics.
return this;
}
setDepth(depth) {
// Sets the depth of this Game Object.
}
setTexture(texture) {
// Sets the texture this Game Object will use to render with.
}
clearTint() {
// Clears any previously set tint.
}
sendToBack() {
// Sends this Game Object to the back of its parent's display list.
}
moveAbove(obj) {
// Moves this Game Object to be above the given Game Object in the display list.
}
moveBelow(obj) {
// Moves this Game Object to be below the given Game Object in the display list.
}
setName(name) {
// return this.phaserSprite.setName(name);
}
bringToTop(obj) {
// Brings this Game Object to the top of its parents display list.
}
on(event, callback, source) {
}
add(obj) {
// Adds a child to this Game Object.
this.list.push(obj);
}
removeAll() {
// Removes all Game Objects from this Container.
this.list = [];
}
addAt(obj, index) {
// Adds a Game Object to this Container at the given index.
this.list.splice(index, 0, obj);
}
remove(obj) {
const index = this.list.indexOf(obj);
if (index !== -1) {
this.list.splice(index, 1);
}
}
getIndex(obj) {
const index = this.list.indexOf(obj);
return index || -1;
}
getAt(index) {
return this.list[index];
}
getAll() {
return this.list;
}
}

View File

@ -0,0 +1,96 @@
export default class MockGraphics {
private scene;
public list = [];
constructor(textureManager, config) {
this.scene = textureManager.scene;
}
fillStyle(color) {
// Sets the fill style to be used by the fill methods.
}
beginPath() {
// Starts a new path by emptying the list of sub-paths. Call this method when you want to create a new path.
}
fillRect(x, y, width, height) {
// Adds a rectangle shape to the path which is filled when you call fill().
}
createGeometryMask() {
// Creates a geometry mask.
}
setOrigin(x, y) {
}
setAlpha(alpha) {
}
setVisible(visible) {
}
setName(name) {
}
once(event, callback, source) {
}
removeFromDisplayList() {
// same as remove or destroy
}
addedToScene() {
// This callback is invoked when this Game Object is added to a Scene.
}
setPositionRelative(source, x, y) {
/// Sets the position of this Game Object to be a relative position from the source Game Object.
}
destroy() {
this.list = [];
}
setScale(scale) {
// Sets the scale of this Game Object.
}
off(event, callback, source) {
}
add(obj) {
// Adds a child to this Game Object.
this.list.push(obj);
}
removeAll() {
// Removes all Game Objects from this Container.
this.list = [];
}
addAt(obj, index) {
// Adds a Game Object to this Container at the given index.
this.list.splice(index, 0, obj);
}
remove(obj) {
const index = this.list.indexOf(obj);
if (index !== -1) {
this.list.splice(index, 1);
}
}
getIndex(obj) {
const index = this.list.indexOf(obj);
return index || -1;
}
getAt(index) {
return this.list[index];
}
getAll() {
return this.list;
}
}

View File

@ -0,0 +1,11 @@
import MockContainer from "#app/test/utils/mocks/mocksContainer/mockContainer";
export default class MockImage extends MockContainer {
private texture;
constructor(textureManager, x, y, texture) {
super(textureManager, x, y);
this.texture = texture;
}
}

View File

@ -0,0 +1,20 @@
import MockContainer from "#app/test/utils/mocks/mocksContainer/mockContainer";
export default class MockNineslice extends MockContainer {
private texture;
private leftWidth;
private rightWidth;
private topHeight;
private bottomHeight;
constructor(textureManager, x, y, texture, frame, width, height, leftWidth, rightWidth, topHeight, bottomHeight) {
super(textureManager, x, y);
this.texture = texture;
this.frame = frame;
this.leftWidth = leftWidth;
this.rightWidth = rightWidth;
this.topHeight = topHeight;
this.bottomHeight = bottomHeight;
}
}

View File

@ -0,0 +1,9 @@
import MockContainer from "#app/test/utils/mocks/mocksContainer/mockContainer";
export default class MockPolygon extends MockContainer {
constructor(textureManager, x, y, content, fillColor, fillAlpha) {
super(textureManager, x, y);
}
}

View File

@ -0,0 +1,74 @@
export default class MockRectangle {
private fillColor;
private scene;
public list = [];
constructor(textureManager, x, y, width, height, fillColor) {
this.fillColor = fillColor;
this.scene = textureManager.scene;
}
setOrigin(x, y) {
}
setAlpha(alpha) {
}
setVisible(visible) {
}
setName(name) {
}
once(event, callback, source) {
}
removeFromDisplayList() {
// same as remove or destroy
}
addedToScene() {
// This callback is invoked when this Game Object is added to a Scene.
}
setPositionRelative(source, x, y) {
/// Sets the position of this Game Object to be a relative position from the source Game Object.
}
destroy() {
this.list = [];
}
add(obj) {
// Adds a child to this Game Object.
this.list.push(obj);
}
removeAll() {
// Removes all Game Objects from this Container.
this.list = [];
}
addAt(obj, index) {
// Adds a Game Object to this Container at the given index.
this.list.splice(index, 0, obj);
}
remove(obj) {
const index = this.list.indexOf(obj);
if (index !== -1) {
this.list.splice(index, 1);
}
}
getIndex(obj) {
const index = this.list.indexOf(obj);
return index || -1;
}
getAt(index) {
return this.list[index];
}
getAll() {
return this.list;
}
}

View File

@ -0,0 +1,205 @@
import Sprite = Phaser.GameObjects.Sprite;
import Frame = Phaser.Textures.Frame;
import Phaser from "phaser";
export default class MockSprite {
private phaserSprite;
public pipelineData;
public texture;
public key;
public frame;
public textureManager;
public scene;
public anims;
public list = [];
constructor(textureManager, x, y, texture) {
this.textureManager = textureManager;
this.scene = textureManager.scene;
Phaser.GameObjects.Sprite.prototype.setInteractive = this.setInteractive;
// @ts-ignore
Phaser.GameObjects.Sprite.prototype.setTexture = this.setTexture;
Phaser.GameObjects.Sprite.prototype.setSizeToFrame = this.setSizeToFrame;
Phaser.GameObjects.Sprite.prototype.setFrame = this.setFrame;
// Phaser.GameObjects.Sprite.prototype.disable = this.disable;
// Phaser.GameObjects.Sprite.prototype.texture = { frameTotal: 1, get: () => null };
this.phaserSprite = new Phaser.GameObjects.Sprite(textureManager.scene, x, y, texture);
this.pipelineData = {};
this.texture = {
key: texture || "",
};
this.anims = {
pause: () => null,
};
}
setTexture(key: string, frame?: string | number) {
return this;
}
setSizeToFrame(frame?: boolean | Frame): Sprite {
return {} as Sprite;
}
setPipeline(obj) {
// Sets the pipeline of this Game Object.
return this.phaserSprite.setPipeline(obj);
}
off(event, callback, source) {
}
setTintFill(color) {
// Sets the tint fill color.
return this.phaserSprite.setTintFill(color);
}
setScale(scale) {
return this.phaserSprite.setScale(scale);
}
setOrigin(x, y) {
return this.phaserSprite.setOrigin(x, y);
}
setSize(width, height) {
// Sets the size of this Game Object.
return this.phaserSprite.setSize(width, height);
}
once(event, callback, source) {
return this.phaserSprite.once(event, callback, source);
}
removeFromDisplayList() {
// same as remove or destroy
return this.phaserSprite.removeFromDisplayList();
}
addedToScene() {
// This callback is invoked when this Game Object is added to a Scene.
return this.phaserSprite.addedToScene();
}
setVisible(visible) {
return this.phaserSprite.setVisible(visible);
}
setPosition(x, y) {
return this.phaserSprite.setPosition(x, y);
}
stop() {
return this.phaserSprite.stop();
}
setInteractive(hitArea, hitAreaCallback, dropZone) {
return null;
}
on(event, callback, source) {
return this.phaserSprite.on(event, callback, source);
}
setAlpha(alpha) {
return this.phaserSprite.setAlpha(alpha);
}
setTint(color) {
// Sets the tint of this Game Object.
return this.phaserSprite.setTint(color);
}
setFrame(frame, updateSize?: boolean, updateOrigin?: boolean) {
// Sets the frame this Game Object will use to render with.
this.frame = frame;
return frame;
}
setPositionRelative(source, x, y) {
/// Sets the position of this Game Object to be a relative position from the source Game Object.
return this.phaserSprite.setPositionRelative(source, x, y);
}
setCrop(x, y, width, height) {
// Sets the crop size of this Game Object.
return this.phaserSprite.setCrop(x, y, width, height);
}
clearTint() {
// Clears any previously set tint.
return this.phaserSprite.clearTint();
}
disableInteractive() {
// Disables Interactive features of this Game Object.
return null;
}
apply() {
return this.phaserSprite.apply();
}
play() {
// return this.phaserSprite.play();
}
setPipelineData(key, value) {
this.pipelineData[key] = value;
}
destroy() {
return this.phaserSprite.destroy();
}
setName(name) {
return this.phaserSprite.setName(name);
}
setAngle(angle) {
return this.phaserSprite.setAngle(angle);
}
setMask() {
}
add(obj) {
// Adds a child to this Game Object.
this.list.push(obj);
}
removeAll() {
// Removes all Game Objects from this Container.
this.list = [];
}
addAt(obj, index) {
// Adds a Game Object to this Container at the given index.
this.list.splice(index, 0, obj);
}
remove(obj) {
const index = this.list.indexOf(obj);
if (index !== -1) {
this.list.splice(index, 1);
}
}
getIndex(obj) {
const index = this.list.indexOf(obj);
return index || -1;
}
getAt(index) {
return this.list[index];
}
getAll() {
return this.list;
}
}

View File

@ -0,0 +1,265 @@
import UI from "#app/ui/ui";
export default class MockText {
private phaserText;
private wordWrapWidth;
private splitRegExp;
private scene;
private textureManager;
public list = [];
constructor(textureManager, x, y, content, styleOptions) {
this.scene = textureManager.scene;
this.textureManager = textureManager;
// Phaser.GameObjects.TextStyle.prototype.setStyle = () => null;
// Phaser.GameObjects.Text.prototype.updateText = () => null;
// Phaser.Textures.TextureManager.prototype.addCanvas = () => {};
UI.prototype.showText = this.showText;
// super(scene, x, y);
// this.phaserText = new Phaser.GameObjects.Text(scene, x, y, content, styleOptions);
}
runWordWrap(text) {
if (!text) {
return "";
}
let result = "";
this.splitRegExp = /(?:\r\n|\r|\n)/;
const lines = text.split(this.splitRegExp);
const lastLineIndex = lines.length - 1;
const whiteSpaceWidth = 2;
for (let i = 0; i <= lastLineIndex; i++) {
let spaceLeft = this.wordWrapWidth;
const words = lines[i].split(" ");
const lastWordIndex = words.length - 1;
for (let j = 0; j <= lastWordIndex; j++) {
const word = words[j];
const wordWidth = word.length * 2;
let wordWidthWithSpace = wordWidth;
if (j < lastWordIndex) {
wordWidthWithSpace += whiteSpaceWidth;
}
if (wordWidthWithSpace > spaceLeft) {
// Skip printing the newline if it's the first word of the line that is greater
// than the word wrap width.
if (j > 0) {
result += "\n";
spaceLeft = this.wordWrapWidth;
}
}
result += word;
if (j < lastWordIndex) {
result += " ";
spaceLeft -= wordWidthWithSpace;
} else {
spaceLeft -= wordWidth;
}
}
if (i < lastLineIndex) {
result += "\n";
}
}
return result;
}
showText(text, delay, callback, callbackDelay, prompt, promptDelay) {
this.scene.messageWrapper.showText(text, delay, callback, callbackDelay, prompt, promptDelay);
if (callback) {
callback();
}
}
setScale(scale) {
// return this.phaserText.setScale(scale);
}
setShadow(shadowXpos, shadowYpos, shadowColor) {
// Sets the shadow settings for this Game Object.
// return this.phaserText.setShadow(shadowXpos, shadowYpos, shadowColor);
}
setLineSpacing(lineSpacing) {
// Sets the line spacing value of this Game Object.
// return this.phaserText.setLineSpacing(lineSpacing);
}
setOrigin(x, y) {
// return this.phaserText.setOrigin(x, y);
}
once(event, callback, source) {
// return this.phaserText.once(event, callback, source);
}
off(event, callback, obj) {
}
removedFromScene() {
}
addToDisplayList() {
}
setStroke(color, thickness) {
// Sets the stroke color and thickness.
// return this.phaserText.setStroke(color, thickness);
}
removeFromDisplayList() {
// same as remove or destroy
// return this.phaserText.removeFromDisplayList();
}
addedToScene() {
// This callback is invoked when this Game Object is added to a Scene.
// return this.phaserText.addedToScene();
}
setVisible(visible) {
// return this.phaserText.setVisible(visible);
}
setY(y) {
// return this.phaserText.setY(y);
}
setX(x) {
// return this.phaserText.setX(x);
}
setText(text) {
// Sets the text this Game Object will display.
// return this.phaserText.setText(text);
}
setAngle(angle) {
// Sets the angle of this Game Object.
// return this.phaserText.setAngle(angle);
}
setPositionRelative(source, x, y) {
/// Sets the position of this Game Object to be a relative position from the source Game Object.
// return this.phaserText.setPositionRelative(source, x, y);
}
setShadowOffset(offsetX, offsetY) {
// Sets the shadow offset values.
// return this.phaserText.setShadowOffset(offsetX, offsetY);
}
setWordWrapWidth(width) {
// Sets the width (in pixels) to use for wrapping lines.
this.wordWrapWidth = width;
}
setFontSize(fontSize) {
// Sets the font size of this Game Object.
// return this.phaserText.setFontSize(fontSize);
}
getBounds() {
// return this.phaserText.getBounds();
return {
width: 1,
};
}
setColor(color) {
// Sets the tint of this Game Object.
// return this.phaserText.setColor(color);
}
setShadowColor(color) {
// Sets the shadow color.
// return this.phaserText.setShadowColor(color);
}
setTint(color) {
// Sets the tint of this Game Object.
// return this.phaserText.setTint(color);
}
setStrokeStyle(thickness, color) {
// Sets the stroke style for the graphics.
// return this.phaserText.setStrokeStyle(thickness, color);
}
destroy() {
// return this.phaserText.destroy();
this.list = [];
}
setAlpha(alpha) {
// return this.phaserText.setAlpha(alpha);
}
setName(name) {
// return this.phaserText.setName(name);
}
setAlign(align) {
// return this.phaserText.setAlign(align);
}
setMask() {
/// Sets the mask that this Game Object will use to render with.
}
getBottomLeft() {
return {
x: 0,
y: 0,
};
}
getTopLeft() {
return {
x: 0,
y: 0,
};
}
add(obj) {
// Adds a child to this Game Object.
this.list.push(obj);
}
removeAll() {
// Removes all Game Objects from this Container.
this.list = [];
}
addAt(obj, index) {
// Adds a Game Object to this Container at the given index.
this.list.splice(index, 0, obj);
}
remove(obj) {
const index = this.list.indexOf(obj);
if (index !== -1) {
this.list.splice(index, 1);
}
}
getIndex(obj) {
const index = this.list.indexOf(obj);
return index || -1;
}
getAt(index) {
return this.list[index];
}
getAll() {
return this.list;
}
}

View File

@ -0,0 +1,279 @@
import {
BattleEndPhase,
BerryPhase,
CheckSwitchPhase, CommandPhase, DamagePhase, EggLapsePhase,
EncounterPhase, EnemyCommandPhase, FaintPhase,
LoginPhase, MessagePhase, MoveEffectPhase, MoveEndPhase, MovePhase, NewBattlePhase, NextEncounterPhase,
PostSummonPhase,
SelectGenderPhase, SelectModifierPhase,
SelectStarterPhase, ShinySparklePhase, ShowAbilityPhase, StatChangePhase, SummonPhase,
TitlePhase, ToggleDoublePositionPhase, TurnEndPhase, TurnInitPhase, TurnStartPhase, VictoryPhase
} from "#app/phases";
import {Mode} from "#app/ui/ui";
export default class PhaseInterceptor {
public scene;
public phases = {};
public log;
private onHold;
private interval;
private promptInterval;
private intervalRun;
private prompts;
private phaseFrom;
/**
* List of phases with their corresponding start methods.
*/
private PHASES = [
[LoginPhase, this.startPhase],
[TitlePhase, this.startPhase],
[SelectGenderPhase, this.startPhase],
[EncounterPhase, this.startPhase],
[SelectStarterPhase, this.startPhase],
[PostSummonPhase, this.startPhase],
[SummonPhase, this.startPhase],
[ToggleDoublePositionPhase, this.startPhase],
[CheckSwitchPhase, this.startPhase],
[ShowAbilityPhase, this.startPhase],
[MessagePhase, this.startPhase],
[TurnInitPhase, this.startPhase],
[CommandPhase, this.startPhase],
[EnemyCommandPhase, this.startPhase],
[TurnStartPhase, this.startPhase],
[MovePhase, this.startPhase],
[MoveEffectPhase, this.startPhase],
[DamagePhase, this.startPhase],
[FaintPhase, this.startPhase],
[BerryPhase, this.startPhase],
[TurnEndPhase, this.startPhase],
[BattleEndPhase, this.startPhase],
[EggLapsePhase, this.startPhase],
[SelectModifierPhase, this.startPhase],
[NextEncounterPhase, this.startPhase],
[NewBattlePhase, this.startPhase],
[VictoryPhase, this.startPhase],
[MoveEndPhase, this.startPhase],
[StatChangePhase, this.startPhase],
[ShinySparklePhase, this.startPhase],
];
/**
* Constructor to initialize the scene and properties, and to start the phase handling.
* @param scene - The scene to be managed.
*/
constructor(scene) {
this.scene = scene;
this.log = [];
this.onHold = [];
this.prompts = [];
this.initPhases();
this.startPromptHander();
}
/**
* Method to set the starting phase.
* @param phaseFrom - The phase to start from.
* @returns The instance of the PhaseInterceptor.
*/
runFrom(phaseFrom) {
this.phaseFrom = phaseFrom;
return this;
}
/**
* Method to transition to a target phase.
* @param phaseTo - The phase to transition to.
* @returns A promise that resolves when the transition is complete.
*/
async to(phaseTo): Promise<void> {
return new Promise(async (resolve) => {
await this.run(this.phaseFrom);
this.phaseFrom = null;
const targetName = typeof phaseTo === "string" ? phaseTo : phaseTo.name;
this.intervalRun = setInterval(async () => {
const currentPhase = this.onHold?.length && this.onHold[0];
if (currentPhase && currentPhase.name !== targetName) {
await this.run(currentPhase.name);
} else if (currentPhase.name === targetName) {
await this.run(currentPhase.name);
clearInterval(this.intervalRun);
return resolve();
}
});
});
}
/**
* Method to run a phase with an optional skip function.
* @param phaseTarget - The phase to run.
* @param skipFn - Optional skip function.
* @returns A promise that resolves when the phase is run.
*/
run(phaseTarget, skipFn?): Promise<void> {
this.scene.moveAnimations = null; // Mandatory to avoid crash
return new Promise(async (resolve) => {
this.waitUntil(phaseTarget, skipFn).then(() => {
const currentPhase = this.onHold.shift();
currentPhase.call();
resolve();
}).catch(() => {
resolve();
});
});
}
/**
* Method to ensure a phase is run, to throw error on test if not.
* @param phaseTarget - The phase to run.
* @returns A promise that resolves when the phase is run.
*/
mustRun(phaseTarget): Promise<void> {
const targetName = typeof phaseTarget === "string" ? phaseTarget : phaseTarget.name;
this.scene.moveAnimations = null; // Mandatory to avoid crash
return new Promise(async (resolve, reject) => {
const interval = setInterval(async () => {
const currentPhase = this.onHold?.length && this.onHold[0];
if (currentPhase && currentPhase.name !== targetName) {
reject(currentPhase);
} else if (currentPhase && currentPhase.name === targetName) {
clearInterval(interval);
await this.run(phaseTarget);
resolve();
}
});
});
}
/**
* Method to execute actions when about to run a phase. Does not run the phase, stop right before.
* @param phaseTarget - The phase to run.
* @param skipFn - Optional skip function.
* @returns A promise that resolves when the phase is about to run.
*/
whenAboutToRun(phaseTarget, skipFn?): Promise<void> {
return new Promise(async (resolve) => {
this.waitUntil(phaseTarget, skipFn).then(() => {
resolve();
}).catch(() => {
resolve();
});
});
}
/**
* Method to remove a phase from the list.
* @param phaseTarget - The phase to remove.
* @param skipFn - Optional skip function.
* @returns A promise that resolves when the phase is removed.
*/
remove(phaseTarget, skipFn?): Promise<void> {
return new Promise(async (resolve) => {
this.waitUntil(phaseTarget, skipFn).then(() => {
this.onHold.shift();
this.scene.getCurrentPhase().end();
resolve();
}).catch(() => {
resolve();
});
});
}
/**
* Method to wait until a specific phase is reached.
* @param phaseTarget - The phase to wait for.
* @param skipFn - Optional skip function.
* @returns A promise that resolves when the phase is reached.
*/
waitUntil(phaseTarget, skipFn?): Promise<void> {
const targetName = typeof phaseTarget === "string" ? phaseTarget : phaseTarget.name;
return new Promise((resolve, reject) => {
this.interval = setInterval(() => {
const currentPhase = this.onHold?.length && this.onHold[0] && this.onHold[0].name;
// if the currentPhase here is not filled, it means it's a phase we haven't added to the list
if (currentPhase === targetName) {
clearInterval(this.interval);
return resolve();
} else if (skipFn && skipFn()) {
clearInterval(this.interval);
return reject("Skipped phase");
}
});
});
}
/**
* Method to initialize phases and their corresponding methods.
*/
initPhases() {
for (const [phase, method] of this.PHASES) {
const originalStart = phase.prototype.start;
this.phases[phase.name] = originalStart;
phase.prototype.start = () => method.call(this, phase);
}
}
/**
* Method to start a phase and log it.
* @param phase - The phase to start.
*/
startPhase(phase) {
this.log.push(phase.name);
const instance = this.scene.getCurrentPhase();
this.onHold.push({
name: phase.name,
call: () => {
this.phases[phase.name].apply(instance);
}
});
}
/**
* Method to start the prompt handler.
*/
startPromptHander() {
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;
if (expireFn) {
this.prompts.shift();
} else if (currentMode === actionForNextPrompt.mode && currentPhase === actionForNextPrompt.phaseTarget) {
this.prompts.shift().callback();
}
}
});
}
/**
* Method to add an action to the next prompt.
* @param phaseTarget - The target phase for the prompt.
* @param mode - The mode of the UI.
* @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) {
this.prompts.push({
phaseTarget,
mode,
callback,
expireFn
});
}
/**
* Restores the original state of phases and clears intervals.
*
* This function iterates through all phases and resets their `start` method to the original
* function stored in `this.phases`. Additionally, it clears the `promptInterval` and `interval`.
*/
restoreOg() {
for (const [phase] of this.PHASES) {
phase.prototype.start = this.phases[phase.name];
}
clearInterval(this.promptInterval);
clearInterval(this.interval);
}
}

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More