// Copyright (c) 2023 Alex Diener. All rights reserved. #include "gamemath/Vector3i.h" #include "gamemath/VectorConversions.h" #include "PROJECT_NAME/Atoms.h" #include "PROJECT_NAME/EntityDestructibleTile.h" #include "PROJECT_NAME/EntityPushBlock.h" #include "PROJECT_NAME/EntityToken.h" #include "PROJECT_NAME/EntityWallCrawler.h" #include "PROJECT_NAME/GameState.h" #include "PROJECT_NAME/RoomState.h" #include "PROJECT_NAME/SharedDefinitions.h" #include "utilities/IOUtilities.h" #include "MetadataKeys.h" #include #include #include #define stemobject_implementation RoomState stemobject_vtable_begin(); stemobject_vtable_entry(dispose); stemobject_vtable_end(); typedef enum TileIDWithEntity { TILE_ID_TOKEN = 4, TILE_ID_WALL_CRUMBLY = 6, TILE_ID_WALL_CRAWLER = 7, TILE_ID_PUSH_BLOCK = 8, TILE_ID_MULTIPUSH_BLOCK = 9 } TileIDWithEntity; RoomState * RoomState_create(unsigned int roomID, RoomData * roomData, GameState * gameState, bool initEntities) { stemobject_create_implementation(init, roomID, roomData, gameState, initEntities) } static bool hasTileEntity(TileIDWithEntity tileID) { switch (tileID) { case TILE_ID_TOKEN: case TILE_ID_WALL_CRUMBLY: case TILE_ID_WALL_CRAWLER: case TILE_ID_PUSH_BLOCK: case TILE_ID_MULTIPUSH_BLOCK: return true; } return false; } static compat_type(GameEntity *) createGameEntityForTileID(RoomState * self, TileIDWithEntity tileID, Vector2i position, DataHashTable * metadata) { switch (tileID) { case TILE_ID_TOKEN: return EntityToken_create(position, self); case TILE_ID_WALL_CRUMBLY: return EntityDestructibleTile_create(position, false, self); case TILE_ID_WALL_CRAWLER: return EntityWallCrawler_create(position, valueGetInt32(hashGet(metadata, METADATA_KEY_facing)), self); case TILE_ID_PUSH_BLOCK: return EntityPushBlock_create(position, false, self); case TILE_ID_MULTIPUSH_BLOCK: return EntityPushBlock_create(position, true, self); } return NULL; } static void initRoomEntities(RoomState * self, TileInstanceGrid * tileGrid, Vector2i offset) { for (unsigned int rowIndex = 0; rowIndex < tileGrid->size.y; rowIndex++) { for (unsigned int columnIndex = 0; columnIndex < tileGrid->size.x; columnIndex++) { TileID tileID = tileGrid->tileInstances[rowIndex * tileGrid->size.x + columnIndex].tileID; DataHashTable * metadata = tileGrid->tileInstances[rowIndex * tileGrid->size.x + columnIndex].metadata; Vector2i position = VECTOR2i(columnIndex + offset.x, rowIndex + offset.y); GameEntity * entity = createGameEntityForTileID(self, tileID, position, metadata); if (entity != NULL) { self->entities[self->entityCount].entity = entity; self->entities[self->entityCount].owned = true; self->entityCount++; } } } } static void postinitEntities(RoomState * self) { for (unsigned int entityIndex = 0; entityIndex < self->entityCount; entityIndex++) { call_virtual(postinit, self->entities[entityIndex].entity); } } bool RoomState_init(RoomState * self, unsigned int roomID, RoomData * roomData, GameState * gameState, bool initEntities) { call_super(init, self); self->eventDispatcher = EventDispatcher_create(); self->roomID = roomID; self->roomData = roomData; self->gameState = gameState; self->entities = NULL; self->entityCount = 0; self->visited = false; if (initEntities) { for (unsigned int layerIndex = 0; layerIndex < self->roomData->layerCount; layerIndex++) { if (self->roomData->layers[layerIndex].type == ROOM_LAYER_TYPE_ENTITY) { TileInstanceGrid * tileGrid = &self->roomData->layers[layerIndex].tileGrid; for (unsigned int rowIndex = 0; rowIndex < tileGrid->size.y; rowIndex++) { for (unsigned int columnIndex = 0; columnIndex < tileGrid->size.x; columnIndex++) { if (hasTileEntity(tileGrid->tileInstances[rowIndex * tileGrid->size.x + columnIndex].tileID)) { self->entityCount++; } } } } } self->private_ivar(entityAllocatedCount) = self->entityCount; if (self->entityCount > 0) { self->entities = malloc(self->private_ivar(entityAllocatedCount) * sizeof(*self->entities)); self->entityCount = 0; for (unsigned int layerIndex = 0; layerIndex < self->roomData->layerCount; layerIndex++) { if (self->roomData->layers[layerIndex].type == ROOM_LAYER_TYPE_ENTITY) { initRoomEntities(self, &self->roomData->layers[layerIndex].tileGrid, self->roomData->layers[layerIndex].offset); } } } postinitEntities(self); } return true; } RoomState * RoomState_copy(RoomState * self) { RoomState * copy = RoomState_create(self->roomID, self->roomData, self->gameState, false); copy->entityCount = copy->private_ivar(entityAllocatedCount) = self->entityCount; if (copy->entityCount > 0) { copy->entities = malloc(copy->private_ivar(entityAllocatedCount) * sizeof(*copy->entities)); for (unsigned int entityIndex = 0; entityIndex < copy->entityCount; entityIndex++) { copy->entities[entityIndex] = self->entities[entityIndex]; if (self->entities[entityIndex].owned) { copy->entities[entityIndex].entity = call_virtual(copy, self->entities[entityIndex].entity, copy); } } postinitEntities(copy); } copy->visited = self->visited; return copy; } void RoomState_dispose(RoomState * self) { for (unsigned int entityIndex = 0; entityIndex < self->entityCount; entityIndex++) { if (self->entities[entityIndex].owned) { call_virtual(dispose, self->entities[entityIndex].entity); } } free(self->entities); call_super_virtual(dispose, self); } static void clearRemovedEntities(RoomState * self) { unsigned int entityOffset = 0; for (unsigned int entityIndex = 0; entityIndex < self->entityCount; entityIndex++) { self->entities[entityIndex] = self->entities[entityIndex + entityOffset]; if (self->entities[entityIndex].entity->markedForRemoval) { EventDispatcher_dispatchEvent(self->eventDispatcher, ATOM_event_entity_removed, self->entities[entityIndex].entity); if (self->entities[entityIndex].owned) { call_virtual(dispose, self->entities[entityIndex].entity); } self->entityCount--; entityIndex--; entityOffset++; } } } void RoomState_entered(RoomState * self) { self->visited = true; for (unsigned int entityIndex = 0; entityIndex < self->entityCount; entityIndex++) { call_virtual(roomEntered, self->entities[entityIndex].entity); } clearRemovedEntities(self); } void RoomState_exited(RoomState * self) { for (unsigned int entityIndex = 0; entityIndex < self->entityCount; entityIndex++) { call_virtual(roomExited, self->entities[entityIndex].entity); } clearRemovedEntities(self); } void RoomState_advanceTurn(RoomState * self) { for (unsigned int entityIndex = 0; entityIndex < self->entityCount; entityIndex++) { call_virtual(preadvance, self->entities[entityIndex].entity); } for (unsigned int entityIndex = 0; entityIndex < self->entityCount; entityIndex++) { if (!self->entities[entityIndex].entity->markedForRemoval) { call_virtual(advanceTurn, self->entities[entityIndex].entity); } } for (unsigned int entityIndex = 0; entityIndex < self->entityCount; entityIndex++) { if (!self->entities[entityIndex].entity->markedForRemoval) { call_virtual(postadvance, self->entities[entityIndex].entity); } } clearRemovedEntities(self); } void RoomState_undoActionPerformed(RoomState * self) { for (unsigned int entityIndex = 0; entityIndex < self->entityCount; entityIndex++) { call_virtual(undoActionPerformed, self->entities[entityIndex].entity); } } void RoomState_addEntity(RoomState * self, compat_type(GameEntity *) entity, bool takeOwnership) { RoomState_insertEntity(self, self->entityCount, entity, takeOwnership); } void RoomState_insertEntity(RoomState * self, unsigned int insertIndex, compat_type(GameEntity *) entity, bool takeOwnership) { assert(insertIndex <= self->entityCount); if (self->entityCount >= self->private_ivar(entityAllocatedCount)) { self->private_ivar(entityAllocatedCount) = self->private_ivar(entityAllocatedCount) * 2 + (self->private_ivar(entityAllocatedCount) == 0); self->entities = realloc(self->entities, self->private_ivar(entityAllocatedCount) * sizeof(*self->entities)); } for (unsigned int entityIndex = self->entityCount; entityIndex > insertIndex; entityIndex--) { self->entities[entityIndex] = self->entities[entityIndex - 1]; } self->entities[insertIndex].entity = entity; self->entities[insertIndex].owned = takeOwnership; self->entityCount++; EventDispatcher_dispatchEvent(self->eventDispatcher, ATOM_event_entity_added, self->entities[insertIndex].entity); } void RoomState_removeEntity(RoomState * self, compat_type(GameEntity *) entity) { for (unsigned int entityIndex = 0; entityIndex < self->entityCount; entityIndex++) { if (self->entities[entityIndex].entity == entity) { RoomState_removeEntityAtIndex(self, entityIndex); break; } } } void RoomState_removeEntityAtIndex(RoomState * self, unsigned int entityIndex) { assert(entityIndex < self->entityCount); EventDispatcher_dispatchEvent(self->eventDispatcher, ATOM_event_entity_removed, self->entities[entityIndex].entity); if (self->entities[entityIndex].owned) { call_virtual(dispose, self->entities[entityIndex].entity); } self->entityCount--; for (; entityIndex < self->entityCount; entityIndex++) { self->entities[entityIndex] = self->entities[entityIndex + 1]; } } TilePropertyBits RoomState_getTilePropertiesAtPosition(RoomState * self, Vector2i position) { TilePropertyBits tileProperties = 0; bool outOfBounds = true; for (unsigned int layerIndex = 0; layerIndex < self->roomData->layerCount; layerIndex++) { if (self->roomData->layers[layerIndex].type == ROOM_LAYER_TYPE_GROUND || self->roomData->layers[layerIndex].type == ROOM_LAYER_TYPE_OBSTACLE) { Vector2i positionInTileGrid = Vector2i_subtract(position, self->roomData->layers[layerIndex].offset); TileInstanceGrid * tileGrid = &self->roomData->layers[layerIndex].tileGrid; if (positionInTileGrid.x >= 0 && (unsigned int) positionInTileGrid.x < tileGrid->size.x && positionInTileGrid.y >= 0 && (unsigned int) positionInTileGrid.y < tileGrid->size.y) { outOfBounds = false; tileProperties |= getTileProperties(tileGrid->tileInstances[positionInTileGrid.y * tileGrid->size.x + positionInTileGrid.x].tileID); } } } if (outOfBounds) { tileProperties |= TILE_OUT_OF_BOUNDS; } return tileProperties; } unsigned int RoomState_getEntityCountAtPosition(RoomState * self, Vector2i position) { unsigned int entityCount = 0; for (unsigned int entityIndex = 0; entityIndex < self->entityCount; entityIndex++) { EntityComponent_position * positionComponent = call_virtual(getComponent, self->entities[entityIndex].entity, COMPONENT_POSITION); if (positionComponent != NULL && positionComponent->position.x == position.x && positionComponent->position.y == position.y) { entityCount++; } } return entityCount; } GameEntity * RoomState_getEntityAtPositionAtIndex(RoomState * self, Vector2i position, unsigned int index) { unsigned int entityCount = 0; for (unsigned int entityIndex = 0; entityIndex < self->entityCount; entityIndex++) { EntityComponent_position * positionComponent = call_virtual(getComponent, self->entities[entityIndex].entity, COMPONENT_POSITION); if (positionComponent != NULL && positionComponent->position.x == position.x && positionComponent->position.y == position.y) { if (entityCount == index) { return self->entities[entityIndex].entity; } entityCount++; } } return NULL; } RoomSavedState * RoomState_createSave(RoomState * self) { RoomSavedState * save = RoomSavedState_create(); for (unsigned int entityIndex = 0; entityIndex < self->entityCount; entityIndex++) { if (self->entities[entityIndex].owned) { RoomSavedState_encodeEntity(save, self->entities[entityIndex].entity); } } return save; } void RoomState_loadSavedState(RoomState * self, RoomSavedState * savedState) { self->entityCount = RoomSavedState_getEntityCount(savedState); self->private_ivar(entityAllocatedCount) = self->entityCount; if (self->entityCount > 0) { self->entities = malloc(self->private_ivar(entityAllocatedCount) * sizeof(*self->entities)); self->entityCount = 0; for (unsigned int entityIndex = 0; entityIndex < self->private_ivar(entityAllocatedCount); entityIndex++) { self->entities[entityIndex].entity = RoomSavedState_decodeEntityAtIndex(savedState, entityIndex, self); self->entities[entityIndex].owned = true; self->entityCount++; } } postinitEntities(self); self->visited = true; } RoomStateUndoData RoomState_createUndoData(RoomState * self) { RoomStateUndoData undoData = {self->entityCount, memdup(self->entities, self->entityCount * sizeof(*self->entities))}; for (unsigned int entityIndex = 0; entityIndex < undoData.entityCount; entityIndex++) { undoData.entities[entityIndex].entity = call_virtual(copy, undoData.entities[entityIndex].entity, self); } return undoData; } void disposeRoomStateUndoData(RoomStateUndoData * undoData) { for (unsigned int entityIndex = 0; entityIndex < undoData->entityCount; entityIndex++) { if (undoData->entities[entityIndex].owned) { call_virtual(dispose, undoData->entities[entityIndex].entity); } } free(undoData->entities); }