const networkClock = 1000 / 16; var allowedIDCharacters = "0123456789ABCDEF"; var tokenCharacters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz._-"; var generateAccessToken = function () { var token = ""; for (var c = 0; c < 64; c++) { token += tokenCharacters[ Math.floor( Math.random() * tokenCharacters.length ) ]; } return token; }; var initMatrixClient = function (baseURL, mxID, withCrypto = false) { return new Promise(function (resolve, reject) { var matrixAccessToken = localStorage.getItem("matrix_access_token"); if (!matrixAccessToken) { matrixAccessToken = generateAccessToken(); localStorage.setItem("matrix_access_token", matrixAccessToken); } var matrixClient = global.matrixcs.createClient({ "baseUrl": baseURL, "accessToken": matrixAccessToken, "userId": mxID, "deviceId": "XVoxel" }); module.exports.matrixClient = matrixClient; resolve(matrixClient); if (withCrypto) { matrixClient.initCrypto().then(function () { }).catch(console.error); } }); }; var loginToMatrix = function (username, password) { return new Promise(function (resolve, reject) { if (!("matrixClient" in module.exports)) return reject(new Error("Matrix client hasn't been initialized yet")); module.exports.matrixClient.loginWithPassword(username, password).then(function (result) { var matrixAccessToken = module.exports.matrixClient.getAccessToken(); localStorage.setItem("matrix_access_token", matrixAccessToken); resolve(result); }).catch(reject); }); }; var startMatrixClient = function () { module.exports.matrixClient.startClient(); module.exports.matrixClient.on("sync", function (state, previousState, response) { if (state === "PREPARED") { var buttonReadyInterval = setInterval(function () { var mxWJBtn = document.querySelector("#matrixworldjoin"); if (!mxWJBtn) return; mxWJBtn.disabled = false; clearInterval(buttonReadyInterval); }, 256); } }); }; var createWorldRoom = function (name) { return new Promise(function (resolve, reject) { module.exports.matrixClient.createRoom({ "name": name, "visibility": "private", "topic": "A Voxel world", "creation_content": { "type": "m.world.voxel" } }).then(resolve).catch(reject); }); }; var getWorldRooms = function () { var rooms = module.exports.matrixClient.getRooms(); if (!rooms) return []; return rooms.filter(room => room.getType() == "m.world.voxel"); }; var syncWorldFromMatrixRoom = function (room) { return new Promise(function (resolve, reject) { module.exports.matrixClient.getStateEvent(room.roomId, "m.world.voxel", "save").then(function (event) { fetch(module.exports.matrixClient.mxcUrlToHttp(event["save_uri"])).then(function (response) { response.arrayBuffer().then(function (arrayBuffer) { global.openSave(arrayBuffer).then(function (loadedWorlds) { for (var worldName in loadedWorlds) { if (loadedWorlds.hasOwnProperty(worldName)) { global.worlds[worldName] = loadedWorlds[worldName]; } } resolve(global.worlds[room.name]); }).catch(reject); }).catch(reject); }).catch(reject); }).catch(reject); }); }; var syncWorldToMatrixRoom = function (room) { return new Promise(function(resolve, reject) { global.makeSave().then(function (archive) { module.exports.matrixClient.uploadContent(archive, { "name": room.name + ".zip", "type": "application/zip" }).then(function (response) { module.exports.matrixClient.sendStateEvent(room.roomId, "m.world.voxel", { "save_uri": response.content_uri }, "save").then(resolve).catch(reject); }).catch(reject); }).catch(reject); }); }; var connectMatrix = function (room) { return new Promise(function (resolve, reject) { var groupCall = module.exports.matrixClient.getGroupCallForRoom(room.roomId); if (!groupCall) { return module.exports.matrixClient.createGroupCall( room.roomId, global.matrixcs.GroupCallType["Voice"], false, global.matrixcs.GroupCallIntent["Room"], true ).then(function (call) { call.initWithAudioMuted = true; call.initWithVideoMuted = true; call.setMicrophoneMuted(true); call.enter(); resolve(call); }).catch(console.error); } groupCall.initWithAudioMuted = true; groupCall.initWithVideoMuted = true; groupCall.setMicrophoneMuted(true); groupCall.enter(); resolve(groupCall); }); }; if (localStorage.getItem("matrix_is_connected")) { initMatrixClient( localStorage.getItem("matrix_baseurl"), localStorage.getItem("matrix_mxid") ).then(function (matrixClient) { module.exports.matrixClient = matrixClient; startMatrixClient(); }).catch(function (error) { console.error(error); localStorage.removeItem("matrix_is_connected"); }); } var connectRTC = function (options) { var randomID = (options.username || "Guest") + "-"; for (var c = 0; c < 16; c++) { randomID += allowedIDCharacters[ Math.floor( Math.random() * allowedIDCharacters.length ) ]; } var peerNetwork = new global.peer.default(randomID, { "host": options.host, "port": options.port, "path": options.path }); return peerNetwork; }; var connect = function (url) { try { var connectionURL = new URL(url); } catch (error) { global.throwError(error); } var splitPathName = connectionURL.pathname.split(/[\/]/); var pathNameParts = splitPathName[2].split("@"); if (pathNameParts.length > 1) { var pathNameCredentials = pathNameParts.split(":"); var pathNameUsername = pathNameCredentials[0]; if (pathNameCredentials.length > 1) { var pathNamePassword = pathNameCredentials[1]; } var pathNameConnectionDataIndex = 1; } else { var pathNameConnectionDataIndex = 0; } var pathNameConnectionData = pathNameParts[pathNameConnectionDataIndex].split(":"); var pathNameAddress = pathNameConnectionData[0]; if (pathNameConnectionData.length > 1) { var pathNamePort = pathNameConnectionData[1]; } var pathNamePath = "/" + splitPathName.slice(3).join("/"); switch (connectionURL.protocol) { case "rtc:": return connectRTC({ "host": pathNameAddress, "port": pathNamePort || 443, "path": pathNamePath || "/", "username": pathNameUsername || "Guest", "password": pathNamePassword || null }); break; case "ws:": break; case "wss:": break; case "http:": break; case "https:": break; default: } }; module.exports = { "initMatrixClient": initMatrixClient, "loginToMatrix": loginToMatrix, "startMatrixClient": startMatrixClient, "createWorldRoom": createWorldRoom, "getWorldRooms": getWorldRooms, "connectMatrix": connectMatrix, "connect": connect, "menus": { "play": { "elements": [ { "type": "button", "label": "Multiplayer", "action": "open-menu", "action-args": ["serversel", 2] } ] }, "serversel": { "elements": [ { "type": "text", "id": "serveraddressinput", "label": global.locales.getRawString(global.currentLanguage, "play.multiplayer.serveraddressinput.label"), "placeholder": global.locales.getRawString(global.currentLanguage, "play.multiplayer.serveraddressinput.placeholder"), "value": "", "action": "run", "runnable": function (event) { var serverConnectButton = global.document.getElementById("serverconnectbutton"); if (event.path[0].value.length < 1) { serverConnectButton.disabled = true; } else { try { new URL(event.path[0].value); serverConnectButton.disabled = false; } catch (e) { serverConnectButton.disabled = true; } } } }, { "type": "button", "id": "serverconnectbutton", "label": global.locales.getRawString(global.currentLanguage, "play.multiplayer.serverconnectbutton.label"), "disabled": true, "action": "run", "runnable": function (event) { var serverAddressInputBox = global.document.getElementById("serveraddressinput"); connect(serverAddressInputBox.value); } }, { "type": "button", "id": "matrixworldjoin", "label": global.locales.getRawString(global.currentLanguage, "play.multiplayer.loadmatrixworldsbutton.label"), "disabled": true, "action": "run", "runnable": function (event) { for (var e = module.exports.menus["serversel"].elements.length - 1; e >= 0; e--) { var menuElement = module.exports.menus["serversel"].elements[e]; if (("classes" in menuElement) && menuElement.classes.indexOf("mxworldjoinbtn") > -1) module.exports.menus["serversel"].elements.splice(e, 1); } var matrixWorldRooms = getWorldRooms(); for (var r = 0; r < matrixWorldRooms.length; r++) { var matrixWorldRoom = matrixWorldRooms[r]; var menuEntry = { "type": "button", "label": global.locales.getFormattedString( global.currentLanguage, "play.singleplayer.openworld.label", { "WORLD_NAME": matrixWorldRoom.name } ), "action": "run", "runnable": (function (event) { syncWorldFromMatrixRoom(this).then((function (world) { global.worlds[this.name] = world; global.startGame(0, this.name); }).bind(this)).catch(global.throwError); global.menu.hideMenus(0); }).bind(matrixWorldRoom), "classes": [ "mxworldjoinbtn" ] }; module.exports.menus["serversel"].elements.push(menuEntry); } global.menu.hideMenus(2); global.menu.menus["serversel"] = global.menu.constructMenu("serversel", module.exports.menus["serversel"]); global.menu.showMenu("serversel", 2); } } ] } } };