/* Copyright (c) 2022 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 "serialization/DeserializationContext.h" #include "serialization/SerializationContext.h" #include "tileset/Tileset.h" #include "utilities/IOUtilities.h" #include #include Tileset * Tileset_create(const char * name, Vector2u tileSize, Vector2i origin, AutoTileRuleset autoTileAdditionalRuleset, unsigned int tileCount, Tile * tiles) { Tileset * tileset = malloc(sizeof(*tileset)); tileset->name = strdup_nullSafe(name); tileset->tileSize = tileSize; tileset->origin = origin; tileset->autoTileAdditionalRuleset = autoTileAdditionalRuleset; tileset->autoTileAdditionalRuleset.rules = memdup(autoTileAdditionalRuleset.rules, sizeof(*autoTileAdditionalRuleset.rules) * autoTileAdditionalRuleset.ruleCount); tileset->tileCount = tileCount; assert(tileCount > 0); tileset->tiles = malloc(tileCount * sizeof(*tileset->tiles)); for (unsigned int tileIndex = 0; tileIndex < tileCount; tileIndex++) { tileset->tiles[tileIndex] = tiles[tileIndex]; tileset->tiles[tileIndex].name = strdup(tiles[tileIndex].name); } return tileset; } void Tileset_dispose(Tileset * tileset) { free(tileset->name); free(tileset->autoTileAdditionalRuleset.rules); for (unsigned int tileIndex = 0; tileIndex < tileset->tileCount; tileIndex++) { free(tileset->tiles[tileIndex].name); } free(tileset->tiles); free(tileset); } Tileset * Tileset_copy(Tileset * tileset) { return Tileset_create(tileset->name, tileset->tileSize, tileset->origin, tileset->autoTileAdditionalRuleset, tileset->tileCount, tileset->tiles); } Tile * Tileset_lookUpTileByTileID(Tileset * tileset, TileID tileID) { for (unsigned int tileIndex = 0; tileIndex < tileset->tileCount; tileIndex++) { if (tileset->tiles[tileIndex].tileID == tileID) { return &tileset->tiles[tileIndex]; } } return NULL; } unsigned int Tileset_getTileIndexByTileID(Tileset * tileset, TileID tileID) { for (unsigned int tileIndex = 0; tileIndex < tileset->tileCount; tileIndex++) { if (tileset->tiles[tileIndex].tileID == tileID) { return tileIndex; } } return UINT_MAX; } unsigned int Tileset_getTileIndexByName(Tileset * tileset, const char * name) { for (unsigned int tileIndex = 0; tileIndex < tileset->tileCount; tileIndex++) { if (!strcmp(tileset->tiles[tileIndex].name, name)) { return tileIndex; } } return UINT_MAX; } TileID Tileset_getFirstUnusedTileID(Tileset * tileset) { TileID tileIDMax = 0; for (unsigned int tileIndex = 0; tileIndex < tileset->tileCount; tileIndex++) { if (tileset->tiles[tileIndex].tileID > tileIDMax) { tileIDMax = tileset->tiles[tileIndex].tileID; } } return tileIDMax + 1; } static const char * variationNames[AUTO_TILE_VARIATION_COUNT] = { "surrounded", "west_edge", "east_edge", "west_east_edges", "north_edge", "northwest_corner", "northeast_corner", "north_dead_end", "south_edge", "southwest_corner", "southeast_corner", "south_dead_end", "north_south_edges", "west_dead_end", "east_dead_end", "isolated" }; Tileset * Tileset_deserialize(compat_type(DeserializationContext *) deserializationContext) { DeserializationContext * context = deserializationContext; call_virtual(beginStructure, context, TILESET_FORMAT_TYPE); const char * formatType = call_virtual(readString, context, "format_type"); if (context->status != SERIALIZATION_ERROR_OK || strcmp(formatType, TILESET_FORMAT_TYPE)) { return NULL; } uint16_t formatVersion = call_virtual(readUInt16, context, "format_version"); if (context->status != SERIALIZATION_ERROR_OK || formatVersion > TILESET_FORMAT_VERSION) { return NULL; } const char * name = call_virtual(readStringNullable, context, "name"); Vector2u tileSize; tileSize.x = call_virtual(readUInt16, context, "base_width"); tileSize.y = call_virtual(readUInt16, context, "base_height"); call_virtual(beginStructure, context, "offset"); Vector2i origin; origin.x = call_virtual(readInt16, context, "x"); origin.y = call_virtual(readInt16, context, "y"); call_virtual(endStructure, context); unsigned int tileCount = call_virtual(beginDictionary, context, "tiles"); if (tileCount == 0 || tileSize.x == 0 || tileSize.y == 0 || context->status != SERIALIZATION_ERROR_OK) { return NULL; } Tile tiles[tileCount]; for (unsigned int tileIndex = 0; tileIndex < tileCount; tileIndex++) { tiles[tileIndex].name = (char *) call_virtual(readNextDictionaryKey, context); call_virtual(beginStructure, context, tiles[tileIndex].name); tiles[tileIndex].tileID = call_virtual(readUInt32, context, "tile_id"); call_virtual(beginStructure, context, "offset"); tiles[tileIndex].offset.x = call_virtual(readInt16, context, "x"); tiles[tileIndex].offset.y = call_virtual(readInt16, context, "y"); call_virtual(endStructure, context); call_virtual(beginStructure, context, "auto_tile_variations"); for (unsigned int variationIndex = 0; variationIndex < AUTO_TILE_VARIATION_COUNT; variationIndex++) { tiles[tileIndex].autoTileVariations[variationIndex] = call_virtual(readUInt32, context, variationNames[variationIndex]); } call_virtual(endStructure, context); tiles[tileIndex].autoTileAtEdge = call_virtual(readBoolean, context, "auto_tile_at_edge"); call_virtual(endStructure, context); } call_virtual(endDictionary, context); call_virtual(endStructure, context); if (context->status != SERIALIZATION_ERROR_OK) { return NULL; } return Tileset_create(name, tileSize, origin, AutoTileRuleset_none, tileCount, tiles); } void Tileset_serialize(Tileset * tileset, compat_type(SerializationContext *) serializationContext) { SerializationContext * context = serializationContext; call_virtual(beginStructure, context, TILESET_FORMAT_TYPE); call_virtual(writeString, context, "format_type", TILESET_FORMAT_TYPE); call_virtual(writeUInt16, context, "format_version", TILESET_FORMAT_VERSION); call_virtual(writeStringNullable, context, "name", tileset->name); call_virtual(writeUInt16, context, "base_width", tileset->tileSize.x); call_virtual(writeUInt16, context, "base_height", tileset->tileSize.y); call_virtual(beginStructure, context, "origin"); call_virtual(writeInt16, context, "x", tileset->origin.x); call_virtual(writeInt16, context, "y", tileset->origin.y); call_virtual(endStructure, context); /*call_virtual(beginArray, context, "auto_tile_rules"); for (unsigned int ruleIndex = 0; ruleIndex < tileset->autoTileRuleset.ruleCount; ruleIndex++) { call_virtual(beginStructure, context, NULL); call_virtual(writeUInt32, context, "base_tile_id", tileset->autoTileRuleset.rules[ruleIndex].baseTileID); call_virtual(writeUInt32, context, "adjusted_tile_id", tileset->autoTileRuleset.rules[ruleIndex].adjustedTileID); call_virtual(writeUInt8, context, "northwest", tileset->autoTileRuleset.rules[ruleIndex].adjacentDirections[ADJACENT_NORTHWEST]); call_virtual(writeUInt8, context, "north", tileset->autoTileRuleset.rules[ruleIndex].adjacentDirections[ADJACENT_NORTH]); call_virtual(writeUInt8, context, "northeast", tileset->autoTileRuleset.rules[ruleIndex].adjacentDirections[ADJACENT_NORTHEAST]); call_virtual(writeUInt8, context, "west", tileset->autoTileRuleset.rules[ruleIndex].adjacentDirections[ADJACENT_WEST]); call_virtual(writeUInt8, context, "east", tileset->autoTileRuleset.rules[ruleIndex].adjacentDirections[ADJACENT_EAST]); call_virtual(writeUInt8, context, "southwest", tileset->autoTileRuleset.rules[ruleIndex].adjacentDirections[ADJACENT_SOUTHWEST]); call_virtual(writeUInt8, context, "south", tileset->autoTileRuleset.rules[ruleIndex].adjacentDirections[ADJACENT_SOUTH]); call_virtual(writeUInt8, context, "southeast", tileset->autoTileRuleset.rules[ruleIndex].adjacentDirections[ADJACENT_SOUTHEAST]); call_virtual(writeBitfield8, context, "options", tileset->autoTileRuleset.rules[ruleIndex].options, ); call_virtual(endStructure, context); } call_virtual(endArray, context);*/ call_virtual(beginDictionary, context, "tiles"); for (unsigned int tileIndex = 0; tileIndex < tileset->tileCount; tileIndex++) { call_virtual(beginStructure, context, tileset->tiles[tileIndex].name); call_virtual(writeUInt32, context, "tile_id", tileset->tiles[tileIndex].tileID); call_virtual(beginStructure, context, "offset"); call_virtual(writeInt16, context, "x", tileset->tiles[tileIndex].offset.x); call_virtual(writeInt16, context, "y", tileset->tiles[tileIndex].offset.y); call_virtual(endStructure, context); call_virtual(beginStructure, context, "auto_tile_variations"); for (unsigned int variationIndex = 0; variationIndex < AUTO_TILE_VARIATION_COUNT; variationIndex++) { call_virtual(writeUInt32, context, variationNames[variationIndex], tileset->tiles[tileIndex].autoTileVariations[variationIndex]); } call_virtual(endStructure, context); call_virtual(writeBoolean, context, "auto_tile_at_edge", tileset->tiles[tileIndex].autoTileAtEdge); call_virtual(endStructure, context); } call_virtual(endDictionary, context); call_virtual(endStructure, context); } void Tileset_resolveAtlasEntries(Tileset * tileset, TextureAtlas * atlas) { for (unsigned int tileIndex = 0; tileIndex < tileset->tileCount; tileIndex++) { tileset->tiles[tileIndex].atlasEntry = TextureAtlas_lookup(atlas, HashTable_stringKey(tileset->tiles[tileIndex].name)); } } AutoTileRuleset Tileset_createAutoTileRuleset(Tileset * tileset) { AutoTileRuleset ruleset = tileset->autoTileAdditionalRuleset; ruleset.rules = memdup(ruleset.rules, sizeof(*ruleset.rules) * ruleset.ruleCount); struct { enum TilesetAutoTileVariation variation; TileID directionMask[ADJACENT_DIRECTION_COUNT]; uint8_t options; } variations[AUTO_TILE_VARIATION_COUNT] = { {AUTO_TILE_VARIATION_SURROUNDED, {-1, 0, -1, 0, 0, -1, 0, -1}, 0}, {AUTO_TILE_VARIATION_ISOLATED, {-1, 0, -1, 0, 0, -1, 0, -1}, AUTO_TILE_OPTION_NEGATIVE}, {AUTO_TILE_VARIATION_SOUTH_DEAD_END, {-1, -1, -1, 0, 0, -1, 0, -1}, AUTO_TILE_OPTION_NEGATIVE}, {AUTO_TILE_VARIATION_NORTH_DEAD_END, {-1, 0, -1, 0, 0, -1, -1, -1}, AUTO_TILE_OPTION_NEGATIVE}, {AUTO_TILE_VARIATION_EAST_DEAD_END, {-1, 0, -1, -1, 0, -1, 0, -1}, AUTO_TILE_OPTION_NEGATIVE}, {AUTO_TILE_VARIATION_WEST_DEAD_END, {-1, 0, -1, 0, -1, -1, 0, -1}, AUTO_TILE_OPTION_NEGATIVE}, {AUTO_TILE_VARIATION_NORTH_SOUTH_EDGES, {-1, 0, -1, -1, -1, -1, 0, -1}, AUTO_TILE_OPTION_NEGATIVE}, {AUTO_TILE_VARIATION_SOUTHEAST_CORNER, {-1, -1, -1, -1, 0, -1, 0, -1}, AUTO_TILE_OPTION_NEGATIVE}, {AUTO_TILE_VARIATION_SOUTHWEST_CORNER, {-1, -1, -1, 0, -1, -1, 0, -1}, AUTO_TILE_OPTION_NEGATIVE}, {AUTO_TILE_VARIATION_NORTHEAST_CORNER, {-1, 0, -1, -1, 0, -1, -1, -1}, AUTO_TILE_OPTION_NEGATIVE}, {AUTO_TILE_VARIATION_NORTHWEST_CORNER, {-1, 0, -1, 0, -1, -1, -1, -1}, AUTO_TILE_OPTION_NEGATIVE}, {AUTO_TILE_VARIATION_WEST_EAST_EDGES, {-1, -1, -1, 0, 0, -1, -1, -1}, AUTO_TILE_OPTION_NEGATIVE}, {AUTO_TILE_VARIATION_SOUTH_EDGE, {-1, -1, -1, -1, -1, -1, 0, -1}, AUTO_TILE_OPTION_NEGATIVE}, {AUTO_TILE_VARIATION_NORTH_EDGE, {-1, 0, -1, -1, -1, -1, -1, -1}, AUTO_TILE_OPTION_NEGATIVE}, {AUTO_TILE_VARIATION_EAST_EDGE, {-1, -1, -1, -1, 0, -1, -1, -1}, AUTO_TILE_OPTION_NEGATIVE}, {AUTO_TILE_VARIATION_WEST_EDGE, {-1, -1, -1, 0, -1, -1, -1, -1}, AUTO_TILE_OPTION_NEGATIVE} }; ruleset.rules = memdup(ruleset.rules, sizeof(*ruleset.rules) * ruleset.ruleCount); for (unsigned int tileIndex = 0; tileIndex < tileset->tileCount; tileIndex++) { uint8_t tileOptions = tileset->tiles[tileIndex].autoTileAtEdge ? AUTO_TILE_OPTION_EDGE_IS_MATCH : 0; for (unsigned int variationIndex = 0; variationIndex < AUTO_TILE_VARIATION_COUNT; variationIndex++) { AutoTileRule rule = { .baseTileID = tileset->tiles[tileIndex].tileID, .adjustedTileID = tileset->tiles[tileIndex].autoTileVariations[variations[variationIndex].variation], .options = tileOptions | variations[variationIndex].options }; if (rule.adjustedTileID != TILE_ID_INVALID) { for (unsigned int directionIndex = 0; directionIndex < ADJACENT_DIRECTION_COUNT; directionIndex++) { rule.adjacentDirections[directionIndex] = rule.baseTileID | variations[variationIndex].directionMask[directionIndex]; } ruleset.rules = realloc(ruleset.rules, sizeof(*ruleset.rules) * (ruleset.ruleCount + 1)); ruleset.rules[ruleset.ruleCount++] = rule; } } } return ruleset; } void Tileset_insertTile(Tileset * tileset, unsigned int tileIndex, Tile tile, bool copyName) { assert(tileIndex <= tileset->tileCount); tileset->tiles = realloc(tileset->tiles, sizeof(*tileset->tiles) * (tileset->tileCount + 1)); for (unsigned int tileIndex2 = tileset->tileCount; tileIndex2 > tileIndex; tileIndex2--) { tileset->tiles[tileIndex2] = tileset->tiles[tileIndex2 - 1]; } tileset->tiles[tileIndex] = tile; if (copyName) { tileset->tiles[tileIndex].name = strdup_nullSafe(tile.name); } tileset->tileCount++; } void Tileset_removeTile(Tileset * tileset, unsigned int tileIndex) { assert(tileIndex < tileset->tileCount); free(tileset->tiles[tileIndex].name); tileset->tileCount--; for (; tileIndex < tileset->tileCount; tileIndex++) { tileset->tiles[tileIndex] = tileset->tiles[tileIndex + 1]; } } void Tileset_reorderTile(Tileset * tileset, unsigned int fromTileIndex, unsigned int toTileIndex) { assert(fromTileIndex < tileset->tileCount); assert(toTileIndex < tileset->tileCount); Tile swap = tileset->tiles[fromTileIndex]; int direction = (toTileIndex > fromTileIndex) * 2 - 1; for (unsigned int swapIndex = fromTileIndex; swapIndex != toTileIndex; swapIndex += direction) { tileset->tiles[swapIndex] = tileset->tiles[swapIndex + direction]; } tileset->tiles[toTileIndex] = swap; }