sandboxels/mods/save_loading.js

521 lines
18 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

var modName = "mods/save_loading.js";
try {
if(typeof(rebuildCurrentPixels) !== "function") {
rebuildCurrentPixels = function() {
var currPix = []; //rebuild currentPixels from pixelMap to try to fix bug
for(pmi = 0; pmi < pixelMap.length; pmi++) {
var pixelMapPart = pixelMap[pmi];
for(pmj = 0; pmj < pixelMapPart.length; pmj++) {
var pixelMapUnit = pixelMapPart[pmj];
if(typeof(pixelMapUnit) === "object") {
if(pixelMapUnit !== null) {
currPix.push(pixelMapUnit);
};
};
};
};
currentPixels = currPix;
};
};
//https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API
function storageAvailable(type) {
let storage;
try {
storage = window[type];
const x = "__storage_test__";
storage.setItem(x, x);
storage.removeItem(x);
return true;
} catch (e) {
return (
e instanceof DOMException &&
// everything except Firefox
(e.code === 22 ||
// Firefox
e.code === 1014 ||
// test name field too, because code might not be present
// everything except Firefox
e.name === "QuotaExceededError" ||
// Firefox
e.name === "NS_ERROR_DOM_QUOTA_REACHED") &&
// acknowledge QuotaExceededError only if there's something already stored
storage &&
storage.length !== 0
);
}
}
function zeroToNull(val) {
if(val === 0) { return null };
return val;
};
if(!localStorage.slSaveSettings) {
localStorage.setItem("slSaveSettings", '{"roundTemps":true}');
};
slSaveSettings = JSON.parse(localStorage.slSaveSettings);
function epsilonRound(num,precision) {
return Math.round((num + Number.EPSILON) * (10 ** precision)) / (10 ** precision);
};
function getSimulationState() {
var simulationState = {
//currentPixels: currentPixels,
pixelMap: structuredClone ? structuredClone(pixelMap) : JSON.parse(JSON.stringify(pixelMap)),
width: width,
height: height,
pixelSize: pixelSize,
pixelTicks: pixelTicks,
settings: settings,
version: 1,
enabledMods: localStorage.enabledMods,
};
for(i = 0; i < simulationState.pixelMap.length; i++) {
var column = simulationState.pixelMap[i];
for(j = 0; j < column.length; j++) {
if(column[j] === null || typeof(column[j]) === "undefined") {
column[j] = 0;
};
};
};
if(slSaveSettings.roundTemps) {
for(i = 0; i < simulationState.pixelMap.length; i++) {
var column = simulationState.pixelMap[i];
for(j = 0; j < column.length; j++) {
var pixel = column[j];
if(pixel?.temp) {
pixel.temp = epsilonRound(pixel.temp,3);
};
};
};
};
return simulationState;
};
//https://stackoverflow.com/a/46118025
function copyToClipboard(text) {
var dummy = document.createElement("textarea");
// to avoid breaking orgain page when copying more words
// cant copy when adding below this code
// dummy.style.display = 'none'
document.body.appendChild(dummy);
//Be careful if you use textarea. setAttribute('value', value), which works with "input" does not work with "textarea". Eduard
dummy.value = text;
dummy.select();
document.execCommand("copy");
document.body.removeChild(dummy);
}
const saveTemplateAsFile = (filename, dataObjToWrite) => { //from https://stackoverflow.com/a/65939108
const blob = new Blob([JSON.stringify(dataObjToWrite)], { type: "text/json" });
const link = document.createElement("a");
link.download = filename;
link.href = window.URL.createObjectURL(blob);
link.dataset.downloadurl = ["text/json", link.download, link.href].join(":");
const evt = new MouseEvent("click", {
view: window,
bubbles: true,
cancelable: true,
});
link.dispatchEvent(evt);
link.remove()
};
function formatCurrentDate() { //derived from https://gist.github.com/Ivlyth/c4921735812dd2c0217a
var d = new Date();
var year = d.getFullYear().toString();
var month = (d.getMonth()+1).toString();
if(month.length == 1) { month = "0" + month };
var day = d.getDate().toString();
if(day.length == 1) { day = "0" + day };
var hour = d.getHours().toString();
if(hour.length == 1) { hour = "0" + hour };
var minute = d.getMinutes().toString();
if(minute.length == 1) { minute = "0" + minute };
var second = d.getSeconds().toString();
if(second.length == 1) { second = "0" + second };
var date_format_str = `${year}-${month}-${day} ${hour}-${minute}-${second}`;
return date_format_str;
};
function savePrompt() {
var filename = prompt("Please enter the desired filename, without the .json (defaults to current date)");
if(filename === null) {
return false;
};
if(filename === "") {
filename = `Sandboxels save ${formatCurrentDate()}`;
};
filename += ".json";
downloadSave(filename)
};
function downloadSave(filename=null) {
if(filename === null) {
filename = `Sandboxels save ${formatCurrentDate()}.json`;
};
saveTemplateAsFile(filename, getSimulationState());
};
rebuildCurrentPixels ??= function() {
var currPix = []; //rebuild currentPixels from pixelMap to try to fix bug
for(pmi = 0; pmi < pixelMap.length; pmi++) {
var pixelMapPart = pixelMap[pmi];
for(pmj = 0; pmj < pixelMapPart.length; pmj++) {
var pixelMapUnit = pixelMapPart[pmj];
if(typeof(pixelMapUnit) === "object") {
if(pixelMapUnit !== null) {
currPix.push(pixelMapUnit);
};
};
};
};
currentPixels = currPix;
};
function quicksave(doSuccessAlert=true,doFailAlert=true) {
if(storageAvailable("localStorage")) {
rebuildCurrentPixels();
localStorage.setItem("quicksave",JSON.stringify(getSimulationState()));
if(doSuccessAlert) { alert("Quicksave saved") };
return true;
} else {
if(doFailAlert) { alert("Could not save quicksave in localStorage") };
throw new Error("Could not save quicksave in localStorage");
};
};
function quickload(pause=true,doSuccessAlert=true,doFailAlert=true) {
clearAll();
rebuildCurrentPixels();
var save = localStorage.getItem("quicksave");
if(!save) {
if(doFailAlert) { alert("No save exists") };
return false;
} else {
importJsonState(JSON.parse(save));
if(doSuccessAlert) { alert("Quicksave loaded") };
if(pause) {
paused = true;
document.getElementById("pauseButton").setAttribute("on","true");
} else {
paused = false;
document.getElementById("pauseButton").setAttribute("on","false");
};
return true;
};
rebuildCurrentPixels();
};
function copySaveJSON(doAlert=true) {
copyToClipboard(JSON.stringify(getSimulationState()));
if(doAlert) { alert("Save copied as JSON") };
};
function loadFile() {
//Initialize
var json;
//load JSON
var file = document.getElementById('myfile').files[0];
if(file === undefined) {
if(document.getElementById("fileFormStatus") !== null) {
document.getElementById("fileFormStatus").style.color = "red";
document.getElementById("fileFormStatus").innerHTML = "No file was uploaded!";
};
throw new Error("No file was uploaded");
};
var reader = new FileReader();
reader.readAsText(file, 'UTF-8');
//after loading
reader.onload = function(evt) {
json = evt.target.result;
//validate
try {
json = JSON.parse(json);
} catch (error) {
if(document.getElementById("fileFormStatus") !== null) {
document.getElementById("fileFormStatus").style.color = "red";
document.getElementById("fileFormStatus").innerHTML = "The file wasn't valid JSON!";
};
throw error;
};
if(document.getElementById("fileFormStatus") !== null) {
document.getElementById("fileFormStatus").style.color = "yellow";
document.getElementById("fileFormStatus").innerHTML = "JSON was parsed successfully";
};
//return json;
return importJsonState(json);
};
};
function loadText() {
//Initialize
var json;
//load JSON
var json = document.getElementById('mytext').value;
if(json === "") {
if(document.getElementById("textFormStatus") !== null) {
document.getElementById("textFormStatus").style.color = "red";
document.getElementById("textFormStatus").innerHTML = "No text was present!";
};
throw new Error("No text was present");
};
//validate
try {
json = JSON.parse(json);
} catch (error) {
if(document.getElementById("textFormStatus") !== null) {
document.getElementById("textFormStatus").style.color = "red";
document.getElementById("textFormStatus").innerHTML = "The text wasn't valid JSON!";
};
throw error;
};
if(document.getElementById("textFormStatus") !== null) {
document.getElementById("textFormStatus").style.color = "yellow";
document.getElementById("textFormStatus").innerHTML = "JSON was parsed successfully";
};
//return json;
return importJsonState(json);
};
function importJsonState(json) {
//check keys
var jsonKeys = Object.keys(json);
var requiredKeys = [/*"currentPixels", */"pixelMap", "width", "height", "pixelSize"];
var hasrequiredKeys = true;
for(i = 0; i < requiredKeys.length; i++) {
var key = requiredKeys[i];
if(!jsonKeys.includes(key)) {
hasrequiredKeys = false;
break;
};
};
if(!hasrequiredKeys) {
if(document.getElementById("fileFormStatus") !== null) {
document.getElementById("fileFormStatus").style.color = "red";
document.getElementById("fileFormStatus").innerHTML = "JSON is not a valid save!";
};
throw new Error("JSON is missing required keys!");
};
//Set values
width = json.width;
height = json.height;
pixelSize = json.pixelSize;
pixelTicks = (json.pixelTicks ?? 0);
//currentPixels = json.currentPixels;
for(i = 0; i < json.pixelMap.length; i++) {
json.pixelMap[i] = json.pixelMap[i].map(x => zeroToNull(x));
};
pixelMap = json.pixelMap;
if(json.settings) {
settings = json.settings;
};
//enabledMods handling {
var enMods = "[]";
if(typeof(json.enabledMods) !== "undefined") {
enMods = json.enabledMods;
};
enMods = JSON.parse(enMods);
//console.log(enMods);
var currentEnmods = JSON.parse(localStorage.enabledMods); //should already exist if you're using this mod in the first place
for(emi = 0; emi < enMods.length; emi++) { //load mods additively to prevent self-disabling and the inconvenience of having to readd your mod list when you get bored
var mod = enMods[emi];
if(!currentEnmods.includes(mod)) {
currentEnmods.push(mod);
};
};
localStorage.setItem("enabledMods",JSON.stringify(currentEnmods));
if((enMods.length > 0 && enMods[0] !== modName) || enMods.length > 1) {
alert("Saves with other mods might require a reload (and then importing the save file again).\nIf you see a blank screen, try refreshing and loading the file again before you panic.");
};
//}
var currPix = []; //rebuild currentPixels from pixelMap to try to fix bug
for(pmi = 0; pmi < pixelMap.length; pmi++) {
var pixelMapPart = pixelMap[pmi];
for(pmj = 0; pmj < pixelMapPart.length; pmj++) {
var pixelMapUnit = pixelMapPart[pmj];
if(typeof(pixelMapUnit) === "object") {
if(pixelMapUnit !== null) {
currPix.push(pixelMapUnit);
};
};
};
};
currentPixels = currPix;
if(document.getElementById("fileFormStatus") !== null) {
document.getElementById("fileFormStatus").style.color = "green";
document.getElementById("fileFormStatus").innerHTML = "JSON was loaded successfully.";
};
return true;
};
function setPixelSize(size=null) {
if(size === null) {
if(document.getElementById("pixelSize") !== null) {
size = document.getElementById("pixelSize").value;
} else {
throw new Error("No size could be read");
};
};
size = parseFloat(size);
if(isNaN(size) || size <= 0) { //NaN check
if(document.getElementById("pixelSizeStatus") !== null) {
document.getElementById("pixelSizeStatus").style.color = "red";
document.getElementById("pixelSizeStatus").innerHTML = "Pixel size is empty or invalid";
};
throw new Error("NaN or negative size");
};
if(document.getElementById("pixelSizeStatus") !== null) {
document.getElementById("pixelSizeStatus").style.color = "green";
document.getElementById("pixelSizeStatus").innerHTML = "Pixel size set successfully";
};
pixelSize = size;
return true;
};
var saveLoaderDescription = `<div>
<span id="downloadButton" onclick=savePrompt() style="color: #FF00FF;">Download simulation</span>
<span id="copyButton" onClick=copySaveJSON() style="color: #FF66FF;">Alternatively, copy simulation JSON</span>
<span id="fileFormStatus">No file loader status</span>
One file, please: <input type="file" name="Save upload button" id="myfile">
<button id="loadButton" onclick=loadFile() style="color: #FF00FF;">Load File</button>
<span>Or paste JSON</span>
<span id="textFormStatus">No text loader status</span>
<input name="Text load field" id="mytext">
<button id="textLoadButton" onclick=loadText() style="color: #FF66FF;">Load Text</button>
<span id="pixelSizeStatus">No size setter status</span>
Pixel size (rendering only): <input id="pixelSize"> (Use if the save looks cut off)
<button id="sizeButton" onclick=setPixelSize() style="color: #FF00FF;">Set pixel size</button>
</div>`;
elements.save_loader = {
behavior: behaviors.SELFDELETE,
excludeRandom: true,
color: "#FFFFFF",
desc: saveLoaderDescription,
};
//Somehow, for some illogical reason, quicksaving causes updateStats to somehow disregard its if-statement and fucking TypeError when you mouse over an empty space; this is an attempt to fix it with overkill-level existence checks.
function updateStats() {
var statsDiv = document.getElementById("stats");
var stats = "<span id='stat-pos' class='stat'>x"+mousePos.x+",y"+mousePos.y+"</span>";
stats += "<span id='stat-pixels' class='stat'>Pxls:" + currentPixels.length+"</span>";
stats += "<span id='stat-tps' class='stat'>" + tps+"tps</span>";
stats += "<span id='stat-ticks' class='stat'>" + pixelTicks+"</span>";
if ((typeof pixelMap).length === 9) { return; }
if (pixelMap[mousePos.x] !== undefined) {
var currentPixel = pixelMap[mousePos.x][mousePos.y];
if (typeof(currentPixel) !== "undefined" && currentPixel && currentPixel !== undefined && currentPixel.element) {
stats += "<span id='stat-element' class='stat'>Elem:"+(elements[currentPixel?.element]?.name || currentPixel?.element)+"</span>";
stats += "<span id='stat-temperature' class='stat'>Temp:"+formatTemp(currentPixel.temp)+"</span>";
if (currentPixel.charge) {
stats += "<span id='stat-charge' class='stat'>C"+currentPixel.charge+"</span>";
}
if (currentPixel.burning) {
stats += "<span id='stat-burning' class='stat'>Burning</span>";
}
}
}
if (shiftDown) {
stats += "<span id='stat-shift' class='stat'>"+shiftDownTypes[shiftDown]+"</span>";
}
// If the view is not null, show the view in all caps
if (view !== null) {
stats += "<span id='stat-view' class='stat'>"+viewKey[view]+"</span>";
}
statsDiv.innerHTML = stats;
}
quickloadIsPaused = true;
//Element-based quicksave toggle has proven impossible due to numerous irreparable illogic incidents, so have some buttons instead
var qsb = document.createElement("button");
qsb.setAttribute("id","quicksaveButton");
qsb.setAttribute("onclick","quicksave()");
qsb.innerText = "Quicksave";
document.getElementById("gameDiv").before(qsb);
qsb.after(document.createTextNode(String.fromCharCode(160)));
var qlb = document.createElement("button");
qlb.setAttribute("id","quickloadButton");
qlb.setAttribute("onclick","clearAll(); quickload(quickloadIsPaused ?? true)");
qlb.innerText = "Quickload";
document.getElementById("gameDiv").before(qlb);
qlb.after(document.createTextNode(String.fromCharCode(160,160)));
var qpc = document.createElement("input");
qpc.setAttribute("type","checkbox");
qpc.setAttribute("id","quickloadPausedInput");
qpc.setAttribute("onchange","quickloadIsPaused = this.checked;");
qpc.checked = true;
var qpcd = document.createElement("span");
qpcd.setAttribute("id","quickloadPausedInputLabel");
qpcd.innerText = "Pause after quickloading?";
document.getElementById("gameDiv").before(qpc);
qpc.after(document.createTextNode(String.fromCharCode(160)));
document.getElementById("gameDiv").before(qpcd);
document.getElementById("gameDiv").before(document.createElement("br"));
quickSlDetectorLastKeys = [];
justPromptedQuickSL = false;
document.addEventListener("keydown", function(e) { //prop prompt listener
quickSlDetectorLastKeys.push(e.key);
if(quickSlDetectorLastKeys.length > 3) {
quickSlDetectorLastKeys.shift();
};
justPromptedQuickSL = false;
});
document.addEventListener("keydown", function(e) { //prop prompt listener
if (quickSlDetectorLastKeys.join(",") == "(,(,L") {
e.preventDefault();
var confirm = prompt("Are you sure you want to quickLOAD? (Type 'yes' to confirm)");
if(confirm == "yes") {
clearAll();
quickload(true,false,true);
};
justPromptedQuickSL = true;
quickSlDetectorLastKeys = [];
} else if (quickSlDetectorLastKeys.join(",") == "(,(,S") {
e.preventDefault();
var confirm = prompt("Are you sure you want to quickSAVE? (Type 'yes' to confirm)");
if(confirm == "yes") {
quicksave(true,true);
};
justPromptedQuickSL = true;
quickSlDetectorLastKeys = [];
};
});
} catch (error) {
alert(`save_loading error: ${error.toString()}`);
};