Merge branch 'beta' of https://github.com/pagefaultgames/pokerogue into assistbug2

This commit is contained in:
Christopher Schmidt 2024-09-07 10:17:36 -04:00
commit d874f98bf6
19 changed files with 733 additions and 328 deletions

View File

@ -15,8 +15,8 @@ on:
types: [checks_requested] types: [checks_requested]
jobs: jobs:
run-tests: # Define a job named "run-tests" run-misc-tests: # Define a job named "run-tests"
name: Run tests # Human-readable name for the job name: Run misc tests # Human-readable name for the job
runs-on: ubuntu-latest # Specify the latest Ubuntu runner for the job runs-on: ubuntu-latest # Specify the latest Ubuntu runner for the job
steps: steps:
@ -31,5 +31,75 @@ jobs:
- name: Install Node.js dependencies # Step to install Node.js dependencies - name: Install Node.js dependencies # Step to install Node.js dependencies
run: npm ci # Use 'npm ci' to install dependencies run: npm ci # Use 'npm ci' to install dependencies
- name: tests # Step to run tests - name: pre-test # pre-test to check overrides
run: npm run test:silent run: npx vitest run --project pre
- name: test misc
run: npx vitest --project misc
run-abilities-tests:
name: Run abilities tests
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install Node.js dependencies
run: npm ci
- name: pre-test
run: npx vitest run --project pre
- name: test abilities
run: npx vitest --project abilities
run-items-tests:
name: Run items tests
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install Node.js dependencies
run: npm ci
- name: pre-test
run: npx vitest run --project pre
- name: test items
run: npx vitest --project items
run-moves-tests:
name: Run moves tests
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install Node.js dependencies
run: npm ci
- name: pre-test
run: npx vitest run --project pre
- name: test moves
run: npx vitest --project moves
run-battle-tests:
name: Run battle tests
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install Node.js dependencies
run: npm ci
- name: pre-test
run: npx vitest run --project pre
- name: test battle
run: npx vitest --project battle

View File

