// Copyright (c) 2014 Alex Diener. All rights reserved. #include "glgraphics/GLIncludes.h" #include "shell/Shell.h" #include "shell/ShellKeyCodes.h" #include "utilities/IOUtilities.h" #include "watertowerclassic/AboutScreen.h" #include "watertowerclassic/ShellStateGlobals.h" #include "watertowerclassic/SharedEvents.h" #include #include #if defined(STEM_PLATFORM_macosx) #include "nsopenglshell/NSOpenGLShell.h" #elif defined(STEM_PLATFORM_win32) || defined(STEM_PLATFORM_win64) #include "wglshell/WGLShell.h" #elif defined(STEM_PLATFORM_linux32) || defined(STEM_PLATFORM_linux64) #include "glxshell/GLXShell.h" #endif #define SUPERCLASS Screen enum { MENU_ITEM_VISIT_WEBSITE, MENU_ITEM_DONE, MENU_ITEM_COUNT }; AboutScreen * AboutScreen_create(GameSession * gameSession) { stemobject_create_implementation(AboutScreen, init, gameSession) } bool AboutScreen_init(AboutScreen * self, GameSession * gameSession) { call_super(init, self); self->activate = AboutScreen_activate; self->deactivate = AboutScreen_deactivate; self->dispose = AboutScreen_dispose; self->gameSession = gameSession; self->inputController = InputController_create(gameSession->inputMap, "left", "right", "down", "up", "jump", "action", "pause", NULL); self->menuItemIndex = MENU_ITEM_DONE; glGenBuffersARB(1, &self->vertexBufferID); glGenBuffersARB(1, &self->indexBufferID); self->bitmapFont = ResourceManager_referenceResource(self->gameSession->resourceManager, "bitmap_font", "pixelfont.json"); return true; } void AboutScreen_dispose(AboutScreen * self) { glDeleteBuffersARB(1, &self->vertexBufferID); glDeleteBuffersARB(1, &self->indexBufferID); ResourceManager_releaseResource(self->gameSession->resourceManager, "bitmap_font", "pixelfont.json"); InputController_dispose(self->inputController); call_super(dispose, self); } static void menuAction(AboutScreen * self, unsigned int menuItemIndex) { self->menuItemIndex = menuItemIndex; switch (menuItemIndex) { case MENU_ITEM_VISIT_WEBSITE: if (Shell_isFullScreen()) { Shell_exitFullScreen(); } Shell_openURL("http://ludobloom.com/"); break; case MENU_ITEM_DONE: InputController_reset(self->inputController); ScreenManager_transition(self->screenManager, "back"); break; } } static void menuDown(AboutScreen * self) { self->menuItemIndex += 1; self->menuItemIndex %= MENU_ITEM_COUNT; Shell_redisplay(); } static void menuUp(AboutScreen * self) { self->menuItemIndex += MENU_ITEM_COUNT - 1; self->menuItemIndex %= MENU_ITEM_COUNT; Shell_redisplay(); } static bool mouseDown(Atom eventID, void * eventData, void * context) { struct mouseEvent * event = eventData; AboutScreen * self = context; if (event->position.y >= 406.0f) { menuAction(self, (event->position.y - 406.0f) / 32.0f); } return false; } static bool keyDown(Atom eventID, void * eventData, void * context) { struct keyEvent * event = eventData; AboutScreen * self = context; if (event->isRepeat) { return false; } switch (event->keyCode) { case KEYBOARD_ESCAPE: menuAction(self, MENU_ITEM_DONE); return true; case KEYBOARD_RETURN_OR_ENTER: case KEYBOARD_SPACEBAR: InputController_triggerAction(self->inputController, ATOM("jump")); return true; case KEYBOARD_DOWN_ARROW: InputController_triggerAction(self->inputController, ATOM("down")); return true; case KEYBOARD_UP_ARROW: InputController_triggerAction(self->inputController, ATOM("up")); return true; } return InputController_keyDown(self->inputController, event->keyCode); } static bool keyUp(Atom eventID, void * eventData, void * context) { struct keyEvent * event = eventData; AboutScreen * self = context; switch (event->keyCode) { case KEYBOARD_RETURN_OR_ENTER: case KEYBOARD_SPACEBAR: InputController_releaseAction(self->inputController, ATOM("jump")); return true; case KEYBOARD_DOWN_ARROW: InputController_releaseAction(self->inputController, ATOM("down")); return true; case KEYBOARD_UP_ARROW: InputController_releaseAction(self->inputController, ATOM("up")); return true; } return InputController_keyUp(self->inputController, event->keyCode); } static bool gamepadButtonDown(Atom eventID, void * eventData, void * context) { struct gamepadButtonEvent * event = eventData; AboutScreen * self = context; return InputController_gamepadButtonDown(self->inputController, event->device->vendorID, event->device->productID, event->buttonID); } static bool gamepadButtonUp(Atom eventID, void * eventData, void * context) { struct gamepadButtonEvent * event = eventData; AboutScreen * self = context; return InputController_gamepadButtonUp(self->inputController, event->device->vendorID, event->device->productID, event->buttonID); } static bool gamepadAxisMove(Atom eventID, void * eventData, void * context) { struct gamepadAxisEvent * event = eventData; AboutScreen * self = context; return InputController_gamepadAxisMoved(self->inputController, event->device->vendorID, event->device->productID, event->axisID, event->value, event->lastValue); } static bool actionDown(Atom eventID, void * eventData, void * context) { AboutScreen * self = context; Atom actionID = eventData; if (actionID == ATOM("down")) { menuDown(self); return true; } if (actionID == ATOM("up")) { menuUp(self); return true; } if (actionID == ATOM("jump")) { menuAction(self, self->menuItemIndex); return true; } if (actionID == ATOM("action")) { menuAction(self, MENU_ITEM_DONE); return true; } return false; } static void getMenuItemVertices(AboutScreen * self, struct vertex_p2f_t2f_c4f * outVertices, GLushort * outIndexes, unsigned int * ioVertexCount, unsigned int * ioIndexCount) { Color4f colors[MENU_ITEM_COUNT]; unsigned int colorIndex; static const char * lines[] = { "In 2003, I published my first game, called Water Tower. It was written", "for the uDevGame 2003 contest run by idevgames.com. The game didn't win", "any prizes in the competition, but it earned 7th place ranking out of 43", "entries in the gameplay category, and I was proud of what I had created.", "", "The original game was written for Macintosh PowerPC computers, using the", "QuickDraw API. QuickDraw is now obsolete, and Water Tower is unable to", "run on modern operating systems. I wanted to play the game again, so I", "ported it to a modern cross-platform framework, replacing the QuickDraw", "graphics layer with OpenGL. The game was reborn as Water Tower Classic.", "", "I've learned a lot in the 11 years since I wrote Water Tower. Although", "there are many things I'd like to have changed about how the game works,", "I decided to leave it mostly intact, making only the updates necessary to", "modernize it enough to be playable. Try my other games too!" }; char versionString[24]; unsigned int lineIndex; for (colorIndex = 0; colorIndex < MENU_ITEM_COUNT; colorIndex++) { if (colorIndex == self->menuItemIndex) { colors[colorIndex] = MENU_COLOR_HIGHLIGHTED; } else { colors[colorIndex] = MENU_COLOR_ENABLED; } } GLBitmapFont_getStringVerticesWithColor(self->bitmapFont, "VISIT WEBSITE", GLBITMAPFONT_USE_STRLEN, -32, VECTOR2f(320.0f, 422.0f), VECTOR2f(0.5f, 0.5f), colors[MENU_ITEM_VISIT_WEBSITE], GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); GLBitmapFont_getStringVerticesWithColor(self->bitmapFont, "BACK", GLBITMAPFONT_USE_STRLEN, -32, VECTOR2f(320.0f, 454.0f), VECTOR2f(0.5f, 0.5f), colors[MENU_ITEM_DONE], GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); GLBitmapFont_getStringVerticesWithColor(self->bitmapFont, "HISTORY", GLBITMAPFONT_USE_STRLEN, -16, VECTOR2f(320.0f, 32.0f), VECTOR2f(0.5f, 1.0f), MENU_COLOR_NONINTERACTIVE, GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); for (lineIndex = 0; lineIndex < sizeof(lines) / sizeof(const char *); lineIndex++) { GLBitmapFont_getStringVerticesWithColor(self->bitmapFont, lines[lineIndex], GLBITMAPFONT_USE_STRLEN, -16, VECTOR2f(80.0f, 50.0f + 18 * lineIndex), VECTOR2f(0.0f, 1.0f), MENU_COLOR_NONINTERACTIVE, GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); } GLBitmapFont_getStringVerticesWithColor(self->bitmapFont, "Water Tower Classic Copyright 2003-2014 Alex Diener", GLBITMAPFONT_USE_STRLEN, -16, VECTOR2f(320.0f, 50.0f + 18 * (lineIndex + 1)), VECTOR2f(0.5f, 1.0f), MENU_COLOR_NONINTERACTIVE, GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); snprintf_safe(versionString, 24, "Version %u.%u.%u", VERSION_MAJOR, VERSION_MINOR, VERSION_TWEAK); GLBitmapFont_getStringVerticesWithColor(self->bitmapFont, versionString, GLBITMAPFONT_USE_STRLEN, -16, VECTOR2f(320.0f, 50.0f + 18 * (lineIndex + 2)), VECTOR2f(0.5f, 1.0f), MENU_COLOR_NONINTERACTIVE, GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); GLBitmapFont_getStringVerticesWithColor(self->bitmapFont, "http://ludobloom.com/", GLBITMAPFONT_USE_STRLEN, -16, VECTOR2f(320.0f, 50.0f + 18 * (lineIndex + 3)), VECTOR2f(0.5f, 1.0f), MENU_COLOR_NONINTERACTIVE, GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); } static bool draw(Atom eventID, void * eventData, void * context) { AboutScreen * self = context; struct vertex_p2f_t2f_c4f * vertices; GLushort * indexes; unsigned int vertexCount, indexCount; glClearColor(1.0f, 1.0f, 1.0f, 0.0f); glClear(GL_COLOR_BUFFER_BIT); glLoadIdentity(); glOrtho(0.0f, 640.0f, 480.0f, 0.0f, -1.0f, 1.0f); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glEnableClientState(GL_COLOR_ARRAY); glBindBufferARB(GL_ARRAY_BUFFER, self->vertexBufferID); glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, self->indexBufferID); vertexCount = indexCount = 0; getMenuItemVertices(self, NULL, NULL, &vertexCount, &indexCount); glBufferDataARB(GL_ARRAY_BUFFER, sizeof(struct vertex_p2f_t2f_c4f) * vertexCount, NULL, GL_STREAM_DRAW); glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLushort) * indexCount, NULL, GL_STREAM_DRAW); vertices = glMapBufferARB(GL_ARRAY_BUFFER, GL_WRITE_ONLY); indexes = glMapBufferARB(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY); vertexCount = indexCount = 0; getMenuItemVertices(self, vertices, indexes, &vertexCount, &indexCount); glUnmapBufferARB(GL_ARRAY_BUFFER); glUnmapBufferARB(GL_ELEMENT_ARRAY_BUFFER); glVertexPointer(2, GL_FLOAT, sizeof(struct vertex_p2f_t2f_c4f), (void *) offsetof(struct vertex_p2f_t2f_c4f, position)); glTexCoordPointer(2, GL_FLOAT, sizeof(struct vertex_p2f_t2f_c4f), (void *) offsetof(struct vertex_p2f_t2f_c4f, texCoords)); glColorPointer(4, GL_FLOAT, sizeof(struct vertex_p2f_t2f_c4f), (void *) offsetof(struct vertex_p2f_t2f_c4f, color)); GLTexture_activate(self->bitmapFont->atlas->texture); glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_SHORT, 0); GLTexture_deactivate(self->bitmapFont->atlas->texture); glBindBufferARB(GL_ARRAY_BUFFER, 0); glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, 0); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glDisableClientState(GL_COLOR_ARRAY); return true; } void AboutScreen_activate(AboutScreen * self) { EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_MOUSE_DOWN), mouseDown, self); EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_KEY_DOWN), keyDown, self); EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_KEY_UP), keyUp, self); EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_GAMEPAD_BUTTON_DOWN), gamepadButtonDown, self); EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_GAMEPAD_BUTTON_UP), gamepadButtonUp, self); EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_GAMEPAD_AXIS_MOVE), gamepadAxisMove, self); EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_DRAW), draw, self); EventDispatcher_registerForEvent(self->inputController->eventDispatcher, ATOM(INPUT_CONTROLLER_EVENT_ACTION_DOWN), actionDown, self); Shell_redisplay(); Shell_setCursorVisible(true); } void AboutScreen_deactivate(AboutScreen * self) { EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_MOUSE_DOWN), mouseDown, self); EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_KEY_DOWN), keyDown, self); EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_KEY_UP), keyUp, self); EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_GAMEPAD_BUTTON_DOWN), gamepadButtonDown, self); EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_GAMEPAD_BUTTON_UP), gamepadButtonUp, self); EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_GAMEPAD_AXIS_MOVE), gamepadAxisMove, self); EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_DRAW), draw, self); EventDispatcher_unregisterForEvent(self->inputController->eventDispatcher, ATOM(INPUT_CONTROLLER_EVENT_ACTION_DOWN), actionDown, self); }