// Copyright (c) 2023 Alex Diener. All rights reserved. #include "PROJECT_NAME/Atoms.h" #include "PROJECT_NAME/GameState.h" #include #include #define stemobject_implementation GameState stemobject_vtable_begin(); stemobject_vtable_entry(dispose); stemobject_vtable_end(); GameState * GameState_create(GameData * gameData, GameSavedState * savedState) { stemobject_create_implementation(init, gameData, savedState) } static bool passthroughCallback(Atom eventID, void * eventData, void * context) { GameState * self = context; return EventDispatcher_dispatchEvent(self->eventDispatcher, eventID, eventData); } static void registerForRoomStateEvents(GameState * self, RoomState * room) { EventDispatcher_registerForEvent(room->eventDispatcher, ATOM_event_player_bumped_wall, passthroughCallback, self); EventDispatcher_registerForEvent(room->eventDispatcher, ATOM_event_player_moved, passthroughCallback, self); EventDispatcher_registerForEvent(room->eventDispatcher, ATOM_event_player_move_failed, passthroughCallback, self); EventDispatcher_registerForEvent(room->eventDispatcher, ATOM_event_tile_destroyed, passthroughCallback, self); EventDispatcher_registerForEvent(room->eventDispatcher, ATOM_event_pickup_collected, passthroughCallback, self); EventDispatcher_registerForEvent(room->eventDispatcher, ATOM_event_block_pushed, passthroughCallback, self); EventDispatcher_registerForEvent(room->eventDispatcher, ATOM_event_enemy_killed, passthroughCallback, self); EventDispatcher_registerForEvent(room->eventDispatcher, ATOM_event_player_killed, passthroughCallback, self); } static EntityPlayer * createDefaultPlayer(GameState * self) { Vector2i startTilePosition = {0, 0}; FacingDirection facing = FACING_SOUTH; DataHashTable * roomEntrance = RoomData_getRoomEntranceByName(&self->gameData->rooms[self->currentRoomIndex], self->gameData->startingRoomEntranceName, &startTilePosition); if (roomEntrance == NULL) { #ifdef DEBUG fprintf(stderr, "Error: Couldn't find room entrance ID \"%s\"\n", self->gameData->startingRoomEntranceName); #endif } else { facing = valueGetInt32(hashGet(roomEntrance, "facing")); } EntityPlayer * player = EntityPlayer_create(startTilePosition, self->rooms[self->currentRoomIndex]); EntityPlayer_setFacing(player, facing); call_virtual(postinit, player); return player; } static EntityPlayer * createPlayerFromSaveData(void * playerData, unsigned int playerDataSize, struct RoomState * roomState) { struct memreadContext context = memreadContextInit(playerData, playerDataSize); return EntityPlayer_createWithEncodedState(&context, roomState); } bool GameState_init(GameState * self, GameData * gameData, GameSavedState * savedState) { call_super(init, self); self->eventDispatcher = EventDispatcher_create(); self->gameData = gameData; self->roomCount = self->gameData->roomCount; self->rooms = malloc(self->roomCount * sizeof(*self->rooms)); for (unsigned int roomIndex = 0; roomIndex < self->roomCount; roomIndex++) { RoomSavedState * roomSavedState = NULL; if (savedState != NULL) { roomSavedState = savedState->roomSaves[roomIndex]; } self->rooms[roomIndex] = RoomState_create(roomIndex, &self->gameData->rooms[roomIndex], self, roomSavedState == NULL); if (roomSavedState != NULL) { RoomState_loadSavedState(self->rooms[roomIndex], roomSavedState); } registerForRoomStateEvents(self, self->rooms[roomIndex]); } if (savedState != NULL) { self->currentRoomIndex = savedState->currentRoomIndex; self->player = createPlayerFromSaveData(savedState->playerData, savedState->playerDataSize, self->rooms[self->currentRoomIndex]); } else { if (GameData_getRoomDataByIdentifier(self->gameData, self->gameData->startingRoomIdentifier, &self->currentRoomIndex) == NULL) { #ifdef DEBUG fprintf(stderr, "Error: Couldn't find starting room ID %u\n", self->gameData->startingRoomIdentifier); #endif self->currentRoomIndex = 0; } self->player = createDefaultPlayer(self); } RoomState_insertEntity(self->rooms[self->currentRoomIndex], 0, self->player, false); call_virtual(postinit, self->player); if (savedState == NULL) { RoomState_entered(self->rooms[self->currentRoomIndex]); self->currentRoomStateCopy = RoomState_copy(self->rooms[self->currentRoomIndex]); RoomState_removeEntity(self->currentRoomStateCopy, self->player); self->roomResetPlayer = call_virtual(copy, self->player, self->rooms[self->currentRoomIndex]); } else { self->currentRoomStateCopy = RoomState_create(self->currentRoomIndex, &self->gameData->rooms[self->currentRoomIndex], self, false); RoomState_loadSavedState(self->currentRoomStateCopy, savedState->roomResetSave); self->roomResetPlayer = createPlayerFromSaveData(savedState->roomResetPlayerData, savedState->roomResetPlayerDataSize, self->rooms[self->currentRoomIndex]); } return true; } void GameState_dispose(GameState * self) { for (unsigned int roomIndex = 0; roomIndex < self->roomCount; roomIndex++) { call_virtual(dispose, self->rooms[roomIndex]); } free(self->rooms); RoomState_dispose(self->currentRoomStateCopy); call_virtual(dispose, self->player); call_virtual(dispose, self->roomResetPlayer); EventDispatcher_dispose(self->eventDispatcher); call_super_virtual(dispose, self); } void GameState_advanceTurn(GameState * self) { EventDispatcher_dispatchEvent(self->eventDispatcher, ATOM_event_turn_started, NULL); RoomState_advanceTurn(self->rooms[self->currentRoomIndex]); EventDispatcher_dispatchEvent(self->eventDispatcher, ATOM_event_turn_ended, NULL); Rect4i roomBounds = RoomData_getLocalBounds(self->rooms[self->currentRoomIndex]->roomData); Vector2i playerPosition = self->player->positionComponent.position; if (playerPosition.x < roomBounds.xMin || playerPosition.x >= roomBounds.xMax || playerPosition.y < roomBounds.yMin || playerPosition.y >= roomBounds.yMax) { unsigned int newRoomIndex = GameData_getRoomIndexAtZonePosition(self->gameData, self->rooms[self->currentRoomIndex]->roomData->zoneLayer, VECTOR2i(playerPosition.x + self->rooms[self->currentRoomIndex]->roomData->zonePosition.x, playerPosition.y + self->rooms[self->currentRoomIndex]->roomData->zonePosition.y)); if (newRoomIndex != UINT_MAX) { GameState_changeRoom(self, newRoomIndex); } else { // TODO: Handle out of bounds without a corresponding room (force undo?) //GameState_respawnPlayerOutOfBounds(self); } } } void GameState_changeRoom(GameState * self, unsigned int newRoomIndex) { Vector2i tileOffset = Vector2i_subtract(self->rooms[self->currentRoomIndex]->roomData->zonePosition, self->rooms[newRoomIndex]->roomData->zonePosition); EntityPlayer_offsetLocalPosition(self->player, tileOffset); RoomState_removeEntity(self->rooms[self->currentRoomIndex], self->player); RoomState_insertEntity(self->rooms[newRoomIndex], 0, self->player, false); self->player->roomState = self->rooms[newRoomIndex]; struct GameState_roomChangedEvent eventData = {self->currentRoomIndex, newRoomIndex}; RoomState_exited(self->rooms[self->currentRoomIndex]); self->currentRoomIndex = newRoomIndex; RoomState_entered(self->rooms[self->currentRoomIndex]); RoomState_dispose(self->currentRoomStateCopy); self->currentRoomStateCopy = RoomState_copy(self->rooms[self->currentRoomIndex]); RoomState_removeEntity(self->currentRoomStateCopy, self->player); call_virtual(dispose, self->roomResetPlayer); self->roomResetPlayer = call_virtual(copy, self->player, self->rooms[self->currentRoomIndex]); EventDispatcher_dispatchEvent(self->eventDispatcher, ATOM_event_room_changed, &eventData); } void GameState_resetCurrentRoom(GameState * self) { RoomState_dispose(self->rooms[self->currentRoomIndex]); self->rooms[self->currentRoomIndex] = RoomState_copy(self->currentRoomStateCopy); call_virtual(dispose, self->player); self->player = call_virtual(copy, self->roomResetPlayer, self->rooms[self->currentRoomIndex]); RoomState_insertEntity(self->rooms[self->currentRoomIndex], 0, self->player, false); registerForRoomStateEvents(self, self->rooms[self->currentRoomIndex]); } GameSavedState * GameState_createSave(GameState * self) { GameSavedState * save = GameSavedState_create(self->roomCount, self->currentRoomIndex, self->player, self->roomResetPlayer); for (unsigned int roomIndex = 0; roomIndex < self->roomCount; roomIndex++) { if (self->rooms[roomIndex]->visited) { save->roomSaves[roomIndex] = RoomState_createSave(self->rooms[roomIndex]); } } save->roomResetSave = RoomState_createSave(self->currentRoomStateCopy); return save; }