// Copyright (c) 2023 Alex Diener. All rights reserved. #include "PROJECT_NAME/Atoms.h" #include "PROJECT_NAME/FileUtilities.h" #include "PROJECT_NAME/GameplayScreen.h" #include "PROJECT_NAME/Globals.h" #include "PROJECT_NAME/RoomStateUndoStateDelta.h" #include "PROJECT_NAME/SharedDefinitions.h" #include "PROJECT_NAME/Sounds.h" #include "shell/ShellKeyCodes.h" #define stemobject_implementation GameplayScreen v_begin(); v_func(dispose); v_func(update); v_func(mouseMoved); v_func(mouseLeave); v_func(keyDown); v_func(listRenderables); v_func(needsRedraw); v_func(activate); v_func(deactivate); v_end(); GameplayScreen * GameplayScreen_create(void) { stemobject_create_implementation(init) } static void resetUndoStack(GameplayScreen * self) { UndoStack_dispose(self->undoStack); self->undoStack = UndoStack_create(self->gameState->rooms[self->gameState->currentRoomIndex]); disposeRoomStateUndoData(&self->roomStateUndoData); self->roomStateUndoData = RoomState_createUndoData(self->gameState->rooms[self->gameState->currentRoomIndex]); } static bool roomChanged(Atom eventID, void * eventData, void * context) { GameplayScreen * self = context; struct GameState_roomChangedEvent * event = eventData; GameView_beginRoomTransition(self->gameView, event->oldRoomIndex); resetUndoStack(self); disposeRoomStateUndoData(&self->roomStateUndoData); self->roomStateUndoData = RoomState_createUndoData(self->gameState->rooms[self->gameState->currentRoomIndex]); return true; } static void addUndoNode(GameplayScreen * self) { RoomStateUndoStateDelta * undoStateDelta = RoomStateUndoStateDelta_create(self->gameState->rooms[self->gameState->currentRoomIndex], &self->roomStateUndoData); UndoStack_appendNode(self->undoStack, undoStateDelta); disposeRoomStateUndoData(&self->roomStateUndoData); self->roomStateUndoData = RoomState_createUndoData(self->gameState->rooms[self->gameState->currentRoomIndex]); } static bool turnEnded(Atom eventID, void * eventData, void * context) { GameplayScreen * self = context; addUndoNode(self); return true; } static void initGameState(GameplayScreen * self, GameSavedState * savedState) { self->gameState = GameState_create(self->gameData, savedState); self->gameView = GameView_create(self->gameState); self->undoStack = UndoStack_create(self->gameState->rooms[self->gameState->currentRoomIndex]); self->roomStateUndoData = RoomState_createUndoData(self->gameState->rooms[self->gameState->currentRoomIndex]); EventDispatcher_registerForEvent(self->gameState->eventDispatcher, ATOM_event_room_changed, roomChanged, self); EventDispatcher_registerForEvent(self->gameState->eventDispatcher, ATOM_event_turn_ended, turnEnded, self); } static void resetGameState(GameplayScreen * self, GameSavedState * savedState) { GameView_dispose(self->gameView); GameState_dispose(self->gameState); UndoStack_dispose(self->undoStack); disposeRoomStateUndoData(&self->roomStateUndoData); initGameState(self, savedState); playSoundEffect(SOUND_RESET); } bool GameplayScreen_init(GameplayScreen * self) { call_super(init, self); self->paused = false; self->gameData = GameData_create(); self->savedGameState = NULL; initGameState(self, NULL); return true; } void GameplayScreen_dispose(GameplayScreen * self) { GameView_dispose(self->gameView); GameState_dispose(self->gameState); GameData_dispose(self->gameData); if (self->savedGameState != NULL) { GameSavedState_dispose(self->savedGameState); } call_super_virtual(dispose, self); } static void pauseGame(GameplayScreen * self) { self->paused = true; pauseMusic(); } static void unpauseGame(GameplayScreen * self) { self->paused = false; unpauseMusic(); Shell_redisplay(); } void GameplayScreen_update(GameplayScreen * self, double deltaTime) { if (!self->paused && !self->backgrounded) { GameView_update(self->gameView, deltaTime); } } bool GameplayScreen_mouseMoved(GameplayScreen * self, float x, float y, float deltaX, float deltaY, unsigned int modifiers, double timestamp) { bool handled = false; if (!self->paused && !self->backgrounded) { handled = GameView_mouseMoved(self->gameView, x, y, deltaX, deltaY, modifiers, timestamp); if (handled) { Shell_redisplay(); } } return handled || call_super_virtual(mouseMoved, self, x, y, deltaX, deltaY, modifiers, timestamp); } bool GameplayScreen_mouseLeave(GameplayScreen * self, unsigned int modifiers, double timestamp) { GameView_hideMouseover(self->gameView); Shell_redisplay(); return true; } bool GameplayScreen_keyDown(GameplayScreen * self, unsigned int charCode, unsigned int keyCode, unsigned int modifiers, bool isRepeat, double timestamp) { if (keyCode == KEY_CODE_R && (modifiers & MODIFIER_ALT_BIT)) { resetGameState(self, NULL); Shell_redisplay(); return true; } return call_super_virtual(keyDown, self, charCode, keyCode, modifiers, isRepeat, timestamp); } void GameplayScreen_listRenderables(GameplayScreen * self, RenderableIO * renderableIO, int drawOrderOffset, Rect4i clipBounds) { GameView_listRenderables(self->gameView, renderableIO, drawOrderOffset, clipBounds); call_super_virtual(listRenderables, self, renderableIO, drawOrderOffset, clipBounds); } bool GameplayScreen_needsRedraw(GameplayScreen * self) { return !self->paused && !self->backgrounded; } static bool isMoveDirectionInput(Atom actionID, MoveInput_axis * outAxis, MoveInput_direction * outDirection) { if (actionID == ATOM_input_left) { *outAxis = MOVE_AXIS_X; *outDirection = MOVE_DIRECTION_NEGATIVE; return true; } if (actionID == ATOM_input_right) { *outAxis = MOVE_AXIS_X; *outDirection = MOVE_DIRECTION_POSITIVE; return true; } if (actionID == ATOM_input_up) { *outAxis = MOVE_AXIS_Y; *outDirection = MOVE_DIRECTION_NEGATIVE; return true; } if (actionID == ATOM_input_down) { *outAxis = MOVE_AXIS_Y; *outDirection = MOVE_DIRECTION_POSITIVE; return true; } return false; } static bool isActionInput(Atom actionID, ActionInput_action * outAction) { if (actionID == ATOM_input_wait) { *outAction = ACTION_WAIT; return true; } if (actionID == ATOM_input_interact) { *outAction = ACTION_INTERACT; return true; } return false; } static bool isMenuDirectionInput(Atom actionID, UINavigationDirection * outDirection) { if (actionID == ATOM_input_menu_left) { *outDirection = UI_LEFT; return true; } if (actionID == ATOM_input_menu_right) { *outDirection = UI_RIGHT; return true; } if (actionID == ATOM_input_menu_up) { *outDirection = UI_UP; return true; } if (actionID == ATOM_input_menu_down) { *outDirection = UI_DOWN; return true; } if (actionID == ATOM_input_menu_next) { *outDirection = UI_NEXT; return true; } if (actionID == ATOM_input_menu_previous) { *outDirection = UI_PREVIOUS; return true; } return false; } static bool isMenuActionInput(Atom actionID, unsigned int * outMenuAction) { if (actionID == ATOM_input_menu_accept) { *outMenuAction = MENU_ACTION_ACCEPT; return true; } if (actionID == ATOM_input_menu_alternate) { *outMenuAction = MENU_ACTION_ALTERNATE; return true; } if (actionID == ATOM_input_menu_cancel) { *outMenuAction = MENU_ACTION_CANCEL; return true; } return false; } static bool isMenuScrollInput(Atom actionID, int * outDeltaY) { if (actionID == ATOM_input_menu_scroll_up) { *outDeltaY = -1; return true; } if (actionID == ATOM_input_menu_scroll_down) { *outDeltaY = 1; return true; } return false; } static bool scrollFocusedElement(GameplayScreen * self, int deltaY, double timestamp) { bool handled = false; UIElement * focusedElement = call_virtual(getFocusedElement, self->rootContainer); if (focusedElement != NULL) { Rect4f bounds = call_virtual(getAbsoluteBounds, focusedElement); handled = call_virtual(scrollWheel, focusedElement, bounds.xMin, bounds.yMin, 0, deltaY, 0, 0, false, timestamp); } if (!handled) { Rect4f bounds = call_virtual(getAbsoluteBounds, self->rootContainer); handled = call_virtual(scrollWheel, self->rootContainer, bounds.xMin, bounds.yMin, 0, deltaY, 0, 0, false, timestamp); } return handled; } static void undo(GameplayScreen * self) { if (UndoStack_canUndo(self->undoStack)) { playSoundEffect(SOUND_UNDO); UndoStack_undo(self->undoStack); RoomState_undoActionPerformed(self->gameState->rooms[self->gameState->currentRoomIndex]); GameView_hideMouseover(self->gameView); disposeRoomStateUndoData(&self->roomStateUndoData); self->roomStateUndoData = RoomState_createUndoData(self->gameState->rooms[self->gameState->currentRoomIndex]); } } static void redo(GameplayScreen * self) { if (UndoStack_canRedo(self->undoStack)) { playSoundEffect(SOUND_REDO); UndoStack_redo(self->undoStack); RoomState_undoActionPerformed(self->gameState->rooms[self->gameState->currentRoomIndex]); GameView_hideMouseover(self->gameView); disposeRoomStateUndoData(&self->roomStateUndoData); self->roomStateUndoData = RoomState_createUndoData(self->gameState->rooms[self->gameState->currentRoomIndex]); } } static void saveGameState(GameplayScreen * self) { if (self->savedGameState != NULL) { GameSavedState_dispose(self->savedGameState); } self->savedGameState = GameState_createSave(self->gameState); GameSavedState_encodeUndoStack(self->savedGameState, self->undoStack); EventDispatcher_dispatchEvent(self->gameState->eventDispatcher, ATOM_event_game_state_saved, NULL); } static void loadGameState(GameplayScreen * self) { if (self->savedGameState != NULL) { resetGameState(self, self->savedGameState); GameSavedState_decodeUndoStack(self->savedGameState, self->undoStack); } } static bool actionDown(Atom eventID, void * eventData, void * context) { GameplayScreen * self = context; struct InputController_event * event = eventData; MoveInput_axis moveAxis; MoveInput_direction moveDirection; ActionInput_action action; UINavigationDirection menuDirection; unsigned int menuAction; int scrollDeltaY; bool handled = false; if (event->actionID == ATOM_input_pause) { if (self->paused) { unpauseGame(self); } else { pauseGame(self); } handled = true; } else if (isMoveDirectionInput(event->actionID, &moveAxis, &moveDirection)) { if (!self->paused) { if (self->gameView->roomTransition != NULL) { self->gameView->roomTransition->speedMultiplier = 2.0f; } handled = EntityPlayer_startMoveInput(self->gameState->player, moveAxis, moveDirection, false); } } else if (isActionInput(event->actionID, &action)) { if (!self->paused) { if (self->gameView->roomTransition != NULL) { self->gameView->roomTransition->speedMultiplier = 2.0f; } handled = EntityPlayer_startActionInput(self->gameState->player, action, false); } } else if (event->actionID == ATOM_input_reset) { GameState_resetCurrentRoom(self->gameState); self->undoStack->target = self->gameState->rooms[self->gameState->currentRoomIndex]; addUndoNode(self); playSoundEffect(SOUND_RESET); handled = true; } else if (event->actionID == ATOM_input_undo) { undo(self); handled = true; } else if (event->actionID == ATOM_input_redo) { redo(self); handled = true; } else if (event->actionID == ATOM_input_save) { saveGameState(self); handled = true; } else if (event->actionID == ATOM_input_load) { loadGameState(self); handled = true; } else if (isMenuDirectionInput(event->actionID, &menuDirection)) { handled = call_virtual(menuDirectionDown, self->rootContainer, menuDirection, false, event->timestamp); } else if (isMenuActionInput(event->actionID, &menuAction)) { handled = call_virtual(menuActionDown, self->rootContainer, menuAction, false, event->timestamp); } else if (isMenuScrollInput(event->actionID, &scrollDeltaY)) { handled = scrollFocusedElement(self, scrollDeltaY, event->timestamp); } if (handled) { Shell_redisplay(); } return handled; } static bool actionRepeat(Atom eventID, void * eventData, void * context) { GameplayScreen * self = context; struct InputController_event * event = eventData; MoveInput_axis moveAxis; MoveInput_direction moveDirection; ActionInput_action action; UINavigationDirection menuDirection; unsigned int menuAction; int scrollDeltaY; bool handled = false; if (isMoveDirectionInput(event->actionID, &moveAxis, &moveDirection)) { if (!self->paused) { if (self->gameView->roomTransition != NULL) { self->gameView->roomTransition->speedMultiplier = 2.0f; } handled = EntityPlayer_startMoveInput(self->gameState->player, moveAxis, moveDirection, true); } } else if (isActionInput(event->actionID, &action)) { if (!self->paused) { if (self->gameView->roomTransition != NULL) { self->gameView->roomTransition->speedMultiplier = 2.0f; } handled = EntityPlayer_startActionInput(self->gameState->player, action, true); } } else if (event->actionID == ATOM_input_undo) { undo(self); handled = true; } else if (event->actionID == ATOM_input_redo) { redo(self); handled = true; } else if (isMenuDirectionInput(event->actionID, &menuDirection)) { handled = call_virtual(menuDirectionDown, self->rootContainer, menuDirection, true, event->timestamp); } else if (isMenuActionInput(event->actionID, &menuAction)) { handled = call_virtual(menuActionDown, self->rootContainer, menuAction, true, event->timestamp); } else if (isMenuScrollInput(event->actionID, &scrollDeltaY)) { handled = scrollFocusedElement(self, scrollDeltaY, event->timestamp); } if (handled) { Shell_redisplay(); } return handled; } static bool actionUp(Atom eventID, void * eventData, void * context) { GameplayScreen * self = context; struct InputController_event * event = eventData; MoveInput_axis moveAxis; MoveInput_direction moveDirection; ActionInput_action action; UINavigationDirection menuDirection; unsigned int menuAction; bool handled = false; if (isMoveDirectionInput(event->actionID, &moveAxis, &moveDirection)) { handled = EntityPlayer_stopMoveInput(self->gameState->player, moveAxis, moveDirection); } else if (isActionInput(event->actionID, &action)) { handled = EntityPlayer_stopActionInput(self->gameState->player, action); } else if (isMenuDirectionInput(event->actionID, &menuDirection)) { handled = call_virtual(menuDirectionUp, self->rootContainer, menuDirection, event->timestamp); } else if (isMenuActionInput(event->actionID, &menuAction)) { handled = call_virtual(menuActionUp, self->rootContainer, menuAction, event->timestamp); } if (handled) { Shell_redisplay(); } return false; } void GameplayScreen_activate(GameplayScreen * self, Screen * lastScreen, const char * transitionName) { call_super_virtual(activate, self, lastScreen, transitionName); EventDispatcher_registerForEvent(g_gameSession->inputController->eventDispatcher, ATOM(INPUT_CONTROLLER_EVENT_ACTION_DOWN), actionDown, self); EventDispatcher_registerForEvent(g_gameSession->inputController->eventDispatcher, ATOM(INPUT_CONTROLLER_EVENT_ACTION_REPEAT), actionRepeat, self); EventDispatcher_registerForEvent(g_gameSession->inputController->eventDispatcher, ATOM(INPUT_CONTROLLER_EVENT_ACTION_UP), actionUp, self); self->paused = false; } void GameplayScreen_deactivate(GameplayScreen * self, Screen * lastScreen, const char * transitionName) { EventDispatcher_unregisterForEvent(g_gameSession->inputController->eventDispatcher, ATOM(INPUT_CONTROLLER_EVENT_ACTION_DOWN), actionDown, self); EventDispatcher_unregisterForEvent(g_gameSession->inputController->eventDispatcher, ATOM(INPUT_CONTROLLER_EVENT_ACTION_REPEAT), actionRepeat, self); EventDispatcher_unregisterForEvent(g_gameSession->inputController->eventDispatcher, ATOM(INPUT_CONTROLLER_EVENT_ACTION_UP), actionUp, self); call_super_virtual(deactivate, self, lastScreen, transitionName); }