ioq3/code/web/client.html
Zack Middleton 8365ea7ed2 Customize the web client HTML file
Modify the client HTML file when copying it to the build directory to
apply current CLIENTBIN and BASEGAME. It always loads engine/data from
the current directory (no need to try to locate the build directory).
2024-06-11 04:40:08 -04:00

96 lines
4.9 KiB
HTML

<!DOCTYPE html><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
<title>__CLIENTBIN__ Emscripten demo</title>
<style>
html, body { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; background: rgb(0, 0, 0); display:flex; align-items: center; justify-content: center; }
canvas { max-width: 100%; max-height: 100%; min-width: 100%; min-height: 100%; object-fit: contain; }
</style>
<canvas id=canvas></canvas>
<script type=module>
// These strings are set in the generated HTML file in the build directory.
let CLIENTBIN = '__CLIENTBIN__';
let BASEGAME = '__BASEGAME__';
// Detect if it's not the generated HTML file.
let clientHtmlFallback = (CLIENTBIN === '\_\_CLIENTBIN\_\_');
// Path or URL containing the client engine .js, .wasm, and possibly .data.
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';
// If displaying the unmodified HTML file, fallback to defaults.
if (clientHtmlFallback) {
CLIENTBIN='ioquake3';
BASEGAME='baseq3';
}
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;
let generatedArguments = `
+set sv_pure 0
+set net_enabled 0
+set r_mode -2
+set fs_game ${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');
if (queryArgs) generatedArguments += ` ${queryArgs} `;
// If displaying the unmodified HTML file, the engine and data are probably located in a different directory.
if (clientHtmlFallback) {
// If buildPath is not specified, try to find a build in one of a few default paths.
let buildPath = urlParams.get('buildPath');
if (buildPath && !buildPath.endsWith('/')) buildPath += '/';
const buildPaths = buildPath ? [buildPath] : ['../../build/debug-emscripten-wasm32/', '../../build/release-emscripten-wasm32/', './'];
const scriptPaths = buildPaths.map(buildPath => buildPath + `${CLIENTBIN}_opengl2.wasm32.js`);
const scriptResponses = await Promise.all(scriptPaths.map(p => fetch(p, {method: 'HEAD'})));
const validBuilds = scriptResponses.filter(r => r.ok).length;
const goodURL = (newPath) => {
const url = new URL(window.location);
url.searchParams.set('buildPath', newPath);
return url.toString().replace(/%2f/gi, '/');
};
if (validBuilds === 0) throw new Error(`Didn't find any wasm builds. Run \`emmake make debug\` to build one, or use the buildPath query parameter to specify a directory containing ${CLIENTBIN}_opengl2.wasm32.[js,wasm,data], e.g. ${goodURL('../../build/debug-emscripten-wasm32/')}`);
if (validBuilds > 1) throw new Error(`Found multiple valid builds at the following paths: [${buildPaths.filter((path, i)=>scriptResponses[i].ok)}]. Please specify which one to run by adding a buildPath query parameter to the URL, e.g. ${goodURL(buildPaths.filter((path, i)=>scriptResponses[i].ok)[0])}`);
const buildIndex = scriptResponses.findIndex(r => r.ok);
enginePath = buildPaths[buildIndex];
dataPath = buildPaths[buildIndex];
configFilename = dataPath + 'ioq3-config.json';
}
const dataURL = new URL(dataPath, location.origin + location.pathname);
const configPromise = fetch(configFilename).then(r => r.ok ? r.json() : {files: []});
const ioquake3 = (await import(enginePath + `${CLIENTBIN}_opengl2.wasm32.js`)).default;
ioquake3({
canvas: canvas,
arguments: generatedArguments.trim().split(/\s+/),
locateFile: (file) => enginePath + file,
preRun: [async (module) => {
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 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;
module.FS.mkdirTree(dir);
module.FS.writeFile(`${dir}/${name}`, new Uint8Array(data));
}
} finally {
module.removeRunDependency('setup-ioq3-filesystem');
}
}],
});
</script>