/* Copyright (c) 2023 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 "dynamictypes/DataSerializer.h" #include "tileset/TileZoneMap.h" #include "utilities/IOUtilities.h" #include static TileZoneMap * TileZoneMap_createNoSchema(TileZoneMapID identifier, const char * name, unsigned int roomCount, TileZoneRoom * rooms) { TileZoneMap * zoneMap = malloc(sizeof(*zoneMap)); zoneMap->identifier = identifier; zoneMap->name = strdup_nullSafe(name); zoneMap->roomCount = roomCount; zoneMap->rooms = memdup(rooms, roomCount * sizeof(*rooms)); zoneMap->metadata = NULL; zoneMap->zoneSchemaInUse = NULL; return zoneMap; } TileZoneMap * TileZoneMap_create(TileZoneMapID identifier, const char * name, TilesetEditData * tileset, unsigned int roomCount, TileZoneRoom * rooms) { TileZoneMap * zoneMap = TileZoneMap_createNoSchema(identifier, name, roomCount, rooms); DataValueSchema * zoneSchema = TilesetEditData_getMetadataSchema(tileset, tileset->tileZoneSchemaID); if (zoneSchema == NULL) { zoneMap->metadata = NULL; } else { zoneMap->zoneSchemaInUse = DataValueSchema_copy(zoneSchema); zoneMap->metadata = DataValueSchema_createBlankValue(zoneSchema, DATA_TYPE_HASH_TABLE).value.hashTable; } return zoneMap; } TileZoneMap * TileZoneMap_copy(TileZoneMap * zoneMap) { TileZoneMap * copy = TileZoneMap_createNoSchema(zoneMap->identifier, zoneMap->name, zoneMap->roomCount, zoneMap->rooms); if (zoneMap->metadata != NULL) { copy->metadata = hashCopy(zoneMap->metadata); } if (zoneMap->zoneSchemaInUse != NULL) { copy->zoneSchemaInUse = DataValueSchema_copy(zoneMap->zoneSchemaInUse); } return copy; } void TileZoneMap_dispose(TileZoneMap * zoneMap) { free(zoneMap->name); free(zoneMap->rooms); if (zoneMap->metadata != NULL) { hashDispose(zoneMap->metadata); } if (zoneMap->zoneSchemaInUse != NULL) { DataValueSchema_dispose(zoneMap->zoneSchemaInUse); } free(zoneMap); } void TileZoneMap_serializeMinimal(TileZoneMap * zoneMap, compat_type(SerializationContext *) serializationContext) { SerializationContext * context = serializationContext; call_virtual(writeUInt32, context, "id", zoneMap->identifier); call_virtual(writeString, context, "name", zoneMap->name); call_virtual(writeUInt16, context, "schema_format_version", DATA_VALUE_SCHEMA_FORMAT_VERSION); call_virtual(beginStructure, context, "zone_schema"); call_virtual(writeBoolean, context, "defined", zoneMap->zoneSchemaInUse != NULL); if (zoneMap->zoneSchemaInUse != NULL) { DataValueSchema_serializeMinimal(zoneMap->zoneSchemaInUse, context); } call_virtual(endStructure, context); call_virtual(beginArray, context, "rooms"); for (unsigned int roomIndex = 0; roomIndex < zoneMap->roomCount; roomIndex++) { call_virtual(beginStructure, context, NULL); call_virtual(writeUInt32, context, "tile_map_id", zoneMap->rooms[roomIndex].tileMapID); call_virtual(writeInt16, context, "offset_x", zoneMap->rooms[roomIndex].offset.x); call_virtual(writeInt16, context, "offset_y", zoneMap->rooms[roomIndex].offset.y); call_virtual(writeUInt16, context, "size_x", zoneMap->rooms[roomIndex].size.x); call_virtual(writeUInt16, context, "size_y", zoneMap->rooms[roomIndex].size.y); call_virtual(writeInt16, context, "layer", zoneMap->rooms[roomIndex].zoneLayer); call_virtual(writeInt16, context, "sublayer", zoneMap->rooms[roomIndex].sublayer); call_virtual(endStructure, context); } call_virtual(endArray, context); if (zoneMap->zoneSchemaInUse != NULL) { DataValue metadataValue = valueCreateHashTable(zoneMap->metadata, false, false); DataValue_serializeWithSchema(&metadataValue, zoneMap->zoneSchemaInUse, "metadata", context); } } void TileZoneMap_serialize(TileZoneMap * zoneMap, compat_type(SerializationContext *) serializationContext) { serialize_implementation_v2(TileZoneMap, zoneMap, TILE_ZONE_MAP); } TileZoneMap * TileZoneMap_deserializeMinimal(compat_type(DeserializationContext *) deserializationContext, uint16_t formatVersion) { DeserializationContext * context = deserializationContext; if (context->status != SERIALIZATION_ERROR_OK) { return NULL; } if (formatVersion > TILE_ZONE_MAP_FORMAT_VERSION) { context->status = SERIALIZATION_ERROR_FORMAT_VERSION_TOO_NEW; return NULL; } TileMapID identifier = call_virtual(readUInt32, context, "id"); const char * name = call_virtual(readString, context, "name"); uint16_t schemaFormatVersion = call_virtual(readUInt16, context, "schema_format_version"); DataValueSchema * zoneSchema = NULL; call_virtual(beginStructure, context, "zone_schema"); if (call_virtual(readBoolean, context, "defined")) { zoneSchema = DataValueSchema_deserializeMinimal(context, schemaFormatVersion); } call_virtual(endStructure, context); unsigned int roomCount = call_virtual(beginArray, context, "rooms"); if (roomCount > TILE_ZONE_MAP_ROOM_COUNT_MAX) { return NULL; } DataHashTable * zoneMetadata = NULL; TileZoneRoom rooms[roomCount]; memset(rooms, 0, sizeof(rooms)); for (unsigned int roomIndex = 0; roomIndex < roomCount; roomIndex++) { call_virtual(beginStructure, context, NULL); rooms[roomIndex].tileMapID = call_virtual(readUInt32, context, "tile_map_id"); rooms[roomIndex].offset.x = call_virtual(readInt16, context, "offset_x"); rooms[roomIndex].offset.y = call_virtual(readInt16, context, "offset_y"); rooms[roomIndex].size.x = call_virtual(readUInt16, context, "size_x"); rooms[roomIndex].size.y = call_virtual(readUInt16, context, "size_y"); rooms[roomIndex].zoneLayer = call_virtual(readInt16, context, "layer"); rooms[roomIndex].sublayer = call_virtual(readInt16, context, "sublayer"); call_virtual(endStructure, context); } call_virtual(endArray, context); if (zoneSchema != NULL) { DataValue metadataValue = DataValue_deserializeWithSchema(zoneSchema, "metadata", DATA_TYPE_HASH_TABLE, context, false); if (metadataValue.type != DATA_TYPE_HASH_TABLE) { valueDispose(&metadataValue); return NULL; } zoneMetadata = metadataValue.value.hashTable; } if (context->status != SERIALIZATION_ERROR_OK) { return NULL; } TileZoneMap * zoneMap = TileZoneMap_createNoSchema(identifier, name, roomCount, rooms); zoneMap->metadata = zoneMetadata; zoneMap->zoneSchemaInUse = zoneSchema; return zoneMap; } TileZoneMap * TileZoneMap_deserialize(compat_type(DeserializationContext *) deserializationContext) { deserialize_implementation_v2(TileZoneMap, TILE_ZONE_MAP); } void TileZoneMap_addRoom(TileZoneMap * zoneMap, TileZoneRoom room) { TileZoneMap_insertRoom(zoneMap, zoneMap->roomCount, room); } void TileZoneMap_insertRoom(TileZoneMap * zoneMap, unsigned int roomIndex, TileZoneRoom room) { zoneMap->rooms = realloc(zoneMap->rooms, (zoneMap->roomCount + 1) * sizeof(*zoneMap->rooms)); for (unsigned int roomIndex2 = zoneMap->roomCount; roomIndex2 > roomIndex; roomIndex2--) { zoneMap->rooms[roomIndex2] = zoneMap->rooms[roomIndex2 - 1]; } zoneMap->rooms[roomIndex] = room; zoneMap->roomCount++; } void TileZoneMap_removeRoomAtIndex(TileZoneMap * zoneMap, unsigned int roomIndex) { if (roomIndex < zoneMap->roomCount) { zoneMap->roomCount--; for (; roomIndex < zoneMap->roomCount; roomIndex++) { zoneMap->rooms[roomIndex] = zoneMap->rooms[roomIndex + 1]; } } } void TileZoneMap_reorderRoom(TileZoneMap * zoneMap, unsigned int fromRoomIndex, unsigned int toRoomIndex) { assert(fromRoomIndex < zoneMap->roomCount); assert(toRoomIndex < zoneMap->roomCount); TileZoneRoom swap = zoneMap->rooms[fromRoomIndex]; int direction = (toRoomIndex > fromRoomIndex) * 2 - 1; for (unsigned int swapIndex = fromRoomIndex; swapIndex != toRoomIndex; swapIndex += direction) { zoneMap->rooms[swapIndex] = zoneMap->rooms[swapIndex + direction]; } zoneMap->rooms[toRoomIndex] = swap; } TileZoneRoom * TileZoneMap_getRoomWithTileMapID(TileZoneMap * zoneMap, TileMapID tileMapID, unsigned int * outRoomIndex) { for (unsigned int roomIndex = 0; roomIndex < zoneMap->roomCount; roomIndex++) { if (zoneMap->rooms[roomIndex].tileMapID == tileMapID) { if (outRoomIndex != NULL) { *outRoomIndex = roomIndex; } return &zoneMap->rooms[roomIndex]; } } return NULL; } TileZoneRoom * TileZoneMap_getRoomAtPosition(TileZoneMap * zoneMap, int zoneLayer, int sublayer, Vector2i layerPosition, unsigned int * outRoomIndex) { for (unsigned int roomIndex = 0; roomIndex < zoneMap->roomCount; roomIndex++) { if (zoneMap->rooms[roomIndex].zoneLayer == zoneLayer && zoneMap->rooms[roomIndex].sublayer == sublayer && Vector2i_isEqual(zoneMap->rooms[roomIndex].offset, layerPosition)) { if (outRoomIndex != NULL) { *outRoomIndex = roomIndex; } return &zoneMap->rooms[roomIndex]; } } return NULL; } TileZoneRoom TileZoneMap_roomForTileMap(TileZoneMap * zoneMap, TileMapEditData * tileMap) { TileZoneRoom room; room.offset = VECTOR2i(0, 0); if (tileMap == NULL) { room.tileMapID = 0; if (zoneMap->roomCount > 0) { room.size = zoneMap->rooms[zoneMap->roomCount - 1].size; } else { room.size = VECTOR2u(16, 12); } } else { room.tileMapID = tileMap->identifier; room.size = tileMap->size; } room.zoneLayer = 0; room.sublayer = 0; return room; }