///
///
///
///
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();
}
})