const Memory = { /** * @type {State} */ state: new State(), /** * @returns {(Object|State)} */ saveToObject () { const prepareSaveData = (saveData) => { const prepareMonster = (monster) => { if (monster.statusEffect && monster.statusEffect.slug === 'lifeleech') { monster.statusEffect = null; } return monster; }; for (const idx in saveData.monsters) { saveData.monsters[idx] = prepareMonster(saveData.monsters[idx]); } for (const idx in saveData.player.monsters) { saveData.player.monsters[idx] = prepareMonster(saveData.player.monsters[idx]); } for (const idx in saveData.opponent.monsters) { saveData.opponent.monsters[idx] = prepareMonster(saveData.opponent.monsters[idx]); } return JSON.parse(JSON.stringify(saveData)); }; const saveMonster = (monsterData, monsterState) => { monsterData.level = monsterState.level; monsterData.hp = monsterState.hp; return monsterData; }; /** * @type {State} */ const saveData = prepareSaveData(Object.assign({}, Memory.state)); // monsters for (const idx in saveData.monsters) { saveData.monsters[idx] = saveMonster(saveData.monsters[idx], Memory.state.monsters[idx]); } // player - monsters for (const idx in saveData.player.monsters) { saveData.player.monsters[idx] = saveMonster(saveData.player.monsters[idx], Memory.state.player.monsters[idx]); } saveData.player.activeMonsterIdx = Memory.state.player.monsters.indexOf(Memory.state.player.activeMonster); // opponent - monsters for (const idx in saveData.opponent.monsters) { saveData.opponent.monsters[idx] = saveMonster(saveData.opponent.monsters[idx], Memory.state.opponent.monsters[idx]); } saveData.opponent.activeMonsterIdx = Memory.state.opponent.monsters.indexOf(Memory.state.opponent.activeMonster); return saveData; }, /** * @returns {string} */ saveToString () { // hash return btoa(JSON.stringify(Memory.saveToObject())); }, saveToLocalStorage () { localStorage.setItem('state', Memory.saveToString()); }, /** * @param {(Object|State)} saveData */ async loadFromObject (saveData) { /** * @param {Area} areaData */ const loadArea = async (areaData) => { const area = await fetchArea(areaData.slug); area.monsterProgress = areaData.monsterProgress; area.trainerProgress = areaData.trainerProgress; return area; }; /** * @param {Monster} monsterData */ const loadMonster = async (monsterData) => { const monster = await fetchMonster(monsterData.slug); monster.level = monsterData.level; monster.hp = monsterData.hp; monster.exp = monsterData.exp; monster.tasteWarm = monsterData.tasteWarm; monster.tasteCold = monsterData.tasteCold; monster.gender = monsterData.gender; monster.heldItem = await loadItem(monsterData.heldItem); monster.statusEffect = await loadStatusEffect(monsterData.statusEffect); monster.statModifiers = monsterData.statModifiers; monster.experienceModifier = monsterData.experienceModifier; monster.moneyModifier = monsterData.moneyModifier; monster.activeTechniques = await Promise.all(monsterData.activeTechniques.map(async (technique) => { return loadTechnique(technique); })); return monster; }; /** * @param {InventoryItem} inventoryItemData */ const loadInventoryItem = async (inventoryItemData) => { const inventoryItem = new InventoryItem(await fetchItem(inventoryItemData.item.slug)); inventoryItem.quantity = inventoryItemData.quantity; return inventoryItem; }; /** * @param {Item} itemData */ const loadItem = async (itemData) => { if (!itemData) { return null; } const item = await fetchItem(itemData.slug); return item; }; /** * @param {StatusEffect} statusEffectData */ const loadStatusEffect = async (statusEffectData) => { if (!statusEffectData) { return null; } const statusEffect = await fetchStatusEffect(statusEffectData.slug); statusEffect.turnsLeft = statusEffectData.turnsLeft; return statusEffect; }; /** * @param {Trainer} trainerData */ const loadTrainer = async (trainerData) => { const trainer = new Trainer(trainerData); return trainer; }; /** * @param {Technique} techniqueData */ const loadTechnique = async (techniqueData) => { const technique = await fetchTechnique(techniqueData.slug); technique.turnLastUse = techniqueData.turnLastUse; return technique; }; /** * @type {State} */ const loadedState = saveData; Memory.state.Settings.name = loadedState.Settings.name; Memory.state.Settings.language = loadedState.Settings.language; await fetchTranslation(Memory.state.Settings.language); applyTranslation(); Memory.state.Settings.currency = loadedState.Settings.currency; Memory.state.Settings.logMaxLength = loadedState.Settings.logMaxLength; if (loadedState.Game) { // backwards compat: TODO: remove check later Memory.state.Game.isInBattle = loadedState.Game.isInBattle; } for (const areaSlug of Object.keys(loadedState.areaProgress)) { const areaData = loadedState.areaProgress[areaSlug]; Memory.state.areaProgress[areaSlug] = await loadArea(areaData); } Memory.state.currentArea = await loadArea(loadedState.currentArea); Memory.state.lastVisitedTown = loadedState.lastVisitedTown; Memory.state.storyProgress = loadedState.storyProgress; Memory.state.currentStory = loadedState.currentStory; Memory.state.turn = loadedState.turn; Memory.state.money = loadedState.money; Memory.state.monsters = await Promise.all(loadedState.monsters.map(async (monsterData) => await loadMonster(monsterData))); Memory.state.player = await loadTrainer(loadedState.player); Memory.state.player.monsters = await Promise.all(loadedState.player.monsters.map(async (monsterData) => await loadMonster(monsterData))); Memory.state.player.inventory = await Promise.all(loadedState.player.inventory.map(async (itemData) => await loadInventoryItem(itemData))); Memory.state.player.activeMonster = Memory.state.player.monsters[loadedState.player.activeMonsterIdx]; Memory.state.opponent = await loadTrainer(loadedState.opponent); Memory.state.opponent.monsters = await Promise.all(loadedState.opponent.monsters.map(async (monsterData) => await loadMonster(monsterData))); Memory.state.opponent.inventory = await Promise.all(loadedState.opponent.inventory.map(async (itemData) => await loadInventoryItem(itemData))); Memory.state.opponent.activeMonster = Memory.state.opponent.monsters[loadedState.opponent.activeMonsterIdx]; Memory.state.rivalMonster = loadedState.rivalMonster; Memory.state.activeTechnique = await loadTechnique(loadedState.activeTechnique); if (typeof loadedState.activeBall === 'string') { // backwards compat: TODO: remove check later Memory.state.activeBall = loadedState.activeBall; } else { Memory.state.activeBall = loadedState.activeBall && loadedState.activeBall.slug; } // draw game if (!Game.isTown(Memory.state.currentArea)) { UI.drawOpponentMonster(); UI.drawActiveMonster(); UI.drawActiveTechniques(); } UI.drawArea(); UI.drawStatus(); UI.closeAllPopups(); Story.progress(Memory.state.currentStory); }, /** * @param {string} saveData */ loadFromString (saveData) { Memory.loadFromObject(JSON.parse(atob(saveData))); }, loadFromLocalStorage () { Memory.loadFromString(localStorage.getItem('state')); }, }; async function initializeState () { Memory.state.currentArea = await fetchArea('paper-town'); Memory.state.player = new Trainer({ monsters: [] }); Memory.state.opponent = new Trainer({ monsters: [] }); Memory.state.activeTechnique = await fetchTechnique('all_in'); }