diff --git a/src/account.ts b/src/account.ts index e5bde56bfe3..4d19513908f 100644 --- a/src/account.ts +++ b/src/account.ts @@ -7,6 +7,7 @@ export interface UserInfo { } export let loggedInUser: UserInfo = null; +// This is a random string that is used to identify the client session - unique per session (tab or window) so that the game will only save on the one that the server is expecting export const clientSessionId = Utils.randomString(32); export function initLoggedInUser(): void { diff --git a/src/data/move.ts b/src/data/move.ts index 39459d7df7f..5d9879d8465 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -3166,10 +3166,8 @@ export class FriendshipPowerAttr extends VariablePowerAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const power = args[0] as Utils.NumberHolder; - if (user instanceof PlayerPokemon) { - const friendshipPower = Math.floor(Math.min(user.friendship, 255) / 2.5); - power.value = Math.max(!this.invert ? friendshipPower : 102 - friendshipPower, 1); - } + const friendshipPower = Math.floor(Math.min(user instanceof PlayerPokemon ? user.friendship : user.species.baseFriendship, 255) / 2.5); + power.value = Math.max(!this.invert ? friendshipPower : 102 - friendshipPower, 1); return true; } @@ -7710,6 +7708,7 @@ export function initMoves() { new AttackMove(Moves.BADDY_BAD, Type.DARK, MoveCategory.SPECIAL, 80, 95, 15, -1, 0, 7) .attr(AddArenaTagAttr, ArenaTagType.REFLECT, 5, false, true), new AttackMove(Moves.SAPPY_SEED, Type.GRASS, MoveCategory.PHYSICAL, 100, 90, 10, 100, 0, 7) + .makesContact(false) .attr(AddBattlerTagAttr, BattlerTagType.SEEDED), new AttackMove(Moves.FREEZY_FROST, Type.ICE, MoveCategory.SPECIAL, 100, 90, 10, -1, 0, 7) .attr(ResetStatsAttr), diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index 58050c99d78..28e39755139 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -3795,7 +3795,7 @@ export const starterPassiveAbilities = { [Species.IRON_TREADS]: Abilities.STEELY_SPIRIT, [Species.IRON_BUNDLE]: Abilities.SNOW_WARNING, [Species.IRON_HANDS]: Abilities.IRON_FIST, - [Species.IRON_JUGULIS]: Abilities.AERILATE, + [Species.IRON_JUGULIS]: Abilities.LIGHTNING_ROD, [Species.IRON_MOTH]: Abilities.LEVITATE, [Species.IRON_THORNS]: Abilities.SAND_STREAM, [Species.FRIGIBAX]: Abilities.SNOW_WARNING, diff --git a/src/loading-scene.ts b/src/loading-scene.ts index fbe1d23f049..f6ca88192da 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -24,6 +24,8 @@ import { Biome } from "#enums/biome"; import { TrainerType } from "#enums/trainer-type"; export class LoadingScene extends SceneBase { + readonly LOAD_EVENTS = Phaser.Loader.Events; + constructor() { super("loading"); @@ -35,10 +37,6 @@ export class LoadingScene extends SceneBase { Utils.localPing(); this.load["manifest"] = this.game["manifest"]; - if (!isMobile()) { - this.load.video("intro_dark", "images/intro_dark.mp4", true); - } - this.loadImage("loading_bg", "arenas"); this.loadImage("logo", ""); this.loadImage("pride-update", "events"); @@ -421,58 +419,46 @@ export class LoadingScene extends SceneBase { }); disclaimerDescriptionText.setOrigin(0.5, 0.5); - disclaimerText.setVisible(false); - disclaimerDescriptionText.setVisible(false); - - const intro = this.add.video(0, 0); - intro.setOrigin(0, 0); - intro.setScale(3); - - this.load.on("progress", (value: string) => { - const parsedValue = parseFloat(value); - percentText.setText(`${Math.floor(parsedValue * 100)}%`); - progressBar.clear(); - progressBar.fillStyle(0xffffff, 0.8); - progressBar.fillRect(midWidth - 320, 360, 640 * parsedValue, 64); - }); - - this.load.on("fileprogress", file => { - assetText.setText(i18next.t("menu:loadingAsset", { assetName: file.key })); - }); - - loadingGraphics.push(bg, graphics, progressBar, progressBox, logo, percentText, assetText); + loadingGraphics.push(bg, graphics, progressBar, progressBox, logo, percentText, assetText, disclaimerText, disclaimerDescriptionText); if (!mobile) { loadingGraphics.map(g => g.setVisible(false)); } - const destroyLoadingAssets = () => { - intro.destroy(); - bg.destroy(); - logo.destroy(); - progressBar.destroy(); - progressBox.destroy(); - percentText.destroy(); - assetText.destroy(); - }; + const intro = this.add.video(0, 0); + intro.on(Phaser.GameObjects.Events.VIDEO_COMPLETE, (video: Phaser.GameObjects.Video) => { + this.tweens.add({ + targets: intro, + duration: 500, + alpha: 0, + ease: "Sine.easeIn", + onComplete: () => video.destroy(), + }); + loadingGraphics.forEach(g => g.setVisible(true)); + }); + intro.setOrigin(0, 0); + intro.setScale(3); - this.load.on("filecomplete", key => { + this.load.once(this.LOAD_EVENTS.START, () => { + // videos do not need to be preloaded + intro.loadURL("images/intro_dark.mp4", true); + if (mobile) { + intro.video.setAttribute("webkit-playsinline", "webkit-playsinline"); + intro.video.setAttribute("playsinline", "playsinline"); + } + intro.play(); + }); + + this.load.on(this.LOAD_EVENTS.PROGRESS , (progress: number) => { + percentText.setText(`${Math.floor(progress * 100)}%`); + progressBar.clear(); + progressBar.fillStyle(0xffffff, 0.8); + progressBar.fillRect(midWidth - 320, 360, 640 * progress, 64); + }); + + this.load.on(this.LOAD_EVENTS.FILE_COMPLETE, (key: string) => { + assetText.setText(i18next.t("menu:loadingAsset", { assetName: key })); switch (key) { - case "intro_dark": - intro.load("intro_dark"); - intro.on("complete", () => { - this.tweens.add({ - targets: intro, - duration: 500, - alpha: 0, - ease: "Sine.easeIn" - }); - loadingGraphics.map(g => g.setVisible(true)); - disclaimerText.setVisible(true); - disclaimerDescriptionText.setVisible(true); - }); - intro.play(); - break; case "loading_bg": bg.setTexture("loading_bg"); if (mobile) { @@ -488,7 +474,7 @@ export class LoadingScene extends SceneBase { } }); - this.load.on("complete", () => destroyLoadingAssets()); + this.load.on(this.LOAD_EVENTS.COMPLETE, () => loadingGraphics.forEach(go => go.destroy())); } get gameHeight() { diff --git a/src/locales/de/ability-trigger.ts b/src/locales/de/ability-trigger.ts index 652c16eb662..042b70acf8c 100644 --- a/src/locales/de/ability-trigger.ts +++ b/src/locales/de/ability-trigger.ts @@ -4,5 +4,5 @@ export const abilityTriggers: SimpleTranslationEntries = { "blockRecoilDamage" : "{{pokemonName}} wurde durch {{abilityName}}\nvor Rückstoß geschützt!", "badDreams": "{{pokemonName}} ist in einem Alptraum gefangen!", "windPowerCharged": "Der Treffer durch {{moveName}} läd die Stärke von {{pokemonName}} auf!", - "iceFaceAvoidedDamage": "{{pokemonName}} avoided\ndamage with {{abilityName}}!", + "iceFaceAvoidedDamage": "{{pokemonName}} wehrt Schaden\nmit {{abilityName}} ab!", } as const; diff --git a/src/locales/de/battle.ts b/src/locales/de/battle.ts index 86336b0e233..48f355e7ea1 100644 --- a/src/locales/de/battle.ts +++ b/src/locales/de/battle.ts @@ -62,12 +62,12 @@ export const battle: SimpleTranslationEntries = { "drainMessage": "{{pokemonName}} wurde Energie abgesaugt", "regainHealth": "KP von {{pokemonName}} wurden wieder aufgefrischt!", "fainted": "{{pokemonNameWithAffix}} wurde besiegt!", - "statRose": "rose", - "statSharplyRose": "sharply rose", - "statRoseDrastically": "rose drastically", - "statWontGoAnyHigher": "won't go any higher", - "statFell": "fell", - "statHarshlyFell": "harshly fell", - "statSeverelyFell": "severely fell", - "statWontGoAnyLower": "won't go any lower", + "statRose": "steigt", + "statSharplyRose": "steigt stark", + "statRoseDrastically": "steigt drastisch", + "statWontGoAnyHigher": "kann nicht weiter erhöht werden", + "statFell": "sinkt", + "statHarshlyFell": "sinkt stark", + "statSeverelyFell": "sinkt drastisch", + "statWontGoAnyLower": "kann nicht weiter sinken", } as const; diff --git a/src/locales/de/menu.ts b/src/locales/de/menu.ts index d8987fcf77b..d8a3c8f14e8 100644 --- a/src/locales/de/menu.ts +++ b/src/locales/de/menu.ts @@ -44,6 +44,10 @@ export const menu: SimpleTranslationEntries = { "dailyRankings": "Tägliche Rangliste", "weeklyRankings": "Wöchentliche Rangliste", "noRankings": "Keine Rangliste", + "positionIcon": "#", + "usernameScoreboard": "Benutzername", + "score": "Punkte", + "wave": "Welle", "loading": "Lade…", "loadingAsset": "Loading asset: {{assetName}}", "playersOnline": "Spieler Online", diff --git a/src/locales/de/pokemon-info.ts b/src/locales/de/pokemon-info.ts index 0d350a75c36..730a4cc4c5f 100644 --- a/src/locales/de/pokemon-info.ts +++ b/src/locales/de/pokemon-info.ts @@ -14,8 +14,8 @@ export const pokemonInfo: PokemonInfoTranslationEntries = { "SPDEFshortened": "SpVert", "SPD": "Initiative", "SPDshortened": "Init", - "ACC": "Accuracy", - "EVA": "Evasiveness" + "ACC": "Genauigkeit", + "EVA": "Fluchtwert", }, Type: { diff --git a/src/locales/de/starter-select-ui-handler.ts b/src/locales/de/starter-select-ui-handler.ts index a448dcedad8..c31f4725806 100644 --- a/src/locales/de/starter-select-ui-handler.ts +++ b/src/locales/de/starter-select-ui-handler.ts @@ -25,7 +25,9 @@ export const starterSelectUiHandler: SimpleTranslationEntries = { "addToParty": "Zum Team hinzufügen", "toggleIVs": "DVs anzeigen/verbergen", "manageMoves": "Attacken ändern", + "manageNature": "Wesen ändern", "useCandies": "Bonbons verwenden", + "selectNature": "Wähle das neue Wesen.", "selectMoveSwapOut": "Wähle die zu ersetzende Attacke.", "selectMoveSwapWith": "Wähle die gewünschte Attacke.", "unlockPassive": "Passiv-Skill freischalten", diff --git a/src/locales/en/menu.ts b/src/locales/en/menu.ts index 03b8f22332d..0b9e45aa6d9 100644 --- a/src/locales/en/menu.ts +++ b/src/locales/en/menu.ts @@ -44,6 +44,10 @@ export const menu: SimpleTranslationEntries = { "dailyRankings": "Daily Rankings", "weeklyRankings": "Weekly Rankings", "noRankings": "No Rankings", + "positionIcon": "#", + "usernameScoreboard": "Username", + "score": "Score", + "wave": "Wave", "loading": "Loading…", "loadingAsset": "Loading asset: {{assetName}}", "playersOnline": "Players Online", diff --git a/src/locales/en/starter-select-ui-handler.ts b/src/locales/en/starter-select-ui-handler.ts index fd2eb6c40df..d17f681b53c 100644 --- a/src/locales/en/starter-select-ui-handler.ts +++ b/src/locales/en/starter-select-ui-handler.ts @@ -25,7 +25,9 @@ export const starterSelectUiHandler: SimpleTranslationEntries = { "addToParty": "Add to Party", "toggleIVs": "Toggle IVs", "manageMoves": "Manage Moves", + "manageNature": "Manage Nature", "useCandies": "Use Candies", + "selectNature": "Select nature.", "selectMoveSwapOut": "Select a move to swap out.", "selectMoveSwapWith": "Select a move to swap with", "unlockPassive": "Unlock Passive", diff --git a/src/locales/es/menu.ts b/src/locales/es/menu.ts index 4bd6d750d69..a201bc91e77 100644 --- a/src/locales/es/menu.ts +++ b/src/locales/es/menu.ts @@ -44,6 +44,10 @@ export const menu: SimpleTranslationEntries = { "dailyRankings": "Rankings Diarios", "weeklyRankings": "Rankings Semanales", "noRankings": "Sin Rankings", + "positionIcon": "#", + "usernameScoreboard": "Username", + "score": "Score", + "wave": "Wave", "loading": "Cargando…", "loadingAsset": "Cargando recurso: {{assetName}}", "playersOnline": "Jugadores en Línea", diff --git a/src/locales/es/starter-select-ui-handler.ts b/src/locales/es/starter-select-ui-handler.ts index 4d025820260..9b6e03b26c0 100644 --- a/src/locales/es/starter-select-ui-handler.ts +++ b/src/locales/es/starter-select-ui-handler.ts @@ -25,7 +25,9 @@ export const starterSelectUiHandler: SimpleTranslationEntries = { "addToParty": "Añadir a Equipo", "toggleIVs": "Mostrar IVs", "manageMoves": "Gestionar Movs.", + "manageNature": "Gestionar Natur", "useCandies": "Usar Caramelos", + "selectNature": "Elige Natur.", "selectMoveSwapOut": "Elige el movimiento que sustituir.", "selectMoveSwapWith": "Elige el movimiento que sustituirá a", "unlockPassive": "Añadir Pasiva", diff --git a/src/locales/fr/menu.ts b/src/locales/fr/menu.ts index f9538e9d26c..4d37bde35d8 100644 --- a/src/locales/fr/menu.ts +++ b/src/locales/fr/menu.ts @@ -39,6 +39,10 @@ export const menu: SimpleTranslationEntries = { "dailyRankings": "Classement du Jour", "weeklyRankings": "Classement de la Semaine", "noRankings": "Pas de Classement", + "positionIcon": "#", + "usernameScoreboard": "Username", + "score": "Score", + "wave": "Wave", "loading": "Chargement…", "loadingAsset": "Loading asset: {{assetName}}", "playersOnline": "Joueurs Connectés", diff --git a/src/locales/fr/starter-select-ui-handler.ts b/src/locales/fr/starter-select-ui-handler.ts index 9f504cab11e..02e5c2742ac 100644 --- a/src/locales/fr/starter-select-ui-handler.ts +++ b/src/locales/fr/starter-select-ui-handler.ts @@ -23,9 +23,11 @@ export const starterSelectUiHandler: SimpleTranslationEntries = { "eggMoves": "Capacités Œuf", "start": "Lancer", "addToParty": "Ajouter à l’équipe", - "toggleIVs": "Voir IVs", - "manageMoves": "Gérer Capacités", - "useCandies": "Utiliser Bonbons", + "toggleIVs": "Voir les IV", + "manageMoves": "Modifier les Capacités", + "manageNature": "Modifier la Nature", + "useCandies": "Utiliser des Bonbons", + "selectNature": "Sélectionnez une nature.", "selectMoveSwapOut": "Sélectionnez la capacité à échanger.", "selectMoveSwapWith": "Sélectionnez laquelle échanger avec", "unlockPassive": "Débloquer Passif", diff --git a/src/locales/it/menu.ts b/src/locales/it/menu.ts index 07af8ee70e9..acfe68403ec 100644 --- a/src/locales/it/menu.ts +++ b/src/locales/it/menu.ts @@ -39,6 +39,10 @@ export const menu: SimpleTranslationEntries = { "dailyRankings": "Classifica giornaliera", "weeklyRankings": "Classifica settimanale", "noRankings": "Nessuna classifica", + "positionIcon": "#", + "usernameScoreboard": "Username", + "score": "Score", + "wave": "Wave", "loading": "Caricamento…", "loadingAsset": "Caricamento asset: {{assetName}}", "playersOnline": "Giocatori online", diff --git a/src/locales/it/starter-select-ui-handler.ts b/src/locales/it/starter-select-ui-handler.ts index 48a0badaefc..fea0d2d8352 100644 --- a/src/locales/it/starter-select-ui-handler.ts +++ b/src/locales/it/starter-select-ui-handler.ts @@ -25,7 +25,9 @@ export const starterSelectUiHandler: SimpleTranslationEntries = { "addToParty": "Aggiungi al gruppo", "toggleIVs": "Vedi/Nascondi IV", "manageMoves": "Gestisci mosse", + "manageNature": "Gestisci natura", "useCandies": "Usa caramelle", + "selectNature": "Seleziona natura.", "selectMoveSwapOut": "Seleziona una mossa da scambiare.", "selectMoveSwapWith": "Seleziona una mossa da scambiare con", "unlockPassive": "Sblocca passiva", diff --git a/src/locales/it/tutorial.ts b/src/locales/it/tutorial.ts index 8488fc3151f..e5bee8ae2be 100644 --- a/src/locales/it/tutorial.ts +++ b/src/locales/it/tutorial.ts @@ -2,41 +2,41 @@ import { SimpleTranslationEntries } from "#app/plugins/i18n"; export const tutorial: SimpleTranslationEntries = { "intro": `Benvenuto in PokéRogue! Questo gioco si concentra sulle battaglie, con elementi roguelite. - $Questo gioco non è monetizzato e non siamo proprietari di Pokemon e Assets presenti nel gioco. - $Il gioco è work-in-progress ma giocabile al 100%.\nPer reportare eventuali bugs è possibile discuterne sul nostro Discord. - $Se il game risulta 'lento', assicurati di aver abilitato l'Accelerazione Hardware nelle impostazioni del tuo Browser`, + $Questo gioco non è monetizzato e non siamo proprietari di Pokémon ed assets presenti nel gioco. + $Il progetto è work-in-progress, ma giocabile al 100%.\nPer segnalare eventuali bug è possibile contattarci al nostro apposito Discord. + $Se il gioco risulta 'lento', assicurati di aver abilitato l'accelerazione hardware nelle impostazioni del tuo browser`, - "accessMenu": "Per accedere al menù, press M o Esc.\nDal menù puoi cambiare impostazioni, controllare la wiki e accedere a varie features.", + "accessMenu": "Per accedere al menu, premi M o esc.\nDal menu puoi modificare le impostazioni, controllare la wiki ed accedere a varie features.", - "menu": `Da questo menù puoi accedere alle impostazioni. - $Dalle impostazioni puoi cambiare velocità di gioco, stile di finestra e altre opzioni. - $Ci sono varie funzionalità, controlla bene e non perderti nulla!`, + "menu": `Da questo menu puoi accedere alle impostazioni. + $Esse ti permettono di cambiare velocità di gioco, stile delle finestre ed altre opzioni. + $Ci sono varie funzionalità: controlla bene e non perderti nulla!`, - "starterSelect": `Da questa schermata puoi selezionare il tuo starter.\nQuesti sono i membri iniziali del tuo parti. + "starterSelect": `Da questa schermata puoi selezionare il tuo starter.\nQuesti sono i membri iniziali della tua squadra. $Ogni starter ha un valore. Puoi avere fino a \n6 Pokèmon, avendo a disposizione un massimo di 10 punti. - $Puoi anche selezionare Sesso, Abilità, e Forma a seconda delle\nvarianti che hai catturato o schiuso. + $Puoi anche selezionare genere, abilità, e forma a seconda delle\nvarianti che hai catturato o schiuso. $Le IVs di una specie sono le migliori rispetto a tutte quelle che hai\ncatturato o schiuso, quindi prova a catturarne il piu possibile!`, - "pokerus": `Giornalmente 3 Starter casuali disponibili avranno il bordo viola. - $Se possiedi uno di questi starter,\nprova ad aggiungerlo al party. Ricorda di controllare le info!`, + "pokerus": `Giornalmente 3 starter casuali disponibili avranno il bordo viola. + $Se possiedi uno di questi starter,\nprova ad aggiungerlo alla squadra. Ricorda di controllarne le info!`, - "statChange": `I cambiamenti alle statistiche persistono fintanto che i tuoi pokèmon resteranno in campo. + "statChange": `I cambiamenti alle statistiche persistono fintanto che i tuoi pokèmon restano in campo. $I tuoi pokemon verranno richiamati quando incontrerai un allenatore o al cambiamento di bioma. $Puoi anche vedere i cambiamenti alle statistiche in corso tenendo premuto C o Shift`, - "selectItem": `Dopo ogni battaglia avrai disponibili tre item.\nPotrai prenderne solo uno. - $Questi spaziano tra consumabili, item tenuti da Pokèmon o con un effetto passivo permanente. - $La maggior parte degli Item non Consumabili possono stackare in diversi modi. - $Alcuni Item risulteranno disponibili solo se possono essere usati, come Item Evolutivi. - $Puoi anche passare un Item tenuto da un Pokèmon ad un altro attraverso l'opzione 'trasferisci strumento'. - $L'opzione 'trasferisci strumento' sarà disponibile solo dopo aver assegnato uno strumento ad un Pokèmon. - $Puoi acquistare consumabili con le monete, progredendo saranno poi disponibili ulteriori oggetti. - $Assicurati di fare un acquisto prima di selezionare un item casuale, poichè passerai subito alla lotta successiva.`, + "selectItem": `Dopo ogni battaglia potrai scegliere tra 3 oggetti.\nPotrai prenderne solo uno. + $Questi spaziano tra consumabili, oggetti tenuti da Pokèmon o con un effetto passivo permanente. + $La maggior parte degli oggetti non consumabili possono accumulare i loro effetti in diversi modi. + $Alcuni risulteranno inoltre disponibili solo se possono essere usati, come ad esempio gli oggetti evolutivi. + $Puoi anche passare un oggetto tenuto da un Pokèmon a un altro attraverso l'opzione 'trasferisci strumento'. + $Quest'ultima sarà disponibile solo dopo aver assegnato uno strumento ad un Pokèmon. + $Puoi acquistare consumabili con le monete; progredendo saranno poi disponibili ulteriori oggetti. + $Assicurati di fare un acquisto prima di selezionare un item casuale, poichè dopo aver fatto ciò passerai subito alla lotta successiva.`, - "eggGacha": `Da questa schermata, puoi riscattare i tuoi vouchers in cambio di\nuova Pokèmon. - $Le uova vanno schiuse e saranno sempre più vicine alla schiusura dopo\nogni battaglia. Le uova più rare impiegheranno più battaglie per la schiusura. - $I Pokémon schiusi non verranno aggiunti alla tua squadra, saranno\naggiunti ai tuoi starters. - $I Pokémon schiusi generalmente hanno IVs migliori rispetto ai\n Pokémon selvatici. - $Alcuni Pokémon possono essere ottenuti solo tramite uova. + "eggGacha": `Da questa schermata puoi riscattare i tuoi vouchers in cambio di\nuova Pokèmon. + $Le uova vanno schiuse, e saranno sempre più vicine alla schiusura dopo\nogni battaglia. Le uova più rare impiegheranno più battaglie per la schiusura. + $I Pokémon schiusi non verranno aggiunti alla tua squadra, ma saranno\ninvece aggiunti ai tuoi starters. + $I Pokémon schiusi hanno (generalmente) IVs migliori rispetto ai\n Pokémon selvatici. + $Inoltre, alcuni Pokémon possono essere ottenuti solo tramite uova. $Ci sono 3 diversi macchinari con differenti\nbonus, scegli quello che preferisci!`, } as const; diff --git a/src/locales/ko/battle.ts b/src/locales/ko/battle.ts index d9774bf1c06..e9be8a7fa43 100644 --- a/src/locales/ko/battle.ts +++ b/src/locales/ko/battle.ts @@ -62,12 +62,12 @@ export const battle: SimpleTranslationEntries = { "drainMessage": "{{pokemonName}}[[로]]부터\n체력을 흡수했다!", "regainHealth": "{{pokemonName}}[[는]]\n기력을 회복했다!", "fainted": "{{pokemonNameWithAffix}}[[는]] 쓰러졌다!", - "statRose": "상승했다", - "statSharplyRose": "약간 상승했다", - "statRoseDrastically": "대폭 상승했다", - "statWontGoAnyHigher": "더 이상 상승할 수 없다", - "statFell": "떨어졌다", - "statHarshlyFell": "약간 떨어졌다", - "statSeverelyFell": "대폭 떨어졌다", - "statWontGoAnyLower": "더 이상 떨어질 수 없다", + "statRose": "[[가]] 올라갔다!", + "statSharplyRose": "[[가]] 크게 올라갔다!", + "statRoseDrastically": "[[가]] 매우 크게 올라갔다!", + "statWontGoAnyHigher": "[[는]] 더 올라가지 않는다!", + "statFell": "[[가]] 떨어졌다!", + "statHarshlyFell": "[[가]] 크게 떨어졌다!", + "statSeverelyFell": "[[가]] 매우 크게 떨어졌다!", + "statWontGoAnyLower": "[[는]] 더 떨어지지 않는다!", } as const; diff --git a/src/locales/ko/menu.ts b/src/locales/ko/menu.ts index 9245d67533a..2ad8b0c9e14 100644 --- a/src/locales/ko/menu.ts +++ b/src/locales/ko/menu.ts @@ -44,6 +44,10 @@ export const menu: SimpleTranslationEntries = { "dailyRankings": "일간 랭킹", "weeklyRankings": "주간 랭킹", "noRankings": "랭킹 정보 없음", + "positionIcon": "#", + "usernameScoreboard": "이름", + "score": "점수", + "wave": "웨이브", "loading": "로딩 중…", "loadingAsset": "Loading asset: {{assetName}}", "playersOnline": "플레이어 온라인", diff --git a/src/locales/pt_BR/menu.ts b/src/locales/pt_BR/menu.ts index f4fc8cc3a72..1743154c0d0 100644 --- a/src/locales/pt_BR/menu.ts +++ b/src/locales/pt_BR/menu.ts @@ -44,6 +44,10 @@ export const menu: SimpleTranslationEntries = { "dailyRankings": "Classificação Diária", "weeklyRankings": "Classificação Semanal", "noRankings": "Sem Classificação", + "positionIcon": "#", + "usernameScoreboard": "Usuário", + "score": "Pontuação", + "wave": "Onda", "loading": "Carregando…", "loadingAsset": "Carregando recurso: {{assetName}}", "playersOnline": "Jogadores Ativos", diff --git a/src/locales/pt_BR/starter-select-ui-handler.ts b/src/locales/pt_BR/starter-select-ui-handler.ts index fc98e72c614..7ee8789f860 100644 --- a/src/locales/pt_BR/starter-select-ui-handler.ts +++ b/src/locales/pt_BR/starter-select-ui-handler.ts @@ -25,7 +25,9 @@ export const starterSelectUiHandler: SimpleTranslationEntries = { "addToParty": "Adicionar à equipe", "toggleIVs": "Mostrar IVs", "manageMoves": "Mudar Movimentos", + "manageNature": "Mudar Natureza", "useCandies": "Usar Doces", + "selectNature": "Escolha Natureza.", "selectMoveSwapOut": "Escolha um movimento para substituir.", "selectMoveSwapWith": "Escolha o movimento que substituirá", "unlockPassive": "Aprender Passiva", diff --git a/src/locales/zh_CN/ability-trigger.ts b/src/locales/zh_CN/ability-trigger.ts index 07fedd8ac3e..4efc3cdb0b5 100644 --- a/src/locales/zh_CN/ability-trigger.ts +++ b/src/locales/zh_CN/ability-trigger.ts @@ -2,7 +2,7 @@ import { SimpleTranslationEntries } from "#app/plugins/i18n"; export const abilityTriggers: SimpleTranslationEntries = { "blockRecoilDamage" : "{{pokemonName}} 的 {{abilityName}}\n抵消了反作用力!", - "badDreams": "{{pokemonName}} 被折磨着!", + "badDreams": "{{pokemonName}} 被折磨着!", "windPowerCharged": "受 {{moveName}} 的影响, {{pokemonName}} 提升了能力!", - "iceFaceAvoidedDamage": "{{pokemonName}} avoided\ndamage with {{abilityName}}!" + "iceFaceAvoidedDamage": "{{pokemonName}} 因为 {{abilityName}}\n避免了伤害!" } as const; diff --git a/src/locales/zh_CN/battle.ts b/src/locales/zh_CN/battle.ts index 6d911b7b502..8c607a6ade6 100644 --- a/src/locales/zh_CN/battle.ts +++ b/src/locales/zh_CN/battle.ts @@ -56,18 +56,18 @@ export const battle: SimpleTranslationEntries = { "skipItemQuestion": "你确定要跳过拾取道具吗?", "eggHatching": "咦?", "ivScannerUseQuestion": "对 {{pokemonName}} 使用个体值扫描仪?", - "wildPokemonWithAffix": "Wild {{pokemonName}}", - "foePokemonWithAffix": "Foe {{pokemonName}}", - "useMove": "{{pokemonNameWithAffix}} used {{moveName}}!", - "drainMessage": "{{pokemonName}} had its\nenergy drained!", - "regainHealth": "{{pokemonName}} regained\nhealth!", - "fainted": "{{pokemonNameWithAffix}} fainted!", - "statRose": "rose", - "statSharplyRose": "sharply rose", - "statRoseDrastically": "rose drastically", - "statWontGoAnyHigher": "won't go any higher", - "statFell": "fell", - "statHarshlyFell": "harshly fell", - "statSeverelyFell": "severely fell", - "statWontGoAnyLower": "won't go any lower", + "wildPokemonWithAffix": "野生的 {{pokemonName}}", + "foePokemonWithAffix": "对手 {{pokemonName}}", + "useMove": "{{pokemonNameWithAffix}} 使用了 {{moveName}}!", + "drainMessage": "{{pokemonName}} 吸取了体力!", + "regainHealth": "{{pokemonName}} 回复了体力!", + "fainted": "{{pokemonNameWithAffix}} 倒下了!", + "statRose": "提高了!", + "statSharplyRose": "大幅提高了!", + "statRoseDrastically": "极大幅提高了!", + "statWontGoAnyHigher": "已经无法再提高了!", + "statFell": "降低了!", + "statHarshlyFell": "大幅降低了!", + "statSeverelyFell": "极大幅降低了!", + "statWontGoAnyLower": "已经无法再降低了!", } as const; diff --git a/src/locales/zh_CN/fight-ui-handler.ts b/src/locales/zh_CN/fight-ui-handler.ts index 7ccab7561da..43b59d09170 100644 --- a/src/locales/zh_CN/fight-ui-handler.ts +++ b/src/locales/zh_CN/fight-ui-handler.ts @@ -4,6 +4,6 @@ export const fightUiHandler: SimpleTranslationEntries = { "pp": "PP", "power": "威力", "accuracy": "命中", - "abilityFlyInText": " {{pokemonName}}'s {{passive}}{{abilityName}}", - "passive": "Passive ", // The space at the end is important + "abilityFlyInText": " {{pokemonName}} 的 {{passive}}{{abilityName}}", + "passive": "被动 ", // The space at the end is important } as const; diff --git a/src/locales/zh_CN/game-mode.ts b/src/locales/zh_CN/game-mode.ts index be342b4c390..3a9ad92fc84 100644 --- a/src/locales/zh_CN/game-mode.ts +++ b/src/locales/zh_CN/game-mode.ts @@ -1,10 +1,10 @@ import { SimpleTranslationEntries } from "#app/plugins/i18n"; export const gameMode: SimpleTranslationEntries = { - "classic": "Classic", - "endless": "Endless", - "endlessSpliced": "Endless (Spliced)", - "dailyRun": "Daily Run", - "unknown": "Unknown", - "challenge": "Challenge", + "classic": "经典模式", + "endless": "无尽模式", + "endlessSpliced": "融合无尽模式", + "dailyRun": "每日挑战", + "unknown": "未知", + "challenge": "挑战模式", } as const; diff --git a/src/locales/zh_CN/growth.ts b/src/locales/zh_CN/growth.ts index 33a1dec8bb8..7297554bd98 100644 --- a/src/locales/zh_CN/growth.ts +++ b/src/locales/zh_CN/growth.ts @@ -1,10 +1,10 @@ import { SimpleTranslationEntries } from "#app/plugins/i18n"; export const growth: SimpleTranslationEntries = { - "Erratic": "最快", - "Fast": "较快", - "Medium_Fast": "快", - "Medium_Slow": "慢", - "Slow": "较慢", - "Fluctuating": "最慢" -} as const; + "Erratic": "非常快", + "Fast": "快", + "Medium_Fast": "较快", + "Medium_Slow": "较慢", + "Slow": "慢", + "Fluctuating": "非常慢" +} as const; diff --git a/src/locales/zh_CN/menu.ts b/src/locales/zh_CN/menu.ts index 39d8e5e3a04..4dd2a4cbe33 100644 --- a/src/locales/zh_CN/menu.ts +++ b/src/locales/zh_CN/menu.ts @@ -44,6 +44,10 @@ export const menu: SimpleTranslationEntries = { "dailyRankings": "每日排名", "weeklyRankings": "每周排名", "noRankings": "无排名", + "positionIcon": "#", + "usernameScoreboard": "Username", + "score": "Score", + "wave": "Wave", "loading": "加载中...", "loadingAsset": "Loading asset: {{assetName}}", "playersOnline": "在线玩家", diff --git a/src/locales/zh_CN/pokemon-info.ts b/src/locales/zh_CN/pokemon-info.ts index 7c72acb1b2a..7f3394219fb 100644 --- a/src/locales/zh_CN/pokemon-info.ts +++ b/src/locales/zh_CN/pokemon-info.ts @@ -14,8 +14,8 @@ export const pokemonInfo: PokemonInfoTranslationEntries = { "SPDEFshortened": "特防", "SPD": "速度", "SPDshortened": "速度", - "ACC": "Accuracy", - "EVA": "Evasiveness" + "ACC": "命中率", + "EVA": "回避率" }, Type: { diff --git a/src/locales/zh_CN/save-slot-select-ui-handler.ts b/src/locales/zh_CN/save-slot-select-ui-handler.ts index 0d657235e49..657c2dccb3a 100644 --- a/src/locales/zh_CN/save-slot-select-ui-handler.ts +++ b/src/locales/zh_CN/save-slot-select-ui-handler.ts @@ -1,9 +1,9 @@ import { SimpleTranslationEntries } from "#app/plugins/i18n"; export const saveSlotSelectUiHandler: SimpleTranslationEntries = { - "overwriteData": "Overwrite the data in the selected slot?", + "overwriteData": "要覆盖该槽位的存档吗?", "loading": "正在加载中...", - "wave": "Wave", + "wave": "层数", "lv": "Lv", "empty": "空", } as const; diff --git a/src/locales/zh_CN/starter-select-ui-handler.ts b/src/locales/zh_CN/starter-select-ui-handler.ts index 803e64e0439..742e3c815f0 100644 --- a/src/locales/zh_CN/starter-select-ui-handler.ts +++ b/src/locales/zh_CN/starter-select-ui-handler.ts @@ -25,7 +25,9 @@ export const starterSelectUiHandler: SimpleTranslationEntries = { "addToParty": "加入队伍", "toggleIVs": "切换个体值", "manageMoves": "管理招式", + "manageNature": "管理性格", "useCandies": "使用糖果", + "selectNature": "选择性格", "selectMoveSwapOut": "选择要替换的招式。", "selectMoveSwapWith": "选择要替换成的招式", "unlockPassive": "解锁被动", diff --git a/src/locales/zh_CN/trainers.ts b/src/locales/zh_CN/trainers.ts index 28b0760cc9b..8238978781e 100644 --- a/src/locales/zh_CN/trainers.ts +++ b/src/locales/zh_CN/trainers.ts @@ -48,7 +48,7 @@ export const trainerClasses: SimpleTranslationEntries = { "depot_agent": "铁路员工", "doctor": "医生", "doctor_female": "医生", - "firebreather": "Firebreather", + "firebreather": "吹火人", "fisherman": "垂钓者", "fisherman_female": "垂钓者", "gentleman": "绅士", @@ -87,13 +87,13 @@ export const trainerClasses: SimpleTranslationEntries = { "pokémon_rangers": "宝可梦巡护员组合", "ranger": "巡护员", "restaurant_staff": "服务生组合", - "rich": "Rich", - "rich_female": "Rich", + "rich": "富豪", + "rich_female": "富豪太太", "rich_boy": "富家少爷", "rich_couple": "富豪夫妇", - "rich_kid": "Rich Kid", - "rich_kid_female": "Rich Kid", - "rich_kids": "富二代组合", + "rich_kid": "富家小孩", + "rich_kid_female": "富家小孩", + "rich_kids": "富家小孩组合", "roughneck": "光头男", "sailor": "水手", "scientist": "研究员", diff --git a/src/locales/zh_TW/menu.ts b/src/locales/zh_TW/menu.ts index d16052f2ac7..a967c0d8c16 100644 --- a/src/locales/zh_TW/menu.ts +++ b/src/locales/zh_TW/menu.ts @@ -44,6 +44,10 @@ export const menu: SimpleTranslationEntries = { "dailyRankings": "每日排名", "weeklyRankings": "每週排名", "noRankings": "無排名", + "positionIcon": "#", + "usernameScoreboard": "Username", + "score": "Score", + "wave": "Wave", "loading": "加載中…", "loadingAsset": "Loading asset: {{assetName}}", "playersOnline": "在線玩家", diff --git a/src/locales/zh_TW/starter-select-ui-handler.ts b/src/locales/zh_TW/starter-select-ui-handler.ts index c28cb39b94b..748cb05af40 100644 --- a/src/locales/zh_TW/starter-select-ui-handler.ts +++ b/src/locales/zh_TW/starter-select-ui-handler.ts @@ -25,7 +25,9 @@ export const starterSelectUiHandler: SimpleTranslationEntries = { "addToParty": "加入隊伍", "toggleIVs": "查看個體值", "manageMoves": "管理技能", + "manageNature": "管理性格", "useCandies": "使用糖果", + "selectNature": "選擇性格", "selectMoveSwapOut": "選擇想要替換走的招式", "selectMoveSwapWith": "選擇想要替換成的招式", "unlockPassive": "解鎖被動", diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index ac7452ae4c9..4d2d2765eac 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -1017,6 +1017,30 @@ export class EnemyEndureChanceModifierType extends ModifierType { export type ModifierTypeFunc = () => ModifierType; type WeightedModifierTypeWeightFunc = (party: Pokemon[], rerollCount?: integer) => integer; +/** + * High order function that returns a WeightedModifierTypeWeightFunc that will only be applied on + * classic and skip an ModifierType if current wave is greater or equal to the one passed down + * @param wave - Wave where we should stop showing the modifier + * @param defaultWeight - ModifierType default weight + * @returns A WeightedModifierTypeWeightFunc + */ +function skipInClassicAfterWave(wave: integer, defaultWeight: integer): WeightedModifierTypeWeightFunc { + return (party: Pokemon[]) => { + const gameMode = party[0].scene.gameMode; + const currentWave = party[0].scene.currentBattle.waveIndex; + return gameMode.isClassic && currentWave >= wave ? 0 : defaultWeight; + }; +} + +/** + * High order function that returns a WeightedModifierTypeWeightFunc that will only be applied on + * classic and it will skip a ModifierType if it is the last wave pull. + * @param defaultWeight ModifierType default weight + * @returns A WeightedModifierTypeWeightFunc + */ +function skipInLastClassicWaveOrDefault(defaultWeight: integer) : WeightedModifierTypeWeightFunc { + return skipInClassicAfterWave(199, defaultWeight); +} class WeightedModifierType { public modifierType: ModifierType; public weight: integer | WeightedModifierTypeWeightFunc; @@ -1313,7 +1337,7 @@ const modifierPool: ModifierPool = { }, 3), new WeightedModifierType(modifierTypes.DIRE_HIT, 4), new WeightedModifierType(modifierTypes.SUPER_LURE, 4), - new WeightedModifierType(modifierTypes.NUGGET, 5), + new WeightedModifierType(modifierTypes.NUGGET, skipInLastClassicWaveOrDefault(5)), new WeightedModifierType(modifierTypes.EVOLUTION_ITEM, (party: Pokemon[]) => { return Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 15), 8); }, 8), @@ -1336,7 +1360,7 @@ const modifierPool: ModifierPool = { [ModifierTier.ULTRA]: [ new WeightedModifierType(modifierTypes.ULTRA_BALL, 24), new WeightedModifierType(modifierTypes.MAX_LURE, 4), - new WeightedModifierType(modifierTypes.BIG_NUGGET, 12), + new WeightedModifierType(modifierTypes.BIG_NUGGET, skipInLastClassicWaveOrDefault(12)), new WeightedModifierType(modifierTypes.PP_UP, 9), new WeightedModifierType(modifierTypes.PP_MAX, 3), new WeightedModifierType(modifierTypes.MINT, 4), @@ -1371,7 +1395,7 @@ const modifierPool: ModifierPool = { }), [ModifierTier.ROGUE]: [ new WeightedModifierType(modifierTypes.ROGUE_BALL, 24), - new WeightedModifierType(modifierTypes.RELIC_GOLD, 2), + new WeightedModifierType(modifierTypes.RELIC_GOLD, skipInLastClassicWaveOrDefault(2)), new WeightedModifierType(modifierTypes.LEFTOVERS, 3), new WeightedModifierType(modifierTypes.SHELL_BELL, 3), new WeightedModifierType(modifierTypes.BERRY_POUCH, 4), diff --git a/src/phases.ts b/src/phases.ts index ad5819edf30..f4c32284f8c 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -94,7 +94,14 @@ export class LoginPhase extends Phase { this.scene.playSound("menu_open"); const loadData = () => { - updateUserInfo().then(() => this.scene.gameData.loadSystem().then(() => this.end())); + updateUserInfo().then(success => { + if (!success[0]) { + Utils.setCookie(Utils.sessionIdKey, ""); + this.scene.reset(true, true); + return; + } + this.scene.gameData.loadSystem().then(() => this.end()); + }); }; this.scene.ui.setMode(Mode.LOGIN_FORM, { @@ -108,7 +115,14 @@ export class LoginPhase extends Phase { buttonActions: [ () => { this.scene.ui.playSelect(); - updateUserInfo().then(() => this.end()); + updateUserInfo().then(success => { + if (!success[0]) { + Utils.setCookie(Utils.sessionIdKey, ""); + this.scene.reset(true, true); + return; + } + this.end(); + } ); }, () => { this.scene.unshiftPhase(new LoginPhase(this.scene, false)); this.end(); @@ -118,6 +132,9 @@ export class LoginPhase extends Phase { } ] }); + } else if (statusCode === 401) { + Utils.setCookie(Utils.sessionIdKey, ""); + this.scene.reset(true, true); } else { this.scene.unshiftPhase(new UnavailablePhase(this.scene)); super.end(); @@ -4198,7 +4215,8 @@ export class GameOverPhase extends BattlePhase { If Offline, execute offlineNewClear(), a localStorage implementation of newClear daily run checks */ if (this.victory) { if (!Utils.isLocal) { - Utils.apiFetch(`savedata/session/newclear?slot=${this.scene.sessionSlotId}&clientSessionId=${clientSessionId}`, true) .then(response => response.json()) + Utils.apiFetch(`savedata/session/newclear?slot=${this.scene.sessionSlotId}&clientSessionId=${clientSessionId}`, true) + .then(response => response.json()) .then(newClear => doGameOver(newClear)); } else { this.scene.gameData.offlineNewClear(this.scene).then(result => { diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 9fb253df092..f36bf1af229 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -136,7 +136,7 @@ interface VoucherUnlocks { } export interface VoucherCounts { - [type: string]: integer; + [type: string]: integer; } export interface DexData { @@ -187,6 +187,46 @@ export interface StarterMoveData { [key: integer]: StarterMoveset | StarterFormMoveData } +export interface StarterAttributes { + nature?: integer; + ability?: integer; + variant?: integer; + form?: integer; + female?: boolean; +} + +export interface StarterPreferences { + [key: integer]: StarterAttributes; +} + +// the latest data saved/loaded for the Starter Preferences. Required to reduce read/writes. Initialize as "{}", since this is the default value and no data needs to be stored if present. +// if they ever add private static variables, move this into StarterPrefs +const StarterPrefers_DEFAULT : string = "{}"; +let StarterPrefers_private_latest : string = StarterPrefers_DEFAULT; + +// This is its own class as StarterPreferences... +// - don't need to be loaded on startup +// - isn't stored with other data +// - don't require to be encrypted +// - shouldn't require calls outside of the starter selection +export class StarterPrefs { + // called on starter selection show once + static load(): StarterPreferences { + return JSON.parse( + StarterPrefers_private_latest = (localStorage.getItem(`starterPrefs_${loggedInUser?.username}`) || StarterPrefers_DEFAULT) + ); + } + + // called on starter selection clear, always + static save(prefs: StarterPreferences): void { + const pStr : string = JSON.stringify(prefs); + if (pStr !== StarterPrefers_private_latest) { + // something changed, store the update + localStorage.setItem(`starterPrefs_${loggedInUser?.username}`, pStr); + } + } +} + export interface StarterDataEntry { moveset: StarterMoveset | StarterFormMoveData; eggMoves: integer; @@ -1261,7 +1301,7 @@ export class GameData { if (!bypassLogin && dataType < GameDataType.SETTINGS) { updateUserInfo().then(success => { - if (!success) { + if (!success[0]) { return displayError(`Could not contact the server. Your ${dataName} data could not be imported.`); } let url: string; diff --git a/src/test/lokalisation/battle-stat.test.ts b/src/test/lokalisation/battle-stat.test.ts index 99269c6ec4c..cd21f638258 100644 --- a/src/test/lokalisation/battle-stat.test.ts +++ b/src/test/lokalisation/battle-stat.test.ts @@ -21,6 +21,7 @@ import { pokemonInfo as zhTwPokemonInfo } from "#app/locales/zh_TW/pokemon-info. import { battle as zhTwBattleStat } from "#app/locales/zh_TW/battle.js"; import i18next, {initI18n} from "#app/plugins/i18n"; +import { KoreanPostpositionProcessor } from "i18next-korean-postposition-processor"; interface BattleStatTestUnit { stat: BattleStat, @@ -198,7 +199,9 @@ describe("Test for BattleStat Localization", () => { it("Test getBattleStatLevelChangeDescription() in 한국어", async () => { i18next.changeLanguage("ko", () => { battleStatLevelUnits.forEach(unit => { - testBattleStatLevelChangeDescription(unit.levels, unit.up, koBattleStat[unit.key]); + const processor = new KoreanPostpositionProcessor(); + const message = processor.process(koBattleStat[unit.key]); + testBattleStatLevelChangeDescription(unit.levels, unit.up, message); }); }); }); diff --git a/src/ui/daily-run-scoreboard.ts b/src/ui/daily-run-scoreboard.ts index aa0cce62525..212d7b2a66c 100644 --- a/src/ui/daily-run-scoreboard.ts +++ b/src/ui/daily-run-scoreboard.ts @@ -1,8 +1,8 @@ +import i18next from "i18next"; import BattleScene from "../battle-scene"; +import * as Utils from "../utils"; import { TextStyle, addTextObject } from "./text"; import { WindowVariant, addWindow } from "./ui-theme"; -import * as Utils from "../utils"; -import i18next from "i18next"; interface RankingEntry { rank: integer, @@ -154,7 +154,7 @@ export class DailyRunScoreboard extends Phaser.GameObjects.Container { return entryContainer; }; - this.rankingsContainer.add(getEntry("#", "Username", "Score", "Wave")); + this.rankingsContainer.add(getEntry(i18next.t("menu:positionIcon"), i18next.t("menu:usernameScoreboard"), i18next.t("menu:score"), i18next.t("menu:wave"))); rankings.forEach((r: RankingEntry, i: integer) => { const entryContainer = getEntry(r.rank.toString(), r.username, r.score.toString(), r.wave.toString()); diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 0aa71de7e90..b9fd5c5c27e 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -17,7 +17,7 @@ import PokemonSpecies, { allSpecies, getPokemonSpecies, getPokemonSpeciesForm, g import { Type } from "../data/type"; import { GameModes } from "../game-mode"; import { SelectChallengePhase, TitlePhase } from "../phases"; -import { AbilityAttr, DexAttr, DexAttrProps, DexEntry, StarterFormMoveData, StarterMoveset } from "../system/game-data"; +import { AbilityAttr, DexAttr, DexAttrProps, DexEntry, StarterFormMoveData, StarterMoveset, StarterAttributes, StarterPreferences, StarterPrefs } from "../system/game-data"; import { Tutorial, handleTutorial } from "../tutorial"; import * as Utils from "../utils"; import { OptionSelectItem } from "./abstact-option-select-ui-handler"; @@ -270,6 +270,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler { private starterSelectCallback: StarterSelectCallback; + private starterPreferences: StarterPreferences; + protected blockInput: boolean = false; constructor(scene: BattleScene) { @@ -780,6 +782,10 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } show(args: any[]): boolean { + if (!this.starterPreferences) { + // starterPreferences haven't been loaded yet + this.starterPreferences = StarterPrefs.load(); + } this.moveInfoOverlay.clear(); // clear this when removing a menu; the cancel button doesn't seem to trigger this automatically on controllers if (args.length >= 1 && args[0] instanceof Function) { super.show(args); @@ -1223,6 +1229,54 @@ export default class StarterSelectUiHandler extends MessageUiHandler { }); } const starterData = this.scene.gameData.starterData[this.lastSpecies.speciesId]; + let starterAttributes = this.starterPreferences[this.lastSpecies.speciesId]; + if (this.canCycleNature) { + // if we could cycle natures, enable the improved nature menu + const showNatureOptions = () => { + ui.setMode(Mode.STARTER_SELECT).then(() => { + ui.showText(i18next.t("starterSelectUiHandler:selectNature"), null, () => { + const natures = this.scene.gameData.getNaturesForAttr(this.speciesStarterDexEntry.natureAttr); + ui.setModeWithoutClear(Mode.OPTION_SELECT, { + options: natures.map((n: Nature, i: number) => { + const option: OptionSelectItem = { + label: getNatureName(n, true, true, true, this.scene.uiTheme), + handler: () => { + // update default nature in starter save data + if (!starterAttributes) { + starterAttributes= + this.starterPreferences[this.lastSpecies.speciesId] = {}; + } + starterAttributes.nature = n as unknown as integer; + this.clearText(); + ui.setMode(Mode.STARTER_SELECT); + // set nature for starter + this.setSpeciesDetails(this.lastSpecies, undefined, undefined, undefined, undefined, undefined, n, undefined); + return true; + } + }; + return option; + }).concat({ + label: i18next.t("menu:cancel"), + handler: () => { + this.clearText(); + ui.setMode(Mode.STARTER_SELECT); + return true; + } + }), + maxOptions: 8, + yOffset: 19 + }); + }); + }); + }; + options.push({ + label: i18next.t("starterSelectUiHandler:manageNature"), + handler: () => { + showNatureOptions(); + return true; + } + }); + } const candyCount = starterData.candyCount; const passiveAttr = starterData.passiveAttr; if (passiveAttr & PassiveAttr.UNLOCKED) { @@ -1362,9 +1416,16 @@ export default class StarterSelectUiHandler extends MessageUiHandler { const rows = Math.ceil(genStarters / 9); const row = Math.floor(this.cursor / 9); const props = this.scene.gameData.getSpeciesDexAttrProps(this.lastSpecies, this.dexAttrCursor); + // prepare persistent starter data to store changes + let starterAttributes = this.starterPreferences[this.lastSpecies.speciesId]; + if (!starterAttributes) { + starterAttributes = + this.starterPreferences[this.lastSpecies.speciesId] = {}; + } switch (button) { case Button.CYCLE_SHINY: if (this.canCycleShiny) { + starterAttributes.variant = !props.shiny ? props.variant : -1; // update shiny setting this.setSpeciesDetails(this.lastSpecies, !props.shiny, undefined, undefined, props.shiny ? 0 : undefined, undefined, undefined); if (this.dexAttrCursor & DexAttr.SHINY) { this.scene.playSound("sparkle"); @@ -1383,12 +1444,14 @@ export default class StarterSelectUiHandler extends MessageUiHandler { break; } } while (newFormIndex !== props.formIndex); + starterAttributes.form = newFormIndex; // store the selected form this.setSpeciesDetails(this.lastSpecies, undefined, newFormIndex, undefined, undefined, undefined, undefined); success = true; } break; case Button.CYCLE_GENDER: if (this.canCycleGender) { + starterAttributes.female = !props.female; this.setSpeciesDetails(this.lastSpecies, undefined, undefined, !props.female, undefined, undefined, undefined); success = true; } @@ -1414,6 +1477,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } } } while (newAbilityIndex !== this.abilityCursor); + starterAttributes.ability = newAbilityIndex; // store the selected ability this.setSpeciesDetails(this.lastSpecies, undefined, undefined, undefined, undefined, newAbilityIndex, undefined); success = true; } @@ -1423,6 +1487,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler { const natures = this.scene.gameData.getNaturesForAttr(this.speciesStarterDexEntry.natureAttr); const natureIndex = natures.indexOf(this.natureCursor); const newNature = natures[natureIndex < natures.length - 1 ? natureIndex + 1 : 0]; + // store cycled nature as default + starterAttributes.nature = newNature as unknown as integer; this.setSpeciesDetails(this.lastSpecies, undefined, undefined, undefined, undefined, undefined, newNature, undefined); success = true; } @@ -1446,6 +1512,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } } } while (newVariant !== props.variant); + starterAttributes.variant = newVariant; // store the selected variant this.setSpeciesDetails(this.lastSpecies, undefined, undefined, undefined, newVariant, undefined, undefined); // Cycle tint based on current sprite tint @@ -1782,6 +1849,60 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.abilityCursor = species ? this.scene.gameData.getStarterSpeciesDefaultAbilityIndex(species) : 0; this.natureCursor = species ? this.scene.gameData.getSpeciesDefaultNature(species) : 0; + const starterAttributes : StarterAttributes = species ? {...this.starterPreferences[species.speciesId]} : null; + // validate starterAttributes + if (starterAttributes) { + // this may cause changes so we created a copy of the attributes before + if (!isNaN(starterAttributes.variant)) { + if (![ + this.speciesStarterDexEntry.caughtAttr & DexAttr.NON_SHINY, + this.speciesStarterDexEntry.caughtAttr & DexAttr.DEFAULT_VARIANT, + this.speciesStarterDexEntry.caughtAttr & DexAttr.VARIANT_2, + this.speciesStarterDexEntry.caughtAttr & DexAttr.VARIANT_3 + ][starterAttributes.variant+1]) { // add 1 as -1 = non-shiny + // requested variant wasn't unlocked, purging setting + delete starterAttributes.variant; + } + } + + if (typeof starterAttributes.female !== "boolean" || !(starterAttributes.female ? + this.speciesStarterDexEntry.caughtAttr & DexAttr.FEMALE : + this.speciesStarterDexEntry.caughtAttr & DexAttr.MALE + )) { + // requested gender wasn't unlocked, purging setting + delete starterAttributes.female; + } + + const abilityAttr = this.scene.gameData.starterData[species.speciesId].abilityAttr; + if (![ + abilityAttr & AbilityAttr.ABILITY_1, + species.ability2 ? (abilityAttr & AbilityAttr.ABILITY_2) : abilityAttr & AbilityAttr.ABILITY_HIDDEN, + species.ability2 && abilityAttr & AbilityAttr.ABILITY_HIDDEN + ][starterAttributes.ability]) { + // requested ability wasn't unlocked, purging setting + delete starterAttributes.ability; + } + + if (!(species.forms[starterAttributes.form]?.isStarterSelectable && this.speciesStarterDexEntry.caughtAttr & this.scene.gameData.getFormAttr(starterAttributes.form))) { + // requested form wasn't unlocked/isn't a starter form, purging setting + delete starterAttributes.form; + } + + if (this.scene.gameData.getNaturesForAttr(this.speciesStarterDexEntry.natureAttr).indexOf(starterAttributes.nature as unknown as Nature) < 0) { + // requested nature wasn't unlocked, purging setting + delete starterAttributes.nature; + } + } + + if (starterAttributes?.nature) { + // load default nature from stater save data, if set + this.natureCursor = starterAttributes.nature; + } + if (!isNaN(starterAttributes?.ability)) { + // load default nature from stater save data, if set + this.abilityCursor = starterAttributes.ability; + } + if (this.statsMode) { if (this.speciesStarterDexEntry?.caughtAttr) { this.statsContainer.setVisible(true); @@ -1920,9 +2041,17 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.setSpeciesDetails(species, props.shiny, props.formIndex, props.female, props.variant, this.starterAbilityIndexes[starterIndex], this.starterNatures[starterIndex]); } else { const defaultDexAttr = this.scene.gameData.getSpeciesDefaultDexAttr(species, false, true); - const defaultAbilityIndex = this.scene.gameData.getStarterSpeciesDefaultAbilityIndex(species); - const defaultNature = this.scene.gameData.getSpeciesDefaultNature(species); + const defaultAbilityIndex = starterAttributes?.ability ?? this.scene.gameData.getStarterSpeciesDefaultAbilityIndex(species); + // load default nature from stater save data, if set + const defaultNature = starterAttributes?.nature || this.scene.gameData.getSpeciesDefaultNature(species); props = this.scene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr); + if (!isNaN(starterAttributes?.variant)) { + if (props.shiny = (starterAttributes.variant >= 0)) { + props.variant = starterAttributes.variant as Variant; + } + } + props.formIndex = starterAttributes?.form ?? props.formIndex; + props.female = starterAttributes?.female ?? props.female; this.setSpeciesDetails(species, props.shiny, props.formIndex, props.female, props.variant, defaultAbilityIndex, defaultNature); } @@ -2417,6 +2546,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler { clear(): void { super.clear(); + + StarterPrefs.save(this.starterPreferences); this.cursor = -1; this.hideInstructions(); this.starterSelectContainer.setVisible(false); diff --git a/src/ui/unavailable-modal-ui-handler.ts b/src/ui/unavailable-modal-ui-handler.ts index 60d01a93c82..cddcde2a1b3 100644 --- a/src/ui/unavailable-modal-ui-handler.ts +++ b/src/ui/unavailable-modal-ui-handler.ts @@ -3,6 +3,7 @@ import { ModalConfig, ModalUiHandler } from "./modal-ui-handler"; import { addTextObject, TextStyle } from "./text"; import { Mode } from "./ui"; import { updateUserInfo } from "#app/account"; +import * as Utils from "#app/utils"; export default class UnavailableModalUiHandler extends ModalUiHandler { private reconnectTimer: NodeJS.Timeout; @@ -55,6 +56,9 @@ export default class UnavailableModalUiHandler extends ModalUiHandler { this.reconnectDuration = this.minTime; this.scene.playSound("pb_bounce_1"); this.reconnectCallback(); + } else if (response[1] === 401) { + Utils.setCookie(Utils.sessionIdKey, ""); + this.scene.reset(true, true); } else { this.reconnectDuration = Math.min(this.reconnectDuration * 2, this.maxTime); // Set a max delay so it isn't infinite this.reconnectTimer =