489 lines
12 KiB
JavaScript
489 lines
12 KiB
JavaScript
const loaderData = {
|
|
"utils": {
|
|
"type": "global",
|
|
"source": "./lib/utils.js",
|
|
"loadAs": "utils",
|
|
"dependsOn": []
|
|
},
|
|
"three": {
|
|
"type": "global",
|
|
"source": "./lib/three.js",
|
|
"loadAs": "three",
|
|
"dependsOn": []
|
|
},
|
|
/*"cannon": {
|
|
"type": "global",
|
|
"source": "./lib/cannon-es.js",
|
|
"loadAs": "cannon",
|
|
"dependsOn": []
|
|
},*/
|
|
"fflate": {
|
|
"type": "global",
|
|
"source": "./lib/fflate.js",
|
|
"loadAs": "fflate",
|
|
"dependsOn": []
|
|
},
|
|
"olm": {
|
|
"type": "global",
|
|
"source": "./lib/olm.js",
|
|
"loadAs": "olm",
|
|
"dependsOn": []
|
|
},
|
|
"matrix": {
|
|
"type": "global",
|
|
"source": "./lib/browser-matrix.min.js",
|
|
"loadAs": "matrix",
|
|
"dependsOn": ["olm"]
|
|
},
|
|
"peer": {
|
|
"type": "global",
|
|
"source": "./lib/peer.js",
|
|
"loadAs": "peer",
|
|
"dependsOn": []
|
|
},
|
|
"gamecore": {
|
|
"type": "global",
|
|
"source": "./core.js",
|
|
"loadAs": "gamecore",
|
|
"dependsOn": ["utils"]
|
|
},
|
|
"locales": {
|
|
"type": "global",
|
|
"source": "./lib/locales.js",
|
|
"loadAs": "locales",
|
|
"dependsOn": []
|
|
},
|
|
"menu": {
|
|
"type": "global",
|
|
"source": "./menu.js",
|
|
"loadAs": "menu",
|
|
"dependsOn": ["locales"]
|
|
}
|
|
};
|
|
|
|
const throwError = window.throwError = function (err) {
|
|
alert(err + "\n" + err.stack);
|
|
throw err;
|
|
};
|
|
|
|
var onLoadingFinished = function (modules) {
|
|
var initPromises = {};
|
|
var promiseReturners = {};
|
|
|
|
initPromises["render"] = new Promise(function (resolve, reject) {
|
|
promiseReturners["render"] = {
|
|
resolve,
|
|
reject
|
|
};
|
|
});
|
|
|
|
initPromises["input"] = new Promise(function (resolve, reject) {
|
|
promiseReturners["input"] = {
|
|
resolve,
|
|
reject
|
|
};
|
|
});
|
|
|
|
core.on("render-init", function () {
|
|
promiseReturners["render"].resolve();
|
|
|
|
fetch("./data/resources/XVoxel.zip").then(function (response) {
|
|
if (response.status != 200) return throwError(new Error("ENOENT"));
|
|
|
|
response.arrayBuffer().then(function (arrayBuffer) {
|
|
core.modules["resources"].ResourcePack.loadFromZIP(arrayBuffer).then(function (pack) {
|
|
window.resourcePacks.push(pack);
|
|
|
|
|
|
}).catch(throwError);
|
|
}).catch(throwError);
|
|
}).catch(throwError);
|
|
});
|
|
|
|
core.on("input-init", function () {
|
|
promiseReturners["input"].resolve();
|
|
});
|
|
|
|
Promise.all(Object.values(initPromises)).then(function () {
|
|
addPlayer(0);
|
|
});
|
|
};
|
|
|
|
window.onload = function (event) {
|
|
renderersContainer = document.querySelector("#renderers");
|
|
var librariesToLoad = Object.keys(loaderData);
|
|
|
|
var libraryLoaders = [];
|
|
|
|
for (var l = 0; l < librariesToLoad.length; l++) {
|
|
libraryLoaders.push(
|
|
loadFromMeta(librariesToLoad[l], loaderData[librariesToLoad[l]])
|
|
);
|
|
}
|
|
|
|
Promise.all(libraryLoaders).then(function (libraries) {
|
|
core = window.core = new window.gamecore.GameCore();
|
|
|
|
fetch("./data/strings/" + window.currentLanguage + ".json").then(function (response) {
|
|
if (response.status != 200) return throwError(new Error("ENOENT"));
|
|
|
|
response.text().then(function (text) {
|
|
window.locales.loadFromJSON(window.currentLanguage, JSON.parse(text));
|
|
|
|
menu.makeMenu();
|
|
}).catch(throwError);
|
|
}).catch(throwError);
|
|
|
|
onLoadingFinished(core.modules);
|
|
|
|
core.init(gamecore.loadData.modules).then(function (gameCore) {
|
|
for (var moduleName in core.modules) {
|
|
if (core.modules.hasOwnProperty(moduleName)) {
|
|
var thisModule = core.modules[moduleName];
|
|
|
|
if ((typeof thisModule) != "object") continue;
|
|
|
|
if ("menus" in thisModule) {
|
|
for (var menuName in thisModule.menus) {
|
|
if (thisModule.menus.hasOwnProperty(menuName)) {
|
|
if (!(menuName in menu.menuData)) {
|
|
menu.menuData[menuName] = thisModule.menus[menuName];
|
|
} else {
|
|
var thisMenu = thisModule.menus[menuName];
|
|
|
|
if (!("elements" in thisMenu)) continue;
|
|
|
|
for (var e = 0; e < thisMenu.elements.length; e++) {
|
|
menu.menuData[menuName].elements.push(thisMenu.elements[e]);
|
|
}
|
|
}
|
|
|
|
menu.menus[menuName] = menu.constructMenu(menuName, menu.menuData[menuName]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
window.menu.showMenu("main", 0);
|
|
}).catch(throwError);
|
|
}).catch(throwError);
|
|
};
|
|
|
|
var loadFromMeta = function (name, metadata) {
|
|
return new Promise((function (resolve, reject) {
|
|
var dependencies = [];
|
|
|
|
if (metadata.dependsOn) {
|
|
for (var d = 0; d < metadata.dependsOn.length; d++) {
|
|
var dependencyName = metadata.dependsOn[d];
|
|
var dependency = loaderData[dependencyName];
|
|
|
|
if (!loadings[dependencyName]) {
|
|
loadings[dependencyName] = loadFromMeta(dependencyName, dependency);
|
|
}
|
|
dependencies.push(loadings[dependencyName]);
|
|
}
|
|
}
|
|
|
|
Promise.all(dependencies).then((function (deps) {
|
|
loadings[name] = load(metadata.source).then(function (mod) {
|
|
window[metadata.loadAs || name] = mod;
|
|
|
|
resolve(mod);
|
|
}).catch(throwError);
|
|
}).bind(name)).catch(throwError);
|
|
}).bind(name));
|
|
};
|
|
|
|
var render = window.render = function (instanceIndex) {
|
|
if (instanceWorlds[instanceIndex]) {
|
|
renderers[instanceIndex].render(worlds[instanceWorlds[instanceIndex]], cameras[instanceIndex]);
|
|
}
|
|
};
|
|
|
|
var createWorld = window.createWorld = function (name, generator = "flat", options, generateInitialChunks = true) {
|
|
return new Promise(function (resolve, reject) {
|
|
window.worlds[name] = new core.modules["world"].World({
|
|
"generator": generator
|
|
});
|
|
|
|
if (!generateInitialChunks) return resolve(window.worlds[name]);
|
|
|
|
var min = [-4, 0, -4];
|
|
var max = [4, 8, 4];
|
|
|
|
var positions = [];
|
|
|
|
for (var x = min[0]; x < max[0]; x++) {
|
|
for (var z = min[2]; z < max[2]; z++) {
|
|
for (var y = min[1]; y < max[1]; y++) {
|
|
positions.push([x, y, z]);
|
|
}
|
|
}
|
|
}
|
|
|
|
var chunkGenerators = [];
|
|
|
|
genNext = function (pos, p) {
|
|
chunkGenerators.push(
|
|
worlds[name].generateChunk(pos[p])
|
|
.then(function (chunk) {
|
|
if (pos[p + 1]) return genNext(pos, p + 1);
|
|
|
|
Promise.all(chunkGenerators).then(function (chunks) {
|
|
resolve(worlds[name]);
|
|
}).catch(throwError);
|
|
})
|
|
.catch(throwError)
|
|
);
|
|
};
|
|
|
|
genNext(positions, 0);
|
|
});
|
|
};
|
|
|
|
var startGame = window.startGame = function (instance, worldName) {
|
|
instanceWorlds[instance] = worldName;
|
|
renderers[instance].setAnimationLoop(render.bind(window, instance));
|
|
|
|
inputs[instance].grab();
|
|
|
|
var chunkSize = window.worlds[worldName].chunkSize;
|
|
|
|
var positions = [];
|
|
|
|
worlds[worldName].chunks.forEach(function (chunk, c) {
|
|
positions.push(
|
|
c.split(":")
|
|
.map(coord => parseInt(coord))
|
|
);
|
|
});
|
|
|
|
if (positions.length > 0) {
|
|
constructNext = function (pos, p) {
|
|
worlds[worldName].constructChunk(pos[p]).then(function (chunk) {
|
|
worlds[worldName].addChunk(chunk);
|
|
|
|
if (pos[p + 1]) return constructNext(pos, p + 1);
|
|
}).catch(throwError);
|
|
};
|
|
|
|
constructNext(positions, 0);
|
|
}
|
|
};
|
|
|
|
window.addEventListener("resize", function (event) {
|
|
reorganizeRenderers(false);
|
|
|
|
for (var r = 0; r < renderers.length; r++) {
|
|
var rdr = renderers[r];
|
|
var rdrDOM = rdr.domElement;
|
|
|
|
var cam = cameras[r];
|
|
|
|
rdr.setSize(rdrDOM.clientWidth, rdrDOM.clientHeight);
|
|
|
|
cam.aspect = rdrDOM.clientWidth / rdrDOM.clientHeight;
|
|
cam.updateProjectionMatrix();
|
|
}
|
|
});
|
|
|
|
var reorganizeRenderers = function (remakeLayout = true) {
|
|
var rows = [];
|
|
var rowAmount = Math.round(Math.sqrt(renderers.length));
|
|
|
|
if (remakeLayout) {
|
|
for (var r = renderersContainer.childElementCount - 1; r >= 0; r--) {
|
|
renderersContainer.children.item(r).remove();
|
|
}
|
|
|
|
for (var r = 0; r < rowAmount; r++) {
|
|
rows[r] = document.createElement("div");
|
|
|
|
rows[r].style.width = "100%";
|
|
rows[r].style.height = "calc(100% / " + rowAmount + ")";
|
|
|
|
renderersContainer.append(rows[r]);
|
|
}
|
|
|
|
for (var r = 0; r < renderers.length; r++) {
|
|
rows[Math.floor((r / renderers.length) * rowAmount)].append(renderers[r].domElement);
|
|
}
|
|
} else {
|
|
for (var r = renderersContainer.childElementCount - 1; r >= 0; r--) {
|
|
rows[r] = renderersContainer.children.item(r);
|
|
}
|
|
}
|
|
|
|
for (var r = 0; r < rowAmount; r++) {
|
|
var row = rows[r];
|
|
|
|
for (var rd = 0; rd < row.childElementCount; rd++) {
|
|
row.children[rd].style.width = "calc(100% / " + row.childElementCount + ")";
|
|
row.children[rd].style.height = "100%";
|
|
}
|
|
}
|
|
};
|
|
|
|
var addPlayer = function (instance, controller) {
|
|
renderers[instance] = new core.modules["render"].Renderer({
|
|
"preserveDrawingBuffer": true,
|
|
"autoResize": true
|
|
});
|
|
|
|
var instanceDOMElement = renderers[instance].domElement;
|
|
|
|
reorganizeRenderers();
|
|
|
|
for (var r = 0; r < renderers.length; r++) {
|
|
var rdr = renderers[r];
|
|
var rdrDOM = rdr.domElement;
|
|
|
|
rdr.setSize(rdrDOM.clientWidth, rdrDOM.clientHeight, false);
|
|
}
|
|
|
|
cameras[instance] = new core.modules["render"].PerspectiveCamera(130, instanceDOMElement.width / instanceDOMElement.height, 0.0005, 8192);
|
|
|
|
if (controller instanceof core.modules["input"].Controller) {
|
|
inputs[instance] = controller;
|
|
inputs[instance].domElement = instanceDOMElement;
|
|
} else {
|
|
inputs[instance] = new core.modules["input"].Controller("desktop", {
|
|
"domElement": instanceDOMElement
|
|
});
|
|
}
|
|
|
|
renderers[instance].setAnimationLoop(render.bind(window, instance));
|
|
};
|
|
|
|
var quitGame = window.quitGame = function (instance) {
|
|
|
|
};
|
|
|
|
var closeGame = window.closeGame = function () {
|
|
window.close();
|
|
};
|
|
|
|
var openSave = window.openSave = function (archive) {
|
|
return new Promise(function (resolve, reject) {
|
|
window.fflate.unzip(new Uint8Array(archive), {}, function (error, data) {
|
|
if (error) return reject(error);
|
|
|
|
var loadedWorlds = {};
|
|
|
|
for (var filePath in data) {
|
|
if (data.hasOwnProperty(filePath)) {
|
|
var matches = filePath.match(/([^\/]+)\/meta\.json/);
|
|
|
|
if (!matches) continue;
|
|
if (matches[0] !== filePath) continue;
|
|
if (!matches[1]) continue;
|
|
|
|
var worldFolderName = matches[1];
|
|
|
|
let worldMeta;
|
|
try {
|
|
worldMeta = JSON.parse(window.fflate.strFromU8(data["" + worldFolderName + "/meta.json"]));
|
|
} catch (err) {
|
|
console.error(err);
|
|
continue;
|
|
}
|
|
|
|
var worldName = worldMeta.name;
|
|
|
|
loadedWorlds[worldName] = new core.modules["world"].World({
|
|
"generator": worldMeta.generator
|
|
});
|
|
|
|
var chunkFileNameMatcher = new RegExp(worldFolderName + "\/chunks\/(-?[0-9]+:-?[0-9]+:-?[0-9]+)\.json");
|
|
|
|
for (var fileName in data) {
|
|
if (data.hasOwnProperty(fileName)) {
|
|
var nameMatches = fileName.match(chunkFileNameMatcher);
|
|
|
|
if (!nameMatches) continue;
|
|
if (nameMatches[0] !== fileName) continue;
|
|
if (!nameMatches[1]) continue;
|
|
|
|
var chunkFileName = nameMatches[1];
|
|
|
|
let chunkData;
|
|
try {
|
|
chunkData = JSON.parse(window.fflate.strFromU8(data[worldFolderName + "/chunks/" + chunkFileName + ".json"]));
|
|
} catch (err) {
|
|
console.error(err);
|
|
continue;
|
|
}
|
|
|
|
var thisChunk = new core.modules["world"].Chunk(chunkData.size, loadedWorlds[worldName]);
|
|
|
|
for (var b = 0; b < chunkData.blocks.length; b++) {
|
|
thisChunk.blocks[b] = chunkData.blocks[b];
|
|
}
|
|
|
|
var chunkPosition = chunkFileName.split(":").map(function (c) {
|
|
return parseInt(c);
|
|
});
|
|
|
|
loadedWorlds[worldName].setChunk(thisChunk, chunkPosition);
|
|
loadedWorlds[worldName].add(thisChunk);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
resolve(loadedWorlds);
|
|
});
|
|
});
|
|
};
|
|
|
|
var makeSave = window.makeSave = function () {
|
|
return new Promise(function (resolve, reject) {
|
|
var files = {};
|
|
|
|
for (var world in worlds) {
|
|
if (worlds.hasOwnProperty(world)) {
|
|
var thisWorld = worlds[world].toJSON();
|
|
|
|
var worldMeta = {
|
|
"name": world,
|
|
"generator": thisWorld.generator
|
|
};
|
|
|
|
files[world + "/meta.json"] = fflate.strToU8(JSON.stringify(worldMeta, null, "\t"));
|
|
|
|
for (var chunk in thisWorld.chunks) {
|
|
if (thisWorld.chunks.hasOwnProperty(chunk)) {
|
|
var thisChunk = thisWorld.chunks[chunk];
|
|
|
|
files[world + "/chunks/" + chunk + ".json"] = fflate.strToU8(JSON.stringify(thisChunk, null, "\t"));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
window.fflate.zip(files, function (error, archive) {
|
|
if (error) return reject(error);
|
|
resolve(archive);
|
|
});
|
|
});
|
|
};
|
|
|
|
let core;
|
|
|
|
var currentLanguage = window.currentLanguage = "en-US";
|
|
|
|
var instanceWorlds = window.instanceWorlds = [];
|
|
|
|
var renderers = window.renderers = [];
|
|
var cameras = window.cameras = [];
|
|
var inputs = window.inputs = [];
|
|
|
|
var loadings = {};
|
|
|
|
let resourcePacks = window.resourcePacks = [];
|
|
let worlds = window.worlds = {};
|
|
|
|
let renderersContainer;
|