README as a small game website in Crookfinger's voice; INSTALL.md, BESTIARY.md, playable standalone file
- README: logo, badges (Rust, WebAssembly, Claude Code, MIT), intro addressed to the master, features with screenshots, the household, how to play, licences and credits - INSTALL.md: server, PWA, building, tests, savegame service and API, languages, how to refresh generated files - BESTIARY.md generated from the game's data (tools/make_bestiary.py) with exported sprites and icons (docs/img, test export_doc_images) - play/DungeonCraft.html: the whole game in one file to download and play offline Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4ce2c2919e
commit
b3583f5e7e
124 files changed
+1178
-104
No files matched your search
@@ -0,0 +1,262 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>DungeonCraft</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
<meta name="theme-color" content="#080608">
|
||||
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMAAAADACAYAAABS3GwHAAAF1UlEQVR4nO3dy26bVRSGYduF1KQJtEnkqkiVIiHBhBEj1DtgxhX0KhhwCQy4il5MhYTEiBGjSkVCdg9JnANNwTYTBgzWi9glp+3vfYar7p+/2/qy1aV9GI43xquBFGp03S8gXScDoGgGQNEMgKIZAEV7j/5g9vTxVb6HdKkmj56UdWcARTMAimYAFM0AKJoBUDTsAhH637R0E7R2L50BFM0AKJoBUDQDoGgGQNGau0DEtUO6ShfVjXQGUDQDoGgGQNEMgKIZAEW7sC4Qce2Q/o/L7i46AyiaAVA0A6BoBkDRDICiGQBFMwCKZgAUzQAomgFQNAOgaJe+FmhdbUD9zrD+nUKfJ2+hfrpaNn1e/84ZQNEMgKIZAEUzAIpmABTNLtDftobDsj4Z3irrO6P6dwc95/agrpPzwaqsn6zq+utl3R2arRZNz0njDKBoBkDRDICiGQBFMwCKFtcFug9rdR7eqofiwajuAu1BF+hDeP74P7zbP72B+hzWAr2ELtDOsn6f54s/y/oUnr+unAEUzQAomgFQNAOgaAZA0da2C0Tdnk+g27MP9YfQBTr7aLes34b3qZ/C6DlbUJ8cvSrr27A2CXeohXWHnAEUzQAomgFQNAOgaAZA0brvAtEOLFrbQ92ezXuTsk77pu5D/Q7U34c6+QPqp1CfQ1dqEz6/fzAr63ge0aJ+o953ljkDKJoBUDQDoGgGQNEMgKJ13wWic3toJxet7aFeRt1bGQzuQf2r778t6z9+8x38jdo51I+h3tpl2oVxOG48d+hkVa8d6oUzgKIZAEUzAIpmABTNAChaN10g2sFEpzTTuT20k4vW9lC352vo9mzD5z+GOjmDOu0UI7SmaArjsHf4oqzTOP8KG8V6ubPMGUDRDICiGQBFMwCKZgAUrZsuEN3ATjvC6JRm6qLQTi5a20PdHvKg8fPzxs/T2iHaQfYB1EeN40zfy9tOzhFyBlA0A6BoBkDRDICiGQBF66YLRGuB6AZ2upOLTmmmHVW0k4vW9lC3pz51qB11dai7Rf8u+uLpPnsaZzxluhPOAIpmABTNACiaAVA0A6BoBkDRDICiGQBFMwCKZgAUzQAoWjdrgeicmXM41/kNfJ7WzND5ObTTis7tad3JReg59HPpPenfRWc603NonHs5/4c4AyiaAVA0A6BoBkDRDICiddMFOoVzZuim8jl8foueD3W6k6v1lGZ6PqFuzyHU6T3p5/4O9ZPGcabvpRfOAIpmABTNACiaAVA0A6Bo3XSBaM0J3WD+EuqTo1dlfQ53ZrXewE5raVq7Rq03xR9AndYUbcI4PINxo3F2LZDUMQOgaAZA0QyAohkAReumC0Rmq0VZ31nW2d6Gu642G38u7bSitTet3aTW51O35zXUz5b1uP0GdRrn3jkDKJoBUDQDoGgGQNEMgKJ13wWinUrPF/XJN3Sn1f7BrKzvjupbxaawdohuYG8daDq3h3Zy0doe6vY8g/GhcaNx7p0zgKIZAEUzAIpmABTNACha910gMqXzaqDLQTubjqH7sXf4oqyPhvXvFLqBndCOMDq3h3Zy0doe6vbguK0pZwBFMwCKZgAUzQAomgFQtLXtAhHqcny2dbesH8FzqH7T0E636WG99imNM4CiGQBFMwCKZgAUzQAoWlwX6Mu7k7Le+ynHrWgcfgjrDjkDKJoBUDQDoGgGQNEMgKKtbRfoC7s974TG7ac17Q45AyiaAVA0A6BoBkDRDICidd8F+txuz5Wgcf658+6QM4CiGQBFMwCKZgAUzQAoWjddoE/t9txI9L380kl3yBlA0QyAohkARTMAimYAFK2bLlAvXQX1xRlA0QyAohkARTMAimYAFO3Su0Czp48v+0dI78wZQNEMgKIZAEUzAIpmABTtwrpAk0dPLupR0pVxBlA0A6BoBkDRDICiGQBFa+4CubZH68QZQNEMgKIZAEUzAIpmABRtON4Yr677JaTr4gygaAZA0QyAohkARTMAivYXEawTv/k1XA4AAAAASUVORK5CYII=">
|
||||
<style>
|
||||
html, body { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; background: #080608; }
|
||||
canvas { display: block; width: 100%; height: 100%; outline: none; image-rendering: pixelated; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="glcanvas" tabindex="1" oncontextmenu="return false"></canvas>
|
||||
<!-- DungeonCraft standalone - MIT License - (c) 2026 Richard Zink - https://git.familie-zink.org/zink/dungeoncraft - made with the help of Claude Code (https://claude.com/claude-code) -->
|
||||
<script>var register_plugin; /* expected by the quad_net plugin in the bundle */</script>
|
||||
<script>
|
||||
// the game module is embedded: fetching it is answered from these bytes
|
||||
var DC_WASM = "AGFzbQEAAAABywZlYAF/AGAAAGADf39/AX9gA39/fwBgBn9/f39/fwBgBX9/f39/AGAEf39/fwBgAn9/AGAHf39/f39/fwF/YAF/AX9gAn9/AX9gBH9/f38Bf2AFf39/f38Bf2ADf319AGAFf319fX0AYAd/fX19fX19AGAGf39/fX9/AGAGf39/f39/AX9gAAF8YAABf2AAAX1gCH9/f39/f39/AGAJf39/f39/f39/AGAHf39/f39/fwBgBH19fX0AYAF9AGAKf39/f39/f39/fwBgBH5+f38BfmADfn5/AX5gAX4Bf2AEf39/fQF/YAJ+fwF/YAJ/fQBgAn1/AX9gBn99fXx/fwBgB39/f39/f3wAYAV/f39/fABgC39/f39/f399fX98AGAHf39/f399fQBgBH9/fX0AYAV/f319fwBgB39/f319f3wAYAN/f30AYA1/f319fX9/f39/f39/AGAFfX19fX8AYAZ9fX19fX8AYAV/fX1/fwBgB39/f319fX8AYAZ/f39/fH8AYAh/f39/f39/fABgBn9/f399fQBgCX99fX9/fX9/fwBgDH99fX9/fX9/f39/fwBgAX8BfWAJf39/f319fX99AGAGf39/fX1/AX9gCH9/fX19fX1/AGACfX0Bf2ACf34AYAd/fX1/fX99AGADf399AX1gB39/fX19f38AYAJ9fQF9YAh/f39/fX19fQBgCX9/fX19f39/fwBgAn98AGAFf31/f38AYAR9fX99AGAEf399fwBgA399fwBgBH99f38AYAp/f39/f39/f319AGAGf399fX19AX9gA39/fQF/YAJ/fwF9YAh/f31/f39/fwBgBX9/fX1/AX9gBH9/f30AYAR/f39+AGAFf39/fn8AYAd/fX19fX9/AGAEf399fQF9YBN/f39/f39/f39/f39/f39/f39/AX9gAX8BfmAGf35/f39/AGAJf39/f39/fn5+AGAEf31/fwF/YAN/fX8Bf2ADf35+AGALf39/f39/f39/f38Bf2AAAX5gAn9+AX9gA35+fgF+YAh/f39/f39/fwF/YAV/f359fQBgA39+fwBgC39/f39/f39/f39/AGACfH8Bf2ABfQF9YAV/fn5+fgBgAn1/AX0C1xR6A2Vudgtjb25zb2xlX2xvZwAAA2VudgdkY19odHRwABEDZW52DGRjX3NldF90b2tlbgAHA2VudgNub3cAEgNlbnYRZGNfY29hcnNlX3BvaW50ZXIAEwNlbnYMZGNfZmlsZV9wb2xsABMDZW52DGRjX2ZpbGVfdGFrZQAAA2VudgxkY19odHRwX3BvbGwACQNlbnYLZGNfaHR0cF9sZW4ACQNlbnYMZGNfaHR0cF90YWtlAAcDZW52DmRjX3N0b3JhZ2Vfc2V0AAYDZW52F2RjX2ZldGNoX2Rvd25sb2FkX3N0YXRlABMDZW52C2RjX2Rvd25sb2FkAAYDZW52DGRjX29wZW5fZmlsZQABA2VudgtkY19vcGVuX3VybAAHA2VudgxkY19jYWNoZV9zZXQABgNlbnYRZGNfZmV0Y2hfZG93bmxvYWQABgNlbnYKaW5pdF93ZWJnbAAAA2VudhFzZXR1cF9jYW52YXNfc2l6ZQAAA2VudgxjYW52YXNfd2lkdGgAEwNlbnYNY2FudmFzX2hlaWdodAATA2VudglkcGlfc2NhbGUAFANlbnYScnVuX2FuaW1hdGlvbl9sb29wAAADZW52DmRjX3N0b3JhZ2VfZ2V0AAoDZW52D2RjX3N0b3JhZ2VfdGFrZQAAA2VudglkY19wcm9tcHQACwNlbnYNZ2xCaW5kVGV4dHVyZQAHA2VudhBnbENvcHlUZXhJbWFnZTJEABUDZW52DWdsR2V0SW50ZWdlcnYABwNlbnYRZ2xHZW5WZXJ0ZXhBcnJheXMABwNlbnYRZ2xCaW5kVmVydGV4QXJyYXkAAANlbnYLZ2xHZXRTdHJpbmcACQNlbnYUc2FwcF9zY2hlZHVsZV91cGRhdGUAAQNlbnYMZ2xHZW5RdWVyaWVzAAcDZW52DGdsQmVnaW5RdWVyeQAHA2VudgpnbEVuZFF1ZXJ5AAADZW52B2dsRmx1c2gAAQNlbnYIZ2xGaW5pc2gAAQNlbnYMY29uc29sZV93YXJuAAADZW52D2dsQ3JlYXRlUHJvZ3JhbQATA2Vudg5nbEF0dGFjaFNoYWRlcgAHA2Vudg1nbExpbmtQcm9ncmFtAAADZW52DmdsRGV0YWNoU2hhZGVyAAcDZW52DmdsRGVsZXRlU2hhZGVyAAADZW52DmdsR2V0UHJvZ3JhbWl2AAMDZW52E2dsR2V0UHJvZ3JhbUluZm9Mb2cABgNlbnYMZ2xVc2VQcm9ncmFtAAADZW52FGdsR2V0VW5pZm9ybUxvY2F0aW9uAAoDZW52DWdsR2VuVGV4dHVyZXMABwNlbnYPZ2xBY3RpdmVUZXh0dXJlAAADZW52DWdsUGl4ZWxTdG9yZWkABwNlbnYSZ2xHZW5SZW5kZXJidWZmZXJzAAcDZW52EmdsQmluZFJlbmRlcmJ1ZmZlcgAHA2VudiBnbFJlbmRlcmJ1ZmZlclN0b3JhZ2VNdWx0aXNhbXBsZQAFA2VudgxnbFRleEltYWdlMkQAFgNlbnYPZ2xUZXhQYXJhbWV0ZXJpAAMDZW52EGdsR2VuZXJhdGVNaXBtYXAAAANlbnYRZ2xHZW5GcmFtZWJ1ZmZlcnMABwNlbnYRZ2xCaW5kRnJhbWVidWZmZXIABwNlbnYWZ2xGcmFtZWJ1ZmZlclRleHR1cmUyRAAFA2VudgxnbFJlYWRQaXhlbHMAFwNlbnYUZ2xEZWxldGVGcmFtZWJ1ZmZlcnMABwNlbnYPZ2xUZXhTdWJJbWFnZTJEABYDZW52GWdsRnJhbWVidWZmZXJSZW5kZXJidWZmZXIABgNlbnYNZ2xEcmF3QnVmZmVycwAHA2VudhhnbENoZWNrRnJhbWVidWZmZXJTdGF0dXMACQNlbnYVZ2xEZWxldGVSZW5kZXJidWZmZXJzAAcDZW52EGdsRGVsZXRlVGV4dHVyZXMABwNlbnYTZ2xHZXRBdHRyaWJMb2NhdGlvbgAKA2VudghnbEVuYWJsZQAAA2VudglnbERpc2FibGUAAANlbnYLZ2xEZXB0aEZ1bmMAAANlbnYLZ2xGcm9udEZhY2UAAANlbnYKZ2xDdWxsRmFjZQAAA2VudhNnbEJsZW5kRnVuY1NlcGFyYXRlAAYDZW52F2dsQmxlbmRFcXVhdGlvblNlcGFyYXRlAAcDZW52C2dsQmxlbmRGdW5jAAcDZW52E2dsU3RlbmNpbE9wU2VwYXJhdGUABgNlbnYVZ2xTdGVuY2lsRnVuY1NlcGFyYXRlAAYDZW52FWdsU3RlbmNpbE1hc2tTZXBhcmF0ZQAHA2VudgtnbENvbG9yTWFzawAGA2VudgxnbEdlbkJ1ZmZlcnMABwNlbnYMZ2xCaW5kQnVmZmVyAAcDZW52DGdsQnVmZmVyRGF0YQAGA2Vudg9nbEJ1ZmZlclN1YkRhdGEABgNlbnYPZ2xEZWxldGVCdWZmZXJzAAcDZW52GmdsRGlzYWJsZVZlcnRleEF0dHJpYkFycmF5AAADZW52D2dsRGVsZXRlUHJvZ3JhbQAAA2VudgpnbFZpZXdwb3J0AAYDZW52CWdsU2Npc3NvcgAGA2VudgtnbFVuaWZvcm0xaQAHA2VudhZnbFZlcnRleEF0dHJpYklQb2ludGVyAAUDZW52FWdsVmVydGV4QXR0cmliUG9pbnRlcgAEA2VudhVnbFZlcnRleEF0dHJpYkRpdmlzb3IABwNlbnYZZ2xFbmFibGVWZXJ0ZXhBdHRyaWJBcnJheQAAA2VudgxnbFVuaWZvcm0xZnYAAwNlbnYMZ2xVbmlmb3JtMmZ2AAMDZW52DGdsVW5pZm9ybTNmdgADA2VudgxnbFVuaWZvcm00ZnYAAwNlbnYMZ2xVbmlmb3JtMWl2AAMDZW52DGdsVW5pZm9ybTJpdgADA2VudgxnbFVuaWZvcm0zaXYAAwNlbnYMZ2xVbmlmb3JtNGl2AAMDZW52EmdsVW5pZm9ybU1hdHJpeDRmdgAGA2VudgxnbENsZWFyQ29sb3IAGANlbnYNZ2xDbGVhckRlcHRoZgAZA2Vudg5nbENsZWFyU3RlbmNpbAAAA2VudgdnbENsZWFyAAADZW52DGdsUmVhZEJ1ZmZlcgAAA2VudhFnbEJsaXRGcmFtZWJ1ZmZlcgAaA2VudhdnbERyYXdFbGVtZW50c0luc3RhbmNlZAAFA2Vudg5nbENyZWF0ZVNoYWRlcgAJA2Vudg5nbFNoYWRlclNvdXJjZQAGA2Vudg9nbENvbXBpbGVTaGFkZXIAAANlbnYNZ2xHZXRTaGFkZXJpdgADA2VudhJnbEdldFNoYWRlckluZm9Mb2cABgNlbnYSc2FwcF9zZXRfY2xpcGJvYXJkAAcDZW52EmZzX2dldF9idWZmZXJfc2l6ZQAJA2Vudg5mc190YWtlX2J1ZmZlcgADA2VudhRzYXBwX3NldF9jdXJzb3JfZ3JhYgAAA2Vudg9zYXBwX3NldF9jdXJzb3IABwNlbnYTc2FwcF9zZXRfZnVsbHNjcmVlbgAAA4wKigoDAwoGAgADCgMBCgMHCgMAAgMXCQAHAAcAFhcWFRUCFgUVBhYFCgcDGwocChwKAQABAAEBAQYFAAAAAAAAAAAAAAAAAAMAAAAAAAcAAAAAAAAAAAAAAAAABQMHBwIDAgcHAgcHBwoKAAoDAgADCh0HBwAHBQcFBwUDBAsKCwsMAwMBAwYHBwMEBAQXBgQDBwcHBwoKCwIMDAIHCwseCwsfCwsMByAKDAICCgICAgIMAgIKAgcCAwMJCQoMCgMDCwMDAwMDAwMDBwMCCgcHCSEHBwcHBwcHBwcJCQcHBwcHBwcHLine truncated
|
||||
(function () {
|
||||
var realFetch = window.fetch ? window.fetch.bind(window) : null;
|
||||
window.fetch = function (url, opts) {
|
||||
if (typeof url === "string" && url.indexOf("dungeoncraft.wasm") >= 0) {
|
||||
var bin = atob(DC_WASM);
|
||||
var bytes = new Uint8Array(bin.length);
|
||||
for (var i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
|
||||
return Promise.resolve(new Response(bytes, { headers: { "Content-Type": "application/wasm" } }));
|
||||
}
|
||||
return realFetch ? realFetch(url, opts) : Promise.reject(new Error("offline"));
|
||||
};
|
||||
})();
|
||||
</script>
|
||||
<script>"use strict";const version=2,canvas=document.querySelector("#glcanvas");var gl,wasm_memory,animation_frame_timeout,FS,GL,Module,wasm_exports,emscripten_shaders_hack,importObject,clipboard=null,plugins=[],high_dpi=!1,blocking_event_loop=!1;function init_webgl(e){if(e==1){gl=canvas.getContext("webgl");function t(e){var t=e.getExtension("OES_vertex_array_object");t?(e.createVertexArray=function(){return t.createVertexArrayOES()},e.deleteVertexArray=function(e){t.deleteVertexArrayOES(e)},e.bindVertexArray=function(e){t.bindVertexArrayOES(e)},e.isVertexArray=function(e){return t.isVertexArrayOES(e)}):alert("Unable to get OES_vertex_array_object extension")}function n(e){var t=e.getExtension("ANGLE_instanced_arrays");t&&(e.vertexAttribDivisor=function(e,n){t.vertexAttribDivisorANGLE(e,n)},e.drawArraysInstanced=function(e,n,s,o){t.drawArraysInstancedANGLE(e,n,s,o)},e.drawElementsInstanced=function(e,n,s,o,i){t.drawElementsInstancedANGLE(e,n,s,o,i)})}function s(e){var t=e.getExtension("EXT_disjoint_timer_query");t&&(e.createQuery=function(){return t.createQueryEXT()},e.beginQuery=function(e,n){return t.beginQueryEXT(e,n)},e.endQuery=function(e){return t.endQueryEXT(e)},e.deleteQuery=function(e){t.deleteQueryEXT(e)},e.getQueryObject=function(e,n){return t.getQueryObjectEXT(e,n)})}function o(e){var t=e.getExtension("WEBGL_draw_buffers");t&&(e.drawBuffers=function(e){return t.drawBuffersWEBGL(e)})}try{gl.getExtension("EXT_shader_texture_lod"),gl.getExtension("OES_standard_derivatives")}catch(e){console.warn(e)}t(gl),n(gl),s(gl),o(gl),gl.getExtension("WEBGL_depth_texture")==null&&alert("Cant initialize WEBGL_depth_texture extension")}else gl=canvas.getContext("webgl2");gl===null&&alert("Unable to initialize WebGL. Your browser or machine may not support it.")}canvas.focus(),canvas.requestPointerLock=canvas.requestPointerLock||canvas.mozRequestPointerLock||function(){},document.exitPointerLock=document.exitPointerLock||document.mozExitPointerLock||function(){};function assert(e,t){e==!1&&alert(t)}function getArray(e,t,n){return new t(wasm_memory.buffer,e,n)}function UTF8ToString(e,t){let i=new Uint8Array(wasm_memory.buffer,e);for(var n,a,r,c,s=0,l=s+t,o="";!(s>=l);){if(n=i[s++],!n)return o;if(!(n&128)){o+=String.fromCharCode(n);continue}if(a=i[s++]&63,(n&224)==192){o+=String.fromCharCode((n&31)<<6|a);continue}r=i[s++]&63,(n&240)==224?n=(n&15)<<12|a<<6|r:((n&248)!=240&&console.warn("Invalid UTF-8 leading byte 0x"+n.toString(16)+" encountered when deserializing a UTF-8 string on the asm.js/wasm heap to a JS string!"),n=(n&7)<<18|a<<12|r<<6|i[s++]&63),n<65536?o+=String.fromCharCode(n):(c=n-65536,o+=String.fromCharCode(55296|c>>10,56320|c&1023))}return o}function stringToUTF8(e,t,n,s){for(var o,r,c=n,i=n+s,a=0;a<e.length;++a)if(o=e.charCodeAt(a),o>=55296&&o<=57343&&(r=e.charCodeAt(++a),o=65536+((o&1023)<<10)|r&1023),o<=127){if(n>=i)break;t[n++]=o}else if(o<=2047){if(n+1>=i)break;t[n++]=192|o>>6,t[n++]=128|o&63}else if(o<=65535){if(n+2>=i)break;t[n++]=224|o>>12,t[n++]=128|o>>6&63,t[n++]=128|o&63}else{if(n+3>=i)break;o>=2097152&&console.warn("Invalid Unicode code point 0x"+o.toString(16)+" encountered when serializing a JS string to an UTF-8 string on the asm.js/wasm heap! (Valid unicode code points should be in range 0-0x1FFFFF)."),t[n++]=240|o>>18,t[n++]=128|o>>12&63,t[n++]=128|o>>6&63,t[n++]=128|o&63}return n-c}FS={loaded_files:[],unique_id:0},GL={counter:1,buffers:[],mappedBuffers:{},programs:[],framebuffers:[],renderbuffers:[],textures:[],uniforms:[],shaders:[],vaos:[],timerQueries:[],contexts:{},programInfos:{},getNewId:function(e){for(var n=GL.counter++,t=e.length;t<n;t++)e[t]=null;return n},validateGLObjectID:function(e,t,n,s){t!=0&&(e[t]===null?console.error(n+" called with an already deleted "+s+" ID "+t+"!"):e[t]||console.error(n+" called with an invalid "+s+" ID "+t+"!"))},getSource:function(e,t,n,s){for(var a,i="",o=0;o<t;++o)a=s==0?0[0]:getArray(s+o*4,Uint32Array,1)[0],i+=UTF8ToString(getArray(n+o*4,Uint32Array,1)[0],a);return i},populateUniformTable:function(e){GL.validateGLObjectID(GL.programs,e,"populateUniformTable","program");for(var t,n,s,i,a,l,o=GL.programs[e],r=GL.programInfos[e]={uniforms:{},maxUniformLength:0,maxAttributeLength:-1,maxUniformBlockNameLength:-1},d=r.uniforms,u=gl.getProgramParameter(o,35718),c=0;c<u;++c)if(i=gl.getActiveUniform(o,c),t=i.name,r.maxUniformLength=Math.max(r.maxUniformLength,t.length+1),t.slice(-1)=="]"&&(t=t.slice(0,t.lastIndexOf("["))),n=gl.getUniformLocation(o,t),n){s=GL.getNewId(GL.uniforms),d[t]=[i.size,s],GL.uniforms[s]=n;for(a=1;a<i.size;++a)l=t+"["+a+"]",n=gl.getUniformLocation(o,l),s=GL.getNewId(GL.uniforms),GL.uniforms[s]=n}}};function _glGenObject(e,t,n,s,o){for(var i,a,r=0;r<e;r++)i=gl[n](),a=i&&GL.getNewId(s),i?(i.name=a,s[a]=i):(console.error("GL_INVALID_OPERATION"),GL.recordError(1282),alert("GL_INVALID_OPERATION in "+o+": GLctx."+n+" returned null - most likely GL context is lost!")),getArray(t+r*4,Int32Array,1)[0]=a}function _webglGet(e,t,n){if(!tLine truncated
|
||||
`,o=o.replace(/gl_FragColor/g,"GL_FragColor")),o.indexOf("attribute")!=-1?(o=o.replace(/attribute/g,"in"),o=o.replace(/varying/g,"out")):o=o.replace(/varying/g,"in"),o=o.replace(/textureCubeLodEXT/g,"textureCubeLod"),o=o.replace(/texture2DLodEXT/g,"texture2DLod"),o=o.replace(/texture2DProjLodEXT/g,"texture2DProjLod"),o=o.replace(/texture2DGradEXT/g,"texture2DGrad"),o=o.replace(/texture2DProjGradEXT/g,"texture2DProjGrad"),o=o.replace(/textureCubeGradEXT/g,"textureCubeGrad"),o=o.replace(/textureCube/g,"texture"),o=o.replace(/texture1D/g,"texture"),o=o.replace(/texture2D/g,"texture"),o=o.replace(/texture3D/g,"texture"),o=o.replace(/#version 100/g,`#version 300 es
|
||||
`+i)),gl.shaderSource(GL.shaders[e],o)},glGetProgramInfoLog:function(e,t,n,s){GL.validateGLObjectID(GL.programs,e,"glGetProgramInfoLog","program");var o,i=gl.getProgramInfoLog(GL.programs[e]);assert(i!==null);let a=getArray(s,Uint8Array,t);for(o=0;o<t;o++)a[o]=i.charCodeAt(o)},glGetString:function(e){var t=gl.getParameter(e).toString(),n=t.length+1,s=wasm_exports.allocate_vec_u8(n),o=new Uint8Array(wasm_memory.buffer,s,n);return o[t.length]=0,stringToUTF8(t,o,0,n),s},glCompileShader:function(e){GL.validateGLObjectID(GL.shaders,e,"glCompileShader","shader"),gl.compileShader(GL.shaders[e])},glGetShaderiv:function(e,t,n){if(assert(n),GL.validateGLObjectID(GL.shaders,e,"glGetShaderiv","shader"),t==35716){var s,i,o=gl.getShaderInfoLog(GL.shaders[e]);assert(o!==null),getArray(n,Int32Array,1)[0]=o.length+1}else t==35720?(s=gl.getShaderSource(GL.shaders[e]),i=s===null||s.length==0?0:s.length+1,getArray(n,Int32Array,1)[0]=i):getArray(n,Int32Array,1)[0]=gl.getShaderParameter(GL.shaders[e],t)},glGetShaderInfoLog:function(e,t,n,s){GL.validateGLObjectID(GL.shaders,e,"glGetShaderInfoLog","shader");var o,i=gl.getShaderInfoLog(GL.shaders[e]);assert(i!==null);let a=getArray(s,Uint8Array,t);for(o=0;o<t;o++)a[o]=i.charCodeAt(o)},glVertexAttribDivisor:function(e,t){gl.vertexAttribDivisor(e,t)},glDrawArraysInstanced:function(e,t,n,s){gl.drawArraysInstanced(e,t,n,s)},glDrawElementsInstanced:function(e,t,n,s,o){gl.drawElementsInstanced(e,t,n,s,o)},glDeleteShader:function(e){var t=GL.shaders[e];if(t==null)return;gl.deleteShader(t),GL.shaders[e]=null},glDeleteProgram:function(e){var t=GL.programs[e];if(t==null)return;gl.deleteProgram(t),GL.programs[e]=null},glDeleteBuffers:function(e,t){for(var n,o,s=0;s<e;s++){if(o=getArray(t+s*4,Uint32Array,1)[0],n=GL.buffers[o],!n)continue;gl.deleteBuffer(n),n.name=0,GL.buffers[o]=null}},glDeleteFramebuffers:function(e,t){for(var n,o,s=0;s<e;s++){if(o=getArray(t+s*4,Uint32Array,1)[0],n=GL.framebuffers[o],!n)continue;gl.deleteFramebuffer(n),n.name=0,GL.framebuffers[o]=null}},glDeleteRenderbuffers:function(e,t){for(var n,o,s=0;s<e;s++){if(o=getArray(t+s*4,Uint32Array,1)[0],n=GL.renderbuffers[o],!n)continue;gl.deleteRenderbuffer(n),n.name=0,GL.renderbuffers[o]=null}},glDeleteTextures:function(e,t){for(var n,o,s=0;s<e;s++){if(o=getArray(t+s*4,Uint32Array,1)[0],n=GL.textures[o],!n)continue;gl.deleteTexture(n),n.name=0,GL.textures[o]=null}},glGenQueries:function(e,t){_glGenObject(e,t,"createQuery",GL.timerQueries,"glGenQueries")},glDeleteQueries:function(e){for(var n,o,s=0;s<e;s++){if(o=getArray(textures+s*4,Uint32Array,1)[0],n=GL.timerQueries[o],!n)continue;gl.deleteQuery(n),n.name=0,GL.timerQueries[o]=null}},glBeginQuery:function(e,t){GL.validateGLObjectID(GL.timerQueries,t,"glBeginQuery","id"),gl.beginQuery(e,GL.timerQueries[t])},glEndQuery:function(e){gl.endQuery(e)},glGetQueryObjectiv:function(e,t,n){GL.validateGLObjectID(GL.timerQueries,e,"glGetQueryObjectiv","id");let s=gl.getQueryObject(GL.timerQueries[e],t);getArray(n,Uint32Array,1)[0]=s},glGetQueryObjectui64v:function(e,t,n){GL.validateGLObjectID(GL.timerQueries,e,"glGetQueryObjectui64v","id");let o=gl.getQueryObject(GL.timerQueries[e],t),s=getArray(n,Uint32Array,2);s[0]=o,s[1]=(o-s[0])/4294967296},glGenerateMipmap:function(e){gl.generateMipmap(e)},glRenderbufferStorageMultisample:function(e,t,n,s,o){gl.renderbufferStorageMultisample(e,t,n,s,o)},glFramebufferRenderbuffer:function(e,t,n,s){GL.validateGLObjectID(GL.renderbuffers,s,"glFramebufferRenderbuffer","renderbuffer"),gl.framebufferRenderbuffer(e,t,n,GL.renderbuffers[s])},glCheckFramebufferStatus:function(e){return gl.checkFramebufferStatus(e)},glReadBuffer:function(e){gl.readBuffer(e)},glBlitFramebuffer:function(e,t,n,s,o,i,a,r,c,l){gl.blitFramebuffer(e,t,n,s,o,i,a,r,c,l)},setup_canvas_size:function(e){window.high_dpi=e,resize(canvas)},run_animation_loop:function(e){canvas.onmousemove=function(e){var t=mouse_relative_position(e.clientX,e.clientY),n=t.x,s=t.y;wasm_exports.mouse_move(Math.floor(n),Math.floor(s)),(e.movementX!=0||e.movementY!=0)&&wasm_exports.raw_mouse_move(Math.floor(e.movementX),Math.floor(e.movementY))},canvas.onmousedown=function(e){var t=mouse_relative_position(e.clientX,e.clientY),n=t.x,s=t.y,o=into_sapp_mousebutton(e.button);wasm_exports.mouse_down(n,s,o)},canvas.addEventListener("wheel",function(e){e.preventDefault(),wasm_exports.mouse_wheel(-e.deltaX,-e.deltaY)}),canvas.onmouseup=function(e){var t=mouse_relative_position(e.clientX,e.clientY),n=t.x,s=t.y,o=into_sapp_mousebutton(e.button);wasm_exports.mouse_up(n,s,o)},canvas.onkeydown=function(e){var n,t=into_sapp_keycode(e.code);switch(t){case 32:case 262:case 263:case 264:case 265:case 290:case 291:case 292:case 293:case 294:case 295:case 296:case 297:case 298:case 299:case 259:case 258:case 39:case 47:e.preventDefault();break}n=0,e.ctrlKey&&(n|=SAPP_MODIFIER_CTRL),e.shiftKey&&(n|=SAPP_MODIFIER_SHIFT),e.altKey&&(n|=SAPP_MODIFIER_ALT),wasm_exports.key_down(t,n,e.repeat),(t==32||t==39||t==47)&&wasm_exports.key_presLine truncated
|
||||
<script>// DungeonCraft browser plugin for miniquad: storage, file download/upload, save-on-leave,
|
||||
// HTTP requests (savegame service), audio output.
|
||||
(function () {
|
||||
"use strict";
|
||||
var pending = null; // value handed over by dc_storage_get
|
||||
var file = null; // bytes of a file chosen via the file picker
|
||||
var cache = {}; // key -> string, persisted to localStorage when the page is hidden/closed
|
||||
var http = {}; // request id -> { done, status, body }
|
||||
var httpNext = 1;
|
||||
var token = ""; // login session of the savegame service (sent with every request)
|
||||
var fetched = { state: 0 }; // download of a file from the server: 0 idle, 1 running, 2 done, 3 failed
|
||||
|
||||
function bytes(ptr, len) {
|
||||
return new Uint8Array(wasm_memory.buffer, ptr, len);
|
||||
}
|
||||
function str(ptr, len) {
|
||||
return new TextDecoder().decode(bytes(ptr, len));
|
||||
}
|
||||
function flushCache() {
|
||||
for (var k in cache) {
|
||||
try { window.localStorage.setItem(k, cache[k]); } catch (e) { }
|
||||
}
|
||||
}
|
||||
window.addEventListener("pagehide", flushCache);
|
||||
document.addEventListener("visibilitychange", function () {
|
||||
if (document.visibilityState === "hidden") flushCache();
|
||||
});
|
||||
|
||||
// hidden <input type=file> for loading savegames
|
||||
var input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.accept = ".dcsave,.json,application/json";
|
||||
input.style.display = "none";
|
||||
input.addEventListener("change", function () {
|
||||
var f = input.files && input.files[0];
|
||||
if (!f) return;
|
||||
f.arrayBuffer().then(function (buf) { file = new Uint8Array(buf); });
|
||||
input.value = "";
|
||||
});
|
||||
document.addEventListener("DOMContentLoaded", function () { document.body.appendChild(input); });
|
||||
|
||||
// Asynchronous HTTP request; the result is polled by the game.
|
||||
// status 0 = network error (e.g. no service, or the page was opened as a local file)
|
||||
function httpStart(method, url, body) {
|
||||
var id = httpNext++;
|
||||
var req = { done: false, status: 0, body: new Uint8Array(0) };
|
||||
http[id] = req;
|
||||
var ctrl = typeof AbortController !== "undefined" ? new AbortController() : null;
|
||||
var timer = setTimeout(function () { if (ctrl) ctrl.abort(); }, 15000);
|
||||
try {
|
||||
var opts = { method: method, cache: "no-store", headers: {} };
|
||||
if (token) opts.headers["Authorization"] = "Bearer " + token;
|
||||
if (ctrl) opts.signal = ctrl.signal;
|
||||
if (body !== null) { opts.body = body; opts.headers["Content-Type"] = "application/json"; }
|
||||
fetch(url, opts).then(function (r) {
|
||||
req.status = r.status;
|
||||
return r.arrayBuffer();
|
||||
}).then(function (buf) {
|
||||
req.body = new Uint8Array(buf);
|
||||
req.done = true;
|
||||
clearTimeout(timer);
|
||||
}).catch(function () {
|
||||
req.status = 0;
|
||||
req.done = true;
|
||||
clearTimeout(timer);
|
||||
});
|
||||
} catch (e) {
|
||||
req.done = true;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
function register(importObject) {
|
||||
var env = importObject.env;
|
||||
env.dc_set_token = function (tp, tl) {
|
||||
token = tl > 0 ? str(tp, tl) : "";
|
||||
};
|
||||
// fetches a file (with the login) and offers it as a download, without copying it into the game
|
||||
env.dc_fetch_download = function (up, ul, np, nl) {
|
||||
var url = str(up, ul), name = str(np, nl);
|
||||
fetched.state = 1;
|
||||
var opts = { cache: "no-store", headers: {} };
|
||||
if (token) opts.headers["Authorization"] = "Bearer " + token;
|
||||
fetch(url, opts).then(function (r) {
|
||||
if (!r.ok) throw new Error("status " + r.status);
|
||||
return r.blob();
|
||||
}).then(function (blob) {
|
||||
var a = document.createElement("a");
|
||||
a.href = URL.createObjectURL(blob);
|
||||
a.download = name;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
setTimeout(function () { URL.revokeObjectURL(a.href); a.remove(); }, 2000);
|
||||
fetched.state = 2;
|
||||
}).catch(function () { fetched.state = 3; });
|
||||
};
|
||||
env.dc_fetch_download_state = function () {
|
||||
var s = fetched.state;
|
||||
if (s >= 2) fetched.state = 0;
|
||||
return s;
|
||||
};
|
||||
env.dc_http = function (mp, ml, up, ul, bp, bl) {
|
||||
var body = bl > 0 ? bytes(bp, bl).slice() : null;
|
||||
return httpStart(str(mp, ml), str(up, ul), body);
|
||||
};
|
||||
// -2 = still running, otherwise the HTTP status (0 = network error)
|
||||
env.dc_http_poll = function (id) {
|
||||
var r = http[id];
|
||||
if (!r) return 0;
|
||||
return r.done ? r.status : -2;
|
||||
};
|
||||
env.dc_http_len = function (id) {
|
||||
var r = http[id];
|
||||
return r ? r.body.length : 0;
|
||||
};
|
||||
// copies the response body and forgets the request
|
||||
env.dc_http_take = function (id, ptr) {
|
||||
var r = http[id];
|
||||
if (!r) return;
|
||||
bytes(ptr, r.body.length).set(r.body);
|
||||
delete http[id];
|
||||
};
|
||||
env.dc_storage_get = function (kp, kl) {
|
||||
var v = null;
|
||||
try { v = window.localStorage.getItem(str(kp, kl)); } catch (e) { }
|
||||
if (v === null) { pending = null; return -1; }
|
||||
pending = new TextEncoder().encode(v);
|
||||
return pending.length;
|
||||
};
|
||||
env.dc_storage_take = function (ptr) {
|
||||
if (pending === null) return;
|
||||
bytes(ptr, pending.length).set(pending);
|
||||
pending = null;
|
||||
};
|
||||
// browser text dialog (touch devices have no keyboard in the canvas); result via dc_storage_take
|
||||
env.dc_prompt = function (tp, tl, dp, dl) {
|
||||
var v = window.prompt(str(tp, tl), str(dp, dl));
|
||||
if (v === null || v === undefined) { pending = null; return -1; }
|
||||
pending = new TextEncoder().encode(v);
|
||||
return pending.length;
|
||||
};
|
||||
// opens a web page in a new tab (repository link)
|
||||
env.dc_open_url = function (up, ul) {
|
||||
try { window.open(str(up, ul), "_blank", "noopener"); } catch (e) { }
|
||||
};
|
||||
// the device's main pointer is a finger (phones, tablets)
|
||||
env.dc_coarse_pointer = function () {
|
||||
try { return window.matchMedia("(pointer: coarse)").matches ? 1 : 0; } catch (e) { return 0; }
|
||||
};
|
||||
env.dc_storage_set = function (kp, kl, vp, vl) {
|
||||
try { window.localStorage.setItem(str(kp, kl), str(vp, vl)); } catch (e) { }
|
||||
};
|
||||
env.dc_cache_set = function (kp, kl, vp, vl) {
|
||||
cache[str(kp, kl)] = str(vp, vl);
|
||||
};
|
||||
env.dc_download = function (np, nl, dp, dl) {
|
||||
var blob = new Blob([bytes(dp, dl).slice()], { type: "application/json" });
|
||||
var a = document.createElement("a");
|
||||
a.href = URL.createObjectURL(blob);
|
||||
a.download = str(np, nl);
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
setTimeout(function () { URL.revokeObjectURL(a.href); a.remove(); }, 1000);
|
||||
};
|
||||
env.dc_open_file = function () {
|
||||
file = null;
|
||||
input.click();
|
||||
};
|
||||
env.dc_file_poll = function () {
|
||||
return file === null ? -1 : file.length;
|
||||
};
|
||||
env.dc_file_take = function (ptr) {
|
||||
if (file === null) return;
|
||||
bytes(ptr, file.length).set(file);
|
||||
file = null;
|
||||
};
|
||||
}
|
||||
|
||||
// ---- audio: the Rust mixer renders on demand (dc_audio_render), started by the first user input
|
||||
var actx = null;
|
||||
var stats = { frames: 0, peak: 0 };
|
||||
// diagnostics, e.g. for automated tests: state of the audio output
|
||||
window.dc_audio_stats = function () {
|
||||
return { state: actx ? actx.state : "not started", frames: stats.frames, peak: stats.peak };
|
||||
};
|
||||
function startAudio() {
|
||||
if (typeof wasm_exports === "undefined" || !wasm_exports.dc_audio_render) return;
|
||||
if (actx) {
|
||||
if (actx.state !== "running") actx.resume();
|
||||
return;
|
||||
}
|
||||
var AC = window.AudioContext || window.webkitAudioContext;
|
||||
if (!AC) return;
|
||||
actx = new AC();
|
||||
wasm_exports.dc_audio_init(actx.sampleRate);
|
||||
var node = actx.createScriptProcessor(2048, 0, 2);
|
||||
node.onaudioprocess = function (e) {
|
||||
var n = e.outputBuffer.length;
|
||||
var l = e.outputBuffer.getChannelData(0);
|
||||
var r = e.outputBuffer.getChannelData(1);
|
||||
var ptr = wasm_exports.dc_audio_render(n);
|
||||
if (!ptr) { l.fill(0); r.fill(0); return; }
|
||||
var buf = new Float32Array(wasm_memory.buffer, ptr, n * 2);
|
||||
var peak = 0;
|
||||
for (var i = 0; i < n; i++) {
|
||||
l[i] = buf[2 * i]; r[i] = buf[2 * i + 1];
|
||||
var a = Math.abs(l[i]); if (a > peak) peak = a;
|
||||
}
|
||||
stats.frames += n;
|
||||
stats.peak = Math.max(stats.peak * 0.9, peak);
|
||||
};
|
||||
node.connect(actx.destination);
|
||||
}
|
||||
["mousedown", "keydown", "touchstart"].forEach(function (ev) {
|
||||
window.addEventListener(ev, startAudio, true);
|
||||
});
|
||||
|
||||
miniquad_add_plugin({ register_plugin: register, version: 1, name: "dungeoncraft" });
|
||||
})();
|
||||
</script>
|
||||
<script>
|
||||
document.getElementById("glcanvas").focus();
|
||||
load("dungeoncraft.wasm");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user