/// /// /// /// interface SavedThing { Name : string; Unique : boolean; Changes : {[id : string] : any} } // The player is always a humanoid interface SavedPlayer { Parts : Array; PlayerChanges : {[id : string] : any}; Name : string; // Are we going to do names? I Mean it'd be fun } interface SaveStructure { Variables : {[id : string] : any}; Wielded : Array; Worn : Array; Carried : Array; Player : SavedPlayer; Rounds : number; Date : string; UniqueThings : Array; } module SaveHandler { let saveName = "Obelisk_Save" let saveExtension = "obsav"; let storagePrefix = "save_"; let saveSlot = 0; let errors : Array = []; let erasing = false; let virgin = new StoredMemory("First time saving", true); export let AfterLoad = new Rulebook("After loading"); export async function readFile () : Promise { let element = document.createElement("input"); element.type = "file"; element.accept = "." + saveExtension; let promise : Promise = new Promise((resolve, reject) => { element.onchange = () => { if (element.files.length == 0) { resolve(undefined); } else { var fr = new FileReader(); fr.onload = (ev) => { resolve(ev.target['result']); } fr.readAsText(element.files[0]); } } }); element.click(); return promise; } function download(filename, text) { var element = document.createElement('a'); element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text)); element.setAttribute('download', filename); element.style.display = 'none'; document.body.appendChild(element); element.click(); document.body.removeChild(element); } export function loadSave () { let input = document.createElement("input"); input.type = "file"; input.accept = "." + saveExtension; document.body.appendChild(input); input.addEventListener("change", () => { let reader = new FileReader(); reader.onload = () => { let text = reader.result; // TODO: Check if this is actually working. Typescript 3 suggested it wouldn't. SaveHandler.loadGame( JSON.parse( text)); }; reader.readAsText(input.files[0]); }); input.click(); document.body.removeChild(input); } function getItem (thing : SavedThing) : Thing { let item : Thing; if (thing.Unique) { item = Thing.getUnique(thing.Name); if (item == undefined) { let error = thing.Name + " no longer exists."; console.error("[SaveHandler] " + error); errors.push(error) return undefined; } } else { let items = Thing.getNonUnique(thing.Name); if (items.length > 0) { item = items[0]; } if (item == undefined) { let error = thing.Name + " no longer exists."; console.error("[SaveHandler] " + error); errors.push(error) return undefined; } else { item = item.clone(true); } } item.setChanges(thing.Changes); return item; } export function loadGame (saveStruc : SaveStructure) { // interface SaveStructure { // Variables : {[id : string] : any}; // Wielded : Array; // Worn : Array; // Carried : Array; // Player : SavedPlayer; // Rounds : number; // } let player = WorldState.player; StoredVariable.updateFromObject(saveStruc.Variables); WorldState.setCurrentTurn(saveStruc.Rounds); Thing.WearRelation.getRight(player).forEach((thing : Thing) => { Thing.WearRelation.unsetRight(thing); }); Thing.WieldRelation.getRight(player).forEach((thing : Thing) => { Thing.WieldRelation.unsetRight(thing); }); Thing.CarryRelation.getRight(player).forEach((thing : Thing) => { Thing.CarryRelation.unsetRight(thing); }); saveStruc.Wielded.forEach((thing : SavedThing) => { let item = getItem(thing); if (item != undefined) WorldState.player.setWielded(item); }); saveStruc.Worn.forEach((thing : SavedThing) => { let item = getItem(thing); if (item != undefined) WorldState.player.setWorn(item); }); saveStruc.Carried.forEach((thing : SavedThing) => { let item = getItem(thing); if (item != undefined) WorldState.player.setCarried(item); }); let savedPlayer = saveStruc.Player; player.setName(savedPlayer.Name); player.setChanges(savedPlayer.PlayerChanges); // this adds the right gendered parts savedPlayer.Parts.forEach((part : SavedThing) => { let bpList = > player.getPartsByName(part.Name); if (bpList != undefined) { bpList[0].setChanges(part.Changes); } }); saveStruc.UniqueThings.forEach((savedThing : SavedThing) => { let thing = Thing.getUnique(savedThing.Name); if (thing != undefined) { thing.setChanges(savedThing.Changes); } }); } function exportPlayer () : SavedPlayer { return { Name : WorldState.player.getName(), PlayerChanges : WorldState.player.getChanges(), Parts : exportThings(WorldState.player.getParts()) } } export function exportThings (arr : Array, changedOnly? : boolean) : Array { let obj = []; for (let i = 0; i < arr.length; i++) { let thing = arr[i]; let savedThing = { Unique : thing.isUnique(), Name : thing.getName(), Changes : thing.getChanges() }; if (!changedOnly || Object.keys(savedThing.Changes).length > 0) { obj.push(savedThing); } } return obj; } export function getSaveStructure () : SaveStructure { let variables = StoredVariable.getVariables(); let savedVariables = {}; for (let i = 0; i < variables.length; i++) { savedVariables[variables[i].id] = variables[i].exportAsObject(); } let wielded = Thing.WieldRelation.getRight(WorldState.player); let worn = Thing.WearRelation.getRight(WorldState.player); let carried = Thing.CarryRelation.getRight(WorldState.player); let saveStruc : SaveStructure = { Variables : savedVariables, UniqueThings : exportThings(Thing.getUniques()), Wielded : exportThings(wielded), Worn : exportThings(worn), Carried : exportThings(carried), Player : exportPlayer(), Rounds : WorldState.getCurrentTurn(), Date : new Date().toLocaleString() }; console.debug("[SaveHandler] Created Save Structure", saveStruc); return saveStruc; } export function setSlot (slotN : number) { saveSlot = slotN; } export function saveToStorage () { Controls.Memory.setValue(storagePrefix + saveSlot, getSaveStructure()); } export async function loadFromStorage () { if (!erasing) { let saveStruct = (Controls.Memory.getValue(storagePrefix + saveSlot, undefined)); if (saveStruct != undefined) { loadGame(saveStruct); return await AfterLoad.execute({noun : saveStruct}); } } // this is a new game! await CharacterCreation.rulebook.execute({}); } export async function loadFromFile () { PlayBegins.LOAD_FAILED = false; let promise = readFile(); let finishedAny; let realPromise = new Promise((resolve) => { finishedAny = resolve; }); let say = new Say("No save file was loaded.", Say.PARAGRAPH_BREAK, Say.CENTERED, new SayBold("Press any key to return.")); let sayElements = await Elements.CurrentTurnHandler.getSayElementsAsContent(say); Elements.CurrentTurnHandler.print(...sayElements); let nextKey = Controls.KeyHandler.getNextKey(); promise.then((file) => { Controls.KeyHandler.stopGivingNextKey(nextKey); Elements.CurrentTurnHandler.unprint(...sayElements); loadGame(getFromFile(file)); // If no file was chosen then change isn't triggered, so it's never undefined... finishedAny(); }); nextKey.then((keyCode : KeyCode) => { PlayBegins.LOAD_FAILED = true; finishedAny(); }); await realPromise; } export function getSayForSlot (slotNumber : number) { let saveStruct : SaveStructure = (Controls.Memory.getValue(storagePrefix + slotNumber, undefined)); if (saveStruct == undefined) { return new Say("New Game"); } else { let erasingText = erasing ? "(ERASE) - " : ""; return new Say(erasingText, saveStruct.Player.Name + " - Turns: " + saveStruct.Rounds + " - Last Played: " + saveStruct.Date); } } export function getFromFile (saveText) : SaveStructure { return JSON.parse(decodeURIComponent(atob(saveText))); } export function saveToFile () { // It's okay if you want to cheat, but if you tamper with the save file, please be mindful when reporting "bugs". download(saveName + "." + saveExtension, btoa(unescape(encodeURIComponent((JSON.stringify(getSaveStructure())))))); } export function isErasing () { return erasing; } export function toggleErasing () { erasing = !erasing; } export function isVirgin () { let was = virgin.getValue(); virgin.storeValue(false); return was; } } // document.getElementById("SaveGameButton").addEventListener("click", () => { // SaveHandler.saveToFile(); // }); TurnSequence.rulebook.createAndAddRule({ name : "Save game to Storage", priority : Rule.PRIORITY_LOWEST, firstPriority : Rule.PRIORITY_LOWEST, code : () => { SaveHandler.saveToStorage(); } })