diff --git a/engine/web/ftejslib.js b/engine/web/ftejslib.js
index c40d5e77c..90f51dc8e 100644
--- a/engine/web/ftejslib.js
+++ b/engine/web/ftejslib.js
@@ -347,7 +347,7 @@ mergeInto(LibraryManager.library,
{
if (event.type == 'touchstart')
{{{makeDynCall('viii','FTEC.evcb.button')}}}(t.identifier+1, 1, -1);
- else if (event.type != 'touchmove')
+ else if (event.type != 'touchmove') //cancel/end/leave...
{{{makeDynCall('viii','FTEC.evcb.button')}}}(t.identifier+1, 0, -1);
}
}
@@ -459,7 +459,7 @@ mergeInto(LibraryManager.library,
const b = gp.buttons[j];
let p;
if (typeof(b) == "object")
- p = b.pressed; //.value is a fractional thing. oh well.
+ p = b.pressed || (b.value > 0.5); //.value is a fractional thing. oh well.
else
p = b > 0.5; //old chrome bug
@@ -596,7 +596,7 @@ mergeInto(LibraryManager.library,
'focus', 'blur']; //try to fix alt-tab
events.forEach(function(event)
{
- Module['canvas'].addEventListener(event, FTEC.handleevent, true);
+ Module['canvas'].addEventListener(event, FTEC.handleevent, {capture:true, passive:false});
});
var docevents = ['keypress', 'keydown', 'keyup',
@@ -894,6 +894,8 @@ mergeInto(LibraryManager.library,
}
else if (Module["close"])
Module["close"]();
+ else if (Module["quiturl"])
+ document.location.replace(Module["quiturl"]);
else
{
try
diff --git a/engine/web/fteshell.html b/engine/web/fteshell.html
index 0c5e77558..ac2c5bba6 100644
--- a/engine/web/fteshell.html
+++ b/engine/web/fteshell.html
@@ -17,7 +17,7 @@
Please allow/unblock our javascript to play.
@@ -46,7 +46,20 @@ var Module = {
//string values are deemed to be URLs, so use the str2ab("") helper if you want to embed raw file data instead.
// "default.fmf": "default.fmf",
// "id1/pak0.pak": "pak0.pak",
- },
+// "id1/default.cfg": "webdefaults.cfg", //autoexec.cfg is evil.
+/* "id1/touch.cfg": str2ab("showpic_removeall\n"
+ "showpic touch_moveforward.tga fwd -128 -112 bm 32 32 +forward 5\n"
+ "showpic touch_moveback.tga back -128 -80 bm 32 32 +back 5\n"
+ "showpic touch_moveleft.tga left -160 -88 bm 32 32 +moveleft 5\n"
+ "showpic touch_moveright.tga rght -96 -88 bm 32 32 +moveright 5\n"
+ "showpic touch_attack.tga fire -160 -160 bm 32 32 +attack 5\n"
+ "showpic touch_jump.tga jump 128 -80 bm 32 32 +jump 5\n"
+ "showpic touch_weapons.tga weap 80 -80 bm 32 32 +weaponwheel 5\n"
+ "showpic touch_menu.tga menu -32 0 tr 32 32 togglemenu 10\n"),
+*/ },
+// quiturl: "/", //url to jump to when 'quitting' (otherwise uses history.back).
+// arguments:["+alias","f_startup","connect","wss://theservertojoin", "-manifest","default.fmf"], //beware the scheme registration stuff (pwa+js methods).
+// manifest: "index.html.fmf", // '-manifest' arg if args are not explicit. also inhibits the #foo.fmf thing.
print: function(msg)
{ //stdout...
console.log(msg);
@@ -55,7 +68,6 @@ var Module = {
{ //stderr...
console.log(text);
},
-// arguments:["wss://theservertojoin", "-manifest", "default.fmf"], //beware the scheme registration stuff (pwa+js methods).
canvas: document.getElementById('canvas'), //for webgl to attach to
setStatus: function(text)
{ //gets spammed some prints during startup. blame emscripten.
@@ -130,6 +142,88 @@ function fixupfilepath(fname, path)
return "id1/maps/" + fname; //bsps get their own extra subdir
return "id1/" + fname; //probably a pak. maybe a cfg, no idea really.
}
+function remfile(fname)
+{
+ delete Module.files[fname];
+ showfiles(); //repaint
+}
+function renamefile(fname)
+{
+ const nname = prompt("Please enter new name", fname);
+ if (nname != null)
+ {
+ Module.files[nname] = Module.files[fname];
+ delete Module.files[fname];
+ showfiles();
+ }
+}
+function remcfile(fname)
+{
+ Module['cache'].delete("/_/"+fname);
+ Module['cache'].keys().then((keys)=>{Module['cachekeys'] = keys; showfiles();}); //repaint
+}
+function remlfile(fname)
+{
+ window.localStorage.removeItem(fname);
+ showfiles(); //repaint
+}
+function savelfile(fname)
+{
+ window.showSaveFilePicker({id: "openfile", startIn: "documents", suggestedName: fname})
+ .then(async (h)=>{
+ let data = await window.localStorage.getitem(fname);
+ let f = await h.createWriteable();
+ await f.write(data);
+ await f.close();
+ });
+}
+function adduserfile()
+{
+ window.showOpenFilePicker(
+ { types:[
+ {
+ description: "Packages",
+ accept:{"text/*":[".pk3", ".pak", ".pk4", ".zip"]}
+ },
+ {
+ description: "Maps",
+ accept:{"text/*":[".bsp.gz", ".bsp", ".map"]}
+ },
+ {
+ description: "Demos",
+ accept:{"application/*":[".mvd.gz", ".qwd.gz", ".dem.gz", ".mvd", ".qwd", ".dem"]} //dm2?
+ },
+ {
+ description: "FTE Manifest",
+ accept:{"text/*":[".fmf"]}
+ },
+ //model formats?... nah, too many/weird. they can always
+ //audio formats? eww
+ //image formats? double eww!
+ {
+ description: "Configs",
+ accept:{"text/*":[".cfg", ".rc"]}
+ }],
+ excludeAcceptAllOption:false, //let em pick anything. we actually support more than listed here (and bitrot...)
+ id:"openfile", //remember the dir we were in for the next invocation
+ multiple:true
+ })
+ .then((r)=>
+ {
+ let gamedir = prompt("Please enter gamedir", "id1");
+ if (gamedir != "")
+ gamedir = gamedir+"/";
+ for (let i of r)
+ {
+ i.getFile().then((f)=>
+ {
+ var n = fixupfilepath(f.name, gamedir);
+ Module.files[n]=f.arrayBuffer(); //actually a promise...
+ Module.files[n].then(buf=>{Module.files[n]=buf;showfiles();}); //try and resolve it now.
+ });
+ }
+ }).catch((e)=>{console.log("showOpenFilePicker() aborted", e);});
+}
function showfiles()
{ //print the pending file list in some pretty way
if (Module.began)
@@ -137,31 +231,78 @@ function showfiles()
Module.setStatus('');
document.getElementById('dropzone').hidden = false;
document.getElementById('begin').hidden = false;
- var nt = "Drag gamedirs or individual package files here to make them available!
Active Files:
";
- var keys = Object.keys(Module.files);
- for(var i = 0; i < keys.length; i++)
+ let nt = "FTE Engine (Browser Port)
";
+ nt = nt + "Drag gamedirs or individual package files here to make them available!";
+ let keys = Object.keys(Module.files);
+ nt += "Session Files ("+keys.length+"):
";
+ for(let i = 0; i < keys.length; i++)
{
+ let rem = " [forget]" +
+ " [rename]";
if (Module.files[keys[i]] instanceof ArrayBuffer)
{
- var sz = Module.files[keys[i]].byteLength;
+ let sz = Module.files[keys[i]].byteLength;
if (sz > 512*1024)
sz = (sz / (1024*1024)) + "mb";
else if (sz > 512)
sz = (sz / 1024) + "kb";
else
sz = (sz) + " bytes";
- nt += " " + keys[i] + " ("+sz+")
";
+ nt += " " + keys[i] + " ("+sz+")"+rem+"
";
}
else
- nt += " " + keys[i] + "
";
+ nt += " " + keys[i] + rem + "
";
}
- nt += "
("+keys.length+" files)";
+
+ //cache is for large data files. for any pk3s the user might add in-engine. large stuff that's easy to fix if it gets wiped.
+ const cache = Module['cache'];
+ const ckeys = Module['cachekeys'];
+ if (ckeys !== undefined && ckeys.length)
+ {
+ nt += "
Cached Files ("+ckeys.length+"):
";
+ for(let r of ckeys)
+ {
+ const idx = r.url.indexOf("/_/")
+ if (idx < 0)
+ continue; //wtf? that entry should not have been in this cache object.
+ const fn = r.url.substr(idx+3);
+ let rem = " [forget]";
+ nt += " " + fn + rem + "
";
+ }
+ }
+
+ //local storage is used for slightly more persistent things, like user configs and saved games. we have quite limited storage, and this is basically text only.
+ try
+ {
+ const ls = window.localStorage;
+ if (ls && ls.length)
+ {
+ nt += "
Local Files ("+ls.length+"):
";
+ for (let i = 0; i < ls.length; i++)
+ {
+ const fn = ls.key(i);
+ const rem = " [forget]" + (window.showSaveFilePicker!==undefined?" [export]":"");
+ nt += " " + fn + rem + "
";
+ }
+ }
+ }
+ catch(e){}
+
+ nt += "
";
+
+ nt += "
Cookie Disclaimer:
This page does not use cookies, however it does use local storage to save configs+games (consistent with natively installed games).
frag-net (our matchmaking service) does not utilise any tracking beyond the session in question, but it does allow connecting to third-party servers which may incorporate ranking systems or accounts or other tracking according to that server's privacy/tracking policies."
document.getElementById('dropzone').innerHTML = nt;
}
function scanfiles(item,path)
{ //for directory drops
if (item.isFile)
{
+ if (path=="")
+ {
+ path = prompt("Please enter gamedir", "id1");
+ if (path != "")
+ path = path+"/";
+ }
item.file(function(f)
{
let n = fixupfilepath(f.name, path);
@@ -193,7 +334,7 @@ function gotdrop(ev)
else if (ev.dataTransfer.items[i].kind === 'file')
{
var f = ev.dataTransfer.items[i].getAsFile();
- var n = fixupfilepath(f.name);
+ var n = fixupfilepath(f.name, "");
Module.files[n]=f.arrayBuffer(); //actually a promise...
Module.files[n].then(buf=>{Module.files[n]=buf;showfiles();}); //try and resolve it now.
}
@@ -201,10 +342,17 @@ function gotdrop(ev)
}
if (window.showOpenFilePicker)
addfile.hidden = false;
-if (window.location.hash != "" || Object.keys(Module.files).length)
+if (window.location.hash != "" || Module["autostart"])
begin(); //if the url has a #foo.fmf then just begin instantly,
else
- showfiles(); //otherwise show our lame file dropper and wait for the user to click 'go'.
+{
+ try {
+ caches.open('user').then((c)=>{Module['cache']=c;return c.keys();}).then((keys)=>{Module['cachekeys'] = keys; showfiles();});
+ } catch(e){
+ } finally {
+ showfiles(); //otherwise show our lame file dropper and wait for the user to click 'go'.
+ }
+}