diff options
Diffstat (limited to 'deploy_template')
22 files changed, 1065 insertions, 20 deletions
diff --git a/deploy_template/.dragonruby/stubs/html5/dragonruby-html5-loader.js b/deploy_template/.dragonruby/stubs/html5/dragonruby-html5-loader.js new file mode 100644 index 0000000..c321422 --- /dev/null +++ b/deploy_template/.dragonruby/stubs/html5/dragonruby-html5-loader.js @@ -0,0 +1,688 @@ +function syncDataFiles(dbname, baseurl) +{ + var retval = {}; + if (typeof (dbname) === "undefined") { dbname = "files"; } + if (typeof (baseurl) === "undefined") { baseurl = ""; } + + // this is appended to files as an arg to defeat XMLHttpRequest cacheing. + // (Don't do this on most hosting services, from itch.io to + // playonjump.com, since we generally don't update the datafiles anyhow + // once we're shipping, and it defeats Cloudflare cacheing, causing it + // to abuse Amazon S3, etc.) + var urlrandomizerarg = ''; + if (false) { + urlrandomizerarg = "?nocache=" + (Date.now() / 1000 | 0); + } + + var state = { + db: null, + reported_result: false, + xhrs: {}, + remote_manifest: {}, + remote_manifest_loaded: false, + local_manifest: {}, + local_manifest_loaded: false, + total_to_download: 0, + total_downloaded: 0, + total_files: 0, + pending_files: 0 + }; + + var log = function(str) { console.log("CACHEAPPDATA: " + str); } + var debug = function(str) {} + //debug = function(str) { log(str); } + + var clear_state = function() { + for (var i in state.xhrs) { + state.xhrs[i].abort(); + } + delete state.db; + delete state.xhrs; + delete state.remote_manifest; + delete state.local_manifest; + }; + + var failed = function(why) { + if (state.reported_result) { return; } + state.reported_result = true; + log("[FAILURE] " + why); + clear_state(); + if (retval.onerror) { + retval.onerror(why); + } + }; + + retval.abort = function() { + failed("Aborted."); + } + + var succeeded = function() { + if (state.reported_result) { return; } + state.reported_result = true; + var why = "File data synchronized (downloaded " + Math.ceil(state.total_downloaded / 1048576) + " megabytes in " + state.total_files + " files)"; + log("[SUCCESS] " + why); + retval.db = state.db; + retval.manifest = state.remote_manifest; + clear_state(); + if (retval.onsuccess) { + retval.onsuccess(why); + } + }; + + var prevprogress = ""; + var progress = function(str) { + if (state.reported_result) { return; } + if (str == prevprogress) { return; } + prevprogress = str; + log("[PROGRESS] " + str); + if (retval.onprogress) { + retval.onprogress(str, state.total_downloaded, state.total_to_download); + } + } + + debug("Database name is '" + dbname + "'."); + progress("Opening database..."); + var dbopen = window.indexedDB.open(dbname, 1); + + // this is called if we change the version or the database doesn't exist. + // Use it to create the schema. + dbopen.onupgradeneeded = function(event) { + progress("Upgrading/creating local database..."); + var db = event.target.result; + var metadataStore = db.createObjectStore("metadata", { keyPath: 'filename' }); + var dataStore = db.createObjectStore("data", { keyPath: 'chunkid', autoIncrement: true }); + dataStore.createIndex("data", "filename", { unique: false }); + }; + + dbopen.onerror = function(event) { + failed("Couldn't open local database: " + event.target.error.message); + }; + + var finished_file = function(fname) { + debug("Finished writing '" + fname + "' to the database!"); + state.pending_files--; + if (state.pending_files < 0) { + state.pending_files = 0; + debug("Uhoh, pending_files went negative?!"); + } + if (state.pending_files == 0) { + succeeded(); + } + }; + + var store_file = function(xhr) { + // write to the database... + var databuf = xhr.response; + var transaction = state.db.transaction(["metadata", "data"], "readwrite"); + var objstoremetadata = transaction.objectStore("metadata"); + var objstoredata = transaction.objectStore("data"); + + objstoremetadata.add({ filename: xhr.filename, filesize: xhr.filesize, filetime: xhr.filetime }); + // !!! FIXME: _of course_ this crashes Safari on large files + /* + var chunksize = 1048576; // 1 megabyte each. + var chunks = Math.ceil(xhr.response.byteLength / chunksize); + for (var i = 0; i < chunks; i++) { + var bufoffset = i * chunksize; + objstoredata.add({ + filename: xhr.filename, + offset: bufoffset, + chunk: new Uint8Array(databuf, bufoffset, chunksize); + }); + } + */ + objstoredata.add({ filename: xhr.filename, offset: 0, chunk: databuf }); + + transaction.oncomplete = function(event) { + finished_file(xhr.filename); // all done here! + }; + }; + + var download_new_files = function() { + if (state.reported_result) { return; } + progress("Downloading new files..."); + var downloadme = []; + for (var i in state.remote_manifest) { + var remoteitem = state.remote_manifest[i]; + var remotefname = i; + if (typeof state.local_manifest[remotefname] !== "undefined") { + debug("remote filename '" + remotefname + "' already downloaded."); + } else { + debug("remote filename '" + remotefname + "' needs downloading."); + // the browser will let a handful of these go in parallel, and + // then will queue the rest, firing events as appropriate + // when it gets around to them, so just fire them all off + // here. + + // !!! FIXME: use the Fetch API, plus streaming, as an option. + // !!! FIXME: It can use less memory, since it doesn't need + // !!! FIXME: to keep the whole file in memory. + state.total_to_download += remoteitem.filesize; + state.total_files++; + state.pending_files++; + + var xhr = new XMLHttpRequest(); + state.xhrs[remotefname] = xhr; + xhr.previously_loaded = 0; + xhr.filename = remotefname; + xhr.filesize = state.remote_manifest[i].filesize; + xhr.filetime = state.remote_manifest[i].filetime; + xhr.expected_filesize = remoteitem.filesize; + xhr.responseType = "arraybuffer"; + xhr.addEventListener("error", function(e) { failed("Download error on '" + e.target.filename + "'!"); }); + xhr.addEventListener("timeout", function(e) { failed("Download timeout on '" + e.target.filename + "'!"); }); + xhr.addEventListener("abort", function(e) { failed("Download abort on '" + e.target.filename + "'!"); }); + + xhr.addEventListener('progress', function(e) { + if (state.reported_result) { return; } + var xhr = e.target; + var additional = e.loaded - xhr.previously_loaded; + state.total_downloaded += additional; + xhr.previously_loaded = e.loaded; + debug("Downloaded " + additional + " more bytes for file '" + xhr.filename + "'"); + var percent = state.total_to_download ? Math.floor((state.total_downloaded / state.total_to_download) * 100.0) : 0; + progress("Downloaded " + percent + "% (" + Math.ceil(state.total_downloaded / 1048576) + "/" + Math.ceil(state.total_to_download / 1048576) + " megabytes)"); + }); + + xhr.addEventListener("load", function(e) { + if (state.reported_result) { return; } + var xhr = e.target; + if (xhr.status != 200) { + failed("Server reported failure downloading '" + xhr.filename + "'!"); + } else { + debug("Finished download of '" + xhr.filename + "'!"); + state.total_downloaded -= xhr.previously_loaded; + state.total_downloaded += xhr.expected_filesize; + xhr.previously_loaded = xhr.expected_filesize; + delete state.xhrs[xhr.filename]; + var percent = state.total_to_download ? Math.floor((state.total_downloaded / state.total_to_download) * 100.0) : 0; + progress("Downloaded " + percent + "% (" + Math.ceil(state.total_downloaded / 1048576) + "/" + Math.ceil(state.total_to_download / 1048576) + " megabytes)"); + store_file(xhr); + } + }); + + xhr.open("get", baseurl + remotefname + urlrandomizerarg, true); + xhr.send(); + } + } + + if (state.pending_files == 0) { + succeeded(); // we're already done. :) + } + }; + + var delete_old_files = function() { + if (state.reported_result) { return; } + var deleteme = [] + for (var i in state.local_manifest) { + var localitem = state.local_manifest[i]; + var localfname = localitem.filename; + var removeme = false; + if (typeof state.remote_manifest[localfname] === "undefined") { + removeme = true; + } else { + var remoteitem = state.remote_manifest[localfname]; + if ( (localitem.filesize != remoteitem.filesize) || + (localitem.filetime != remoteitem.filetime) ) { + removeme = true; + } + } + + if (removeme) { + debug("Marking old file '" + localfname + "' for removal."); + deleteme.push(localfname); + delete state.local_manifest[i]; + } + } + + if (deleteme.length == 0) { + debug("No old files to delete."); + download_new_files(); // just move on to the next stage. + } else { + progress("Cleaning up old files..."); + var transaction = state.db.transaction(["data", "metadata"], "readwrite"); + transaction.oncomplete = function(event) { + debug("All old files are deleted."); + download_new_files(); + }; + + var objstoremetadata = transaction.objectStore("metadata"); + var objstoredata = transaction.objectStore("data"); + var dataindex = objstoredata.index("data"); + for (var i of deleteme) { + debug("Deleting metadata for '" + i + "'."); + objstoremetadata.delete(i); + dataindex.openCursor(IDBKeyRange.only(i)).onsuccess = function(event) { + var cursor = event.target.result; + if (cursor) { + debug("Deleting file chunk " + cursor.value.chunkid + " for '" + cursor.value.filename + "' (offset=" + cursor.value.offset + ", size=" + cursor.value.size + ")."); + objstoredata.delete(cursor.value.chunkid); + cursor.continue(); + } + } + } + } + }; + + var manifest_loaded = function() { + if (state.reported_result) { return; } + if (state.local_manifest_loaded && state.remote_manifest_loaded) { + debug("both manifests loaded, moving on to next step."); + delete_old_files(); // on success, will start downloads. + } + }; + + var load_local_manifest = function(db) { + if (state.reported_result) { return; } + debug("Loading local manifest..."); + var transaction = db.transaction("metadata", "readonly"); + var objstore = transaction.objectStore("metadata"); + var cursor = objstore.openCursor(); + + // this gets called once for each item in the object store. + cursor.onsuccess = function(event) { + if (state.reported_result) { return; } + var cursor = event.target.result; + if (cursor) { + debug("Another local manifest item: '" + cursor.value.filename + "'"); + state.local_manifest[cursor.value.filename] = cursor.value; + cursor.continue(); + } else { + debug("All local manifest items iterated."); + state.local_manifest_loaded = true; + manifest_loaded(); // maybe move on to next step. + } + }; + }; + + dbopen.onsuccess = function(event) { + debug("Database is open!"); + var db = event.target.result; + state.db = db; + + // just catch all database errors here, where they will bubble up + // from objectstores and transactions. + db.onerror = function(event) { + failed("Database error: " + event.target.error.message); + }; + + progress("Loading file manifests..."); + + // this is async, so it happens while remote manifest downloads. + load_local_manifest(db); + + debug("Loading remote manifest..."); + var xhr = new XMLHttpRequest(); + xhr.responseType = "text"; + xhr.addEventListener("error", function(e) { failed("Manifest download error!"); }); + xhr.addEventListener("timeout", function(e) { failed("Manifest download timeout!"); }); + xhr.addEventListener("abort", function(e) { failed("Manifest download abort!"); }); + xhr.addEventListener("load", function(e) { + if (e.target.status != 200) { + failed("Server reported failure downloading manifest!"); + } else { + debug("Remote manifest loaded!"); + debug("json: " + e.target.responseText); + state.remote_manifest_loaded = true; + try { + state.remote_manifest = JSON.parse(e.target.responseText); + } catch (e) { + failed("Remote manifest is corrupted."); + } + delete state.remote_manifest[""] + manifest_loaded(); // maybe move on to next step. + } + }); + xhr.open("get", "manifest.json" + urlrandomizerarg, true); + xhr.send(); + }; + + return retval; +} + +var statusElement = document.getElementById('status'); +var progressElement = document.getElementById('progress'); +var canvasElement = document.getElementById('canvas'); + +canvasElement.style.width = '1280px'; +canvasElement.style.height = '720px'; +canvasElement.style.display = 'block'; +canvasElement.style['margin-left'] = 'auto'; +canvasElement.style['margin-right'] = 'auto'; + +statusElement.style.display = 'none'; +progressElement.style.display = 'none'; +document.getElementById('progressdiv').style.display = 'none'; +document.getElementById('output').style.display = 'none'; +document.getElementById('game-input').style.display = "none" + +if (!window.parent.window.gtk) { + window.parent.window.gtk = {}; +} + +window.parent.window.gtk.saveMain = function(text) { + FS.writeFile('app/main.rb', text); + window.gtk.play(); +} + + +var loadDataFiles = function(dbname, baseurl, onsuccess) { + var syncdata = syncDataFiles(dbname, baseurl); + window.gtk.syncdata = syncdata; + + syncdata.onerror = function(why) { + Module.setStatus(why); + } + + syncdata.onprogress = function(why, total_downloaded, total_to_download) { + Module.setStatus(why); + } + + syncdata.onsuccess = function(why) { + //Module.setStatus(why); + console.log(why); + + GGameFilesDatabase = syncdata.db; + window.gtk.filedb = syncdata.db; + + var db = syncdata.db; + var manifest = syncdata.manifest; + syncdata.failed = false; + syncdata.num_requests = 0; + syncdata.total_requests = 0; + + db.onerror = function(event) { + Module.setStatus("Database error: " + event.target.error.message); + syncdata.failed = true; + }; + + var transaction = db.transaction("data", "readonly"); + var objstore = transaction.objectStore("data"); + var dataindex = objstore.index("data"); + + for (var i in manifest) { + // !!! FIXME: this assumes the whole file is in one chunk, but + // !!! FIXME: that was not my original plan. + syncdata.total_requests++; + syncdata.num_requests++; + //console.log("'" + i + "' is headed for MEMFS..."); + var req = dataindex.get(i); + req.filesize = manifest[i].filesize; + req.onsuccess = function(event) { + var path = "/" + event.target.result.filename; + //console.log("'" + path + "' is loaded in from IndexedDB..."); + var ui8arr = new Uint8Array(event.target.result.chunk); + var len = event.target.filesize; + var arr = new Array(len); + for (var i = 0; i < len; ++i) { + arr[i] = ui8arr[i]; + } + + var basedir = PATH.dirname(path); + FS.mkdirTree(basedir); + + var okay = false; + try { + okay = FS.createDataFile(basedir, PATH.basename(path), arr, true, true, true); + } catch (err) { // throws if file exists, etc. Nuke and try one more time. + FS.unlink(path); + try { + okay = FS.createDataFile(basedir, PATH.basename(path), arr, true, true, true); + } catch (err) { + okay = false; // oh well. + } + } + + if (!okay) { + Module.setStatus("ERROR: Failed to put '" + path + "' in MEMFS."); + } else { + var completed = syncdata.total_requests - syncdata.num_requests; + var percent = Math.floor((completed / syncdata.total_requests) * 100.0); + Module.setStatus("Preparing game data: " + percent + "%"); + //console.log("'" + path + "' has made it to MEMFS! (" + syncdata.num_requests + " to go)"); + syncdata.num_requests--; + if (syncdata.num_requests <= 0) { + if (!syncdata.failed) { + onsuccess(); + } + } + } + }; + } + } +} + +// https://stackoverflow.com/a/7372816 +var base64Encode = function(ui8array) { + var CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + var out = "", i = 0, len = ui8array.length, c1, c2, c3; + while (i < len) { + c1 = ui8array[i++] & 0xff; + if (i == len) { + out += CHARS.charAt(c1 >> 2); + out += CHARS.charAt((c1 & 0x3) << 4); + out += "=="; + break; + } + c2 = ui8array[i++]; + if (i == len) { + out += CHARS.charAt(c1 >> 2); + out += CHARS.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4)); + out += CHARS.charAt((c2 & 0xF) << 2); + out += "="; + break; + } + c3 = ui8array[i++]; + out += CHARS.charAt(c1 >> 2); + out += CHARS.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4)); + out += CHARS.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6)); + out += CHARS.charAt(c3 & 0x3F); + } + return out; +} + +var Module = { + noInitialRun: true, + preInit: [], + clickedToPlay: false, + clickToPlayListener: function() { + if (Module.clickedToPlay) return; + Module.clickedToPlay = true; + var div = document.getElementById('clicktoplaydiv'); + if (div) { + div.removeEventListener('click', Module.clickToPlayListener); + document.body.removeChild(div); + } + if (window.parent.window.gtk.starting) { + window.parent.window.gtk.starting(); + } + Module["callMain"](); // go go go! + }, + startClickToPlay: function() { + var base64 = base64Encode(FS.readFile(GDragonRubyIcon, {})); + var div = document.createElement('div'); + var leftPx = ((window.innerWidth - 640) / 2); + var leftPerc = Math.floor((leftPx / window.innerWidth) * 100); + div.id = 'clicktoplaydiv'; + div.style.backgroundColor = 'rgb(40, 44, 52)'; + div.style.left = leftPerc.toString() + "%"; + div.style.top = '10%'; + div.style.display = 'block'; + div.style.position = 'absolute'; + div.style.width = "640px" + div.style.height = "360px" + + + + var img = new Image(); + img.onload = function() { // once we know its size, scale it, keeping aspect ratio. + var zoomRatio = 100.0 / this.width; + img.style.width = (zoomRatio * this.width.toString()) + "px"; + img.style.height = (zoomRatio * this.height.toString()) + "px"; + img.style.display = "block"; + img.style['margin-left'] = 'auto'; + img.style['margin-right'] = 'auto'; + } + + img.style.display = 'none'; + img.src = 'data:image/png;base64,' + base64; + div.appendChild(img); + + + var p; + + p = document.createElement('h1'); + p.textContent = GDragonRubyGameTitle + " " + GDragonRubyGameVersion + " by " + GDragonRubyDevTitle; + p.style.textAlign = 'center'; + p.style.color = '#FFFFFF'; + p.style.width = '100%'; + p.style['font-family'] = "monospace"; + div.appendChild(p); + + p = document.createElement('p'); + p.innerHTML = 'Click here to begin.'; + p.style['font-family'] = "monospace"; + p.style['font-size'] = "20px"; + p.style.textAlign = 'center'; + p.style.backgroundColor = 'rgb(40, 44, 52)'; + p.style.color = '#FFFFFF'; + p.style.width = '100%'; + div.appendChild(p); + + document.body.appendChild(div); + div.addEventListener('click', Module.clickToPlayListener); + window.gtk.play = Module.clickToPlayListener; + }, + preRun: function() { + // set up a persistent store for save games, etc. + FS.mkdir('/persistent'); + FS.mount(IDBFS, {}, '/persistent'); + FS.syncfs(true, function(err) { + if (err) { + console.log("WARNING: Failed to populate persistent store. Save games likely lost?"); + } else { + console.log("Read in from persistent store."); + } + + loadDataFiles(GDragonRubyGameId, 'gamedata/', function() { + console.log("Game data is sync'd to MEMFS. Starting click-to-play()..."); + //Module.setStatus("Ready!"); + //setTimeout(function() { Module.setStatus(""); statusElement.style.display='none'; }, 1000); + Module.setStatus(""); + statusElement.style.display='none'; + Module.startClickToPlay(); + }); + }); + }, + postRun: [], + print: (function() { + var element = document.getElementById('output'); + if (element) element.value = ''; // clear browser cache + return function(text) { + if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' '); + // These replacements are necessary if you render to raw HTML + //text = text.replace(/&/g, "&"); + //text = text.replace(/</g, "<"); + //text = text.replace(/>/g, ">"); + //text = text.replace('\n', '<br>', 'g'); + console.log(text); + if (element) { + element.value += text + "\n"; + element.scrollTop = element.scrollHeight; // focus on bottom + } + }; + })(), + printErr: function(text) { + if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' '); + if (0) { // XXX disabled for safety typeof dump == 'function') { + dump(text + '\n'); // fast, straight to the real console + } else { + console.error(text); + } + }, + 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); + canvas.addEventListener("click", function() { + document.getElementById('toplevel').click(); + document.getElementById('toplevel').focus(); + document.getElementById('game-input').style.display = "inline" + document.getElementById('game-input').focus(); + document.getElementById('game-input').blur(); + document.getElementById('game-input').style.display = "none" + canvas.focus(); + }); + + + return canvas; + })(), + setStatus: function(text) { + if (!Module.setStatus.last) Module.setStatus.last = { time: Date.now(), text: '' }; + if (text === Module.setStatus.text) return; + var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/); + var now = Date.now(); + if (m && now - Date.now() < 30) return; // if this is a progress update, skip it if too soon + 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; + } + statusElement.innerHTML = text; + }, + 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.setStatus('Downloading...'); +window.onerror = function(event) { + // TODO: do not warn on ok events like simulating an infinite loop or exitStatus + Module.setStatus('Exception thrown, see JavaScript console'); + Module.setStatus = function(text) { + if (text) Module.printErr('[post-exception status] ' + text); + }; +}; + +var hasWebAssembly = false; +if (typeof WebAssembly==="object" && typeof WebAssembly.Memory==="function") { + hasWebAssembly = true; +} +//console.log("Do we have WebAssembly? " + ((hasWebAssembly) ? "YES" : "NO")); + +var buildtype = hasWebAssembly ? "wasm" : "asmjs"; +var module = "dragonruby-" + buildtype + ".js"; +window.gtk = {}; +window.gtk.module = Module; + +//console.log("Our main module is: " + module); + +var script = document.createElement('script'); +script.src = module; +if (hasWebAssembly) { + script.async = true; +} else { + script.async = false; // !!! FIXME: can this be async? + (function() { + var memoryInitializer = module + '.mem'; + if (typeof Module['locateFile'] === 'function') { + memoryInitializer = Module['locateFile'](memoryInitializer); + } else if (Module['memoryInitializerPrefixURL']) { + memoryInitializer = Module['memoryInitializerPrefixURL'] + memoryInitializer; + } + var meminitXHR = Module['memoryInitializerRequest'] = new XMLHttpRequest(); + meminitXHR.open('GET', memoryInitializer, true); + meminitXHR.responseType = 'arraybuffer'; + meminitXHR.send(null); + })(); +} +document.body.appendChild(script); diff --git a/deploy_template/.dragonruby/stubs/html5/stub/game.css b/deploy_template/.dragonruby/stubs/html5/stub/game.css new file mode 100644 index 0000000..3fb16d4 --- /dev/null +++ b/deploy_template/.dragonruby/stubs/html5/stub/game.css @@ -0,0 +1,8 @@ +body { + margin: 0; + padding: 0; + border: 0; +} +canvas { + background-color: rgb(40, 44, 52); +} diff --git a/deploy_template/.dragonruby/stubs/html5/stub/index.html b/deploy_template/.dragonruby/stubs/html5/stub/index.html new file mode 100644 index 0000000..b2bcf9f --- /dev/null +++ b/deploy_template/.dragonruby/stubs/html5/stub/index.html @@ -0,0 +1,93 @@ +<!doctype html> +<html lang="en-us"> + <head> + <meta charset="utf-8"> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> + <link rel="icon" href="favicon.png" type="image/png" /> + <title>DragonRuby</title> + <style> + body { + font-family: arial; + margin: 0; + padding: none; + background: #000000; + } + + .emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; } + div.emscripten { text-align: center; } + div.emscripten_border { border: 1px solid black; } + /* the canvas *must not* have any border or padding, or mouse coords will be wrong */ + canvas.emscripten { border: 0px none; background-color: black; } + + #emscripten_logo { + display: inline-block; + margin: 0; + } + + @-webkit-keyframes rotation { + from {-webkit-transform: rotate(0deg);} + to {-webkit-transform: rotate(360deg);} + } + @-moz-keyframes rotation { + from {-moz-transform: rotate(0deg);} + to {-moz-transform: rotate(360deg);} + } + @-o-keyframes rotation { + from {-o-transform: rotate(0deg);} + to {-o-transform: rotate(360deg);} + } + @keyframes rotation { + from {transform: rotate(0deg);} + to {transform: rotate(360deg);} + } + + #status { + display: inline-block; + font-weight: bold; + color: rgb(120, 120, 120); + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } + + #progress { + height: 20px; + width: 30px; + } + + #output { + width: 100%; + height: 200px; + margin: 0 auto; + margin-top: 10px; + border-left: 0px; + border-right: 0px; + padding-left: 0px; + padding-right: 0px; + display: none; + background-color: black; + color: white; + font-family: 'Lucida Console', Monaco, monospace; + outline: none; + } + </style> + <link rel="stylesheet" href="game.css" /> + <title>DragonRuby Game Toolkit Tutorial</title> + </head> + <body id='toplevel'> + <div class="emscripten_border" id='borderdiv'> + <input id="game-input" autocomplete="off" /> + <canvas class="game emscripten" id="canvas" oncontextmenu="event.preventDefault()"></canvas> + </div> + <br/> + <textarea style="display: none;" id="output" rows="8"></textarea> + + <div class="emscripten" id="status"></div> + <div class="emscripten" id='progressdiv'> + <progress value="0" max="100" id="progress"></progress> + </div> + + <script type='text/javascript' src='dragonruby-html5-loader.js'></script> + </body> +</html> diff --git a/deploy_template/CHANGELOG.txt b/deploy_template/CHANGELOG.txt index 9bde121..cf22fc9 100644 --- a/deploy_template/CHANGELOG.txt +++ b/deploy_template/CHANGELOG.txt @@ -1,3 +1,261 @@ += 1.14 = + + * [Support] Better HTML5 template. Additional JS events added to + handle loss of keyboard input within an iframe. + * [Bugfix] `args.outputs.screenshots` regression fixed. + * [Docs] Added documentation for a few more Numeric methods. + * [Samples] Brand new advanced sample app: 99_sample_sprite_animation_creator. + The sample app uses `args.outputs.screenshots` and `render_targets` heavily along with + in memory queues as a means to consolidate events coming from + different parts of the app. + + += 1.13 = + + * [API] Sprite angle now accepts fractional degrees. + * [Samples] Better font added to LOWREZJAM 2020 template. + * [API] Added `args.outputs[RENDER_TARGET_NAME]` as an alias to + `args.render_target(RENDER_TARGET_NAME)`. Either of the following will work: + + ```ruby + def tick args + if args.state.tick_count == 1 + args.render_target(:camera).width = 100 + args.render_target(:camera).height = 100 + args.render_target(:camera).solids << [0, 0, 50, 50, 255, 0, 0] + end + + if args.state.tick_count > 0 + args.outputs.sprites << { x: 0, + y: 0, + w: 500, + h: 500, + source_x: 0, + source_y: 0, + source_w: 50, + source_h: 50, + path: :camera } + end + end + + $gtk.reset + ``` + + Is the same as: + + ```ruby + def tick args + if args.state.tick_count == 1 + args.outputs[:camera].width = 100 + args.outputs[:camera].height = 100 + args.outputs[:camera].solids << [0, 0, 50, 50, 255, 0, 0] + end + + if args.state.tick_count > 0 + args.outputs.sprites << { x: 0, + y: 0, + w: 500, + h: 500, + source_x: 0, + source_y: 0, + source_w: 50, + source_h: 50, + path: :camera } + end + end + + $gtk.reset + ``` + += 1.12 = + + * [Samples] LOWREZ Jam sample app reworked in preparation for LOWREZ + Jam 2020 (starts on August 1st so hurry and join). + * [Docs] Docs added for GTK::Mouse, you can access them via the + Console by typing `GTK::Mouse.docs` or `$gtk.args.inputs.mouse.docs`. + * [MacOS] Updated minimum OS support to include MacOS 10.9+. + += 1.11 = + + * [Bugfix] Fixed error in docs_search "TERM". + += 1.10 = + + * [Support] Documentation infrastructure added (take a look at docs/docs.html). Bring up the DragonRuby Console and: + + To search docs you can type `docs_search "SEARCH TERM"` + + If you want to get fancy you can provide a `lambda` to filter documentation: + + docs_search { |entry| (entry.include? "Array") && (!entry.include? "Enumerable") } + + * [Bugfix] Fixed sprite rendering issues with source_(x|y|w|h) properties on sprites. + * [Support] Removed double buffering of game if framerate drops below 60 fps. + * [Support] Console now supports mouse wheel scrolling. + * [Support] One time notifications have less noise/easier to read. + * [Bugfix] Rogue ~app/main.rb~ directory will no longer be created if you run a sample app. + += 1.9 = + + * [Bugfix] HTTP on windows should now work, for real this time. + * [Bugfix] Non-720p render targets now use correct coordinate system. + += 1.8 = + + * [Experimental] Added the ability to control the logical game size. You can use cli + arguments to set it. Example ultra-wide support would be: + `./dragonruby --window_width 3840 --window_height 1080` + * [Bugfix] HTTP on windows should now work. + * [Bugfix] `even?` and `odd?` return the correct result for Fixnum. + * [Bugfix] args.intputs.mouse_wheel now reports the delta change in x and y correctly. + * [Bugfix] Improved analog joystick accuracy when converting to percentages. + * [Support] Incorporated pull request from https://github.com/kfischer-okarin that adds + autocompletion to the Console. This is the PR: + - https://github.com/DragonRuby/dragonruby-game-toolkit-contrib/commit/da0fdcfbd2bd9739fe056eb646920df79a32954c + - https://github.com/DragonRuby/dragonruby-game-toolkit-contrib/commit/99305ca79118fa0704c8681f4019738b8c7a500d + += 1.7 = + + * [BREAKING] args.inputs.mouse.wheel.point is gone. Use args.inputs.mouse.x + and .y if you need cursor position. + * [BREAKING] args.inputs.mouse.wheel.x and .y now represent the amount the + mousewheel/trackpad has moved since the last tick and not the mouse cursor + position. Use args.inputs.mouse.x and .y if you need cursor position. + += 1.6 = + + * [API] Sprite now supports source_(x|y|w|h). These properties are consistent with the origin + being in the bottom left. The existing properties tile_(x|y|w|h) assumes that origin 0, 0 is in the top left. + The code below will render the same sprite (in their respective coordinate systems): + + # using tile_(x|y|w|h) properties + args.outputs.sprites << { x: 0, + y: 0, + w: 1280, + h: 100, + path: :block, + tile_x: 0, + tile_y: 720 - 100, + tile_w: 1280, + tile_h: 100 } + + is equivalent to: + + # using source_(x|y|w|h) properties + args.outputs.sprites << { x: 0, + y: 0, + w: 1280, + h: 100, + path: :block, + source_x: 0, + source_y: 0, + source_w: 1280, + source_h: 100 } + + Note: if you provide both tile_(x|y|w|h) and source_(x|y|w|h). The values of tile_ will "win" so as not to + break existing code out there. + * [Bugfix] Updated require to remove duplicate requires of the same file (or files that have recently been required). + * [Bugfix] Strict entities of different types/names serialize and deserialize correctly. + * [Samples] Updated render targets sample app to show two render targets with transparencies. + * [API] No really, render targets now have a transparent background and respect opacity. + += 1.5 = + + * [API] Added $gtk.show_cursor and $gtk.hide_cursor to show and hide the mouse cursor. The + function only needs to be called once. EG: args.gtk.hide_cursor if args.state.tick_count == 0. + * [Samples] Jam Craft 2020 sample app updated to have more comments and demonstrate a custom + mouse cursor. + += 1.4 = + + * [Bugfix] Adding $gtk.reset at the bottom of main.rb will no longer cause an infinite loop. + * [Samples] Sample app added for Jam Craft 2020. + += 1.3 = + + * [Bugfix] Adding $gtk.reset at the bottom of main.rb will no longer cause an infinite loop. + * [Samples] Better instructions added to various sample apps. + += 1.2 = + + * [Bugfix] Top-level require statements within main.rb will load before + invoking the rest of the code in main.rb. + * [Samples] Better keyboard input sample app. + * [Samples] New sample app that shows how to use Numeric#ease_spline. + * [Bugfix] Fixed "FFI::Draw cannot be serialized" error message. + += 1.1 = + + * [Bugfix] Fixed exception associated with providing serialization related help. + * [Bugfix] Fixed comments on how to run tests from CLI. + * [Support] More helpful global variables added. Here's a list: + - $gtk + - $console + - $args + - $state + - $tests + - $record + - $replay + * [API] inputs.keyboard.key_(down|held|up).any? and inputs.keyboard.key_(down|held|up).all? + added. + * [Support] Recording gameplay and replaying streamlined a bit more. GIVE THE + REPLAY FEATURE A SHOT! IT'S AWESOME!! Bring up the console and run: $record.start SEED_NUMBER. + * [Support] Bringing up the console will stop a replay if one is running. + += 1.0 = + + * [News] DragonRuby Game Toolkit turns 1.0. Happy birthday! + * [Bugfix] args.state.new_entity_strict serializes and deserializes correctly now. + * [BREAKING] Entity#hash has been renamed to Entity#as_hash so as not to redefine + Object#hash. This is a "private" method so you probably don't have to worry about + anything breaking on your end. + * [BREAKING] gtk.save_state and gtk.load_state have been replaced with gtk.serialize_state + and gtk.deserialize_state (helpful error messages have been added). + * [Support] Console can now render sprites (this is still in its early stages). Try + $gtk.console.addsprite(w: 50, h: 50, path: "some_path.png"). + * [API] Render targets now have a transparent background and respect opacity. + * [API] Render targets can be cached/programatically created once and reused. + * [Samples] A new render target sample app has been created to show how to cache them. + * [Samples] Easing sample app reworked/simplified. + * [Support] GTK will keep a backup of your source file changes under the tmp directory. + One day this feature will save your ass. + += 20200301 = + + * [Samples] Added sample app that shows how you can draw a cubic bezier curves. + * [Support] Keyup event prints key and raw_key to the console. + * [Support] Circumflex now opens the console. + += 20200227 = + + * [Bugfix] Game will now auto restart in the event of a syntax error. + * [Samples] Sample app added to show how to use a sprite sheet for sprite animations: + 09_sprite_animation_using_tile_sheet. + * [Samples] Sample app added to show how to use a tile sheet for a roguelike: + 20_roguelike_starting_point_two. + * [Samples] Example code added to show how sort an array with a custom sort block: + 00_intermediate_ruby_primer/07_powerful_arrays.txt + * [OSS] The following files have been open sourced at https://github.com/DragonRuby/dragonruby-game-toolkit-contrib: + - modified: dragon/args.rb + - new file: dragon/assert.rb + - new file: dragon/attr_gtk.rb + - modified: dragon/console.rb + - new file: dragon/docs.rb + - new file: dragon/geometry.rb + - new file: dragon/help.rb + - modified: dragon/index.rb + - modified: dragon/inputs.rb + - new file: dragon/log.rb + - new file: dragon/numeric.rb + - new file: dragon/string.rb + - new file: dragon/tests.rb + - new file: dragon/trace.rb + * [Support] Added isometric placeholder sprites. + * [Support] Added $gtk.reset_sprite 'path' to refresh a sprite from + the file system (while the game is running). Future releases will + automatically auto load sprites but you can use this to reload them + on demand. + = 20200225 = * [Bugfix] Fixed macOS compatibility back to Mac OS X 10.9 or so. @@ -22,9 +280,8 @@ = 20200217 = * [Bugfix] `dragonruby-publish` would only build html5. It now builds - all of the platforms again. Ryan "The Juggernaut" Gordon has given Amir - a warning and has told him to be more careful releasing. Amir cackled - in defiance. + all of the platforms again. Ryan has given Amir a warning and has told him + to be more careful releasing. Amir cackled in defiance. = 20200213 = @@ -87,7 +344,7 @@ http. * [Support] [Samples] Added a sample app that shows how create collision detection associated with constraint points against - a ramp. This sample app also demostrates the use of `gtk.slowmo!` + a ramp. This sample app also demonstrates the use of `gtk.slowmo!` which you can use the slow down the tick execution of your game to see things frame by frame (still experimental). * [Support] Added sprites to default template folder. The sprites @@ -165,7 +422,7 @@ * [Samples] Added sample app that shows how trace can be used within a class. * [Support] Added $gtk.log_level. Valid values are :on, - :off. When the value is set to :on, GTK log mesages will show up + :off. When the value is set to :on, GTK log messages will show up in the DragonRuby Console *AND* be written to logs/log.txt. When the value is set to :off, GTK log messages will *NOT* show up in the console, but will *STILL* be written to logs/log.txt @@ -306,7 +563,7 @@ * [Samples] New sample app called 02_collisions_02_moving_objects has been added. * [Support] Previous console messages are subdued if unhandled exception is resolved. This removes visual confusion if another exception is thrown (easier to determine what the current exception is). - * [Support] Added api documentation about thruthy_keys for keyboards and controllers. + * [Support] Added api documentation about truthy_keys for keyboards and controllers. * [API] [Experimental] Hashes now quack like render primitives. For example some_hash[:x] is the same as some_hash.x and can be interchangeable with some_array.x (this is a work in progress so there may be some geometric/collision apis that aren't covered). @@ -316,7 +573,7 @@ loops, and arrays. Special thanks to @raulrita (https://www.twitch.tv/raulrita) for live streaming and bringing light to these enhancements. * [Support] `puts` statements that begin with "====" are rendered as teal in the Console. This - provides a visual seperation that will help with print line debugging. + provides a visual separation that will help with print line debugging. * [OSS] The DragonRuby Game Toolkit Console has been open sourced under an MIT license. You can find it here: https://github.com/DragonRuby/dragonruby-game-toolkit-contrib @@ -325,9 +582,9 @@ * [Support] The mygame directory now contains a documentation folder that provides high level APIs for the engine (it's a work in progress, but a good starting point). * [Samples] A sample app called "hash primitives" has been added that shows how you can use a Hash - to render a primitive. This will make specifying sprite's advanced properites much easier. + to render a primitive. This will make specifying sprite's advanced properties much easier. * [Samples] The sprite limits sample app shows how you can ducktype a class and render it as a sprite. - Doing this is a tiny bit more work, but there is a huge perfomance benefit. + Doing this is a tiny bit more work, but there is a huge performance benefit. * [Bugfix] Internal limits total textures/render targets/fonts is removed. * [BREAKING] args.dragon has been deprecated, you must now use the new property args.gtk. * [BREAKING] args.game has been deprecated, you must now use the new property args.state. @@ -390,7 +647,7 @@ = release-20190731 = - * [Bugfix] Fixed bug in dragon ruby console returning evalued value. + * [Bugfix] Fixed bug in DragonRuby console returning evaled value. * [Support] Updated collisions sample app with comments. * [Support] Added comments for sprite animation sample app. * [Support] dragonruby-publish will look for your game in the @@ -472,9 +729,9 @@ * [API] A new entity type has been introduced and is accessible via `args.state.new_entity_strict` the usage of StrictEntity over OpenEntity (`args.state.new_entity`) yield significantly faster property access. The downside is that `args.state.new_entity_strict` requires - you to define all properties upfront within the implicit block. You will recieve + you to define all properties upfront within the implicit block. You will receive an exception if you attempt to access a property that hasn't be - pre-defined. For usage info and preformance differences, take a look at the Sprite Limit + pre-defined. For usage info and performance differences, take a look at the Sprite Limit sample app. * [Support] Exception messages have been significantly improved. Hashes and Type .to_s information is well formatted. "stack too deep" exceptions resolved. @@ -484,7 +741,7 @@ * [Support] Framerate warnings wait 10 seconds before calculating the moving average. If your framerates begin to drop, consider using `args.state.new_entity_static` for game structures that have been fleshed out. - * [Performance] Rendering of primitives is can support over twice as many sprites at 60 fps (see Sprit Limits + * [Performance] Rendering of primitives is can support over twice as many sprites at 60 fps (see Sprite Limits sample app for demonstration). * [Support] Headless testing has been added. Take a look at the Basic Gorillas sample app to see how headless testing can help you dial into frame-by-frame problems within your game. @@ -507,7 +764,7 @@ args.outputs.sprites << create_sprite(x: 0, y: 0, w: 100, h: 100, vflip: true) ``` - We're still chewing on the API above before it get's integrated + We're still chewing on the API above before it gets integrated into GTK proper. * [API] "Superscript Two" can be used to bring up the DragonRuby Console. People with international keyboards (which don't have a ~ @@ -597,11 +854,11 @@ course of Ruby the programming language. * [Sample] The composition of primitives in DragonRuby GTK are incredibly flexible. A sample app called "Fluid Primitives" has - been beed added to show this flexibility. + been added to show this flexibility. * [MacOS] [Linux] [Windows] If your screen resolution is below 720p, the game will start at a smaller (but still aspect-correct) resolution. * [Sample] Sample added showing `intersects_rect?` collision - tollerances as a topdown level (similar to what's + tolerances as a topdown level (similar to what's in Zelda for the NES). = release-20190516 = @@ -675,7 +932,7 @@ `notepad.exe`. * [Packaging] Default icon added to `mygame`. * [Samples] Reworked `doomwipe` (render targets tech demo) sample app so that - it's less eratic before the effect is revealed. + it's less erratic before the effect is revealed. * [Windows] Fixed bug where rendering would stop on Windows if the screen was resized. * [Size] Deleted files that don't need to be packaged with DragonRuby GTK. diff --git a/deploy_template/mygame/app/main.rb b/deploy_template/mygame/app/main.rb index a7d9ad6..5683cc2 100644 --- a/deploy_template/mygame/app/main.rb +++ b/deploy_template/mygame/app/main.rb @@ -1,6 +1,5 @@ def tick args args.outputs.labels << [ 580, 500, 'Hello World!' ] - args.outputs.labels << [ 475, 150, '(Consider reading README.txt now.)' ] + args.outputs.labels << [ 640, 460, 'Go to docs/docs.html and read it!', 5, 1 ] args.outputs.sprites << [ 576, 310, 128, 101, 'dragonruby.png' ] end - diff --git a/deploy_template/mygame/app/tests.rb b/deploy_template/mygame/app/tests.rb index a60c8be..db71ff6 100644 --- a/deploy_template/mygame/app/tests.rb +++ b/deploy_template/mygame/app/tests.rb @@ -4,7 +4,7 @@ # Here is an example test and game -# To run the test: ./dragonruby mygame --eval tests.rb --no-tick +# To run the test: ./dragonruby mygame --eval app/tests.rb --no-tick class MySuperHappyFunGame attr_gtk diff --git a/deploy_template/mygame/sprites/border-black.png b/deploy_template/mygame/sprites/border-black.png Binary files differnew file mode 100644 index 0000000..c9d0bad --- /dev/null +++ b/deploy_template/mygame/sprites/border-black.png diff --git a/deploy_template/mygame/sprites/dragon-0.png b/deploy_template/mygame/sprites/dragon-0.png Binary files differnew file mode 100644 index 0000000..fb179af --- /dev/null +++ b/deploy_template/mygame/sprites/dragon-0.png diff --git a/deploy_template/mygame/sprites/dragon-1.png b/deploy_template/mygame/sprites/dragon-1.png Binary files differnew file mode 100644 index 0000000..8cfe531 --- /dev/null +++ b/deploy_template/mygame/sprites/dragon-1.png diff --git a/deploy_template/mygame/sprites/dragon-2.png b/deploy_template/mygame/sprites/dragon-2.png Binary files differnew file mode 100644 index 0000000..cb462e1 --- /dev/null +++ b/deploy_template/mygame/sprites/dragon-2.png diff --git a/deploy_template/mygame/sprites/dragon-3.png b/deploy_template/mygame/sprites/dragon-3.png Binary files differnew file mode 100644 index 0000000..04c4977 --- /dev/null +++ b/deploy_template/mygame/sprites/dragon-3.png diff --git a/deploy_template/mygame/sprites/dragon-4.png b/deploy_template/mygame/sprites/dragon-4.png Binary files differnew file mode 100644 index 0000000..b29fa3d --- /dev/null +++ b/deploy_template/mygame/sprites/dragon-4.png diff --git a/deploy_template/mygame/sprites/dragon-5.png b/deploy_template/mygame/sprites/dragon-5.png Binary files differnew file mode 100644 index 0000000..99f4e74 --- /dev/null +++ b/deploy_template/mygame/sprites/dragon-5.png diff --git a/deploy_template/mygame/sprites/explosion-0.png b/deploy_template/mygame/sprites/explosion-0.png Binary files differnew file mode 100644 index 0000000..f48636f --- /dev/null +++ b/deploy_template/mygame/sprites/explosion-0.png diff --git a/deploy_template/mygame/sprites/explosion-1.png b/deploy_template/mygame/sprites/explosion-1.png Binary files differnew file mode 100644 index 0000000..b4018d9 --- /dev/null +++ b/deploy_template/mygame/sprites/explosion-1.png diff --git a/deploy_template/mygame/sprites/explosion-2.png b/deploy_template/mygame/sprites/explosion-2.png Binary files differnew file mode 100644 index 0000000..3abaedd --- /dev/null +++ b/deploy_template/mygame/sprites/explosion-2.png diff --git a/deploy_template/mygame/sprites/explosion-3.png b/deploy_template/mygame/sprites/explosion-3.png Binary files differnew file mode 100644 index 0000000..fe94a5a --- /dev/null +++ b/deploy_template/mygame/sprites/explosion-3.png diff --git a/deploy_template/mygame/sprites/explosion-4.png b/deploy_template/mygame/sprites/explosion-4.png Binary files differnew file mode 100644 index 0000000..ed04237 --- /dev/null +++ b/deploy_template/mygame/sprites/explosion-4.png diff --git a/deploy_template/mygame/sprites/explosion-5.png b/deploy_template/mygame/sprites/explosion-5.png Binary files differnew file mode 100644 index 0000000..2cd8f06 --- /dev/null +++ b/deploy_template/mygame/sprites/explosion-5.png diff --git a/deploy_template/mygame/sprites/explosion-6.png b/deploy_template/mygame/sprites/explosion-6.png Binary files differnew file mode 100644 index 0000000..e55909c --- /dev/null +++ b/deploy_template/mygame/sprites/explosion-6.png diff --git a/deploy_template/mygame/sprites/explosion-sheet.png b/deploy_template/mygame/sprites/explosion-sheet.png Binary files differnew file mode 100644 index 0000000..8559a5c --- /dev/null +++ b/deploy_template/mygame/sprites/explosion-sheet.png diff --git a/deploy_template/mygame/sprites/star.png b/deploy_template/mygame/sprites/star.png Binary files differnew file mode 100644 index 0000000..e0ee0f9 --- /dev/null +++ b/deploy_template/mygame/sprites/star.png |
