diff --git a/display.cpp b/display.cpp index 866c42d..a2db946 100644 --- a/display.cpp +++ b/display.cpp @@ -43,7 +43,10 @@ bool displayClass::init(bool fullscreen, int displayToUseIn) // Get current display mode of all displays. numOfDisplays = SDL_GetNumVideoDisplays(); - SDL_Rect displayBounds[numOfDisplays]={0}; + // clang refuses to initialize a variable-length array; zero it by hand (and keep at least one). + SDL_Rect displayBounds[numOfDisplays > 0 ? numOfDisplays : 1]; + for(int i = 0; i < (numOfDisplays > 0 ? numOfDisplays : 1); ++i) + displayBounds[i].x = displayBounds[i].y = displayBounds[i].w = displayBounds[i].h = 0; for(int i = 0; i < numOfDisplays; ++i){ @@ -74,6 +77,12 @@ bool displayClass::init(bool fullscreen, int displayToUseIn) displayToUse = 0; } +#ifdef __EMSCRIPTEN__ + // In the browser the "display" is the canvas: a fixed 4:3 image that the page scales to fit. + SDL_videomodeSettings = SDL_WINDOW_OPENGL; + displayBounds[displayToUse].w = 1024; + displayBounds[displayToUse].h = 768; +#endif currentW = displayBounds[displayToUse].w; currentH = displayBounds[displayToUse].h; diff --git a/fredygames-gl.h b/fredygames-gl.h new file mode 100644 index 0000000..55ff795 --- /dev/null +++ b/fredygames-gl.h @@ -0,0 +1,206 @@ +/* + * fredygames-gl.h: what the WebAssembly build of SDL-Ball needs on top of the OpenGL emulation of + * Emscripten (-sLEGACY_GL_EMULATION). + * + * - glBegin/glEnd: the emulation stores the attributes of each vertex in the order they are called and + * expects every vertex to carry the same ones, while desktop OpenGL keeps a current color and + * texture coordinate. SDL-Ball often sets a color for some vertices of a quad only, so the vertices + * are collected here with the current color and texture coordinate and handed over complete at + * glEnd. A color set inside glBegin/glEnd stays current afterwards, as in OpenGL. + * - Display lists, which the emulation lacks: glNewList records the calls SDL-Ball puts in its lists + * (glBindTexture, glBegin, glEnd, glTexCoord2f, glVertex3f, glColor4f, glLoadIdentity, + * glTranslatef, glEnable) and glCallList replays them. The game only uses GL_COMPILE. + * - gluBuild2DMipmaps (the font textures), also missing, becomes one glTexImage2D with linear + * filtering. + * - GL_CLAMP does not exist in WebGL; GL_CLAMP_TO_EDGE is what the game means. + * + * Included by main.cpp after the GL headers, only when building with Emscripten. + * + * Copyright 2026 FredyGames. Part of the SDL-Ball build for fredygames.com; distributed with + * SDL-Ball under the GNU General Public License, version 3 or (at your option) any later version. + */ +#ifndef FREDYGAMES_GL_H +#define FREDYGAMES_GL_H + +#include +#include + +/*===========================================================================*/ +// Immediate mode: + +struct fg_vertex { GLfloat color[4], tc[2], pos[3]; }; + +static GLfloat fg_color[4] = { 1, 1, 1, 1 }; +static GLfloat fg_tc[2] = { 0, 0 }; +static GLenum fg_mode; +static int fg_in_begin = 0, fg_used_color = 0, fg_used_tc = 0; +static fg_vertex *fg_vertices = NULL; +static int fg_count = 0, fg_capacity = 0; + +static void fg_begin(GLenum mode) { + fg_mode = mode; + fg_in_begin = 1; + fg_used_color = fg_used_tc = 0; + fg_count = 0; +} + +static void fg_color4f(GLfloat r, GLfloat g, GLfloat b, GLfloat a) { + fg_color[0] = r; fg_color[1] = g; fg_color[2] = b; fg_color[3] = a; + if (fg_in_begin) fg_used_color = 1; else glColor4f(r, g, b, a); +} + +static void fg_texcoord2f(GLfloat s, GLfloat t) { + fg_tc[0] = s; fg_tc[1] = t; + if (fg_in_begin) fg_used_tc = 1; +} + +static void fg_vertex3f(GLfloat x, GLfloat y, GLfloat z) { + if (!fg_in_begin) return; + if (fg_count == fg_capacity) { + fg_capacity = fg_capacity ? fg_capacity * 2 : 256; + fg_vertices = (fg_vertex *)realloc(fg_vertices, fg_capacity * sizeof(fg_vertex)); + } + fg_vertex *v = &fg_vertices[fg_count++]; + for (int i = 0; i < 4; ++i) v->color[i] = fg_color[i]; + v->tc[0] = fg_tc[0]; v->tc[1] = fg_tc[1]; + v->pos[0] = x; v->pos[1] = y; v->pos[2] = z; +} + +static void fg_end(void) { + if (!fg_in_begin) return; + fg_in_begin = 0; + if (fg_count == 0) return; + glBegin(fg_mode); + for (int i = 0; i < fg_count; ++i) { + const fg_vertex *v = &fg_vertices[i]; + if (fg_used_color) glColor4f(v->color[0], v->color[1], v->color[2], v->color[3]); + if (fg_used_tc) glTexCoord2f(v->tc[0], v->tc[1]); + glVertex3f(v->pos[0], v->pos[1], v->pos[2]); + } + glEnd(); + if (fg_used_color) glColor4f(fg_color[0], fg_color[1], fg_color[2], fg_color[3]); +} + +/*===========================================================================*/ +// Display lists: + +enum { FG_BIND, FG_BEGIN, FG_END, FG_TEXCOORD, FG_VERTEX, FG_COLOR, FG_IDENTITY, FG_TRANSLATE, + FG_ENABLE }; + +struct fg_call { int op; unsigned int e; GLfloat v[4]; }; +struct fg_list { fg_call *calls; int count, capacity; }; + +static fg_list *fg_lists = NULL; +static GLuint fg_num_lists = 0; +static fg_list *fg_recording = NULL; + +static void fg_record(int op, unsigned int e, GLfloat a, GLfloat b, GLfloat c, GLfloat d) { + fg_list *l = fg_recording; + if (l->count == l->capacity) { + l->capacity = l->capacity ? l->capacity * 2 : 32; + l->calls = (fg_call *)realloc(l->calls, l->capacity * sizeof(fg_call)); + } + fg_call *call = &l->calls[l->count++]; + call->op = op; call->e = e; + call->v[0] = a; call->v[1] = b; call->v[2] = c; call->v[3] = d; +} + +static GLuint fg_glGenLists(GLsizei range) { + GLuint first = fg_num_lists + 1; + fg_num_lists += range; + fg_lists = (fg_list *)realloc(fg_lists, (fg_num_lists + 1) * sizeof(fg_list)); + for (GLuint i = first; i <= fg_num_lists; ++i) { + fg_lists[i].calls = NULL; fg_lists[i].count = fg_lists[i].capacity = 0; + } + return first; +} + +static void fg_glNewList(GLuint list, GLenum mode) { + (void)mode; + if (list == 0 || list > fg_num_lists) return; + fg_recording = &fg_lists[list]; + fg_recording->count = 0; +} + +static void fg_glEndList(void) { fg_recording = NULL; } + +static void fg_glBindTexture(GLenum target, GLuint texture) { + if (fg_recording) fg_record(FG_BIND, texture, 0, 0, 0, 0); else glBindTexture(target, texture); +} +static void fg_glBegin(GLenum mode) { + if (fg_recording) fg_record(FG_BEGIN, mode, 0, 0, 0, 0); else fg_begin(mode); +} +static void fg_glEnd(void) { + if (fg_recording) fg_record(FG_END, 0, 0, 0, 0, 0); else fg_end(); +} +static void fg_glTexCoord2f(GLfloat s, GLfloat t) { + if (fg_recording) fg_record(FG_TEXCOORD, 0, s, t, 0, 0); else fg_texcoord2f(s, t); +} +static void fg_glVertex3f(GLfloat x, GLfloat y, GLfloat z) { + if (fg_recording) fg_record(FG_VERTEX, 0, x, y, z, 0); else fg_vertex3f(x, y, z); +} +static void fg_glColor4f(GLfloat r, GLfloat g, GLfloat b, GLfloat a) { + if (fg_recording) fg_record(FG_COLOR, 0, r, g, b, a); else fg_color4f(r, g, b, a); +} +static void fg_glColor3d(GLdouble r, GLdouble g, GLdouble b) { + fg_glColor4f((GLfloat)r, (GLfloat)g, (GLfloat)b, 1.0f); +} +static void fg_glLoadIdentity(void) { + if (fg_recording) fg_record(FG_IDENTITY, 0, 0, 0, 0, 0); else glLoadIdentity(); +} +static void fg_glTranslatef(GLfloat x, GLfloat y, GLfloat z) { + if (fg_recording) fg_record(FG_TRANSLATE, 0, x, y, z, 0); else glTranslatef(x, y, z); +} +static void fg_glEnable(GLenum cap) { + if (fg_recording) fg_record(FG_ENABLE, cap, 0, 0, 0, 0); else glEnable(cap); +} + +static void fg_glCallList(GLuint list) { + if (list == 0 || list > fg_num_lists) return; + const fg_list *l = &fg_lists[list]; + for (int i = 0; i < l->count; ++i) { + const fg_call *c = &l->calls[i]; + switch (c->op) { + case FG_BIND: glBindTexture(GL_TEXTURE_2D, c->e); break; + case FG_BEGIN: fg_begin(c->e); break; + case FG_END: fg_end(); break; + case FG_TEXCOORD: fg_texcoord2f(c->v[0], c->v[1]); break; + case FG_VERTEX: fg_vertex3f(c->v[0], c->v[1], c->v[2]); break; + case FG_COLOR: fg_color4f(c->v[0], c->v[1], c->v[2], c->v[3]); break; + case FG_IDENTITY: glLoadIdentity(); break; + case FG_TRANSLATE: glTranslatef(c->v[0], c->v[1], c->v[2]); break; + case FG_ENABLE: glEnable(c->e); break; + } + } +} + +/*===========================================================================*/ +// Textures: + +static GLint fg_gluBuild2DMipmaps(GLenum target, GLint internalFormat, GLsizei width, + GLsizei height, GLenum format, GLenum type, const void *data) { + glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexImage2D(target, 0, internalFormat, width, height, 0, format, type, data); + return 0; +} + +#define glGenLists fg_glGenLists +#define glNewList fg_glNewList +#define glEndList fg_glEndList +#define glCallList fg_glCallList +#define glBindTexture fg_glBindTexture +#define glBegin fg_glBegin +#define glEnd fg_glEnd +#define glTexCoord2f fg_glTexCoord2f +#define glVertex3f fg_glVertex3f +#define glColor4f fg_glColor4f +#define glColor3d fg_glColor3d +#define glLoadIdentity fg_glLoadIdentity +#define glTranslatef fg_glTranslatef +#define glEnable fg_glEnable +#define gluBuild2DMipmaps fg_gluBuild2DMipmaps +#undef GL_CLAMP +#define GL_CLAMP GL_CLAMP_TO_EDGE + +#endif diff --git a/highscores.cpp b/highscores.cpp index a15df30..353fa05 100644 --- a/highscores.cpp +++ b/highscores.cpp @@ -70,8 +70,8 @@ class highScoreClass { { if(name.length() > 0) name.erase(name.length()-1); - } else { - name += e.key.keysym.sym; + } else if(e.key.keysym.sym >= 32 && e.key.keysym.sym < 127) { + name += e.key.keysym.sym; // SDL 2 key codes of arrows, Shift etc. are not characters } } else { var.showHighScores = 0; diff --git a/input.cpp b/input.cpp index fda49a4..8dc9182 100644 --- a/input.cpp +++ b/input.cpp @@ -149,9 +149,10 @@ bool controllerClass::get() shotTime += globalTicks; SDL_PumpEvents(); keyStates = SDL_GetKeyboardState(NULL); - keyDown[0] = keyStates[setting.keyLeft]; - keyDown[1] = keyStates[setting.keyRight]; - keyDown[2] = keyStates[setting.keyShoot]; + // SDL 2 indexes the keyboard state by scancode, not by key code. + keyDown[0] = keyStates[SDL_GetScancodeFromKey(setting.keyLeft)]; + keyDown[1] = keyStates[SDL_GetScancodeFromKey(setting.keyRight)]; + keyDown[2] = keyStates[SDL_GetScancodeFromKey(setting.keyShoot)]; itemSelectTime += globalTicks; //Read joystick here so we can override keypresses if the joystick is digital diff --git a/main.cpp b/main.cpp index ce3c9ea..007061f 100644 --- a/main.cpp +++ b/main.cpp @@ -35,6 +35,10 @@ #include #include "display.hpp" +#ifdef __EMSCRIPTEN__ + #include + #include "fredygames-gl.h" +#endif #include @@ -3657,9 +3661,9 @@ int main (int argc, char *argv[]) { setting.sound = 1; setting.stereo=1; //Defaults for keyboard/joystick control - setting.keyLeft = (SDL_Keycode)276; - setting.keyRight= (SDL_Keycode)275; - setting.keyShoot= (SDL_Keycode)273; + setting.keyLeft = SDLK_LEFT; // were 276, 275 and 273: the SDL 1.2 numbers of these keys + setting.keyRight= SDLK_RIGHT; + setting.keyShoot= SDLK_UP; setting.keyNextPo=(SDL_Keycode)SDLK_v; setting.keyBuyPo =(SDL_Keycode)SDLK_b; setting.keyPrevPo=(SDL_Keycode)SDLK_n; @@ -4472,13 +4476,16 @@ int main (int argc, char *argv[]) { var.paused ? resumeGame() : pauseGame(); } +#ifndef __EMSCRIPTEN__ // in the browser, no quitting by a stray key and no screenshot files if( sdlevent.key.keysym.sym == SDLK_q ) { var.quit=1; } else if( sdlevent.key.keysym.sym == SDLK_s ) { screenShot(); - } else if( sdlevent.key.keysym.sym == setting.keyNextPo) + } else +#endif + if( sdlevent.key.keysym.sym == setting.keyNextPo) { gVar.shopPrevItem=1; } else if( sdlevent.key.keysym.sym == setting.keyBuyPo) @@ -4536,7 +4543,7 @@ int main (int argc, char *argv[]) { resumeGame(); } } -#ifndef WIN32 +#if !defined(WIN32) && !defined(__EMSCRIPTEN__) else if( sdlevent.key.keysym.sym == SDLK_F11 ) { if(setting.fullscreen) @@ -4625,6 +4632,8 @@ int main (int argc, char *argv[]) { } #ifdef WIN32 Sleep( 1 ); +#elif defined(__EMSCRIPTEN__) + emscripten_sleep( 1 ); // gives the browser its turn (Asyncify): input, audio and the frame drawn #else usleep( 1000 ); #endif diff --git a/menu.cpp b/menu.cpp index 1de1f99..0a853ff 100644 --- a/menu.cpp +++ b/menu.cpp @@ -252,8 +252,8 @@ public: { if(saveGameName[saveGameSlot].length() > 0) saveGameName[saveGameSlot].erase(saveGameName[saveGameSlot].length()-1); - } else { - saveGameName[saveGameSlot] += e.key.keysym.sym; + } else if(e.key.keysym.sym >= 32 && e.key.keysym.sym < 127) { + saveGameName[saveGameSlot] += e.key.keysym.sym; // SDL 2 key codes of arrows, Shift etc. are not characters } } else { if(e.key.keysym.sym == SDLK_RETURN) diff --git a/themes/default/font/COPYING b/themes/default/font/COPYING index f859103..e292d9c 100644 --- a/themes/default/font/COPYING +++ b/themes/default/font/COPYING @@ -1,12 +1,12 @@ -Bandal.ttf - From debian package: ttf-alee_12_all.deb -Copyright 2006 (C) A Lee . All rights reserved. - Bandal.ttf are free; you can redistribute it and/or modify it - under the terms of the Artistic License; either version 1.0 of the - License, or (at your option) any later version. - See http://www.opensource.org/licenses/artistic-license-1.0.php for full license. +The fonts of this build of SDL-Ball (fredygames.com) replace the two fonts the game shipped +with (Bandal.ttf and subgamefont.ttf), which were removed from it. Both come unmodified from the +google/fonts repository and are licensed under the SIL Open Font License, Version 1.1: -subgamefont.ttf - From debian package: hannah-data_1.0-2.deb -License: this program is distribued under the terms of the GNU General - Public License, version 2. In Debian systems, you can find the full text - of the GNU General Public License at +LilitaOne-Regular.ttf - Lilita One, Copyright (c) 2011 Juan Montoreano (juan@remolacha.biz), + with Reserved Font Name Lilita. Used for the menus and the intro texts. +ChakraPetch-Medium.ttf - Chakra Petch, Copyright 2018 The Chakra Petch Project Authors + (https://github.com/m4rc1e/Chakra-Petch.git). Used for announcements and the highscores. + +The license of each font, as google/fonts publishes it, is next to it: OFL-LilitaOne.txt and +OFL-ChakraPetch.txt. diff --git a/themes/default/font/fonts.txt b/themes/default/font/fonts.txt index e7eb812..a2dc877 100644 --- a/themes/default/font/fonts.txt +++ b/themes/default/font/fonts.txt @@ -2,23 +2,23 @@ All fields are required. File - The case-sensitive filename of the font, relative to the theme root dir. Size - A whole number, the size that the TTF font is rendered. -menufile=font/subgamefont.ttf +menufile=font/LilitaOne-Regular.ttf menusize=30 -announcegoodfile=font/Bandal.ttf +announcegoodfile=font/ChakraPetch-Medium.ttf announcegoodsize=60 -announcebadfile=font/Bandal.ttf +announcebadfile=font/ChakraPetch-Medium.ttf announcebadsize=60 -highscorefile=font/Bandal.ttf +highscorefile=font/ChakraPetch-Medium.ttf highscoresize=120 -menuhighscorefile=font/subgamefont.ttf +menuhighscorefile=font/LilitaOne-Regular.ttf menuhighscoresize=25 -introhighscorefile=font/Bandal.ttf +introhighscorefile=font/ChakraPetch-Medium.ttf introhighscoresize=40 -introdescriptionfile=font/subgamefont.ttf +introdescriptionfile=font/LilitaOne-Regular.ttf introdescriptionsize=20