#include #include #include #include #include "cJSON.h" #include "common.h" struct tilemap_T { unsigned width, height; int *tilemap; }; #define PACKED __attribute__((__packed__)) #define MAGIC "TMv1" struct PACKED sets { uint8_t magic[4]; uint32_t tilesets_count; uint32_t wang_count; }; struct PACKED map { struct { uint8_t r, g, b, a; } backdrop; uint32_t width; uint32_t height; uint32_t layers; uint32_t middleground; uint8_t tiles[]; }; struct tilemap_T make_tilemap(cJSON const *const layer, int const wangStart, int const wangSize) { cJSON const *width_obj = cJSON_GetObjectItemCaseSensitive(layer, "width"); cJSON const *height_obj = cJSON_GetObjectItemCaseSensitive(layer, "height"); if (!cJSON_IsNumber(width_obj) || !cJSON_IsNumber(height_obj)) { fputs("missing tile layer size\n", stderr); return (struct tilemap_T) {0, 0, NULL}; } int const width = width_obj->valueint; int const height = height_obj->valueint; cJSON const *data = cJSON_GetObjectItemCaseSensitive(layer, "data"); int const size = cJSON_GetArraySize(data); if (width * height != size) { fputs("tile data size mismatch", stderr); return (struct tilemap_T) {0, 0, NULL}; } struct tilemap_T in = {.tilemap = NULL, .width = width, .height = height}; in.tilemap = malloc(size * sizeof (int)); cJSON const *tile = NULL; size_t i = 0; cJSON_ArrayForEach (tile, data) { in.tilemap[i] = tile->valueint; i++; } struct tilemap_T out = {.tilemap = NULL, .width = width - 1, .height = height - 1}; out.tilemap = malloc(out.width * out.height * sizeof (int)); //printf("=== tilemap (%d tiles) ===\n", size); for (int y = 0; y < out.height; y++) { for (int x = 0; x < out.width; x++) { int a = in.tilemap[x + y * in.width]; if (a == 0) { out.tilemap[x + y * out.width] = 0; } else if (a < wangStart) { out.tilemap[x + y * out.width] = a - 1; } else if (a > wangStart + wangSize) { out.tilemap[x + y * out.width] = a - wangSize - 1; } else { int b = (in.tilemap[x + y * in.width] - wangStart) >> 4; int c = (in.tilemap[(x + 1) + y * in.width] - wangStart) >> 4; int d = (in.tilemap[x + (y + 1) * in.width] - wangStart) >> 4; int e = (in.tilemap[(x + 1) + (y + 1) * in.width] - wangStart) >> 4; int f = b == c && b == d && b == e; if (f && ((a - wangStart) & 0x8)) { out.tilemap[x + y * out.width] = b + 0xf0; } else { out.tilemap[x + y * out.width] = 0; } } } } free(in.tilemap); return out; } size_t safe_write(void const *restrict buf, size_t size, FILE *restrict stream) { if (stream == NULL) { return 0; } return fwrite(buf, 1, size, stream); } int main(int argc, char **argv) { if (argc != 2 && argc != 3) { return EXIT_FAILURE; } struct blob file = load_file(argv[1]); cJSON *json = cJSON_ParseWithLength(file.data, file.size); free(file.data); FILE *outfile = NULL; if (argc == 3) { outfile = fopen(argv[2], "wb"); } int status = EXIT_SUCCESS; if (json == NULL) { puts("null"); status = EXIT_FAILURE; goto end; } cJSON const *tilewidth = cJSON_GetObjectItemCaseSensitive(json, "tilewidth"); cJSON const *tileheight = cJSON_GetObjectItemCaseSensitive(json, "tileheight"); if (!cJSON_IsNumber(tilewidth) || !cJSON_IsNumber(tileheight) || tilewidth->valueint != 8 || tileheight->valueint != 8) { status = EXIT_FAILURE; goto end; } cJSON const *tilesets = cJSON_GetObjectItemCaseSensitive(json, "tilesets"); cJSON const *tileset = NULL; int wangStart = 0; int wangSize = 0; char *wang_tileset = NULL; uint32_t wang_height; char *tilesets_data = NULL; size_t tilesets_size = 0; struct sets counts = {MAGIC, 0, 0}; cJSON_ArrayForEach(tileset, tilesets) { //printf("=== tileset (%% tiles) ===\n"); cJSON const *wangs = cJSON_GetObjectItemCaseSensitive(tileset, "wangsets"); if (wangs != NULL) { cJSON const *wang = NULL; cJSON_ArrayForEach(wang, wangs) { //puts("=== wang ==="); cJSON const *type = cJSON_GetObjectItemCaseSensitive(wang, "type"); if (strcmp("corner", type->valuestring) == 0) { if (wangStart != 0) { fputs("two or more corner sets\n", stderr); status = EXIT_FAILURE; goto end; } wangStart = cJSON_GetObjectItemCaseSensitive(tileset, "firstgid")->valueint; wangSize = cJSON_GetObjectItemCaseSensitive(tileset, "tilecount")->valueint; wang_tileset = cJSON_GetObjectItemCaseSensitive(tileset, "image")->valuestring; switch (cJSON_GetObjectItemCaseSensitive(tileset, "imagewidth")->valueint) { case 32: wang_height = cJSON_GetObjectItemCaseSensitive(tileset, "imageheight")->valueint / 4; break; case 128: wang_height = cJSON_GetObjectItemCaseSensitive(tileset, "imageheight")->valueint; break; default: goto end; } //printf("=== wang %u-%u ===\n", wangStart, wangStart + wangSize); counts.wang_count++; goto wang_continue; } } } cJSON const *image = cJSON_GetObjectItemCaseSensitive(tileset, "image"); { char const *const str = image->valuestring; size_t start = 0, length = strlen(str); for (size_t i = 0; i < length; i++) { if (str[i] == '/') { start = i + 1; } } length -= start; for (size_t i = length; i > 0; i--) { if (str[start + i] == '.') { length = i; break; } } //printf("%.*s\n", (int) length, str + start); uint32_t height = cJSON_GetObjectItemCaseSensitive(tileset, "imageheight")->valueint; tilesets_data = realloc(tilesets_data, tilesets_size + length + sizeof (uint32_t) + 1); memcpy(tilesets_data + tilesets_size, &height, sizeof (uint32_t)); memcpy(tilesets_data + tilesets_size + sizeof (uint32_t), str + start, length); tilesets_data[tilesets_size + sizeof (uint32_t) + length] = 0; //puts(tilesets_data + tilesets_size + sizeof (uint32_t)); tilesets_size += length + sizeof (uint32_t) + 1; counts.tilesets_count++; } //puts(image->valuestring); #if 0 char *str = cJSON_Print(tileset); puts(str); free(str); #endif wang_continue:; } if (counts.wang_count == 1) { char const *const str = wang_tileset; size_t start = 0, length = strlen(str); for (size_t i = 0; i < length; i++) { if (str[i] == '/') { start = i + 1; } } length -= start; for (size_t i = length; i > 0; i--) { if (str[start + i] == '.') { length = i; break; } } //printf("%.*s\n", (int) length, str + start); wang_tileset = malloc(length + 1); memcpy(wang_tileset, str + start, length); wang_tileset[length] = 0; //puts(wang_tileset); } cJSON const *width_obj = cJSON_GetObjectItemCaseSensitive(json, "width"); cJSON const *height_obj = cJSON_GetObjectItemCaseSensitive(json, "height"); if (!cJSON_IsNumber(width_obj) || !cJSON_IsNumber(height_obj)) { fputs("missing tilemap size\n", stderr); return 1; } struct map map = { .backdrop = {0x9f, 0x9f, 0x9f, 0xff}, .width = width_obj->valueint - 1, .height = height_obj->valueint - 1, .layers = 0, .middleground = 0, }; uint8_t *tile_data = NULL; float *parallaxes = NULL; cJSON const *backdrop = cJSON_GetObjectItemCaseSensitive(json, "backgroundcolor"); if (cJSON_IsString(backdrop)) { switch (strlen(backdrop->valuestring)) { unsigned value; case 9: value = strtoul(backdrop->valuestring + 1, NULL, 16); map.backdrop.r = value >> 16; map.backdrop.g = value >> 8; map.backdrop.b = value >> 0; map.backdrop.a = value >> 24; break; case 7: value = strtoul(backdrop->valuestring + 1, NULL, 16); map.backdrop.r = value >> 16; map.backdrop.g = value >> 8; map.backdrop.b = value >> 0; map.backdrop.a = 0xff; break; default: fputs("incorrect backdrop color format\n", stderr); return 1; } } //printf("sky: # %02x %02x %02x %02x\n", map.backdrop.r, map.backdrop.g, map.backdrop.b, map.backdrop.a); cJSON const *layers = cJSON_GetObjectItemCaseSensitive(json, "layers"); cJSON const *layer = NULL; cJSON_ArrayForEach (layer, layers) { cJSON const *type_obj = cJSON_GetObjectItemCaseSensitive(layer, "type"); if (!cJSON_IsString(type_obj)) { status = EXIT_FAILURE; goto end; } char const *type = type_obj->valuestring; //puts(type); if (strcmp(type, "group") == 0) { // group layer cJSON const *sublayers = cJSON_GetObjectItemCaseSensitive(layer, "layers"); cJSON const *sublayer = NULL; struct tilemap_T layert = {.tilemap = NULL, .width = 0, .height = 0}; cJSON_ArrayForEach (sublayer, sublayers) { struct tilemap_T tilemap = make_tilemap(sublayer, wangStart, wangSize); if (tilemap.tilemap == NULL) { status = EXIT_FAILURE; goto end; } if (layert.tilemap == NULL) { layert.width = tilemap.width; layert.height = tilemap.height; layert.tilemap = malloc(layert.width * layert.height * sizeof (int)); memcpy(layert.tilemap, tilemap.tilemap, layert.width * layert.height * sizeof (int)); } else { for (size_t i = 0; i < layert.width * layert.height; i++) { if (tilemap.tilemap[i] != 0) { layert.tilemap[i] = tilemap.tilemap[i]; } } } free(tilemap.tilemap); } for (int y = 0; y < layert.height; y++) { for (int x = 0; x < layert.width; x++) { //printf("%2x,", layert.tilemap[x + y * layert.width]); } //fputc('\n', stdout); } size_t const layer_size = map.width * map.height; tile_data = realloc(tile_data, layer_size * (map.layers + 1)); for (size_t i = 0; i < layer_size; i++) { tile_data[layer_size * map.layers + i] = layert.tilemap[i]; } parallaxes = realloc(parallaxes, sizeof (float) * 2 * (map.layers + 1)); float px = 1.0f, py = 1.0f; cJSON const *parallax; parallax = cJSON_GetObjectItemCaseSensitive(layer, "parallaxx"); if (cJSON_IsNumber(parallax)) { px = parallax->valuedouble; } parallax = cJSON_GetObjectItemCaseSensitive(layer, "parallaxy"); if (cJSON_IsNumber(parallax)) { py = parallax->valuedouble; } //printf("parallax: %f %f\n", px, py); parallaxes[map.layers * 2 + 0] = px; parallaxes[map.layers * 2 + 1] = py; map.layers++; free(layert.tilemap); } else if (strcmp(type, "tilelayer") == 0) { // tile layer struct tilemap_T tilemap = make_tilemap(layer, wangStart, wangSize); if (tilemap.tilemap == NULL) { status = EXIT_FAILURE; goto end; } for (int y = 0; y < tilemap.height; y++) { for (int x = 0; x < tilemap.width; x++) { //printf("%2x,", tilemap.tilemap[x + y * tilemap.width]); } //fputc('\n', stdout); } size_t const layer_size = map.width * map.height; tile_data = realloc(tile_data, layer_size * (map.layers + 1)); for (size_t i = 0; i < layer_size; i++) { tile_data[layer_size * map.layers + i] = tilemap.tilemap[i]; } parallaxes = realloc(parallaxes, sizeof (float) * 2 * (map.layers + 1)); float px = 1.0f, py = 1.0f; cJSON const *parallax; parallax = cJSON_GetObjectItemCaseSensitive(layer, "parallaxx"); if (cJSON_IsNumber(parallax)) { px = parallax->valuedouble; } parallax = cJSON_GetObjectItemCaseSensitive(layer, "parallaxy"); if (cJSON_IsNumber(parallax)) { py = parallax->valuedouble; } //printf("parallax: %fx%f\n", px, py); parallaxes[map.layers * 2 + 0] = px; parallaxes[map.layers * 2 + 1] = py; map.layers++; free(tilemap.tilemap); #if 0 char *str = cJSON_Print(layer); puts(str); free(str); #endif } else { // ??? layer } cJSON const *properties = cJSON_GetObjectItemCaseSensitive(layer, "properties"); cJSON const *property = NULL; cJSON_ArrayForEach (property, properties) { char const *const name = cJSON_GetObjectItemCaseSensitive(property, "name")->valuestring; if (strcmp(name, "middleground") == 0) { if (cJSON_IsTrue(cJSON_GetObjectItemCaseSensitive(property, "value"))) { map.middleground = map.layers; } } else { printf("unknown property '%s'\n", name); return 1; } } } fwrite(&counts, 1, sizeof (counts), outfile); fwrite(tilesets_data, 1, tilesets_size, outfile); if (wang_tileset != NULL) { fwrite(&wang_height, 1, sizeof (uint32_t), outfile); fwrite(wang_tileset, 1, strlen(wang_tileset) + 1, outfile); } fwrite(&map, 1, sizeof (map), outfile); fwrite(tile_data, 1, map.width * map.height * map.layers, outfile); fwrite(parallaxes, 1, sizeof (float) * 2 * map.layers, outfile); free(tile_data); end: if (outfile != NULL) { fclose(outfile); } cJSON_Delete(json); return status; }