@ -4,7 +4,8 @@ import { fileURLToPath } from 'url';
/** /**
* This script creates a test boilerplate file for a move or ability. * This script creates a test boilerplate file for a move or ability.
* @param {string} type - The type of test to create. Either "move" or "ability". * @param {string} type - The type of test to create. Either "move", "ability",
* or "item".
* @param {string} fileName - The name of the file to create. * @param {string} fileName - The name of the file to create.
* @example npm run create-test move tackle * @example npm run create-test move tackle
*/ */
@ -19,7 +20,7 @@ const type = args[0]; // "move" or "ability"
let fileName = args[1]; // The file name let fileName = args[1]; // The file name
if (!type || !fileName) { if (!type || !fileName) {
console.error('Please provide both a type ("move" or "ability") and a file name.'); console.error('Please provide both a type ("move", "ability", or "item") and a file name.');
process.exit(1); process.exit(1);
} }
@ -40,8 +41,11 @@ if (type === 'move') {
} else if (type === 'ability') { } else if (type === 'ability') {
dir = path.join(__dirname, 'src', 'test', 'abilities'); dir = path.join(__dirname, 'src', 'test', 'abilities');
description = `Abilities - ${formattedName}`; description = `Abilities - ${formattedName}`;
} else if (type === "item") {
dir = path.join(__dirname, 'src', 'test', 'items');
description = `Items - ${formattedName}`;
} else { } else {
console.error('Invalid type. Please use "move" or "ability".'); console.error('Invalid type. Please use "move", "ability", or "item".');
process.exit(1); process.exit(1);
} }
@ -98,4 +102,4 @@ describe("${description}", () => {
// Write the template content to the file // Write the template content to the file
fs.writeFileSync(filePath, content, 'utf8'); fs.writeFileSync(filePath, content, 'utf8');
console.log(`File created at: ${filePath}`); console.log(`File created at: ${filePath}`);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -47,10 +47,14 @@
"description": "Changes a Pokémon's nature to {{natureName}} and permanently unlocks the nature for the starter." "description": "Changes a Pokémon's nature to {{natureName}} and permanently unlocks the nature for the starter."
}, },
"DoubleBattleChanceBoosterModifierType": { "DoubleBattleChanceBoosterModifierType": {
"description": "Doubles the chance of an encounter being a double battle for {{battleCount}} battles." "description": "Quadruples the chance of an encounter being a double battle for up to {{battleCount}} battles."
}, },
"TempStatStageBoosterModifierType": { "TempStatStageBoosterModifierType": {
"description": "Increases the {{stat}} of all party members by 1 stage for 5 battles." "description": "Increases the {{stat}} of all party members by {{amount}} for up to 5 battles.",
"extra": {
"stage": "1 stage",
"percentage": "30%"
}
}, },
"AttackTypeBoosterModifierType": { "AttackTypeBoosterModifierType": {
"description": "Increases the power of a Pokémon's {{moveType}}-type moves by 20%." "description": "Increases the power of a Pokémon's {{moveType}}-type moves by 20%."

View File

@ -1,150 +1,150 @@
{ {
"music": "Music: ", "music": "BGM: ",
"missing_entries": "{{name}}", "missing_entries": "{{name}}",
"battle_kanto_champion": "B2W2 Kanto Champion Battle", "battle_kanto_champion": "B2W2 戦闘!チャンピオン(カントー)",
"battle_johto_champion": "B2W2 Johto Champion Battle", "battle_johto_champion": "B2W2 戦闘!チャンピオン(ジョウト)",
"battle_hoenn_champion_g5": "B2W2 Hoenn Champion Battle", "battle_hoenn_champion_g5": "B2W2 戦闘!チャンピオン(ホウエン)",
"battle_hoenn_champion_g6": "ORAS Hoenn Champion Battle", "battle_hoenn_champion_g6": "ORAS 決戦!ダイゴ",
"battle_sinnoh_champion": "B2W2 Sinnoh Champion Battle", "battle_sinnoh_champion": "B2W2 戦闘!チャンピオン(シンオウ)",
"battle_champion_alder": "BW Unova Champion Battle", "battle_champion_alder": "BW チャンピオン アデク",
"battle_champion_iris": "B2W2 Unova Champion Battle", "battle_champion_iris": "B2W2 戦闘!チャンピオンアイリス",
"battle_kalos_champion": "XY Kalos Champion Battle", "battle_kalos_champion": "XY 戦闘!チャンピオン",
"battle_alola_champion": "USUM Alola Champion Battle", "battle_alola_champion": "USUM 頂上決戦!ハウ",
"battle_galar_champion": "SWSH Galar Champion Battle", "battle_galar_champion": "SWSH 決戦!チャンピオンダンデ",
"battle_champion_geeta": "SV Champion Geeta Battle", "battle_champion_geeta": "SV 戦闘!トップチャンピオン",
"battle_champion_nemona": "SV Champion Nemona Battle", "battle_champion_nemona": "SV 戦闘!チャンピオンネモ",
"battle_champion_kieran": "SV Champion Kieran Battle", "battle_champion_kieran": "SV 戦闘!チャンピオンスグリ",
"battle_hoenn_elite": "ORAS Elite Four Battle", "battle_hoenn_elite": "ORAS 戦闘!四天王",
"battle_unova_elite": "BW Elite Four Battle", "battle_unova_elite": "BW 戦闘!四天王",
"battle_kalos_elite": "XY Elite Four Battle", "battle_kalos_elite": "XY 戦闘!四天王",
"battle_alola_elite": "SM Elite Four Battle", "battle_alola_elite": "SM 戦闘!四天王",
"battle_galar_elite": "SWSH League Tournament Battle", "battle_galar_elite": "SWSH 戦闘!ファイナルトーナメント!",
"battle_paldea_elite": "SV Elite Four Battle", "battle_paldea_elite": "SV 戦闘!四天王",
"battle_bb_elite": "SV BB League Elite Four Battle", "battle_bb_elite": "SV 戦闘!ブルベリーグ四天王",
"battle_final_encounter": "PMD RTDX Rayquaza's Domain", "battle_final_encounter": "ポケダンDX レックウザ登場",
"battle_final": "BW Ghetsis Battle", "battle_final": "BW 戦闘!ゲーチス",
"battle_kanto_gym": "B2W2 Kanto Gym Battle", "battle_kanto_gym": "B2W2 戦闘!ジムリーダー(カントー)",
"battle_johto_gym": "B2W2 Johto Gym Battle", "battle_johto_gym": "B2W2 戦闘!ジムリーダー(ジョウト)",
"battle_hoenn_gym": "B2W2 Hoenn Gym Battle", "battle_hoenn_gym": "B2W2 戦闘!ジムリーダー(ホウエン)",
"battle_sinnoh_gym": "B2W2 Sinnoh Gym Battle", "battle_sinnoh_gym": "B2W2 戦闘!ジムリーダー(シンオウ)",
"battle_unova_gym": "BW Unova Gym Battle", "battle_unova_gym": "BW 戦闘!ジムリーダー",
"battle_kalos_gym": "XY Kalos Gym Battle", "battle_kalos_gym": "XY 戦闘!ジムリーダー",
"battle_galar_gym": "SWSH Galar Gym Battle", "battle_galar_gym": "SWSH 戦闘!ジムリーダー",
"battle_paldea_gym": "SV Paldea Gym Battle", "battle_paldea_gym": "SV 戦闘!ジムリーダー",
"battle_legendary_kanto": "XY Kanto Legendary Battle", "battle_legendary_kanto": "XY 戦闘!ミュウツー",
"battle_legendary_raikou": "HGSS Raikou Battle", "battle_legendary_raikou": "HGSS 戦闘!ライコウ",
"battle_legendary_entei": "HGSS Entei Battle", "battle_legendary_entei": "HGSS 戦闘!エンテイ",
"battle_legendary_suicune": "HGSS Suicune Battle", "battle_legendary_suicune": "HGSS 戦闘!スイクン",
"battle_legendary_lugia": "HGSS Lugia Battle", "battle_legendary_lugia": "HGSS 戦闘!ルギア",
"battle_legendary_ho_oh": "HGSS Ho-oh Battle", "battle_legendary_ho_oh": "HGSS 戦闘!ホウオウ",
"battle_legendary_regis_g5": "B2W2 Legendary Titan Battle", "battle_legendary_regis_g5": "B2W2 戦闘!レジロック・レジアイス・レジスチル",
"battle_legendary_regis_g6": "ORAS Legendary Titan Battle", "battle_legendary_regis_g6": "ORAS 戦闘!レジロック・レジアイス・レジスチル",
"battle_legendary_gro_kyo": "ORAS Groudon & Kyogre Battle", "battle_legendary_gro_kyo": "ORAS 戦闘!ゲンシカイキ",
"battle_legendary_rayquaza": "ORAS Rayquaza Battle", "battle_legendary_rayquaza": "ORAS 戦闘!超古代ポケモン",
"battle_legendary_deoxys": "ORAS Deoxys Battle", "battle_legendary_deoxys": "ORAS 戦闘!デオキシス",
"battle_legendary_lake_trio": "ORAS Lake Guardians Battle", "battle_legendary_lake_trio": "ORAS 戦闘!ユクシー・エムリット・アグノム",
"battle_legendary_sinnoh": "ORAS Sinnoh Legendary Battle", "battle_legendary_sinnoh": "ORAS 戦闘!伝説のポケモン(シンオウ)",
"battle_legendary_dia_pal": "ORAS Dialga & Palkia Battle", "battle_legendary_dia_pal": "ORAS 戦闘!ディアルガ・パルキア",
"battle_legendary_origin_forme": "LA Origin Dialga & Palkia Battle", "battle_legendary_origin_forme": "LA 戦い:ディアルガ・パルキア(オリジンフォルム)",
"battle_legendary_giratina": "ORAS Giratina Battle", "battle_legendary_giratina": "ORAS 戦闘!ギラティナ",
"battle_legendary_arceus": "HGSS Arceus Battle", "battle_legendary_arceus": "HGSS アルセウス",
"battle_legendary_unova": "BW Unova Legendary Battle", "battle_legendary_unova": "BW 戦闘!伝説のポケモン",
"battle_legendary_kyurem": "BW Kyurem Battle", "battle_legendary_kyurem": "BW 戦闘!キュレム",
"battle_legendary_res_zek": "BW Reshiram & Zekrom Battle", "battle_legendary_res_zek": "BW 戦闘!ゼクロム・レシラム",
"battle_legendary_xern_yvel": "XY Xerneas & Yveltal Battle", "battle_legendary_xern_yvel": "XY 戦闘!ゼルネアス・イベルタル・ジガルデ",
"battle_legendary_tapu": "SM Tapu Battle", "battle_legendary_tapu": "SM 戦闘!カプ",
"battle_legendary_sol_lun": "SM Solgaleo & Lunala Battle", "battle_legendary_sol_lun": "SM 戦闘!ソルガレオ・ルナアーラ",
"battle_legendary_ub": "SM Ultra Beast Battle", "battle_legendary_ub": "SM 戦闘!ウルトラビースト",
"battle_legendary_dusk_dawn": "USUM Dusk Mane & Dawn Wings Necrozma Battle", "battle_legendary_dusk_dawn": "USUM 戦闘!日食・月食ネクロズマ",
"battle_legendary_ultra_nec": "USUM Ultra Necrozma Battle", "battle_legendary_ultra_nec": "USUM 戦闘!ウルトラネクロズマ",
"battle_legendary_zac_zam": "SWSH Zacian & Zamazenta Battle", "battle_legendary_zac_zam": "SWSH 戦闘!ザシアン・ザマゼンタ",
"battle_legendary_glas_spec": "SWSH Glastrier & Spectrier Battle", "battle_legendary_glas_spec": "SWSH 戦闘!ブリザポス・レイスポス",
"battle_legendary_calyrex": "SWSH Calyrex Battle", "battle_legendary_calyrex": "SWSH 戦闘!バドレックス",
"battle_legendary_riders": "SWSH Ice & Shadow Rider Calyrex Battle", "battle_legendary_riders": "SWSH 戦闘!豊穣の王",
"battle_legendary_birds_galar": "SWSH Galarian Legendary Birds Battle", "battle_legendary_birds_galar": "SWSH 戦闘!伝説のとりポケモン",
"battle_legendary_ruinous": "SV Treasures of Ruin Battle", "battle_legendary_ruinous": "SV 戦闘!災厄ポケモン",
"battle_legendary_kor_mir": "SV Depths of Area Zero Battle", "battle_legendary_kor_mir": "SV 戦闘!エリアゼロのポケモン",
"battle_legendary_loyal_three": "SV Loyal Three Battle", "battle_legendary_loyal_three": "SV 戦闘!ともっこ",
"battle_legendary_ogerpon": "SV Ogerpon Battle", "battle_legendary_ogerpon": "SV 戦闘!オーガポン",
"battle_legendary_terapagos": "SV Terapagos Battle", "battle_legendary_terapagos": "SV 戦闘!テラパゴス",
"battle_legendary_pecharunt": "SV Pecharunt Battle", "battle_legendary_pecharunt": "SV 戦闘!モモワロウ",
"battle_rival": "BW Rival Battle", "battle_rival": "BW 戦闘!チェレン・ベル",
"battle_rival_2": "BW N Battle", "battle_rival_2": "BW 戦闘!N",
"battle_rival_3": "BW Final N Battle", "battle_rival_3": "BW 決戦N",
"battle_trainer": "BW Trainer Battle", "battle_trainer": "BW 戦闘!トレーナー",
"battle_wild": "BW Wild Battle", "battle_wild": "BW 戦闘!野生ポケモン",
"battle_wild_strong": "BW Strong Wild Battle", "battle_wild_strong": "BW 戦闘!強い野生ポケモン",
"end_summit": "PMD RTDX Sky Tower Summit", "end_summit": "ポケダンDX 天空の塔 最上階",
"battle_rocket_grunt": "HGSS Team Rocket Battle", "battle_rocket_grunt": "HGSS 戦闘!ロケット団",
"battle_aqua_magma_grunt": "ORAS Team Aqua & Magma Battle", "battle_aqua_magma_grunt": "ORAS 戦闘!アクア団・マグマ団",
"battle_galactic_grunt": "BDSP Team Galactic Battle", "battle_galactic_grunt": "BDSP 戦闘!ギンガ団",
"battle_plasma_grunt": "BW Team Plasma Battle", "battle_plasma_grunt": "BW 戦闘!プラズマ団",
"battle_flare_grunt": "XY Team Flare Battle", "battle_flare_grunt": "XY 戦闘!フレア団",
"battle_aether_grunt": "SM Aether Foundation Battle", "battle_aether_grunt": "SM 戦闘!エーテル財団トレーナー",
"battle_skull_grunt": "SM Team Skull Battle", "battle_skull_grunt": "SM 戦闘!スカル団",
"battle_macro_grunt": "SWSH Trainer Battle", "battle_macro_grunt": "SWSH 戦闘!トレーナー",
"battle_galactic_admin": "BDSP Team Galactic Admin Battle", "battle_galactic_admin": "BDSP 戦闘!ギンガ団幹部",
"battle_skull_admin": "SM Team Skull Admin Battle", "battle_skull_admin": "SM 戦闘!スカル団幹部",
"battle_oleana": "SWSH Oleana Battle", "battle_oleana": "SWSH 戦闘!オリーヴ",
"battle_rocket_boss": "USUM Giovanni Battle", "battle_rocket_boss": "USUM 戦闘!レインボーロケット団ボス",
"battle_aqua_magma_boss": "ORAS Archie & Maxie Battle", "battle_aqua_magma_boss": "ORAS 戦闘!アクア団・マグマ団のリーダー",
"battle_galactic_boss": "BDSP Cyrus Battle", "battle_galactic_boss": "BDSP 戦闘!ギンガ団ボス",
"battle_plasma_boss": "B2W2 Ghetsis Battle", "battle_plasma_boss": "B2W2 戦闘!ゲーチス",
"battle_flare_boss": "XY Lysandre Battle", "battle_flare_boss": "XY 戦闘!フラダリ",
"battle_aether_boss": "SM Lusamine Battle", "battle_aether_boss": "SM 戦闘!ルザミーネ",
"battle_skull_boss": "SM Guzma Battle", "battle_skull_boss": "SM 戦闘!スカル団ボス",
"battle_macro_boss": "SWSH Rose Battle", "battle_macro_boss": "SWSH 戦闘!ローズ",
"abyss": "PMD EoS Dark Crater", "abyss": "ポケダン空 やみのかこう",
"badlands": "PMD EoS Barren Valley", "badlands": "ポケダン空 こかつのたに",
"beach": "PMD EoS Drenched Bluff", "beach": "ポケダン空 しめったいわば",
"cave": "PMD EoS Sky Peak Cave", "cave": "ポケダン空 そらのいただき(どうくつ)",
"construction_site": "PMD EoS Boulder Quarry", "construction_site": "ポケダン空 きょだいがんせきぐん",
"desert": "PMD EoS Northern Desert", "desert": "ポケダン空 きたのさばく",
"dojo": "PMD EoS Marowak Dojo", "dojo": "ポケダン空 ガラガラどうじょう",
"end": "PMD RTDX Sky Tower", "end": "ポケダンDX 天空の塔",
"factory": "PMD EoS Concealed Ruins", "factory": "ポケダン空 かくされたいせき",
"fairy_cave": "PMD EoS Star Cave", "fairy_cave": "ポケダン空 ほしのどうくつ",
"forest": "PMD EoS Dusk Forest", "forest": "ポケダン空 くろのもり",
"grass": "PMD EoS Apple Woods", "grass": "ポケダン空 リンゴのもり",
"graveyard": "PMD EoS Mystifying Forest", "graveyard": "ポケダン空 しんぴのもり",
"ice_cave": "PMD EoS Vast Ice Mountain", "ice_cave": "ポケダン空 だいひょうざん",
"island": "PMD EoS Craggy Coast", "island": "ポケダン空 えんがんのいわば",
"jungle": "Lmz - Jungle", "jungle": "Lmz - Jungle(ジャングル)",
"laboratory": "Firel - Laboratory", "laboratory": "Firel - Laboratory(ラボラトリー)",
"lake": "PMD EoS Crystal Cave", "lake": "ポケダン空 すいしょうのどうくつ",
"meadow": "PMD EoS Sky Peak Forest", "meadow": "ポケダン空 そらのいただき(もり)",
"metropolis": "Firel - Metropolis", "metropolis": "Firel - Metropolis(大都市)",
"mountain": "PMD EoS Mt. Horn", "mountain": "ポケダン空 ツノやま",
"plains": "PMD EoS Sky Peak Prairie", "plains": "ポケダン空 そらのいただき(そうげん)",
"power_plant": "PMD EoS Far Amp Plains", "power_plant": "ポケダン空 エレキへいげん",
"ruins": "PMD EoS Deep Sealed Ruin", "ruins": "ポケダン空 ふういんのいわば",
"sea": "Andr06 - Marine Mystique", "sea": "Andr06 - Marine Mystique(海の神秘性)",
"seabed": "Firel - Seabed", "seabed": "Firel - Seabed(海底)",
"slum": "Andr06 - Sneaky Snom", "slum": "Andr06 - Sneaky Snom(ずるいユキハミ)",
"snowy_forest": "PMD EoS Sky Peak Snowfield", "snowy_forest": "ポケダン空 そらのいただき(ゆきやま)",
"space": "Firel - Aether", "space": "Firel - Aether(エーテル)",
"swamp": "PMD EoS Surrounded Sea", "swamp": "ポケダン空 とざされたうみ",
"tall_grass": "PMD EoS Foggy Forest", "tall_grass": "ポケダン空 のうむのもり",
"temple": "PMD EoS Aegis Cave", "temple": "ポケダン空 ばんにんのどうくつ",
"town": "PMD EoS Random Dungeon Theme 3", "town": "ポケダン空 ランダムダンジョン3",
"volcano": "PMD EoS Steam Cave", "volcano": "ポケダン空 ねっすいのどうくつ",
"wasteland": "PMD EoS Hidden Highland", "wasteland": "ポケダン空 まぼろしのだいち",
"encounter_ace_trainer": "BW Trainers' Eyes Meet (Ace Trainer)", "encounter_ace_trainer": "BW 視線!エリートトレーナー",
"encounter_backpacker": "BW Trainers' Eyes Meet (Backpacker)", "encounter_backpacker": "BW 視線!バックパッカー",
"encounter_clerk": "BW Trainers' Eyes Meet (Clerk)", "encounter_clerk": "BW 視線!ビジネスマン",
"encounter_cyclist": "BW Trainers' Eyes Meet (Cyclist)", "encounter_cyclist": "BW 視線!サイクリング",
"encounter_lass": "BW Trainers' Eyes Meet (Lass)", "encounter_lass": "BW 視線!ミニスカート",
"encounter_parasol_lady": "BW Trainers' Eyes Meet (Parasol Lady)", "encounter_parasol_lady": "BW 視線!パラソルおねえさん",
"encounter_pokefan": "BW Trainers' Eyes Meet (Poke Fan)", "encounter_pokefan": "BW 視線!だいすきクラブ",
"encounter_psychic": "BW Trainers' Eyes Meet (Psychic)", "encounter_psychic": "BW 視線!サイキッカー",
"encounter_rich": "BW Trainers' Eyes Meet (Gentleman)", "encounter_rich": "BW 視線!ジェントルマン",
"encounter_rival": "BW Cheren", "encounter_rival": "BW チェレンのテーマ",
"encounter_roughneck": "BW Trainers' Eyes Meet (Roughneck)", "encounter_roughneck": "BW 視線!スキンヘッズ",
"encounter_scientist": "BW Trainers' Eyes Meet (Scientist)", "encounter_scientist": "BW 視線!けんきゅういん",
"encounter_twins": "BW Trainers' Eyes Meet (Twins)", "encounter_twins": "BW 視線!ふたごちゃん",
"encounter_youngster": "BW Trainers' Eyes Meet (Youngster)", "encounter_youngster": "BW 視線!たんぱんこぞう",
"heal": "BW Pokémon Heal", "heal": "BW 回復",
"menu": "PMD EoS Welcome to the World of Pokémon!", "menu": "ポケダン空 ようこそ! ポケモンたちのせかいへ!",
"title": "PMD EoS Top Menu Theme" "title": "ポケダン空 トップメニュー"
} }

View File

@ -433,37 +433,44 @@ export class RememberMoveModifierType extends PokemonModifierType {
} }
export class DoubleBattleChanceBoosterModifierType extends ModifierType { export class DoubleBattleChanceBoosterModifierType extends ModifierType {
public battleCount: integer; private maxBattles: number;
constructor(localeKey: string, iconImage: string, battleCount: integer) { constructor(localeKey: string, iconImage: string, maxBattles: number) {
super(localeKey, iconImage, (_type, _args) => new Modifiers.DoubleBattleChanceBoosterModifier(this, this.battleCount), "lure"); super(localeKey, iconImage, (_type, _args) => new Modifiers.DoubleBattleChanceBoosterModifier(this, maxBattles), "lure");
this.battleCount = battleCount; this.maxBattles = maxBattles;
} }
getDescription(scene: BattleScene): string { getDescription(_scene: BattleScene): string {
return i18next.t("modifierType:ModifierType.DoubleBattleChanceBoosterModifierType.description", { battleCount: this.battleCount }); return i18next.t("modifierType:ModifierType.DoubleBattleChanceBoosterModifierType.description", {
battleCount: this.maxBattles
});
} }
} }
export class TempStatStageBoosterModifierType extends ModifierType implements GeneratedPersistentModifierType { export class TempStatStageBoosterModifierType extends ModifierType implements GeneratedPersistentModifierType {
private stat: TempBattleStat; private stat: TempBattleStat;
private key: string; private nameKey: string;
private quantityKey: string;
constructor(stat: TempBattleStat) { constructor(stat: TempBattleStat) {
const key = TempStatStageBoosterModifierTypeGenerator.items[stat]; const nameKey = TempStatStageBoosterModifierTypeGenerator.items[stat];
super("", key, (_type, _args) => new Modifiers.TempStatStageBoosterModifier(this, this.stat)); super("", nameKey, (_type, _args) => new Modifiers.TempStatStageBoosterModifier(this, this.stat, 5));
this.stat = stat; this.stat = stat;
this.key = key; this.nameKey = nameKey;
this.quantityKey = (stat !== Stat.ACC) ? "percentage" : "stage";
} }
get name(): string { get name(): string {
return i18next.t(`modifierType:TempStatStageBoosterItem.${this.key}`); return i18next.t(`modifierType:TempStatStageBoosterItem.${this.nameKey}`);
} }
getDescription(_scene: BattleScene): string { getDescription(_scene: BattleScene): string {
return i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.description", { stat: i18next.t(getStatKey(this.stat)) }); return i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.description", {
stat: i18next.t(getStatKey(this.stat)),
amount: i18next.t(`modifierType:ModifierType.TempStatStageBoosterModifierType.extra.${this.quantityKey}`)
});
} }
getPregenArgs(): any[] { getPregenArgs(): any[] {
@ -1348,9 +1355,9 @@ export const modifierTypes = {
SUPER_REPEL: () => new DoubleBattleChanceBoosterModifierType('Super Repel', 10), SUPER_REPEL: () => new DoubleBattleChanceBoosterModifierType('Super Repel', 10),
MAX_REPEL: () => new DoubleBattleChanceBoosterModifierType('Max Repel', 25),*/ MAX_REPEL: () => new DoubleBattleChanceBoosterModifierType('Max Repel', 25),*/
LURE: () => new DoubleBattleChanceBoosterModifierType("modifierType:ModifierType.LURE", "lure", 5), LURE: () => new DoubleBattleChanceBoosterModifierType("modifierType:ModifierType.LURE", "lure", 10),
SUPER_LURE: () => new DoubleBattleChanceBoosterModifierType("modifierType:ModifierType.SUPER_LURE", "super_lure", 10), SUPER_LURE: () => new DoubleBattleChanceBoosterModifierType("modifierType:ModifierType.SUPER_LURE", "super_lure", 15),
MAX_LURE: () => new DoubleBattleChanceBoosterModifierType("modifierType:ModifierType.MAX_LURE", "max_lure", 25), MAX_LURE: () => new DoubleBattleChanceBoosterModifierType("modifierType:ModifierType.MAX_LURE", "max_lure", 30),
SPECIES_STAT_BOOSTER: () => new SpeciesStatBoosterModifierTypeGenerator(), SPECIES_STAT_BOOSTER: () => new SpeciesStatBoosterModifierTypeGenerator(),
@ -1358,9 +1365,12 @@ export const modifierTypes = {
DIRE_HIT: () => new class extends ModifierType { DIRE_HIT: () => new class extends ModifierType {
getDescription(_scene: BattleScene): string { getDescription(_scene: BattleScene): string {
return i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.description", { stat: i18next.t("modifierType:ModifierType.DIRE_HIT.extra.raises") }); return i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.description", {
stat: i18next.t("modifierType:ModifierType.DIRE_HIT.extra.raises"),
amount: i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.extra.stage")
});
} }
}("modifierType:ModifierType.DIRE_HIT", "dire_hit", (type, _args) => new Modifiers.TempCritBoosterModifier(type)), }("modifierType:ModifierType.DIRE_HIT", "dire_hit", (type, _args) => new Modifiers.TempCritBoosterModifier(type, 5)),
BASE_STAT_BOOSTER: () => new BaseStatBoosterModifierTypeGenerator(), BASE_STAT_BOOSTER: () => new BaseStatBoosterModifierTypeGenerator(),

View File

@ -292,70 +292,131 @@ export class AddVoucherModifier extends ConsumableModifier {
} }
} }
/**
* Modifier used for party-wide or passive items that start an initial
* {@linkcode battleCount} equal to {@linkcode maxBattles} that, for every
* battle, decrements. Typically, when {@linkcode battleCount} reaches 0, the
* modifier will be removed. If a modifier of the same type is to be added, it
* will reset {@linkcode battleCount} back to {@linkcode maxBattles} of the
* existing modifier instead of adding that modifier directly.
* @extends PersistentModifier
* @abstract
* @see {@linkcode add}
*/
export abstract class LapsingPersistentModifier extends PersistentModifier { export abstract class LapsingPersistentModifier extends PersistentModifier {
protected battlesLeft: integer; /** The maximum amount of battles the modifier will exist for */
private maxBattles: number;
/** The current amount of battles the modifier will exist for */
private battleCount: number;
constructor(type: ModifierTypes.ModifierType, battlesLeft?: integer, stackCount?: integer) { constructor(type: ModifierTypes.ModifierType, maxBattles: number, battleCount?: number, stackCount?: integer) {
super(type, stackCount); super(type, stackCount);
this.battlesLeft = battlesLeft!; // TODO: is this bang correct? this.maxBattles = maxBattles;
this.battleCount = battleCount ?? this.maxBattles;
} }
lapse(args: any[]): boolean { /**
return !!--this.battlesLeft; * Goes through existing modifiers for any that match the selected modifier,
* which will then either add it to the existing modifiers if none were found
* or, if one was found, it will refresh {@linkcode battleCount}.
* @param modifiers {@linkcode PersistentModifier} array of the player's modifiers
* @param _virtual N/A
* @param _scene N/A
* @returns true if the modifier was successfully added or applied, false otherwise
*/
add(modifiers: PersistentModifier[], _virtual: boolean, scene: BattleScene): boolean {
for (const modifier of modifiers) {
if (this.match(modifier)) {
const modifierInstance = modifier as LapsingPersistentModifier;
if (modifierInstance.getBattleCount() < modifierInstance.getMaxBattles()) {
modifierInstance.resetBattleCount();
scene.playSound("se/restore");
return true;
}
// should never get here
return false;
}
}
modifiers.push(this);
return true;
}
lapse(_args: any[]): boolean {
this.battleCount--;
return this.battleCount > 0;
} }
getIcon(scene: BattleScene): Phaser.GameObjects.Container { getIcon(scene: BattleScene): Phaser.GameObjects.Container {
const container = super.getIcon(scene); const container = super.getIcon(scene);
const battleCountText = addTextObject(scene, 27, 0, this.battlesLeft.toString(), TextStyle.PARTY, { fontSize: "66px", color: "#f89890" }); // Linear interpolation on hue
const hue = Math.floor(120 * (this.battleCount / this.maxBattles) + 5);
// Generates the color hex code with a constant saturation and lightness but varying hue
const typeHex = Utils.hslToHex(hue, 0.50, 0.90);
const strokeHex = Utils.hslToHex(hue, 0.70, 0.30);
const battleCountText = addTextObject(scene, 27, 0, this.battleCount.toString(), TextStyle.PARTY, { fontSize: "66px", color: typeHex });
battleCountText.setShadow(0, 0); battleCountText.setShadow(0, 0);
battleCountText.setStroke("#984038", 16); battleCountText.setStroke(strokeHex, 16);
battleCountText.setOrigin(1, 0); battleCountText.setOrigin(1, 0);
container.add(battleCountText); container.add(battleCountText);
return container; return container;
} }
getBattlesLeft(): integer { getBattleCount(): number {
return this.battlesLeft; return this.battleCount;
} }
getMaxStackCount(scene: BattleScene, forThreshold?: boolean): number { resetBattleCount(): void {
return 99; this.battleCount = this.maxBattles;
}
}
export class DoubleBattleChanceBoosterModifier extends LapsingPersistentModifier {
constructor(type: ModifierTypes.DoubleBattleChanceBoosterModifierType, battlesLeft: integer, stackCount?: integer) {
super(type, battlesLeft, stackCount);
} }
match(modifier: Modifier): boolean { getMaxBattles(): number {
if (modifier instanceof DoubleBattleChanceBoosterModifier) { return this.maxBattles;
// Check type id to not match different tiers of lures
return modifier.type.id === this.type.id && modifier.battlesLeft === this.battlesLeft;
}
return false;
}
clone(): DoubleBattleChanceBoosterModifier {
return new DoubleBattleChanceBoosterModifier(this.type as ModifierTypes.DoubleBattleChanceBoosterModifierType, this.battlesLeft, this.stackCount);
} }
getArgs(): any[] { getArgs(): any[] {
return [ this.battlesLeft ]; return [ this.maxBattles, this.battleCount ];
} }
getMaxStackCount(_scene: BattleScene, _forThreshold?: boolean): number {
return 1;
}
}
/**
* Modifier used for passive items, specifically lures, that
* temporarily increases the chance of a double battle.
* @extends LapsingPersistentModifier
* @see {@linkcode apply}
*/
export class DoubleBattleChanceBoosterModifier extends LapsingPersistentModifier {
constructor(type: ModifierType, maxBattles:number, battleCount?: number, stackCount?: integer) {
super(type, maxBattles, battleCount, stackCount);
}
match(modifier: Modifier): boolean {
return (modifier instanceof DoubleBattleChanceBoosterModifier) && (modifier.getMaxBattles() === this.getMaxBattles());
}
clone(): DoubleBattleChanceBoosterModifier {
return new DoubleBattleChanceBoosterModifier(this.type as ModifierTypes.DoubleBattleChanceBoosterModifierType, this.getMaxBattles(), this.getBattleCount(), this.stackCount);
}
/** /**
* Modifies the chance of a double battle occurring * Modifies the chance of a double battle occurring
* @param args A single element array containing the double battle chance as a NumberHolder * @param args [0] {@linkcode Utils.NumberHolder} for double battle chance
* @returns {boolean} Returns true if the modifier was applied * @returns true if the modifier was applied
*/ */
apply(args: any[]): boolean { apply(args: any[]): boolean {
const doubleBattleChance = args[0] as Utils.NumberHolder; const doubleBattleChance = args[0] as Utils.NumberHolder;
// This is divided because the chance is generated as a number from 0 to doubleBattleChance.value using Utils.randSeedInt // This is divided because the chance is generated as a number from 0 to doubleBattleChance.value using Utils.randSeedInt
// A double battle will initiate if the generated number is 0 // A double battle will initiate if the generated number is 0
doubleBattleChance.value = Math.ceil(doubleBattleChance.value / 2); doubleBattleChance.value = Math.ceil(doubleBattleChance.value / 4);
return true; return true;
} }
@ -369,16 +430,18 @@ export class DoubleBattleChanceBoosterModifier extends LapsingPersistentModifier
* @see {@linkcode apply} * @see {@linkcode apply}
*/ */
export class TempStatStageBoosterModifier extends LapsingPersistentModifier { export class TempStatStageBoosterModifier extends LapsingPersistentModifier {
/** The stat whose stat stage multiplier will be temporarily increased */
private stat: TempBattleStat; private stat: TempBattleStat;
private multiplierBoost: number; /** The amount by which the stat stage itself or its multiplier will be increased by */
private boost: number;
constructor(type: ModifierType, stat: TempBattleStat, battlesLeft?: number, stackCount?: number) { constructor(type: ModifierType, stat: TempBattleStat, maxBattles: number, battleCount?: number, stackCount?: number) {
super(type, battlesLeft ?? 5, stackCount); super(type, maxBattles, battleCount, stackCount);
this.stat = stat; this.stat = stat;
// Note that, because we want X Accuracy to maintain its original behavior, // Note that, because we want X Accuracy to maintain its original behavior,
// it will increment as it did previously, directly to the stat stage. // it will increment as it did previously, directly to the stat stage.
this.multiplierBoost = stat !== Stat.ACC ? 0.3 : 1; this.boost = (stat !== Stat.ACC) ? 0.3 : 1;
} }
match(modifier: Modifier): boolean { match(modifier: Modifier): boolean {
@ -390,11 +453,11 @@ export class TempStatStageBoosterModifier extends LapsingPersistentModifier {
} }
clone() { clone() {
return new TempStatStageBoosterModifier(this.type, this.stat, this.battlesLeft, this.stackCount); return new TempStatStageBoosterModifier(this.type, this.stat, this.getMaxBattles(), this.getBattleCount(), this.stackCount);
} }
getArgs(): any[] { getArgs(): any[] {
return [ this.stat, this.battlesLeft ]; return [ this.stat, ...super.getArgs() ];
} }
/** /**
@ -409,44 +472,14 @@ export class TempStatStageBoosterModifier extends LapsingPersistentModifier {
} }
/** /**
* Increases the incoming stat stage matching {@linkcode stat} by {@linkcode multiplierBoost}. * Increases the incoming stat stage matching {@linkcode stat} by {@linkcode boost}.
* @param args [0] {@linkcode TempBattleStat} N/A * @param args [0] {@linkcode TempBattleStat} N/A
* [1] {@linkcode Utils.NumberHolder} that holds the resulting value of the stat stage multiplier * [1] {@linkcode Utils.NumberHolder} that holds the resulting value of the stat stage multiplier
*/ */
apply(args: any[]): boolean { apply(args: any[]): boolean {
(args[1] as Utils.NumberHolder).value += this.multiplierBoost; (args[1] as Utils.NumberHolder).value += this.boost;
return true; return true;
} }
/**
* Goes through existing modifiers for any that match the selected modifier,
* which will then either add it to the existing modifiers if none were found
* or, if one was found, it will refresh {@linkcode battlesLeft}.
* @param modifiers {@linkcode PersistentModifier} array of the player's modifiers
* @param _virtual N/A
* @param _scene N/A
* @returns true if the modifier was successfully added or applied, false otherwise
*/
add(modifiers: PersistentModifier[], _virtual: boolean, _scene: BattleScene): boolean {
for (const modifier of modifiers) {
if (this.match(modifier)) {
const modifierInstance = modifier as TempStatStageBoosterModifier;
if (modifierInstance.getBattlesLeft() < 5) {
modifierInstance.battlesLeft = 5;
return true;
}
// should never get here
return false;
}
}
modifiers.push(this);
return true;
}
getMaxStackCount(_scene: BattleScene, _forThreshold?: boolean): number {
return 1;
}
} }
/** /**
@ -456,12 +489,12 @@ export class TempStatStageBoosterModifier extends LapsingPersistentModifier {
* @see {@linkcode apply} * @see {@linkcode apply}
*/ */
export class TempCritBoosterModifier extends LapsingPersistentModifier { export class TempCritBoosterModifier extends LapsingPersistentModifier {
constructor(type: ModifierType, battlesLeft?: integer, stackCount?: number) { constructor(type: ModifierType, maxBattles: number, battleCount?: number, stackCount?: number) {
super(type, battlesLeft || 5, stackCount); super(type, maxBattles, battleCount, stackCount);
} }
clone() { clone() {
return new TempCritBoosterModifier(this.type, this.stackCount); return new TempCritBoosterModifier(this.type, this.getMaxBattles(), this.getBattleCount(), this.stackCount);
} }
match(modifier: Modifier): boolean { match(modifier: Modifier): boolean {
@ -486,36 +519,6 @@ export class TempCritBoosterModifier extends LapsingPersistentModifier {
(args[0] as Utils.NumberHolder).value++; (args[0] as Utils.NumberHolder).value++;
return true; return true;
} }
/**
* Goes through existing modifiers for any that match the selected modifier,
* which will then either add it to the existing modifiers if none were found
* or, if one was found, it will refresh {@linkcode battlesLeft}.
* @param modifiers {@linkcode PersistentModifier} array of the player's modifiers
* @param _virtual N/A
* @param _scene N/A
* @returns true if the modifier was successfully added or applied, false otherwise
*/
add(modifiers: PersistentModifier[], _virtual: boolean, _scene: BattleScene): boolean {
for (const modifier of modifiers) {
if (this.match(modifier)) {
const modifierInstance = modifier as TempCritBoosterModifier;
if (modifierInstance.getBattlesLeft() < 5) {
modifierInstance.battlesLeft = 5;
return true;
}
// should never get here
return false;
}
}
modifiers.push(this);
return true;
}
getMaxStackCount(_scene: BattleScene, _forThreshold?: boolean): number {
return 1;
}
} }
export class MapModifier extends PersistentModifier { export class MapModifier extends PersistentModifier {

View File

@ -17,6 +17,7 @@ import { ToggleDoublePositionPhase } from "./toggle-double-position-phase";
import { GameOverPhase } from "./game-over-phase"; import { GameOverPhase } from "./game-over-phase";
import { SwitchPhase } from "./switch-phase"; import { SwitchPhase } from "./switch-phase";
import { VictoryPhase } from "./victory-phase"; import { VictoryPhase } from "./victory-phase";
import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms";
export class FaintPhase extends PokemonPhase { export class FaintPhase extends PokemonPhase {
private preventEndure: boolean; private preventEndure: boolean;
@ -59,6 +60,7 @@ export class FaintPhase extends PokemonPhase {
} }
this.scene.queueMessage(i18next.t("battle:fainted", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), null, true); this.scene.queueMessage(i18next.t("battle:fainted", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), null, true);
this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true);
if (pokemon.turnData?.attacksReceived?.length) { if (pokemon.turnData?.attacksReceived?.length) {
const lastAttack = pokemon.turnData.attacksReceived[0]; const lastAttack = pokemon.turnData.attacksReceived[0];

View File

@ -49,7 +49,9 @@ export class GameOverPhase extends BattlePhase {
} }
if (this.victory && this.scene.gameMode.isEndless) { if (this.victory && this.scene.gameMode.isEndless) {
this.scene.ui.showDialogue(i18next.t("PGMmiscDialogue:ending_endless"), i18next.t("PGMmiscDialogue:ending_name"), 0, () => this.handleGameOver()); const genderIndex = this.scene.gameData.gender ?? PlayerGender.UNSET;
const genderStr = PlayerGender[genderIndex].toLowerCase();
this.scene.ui.showDialogue(i18next.t("miscDialogue:ending_endless", { context: genderStr }), i18next.t("miscDialogue:ending_name"), 0, () => this.handleGameOver());
} else if (this.victory || !this.scene.enableRetries) { } else if (this.victory || !this.scene.enableRetries) {
this.handleGameOver(); this.handleGameOver();
} else { } else {

View File

@ -1,10 +1,11 @@
import BattleScene from "#app/battle-scene.js"; import BattleScene from "#app/battle-scene";
import { applyPreWeatherEffectAbAttrs, SuppressWeatherEffectAbAttr, PreWeatherDamageAbAttr, applyAbAttrs, BlockNonDirectDamageAbAttr, applyPostWeatherLapseAbAttrs, PostWeatherLapseAbAttr } from "#app/data/ability.js"; import { applyPreWeatherEffectAbAttrs, SuppressWeatherEffectAbAttr, PreWeatherDamageAbAttr, applyAbAttrs, BlockNonDirectDamageAbAttr, applyPostWeatherLapseAbAttrs, PostWeatherLapseAbAttr } from "#app/data/ability.js";
import { CommonAnim } from "#app/data/battle-anims.js"; import { CommonAnim } from "#app/data/battle-anims";
import { Weather, getWeatherDamageMessage, getWeatherLapseMessage } from "#app/data/weather.js"; import { Weather, getWeatherDamageMessage, getWeatherLapseMessage } from "#app/data/weather";
import { WeatherType } from "#app/enums/weather-type.js"; import { BattlerTagType } from "#app/enums/battler-tag-type.js";
import Pokemon, { HitResult } from "#app/field/pokemon.js"; import { WeatherType } from "#app/enums/weather-type";
import * as Utils from "#app/utils.js"; import Pokemon, { HitResult } from "#app/field/pokemon";
import * as Utils from "#app/utils";
import { CommonAnimPhase } from "./common-anim-phase"; import { CommonAnimPhase } from "./common-anim-phase";
export class WeatherEffectPhase extends CommonAnimPhase { export class WeatherEffectPhase extends CommonAnimPhase {
@ -39,7 +40,7 @@ export class WeatherEffectPhase extends CommonAnimPhase {
applyPreWeatherEffectAbAttrs(PreWeatherDamageAbAttr, pokemon, this.weather, cancelled); applyPreWeatherEffectAbAttrs(PreWeatherDamageAbAttr, pokemon, this.weather, cancelled);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
if (cancelled.value) { if (cancelled.value || pokemon.getTag(BattlerTagType.UNDERGROUND) || pokemon.getTag(BattlerTagType.UNDERWATER)) {
return; return;
} }

View File

@ -857,6 +857,14 @@ export class GameData {
const settings = JSON.parse(localStorage.getItem("settings")!); // TODO: is this bang correct? const settings = JSON.parse(localStorage.getItem("settings")!); // TODO: is this bang correct?
// TODO: Remove this block after save migration is implemented
if (settings.hasOwnProperty("REROLL_TARGET") && !settings.hasOwnProperty(SettingKeys.Shop_Cursor_Target)) {
settings[SettingKeys.Shop_Cursor_Target] = settings["REROLL_TARGET"];
delete settings["REROLL_TARGET"];
localStorage.setItem("settings", JSON.stringify(settings));
}
// End of block to remove
for (const setting of Object.keys(settings)) { for (const setting of Object.keys(settings)) {
setSetting(this.scene, setting, settings[setting]); setSetting(this.scene, setting, settings[setting]);
} }

View File

@ -0,0 +1,62 @@
import { WeatherType } from "#app/data/weather";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import { SPLASH_ONLY } from "../utils/testUtils";
import { BattlerIndex } from "#app/battle";
describe("Weather - Hail", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.weather(WeatherType.HAIL)
.battleType("single")
.moveset(SPLASH_ONLY)
.enemyMoveset(SPLASH_ONLY)
.enemySpecies(Species.MAGIKARP);
});
it("inflicts damage equal to 1/16 of Pokemon's max HP at turn end", async () => {
await game.classicMode.startBattle([Species.MAGIKARP]);
game.move.select(Moves.SPLASH);
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
await game.phaseInterceptor.to("TurnEndPhase");
game.scene.getField(true).forEach(pokemon => {
expect(pokemon.hp).toBeLessThan(pokemon.getMaxHp() - Math.floor(pokemon.getMaxHp() / 16));
});
});
it("does not inflict damage to a Pokemon that is underwater (Dive) or underground (Dig)", async () => {
game.override.moveset([Moves.DIG]);
await game.classicMode.startBattle([Species.MAGIKARP]);
game.move.select(Moves.DIG);
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
await game.phaseInterceptor.to("TurnEndPhase");
const playerPokemon = game.scene.getPlayerPokemon()!;
const enemyPokemon = game.scene.getEnemyPokemon()!;
expect(playerPokemon.hp).toBe(playerPokemon.getMaxHp());
expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp() - Math.floor(enemyPokemon.getMaxHp() / 16));
});
});

View File

@ -0,0 +1,59 @@
import { WeatherType } from "#app/data/weather";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import { SPLASH_ONLY } from "../utils/testUtils";
describe("Weather - Sandstorm", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.weather(WeatherType.SANDSTORM)
.battleType("single")
.moveset(SPLASH_ONLY)
.enemyMoveset(SPLASH_ONLY)
.enemySpecies(Species.MAGIKARP);
});
it("inflicts damage equal to 1/16 of Pokemon's max HP at turn end", async () => {
await game.classicMode.startBattle([Species.MAGIKARP]);
game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to("TurnEndPhase");
game.scene.getField(true).forEach(pokemon => {
expect(pokemon.hp).toBeLessThan(pokemon.getMaxHp() - Math.floor(pokemon.getMaxHp() / 16));
});
});
it("does not inflict damage to a Pokemon that is underwater (Dive) or underground (Dig)", async () => {
game.override.moveset([Moves.DIVE]);
await game.classicMode.startBattle([Species.MAGIKARP]);
game.move.select(Moves.DIVE);
await game.phaseInterceptor.to("TurnEndPhase");
const playerPokemon = game.scene.getPlayerPokemon()!;
const enemyPokemon = game.scene.getEnemyPokemon()!;
expect(playerPokemon.hp).toBe(playerPokemon.getMaxHp());
expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp() - Math.floor(enemyPokemon.getMaxHp() / 16));
});
});

View File

@ -72,7 +72,7 @@ describe("Items - Dire Hit", () => {
await game.phaseInterceptor.to(BattleEndPhase); await game.phaseInterceptor.to(BattleEndPhase);
const modifier = game.scene.findModifier(m => m instanceof TempCritBoosterModifier) as TempCritBoosterModifier; const modifier = game.scene.findModifier(m => m instanceof TempCritBoosterModifier) as TempCritBoosterModifier;
expect(modifier.getBattlesLeft()).toBe(4); expect(modifier.getBattleCount()).toBe(4);
// Forced DIRE_HIT to spawn in the first slot with override // Forced DIRE_HIT to spawn in the first slot with override
game.onNextPrompt("SelectModifierPhase", Mode.MODIFIER_SELECT, () => { game.onNextPrompt("SelectModifierPhase", Mode.MODIFIER_SELECT, () => {
@ -90,7 +90,7 @@ describe("Items - Dire Hit", () => {
for (const m of game.scene.modifiers) { for (const m of game.scene.modifiers) {
if (m instanceof TempCritBoosterModifier) { if (m instanceof TempCritBoosterModifier) {
count++; count++;
expect((m as TempCritBoosterModifier).getBattlesLeft()).toBe(5); expect((m as TempCritBoosterModifier).getBattleCount()).toBe(5);
} }
} }
expect(count).toBe(1); expect(count).toBe(1);

View File

@ -0,0 +1,105 @@
import { Moves } from "#app/enums/moves.js";
import { Species } from "#app/enums/species.js";
import { DoubleBattleChanceBoosterModifier } from "#app/modifier/modifier";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import { SPLASH_ONLY } from "../utils/testUtils";
import { ShopCursorTarget } from "#app/enums/shop-cursor-target.js";
import { Mode } from "#app/ui/ui.js";
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler.js";
import { Button } from "#app/enums/buttons.js";
describe("Items - Double Battle Chance Boosters", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
const TIMEOUT = 20 * 1000;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
});
it("should guarantee double battle with 2 unique tiers", async () => {
game.override
.startingModifier([
{ name: "LURE" },
{ name: "SUPER_LURE" }
])
.startingWave(2);
await game.classicMode.startBattle();
expect(game.scene.getEnemyField().length).toBe(2);
}, TIMEOUT);
it("should guarantee double boss battle with 3 unique tiers", async () => {
game.override
.startingModifier([
{ name: "LURE" },
{ name: "SUPER_LURE" },
{ name: "MAX_LURE" }
])
.startingWave(10);
await game.classicMode.startBattle();
const enemyField = game.scene.getEnemyField();
expect(enemyField.length).toBe(2);
expect(enemyField[0].isBoss()).toBe(true);
expect(enemyField[1].isBoss()).toBe(true);
}, TIMEOUT);
it("should renew how many battles are left of existing booster when picking up new booster of same tier", async() => {
game.override
.startingModifier([{ name: "LURE" }])
.itemRewards([{ name: "LURE" }])
.moveset(SPLASH_ONLY)
.startingLevel(200);
await game.classicMode.startBattle([
Species.PIKACHU
]);
game.move.select(Moves.SPLASH);
await game.doKillOpponents();
await game.phaseInterceptor.to("BattleEndPhase");
const modifier = game.scene.findModifier(m => m instanceof DoubleBattleChanceBoosterModifier) as DoubleBattleChanceBoosterModifier;
expect(modifier.getBattleCount()).toBe(9);
// Forced LURE to spawn in the first slot with override
game.onNextPrompt("SelectModifierPhase", Mode.MODIFIER_SELECT, () => {
const handler = game.scene.ui.getHandler() as ModifierSelectUiHandler;
// Traverse to first modifier slot
handler.setCursor(0);
handler.setRowCursor(ShopCursorTarget.REWARDS);
handler.processInput(Button.ACTION);
}, () => game.isCurrentPhase("CommandPhase") || game.isCurrentPhase("NewBattlePhase"), true);
await game.phaseInterceptor.to("TurnInitPhase");
// Making sure only one booster is in the modifier list even after picking up another
let count = 0;
for (const m of game.scene.modifiers) {
if (m instanceof DoubleBattleChanceBoosterModifier) {
count++;
const modifierInstance = m as DoubleBattleChanceBoosterModifier;
expect(modifierInstance.getBattleCount()).toBe(modifierInstance.getMaxBattles());
}
}
expect(count).toBe(1);
}, TIMEOUT);
});

View File

@ -10,12 +10,7 @@ import { Abilities } from "#app/enums/abilities";
import { TempStatStageBoosterModifier } from "#app/modifier/modifier"; import { TempStatStageBoosterModifier } from "#app/modifier/modifier";
import { Mode } from "#app/ui/ui"; import { Mode } from "#app/ui/ui";
import { Button } from "#app/enums/buttons"; import { Button } from "#app/enums/buttons";
import { CommandPhase } from "#app/phases/command-phase";
import { NewBattlePhase } from "#app/phases/new-battle-phase";
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
import { TurnInitPhase } from "#app/phases/turn-init-phase";
import { BattleEndPhase } from "#app/phases/battle-end-phase";
import { EnemyCommandPhase } from "#app/phases/enemy-command-phase";
import { ShopCursorTarget } from "#app/enums/shop-cursor-target"; import { ShopCursorTarget } from "#app/enums/shop-cursor-target";
@ -46,7 +41,7 @@ describe("Items - Temporary Stat Stage Boosters", () => {
}); });
it("should provide a x1.3 stat stage multiplier", async() => { it("should provide a x1.3 stat stage multiplier", async() => {
await game.startBattle([ await game.classicMode.startBattle([
Species.PIKACHU Species.PIKACHU
]); ]);
@ -56,7 +51,7 @@ describe("Items - Temporary Stat Stage Boosters", () => {
game.move.select(Moves.TACKLE); game.move.select(Moves.TACKLE);
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnEndPhase); await game.phaseInterceptor.runFrom("EnemyCommandPhase").to(TurnEndPhase);
expect(partyMember.getStatStageMultiplier).toHaveReturnedWith(1.3); expect(partyMember.getStatStageMultiplier).toHaveReturnedWith(1.3);
}, 20000); }, 20000);
@ -66,7 +61,7 @@ describe("Items - Temporary Stat Stage Boosters", () => {
.startingModifier([{ name: "TEMP_STAT_STAGE_BOOSTER", type: Stat.ACC }]) .startingModifier([{ name: "TEMP_STAT_STAGE_BOOSTER", type: Stat.ACC }])
.ability(Abilities.SIMPLE); .ability(Abilities.SIMPLE);
await game.startBattle([ await game.classicMode.startBattle([
Species.PIKACHU Species.PIKACHU
]); ]);
@ -89,7 +84,7 @@ describe("Items - Temporary Stat Stage Boosters", () => {
it("should increase existing stat stage multiplier by 3/10 for the rest of the boosters", async() => { it("should increase existing stat stage multiplier by 3/10 for the rest of the boosters", async() => {
await game.startBattle([ await game.classicMode.startBattle([
Species.PIKACHU Species.PIKACHU
]); ]);
@ -113,7 +108,7 @@ describe("Items - Temporary Stat Stage Boosters", () => {
it("should not increase past maximum stat stage multiplier", async() => { it("should not increase past maximum stat stage multiplier", async() => {
game.override.startingModifier([{ name: "TEMP_STAT_STAGE_BOOSTER", type: Stat.ACC }, { name: "TEMP_STAT_STAGE_BOOSTER", type: Stat.ATK }]); game.override.startingModifier([{ name: "TEMP_STAT_STAGE_BOOSTER", type: Stat.ACC }, { name: "TEMP_STAT_STAGE_BOOSTER", type: Stat.ATK }]);
await game.startBattle([ await game.classicMode.startBattle([
Species.PIKACHU Species.PIKACHU
]); ]);
@ -138,7 +133,7 @@ describe("Items - Temporary Stat Stage Boosters", () => {
.startingLevel(200) .startingLevel(200)
.itemRewards([{ name: "TEMP_STAT_STAGE_BOOSTER", type: Stat.ATK }]); .itemRewards([{ name: "TEMP_STAT_STAGE_BOOSTER", type: Stat.ATK }]);
await game.startBattle([ await game.classicMode.startBattle([
Species.PIKACHU Species.PIKACHU
]); ]);
@ -146,10 +141,10 @@ describe("Items - Temporary Stat Stage Boosters", () => {
await game.doKillOpponents(); await game.doKillOpponents();
await game.phaseInterceptor.to(BattleEndPhase); await game.phaseInterceptor.to("BattleEndPhase");
const modifier = game.scene.findModifier(m => m instanceof TempStatStageBoosterModifier) as TempStatStageBoosterModifier; const modifier = game.scene.findModifier(m => m instanceof TempStatStageBoosterModifier) as TempStatStageBoosterModifier;
expect(modifier.getBattlesLeft()).toBe(4); expect(modifier.getBattleCount()).toBe(4);
// Forced X_ATTACK to spawn in the first slot with override // Forced X_ATTACK to spawn in the first slot with override
game.onNextPrompt("SelectModifierPhase", Mode.MODIFIER_SELECT, () => { game.onNextPrompt("SelectModifierPhase", Mode.MODIFIER_SELECT, () => {
@ -158,16 +153,17 @@ describe("Items - Temporary Stat Stage Boosters", () => {
handler.setCursor(0); handler.setCursor(0);
handler.setRowCursor(ShopCursorTarget.REWARDS); handler.setRowCursor(ShopCursorTarget.REWARDS);
handler.processInput(Button.ACTION); handler.processInput(Button.ACTION);
}, () => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(NewBattlePhase), true); }, () => game.isCurrentPhase("CommandPhase") || game.isCurrentPhase("NewBattlePhase"), true);
await game.phaseInterceptor.to(TurnInitPhase); await game.phaseInterceptor.to("TurnInitPhase");
// Making sure only one booster is in the modifier list even after picking up another // Making sure only one booster is in the modifier list even after picking up another
let count = 0; let count = 0;
for (const m of game.scene.modifiers) { for (const m of game.scene.modifiers) {
if (m instanceof TempStatStageBoosterModifier) { if (m instanceof TempStatStageBoosterModifier) {
count++; count++;
expect((m as TempStatStageBoosterModifier).getBattlesLeft()).toBe(5); const modifierInstance = m as TempStatStageBoosterModifier;
expect(modifierInstance.getBattleCount()).toBe(modifierInstance.getMaxBattles());
} }
} }
expect(count).toBe(1); expect(count).toBe(1);

View File

@ -455,6 +455,26 @@ export function rgbaToInt(rgba: integer[]): integer {
return (rgba[0] << 24) + (rgba[1] << 16) + (rgba[2] << 8) + rgba[3]; return (rgba[0] << 24) + (rgba[1] << 16) + (rgba[2] << 8) + rgba[3];
} }
/**
* Provided valid HSV values, calculates and stitches together a string of that
* HSV color's corresponding hex code.
*
* Sourced from {@link https://stackoverflow.com/a/44134328}.
* @param h Hue in degrees, must be in a range of [0, 360]
* @param s Saturation percentage, must be in a range of [0, 1]
* @param l Ligthness percentage, must be in a range of [0, 1]
* @returns a string of the corresponding color hex code with a "#" prefix
*/
export function hslToHex(h: number, s: number, l: number): string {
const a = s * Math.min(l, 1 - l);
const f = (n: number) => {
const k = (n + h / 30) % 12;
const rgb = l - a * Math.max(-1, Math.min(k - 3, 9 - k, 1));
return Math.round(rgb * 255).toString(16).padStart(2, "0");
};
return `#${f(0)}${f(8)}${f(4)}`;
}
/*This function returns true if the current lang is available for some functions /*This function returns true if the current lang is available for some functions
If the lang is not in the function, it usually means that lang is going to use the default english version If the lang is not in the function, it usually means that lang is going to use the default english version
This function is used in: This function is used in:

View File

@ -1,38 +1,43 @@
import { defineProject } from 'vitest/config'; import { defineProject, UserWorkspaceConfig } from 'vitest/config';
import { defaultConfig } from './vite.config'; import { defaultConfig } from './vite.config';
export const defaultProjectTestConfig: UserWorkspaceConfig["test"] = {
setupFiles: ['./src/test/vitest.setup.ts'],
server: {
deps: {
inline: ['vitest-canvas-mock'],
//@ts-ignore
optimizer: {
web: {
include: ['vitest-canvas-mock'],
}
}
}
},
environment: 'jsdom' as const,
environmentOptions: {
jsdom: {
resources: 'usable',
},
},
threads: false,
trace: true,
restoreMocks: true,
watch: false,
coverage: {
provider: 'istanbul' as const,
reportsDirectory: 'coverage' as const,
reporters: ['text-summary', 'html'],
},
}
export default defineProject(({ mode }) => ({ export default defineProject(({ mode }) => ({
...defaultConfig, ...defaultConfig,
test: { test: {
...defaultProjectTestConfig,
name: "main", name: "main",
include: ["./src/test/**/*.{test,spec}.ts"], include: ["./src/test/**/*.{test,spec}.ts"],
exclude: ["./src/test/pre.test.ts"], exclude: ["./src/test/pre.test.ts"],
setupFiles: ['./src/test/vitest.setup.ts'],
server: {
deps: {
inline: ['vitest-canvas-mock'],
optimizer: {
web: {
include: ['vitest-canvas-mock'],
}
}
}
},
environment: 'jsdom' as const,
environmentOptions: {
jsdom: {
resources: 'usable',
},
},
threads: false,
trace: true,
restoreMocks: true,
watch: false,
coverage: {
provider: 'istanbul' as const,
reportsDirectory: 'coverage' as const,
reporters: ['text-summary', 'html'],
},
}, },
esbuild: { esbuild: {
pure: mode === 'production' ? [ 'console.log' ] : [], pure: mode === 'production' ? [ 'console.log' ] : [],

View File

@ -1,5 +1,6 @@
import { defineWorkspace } from "vitest/config"; import { defineWorkspace } from "vitest/config";
import { defaultConfig } from "./vite.config"; import { defaultConfig } from "./vite.config";
import { defaultProjectTestConfig } from "./vitest.config";
export default defineWorkspace([ export default defineWorkspace([
{ {
@ -10,5 +11,58 @@ export default defineWorkspace([
environment: "jsdom", environment: "jsdom",
}, },
}, },
{
...defaultConfig,
test: {
...defaultProjectTestConfig,
name: "misc",
include: [
"src/test/achievements/**/*.{test,spec}.ts",
"src/test/arena/**/*.{test,spec}.ts",
"src/test/battlerTags/**/*.{test,spec}.ts",
"src/test/eggs/**/*.{test,spec}.ts",
"src/test/field/**/*.{test,spec}.ts",
"src/test/inputs/**/*.{test,spec}.ts",
"src/test/localization/**/*.{test,spec}.ts",
"src/test/phases/**/*.{test,spec}.ts",
"src/test/settingMenu/**/*.{test,spec}.ts",
"src/test/sprites/**/*.{test,spec}.ts",
"src/test/ui/**/*.{test,spec}.ts",
"src/test/*.{test,spec}.ts",
],
},
},
{
...defaultConfig,
test: {
...defaultProjectTestConfig,
name: "abilities",
include: ["src/test/abilities/**/*.{test,spec}.ts"],
},
},
{
...defaultConfig,
test: {
...defaultProjectTestConfig,
name: "battle",
include: ["src/test/battle/**/*.{test,spec}.ts"],
},
},
{
...defaultConfig,
test: {
...defaultProjectTestConfig,
name: "items",
include: ["src/test/items/**/*.{test,spec}.ts"],
},
},
{
...defaultConfig,
test: {
...defaultProjectTestConfig,
name: "moves",
include: ["src/test/moves/**/*.{test,spec}.ts"],
},
},
"./vitest.config.ts", "./vitest.config.ts",
]); ]);