#include #include #include #include "game.h" int lastJoy = -1; EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context; int WEBGL_VERSION; // timing int osGetTimeMS() { return (int)emscripten_get_now(); } // storage and data downloading const char *IDB = "db"; void onError(void *arg) { Stream *stream = (Stream*)arg; LOG("! IDB error for %s\n", stream->name); if (stream->callback) stream->callback(NULL, stream->userData); delete stream; } void onLoad(void *arg, void *data, int size) { Stream *stream = (Stream*)arg; FILE *f = fopen(stream->name, "wb"); fwrite(data, size, 1, f); fclose(f); stream->callback(new Stream(stream->name), stream->userData); delete stream; } void onLoadAndStore(void *arg, void *data, int size) { emscripten_idb_async_store(IDB, ((Stream*)arg)->name, data, size, NULL, NULL, onError); onLoad(arg, data, size); } void onExists(void *arg, int exists) { if (exists) emscripten_idb_async_load(IDB, ((Stream*)arg)->name, arg, onLoad, onError); else emscripten_async_wget_data(((Stream*)arg)->name, arg, onLoadAndStore, onError); } void osDownload(Stream *stream) { emscripten_idb_async_exists(IDB, stream->name, stream, onExists, onError); } void onCacheStore(void *arg) { Stream *stream = (Stream*)arg; LOG("cache stored: %s\n", stream->name); if (stream->callback) stream->callback(new Stream(stream->name, stream->data, stream->size), stream->userData); delete stream; } void onCacheLoad(void *arg, void *data, int size) { Stream *stream = (Stream*)arg; LOG("cache loaded: %s\n", stream->name); if (stream->callback) stream->callback(new Stream(stream->name, data, size), stream->userData); delete stream; } void onCacheError(void *arg) { Stream *stream = (Stream*)arg; LOG("! cache error: %s\n", stream->name); if (stream->callback) stream->callback(NULL, stream->userData); delete stream; } void osCacheWrite(Stream *stream) { emscripten_idb_async_store(IDB, stream->name, stream->data, stream->size, stream, onCacheStore, onCacheError); } void osCacheRead(Stream *stream) { emscripten_idb_async_load(IDB, stream->name, stream, onCacheLoad, onCacheError); } // memory card void osReadSlot(Stream *stream) { return osCacheRead(stream); } void osWriteSlot(Stream *stream) { return osCacheWrite(stream); } JoyKey joyToInputKey(int code) { static const JoyKey keys[16] = { jkA, jkB, jkX, jkY, jkLB, jkRB, jkLT, jkRT, jkSelect, jkStart, jkL, jkR, jkUp, jkDown, jkLeft, jkRight, }; if (code >= 0 && code < 16) return keys[code]; return jkNone; } #define JOY_DEAD_ZONE_TRIGGER 0.01f vec2 joyTrigger(float x) { return vec2(x > JOY_DEAD_ZONE_TRIGGER ? x : 0.0f, 0.0f); } bool joyReady[INPUT_JOY_COUNT] = { 0 }; bool osJoyReady(int index) { return joyReady[index]; } void osJoyVibrate(int index, float L, float R) { // } void joyUpdate() { int count = min(emscripten_get_num_gamepads(), INPUT_JOY_COUNT); for (int j = 0; j < count; j++) { EmscriptenGamepadEvent state; bool wasReady = joyReady[j]; joyReady[j] = emscripten_get_gamepad_status(j, &state) == EMSCRIPTEN_RESULT_SUCCESS; if (!joyReady[j]) { if (wasReady) Input::reset(); continue; } for (int i = 0; i < state.numButtons; i++) { JoyKey key = joyToInputKey(i); Input::setJoyDown(j, key, state.digitalButton[i]); if (key == jkLT || key == jkRT) Input::setJoyPos(j, key, joyTrigger(state.analogButton[i])); } Input::setJoyPos(j, jkL, vec2(state.axis[0], state.axis[1])); Input::setJoyPos(j, jkR, vec2(state.axis[2], state.axis[3])); } } void main_loop() { joyUpdate(); if (Game::update()) { Game::render(); // clear backbuffer alpha by 1.0f to make opaque canvas layer glColorMask(false, false, false, true); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glColorMask(true, true, true, true); } } bool initGL() { EmscriptenWebGLContextAttributes attrs; emscripten_webgl_init_context_attributes(&attrs); attrs.alpha = true; attrs.depth = true; attrs.enableExtensionsByDefault = true; attrs.antialias = false; attrs.premultipliedAlpha = false; attrs.majorVersion = 2; context = emscripten_webgl_create_context(0, &attrs); if (!context) { attrs.majorVersion = 1; context = emscripten_webgl_create_context(0, &attrs); } if (!context) { LOG("! can't initialize WebGL !\n"); return false; } WEBGL_VERSION = attrs.majorVersion; emscripten_webgl_make_context_current(context); char *ext = (char*)glGetString(GL_EXTENSIONS); if (ext != NULL) { char buf[255]; int len = strlen(ext); int start = 0; for (int i = 0; i < len; i++) if (ext[i] == ' ' || (i == len - 1)) { memcpy(buf, &ext[start], i - start); buf[i - start] = 0; emscripten_webgl_enable_extension(context, buf); //LOG("enable: %s\n", buf); start = i + 1; } } return true; } void freeGL() { emscripten_webgl_destroy_context(context); } EM_BOOL resize() { double w, h; emscripten_get_element_css_size(NULL, &w, &h); Core::width = int(w); Core::height = int(h); emscripten_set_canvas_size(Core::width, Core::height); LOG("resize %d %d\n", Core::width, Core::height); return 1; } EM_BOOL resizeCallback(int eventType, const EmscriptenUiEvent *e, void *userData) { return resize(); } EM_BOOL fullscreenCallback(int eventType, const void *reserved, void *userData) { return resize(); } bool isFullScreen() { EmscriptenFullscreenChangeEvent status; emscripten_get_fullscreen_status(&status); return status.isFullscreen; } extern "C" { void EMSCRIPTEN_KEEPALIVE set_def_lang(int id) { Core::defLang = id; } void EMSCRIPTEN_KEEPALIVE snd_fill(Sound::Frame *frames, int count) { Sound::fill(frames, count); } void EMSCRIPTEN_KEEPALIVE game_level_load(char *data, int size) { Game::startLevel(new Stream(NULL, data, size)); } void EMSCRIPTEN_KEEPALIVE change_fs_mode() { EM_ASM(JSEvents.inEventHandler = true); EM_ASM(JSEvents.currentEventHandler = {allowsDeferredCalls:true}); if (!isFullScreen()) { EmscriptenFullscreenStrategy s; s.scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH; s.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_HIDEF; s.filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT; s.canvasResizedCallback = fullscreenCallback; emscripten_request_fullscreen_strategy(NULL, 0, &s); } else { LOG("exit fs\n"); emscripten_exit_fullscreen(); } } } InputKey keyToInputKey(int code) { static const int codes[] = { 0x25, 0x27, 0x26, 0x28, 0x20, 0x09, 0x0D, 0x1B, 0x10, 0x11, 0x12, '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6B, 0x6D, 0x6A, 0x6F, 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0xBB, 0xBD, 0xDB, 0xDD, 0xBF, 0xDC, 0xBC, 0xBE, 0xC0, 0xBA, 0xDE, 0x21, 0x22, 0x24, 0x23, 0x2E, 0x2D, 0x08 }; for (int i = 0; i < COUNT(codes); i++) { if (codes[i] == code) { return (InputKey)(ikLeft + i); } } return ikNone; } EM_BOOL keyCallback(int eventType, const EmscriptenKeyboardEvent *e, void *userData) { switch(eventType) { case EMSCRIPTEN_EVENT_KEYDOWN: case EMSCRIPTEN_EVENT_KEYUP: if (eventType == EMSCRIPTEN_EVENT_KEYDOWN && e->altKey && e->keyCode == 0x0D) { // Alt + Enter change_fs_mode(); break; } Input::setDown(keyToInputKey(e->keyCode), eventType == EMSCRIPTEN_EVENT_KEYDOWN); break; } return 1; } EM_BOOL touchCallback(int eventType, const EmscriptenTouchEvent *e, void *userData) { for (int i = 0; i < e->numTouches; i++) { if (!e->touches[i].isChanged) continue; InputKey key = Input::getTouch(e->touches[i].identifier); if (key == ikNone) continue; Input::setPos(key, vec2(e->touches[i].canvasX, e->touches[i].canvasY)); if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART || eventType == EMSCRIPTEN_EVENT_TOUCHEND || eventType == EMSCRIPTEN_EVENT_TOUCHCANCEL) Input::setDown(key, eventType == EMSCRIPTEN_EVENT_TOUCHSTART); } return 0; } EM_BOOL mouseCallback(int eventType, const EmscriptenMouseEvent *e, void *userData) { switch (eventType) { case EMSCRIPTEN_EVENT_MOUSEDOWN : case EMSCRIPTEN_EVENT_MOUSEUP : { InputKey key = e->button == 0 ? ikMouseL : (e->button == 2 ? ikMouseR : ikMouseM); Input::setPos(key, vec2((float)e->clientX, (float)e->clientY)); Input::setDown(key, eventType != EMSCRIPTEN_EVENT_MOUSEUP); break; } case EMSCRIPTEN_EVENT_MOUSEMOVE : Input::setPos(ikMouseL, vec2((float)e->clientX, (float)e->clientY)); break; } return 1; } int main() { if (!initGL()) { return 0; } cacheDir[0] = saveDir[0] = contentDir[0] = 0; emscripten_set_keydown_callback(0, 0, 1, keyCallback); emscripten_set_keyup_callback(0, 0, 1, keyCallback); emscripten_set_resize_callback(0, 0, 0, resizeCallback); emscripten_set_touchstart_callback(0, 0, 0, touchCallback); emscripten_set_touchend_callback(0, 0, 0, touchCallback); emscripten_set_touchmove_callback(0, 0, 0, touchCallback); emscripten_set_touchcancel_callback(0, 0, 0, touchCallback); emscripten_set_mousedown_callback(0, 0, 1, mouseCallback); emscripten_set_mouseup_callback(0, 0, 1, mouseCallback); emscripten_set_mousemove_callback(0, 0, 1, mouseCallback); emscripten_run_script("getLanguage()"); Game::init(); emscripten_run_script("snd_init()"); resize(); emscripten_set_main_loop(main_loop, 0, true); Game::deinit(); freeGL(); return 0; }