SDL2 support + emscripten

This commit is contained in:
lemon-sherbet 2020-09-01 16:50:56 +02:00
parent 1e5a105036
commit b842059480
8 changed files with 432 additions and 153 deletions

1
.gitignore vendored
View File

@ -9,4 +9,5 @@ build/
*.elf
*.smdh
win-build/
em-build/
ccleste-win.zip

View File

@ -1,5 +1,21 @@
CFLAGS=-Wall -g -O2
LDFLAGS=`sdl-config --cflags --libs` -lSDL_mixer
# set to 1 to use SDL1.2
SDL_VER?=2
ifeq ($(SDL_VER),2)
SDL_CONFIG=sdl2-config
SDL_LD=-lSDL2 -lSDL2_mixer
else
ifeq ($(SDL_VER),1)
SDL_CONFIG=sdl-config
SDL_LD=-lSDL -lSDL_mixer
else
SDL_CONFIG=$(error "invalid SDL version '$(SDL_VER)'. possible values are '1' and '2'")
endif
endif
CFLAGS=-Wall -g -O2 `$(SDL_CONFIG) --cflags`
LDFLAGS=$(SDL_LD)
CELESTE_CC=$(CC)
ifneq ($(USE_FIXEDP),)

View File

@ -86,7 +86,7 @@ export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
export DEPSDIR := $(CURDIR)/$(BUILD)
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
CFILES := sdl12main.c celeste.c
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
PICAFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.v.pica)))

View File

