diff --git a/.github/workflows/build_ship.yml b/.github/workflows/build_ship.yml index 623d061..5d6371c 100644 --- a/.github/workflows/build_ship.yml +++ b/.github/workflows/build_ship.yml @@ -38,7 +38,7 @@ jobs: run: ctest - name: Create libtmj Artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ matrix.os }}-${{ matrix.cc }}-libtmj-master path: lib/* @@ -99,7 +99,7 @@ jobs: run: ctest -C Debug - name: Create libtmj Artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: windows-latest-msvc-libtmj-master path: lib/* @@ -115,7 +115,8 @@ jobs: environment: name: github-pages - url: ${{ steps.deployment.outputs.page_url }} + #url: ${{ steps.deployment.outputs.page_url }} + url: https://zer0-one.github.io/libtmj/ runs-on: ubuntu-latest diff --git a/CMakeLists.txt b/CMakeLists.txt index aeb51ee..b207ff0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.22.2) -project(libtmj VERSION 0.1.0 DESCRIPTION "A C library for loading Tiled maps and tilesets in JSON format") +project(libtmj VERSION 1.0.0 DESCRIPTION "A C library for loading Tiled maps and tilesets in JSON format") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/") diff --git a/Doxyfile b/Doxyfile index 93d5e0d..63e55de 100644 --- a/Doxyfile +++ b/Doxyfile @@ -47,7 +47,7 @@ PROJECT_NAME = "LibTMJ" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 0.1.0 +PROJECT_NUMBER = 1.0.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/LICENSE b/LICENSE index e10af19..2d60008 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 2-Clause License -Copyright (c) 2023, David Zero +Copyright (c) 2023-2024, David Zero All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/README.md b/README.md index f3b5bc3..b1d9569 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,8 @@ A library for loading [Tiled](https://www.mapeditor.org) maps in JSON format -Bear in mind that this library is currently in beta and not yet stable. If you -find bugs, or find that the library is missing an important feature, please -file an issue. +Bear in mind that this library is relatively new. If you find bugs, or find +that the library is missing an important feature, please file an issue. ## Dependencies @@ -38,6 +37,7 @@ Available cmake build options: Option | Description ------------------- | ----------- BUILD\_SHARED\_LIBS | Builds a shared library instead of a static library. +CMAKE\_BUILD\_TYPE | One of "Release" (optimization) or "Debug" (runtime sanitizers + debug symbols) LIBTMJ\_DOCS | Also build documentation. LIBTMJ\_ZSTD | Build zstd decompression routines. LIBTMJ\_ZLIB | Build zlib and gzip decompression routines. @@ -52,12 +52,12 @@ git submodule update --init To enable the test suite, invoke cmake with: ``` --DLIBTMJ_TEST=True +-DCMAKE_BUILD_TYPE=Debug -DLIBTMJ_TEST=True ``` Then run the tests with: ``` ctest // For *nix -ctest -C Release // For Windows +ctest -C Debug // For Windows ``` ## Usage example @@ -134,7 +134,7 @@ int main(){ BSD 2-Clause License - Copyright (c) 2023, David Zero + Copyright (c) 2023-2024, David Zero All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/example/testmap.tmj b/example/testmap.tmj new file mode 100644 index 0000000..1b6229d --- /dev/null +++ b/example/testmap.tmj @@ -0,0 +1,80 @@ +{ "compressionlevel":-1, + "height":20, + "infinite":false, + "layers":[ + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":20, + "id":1, + "name":"Tile Layer 1", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":30, + "x":0, + "y":0 + }, + { + "draworder":"topdown", + "id":2, + "name":"Object Layer 1", + "objects":[ + { + "height":160.2053915276, + "id":1, + "name":"square_thing", + "rotation":0, + "type":"", + "visible":true, + "width":236.611039794608, + "x":377.098844672657, + "y":263.722721437741 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + }], + "nextlayerid":3, + "nextobjectid":2, + "orientation":"orthogonal", + "properties":[ + { + "name":"foo", + "type":"string", + "value":"bar" + }, + { + "name":"test", + "type":"object", + "value":1 + }], + "renderorder":"right-down", + "tiledversion":"git", + "tileheight":32, + "tilesets":[], + "tilewidth":32, + "type":"map", + "version":"1.10", + "width":30 +} \ No newline at end of file diff --git a/include/tmj.h b/include/tmj.h index 995736f..0d15f71 100644 --- a/include/tmj.h +++ b/include/tmj.h @@ -23,19 +23,18 @@ extern "C" { */ typedef struct Property { char* name; - char* propertytype; //"When applicable", whatever the fuck that means + char* propertytype; char* type; - json_t* value; //To-Do: parse this out into an actual C value. - //union{ - // char* string_value; - // int int_value; - // double float_value; - // bool bool_value; - // char* color_value; - // char* file_value; - // int object_value; - //}; + union{ + char* value_string; + int value_int; + double value_float; + bool value_bool; + char* value_color; + char* value_file; + int value_object; + }; } Property; /** @@ -492,7 +491,6 @@ void tmj_map_free(Map* map); */ void tmj_tileset_free(Tileset* tileset); - /** * @ingroup tmj */ @@ -504,24 +502,6 @@ typedef enum TMJ_LOG_PRIORITY{ TMJ_LOG_CRIT } tmj_log_priority; -////** -// * @ingroup logging -// * The LogMessage struct is sent in logging callbacks to provide the caller -// * with debugging and error information. -// */ -//typedef struct LogMessage{ -// log_priority priority; -// -// /*-----------*/ -// // These fields are only included when debugging is enabled for the callback -// int line; -// char* file; -// char* func; -// /*-----------*/ -// -// const char* msg; -//} LogMessage; - /** * @ingroup tmj * Registers a callback function to handle logging events. diff --git a/src/map.c b/src/map.c index 9b7615d..31cb57e 100644 --- a/src/map.c +++ b/src/map.c @@ -33,6 +33,8 @@ Property* unpack_properties(json_t* properties){ return NULL; } + json_t* value; + size_t idx = 0; json_t* property = NULL; @@ -44,7 +46,7 @@ Property* unpack_properties(json_t* properties){ "name", &ret[idx].name, "type", &ret[idx].type, "propertytype", &ret[idx].propertytype, - "value", &ret[idx].value + "value", &value ); if(unpk == -1){ @@ -54,6 +56,91 @@ Property* unpack_properties(json_t* properties){ return NULL; } + + // note: string is default type, so missing field means string + if(ret[idx].type == NULL || strcmp(ret[idx].type, "string") == 0){ + unpk = json_unpack_ex(value, &error, 0, "s", &ret[idx].value_string); + + if(unpk == -1){ + logmsg(TMJ_LOG_ERR, "Unable to unpack string value from property, %s at line %d column %d", error.text, error.line, error.column); + + free(ret); + + return NULL; + } + } + + if(strcmp(ret[idx].type, "int") == 0){ + unpk = json_unpack_ex(value, &error, 0, "i", &ret[idx].value_int); + + if(unpk == -1){ + logmsg(TMJ_LOG_ERR, "Unable to unpack integer value from property, %s at line %d column %d", error.text, error.line, error.column); + + free(ret); + + return NULL; + } + } + + if(strcmp(ret[idx].type, "float") == 0){ + unpk = json_unpack_ex(value, &error, 0, "i", &ret[idx].value_float); + + if(unpk == -1){ + logmsg(TMJ_LOG_ERR, "Unable to unpack float value from property, %s at line %d column %d", error.text, error.line, error.column); + + free(ret); + + return NULL; + } + } + + if(strcmp(ret[idx].type, "bool") == 0){ + unpk = json_unpack_ex(value, &error, 0, "b", &ret[idx].value_bool); + + if(unpk == -1){ + logmsg(TMJ_LOG_ERR, "Unable to unpack bool value from property, %s at line %d column %d", error.text, error.line, error.column); + + free(ret); + + return NULL; + } + } + + if(strcmp(ret[idx].type, "color") == 0){ + unpk = json_unpack_ex(value, &error, 0, "s", &ret[idx].value_color); + + if(unpk == -1){ + logmsg(TMJ_LOG_ERR, "Unable to unpack color value from property, %s at line %d column %d", error.text, error.line, error.column); + + free(ret); + + return NULL; + } + } + + if(strcmp(ret[idx].type, "file") == 0){ + unpk = json_unpack_ex(value, &error, 0, "s", &ret[idx].value_file); + + if(unpk == -1){ + logmsg(TMJ_LOG_ERR, "Unable to unpack file value from property, %s at line %d column %d", error.text, error.line, error.column); + + free(ret); + + return NULL; + } + } + + if(strcmp(ret[idx].type, "object") == 0){ + unpk = json_unpack_ex(value, &error, 0, "i", &ret[idx].value_object); + + if(unpk == -1){ + logmsg(TMJ_LOG_ERR, "Unable to unpack object value from property, %s at line %d column %d", error.text, error.line, error.column); + + free(ret); + + return NULL; + } + } } return ret; @@ -227,6 +314,12 @@ Object* unpack_objects(json_t* objects){ //Unpack properties if(properties != NULL){ + if(!json_is_array(properties)){ + logmsg(TMJ_LOG_ERR, "'properties' must be an array"); + + goto fail_properties; + } + ret[idx].properties = unpack_properties(properties); if(ret[idx].properties == NULL){ @@ -256,6 +349,12 @@ Object* unpack_objects(json_t* objects){ // Unpack Polygon if(polygon != NULL){ + if(!json_is_array(polygon)){ + logmsg(TMJ_LOG_ERR, "'polygon' must be an array"); + + goto fail_polygon; + } + ret[idx].polygon = unpack_points(polygon); if(ret[idx].polygon == NULL){ @@ -270,6 +369,12 @@ Object* unpack_objects(json_t* objects){ // Unpack Polyline if(polyline != NULL){ + if(!json_is_array(polyline)){ + logmsg(TMJ_LOG_ERR, "'polyline' must be an array"); + + goto fail_polyline; + } + ret[idx].polyline = unpack_points(polyline); if(ret[idx].polyline == NULL){ diff --git a/test/map_tests.c b/test/map_tests.c index 2c24858..60c2c68 100644 --- a/test/map_tests.c +++ b/test/map_tests.c @@ -31,15 +31,21 @@ void setUp(void){ void tearDown(void){} char* testmap_path = "example/overworld.tmj"; +char* testmap_path2 = "example/testmap.tmj"; Map* mf = NULL; +Map* mf2 = NULL; Map* ms = NULL; void test_map_loadf(void){ mf = tmj_map_loadf(testmap_path, true); + mf2 = tmj_map_loadf(testmap_path2, true); TEST_ASSERT_NOT_NULL(mf); + TEST_ASSERT_NOT_NULL(mf2); TEST_ASSERT_EQUAL_size_t(4, mf->layer_count); TEST_ASSERT_EQUAL_STRING("tilelayer", mf->layers[0].type); + TEST_ASSERT_EQUAL_STRING("bar", mf2->properties[0].value_string); + TEST_ASSERT_EQUAL_INT(1, mf2->properties[1].value_int); } void test_map_load(void){ @@ -65,6 +71,7 @@ void test_map_load(void){ void test_map_free(void){ tmj_map_free(mf); + tmj_map_free(mf2); tmj_map_free(ms); }