mirror of
https://github.com/ioquake/ioq3.git
synced 2024-11-09 23:01:51 +00:00
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.
This commit is contained in:
parent
8365ea7ed2
commit
2660bb4a03
6 changed files with 59 additions and 34 deletions
7
Makefile
7
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
|
# 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.
|
# 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
|
# 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.
|
# changed later.
|
||||||
ifeq ($(EMSCRIPTEN_PRELOAD_FILE),1)
|
ifeq ($(EMSCRIPTEN_PRELOAD_FILE),1)
|
||||||
ifeq ($(wildcard $(BASEGAME)/*),)
|
ifeq ($(wildcard $(BASEGAME)/*),)
|
||||||
$(error "No files in '$(BASEGAME)' directory for emscripten to preload.")
|
$(error "No files in '$(BASEGAME)' directory for emscripten to preload.")
|
||||||
endif
|
endif
|
||||||
CLIENT_LDFLAGS+=--preload-file $(BASEGAME)
|
CLIENT_LDFLAGS+=--preload-file $(BASEGAME)
|
||||||
CLIENT_EXTRA_FILES+=code/web/empty/ioq3-config.json
|
|
||||||
else
|
else
|
||||||
CLIENT_EXTRA_FILES+=code/web/$(BASEGAME)/ioq3-config.json
|
CLIENT_EXTRA_FILES+=code/web/client-config.json
|
||||||
endif
|
endif
|
||||||
|
|
||||||
OPTIMIZEVM = -O3
|
OPTIMIZEVM = -O3
|
||||||
|
@ -3093,7 +3092,7 @@ $(B)/$(MISSIONPACK)/qcommon/%.asm: $(CMDIR)/%.c $(Q3LCC)
|
||||||
|
|
||||||
$(B)/$(CLIENTBIN).html: $(WEBDIR)/client.html
|
$(B)/$(CLIENTBIN).html: $(WEBDIR)/client.html
|
||||||
$(echo_cmd) "SED $@"
|
$(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' < $< > $@
|
||||||
|
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
|
@ -105,7 +105,7 @@ For Web, building with Emscripten
|
||||||
2. Run `emmake make debug` (or release).
|
2. Run `emmake make debug` (or release).
|
||||||
3. Copy or symlink your baseq3 pk3 files into the `build/debug-emscripten-wasm32/baseq3`
|
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
|
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`
|
4. Start a web server serving this directory. `python3 -m http.server`
|
||||||
is an easy default that you may already have installed.
|
is an easy default that you may already have installed.
|
||||||
5. Open `http://localhost:8000/build/debug-emscripten-wasm32/ioquake3.html`
|
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
|
EMSCRIPTEN_PRELOAD_FILE - set to 1 to package 'baseq3' (BASEGAME) directory
|
||||||
containing pk3s and loose files as a single
|
containing pk3s and loose files as a single
|
||||||
.data file that is loaded instead of listing
|
.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.
|
The defaults for these variables differ depending on the target platform.
|
||||||
|
|
|
@ -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"}
|
|
||||||
]
|
|
||||||
}
|
|
36
code/web/client-config.json
Normal file
36
code/web/client-config.json
Normal file
|
@ -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"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.
|
// These strings are set in the generated HTML file in the build directory.
|
||||||
let CLIENTBIN = '__CLIENTBIN__';
|
let CLIENTBIN = '__CLIENTBIN__';
|
||||||
let BASEGAME = '__BASEGAME__';
|
let BASEGAME = '__BASEGAME__';
|
||||||
|
let EMSCRIPTEN_PRELOAD_FILE = Number('__EMSCRIPTEN_PRELOAD_FILE__');
|
||||||
// Detect if it's not the generated HTML file.
|
// Detect if it's not the generated HTML file.
|
||||||
let clientHtmlFallback = (CLIENTBIN === '\_\_CLIENTBIN\_\_');
|
let clientHtmlFallback = (CLIENTBIN === '\_\_CLIENTBIN\_\_');
|
||||||
|
|
||||||
|
@ -19,24 +20,25 @@ let enginePath = './';
|
||||||
// Path or URL containing fs_game directories.
|
// Path or URL containing fs_game directories.
|
||||||
let dataPath = './';
|
let dataPath = './';
|
||||||
// Path or URL for config file that specifies the files to load for each fs_game.
|
// 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 displaying the unmodified HTML file, fallback to defaults.
|
||||||
if (clientHtmlFallback) {
|
if (clientHtmlFallback) {
|
||||||
CLIENTBIN='ioquake3';
|
CLIENTBIN='ioquake3';
|
||||||
BASEGAME='baseq3';
|
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`);
|
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.
|
// First set up the command line arguments and the Emscripten filesystem.
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
const basegame = urlParams.get('basegame') || BASEGAME;
|
const com_basegame = urlParams.get('com_basegame') || BASEGAME;
|
||||||
let generatedArguments = `
|
let generatedArguments = `
|
||||||
+set sv_pure 0
|
+set sv_pure 0
|
||||||
+set net_enabled 0
|
+set net_enabled 0
|
||||||
+set r_mode -2
|
+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.
|
// 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');
|
const queryArgs = urlParams.get('args');
|
||||||
|
@ -62,12 +64,13 @@ if (clientHtmlFallback) {
|
||||||
|
|
||||||
enginePath = buildPaths[buildIndex];
|
enginePath = buildPaths[buildIndex];
|
||||||
dataPath = 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 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;
|
const ioquake3 = (await import(enginePath + `${CLIENTBIN}_opengl2.wasm32.js`)).default;
|
||||||
ioquake3({
|
ioquake3({
|
||||||
|
@ -78,13 +81,19 @@ ioquake3({
|
||||||
module.addRunDependency('setup-ioq3-filesystem');
|
module.addRunDependency('setup-ioq3-filesystem');
|
||||||
try {
|
try {
|
||||||
const config = await configPromise;
|
const config = await configPromise;
|
||||||
const fetches = config.files.map(file => fetch(new URL(file.src, dataURL)));
|
const gamedir = com_basegame;
|
||||||
for (let i = 0; i < config.files.length; i++) {
|
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];
|
const response = await fetches[i];
|
||||||
if (!response.ok) continue;
|
if (!response.ok) continue;
|
||||||
const data = await response.arrayBuffer();
|
const data = await response.arrayBuffer();
|
||||||
let name = config.files[i].src.match(/[^/]+$/)[0];
|
let name = files[i].src.match(/[^/]+$/)[0];
|
||||||
let dir = config.files[i].dst;
|
let dir = files[i].dst;
|
||||||
module.FS.mkdirTree(dir);
|
module.FS.mkdirTree(dir);
|
||||||
module.FS.writeFile(`${dir}/${name}`, new Uint8Array(data));
|
module.FS.writeFile(`${dir}/${name}`, new Uint8Array(data));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"files": []
|
|
||||||
}
|
|
Loading…
Reference in a new issue