491 lines
15 KiB
JavaScript
491 lines
15 KiB
JavaScript
// These are relative paths
|
|
const RELEASE_DIR = '%__RELEASE_UUID__%'; // set by build_www.sh
|
|
const PACKS_DIR = RELEASE_DIR + '/packs';
|
|
|
|
const rtCSS = `
|
|
body {
|
|
font-family: arial;
|
|
margin: 0;
|
|
padding: none;
|
|
background-color: black;
|
|
}
|
|
|
|
.emscripten {
|
|
color: #aaaaaa;
|
|
padding-right: 0;
|
|
margin-left: auto;
|
|
margin-right: auto;
|
|
display: block;
|
|
}
|
|
|
|
div.emscripten {
|
|
text-align: center;
|
|
width: 100%;
|
|
}
|
|
|
|
/* the canvas *must not* have any border or padding, or mouse coords will be wrong */
|
|
canvas.emscripten {
|
|
border: 0px none;
|
|
background-color: black;
|
|
}
|
|
|
|
#progress {
|
|
height: 20px;
|
|
width: 300px;
|
|
}
|
|
|
|
#controls {
|
|
display: inline-block;
|
|
vertical-align: top;
|
|
height: 25px;
|
|
}
|
|
|
|
#output {
|
|
width: 100%;
|
|
height: 200px;
|
|
margin: 0 auto;
|
|
margin-top: 0px;
|
|
border-left: 0px;
|
|
border-right: 0px;
|
|
padding-left: 0px;
|
|
padding-right: 0px;
|
|
display: block;
|
|
background-color: black;
|
|
color: white;
|
|
font-family: 'Lucida Console', Monaco, monospace;
|
|
outline: none;
|
|
}
|
|
|
|
.launchbutton {
|
|
position: absolute;
|
|
width: 300px;
|
|
height: 120px;
|
|
z-index: 10;
|
|
font-size: 20pt;
|
|
}
|
|
`;
|
|
|
|
const rtHTML = `
|
|
<div id="header">
|
|
|
|
<div class="emscripten">
|
|
<span id="controls">
|
|
<span>
|
|
<select id="resolution" onchange="fixGeometry()">
|
|
<option value="high">High Res</option>
|
|
<option value="medium">Medium</option>
|
|
<option value="low">Low Res</option>
|
|
</select>
|
|
</span>
|
|
<span>
|
|
<select id="aspectRatio" onchange="fixGeometry()">
|
|
<option value="any">Fit Screen</option>
|
|
<option value="4:3">4:3</option>
|
|
<option value="16:9">16:9</option>
|
|
<option value="5:4">5:4</option>
|
|
<option value="21:9">21:9</option>
|
|
<option value="32:9">32:9</option>
|
|
<option value="1:1">1:1</option>
|
|
</select>
|
|
</span>
|
|
<!-- <span><input type="button" value="Toggle Fullscreen" onclick="fullscreen_button()"></span> -->
|
|
<span><input id="console_button" type="button" value="Show Console" onclick="toggle_console()"></span>
|
|
<span>F11 Full Screen</span>
|
|
</span>
|
|
</div>
|
|
|
|
<div class="emscripten">
|
|
<progress value="0" max="100" id="progress" hidden=1></progress>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="emscripten">
|
|
<canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault()" onclick="doLaunch()" tabindex=-1 width="1024" height="600">
|
|
</canvas>
|
|
</div>
|
|
|
|
<div id="footer">
|
|
<textarea id="output" rows="8" style="display: none;"></textarea>
|
|
</div>
|
|
`;
|
|
|
|
const extraCSS = document.createElement("style");
|
|
extraCSS.innerText = rtCSS;
|
|
document.head.appendChild(extraCSS);
|
|
document.body.innerHTML = rtHTML;
|
|
|
|
var progressElement = document.getElementById('progress');
|
|
|
|
function toggle_console() {
|
|
var button = document.getElementById('console_button');
|
|
var element = document.getElementById('output');
|
|
element.style.display = (element.style.display == 'block') ? 'none' : 'block';
|
|
button.value = (element.style.display == 'none') ? 'Show Console' : 'Hide Console';
|
|
fixGeometry();
|
|
}
|
|
|
|
var consoleElement = document.getElementById('output');
|
|
var enableTracing = false;
|
|
var consoleText = [];
|
|
var consoleLengthMax = 1000;
|
|
var consoleTextLast = 0;
|
|
var wasmReady = false;
|
|
var invokedMain = false;
|
|
var packsReady = false;
|
|
var packs = [];
|
|
|
|
// Called by MainLoop when the wasm module is ready
|
|
function emloop_ready() {
|
|
wasmReady = true;
|
|
emloop_invoke_main = cwrap("emloop_invoke_main", null, ["number", "number"]);
|
|
emloop_install_pack = cwrap("emloop_install_pack", null, ["number", "number", "number"]);
|
|
irrlicht_want_pointerlock = cwrap("irrlicht_want_pointerlock", "number");
|
|
irrlicht_resize = cwrap("irrlicht_resize", null, ["number", "number"]);
|
|
maybeStart();
|
|
}
|
|
|
|
function all_packs_ready() {
|
|
packsReady = true;
|
|
maybeStart();
|
|
}
|
|
|
|
function maybeStart() {
|
|
if (!wasmReady || !packsReady) return;
|
|
if (packs.length > 0) {
|
|
for (const [name, data] of packs) {
|
|
installPack(name, data);
|
|
}
|
|
packs = [];
|
|
}
|
|
showLaunchButton();
|
|
}
|
|
|
|
var launchButton;
|
|
function showLaunchButton() {
|
|
if (launchButton) return;
|
|
launchButton = document.createElement('button');
|
|
launchButton.className = 'launchbutton';
|
|
launchButton.innerText = 'Click to Launch';
|
|
launchButton.addEventListener('click', doLaunch);
|
|
document.body.appendChild(launchButton);
|
|
fixGeometry();
|
|
}
|
|
|
|
function makeArgv(args) {
|
|
// Assuming 4-byte pointers
|
|
const argv = _malloc((args.length + 1) * 4);
|
|
let i;
|
|
for (i = 0; i < args.length; i++) {
|
|
HEAPU32[(argv >> 2) + i] = allocateUTF8(args[i]);
|
|
}
|
|
HEAPU32[(argv >> 2) + i] = 0; // argv[argc] == NULL
|
|
return [i, argv];
|
|
}
|
|
|
|
function fetchPacks() {
|
|
const params = new URLSearchParams(window.location.search);
|
|
fetchPack('base');
|
|
if (params.has('gameid')) {
|
|
const gameid = params.get('gameid');
|
|
if (gameid != 'minetest_game' && gameid != 'devtest') {
|
|
fetchPack(params.get('gameid'));
|
|
}
|
|
}
|
|
}
|
|
|
|
var pendingPacks = 0;
|
|
function fetchPack(name) {
|
|
pendingPacks += 1;
|
|
const xhr = new XMLHttpRequest();
|
|
xhr.open('GET', PACKS_DIR + '/' + name + '.pack', true);
|
|
xhr.responseType = 'arraybuffer';
|
|
xhr.onprogress = (event) => {
|
|
console.log(`Fetched ${event.loaded} of ${event.total}`);
|
|
};
|
|
xhr.onload = (event) => {
|
|
if (xhr.status == 200 || xhr.status == 304 || xhr.status == 206 || (xhr.status == 0 && xhr.response)) {
|
|
packs.push([name, xhr.response]);
|
|
pendingPacks -= 1;
|
|
if (pendingPacks == 0) {
|
|
all_packs_ready();
|
|
}
|
|
} else {
|
|
throw new Error(xhr.statusText + " : " + xhr.responseURL);
|
|
}
|
|
};
|
|
xhr.send(null);
|
|
}
|
|
|
|
function installPack(name, arrayBuffer) {
|
|
const arr = new Uint8Array(arrayBuffer);
|
|
const data = _malloc(arr.length * arr.BYTES_PER_ELEMENT);
|
|
HEAP8.set(arr, data);
|
|
emloop_install_pack(allocateUTF8(name), data, arr.length);
|
|
_free(data);
|
|
}
|
|
|
|
function parseQueryArgs() {
|
|
const args = ['./minetest'];
|
|
const params = new URLSearchParams(window.location.search);
|
|
const keyList0 = ['go', 'server'];
|
|
const keyList1 = ['name', 'gameid', 'address', 'address', 'port'];
|
|
for (const key of keyList0) {
|
|
if (params.has(key)) {
|
|
args.push('--' + key);
|
|
}
|
|
}
|
|
for (const key of keyList1) {
|
|
if (params.has(key)) {
|
|
args.push('--' + key);
|
|
args.push(params.get(key));
|
|
}
|
|
}
|
|
return args;
|
|
}
|
|
|
|
function doLaunch() {
|
|
if (launchButton) {
|
|
launchButton.remove();
|
|
launchButton = null;
|
|
}
|
|
|
|
if (!invokedMain && wasmReady) {
|
|
invokedMain = true;
|
|
const args = parseQueryArgs();
|
|
const [argc, argv] = makeArgv(args);
|
|
emloop_invoke_main(argc, argv);
|
|
// irrlicht initialization resets the width/height
|
|
fixGeometry();
|
|
}
|
|
}
|
|
|
|
var consoleDirty = false;
|
|
function consoleUpdate() {
|
|
if (consoleDirty) {
|
|
if (consoleText.length > consoleLengthMax) {
|
|
consoleText = consoleText.slice(-consoleLengthMax);
|
|
}
|
|
consoleElement.value = consoleText.join('');
|
|
consoleElement.scrollTop = consoleElement.scrollHeight; // focus on bottom
|
|
consoleDirty = false;
|
|
}
|
|
window.requestAnimationFrame(consoleUpdate);
|
|
}
|
|
consoleUpdate();
|
|
|
|
var Module = {
|
|
preRun: [],
|
|
postRun: [],
|
|
print: (function() {
|
|
return function(text) {
|
|
if (enableTracing) {
|
|
console.trace(text);
|
|
}
|
|
consoleText.push(text + "\n");
|
|
consoleDirty = true;
|
|
};
|
|
})(),
|
|
canvas: (function() {
|
|
var canvas = document.getElementById('canvas');
|
|
|
|
// As a default initial behavior, pop up an alert when webgl context is lost. To make your
|
|
// application robust, you may want to override this behavior before shipping!
|
|
// See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2
|
|
canvas.addEventListener("webglcontextlost", function(e) { alert('WebGL context lost. You will need to reload the page.'); e.preventDefault(); }, false);
|
|
|
|
return canvas;
|
|
})(),
|
|
setStatus: function(text) {
|
|
if (!Module.setStatus.last) Module.setStatus.last = { time: Date.now(), text: '' };
|
|
if (text === Module.setStatus.last.text) return;
|
|
if (text) Module.print('[status] ' + text);
|
|
|
|
var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
|
|
var now = Date.now();
|
|
if (m && now - Module.setStatus.last.time < 30) return; // if this is a progress update, skip it if too soon
|
|
Module.setStatus.last.time = now;
|
|
Module.setStatus.last.text = text;
|
|
if (m) {
|
|
text = m[1];
|
|
progressElement.value = parseInt(m[2])*100;
|
|
progressElement.max = parseInt(m[4])*100;
|
|
progressElement.hidden = false;
|
|
} else {
|
|
progressElement.value = null;
|
|
progressElement.max = null;
|
|
progressElement.hidden = true;
|
|
}
|
|
},
|
|
totalDependencies: 0,
|
|
monitorRunDependencies: function(left) {
|
|
this.totalDependencies = Math.max(this.totalDependencies, left);
|
|
Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies-left) + '/' + this.totalDependencies + ')' : 'All downloads complete.');
|
|
}
|
|
};
|
|
|
|
Module['printErr'] = Module['print'];
|
|
Module['onFullScreen'] = () => { fixGeometry(); };
|
|
Module.setStatus('Downloading...');
|
|
window.onerror = function(event) {
|
|
// TODO: do not warn on ok events like simulating an infinite loop or exitStatus
|
|
Module.print('Exception thrown, see JavaScript console');
|
|
Module.setStatus = function(text) {
|
|
if (text) Module.print('[status] ' + text);
|
|
};
|
|
};
|
|
var pointerlock_pending = false;
|
|
var emloop_invoke_main;
|
|
var irrlicht_want_pointerlock;
|
|
var irrlicht_resize;
|
|
|
|
function fullscreen_button() {
|
|
var canvas = document.getElementById('canvas');
|
|
if (wasmReady) {
|
|
var alsoLockPointer = irrlicht_want_pointerlock();
|
|
// This calls Module['onFullScreen'] when finished, which calls fixGeometry.
|
|
Module.requestFullscreen(alsoLockPointer, false);
|
|
}
|
|
}
|
|
|
|
function resizeCanvas(width, height) {
|
|
var canvas = document.getElementById('canvas');
|
|
if (canvas.width != width || canvas.height != height) {
|
|
canvas.width = width;
|
|
canvas.height = height;
|
|
canvas.widthNative = width;
|
|
canvas.heightNative = height;
|
|
}
|
|
// Trigger SDL window resize.
|
|
// This should happen automatically, it's disappointing that it doesn't.
|
|
if (wasmReady) {
|
|
irrlicht_resize(width, height);
|
|
}
|
|
}
|
|
|
|
var resolutionSelect = document.getElementById('resolution');
|
|
var aspectRatioSelect = document.getElementById('aspectRatio');
|
|
|
|
function now() {
|
|
return (new Date()).getTime();
|
|
}
|
|
|
|
// Only allow fixGeometry to be called every 250ms
|
|
// Firefox calls this way too often, causing flicker.
|
|
var fixGeometryPause = 0;
|
|
function fixGeometry(override) {
|
|
if (!override && now() < fixGeometryPause) {
|
|
return;
|
|
}
|
|
|
|
var canvas = document.getElementById('canvas');
|
|
var resolution = resolutionSelect.value;
|
|
var aspectRatio = aspectRatioSelect.value;
|
|
var screenX;
|
|
var screenY;
|
|
|
|
// Prevent the controls from getting focus
|
|
canvas.focus();
|
|
|
|
var isFullScreen = document.fullscreenElement ? true : false;
|
|
if (isFullScreen) {
|
|
screenX = screen.width;
|
|
screenY = screen.height;
|
|
} else {
|
|
// F11-style full screen
|
|
var controls = document.getElementById('controls');
|
|
var maximized = !window.screenTop && !window.screenY;
|
|
controls.style = maximized ? 'display: none' : '';
|
|
|
|
var headerHeight = document.getElementById('header').offsetHeight;
|
|
var footerHeight = document.getElementById('footer').offsetHeight;
|
|
screenX = document.documentElement.clientWidth - 6;
|
|
screenY = document.documentElement.clientHeight - headerHeight - footerHeight - 6;
|
|
}
|
|
|
|
// Size of the viewport (after scaling)
|
|
var realX;
|
|
var realY;
|
|
if (aspectRatio == 'any') {
|
|
realX = screenX;
|
|
realY = screenY;
|
|
} else {
|
|
var ar = aspectRatio.split(':');
|
|
var innerRatio = parseInt(ar[0]) / parseInt(ar[1]);
|
|
var outerRatio = screenX / screenY;
|
|
if (innerRatio <= outerRatio) {
|
|
realX = Math.floor(innerRatio * screenY);
|
|
realY = screenY;
|
|
} else {
|
|
realX = screenX;
|
|
realY = Math.floor(screenX / innerRatio);
|
|
}
|
|
}
|
|
|
|
// Native canvas resolution
|
|
var resX;
|
|
var resY;
|
|
var scale = false;
|
|
if (resolution == 'high') {
|
|
resX = realX;
|
|
resY = realY;
|
|
} else if (resolution == 'medium') {
|
|
resX = Math.floor(realX / 1.5);
|
|
resY = Math.floor(realY / 1.5);
|
|
scale = true;
|
|
} else {
|
|
resX = Math.floor(realX / 2.0);
|
|
resY = Math.floor(realY / 2.0);
|
|
scale = true;
|
|
}
|
|
resizeCanvas(resX, resY);
|
|
|
|
if (scale) {
|
|
var styleWidth = realX + "px";
|
|
var styleHeight = realY + "px";
|
|
canvas.style.setProperty("width", styleWidth, "important");
|
|
canvas.style.setProperty("height", styleHeight, "important");
|
|
} else {
|
|
canvas.style.removeProperty("width");
|
|
canvas.style.removeProperty("height");
|
|
}
|
|
|
|
if (launchButton) {
|
|
var canvasRect = canvas.getBoundingClientRect();
|
|
var midX = Math.floor((canvasRect.top + canvasRect.bottom) / 2);
|
|
var midY = Math.floor((canvasRect.left + canvasRect.right) / 2);
|
|
launchButton.style.left = (midY - 300/2) + 'px';
|
|
launchButton.style.top = (midX - 120/2) + 'px';
|
|
}
|
|
}
|
|
|
|
window.addEventListener('load', () => { fixGeometry(); });
|
|
window.addEventListener('resize', () => { fixGeometry(); });
|
|
|
|
// Needed to prevent special keys from triggering browser actions, like
|
|
// F5 causing page reload.
|
|
document.addEventListener('keydown', (e) => {
|
|
// Allow F11 to go full screen
|
|
if (e.code == "F11") {
|
|
// On Firefox, F11 is animated. The window smoothly grows to
|
|
// full screen over several seconds. During this transition, the 'resize'
|
|
// event is triggered hundreds of times. To prevent flickering, have
|
|
// fixGeometry ignore repeated calls, and instead resize every 500ms
|
|
// for 2.5 seconds. By then it should be finished.
|
|
fixGeometryPause = now() + 2000;
|
|
for (var delay = 100; delay <= 2600; delay += 500) {
|
|
setTimeout(() => { fixGeometry(true); }, delay);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Start fetching data packs
|
|
fetchPacks();
|
|
|
|
// Start loading the wasm module
|
|
const mtModuleScript = document.createElement("script");
|
|
mtModuleScript.type = "text/javascript";
|
|
mtModuleScript.src = RELEASE_DIR + "/minetest.js";
|
|
mtModuleScript.async = true;
|
|
document.body.appendChild(mtModuleScript);
|