Files
PixelDefense/engine/breeze/map/map.c

398 lines
13 KiB
C

#include "map.h"
#include "../core/logger.h"
#include "../core/memory.h"
#include "../math/vec2i.h"
#include <cute_tiled.h>
#include <string.h>
BzTileMap BZ_TILEMAP_INVALID = {.isValid = false};
bool bzTileLayerClear(BzTileLayer *layer, BzTile *data, i32 dataCount) {
return true;
}
bool bzTileObjectsClear(BzTileObjectGroup *objectGroup, BzTileObject *objects, i32 objectCount) {
return true;
}
BzTileLayerFunc BZ_TILE_LAYER_CLEAR = bzTileLayerClear;
BzTileObjectsFunc BZ_TILE_OBJECTS_CLEAR = bzTileObjectsClear;
BzTile bzTileLayerGetTile(BzTileLayer *layer, i32 x, i32 y) {
return layer->data[layer->width * y + x];
}
BzTileset *bzTileLayerGetTileset(BzTileMap *map, BzTileLayer *layer) {
i32 idx = layer->tilesetIdx;
if (idx < 0 || idx >= map->tilesetCount)
return NULL;
return &map->tilesets[idx];
}
static void handleTileLayer(BzTileLayer *layer, cute_tiled_layer_t *cuteLayer) {
layer->id = cuteLayer->id;
layer->dataCount = cuteLayer->data_count;
layer->data = NULL;
if (layer->dataCount > 0) {
layer->data = bzAlloc(layer->dataCount * sizeof(*layer->data));
layer->minData = layer->maxData = (BzTile) cuteLayer->data[0];
for (i32 i = 0; i < layer->dataCount; i++) {
layer->data[i] = (BzTile) cuteLayer->data[i];
if (layer->data[i] < layer->minData)
layer->minData = layer->data[i];
else if (layer->data[i] > layer->maxData)
layer->maxData = layer->data[i];
}
}
layer->width = cuteLayer->width;
layer->height = cuteLayer->height;
layer->offsetX = cuteLayer->offsetx;
layer->offsetY = cuteLayer->offsety;
layer->opacity = cuteLayer->opacity;
}
// tileset.c
BzTileShape bzCuteObjectToTileShape(cute_tiled_object_t *object);
static void handleTileObjectLayer(BzTileObjectGroup *layer, cute_tiled_layer_t *cuteLayer,
BzStringHashFunc hashFunc) {
// Count objects
layer->objectCount = 0;
cute_tiled_object_t *object = cuteLayer->objects;
while (object) {
layer->objectCount++;
object = object->next;
}
layer->objects = bzAlloc(layer->objectCount * sizeof(*layer->objects));
object = cuteLayer->objects;
for (i32 i = 0; i < layer->objectCount; i++) {
layer->objects[i].id = hashFunc(object->name.ptr);
layer->objects[i].shape = bzCuteObjectToTileShape(object);
object = object->next;
}
}
static void updateColliders(BzTileMap *map, i32 startX, i32 startY, i32 endX, i32 endY) {
BZ_ASSERT(startX >= 0 && endX <= map->width &&
startY >= 0 && endY <= map->height);
// Top-most layer takes priority
for (i32 i = map->layerCount - 1; i >= 0; i--) {
BzTileLayer *layer = map->layers + i;
if (!layer->data) continue;
if (layer->tilesetIdx == -1) continue;
BzTileset *tileset = map->tilesets + layer->tilesetIdx;
for (i32 y = startY; y < endY; y++) {
for (i32 x = startX; x < endX; x++) {
i32 tile = bzTileLayerGetTile(layer, x, y);
BzTileShape tilesetShape = bzTilesetGetTileCollider(tileset, tile);
if (tilesetShape.type == BZ_TILE_SHAPE_NONE ||
tilesetShape.type == BZ_TILE_SHAPE_POINT)
continue;
tilesetShape.x += layer->offsetX;
tilesetShape.y += layer->offsetY;
i32 colliderIdx = y * map->width + x;
BzTileCollider *collider = map->colliderMap + colliderIdx;
for (i32 sIdx = 0; sIdx < BZ_MAP_COLLIDER_DEPTH; sIdx++) {
BzTileShape *shape = collider->shapes + sIdx;
if (shape->type == BZ_TILE_SHAPE_NONE) {
*shape = tilesetShape;
break;
}
}
}
}
}
}
static void createColliders(BzTileMap *map) {
map->collidersCount = map->width * map->height;
map->colliderMap = bzAlloc(map->collidersCount * sizeof(*map->colliderMap));
for (i32 i = 0; i < map->collidersCount; i++) {
map->colliderMap[i] = (BzTileCollider) {{BZ_TILE_SHAPE_NONE}};
}
updateColliders(map, 0, 0, map->width, map->height);
}
BzTileMap bzTileMapCreate(const BzTileMapDesc *desc) {
BzTileMap map = {};
// Auto detect tileset count.
for (i32 i = 0; i < BZ_MAX_MAP_TILESETS; i++) {
if (!desc->tilesets[i].isValid)
break;
map.tilesetCount++;
}
for (i32 i = 0; i < map.tilesetCount; i++) {
map.tilesets[i] = desc->tilesets[i];
}
cute_tiled_map_t *cuteMap = cute_tiled_load_map_from_file(desc->path, NULL);
map.backgroundColor = GetColor(cuteMap->backgroundcolor);
map.width = cuteMap->width;
map.height = cuteMap->height;
map.tileWidth = cuteMap->tilewidth;
map.tileHeight = cuteMap->tileheight;
cute_tiled_layer_t *cuteLayer = cuteMap->layers;
while (cuteLayer) {
BZ_ASSERT(map.layerCount < BZ_MAX_MAP_LAYERS);
BZ_ASSERT(map.objectGroupCount < BZ_MAX_MAP_LAYERS);
// Find slot
i32 slot = -1;
for (i32 i = 0; i < BZ_MAX_MAP_LAYERS; i++) {
const BzTileLayerDesc *layerDesc = desc->layers + i;
const BzTileObjectsDesc *objectsDesc = desc->objectGroups + i;
if (layerDesc->name && strcmp(layerDesc->name, cuteLayer->name.ptr) == 0 ||
objectsDesc->name && strcmp(objectsDesc->name, cuteLayer->name.ptr) == 0) {
slot = i;
break;
} else if (!layerDesc->name && !objectsDesc->name) {
break;
}
}
if (slot == -1) {
bzLogWarning("Dangling layer: %s", cuteLayer->name.ptr);
cuteLayer = cuteLayer->next;
continue;
}
if (cuteLayer->data) {
BzTileLayer *layer = map.layers + slot;
layer->desc = desc->layers[slot];
handleTileLayer(layer, cuteLayer);
layer->hasOwnership = true;
map.layerCount++;
} else {
BzTileObjectGroup *objectGroup = map.objectGroups + slot;
objectGroup->desc = desc->objectGroups[slot];
if (!objectGroup->desc.hashFunc)
objectGroup->desc.hashFunc = bzStringDefaultHash;
handleTileObjectLayer(objectGroup, cuteLayer, objectGroup->desc.hashFunc);
map.objectGroupCount++;
}
cuteLayer = cuteLayer->next;
}
cute_tiled_tileset_t *cuteTileset = cuteMap->tilesets;
int tilesetCount = 0;
for (; cuteTileset; cuteTileset = cuteTileset->next) tilesetCount++;
BZ_ASSERT(tilesetCount == map.tilesetCount);
cuteTileset = cuteMap->tilesets;
for (i32 i = 0; i < map.tilesetCount; i++) {
BzTileset *tileset = map.tilesets + i;
tileset->startID = cuteTileset->firstgid;
cuteTileset = cuteTileset->next;
}
// Assign tilesets to layers
for (i32 i = 0; i < map.layerCount; i++) {
BzTileLayer *layer = map.layers + i;
layer->tilesetIdx = -1;
for (i32 j = map.tilesetCount - 1; j >= 0; j--) {
BzTileset *tileset = map.tilesets + j;
if (tileset->startID >= layer->minData &&
tileset->startID <= layer->maxData) {
layer->tilesetIdx = j;
break;
}
}
}
cute_tiled_free_map(cuteMap);
createColliders(&map);
map.isValid = true;
return map;
}
void bzTileMapDestroy(BzTileMap *map) {
for (i32 i = 0; i < map->layerCount; i++) {
BzTileLayer *layer = map->layers + i;
if (layer->hasOwnership && layer->data) {
bzFree(layer->data);
layer->data = NULL;
layer->dataCount = 0;
}
}
for (i32 i = 0; i < map->objectGroupCount; i++) {
BzTileObjectGroup *objectLayer = map->objectGroups + i;
if (objectLayer->hasOwnership && objectLayer->objects) {
bzFree(objectLayer->objects);
objectLayer->objects = NULL;
objectLayer->objectCount = 0;
}
}
bzFree(map->colliderMap);
map->collidersCount = 0;
*map = BZ_TILEMAP_INVALID;
}
void bzTileMapOverrideLayer(BzTileMap *map, i32 slotID, BzTileLayerFunc func) {
BZ_ASSERT(slotID >= 0 && slotID < map->layerCount);
BzTileLayer *layer = map->layers + slotID;
i32 dataCount = layer->dataCount;
BzTile *data = layer->data;
if (func(layer, data, dataCount)) {
if (!layer->hasOwnership) return;
bzFree(layer->data);
if (layer->data == data) {
layer->data = NULL;
layer->dataCount = 0;
}
layer->hasOwnership = false;
} else {
BZ_ASSERT(data == layer->data && dataCount == layer->dataCount);
}
}
void bzTileMapOverrideObjectGroup(BzTileMap *map, i32 slotID, BzTileObjectsFunc func) {
BZ_ASSERT(slotID >= 0 && slotID < map->objectGroupCount);
BzTileObjectGroup *objectGroup = map->objectGroups + slotID;
BzTileObject *objects = objectGroup->objects;
i32 objectCount = objectGroup->objectCount;
if (func(objectGroup, objects, objectCount)) {
bzFree(objectGroup->objects);
if (objectGroup->objects == objects) {
objectGroup->objects = NULL;
objectGroup->objectCount = 0;
}
objectGroup->hasOwnership = false;
} else {
BZ_ASSERT(objects == objectGroup->objects && objectCount == objectGroup->objectCount);
}
}
static void drawLayer(BzTileLayer *layer, BzTileset *tileset) {
if (!tileset) return;
if (layer->minData == layer->maxData) return;
Vector2 drawPos = {layer->offsetX, layer->offsetY};
for (i32 y = 0; y < layer->height; y++) {
for (i32 x = 0; x < layer->width; x++) {
BzTile tile = bzTileLayerGetTile(layer, x, y);
if (tile - tileset->startID != -1) {
Rectangle rec = bzTilesetGetTileRegion(tileset, tile);
DrawTextureRec(tileset->tiles, rec, drawPos, WHITE);
}
drawPos.x += (float) tileset->tileWidth;
}
drawPos.x = layer->offsetX;
drawPos.y += (float) tileset->tileHeight;
}
}
static void drawObjectLayer(BzTileObjectGroup *objectLayer) {
Color color = ORANGE;
for (int i = 0; i < objectLayer->objectCount; i++) {
BzTileShape shape = objectLayer->objects[i].shape;
switch (shape.type) {
case BZ_TILE_SHAPE_NONE:
break;
case BZ_TILE_SHAPE_POINT:
DrawCircle(shape.x, shape.y, 2.0f, color);
break;
case BZ_TILE_SHAPE_RECT:
DrawRectangle(shape.x, shape.y, shape.sizeX, shape.sizeY, color);
break;
case BZ_TILE_SHAPE_ELLIPSE:
DrawEllipse(shape.x, shape.y, shape.sizeX, shape.sizeY, color);
break;
}
}
}
BzTileLayer *bzTileMapGetLayer(BzTileMap *map, i32 slotID) {
BZ_ASSERT(slotID >= 0 && slotID < map->layerCount);
return &map->layers[slotID];
}
BzTileObjectGroup *bzTileMapGetObjects(BzTileMap *map, i32 slotID) {
BZ_ASSERT(slotID >= 0 && slotID < map->objectGroupCount);
return &map->objectGroups[slotID];
}
void bzTileMapDraw(BzTileMap *map) {
for (i32 i = 0; i < map->layerCount; i++) {
BzTileLayer *layer = map->layers + i;
if (!layer->data) continue;
BzTileset *tileset = NULL;
if (layer->tilesetIdx != -1) {
tileset = map->tilesets + layer->tilesetIdx;
}
drawLayer(map->layers + i, tileset);
}
for (i32 i = 0; i < map->objectGroupCount; i++) {
BzTileObjectGroup *objectLayer = map->objectGroups + i;
if (!objectLayer->objects) continue;
drawObjectLayer(objectLayer);
}
}
void bzTileMapDrawColliders(BzTileMap *map) {
Color color = RED;
color.a = 150;
for (i32 y = 0; y < map->height; y++) {
for (i32 x = 0; x < map->width; x++) {
i32 idx = y * map->width + x;
BzTileCollider collider = map->colliderMap[idx];
for (i32 i = 0; i < BZ_MAP_COLLIDER_DEPTH; i++) {
BzTileShape shape = collider.shapes[i];
if (shape.type == BZ_TILE_SHAPE_NONE)
break;
i32 posX = x * map->tileWidth + shape.x;
i32 posY = y * map->tileHeight + shape.y;
f32 sizeX = shape.sizeX;
f32 sizeY = shape.sizeY;
switch (shape.type) {
case BZ_TILE_SHAPE_NONE:
default:
break;
case BZ_TILE_SHAPE_RECT:
DrawRectangleLines(posX, posY, sizeX, sizeY, color);
break;
case BZ_TILE_SHAPE_ELLIPSE:
DrawEllipseLines(posX, posY, sizeX, sizeY, color);
break;
}
}
}
}
}
void bzTileMapUpdateCollider(BzTileMap *map, i32 x, i32 y) {
i32 idx = y * map->width + x;
BZ_ASSERT(idx >= 0 && idx < map->collidersCount);
map->colliderMap[idx] = (BzTileCollider){};
updateColliders(map, x, y, x + 1, y + 1);
}
BzTileCollider bzTileMapGetCollider(BzTileMap *map, i32 x, i32 y) {
i32 idx = y * map->width + x;
if (idx < 0 || idx >= map->collidersCount) {
return (BzTileCollider) {{BZ_TILE_SHAPE_NONE}};
}
return map->colliderMap[idx];
}