From 4f4d107b2184c6c4d1fd03e9304ad1cf20bbe9d6 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Sun, 7 Jul 2024 14:04:53 -0400 Subject: [PATCH] Logger UI --- src/logger.ts | 69 +++++++++++ src/phases.ts | 261 +++++++++++++++++++++++++++++++++++++++- src/system/game-data.ts | 2 + 3 files changed, 327 insertions(+), 5 deletions(-) create mode 100644 src/logger.ts diff --git a/src/logger.ts b/src/logger.ts new file mode 100644 index 00000000000..9ac47f0702a --- /dev/null +++ b/src/logger.ts @@ -0,0 +1,69 @@ +import i18next from "i18next"; +import * as Utils from "./utils"; + +/** + * All logs. + * + * Format: [filename, localStorage key, name, header] + */ +export const logs: string[][] = [ + ["instructions.txt", "path_log", "Steps", "Run Steps", "wide_lens"], + ["encounters.csv", "enc_log", "Encounters", "Encounter Data"], + ["log.txt", "debug_log", "Debug", "Debug Log"], +] +export var logKeys: string[] = [ + "i", // Instructions/steps + "e", // Encounters + "d", // Debug +]; + +export const byteSize = str => new Blob([str]).size +const filesizes = ["b", "kb", "mb", "gb", "tb"] +export function getSize(str: string) { + var d = byteSize(str) + var unit = 0 + while (d > 1000 && unit < filesizes.length - 1) { + d = Math.round(d/100)/10 + unit++ + } + return d.toString() + filesizes[unit] +} + +/** + * Writes data to a new line. + * @param keyword The identifier key for the log you're writing to + * @param data The string you're writing to the given log + */ +export function toLog(keyword: string, data: string) { + localStorage.setItem(logs[logKeys.indexOf(keyword)][1], localStorage.getItem(logs[logKeys.indexOf(keyword)][1] + "\n" + data)) +} +/** + * Writes data on the same line you were on. + * @param keyword The identifier key for the log you're writing to + * @param data The string you're writing to the given log + */ +export function appendLog(keyword: string, data: string) { + localStorage.setItem(logs[logKeys.indexOf(keyword)][1], localStorage.getItem(logs[logKeys.indexOf(keyword)][1] + data)) +} +/** + * + * Clears all data from a log. + * @param keyword The identifier key for the log you want to reste + */ +export function clearLog(keyword: string) { + localStorage.setItem(logs[logKeys.indexOf(keyword)][1], "---- " + logs[logKeys.indexOf(keyword)][3] + " ----") +} +/** + * Saves a log to your device. + * @param keyword The identifier key for the log you want to reste + */ +export function downloadLog(keyword: string) { + var d = localStorage.getItem(logs[logKeys.indexOf(keyword)][1]) + // logs[logKeys.indexOf(keyword)][1] + const blob = new Blob([ d ], {type: "text/json"}); + const link = document.createElement("a"); + link.href = window.URL.createObjectURL(blob); + link.download = `${logs[logKeys.indexOf(keyword)][0]}`; + link.click(); + link.remove(); +} \ No newline at end of file diff --git a/src/phases.ts b/src/phases.ts index 51391971308..8b8602b5d5c 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -36,7 +36,7 @@ import { EggHatchPhase } from "./egg-hatch-phase"; import { Egg } from "./data/egg"; import { vouchers } from "./system/voucher"; import { clientSessionId, loggedInUser, updateUserInfo } from "./account"; -import { SessionSaveData } from "./system/game-data"; +import { SessionSaveData, decrypt } from "./system/game-data"; import { addPokeballCaptureStars, addPokeballOpenParticles } from "./field/anims"; import { SpeciesFormChangeActiveTrigger, SpeciesFormChangeManualTrigger, SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangePostMoveTrigger, SpeciesFormChangePreMoveTrigger } from "./data/pokemon-forms"; import { battleSpecDialogue, getCharVariantFromDialogue, miscDialogue } from "./data/dialogue"; @@ -65,9 +65,171 @@ import { Moves } from "#enums/moves"; import { PlayerGender } from "#enums/player-gender"; import { Species } from "#enums/species"; import { TrainerType } from "#enums/trainer-type"; +import TrainerData from "./system/trainer-data"; +import PersistentModifierData from "./system/modifier-data"; +import ArenaData from "./system/arena-data"; +import ChallengeData from "./system/challenge-data"; +import { Challenges } from "./enums/challenges" +import PokemonData from "./system/pokemon-data" +import * as LoggerTools from "./logger" const { t } = i18next; +export function parseSlotData(slotId: integer): SessionSaveData { + var S = localStorage.getItem(`sessionData${slotId ? slotId : ""}_${loggedInUser.username}`) + if (S == null) { + // No data in this slot + return undefined; + } + var dataStr = decrypt(S, true) + var Save = JSON.parse(dataStr, (k: string, v: any) => { + /*const versions = [ scene.game.config.gameVersion, sessionData.gameVersion || '0.0.0' ]; + + if (versions[0] !== versions[1]) { + const [ versionNumbers, oldVersionNumbers ] = versions.map(ver => ver.split('.').map(v => parseInt(v))); + }*/ + + if (k === "party" || k === "enemyParty") { + const ret: PokemonData[] = []; + if (v === null) { + v = []; + } + for (const pd of v) { + ret.push(new PokemonData(pd)); + } + return ret; + } + + if (k === "trainer") { + return v ? new TrainerData(v) : null; + } + + if (k === "modifiers" || k === "enemyModifiers") { + const player = k === "modifiers"; + const ret: PersistentModifierData[] = []; + if (v === null) { + v = []; + } + for (const md of v) { + if (md?.className === "ExpBalanceModifier") { // Temporarily limit EXP Balance until it gets reworked + md.stackCount = Math.min(md.stackCount, 4); + } + if (md instanceof EnemyAttackStatusEffectChanceModifier && md.effect === StatusEffect.FREEZE || md.effect === StatusEffect.SLEEP) { + continue; + } + ret.push(new PersistentModifierData(md, player)); + } + return ret; + } + + if (k === "arena") { + return new ArenaData(v); + } + + if (k === "challenges") { + const ret: ChallengeData[] = []; + if (v === null) { + v = []; + } + for (const c of v) { + ret.push(new ChallengeData(c)); + } + return ret; + } + + return v; + }) as SessionSaveData; + Save.slot = slotId + Save.description = slotId + " - " + var challengeParts: ChallengeData[] = new Array(5) + var nameParts: string[] = new Array(5) + if (Save.challenges != undefined) { + for (var i = 0; i < Save.challenges.length; i++) { + switch (Save.challenges[i].id) { + case Challenges.SINGLE_TYPE: + challengeParts[0] = Save.challenges[i] + nameParts[1] = Save.challenges[i].toChallenge().getValue() + nameParts[1] = nameParts[1][0].toUpperCase() + nameParts[1].substring(1) + if (nameParts[1] == "unknown") { + nameParts[1] = undefined + challengeParts[1] = undefined + } + break; + case Challenges.SINGLE_GENERATION: + challengeParts[1] = Save.challenges[i] + nameParts[0] = "Gen " + Save.challenges[i].value + if (nameParts[0] == "Gen 0") { + nameParts[0] = undefined + challengeParts[0] = undefined + } + break; + case Challenges.LOWER_MAX_STARTER_COST: + challengeParts[2] = Save.challenges[i] + nameParts[3] = (10 - challengeParts[0].value) + "cost" + break; + case Challenges.LOWER_STARTER_POINTS: + challengeParts[3] = Save.challenges[i] + nameParts[4] = (10 - challengeParts[0].value) + "pt" + break; + case Challenges.FRESH_START: + challengeParts[4] = Save.challenges[i] + nameParts[2] = "FS" + break; + } + } + } + for (var i = 0; i < challengeParts.length; i++) { + if (challengeParts[i] == undefined || challengeParts[i] == null) { + challengeParts.splice(i, 1) + i-- + } + } + for (var i = 0; i < nameParts.length; i++) { + if (nameParts[i] == undefined || nameParts[i] == null || nameParts[i] == "") { + nameParts.splice(i, 1) + i-- + } + } + if (challengeParts.length == 1 && false) { + switch (challengeParts[0].id) { + case Challenges.SINGLE_TYPE: + Save.description += "Mono " + challengeParts[0].toChallenge().getValue() + break; + case Challenges.SINGLE_GENERATION: + Save.description += "Gen " + challengeParts[0].value + break; + case Challenges.LOWER_MAX_STARTER_COST: + Save.description += "Max cost " + (10 - challengeParts[0].value) + break; + case Challenges.LOWER_STARTER_POINTS: + Save.description += (10 - challengeParts[0].value) + "-point" + break; + case Challenges.FRESH_START: + Save.description += "Fresh Start" + break; + } + } else if (challengeParts.length == 0) { + switch (Save.gameMode) { + case GameModes.CLASSIC: + Save.description += "Classic"; + break; + case GameModes.ENDLESS: + Save.description += "Endless"; + break; + case GameModes.SPLICED_ENDLESS: + Save.description += "Endless+"; + break; + case GameModes.DAILY: + Save.description += "Daily"; + break; + } + } else { + Save.description += nameParts.join(" ") + } + Save.description += " (" + getBiomeName(Save.arena.biome) + " " + Save.waveIndex + ")" + return Save; +} + export class LoginPhase extends Phase { private showText: boolean; @@ -198,8 +360,25 @@ export class TitlePhase extends Phase { }); } + getLastSave(log?: boolean, dailyOnly?: boolean): SessionSaveData { + var saves: Array> = []; + for (var i = 0; i < 5; i++) { + var s = parseSlotData(i); + if (s != undefined) { + if (!dailyOnly || s.gameMode == GameModes.DAILY) { + saves.push([i, s, s.timestamp]); + } + } + } + saves.sort((a, b): integer => {return b[2] - a[2]}) + if (log) console.log(saves) + return saves[0][1] + } + showOptions(): void { + var hasFile = true const options: OptionSelectItem[] = []; + if (false) if (loggedInUser.lastSessionSlot > -1) { options.push({ label: i18next.t("continue", null, { ns: "menu"}), @@ -209,6 +388,34 @@ export class TitlePhase extends Phase { } }); } + // Replaces 'Continue' with the last Daily Run that the player completed a floor on + // If there are no daily runs, it instead shows the most recently saved run + // If this fails too, there are no saves, and the option does not appear + var lastsave = this.getLastSave(false, true); + if (lastsave != undefined) { + options.push({ + label: (this.getLastSave().description ? this.getLastSave().description : "[???]"), + handler: () => { + this.loadSaveSlot(this.getLastSave().slot); + return true; + } + }) + } else { + lastsave = this.getLastSave(false); + if (lastsave != undefined) { + options.push({ + label: (this.getLastSave().description ? this.getLastSave().description : "[???]"), + handler: () => { + this.loadSaveSlot(this.getLastSave().slot); + return true; + } + }) + } else { + console.log("Failed to get last save") + this.getLastSave(true) + hasFile = false + } + } options.push({ label: i18next.t("menu:newGame"), handler: () => { @@ -269,8 +476,52 @@ export class TitlePhase extends Phase { } return true; } - }, - { + }, { + label: "Manage Logs", + handler: () => { + const options: OptionSelectItem[] = []; + for (var i = 0; i < LoggerTools.logs.length; i++) { + if (localStorage.getItem(LoggerTools.logs[i][1]) != null) + options.push({ + label: `Export ${LoggerTools.logs[i][2]} (${LoggerTools.getSize(LoggerTools.logs[i][2])})`, + handler: () => { + LoggerTools.downloadLog(LoggerTools.logKeys[i]) + return true; + } + }) + } + options.push({ + label: `Export all (${options.length})`, + handler: () => { + for (var i = 0; i < LoggerTools.logKeys[i].length; i++) { + LoggerTools.downloadLog(LoggerTools.logKeys[i]) + } + return true; + } + }, { + label: `Reset all (${LoggerTools.logKeys[i].length})`, + handler: () => { + for (var i = 0; i < LoggerTools.logKeys[i].length; i++) { + LoggerTools.clearLog(LoggerTools.logKeys[i]) + } + return true; + } + }, { + label: i18next.t("menu:cancel"), + handler: () => { + this.scene.clearPhaseQueue(); + this.scene.pushPhase(new TitlePhase(this.scene)); + super.end(); + return true; + } + }); + this.scene.ui.showText("Export or clear game logs.", null, () => this.scene.ui.setOverlayMode(Mode.OPTION_SELECT, { options: options })); + return true; + } + }) + // If the player has no save data (as determined above), hide the "Load Game" button + if (hasFile) + options.push({ label: i18next.t("menu:loadGame"), handler: () => { this.scene.ui.setOverlayMode(Mode.SAVE_SLOT, SaveSlotUiMode.LOAD, @@ -282,8 +533,8 @@ export class TitlePhase extends Phase { }); return true; } - }, - { + }) + options.push({ label: i18next.t("menu:dailyRun"), handler: () => { this.initDailyRun(); diff --git a/src/system/game-data.ts b/src/system/game-data.ts index ac54c942fc7..ff4b9fb686b 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -122,6 +122,8 @@ export interface SessionSaveData { gameVersion: string; timestamp: integer; challenges: ChallengeData[]; + slot: integer; + description: string; } interface Unlocks {