SaveHandler.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. /// <reference path="World/Classes/Save/StoredVariable.ts" />
  2. /// <reference path="World/TurnSequence.ts" />
  3. /// <reference path="World/Classes/Rule.ts" />
  4. /// <reference path="Controls/Classes/StoredMemory.ts" />
  5. interface SavedThing {
  6. Name : string;
  7. Unique : boolean;
  8. Changes : {[id : string] : any}
  9. }
  10. // The player is always a humanoid
  11. interface SavedPlayer {
  12. Parts : Array<SavedThing>;
  13. PlayerChanges : {[id : string] : any};
  14. Name : string; // Are we going to do names? I Mean it'd be fun
  15. }
  16. interface SaveStructure {
  17. Variables : {[id : string] : any};
  18. Wielded : Array<SavedThing>;
  19. Worn : Array<SavedThing>;
  20. Carried : Array<SavedThing>;
  21. Player : SavedPlayer;
  22. Rounds : number;
  23. Date : string;
  24. UniqueThings : Array<SavedThing>;
  25. }
  26. module SaveHandler {
  27. let saveName = "Obelisk_Save"
  28. let saveExtension = "obsav";
  29. let storagePrefix = "save_";
  30. let saveSlot = 0;
  31. let errors : Array<string> = [];
  32. let erasing = false;
  33. let virgin = new StoredMemory("First time saving", true);
  34. export let AfterLoad = new Rulebook<SaveStructure>("After loading");
  35. export async function readFile () : Promise<string> {
  36. let element = <HTMLInputElement> document.createElement("input");
  37. element.type = "file";
  38. element.accept = "." + saveExtension;
  39. let promise : Promise<string> = new Promise((resolve, reject) => {
  40. element.onchange = () => {
  41. if (element.files.length == 0) {
  42. resolve(undefined);
  43. } else {
  44. var fr = new FileReader();
  45. fr.onload = (ev) => {
  46. resolve(ev.target['result']);
  47. }
  48. fr.readAsText(element.files[0]);
  49. }
  50. }
  51. });
  52. element.click();
  53. return promise;
  54. }
  55. function download(filename, text) {
  56. var element = document.createElement('a');
  57. element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
  58. element.setAttribute('download', filename);
  59. element.style.display = 'none';
  60. document.body.appendChild(element);
  61. element.click();
  62. document.body.removeChild(element);
  63. }
  64. export function loadSave () {
  65. let input = <HTMLInputElement> document.createElement("input");
  66. input.type = "file";
  67. input.accept = "." + saveExtension;
  68. document.body.appendChild(input);
  69. input.addEventListener("change", () => {
  70. let reader = new FileReader();
  71. reader.onload = () => {
  72. let text = reader.result;
  73. // TODO: Check if this is actually working. Typescript 3 suggested it wouldn't.
  74. SaveHandler.loadGame(<SaveStructure> JSON.parse(<string> text));
  75. };
  76. reader.readAsText(input.files[0]);
  77. });
  78. input.click();
  79. document.body.removeChild(input);
  80. }
  81. function getItem (thing : SavedThing) : Thing {
  82. let item : Thing;
  83. if (thing.Unique) {
  84. item = Thing.getUnique(thing.Name);
  85. if (item == undefined) {
  86. let error = thing.Name + " no longer exists.";
  87. console.error("[SaveHandler] " + error);
  88. errors.push(error)
  89. return undefined;
  90. }
  91. } else {
  92. let items = Thing.getNonUnique(thing.Name);
  93. if (items.length > 0) {
  94. item = items[0];
  95. }
  96. if (item == undefined) {
  97. let error = thing.Name + " no longer exists.";
  98. console.error("[SaveHandler] " + error);
  99. errors.push(error)
  100. return undefined;
  101. } else {
  102. item = item.clone(true);
  103. }
  104. }
  105. item.setChanges(thing.Changes);
  106. return item;
  107. }
  108. export function loadGame (saveStruc : SaveStructure) {
  109. // interface SaveStructure {
  110. // Variables : {[id : string] : any};
  111. // Wielded : Array<SavedThing>;
  112. // Worn : Array<SavedThing>;
  113. // Carried : Array<SavedThing>;
  114. // Player : SavedPlayer;
  115. // Rounds : number;
  116. // }
  117. let player = WorldState.player;
  118. StoredVariable.updateFromObject(saveStruc.Variables);
  119. WorldState.setCurrentTurn(saveStruc.Rounds);
  120. Thing.WearRelation.getRight(player).forEach((thing : Thing) => {
  121. Thing.WearRelation.unsetRight(thing);
  122. });
  123. Thing.WieldRelation.getRight(player).forEach((thing : Thing) => {
  124. Thing.WieldRelation.unsetRight(thing);
  125. });
  126. Thing.CarryRelation.getRight(player).forEach((thing : Thing) => {
  127. Thing.CarryRelation.unsetRight(thing);
  128. });
  129. saveStruc.Wielded.forEach((thing : SavedThing) => {
  130. let item = getItem(thing);
  131. if (item != undefined) WorldState.player.setWielded(item);
  132. });
  133. saveStruc.Worn.forEach((thing : SavedThing) => {
  134. let item = getItem(thing);
  135. if (item != undefined) WorldState.player.setWorn(item);
  136. });
  137. saveStruc.Carried.forEach((thing : SavedThing) => {
  138. let item = getItem(thing);
  139. if (item != undefined) WorldState.player.setCarried(item);
  140. });
  141. let savedPlayer = saveStruc.Player;
  142. player.setName(savedPlayer.Name);
  143. player.setChanges(savedPlayer.PlayerChanges); // this adds the right gendered parts
  144. savedPlayer.Parts.forEach((part : SavedThing) => {
  145. let bpList = <Array<Bodypart>> player.getPartsByName(part.Name);
  146. if (bpList != undefined) {
  147. bpList[0].setChanges(part.Changes);
  148. }
  149. });
  150. saveStruc.UniqueThings.forEach((savedThing : SavedThing) => {
  151. let thing = Thing.getUnique(savedThing.Name);
  152. if (thing != undefined) {
  153. thing.setChanges(savedThing.Changes);
  154. }
  155. });
  156. }
  157. function exportPlayer () : SavedPlayer {
  158. return {
  159. Name : WorldState.player.getName(),
  160. PlayerChanges : WorldState.player.getChanges(),
  161. Parts : exportThings(WorldState.player.getParts())
  162. }
  163. }
  164. export function exportThings (arr : Array<Thing>, changedOnly? : boolean) : Array<SavedThing> {
  165. let obj = [];
  166. for (let i = 0; i < arr.length; i++) {
  167. let thing = <Thing> arr[i];
  168. let savedThing = {
  169. Unique : thing.isUnique(),
  170. Name : thing.getName(),
  171. Changes : thing.getChanges()
  172. };
  173. if (!changedOnly || Object.keys(savedThing.Changes).length > 0) {
  174. obj.push(savedThing);
  175. }
  176. }
  177. return obj;
  178. }
  179. export function getSaveStructure () : SaveStructure {
  180. let variables = StoredVariable.getVariables();
  181. let savedVariables = {};
  182. for (let i = 0; i < variables.length; i++) {
  183. savedVariables[variables[i].id] = variables[i].exportAsObject();
  184. }
  185. let wielded = Thing.WieldRelation.getRight(WorldState.player);
  186. let worn = Thing.WearRelation.getRight(WorldState.player);
  187. let carried = Thing.CarryRelation.getRight(WorldState.player);
  188. let saveStruc : SaveStructure = {
  189. Variables : savedVariables,
  190. UniqueThings : exportThings(Thing.getUniques()),
  191. Wielded : exportThings(wielded),
  192. Worn : exportThings(worn),
  193. Carried : exportThings(carried),
  194. Player : exportPlayer(),
  195. Rounds : WorldState.getCurrentTurn(),
  196. Date : new Date().toLocaleString()
  197. };
  198. console.debug("[SaveHandler] Created Save Structure", saveStruc);
  199. return saveStruc;
  200. }
  201. export function setSlot (slotN : number) {
  202. saveSlot = slotN;
  203. }
  204. export function saveToStorage () {
  205. Controls.Memory.setValue(storagePrefix + saveSlot, getSaveStructure());
  206. }
  207. export async function loadFromStorage () {
  208. if (!erasing) {
  209. let saveStruct = (Controls.Memory.getValue(storagePrefix + saveSlot, undefined));
  210. if (saveStruct != undefined) {
  211. loadGame(saveStruct);
  212. return await AfterLoad.execute({noun : saveStruct});
  213. }
  214. }
  215. // this is a new game!
  216. await CharacterCreation.rulebook.execute({});
  217. }
  218. export async function loadFromFile () {
  219. PlayBegins.LOAD_FAILED = false;
  220. let promise = readFile();
  221. let finishedAny;
  222. let realPromise = new Promise((resolve) => {
  223. finishedAny = resolve;
  224. });
  225. let say = new Say("No save file was loaded.", Say.PARAGRAPH_BREAK, Say.CENTERED, new SayBold("Press any key to return."));
  226. let sayElements = await Elements.CurrentTurnHandler.getSayElementsAsContent(say);
  227. Elements.CurrentTurnHandler.print(...sayElements);
  228. let nextKey = Controls.KeyHandler.getNextKey();
  229. promise.then((file) => {
  230. Controls.KeyHandler.stopGivingNextKey(nextKey);
  231. Elements.CurrentTurnHandler.unprint(...sayElements);
  232. loadGame(getFromFile(file)); // If no file was chosen then change isn't triggered, so it's never undefined...
  233. finishedAny();
  234. });
  235. nextKey.then((keyCode : KeyCode) => {
  236. PlayBegins.LOAD_FAILED = true;
  237. finishedAny();
  238. });
  239. await realPromise;
  240. }
  241. export function getSayForSlot (slotNumber : number) {
  242. let saveStruct : SaveStructure = (Controls.Memory.getValue(storagePrefix + slotNumber, undefined));
  243. if (saveStruct == undefined) {
  244. return new Say("New Game");
  245. } else {
  246. let erasingText = erasing ? "(ERASE) - " : "";
  247. return new Say(erasingText, saveStruct.Player.Name + " - Turns: " + saveStruct.Rounds + " - Last Played: " + saveStruct.Date);
  248. }
  249. }
  250. export function getFromFile (saveText) : SaveStructure {
  251. return JSON.parse(decodeURIComponent(atob(saveText)));
  252. }
  253. export function saveToFile () {
  254. // It's okay if you want to cheat, but if you tamper with the save file, please be mindful when reporting "bugs".
  255. download(saveName + "." + saveExtension, btoa(unescape(encodeURIComponent((JSON.stringify(getSaveStructure()))))));
  256. }
  257. export function isErasing () {
  258. return erasing;
  259. }
  260. export function toggleErasing () {
  261. erasing = !erasing;
  262. }
  263. export function isVirgin () {
  264. let was = virgin.getValue();
  265. virgin.storeValue(false);
  266. return was;
  267. }
  268. }
  269. // document.getElementById("SaveGameButton").addEventListener("click", () => {
  270. // SaveHandler.saveToFile();
  271. // });
  272. TurnSequence.rulebook.createAndAddRule({
  273. name : "Save game to Storage",
  274. priority : Rule.PRIORITY_LOWEST,
  275. firstPriority : Rule.PRIORITY_LOWEST,
  276. code : () => {
  277. SaveHandler.saveToStorage();
  278. }
  279. })