@ -6,6 +6,8 @@ This is a C source port of the [original celeste (Celeste classic)](https://www.
Go to [the releases tab](https://github.com/lemon-sherbet/ccleste/releases) for the latest pre-built binaries.
An experimental web port is also available [here](https://lemon-sherbet.github.io/ccleste.html).
celeste.c + celeste.h is where the game code is, translated from the pico 8 lua code by hand.
These files don't depend on anything other than the c standard library and don't perform any allocations (it uses its own internal global state).

9
embuild.sh Executable file
View File

@ -0,0 +1,9 @@
#!/bin/sh
CFLAGS="-Wall -O2"
EMFLAGS="-s ALLOW_MEMORY_GROWTH=1 -s USE_SDL=2 -s USE_SDL_MIXER=2 -s USE_OGG=1"
OUTDIR="em-build/"
mkdir -p $OUTDIR
emcc $CFLAGS $EMFLAGS sdl12main.c -c -o $OUTDIR/sdl12main.wasm
emcc $CFLAGS $EMFLAGS celeste.c -c -o $OUTDIR/celeste.wasm
emcc $EMFLAGS --shell-file emscripten-shell.html --preload-file data/ $OUTDIR/sdl12main.wasm $OUTDIR/celeste.wasm -o $OUTDIR/ccleste.html

162
emscripten-shell.html Normal file
View File

@ -0,0 +1,162 @@
<!doctype html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>ccleste (experimental emscripten build)</title>
<style>
.emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; }
textarea.emscripten { font-family: monospace; width: 80%; }
div.emscripten { text-align: center; }
div.emscripten_border { border: 1px solid black; }
/* the canvas *must not* have any border or padding, or mouse coords will be wrong */
canvas.emscripten { border: 0px none; background-color: black; }
.spinner {
height: 50px;
width: 50px;
margin: 0px auto;
-webkit-animation: rotation .8s linear infinite;
-moz-animation: rotation .8s linear infinite;
-o-animation: rotation .8s linear infinite;
animation: rotation 0.8s linear infinite;
border-left: 10px solid rgb(0,150,240);
border-right: 10px solid rgb(0,150,240);
border-bottom: 10px solid rgb(0,150,240);
border-top: 10px solid rgb(100,0,200);
border-radius: 100%;
background-color: rgb(200,100,250);
}
@-webkit-keyframes rotation {
from {-webkit-transform: rotate(0deg);}
to {-webkit-transform: rotate(360deg);}
}
@-moz-keyframes rotation {
from {-moz-transform: rotate(0deg);}
to {-moz-transform: rotate(360deg);}
}
@-o-keyframes rotation {
from {-o-transform: rotate(0deg);}
to {-o-transform: rotate(360deg);}
}
@keyframes rotation {
from {transform: rotate(0deg);}
to {transform: rotate(360deg);}
}
</style>
</head>
<body>
<hr/>
<div class="emscripten">
<p>Experimental <a href="https://emscripten.org/">emscripten</a> build of <a href="https://github.com/lemon-sherbet/ccleste">ccleste</a>. <br>
<ul>
<li>Jump with Z/C</li>
<li>Dash with X/V</li>
<li>Move with arrow keys</li>
<li>Escape to pause</li>
<li>Shift+S to save state</li>
<li>Shift+D to load state</li>
<li>Hold F9 to reset</li>
</ul>
</p>
</div>
<figure style="overflow:visible;" id="spinner"><div class="spinner"></div><center style="margin-top:0.5em"><strong>emscripten</strong></center></figure>
<div class="emscripten" id="status">Downloading...</div>
<div class="emscripten">
<progress value="0" max="100" id="progress" hidden=1></progress>
</div>
<div class="emscripten_border">
<canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault()" tabindex=-1></canvas>
</div>
<hr/>
<div class="emscripten">
<input type="checkbox" id="resize">Resize canvas
<input type="checkbox" id="pointerLock" checked>Lock/hide mouse pointer
&nbsp;&nbsp;&nbsp;
<!--input type="button" value="Fullscreen" onclick="Module.requestFullscreen(document.getElementById('pointerLock').checked,
document.getElementById('resize').checked)" !-->
</div>
<hr/>
<textarea class="emscripten" id="output" rows="8"></textarea>
<hr>
<script type='text/javascript'>
var statusElement = document.getElementById('status');
var progressElement = document.getElementById('progress');
var spinnerElement = document.getElementById('spinner');
var Module = {
preRun: [],
postRun: [],
print: (function() {
var element = document.getElementById('output');
if (element) element.value = ''; // clear browser cache
return function(text) {
if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
// These replacements are necessary if you render to raw HTML
//text = text.replace(/&/g, "&amp;");
//text = text.replace(/</g, "&lt;");
//text = text.replace(/>/g, "&gt;");
//text = text.replace('\n', '<br>', 'g');
console.log(text);
if (element) {
element.value += text + "\n";
element.scrollTop = element.scrollHeight; // focus on bottom
}
};
})(),
printErr: function(text) {
if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
console.error(text);
},
canvas: (function() {
var canvas = document.getElementById('canvas');
// As a default initial behavior, pop up an alert when webgl context is lost. To make your
// application robust, you may want to override this behavior before shipping!
// See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2
canvas.addEventListener("webglcontextlost", function(e) { alert('WebGL context lost. You will need to reload the page.'); e.preventDefault(); }, false);
return canvas;
})(),
setStatus: function(text) {
if (!Module.setStatus.last) Module.setStatus.last = { time: Date.now(), text: '' };
if (text === Module.setStatus.last.text) return;
var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
var now = Date.now();
if (m && now - Module.setStatus.last.time < 30) return; // if this is a progress update, skip it if too soon
Module.setStatus.last.time = now;
Module.setStatus.last.text = text;
if (m) {
text = m[1];
progressElement.value = parseInt(m[2])*100;
progressElement.max = parseInt(m[4])*100;
progressElement.hidden = false;
spinnerElement.hidden = false;
} else {
progressElement.value = null;
progressElement.max = null;
progressElement.hidden = true;
if (!text) spinnerElement.hidden = true;
}
statusElement.innerHTML = text;
},
totalDependencies: 0,
monitorRunDependencies: function(left) {
this.totalDependencies = Math.max(this.totalDependencies, left);
Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies-left) + '/' + this.totalDependencies + ')' : 'All downloads complete.');
}
};
Module.setStatus('Downloading...');
window.onerror = function() {
Module.setStatus('Exception thrown, see JavaScript console');
spinnerElement.style.display = 'none';
Module.setStatus = function(text) {
if (text) Module.printErr('[post-exception status] ' + text);
};
};
</script>
{{{ SCRIPT }}}
</body>
</html>

View File

