diff --git a/extra/js/dpad.js b/extra/js/dpad.js new file mode 100644 index 0000000..0ab1e6a --- /dev/null +++ b/extra/js/dpad.js @@ -0,0 +1,84 @@ +function simulateKeyEvent(eventType, keyCode, charCode) { + var e = document.createEventObject ? document.createEventObject() : document.createEvent("Events"); + if (e.initEvent) e.initEvent(eventType, true, true); + + e.keyCode = keyCode; + e.which = keyCode; + e.charCode = charCode; + + // Dispatch directly to Emscripten's html5.h API (use this if page uses emscripten/html5.h event handling): + if (typeof JSEvents !== 'undefined' && JSEvents.eventHandlers && JSEvents.eventHandlers.length > 0) { + for(var i = 0; i < JSEvents.eventHandlers.length; ++i) { + if ((JSEvents.eventHandlers[i].target == Module['canvas'] || JSEvents.eventHandlers[i].target == window) + && JSEvents.eventHandlers[i].eventTypeString == eventType) { + JSEvents.eventHandlers[i].handlerFunc(e); + } + } + } else { + // Dispatch to browser for real (use this if page uses SDL or something else for event handling): + Module['canvas'].dispatchEvent ? Module['canvas'].dispatchEvent(e) : Module['canvas'].fireEvent("on" + eventType, e); + } +} + +// Mappings +const keyMap = {}; +const keysDown = {}; + +/** Add virtual key binding */ +function bindKey(elem, key) { + keyMap[elem] = key; + const ne = document.getElementById(elem); + + ne.addEventListener('touchstart', function(e) { + e.preventDefault(); + simulateKeyEvent('keydown', key); + keysDown[e.target.id] = elem; + }); + ne.addEventListener('touchend', function(e) { + e.preventDefault(); + if (keysDown[e.target.id] && keyMap[keysDown[e.target.id]]) { + simulateKeyEvent('keyup', keyMap[keysDown[e.target.id]]); + } + keysDown[e.target.id] = 0; + }); + + ne.addEventListener('touchmove', function(event) { + const myLocation = event.changedTouches[0]; + const realTarget = document.elementFromPoint(myLocation.clientX, myLocation.clientY).id; + const origTarget = keysDown[myLocation.target.id]; + + if (origTarget !== realTarget) { + if (origTarget) { + simulateKeyEvent('keyup', keyMap[origTarget]); + keysDown[myLocation.target.id] = 0; + } + if (keyMap[realTarget]) { + simulateKeyEvent('keydown', keyMap[realTarget]); + keysDown[myLocation.target.id] = realTarget; + } + } + }); +} + +function is_touch_device() { + try { + document.createEvent("TouchEvent"); + return true; + } catch (e) { + return false; + } +} + +const resize = function() { + const el = document.getElementById('canvas'); + if (window.innerHeight > window.innerWidth) { + el.style.height = 'unset'; + el.style.width = '100%'; + } else { + el.style.width = 'unset'; + el.style.height = '100%'; + } +} + +window.addEventListener('resize', resize); +window.addEventListener('load', resize); diff --git a/extra/js/drive.js b/extra/js/drive.js index 4f2a546..5afbe24 100644 --- a/extra/js/drive.js +++ b/extra/js/drive.js @@ -21,24 +21,27 @@ function _base64ToBytes(base64) { // Canvas used for image generation var generationCanvas = document.createElement('canvas') +window.fileAsyncCache = {}; + +window.getMappingKey = function(file) { + return file.toLowerCase().replace(new RegExp("\\.[^/.]+$"), "") +} window.loadFileAsync = function(fullPath, bitmap, callback) { // noop callback = callback || (() => {}); - // Make cache object - if (!window.fileAsyncCache) window.fileAsyncCache = {}; + // Get mapping key + const mappingKey = getMappingKey(fullPath); + const mappingValue = mapping[mappingKey]; // Check if already loaded - if (window.fileAsyncCache.hasOwnProperty(fullPath)) return callback(); + if (window.fileAsyncCache.hasOwnProperty(mappingKey)) return callback(); + console.log('load', mappingKey); // Show spinner if (!bitmap && window.setBusy) window.setBusy(); - // Get mapping key - const mappingKey = fullPath.toLowerCase().replace(new RegExp("\\.[^/.]+$"), ""); - const mappingValue = mapping[mappingKey]; - // Check if this is a folder if (!mappingValue || mappingValue.endsWith("h=")) { console.error("Skipping loading", fullPath, mappingValue); @@ -56,7 +59,7 @@ window.loadFileAsync = function(fullPath, bitmap, callback) { const load = (cb1) => { getLazyAsset(iurl, filename, (data) => { FS.createPreloadedFile(path, filename, new Uint8Array(data), true, true, function() { - window.fileAsyncCache[fullPath] = 1; + window.fileAsyncCache[mappingKey] = 1; if (!bitmap && window.setNotBusy) window.setNotBusy(); if (window.fileLoadedAsync) window.fileLoadedAsync(fullPath); callback(); @@ -103,3 +106,152 @@ window.loadFileAsync = function(fullPath, bitmap, callback) { load(); } } + + +window.saveFile = function(filename) { + const buf = FS.readFile('/game/' + filename); + const b64 = _bytesToBase64(buf); + localforage.setItem(namespace + filename, b64); + + localforage.getItem(namespace, function(err, res) { + if (err || !res) res = {}; + res[filename] = 1; + localforage.setItem(namespace, res); + }); +}; + +var loadFiles = function() { + localforage.getItem(namespace, function(err, res) { + if (err || !res) return; + + const keys = Object.keys(res); + + console.log('Locally stored savefiles', keys); + + keys.forEach((key) => { + localforage.getItem(namespace + key, (err, res) => { + if (err) return; + + const buf = _base64ToBytes(res); + FS.writeFile('/game/' + key, buf); + }); + }); + }); +} + +var createDummies = function() { + // Base directory + FS.mkdir('/game'); + + // Create dummy objects + Object.values(mapping).forEach((file) => { + // Get filename + const filename = '/game/' + file.split("?")[0]; + + // Check if folder + if (file.endsWith('h=')) { + return FS.mkdir(filename); + } + + // Create dummy file + FS.writeFile(filename, '1'); + }); +}; + +window.setBusy = function() { + document.getElementById('spinner').style.opacity = "0.5"; +}; + +window.setNotBusy = function() { + document.getElementById('spinner').style.opacity = "0"; +}; + +window.onerror = function() { + console.error("An error occured!") +}; + +function preloadList(jsonArray) { + jsonArray.forEach((file) => { + const mappingKey = getMappingKey(file); + const mappingValue = mapping[mappingKey]; + if (!mappingValue || window.fileAsyncCache[mappingKey]) return; + + // Get path and filename + const path = "/game/" + mappingValue.substring(0, mappingValue.lastIndexOf("/")); + const filename = mappingValue.substring(mappingValue.lastIndexOf("/") + 1).split("?")[0]; + + // Preload the asset + FS.createPreloadedFile(path, filename, "gameasync/" + mappingValue, true, true, function() { + window.fileAsyncCache[mappingKey] = 1; + console.log('preload', mappingKey); + }, console.error, false, false, () => { + try { FS.unlink(path + "/" + filename); } catch (err) {} + }); + }); +} + +window.fileLoadedAsync = function(file) { + document.title = wTitle; + + if (!(/.*Map.*rxdata/i.test(file))) return; + + fetch('preload/' + file + '.json') + .then(function(response) { + return response.json(); + }) + .then(function(jsonResponse) { + setTimeout(() => { + preloadList(jsonResponse); + }, 200); + }); +}; + +var hideTimer = 0; +function getLazyAsset(url, filename, callback) { + const xhr = new XMLHttpRequest(); + xhr.responseType = "arraybuffer"; + const pdiv = document.getElementById("progress"); + let showTimer = 0; + let abortTimer = 0; + + const retry = () => { + xhr.abort(); + getLazyAsset(url, filename, callback); + } + + xhr.onreadystatechange = function() { + if (xhr.readyState == XMLHttpRequest.DONE && xhr.status >= 200 && xhr.status < 400) { + pdiv.innerHTML = `${filename} - done`; + hideTimer = setTimeout(() => { + pdiv.style.opacity = '0'; + hideTimer = 0; + }, 500); + clearTimeout(showTimer); + clearTimeout(abortTimer); + callback(xhr.response); + } + } + xhr.onprogress = function (event) { + const loaded = Math.round(event.loaded / 1024); + const total = Math.round(event.total / 1024); + pdiv.innerHTML = `${filename} - ${loaded}KB / ${total}KB`; + + clearTimeout(abortTimer); + abortTimer = setTimeout(retry, 3000); + }; + xhr.open('GET', url); + xhr.send(); + + pdiv.innerHTML = `${filename} - starting`; + + showTimer = setTimeout(() => { + pdiv.style.opacity = '0.5'; + }, 100); + + abortTimer = setTimeout(retry, 3000); + + if (hideTimer) { + clearTimeout(hideTimer); + hideTimer = 0; + } +} diff --git a/extra/shell.html b/extra/shell.html index acfd3a5..2493e9f 100644 --- a/extra/shell.html +++ b/extra/shell.html @@ -7,6 +7,7 @@ + @@ -180,60 +181,11 @@ diff --git a/src/emscripten.cpp b/src/emscripten.cpp index 1061047..6194fa0 100644 --- a/src/emscripten.cpp +++ b/src/emscripten.cpp @@ -13,7 +13,9 @@ EM_JS(void, save_file_async_js, (const char* fullPathC), { }); EM_JS(int, file_is_cached, (const char* fullPathC), { - return window.fileAsyncCache.hasOwnProperty(UTF8ToString(fullPathC)) ? 1 : 0; + const fullPath = UTF8ToString(fullPathC); + const mappingKey = getMappingKey(fullPath); + return window.fileAsyncCache.hasOwnProperty(mappingKey) ? 1 : 0; }); #endif