/* Copyright (c) 2014 Alex Diener This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. Alex Diener alex@ludobloom.com */ #include "gamepad/Gamepad.h" #include "glgraphics/GLGraphics.h" #include "glgraphics/GLIncludes.h" #include "shell/Shell.h" #include "shell/ShellBatteryInfo.h" #include "shell/ShellCallbacks.h" #include "shell/ShellKeyCodes.h" #include "screenmanager/ScreenManager.h" #include "watertowerclassic/AboutScreen.h" #include "watertowerclassic/AudioManager.h" #include "watertowerclassic/ControlsScreen.h" #include "watertowerclassic/GameplayScreen.h" #include "watertowerclassic/LevelEditorScreen.h" #include "watertowerclassic/MainMenuScreen.h" #include "watertowerclassic/ManageLevelsScreen.h" #include "watertowerclassic/SettingsScreen.h" #include "watertowerclassic/SharedEvents.h" #include "watertowerclassic/ShellStateGlobals.h" #if defined(STEM_PLATFORM_macosx) #include "nsopenglshell/NSOpenGLShell.h" #include "nsopenglshell/NSOpenGLTarget.h" #elif defined(STEM_PLATFORM_win32) || defined(STEM_PLATFORM_win64) #include "wglshell/WGLShell.h" #include "wglshell/WGLTarget.h" #elif defined(STEM_PLATFORM_linux32) || defined(STEM_PLATFORM_linux64) #include "glxshell/GLXShell.h" #include "glxshell/GLXTarget.h" #else #include "glutshell/GLUTTarget.h" #endif #include #include #include #include #define FBO_WIDTH 640 #define FBO_HEIGHT 480 static ScreenManager * screenManager; static GameSession * gameSession; static GLuint fboID; static GLuint textureID; static bool noFBOCompatibilityMode; static bool Target_draw() { float viewRatio, targetRatio; float integerMultipleScale = 1.0f; static bool lastLinearFilter; viewRatio = (float) g_windowWidth / g_windowHeight; targetRatio = (float) FBO_WIDTH / FBO_HEIGHT; if (viewRatio > targetRatio) { if (valueGetBoolean(Preferences_get(gameSession->preferences, "integer_scale"))) { if (g_windowHeight > FBO_HEIGHT) { integerMultipleScale = (g_windowHeight - fmod(g_windowHeight, FBO_HEIGHT)) / g_windowHeight; } else { integerMultipleScale = (g_windowHeight - fmod(g_windowHeight, FBO_HEIGHT / ceil(1.0f / (g_windowHeight / (float) FBO_HEIGHT)))) / g_windowHeight; } } } else { if (valueGetBoolean(Preferences_get(gameSession->preferences, "integer_scale"))) { if (g_windowWidth > FBO_WIDTH) { integerMultipleScale = (g_windowWidth - fmod(g_windowWidth, FBO_WIDTH)) / g_windowWidth; } else { integerMultipleScale = (g_windowWidth - fmod(g_windowWidth, FBO_WIDTH / ceil(1.0f / (g_windowWidth / (float) FBO_WIDTH)))) / g_windowWidth; } } } if (noFBOCompatibilityMode) { unsigned int viewportWidth, viewportHeight; glViewport(0, 0, g_windowWidth, g_windowHeight); glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glClear(GL_COLOR_BUFFER_BIT); if (viewRatio > targetRatio) { viewportWidth = g_windowHeight * targetRatio * integerMultipleScale; viewportHeight = g_windowHeight * integerMultipleScale; } else { viewportWidth = g_windowWidth * integerMultipleScale; viewportHeight = g_windowWidth / targetRatio * integerMultipleScale; } glViewport((g_windowWidth - viewportWidth) / 2, (g_windowHeight - viewportHeight) / 2, viewportWidth, viewportHeight); EventDispatcher_dispatchEvent(screenManager->eventDispatcher, ATOM(EVENT_DRAW), NULL); } else { glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboID); glViewport(0, 0, FBO_WIDTH, FBO_HEIGHT); EventDispatcher_dispatchEvent(screenManager->eventDispatcher, ATOM(EVENT_DRAW), NULL); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); glViewport(0, 0, g_windowWidth, g_windowHeight); glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glClear(GL_COLOR_BUFFER_BIT); glLoadIdentity(); if (viewRatio > targetRatio) { glOrtho(-viewRatio, viewRatio, -1.0f, 1.0f, -1.0f, 1.0f); } else { glOrtho(-targetRatio, targetRatio, -targetRatio / viewRatio, targetRatio / viewRatio, -1.0f, 1.0f); } glScalef(integerMultipleScale, integerMultipleScale, 1.0f); glEnable(GL_TEXTURE_RECTANGLE_EXT); glBindTexture(GL_TEXTURE_RECTANGLE_EXT, textureID); if (valueGetBoolean(Preferences_get(gameSession->preferences, "linear_filter")) != lastLinearFilter) { lastLinearFilter = valueGetBoolean(Preferences_get(gameSession->preferences, "linear_filter")); if (lastLinearFilter) { glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_MAG_FILTER, GL_LINEAR); } else { glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_MAG_FILTER, GL_NEAREST); } } glColor4f(1.0f, 1.0f, 1.0f, 1.0f); glBegin(GL_TRIANGLE_FAN); glTexCoord2i(0, 0); glVertex2f(-targetRatio, -1.0f); glTexCoord2i(FBO_WIDTH, 0); glVertex2f(targetRatio, -1.0f); glTexCoord2i(FBO_WIDTH, FBO_HEIGHT); glVertex2f(targetRatio, 1.0f); glTexCoord2i(0, FBO_HEIGHT); glVertex2f(-targetRatio, 1.0f); glEnd(); glDisable(GL_TEXTURE_RECTANGLE_EXT); glBindTexture(GL_TEXTURE_RECTANGLE_EXT, 0); } return true; } static void Target_keyDown(unsigned int charCode, unsigned int keyCode, unsigned int modifierFlags, bool isRepeat) { struct keyEvent event; if (keyCode == KEYBOARD_RETURN_OR_ENTER && (modifierFlags & MODIFIER_ALT_BIT)) { if (Shell_isFullScreen()) { Shell_exitFullScreen(); } else { Shell_enterFullScreen(Shell_getDisplayIndexFromWindow()); } EventDispatcher_dispatchEvent(screenManager->eventDispatcher, ATOM(Shell_isFullScreen() ? EVENT_FULLSCREENED : EVENT_WINDOWED), NULL); } else { event.keyCode = keyCode; event.charCode = charCode; event.modifiers = modifierFlags; event.isRepeat = isRepeat; EventDispatcher_dispatchEvent(screenManager->eventDispatcher, ATOM(EVENT_KEY_DOWN), &event); } } static void Target_keyUp(unsigned int keyCode, unsigned int modifiers) { struct keyEvent event; event.keyCode = keyCode; event.modifiers = modifiers; EventDispatcher_dispatchEvent(screenManager->eventDispatcher, ATOM(EVENT_KEY_UP), &event); } static void Target_keyModifiersChanged(unsigned int modifiers) { struct keyEvent event; event.modifiers = modifiers; EventDispatcher_dispatchEvent(screenManager->eventDispatcher, ATOM(EVENT_KEY_MODIFIERS_CHANGED), &event); } static Vector2f translateMousePosition(float x, float y) { float viewRatio, targetRatio; float integerMultipleScale = 1.0f; x = x * 2.0f / g_windowWidth - 1.0f; y = y * 2.0f / g_windowHeight - 1.0f; viewRatio = (float) g_windowWidth / g_windowHeight; targetRatio = (float) FBO_WIDTH / FBO_HEIGHT; if (viewRatio > targetRatio) { x *= viewRatio / targetRatio; if (valueGetBoolean(Preferences_get(gameSession->preferences, "integer_scale"))) { if (g_windowHeight > FBO_HEIGHT) { integerMultipleScale = (g_windowHeight - fmod(g_windowHeight, FBO_HEIGHT)) / g_windowHeight; } else { integerMultipleScale = (g_windowHeight - fmod(g_windowHeight, FBO_HEIGHT / ceil(1.0f / (g_windowHeight / (float) FBO_HEIGHT)))) / g_windowHeight; } } } else { y *= targetRatio / viewRatio; if (valueGetBoolean(Preferences_get(gameSession->preferences, "integer_scale"))) { if (g_windowWidth > FBO_WIDTH) { integerMultipleScale = (g_windowWidth - fmod(g_windowWidth, FBO_WIDTH)) / g_windowWidth; } else { integerMultipleScale = (g_windowWidth - fmod(g_windowWidth, FBO_WIDTH / ceil(1.0f / (g_windowWidth / (float) FBO_WIDTH)))) / g_windowWidth; } } } x = x / integerMultipleScale * FBO_WIDTH / 2 + FBO_WIDTH / 2; y = y / integerMultipleScale * FBO_HEIGHT / 2 + FBO_HEIGHT / 2; return VECTOR2f(x, y); } static void Target_mouseDown(unsigned int buttonNumber, float x, float y) { struct mouseEvent event; event.button = buttonNumber; event.position = translateMousePosition(x, y); event.rawPosition = VECTOR2f(x, y); #if defined(STEM_PLATFORM_macosx) event.rawPosition.x /= NSOpenGLShell_getDisplayScaleFactor(Shell_getDisplayIndexFromWindow()); event.rawPosition.y /= NSOpenGLShell_getDisplayScaleFactor(Shell_getDisplayIndexFromWindow()); #endif EventDispatcher_dispatchEvent(screenManager->eventDispatcher, ATOM(EVENT_MOUSE_DOWN), &event); } static void Target_mouseUp(unsigned int buttonNumber, float x, float y) { struct mouseEvent event; event.button = buttonNumber; event.position = translateMousePosition(x, y); event.rawPosition = VECTOR2f(x, y); #if defined(STEM_PLATFORM_macosx) event.rawPosition.x /= NSOpenGLShell_getDisplayScaleFactor(Shell_getDisplayIndexFromWindow()); event.rawPosition.y /= NSOpenGLShell_getDisplayScaleFactor(Shell_getDisplayIndexFromWindow()); #endif EventDispatcher_dispatchEvent(screenManager->eventDispatcher, ATOM(EVENT_MOUSE_UP), &event); } static void Target_mouseMoved(float x, float y) { struct mouseEvent event; event.position = translateMousePosition(x, y); event.rawPosition = VECTOR2f(x, y); #if defined(STEM_PLATFORM_macosx) event.rawPosition.x /= NSOpenGLShell_getDisplayScaleFactor(Shell_getDisplayIndexFromWindow()); event.rawPosition.y /= NSOpenGLShell_getDisplayScaleFactor(Shell_getDisplayIndexFromWindow()); #endif EventDispatcher_dispatchEvent(screenManager->eventDispatcher, ATOM(EVENT_MOUSE_MOVED), &event); } static void Target_mouseDragged(unsigned int buttonMask, float x, float y) { struct mouseEvent event; event.button = buttonMask; event.position = translateMousePosition(x, y); event.rawPosition = VECTOR2f(x, y); #if defined(STEM_PLATFORM_macosx) event.rawPosition.x /= NSOpenGLShell_getDisplayScaleFactor(Shell_getDisplayIndexFromWindow()); event.rawPosition.y /= NSOpenGLShell_getDisplayScaleFactor(Shell_getDisplayIndexFromWindow()); #endif EventDispatcher_dispatchEvent(screenManager->eventDispatcher, ATOM(EVENT_MOUSE_DRAGGED), &event); } static void gamepadEventTimer(ShellTimer timerID, void * context) { Gamepad_detectDevices(); Gamepad_processEvents(); } void gamepadAttached(struct Gamepad_device * device, void * context) { EventDispatcher_dispatchEvent(screenManager->eventDispatcher, ATOM(EVENT_GAMEPAD_ATTACH), device); } void gamepadRemoved(struct Gamepad_device * device, void * context) { EventDispatcher_dispatchEvent(screenManager->eventDispatcher, ATOM(EVENT_GAMEPAD_DETACH), device); } void gamepadButtonDown(struct Gamepad_device * device, unsigned int buttonID, double timestamp, void * context) { struct gamepadButtonEvent event; event.device = device; event.buttonID = buttonID; event.timestamp = timestamp; EventDispatcher_dispatchEvent(screenManager->eventDispatcher, ATOM(EVENT_GAMEPAD_BUTTON_DOWN), &event); } void gamepadButtonUp(struct Gamepad_device * device, unsigned int buttonID, double timestamp, void * context) { struct gamepadButtonEvent event; event.device = device; event.buttonID = buttonID; event.timestamp = timestamp; EventDispatcher_dispatchEvent(screenManager->eventDispatcher, ATOM(EVENT_GAMEPAD_BUTTON_UP), &event); } void gamepadAxisMoved(struct Gamepad_device * device, unsigned int axisID, float value, float lastValue, double timestamp, void * context) { struct gamepadAxisEvent event; event.device = device; event.axisID = axisID; event.value = value; event.lastValue = lastValue; event.timestamp = timestamp; EventDispatcher_dispatchEvent(screenManager->eventDispatcher, ATOM(EVENT_GAMEPAD_AXIS_MOVE), &event); } static void Target_resized(unsigned int newWidth, unsigned int newHeight) { g_windowWidth = newWidth; g_windowHeight = newHeight; glViewport(0, 0, newWidth, newHeight); if (screenManager != NULL) { EventDispatcher_dispatchEvent(screenManager->eventDispatcher, ATOM(EVENT_RESIZED), NULL); } } static void Target_backgrounded() { if (screenManager != NULL) { EventDispatcher_dispatchEvent(screenManager->eventDispatcher, ATOM(EVENT_BACKGROUNDED), NULL); } } static void Target_foregrounded() { if (screenManager != NULL) { EventDispatcher_dispatchEvent(screenManager->eventDispatcher, ATOM(EVENT_FOREGROUNDED), NULL); } } static bool Target_confirmQuit() { if (!gameSession->hasUnsavedLevelChanges) { return true; } while (EventDispatcher_dispatchEvent(screenManager->eventDispatcher, ATOM(EVENT_CONFIRM_QUIT), NULL)) {} return false; } static void registerShellCallbacks() { Shell_drawFunc(Target_draw); Shell_resizeFunc(Target_resized); Shell_keyDownFunc(Target_keyDown); Shell_keyUpFunc(Target_keyUp); Shell_keyModifiersChangedFunc(Target_keyModifiersChanged); Shell_mouseDownFunc(Target_mouseDown); Shell_mouseUpFunc(Target_mouseUp); Shell_mouseMovedFunc(Target_mouseMoved); Shell_mouseDraggedFunc(Target_mouseDragged); Shell_backgroundedFunc(Target_backgrounded); Shell_foregroundedFunc(Target_foregrounded); Shell_confirmQuitFunc(Target_confirmQuit); } #if defined(STEM_PLATFORM_macosx) void NSOpenGLTarget_configure(int argc, const char ** argv, struct NSOpenGLShellConfiguration * configuration) { #elif defined(STEM_PLATFORM_win32) || defined(STEM_PLATFORM_win64) void WGLTarget_configure(void * instance, void * prevInstance, char * commandLine, int command, int argc, const char ** argv, struct WGLShellConfiguration * configuration) { #elif defined(STEM_PLATFORM_linux32) || defined(STEM_PLATFORM_linux64) void GLXTarget_configure(int argc, const char ** argv, struct GLXShellConfiguration * configuration) { #else void GLUTTarget_configure(int argc, const char ** argv, struct GLUTShellConfiguration * configuration) { #endif float displayRatio, targetRatio; float integerMultipleScale = 1.0f; int displayX, displayY; unsigned int displayWidth, displayHeight; unsigned int width, height; #if defined(STEM_PLATFORM_macosx) float scaleFactor; scaleFactor = NSOpenGLShell_getDisplayScaleFactor(0); #endif Shell_getSafeWindowRect(0, &displayX, &displayY, &displayWidth, &displayHeight); #if defined(STEM_PLATFORM_macosx) displayWidth *= scaleFactor; displayHeight *= scaleFactor; #endif displayRatio = (float) displayWidth / displayHeight; targetRatio = (float) FBO_WIDTH / FBO_HEIGHT; if (displayRatio > targetRatio) { if (displayHeight > FBO_HEIGHT) { integerMultipleScale = (displayHeight - fmod(displayHeight, FBO_HEIGHT)) / displayHeight; } else { integerMultipleScale = (displayHeight - fmod(displayHeight, FBO_HEIGHT / ceil(1.0f / (displayHeight / (float) FBO_HEIGHT)))) / displayHeight; } height = ceil(displayHeight * integerMultipleScale); width = height * targetRatio; } else { if (displayWidth > FBO_WIDTH) { integerMultipleScale = (displayWidth - fmod(displayWidth, FBO_WIDTH)) / displayWidth; } else { integerMultipleScale = (displayWidth - fmod(displayWidth, FBO_WIDTH / ceil(1.0f / (displayWidth / (float) FBO_WIDTH)))) / displayWidth; } width = ceil(displayWidth * integerMultipleScale); height = width / targetRatio; } g_windowWidth = width; g_windowHeight = height; #if defined(STEM_PLATFORM_macosx) displayWidth /= scaleFactor; displayHeight /= scaleFactor; width /= scaleFactor; height /= scaleFactor; #endif configuration->windowX = displayX + (displayWidth - width) / 2; configuration->windowY = displayY + (displayHeight - height) / 2; configuration->windowWidth = width; configuration->windowHeight = height; configuration->windowTitle = "Water Tower Classic"; #if defined(STEM_PLATFORM_win32) || defined(STEM_PLATFORM_win64) configuration->iconResource = 400; #endif registerShellCallbacks(); } void Target_init() { GLenum status; MainMenuScreen * mainMenuScreen; GameplayScreen * gameplayScreen; LevelEditorScreen * levelEditorScreen; SettingsScreen * settingsScreen; ControlsScreen * controlsScreen; ManageLevelsScreen * manageLevelsScreen; AboutScreen * aboutScreen; chdir(Shell_getResourcePath()); gameSession = GameSession_create(); gameSession->resourceManager->referenceResource(gameSession->resourceManager, "bitmap_font", "pixelfont.json"); gameSession->resourceManager->referenceResource(gameSession->resourceManager, "texture_atlas", "sprite_atlas.json"); gameSession->resourceManager->referenceResource(gameSession->resourceManager, "texture", "sprites.json"); AudioManager_globalInit(gameSession); Gamepad_init(); Gamepad_deviceAttachFunc(gamepadAttached, NULL); Gamepad_deviceRemoveFunc(gamepadRemoved, NULL); Gamepad_buttonDownFunc(gamepadButtonDown, NULL); Gamepad_buttonUpFunc(gamepadButtonUp, NULL); Gamepad_axisMoveFunc(gamepadAxisMoved, NULL); Shell_setTimer(1.0 / 60.0, true, gamepadEventTimer, NULL); if (!strstr((char *) glGetString(GL_EXTENSIONS), "GL_EXT_framebuffer_object")) { noFBOCompatibilityMode = true; } else { glGenTextures(1, &textureID); glBindTexture(GL_TEXTURE_RECTANGLE_EXT, textureID); glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexImage2D(GL_TEXTURE_RECTANGLE_EXT, 0, GL_RGBA8, FBO_WIDTH, FBO_HEIGHT, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL); glBindTexture(GL_TEXTURE_RECTANGLE_EXT, 0); glGenFramebuffersEXT(1, &fboID); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboID); glFramebufferTexture2DEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE_EXT, textureID, 0); status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); if (status != GL_FRAMEBUFFER_COMPLETE_EXT) { fprintf(stderr, "Framebuffer incomplete: 0x%X\n", status); } glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); } screenManager = ScreenManager_create(); mainMenuScreen = MainMenuScreen_create(gameSession); gameplayScreen = GameplayScreen_create(gameSession); levelEditorScreen = LevelEditorScreen_create(gameSession); settingsScreen = SettingsScreen_create(gameSession); controlsScreen = ControlsScreen_create(gameSession); manageLevelsScreen = ManageLevelsScreen_create(gameSession); aboutScreen = AboutScreen_create(gameSession); ScreenManager_addScreen(screenManager, mainMenuScreen); ScreenManager_addScreen(screenManager, gameplayScreen); ScreenManager_addScreen(screenManager, levelEditorScreen); ScreenManager_addScreen(screenManager, settingsScreen); ScreenManager_addScreen(screenManager, controlsScreen); ScreenManager_addScreen(screenManager, manageLevelsScreen); ScreenManager_addScreen(screenManager, aboutScreen); ScreenManager_addTransition(screenManager, mainMenuScreen, gameplayScreen, "start_game"); ScreenManager_addTransition(screenManager, mainMenuScreen, settingsScreen, "settings"); ScreenManager_addTransition(screenManager, mainMenuScreen, manageLevelsScreen, "manage_levels"); ScreenManager_addTransition(screenManager, mainMenuScreen, aboutScreen, "about"); ScreenManager_addTransition(screenManager, gameplayScreen, mainMenuScreen, "game_over"); ScreenManager_addTransition(screenManager, gameplayScreen, levelEditorScreen, "level_test_ended"); ScreenManager_addTransition(screenManager, gameplayScreen, settingsScreen, "settings"); ScreenManager_addTransition(screenManager, settingsScreen, mainMenuScreen, "back"); ScreenManager_addTransition(screenManager, settingsScreen, gameplayScreen, "back_to_game"); ScreenManager_addTransition(screenManager, settingsScreen, controlsScreen, "controls"); ScreenManager_addTransition(screenManager, controlsScreen, settingsScreen, "back"); ScreenManager_addTransition(screenManager, aboutScreen, mainMenuScreen, "back"); ScreenManager_addTransition(screenManager, manageLevelsScreen, levelEditorScreen, "edit_level"); ScreenManager_addTransition(screenManager, manageLevelsScreen, mainMenuScreen, "back"); ScreenManager_addTransition(screenManager, levelEditorScreen, manageLevelsScreen, "back"); ScreenManager_addTransition(screenManager, levelEditorScreen, gameplayScreen, "test_level"); ScreenManager_setScreen(screenManager, mainMenuScreen); Shell_setVSync(valueGetBoolean(Preferences_get(gameSession->preferences, "vsync_window")), false); Shell_setVSync(valueGetBoolean(Preferences_get(gameSession->preferences, "vsync_fullscreen")), true); if (valueGetBoolean(Preferences_get(gameSession->preferences, "fullscreen"))) { Shell_enterFullScreen(Shell_getDisplayIndexFromWindow()); } Shell_mainLoop(); }