@ -1,5 +1,8 @@
#include <SDL.h>
#include <SDL_mixer.h>
#if SDL_MAJOR_VERSION >= 2
#include "sdl20compat.inc.c"
#endif
#include <math.h>
#include <stdarg.h>
#include <stdio.h>
@ -160,9 +163,9 @@ static void LoadData(void) {
loadbmpscale("font.bmp", &font);
LOGDONE();
static signed char sndids[] = {0,1,2,3,4,5,6,7,8,9,13,14,15,16,23,35,37,38,40,50,51,54,55, -1};
for (signed char* iid = sndids; *iid != -1; iid++) {
int id = *iid;
static const char sndids[] = {0,1,2,3,4,5,6,7,8,9,13,14,15,16,23,35,37,38,40,50,51,54,55};
for (int iid = 0; iid < sizeof sndids; iid++) {
int id = sndids[iid];
char fname[20];
sprintf(fname, "snd%i.wav", id);
char path[4096];
@ -174,9 +177,9 @@ static void LoadData(void) {
}
LOGDONE();
}
static signed char musids[] = {0,10,20,30,40, -1};
for (signed char* iid = musids; *iid != -1; iid++) {
int id = *iid;
static const char musids[] = {0,10,20,30,40};
for (int iid = 0; iid < sizeof musids; iid++) {
int id = musids[iid];
char fname[20];
sprintf(fname, "mus%i.ogg", id);
LOGLOAD(fname);
@ -229,6 +232,13 @@ static void OSDdraw(void) {
static Mix_Music* current_music = NULL;
static _Bool enable_screenshake = 1;
static _Bool paused = 0;
static _Bool running = 1;
static void* initial_game_state = NULL;
static void* game_state = NULL;
static Mix_Music* game_state_music = NULL;
static void mainLoop(void);
static FILE* TAS = NULL;
int main(int argc, char** argv) {
SDL_CHECK(SDL_Init(SDL_INIT_AUDIO | SDL_INIT_VIDEO) == 0);
@ -260,7 +270,6 @@ int main(int argc, char** argv) {
ResetPalette();
SDL_ShowCursor(0);
FILE* TAS = NULL;
if (argc > 1) {
TAS = fopen(argv[1], "r");
if (!TAS) {
@ -310,7 +319,7 @@ int main(int argc, char** argv) {
Celeste_P8_set_call_func(pico8emu);
//for reset
void* initial_game_state = SDL_malloc(Celeste_P8_get_state_size());
initial_game_state = SDL_malloc(Celeste_P8_get_state_size());
if (initial_game_state) Celeste_P8_save_state(initial_game_state);
if (TAS) {
@ -324,150 +333,15 @@ int main(int argc, char** argv) {
printf("ready\n");
void* game_state = NULL;
Mix_Music* game_state_music = NULL;
_Bool running = 1;
_Bool paused = 0;
while (running) {
Uint8* kbstate = SDL_GetKeyState(NULL);
static int reset_input_timer = 0;
//hold F9 (select+start+y) to reset
if (initial_game_state != NULL
#ifdef _3DS
&& kbstate[SDLK_LSHIFT] && kbstate[SDLK_ESCAPE] && kbstate[SDLK_F11]
#ifndef EMSCRIPTEN
while (running) mainLoop();
#else
&& kbstate[SDLK_F9]
#include <emscripten.h>
//FIXME: this assumes that the display refreshes at 60Hz
emscripten_set_main_loop(mainLoop, 0, 0);
emscripten_set_main_loop_timing(EM_TIMING_RAF, 2);
return 0;
#endif
) {
reset_input_timer++;
if (reset_input_timer >= 30) {
reset_input_timer=0;
//reset
OSDset("reset");
paused = 0;
Celeste_P8_load_state(initial_game_state);
Mix_HaltChannel(-1);
Mix_HaltMusic();
Celeste_P8_init();
}
} else reset_input_timer = 0;
SDL_Event ev;
while (SDL_PollEvent(&ev)) switch (ev.type) {
case SDL_QUIT: running = 0; break;
case SDL_KEYDOWN: {
/*if (ev.key.keysym.sym == SDLK_ESCAPE) {
running = 0;
break;
} else*/ if (ev.key.keysym.sym == SDLK_ESCAPE) { //do pause
if (paused) Mix_Resume(-1), Mix_ResumeMusic(); else Mix_Pause(-1), Mix_PauseMusic();
paused = !paused;
break;
} else if (ev.key.keysym.sym == SDLK_F11 && !(kbstate[SDLK_LSHIFT] || kbstate[SDLK_ESCAPE])) {
if (SDL_WM_ToggleFullScreen(screen)) { //this doesn't work on windows..
OSDset("toggle fullscreen");
}
break;
} else if (0 && ev.key.keysym.sym == SDLK_5) {
Celeste_P8__DEBUG();
break;
} else if (ev.key.keysym.sym == SDLK_s && kbstate[SDLK_LSHIFT]) { //save state
game_state = game_state ? game_state : SDL_malloc(Celeste_P8_get_state_size());
if (game_state) {
OSDset("save state");
Celeste_P8_save_state(game_state);
game_state_music = current_music;
}
break;
} else if (ev.key.keysym.sym == SDLK_d && kbstate[SDLK_LSHIFT]) { //load state
if (game_state) {
OSDset("load state");
if (paused) paused = 0, Mix_Resume(-1), Mix_ResumeMusic();
Celeste_P8_load_state(game_state);
if (current_music != game_state_music) {
Mix_HaltMusic();
current_music = game_state_music;
if (game_state_music) Mix_PlayMusic(game_state_music, -1);
}
}
break;
} else if ( //toggle screenshake (e / L+R)
#ifdef _3DS
(ev.key.keysym.sym == SDLK_d && kbstate[SDLK_s]) || (ev.key.keysym.sym == SDLK_s && kbstate[SDLK_d])
#else
ev.key.keysym.sym == SDLK_e
#endif
) {
enable_screenshake = !enable_screenshake;
OSDset("screenshake: %s", enable_screenshake ? "on" : "off");
}
//else: fallthrough
}
case SDL_KEYUP: {
int down = ev.type == SDL_KEYDOWN;
int b = -1;
switch (ev.key.keysym.sym) {
case SDLK_LEFT: b = 0; break;
case SDLK_RIGHT: b = 1; break;
case SDLK_UP: b = 2; break;
case SDLK_DOWN: b = 3; break;
case SDLK_z: case SDLK_c: case SDLK_n: case SDLK_a:
b = 4; break;
case SDLK_x: case SDLK_v: case SDLK_m: case SDLK_b:
b = 5; break;
default: break;
}
if (!TAS && b >= 0) {
if (down) buttons_state |= (1<<b);
else buttons_state &= ~(1<<b);
}
}
}
if (TAS && !paused) {
static int t = 0;
t++;
if (t==1) buttons_state = 1<<4;
else if (t > 80) {
fscanf(TAS, "%d,", &buttons_state);
} else buttons_state = 0;
}
if (paused) {
const int x0 = PICO8_W/2-3*4, y0 = 8;
p8_rectfill(x0-1,y0-1, 6*4+x0+1,6+y0+1, 6);
p8_rectfill(x0,y0, 6*4+x0,6+y0, 0);
p8_print("paused", x0+1, y0+1, 7);
} else {
Celeste_P8_update();
Celeste_P8_draw();
}
OSDdraw();
/*for (int i = 0 ; i < 16;i++) {
SDL_Rect rc = {i*8*scale, 0, 8*scale, 4*scale};
SDL_FillRect(screen, &rc, i);
}*/
SDL_Flip(screen);
#ifdef _3DS //using SDL_DOUBLEBUF for videomode makes it so SDL_Flip waits for Vsync; so we dont have to delay manually
SDL_Delay(1);
#else
static int t = 0;
static unsigned frame_start = 0;
unsigned frame_end = SDL_GetTicks();
unsigned frame_time = frame_end-frame_start;
static const unsigned target_millis = 1000/30;
if (frame_time < target_millis) {
SDL_Delay((target_millis - frame_time) + (t & 1));
}
t++;
frame_start = SDL_GetTicks();
#endif
}
if (game_state) SDL_free(game_state);
if (initial_game_state) SDL_free(initial_game_state);
@ -487,6 +361,148 @@ int main(int argc, char** argv) {
return 0;
}
static void mainLoop(void) {
const Uint8* kbstate = SDL_GetKeyState(NULL);
static int reset_input_timer = 0;
//hold F9 (select+start+y) to reset
if (initial_game_state != NULL
#ifdef _3DS
&& kbstate[SDLK_LSHIFT] && kbstate[SDLK_ESCAPE] && kbstate[SDLK_F11]
#else
&& kbstate[SDLK_F9]
#endif
) {
reset_input_timer++;
if (reset_input_timer >= 30) {
reset_input_timer=0;
//reset
OSDset("reset");
paused = 0;
Celeste_P8_load_state(initial_game_state);
Mix_HaltChannel(-1);
Mix_HaltMusic();
Celeste_P8_init();
}
} else reset_input_timer = 0;
SDL_Event ev;
while (SDL_PollEvent(&ev)) switch (ev.type) {
case SDL_QUIT: running = 0; break;
case SDL_KEYDOWN: {
/*if (ev.key.keysym.sym == SDLK_ESCAPE) {
running = 0;
break;
} else*/ if (ev.key.keysym.sym == SDLK_ESCAPE) { //do pause
if (paused) Mix_Resume(-1), Mix_ResumeMusic(); else Mix_Pause(-1), Mix_PauseMusic();
paused = !paused;
break;
} else if (ev.key.keysym.sym == SDLK_F11 && !(kbstate[SDLK_LSHIFT] || kbstate[SDLK_ESCAPE])) {
if (SDL_WM_ToggleFullScreen(screen)) { //this doesn't work on windows..
OSDset("toggle fullscreen");
}
screen = SDL_GetVideoSurface();
break;
} else if (0 && ev.key.keysym.sym == SDLK_5) {
Celeste_P8__DEBUG();
break;
} else if (ev.key.keysym.sym == SDLK_s && kbstate[SDLK_LSHIFT]) { //save state
game_state = game_state ? game_state : SDL_malloc(Celeste_P8_get_state_size());
if (game_state) {
OSDset("save state");
Celeste_P8_save_state(game_state);
game_state_music = current_music;
}
break;
} else if (ev.key.keysym.sym == SDLK_d && kbstate[SDLK_LSHIFT]) { //load state
if (game_state) {
OSDset("load state");
if (paused) paused = 0, Mix_Resume(-1), Mix_ResumeMusic();
Celeste_P8_load_state(game_state);
if (current_music != game_state_music) {
Mix_HaltMusic();
current_music = game_state_music;
if (game_state_music) Mix_PlayMusic(game_state_music, -1);
}
}
break;
} else if ( //toggle screenshake (e / L+R)
#ifdef _3DS
(ev.key.keysym.sym == SDLK_d && kbstate[SDLK_s]) || (ev.key.keysym.sym == SDLK_s && kbstate[SDLK_d])
#else
ev.key.keysym.sym == SDLK_e
#endif
) {
enable_screenshake = !enable_screenshake;
OSDset("screenshake: %s", enable_screenshake ? "on" : "off");
}
//else: fallthrough
}
case SDL_KEYUP: {
int down = ev.type == SDL_KEYDOWN;
int b = -1;
switch (ev.key.keysym.sym) {
case SDLK_LEFT: b = 0; break;
case SDLK_RIGHT: b = 1; break;
case SDLK_UP: b = 2; break;
case SDLK_DOWN: b = 3; break;
case SDLK_z: case SDLK_c: case SDLK_n: case SDLK_a:
b = 4; break;
case SDLK_x: case SDLK_v: case SDLK_m: case SDLK_b:
b = 5; break;
default: break;
}
if (!TAS && b >= 0) {
if (down) buttons_state |= (1<<b);
else buttons_state &= ~(1<<b);
}
}
}
if (TAS && !paused) {
static int t = 0;
t++;
if (t==1) buttons_state = 1<<4;
else if (t > 80) {
fscanf(TAS, "%d,", &buttons_state);
} else buttons_state = 0;
}
if (paused) {
const int x0 = PICO8_W/2-3*4, y0 = 8;
p8_rectfill(x0-1,y0-1, 6*4+x0+1,6+y0+1, 6);
p8_rectfill(x0,y0, 6*4+x0,6+y0, 0);
p8_print("paused", x0+1, y0+1, 7);
} else {
Celeste_P8_update();
Celeste_P8_draw();
}
OSDdraw();
/*for (int i = 0 ; i < 16;i++) {
SDL_Rect rc = {i*8*scale, 0, 8*scale, 4*scale};
SDL_FillRect(screen, &rc, i);
}*/
SDL_Flip(screen);
#if defined(_3DS) /*using SDL_DOUBLEBUF for videomode makes it so SDL_Flip waits for Vsync; so we dont have to delay manually*/ \
|| defined(EMSCRIPTEN) //emscripten_set_main_loop already sets the fps
SDL_Delay(1);
#else
static int t = 0;
static unsigned frame_start = 0;
unsigned frame_end = SDL_GetTicks();
unsigned frame_time = frame_end-frame_start;
static const unsigned target_millis = 1000/30;
if (frame_time < target_millis) {
SDL_Delay((target_millis - frame_time) + (t & 1));
}
t++;
frame_start = SDL_GetTicks();
#endif
}
static int gettileflag(int, int);
static void p8_line(int,int,int,int,unsigned char);

73
sdl20compat.inc.c Normal file
View File

@ -0,0 +1,73 @@
#include<assert.h>
//dummy values
enum {
SDL_PHYSPAL = 1,
SDL_LOGPAL = 2,
SDL_SRCCOLORKEY = 4,
SDL_HWPALETTE = 8,
};
static SDL_Surface* sdl2_screen = NULL;
static SDL_Window* sdl2_window = NULL;
static SDL_Surface *SDL_SetVideoMode(int width, int height, int bpp, Uint32 flags) {
if (!sdl2_window) {
sdl2_window = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, 0);
if (!sdl2_window) return NULL;
}
sdl2_screen = SDL_GetWindowSurface(sdl2_window);
assert(sdl2_screen && sdl2_screen->format->BitsPerPixel == bpp);
return sdl2_screen;
}
static void SDL_SetPalette(SDL_Surface* surf, int flag, SDL_Color* pal, int begin, int count) {
(void)surf;
(void)flag;
(void)pal;
(void)begin;
(void)count;
}
static void SDL_WM_SetCaption(const char* title, const char* icon) {
assert(sdl2_window != NULL);
SDL_SetWindowTitle(sdl2_window, title);
(void)icon;
}
static int SDL_WM_ToggleFullScreen(SDL_Surface* screen) {
assert(screen == sdl2_screen);
assert(sdl2_window != NULL);
static int fullscreen = 0;
fullscreen = !fullscreen;
return SDL_SetWindowFullscreen(sdl2_window, fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0) == 0;
}
static SDL_Surface* SDL_GetVideoSurface(void) {
return sdl2_screen = SDL_GetWindowSurface(sdl2_window);
}
static void SDL_Flip_(SDL_Surface* screen) {
assert(screen == sdl2_screen);
assert(sdl2_window != NULL);
SDL_UpdateWindowSurface(sdl2_window);
}
//hack because for some reason SDL_GetVideoSurface right after SDL_SetWindowFullscreen isn't enough to get the screen back(?) (i think its because of a resize event)
#define SDL_Flip(ref_screen) SDL_Flip_((ref_screen = SDL_GetVideoSurface()))
#define SDL_GetKeyState SDL_GetKeyboardState
//the above function now returns array indexed by scancodes, so we need to use those constants
#define SDLK_F9 SDL_SCANCODE_F9
#define SDLK_ESCAPE SDL_SCANCODE_ESCAPE
#define SDLK_F11 SDL_SCANCODE_F11
#define SDLK_LSHIFT SDL_SCANCODE_LSHIFT
#define SDLK_LEFT SDL_SCANCODE_LEFT
#define SDLK_RIGHT SDL_SCANCODE_RIGHT
#define SDLK_UP SDL_SCANCODE_UP
#define SDLK_DOWN SDL_SCANCODE_DOWN
#define SDLK_5 SDL_SCANCODE_5
#define SDLK_s SDL_SCANCODE_S
#define SDLK_d SDL_SCANCODE_D
#define SDLK_c SDL_SCANCODE_C
#define SDLK_x SDL_SCANCODE_X
#define SDLK_n SDL_SCANCODE_N
#define SDLK_a SDL_SCANCODE_A
#define SDLK_b SDL_SCANCODE_B
#define SDLK_z SDL_SCANCODE_Z
#define SDLK_e SDL_SCANCODE_E
#define SDLK_v SDL_SCANCODE_V
#define sym scancode // SDL_Keysym.sym -> SDL_Keysym.scancode