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 01/94] 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 { From ab96325a825d49086ac8c2db09708dfc79016f6e Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Sun, 7 Jul 2024 14:19:20 -0400 Subject: [PATCH 02/94] Add to GUI --- src/phases.ts | 45 ++++++++++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/src/phases.ts b/src/phases.ts index 8b8602b5d5c..9002b9b0aa0 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -140,7 +140,7 @@ export function parseSlotData(slotId: integer): SessionSaveData { return v; }) as SessionSaveData; Save.slot = slotId - Save.description = slotId + " - " + Save.description = (slotId + 1) + " - " var challengeParts: ChallengeData[] = new Array(5) var nameParts: string[] = new Array(5) if (Save.challenges != undefined) { @@ -372,8 +372,25 @@ export class TitlePhase extends Phase { } saves.sort((a, b): integer => {return b[2] - a[2]}) if (log) console.log(saves) + if (saves == undefined) return undefined; + if (saves[0] == undefined) return undefined; return saves[0][1] } + getSaves(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) + if (saves == undefined) return undefined; + return saves.map(f => f[1]); + } showOptions(): void { var hasFile = true @@ -388,25 +405,27 @@ export class TitlePhase extends Phase { } }); } - // Replaces 'Continue' with the last Daily Run that the player completed a floor on + // Replaces 'Continue' with all Daily Run saves, sorted by when they last saved // 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; - } + var lastsaves = this.getSaves(false, true); + if (lastsaves != undefined) { + lastsaves.forEach(lastsave => { + options.push({ + label: (lastsave.description ? lastsave.description : "[???]"), + handler: () => { + this.loadSaveSlot(lastsave.slot); + return true; + } + }) }) } else { - lastsave = this.getLastSave(false); + var lastsave = this.getLastSave(false); if (lastsave != undefined) { options.push({ - label: (this.getLastSave().description ? this.getLastSave().description : "[???]"), + label: (lastsave.description ? lastsave.description : "[???]"), handler: () => { - this.loadSaveSlot(this.getLastSave().slot); + this.loadSaveSlot(lastsave.slot); return true; } }) From 9722264f1b25399c233179b4c724a547c023d6a4 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Sun, 7 Jul 2024 17:22:51 -0400 Subject: [PATCH 03/94] Log encounters --- src/logger.ts | 115 +++++++++++++++++++++++++++++++++++++++++++++++--- src/phases.ts | 21 +++++---- 2 files changed, 120 insertions(+), 16 deletions(-) diff --git a/src/logger.ts b/src/logger.ts index 9ac47f0702a..5d8a1970952 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,15 +1,21 @@ import i18next from "i18next"; import * as Utils from "./utils"; +import Pokemon from "./field/pokemon"; +import { PlayerPokemon, EnemyPokemon } from "./field/pokemon"; +import { Nature, getNatureName } from "./data/nature"; +import BattleScene from "./battle-scene"; +import { OptionSelectItem } from "./ui/abstact-option-select-ui-handler"; +import { TrainerType } from "#enums/trainer-type"; /** * All logs. * - * Format: [filename, localStorage key, name, header] + * Format: [filename, localStorage key, name, header, item sprite, header suffix] */ 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"], + ["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 @@ -29,6 +35,16 @@ export function getSize(str: string) { return d.toString() + filesizes[unit] } +export function generateOption(i: integer): OptionSelectItem { + return { + label: `Export ${logs[i][2]} (${getSize(localStorage.getItem(logs[i][1]))})`, + handler: () => { + downloadLogByID(i) + return false; + } + } +} + /** * Writes data to a new line. * @param keyword The identifier key for the log you're writing to @@ -51,7 +67,7 @@ export function appendLog(keyword: string, data: string) { * @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] + " ----") + localStorage.setItem(logs[logKeys.indexOf(keyword)][1], "---- " + logs[logKeys.indexOf(keyword)][3] + " ----" + logs[logKeys.indexOf(keyword)][5]) } /** * Saves a log to your device. @@ -66,4 +82,93 @@ export function downloadLog(keyword: string) { link.download = `${logs[logKeys.indexOf(keyword)][0]}`; link.click(); link.remove(); +} +export function downloadLogByID(i: integer) { + console.log(i) + var d = localStorage.getItem(logs[i][1]) + const blob = new Blob([ d ], {type: "text/json"}); + const link = document.createElement("a"); + link.href = window.URL.createObjectURL(blob); + link.download = `${logs[i][0]}`; + link.click(); + link.remove(); +} +export function logTeam(scene: BattleScene, floor: integer = undefined) { + if (floor == undefined) floor = scene.currentBattle.waveIndex + var team = scene.getEnemyParty() + if (team[0].hasTrainer()) { + var sprite = scene.currentBattle.trainer.config.getSpriteKey() + var trainerCat = Utils.getEnumKeys(TrainerType)[Utils.getEnumValues(TrainerType).indexOf(scene.currentBattle.trainer.config.trainerType)] + setRow("e", floor + "," + team.length + "," + sprite + ",trainer," + trainerCat + ",,,,,,,,,,,,", floor, 0) + } else { + for (var i = 0; i < team.length; i++) { + logPokemon(scene, floor, i, team[i]) + } + if (team.length == 1) { + setRow("e", ",,,,,,,,,,,,,,,,", floor, 1) + } + } +} +export function logPokemon(scene: BattleScene, floor: integer = undefined, slot: integer, pokemon: EnemyPokemon) { + if (floor == undefined) floor = scene.currentBattle.waveIndex + var modifiers: string[] = [] + var mods = pokemon.getHeldItems() + for (var i = 0; i < mods.length; i++) { + modifiers.push(mods[i].type.name + (mods[i].getMaxStackCount(scene) == 1 ? "" : " x" + mods[i].getStackCount())) + } + var sprite = pokemon.getBattleSpriteAtlasPath() + // floor,party slot,encounter,species,ability,passive,level,gender,isBoss,nature,HP IV,Attack IV,Defense IV,Sp. Atk IV,Sp. Def IV,Speed IV,Items separated by slashes / + var newLine = floor + "," + + slot + "," + + sprite + "," + + (pokemon.hasTrainer() ? "trainer_pokemon" : "wild") + "," + + pokemon.species.getName(pokemon.formIndex) + (pokemon.getFormKey() == "" ? "" : " (" + pokemon.getFormKey() + ")") + "," + + pokemon.getAbility().name.toLowerCase() + "," + + pokemon.getPassiveAbility().name.toLowerCase() + "," + + pokemon.level + "," + + (pokemon.gender == 0 ? "M" : (pokemon.gender == 1 ? "F" : "")) + "," + + (pokemon.isBoss() ? "true" : "false") + "," + + getNatureName(pokemon.nature) + "," + + pokemon.ivs[0] + "," + + pokemon.ivs[1] + "," + + pokemon.ivs[2] + "," + + pokemon.ivs[3] + "," + + pokemon.ivs[4] + "," + + pokemon.ivs[5] + "," + + modifiers.join("/") + //console.log(idx, data.slice(0, idx), newLine, data.slice(idx)) + setRow("e", newLine, floor, slot) + //console.log(localStorage.getItem(logs[logKeys.indexOf("e")][1]).split("\n")) +} +export function setRow(keyword: string, newLine: string, floor: integer, slot: integer) { + var data = localStorage.getItem(logs[logKeys.indexOf(keyword)][1]).split("\n") + var idx = 1 + if (slot == -1) { + while (idx < data.length && (data[idx].split(",")[0] as any) * 1 < floor) { + idx++ + } + idx-- + slot = ((data[idx].split(",")[1] as any) * 1) + 1 + } else { + while (idx < data.length && (data[idx].split(",")[0] as any) * 1 <= floor && (data[idx].split(",")[1] as any) * 1 <= slot) { + idx++ + } + } + localStorage.setItem(logs[logKeys.indexOf(keyword)][1], data.slice(0, idx).join("\n") + "\n" + newLine + (data.slice(idx).length == 0 ? "" : "\n") + data.slice(idx).join("\n")); +} +export function setRowByID(key: integer, newLine: string, floor: integer, slot: integer) { + var data = localStorage.getItem(logs[key][1]).split("\n") + var idx = 1 + if (slot == -1) { + while (idx < data.length && (data[idx].split(",")[0] as any) * 1 < floor) { + idx++ + } + idx-- + slot = ((data[idx].split(",")[1] as any) * 1) + 1 + } else { + while (idx < data.length && (data[idx].split(",")[0] as any) * 1 <= floor && (data[idx].split(",")[1] as any) * 1 <= slot) { + idx++ + } + } + localStorage.setItem(logs[key][1], data.slice(0, idx).join("\n") + "\n" + newLine + (data.slice(idx).length == 0 ? "" : "\n") + data.slice(idx).join("\n")); } \ No newline at end of file diff --git a/src/phases.ts b/src/phases.ts index 9002b9b0aa0..18a5d112a08 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -501,28 +501,25 @@ export class TitlePhase extends Phase { 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(LoggerTools.generateOption(i) as OptionSelectItem) } options.push({ label: `Export all (${options.length})`, handler: () => { - for (var i = 0; i < LoggerTools.logKeys[i].length; i++) { + for (var i = 0; i < LoggerTools.logKeys.length; i++) { LoggerTools.downloadLog(LoggerTools.logKeys[i]) } - return true; + return false; } }, { - label: `Reset all (${LoggerTools.logKeys[i].length})`, + label: `Reset all (${LoggerTools.logKeys.length})`, handler: () => { - for (var i = 0; i < LoggerTools.logKeys[i].length; i++) { + for (var i = 0; i < LoggerTools.logKeys.length; i++) { LoggerTools.clearLog(LoggerTools.logKeys[i]) } + this.scene.clearPhaseQueue(); + this.scene.pushPhase(new TitlePhase(this.scene)); + super.end(); return true; } }, { @@ -1250,6 +1247,8 @@ export class EncounterPhase extends BattlePhase { doEncounterCommon(showEncounterMessage: boolean = true) { const enemyField = this.scene.getEnemyField(); + LoggerTools.logTeam(this.scene, this.scene.currentBattle.waveIndex) + if (this.scene.currentBattle.battleType === BattleType.WILD) { enemyField.forEach(enemyPokemon => { enemyPokemon.untint(100, "Sine.easeOut"); From 307d0af860aa64c2095a1bb6b42477edc7a999f0 Mon Sep 17 00:00:00 2001 From: EmberCM Date: Mon, 8 Jul 2024 09:03:01 -0500 Subject: [PATCH 04/94] [Bug] Fix venom drench tm learnset (#2910) --- src/data/tms.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/data/tms.ts b/src/data/tms.ts index c51a4ede8b5..0a13cef4ee8 100644 --- a/src/data/tms.ts +++ b/src/data/tms.ts @@ -59855,16 +59855,11 @@ export const tmSpecies: TmSpecies = { Species.ZUBAT, Species.GOLBAT, Species.TENTACRUEL, - Species.MUK, Species.KOFFING, Species.WEEZING, Species.MEW, - Species.ARIADOS, Species.CROBAT, Species.QWILFISH, - Species.GULPIN, - Species.SWALOT, - Species.SEVIPER, Species.ROSERADE, Species.STUNKY, Species.SKUNTANK, @@ -59896,8 +59891,6 @@ export const tmSpecies: TmSpecies = { Species.NAGANADEL, Species.PINCURCHIN, Species.ETERNATUS, - Species.PIKACHU, - Species.ALOLA_MUK, Species.GALAR_WEEZING, Species.GALAR_SLOWKING, [ From 7b484e3d0bdc66a1c1df202d6d9646902a91b2d9 Mon Sep 17 00:00:00 2001 From: Kyurl21 <72814223+Kyurl21@users.noreply.github.com> Date: Mon, 8 Jul 2024 16:07:28 +0200 Subject: [PATCH 05/94] [Localization(de)]fixed translation error in settings (#2898) fixed a translation error in german in the settings --- src/locales/de/settings.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/locales/de/settings.ts b/src/locales/de/settings.ts index 85f8645d69f..0254611b5d5 100644 --- a/src/locales/de/settings.ts +++ b/src/locales/de/settings.ts @@ -82,7 +82,7 @@ export const settings: SimpleTranslationEntries = { "buttonMenu": "Menü", "buttonSubmit": "Bestätigen", "buttonCancel": "Abbrechen", - "buttonStats": "Statistiken", + "buttonStats": "Statuswerte", "buttonCycleForm": "Form wechseln", "buttonCycleShiny": "Schillernd wechseln", "buttonCycleGender": "Geschlecht wechseln", From 1234554d74157ba4fddbf17b964476bbe59f7ca9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Ricardo=20Fleury=20Oliveira?= Date: Mon, 8 Jul 2024 11:14:11 -0300 Subject: [PATCH 06/94] Localization(pt): Fixed some translations (#2915) --- src/locales/pt_BR/ability-trigger.ts | 4 ++-- src/locales/pt_BR/biome.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/locales/pt_BR/ability-trigger.ts b/src/locales/pt_BR/ability-trigger.ts index 526c6def80d..11cbaed182d 100644 --- a/src/locales/pt_BR/ability-trigger.ts +++ b/src/locales/pt_BR/ability-trigger.ts @@ -1,7 +1,7 @@ import { SimpleTranslationEntries } from "#app/interfaces/locales"; export const abilityTriggers: SimpleTranslationEntries = { - "blockRecoilDamage" : "{{abilityName}} de {{pokemonName}}\nprotegeu-o do dano de recuo!", + "blockRecoilDamage": "{{abilityName}} de {{pokemonName}}\nprotegeu-o do dano reverso!", "badDreams": "{{pokemonName}} está tendo pesadelos!", "costar": "{{pokemonName}} copiou as mudanças\nde atributo de {{allyName}}!", "iceFaceAvoidedDamage": "{{pokemonName}} evitou\ndanos com sua {{abilityName}}!", @@ -9,5 +9,5 @@ export const abilityTriggers: SimpleTranslationEntries = { "poisonHeal": "{{abilityName}} de {{pokemonName}}\nrestaurou seus PS um pouco!", "trace": "{{pokemonName}} copiou {{abilityName}}\nde {{targetName}}!", "windPowerCharged": "Ser atingido por {{moveName}} carregou {{pokemonName}} com poder!", - "quickDraw":"{{pokemonName}} pode agir mais rápido que o normal\ngraças ao seu Quick Draw!", + "quickDraw": "{{pokemonName}} pode agir mais rápido que o normal\ngraças ao seu Quick Draw!", } as const; diff --git a/src/locales/pt_BR/biome.ts b/src/locales/pt_BR/biome.ts index 46dad06b0de..0217836ed20 100644 --- a/src/locales/pt_BR/biome.ts +++ b/src/locales/pt_BR/biome.ts @@ -1,7 +1,7 @@ import { SimpleTranslationEntries } from "#app/interfaces/locales"; export const biome: SimpleTranslationEntries = { - "unknownLocation": "Em algum lugar do qual você não se lembra", + "unknownLocation": "em algum lugar do qual você não se lembra", "TOWN": "Cidade", "PLAINS": "Planície", "GRASS": "Grama", From 965bc687b38f3d5c0296ca1eede83b7569d86b15 Mon Sep 17 00:00:00 2001 From: Enoch Date: Mon, 8 Jul 2024 23:16:55 +0900 Subject: [PATCH 07/94] [Localization] Localize terrain message and translate in Korean (#2806) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add terrain localization and test code, change folder name * Update src/locales/fr/weather.ts Co-authored-by: Lugiad' * Update src/locales/pt_BR/weather.ts Co-authored-by: José Ricardo Fleury Oliveira * Update src/locales/zh_CN/weather.ts Co-authored-by: Yonmaru40 <47717431+40chyan@users.noreply.github.com> * Update src/locales/de/weather.ts Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> * Update src/locales/zh_TW/weather.ts Co-authored-by: Yonmaru40 <47717431+40chyan@users.noreply.github.com> * Use testUtil * add missed en messages --------- Co-authored-by: Lugiad' Co-authored-by: José Ricardo Fleury Oliveira Co-authored-by: Yonmaru40 <47717431+40chyan@users.noreply.github.com> Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> --- src/data/terrain.ts | 17 ++ src/data/weather.ts | 24 +-- src/locales/de/config.ts | 3 +- src/locales/de/weather.ts | 21 ++ src/locales/en/config.ts | 5 +- src/locales/en/weather.ts | 21 ++ src/locales/es/config.ts | 3 +- src/locales/es/weather.ts | 21 ++ src/locales/fr/config.ts | 3 +- src/locales/fr/weather.ts | 21 ++ src/locales/it/config.ts | 3 +- src/locales/it/weather.ts | 21 ++ src/locales/ko/config.ts | 3 +- src/locales/ko/weather.ts | 21 ++ src/locales/pt_BR/config.ts | 5 +- src/locales/pt_BR/weather.ts | 21 ++ src/locales/zh_CN/config.ts | 3 +- src/locales/zh_CN/weather.ts | 21 ++ src/locales/zh_TW/config.ts | 3 +- src/locales/zh_TW/weather.ts | 21 ++ .../battle-stat.test.ts | 0 .../french.test.ts | 0 .../status-effect.test.ts | 0 src/test/localization/terrain.test.ts | 192 ++++++++++++++++++ 24 files changed, 430 insertions(+), 23 deletions(-) rename src/test/{lokalisation => localization}/battle-stat.test.ts (100%) rename src/test/{lokalisation => localization}/french.test.ts (100%) rename src/test/{lokalisation => localization}/status-effect.test.ts (100%) create mode 100644 src/test/localization/terrain.test.ts diff --git a/src/data/terrain.ts b/src/data/terrain.ts index e396c693c4e..f7324c28b93 100644 --- a/src/data/terrain.ts +++ b/src/data/terrain.ts @@ -5,6 +5,7 @@ import * as Utils from "../utils"; import { IncrementMovePriorityAbAttr, applyAbAttrs } from "./ability"; import { ProtectAttr } from "./move"; import { BattlerIndex } from "#app/battle.js"; +import i18next from "i18next"; export enum TerrainType { NONE, @@ -67,6 +68,22 @@ export class Terrain { } } +export function getTerrainName(terrainType: TerrainType): string { + switch (terrainType) { + case TerrainType.MISTY: + return i18next.t("terrain:misty"); + case TerrainType.ELECTRIC: + return i18next.t("terrain:electric"); + case TerrainType.GRASSY: + return i18next.t("terrain:grassy"); + case TerrainType.PSYCHIC: + return i18next.t("terrain:psychic"); + } + + return ""; +} + + export function getTerrainColor(terrainType: TerrainType): [ integer, integer, integer ] { switch (terrainType) { case TerrainType.MISTY: diff --git a/src/data/weather.ts b/src/data/weather.ts index 425dd3724f6..f671c754873 100644 --- a/src/data/weather.ts +++ b/src/data/weather.ts @@ -1,12 +1,12 @@ import { Biome } from "#enums/biome"; -import { getPokemonMessage, getPokemonNameWithAffix } from "../messages"; +import { getPokemonNameWithAffix } from "../messages"; import Pokemon from "../field/pokemon"; import { Type } from "./type"; import Move, { AttackMove } from "./move"; import * as Utils from "../utils"; import BattleScene from "../battle-scene"; import { SuppressWeatherEffectAbAttr } from "./ability"; -import { TerrainType } from "./terrain"; +import { TerrainType, getTerrainName } from "./terrain"; import i18next from "i18next"; export enum WeatherType { @@ -216,34 +216,34 @@ export function getWeatherClearMessage(weatherType: WeatherType): string { export function getTerrainStartMessage(terrainType: TerrainType): string { switch (terrainType) { case TerrainType.MISTY: - return "Mist swirled around the battlefield!"; + return i18next.t("terrain:mistyStartMessage"); case TerrainType.ELECTRIC: - return "An electric current ran across the battlefield!"; + return i18next.t("terrain:electricStartMessage"); case TerrainType.GRASSY: - return "Grass grew to cover the battlefield!"; + return i18next.t("terrain:grassyStartMessage"); case TerrainType.PSYCHIC: - return "The battlefield got weird!"; + return i18next.t("terrain:psychicStartMessage"); } } export function getTerrainClearMessage(terrainType: TerrainType): string { switch (terrainType) { case TerrainType.MISTY: - return "The mist disappeared from the battlefield."; + return i18next.t("terrain:mistyClearMessage"); case TerrainType.ELECTRIC: - return "The electricity disappeared from the battlefield."; + return i18next.t("terrain:electricClearMessage"); case TerrainType.GRASSY: - return "The grass disappeared from the battlefield."; + return i18next.t("terrain:grassyClearMessage"); case TerrainType.PSYCHIC: - return "The weirdness disappeared from the battlefield!"; + return i18next.t("terrain:psychicClearMessage"); } } export function getTerrainBlockMessage(pokemon: Pokemon, terrainType: TerrainType): string { if (terrainType === TerrainType.MISTY) { - return getPokemonMessage(pokemon, " surrounds itself with a protective mist!"); + return i18next.t("terrain:mistyBlockMessage", {pokemonNameWithAffix: getPokemonNameWithAffix(pokemon)}); } - return getPokemonMessage(pokemon, ` is protected by the ${Utils.toReadableString(TerrainType[terrainType])} Terrain!`); + return i18next.t("terrain:defaultBlockMessage", {pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), terrainName: getTerrainName(terrainType)}); } interface WeatherPoolEntry { diff --git a/src/locales/de/config.ts b/src/locales/de/config.ts index 92544d87ea3..3c16f81270d 100644 --- a/src/locales/de/config.ts +++ b/src/locales/de/config.ts @@ -39,7 +39,7 @@ import { statusEffect } from "./status-effect"; import { titles, trainerClasses, trainerNames } from "./trainers"; import { tutorial } from "./tutorial"; import { voucher } from "./voucher"; -import { weather } from "./weather"; +import { terrain, weather } from "./weather"; import { partyUiHandler } from "./party-ui-handler"; import { settings } from "./settings.js"; import { common } from "./common.js"; @@ -85,6 +85,7 @@ export const deConfig = { splashMessages: splashMessages, starterSelectUiHandler: starterSelectUiHandler, statusEffect: statusEffect, + terrain: terrain, titles: titles, trainerClasses: trainerClasses, trainerNames: trainerNames, diff --git a/src/locales/de/weather.ts b/src/locales/de/weather.ts index 305fd7e7827..8a820f3d549 100644 --- a/src/locales/de/weather.ts +++ b/src/locales/de/weather.ts @@ -43,3 +43,24 @@ export const weather: SimpleTranslationEntries = { "strongWindsEffectMessage": "Rätselhafte Luftströmungen haben den Angriff abgeschwächt!", "strongWindsClearMessage": "Die rätselhafte Luftströmung hat sich wieder geleget.", }; + +export const terrain: SimpleTranslationEntries = { + "misty": "Nebelfeld", + "mistyStartMessage": "Am Boden breitet sich dichter Nebel aus!", + "mistyClearMessage": "Das Nebelfeld ist wieder verschwunden!", + "mistyBlockMessage": "{{pokemonNameWithAffix}} wird vom Nebelfeld geschützt!", + + "electric": "Elektrofeld", + "electricStartMessage": "Elektrische Energie fließt durch den Boden!", + "electricClearMessage": "Das Elektrofeld ist wieder verschwunden!", + + "grassy": "Grasfeld", + "grassyStartMessage": "Dichtes Gras schießt aus dem Boden!", + "grassyClearMessage": "Das Grasfeld ist wieder verschwunden!", + + "psychic": "Psychofeld", + "psychicStartMessage": "Der Boden fühlt sich seltsam an!", + "psychicClearMessage": "Das Psychofeld ist wieder verschwunden!", + + "defaultBlockMessage": "{{pokemonNameWithAffix}} wird vom {{terrainName}} geschützt!" +}; diff --git a/src/locales/en/config.ts b/src/locales/en/config.ts index a318bbe0128..b0c1b7a7105 100644 --- a/src/locales/en/config.ts +++ b/src/locales/en/config.ts @@ -42,7 +42,7 @@ import { statusEffect } from "./status-effect"; import { titles, trainerClasses, trainerNames } from "./trainers"; import { tutorial } from "./tutorial"; import { voucher } from "./voucher"; -import { weather } from "./weather"; +import { terrain, weather } from "./weather"; import { modifierSelectUiHandler } from "./modifier-select-ui-handler"; export const enConfig = { @@ -76,7 +76,6 @@ export const enConfig = { modifierType: modifierType, move: move, nature: nature, - partyUiHandler: partyUiHandler, pokeball: pokeball, pokemon: pokemon, pokemonInfo: pokemonInfo, @@ -86,11 +85,13 @@ export const enConfig = { splashMessages: splashMessages, starterSelectUiHandler: starterSelectUiHandler, statusEffect: statusEffect, + terrain: terrain, titles: titles, trainerClasses: trainerClasses, trainerNames: trainerNames, tutorial: tutorial, voucher: voucher, weather: weather, + partyUiHandler: partyUiHandler, modifierSelectUiHandler: modifierSelectUiHandler }; diff --git a/src/locales/en/weather.ts b/src/locales/en/weather.ts index c7b2963ccd8..8222064f341 100644 --- a/src/locales/en/weather.ts +++ b/src/locales/en/weather.ts @@ -43,3 +43,24 @@ export const weather: SimpleTranslationEntries = { "strongWindsEffectMessage": "The mysterious air current weakened the attack!", "strongWindsClearMessage": "The heavy wind stopped." }; + +export const terrain: SimpleTranslationEntries = { + "misty": "Misty", + "mistyStartMessage": "Mist swirled around the battlefield!", + "mistyClearMessage": "The mist disappeared from the battlefield.", + "mistyBlockMessage": "{{pokemonNameWithAffix}} surrounds itself with a protective mist!", + + "electric": "Electric", + "electricStartMessage": "An electric current ran across the battlefield!", + "electricClearMessage": "The electricity disappeared from the battlefield.", + + "grassy": "Grassy", + "grassyStartMessage": "Grass grew to cover the battlefield!", + "grassyClearMessage": "The grass disappeared from the battlefield.", + + "psychic": "Psychic", + "psychicStartMessage": "The battlefield got weird!", + "psychicClearMessage": "The weirdness disappeared from the battlefield!", + + "defaultBlockMessage": "{{pokemonNameWithAffix}} is protected by the {{terrainName}} Terrain!" +}; diff --git a/src/locales/es/config.ts b/src/locales/es/config.ts index bedd53dcc29..c37f96a2aa5 100644 --- a/src/locales/es/config.ts +++ b/src/locales/es/config.ts @@ -39,7 +39,7 @@ import { statusEffect } from "./status-effect"; import { titles, trainerClasses, trainerNames } from "./trainers"; import { tutorial } from "./tutorial"; import { voucher } from "./voucher"; -import { weather } from "./weather"; +import { terrain, weather } from "./weather"; import { partyUiHandler } from "./party-ui-handler"; import { settings } from "./settings.js"; import { common } from "./common.js"; @@ -85,6 +85,7 @@ export const esConfig = { splashMessages: splashMessages, starterSelectUiHandler: starterSelectUiHandler, statusEffect: statusEffect, + terrain: terrain, titles: titles, trainerClasses: trainerClasses, trainerNames: trainerNames, diff --git a/src/locales/es/weather.ts b/src/locales/es/weather.ts index 37f574878dc..1129443d71b 100644 --- a/src/locales/es/weather.ts +++ b/src/locales/es/weather.ts @@ -43,3 +43,24 @@ export const weather: SimpleTranslationEntries = { "strongWindsEffectMessage": "¡Las misteriosas turbulencias atenúan el ataque!", "strongWindsClearMessage": "El fuerte viento cesó." }; + +export const terrain: SimpleTranslationEntries = { + "misty": "Misty", + "mistyStartMessage": "Mist swirled around the battlefield!", + "mistyClearMessage": "The mist disappeared from the battlefield.", + "mistyBlockMessage": "{{pokemonNameWithAffix}} surrounds itself with a protective mist!", + + "electric": "Electric", + "electricStartMessage": "An electric current ran across the battlefield!", + "electricClearMessage": "The electricity disappeared from the battlefield.", + + "grassy": "Grassy", + "grassyStartMessage": "Grass grew to cover the battlefield!", + "grassyClearMessage": "The grass disappeared from the battlefield.", + + "psychic": "Psychic", + "psychicStartMessage": "The battlefield got weird!", + "psychicClearMessage": "The weirdness disappeared from the battlefield!", + + "defaultBlockMessage": "{{pokemonNameWithAffix}} is protected by the {{terrainName}} Terrain!" +}; diff --git a/src/locales/fr/config.ts b/src/locales/fr/config.ts index 55eae8c1dd5..af18aab3400 100644 --- a/src/locales/fr/config.ts +++ b/src/locales/fr/config.ts @@ -39,7 +39,7 @@ import { statusEffect } from "./status-effect"; import { titles, trainerClasses, trainerNames } from "./trainers"; import { tutorial } from "./tutorial"; import { voucher } from "./voucher"; -import { weather } from "./weather"; +import { terrain, weather } from "./weather"; import { partyUiHandler } from "./party-ui-handler"; import { settings } from "./settings.js"; import { common } from "./common.js"; @@ -85,6 +85,7 @@ export const frConfig = { splashMessages: splashMessages, starterSelectUiHandler: starterSelectUiHandler, statusEffect: statusEffect, + terrain: terrain, titles: titles, trainerClasses: trainerClasses, trainerNames: trainerNames, diff --git a/src/locales/fr/weather.ts b/src/locales/fr/weather.ts index 3df8d0e20c9..3427748480e 100644 --- a/src/locales/fr/weather.ts +++ b/src/locales/fr/weather.ts @@ -43,3 +43,24 @@ export const weather: SimpleTranslationEntries = { "strongWindsEffectMessage": "Le courant aérien mystérieux affaiblit l’attaque !", "strongWindsClearMessage": "Le vent mystérieux s’est dissipé…" }; + +export const terrain: SimpleTranslationEntries = { + "misty": "Brumeux", + "mistyStartMessage": "La brume recouvre le terrain !", + "mistyClearMessage": "La brume qui recouvrait le terrain se dissipe…", + "mistyBlockMessage": "La brume enveloppe {{pokemonNameWithAffix}} !", + + "electric": "Électrifié", + "electricStartMessage": "De l’électricité parcourt le terrain !", + "electricClearMessage": "L’électricité parcourant le terrain s’est dissipée…", + + "grassy": "Herbu", + "grassyStartMessage": "Un beau gazon pousse sur le terrain !", + "grassyClearMessage": "Le gazon disparait…", + + "psychic": "Psychique", + "psychicStartMessage": "Le sol se met à réagir de façon bizarre…", + "psychicClearMessage": "Le sol redevient normal !", + + "defaultBlockMessage": "{{pokemonNameWithAffix}} est protégé\npar le Champ {{terrainName}} !" +}; diff --git a/src/locales/it/config.ts b/src/locales/it/config.ts index 9175d40b97f..ac77e814623 100644 --- a/src/locales/it/config.ts +++ b/src/locales/it/config.ts @@ -39,7 +39,7 @@ import { statusEffect } from "./status-effect"; import { titles, trainerClasses, trainerNames } from "./trainers"; import { tutorial } from "./tutorial"; import { voucher } from "./voucher"; -import { weather } from "./weather"; +import { terrain, weather } from "./weather"; import { partyUiHandler } from "./party-ui-handler"; import { settings } from "./settings.js"; import { common } from "./common.js"; @@ -85,6 +85,7 @@ export const itConfig = { splashMessages: splashMessages, starterSelectUiHandler: starterSelectUiHandler, statusEffect: statusEffect, + terrain: terrain, titles: titles, trainerClasses: trainerClasses, trainerNames: trainerNames, diff --git a/src/locales/it/weather.ts b/src/locales/it/weather.ts index f5d8e3b9397..604108435c3 100644 --- a/src/locales/it/weather.ts +++ b/src/locales/it/weather.ts @@ -43,3 +43,24 @@ export const weather: SimpleTranslationEntries = { "strongWindsEffectMessage": "La corrente misteriosa indebolisce l’attacco!", "strongWindsClearMessage": "La corrente d'aria è cessata." }; + +export const terrain: SimpleTranslationEntries = { + "misty": "Misty", + "mistyStartMessage": "Mist swirled around the battlefield!", + "mistyClearMessage": "The mist disappeared from the battlefield.", + "mistyBlockMessage": "{{pokemonNameWithAffix}} surrounds itself with a protective mist!", + + "electric": "Electric", + "electricStartMessage": "An electric current ran across the battlefield!", + "electricClearMessage": "The electricity disappeared from the battlefield.", + + "grassy": "Grassy", + "grassyStartMessage": "Grass grew to cover the battlefield!", + "grassyClearMessage": "The grass disappeared from the battlefield.", + + "psychic": "Psychic", + "psychicStartMessage": "The battlefield got weird!", + "psychicClearMessage": "The weirdness disappeared from the battlefield!", + + "defaultBlockMessage": "{{pokemonNameWithAffix}} is protected by the {{terrainName}} Terrain!" +}; diff --git a/src/locales/ko/config.ts b/src/locales/ko/config.ts index dc64e12356a..fa81dee55ab 100644 --- a/src/locales/ko/config.ts +++ b/src/locales/ko/config.ts @@ -39,7 +39,7 @@ import { statusEffect } from "./status-effect"; import { titles, trainerClasses, trainerNames } from "./trainers"; import { tutorial } from "./tutorial"; import { voucher } from "./voucher"; -import { weather } from "./weather"; +import { terrain, weather } from "./weather"; import { partyUiHandler } from "./party-ui-handler"; import { settings } from "./settings.js"; import { common } from "./common.js"; @@ -85,6 +85,7 @@ export const koConfig = { splashMessages: splashMessages, starterSelectUiHandler: starterSelectUiHandler, statusEffect: statusEffect, + terrain: terrain, titles: titles, trainerClasses: trainerClasses, trainerNames: trainerNames, diff --git a/src/locales/ko/weather.ts b/src/locales/ko/weather.ts index 9aca57c49d2..c89cc335859 100644 --- a/src/locales/ko/weather.ts +++ b/src/locales/ko/weather.ts @@ -44,3 +44,24 @@ export const weather: SimpleTranslationEntries = { "strongWindsEffectMessage": "수수께끼의 난기류가 공격을 약하게 만들었다!", "strongWindsClearMessage": "수수께끼의 난기류가 멈췄다!" // 임의번역 }; + +export const terrain: SimpleTranslationEntries = { + "misty": "미스트필드", + "mistyStartMessage": "발밑이 안개로 자욱해졌다!", + "mistyClearMessage": "발밑의 안개가 사라졌다!", + "mistyBlockMessage": "{{pokemonNameWithAffix}}[[를]]\n미스트필드가 지켜주고 있다!", + + "electric": "일렉트릭필드", + "electricStartMessage": "발밑에 전기가 흐르기 시작했다!", + "electricClearMessage": "발밑의 전기가 사라졌다!", + + "grassy": "그래스필드", + "grassyStartMessage": "발밑에 풀이 무성해졌다!", + "grassyClearMessage": "발밑의 풀이 사라졌다!", + + "psychic": "사이코필드", + "psychicStartMessage": "발밑에서 이상한 느낌이 든다!", + "psychicClearMessage": "발밑의 이상한 느낌이 사라졌다!", + + "defaultBlockMessage": "{{pokemonNameWithAffix}}[[를]]\n{{terrainName}}[[가]] 지켜주고 있다!" +}; diff --git a/src/locales/pt_BR/config.ts b/src/locales/pt_BR/config.ts index 5a571815c97..4b3649f96ad 100644 --- a/src/locales/pt_BR/config.ts +++ b/src/locales/pt_BR/config.ts @@ -39,7 +39,7 @@ import { statusEffect } from "./status-effect"; import { titles, trainerClasses, trainerNames } from "./trainers"; import { tutorial } from "./tutorial"; import { voucher } from "./voucher"; -import { weather } from "./weather"; +import { terrain, weather } from "./weather"; import { partyUiHandler } from "./party-ui-handler"; import { settings } from "./settings.js"; import { common } from "./common.js"; @@ -76,13 +76,13 @@ export const ptBrConfig = { modifierType: modifierType, move: move, nature: nature, - partyUiHandler: partyUiHandler, pokeball: pokeball, pokemon: pokemon, pokemonInfo: pokemonInfo, pokemonInfoContainer: pokemonInfoContainer, saveSlotSelectUiHandler: saveSlotSelectUiHandler, statusEffect: statusEffect, + terrain: terrain, settings: settings, splashMessages: splashMessages, starterSelectUiHandler: starterSelectUiHandler, @@ -92,5 +92,6 @@ export const ptBrConfig = { tutorial: tutorial, voucher: voucher, weather: weather, + partyUiHandler: partyUiHandler, modifierSelectUiHandler: modifierSelectUiHandler }; diff --git a/src/locales/pt_BR/weather.ts b/src/locales/pt_BR/weather.ts index 0787b32e416..31e35657c7f 100644 --- a/src/locales/pt_BR/weather.ts +++ b/src/locales/pt_BR/weather.ts @@ -43,3 +43,24 @@ export const weather: SimpleTranslationEntries = { "strongWindsEffectMessage": "The mysterious air current weakened the attack!", "strongWindsClearMessage": "Os ventos fortes diminuíram.", }; + +export const terrain: SimpleTranslationEntries = { + "misty": "Enevoado", + "mistyStartMessage": "Uma névoa se espalhou pelo campo de batalha!", + "mistyClearMessage": "A névou sumiu do campo de batalha.", + "mistyBlockMessage": "{{pokemonNameWithAffix}} se envolveu com uma névoa protetora!", + + "electric": "Elétrico", + "electricStartMessage": "Uma corrente elétrica se espalhou pelo campo de batalha!", + "electricClearMessage": "A eletricidade sumiu do campo de batalha.", + + "grassy": "de Plantas", + "grassyStartMessage": "Grama cresceu para cobrir o campo de batalha!", + "grassyClearMessage": "A grama sumiu do campo de batalha.", + + "psychic": "Psíquico", + "psychicStartMessage": "O campo de batalha ficou esquisito!", + "psychicClearMessage": "A esquisitice sumiu do campo de batalha", + + "defaultBlockMessage": "{{pokemonNameWithAffix}} está protegido pelo Terreno {{terrainName}}!" +}; diff --git a/src/locales/zh_CN/config.ts b/src/locales/zh_CN/config.ts index 8f654b56d9a..b55b2aa865c 100644 --- a/src/locales/zh_CN/config.ts +++ b/src/locales/zh_CN/config.ts @@ -39,7 +39,7 @@ import { statusEffect } from "./status-effect"; import { titles, trainerClasses, trainerNames } from "./trainers"; import { tutorial } from "./tutorial"; import { voucher } from "./voucher"; -import { weather } from "./weather"; +import { terrain, weather } from "./weather"; import { partyUiHandler } from "./party-ui-handler"; import { settings } from "./settings.js"; import { common } from "./common.js"; @@ -85,6 +85,7 @@ export const zhCnConfig = { splashMessages: splashMessages, starterSelectUiHandler: starterSelectUiHandler, statusEffect: statusEffect, + terrain: terrain, titles: titles, trainerClasses: trainerClasses, trainerNames: trainerNames, diff --git a/src/locales/zh_CN/weather.ts b/src/locales/zh_CN/weather.ts index d280dfccb95..ea4deffbd55 100644 --- a/src/locales/zh_CN/weather.ts +++ b/src/locales/zh_CN/weather.ts @@ -43,3 +43,24 @@ export const weather: SimpleTranslationEntries = { "strongWindsEffectMessage": "The mysterious air current weakened the attack!", "strongWindsClearMessage": "神秘的乱流停止了。" }; + +export const terrain: SimpleTranslationEntries = { + "misty": "薄雾", + "mistyStartMessage": "脚下雾气缭绕!", + "mistyClearMessage": "脚下的雾气消失不见了!", + "mistyBlockMessage": "{{pokemonNameWithAffix}}正受到薄雾场地的保护!", + + "electric": "电气", + "electricStartMessage": "脚下电光飞闪!", + "electricClearMessage": "脚下的电光消失不见了!", + + "grassy": "青草", + "grassyStartMessage": "脚下青草如茵!", + "grassyClearMessage": "脚下的青草消失不见了!", + + "psychic": "精神", + "psychicStartMessage": "脚下传来了奇妙的感觉!", + "psychicClearMessage": "脚下的奇妙感觉消失了!", + + "defaultBlockMessage": "{{pokemonNameWithAffix}}正受到{{terrainName}}的的保护!" +}; diff --git a/src/locales/zh_TW/config.ts b/src/locales/zh_TW/config.ts index e4dfafdd43e..6886a750d62 100644 --- a/src/locales/zh_TW/config.ts +++ b/src/locales/zh_TW/config.ts @@ -39,7 +39,7 @@ import { statusEffect } from "./status-effect"; import { titles, trainerClasses, trainerNames } from "./trainers"; import { tutorial } from "./tutorial"; import { voucher } from "./voucher"; -import { weather } from "./weather"; +import { terrain, weather } from "./weather"; import { partyUiHandler } from "./party-ui-handler"; import { settings } from "./settings.js"; import { common } from "./common.js"; @@ -85,6 +85,7 @@ export const zhTwConfig = { splashMessages: splashMessages, starterSelectUiHandler: starterSelectUiHandler, statusEffect: statusEffect, + terrain: terrain, titles: titles, trainerClasses: trainerClasses, trainerNames: trainerNames, diff --git a/src/locales/zh_TW/weather.ts b/src/locales/zh_TW/weather.ts index ae0646ce33d..bfc5e0998dc 100644 --- a/src/locales/zh_TW/weather.ts +++ b/src/locales/zh_TW/weather.ts @@ -43,3 +43,24 @@ export const weather: SimpleTranslationEntries = { "strongWindsEffectMessage": "The mysterious air current weakened the attack!", "strongWindsClearMessage": "神秘的亂流停止了。" }; + +export const terrain: SimpleTranslationEntries = { + "misty": "薄霧", + "mistyStartMessage": "腳下霧氣繚繞!", + "mistyClearMessage": "腳下的霧氣消失不見了!", + "mistyBlockMessage": "{{pokemonNameWithAffix}}正受到薄霧場地的保護!", + + "electric": "電氣", + "electricStartMessage": "腳下電流飛閃!", + "electricClearMessage": "腳下的電流消失了!", + + "grassy": "青草", + "grassyStartMessage": "腳下青草如茵!", + "grassyClearMessage": "腳下的青草消失不見了!", + + "psychic": "精神", + "psychicStartMessage": "腳下傳來了奇妙的感覺!", + "psychicClearMessage": "腳下的奇妙感覺消失了!", + + "defaultBlockMessage": "{{pokemonNameWithAffix}}正受到{{terrainName}}的保護!" +}; diff --git a/src/test/lokalisation/battle-stat.test.ts b/src/test/localization/battle-stat.test.ts similarity index 100% rename from src/test/lokalisation/battle-stat.test.ts rename to src/test/localization/battle-stat.test.ts diff --git a/src/test/lokalisation/french.test.ts b/src/test/localization/french.test.ts similarity index 100% rename from src/test/lokalisation/french.test.ts rename to src/test/localization/french.test.ts diff --git a/src/test/lokalisation/status-effect.test.ts b/src/test/localization/status-effect.test.ts similarity index 100% rename from src/test/lokalisation/status-effect.test.ts rename to src/test/localization/status-effect.test.ts diff --git a/src/test/localization/terrain.test.ts b/src/test/localization/terrain.test.ts new file mode 100644 index 00000000000..89884290e00 --- /dev/null +++ b/src/test/localization/terrain.test.ts @@ -0,0 +1,192 @@ +import { beforeAll, describe, beforeEach, afterEach, expect, it, vi } from "vitest"; +import Phaser from "phaser"; +import GameManager from "#app/test/utils/gameManager"; +import * as overrides from "#app/overrides"; +import { Species } from "#enums/species"; +import { TerrainType, getTerrainName } from "#app/data/terrain"; +import { getTerrainStartMessage, getTerrainClearMessage, getTerrainBlockMessage } from "#app/data/weather"; +import i18next from "i18next"; +import { mockI18next } from "../utils/testUtils"; + +describe("terrain", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + i18next.init(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true); + }); + + describe("NONE", () => { + const terrainType = TerrainType.NONE; + + it("should return the obtain text", () => { + mockI18next(); + + const text = getTerrainName(terrainType); + expect(text).toBe(""); + }); + + it("should return the start text", () => { + mockI18next(); + + const text = getTerrainStartMessage(terrainType); + expect(text).toBe(undefined); + }); + + it("should return the clear text", () => { + mockI18next(); + const text = getTerrainClearMessage(terrainType); + expect(text).toBe(undefined); + }); + + it("should return the block text", async () => { + await game.startBattle([Species.MAGIKARP]); + const pokemon = game.scene.getPlayerPokemon(); + mockI18next(); + const text = getTerrainBlockMessage(pokemon, terrainType); + expect(text).toBe("terrain:defaultBlockMessage"); + }); + }); + + describe("MISTY", () => { + const terrainType = TerrainType.MISTY; + + it("should return the obtain text", () => { + mockI18next(); + + const text = getTerrainName(terrainType); + expect(text).toBe("terrain:misty"); + }); + + it("should return the start text", () => { + mockI18next(); + + const text = getTerrainStartMessage(terrainType); + expect(text).toBe("terrain:mistyStartMessage"); + }); + + it("should return the clear text", () => { + mockI18next(); + const text = getTerrainClearMessage(terrainType); + expect(text).toBe("terrain:mistyClearMessage"); + }); + + it("should return the block text", async () => { + await game.startBattle([Species.MAGIKARP]); + const pokemon = game.scene.getPlayerPokemon(); + mockI18next(); + const text = getTerrainBlockMessage(pokemon, terrainType); + expect(text).toBe("terrain:mistyBlockMessage"); + }); + }); + + describe("ELECTRIC", () => { + const terrainType = TerrainType.ELECTRIC; + + it("should return the obtain text", () => { + mockI18next(); + + const text = getTerrainName(terrainType); + expect(text).toBe("terrain:electric"); + }); + + it("should return the start text", () => { + mockI18next(); + + const text = getTerrainStartMessage(terrainType); + expect(text).toBe("terrain:electricStartMessage"); + }); + + it("should return the clear text", () => { + mockI18next(); + const text = getTerrainClearMessage(terrainType); + expect(text).toBe("terrain:electricClearMessage"); + }); + + it("should return the block text", async () => { + await game.startBattle([Species.MAGIKARP]); + const pokemon = game.scene.getPlayerPokemon(); + mockI18next(); + const text = getTerrainBlockMessage(pokemon, terrainType); + expect(text).toBe("terrain:defaultBlockMessage"); + }); + }); + + describe("GRASSY", () => { + const terrainType = TerrainType.GRASSY; + + it("should return the obtain text", () => { + mockI18next(); + + const text = getTerrainName(terrainType); + expect(text).toBe("terrain:grassy"); + }); + + it("should return the start text", () => { + mockI18next(); + + const text = getTerrainStartMessage(terrainType); + expect(text).toBe("terrain:grassyStartMessage"); + }); + + it("should return the clear text", () => { + mockI18next(); + const text = getTerrainClearMessage(terrainType); + expect(text).toBe("terrain:grassyClearMessage"); + }); + + it("should return the block text", async () => { + await game.startBattle([Species.MAGIKARP]); + const pokemon = game.scene.getPlayerPokemon(); + mockI18next(); + const text = getTerrainBlockMessage(pokemon, terrainType); + expect(text).toBe("terrain:defaultBlockMessage"); + }); + }); + + describe("PSYCHIC", () => { + const terrainType = TerrainType.PSYCHIC; + + it("should return the obtain text", () => { + mockI18next(); + + const text = getTerrainName(terrainType); + expect(text).toBe("terrain:psychic"); + }); + + it("should return the start text", () => { + mockI18next(); + + const text = getTerrainStartMessage(terrainType); + expect(text).toBe("terrain:psychicStartMessage"); + }); + + it("should return the clear text", () => { + mockI18next(); + const text = getTerrainClearMessage(terrainType); + expect(text).toBe("terrain:psychicClearMessage"); + }); + + it("should return the block text", async () => { + await game.startBattle([Species.MAGIKARP]); + const pokemon = game.scene.getPlayerPokemon(); + mockI18next(); + const text = getTerrainBlockMessage(pokemon, terrainType); + expect(text).toBe("terrain:defaultBlockMessage"); + }); + }); + + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + vi.resetAllMocks(); + }); +}); From 9a1ff57941a6a719c5fc03de160c7d26194059f0 Mon Sep 17 00:00:00 2001 From: Enoch Date: Mon, 8 Jul 2024 23:18:32 +0900 Subject: [PATCH 08/94] Modify Korean 'shiny' and gacha texts to match original (#2843) --- src/locales/ko/egg.ts | 4 ++-- src/locales/ko/settings.ts | 2 +- src/locales/ko/starter-select-ui-handler.ts | 2 +- src/ui/egg-gacha-ui-handler.ts | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/locales/ko/egg.ts b/src/locales/ko/egg.ts index 412d97bb7f3..7b10b548bc4 100644 --- a/src/locales/ko/egg.ts +++ b/src/locales/ko/egg.ts @@ -22,7 +22,7 @@ export const egg: SimpleTranslationEntries = { "hatchFromTheEgg": "알이 부화해서\n{{pokemonName}}[[가]] 태어났다!", "eggMoveUnlock": "알 기술 {{moveName}}[[를]]\n사용할 수 있게 되었다!", "rareEggMoveUnlock": "레어 알 기술 {{moveName}}[[를]]\n사용할 수 있게 되었다!", - "moveUPGacha": "기술 UP!", - "shinyUPGacha": "특별색 UP!", + "moveUPGacha": "알 기술 UP!", + "shinyUPGacha": "색이 다른 포켓몬\nUP!", "legendaryUPGacha": "UP!", } as const; diff --git a/src/locales/ko/settings.ts b/src/locales/ko/settings.ts index ef1469fc8cb..6514683cd94 100644 --- a/src/locales/ko/settings.ts +++ b/src/locales/ko/settings.ts @@ -84,7 +84,7 @@ export const settings: SimpleTranslationEntries = { "buttonCancel": "취소", "buttonStats": "스탯", "buttonCycleForm": "폼 변환", - "buttonCycleShiny": "특별한 색 변환", + "buttonCycleShiny": "색이 다른 변환", "buttonCycleGender": "성별 변환", "buttonCycleAbility": "특성 변환", "buttonCycleNature": "성격 변환", diff --git a/src/locales/ko/starter-select-ui-handler.ts b/src/locales/ko/starter-select-ui-handler.ts index 6fdd21a3454..d7f8ddbe3ed 100644 --- a/src/locales/ko/starter-select-ui-handler.ts +++ b/src/locales/ko/starter-select-ui-handler.ts @@ -32,7 +32,7 @@ export const starterSelectUiHandler: SimpleTranslationEntries = { "unlockPassive": "패시브 해금", "reduceCost": "코스트 줄이기", "sameSpeciesEgg": "알 구매하기", - "cycleShiny": ": 특별한 색", + "cycleShiny": ": 색이 다른", "cycleForm": ": 폼", "cycleGender": ": 암수", "cycleAbility": ": 특성", diff --git a/src/ui/egg-gacha-ui-handler.ts b/src/ui/egg-gacha-ui-handler.ts index 1a9320ac9e9..b5335ed8f2e 100644 --- a/src/ui/egg-gacha-ui-handler.ts +++ b/src/ui/egg-gacha-ui-handler.ts @@ -103,7 +103,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { let pokemonIconX = -20; let pokemonIconY = 6; - if (["de", "es", "fr", "pt-BR"].includes(currentLanguage)) { + if (["de", "es", "fr", "ko", "pt-BR"].includes(currentLanguage)) { gachaTextStyle = TextStyle.SMALLER_WINDOW_ALT; gachaX = 2; gachaY = 2; @@ -155,7 +155,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { gachaUpLabel.setOrigin(0.5, 0); break; case GachaType.SHINY: - if (["de", "fr"].includes(currentLanguage)) { + if (["de", "fr", "ko"].includes(currentLanguage)) { gachaUpLabel.setAlign("center"); gachaUpLabel.setY(0); } From 2ccb48093009af37b2d263c892b743a9f38ac8fd Mon Sep 17 00:00:00 2001 From: Enoch Date: Mon, 8 Jul 2024 23:21:50 +0900 Subject: [PATCH 09/94] [Localization] localize modifier.ts apply event message & translate in Korean (#2882) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * localize modifier apply event message * modify korean postposition * Update src/locales/pt_BR/modifier.ts Co-authored-by: José Ricardo Fleury Oliveira * Update src/locales/zh_CN/modifier.ts Co-authored-by: Yonmaru40 <47717431+40chyan@users.noreply.github.com> * Update src/locales/zh_TW/modifier.ts Co-authored-by: Yonmaru40 <47717431+40chyan@users.noreply.github.com> * Update src/locales/de/modifier.ts Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> * aUpdate src/locales/fr/modifier.ts Co-authored-by: Lugiad' * Update src/locales/fr/modifier.ts Co-authored-by: Lugiad' * Update src/locales/pt_BR/modifier.ts Co-authored-by: José Ricardo Fleury Oliveira * Update src/locales/pt_BR/modifier.ts Co-authored-by: José Ricardo Fleury Oliveira * Update src/locales/pt_BR/modifier.ts Co-authored-by: José Ricardo Fleury Oliveira * Update src/locales/fr/modifier.ts Co-authored-by: Lugiad' --------- Co-authored-by: José Ricardo Fleury Oliveira Co-authored-by: Yonmaru40 <47717431+40chyan@users.noreply.github.com> Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> Co-authored-by: Lugiad' --- src/locales/de/config.ts | 2 ++ src/locales/de/modifier.ts | 12 ++++++++++++ src/locales/en/config.ts | 2 ++ src/locales/en/modifier.ts | 12 ++++++++++++ src/locales/es/config.ts | 2 ++ src/locales/es/modifier.ts | 12 ++++++++++++ src/locales/fr/config.ts | 2 ++ src/locales/fr/modifier.ts | 12 ++++++++++++ src/locales/it/config.ts | 2 ++ src/locales/it/modifier.ts | 12 ++++++++++++ src/locales/ko/config.ts | 2 ++ src/locales/ko/modifier.ts | 12 ++++++++++++ src/locales/pt_BR/config.ts | 2 ++ src/locales/pt_BR/modifier.ts | 12 ++++++++++++ src/locales/zh_CN/config.ts | 2 ++ src/locales/zh_CN/modifier.ts | 12 ++++++++++++ src/locales/zh_TW/config.ts | 2 ++ src/locales/zh_TW/modifier.ts | 12 ++++++++++++ src/modifier/modifier.ts | 20 ++++++++++++-------- 19 files changed, 138 insertions(+), 8 deletions(-) create mode 100644 src/locales/de/modifier.ts create mode 100644 src/locales/en/modifier.ts create mode 100644 src/locales/es/modifier.ts create mode 100644 src/locales/fr/modifier.ts create mode 100644 src/locales/it/modifier.ts create mode 100644 src/locales/ko/modifier.ts create mode 100644 src/locales/pt_BR/modifier.ts create mode 100644 src/locales/zh_CN/modifier.ts create mode 100644 src/locales/zh_TW/modifier.ts diff --git a/src/locales/de/config.ts b/src/locales/de/config.ts index 3c16f81270d..ffbb2733205 100644 --- a/src/locales/de/config.ts +++ b/src/locales/de/config.ts @@ -25,6 +25,7 @@ import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { growth } from "./growth"; import { menu } from "./menu"; import { menuUiHandler } from "./menu-ui-handler"; +import { modifier } from "./modifier"; import { modifierType } from "./modifier-type"; import { move } from "./move"; import { nature } from "./nature"; @@ -73,6 +74,7 @@ export const deConfig = { growth: growth, menu: menu, menuUiHandler: menuUiHandler, + modifier: modifier, modifierType: modifierType, move: move, nature: nature, diff --git a/src/locales/de/modifier.ts b/src/locales/de/modifier.ts new file mode 100644 index 00000000000..c1a282ee5f1 --- /dev/null +++ b/src/locales/de/modifier.ts @@ -0,0 +1,12 @@ +import { SimpleTranslationEntries } from "#app/interfaces/locales"; + +export const modifier: SimpleTranslationEntries = { + "surviveDamageApply": "{{pokemonNameWithAffix}} hält mithilfe des Items {{typeName}} durch!", + "turnHealApply": "{{typeName}} von {{pokemonNameWithAffix}} füllt einige KP auf!", + "hitHealApply": "{{typeName}} von {{pokemonNameWithAffix}} füllt einige KP auf!", + "pokemonInstantReviveApply": "{{pokemonNameWithAffix}} wurde durch {{typeName}} wiederbelebt!", + "moneyInterestApply": "Du erhählst {{moneyAmount}} ₽ durch das Item {{typeName}}!", + "turnHeldItemTransferApply": "{{itemName}} von {{pokemonNameWithAffix}} wurde durch {{typeName}} von {{pokemonName}} absorbiert!", + "contactHeldItemTransferApply": "{{itemName}} von {{pokemonNameWithAffix}} wurde durch {{typeName}} von {{pokemonName}} geklaut!", + "enemyTurnHealApply": "{{pokemonNameWithAffix}} stellt einige KP wieder her!", +} as const; diff --git a/src/locales/en/config.ts b/src/locales/en/config.ts index b0c1b7a7105..1c5449a2e88 100644 --- a/src/locales/en/config.ts +++ b/src/locales/en/config.ts @@ -27,6 +27,7 @@ import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { growth } from "./growth"; import { menu } from "./menu"; import { menuUiHandler } from "./menu-ui-handler"; +import { modifier } from "./modifier"; import { modifierType } from "./modifier-type"; import { move } from "./move"; import { nature } from "./nature"; @@ -73,6 +74,7 @@ export const enConfig = { growth: growth, menu: menu, menuUiHandler: menuUiHandler, + modifier: modifier, modifierType: modifierType, move: move, nature: nature, diff --git a/src/locales/en/modifier.ts b/src/locales/en/modifier.ts new file mode 100644 index 00000000000..d3da4c2150b --- /dev/null +++ b/src/locales/en/modifier.ts @@ -0,0 +1,12 @@ +import { SimpleTranslationEntries } from "#app/interfaces/locales"; + +export const modifier: SimpleTranslationEntries = { + "surviveDamageApply": "{{pokemonNameWithAffix}} hung on\nusing its {{typeName}}!", + "turnHealApply": "{{pokemonNameWithAffix}} restored a little HP using\nits {{typeName}}!", + "hitHealApply": "{{pokemonNameWithAffix}} restored a little HP using\nits {{typeName}}!", + "pokemonInstantReviveApply": "{{pokemonNameWithAffix}} was revived\nby its {{typeName}}!", + "moneyInterestApply": "You received interest of ₽{{moneyAmount}}\nfrom the {{typeName}}!", + "turnHeldItemTransferApply": "{{pokemonNameWithAffix}}'s {{itemName}} was absorbed\nby {{pokemonName}}'s {{typeName}}!", + "contactHeldItemTransferApply": "{{pokemonNameWithAffix}}'s {{itemName}} was snatched\nby {{pokemonName}}'s {{typeName}}!", + "enemyTurnHealApply": "{{pokemonNameWithAffix}}\nrestored some HP!", +} as const; diff --git a/src/locales/es/config.ts b/src/locales/es/config.ts index c37f96a2aa5..341fafa4ca9 100644 --- a/src/locales/es/config.ts +++ b/src/locales/es/config.ts @@ -25,6 +25,7 @@ import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { growth } from "./growth"; import { menu } from "./menu"; import { menuUiHandler } from "./menu-ui-handler"; +import { modifier } from "./modifier"; import { modifierType } from "./modifier-type"; import { move } from "./move"; import { nature } from "./nature"; @@ -73,6 +74,7 @@ export const esConfig = { growth: growth, menu: menu, menuUiHandler: menuUiHandler, + modifier: modifier, modifierType: modifierType, move: move, nature: nature, diff --git a/src/locales/es/modifier.ts b/src/locales/es/modifier.ts new file mode 100644 index 00000000000..d3da4c2150b --- /dev/null +++ b/src/locales/es/modifier.ts @@ -0,0 +1,12 @@ +import { SimpleTranslationEntries } from "#app/interfaces/locales"; + +export const modifier: SimpleTranslationEntries = { + "surviveDamageApply": "{{pokemonNameWithAffix}} hung on\nusing its {{typeName}}!", + "turnHealApply": "{{pokemonNameWithAffix}} restored a little HP using\nits {{typeName}}!", + "hitHealApply": "{{pokemonNameWithAffix}} restored a little HP using\nits {{typeName}}!", + "pokemonInstantReviveApply": "{{pokemonNameWithAffix}} was revived\nby its {{typeName}}!", + "moneyInterestApply": "You received interest of ₽{{moneyAmount}}\nfrom the {{typeName}}!", + "turnHeldItemTransferApply": "{{pokemonNameWithAffix}}'s {{itemName}} was absorbed\nby {{pokemonName}}'s {{typeName}}!", + "contactHeldItemTransferApply": "{{pokemonNameWithAffix}}'s {{itemName}} was snatched\nby {{pokemonName}}'s {{typeName}}!", + "enemyTurnHealApply": "{{pokemonNameWithAffix}}\nrestored some HP!", +} as const; diff --git a/src/locales/fr/config.ts b/src/locales/fr/config.ts index af18aab3400..d8f9a6601c1 100644 --- a/src/locales/fr/config.ts +++ b/src/locales/fr/config.ts @@ -25,6 +25,7 @@ import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { growth } from "./growth"; import { menu } from "./menu"; import { menuUiHandler } from "./menu-ui-handler"; +import { modifier } from "./modifier"; import { modifierType } from "./modifier-type"; import { move } from "./move"; import { nature } from "./nature"; @@ -73,6 +74,7 @@ export const frConfig = { growth: growth, menu: menu, menuUiHandler: menuUiHandler, + modifier: modifier, modifierType: modifierType, move: move, nature: nature, diff --git a/src/locales/fr/modifier.ts b/src/locales/fr/modifier.ts new file mode 100644 index 00000000000..f215e258a76 --- /dev/null +++ b/src/locales/fr/modifier.ts @@ -0,0 +1,12 @@ +import { SimpleTranslationEntries } from "#app/interfaces/locales"; + +export const modifier: SimpleTranslationEntries = { + "surviveDamageApply": "{{pokemonNameWithAffix}} tient bon\ngrâce à son {{typeName}} !", + "turnHealApply": "Les PV de {{pokemonNameWithAffix}}\nsont un peu restaurés par les {{typeName}} !", + "hitHealApply": "Les PV de {{pokemonNameWithAffix}}\nsont un peu restaurés par le {{typeName}} !", + "pokemonInstantReviveApply": "{{pokemonNameWithAffix}} a repris connaissance\navec sa {{typeName}} et est prêt à se battre de nouveau !", + "moneyInterestApply": "La {{typeName}} vous rapporte\n{{moneyAmount}} ₽ d’intérêts !", + "turnHeldItemTransferApply": "{{itemName}} de {{pokemonNameWithAffix}} est absorbé·e\npar le {{typeName}} de {{pokemonName}} !", + "contactHeldItemTransferApply": "{{itemName}} de {{pokemonNameWithAffix}} est volé·e\npar l’{{typeName}} de {{pokemonName}} !", + "enemyTurnHealApply": "{{pokemonNameWithAffix}}\nrestaure un peu ses PV !", +} as const; diff --git a/src/locales/it/config.ts b/src/locales/it/config.ts index ac77e814623..5b370b00e4b 100644 --- a/src/locales/it/config.ts +++ b/src/locales/it/config.ts @@ -25,6 +25,7 @@ import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { growth } from "./growth"; import { menu } from "./menu"; import { menuUiHandler } from "./menu-ui-handler"; +import { modifier } from "./modifier"; import { modifierType } from "./modifier-type"; import { move } from "./move"; import { nature } from "./nature"; @@ -73,6 +74,7 @@ export const itConfig = { growth: growth, menu: menu, menuUiHandler: menuUiHandler, + modifier: modifier, modifierType: modifierType, move: move, nature: nature, diff --git a/src/locales/it/modifier.ts b/src/locales/it/modifier.ts new file mode 100644 index 00000000000..d3da4c2150b --- /dev/null +++ b/src/locales/it/modifier.ts @@ -0,0 +1,12 @@ +import { SimpleTranslationEntries } from "#app/interfaces/locales"; + +export const modifier: SimpleTranslationEntries = { + "surviveDamageApply": "{{pokemonNameWithAffix}} hung on\nusing its {{typeName}}!", + "turnHealApply": "{{pokemonNameWithAffix}} restored a little HP using\nits {{typeName}}!", + "hitHealApply": "{{pokemonNameWithAffix}} restored a little HP using\nits {{typeName}}!", + "pokemonInstantReviveApply": "{{pokemonNameWithAffix}} was revived\nby its {{typeName}}!", + "moneyInterestApply": "You received interest of ₽{{moneyAmount}}\nfrom the {{typeName}}!", + "turnHeldItemTransferApply": "{{pokemonNameWithAffix}}'s {{itemName}} was absorbed\nby {{pokemonName}}'s {{typeName}}!", + "contactHeldItemTransferApply": "{{pokemonNameWithAffix}}'s {{itemName}} was snatched\nby {{pokemonName}}'s {{typeName}}!", + "enemyTurnHealApply": "{{pokemonNameWithAffix}}\nrestored some HP!", +} as const; diff --git a/src/locales/ko/config.ts b/src/locales/ko/config.ts index fa81dee55ab..5b29a46c044 100644 --- a/src/locales/ko/config.ts +++ b/src/locales/ko/config.ts @@ -25,6 +25,7 @@ import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { growth } from "./growth"; import { menu } from "./menu"; import { menuUiHandler } from "./menu-ui-handler"; +import { modifier } from "./modifier"; import { modifierType } from "./modifier-type"; import { move } from "./move"; import { nature } from "./nature"; @@ -73,6 +74,7 @@ export const koConfig = { growth: growth, menu: menu, menuUiHandler: menuUiHandler, + modifier: modifier, modifierType: modifierType, move: move, nature: nature, diff --git a/src/locales/ko/modifier.ts b/src/locales/ko/modifier.ts new file mode 100644 index 00000000000..c61d2b3def0 --- /dev/null +++ b/src/locales/ko/modifier.ts @@ -0,0 +1,12 @@ +import { SimpleTranslationEntries } from "#app/interfaces/locales"; + +export const modifier: SimpleTranslationEntries = { + "surviveDamageApply": "{{pokemonNameWithAffix}}[[는]]\n{{typeName}}[[로]] 버텼다!!", + "turnHealApply": "{{pokemonNameWithAffix}}[[는]]\n{{typeName}}[[로]] 인해 조금 회복했다.", + "hitHealApply": "{{pokemonNameWithAffix}}[[는]]\n{{typeName}}[[로]] 인해 조금 회복했다.", + "pokemonInstantReviveApply": "{{pokemonNameWithAffix}}[[는]] {{typeName}}[[로]]\n정신을 차려 싸울 수 있게 되었다!", + "moneyInterestApply": "{{typeName}}[[로]]부터\n₽{{moneyAmount}}[[를]] 받았다!", + "turnHeldItemTransferApply": "{{pokemonName}}의 {{typeName}}[[는]]\n{{pokemonNameWithAffix}}의 {{itemName}}[[를]] 흡수했다!", + "contactHeldItemTransferApply": "{{pokemonName}}의 {{typeName}}[[는]]\n{{pokemonNameWithAffix}}의 {{itemName}}[[를]] 가로챘다!", + "enemyTurnHealApply": "{{pokemonNameWithAffix}}의\n체력이 약간 회복되었다!", +} as const; diff --git a/src/locales/pt_BR/config.ts b/src/locales/pt_BR/config.ts index 4b3649f96ad..c02da112770 100644 --- a/src/locales/pt_BR/config.ts +++ b/src/locales/pt_BR/config.ts @@ -25,6 +25,7 @@ import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { growth } from "./growth"; import { menu } from "./menu"; import { menuUiHandler } from "./menu-ui-handler"; +import { modifier } from "./modifier"; import { modifierType } from "./modifier-type"; import { move } from "./move"; import { nature } from "./nature"; @@ -73,6 +74,7 @@ export const ptBrConfig = { growth: growth, menu: menu, menuUiHandler: menuUiHandler, + modifier: modifier, modifierType: modifierType, move: move, nature: nature, diff --git a/src/locales/pt_BR/modifier.ts b/src/locales/pt_BR/modifier.ts new file mode 100644 index 00000000000..7cc90df5caa --- /dev/null +++ b/src/locales/pt_BR/modifier.ts @@ -0,0 +1,12 @@ +import { SimpleTranslationEntries } from "#app/interfaces/locales"; + +export const modifier: SimpleTranslationEntries = { + "surviveDamageApply": "{{pokemonNameWithAffix}} aguentou o tranco\nusando sua {{typeName}}!", + "turnHealApply": "{{pokemonNameWithAffix}} restaurou um pouco de PS usando\nsuas {{typeName}}!", + "hitHealApply": "{{pokemonNameWithAffix}} restaurou um pouco de PS usando\nsua {{typeName}}!", + "pokemonInstantReviveApply": "{{pokemonNameWithAffix}} foi revivido\npor sua {{typeName}}!", + "moneyInterestApply": "Você recebeu um juros de ₽{{moneyAmount}}\nde sua {{typeName}}!", + "turnHeldItemTransferApply": "{{itemName}} de {{pokemonNameWithAffix}} foi absorvido(a)\npelo {{typeName}} de {{pokemonName}}!", + "contactHeldItemTransferApply": "{{itemName}} de {{pokemonNameWithAffix}} foi pego(a)\npela {{typeName}} de {{pokemonName}}!", + "enemyTurnHealApply": "{{pokemonNameWithAffix}}\nrestaurou um pouco de seus PS!", +} as const; diff --git a/src/locales/zh_CN/config.ts b/src/locales/zh_CN/config.ts index b55b2aa865c..eaf7ba4cc09 100644 --- a/src/locales/zh_CN/config.ts +++ b/src/locales/zh_CN/config.ts @@ -25,6 +25,7 @@ import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { growth } from "./growth"; import { menu } from "./menu"; import { menuUiHandler } from "./menu-ui-handler"; +import { modifier } from "./modifier"; import { modifierType } from "./modifier-type"; import { move } from "./move"; import { nature } from "./nature"; @@ -73,6 +74,7 @@ export const zhCnConfig = { growth: growth, menu: menu, menuUiHandler: menuUiHandler, + modifier: modifier, modifierType: modifierType, move: move, nature: nature, diff --git a/src/locales/zh_CN/modifier.ts b/src/locales/zh_CN/modifier.ts new file mode 100644 index 00000000000..fabd17465b2 --- /dev/null +++ b/src/locales/zh_CN/modifier.ts @@ -0,0 +1,12 @@ +import { SimpleTranslationEntries } from "#app/interfaces/locales"; + +export const modifier: SimpleTranslationEntries = { + "surviveDamageApply": "{{pokemonNameWithAffix}}用{{typeName}}\n撑住了!", + "turnHealApply": "{{pokemonNameWithAffix}}用{{typeName}}\n回复了体力!", + "hitHealApply": "{{pokemonNameWithAffix}}用{{typeName}}\n回复了体力!", + "pokemonInstantReviveApply": "{{pokemonNameWithAffix}}用{{typeName}}\n恢复了活力!", + "moneyInterestApply": "用{{typeName}}\n获得了 ₽{{moneyAmount}} 利息!", + "turnHeldItemTransferApply": "{{pokemonNameWithAffix}}的{{itemName}}被\n{{pokemonName}}的{{typeName}}吸收了!", + "contactHeldItemTransferApply": "{{pokemonNameWithAffix}}的{{itemName}}被\n{{pokemonName}}的{{typeName}}夺取了!", + "enemyTurnHealApply": "{{pokemonNameWithAffix}}\n回复了一些体力!", +} as const; diff --git a/src/locales/zh_TW/config.ts b/src/locales/zh_TW/config.ts index 6886a750d62..f19a6941a88 100644 --- a/src/locales/zh_TW/config.ts +++ b/src/locales/zh_TW/config.ts @@ -25,6 +25,7 @@ import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { growth } from "./growth"; import { menu } from "./menu"; import { menuUiHandler } from "./menu-ui-handler"; +import { modifier } from "./modifier"; import { modifierType } from "./modifier-type"; import { move } from "./move"; import { nature } from "./nature"; @@ -73,6 +74,7 @@ export const zhTwConfig = { growth: growth, menu: menu, menuUiHandler: menuUiHandler, + modifier: modifier, modifierType: modifierType, move: move, nature: nature, diff --git a/src/locales/zh_TW/modifier.ts b/src/locales/zh_TW/modifier.ts new file mode 100644 index 00000000000..01de87827c0 --- /dev/null +++ b/src/locales/zh_TW/modifier.ts @@ -0,0 +1,12 @@ +import { SimpleTranslationEntries } from "#app/interfaces/locales"; + +export const modifier: SimpleTranslationEntries = { + "surviveDamageApply": "{{pokemonNameWithAffix}}用{{typeName}}\n撐住了!", + "turnHealApply": "{{pokemonNameWithAffix}}用{{typeName}}\n回復了體力!", + "hitHealApply": "{{pokemonNameWithAffix}}用{{typeName}}\n回復了體力!", + "pokemonInstantReviveApply": "{{pokemonNameWithAffix}}用{{typeName}}\n回復了活力!", + "moneyInterestApply": "用{{typeName}}\n獲得了 ₽{{moneyAmount}} 利息!", + "turnHeldItemTransferApply": "{{pokemonNameWithAffix}}的{{itemName}}被\n{{pokemonName}}的{{typeName}}吸收了!", + "contactHeldItemTransferApply": "{{pokemonNameWithAffix}}的{{itemName}}被\n{{pokemonName}}的{{typeName}}奪取了!", + "enemyTurnHealApply": "{{pokemonNameWithAffix}}\n回復了一些體力!", +} as const; diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 0b339906cc5..6f098ade124 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -24,6 +24,7 @@ import * as Overrides from "../overrides"; import { ModifierType, modifierTypes } from "./modifier-type"; import { Command } from "#app/ui/command-ui-handler.js"; import { Species } from "#enums/species"; +import i18next from "i18next"; import { allMoves } from "#app/data/move.js"; import { Abilities } from "#app/enums/abilities.js"; @@ -965,7 +966,7 @@ export class SurviveDamageModifier extends PokemonHeldItemModifier { if (!surviveDamage.value && pokemon.randSeedInt(10) < this.getStackCount()) { surviveDamage.value = true; - pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` hung on\nusing its ${this.type.name}!`)); + pokemon.scene.queueMessage(i18next.t("modifier:surviveDamageApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name })); return true; } @@ -1070,7 +1071,7 @@ export class TurnHealModifier extends PokemonHeldItemModifier { if (pokemon.getHpRatio() < 1) { const scene = pokemon.scene; scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(), - Math.max(Math.floor(pokemon.getMaxHp() / 16) * this.stackCount, 1), getPokemonMessage(pokemon, `'s ${this.type.name}\nrestored its HP a little!`), true)); + Math.max(Math.floor(pokemon.getMaxHp() / 16) * this.stackCount, 1), i18next.t("modifier:turnHealApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name }), true)); return true; } @@ -1161,7 +1162,7 @@ export class HitHealModifier extends PokemonHeldItemModifier { if (pokemon.turnData.damageDealt && pokemon.getHpRatio() < 1) { const scene = pokemon.scene; scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(), - Math.max(Math.floor(pokemon.turnData.damageDealt / 8) * this.stackCount, 1), getPokemonMessage(pokemon, `'s ${this.type.name}\nrestored its HP a little!`), true)); + Math.max(Math.floor(pokemon.turnData.damageDealt / 8) * this.stackCount, 1), i18next.t("modifier:hitHealApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name }), true)); } return true; @@ -1296,7 +1297,7 @@ export class PokemonInstantReviveModifier extends PokemonHeldItemModifier { const pokemon = args[0] as Pokemon; pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(), - Math.max(Math.floor(pokemon.getMaxHp() / 2), 1), getPokemonMessage(pokemon, ` was revived\nby its ${this.type.name}!`), false, false, true)); + Math.max(Math.floor(pokemon.getMaxHp() / 2), 1), i18next.t("modifier:pokemonInstantReviveApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name }), false, false, true)); pokemon.resetStatus(true, false, true); return true; @@ -2010,7 +2011,10 @@ export class MoneyInterestModifier extends PersistentModifier { const interestAmount = Math.floor(scene.money * 0.1 * this.getStackCount()); scene.addMoney(interestAmount); - scene.queueMessage(`You received interest of ₽${interestAmount.toLocaleString("en-US")}\nfrom the ${this.type.name}!`, null, true); + const userLocale = navigator.language || "en-US"; + const formattedMoneyAmount = interestAmount.toLocaleString(userLocale); + const message = i18next.t("modifier:moneyInterestApply", { moneyAmount: formattedMoneyAmount, typeName: this.type.name }); + scene.queueMessage(message, null, true); return true; } @@ -2231,7 +2235,7 @@ export class TurnHeldItemTransferModifier extends HeldItemTransferModifier { } getTransferMessage(pokemon: Pokemon, targetPokemon: Pokemon, item: ModifierTypes.ModifierType): string { - return getPokemonMessage(targetPokemon, `'s ${item.name} was absorbed\nby ${pokemon.name}'s ${this.type.name}!`); + return i18next.t("modifier:turnHeldItemTransferApply", { pokemonNameWithAffix: getPokemonNameWithAffix(targetPokemon), itemName: item.name, pokemonName: pokemon.name, typeName: this.type.name }); } getMaxHeldItemCount(pokemon: Pokemon): integer { @@ -2285,7 +2289,7 @@ export class ContactHeldItemTransferChanceModifier extends HeldItemTransferModif } getTransferMessage(pokemon: Pokemon, targetPokemon: Pokemon, item: ModifierTypes.ModifierType): string { - return getPokemonMessage(targetPokemon, `'s ${item.name} was snatched\nby ${pokemon.name}'s ${this.type.name}!`); + return i18next.t("modifier:contactHeldItemTransferApply", { pokemonNameWithAffix: getPokemonNameWithAffix(targetPokemon), itemName: item.name, pokemonName: pokemon.name, typeName: this.type.name }); } getMaxHeldItemCount(pokemon: Pokemon): integer { @@ -2443,7 +2447,7 @@ export class EnemyTurnHealModifier extends EnemyPersistentModifier { if (pokemon.getHpRatio() < 1) { const scene = pokemon.scene; scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(), - Math.max(Math.floor(pokemon.getMaxHp() / (100 / this.healPercent)) * this.stackCount, 1), getPokemonMessage(pokemon, "\nrestored some HP!"), true, false, false, false, true)); + Math.max(Math.floor(pokemon.getMaxHp() / (100 / this.healPercent)) * this.stackCount, 1), i18next.t("modifier:enemyTurnHealApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), true, false, false, false, true)); return true; } From 0d9dd1dfc8c5d2161c1f613e0aac5bff19bd359f Mon Sep 17 00:00:00 2001 From: mercurius-00 <80205689+mercurius-00@users.noreply.github.com> Date: Mon, 8 Jul 2024 22:25:46 +0800 Subject: [PATCH 10/94] Localized moneyPickedUp message (#2788) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Localized moneyPickedUp message * Update src/locales/ko/battle.ts Co-authored-by: Leo Kim <47556641+KimJeongSun@users.noreply.github.com> * Update src/locales/fr/battle.ts Co-authored-by: Lugiad' * Update src/locales/de/battle.ts Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> * Update src/locales/pt_BR/battle.ts Co-authored-by: José Ricardo Fleury Oliveira * Update battle.ts * Update battle.ts --------- Co-authored-by: Leo Kim <47556641+KimJeongSun@users.noreply.github.com> Co-authored-by: Lugiad' Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> Co-authored-by: José Ricardo Fleury Oliveira --- src/battle.ts | 6 +++++- src/locales/de/battle.ts | 1 + src/locales/en/battle.ts | 1 + src/locales/es/battle.ts | 1 + src/locales/fr/battle.ts | 1 + src/locales/it/battle.ts | 1 + src/locales/ko/battle.ts | 1 + src/locales/pt_BR/battle.ts | 1 + src/locales/zh_CN/battle.ts | 1 + src/locales/zh_TW/battle.ts | 1 + 10 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/battle.ts b/src/battle.ts index c3a481e9956..3f2519df3e8 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -13,6 +13,7 @@ import { Moves } from "#enums/moves"; import { PlayerGender } from "#enums/player-gender"; import { Species } from "#enums/species"; import { TrainerType } from "#enums/trainer-type"; +import i18next from "#app/plugins/i18n"; export enum BattleType { WILD, @@ -173,7 +174,10 @@ export default class Battle { scene.addMoney(moneyAmount.value); - scene.queueMessage(`You picked up ₽${moneyAmount.value.toLocaleString("en-US")}!`, null, true); + const userLocale = navigator.language || "en-US"; + const formattedMoneyAmount = moneyAmount.value.toLocaleString(userLocale); + const message = i18next.t("battle:moneyPickedUp", { moneyAmount: formattedMoneyAmount }); + scene.queueMessage(message, null, true); scene.currentBattle.moneyScattered = 0; } diff --git a/src/locales/de/battle.ts b/src/locales/de/battle.ts index 099020d46d5..06b9ec719ba 100644 --- a/src/locales/de/battle.ts +++ b/src/locales/de/battle.ts @@ -14,6 +14,7 @@ export const battle: SimpleTranslationEntries = { "switchQuestion": "Möchtest du\n{{pokemonName}} auswechseln?", "trainerDefeated": "{{trainerName}}\nwurde besiegt!", "moneyWon": "Du gewinnst\n{{moneyAmount}} ₽!", + "moneyPickedUp": "Du hebst {{moneyAmount}} ₽ auf!", "pokemonCaught": "{{pokemonName}} wurde gefangen!", "addedAsAStarter": "{{pokemonName}} wurde als Starterpokémon hinzugefügt!", "partyFull": "Dein Team ist voll.\nMöchtest du ein Pokémon durch {{pokemonName}} ersetzen?", diff --git a/src/locales/en/battle.ts b/src/locales/en/battle.ts index a42743ef254..c7e2ef96be4 100644 --- a/src/locales/en/battle.ts +++ b/src/locales/en/battle.ts @@ -14,6 +14,7 @@ export const battle: SimpleTranslationEntries = { "switchQuestion": "Will you switch\n{{pokemonName}}?", "trainerDefeated": "You defeated\n{{trainerName}}!", "moneyWon": "You got\n₽{{moneyAmount}} for winning!", + "moneyPickedUp": "You picked up ₽{{moneyAmount}}!", "pokemonCaught": "{{pokemonName}} was caught!", "addedAsAStarter": "{{pokemonName}} has been\nadded as a starter!", "partyFull": "Your party is full.\nRelease a Pokémon to make room for {{pokemonName}}?", diff --git a/src/locales/es/battle.ts b/src/locales/es/battle.ts index ddba5fab9a8..bc0dd1e4b78 100644 --- a/src/locales/es/battle.ts +++ b/src/locales/es/battle.ts @@ -14,6 +14,7 @@ export const battle: SimpleTranslationEntries = { "switchQuestion": "¿Quieres cambiar a\n{{pokemonName}}?", "trainerDefeated": "¡Has derrotado a\n{{trainerName}}!", "moneyWon": "¡Has ganado\n₽{{moneyAmount}} por vencer!", + "moneyPickedUp": "You picked up ₽{{moneyAmount}}!", "pokemonCaught": "¡{{pokemonName}} atrapado!", "addedAsAStarter": "{{pokemonName}} ha sido añadido\na tus iniciales!", "partyFull": "Tu equipo esta completo.\n¿Quieres liberar un Pokémon para meter a {{pokemonName}}?", diff --git a/src/locales/fr/battle.ts b/src/locales/fr/battle.ts index 5bcf0763ef0..fc155664aaa 100644 --- a/src/locales/fr/battle.ts +++ b/src/locales/fr/battle.ts @@ -14,6 +14,7 @@ export const battle: SimpleTranslationEntries = { "switchQuestion": "Voulez-vous changer\nvotre {{pokemonName}} ?", "trainerDefeated": "Vous avez battu\n{{trainerName}} !", "moneyWon": "Vous remportez\n{{moneyAmount}} ₽ !", + "moneyPickedUp": "Vous obtenez {{moneyAmount}} ₽ !", "pokemonCaught": "Vous avez attrapé {{pokemonName}} !", "addedAsAStarter": "{{pokemonName}} est ajouté\ncomme starter !", "partyFull": "Votre équipe est pleine.\nRelâcher un Pokémon pour {{pokemonName}} ?", diff --git a/src/locales/it/battle.ts b/src/locales/it/battle.ts index f44ca8e493c..787888e333b 100644 --- a/src/locales/it/battle.ts +++ b/src/locales/it/battle.ts @@ -14,6 +14,7 @@ export const battle: SimpleTranslationEntries = { "switchQuestion": "Vuoi cambiare\n{{pokemonName}}?", "trainerDefeated": "Hai sconfitto\n{{trainerName}}!", "moneyWon": "Hai vinto {{moneyAmount}}₽", + "moneyPickedUp": "You picked up ₽{{moneyAmount}}!", "pokemonCaught": "Preso! {{pokemonName}} è stato catturato!", "addedAsAStarter": "{{pokemonName}} has been\nadded as a starter!", "partyFull": "La tua squadra è al completo.\nVuoi liberare un Pokémon per far spazio a {{pokemonName}}?", diff --git a/src/locales/ko/battle.ts b/src/locales/ko/battle.ts index 25ff106946b..dbb425da63f 100644 --- a/src/locales/ko/battle.ts +++ b/src/locales/ko/battle.ts @@ -14,6 +14,7 @@ export const battle: SimpleTranslationEntries = { "switchQuestion": "{{pokemonName}}[[를]]\n교체하시겠습니까?", "trainerDefeated": "{{trainerName}}[[와]]의\n승부에서 이겼다!", "moneyWon": "상금으로\n₽{{moneyAmount}}을 손에 넣었다!", + "moneyPickedUp": "₽{{moneyAmount}}을 주웠다!", "pokemonCaught": "신난다-!\n{{pokemonName}}[[를]] 잡았다!", "addedAsAStarter": "{{pokemonName}}[[가]]\n스타팅 포켓몬에 추가되었다!", "partyFull": "지닌 포켓몬이 가득 찼습니다. {{pokemonName}}[[를]]\n대신해 포켓몬을 놓아주시겠습니까?", diff --git a/src/locales/pt_BR/battle.ts b/src/locales/pt_BR/battle.ts index b63a03b25cf..8c97d2be6de 100644 --- a/src/locales/pt_BR/battle.ts +++ b/src/locales/pt_BR/battle.ts @@ -14,6 +14,7 @@ export const battle: SimpleTranslationEntries = { "switchQuestion": "Quer trocar\nde {{pokemonName}}?", "trainerDefeated": "Você derrotou\n{{trainerName}}!", "moneyWon": "Você ganhou\n₽{{moneyAmount}} por ganhar!", + "moneyPickedUp": "Você pegou ₽{{moneyAmount}} do chão!", "pokemonCaught": "{{pokemonName}} foi capturado!", "addedAsAStarter": "{{pokemonName}} foi adicionado\naos seus iniciais!", "partyFull": "Sua equipe está cheia.\nSolte um Pokémon para ter espaço para {{pokemonName}}?", diff --git a/src/locales/zh_CN/battle.ts b/src/locales/zh_CN/battle.ts index d8388064bd7..ff2e90a2c59 100644 --- a/src/locales/zh_CN/battle.ts +++ b/src/locales/zh_CN/battle.ts @@ -14,6 +14,7 @@ export const battle: SimpleTranslationEntries = { "switchQuestion": "要更换\n{{pokemonName}}吗?", "trainerDefeated": "你击败了\n{{trainerName}}!", "moneyWon": "你赢得了\n₽{{moneyAmount}}!", + "moneyPickedUp": "捡到了 ₽{{moneyAmount}}!", "pokemonCaught": "{{pokemonName}}被抓住了!", "addedAsAStarter": "增加了{{pokemonName}}作为\n一个新的基础宝可梦!", "partyFull": "你的队伍已满员。是否放生其他宝可梦\n为{{pokemonName}}腾出空间?", diff --git a/src/locales/zh_TW/battle.ts b/src/locales/zh_TW/battle.ts index bfd3885ca31..bc7b712185a 100644 --- a/src/locales/zh_TW/battle.ts +++ b/src/locales/zh_TW/battle.ts @@ -12,6 +12,7 @@ export const battle: SimpleTranslationEntries = { "trainerGo": "{{trainerName}} 派出了 {{pokemonName}}!", "switchQuestion": "要更換\n{{pokemonName}}嗎?", "trainerDefeated": "你擊敗了\n{{trainerName}}!", + "moneyPickedUp": "撿到了 ₽{{moneyAmount}}!", "pokemonCaught": "{{pokemonName}} 被抓住了!", "addedAsAStarter": "{{pokemonName}} has been\nadded as a starter!", "pokemon": "寶可夢", From f9327680ddf602947c6c3ae9ffb2c37fc30f9c1b Mon Sep 17 00:00:00 2001 From: Tempoanon <163687446+Tempo-anon@users.noreply.github.com> Date: Mon, 8 Jul 2024 10:33:47 -0400 Subject: [PATCH 11/94] [Feature] Stop random trainers from spawning near fixed battles (#2610) * Stop trainer spawns on evil team and E4 floors * Thanks Xavion * change "floors" to "wave" in coment * at test for not spawning 3 waves within fixed trainer battle * remove out-commented code * apply code formatting * Updated test and make sure isWaveTrainer returns a boolean * Update comment --------- Co-authored-by: Felix Staud --- src/field/arena.ts | 4 +++ src/game-mode.ts | 19 ++++++++++++-- src/test/game-mode.test.ts | 52 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 src/test/game-mode.test.ts diff --git a/src/field/arena.ts b/src/field/arena.ts index 2d20abeedd1..ddb3499b3ae 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -384,6 +384,10 @@ export class Arena { return weatherMultiplier * terrainMultiplier; } + /** + * Gets the denominator for the chance for a trainer spawn + * @returns n where 1/n is the chance of a trainer battle + */ getTrainerChance(): integer { switch (this.biomeType) { case Biome.METROPOLIS: diff --git a/src/game-mode.ts b/src/game-mode.ts index 0a472e223e3..dd22e69d719 100644 --- a/src/game-mode.ts +++ b/src/game-mode.ts @@ -107,22 +107,37 @@ export class GameMode implements GameModeConfig { } } + /** + * Determines whether or not to generate a trainer + * @param waveIndex the current floor the player is on (trainer sprites fail to generate on X1 floors) + * @param arena the arena that contains the scene and functions + * @returns true if a trainer should be generated, false otherwise + */ isWaveTrainer(waveIndex: integer, arena: Arena): boolean { + /** + * Daily spawns trainers on floors 5, 15, 20, 25, 30, 35, 40, and 45 + */ if (this.isDaily) { return waveIndex % 10 === 5 || (!(waveIndex % 10) && waveIndex > 10 && !this.isWaveFinal(waveIndex)); } if ((waveIndex % 30) === (arena.scene.offsetGym ? 0 : 20) && !this.isWaveFinal(waveIndex)) { return true; } else if (waveIndex % 10 !== 1 && waveIndex % 10) { + /** + * Do not check X1 floors since there's a bug that stops trainer sprites from appearing + * after a X0 full party heal + */ + const trainerChance = arena.getTrainerChance(); let allowTrainerBattle = true; if (trainerChance) { const waveBase = Math.floor(waveIndex / 10) * 10; + // Stop generic trainers from spawning in within 3 waves of a trainer battle for (let w = Math.max(waveIndex - 3, waveBase + 2); w <= Math.min(waveIndex + 3, waveBase + 9); w++) { if (w === waveIndex) { continue; } - if ((w % 30) === (arena.scene.offsetGym ? 0 : 20) || this.isFixedBattle(waveIndex)) { + if ((w % 30) === (arena.scene.offsetGym ? 0 : 20) || this.isFixedBattle(w)) { allowTrainerBattle = false; break; } else if (w < waveIndex) { @@ -138,7 +153,7 @@ export class GameMode implements GameModeConfig { } } } - return allowTrainerBattle && trainerChance && !Utils.randSeedInt(trainerChance); + return Boolean(allowTrainerBattle && trainerChance && !Utils.randSeedInt(trainerChance)); } return false; } diff --git a/src/test/game-mode.test.ts b/src/test/game-mode.test.ts new file mode 100644 index 00000000000..04376c20361 --- /dev/null +++ b/src/test/game-mode.test.ts @@ -0,0 +1,52 @@ +import { GameMode, GameModes, getGameMode } from "#app/game-mode.js"; +import { + afterEach, + beforeAll, + beforeEach, + describe, + expect, + it, + vi, +} from "vitest"; +import GameManager from "./utils/gameManager"; +import * as Utils from "../utils"; +describe("game-mode", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + afterEach(() => { + game.phaseInterceptor.restoreOg(); + vi.resetAllMocks(); + }); + beforeEach(() => { + game = new GameManager(phaserGame); + }); + describe("classic", () => { + let classicGameMode: GameMode; + beforeEach(() => { + classicGameMode = getGameMode(GameModes.CLASSIC); + }); + it("does NOT spawn trainers within 3 waves of fixed battle", () => { + const { arena } = game.scene; + /** set wave 16 to be a fixed trainer fight meaning wave 13-19 don't allow trainer spawns */ + vi.spyOn(classicGameMode, "isFixedBattle").mockImplementation( + (n: number) => (n === 16 ? true : false) + ); + vi.spyOn(arena, "getTrainerChance").mockReturnValue(1); + vi.spyOn(Utils, "randSeedInt").mockReturnValue(0); + expect(classicGameMode.isWaveTrainer(11, arena)).toBeFalsy(); + expect(classicGameMode.isWaveTrainer(12, arena)).toBeTruthy(); + expect(classicGameMode.isWaveTrainer(13, arena)).toBeFalsy(); + expect(classicGameMode.isWaveTrainer(14, arena)).toBeFalsy(); + expect(classicGameMode.isWaveTrainer(15, arena)).toBeFalsy(); + // Wave 16 is a fixed trainer battle + expect(classicGameMode.isWaveTrainer(17, arena)).toBeFalsy(); + expect(classicGameMode.isWaveTrainer(18, arena)).toBeFalsy(); + expect(classicGameMode.isWaveTrainer(19, arena)).toBeFalsy(); + }); + }); +}); From 671c0385400c7e61228ccb0a4a9ddf946829fe9d Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Mon, 8 Jul 2024 10:46:54 -0400 Subject: [PATCH 12/94] Make battle flyout editable externally --- src/ui/battle-flyout.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/battle-flyout.ts b/src/ui/battle-flyout.ts index 5b34a6b5411..a17ce3aefbe 100644 --- a/src/ui/battle-flyout.ts +++ b/src/ui/battle-flyout.ts @@ -51,7 +51,7 @@ export default class BattleFlyout extends Phaser.GameObjects.Container { private flyoutContainer: Phaser.GameObjects.Container; /** The array of {@linkcode Phaser.GameObjects.Text} objects which are drawn on the flyout */ - private flyoutText: Phaser.GameObjects.Text[] = new Array(4); + public flyoutText: Phaser.GameObjects.Text[] = new Array(4); /** The array of {@linkcode MoveInfo} used to track moves for the {@linkcode Pokemon} linked to the flyout */ private moveInfo: MoveInfo[] = new Array(); From 864d4ffa2c23125578e7ca1cba3cbe113be7144d Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Mon, 8 Jul 2024 10:54:36 -0400 Subject: [PATCH 13/94] Progress for July 8 - Use score to display catch rates - Display catch rates in Pokeball menu - Show possible damage in type effectiveness window - Show a flyout with basic details about the opponent(s) during the start of a wild battle - Fix logger --- src/battle-scene.ts | 6 +- src/data/pokeball.ts | 24 ++++++++ src/logger.ts | 65 +++++++++++++++------ src/phases.ts | 113 ++++++++++++++++++++++++++++++++++++- src/ui/fight-ui-handler.ts | 99 +++++++++++++++++++++++++++++++- 5 files changed, 285 insertions(+), 22 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index f20c6503229..03216101f27 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -224,7 +224,7 @@ export default class BattleScene extends SceneBase { private fieldOverlay: Phaser.GameObjects.Rectangle; private shopOverlay: Phaser.GameObjects.Rectangle; public modifiers: PersistentModifier[]; - private enemyModifiers: PersistentModifier[]; + public enemyModifiers: PersistentModifier[]; public uiContainer: Phaser.GameObjects.Container; public ui: UI; @@ -1497,6 +1497,10 @@ export default class BattleScene extends SceneBase { this.scoreText.setText(`Score: ${this.score.toString()}`); this.scoreText.setVisible(this.gameMode.isDaily); } + setScoreText(text: string): void { + this.scoreText.setText(text); + this.scoreText.setVisible(true); + } updateAndShowText(duration: integer): void { const labels = [ this.luckLabelText, this.luckText ]; diff --git a/src/data/pokeball.ts b/src/data/pokeball.ts index 5964884d967..babd263d676 100644 --- a/src/data/pokeball.ts +++ b/src/data/pokeball.ts @@ -53,6 +53,30 @@ export function getPokeballName(type: PokeballType): string { } return ret; } +export function getPokeballShortName(type: PokeballType): string { + let ret: string; + switch (type) { + case PokeballType.POKEBALL: + ret = "Poké"; + break; + case PokeballType.GREAT_BALL: + ret = "Great"; + break; + case PokeballType.ULTRA_BALL: + ret = "Ultra"; + break; + case PokeballType.ROGUE_BALL: + ret = "Rogue"; + break; + case PokeballType.MASTER_BALL: + ret = "Master"; + break; + case PokeballType.LUXURY_BALL: + ret = "Luxury"; + break; + } + return ret; +} export function getPokeballCatchMultiplier(type: PokeballType): number { switch (type) { diff --git a/src/logger.ts b/src/logger.ts index 5d8a1970952..a4adee5fb4c 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -99,7 +99,7 @@ export function logTeam(scene: BattleScene, floor: integer = undefined) { if (team[0].hasTrainer()) { var sprite = scene.currentBattle.trainer.config.getSpriteKey() var trainerCat = Utils.getEnumKeys(TrainerType)[Utils.getEnumValues(TrainerType).indexOf(scene.currentBattle.trainer.config.trainerType)] - setRow("e", floor + "," + team.length + "," + sprite + ",trainer," + trainerCat + ",,,,,,,,,,,,", floor, 0) + setRow("e", floor + ",0," + sprite + ",trainer," + trainerCat + ",,,,,,,,,,,,", floor, 0) } else { for (var i = 0; i < team.length; i++) { logPokemon(scene, floor, i, team[i]) @@ -140,8 +140,23 @@ export function logPokemon(scene: BattleScene, floor: integer = undefined, slot: setRow("e", newLine, floor, slot) //console.log(localStorage.getItem(logs[logKeys.indexOf("e")][1]).split("\n")) } +export function dataSorter(a: string, b: string) { + var da = a.split(",") + var db = b.split(",") + if (da[0] == "---- " + logs[logKeys.indexOf("e")][3] + " ----") { + return -1; + } + if (db[0] == "---- " + logs[logKeys.indexOf("e")][3] + " ----") { + return 1; + } + if (da[0] == db[0]) { + return ((da[1] as any) * 1) - ((db[1] as any) * 1) + } + return ((da[0] as any) * 1) - ((db[0] as any) * 1) +} export function setRow(keyword: string, newLine: string, floor: integer, slot: integer) { var data = localStorage.getItem(logs[logKeys.indexOf(keyword)][1]).split("\n") + data.sort(dataSorter) var idx = 1 if (slot == -1) { while (idx < data.length && (data[idx].split(",")[0] as any) * 1 < floor) { @@ -153,22 +168,38 @@ export function setRow(keyword: string, newLine: string, floor: integer, slot: i while (idx < data.length && (data[idx].split(",")[0] as any) * 1 <= floor && (data[idx].split(",")[1] as any) * 1 <= slot) { idx++ } + idx-- + console.log((data[idx].split(",")[0] as any) * 1, floor, (data[idx].split(",")[1] as any) * 1, slot) + if (idx < data.length && (data[idx].split(",")[0] as any) * 1 == floor && (data[idx].split(",")[1] as any) * 1 == slot) { + data[idx] = newLine + console.log("Overwrote data at " + idx) + for (var i = 0; i < Math.max(0, idx - 2) && i < 2; i++) { + console.log(i + " " + data[i]) + } + if (Math.min(0, idx - 2) > 3) { + console.log("...") + } + for (var i = Math.max(0, idx - 2); i <= idx + 2 && i < data.length; i++) { + console.log(i + (i == idx ? " >> " : " ") + data[i]) + } + localStorage.setItem(logs[logKeys.indexOf(keyword)][1], data.join("\n")); + return; + } + idx++ + } + console.log("Inserted data at " + idx) + for (var i = 0; i < Math.max(0, idx - 2) && i < 2; i++) { + console.log(i + " " + data[i]) + } + if (Math.min(0, idx - 2) > 3) { + console.log("...") + } + for (var i = Math.max(0, idx - 2); i < idx; i++) { + console.log(i + " " + data[i]) + } + console.log(i + " >> " + newLine) + for (var i = idx; i <= idx + 2 && i < data.length; i++) { + console.log(i + " " + data[i]) } localStorage.setItem(logs[logKeys.indexOf(keyword)][1], data.slice(0, idx).join("\n") + "\n" + newLine + (data.slice(idx).length == 0 ? "" : "\n") + data.slice(idx).join("\n")); -} -export function setRowByID(key: integer, newLine: string, floor: integer, slot: integer) { - var data = localStorage.getItem(logs[key][1]).split("\n") - var idx = 1 - if (slot == -1) { - while (idx < data.length && (data[idx].split(",")[0] as any) * 1 < floor) { - idx++ - } - idx-- - slot = ((data[idx].split(",")[1] as any) * 1) + 1 - } else { - while (idx < data.length && (data[idx].split(",")[0] as any) * 1 <= floor && (data[idx].split(",")[1] as any) * 1 <= slot) { - idx++ - } - } - localStorage.setItem(logs[key][1], data.slice(0, idx).join("\n") + "\n" + newLine + (data.slice(idx).length == 0 ? "" : "\n") + data.slice(idx).join("\n")); } \ No newline at end of file diff --git a/src/phases.ts b/src/phases.ts index 18a5d112a08..54c76d8d59f 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -72,6 +72,7 @@ import ChallengeData from "./system/challenge-data"; import { Challenges } from "./enums/challenges" import PokemonData from "./system/pokemon-data" import * as LoggerTools from "./logger" +import { getNatureName } from "./data/nature"; const { t } = i18next; @@ -2058,14 +2059,51 @@ export class CheckSwitchPhase extends BattlePhase { return; } + for (var i = 0; i < this.scene.getEnemyField().length; i++) { + var pk = this.scene.getEnemyField()[i] + var maxIVs = [] + var ivnames = ["HP", "Atk", "Def", "Sp.Atk", "Sp.Def", "Speed"] + pk.ivs.forEach((iv, j) => {if (iv == 31) maxIVs.push(ivnames[j])}) + var ivDesc = maxIVs.join(",") + if (ivDesc == "") { + ivDesc = "No Max IVs" + } else { + ivDesc = "31: " + ivDesc + } + pk.getBattleInfo().flyoutMenu.toggleFlyout(true) + pk.getBattleInfo().flyoutMenu.flyoutText[0].text = getNatureName(pk.nature) + pk.getBattleInfo().flyoutMenu.flyoutText[1].text = ivDesc + pk.getBattleInfo().flyoutMenu.flyoutText[2].text = pk.getAbility().name + pk.getBattleInfo().flyoutMenu.flyoutText[3].text = pk.getPassiveAbility().name + if (pk.hasAbility(pk.species.abilityHidden, true, true)) { + pk.getBattleInfo().flyoutMenu.flyoutText[2].setColor("#e8e8a8") + } + } + this.scene.ui.showText(i18next.t("battle:switchQuestion", { pokemonName: this.useName ? pokemon.name : i18next.t("battle:pokemon") }), null, () => { this.scene.ui.setMode(Mode.CONFIRM, () => { this.scene.ui.setMode(Mode.MESSAGE); this.scene.tryRemovePhase(p => p instanceof PostSummonPhase && p.player && p.fieldIndex === this.fieldIndex); this.scene.unshiftPhase(new SwitchPhase(this.scene, this.fieldIndex, false, true)); + for (var i = 0; i < this.scene.getEnemyField().length; i++) { + this.scene.getEnemyField()[i].getBattleInfo().flyoutMenu.toggleFlyout(false) + this.scene.getEnemyField()[i].getBattleInfo().flyoutMenu.flyoutText[0].text = "???" + this.scene.getEnemyField()[i].getBattleInfo().flyoutMenu.flyoutText[1].text = "???" + this.scene.getEnemyField()[i].getBattleInfo().flyoutMenu.flyoutText[2].text = "???" + this.scene.getEnemyField()[i].getBattleInfo().flyoutMenu.flyoutText[3].text = "???" + this.scene.getEnemyField()[i].getBattleInfo().flyoutMenu.flyoutText[2].setColor("#f8f8f8") + } this.end(); }, () => { this.scene.ui.setMode(Mode.MESSAGE); + for (var i = 0; i < this.scene.getEnemyField().length; i++) { + this.scene.getEnemyField()[i].getBattleInfo().flyoutMenu.toggleFlyout(false) + this.scene.getEnemyField()[i].getBattleInfo().flyoutMenu.flyoutText[0].text = "???" + this.scene.getEnemyField()[i].getBattleInfo().flyoutMenu.flyoutText[1].text = "???" + this.scene.getEnemyField()[i].getBattleInfo().flyoutMenu.flyoutText[2].text = "???" + this.scene.getEnemyField()[i].getBattleInfo().flyoutMenu.flyoutText[3].text = "???" + this.scene.getEnemyField()[i].getBattleInfo().flyoutMenu.flyoutText[2].setColor("#f8f8f8") + } this.end(); }); }); @@ -2104,6 +2142,60 @@ export class TurnInitPhase extends FieldPhase { super(scene); } + catchCalc(pokemon: EnemyPokemon) { + const _3m = 3 * pokemon.getMaxHp(); + const _2h = 2 * pokemon.hp; + const catchRate = pokemon.species.catchRate; + const statusMultiplier = pokemon.status ? getStatusEffectCatchRateMultiplier(pokemon.status.effect) : 1; + const rate1 = Math.round(65536 / Math.sqrt(Math.sqrt(255 / (Math.round((((_3m - _2h) * catchRate * 1) / _3m) * statusMultiplier))))); + const rate2 = Math.round(65536 / Math.sqrt(Math.sqrt(255 / (Math.round((((_3m - _2h) * catchRate * 1.5) / _3m) * statusMultiplier))))); + const rate3 = Math.round(65536 / Math.sqrt(Math.sqrt(255 / (Math.round((((_3m - _2h) * catchRate * 2) / _3m) * statusMultiplier))))); + const rate4 = Math.round(65536 / Math.sqrt(Math.sqrt(255 / (Math.round((((_3m - _2h) * catchRate * 3) / _3m) * statusMultiplier))))); + + var rates = [rate1, rate2, rate3, rate4] + var rates2 = rates.map(r => ((r/65536) ** 3)) + console.log(rates2) + + return rates2 + } + + /** + * Finds the best Poké Ball to catch a Pokemon with, and the % chance of capturing it. + * @param pokemon The Pokémon to get the catch rate for. + * @param override Show the best Poké Ball to use, even if you don't have any. + * @returns The name and % rate of the best Poké Ball. + */ + findBest(pokemon: EnemyPokemon, override?: boolean) { + var rates = this.catchCalc(pokemon) + if (this.scene.pokeballCounts[0] == 0 && !override) rates[0] = 0 + if (this.scene.pokeballCounts[1] == 0 && !override) rates[1] = 0 + if (this.scene.pokeballCounts[2] == 0 && !override) rates[2] = 0 + if (this.scene.pokeballCounts[3] == 0 && !override) rates[3] = 0 + var rates2 = rates.slice() + rates2.sort(function(a, b) {return b - a}) + switch (rates2[0]) { + case rates[0]: + // Poke Balls are best + return "Poké Ball " + Math.round(rates2[0] * 100) + "%"; + case rates[1]: + // Great Balls are best + return "Great Ball " + Math.round(rates2[0] * 100) + "%"; + case rates[2]: + // Ultra Balls are best + return "Ultra Ball " + Math.round(rates2[0] * 100) + "%"; + case rates[3]: + // Rogue Balls are best + return "Rogue Ball " + Math.round(rates2[0] * 100) + "%"; + default: + // Master Balls are the only thing that will work + if (this.scene.pokeballCounts[4] != 0 || override) { + return "Master Ball"; + } else { + return "No balls" + } + } + } + start() { super.start(); @@ -2152,6 +2244,15 @@ export class TurnInitPhase extends FieldPhase { this.scene.pushPhase(new TurnStartPhase(this.scene)); + var txt = ["Turn: " + this.scene.currentBattle.turn] + if (!this.scene.getEnemyField()[0].hasTrainer()) { + this.scene.getEnemyField().forEach((pk, i) => { + txt = txt.concat(this.findBest(pk)) + }) + } + + this.scene.setScoreText(txt.join("/")) + this.end(); } } @@ -5048,6 +5149,16 @@ export class AttemptCapturePhase extends PokemonPhase { this.pokeballType = pokeballType; } + roll(y?: integer) { + var roll = (this.getPokemon() as EnemyPokemon).randSeedInt(65536) + if (y != undefined) { + console.log(roll, y, roll < y) + } else { + console.log(roll) + } + return roll; + } + start() { super.start(); @@ -5129,7 +5240,7 @@ export class AttemptCapturePhase extends PokemonPhase { shakeCounter.stop(); this.failCatch(shakeCount); } else if (shakeCount++ < 3) { - if (pokeballMultiplier === -1 || pokemon.randSeedInt(65536) < y) { + if (pokeballMultiplier === -1 || this.roll(y) < y) { this.scene.playSound("pb_move"); } else { shakeCounter.stop(); diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index ed520512443..c1018fbc74d 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -6,10 +6,15 @@ import { Mode } from "./ui"; import UiHandler from "./ui-handler"; import * as Utils from "../utils"; import { CommandPhase } from "../phases"; -import { MoveCategory } from "#app/data/move.js"; +import * as MoveData from "#app/data/move.js"; import i18next from "i18next"; import {Button} from "#enums/buttons"; -import Pokemon, { PokemonMove } from "#app/field/pokemon.js"; +import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon.js"; +import Battle from "#app/battle.js"; +import { Stat } from "#app/data/pokemon-stat.js"; +import { Abilities } from "#app/enums/abilities.js"; +import { WeatherType } from "#app/data/weather.js"; +import { Moves } from "#app/enums/moves.js"; export default class FightUiHandler extends UiHandler { private movesContainer: Phaser.GameObjects.Container; @@ -153,6 +158,93 @@ export default class FightUiHandler extends UiHandler { return !this.fieldIndex ? this.cursor : this.cursor2; } + calcDamage(scene: BattleScene, user: PlayerPokemon, target: Pokemon, move: PokemonMove) { + var power = move.getMove().power + var myAtk = 0 + var theirDef = 0 + var myAtkC = 0 + var theirDefC = 0 + switch (move.getMove().category) { + case MoveData.MoveCategory.PHYSICAL: + myAtk = user.getBattleStat(Stat.ATK, target, move.getMove()) + myAtkC = user.getBattleStat(Stat.ATK, target, move.getMove(), true) + theirDef = target.getBattleStat(Stat.DEF, user, move.getMove()) + theirDefC = target.getBattleStat(Stat.DEF, user, move.getMove(), true) + break; + case MoveData.MoveCategory.SPECIAL: + myAtk = user.getBattleStat(Stat.SPATK, target, move.getMove()) + myAtkC = user.getBattleStat(Stat.SPATK, target, move.getMove(), true) + theirDef = target.getBattleStat(Stat.SPDEF, user, move.getMove()) + theirDefC = target.getBattleStat(Stat.SPDEF, user, move.getMove(), true) + break; + case MoveData.MoveCategory.STATUS: + return "---" + } + var stabBonus = 1 + var types = user.getTypes() + // Apply STAB bonus + for (var i = 0; i < types.length; i++) { + if (types[i] == move.getMove().type) { + stabBonus = 1.5 + } + } + // Apply Tera Type bonus + if (stabBonus == 1.5) { + // STAB + if (move.getMove().type == user.getTeraType()) { + stabBonus = 2 + } + } else if (move.getMove().type == user.getTeraType()) { + stabBonus = 1.5 + } + // Apply adaptability + if (stabBonus == 2) { + // Tera-STAB + if (move.getMove().type == user.getTeraType()) { + stabBonus = 2.25 + } + } else if (stabBonus == 1.5) { + // STAB or Tera + if (move.getMove().type == user.getTeraType()) { + stabBonus = 2 + } + } else if (move.getMove().type == user.getTeraType()) { + // Adaptability + stabBonus = 1.5 + } + var weatherBonus = 1 + if (this.scene.arena.weather.weatherType == WeatherType.RAIN || this.scene.arena.weather.weatherType == WeatherType.HEAVY_RAIN) { + if (move.getMove().type == Type.WATER) { + weatherBonus = 1.5 + } + if (move.getMove().type == Type.FIRE) { + weatherBonus = this.scene.arena.weather.weatherType == WeatherType.HEAVY_RAIN ? 0 : 0.5 + } + } + if (this.scene.arena.weather.weatherType == WeatherType.SUNNY || this.scene.arena.weather.weatherType == WeatherType.HARSH_SUN) { + if (move.getMove().type == Type.FIRE) { + weatherBonus = 1.5 + } + if (move.getMove().type == Type.WATER) { + weatherBonus = this.scene.arena.weather.weatherType == WeatherType.HARSH_SUN ? 0 : (move.moveId == Moves.HYDRO_STEAM ? 1.5 : 0.5) + } + } + var typeBonus = target.getAttackMoveEffectiveness(user, move) + var modifiers = stabBonus * weatherBonus + var dmgLow = (((2*user.level/5 + 2) * power * myAtk / theirDef)/50 + 2) * 0.85 * modifiers + var dmgHigh = (((2*user.level/5 + 2) * power * myAtkC / theirDefC)/50 + 2) * 1.5 * modifiers + if (user.hasAbility(Abilities.PARENTAL_BOND)) { + // Second hit deals 0.25x damage + dmgLow *= 1.25 + dmgHigh *= 1.25 + } + return (Math.round(dmgLow) == Math.round(dmgHigh) ? Math.round(dmgLow).toString() : Math.round(dmgLow) + "-" + Math.round(dmgHigh)) + ((Math.round(dmgLow) > target.hp) ? " KO" : "") + dmgLow = Math.round((dmgLow)/target.getBattleStat(Stat.HP)*100) + dmgHigh = Math.round((dmgHigh)/target.getBattleStat(Stat.HP)*100) + return (dmgLow == dmgHigh ? dmgLow + "%" : dmgLow + "%-" + dmgHigh + "%") + ((Math.round(dmgLow) > target.hp) ? " KO" : "") + return "???" + } + setCursor(cursor: integer): boolean { const ui = this.getUi(); @@ -178,7 +270,7 @@ export default class FightUiHandler extends UiHandler { if (hasMove) { const pokemonMove = moveset[cursor]; this.typeIcon.setTexture(`types${Utils.verifyLang(i18next.resolvedLanguage) ? `_${i18next.resolvedLanguage}` : ""}`, Type[pokemonMove.getMove().type].toLowerCase()).setScale(0.8); - this.moveCategoryIcon.setTexture("categories", MoveCategory[pokemonMove.getMove().category].toLowerCase()).setScale(1.0); + this.moveCategoryIcon.setTexture("categories", MoveData.MoveCategory[pokemonMove.getMove().category].toLowerCase()).setScale(1.0); const power = pokemonMove.getMove().power; const accuracy = pokemonMove.getMove().accuracy; @@ -207,6 +299,7 @@ export default class FightUiHandler extends UiHandler { pokemon.getOpponents().forEach((opponent) => { opponent.updateEffectiveness(this.getEffectivenessText(pokemon, opponent, pokemonMove)); + opponent.updateEffectiveness(this.calcDamage(this.scene, pokemon, opponent, pokemonMove)); }); } From 60db2d034baab65f9f3d92a1935ff0a882a16d6f Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Mon, 8 Jul 2024 11:13:50 -0400 Subject: [PATCH 14/94] Update README.md Changed the README to reflect this project's purpose and progress --- README.md | 125 +++++------------------------------------------------- 1 file changed, 11 insertions(+), 114 deletions(-) diff --git a/README.md b/README.md index d1b46e630bf..24dc3490d1f 100644 --- a/README.md +++ b/README.md @@ -1,116 +1,13 @@ -PokéRogue +*PokéRogue is a browser based Pokémon fangame heavily inspired by the roguelite genre. Battle endlessly while gathering stacking items, exploring many different biomes, fighting trainers, bosses, and more!* -PokéRogue is a browser based Pokémon fangame heavily inspired by the roguelite genre. Battle endlessly while gathering stacking items, exploring many different biomes, fighting trainers, bosses, and more! +This is a mod for PokéRogue, for use with the offline version. +It's used to help with our routing project. -# Contributing -## 🛠️ Development -If you have the motivation and experience with Typescript/Javascript (or are willing to learn) please feel free to fork the repository and make pull requests with contributions. If you don't know what to work on but want to help, reference the below **To-Do** section or the **#feature-vote** channel in the discord. - -### 💻 Environment Setup -#### Prerequisites -- node: 20.13.1 -- npm: [how to install](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) - -#### Running Locally -1. Clone the repo and in the root directory run `npm install` - - *if you run into any errors, reach out in the **#dev-corner** channel in discord* -2. Run `npm run start:dev` to locally run the project in `localhost:8000` - -#### Linting -We're using ESLint as our common linter and formatter. It will run automatically during the pre-commit hook but if you would like to manually run it, use the `npm run eslint` script. - -### ❔ FAQ - -**How do I test a new _______?** -- In the `src/overrides.ts` file there are overrides for most values you'll need to change for testing - - -## 🪧 To Do -Check out [Github Issues](https://github.com/pagefaultgames/pokerogue/issues) to see how can you help us! - -# 📝 Credits -> If this project contains assets you have produced and you do not see your name here, **please** reach out. - -### 🎵 BGM - - Pokémon Mystery Dungeon: Explorers of Sky - - Arata Iiyoshi - - Hideki Sakamoto - - Keisuke Ito - - Ken-ichi Saito - - Yoshihiro Maeda - - Pokémon Black/White - - Go Ichinose - - Hitomi Sato - - Shota Kageyama - - Pokémon Mystery Dungeon: Rescue Team DX - - Keisuke Ito - - Arata Iiyoshi - - Atsuhiro Ishizuna - - Pokémon HeartGold/SoulSilver - - Pokémon Black/White 2 - - Pokémon X/Y - - Pokémon Omega Ruby/Alpha Sapphire - - Pokémon Sun/Moon - - Pokémon Ultra Sun/Ultra Moon - - Pokémon Sword/Shield - - Pokémon Scarlet/Violet - - Firel (Custom Laboratory, Metropolis, Seabed, and Space biome music) - - Lmz (Custom Jungle biome music) - -### 🎵 Sound Effects - - Pokémon Emerald - - Pokémon Black/White - -### 🎨 Backgrounds - - Squip (Paid Commissions) - - Contributions by Someonealive-QN - -### 🎨 UI - - GAMEFREAK - - LJ Birdman - -### 🎨 Pagefault Games Intro - - Spectremint - -### 🎨 Game Logo - - Gonstar (Paid Commission) - -### 🎨 Trainer Sprites - - GAMEFREAK (Pokémon Black/White 2, Pokémon Diamond/Pearl) - - kyledove - - Brumirage - - pkmn_realidea (Paid Commissions) - -### 🎨 Trainer Portraits - - pkmn_realidea (Paid Commissions) - -### 🎨 Pokemon Sprites and Animation - - GAMEFREAK (Pokémon Black/White 2) - - Smogon Sprite Project (Various Artists) - - Skyflyer - - Nolo33 - - Ebaru - - EricLostie - - KingOfThe-X-Roads - - kiriaura - - Caruban - - Sopita_Yorita - - Azrita - - AshnixsLaw - - Hellfire0raptor - - RetroNC - - Franark122k - - OldSoulja - - PKMarioG - - ItsYugen - - lucasomi - - Pkm Sinfonia - - Poki Papillon - - Fleimer_ - - bizcoeindoloro - - mangalos810 - - Involuntary-Twitch - - selstar - -### 🎨 Move Animations - - Pokémon Reborn +Feature progress: +- [ ] Logs all the steps you take while playing +- [x] Logs the wild Pokémon you encounter and their stats +- [x] Logs the category of trainers you encounter +- [x] In-Game GUI to export logs +- [ ] Show damage values for attacks (present, but incomplete) +- [x] Show catch rates +- [x] Show attributes of wild Pokémon (max IVs, nature, abilities) From a8552b5c5e287405f618c6aebdcddd29cae1dad2 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Mon, 8 Jul 2024 13:21:10 -0400 Subject: [PATCH 15/94] Add instructions Detailed steps on how to install offline, download the mod, import a daily run, and export logs --- README.md | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 24dc3490d1f..2920daba98f 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,10 @@ This is a mod for PokéRogue, for use with the offline version. It's used to help with our routing project. -Feature progress: +This program is for Windows - it does not have installers for Mac or Linux right now. +(You can still do run validation without this mod, of course) + +## Feature progress - [ ] Logs all the steps you take while playing - [x] Logs the wild Pokémon you encounter and their stats - [x] Logs the category of trainers you encounter @@ -11,3 +14,33 @@ Feature progress: - [ ] Show damage values for attacks (present, but incomplete) - [x] Show catch rates - [x] Show attributes of wild Pokémon (max IVs, nature, abilities) + +# Instructions +### Installation +- Make sure you have the app (download v1.3.1 [here](https://github.com/Admiral-Billy/Pokerogue-App/releases) - v2.0.0 and up will not work!) +- Look on the `record-path` channel for the modified installer that allows downloading different versions +- Replace `resources/update-game.js` in the offline version's files with the modified installer +- Run the installer, typing `y` and pressing enter to confirm you want to install offline mode +- Select Pokerogue-Projects/Pathing-Tool (option 2 by default) and press enter again +- Wait (it will take a few minutes to install no matter which version you selected) +- Choose whether you want the offline version of the `pkmn.help` type calculator, then press enter one final time when prompted to close the terminal +### Setting up a run +- Open PokéRogue online (you can use [PokeRogue](https://pokerogue.net/) or the online mode of the app) +- Start a new Daily Run +- Save & Quit +- Open the menu +- Go to Manage Data, select Export Session, and select the slot you saved the Daily Run to - you will download a `.prsv` file +- Open the app in offline mode by running `Pokerogue Offine.bat` +- Open the menu, go to Manage Data, and instead *import* a session +- Select the `.prsv` you downloaded, and select a slot to save it to. When the game reloads, you'll see that the newly imported run has appeared as an option on the title screen. +- Open Manage Logs on the title screen. +- If you played a run already, be sure to export your files first. +- Select `Clear All (3)` to delete any previous run data. +### Playing the Daily Run +- All Daily Run saves will appear as buttons on the title screen. Selecting them will load the file as if you had opened the Load Game menu. (Selecting them in that menu still works, of course.) +- Play! The game will automatically log your run as you go. + - **Warning**: The logs do not discriminate between saves, and if you open another save file, it will **overwrite** any data in Steps (`instructions.txt`) or Encounters (`encounters.csv`). +- When you're done, go to the title screen and open Manage Logs. + - Select a log to save it to your device (the number in parenthases indicates the file size) + - Select "Export All" to save all logs to your device at once (the number in parenthases indicates how many logs will be exported) + - Select "Reset All" to delete all existing run data From 458245f5425941770e47678bc31a323f37bd2ccc Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Mon, 8 Jul 2024 13:37:22 -0700 Subject: [PATCH 16/94] [Unittest] Prevent Arena Trap/etc from breaking Costar test (#2923) --- src/test/abilities/costar.test.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/test/abilities/costar.test.ts b/src/test/abilities/costar.test.ts index 1b7eb3f7b90..ecd70088aa2 100644 --- a/src/test/abilities/costar.test.ts +++ b/src/test/abilities/costar.test.ts @@ -29,7 +29,7 @@ describe("Abilities - COSTAR", () => { game = new GameManager(phaserGame); vi.spyOn(Overrides, "DOUBLE_BATTLE_OVERRIDE", "get").mockReturnValue(true); vi.spyOn(Overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.COSTAR); - vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH, Moves.NASTY_PLOT, Moves.CURSE]); + vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH, Moves.NASTY_PLOT]); vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH, Moves.SPLASH, Moves.SPLASH, Moves.SPLASH]); }); @@ -37,15 +37,17 @@ describe("Abilities - COSTAR", () => { test( "ability copies positive stat changes", async () => { + vi.spyOn(Overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.BALL_FETCH); + await game.startBattle([Species.MAGIKARP, Species.MAGIKARP, Species.FLAMIGO]); let [leftPokemon, rightPokemon] = game.scene.getPlayerField(); - expect(leftPokemon).not.toBe(undefined); - expect(rightPokemon).not.toBe(undefined); + expect(leftPokemon).toBeDefined(); + expect(rightPokemon).toBeDefined(); game.doAttack(getMovePosition(game.scene, 0, Moves.NASTY_PLOT)); await game.phaseInterceptor.to(CommandPhase); - game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); + game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); await game.toNextTurn(); expect(leftPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(+2); @@ -71,8 +73,8 @@ describe("Abilities - COSTAR", () => { await game.startBattle([Species.MAGIKARP, Species.MAGIKARP, Species.FLAMIGO]); let [leftPokemon, rightPokemon] = game.scene.getPlayerField(); - expect(leftPokemon).not.toBe(undefined); - expect(rightPokemon).not.toBe(undefined); + expect(leftPokemon).toBeDefined(); + expect(rightPokemon).toBeDefined(); expect(leftPokemon.summonData.battleStats[BattleStat.ATK]).toBe(-2); expect(leftPokemon.summonData.battleStats[BattleStat.ATK]).toBe(-2); From 7474eac80866f2fb06b26121bc5f8e493ee40f1b Mon Sep 17 00:00:00 2001 From: schmidtc1 <62030095+schmidtc1@users.noreply.github.com> Date: Mon, 8 Jul 2024 16:53:16 -0400 Subject: [PATCH 17/94] [Bug] Removes firstHitOnly from AddArenaTagAttr to allow multi hit set up with Ceaseless Edge (#2727) * Removes firstHitOnly from AddArenaTagAttr to allow multi hit set up with ceaseless edge * Creates initial test file with basic cases for ceaseless edge * Fixes ceaseless edge unit test and expands on case 3 * Adds truthy/falsy expectations prior to use --- src/data/move.ts | 2 +- src/test/moves/ceaseless_edge.test.ts | 135 ++++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 src/test/moves/ceaseless_edge.test.ts diff --git a/src/data/move.ts b/src/data/move.ts index d87d7f918bd..321fbc6d097 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -4218,7 +4218,7 @@ export class AddArenaTagAttr extends MoveEffectAttr { public selfSideTarget: boolean; constructor(tagType: ArenaTagType, turnCount?: integer, failOnOverlap: boolean = false, selfSideTarget: boolean = false) { - super(true, MoveEffectTrigger.POST_APPLY, true); + super(true, MoveEffectTrigger.POST_APPLY); this.tagType = tagType; this.turnCount = turnCount; diff --git a/src/test/moves/ceaseless_edge.test.ts b/src/test/moves/ceaseless_edge.test.ts new file mode 100644 index 00000000000..de47027ccd5 --- /dev/null +++ b/src/test/moves/ceaseless_edge.test.ts @@ -0,0 +1,135 @@ +import {afterEach, beforeAll, beforeEach, describe, expect, test, vi} from "vitest"; +import Phaser from "phaser"; +import GameManager from "#app/test/utils/gameManager"; +import * as overrides from "#app/overrides"; +import { + MoveEffectPhase, + TurnEndPhase +} from "#app/phases"; +import {getMovePosition} from "#app/test/utils/gameManagerUtils"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import { ArenaTagType } from "#app/enums/arena-tag-type.js"; +import { allMoves } from "#app/data/move.js"; +import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag.js"; + +const TIMEOUT = 20 * 1000; + +describe("Moves - Ceaseless Edge", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true); + vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA); + vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(100); + vi.spyOn(overrides, "OPP_LEVEL_OVERRIDE", "get").mockReturnValue(100); + vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([ Moves.CEASELESS_EDGE, Moves.SPLASH, Moves.ROAR ]); + vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH,Moves.SPLASH,Moves.SPLASH,Moves.SPLASH]); + vi.spyOn(allMoves[Moves.CEASELESS_EDGE], "accuracy", "get").mockReturnValue(100); + + }); + + test( + "move should hit and apply spikes", + async () => { + await game.startBattle([ Species.ILLUMISE ]); + + const leadPokemon = game.scene.getPlayerPokemon(); + expect(leadPokemon).toBeDefined(); + + const enemyPokemon = game.scene.getEnemyPokemon(); + expect(enemyPokemon).toBeDefined(); + + const enemyStartingHp = enemyPokemon.hp; + + game.doAttack(getMovePosition(game.scene, 0, Moves.CEASELESS_EDGE)); + + await game.phaseInterceptor.to(MoveEffectPhase, false); + // Spikes should not have any layers before move effect is applied + const tagBefore = game.scene.arena.getTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.ENEMY) as ArenaTrapTag; + expect(tagBefore instanceof ArenaTrapTag).toBeFalsy(); + + await game.phaseInterceptor.to(TurnEndPhase); + const tagAfter = game.scene.arena.getTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.ENEMY) as ArenaTrapTag; + expect(tagAfter instanceof ArenaTrapTag).toBeTruthy(); + expect(tagAfter.layers).toBe(1); + expect(enemyPokemon.hp).toBeLessThan(enemyStartingHp); + }, TIMEOUT + ); + + test( + "move should hit twice with multi lens and apply two layers of spikes", + async () => { + vi.spyOn(overrides, "STARTING_HELD_ITEMS_OVERRIDE", "get").mockReturnValue([{name: "MULTI_LENS"}]); + await game.startBattle([ Species.ILLUMISE ]); + + const leadPokemon = game.scene.getPlayerPokemon(); + expect(leadPokemon).toBeDefined(); + + const enemyPokemon = game.scene.getEnemyPokemon(); + expect(enemyPokemon).toBeDefined(); + + const enemyStartingHp = enemyPokemon.hp; + + game.doAttack(getMovePosition(game.scene, 0, Moves.CEASELESS_EDGE)); + + await game.phaseInterceptor.to(MoveEffectPhase, false); + // Spikes should not have any layers before move effect is applied + const tagBefore = game.scene.arena.getTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.ENEMY) as ArenaTrapTag; + expect(tagBefore instanceof ArenaTrapTag).toBeFalsy(); + + await game.phaseInterceptor.to(TurnEndPhase); + const tagAfter = game.scene.arena.getTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.ENEMY) as ArenaTrapTag; + expect(tagAfter instanceof ArenaTrapTag).toBeTruthy(); + expect(tagAfter.layers).toBe(2); + expect(enemyPokemon.hp).toBeLessThan(enemyStartingHp); + }, TIMEOUT + ); + + test( + "trainer - move should hit twice, apply two layers of spikes, force switch opponent - opponent takes damage", + async () => { + vi.spyOn(overrides, "STARTING_HELD_ITEMS_OVERRIDE", "get").mockReturnValue([{name: "MULTI_LENS"}]); + vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(5); + vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(0); + + await game.startBattle([ Species.SNORLAX, Species.MUNCHLAX ]); + + const leadPokemon = game.scene.getPlayerPokemon(); + expect(leadPokemon).toBeDefined(); + + const enemyPokemon = game.scene.getEnemyPokemon(); + expect(enemyPokemon).toBeDefined(); + + game.doAttack(getMovePosition(game.scene, 0, Moves.CEASELESS_EDGE)); + await game.phaseInterceptor.to(MoveEffectPhase, false); + // Spikes should not have any layers before move effect is applied + const tagBefore = game.scene.arena.getTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.ENEMY) as ArenaTrapTag; + expect(tagBefore instanceof ArenaTrapTag).toBeFalsy(); + + await game.phaseInterceptor.to(TurnEndPhase, false); + const tagAfter = game.scene.arena.getTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.ENEMY) as ArenaTrapTag; + expect(tagAfter instanceof ArenaTrapTag).toBeTruthy(); + expect(tagAfter.layers).toBe(2); + + const hpBeforeSpikes = game.scene.currentBattle.enemyParty[1].hp; + // Check HP of pokemon that WILL BE switched in (index 1) + game.forceOpponentToSwitch(); + game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); + await game.phaseInterceptor.to(TurnEndPhase, false); + expect(game.scene.currentBattle.enemyParty[0].hp).toBeLessThan(hpBeforeSpikes); + }, TIMEOUT + ); +}); From 433d682d8520e8864642db969dfbc8f1bbe0b0ba Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Mon, 8 Jul 2024 18:38:36 -0400 Subject: [PATCH 18/94] Remove hiding load button if no saves were detected --- src/phases.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/phases.ts b/src/phases.ts index 54c76d8d59f..e869efef147 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -537,7 +537,7 @@ export class TitlePhase extends Phase { } }) // If the player has no save data (as determined above), hide the "Load Game" button - if (hasFile) + if (hasFile || true) options.push({ label: i18next.t("menu:loadGame"), handler: () => { From c78f3ab5c29960f4cd659dd2b93b3b605b6b4cc9 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Mon, 8 Jul 2024 18:40:51 -0400 Subject: [PATCH 19/94] Update damage calculation --- src/logger.ts | 27 +- src/ui/battle-info.ts | 4 +- src/ui/fight-ui-handler.ts | 686 ++++++++++++++++++++++++++++++++++++- 3 files changed, 701 insertions(+), 16 deletions(-) diff --git a/src/logger.ts b/src/logger.ts index a4adee5fb4c..c690eed9e89 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -13,9 +13,9 @@ import { TrainerType } from "#enums/trainer-type"; * Format: [filename, localStorage key, name, header, item sprite, header suffix] */ 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", "", ""], + ["instructions.txt", "path_log", "Steps", "Run Steps", "blunder_policy", ""], + ["encounters.csv", "enc_log", "Encounters", "Encounter Data", "ub", ",,,,,,,,,,,,,,,,"], + ["log.txt", "debug_log", "Debug", "Debug Log", "wide_lens", ""], ] export var logKeys: string[] = [ "i", // Instructions/steps @@ -36,11 +36,22 @@ export function getSize(str: string) { } export function generateOption(i: integer): OptionSelectItem { - return { - label: `Export ${logs[i][2]} (${getSize(localStorage.getItem(logs[i][1]))})`, - handler: () => { - downloadLogByID(i) - return false; + if (logs[i][4] != "") { + return { + label: `Export ${logs[i][2]} (${getSize(localStorage.getItem(logs[i][1]))})`, + handler: () => { + downloadLogByID(i) + return false; + }, + item: logs[i][4] + } + } else { + return { + label: `Export ${logs[i][2]} (${getSize(localStorage.getItem(logs[i][1]))})`, + handler: () => { + downloadLogByID(i) + return false; + } } } } diff --git a/src/ui/battle-info.ts b/src/ui/battle-info.ts index 3b889228e27..01446a58931 100644 --- a/src/ui/battle-info.ts +++ b/src/ui/battle-info.ts @@ -11,6 +11,7 @@ import { BattleStat } from "#app/data/battle-stat"; import BattleFlyout from "./battle-flyout"; import { WindowVariant, addWindow } from "./ui-theme"; import i18next from "i18next"; +import { calcDamage } from "./fight-ui-handler"; const battleStatOrder = [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.ACC, BattleStat.EVA, BattleStat.SPD ]; @@ -742,7 +743,8 @@ export default class BattleInfo extends Phaser.GameObjects.Container { if (visible) { this.effectivenessContainer?.setVisible(false); } else { - this.updateEffectiveness(this.currentEffectiveness); + //this.updateEffectiveness(this.currentEffectiveness); + this.effectivenessContainer?.setVisible(true); } } diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index c1018fbc74d..d7f2ae81f77 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -5,16 +5,25 @@ import { Command } from "./command-ui-handler"; import { Mode } from "./ui"; import UiHandler from "./ui-handler"; import * as Utils from "../utils"; -import { CommandPhase } from "../phases"; -import * as MoveData from "#app/data/move.js"; +import { CommandPhase, MoveEffectPhase } from "../phases"; +import Move, * as MoveData from "../data/move"; import i18next from "i18next"; import {Button} from "#enums/buttons"; -import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon.js"; +import Pokemon, { DamageResult, EnemyPokemon, HitResult, PlayerPokemon, PokemonMove } from "#app/field/pokemon.js"; import Battle from "#app/battle.js"; import { Stat } from "#app/data/pokemon-stat.js"; import { Abilities } from "#app/enums/abilities.js"; import { WeatherType } from "#app/data/weather.js"; import { Moves } from "#app/enums/moves.js"; +import { AddSecondStrikeAbAttr, AllyMoveCategoryPowerBoostAbAttr, applyAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, ConditionalCritAbAttr, DamageBoostAbAttr, FieldMoveTypePowerBoostAbAttr, FieldPriorityMoveImmunityAbAttr, MoveImmunityAbAttr, MoveTypeChangeAttr, MultCritAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, StabBoostAbAttr, TypeImmunityAbAttr, UserFieldMoveTypePowerBoostAbAttr, VariableMovePowerAbAttr } from "#app/data/ability.js"; +import { ArenaTagType } from "#app/enums/arena-tag-type.js"; +import { ArenaTagSide, WeakenMoveScreenTag, WeakenMoveTypeTag } from "#app/data/arena-tag.js"; +import { BattlerTagLapseType, HelpingHandTag, TypeBoostTag } from "#app/data/battler-tags.js"; +import { TerrainType } from "#app/data/terrain.js"; +import { AttackTypeBoosterModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, PokemonMultiHitModifier, TempBattleStatBoosterModifier } from "#app/modifier/modifier.js"; +import { BattlerTagType } from "#app/enums/battler-tag-type.js"; +import { TempBattleStat } from "#app/data/temp-battle-stat.js"; +import { StatusEffect } from "#app/data/status-effect.js"; export default class FightUiHandler extends UiHandler { private movesContainer: Phaser.GameObjects.Container; @@ -158,7 +167,280 @@ export default class FightUiHandler extends UiHandler { return !this.fieldIndex ? this.cursor : this.cursor2; } + simulateAttack(scene: BattleScene, user: Pokemon, target: Pokemon, move: Move) { + let result: HitResult; + const damage1 = new Utils.NumberHolder(0); + const damage2 = new Utils.NumberHolder(0); + const defendingSidePlayField = target.isPlayer() ? this.scene.getPlayerField() : this.scene.getEnemyField(); + + const variableCategory = new Utils.IntegerHolder(move.category); + MoveData.applyMoveAttrs(MoveData.VariableMoveCategoryAttr, user, target, move, variableCategory); + const moveCategory = variableCategory.value as MoveData.MoveCategory; + + const typeChangeMovePowerMultiplier = new Utils.NumberHolder(1); + MoveData.applyMoveAttrs(MoveData.VariableMoveTypeAttr, user, target, move); + applyPreAttackAbAttrs(MoveTypeChangeAttr, user, target, move, typeChangeMovePowerMultiplier); + const types = target.getTypes(true, true); + + const cancelled = new Utils.BooleanHolder(false); + const typeless = move.hasAttr(MoveData.TypelessAttr); + const typeMultiplier = new Utils.NumberHolder(!typeless && (moveCategory !== MoveData.MoveCategory.STATUS || move.getAttrs(MoveData.StatusMoveTypeImmunityAttr).find(attr => types.includes(attr.immuneType))) + ? target.getAttackTypeEffectiveness(move.type, user, false, false) + : 1); + MoveData.applyMoveAttrs(MoveData.VariableMoveTypeMultiplierAttr, user, target, move, typeMultiplier); + if (typeless) { + typeMultiplier.value = 1; + } + if (types.find(t => move.isTypeImmune(user, target, t))) { + typeMultiplier.value = 0; + } + + // Apply arena tags for conditional protection + if (!move.checkFlag(MoveData.MoveFlags.IGNORE_PROTECT, user, target) && !move.isAllyTarget()) { + const defendingSide = target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; + this.scene.arena.applyTagsForSide(ArenaTagType.QUICK_GUARD, defendingSide, cancelled, this, move.priority); + this.scene.arena.applyTagsForSide(ArenaTagType.WIDE_GUARD, defendingSide, cancelled, this, move.moveTarget); + this.scene.arena.applyTagsForSide(ArenaTagType.MAT_BLOCK, defendingSide, cancelled, this, move.category); + this.scene.arena.applyTagsForSide(ArenaTagType.CRAFTY_SHIELD, defendingSide, cancelled, this, move.category, move.moveTarget); + } + + switch (moveCategory) { + case MoveData.MoveCategory.PHYSICAL: + case MoveData.MoveCategory.SPECIAL: + const isPhysical = moveCategory === MoveData.MoveCategory.PHYSICAL; + const power = new Utils.NumberHolder(move.power); + const sourceTeraType = user.getTeraType(); + if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === move.type && power.value < 60 && move.priority <= 0 && !move.hasAttr(MoveData.MultiHitAttr) && !this.scene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === user.id)) { + power.value = 60; + } + applyPreAttackAbAttrs(VariableMovePowerAbAttr, user, target, move, power); + + if (user.getAlly()?.hasAbilityWithAttr(AllyMoveCategoryPowerBoostAbAttr)) { + applyPreAttackAbAttrs(AllyMoveCategoryPowerBoostAbAttr, user, target, move, power); + } + + const fieldAuras = new Set( + this.scene.getField(true) + .map((p) => p.getAbilityAttrs(FieldMoveTypePowerBoostAbAttr) as FieldMoveTypePowerBoostAbAttr[]) + .flat(), + ); + for (const aura of fieldAuras) { + // The only relevant values are `move` and the `power` holder + aura.applyPreAttack(null, null, null, move, [power]); + } + + const alliedField: Pokemon[] = user instanceof PlayerPokemon ? this.scene.getPlayerField() : this.scene.getEnemyField(); + alliedField.forEach(p => applyPreAttackAbAttrs(UserFieldMoveTypePowerBoostAbAttr, p, user, move, power)); + + power.value *= typeChangeMovePowerMultiplier.value; + + if (!typeless) { + applyPreDefendAbAttrs(TypeImmunityAbAttr, user, target, move, cancelled, typeMultiplier); + MoveData.applyMoveAttrs(MoveData.NeutralDamageAgainstFlyingTypeMultiplierAttr, user, target, move, typeMultiplier); + } + if (!cancelled.value) { + applyPreDefendAbAttrs(MoveImmunityAbAttr, user, target, move, cancelled, typeMultiplier); + defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, user, move, cancelled, typeMultiplier)); + } + + if (cancelled.value) { + //user.stopMultiHit(target); + result = HitResult.NO_EFFECT; + } else { + const typeBoost = user.findTag(t => t instanceof TypeBoostTag && t.boostedType === move.type) as TypeBoostTag; + if (typeBoost) { + power.value *= typeBoost.boostValue; + if (typeBoost.oneUse) { + //user.removeTag(typeBoost.tagType); + } + } + const arenaAttackTypeMultiplier = new Utils.NumberHolder(this.scene.arena.getAttackTypeMultiplier(move.type, user.isGrounded())); + MoveData.applyMoveAttrs(MoveData.IgnoreWeatherTypeDebuffAttr, user, target, move, arenaAttackTypeMultiplier); + if (this.scene.arena.getTerrainType() === TerrainType.GRASSY && target.isGrounded() && move.type === Type.GROUND && move.moveTarget === MoveData.MoveTarget.ALL_NEAR_OTHERS) { + power.value /= 2; + } + + MoveData.applyMoveAttrs(MoveData.VariablePowerAttr, user, target, move, power); + + this.scene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, new Utils.IntegerHolder(0), power); + if (!typeless) { + this.scene.arena.applyTags(WeakenMoveTypeTag, move.type, power); + this.scene.applyModifiers(AttackTypeBoosterModifier, user.isPlayer(), user, move.type, power); + } + if (user.getTag(HelpingHandTag)) { + power.value *= 1.5; + } + let isCritical: boolean = true; + const critOnly = new Utils.BooleanHolder(false); + const critAlways = user.getTag(BattlerTagType.ALWAYS_CRIT); + MoveData.applyMoveAttrs(MoveData.CritOnlyAttr, user, target, move, critOnly); + applyAbAttrs(ConditionalCritAbAttr, user, null, critOnly, target, move); + if (isCritical) { + const blockCrit = new Utils.BooleanHolder(false); + applyAbAttrs(BlockCritAbAttr, target, null, blockCrit); + if (blockCrit.value) { + isCritical = false; + } + } + const sourceAtk = new Utils.IntegerHolder(user.getBattleStat(isPhysical ? Stat.ATK : Stat.SPATK, target, null, false)); + const targetDef = new Utils.IntegerHolder(target.getBattleStat(isPhysical ? Stat.DEF : Stat.SPDEF, user, move, false)); + const sourceAtkCrit = new Utils.IntegerHolder(user.getBattleStat(isPhysical ? Stat.ATK : Stat.SPATK, target, null, isCritical)); + const targetDefCrit = new Utils.IntegerHolder(target.getBattleStat(isPhysical ? Stat.DEF : Stat.SPDEF, user, move, isCritical)); + const criticalMultiplier = new Utils.NumberHolder(isCritical ? 1.5 : 1); + applyAbAttrs(MultCritAbAttr, user, null, criticalMultiplier); + const screenMultiplier = new Utils.NumberHolder(1); + if (!isCritical) { + this.scene.arena.applyTagsForSide(WeakenMoveScreenTag, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, move.category, this.scene.currentBattle.double, screenMultiplier); + } + const isTypeImmune = (typeMultiplier.value * arenaAttackTypeMultiplier.value) === 0; + const sourceTypes = user.getTypes(); + const matchesSourceType = sourceTypes[0] === move.type || (sourceTypes.length > 1 && sourceTypes[1] === move.type); + const stabMultiplier = new Utils.NumberHolder(1); + if (sourceTeraType === Type.UNKNOWN && matchesSourceType) { + stabMultiplier.value += 0.5; + } else if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === move.type) { + stabMultiplier.value += 0.5; + } + + applyAbAttrs(StabBoostAbAttr, user, null, stabMultiplier); + + if (sourceTeraType !== Type.UNKNOWN && matchesSourceType) { + stabMultiplier.value = Math.min(stabMultiplier.value + 0.5, 2.25); + } + + MoveData.applyMoveAttrs(MoveData.VariableAtkAttr, user, target, move, sourceAtk); + MoveData.applyMoveAttrs(MoveData.VariableDefAttr, user, target, move, targetDef); + MoveData.applyMoveAttrs(MoveData.VariableAtkAttr, user, target, move, sourceAtkCrit); + MoveData.applyMoveAttrs(MoveData.VariableDefAttr, user, target, move, targetDefCrit); + + const effectPhase = this.scene.getCurrentPhase(); + let numTargets = 1; + if (effectPhase instanceof MoveEffectPhase) { + numTargets = effectPhase.getTargets().length; + } + const twoStrikeMultiplier = new Utils.NumberHolder(1); + applyPreAttackAbAttrs(AddSecondStrikeAbAttr, user, target, move, numTargets, new Utils.IntegerHolder(0), twoStrikeMultiplier); + + if (!isTypeImmune) { + damage1.value = Math.ceil(((((2 * user.level / 5 + 2) * power.value * sourceAtk.value / targetDef.value) / 50) + 2) * stabMultiplier.value * typeMultiplier.value * arenaAttackTypeMultiplier.value * screenMultiplier.value * twoStrikeMultiplier.value * 0.85); // low roll + damage2.value = Math.ceil(((((2 * user.level / 5 + 2) * power.value * sourceAtkCrit.value / targetDefCrit.value) / 50) + 2) * stabMultiplier.value * typeMultiplier.value * arenaAttackTypeMultiplier.value * screenMultiplier.value * twoStrikeMultiplier.value * criticalMultiplier.value); // high roll crit + if (isPhysical && user.status && user.status.effect === StatusEffect.BURN) { + if (!move.hasAttr(MoveData.BypassBurnDamageReductionAttr)) { + const burnDamageReductionCancelled = new Utils.BooleanHolder(false); + applyAbAttrs(BypassBurnDamageReductionAbAttr, user, burnDamageReductionCancelled); + if (!burnDamageReductionCancelled.value) { + damage1.value = Math.floor(damage1.value / 2); + damage2.value = Math.floor(damage2.value / 2); + } + } + } + + applyPreAttackAbAttrs(DamageBoostAbAttr, user, target, move, damage1); + applyPreAttackAbAttrs(DamageBoostAbAttr, user, target, move, damage2); + + /** + * For each {@link HitsTagAttr} the move has, doubles the damage of the move if: + * The target has a {@link BattlerTagType} that this move interacts with + * AND + * The move doubles damage when used against that tag + */ + move.getAttrs(MoveData.HitsTagAttr).filter(hta => hta.doubleDamage).forEach(hta => { + if (target.getTag(hta.tagType)) { + damage1.value *= 2; + damage2.value *= 2; + } + }); + } + + if (this.scene.arena.terrain?.terrainType === TerrainType.MISTY && target.isGrounded() && move.type === Type.DRAGON) { + damage1.value = Math.floor(damage1.value / 2); + damage2.value = Math.floor(damage2.value / 2); + } + + const fixedDamage = new Utils.IntegerHolder(0); + MoveData.applyMoveAttrs(MoveData.FixedDamageAttr, user, target, move, fixedDamage); + if (!isTypeImmune && fixedDamage.value) { + damage1.value = fixedDamage.value; + damage2.value = fixedDamage.value; + isCritical = false; + result = HitResult.EFFECTIVE; + } + + if (!result) { + if (!typeMultiplier.value) { + result = move.id === Moves.SHEER_COLD ? HitResult.IMMUNE : HitResult.NO_EFFECT; + } else { + const oneHitKo = new Utils.BooleanHolder(false); + MoveData.applyMoveAttrs(MoveData.OneHitKOAttr, user, target, move, oneHitKo); + if (oneHitKo.value) { + result = HitResult.ONE_HIT_KO; + isCritical = false; + damage1.value = target.hp; + damage2.value = target.hp; + } else if (typeMultiplier.value >= 2) { + result = HitResult.SUPER_EFFECTIVE; + } else if (typeMultiplier.value >= 1) { + result = HitResult.EFFECTIVE; + } else { + result = HitResult.NOT_VERY_EFFECTIVE; + } + } + } + + if (!fixedDamage.value) { + if (!user.isPlayer()) { + this.scene.applyModifiers(EnemyDamageBoosterModifier, false, damage1); + this.scene.applyModifiers(EnemyDamageBoosterModifier, false, damage2); + } + if (!target.isPlayer()) { + this.scene.applyModifiers(EnemyDamageReducerModifier, false, damage1); + this.scene.applyModifiers(EnemyDamageReducerModifier, false, damage2); + } + } + + MoveData.applyMoveAttrs(MoveData.ModifiedDamageAttr, user, target, move, damage1); + MoveData.applyMoveAttrs(MoveData.ModifiedDamageAttr, user, target, move, damage2); + applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, user, target, move, cancelled, damage1); + applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, user, target, move, cancelled, damage2); + + console.log("damage (min)", damage1.value, move.name, power.value, sourceAtk, targetDef); + console.log("damage (max)", damage2.value, move.name, power.value, sourceAtkCrit, targetDefCrit); + + // In case of fatal damage, this tag would have gotten cleared before we could lapse it. + const destinyTag = target.getTag(BattlerTagType.DESTINY_BOND); + + const oneHitKo = result === HitResult.ONE_HIT_KO; + if (damage1.value) { + if (target.getHpRatio() === 1) { + applyPreDefendAbAttrs(PreDefendFullHpEndureAbAttr, target, user, move, cancelled, damage1); + } + } + if (damage2.value) { + if (target.getHpRatio() === 1) { + applyPreDefendAbAttrs(PreDefendFullHpEndureAbAttr, target, user, move, cancelled, damage2); + } + } + } + break; + case MoveData.MoveCategory.STATUS: + if (!typeless) { + applyPreDefendAbAttrs(TypeImmunityAbAttr, target, user, move, cancelled, typeMultiplier); + } + if (!cancelled.value) { + applyPreDefendAbAttrs(MoveImmunityAbAttr, target, user, move, cancelled, typeMultiplier); + defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, user, move, cancelled, typeMultiplier)); + } + if (!typeMultiplier.value) { + return -1 + } + result = cancelled.value || !typeMultiplier.value ? HitResult.NO_EFFECT : HitResult.STATUS; + break; + } + return [damage1.value, damage2.value] + } + calcDamage(scene: BattleScene, user: PlayerPokemon, target: Pokemon, move: PokemonMove) { + /* var power = move.getMove().power var myAtk = 0 var theirDef = 0 @@ -231,17 +513,32 @@ export default class FightUiHandler extends UiHandler { } var typeBonus = target.getAttackMoveEffectiveness(user, move) var modifiers = stabBonus * weatherBonus - var dmgLow = (((2*user.level/5 + 2) * power * myAtk / theirDef)/50 + 2) * 0.85 * modifiers - var dmgHigh = (((2*user.level/5 + 2) * power * myAtkC / theirDefC)/50 + 2) * 1.5 * modifiers + */ + var dmgHigh = 0 + var dmgLow = 0 + // dmgLow = (((2*user.level/5 + 2) * power * myAtk / theirDef)/50 + 2) * 0.85 * modifiers + // dmgHigh = (((2*user.level/5 + 2) * power * myAtkC / theirDefC)/50 + 2) * 1.5 * modifiers + var out = this.simulateAttack(scene, user, target, move.getMove()) + dmgLow = out[0] + dmgHigh = out[1] + /* if (user.hasAbility(Abilities.PARENTAL_BOND)) { // Second hit deals 0.25x damage dmgLow *= 1.25 dmgHigh *= 1.25 } - return (Math.round(dmgLow) == Math.round(dmgHigh) ? Math.round(dmgLow).toString() : Math.round(dmgLow) + "-" + Math.round(dmgHigh)) + ((Math.round(dmgLow) > target.hp) ? " KO" : "") + */ + var koText = "" + if (Math.floor(dmgLow) >= target.hp) { + koText = " (KO)" + } else if (Math.ceil(dmgHigh) >= target.hp) { + var percentChance = (target.hp - dmgLow + 1) / (dmgHigh - dmgLow + 1) + koText = " (" + Math.round(percentChance * 100) + "% KO)" + } + return (Math.round(dmgLow) == Math.round(dmgHigh) ? Math.round(dmgLow).toString() : Math.round(dmgLow) + "-" + Math.round(dmgHigh)) + koText dmgLow = Math.round((dmgLow)/target.getBattleStat(Stat.HP)*100) dmgHigh = Math.round((dmgHigh)/target.getBattleStat(Stat.HP)*100) - return (dmgLow == dmgHigh ? dmgLow + "%" : dmgLow + "%-" + dmgHigh + "%") + ((Math.round(dmgLow) > target.hp) ? " KO" : "") + return (dmgLow == dmgHigh ? dmgLow + "%" : dmgLow + "%-" + dmgHigh + "%") + koText return "???" } @@ -405,3 +702,378 @@ export default class FightUiHandler extends UiHandler { this.cursorObj = null; } } + +export function simulateAttack(scene: BattleScene, user: Pokemon, target: Pokemon, move: Move) { + let result: HitResult; + const damage1 = new Utils.NumberHolder(0); + const damage2 = new Utils.NumberHolder(0); + const defendingSidePlayField = target.isPlayer() ? scene.getPlayerField() : scene.getEnemyField(); + + const variableCategory = new Utils.IntegerHolder(move.category); + MoveData.applyMoveAttrs(MoveData.VariableMoveCategoryAttr, user, target, move, variableCategory); + const moveCategory = variableCategory.value as MoveData.MoveCategory; + + const typeChangeMovePowerMultiplier = new Utils.NumberHolder(1); + MoveData.applyMoveAttrs(MoveData.VariableMoveTypeAttr, user, target, move); + applyPreAttackAbAttrs(MoveTypeChangeAttr, user, target, move, typeChangeMovePowerMultiplier); + const types = target.getTypes(true, true); + + const cancelled = new Utils.BooleanHolder(false); + const typeless = move.hasAttr(MoveData.TypelessAttr); + const typeMultiplier = new Utils.NumberHolder(!typeless && (moveCategory !== MoveData.MoveCategory.STATUS || move.getAttrs(MoveData.StatusMoveTypeImmunityAttr).find(attr => types.includes(attr.immuneType))) + ? target.getAttackTypeEffectiveness(move.type, user, false, false) + : 1); + MoveData.applyMoveAttrs(MoveData.VariableMoveTypeMultiplierAttr, user, target, move, typeMultiplier); + if (typeless) { + typeMultiplier.value = 1; + } + if (types.find(t => move.isTypeImmune(user, target, t))) { + typeMultiplier.value = 0; + } + + // Apply arena tags for conditional protection + if (!move.checkFlag(MoveData.MoveFlags.IGNORE_PROTECT, user, target) && !move.isAllyTarget()) { + const defendingSide = target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; + scene.arena.applyTagsForSide(ArenaTagType.QUICK_GUARD, defendingSide, cancelled, this, move.priority); + scene.arena.applyTagsForSide(ArenaTagType.WIDE_GUARD, defendingSide, cancelled, this, move.moveTarget); + scene.arena.applyTagsForSide(ArenaTagType.MAT_BLOCK, defendingSide, cancelled, this, move.category); + scene.arena.applyTagsForSide(ArenaTagType.CRAFTY_SHIELD, defendingSide, cancelled, this, move.category, move.moveTarget); + } + + switch (moveCategory) { + case MoveData.MoveCategory.PHYSICAL: + case MoveData.MoveCategory.SPECIAL: + const isPhysical = moveCategory === MoveData.MoveCategory.PHYSICAL; + const power = new Utils.NumberHolder(move.power); + const sourceTeraType = user.getTeraType(); + if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === move.type && power.value < 60 && move.priority <= 0 && !move.hasAttr(MoveData.MultiHitAttr) && !scene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === user.id)) { + power.value = 60; + } + applyPreAttackAbAttrs(VariableMovePowerAbAttr, user, target, move, power); + + if (user.getAlly()?.hasAbilityWithAttr(AllyMoveCategoryPowerBoostAbAttr)) { + applyPreAttackAbAttrs(AllyMoveCategoryPowerBoostAbAttr, user, target, move, power); + } + + const fieldAuras = new Set( + scene.getField(true) + .map((p) => p.getAbilityAttrs(FieldMoveTypePowerBoostAbAttr) as FieldMoveTypePowerBoostAbAttr[]) + .flat(), + ); + for (const aura of fieldAuras) { + // The only relevant values are `move` and the `power` holder + aura.applyPreAttack(null, null, null, move, [power]); + } + + const alliedField: Pokemon[] = user instanceof PlayerPokemon ? scene.getPlayerField() : scene.getEnemyField(); + alliedField.forEach(p => applyPreAttackAbAttrs(UserFieldMoveTypePowerBoostAbAttr, p, user, move, power)); + + power.value *= typeChangeMovePowerMultiplier.value; + + if (!typeless) { + applyPreDefendAbAttrs(TypeImmunityAbAttr, user, target, move, cancelled, typeMultiplier); + MoveData.applyMoveAttrs(MoveData.NeutralDamageAgainstFlyingTypeMultiplierAttr, user, target, move, typeMultiplier); + } + if (!cancelled.value) { + applyPreDefendAbAttrs(MoveImmunityAbAttr, user, target, move, cancelled, typeMultiplier); + defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, user, move, cancelled, typeMultiplier)); + } + + if (cancelled.value) { + //user.stopMultiHit(target); + result = HitResult.NO_EFFECT; + } else { + const typeBoost = user.findTag(t => t instanceof TypeBoostTag && t.boostedType === move.type) as TypeBoostTag; + if (typeBoost) { + power.value *= typeBoost.boostValue; + if (typeBoost.oneUse) { + //user.removeTag(typeBoost.tagType); + } + } + const arenaAttackTypeMultiplier = new Utils.NumberHolder(scene.arena.getAttackTypeMultiplier(move.type, user.isGrounded())); + MoveData.applyMoveAttrs(MoveData.IgnoreWeatherTypeDebuffAttr, user, target, move, arenaAttackTypeMultiplier); + if (scene.arena.getTerrainType() === TerrainType.GRASSY && target.isGrounded() && move.type === Type.GROUND && move.moveTarget === MoveData.MoveTarget.ALL_NEAR_OTHERS) { + power.value /= 2; + } + + MoveData.applyMoveAttrs(MoveData.VariablePowerAttr, user, target, move, power); + + scene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, new Utils.IntegerHolder(0), power); + if (!typeless) { + scene.arena.applyTags(WeakenMoveTypeTag, move.type, power); + scene.applyModifiers(AttackTypeBoosterModifier, user.isPlayer(), user, move.type, power); + } + if (user.getTag(HelpingHandTag)) { + power.value *= 1.5; + } + let isCritical: boolean = true; + const critOnly = new Utils.BooleanHolder(false); + const critAlways = user.getTag(BattlerTagType.ALWAYS_CRIT); + MoveData.applyMoveAttrs(MoveData.CritOnlyAttr, user, target, move, critOnly); + applyAbAttrs(ConditionalCritAbAttr, user, null, critOnly, target, move); + if (isCritical) { + const blockCrit = new Utils.BooleanHolder(false); + applyAbAttrs(BlockCritAbAttr, target, null, blockCrit); + if (blockCrit.value) { + isCritical = false; + } + } + const sourceAtk = new Utils.IntegerHolder(user.getBattleStat(isPhysical ? Stat.ATK : Stat.SPATK, target, null, false)); + const targetDef = new Utils.IntegerHolder(target.getBattleStat(isPhysical ? Stat.DEF : Stat.SPDEF, user, move, false)); + const sourceAtkCrit = new Utils.IntegerHolder(user.getBattleStat(isPhysical ? Stat.ATK : Stat.SPATK, target, null, isCritical)); + const targetDefCrit = new Utils.IntegerHolder(target.getBattleStat(isPhysical ? Stat.DEF : Stat.SPDEF, user, move, isCritical)); + const criticalMultiplier = new Utils.NumberHolder(isCritical ? 1.5 : 1); + applyAbAttrs(MultCritAbAttr, user, null, criticalMultiplier); + const screenMultiplier = new Utils.NumberHolder(1); + if (!isCritical) { + scene.arena.applyTagsForSide(WeakenMoveScreenTag, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, move.category, scene.currentBattle.double, screenMultiplier); + } + const isTypeImmune = (typeMultiplier.value * arenaAttackTypeMultiplier.value) === 0; + const sourceTypes = user.getTypes(); + const matchesSourceType = sourceTypes[0] === move.type || (sourceTypes.length > 1 && sourceTypes[1] === move.type); + const stabMultiplier = new Utils.NumberHolder(1); + if (sourceTeraType === Type.UNKNOWN && matchesSourceType) { + stabMultiplier.value += 0.5; + } else if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === move.type) { + stabMultiplier.value += 0.5; + } + + applyAbAttrs(StabBoostAbAttr, user, null, stabMultiplier); + + if (sourceTeraType !== Type.UNKNOWN && matchesSourceType) { + stabMultiplier.value = Math.min(stabMultiplier.value + 0.5, 2.25); + } + + MoveData.applyMoveAttrs(MoveData.VariableAtkAttr, user, target, move, sourceAtk); + MoveData.applyMoveAttrs(MoveData.VariableDefAttr, user, target, move, targetDef); + MoveData.applyMoveAttrs(MoveData.VariableAtkAttr, user, target, move, sourceAtkCrit); + MoveData.applyMoveAttrs(MoveData.VariableDefAttr, user, target, move, targetDefCrit); + + const effectPhase = scene.getCurrentPhase(); + let numTargets = 1; + if (effectPhase instanceof MoveEffectPhase) { + numTargets = effectPhase.getTargets().length; + } + const twoStrikeMultiplier = new Utils.NumberHolder(1); + applyPreAttackAbAttrs(AddSecondStrikeAbAttr, user, target, move, numTargets, new Utils.IntegerHolder(0), twoStrikeMultiplier); + + if (!isTypeImmune) { + damage1.value = Math.ceil(((((2 * user.level / 5 + 2) * power.value * sourceAtk.value / targetDef.value) / 50) + 2) * stabMultiplier.value * typeMultiplier.value * arenaAttackTypeMultiplier.value * screenMultiplier.value * twoStrikeMultiplier.value * 0.85); // low roll + damage2.value = Math.ceil(((((2 * user.level / 5 + 2) * power.value * sourceAtkCrit.value / targetDefCrit.value) / 50) + 2) * stabMultiplier.value * typeMultiplier.value * arenaAttackTypeMultiplier.value * screenMultiplier.value * twoStrikeMultiplier.value * criticalMultiplier.value); // high roll crit + if (isPhysical && user.status && user.status.effect === StatusEffect.BURN) { + if (!move.hasAttr(MoveData.BypassBurnDamageReductionAttr)) { + const burnDamageReductionCancelled = new Utils.BooleanHolder(false); + applyAbAttrs(BypassBurnDamageReductionAbAttr, user, burnDamageReductionCancelled); + if (!burnDamageReductionCancelled.value) { + damage1.value = Math.floor(damage1.value / 2); + damage2.value = Math.floor(damage2.value / 2); + } + } + } + + applyPreAttackAbAttrs(DamageBoostAbAttr, user, target, move, damage1); + applyPreAttackAbAttrs(DamageBoostAbAttr, user, target, move, damage2); + + /** + * For each {@link HitsTagAttr} the move has, doubles the damage of the move if: + * The target has a {@link BattlerTagType} that this move interacts with + * AND + * The move doubles damage when used against that tag + */ + move.getAttrs(MoveData.HitsTagAttr).filter(hta => hta.doubleDamage).forEach(hta => { + if (target.getTag(hta.tagType)) { + damage1.value *= 2; + damage2.value *= 2; + } + }); + } + + if (scene.arena.terrain?.terrainType === TerrainType.MISTY && target.isGrounded() && move.type === Type.DRAGON) { + damage1.value = Math.floor(damage1.value / 2); + damage2.value = Math.floor(damage2.value / 2); + } + + const fixedDamage = new Utils.IntegerHolder(0); + MoveData.applyMoveAttrs(MoveData.FixedDamageAttr, user, target, move, fixedDamage); + if (!isTypeImmune && fixedDamage.value) { + damage1.value = fixedDamage.value; + damage2.value = fixedDamage.value; + isCritical = false; + result = HitResult.EFFECTIVE; + } + + if (!result) { + if (!typeMultiplier.value) { + result = move.id === Moves.SHEER_COLD ? HitResult.IMMUNE : HitResult.NO_EFFECT; + } else { + const oneHitKo = new Utils.BooleanHolder(false); + MoveData.applyMoveAttrs(MoveData.OneHitKOAttr, user, target, move, oneHitKo); + if (oneHitKo.value) { + result = HitResult.ONE_HIT_KO; + isCritical = false; + damage1.value = target.hp; + damage2.value = target.hp; + } else if (typeMultiplier.value >= 2) { + result = HitResult.SUPER_EFFECTIVE; + } else if (typeMultiplier.value >= 1) { + result = HitResult.EFFECTIVE; + } else { + result = HitResult.NOT_VERY_EFFECTIVE; + } + } + } + + if (!fixedDamage.value) { + if (!user.isPlayer()) { + scene.applyModifiers(EnemyDamageBoosterModifier, false, damage1); + scene.applyModifiers(EnemyDamageBoosterModifier, false, damage2); + } + if (!target.isPlayer()) { + scene.applyModifiers(EnemyDamageReducerModifier, false, damage1); + scene.applyModifiers(EnemyDamageReducerModifier, false, damage2); + } + } + + MoveData.applyMoveAttrs(MoveData.ModifiedDamageAttr, user, target, move, damage1); + MoveData.applyMoveAttrs(MoveData.ModifiedDamageAttr, user, target, move, damage2); + applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, user, target, move, cancelled, damage1); + applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, user, target, move, cancelled, damage2); + + console.log("damage (min)", damage1.value, move.name, power.value, sourceAtk, targetDef); + console.log("damage (max)", damage2.value, move.name, power.value, sourceAtkCrit, targetDefCrit); + + // In case of fatal damage, this tag would have gotten cleared before we could lapse it. + const destinyTag = target.getTag(BattlerTagType.DESTINY_BOND); + + const oneHitKo = result === HitResult.ONE_HIT_KO; + if (damage1.value) { + if (target.getHpRatio() === 1) { + applyPreDefendAbAttrs(PreDefendFullHpEndureAbAttr, target, user, move, cancelled, damage1); + } + } + if (damage2.value) { + if (target.getHpRatio() === 1) { + applyPreDefendAbAttrs(PreDefendFullHpEndureAbAttr, target, user, move, cancelled, damage2); + } + } + } + break; + case MoveData.MoveCategory.STATUS: + if (!typeless) { + applyPreDefendAbAttrs(TypeImmunityAbAttr, target, user, move, cancelled, typeMultiplier); + } + if (!cancelled.value) { + applyPreDefendAbAttrs(MoveImmunityAbAttr, target, user, move, cancelled, typeMultiplier); + defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, user, move, cancelled, typeMultiplier)); + } + if (!typeMultiplier.value) { + return -1 + } + result = cancelled.value || !typeMultiplier.value ? HitResult.NO_EFFECT : HitResult.STATUS; + break; + } + return [damage1.value, damage2.value] +} + +export function calcDamage(scene: BattleScene, user: PlayerPokemon, target: Pokemon, move: PokemonMove) { + /* + var power = move.getMove().power + var myAtk = 0 + var theirDef = 0 + var myAtkC = 0 + var theirDefC = 0 + switch (move.getMove().category) { + case MoveData.MoveCategory.PHYSICAL: + myAtk = user.getBattleStat(Stat.ATK, target, move.getMove()) + myAtkC = user.getBattleStat(Stat.ATK, target, move.getMove(), true) + theirDef = target.getBattleStat(Stat.DEF, user, move.getMove()) + theirDefC = target.getBattleStat(Stat.DEF, user, move.getMove(), true) + break; + case MoveData.MoveCategory.SPECIAL: + myAtk = user.getBattleStat(Stat.SPATK, target, move.getMove()) + myAtkC = user.getBattleStat(Stat.SPATK, target, move.getMove(), true) + theirDef = target.getBattleStat(Stat.SPDEF, user, move.getMove()) + theirDefC = target.getBattleStat(Stat.SPDEF, user, move.getMove(), true) + break; + case MoveData.MoveCategory.STATUS: + return "---" + } + var stabBonus = 1 + var types = user.getTypes() + // Apply STAB bonus + for (var i = 0; i < types.length; i++) { + if (types[i] == move.getMove().type) { + stabBonus = 1.5 + } + } + // Apply Tera Type bonus + if (stabBonus == 1.5) { + // STAB + if (move.getMove().type == user.getTeraType()) { + stabBonus = 2 + } + } else if (move.getMove().type == user.getTeraType()) { + stabBonus = 1.5 + } + // Apply adaptability + if (stabBonus == 2) { + // Tera-STAB + if (move.getMove().type == user.getTeraType()) { + stabBonus = 2.25 + } + } else if (stabBonus == 1.5) { + // STAB or Tera + if (move.getMove().type == user.getTeraType()) { + stabBonus = 2 + } + } else if (move.getMove().type == user.getTeraType()) { + // Adaptability + stabBonus = 1.5 + } + var weatherBonus = 1 + if (scene.arena.weather.weatherType == WeatherType.RAIN || scene.arena.weather.weatherType == WeatherType.HEAVY_RAIN) { + if (move.getMove().type == Type.WATER) { + weatherBonus = 1.5 + } + if (move.getMove().type == Type.FIRE) { + weatherBonus = scene.arena.weather.weatherType == WeatherType.HEAVY_RAIN ? 0 : 0.5 + } + } + if (scene.arena.weather.weatherType == WeatherType.SUNNY || scene.arena.weather.weatherType == WeatherType.HARSH_SUN) { + if (move.getMove().type == Type.FIRE) { + weatherBonus = 1.5 + } + if (move.getMove().type == Type.WATER) { + weatherBonus = scene.arena.weather.weatherType == WeatherType.HARSH_SUN ? 0 : (move.moveId == Moves.HYDRO_STEAM ? 1.5 : 0.5) + } + } + var typeBonus = target.getAttackMoveEffectiveness(user, move) + var modifiers = stabBonus * weatherBonus + */ + var dmgHigh = 0 + var dmgLow = 0 + // dmgLow = (((2*user.level/5 + 2) * power * myAtk / theirDef)/50 + 2) * 0.85 * modifiers + // dmgHigh = (((2*user.level/5 + 2) * power * myAtkC / theirDefC)/50 + 2) * 1.5 * modifiers + var out = this.simulateAttack(scene, user, target, move.getMove()) + dmgLow = out[0] + dmgHigh = out[1] + /* + if (user.hasAbility(Abilities.PARENTAL_BOND)) { + // Second hit deals 0.25x damage + dmgLow *= 1.25 + dmgHigh *= 1.25 + } + */ + var koText = "" + if (Math.floor(dmgLow) >= target.hp) { + koText = " (KO)" + } else if (Math.ceil(dmgHigh) >= target.hp) { + var percentChance = (target.hp - dmgLow + 1) / (dmgHigh - dmgLow + 1) + koText = " (" + Math.round(percentChance * 100) + "% KO)" + } + return (Math.round(dmgLow) == Math.round(dmgHigh) ? Math.round(dmgLow).toString() : Math.round(dmgLow) + "-" + Math.round(dmgHigh)) + koText + dmgLow = Math.round((dmgLow)/target.getBattleStat(Stat.HP)*100) + dmgHigh = Math.round((dmgHigh)/target.getBattleStat(Stat.HP)*100) + return (dmgLow == dmgHigh ? dmgLow + "%" : dmgLow + "%-" + dmgHigh + "%") + koText + return "???" +} \ No newline at end of file From 48b079438b45a8c4a6bb8f0f44b866952aaa7601 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Mon, 8 Jul 2024 20:55:51 -0400 Subject: [PATCH 20/94] JSON Framework Starter template for 'DRPD' run data format --- src/logger.ts | 162 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 154 insertions(+), 8 deletions(-) diff --git a/src/logger.ts b/src/logger.ts index c690eed9e89..2bedfa3047e 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -6,6 +6,10 @@ import { Nature, getNatureName } from "./data/nature"; import BattleScene from "./battle-scene"; import { OptionSelectItem } from "./ui/abstact-option-select-ui-handler"; import { TrainerType } from "#enums/trainer-type"; +import { Modifier, PokemonHeldItemModifier } from "./modifier/modifier"; +import Battle from "./battle"; +import { getBiomeName } from "./data/biomes"; +import { trainerConfigs } from "./data/trainer-config"; /** * All logs. @@ -23,6 +27,132 @@ export var logKeys: string[] = [ "d", // Debug ]; +export const DRPD_Version = "0.1.2" +export interface DRPD { + version: string, + title: string, + authors: string[], + date: string, + waves: Wave[], + starters: PokeData[] +} +export interface Wave { + id: integer, // Renamed to 'id' in file + reload: boolean, + type: string, + double: boolean, + actions: string[], + shop: boolean | string, // suggest using only 'string' and returning undefined if nothing is taken + biome: string, + trainer?: TrainerData, + pokeLeft?: PokeData, + pokeRight?: PokeData +} +export interface PokeData { + id: integer, + name: string, + ability: string, + isHiddenAbility: boolean, + passiveAbility: string, + nature: string, + gender: string, + rarity: string, + capture: boolean, + level: integer, + items: ItemData[], + ivs: IVData +} +export interface IVData { + hp: integer, + atk: integer, + def: integer, + spatk: integer, + spdef: integer, + speed: integer +} +export interface TrainerData { + id: integer, + name: string, + type: string, +} +export interface ItemData { + id: string, + name: string, + quantity: integer, +} + +export function newDocument(name: string = "Untitled Run " + (new Date().getUTCMonth() + 1 < 10 ? "0" : "") + (new Date().getUTCMonth() + 1) + "/" + (new Date().getUTCDate() < 10 ? "0" : "") + new Date().getUTCDate() + "/" + new Date().getUTCFullYear(), authorName: string | string[] = "Write your name here"): DRPD { + return { + version: DRPD_Version, + title: name, + authors: (Array.isArray(authorName) ? authorName : [authorName]), + date: new Date().getUTCFullYear() + "-" + (new Date().getUTCMonth() + 1 < 10 ? "0" : "") + (new Date().getUTCMonth() + 1) + "-" + (new Date().getUTCDate() < 10 ? "0" : "") + new Date().getUTCDate(), + waves: new Array(50), + starters: new Array(3) + } +} +export function exportPokemon(pokemon: Pokemon, encounterRarity?: string): PokeData { + return { + id: pokemon.species.speciesId, + name: pokemon.species.getName(), + ability: pokemon.getAbility().name, + isHiddenAbility: pokemon.hasAbility(pokemon.species.abilityHidden), + passiveAbility: pokemon.getPassiveAbility().name, + nature: getNatureName(pokemon.nature), + gender: pokemon.gender == 0 ? "Male" : (pokemon.gender == 1 ? "Female" : "Genderless"), + rarity: encounterRarity, + capture: false, + level: pokemon.level, + items: pokemon.getHeldItems().map(item => exportItem(item)), + ivs: exportIVs(pokemon.ivs) + } +} +export function exportItem(item: PokemonHeldItemModifier): ItemData { + return { + id: item.type.id, + name: item.type.name, + quantity: item.getStackCount() + } +} +export function exportIVs(ivs: integer[]): IVData { + return { + hp: ivs[0], + atk: ivs[1], + def: ivs[2], + spatk: ivs[3], + spdef: ivs[4], + speed: ivs[5] + } +} +export function exportWave(scene: BattleScene): Wave { + var ret: Wave = { + id: scene.currentBattle.waveIndex, + reload: false, + type: scene.getEnemyField()[0].hasTrainer() ? "trainer" : scene.getEnemyField()[0].isBoss() ? "boss" : "wild", + double: scene.currentBattle.double, + actions: [], + shop: "", + biome: getBiomeName(scene.arena.biomeType) + } + switch (ret.type) { + case "wild": + case "boss": + ret.pokeLeft = exportPokemon(scene.getEnemyField()[0]) + if (ret.double) { + ret.pokeRight = exportPokemon(scene.getEnemyField()[1]) + } + break; + case "trainer": + ret.trainer = { + id: scene.currentBattle.trainer.config.trainerType, + name: scene.currentBattle.trainer.name, + type: scene.currentBattle.trainer.config.title + } + break; + } + return ret; +} + export const byteSize = str => new Blob([str]).size const filesizes = ["b", "kb", "mb", "gb", "tb"] export function getSize(str: string) { @@ -116,7 +246,7 @@ export function logTeam(scene: BattleScene, floor: integer = undefined) { logPokemon(scene, floor, i, team[i]) } if (team.length == 1) { - setRow("e", ",,,,,,,,,,,,,,,,", floor, 1) + //setRow("e", ",,,,,,,,,,,,,,,,", floor, 1) } } } @@ -180,17 +310,25 @@ export function setRow(keyword: string, newLine: string, floor: integer, slot: i idx++ } idx-- + for (var i = 0; i < data.length; i++) { + if (data[i] == ",,,,,,,,,,,,,,,,") { + data.splice(i, 1) + if (idx > i) idx-- + i-- + } + } console.log((data[idx].split(",")[0] as any) * 1, floor, (data[idx].split(",")[1] as any) * 1, slot) if (idx < data.length && (data[idx].split(",")[0] as any) * 1 == floor && (data[idx].split(",")[1] as any) * 1 == slot) { data[idx] = newLine console.log("Overwrote data at " + idx) - for (var i = 0; i < Math.max(0, idx - 2) && i < 2; i++) { + var i: number; + for (i = 0; i < Math.max(0, idx - 2) && i < 2; i++) { console.log(i + " " + data[i]) } - if (Math.min(0, idx - 2) > 3) { + if (i == 3 && i != Math.min(0, idx - 2)) { console.log("...") } - for (var i = Math.max(0, idx - 2); i <= idx + 2 && i < data.length; i++) { + for (i = Math.max(0, idx - 2); i <= idx + 2 && i < data.length; i++) { console.log(i + (i == idx ? " >> " : " ") + data[i]) } localStorage.setItem(logs[logKeys.indexOf(keyword)][1], data.join("\n")); @@ -198,18 +336,26 @@ export function setRow(keyword: string, newLine: string, floor: integer, slot: i } idx++ } + for (var i = 0; i < data.length; i++) { + if (data[i] == ",,,,,,,,,,,,,,,,") { + data.splice(i, 1) + if (idx > i) idx-- + i-- + } + } console.log("Inserted data at " + idx) - for (var i = 0; i < Math.max(0, idx - 2) && i < 2; i++) { + var i: number; + for (i = 0; i < Math.max(0, idx - 2) && i < 2; i++) { console.log(i + " " + data[i]) } - if (Math.min(0, idx - 2) > 3) { + if (i == 3 && i != Math.min(0, idx - 2)) { console.log("...") } - for (var i = Math.max(0, idx - 2); i < idx; i++) { + for (i = Math.max(0, idx - 2); i < idx; i++) { console.log(i + " " + data[i]) } console.log(i + " >> " + newLine) - for (var i = idx; i <= idx + 2 && i < data.length; i++) { + for (i = idx; i <= idx + 2 && i < data.length; i++) { console.log(i + " " + data[i]) } localStorage.setItem(logs[logKeys.indexOf(keyword)][1], data.slice(0, idx).join("\n") + "\n" + newLine + (data.slice(idx).length == 0 ? "" : "\n") + data.slice(idx).join("\n")); From 0b5b481de63bbca03e1c6c45237d1d2decbd87f6 Mon Sep 17 00:00:00 2001 From: schmidtc1 <62030095+schmidtc1@users.noreply.github.com> Date: Mon, 8 Jul 2024 22:49:52 -0400 Subject: [PATCH 21/94] [Test] Prevent flying types/levitate from breaking Ceaseless Edge test (#2931) * Modifies ceaseless edge test to account for flying types/levitate ability * Removes .js from imports --- src/test/moves/ceaseless_edge.test.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/test/moves/ceaseless_edge.test.ts b/src/test/moves/ceaseless_edge.test.ts index de47027ccd5..6443e34d8d2 100644 --- a/src/test/moves/ceaseless_edge.test.ts +++ b/src/test/moves/ceaseless_edge.test.ts @@ -9,9 +9,10 @@ import { import {getMovePosition} from "#app/test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; -import { ArenaTagType } from "#app/enums/arena-tag-type.js"; -import { allMoves } from "#app/data/move.js"; -import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag.js"; +import { ArenaTagType } from "#app/enums/arena-tag-type"; +import { allMoves } from "#app/data/move"; +import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag"; +import { Abilities } from "#app/enums/abilities"; const TIMEOUT = 20 * 1000; @@ -33,6 +34,8 @@ describe("Moves - Ceaseless Edge", () => { game = new GameManager(phaserGame); vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true); vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA); + vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.RUN_AWAY); + vi.spyOn(overrides, "OPP_PASSIVE_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.RUN_AWAY); vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(100); vi.spyOn(overrides, "OPP_LEVEL_OVERRIDE", "get").mockReturnValue(100); vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([ Moves.CEASELESS_EDGE, Moves.SPLASH, Moves.ROAR ]); @@ -103,9 +106,8 @@ describe("Moves - Ceaseless Edge", () => { async () => { vi.spyOn(overrides, "STARTING_HELD_ITEMS_OVERRIDE", "get").mockReturnValue([{name: "MULTI_LENS"}]); vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(5); - vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(0); - await game.startBattle([ Species.SNORLAX, Species.MUNCHLAX ]); + await game.startBattle([ Species.ILLUMISE ]); const leadPokemon = game.scene.getPlayerPokemon(); expect(leadPokemon).toBeDefined(); From 0c18e95fa4e47808b7014cfc546049efe3810050 Mon Sep 17 00:00:00 2001 From: AJ Fontaine <36677462+Fontbane@users.noreply.github.com> Date: Tue, 9 Jul 2024 09:58:16 -0400 Subject: [PATCH 22/94] [Bug] Fix Meditite back and female variant sprites (#2929) --- .../images/pokemon/variant/_masterlist.json | 10 ++++++ public/images/pokemon/variant/back/307.json | 20 +++++------ .../pokemon/variant/back/female/307.json | 22 ++++++++++++ public/images/pokemon/variant/female/307.json | 34 +++++++++++++++++++ 4 files changed, 76 insertions(+), 10 deletions(-) create mode 100644 public/images/pokemon/variant/back/female/307.json create mode 100644 public/images/pokemon/variant/female/307.json diff --git a/public/images/pokemon/variant/_masterlist.json b/public/images/pokemon/variant/_masterlist.json index f3e690395e1..e2feeaa9e55 100644 --- a/public/images/pokemon/variant/_masterlist.json +++ b/public/images/pokemon/variant/_masterlist.json @@ -3300,6 +3300,11 @@ 1, 1 ], + "307": [ + 0, + 1, + 1 + ], "308": [ 0, 1, @@ -6678,6 +6683,11 @@ 1, 1 ], + "307": [ + 0, + 1, + 1 + ], "308": [ 0, 1, diff --git a/public/images/pokemon/variant/back/307.json b/public/images/pokemon/variant/back/307.json index 3c2ef92171c..3bdadaa8e16 100644 --- a/public/images/pokemon/variant/back/307.json +++ b/public/images/pokemon/variant/back/307.json @@ -1,15 +1,5 @@ { "1": { - "7b6b6b": "314b76", - "b5adad": "677d98", - "e6dede": "c2cfdb", - "000000": "000000", - "3a84b5": "51876e", - "3a4a5a": "113926", - "6bcee6": "7edfb7", - "5aa5ce": "66c3a3" - }, - "2": { "7b6b6b": "7a5f5f", "b5adad": "9f8383", "e6dede": "deccc3", @@ -18,5 +8,15 @@ "3a4a5a": "5a2859", "6bcee6": "f4a8c8", "5aa5ce": "ce7bb0" + }, + "2": { + "7b6b6b": "314b76", + "b5adad": "677d98", + "e6dede": "c2cfdb", + "000000": "000000", + "3a84b5": "51876e", + "3a4a5a": "113926", + "6bcee6": "7edfb7", + "5aa5ce": "66c3a3" } } \ No newline at end of file diff --git a/public/images/pokemon/variant/back/female/307.json b/public/images/pokemon/variant/back/female/307.json new file mode 100644 index 00000000000..3bdadaa8e16 --- /dev/null +++ b/public/images/pokemon/variant/back/female/307.json @@ -0,0 +1,22 @@ +{ + "1": { + "7b6b6b": "7a5f5f", + "b5adad": "9f8383", + "e6dede": "deccc3", + "000000": "000000", + "3a84b5": "7e4377", + "3a4a5a": "5a2859", + "6bcee6": "f4a8c8", + "5aa5ce": "ce7bb0" + }, + "2": { + "7b6b6b": "314b76", + "b5adad": "677d98", + "e6dede": "c2cfdb", + "000000": "000000", + "3a84b5": "51876e", + "3a4a5a": "113926", + "6bcee6": "7edfb7", + "5aa5ce": "66c3a3" + } +} \ No newline at end of file diff --git a/public/images/pokemon/variant/female/307.json b/public/images/pokemon/variant/female/307.json new file mode 100644 index 00000000000..d3e6a2437f1 --- /dev/null +++ b/public/images/pokemon/variant/female/307.json @@ -0,0 +1,34 @@ +{ + "1": { + "7b6b6b": "7a5f5f", + "000000": "000000", + "e6dede": "deccc3", + "b5adad": "9f8383", + "4a4242": "4a4242", + "ffffff": "ffffff", + "3a4a5a": "5a2859", + "b5d6ff": "f4a8c8", + "6bcee6": "ce7bb0", + "d65252": "d65287", + "84424a": "84424a", + "3a84b5": "7e4377", + "5aa5ce": "b95ba1", + "d65273": "d65273" + }, + "2": { + "7b6b6b": "314b76", + "000000": "000000", + "e6dede": "c2cfdb", + "b5adad": "6f89aa", + "4a4242": "1e2f52", + "ffffff": "ffffff", + "3a4a5a": "113926", + "b5d6ff": "7edfb7", + "6bcee6": "66c3a3", + "d65252": "c067c7", + "84424a": "84424a", + "3a84b5": "375a47", + "5aa5ce": "579578", + "d65273": "d65273" + } +} \ No newline at end of file From 18dcef5b4a35bd41c5227f0d3d8cdd85b5e66f52 Mon Sep 17 00:00:00 2001 From: flx-sta <50131232+flx-sta@users.noreply.github.com> Date: Tue, 9 Jul 2024 11:05:45 -0700 Subject: [PATCH 23/94] [Bug] fix starter select alpha in challenge modes (#2924) * fix starter select alpha in challenge modes * [NIT] resolve review comment https://github.com/pagefaultgames/pokerogue/pull/2924#discussion_r1670587051 * chore --- src/ui/starter-select-ui-handler.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 25d1060f23e..0caf9ef6fa6 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -2322,10 +2322,13 @@ export default class StarterSelectUiHandler extends MessageUiHandler { const isValidForChallenge = new Utils.BooleanHolder(true); + const currentPartyValue = this.starterGens.reduce((total: number, gen: number, i: number) => total += this.scene.gameData.getSpeciesStarterValue(this.genSpecies[gen][this.starterCursors[i]].speciesId), 0); + const cursorCost = this.scene.gameData.getSpeciesStarterValue(species.speciesId); + const isValidNextPartyValue = (currentPartyValue + cursorCost) <= this.getValueLimit(); Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, species, isValidForChallenge, this.scene.gameData.getSpeciesDexAttrProps(species, this.dexAttrCursor), this.starterGens.length); const starterSprite = this.starterSelectGenIconContainers[this.getGenCursorWithScroll()].getAt(this.cursor) as Phaser.GameObjects.Sprite; starterSprite.setTexture(species.getIconAtlasKey(formIndex, shiny, variant), species.getIconId(female, formIndex, shiny, variant)); - starterSprite.setAlpha(isValidForChallenge.value ? 1 : 0.375); + starterSprite.setAlpha(isValidForChallenge.value && isValidNextPartyValue ? 1 : 0.375); this.checkIconId((this.starterSelectGenIconContainers[this.getGenCursorWithScroll()].getAt(this.cursor) as Phaser.GameObjects.Sprite), species, female, formIndex, shiny, variant); this.canCycleShiny = !!(dexEntry.caughtAttr & DexAttr.NON_SHINY && dexEntry.caughtAttr & DexAttr.SHINY); this.canCycleGender = !!(dexEntry.caughtAttr & DexAttr.MALE && dexEntry.caughtAttr & DexAttr.FEMALE); From 2a7b787fd04c8442df18043ae438b74326693638 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Tue, 9 Jul 2024 15:53:48 -0400 Subject: [PATCH 24/94] Update to standard 0.1.3 --- src/logger.ts | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/logger.ts b/src/logger.ts index 2bedfa3047e..08e33fdf8b0 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -19,7 +19,6 @@ import { trainerConfigs } from "./data/trainer-config"; export const logs: string[][] = [ ["instructions.txt", "path_log", "Steps", "Run Steps", "blunder_policy", ""], ["encounters.csv", "enc_log", "Encounters", "Encounter Data", "ub", ",,,,,,,,,,,,,,,,"], - ["log.txt", "debug_log", "Debug", "Debug Log", "wide_lens", ""], ] export var logKeys: string[] = [ "i", // Instructions/steps @@ -27,7 +26,7 @@ export var logKeys: string[] = [ "d", // Debug ]; -export const DRPD_Version = "0.1.2" +export const DRPD_Version = "0.1.3" export interface DRPD { version: string, title: string, @@ -37,16 +36,15 @@ export interface DRPD { starters: PokeData[] } export interface Wave { - id: integer, // Renamed to 'id' in file + id: integer, reload: boolean, type: string, double: boolean, actions: string[], - shop: boolean | string, // suggest using only 'string' and returning undefined if nothing is taken + shop: boolean | string, biome: string, trainer?: TrainerData, - pokeLeft?: PokeData, - pokeRight?: PokeData + pokemon?: PokeData[] } export interface PokeData { id: integer, @@ -57,10 +55,11 @@ export interface PokeData { nature: string, gender: string, rarity: string, - capture: boolean, + captured: boolean, level: integer, items: ItemData[], - ivs: IVData + ivs: IVData, + source?: Pokemon } export interface IVData { hp: integer, @@ -86,7 +85,7 @@ export function newDocument(name: string = "Untitled Run " + (new Date().getUTCM version: DRPD_Version, title: name, authors: (Array.isArray(authorName) ? authorName : [authorName]), - date: new Date().getUTCFullYear() + "-" + (new Date().getUTCMonth() + 1 < 10 ? "0" : "") + (new Date().getUTCMonth() + 1) + "-" + (new Date().getUTCDate() < 10 ? "0" : "") + new Date().getUTCDate(), + date: (new Date().getUTCMonth() + 1 < 10 ? "0" : "") + (new Date().getUTCMonth() + 1) + "-" + (new Date().getUTCDate() < 10 ? "0" : "") + new Date().getUTCDate() + "-" + new Date().getUTCFullYear(), waves: new Array(50), starters: new Array(3) } @@ -101,7 +100,7 @@ export function exportPokemon(pokemon: Pokemon, encounterRarity?: string): PokeD nature: getNatureName(pokemon.nature), gender: pokemon.gender == 0 ? "Male" : (pokemon.gender == 1 ? "Female" : "Genderless"), rarity: encounterRarity, - capture: false, + captured: false, level: pokemon.level, items: pokemon.getHeldItems().map(item => exportItem(item)), ivs: exportIVs(pokemon.ivs) @@ -137,9 +136,9 @@ export function exportWave(scene: BattleScene): Wave { switch (ret.type) { case "wild": case "boss": - ret.pokeLeft = exportPokemon(scene.getEnemyField()[0]) - if (ret.double) { - ret.pokeRight = exportPokemon(scene.getEnemyField()[1]) + ret.pokemon = [] + for (var i = 0; i < scene.getEnemyParty().length; i++) { + ret.pokemon.push(exportPokemon(scene.getEnemyParty()[i])) } break; case "trainer": @@ -148,6 +147,10 @@ export function exportWave(scene: BattleScene): Wave { name: scene.currentBattle.trainer.name, type: scene.currentBattle.trainer.config.title } + ret.pokemon = [] + for (var i = 0; i < scene.getEnemyParty().length; i++) { + ret.pokemon.push(exportPokemon(scene.getEnemyParty()[i])) + } break; } return ret; From b0912a278c844e6e72b1dfdf35f4061a8bc41eb7 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Tue, 9 Jul 2024 16:12:32 -0400 Subject: [PATCH 25/94] Update DRPD Change DRPD some more & start replacing daily run button --- src/logger.ts | 8 +++++--- src/phases.ts | 48 ++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/src/logger.ts b/src/logger.ts index 08e33fdf8b0..7610450ebb2 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -29,11 +29,12 @@ export var logKeys: string[] = [ export const DRPD_Version = "0.1.3" export interface DRPD { version: string, - title: string, + title?: string, authors: string[], date: string, waves: Wave[], - starters: PokeData[] + starters?: PokeData[], + filename: string } export interface Wave { id: integer, @@ -87,7 +88,8 @@ export function newDocument(name: string = "Untitled Run " + (new Date().getUTCM authors: (Array.isArray(authorName) ? authorName : [authorName]), date: (new Date().getUTCMonth() + 1 < 10 ? "0" : "") + (new Date().getUTCMonth() + 1) + "-" + (new Date().getUTCDate() < 10 ? "0" : "") + new Date().getUTCDate() + "-" + new Date().getUTCFullYear(), waves: new Array(50), - starters: new Array(3) + starters: new Array(3), + filename: (new Date().getUTCMonth() + 1 < 10 ? "0" : "") + (new Date().getUTCMonth() + 1) + "-" + (new Date().getUTCDate() < 10 ? "0" : "") + new Date().getUTCDate() + "-" + new Date().getUTCFullYear() + "_untitled" } } export function exportPokemon(pokemon: Pokemon, encounterRarity?: string): PokeData { diff --git a/src/phases.ts b/src/phases.ts index e869efef147..f65dd406337 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -73,6 +73,7 @@ import { Challenges } from "./enums/challenges" import PokemonData from "./system/pokemon-data" import * as LoggerTools from "./logger" import { getNatureName } from "./data/nature"; +import { GameDataType } from "./enums/game-data-type"; const { t } = i18next; @@ -554,7 +555,7 @@ export class TitlePhase extends Phase { options.push({ label: i18next.t("menu:dailyRun"), handler: () => { - this.initDailyRun(); + this.setupDaily(); return true; }, keepOpen: true @@ -659,7 +660,50 @@ export class TitlePhase extends Phase { } }); } - + setupDaily(): void { + // TODO + var saves = this.getSaves() + var saveNames = new Array(5).fill("") + for (var i = 0; i < saves.length; i++) { + saveNames[saves[i][0]] = saves[i][1].description + } + const ui = this.scene.ui + const confirmSlot = (message: string, slotFilter: (i: integer) => boolean, callback: (i: integer) => void) => { + ui.revertMode(); + ui.showText(message, null, () => { + const config: OptionSelectConfig = { + options: new Array(5).fill(null).map((_, i) => i).filter(slotFilter).map(i => { + return { + label: (i+1) + " " + saveNames[i], + handler: () => { + callback(i); + ui.revertMode(); + ui.showText(null, 0); + return true; + } + }; + }).concat([{ + label: i18next.t("menuUiHandler:cancel"), + handler: () => { + ui.revertMode(); + ui.showText(null, 0); + return true; + } + }]), + xOffset: 98 + }; + ui.setOverlayMode(Mode.MENU_OPTION_SELECT, config); + }); + }; + ui.showText("This feature is incomplete.", null, () => { + this.scene.clearPhaseQueue(); + this.scene.pushPhase(new TitlePhase(this.scene)); + super.end(); + return true; + }) + return; + confirmSlot("Select a slot to replace.", () => true, slotId => this.scene.gameData.importData(GameDataType.SESSION, slotId)); + } end(): void { if (!this.loaded && !this.scene.gameMode.isDaily) { this.scene.arena.preloadBgm(); From c958755ebd508db460bdc181820b67b616280f9b Mon Sep 17 00:00:00 2001 From: mercurius-00 <80205689+mercurius-00@users.noreply.github.com> Date: Wed, 10 Jul 2024 22:13:56 +0800 Subject: [PATCH 26/94] [Localization(zh-cn)] Fix linebreak in move.ts (#2948) fix linebreak --- src/locales/zh_CN/move.ts | 214 +++++++++++++++++++------------------- 1 file changed, 107 insertions(+), 107 deletions(-) diff --git a/src/locales/zh_CN/move.ts b/src/locales/zh_CN/move.ts index a444a59a3ff..6513f3bcfc7 100644 --- a/src/locales/zh_CN/move.ts +++ b/src/locales/zh_CN/move.ts @@ -107,7 +107,7 @@ export const move: MoveTranslationEntries = { }, "rollingKick": { name: "回旋踢", - effect: "一边使身体快速旋转,\n一边踢飞对手进行攻击。有时会使对手畏缩", + effect: "一边使身体快速旋转,\n一边踢飞对手进行攻击。\n有时会使对手畏缩", }, "sandAttack": { name: "泼沙", @@ -179,7 +179,7 @@ export const move: MoveTranslationEntries = { }, "growl": { name: "叫声", - effect: "让对手听可爱的叫声,\n引开注意力使其疏忽,从而降低对手的攻击", + effect: "让对手听可爱的叫声,\n引开注意力使其疏忽,\n从而降低对手的攻击", }, "roar": { name: "吼叫", @@ -399,7 +399,7 @@ export const move: MoveTranslationEntries = { }, "teleport": { name: "瞬间移动", - effect: "当有后备宝可梦时使用,\n就可以进行替换。野生的宝可梦使用则会逃走", + effect: "当有后备宝可梦时使用,\n就可以进行替换。\n野生的宝可梦使用则会逃走", }, "nightShade": { name: "黑夜魔影", @@ -611,7 +611,7 @@ export const move: MoveTranslationEntries = { }, "explosion": { name: "大爆炸", - effect: "引发大爆炸,攻击自己周围所有的宝可梦。\n使用后自己会陷入昏厥", + effect: "引发大爆炸,\n攻击自己周围所有的宝可梦。\n使用后自己会陷入昏厥", }, "furySwipes": { name: "乱抓", @@ -643,7 +643,7 @@ export const move: MoveTranslationEntries = { }, "triAttack": { name: "三重攻击", - effect: "用3种光线进行攻击。\n有时会让对手陷入麻痹、灼伤或冰冻的状态", + effect: "用3种光线进行攻击。\n有时会让对手陷入麻痹、\n灼伤或冰冻的状态", }, "superFang": { name: "愤怒门牙", @@ -659,7 +659,7 @@ export const move: MoveTranslationEntries = { }, "struggle": { name: "挣扎", - effect: "当自己的PP耗尽时,\n努力挣扎攻击对手。自己也会受到少许伤害", + effect: "当自己的PP耗尽时,\n努力挣扎攻击对手。\n自己也会受到少许伤害", }, "sketch": { name: "写生", @@ -775,7 +775,7 @@ export const move: MoveTranslationEntries = { }, "destinyBond": { name: "同命", - effect: "使出招式后,当受到对手攻击陷入昏厥时,\n对手也会一同昏厥。\n连续使出则会失败", + effect: "使出招式后,当受到对手攻击\n陷入昏厥时,对手也会一同昏厥。\n连续使出则会失败", }, "perishSong": { name: "终焉之歌", @@ -911,7 +911,7 @@ export const move: MoveTranslationEntries = { }, "pursuit": { name: "追打", - effect: "当对手替换宝可梦上场时使出此招式的话,\n能够以2倍的威力进行攻击", + effect: "当对手替换宝可梦上场时\n使出此招式的话,\n能够以2倍的威力进行攻击", }, "rapidSpin": { name: "高速旋转", @@ -959,7 +959,7 @@ export const move: MoveTranslationEntries = { }, "rainDance": { name: "求雨", - effect: "在5回合内一直降雨,\n从而提高水属性的招式威力。火属性的招式威\n力则降低", + effect: "在5回合内一直降雨,\n从而提高水属性的招式威力。\n火属性的招式威力则降低", }, "sunnyDay": { name: "大晴天", @@ -979,7 +979,7 @@ export const move: MoveTranslationEntries = { }, "extremeSpeed": { name: "神速", - effect: "以迅雷不及掩耳之势猛撞向对手进行攻击。\n必定能够先制攻击", + effect: "以迅雷不及掩耳之势猛\n撞向对手进行攻击。\n必定能够先制攻击", }, "ancientPower": { name: "原始之力", @@ -1023,7 +1023,7 @@ export const move: MoveTranslationEntries = { }, "swallow": { name: "吞下", - effect: "将积蓄的力量吞下,从而回复自己的HP。\n积蓄得越多,回复越大", + effect: "将积蓄的力量吞下,\n从而回复自己的HP。\n积蓄得越多,回复越大", }, "heatWave": { name: "热风", @@ -1031,7 +1031,7 @@ export const move: MoveTranslationEntries = { }, "hail": { name: "冰雹", - effect: "在5回合内一直降冰雹,\n除冰属性的宝可梦以外,给予全体宝可梦伤害", + effect: "在5回合内一直降冰雹,\n除冰属性的宝可梦以外,\n给予全体宝可梦伤害", }, "torment": { name: "无理取闹", @@ -1059,7 +1059,7 @@ export const move: MoveTranslationEntries = { }, "smellingSalts": { name: "清醒", - effect: "对于麻痹状态下的对手,\n威力会变成2倍。但相反对手的麻痹也会被治愈", + effect: "对于麻痹状态下的对手,\n威力会变成2倍。\n但相反对手的麻痹也会被治愈", }, "followMe": { name: "看我嘛", @@ -1067,7 +1067,7 @@ export const move: MoveTranslationEntries = { }, "naturePower": { name: "自然之力", - effect: "用自然之力进行攻击。\n根据所使用场所的不同,使出的招式也会有所变化", + effect: "用自然之力进行攻击。\n根据所使用场所的不同,\n使出的招式也会有所变化", }, "charge": { name: "充电", @@ -1111,7 +1111,7 @@ export const move: MoveTranslationEntries = { }, "recycle": { name: "回收利用", - effect: "使战斗中已经消耗掉的自己的持有物再生,\n并可以再次使用", + effect: "使战斗中已经消耗掉的\n自己的持有物再生,\n并可以再次使用", }, "revenge": { name: "报复", @@ -1199,7 +1199,7 @@ export const move: MoveTranslationEntries = { }, "mudSport": { name: "玩泥巴", - effect: "一旦使用此招式,周围就会弄得到处是泥。\n在5回合内减弱电属性的招式", + effect: "一旦使用此招式,\n周围就会弄得到处是泥。\n在5回合内减弱电属性的招式", }, "iceBall": { name: "冰球", @@ -1259,7 +1259,7 @@ export const move: MoveTranslationEntries = { }, "overheat": { name: "过热", - effect: "使出全部力量攻击对手。\n使用之后会因为反作用力,自己的特攻大幅降低", + effect: "使出全部力量攻击对手。\n使用之后会因为反作用力,\n自己的特攻大幅降低", }, "odorSleuth": { name: "气味侦测", @@ -1367,11 +1367,11 @@ export const move: MoveTranslationEntries = { }, "poisonTail": { name: "毒尾", - effect: "用尾巴拍打。有时会让对手陷入中毒状态,\n也容易击中要害", + effect: "用尾巴拍打。\n有时会让对手陷入中毒状态,\n也容易击中要害", }, "covet": { name: "渴望", - effect: "一边可爱地撒娇,一边靠近对手进行攻击,\n还能夺取对手携带的道具", + effect: "一边可爱地撒娇,\n一边靠近对手进行攻击,\n还能夺取对手携带的道具", }, "voltTackle": { name: "伏特攻击", @@ -1415,7 +1415,7 @@ export const move: MoveTranslationEntries = { }, "psychoBoost": { name: "精神突进", - effect: "使出全部力量攻击对手。\n使用之后会因为反作用力,自己的特攻大幅降低", + effect: "使出全部力量攻击对手。\n使用之后会因为反作用力,\n自己的特攻大幅降低", }, "roost": { name: "羽栖", @@ -1527,11 +1527,11 @@ export const move: MoveTranslationEntries = { }, "meFirst": { name: "抢先一步", - effect: "提高威力,抢先使出对手想要使出的招式。\n如果不先使出则会失败", + effect: "提高威力,\n抢先使出对手想要使出的招式。\n如果不先使出则会失败", }, "copycat": { name: "仿效", - effect: "模仿对手刚才使出的招式,\n并使出相同招式。如果对手还没出招则会失败", + effect: "模仿对手刚才使出的招式,\n并使出相同招式。\n如果对手还没出招则会失败", }, "powerSwap": { name: "力量互换", @@ -1715,7 +1715,7 @@ export const move: MoveTranslationEntries = { }, "mirrorShot": { name: "镜光射击", - effect: "抛光自己的身体,向对手释放出闪光之力。\n有时会降低对手的命中率", + effect: "抛光自己的身体,\n向对手释放出闪光之力。\n有时会降低对手的命中率", }, "flashCannon": { name: "加农光炮", @@ -1735,11 +1735,11 @@ export const move: MoveTranslationEntries = { }, "dracoMeteor": { name: "流星群", - effect: "从天空中向对手落下陨石。\n使用之后因为反作用力,自己的特攻会大幅降低", + effect: "从天空中向对手落下陨石。\n使用之后因为反作用力,\n自己的特攻会大幅降低", }, "discharge": { name: "放电", - effect: "用耀眼的电击攻击自己周围所有的宝可梦。\n有时会陷入麻痹状态", + effect: "用耀眼的电击攻击\n自己周围所有的宝可梦。\n有时会陷入麻痹状态", }, "lavaPlume": { name: "喷烟", @@ -1791,7 +1791,7 @@ export const move: MoveTranslationEntries = { }, "chatter": { name: "喋喋不休", - effect: "用非常烦人的,喋喋不休的音波攻击对手。\n使对手混乱", + effect: "用非常烦人的,\n喋喋不休的音波攻击对手。\n使对手混乱", }, "judgment": { name: "制裁光砾", @@ -1827,7 +1827,7 @@ export const move: MoveTranslationEntries = { }, "headSmash": { name: "双刃头锤", - effect: "拼命使出浑身力气,向对手进行头锤攻击。\n自己也会受到非常大的伤害", + effect: "拼命使出浑身力气,\n向对手进行头锤攻击。\n自己也会受到非常大的伤害", }, "doubleHit": { name: "二连击", @@ -1943,7 +1943,7 @@ export const move: MoveTranslationEntries = { }, "electroBall": { name: "电球", - effect: "用电气团撞向对手。自己比对手速度越快,\n威力越大", + effect: "用电气团撞向对手。\n自己比对手速度越快,\n威力越大", }, "soak": { name: "浸水", @@ -1991,7 +1991,7 @@ export const move: MoveTranslationEntries = { }, "chipAway": { name: "逐步击破", - effect: "看准机会稳步攻击。无视对手的能力变化,\n直接给予伤害", + effect: "看准机会稳步攻击。\n无视对手的能力变化,\n直接给予伤害", }, "clearSmog": { name: "清除之烟", @@ -2007,7 +2007,7 @@ export const move: MoveTranslationEntries = { }, "allySwitch": { name: "交换场地", - effect: "用神奇的力量瞬间移动,\n互换自己和同伴所在的位置。连续使出则容易失败", + effect: "用神奇的力量瞬间移动,\n互换自己和同伴所在的位置。\n连续使出则容易失败", }, "scald": { name: "热水", @@ -2115,7 +2115,7 @@ export const move: MoveTranslationEntries = { }, "drillRun": { name: "直冲钻", - effect: "像钢钻一样,一边旋转身体一边撞击对手。\n容易击中要害", + effect: "像钢钻一样,\n一边旋转身体一边撞击对手。\n容易击中要害", }, "dualChop": { name: "二连劈", @@ -2199,11 +2199,11 @@ export const move: MoveTranslationEntries = { }, "boltStrike": { name: "雷击", - effect: "让强大的电流覆盖全身,\n猛撞向对手进行攻击。有时会让对手陷入麻痹状态", + effect: "让强大的电流覆盖全身,\n猛撞向对手进行攻击。\n有时会让对手陷入麻痹状态", }, "blueFlare": { name: "青焰", - effect: "用美丽而激烈的青焰包裹住对手进行攻击。\n有时会让对手陷入灼伤状态", + effect: "用美丽而激烈的青焰\n包裹住对手进行攻击。\n有时会让对手陷入灼伤状态", }, "fieryDance": { name: "火之舞", @@ -2211,7 +2211,7 @@ export const move: MoveTranslationEntries = { }, "freezeShock": { name: "冰冻伏特", - effect: "用覆盖着电流的冰块,\n在第2回合撞向对手。有时会让对手陷入麻痹状态", + effect: "用覆盖着电流的冰块,\n在第2回合撞向对手。\n有时会让对手陷入麻痹状态", }, "iceBurn": { name: "极寒冷焰", @@ -2263,7 +2263,7 @@ export const move: MoveTranslationEntries = { }, "phantomForce": { name: "潜灵奇袭", - effect: "第1回合消失在某处,\n第2回合攻击对手。可以无视守护进行攻击", + effect: "第1回合消失在某处,\n第2回合攻击对手。\n可以无视守护进行攻击", }, "trickOrTreat": { name: "万圣夜", @@ -2295,7 +2295,7 @@ export const move: MoveTranslationEntries = { }, "disarmingVoice": { name: "魅惑之声", - effect: "发出魅惑的叫声,给予对手精神上的伤害。\n攻击必定会命中", + effect: "发出魅惑的叫声,\n给予对手精神上的伤害。\n攻击必定会命中", }, "partingShot": { name: "抛下狠话", @@ -2311,7 +2311,7 @@ export const move: MoveTranslationEntries = { }, "craftyShield": { name: "戏法防守", - effect: "使用神奇的力量防住攻击我方的变化招式。\n但无法防住伤害招式的攻击", + effect: "使用神奇的力量防住\n攻击我方的变化招式。\n但无法防住伤害招式的攻击", }, "flowerShield": { name: "鲜花防守", @@ -2351,7 +2351,7 @@ export const move: MoveTranslationEntries = { }, "kingsShield": { name: "王者盾牌", - effect: "防住对手攻击的同时,\n自己变为防御姿态。能够降低所接触到的对手的攻击", + effect: "防住对手攻击的同时,\n自己变为防御姿态。\n能够降低所接触到的对手的攻击", }, "playNice": { name: "和睦相处", @@ -2395,7 +2395,7 @@ export const move: MoveTranslationEntries = { }, "venomDrench": { name: "毒液陷阱", - effect: "将特殊的毒液泼向对手。\n对处于中毒状态的对手,其攻击、特攻和速\n度都会降低", + effect: "将特殊的毒液泼向对手。\n对处于中毒状态的对手,其攻击、\n特攻和速度都会降低", }, "powder": { name: "粉尘", @@ -2459,15 +2459,15 @@ export const move: MoveTranslationEntries = { }, "thousandWaves": { name: "千波激荡", - effect: "从地面掀起波浪进行攻击。\n被掀入波浪中的对手,将无法从战斗中逃走", + effect: "从地面掀起波浪进行攻击。\n被掀入波浪中的对手,\n将无法从战斗中逃走", }, "landsWrath": { name: "大地神力", - effect: "聚集大地的力量,将此力量集中攻击对手,\n并给予伤害", + effect: "聚集大地的力量,\n将此力量集中攻击对手,\n并给予伤害", }, "lightOfRuin": { name: "破灭之光", - effect: "借用永恒之花的力量,\n发射出强力光线。自己也会受到非常大的伤害", + effect: "借用永恒之花的力量,\n发射出强力光线。\n自己也会受到非常大的伤害", }, "originPulse": { name: "根源波动", @@ -2495,43 +2495,43 @@ export const move: MoveTranslationEntries = { }, "allOutPummelingPhysical": { name: "格斗Z全力无双激烈拳", - effect: "通过Z力量制造出能量弹,\n全力撞向对手。威力会根据原来的招式而改变", + effect: "通过Z力量制造出能量弹,\n全力撞向对手。\n威力会根据原来的招式而改变", }, "allOutPummelingSpecial": { name: "格斗Z全力无双激烈拳", - effect: "通过Z力量制造出能量弹,\n全力撞向对手。威力会根据原来的招式而改变", + effect: "通过Z力量制造出能量弹,\n全力撞向对手。\n威力会根据原来的招式而改变", }, "supersonicSkystrikePhysical": { name: "飞行Z极速俯冲轰烈撞", - effect: "通过Z力量猛烈地飞向天空,\n朝对手全力落下。威力会根据原来的招\n式而改变", + effect: "通过Z力量猛烈地飞向天空,\n朝对手全力落下。\n威力会根据原来的招式而改变", }, "supersonicSkystrikeSpecial": { name: "飞行Z极速俯冲轰烈撞", - effect: "通过Z力量猛烈地飞向天空,\n朝对手全力落下。威力会根据原来的招\n式而改变", + effect: "通过Z力量猛烈地飞向天空,\n朝对手全力落下。\n威力会根据原来的招式而改变", }, "acidDownpourPhysical": { name: "毒Z强酸剧毒灭绝雨", - effect: "通过Z力量使毒沼涌起,\n全力让对手沉下去。威力会根据原来的招式而改变", + effect: "通过Z力量使毒沼涌起,\n全力让对手沉下去。\n威力会根据原来的招式而改变", }, "acidDownpourSpecial": { name: "毒Z强酸剧毒灭绝雨", - effect: "通过Z力量使毒沼涌起,\n全力让对手沉下去。威力会根据原来的招式而改变", + effect: "通过Z力量使毒沼涌起,\n全力让对手沉下去。\n威力会根据原来的招式而改变", }, "tectonicRagePhysical": { name: "地面Z地隆啸天大终结", - effect: "通过Z力量潜入地里最深处,\n全力撞上对手。威力会根据原来的招式而改变", + effect: "通过Z力量潜入地里最深处,\n全力撞上对手。\n威力会根据原来的招式而改变", }, "tectonicRageSpecial": { name: "地面Z地隆啸天大终结", - effect: "通过Z力量潜入地里最深处,\n全力撞上对手。威力会根据原来的招式而改变", + effect: "通过Z力量潜入地里最深处,\n全力撞上对手。\n威力会根据原来的招式而改变", }, "continentalCrushPhysical": { name: "岩石Z毁天灭地巨岩坠", - effect: "通过Z力量召唤大大的岩山,\n全力撞向对手。威力会根据原来的招式而改变", + effect: "通过Z力量召唤大大的岩山,\n全力撞向对手。\n威力会根据原来的招式而改变", }, "continentalCrushSpecial": { name: "岩石Z毁天灭地巨岩坠", - effect: "通过Z力量召唤大大的岩山,\n全力撞向对手。威力会根据原来的招式而改变", + effect: "通过Z力量召唤大大的岩山,\n全力撞向对手。\n威力会根据原来的招式而改变", }, "savageSpinOutPhysical": { name: "虫Z绝对捕食回旋斩", @@ -2543,43 +2543,43 @@ export const move: MoveTranslationEntries = { }, "neverEndingNightmarePhysical": { name: "幽灵Z无尽暗夜之诱惑", - effect: "通过Z力量召唤强烈的怨念,\n全力降临到对手身上。威力会根据原来\n的招式而改变", + effect: "通过Z力量召唤强烈的怨念,\n全力降临到对手身上。\n威力会根据原来的招式而改变", }, "neverEndingNightmareSpecial": { name: "幽灵Z无尽暗夜之诱惑", - effect: "通过Z力量召唤强烈的怨念,\n全力降临到对手身上。威力会根据原来\n的招式而改变", + effect: "通过Z力量召唤强烈的怨念,\n全力降临到对手身上。\n威力会根据原来的招式而改变", }, "corkscrewCrashPhysical": { name: "钢Z超绝螺旋连击", - effect: "通过Z力量进行高速旋转,\n全力撞上对手。威力会根据原来的招式而改变", + effect: "通过Z力量进行高速旋转,\n全力撞上对手。\n威力会根据原来的招式而改变", }, "corkscrewCrashSpecial": { name: "钢Z超绝螺旋连击", - effect: "通过Z力量进行高速旋转,\n全力撞上对手。威力会根据原来的招式而改变", + effect: "通过Z力量进行高速旋转,\n全力撞上对手。\n威力会根据原来的招式而改变", }, "infernoOverdrivePhysical": { name: "火Z超强极限爆焰弹", - effect: "通过Z力量喷出熊熊烈火,\n全力撞向对手。威力会根据原来的招式而改变", + effect: "通过Z力量喷出熊熊烈火,\n全力撞向对手。\n威力会根据原来的招式而改变", }, "infernoOverdriveSpecial": { name: "火Z超强极限爆焰弹", - effect: "通过Z力量喷出熊熊烈火,\n全力撞向对手。威力会根据原来的招式而改变", + effect: "通过Z力量喷出熊熊烈火,\n全力撞向对手。\n威力会根据原来的招式而改变", }, "hydroVortexPhysical": { name: "水Z超级水流大漩涡", - effect: "通过Z力量制造大大的潮旋,\n全力吞没对手。威力会根据原来的招式而改变", + effect: "通过Z力量制造大大的潮旋,\n全力吞没对手。\n威力会根据原来的招式而改变", }, "hydroVortexSpecial": { name: "水Z超级水流大漩涡", - effect: "通过Z力量制造大大的潮旋,\n全力吞没对手。威力会根据原来的招式而改变", + effect: "通过Z力量制造大大的潮旋,\n全力吞没对手。\n威力会根据原来的招式而改变", }, "bloomDoomPhysical": { name: "草Z绚烂缤纷花怒放", - effect: "通过Z力量借助花草的能量,\n全力攻击对手。威力会根据原来的招式而改变", + effect: "通过Z力量借助花草的能量,\n全力攻击对手。\n威力会根据原来的招式而改变", }, "bloomDoomSpecial": { name: "草Z绚烂缤纷花怒放", - effect: "通过Z力量借助花草的能量,\n全力攻击对手。威力会根据原来的招式而改变", + effect: "通过Z力量借助花草的能量,\n全力攻击对手。\n威力会根据原来的招式而改变", }, "gigavoltHavocPhysical": { name: "电Z终极伏特狂雷闪", @@ -2591,43 +2591,43 @@ export const move: MoveTranslationEntries = { }, "shatteredPsychePhysical": { name: "超能力Z至高精神破坏波", - effect: "通过Z力量操纵对手,\n全力使其感受到痛苦。威力会根据原来的招式而改变", + effect: "通过Z力量操纵对手,\n全力使其感受到痛苦。\n威力会根据原来的招式而改变", }, "shatteredPsycheSpecial": { name: "超能力Z至高精神破坏波", - effect: "通过Z力量操纵对手,\n全力使其感受到痛苦。威力会根据原来的招式而改变", + effect: "通过Z力量操纵对手,\n全力使其感受到痛苦。\n威力会根据原来的招式而改变", }, "subzeroSlammerPhysical": { name: "冰Z激狂大地万里冰", - effect: "通过Z力量急剧降低气温,\n全力冰冻对手。威力会根据原来的招式而改变", + effect: "通过Z力量急剧降低气温,\n全力冰冻对手。\n威力会根据原来的招式而改变", }, "subzeroSlammerSpecial": { name: "冰Z激狂大地万里冰", - effect: "通过Z力量急剧降低气温,\n全力冰冻对手。威力会根据原来的招式而改变", + effect: "通过Z力量急剧降低气温,\n全力冰冻对手。\n威力会根据原来的招式而改变", }, "devastatingDrakePhysical": { name: "龙Z究极巨龙震天地", - effect: "通过Z力量将气场实体化,\n向对手全力发动袭击。威力会根据原来的\n招式而改变", + effect: "通过Z力量将气场实体化,\n向对手全力发动袭击。\n威力会根据原来的招式而改变", }, "devastatingDrakeSpecial": { name: "龙Z究极巨龙震天地", - effect: "通过Z力量将气场实体化,\n向对手全力发动袭击。威力会根据原来的\n招式而改变", + effect: "通过Z力量将气场实体化,\n向对手全力发动袭击。\n威力会根据原来的招式而改变", }, "blackHoleEclipsePhysical": { name: "恶Z黑洞吞噬万物灭", - effect: "通过Z力量收集恶能量,\n全力将对手吸入。威力会根据原来的招式而改变", + effect: "通过Z力量收集恶能量,\n全力将对手吸入。\n威力会根据原来的招式而改变", }, "blackHoleEclipseSpecial": { name: "恶Z黑洞吞噬万物灭", - effect: "通过Z力量收集恶能量,\n全力将对手吸入。威力会根据原来的招式而改变", + effect: "通过Z力量收集恶能量,\n全力将对手吸入。\n威力会根据原来的招式而改变", }, "twinkleTacklePhysical": { name: "妖精Z可爱星星飞天撞", - effect: "通过Z力量制造魅惑空间,\n全力捉弄对手。威力会根据原来的招式而改变", + effect: "通过Z力量制造魅惑空间,\n全力捉弄对手。\n威力会根据原来的招式而改变", }, "twinkleTackleSpecial": { name: "妖精Z可爱星星飞天撞", - effect: "通过Z力量制造魅惑空间,\n全力捉弄对手。威力会根据原来的招式而改变", + effect: "通过Z力量制造魅惑空间,\n全力捉弄对手。\n威力会根据原来的招式而改变", }, "catastropika": { name: "皮卡丘Z皮卡皮卡必杀击", @@ -2651,7 +2651,7 @@ export const move: MoveTranslationEntries = { }, "darkestLariat": { name: "DD金勾臂", - effect: "旋转双臂打向对手。无视对手的能力变化,\n直接给予伤害", + effect: "旋转双臂打向对手。\n无视对手的能力变化,\n直接给予伤害", }, "sparklingAria": { name: "泡影的咏叹调", @@ -2671,7 +2671,7 @@ export const move: MoveTranslationEntries = { }, "strengthSap": { name: "吸取力量", - effect: "给自己回复和对手攻击力相同数值的HP,\n然后降低对手的攻击", + effect: "给自己回复和对手攻击力\n相同数值的HP,\n然后降低对手的攻击", }, "solarBlade": { name: "日光刃", @@ -2727,7 +2727,7 @@ export const move: MoveTranslationEntries = { }, "burnUp": { name: "燃尽", - effect: "将自己全身燃烧起火焰来,\n给予对手大大的伤害。自己的火属性将会消失", + effect: "将自己全身燃烧起火焰来,\n给予对手大大的伤害。\n自己的火属性将会消失", }, "speedSwap": { name: "速度互换", @@ -2763,7 +2763,7 @@ export const move: MoveTranslationEntries = { }, "clangingScales": { name: "鳞片噪音", - effect: "摩擦全身鳞片,发出响亮的声音进行攻击。\n攻击后自己的防御会降低", + effect: "摩擦全身鳞片,\n发出响亮的声音进行攻击。\n攻击后自己的防御会降低", }, "dragonHammer": { name: "龙锤", @@ -2827,7 +2827,7 @@ export const move: MoveTranslationEntries = { }, "stompingTantrum": { name: "跺脚", - effect: "化悔恨为力量进行攻击。\n如果上一回合招式没有打中,威力就会翻倍", + effect: "化悔恨为力量进行攻击。\n如果上一回合招式没有打中,\n威力就会翻倍", }, "shadowBone": { name: "暗影之骨", @@ -2871,7 +2871,7 @@ export const move: MoveTranslationEntries = { }, "multiAttack": { name: "多属性攻击", - effect: "一边覆盖高能量,一边撞向对手进行攻击。\n根据存储碟不同,\n属性会改变", + effect: "一边覆盖高能量,\n一边撞向对手进行攻击。\n根据存储碟不同,\n属性会改变", }, "tenMillionVoltThunderbolt": { name: "智皮卡Z千万伏特", @@ -2895,7 +2895,7 @@ export const move: MoveTranslationEntries = { }, "searingSunrazeSmash": { name: "索尔迦雷欧Z日光回旋下苍穹", - effect: "得到Z力量的索尔迦雷欧将全力进行攻击。\n可以无视对手的特性效果", + effect: "得到Z力量的索尔迦雷欧\n将全力进行攻击。\n可以无视对手的特性效果", }, "menacingMoonrazeMaelstrom": { name: "露奈雅拉Z月华飞溅落灵霄", @@ -2911,7 +2911,7 @@ export const move: MoveTranslationEntries = { }, "clangorousSoulblaze": { name: "杖尾鳞甲龙Z炽魂热舞烈音爆", - effect: "得到Z力量的杖尾鳞甲龙将全力攻击对手。\n并且自己的能力会提高", + effect: "得到Z力量的杖尾鳞甲龙\n将全力攻击对手。\n并且自己的能力会提高", }, "zippyZap": { name: "电电加速", @@ -2983,7 +2983,7 @@ export const move: MoveTranslationEntries = { }, "jawLock": { name: "紧咬不放", - effect: "使双方直到一方昏厥为止无法替换宝可梦。\n其中一方退场则可以解除效果", + effect: "使双方直到一方昏厥为止\n无法替换宝可梦。\n其中一方退场则可以解除效果", }, "stuffCheeks": { name: "大快朵颐", @@ -3015,11 +3015,11 @@ export const move: MoveTranslationEntries = { }, "boltBeak": { name: "电喙", - effect: "用带电的喙啄刺对手。\n如果比对手先出手攻击,招式的威力会变成2倍", + effect: "用带电的喙啄刺对手。\n如果比对手先出手攻击,\n招式的威力会变成2倍", }, "fishiousRend": { name: "鳃咬", - effect: "用坚硬的腮咬住对手。\n如果比对手先出手攻击,招式的威力会变成2倍", + effect: "用坚硬的腮咬住对手。\n如果比对手先出手攻击,\n招式的威力会变成2倍", }, "courtChange": { name: "换场", @@ -3179,7 +3179,7 @@ export const move: MoveTranslationEntries = { }, "eternabeam": { name: "无极光束", - effect: "无极汰那变回原来的样子后,\n发动的最强攻击。下一回合自己将无法动弹", + effect: "无极汰那变回原来的样子后,\n发动的最强攻击。\n下一回合自己将无法动弹", }, "steelBeam": { name: "铁蹄光线", @@ -3227,7 +3227,7 @@ export const move: MoveTranslationEntries = { }, "burningJealousy": { name: "妒火", - effect: "用嫉妒的能量攻击对手。\n会让在该回合内能力有所提高的宝可梦陷入\n灼伤状态", + effect: "用嫉妒的能量攻击对手。\n会让在该回合内能力有所提高\n的宝可梦陷入灼伤状态", }, "lashOut": { name: "泄愤", @@ -3291,7 +3291,7 @@ export const move: MoveTranslationEntries = { }, "thunderousKick": { name: "雷鸣蹴击", - effect: "以雷电般的动作戏耍对手的同时使出脚踢。\n可降低对手的防御", + effect: "以雷电般的动作\n戏耍对手的同时使出脚踢。\n可降低对手的防御", }, "glacialLance": { name: "雪矛", @@ -3307,7 +3307,7 @@ export const move: MoveTranslationEntries = { }, "direClaw": { name: "克命爪", - effect: "以破灭之爪进行攻击。\n有时还会让对手陷入中毒、麻痹、睡眠之中的\n一种状态", + effect: "以破灭之爪进行攻击。\n有时还会让对手陷入中毒、麻痹、\n睡眠之中的一种状态", }, "psyshieldBash": { name: "屏障猛攻", @@ -3323,7 +3323,7 @@ export const move: MoveTranslationEntries = { }, "springtideStorm": { name: "阳春风暴", - effect: "用交织着爱与恨的烈风席卷对手进行攻击。\n有时会降低对手的攻击", + effect: "用交织着爱与恨的烈风席卷对手\n进行攻击。有时会降低对手的攻击", }, "mysticalPower": { name: "神秘之力", @@ -3355,7 +3355,7 @@ export const move: MoveTranslationEntries = { }, "barbBarrage": { name: "毒千针", - effect: "用无数的毒针进行攻击。\n有时还会让对手陷入中毒状态。\n攻击处于中毒状态的对手时,威力会变成2倍", + effect: "用无数的毒针进行攻击。\n有时还会让对手陷入中毒状态。\n攻击处于中毒状态的对手时,\n威力会变成2倍", }, "esperWing": { name: "气场之翼", @@ -3375,11 +3375,11 @@ export const move: MoveTranslationEntries = { }, "infernalParade": { name: "群魔乱舞", - effect: "用无数的火球进行攻击。\n有时会让对手陷入灼伤状态。攻击处于异常\n状态的对手时,威力会变成2倍", + effect: "用无数的火球进行攻击。有时会让对手陷\n入灼伤状态。攻击处于异常状态\n的对手时,威力会变成2倍", }, "ceaselessEdge": { name: "秘剑・千重涛", - effect: "用贝壳之剑进行攻击。\n散落的贝壳碎片会散落在对手脚下成为撒菱", + effect: "用贝壳之剑进行攻击。\n散落的贝壳碎片会散落\n在对手脚下成为撒菱", }, "bleakwindStorm": { name: "枯叶风暴", @@ -3487,7 +3487,7 @@ export const move: MoveTranslationEntries = { }, "gMaxSmite": { name: "超极巨天谴雷诛", - effect: "超极巨化的布莉姆温使出的妖精属性攻击。\n会让对手陷入混乱状态", + effect: "超极巨化的布莉姆温使出的\n妖精属性攻击。\n会让对手陷入混乱状态", }, "gMaxSteelsurge": { name: "超极巨钢铁阵法", @@ -3515,7 +3515,7 @@ export const move: MoveTranslationEntries = { }, "gMaxDrumSolo": { name: "超极巨狂擂乱打", - effect: "超极巨化的轰擂金刚猩使出的草属性攻击。\n不会受到对手特性的干扰", + effect: "超极巨化的轰擂金刚猩使出的\n草属性攻击。\n不会受到对手特性的干扰", }, "gMaxFireball": { name: "超极巨破阵火球", @@ -3567,11 +3567,11 @@ export const move: MoveTranslationEntries = { }, "spinOut": { name: "疾速转轮", - effect: "通过往腿上增加负荷,\n以激烈的旋转给予对手伤害。自己的速度会大幅降低", + effect: "通过往腿上增加负荷,\n以激烈的旋转给予对手伤害。\n自己的速度会大幅降低", }, "populationBomb": { name: "鼠数儿", - effect: "伙伴们会纷纷赶来集合,\n以群体行动给予对手攻击。连续命中1~10次", + effect: "伙伴们会纷纷赶来集合,\n以群体行动给予对手攻击。\n连续命中1~10次", }, "iceSpinner": { name: "冰旋", @@ -3583,11 +3583,11 @@ export const move: MoveTranslationEntries = { }, "revivalBlessing": { name: "复生祈祷", - effect: "通过以慈爱之心祈祷,\n让陷入昏厥的后备宝可梦以回复一半HP的状态复活", + effect: "通过以慈爱之心祈祷,\n让陷入昏厥的后备宝可梦\n以回复一半HP的状态复活", }, "saltCure": { name: "盐腌", - effect: "使对手陷入盐腌状态,\n每回合给予对手伤害。对手为钢或水属性时会更痛苦", + effect: "使对手陷入盐腌状态,\n每回合给予对手伤害。\n对手为钢或水属性时会更痛苦", }, "tripleDive": { name: "三连钻", @@ -3599,7 +3599,7 @@ export const move: MoveTranslationEntries = { }, "doodle": { name: "描绘", - effect: "把握并映射出对手的本质,\n让自己和同伴宝可梦的特性变得和对手相同", + effect: "把握并映射出对手的本质,\n让自己和同伴宝可梦的特性\n变得和对手相同", }, "filletAway": { name: "甩肉", @@ -3615,7 +3615,7 @@ export const move: MoveTranslationEntries = { }, "torchSong": { name: "闪焰高歌", - effect: "如唱歌一样喷出熊熊燃烧的火焰烧焦对手。\n会提高自己的特攻", + effect: "如唱歌一样喷出熊熊燃烧的火焰\n烧焦对手。会提高自己的特攻", }, "aquaStep": { name: "流水旋舞", @@ -3623,7 +3623,7 @@ export const move: MoveTranslationEntries = { }, "ragingBull": { name: "怒牛", - effect: "狂怒暴牛的猛烈冲撞。\n招式的属性随形态改变,光墙和反射壁等招式\n也能破坏", + effect: "狂怒暴牛的猛烈冲撞。\n招式的属性随形态改变,\n光墙和反射壁等招式也能破坏", }, "makeItRain": { name: "淘金潮", @@ -3631,7 +3631,7 @@ export const move: MoveTranslationEntries = { }, "psyblade": { name: "精神剑", - effect: "用无形的利刃劈开对手。\n处于电气场地时,招式威力会变成1.5倍", + effect: "用无形的利刃劈开对手。\n处于电气场地时,\n招式威力会变成1.5倍", }, "hydroSteam": { name: "水蒸气", @@ -3655,7 +3655,7 @@ export const move: MoveTranslationEntries = { }, "chillyReception": { name: "冷笑话", - effect: "留下冷场的冷笑话后,\n和后备宝可梦进行替换。在5回合内会下雪", + effect: "留下冷场的冷笑话后,\n和后备宝可梦进行替换。\n在5回合内会下雪", }, "tidyUp": { name: "大扫除", @@ -3691,7 +3691,7 @@ export const move: MoveTranslationEntries = { }, "armorCannon": { name: "铠农炮", - effect: "熊熊燃烧自己的铠甲,\n将其做成炮弹射出攻击。自己的防御和特防会降低", + effect: "熊熊燃烧自己的铠甲,\n将其做成炮弹射出攻击。\n自己的防御和特防会降低", }, "bitterBlade": { name: "悔念剑", @@ -3699,7 +3699,7 @@ export const move: MoveTranslationEntries = { }, "doubleShock": { name: "电光双击", - effect: "将全身所有的电力放出,\n给予对手大大的伤害。自己的电属性将会消失", + effect: "将全身所有的电力放出,\n给予对手大大的伤害。\n自己的电属性将会消失", }, "gigatonHammer": { name: "巨力锤", @@ -3743,11 +3743,11 @@ export const move: MoveTranslationEntries = { }, "syrupBomb": { name: "糖浆炸弹", - effect: "使粘稠的麦芽糖浆爆炸,\n让对手陷入满身糖状态,在3回合内持续降\n低其速度", + effect: "使粘稠的麦芽糖浆爆炸,\n让对手陷入满身糖状态,\n在3回合内持续降\n低其速度", }, "ivyCudgel": { name: "棘藤棒", - effect: "用缠有藤蔓的棍棒殴打。\n属性会随所戴的面具而改变。容易击中要害", + effect: "用缠有藤蔓的棍棒殴打。\n属性会随所戴的面具而改变。\n容易击中要害", }, "electroShot": { name: "电光束", @@ -3787,7 +3787,7 @@ export const move: MoveTranslationEntries = { }, "alluringVoice": { name: "魅诱之声", - effect: "用天使般的歌声攻击对手。\n会让此回合内能力有提高的宝可梦陷入混乱状态", + effect: "用天使般的歌声攻击对手。\n会让此回合内能力有提高的\n宝可梦陷入混乱状态", }, "temperFlare": { name: "豁出去", From 63667aa062122e3ad82ac8086c3f24babc955d51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niccol=C3=B2?= <123510358+NicusPulcis@users.noreply.github.com> Date: Wed, 10 Jul 2024 16:15:57 +0200 Subject: [PATCH 27/94] [Localization(it)] Localize luck in common.ts (Italian) (#2952) --- src/locales/it/common.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/locales/it/common.ts b/src/locales/it/common.ts index de5197cbeeb..2a84e982350 100644 --- a/src/locales/it/common.ts +++ b/src/locales/it/common.ts @@ -2,7 +2,7 @@ import { SimpleTranslationEntries } from "#app/interfaces/locales"; export const common: SimpleTranslationEntries = { "start": "Inizia", - "luckIndicator": "Luck:", + "luckIndicator": "Fortuna:", "shinyOnHover": "Shiny", "commonShiny": "Comune", "rareShiny": "Raro", From ffbc922e0991ecd9de4d30e2488072cb7fe837c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niccol=C3=B2?= <123510358+NicusPulcis@users.noreply.github.com> Date: Wed, 10 Jul 2024 16:18:53 +0200 Subject: [PATCH 28/94] Update ability-trigger.ts (Italian) (#2949) --- src/locales/it/ability-trigger.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/locales/it/ability-trigger.ts b/src/locales/it/ability-trigger.ts index 1f6fcfb1258..fd18147ac5a 100644 --- a/src/locales/it/ability-trigger.ts +++ b/src/locales/it/ability-trigger.ts @@ -3,9 +3,9 @@ import { SimpleTranslationEntries } from "#app/interfaces/locales"; export const abilityTriggers: SimpleTranslationEntries = { "blockRecoilDamage" : "{{abilityName}} di {{pokemonName}}\nl'ha protetto dal contraccolpo!", "badDreams": "{{pokemonName}} è tormentato dagli incubi!", - "costar": "{{pokemonName}} copied {{allyName}}'s stat changes!", + "costar": "{{pokemonName}} ha copiato le modifiche alle statistiche\ndel suo alleato {{allyName}}!", "iceFaceAvoidedDamage": "{{pokemonName}} ha evitato\ni danni grazie a {{abilityName}}!", - "trace": "{{pokemonName}} copied {{targetName}}'s\n{{abilityName}}!", + "trace": "L'abilità {{abilityName}} di {{targetName}}\nviene copiata da {{pokemonName}} con Traccia!", "windPowerCharged": "Venire colpito da {{moveName}} ha caricato {{pokemonName}}!", - "quickDraw":"{{pokemonName}} can act faster than normal, thanks to its Quick Draw!", + "quickDraw":"{{pokemonName}} agisce più rapidamente del normale grazie a Colpolesto!", } as const; From fce810fa7372efceb1cc8120ec5f01d5b509161d Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Wed, 10 Jul 2024 15:34:27 -0400 Subject: [PATCH 29/94] Uploading changes to test I'm lazy and am not going to copy the files into my offline version even though using the installer is slower --- src/data/nature.ts | 83 ++++++++++++++++++++++++++++++++++++++++++++++ src/logger.ts | 68 ++++++++++++++++++++++++++++++++++--- src/phases.ts | 1 + 3 files changed, 147 insertions(+), 5 deletions(-) diff --git a/src/data/nature.ts b/src/data/nature.ts index 0d9be0f663d..90924de99ed 100644 --- a/src/data/nature.ts +++ b/src/data/nature.ts @@ -61,6 +61,89 @@ export function getNatureName(nature: Nature, includeStatEffects: boolean = fals return ret; } +export function getNatureIncrease(nature: Nature): string { + switch (nature) { + case Nature.LONELY: + case Nature.BRAVE: + case Nature.ADAMANT: + case Nature.NAUGHTY: + return "atk"; + case Nature.BOLD: + case Nature.RELAXED: + case Nature.IMPISH: + case Nature.LAX: + return "def"; + case Nature.MODEST: + case Nature.MILD: + case Nature.QUIET: + case Nature.RASH: + return "spatk"; + case Nature.CALM: + case Nature.GENTLE: + case Nature.SASSY: + case Nature.CAREFUL: + return "spdef"; + case Nature.TIMID: + case Nature.HASTY: + case Nature.JOLLY: + case Nature.NAIVE: + return "speed" + case Nature.HARDY: + //return "atk" + case Nature.DOCILE: + //return "def" + case Nature.SERIOUS: + //return "spatk" + case Nature.BASHFUL: + //return "spdef" + case Nature.QUIRKY: + //return "speed" + default: + return "" + } +} +export function getNatureDecrease(nature: Nature): string { + switch (nature) { + case Nature.BOLD: + case Nature.TIMID: + case Nature.MODEST: + case Nature.CALM: + return "atk"; + case Nature.LONELY: + case Nature.HASTY: + case Nature.MILD: + case Nature.GENTLE: + return "def" + case Nature.ADAMANT: + case Nature.IMPISH: + case Nature.JOLLY: + case Nature.CAREFUL: + return "spatk" + case Nature.NAUGHTY: + case Nature.LAX: + case Nature.NAIVE: + case Nature.RASH: + return "spdef" + case Nature.BRAVE: + case Nature.RELAXED: + case Nature.QUIET: + case Nature.SASSY: + return "speed" + case Nature.HARDY: + //return "atk" + case Nature.DOCILE: + //return "def" + case Nature.SERIOUS: + //return "spatk" + case Nature.BASHFUL: + //return "spdef" + case Nature.QUIRKY: + //return "speed" + default: + return "" + } +} + export function getNatureStatMultiplier(nature: Nature, stat: Stat): number { switch (stat) { case Stat.ATK: diff --git a/src/logger.ts b/src/logger.ts index 7610450ebb2..e56cc2bd261 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -2,7 +2,7 @@ import i18next from "i18next"; import * as Utils from "./utils"; import Pokemon from "./field/pokemon"; import { PlayerPokemon, EnemyPokemon } from "./field/pokemon"; -import { Nature, getNatureName } from "./data/nature"; +import { Nature, getNatureDecrease, getNatureIncrease, getNatureName } from "./data/nature"; import BattleScene from "./battle-scene"; import { OptionSelectItem } from "./ui/abstact-option-select-ui-handler"; import { TrainerType } from "#enums/trainer-type"; @@ -26,7 +26,10 @@ export var logKeys: string[] = [ "d", // Debug ]; -export const DRPD_Version = "0.1.3" +export const DRPD_Version = "1.0.0" +export const acceptedVersions = [ + "1.0.0" +] export interface DRPD { version: string, title?: string, @@ -42,7 +45,7 @@ export interface Wave { type: string, double: boolean, actions: string[], - shop: boolean | string, + shop: string, biome: string, trainer?: TrainerData, pokemon?: PokeData[] @@ -53,7 +56,7 @@ export interface PokeData { ability: string, isHiddenAbility: boolean, passiveAbility: string, - nature: string, + nature: NatureData, gender: string, rarity: string, captured: boolean, @@ -62,6 +65,11 @@ export interface PokeData { ivs: IVData, source?: Pokemon } +export interface NatureData { + name: string, + increased: string, + decreased: string +} export interface IVData { hp: integer, atk: integer, @@ -92,6 +100,25 @@ export function newDocument(name: string = "Untitled Run " + (new Date().getUTCM filename: (new Date().getUTCMonth() + 1 < 10 ? "0" : "") + (new Date().getUTCMonth() + 1) + "-" + (new Date().getUTCDate() < 10 ? "0" : "") + new Date().getUTCDate() + "-" + new Date().getUTCFullYear() + "_untitled" } } +export function importDocument(drpd: string): DRPD { + return JSON.parse(drpd) as DRPD; +} +export function importPokemon(pokemon: any): PokeData { + return { + id: pokemon.id, + name: pokemon.name, + ability: pokemon.ability, + isHiddenAbility: pokemon.isHiddenAbility, + passiveAbility: pokemon.passiveAbility, + nature: importNature(pokemon.nature), + gender: pokemon.gender, + rarity: pokemon.rarity, + captured: pokemon.captured, + level: pokemon.level, + items: pokemon.items.map(itm => importItem(itm)), + ivs: importIVs(pokemon.ivs) + } +} export function exportPokemon(pokemon: Pokemon, encounterRarity?: string): PokeData { return { id: pokemon.species.speciesId, @@ -99,7 +126,7 @@ export function exportPokemon(pokemon: Pokemon, encounterRarity?: string): PokeD ability: pokemon.getAbility().name, isHiddenAbility: pokemon.hasAbility(pokemon.species.abilityHidden), passiveAbility: pokemon.getPassiveAbility().name, - nature: getNatureName(pokemon.nature), + nature: exportNature(pokemon.nature), gender: pokemon.gender == 0 ? "Male" : (pokemon.gender == 1 ? "Female" : "Genderless"), rarity: encounterRarity, captured: false, @@ -108,6 +135,27 @@ export function exportPokemon(pokemon: Pokemon, encounterRarity?: string): PokeD ivs: exportIVs(pokemon.ivs) } } +export function importNature(nature: any): NatureData { + return { + name: nature.name, + increased: nature.increased, + decreased: nature.decreased + } +} +export function exportNature(nature: Nature): NatureData { + return { + name: getNatureName(nature), + increased: getNatureIncrease(nature), + decreased: getNatureDecrease(nature), + } +} +export function importItem(item: any): ItemData { + return { + id: item.id, + name: item.name, + quantity: item.quantity + } +} export function exportItem(item: PokemonHeldItemModifier): ItemData { return { id: item.type.id, @@ -115,6 +163,16 @@ export function exportItem(item: PokemonHeldItemModifier): ItemData { quantity: item.getStackCount() } } +export function importIVs(ivs: any): IVData { + return { + hp: ivs[0], + atk: ivs[1], + def: ivs[2], + spatk: ivs[3], + spdef: ivs[4], + speed: ivs[5] + } +} export function exportIVs(ivs: integer[]): IVData { return { hp: ivs[0], diff --git a/src/phases.ts b/src/phases.ts index f65dd406337..3452eefc8c1 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -342,6 +342,7 @@ export class TitlePhase extends Phase { start(): void { super.start(); + console.log(LoggerTools.importDocument(JSON.stringify(LoggerTools.newDocument()))) this.scene.ui.clearText(); this.scene.ui.fadeIn(250); From 24907cc862fd9e861e075e6bb8a45d7142b42a60 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Wed, 10 Jul 2024 15:39:07 -0400 Subject: [PATCH 30/94] Remove test nvm updating the local directly is easier --- src/phases.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/phases.ts b/src/phases.ts index 3452eefc8c1..f65dd406337 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -342,7 +342,6 @@ export class TitlePhase extends Phase { start(): void { super.start(); - console.log(LoggerTools.importDocument(JSON.stringify(LoggerTools.newDocument()))) this.scene.ui.clearText(); this.scene.ui.fadeIn(250); From ae680822cc1b7dcf9f03c1fc5be7c22bb0c26ba7 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Wed, 10 Jul 2024 15:41:42 -0400 Subject: [PATCH 31/94] Remove redundant files --- src/logger.ts | 40 ---------------------------------------- 1 file changed, 40 deletions(-) diff --git a/src/logger.ts b/src/logger.ts index e56cc2bd261..575927bd59e 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -103,22 +103,6 @@ export function newDocument(name: string = "Untitled Run " + (new Date().getUTCM export function importDocument(drpd: string): DRPD { return JSON.parse(drpd) as DRPD; } -export function importPokemon(pokemon: any): PokeData { - return { - id: pokemon.id, - name: pokemon.name, - ability: pokemon.ability, - isHiddenAbility: pokemon.isHiddenAbility, - passiveAbility: pokemon.passiveAbility, - nature: importNature(pokemon.nature), - gender: pokemon.gender, - rarity: pokemon.rarity, - captured: pokemon.captured, - level: pokemon.level, - items: pokemon.items.map(itm => importItem(itm)), - ivs: importIVs(pokemon.ivs) - } -} export function exportPokemon(pokemon: Pokemon, encounterRarity?: string): PokeData { return { id: pokemon.species.speciesId, @@ -135,13 +119,6 @@ export function exportPokemon(pokemon: Pokemon, encounterRarity?: string): PokeD ivs: exportIVs(pokemon.ivs) } } -export function importNature(nature: any): NatureData { - return { - name: nature.name, - increased: nature.increased, - decreased: nature.decreased - } -} export function exportNature(nature: Nature): NatureData { return { name: getNatureName(nature), @@ -149,13 +126,6 @@ export function exportNature(nature: Nature): NatureData { decreased: getNatureDecrease(nature), } } -export function importItem(item: any): ItemData { - return { - id: item.id, - name: item.name, - quantity: item.quantity - } -} export function exportItem(item: PokemonHeldItemModifier): ItemData { return { id: item.type.id, @@ -163,16 +133,6 @@ export function exportItem(item: PokemonHeldItemModifier): ItemData { quantity: item.getStackCount() } } -export function importIVs(ivs: any): IVData { - return { - hp: ivs[0], - atk: ivs[1], - def: ivs[2], - spatk: ivs[3], - spdef: ivs[4], - speed: ivs[5] - } -} export function exportIVs(ivs: integer[]): IVData { return { hp: ivs[0], From abd6da7854d97cfd5a605b570d39c9b88ea58162 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Wed, 10 Jul 2024 15:51:37 -0400 Subject: [PATCH 32/94] Store local copy of log Storing a local copy of the current save's log for easy(?) access --- src/logger.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/logger.ts b/src/logger.ts index 575927bd59e..cc861e9cc62 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -26,6 +26,8 @@ export var logKeys: string[] = [ "d", // Debug ]; +export var StoredLog: DRPD = undefined; + export const DRPD_Version = "1.0.0" export const acceptedVersions = [ "1.0.0" From 216cae16cd93459ede0beef691f65234ee48fcc8 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Thu, 11 Jul 2024 21:04:34 -0400 Subject: [PATCH 33/94] Log everything except the logging part --- src/battle-scene.ts | 2 + src/events/arena.ts | 921 +++++++++++++++++++++++++++++++++++++++----- src/field/arena.ts | 2 +- src/logger.ts | 445 +++++++++++++++++++-- src/phases.ts | 116 ++++-- 5 files changed, 1323 insertions(+), 163 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 03216101f27..131cb0c91b6 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -67,6 +67,7 @@ import { Species } from "#enums/species"; import { UiTheme } from "#enums/ui-theme"; import { TimedEventManager } from "#app/timed-event-manager.js"; import i18next from "i18next"; +import * as LoggerTools from "./logger" export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1"; @@ -1585,6 +1586,7 @@ export default class BattleScene extends SceneBase { if (fromArenaPool) { return this.arena.randomSpecies(waveIndex, level,null , getPartyLuckValue(this.party)); } + LoggerTools.rarities[LoggerTools.rarityslot[0]] = "" const filteredSpecies = speciesFilter ? [...new Set(allSpecies.filter(s => s.isCatchable()).filter(speciesFilter).map(s => { if (!filterAllEvolutions) { while (pokemonPrevolutions.hasOwnProperty(s.speciesId)) { diff --git a/src/events/arena.ts b/src/events/arena.ts index 67b423f3b75..8658b35d756 100644 --- a/src/events/arena.ts +++ b/src/events/arena.ts @@ -1,103 +1,850 @@ -import { ArenaTagSide } from "#app/data/arena-tag.js"; +import BattleScene from "../battle-scene"; +import { BiomePoolTier, PokemonPools, BiomeTierTrainerPools, biomePokemonPools, biomeTrainerPools } from "../data/biomes"; +import { Constructor } from "#app/utils"; +import * as Utils from "../utils"; +import PokemonSpecies, { getPokemonSpecies } from "../data/pokemon-species"; +import { Weather, WeatherType, getTerrainClearMessage, getTerrainStartMessage, getWeatherClearMessage, getWeatherStartMessage } from "../data/weather"; +import { CommonAnimPhase } from "../phases"; +import { CommonAnim } from "../data/battle-anims"; +import { Type } from "../data/type"; +import Move from "../data/move"; +import { ArenaTag, ArenaTagSide, ArenaTrapTag, getArenaTag } from "../data/arena-tag"; +import { BattlerIndex } from "../battle"; +import { Terrain, TerrainType } from "../data/terrain"; +import { PostTerrainChangeAbAttr, PostWeatherChangeAbAttr, applyPostTerrainChangeAbAttrs, applyPostWeatherChangeAbAttrs } from "../data/ability"; +import Pokemon from "./pokemon"; +import * as Overrides from "../overrides"; +import { WeatherChangedEvent, TerrainChangedEvent, TagAddedEvent, TagRemovedEvent } from "../events/arena"; import { ArenaTagType } from "#enums/arena-tag-type"; -import { TerrainType } from "#app/data/terrain.js"; -import { WeatherType } from "#app/data/weather.js"; +import { Biome } from "#enums/biome"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import { TimeOfDay } from "#enums/time-of-day"; +import { TrainerType } from "#enums/trainer-type"; +import * as LoggerTools from "../logger" -/** Alias for all {@linkcode ArenaEvent} type strings */ -export enum ArenaEventType { - /** Triggers when a {@linkcode WeatherType} is added, overlapped, or removed */ - WEATHER_CHANGED = "onWeatherChanged", - /** Triggers when a {@linkcode TerrainType} is added, overlapped, or removed */ - TERRAIN_CHANGED = "onTerrainChanged", +export class Arena { + public scene: BattleScene; + public biomeType: Biome; + public weather: Weather; + public terrain: Terrain; + public tags: ArenaTag[]; + public bgm: string; + public ignoreAbilities: boolean; - /** Triggers when a {@linkcode ArenaTagType} is added */ - TAG_ADDED = "onTagAdded", - /** Triggers when a {@linkcode ArenaTagType} is removed */ - TAG_REMOVED = "onTagRemoved", -} + private lastTimeOfDay: TimeOfDay; -/** - * Base container class for all {@linkcode ArenaEventType} events - * @extends Event - */ -export class ArenaEvent extends Event { - /** The total duration of the {@linkcode ArenaEventType} */ - public duration: number; - constructor(eventType: ArenaEventType, duration: number) { - super(eventType); + public pokemonPool: PokemonPools; + private trainerPool: BiomeTierTrainerPools; - this.duration = duration; + public readonly eventTarget: EventTarget = new EventTarget(); + + constructor(scene: BattleScene, biome: Biome, bgm: string) { + this.scene = scene; + this.biomeType = biome; + this.tags = []; + this.bgm = bgm; + this.trainerPool = biomeTrainerPools[biome]; + this.updatePoolsForTimeOfDay(); } -} -/** - * Container class for {@linkcode ArenaEventType.WEATHER_CHANGED} events - * @extends ArenaEvent -*/ -export class WeatherChangedEvent extends ArenaEvent { - /** The {@linkcode WeatherType} being overridden */ - public oldWeatherType: WeatherType; - /** The {@linkcode WeatherType} being set */ - public newWeatherType: WeatherType; - constructor(oldWeatherType: WeatherType, newWeatherType: WeatherType, duration: number) { - super(ArenaEventType.WEATHER_CHANGED, duration); - this.oldWeatherType = oldWeatherType; - this.newWeatherType = newWeatherType; + init() { + const biomeKey = getBiomeKey(this.biomeType); + + this.scene.arenaPlayer.setBiome(this.biomeType); + this.scene.arenaPlayerTransition.setBiome(this.biomeType); + this.scene.arenaEnemy.setBiome(this.biomeType); + this.scene.arenaNextEnemy.setBiome(this.biomeType); + this.scene.arenaBg.setTexture(`${biomeKey}_bg`); + this.scene.arenaBgTransition.setTexture(`${biomeKey}_bg`); + + // Redo this on initialise because during save/load the current wave isn't always + // set correctly during construction + this.updatePoolsForTimeOfDay(); } -} -/** - * Container class for {@linkcode ArenaEventType.TERRAIN_CHANGED} events - * @extends ArenaEvent -*/ -export class TerrainChangedEvent extends ArenaEvent { - /** The {@linkcode TerrainType} being overridden */ - public oldTerrainType: TerrainType; - /** The {@linkcode TerrainType} being set */ - public newTerrainType: TerrainType; - constructor(oldTerrainType: TerrainType, newTerrainType: TerrainType, duration: number) { - super(ArenaEventType.TERRAIN_CHANGED, duration); - this.oldTerrainType = oldTerrainType; - this.newTerrainType = newTerrainType; + updatePoolsForTimeOfDay(): void { + const timeOfDay = this.getTimeOfDay(); + if (timeOfDay !== this.lastTimeOfDay) { + this.pokemonPool = {}; + for (const tier of Object.keys(biomePokemonPools[this.biomeType])) { + this.pokemonPool[tier] = Object.assign([], biomePokemonPools[this.biomeType][tier][TimeOfDay.ALL]).concat(biomePokemonPools[this.biomeType][tier][timeOfDay]); + } + this.lastTimeOfDay = timeOfDay; + } + } + + randomSpecies(waveIndex: integer, level: integer, attempt?: integer, luckValue?: integer): PokemonSpecies { + const overrideSpecies = this.scene.gameMode.getOverrideSpecies(waveIndex); + if (overrideSpecies) { + return overrideSpecies; + } + const isBoss = !!this.scene.getEncounterBossSegments(waveIndex, level) && !!this.pokemonPool[BiomePoolTier.BOSS].length + && (this.biomeType !== Biome.END || this.scene.gameMode.isClassic || this.scene.gameMode.isWaveFinal(waveIndex)); + const randVal = isBoss ? 64 : 512; + // luck influences encounter rarity + let luckModifier = 0; + if (typeof luckValue !== "undefined") { + luckModifier = luckValue * (isBoss ? 0.5 : 2); + } + const tierValue = Utils.randSeedInt(randVal - luckModifier); + let tier = !isBoss + ? tierValue >= 156 ? BiomePoolTier.COMMON : tierValue >= 32 ? BiomePoolTier.UNCOMMON : tierValue >= 6 ? BiomePoolTier.RARE : tierValue >= 1 ? BiomePoolTier.SUPER_RARE : BiomePoolTier.ULTRA_RARE + : tierValue >= 20 ? BiomePoolTier.BOSS : tierValue >= 6 ? BiomePoolTier.BOSS_RARE : tierValue >= 1 ? BiomePoolTier.BOSS_SUPER_RARE : BiomePoolTier.BOSS_ULTRA_RARE; + console.log(BiomePoolTier[tier]); + var tiernames = [ + "Common", + "Uncommon", + "Rare", + "Super Rare", + "Ultra Rare", + "Common", + "Rare", + "Super Rare", + "Ultra Rare", + ] + LoggerTools.rarities[LoggerTools.rarityslot[0]] = tiernames[tier] + console.log(tiernames[tier]) + while (!this.pokemonPool[tier].length) { + console.log(`Downgraded rarity tier from ${BiomePoolTier[tier]} to ${BiomePoolTier[tier - 1]}`); + tier--; + } + const tierPool = this.pokemonPool[tier]; + let ret: PokemonSpecies; + let regen = false; + if (!tierPool.length) { + ret = this.scene.randomSpecies(waveIndex, level); + } else { + const entry = tierPool[Utils.randSeedInt(tierPool.length)]; + let species: Species; + if (typeof entry === "number") { + species = entry as Species; + } else { + const levelThresholds = Object.keys(entry); + for (let l = levelThresholds.length - 1; l >= 0; l--) { + const levelThreshold = parseInt(levelThresholds[l]); + if (level >= levelThreshold) { + const speciesIds = entry[levelThreshold]; + if (speciesIds.length > 1) { + species = speciesIds[Utils.randSeedInt(speciesIds.length)]; + } else { + species = speciesIds[0]; + } + break; + } + } + } + + ret = getPokemonSpecies(species); + + if (ret.subLegendary || ret.legendary || ret.mythical) { + switch (true) { + case (ret.baseTotal >= 720): + regen = level < 90; + break; + case (ret.baseTotal >= 670): + regen = level < 70; + break; + case (ret.baseTotal >= 580): + regen = level < 50; + break; + default: + regen = level < 30; + break; + } + } + } + + if (regen && (attempt || 0) < 10) { + console.log("Incompatible level: regenerating..."); + return this.randomSpecies(waveIndex, level, (attempt || 0) + 1); + } + + const newSpeciesId = ret.getWildSpeciesForLevel(level, true, isBoss, this.scene.gameMode); + if (newSpeciesId !== ret.speciesId) { + console.log("Replaced", Species[ret.speciesId], "with", Species[newSpeciesId]); + ret = getPokemonSpecies(newSpeciesId); + } + return ret; + } + + randomTrainerType(waveIndex: integer): TrainerType { + const isBoss = !!this.trainerPool[BiomePoolTier.BOSS].length + && this.scene.gameMode.isTrainerBoss(waveIndex, this.biomeType, this.scene.offsetGym); + console.log(isBoss, this.trainerPool); + const tierValue = Utils.randSeedInt(!isBoss ? 512 : 64); + let tier = !isBoss + ? tierValue >= 156 ? BiomePoolTier.COMMON : tierValue >= 32 ? BiomePoolTier.UNCOMMON : tierValue >= 6 ? BiomePoolTier.RARE : tierValue >= 1 ? BiomePoolTier.SUPER_RARE : BiomePoolTier.ULTRA_RARE + : tierValue >= 20 ? BiomePoolTier.BOSS : tierValue >= 6 ? BiomePoolTier.BOSS_RARE : tierValue >= 1 ? BiomePoolTier.BOSS_SUPER_RARE : BiomePoolTier.BOSS_ULTRA_RARE; + console.log(BiomePoolTier[tier]); + while (tier && !this.trainerPool[tier].length) { + console.log(`Downgraded trainer rarity tier from ${BiomePoolTier[tier]} to ${BiomePoolTier[tier - 1]}`); + tier--; + } + const tierPool = this.trainerPool[tier] || []; + return !tierPool.length ? TrainerType.BREEDER : tierPool[Utils.randSeedInt(tierPool.length)]; + } + + getSpeciesFormIndex(species: PokemonSpecies): integer { + switch (species.speciesId) { + case Species.BURMY: + case Species.WORMADAM: + switch (this.biomeType) { + case Biome.BEACH: + return 1; + case Biome.SLUM: + return 2; + } + break; + case Species.ROTOM: + switch (this.biomeType) { + case Biome.VOLCANO: + return 1; + case Biome.SEA: + return 2; + case Biome.ICE_CAVE: + return 3; + case Biome.MOUNTAIN: + return 4; + case Biome.TALL_GRASS: + return 5; + } + break; + case Species.LYCANROC: + const timeOfDay = this.getTimeOfDay(); + switch (timeOfDay) { + case TimeOfDay.DAY: + case TimeOfDay.DAWN: + return 0; + case TimeOfDay.DUSK: + return 2; + case TimeOfDay.NIGHT: + return 1; + } + break; + } + + return 0; + } + + getTypeForBiome() { + switch (this.biomeType) { + case Biome.TOWN: + case Biome.PLAINS: + case Biome.METROPOLIS: + return Type.NORMAL; + case Biome.GRASS: + case Biome.TALL_GRASS: + return Type.GRASS; + case Biome.FOREST: + case Biome.JUNGLE: + return Type.BUG; + case Biome.SLUM: + case Biome.SWAMP: + return Type.POISON; + case Biome.SEA: + case Biome.BEACH: + case Biome.LAKE: + case Biome.SEABED: + return Type.WATER; + case Biome.MOUNTAIN: + return Type.FLYING; + case Biome.BADLANDS: + return Type.GROUND; + case Biome.CAVE: + case Biome.DESERT: + return Type.ROCK; + case Biome.ICE_CAVE: + case Biome.SNOWY_FOREST: + return Type.ICE; + case Biome.MEADOW: + case Biome.FAIRY_CAVE: + case Biome.ISLAND: + return Type.FAIRY; + case Biome.POWER_PLANT: + return Type.ELECTRIC; + case Biome.VOLCANO: + return Type.FIRE; + case Biome.GRAVEYARD: + case Biome.TEMPLE: + return Type.GHOST; + case Biome.DOJO: + case Biome.CONSTRUCTION_SITE: + return Type.FIGHTING; + case Biome.FACTORY: + case Biome.LABORATORY: + return Type.STEEL; + case Biome.RUINS: + case Biome.SPACE: + return Type.PSYCHIC; + case Biome.WASTELAND: + case Biome.END: + return Type.DRAGON; + case Biome.ABYSS: + return Type.DARK; + default: + return Type.UNKNOWN; + } + } + + getBgTerrainColorRatioForBiome(): number { + switch (this.biomeType) { + case Biome.SPACE: + return 1; + case Biome.END: + return 0; + } + + return 131 / 180; + } + + /** + * Sets weather to the override specified in overrides.ts + * @param weather new weather to set of type WeatherType + * @returns true to force trySetWeather to return true + */ + trySetWeatherOverride(weather: WeatherType): boolean { + this.weather = new Weather(weather, 0); + this.scene.unshiftPhase(new CommonAnimPhase(this.scene, undefined, undefined, CommonAnim.SUNNY + (weather - 1))); + this.scene.queueMessage(getWeatherStartMessage(weather)); + return true; + } + + /** + * Attempts to set a new weather to the battle + * @param weather new weather to set of type WeatherType + * @param hasPokemonSource is the new weather from a pokemon + * @returns true if new weather set, false if no weather provided or attempting to set the same weather as currently in use + */ + trySetWeather(weather: WeatherType, hasPokemonSource: boolean): boolean { + if (Overrides.WEATHER_OVERRIDE) { + return this.trySetWeatherOverride(Overrides.WEATHER_OVERRIDE); + } + + if (this.weather?.weatherType === (weather || undefined)) { + return false; + } + + const oldWeatherType = this.weather?.weatherType || WeatherType.NONE; + + this.weather = weather ? new Weather(weather, hasPokemonSource ? 5 : 0) : null; + this.eventTarget.dispatchEvent(new WeatherChangedEvent(oldWeatherType, this.weather?.weatherType, this.weather?.turnsLeft)); + + if (this.weather) { + this.scene.unshiftPhase(new CommonAnimPhase(this.scene, undefined, undefined, CommonAnim.SUNNY + (weather - 1))); + this.scene.queueMessage(getWeatherStartMessage(weather)); + } else { + this.scene.queueMessage(getWeatherClearMessage(oldWeatherType)); + } + + this.scene.getField(true).filter(p => p.isOnField()).map(pokemon => { + pokemon.findAndRemoveTags(t => "weatherTypes" in t && !(t.weatherTypes as WeatherType[]).find(t => t === weather)); + applyPostWeatherChangeAbAttrs(PostWeatherChangeAbAttr, pokemon, weather); + }); + + return true; + } + + trySetTerrain(terrain: TerrainType, hasPokemonSource: boolean, ignoreAnim: boolean = false): boolean { + if (this.terrain?.terrainType === (terrain || undefined)) { + return false; + } + + const oldTerrainType = this.terrain?.terrainType || TerrainType.NONE; + + this.terrain = terrain ? new Terrain(terrain, hasPokemonSource ? 5 : 0) : null; + this.eventTarget.dispatchEvent(new TerrainChangedEvent(oldTerrainType,this.terrain?.terrainType, this.terrain?.turnsLeft)); + + if (this.terrain) { + if (!ignoreAnim) { + this.scene.unshiftPhase(new CommonAnimPhase(this.scene, undefined, undefined, CommonAnim.MISTY_TERRAIN + (terrain - 1))); + } + this.scene.queueMessage(getTerrainStartMessage(terrain)); + } else { + this.scene.queueMessage(getTerrainClearMessage(oldTerrainType)); + } + + this.scene.getField(true).filter(p => p.isOnField()).map(pokemon => { + pokemon.findAndRemoveTags(t => "terrainTypes" in t && !(t.terrainTypes as TerrainType[]).find(t => t === terrain)); + applyPostTerrainChangeAbAttrs(PostTerrainChangeAbAttr, pokemon, terrain); + }); + + return true; + } + + isMoveWeatherCancelled(move: Move) { + return this.weather && !this.weather.isEffectSuppressed(this.scene) && this.weather.isMoveWeatherCancelled(move); + } + + isMoveTerrainCancelled(user: Pokemon, targets: BattlerIndex[], move: Move) { + return this.terrain && this.terrain.isMoveTerrainCancelled(user, targets, move); + } + + getTerrainType() : TerrainType { + return this.terrain?.terrainType || TerrainType.NONE; + } + + getAttackTypeMultiplier(attackType: Type, grounded: boolean): number { + let weatherMultiplier = 1; + if (this.weather && !this.weather.isEffectSuppressed(this.scene)) { + weatherMultiplier = this.weather.getAttackTypeMultiplier(attackType); + } + + let terrainMultiplier = 1; + if (this.terrain && grounded) { + terrainMultiplier = this.terrain.getAttackTypeMultiplier(attackType); + } + + return weatherMultiplier * terrainMultiplier; + } + + /** + * Gets the denominator for the chance for a trainer spawn + * @returns n where 1/n is the chance of a trainer battle + */ + getTrainerChance(): integer { + switch (this.biomeType) { + case Biome.METROPOLIS: + return 2; + case Biome.SLUM: + case Biome.BEACH: + case Biome.DOJO: + case Biome.CONSTRUCTION_SITE: + return 4; + case Biome.PLAINS: + case Biome.GRASS: + case Biome.LAKE: + case Biome.CAVE: + return 6; + case Biome.TALL_GRASS: + case Biome.FOREST: + case Biome.SEA: + case Biome.SWAMP: + case Biome.MOUNTAIN: + case Biome.BADLANDS: + case Biome.DESERT: + case Biome.MEADOW: + case Biome.POWER_PLANT: + case Biome.GRAVEYARD: + case Biome.FACTORY: + case Biome.SNOWY_FOREST: + return 8; + case Biome.ICE_CAVE: + case Biome.VOLCANO: + case Biome.RUINS: + case Biome.WASTELAND: + case Biome.JUNGLE: + case Biome.FAIRY_CAVE: + return 12; + case Biome.SEABED: + case Biome.ABYSS: + case Biome.SPACE: + case Biome.TEMPLE: + return 16; + default: + return 0; + } + } + + getTimeOfDay(): TimeOfDay { + switch (this.biomeType) { + case Biome.ABYSS: + return TimeOfDay.NIGHT; + } + + const waveCycle = ((this.scene.currentBattle?.waveIndex || 0) + this.scene.waveCycleOffset) % 40; + + if (waveCycle < 15) { + return TimeOfDay.DAY; + } + + if (waveCycle < 20) { + return TimeOfDay.DUSK; + } + + if (waveCycle < 35) { + return TimeOfDay.NIGHT; + } + + return TimeOfDay.DAWN; + } + + isOutside(): boolean { + switch (this.biomeType) { + case Biome.SEABED: + case Biome.CAVE: + case Biome.ICE_CAVE: + case Biome.POWER_PLANT: + case Biome.DOJO: + case Biome.FACTORY: + case Biome.ABYSS: + case Biome.FAIRY_CAVE: + case Biome.TEMPLE: + case Biome.LABORATORY: + return false; + default: + return true; + } + } + + overrideTint(): [integer, integer, integer] { + switch (Overrides.ARENA_TINT_OVERRIDE) { + case TimeOfDay.DUSK: + return [ 98, 48, 73 ].map(c => Math.round((c + 128) / 2)) as [integer, integer, integer]; + break; + case (TimeOfDay.NIGHT): + return [ 64, 64, 64 ]; + break; + case TimeOfDay.DAWN: + case TimeOfDay.DAY: + default: + return [ 128, 128, 128 ]; + break; + } + } + + getDayTint(): [integer, integer, integer] { + if (Overrides.ARENA_TINT_OVERRIDE !== null) { + return this.overrideTint(); + } + switch (this.biomeType) { + case Biome.ABYSS: + return [ 64, 64, 64 ]; + default: + return [ 128, 128, 128 ]; + } + } + + getDuskTint(): [integer, integer, integer] { + if (Overrides.ARENA_TINT_OVERRIDE) { + return this.overrideTint(); + } + if (!this.isOutside()) { + return [ 0, 0, 0 ]; + } + + switch (this.biomeType) { + default: + return [ 98, 48, 73 ].map(c => Math.round((c + 128) / 2)) as [integer, integer, integer]; + } + } + + getNightTint(): [integer, integer, integer] { + if (Overrides.ARENA_TINT_OVERRIDE) { + return this.overrideTint(); + } + switch (this.biomeType) { + case Biome.ABYSS: + case Biome.SPACE: + case Biome.END: + return this.getDayTint(); + } + + if (!this.isOutside()) { + return [ 64, 64, 64 ]; + } + + switch (this.biomeType) { + default: + return [ 48, 48, 98 ]; + } + } + + setIgnoreAbilities(ignoreAbilities: boolean = true): void { + this.ignoreAbilities = ignoreAbilities; + } + + applyTagsForSide(tagType: ArenaTagType | Constructor, side: ArenaTagSide, ...args: unknown[]): void { + let tags = typeof tagType === "string" + ? this.tags.filter(t => t.tagType === tagType) + : this.tags.filter(t => t instanceof tagType); + if (side !== ArenaTagSide.BOTH) { + tags = tags.filter(t => t.side === side); + } + tags.forEach(t => t.apply(this, args)); + } + + applyTags(tagType: ArenaTagType | Constructor, ...args: unknown[]): void { + this.applyTagsForSide(tagType, ArenaTagSide.BOTH, ...args); + } + + addTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves, sourceId: integer, side: ArenaTagSide = ArenaTagSide.BOTH, quiet: boolean = false, targetIndex?: BattlerIndex): boolean { + const existingTag = this.getTagOnSide(tagType, side); + if (existingTag) { + existingTag.onOverlap(this); + + if (existingTag instanceof ArenaTrapTag) { + const { tagType, side, turnCount, layers, maxLayers } = existingTag as ArenaTrapTag; + this.eventTarget.dispatchEvent(new TagAddedEvent(tagType, side, turnCount, layers, maxLayers)); + } + + return false; + } + + const newTag = getArenaTag(tagType, turnCount || 0, sourceMove, sourceId, targetIndex, side); + this.tags.push(newTag); + newTag.onAdd(this, quiet); + + const { layers = 0, maxLayers = 0 } = newTag instanceof ArenaTrapTag ? newTag : {}; + + this.eventTarget.dispatchEvent(new TagAddedEvent(newTag.tagType, newTag.side, newTag.turnCount, layers, maxLayers)); + + return true; + } + + getTag(tagType: ArenaTagType | Constructor): ArenaTag { + return this.getTagOnSide(tagType, ArenaTagSide.BOTH); + } + + getTagOnSide(tagType: ArenaTagType | Constructor, side: ArenaTagSide): ArenaTag { + return typeof(tagType) === "string" + ? this.tags.find(t => t.tagType === tagType && (side === ArenaTagSide.BOTH || t.side === ArenaTagSide.BOTH || t.side === side)) + : this.tags.find(t => t instanceof tagType && (side === ArenaTagSide.BOTH || t.side === ArenaTagSide.BOTH || t.side === side)); + } + + findTags(tagPredicate: (t: ArenaTag) => boolean): ArenaTag[] { + return this.findTagsOnSide(tagPredicate, ArenaTagSide.BOTH); + } + + findTagsOnSide(tagPredicate: (t: ArenaTag) => boolean, side: ArenaTagSide): ArenaTag[] { + return this.tags.filter(t => tagPredicate(t) && (side === ArenaTagSide.BOTH || t.side === ArenaTagSide.BOTH || t.side === side)); + } + + lapseTags(): void { + this.tags.filter(t => !(t.lapse(this))).forEach(t => { + t.onRemove(this); + this.tags.splice(this.tags.indexOf(t), 1); + + this.eventTarget.dispatchEvent(new TagRemovedEvent(t.tagType, t.side, t.turnCount)); + }); + } + + removeTag(tagType: ArenaTagType): boolean { + const tags = this.tags; + const tag = tags.find(t => t.tagType === tagType); + if (tag) { + tag.onRemove(this); + tags.splice(tags.indexOf(tag), 1); + + this.eventTarget.dispatchEvent(new TagRemovedEvent(tag.tagType, tag.side, tag.turnCount)); + } + return !!tag; + } + + removeTagOnSide(tagType: ArenaTagType, side: ArenaTagSide, quiet: boolean = false): boolean { + const tag = this.getTagOnSide(tagType, side); + if (tag) { + tag.onRemove(this, quiet); + this.tags.splice(this.tags.indexOf(tag), 1); + + this.eventTarget.dispatchEvent(new TagRemovedEvent(tag.tagType, tag.side, tag.turnCount)); + } + return !!tag; + } + + + removeAllTags(): void { + while (this.tags.length) { + this.tags[0].onRemove(this); + this.eventTarget.dispatchEvent(new TagRemovedEvent(this.tags[0].tagType, this.tags[0].side, this.tags[0].turnCount)); + + this.tags.splice(0, 1); + } + } + + preloadBgm(): void { + this.scene.loadBgm(this.bgm); + } + + getBgmLoopPoint(): number { + switch (this.biomeType) { + case Biome.TOWN: + return 7.288; + case Biome.PLAINS: + return 7.693; + case Biome.GRASS: + return 1.995; + case Biome.TALL_GRASS: + return 9.608; + case Biome.METROPOLIS: + return 141.470; + case Biome.FOREST: + return 4.294; + case Biome.SEA: + return 1.672; + case Biome.SWAMP: + return 4.461; + case Biome.BEACH: + return 3.462; + case Biome.LAKE: + return 5.350; + case Biome.SEABED: + return 2.600; + case Biome.MOUNTAIN: + return 4.018; + case Biome.BADLANDS: + return 17.790; + case Biome.CAVE: + return 14.240; + case Biome.DESERT: + return 1.143; + case Biome.ICE_CAVE: + return 15.010; + case Biome.MEADOW: + return 3.891; + case Biome.POWER_PLANT: + return 2.810; + case Biome.VOLCANO: + return 5.116; + case Biome.GRAVEYARD: + return 3.232; + case Biome.DOJO: + return 6.205; + case Biome.FACTORY: + return 4.985; + case Biome.RUINS: + return 2.270; + case Biome.WASTELAND: + return 6.336; + case Biome.ABYSS: + return 5.130; + case Biome.SPACE: + return 20.036; + case Biome.CONSTRUCTION_SITE: + return 1.222; + case Biome.JUNGLE: + return 0.000; + case Biome.FAIRY_CAVE: + return 4.542; + case Biome.TEMPLE: + return 2.547; + case Biome.ISLAND: + return 2.751; + case Biome.LABORATORY: + return 114.862; + case Biome.SLUM: + return 1.221; + case Biome.SNOWY_FOREST: + return 3.047; + } } } -/** - * Container class for {@linkcode ArenaEventType.TAG_ADDED} events - * @extends ArenaEvent -*/ -export class TagAddedEvent extends ArenaEvent { - /** The {@linkcode ArenaTagType} being added */ - public arenaTagType: ArenaTagType; - /** The {@linkcode ArenaTagSide} the tag is being placed on */ - public arenaTagSide: ArenaTagSide; - /** The current number of layers of the arena trap. */ - public arenaTagLayers: number; - /** The maximum amount of layers of the arena trap. */ - public arenaTagMaxLayers: number; +export function getBiomeKey(biome: Biome): string { + return Biome[biome].toLowerCase(); +} - constructor(arenaTagType: ArenaTagType, arenaTagSide: ArenaTagSide, duration: number, arenaTagLayers?: number, arenaTagMaxLayers?: number) { - super(ArenaEventType.TAG_ADDED, duration); +export function getBiomeHasProps(biomeType: Biome): boolean { + switch (biomeType) { + case Biome.METROPOLIS: + case Biome.BEACH: + case Biome.LAKE: + case Biome.SEABED: + case Biome.MOUNTAIN: + case Biome.BADLANDS: + case Biome.CAVE: + case Biome.DESERT: + case Biome.ICE_CAVE: + case Biome.MEADOW: + case Biome.POWER_PLANT: + case Biome.VOLCANO: + case Biome.GRAVEYARD: + case Biome.FACTORY: + case Biome.RUINS: + case Biome.WASTELAND: + case Biome.ABYSS: + case Biome.CONSTRUCTION_SITE: + case Biome.JUNGLE: + case Biome.FAIRY_CAVE: + case Biome.TEMPLE: + case Biome.SNOWY_FOREST: + case Biome.ISLAND: + case Biome.LABORATORY: + case Biome.END: + return true; + } - this.arenaTagType = arenaTagType; - this.arenaTagSide = arenaTagSide; - this.arenaTagLayers = arenaTagLayers; - this.arenaTagMaxLayers = arenaTagMaxLayers; - } -} -/** - * Container class for {@linkcode ArenaEventType.TAG_REMOVED} events - * @extends ArenaEvent -*/ -export class TagRemovedEvent extends ArenaEvent { - /** The {@linkcode ArenaTagType} being removed */ - public arenaTagType: ArenaTagType; - /** The {@linkcode ArenaTagSide} the tag was being placed on */ - public arenaTagSide: ArenaTagSide; - constructor(arenaTagType: ArenaTagType, arenaTagSide: ArenaTagSide, duration: number) { - super(ArenaEventType.TAG_REMOVED, duration); - - this.arenaTagType = arenaTagType; - this.arenaTagSide = arenaTagSide; + return false; +} + +export class ArenaBase extends Phaser.GameObjects.Container { + public player: boolean; + public biome: Biome; + public propValue: integer; + public base: Phaser.GameObjects.Sprite; + public props: Phaser.GameObjects.Sprite[]; + + constructor(scene: BattleScene, player: boolean) { + super(scene, 0, 0); + + this.player = player; + + this.base = scene.addFieldSprite(0, 0, "plains_a", null, 1); + this.base.setOrigin(0, 0); + + this.props = !player ? + new Array(3).fill(null).map(() => { + const ret = scene.addFieldSprite(0, 0, "plains_b", null, 1); + ret.setOrigin(0, 0); + ret.setVisible(false); + return ret; + }) : []; + } + + setBiome(biome: Biome, propValue?: integer): void { + const hasProps = getBiomeHasProps(biome); + const biomeKey = getBiomeKey(biome); + const baseKey = `${biomeKey}_${this.player ? "a" : "b"}`; + + if (biome !== this.biome) { + this.base.setTexture(baseKey); + + if (this.base.texture.frameTotal > 1) { + const baseFrameNames = this.scene.anims.generateFrameNames(baseKey, { zeroPad: 4, suffix: ".png", start: 1, end: this.base.texture.frameTotal - 1 }); + if (!(this.scene.anims.exists(baseKey))) { + this.scene.anims.create({ + key: baseKey, + frames: baseFrameNames, + frameRate: 12, + repeat: -1 + }); + } + this.base.play(baseKey); + } else { + this.base.stop(); + } + + this.add(this.base); + } + + if (!this.player) { + (this.scene as BattleScene).executeWithSeedOffset(() => { + this.propValue = propValue === undefined + ? hasProps ? Utils.randSeedInt(8) : 0 + : propValue; + this.props.forEach((prop, p) => { + const propKey = `${biomeKey}_b${hasProps ? `_${p + 1}` : ""}`; + prop.setTexture(propKey); + + if (hasProps && prop.texture.frameTotal > 1) { + const propFrameNames = this.scene.anims.generateFrameNames(propKey, { zeroPad: 4, suffix: ".png", start: 1, end: prop.texture.frameTotal - 1 }); + if (!(this.scene.anims.exists(propKey))) { + this.scene.anims.create({ + key: propKey, + frames: propFrameNames, + frameRate: 12, + repeat: -1 + }); + } + prop.play(propKey); + } else { + prop.stop(); + } + + prop.setVisible(hasProps && !!(this.propValue & (1 << p))); + this.add(prop); + }); + }, (this.scene as BattleScene).currentBattle?.waveIndex || 0, (this.scene as BattleScene).waveSeed); + } } } diff --git a/src/field/arena.ts b/src/field/arena.ts index ddb3499b3ae..51dba1089e9 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -33,7 +33,7 @@ export class Arena { private lastTimeOfDay: TimeOfDay; - private pokemonPool: PokemonPools; + public pokemonPool: PokemonPools; private trainerPool: BiomeTierTrainerPools; public readonly eventTarget: EventTarget = new EventTarget(); diff --git a/src/logger.ts b/src/logger.ts index cc861e9cc62..c93bc31cf20 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -8,8 +8,14 @@ import { OptionSelectItem } from "./ui/abstact-option-select-ui-handler"; import { TrainerType } from "#enums/trainer-type"; import { Modifier, PokemonHeldItemModifier } from "./modifier/modifier"; import Battle from "./battle"; -import { getBiomeName } from "./data/biomes"; +import { getBiomeName, PokemonPools, SpeciesTree } from "./data/biomes"; import { trainerConfigs } from "./data/trainer-config"; +import { Mode } from "./ui/ui"; +import { TitlePhase } from "./phases"; +import { Item } from "pokenode-ts"; +import Trainer from "./field/trainer"; +import { Species } from "./enums/species"; +import { junit } from "node:test/reporters"; /** * All logs. @@ -17,15 +23,24 @@ import { trainerConfigs } from "./data/trainer-config"; * Format: [filename, localStorage key, name, header, item sprite, header suffix] */ export const logs: string[][] = [ - ["instructions.txt", "path_log", "Steps", "Run Steps", "blunder_policy", ""], - ["encounters.csv", "enc_log", "Encounters", "Encounter Data", "ub", ",,,,,,,,,,,,,,,,"], + //["instructions.txt", "path_log", "Steps", "Run Steps", "blunder_policy", ""], + //["encounters.csv", "enc_log", "Encounters", "Encounter Data", "ub", ",,,,,,,,,,,,,,,,"], + ["drpd.json", "drpd", "DRPD", "", "wide_lens", ""], + //["drpd1.json", "drpd1", "DRPD 1", "", "wide_lens", ""], + //["drpd2.json", "drpd2", "DRPD 2", "", "wide_lens", ""], + //["drpd3.json", "drpd3", "DRPD 3", "", "wide_lens", ""], + //["drpd4.json", "drpd4", "DRPD 4", "", "wide_lens", ""], + //["drpd5.json", "drpd5", "DRPD 5", "", "wide_lens", ""], ] -export var logKeys: string[] = [ +export const logKeys: string[] = [ "i", // Instructions/steps "e", // Encounters "d", // Debug ]; +export const rarities = [] +export const rarityslot = [0] + export var StoredLog: DRPD = undefined; export const DRPD_Version = "1.0.0" @@ -38,8 +53,7 @@ export interface DRPD { authors: string[], date: string, waves: Wave[], - starters?: PokeData[], - filename: string + starters?: PokeData[] } export interface Wave { id: integer, @@ -91,7 +105,7 @@ export interface ItemData { quantity: integer, } -export function newDocument(name: string = "Untitled Run " + (new Date().getUTCMonth() + 1 < 10 ? "0" : "") + (new Date().getUTCMonth() + 1) + "/" + (new Date().getUTCDate() < 10 ? "0" : "") + new Date().getUTCDate() + "/" + new Date().getUTCFullYear(), authorName: string | string[] = "Write your name here"): DRPD { +export function newDocument(name: string = "Untitled Run", authorName: string | string[] = "Write your name here"): DRPD { return { version: DRPD_Version, title: name, @@ -99,7 +113,6 @@ export function newDocument(name: string = "Untitled Run " + (new Date().getUTCM date: (new Date().getUTCMonth() + 1 < 10 ? "0" : "") + (new Date().getUTCMonth() + 1) + "-" + (new Date().getUTCDate() < 10 ? "0" : "") + new Date().getUTCDate() + "-" + new Date().getUTCFullYear(), waves: new Array(50), starters: new Array(3), - filename: (new Date().getUTCMonth() + 1 < 10 ? "0" : "") + (new Date().getUTCMonth() + 1) + "-" + (new Date().getUTCDate() < 10 ? "0" : "") + new Date().getUTCDate() + "-" + new Date().getUTCFullYear() + "_untitled" } } export function importDocument(drpd: string): DRPD { @@ -177,6 +190,13 @@ export function exportWave(scene: BattleScene): Wave { } return ret; } +export function exportTrainer(trainer: Trainer): TrainerData { + return { + id: trainer.config.trainerType, + name: trainer.name, + type: trainer.config.getTitle(0, trainer.variant) + } +} export const byteSize = str => new Blob([str]).size const filesizes = ["b", "kb", "mb", "gb", "tb"] @@ -191,24 +211,29 @@ export function getSize(str: string) { } export function generateOption(i: integer): OptionSelectItem { - if (logs[i][4] != "") { - return { - label: `Export ${logs[i][2]} (${getSize(localStorage.getItem(logs[i][1]))})`, - handler: () => { - downloadLogByID(i) - return false; - }, - item: logs[i][4] - } - } else { - return { - label: `Export ${logs[i][2]} (${getSize(localStorage.getItem(logs[i][1]))})`, - handler: () => { - downloadLogByID(i) - return false; - } + var filename: string = (JSON.parse(localStorage.getItem(logs[i][1])) as DRPD).title + var op: OptionSelectItem = { + label: ` Export ${filename} (${getSize(localStorage.getItem(logs[i][1]))})`, + handler: () => { + downloadLogByID(i) + return false; } } + if (logs[i][4] != "") { + op.item = logs[i][4] + } + return op; +} +export function generateAddOption(i: integer, scene: BattleScene, o: TitlePhase) { + var op: OptionSelectItem = { + label: "Generate log " + logs[i][0], + handler: () => { + localStorage.setItem(logs[i][1], JSON.stringify(newDocument())) + o.callEnd(); + return true; + } + } + return op; } /** @@ -240,22 +265,25 @@ export function clearLog(keyword: string) { * @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"}); + var d = JSON.parse(localStorage.getItem(logs[logKeys.indexOf(keyword)][1])) + const blob = new Blob([ JSON.stringify(d) ], {type: "text/json"}); const link = document.createElement("a"); link.href = window.URL.createObjectURL(blob); - link.download = `${logs[logKeys.indexOf(keyword)][0]}`; + var date: string = (d as DRPD).date + var filename: string = date[0] + date[1] + "_" + date[3] + date[4] + "_" + date[6] + date[7] + date[8] + date[9] + "_route.json" + link.download = `${filename}`; link.click(); link.remove(); } export function downloadLogByID(i: integer) { console.log(i) - var d = localStorage.getItem(logs[i][1]) - const blob = new Blob([ d ], {type: "text/json"}); + var d = JSON.parse(localStorage.getItem(logs[i][1])) + const blob = new Blob([ printDRPD("", "", d as DRPD) ], {type: "text/json"}); const link = document.createElement("a"); link.href = window.URL.createObjectURL(blob); - link.download = `${logs[i][0]}`; + var date: string = (d as DRPD).date + var filename: string = date[0] + date[1] + "_" + date[3] + date[4] + "_" + date[6] + date[7] + date[8] + date[9] + "_route.json" + link.download = `${filename}`; link.click(); link.remove(); } @@ -263,20 +291,149 @@ export function logTeam(scene: BattleScene, floor: integer = undefined) { if (floor == undefined) floor = scene.currentBattle.waveIndex var team = scene.getEnemyParty() if (team[0].hasTrainer()) { - var sprite = scene.currentBattle.trainer.config.getSpriteKey() - var trainerCat = Utils.getEnumKeys(TrainerType)[Utils.getEnumValues(TrainerType).indexOf(scene.currentBattle.trainer.config.trainerType)] - setRow("e", floor + ",0," + sprite + ",trainer," + trainerCat + ",,,,,,,,,,,,", floor, 0) + //var sprite = scene.currentBattle.trainer.config.getSpriteKey() + //var trainerCat = Utils.getEnumKeys(TrainerType)[Utils.getEnumValues(TrainerType).indexOf(scene.currentBattle.trainer.config.trainerType)] + //setRow("e", floor + ",0," + sprite + ",trainer," + trainerCat + ",,,,,,,,,,,,", floor, 0) } else { for (var i = 0; i < team.length; i++) { - logPokemon(scene, floor, i, team[i]) + logPokemon(scene, floor, i, team[i], rarities[i]) } if (team.length == 1) { //setRow("e", ",,,,,,,,,,,,,,,,", floor, 1) } } } -export function logPokemon(scene: BattleScene, floor: integer = undefined, slot: integer, pokemon: EnemyPokemon) { +export function getWave(drpd: DRPD, floor: integer, scene: BattleScene) { + var wv: Wave; + var insertPos: integer; + for (var i = 0; i < drpd.waves.length; i++) { + if (drpd.waves[i] != undefined) { + if (drpd.waves[i].id == floor) { + wv = drpd.waves[i] + if (wv.pokemon == undefined) wv.pokemon = [] + } + } else if (insertPos == undefined) { + insertPos = i + } + } + if (wv == undefined && insertPos != undefined) { + drpd.waves[insertPos] = { + id: floor, + reload: false, + //type: floor % 10 == 0 ? "boss" : (floor % 10 == 5 ? "trainer" : "wild"), + type: floor % 10 == 0 ? "boss" : "wild", + double: scene.currentBattle.double, + actions: [], + shop: "", + biome: getBiomeName(scene.arena.biomeType), + pokemon: [] + } + wv = drpd.waves[insertPos] + } + drpd.waves.sort((a, b) => { + if (a == undefined) return 1; // empty values move to the bottom + if (b == undefined) return -1; // empty values move to the bottom + return a.id - b.id + }) + if (wv == undefined) { + console.error("Out of wave slots??") + scene.ui.showText("Out of wave slots!\nClearing duplicates...", null, () => { + for (var i = 0; i < drpd.waves.length - 1; i++) { + if (drpd.waves[i] != undefined && drpd.waves[i+1] != undefined) { + if (drpd.waves[i].id == drpd.waves[i+1].id) { + drpd.waves[i+1] = undefined + drpd.waves.sort((a, b) => { + if (a == undefined) return 1; // empty values move to the bottom + if (b == undefined) return -1; // empty values move to the bottom + return a.id - b.id + }) + } + } + } + if (drpd.waves[49] != undefined) { + scene.ui.showText("No space!\nPress F12 for info") + console.error("There should have been 50 slots, but somehow the program ran out of space.") + console.error("Go yell at @redstonewolf8557 to fix this") + } else { + for (var i = 0; i < drpd.waves.length; i++) { + if (drpd.waves[i] != undefined) { + if (drpd.waves[i].id == floor) { + wv = drpd.waves[i] + if (wv.pokemon == undefined) wv.pokemon = [] + } + } else if (insertPos == undefined) { + insertPos = i + } + } + if (wv == undefined && insertPos != undefined) { + drpd.waves[insertPos] = { + id: floor, + reload: false, + //type: floor % 10 == 0 ? "boss" : (floor % 10 == 5 ? "trainer" : "wild"), + type: floor % 10 == 0 ? "boss" : "wild", + double: scene.currentBattle.double, + actions: [], + shop: "", + biome: getBiomeName(scene.arena.biomeType), + pokemon: [] + } + wv = drpd.waves[insertPos] + } + drpd.waves.sort((a, b) => { + if (a == undefined) return 1; // empty values move to the bottom + if (b == undefined) return -1; // empty values move to the bottom + return a.id - b.id + }) + if (wv == undefined) { + scene.ui.showText("Failed to make space\nPress F12 for info") + console.error("There should be space to store a new wave, but the program failed to find space anyways") + console.error("Go yell at @redstonewolf8557 to fix this") + } + } + }) + } + return wv; +} +/* +const entry = tierPool[Utils.randSeedInt(tierPool.length)]; +let species: Species; +if (typeof entry === "number") { + species = entry as Species; +} else { + const levelThresholds = Object.keys(entry); + for (let l = levelThresholds.length - 1; l >= 0; l--) { + const levelThreshold = parseInt(levelThresholds[l]); + if (level >= levelThreshold) { + const speciesIds = entry[levelThreshold]; + if (speciesIds.length > 1) { + species = speciesIds[Utils.randSeedInt(speciesIds.length)]; + } else { + species = speciesIds[0]; + } + break; + } + } +}*/ +function checkForPokeInBiome(species: Species, pool: (Species | SpeciesTree)[]): boolean { + //console.log(species, pool) + for (var i = 0; i < pool.length; i++) { + if (typeof pool[i] === "number") { + //console.log(pool[i] + " == " + species + "? " + (pool[i] == species)) + if (pool[i] == species) return true; + } else { + var k = Object.keys(pool[i]) + //console.log(pool[i], k) + for (var j = 0; j < k.length; j++) { + //console.log(pool[i][k[j]] + " == " + species + "? " + (pool[i][k[j]] == species)) + if (pool[i][k[j]] == species) return true; + } + } + } + return false; +} +export function logPokemon(scene: BattleScene, floor: integer = undefined, slot: integer, pokemon: EnemyPokemon, encounterRarity?: string) { if (floor == undefined) floor = scene.currentBattle.waveIndex + /* var modifiers: string[] = [] var mods = pokemon.getHeldItems() for (var i = 0; i < mods.length; i++) { @@ -305,7 +462,88 @@ export function logPokemon(scene: BattleScene, floor: integer = undefined, slot: //console.log(idx, data.slice(0, idx), newLine, data.slice(idx)) setRow("e", newLine, floor, slot) //console.log(localStorage.getItem(logs[logKeys.indexOf("e")][1]).split("\n")) + */ + if (localStorage.getItem("drpd") == null) localStorage.setItem("drpd", JSON.stringify(newDocument())) + var drpd: DRPD = JSON.parse(localStorage.getItem("drpd")) as DRPD; + var wv: Wave = getWave(drpd, floor, scene) + var pk: PokeData = exportPokemon(pokemon, encounterRarity) + if (wv.pokemon[slot] != undefined) { + if (encounterRarity == "" || encounterRarity == undefined) { + if (wv.pokemon[slot].rarity != undefined && wv.pokemon[slot].rarity != "???") pk.rarity = wv.pokemon[slot].rarity + else { + var biome = scene.arena.biomeType + console.log(scene.arena.pokemonPool) + var tiernames = [ + "Common", + "Uncommon", + "Rare", + "Super Rare", + "Ultra Rare", + "Common", + "Rare", + "Super Rare", + "Ultra Rare", + ] + for (var i = 0; i < tiernames.length; i++) { + if (checkForPokeInBiome(wv.pokemon[slot].id, scene.arena.pokemonPool[i]) == true) { + console.log("Autofilled rarity for " + pk.name + " as " + tiernames[i]) + pk.rarity = tiernames[i] + } + } + } + } + if (JSON.stringify(wv.pokemon[slot]) != JSON.stringify(pk)) { + console.log("A different Pokemon already exists in this slot! Flagging as a reload") + wv.reload = true + } + } + if (pk.rarity == undefined) { + var biome = scene.arena.biomeType + console.log(scene.arena.pokemonPool) + var tiernames = [ + "Common", + "Uncommon", + "Rare", + "Super Rare", + "Ultra Rare", + "Common", + "Rare", + "Super Rare", + "Ultra Rare", + ] + for (var i = 0; i < tiernames.length; i++) { + if (checkForPokeInBiome(wv.pokemon[slot].id, scene.arena.pokemonPool[i]) == true) { + console.log("Autofilled rarity for " + pk.name + " as " + tiernames[i]) + pk.rarity = tiernames[i] + } + } + } + wv.pokemon[slot] = pk; + console.log(drpd) + localStorage.setItem("drpd", JSON.stringify(drpd)) } +export function logTrainer(scene: BattleScene, floor: integer = undefined) { + if (localStorage.getItem("drpd") == null) localStorage.setItem("drpd", JSON.stringify(newDocument())) + var drpd: DRPD = JSON.parse(localStorage.getItem("drpd")) as DRPD; + var wv: Wave = getWave(drpd, floor, scene) + var t: TrainerData = exportTrainer(scene.currentBattle.trainer) + wv.trainer = t + wv.type = "trainer" + console.log(drpd) + localStorage.setItem("drpd", JSON.stringify(drpd)) +} +export function logPlayerTeam(scene: BattleScene) { + if (localStorage.getItem("drpd") == null) localStorage.setItem("drpd", JSON.stringify(newDocument())) + var drpd: DRPD = JSON.parse(localStorage.getItem("drpd")) as DRPD; + //var wv: Wave = getWave(drpd, 1, scene) + var P = scene.getParty() + for (var i = 0; i < P.length; i++) { + drpd.starters[i] = exportPokemon(P[i]) + } + console.log(drpd) + localStorage.setItem("drpd", JSON.stringify(drpd)) +} + export function dataSorter(a: string, b: string) { var da = a.split(",") var db = b.split(",") @@ -384,4 +622,139 @@ export function setRow(keyword: string, newLine: string, floor: integer, slot: i console.log(i + " " + data[i]) } localStorage.setItem(logs[logKeys.indexOf(keyword)][1], data.slice(0, idx).join("\n") + "\n" + newLine + (data.slice(idx).length == 0 ? "" : "\n") + data.slice(idx).join("\n")); +} +export function printDRPD(inData: string, indent: string, drpd: DRPD): string { + inData += indent + "{" + inData += "\n" + indent + " \"version\": \"" + drpd.version + "\"" + inData += ",\n" + indent + " \"title\": \"" + drpd.title + "\"" + inData += ",\n" + indent + " \"authors\": [\"" + drpd.authors.join("\", \"") + "\"]" + inData += ",\n" + indent + " \"date\": \"" + drpd.date + "\"" + inData += ",\n" + indent + " \"waves\": [\n" + var isFirst = true + for (var i = 0; i < drpd.waves.length; i++) { + if (drpd.waves[i] != undefined) { + if (isFirst) { + isFirst = false; + } else { + inData += ",\n" + } + inData = printWave(inData, indent + " ", drpd.waves[i]) + } + } + inData += "\n" + indent + " ]\n" + indent + "}" + return inData; +} +function printWave(inData: string, indent: string, wave: Wave): string { + inData += indent + "{" + inData += "\n" + indent + " \"id\": " + wave.id + "" + inData += ",\n" + indent + " \"reload\": " + wave.reload + "" + inData += ",\n" + indent + " \"type\": \"" + wave.type + "\"" + inData += ",\n" + indent + " \"double\": " + wave.double + "" + inData += ",\n" + indent + " \"actions\": [\n" + var isFirst = true + for (var i = 0; i < wave.actions.length; i++) { + if (wave.actions[i] != undefined) { + if (isFirst) { + isFirst = false; + } else { + inData += "," + } + inData += "\n " + indent + "\"" + wave.actions[i] + "\"" + } + } + if (!isFirst) inData += "\n" + inData += indent + " ]" + inData += ",\n " + indent + "\"shop\": \"" + wave.shop + "\"" + inData += ",\n " + indent + "\"biome\": \"" + wave.biome + "\"" + if (wave.trainer) + inData += ",\n " + indent + "\"trainer\": " + wave.trainer + if (wave.pokemon) { + inData += ",\n " + indent + "\"pokemon\": [\n" + isFirst = true + for (var i = 0; i < wave.pokemon.length; i++) { + if (wave.pokemon[i] != undefined) { + if (isFirst) { + isFirst = false; + } else { + inData += ",\n" + } + inData = printPoke(inData, indent + " ", wave.pokemon[i]) + } + } + } + inData += "\n" + indent + " ]\n" + indent + "}" + return inData; +} +function printPoke(inData: string, indent: string, pokemon: PokeData) { + var itemdata: string = "" + inData += indent + "{" + inData += "\n" + indent + " \"id\": " + pokemon.id + inData += ",\n" + indent + " \"name\": \"" + pokemon.name + "\"" + inData += ",\n" + indent + " \"ability\": \"" + pokemon.ability + "\"" + inData += ",\n" + indent + " \"isHiddenAbility\": " + pokemon.isHiddenAbility + inData += ",\n" + indent + " \"passiveAbility\": \"" + pokemon.passiveAbility + "\"" + inData += ",\n" + indent + " \"nature\": \n" + inData = printNature(inData, indent + " ", pokemon.nature) + inData += ",\n" + indent + " \"gender\": \"" + pokemon.gender + "\"" + inData += ",\n" + indent + " \"rarity\": \"" + pokemon.rarity + "\"" + inData += ",\n" + indent + " \"captured\": " + pokemon.captured + inData += ",\n" + indent + " \"level\": " + pokemon.level + if (pokemon.items.length > 0) { + inData += ",\n" + indent + " \"items\": [\n" + var isFirst = true + for (var i = 0; i < pokemon.items.length; i++) { + if (pokemon.items[i] != undefined) { + if (isFirst) { + isFirst = false; + } else { + inData += "," + } + inData = printItem(inData, indent + " ", pokemon.items[i]) + } + } + if (!isFirst) inData += "\n" + inData += indent + " ]" + } else { + inData += ",\n" + indent + " \"items\": []" + } + inData += ",\n" + indent + " \"ivs\": " + inData = printIV(inData, indent + " ", pokemon.ivs) + //inData += ",\n" + indent + " \"rarity\": " + pokemon.rarity + inData += "\n" + indent + "}" + return inData; +} +function printNature(inData: string, indent: string, nature: NatureData) { + inData += indent + "{" + inData += "\n" + indent + " \"name\": \"" + nature.name + "\"" + inData += ",\n" + indent + " \"increased\": \"" + nature.increased + "\"" + inData += ",\n" + indent + " \"decreased\": \"" + nature.decreased + "\"" + inData += "\n" + indent + "}" + return inData; +} +function printIV(inData: string, indent: string, iv: IVData) { + inData += "{" + inData += "\n" + indent + " \"hp\": " + iv.hp + inData += ",\n" + indent + " \"atk\": " + iv.atk + inData += ",\n" + indent + " \"def\": " + iv.def + inData += ",\n" + indent + " \"spatk\": " + iv.spatk + inData += ",\n" + indent + " \"spdef\": " + iv.spdef + inData += ",\n" + indent + " \"speed\": " + iv.speed + inData += "\n" + indent + "}" + return inData; +} +function printTrainer(inData: string, indent: string, trainer: TrainerData) { + inData += indent + "{" + inData += "\n" + indent + " \"id\": \"" + trainer.id + "\"" + inData += ",\n" + indent + " \"name\": \"" + trainer.name + "\"" + inData += ",\n" + indent + " \"type\": \"" + trainer.type + "\"" + inData += "\n" + indent + "}" + return inData; +} +function printItem(inData: string, indent: string, item: ItemData) { + inData += indent + "{" + inData += "\n" + indent + " \"id\": \"" + item.id + "\"" + inData += ",\n" + indent + " \"name\": \"" + item.name + "\"" + inData += ",\n" + indent + " \"quantity\": " + item.quantity + inData += "\n" + indent + "}" + return inData; } \ No newline at end of file diff --git a/src/phases.ts b/src/phases.ts index f65dd406337..f81791ccef8 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -342,6 +342,7 @@ export class TitlePhase extends Phase { start(): void { super.start(); + console.log(LoggerTools.importDocument(JSON.stringify(LoggerTools.newDocument()))) this.scene.ui.clearText(); this.scene.ui.fadeIn(250); @@ -394,8 +395,61 @@ export class TitlePhase extends Phase { return saves.map(f => f[1]); } + callEnd(): boolean { + this.scene.clearPhaseQueue(); + this.scene.pushPhase(new TitlePhase(this.scene)); + super.end(); + return true; + } + + showLoggerOptions(txt: string, options: OptionSelectItem[]): boolean { + this.scene.ui.showText("Export or clear game logs.", null, () => this.scene.ui.setOverlayMode(Mode.OPTION_SELECT, { options: options })); + return true; + } + + logMenu(): boolean { + const options: OptionSelectItem[] = []; + for (var i = 0; i < LoggerTools.logs.length; i++) { + if (localStorage.getItem(LoggerTools.logs[i][1]) != null) { + options.push(LoggerTools.generateOption(i) as OptionSelectItem) + } else { + //options.push(LoggerTools.generateAddOption(i, this.scene, this)) + } + } + for (var i = 0; i < LoggerTools.logs.length; i++) { + if (localStorage.getItem(LoggerTools.logs[i][1]) != null) { + //options.push(LoggerTools.generateOption(i, this.scene, this.logMenu) as OptionSelectItem) + } else { + options.push(LoggerTools.generateAddOption(i, this.scene, this)) + } + } + options.push({ + label: "Delete all", + handler: () => { + for (var i = 0; i < LoggerTools.logs.length; i++) { + if (localStorage.getItem(LoggerTools.logs[i][1]) != null) { + localStorage.removeItem(LoggerTools.logs[i][1]) + } + } + this.scene.clearPhaseQueue(); + this.scene.pushPhase(new TitlePhase(this.scene)); + super.end(); + 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; + } + showOptions(): void { - var hasFile = true const options: OptionSelectItem[] = []; if (false) if (loggedInUser.lastSessionSlot > -1) { @@ -434,7 +488,15 @@ export class TitlePhase extends Phase { } else { console.log("Failed to get last save") this.getLastSave(true) - hasFile = false + if (loggedInUser.lastSessionSlot > -1) { + options.push({ + label: i18next.t("continue", null, { ns: "menu"}), + handler: () => { + this.loadSaveSlot(this.lastSessionData ? -1 : loggedInUser.lastSessionSlot); + return true; + } + }); + } } } options.push({ @@ -500,45 +562,9 @@ export class TitlePhase extends Phase { }, { 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(LoggerTools.generateOption(i) as OptionSelectItem) - } - options.push({ - label: `Export all (${options.length})`, - handler: () => { - for (var i = 0; i < LoggerTools.logKeys.length; i++) { - LoggerTools.downloadLog(LoggerTools.logKeys[i]) - } - return false; - } - }, { - label: `Reset all (${LoggerTools.logKeys.length})`, - handler: () => { - for (var i = 0; i < LoggerTools.logKeys.length; i++) { - LoggerTools.clearLog(LoggerTools.logKeys[i]) - } - this.scene.clearPhaseQueue(); - this.scene.pushPhase(new TitlePhase(this.scene)); - super.end(); - 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; + return this.logMenu() } }) - // If the player has no save data (as determined above), hide the "Load Game" button - if (hasFile || true) options.push({ label: i18next.t("menu:loadGame"), handler: () => { @@ -1128,11 +1154,16 @@ export class EncounterPhase extends BattlePhase { let totalBst = 0; + while (LoggerTools.rarities.length > 0) { + LoggerTools.rarities.pop() + } + LoggerTools.rarityslot[0] = 0 battle.enemyLevels.forEach((level, e) => { if (!this.loaded) { if (battle.battleType === BattleType.TRAINER) { battle.enemyParty[e] = battle.trainer.genPartyMember(e); } else { + LoggerTools.rarityslot[0] = e const enemySpecies = this.scene.randomSpecies(battle.waveIndex, level, true); battle.enemyParty[e] = this.scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.NONE, !!this.scene.getEncounterBossSegments(battle.waveIndex, level, enemySpecies)); if (this.scene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) { @@ -1172,6 +1203,7 @@ export class EncounterPhase extends BattlePhase { console.log(enemyPokemon.name, enemyPokemon.species.speciesId, enemyPokemon.stats); }); + console.log(LoggerTools.rarities) if (this.scene.getParty().filter(p => p.isShiny()).length === 6) { this.scene.validateAchv(achvs.SHINY_PARTY); @@ -1293,6 +1325,12 @@ export class EncounterPhase extends BattlePhase { const enemyField = this.scene.getEnemyField(); LoggerTools.logTeam(this.scene, this.scene.currentBattle.waveIndex) + if (this.scene.getEnemyParty()[0].hasTrainer()) { + LoggerTools.logTrainer(this.scene, this.scene.currentBattle.waveIndex) + } + if (this.scene.currentBattle.waveIndex == 1) { + LoggerTools.logPlayerTeam(this.scene) + } if (this.scene.currentBattle.battleType === BattleType.WILD) { enemyField.forEach(enemyPokemon => { From 63f9988cb8318bf5b0f982a7c43e86f82569cfb9 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Thu, 11 Jul 2024 21:06:23 -0400 Subject: [PATCH 34/94] Fix incorrect uploaded file --- src/events/arena.ts | 923 +++++--------------------------------------- src/field/arena.ts | 14 + 2 files changed, 102 insertions(+), 835 deletions(-) diff --git a/src/events/arena.ts b/src/events/arena.ts index 8658b35d756..67b423f3b75 100644 --- a/src/events/arena.ts +++ b/src/events/arena.ts @@ -1,850 +1,103 @@ -import BattleScene from "../battle-scene"; -import { BiomePoolTier, PokemonPools, BiomeTierTrainerPools, biomePokemonPools, biomeTrainerPools } from "../data/biomes"; -import { Constructor } from "#app/utils"; -import * as Utils from "../utils"; -import PokemonSpecies, { getPokemonSpecies } from "../data/pokemon-species"; -import { Weather, WeatherType, getTerrainClearMessage, getTerrainStartMessage, getWeatherClearMessage, getWeatherStartMessage } from "../data/weather"; -import { CommonAnimPhase } from "../phases"; -import { CommonAnim } from "../data/battle-anims"; -import { Type } from "../data/type"; -import Move from "../data/move"; -import { ArenaTag, ArenaTagSide, ArenaTrapTag, getArenaTag } from "../data/arena-tag"; -import { BattlerIndex } from "../battle"; -import { Terrain, TerrainType } from "../data/terrain"; -import { PostTerrainChangeAbAttr, PostWeatherChangeAbAttr, applyPostTerrainChangeAbAttrs, applyPostWeatherChangeAbAttrs } from "../data/ability"; -import Pokemon from "./pokemon"; -import * as Overrides from "../overrides"; -import { WeatherChangedEvent, TerrainChangedEvent, TagAddedEvent, TagRemovedEvent } from "../events/arena"; +import { ArenaTagSide } from "#app/data/arena-tag.js"; import { ArenaTagType } from "#enums/arena-tag-type"; -import { Biome } from "#enums/biome"; -import { Moves } from "#enums/moves"; -import { Species } from "#enums/species"; -import { TimeOfDay } from "#enums/time-of-day"; -import { TrainerType } from "#enums/trainer-type"; -import * as LoggerTools from "../logger" +import { TerrainType } from "#app/data/terrain.js"; +import { WeatherType } from "#app/data/weather.js"; -export class Arena { - public scene: BattleScene; - public biomeType: Biome; - public weather: Weather; - public terrain: Terrain; - public tags: ArenaTag[]; - public bgm: string; - public ignoreAbilities: boolean; +/** Alias for all {@linkcode ArenaEvent} type strings */ +export enum ArenaEventType { + /** Triggers when a {@linkcode WeatherType} is added, overlapped, or removed */ + WEATHER_CHANGED = "onWeatherChanged", + /** Triggers when a {@linkcode TerrainType} is added, overlapped, or removed */ + TERRAIN_CHANGED = "onTerrainChanged", - private lastTimeOfDay: TimeOfDay; + /** Triggers when a {@linkcode ArenaTagType} is added */ + TAG_ADDED = "onTagAdded", + /** Triggers when a {@linkcode ArenaTagType} is removed */ + TAG_REMOVED = "onTagRemoved", +} - public pokemonPool: PokemonPools; - private trainerPool: BiomeTierTrainerPools; +/** + * Base container class for all {@linkcode ArenaEventType} events + * @extends Event + */ +export class ArenaEvent extends Event { + /** The total duration of the {@linkcode ArenaEventType} */ + public duration: number; + constructor(eventType: ArenaEventType, duration: number) { + super(eventType); - public readonly eventTarget: EventTarget = new EventTarget(); - - constructor(scene: BattleScene, biome: Biome, bgm: string) { - this.scene = scene; - this.biomeType = biome; - this.tags = []; - this.bgm = bgm; - this.trainerPool = biomeTrainerPools[biome]; - this.updatePoolsForTimeOfDay(); + this.duration = duration; } +} +/** + * Container class for {@linkcode ArenaEventType.WEATHER_CHANGED} events + * @extends ArenaEvent +*/ +export class WeatherChangedEvent extends ArenaEvent { + /** The {@linkcode WeatherType} being overridden */ + public oldWeatherType: WeatherType; + /** The {@linkcode WeatherType} being set */ + public newWeatherType: WeatherType; + constructor(oldWeatherType: WeatherType, newWeatherType: WeatherType, duration: number) { + super(ArenaEventType.WEATHER_CHANGED, duration); - init() { - const biomeKey = getBiomeKey(this.biomeType); - - this.scene.arenaPlayer.setBiome(this.biomeType); - this.scene.arenaPlayerTransition.setBiome(this.biomeType); - this.scene.arenaEnemy.setBiome(this.biomeType); - this.scene.arenaNextEnemy.setBiome(this.biomeType); - this.scene.arenaBg.setTexture(`${biomeKey}_bg`); - this.scene.arenaBgTransition.setTexture(`${biomeKey}_bg`); - - // Redo this on initialise because during save/load the current wave isn't always - // set correctly during construction - this.updatePoolsForTimeOfDay(); + this.oldWeatherType = oldWeatherType; + this.newWeatherType = newWeatherType; } - - updatePoolsForTimeOfDay(): void { - const timeOfDay = this.getTimeOfDay(); - if (timeOfDay !== this.lastTimeOfDay) { - this.pokemonPool = {}; - for (const tier of Object.keys(biomePokemonPools[this.biomeType])) { - this.pokemonPool[tier] = Object.assign([], biomePokemonPools[this.biomeType][tier][TimeOfDay.ALL]).concat(biomePokemonPools[this.biomeType][tier][timeOfDay]); - } - this.lastTimeOfDay = timeOfDay; - } - } - - randomSpecies(waveIndex: integer, level: integer, attempt?: integer, luckValue?: integer): PokemonSpecies { - const overrideSpecies = this.scene.gameMode.getOverrideSpecies(waveIndex); - if (overrideSpecies) { - return overrideSpecies; - } - const isBoss = !!this.scene.getEncounterBossSegments(waveIndex, level) && !!this.pokemonPool[BiomePoolTier.BOSS].length - && (this.biomeType !== Biome.END || this.scene.gameMode.isClassic || this.scene.gameMode.isWaveFinal(waveIndex)); - const randVal = isBoss ? 64 : 512; - // luck influences encounter rarity - let luckModifier = 0; - if (typeof luckValue !== "undefined") { - luckModifier = luckValue * (isBoss ? 0.5 : 2); - } - const tierValue = Utils.randSeedInt(randVal - luckModifier); - let tier = !isBoss - ? tierValue >= 156 ? BiomePoolTier.COMMON : tierValue >= 32 ? BiomePoolTier.UNCOMMON : tierValue >= 6 ? BiomePoolTier.RARE : tierValue >= 1 ? BiomePoolTier.SUPER_RARE : BiomePoolTier.ULTRA_RARE - : tierValue >= 20 ? BiomePoolTier.BOSS : tierValue >= 6 ? BiomePoolTier.BOSS_RARE : tierValue >= 1 ? BiomePoolTier.BOSS_SUPER_RARE : BiomePoolTier.BOSS_ULTRA_RARE; - console.log(BiomePoolTier[tier]); - var tiernames = [ - "Common", - "Uncommon", - "Rare", - "Super Rare", - "Ultra Rare", - "Common", - "Rare", - "Super Rare", - "Ultra Rare", - ] - LoggerTools.rarities[LoggerTools.rarityslot[0]] = tiernames[tier] - console.log(tiernames[tier]) - while (!this.pokemonPool[tier].length) { - console.log(`Downgraded rarity tier from ${BiomePoolTier[tier]} to ${BiomePoolTier[tier - 1]}`); - tier--; - } - const tierPool = this.pokemonPool[tier]; - let ret: PokemonSpecies; - let regen = false; - if (!tierPool.length) { - ret = this.scene.randomSpecies(waveIndex, level); - } else { - const entry = tierPool[Utils.randSeedInt(tierPool.length)]; - let species: Species; - if (typeof entry === "number") { - species = entry as Species; - } else { - const levelThresholds = Object.keys(entry); - for (let l = levelThresholds.length - 1; l >= 0; l--) { - const levelThreshold = parseInt(levelThresholds[l]); - if (level >= levelThreshold) { - const speciesIds = entry[levelThreshold]; - if (speciesIds.length > 1) { - species = speciesIds[Utils.randSeedInt(speciesIds.length)]; - } else { - species = speciesIds[0]; - } - break; - } - } - } - - ret = getPokemonSpecies(species); - - if (ret.subLegendary || ret.legendary || ret.mythical) { - switch (true) { - case (ret.baseTotal >= 720): - regen = level < 90; - break; - case (ret.baseTotal >= 670): - regen = level < 70; - break; - case (ret.baseTotal >= 580): - regen = level < 50; - break; - default: - regen = level < 30; - break; - } - } - } - - if (regen && (attempt || 0) < 10) { - console.log("Incompatible level: regenerating..."); - return this.randomSpecies(waveIndex, level, (attempt || 0) + 1); - } - - const newSpeciesId = ret.getWildSpeciesForLevel(level, true, isBoss, this.scene.gameMode); - if (newSpeciesId !== ret.speciesId) { - console.log("Replaced", Species[ret.speciesId], "with", Species[newSpeciesId]); - ret = getPokemonSpecies(newSpeciesId); - } - return ret; - } - - randomTrainerType(waveIndex: integer): TrainerType { - const isBoss = !!this.trainerPool[BiomePoolTier.BOSS].length - && this.scene.gameMode.isTrainerBoss(waveIndex, this.biomeType, this.scene.offsetGym); - console.log(isBoss, this.trainerPool); - const tierValue = Utils.randSeedInt(!isBoss ? 512 : 64); - let tier = !isBoss - ? tierValue >= 156 ? BiomePoolTier.COMMON : tierValue >= 32 ? BiomePoolTier.UNCOMMON : tierValue >= 6 ? BiomePoolTier.RARE : tierValue >= 1 ? BiomePoolTier.SUPER_RARE : BiomePoolTier.ULTRA_RARE - : tierValue >= 20 ? BiomePoolTier.BOSS : tierValue >= 6 ? BiomePoolTier.BOSS_RARE : tierValue >= 1 ? BiomePoolTier.BOSS_SUPER_RARE : BiomePoolTier.BOSS_ULTRA_RARE; - console.log(BiomePoolTier[tier]); - while (tier && !this.trainerPool[tier].length) { - console.log(`Downgraded trainer rarity tier from ${BiomePoolTier[tier]} to ${BiomePoolTier[tier - 1]}`); - tier--; - } - const tierPool = this.trainerPool[tier] || []; - return !tierPool.length ? TrainerType.BREEDER : tierPool[Utils.randSeedInt(tierPool.length)]; - } - - getSpeciesFormIndex(species: PokemonSpecies): integer { - switch (species.speciesId) { - case Species.BURMY: - case Species.WORMADAM: - switch (this.biomeType) { - case Biome.BEACH: - return 1; - case Biome.SLUM: - return 2; - } - break; - case Species.ROTOM: - switch (this.biomeType) { - case Biome.VOLCANO: - return 1; - case Biome.SEA: - return 2; - case Biome.ICE_CAVE: - return 3; - case Biome.MOUNTAIN: - return 4; - case Biome.TALL_GRASS: - return 5; - } - break; - case Species.LYCANROC: - const timeOfDay = this.getTimeOfDay(); - switch (timeOfDay) { - case TimeOfDay.DAY: - case TimeOfDay.DAWN: - return 0; - case TimeOfDay.DUSK: - return 2; - case TimeOfDay.NIGHT: - return 1; - } - break; - } - - return 0; - } - - getTypeForBiome() { - switch (this.biomeType) { - case Biome.TOWN: - case Biome.PLAINS: - case Biome.METROPOLIS: - return Type.NORMAL; - case Biome.GRASS: - case Biome.TALL_GRASS: - return Type.GRASS; - case Biome.FOREST: - case Biome.JUNGLE: - return Type.BUG; - case Biome.SLUM: - case Biome.SWAMP: - return Type.POISON; - case Biome.SEA: - case Biome.BEACH: - case Biome.LAKE: - case Biome.SEABED: - return Type.WATER; - case Biome.MOUNTAIN: - return Type.FLYING; - case Biome.BADLANDS: - return Type.GROUND; - case Biome.CAVE: - case Biome.DESERT: - return Type.ROCK; - case Biome.ICE_CAVE: - case Biome.SNOWY_FOREST: - return Type.ICE; - case Biome.MEADOW: - case Biome.FAIRY_CAVE: - case Biome.ISLAND: - return Type.FAIRY; - case Biome.POWER_PLANT: - return Type.ELECTRIC; - case Biome.VOLCANO: - return Type.FIRE; - case Biome.GRAVEYARD: - case Biome.TEMPLE: - return Type.GHOST; - case Biome.DOJO: - case Biome.CONSTRUCTION_SITE: - return Type.FIGHTING; - case Biome.FACTORY: - case Biome.LABORATORY: - return Type.STEEL; - case Biome.RUINS: - case Biome.SPACE: - return Type.PSYCHIC; - case Biome.WASTELAND: - case Biome.END: - return Type.DRAGON; - case Biome.ABYSS: - return Type.DARK; - default: - return Type.UNKNOWN; - } - } - - getBgTerrainColorRatioForBiome(): number { - switch (this.biomeType) { - case Biome.SPACE: - return 1; - case Biome.END: - return 0; - } - - return 131 / 180; - } - - /** - * Sets weather to the override specified in overrides.ts - * @param weather new weather to set of type WeatherType - * @returns true to force trySetWeather to return true - */ - trySetWeatherOverride(weather: WeatherType): boolean { - this.weather = new Weather(weather, 0); - this.scene.unshiftPhase(new CommonAnimPhase(this.scene, undefined, undefined, CommonAnim.SUNNY + (weather - 1))); - this.scene.queueMessage(getWeatherStartMessage(weather)); - return true; - } - - /** - * Attempts to set a new weather to the battle - * @param weather new weather to set of type WeatherType - * @param hasPokemonSource is the new weather from a pokemon - * @returns true if new weather set, false if no weather provided or attempting to set the same weather as currently in use - */ - trySetWeather(weather: WeatherType, hasPokemonSource: boolean): boolean { - if (Overrides.WEATHER_OVERRIDE) { - return this.trySetWeatherOverride(Overrides.WEATHER_OVERRIDE); - } - - if (this.weather?.weatherType === (weather || undefined)) { - return false; - } - - const oldWeatherType = this.weather?.weatherType || WeatherType.NONE; - - this.weather = weather ? new Weather(weather, hasPokemonSource ? 5 : 0) : null; - this.eventTarget.dispatchEvent(new WeatherChangedEvent(oldWeatherType, this.weather?.weatherType, this.weather?.turnsLeft)); - - if (this.weather) { - this.scene.unshiftPhase(new CommonAnimPhase(this.scene, undefined, undefined, CommonAnim.SUNNY + (weather - 1))); - this.scene.queueMessage(getWeatherStartMessage(weather)); - } else { - this.scene.queueMessage(getWeatherClearMessage(oldWeatherType)); - } - - this.scene.getField(true).filter(p => p.isOnField()).map(pokemon => { - pokemon.findAndRemoveTags(t => "weatherTypes" in t && !(t.weatherTypes as WeatherType[]).find(t => t === weather)); - applyPostWeatherChangeAbAttrs(PostWeatherChangeAbAttr, pokemon, weather); - }); - - return true; - } - - trySetTerrain(terrain: TerrainType, hasPokemonSource: boolean, ignoreAnim: boolean = false): boolean { - if (this.terrain?.terrainType === (terrain || undefined)) { - return false; - } - - const oldTerrainType = this.terrain?.terrainType || TerrainType.NONE; - - this.terrain = terrain ? new Terrain(terrain, hasPokemonSource ? 5 : 0) : null; - this.eventTarget.dispatchEvent(new TerrainChangedEvent(oldTerrainType,this.terrain?.terrainType, this.terrain?.turnsLeft)); - - if (this.terrain) { - if (!ignoreAnim) { - this.scene.unshiftPhase(new CommonAnimPhase(this.scene, undefined, undefined, CommonAnim.MISTY_TERRAIN + (terrain - 1))); - } - this.scene.queueMessage(getTerrainStartMessage(terrain)); - } else { - this.scene.queueMessage(getTerrainClearMessage(oldTerrainType)); - } - - this.scene.getField(true).filter(p => p.isOnField()).map(pokemon => { - pokemon.findAndRemoveTags(t => "terrainTypes" in t && !(t.terrainTypes as TerrainType[]).find(t => t === terrain)); - applyPostTerrainChangeAbAttrs(PostTerrainChangeAbAttr, pokemon, terrain); - }); - - return true; - } - - isMoveWeatherCancelled(move: Move) { - return this.weather && !this.weather.isEffectSuppressed(this.scene) && this.weather.isMoveWeatherCancelled(move); - } - - isMoveTerrainCancelled(user: Pokemon, targets: BattlerIndex[], move: Move) { - return this.terrain && this.terrain.isMoveTerrainCancelled(user, targets, move); - } - - getTerrainType() : TerrainType { - return this.terrain?.terrainType || TerrainType.NONE; - } - - getAttackTypeMultiplier(attackType: Type, grounded: boolean): number { - let weatherMultiplier = 1; - if (this.weather && !this.weather.isEffectSuppressed(this.scene)) { - weatherMultiplier = this.weather.getAttackTypeMultiplier(attackType); - } - - let terrainMultiplier = 1; - if (this.terrain && grounded) { - terrainMultiplier = this.terrain.getAttackTypeMultiplier(attackType); - } - - return weatherMultiplier * terrainMultiplier; - } - - /** - * Gets the denominator for the chance for a trainer spawn - * @returns n where 1/n is the chance of a trainer battle - */ - getTrainerChance(): integer { - switch (this.biomeType) { - case Biome.METROPOLIS: - return 2; - case Biome.SLUM: - case Biome.BEACH: - case Biome.DOJO: - case Biome.CONSTRUCTION_SITE: - return 4; - case Biome.PLAINS: - case Biome.GRASS: - case Biome.LAKE: - case Biome.CAVE: - return 6; - case Biome.TALL_GRASS: - case Biome.FOREST: - case Biome.SEA: - case Biome.SWAMP: - case Biome.MOUNTAIN: - case Biome.BADLANDS: - case Biome.DESERT: - case Biome.MEADOW: - case Biome.POWER_PLANT: - case Biome.GRAVEYARD: - case Biome.FACTORY: - case Biome.SNOWY_FOREST: - return 8; - case Biome.ICE_CAVE: - case Biome.VOLCANO: - case Biome.RUINS: - case Biome.WASTELAND: - case Biome.JUNGLE: - case Biome.FAIRY_CAVE: - return 12; - case Biome.SEABED: - case Biome.ABYSS: - case Biome.SPACE: - case Biome.TEMPLE: - return 16; - default: - return 0; - } - } - - getTimeOfDay(): TimeOfDay { - switch (this.biomeType) { - case Biome.ABYSS: - return TimeOfDay.NIGHT; - } - - const waveCycle = ((this.scene.currentBattle?.waveIndex || 0) + this.scene.waveCycleOffset) % 40; - - if (waveCycle < 15) { - return TimeOfDay.DAY; - } - - if (waveCycle < 20) { - return TimeOfDay.DUSK; - } - - if (waveCycle < 35) { - return TimeOfDay.NIGHT; - } - - return TimeOfDay.DAWN; - } - - isOutside(): boolean { - switch (this.biomeType) { - case Biome.SEABED: - case Biome.CAVE: - case Biome.ICE_CAVE: - case Biome.POWER_PLANT: - case Biome.DOJO: - case Biome.FACTORY: - case Biome.ABYSS: - case Biome.FAIRY_CAVE: - case Biome.TEMPLE: - case Biome.LABORATORY: - return false; - default: - return true; - } - } - - overrideTint(): [integer, integer, integer] { - switch (Overrides.ARENA_TINT_OVERRIDE) { - case TimeOfDay.DUSK: - return [ 98, 48, 73 ].map(c => Math.round((c + 128) / 2)) as [integer, integer, integer]; - break; - case (TimeOfDay.NIGHT): - return [ 64, 64, 64 ]; - break; - case TimeOfDay.DAWN: - case TimeOfDay.DAY: - default: - return [ 128, 128, 128 ]; - break; - } - } - - getDayTint(): [integer, integer, integer] { - if (Overrides.ARENA_TINT_OVERRIDE !== null) { - return this.overrideTint(); - } - switch (this.biomeType) { - case Biome.ABYSS: - return [ 64, 64, 64 ]; - default: - return [ 128, 128, 128 ]; - } - } - - getDuskTint(): [integer, integer, integer] { - if (Overrides.ARENA_TINT_OVERRIDE) { - return this.overrideTint(); - } - if (!this.isOutside()) { - return [ 0, 0, 0 ]; - } - - switch (this.biomeType) { - default: - return [ 98, 48, 73 ].map(c => Math.round((c + 128) / 2)) as [integer, integer, integer]; - } - } - - getNightTint(): [integer, integer, integer] { - if (Overrides.ARENA_TINT_OVERRIDE) { - return this.overrideTint(); - } - switch (this.biomeType) { - case Biome.ABYSS: - case Biome.SPACE: - case Biome.END: - return this.getDayTint(); - } - - if (!this.isOutside()) { - return [ 64, 64, 64 ]; - } - - switch (this.biomeType) { - default: - return [ 48, 48, 98 ]; - } - } - - setIgnoreAbilities(ignoreAbilities: boolean = true): void { - this.ignoreAbilities = ignoreAbilities; - } - - applyTagsForSide(tagType: ArenaTagType | Constructor, side: ArenaTagSide, ...args: unknown[]): void { - let tags = typeof tagType === "string" - ? this.tags.filter(t => t.tagType === tagType) - : this.tags.filter(t => t instanceof tagType); - if (side !== ArenaTagSide.BOTH) { - tags = tags.filter(t => t.side === side); - } - tags.forEach(t => t.apply(this, args)); - } - - applyTags(tagType: ArenaTagType | Constructor, ...args: unknown[]): void { - this.applyTagsForSide(tagType, ArenaTagSide.BOTH, ...args); - } - - addTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves, sourceId: integer, side: ArenaTagSide = ArenaTagSide.BOTH, quiet: boolean = false, targetIndex?: BattlerIndex): boolean { - const existingTag = this.getTagOnSide(tagType, side); - if (existingTag) { - existingTag.onOverlap(this); - - if (existingTag instanceof ArenaTrapTag) { - const { tagType, side, turnCount, layers, maxLayers } = existingTag as ArenaTrapTag; - this.eventTarget.dispatchEvent(new TagAddedEvent(tagType, side, turnCount, layers, maxLayers)); - } - - return false; - } - - const newTag = getArenaTag(tagType, turnCount || 0, sourceMove, sourceId, targetIndex, side); - this.tags.push(newTag); - newTag.onAdd(this, quiet); - - const { layers = 0, maxLayers = 0 } = newTag instanceof ArenaTrapTag ? newTag : {}; - - this.eventTarget.dispatchEvent(new TagAddedEvent(newTag.tagType, newTag.side, newTag.turnCount, layers, maxLayers)); - - return true; - } - - getTag(tagType: ArenaTagType | Constructor): ArenaTag { - return this.getTagOnSide(tagType, ArenaTagSide.BOTH); - } - - getTagOnSide(tagType: ArenaTagType | Constructor, side: ArenaTagSide): ArenaTag { - return typeof(tagType) === "string" - ? this.tags.find(t => t.tagType === tagType && (side === ArenaTagSide.BOTH || t.side === ArenaTagSide.BOTH || t.side === side)) - : this.tags.find(t => t instanceof tagType && (side === ArenaTagSide.BOTH || t.side === ArenaTagSide.BOTH || t.side === side)); - } - - findTags(tagPredicate: (t: ArenaTag) => boolean): ArenaTag[] { - return this.findTagsOnSide(tagPredicate, ArenaTagSide.BOTH); - } - - findTagsOnSide(tagPredicate: (t: ArenaTag) => boolean, side: ArenaTagSide): ArenaTag[] { - return this.tags.filter(t => tagPredicate(t) && (side === ArenaTagSide.BOTH || t.side === ArenaTagSide.BOTH || t.side === side)); - } - - lapseTags(): void { - this.tags.filter(t => !(t.lapse(this))).forEach(t => { - t.onRemove(this); - this.tags.splice(this.tags.indexOf(t), 1); - - this.eventTarget.dispatchEvent(new TagRemovedEvent(t.tagType, t.side, t.turnCount)); - }); - } - - removeTag(tagType: ArenaTagType): boolean { - const tags = this.tags; - const tag = tags.find(t => t.tagType === tagType); - if (tag) { - tag.onRemove(this); - tags.splice(tags.indexOf(tag), 1); - - this.eventTarget.dispatchEvent(new TagRemovedEvent(tag.tagType, tag.side, tag.turnCount)); - } - return !!tag; - } - - removeTagOnSide(tagType: ArenaTagType, side: ArenaTagSide, quiet: boolean = false): boolean { - const tag = this.getTagOnSide(tagType, side); - if (tag) { - tag.onRemove(this, quiet); - this.tags.splice(this.tags.indexOf(tag), 1); - - this.eventTarget.dispatchEvent(new TagRemovedEvent(tag.tagType, tag.side, tag.turnCount)); - } - return !!tag; - } - - - removeAllTags(): void { - while (this.tags.length) { - this.tags[0].onRemove(this); - this.eventTarget.dispatchEvent(new TagRemovedEvent(this.tags[0].tagType, this.tags[0].side, this.tags[0].turnCount)); - - this.tags.splice(0, 1); - } - } - - preloadBgm(): void { - this.scene.loadBgm(this.bgm); - } - - getBgmLoopPoint(): number { - switch (this.biomeType) { - case Biome.TOWN: - return 7.288; - case Biome.PLAINS: - return 7.693; - case Biome.GRASS: - return 1.995; - case Biome.TALL_GRASS: - return 9.608; - case Biome.METROPOLIS: - return 141.470; - case Biome.FOREST: - return 4.294; - case Biome.SEA: - return 1.672; - case Biome.SWAMP: - return 4.461; - case Biome.BEACH: - return 3.462; - case Biome.LAKE: - return 5.350; - case Biome.SEABED: - return 2.600; - case Biome.MOUNTAIN: - return 4.018; - case Biome.BADLANDS: - return 17.790; - case Biome.CAVE: - return 14.240; - case Biome.DESERT: - return 1.143; - case Biome.ICE_CAVE: - return 15.010; - case Biome.MEADOW: - return 3.891; - case Biome.POWER_PLANT: - return 2.810; - case Biome.VOLCANO: - return 5.116; - case Biome.GRAVEYARD: - return 3.232; - case Biome.DOJO: - return 6.205; - case Biome.FACTORY: - return 4.985; - case Biome.RUINS: - return 2.270; - case Biome.WASTELAND: - return 6.336; - case Biome.ABYSS: - return 5.130; - case Biome.SPACE: - return 20.036; - case Biome.CONSTRUCTION_SITE: - return 1.222; - case Biome.JUNGLE: - return 0.000; - case Biome.FAIRY_CAVE: - return 4.542; - case Biome.TEMPLE: - return 2.547; - case Biome.ISLAND: - return 2.751; - case Biome.LABORATORY: - return 114.862; - case Biome.SLUM: - return 1.221; - case Biome.SNOWY_FOREST: - return 3.047; - } +} +/** + * Container class for {@linkcode ArenaEventType.TERRAIN_CHANGED} events + * @extends ArenaEvent +*/ +export class TerrainChangedEvent extends ArenaEvent { + /** The {@linkcode TerrainType} being overridden */ + public oldTerrainType: TerrainType; + /** The {@linkcode TerrainType} being set */ + public newTerrainType: TerrainType; + constructor(oldTerrainType: TerrainType, newTerrainType: TerrainType, duration: number) { + super(ArenaEventType.TERRAIN_CHANGED, duration); + + this.oldTerrainType = oldTerrainType; + this.newTerrainType = newTerrainType; } } -export function getBiomeKey(biome: Biome): string { - return Biome[biome].toLowerCase(); -} +/** + * Container class for {@linkcode ArenaEventType.TAG_ADDED} events + * @extends ArenaEvent +*/ +export class TagAddedEvent extends ArenaEvent { + /** The {@linkcode ArenaTagType} being added */ + public arenaTagType: ArenaTagType; + /** The {@linkcode ArenaTagSide} the tag is being placed on */ + public arenaTagSide: ArenaTagSide; + /** The current number of layers of the arena trap. */ + public arenaTagLayers: number; + /** The maximum amount of layers of the arena trap. */ + public arenaTagMaxLayers: number; -export function getBiomeHasProps(biomeType: Biome): boolean { - switch (biomeType) { - case Biome.METROPOLIS: - case Biome.BEACH: - case Biome.LAKE: - case Biome.SEABED: - case Biome.MOUNTAIN: - case Biome.BADLANDS: - case Biome.CAVE: - case Biome.DESERT: - case Biome.ICE_CAVE: - case Biome.MEADOW: - case Biome.POWER_PLANT: - case Biome.VOLCANO: - case Biome.GRAVEYARD: - case Biome.FACTORY: - case Biome.RUINS: - case Biome.WASTELAND: - case Biome.ABYSS: - case Biome.CONSTRUCTION_SITE: - case Biome.JUNGLE: - case Biome.FAIRY_CAVE: - case Biome.TEMPLE: - case Biome.SNOWY_FOREST: - case Biome.ISLAND: - case Biome.LABORATORY: - case Biome.END: - return true; - } + constructor(arenaTagType: ArenaTagType, arenaTagSide: ArenaTagSide, duration: number, arenaTagLayers?: number, arenaTagMaxLayers?: number) { + super(ArenaEventType.TAG_ADDED, duration); - return false; -} - -export class ArenaBase extends Phaser.GameObjects.Container { - public player: boolean; - public biome: Biome; - public propValue: integer; - public base: Phaser.GameObjects.Sprite; - public props: Phaser.GameObjects.Sprite[]; - - constructor(scene: BattleScene, player: boolean) { - super(scene, 0, 0); - - this.player = player; - - this.base = scene.addFieldSprite(0, 0, "plains_a", null, 1); - this.base.setOrigin(0, 0); - - this.props = !player ? - new Array(3).fill(null).map(() => { - const ret = scene.addFieldSprite(0, 0, "plains_b", null, 1); - ret.setOrigin(0, 0); - ret.setVisible(false); - return ret; - }) : []; - } - - setBiome(biome: Biome, propValue?: integer): void { - const hasProps = getBiomeHasProps(biome); - const biomeKey = getBiomeKey(biome); - const baseKey = `${biomeKey}_${this.player ? "a" : "b"}`; - - if (biome !== this.biome) { - this.base.setTexture(baseKey); - - if (this.base.texture.frameTotal > 1) { - const baseFrameNames = this.scene.anims.generateFrameNames(baseKey, { zeroPad: 4, suffix: ".png", start: 1, end: this.base.texture.frameTotal - 1 }); - if (!(this.scene.anims.exists(baseKey))) { - this.scene.anims.create({ - key: baseKey, - frames: baseFrameNames, - frameRate: 12, - repeat: -1 - }); - } - this.base.play(baseKey); - } else { - this.base.stop(); - } - - this.add(this.base); - } - - if (!this.player) { - (this.scene as BattleScene).executeWithSeedOffset(() => { - this.propValue = propValue === undefined - ? hasProps ? Utils.randSeedInt(8) : 0 - : propValue; - this.props.forEach((prop, p) => { - const propKey = `${biomeKey}_b${hasProps ? `_${p + 1}` : ""}`; - prop.setTexture(propKey); - - if (hasProps && prop.texture.frameTotal > 1) { - const propFrameNames = this.scene.anims.generateFrameNames(propKey, { zeroPad: 4, suffix: ".png", start: 1, end: prop.texture.frameTotal - 1 }); - if (!(this.scene.anims.exists(propKey))) { - this.scene.anims.create({ - key: propKey, - frames: propFrameNames, - frameRate: 12, - repeat: -1 - }); - } - prop.play(propKey); - } else { - prop.stop(); - } - - prop.setVisible(hasProps && !!(this.propValue & (1 << p))); - this.add(prop); - }); - }, (this.scene as BattleScene).currentBattle?.waveIndex || 0, (this.scene as BattleScene).waveSeed); - } + this.arenaTagType = arenaTagType; + this.arenaTagSide = arenaTagSide; + this.arenaTagLayers = arenaTagLayers; + this.arenaTagMaxLayers = arenaTagMaxLayers; + } +} +/** + * Container class for {@linkcode ArenaEventType.TAG_REMOVED} events + * @extends ArenaEvent +*/ +export class TagRemovedEvent extends ArenaEvent { + /** The {@linkcode ArenaTagType} being removed */ + public arenaTagType: ArenaTagType; + /** The {@linkcode ArenaTagSide} the tag was being placed on */ + public arenaTagSide: ArenaTagSide; + constructor(arenaTagType: ArenaTagType, arenaTagSide: ArenaTagSide, duration: number) { + super(ArenaEventType.TAG_REMOVED, duration); + + this.arenaTagType = arenaTagType; + this.arenaTagSide = arenaTagSide; } } diff --git a/src/field/arena.ts b/src/field/arena.ts index 51dba1089e9..8658b35d756 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -21,6 +21,7 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { TimeOfDay } from "#enums/time-of-day"; import { TrainerType } from "#enums/trainer-type"; +import * as LoggerTools from "../logger" export class Arena { public scene: BattleScene; @@ -91,6 +92,19 @@ export class Arena { ? tierValue >= 156 ? BiomePoolTier.COMMON : tierValue >= 32 ? BiomePoolTier.UNCOMMON : tierValue >= 6 ? BiomePoolTier.RARE : tierValue >= 1 ? BiomePoolTier.SUPER_RARE : BiomePoolTier.ULTRA_RARE : tierValue >= 20 ? BiomePoolTier.BOSS : tierValue >= 6 ? BiomePoolTier.BOSS_RARE : tierValue >= 1 ? BiomePoolTier.BOSS_SUPER_RARE : BiomePoolTier.BOSS_ULTRA_RARE; console.log(BiomePoolTier[tier]); + var tiernames = [ + "Common", + "Uncommon", + "Rare", + "Super Rare", + "Ultra Rare", + "Common", + "Rare", + "Super Rare", + "Ultra Rare", + ] + LoggerTools.rarities[LoggerTools.rarityslot[0]] = tiernames[tier] + console.log(tiernames[tier]) while (!this.pokemonPool[tier].length) { console.log(`Downgraded rarity tier from ${BiomePoolTier[tier]} to ${BiomePoolTier[tier - 1]}`); tier--; From 8c12cb54832d26f0c5d34bd9d23b7003b45e8400 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Thu, 11 Jul 2024 23:14:13 -0400 Subject: [PATCH 35/94] Logger updates Log trainers Log Pokemon correctly Log moves / actions --- src/logger.ts | 107 +++++++++++++++++++++++++++++++----------------- src/phases.ts | 110 ++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 175 insertions(+), 42 deletions(-) diff --git a/src/logger.ts b/src/logger.ts index c93bc31cf20..d72dd36cb36 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -105,6 +105,8 @@ export interface ItemData { quantity: integer, } +export const Actions = [] + export function newDocument(name: string = "Untitled Run", authorName: string | string[] = "Write your name here"): DRPD { return { version: DRPD_Version, @@ -191,6 +193,20 @@ export function exportWave(scene: BattleScene): Wave { return ret; } export function exportTrainer(trainer: Trainer): TrainerData { + if (trainer.config.getTitle(0, trainer.variant) == "Finn") { + return { + id: trainer.config.trainerType, + name: "Finn", + type: "Rival" + } + } + if (trainer.config.getTitle(0, trainer.variant) == "Ivy") { + return { + id: trainer.config.trainerType, + name: "Ivy", + type: "Rival" + } + } return { id: trainer.config.trainerType, name: trainer.name, @@ -213,7 +229,7 @@ export function getSize(str: string) { export function generateOption(i: integer): OptionSelectItem { var filename: string = (JSON.parse(localStorage.getItem(logs[i][1])) as DRPD).title var op: OptionSelectItem = { - label: ` Export ${filename} (${getSize(localStorage.getItem(logs[i][1]))})`, + label: ` Export ${filename} (${getSize(printDRPD("", "", JSON.parse(localStorage.getItem(logs[i][1])) as DRPD))})`, handler: () => { downloadLogByID(i) return false; @@ -266,7 +282,7 @@ export function clearLog(keyword: string) { */ export function downloadLog(keyword: string) { var d = JSON.parse(localStorage.getItem(logs[logKeys.indexOf(keyword)][1])) - const blob = new Blob([ JSON.stringify(d) ], {type: "text/json"}); + const blob = new Blob([ printDRPD("", "", d as DRPD) ], {type: "text/json"}); const link = document.createElement("a"); link.href = window.URL.createObjectURL(blob); var date: string = (d as DRPD).date @@ -303,6 +319,14 @@ export function logTeam(scene: BattleScene, floor: integer = undefined) { } } } +export function logActions(scene: BattleScene, floor: integer, action: string) { + if (localStorage.getItem("drpd") == null) localStorage.setItem("drpd", JSON.stringify(newDocument())) + var drpd: DRPD = JSON.parse(localStorage.getItem("drpd")) as DRPD; + var wv: Wave = getWave(drpd, floor, scene) + wv.actions.push(action) + console.log(drpd) + localStorage.setItem("drpd", JSON.stringify(drpd)) +} export function getWave(drpd: DRPD, floor: integer, scene: BattleScene) { var wv: Wave; var insertPos: integer; @@ -335,6 +359,18 @@ export function getWave(drpd: DRPD, floor: integer, scene: BattleScene) { if (b == undefined) return -1; // empty values move to the bottom return a.id - b.id }) + for (var i = 0; i < drpd.waves.length - 1; i++) { + if (drpd.waves[i] != undefined && drpd.waves[i+1] != undefined) { + if (drpd.waves[i].id == drpd.waves[i+1].id) { + drpd.waves[i] = undefined + drpd.waves.sort((a, b) => { + if (a == undefined) return 1; // empty values move to the bottom + if (b == undefined) return -1; // empty values move to the bottom + return a.id - b.id + }) + } + } + } if (wv == undefined) { console.error("Out of wave slots??") scene.ui.showText("Out of wave slots!\nClearing duplicates...", null, () => { @@ -394,26 +430,6 @@ export function getWave(drpd: DRPD, floor: integer, scene: BattleScene) { } return wv; } -/* -const entry = tierPool[Utils.randSeedInt(tierPool.length)]; -let species: Species; -if (typeof entry === "number") { - species = entry as Species; -} else { - const levelThresholds = Object.keys(entry); - for (let l = levelThresholds.length - 1; l >= 0; l--) { - const levelThreshold = parseInt(levelThresholds[l]); - if (level >= levelThreshold) { - const speciesIds = entry[levelThreshold]; - if (speciesIds.length > 1) { - species = speciesIds[Utils.randSeedInt(speciesIds.length)]; - } else { - species = speciesIds[0]; - } - break; - } - } -}*/ function checkForPokeInBiome(species: Species, pool: (Species | SpeciesTree)[]): boolean { //console.log(species, pool) for (var i = 0; i < pool.length; i++) { @@ -512,16 +528,25 @@ export function logPokemon(scene: BattleScene, floor: integer = undefined, slot: "Ultra Rare", ] for (var i = 0; i < tiernames.length; i++) { + if (wv.pokemon[slot] != undefined) if (checkForPokeInBiome(wv.pokemon[slot].id, scene.arena.pokemonPool[i]) == true) { console.log("Autofilled rarity for " + pk.name + " as " + tiernames[i]) pk.rarity = tiernames[i] } } } + if (pk.rarity == undefined) pk.rarity = "[Unknown]" wv.pokemon[slot] = pk; console.log(drpd) localStorage.setItem("drpd", JSON.stringify(drpd)) } +export function resetWave(scene: BattleScene, floor: integer = undefined) { + if (localStorage.getItem("drpd") == null) localStorage.setItem("drpd", JSON.stringify(newDocument())) + var drpd: DRPD = JSON.parse(localStorage.getItem("drpd")) as DRPD; + drpd.waves[floor] = exportWave(scene) + console.log(drpd) + localStorage.setItem("drpd", JSON.stringify(drpd)) +} export function logTrainer(scene: BattleScene, floor: integer = undefined) { if (localStorage.getItem("drpd") == null) localStorage.setItem("drpd", JSON.stringify(newDocument())) var drpd: DRPD = JSON.parse(localStorage.getItem("drpd")) as DRPD; @@ -650,25 +675,31 @@ function printWave(inData: string, indent: string, wave: Wave): string { inData += ",\n" + indent + " \"reload\": " + wave.reload + "" inData += ",\n" + indent + " \"type\": \"" + wave.type + "\"" inData += ",\n" + indent + " \"double\": " + wave.double + "" - inData += ",\n" + indent + " \"actions\": [\n" var isFirst = true - for (var i = 0; i < wave.actions.length; i++) { - if (wave.actions[i] != undefined) { - if (isFirst) { - isFirst = false; - } else { - inData += "," + if (wave.actions.length > 0) { + inData += ",\n" + indent + " \"actions\": [" + for (var i = 0; i < wave.actions.length; i++) { + if (wave.actions[i] != undefined) { + if (isFirst) { + isFirst = false; + } else { + inData += "," + } + inData += "\n " + indent + "\"" + wave.actions[i] + "\"" } - inData += "\n " + indent + "\"" + wave.actions[i] + "\"" } + if (!isFirst) inData += "\n" + inData += indent + " ]" + } else { + inData += ",\n" + indent + " \"actions\": []" } - if (!isFirst) inData += "\n" - inData += indent + " ]" inData += ",\n " + indent + "\"shop\": \"" + wave.shop + "\"" inData += ",\n " + indent + "\"biome\": \"" + wave.biome + "\"" - if (wave.trainer) - inData += ",\n " + indent + "\"trainer\": " + wave.trainer - if (wave.pokemon) { + if (wave.trainer) { + inData += ",\n " + indent + "\"trainer\": " + inData = printTrainer(inData, indent + " ", wave.trainer) + } + if (wave.pokemon.length > 0) { inData += ",\n " + indent + "\"pokemon\": [\n" isFirst = true for (var i = 0; i < wave.pokemon.length; i++) { @@ -681,12 +712,12 @@ function printWave(inData: string, indent: string, wave: Wave): string { inData = printPoke(inData, indent + " ", wave.pokemon[i]) } } + inData += "\n" + indent + " ]" } - inData += "\n" + indent + " ]\n" + indent + "}" + inData += "\n" + indent + "}" return inData; } function printPoke(inData: string, indent: string, pokemon: PokeData) { - var itemdata: string = "" inData += indent + "{" inData += "\n" + indent + " \"id\": " + pokemon.id inData += ",\n" + indent + " \"name\": \"" + pokemon.name + "\"" @@ -743,7 +774,7 @@ function printIV(inData: string, indent: string, iv: IVData) { return inData; } function printTrainer(inData: string, indent: string, trainer: TrainerData) { - inData += indent + "{" + inData += "{" inData += "\n" + indent + " \"id\": \"" + trainer.id + "\"" inData += ",\n" + indent + " \"name\": \"" + trainer.name + "\"" inData += ",\n" + indent + " \"type\": \"" + trainer.type + "\"" diff --git a/src/phases.ts b/src/phases.ts index f81791ccef8..5e17e68c6a3 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -26,7 +26,7 @@ import { Gender } from "./data/gender"; import { Weather, WeatherType, getRandomWeatherType, getTerrainBlockMessage, getWeatherDamageMessage, getWeatherLapseMessage } from "./data/weather"; import { TempBattleStat } from "./data/temp-battle-stat"; import { ArenaTagSide, ArenaTrapTag, MistTag, TrickRoomTag } from "./data/arena-tag"; -import { CheckTrappedAbAttr, IgnoreOpponentStatChangesAbAttr, IgnoreOpponentEvasionAbAttr, PostAttackAbAttr, PostBattleAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreSwitchOutAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RedirectMoveAbAttr, BlockRedirectAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostBattleAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreSwitchOutAbAttrs, applyPreWeatherEffectAbAttrs, BattleStatMultiplierAbAttr, applyBattleStatMultiplierAbAttrs, IncrementMovePriorityAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr, BlockNonDirectDamageAbAttr as BlockNonDirectDamageAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, PostBiomeChangeAbAttr, applyPostFaintAbAttrs, PostFaintAbAttr, IncreasePpAbAttr, PostStatChangeAbAttr, applyPostStatChangeAbAttrs, AlwaysHitAbAttr, PreventBerryUseAbAttr, StatChangeCopyAbAttr, PokemonTypeChangeAbAttr, applyPreAttackAbAttrs, applyPostMoveUsedAbAttrs, PostMoveUsedAbAttr, MaxMultiHitAbAttr, HealFromBerryUseAbAttr, WonderSkinAbAttr, applyPreDefendAbAttrs, IgnoreMoveEffectsAbAttr, BlockStatusDamageAbAttr, BypassSpeedChanceAbAttr, AddSecondStrikeAbAttr } from "./data/ability"; +import { CheckTrappedAbAttr, IgnoreOpponentStatChangesAbAttr, IgnoreOpponentEvasionAbAttr, PostAttackAbAttr, PostBattleAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreSwitchOutAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RedirectMoveAbAttr, BlockRedirectAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostBattleAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreSwitchOutAbAttrs, applyPreWeatherEffectAbAttrs, BattleStatMultiplierAbAttr, applyBattleStatMultiplierAbAttrs, IncrementMovePriorityAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr, BlockNonDirectDamageAbAttr as BlockNonDirectDamageAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, PostBiomeChangeAbAttr, applyPostFaintAbAttrs, PostFaintAbAttr, IncreasePpAbAttr, PostStatChangeAbAttr, applyPostStatChangeAbAttrs, AlwaysHitAbAttr, PreventBerryUseAbAttr, StatChangeCopyAbAttr, PokemonTypeChangeAbAttr, applyPreAttackAbAttrs, applyPostMoveUsedAbAttrs, PostMoveUsedAbAttr, MaxMultiHitAbAttr, HealFromBerryUseAbAttr, WonderSkinAbAttr, applyPreDefendAbAttrs, IgnoreMoveEffectsAbAttr, BlockStatusDamageAbAttr, BypassSpeedChanceAbAttr, AddSecondStrikeAbAttr, BattlerTagImmunityAbAttr } from "./data/ability"; import { Unlockables, getUnlockableName } from "./system/unlockables"; import { getBiomeKey } from "./field/arena"; import { BattleType, BattlerIndex, TurnCommand } from "./battle"; @@ -465,7 +465,7 @@ export class TitlePhase extends Phase { // 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 lastsaves = this.getSaves(false, true); - if (lastsaves != undefined) { + if (lastsaves != undefined && lastsaves.length > 0) { lastsaves.forEach(lastsave => { options.push({ label: (lastsave.description ? lastsave.description : "[???]"), @@ -1324,6 +1324,7 @@ export class EncounterPhase extends BattlePhase { doEncounterCommon(showEncounterMessage: boolean = true) { const enemyField = this.scene.getEnemyField(); + LoggerTools.resetWave(this.scene, this.scene.currentBattle.waveIndex) LoggerTools.logTeam(this.scene, this.scene.currentBattle.waveIndex) if (this.scene.getEnemyParty()[0].hasTrainer()) { LoggerTools.logTrainer(this.scene, this.scene.currentBattle.waveIndex) @@ -2683,6 +2684,34 @@ export class TurnStartPhase extends FieldPhase { super(scene); } + generateTargString(t: BattlerIndex[]) { + var targets = ['Self'] + for (var i = 0; i < this.scene.getField().length; i++) { + if (this.scene.getField()[i] != null) + targets[this.scene.getField()[i].getBattlerIndex() + 1] = this.scene.getField()[i].name + } + for (var i = 0; i < this.scene.getEnemyField().length; i++) { + if (this.scene.getEnemyField()[i] != null) + targets[this.scene.getEnemyField()[i].getBattlerIndex() + 1] = this.scene.getEnemyField()[i].name + } + var targetFull = [] + for (var i = 0; i < t.length; i++) { + targetFull.push(targets[t[i] + 1]) + } + if (targetFull.join(", ") == targets.join(", ")) return "" + return " → " + targetFull.join(", ") + } + + getBattlers(user: Pokemon): Pokemon[] { + var battlers = [] + battlers[0] = this.scene.getField()[0] + battlers[1] = this.scene.getField()[1] + battlers[2] = this.scene.getEnemyField()[0] + battlers[3] = this.scene.getEnemyField()[1] + battlers.unshift(user) + return battlers; + } + start() { super.start(); @@ -2691,6 +2720,70 @@ export class TurnStartPhase extends FieldPhase { const battlerBypassSpeed = {}; + const playerActions = [] + + const moveOrder = order.slice(0); + + while (LoggerTools.Actions.length > 0) { + LoggerTools.Actions.pop() + } + + for (const o of moveOrder) { + + const pokemon = field[o]; + const turnCommand = this.scene.currentBattle.turnCommands[o]; + + if (turnCommand.skip || !pokemon.isPlayer()) { + continue; + } + + switch (turnCommand.command) { + case Command.FIGHT: + const queuedMove = turnCommand.move; + if (!queuedMove) { + continue; + } + const move = pokemon.getMoveset().find(m => m.moveId === queuedMove.move) || new PokemonMove(queuedMove.move); + if (!this.scene.currentBattle.double) { + playerActions.push(move.getName()) + } else { + var T = this.getBattlers(pokemon) + for (var i = 0; i < T.length; i++) { + if (T[i] == undefined) { + T.splice(i, 1) + i-- + } + } + console.log(queuedMove.targets, T, queuedMove.targets.map(p => T[p+1].name)) + playerActions.push(move.getName() + " → " + queuedMove.targets.map(p => T[p+1].name).join(", ")) + } + break; + case Command.BALL: + var ballNames = [ + "Poké Ball", + "Great Ball", + "Ultra Ball", + "Rogue Ball", + "Master Ball", + "Luxury Ball" + ] + LoggerTools.Actions[pokemon.getBattlerIndex()] = ballNames[turnCommand.cursor] + playerActions.push(ballNames[turnCommand.cursor]) + //this.scene.unshiftPhase(new AttemptCapturePhase(this.scene, turnCommand.targets[0] % 2, turnCommand.cursor)); + break; + case Command.POKEMON: + LoggerTools.Actions[pokemon.getBattlerIndex()] = "Switch " + this.scene.getParty()[pokemon.getFieldIndex()].name + " to " + this.scene.getParty()[turnCommand.cursor].name + //playerActions.push("Switch " + this.scene.getParty()[pokemon.getFieldIndex()].name + " to " + this.scene.getParty()[turnCommand.cursor].name) + //this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, pokemon.getFieldIndex(), turnCommand.cursor, true, turnCommand.args[0] as boolean, pokemon.isPlayer())); + break; + case Command.RUN: + LoggerTools.Actions[pokemon.getBattlerIndex()] = "Run" + playerActions.push("Run") + break; + } + } + //LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, playerActions.join(" | ")) + this.scene.getField(true).filter(p => p.summonData).map(p => { const bypassSpeed = new Utils.BooleanHolder(false); applyAbAttrs(BypassSpeedChanceAbAttr, p, null, bypassSpeed); @@ -2698,8 +2791,6 @@ export class TurnStartPhase extends FieldPhase { battlerBypassSpeed[p.getBattlerIndex()] = bypassSpeed; }); - const moveOrder = order.slice(0); - moveOrder.sort((a, b) => { const aCommand = this.scene.currentBattle.turnCommands[a]; const bCommand = this.scene.currentBattle.turnCommands[b]; @@ -2906,6 +2997,8 @@ export class TurnEndPhase extends FieldPhase { this.scene.arena.trySetTerrain(TerrainType.NONE, false); } + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, LoggerTools.Actions.join(" | ")) + this.end(); } } @@ -3163,6 +3256,15 @@ export class MovePhase extends BattlePhase { if (!allMoves[this.move.moveId].hasAttr(CopyMoveAttr)) { this.scene.currentBattle.lastMove = this.move.moveId; } + if (this.pokemon.isPlayer()) { + LoggerTools.Actions[this.pokemon.getBattlerIndex()] = this.move.getName() + if (this.scene.currentBattle.double) { + var targIDs = ["Counter", "Self", "Ally", "L", "R"] + if (this.pokemon.getBattlerIndex() == 1) targIDs = ["Counter", "Ally", "Self", "L", "R"] + LoggerTools.Actions[this.pokemon.getBattlerIndex()] += " → " + this.targets.map(v => targIDs[v+1]) + } + console.log(this.move.getName(), this.targets) + } // Assume conditions affecting targets only apply to moves with a single target let success = this.move.getMove().applyConditions(this.pokemon, targets[0], this.move.getMove()); From 06b782cd06a3f89a0481afbf78dbbe9427c87a5f Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Fri, 12 Jul 2024 10:44:03 -0400 Subject: [PATCH 36/94] Log item transfers --- src/battle-scene.ts | 1 + src/logger.ts | 117 ++++++++++++++++++++------ src/phases.ts | 33 ++++---- src/system/settings/settings.ts | 22 ++++- src/ui/fight-ui-handler.ts | 140 ++++++++++++++++++++++++++++++-- src/ui/party-ui-handler.ts | 4 +- 6 files changed, 263 insertions(+), 54 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 131cb0c91b6..331b06c57f4 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -120,6 +120,7 @@ export default class BattleScene extends SceneBase { public enableTutorials: boolean = import.meta.env.VITE_BYPASS_TUTORIAL === "1"; public enableMoveInfo: boolean = true; public enableRetries: boolean = false; + public damageDisplay: string = "Off"; /** * Determines the condition for a notification should be shown for Candy Upgrades * - 0 = 'Off' diff --git a/src/logger.ts b/src/logger.ts index d72dd36cb36..04d546c3832 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -16,6 +16,8 @@ import { Item } from "pokenode-ts"; import Trainer from "./field/trainer"; import { Species } from "./enums/species"; import { junit } from "node:test/reporters"; +import { i } from "vitest/dist/reporters-xEmem8D4.js"; +import { GameModes } from "./game-mode"; /** * All logs. @@ -38,6 +40,33 @@ export const logKeys: string[] = [ "d", // Debug ]; +export function getLogID(scene: BattleScene) { + return "drpd_log:" + scene.seed +} +export function getLogs() { + while(logs.length > 0) + logs.pop() + for (var i = 0; i < localStorage.length; i++) { + if (localStorage.key(i).substring(0, 9) == "drpd_log:") { + logs.push(["drpd.json", localStorage.key(i), localStorage.key(i).substring(9), "", "", ""]) + } + } +} +export function getMode(scene: BattleScene) { + switch (scene.gameMode.modeId) { + case GameModes.CLASSIC: + return "Classic" + case GameModes.ENDLESS: + return "Endless" + case GameModes.SPLICED_ENDLESS: + return "Spliced Endless" + case GameModes.DAILY: + return "Daily" + case GameModes.CHALLENGE: + return "Challenge" + } +} + export const rarities = [] export const rarityslot = [0] @@ -226,16 +255,23 @@ export function getSize(str: string) { return d.toString() + filesizes[unit] } -export function generateOption(i: integer): OptionSelectItem { +export function generateOption(i: integer, saves: any): OptionSelectItem { var filename: string = (JSON.parse(localStorage.getItem(logs[i][1])) as DRPD).title var op: OptionSelectItem = { - label: ` Export ${filename} (${getSize(printDRPD("", "", JSON.parse(localStorage.getItem(logs[i][1])) as DRPD))})`, + label: `Export ${filename} (${getSize(printDRPD("", "", JSON.parse(localStorage.getItem(logs[i][1])) as DRPD))})`, handler: () => { downloadLogByID(i) return false; } } + for (var j = 0; j < saves.length; j++) { + console.log(saves[j].seed, logs[i][2], saves[j].seed == logs[i][2]) + if (saves[j].seed == logs[i][2]) { + op.label = "[Slot " + (j + 1) + "]" + op.label.substring(6) + } + } if (logs[i][4] != "") { + op.label = " " + op.label op.item = logs[i][4] } return op; @@ -306,6 +342,7 @@ export function downloadLogByID(i: integer) { export function logTeam(scene: BattleScene, floor: integer = undefined) { if (floor == undefined) floor = scene.currentBattle.waveIndex var team = scene.getEnemyParty() + console.log("Log Enemy Team") if (team[0].hasTrainer()) { //var sprite = scene.currentBattle.trainer.config.getSpriteKey() //var trainerCat = Utils.getEnumKeys(TrainerType)[Utils.getEnumValues(TrainerType).indexOf(scene.currentBattle.trainer.config.trainerType)] @@ -320,20 +357,47 @@ export function logTeam(scene: BattleScene, floor: integer = undefined) { } } export function logActions(scene: BattleScene, floor: integer, action: string) { - if (localStorage.getItem("drpd") == null) localStorage.setItem("drpd", JSON.stringify(newDocument())) - var drpd: DRPD = JSON.parse(localStorage.getItem("drpd")) as DRPD; - var wv: Wave = getWave(drpd, floor, scene) - wv.actions.push(action) - console.log(drpd) - localStorage.setItem("drpd", JSON.stringify(drpd)) + if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) + var drpd: DRPD = JSON.parse(localStorage.getItem(getLogID(scene))) as DRPD; + console.log("Log Action", drpd) + var wv: Wave = getWave(drpd, floor, scene) + wv.actions.push(action) + console.log(drpd) + localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) +} +export function logShop(scene: BattleScene, floor: integer, action: string) { + if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) + var drpd: DRPD = JSON.parse(localStorage.getItem(getLogID(scene))) as DRPD; + console.log("Log Shop Item", drpd) + var wv: Wave = getWave(drpd, floor, scene) + wv.shop = action + console.log(drpd) + localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) } export function getWave(drpd: DRPD, floor: integer, scene: BattleScene) { var wv: Wave; var insertPos: integer; + console.log(drpd.waves) + if (drpd.waves[floor - 1] != undefined) { + return drpd.waves[floor - 1] + } + drpd.waves[floor - 1] = { + id: floor, + reload: false, + //type: floor % 10 == 0 ? "boss" : (floor % 10 == 5 ? "trainer" : "wild"), + type: floor % 10 == 0 ? "boss" : "wild", + double: scene.currentBattle.double, + actions: [], + shop: "", + biome: getBiomeName(scene.arena.biomeType), + pokemon: [] + } + return drpd.waves[floor - 1] for (var i = 0; i < drpd.waves.length; i++) { - if (drpd.waves[i] != undefined) { + if (drpd.waves[i] != undefined && drpd.waves[i] != null) { if (drpd.waves[i].id == floor) { wv = drpd.waves[i] + console.log("Found wave for floor " + floor + " at index " + i) if (wv.pokemon == undefined) wv.pokemon = [] } } else if (insertPos == undefined) { @@ -341,6 +405,7 @@ export function getWave(drpd: DRPD, floor: integer, scene: BattleScene) { } } if (wv == undefined && insertPos != undefined) { + console.log("Created new wave for floor " + floor + " at index " + insertPos) drpd.waves[insertPos] = { id: floor, reload: false, @@ -392,9 +457,10 @@ export function getWave(drpd: DRPD, floor: integer, scene: BattleScene) { console.error("Go yell at @redstonewolf8557 to fix this") } else { for (var i = 0; i < drpd.waves.length; i++) { - if (drpd.waves[i] != undefined) { + if (drpd.waves[i] != undefined && drpd.waves[i] != null) { if (drpd.waves[i].id == floor) { wv = drpd.waves[i] + console.log("Found wave for floor " + floor + " at index " + i) if (wv.pokemon == undefined) wv.pokemon = [] } } else if (insertPos == undefined) { @@ -402,6 +468,7 @@ export function getWave(drpd: DRPD, floor: integer, scene: BattleScene) { } } if (wv == undefined && insertPos != undefined) { + console.log("Created new wave for floor " + floor + " at index " + insertPos) drpd.waves[insertPos] = { id: floor, reload: false, @@ -479,8 +546,9 @@ export function logPokemon(scene: BattleScene, floor: integer = undefined, slot: setRow("e", newLine, floor, slot) //console.log(localStorage.getItem(logs[logKeys.indexOf("e")][1]).split("\n")) */ - if (localStorage.getItem("drpd") == null) localStorage.setItem("drpd", JSON.stringify(newDocument())) - var drpd: DRPD = JSON.parse(localStorage.getItem("drpd")) as DRPD; + if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) + var drpd: DRPD = JSON.parse(localStorage.getItem(getLogID(scene))) as DRPD; + console.log("Log Enemy Pokemon", drpd) var wv: Wave = getWave(drpd, floor, scene) var pk: PokeData = exportPokemon(pokemon, encounterRarity) if (wv.pokemon[slot] != undefined) { @@ -537,36 +605,33 @@ export function logPokemon(scene: BattleScene, floor: integer = undefined, slot: } if (pk.rarity == undefined) pk.rarity = "[Unknown]" wv.pokemon[slot] = pk; + while (wv.actions.length > 0) + wv.actions.pop() console.log(drpd) - localStorage.setItem("drpd", JSON.stringify(drpd)) -} -export function resetWave(scene: BattleScene, floor: integer = undefined) { - if (localStorage.getItem("drpd") == null) localStorage.setItem("drpd", JSON.stringify(newDocument())) - var drpd: DRPD = JSON.parse(localStorage.getItem("drpd")) as DRPD; - drpd.waves[floor] = exportWave(scene) - console.log(drpd) - localStorage.setItem("drpd", JSON.stringify(drpd)) + localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) } export function logTrainer(scene: BattleScene, floor: integer = undefined) { - if (localStorage.getItem("drpd") == null) localStorage.setItem("drpd", JSON.stringify(newDocument())) - var drpd: DRPD = JSON.parse(localStorage.getItem("drpd")) as DRPD; + if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) + var drpd: DRPD = JSON.parse(localStorage.getItem(getLogID(scene))) as DRPD; + console.log("Log Trainer", drpd) var wv: Wave = getWave(drpd, floor, scene) var t: TrainerData = exportTrainer(scene.currentBattle.trainer) wv.trainer = t wv.type = "trainer" console.log(drpd) - localStorage.setItem("drpd", JSON.stringify(drpd)) + localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) } export function logPlayerTeam(scene: BattleScene) { - if (localStorage.getItem("drpd") == null) localStorage.setItem("drpd", JSON.stringify(newDocument())) - var drpd: DRPD = JSON.parse(localStorage.getItem("drpd")) as DRPD; + if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) + var drpd: DRPD = JSON.parse(localStorage.getItem(getLogID(scene))) as DRPD; //var wv: Wave = getWave(drpd, 1, scene) + console.log("Log Player Starters", drpd) var P = scene.getParty() for (var i = 0; i < P.length; i++) { drpd.starters[i] = exportPokemon(P[i]) } console.log(drpd) - localStorage.setItem("drpd", JSON.stringify(drpd)) + localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) } export function dataSorter(a: string, b: string) { diff --git a/src/phases.ts b/src/phases.ts index 5e17e68c6a3..b95a86614dd 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -409,9 +409,10 @@ export class TitlePhase extends Phase { logMenu(): boolean { const options: OptionSelectItem[] = []; + LoggerTools.getLogs() for (var i = 0; i < LoggerTools.logs.length; i++) { if (localStorage.getItem(LoggerTools.logs[i][1]) != null) { - options.push(LoggerTools.generateOption(i) as OptionSelectItem) + options.push(LoggerTools.generateOption(i, this.getSaves()) as OptionSelectItem) } else { //options.push(LoggerTools.generateAddOption(i, this.scene, this)) } @@ -1324,7 +1325,7 @@ export class EncounterPhase extends BattlePhase { doEncounterCommon(showEncounterMessage: boolean = true) { const enemyField = this.scene.getEnemyField(); - LoggerTools.resetWave(this.scene, this.scene.currentBattle.waveIndex) + //LoggerTools.resetWave(this.scene, this.scene.currentBattle.waveIndex) LoggerTools.logTeam(this.scene, this.scene.currentBattle.waveIndex) if (this.scene.getEnemyParty()[0].hasTrainer()) { LoggerTools.logTrainer(this.scene, this.scene.currentBattle.waveIndex) @@ -2743,20 +2744,6 @@ export class TurnStartPhase extends FieldPhase { if (!queuedMove) { continue; } - const move = pokemon.getMoveset().find(m => m.moveId === queuedMove.move) || new PokemonMove(queuedMove.move); - if (!this.scene.currentBattle.double) { - playerActions.push(move.getName()) - } else { - var T = this.getBattlers(pokemon) - for (var i = 0; i < T.length; i++) { - if (T[i] == undefined) { - T.splice(i, 1) - i-- - } - } - console.log(queuedMove.targets, T, queuedMove.targets.map(p => T[p+1].name)) - playerActions.push(move.getName() + " → " + queuedMove.targets.map(p => T[p+1].name).join(", ")) - } break; case Command.BALL: var ballNames = [ @@ -4502,11 +4489,13 @@ export class VictoryPhase extends PokemonPhase { if (this.scene.currentBattle.waveIndex % 10) { this.scene.pushPhase(new SelectModifierPhase(this.scene)); } else if (this.scene.gameMode.isDaily) { + LoggerTools.logShop(this.scene, this.scene.currentBattle.waveIndex, "") this.scene.pushPhase(new ModifierRewardPhase(this.scene, modifierTypes.EXP_CHARM)); if (this.scene.currentBattle.waveIndex > 10 && !this.scene.gameMode.isWaveFinal(this.scene.currentBattle.waveIndex)) { this.scene.pushPhase(new ModifierRewardPhase(this.scene, modifierTypes.GOLDEN_POKEBALL)); } } else { + LoggerTools.logShop(this.scene, this.scene.currentBattle.waveIndex, "") const superExpWave = !this.scene.gameMode.isEndless ? (this.scene.offsetGym ? 0 : 20) : 10; if (this.scene.gameMode.isEndless && this.scene.currentBattle.waveIndex === 10) { this.scene.pushPhase(new ModifierRewardPhase(this.scene, modifierTypes.EXP_SHARE)); @@ -4524,6 +4513,7 @@ export class VictoryPhase extends PokemonPhase { } this.scene.pushPhase(new NewBattlePhase(this.scene)); } else { + LoggerTools.logShop(this.scene, this.scene.currentBattle.waveIndex, "") this.scene.currentBattle.battleType = BattleType.CLEAR; this.scene.score += this.scene.gameMode.getClearScoreBonus(); this.scene.updateScoreText(); @@ -5612,6 +5602,7 @@ export class AttemptRunPhase extends PokemonPhase { if (playerPokemon.randSeedInt(256) < escapeChance.value) { this.scene.playSound("flee"); + LoggerTools.logShop(this.scene, this.scene.currentBattle.waveIndex, "") this.scene.queueMessage(i18next.t("battle:runAwaySuccess"), null, true, 500); this.scene.tweens.add({ @@ -5672,6 +5663,7 @@ export class SelectModifierPhase extends BattlePhase { if (rowCursor < 0 || cursor < 0) { this.scene.ui.showText(i18next.t("battle:skipItemQuestion"), null, () => { this.scene.ui.setOverlayMode(Mode.CONFIRM, () => { + LoggerTools.logShop(this.scene, this.scene.currentBattle.waveIndex, "") this.scene.ui.revertMode(); this.scene.ui.setMode(Mode.MESSAGE); super.end(); @@ -5691,6 +5683,7 @@ export class SelectModifierPhase extends BattlePhase { return false; } else { this.scene.reroll = true; + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, "Reroll" + (this.scene.lockModifierTiers ? " (Locked)" : "")) this.scene.unshiftPhase(new SelectModifierPhase(this.scene, this.rerollCount + 1, typeOptions.map(o => o.type.tier))); this.scene.ui.clearText(); this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end()); @@ -5701,11 +5694,17 @@ export class SelectModifierPhase extends BattlePhase { } break; case 1: - this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.MODIFIER_TRANSFER, -1, (fromSlotIndex: integer, itemIndex: integer, itemQuantity: integer, toSlotIndex: integer) => { + this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.MODIFIER_TRANSFER, -1, (fromSlotIndex: integer, itemIndex: integer, itemQuantity: integer, toSlotIndex: integer, isAll: boolean, isFirst: boolean) => { if (toSlotIndex !== undefined && fromSlotIndex < 6 && toSlotIndex < 6 && fromSlotIndex !== toSlotIndex && itemIndex > -1) { const itemModifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier && (m as PokemonHeldItemModifier).getTransferrable(true) && (m as PokemonHeldItemModifier).pokemonId === party[fromSlotIndex].id) as PokemonHeldItemModifier[]; const itemModifier = itemModifiers[itemIndex]; + if (isAll) { + if (isFirst) + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, "Transfer [" + (fromSlotIndex + 1) + "] " + this.scene.getParty()[fromSlotIndex].name + " (All) → [" + (toSlotIndex + 1) + "] " + this.scene.getParty()[toSlotIndex].name) + } else { + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, "Transfer [" + (fromSlotIndex + 1) + "] " + this.scene.getParty()[fromSlotIndex].name + " (" + itemModifier.type.name + (itemQuantity == itemModifier.getStackCount() ? "" : " x" + itemQuantity) + ") → [" + (toSlotIndex + 1) + "] " + this.scene.getParty()[toSlotIndex].name) + } this.scene.tryTransferHeldItemModifier(itemModifier, party[toSlotIndex], true, itemQuantity); } else { this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); diff --git a/src/system/settings/settings.ts b/src/system/settings/settings.ts index b09de095259..920df6b1063 100644 --- a/src/system/settings/settings.ts +++ b/src/system/settings/settings.ts @@ -43,7 +43,8 @@ const AUTO_DISABLED: SettingOption[] = [ export enum SettingType { GENERAL, DISPLAY, - AUDIO + AUDIO, + MOD } type SettingOption = { @@ -98,6 +99,7 @@ export const SettingKeys = { SE_Volume: "SE_VOLUME", Music_Preference: "MUSIC_PREFERENCE", Show_BGM_Bar: "SHOW_BGM_BAR", + Damage_Display: "DAMAGE_DISPLAY" }; /** @@ -242,6 +244,22 @@ export const Setting: Array = [ default: 0, type: SettingType.GENERAL }, + { + key: SettingKeys.Damage_Display, + label: "Damage Display", + options: [{ + label: "Off", + value: "Off" + }, { + label: "Value", + value: "Value" + }, { + label: "Percent", + value: "Percent" + }], + default: 0, + type: SettingType.GENERAL + }, { key: SettingKeys.Tutorials, label: i18next.t("settings:tutorials"), @@ -604,6 +622,8 @@ export function setSetting(scene: BattleScene, setting: string, value: integer): case SettingKeys.Enable_Retries: scene.enableRetries = Setting[index].options[value].value === "On"; break; + case SettingKeys.Damage_Display: + scene.damageDisplay = Setting[index].options[value].value case SettingKeys.Skip_Seen_Dialogues: scene.skipSeenDialogues = Setting[index].options[value].value === "On"; break; diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index d7f2ae81f77..147142eacc4 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -15,15 +15,16 @@ import { Stat } from "#app/data/pokemon-stat.js"; import { Abilities } from "#app/enums/abilities.js"; import { WeatherType } from "#app/data/weather.js"; import { Moves } from "#app/enums/moves.js"; -import { AddSecondStrikeAbAttr, AllyMoveCategoryPowerBoostAbAttr, applyAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, ConditionalCritAbAttr, DamageBoostAbAttr, FieldMoveTypePowerBoostAbAttr, FieldPriorityMoveImmunityAbAttr, MoveImmunityAbAttr, MoveTypeChangeAttr, MultCritAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, StabBoostAbAttr, TypeImmunityAbAttr, UserFieldMoveTypePowerBoostAbAttr, VariableMovePowerAbAttr } from "#app/data/ability.js"; +import { AddSecondStrikeAbAttr, AllyMoveCategoryPowerBoostAbAttr, AlwaysHitAbAttr, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, ConditionalCritAbAttr, DamageBoostAbAttr, FieldMoveTypePowerBoostAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentEvasionAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, MoveTypeChangeAttr, MultCritAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, StabBoostAbAttr, TypeImmunityAbAttr, UserFieldMoveTypePowerBoostAbAttr, VariableMovePowerAbAttr, WonderSkinAbAttr } from "#app/data/ability.js"; import { ArenaTagType } from "#app/enums/arena-tag-type.js"; import { ArenaTagSide, WeakenMoveScreenTag, WeakenMoveTypeTag } from "#app/data/arena-tag.js"; -import { BattlerTagLapseType, HelpingHandTag, TypeBoostTag } from "#app/data/battler-tags.js"; +import { BattlerTagLapseType, HelpingHandTag, SemiInvulnerableTag, TypeBoostTag } from "#app/data/battler-tags.js"; import { TerrainType } from "#app/data/terrain.js"; -import { AttackTypeBoosterModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, PokemonMultiHitModifier, TempBattleStatBoosterModifier } from "#app/modifier/modifier.js"; +import { AttackTypeBoosterModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, PokemonMoveAccuracyBoosterModifier, PokemonMultiHitModifier, TempBattleStatBoosterModifier } from "#app/modifier/modifier.js"; import { BattlerTagType } from "#app/enums/battler-tag-type.js"; import { TempBattleStat } from "#app/data/temp-battle-stat.js"; import { StatusEffect } from "#app/data/status-effect.js"; +import { BattleStat } from "#app/data/battle-stat.js"; export default class FightUiHandler extends UiHandler { private movesContainer: Phaser.GameObjects.Container; @@ -439,6 +440,117 @@ export default class FightUiHandler extends UiHandler { return [damage1.value, damage2.value] } + calculateAccuracy(user: Pokemon, target: Pokemon, move: PokemonMove) { + if (this.scene.currentBattle.double && false) { + switch (move.getMove().moveTarget) { + case MoveData.MoveTarget.USER: // Targets yourself + return -1; // Moves targeting yourself always hit + case MoveData.MoveTarget.OTHER: // Targets one Pokemon + return move.getMove().accuracy + case MoveData.MoveTarget.ALL_OTHERS: // Targets all Pokemon + return move.getMove().accuracy; + case MoveData.MoveTarget.NEAR_OTHER: // Targets a Pokemon adjacent to the user + return move.getMove().accuracy; + case MoveData.MoveTarget.ALL_NEAR_OTHERS: // Targets all Pokemon adjacent to the user + return move.getMove().accuracy; + case MoveData.MoveTarget.NEAR_ENEMY: // Targets an opponent adjacent to the user + return move.getMove().accuracy; + case MoveData.MoveTarget.ALL_NEAR_ENEMIES: // Targets all opponents adjacent to the user + return move.getMove().accuracy; + case MoveData.MoveTarget.RANDOM_NEAR_ENEMY: // Targets a random opponent adjacent to the user + return move.getMove().accuracy; + case MoveData.MoveTarget.ALL_ENEMIES: // Targets all opponents + return move.getMove().accuracy; + case MoveData.MoveTarget.ATTACKER: // Counter move + return move.getMove().accuracy; + case MoveData.MoveTarget.NEAR_ALLY: // Targets an adjacent ally + return move.getMove().accuracy; + case MoveData.MoveTarget.ALLY: // Targets an ally + return move.getMove().accuracy; + case MoveData.MoveTarget.USER_OR_NEAR_ALLY: // Targets an ally or yourself + return move.getMove().accuracy; + case MoveData.MoveTarget.USER_AND_ALLIES: // Targets all on your side + return move.getMove().accuracy; + case MoveData.MoveTarget.ALL: // Targets everyone + return move.getMove().accuracy; + case MoveData.MoveTarget.USER_SIDE: // Targets your field + return move.getMove().accuracy; + case MoveData.MoveTarget.ENEMY_SIDE: // Targets enemy field + return -1; // Moves placing entry hazards always hit + case MoveData.MoveTarget.BOTH_SIDES: // Targets the entire field + return move.getMove().accuracy; + case MoveData.MoveTarget.PARTY: // Targets all of the Player's Pokemon, including ones that aren't active + return move.getMove().accuracy; + case MoveData.MoveTarget.CURSE: + return move.getMove().accuracy; + } + } + // Moves targeting the user and entry hazards can't miss + if ([MoveData.MoveTarget.USER, MoveData.MoveTarget.ENEMY_SIDE].includes(move.getMove().moveTarget)) { + return -1; + } + if (target == undefined) return move.getMove().accuracy; + // If either Pokemon has No Guard, + if (user.hasAbilityWithAttr(AlwaysHitAbAttr) || target.hasAbilityWithAttr(AlwaysHitAbAttr)) { + return -1; + } + // If the user should ignore accuracy on a target, check who the user targeted last turn and see if they match + if (user.getTag(BattlerTagType.IGNORE_ACCURACY) && (user.getLastXMoves().slice(1).find(() => true)?.targets || []).indexOf(target.getBattlerIndex()) !== -1) { + return -1; + } + + const hiddenTag = target.getTag(SemiInvulnerableTag); + if (hiddenTag && !move.getMove().getAttrs(MoveData.HitsTagAttr).some(hta => hta.tagType === hiddenTag.tagType)) { + return 0; + } + const moveAccuracy = new Utils.NumberHolder(move.getMove().accuracy); + + MoveData.applyMoveAttrs(MoveData.VariableAccuracyAttr, user, target, move.getMove(), moveAccuracy); + applyPreDefendAbAttrs(WonderSkinAbAttr, target, user, move.getMove(), { value: false }, moveAccuracy); + + if (moveAccuracy.value === -1) { + return -1; + } + + const isOhko = move.getMove().hasAttr(MoveData.OneHitKOAccuracyAttr); + + if (!isOhko) { + user.scene.applyModifiers(PokemonMoveAccuracyBoosterModifier, user.isPlayer(), user, moveAccuracy); + } + + if (this.scene.arena.weather?.weatherType === WeatherType.FOG) { + moveAccuracy.value = Math.floor(moveAccuracy.value * 0.9); + } + + if (!isOhko && this.scene.arena.getTag(ArenaTagType.GRAVITY)) { + moveAccuracy.value = Math.floor(moveAccuracy.value * 1.67); + } + + const userAccuracyLevel = new Utils.IntegerHolder(user.summonData.battleStats[BattleStat.ACC]); + const targetEvasionLevel = new Utils.IntegerHolder(target.summonData.battleStats[BattleStat.EVA]); + applyAbAttrs(IgnoreOpponentStatChangesAbAttr, target, null, userAccuracyLevel); + applyAbAttrs(IgnoreOpponentStatChangesAbAttr, user, null, targetEvasionLevel); + applyAbAttrs(IgnoreOpponentEvasionAbAttr, user, null, targetEvasionLevel); + MoveData.applyMoveAttrs(MoveData.IgnoreOpponentStatChangesAttr, user, target, move.getMove(), targetEvasionLevel); + this.scene.applyModifiers(TempBattleStatBoosterModifier, user.isPlayer(), TempBattleStat.ACC, userAccuracyLevel); + + const accuracyMultiplier = new Utils.NumberHolder(1); + if (userAccuracyLevel.value !== targetEvasionLevel.value) { + accuracyMultiplier.value = userAccuracyLevel.value > targetEvasionLevel.value + ? (3 + Math.min(userAccuracyLevel.value - targetEvasionLevel.value, 6)) / 3 + : 3 / (3 + Math.min(targetEvasionLevel.value - userAccuracyLevel.value, 6)); + } + + applyBattleStatMultiplierAbAttrs(BattleStatMultiplierAbAttr, user, BattleStat.ACC, accuracyMultiplier, move.getMove()); + + const evasionMultiplier = new Utils.NumberHolder(1); + applyBattleStatMultiplierAbAttrs(BattleStatMultiplierAbAttr, target, BattleStat.EVA, evasionMultiplier); + + accuracyMultiplier.value /= evasionMultiplier.value; + + return moveAccuracy.value * accuracyMultiplier.value + } + calcDamage(scene: BattleScene, user: PlayerPokemon, target: Pokemon, move: PokemonMove) { /* var power = move.getMove().power @@ -535,11 +647,17 @@ export default class FightUiHandler extends UiHandler { var percentChance = (target.hp - dmgLow + 1) / (dmgHigh - dmgLow + 1) koText = " (" + Math.round(percentChance * 100) + "% KO)" } - return (Math.round(dmgLow) == Math.round(dmgHigh) ? Math.round(dmgLow).toString() : Math.round(dmgLow) + "-" + Math.round(dmgHigh)) + koText + if (target.getMoveEffectiveness(user, move) == undefined) { + return "---" + } + if (scene.damageDisplay == "Value") + return target.getMoveEffectiveness(user, move) + "x - " + (Math.round(dmgLow) == Math.round(dmgHigh) ? Math.round(dmgLow).toString() : Math.round(dmgLow) + "-" + Math.round(dmgHigh)) + koText dmgLow = Math.round((dmgLow)/target.getBattleStat(Stat.HP)*100) dmgHigh = Math.round((dmgHigh)/target.getBattleStat(Stat.HP)*100) - return (dmgLow == dmgHigh ? dmgLow + "%" : dmgLow + "%-" + dmgHigh + "%") + koText - return "???" + if (scene.damageDisplay == "Percent") + return target.getMoveEffectiveness(user, move) + "x - " + (dmgLow == dmgHigh ? dmgLow + "%" : dmgLow + "%-" + dmgHigh + "%") + koText + if (scene.damageDisplay == "Off") + return target.getMoveEffectiveness(user, move) + "x" } setCursor(cursor: integer): boolean { @@ -574,9 +692,15 @@ export default class FightUiHandler extends UiHandler { const maxPP = pokemonMove.getMovePp(); const pp = maxPP - pokemonMove.ppUsed; + const accuracy1 = this.calculateAccuracy(pokemon, this.scene.getEnemyField()[0], pokemonMove) + const accuracy2 = this.calculateAccuracy(pokemon, this.scene.getEnemyField()[1], pokemonMove) + this.ppText.setText(`${Utils.padInt(pp, 2, " ")}/${Utils.padInt(maxPP, 2, " ")}`); this.powerText.setText(`${power >= 0 ? power : "---"}`); this.accuracyText.setText(`${accuracy >= 0 ? accuracy : "---"}`); + this.accuracyText.setText(`${accuracy1 >= 0 ? accuracy1 : "---"}`); + if (this.scene.getEnemyField()[1] != undefined) + this.accuracyText.setText(`${accuracy1 >= 0 ? accuracy1 : "---"}/${accuracy2 >= 0 ? accuracy2 : "---"}`); const ppPercentLeft = pp / maxPP; @@ -939,8 +1063,8 @@ export function simulateAttack(scene: BattleScene, user: Pokemon, target: Pokemo applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, user, target, move, cancelled, damage1); applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, user, target, move, cancelled, damage2); - console.log("damage (min)", damage1.value, move.name, power.value, sourceAtk, targetDef); - console.log("damage (max)", damage2.value, move.name, power.value, sourceAtkCrit, targetDefCrit); + //console.log("damage (min)", damage1.value, move.name, power.value, sourceAtk, targetDef); + //console.log("damage (max)", damage2.value, move.name, power.value, sourceAtkCrit, targetDefCrit); // In case of fatal damage, this tag would have gotten cleared before we could lapse it. const destinyTag = target.getTag(BattlerTagType.DESTINY_BOND); diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index e820c8cb0d2..8a89722dca3 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -63,7 +63,7 @@ export enum PartyOption { } export type PartySelectCallback = (cursor: integer, option: PartyOption) => void; -export type PartyModifierTransferSelectCallback = (fromCursor: integer, index: integer, itemQuantity?: integer, toCursor?: integer) => void; +export type PartyModifierTransferSelectCallback = (fromCursor: integer, index: integer, itemQuantity?: integer, toCursor?: integer, isAll?: boolean, isFirst?: boolean) => void; export type PartyModifierSpliceSelectCallback = (fromCursor: integer, toCursor?: integer) => void; export type PokemonSelectFilter = (pokemon: PlayerPokemon) => string; export type PokemonModifierTransferSelectFilter = (pokemon: PlayerPokemon, modifier: PokemonHeldItemModifier) => string; @@ -326,7 +326,7 @@ export default class PartyUiHandler extends MessageUiHandler { if (option === PartyOption.TRANSFER) { if (this.transferCursor !== this.cursor) { if (this.transferAll) { - getTransferrableItemsFromPokemon(this.scene.getParty()[this.transferCursor]).forEach((_, i) => (this.selectCallback as PartyModifierTransferSelectCallback)(this.transferCursor, i, this.transferQuantitiesMax[i], this.cursor)); + getTransferrableItemsFromPokemon(this.scene.getParty()[this.transferCursor]).forEach((_, i) => (this.selectCallback as PartyModifierTransferSelectCallback)(this.transferCursor, i, this.transferQuantitiesMax[i], this.cursor, true, i == 0)); } else { (this.selectCallback as PartyModifierTransferSelectCallback)(this.transferCursor, this.transferOptionCursor, this.transferQuantities[this.transferOptionCursor], this.cursor); } From cb7cb3332f300f784d1c2bf6dc061a8c04c8d964 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Fri, 12 Jul 2024 15:34:25 -0400 Subject: [PATCH 37/94] Almost done --- src/evolution-phase.ts | 4 + src/logger.ts | 404 +++++++++++++++++++++++------ src/modifier/modifier-type.ts | 154 ++++++++++- src/modifier/modifier.ts | 4 + src/phases.ts | 16 +- src/system/settings/settings.ts | 2 +- src/ui/fight-ui-handler.ts | 4 +- src/ui/log-name-form-ui-handler.ts | 86 ++++++ src/ui/ui.ts | 6 +- 9 files changed, 586 insertions(+), 94 deletions(-) create mode 100644 src/ui/log-name-form-ui-handler.ts diff --git a/src/evolution-phase.ts b/src/evolution-phase.ts index c7986f6664f..35ccf0ee01d 100644 --- a/src/evolution-phase.ts +++ b/src/evolution-phase.ts @@ -10,6 +10,7 @@ import { cos, sin } from "./field/anims"; import { PlayerPokemon } from "./field/pokemon"; import { getTypeRgb } from "./data/type"; import i18next from "i18next"; +import * as LoggerTools from "./logger"; export class EvolutionPhase extends Phase { protected pokemon: PlayerPokemon; @@ -194,12 +195,14 @@ export class EvolutionPhase extends Phase { this.scene.ui.showText(i18next.t("menu:stoppedEvolving", { pokemonName: preName }), null, () => { this.scene.ui.showText(i18next.t("menu:pauseEvolutionsQuestion", { pokemonName: preName }), null, () => { const end = () => { + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, "Cancel " + preName + "'s evolution") this.scene.ui.showText(null, 0); this.scene.playBgm(); evolvedPokemon.destroy(); this.end(); }; this.scene.ui.setOverlayMode(Mode.CONFIRM, () => { + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, "Cancel " + preName + "'s evolution and pause evolutions") this.scene.ui.revertMode(); this.pokemon.pauseEvolutions = true; this.scene.ui.showText(i18next.t("menu:evolutionsPaused", { pokemonName: preName }), null, end, 3000); @@ -219,6 +222,7 @@ export class EvolutionPhase extends Phase { evolutionHandler.canCancel = false; this.pokemon.evolve(this.evolution).then(() => { + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, "Evolve " + preName) const levelMoves = this.pokemon.getLevelMoves(this.lastLevel + 1, true); for (const lm of levelMoves) { this.scene.unshiftPhase(new LearnMovePhase(this.scene, this.scene.getParty().indexOf(this.pokemon), lm[1])); diff --git a/src/logger.ts b/src/logger.ts index 04d546c3832..e142c451a8c 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -40,18 +40,41 @@ export const logKeys: string[] = [ "d", // Debug ]; +/** + * Uses the save's RNG seed to create a log ID. Used to assign each save its own log. + * @param scene The BattleScene. + * @returns The ID of the current save's log. + */ export function getLogID(scene: BattleScene) { return "drpd_log:" + scene.seed } +/** + * Gets a log's item list storage, for detecting reloads via a change in the loot rewards. + * + * Not used yet. + * @param scene The BattleScene. + * @returns The ID of the current save's log. + */ +export function getItemsID(scene: BattleScene) { + return "drpd_items:" + scene.seed +} +/** + * Resets the `logs` array, and creates a list of all game logs in LocalStorage. + */ export function getLogs() { while(logs.length > 0) logs.pop() for (var i = 0; i < localStorage.length; i++) { if (localStorage.key(i).substring(0, 9) == "drpd_log:") { - logs.push(["drpd.json", localStorage.key(i), localStorage.key(i).substring(9), "", "", ""]) + logs.push(["drpd.json", localStorage.key(i), localStorage.key(i).substring(9), "drpd_items:" + localStorage.key(i).substring(9), "", ""]) } } } +/** + * Returns a string for the name of the current game mode. + * @param scene The BattleScene. Used to get the game mode. + * @returns The name of the game mode, for use in naming a game log. + */ export function getMode(scene: BattleScene) { switch (scene.gameMode.modeId) { case GameModes.CLASSIC: @@ -67,9 +90,37 @@ export function getMode(scene: BattleScene) { } } +/** + * Formats a Pokemon in the player's party. + * @param scene The BattleScene, for getting the player's party. + * @param index The slot index. + * @returns [INDEX] NAME (example: `[1] Walking Wake` is a Walking Wake in the first party slot) + */ +export function playerPokeName(scene: BattleScene, index: integer | Pokemon | PlayerPokemon) { + if (typeof index == "number") { + return "[" + (index + 1) + "] " + scene.getParty()[index].name + } + return "[" + (scene.getParty().indexOf(index as PlayerPokemon) + 1) + "] " + index.name +} +/** + * Formats a Pokemon in the opposing party. + * @param scene The BattleScene, for getting the enemy's party. + * @param index The slot index. + * @returns [INDEX] NAME (example: `[2] Zigzagoon` is a Zigzagoon in the right slot (for a double battle) or in the second party slot (for a single battle against a Trainer)) + */ +export function enemyPokeName(scene: BattleScene, index: integer | Pokemon | EnemyPokemon) { + if (typeof index == "number") { + return "[" + (index + 1) + "] " + scene.getEnemyParty()[index].name + } + return "[" + (scene.getEnemyParty().indexOf(index as EnemyPokemon) + 1) + "] " + index.name +} +// LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, "") + export const rarities = [] export const rarityslot = [0] +export const isPreSwitch: Utils.BooleanHolder = new Utils.BooleanHolder(false); + export var StoredLog: DRPD = undefined; export const DRPD_Version = "1.0.0" @@ -129,13 +180,19 @@ export interface TrainerData { type: string, } export interface ItemData { - id: string, + id: integer, name: string, quantity: integer, } export const Actions = [] +/** + * Creates a new document in the DRPD format + * @param name (Optional) The name for the file. Defaults to "Untitled Run". + * @param authorName (Optional) The author(s) of the file. Defaults to "Write your name here". + * @returns The fresh DRPD document. + */ export function newDocument(name: string = "Untitled Run", authorName: string | string[] = "Write your name here"): DRPD { return { version: DRPD_Version, @@ -146,9 +203,20 @@ export function newDocument(name: string = "Untitled Run", authorName: string | starters: new Array(3), } } +/** + * Imports a string as a DRPD. + * @param drpd The JSON string to import. + * @returns The imported document. + */ export function importDocument(drpd: string): DRPD { return JSON.parse(drpd) as DRPD; } +/** + * Exports a Pokemon's data as `PokeData`. + * @param pokemon The Pokemon to store. + * @param encounterRarity The rarity tier of the Pokemon for this biome. + * @returns The Pokemon data. + */ export function exportPokemon(pokemon: Pokemon, encounterRarity?: string): PokeData { return { id: pokemon.species.speciesId, @@ -161,10 +229,15 @@ export function exportPokemon(pokemon: Pokemon, encounterRarity?: string): PokeD rarity: encounterRarity, captured: false, level: pokemon.level, - items: pokemon.getHeldItems().map(item => exportItem(item)), + items: pokemon.getHeldItems().map((item, idx) => exportItem(item, idx)), ivs: exportIVs(pokemon.ivs) } } +/** + * Exports a Pokemon's nature as `NatureData`. + * @param nature The nature to store. + * @returns The nature data. + */ export function exportNature(nature: Nature): NatureData { return { name: getNatureName(nature), @@ -172,13 +245,23 @@ export function exportNature(nature: Nature): NatureData { decreased: getNatureDecrease(nature), } } -export function exportItem(item: PokemonHeldItemModifier): ItemData { +/** + * Exports a Held Item as `ItemData`. + * @param item The item to store. + * @returns The item data. + */ +export function exportItem(item: PokemonHeldItemModifier, index: integer): ItemData { return { - id: item.type.id, + id: index, name: item.type.name, quantity: item.getStackCount() } } +/** + * Exports a Pokemon's IVs as `IVData`. + * @param ivs The IV array to store. + * @returns The IV data. + */ export function exportIVs(ivs: integer[]): IVData { return { hp: ivs[0], @@ -189,6 +272,11 @@ export function exportIVs(ivs: integer[]): IVData { speed: ivs[5] } } +/** + * Exports the current battle as a `Wave`. + * @param scene The BattleScene. Used to retrieve information about the current wave. + * @returns The wave data. + */ export function exportWave(scene: BattleScene): Wave { var ret: Wave = { id: scene.currentBattle.waveIndex, @@ -221,6 +309,11 @@ export function exportWave(scene: BattleScene): Wave { } return ret; } +/** + * Exports the opposing trainer as `TrainerData`. + * @param trainer The Trainer to store. + * @returns The Trainer data. + */ export function exportTrainer(trainer: Trainer): TrainerData { if (trainer.config.getTitle(0, trainer.variant) == "Finn") { return { @@ -255,6 +348,12 @@ export function getSize(str: string) { return d.toString() + filesizes[unit] } +/** + * Generates a UI option to save a log to your device. + * @param i The slot number. Corresponds to an index in `logs`. + * @param saves Your session data. Used to label logs if they match one of your save slots. + * @returns A UI option. + */ export function generateOption(i: integer, saves: any): OptionSelectItem { var filename: string = (JSON.parse(localStorage.getItem(logs[i][1])) as DRPD).title var op: OptionSelectItem = { @@ -267,7 +366,7 @@ export function generateOption(i: integer, saves: any): OptionSelectItem { for (var j = 0; j < saves.length; j++) { console.log(saves[j].seed, logs[i][2], saves[j].seed == logs[i][2]) if (saves[j].seed == logs[i][2]) { - op.label = "[Slot " + (j + 1) + "]" + op.label.substring(6) + op.label = "[Slot " + (saves[j].slot + 1) + "]" + op.label.substring(6) } } if (logs[i][4] != "") { @@ -276,6 +375,18 @@ export function generateOption(i: integer, saves: any): OptionSelectItem { } return op; } +/** + * Generates an option to create a new log. + * + * Not used. + * @param i The slot number. Corresponds to an index in `logs`. + * @param scene The current scene. Not used. + * @param o The current game phase. Used to return to the previous menu. Not necessary anymore lol + * @returns A UI option. + * + * wow this function sucks + * @deprecated + */ export function generateAddOption(i: integer, scene: BattleScene, o: TitlePhase) { var op: OptionSelectItem = { label: "Generate log " + logs[i][0], @@ -314,7 +425,7 @@ export function clearLog(keyword: string) { } /** * Saves a log to your device. - * @param keyword The identifier key for the log you want to reste + * @param keyword The identifier key for the log you want to save. */ export function downloadLog(keyword: string) { var d = JSON.parse(localStorage.getItem(logs[logKeys.indexOf(keyword)][1])) @@ -327,6 +438,10 @@ export function downloadLog(keyword: string) { link.click(); link.remove(); } +/** + * Saves a log to your device. + * @param i The index of the log you want to save. + */ export function downloadLogByID(i: integer) { console.log(i) var d = JSON.parse(localStorage.getItem(logs[i][1])) @@ -339,6 +454,11 @@ export function downloadLogByID(i: integer) { link.click(); link.remove(); } +/** + * Calls `logPokemon` once for each opponent or, if it's a trainer battle, logs the trainer's data. + * @param scene The BattleScene. Used to get the enemy team and whether it's a trainer battle or not. + * @param floor The wave index to write to. Defaults to the current wave. + */ export function logTeam(scene: BattleScene, floor: integer = undefined) { if (floor == undefined) floor = scene.currentBattle.waveIndex var team = scene.getEnemyParty() @@ -356,6 +476,14 @@ export function logTeam(scene: BattleScene, floor: integer = undefined) { } } } +/** + * Logs the actions that the player took. + * + * This includes attacks you perform, items you transfer during the shop, Poke Balls you throw, running from battl, (or attempting to), and switching (including pre-switches). + * @param scene The BattleScene. Used to get the log ID. + * @param floor The wave index to write to. + * @param action The text you want to add to the actions list. + */ export function logActions(scene: BattleScene, floor: integer, action: string) { if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) var drpd: DRPD = JSON.parse(localStorage.getItem(getLogID(scene))) as DRPD; @@ -365,6 +493,12 @@ export function logActions(scene: BattleScene, floor: integer, action: string) { console.log(drpd) localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) } +/** + * Logs what the player took from the rewards pool and, if applicable, who they used it on. + * @param scene The BattleScene. Used to get the log ID. + * @param floor The wave index to write to. + * @param action The shop action. Left blank if there was no shop this floor or if you ran away. Logged as "Skip taking items" if you didn't take anything for some reason. + */ export function logShop(scene: BattleScene, floor: integer, action: string) { if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) var drpd: DRPD = JSON.parse(localStorage.getItem(getLogID(scene))) as DRPD; @@ -374,31 +508,24 @@ export function logShop(scene: BattleScene, floor: integer, action: string) { console.log(drpd) localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) } -export function getWave(drpd: DRPD, floor: integer, scene: BattleScene) { +/** + * Retrieves a wave from the DRPD. If the wave doesn't exist, it creates a new one. + * @param drpd The document to read from. + * @param floor The wave index to retrieve. + * @param scene The BattleScene, used for creating a new wave + * @returns The requested `Wave`. + */ +export function getWave(drpd: DRPD, floor: integer, scene: BattleScene): Wave { var wv: Wave; var insertPos: integer; console.log(drpd.waves) - if (drpd.waves[floor - 1] != undefined) { - return drpd.waves[floor - 1] - } - drpd.waves[floor - 1] = { - id: floor, - reload: false, - //type: floor % 10 == 0 ? "boss" : (floor % 10 == 5 ? "trainer" : "wild"), - type: floor % 10 == 0 ? "boss" : "wild", - double: scene.currentBattle.double, - actions: [], - shop: "", - biome: getBiomeName(scene.arena.biomeType), - pokemon: [] - } - return drpd.waves[floor - 1] for (var i = 0; i < drpd.waves.length; i++) { if (drpd.waves[i] != undefined && drpd.waves[i] != null) { if (drpd.waves[i].id == floor) { wv = drpd.waves[i] console.log("Found wave for floor " + floor + " at index " + i) if (wv.pokemon == undefined) wv.pokemon = [] + return wv; } } else if (insertPos == undefined) { insertPos = i @@ -489,14 +616,28 @@ export function getWave(drpd: DRPD, floor: integer, scene: BattleScene) { }) if (wv == undefined) { scene.ui.showText("Failed to make space\nPress F12 for info") - console.error("There should be space to store a new wave, but the program failed to find space anyways") - console.error("Go yell at @redstonewolf8557 to fix this") + console.error("There should be space to store a new wave, but the program failed to find space anyways") + console.error("Go yell at @redstonewolf8557 to fix this") + return undefined; } } }) } + if (wv == undefined) { + scene.ui.showText("Failed to retrieve wave\nPress F12 for info") + console.error("Failed to retrieve wave??") + console.error("this mod i stg") + console.error("Go yell at @redstonewolf8557 to fix this") + return undefined; + } return wv; } +/** + * Compares a Species to a biome's tier pool. + * @param species The species to search for. + * @param pool The SpeciesPool tier to compare. + * @returns whether or not `species` was found in the `pool`. + */ function checkForPokeInBiome(species: Species, pool: (Species | SpeciesTree)[]): boolean { //console.log(species, pool) for (var i = 0; i < pool.length; i++) { @@ -514,38 +655,16 @@ function checkForPokeInBiome(species: Species, pool: (Species | SpeciesTree)[]): } return false; } +/** + * Logs a wild Pokemon to a wave's data. + * @param scene The BattleScene. Used to retrieve the log ID. + * @param floor The wave index to write to. Defaults to the current floor. + * @param slot The slot to write to. In a single battle, 0 = the Pokemon that is out first. In a double battle, 0 = Left and 1 = Right. + * @param pokemon The `EnemyPokemon` to store the data of. (Automatically converted via `exportPokemon`) + * @param encounterRarity The rarity tier of this Pokemon. If not specified, it calculates this automatically by searching the current biome's species pool. + */ export function logPokemon(scene: BattleScene, floor: integer = undefined, slot: integer, pokemon: EnemyPokemon, encounterRarity?: string) { if (floor == undefined) floor = scene.currentBattle.waveIndex - /* - var modifiers: string[] = [] - var mods = pokemon.getHeldItems() - for (var i = 0; i < mods.length; i++) { - modifiers.push(mods[i].type.name + (mods[i].getMaxStackCount(scene) == 1 ? "" : " x" + mods[i].getStackCount())) - } - var sprite = pokemon.getBattleSpriteAtlasPath() - // floor,party slot,encounter,species,ability,passive,level,gender,isBoss,nature,HP IV,Attack IV,Defense IV,Sp. Atk IV,Sp. Def IV,Speed IV,Items separated by slashes / - var newLine = floor + "," - + slot + "," - + sprite + "," - + (pokemon.hasTrainer() ? "trainer_pokemon" : "wild") + "," - + pokemon.species.getName(pokemon.formIndex) + (pokemon.getFormKey() == "" ? "" : " (" + pokemon.getFormKey() + ")") + "," - + pokemon.getAbility().name.toLowerCase() + "," - + pokemon.getPassiveAbility().name.toLowerCase() + "," - + pokemon.level + "," - + (pokemon.gender == 0 ? "M" : (pokemon.gender == 1 ? "F" : "")) + "," - + (pokemon.isBoss() ? "true" : "false") + "," - + getNatureName(pokemon.nature) + "," - + pokemon.ivs[0] + "," - + pokemon.ivs[1] + "," - + pokemon.ivs[2] + "," - + pokemon.ivs[3] + "," - + pokemon.ivs[4] + "," - + pokemon.ivs[5] + "," - + modifiers.join("/") - //console.log(idx, data.slice(0, idx), newLine, data.slice(idx)) - setRow("e", newLine, floor, slot) - //console.log(localStorage.getItem(logs[logKeys.indexOf("e")][1]).split("\n")) - */ if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) var drpd: DRPD = JSON.parse(localStorage.getItem(getLogID(scene))) as DRPD; console.log("Log Enemy Pokemon", drpd) @@ -563,10 +682,10 @@ export function logPokemon(scene: BattleScene, floor: integer = undefined, slot: "Rare", "Super Rare", "Ultra Rare", - "Common", - "Rare", - "Super Rare", - "Ultra Rare", + "Common Boss", + "Rare Boss", + "Super Rare Boss", + "Ultra Rare Boss", ] for (var i = 0; i < tiernames.length; i++) { if (checkForPokeInBiome(wv.pokemon[slot].id, scene.arena.pokemonPool[i]) == true) { @@ -590,10 +709,10 @@ export function logPokemon(scene: BattleScene, floor: integer = undefined, slot: "Rare", "Super Rare", "Ultra Rare", - "Common", - "Rare", - "Super Rare", - "Ultra Rare", + "Common Boss", + "Rare Boss", + "Super Rare Boss", + "Ultra Rare Boss", ] for (var i = 0; i < tiernames.length; i++) { if (wv.pokemon[slot] != undefined) @@ -603,14 +722,38 @@ export function logPokemon(scene: BattleScene, floor: integer = undefined, slot: } } } - if (pk.rarity == undefined) pk.rarity = "[Unknown]" + if (pk.rarity == undefined) + pk.rarity = "[Unknown]" wv.pokemon[slot] = pk; while (wv.actions.length > 0) wv.actions.pop() + wv.actions = [] + wv.shop = "" console.log(drpd) localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) } +/** + * Clears the action list for a wave. + * @param scene The BattleScene. Used to get the log ID and trainer data. + * @param floor The wave index to write to. Defaults to the current floor. + */ +export function resetWaveActions(scene: BattleScene, floor: integer = undefined) { + if (floor == undefined) floor = scene.currentBattle.waveIndex + if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) + var drpd: DRPD = JSON.parse(localStorage.getItem(getLogID(scene))) as DRPD; + console.log("Clear Actions", drpd) + var wv: Wave = getWave(drpd, floor, scene) + wv.actions = [] + console.log(drpd, wv) + localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) +} +/** + * Logs the current floor's Trainer. + * @param scene The BattleScene. Used to get the log ID and trainer data. + * @param floor The wave index to write to. Defaults to the current floor. + */ export function logTrainer(scene: BattleScene, floor: integer = undefined) { + if (floor == undefined) floor = scene.currentBattle.waveIndex if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) var drpd: DRPD = JSON.parse(localStorage.getItem(getLogID(scene))) as DRPD; console.log("Log Trainer", drpd) @@ -621,6 +764,12 @@ export function logTrainer(scene: BattleScene, floor: integer = undefined) { console.log(drpd) localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) } +/** + * Logs the player's current party. + * + * Called on Floor 1 to store the starters list. + * @param scene The BattleScene. Used to get the log ID and the player's party. + */ export function logPlayerTeam(scene: BattleScene) { if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) var drpd: DRPD = JSON.parse(localStorage.getItem(getLogID(scene))) as DRPD; @@ -633,7 +782,12 @@ export function logPlayerTeam(scene: BattleScene) { console.log(drpd) localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) } - +/** + * A sort function, used to sort csv columns. + * + * No longer used as we are using .json format instead. + * @deprecated + */ export function dataSorter(a: string, b: string) { var da = a.split(",") var db = b.split(",") @@ -648,6 +802,16 @@ export function dataSorter(a: string, b: string) { } return ((da[0] as any) * 1) - ((db[0] as any) * 1) } +/** + * Writes or replaces a csv row. + * + * No longer used as we are using .json format instead. + * @param keyword The keyword/ID of the log to write to. + * @param newLine The data to write. + * @param floor The floor to write to. Used for sorting. + * @param slot The slot to write to. Used for sorting. + * @deprecated + */ export function setRow(keyword: string, newLine: string, floor: integer, slot: integer) { var data = localStorage.getItem(logs[logKeys.indexOf(keyword)][1]).split("\n") data.sort(dataSorter) @@ -713,27 +877,49 @@ export function setRow(keyword: string, newLine: string, floor: integer, slot: i } localStorage.setItem(logs[logKeys.indexOf(keyword)][1], data.slice(0, idx).join("\n") + "\n" + newLine + (data.slice(idx).length == 0 ? "" : "\n") + data.slice(idx).join("\n")); } +/** + * Prints a DRPD as a string, for saving it to your device. + * @param inData The data to add on to. + * @param indent The indent string (just a bunch of spaces). + * @param drpd The `DRPD` to export. + * @returns `inData`, with all the DRPD's data appended to it. + * + * @see printWave + */ export function printDRPD(inData: string, indent: string, drpd: DRPD): string { inData += indent + "{" inData += "\n" + indent + " \"version\": \"" + drpd.version + "\"" inData += ",\n" + indent + " \"title\": \"" + drpd.title + "\"" inData += ",\n" + indent + " \"authors\": [\"" + drpd.authors.join("\", \"") + "\"]" inData += ",\n" + indent + " \"date\": \"" + drpd.date + "\"" - inData += ",\n" + indent + " \"waves\": [\n" - var isFirst = true - for (var i = 0; i < drpd.waves.length; i++) { - if (drpd.waves[i] != undefined) { - if (isFirst) { - isFirst = false; - } else { - inData += ",\n" + if (drpd.waves) { + inData += ",\n" + indent + " \"waves\": [\n" + var isFirst = true + for (var i = 0; i < drpd.waves.length; i++) { + if (drpd.waves[i] != undefined && drpd.waves[i] != null) { + if (isFirst) { + isFirst = false; + } else { + inData += ",\n" + } + inData = printWave(inData, indent + " ", drpd.waves[i]) } - inData = printWave(inData, indent + " ", drpd.waves[i]) } + } else { + inData += ",\n" + indent + " \"waves\": []" } inData += "\n" + indent + " ]\n" + indent + "}" return inData; } +/** + * Prints a wave as a string, for saving a DRPD to your device. + * @param inData The data to add on to. + * @param indent The indent string (just a bunch of spaces). + * @param wave The `Wave` to export. + * @returns `inData`, with all the wave's data appended to it. + * + * @see printDRPD + */ function printWave(inData: string, indent: string, wave: Wave): string { inData += indent + "{" inData += "\n" + indent + " \"id\": " + wave.id + "" @@ -764,24 +950,34 @@ function printWave(inData: string, indent: string, wave: Wave): string { inData += ",\n " + indent + "\"trainer\": " inData = printTrainer(inData, indent + " ", wave.trainer) } - if (wave.pokemon.length > 0) { - inData += ",\n " + indent + "\"pokemon\": [\n" - isFirst = true - for (var i = 0; i < wave.pokemon.length; i++) { - if (wave.pokemon[i] != undefined) { - if (isFirst) { - isFirst = false; - } else { - inData += ",\n" + if (wave.pokemon) + if (wave.pokemon.length > 0) { + inData += ",\n " + indent + "\"pokemon\": [\n" + isFirst = true + for (var i = 0; i < wave.pokemon.length; i++) { + if (wave.pokemon[i] != undefined) { + if (isFirst) { + isFirst = false; + } else { + inData += ",\n" + } + inData = printPoke(inData, indent + " ", wave.pokemon[i]) } - inData = printPoke(inData, indent + " ", wave.pokemon[i]) } + inData += "\n" + indent + " ]" } - inData += "\n" + indent + " ]" - } inData += "\n" + indent + "}" return inData; } +/** + * Prints a Pokemon as a string, for saving a DRPD to your device. + * @param inData The data to add on to. + * @param indent The indent string (just a bunch of spaces). + * @param wave The `PokeData` to export. + * @returns `inData`, with all the Pokemon's data appended to it. + * + * @see printDRPD + */ function printPoke(inData: string, indent: string, pokemon: PokeData) { inData += indent + "{" inData += "\n" + indent + " \"id\": " + pokemon.id @@ -819,6 +1015,15 @@ function printPoke(inData: string, indent: string, pokemon: PokeData) { inData += "\n" + indent + "}" return inData; } +/** + * Prints a Nature as a string, for saving a DRPD to your device. + * @param inData The data to add on to. + * @param indent The indent string (just a bunch of spaces). + * @param wave The `NatureData` to export. + * @returns `inData`, with all the nature data appended to it. + * + * @see printDRPD + */ function printNature(inData: string, indent: string, nature: NatureData) { inData += indent + "{" inData += "\n" + indent + " \"name\": \"" + nature.name + "\"" @@ -827,6 +1032,15 @@ function printNature(inData: string, indent: string, nature: NatureData) { inData += "\n" + indent + "}" return inData; } +/** + * Prints a Pokemon's IV data as a string, for saving a DRPD to your device. + * @param inData The data to add on to. + * @param indent The indent string (just a bunch of spaces). + * @param wave The `IVData` to export. + * @returns `inData`, with the IV data appended to it. + * + * @see printDRPD + */ function printIV(inData: string, indent: string, iv: IVData) { inData += "{" inData += "\n" + indent + " \"hp\": " + iv.hp @@ -838,6 +1052,15 @@ function printIV(inData: string, indent: string, iv: IVData) { inData += "\n" + indent + "}" return inData; } +/** + * Prints a Trainer as a string, for saving a DRPD to your device. + * @param inData The data to add on to. + * @param indent The indent string (just a bunch of spaces). + * @param wave The `TrainerData` to export. + * @returns `inData`, with all the Trainer's data appended to it. + * + * @see printDRPD + */ function printTrainer(inData: string, indent: string, trainer: TrainerData) { inData += "{" inData += "\n" + indent + " \"id\": \"" + trainer.id + "\"" @@ -846,6 +1069,15 @@ function printTrainer(inData: string, indent: string, trainer: TrainerData) { inData += "\n" + indent + "}" return inData; } +/** + * Prints an item as a string, for saving a DRPD to your device. + * @param inData The data to add on to. + * @param indent The indent string (just a bunch of spaces). + * @param wave The `ItemData` to export. + * @returns `inData`, with all the Item's data appended to it. + * + * @see printDRPD + */ function printItem(inData: string, indent: string, item: ItemData) { inData += indent + "{" inData += "\n" + indent + " \"id\": \"" + item.id + "\"" diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index d8ec0072bd4..be365ddd75e 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -64,6 +64,10 @@ export class ModifierType { return i18next.t(`${this.localeKey}.name` as any); } + get identifier(): string { + return "Modifier:" + this.localeKey.split(".")[1]; + } + getDescription(scene: BattleScene): string { return i18next.t(`${this.localeKey}.description` as any); } @@ -159,6 +163,9 @@ class AddPokeballModifierType extends ModifierType { "pokeballName": getPokeballName(this.pokeballType), }); } + get identifier(): string { + return "PokeballModifier:" + Utils.getEnumKeys(PokeballType)[this.pokeballType]; + } getDescription(scene: BattleScene): string { return i18next.t("modifierType:ModifierType.AddPokeballModifierType.description", { @@ -198,6 +205,10 @@ class AddVoucherModifierType extends ModifierType { export class PokemonModifierType extends ModifierType { public selectFilter: PokemonSelectFilter; + get identifier(): string { + return "PokemonModifier:undefined"; + } + constructor(localeKey: string, iconImage: string, newModifierFunc: NewModifierFunc, selectFilter?: PokemonSelectFilter, group?: string, soundName?: string) { super(localeKey, iconImage, newModifierFunc, group, soundName); @@ -221,6 +232,10 @@ export class PokemonHeldItemModifierType extends PokemonModifierType { }, group, soundName); } + get identifier(): string { + return "HeldItem:" + this.localeKey.split(".")[1]; + } + newModifier(...args: any[]): Modifiers.PokemonHeldItemModifier { return super.newModifier(...args) as Modifiers.PokemonHeldItemModifier; } @@ -245,6 +260,10 @@ export class PokemonHpRestoreModifierType extends PokemonModifierType { this.healStatus = healStatus; } + get identifier(): string { + return "HpRestore:" + this.localeKey.split(".")[1]; + } + getDescription(scene: BattleScene): string { return this.restorePoints ? i18next.t("modifierType:ModifierType.PokemonHpRestoreModifierType.description", { @@ -274,6 +293,9 @@ export class PokemonReviveModifierType extends PokemonHpRestoreModifierType { return null; }; } + get identifier(): string { + return "Revive:" + this.localeKey.split(".")[1]; + } getDescription(scene: BattleScene): string { return i18next.t("modifierType:ModifierType.PokemonReviveModifierType.description", { restorePercent: this.restorePercent }); @@ -291,6 +313,10 @@ export class PokemonStatusHealModifierType extends PokemonModifierType { })); } + get identifier(): string { + return "StatusCure:" + this.localeKey.split(".")[1]; + } + getDescription(scene: BattleScene): string { return i18next.t("modifierType:ModifierType.PokemonStatusHealModifierType.description"); } @@ -323,6 +349,10 @@ export class PokemonPpRestoreModifierType extends PokemonMoveModifierType { this.restorePoints = restorePoints; } + get identifier(): string { + return "PpRestore:" + this.localeKey.split(".")[1]; + } + getDescription(scene: BattleScene): string { return this.restorePoints > -1 ? i18next.t("modifierType:ModifierType.PokemonPpRestoreModifierType.description", { restorePoints: this.restorePoints }) @@ -346,6 +376,10 @@ export class PokemonAllMovePpRestoreModifierType extends PokemonModifierType { this.restorePoints = restorePoints; } + get identifier(): string { + return "PpAllRestore:" + this.localeKey.split(".")[1]; + } + getDescription(scene: BattleScene): string { return this.restorePoints > -1 ? i18next.t("modifierType:ModifierType.PokemonAllMovePpRestoreModifierType.description", { restorePoints: this.restorePoints }) @@ -371,6 +405,10 @@ export class PokemonPpUpModifierType extends PokemonMoveModifierType { this.upPoints = upPoints; } + get identifier(): string { + return "PpBooster:" + this.localeKey.split(".")[1]; + } + getDescription(scene: BattleScene): string { return i18next.t("modifierType:ModifierType.PokemonPpUpModifierType.description", { upPoints: this.upPoints }); } @@ -395,6 +433,10 @@ export class PokemonNatureChangeModifierType extends PokemonModifierType { return i18next.t("modifierType:ModifierType.PokemonNatureChangeModifierType.name", { natureName: getNatureName(this.nature) }); } + get identifier(): string { + return "Mint:" + this.localeKey.split(".")[1]; + } + getDescription(scene: BattleScene): string { return i18next.t("modifierType:ModifierType.PokemonNatureChangeModifierType.description", { natureName: getNatureName(this.nature, true, true, true) }); } @@ -410,6 +452,10 @@ export class RememberMoveModifierType extends PokemonModifierType { return null; }, group); } + + get identifier(): string { + return "MemoryMushroom:" + this.localeKey.split(".")[1]; + } } export class DoubleBattleChanceBoosterModifierType extends ModifierType { @@ -421,6 +467,10 @@ export class DoubleBattleChanceBoosterModifierType extends ModifierType { this.battleCount = battleCount; } + get identifier(): string { + return "DoubleModifier:" + this.localeKey.split(".")[1]; + } + getDescription(scene: BattleScene): string { return i18next.t("modifierType:ModifierType.DoubleBattleChanceBoosterModifierType.description", { battleCount: this.battleCount }); } @@ -440,6 +490,10 @@ export class TempBattleStatBoosterModifierType extends ModifierType implements G return i18next.t(`modifierType:TempBattleStatBoosterItem.${getTempBattleStatBoosterItemName(this.tempBattleStat).replace(/\./g, "").replace(/[ ]/g, "_").toLowerCase()}`); } + get identifier(): string { + return "TempStatBooster:" + Utils.getEnumKeys(TempBattleStat)[this.tempBattleStat] + } + getDescription(scene: BattleScene): string { return i18next.t("modifierType:ModifierType.TempBattleStatBoosterModifierType.description", { tempBattleStatName: getTempBattleStatName(this.tempBattleStat) }); } @@ -462,6 +516,10 @@ export class BerryModifierType extends PokemonHeldItemModifierType implements Ge return getBerryName(this.berryType); } + get identifier(): string { + return "Berry:" + Utils.getEnumKeys(BerryType)[this.berryType] + } + getDescription(scene: BattleScene): string { return getBerryEffectDescription(this.berryType); } @@ -528,6 +586,10 @@ export class AttackTypeBoosterModifierType extends PokemonHeldItemModifierType i return i18next.t(`modifierType:AttackTypeBoosterItem.${getAttackTypeBoosterItemName(this.moveType).replace(/[ \-]/g, "_").toLowerCase()}`); } + get identifier(): string { + return "MoveBooster:" + Utils.getEnumKeys(Type)[this.moveType] + } + getDescription(scene: BattleScene): string { // TODO: Need getTypeName? return i18next.t("modifierType:ModifierType.AttackTypeBoosterModifierType.description", { moveType: i18next.t(`pokemonInfo:Type.${Type[this.moveType]}`) }); @@ -554,6 +616,10 @@ export class SpeciesStatBoosterModifierType extends PokemonHeldItemModifierType this.key = key; } + + get identifier(): string { + return "SpeciesBooster:" + this.key + } getPregenArgs(): any[] { return [ this.key ]; @@ -565,6 +631,10 @@ export class PokemonLevelIncrementModifierType extends PokemonModifierType { super(localeKey, iconImage, (_type, args) => new Modifiers.PokemonLevelIncrementModifier(this, (args[0] as PlayerPokemon).id), (_pokemon: PlayerPokemon) => null); } + get identifier(): string { + return "RareCandy:" + this.localeKey.split(".")[1] + } + getDescription(scene: BattleScene): string { return i18next.t("modifierType:ModifierType.PokemonLevelIncrementModifierType.description"); } @@ -575,6 +645,10 @@ export class AllPokemonLevelIncrementModifierType extends ModifierType { super(localeKey, iconImage, (_type, _args) => new Modifiers.PokemonLevelIncrementModifier(this, -1)); } + get identifier(): string { + return "RareCandy:" + this.localeKey.split(".")[1] + } + getDescription(scene: BattleScene): string { return i18next.t("modifierType:ModifierType.AllPokemonLevelIncrementModifierType.description"); } @@ -612,6 +686,10 @@ export class PokemonBaseStatBoosterModifierType extends PokemonHeldItemModifierT return i18next.t(`modifierType:BaseStatBoosterItem.${this.localeName.replace(/[ \-]/g, "_").toLowerCase()}`); } + get identifier(): string { + return "StatBooster:" + Utils.getEnumKeys(Stat)[this.stat] + } + getDescription(scene: BattleScene): string { return i18next.t("modifierType:ModifierType.PokemonBaseStatBoosterModifierType.description", { statName: getStatName(this.stat) }); } @@ -630,6 +708,10 @@ class AllPokemonFullHpRestoreModifierType extends ModifierType { this.descriptionKey = descriptionKey; } + get identifier(): string { + return "HealAll:" + this.localeKey.split(".")[1] + } + getDescription(scene: BattleScene): string { return i18next.t(`${this.descriptionKey || "modifierType:ModifierType.AllPokemonFullHpRestoreModifierType"}.description` as any); } @@ -639,6 +721,10 @@ class AllPokemonFullReviveModifierType extends AllPokemonFullHpRestoreModifierTy constructor(localeKey: string, iconImage: string) { super(localeKey, iconImage, "modifierType:ModifierType.AllPokemonFullReviveModifierType", (_type, _args) => new Modifiers.PokemonHpRestoreModifier(this, -1, 0, 100, false, true)); } + + get identifier(): string { + return "ReviveAll:" + this.localeKey.split(".")[1] + } } export class MoneyRewardModifierType extends ModifierType { @@ -652,6 +738,10 @@ export class MoneyRewardModifierType extends ModifierType { this.moneyMultiplierDescriptorKey = moneyMultiplierDescriptorKey; } + get identifier(): string { + return "Money:" + this.localeKey.split(".")[1] + } + getDescription(scene: BattleScene): string { const moneyAmount = new Utils.IntegerHolder(scene.getWaveMoneyAmount(this.moneyMultiplier)); scene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount); @@ -673,6 +763,10 @@ export class ExpBoosterModifierType extends ModifierType { this.boostPercent = boostPercent; } + get identifier(): string { + return "ExpBooster:" + this.localeKey.split(".")[1] + } + getDescription(scene: BattleScene): string { return i18next.t("modifierType:ModifierType.ExpBoosterModifierType.description", { boostPercent: this.boostPercent }); } @@ -687,6 +781,10 @@ export class PokemonExpBoosterModifierType extends PokemonHeldItemModifierType { this.boostPercent = boostPercent; } + get identifier(): string { + return "PokemonExpBooster:" + this.localeKey.split(".")[1] + } + getDescription(scene: BattleScene): string { return i18next.t("modifierType:ModifierType.PokemonExpBoosterModifierType.description", { boostPercent: this.boostPercent }); } @@ -697,6 +795,10 @@ export class PokemonFriendshipBoosterModifierType extends PokemonHeldItemModifie super(localeKey, iconImage, (_type, args) => new Modifiers.PokemonFriendshipBoosterModifier(this, (args[0] as Pokemon).id)); } + get identifier(): string { + return "FriendshipBooster:" + this.localeKey.split(".")[1] + } + getDescription(scene: BattleScene): string { return i18next.t("modifierType:ModifierType.PokemonFriendshipBoosterModifierType.description"); } @@ -711,6 +813,10 @@ export class PokemonMoveAccuracyBoosterModifierType extends PokemonHeldItemModif this.amount = amount; } + get identifier(): string { + return "AccuracyBooster:" + this.localeKey.split(".")[1] + } + getDescription(scene: BattleScene): string { return i18next.t("modifierType:ModifierType.PokemonMoveAccuracyBoosterModifierType.description", { accuracyAmount: this.amount }); } @@ -721,6 +827,10 @@ export class PokemonMultiHitModifierType extends PokemonHeldItemModifierType { super(localeKey, iconImage, (type, args) => new Modifiers.PokemonMultiHitModifier(type as PokemonMultiHitModifierType, (args[0] as Pokemon).id)); } + get identifier(): string { + return "MultiHit:" + this.localeKey.split(".")[1] + } + getDescription(scene: BattleScene): string { return i18next.t("modifierType:ModifierType.PokemonMultiHitModifierType.description"); } @@ -728,8 +838,9 @@ export class PokemonMultiHitModifierType extends PokemonHeldItemModifierType { export class TmModifierType extends PokemonModifierType { public moveId: Moves; + public rarity: string; - constructor(moveId: Moves) { + constructor(moveId: Moves, rarity: ModifierTier) { super("", `tm_${Type[allMoves[moveId].type].toLowerCase()}`, (_type, args) => new Modifiers.TmModifier(this, (args[0] as PlayerPokemon).id), (pokemon: PlayerPokemon) => { if (pokemon.compatibleTms.indexOf(moveId) === -1 || pokemon.getMoveset().filter(m => m?.moveId === moveId).length) { @@ -739,6 +850,26 @@ export class TmModifierType extends PokemonModifierType { }, "tm"); this.moveId = moveId; + switch (rarity) { + case ModifierTier.COMMON: + this.rarity = "Common" + break; + case ModifierTier.GREAT: + this.rarity = "Great" + break; + case ModifierTier.ULTRA: + this.rarity = "Ultra" + break; + case ModifierTier.ROGUE: + this.rarity = "Rogue" + break; + case ModifierTier.MASTER: + this.rarity = "Master" + break; + case ModifierTier.LUXURY: + this.rarity = "Luxury" + break; + } } get name(): string { @@ -748,6 +879,10 @@ export class TmModifierType extends PokemonModifierType { }); } + get identifier(): string { + return "Tm" + this.rarity + ":" + Utils.getEnumKeys(Moves)[this.moveId] + } + getDescription(scene: BattleScene): string { return i18next.t(scene.enableMoveInfo ? "modifierType:ModifierType.TmModifierTypeWithInfo.description" : "modifierType:ModifierType.TmModifierType.description", { moveName: allMoves[this.moveId].name }); } @@ -777,6 +912,10 @@ export class EvolutionItemModifierType extends PokemonModifierType implements Ge return i18next.t(`modifierType:EvolutionItem.${EvolutionItem[this.evolutionItem]}`); } + get identifier(): string { + return "Evolution:" + Utils.getEnumKeys(EvolutionItem)[this.evolutionItem] + } + getDescription(scene: BattleScene): string { return i18next.t("modifierType:ModifierType.EvolutionItemModifierType.description"); } @@ -815,6 +954,9 @@ export class FormChangeItemModifierType extends PokemonModifierType implements G get name(): string { return i18next.t(`modifierType:FormChangeItem.${FormChangeItem[this.formChangeItem]}`); } + get identifier(): string { + return "FormChange:" + Utils.getEnumKeys(FormChangeItem)[this.formChangeItem] + } getDescription(scene: BattleScene): string { return i18next.t("modifierType:ModifierType.FormChangeItemModifierType.description"); @@ -836,6 +978,10 @@ export class FusePokemonModifierType extends PokemonModifierType { }); } + get identifier(): string { + return "Fusion:" + this.localeKey.split(".")[1] + } + getDescription(scene: BattleScene): string { return i18next.t("modifierType:ModifierType.FusePokemonModifierType.description"); } @@ -975,7 +1121,7 @@ class TmModifierTypeGenerator extends ModifierTypeGenerator { return null; } const randTmIndex = Utils.randSeedInt(tierUniqueCompatibleTms.length); - return new TmModifierType(tierUniqueCompatibleTms[randTmIndex]); + return new TmModifierType(tierUniqueCompatibleTms[randTmIndex], tier); }); } } @@ -1044,6 +1190,10 @@ export class TerastallizeModifierType extends PokemonHeldItemModifierType implem return i18next.t("modifierType:ModifierType.TerastallizeModifierType.name", { teraType: i18next.t(`pokemonInfo:Type.${Type[this.teraType]}`) }); } + get identifier(): string { + return "TeraShard:" + Utils.getEnumKeys(Type)[this.teraType] + } + getDescription(scene: BattleScene): string { return i18next.t("modifierType:ModifierType.TerastallizeModifierType.description", { teraType: i18next.t(`pokemonInfo:Type.${Type[this.teraType]}`) }); } diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 6f098ade124..d965e2a45b1 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -143,6 +143,10 @@ export abstract class Modifier { return true; } + get identifier(): string { + return this.type.identifier; + } + abstract apply(args: any[]): boolean | Promise; } diff --git a/src/phases.ts b/src/phases.ts index b95a86614dd..e9d8c637f82 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -1333,6 +1333,7 @@ export class EncounterPhase extends BattlePhase { if (this.scene.currentBattle.waveIndex == 1) { LoggerTools.logPlayerTeam(this.scene) } + LoggerTools.resetWaveActions(this.scene) if (this.scene.currentBattle.battleType === BattleType.WILD) { enemyField.forEach(enemyPokemon => { @@ -2167,6 +2168,7 @@ export class CheckSwitchPhase extends BattlePhase { this.scene.ui.showText(i18next.t("battle:switchQuestion", { pokemonName: this.useName ? pokemon.name : i18next.t("battle:pokemon") }), null, () => { this.scene.ui.setMode(Mode.CONFIRM, () => { this.scene.ui.setMode(Mode.MESSAGE); + LoggerTools.isPreSwitch.value = true this.scene.tryRemovePhase(p => p instanceof PostSummonPhase && p.player && p.fieldIndex === this.fieldIndex); this.scene.unshiftPhase(new SwitchPhase(this.scene, this.fieldIndex, false, true)); for (var i = 0; i < this.scene.getEnemyField().length; i++) { @@ -4968,11 +4970,13 @@ export class SwitchPhase extends BattlePhase { // Skip modal switch if impossible if (this.isModal && !this.scene.getParty().filter(p => p.isAllowedInBattle() && !p.isActive(true)).length) { + LoggerTools.isPreSwitch.value = false; return super.end(); } // Check if there is any space still in field if (this.isModal && this.scene.getPlayerField().filter(p => p.isAllowedInBattle() && p.isActive(true)).length >= this.scene.currentBattle.getBattlerCount()) { + LoggerTools.isPreSwitch.value = false; return super.end(); } @@ -4981,8 +4985,12 @@ export class SwitchPhase extends BattlePhase { this.scene.ui.setMode(Mode.PARTY, this.isModal ? PartyUiMode.FAINT_SWITCH : PartyUiMode.POST_BATTLE_SWITCH, fieldIndex, (slotIndex: integer, option: PartyOption) => { if (slotIndex >= this.scene.currentBattle.getBattlerCount() && slotIndex < 6) { + if (LoggerTools.isPreSwitch.value) { + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, "Pre-switch " + LoggerTools.playerPokeName(this.scene, fieldIndex) + (option == PartyOption.PASS_BATON ? " → Baton" : "") + " → " + LoggerTools.playerPokeName(this.scene, slotIndex)) + } this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, fieldIndex, slotIndex, this.doReturn, option === PartyOption.PASS_BATON)); } + LoggerTools.isPreSwitch.value = false; this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end()); }, PartyUiHandler.FilterNonFainted); } @@ -5179,6 +5187,7 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { this.scene.ui.showText(i18next.t("battle:learnMoveStopTeaching", { moveName: move.name }), null, () => { this.scene.ui.setModeWithoutClear(Mode.CONFIRM, () => { this.scene.ui.setMode(messageMode); + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, LoggerTools.playerPokeName(this.scene, pokemon) + " | Skip " + move.name) this.scene.ui.showText(i18next.t("battle:learnMoveNotLearned", { pokemonName: pokemon.name, moveName: move.name }), null, () => this.end(), null, true); }, () => { this.scene.ui.setMode(messageMode); @@ -5200,6 +5209,7 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { this.scene.ui.showText(i18next.t("battle:countdownPoof"), null, () => { this.scene.ui.showText(i18next.t("battle:learnMoveForgetSuccess", { pokemonName: pokemon.name, moveName: pokemon.moveset[moveIndex].getName() }), null, () => { this.scene.ui.showText(i18next.t("battle:learnMoveAnd"), null, () => { + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, LoggerTools.playerPokeName(this.scene, pokemon) + " | Replace " + pokemon.moveset[moveIndex].getName() + " with " + new PokemonMove(this.moveId).getName()) pokemon.setMove(moveIndex, Moves.NONE); this.scene.unshiftPhase(new LearnMovePhase(this.scene, this.partyMemberIndex, this.moveId)); this.end(); @@ -5663,7 +5673,7 @@ export class SelectModifierPhase extends BattlePhase { if (rowCursor < 0 || cursor < 0) { this.scene.ui.showText(i18next.t("battle:skipItemQuestion"), null, () => { this.scene.ui.setOverlayMode(Mode.CONFIRM, () => { - LoggerTools.logShop(this.scene, this.scene.currentBattle.waveIndex, "") + LoggerTools.logShop(this.scene, this.scene.currentBattle.waveIndex, "Skip taking items") this.scene.ui.revertMode(); this.scene.ui.setMode(Mode.MESSAGE); super.end(); @@ -5773,6 +5783,7 @@ export class SelectModifierPhase extends BattlePhase { if (modifierType instanceof FusePokemonModifierType) { this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.SPLICE, -1, (fromSlotIndex: integer, spliceSlotIndex: integer) => { if (spliceSlotIndex !== undefined && fromSlotIndex < 6 && spliceSlotIndex < 6 && fromSlotIndex !== spliceSlotIndex) { + LoggerTools.logShop(this.scene, this.scene.currentBattle.waveIndex, modifierType.name + " → " + this.scene.getParty()[fromSlotIndex].name + " + " + this.scene.getParty()[spliceSlotIndex].name) this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer()).then(() => { const modifier = modifierType.newModifier(party[fromSlotIndex], party[spliceSlotIndex]); applyModifier(modifier, true); @@ -5802,6 +5813,7 @@ export class SelectModifierPhase extends BattlePhase { ? modifierType.newModifier(party[slotIndex]) : modifierType.newModifier(party[slotIndex], option as integer) : modifierType.newModifier(party[slotIndex], option - PartyOption.MOVE_1); + LoggerTools.logShop(this.scene, this.scene.currentBattle.waveIndex, modifierType.name + " → " + this.scene.getParty()[slotIndex].name) applyModifier(modifier, true); }); } else { @@ -5810,6 +5822,7 @@ export class SelectModifierPhase extends BattlePhase { }, pokemonModifierType.selectFilter, modifierType instanceof PokemonMoveModifierType ? (modifierType as PokemonMoveModifierType).moveSelectFilter : undefined, tmMoveId, isPpRestoreModifier); } } else { + LoggerTools.logShop(this.scene, this.scene.currentBattle.waveIndex, modifierType.name) applyModifier(modifierType.newModifier()); } @@ -6015,6 +6028,7 @@ export class ScanIvsPhase extends PokemonPhase { this.scene.ui.showText(i18next.t("battle:ivScannerUseQuestion", { pokemonName: pokemon.name }), null, () => { this.scene.ui.setMode(Mode.CONFIRM, () => { + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, "IV Scanner → " + LoggerTools.enemyPokeName(this.scene, pokemon)) this.scene.ui.setMode(Mode.MESSAGE); this.scene.ui.clearText(); new CommonBattleAnim(CommonAnim.LOCK_ON, pokemon, pokemon).play(this.scene, () => { diff --git a/src/system/settings/settings.ts b/src/system/settings/settings.ts index 920df6b1063..534155a40a5 100644 --- a/src/system/settings/settings.ts +++ b/src/system/settings/settings.ts @@ -258,7 +258,7 @@ export const Setting: Array = [ value: "Percent" }], default: 0, - type: SettingType.GENERAL + type: SettingType.GENERAL, }, { key: SettingKeys.Tutorials, diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index 147142eacc4..36eba44f09d 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -698,9 +698,9 @@ export default class FightUiHandler extends UiHandler { this.ppText.setText(`${Utils.padInt(pp, 2, " ")}/${Utils.padInt(maxPP, 2, " ")}`); this.powerText.setText(`${power >= 0 ? power : "---"}`); this.accuracyText.setText(`${accuracy >= 0 ? accuracy : "---"}`); - this.accuracyText.setText(`${accuracy1 >= 0 ? accuracy1 : "---"}`); + this.accuracyText.setText(`${accuracy1 >= 0 ? Math.round(accuracy1) : "---"}`); if (this.scene.getEnemyField()[1] != undefined) - this.accuracyText.setText(`${accuracy1 >= 0 ? accuracy1 : "---"}/${accuracy2 >= 0 ? accuracy2 : "---"}`); + this.accuracyText.setText(`${accuracy1 >= 0 ? Math.round(accuracy1) : "---"}/${accuracy2 >= 0 ? Math.round(accuracy2) : "---"}`); const ppPercentLeft = pp / maxPP; diff --git a/src/ui/log-name-form-ui-handler.ts b/src/ui/log-name-form-ui-handler.ts new file mode 100644 index 00000000000..c1f57239749 --- /dev/null +++ b/src/ui/log-name-form-ui-handler.ts @@ -0,0 +1,86 @@ +import { FormModalUiHandler } from "./form-modal-ui-handler"; +import { ModalConfig } from "./modal-ui-handler"; +import * as Utils from "../utils"; +import { Mode } from "./ui"; +import i18next from "i18next"; + +export default class LogNameFormUiHandler extends FormModalUiHandler { + getModalTitle(config?: ModalConfig): string { + return "Create game log"; + } + + getFields(config?: ModalConfig): string[] { + return [ i18next.t("menu:username"), i18next.t("menu:password") ]; + } + + getWidth(config?: ModalConfig): number { + return 160; + } + + getMargin(config?: ModalConfig): [number, number, number, number] { + return [ 0, 0, 48, 0 ]; + } + + getButtonLabels(config?: ModalConfig): string[] { + return [ i18next.t("menu:login"), i18next.t("menu:register") ]; + } + + getReadableErrorMessage(error: string): string { + const colonIndex = error?.indexOf(":"); + if (colonIndex > 0) { + error = error.slice(0, colonIndex); + } + switch (error) { + case "invalid username": + return i18next.t("menu:invalidLoginUsername"); + case "invalid password": + return i18next.t("menu:invalidLoginPassword"); + case "account doesn't exist": + return i18next.t("menu:accountNonExistent"); + case "password doesn't match": + return i18next.t("menu:unmatchingPassword"); + } + + return super.getReadableErrorMessage(error); + } + + show(args: any[]): boolean { + if (super.show(args)) { + const config = args[0] as ModalConfig; + + const originalLoginAction = this.submitAction; + this.submitAction = (_) => { + // Prevent overlapping overrides on action modification + this.submitAction = originalLoginAction; + this.sanitizeInputs(); + this.scene.ui.setMode(Mode.LOADING, { buttonActions: [] }); + const onFail = error => { + this.scene.ui.setMode(Mode.LOGIN_FORM, Object.assign(config, { errorMessage: error?.trim() })); + this.scene.ui.playError(); + }; + if (!this.inputs[0].text) { + return onFail(i18next.t("menu:emptyUsername")); + } + Utils.apiPost("account/login", `username=${encodeURIComponent(this.inputs[0].text)}&password=${encodeURIComponent(this.inputs[1].text)}`, "application/x-www-form-urlencoded") + .then(response => { + if (!response.ok) { + return response.text(); + } + return response.json(); + }) + .then(response => { + if (response.hasOwnProperty("token")) { + Utils.setCookie(Utils.sessionIdKey, response.token); + originalLoginAction(); + } else { + onFail(response); + } + }); + }; + + return true; + } + + return false; + } +} diff --git a/src/ui/ui.ts b/src/ui/ui.ts index ce834a83645..fe50679f566 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -83,7 +83,8 @@ export enum Mode { SESSION_RELOAD, UNAVAILABLE, OUTDATED, - CHALLENGE_SELECT + CHALLENGE_SELECT, + NAME_LOG } const transitionModes = [ @@ -119,7 +120,8 @@ const noTransitionModes = [ Mode.LOADING, Mode.SESSION_RELOAD, Mode.UNAVAILABLE, - Mode.OUTDATED + Mode.OUTDATED, + Mode.NAME_LOG ]; export default class UI extends Phaser.GameObjects.Container { From c1d1dbe4c47b5a0889ccb391171b3d487fa80641 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Fri, 12 Jul 2024 15:42:17 -0400 Subject: [PATCH 38/94] Add update feature --- src/logger.ts | 46 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/src/logger.ts b/src/logger.ts index e142c451a8c..bd41186bd03 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -123,9 +123,10 @@ export const isPreSwitch: Utils.BooleanHolder = new Utils.BooleanHolder(false); export var StoredLog: DRPD = undefined; -export const DRPD_Version = "1.0.0" +export const DRPD_Version = "1.0.0a" export const acceptedVersions = [ - "1.0.0" + "1.0.0", + "1.0.0a", ] export interface DRPD { version: string, @@ -180,7 +181,7 @@ export interface TrainerData { type: string, } export interface ItemData { - id: integer, + id: string, name: string, quantity: integer, } @@ -229,7 +230,7 @@ export function exportPokemon(pokemon: Pokemon, encounterRarity?: string): PokeD rarity: encounterRarity, captured: false, level: pokemon.level, - items: pokemon.getHeldItems().map((item, idx) => exportItem(item, idx)), + items: pokemon.getHeldItems().map((item, idx) => exportItem(item)), ivs: exportIVs(pokemon.ivs) } } @@ -250,9 +251,9 @@ export function exportNature(nature: Nature): NatureData { * @param item The item to store. * @returns The item data. */ -export function exportItem(item: PokemonHeldItemModifier, index: integer): ItemData { +export function exportItem(item: PokemonHeldItemModifier): ItemData { return { - id: index, + id: item.type.identifier, name: item.type.name, quantity: item.getStackCount() } @@ -487,6 +488,7 @@ export function logTeam(scene: BattleScene, floor: integer = undefined) { export function logActions(scene: BattleScene, floor: integer, action: string) { if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) var drpd: DRPD = JSON.parse(localStorage.getItem(getLogID(scene))) as DRPD; + drpd = updateLog(drpd); console.log("Log Action", drpd) var wv: Wave = getWave(drpd, floor, scene) wv.actions.push(action) @@ -502,6 +504,7 @@ export function logActions(scene: BattleScene, floor: integer, action: string) { export function logShop(scene: BattleScene, floor: integer, action: string) { if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) var drpd: DRPD = JSON.parse(localStorage.getItem(getLogID(scene))) as DRPD; + drpd = updateLog(drpd); console.log("Log Shop Item", drpd) var wv: Wave = getWave(drpd, floor, scene) wv.shop = action @@ -667,6 +670,7 @@ export function logPokemon(scene: BattleScene, floor: integer = undefined, slot: if (floor == undefined) floor = scene.currentBattle.waveIndex if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) var drpd: DRPD = JSON.parse(localStorage.getItem(getLogID(scene))) as DRPD; + drpd = updateLog(drpd); console.log("Log Enemy Pokemon", drpd) var wv: Wave = getWave(drpd, floor, scene) var pk: PokeData = exportPokemon(pokemon, encounterRarity) @@ -740,7 +744,8 @@ export function logPokemon(scene: BattleScene, floor: integer = undefined, slot: export function resetWaveActions(scene: BattleScene, floor: integer = undefined) { if (floor == undefined) floor = scene.currentBattle.waveIndex if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) - var drpd: DRPD = JSON.parse(localStorage.getItem(getLogID(scene))) as DRPD; + var drpd: DRPD = JSON.parse(localStorage.getItem(getLogID(scene))) as DRPD; + drpd = updateLog(drpd); console.log("Clear Actions", drpd) var wv: Wave = getWave(drpd, floor, scene) wv.actions = [] @@ -756,6 +761,7 @@ export function logTrainer(scene: BattleScene, floor: integer = undefined) { if (floor == undefined) floor = scene.currentBattle.waveIndex if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) var drpd: DRPD = JSON.parse(localStorage.getItem(getLogID(scene))) as DRPD; + drpd = updateLog(drpd); console.log("Log Trainer", drpd) var wv: Wave = getWave(drpd, floor, scene) var t: TrainerData = exportTrainer(scene.currentBattle.trainer) @@ -774,6 +780,7 @@ export function logPlayerTeam(scene: BattleScene) { if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) var drpd: DRPD = JSON.parse(localStorage.getItem(getLogID(scene))) as DRPD; //var wv: Wave = getWave(drpd, 1, scene) + drpd = updateLog(drpd); console.log("Log Player Starters", drpd) var P = scene.getParty() for (var i = 0; i < P.length; i++) { @@ -1085,4 +1092,29 @@ function printItem(inData: string, indent: string, item: ItemData) { inData += ",\n" + indent + " \"quantity\": " + item.quantity inData += "\n" + indent + "}" return inData; +} + + +function updateLog(drpd: DRPD): DRPD { + if (drpd.version == "1.0.0") { + drpd.version = "1.0.0a" + console.log("Updated to 1.0.0a - changed item IDs to strings") + for (var i = 0; i < drpd.waves.length; i++) { + if (drpd.waves[i] != undefined) { + if (drpd.waves[i].pokemon != undefined) { + for (var j = 0; j < drpd.waves[i].pokemon.length; j++) { + for (var k = 0; k < drpd.waves[i].pokemon[j].items.length; k++) { + drpd.waves[i].pokemon[j].items[k].id = drpd.waves[i].pokemon[j].items[k].id.toString() + } + } + } + } + } + for (var j = 0; j < drpd.starters.length; j++) { + for (var k = 0; k < drpd.starters[j].items.length; k++) { + drpd.starters[j].items[k].id = drpd.starters[j].items[k].id.toString() + } + } + } // 1.0.0 → 1.0.0a + return drpd; } \ No newline at end of file From bfdce2b1f81855edc79401512810a601a5ec6b8e Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Fri, 12 Jul 2024 16:02:57 -0400 Subject: [PATCH 39/94] Change ID for Rare Evolution Items --- src/modifier/modifier-type.ts | 2 +- src/ui/log-name-form-ui-handler.ts | 6 +++--- src/ui/ui.ts | 4 +++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index be365ddd75e..91702496c8d 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -913,7 +913,7 @@ export class EvolutionItemModifierType extends PokemonModifierType implements Ge } get identifier(): string { - return "Evolution:" + Utils.getEnumKeys(EvolutionItem)[this.evolutionItem] + return "Evolution" + (this.evolutionItem > 50 ? "Rare" : "") + ":" + Utils.getEnumKeys(EvolutionItem)[this.evolutionItem] } getDescription(scene: BattleScene): string { diff --git a/src/ui/log-name-form-ui-handler.ts b/src/ui/log-name-form-ui-handler.ts index c1f57239749..83a883c73ab 100644 --- a/src/ui/log-name-form-ui-handler.ts +++ b/src/ui/log-name-form-ui-handler.ts @@ -6,11 +6,11 @@ import i18next from "i18next"; export default class LogNameFormUiHandler extends FormModalUiHandler { getModalTitle(config?: ModalConfig): string { - return "Create game log"; + return "New Log"; } getFields(config?: ModalConfig): string[] { - return [ i18next.t("menu:username"), i18next.t("menu:password") ]; + return [ "Name", "Author(s)" ]; } getWidth(config?: ModalConfig): number { @@ -22,7 +22,7 @@ export default class LogNameFormUiHandler extends FormModalUiHandler { } getButtonLabels(config?: ModalConfig): string[] { - return [ i18next.t("menu:login"), i18next.t("menu:register") ]; + return [ "Create" ]; } getReadableErrorMessage(error: string): string { diff --git a/src/ui/ui.ts b/src/ui/ui.ts index fe50679f566..eb3be06fed8 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -46,6 +46,7 @@ import SettingsDisplayUiHandler from "./settings/settings-display-ui-handler"; import SettingsAudioUiHandler from "./settings/settings-audio-ui-handler"; import { PlayerGender } from "#enums/player-gender"; import BgmBar from "#app/ui/bgm-bar"; +import LogNameFormUiHandler from "./log-name-form-ui-handler"; export enum Mode { MESSAGE, @@ -182,7 +183,8 @@ export default class UI extends Phaser.GameObjects.Container { new SessionReloadModalUiHandler(scene), new UnavailableModalUiHandler(scene), new OutdatedModalUiHandler(scene), - new GameChallengesUiHandler(scene) + new GameChallengesUiHandler(scene), + new LogNameFormUiHandler(scene) ]; } From d464e461502a3f0d1831d68b228bf725078ec00d Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Fri, 12 Jul 2024 16:57:44 -0400 Subject: [PATCH 40/94] Add UI to rename files --- src/logger.ts | 66 +++++++++++++++++++++++++++++- src/ui/log-name-form-ui-handler.ts | 34 +++++++-------- src/ui/ui.ts | 6 +-- 3 files changed, 84 insertions(+), 22 deletions(-) diff --git a/src/logger.ts b/src/logger.ts index bd41186bd03..ba7e38d52d4 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -11,7 +11,7 @@ import Battle from "./battle"; import { getBiomeName, PokemonPools, SpeciesTree } from "./data/biomes"; import { trainerConfigs } from "./data/trainer-config"; import { Mode } from "./ui/ui"; -import { TitlePhase } from "./phases"; +import { LoginPhase, TitlePhase } from "./phases"; import { Item } from "pokenode-ts"; import Trainer from "./field/trainer"; import { Species } from "./enums/species"; @@ -117,7 +117,7 @@ export function enemyPokeName(scene: BattleScene, index: integer | Pokemon | Ene // LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, "") export const rarities = [] -export const rarityslot = [0] +export const rarityslot = [0, ""] export const isPreSwitch: Utils.BooleanHolder = new Utils.BooleanHolder(false); @@ -376,6 +376,47 @@ export function generateOption(i: integer, saves: any): OptionSelectItem { } return op; } +/** + * Generates a UI option to save a log to your device. + * @param i The slot number. Corresponds to an index in `logs`. + * @param saves Your session data. Used to label logs if they match one of your save slots. + * @returns A UI option. + */ +export function generateEditOption(scene: BattleScene, i: integer, saves: any, phase: TitlePhase): OptionSelectItem { + var filename: string = (JSON.parse(localStorage.getItem(logs[i][1])) as DRPD).title + var op: OptionSelectItem = { + label: `Export ${filename} (${getSize(printDRPD("", "", JSON.parse(localStorage.getItem(logs[i][1])) as DRPD))})`, + handler: () => { + rarityslot[1] = logs[i][1] + //scene.phaseQueue[0].end() + scene.ui.setMode(Mode.NAME_LOG, { + autofillfields: [ + (JSON.parse(localStorage.getItem(logs[i][1])) as DRPD).title, + (JSON.parse(localStorage.getItem(logs[i][1])) as DRPD).authors.join(", ") + ], + buttonActions: [ + () => { + scene.ui.playSelect(); + console.log("Ending UI phase thingy"); + phase.callEnd() + } + ] + }); + return false; + } + } + for (var j = 0; j < saves.length; j++) { + console.log(saves[j].seed, logs[i][2], saves[j].seed == logs[i][2]) + if (saves[j].seed == logs[i][2]) { + op.label = "[Slot " + (saves[j].slot + 1) + "]" + op.label.substring(6) + } + } + if (logs[i][4] != "") { + op.label = " " + op.label + op.item = logs[i][4] + } + return op; +} /** * Generates an option to create a new log. * @@ -424,6 +465,27 @@ export function appendLog(keyword: string, data: string) { export function clearLog(keyword: string) { localStorage.setItem(logs[logKeys.indexOf(keyword)][1], "---- " + logs[logKeys.indexOf(keyword)][3] + " ----" + logs[logKeys.indexOf(keyword)][5]) } +export function setFileInfo(title: string, authors: string[]) { + var fileID = rarityslot[1] as string + var drpd = JSON.parse(localStorage.getItem(fileID)) as DRPD; + drpd.title = title; + for (var i = 0; i < authors.length; i++) { + while (authors[i][0] == " ") { + authors[i] = authors[i].substring(1) + } + while (authors[i][authors[i].length - 1] == " ") { + authors[i] = authors[i].substring(0, authors[i].length - 1) + } + } + for (var i = 0; i < authors.length; i++) { + if (authors[i] == "") { + authors.splice(i, 1) + i--; + } + } + drpd.authors = authors; + localStorage.setItem(fileID, JSON.stringify(drpd)) +} /** * Saves a log to your device. * @param keyword The identifier key for the log you want to save. diff --git a/src/ui/log-name-form-ui-handler.ts b/src/ui/log-name-form-ui-handler.ts index 83a883c73ab..b4b69e93aee 100644 --- a/src/ui/log-name-form-ui-handler.ts +++ b/src/ui/log-name-form-ui-handler.ts @@ -3,6 +3,8 @@ import { ModalConfig } from "./modal-ui-handler"; import * as Utils from "../utils"; import { Mode } from "./ui"; import i18next from "i18next"; +import * as LoggerTools from "../logger"; +import { addTextObject, TextStyle } from "./text"; export default class LogNameFormUiHandler extends FormModalUiHandler { getModalTitle(config?: ModalConfig): string { @@ -44,38 +46,36 @@ export default class LogNameFormUiHandler extends FormModalUiHandler { return super.getReadableErrorMessage(error); } + setup(): void { + super.setup(); + + //const label = addTextObject(this.scene, 10, 87, "Text", TextStyle.TOOLTIP_CONTENT, { fontSize: "42px" }); + + //this.modalContainer.add(label); + } + show(args: any[]): boolean { + console.error("Shown") if (super.show(args)) { const config = args[0] as ModalConfig; const originalLoginAction = this.submitAction; + this.inputs[0].setText(args[0].autofillfields[0]) + this.inputs[1].setText(args[0].autofillfields[1]) this.submitAction = (_) => { // Prevent overlapping overrides on action modification this.submitAction = originalLoginAction; this.sanitizeInputs(); this.scene.ui.setMode(Mode.LOADING, { buttonActions: [] }); const onFail = error => { - this.scene.ui.setMode(Mode.LOGIN_FORM, Object.assign(config, { errorMessage: error?.trim() })); + this.scene.ui.setMode(Mode.NAME_LOG, Object.assign(config, { errorMessage: error?.trim() })); this.scene.ui.playError(); }; if (!this.inputs[0].text) { - return onFail(i18next.t("menu:emptyUsername")); + //return onFail(i18next.t("menu:emptyUsername")); } - Utils.apiPost("account/login", `username=${encodeURIComponent(this.inputs[0].text)}&password=${encodeURIComponent(this.inputs[1].text)}`, "application/x-www-form-urlencoded") - .then(response => { - if (!response.ok) { - return response.text(); - } - return response.json(); - }) - .then(response => { - if (response.hasOwnProperty("token")) { - Utils.setCookie(Utils.sessionIdKey, response.token); - originalLoginAction(); - } else { - onFail(response); - } - }); + LoggerTools.setFileInfo(this.inputs[0].text, this.inputs[1].text.split(",")) + originalLoginAction() }; return true; diff --git a/src/ui/ui.ts b/src/ui/ui.ts index eb3be06fed8..d4dddab5922 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -97,7 +97,8 @@ const transitionModes = [ Mode.EGG_HATCH_SCENE, Mode.EGG_LIST, Mode.EGG_GACHA, - Mode.CHALLENGE_SELECT + Mode.CHALLENGE_SELECT, + Mode.NAME_LOG ]; const noTransitionModes = [ @@ -121,8 +122,7 @@ const noTransitionModes = [ Mode.LOADING, Mode.SESSION_RELOAD, Mode.UNAVAILABLE, - Mode.OUTDATED, - Mode.NAME_LOG + Mode.OUTDATED ]; export default class UI extends Phaser.GameObjects.Container { From c39ecd718189f31d1baa793ad9142a44c0b3c84f Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Fri, 12 Jul 2024 17:04:34 -0400 Subject: [PATCH 41/94] Merge export and rename UIs The renaming UI is used to rename, export, or delete your game logs --- src/logger.ts | 11 ++++++++++- src/phases.ts | 36 ++++++++++++++++++++++++++++++++---- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/src/logger.ts b/src/logger.ts index ba7e38d52d4..a06488578d6 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -397,7 +397,16 @@ export function generateEditOption(scene: BattleScene, i: integer, saves: any, p buttonActions: [ () => { scene.ui.playSelect(); - console.log("Ending UI phase thingy"); + phase.callEnd() + }, + () => { + scene.ui.playSelect(); + downloadLogByID(i) + phase.callEnd() + }, + () => { + scene.ui.playSelect(); + localStorage.removeItem(logs[i][1]) phase.callEnd() } ] diff --git a/src/phases.ts b/src/phases.ts index e9d8c637f82..43c25914d92 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -417,11 +417,39 @@ export class TitlePhase extends Phase { //options.push(LoggerTools.generateAddOption(i, this.scene, this)) } } + options.push({ + label: "Delete all", + handler: () => { + for (var i = 0; i < LoggerTools.logs.length; i++) { + if (localStorage.getItem(LoggerTools.logs[i][1]) != null) { + localStorage.removeItem(LoggerTools.logs[i][1]) + } + } + this.scene.clearPhaseQueue(); + this.scene.pushPhase(new TitlePhase(this.scene)); + super.end(); + 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; + } + logRenameMenu(): boolean { + const options: OptionSelectItem[] = []; + LoggerTools.getLogs() for (var i = 0; i < LoggerTools.logs.length; i++) { if (localStorage.getItem(LoggerTools.logs[i][1]) != null) { - //options.push(LoggerTools.generateOption(i, this.scene, this.logMenu) as OptionSelectItem) + options.push(LoggerTools.generateEditOption(this.scene, i, this.getSaves(), this) as OptionSelectItem) } else { - options.push(LoggerTools.generateAddOption(i, this.scene, this)) + //options.push(LoggerTools.generateAddOption(i, this.scene, this)) } } options.push({ @@ -446,7 +474,7 @@ export class TitlePhase extends Phase { return true; } }); - this.scene.ui.showText("Export or clear game logs.", null, () => this.scene.ui.setOverlayMode(Mode.OPTION_SELECT, { options: options })); + this.scene.ui.showText("Export, rename, or delete logs.", null, () => this.scene.ui.setOverlayMode(Mode.OPTION_SELECT, { options: options })); return true; } @@ -563,7 +591,7 @@ export class TitlePhase extends Phase { }, { label: "Manage Logs", handler: () => { - return this.logMenu() + return this.logRenameMenu() } }) options.push({ From c1873cdb5622cd49d806dc6d19c9aed69dc3e22b Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Fri, 12 Jul 2024 17:09:32 -0400 Subject: [PATCH 42/94] Add settings Lazy Reloads: Flags every page refresh as a required reload, even if nothing changed --- src/battle-scene.ts | 1 + src/system/settings/settings.ts | 50 ++++++++++++++++++++++----------- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 331b06c57f4..ebef2f4dd6d 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -121,6 +121,7 @@ export default class BattleScene extends SceneBase { public enableMoveInfo: boolean = true; public enableRetries: boolean = false; public damageDisplay: string = "Off"; + public lazyReloads: boolean = false; /** * Determines the condition for a notification should be shown for Candy Upgrades * - 0 = 'Off' diff --git a/src/system/settings/settings.ts b/src/system/settings/settings.ts index 534155a40a5..1e1c54d9766 100644 --- a/src/system/settings/settings.ts +++ b/src/system/settings/settings.ts @@ -99,7 +99,8 @@ export const SettingKeys = { SE_Volume: "SE_VOLUME", Music_Preference: "MUSIC_PREFERENCE", Show_BGM_Bar: "SHOW_BGM_BAR", - Damage_Display: "DAMAGE_DISPLAY" + Damage_Display: "DAMAGE_DISPLAY", + LazyReloads: "FLAG_EVERY_RESET_AS_RELOAD" }; /** @@ -146,6 +147,35 @@ export const Setting: Array = [ default: 3, type: SettingType.GENERAL }, + { + key: SettingKeys.Damage_Display, + label: "Damage Display", + options: [{ + label: "Off", + value: "Off" + }, { + label: "Value", + value: "Value" + }, { + label: "Percent", + value: "Percent" + }], + default: 0, + type: SettingType.GENERAL, + }, + { + key: SettingKeys.LazyReloads, + label: "Lazy Reloads", + options: [{ + label: "Off", + value: "Off" + }, { + label: "On", + value: "On" + }], + default: 0, + type: SettingType.GENERAL, + }, { key: SettingKeys.HP_Bar_Speed, label: i18next.t("settings:hpBarSpeed"), @@ -244,22 +274,6 @@ export const Setting: Array = [ default: 0, type: SettingType.GENERAL }, - { - key: SettingKeys.Damage_Display, - label: "Damage Display", - options: [{ - label: "Off", - value: "Off" - }, { - label: "Value", - value: "Value" - }, { - label: "Percent", - value: "Percent" - }], - default: 0, - type: SettingType.GENERAL, - }, { key: SettingKeys.Tutorials, label: i18next.t("settings:tutorials"), @@ -624,6 +638,8 @@ export function setSetting(scene: BattleScene, setting: string, value: integer): break; case SettingKeys.Damage_Display: scene.damageDisplay = Setting[index].options[value].value + case SettingKeys.LazyReloads: + scene.lazyReloads = Setting[index].options[value].value == "On" case SettingKeys.Skip_Seen_Dialogues: scene.skipSeenDialogues = Setting[index].options[value].value === "On"; break; From a8449833a5624ab7ec9efd0de2cddf8d75f08bfe Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Fri, 12 Jul 2024 17:12:20 -0400 Subject: [PATCH 43/94] Make use of LazyReloads setting --- src/logger.ts | 12 ++++++++++++ src/phases.ts | 3 +++ 2 files changed, 15 insertions(+) diff --git a/src/logger.ts b/src/logger.ts index a06488578d6..b64b21e2fd7 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -955,6 +955,18 @@ export function setRow(keyword: string, newLine: string, floor: integer, slot: i } localStorage.setItem(logs[logKeys.indexOf(keyword)][1], data.slice(0, idx).join("\n") + "\n" + newLine + (data.slice(idx).length == 0 ? "" : "\n") + data.slice(idx).join("\n")); } +export function flagReset(scene: BattleScene, floor: integer = undefined) { + if (floor == undefined) + floor = scene.currentBattle.waveIndex; + if (localStorage.getItem(getLogID(scene)) == null) + localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) + var drpd: DRPD = JSON.parse(localStorage.getItem(getLogID(scene))) as DRPD; + drpd = updateLog(drpd); + var wv = getWave(drpd, floor, scene) + wv.reload = true; + console.log(drpd) + localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) +} /** * Prints a DRPD as a string, for saving it to your device. * @param inData The data to add on to. diff --git a/src/phases.ts b/src/phases.ts index 43c25914d92..99645a81531 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -1362,6 +1362,9 @@ export class EncounterPhase extends BattlePhase { LoggerTools.logPlayerTeam(this.scene) } LoggerTools.resetWaveActions(this.scene) + if (this.scene.lazyReloads) { + LoggerTools.flagReset(this.scene) + } if (this.scene.currentBattle.battleType === BattleType.WILD) { enemyField.forEach(enemyPokemon => { From 299c00f0a4cd2841bfb4225eeccc66918656a797 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Fri, 12 Jul 2024 17:16:19 -0400 Subject: [PATCH 44/94] Fix LazyReloads --- src/phases.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/phases.ts b/src/phases.ts index 99645a81531..290393302ea 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -1362,7 +1362,7 @@ export class EncounterPhase extends BattlePhase { LoggerTools.logPlayerTeam(this.scene) } LoggerTools.resetWaveActions(this.scene) - if (this.scene.lazyReloads) { + if (this.scene.lazyReloads && this.loaded) { LoggerTools.flagReset(this.scene) } From c7fa2ff8e1ab8deec324745939eeebf52681d3cb Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Fri, 12 Jul 2024 17:46:57 -0400 Subject: [PATCH 45/94] Log switching after fainting --- src/logger.ts | 1 + src/phases.ts | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/src/logger.ts b/src/logger.ts index b64b21e2fd7..7d5e864e7bf 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -120,6 +120,7 @@ export const rarities = [] export const rarityslot = [0, ""] export const isPreSwitch: Utils.BooleanHolder = new Utils.BooleanHolder(false); +export const isFaintSwitch: Utils.BooleanHolder = new Utils.BooleanHolder(false); export var StoredLog: DRPD = undefined; diff --git a/src/phases.ts b/src/phases.ts index 290393302ea..261c6d1c287 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -4341,6 +4341,7 @@ export class FaintPhase extends PokemonPhase { if (!nonFaintedPartyMemberCount) { this.scene.unshiftPhase(new GameOverPhase(this.scene)); } else if (nonFaintedPartyMemberCount >= this.scene.currentBattle.getBattlerCount() || (this.scene.currentBattle.double && !nonFaintedLegalPartyMembers[0].isActive(true))) { + LoggerTools.isFaintSwitch.value = true; this.scene.pushPhase(new SwitchPhase(this.scene, this.fieldIndex, true, false)); } if (nonFaintedPartyMemberCount === 1 && this.scene.currentBattle.double) { @@ -5002,12 +5003,14 @@ export class SwitchPhase extends BattlePhase { // Skip modal switch if impossible if (this.isModal && !this.scene.getParty().filter(p => p.isAllowedInBattle() && !p.isActive(true)).length) { LoggerTools.isPreSwitch.value = false; + LoggerTools.isFaintSwitch.value = false; return super.end(); } // Check if there is any space still in field if (this.isModal && this.scene.getPlayerField().filter(p => p.isAllowedInBattle() && p.isActive(true)).length >= this.scene.currentBattle.getBattlerCount()) { LoggerTools.isPreSwitch.value = false; + LoggerTools.isFaintSwitch.value = false; return super.end(); } @@ -5019,6 +5022,9 @@ export class SwitchPhase extends BattlePhase { if (LoggerTools.isPreSwitch.value) { LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, "Pre-switch " + LoggerTools.playerPokeName(this.scene, fieldIndex) + (option == PartyOption.PASS_BATON ? " → Baton" : "") + " → " + LoggerTools.playerPokeName(this.scene, slotIndex)) } + if (LoggerTools.isFaintSwitch.value) { + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, "Send in " + LoggerTools.playerPokeName(this.scene, slotIndex)) + } this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, fieldIndex, slotIndex, this.doReturn, option === PartyOption.PASS_BATON)); } LoggerTools.isPreSwitch.value = false; From db4261bb2d410366bcf2e4fc4b31580cf59357e5 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Fri, 12 Jul 2024 18:12:24 -0400 Subject: [PATCH 46/94] Fix bugs --- src/phases.ts | 91 +++++++++++++++++++++++++----- src/ui/log-name-form-ui-handler.ts | 5 +- 2 files changed, 81 insertions(+), 15 deletions(-) diff --git a/src/phases.ts b/src/phases.ts index 261c6d1c287..5e036c203a4 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -2777,6 +2777,7 @@ export class TurnStartPhase extends FieldPhase { if (!queuedMove) { continue; } + LoggerTools.Actions[pokemon.getBattlerIndex()] = new PokemonMove(queuedMove.move).getName() break; case Command.BALL: var ballNames = [ @@ -2792,9 +2793,7 @@ export class TurnStartPhase extends FieldPhase { //this.scene.unshiftPhase(new AttemptCapturePhase(this.scene, turnCommand.targets[0] % 2, turnCommand.cursor)); break; case Command.POKEMON: - LoggerTools.Actions[pokemon.getBattlerIndex()] = "Switch " + this.scene.getParty()[pokemon.getFieldIndex()].name + " to " + this.scene.getParty()[turnCommand.cursor].name - //playerActions.push("Switch " + this.scene.getParty()[pokemon.getFieldIndex()].name + " to " + this.scene.getParty()[turnCommand.cursor].name) - //this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, pokemon.getFieldIndex(), turnCommand.cursor, true, turnCommand.args[0] as boolean, pokemon.isPlayer())); + LoggerTools.Actions[pokemon.getBattlerIndex()] = ((turnCommand.args[0] as boolean) ? "Baton" : "Switch") + " " + LoggerTools.playerPokeName(this.scene, pokemon) + " to " + LoggerTools.playerPokeName(this.scene, turnCommand.cursor) break; case Command.RUN: LoggerTools.Actions[pokemon.getBattlerIndex()] = "Run" @@ -2868,6 +2867,79 @@ export class TurnStartPhase extends FieldPhase { if (pokemon.isPlayer()) { if (turnCommand.cursor === -1) { this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move.targets, move)); + var targets = turnCommand.targets || turnCommand.move.targets + var mv = new PokemonMove(queuedMove.move) + if (pokemon.isPlayer()) { + LoggerTools.Actions[pokemon.getBattlerIndex()] = mv.getName() + if (this.scene.currentBattle.double) { + var targIDs = ["Counter", "Self", "Ally", "L", "R"] + if (pokemon.getBattlerIndex() == 1) targIDs = ["Counter", "Ally", "Self", "L", "R"] + switch (mv.getMove().moveTarget) { + case MoveTarget.USER: + //LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + break; + case MoveTarget.OTHER: + LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + break; + case MoveTarget.ALL_OTHERS: + //LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + break; + case MoveTarget.NEAR_OTHER: + LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + break; + case MoveTarget.ALL_NEAR_OTHERS: + //LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + break; + case MoveTarget.NEAR_ENEMY: + LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + break; + case MoveTarget.ALL_NEAR_ENEMIES: + //LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + break; + case MoveTarget.RANDOM_NEAR_ENEMY: + //LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + break; + case MoveTarget.ALL_ENEMIES: + //LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + break; + case MoveTarget.ATTACKER: + //LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + break; + break; + case MoveTarget.NEAR_ALLY: + LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + break; + case MoveTarget.ALLY: + LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + break; + case MoveTarget.USER_OR_NEAR_ALLY: + LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + break; + case MoveTarget.USER_AND_ALLIES: + //LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + break; + case MoveTarget.ALL: + //LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + break; + case MoveTarget.USER_SIDE: + //LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + break; + case MoveTarget.ENEMY_SIDE: + //LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + break; + case MoveTarget.BOTH_SIDES: + //LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + break; + case MoveTarget.PARTY: + //LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + break; + case MoveTarget.CURSE: + //LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + break; + } + } + console.log(mv.getName(), targets) + } } else { const playerPhase = new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move.targets, move, false, queuedMove.ignorePP); this.scene.pushPhase(playerPhase); @@ -3276,15 +3348,6 @@ export class MovePhase extends BattlePhase { if (!allMoves[this.move.moveId].hasAttr(CopyMoveAttr)) { this.scene.currentBattle.lastMove = this.move.moveId; } - if (this.pokemon.isPlayer()) { - LoggerTools.Actions[this.pokemon.getBattlerIndex()] = this.move.getName() - if (this.scene.currentBattle.double) { - var targIDs = ["Counter", "Self", "Ally", "L", "R"] - if (this.pokemon.getBattlerIndex() == 1) targIDs = ["Counter", "Ally", "Self", "L", "R"] - LoggerTools.Actions[this.pokemon.getBattlerIndex()] += " → " + this.targets.map(v => targIDs[v+1]) - } - console.log(this.move.getName(), this.targets) - } // Assume conditions affecting targets only apply to moves with a single target let success = this.move.getMove().applyConditions(this.pokemon, targets[0], this.move.getMove()); @@ -5020,10 +5083,10 @@ export class SwitchPhase extends BattlePhase { this.scene.ui.setMode(Mode.PARTY, this.isModal ? PartyUiMode.FAINT_SWITCH : PartyUiMode.POST_BATTLE_SWITCH, fieldIndex, (slotIndex: integer, option: PartyOption) => { if (slotIndex >= this.scene.currentBattle.getBattlerCount() && slotIndex < 6) { if (LoggerTools.isPreSwitch.value) { - LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, "Pre-switch " + LoggerTools.playerPokeName(this.scene, fieldIndex) + (option == PartyOption.PASS_BATON ? " → Baton" : "") + " → " + LoggerTools.playerPokeName(this.scene, slotIndex)) + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, "Pre-switch " + (option == PartyOption.PASS_BATON ? "+ Baton" : "") + " to " + LoggerTools.playerPokeName(this.scene, slotIndex)) } if (LoggerTools.isFaintSwitch.value) { - LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, "Send in " + LoggerTools.playerPokeName(this.scene, slotIndex)) + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, (option == PartyOption.PASS_BATON ? "Baton" : "Switch") + "in " + LoggerTools.playerPokeName(this.scene, slotIndex)) } this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, fieldIndex, slotIndex, this.doReturn, option === PartyOption.PASS_BATON)); } diff --git a/src/ui/log-name-form-ui-handler.ts b/src/ui/log-name-form-ui-handler.ts index b4b69e93aee..d51adee3796 100644 --- a/src/ui/log-name-form-ui-handler.ts +++ b/src/ui/log-name-form-ui-handler.ts @@ -24,7 +24,7 @@ export default class LogNameFormUiHandler extends FormModalUiHandler { } getButtonLabels(config?: ModalConfig): string[] { - return [ "Create" ]; + return [ "Rename", "Export" ]; } getReadableErrorMessage(error: string): string { @@ -52,6 +52,9 @@ export default class LogNameFormUiHandler extends FormModalUiHandler { //const label = addTextObject(this.scene, 10, 87, "Text", TextStyle.TOOLTIP_CONTENT, { fontSize: "42px" }); //this.modalContainer.add(label); + + this.inputs[0].maxLength = 99 + this.inputs[1].maxLength = 200 } show(args: any[]): boolean { From 4d95fd0ca2eb7df451c16f9174475861718d9560 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Fri, 12 Jul 2024 20:03:16 -0400 Subject: [PATCH 47/94] Update default settings Disable tutorials because annoying Enable Type Hints because of damage display --- src/system/settings/settings.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/system/settings/settings.ts b/src/system/settings/settings.ts index 1e1c54d9766..5897490b2a2 100644 --- a/src/system/settings/settings.ts +++ b/src/system/settings/settings.ts @@ -278,7 +278,7 @@ export const Setting: Array = [ key: SettingKeys.Tutorials, label: i18next.t("settings:tutorials"), options: OFF_ON, - default: 1, + default: 0, type: SettingType.GENERAL }, { @@ -518,7 +518,7 @@ export const Setting: Array = [ key: SettingKeys.Type_Hints, label: i18next.t("settings:typeHints"), options: OFF_ON, - default: 0, + default: 1, type: SettingType.DISPLAY }, { From 798b57eb4f3ad6f2f25021b2bb91dbdcd1548398 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Fri, 12 Jul 2024 21:33:28 -0400 Subject: [PATCH 48/94] Fix reload(?) and add IV menu --- src/logger.ts | 21 +++++++++++++++++++++ src/phases.ts | 6 +++--- src/ui/arena-flyout.ts | 31 ++++++++++++++++++++++++++++++- src/ui/battle-info.ts | 5 +++++ 4 files changed, 59 insertions(+), 4 deletions(-) diff --git a/src/logger.ts b/src/logger.ts index 7d5e864e7bf..d6a23adc3d3 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -968,6 +968,27 @@ export function flagReset(scene: BattleScene, floor: integer = undefined) { console.log(drpd) localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) } +export function flagResetIfExists(scene: BattleScene, floor: integer = undefined) { + if (floor == undefined) + floor = scene.currentBattle.waveIndex; + if (localStorage.getItem(getLogID(scene)) == null) + localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) + var drpd: DRPD = JSON.parse(localStorage.getItem(getLogID(scene))) as DRPD; + drpd = updateLog(drpd); + var waveExists = false + for (var i = 0; i < drpd.waves.length; i++) { + if (drpd.waves[i] != undefined) { + if (drpd.waves[i].id == floor) { + waveExists = true; + } + } + } + if (!waveExists) return; + var wv = getWave(drpd, floor, scene) + wv.reload = true; + console.log(drpd) + localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) +} /** * Prints a DRPD as a string, for saving it to your device. * @param inData The data to add on to. diff --git a/src/phases.ts b/src/phases.ts index 5e036c203a4..046ab6dc385 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -1354,6 +1354,9 @@ export class EncounterPhase extends BattlePhase { const enemyField = this.scene.getEnemyField(); //LoggerTools.resetWave(this.scene, this.scene.currentBattle.waveIndex) + if (this.scene.lazyReloads) { + LoggerTools.flagResetIfExists(this.scene) + } LoggerTools.logTeam(this.scene, this.scene.currentBattle.waveIndex) if (this.scene.getEnemyParty()[0].hasTrainer()) { LoggerTools.logTrainer(this.scene, this.scene.currentBattle.waveIndex) @@ -1362,9 +1365,6 @@ export class EncounterPhase extends BattlePhase { LoggerTools.logPlayerTeam(this.scene) } LoggerTools.resetWaveActions(this.scene) - if (this.scene.lazyReloads && this.loaded) { - LoggerTools.flagReset(this.scene) - } if (this.scene.currentBattle.battleType === BattleType.WILD) { enemyField.forEach(enemyPokemon => { diff --git a/src/ui/arena-flyout.ts b/src/ui/arena-flyout.ts index 9c0a2e7c8ce..fbd67d9521a 100644 --- a/src/ui/arena-flyout.ts +++ b/src/ui/arena-flyout.ts @@ -9,6 +9,7 @@ import { BattleSceneEventType, TurnEndEvent } from "../events/battle-scene"; import { ArenaTagType } from "#enums/arena-tag-type"; import TimeOfDayWidget from "./time-of-day-widget"; import * as Utils from "../utils"; +import { getNatureDecrease, getNatureIncrease, getNatureName } from "#app/data/nature.js"; /** Enum used to differentiate {@linkcode Arena} effects */ enum ArenaEffectType { @@ -193,12 +194,40 @@ export default class ArenaFlyout extends Phaser.GameObjects.Container { this.flyoutTextEnemy.text = ""; } + public printIVs() { + var poke = (this.scene as BattleScene).getEnemyField() + this.flyoutTextPlayer.text = "" + this.flyoutTextField.text = "" + this.flyoutTextEnemy.text = "" + this.flyoutTextHeaderField.text = "Stats" + this.flyoutTextHeaderPlayer.text = "" + this.flyoutTextHeaderEnemy.text = "" + this.flyoutTextHeader.text = "IVs" + for (var i = 0; i < poke.length; i++) { + if (i == 1 || true) { + this.flyoutTextPlayer.text += poke[i].name + "\n" + this.flyoutTextEnemy.text += poke[i].getAbility().name + " / " + (poke[i].isBoss() ? poke[i].getPassiveAbility().name + " / " : "") + getNatureName(poke[i].nature) + (getNatureIncrease(poke[i].nature) != "" ? " (+" + getNatureIncrease(poke[i].nature) + " -" + getNatureDecrease(poke[i].nature) + ")" : "") + "\n\n\n" + } + this.flyoutTextPlayer.text += "HP: " + poke[i].ivs[0] + this.flyoutTextPlayer.text += ", Atk: " + poke[i].ivs[1] + this.flyoutTextPlayer.text += ", Def: " + poke[i].ivs[2] + this.flyoutTextPlayer.text += ", Sp.A: " + poke[i].ivs[3] + this.flyoutTextPlayer.text += ", Sp.D: " + poke[i].ivs[4] + this.flyoutTextPlayer.text += ", Speed: " + poke[i].ivs[5] + "\n\n" + } + } + /** Parses through all set Arena Effects and puts them into the proper {@linkcode Phaser.GameObjects.Text} object */ - private updateFieldText() { + public updateFieldText() { this.clearText(); this.fieldEffectInfo.sort((infoA, infoB) => infoA.duration - infoB.duration); + this.flyoutTextHeaderPlayer.text = "Player" + this.flyoutTextHeaderField.text = "Neutral" + this.flyoutTextHeaderEnemy.text = "Enemy" + this.flyoutTextHeader.text = "Active Battle Effects" + for (let i = 0; i < this.fieldEffectInfo.length; i++) { const fieldEffectInfo = this.fieldEffectInfo[i]; diff --git a/src/ui/battle-info.ts b/src/ui/battle-info.ts index 01446a58931..5d066cfb154 100644 --- a/src/ui/battle-info.ts +++ b/src/ui/battle-info.ts @@ -443,6 +443,11 @@ export default class BattleInfo extends Phaser.GameObjects.Container { ease: "Sine.easeInOut", alpha: visible ? 1 : 0 }); + if (visible) { + (this.scene as BattleScene).arenaFlyout.printIVs() + } else { + (this.scene as BattleScene).arenaFlyout.updateFieldText() + } } updateBossSegments(pokemon: EnemyPokemon): void { From c4659d64670b2a8dcd190635058acee0a7f8105c Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Fri, 12 Jul 2024 21:58:55 -0400 Subject: [PATCH 49/94] update --- src/logger.ts | 29 ++++++++++++++--------------- src/ui/arena-flyout.ts | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 15 deletions(-) diff --git a/src/logger.ts b/src/logger.ts index d6a23adc3d3..3fc9e85fcc1 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -350,6 +350,13 @@ export function getSize(str: string) { return d.toString() + filesizes[unit] } +export function getDRPD(scene: BattleScene): DRPD { + var drpd: DRPD = JSON.parse(localStorage.getItem(getLogID(scene))) as DRPD; + drpd = updateLog(drpd); + scene.arenaFlyout.printIVs() + return drpd; +} + /** * Generates a UI option to save a log to your device. * @param i The slot number. Corresponds to an index in `logs`. @@ -559,8 +566,7 @@ export function logTeam(scene: BattleScene, floor: integer = undefined) { */ export function logActions(scene: BattleScene, floor: integer, action: string) { if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) - var drpd: DRPD = JSON.parse(localStorage.getItem(getLogID(scene))) as DRPD; - drpd = updateLog(drpd); + var drpd = getDRPD(scene) console.log("Log Action", drpd) var wv: Wave = getWave(drpd, floor, scene) wv.actions.push(action) @@ -575,8 +581,7 @@ export function logActions(scene: BattleScene, floor: integer, action: string) { */ export function logShop(scene: BattleScene, floor: integer, action: string) { if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) - var drpd: DRPD = JSON.parse(localStorage.getItem(getLogID(scene))) as DRPD; - drpd = updateLog(drpd); + var drpd = getDRPD(scene) console.log("Log Shop Item", drpd) var wv: Wave = getWave(drpd, floor, scene) wv.shop = action @@ -741,8 +746,7 @@ function checkForPokeInBiome(species: Species, pool: (Species | SpeciesTree)[]): export function logPokemon(scene: BattleScene, floor: integer = undefined, slot: integer, pokemon: EnemyPokemon, encounterRarity?: string) { if (floor == undefined) floor = scene.currentBattle.waveIndex if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) - var drpd: DRPD = JSON.parse(localStorage.getItem(getLogID(scene))) as DRPD; - drpd = updateLog(drpd); + var drpd = getDRPD(scene) console.log("Log Enemy Pokemon", drpd) var wv: Wave = getWave(drpd, floor, scene) var pk: PokeData = exportPokemon(pokemon, encounterRarity) @@ -816,8 +820,7 @@ export function logPokemon(scene: BattleScene, floor: integer = undefined, slot: export function resetWaveActions(scene: BattleScene, floor: integer = undefined) { if (floor == undefined) floor = scene.currentBattle.waveIndex if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) - var drpd: DRPD = JSON.parse(localStorage.getItem(getLogID(scene))) as DRPD; - drpd = updateLog(drpd); + var drpd = getDRPD(scene) console.log("Clear Actions", drpd) var wv: Wave = getWave(drpd, floor, scene) wv.actions = [] @@ -850,9 +853,7 @@ export function logTrainer(scene: BattleScene, floor: integer = undefined) { */ export function logPlayerTeam(scene: BattleScene) { if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) - var drpd: DRPD = JSON.parse(localStorage.getItem(getLogID(scene))) as DRPD; - //var wv: Wave = getWave(drpd, 1, scene) - drpd = updateLog(drpd); + var drpd = getDRPD(scene) console.log("Log Player Starters", drpd) var P = scene.getParty() for (var i = 0; i < P.length; i++) { @@ -961,8 +962,7 @@ export function flagReset(scene: BattleScene, floor: integer = undefined) { floor = scene.currentBattle.waveIndex; if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) - var drpd: DRPD = JSON.parse(localStorage.getItem(getLogID(scene))) as DRPD; - drpd = updateLog(drpd); + var drpd = getDRPD(scene) var wv = getWave(drpd, floor, scene) wv.reload = true; console.log(drpd) @@ -973,8 +973,7 @@ export function flagResetIfExists(scene: BattleScene, floor: integer = undefined floor = scene.currentBattle.waveIndex; if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) - var drpd: DRPD = JSON.parse(localStorage.getItem(getLogID(scene))) as DRPD; - drpd = updateLog(drpd); + var drpd = getDRPD(scene) var waveExists = false for (var i = 0; i < drpd.waves.length; i++) { if (drpd.waves[i] != undefined) { diff --git a/src/ui/arena-flyout.ts b/src/ui/arena-flyout.ts index fbd67d9521a..499773f63c6 100644 --- a/src/ui/arena-flyout.ts +++ b/src/ui/arena-flyout.ts @@ -10,6 +10,8 @@ import { ArenaTagType } from "#enums/arena-tag-type"; import TimeOfDayWidget from "./time-of-day-widget"; import * as Utils from "../utils"; import { getNatureDecrease, getNatureIncrease, getNatureName } from "#app/data/nature.js"; +import * as LoggerTools from "../logger" +import { BattleEndPhase } from "#app/phases.js"; /** Enum used to differentiate {@linkcode Arena} effects */ enum ArenaEffectType { @@ -192,9 +194,12 @@ export default class ArenaFlyout extends Phaser.GameObjects.Container { this.flyoutTextPlayer.text = ""; this.flyoutTextField.text = ""; this.flyoutTextEnemy.text = ""; + this.flyoutTextPlayer.setPosition(6, 13) + this.flyoutTextPlayer.setFontSize(48); } public printIVs() { + this.clearText() var poke = (this.scene as BattleScene).getEnemyField() this.flyoutTextPlayer.text = "" this.flyoutTextField.text = "" @@ -261,6 +266,36 @@ export default class ArenaFlyout extends Phaser.GameObjects.Container { textObject.text += "\n"; } + this.flyoutTextPlayer.text = "" + this.flyoutTextField.text = "" + this.flyoutTextEnemy.text = "" + this.flyoutTextHeaderField.text = "Stats" + this.flyoutTextHeaderPlayer.text = "" + this.flyoutTextHeaderEnemy.text = "" + this.flyoutTextPlayer.setPosition(6, 5) + this.flyoutTextPlayer.setFontSize(30); + var instructions = [] + var drpd = LoggerTools.getDRPD(this.scene as BattleScene); + var doWaveInstructions = true; + for (var i = 0; i < drpd.waves.length && drpd.waves[i] != undefined && doWaveInstructions; i++) { + if (drpd.waves[i].id > (this.scene as BattleScene).currentBattle.waveIndex) { + doWaveInstructions = false; + } else { + instructions.push("") + instructions.push("Wave " + drpd.waves[i].id) + for (var j = 0; j < drpd.waves[i].actions.length; j++) { + instructions.push("- " + drpd.waves[i].actions[j]) + } + if (drpd.waves[i].shop != "") + instructions.push("Reward: " + drpd.waves[i].shop) + } + } + for (var i = instructions.length - 8; i < instructions.length; i++) { + if (i >= 0) { + this.flyoutTextPlayer.text += instructions[i] + } + this.flyoutTextPlayer.text += "\n" + } } /** From 1990b69c184da23ec679816c17be979ab33e71f8 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Fri, 12 Jul 2024 22:02:27 -0400 Subject: [PATCH 50/94] correct an error --- src/logger.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/logger.ts b/src/logger.ts index 3fc9e85fcc1..a65d824703a 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -353,7 +353,7 @@ export function getSize(str: string) { export function getDRPD(scene: BattleScene): DRPD { var drpd: DRPD = JSON.parse(localStorage.getItem(getLogID(scene))) as DRPD; drpd = updateLog(drpd); - scene.arenaFlyout.printIVs() + scene.arenaFlyout.updateFieldText() return drpd; } From 9e442f2b0782c84b1e180aa796da4271a20fa45e Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Fri, 12 Jul 2024 22:12:32 -0400 Subject: [PATCH 51/94] more fixes --- src/logger.ts | 2 +- src/phases.ts | 33 ++++++++++++++++++--------------- src/ui/arena-flyout.ts | 4 ++-- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/logger.ts b/src/logger.ts index a65d824703a..b05fdd86b15 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -353,7 +353,7 @@ export function getSize(str: string) { export function getDRPD(scene: BattleScene): DRPD { var drpd: DRPD = JSON.parse(localStorage.getItem(getLogID(scene))) as DRPD; drpd = updateLog(drpd); - scene.arenaFlyout.updateFieldText() + //scene.arenaFlyout.updateFieldText() return drpd; } diff --git a/src/phases.ts b/src/phases.ts index 046ab6dc385..413a30cf5b7 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -2368,6 +2368,8 @@ export class TurnInitPhase extends FieldPhase { }) } + this.scene.arenaFlyout.updateFieldText() + this.scene.setScoreText(txt.join("/")) this.end(); @@ -2876,35 +2878,34 @@ export class TurnStartPhase extends FieldPhase { if (pokemon.getBattlerIndex() == 1) targIDs = ["Counter", "Ally", "Self", "L", "R"] switch (mv.getMove().moveTarget) { case MoveTarget.USER: - //LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) break; case MoveTarget.OTHER: LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) break; case MoveTarget.ALL_OTHERS: - //LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) break; case MoveTarget.NEAR_OTHER: LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) break; case MoveTarget.ALL_NEAR_OTHERS: - //LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) break; case MoveTarget.NEAR_ENEMY: LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) break; case MoveTarget.ALL_NEAR_ENEMIES: - //LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) break; case MoveTarget.RANDOM_NEAR_ENEMY: - //LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) break; case MoveTarget.ALL_ENEMIES: - //LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) break; case MoveTarget.ATTACKER: - //LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) - break; + LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) break; case MoveTarget.NEAR_ALLY: LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) @@ -2916,25 +2917,25 @@ export class TurnStartPhase extends FieldPhase { LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) break; case MoveTarget.USER_AND_ALLIES: - //LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) break; case MoveTarget.ALL: - //LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) break; case MoveTarget.USER_SIDE: - //LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) break; case MoveTarget.ENEMY_SIDE: - //LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) break; case MoveTarget.BOTH_SIDES: - //LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) break; case MoveTarget.PARTY: - //LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) break; case MoveTarget.CURSE: - //LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) break; } } @@ -3045,6 +3046,8 @@ export class TurnEndPhase extends FieldPhase { start() { super.start(); + this.scene.arenaFlyout.updateFieldText() + this.scene.currentBattle.incrementTurn(this.scene); this.scene.eventTarget.dispatchEvent(new TurnEndEvent(this.scene.currentBattle.turn)); diff --git a/src/ui/arena-flyout.ts b/src/ui/arena-flyout.ts index 499773f63c6..e80ebd18478 100644 --- a/src/ui/arena-flyout.ts +++ b/src/ui/arena-flyout.ts @@ -272,7 +272,7 @@ export default class ArenaFlyout extends Phaser.GameObjects.Container { this.flyoutTextHeaderField.text = "Stats" this.flyoutTextHeaderPlayer.text = "" this.flyoutTextHeaderEnemy.text = "" - this.flyoutTextPlayer.setPosition(6, 5) + this.flyoutTextPlayer.setPosition(6, 4) this.flyoutTextPlayer.setFontSize(30); var instructions = [] var drpd = LoggerTools.getDRPD(this.scene as BattleScene); @@ -290,7 +290,7 @@ export default class ArenaFlyout extends Phaser.GameObjects.Container { instructions.push("Reward: " + drpd.waves[i].shop) } } - for (var i = instructions.length - 8; i < instructions.length; i++) { + for (var i = instructions.length - 10; i < instructions.length; i++) { if (i >= 0) { this.flyoutTextPlayer.text += instructions[i] } From 94ad9d2ba29f847d4be81291ea04a762f1876fce Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Fri, 12 Jul 2024 22:23:45 -0400 Subject: [PATCH 52/94] Fix action targets not being logged --- src/phases.ts | 127 ++++++++++++++++++++++++++------------------------ 1 file changed, 65 insertions(+), 62 deletions(-) diff --git a/src/phases.ts b/src/phases.ts index 413a30cf5b7..7a4dd81c78a 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -2872,81 +2872,84 @@ export class TurnStartPhase extends FieldPhase { var targets = turnCommand.targets || turnCommand.move.targets var mv = new PokemonMove(queuedMove.move) if (pokemon.isPlayer()) { + console.log(turnCommand.targets, turnCommand.move.targets) LoggerTools.Actions[pokemon.getBattlerIndex()] = mv.getName() if (this.scene.currentBattle.double) { var targIDs = ["Counter", "Self", "Ally", "L", "R"] if (pokemon.getBattlerIndex() == 1) targIDs = ["Counter", "Ally", "Self", "L", "R"] - switch (mv.getMove().moveTarget) { - case MoveTarget.USER: - LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) - break; - case MoveTarget.OTHER: - LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) - break; - case MoveTarget.ALL_OTHERS: - LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) - break; - case MoveTarget.NEAR_OTHER: - LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) - break; - case MoveTarget.ALL_NEAR_OTHERS: - LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) - break; - case MoveTarget.NEAR_ENEMY: - LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) - break; - case MoveTarget.ALL_NEAR_ENEMIES: - LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) - break; - case MoveTarget.RANDOM_NEAR_ENEMY: - LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) - break; - case MoveTarget.ALL_ENEMIES: - LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) - break; - case MoveTarget.ATTACKER: - LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) - break; - case MoveTarget.NEAR_ALLY: - LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) - break; - case MoveTarget.ALLY: - LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) - break; - case MoveTarget.USER_OR_NEAR_ALLY: - LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) - break; - case MoveTarget.USER_AND_ALLIES: - LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) - break; - case MoveTarget.ALL: - LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) - break; - case MoveTarget.USER_SIDE: - LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) - break; - case MoveTarget.ENEMY_SIDE: - LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) - break; - case MoveTarget.BOTH_SIDES: - LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) - break; - case MoveTarget.PARTY: - LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) - break; - case MoveTarget.CURSE: - LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) - break; - } + LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + } else { + var targIDs = ["Counter", "", "", "", ""] + var myField = this.scene.getField() + if (myField[0]) + targIDs[1] = myField[0].name + if (myField[1]) + targIDs[2] = myField[1].name + var eField = this.scene.getEnemyField() + if (eField[0]) + targIDs[3] = eField[0].name + if (eField[1]) + targIDs[4] = eField[1].name + //LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) } console.log(mv.getName(), targets) } } else { const playerPhase = new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move.targets, move, false, queuedMove.ignorePP); + var targets = turnCommand.targets || turnCommand.move.targets + var mv = new PokemonMove(queuedMove.move) + if (pokemon.isPlayer()) { + console.log(turnCommand.targets, turnCommand.move.targets) + LoggerTools.Actions[pokemon.getBattlerIndex()] = mv.getName() + if (this.scene.currentBattle.double) { + var targIDs = ["Counter", "Self", "Ally", "L", "R"] + if (pokemon.getBattlerIndex() == 1) targIDs = ["Counter", "Ally", "Self", "L", "R"] + LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + } else { + var targIDs = ["Counter", "", "", "", ""] + var myField = this.scene.getField() + if (myField[0]) + targIDs[1] = myField[0].name + if (myField[1]) + targIDs[2] = myField[1].name + var eField = this.scene.getEnemyField() + if (eField[0]) + targIDs[3] = eField[0].name + if (eField[1]) + targIDs[4] = eField[1].name + //LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + } + console.log(mv.getName(), targets) + } this.scene.pushPhase(playerPhase); } } else { this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move.targets, move, false, queuedMove.ignorePP)); + var targets = turnCommand.targets || turnCommand.move.targets + var mv = new PokemonMove(queuedMove.move) + if (pokemon.isPlayer()) { + console.log(turnCommand.targets, turnCommand.move.targets) + LoggerTools.Actions[pokemon.getBattlerIndex()] = mv.getName() + if (this.scene.currentBattle.double) { + var targIDs = ["Counter", "Self", "Ally", "L", "R"] + if (pokemon.getBattlerIndex() == 1) targIDs = ["Counter", "Ally", "Self", "L", "R"] + LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + } else { + var targIDs = ["Counter", "", "", "", ""] + var myField = this.scene.getField() + if (myField[0]) + targIDs[1] = myField[0].name + if (myField[1]) + targIDs[2] = myField[1].name + var eField = this.scene.getEnemyField() + if (eField[0]) + targIDs[3] = eField[0].name + if (eField[1]) + targIDs[4] = eField[1].name + //LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + } + console.log(mv.getName(), targets) + } } break; case Command.BALL: From bdef9ece22ddd89d4a634beba9522e9493394747 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Fri, 12 Jul 2024 23:30:48 -0400 Subject: [PATCH 53/94] something I don't remember what these updates contain anymore --- src/data/ability.ts | 87 ++++++++++++++++++++++++++ src/data/move.ts | 2 +- src/phases.ts | 2 + src/ui/fight-ui-handler.ts | 123 +++++++++++-------------------------- 4 files changed, 127 insertions(+), 87 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index f449b33992b..8f49e2dfd9a 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -3999,6 +3999,88 @@ function applyAbAttrsInternal(attrType: Constructor applyNextAbAttr(); }); } +function applyAbAttrsInternalNoApply(attrType: Constructor, + pokemon: Pokemon, applyFunc: AbAttrApplyFunc, args: any[], isAsync: boolean = false, showAbilityInstant: boolean = false, quiet: boolean = false, passive: boolean = false): Promise { + return new Promise(resolve => { + if (!pokemon.canApplyAbility(passive)) { + if (!passive) { + args[0].value = 0 + return resolve(); + return applyAbAttrsInternal(attrType, pokemon, applyFunc, args, isAsync, showAbilityInstant, quiet, true).then(() => resolve()); + } else { + return resolve(); + } + } + + const ability = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()); + const attrs = ability.getAttrs(attrType); + + const clearSpliceQueueAndResolve = () => { + pokemon.scene?.clearPhaseQueueSplice(); + if (!passive) { + args[0].value = 0 + return resolve() + return applyAbAttrsInternal(attrType, pokemon, applyFunc, args, isAsync, showAbilityInstant, quiet, true).then(() => resolve()); + } else { + return resolve(); + } + }; + return resolve(); + const applyNextAbAttr = () => { + if (attrs.length) { + applyAbAttr(attrs.shift()); + } else { + clearSpliceQueueAndResolve(); + } + }; + const applyAbAttr = (attr: TAttr) => { + if (!canApplyAttr(pokemon, attr)) { + return applyNextAbAttr(); + } + pokemon.scene.setPhaseQueueSplice(); + const onApplySuccess = () => { + if (pokemon.summonData && !pokemon.summonData.abilitiesApplied.includes(ability.id)) { + pokemon.summonData.abilitiesApplied.push(ability.id); + } + if (pokemon.battleData && !pokemon.battleData.abilitiesApplied.includes(ability.id)) { + pokemon.battleData.abilitiesApplied.push(ability.id); + } + if (attr.showAbility && !quiet) { + if (showAbilityInstant) { + pokemon.scene.abilityBar.showAbility(pokemon, passive); + } else { + queueShowAbility(pokemon, passive); + } + } + if (!quiet) { + const message = attr.getTriggerMessage(pokemon, (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name, args); + if (message) { + if (isAsync) { + pokemon.scene.ui.showText(message, null, () => pokemon.scene.ui.showText(null, 0), null, true); + } else { + pokemon.scene.queueMessage(message); + } + } + } + }; + const result = applyFunc(attr, passive); + if (result instanceof Promise) { + result.then(success => { + if (success) { + onApplySuccess(); + } + applyNextAbAttr(); + }); + } else { + if (result) { + onApplySuccess(); + } + applyNextAbAttr(); + } + }; + applyNextAbAttr(); + }); +} export function applyAbAttrs(attrType: Constructor, pokemon: Pokemon, cancelled: Utils.BooleanHolder, ...args: any[]): Promise { return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.apply(pokemon, passive, cancelled, args), args); @@ -4014,6 +4096,11 @@ export function applyPreDefendAbAttrs(attrType: Constructor, const simulated = args.length > 1 && args[1]; return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPreDefend(pokemon, passive, attacker, move, cancelled, args), args, false, false, simulated); } +export function applyPreDefendAbAttrsNoApply(attrType: Constructor, + pokemon: Pokemon, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, ...args: any[]): Promise { + const simulated = args.length > 1 && args[1]; + return applyAbAttrsInternalNoApply(attrType, pokemon, (attr, passive) => attr.applyPreDefend(pokemon, passive, attacker, move, cancelled, args), args, false, false, simulated); +} export function applyPostDefendAbAttrs(attrType: Constructor, pokemon: Pokemon, attacker: Pokemon, move: Move, hitResult: HitResult, ...args: any[]): Promise { diff --git a/src/data/move.ts b/src/data/move.ts index 321fbc6d097..d13c936cc22 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -1590,7 +1590,7 @@ export class IncrementMovePriorityAttr extends MoveAttr { * @see {@linkcode apply} */ export class MultiHitAttr extends MoveAttr { - private multiHitType: MultiHitType; + public multiHitType: MultiHitType; constructor(multiHitType?: MultiHitType) { super(); diff --git a/src/phases.ts b/src/phases.ts index 7a4dd81c78a..037a63a6dd4 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -2994,6 +2994,8 @@ export class TurnStartPhase extends FieldPhase { this.scene.pushPhase(new BerryPhase(this.scene)); this.scene.pushPhase(new TurnEndPhase(this.scene)); + this.scene.arenaFlyout.updateFieldText() + this.end(); } } diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index 36eba44f09d..d2852ef965e 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -15,7 +15,7 @@ import { Stat } from "#app/data/pokemon-stat.js"; import { Abilities } from "#app/enums/abilities.js"; import { WeatherType } from "#app/data/weather.js"; import { Moves } from "#app/enums/moves.js"; -import { AddSecondStrikeAbAttr, AllyMoveCategoryPowerBoostAbAttr, AlwaysHitAbAttr, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, ConditionalCritAbAttr, DamageBoostAbAttr, FieldMoveTypePowerBoostAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentEvasionAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, MoveTypeChangeAttr, MultCritAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, StabBoostAbAttr, TypeImmunityAbAttr, UserFieldMoveTypePowerBoostAbAttr, VariableMovePowerAbAttr, WonderSkinAbAttr } from "#app/data/ability.js"; +import { AddSecondStrikeAbAttr, AllyMoveCategoryPowerBoostAbAttr, AlwaysHitAbAttr, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreDefendAbAttrsNoApply, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, ConditionalCritAbAttr, DamageBoostAbAttr, FieldMoveTypePowerBoostAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentEvasionAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, MoveTypeChangeAttr, MultCritAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, StabBoostAbAttr, TypeImmunityAbAttr, UserFieldMoveTypePowerBoostAbAttr, VariableMovePowerAbAttr, WonderSkinAbAttr } from "#app/data/ability.js"; import { ArenaTagType } from "#app/enums/arena-tag-type.js"; import { ArenaTagSide, WeakenMoveScreenTag, WeakenMoveTypeTag } from "#app/data/arena-tag.js"; import { BattlerTagLapseType, HelpingHandTag, SemiInvulnerableTag, TypeBoostTag } from "#app/data/battler-tags.js"; @@ -236,7 +236,10 @@ export default class FightUiHandler extends UiHandler { power.value *= typeChangeMovePowerMultiplier.value; if (!typeless) { - applyPreDefendAbAttrs(TypeImmunityAbAttr, user, target, move, cancelled, typeMultiplier); + if (target.hasAbilityWithAttr(TypeImmunityAbAttr)) { + // + } + applyPreDefendAbAttrsNoApply(TypeImmunityAbAttr, user, target, move, cancelled, typeMultiplier); MoveData.applyMoveAttrs(MoveData.NeutralDamageAgainstFlyingTypeMultiplierAttr, user, target, move, typeMultiplier); } if (!cancelled.value) { @@ -404,8 +407,8 @@ export default class FightUiHandler extends UiHandler { applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, user, target, move, cancelled, damage1); applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, user, target, move, cancelled, damage2); - console.log("damage (min)", damage1.value, move.name, power.value, sourceAtk, targetDef); - console.log("damage (max)", damage2.value, move.name, power.value, sourceAtkCrit, targetDefCrit); + //console.log("damage (min)", damage1.value, move.name, power.value, sourceAtk, targetDef); + //console.log("damage (max)", damage2.value, move.name, power.value, sourceAtkCrit, targetDefCrit); // In case of fatal damage, this tag would have gotten cleared before we could lapse it. const destinyTag = target.getTag(BattlerTagType.DESTINY_BOND); @@ -425,7 +428,7 @@ export default class FightUiHandler extends UiHandler { break; case MoveData.MoveCategory.STATUS: if (!typeless) { - applyPreDefendAbAttrs(TypeImmunityAbAttr, target, user, move, cancelled, typeMultiplier); + applyPreDefendAbAttrsNoApply(TypeImmunityAbAttr, target, user, move, cancelled, typeMultiplier); } if (!cancelled.value) { applyPreDefendAbAttrs(MoveImmunityAbAttr, target, user, move, cancelled, typeMultiplier); @@ -631,8 +634,35 @@ export default class FightUiHandler extends UiHandler { // dmgLow = (((2*user.level/5 + 2) * power * myAtk / theirDef)/50 + 2) * 0.85 * modifiers // dmgHigh = (((2*user.level/5 + 2) * power * myAtkC / theirDefC)/50 + 2) * 1.5 * modifiers var out = this.simulateAttack(scene, user, target, move.getMove()) - dmgLow = out[0] - dmgHigh = out[1] + var minHits = 1 + var maxHits = 1 + var mh = move.getMove().getAttrs(MoveData.MultiHitAttr) + for (var i = 0; i < mh.length; i++) { + var mh2 = mh[i] as MoveData.MultiHitAttr + switch (mh2.multiHitType) { + case MoveData.MultiHitType._2: + minHits = 2; + maxHits = 2; + case MoveData.MultiHitType._2_TO_5: + minHits = 2; + maxHits = 5; + case MoveData.MultiHitType._3: + minHits = 3; + maxHits = 3; + case MoveData.MultiHitType._10: + minHits = 10; + maxHits = 10; + case MoveData.MultiHitType.BEAT_UP: + const party = user.isPlayer() ? user.scene.getParty() : user.scene.getEnemyParty(); + // No status means the ally pokemon can contribute to Beat Up + minHits = party.reduce((total, pokemon) => { + return total + (pokemon.id === user.id ? 1 : pokemon?.status && pokemon.status.effect !== StatusEffect.NONE ? 0 : 1); + }, 0); + maxHits = minHits + } + } + dmgLow = out[0] * minHits + dmgHigh = out[1] * maxHits /* if (user.hasAbility(Abilities.PARENTAL_BOND)) { // Second hit deals 0.25x damage @@ -1062,11 +1092,6 @@ export function simulateAttack(scene: BattleScene, user: Pokemon, target: Pokemo MoveData.applyMoveAttrs(MoveData.ModifiedDamageAttr, user, target, move, damage2); applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, user, target, move, cancelled, damage1); applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, user, target, move, cancelled, damage2); - - //console.log("damage (min)", damage1.value, move.name, power.value, sourceAtk, targetDef); - //console.log("damage (max)", damage2.value, move.name, power.value, sourceAtkCrit, targetDefCrit); - - // In case of fatal damage, this tag would have gotten cleared before we could lapse it. const destinyTag = target.getTag(BattlerTagType.DESTINY_BOND); const oneHitKo = result === HitResult.ONE_HIT_KO; @@ -1100,80 +1125,6 @@ export function simulateAttack(scene: BattleScene, user: Pokemon, target: Pokemo } export function calcDamage(scene: BattleScene, user: PlayerPokemon, target: Pokemon, move: PokemonMove) { - /* - var power = move.getMove().power - var myAtk = 0 - var theirDef = 0 - var myAtkC = 0 - var theirDefC = 0 - switch (move.getMove().category) { - case MoveData.MoveCategory.PHYSICAL: - myAtk = user.getBattleStat(Stat.ATK, target, move.getMove()) - myAtkC = user.getBattleStat(Stat.ATK, target, move.getMove(), true) - theirDef = target.getBattleStat(Stat.DEF, user, move.getMove()) - theirDefC = target.getBattleStat(Stat.DEF, user, move.getMove(), true) - break; - case MoveData.MoveCategory.SPECIAL: - myAtk = user.getBattleStat(Stat.SPATK, target, move.getMove()) - myAtkC = user.getBattleStat(Stat.SPATK, target, move.getMove(), true) - theirDef = target.getBattleStat(Stat.SPDEF, user, move.getMove()) - theirDefC = target.getBattleStat(Stat.SPDEF, user, move.getMove(), true) - break; - case MoveData.MoveCategory.STATUS: - return "---" - } - var stabBonus = 1 - var types = user.getTypes() - // Apply STAB bonus - for (var i = 0; i < types.length; i++) { - if (types[i] == move.getMove().type) { - stabBonus = 1.5 - } - } - // Apply Tera Type bonus - if (stabBonus == 1.5) { - // STAB - if (move.getMove().type == user.getTeraType()) { - stabBonus = 2 - } - } else if (move.getMove().type == user.getTeraType()) { - stabBonus = 1.5 - } - // Apply adaptability - if (stabBonus == 2) { - // Tera-STAB - if (move.getMove().type == user.getTeraType()) { - stabBonus = 2.25 - } - } else if (stabBonus == 1.5) { - // STAB or Tera - if (move.getMove().type == user.getTeraType()) { - stabBonus = 2 - } - } else if (move.getMove().type == user.getTeraType()) { - // Adaptability - stabBonus = 1.5 - } - var weatherBonus = 1 - if (scene.arena.weather.weatherType == WeatherType.RAIN || scene.arena.weather.weatherType == WeatherType.HEAVY_RAIN) { - if (move.getMove().type == Type.WATER) { - weatherBonus = 1.5 - } - if (move.getMove().type == Type.FIRE) { - weatherBonus = scene.arena.weather.weatherType == WeatherType.HEAVY_RAIN ? 0 : 0.5 - } - } - if (scene.arena.weather.weatherType == WeatherType.SUNNY || scene.arena.weather.weatherType == WeatherType.HARSH_SUN) { - if (move.getMove().type == Type.FIRE) { - weatherBonus = 1.5 - } - if (move.getMove().type == Type.WATER) { - weatherBonus = scene.arena.weather.weatherType == WeatherType.HARSH_SUN ? 0 : (move.moveId == Moves.HYDRO_STEAM ? 1.5 : 0.5) - } - } - var typeBonus = target.getAttackMoveEffectiveness(user, move) - var modifiers = stabBonus * weatherBonus - */ var dmgHigh = 0 var dmgLow = 0 // dmgLow = (((2*user.level/5 + 2) * power * myAtk / theirDef)/50 + 2) * 0.85 * modifiers From 761ba69aba33704375858cfddd3494c07bc86433 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Fri, 12 Jul 2024 23:39:26 -0400 Subject: [PATCH 54/94] Remove log length limit for modes outside Daily --- src/logger.ts | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/logger.ts b/src/logger.ts index b05fdd86b15..3bc120364f1 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -17,7 +17,7 @@ import Trainer from "./field/trainer"; import { Species } from "./enums/species"; import { junit } from "node:test/reporters"; import { i } from "vitest/dist/reporters-xEmem8D4.js"; -import { GameModes } from "./game-mode"; +import { GameMode, GameModes } from "./game-mode"; /** * All logs. @@ -658,10 +658,27 @@ export function getWave(drpd: DRPD, floor: integer, scene: BattleScene): Wave { } } } - if (drpd.waves[49] != undefined) { - scene.ui.showText("No space!\nPress F12 for info") - console.error("There should have been 50 slots, but somehow the program ran out of space.") - console.error("Go yell at @redstonewolf8557 to fix this") + if (drpd.waves[drpd.waves.length - 1] != undefined) { + if (scene.gameMode.modeId == GameModes.DAILY) { + scene.ui.showText("No space!\nPress F12 for info") + console.error("There should have been 50 slots, but somehow the program ran out of space.") + console.error("Go yell at @redstonewolf8557 to fix this") + } else { + drpd.waves.push(null) + console.log("Created new wave for floor " + floor + " at newly inserted index " + insertPos) + drpd.waves[drpd.waves.length - 1] = { + id: floor, + reload: false, + //type: floor % 10 == 0 ? "boss" : (floor % 10 == 5 ? "trainer" : "wild"), + type: floor % 10 == 0 ? "boss" : "wild", + double: scene.currentBattle.double, + actions: [], + shop: "", + biome: getBiomeName(scene.arena.biomeType), + pokemon: [] + } + wv = drpd.waves[drpd.waves.length - 1] + } } else { for (var i = 0; i < drpd.waves.length; i++) { if (drpd.waves[i] != undefined && drpd.waves[i] != null) { From 708415518614c734df5490c4469899c55fcf7e98 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Fri, 12 Jul 2024 23:49:51 -0400 Subject: [PATCH 55/94] Fix undefined double battle state --- src/logger.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/logger.ts b/src/logger.ts index 3bc120364f1..6a9ab231abf 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -289,6 +289,7 @@ export function exportWave(scene: BattleScene): Wave { shop: "", biome: getBiomeName(scene.arena.biomeType) } + if (ret.double == undefined) ret.double = false; switch (ret.type) { case "wild": case "boss": From c80c2918050e20712b7dd93924fe410127a279b9 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Sat, 13 Jul 2024 00:00:37 -0400 Subject: [PATCH 56/94] Add log buttons Export for Sheets (ExSheet): Exports a file replacing \n with CHAR(10) Delete: Deletes the file --- src/logger.ts | 17 +++++++++++++++++ src/ui/log-name-form-ui-handler.ts | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/logger.ts b/src/logger.ts index 6a9ab231abf..8d780b72d2d 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -413,6 +413,11 @@ export function generateEditOption(scene: BattleScene, i: integer, saves: any, p downloadLogByID(i) phase.callEnd() }, + () => { + scene.ui.playSelect(); + downloadLogByIDToSheet(i) + phase.callEnd() + },, () => { scene.ui.playSelect(); localStorage.removeItem(logs[i][1]) @@ -535,6 +540,18 @@ export function downloadLogByID(i: integer) { link.click(); link.remove(); } +export function downloadLogByIDToSheet(i: integer) { + console.log(i) + var d = JSON.parse(localStorage.getItem(logs[i][1])) + const blob = new Blob([ printDRPD("", "", d as DRPD).split("\n").join("CHAR(10)") ], {type: "text/json"}); + const link = document.createElement("a"); + link.href = window.URL.createObjectURL(blob); + var date: string = (d as DRPD).date + var filename: string = date[0] + date[1] + "_" + date[3] + date[4] + "_" + date[6] + date[7] + date[8] + date[9] + "_route.json" + link.download = `${filename}`; + link.click(); + link.remove(); +} /** * Calls `logPokemon` once for each opponent or, if it's a trainer battle, logs the trainer's data. * @param scene The BattleScene. Used to get the enemy team and whether it's a trainer battle or not. diff --git a/src/ui/log-name-form-ui-handler.ts b/src/ui/log-name-form-ui-handler.ts index d51adee3796..1b285f0cb40 100644 --- a/src/ui/log-name-form-ui-handler.ts +++ b/src/ui/log-name-form-ui-handler.ts @@ -24,7 +24,7 @@ export default class LogNameFormUiHandler extends FormModalUiHandler { } getButtonLabels(config?: ModalConfig): string[] { - return [ "Rename", "Export" ]; + return [ "Rename", "Export", "ExSheet", "Delete" ]; } getReadableErrorMessage(error: string): string { From 9dec435ecf50c4667f89b3d4040b743297891e6b Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Sat, 13 Jul 2024 00:27:19 -0400 Subject: [PATCH 57/94] Fix export to sheets --- src/logger.ts | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/logger.ts b/src/logger.ts index 8d780b72d2d..8e9cf415951 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -528,6 +528,7 @@ export function downloadLog(keyword: string) { * Saves a log to your device. * @param i The index of the log you want to save. */ +export const SheetsMode = new Utils.BooleanHolder(false) export function downloadLogByID(i: integer) { console.log(i) var d = JSON.parse(localStorage.getItem(logs[i][1])) @@ -543,7 +544,9 @@ export function downloadLogByID(i: integer) { export function downloadLogByIDToSheet(i: integer) { console.log(i) var d = JSON.parse(localStorage.getItem(logs[i][1])) - const blob = new Blob([ printDRPD("", "", d as DRPD).split("\n").join("CHAR(10)") ], {type: "text/json"}); + SheetsMode.value = true; + const blob = new Blob([ printDRPD("", "", d as DRPD) ], {type: "text/json"}); + SheetsMode.value = false; const link = document.createElement("a"); link.href = window.URL.createObjectURL(blob); var date: string = (d as DRPD).date @@ -1074,19 +1077,23 @@ function printWave(inData: string, indent: string, wave: Wave): string { inData += ",\n" + indent + " \"double\": " + wave.double + "" var isFirst = true if (wave.actions.length > 0) { - inData += ",\n" + indent + " \"actions\": [" - for (var i = 0; i < wave.actions.length; i++) { - if (wave.actions[i] != undefined) { - if (isFirst) { - isFirst = false; - } else { - inData += "," + if (SheetsMode.value) { + inData += ",\n" + indent + " \"actions\": [" + wave.actions.join("CHAR(10)") + "]" + } else { + inData += ",\n" + indent + " \"actions\": [" + for (var i = 0; i < wave.actions.length; i++) { + if (wave.actions[i] != undefined) { + if (isFirst) { + isFirst = false; + } else { + inData += "," + } + inData += "\n " + indent + "\"" + wave.actions[i] + "\"" } - inData += "\n " + indent + "\"" + wave.actions[i] + "\"" } + if (!isFirst) inData += "\n" + inData += indent + " ]" } - if (!isFirst) inData += "\n" - inData += indent + " ]" } else { inData += ",\n" + indent + " \"actions\": []" } From 3bae3fd3029d8cea84c65da9d674eaa078de5daf Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Sat, 13 Jul 2024 01:16:28 -0400 Subject: [PATCH 58/94] Exit sheets exporter --- src/logger.ts | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/logger.ts b/src/logger.ts index 8e9cf415951..82a3090c9bb 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1117,6 +1117,9 @@ function printWave(inData: string, indent: string, wave: Wave): string { inData = printPoke(inData, indent + " ", wave.pokemon[i]) } } + if (SheetsMode.value && wave.pokemon.length == 1) { + inData += "," + indent + " \n{" + indent + " \n}" + } inData += "\n" + indent + " ]" } inData += "\n" + indent + "}" @@ -1144,24 +1147,25 @@ function printPoke(inData: string, indent: string, pokemon: PokeData) { inData += ",\n" + indent + " \"rarity\": \"" + pokemon.rarity + "\"" inData += ",\n" + indent + " \"captured\": " + pokemon.captured inData += ",\n" + indent + " \"level\": " + pokemon.level - if (pokemon.items.length > 0) { - inData += ",\n" + indent + " \"items\": [\n" - var isFirst = true - for (var i = 0; i < pokemon.items.length; i++) { - if (pokemon.items[i] != undefined) { - if (isFirst) { - isFirst = false; - } else { - inData += "," + if (!SheetsMode.value) + if (pokemon.items.length > 0) { + inData += ",\n" + indent + " \"items\": [\n" + var isFirst = true + for (var i = 0; i < pokemon.items.length; i++) { + if (pokemon.items[i] != undefined) { + if (isFirst) { + isFirst = false; + } else { + inData += "," + } + inData = printItem(inData, indent + " ", pokemon.items[i]) } - inData = printItem(inData, indent + " ", pokemon.items[i]) } + if (!isFirst) inData += "\n" + inData += indent + " ]" + } else { + inData += ",\n" + indent + " \"items\": []" } - if (!isFirst) inData += "\n" - inData += indent + " ]" - } else { - inData += ",\n" + indent + " \"items\": []" - } inData += ",\n" + indent + " \"ivs\": " inData = printIV(inData, indent + " ", pokemon.ivs) //inData += ",\n" + indent + " \"rarity\": " + pokemon.rarity From 8c89200422964a9d1cd270f949ad0f804e9df418 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Sat, 13 Jul 2024 11:14:46 -0400 Subject: [PATCH 59/94] Fix catching bugs Actions now log in the correct order when you catch something Log adding/skipping Pokemon when your party is full Fix a small spelling error --- src/phases.ts | 11 ++++++++--- src/ui/arena-flyout.ts | 3 ++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/phases.ts b/src/phases.ts index 037a63a6dd4..b34f361150c 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -2996,6 +2996,11 @@ export class TurnStartPhase extends FieldPhase { this.scene.arenaFlyout.updateFieldText() + if (LoggerTools.Actions.length > 1 && (LoggerTools.Actions[0] == "" || LoggerTools.Actions[0] == undefined || LoggerTools.Actions[0] == null)) + LoggerTools.Actions.shift() // If the left slot isn't doing anything, delete its entry + + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, LoggerTools.Actions.join(" | ")) + this.end(); } } @@ -3097,8 +3102,6 @@ export class TurnEndPhase extends FieldPhase { this.scene.arena.trySetTerrain(TerrainType.NONE, false); } - LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, LoggerTools.Actions.join(" | ")) - this.end(); } } @@ -5094,7 +5097,7 @@ export class SwitchPhase extends BattlePhase { LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, "Pre-switch " + (option == PartyOption.PASS_BATON ? "+ Baton" : "") + " to " + LoggerTools.playerPokeName(this.scene, slotIndex)) } if (LoggerTools.isFaintSwitch.value) { - LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, (option == PartyOption.PASS_BATON ? "Baton" : "Switch") + "in " + LoggerTools.playerPokeName(this.scene, slotIndex)) + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, (option == PartyOption.PASS_BATON ? "Baton" : "Switch") + " in " + LoggerTools.playerPokeName(this.scene, slotIndex)) } this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, fieldIndex, slotIndex, this.doReturn, option === PartyOption.PASS_BATON)); } @@ -5668,6 +5671,7 @@ export class AttemptCapturePhase extends PokemonPhase { this.scene.ui.setMode(Mode.PARTY, PartyUiMode.RELEASE, this.fieldIndex, (slotIndex: integer, _option: PartyOption) => { this.scene.ui.setMode(Mode.MESSAGE).then(() => { if (slotIndex < 6) { + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, "Release " + LoggerTools.playerPokeName(this.scene, slotIndex)) addToParty(); } else { promptRelease(); @@ -5675,6 +5679,7 @@ export class AttemptCapturePhase extends PokemonPhase { }); }); }, () => { + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, "Do Not Keep") this.scene.ui.setMode(Mode.MESSAGE).then(() => { removePokemon(); end(); diff --git a/src/ui/arena-flyout.ts b/src/ui/arena-flyout.ts index e80ebd18478..fd21397594e 100644 --- a/src/ui/arena-flyout.ts +++ b/src/ui/arena-flyout.ts @@ -269,9 +269,10 @@ export default class ArenaFlyout extends Phaser.GameObjects.Container { this.flyoutTextPlayer.text = "" this.flyoutTextField.text = "" this.flyoutTextEnemy.text = "" - this.flyoutTextHeaderField.text = "Stats" + this.flyoutTextHeaderField.text = "" this.flyoutTextHeaderPlayer.text = "" this.flyoutTextHeaderEnemy.text = "" + this.flyoutTextHeader.text = "Game Logs" this.flyoutTextPlayer.setPosition(6, 4) this.flyoutTextPlayer.setFontSize(30); var instructions = [] From ff7e215d6c21250ea88590b242a9fe69d6bff81c Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Sat, 13 Jul 2024 12:50:34 -0400 Subject: [PATCH 60/94] Framework for autosaves --- src/logger.ts | 9 ++++++ src/phases.ts | 8 ++--- src/system/game-data.ts | 18 +++++++---- src/ui/save-slot-select-ui-handler.ts | 46 +++++++++++++++++++++------ 4 files changed, 61 insertions(+), 20 deletions(-) diff --git a/src/logger.ts b/src/logger.ts index 82a3090c9bb..0e2644873cd 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -40,6 +40,15 @@ export const logKeys: string[] = [ "d", // Debug ]; +export const autoCheckpoints: integer[] = [ + 1, + 11, + 21, + 31, + 41, + 50 +] + /** * Uses the save's RNG seed to create a log ID. Used to assign each save its own log. * @param scene The BattleScene. diff --git a/src/phases.ts b/src/phases.ts index b34f361150c..5e0adac600a 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -598,11 +598,11 @@ export class TitlePhase extends Phase { label: i18next.t("menu:loadGame"), handler: () => { this.scene.ui.setOverlayMode(Mode.SAVE_SLOT, SaveSlotUiMode.LOAD, - (slotId: integer) => { + (slotId: integer, autoSlot: integer) => { if (slotId === -1) { return this.showOptions(); } - this.loadSaveSlot(slotId); + this.loadSaveSlot(slotId, autoSlot); }); return true; } @@ -631,10 +631,10 @@ export class TitlePhase extends Phase { this.scene.ui.setMode(Mode.TITLE, config); } - loadSaveSlot(slotId: integer): void { + loadSaveSlot(slotId: integer, autoSlot?: integer): void { this.scene.sessionSlotId = slotId > -1 ? slotId : loggedInUser.lastSessionSlot; this.scene.ui.setMode(Mode.MESSAGE); - this.scene.gameData.loadSession(this.scene, slotId, slotId === -1 ? this.lastSessionData : null).then((success: boolean) => { + this.scene.gameData.loadSession(this.scene, slotId, slotId === -1 ? this.lastSessionData : null, autoSlot).then((success: boolean) => { if (success) { this.loaded = true; this.scene.ui.showText(i18next.t("menu:sessionSuccess"), null, () => this.end()); diff --git a/src/system/game-data.ts b/src/system/game-data.ts index ff4b9fb686b..6d0c47e841b 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -124,6 +124,7 @@ export interface SessionSaveData { challenges: ChallengeData[]; slot: integer; description: string; + autoSlot: integer; } interface Unlocks { @@ -842,7 +843,7 @@ export class GameData { } as SessionSaveData; } - getSession(slotId: integer): Promise { + getSession(slotId: integer, autoSlot?: integer): Promise { return new Promise(async (resolve, reject) => { if (slotId < 0) { return resolve(null); @@ -850,14 +851,18 @@ export class GameData { const handleSessionData = async (sessionDataStr: string) => { try { const sessionData = this.parseSessionData(sessionDataStr); + sessionData.autoSlot = autoSlot; resolve(sessionData); } catch (err) { reject(err); return; } }; - - if (!bypassLogin && !localStorage.getItem(`sessionData${slotId ? slotId : ""}_${loggedInUser.username}`)) { + var autokey = "" + if (autoSlot != undefined) { + autokey = "_auto" + autoSlot + } + if (!bypassLogin && !localStorage.getItem(`sessionData${slotId ? slotId : ""}_${loggedInUser.username}${autokey}`)) { Utils.apiFetch(`savedata/session/get?slot=${slotId}&clientSessionId=${clientSessionId}`, true) .then(response => response.text()) .then(async response => { @@ -871,7 +876,8 @@ export class GameData { await handleSessionData(response); }); } else { - const sessionData = localStorage.getItem(`sessionData${slotId ? slotId : ""}_${loggedInUser.username}`); + const sessionData = localStorage.getItem(`sessionData${slotId ? slotId : ""}_${loggedInUser.username}${autokey}`); + console.log(`sessionData${slotId ? slotId : ""}_${loggedInUser.username}${autokey}`, sessionData) if (sessionData) { await handleSessionData(decrypt(sessionData, bypassLogin)); } else { @@ -881,7 +887,7 @@ export class GameData { }); } - loadSession(scene: BattleScene, slotId: integer, sessionData?: SessionSaveData): Promise { + loadSession(scene: BattleScene, slotId: integer, sessionData?: SessionSaveData, autoSlot?: integer): Promise { return new Promise(async (resolve, reject) => { try { const initSessionFromData = async (sessionData: SessionSaveData) => { @@ -981,7 +987,7 @@ export class GameData { if (sessionData) { initSessionFromData(sessionData); } else { - this.getSession(slotId) + this.getSession(slotId, autoSlot) .then(data => initSessionFromData(data)) .catch(err => { reject(err); diff --git a/src/ui/save-slot-select-ui-handler.ts b/src/ui/save-slot-select-ui-handler.ts index 8a81ac4858d..6dedcca229c 100644 --- a/src/ui/save-slot-select-ui-handler.ts +++ b/src/ui/save-slot-select-ui-handler.ts @@ -10,6 +10,7 @@ import MessageUiHandler from "./message-ui-handler"; import { TextStyle, addTextObject } from "./text"; import { Mode } from "./ui"; import { addWindow } from "./ui-theme"; +import * as LoggerTools from "../logger" const sessionSlotCount = 5; @@ -18,7 +19,7 @@ export enum SaveSlotUiMode { SAVE } -export type SaveSlotSelectCallback = (cursor: integer) => void; +export type SaveSlotSelectCallback = (cursor: integer, cursor2?: integer) => void; export default class SaveSlotSelectUiHandler extends MessageUiHandler { @@ -106,7 +107,7 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { switch (this.uiMode) { case SaveSlotUiMode.LOAD: this.saveSlotSelectCallback = null; - originalCallback(cursor); + originalCallback(this.sessionSlots[cursor].slotId, this.sessionSlots[cursor].autoSlot); break; case SaveSlotUiMode.SAVE: const saveAndCallback = () => { @@ -115,8 +116,11 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { ui.revertMode(); ui.showText(null, 0); ui.setMode(Mode.MESSAGE); - originalCallback(cursor); + originalCallback(this.sessionSlots[cursor].slotId, this.sessionSlots[cursor].autoSlot); }; + if (this.sessionSlots[cursor].autoSlot != undefined) { + return false; + } if (this.sessionSlots[cursor].hasData) { ui.showText(i18next.t("saveSlotSelectUiHandler:overwriteData"), null, () => { ui.setOverlayMode(Mode.CONFIRM, () => { @@ -158,7 +162,7 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { case Button.DOWN: if (this.cursor < 2) { success = this.setCursor(this.cursor + 1); - } else if (this.scrollCursor < sessionSlotCount - 3) { + } else if (this.scrollCursor < this.sessionSlots.length - 3) { success = this.setScrollCursor(this.scrollCursor + 1); } break; @@ -175,12 +179,28 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { } populateSessionSlots() { + var ui = this.getUi(); + var ypos = 0; for (let s = 0; s < sessionSlotCount; s++) { - const sessionSlot = new SessionSlot(this.scene, s); + const sessionSlot = new SessionSlot(this.scene, s, ypos); + ypos++ sessionSlot.load(); this.scene.add.existing(sessionSlot); this.sessionSlotsContainer.add(sessionSlot); this.sessionSlots.push(sessionSlot); + if (this.uiMode != SaveSlotUiMode.SAVE) { + for (var j = 0; j < LoggerTools.autoCheckpoints.length; j++) { + var k = "sessionData" + (s ? s : "") + "_Guest_auto" + j + if (localStorage.getItem(k) != null) { + const sessionSlot = new SessionSlot(this.scene, s, ypos, j); + ypos++ + sessionSlot.load(); + this.scene.add.existing(sessionSlot); + this.sessionSlotsContainer.add(sessionSlot); + this.sessionSlots.push(sessionSlot); + } + } + } } } @@ -251,13 +271,15 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { class SessionSlot extends Phaser.GameObjects.Container { public slotId: integer; + public autoSlot: integer; public hasData: boolean; private loadingLabel: Phaser.GameObjects.Text; - constructor(scene: BattleScene, slotId: integer) { - super(scene, 0, slotId * 56); + constructor(scene: BattleScene, slotId: integer, ypos: integer, autoSlot?: integer) { + super(scene, 0, ypos * 56); this.slotId = slotId; + this.autoSlot = autoSlot this.setup(); } @@ -273,8 +295,12 @@ class SessionSlot extends Phaser.GameObjects.Container { async setupWithData(data: SessionSaveData) { this.remove(this.loadingLabel, true); - - const gameModeLabel = addTextObject(this.scene, 8, 5, `${GameMode.getModeName(data.gameMode) || i18next.t("gameMode:unkown")} - ${i18next.t("saveSlotSelectUiHandler:wave")} ${data.waveIndex}`, TextStyle.WINDOW); + var lbl = `${GameMode.getModeName(data.gameMode) || i18next.t("gameMode:unkown")} - ${i18next.t("saveSlotSelectUiHandler:wave")} ${data.waveIndex}` + if (this.autoSlot != undefined) { + lbl = `Slot ${this.slotId} (Auto) - ${i18next.t("saveSlotSelectUiHandler:wave")} ${data.waveIndex}` + } + console.log(data, this.slotId, this.autoSlot, lbl) + const gameModeLabel = addTextObject(this.scene, 8, 5, lbl, TextStyle.WINDOW); this.add(gameModeLabel); const timestampLabel = addTextObject(this.scene, 8, 19, new Date(data.timestamp).toLocaleString(), TextStyle.WINDOW); @@ -329,7 +355,7 @@ class SessionSlot extends Phaser.GameObjects.Container { load(): Promise { return new Promise(resolve => { - this.scene.gameData.getSession(this.slotId).then(async sessionData => { + this.scene.gameData.getSession(this.slotId, this.autoSlot).then(async sessionData => { if (!sessionData) { this.hasData = false; this.loadingLabel.setText(i18next.t("saveSlotSelectUiHandler:empty")); From 3af044de2b0581d0948b4751ea23f403a44bbf56 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Sat, 13 Jul 2024 14:23:33 -0400 Subject: [PATCH 61/94] Autosaves Game will create a duplicate of your save on waves 1, 11, 21, 31, 41, and 50 --- src/logger.ts | 54 +++++++++++++++++++++++++-- src/phases.ts | 4 ++ src/system/game-data.ts | 10 ++++- src/ui/log-name-form-ui-handler.ts | 23 ++++++++++-- src/ui/save-slot-select-ui-handler.ts | 6 +++ 5 files changed, 88 insertions(+), 9 deletions(-) diff --git a/src/logger.ts b/src/logger.ts index 0e2644873cd..0ea68fedb02 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -414,20 +414,24 @@ export function generateEditOption(scene: BattleScene, i: integer, saves: any, p ], buttonActions: [ () => { + console.log("Rename") scene.ui.playSelect(); phase.callEnd() }, () => { + console.log("Export") scene.ui.playSelect(); downloadLogByID(i) phase.callEnd() }, () => { + console.log("Export to Sheets") scene.ui.playSelect(); downloadLogByIDToSheet(i) phase.callEnd() - },, + }, () => { + console.log("Delete") scene.ui.playSelect(); localStorage.removeItem(logs[i][1]) phase.callEnd() @@ -438,7 +442,7 @@ export function generateEditOption(scene: BattleScene, i: integer, saves: any, p } } for (var j = 0; j < saves.length; j++) { - console.log(saves[j].seed, logs[i][2], saves[j].seed == logs[i][2]) + //console.log(saves[j].seed, logs[i][2], saves[j].seed == logs[i][2]) if (saves[j].seed == logs[i][2]) { op.label = "[Slot " + (saves[j].slot + 1) + "]" + op.label.substring(6) } @@ -498,6 +502,7 @@ export function clearLog(keyword: string) { localStorage.setItem(logs[logKeys.indexOf(keyword)][1], "---- " + logs[logKeys.indexOf(keyword)][3] + " ----" + logs[logKeys.indexOf(keyword)][5]) } export function setFileInfo(title: string, authors: string[]) { + console.log("Setting file " + rarityslot[1] + " to " + title + " / [" + authors.join(", ") + "]") var fileID = rarityslot[1] as string var drpd = JSON.parse(localStorage.getItem(fileID)) as DRPD; drpd.title = title; @@ -1045,6 +1050,7 @@ export function flagResetIfExists(scene: BattleScene, floor: integer = undefined * @see printWave */ export function printDRPD(inData: string, indent: string, drpd: DRPD): string { + console.log("Printing for sheet?: " + SheetsMode.value) inData += indent + "{" inData += "\n" + indent + " \"version\": \"" + drpd.version + "\"" inData += ",\n" + indent + " \"title\": \"" + drpd.title + "\"" @@ -1087,7 +1093,19 @@ function printWave(inData: string, indent: string, wave: Wave): string { var isFirst = true if (wave.actions.length > 0) { if (SheetsMode.value) { - inData += ",\n" + indent + " \"actions\": [" + wave.actions.join("CHAR(10)") + "]" + inData += ",\n" + indent + " \"actions\": \"" + var isFirst = true + for (var i = 0; i < wave.actions.length; i++) { + if (wave.actions[i] != undefined) { + if (isFirst) { + isFirst = false; + } else { + inData += "CHAR(10)" + } + inData += wave.actions[i] + } + } + inData += "\"" } else { inData += ",\n" + indent + " \"actions\": [" for (var i = 0; i < wave.actions.length; i++) { @@ -1156,7 +1174,21 @@ function printPoke(inData: string, indent: string, pokemon: PokeData) { inData += ",\n" + indent + " \"rarity\": \"" + pokemon.rarity + "\"" inData += ",\n" + indent + " \"captured\": " + pokemon.captured inData += ",\n" + indent + " \"level\": " + pokemon.level - if (!SheetsMode.value) + if (SheetsMode.value) { + inData += ",\n" + indent + " \"items\": \"" + var isFirst = true + for (var i = 0; i < pokemon.items.length; i++) { + if (pokemon.items[i] != undefined) { + if (isFirst) { + isFirst = false; + } else { + inData += "CHAR(10)" + } + inData += printItemNoNewline(inData, "", pokemon.items[i]) + } + } + inData += "\"" + } else { if (pokemon.items.length > 0) { inData += ",\n" + indent + " \"items\": [\n" var isFirst = true @@ -1175,6 +1207,7 @@ function printPoke(inData: string, indent: string, pokemon: PokeData) { } else { inData += ",\n" + indent + " \"items\": []" } + } inData += ",\n" + indent + " \"ivs\": " inData = printIV(inData, indent + " ", pokemon.ivs) //inData += ",\n" + indent + " \"rarity\": " + pokemon.rarity @@ -1252,6 +1285,19 @@ function printItem(inData: string, indent: string, item: ItemData) { inData += "\n" + indent + "}" return inData; } +/** + * Prints an item as a string, for saving a DRPD to your device. + * @param inData The data to add on to. + * @param indent The indent string (just a bunch of spaces). + * @param wave The `ItemData` to export. + * @returns `inData`, with all the Item's data appended to it. + * + * @see printDRPD + */ +function printItemNoNewline(inData: string, indent: string, item: ItemData) { + inData = "{\\\"id\\\": \\\"" + item.id + "\\\", \\\"name\\\": \\\"" + item.name + "\\\", \\\"quantity\\\": " + item.quantity + "}" + return inData; +} function updateLog(drpd: DRPD): DRPD { diff --git a/src/phases.ts b/src/phases.ts index 5e0adac600a..820b56817ba 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -1366,6 +1366,10 @@ export class EncounterPhase extends BattlePhase { } LoggerTools.resetWaveActions(this.scene) + if (LoggerTools.autoCheckpoints.includes(this.scene.currentBattle.waveIndex)) { + this.scene.gameData.saveGameToAuto(this.scene) + } + if (this.scene.currentBattle.battleType === BattleType.WILD) { enemyField.forEach(enemyPokemon => { enemyPokemon.untint(100, "Sine.easeOut"); diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 6d0c47e841b..204825b6deb 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -40,6 +40,7 @@ import { GameDataType } from "#enums/game-data-type"; import { Moves } from "#enums/moves"; import { PlayerGender } from "#enums/player-gender"; import { Species } from "#enums/species"; +import * as LoggerTools from "../logger" export const defaultStarterSpecies: Species[] = [ Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE, @@ -877,7 +878,7 @@ export class GameData { }); } else { const sessionData = localStorage.getItem(`sessionData${slotId ? slotId : ""}_${loggedInUser.username}${autokey}`); - console.log(`sessionData${slotId ? slotId : ""}_${loggedInUser.username}${autokey}`, sessionData) + //console.log(`sessionData${slotId ? slotId : ""}_${loggedInUser.username}${autokey}`, sessionData) if (sessionData) { await handleSessionData(decrypt(sessionData, bypassLogin)); } else { @@ -1157,6 +1158,13 @@ export class GameData { }) as SessionSaveData; } + saveGameToAuto(scene: BattleScene) { + var autoSlot = LoggerTools.autoCheckpoints.indexOf(scene.currentBattle.waveIndex) + var dat = this.getSessionSaveData(scene) + console.log(`Stored autosave as sessionData${scene.sessionSlotId ? scene.sessionSlotId : ""}_${loggedInUser.username}_auto${autoSlot}`) + localStorage.setItem(`sessionData${scene.sessionSlotId ? scene.sessionSlotId : ""}_${loggedInUser.username}_auto${autoSlot}`, encrypt(JSON.stringify(dat), bypassLogin)); + } + saveAll(scene: BattleScene, skipVerification: boolean = false, sync: boolean = false, useCachedSession: boolean = false, useCachedSystem: boolean = false): Promise { return new Promise(resolve => { Utils.executeIf(!skipVerification, updateUserInfo).then(success => { diff --git a/src/ui/log-name-form-ui-handler.ts b/src/ui/log-name-form-ui-handler.ts index 1b285f0cb40..c6fce79836a 100644 --- a/src/ui/log-name-form-ui-handler.ts +++ b/src/ui/log-name-form-ui-handler.ts @@ -7,8 +7,10 @@ import * as LoggerTools from "../logger"; import { addTextObject, TextStyle } from "./text"; export default class LogNameFormUiHandler extends FormModalUiHandler { + name: string; + getModalTitle(config?: ModalConfig): string { - return "New Log"; + return "Manage " + (this.name ? this.name : "Log"); } getFields(config?: ModalConfig): string[] { @@ -49,8 +51,7 @@ export default class LogNameFormUiHandler extends FormModalUiHandler { setup(): void { super.setup(); - //const label = addTextObject(this.scene, 10, 87, "Text", TextStyle.TOOLTIP_CONTENT, { fontSize: "42px" }); - + //const label = addTextObject(this.scene, 10, 87, "Clicking Export or ExSheets does NOT save any text you entered\nPress \"Rename\", then reopen this menu and click Export", TextStyle.TOOLTIP_CONTENT, { fontSize: "42px" }); //this.modalContainer.add(label); this.inputs[0].maxLength = 99 @@ -58,14 +59,16 @@ export default class LogNameFormUiHandler extends FormModalUiHandler { } show(args: any[]): boolean { - console.error("Shown") + this.name = args[0].autofillfields[0] if (super.show(args)) { const config = args[0] as ModalConfig; + console.log("Shown", args) const originalLoginAction = this.submitAction; this.inputs[0].setText(args[0].autofillfields[0]) this.inputs[1].setText(args[0].autofillfields[1]) this.submitAction = (_) => { + console.log("submitAction") // Prevent overlapping overrides on action modification this.submitAction = originalLoginAction; this.sanitizeInputs(); @@ -77,9 +80,21 @@ export default class LogNameFormUiHandler extends FormModalUiHandler { if (!this.inputs[0].text) { //return onFail(i18next.t("menu:emptyUsername")); } + console.log(`Calling LoggerTools.setFileInfo(${this.inputs[0].text}, ${this.inputs[1].text.split(",")})`) LoggerTools.setFileInfo(this.inputs[0].text, this.inputs[1].text.split(",")) + console.log(`Calling originalLoginAction()`) originalLoginAction() }; + const exportaction1 = config.buttonActions[1] + config.buttonActions[1] = (_) => { + LoggerTools.setFileInfo(this.inputs[0].text, this.inputs[1].text.split(",")) + exportaction1() + } + const exportaction2 = config.buttonActions[2] + config.buttonActions[2] = (_) => { + LoggerTools.setFileInfo(this.inputs[0].text, this.inputs[1].text.split(",")) + exportaction2() + } return true; } diff --git a/src/ui/save-slot-select-ui-handler.ts b/src/ui/save-slot-select-ui-handler.ts index 6dedcca229c..e878de3d692 100644 --- a/src/ui/save-slot-select-ui-handler.ts +++ b/src/ui/save-slot-select-ui-handler.ts @@ -11,6 +11,7 @@ import { TextStyle, addTextObject } from "./text"; import { Mode } from "./ui"; import { addWindow } from "./ui-theme"; import * as LoggerTools from "../logger" +import { loggedInUser } from "#app/account.js"; const sessionSlotCount = 5; @@ -113,6 +114,11 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { const saveAndCallback = () => { const originalCallback = this.saveSlotSelectCallback; this.saveSlotSelectCallback = null; + var dataslot = this.sessionSlots[cursor].slotId + for (var i = 0; i < LoggerTools.autoCheckpoints.length; i++) { + // Delete any autosaves associated with this slot + localStorage.removeItem(`sessionData${dataslot ? dataslot : ""}_${loggedInUser.username}_auto${i}`) + } ui.revertMode(); ui.showText(null, 0); ui.setMode(Mode.MESSAGE); From 48ff515269643f99e9bdcb21cf595b9a5a65236e Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Sat, 13 Jul 2024 16:31:04 -0400 Subject: [PATCH 62/94] Fancy Title Screen Tutorials are off by default Type Hints are on by default Added an option to make the title screen's biome change as you navigate menus --- src/battle-scene.ts | 17 +++++++++++++++-- src/logger.ts | 2 ++ src/system/settings/settings.ts | 22 +++++++++++++++++++--- src/ui/abstact-option-select-ui-handler.ts | 2 ++ src/ui/save-slot-select-ui-handler.ts | 22 ++++++++++++++++++---- src/utils.ts | 1 + 6 files changed, 57 insertions(+), 9 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index ebef2f4dd6d..466fb384206 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -122,6 +122,7 @@ export default class BattleScene extends SceneBase { public enableRetries: boolean = false; public damageDisplay: string = "Off"; public lazyReloads: boolean = false; + public menuChangesBiome: boolean; /** * Determines the condition for a notification should be shown for Candy Upgrades * - 0 = 'Off' @@ -256,6 +257,8 @@ export default class BattleScene extends SceneBase { public eventManager: TimedEventManager; + public biomeChangeMode: boolean = false; + /** * Allows subscribers to listen for events * @@ -298,6 +301,7 @@ export default class BattleScene extends SceneBase { Phaser.Math.RND.realInRange = function (min: number, max: number): number { const ret = originalRealInRange.apply(this, [ min, max ]); const args = [ "RNG", ++scene.rngCounter, ret / (max - min), `min: ${min} / max: ${max}` ]; + scene.setScoreText("RNG: " + this.rngCounter + ")") args.push(`seed: ${scene.rngSeedOverride || scene.waveSeed || scene.seed}`); if (scene.rngOffset) { args.push(`offset: ${scene.rngOffset}`); @@ -901,6 +905,7 @@ export default class BattleScene extends SceneBase { setSeed(seed: string): void { this.seed = seed; this.rngCounter = 0; + //this.setScoreText("RNG: 0") this.waveCycleOffset = this.getGeneratedWaveCycleOffset(); this.offsetGym = this.gameMode.isClassic && this.getGeneratedOffsetGym(); } @@ -1352,6 +1357,7 @@ export default class BattleScene extends SceneBase { Phaser.Math.RND.sow([ this.waveSeed ]); console.log("Wave Seed:", this.waveSeed, wave); this.rngCounter = 0; + //this.setScoreText("RNG: 0") } executeWithSeedOffset(func: Function, offset: integer, seedOverride?: string): void { @@ -1368,6 +1374,7 @@ export default class BattleScene extends SceneBase { this.rngSeedOverride = seedOverride || ""; func(); Phaser.Math.RND.state(state); + this.setScoreText("RNG: " + tempRngCounter + " (Last sim: " + this.rngCounter + ")") this.rngCounter = tempRngCounter; this.rngOffset = tempRngOffset; this.rngSeedOverride = tempRngSeedOverride; @@ -1497,10 +1504,16 @@ export default class BattleScene extends SceneBase { } updateScoreText(): void { - this.scoreText.setText(`Score: ${this.score.toString()}`); - this.scoreText.setVisible(this.gameMode.isDaily); + //this.scoreText.setText(`Score: ${this.score.toString()}`); + //this.scoreText.setVisible(this.gameMode.isDaily); } setScoreText(text: string): void { + if (this.scoreText == undefined) + return; + if (this.scoreText.setText == undefined) + return; + if (this.scoreText.setVisible == undefined) + return; this.scoreText.setText(text); this.scoreText.setVisible(true); } diff --git a/src/logger.ts b/src/logger.ts index 0ea68fedb02..a09e2b667c8 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -40,6 +40,8 @@ export const logKeys: string[] = [ "d", // Debug ]; +export const RNGState: number[] = [] + export const autoCheckpoints: integer[] = [ 1, 11, diff --git a/src/system/settings/settings.ts b/src/system/settings/settings.ts index 5897490b2a2..e1ef70898be 100644 --- a/src/system/settings/settings.ts +++ b/src/system/settings/settings.ts @@ -100,7 +100,8 @@ export const SettingKeys = { Music_Preference: "MUSIC_PREFERENCE", Show_BGM_Bar: "SHOW_BGM_BAR", Damage_Display: "DAMAGE_DISPLAY", - LazyReloads: "FLAG_EVERY_RESET_AS_RELOAD" + LazyReloads: "FLAG_EVERY_RESET_AS_RELOAD", + FancyBiome: "FANCY_BIOMES" }; /** @@ -176,6 +177,19 @@ export const Setting: Array = [ default: 0, type: SettingType.GENERAL, }, + { + key: SettingKeys.FancyBiome, + label: "Fancy Title Screen", + options: [{ + label: "Off", + value: "Off" + }, { + label: "On", + value: "On" + }], + default: 0, + type: SettingType.GENERAL, + }, { key: SettingKeys.HP_Bar_Speed, label: i18next.t("settings:hpBarSpeed"), @@ -278,7 +292,7 @@ export const Setting: Array = [ key: SettingKeys.Tutorials, label: i18next.t("settings:tutorials"), options: OFF_ON, - default: 0, + default: 1, type: SettingType.GENERAL }, { @@ -518,7 +532,7 @@ export const Setting: Array = [ key: SettingKeys.Type_Hints, label: i18next.t("settings:typeHints"), options: OFF_ON, - default: 1, + default: 0, type: SettingType.DISPLAY }, { @@ -640,6 +654,8 @@ export function setSetting(scene: BattleScene, setting: string, value: integer): scene.damageDisplay = Setting[index].options[value].value case SettingKeys.LazyReloads: scene.lazyReloads = Setting[index].options[value].value == "On" + case SettingKeys.FancyBiome: + scene.menuChangesBiome = Setting[index].options[value].value == "On" case SettingKeys.Skip_Seen_Dialogues: scene.skipSeenDialogues = Setting[index].options[value].value === "On"; break; diff --git a/src/ui/abstact-option-select-ui-handler.ts b/src/ui/abstact-option-select-ui-handler.ts index 2069f034e89..eda469adaf3 100644 --- a/src/ui/abstact-option-select-ui-handler.ts +++ b/src/ui/abstact-option-select-ui-handler.ts @@ -6,6 +6,8 @@ import { addWindow } from "./ui-theme"; import * as Utils from "../utils"; import { argbFromRgba } from "@material/material-color-utilities"; import {Button} from "#enums/buttons"; +import { Biome } from "#app/enums/biome.js"; +import { getBiomeKey } from "#app/field/arena.js"; export interface OptionSelectConfig { xOffset?: number; diff --git a/src/ui/save-slot-select-ui-handler.ts b/src/ui/save-slot-select-ui-handler.ts index e878de3d692..50d7ce00dca 100644 --- a/src/ui/save-slot-select-ui-handler.ts +++ b/src/ui/save-slot-select-ui-handler.ts @@ -107,8 +107,20 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { } else { switch (this.uiMode) { case SaveSlotUiMode.LOAD: - this.saveSlotSelectCallback = null; - originalCallback(this.sessionSlots[cursor].slotId, this.sessionSlots[cursor].autoSlot); + if (this.sessionSlots[cursor].autoSlot) { + ui.showText("This will revert slot " + (this.sessionSlots[cursor].slotId + 1) + " to wave " + (this.sessionSlots[cursor].wv) + ".\nIs that okay?", null, () => { + ui.setOverlayMode(Mode.CONFIRM, () => { + this.saveSlotSelectCallback = null; + originalCallback(this.sessionSlots[cursor].slotId, this.sessionSlots[cursor].autoSlot); + }, () => { + ui.revertMode(); + ui.showText(null, 0); + }, false, 0, 19, 500); + }); + } else { + this.saveSlotSelectCallback = null; + originalCallback(this.sessionSlots[cursor].slotId, this.sessionSlots[cursor].autoSlot); + } break; case SaveSlotUiMode.SAVE: const saveAndCallback = () => { @@ -279,6 +291,7 @@ class SessionSlot extends Phaser.GameObjects.Container { public slotId: integer; public autoSlot: integer; public hasData: boolean; + public wv: integer; private loadingLabel: Phaser.GameObjects.Text; constructor(scene: BattleScene, slotId: integer, ypos: integer, autoSlot?: integer) { @@ -301,9 +314,10 @@ class SessionSlot extends Phaser.GameObjects.Container { async setupWithData(data: SessionSaveData) { this.remove(this.loadingLabel, true); - var lbl = `${GameMode.getModeName(data.gameMode) || i18next.t("gameMode:unkown")} - ${i18next.t("saveSlotSelectUiHandler:wave")} ${data.waveIndex}` + this.wv = data.waveIndex; + var lbl = `Slot ${this.slotId+1} (${GameMode.getModeName(data.gameMode) || i18next.t("gameMode:unkown")}) - ${i18next.t("saveSlotSelectUiHandler:wave")} ${data.waveIndex}` if (this.autoSlot != undefined) { - lbl = `Slot ${this.slotId} (Auto) - ${i18next.t("saveSlotSelectUiHandler:wave")} ${data.waveIndex}` + lbl = `Slot ${this.slotId+1} (Auto) - ${i18next.t("saveSlotSelectUiHandler:wave")} ${data.waveIndex}` } console.log(data, this.slotId, this.autoSlot, lbl) const gameModeLabel = addTextObject(this.scene, 8, 5, lbl, TextStyle.WINDOW); diff --git a/src/utils.ts b/src/utils.ts index 5aa558bae3a..69584191d5c 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,5 +1,6 @@ import i18next from "i18next"; import { MoneyFormat } from "#enums/money-format"; +import * as LoggerTools from "./logger" export const MissingTextureKey = "__MISSING"; From 324c92024b0d20d5c22fcc34e8327a629ffa1db8 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Sat, 13 Jul 2024 17:44:51 -0400 Subject: [PATCH 63/94] Extras --- src/battle-scene.ts | 2 +- src/phases.ts | 44 ++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 466fb384206..3058ff044da 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -1374,7 +1374,7 @@ export default class BattleScene extends SceneBase { this.rngSeedOverride = seedOverride || ""; func(); Phaser.Math.RND.state(state); - this.setScoreText("RNG: " + tempRngCounter + " (Last sim: " + this.rngCounter + ")") + //this.setScoreText("RNG: " + tempRngCounter + " (Last sim: " + this.rngCounter + ")") this.rngCounter = tempRngCounter; this.rngOffset = tempRngOffset; this.rngSeedOverride = tempRngSeedOverride; diff --git a/src/phases.ts b/src/phases.ts index 820b56817ba..840ccf9c318 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -74,6 +74,7 @@ import PokemonData from "./system/pokemon-data" import * as LoggerTools from "./logger" import { getNatureName } from "./data/nature"; import { GameDataType } from "./enums/game-data-type"; +import { Session } from "inspector"; const { t } = i18next; @@ -340,6 +341,16 @@ export class TitlePhase extends Phase { this.loaded = false; } + setBiomeByType(biome: Biome): void { + this.scene.arenaBg.setTexture(`${getBiomeKey(biome)}_bg`); + } + setBiomeByName(biome: string): void { + this.scene.arenaBg.setTexture(`${getBiomeKey(Utils.getEnumValues(Biome)[Utils.getEnumKeys(Biome).indexOf(biome)])}_bg`); + } + setBiomeByFile(sessionData: SessionSaveData): void { + this.scene.arenaBg.setTexture(`${getBiomeKey(sessionData.arena.biome)}_bg`); + } + start(): void { super.start(); console.log(LoggerTools.importDocument(JSON.stringify(LoggerTools.newDocument()))) @@ -349,12 +360,13 @@ export class TitlePhase extends Phase { this.scene.playBgm("title", true); + this.scene.biomeChangeMode = false + this.scene.gameData.getSession(loggedInUser.lastSessionSlot).then(sessionData => { if (sessionData) { this.lastSessionData = sessionData; - const biomeKey = getBiomeKey(sessionData.arena.biome); - const bgTexture = `${biomeKey}_bg`; - this.scene.arenaBg.setTexture(bgTexture); + //this.setBiomeByFile(sessionData) + this.setBiomeByType(Biome.END) } this.showOptions(); }).catch(err => { @@ -394,6 +406,20 @@ export class TitlePhase extends Phase { if (saves == undefined) return undefined; return saves.map(f => f[1]); } + getSavesUnsorted(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]); + } + } + } + if (log) console.log(saves) + if (saves == undefined) return undefined; + return saves.map(f => f[1]); + } callEnd(): boolean { this.scene.clearPhaseQueue(); @@ -445,6 +471,7 @@ export class TitlePhase extends Phase { logRenameMenu(): boolean { const options: OptionSelectItem[] = []; LoggerTools.getLogs() + this.setBiomeByType(Biome.FACTORY) for (var i = 0; i < LoggerTools.logs.length; i++) { if (localStorage.getItem(LoggerTools.logs[i][1]) != null) { options.push(LoggerTools.generateEditOption(this.scene, i, this.getSaves(), this) as OptionSelectItem) @@ -479,6 +506,7 @@ export class TitlePhase extends Phase { } showOptions(): void { + this.scene.biomeChangeMode = true const options: OptionSelectItem[] = []; if (false) if (loggedInUser.lastSessionSlot > -1) { @@ -494,6 +522,7 @@ export class TitlePhase extends Phase { // 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 lastsaves = this.getSaves(false, true); + //lastsaves = this.getSavesUnsorted() if (lastsaves != undefined && lastsaves.length > 0) { lastsaves.forEach(lastsave => { options.push({ @@ -531,6 +560,8 @@ export class TitlePhase extends Phase { options.push({ label: i18next.t("menu:newGame"), handler: () => { + this.scene.biomeChangeMode = false + this.setBiomeByType(Biome.TOWN) const setModeAndEnd = (gameMode: GameModes) => { this.gameMode = gameMode; this.scene.ui.setMode(Mode.MESSAGE); @@ -591,12 +622,14 @@ export class TitlePhase extends Phase { }, { label: "Manage Logs", handler: () => { + this.scene.biomeChangeMode = false return this.logRenameMenu() } }) options.push({ label: i18next.t("menu:loadGame"), handler: () => { + this.scene.biomeChangeMode = false this.scene.ui.setOverlayMode(Mode.SAVE_SLOT, SaveSlotUiMode.LOAD, (slotId: integer, autoSlot: integer) => { if (slotId === -1) { @@ -610,6 +643,7 @@ export class TitlePhase extends Phase { options.push({ label: i18next.t("menu:dailyRun"), handler: () => { + this.scene.biomeChangeMode = false this.setupDaily(); return true; }, @@ -618,6 +652,7 @@ export class TitlePhase extends Phase { { label: i18next.t("menu:settings"), handler: () => { + this.scene.biomeChangeMode = false this.scene.ui.setOverlayMode(Mode.SETTINGS); return true; }, @@ -760,6 +795,7 @@ export class TitlePhase extends Phase { confirmSlot("Select a slot to replace.", () => true, slotId => this.scene.gameData.importData(GameDataType.SESSION, slotId)); } end(): void { + this.scene.biomeChangeMode = false if (!this.loaded && !this.scene.gameMode.isDaily) { this.scene.arena.preloadBgm(); this.scene.gameMode = getGameMode(this.gameMode); @@ -2374,7 +2410,7 @@ export class TurnInitPhase extends FieldPhase { this.scene.arenaFlyout.updateFieldText() - this.scene.setScoreText(txt.join("/")) + this.scene.setScoreText(txt.join(" / ")) this.end(); } From 2cf8a75609ed61aaf4f4d79b9677471c26efb813 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Sat, 13 Jul 2024 17:58:21 -0400 Subject: [PATCH 64/94] Push all updates --- src/loading-scene.ts | 89 +++++++++++++++++++++++++++ src/phases.ts | 14 +++-- src/ui/save-slot-select-ui-handler.ts | 10 +++ 3 files changed, 109 insertions(+), 4 deletions(-) diff --git a/src/loading-scene.ts b/src/loading-scene.ts index 15cd295d23c..a684713acfd 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -23,6 +23,60 @@ import { initVouchers } from "./system/voucher"; import { Biome } from "#enums/biome"; import { TrainerType } from "#enums/trainer-type"; +const biomePanelIDs: string[] = [ + "town", + "plains", + "grass", + "tall_grass", + "metropolis", + "forest", + "sea", + "swamp", + "beach", + "lake", + "seabed", + "mountain", + "badlands", + "cave", + "desert", + "ice_cave", + "meadow", + "power_plant", + "volcano", + "graveyard", + "dojo", + "factory", + "ruins", + "wasteland", + "abyss", + "space", + "construction_site", + "jungle", + "fairy_cave", + "temple", + "slum", + "snowy_forest", + "", + "", + "", + "", + "", + "", + "", + "", + "island", + "laboratory", + "", + "", + "", + "", + "", + "", + "", + "", + "end" +] + export class LoadingScene extends SceneBase { readonly LOAD_EVENTS = Phaser.Loader.Events; @@ -53,6 +107,41 @@ export class LoadingScene extends SceneBase { this.loadImage(`window_${w}${getWindowVariantSuffix(wv)}`, "ui/windows"); } } + //this.loadImage(`abyss_panel`, "ui/windows"); + //this.loadImage(`badlands_panel`, "ui/windows"); + //this.loadImage(`beach_panel`, "ui/windows"); + //this.loadImage(`cave_panel`, "ui/windows"); + //this.loadImage(`construction_site_panel`, "ui/windows"); + //this.loadImage(`desert_panel`, "ui/windows"); + //this.loadImage(`dojo_panel`, "ui/windows"); + //this.loadImage(`end_panel`, "ui/windows"); + //this.loadImage(`factory_panel`, "ui/windows"); + //this.loadImage(`fairy_cave_panel`, "ui/windows"); + //this.loadImage(`forest_panel`, "ui/windows"); + //this.loadImage(`grass_panel`, "ui/windows"); + //this.loadImage(`graveyard_panel`, "ui/windows"); + //this.loadImage(`ice_cave_panel`, "ui/windows"); + //this.loadImage(`island_panel`, "ui/windows"); + //this.loadImage(`jungle_panel`, "ui/windows"); + //this.loadImage(`laboratory_panel`, "ui/windows"); + //this.loadImage(`lake_panel`, "ui/windows"); + //this.loadImage(`meadow_panel`, "ui/windows"); + //this.loadImage(`metropolis_panel`, "ui/windows"); + //this.loadImage(`mountain_panel`, "ui/windows"); + //this.loadImage(`plains_panel`, "ui/windows"); + //this.loadImage(`power_plant_panel`, "ui/windows"); + //this.loadImage(`ruins_panel`, "ui/windows"); + //this.loadImage(`sea_panel`, "ui/windows"); + //this.loadImage(`seabed_panel`, "ui/windows"); + //this.loadImage(`slum_panel`, "ui/windows"); + //this.loadImage(`snowy_forest_panel`, "ui/windows"); + //this.loadImage(`space_panel`, "ui/windows"); + //this.loadImage(`swamp_panel`, "ui/windows"); + //this.loadImage(`tall_grass_panel`, "ui/windows"); + //this.loadImage(`temple_panel`, "ui/windows"); + //this.loadImage(`town_panel`, "ui/windows"); + //this.loadImage(`volcano_panel`, "ui/windows"); + //this.loadImage(`wasteland_panel`, "ui/windows"); this.loadAtlas("namebox", "ui"); this.loadImage("pbinfo_player", "ui"); this.loadImage("pbinfo_player_stats", "ui"); diff --git a/src/phases.ts b/src/phases.ts index 840ccf9c318..9c9654206ca 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -341,13 +341,19 @@ export class TitlePhase extends Phase { this.loaded = false; } - setBiomeByType(biome: Biome): void { + setBiomeByType(biome: Biome, override?: boolean): void { + if (!this.scene.menuChangesBiome && !override) + return; this.scene.arenaBg.setTexture(`${getBiomeKey(biome)}_bg`); } - setBiomeByName(biome: string): void { + setBiomeByName(biome: string, override?: boolean): void { + if (!this.scene.menuChangesBiome && !override) + return; this.scene.arenaBg.setTexture(`${getBiomeKey(Utils.getEnumValues(Biome)[Utils.getEnumKeys(Biome).indexOf(biome)])}_bg`); } - setBiomeByFile(sessionData: SessionSaveData): void { + setBiomeByFile(sessionData: SessionSaveData, override?: boolean): void { + if (!this.scene.menuChangesBiome && !override) + return; this.scene.arenaBg.setTexture(`${getBiomeKey(sessionData.arena.biome)}_bg`); } @@ -365,7 +371,7 @@ export class TitlePhase extends Phase { this.scene.gameData.getSession(loggedInUser.lastSessionSlot).then(sessionData => { if (sessionData) { this.lastSessionData = sessionData; - //this.setBiomeByFile(sessionData) + this.setBiomeByFile(sessionData, true) this.setBiomeByType(Biome.END) } this.showOptions(); diff --git a/src/ui/save-slot-select-ui-handler.ts b/src/ui/save-slot-select-ui-handler.ts index 50d7ce00dca..8adfa4a72e7 100644 --- a/src/ui/save-slot-select-ui-handler.ts +++ b/src/ui/save-slot-select-ui-handler.ts @@ -293,6 +293,7 @@ class SessionSlot extends Phaser.GameObjects.Container { public hasData: boolean; public wv: integer; private loadingLabel: Phaser.GameObjects.Text; + public backer: Phaser.GameObjects.Image constructor(scene: BattleScene, slotId: integer, ypos: integer, autoSlot?: integer) { super(scene, 0, ypos * 56); @@ -307,6 +308,15 @@ class SessionSlot extends Phaser.GameObjects.Container { const slotWindow = addWindow(this.scene, 0, 0, 304, 52); this.add(slotWindow); + if (this.slotId == 0) { + //this.backer = this.scene.add.image(0, 0, `sea_panel`) + //this.backer.setOrigin(0.5, 0.5) + //this.backer.setScale(304/909, 52/155) + //this.backer.setPosition(102*1.5 - 1, 26) + //this.backer.setSize(304, 52) + //this.add(this.backer) + } + this.loadingLabel = addTextObject(this.scene, 152, 26, i18next.t("saveSlotSelectUiHandler:loading"), TextStyle.WINDOW); this.loadingLabel.setOrigin(0.5, 0.5); this.add(this.loadingLabel); From 98dd5ea09b93bdcd164f4db9771f7f8c42e862fc Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Sat, 13 Jul 2024 23:40:11 -0400 Subject: [PATCH 65/94] Bug Fixes I heard you liked bug fixes so I gave you bug fixes with your bug fixes --- src/battle-scene.ts | 3 ++- src/battle.ts | 1 + src/field/pokemon.ts | 2 ++ src/logger.ts | 16 +++++++++++++++- src/phases.ts | 1 - src/system/settings/settings.ts | 18 +++++++++++++++++- src/ui/menu-ui-handler.ts | 18 ++++++++++++++++++ src/ui/party-ui-handler.ts | 2 ++ src/ui/save-slot-select-ui-handler.ts | 2 +- 9 files changed, 58 insertions(+), 5 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 3058ff044da..31474292b31 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -122,7 +122,8 @@ export default class BattleScene extends SceneBase { public enableRetries: boolean = false; public damageDisplay: string = "Off"; public lazyReloads: boolean = false; - public menuChangesBiome: boolean; + public menuChangesBiome: boolean = false; + public showAutosaves: boolean = false; /** * Determines the condition for a notification should be shown for Candy Upgrades * - 0 = 'Off' diff --git a/src/battle.ts b/src/battle.ts index 3f2519df3e8..9f9f431b8ec 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -363,6 +363,7 @@ export default class Battle { const ret = Utils.randSeedInt(range, min); this.battleSeedState = Phaser.Math.RND.state(); Phaser.Math.RND.state(state); + //scene.setScoreText("RNG: " + tempRngCounter + " (Last sim: " + this.rngCounter + ")") scene.rngCounter = tempRngCounter; scene.rngSeedOverride = tempSeedOverride; return ret; diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 0fb74d51304..68e16b919ab 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -108,6 +108,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { private shinySparkle: Phaser.GameObjects.Sprite; + public partyslot: integer; + constructor(scene: BattleScene, x: number, y: number, species: PokemonSpecies, level: integer, abilityIndex?: integer, formIndex?: integer, gender?: Gender, shiny?: boolean, variant?: Variant, ivs?: integer[], nature?: Nature, dataSource?: Pokemon | PokemonData) { super(scene, x, y); diff --git a/src/logger.ts b/src/logger.ts index a09e2b667c8..097ca536518 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -87,6 +87,8 @@ export function getLogs() { * @returns The name of the game mode, for use in naming a game log. */ export function getMode(scene: BattleScene) { + if (scene.gameMode == undefined) + return "???" switch (scene.gameMode.modeId) { case GameModes.CLASSIC: return "Classic" @@ -625,6 +627,16 @@ export function logShop(scene: BattleScene, floor: integer, action: string) { console.log(drpd) localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) } +export function logCapture(scene: BattleScene, floor: integer, target: EnemyPokemon) { + //if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) + var drpd = getDRPD(scene) + console.log("Log Capture", drpd) + var wv: Wave = getWave(drpd, floor, scene) + var pkslot = target.partyslot + wv.pokemon[pkslot].captured = true; + console.log(drpd) + localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) +} /** * Retrieves a wave from the DRPD. If the wave doesn't exist, it creates a new one. * @param drpd The document to read from. @@ -804,6 +816,8 @@ export function logPokemon(scene: BattleScene, floor: integer = undefined, slot: console.log("Log Enemy Pokemon", drpd) var wv: Wave = getWave(drpd, floor, scene) var pk: PokeData = exportPokemon(pokemon, encounterRarity) + pk.source = pokemon + pokemon.partyslot = slot; if (wv.pokemon[slot] != undefined) { if (encounterRarity == "" || encounterRarity == undefined) { if (wv.pokemon[slot].rarity != undefined && wv.pokemon[slot].rarity != "???") pk.rarity = wv.pokemon[slot].rarity @@ -1124,7 +1138,7 @@ function printWave(inData: string, indent: string, wave: Wave): string { inData += indent + " ]" } } else { - inData += ",\n" + indent + " \"actions\": []" + inData += ",\n" + indent + " \"actions\": [\"[No actions?]\"]" } inData += ",\n " + indent + "\"shop\": \"" + wave.shop + "\"" inData += ",\n " + indent + "\"biome\": \"" + wave.biome + "\"" diff --git a/src/phases.ts b/src/phases.ts index 9c9654206ca..cf2cd208a13 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -5717,7 +5717,6 @@ export class AttemptCapturePhase extends PokemonPhase { this.scene.ui.setMode(Mode.PARTY, PartyUiMode.RELEASE, this.fieldIndex, (slotIndex: integer, _option: PartyOption) => { this.scene.ui.setMode(Mode.MESSAGE).then(() => { if (slotIndex < 6) { - LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, "Release " + LoggerTools.playerPokeName(this.scene, slotIndex)) addToParty(); } else { promptRelease(); diff --git a/src/system/settings/settings.ts b/src/system/settings/settings.ts index e1ef70898be..24de349327f 100644 --- a/src/system/settings/settings.ts +++ b/src/system/settings/settings.ts @@ -101,7 +101,8 @@ export const SettingKeys = { Show_BGM_Bar: "SHOW_BGM_BAR", Damage_Display: "DAMAGE_DISPLAY", LazyReloads: "FLAG_EVERY_RESET_AS_RELOAD", - FancyBiome: "FANCY_BIOMES" + FancyBiome: "FANCY_BIOMES", + ShowAutosaves: "SHOW_AUTOSAVES" }; /** @@ -543,6 +544,19 @@ export const Setting: Array = [ type: SettingType.DISPLAY, requireReload: true }, + { + key: SettingKeys.ShowAutosaves, + label: "Show Autosaves", + options: [{ + label: "Off", + value: "Off" + }, { + label: "On", + value: "On" + }], + default: 0, + type: SettingType.DISPLAY, + }, { key: SettingKeys.Master_Volume, label: i18next.t("settings:masterVolume"), @@ -656,6 +670,8 @@ export function setSetting(scene: BattleScene, setting: string, value: integer): scene.lazyReloads = Setting[index].options[value].value == "On" case SettingKeys.FancyBiome: scene.menuChangesBiome = Setting[index].options[value].value == "On" + case SettingKeys.ShowAutosaves: + scene.showAutosaves = Setting[index].options[value].value == "On" case SettingKeys.Skip_Seen_Dialogues: scene.skipSeenDialogues = Setting[index].options[value].value === "On"; break; diff --git a/src/ui/menu-ui-handler.ts b/src/ui/menu-ui-handler.ts index 571a09f3b37..4a4d074aec7 100644 --- a/src/ui/menu-ui-handler.ts +++ b/src/ui/menu-ui-handler.ts @@ -22,6 +22,7 @@ enum MenuOptions { MANAGE_DATA, COMMUNITY, SAVE_AND_QUIT, + SAVE_AND_REFRESH, LOG_OUT } @@ -356,6 +357,23 @@ export default class MenuUiHandler extends MessageUiHandler { error = true; } break; + case MenuOptions.SAVE_AND_REFRESH: + if (this.scene.currentBattle) { + success = true; + if (this.scene.currentBattle.turn > 1) { + ui.showText(i18next.t("menuUiHandler:losingProgressionWarning"), null, () => { + ui.setOverlayMode(Mode.CONFIRM, () => this.scene.gameData.saveAll(this.scene, true, true, true, true).then(() => this.scene.reset(true)), () => { + ui.revertMode(); + ui.showText(null, 0); + }, false, -98); + }); + } else { + this.scene.gameData.saveAll(this.scene, true, true, true, true).then(() => this.scene.reset(true)); + } + } else { + error = true; + } + break; case MenuOptions.LOG_OUT: success = true; const doLogout = () => { diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index 8a89722dca3..528730ad770 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -21,6 +21,7 @@ import MoveInfoOverlay from "./move-info-overlay"; import i18next from "i18next"; import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; import { Moves } from "#enums/moves"; +import * as LoggerTools from "../logger"; const defaultMessage = i18next.t("partyUiHandler:choosePokemon"); @@ -932,6 +933,7 @@ export default class PartyUiHandler extends MessageUiHandler { this.clearPartySlots(); this.scene.removePartyMemberModifiers(slotIndex); const releasedPokemon = this.scene.getParty().splice(slotIndex, 1)[0]; + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, `Release ${releasedPokemon.name} (Slot ${slotIndex + 1})`) releasedPokemon.destroy(); this.populatePartySlots(); if (this.cursor >= this.scene.getParty().length) { diff --git a/src/ui/save-slot-select-ui-handler.ts b/src/ui/save-slot-select-ui-handler.ts index 8adfa4a72e7..fa1dced0035 100644 --- a/src/ui/save-slot-select-ui-handler.ts +++ b/src/ui/save-slot-select-ui-handler.ts @@ -206,7 +206,7 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { this.scene.add.existing(sessionSlot); this.sessionSlotsContainer.add(sessionSlot); this.sessionSlots.push(sessionSlot); - if (this.uiMode != SaveSlotUiMode.SAVE) { + if (this.uiMode != SaveSlotUiMode.SAVE && this.scene.showAutosaves) { for (var j = 0; j < LoggerTools.autoCheckpoints.length; j++) { var k = "sessionData" + (s ? s : "") + "_Guest_auto" + j if (localStorage.getItem(k) != null) { From 84fc8c9443d67b0c6077c18924e1c9ff7798a186 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Sat, 13 Jul 2024 23:46:57 -0400 Subject: [PATCH 66/94] Biome Panels shh dont tell scarlet --- public/images/ui/windows/abyss_panel.png | Bin 0 -> 4435 bytes public/images/ui/windows/beach_panel.png | Bin 0 -> 4802 bytes public/images/ui/windows/end_panel.png | Bin 0 -> 34393 bytes public/images/ui/windows/sea_panel.png | Bin 0 -> 4358 bytes public/images/ui/windows/slum_panel.png | Bin 0 -> 4553 bytes public/images/ui/windows/space_panel.png | Bin 0 -> 56632 bytes public/images/ui/windows/town_panel.png | Bin 0 -> 4564 bytes src/loading-scene.ts | 52 ++++++++++++++++++++--- src/ui/save-slot-select-ui-handler.ts | 23 ++++++---- 9 files changed, 61 insertions(+), 14 deletions(-) create mode 100644 public/images/ui/windows/abyss_panel.png create mode 100644 public/images/ui/windows/beach_panel.png create mode 100644 public/images/ui/windows/end_panel.png create mode 100644 public/images/ui/windows/sea_panel.png create mode 100644 public/images/ui/windows/slum_panel.png create mode 100644 public/images/ui/windows/space_panel.png create mode 100644 public/images/ui/windows/town_panel.png diff --git a/public/images/ui/windows/abyss_panel.png b/public/images/ui/windows/abyss_panel.png new file mode 100644 index 0000000000000000000000000000000000000000..30c2918e5f6722c4f3ee554264128e6bd8fe1341 GIT binary patch literal 4435 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 WpFCl52>z7>WovD3b&cr#(|-VBAfqV& literal 0 HcmV?d00001 diff --git a/public/images/ui/windows/beach_panel.png b/public/images/ui/windows/beach_panel.png new file mode 100644 index 0000000000000000000000000000000000000000..eccdac1779f9523f4559c673ecd8eabc125bd80b GIT binary patch literal 4802 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 Gx&HzaK#<@7 literal 0 HcmV?d00001 diff --git a/public/images/ui/windows/end_panel.png b/public/images/ui/windows/end_panel.png new file mode 100644 index 0000000000000000000000000000000000000000..96269ae6cf6626c731496d157d066b12551dec6b GIT binary patch literal 34393 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%0DRr1 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 literal 0 HcmV?d00001 diff --git a/public/images/ui/windows/slum_panel.png b/public/images/ui/windows/slum_panel.png new file mode 100644 index 0000000000000000000000000000000000000000..14e1e099b50c5ffd078628b02785ec6a2de02e95 GIT binary patch literal 4553 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 literal 0 HcmV?d00001 diff --git a/public/images/ui/windows/space_panel.png b/public/images/ui/windows/space_panel.png new file mode 100644 index 0000000000000000000000000000000000000000..65a0c6bc238ac58f99d54bfb9350022a0318e249 GIT binary patch literal 56632 zcmZ^Kbx>PT_ib>8Vg-Vh;Kd2<1%el+xCIIB?$AP82=3mdxI>{x@isUFhoZ%`6e|vo z@5x{9`^}z--I?4o=WaV|t^Gm!wK5St5FY>l5UHvt>H+{5@TbqYxY$pxNTD$1r#Ccj zUFBDRnlZXP0DuXgswl4?U~$-C>}6}}#j`!}O>Wz@;*T(bMiwSWb96r;7MT+vbJc?f zL(h4oDNl~VT)~kAqY=rGU7Qy%v^yCoJwKRr6(pBCuXf?_UtJvkfXfu2m^wOUsw2mY zLCSu2*qF#?Ih-lV^62i@FP`$+cuu%T+Z7|9 zVb_&xSp9ABofjW-0B*$RN5{MBJJ&PO@cZ2DyUVkc+}Ye*Eir~IL^^KTf3>%`cUzg^ zfu2Gw#t%OqFR0odxgP`l$-ocykAE*+13?|C{$2*mM*npo+RT3g71wH!;TLL(85@>x zQD4-q3v^@Ay=ys0>|w|Kky~jyeBUCRL5HibL9yt+*qYGC?#IqHjlzH7w|_|XZjU=} zYbdjkCHLxQPyb?0g}S1tPCC_5$5i|OOy&SL4;>zUof*Dx{gW>Y?L!6l!Q`P$=uzto z>D1(;!Ir9o1~0tG;biRmA8yQvy80G=@$LQ?xq8%fHA)q%qPzD#T2?9LhNe2Y7u}|GND*xD=?XiNfAfwEtmAyy)Zjz{k0t zb9O=4T&R>!U^B;zLughlK5I0CnGb5VR;{z@Fx=Ia^l`3p`_+p%kx}EqHSO5xx$*^ zyPZ!C=LiB5@ii?8^vhN2fFmo$%o4CEkK?!Y$-VP0bF`{mvKc*VuWq6V_AX-X0ud7_ z_hBy&E_5@7v$?lL1#?#iY&=1gKQ(3#-nle8FSv>}tR7E>u02@u&28R8|9ReF&jQ_M zzQ>B@PC7LeFS@CJn@VS3ST69XCth@YFs+=a*rfRus^)Ug_6?f2I_UO|YLlUT*|2#r z-{mBoI6a&bPeyG?(^3=!~}7FZ^ls+qrmtf=`?I-UasLaiT3hDj2=<*iU)V?Xq~`P_=PF z-jBC8cH1V{^aqjI{!t8Af_cDas&amnTY0ZLL z{5+1Jp*(gS#sgl`>DQ+a`$p$%joM9nFIIA?5>dfIO}qNMVW*~)QG1@nquGBH!<sjN`1(DNc8Xg8$757g57mJZj`3E+(LTbrRB8R zA&T7tqeMld?7S|c5AD7vqd=Z_Zz+>hP#l0n%@?-dq?@~I|mP(%lSM1guA8TDZLREfW1=^$6EP#-5rpryPTelF?wkk_`(N)CT4CM+8#;0aUzz?o zD)4E?*n6R4mBP;kbMa4N`fBS?|09=LPI*5L+h6?66;K zzl8`O5fdJ4Tq_7O!vK%@H}uS50xugZ7cU~5qvnWff+<)5=sOK&qg)ZBMV^AK8GDD2 z5zlo-J8@U6>x+>BE~n7v)Af5LwOPaPVpFN`|M%0Sg}f*e{v^J0_9) zj3KABe!vaQchsQ$A^<0qvuCn$K}1626z!j# zO3&?$KJF;I=DT@k6Cq$Qpe?U(p;0+yQrE4NpeDqnKF?p#T_1rQ58(`*Wt6p0=4pUe z8!ETu7x)A#>_xhM9r7wAK+W4We6!%OYswi45lG*%VFn~sUg$PQ+Ksq`{ileLRfb+k z!1PP%v4N72j$b>Z(9%t#%9UQJuMg-dm5^RWaKUU(cjD&}6eN{+c{_r{^yD74uTza> z!V-hC&>Eqw(QKB;HSvR8?k_?KNSUA>rqab#actF$q zAHOg()>m_R^!8|eW3c^-JAS?X6#!fu67dTT-MdKlJR|`CW6{L^1&+hOa8DO-x+5mc zML2@Bc)^uxc-~zl7?noMFeZo&_5+4|H6?OvO0)aXR21%GaOB4U27tK^RGOcjY-{F4 zNIwp>azla@&56;GZ5QbRxhEG2)YoF3YHfzb&&wv58*T7CV{ntc>va$RSgBxPw6)m) z@bJ=PZqtp{0Dpk>tl&)S_Ac}|Gdpbv6Mx=>L;O6%f3tlD1KyPvp1N}AA3pT8#9c&H z&572EFb6{^J(h#(QnpkMT7@iz&lY_WjFZo)kV4FzFz6JRNIUuwsL}de9G7OQYFBSM zA>QmKoF=@Ble+vo%nhxH7uL~q_HT)NLF$Pb$Q}37bVL&&U?Cn8^HOg+77qF&pRtpU z8?r{Ka%ty|W%VlZZQlY!KTCPi&aSoJh7g}GskA1etQQ^pnM^YC!d}&wi$I8tlzVOt zrxReY_V=|AoPJLp2xmz@)j!-A`Z#?SwMZ;E2uh5AU~#hXMJpan$ZhNee`|z`**CnO z4ML0*K9@EI_~VtU-f_I#2aKhk5!TckW}|FFzvkbdnD@j?Nsuf?L)t_f?g>{S1G(96?lDx)gKUDF^P zd4m8G4<;jx2vWIv1=RamK=SttI)1h~qc?a_`05}ITx)^b>sP-(WTACY` zD?w<5UQ)R5fLPSF6B$1j%tfWLQUgj0ASH=FhV>aiu;6LM=-?FAVKqU^L9Twozq>`A ztAhbv=4_ZTf&|~Z<6*&Gz zOj7M-PupNP$$Sui@B5akvky|sQUVuK)A=KnxrG;qRdL>bW0qU>(NXq!XY;T5?i|e6 zMIzn+{f`-UZGG>$RzKE#q;aod{mzPB=#2TQcp-?kJs)iWQq7(CW$Fm0#8X~%-$q6_ zL#MdBT|eo=Y0J29$(A=`T(_|Xw7v4QMJO2fl58wh10iIpg$6v9m_sY0v5NE3VGCM` zrm!vgCfcNEY__|l@eQ2#QB{pRg78RiX6WA_$)^vFV-A7=BQC`c4cAy=u+2ol+bplr z`vPue{1$wmsI4~zxW+EkHe%@ct@A47*7(lqh1n0`w^BeseZWuN8`5MF8rbryNX!M}1Ps8@`=0%;lU8#(2Zw;gh{@Pqak<*sz#YKk{DUzHA^W z^29;4EDX9~H1(K>a0f-%<(V*of@t8@d78HMS;J0vs{3jpv%@XM&eqYERg-vkwXZ^0{}>8=O4i-fCWA{(QS;kWWz=h1$uu z0E&8x-52#9pWga&_S&>OqNcXrX8#mVBfVX}XYmuf*?!-9Ua^qLJt?w``|U?4ywI`h zV0~uXVI%m8U9*F^s_ZKs85(u_a_3i_>wP(gTHUtMAl6*rFStoG+<43{KIovbG_e`N zz+0l~m@*Hz9$JFgqO6+BGO6f8dckE}Bu0mQ771KDS&$_k&olGd5dgcsrR>A|G_AiB zq+&zh*{$+HT;*GrV|$?YZ;>s=Po)#19$!l*G@%Tsj#BjH+`>(CTewd(L;yp1+)mfR z_bYEcSnGP!UEusf_xRb#U7WI4>90htuEMM$AgqcXj?bJrsGU@b3`70^n`nW)lmpZPlh7JSjlqpgJCbrWCIyqg+^t{w*bs3r?@NF*RUem;qE5z>?y5hmPo*S?!lF8+nxJG(~(6&AFFeUpHBCq!&iP( ztL^U^Vz=y$ZlA24yI5h6v{ywdp}Y8Vo#npr;H?Do_oH|0a4#g@imTf65hxlX{it#m zm<&4g)o@VnA1Y}?KMka_ zf{1p1xV_(@oj4s#R9bUG?Ve8S{VDJNlllpDFk+}wP*G**_ZllYeOYhDWyA57A`Rs^ z%criFecs;4d6wH}(MQp5+Kq5{@vXYeY4i0a?YEK_1{e~ZZ)RH@rf3=B3scuh)Ql^D z6u->?KWgFTW(uG2*$tdud}z&R%FiGeVwM}~8GSv{O(-2N9RbM5 zF6&^eh8FkSEdrxk#jl<{zf~g*3^%V_O&R0e zqpvh3xe;2a=_yT|Z+-y51ydSiR~RA>uT&)WUMG&IFvjB|F?{+00EV&l4NM?G_Vk?nZfaKL^bl>W{a%M+h+^C!9Bq4RGwRgf9g#UDDjNBf5@qx!y zKt`EqW);(3O&q~#+~=Wg&w8LaskV&+Ffg5{estqM8ehKz5oT}O_XbqH;2+k|n?Bj$ z*yaxDhZ7b@F6aRsuN(wo3Z9FZ3LU>BvC!6Z9n`+aGard=>vwmO*!11zLw7$} zn$?#nR0QS`ESz*;H%=v>(^;{9QUb`sW`egl9%RPG9{9LY%30y5L&;U4AL( zkjWfnupU1}yp#LSSd3AntF)UG;WAB!ipXov@ycM%W!960g6iUz!?bRxVlnoJa4_N3 zosmd4WfODy{@*0$vSeU3ktw9i*KQUOp9dS|FN=`J&nmgHB!q|yfff6Q%}-kKh^FVP z-gINO!rP<(vhE2+=KxdNHC#Q~f2eiq2EF)cfYnmXT!W=Mr`_`J<6+9ktq=*sTVILn z0iTKymU+m)n~9gID40?Tl4mhiCK)q?py6{x*sv5|_4x3g3a#;Z4 z_Rn!kNTVH0F=J%j(|NTyMa6aEX#y+43)rl$D8}Tj2@BmYeIDN(4r0IZPjmFS)Tgj9 zEBN?e<*$V`^}fK`;W(8BSxQ@RI=BKt_ZNhx&6JZ82iK2C3lEAU3Yf{`X9ok*9raN# zglM-1O~fW)zZL4T1tFS%v&%9CL-g-L7Qi zM=3@@gt``Bd?0V`rBu(c0<-~H3)_O5blpu?HerbVZoFO#F$M?mjdn~CiFxD(8q5Wq zn@bxLwj>Gj>DQ;;jc`<4T`%J6n2>A3h(LCJz1ej?R3R^_pGxs7WhH)YI!lmU!s#Jh zxde_xW<>TsmxJ03o~z;1Xz(YRvn9q%XzIudWmu+)~mM{Zr14lLqQ#v->7Db>`hT>lMt-;PJ1W`j0=1H4GRdRC&$l2nC;&bio^B>ZL%2kqaP{o%EOln^mW4$ARAs1d zvZK^`$^8IfX6_)zF!kZdfd;An~8{6fAYk`2MA8QmuO)r@{HkqS!{OeMpFJ4)O zrnhZN9N|ca#*px?(RqYr1#3D#o?v#ljgdVeJkx!UKC

d`xrtnL8Md%%ALGCi9zF6fFpGvq)E5J9-NG2$^< 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 Kx0jOk#Qh7(`SU>l literal 0 HcmV?d00001 diff --git a/src/loading-scene.ts b/src/loading-scene.ts index a684713acfd..076b5c3a3b6 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -23,7 +23,7 @@ import { initVouchers } from "./system/voucher"; import { Biome } from "#enums/biome"; import { TrainerType } from "#enums/trainer-type"; -const biomePanelIDs: string[] = [ +export const biomePanelIDs: string[] = [ "town", "plains", "grass", @@ -76,6 +76,43 @@ const biomePanelIDs: string[] = [ "", "end" ] +export const allpanels: string[] = [ + "abyss", + //"badlands", + "beach", + //"cave", + //"construction_site", + //"desert", + //"dojo", + "end", + //"factory", + //"fairy_cave", + //"forest", + //"grass", + //"graveyard", + //"ice_cave", + //"island", + //"jungle", + //"laboratory", + //"lake", + //"meadow", + //"metropolis", + //"mountain", + //"plains", + //"power_plant", + //"ruins", + "sea", + //"seabed", + "slum", + //"snowy_forest", + "space", + //"swamp", + //"tall_grass", + //"temple", + "town", + "volcano", + "wasteland" +] export class LoadingScene extends SceneBase { readonly LOAD_EVENTS = Phaser.Loader.Events; @@ -114,7 +151,7 @@ export class LoadingScene extends SceneBase { //this.loadImage(`construction_site_panel`, "ui/windows"); //this.loadImage(`desert_panel`, "ui/windows"); //this.loadImage(`dojo_panel`, "ui/windows"); - //this.loadImage(`end_panel`, "ui/windows"); +// this.loadImage(`end_panel`, "ui/windows"); //this.loadImage(`factory_panel`, "ui/windows"); //this.loadImage(`fairy_cave_panel`, "ui/windows"); //this.loadImage(`forest_panel`, "ui/windows"); @@ -131,17 +168,20 @@ export class LoadingScene extends SceneBase { //this.loadImage(`plains_panel`, "ui/windows"); //this.loadImage(`power_plant_panel`, "ui/windows"); //this.loadImage(`ruins_panel`, "ui/windows"); - //this.loadImage(`sea_panel`, "ui/windows"); +// this.loadImage(`sea_panel`, "ui/windows"); //this.loadImage(`seabed_panel`, "ui/windows"); //this.loadImage(`slum_panel`, "ui/windows"); //this.loadImage(`snowy_forest_panel`, "ui/windows"); - //this.loadImage(`space_panel`, "ui/windows"); +// this.loadImage(`space_panel`, "ui/windows"); //this.loadImage(`swamp_panel`, "ui/windows"); //this.loadImage(`tall_grass_panel`, "ui/windows"); //this.loadImage(`temple_panel`, "ui/windows"); //this.loadImage(`town_panel`, "ui/windows"); - //this.loadImage(`volcano_panel`, "ui/windows"); - //this.loadImage(`wasteland_panel`, "ui/windows"); +// this.loadImage(`volcano_panel`, "ui/windows"); +// this.loadImage(`wasteland_panel`, "ui/windows"); + for (var i = 0; i < allpanels.length; i++) { + this.loadImage(`${allpanels[i]}_panel`, "ui/windows"); + } this.loadAtlas("namebox", "ui"); this.loadImage("pbinfo_player", "ui"); this.loadImage("pbinfo_player_stats", "ui"); diff --git a/src/ui/save-slot-select-ui-handler.ts b/src/ui/save-slot-select-ui-handler.ts index fa1dced0035..15d49126741 100644 --- a/src/ui/save-slot-select-ui-handler.ts +++ b/src/ui/save-slot-select-ui-handler.ts @@ -12,6 +12,7 @@ import { Mode } from "./ui"; import { addWindow } from "./ui-theme"; import * as LoggerTools from "../logger" import { loggedInUser } from "#app/account.js"; +import { allpanels, biomePanelIDs } from "../loading-scene" const sessionSlotCount = 5; @@ -308,14 +309,13 @@ class SessionSlot extends Phaser.GameObjects.Container { const slotWindow = addWindow(this.scene, 0, 0, 304, 52); this.add(slotWindow); - if (this.slotId == 0) { - //this.backer = this.scene.add.image(0, 0, `sea_panel`) - //this.backer.setOrigin(0.5, 0.5) - //this.backer.setScale(304/909, 52/155) - //this.backer.setPosition(102*1.5 - 1, 26) - //this.backer.setSize(304, 52) - //this.add(this.backer) - } + this.backer = this.scene.add.image(0, 0, `end_panel`) + this.backer.setOrigin(0.5, 0.5) + this.backer.setScale(304/909, 52/155) + this.backer.setPosition(102*1.5 - 1, 26) + this.backer.setSize(304, 52) + this.backer.setVisible(false) + this.add(this.backer) this.loadingLabel = addTextObject(this.scene, 152, 26, i18next.t("saveSlotSelectUiHandler:loading"), TextStyle.WINDOW); this.loadingLabel.setOrigin(0.5, 0.5); @@ -339,6 +339,13 @@ class SessionSlot extends Phaser.GameObjects.Container { const playTimeLabel = addTextObject(this.scene, 8, 33, Utils.getPlayTimeString(data.playTime), TextStyle.WINDOW); this.add(playTimeLabel); + console.log(biomePanelIDs[data.arena.biome]) + + if (allpanels.includes(biomePanelIDs[data.arena.biome])) { + this.backer.setTexture(`${biomePanelIDs[data.arena.biome]}_panel`) + this.backer.setVisible(true) + } + const pokemonIconsContainer = this.scene.add.container(144, 4); data.party.forEach((p: PokemonData, i: integer) => { const iconContainer = this.scene.add.container(26 * i, 0); From a140719cd2926c8fc383db23ebccc66482f8d56b Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Sun, 14 Jul 2024 00:04:09 -0400 Subject: [PATCH 67/94] Fix action logging --- src/logger.ts | 31 +++++++++++++++++++++++-------- src/phases.ts | 3 ++- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/logger.ts b/src/logger.ts index 097ca536518..de609da2891 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -158,6 +158,7 @@ export interface Wave { actions: string[], shop: string, biome: string, + clearActionsFlag: boolean, trainer?: TrainerData, pokemon?: PokeData[] } @@ -300,6 +301,7 @@ export function exportWave(scene: BattleScene): Wave { double: scene.currentBattle.double, actions: [], shop: "", + clearActionsFlag: false, biome: getBiomeName(scene.arena.biomeType) } if (ret.double == undefined) ret.double = false; @@ -608,6 +610,11 @@ export function logActions(scene: BattleScene, floor: integer, action: string) { var drpd = getDRPD(scene) console.log("Log Action", drpd) var wv: Wave = getWave(drpd, floor, scene) + if (wv.clearActionsFlag) { + console.log("Triggered clearActionsFlag") + wv.clearActionsFlag = false + wv.actions = [] + } wv.actions.push(action) console.log(drpd) localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) @@ -670,8 +677,9 @@ export function getWave(drpd: DRPD, floor: integer, scene: BattleScene): Wave { double: scene.currentBattle.double, actions: [], shop: "", + clearActionsFlag: false, biome: getBiomeName(scene.arena.biomeType), - pokemon: [] + //pokemon: [] } wv = drpd.waves[insertPos] } @@ -724,7 +732,8 @@ export function getWave(drpd: DRPD, floor: integer, scene: BattleScene): Wave { actions: [], shop: "", biome: getBiomeName(scene.arena.biomeType), - pokemon: [] + clearActionsFlag: false, + //pokemon: [] } wv = drpd.waves[drpd.waves.length - 1] } @@ -750,8 +759,9 @@ export function getWave(drpd: DRPD, floor: integer, scene: BattleScene): Wave { double: scene.currentBattle.double, actions: [], shop: "", + clearActionsFlag: false, biome: getBiomeName(scene.arena.biomeType), - pokemon: [] + //pokemon: [] } wv = drpd.waves[insertPos] } @@ -873,9 +883,10 @@ export function logPokemon(scene: BattleScene, floor: integer = undefined, slot: if (pk.rarity == undefined) pk.rarity = "[Unknown]" wv.pokemon[slot] = pk; - while (wv.actions.length > 0) - wv.actions.pop() - wv.actions = [] + //while (wv.actions.length > 0) + //wv.actions.pop() + //wv.actions = [] + wv.clearActionsFlag = false; wv.shop = "" console.log(drpd) localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) @@ -885,13 +896,17 @@ export function logPokemon(scene: BattleScene, floor: integer = undefined, slot: * @param scene The BattleScene. Used to get the log ID and trainer data. * @param floor The wave index to write to. Defaults to the current floor. */ -export function resetWaveActions(scene: BattleScene, floor: integer = undefined) { +export function resetWaveActions(scene: BattleScene, floor: integer = undefined, softflag: boolean) { if (floor == undefined) floor = scene.currentBattle.waveIndex if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) var drpd = getDRPD(scene) console.log("Clear Actions", drpd) var wv: Wave = getWave(drpd, floor, scene) - wv.actions = [] + if (softflag) { + wv.clearActionsFlag = true; + } else { + wv.actions = [] + } console.log(drpd, wv) localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) } diff --git a/src/phases.ts b/src/phases.ts index cf2cd208a13..5e8954f33ab 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -1406,7 +1406,7 @@ export class EncounterPhase extends BattlePhase { if (this.scene.currentBattle.waveIndex == 1) { LoggerTools.logPlayerTeam(this.scene) } - LoggerTools.resetWaveActions(this.scene) + LoggerTools.resetWaveActions(this.scene, undefined, true) if (LoggerTools.autoCheckpoints.includes(this.scene.currentBattle.waveIndex)) { this.scene.gameData.saveGameToAuto(this.scene) @@ -5684,6 +5684,7 @@ export class AttemptCapturePhase extends PokemonPhase { this.removePb(); this.end(); }; + LoggerTools.logCapture(this.scene, this.scene.currentBattle.waveIndex, pokemon) const removePokemon = () => { this.scene.addFaintedEnemyScore(pokemon); this.scene.getPlayerField().filter(p => p.isActive(true)).forEach(playerPokemon => playerPokemon.removeTagsBySourceId(pokemon.id)); From 6947d137fca8011923f39072adf5c85cdb8d63d6 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Sun, 14 Jul 2024 00:13:04 -0400 Subject: [PATCH 68/94] Update save-slot-select-ui-handler.ts --- src/ui/save-slot-select-ui-handler.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/ui/save-slot-select-ui-handler.ts b/src/ui/save-slot-select-ui-handler.ts index 15d49126741..10eb3ead3e9 100644 --- a/src/ui/save-slot-select-ui-handler.ts +++ b/src/ui/save-slot-select-ui-handler.ts @@ -309,13 +309,13 @@ class SessionSlot extends Phaser.GameObjects.Container { const slotWindow = addWindow(this.scene, 0, 0, 304, 52); this.add(slotWindow); - this.backer = this.scene.add.image(0, 0, `end_panel`) - this.backer.setOrigin(0.5, 0.5) - this.backer.setScale(304/909, 52/155) - this.backer.setPosition(102*1.5 - 1, 26) - this.backer.setSize(304, 52) - this.backer.setVisible(false) - this.add(this.backer) + //this.backer = this.scene.add.image(0, 0, `end_panel`) + //this.backer.setOrigin(0.5, 0.5) + //this.backer.setScale(304/909, 52/155) + //this.backer.setPosition(102*1.5 - 1, 26) + //this.backer.setSize(304, 52) + //this.backer.setVisible(false) + //this.add(this.backer) this.loadingLabel = addTextObject(this.scene, 152, 26, i18next.t("saveSlotSelectUiHandler:loading"), TextStyle.WINDOW); this.loadingLabel.setOrigin(0.5, 0.5); @@ -342,8 +342,8 @@ class SessionSlot extends Phaser.GameObjects.Container { console.log(biomePanelIDs[data.arena.biome]) if (allpanels.includes(biomePanelIDs[data.arena.biome])) { - this.backer.setTexture(`${biomePanelIDs[data.arena.biome]}_panel`) - this.backer.setVisible(true) + //this.backer.setTexture(`${biomePanelIDs[data.arena.biome]}_panel`) + //this.backer.setVisible(true) } const pokemonIconsContainer = this.scene.add.container(144, 4); From e0d20f309c81202ec6ceec25fdba2847258e4fdc Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Sun, 14 Jul 2024 12:24:47 -0400 Subject: [PATCH 69/94] Push fixes --- src/data/nature.ts | 8 ++++---- src/logger.ts | 4 +++- src/phases.ts | 4 ++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/data/nature.ts b/src/data/nature.ts index 90924de99ed..86da8bc66a4 100644 --- a/src/data/nature.ts +++ b/src/data/nature.ts @@ -87,7 +87,7 @@ export function getNatureIncrease(nature: Nature): string { case Nature.HASTY: case Nature.JOLLY: case Nature.NAIVE: - return "speed" + return "spe" case Nature.HARDY: //return "atk" case Nature.DOCILE: @@ -97,7 +97,7 @@ export function getNatureIncrease(nature: Nature): string { case Nature.BASHFUL: //return "spdef" case Nature.QUIRKY: - //return "speed" + //return "spe" default: return "" } @@ -128,7 +128,7 @@ export function getNatureDecrease(nature: Nature): string { case Nature.RELAXED: case Nature.QUIET: case Nature.SASSY: - return "speed" + return "spe" case Nature.HARDY: //return "atk" case Nature.DOCILE: @@ -138,7 +138,7 @@ export function getNatureDecrease(nature: Nature): string { case Nature.BASHFUL: //return "spdef" case Nature.QUIRKY: - //return "speed" + //return "spe" default: return "" } diff --git a/src/logger.ts b/src/logger.ts index de609da2891..5546970f08b 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -828,6 +828,8 @@ export function logPokemon(scene: BattleScene, floor: integer = undefined, slot: var pk: PokeData = exportPokemon(pokemon, encounterRarity) pk.source = pokemon pokemon.partyslot = slot; + if (wv.pokemon == undefined) + wv.pokemon = [] if (wv.pokemon[slot] != undefined) { if (encounterRarity == "" || encounterRarity == undefined) { if (wv.pokemon[slot].rarity != undefined && wv.pokemon[slot].rarity != "???") pk.rarity = wv.pokemon[slot].rarity @@ -1278,7 +1280,7 @@ function printIV(inData: string, indent: string, iv: IVData) { inData += ",\n" + indent + " \"def\": " + iv.def inData += ",\n" + indent + " \"spatk\": " + iv.spatk inData += ",\n" + indent + " \"spdef\": " + iv.spdef - inData += ",\n" + indent + " \"speed\": " + iv.speed + inData += ",\n" + indent + " \"spe\": " + iv.speed inData += "\n" + indent + "}" return inData; } diff --git a/src/phases.ts b/src/phases.ts index 5e8954f33ab..0e2b774c406 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -5140,10 +5140,10 @@ export class SwitchPhase extends BattlePhase { this.scene.ui.setMode(Mode.PARTY, this.isModal ? PartyUiMode.FAINT_SWITCH : PartyUiMode.POST_BATTLE_SWITCH, fieldIndex, (slotIndex: integer, option: PartyOption) => { if (slotIndex >= this.scene.currentBattle.getBattlerCount() && slotIndex < 6) { if (LoggerTools.isPreSwitch.value) { - LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, "Pre-switch " + (option == PartyOption.PASS_BATON ? "+ Baton" : "") + " to " + LoggerTools.playerPokeName(this.scene, slotIndex)) + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, "Pre-switch " + (option == PartyOption.PASS_BATON ? "+ Baton" : "") + " " + LoggerTools.playerPokeName(this.scene, fieldIndex) + " to " + LoggerTools.playerPokeName(this.scene, slotIndex)) } if (LoggerTools.isFaintSwitch.value) { - LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, (option == PartyOption.PASS_BATON ? "Baton" : "Switch") + " in " + LoggerTools.playerPokeName(this.scene, slotIndex)) + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, (option == PartyOption.PASS_BATON ? "Baton" : "Send") + " in " + LoggerTools.playerPokeName(this.scene, slotIndex)) } this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, fieldIndex, slotIndex, this.doReturn, option === PartyOption.PASS_BATON)); } From 888a817ee1d6d3bc58c5001423a1790d60e69f78 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Sun, 14 Jul 2024 13:47:57 -0400 Subject: [PATCH 70/94] Organize logger.ts Still working on documentation, but everything has been sorted --- src/data/nature.ts | 4 +- src/logger.ts | 1954 +++++++++++++++++++++++++------------------- 2 files changed, 1133 insertions(+), 825 deletions(-) diff --git a/src/data/nature.ts b/src/data/nature.ts index 86da8bc66a4..724314390af 100644 --- a/src/data/nature.ts +++ b/src/data/nature.ts @@ -61,7 +61,7 @@ export function getNatureName(nature: Nature, includeStatEffects: boolean = fals return ret; } -export function getNatureIncrease(nature: Nature): string { +export function getNatureIncrease(nature: Nature) { switch (nature) { case Nature.LONELY: case Nature.BRAVE: @@ -102,7 +102,7 @@ export function getNatureIncrease(nature: Nature): string { return "" } } -export function getNatureDecrease(nature: Nature): string { +export function getNatureDecrease(nature: Nature) { switch (nature) { case Nature.BOLD: case Nature.TIMID: diff --git a/src/logger.ts b/src/logger.ts index 5546970f08b..9d6a03bc1bb 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,3 +1,4 @@ +//#region Imports import i18next from "i18next"; import * as Utils from "./utils"; import Pokemon from "./field/pokemon"; @@ -5,52 +6,111 @@ import { PlayerPokemon, EnemyPokemon } from "./field/pokemon"; import { Nature, getNatureDecrease, getNatureIncrease, getNatureName } from "./data/nature"; import BattleScene from "./battle-scene"; import { OptionSelectItem } from "./ui/abstact-option-select-ui-handler"; -import { TrainerType } from "#enums/trainer-type"; -import { Modifier, PokemonHeldItemModifier } from "./modifier/modifier"; -import Battle from "./battle"; +import { PokemonHeldItemModifier } from "./modifier/modifier"; import { getBiomeName, PokemonPools, SpeciesTree } from "./data/biomes"; -import { trainerConfigs } from "./data/trainer-config"; import { Mode } from "./ui/ui"; -import { LoginPhase, TitlePhase } from "./phases"; -import { Item } from "pokenode-ts"; +import { TitlePhase } from "./phases"; import Trainer from "./field/trainer"; import { Species } from "./enums/species"; -import { junit } from "node:test/reporters"; -import { i } from "vitest/dist/reporters-xEmem8D4.js"; import { GameMode, GameModes } from "./game-mode"; +import { randomUUID, UUID } from "node:crypto"; +//#endregion + + + + +// #region Variables + +// Value holders +export const rarities = [] +export const rarityslot = [0, ""] +export const Actions = [] + +// Booleans +export const isPreSwitch: Utils.BooleanHolder = new Utils.BooleanHolder(false); +export const isFaintSwitch: Utils.BooleanHolder = new Utils.BooleanHolder(false); +export const SheetsMode = new Utils.BooleanHolder(false) + +// (unused) Stores the current DRPD +/** @deprecated */ +export var StoredLog: DRPD = undefined; + +/** The current DRPD version. */ +export const DRPD_Version = "1.1.0" +/** (Unused / reference only) All the log versions that this mod can keep updated. + * @see updateLog +*/ +export const acceptedVersions = [ + "1.0.0", + "1.0.0a", + "1.1.0", +] + +// #endregion + + + + + +//#region Downloading /** - * All logs. + * Saves a log to your device. + * @param i The index of the log you want to save. + */ +export function downloadLogByID(i: integer) { + console.log(i) + var d = JSON.parse(localStorage.getItem(logs[i][1])) + const blob = new Blob([ printDRPD("", "", d as DRPD) ], {type: "text/json"}); + const link = document.createElement("a"); + link.href = window.URL.createObjectURL(blob); + var date: string = (d as DRPD).date + var filename: string = date[5] + date[6] + "_" + date[8] + date[9] + "_" + date[0] + date[1] + date[2] + date[3] + "_" + (d as DRPD).label + ".json" + link.download = `${filename}`; + link.click(); + link.remove(); +} +/** + * Saves a log to your device in an alternate format. + * @param i The index of the log you want to save. + */ +export function downloadLogByIDToSheet(i: integer) { + console.log(i) + var d = JSON.parse(localStorage.getItem(logs[i][1])) + SheetsMode.value = true; + const blob = new Blob([ printDRPD("", "", d as DRPD) ], {type: "text/json"}); + SheetsMode.value = false; + const link = document.createElement("a"); + link.href = window.URL.createObjectURL(blob); + var date: string = (d as DRPD).date + var filename: string = date[5] + date[6] + "_" + date[8] + date[9] + "_" + date[0] + date[1] + date[2] + date[3] + "_" + (d as DRPD).label + "_sheetexport" + ".json" + link.download = `${filename}`; + link.click(); + link.remove(); +} +//#endregion + + + + + +// #region Log Handler +/** + * Stores logs. + * Generate a new list with `getLogs()`. * - * Format: [filename, localStorage key, name, header, item sprite, header suffix] + * @see getLogs */ export const logs: string[][] = [ - //["instructions.txt", "path_log", "Steps", "Run Steps", "blunder_policy", ""], - //["encounters.csv", "enc_log", "Encounters", "Encounter Data", "ub", ",,,,,,,,,,,,,,,,"], ["drpd.json", "drpd", "DRPD", "", "wide_lens", ""], - //["drpd1.json", "drpd1", "DRPD 1", "", "wide_lens", ""], - //["drpd2.json", "drpd2", "DRPD 2", "", "wide_lens", ""], - //["drpd3.json", "drpd3", "DRPD 3", "", "wide_lens", ""], - //["drpd4.json", "drpd4", "DRPD 4", "", "wide_lens", ""], - //["drpd5.json", "drpd5", "DRPD 5", "", "wide_lens", ""], ] +/** @deprecated */ export const logKeys: string[] = [ "i", // Instructions/steps "e", // Encounters "d", // Debug ]; -export const RNGState: number[] = [] - -export const autoCheckpoints: integer[] = [ - 1, - 11, - 21, - 31, - 41, - 50 -] - /** * Uses the save's RNG seed to create a log ID. Used to assign each save its own log. * @param scene The BattleScene. @@ -71,6 +131,8 @@ export function getItemsID(scene: BattleScene) { } /** * Resets the `logs` array, and creates a list of all game logs in LocalStorage. + * + * @see logs */ export function getLogs() { while(logs.length > 0) @@ -103,6 +165,103 @@ export function getMode(scene: BattleScene) { } } +// #endregion + + + + + +// #region Utilities + +/** + * Pulls the current run's DRPD from LocalStorage using the run's RNG seed. + * @param scene The BattleScene. Used to get the wave number, which is what determines the name of the log we need. + * @returns The DRPD file, or `null` if there is no file for this run. + */ +export function getDRPD(scene: BattleScene): DRPD { + var drpd: DRPD = JSON.parse(localStorage.getItem(getLogID(scene))) as DRPD; + if (drpd == undefined || drpd == null) + return null; + drpd = updateLog(drpd); + //scene.arenaFlyout.updateFieldText() + return drpd; +} + +/** + * Testing purposes only. + */ +export const RNGState: number[] = [] + +/** + * The waves that autosaves are created at. + */ +export const autoCheckpoints: integer[] = [ + 1, + 11, + 21, + 31, + 41, + 50 +] + +/** + * Used to get the filesize of a string. + */ +export const byteSize = str => new Blob([str]).size +/** + * Contains names for different file size units. + * + * B: 1 byte + * + * KB: 1,000 bytes + * + * MB: 1,000,000 bytes + * + * GB: 1,000,000,000 bytes + * + * TB: 1,000,000,000,000 bytes + */ +const filesizes = ["b", "kb", "mb", "gb", "tb"] +/** + * Returns the size of a file, in bytes, KB, MB, GB, or (hopefully not) TB. + * @param str The data to get the size of. + * @returns The file size. Every thousand units is moved up to the next unit and rounded to the nearest tenth (i.e. 1,330,000 bytes will return "1.3mb" - 1,330,000b --> 1,330kb --> 1.3mb) + * @see filesizes + */ +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] +} + +/** + * Compares a Species to a biome's tier pool. + * @param species The species to search for. + * @param pool The SpeciesPool tier to compare. + * @returns whether or not `species` was found in the `pool`. + */ +function checkForPokeInBiome(species: Species, pool: (Species | SpeciesTree)[]): boolean { + //console.log(species, pool) + for (var i = 0; i < pool.length; i++) { + if (typeof pool[i] === "number") { + //console.log(pool[i] + " == " + species + "? " + (pool[i] == species)) + if (pool[i] == species) return true; + } else { + var k = Object.keys(pool[i]) + //console.log(pool[i], k) + for (var j = 0; j < k.length; j++) { + //console.log(pool[i][k[j]] + " == " + species + "? " + (pool[i][k[j]] == species)) + if (pool[i][k[j]] == species) return true; + } + } + } + return false; +} + /** * Formats a Pokemon in the player's party. * @param scene The BattleScene, for getting the player's party. @@ -129,95 +288,80 @@ export function enemyPokeName(scene: BattleScene, index: integer | Pokemon | Ene } // LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, "") -export const rarities = [] -export const rarityslot = [0, ""] +// #endregion -export const isPreSwitch: Utils.BooleanHolder = new Utils.BooleanHolder(false); -export const isFaintSwitch: Utils.BooleanHolder = new Utils.BooleanHolder(false); -export var StoredLog: DRPD = undefined; -export const DRPD_Version = "1.0.0a" -export const acceptedVersions = [ - "1.0.0", - "1.0.0a", -] -export interface DRPD { - version: string, - title?: string, - authors: string[], - date: string, - waves: Wave[], - starters?: PokeData[] -} -export interface Wave { - id: integer, - reload: boolean, - type: string, - double: boolean, - actions: string[], - shop: string, - biome: string, - clearActionsFlag: boolean, - trainer?: TrainerData, - pokemon?: PokeData[] -} -export interface PokeData { - id: integer, - name: string, - ability: string, - isHiddenAbility: boolean, - passiveAbility: string, - nature: NatureData, - gender: string, - rarity: string, - captured: boolean, - level: integer, - items: ItemData[], - ivs: IVData, - source?: Pokemon -} -export interface NatureData { - name: string, - increased: string, - decreased: string -} -export interface IVData { - hp: integer, - atk: integer, - def: integer, - spatk: integer, - spdef: integer, - speed: integer -} -export interface TrainerData { - id: integer, - name: string, - type: string, -} -export interface ItemData { - id: string, - name: string, - quantity: integer, + + +// #region DRPD +/** + * Updates a DRPD, checkings its version and making any necessary changes to it in order to keep it up to date. + * + * @param drpd The DRPD document to update. Its version will be read automatically. + * @see DRPD + */ +function updateLog(drpd: DRPD): DRPD { + if (drpd.version == "1.0.0") { + drpd.version = "1.0.0a" + console.log("Updated to 1.0.0a - changed item IDs to strings") + for (var i = 0; i < drpd.waves.length; i++) { + if (drpd.waves[i] != undefined) { + if (drpd.waves[i].pokemon != undefined) { + for (var j = 0; j < drpd.waves[i].pokemon.length; j++) { + for (var k = 0; k < drpd.waves[i].pokemon[j].items.length; k++) { + drpd.waves[i].pokemon[j].items[k].id = drpd.waves[i].pokemon[j].items[k].id.toString() + } + } + } + } + } + for (var j = 0; j < drpd.starters.length; j++) { + for (var k = 0; k < drpd.starters[j].items.length; k++) { + drpd.starters[j].items[k].id = drpd.starters[j].items[k].id.toString() + } + } + } // 1.0.0 → 1.0.0a + if (drpd.version == "1.0.0a") { + drpd.version = "1.1.0" + drpd.uuid = randomUUID() + drpd.label = "route" + } // 1.0.0a → 1.1.0 + return drpd; } -export const Actions = [] + + + /** - * Creates a new document in the DRPD format - * @param name (Optional) The name for the file. Defaults to "Untitled Run". - * @param authorName (Optional) The author(s) of the file. Defaults to "Write your name here". - * @returns The fresh DRPD document. + * The Daily Run Pathing Description (DRPD) Specification is a JSON standard for organizing the information about a daily run. */ -export function newDocument(name: string = "Untitled Run", authorName: string | string[] = "Write your name here"): DRPD { - return { - version: DRPD_Version, - title: name, - authors: (Array.isArray(authorName) ? authorName : [authorName]), - date: (new Date().getUTCMonth() + 1 < 10 ? "0" : "") + (new Date().getUTCMonth() + 1) + "-" + (new Date().getUTCDate() < 10 ? "0" : "") + new Date().getUTCDate() + "-" + new Date().getUTCFullYear(), - waves: new Array(50), - starters: new Array(3), - } +export interface DRPD { + /** The version of this run. @see DRPD_Version */ + version: string, + /** The display name of this run. Not to be confused with `label`. Entered by the user. */ + title?: string, + /** The webpage path and internal name of this run. Entered by the user. Not to be confused with `title`, which is only a cosmetic identifier. */ + label: string, + /** A unique ID for this run. Currently unused, but may be used in the future. */ + uuid: UUID, + /** The name(s) of the users that worked on this run. Entered by the user. */ + authors: string[], + /** The date that this document was created on. Does NOT automatically detect the date of daily runs (It can't) */ + date: string, + /** + * A list of all the waves in this Daily Run. + * + * A completed Daily Run will have 50 waves. + * + * This array automatically sorts by wave number, with blank slots being pushed to the bottom. + * + * @see Wave + */ + waves: Wave[], + /** The Pokemon that the player started with. Daily runs will have 3. @see PokeData */ + starters?: PokeData[] } /** * Imports a string as a DRPD. @@ -228,65 +372,120 @@ export function importDocument(drpd: string): DRPD { return JSON.parse(drpd) as DRPD; } /** - * Exports a Pokemon's data as `PokeData`. - * @param pokemon The Pokemon to store. - * @param encounterRarity The rarity tier of the Pokemon for this biome. - * @returns The Pokemon data. + * Creates a new document in the DRPD format + * @param name (Optional) The name for the file. Defaults to "Untitled Run". + * @param authorName (Optional) The author(s) of the file. Defaults to "Write your name here". + * @returns The fresh DRPD document. */ -export function exportPokemon(pokemon: Pokemon, encounterRarity?: string): PokeData { +export function newDocument(name: string = "Untitled Run", authorName: string | string[] = "Write your name here"): DRPD { return { - id: pokemon.species.speciesId, - name: pokemon.species.getName(), - ability: pokemon.getAbility().name, - isHiddenAbility: pokemon.hasAbility(pokemon.species.abilityHidden), - passiveAbility: pokemon.getPassiveAbility().name, - nature: exportNature(pokemon.nature), - gender: pokemon.gender == 0 ? "Male" : (pokemon.gender == 1 ? "Female" : "Genderless"), - rarity: encounterRarity, - captured: false, - level: pokemon.level, - items: pokemon.getHeldItems().map((item, idx) => exportItem(item)), - ivs: exportIVs(pokemon.ivs) + version: DRPD_Version, + title: name, + label: "", + uuid: randomUUID(), + authors: (Array.isArray(authorName) ? authorName : [authorName]), + date: new Date().getUTCFullYear() + "-" + (new Date().getUTCMonth() + 1 < 10 ? "0" : "") + (new Date().getUTCMonth() + 1) + "-" + (new Date().getUTCDate() < 10 ? "0" : "") + new Date().getUTCDate(), + waves: new Array(50), + starters: new Array(3), } } /** - * Exports a Pokemon's nature as `NatureData`. - * @param nature The nature to store. - * @returns The nature data. + * Prints a DRPD as a string, for saving it to your device. + * @param inData The data to add on to. + * @param indent The indent string (just a bunch of spaces). + * @param drpd The `DRPD` to export. + * @returns `inData`, with all the DRPD's data appended to it. + * + * @see downloadLogByID */ -export function exportNature(nature: Nature): NatureData { - return { - name: getNatureName(nature), - increased: getNatureIncrease(nature), - decreased: getNatureDecrease(nature), +export function printDRPD(inData: string, indent: string, drpd: DRPD): string { + console.log("Printing for sheet?: " + SheetsMode.value) + inData += indent + "{" + inData += "\n" + indent + " \"version\": \"" + drpd.version + "\"" + inData += ",\n" + indent + " \"title\": \"" + drpd.title + "\"" + inData += ",\n" + indent + " \"authors\": [\"" + drpd.authors.join("\", \"") + "\"]" + inData += ",\n" + indent + " \"date\": \"" + drpd.date + "\"" + if (drpd.waves) { + inData += ",\n" + indent + " \"waves\": [\n" + var isFirst = true + for (var i = 0; i < drpd.waves.length; i++) { + if (drpd.waves[i] != undefined && drpd.waves[i] != null) { + if (isFirst) { + isFirst = false; + } else { + inData += ",\n" + } + inData = printWave(inData, indent + " ", drpd.waves[i]) + } + } + } else { + inData += ",\n" + indent + " \"waves\": []" } + inData += ",\n" + indent + " \"starters\": [\n" + var isFirst = true + for (var i = 0; i < drpd.starters.length; i++) { + if (drpd.starters[i] != undefined && drpd.starters[i] != null) { + if (isFirst) { + isFirst = false; + } else { + inData += ",\n" + } + inData = printPoke(inData, indent + " ", drpd.starters[i]) + } + } + inData += "\n" + indent + " ]\n" + indent + "}" + return inData; } +// #endregion + + + + + +// #region Wave /** - * Exports a Held Item as `ItemData`. - * @param item The item to store. - * @returns The item data. + * A Wave is one individual battle in the run. Each group of ten waves has the same biome. */ -export function exportItem(item: PokemonHeldItemModifier): ItemData { - return { - id: item.type.identifier, - name: item.type.name, - quantity: item.getStackCount() - } -} -/** - * Exports a Pokemon's IVs as `IVData`. - * @param ivs The IV array to store. - * @returns The IV data. - */ -export function exportIVs(ivs: integer[]): IVData { - return { - hp: ivs[0], - atk: ivs[1], - def: ivs[2], - spatk: ivs[3], - spdef: ivs[4], - speed: ivs[5] - } +export interface Wave { + /** The wave number. Used to label the wave, detect and delete duplicates, and automatically sort `DRPD.waves[]`. */ + id: integer, + /** Set to `true` if a reload is required to play this wave properly. Setting this value is the PITA I have ever dealt with. */ + reload: boolean, + /** + * The specific type of wave. + * + * `wild`: This is a wild encounter. + * + * `trainer`: This is a trainer battle. + * + * `boss`: This is a boss floor (floors 10, 20, 30, etc). Overrides the two values above. + */ + type: "wild" | "trainer" | "boss", + /** Set to `true` if this is a double battle. */ + double: boolean, + /** The list of actions that the player took during this wave. */ + actions: string[], + /** The item that the player took in the shop. A blank string (`""`) if there is no shop (wave 10, 20, 30, etc.) or the player fled from battle. */ + shop: string, + /** The biome that this battle takes place in. */ + biome: string, + /** If true, the next time an action is logged, all previous actions will be deleted. + * @see Wave.actions + * @see logActions + * @see resetWaveActions + */ + clearActionsFlag: boolean, + /** The trainer that you fight in this floor, if any. + * @see TrainerData + * @see Wave.type + */ + trainer?: TrainerData, + /** The Pokémon that you have to battle against. + * Not included if this is a trainer battle. + * @see PokeData + * @see Wave.type + */ + pokemon?: PokeData[] } /** * Exports the current battle as a `Wave`. @@ -328,322 +527,87 @@ export function exportWave(scene: BattleScene): Wave { return ret; } /** - * Exports the opposing trainer as `TrainerData`. - * @param trainer The Trainer to store. - * @returns The Trainer data. + * Prints a wave as a string, for saving a DRPD to your device. + * @param inData The data to add on to. + * @param indent The indent string (just a bunch of spaces). + * @param wave The `Wave` to export. + * @returns `inData`, with all the wave's data appended to it. + * + * @see printDRPD */ -export function exportTrainer(trainer: Trainer): TrainerData { - if (trainer.config.getTitle(0, trainer.variant) == "Finn") { - return { - id: trainer.config.trainerType, - name: "Finn", - type: "Rival" - } - } - if (trainer.config.getTitle(0, trainer.variant) == "Ivy") { - return { - id: trainer.config.trainerType, - name: "Ivy", - type: "Rival" - } - } - return { - id: trainer.config.trainerType, - name: trainer.name, - type: trainer.config.getTitle(0, trainer.variant) - } -} - -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] -} - -export function getDRPD(scene: BattleScene): DRPD { - var drpd: DRPD = JSON.parse(localStorage.getItem(getLogID(scene))) as DRPD; - drpd = updateLog(drpd); - //scene.arenaFlyout.updateFieldText() - return drpd; -} - -/** - * Generates a UI option to save a log to your device. - * @param i The slot number. Corresponds to an index in `logs`. - * @param saves Your session data. Used to label logs if they match one of your save slots. - * @returns A UI option. - */ -export function generateOption(i: integer, saves: any): OptionSelectItem { - var filename: string = (JSON.parse(localStorage.getItem(logs[i][1])) as DRPD).title - var op: OptionSelectItem = { - label: `Export ${filename} (${getSize(printDRPD("", "", JSON.parse(localStorage.getItem(logs[i][1])) as DRPD))})`, - handler: () => { - downloadLogByID(i) - return false; - } - } - for (var j = 0; j < saves.length; j++) { - console.log(saves[j].seed, logs[i][2], saves[j].seed == logs[i][2]) - if (saves[j].seed == logs[i][2]) { - op.label = "[Slot " + (saves[j].slot + 1) + "]" + op.label.substring(6) - } - } - if (logs[i][4] != "") { - op.label = " " + op.label - op.item = logs[i][4] - } - return op; -} -/** - * Generates a UI option to save a log to your device. - * @param i The slot number. Corresponds to an index in `logs`. - * @param saves Your session data. Used to label logs if they match one of your save slots. - * @returns A UI option. - */ -export function generateEditOption(scene: BattleScene, i: integer, saves: any, phase: TitlePhase): OptionSelectItem { - var filename: string = (JSON.parse(localStorage.getItem(logs[i][1])) as DRPD).title - var op: OptionSelectItem = { - label: `Export ${filename} (${getSize(printDRPD("", "", JSON.parse(localStorage.getItem(logs[i][1])) as DRPD))})`, - handler: () => { - rarityslot[1] = logs[i][1] - //scene.phaseQueue[0].end() - scene.ui.setMode(Mode.NAME_LOG, { - autofillfields: [ - (JSON.parse(localStorage.getItem(logs[i][1])) as DRPD).title, - (JSON.parse(localStorage.getItem(logs[i][1])) as DRPD).authors.join(", ") - ], - buttonActions: [ - () => { - console.log("Rename") - scene.ui.playSelect(); - phase.callEnd() - }, - () => { - console.log("Export") - scene.ui.playSelect(); - downloadLogByID(i) - phase.callEnd() - }, - () => { - console.log("Export to Sheets") - scene.ui.playSelect(); - downloadLogByIDToSheet(i) - phase.callEnd() - }, - () => { - console.log("Delete") - scene.ui.playSelect(); - localStorage.removeItem(logs[i][1]) - phase.callEnd() +function printWave(inData: string, indent: string, wave: Wave): string { + inData += indent + "{" + inData += "\n" + indent + " \"id\": " + wave.id + "" + inData += ",\n" + indent + " \"reload\": " + wave.reload + "" + inData += ",\n" + indent + " \"type\": \"" + wave.type + "\"" + inData += ",\n" + indent + " \"double\": " + wave.double + "" + var isFirst = true + if (wave.actions.length > 0) { + if (SheetsMode.value) { + inData += ",\n" + indent + " \"actions\": \"" + var isFirst = true + for (var i = 0; i < wave.actions.length; i++) { + if (wave.actions[i] != undefined) { + if (isFirst) { + isFirst = false; + } else { + inData += "CHAR(10)" + } + inData += wave.actions[i] } - ] - }); - return false; + } + inData += "\"" + } else { + inData += ",\n" + indent + " \"actions\": [" + for (var i = 0; i < wave.actions.length; i++) { + if (wave.actions[i] != undefined) { + if (isFirst) { + isFirst = false; + } else { + inData += "," + } + inData += "\n " + indent + "\"" + wave.actions[i] + "\"" + } + } + if (!isFirst) inData += "\n" + inData += indent + " ]" } + } else { + inData += ",\n" + indent + " \"actions\": [\"[No actions?]\"]" } - for (var j = 0; j < saves.length; j++) { - //console.log(saves[j].seed, logs[i][2], saves[j].seed == logs[i][2]) - if (saves[j].seed == logs[i][2]) { - op.label = "[Slot " + (saves[j].slot + 1) + "]" + op.label.substring(6) + inData += ",\n " + indent + "\"shop\": \"" + wave.shop + "\"" + inData += ",\n " + indent + "\"biome\": \"" + wave.biome + "\"" + if (wave.trainer) { + inData += ",\n " + indent + "\"trainer\": " + inData = printTrainer(inData, indent + " ", wave.trainer) + } + if (wave.pokemon) + if (wave.pokemon.length > 0) { + inData += ",\n " + indent + "\"pokemon\": [\n" + isFirst = true + for (var i = 0; i < wave.pokemon.length; i++) { + if (wave.pokemon[i] != undefined) { + if (isFirst) { + isFirst = false; + } else { + inData += ",\n" + } + inData = printPoke(inData, indent + " ", wave.pokemon[i]) + } + } + if (SheetsMode.value && wave.pokemon.length == 1) { + inData += "," + indent + " \n{" + indent + " \n}" + } + inData += "\n" + indent + " ]" } - } - if (logs[i][4] != "") { - op.label = " " + op.label - op.item = logs[i][4] - } - return op; -} -/** - * Generates an option to create a new log. - * - * Not used. - * @param i The slot number. Corresponds to an index in `logs`. - * @param scene The current scene. Not used. - * @param o The current game phase. Used to return to the previous menu. Not necessary anymore lol - * @returns A UI option. - * - * wow this function sucks - * @deprecated - */ -export function generateAddOption(i: integer, scene: BattleScene, o: TitlePhase) { - var op: OptionSelectItem = { - label: "Generate log " + logs[i][0], - handler: () => { - localStorage.setItem(logs[i][1], JSON.stringify(newDocument())) - o.callEnd(); - return true; - } - } - return op; + inData += "\n" + indent + "}" + return inData; } -/** - * 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] + " ----" + logs[logKeys.indexOf(keyword)][5]) -} -export function setFileInfo(title: string, authors: string[]) { - console.log("Setting file " + rarityslot[1] + " to " + title + " / [" + authors.join(", ") + "]") - var fileID = rarityslot[1] as string - var drpd = JSON.parse(localStorage.getItem(fileID)) as DRPD; - drpd.title = title; - for (var i = 0; i < authors.length; i++) { - while (authors[i][0] == " ") { - authors[i] = authors[i].substring(1) - } - while (authors[i][authors[i].length - 1] == " ") { - authors[i] = authors[i].substring(0, authors[i].length - 1) - } - } - for (var i = 0; i < authors.length; i++) { - if (authors[i] == "") { - authors.splice(i, 1) - i--; - } - } - drpd.authors = authors; - localStorage.setItem(fileID, JSON.stringify(drpd)) -} -/** - * Saves a log to your device. - * @param keyword The identifier key for the log you want to save. - */ -export function downloadLog(keyword: string) { - var d = JSON.parse(localStorage.getItem(logs[logKeys.indexOf(keyword)][1])) - const blob = new Blob([ printDRPD("", "", d as DRPD) ], {type: "text/json"}); - const link = document.createElement("a"); - link.href = window.URL.createObjectURL(blob); - var date: string = (d as DRPD).date - var filename: string = date[0] + date[1] + "_" + date[3] + date[4] + "_" + date[6] + date[7] + date[8] + date[9] + "_route.json" - link.download = `${filename}`; - link.click(); - link.remove(); -} -/** - * Saves a log to your device. - * @param i The index of the log you want to save. - */ -export const SheetsMode = new Utils.BooleanHolder(false) -export function downloadLogByID(i: integer) { - console.log(i) - var d = JSON.parse(localStorage.getItem(logs[i][1])) - const blob = new Blob([ printDRPD("", "", d as DRPD) ], {type: "text/json"}); - const link = document.createElement("a"); - link.href = window.URL.createObjectURL(blob); - var date: string = (d as DRPD).date - var filename: string = date[0] + date[1] + "_" + date[3] + date[4] + "_" + date[6] + date[7] + date[8] + date[9] + "_route.json" - link.download = `${filename}`; - link.click(); - link.remove(); -} -export function downloadLogByIDToSheet(i: integer) { - console.log(i) - var d = JSON.parse(localStorage.getItem(logs[i][1])) - SheetsMode.value = true; - const blob = new Blob([ printDRPD("", "", d as DRPD) ], {type: "text/json"}); - SheetsMode.value = false; - const link = document.createElement("a"); - link.href = window.URL.createObjectURL(blob); - var date: string = (d as DRPD).date - var filename: string = date[0] + date[1] + "_" + date[3] + date[4] + "_" + date[6] + date[7] + date[8] + date[9] + "_route.json" - link.download = `${filename}`; - link.click(); - link.remove(); -} -/** - * Calls `logPokemon` once for each opponent or, if it's a trainer battle, logs the trainer's data. - * @param scene The BattleScene. Used to get the enemy team and whether it's a trainer battle or not. - * @param floor The wave index to write to. Defaults to the current wave. - */ -export function logTeam(scene: BattleScene, floor: integer = undefined) { - if (floor == undefined) floor = scene.currentBattle.waveIndex - var team = scene.getEnemyParty() - console.log("Log Enemy Team") - if (team[0].hasTrainer()) { - //var sprite = scene.currentBattle.trainer.config.getSpriteKey() - //var trainerCat = Utils.getEnumKeys(TrainerType)[Utils.getEnumValues(TrainerType).indexOf(scene.currentBattle.trainer.config.trainerType)] - //setRow("e", floor + ",0," + sprite + ",trainer," + trainerCat + ",,,,,,,,,,,,", floor, 0) - } else { - for (var i = 0; i < team.length; i++) { - logPokemon(scene, floor, i, team[i], rarities[i]) - } - if (team.length == 1) { - //setRow("e", ",,,,,,,,,,,,,,,,", floor, 1) - } - } -} -/** - * Logs the actions that the player took. - * - * This includes attacks you perform, items you transfer during the shop, Poke Balls you throw, running from battl, (or attempting to), and switching (including pre-switches). - * @param scene The BattleScene. Used to get the log ID. - * @param floor The wave index to write to. - * @param action The text you want to add to the actions list. - */ -export function logActions(scene: BattleScene, floor: integer, action: string) { - if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) - var drpd = getDRPD(scene) - console.log("Log Action", drpd) - var wv: Wave = getWave(drpd, floor, scene) - if (wv.clearActionsFlag) { - console.log("Triggered clearActionsFlag") - wv.clearActionsFlag = false - wv.actions = [] - } - wv.actions.push(action) - console.log(drpd) - localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) -} -/** - * Logs what the player took from the rewards pool and, if applicable, who they used it on. - * @param scene The BattleScene. Used to get the log ID. - * @param floor The wave index to write to. - * @param action The shop action. Left blank if there was no shop this floor or if you ran away. Logged as "Skip taking items" if you didn't take anything for some reason. - */ -export function logShop(scene: BattleScene, floor: integer, action: string) { - if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) - var drpd = getDRPD(scene) - console.log("Log Shop Item", drpd) - var wv: Wave = getWave(drpd, floor, scene) - wv.shop = action - console.log(drpd) - localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) -} -export function logCapture(scene: BattleScene, floor: integer, target: EnemyPokemon) { - //if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) - var drpd = getDRPD(scene) - console.log("Log Capture", drpd) - var wv: Wave = getWave(drpd, floor, scene) - var pkslot = target.partyslot - wv.pokemon[pkslot].captured = true; - console.log(drpd) - localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) -} + + + + /** * Retrieves a wave from the DRPD. If the wave doesn't exist, it creates a new one. * @param drpd The document to read from. @@ -788,28 +752,571 @@ export function getWave(drpd: DRPD, floor: integer, scene: BattleScene): Wave { } return wv; } +// #endregion + + + + + +// #region Pokémon /** - * Compares a Species to a biome's tier pool. - * @param species The species to search for. - * @param pool The SpeciesPool tier to compare. - * @returns whether or not `species` was found in the `pool`. + * A Pokémon. + * + * This data type is used in `DRPD.starters` to list the player's starting Pokémon, or in `Wave.pokemon` to list the opponent(s) in a wild encounter. */ -function checkForPokeInBiome(species: Species, pool: (Species | SpeciesTree)[]): boolean { - //console.log(species, pool) - for (var i = 0; i < pool.length; i++) { - if (typeof pool[i] === "number") { - //console.log(pool[i] + " == " + species + "? " + (pool[i] == species)) - if (pool[i] == species) return true; - } else { - var k = Object.keys(pool[i]) - //console.log(pool[i], k) - for (var j = 0; j < k.length; j++) { - //console.log(pool[i][k[j]] + " == " + species + "? " + (pool[i][k[j]] == species)) - if (pool[i][k[j]] == species) return true; +export interface PokeData { + /** The party position of this Pokémon, as of the beginning of the battle. */ + id: integer, + /** The name of this Pokémon as it would appear in the party list or in battle. */ + name: string, + /** The Pokémon's primary ability. */ + ability: string, + /** Set to `true` if this Pokémon's ability is its Hidden Ability. + * @see PokeData.ability + */ + isHiddenAbility: boolean, + /** The Pokémon's passive / secondary ability. */ + passiveAbility: string, + /** The Pokémon's nature. Influences its stats. + * @see NatureData + */ + nature: NatureData, + /** The Pokémon's gender. */ + gender: "Male" | "Female" | "Genderless", + /** The Pokémon's encounter rarity within the current biome. */ + rarity: string, + /** Whether or not the Pokémon was captured. */ + captured: boolean, + /** The Pokémon's level. */ + level: integer, + /** The Pokémon's Held Items, if any. + * @see ItemData + */ + items: ItemData[], + /** The Pokémon's IVs. Influences its base stats. + * @see IVData + */ + ivs: IVData, + /** The Pokémon that was used to generate this `PokeData`. Not exported. + * @see Pokemon + */ + source?: Pokemon +} +/** + * Exports a Pokemon's data as `PokeData`. + * @param pokemon The Pokemon to store. + * @param encounterRarity The rarity tier of the Pokemon for this biome. + * @returns The Pokemon data. + */ +export function exportPokemon(pokemon: Pokemon, encounterRarity?: string): PokeData { + return { + id: pokemon.species.speciesId, + name: pokemon.species.getName(), + ability: pokemon.getAbility().name, + isHiddenAbility: pokemon.hasAbility(pokemon.species.abilityHidden), + passiveAbility: pokemon.getPassiveAbility().name, + nature: exportNature(pokemon.nature), + gender: pokemon.gender == 0 ? "Male" : (pokemon.gender == 1 ? "Female" : "Genderless"), + rarity: encounterRarity, + captured: false, + level: pokemon.level, + items: pokemon.getHeldItems().map((item, idx) => exportItem(item)), + ivs: exportIVs(pokemon.ivs) + } +} +/** + * Prints a Pokemon as a string, for saving a DRPD to your device. + * @param inData The data to add on to. + * @param indent The indent string (just a bunch of spaces). + * @param wave The `PokeData` to export. + * @returns `inData`, with all the Pokemon's data appended to it. + * + * @see printDRPD + */ +function printPoke(inData: string, indent: string, pokemon: PokeData) { + inData += indent + "{" + inData += "\n" + indent + " \"id\": " + pokemon.id + inData += ",\n" + indent + " \"name\": \"" + pokemon.name + "\"" + inData += ",\n" + indent + " \"ability\": \"" + pokemon.ability + "\"" + inData += ",\n" + indent + " \"isHiddenAbility\": " + pokemon.isHiddenAbility + inData += ",\n" + indent + " \"passiveAbility\": \"" + pokemon.passiveAbility + "\"" + inData += ",\n" + indent + " \"nature\": \n" + inData = printNature(inData, indent + " ", pokemon.nature) + inData += ",\n" + indent + " \"gender\": \"" + pokemon.gender + "\"" + inData += ",\n" + indent + " \"rarity\": \"" + pokemon.rarity + "\"" + inData += ",\n" + indent + " \"captured\": " + pokemon.captured + inData += ",\n" + indent + " \"level\": " + pokemon.level + if (SheetsMode.value) { + inData += ",\n" + indent + " \"items\": \"" + var isFirst = true + for (var i = 0; i < pokemon.items.length; i++) { + if (pokemon.items[i] != undefined) { + if (isFirst) { + isFirst = false; + } else { + inData += "CHAR(10)" + } + inData += printItemNoNewline(inData, "", pokemon.items[i]) + } } + inData += "\"" + } else { + if (pokemon.items.length > 0) { + inData += ",\n" + indent + " \"items\": [\n" + var isFirst = true + for (var i = 0; i < pokemon.items.length; i++) { + if (pokemon.items[i] != undefined) { + if (isFirst) { + isFirst = false; + } else { + inData += "," + } + inData = printItem(inData, indent + " ", pokemon.items[i]) + } + } + if (!isFirst) inData += "\n" + inData += indent + " ]" + } else { + inData += ",\n" + indent + " \"items\": []" } } - return false; + inData += ",\n" + indent + " \"ivs\": " + inData = printIV(inData, indent + " ", pokemon.ivs) + //inData += ",\n" + indent + " \"rarity\": " + pokemon.rarity + inData += "\n" + indent + "}" + return inData; +} + + + + + +/** + * Calls `logPokemon` once for each opponent or, if it's a trainer battle, logs the trainer's data. + * @param scene The BattleScene. Used to get the enemy team and whether it's a trainer battle or not. + * @param floor The wave index to write to. Defaults to the current wave. + */ +export function logTeam(scene: BattleScene, floor: integer = undefined) { + if (floor == undefined) floor = scene.currentBattle.waveIndex + var team = scene.getEnemyParty() + console.log("Log Enemy Team") + if (team[0].hasTrainer()) { + //var sprite = scene.currentBattle.trainer.config.getSpriteKey() + //var trainerCat = Utils.getEnumKeys(TrainerType)[Utils.getEnumValues(TrainerType).indexOf(scene.currentBattle.trainer.config.trainerType)] + //setRow("e", floor + ",0," + sprite + ",trainer," + trainerCat + ",,,,,,,,,,,,", floor, 0) + } else { + for (var i = 0; i < team.length; i++) { + logPokemon(scene, floor, i, team[i], rarities[i]) + } + if (team.length == 1) { + //setRow("e", ",,,,,,,,,,,,,,,,", floor, 1) + } + } +} +// #endregion + + + + + +// #region Nature +export interface NatureData { + /** The display name of this nature. */ + name: string, + /** The stat that gets a 10% increase from this nature, if any. */ + increased: "" | "atk" | "def" | "spatk" | "spdef" | "spe", + /** The stat that gets a 10% decrease from this nature, if any. */ + decreased: "" | "atk" | "def" | "spatk" | "spdef" | "spe" +} +/** + * Exports a Pokemon's nature as `NatureData`. + * @param nature The nature to store. + * @returns The nature data. + */ +export function exportNature(nature: Nature): NatureData { + return { + name: getNatureName(nature), + increased: getNatureIncrease(nature), + decreased: getNatureDecrease(nature), + } +} +/** + * Prints a Nature as a string, for saving a DRPD to your device. + * @param inData The data to add on to. + * @param indent The indent string (just a bunch of spaces). + * @param wave The `NatureData` to export. + * @returns `inData`, with all the nature data appended to it. + * + * @see printDRPD + */ +function printNature(inData: string, indent: string, nature: NatureData) { + inData += indent + "{" + inData += "\n" + indent + " \"name\": \"" + nature.name + "\"" + inData += ",\n" + indent + " \"increased\": \"" + nature.increased + "\"" + inData += ",\n" + indent + " \"decreased\": \"" + nature.decreased + "\"" + inData += "\n" + indent + "}" + return inData; +} +// #endregion + + + + + +// #region IVs +export interface IVData { + /** Influences a Pokémon's maximum health. */ + hp: integer, + /** Influences a Pokémon's physical strength. */ + atk: integer, + /** Influences a Pokémon's resistance to physical attacks. */ + def: integer, + /** Influences the power of a Pokémon's ranged attacks */ + spatk: integer, + /** Influences a Pokémon's resistance to ranged attacks. */ + spdef: integer, + /** Influences a Pokémon's action speed. */ + speed: integer +} +/** + * Exports a Pokémon's IVs as `IVData`. + * @param ivs The IV array to store. + * @returns The IV data. + */ +export function exportIVs(ivs: integer[]): IVData { + return { + hp: ivs[0], + atk: ivs[1], + def: ivs[2], + spatk: ivs[3], + spdef: ivs[4], + speed: ivs[5] + } +} +/** + * Prints a Pokemon's IV data as a string, for saving a DRPD to your device. + * @param inData The data to add on to. + * @param indent The indent string (just a bunch of spaces). + * @param wave The `IVData` to export. + * @returns `inData`, with the IV data appended to it. + * + * @see printDRPD + */ +function printIV(inData: string, indent: string, iv: IVData) { + inData += "{" + inData += "\n" + indent + " \"hp\": " + iv.hp + inData += ",\n" + indent + " \"atk\": " + iv.atk + inData += ",\n" + indent + " \"def\": " + iv.def + inData += ",\n" + indent + " \"spatk\": " + iv.spatk + inData += ",\n" + indent + " \"spdef\": " + iv.spdef + inData += ",\n" + indent + " \"spe\": " + iv.speed + inData += "\n" + indent + "}" + return inData; +} +// #endregion + + + + + +// #region Trainer +export interface TrainerData { + /** The trainer type's position in the Trainers enum. + * @see Trainer + */ + id: integer, + /** The Trainer's ingame name. */ + name: string, + /** The Trainer's ingame title. */ + type: string, +} +/** + * Exports the opposing trainer as `TrainerData`. + * @param trainer The Trainer to store. + * @returns The Trainer data. + */ +export function exportTrainer(trainer: Trainer): TrainerData { + if (trainer.config.getTitle(0, trainer.variant) == "Finn") { + return { + id: trainer.config.trainerType, + name: "Finn", + type: "Rival" + } + } + if (trainer.config.getTitle(0, trainer.variant) == "Ivy") { + return { + id: trainer.config.trainerType, + name: "Ivy", + type: "Rival" + } + } + return { + id: trainer.config.trainerType, + name: trainer.name, + type: trainer.config.getTitle(0, trainer.variant) + } +} +/** + * Prints a Trainer as a string, for saving a DRPD to your device. + * @param inData The data to add on to. + * @param indent The indent string (just a bunch of spaces). + * @param wave The `TrainerData` to export. + * @returns `inData`, with all the Trainer's data appended to it. + * + * @see printDRPD + */ +function printTrainer(inData: string, indent: string, trainer: TrainerData) { + inData += "{" + inData += "\n" + indent + " \"id\": \"" + trainer.id + "\"" + inData += ",\n" + indent + " \"name\": \"" + trainer.name + "\"" + inData += ",\n" + indent + " \"type\": \"" + trainer.type + "\"" + inData += "\n" + indent + "}" + return inData; +} +// #endregion + + + + + +// #region Item +/** An item held by a Pokémon. Quantities and ownership are recorded at the start of the battle, and do not reflect items being used up or stolen. */ +export interface ItemData { + /** A type:key pair identifying the specific item. + * + * Example: `FormChange:TOXIC_PLATE` + */ + id: string, + /** The item's ingame name. */ + name: string, + /** This item's stack size. */ + quantity: integer, +} +/** + * Exports a Held Item as `ItemData`. + * @param item The item to store. + * @returns The item data. + */ +export function exportItem(item: PokemonHeldItemModifier): ItemData { + return { + id: item.type.identifier, + name: item.type.name, + quantity: item.getStackCount() + } +} +/** + * Prints an item as a string, for saving a DRPD to your device. + * @param inData The data to add on to. + * @param indent The indent string (just a bunch of spaces). + * @param wave The `ItemData` to export. + * @returns `inData`, with all the Item's data appended to it. + * + * @see printDRPD + */ +function printItem(inData: string, indent: string, item: ItemData) { + inData += indent + "{" + inData += "\n" + indent + " \"id\": \"" + item.id + "\"" + inData += ",\n" + indent + " \"name\": \"" + item.name + "\"" + inData += ",\n" + indent + " \"quantity\": " + item.quantity + inData += "\n" + indent + "}" + return inData; +} +/** + * Prints an item as a string, for saving a DRPD to your device. + * @param inData The data to add on to. + * @param indent The indent string (just a bunch of spaces). + * @param wave The `ItemData` to export. + * @returns `inData`, with all the Item's data appended to it. + * + * @see `downloadLogByIDToSheet` + */ +function printItemNoNewline(inData: string, indent: string, item: ItemData) { + inData = "{\\\"id\\\": \\\"" + item.id + "\\\", \\\"name\\\": \\\"" + item.name + "\\\", \\\"quantity\\\": " + item.quantity + "}" + return inData; +} +// #endregion + + + + + +//#region Manage Logs menu + +/** + * Sets the name, author, and [todo] label for a file. + * @param title The display name of the file. + * @param authors The author(s) of the file. + * @todo Add label field. + */ +export function setFileInfo(title: string, authors: string[]) { + console.log("Setting file " + rarityslot[1] + " to " + title + " / [" + authors.join(", ") + "]") + var fileID = rarityslot[1] as string + var drpd = JSON.parse(localStorage.getItem(fileID)) as DRPD; + drpd.title = title; + for (var i = 0; i < authors.length; i++) { + while (authors[i][0] == " ") { + authors[i] = authors[i].substring(1) + } + while (authors[i][authors[i].length - 1] == " ") { + authors[i] = authors[i].substring(0, authors[i].length - 1) + } + } + for (var i = 0; i < authors.length; i++) { + if (authors[i] == "") { + authors.splice(i, 1) + i--; + } + } + drpd.authors = authors; + localStorage.setItem(fileID, JSON.stringify(drpd)) +} + +/** + * Generates a UI option to save a log to your device. + * @param i The slot number. Corresponds to an index in `logs`. + * @param saves Your session data. Used to label logs if they match one of your save slots. + * @returns A UI option. + */ +export function generateOption(i: integer, saves: any): OptionSelectItem { + var filename: string = (JSON.parse(localStorage.getItem(logs[i][1])) as DRPD).title + var op: OptionSelectItem = { + label: `Export ${filename} (${getSize(printDRPD("", "", JSON.parse(localStorage.getItem(logs[i][1])) as DRPD))})`, + handler: () => { + downloadLogByID(i) + return false; + } + } + for (var j = 0; j < saves.length; j++) { + console.log(saves[j].seed, logs[i][2], saves[j].seed == logs[i][2]) + if (saves[j].seed == logs[i][2]) { + op.label = "[Slot " + (saves[j].slot + 1) + "]" + op.label.substring(6) + } + } + if (logs[i][4] != "") { + op.label = " " + op.label + op.item = logs[i][4] + } + return op; +} +/** + * Generates a UI option to save a log to your device. + * @param i The slot number. Corresponds to an index in `logs`. + * @param saves Your session data. Used to label logs if they match one of your save slots. + * @returns A UI option. + */ +export function generateEditOption(scene: BattleScene, i: integer, saves: any, phase: TitlePhase): OptionSelectItem { + var filename: string = (JSON.parse(localStorage.getItem(logs[i][1])) as DRPD).title + var op: OptionSelectItem = { + label: `Export ${filename} (${getSize(printDRPD("", "", JSON.parse(localStorage.getItem(logs[i][1])) as DRPD))})`, + handler: () => { + rarityslot[1] = logs[i][1] + //scene.phaseQueue[0].end() + scene.ui.setMode(Mode.NAME_LOG, { + autofillfields: [ + (JSON.parse(localStorage.getItem(logs[i][1])) as DRPD).title, + (JSON.parse(localStorage.getItem(logs[i][1])) as DRPD).authors.join(", ") + ], + buttonActions: [ + () => { + console.log("Rename") + scene.ui.playSelect(); + phase.callEnd() + }, + () => { + console.log("Export") + scene.ui.playSelect(); + downloadLogByID(i) + phase.callEnd() + }, + () => { + console.log("Export to Sheets") + scene.ui.playSelect(); + downloadLogByIDToSheet(i) + phase.callEnd() + }, + () => { + console.log("Delete") + scene.ui.playSelect(); + localStorage.removeItem(logs[i][1]) + phase.callEnd() + } + ] + }); + return false; + } + } + for (var j = 0; j < saves.length; j++) { + //console.log(saves[j].seed, logs[i][2], saves[j].seed == logs[i][2]) + if (saves[j].seed == logs[i][2]) { + op.label = "[Slot " + (saves[j].slot + 1) + "]" + op.label.substring(6) + } + } + if (logs[i][4] != "") { + op.label = " " + op.label + op.item = logs[i][4] + } + return op; +} + +//#endregion + + + + + +//#region Logging Events +/** + * Logs the actions that the player took. + * + * This includes attacks you perform, items you transfer during the shop, Poke Balls you throw, running from battl, (or attempting to), and switching (including pre-switches). + * @param scene The BattleScene. Used to get the log ID. + * @param floor The wave index to write to. + * @param action The text you want to add to the actions list. + * + * @see resetWaveActions + */ +export function logActions(scene: BattleScene, floor: integer, action: string) { + if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) + var drpd = getDRPD(scene) + console.log("Log Action", drpd) + var wv: Wave = getWave(drpd, floor, scene) + if (wv.double == undefined) + wv.double = false + if (wv.clearActionsFlag) { + console.log("Triggered clearActionsFlag") + wv.clearActionsFlag = false + wv.actions = [] + } + wv.actions.push(action) + console.log(drpd) + localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) +} +export function logCapture(scene: BattleScene, floor: integer, target: EnemyPokemon) { + //if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) + var drpd = getDRPD(scene) + console.log("Log Capture", drpd) + var wv: Wave = getWave(drpd, floor, scene) + var pkslot = target.partyslot + wv.pokemon[pkslot].captured = true; + console.log(drpd) + localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) +} +/** + * Logs the player's current party. + * + * Called on Floor 1 to store the starters list. + * @param scene The BattleScene. Used to get the log ID and the player's party. + */ +export function logPlayerTeam(scene: BattleScene) { + if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) + var drpd = getDRPD(scene) + console.log("Log Player Starters", drpd) + var P = scene.getParty() + for (var i = 0; i < P.length; i++) { + drpd.starters[i] = exportPokemon(P[i]) + } + console.log(drpd) + localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) } /** * Logs a wild Pokemon to a wave's data. @@ -894,22 +1401,18 @@ export function logPokemon(scene: BattleScene, floor: integer = undefined, slot: localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) } /** - * Clears the action list for a wave. - * @param scene The BattleScene. Used to get the log ID and trainer data. - * @param floor The wave index to write to. Defaults to the current floor. + * Logs what the player took from the rewards pool and, if applicable, who they used it on. + * @param scene The BattleScene. Used to get the log ID. + * @param floor The wave index to write to. + * @param action The shop action. Left blank if there was no shop this floor or if you ran away. Logged as "Skip taking items" if you didn't take anything for some reason. */ -export function resetWaveActions(scene: BattleScene, floor: integer = undefined, softflag: boolean) { - if (floor == undefined) floor = scene.currentBattle.waveIndex +export function logShop(scene: BattleScene, floor: integer, action: string) { if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) var drpd = getDRPD(scene) - console.log("Clear Actions", drpd) + console.log("Log Shop Item", drpd) var wv: Wave = getWave(drpd, floor, scene) - if (softflag) { - wv.clearActionsFlag = true; - } else { - wv.actions = [] - } - console.log(drpd, wv) + wv.shop = action + console.log(drpd) localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) } /** @@ -930,23 +1433,141 @@ export function logTrainer(scene: BattleScene, floor: integer = undefined) { console.log(drpd) localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) } -/** - * Logs the player's current party. - * - * Called on Floor 1 to store the starters list. - * @param scene The BattleScene. Used to get the log ID and the player's party. - */ -export function logPlayerTeam(scene: BattleScene) { - if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) + + + + + +export function flagReset(scene: BattleScene, floor: integer = undefined) { + if (floor == undefined) + floor = scene.currentBattle.waveIndex; + if (localStorage.getItem(getLogID(scene)) == null) + localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) var drpd = getDRPD(scene) - console.log("Log Player Starters", drpd) - var P = scene.getParty() - for (var i = 0; i < P.length; i++) { - drpd.starters[i] = exportPokemon(P[i]) - } + var wv = getWave(drpd, floor, scene) + wv.reload = true; console.log(drpd) localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) } +export function flagResetIfExists(scene: BattleScene, floor: integer = undefined) { + if (floor == undefined) + floor = scene.currentBattle.waveIndex; + if (localStorage.getItem(getLogID(scene)) == null) + localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) + var drpd = getDRPD(scene) + var waveExists = false + for (var i = 0; i < drpd.waves.length; i++) { + if (drpd.waves[i] != undefined) { + if (drpd.waves[i].id == floor) { + waveExists = true; + } + } + } + if (!waveExists) return; + var wv = getWave(drpd, floor, scene) + wv.reload = true; + console.log(drpd) + localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) +} + + + +/** + * Clears the action list for a wave. + * @param scene The BattleScene. Used to get the log ID and trainer data. + * @param floor The wave index to write to. Defaults to the current floor. + * + * @see logActions + */ +export function resetWaveActions(scene: BattleScene, floor: integer = undefined, softflag: boolean) { + if (floor == undefined) floor = scene.currentBattle.waveIndex + if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) + var drpd = getDRPD(scene) + console.log("Clear Actions", drpd) + var wv: Wave = getWave(drpd, floor, scene) + if (softflag) { + wv.clearActionsFlag = true; + } else { + wv.actions = [] + } + console.log(drpd, wv) + localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) +} +//#endregion + + + + + +//#region Deprecated + +/** + * 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 + * @deprecated + */ +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 + * @deprecated + */ +export function appendLog(keyword: string, data: string) { + localStorage.setItem(logs[logKeys.indexOf(keyword)][1], localStorage.getItem(logs[logKeys.indexOf(keyword)][1] + data)) +} +/** + * Saves a log to your device. + * @param keyword The identifier key for the log you want to save. + * @deprecated + */ +export function downloadLog(keyword: string) { + var d = JSON.parse(localStorage.getItem(logs[logKeys.indexOf(keyword)][1])) + const blob = new Blob([ printDRPD("", "", d as DRPD) ], {type: "text/json"}); + const link = document.createElement("a"); + link.href = window.URL.createObjectURL(blob); + var date: string = (d as DRPD).date + var filename: string = date[0] + date[1] + "_" + date[3] + date[4] + "_" + date[6] + date[7] + date[8] + date[9] + "_route.json" + link.download = `${filename}`; + link.click(); + link.remove(); +} +/** + * + * Clears all data from a log. + * @param keyword The identifier key for the log you want to reste + * @deprecated + */ +export function clearLog(keyword: string) { + localStorage.setItem(logs[logKeys.indexOf(keyword)][1], "---- " + logs[logKeys.indexOf(keyword)][3] + " ----" + logs[logKeys.indexOf(keyword)][5]) +} + +/** + * Generates an option to create a new log. + * + * Not used. + * @param i The slot number. Corresponds to an index in `logs`. + * @param scene The current scene. Not used. + * @param o The current game phase. Used to return to the previous menu. Not necessary anymore lol + * @returns A UI option. + * + * wow this function sucks + * @deprecated + */ +export function generateAddOption(i: integer, scene: BattleScene, o: TitlePhase) { + var op: OptionSelectItem = { + label: "Generate log " + logs[i][0], + handler: () => { + localStorage.setItem(logs[i][1], JSON.stringify(newDocument())) + o.callEnd(); + return true; + } + } + return op; +} /** * A sort function, used to sort csv columns. * @@ -1042,317 +1663,4 @@ export function setRow(keyword: string, newLine: string, floor: integer, slot: i } localStorage.setItem(logs[logKeys.indexOf(keyword)][1], data.slice(0, idx).join("\n") + "\n" + newLine + (data.slice(idx).length == 0 ? "" : "\n") + data.slice(idx).join("\n")); } -export function flagReset(scene: BattleScene, floor: integer = undefined) { - if (floor == undefined) - floor = scene.currentBattle.waveIndex; - if (localStorage.getItem(getLogID(scene)) == null) - localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) - var drpd = getDRPD(scene) - var wv = getWave(drpd, floor, scene) - wv.reload = true; - console.log(drpd) - localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) -} -export function flagResetIfExists(scene: BattleScene, floor: integer = undefined) { - if (floor == undefined) - floor = scene.currentBattle.waveIndex; - if (localStorage.getItem(getLogID(scene)) == null) - localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) - var drpd = getDRPD(scene) - var waveExists = false - for (var i = 0; i < drpd.waves.length; i++) { - if (drpd.waves[i] != undefined) { - if (drpd.waves[i].id == floor) { - waveExists = true; - } - } - } - if (!waveExists) return; - var wv = getWave(drpd, floor, scene) - wv.reload = true; - console.log(drpd) - localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) -} -/** - * Prints a DRPD as a string, for saving it to your device. - * @param inData The data to add on to. - * @param indent The indent string (just a bunch of spaces). - * @param drpd The `DRPD` to export. - * @returns `inData`, with all the DRPD's data appended to it. - * - * @see printWave - */ -export function printDRPD(inData: string, indent: string, drpd: DRPD): string { - console.log("Printing for sheet?: " + SheetsMode.value) - inData += indent + "{" - inData += "\n" + indent + " \"version\": \"" + drpd.version + "\"" - inData += ",\n" + indent + " \"title\": \"" + drpd.title + "\"" - inData += ",\n" + indent + " \"authors\": [\"" + drpd.authors.join("\", \"") + "\"]" - inData += ",\n" + indent + " \"date\": \"" + drpd.date + "\"" - if (drpd.waves) { - inData += ",\n" + indent + " \"waves\": [\n" - var isFirst = true - for (var i = 0; i < drpd.waves.length; i++) { - if (drpd.waves[i] != undefined && drpd.waves[i] != null) { - if (isFirst) { - isFirst = false; - } else { - inData += ",\n" - } - inData = printWave(inData, indent + " ", drpd.waves[i]) - } - } - } else { - inData += ",\n" + indent + " \"waves\": []" - } - inData += "\n" + indent + " ]\n" + indent + "}" - return inData; -} -/** - * Prints a wave as a string, for saving a DRPD to your device. - * @param inData The data to add on to. - * @param indent The indent string (just a bunch of spaces). - * @param wave The `Wave` to export. - * @returns `inData`, with all the wave's data appended to it. - * - * @see printDRPD - */ -function printWave(inData: string, indent: string, wave: Wave): string { - inData += indent + "{" - inData += "\n" + indent + " \"id\": " + wave.id + "" - inData += ",\n" + indent + " \"reload\": " + wave.reload + "" - inData += ",\n" + indent + " \"type\": \"" + wave.type + "\"" - inData += ",\n" + indent + " \"double\": " + wave.double + "" - var isFirst = true - if (wave.actions.length > 0) { - if (SheetsMode.value) { - inData += ",\n" + indent + " \"actions\": \"" - var isFirst = true - for (var i = 0; i < wave.actions.length; i++) { - if (wave.actions[i] != undefined) { - if (isFirst) { - isFirst = false; - } else { - inData += "CHAR(10)" - } - inData += wave.actions[i] - } - } - inData += "\"" - } else { - inData += ",\n" + indent + " \"actions\": [" - for (var i = 0; i < wave.actions.length; i++) { - if (wave.actions[i] != undefined) { - if (isFirst) { - isFirst = false; - } else { - inData += "," - } - inData += "\n " + indent + "\"" + wave.actions[i] + "\"" - } - } - if (!isFirst) inData += "\n" - inData += indent + " ]" - } - } else { - inData += ",\n" + indent + " \"actions\": [\"[No actions?]\"]" - } - inData += ",\n " + indent + "\"shop\": \"" + wave.shop + "\"" - inData += ",\n " + indent + "\"biome\": \"" + wave.biome + "\"" - if (wave.trainer) { - inData += ",\n " + indent + "\"trainer\": " - inData = printTrainer(inData, indent + " ", wave.trainer) - } - if (wave.pokemon) - if (wave.pokemon.length > 0) { - inData += ",\n " + indent + "\"pokemon\": [\n" - isFirst = true - for (var i = 0; i < wave.pokemon.length; i++) { - if (wave.pokemon[i] != undefined) { - if (isFirst) { - isFirst = false; - } else { - inData += ",\n" - } - inData = printPoke(inData, indent + " ", wave.pokemon[i]) - } - } - if (SheetsMode.value && wave.pokemon.length == 1) { - inData += "," + indent + " \n{" + indent + " \n}" - } - inData += "\n" + indent + " ]" - } - inData += "\n" + indent + "}" - return inData; -} -/** - * Prints a Pokemon as a string, for saving a DRPD to your device. - * @param inData The data to add on to. - * @param indent The indent string (just a bunch of spaces). - * @param wave The `PokeData` to export. - * @returns `inData`, with all the Pokemon's data appended to it. - * - * @see printDRPD - */ -function printPoke(inData: string, indent: string, pokemon: PokeData) { - inData += indent + "{" - inData += "\n" + indent + " \"id\": " + pokemon.id - inData += ",\n" + indent + " \"name\": \"" + pokemon.name + "\"" - inData += ",\n" + indent + " \"ability\": \"" + pokemon.ability + "\"" - inData += ",\n" + indent + " \"isHiddenAbility\": " + pokemon.isHiddenAbility - inData += ",\n" + indent + " \"passiveAbility\": \"" + pokemon.passiveAbility + "\"" - inData += ",\n" + indent + " \"nature\": \n" - inData = printNature(inData, indent + " ", pokemon.nature) - inData += ",\n" + indent + " \"gender\": \"" + pokemon.gender + "\"" - inData += ",\n" + indent + " \"rarity\": \"" + pokemon.rarity + "\"" - inData += ",\n" + indent + " \"captured\": " + pokemon.captured - inData += ",\n" + indent + " \"level\": " + pokemon.level - if (SheetsMode.value) { - inData += ",\n" + indent + " \"items\": \"" - var isFirst = true - for (var i = 0; i < pokemon.items.length; i++) { - if (pokemon.items[i] != undefined) { - if (isFirst) { - isFirst = false; - } else { - inData += "CHAR(10)" - } - inData += printItemNoNewline(inData, "", pokemon.items[i]) - } - } - inData += "\"" - } else { - if (pokemon.items.length > 0) { - inData += ",\n" + indent + " \"items\": [\n" - var isFirst = true - for (var i = 0; i < pokemon.items.length; i++) { - if (pokemon.items[i] != undefined) { - if (isFirst) { - isFirst = false; - } else { - inData += "," - } - inData = printItem(inData, indent + " ", pokemon.items[i]) - } - } - if (!isFirst) inData += "\n" - inData += indent + " ]" - } else { - inData += ",\n" + indent + " \"items\": []" - } - } - inData += ",\n" + indent + " \"ivs\": " - inData = printIV(inData, indent + " ", pokemon.ivs) - //inData += ",\n" + indent + " \"rarity\": " + pokemon.rarity - inData += "\n" + indent + "}" - return inData; -} -/** - * Prints a Nature as a string, for saving a DRPD to your device. - * @param inData The data to add on to. - * @param indent The indent string (just a bunch of spaces). - * @param wave The `NatureData` to export. - * @returns `inData`, with all the nature data appended to it. - * - * @see printDRPD - */ -function printNature(inData: string, indent: string, nature: NatureData) { - inData += indent + "{" - inData += "\n" + indent + " \"name\": \"" + nature.name + "\"" - inData += ",\n" + indent + " \"increased\": \"" + nature.increased + "\"" - inData += ",\n" + indent + " \"decreased\": \"" + nature.decreased + "\"" - inData += "\n" + indent + "}" - return inData; -} -/** - * Prints a Pokemon's IV data as a string, for saving a DRPD to your device. - * @param inData The data to add on to. - * @param indent The indent string (just a bunch of spaces). - * @param wave The `IVData` to export. - * @returns `inData`, with the IV data appended to it. - * - * @see printDRPD - */ -function printIV(inData: string, indent: string, iv: IVData) { - inData += "{" - inData += "\n" + indent + " \"hp\": " + iv.hp - inData += ",\n" + indent + " \"atk\": " + iv.atk - inData += ",\n" + indent + " \"def\": " + iv.def - inData += ",\n" + indent + " \"spatk\": " + iv.spatk - inData += ",\n" + indent + " \"spdef\": " + iv.spdef - inData += ",\n" + indent + " \"spe\": " + iv.speed - inData += "\n" + indent + "}" - return inData; -} -/** - * Prints a Trainer as a string, for saving a DRPD to your device. - * @param inData The data to add on to. - * @param indent The indent string (just a bunch of spaces). - * @param wave The `TrainerData` to export. - * @returns `inData`, with all the Trainer's data appended to it. - * - * @see printDRPD - */ -function printTrainer(inData: string, indent: string, trainer: TrainerData) { - inData += "{" - inData += "\n" + indent + " \"id\": \"" + trainer.id + "\"" - inData += ",\n" + indent + " \"name\": \"" + trainer.name + "\"" - inData += ",\n" + indent + " \"type\": \"" + trainer.type + "\"" - inData += "\n" + indent + "}" - return inData; -} -/** - * Prints an item as a string, for saving a DRPD to your device. - * @param inData The data to add on to. - * @param indent The indent string (just a bunch of spaces). - * @param wave The `ItemData` to export. - * @returns `inData`, with all the Item's data appended to it. - * - * @see printDRPD - */ -function printItem(inData: string, indent: string, item: ItemData) { - inData += indent + "{" - inData += "\n" + indent + " \"id\": \"" + item.id + "\"" - inData += ",\n" + indent + " \"name\": \"" + item.name + "\"" - inData += ",\n" + indent + " \"quantity\": " + item.quantity - inData += "\n" + indent + "}" - return inData; -} -/** - * Prints an item as a string, for saving a DRPD to your device. - * @param inData The data to add on to. - * @param indent The indent string (just a bunch of spaces). - * @param wave The `ItemData` to export. - * @returns `inData`, with all the Item's data appended to it. - * - * @see printDRPD - */ -function printItemNoNewline(inData: string, indent: string, item: ItemData) { - inData = "{\\\"id\\\": \\\"" + item.id + "\\\", \\\"name\\\": \\\"" + item.name + "\\\", \\\"quantity\\\": " + item.quantity + "}" - return inData; -} - - -function updateLog(drpd: DRPD): DRPD { - if (drpd.version == "1.0.0") { - drpd.version = "1.0.0a" - console.log("Updated to 1.0.0a - changed item IDs to strings") - for (var i = 0; i < drpd.waves.length; i++) { - if (drpd.waves[i] != undefined) { - if (drpd.waves[i].pokemon != undefined) { - for (var j = 0; j < drpd.waves[i].pokemon.length; j++) { - for (var k = 0; k < drpd.waves[i].pokemon[j].items.length; k++) { - drpd.waves[i].pokemon[j].items[k].id = drpd.waves[i].pokemon[j].items[k].id.toString() - } - } - } - } - } - for (var j = 0; j < drpd.starters.length; j++) { - for (var k = 0; k < drpd.starters[j].items.length; k++) { - drpd.starters[j].items[k].id = drpd.starters[j].items[k].id.toString() - } - } - } // 1.0.0 → 1.0.0a - return drpd; -} \ No newline at end of file +//#endregion \ No newline at end of file From 564285309e965f3ec58581e9b9c005ebb560baea Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Sun, 14 Jul 2024 15:45:31 -0400 Subject: [PATCH 71/94] Finish organizing Finished documenting logger.ts Numbered the sections in case I wanted to make a list of functions later Corrected wording of "Send in" to "Send out" --- src/logger.ts | 75 ++++++++++++++++++++++++++++++++++++--------------- src/phases.ts | 2 +- 2 files changed, 55 insertions(+), 22 deletions(-) diff --git a/src/logger.ts b/src/logger.ts index 9d6a03bc1bb..4eaef8b4b4d 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,4 +1,4 @@ -//#region Imports +//#region 00 Imports import i18next from "i18next"; import * as Utils from "./utils"; import Pokemon from "./field/pokemon"; @@ -20,7 +20,7 @@ import { randomUUID, UUID } from "node:crypto"; -// #region Variables +// #region 01 Variables // Value holders export const rarities = [] @@ -53,7 +53,7 @@ export const acceptedVersions = [ -//#region Downloading +//#region 02 Downloading /** * Saves a log to your device. * @param i The index of the log you want to save. @@ -94,7 +94,7 @@ export function downloadLogByIDToSheet(i: integer) { -// #region Log Handler +// #region 03 Log Handler /** * Stores logs. * Generate a new list with `getLogs()`. @@ -171,7 +171,7 @@ export function getMode(scene: BattleScene) { -// #region Utilities +// #region 04 Utilities /** * Pulls the current run's DRPD from LocalStorage using the run's RNG seed. @@ -294,7 +294,7 @@ export function enemyPokeName(scene: BattleScene, index: integer | Pokemon | Ene -// #region DRPD +// #region 05 DRPD /** * Updates a DRPD, checkings its version and making any necessary changes to it in order to keep it up to date. * @@ -442,14 +442,15 @@ export function printDRPD(inData: string, indent: string, drpd: DRPD): string { -// #region Wave +// #region 06 Wave /** - * A Wave is one individual battle in the run. Each group of ten waves has the same biome. + * A Wave is one individual battle in the run. + * Each group of ten waves has the same biome. */ export interface Wave { /** The wave number. Used to label the wave, detect and delete duplicates, and automatically sort `DRPD.waves[]`. */ id: integer, - /** Set to `true` if a reload is required to play this wave properly. Setting this value is the PITA I have ever dealt with. */ + /** Set to `true` if a reload is required to play this wave properly.Setting this value is the PITA I have ever dealt with. */ reload: boolean, /** * The specific type of wave. @@ -758,9 +759,9 @@ export function getWave(drpd: DRPD, floor: integer, scene: BattleScene): Wave { -// #region Pokémon +// #region 07 Pokémon /** - * A Pokémon. + * Stores information about a Pokémon. * * This data type is used in `DRPD.starters` to list the player's starting Pokémon, or in `Wave.pokemon` to list the opponent(s) in a wild encounter. */ @@ -919,7 +920,10 @@ export function logTeam(scene: BattleScene, floor: integer = undefined) { -// #region Nature +// #region 08 Nature +/** + * Information about a Pokémon's nature. + */ export interface NatureData { /** The display name of this nature. */ name: string, @@ -963,7 +967,10 @@ function printNature(inData: string, indent: string, nature: NatureData) { -// #region IVs +// #region 09 IVs +/** + * Information about a Pokémon's Individual Values (IVs). + */ export interface IVData { /** Influences a Pokémon's maximum health. */ hp: integer, @@ -1019,7 +1026,13 @@ function printIV(inData: string, indent: string, iv: IVData) { -// #region Trainer +// #region 10 Trainer +/** + * A Trainer that the player has to battle against. + * A Trainer will have 1-6 Pokémon in their party, depending on their difficulty. + * + * If the wave has a Trainer, their party is not logged, and `Wave.pokemon` is left empty. + */ export interface TrainerData { /** The trainer type's position in the Trainers enum. * @see Trainer @@ -1079,7 +1092,7 @@ function printTrainer(inData: string, indent: string, trainer: TrainerData) { -// #region Item +// #region 11 Item /** An item held by a Pokémon. Quantities and ownership are recorded at the start of the battle, and do not reflect items being used up or stolen. */ export interface ItemData { /** A type:key pair identifying the specific item. @@ -1140,7 +1153,7 @@ function printItemNoNewline(inData: string, indent: string, item: ItemData) { -//#region Manage Logs menu +//#region 12 Manage Logs /** * Sets the name, author, and [todo] label for a file. @@ -1264,7 +1277,10 @@ export function generateEditOption(scene: BattleScene, i: integer, saves: any, p -//#region Logging Events +//#region 13 Logging Events + +// * The functions in this section are sorted in alphabetical order. + /** * Logs the actions that the player took. * @@ -1291,6 +1307,12 @@ export function logActions(scene: BattleScene, floor: integer, action: string) { console.log(drpd) localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) } +/** + * Logs that a Pokémon was captured. + * @param scene The BattleScene. Used to get the log ID. + * @param floor The wave index to write to. + * @param target The Pokémon that you captured. + */ export function logCapture(scene: BattleScene, floor: integer, target: EnemyPokemon) { //if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) var drpd = getDRPD(scene) @@ -1319,12 +1341,12 @@ export function logPlayerTeam(scene: BattleScene) { localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) } /** - * Logs a wild Pokemon to a wave's data. + * Logs a wild Pokémon to a wave's data. * @param scene The BattleScene. Used to retrieve the log ID. * @param floor The wave index to write to. Defaults to the current floor. - * @param slot The slot to write to. In a single battle, 0 = the Pokemon that is out first. In a double battle, 0 = Left and 1 = Right. + * @param slot The slot to write to. In a single battle, 0 = the Pokémon that is out first. In a double battle, 0 = Left and 1 = Right. * @param pokemon The `EnemyPokemon` to store the data of. (Automatically converted via `exportPokemon`) - * @param encounterRarity The rarity tier of this Pokemon. If not specified, it calculates this automatically by searching the current biome's species pool. + * @param encounterRarity The rarity tier of this Pokémon. If not specified, it calculates this automatically by searching the current biome's species pool. */ export function logPokemon(scene: BattleScene, floor: integer = undefined, slot: integer, pokemon: EnemyPokemon, encounterRarity?: string) { if (floor == undefined) floor = scene.currentBattle.waveIndex @@ -1438,6 +1460,11 @@ export function logTrainer(scene: BattleScene, floor: integer = undefined) { +/** + * Flags a wave as a reset. + * @param scene The BattleScene. Used to get the log ID. + * @param floor The wave index to write to. + */ export function flagReset(scene: BattleScene, floor: integer = undefined) { if (floor == undefined) floor = scene.currentBattle.waveIndex; @@ -1449,6 +1476,11 @@ export function flagReset(scene: BattleScene, floor: integer = undefined) { console.log(drpd) localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) } +/** + * Flags a wave as a reset, unless this is your first time playing the wave. + * @param scene The BattleScene. Used to get the log ID. + * @param floor The wave index to write to. + */ export function flagResetIfExists(scene: BattleScene, floor: integer = undefined) { if (floor == undefined) floor = scene.currentBattle.waveIndex; @@ -1476,6 +1508,7 @@ export function flagResetIfExists(scene: BattleScene, floor: integer = undefined * Clears the action list for a wave. * @param scene The BattleScene. Used to get the log ID and trainer data. * @param floor The wave index to write to. Defaults to the current floor. + * @param softflag Rather than deleting everything right away, the actions will be cleared the next time we attempt to log an action. * * @see logActions */ @@ -1499,7 +1532,7 @@ export function resetWaveActions(scene: BattleScene, floor: integer = undefined, -//#region Deprecated +//#region 14 Deprecated /** * Writes data to a new line. diff --git a/src/phases.ts b/src/phases.ts index 0e2b774c406..0a9231b9870 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -5143,7 +5143,7 @@ export class SwitchPhase extends BattlePhase { LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, "Pre-switch " + (option == PartyOption.PASS_BATON ? "+ Baton" : "") + " " + LoggerTools.playerPokeName(this.scene, fieldIndex) + " to " + LoggerTools.playerPokeName(this.scene, slotIndex)) } if (LoggerTools.isFaintSwitch.value) { - LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, (option == PartyOption.PASS_BATON ? "Baton" : "Send") + " in " + LoggerTools.playerPokeName(this.scene, slotIndex)) + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, (option == PartyOption.PASS_BATON ? "Baton" : "Send") + " out " + LoggerTools.playerPokeName(this.scene, slotIndex)) } this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, fieldIndex, slotIndex, this.doReturn, option === PartyOption.PASS_BATON)); } From fbff640e2d9b19c6c2a5d8a1b732dcfadd478e36 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Sun, 14 Jul 2024 15:48:18 -0400 Subject: [PATCH 72/94] I forgot a couple --- public/images/ui/windows/volcano_panel.png | Bin 0 -> 4702 bytes public/images/ui/windows/wasteland_panel.png | Bin 0 -> 4610 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 public/images/ui/windows/volcano_panel.png create mode 100644 public/images/ui/windows/wasteland_panel.png diff --git a/public/images/ui/windows/volcano_panel.png b/public/images/ui/windows/volcano_panel.png new file mode 100644 index 0000000000000000000000000000000000000000..ea01f1e8aa412c34e9326eb573f178b6299cefdb GIT binary patch literal 4702 zcmeHLc|6o>7e^yAmdwrCxN1sSQkH8hC7KW=QI*@ka^Lqp^IreI|GfY3`#jI*Ip=%6=bZ0zo{2nXY#_jk;^pAr z5IB8G&y<5>8wL7Kf^UcZHmC&egZ^=PnHuPD(3^jp;NaLPep>H@x!;wUj|2D3M|Sbd zsi;)cANsm2ntS_+yS&+&I(*&cxq8n}xBWU28a14G+_O!y(TE7#zr@mM&ft=vge?(=^P9gOhV>eRA7E+QtM&BJN zlf6UGZ9VttkC=xn+CtT`R90p^AQP2pjpy5?yYsK(-|b+% z^S!Ts4Ga!;Xj@$j^u94>Dtz)xY_@5Y5Ng37U5Z8jc$rKdA)C6}1rZJ0?U-|`b8vM4 z100DLB#9X+9#ig8R{wZ${7J5{MVz6;nW&_41&i6hM$6mV)9a>ld+xP0W@X_tt|-pl z{`sD$Ot0>nM}G$b8Id9p$G#3b4eYAexxRisDq{D=@ryP2UO!pA71+TqobkqR$alhb zY;I-gQ5Or0M~y3!}sq}Zfj01;GXP3OJi=4~M!3$na!Fc{2fiuW%2H|v|U?Z=eE z|C5M~kHKm#7hZ~t?~*ohXfj=J@kA5?Jb_jv@dQaV6tvPxOV-P^EbvA%?_Bl1r*YE^ zsT~&9(4OtawlS@lRh0}M^my#v{@k)_hW1RC zSwy3#s=ok`LX}s?>&hpBdM&LUmfZIV%p>}oSz4cZOusrsSEYBjX*AQ{SQ074Gnz~qH6x+@(sRKP`;7+e%J<{sehJlEC*T7WsjpHCqc7u#7*)YgvEK&T`K zF0x3J+I#m(+z$b{4B;T@agXwmbnHE<6siA11uoX=W1%l|HxIMdV16_EZ660Y<914HxIytQOj;(sE1ls z9bh3q=dy56blWsnGYi(3W+afVH@y5dh{dD^UCk2>t(w5B{cM_kOU@YJnv-jZkKZO)KnS($F_T>rAi~yO=<}o(%qTk@ z`=7lOwqQu#{X%_MgQNQ4t(=Yxj6$1P>V04DsI($P*x8K`{X!V-H{ml2LN@nTlOxm)~i5w`reZi)R8J=o664h~H6k z8H^=bi<7Dx;FxI-7UwV0XiTQQn-sWq%>;{o*Pk#E4-!m6z$vuzy|=-&9w8*^1<~QC zG=L53%%x-R+^)6{T>A`Z%ye?XJut%PL`W!5MU?+T{2#K2w#0AL*FQC0!AOJ|8BtE} zc(T?#ZeA&kQ;2&ee>1te%UZ>ds{A|9oH4&@-?3VRKz3Rr0dS?0Z9Y=liaYzBP(xd|7S&gx=Qs z2&`~er@~z8h!fRw#`*$q^<&ejV4-CFlC0t`vV#?1txyg*{n=cw?#(E7h6}L%;S9R_ z*7Zt4MwJ4l4KNgamSmgPm+jj8vzbvB2pSqpVv;)iKA78-%}-~Js9jY%2+Dn-+$Lf3 z69Q2^!fmSH9yDS!M&8>H-&97oepkYIpzD6mu4SF3DrXpExdG_l`xTLgFy&gRtoovD zps7g_xNkrX&ro}Sx3&q8YtkRwp<3|Q^6C$cTWf;jM%tB)c)Dwr`tQq4N!E=4uF+WZ zE=Wt4cVA7M3_uCG+Qhxn%K@mshG6Sq;d#xkicAG45x(B4u5dD_4I9T*a1KxfjS+oi zRAO>Ky3hisJwf*jf38&POSdBomnLXmi-oOB+VnsGcx94Fo+24RX;~Vxy8B*(ZBL8T z;%-iE=+wL*^aF5-rYUp1)XC^Ilkf`30kC1wr%ARYS^h}l2d{I~z;vLbkkcnLc=jAt zb>Ol9!C}$Lr291q6ql@(#r8eNI+MH4@v?WHJM&E*^qbOlfE>X0@4r4-M^PHUR2LG@ z=t#+(`8#`1=Tdfl8C+e0;B+_^t2uA?VP|PCX}LyRPzYrsAuEG zTyx9%{~^|EVoWF;`GArw=J3H=3tiCIQy;M6^3M@m!)GX*A^sUY4NhRBQ4wfDw&|}b zyJ6w2&qf`QTYGiVA>IQ;*2kd%7GQ}sSiI|+%KN?GVkk7Dyc~Bk0!)xb#gM3^^}F8S zh(xl5W)vlV39v=dSkOE_dR$5p%nmvpn?s$??OF3vidhyL9@dblfjZ09 z`Oj+&ZSSUMA0>DmOSQrW5~~$cR^JnppND<~n**DcS^JR1@@r}xzSgwl^D*ThrgO0f zh?;dS>(&32@2Q%JuAleykt+g`gA(<4Vrw&3x=kLpZe908&(y{TV8?TcB$G2i= zd~;2Xa|ba*=V0q~m{TK?o=ib2Oh=>}d*TE{EWa((6C3Ei6?u&iSj`o?XV-*K63%pS zFkwGQp)n4l*%(6rJ86;olLRVa`}R9Qv4(bqeiaKd%i%H?tQK?0EF73g;X&rh`V1tk!hxbe`px3_1-Y7;tS-+m)pSF(vruX`+e zFc@cLU8B+4y4*=K6MOI4d4-Krr12mzLXgP6`*`(}sPr#(G-8cYB_ttQm`73R4_V_z z9tC;k2QB!>p9WHn9YN7AYl0OS0{Qi*oYx=1>FRc@3sBZMB~aHUtsKJ(p&yJmPMrS literal 0 HcmV?d00001 diff --git a/public/images/ui/windows/wasteland_panel.png b/public/images/ui/windows/wasteland_panel.png new file mode 100644 index 0000000000000000000000000000000000000000..e311290ad4f726fb238adc7103f5e7c9b02e7b84 GIT binary patch literal 4610 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 literal 0 HcmV?d00001 From 2e39c15df5678c00b9b1b6db0028a93f16951ffd Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Sun, 14 Jul 2024 16:17:36 -0400 Subject: [PATCH 73/94] Push all changes - Make sure every file is 100% up to date - Label phases with regions so I can see where phases are in the sidebar --- src/data/move.ts | 2 +- src/phases.ts | 479 ++++++++++++++++++++++++++++++++++++- src/ui/fight-ui-handler.ts | 46 +++- 3 files changed, 523 insertions(+), 4 deletions(-) diff --git a/src/data/move.ts b/src/data/move.ts index d13c936cc22..2a8943544cf 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -4717,7 +4717,7 @@ export class AddTypeAttr extends MoveEffectAttr { } apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const types = target.getTypes().slice(0, 2).filter(t => t !== Type.UNKNOWN); // TODO: Figure out some way to actually check if another version of this effect is already applied + const types = target.getTypes().slice(0, 2).filter(t => t !== Type.UNKNOWN).map(t => t as Type); // TODO: Figure out some way to actually check if another version of this effect is already applied types.push(this.type); target.summonData.types = types; target.updateInfo(); diff --git a/src/phases.ts b/src/phases.ts index 0a9231b9870..9784d99473c 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -1,3 +1,4 @@ +//#region 00 Imports import BattleScene, { bypassLogin } from "./battle-scene"; import { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove, MoveResult, DamageResult, FieldPosition, HitResult, TurnMove } from "./field/pokemon"; import * as Utils from "./utils"; @@ -78,6 +79,15 @@ import { Session } from "inspector"; const { t } = i18next; + + +//#endregion + + + + + +//#region 01 Uncategorized export function parseSlotData(slotId: integer): SessionSaveData { var S = localStorage.getItem(`sessionData${slotId ? slotId : ""}_${loggedInUser.username}`) if (S == null) { @@ -232,7 +242,13 @@ export function parseSlotData(slotId: integer): SessionSaveData { Save.description += " (" + getBiomeName(Save.arena.biome) + " " + Save.waveIndex + ")" return Save; } +//#endregion + + + + +//#region 02 LoginPhase export class LoginPhase extends Phase { private showText: boolean; @@ -329,7 +345,13 @@ export class LoginPhase extends Phase { handleTutorial(this.scene, Tutorial.Intro).then(() => super.end()); } } +//#endregion + + + + +//#region 03 TitlePhase export class TitlePhase extends Phase { private loaded: boolean; private lastSessionData: SessionSaveData; @@ -845,7 +867,13 @@ export class TitlePhase extends Phase { super.end(); } } +//#endregion + + + + +//#region 04 Unavailable export class UnavailablePhase extends Phase { constructor(scene: BattleScene) { super(scene); @@ -858,7 +886,13 @@ export class UnavailablePhase extends Phase { }); } } +//#endregion + + + + +//#region 05 ReloadSession export class ReloadSessionPhase extends Phase { private systemDataStr: string; @@ -893,7 +927,13 @@ export class ReloadSessionPhase extends Phase { }); } } +//#endregion + + + + +//#region 06 OutdatedPhase export class OutdatedPhase extends Phase { constructor(scene: BattleScene) { super(scene); @@ -903,7 +943,13 @@ export class OutdatedPhase extends Phase { this.scene.ui.setMode(Mode.OUTDATED); } } +//#endregion + + + + +//#region 07 SelectGender export class SelectGenderPhase extends Phase { constructor(scene: BattleScene) { super(scene); @@ -943,7 +989,13 @@ export class SelectGenderPhase extends Phase { super.end(); } } +//#endregion + + + + +//#region 08 SelectChallenge export class SelectChallengePhase extends Phase { constructor(scene: BattleScene) { super(scene); @@ -957,7 +1009,13 @@ export class SelectChallengePhase extends Phase { this.scene.ui.setMode(Mode.CHALLENGE_SELECT); } } +//#endregion + + + + +//#region 09 SelectStarter export class SelectStarterPhase extends Phase { constructor(scene: BattleScene) { @@ -1044,7 +1102,13 @@ export class SelectStarterPhase extends Phase { }); } } +//#endregion + + + + +//#region 10 BattlePhase export class BattlePhase extends Phase { constructor(scene: BattleScene) { super(scene); @@ -1088,9 +1152,14 @@ export class BattlePhase extends Phase { }); } } +//#endregion + + + + +//#region 11 FieldPhase type PokemonFunc = (pokemon: Pokemon) => void; - export abstract class FieldPhase extends BattlePhase { getOrder(): BattlerIndex[] { const playerField = this.scene.getPlayerField().filter(p => p.isActive()) as Pokemon[]; @@ -1126,7 +1195,13 @@ export abstract class FieldPhase extends BattlePhase { field.forEach(pokemon => func(pokemon)); } } +//#endregion + + + + +//#region 12 PokemonPhase export abstract class PokemonPhase extends FieldPhase { protected battlerIndex: BattlerIndex | integer; public player: boolean; @@ -1151,7 +1226,13 @@ export abstract class PokemonPhase extends FieldPhase { return this.scene.getField()[this.battlerIndex]; } } +//#endregion + + + + +//#region 13 PartyMembPkmn export abstract class PartyMemberPokemonPhase extends FieldPhase { protected partyMemberIndex: integer; protected fieldIndex: integer; @@ -1175,7 +1256,13 @@ export abstract class PartyMemberPokemonPhase extends FieldPhase { return this.getParty()[this.partyMemberIndex]; } } +//#endregion + + + + +//#region 14 PlayerPartyMemberPokemonPhase export abstract class PlayerPartyMemberPokemonPhase extends PartyMemberPokemonPhase { constructor(scene: BattleScene, partyMemberIndex: integer) { super(scene, partyMemberIndex, true); @@ -1185,7 +1272,13 @@ export abstract class PlayerPartyMemberPokemonPhase extends PartyMemberPokemonPh return super.getPokemon() as PlayerPokemon; } } +//#endregion + + + + +//#region 15 EnemyPartyMemberPokemonPhase export abstract class EnemyPartyMemberPokemonPhase extends PartyMemberPokemonPhase { constructor(scene: BattleScene, partyMemberIndex: integer) { super(scene, partyMemberIndex, false); @@ -1195,7 +1288,13 @@ export abstract class EnemyPartyMemberPokemonPhase extends PartyMemberPokemonPha return super.getPokemon() as EnemyPokemon; } } +//#endregion + + + + +//#region 16 EncounterPhase export class EncounterPhase extends BattlePhase { private loaded: boolean; @@ -1555,7 +1654,13 @@ export class EncounterPhase extends BattlePhase { return false; } } +//#endregion + + + + +//#region 17 NextEncounterPhase export class NextEncounterPhase extends EncounterPhase { constructor(scene: BattleScene) { super(scene); @@ -1599,7 +1704,13 @@ export class NextEncounterPhase extends EncounterPhase { }); } } +//#endregion + + + + +//#region 18 NewBiomeEncounterPhase export class NewBiomeEncounterPhase extends NextEncounterPhase { constructor(scene: BattleScene) { super(scene); @@ -1633,7 +1744,13 @@ export class NewBiomeEncounterPhase extends NextEncounterPhase { }); } } +//#endregion + + + + +//#region 19 PostSummonPhase export class PostSummonPhase extends PokemonPhase { constructor(scene: BattleScene, battlerIndex: BattlerIndex) { super(scene, battlerIndex); @@ -1651,7 +1768,13 @@ export class PostSummonPhase extends PokemonPhase { applyPostSummonAbAttrs(PostSummonAbAttr, pokemon).then(() => this.end()); } } +//#endregion + + + + +//#region 20 SelectBiomePh. export class SelectBiomePhase extends BattlePhase { constructor(scene: BattleScene) { super(scene); @@ -1725,7 +1848,13 @@ export class SelectBiomePhase extends BattlePhase { return this.scene.generateRandomBiome(this.scene.currentBattle.waveIndex); } } +//#endregion + + + + +//#region 21 SwitchBiomePhase export class SwitchBiomePhase extends BattlePhase { private nextBiome: Biome; @@ -1786,7 +1915,13 @@ export class SwitchBiomePhase extends BattlePhase { }); } } +//#endregion + + + + +//#region 22 SummonPhase export class SummonPhase extends PartyMemberPokemonPhase { private loaded: boolean; @@ -1969,7 +2104,13 @@ export class SummonPhase extends PartyMemberPokemonPhase { super.end(); } } +//#endregion + + + + +//#region 23 SwitchSummonPhase export class SwitchSummonPhase extends SummonPhase { private slotIndex: integer; private doReturn: boolean; @@ -2111,7 +2252,13 @@ export class SwitchSummonPhase extends SummonPhase { this.scene.unshiftPhase(new PostSummonPhase(this.scene, this.getPokemon().getBattlerIndex())); } } +//#endregion + + + + +//#region 24 ReturnPhase export class ReturnPhase extends SwitchSummonPhase { constructor(scene: BattleScene, fieldIndex: integer) { super(scene, fieldIndex, -1, true, false); @@ -2134,7 +2281,13 @@ export class ReturnPhase extends SwitchSummonPhase { this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger); } } +//#endregion + + + + +//#region 25 ShowTrainerPhase export class ShowTrainerPhase extends BattlePhase { constructor(scene: BattleScene) { super(scene); @@ -2155,7 +2308,13 @@ export class ShowTrainerPhase extends BattlePhase { }); } } +//#endregion + + + + +//#region 26 ToggleDoublePositionPhase export class ToggleDoublePositionPhase extends BattlePhase { private double: boolean; @@ -2183,7 +2342,13 @@ export class ToggleDoublePositionPhase extends BattlePhase { } } } +//#endregion + + + + +//#region 27 CheckSwitchPhase export class CheckSwitchPhase extends BattlePhase { protected fieldIndex: integer; protected useName: boolean; @@ -2272,7 +2437,13 @@ export class CheckSwitchPhase extends BattlePhase { }); } } +//#endregion + + + + +//#region 28 SummonMissingPhase export class SummonMissingPhase extends SummonPhase { constructor(scene: BattleScene, fieldIndex: integer) { super(scene, fieldIndex); @@ -2283,7 +2454,13 @@ export class SummonMissingPhase extends SummonPhase { this.scene.time.delayedCall(250, () => this.summon()); } } +//#endregion + + + + +//#region 29 LevelCapPhase export class LevelCapPhase extends FieldPhase { constructor(scene: BattleScene) { super(scene); @@ -2299,7 +2476,13 @@ export class LevelCapPhase extends FieldPhase { }); } } +//#endregion + + + + +//#region 30 TurnInitPhase export class TurnInitPhase extends FieldPhase { constructor(scene: BattleScene) { super(scene); @@ -2421,7 +2604,13 @@ export class TurnInitPhase extends FieldPhase { this.end(); } } +//#endregion + + + + +//#region 31 CommandPhase export class CommandPhase extends FieldPhase { protected fieldIndex: integer; @@ -2674,7 +2863,13 @@ export class CommandPhase extends FieldPhase { this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end()); } } +//#endregion + + + + +//#region 32 EnemyCommandPhase export class EnemyCommandPhase extends FieldPhase { protected fieldIndex: integer; @@ -2734,7 +2929,13 @@ export class EnemyCommandPhase extends FieldPhase { this.end(); } } +//#endregion + + + + +//#region 33 SelectTargetPhase export class SelectTargetPhase extends PokemonPhase { constructor(scene: BattleScene, fieldIndex: integer) { super(scene, fieldIndex); @@ -2760,7 +2961,13 @@ export class SelectTargetPhase extends PokemonPhase { }); } } +//#endregion + + + + +//#region 34 TurnStartPhase export class TurnStartPhase extends FieldPhase { constructor(scene: BattleScene) { super(scene); @@ -3050,7 +3257,13 @@ export class TurnStartPhase extends FieldPhase { this.end(); } } +//#endregion + + + + +//#region 35 BerryPhase /** The phase after attacks where the pokemon eat berries */ export class BerryPhase extends FieldPhase { start() { @@ -3093,7 +3306,13 @@ export class BerryPhase extends FieldPhase { this.end(); } } +//#endregion + + + + +//#region 36 TurnEndPhase export class TurnEndPhase extends FieldPhase { constructor(scene: BattleScene) { super(scene); @@ -3151,7 +3370,13 @@ export class TurnEndPhase extends FieldPhase { this.end(); } } +//#endregion + + + + +//#region 37 BattleEndPhase export class BattleEndPhase extends BattlePhase { start() { super.start(); @@ -3202,7 +3427,13 @@ export class BattleEndPhase extends BattlePhase { this.scene.updateModifiers().then(() => this.end()); } } +//#endregion + + + + +//#region 38 NewBattlePhase export class NewBattlePhase extends BattlePhase { start() { super.start(); @@ -3212,7 +3443,13 @@ export class NewBattlePhase extends BattlePhase { this.end(); } } +//#endregion + + + + +//#region 39 CommonAnimPhase export class CommonAnimPhase extends PokemonPhase { private anim: CommonAnim; private targetIndex: integer; @@ -3234,7 +3471,13 @@ export class CommonAnimPhase extends PokemonPhase { }); } } +//#endregion + + + + +//#region 40 MovePhase export class MovePhase extends BattlePhase { public pokemon: Pokemon; public move: PokemonMove; @@ -3530,7 +3773,13 @@ export class MovePhase extends BattlePhase { super.end(); } } +//#endregion + + + + +//#region 41 MoveEffectPhase export class MoveEffectPhase extends PokemonPhase { public move: PokemonMove; protected targets: BattlerIndex[]; @@ -3837,7 +4086,13 @@ export class MoveEffectPhase extends PokemonPhase { return new MoveEffectPhase(this.scene, this.battlerIndex, this.targets, this.move); } } +//#endregion + + + + +//#region 42 MoveEndPhase export class MoveEndPhase extends PokemonPhase { constructor(scene: BattleScene, battlerIndex: BattlerIndex) { super(scene, battlerIndex); @@ -3856,7 +4111,13 @@ export class MoveEndPhase extends PokemonPhase { this.end(); } } +//#endregion + + + + +//#region 43 MoveAnimTestPhase export class MoveAnimTestPhase extends BattlePhase { private moveQueue: Moves[]; @@ -3894,7 +4155,13 @@ export class MoveAnimTestPhase extends BattlePhase { }); } } +//#endregion + + + + +//#region 44 ShowAbilityPhase export class ShowAbilityPhase extends PokemonPhase { private passive: boolean; @@ -3917,7 +4184,13 @@ export class ShowAbilityPhase extends PokemonPhase { this.end(); } } +//#endregion + + + + +//#region 45 StatChangePhase export class StatChangePhase extends PokemonPhase { private stats: BattleStat[]; private selfTarget: boolean; @@ -4116,7 +4389,13 @@ export class StatChangePhase extends PokemonPhase { return messages; } } +//#endregion + + + + +//#region 46 WeatherEffectPhase export class WeatherEffectPhase extends CommonAnimPhase { public weather: Weather; @@ -4175,7 +4454,13 @@ export class WeatherEffectPhase extends CommonAnimPhase { }); } } +//#endregion + + + + +//#region 47 ObtainStatusEffectPhase export class ObtainStatusEffectPhase extends PokemonPhase { private statusEffect: StatusEffect; private cureTurn: integer; @@ -4214,7 +4499,13 @@ export class ObtainStatusEffectPhase extends PokemonPhase { this.end(); } } +//#endregion + + + + +//#region 48 PostTurnStatusEffectPhase export class PostTurnStatusEffectPhase extends PokemonPhase { constructor(scene: BattleScene, battlerIndex: BattlerIndex) { super(scene, battlerIndex); @@ -4256,7 +4547,13 @@ export class PostTurnStatusEffectPhase extends PokemonPhase { } } } +//#endregion + + + + +//#region 49 MessagePhase export class MessagePhase extends Phase { private text: string; private callbackDelay: integer; @@ -4292,7 +4589,13 @@ export class MessagePhase extends Phase { super.end(); } } +//#endregion + + + + +//#region 50 DamagePhase export class DamagePhase extends PokemonPhase { private amount: integer; private damageResult: DamageResult; @@ -4392,7 +4695,13 @@ export class DamagePhase extends PokemonPhase { super.end(); } } +//#endregion + + + + +//#region 51 FaintPhase export class FaintPhase extends PokemonPhase { private preventEndure: boolean; @@ -4541,7 +4850,13 @@ export class FaintPhase extends PokemonPhase { return false; } } +//#endregion + + + + +//#region 52 VictoryPhase export class VictoryPhase extends PokemonPhase { constructor(scene: BattleScene, battlerIndex: BattlerIndex) { super(scene, battlerIndex); @@ -4678,7 +4993,13 @@ export class VictoryPhase extends PokemonPhase { this.end(); } } +//#endregion + + + + +//#region 53 TrainerVictoryPhase export class TrainerVictoryPhase extends BattlePhase { constructor(scene: BattleScene) { super(scene); @@ -4731,7 +5052,13 @@ export class TrainerVictoryPhase extends BattlePhase { this.showEnemyTrainer(); } } +//#endregion + + + + +//#region 54 MoneyRewardPhase export class MoneyRewardPhase extends BattlePhase { private moneyMultiplier: number; @@ -4759,7 +5086,13 @@ export class MoneyRewardPhase extends BattlePhase { this.scene.ui.showText(message, null, () => this.end(), null, true); } } +//#endregion + + + + +//#region 55 ModifierRewardPhase export class ModifierRewardPhase extends BattlePhase { protected modifierType: ModifierType; @@ -4785,7 +5118,13 @@ export class ModifierRewardPhase extends BattlePhase { }); } } +//#endregion + + + + +//#region 56 GameOverModifierRewardPhase export class GameOverModifierRewardPhase extends ModifierRewardPhase { constructor(scene: BattleScene, modifierTypeFunc: ModifierTypeFunc) { super(scene, modifierTypeFunc); @@ -4807,7 +5146,13 @@ export class GameOverModifierRewardPhase extends ModifierRewardPhase { }); } } +//#endregion + + + + +//#region 57 RibbonModifierRewardPhase export class RibbonModifierRewardPhase extends ModifierRewardPhase { private species: PokemonSpecies; @@ -4830,7 +5175,13 @@ export class RibbonModifierRewardPhase extends ModifierRewardPhase { }); } } +//#endregion + + + + +//#region 58 GameOverPhase export class GameOverPhase extends BattlePhase { private victory: boolean; private firstRibbons: PokemonSpecies[] = []; @@ -5004,7 +5355,13 @@ export class GameOverPhase extends BattlePhase { } } } +//#endregion + + + + +//#region 59 EndCardPhase export class EndCardPhase extends Phase { public endCard: Phaser.GameObjects.Image; public text: Phaser.GameObjects.Text; @@ -5039,7 +5396,13 @@ export class EndCardPhase extends Phase { }); } } +//#endregion + + + + +//#region 60 UnlockPhase export class UnlockPhase extends Phase { private unlockable: Unlockables; @@ -5061,7 +5424,13 @@ export class UnlockPhase extends Phase { }); } } +//#endregion + + + + +//#region 61 PostGameOverPhase export class PostGameOverPhase extends Phase { private endCardPhase: EndCardPhase; @@ -5103,7 +5472,13 @@ export class PostGameOverPhase extends Phase { } } } +//#endregion + + + + +//#region 62 SwitchPhase export class SwitchPhase extends BattlePhase { protected fieldIndex: integer; private isModal: boolean; @@ -5152,7 +5527,13 @@ export class SwitchPhase extends BattlePhase { }, PartyUiHandler.FilterNonFainted); } } +//#endregion + + + + +//#region 63 ExpPhase export class ExpPhase extends PlayerPartyMemberPokemonPhase { private expValue: number; @@ -5180,7 +5561,13 @@ export class ExpPhase extends PlayerPartyMemberPokemonPhase { }, null, true); } } +//#endregion + + + + +//#region 64 ShowPartyExpBarPhase export class ShowPartyExpBarPhase extends PlayerPartyMemberPokemonPhase { private expValue: number; @@ -5229,7 +5616,13 @@ export class ShowPartyExpBarPhase extends PlayerPartyMemberPokemonPhase { } } +//#endregion + + + + +//#region 65 HidePartyExpBarPhase export class HidePartyExpBarPhase extends BattlePhase { constructor(scene: BattleScene) { super(scene); @@ -5241,7 +5634,13 @@ export class HidePartyExpBarPhase extends BattlePhase { this.scene.partyExpBar.hide().then(() => this.end()); } } +//#endregion + + + + +//#region 66 LevelUpPhase export class LevelUpPhase extends PlayerPartyMemberPokemonPhase { private lastLevel: integer; private level: integer; @@ -5290,7 +5689,13 @@ export class LevelUpPhase extends PlayerPartyMemberPokemonPhase { } } } +//#endregion + + + + +//#region 67 LearnMovePhase export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { private moveId: Moves; @@ -5384,7 +5789,13 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { } } } +//#endregion + + + + +//#region 68 PokemonHealPhase export class PokemonHealPhase extends CommonAnimPhase { private hpHealed: integer; private message: string; @@ -5478,7 +5889,13 @@ export class PokemonHealPhase extends CommonAnimPhase { } } } +//#endregion + + + + +//#region 69 AttemptCapturePhase export class AttemptCapturePhase extends PokemonPhase { private pokeballType: PokeballType; private pokeball: Phaser.GameObjects.Sprite; @@ -5752,7 +6169,13 @@ export class AttemptCapturePhase extends PokemonPhase { }); } } +//#endregion + + + + +//#region 70 AttemptRunPhase export class AttemptRunPhase extends PokemonPhase { constructor(scene: BattleScene, fieldIndex: integer) { super(scene, fieldIndex); @@ -5799,7 +6222,13 @@ export class AttemptRunPhase extends PokemonPhase { this.end(); } } +//#endregion + + + + +//#region 71 SelectModifierPhase export class SelectModifierPhase extends BattlePhase { private rerollCount: integer; private modifierTiers: ModifierTier[]; @@ -6023,7 +6452,13 @@ export class SelectModifierPhase extends BattlePhase { return this.scene.addModifier(modifier, false, true); } } +//#endregion + + + + +//#region 72 EggLapsePhase export class EggLapsePhase extends Phase { constructor(scene: BattleScene) { super(scene); @@ -6052,7 +6487,13 @@ export class EggLapsePhase extends Phase { this.end(); } } +//#endregion + + + + +//#region 73 AddEnemyBuffModifierPhase export class AddEnemyBuffModifierPhase extends Phase { constructor(scene: BattleScene) { super(scene); @@ -6073,7 +6514,13 @@ export class AddEnemyBuffModifierPhase extends Phase { this.scene.updateModifiers(false, true).then(() => this.end()); } } +//#endregion + + + + +//#region 74 PartyStatusCurePhase /** * Cures the party of all non-volatile status conditions, shows a message * @param {BattleScene} scene The current scene @@ -6116,7 +6563,13 @@ export class PartyStatusCurePhase extends BattlePhase { this.end(); } } +//#endregion + + + + +//#region 75 PartyHealPhase export class PartyHealPhase extends BattlePhase { private resumeBgm: boolean; @@ -6153,7 +6606,13 @@ export class PartyHealPhase extends BattlePhase { }); } } +//#endregion + + + + +//#region 76 ShinySparklePhase export class ShinySparklePhase extends PokemonPhase { constructor(scene: BattleScene, battlerIndex: BattlerIndex) { super(scene, battlerIndex); @@ -6166,7 +6625,13 @@ export class ShinySparklePhase extends PokemonPhase { this.scene.time.delayedCall(1000, () => this.end()); } } +//#endregion + + + + +//#region 77 ScanIvsPhase export class ScanIvsPhase extends PokemonPhase { private shownIvs: integer; @@ -6201,7 +6666,13 @@ export class ScanIvsPhase extends PokemonPhase { }); } } +//#endregion + + + + +//#region 78 TrainerMessageTestPhase export class TrainerMessageTestPhase extends BattlePhase { private trainerTypes: TrainerType[]; @@ -6237,7 +6708,13 @@ export class TrainerMessageTestPhase extends BattlePhase { this.end(); } } +//#endregion + + + + +//#region 79 TestMessagePhase export class TestMessagePhase extends MessagePhase { constructor(scene: BattleScene, message: string) { super(scene, message, null, true); diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index d2852ef965e..54024bafc67 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -25,6 +25,7 @@ import { BattlerTagType } from "#app/enums/battler-tag-type.js"; import { TempBattleStat } from "#app/data/temp-battle-stat.js"; import { StatusEffect } from "#app/data/status-effect.js"; import { BattleStat } from "#app/data/battle-stat.js"; +import { PokemonMultiHitModifierType } from "#app/modifier/modifier-type.js"; export default class FightUiHandler extends UiHandler { private movesContainer: Phaser.GameObjects.Container; @@ -661,6 +662,13 @@ export default class FightUiHandler extends UiHandler { maxHits = minHits } } + var h = user.getHeldItems() + for (var i = 0; i < h.length; i++) { + if (h[i].type instanceof PokemonMultiHitModifierType) { + minHits += h[i].getStackCount() + maxHits += h[i].getStackCount() + } + } dmgLow = out[0] * minHits dmgHigh = out[1] * maxHits /* @@ -1130,8 +1138,42 @@ export function calcDamage(scene: BattleScene, user: PlayerPokemon, target: Poke // dmgLow = (((2*user.level/5 + 2) * power * myAtk / theirDef)/50 + 2) * 0.85 * modifiers // dmgHigh = (((2*user.level/5 + 2) * power * myAtkC / theirDefC)/50 + 2) * 1.5 * modifiers var out = this.simulateAttack(scene, user, target, move.getMove()) - dmgLow = out[0] - dmgHigh = out[1] + var minHits = 1 + var maxHits = 1 + var mh = move.getMove().getAttrs(MoveData.MultiHitAttr) + for (var i = 0; i < mh.length; i++) { + var mh2 = mh[i] as MoveData.MultiHitAttr + switch (mh2.multiHitType) { + case MoveData.MultiHitType._2: + minHits = 2; + maxHits = 2; + case MoveData.MultiHitType._2_TO_5: + minHits = 2; + maxHits = 5; + case MoveData.MultiHitType._3: + minHits = 3; + maxHits = 3; + case MoveData.MultiHitType._10: + minHits = 10; + maxHits = 10; + case MoveData.MultiHitType.BEAT_UP: + const party = user.isPlayer() ? user.scene.getParty() : user.scene.getEnemyParty(); + // No status means the ally pokemon can contribute to Beat Up + minHits = party.reduce((total, pokemon) => { + return total + (pokemon.id === user.id ? 1 : pokemon?.status && pokemon.status.effect !== StatusEffect.NONE ? 0 : 1); + }, 0); + maxHits = minHits + } + } + var h = user.getHeldItems() + for (var i = 0; i < h.length; i++) { + if (h[i].type instanceof PokemonMultiHitModifierType) { + minHits += h[i].getStackCount() + maxHits += h[i].getStackCount() + } + } + dmgLow = out[0] * minHits + dmgHigh = out[1] * maxHits /* if (user.hasAbility(Abilities.PARENTAL_BOND)) { // Second hit deals 0.25x damage From 29d2119cf1e13ea2e917149595460611e5d29cc2 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Sun, 14 Jul 2024 16:27:01 -0400 Subject: [PATCH 74/94] Only flag slot number of duplicate species in your party --- src/logger.ts | 43 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/src/logger.ts b/src/logger.ts index 4eaef8b4b4d..2f495ae30ce 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -14,6 +14,7 @@ import Trainer from "./field/trainer"; import { Species } from "./enums/species"; import { GameMode, GameModes } from "./game-mode"; import { randomUUID, UUID } from "node:crypto"; +import PokemonSpecies from "./data/pokemon-species"; //#endregion @@ -264,27 +265,57 @@ function checkForPokeInBiome(species: Species, pool: (Species | SpeciesTree)[]): /** * Formats a Pokemon in the player's party. + * + * If multiple Pokemon of the same species exist in the party, it will specify which slot they are in. * @param scene The BattleScene, for getting the player's party. * @param index The slot index. * @returns [INDEX] NAME (example: `[1] Walking Wake` is a Walking Wake in the first party slot) */ export function playerPokeName(scene: BattleScene, index: integer | Pokemon | PlayerPokemon) { - if (typeof index == "number") { - return "[" + (index + 1) + "] " + scene.getParty()[index].name + var species: PokemonSpecies[] = [] + var dupeSpecies: PokemonSpecies[] = [] + for (var i = 0; i < scene.getParty().length; i++) { + if (!species.includes(scene.getParty()[i].species)) { + species.push(scene.getParty()[i].species) + } else if (!dupeSpecies.includes(scene.getParty()[i].species)) { + dupeSpecies.push(scene.getParty()[i].species) + } } - return "[" + (scene.getParty().indexOf(index as PlayerPokemon) + 1) + "] " + index.name + if (typeof index == "number") { + if (dupeSpecies.includes(scene.getParty()[index].species)) + return scene.getParty()[index].name + " (Slot " + (index + 1) + ")" + return scene.getParty()[index].name + } + if (dupeSpecies.includes(index.species)) + return index.name + " (Slot " + (scene.getParty().indexOf(index as PlayerPokemon) + 1) + ")" + return index.name } /** * Formats a Pokemon in the opposing party. + * + * If multiple Pokemon of the same species exist in the party, it will specify which slot they are in. * @param scene The BattleScene, for getting the enemy's party. * @param index The slot index. * @returns [INDEX] NAME (example: `[2] Zigzagoon` is a Zigzagoon in the right slot (for a double battle) or in the second party slot (for a single battle against a Trainer)) */ export function enemyPokeName(scene: BattleScene, index: integer | Pokemon | EnemyPokemon) { - if (typeof index == "number") { - return "[" + (index + 1) + "] " + scene.getEnemyParty()[index].name + var species: PokemonSpecies[] = [] + var dupeSpecies: PokemonSpecies[] = [] + for (var i = 0; i < scene.getEnemyParty().length; i++) { + if (!species.includes(scene.getEnemyParty()[i].species)) { + species.push(scene.getEnemyParty()[i].species) + } else if (!dupeSpecies.includes(scene.getEnemyParty()[i].species)) { + dupeSpecies.push(scene.getEnemyParty()[i].species) + } } - return "[" + (scene.getEnemyParty().indexOf(index as EnemyPokemon) + 1) + "] " + index.name + if (typeof index == "number") { + if (dupeSpecies.includes(scene.getEnemyParty()[index].species)) + return scene.getEnemyParty()[index].name + " (Slot " + (index + 1) + ")" + return scene.getEnemyParty()[index].name + } + if (dupeSpecies.includes(index.species)) + return index.name + " (Slot " + (scene.getEnemyParty().indexOf(index as EnemyPokemon) + 1) + ")" + return index.name } // LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, "") From 467297872957ba934dd7222595968b698d8a6e59 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Sun, 14 Jul 2024 16:29:09 -0400 Subject: [PATCH 75/94] Check duplicate names instead Since regional variants will have the same name despite being different species, the slot label now checks for identical names instead --- src/logger.ts | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/logger.ts b/src/logger.ts index 2f495ae30ce..b98a9b19e57 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -272,21 +272,21 @@ function checkForPokeInBiome(species: Species, pool: (Species | SpeciesTree)[]): * @returns [INDEX] NAME (example: `[1] Walking Wake` is a Walking Wake in the first party slot) */ export function playerPokeName(scene: BattleScene, index: integer | Pokemon | PlayerPokemon) { - var species: PokemonSpecies[] = [] - var dupeSpecies: PokemonSpecies[] = [] + var species = [] + var dupeSpecies = [] for (var i = 0; i < scene.getParty().length; i++) { - if (!species.includes(scene.getParty()[i].species)) { - species.push(scene.getParty()[i].species) - } else if (!dupeSpecies.includes(scene.getParty()[i].species)) { - dupeSpecies.push(scene.getParty()[i].species) + if (!species.includes(scene.getParty()[i].name)) { + species.push(scene.getParty()[i].name) + } else if (!dupeSpecies.includes(scene.getParty()[i].name)) { + dupeSpecies.push(scene.getParty()[i].name) } } if (typeof index == "number") { - if (dupeSpecies.includes(scene.getParty()[index].species)) + if (dupeSpecies.includes(scene.getParty()[index].name)) return scene.getParty()[index].name + " (Slot " + (index + 1) + ")" return scene.getParty()[index].name } - if (dupeSpecies.includes(index.species)) + if (dupeSpecies.includes(index.name)) return index.name + " (Slot " + (scene.getParty().indexOf(index as PlayerPokemon) + 1) + ")" return index.name } @@ -299,21 +299,21 @@ export function playerPokeName(scene: BattleScene, index: integer | Pokemon | Pl * @returns [INDEX] NAME (example: `[2] Zigzagoon` is a Zigzagoon in the right slot (for a double battle) or in the second party slot (for a single battle against a Trainer)) */ export function enemyPokeName(scene: BattleScene, index: integer | Pokemon | EnemyPokemon) { - var species: PokemonSpecies[] = [] - var dupeSpecies: PokemonSpecies[] = [] + var species = [] + var dupeSpecies = [] for (var i = 0; i < scene.getEnemyParty().length; i++) { - if (!species.includes(scene.getEnemyParty()[i].species)) { - species.push(scene.getEnemyParty()[i].species) - } else if (!dupeSpecies.includes(scene.getEnemyParty()[i].species)) { - dupeSpecies.push(scene.getEnemyParty()[i].species) + if (!species.includes(scene.getEnemyParty()[i].name)) { + species.push(scene.getEnemyParty()[i].name) + } else if (!dupeSpecies.includes(scene.getEnemyParty()[i].name)) { + dupeSpecies.push(scene.getEnemyParty()[i].name) } } if (typeof index == "number") { - if (dupeSpecies.includes(scene.getEnemyParty()[index].species)) + if (dupeSpecies.includes(scene.getEnemyParty()[index].name)) return scene.getEnemyParty()[index].name + " (Slot " + (index + 1) + ")" return scene.getEnemyParty()[index].name } - if (dupeSpecies.includes(index.species)) + if (dupeSpecies.includes(index.name)) return index.name + " (Slot " + (scene.getEnemyParty().indexOf(index as EnemyPokemon) + 1) + ")" return index.name } From dfc00c18f25c56781ac7005b4c676f96a0b1df51 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Sun, 14 Jul 2024 16:46:11 -0400 Subject: [PATCH 76/94] Update wording for relearning moves --- src/phases.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/phases.ts b/src/phases.ts index 9784d99473c..57da7bf06e8 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -5749,7 +5749,12 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { this.scene.ui.showText(i18next.t("battle:learnMoveStopTeaching", { moveName: move.name }), null, () => { this.scene.ui.setModeWithoutClear(Mode.CONFIRM, () => { this.scene.ui.setMode(messageMode); - LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, LoggerTools.playerPokeName(this.scene, pokemon) + " | Skip " + move.name) + var W = LoggerTools.getWave(LoggerTools.getDRPD(this.scene), this.scene.currentBattle.waveIndex, this.scene) + if (W.shop != "") { + LoggerTools.logShop(this.scene, this.scene.currentBattle.waveIndex, W.shop + "; skip learning it") + } else { + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, LoggerTools.playerPokeName(this.scene, pokemon) + " | Skip " + move.name) + } this.scene.ui.showText(i18next.t("battle:learnMoveNotLearned", { pokemonName: pokemon.name, moveName: move.name }), null, () => this.end(), null, true); }, () => { this.scene.ui.setMode(messageMode); @@ -5771,7 +5776,12 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { this.scene.ui.showText(i18next.t("battle:countdownPoof"), null, () => { this.scene.ui.showText(i18next.t("battle:learnMoveForgetSuccess", { pokemonName: pokemon.name, moveName: pokemon.moveset[moveIndex].getName() }), null, () => { this.scene.ui.showText(i18next.t("battle:learnMoveAnd"), null, () => { - LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, LoggerTools.playerPokeName(this.scene, pokemon) + " | Replace " + pokemon.moveset[moveIndex].getName() + " with " + new PokemonMove(this.moveId).getName()) + var W = LoggerTools.getWave(LoggerTools.getDRPD(this.scene), this.scene.currentBattle.waveIndex, this.scene) + if (W.shop != "") { + LoggerTools.logShop(this.scene, this.scene.currentBattle.waveIndex, W.shop + " → replace " + pokemon.moveset[moveIndex].getName()) + } else { + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, LoggerTools.playerPokeName(this.scene, pokemon) + " | Learn " + new PokemonMove(this.moveId).getName() + " → replace " + pokemon.moveset[moveIndex].getName()) + } pokemon.setMove(moveIndex, Moves.NONE); this.scene.unshiftPhase(new LearnMovePhase(this.scene, this.partyMemberIndex, this.moveId)); this.end(); From 607db5d0915f80414ca7e0480d58aac222b67cb3 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Sun, 14 Jul 2024 17:20:16 -0400 Subject: [PATCH 77/94] Fix UUID node:crypto cannot be used clientside, so use phaser's UUID generator (I wanted to use phaser's uuid anyways but found node:crypto first) --- src/logger.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/logger.ts b/src/logger.ts index b98a9b19e57..4491ee66db7 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -13,7 +13,6 @@ import { TitlePhase } from "./phases"; import Trainer from "./field/trainer"; import { Species } from "./enums/species"; import { GameMode, GameModes } from "./game-mode"; -import { randomUUID, UUID } from "node:crypto"; import PokemonSpecies from "./data/pokemon-species"; //#endregion @@ -355,7 +354,9 @@ function updateLog(drpd: DRPD): DRPD { } // 1.0.0 → 1.0.0a if (drpd.version == "1.0.0a") { drpd.version = "1.1.0" - drpd.uuid = randomUUID() + var RState = Phaser.Math.RND.state() + drpd.uuid = Phaser.Math.RND.uuid() + Phaser.Math.RND.state(RState) drpd.label = "route" } // 1.0.0a → 1.1.0 return drpd; @@ -376,7 +377,7 @@ export interface DRPD { /** The webpage path and internal name of this run. Entered by the user. Not to be confused with `title`, which is only a cosmetic identifier. */ label: string, /** A unique ID for this run. Currently unused, but may be used in the future. */ - uuid: UUID, + uuid: string, /** The name(s) of the users that worked on this run. Entered by the user. */ authors: string[], /** The date that this document was created on. Does NOT automatically detect the date of daily runs (It can't) */ @@ -409,16 +410,20 @@ export function importDocument(drpd: string): DRPD { * @returns The fresh DRPD document. */ export function newDocument(name: string = "Untitled Run", authorName: string | string[] = "Write your name here"): DRPD { - return { + var ret: DRPD = { version: DRPD_Version, title: name, label: "", - uuid: randomUUID(), + uuid: undefined, authors: (Array.isArray(authorName) ? authorName : [authorName]), date: new Date().getUTCFullYear() + "-" + (new Date().getUTCMonth() + 1 < 10 ? "0" : "") + (new Date().getUTCMonth() + 1) + "-" + (new Date().getUTCDate() < 10 ? "0" : "") + new Date().getUTCDate(), waves: new Array(50), starters: new Array(3), } + var RState = Phaser.Math.RND.state() + ret.uuid = Phaser.Math.RND.uuid() + Phaser.Math.RND.state(RState) + return ret; } /** * Prints a DRPD as a string, for saving it to your device. From e3272af46edff05c30dcdbc9a8dd64f11b1182ee Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Sun, 14 Jul 2024 17:28:15 -0400 Subject: [PATCH 78/94] Add log label field --- src/logger.ts | 5 ++-- src/phases.ts | 44 ++++++++++++++++++------------ src/ui/log-name-form-ui-handler.ts | 5 ++-- src/ui/party-ui-handler.ts | 2 +- 4 files changed, 33 insertions(+), 23 deletions(-) diff --git a/src/logger.ts b/src/logger.ts index 4491ee66db7..54b6d4c7948 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1189,7 +1189,7 @@ function printItemNoNewline(inData: string, indent: string, item: ItemData) { -//#region 12 Manage Logs +//#region 12 Ingame Menu /** * Sets the name, author, and [todo] label for a file. @@ -1263,7 +1263,8 @@ export function generateEditOption(scene: BattleScene, i: integer, saves: any, p scene.ui.setMode(Mode.NAME_LOG, { autofillfields: [ (JSON.parse(localStorage.getItem(logs[i][1])) as DRPD).title, - (JSON.parse(localStorage.getItem(logs[i][1])) as DRPD).authors.join(", ") + (JSON.parse(localStorage.getItem(logs[i][1])) as DRPD).authors.join(", "), + (JSON.parse(localStorage.getItem(logs[i][1])) as DRPD).label, ], buttonActions: [ () => { diff --git a/src/phases.ts b/src/phases.ts index 57da7bf06e8..b959e2c7e3f 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -873,7 +873,7 @@ export class TitlePhase extends Phase { -//#region 04 Unavailable +//#region 04 UnavailablePhase export class UnavailablePhase extends Phase { constructor(scene: BattleScene) { super(scene); @@ -892,7 +892,7 @@ export class UnavailablePhase extends Phase { -//#region 05 ReloadSession +//#region 05 ReloadSessionPhase export class ReloadSessionPhase extends Phase { private systemDataStr: string; @@ -949,7 +949,7 @@ export class OutdatedPhase extends Phase { -//#region 07 SelectGender +//#region 07 SelectGenderPhase export class SelectGenderPhase extends Phase { constructor(scene: BattleScene) { super(scene); @@ -995,7 +995,7 @@ export class SelectGenderPhase extends Phase { -//#region 08 SelectChallenge +//#region 08 SelectChallengePhase export class SelectChallengePhase extends Phase { constructor(scene: BattleScene) { super(scene); @@ -1015,7 +1015,7 @@ export class SelectChallengePhase extends Phase { -//#region 09 SelectStarter +//#region 09 SelectStarterPhase export class SelectStarterPhase extends Phase { constructor(scene: BattleScene) { @@ -5907,8 +5907,11 @@ export class PokemonHealPhase extends CommonAnimPhase { //#region 69 AttemptCapturePhase export class AttemptCapturePhase extends PokemonPhase { + /** The Pokeball being used. */ private pokeballType: PokeballType; + /** The Pokeball sprite. */ private pokeball: Phaser.GameObjects.Sprite; + /** The sprite's original Y position. */ private originalY: number; constructor(scene: BattleScene, targetIndex: integer, pokeballType: PokeballType) { @@ -6080,29 +6083,26 @@ export class AttemptCapturePhase extends PokemonPhase { } catch() { + /** The Pokemon being caught. */ const pokemon = this.getPokemon() as EnemyPokemon; this.scene.unshiftPhase(new VictoryPhase(this.scene, this.battlerIndex)); + /** Used for achievements. */ const speciesForm = !pokemon.fusionSpecies ? pokemon.getSpeciesForm() : pokemon.getFusionSpeciesForm(); - if (speciesForm.abilityHidden && (pokemon.fusionSpecies ? pokemon.fusionAbilityIndex : pokemon.abilityIndex) === speciesForm.getAbilityCount() - 1) { + // Achievements + if (speciesForm.abilityHidden && (pokemon.fusionSpecies ? pokemon.fusionAbilityIndex : pokemon.abilityIndex) === speciesForm.getAbilityCount() - 1) this.scene.validateAchv(achvs.HIDDEN_ABILITY); - } - - if (pokemon.species.subLegendary) { + if (pokemon.species.subLegendary) this.scene.validateAchv(achvs.CATCH_SUB_LEGENDARY); - } - - if (pokemon.species.legendary) { + if (pokemon.species.legendary) this.scene.validateAchv(achvs.CATCH_LEGENDARY); - } - - if (pokemon.species.mythical) { + if (pokemon.species.mythical) this.scene.validateAchv(achvs.CATCH_MYTHICAL); - } + // Show its info this.scene.pokemonInfoContainer.show(pokemon, true); - + // Update new IVs this.scene.gameData.updateSpeciesDexIvs(pokemon.species.getRootSpeciesId(true), pokemon.ivs); this.scene.ui.showText(i18next.t("battle:pokemonCaught", { pokemonName: pokemon.name }), null, () => { @@ -6139,9 +6139,13 @@ export class AttemptCapturePhase extends PokemonPhase { Promise.all([pokemon.hideInfo(), this.scene.gameData.setPokemonCaught(pokemon)]).then(() => { if (this.scene.getParty().length === 6) { const promptRelease = () => { + // Say that your party is full this.scene.ui.showText(i18next.t("battle:partyFull", { pokemonName: pokemon.name }), null, () => { + // Ask if you want to make room this.scene.pokemonInfoContainer.makeRoomForConfirmUi(); this.scene.ui.setMode(Mode.CONFIRM, () => { + // YES + // Open up the party menu on the RELEASE setting this.scene.ui.setMode(Mode.PARTY, PartyUiMode.RELEASE, this.fieldIndex, (slotIndex: integer, _option: PartyOption) => { this.scene.ui.setMode(Mode.MESSAGE).then(() => { if (slotIndex < 6) { @@ -6152,7 +6156,8 @@ export class AttemptCapturePhase extends PokemonPhase { }); }); }, () => { - LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, "Do Not Keep") + // NO + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, "Don't keep " + pokemon.name) this.scene.ui.setMode(Mode.MESSAGE).then(() => { removePokemon(); end(); @@ -6162,12 +6167,14 @@ export class AttemptCapturePhase extends PokemonPhase { }; promptRelease(); } else { + //LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, `${pokemon.name} added to party`) addToParty(); } }); }, 0, true); } + /** Remove the Poke Ball from the scene. */ removePb() { this.scene.tweens.add({ targets: this.pokeball, @@ -6730,3 +6737,4 @@ export class TestMessagePhase extends MessagePhase { super(scene, message, null, true); } } +//#endregion \ No newline at end of file diff --git a/src/ui/log-name-form-ui-handler.ts b/src/ui/log-name-form-ui-handler.ts index c6fce79836a..85d2740549c 100644 --- a/src/ui/log-name-form-ui-handler.ts +++ b/src/ui/log-name-form-ui-handler.ts @@ -10,11 +10,11 @@ export default class LogNameFormUiHandler extends FormModalUiHandler { name: string; getModalTitle(config?: ModalConfig): string { - return "Manage " + (this.name ? this.name : "Log"); + return (this.name ? this.name : "Manage Log"); } getFields(config?: ModalConfig): string[] { - return [ "Name", "Author(s)" ]; + return [ "Name", "Author(s)", "Label" ]; } getWidth(config?: ModalConfig): number { @@ -67,6 +67,7 @@ export default class LogNameFormUiHandler extends FormModalUiHandler { const originalLoginAction = this.submitAction; this.inputs[0].setText(args[0].autofillfields[0]) this.inputs[1].setText(args[0].autofillfields[1]) + this.inputs[2].setText(args[0].autofillfields[2]) this.submitAction = (_) => { console.log("submitAction") // Prevent overlapping overrides on action modification diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index 528730ad770..32ce45d2a63 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -407,6 +407,7 @@ export default class PartyUiHandler extends MessageUiHandler { this.showText(i18next.t("partyUiHandler:releaseConfirmation", { pokemonName: pokemon.name }), null, () => { ui.setModeWithoutClear(Mode.CONFIRM, () => { ui.setMode(Mode.PARTY); + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, `Add ${pokemon.name} to party, replacing ${this.scene.getParty()[this.cursor].name} (Slot ${this.cursor + 1})`) this.doRelease(this.cursor); }, () => { ui.setMode(Mode.PARTY); @@ -933,7 +934,6 @@ export default class PartyUiHandler extends MessageUiHandler { this.clearPartySlots(); this.scene.removePartyMemberModifiers(slotIndex); const releasedPokemon = this.scene.getParty().splice(slotIndex, 1)[0]; - LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, `Release ${releasedPokemon.name} (Slot ${slotIndex + 1})`) releasedPokemon.destroy(); this.populatePartySlots(); if (this.cursor >= this.scene.getParty().length) { From 54ddc63a148d86c80ed8c927512f2ce16e019ec6 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Sun, 14 Jul 2024 19:29:16 -0400 Subject: [PATCH 79/94] Condense actions --- src/logger.ts | 5 +++++ src/phases.ts | 26 +++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/logger.ts b/src/logger.ts index 54b6d4c7948..6f4fc7449f8 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -187,6 +187,11 @@ export function getDRPD(scene: BattleScene): DRPD { return drpd; } +export function save(scene: BattleScene, drpd: DRPD) { + console.log(drpd) + localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) +} + /** * Testing purposes only. */ diff --git a/src/phases.ts b/src/phases.ts index b959e2c7e3f..5277e35fde6 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -73,7 +73,7 @@ import ChallengeData from "./system/challenge-data"; import { Challenges } from "./enums/challenges" import PokemonData from "./system/pokemon-data" import * as LoggerTools from "./logger" -import { getNatureName } from "./data/nature"; +import { getNatureDecrease, getNatureIncrease, getNatureName } from "./data/nature"; import { GameDataType } from "./enums/game-data-type"; import { Session } from "inspector"; @@ -3383,6 +3383,30 @@ export class BattleEndPhase extends BattlePhase { this.scene.currentBattle.addBattleScore(this.scene); + var drpd: LoggerTools.DRPD = LoggerTools.getDRPD(this.scene) + var wv: LoggerTools.Wave = LoggerTools.getWave(drpd, this.scene.currentBattle.waveIndex, this.scene) + var lastcount = 0; + var lastval = undefined; + var tempActions = wv.actions.slice(); + wv.actions = [] + // Loop through each action + for (var i = 0; i < tempActions.length; i++) { + if (tempActions[i] != lastval) { + if (lastcount > 0) { + wv.actions.push(lastval + (lastcount == 1 ? "" : " x" + lastcount)) + } + lastval = tempActions[i] + lastcount = 1 + } else { + lastcount++ + } + } + if (lastcount > 0) { + wv.actions.push(lastval + (lastcount == 1 ? "" : " x" + lastcount)) + } + console.log(tempActions, wv.actions) + LoggerTools.save(this.scene, drpd) + this.scene.gameData.gameStats.battles++; if (this.scene.currentBattle.trainer) { this.scene.gameData.gameStats.trainersDefeated++; From 06e7e24aa76469d117543d35ff9e3a1fce5ca61c Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Sun, 14 Jul 2024 19:50:40 -0400 Subject: [PATCH 80/94] Fix abilities --- src/data/ability.ts | 2 ++ src/phases.ts | 23 +++++++++++++++++++++++ src/ui/pokemon-info-container.ts | 13 +++++++++++-- src/ui/stats-container.ts | 5 +++-- 4 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index 8f49e2dfd9a..61bd215caf3 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -2645,6 +2645,8 @@ function getAnticipationCondition(): AbAttrCondition { return (pokemon: Pokemon) => { for (const opponent of pokemon.getOpponents()) { for (const move of opponent.moveset) { + if (move == undefined) + continue; // move is super effective if (move.getMove() instanceof AttackMove && pokemon.getAttackTypeEffectiveness(move.getMove().type, opponent, true) >= 2) { return true; diff --git a/src/phases.ts b/src/phases.ts index 5277e35fde6..01349fa2555 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -2406,6 +2406,27 @@ export class CheckSwitchPhase extends BattlePhase { pk.getBattleInfo().flyoutMenu.flyoutText[2].setColor("#e8e8a8") } } + if (false) { + this.scene.pokemonInfoContainer.show(this.scene.getEnemyField()[0], false, 1, true); + if (this.scene.getEnemyField()[1] != undefined) { + this.scene.tweens.add({ + targets: this.scene.pokemonInfoContainer, + alpha: 1, + duration: 5000, + onComplete: () => { + this.scene.pokemonInfoContainer.hide(1.3) + this.scene.tweens.add({ + targets: this.scene.pokemonInfoContainer, + alpha: 1, + duration: 1000, + onComplete: () => { + this.scene.pokemonInfoContainer.show(this.scene.getEnemyField()[1], false, 1, true); + } + }) + } + }) + } + } this.scene.ui.showText(i18next.t("battle:switchQuestion", { pokemonName: this.useName ? pokemon.name : i18next.t("battle:pokemon") }), null, () => { this.scene.ui.setMode(Mode.CONFIRM, () => { @@ -2421,6 +2442,7 @@ export class CheckSwitchPhase extends BattlePhase { this.scene.getEnemyField()[i].getBattleInfo().flyoutMenu.flyoutText[3].text = "???" this.scene.getEnemyField()[i].getBattleInfo().flyoutMenu.flyoutText[2].setColor("#f8f8f8") } + //this.scene.pokemonInfoContainer.hide() this.end(); }, () => { this.scene.ui.setMode(Mode.MESSAGE); @@ -2432,6 +2454,7 @@ export class CheckSwitchPhase extends BattlePhase { this.scene.getEnemyField()[i].getBattleInfo().flyoutMenu.flyoutText[3].text = "???" this.scene.getEnemyField()[i].getBattleInfo().flyoutMenu.flyoutText[2].setColor("#f8f8f8") } + //this.scene.pokemonInfoContainer.hide() this.end(); }); }); diff --git a/src/ui/pokemon-info-container.ts b/src/ui/pokemon-info-container.ts index 9f4df2b20b4..30d1c956791 100644 --- a/src/ui/pokemon-info-container.ts +++ b/src/ui/pokemon-info-container.ts @@ -207,7 +207,7 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { this.setVisible(false); } - show(pokemon: Pokemon, showMoves: boolean = false, speedMultiplier: number = 1): Promise { + show(pokemon: Pokemon, showMoves: boolean = false, speedMultiplier: number = 1, lessInfo: boolean = false): Promise { return new Promise(resolve => { const caughtAttr = BigInt(pokemon.scene.gameData.dexData[pokemon.species.speciesId].caughtAttr); if (pokemon.gender > Gender.GENDERLESS) { @@ -290,6 +290,11 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { this.pokemonNatureLabelText.setColor(getTextColor(TextStyle.WINDOW, false, this.scene.uiTheme)); this.pokemonNatureLabelText.setShadowColor(getTextColor(TextStyle.WINDOW, true, this.scene.uiTheme)); } + + this.pokemonAbilityText.setVisible(!lessInfo) + this.pokemonAbilityLabelText.setVisible(!lessInfo) + this.pokemonNatureText.setVisible(!lessInfo) + this.pokemonNatureLabelText.setVisible(!lessInfo) const isFusion = pokemon.isFusion(); const doubleShiny = isFusion && pokemon.shiny && pokemon.fusionShiny; @@ -328,7 +333,11 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { ? this.scene.gameData.dexData[starterSpeciesId].ivs : null; - this.statsContainer.updateIvs(pokemon.ivs, originalIvs); + if (lessInfo) { + this.statsContainer.updateIvs(pokemon.species.baseStats, undefined, 255); + } else { + this.statsContainer.updateIvs(pokemon.ivs, originalIvs); + } this.scene.tweens.add({ targets: this, diff --git a/src/ui/stats-container.ts b/src/ui/stats-container.ts index b4e799bafc0..f94861cdc4b 100644 --- a/src/ui/stats-container.ts +++ b/src/ui/stats-container.ts @@ -65,9 +65,10 @@ export class StatsContainer extends Phaser.GameObjects.Container { }); } - updateIvs(ivs: integer[], originalIvs?: integer[]): void { + updateIvs(ivs: integer[], originalIvs?: integer[], max?: integer): void { if (ivs) { - const ivChartData = new Array(6).fill(null).map((_, i) => [ (ivs[ivChartStatIndexes[i]] / 31) * ivChartSize * ivChartStatCoordMultipliers[ivChartStatIndexes[i]][0], (ivs[ivChartStatIndexes[i]] / 31) * ivChartSize * ivChartStatCoordMultipliers[ivChartStatIndexes[i]][1] ] ).flat(); + var maxIV = max || 31 + const ivChartData = new Array(6).fill(null).map((_, i) => [ (ivs[ivChartStatIndexes[i]] / maxIV) * ivChartSize * ivChartStatCoordMultipliers[ivChartStatIndexes[i]][0], (ivs[ivChartStatIndexes[i]] / maxIV) * ivChartSize * ivChartStatCoordMultipliers[ivChartStatIndexes[i]][1] ] ).flat(); const lastIvChartData = this.statsIvsCache || defaultIvChartData; const perfectIVColor: string = getTextColor(TextStyle.SUMMARY_GOLD, false, (this.scene as BattleScene).uiTheme); this.statsIvsCache = ivChartData.slice(0); From 7246da594140ce82410726c3df8840581ff5187d Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Sun, 14 Jul 2024 20:10:27 -0400 Subject: [PATCH 81/94] Biome Panels Added Cave, Badlands, and Construction Site Biome panels no longer create error message spam from not loading nonexistent legacy assets --- public/images/ui/windows/badlands_panel.png | Bin 0 -> 4712 bytes public/images/ui/windows/cave_panel.png | Bin 0 -> 4305 bytes .../ui/windows/construction_site_panel.png | Bin 0 -> 4562 bytes src/loading-scene.ts | 10 +++++----- src/scene-base.ts | 6 ++++++ 5 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 public/images/ui/windows/badlands_panel.png create mode 100644 public/images/ui/windows/cave_panel.png create mode 100644 public/images/ui/windows/construction_site_panel.png diff --git a/public/images/ui/windows/badlands_panel.png b/public/images/ui/windows/badlands_panel.png new file mode 100644 index 0000000000000000000000000000000000000000..c601abc89dba80da2e5377e2c7d1890325842a77 GIT binary patch literal 4712 zcmeHLc|6qX8fWAf+;B>x#?m;7O19HvDP>X&5veFyOEG0{$Y`=mp@pA{F3Q&Alq^vc zF}8CslxBFra8tL(Ya#R}%t8#Pb0^!hg7Us4dbW5~| z6{AmkgdWc@JBa?pd`wlh@mO0*m}dM?wNxXsRXVBn@%9AwwUe^t>E~U!wp=ah zjB;qe57j^Qt?a|1vU26EmS&Zi4MXf=L;Rt~%A8gqu#ipyyolR#) zI)A5f;@>RIv@5*Jav5v))Y^@AYkQUAYGn3GV(;w?s5xEgig#p-G#HLH7P9vEu$G7^ z&$!oV!(fI!jv=MnvUk3_6hPV;>X=O${Tk&t(lnD)r7@WZ33OZblqYEkYB{ zwCSRLn(I!8my_!)bc$2sehHVZY0P!G5C!QceeP*AQ8cwHc+)}Y38pG&DW`^~oR9J; z)ZBZ2Gy<$9+n_tVUT&D}@6=A7Ue26*cQQZJcTzq_L`1zR)SdBsa*$v6-Nz@ zMWS@9!Z+?#g6uJ9V+pKI{z6(sxOiXZcy0a5tkl#cpR(3jxox%Aj-Ltrn`^Y#oC>Ou z-+*54Df{;cfjl%_k{9|v9r^3j)3oRfSdH0fDe<27qWhj{?2tzULe)xc`mXnUH&4KwxARuN?XTd?0c z6@b?En#>&zaQ1RH6_Q>7?9gzM7i1u7oftM7d&3dr^=@<^E2df7>mX1BZT0jxeE)7y zL{gk0FI_FolN18d<|~MXd}|JBfJIjx#_hx>3~51@(rao!*xki~i)zDKkkj*mmO~&e z0**`*Xb#98dr@Dwuhn(AR#`V}PO5fJE{o){yKtg&6$8AyHm~?g0Nja3A_dyW+ zHE#Qt5lQv3AZD)LOynw|b;XyBj7i$FlZp;LZr)(q#0-Qwe3lksSd>olw1}GR-@~P5 zSV_Q$rpv2ViCi44a(2+|o!51^q$vc_Bc|lxbAmK=#9?aixhdGXSA-S~i2GD+pVS7h z0XH$|ud(_*rjShGG+5YO44Oe5ZrAym;^nggOhCY$=ytMWM^TdIRG$(p;Bt~QxXM`RQCv6jQ{`3WosrwRqK21%e414~|^bdyRCAb8hJ+64mO zMG2^g%^pc2x=Ag*D!2{~ffDA8+}J(I=v+!#6cY6H4R0pfUQb(L+3k_;oXs0ko!_OV zK8(y%pPk;))d3q%bHPCBKi^*AM*tL4^ce6Sfkn#D@v$7OeQtulWk_Qb<;%+B69URB zRuMnLec>`ZHk<}g=2*ut;@rF9Zw8t;n02nyJ>jMxe9DBZsgz@=_Vo+i%$d zX23}J{8KZMF~sD64kV_6!!zp$0$}qiEeNFQ%XloPPeMT;YoiXIG&l-4D|r93*OS6P zfsdnf9TxxL1&#yFg+GYtLQGNv+gT=zhQ@cX5G7=7fH$Nx!~}ij{{Ues|JN8iLx1Vc zO_*bkAlzmRH@T!_EN_}430v6dCBB2+M=Z_F$rB&?ixNJ?N|qXpJnY4wAwyM!g`qJN zN7Ze38F(5fBT>lxaKEIH(f@v{($drMpw9Xe?*8Di3SK@r2|%zyCm$o2+5Ph=1X=S!LBaab$^F!AUi!eF(+p`Zs*bbK6|!4^StoE& zXEgp8uY?vwZ-^j#W~LH~#BiP<8~8w2`(+Y*$$V0GLEHE3sUH@3MIr}Y2FWLSr@*sT zE~muY*A+p`+Z9T*ke9&SJ`v}R$3du*G^u#r(=y#E_fIzr9=^fRe@jAk~%G- zu0!EUpyYT_na@hz>*y@j<_rwiDW|2jEq8?b>FD+E-W?F<-o(AFHTo1q2m926-YKQd9w5`SUd>e_KI>lt$*8S?g zc6u(w7kA0ynQN=xfkC7vB~x@y%ZhWG`=HH*_Gf$s;P?F?rGip%h)7OP>*Y|1{VsEF zm#j{=roRTOB;ZW!%d^gtO%!jBE5)ksmee!rQhi;}m&NT2D8M&p literal 0 HcmV?d00001 diff --git a/public/images/ui/windows/cave_panel.png b/public/images/ui/windows/cave_panel.png new file mode 100644 index 0000000000000000000000000000000000000000..0dce031b69e19cf3f6072cc2bc81011ca00bad0d GIT binary patch literal 4305 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 Dti4Lu literal 0 HcmV?d00001 diff --git a/public/images/ui/windows/construction_site_panel.png b/public/images/ui/windows/construction_site_panel.png new file mode 100644 index 0000000000000000000000000000000000000000..bb441df064a8a68ca9f9dbeac5dcf193e33a2c81 GIT binary patch literal 4562 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 d2Q(*+#UA+iw#j-$ Date: Sun, 14 Jul 2024 20:21:39 -0400 Subject: [PATCH 82/94] Biome Panels setting Adds an option to enable biome panels Displays the biome that a save is in --- src/battle-scene.ts | 1 + src/system/settings/settings.ts | 18 +++++++++++++++++- src/ui/save-slot-select-ui-handler.ts | 25 ++++++++++++++----------- 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 31474292b31..c42b16fc126 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -124,6 +124,7 @@ export default class BattleScene extends SceneBase { public lazyReloads: boolean = false; public menuChangesBiome: boolean = false; public showAutosaves: boolean = false; + public doBiomePanels: boolean = false; /** * Determines the condition for a notification should be shown for Candy Upgrades * - 0 = 'Off' diff --git a/src/system/settings/settings.ts b/src/system/settings/settings.ts index 24de349327f..611ed8f15ad 100644 --- a/src/system/settings/settings.ts +++ b/src/system/settings/settings.ts @@ -102,7 +102,8 @@ export const SettingKeys = { Damage_Display: "DAMAGE_DISPLAY", LazyReloads: "FLAG_EVERY_RESET_AS_RELOAD", FancyBiome: "FANCY_BIOMES", - ShowAutosaves: "SHOW_AUTOSAVES" + ShowAutosaves: "SHOW_AUTOSAVES", + BiomePanels: "BIOME_PANELS" }; /** @@ -544,6 +545,19 @@ export const Setting: Array = [ type: SettingType.DISPLAY, requireReload: true }, + { + key: SettingKeys.BiomePanels, + label: "Biome Panels", + options: [{ + label: "Off", + value: "Off" + }, { + label: "On", + value: "On" + }], + default: 0, + type: SettingType.DISPLAY, + }, { key: SettingKeys.ShowAutosaves, label: "Show Autosaves", @@ -672,6 +686,8 @@ export function setSetting(scene: BattleScene, setting: string, value: integer): scene.menuChangesBiome = Setting[index].options[value].value == "On" case SettingKeys.ShowAutosaves: scene.showAutosaves = Setting[index].options[value].value == "On" + case SettingKeys.BiomePanels: + scene.doBiomePanels = Setting[index].options[value].value == "On" case SettingKeys.Skip_Seen_Dialogues: scene.skipSeenDialogues = Setting[index].options[value].value === "On"; break; diff --git a/src/ui/save-slot-select-ui-handler.ts b/src/ui/save-slot-select-ui-handler.ts index 10eb3ead3e9..3a3586ff361 100644 --- a/src/ui/save-slot-select-ui-handler.ts +++ b/src/ui/save-slot-select-ui-handler.ts @@ -13,6 +13,7 @@ import { addWindow } from "./ui-theme"; import * as LoggerTools from "../logger" import { loggedInUser } from "#app/account.js"; import { allpanels, biomePanelIDs } from "../loading-scene" +import { getBiomeName } from "#app/data/biomes.js"; const sessionSlotCount = 5; @@ -309,13 +310,15 @@ class SessionSlot extends Phaser.GameObjects.Container { const slotWindow = addWindow(this.scene, 0, 0, 304, 52); this.add(slotWindow); - //this.backer = this.scene.add.image(0, 0, `end_panel`) - //this.backer.setOrigin(0.5, 0.5) - //this.backer.setScale(304/909, 52/155) - //this.backer.setPosition(102*1.5 - 1, 26) - //this.backer.setSize(304, 52) - //this.backer.setVisible(false) - //this.add(this.backer) + if (this.scene.doBiomePanels) { + this.backer = this.scene.add.image(0, 0, `end_panel`) + this.backer.setOrigin(0.5, 0.5) + this.backer.setScale(304/909, 52/155) + this.backer.setPosition(102*1.5 - 1, 26) + this.backer.setSize(304, 52) + this.backer.setVisible(false) + this.add(this.backer) + } this.loadingLabel = addTextObject(this.scene, 152, 26, i18next.t("saveSlotSelectUiHandler:loading"), TextStyle.WINDOW); this.loadingLabel.setOrigin(0.5, 0.5); @@ -336,14 +339,14 @@ class SessionSlot extends Phaser.GameObjects.Container { const timestampLabel = addTextObject(this.scene, 8, 19, new Date(data.timestamp).toLocaleString(), TextStyle.WINDOW); this.add(timestampLabel); - const playTimeLabel = addTextObject(this.scene, 8, 33, Utils.getPlayTimeString(data.playTime), TextStyle.WINDOW); + const playTimeLabel = addTextObject(this.scene, 8, 33, Utils.getPlayTimeString(data.playTime) + " " + (getBiomeName(data.arena.biome) == "Construction Site" ? "Construction" : getBiomeName(data.arena.biome)), TextStyle.WINDOW); this.add(playTimeLabel); console.log(biomePanelIDs[data.arena.biome]) - if (allpanels.includes(biomePanelIDs[data.arena.biome])) { - //this.backer.setTexture(`${biomePanelIDs[data.arena.biome]}_panel`) - //this.backer.setVisible(true) + if (this.backer && allpanels.includes(biomePanelIDs[data.arena.biome]) && this.scene.doBiomePanels) { + this.backer.setTexture(`${biomePanelIDs[data.arena.biome]}_panel`) + this.backer.setVisible(true) } const pokemonIconsContainer = this.scene.add.container(144, 4); From ad7cfd5b0ad69242da14662afa5fa94d4e265079 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Sun, 14 Jul 2024 20:35:01 -0400 Subject: [PATCH 83/94] Fix gender --- src/ui/arena-flyout.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ui/arena-flyout.ts b/src/ui/arena-flyout.ts index fd21397594e..41ae305548c 100644 --- a/src/ui/arena-flyout.ts +++ b/src/ui/arena-flyout.ts @@ -12,6 +12,7 @@ import * as Utils from "../utils"; import { getNatureDecrease, getNatureIncrease, getNatureName } from "#app/data/nature.js"; import * as LoggerTools from "../logger" import { BattleEndPhase } from "#app/phases.js"; +import { Gender } from "#app/data/gender.js"; /** Enum used to differentiate {@linkcode Arena} effects */ enum ArenaEffectType { @@ -210,7 +211,7 @@ export default class ArenaFlyout extends Phaser.GameObjects.Container { this.flyoutTextHeader.text = "IVs" for (var i = 0; i < poke.length; i++) { if (i == 1 || true) { - this.flyoutTextPlayer.text += poke[i].name + "\n" + this.flyoutTextPlayer.text += poke[i].name + " " + (poke[i].gender == Gender.MALE ? "♂" : (poke[i].gender == Gender.FEMALE ? "♀" : "-")) + " " + poke[i].level + "\n" this.flyoutTextEnemy.text += poke[i].getAbility().name + " / " + (poke[i].isBoss() ? poke[i].getPassiveAbility().name + " / " : "") + getNatureName(poke[i].nature) + (getNatureIncrease(poke[i].nature) != "" ? " (+" + getNatureIncrease(poke[i].nature) + " -" + getNatureDecrease(poke[i].nature) + ")" : "") + "\n\n\n" } this.flyoutTextPlayer.text += "HP: " + poke[i].ivs[0] From fc915676708531234b8f85c7863b1ef852159c6d Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Sun, 14 Jul 2024 20:39:02 -0400 Subject: [PATCH 84/94] Fix move learned by mushroom/TM not displaying --- src/phases.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/phases.ts b/src/phases.ts index 01349fa2555..766a594ec51 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -5825,7 +5825,7 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { this.scene.ui.showText(i18next.t("battle:learnMoveAnd"), null, () => { var W = LoggerTools.getWave(LoggerTools.getDRPD(this.scene), this.scene.currentBattle.waveIndex, this.scene) if (W.shop != "") { - LoggerTools.logShop(this.scene, this.scene.currentBattle.waveIndex, W.shop + " → replace " + pokemon.moveset[moveIndex].getName()) + LoggerTools.logShop(this.scene, this.scene.currentBattle.waveIndex, W.shop + " → learn " + new PokemonMove(this.moveId).getName() + " → replace " + pokemon.moveset[moveIndex].getName()) } else { LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, LoggerTools.playerPokeName(this.scene, pokemon) + " | Learn " + new PokemonMove(this.moveId).getName() + " → replace " + pokemon.moveset[moveIndex].getName()) } From 96e2372c9c9900b3919001ccda2939ccdccc6b61 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Mon, 15 Jul 2024 16:49:29 -0400 Subject: [PATCH 85/94] fix drpd export --- src/logger.ts | 3 +++ src/phases.ts | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/src/logger.ts b/src/logger.ts index 6f4fc7449f8..3bcd9b9e5e4 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -446,6 +446,8 @@ export function printDRPD(inData: string, indent: string, drpd: DRPD): string { inData += ",\n" + indent + " \"title\": \"" + drpd.title + "\"" inData += ",\n" + indent + " \"authors\": [\"" + drpd.authors.join("\", \"") + "\"]" inData += ",\n" + indent + " \"date\": \"" + drpd.date + "\"" + inData += ",\n" + indent + " \"label\": \"" + drpd.label + "\"" + inData += ",\n" + indent + " \"uuid\": \"" + drpd.uuid + "\"" if (drpd.waves) { inData += ",\n" + indent + " \"waves\": [\n" var isFirst = true @@ -459,6 +461,7 @@ export function printDRPD(inData: string, indent: string, drpd: DRPD): string { inData = printWave(inData, indent + " ", drpd.waves[i]) } } + inData += ",\n" + indent + " ]\n" } else { inData += ",\n" + indent + " \"waves\": []" } diff --git a/src/phases.ts b/src/phases.ts index 766a594ec51..2813bc563ef 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -6465,7 +6465,15 @@ export class SelectModifierPhase extends BattlePhase { ? modifierType.newModifier(party[slotIndex]) : modifierType.newModifier(party[slotIndex], option as integer) : modifierType.newModifier(party[slotIndex], option - PartyOption.MOVE_1); + if (isPpRestoreModifier) { LoggerTools.logShop(this.scene, this.scene.currentBattle.waveIndex, modifierType.name + " → " + this.scene.getParty()[slotIndex].name) + } else if (isRememberMoveModifier) { + LoggerTools.logShop(this.scene, this.scene.currentBattle.waveIndex, modifierType.name + " → " + this.scene.getParty()[slotIndex].name) + } else if (isTmModifier) { + LoggerTools.logShop(this.scene, this.scene.currentBattle.waveIndex, modifierType.name + " → " + this.scene.getParty()[slotIndex].name) + } else { + LoggerTools.logShop(this.scene, this.scene.currentBattle.waveIndex, modifierType.name + " → " + this.scene.getParty()[slotIndex].name) + } applyModifier(modifier, true); }); } else { From 9e253f76461632af9279785124cc5c800c01aafe Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Mon, 15 Jul 2024 16:58:02 -0400 Subject: [PATCH 86/94] Fix evolution logging --- src/evolution-phase.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evolution-phase.ts b/src/evolution-phase.ts index 35ccf0ee01d..3796fb40db4 100644 --- a/src/evolution-phase.ts +++ b/src/evolution-phase.ts @@ -195,7 +195,6 @@ export class EvolutionPhase extends Phase { this.scene.ui.showText(i18next.t("menu:stoppedEvolving", { pokemonName: preName }), null, () => { this.scene.ui.showText(i18next.t("menu:pauseEvolutionsQuestion", { pokemonName: preName }), null, () => { const end = () => { - LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, "Cancel " + preName + "'s evolution") this.scene.ui.showText(null, 0); this.scene.playBgm(); evolvedPokemon.destroy(); @@ -207,6 +206,7 @@ export class EvolutionPhase extends Phase { this.pokemon.pauseEvolutions = true; this.scene.ui.showText(i18next.t("menu:evolutionsPaused", { pokemonName: preName }), null, end, 3000); }, () => { + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, "Cancel " + preName + "'s evolution") this.scene.ui.revertMode(); this.scene.time.delayedCall(3000, end); }); From ef8e9904e4b99ab862d632e31928864fbb252659 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Mon, 15 Jul 2024 17:21:55 -0400 Subject: [PATCH 87/94] Log PP items correctly --- src/phases.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/phases.ts b/src/phases.ts index 2813bc563ef..9701c87c2c6 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -6466,7 +6466,7 @@ export class SelectModifierPhase extends BattlePhase { : modifierType.newModifier(party[slotIndex], option as integer) : modifierType.newModifier(party[slotIndex], option - PartyOption.MOVE_1); if (isPpRestoreModifier) { - LoggerTools.logShop(this.scene, this.scene.currentBattle.waveIndex, modifierType.name + " → " + this.scene.getParty()[slotIndex].name) + LoggerTools.logShop(this.scene, this.scene.currentBattle.waveIndex, modifierType.name + " → " + this.scene.getParty()[slotIndex].name + " → " + this.scene.getParty()[slotIndex].moveset[option - PartyOption.MOVE_1].getName()) } else if (isRememberMoveModifier) { LoggerTools.logShop(this.scene, this.scene.currentBattle.waveIndex, modifierType.name + " → " + this.scene.getParty()[slotIndex].name) } else if (isTmModifier) { From 6af3f4ef3f1c836809495f990c64be7fa8a5e6d5 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Tue, 16 Jul 2024 11:08:31 -0400 Subject: [PATCH 88/94] Fix trainer title logging --- src/field/trainer.ts | 106 +++++++++++++++++++++++++++++++++++++ src/logger.ts | 18 +------ src/ui/fight-ui-handler.ts | 2 +- 3 files changed, 109 insertions(+), 17 deletions(-) diff --git a/src/field/trainer.ts b/src/field/trainer.ts index 3e78afeae83..a543568fbc7 100644 --- a/src/field/trainer.ts +++ b/src/field/trainer.ts @@ -168,6 +168,112 @@ export default class Trainer extends Phaser.GameObjects.Container { // Return the formatted name, including the title if it is set. return title ? `${title} ${name}` : name; } + getNameOnly(trainerSlot: TrainerSlot = TrainerSlot.NONE): string { + // Get the base title based on the trainer slot and variant. + let name = this.config.getTitle(trainerSlot, this.variant); + + // Determine the title to include based on the configuration and includeTitle flag. + let title = true && this.config.title ? this.config.title : null; + + if (this.name === "" && name.toLowerCase().includes("grunt")) { + // This is a evil team grunt so we localize it by only using the "name" as the title + title = i18next.t(`trainerClasses:${name.toLowerCase().replace(/\s/g, "_")}`); + console.log("Localized grunt name: " + title); + // Since grunts are not named we can just return the title + return title; + } + + // If the trainer has a name (not null or undefined). + if (this.name) { + // If the title should be included. + if (true) { + // Check if the internationalization (i18n) system is initialized. + if (!getIsInitialized()) { + // Initialize the i18n system if it is not already initialized. + initI18n(); + } + // Get the localized trainer class name from the i18n file and set it as the title. + // This is used for trainer class names, not titles like "Elite Four, Champion, etc." + title = i18next.t(`trainerClasses:${name.toLowerCase().replace(/\s/g, "_")}`); + } + + // If no specific trainer slot is set. + if (!trainerSlot) { + // Use the trainer's name. + name = this.name; + // If there is a partner name, concatenate it with the trainer's name using "&". + if (this.partnerName) { + name = `${name} & ${this.partnerName}`; + } + } else { + // Assign the name based on the trainer slot: + // Use 'this.name' if 'trainerSlot' is TRAINER. + // Otherwise, use 'this.partnerName' if it exists, or 'this.name' if it doesn't. + name = trainerSlot === TrainerSlot.TRAINER ? this.name : this.partnerName || this.name; + } + } + + if (this.config.titleDouble && this.variant === TrainerVariant.DOUBLE && !this.config.doubleOnly) { + title = this.config.titleDouble; + name = i18next.t(`trainerNames:${this.config.nameDouble.toLowerCase().replace(/\s/g, "_")}`); + } + + // Return the formatted name, including the title if it is set. + return name || ""; + } + getTitleOnly(trainerSlot: TrainerSlot = TrainerSlot.NONE): string { + // Get the base title based on the trainer slot and variant. + let name = this.config.getTitle(trainerSlot, this.variant); + + // Determine the title to include based on the configuration and includeTitle flag. + let title = true && this.config.title ? this.config.title : null; + + if (this.name === "" && name.toLowerCase().includes("grunt")) { + // This is a evil team grunt so we localize it by only using the "name" as the title + title = i18next.t(`trainerClasses:${name.toLowerCase().replace(/\s/g, "_")}`); + console.log("Localized grunt name: " + title); + // Since grunts are not named we can just return the title + return title; + } + + // If the trainer has a name (not null or undefined). + if (this.name) { + // If the title should be included. + if (true) { + // Check if the internationalization (i18n) system is initialized. + if (!getIsInitialized()) { + // Initialize the i18n system if it is not already initialized. + initI18n(); + } + // Get the localized trainer class name from the i18n file and set it as the title. + // This is used for trainer class names, not titles like "Elite Four, Champion, etc." + title = i18next.t(`trainerClasses:${name.toLowerCase().replace(/\s/g, "_")}`); + } + + // If no specific trainer slot is set. + if (!trainerSlot) { + // Use the trainer's name. + name = this.name; + // If there is a partner name, concatenate it with the trainer's name using "&". + if (this.partnerName) { + name = `${name} & ${this.partnerName}`; + } + } else { + // Assign the name based on the trainer slot: + // Use 'this.name' if 'trainerSlot' is TRAINER. + // Otherwise, use 'this.partnerName' if it exists, or 'this.name' if it doesn't. + name = trainerSlot === TrainerSlot.TRAINER ? this.name : this.partnerName || this.name; + } + } + + if (this.config.titleDouble && this.variant === TrainerVariant.DOUBLE && !this.config.doubleOnly) { + title = this.config.titleDouble; + name = i18next.t(`trainerNames:${this.config.nameDouble.toLowerCase().replace(/\s/g, "_")}`); + } + + // Return the formatted name, including the title if it is set. + return title || ""; + } isDouble(): boolean { diff --git a/src/logger.ts b/src/logger.ts index 3bcd9b9e5e4..1a6da1a447c 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1093,24 +1093,10 @@ export interface TrainerData { * @returns The Trainer data. */ export function exportTrainer(trainer: Trainer): TrainerData { - if (trainer.config.getTitle(0, trainer.variant) == "Finn") { - return { - id: trainer.config.trainerType, - name: "Finn", - type: "Rival" - } - } - if (trainer.config.getTitle(0, trainer.variant) == "Ivy") { - return { - id: trainer.config.trainerType, - name: "Ivy", - type: "Rival" - } - } return { id: trainer.config.trainerType, - name: trainer.name, - type: trainer.config.getTitle(0, trainer.variant) + name: trainer.getNameOnly(), + type: trainer.getTitleOnly() } } /** diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index 54024bafc67..bc7cc0a15d0 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -682,7 +682,7 @@ export default class FightUiHandler extends UiHandler { if (Math.floor(dmgLow) >= target.hp) { koText = " (KO)" } else if (Math.ceil(dmgHigh) >= target.hp) { - var percentChance = (target.hp - dmgLow + 1) / (dmgHigh - dmgLow + 1) + var percentChance = 1 - ((target.hp - dmgLow + 1) / (dmgHigh - dmgLow + 1)) koText = " (" + Math.round(percentChance * 100) + "% KO)" } if (target.getMoveEffectiveness(user, move) == undefined) { From cef161c69c8928145363d65908286b528eef0975 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Tue, 16 Jul 2024 11:44:25 -0400 Subject: [PATCH 89/94] working on fixes yay me when when game is broken --- src/logger.ts | 7 ++++++- src/phases.ts | 33 ++++++--------------------------- 2 files changed, 12 insertions(+), 28 deletions(-) diff --git a/src/logger.ts b/src/logger.ts index 1a6da1a447c..282324ce32d 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1502,6 +1502,7 @@ export function flagReset(scene: BattleScene, floor: integer = undefined) { if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) var drpd = getDRPD(scene) + console.log("Flag Reset", drpd) var wv = getWave(drpd, floor, scene) wv.reload = true; console.log(drpd) @@ -1526,7 +1527,11 @@ export function flagResetIfExists(scene: BattleScene, floor: integer = undefined } } } - if (!waveExists) return; + if (!waveExists) { + console.log("Skipped wave reset because this is not a reload", drpd) + return; + } + console.log("Flag reset as wave was already played before", drpd) var wv = getWave(drpd, floor, scene) wv.reload = true; console.log(drpd) diff --git a/src/phases.ts b/src/phases.ts index 9701c87c2c6..6345fb6e83a 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -3055,7 +3055,7 @@ export class TurnStartPhase extends FieldPhase { if (!queuedMove) { continue; } - LoggerTools.Actions[pokemon.getBattlerIndex()] = new PokemonMove(queuedMove.move).getName() + LoggerTools.Actions[pokemon.getBattlerIndex()] = `[[ ${new PokemonMove(queuedMove.move).getName()} unknown target ]]` break; case Command.BALL: var ballNames = [ @@ -3144,9 +3144,10 @@ export class TurnStartPhase extends FieldPhase { const move = pokemon.getMoveset().find(m => m.moveId === queuedMove.move) || new PokemonMove(queuedMove.move); if (pokemon.isPlayer()) { if (turnCommand.cursor === -1) { + console.log("turncommand cursor was -1 -- running TOP block") this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move.targets, move)); var targets = turnCommand.targets || turnCommand.move.targets - var mv = new PokemonMove(queuedMove.move) + var mv = move if (pokemon.isPlayer()) { console.log(turnCommand.targets, turnCommand.move.targets) LoggerTools.Actions[pokemon.getBattlerIndex()] = mv.getName() @@ -3171,9 +3172,10 @@ export class TurnStartPhase extends FieldPhase { console.log(mv.getName(), targets) } } else { + console.log("turncommand = ", turnCommand, " -- running BOTTO< block") const playerPhase = new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move.targets, move, false, queuedMove.ignorePP); var targets = turnCommand.targets || turnCommand.move.targets - var mv = new PokemonMove(queuedMove.move) + var mv = move if (pokemon.isPlayer()) { console.log(turnCommand.targets, turnCommand.move.targets) LoggerTools.Actions[pokemon.getBattlerIndex()] = mv.getName() @@ -3202,30 +3204,7 @@ export class TurnStartPhase extends FieldPhase { } else { this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move.targets, move, false, queuedMove.ignorePP)); var targets = turnCommand.targets || turnCommand.move.targets - var mv = new PokemonMove(queuedMove.move) - if (pokemon.isPlayer()) { - console.log(turnCommand.targets, turnCommand.move.targets) - LoggerTools.Actions[pokemon.getBattlerIndex()] = mv.getName() - if (this.scene.currentBattle.double) { - var targIDs = ["Counter", "Self", "Ally", "L", "R"] - if (pokemon.getBattlerIndex() == 1) targIDs = ["Counter", "Ally", "Self", "L", "R"] - LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) - } else { - var targIDs = ["Counter", "", "", "", ""] - var myField = this.scene.getField() - if (myField[0]) - targIDs[1] = myField[0].name - if (myField[1]) - targIDs[2] = myField[1].name - var eField = this.scene.getEnemyField() - if (eField[0]) - targIDs[3] = eField[0].name - if (eField[1]) - targIDs[4] = eField[1].name - //LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) - } - console.log(mv.getName(), targets) - } + var mv = new PokemonMove(queuedMove.move) } break; case Command.BALL: From 13e1fd3b0aedcbc21ec7e7882ba6d003b5ae4c5d Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Tue, 16 Jul 2024 16:38:13 -0400 Subject: [PATCH 90/94] Disable Shiny Luck in Daily Disables shiny luck in daily (can be turned back on in Settings) * Shinies can still appear and do have their custom sprites, they just don't affect rolls. Added a currently nonfunctional setting to change how the quickload button appears (or disable it, locking it to "Continue") Added an in-progress UI for displaying Daily Runs (currently disabled) --- src/battle-scene.ts | 30 ++- src/logger.ts | 64 +++++- src/modifier/modifier-type.ts | 37 ++-- src/phases.ts | 14 +- src/system/settings/settings.ts | 38 +++- src/ui/log-select-ui-handler.ts | 355 ++++++++++++++++++++++++++++++++ src/ui/ui.ts | 9 +- 7 files changed, 526 insertions(+), 21 deletions(-) create mode 100644 src/ui/log-select-ui-handler.ts diff --git a/src/battle-scene.ts b/src/battle-scene.ts index c42b16fc126..38691decd52 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -125,6 +125,8 @@ export default class BattleScene extends SceneBase { public menuChangesBiome: boolean = false; public showAutosaves: boolean = false; public doBiomePanels: boolean = false; + public disableDailyShinies: boolean = true; // Disables shiny luck in Daily Runs to prevent affecting RNG + public quickloadDisplayMode: string = "Dailies"; /** * Determines the condition for a notification should be shown for Candy Upgrades * - 0 = 'Off' @@ -903,6 +905,32 @@ export default class BattleScene extends SceneBase { return container; } + addPkIcon(pokemon: PokemonSpecies, form: integer = 0, x: number, y: number, originX: number = 0.5, originY: number = 0.5, ignoreOverride: boolean = false): Phaser.GameObjects.Container { + const container = this.add.container(x, y); + container.setName(`${pokemon.name}-icon`); + + const icon = this.add.sprite(0, 0, pokemon.getIconAtlasKey(form)); + icon.setName(`sprite-${pokemon.name}-icon`); + icon.setFrame(pokemon.getIconId(true)); + // Temporary fix to show pokemon's default icon if variant icon doesn't exist + if (icon.frame.name !== pokemon.getIconId(true)) { + console.log(`${pokemon.name}'s variant icon does not exist. Replacing with default.`); + icon.setTexture(pokemon.getIconAtlasKey(0)); + icon.setFrame(pokemon.getIconId(true)); + } + icon.setOrigin(0.5, 0); + + container.add(icon); + + if (originX !== 0.5) { + container.x -= icon.width * (originX - 0.5); + } + if (originY !== 0) { + container.y -= icon.height * originY; + } + + return container; + } setSeed(seed: string): void { this.seed = seed; @@ -2342,7 +2370,7 @@ export default class BattleScene extends SceneBase { if (isBoss) { count = Math.max(count, Math.floor(chances / 2)); } - getEnemyModifierTypesForWave(difficultyWaveIndex, count, [ enemyPokemon ], this.currentBattle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD, upgradeChance) + getEnemyModifierTypesForWave(difficultyWaveIndex, count, [ enemyPokemon ], this.currentBattle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD, upgradeChance, this) .map(mt => mt.newModifier(enemyPokemon).add(this.enemyModifiers, false, this)); }); diff --git a/src/logger.ts b/src/logger.ts index 282324ce32d..ca46c913d01 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -9,7 +9,7 @@ import { OptionSelectItem } from "./ui/abstact-option-select-ui-handler"; import { PokemonHeldItemModifier } from "./modifier/modifier"; import { getBiomeName, PokemonPools, SpeciesTree } from "./data/biomes"; import { Mode } from "./ui/ui"; -import { TitlePhase } from "./phases"; +import { parseSlotData, TitlePhase } from "./phases"; import Trainer from "./field/trainer"; import { Species } from "./enums/species"; import { GameMode, GameModes } from "./game-mode"; @@ -139,7 +139,14 @@ export function getLogs() { logs.pop() for (var i = 0; i < localStorage.length; i++) { if (localStorage.key(i).substring(0, 9) == "drpd_log:") { - logs.push(["drpd.json", localStorage.key(i), localStorage.key(i).substring(9), "drpd_items:" + localStorage.key(i).substring(9), "", ""]) + logs.push(["drpd.json", localStorage.key(i), localStorage.key(i).substring(9), "", "", ""]) + for (var j = 0; j < 5; j++) { + var D = parseSlotData(j) + if (D != undefined) + if (logs[logs.length - 1][2] == D.seed) { + logs[logs.length - 1][3] = j.toString() + } + } } } } @@ -1301,6 +1308,59 @@ export function generateEditOption(scene: BattleScene, i: integer, saves: any, p } return op; } +/** + * Generates a UI option to save a log to your device. + * @param i The slot number. Corresponds to an index in `logs`. + * @param saves Your session data. Used to label logs if they match one of your save slots. + * @returns A UI option. + */ +export function generateEditHandler(scene: BattleScene, logId: string, callback: Function) { + var i; + for (var j = 0; j < logs.length; j++) { + if (logs[j][2] == logId) { + i = j; + } + } + if (i == undefined) + return; // Failed to find a log + return (): boolean => { + rarityslot[1] = logs[i][1] + //scene.phaseQueue[0].end() + scene.ui.setMode(Mode.NAME_LOG, { + autofillfields: [ + (JSON.parse(localStorage.getItem(logs[i][1])) as DRPD).title, + (JSON.parse(localStorage.getItem(logs[i][1])) as DRPD).authors.join(", "), + (JSON.parse(localStorage.getItem(logs[i][1])) as DRPD).label, + ], + buttonActions: [ + () => { + console.log("Rename") + scene.ui.playSelect(); + callback() + }, + () => { + console.log("Export") + scene.ui.playSelect(); + downloadLogByID(i) + callback() + }, + () => { + console.log("Export to Sheets") + scene.ui.playSelect(); + downloadLogByIDToSheet(i) + callback() + }, + () => { + console.log("Delete") + scene.ui.playSelect(); + localStorage.removeItem(logs[i][1]) + callback() + } + ] + }); + return false; + } +} //#endregion diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 91702496c8d..09748ad4906 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -27,6 +27,7 @@ import { BattlerTagType } from "#enums/battler-tag-type"; import { BerryType } from "#enums/berry-type"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import { GameModes } from "#app/game-mode.js"; const outputModifierData = false; const useMaxWeightForOutput = false; @@ -1955,14 +1956,14 @@ export function getModifierTypeFuncById(id: string): ModifierTypeFunc { return modifierTypes[id]; } -export function getPlayerModifierTypeOptions(count: integer, party: PlayerPokemon[], modifierTiers?: ModifierTier[]): ModifierTypeOption[] { +export function getPlayerModifierTypeOptions(count: integer, party: PlayerPokemon[], modifierTiers?: ModifierTier[], scene?: BattleScene): ModifierTypeOption[] { const options: ModifierTypeOption[] = []; const retryCount = Math.min(count * 5, 50); new Array(count).fill(0).map((_, i) => { - let candidate = getNewModifierTypeOption(party, ModifierPoolType.PLAYER, modifierTiers?.length > i ? modifierTiers[i] : undefined); + let candidate = getNewModifierTypeOption(party, ModifierPoolType.PLAYER, modifierTiers?.length > i ? modifierTiers[i] : undefined, undefined, undefined, scene); let r = 0; while (options.length && ++r < retryCount && options.filter(o => o.type.name === candidate.type.name || o.type.group === candidate.type.group).length) { - candidate = getNewModifierTypeOption(party, ModifierPoolType.PLAYER, candidate.type.tier, candidate.upgradeCount); + candidate = getNewModifierTypeOption(party, ModifierPoolType.PLAYER, candidate.type.tier, candidate.upgradeCount, undefined, scene); } options.push(candidate); }); @@ -2017,11 +2018,11 @@ export function getPlayerShopModifierTypeOptionsForWave(waveIndex: integer, base export function getEnemyBuffModifierForWave(tier: ModifierTier, enemyModifiers: Modifiers.PersistentModifier[], scene: BattleScene): Modifiers.EnemyPersistentModifier { const tierStackCount = tier === ModifierTier.ULTRA ? 5 : tier === ModifierTier.GREAT ? 3 : 1; const retryCount = 50; - let candidate = getNewModifierTypeOption(null, ModifierPoolType.ENEMY_BUFF, tier); + let candidate = getNewModifierTypeOption(null, ModifierPoolType.ENEMY_BUFF, tier, undefined, undefined, scene); let r = 0; let matchingModifier: Modifiers.PersistentModifier; while (++r < retryCount && (matchingModifier = enemyModifiers.find(m => m.type.id === candidate.type.id)) && matchingModifier.getMaxStackCount(scene) < matchingModifier.stackCount + (r < 10 ? tierStackCount : 1)) { - candidate = getNewModifierTypeOption(null, ModifierPoolType.ENEMY_BUFF, tier); + candidate = getNewModifierTypeOption(null, ModifierPoolType.ENEMY_BUFF, tier, undefined, undefined, scene); } const modifier = candidate.type.newModifier() as Modifiers.EnemyPersistentModifier; @@ -2030,21 +2031,21 @@ export function getEnemyBuffModifierForWave(tier: ModifierTier, enemyModifiers: return modifier; } -export function getEnemyModifierTypesForWave(waveIndex: integer, count: integer, party: EnemyPokemon[], poolType: ModifierPoolType.WILD | ModifierPoolType.TRAINER, upgradeChance: integer = 0): PokemonHeldItemModifierType[] { - const ret = new Array(count).fill(0).map(() => getNewModifierTypeOption(party, poolType, undefined, upgradeChance && !Utils.randSeedInt(upgradeChance) ? 1 : 0).type as PokemonHeldItemModifierType); +export function getEnemyModifierTypesForWave(waveIndex: integer, count: integer, party: EnemyPokemon[], poolType: ModifierPoolType.WILD | ModifierPoolType.TRAINER, upgradeChance: integer = 0, scene?: BattleScene): PokemonHeldItemModifierType[] { + const ret = new Array(count).fill(0).map(() => getNewModifierTypeOption(party, poolType, undefined, upgradeChance && !Utils.randSeedInt(upgradeChance) ? 1 : 0, undefined, scene).type as PokemonHeldItemModifierType); if (!(waveIndex % 1000)) { ret.push(getModifierType(modifierTypes.MINI_BLACK_HOLE) as PokemonHeldItemModifierType); } return ret; } -export function getDailyRunStarterModifiers(party: PlayerPokemon[]): Modifiers.PokemonHeldItemModifier[] { +export function getDailyRunStarterModifiers(party: PlayerPokemon[], scene?: BattleScene): Modifiers.PokemonHeldItemModifier[] { const ret: Modifiers.PokemonHeldItemModifier[] = []; for (const p of party) { for (let m = 0; m < 3; m++) { const tierValue = Utils.randSeedInt(64); const tier = tierValue > 25 ? ModifierTier.COMMON : tierValue > 12 ? ModifierTier.GREAT : tierValue > 4 ? ModifierTier.ULTRA : tierValue ? ModifierTier.ROGUE : ModifierTier.MASTER; - const modifier = getNewModifierTypeOption(party, ModifierPoolType.DAILY_STARTER, tier).type.newModifier(p) as Modifiers.PokemonHeldItemModifier; + const modifier = getNewModifierTypeOption(party, ModifierPoolType.DAILY_STARTER, tier, undefined, undefined, scene).type.newModifier(p) as Modifiers.PokemonHeldItemModifier; ret.push(modifier); } } @@ -2052,7 +2053,7 @@ export function getDailyRunStarterModifiers(party: PlayerPokemon[]): Modifiers.P return ret; } -function getNewModifierTypeOption(party: Pokemon[], poolType: ModifierPoolType, tier?: ModifierTier, upgradeCount?: integer, retryCount: integer = 0): ModifierTypeOption { +function getNewModifierTypeOption(party: Pokemon[], poolType: ModifierPoolType, tier?: ModifierTier, upgradeCount?: integer, retryCount: integer = 0, scene?: BattleScene): ModifierTypeOption { const player = !poolType; const pool = getModifierPoolForType(poolType); let thresholds: object; @@ -2079,7 +2080,12 @@ function getNewModifierTypeOption(party: Pokemon[], poolType: ModifierPoolType, upgradeCount = 0; } if (player && tierValue) { - const partyLuckValue = getPartyLuckValue(party); + var partyLuckValue = getPartyLuckValue(party); + if (scene) { + if (scene.gameMode.modeId == GameModes.DAILY && scene.disableDailyShinies) { + partyLuckValue = 0 + } + } const upgradeOdds = Math.floor(128 / ((partyLuckValue + 4) / 4)); let upgraded = false; do { @@ -2104,7 +2110,12 @@ function getNewModifierTypeOption(party: Pokemon[], poolType: ModifierPoolType, } else if (upgradeCount === undefined && player) { upgradeCount = 0; if (tier < ModifierTier.MASTER) { - const partyShinyCount = party.filter(p => p.isShiny() && !p.isFainted()).length; + var partyShinyCount = party.filter(p => p.isShiny() && !p.isFainted()).length; + if (scene) { + if (scene.gameMode.modeId == GameModes.DAILY && scene.disableDailyShinies) { + partyShinyCount = 0 + } + } const upgradeOdds = Math.floor(32 / ((partyShinyCount + 2) / 2)); while (modifierPool.hasOwnProperty(tier + upgradeCount + 1) && modifierPool[tier + upgradeCount + 1].length) { if (!Utils.randSeedInt(upgradeOdds)) { @@ -2146,7 +2157,7 @@ function getNewModifierTypeOption(party: Pokemon[], poolType: ModifierPoolType, if (player) { console.log(ModifierTier[tier], upgradeCount); } - return getNewModifierTypeOption(party, poolType, tier, upgradeCount, ++retryCount); + return getNewModifierTypeOption(party, poolType, tier, upgradeCount, ++retryCount, scene); } } diff --git a/src/phases.ts b/src/phases.ts index 6345fb6e83a..7d22f02d4ab 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -652,6 +652,18 @@ export class TitlePhase extends Phase { handler: () => { this.scene.biomeChangeMode = false return this.logRenameMenu() + /* + this.scene.ui.setOverlayMode(Mode.LOG_HANDLER, + (k: string) => { + if (k === undefined) { + return this.showOptions(); + } + console.log(k) + }, () => { + this.showOptions(); + }); + return true; + */ } }) options.push({ @@ -6496,7 +6508,7 @@ export class SelectModifierPhase extends BattlePhase { } getModifierTypeOptions(modifierCount: integer): ModifierTypeOption[] { - return getPlayerModifierTypeOptions(modifierCount, this.scene.getParty(), this.scene.lockModifierTiers ? this.modifierTiers : undefined); + return getPlayerModifierTypeOptions(modifierCount, this.scene.getParty(), this.scene.lockModifierTiers ? this.modifierTiers : undefined, this.scene); } addModifier(modifier: Modifier): Promise { diff --git a/src/system/settings/settings.ts b/src/system/settings/settings.ts index 611ed8f15ad..5bf04301dfc 100644 --- a/src/system/settings/settings.ts +++ b/src/system/settings/settings.ts @@ -103,7 +103,10 @@ export const SettingKeys = { LazyReloads: "FLAG_EVERY_RESET_AS_RELOAD", FancyBiome: "FANCY_BIOMES", ShowAutosaves: "SHOW_AUTOSAVES", - BiomePanels: "BIOME_PANELS" + TitleScreenContinueMode: "TITLE_SCREEN_QUICKLOAD", + BiomePanels: "BIOME_PANELS", + DailyShinyLuck: "DAILY_LUCK", + QuickloadDisplay: "QUICKLOAD_MODE" }; /** @@ -192,6 +195,35 @@ export const Setting: Array = [ default: 0, type: SettingType.GENERAL, }, + { + key: SettingKeys.TitleScreenContinueMode, + label: "Quick Load", + options: [{ + label: "Off", + value: "Off" // Shows "Continue" button on the home screen + }, { + label: "Daily", + value: "Daily" // Shows the last played Daily Run, or the last run if there are no Daily Runs + }, { + label: "Dailies", + value: "Dailies" // Shows all Daily Runs, or the last run if there are no Daily Runs + }, { + label: "Latest", + value: "Latest" // Shows the last run + }, { + label: "Both", + value: "Both" // Shows the last run and the last Daily Run, or only the last played game if it is a Daily Run + }], + default: 2, + type: SettingType.GENERAL, + }, + { + key: SettingKeys.DailyShinyLuck, + label: "Daily Shiny Luck", + options: OFF_ON, + default: 0, + type: SettingType.GENERAL, + }, { key: SettingKeys.HP_Bar_Speed, label: i18next.t("settings:hpBarSpeed"), @@ -688,6 +720,10 @@ export function setSetting(scene: BattleScene, setting: string, value: integer): scene.showAutosaves = Setting[index].options[value].value == "On" case SettingKeys.BiomePanels: scene.doBiomePanels = Setting[index].options[value].value == "On" + case SettingKeys.DailyShinyLuck: + scene.disableDailyShinies = Setting[index].options[value].value == "Off" + case SettingKeys.QuickloadDisplay: + scene.quickloadDisplayMode = Setting[index].options[value].value; case SettingKeys.Skip_Seen_Dialogues: scene.skipSeenDialogues = Setting[index].options[value].value === "On"; break; diff --git a/src/ui/log-select-ui-handler.ts b/src/ui/log-select-ui-handler.ts new file mode 100644 index 00000000000..bd5d8057f01 --- /dev/null +++ b/src/ui/log-select-ui-handler.ts @@ -0,0 +1,355 @@ +import i18next from "i18next"; +import BattleScene from "../battle-scene"; +import { Button } from "#enums/buttons"; +import { GameMode } from "../game-mode"; +import { PokemonHeldItemModifier } from "../modifier/modifier"; +import { SessionSaveData } from "../system/game-data"; +import PokemonData from "../system/pokemon-data"; +import * as Utils from "../utils"; +import MessageUiHandler from "./message-ui-handler"; +import { TextStyle, addTextObject } from "./text"; +import { Mode } from "./ui"; +import { addWindow } from "./ui-theme"; +import * as LoggerTools from "../logger" +import { loggedInUser } from "#app/account.js"; +import { allpanels, biomePanelIDs } from "../loading-scene" +import { getBiomeName } from "#app/data/biomes.js"; +import { Species } from "#app/enums/species.js"; +import { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species.js"; + +const sessionSlotCount = 5; + +export type LogSelectCallback = (key: string) => void; + +export default class LogSelectUiHandler extends MessageUiHandler { + + private saveSlotSelectContainer: Phaser.GameObjects.Container; + private sessionSlotsContainer: Phaser.GameObjects.Container; + private saveSlotSelectMessageBox: Phaser.GameObjects.NineSlice; + private saveSlotSelectMessageBoxContainer: Phaser.GameObjects.Container; + private sessionSlots: SessionSlot[]; + + private selectCallback: LogSelectCallback; + + private scrollCursor: integer = 0; + + private cursorObj: Phaser.GameObjects.NineSlice; + + private sessionSlotsContainerInitialY: number; + + constructor(scene: BattleScene) { + super(scene, Mode.LOG_HANDLER); + } + + setup() { + const ui = this.getUi(); + + this.saveSlotSelectContainer = this.scene.add.container(0, 0); + this.saveSlotSelectContainer.setVisible(false); + ui.add(this.saveSlotSelectContainer); + + const loadSessionBg = this.scene.add.rectangle(0, 0, this.scene.game.canvas.width / 6, -this.scene.game.canvas.height / 6, 0x006860); + loadSessionBg.setOrigin(0, 0); + this.saveSlotSelectContainer.add(loadSessionBg); + + this.sessionSlotsContainerInitialY = -this.scene.game.canvas.height / 6 + 8; + + this.sessionSlotsContainer = this.scene.add.container(8, this.sessionSlotsContainerInitialY); + this.saveSlotSelectContainer.add(this.sessionSlotsContainer); + + this.saveSlotSelectMessageBoxContainer = this.scene.add.container(0, 0); + this.saveSlotSelectMessageBoxContainer.setVisible(false); + this.saveSlotSelectContainer.add(this.saveSlotSelectMessageBoxContainer); + + this.saveSlotSelectMessageBox = addWindow(this.scene, 1, -1, 318, 28); + this.saveSlotSelectMessageBox.setOrigin(0, 1); + this.saveSlotSelectMessageBoxContainer.add(this.saveSlotSelectMessageBox); + + this.message = addTextObject(this.scene, 8, 8, "", TextStyle.WINDOW, { maxLines: 2 }); + this.message.setOrigin(0, 0); + this.saveSlotSelectMessageBoxContainer.add(this.message); + + this.sessionSlots = []; + } + + show(args: any[]): boolean { + if ((args.length < 1 || !(args[0] instanceof Function))) { + return false; + } + + super.show(args); + + this.selectCallback = args[0] as LogSelectCallback; + + this.saveSlotSelectContainer.setVisible(true); + this.populateSessionSlots(); + this.setScrollCursor(0); + this.setCursor(0); + + return true; + } + + processInput(button: Button): boolean { + const ui = this.getUi(); + + let success = false; + let error = false; + + if (button === Button.ACTION || button === Button.CANCEL) { + const originalCallback = this.selectCallback; + if (button === Button.ACTION) { + const cursor = this.cursor + this.scrollCursor; + this.selectCallback = null; + originalCallback(this.sessionSlots[cursor].key); + success = true; + } else { + this.selectCallback = null; + originalCallback(undefined); + success = true; + } + } else { + switch (button) { + case Button.UP: + if (this.cursor) { + success = this.setCursor(this.cursor - 1); + } else if (this.scrollCursor) { + success = this.setScrollCursor(this.scrollCursor - 1); + } + break; + case Button.DOWN: + if (this.cursor < 2) { + success = this.setCursor(this.cursor + 1); + } else if (this.scrollCursor < this.sessionSlots.length - 3) { + success = this.setScrollCursor(this.scrollCursor + 1); + } + break; + } + } + + if (success) { + ui.playSelect(); + } else if (error) { + ui.playError(); + } + + return success || error; + } + + populateSessionSlots() { + var ui = this.getUi(); + var ypos = 0; + LoggerTools.getLogs() + for (let s = 0; s < sessionSlotCount; s++) { + var found = false + for (var i = 0; i < LoggerTools.logs.length; i++) { + if (LoggerTools.logs[i][3] == s.toString()) { + found = true + const sessionSlot = new SessionSlot(this.scene, s, ypos); + ypos++ + sessionSlot.load(LoggerTools.logs[i][1]); + this.scene.add.existing(sessionSlot); + this.sessionSlotsContainer.add(sessionSlot); + this.sessionSlots.push(sessionSlot); + } + } + if (!found) { + const sessionSlot = new SessionSlot(this.scene, s, ypos); + ypos++ + sessionSlot.load(undefined); + this.scene.add.existing(sessionSlot); + this.sessionSlotsContainer.add(sessionSlot); + this.sessionSlots.push(sessionSlot); + } + } + for (var i = 0; i < LoggerTools.logs.length; i++) { + if (LoggerTools.logs[i][3] == "") { + const sessionSlot = new SessionSlot(this.scene, undefined, ypos); + ypos++ + sessionSlot.load(LoggerTools.logs[i][1]); + this.scene.add.existing(sessionSlot); + this.sessionSlotsContainer.add(sessionSlot); + this.sessionSlots.push(sessionSlot); + } + } + } + + showText(text: string, delay?: integer, callback?: Function, callbackDelay?: integer, prompt?: boolean, promptDelay?: integer) { + super.showText(text, delay, callback, callbackDelay, prompt, promptDelay); + + if (text?.indexOf("\n") === -1) { + this.saveSlotSelectMessageBox.setSize(318, 28); + this.message.setY(-22); + } else { + this.saveSlotSelectMessageBox.setSize(318, 42); + this.message.setY(-37); + } + + this.saveSlotSelectMessageBoxContainer.setVisible(!!text?.length); + } + + setCursor(cursor: integer): boolean { + const changed = super.setCursor(cursor); + + if (!this.cursorObj) { + this.cursorObj = this.scene.add.nineslice(0, 0, "select_cursor_highlight_thick", null, 296, 44, 6, 6, 6, 6); + this.cursorObj.setOrigin(0, 0); + this.sessionSlotsContainer.add(this.cursorObj); + } + this.cursorObj.setPosition(4, 4 + (cursor + this.scrollCursor) * 56); + + return changed; + } + + setScrollCursor(scrollCursor: integer): boolean { + const changed = scrollCursor !== this.scrollCursor; + + if (changed) { + this.scrollCursor = scrollCursor; + this.setCursor(this.cursor); + this.scene.tweens.add({ + targets: this.sessionSlotsContainer, + y: this.sessionSlotsContainerInitialY - 56 * scrollCursor, + duration: Utils.fixedInt(325), + ease: "Sine.easeInOut" + }); + } + + return changed; + } + + clear() { + super.clear(); + this.saveSlotSelectContainer.setVisible(false); + this.eraseCursor(); + this.selectCallback = null; + this.clearSessionSlots(); + } + + eraseCursor() { + if (this.cursorObj) { + this.cursorObj.destroy(); + } + this.cursorObj = null; + } + + clearSessionSlots() { + this.sessionSlots.splice(0, this.sessionSlots.length); + this.sessionSlotsContainer.removeAll(true); + } +} + +class SessionSlot extends Phaser.GameObjects.Container { + public slotId: integer; + public autoSlot: integer; + public hasData: boolean; + public wv: integer; + public key: string; + private loadingLabel: Phaser.GameObjects.Text; + + constructor(scene: BattleScene, slotId: integer = undefined, ypos: integer, autoSlot?: integer) { + super(scene, 0, ypos * 56); + + this.slotId = slotId; + this.autoSlot = autoSlot + + this.setup(); + } + + setup() { + const slotWindow = addWindow(this.scene, 0, 0, 304, 52); + this.add(slotWindow); + + this.loadingLabel = addTextObject(this.scene, 152, 26, i18next.t("saveSlotSelectUiHandler:loading"), TextStyle.WINDOW); + this.loadingLabel.setOrigin(0.5, 0.5); + this.add(this.loadingLabel); + } + + async setupWithData(data: LoggerTools.DRPD) { + this.remove(this.loadingLabel, true); + var lbl = `???` + lbl = data.title + if (this.slotId) { + lbl = `[${this.slotId}] ${lbl}` + } + console.log(data, this.slotId, this.autoSlot, lbl) + const gameModeLabel = addTextObject(this.scene, 8, 5, lbl, TextStyle.WINDOW); + this.add(gameModeLabel); + + const timestampLabel = addTextObject(this.scene, 8, 19, data.date, TextStyle.WINDOW); + this.add(timestampLabel); + + const playTimeLabel = addTextObject(this.scene, 8, 33, data.version + " / " + (data.label || "") + " / " + (data.uuid || ""), TextStyle.WINDOW); + this.add(playTimeLabel); + + const pokemonIconsContainer = this.scene.add.container(144, 4); + if (false || data.starters) + data.starters.forEach((p: LoggerTools.PokeData, i: integer) => { + if (p == undefined) + return; + const iconContainer = this.scene.add.container(26 * i, 0); + iconContainer.setScale(0.75); + + if (Utils.getEnumValues(Species)[p.id] == undefined) + return; + + if (getPokemonSpecies(Utils.getEnumValues(Species)[p.id]) == undefined) + return; + + const icon = this.scene.addPkIcon(getPokemonSpecies(Utils.getEnumValues(Species)[p.id]), 0, 0, 0, 0, 0); + + const text = addTextObject(this.scene, 32, 20, `${i18next.t("saveSlotSelectUiHandler:lv")}${Utils.formatLargeNumber(p.level, 1000)}`, TextStyle.PARTY, { fontSize: "54px", color: "#f8f8f8" }); + text.setShadow(0, 0, null); + text.setStroke("#424242", 14); + text.setOrigin(1, 0); + + iconContainer.add(icon); + iconContainer.add(text); + + pokemonIconsContainer.add(iconContainer); + }); + + this.add(pokemonIconsContainer); + + //const modifiersModule = await import("../modifier/modifier"); + + const modifierIconsContainer = this.scene.add.container(148, 30); + modifierIconsContainer.setScale(0.5); + let visibleModifierIndex = 0; + + this.add(modifierIconsContainer); + } + + load(l?: string, slot?: integer): Promise { + return new Promise(resolve => { + if (l == undefined) { + this.hasData = false; + this.loadingLabel.setText("No data for this run"); + resolve(false); + return; + } + this.key = l + if (slot) { + this.slotId = slot + } + this.setupWithData(JSON.parse(localStorage.getItem(l))) + resolve(true); + }); + return new Promise(resolve => { + this.scene.gameData.getSession(this.slotId, this.autoSlot).then(async sessionData => { + if (!sessionData) { + this.hasData = false; + this.loadingLabel.setText(i18next.t("saveSlotSelectUiHandler:empty")); + resolve(false); + return; + } + this.hasData = true; + await this.setupWithData(undefined); + resolve(true); + }); + }); + } +} + +interface SessionSlot { + scene: BattleScene; +} diff --git a/src/ui/ui.ts b/src/ui/ui.ts index d4dddab5922..09bf39e8db3 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -85,7 +85,8 @@ export enum Mode { UNAVAILABLE, OUTDATED, CHALLENGE_SELECT, - NAME_LOG + NAME_LOG, + LOG_HANDLER } const transitionModes = [ @@ -98,7 +99,8 @@ const transitionModes = [ Mode.EGG_LIST, Mode.EGG_GACHA, Mode.CHALLENGE_SELECT, - Mode.NAME_LOG + Mode.NAME_LOG, + Mode.LOG_HANDLER ]; const noTransitionModes = [ @@ -184,7 +186,8 @@ export default class UI extends Phaser.GameObjects.Container { new UnavailableModalUiHandler(scene), new OutdatedModalUiHandler(scene), new GameChallengesUiHandler(scene), - new LogNameFormUiHandler(scene) + new LogNameFormUiHandler(scene), + new TargetSelectUiHandler(scene) ]; } From 7d4c4118fe2943821aae8963f4b200620c10c079 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Thu, 18 Jul 2024 13:49:35 -0400 Subject: [PATCH 91/94] Fix formatting issue --- src/logger.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/logger.ts b/src/logger.ts index ca46c913d01..1d6c67c1838 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -468,7 +468,7 @@ export function printDRPD(inData: string, indent: string, drpd: DRPD): string { inData = printWave(inData, indent + " ", drpd.waves[i]) } } - inData += ",\n" + indent + " ]\n" + inData += "\n" + indent + " ]\n" } else { inData += ",\n" + indent + " \"waves\": []" } From 9d968917f57a5460c2064ef035be0170a39d7f53 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Thu, 18 Jul 2024 14:24:26 -0400 Subject: [PATCH 92/94] Fix capture logging Logs when a Pokemon is released from your party to add another one to the team Logs who a Poke Ball caught, next to the instruction to use it, so there's never any confusion Marks the shop as "Flee" if you ran away from battle Added logger function to append text onto the most recently logged action --- src/logger.ts | 24 ++++++++++++++++++++++++ src/phases.ts | 6 ++++-- src/ui/party-ui-handler.ts | 12 +++++++++++- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/logger.ts b/src/logger.ts index 1d6c67c1838..c6fd8d023c3 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1398,6 +1398,30 @@ export function logActions(scene: BattleScene, floor: integer, action: string) { console.log(drpd) localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) } +/** + * Logs the actions that the player took, adding text to the most recent action. + * @param scene The BattleScene. Used to get the log ID. + * @param floor The wave index to write to. + * @param action The text you want to add to the actions list. + * + * @see resetWaveActions + */ +export function appendAction(scene: BattleScene, floor: integer, action: string) { + if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) + var drpd = getDRPD(scene) + console.log("Append to Action", drpd) + var wv: Wave = getWave(drpd, floor, scene) + if (wv.double == undefined) + wv.double = false + if (wv.clearActionsFlag) { + console.log("Triggered clearActionsFlag") + wv.clearActionsFlag = false + wv.actions = [] + } + wv.actions[wv.actions.length - 1] = wv.actions[wv.actions.length - 1] + action + console.log(drpd) + localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) +} /** * Logs that a Pokémon was captured. * @param scene The BattleScene. Used to get the log ID. diff --git a/src/phases.ts b/src/phases.ts index 7d22f02d4ab..60a9b99303d 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -6142,6 +6142,8 @@ export class AttemptCapturePhase extends PokemonPhase { this.scene.pokemonInfoContainer.show(pokemon, true); // Update new IVs this.scene.gameData.updateSpeciesDexIvs(pokemon.species.getRootSpeciesId(true), pokemon.ivs); + + LoggerTools.appendAction(this.scene, this.scene.currentBattle.waveIndex, ` (Catches ${pokemon.name})`) this.scene.ui.showText(i18next.t("battle:pokemonCaught", { pokemonName: pokemon.name }), null, () => { const end = () => { @@ -6192,7 +6194,7 @@ export class AttemptCapturePhase extends PokemonPhase { promptRelease(); } }); - }); + }, undefined, undefined, undefined, undefined, pokemon.name); }, () => { // NO LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, "Don't keep " + pokemon.name) @@ -6249,7 +6251,7 @@ export class AttemptRunPhase extends PokemonPhase { if (playerPokemon.randSeedInt(256) < escapeChance.value) { this.scene.playSound("flee"); - LoggerTools.logShop(this.scene, this.scene.currentBattle.waveIndex, "") + LoggerTools.logShop(this.scene, this.scene.currentBattle.waveIndex, "Fled") this.scene.queueMessage(i18next.t("battle:runAwaySuccess"), null, true, 500); this.scene.tweens.add({ diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index 32ce45d2a63..1e2fd6f98f2 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -110,6 +110,8 @@ export default class PartyUiHandler extends MessageUiHandler { private tmMoveId: Moves; private showMovePp: boolean; + private incomingMon: string; + private iconAnimHandler: PokemonIconAnimHandler; private static FilterAll = (_pokemon: PlayerPokemon) => null; @@ -251,6 +253,7 @@ export default class PartyUiHandler extends MessageUiHandler { : PartyUiHandler.FilterAllMoves; this.tmMoveId = args.length > 5 && args[5] ? args[5] : Moves.NONE; this.showMovePp = args.length > 6 && args[6]; + this.incomingMon = args.length > 7 && args[7] ? args[7] : undefined this.partyContainer.setVisible(true); this.partyBg.setTexture(`party_bg${this.scene.currentBattle.double ? "_double" : ""}`); @@ -284,7 +287,15 @@ export default class PartyUiHandler extends MessageUiHandler { if (this.optionsMode) { const option = this.options[this.optionsCursor]; if (button === Button.ACTION) { + //console.log("Menu Action (" + option + " - targ " + PartyOption.RELEASE + ")") const pokemon = this.scene.getParty()[this.cursor]; + if (option === PartyOption.RELEASE) { + if (this.incomingMon != undefined) { + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, `Add ${this.incomingMon}, replacing ${this.scene.getParty()[this.cursor].name} (Slot ${this.cursor + 1})`) + } else { + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, `Release ${this.scene.getParty()[this.cursor].name} (Slot ${this.cursor + 1})`) + } + } if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER && !this.transferMode && option !== PartyOption.CANCEL) { this.startTransfer(); this.clearOptions(); @@ -407,7 +418,6 @@ export default class PartyUiHandler extends MessageUiHandler { this.showText(i18next.t("partyUiHandler:releaseConfirmation", { pokemonName: pokemon.name }), null, () => { ui.setModeWithoutClear(Mode.CONFIRM, () => { ui.setMode(Mode.PARTY); - LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, `Add ${pokemon.name} to party, replacing ${this.scene.getParty()[this.cursor].name} (Slot ${this.cursor + 1})`) this.doRelease(this.cursor); }, () => { ui.setMode(Mode.PARTY); From ff40f569926051e1e7642a162427c2fbef1f319a Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Thu, 18 Jul 2024 14:31:49 -0400 Subject: [PATCH 93/94] Testing Commented these log statements out, but may add them back later --- src/logger.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/logger.ts b/src/logger.ts index c6fd8d023c3..cbba195bee3 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -293,10 +293,12 @@ export function playerPokeName(scene: BattleScene, index: integer | Pokemon | Pl } } if (typeof index == "number") { + //console.log(scene.getParty()[index], species, dupeSpecies) if (dupeSpecies.includes(scene.getParty()[index].name)) return scene.getParty()[index].name + " (Slot " + (index + 1) + ")" return scene.getParty()[index].name } + //console.log(index.name, species, dupeSpecies) if (dupeSpecies.includes(index.name)) return index.name + " (Slot " + (scene.getParty().indexOf(index as PlayerPokemon) + 1) + ")" return index.name @@ -320,10 +322,12 @@ export function enemyPokeName(scene: BattleScene, index: integer | Pokemon | Ene } } if (typeof index == "number") { + //console.log(scene.getEnemyParty()[index], species, dupeSpecies) if (dupeSpecies.includes(scene.getEnemyParty()[index].name)) return scene.getEnemyParty()[index].name + " (Slot " + (index + 1) + ")" return scene.getEnemyParty()[index].name } + //console.log(index.name, species, dupeSpecies) if (dupeSpecies.includes(index.name)) return index.name + " (Slot " + (scene.getEnemyParty().indexOf(index as EnemyPokemon) + 1) + ")" return index.name From e93b84cef57612804e043be613dfc5b91853bfcf Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Thu, 18 Jul 2024 15:17:40 -0400 Subject: [PATCH 94/94] Title screen setting Can change how the quick-load button on the title screen appears Changed grunts' title to "Grunt" --- src/field/trainer.ts | 1 + src/phases.ts | 91 +++++++++++++++++++++++++-------- src/system/settings/settings.ts | 7 ++- 3 files changed, 75 insertions(+), 24 deletions(-) diff --git a/src/field/trainer.ts b/src/field/trainer.ts index a543568fbc7..24fa56f6b55 100644 --- a/src/field/trainer.ts +++ b/src/field/trainer.ts @@ -229,6 +229,7 @@ export default class Trainer extends Phaser.GameObjects.Container { let title = true && this.config.title ? this.config.title : null; if (this.name === "" && name.toLowerCase().includes("grunt")) { + return "Grunt"; // This is a evil team grunt so we localize it by only using the "name" as the title title = i18next.t(`trainerClasses:${name.toLowerCase().replace(/\s/g, "_")}`); console.log("Localized grunt name: " + title); diff --git a/src/phases.ts b/src/phases.ts index 60a9b99303d..e034a9ed88b 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -403,12 +403,12 @@ export class TitlePhase extends Phase { }); } - getLastSave(log?: boolean, dailyOnly?: boolean): SessionSaveData { + getLastSave(log?: boolean, dailyOnly?: boolean, noDaily?: 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) { + if ((!noDaily && !dailyOnly) || (s.gameMode == GameModes.DAILY && dailyOnly) || (s.gameMode != GameModes.DAILY && noDaily)) { saves.push([i, s, s.timestamp]); } } @@ -419,6 +419,36 @@ export class TitlePhase extends Phase { if (saves[0] == undefined) return undefined; return saves[0][1] } + getLastSavesOfEach(log?: boolean): SessionSaveData[] { + var saves: Array> = []; + for (var i = 0; i < 5; i++) { + var s = parseSlotData(i); + if (s != undefined) { + saves.push([i, s, s.timestamp]); + } + } + saves.sort((a, b): integer => {return b[2] - a[2]}) + if (log) console.log(saves) + if (saves == undefined) return undefined; + if (saves[0] == undefined) return undefined; + var validSaves = [] + var hasNormal = false; + var hasDaily = false; + for (var i = 0; i < saves.length; i++) { + if (saves[i][1].gameMode == GameModes.DAILY && !hasDaily) { + hasDaily = true; + validSaves.push(saves[i]) + } + if (saves[i][1].gameMode != GameModes.DAILY && !hasNormal) { + hasNormal = true; + validSaves.push(saves[i]) + } + } + console.log(saves, validSaves) + if (validSaves.length == 0) + return undefined; + return validSaves.map(f => f[1]); + } getSaves(log?: boolean, dailyOnly?: boolean): SessionSaveData[] { var saves: Array> = []; for (var i = 0; i < 5; i++) { @@ -549,10 +579,32 @@ export class TitlePhase extends Phase { // Replaces 'Continue' with all Daily Run saves, sorted by when they last saved // 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 lastsaves = this.getSaves(false, true); - //lastsaves = this.getSavesUnsorted() - if (lastsaves != undefined && lastsaves.length > 0) { - lastsaves.forEach(lastsave => { + var lastsaves = this.getSaves(false, true); // Gets all Daily Runs sorted by last play time + var lastsave = this.getLastSave(); // Gets the last save you played + var ls1 = this.getLastSave(false, true) + var ls2 = this.getLastSavesOfEach() + switch (true) { + case (this.scene.quickloadDisplayMode == "Daily" && this.getLastSave(false, true) != undefined): + options.push({ + label: (ls1.description ? ls1.description : "[???]"), + handler: () => { + this.loadSaveSlot(ls1.slot); + return true; + } + }) + break; + case this.scene.quickloadDisplayMode == "Dailies" && this.getLastSave(false, true) != undefined: + lastsaves.forEach(lastsave1 => { + options.push({ + label: (lastsave1.description ? lastsave1.description : "[???]"), + handler: () => { + this.loadSaveSlot(lastsave1.slot); + return true; + } + }) + }) + break; + case lastsave != undefined && (this.scene.quickloadDisplayMode == "Latest" || ((this.scene.quickloadDisplayMode == "Daily" || this.scene.quickloadDisplayMode == "Dailies") && this.getLastSave(false, true) == undefined)): options.push({ label: (lastsave.description ? lastsave.description : "[???]"), handler: () => { @@ -560,20 +612,19 @@ export class TitlePhase extends Phase { return true; } }) - }) - } else { - var lastsave = this.getLastSave(false); - if (lastsave != undefined) { - options.push({ - label: (lastsave.description ? lastsave.description : "[???]"), - handler: () => { - this.loadSaveSlot(lastsave.slot); - return true; - } + break; + case this.scene.quickloadDisplayMode == "Both" && ls2 != undefined: + ls2.forEach(lastsave2 => { + options.push({ + label: (lastsave2.description ? lastsave2.description : "[???]"), + handler: () => { + this.loadSaveSlot(lastsave2.slot); + return true; + } + }) }) - } else { - console.log("Failed to get last save") - this.getLastSave(true) + break; + default: // If set to "Off" or all above conditions failed if (loggedInUser.lastSessionSlot > -1) { options.push({ label: i18next.t("continue", null, { ns: "menu"}), @@ -583,7 +634,7 @@ export class TitlePhase extends Phase { } }); } - } + break; } options.push({ label: i18next.t("menu:newGame"), diff --git a/src/system/settings/settings.ts b/src/system/settings/settings.ts index 5bf04301dfc..53f4ee66c79 100644 --- a/src/system/settings/settings.ts +++ b/src/system/settings/settings.ts @@ -105,8 +105,7 @@ export const SettingKeys = { ShowAutosaves: "SHOW_AUTOSAVES", TitleScreenContinueMode: "TITLE_SCREEN_QUICKLOAD", BiomePanels: "BIOME_PANELS", - DailyShinyLuck: "DAILY_LUCK", - QuickloadDisplay: "QUICKLOAD_MODE" + DailyShinyLuck: "DAILY_LUCK" }; /** @@ -214,7 +213,7 @@ export const Setting: Array = [ label: "Both", value: "Both" // Shows the last run and the last Daily Run, or only the last played game if it is a Daily Run }], - default: 2, + default: 1, type: SettingType.GENERAL, }, { @@ -722,7 +721,7 @@ export function setSetting(scene: BattleScene, setting: string, value: integer): scene.doBiomePanels = Setting[index].options[value].value == "On" case SettingKeys.DailyShinyLuck: scene.disableDailyShinies = Setting[index].options[value].value == "Off" - case SettingKeys.QuickloadDisplay: + case SettingKeys.TitleScreenContinueMode: scene.quickloadDisplayMode = Setting[index].options[value].value; case SettingKeys.Skip_Seen_Dialogues: scene.skipSeenDialogues = Setting[index].options[value].value === "On";