diff --git a/Source/Game.hpp b/Source/Game.hpp
index 8c51445..b60b884 100644
--- a/Source/Game.hpp
+++ b/Source/Game.hpp
@@ -182,6 +182,7 @@ static __forceinline void swap_gl_buffers(void)
extern SDL_Window* sdlwindow;
SDL_GL_SwapWindow(sdlwindow);
+#ifndef __EMSCRIPTEN__ // in the browser the animation frame sets the pace
// try to limit this to 60fps, even if vsync fails.
Uint32 now;
static Uint32 frameticks = 0;
@@ -189,6 +190,7 @@ static __forceinline void swap_gl_buffers(void)
while ((now = SDL_GetTicks()) < endticks) { /* spin. */
}
frameticks = now;
+#endif
}
enum maptypes
diff --git a/Source/Graphic/Text.cpp b/Source/Graphic/Text.cpp
index b7ec4e5..488e0f1 100644
--- a/Source/Graphic/Text.cpp
+++ b/Source/Graphic/Text.cpp
@@ -29,53 +29,11 @@ void Text::LoadFontTexture(const std::string& fileName)
LOG(std::string("Loading font texture...") + fileName);
FontTexture.load(fileName, false);
- if (base) {
- glDeleteLists(base, 512);
- base = 0;
- }
}
-void Text::BuildFont() // Build Our Font Display List
+void Text::BuildFont() // Display lists do not exist in the browser's OpenGL: the characters are drawn directly
{
- float cx; // Holds Our X Character Coord
- float cy; // Holds Our Y Character Coord
- int loop;
-
LOGFUNC;
-
- if (base) {
- glDeleteLists(base, 512);
- base = 0;
- }
-
- base = glGenLists(512); // Creating 256 Display Lists
- FontTexture.bind();
- for (loop = 0; loop < 512; loop++) { // Loop Through All 256 Lists
- if (loop < 256) {
- cx = float(loop % 16) / 16.0f; // X Position Of Current Character
- cy = float(loop / 16) / 16.0f; // Y Position Of Current Character
- } else {
- cx = float((loop - 256) % 16) / 16.0f; // X Position Of Current Character
- cy = float((loop - 256) / 16) / 16.0f; // Y Position Of Current Character
- }
- glNewList(base + loop, GL_COMPILE); // Start Building A List
- glBegin(GL_QUADS); // Use A Quad For Each Character
- glTexCoord2f(cx, 1 - cy - 0.0625f + .001); // Texture Coord (Bottom Left)
- glVertex2i(0, 0); // Vertex Coord (Bottom Left)
- glTexCoord2f(cx + 0.0625f, 1 - cy - 0.0625f + .001); // Texture Coord (Bottom Right)
- glVertex2i(16, 0); // Vertex Coord (Bottom Right)
- glTexCoord2f(cx + 0.0625f, 1 - cy - .001); // Texture Coord (Top Right)
- glVertex2i(16, 16); // Vertex Coord (Top Right)
- glTexCoord2f(cx, 1 - cy - +.001); // Texture Coord (Top Left)
- glVertex2i(0, 16); // Vertex Coord (Top Left)
- glEnd(); // Done Building Our Quad (Character)
- if (loop < 256) {
- glTranslated(10, 0, 0); // Move To The Right Of The Character
- } else {
- glTranslated(8, 0, 0); // Move To The Right Of The Character
- }
- glEndList(); // Done Building The Display List
- } // Loop Until All 256 Are Built
}
void Text::_glPrint(float x, float y, const std::string& string, int set, float size, float width, float height, int start, int end, int offset) // Where The Printing Happens
@@ -98,8 +56,28 @@ void Text::_glPrint(float x, float y, const std::string& string, int set, float
glLoadIdentity();
glTranslated(x, y, 0);
glScalef(size, size, 1);
- glListBase(base - 32 + (128 * set) + offset); // Choose The Font Set (0 or 1)
- glCallLists(end - start, GL_BYTE, &string[start]); // Write The Text To The Screen
+ // Write The Text To The Screen: one textured quad per character (set 0 or 1, plain or outlined)
+ glBegin(GL_QUADS);
+ float px = 0;
+ for (int i = start; i < end; i++) {
+ const int loop = (signed char)string[i] - 32 + (128 * set) + offset;
+ if (loop < 0 || loop >= 512) {
+ continue;
+ }
+ const int l = loop < 256 ? loop : loop - 256;
+ const float cx = float(l % 16) / 16.0f;
+ const float cy = float(l / 16) / 16.0f;
+ glTexCoord2f(cx, 1 - cy - 0.0625f + .001);
+ glVertex2i(px, 0);
+ glTexCoord2f(cx + 0.0625f, 1 - cy - 0.0625f + .001);
+ glVertex2i(px + 16, 0);
+ glTexCoord2f(cx + 0.0625f, 1 - cy - .001);
+ glVertex2i(px + 16, 16);
+ glTexCoord2f(cx, 1 - cy - +.001);
+ glVertex2i(px, 16);
+ px += loop < 256 ? 10 : 8;
+ }
+ glEnd();
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
@@ -144,8 +122,4 @@ Text::Text()
Text::~Text()
{
- if (base) {
- glDeleteLists(base, 512);
- base = 0;
- }
}
diff --git a/Source/Graphic/gamegl.hpp b/Source/Graphic/gamegl.hpp
index b824a43..6e66004 100644
--- a/Source/Graphic/gamegl.hpp
+++ b/Source/Graphic/gamegl.hpp
@@ -48,6 +48,10 @@ along with Lugaru. If not, see .
#include
#endif
+#ifdef __EMSCRIPTEN__
+#include "Graphic/lugaru-gl.h"
+#endif
+
using namespace std;
/* !!! FIXME: until we replace logger better. --ryan. */
diff --git a/Source/Graphic/lugaru-gl.h b/Source/Graphic/lugaru-gl.h
new file mode 100644
index 0000000..377a7f6
--- /dev/null
+++ b/Source/Graphic/lugaru-gl.h
@@ -0,0 +1,283 @@
+/*
+ * lugaru-gl.h: what the WebAssembly build of Lugaru (fredygames.com) needs on top of the OpenGL
+ * emulation of Emscripten (-sLEGACY_GL_EMULATION), which maps fixed-function OpenGL 1.x onto WebGL.
+ *
+ * - glBegin/glEnd: the emulation stores the attributes of every vertex in the order the calls come and
+ * expects all vertices to carry the same ones, while desktop OpenGL keeps a current color, texture
+ * coordinate and normal. The vertices are collected here with the current attributes and handed over
+ * complete at glEnd. A color set inside glBegin/glEnd stays current afterwards, as in OpenGL.
+ * - glInterleavedArrays, for the two formats the game uses, as separate array pointers.
+ * - Textures: WebGL accepts neither GL_CLAMP, nor GL_GENERATE_MIPMAP, nor numeric internal formats
+ * different from the format; glTexImage2D is wrapped to translate them (mipmaps are generated after
+ * the upload when the texture asked for them).
+ * - Calls with no WebGL equivalent (glPixelTransfer, glDrawBuffer, glReadBuffer, the texture LOD bias
+ * and GL_COMBINE texture environments, glHint) are dropped.
+ *
+ * Included at the end of gamegl.hpp, only when building with Emscripten.
+ *
+ * Copyright (C) 2026 FredyGames. Part of the Lugaru build for fredygames.com; distributed with Lugaru
+ * under the GNU General Public License, version 2 or (at your option) any later version.
+ */
+#ifndef LUGARU_GL_H
+#define LUGARU_GL_H
+
+#include
+#include
+#include
+#include
+
+/*===========================================================================*/
+// Immediate mode:
+
+struct lg_vertex {
+ GLfloat color[4], tc[2], normal[3], pos[3];
+};
+
+static GLfloat lg_color[4] = { 1, 1, 1, 1 };
+static GLfloat lg_tc[2] = { 0, 0 };
+static GLfloat lg_normal[3] = { 0, 0, 1 };
+static GLenum lg_mode;
+static int lg_in_begin = 0, lg_used_color = 0, lg_used_tc = 0, lg_used_normal = 0;
+static lg_vertex* lg_vertices = NULL;
+static int lg_count = 0, lg_capacity = 0;
+
+static void lg_glBegin(GLenum mode)
+{
+ lg_mode = mode;
+ lg_in_begin = 1;
+ lg_used_color = lg_used_tc = lg_used_normal = 0;
+ lg_count = 0;
+}
+
+static void lg_glColor4f(GLfloat r, GLfloat g, GLfloat b, GLfloat a)
+{
+ lg_color[0] = r;
+ lg_color[1] = g;
+ lg_color[2] = b;
+ lg_color[3] = a;
+ if (lg_in_begin) {
+ lg_used_color = 1;
+ } else {
+ glColor4f(r, g, b, a);
+ }
+}
+
+static void lg_glColor3f(GLfloat r, GLfloat g, GLfloat b)
+{
+ lg_glColor4f(r, g, b, 1.0f);
+}
+
+static void lg_glTexCoord2f(GLfloat s, GLfloat t)
+{
+ lg_tc[0] = s;
+ lg_tc[1] = t;
+ if (lg_in_begin) {
+ lg_used_tc = 1;
+ }
+}
+
+static void lg_glNormal3f(GLfloat x, GLfloat y, GLfloat z)
+{
+ lg_normal[0] = x;
+ lg_normal[1] = y;
+ lg_normal[2] = z;
+ if (lg_in_begin) {
+ lg_used_normal = 1;
+ } else {
+ glNormal3f(x, y, z);
+ }
+}
+
+static void lg_glVertex3f(GLfloat x, GLfloat y, GLfloat z)
+{
+ if (!lg_in_begin) {
+ return;
+ }
+ if (lg_count == lg_capacity) {
+ lg_capacity = lg_capacity ? lg_capacity * 2 : 256;
+ lg_vertices = (lg_vertex*)realloc(lg_vertices, lg_capacity * sizeof(lg_vertex));
+ }
+ lg_vertex* v = &lg_vertices[lg_count++];
+ memcpy(v->color, lg_color, sizeof(lg_color));
+ memcpy(v->tc, lg_tc, sizeof(lg_tc));
+ memcpy(v->normal, lg_normal, sizeof(lg_normal));
+ v->pos[0] = x;
+ v->pos[1] = y;
+ v->pos[2] = z;
+}
+
+static void lg_glVertex2i(GLint x, GLint y)
+{
+ lg_glVertex3f((GLfloat)x, (GLfloat)y, 0.0f);
+}
+
+static void lg_glEnd(void)
+{
+ if (!lg_in_begin) {
+ return;
+ }
+ lg_in_begin = 0;
+ if (lg_count == 0) {
+ return;
+ }
+ glBegin(lg_mode);
+ for (int i = 0; i < lg_count; ++i) {
+ const lg_vertex* v = &lg_vertices[i];
+ if (lg_used_color) {
+ glColor4f(v->color[0], v->color[1], v->color[2], v->color[3]);
+ }
+ if (lg_used_tc) {
+ glTexCoord2f(v->tc[0], v->tc[1]);
+ }
+ if (lg_used_normal) {
+ glNormal3f(v->normal[0], v->normal[1], v->normal[2]);
+ }
+ glVertex3f(v->pos[0], v->pos[1], v->pos[2]);
+ }
+ glEnd();
+ if (lg_used_color) {
+ glColor4f(lg_color[0], lg_color[1], lg_color[2], lg_color[3]);
+ }
+}
+
+/*===========================================================================*/
+// Vertex arrays:
+
+// Only GL_T2F_C3F_V3F and GL_T2F_N3F_V3F, the two formats the game uses.
+static void lg_glInterleavedArrays(GLenum format, GLsizei stride, const void* pointer)
+{
+ const GLfloat* p = (const GLfloat*)pointer;
+ glTexCoordPointer(2, GL_FLOAT, stride, p);
+ glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+ if (format == GL_T2F_C3F_V3F) {
+ glColorPointer(3, GL_FLOAT, stride, p + 2);
+ glEnableClientState(GL_COLOR_ARRAY);
+ glDisableClientState(GL_NORMAL_ARRAY);
+ glVertexPointer(3, GL_FLOAT, stride, p + 5);
+ } else {
+ glNormalPointer(GL_FLOAT, stride, p + 2);
+ glEnableClientState(GL_NORMAL_ARRAY);
+ glDisableClientState(GL_COLOR_ARRAY);
+ glVertexPointer(3, GL_FLOAT, stride, p + 5);
+ }
+ glEnableClientState(GL_VERTEX_ARRAY);
+}
+
+/*===========================================================================*/
+// Textures:
+
+#ifndef GL_CLAMP
+#define GL_CLAMP 0x2900
+#endif
+#ifndef GL_GENERATE_MIPMAP
+#define GL_GENERATE_MIPMAP 0x8191
+#endif
+
+// WebGL 1 samples a texture whose sides are not powers of two as black unless it has no mipmaps and is
+// clamped, so those textures (the 40x40 snowflake, for one) are forced to GL_LINEAR and GL_CLAMP_TO_EDGE.
+#define LG_TEXTURES 4096
+static unsigned char lg_wants_mipmaps[LG_TEXTURES];
+static unsigned char lg_npot[LG_TEXTURES];
+static GLuint lg_bound = 0;
+
+static void lg_glBindTexture(GLenum target, GLuint texture)
+{
+ glBindTexture(target, texture);
+ if (target == GL_TEXTURE_2D) {
+ lg_bound = texture;
+ }
+}
+
+static void lg_glTexParameteri(GLenum target, GLenum pname, GLint param)
+{
+ if (pname == GL_GENERATE_MIPMAP) {
+ if (lg_bound < LG_TEXTURES) {
+ lg_wants_mipmaps[lg_bound] = param ? 1 : 0;
+ }
+ return;
+ }
+ if ((pname == GL_TEXTURE_WRAP_S || pname == GL_TEXTURE_WRAP_T) && param == GL_CLAMP) {
+ param = GL_CLAMP_TO_EDGE;
+ }
+ if (target == GL_TEXTURE_2D && lg_bound < LG_TEXTURES && lg_npot[lg_bound]) {
+ if (pname == GL_TEXTURE_MIN_FILTER) {
+ param = GL_LINEAR;
+ } else if (pname == GL_TEXTURE_WRAP_S || pname == GL_TEXTURE_WRAP_T) {
+ param = GL_CLAMP_TO_EDGE;
+ }
+ }
+ glTexParameteri(target, pname, param);
+}
+
+static void lg_glTexParameterf(GLenum target, GLenum pname, GLfloat param)
+{
+ lg_glTexParameteri(target, pname, (GLint)param);
+}
+
+static void lg_glTexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width,
+ GLsizei height, GLint border, GLenum format, GLenum type, const void* pixels)
+{
+ // WebGL wants the internal format equal to the format
+ glTexImage2D(target, level, (GLint)format, width, height, border, format, type, pixels);
+ if (target == GL_TEXTURE_2D && level == 0 && lg_bound < LG_TEXTURES) {
+ lg_npot[lg_bound] = ((width & (width - 1)) || (height & (height - 1))) ? 1 : 0;
+ if (lg_npot[lg_bound]) {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ } else if (lg_wants_mipmaps[lg_bound] && pixels != NULL) {
+ glGenerateMipmap(GL_TEXTURE_2D);
+ }
+ }
+}
+
+// The emulation implements only the modes of the fixed pipeline that WebGL shaders can reproduce
+static void lg_glTexEnvi(GLenum target, GLenum pname, GLint param)
+{
+ if (target != GL_TEXTURE_ENV || pname != GL_TEXTURE_ENV_MODE) {
+ return;
+ }
+ if (param != GL_MODULATE && param != GL_REPLACE && param != GL_DECAL && param != GL_BLEND) {
+ param = GL_MODULATE;
+ }
+ glTexEnvi(target, pname, param);
+}
+
+static void lg_glTexEnvf(GLenum target, GLenum pname, GLfloat param)
+{
+ lg_glTexEnvi(target, pname, (GLint)param);
+}
+
+static void lg_glTexEnvfv(GLenum, GLenum, const GLfloat*) { }
+static void lg_glPixelTransferi(GLenum, GLint) { }
+static void lg_glDrawBuffer(GLenum) { }
+static void lg_glReadBuffer(GLenum) { }
+static void lg_glHint(GLenum, GLenum) { }
+static void lg_glTranslated(GLdouble x, GLdouble y, GLdouble z)
+{
+ glTranslatef((GLfloat)x, (GLfloat)y, (GLfloat)z);
+}
+
+#define glBegin lg_glBegin
+#define glEnd lg_glEnd
+#define glColor4f lg_glColor4f
+#define glColor3f lg_glColor3f
+#define glTexCoord2f lg_glTexCoord2f
+#define glNormal3f lg_glNormal3f
+#define glVertex3f lg_glVertex3f
+#define glVertex2i lg_glVertex2i
+#define glInterleavedArrays lg_glInterleavedArrays
+#define glBindTexture lg_glBindTexture
+#define glTexParameteri lg_glTexParameteri
+#define glTexParameterf lg_glTexParameterf
+#define glTexImage2D lg_glTexImage2D
+#define glTexEnvi lg_glTexEnvi
+#define glTexEnvf lg_glTexEnvf
+#define glTexEnvfv lg_glTexEnvfv
+#define glPixelTransferi lg_glPixelTransferi
+#define glDrawBuffer lg_glDrawBuffer
+#define glReadBuffer lg_glReadBuffer
+#define glHint lg_glHint
+#define glTranslated lg_glTranslated
+
+#endif
diff --git a/Source/Menu/Menu.cpp b/Source/Menu/Menu.cpp
index 253b764..b41354e 100644
--- a/Source/Menu/Menu.cpp
+++ b/Source/Menu/Menu.cpp
@@ -381,12 +381,20 @@ void Menu::Load()
addImage(0, Mainmenuitems[0], 150, 480 - 128, 256, 128);
addButtonImage(1, Mainmenuitems[mainmenu == 1 ? 1 : 5], 18, 480 - 152 - 32, 128, 32);
addButtonImage(2, Mainmenuitems[2], 18, 480 - 228 - 32, 112, 32);
+#ifdef __EMSCRIPTEN__ // nothing to quit to in a browser tab; "End game" stays
+ if (mainmenu != 1) {
+#endif
addButtonImage(3, Mainmenuitems[mainmenu == 1 ? 3 : 6], 18, 480 - 306 - 32, mainmenu == 1 ? 68 : 132, 32);
+#ifdef __EMSCRIPTEN__
+ }
+#endif
addLabel(-1, VERSION_NUMBER + VERSION_SUFFIX, 640 - 100, 10);
break;
case 3:
+#ifndef __EMSCRIPTEN__ // the canvas has a fixed size and the page has the fullscreen button
addButton(0, "", 10 + 20, 440);
addButton(14, "", 10 + 400, 440);
+#endif
addButton(1, "", 10 + 60, 405);
addButton(2, "", 10 + 70, 370);
addButton(4, "", 10, 335);
@@ -397,7 +405,9 @@ void Menu::Load()
addButton(11, "", 10 + 60, 160);
addButton(13, "", 30, 125);
addButton(7, "-Configure Controls-", 10 + 15, 90);
+#ifndef __EMSCRIPTEN__ // anaglyph and side by side 3D modes need more than the browser's OpenGL offers
addButton(12, "-Configure Stereo -", 10 + 15, 55);
+#endif
addButton(8, "Back", 10, 10);
updateSettingsMenu();
break;
@@ -951,23 +961,26 @@ void Menu::Tick()
oldmainmenu = mainmenu;
}
-int setKeySelected_thread(void*)
+// Called by the main loop for each event while the game waits for the key to assign (it was a thread
+// blocked in SDL_WaitEvent, which the browser has no room for). Returns true once the key is chosen.
+bool menuCapturingKey = false;
+
+bool Menu::keySelectedEvent(const SDL_Event& evenement)
{
using namespace Game;
int scancode = -1;
- SDL_Event evenement;
- while (scancode == -1) {
- SDL_WaitEvent(&evenement);
- switch (evenement.type) {
- case SDL_KEYDOWN:
- scancode = evenement.key.keysym.scancode;
- break;
- case SDL_MOUSEBUTTONDOWN:
- scancode = SDL_NUM_SCANCODES + evenement.button.button;
- break;
- default:
- break;
- }
+ switch (evenement.type) {
+ case SDL_KEYDOWN:
+ scancode = evenement.key.keysym.scancode;
+ break;
+ case SDL_MOUSEBUTTONDOWN:
+ scancode = SDL_NUM_SCANCODES + evenement.button.button;
+ break;
+ default:
+ break;
+ }
+ if (scancode == -1) {
+ return false;
}
if (scancode != SDL_SCANCODE_ESCAPE) {
fireSound();
@@ -1008,18 +1021,13 @@ int setKeySelected_thread(void*)
}
keyselect = -1;
waiting = false;
+ menuCapturingKey = false;
Menu::Load();
- return 0;
+ return true;
}
void Menu::setKeySelected()
{
waiting = true;
- printf("launch thread\n");
- SDL_Thread* thread = SDL_CreateThread(setKeySelected_thread, NULL, NULL);
- if (thread == NULL) {
- fprintf(stderr, "Unable to create thread: %s\n", SDL_GetError());
- waiting = false;
- return;
- }
+ menuCapturingKey = true;
}
diff --git a/Source/Menu/Menu.hpp b/Source/Menu/Menu.hpp
index a3ce2fe..3f121a5 100644
--- a/Source/Menu/Menu.hpp
+++ b/Source/Menu/Menu.hpp
@@ -23,6 +23,8 @@ along with Lugaru. If not, see .
#include "Game.hpp"
+extern bool menuCapturingKey;
+
struct MenuItem
{
enum MenuItemType
@@ -73,6 +75,7 @@ public:
static void updateStereoConfigMenu();
static void updateControlsMenu();
static void setKeySelected();
+ static bool keySelectedEvent(const SDL_Event& e);
static void startChallengeLevel(int selected);
private:
diff --git a/Source/main.cpp b/Source/main.cpp
index 4e94243..3d176c3 100644
--- a/Source/main.cpp
+++ b/Source/main.cpp
@@ -34,6 +34,10 @@ along with Lugaru. If not, see .
#include
#include
#include
+#ifdef __EMSCRIPTEN__
+#include
+#include
+#endif
using namespace Game;
@@ -196,13 +200,26 @@ bool SetUp()
SaveSettings();
}
+#ifdef __EMSCRIPTEN__
+ // The canvas has one fixed size, which the page scales to the window; no resolutions, no fullscreen
+ // of our own (the page has the button).
+ newscreenwidth = kContextWidth = 1024;
+ newscreenheight = kContextHeight = 768;
+ fullscreen = 0;
+#endif
+
if (SDL_GL_LoadLibrary(NULL) == -1) {
fprintf(stderr, "SDL_GL_LoadLibrary() failed: %s\n", SDL_GetError());
SDL_Quit();
return false;
}
+#ifdef __EMSCRIPTEN__
+ resolutions.insert(pair(kContextWidth, kContextHeight));
+ for (int displayIdx = 0; displayIdx < 0; ++displayIdx) {
+#else
for (int displayIdx = 0; displayIdx < SDL_GetNumVideoDisplays(); ++displayIdx) {
+#endif
for (int i = 0; i < SDL_GetNumDisplayModes(displayIdx); ++i) {
SDL_DisplayMode mode;
if (SDL_GetDisplayMode(displayIdx, i, &mode) == -1) {
@@ -322,6 +339,17 @@ static void DoMouse()
{
if (mainmenu || ((abs(deltah) < 10 * realmultiplier * 1000) && (abs(deltav) < 10 * realmultiplier * 1000))) {
+#ifdef __EMSCRIPTEN__
+ if (mainmenu) {
+ // The page scales the canvas to the window: in the menus the cursor must follow the mouse
+ // on the screen, so the motion (in screen pixels) is converted to canvas pixels.
+ double cssWidth = 0, cssHeight = 0;
+ if (emscripten_get_element_css_size("#canvas", &cssWidth, &cssHeight) == 0 && cssWidth > 0) {
+ deltah *= kContextWidth / cssWidth;
+ deltav *= kContextHeight / cssHeight;
+ }
+ }
+#endif
deltah *= usermousesensitivity;
deltav *= usermousesensitivity;
mousecoordh += deltah;
@@ -611,6 +639,41 @@ const option::Descriptor usage[] =
option::Option commandLineOptions[commandLineOptionsNumber];
option::Option* commandLineOptionsBuffer;
+#ifdef __EMSCRIPTEN__
+// One iteration of the game loop, run by the browser for each animation frame (the original loop
+// blocked in SDL_WaitEvent when the window lost the focus, which the browser has no room for; here
+// the game keeps its frame and the page decides when to call it).
+static void browserFrame()
+{
+ try {
+ deltah = 0;
+ deltav = 0;
+ SDL_Event e;
+ bool done = false;
+ if (!waiting || menuCapturingKey) {
+ while (SDL_PollEvent(&e)) {
+ if (!sdlEventProc(e)) {
+ done = true;
+ break;
+ }
+ if (menuCapturingKey && Menu::keySelectedEvent(e)) {
+ break;
+ }
+ }
+ }
+ if (!done && !tryquit) {
+ DoUpdate();
+ }
+ if (done || tryquit) {
+ emscripten_cancel_main_loop();
+ }
+ } catch (const std::exception& error) {
+ LOG(std::string("Caught exception: ") + error.what());
+ emscripten_cancel_main_loop();
+ }
+}
+#endif
+
int main(int argc, char** argv)
{
argc -= (argc > 0);
@@ -680,6 +743,22 @@ int main(int argc, char** argv)
devtools = true;
}
+#ifdef __EMSCRIPTEN__
+ srand(time(nullptr));
+
+ if (commandLineOptions[CMD].count() > 0) {
+ devtools = true;
+ Menu::startChallengeLevel(1);
+ for (option::Option* opt = commandLineOptions[CMD]; opt; opt = opt->next()) {
+ if (opt->arg && (strlen(opt->arg) > 0)) {
+ cmd_dispatch(opt->arg);
+ }
+ }
+ }
+
+ // The browser calls one frame at a time; main never returns
+ emscripten_set_main_loop(browserFrame, 0, 1);
+#else
bool gameDone = false;
bool gameFocused = true;
@@ -728,6 +807,7 @@ int main(int argc, char** argv)
}
}
+#endif
deleteGame();
}