From 2660bb4a03213a430b68f22d1f1b08aaff8004ee Mon Sep 17 00:00:00 2001 From: Zack Middleton Date: Sun, 9 Jun 2024 22:28:43 -0500 Subject: [PATCH] Add support for overriding basegame to web client List files for multiple games in a single client-config.json file so that com_basegame argument can pick different game data. Use ioquake3.html?com_basegame=demoq3 (or tademo) to run the Quake 3 or Team Arena demo. They require new QVMs from baseq3/missionpack to run. --- Makefile | 7 +++---- README.md | 4 ++-- code/web/baseq3/ioq3-config.json | 16 -------------- code/web/client-config.json | 36 ++++++++++++++++++++++++++++++++ code/web/client.html | 27 ++++++++++++++++-------- code/web/empty/ioq3-config.json | 3 --- 6 files changed, 59 insertions(+), 34 deletions(-) delete mode 100644 code/web/baseq3/ioq3-config.json create mode 100644 code/web/client-config.json delete mode 100644 code/web/empty/ioq3-config.json diff --git a/Makefile b/Makefile index 19b2df83..fe8b3709 100644 --- a/Makefile +++ b/Makefile @@ -1086,16 +1086,15 @@ ifeq ($(PLATFORM),emscripten) # and added to the virtual filesystem before the game starts. This requires the game data to be # present at build time and it can't be changed afterward. # For more flexibility, game data files can be loaded from a web server at runtime by listing - # them in ioq3-config.json. This way they don't have to be present at build time and can be + # them in client-config.json. This way they don't have to be present at build time and can be # changed later. ifeq ($(EMSCRIPTEN_PRELOAD_FILE),1) ifeq ($(wildcard $(BASEGAME)/*),) $(error "No files in '$(BASEGAME)' directory for emscripten to preload.") endif CLIENT_LDFLAGS+=--preload-file $(BASEGAME) - CLIENT_EXTRA_FILES+=code/web/empty/ioq3-config.json else - CLIENT_EXTRA_FILES+=code/web/$(BASEGAME)/ioq3-config.json + CLIENT_EXTRA_FILES+=code/web/client-config.json endif OPTIMIZEVM = -O3 @@ -3093,7 +3092,7 @@ $(B)/$(MISSIONPACK)/qcommon/%.asm: $(CMDIR)/%.c $(Q3LCC) $(B)/$(CLIENTBIN).html: $(WEBDIR)/client.html $(echo_cmd) "SED $@" - $(Q)sed 's/__CLIENTBIN__/$(CLIENTBIN)/g;s/__BASEGAME__/$(BASEGAME)/g' < $< > $@ + $(Q)sed 's/__CLIENTBIN__/$(CLIENTBIN)/g;s/__BASEGAME__/$(BASEGAME)/g;s/__EMSCRIPTEN_PRELOAD_FILE__/$(EMSCRIPTEN_PRELOAD_FILE)/g' < $< > $@ ############################################################################# diff --git a/README.md b/README.md index dd374263..bbaede21 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ For Web, building with Emscripten 2. Run `emmake make debug` (or release). 3. Copy or symlink your baseq3 pk3 files into the `build/debug-emscripten-wasm32/baseq3` directory so they can be loaded at run-time. Only game files listed in - `ioq3-config.json` will be loaded. + `client-config.json` will be loaded. 4. Start a web server serving this directory. `python3 -m http.server` is an easy default that you may already have installed. 5. Open `http://localhost:8000/build/debug-emscripten-wasm32/ioquake3.html` @@ -177,7 +177,7 @@ Makefile.local: EMSCRIPTEN_PRELOAD_FILE - set to 1 to package 'baseq3' (BASEGAME) directory containing pk3s and loose files as a single .data file that is loaded instead of listing - individual files in ioq3-config.json + individual files in client-config.json ``` The defaults for these variables differ depending on the target platform. diff --git a/code/web/baseq3/ioq3-config.json b/code/web/baseq3/ioq3-config.json deleted file mode 100644 index 1ee8cc01..00000000 --- a/code/web/baseq3/ioq3-config.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "files": [ - {"src": "baseq3/pak0.pk3", "dst": "/baseq3"}, - {"src": "baseq3/pak1.pk3", "dst": "/baseq3"}, - {"src": "baseq3/pak2.pk3", "dst": "/baseq3"}, - {"src": "baseq3/pak3.pk3", "dst": "/baseq3"}, - {"src": "baseq3/pak4.pk3", "dst": "/baseq3"}, - {"src": "baseq3/pak5.pk3", "dst": "/baseq3"}, - {"src": "baseq3/pak6.pk3", "dst": "/baseq3"}, - {"src": "baseq3/pak7.pk3", "dst": "/baseq3"}, - {"src": "baseq3/pak8.pk3", "dst": "/baseq3"}, - {"src": "baseq3/vm/cgame.qvm", "dst": "/baseq3/vm"}, - {"src": "baseq3/vm/qagame.qvm", "dst": "/baseq3/vm"}, - {"src": "baseq3/vm/ui.qvm", "dst": "/baseq3/vm"} - ] -} diff --git a/code/web/client-config.json b/code/web/client-config.json new file mode 100644 index 00000000..3dc05b98 --- /dev/null +++ b/code/web/client-config.json @@ -0,0 +1,36 @@ +{ + "baseq3": { + "files": [ + {"src": "baseq3/pak0.pk3", "dst": "/baseq3"}, + {"src": "baseq3/pak1.pk3", "dst": "/baseq3"}, + {"src": "baseq3/pak2.pk3", "dst": "/baseq3"}, + {"src": "baseq3/pak3.pk3", "dst": "/baseq3"}, + {"src": "baseq3/pak4.pk3", "dst": "/baseq3"}, + {"src": "baseq3/pak5.pk3", "dst": "/baseq3"}, + {"src": "baseq3/pak6.pk3", "dst": "/baseq3"}, + {"src": "baseq3/pak7.pk3", "dst": "/baseq3"}, + {"src": "baseq3/pak8.pk3", "dst": "/baseq3"}, + {"src": "baseq3/vm/cgame.qvm", "dst": "/baseq3/vm"}, + {"src": "baseq3/vm/qagame.qvm", "dst": "/baseq3/vm"}, + {"src": "baseq3/vm/ui.qvm", "dst": "/baseq3/vm"} + ] + }, + "demoq3": { + "_comment": "Copy baseq3/vm/*.qvm to demoq3/vm/ as the Quake 3 demo QVMs are not compatible. However the botfiles are not fully compatible with newer QVMs.", + "files": [ + {"src": "demoq3/pak0.pk3", "dst": "/demoq3"}, + {"src": "demoq3/vm/cgame.qvm", "dst": "/demoq3/vm"}, + {"src": "demoq3/vm/qagame.qvm", "dst": "/demoq3/vm"}, + {"src": "demoq3/vm/ui.qvm", "dst": "/demoq3/vm"} + ] + }, + "tademo": { + "_comment": "Copy missionpack/vm/*.qvm to tademo/vm/ as the Team Arena demo QVMs are not compatible.", + "files": [ + {"src": "tademo/pak0.pk3", "dst": "/tademo"}, + {"src": "tademo/vm/cgame.qvm", "dst": "/tademo/vm"}, + {"src": "tademo/vm/qagame.qvm", "dst": "/tademo/vm"}, + {"src": "tademo/vm/ui.qvm", "dst": "/tademo/vm"} + ] + } +} diff --git a/code/web/client.html b/code/web/client.html index 3814ad6c..a354ad2c 100644 --- a/code/web/client.html +++ b/code/web/client.html @@ -11,6 +11,7 @@ canvas { max-width: 100%; max-height: 100%; min-width: 100%; min-height: 100%; o // These strings are set in the generated HTML file in the build directory. let CLIENTBIN = '__CLIENTBIN__'; let BASEGAME = '__BASEGAME__'; +let EMSCRIPTEN_PRELOAD_FILE = Number('__EMSCRIPTEN_PRELOAD_FILE__'); // Detect if it's not the generated HTML file. let clientHtmlFallback = (CLIENTBIN === '\_\_CLIENTBIN\_\_'); @@ -19,24 +20,25 @@ let enginePath = './'; // Path or URL containing fs_game directories. let dataPath = './'; // Path or URL for config file that specifies the files to load for each fs_game. -let configFilename = './ioq3-config.json'; +let configFilename = './client-config.json'; // If displaying the unmodified HTML file, fallback to defaults. if (clientHtmlFallback) { CLIENTBIN='ioquake3'; BASEGAME='baseq3'; + EMSCRIPTEN_PRELOAD_FILE=0; } if (window.location.protocol === 'file:') throw new Error(`Unfortunately browser security restrictions prevent loading wasm from a file: URL. This file must be loaded from a web server. The easiest way to do this is probably to use Python\'s built-in web server by running \`python3 -m http.server\` in the top level source directory and then navigate to http://localhost:8000/build/debug-emscripten-wasm32/${CLIENTBIN}.html`); // First set up the command line arguments and the Emscripten filesystem. const urlParams = new URLSearchParams(window.location.search); -const basegame = urlParams.get('basegame') || BASEGAME; +const com_basegame = urlParams.get('com_basegame') || BASEGAME; let generatedArguments = ` +set sv_pure 0 +set net_enabled 0 +set r_mode -2 - +set fs_game ${basegame} + +set com_basegame "${com_basegame}" `; // Note that unfortunately "+" needs to be encoded as "%2b" in URL query strings or it will be stripped by the browser. const queryArgs = urlParams.get('args'); @@ -62,12 +64,13 @@ if (clientHtmlFallback) { enginePath = buildPaths[buildIndex]; dataPath = buildPaths[buildIndex]; - configFilename = dataPath + 'ioq3-config.json'; + configFilename = dataPath + 'client-config.json'; } const dataURL = new URL(dataPath, location.origin + location.pathname); -const configPromise = fetch(configFilename).then(r => r.ok ? r.json() : {files: []}); +const configPromise = ( EMSCRIPTEN_PRELOAD_FILE === 1 ) ? Promise.resolve({[BASEGAME]: {files: []}}) + : fetch(configFilename).then(r => r.ok ? r.json() : { /* empty config */ }); const ioquake3 = (await import(enginePath + `${CLIENTBIN}_opengl2.wasm32.js`)).default; ioquake3({ @@ -78,13 +81,19 @@ ioquake3({ module.addRunDependency('setup-ioq3-filesystem'); try { const config = await configPromise; - const fetches = config.files.map(file => fetch(new URL(file.src, dataURL))); - for (let i = 0; i < config.files.length; i++) { + const gamedir = com_basegame; + if (config[gamedir] === null + || config[gamedir].files === null) { + console.warn(`Game directory '${gamedir}' cannot be used. It must have files listed in ${configFilename}.`); + } + const files = config[gamedir].files; + const fetches = files.map(file => fetch(new URL(file.src, dataURL))); + for (let i = 0; i < files.length; i++) { const response = await fetches[i]; if (!response.ok) continue; const data = await response.arrayBuffer(); - let name = config.files[i].src.match(/[^/]+$/)[0]; - let dir = config.files[i].dst; + let name = files[i].src.match(/[^/]+$/)[0]; + let dir = files[i].dst; module.FS.mkdirTree(dir); module.FS.writeFile(`${dir}/${name}`, new Uint8Array(data)); } diff --git a/code/web/empty/ioq3-config.json b/code/web/empty/ioq3-config.json deleted file mode 100644 index 158547c2..00000000 --- a/code/web/empty/ioq3-config.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "files": [] -}