mirror of
https://github.com/openmoh/openmohaa.git
synced 2025-04-28 21:57:57 +03:00
Add Emscripten implementation from ioquake3 upstream
This commit is contained in:
parent
2d15696a5e
commit
ee514710c5
4 changed files with 280 additions and 40 deletions
|
@ -355,6 +355,22 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
//================================================================== EMSCRIPTEN ===
|
||||||
|
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
|
||||||
|
#define OS_STRING "emscripten"
|
||||||
|
#define ID_INLINE inline
|
||||||
|
#define PATH_SEP '/'
|
||||||
|
|
||||||
|
#define ARCH_STRING "wasm32"
|
||||||
|
|
||||||
|
#define Q3_LITTLE_ENDIAN
|
||||||
|
|
||||||
|
#define DLL_EXT ".wasm"
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
//================================================================== Q3VM ===
|
//================================================================== Q3VM ===
|
||||||
|
|
||||||
#ifdef Q3_VM
|
#ifdef Q3_VM
|
||||||
|
|
|
@ -52,6 +52,7 @@ cvar_t *r_allowSoftwareGL; // Don't abort out if a hardware visual can't be obta
|
||||||
cvar_t *r_allowResize; // make window resizable
|
cvar_t *r_allowResize; // make window resizable
|
||||||
cvar_t *r_centerWindow;
|
cvar_t *r_centerWindow;
|
||||||
cvar_t *r_sdlDriver;
|
cvar_t *r_sdlDriver;
|
||||||
|
cvar_t *r_useOpenGLES;
|
||||||
|
|
||||||
int qglMajorVersion, qglMinorVersion;
|
int qglMajorVersion, qglMinorVersion;
|
||||||
int qglesMajorVersion, qglesMinorVersion;
|
int qglesMajorVersion, qglesMinorVersion;
|
||||||
|
@ -230,6 +231,27 @@ static void GLimp_DetectAvailableModes(void)
|
||||||
SDL_free( modes );
|
SDL_free( modes );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
===============
|
||||||
|
OpenGL ES compatibility
|
||||||
|
===============
|
||||||
|
*/
|
||||||
|
static void APIENTRY GLimp_GLES_ClearDepth( GLclampd depth ) {
|
||||||
|
qglClearDepthf( depth );
|
||||||
|
}
|
||||||
|
|
||||||
|
static void APIENTRY GLimp_GLES_DepthRange( GLclampd near_val, GLclampd far_val ) {
|
||||||
|
qglDepthRangef( near_val, far_val );
|
||||||
|
}
|
||||||
|
|
||||||
|
static void APIENTRY GLimp_GLES_DrawBuffer( GLenum mode ) {
|
||||||
|
// unsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
static void APIENTRY GLimp_GLES_PolygonMode( GLenum face, GLenum mode ) {
|
||||||
|
// unsupported
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
===============
|
===============
|
||||||
GLimp_GetProcAddresses
|
GLimp_GetProcAddresses
|
||||||
|
@ -293,6 +315,7 @@ static qboolean GLimp_GetProcAddresses( qboolean fixedFunction ) {
|
||||||
} else {
|
} else {
|
||||||
Com_Error( ERR_FATAL, "Unsupported OpenGL Version (%s), OpenGL 1.1 is required", version );
|
Com_Error( ERR_FATAL, "Unsupported OpenGL Version (%s), OpenGL 1.1 is required", version );
|
||||||
}
|
}
|
||||||
|
// Add compression-related GL functions for the renderer
|
||||||
if ( QGL_VERSION_ATLEAST( 1, 3 ) ) {
|
if ( QGL_VERSION_ATLEAST( 1, 3 ) ) {
|
||||||
QGL_1_3_PROCS;
|
QGL_1_3_PROCS;
|
||||||
}
|
}
|
||||||
|
@ -309,8 +332,11 @@ static qboolean GLimp_GetProcAddresses( qboolean fixedFunction ) {
|
||||||
QGL_1_3_PROCS;
|
QGL_1_3_PROCS;
|
||||||
QGL_1_5_PROCS;
|
QGL_1_5_PROCS;
|
||||||
QGL_2_0_PROCS;
|
QGL_2_0_PROCS;
|
||||||
// error so this doesn't segfault due to NULL desktop GL functions being used
|
|
||||||
Com_Error( ERR_FATAL, "Unsupported OpenGL Version: %s", version );
|
qglClearDepth = GLimp_GLES_ClearDepth;
|
||||||
|
qglDepthRange = GLimp_GLES_DepthRange;
|
||||||
|
qglDrawBuffer = GLimp_GLES_DrawBuffer;
|
||||||
|
qglPolygonMode = GLimp_GLES_PolygonMode;
|
||||||
} else {
|
} else {
|
||||||
Com_Error( ERR_FATAL, "Unsupported OpenGL Version (%s), OpenGL 2.0 is required", version );
|
Com_Error( ERR_FATAL, "Unsupported OpenGL Version (%s), OpenGL 2.0 is required", version );
|
||||||
}
|
}
|
||||||
|
@ -636,23 +662,47 @@ static int GLimp_SetMode(int mode, qboolean fullscreen, qboolean noborder, qbool
|
||||||
|
|
||||||
if (!fixedFunction)
|
if (!fixedFunction)
|
||||||
{
|
{
|
||||||
int profileMask, majorVersion, minorVersion;
|
int profileMask;
|
||||||
SDL_GL_GetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, &profileMask);
|
|
||||||
SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &majorVersion);
|
|
||||||
SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &minorVersion);
|
|
||||||
|
|
||||||
|
SDL_GL_GetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, &profileMask );
|
||||||
|
|
||||||
|
if ( r_useOpenGLES->integer == 1 || ( r_useOpenGLES->integer == -1 && profileMask == SDL_GL_CONTEXT_PROFILE_ES ) )
|
||||||
|
{
|
||||||
|
ri.Printf( PRINT_ALL, "Trying to get an OpenGL ES 2.0 context\n" );
|
||||||
|
SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES );
|
||||||
|
SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, 2 );
|
||||||
|
SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, 0 );
|
||||||
|
|
||||||
|
SDL_glContext = SDL_GL_CreateContext( SDL_window );
|
||||||
|
if ( !SDL_glContext )
|
||||||
|
{
|
||||||
|
ri.Printf( PRINT_ALL, "SDL_GL_CreateContext failed: %s\n", SDL_GetError() );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ri.Printf( PRINT_ALL, "SDL_GL_CreateContext succeeded.\n" );
|
||||||
|
|
||||||
|
if ( !GLimp_GetProcAddresses( fixedFunction ) )
|
||||||
|
{
|
||||||
|
ri.Printf( PRINT_ALL, "GLimp_GetProcAddresses() failed for OpenGL ES 2.0 context\n" );
|
||||||
|
GLimp_ClearProcAddresses();
|
||||||
|
SDL_GL_DeleteContext( SDL_glContext );
|
||||||
|
SDL_glContext = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !SDL_glContext )
|
||||||
|
{
|
||||||
ri.Printf( PRINT_ALL, "Trying to get an OpenGL 3.2 core context\n" );
|
ri.Printf( PRINT_ALL, "Trying to get an OpenGL 3.2 core context\n" );
|
||||||
SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE );
|
SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE );
|
||||||
SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, 3 );
|
SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, 3 );
|
||||||
SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, 2 );
|
SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, 2 );
|
||||||
if ((SDL_glContext = SDL_GL_CreateContext(SDL_window)) == NULL)
|
|
||||||
|
SDL_glContext = SDL_GL_CreateContext( SDL_window );
|
||||||
|
if ( !SDL_glContext )
|
||||||
{
|
{
|
||||||
ri.Printf( PRINT_ALL, "SDL_GL_CreateContext failed: %s\n", SDL_GetError() );
|
ri.Printf( PRINT_ALL, "SDL_GL_CreateContext failed: %s\n", SDL_GetError() );
|
||||||
ri.Printf(PRINT_ALL, "Reverting to default context\n");
|
|
||||||
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profileMask);
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, majorVersion);
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, minorVersion);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -670,7 +720,7 @@ static int GLimp_SetMode(int mode, qboolean fullscreen, qboolean noborder, qbool
|
||||||
renderer = NULL;
|
renderer = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!renderer || (strstr(renderer, "Software Renderer") || strstr(renderer, "Software Rasterizer")))
|
if ( !renderer || strstr( renderer, "Software Renderer" ) || strstr( renderer, "Software Rasterizer" ) )
|
||||||
{
|
{
|
||||||
if ( renderer )
|
if ( renderer )
|
||||||
ri.Printf(PRINT_ALL, "GL_RENDERER is %s, rejecting context\n", renderer);
|
ri.Printf(PRINT_ALL, "GL_RENDERER is %s, rejecting context\n", renderer);
|
||||||
|
@ -678,15 +728,25 @@ static int GLimp_SetMode(int mode, qboolean fullscreen, qboolean noborder, qbool
|
||||||
GLimp_ClearProcAddresses();
|
GLimp_ClearProcAddresses();
|
||||||
SDL_GL_DeleteContext( SDL_glContext );
|
SDL_GL_DeleteContext( SDL_glContext );
|
||||||
SDL_glContext = NULL;
|
SDL_glContext = NULL;
|
||||||
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profileMask);
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, majorVersion);
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, minorVersion);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( !SDL_glContext )
|
||||||
|
{
|
||||||
|
ri.Printf( PRINT_ALL, "Trying to get an OpenGL 2.0 context\n" );
|
||||||
|
|
||||||
|
SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, 0 );
|
||||||
|
SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, 2 );
|
||||||
|
SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, 0 );
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, 0 );
|
||||||
|
SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, 1 );
|
||||||
|
SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, 1 );
|
||||||
|
|
||||||
SDL_glContext = NULL;
|
SDL_glContext = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -818,7 +878,7 @@ static void GLimp_InitExtensions( qboolean fixedFunction )
|
||||||
glConfig.textureCompression = TC_NONE;
|
glConfig.textureCompression = TC_NONE;
|
||||||
|
|
||||||
// GL_EXT_texture_compression_s3tc
|
// GL_EXT_texture_compression_s3tc
|
||||||
if ( SDL_GL_ExtensionSupported( "GL_ARB_texture_compression" ) &&
|
if ( ( QGLES_VERSION_ATLEAST( 2, 0 ) || SDL_GL_ExtensionSupported( "GL_ARB_texture_compression" ) ) &&
|
||||||
SDL_GL_ExtensionSupported( "GL_EXT_texture_compression_s3tc" ) )
|
SDL_GL_ExtensionSupported( "GL_EXT_texture_compression_s3tc" ) )
|
||||||
{
|
{
|
||||||
if ( r_ext_compressed_textures->value )
|
if ( r_ext_compressed_textures->value )
|
||||||
|
@ -999,6 +1059,7 @@ void GLimp_Init( qboolean fixedFunction )
|
||||||
r_sdlDriver = ri.Cvar_Get( "r_sdlDriver", "", CVAR_ROM );
|
r_sdlDriver = ri.Cvar_Get( "r_sdlDriver", "", CVAR_ROM );
|
||||||
r_allowResize = ri.Cvar_Get( "r_allowResize", "0", CVAR_ARCHIVE | CVAR_LATCH );
|
r_allowResize = ri.Cvar_Get( "r_allowResize", "0", CVAR_ARCHIVE | CVAR_LATCH );
|
||||||
r_centerWindow = ri.Cvar_Get( "r_centerWindow", "0", CVAR_ARCHIVE | CVAR_LATCH );
|
r_centerWindow = ri.Cvar_Get( "r_centerWindow", "0", CVAR_ARCHIVE | CVAR_LATCH );
|
||||||
|
r_useOpenGLES = ri.Cvar_Get( "r_useOpenGLES", "-1", CVAR_ARCHIVE | CVAR_LATCH );
|
||||||
|
|
||||||
if( ri.Cvar_VariableIntegerValue( "com_abnormalExit" ) )
|
if( ri.Cvar_VariableIntegerValue( "com_abnormalExit" ) )
|
||||||
{
|
{
|
||||||
|
|
47
code/web/client-config.json
Normal file
47
code/web/client-config.json
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
{
|
||||||
|
"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"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"missionpack": {
|
||||||
|
"files": [
|
||||||
|
{"src": "missionpack/pak0.pk3", "dst": "/missionpack"},
|
||||||
|
{"src": "missionpack/pak1.pk3", "dst": "/missionpack"},
|
||||||
|
{"src": "missionpack/pak2.pk3", "dst": "/missionpack"},
|
||||||
|
{"src": "missionpack/pak3.pk3", "dst": "/missionpack"},
|
||||||
|
{"src": "missionpack/vm/cgame.qvm", "dst": "/missionpack/vm"},
|
||||||
|
{"src": "missionpack/vm/qagame.qvm", "dst": "/missionpack/vm"},
|
||||||
|
{"src": "missionpack/vm/ui.qvm", "dst": "/missionpack/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"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
116
code/web/client.html
Normal file
116
code/web/client.html
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
<!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__';
|
||||||
|
let EMSCRIPTEN_PRELOAD_FILE = Number('__EMSCRIPTEN_PRELOAD_FILE__');
|
||||||
|
// 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 = `./${CLIENTBIN}-config.json`;
|
||||||
|
|
||||||
|
// If displaying the unmodified HTML file, fallback to defaults.
|
||||||
|
if (clientHtmlFallback) {
|
||||||
|
CLIENTBIN='ioquake3';
|
||||||
|
BASEGAME='baseq3';
|
||||||
|
EMSCRIPTEN_PRELOAD_FILE=0;
|
||||||
|
configFilename='./client-config.json';
|
||||||
|
}
|
||||||
|
|
||||||
|
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 com_basegame = urlParams.get('com_basegame') || BASEGAME;
|
||||||
|
const fs_basegame = urlParams.get('fs_basegame') || '';
|
||||||
|
const fs_game = urlParams.get('fs_game') || '';
|
||||||
|
let generatedArguments = `
|
||||||
|
+set sv_pure 0
|
||||||
|
+set net_enabled 0
|
||||||
|
+set r_mode -2
|
||||||
|
+set com_basegame "${com_basegame}"
|
||||||
|
+set fs_basegame "${fs_basegame}"
|
||||||
|
+set fs_game "${fs_game}"
|
||||||
|
`;
|
||||||
|
// 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];
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataURL = new URL(dataPath, location.origin + location.pathname);
|
||||||
|
|
||||||
|
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({
|
||||||
|
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 gamedirs = [com_basegame,fs_basegame,fs_game];
|
||||||
|
for (let g = 0; g < gamedirs.length; g++) {
|
||||||
|
const gamedir = gamedirs[g];
|
||||||
|
if (gamedir === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (config[gamedir] === null
|
||||||
|
|| config[gamedir].files === null) {
|
||||||
|
console.warn(`Game directory '${gamedir}' cannot be used. It must have files listed in ${configFilename}.`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
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 = files[i].src.match(/[^/]+$/)[0];
|
||||||
|
let dir = files[i].dst;
|
||||||
|
module.FS.mkdirTree(dir);
|
||||||
|
module.FS.writeFile(`${dir}/${name}`, new Uint8Array(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
module.removeRunDependency('setup-ioq3-filesystem');
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
</script>
|
Loading…
Add table
Add a link
Reference in a new issue