SaveHandler.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  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. } else {
  86. let items = Thing.getNonUnique(thing.Name);
  87. if (items.length > 0) {
  88. item = items[0];
  89. }
  90. if (item == undefined) {
  91. let error = thing.Name + " no longer exists.";
  92. console.error("[SaveHandler] " + error);
  93. errors.push(error)
  94. return undefined;
  95. } else {
  96. item = item.clone(true);
  97. }
  98. }
  99. item.setChanges(thing.Changes);
  100. return item;
  101. }
  102. export function loadGame (saveStruc : SaveStructure) {
  103. // interface SaveStructure {
  104. // Variables : {[id : string] : any};
  105. // Wielded : Array<SavedThing>;
  106. // Worn : Array<SavedThing>;
  107. // Carried : Array<SavedThing>;
  108. // Player : SavedPlayer;
  109. // Rounds : number;
  110. // }
  111. let player = WorldState.player;
  112. StoredVariable.updateFromObject(saveStruc.Variables);
  113. WorldState.setCurrentTurn(saveStruc.Rounds);
  114. Thing.WearRelation.getRight(player).forEach((thing : Thing) => {
  115. Thing.WearRelation.unsetRight(thing);
  116. });
  117. Thing.WieldRelation.getRight(player).forEach((thing : Thing) => {
  118. Thing.WieldRelation.unsetRight(thing);
  119. });
  120. Thing.CarryRelation.getRight(player).forEach((thing : Thing) => {
  121. Thing.CarryRelation.unsetRight(thing);
  122. });
  123. saveStruc.Wielded.forEach((thing : SavedThing) => {
  124. let item = getItem(thing);
  125. if (item != undefined) WorldState.player.setWielded(item);
  126. });
  127. saveStruc.Worn.forEach((thing : SavedThing) => {
  128. let item = getItem(thing);
  129. if (item != undefined) WorldState.player.setWorn(item);
  130. });
  131. saveStruc.Carried.forEach((thing : SavedThing) => {
  132. let item = getItem(thing);
  133. if (item != undefined) WorldState.player.setCarried(item);
  134. });
  135. let savedPlayer = saveStruc.Player;
  136. player.setName(savedPlayer.Name);
  137. player.setChanges(savedPlayer.PlayerChanges); // this adds the right gendered parts
  138. savedPlayer.Parts.forEach((part : SavedThing) => {
  139. let bpList = <Array<Bodypart>> player.getPartsByName(part.Name);
  140. if (bpList != undefined) {
  141. bpList[0].setChanges(part.Changes);
  142. }
  143. });
  144. saveStruc.UniqueThings.forEach((savedThing : SavedThing) => {
  145. let thing = Thing.getUnique(savedThing.Name);
  146. if (thing != undefined) {
  147. thing.setChanges(savedThing.Changes);
  148. }
  149. });
  150. }
  151. function exportPlayer () : SavedPlayer {
  152. return {
  153. Name : WorldState.player.getName(),
  154. PlayerChanges : WorldState.player.getChanges(),
  155. Parts : exportThings(WorldState.player.getParts())
  156. }
  157. }
  158. export function exportThings (arr : Array<Thing>, changedOnly? : boolean) : Array<SavedThing> {
  159. let obj = [];
  160. for (let i = 0; i < arr.length; i++) {
  161. let thing = <Thing> arr[i];
  162. let savedThing = {
  163. Unique : thing.isUnique(),
  164. Name : thing.getName(),
  165. Changes : thing.getChanges()
  166. };
  167. if (!changedOnly || Object.keys(savedThing.Changes).length > 0) {
  168. obj.push(savedThing);
  169. }
  170. }
  171. return obj;
  172. }
  173. export function getSaveStructure () : SaveStructure {
  174. let variables = StoredVariable.getVariables();
  175. let savedVariables = {};
  176. for (let i = 0; i < variables.length; i++) {
  177. savedVariables[variables[i].id] = variables[i].exportAsObject();
  178. }
  179. let wielded = Thing.WieldRelation.getRight(WorldState.player);
  180. let worn = Thing.WearRelation.getRight(WorldState.player);
  181. let carried = Thing.CarryRelation.getRight(WorldState.player);
  182. let saveStruc : SaveStructure = {
  183. Variables : savedVariables,
  184. UniqueThings : exportThings(Thing.getUniques()),
  185. Wielded : exportThings(wielded),
  186. Worn : exportThings(worn),
  187. Carried : exportThings(carried),
  188. Player : exportPlayer(),
  189. Rounds : WorldState.getCurrentTurn(),
  190. Date : new Date().toLocaleString()
  191. };
  192. console.debug("[SaveHandler] Created Save Structure", saveStruc);
  193. return saveStruc;
  194. }
  195. export function setSlot (slotN : number) {
  196. saveSlot = slotN;
  197. }
  198. export function saveToStorage () {
  199. Controls.Memory.setValue(storagePrefix + saveSlot, getSaveStructure());
  200. }
  201. export async function loadFromStorage () {
  202. if (!erasing) {
  203. let saveStruct = (Controls.Memory.getValue(storagePrefix + saveSlot, undefined));
  204. if (saveStruct != undefined) {
  205. loadGame(saveStruct);
  206. return await AfterLoad.execute({noun : saveStruct});
  207. }
  208. }
  209. // this is a new game!
  210. await CharacterCreation.rulebook.execute({});
  211. }
  212. export async function loadFromFile () {
  213. PlayBegins.LOAD_FAILED = false;
  214. let promise = readFile();
  215. let finishedAny;
  216. let realPromise = new Promise((resolve) => {
  217. finishedAny = resolve;
  218. });
  219. let say = new Say("No save file was loaded.", Say.PARAGRAPH_BREAK, Say.CENTERED, new SayBold("Press any key to return."));
  220. let sayElements = await Elements.CurrentTurnHandler.getSayElementsAsContent(say);
  221. Elements.CurrentTurnHandler.print(...sayElements);
  222. let nextKey = Controls.KeyHandler.getNextKey();
  223. promise.then((file) => {
  224. Controls.KeyHandler.stopGivingNextKey(nextKey);
  225. Elements.CurrentTurnHandler.unprint(...sayElements);
  226. loadGame(getFromFile(file)); // If no file was chosen then change isn't triggered, so it's never undefined...
  227. finishedAny();
  228. });
  229. nextKey.then((keyCode : KeyCode) => {
  230. PlayBegins.LOAD_FAILED = true;
  231. finishedAny();
  232. });
  233. await realPromise;
  234. }
  235. export function getSayForSlot (slotNumber : number) {
  236. let saveStruct : SaveStructure = (Controls.Memory.getValue(storagePrefix + slotNumber, undefined));
  237. if (saveStruct == undefined) {
  238. return new Say("New Game");
  239. } else {
  240. let erasingText = erasing ? "(ERASE) - " : "";
  241. return new Say(erasingText, saveStruct.Player.Name + " - Turns: " + saveStruct.Rounds + " - Last Played: " + saveStruct.Date);
  242. }
  243. }
  244. export function getFromFile (saveText) : SaveStructure {
  245. return JSON.parse(decodeURIComponent(atob(saveText)));
  246. }
  247. export function saveToFile () {
  248. // It's okay if you want to cheat, but if you tamper with the save file, please be mindful when reporting "bugs".
  249. download(saveName + "." + saveExtension, btoa(unescape(encodeURIComponent((JSON.stringify(getSaveStructure()))))));
  250. }
  251. export function isErasing () {
  252. return erasing;
  253. }
  254. export function toggleErasing () {
  255. erasing = !erasing;
  256. }
  257. export function isVirgin () {
  258. let was = virgin.getValue();
  259. virgin.storeValue(false);
  260. return was;
  261. }
  262. }
  263. // document.getElementById("SaveGameButton").addEventListener("click", () => {
  264. // SaveHandler.saveToFile();
  265. // });
  266. TurnSequence.rulebook.createAndAddRule({
  267. name : "Save game to Storage",
  268. priority : Rule.PRIORITY_LOWEST,
  269. firstPriority : Rule.PRIORITY_LOWEST,
  270. code : () => {
  271. SaveHandler.saveToStorage();
  272. }
  273. })