diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0c54c6ab8d..84d49ba76b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,7 @@ jobs: name: Check code formatting runs-on: ubuntu-latest container: - image: openrct2/openrct2-build:0.2.4-format + image: openrct2/openrct2-build:0.3.0-format steps: - name: Checkout uses: actions/checkout@v1 @@ -81,7 +81,7 @@ jobs: runs-on: ubuntu-latest needs: [check-code-formatting] container: - image: openrct2/openrct2-build:0.2.4-mingw + image: openrct2/openrct2-build:0.3.0-mingw steps: - name: Checkout uses: actions/checkout@v2 @@ -125,7 +125,7 @@ jobs: runs-on: ubuntu-latest needs: [check-code-formatting] container: - image: openrct2/openrct2-build:0.2.4-bionic + image: openrct2/openrct2-build:0.3.0-bionic steps: - name: Checkout uses: actions/checkout@v1 @@ -160,7 +160,7 @@ jobs: runs-on: ubuntu-latest needs: [check-code-formatting] container: - image: openrct2/openrct2-build:0.2.4-bionic32 + image: openrct2/openrct2-build:0.3.0-bionic32 steps: - name: Checkout uses: actions/checkout@v1 @@ -200,7 +200,7 @@ jobs: runs-on: ubuntu-latest needs: [check-code-formatting] container: - image: openrct2/openrct2-build:0.2.4-bionic + image: openrct2/openrct2-build:0.3.0-bionic steps: - name: Checkout uses: actions/checkout@v1 @@ -258,7 +258,7 @@ jobs: runs-on: ubuntu-latest needs: [check-code-formatting] container: - image: openrct2/openrct2-build:0.2.4-bionic + image: openrct2/openrct2-build:0.3.0-bionic steps: - name: Checkout uses: actions/checkout@v1 @@ -270,7 +270,7 @@ jobs: runs-on: ubuntu-latest needs: [check-code-formatting] container: - image: openrct2/openrct2-build:0.2.4-android + image: openrct2/openrct2-build:0.3.0-android steps: - name: Checkout uses: actions/checkout@v1 diff --git a/CMakeLists_mingw.txt b/CMakeLists_mingw.txt index 56588e8573..ffb338d416 100644 --- a/CMakeLists_mingw.txt +++ b/CMakeLists_mingw.txt @@ -23,5 +23,3 @@ SET(CMAKE_FIND_ROOT_PATH ${TARGET_ENVIRONMENT}) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) - -# INCLUDE_DIRECTORIES(${ORCTLIBS_INCLUDE} ${JANSSON_INCLUDE}) diff --git a/OpenRCT2.xcodeproj/project.pbxproj b/OpenRCT2.xcodeproj/project.pbxproj index 594477890c..ea070c6dbc 100644 --- a/OpenRCT2.xcodeproj/project.pbxproj +++ b/OpenRCT2.xcodeproj/project.pbxproj @@ -154,9 +154,10 @@ 93F76F0520BFF77B00D4512C /* Paint.TileElement.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 93F76EFD20BFF77A00D4512C /* Paint.TileElement.cpp */; }; 93F76F0620BFF77B00D4512C /* Paint.Entrance.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 93F76EFE20BFF77A00D4512C /* Paint.Entrance.cpp */; }; 93F9DA3820B46F9D00D1BE92 /* ShopItem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4CDCB0BC20A9902E00321367 /* ShopItem.cpp */; }; - 93F9DA3920B46FB800D1BE92 /* ObjectJsonHelpers.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4CE9AAAB1FDA7B14004093C6 /* ObjectJsonHelpers.cpp */; }; 93F9DA3A20B46FCA00D1BE92 /* SceneryObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4C1A53EC205FD19F000F8EF5 /* SceneryObject.cpp */; }; 93F9DA3B20B4701100D1BE92 /* StdInOutConsole.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4C3B423720591513000C5BB7 /* StdInOutConsole.cpp */; }; + 93FB271F24ED32B7008241C9 /* json.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 93FB271E24ED32B7008241C9 /* json.hpp */; }; + 93FB272124ED3601008241C9 /* Cursors.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 93FB272024ED3601008241C9 /* Cursors.cpp */; }; 93FC08FF2418F3ED00CA3054 /* duktape.h in Headers */ = {isa = PBXBuildFile; fileRef = 93FC08FD2418F3ED00CA3054 /* duktape.h */; }; 93FC09002418F3ED00CA3054 /* duk_config.h in Headers */ = {isa = PBXBuildFile; fileRef = 93FC08FE2418F3ED00CA3054 /* duk_config.h */; }; 93FC09022418F3F500CA3054 /* libduktape.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 93FC09012418F3F500CA3054 /* libduktape.dylib */; }; @@ -442,12 +443,10 @@ D41B74731C2125E50080A7B9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D41B74721C2125E50080A7B9 /* Assets.xcassets */; }; D43407E21D0E14CE00C2B3D4 /* shaders in Resources */ = {isa = PBXBuildFile; fileRef = D43407E11D0E14CE00C2B3D4 /* shaders */; }; D45A38BC1CF3006400659A24 /* libcrypto.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = D45A38B31CF3006400659A24 /* libcrypto.dylib */; }; - D45A38BE1CF3006400659A24 /* libjansson.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = D45A38B51CF3006400659A24 /* libjansson.dylib */; }; D45A38C11CF3006400659A24 /* libSDL2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = D45A38B81CF3006400659A24 /* libSDL2.dylib */; }; D45A38C21CF3006400659A24 /* libspeexdsp.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = D45A38B91CF3006400659A24 /* libspeexdsp.dylib */; }; D45A39591CF300AF00659A24 /* libcrypto.dylib in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D45A38B31CF3006400659A24 /* libcrypto.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; D45A395A1CF300AF00659A24 /* libfreetype.dylib in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D45A38B41CF3006400659A24 /* libfreetype.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; - D45A395B1CF300AF00659A24 /* libjansson.dylib in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D45A38B51CF3006400659A24 /* libjansson.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; D45A395E1CF300AF00659A24 /* libSDL2.dylib in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D45A38B81CF3006400659A24 /* libSDL2.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; D45A395F1CF300AF00659A24 /* libspeexdsp.dylib in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D45A38B91CF3006400659A24 /* libspeexdsp.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; D47304D51C4FF8250015C0EA /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D47304D41C4FF8250015C0EA /* libz.tbd */; }; @@ -558,13 +557,11 @@ F7D774901EC66FB000BE6EBC /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D47304D41C4FF8250015C0EA /* libz.tbd */; }; F7D774911EC66FBA00BE6EBC /* libcrypto.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = D45A38B31CF3006400659A24 /* libcrypto.dylib */; }; F7D774921EC66FBA00BE6EBC /* libfreetype.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = D45A38B41CF3006400659A24 /* libfreetype.dylib */; }; - F7D774931EC66FBA00BE6EBC /* libjansson.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = D45A38B51CF3006400659A24 /* libjansson.dylib */; }; F7D774941EC66FBA00BE6EBC /* libpng16.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = D4A8B4B31DB41873007A2F29 /* libpng16.dylib */; }; F7D774951EC66FBA00BE6EBC /* libspeexdsp.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = D45A38B91CF3006400659A24 /* libspeexdsp.dylib */; }; F7D774961EC66FBA00BE6EBC /* libzip.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = C6E96E351E0408B40076A04F /* libzip.dylib */; }; F7D774971EC6705F00BE6EBC /* libcrypto.dylib in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D45A38B31CF3006400659A24 /* libcrypto.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; F7D774981EC6705F00BE6EBC /* libfreetype.dylib in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D45A38B41CF3006400659A24 /* libfreetype.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; - F7D774991EC6705F00BE6EBC /* libjansson.dylib in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D45A38B51CF3006400659A24 /* libjansson.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; F7D7749A1EC6705F00BE6EBC /* libpng16.dylib in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D4A8B4B31DB41873007A2F29 /* libpng16.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; F7D7749B1EC6705F00BE6EBC /* libspeexdsp.dylib in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D45A38B91CF3006400659A24 /* libspeexdsp.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; F7D7749C1EC6705F00BE6EBC /* libzip.dylib in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C6E96E351E0408B40076A04F /* libzip.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; @@ -618,7 +615,6 @@ D45A39591CF300AF00659A24 /* libcrypto.dylib in Embed Frameworks */, 93FC09032418F41700CA3054 /* libduktape.dylib in Embed Frameworks */, D45A395A1CF300AF00659A24 /* libfreetype.dylib in Embed Frameworks */, - D45A395B1CF300AF00659A24 /* libjansson.dylib in Embed Frameworks */, D4A8B4B51DB4188D007A2F29 /* libpng16.dylib in Embed Frameworks */, D45A395E1CF300AF00659A24 /* libSDL2.dylib in Embed Frameworks */, 933F32ED24183CBB008376CE /* libicudata.dylib in Embed Frameworks */, @@ -636,7 +632,6 @@ F7D774A21EC6715C00BE6EBC /* libSDL2.dylib in Embed Frameworks */, F7D774971EC6705F00BE6EBC /* libcrypto.dylib in Embed Frameworks */, F7D774981EC6705F00BE6EBC /* libfreetype.dylib in Embed Frameworks */, - F7D774991EC6705F00BE6EBC /* libjansson.dylib in Embed Frameworks */, F7D7749A1EC6705F00BE6EBC /* libpng16.dylib in Embed Frameworks */, F7D7749B1EC6705F00BE6EBC /* libspeexdsp.dylib in Embed Frameworks */, F7D7749C1EC6705F00BE6EBC /* libzip.dylib in Embed Frameworks */, @@ -962,8 +957,6 @@ 4CE462461FD1613D0001CD98 /* Platform.Linux.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Platform.Linux.cpp; sourceTree = ""; }; 4CE462481FD1613D0001CD98 /* Platform.Posix.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Platform.Posix.cpp; sourceTree = ""; }; 4CE462491FD1613D0001CD98 /* Platform.Win32.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Platform.Win32.cpp; sourceTree = ""; }; - 4CE9AAAB1FDA7B14004093C6 /* ObjectJsonHelpers.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ObjectJsonHelpers.cpp; sourceTree = ""; }; - 4CE9AAAC1FDA7B14004093C6 /* ObjectJsonHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ObjectJsonHelpers.h; sourceTree = ""; }; 4CF67196206B7E720034ADDD /* object */ = {isa = PBXFileReference; lastKnownFileType = folder; name = object; path = data/object; sourceTree = ""; }; 4CFE4E7B1F90A3F1005243C2 /* Peep.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Peep.cpp; sourceTree = ""; }; 4CFE4E7C1F90A3F1005243C2 /* Peep.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Peep.h; sourceTree = ""; }; @@ -1368,6 +1361,8 @@ 93F76EFC20BFF77A00D4512C /* Paint.Banner.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Paint.Banner.cpp; sourceTree = ""; }; 93F76EFD20BFF77A00D4512C /* Paint.TileElement.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Paint.TileElement.cpp; sourceTree = ""; }; 93F76EFE20BFF77A00D4512C /* Paint.Entrance.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Paint.Entrance.cpp; sourceTree = ""; }; + 93FB271E24ED32B7008241C9 /* json.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = json.hpp; sourceTree = ""; }; + 93FB272024ED3601008241C9 /* Cursors.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Cursors.cpp; sourceTree = ""; }; 93FC08FD2418F3ED00CA3054 /* duktape.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = duktape.h; sourceTree = ""; }; 93FC08FE2418F3ED00CA3054 /* duk_config.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = duk_config.h; sourceTree = ""; }; 93FC09012418F3F500CA3054 /* libduktape.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libduktape.dylib; sourceTree = ""; }; @@ -1478,11 +1473,8 @@ D43BAB921F8C2B2B00A9E362 /* OpenGLAPIProc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OpenGLAPIProc.h; sourceTree = ""; }; D45A38B31CF3006400659A24 /* libcrypto.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libcrypto.dylib; sourceTree = ""; }; D45A38B41CF3006400659A24 /* libfreetype.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libfreetype.dylib; sourceTree = ""; }; - D45A38B51CF3006400659A24 /* libjansson.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libjansson.dylib; sourceTree = ""; }; D45A38B81CF3006400659A24 /* libSDL2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libSDL2.dylib; sourceTree = ""; }; D45A38B91CF3006400659A24 /* libspeexdsp.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libspeexdsp.dylib; sourceTree = ""; }; - D45A38C41CF3007A00659A24 /* jansson_config.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = jansson_config.h; sourceTree = ""; }; - D45A38C51CF3007A00659A24 /* jansson.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = jansson.h; sourceTree = ""; }; D45A38C71CF3007A00659A24 /* png.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = png.h; sourceTree = ""; }; D45A38C81CF3007A00659A24 /* pngconf.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pngconf.h; sourceTree = ""; }; D45A38C91CF3007A00659A24 /* pnglibconf.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pnglibconf.h; sourceTree = ""; }; @@ -1883,7 +1875,6 @@ D45A38BC1CF3006400659A24 /* libcrypto.dylib in Frameworks */, 93FC09022418F3F500CA3054 /* libduktape.dylib in Frameworks */, 933F32EC24183CBB008376CE /* libicudata.dylib in Frameworks */, - D45A38BE1CF3006400659A24 /* libjansson.dylib in Frameworks */, 933F32EA24183CBB008376CE /* libicuuc.dylib in Frameworks */, D4A8B4B41DB41873007A2F29 /* libpng16.dylib in Frameworks */, D45A38C11CF3006400659A24 /* libSDL2.dylib in Frameworks */, @@ -1905,7 +1896,6 @@ F7D7748E1EC66FA000BE6EBC /* libiconv.tbd in Frameworks */, F7D774911EC66FBA00BE6EBC /* libcrypto.dylib in Frameworks */, F7D774921EC66FBA00BE6EBC /* libfreetype.dylib in Frameworks */, - F7D774931EC66FBA00BE6EBC /* libjansson.dylib in Frameworks */, F7D774941EC66FBA00BE6EBC /* libpng16.dylib in Frameworks */, F7D774951EC66FBA00BE6EBC /* libspeexdsp.dylib in Frameworks */, F7D774961EC66FBA00BE6EBC /* libzip.dylib in Frameworks */, @@ -2263,6 +2253,15 @@ path = scripting; sourceTree = ""; }; + 93FB271D24ED32B7008241C9 /* nlohmann */ = { + isa = PBXGroup; + children = ( + 93FB271E24ED32B7008241C9 /* json.hpp */, + ); + name = nlohmann; + path = libxc/include/nlohmann; + sourceTree = SOURCE_ROOT; + }; C6352B871F477032006CCEE3 /* actions */ = { isa = PBXGroup; children = ( @@ -2632,14 +2631,13 @@ children = ( 9350B4F620B46E0900897BC5 /* freetype2 */, D45A38C61CF3007A00659A24 /* libpng16 */, + 93FB271D24ED32B7008241C9 /* nlohmann */, D45A38CA1CF3007A00659A24 /* openssl */, D45A39161CF3007A00659A24 /* SDL2 */, D45A39521CF3007A00659A24 /* speex */, 9350B44320B46E0800897BC5 /* unicode */, 93FC08FE2418F3ED00CA3054 /* duk_config.h */, 93FC08FD2418F3ED00CA3054 /* duktape.h */, - D45A38C41CF3007A00659A24 /* jansson_config.h */, - D45A38C51CF3007A00659A24 /* jansson.h */, C6E96E331E0408A80076A04F /* zip.h */, C6E96E341E0408A80076A04F /* zipconf.h */, ); @@ -2654,7 +2652,6 @@ D45A38B41CF3006400659A24 /* libfreetype.dylib */, 933F32E924183CBB008376CE /* libicudata.dylib */, 933F32E824183CBB008376CE /* libicuuc.dylib */, - D45A38B51CF3006400659A24 /* libjansson.dylib */, D4A8B4B31DB41873007A2F29 /* libpng16.dylib */, D45A38B81CF3006400659A24 /* libSDL2.dylib */, D45A38B91CF3006400659A24 /* libspeexdsp.dylib */, @@ -2892,6 +2889,7 @@ 4C7B53DE200143C200A52E21 /* Chat.h */, 4C7B53DF200143C200A52E21 /* Colour.cpp */, 4C7B53E0200143C200A52E21 /* Colour.h */, + 93FB272024ED3601008241C9 /* Cursors.cpp */, 4C7B53E3200143C200A52E21 /* Cursors.h */, 4C7B53E4200143C200A52E21 /* FontFamilies.cpp */, 4C7B53E5200143C200A52E21 /* FontFamilies.h */, @@ -3014,8 +3012,6 @@ F76C841F1EC4E7CC00FA49E2 /* Object.h */, F76C84201EC4E7CC00FA49E2 /* ObjectFactory.cpp */, F76C84211EC4E7CC00FA49E2 /* ObjectFactory.h */, - 4CE9AAAB1FDA7B14004093C6 /* ObjectJsonHelpers.cpp */, - 4CE9AAAC1FDA7B14004093C6 /* ObjectJsonHelpers.h */, 4C7B53A21FFC15ED00A52E21 /* ObjectLimits.h */, 4C7B53A31FFC180400A52E21 /* ObjectList.cpp */, 4C7B53A41FFC180400A52E21 /* ObjectList.h */, @@ -3669,6 +3665,7 @@ 939A359F20C12FDE00630B3F /* Paint.Surface.h in Headers */, C67B28192002D7F200109C93 /* Window_internal.h in Headers */, 93DFD05024521C1A001FCBAF /* ScPark.hpp in Headers */, + 93FB271F24ED32B7008241C9 /* json.hpp in Headers */, 93DFD02E24521BA0001FCBAF /* FileWatcher.h in Headers */, 2ADE2F28224418B2002598AF /* DataSerialiserTag.h in Headers */, 93DFD04C24521C1A001FCBAF /* ScDisposable.hpp in Headers */, @@ -3882,7 +3879,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "version=\"23\"\nzipname=\"openrct2-libs-v23-x64-macos-dylibs.zip\"\nliburl=\"https://github.com/OpenRCT2/Dependencies/releases/download/v$version/$zipname\"\n\n[[ ! -d \"${SRCROOT}/libxc\" || ! -e \"${SRCROOT}/libversion\" || $(head -n 1 \"${SRCROOT}/libversion\") != $version ]]\noutdated=$?\n\nif [[ $outdated -eq 0 ]]; then\nif [[ -d \"${SRCROOT}/libxc\" ]]; then rm -r \"${SRCROOT}/libxc\"; fi\nmkdir \"${SRCROOT}/libxc\"\n\ncurl -L -o \"${SRCROOT}/libxc/$zipname\" \"$liburl\"\nunzip -uaq -d \"${SRCROOT}/libxc\" \"${SRCROOT}/libxc/$zipname\"\nrm \"${SRCROOT}/libxc/$zipname\"\n\necho $version > \"${SRCROOT}/libversion\"\nfi\n"; + shellScript = "version=\"25\"\nzipname=\"openrct2-libs-v25-x64-macos-dylibs.zip\"\nliburl=\"https://github.com/OpenRCT2/Dependencies/releases/download/v$version/$zipname\"\n\n[[ ! -d \"${SRCROOT}/libxc\" || ! -e \"${SRCROOT}/libversion\" || $(head -n 1 \"${SRCROOT}/libversion\") != $version ]]\noutdated=$?\n\nif [[ $outdated -eq 0 ]]; then\nif [[ -d \"${SRCROOT}/libxc\" ]]; then rm -r \"${SRCROOT}/libxc\"; fi\nmkdir \"${SRCROOT}/libxc\"\n\ncurl -L -o \"${SRCROOT}/libxc/$zipname\" \"$liburl\"\nunzip -uaq -d \"${SRCROOT}/libxc\" \"${SRCROOT}/libxc/$zipname\"\nrm \"${SRCROOT}/libxc/$zipname\"\n\necho $version > \"${SRCROOT}/libversion\"\nfi\n"; }; D42C09D21C254F4E00309751 /* Build g2.dat */ = { isa = PBXShellScriptBuildPhase; @@ -4309,6 +4306,7 @@ C688788E20289AE70084B384 /* SSE41Drawing.cpp in Sources */, F76C866C1EC4E88400FA49E2 /* Object.cpp in Sources */, F76C866E1EC4E88400FA49E2 /* ObjectFactory.cpp in Sources */, + 93FB272124ED3601008241C9 /* Cursors.cpp in Sources */, C68878A220289B200084B384 /* RealNames.cpp in Sources */, C688787120289A780084B384 /* Ride.cpp in Sources */, F76C86701EC4E88400FA49E2 /* ObjectManager.cpp in Sources */, @@ -4333,7 +4331,6 @@ C68878CD20289B9B0084B384 /* DefaultObjects.cpp in Sources */, 939A359A20C12FC800630B3F /* Paint.Litter.cpp in Sources */, C688788220289ADE0084B384 /* Rect.cpp in Sources */, - 93F9DA3920B46FB800D1BE92 /* ObjectJsonHelpers.cpp in Sources */, C688787320289A780084B384 /* RideRatings.cpp in Sources */, C688790D20289B9B0084B384 /* Circus.cpp in Sources */, C688788F20289B140084B384 /* Chat.cpp in Sources */, diff --git a/debian/control b/debian/control index 7539bb83e7..9c3d0a27de 100644 --- a/debian/control +++ b/debian/control @@ -4,7 +4,7 @@ Section: misc Priority: optional Standards-Version: 3.9.2 Multi-Arch: same -Build-Depends: debhelper (>= 9), cmake (>= 3.8), duktape-dev, libsdl2-dev, g++ (>= 4:7), pkg-config, libjansson4 (>= 2.5), libjansson-dev (>= 2.3), libspeex-dev, libspeexdsp-dev, libcurl4-openssl-dev, libcrypto++-dev, libfontconfig1-dev, libfreetype6-dev, libpng-dev, libssl-dev, libzip-dev (>= 1.0.0), libicu-dev (>= 59.0) +Build-Depends: debhelper (>= 9), cmake (>= 3.8), duktape-dev, libsdl2-dev, g++ (>= 4:7), pkg-config, nlohmann-json3-dev (>= 3.6.0), libspeex-dev, libspeexdsp-dev, libcurl4-openssl-dev, libcrypto++-dev, libfontconfig1-dev, libfreetype6-dev, libpng-dev, libssl-dev, libzip-dev (>= 1.0.0), libicu-dev (>= 59.0) Package: openrct2 Architecture: any diff --git a/distribution/readme.txt b/distribution/readme.txt index d31b6ec4d1..5899643335 100644 --- a/distribution/readme.txt +++ b/distribution/readme.txt @@ -1,4 +1,4 @@ -Last updated: 2020-08-15 +Last updated: 2020-09-03 Release version: 0.3.0 ------------------------------------------------------------------------ @@ -55,7 +55,7 @@ following information in your bug report: * Bug details, including instructions how to reproduce it * Platform (Windows, Linux, FreeBSD, ...) and compiler (including version) if you compiled OpenRCT2 yourself. - * The processor architecture of your OS (32 bits Windows, 64 bits Windows, + * The processor architecture of your OS (x86 Windows, x86-64 Windows, Android on an ARM, Linux on a PowerPC, ...) * The language and culture your operating system is using. * Attach a saved game *and* a screenshot if possible @@ -66,8 +66,8 @@ following information in your bug report: 3.0) Supported platforms ---- ------------------- -OpenRCT2 is currently supported on Windows 7 and above, many distributions of -Linux, macOS 10.9 or higher, Android, FreeBSD and OpenBSD. OpenRCT2 will only work on +OpenRCT2 is currently supported on Windows Vista and above, many distributions of +Linux, macOS 10.13 or higher, Android, FreeBSD and OpenBSD. OpenRCT2 will only work on little-endian architectures. Further instructions can be found on GitHub. @@ -143,12 +143,12 @@ to all files in this distribution, except as noted below. dukglue | MIT licence. duktape | MIT licence. -Jansson | MIT licence. libcURL | MIT (or Modified BSD-style) licence. libicu | Unicode licence. -libspeex | BSD-style licence. libpng | libpng licence. +libspeex | BSD-style licence. libzip | BSD 3 clause licence. +nlohmann-json | MIT licence. OpenSSL | OpenSSL Licence SDL2 | zlib licence. zlib | zlib licence. diff --git a/openrct2.common.props b/openrct2.common.props index d79ec76ac1..650b17347d 100644 --- a/openrct2.common.props +++ b/openrct2.common.props @@ -76,7 +76,7 @@ DebugFull - benchmarkd.lib;libbreakpadd.lib;libbreakpad_clientd.lib;bz2d.lib;discord-rpc.lib;duktape.lib;freetyped.lib;jansson_d.lib;libpng16d.lib;libspeexdsp.lib;SDL2d.lib;zip.lib;zlibd.lib;%(AdditionalDependencies) + benchmarkd.lib;brotlicommon-static.lib;brotlidec-static.lib;brotlienc-static.lib;libbreakpadd.lib;libbreakpad_clientd.lib;bz2d.lib;discord-rpc.lib;duktape.lib;freetyped.lib;libpng16d.lib;libspeexdsp.lib;SDL2d.lib;zip.lib;zlibd.lib;%(AdditionalDependencies) @@ -94,7 +94,7 @@ DebugFull true true - benchmark.lib;libbreakpad.lib;libbreakpad_client.lib;bz2.lib;discord-rpc.lib;duktape.lib;freetype.lib;jansson.lib;libpng16.lib;libspeexdsp.lib;SDL2.lib;zip.lib;zlib.lib;%(AdditionalDependencies) + benchmark.lib;brotlicommon-static.lib;brotlidec-static.lib;brotlienc-static.lib;libbreakpad.lib;libbreakpad_client.lib;bz2.lib;discord-rpc.lib;duktape.lib;freetype.lib;libpng16.lib;libspeexdsp.lib;SDL2.lib;zip.lib;zlib.lib;%(AdditionalDependencies) diff --git a/openrct2.proj b/openrct2.proj index 5fc7f00b5c..5cd64342d8 100644 --- a/openrct2.proj +++ b/openrct2.proj @@ -37,10 +37,10 @@ $(RootDir).dependencies - https://github.com/OpenRCT2/Dependencies/releases/download/v24/openrct2-libs-v21-x86-windows-static.zip - 21eef7db74fd1c886f3a1ef3f7989721e42a726b - https://github.com/OpenRCT2/Dependencies/releases/download/v24/openrct2-libs-v21-x64-windows-static.zip - 6367d76f6b95859f8b45cbf03b222b8b5200d8b5 + https://github.com/OpenRCT2/Dependencies/releases/download/v25/openrct2-libs-v25-x86-windows-static.zip + c8c1a3052721c7dedde8c3cab73ed09b793a9b8e + https://github.com/OpenRCT2/Dependencies/releases/download/v25/openrct2-libs-v25-x64-windows-static.zip + 5384dffd21b4e87c36bb91c6882e277023285612 2fe3bd994b3189899d93f1d5a881e725e046fdc2 https://github.com/google/googletest/archive/$(GtestVersion).zip 058b9df80244c03f1633cb06e9f70471a29ebb8e diff --git a/readme.md b/readme.md index 724a645f01..fe812410ce 100644 --- a/readme.md +++ b/readme.md @@ -109,7 +109,7 @@ The program can also be built as a command line program using CMake. This type o - libpng (>= 1.2) - speexdsp (only for UI client) - curl (only if building with http support) -- jansson (>= 2.5) +- nlohmann-json (>= 3.6.0) - openssl (>= 1.0; only if building with multiplayer support) - icu (>= 59.0) - zlib @@ -148,7 +148,7 @@ The recommended way of building OpenRCT2 for macOS is with Xcode. The Xcode buil #### CMake: A command line version of OpenRCT2 can be built using CMake. This type of build requires you to provide the dependencies yourself. The supported method of doing this is with [Homebrew](http://brew.sh). Once you have Homebrew installed, you can download all the required libraries with this command: ``` -brew install cmake duktape freetype icu4c jansson libpng libzip openssl pkg-config sdl2 speexdsp +brew install cmake duktape freetype icu4c libpng libzip nlohmann-json openssl pkg-config sdl2 speexdsp ``` Once you have the dependencies installed, you can build the project using CMake using the following commands: diff --git a/scripts/run-clang-format.py b/scripts/run-clang-format.py index be5e165f49..8f88bfc462 100755 --- a/scripts/run-clang-format.py +++ b/scripts/run-clang-format.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """A wrapper script around clang-format, suitable for linting multiple files and to use for continuous integration. diff --git a/src/openrct2-android/app/src/main/CMakeLists.txt b/src/openrct2-android/app/src/main/CMakeLists.txt index ada58e4c05..7b0c331fbe 100644 --- a/src/openrct2-android/app/src/main/CMakeLists.txt +++ b/src/openrct2-android/app/src/main/CMakeLists.txt @@ -32,7 +32,6 @@ ExternalProject_Add(libs BUILD_BYPRODUCTS ${CMAKE_BINARY_DIR}/libs/lib/${CMAKE_SHARED_LIBRARY_PREFIX}freetype${CMAKE_SHARED_LIBRARY_SUFFIX} - ${CMAKE_BINARY_DIR}/libs/lib/${CMAKE_SHARED_LIBRARY_PREFIX}jansson${CMAKE_SHARED_LIBRARY_SUFFIX} ${CMAKE_BINARY_DIR}/libs/lib/${CMAKE_SHARED_LIBRARY_PREFIX}png16${CMAKE_SHARED_LIBRARY_SUFFIX} ${CMAKE_BINARY_DIR}/libs/lib/${CMAKE_SHARED_LIBRARY_PREFIX}SDL2-2.0${CMAKE_SHARED_LIBRARY_SUFFIX} ${CMAKE_BINARY_DIR}/libs/lib/${CMAKE_STATIC_LIBRARY_PREFIX}SDL2main${CMAKE_STATIC_LIBRARY_SUFFIX} @@ -58,7 +57,6 @@ add_custom_command(TARGET libs POST_BUILD # COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_BINARY_DIR}/libs/lib/*.so" ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_BINARY_DIR}/libs/lib/libcrypto.so" ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_BINARY_DIR}/libs/lib/libfreetype.so" ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} - COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_BINARY_DIR}/libs/lib/libjansson.so" ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_BINARY_DIR}/libs/lib/libpng16.so" ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_BINARY_DIR}/libs/lib/libSDL2-2.0.so" ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_BINARY_DIR}/libs/lib/libspeexdsp.so" ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} @@ -71,12 +69,6 @@ set_target_properties(freetype PROPERTIES IMPORTED_LOCATION ) add_dependencies(freetype libs) -add_library(jansson SHARED IMPORTED) -set_target_properties(jansson PROPERTIES IMPORTED_LOCATION - ${CMAKE_BINARY_DIR}/libs/lib/${CMAKE_SHARED_LIBRARY_PREFIX}jansson${CMAKE_SHARED_LIBRARY_SUFFIX} -) -add_dependencies(jansson libs) - add_library(png SHARED IMPORTED) set_target_properties(png PROPERTIES IMPORTED_LOCATION ${CMAKE_BINARY_DIR}/libs/lib/${CMAKE_SHARED_LIBRARY_PREFIX}png16${CMAKE_SHARED_LIBRARY_SUFFIX} @@ -175,7 +167,7 @@ file(GLOB_RECURSE OPENRCT2_CLI_SOURCES "${ORCT2_ROOT}/src/openrct2-cli/*.hpp") add_library(openrct2 SHARED ${LIBOPENRCT2_SOURCES}) -target_link_libraries(openrct2 android stdc++ log dl z SDL2 png jansson icu icuuc icudata ssl crypto) +target_link_libraries(openrct2 android stdc++ log dl z SDL2 png icu icuuc icudata ssl crypto) add_library(openrct2-ui SHARED ${OPENRCT2_GUI_SOURCES}) target_link_libraries(openrct2-ui openrct2 android stdc++ GLESv1_CM GLESv2 SDL2main speexdsp) @@ -187,3 +179,8 @@ target_include_directories(openrct2 SYSTEM PRIVATE "${ORCT2_ROOT}/src/thirdparty target_include_directories(openrct2-ui PRIVATE "${ORCT2_ROOT}/src") target_include_directories(openrct2-ui SYSTEM PRIVATE "${ORCT2_ROOT}/src/thirdparty") target_include_directories(openrct2-cli PRIVATE "${ORCT2_ROOT}/src") + +# Header-only nlohmann library is installed in CI images at /nlohmann. +# To be tiny bit more specific, use '/nlohmann/../' instead of just '/' +target_include_directories(openrct2 PRIVATE "/nlohmann/../") +target_include_directories(openrct2-ui PRIVATE "/nlohmann/../") diff --git a/src/openrct2-android/app/src/main/java/io/openrct2/GameActivity.java b/src/openrct2-android/app/src/main/java/io/openrct2/GameActivity.java index 31334ca761..db89ea1dd9 100644 --- a/src/openrct2-android/app/src/main/java/io/openrct2/GameActivity.java +++ b/src/openrct2-android/app/src/main/java/io/openrct2/GameActivity.java @@ -15,7 +15,6 @@ public class GameActivity extends SDLActivity { return new String[]{ "c++_shared", "speexdsp", - "jansson", "png16", "SDL2-2.0", diff --git a/src/openrct2-ui/CMakeLists.txt b/src/openrct2-ui/CMakeLists.txt index d4978b2ffa..827a49a420 100644 --- a/src/openrct2-ui/CMakeLists.txt +++ b/src/openrct2-ui/CMakeLists.txt @@ -9,11 +9,9 @@ option(DISABLE_OPENGL "Disable OpenGL support.") # Third party libraries if (MSVC) - find_package(jansson REQUIRED) find_package(SDL2 REQUIRED) find_library(SPEEX_LDFLAGS libspeexdsp) else () - PKG_CHECK_MODULES(JANSSON REQUIRED jansson>=2.5) PKG_CHECK_MODULES(SDL2 REQUIRED sdl2) PKG_CHECK_MODULES(SPEEX REQUIRED speexdsp) endif () diff --git a/src/openrct2-ui/interface/Theme.cpp b/src/openrct2-ui/interface/Theme.cpp index 8980ae887a..dc741e9221 100644 --- a/src/openrct2-ui/interface/Theme.cpp +++ b/src/openrct2-ui/interface/Theme.cpp @@ -14,7 +14,6 @@ #include "Window.h" #include -#include #include #include #include @@ -53,8 +52,11 @@ struct UIThemeWindowEntry rct_windowclass WindowClass; WindowTheme Theme; - json_t* ToJson() const; - static UIThemeWindowEntry FromJson(const WindowThemeDesc* wtDesc, const json_t* json); + json_t ToJson() const; + /** + * @note json is deliberately left non-const: json_t behaviour changes when const + */ + static UIThemeWindowEntry FromJson(const WindowThemeDesc* wtDesc, json_t& json); }; /** @@ -76,10 +78,13 @@ public: void SetEntry(const UIThemeWindowEntry* entry); void RemoveEntry(rct_windowclass windowClass); - json_t* ToJson() const; + json_t ToJson() const; bool WriteToFile(const std::string& path) const; - static UITheme* FromJson(const json_t* json); + /** + * @note json is deliberately left non-const: json_t behaviour changes when const + */ + static UITheme* FromJson(json_t& json); static UITheme* FromFile(const std::string& path); static UITheme CreatePredefined(const std::string& name, const UIThemeWindowEntry* entries, uint8_t flags); }; @@ -266,7 +271,7 @@ static void ThrowThemeLoadException() #pragma region UIThemeEntry -json_t* UIThemeWindowEntry::ToJson() const +json_t UIThemeWindowEntry::ToJson() const { const WindowThemeDesc* wtDesc = GetWindowThemeDescriptor(WindowClass); if (wtDesc == nullptr) @@ -274,37 +279,41 @@ json_t* UIThemeWindowEntry::ToJson() const return nullptr; } - json_t* jsonColours = json_array(); + json_t jsonColours = json_t::array(); for (uint8_t i = 0; i < wtDesc->NumColours; i++) { colour_t colour = Theme.Colours[i]; - json_array_append_new(jsonColours, json_integer(colour)); + jsonColours.push_back(colour); } - json_t* jsonEntry = json_object(); - json_object_set_new(jsonEntry, "colours", jsonColours); + json_t jsonEntry = { + { "colours", jsonColours }, + }; return jsonEntry; } -UIThemeWindowEntry UIThemeWindowEntry::FromJson(const WindowThemeDesc* wtDesc, const json_t* json) +UIThemeWindowEntry UIThemeWindowEntry::FromJson(const WindowThemeDesc* wtDesc, json_t& jsonData) { - json_t* jsonColours = json_object_get(json, "colours"); - if (jsonColours == nullptr) + Guard::Assert(jsonData.is_object(), "UIThemeWindowEntry::FromJson expects parameter jsonData to be object"); + + auto jsonColours = Json::AsArray(jsonData["colours"]); + + if (jsonColours.empty()) { ThrowThemeLoadException(); } - uint8_t numColours = static_cast(json_array_size(jsonColours)); - numColours = std::min(numColours, wtDesc->NumColours); - UIThemeWindowEntry result{}; result.WindowClass = wtDesc->WindowClass; result.Theme = wtDesc->DefaultTheme; - for (uint8_t i = 0; i < numColours; i++) + // result.Theme.Colours only has 6 values + size_t colourCount = std::min(jsonColours.size(), static_cast(wtDesc->NumColours)); + + for (size_t i = 0; i < colourCount; i++) { - result.Theme.Colours[i] = static_cast(json_integer_value(json_array_get(jsonColours, i))); + result.Theme.Colours[i] = Json::GetNumber(jsonColours[i]); } return result; @@ -355,10 +364,10 @@ void UITheme::RemoveEntry(rct_windowclass windowClass) } } -json_t* UITheme::ToJson() const +json_t UITheme::ToJson() const { // Create entries - json_t* jsonEntries = json_object(); + json_t jsonEntries; for (const UIThemeWindowEntry& entry : Entries) { const WindowThemeDesc* wtDesc = GetWindowThemeDescriptor(entry.WindowClass); @@ -366,30 +375,29 @@ json_t* UITheme::ToJson() const { return nullptr; } - json_object_set_new(jsonEntries, wtDesc->WindowClassSZ, entry.ToJson()); + jsonEntries[wtDesc->WindowClassSZ] = entry.ToJson(); } // Create theme object - json_t* jsonTheme = json_object(); - json_object_set_new(jsonTheme, "name", json_string(Name.c_str())); - json_object_set_new(jsonTheme, "entries", jsonEntries); - - json_object_set_new(jsonTheme, "useLightsRide", json_boolean(Flags & UITHEME_FLAG_USE_LIGHTS_RIDE)); - json_object_set_new(jsonTheme, "useLightsPark", json_boolean(Flags & UITHEME_FLAG_USE_LIGHTS_PARK)); - json_object_set_new( - jsonTheme, "useAltScenarioSelectFont", json_boolean(Flags & UITHEME_FLAG_USE_ALTERNATIVE_SCENARIO_SELECT_FONT)); - json_object_set_new(jsonTheme, "useFullBottomToolbar", json_boolean(Flags & UITHEME_FLAG_USE_FULL_BOTTOM_TOOLBAR)); + json_t jsonTheme = { + { "name", Name }, + { "entries", jsonEntries }, + { "useLightsRide", (Flags & UITHEME_FLAG_USE_LIGHTS_RIDE) != 0 }, + { "useLightsPark", (Flags & UITHEME_FLAG_USE_LIGHTS_PARK) != 0 }, + { "useAltScenarioSelectFont", (Flags & UITHEME_FLAG_USE_ALTERNATIVE_SCENARIO_SELECT_FONT) != 0 }, + { "useFullBottomToolbar", (Flags & UITHEME_FLAG_USE_FULL_BOTTOM_TOOLBAR) != 0 }, + }; return jsonTheme; } bool UITheme::WriteToFile(const std::string& path) const { - json_t* jsonTheme = ToJson(); + auto jsonTheme = ToJson(); bool result; try { - Json::WriteToFile(path.c_str(), jsonTheme, JSON_INDENT(4) | JSON_PRESERVE_ORDER); + Json::WriteToFile(path.c_str(), jsonTheme); result = true; } catch (const std::exception& ex) @@ -398,52 +406,51 @@ bool UITheme::WriteToFile(const std::string& path) const result = false; } - json_decref(jsonTheme); return result; } -UITheme* UITheme::FromJson(const json_t* json) +UITheme* UITheme::FromJson(json_t& jsonObj) { - const char* themeName = json_string_value(json_object_get(json, "name")); - if (themeName == nullptr) + Guard::Assert(jsonObj.is_object(), "UITheme::FromJson expects parameter jsonObj to be object"); + + const std::string themeName = Json::GetString(jsonObj["name"]); + if (themeName.empty()) { ThrowThemeLoadException(); } - json_t* jsonEntries = json_object_get(json, "entries"); + json_t jsonEntries = jsonObj["entries"]; UITheme* result = nullptr; try { result = new UITheme(themeName); - if (json_is_true(json_object_get(json, "useLightsRide"))) - { - result->Flags |= UITHEME_FLAG_USE_LIGHTS_RIDE; - } - if (json_is_true(json_object_get(json, "useLightsPark"))) - { - result->Flags |= UITHEME_FLAG_USE_LIGHTS_PARK; - } - if (json_is_true(json_object_get(json, "useAltScenarioSelectFont"))) - { - result->Flags |= UITHEME_FLAG_USE_ALTERNATIVE_SCENARIO_SELECT_FONT; - } - if (json_is_true(json_object_get(json, "useFullBottomToolbar"))) - { - result->Flags |= UITHEME_FLAG_USE_FULL_BOTTOM_TOOLBAR; - } + result->Flags = Json::GetFlags( + jsonObj, + { + { "useLightsRide", UITHEME_FLAG_USE_LIGHTS_RIDE }, + { "useLightsPark", UITHEME_FLAG_USE_LIGHTS_PARK }, + { "useAltScenarioSelectFont", UITHEME_FLAG_USE_ALTERNATIVE_SCENARIO_SELECT_FONT }, + { "useFullBottomToolbar", UITHEME_FLAG_USE_FULL_BOTTOM_TOOLBAR }, + }); - const char* jkey; - json_t* jvalue; - json_object_foreach(jsonEntries, jkey, jvalue) + if (jsonEntries.is_object()) { - const WindowThemeDesc* wtDesc = GetWindowThemeDescriptor(jkey); - if (wtDesc == nullptr) - continue; + for (auto& [jsonKey, jsonValue] : jsonEntries.items()) + { + if (jsonValue.is_object()) + { + const WindowThemeDesc* wtDesc = GetWindowThemeDescriptor(jsonKey.data()); + if (wtDesc == nullptr) + { + continue; + } - UIThemeWindowEntry entry = UIThemeWindowEntry::FromJson(wtDesc, jvalue); - result->SetEntry(&entry); + UIThemeWindowEntry entry = UIThemeWindowEntry::FromJson(wtDesc, jsonValue); + result->SetEntry(&entry); + } + } } return result; @@ -457,8 +464,8 @@ UITheme* UITheme::FromJson(const json_t* json) UITheme* UITheme::FromFile(const std::string& path) { - json_t* json = nullptr; UITheme* result = nullptr; + json_t json; try { json = Json::ReadFromFile(path.c_str()); @@ -469,8 +476,6 @@ UITheme* UITheme::FromFile(const std::string& path) log_error("Unable to read theme: %s", path.c_str()); result = nullptr; } - - json_decref(json); return result; } diff --git a/src/openrct2-ui/windows/ObjectLoadError.cpp b/src/openrct2-ui/windows/ObjectLoadError.cpp index 3e53d181d2..ab577616a1 100644 --- a/src/openrct2-ui/windows/ObjectLoadError.cpp +++ b/src/openrct2-ui/windows/ObjectLoadError.cpp @@ -211,18 +211,17 @@ private: if (response.status == Http::Status::OK) { auto jresponse = Json::FromString(response.body); - if (jresponse != nullptr) + if (jresponse.is_object()) { - auto objName = json_string_value(json_object_get(jresponse, "name")); - auto source = json_string_value(json_object_get(jresponse, "source")); - auto downloadLink = json_string_value(json_object_get(jresponse, "download")); - if (downloadLink != nullptr) + auto objName = Json::GetString(jresponse["name"]); + auto source = Json::GetString(jresponse["source"]); + auto downloadLink = Json::GetString(jresponse["download"]); + if (!downloadLink.empty()) { _lastDownloadSource = source; UpdateProgress({ name, source, _currentDownloadIndex, _entries.size() }); DownloadObject(entry, objName, downloadLink); } - json_decref(jresponse); } } else if (response.status == Http::Status::NotFound) diff --git a/src/openrct2/CMakeLists.txt b/src/openrct2/CMakeLists.txt index 23a7c0b5e1..8dcd0234d2 100644 --- a/src/openrct2/CMakeLists.txt +++ b/src/openrct2/CMakeLists.txt @@ -112,15 +112,12 @@ endif () # Third party libraries if (MSVC) - find_package(jansson CONFIG REQUIRED) - set(JANSSON_LIBRARIES "jansson::jansson") find_package(png 1.6 REQUIRED) find_package(zlib REQUIRED) find_path(LIBZIP_INCLUDE_DIRS zip.h) find_library(LIBZIP_LIBRARIES zip) else () - PKG_CHECK_MODULES(JANSSON REQUIRED jansson>=2.5) PKG_CHECK_MODULES(LIBZIP REQUIRED libzip>=1.0) PKG_CHECK_MODULES(ZLIB REQUIRED zlib) @@ -137,12 +134,12 @@ else () endif () if (STATIC) - target_link_libraries(${PROJECT_NAME} ${JANSSON_STATIC_LIBRARIES} + target_link_libraries(${PROJECT_NAME} ${PNG_STATIC_LIBRARIES} ${ZLIB_STATIC_LIBRARIES} ${LIBZIP_STATIC_LIBRARIES}) else () - target_link_libraries(${PROJECT_NAME} ${JANSSON_LIBRARIES} + target_link_libraries(${PROJECT_NAME} ${PNG_LIBRARIES} ${ZLIB_LIBRARIES} ${LIBZIP_LIBRARIES}) @@ -201,7 +198,6 @@ endif() # Includes target_include_directories(${PROJECT_NAME} PRIVATE ${LIBZIP_INCLUDE_DIRS}) -target_include_directories(${PROJECT_NAME} PUBLIC ${JANSSON_INCLUDE_DIRS}) target_include_directories(${PROJECT_NAME} PRIVATE ${PNG_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIRS}) include_directories(${PROJECT_NAME} SYSTEM ${CMAKE_CURRENT_LIST_DIR}/../thirdparty) diff --git a/src/openrct2/CmdlineSprite.cpp b/src/openrct2/CmdlineSprite.cpp index f4027f8cc5..533710c13c 100644 --- a/src/openrct2/CmdlineSprite.cpp +++ b/src/openrct2/CmdlineSprite.cpp @@ -14,6 +14,7 @@ #include "Context.h" #include "OpenRCT2.h" #include "core/Imaging.h" +#include "core/Json.hpp" #include "drawing/Drawing.h" #include "drawing/ImageImporter.h" #include "object/ObjectLimits.h" @@ -26,7 +27,6 @@ #include #include -#include #ifdef _WIN32 # include "core/String.hpp" @@ -675,28 +675,16 @@ int32_t cmdline_for_sprite(const char** argv, int32_t argc) const char* spriteDescriptionPath = argv[2]; char* directoryPath = path_get_directory(spriteDescriptionPath); - json_error_t error; - auto fp = fopen(spriteDescriptionPath, "rb"); - if (fp == nullptr) + json_t jsonSprites = Json::ReadFromFile(spriteDescriptionPath); + if (jsonSprites.is_null()) { fprintf(stderr, "Unable to read sprite description file: %s\n", spriteDescriptionPath); return -1; } - json_t* sprite_list = json_loadf(fp, JSON_REJECT_DUPLICATES, &error); - fclose(fp); - if (sprite_list == nullptr) - { - fprintf( - stderr, "Error parsing sprite description file: %s at line %d column %d\n", error.text, error.line, - error.column); - return -1; - } - - if (!json_is_array(sprite_list)) + if (!jsonSprites.is_array()) { fprintf(stderr, "Error: expected array\n"); - json_decref(sprite_list); return -1; } @@ -708,66 +696,45 @@ int32_t cmdline_for_sprite(const char** argv, int32_t argc) fprintf(stdout, "Building: %s\n", spriteFilePath); - size_t i; - json_t* sprite_description; + json_t sprite_description; - json_array_foreach(sprite_list, i, sprite_description) + // Note: jsonSprite is deliberately left non-const: json_t behaviour changes when const + for (auto& [jsonKey, jsonSprite] : jsonSprites.items()) { - if (!json_is_object(sprite_description)) + if (!jsonSprite.is_object()) { - fprintf(stderr, "Error: expected object for sprite %lu\n", static_cast(i)); - json_decref(sprite_list); + fprintf(stderr, "Error: expected object for sprite %s\n", jsonKey.c_str()); return -1; } - json_t* path = json_object_get(sprite_description, "path"); - if (!json_is_string(path)) + json_t path = jsonSprite["path"]; + if (!path.is_string()) { - fprintf(stderr, "Error: no path provided for sprite %lu\n", static_cast(i)); - json_decref(sprite_list); + fprintf(stderr, "Error: no path provided for sprite %s\n", jsonKey.c_str()); return -1; } - // Get x and y offsets, if present - json_t* x_offset = json_object_get(sprite_description, "x_offset"); - json_t* y_offset = json_object_get(sprite_description, "y_offset"); + std::string strPath = Json::GetString(path); - // Get palette option, if present - bool keep_palette = false; - json_t* palette = json_object_get(sprite_description, "palette"); - if (json_is_string(palette)) - { - const char* option = json_string_value(palette); - if (strncmp(option, "keep", 4) == 0) - { - keep_palette = true; - } - } + json_t x_offset = jsonSprite["x_offset"]; + json_t y_offset = jsonSprite["y_offset"]; - // Get forcebmp option, if present - bool forceBmp = false; - json_t* forceBmpObject = json_object_get(sprite_description, "forceBmp"); - if (palette && json_is_boolean(forceBmpObject)) - { - forceBmp = json_boolean_value(forceBmpObject); - } + bool keep_palette = Json::GetString(jsonSprite["palette"]) == "keep"; + bool forceBmp = !jsonSprite["palette"].is_null() && Json::GetBoolean(jsonSprite["forceBmp"]); - // Resolve absolute sprite path - auto imagePath = platform_get_absolute_path(json_string_value(path), directoryPath); + auto imagePath = platform_get_absolute_path(strPath.c_str(), directoryPath); auto importResult = sprite_file_import( - imagePath.c_str(), x_offset == nullptr ? 0 : json_integer_value(x_offset), - y_offset == nullptr ? 0 : json_integer_value(y_offset), keep_palette, forceBmp, gSpriteMode); + imagePath.c_str(), Json::GetNumber(x_offset), Json::GetNumber(y_offset), keep_palette, + forceBmp, gSpriteMode); if (importResult == std::nullopt) { fprintf(stderr, "Could not import image file: %s\nCanceling\n", imagePath.c_str()); - json_decref(sprite_list); return -1; } if (!sprite_file_open(spriteFilePath)) { fprintf(stderr, "Unable to open sprite file: %s\nCanceling\n", spriteFilePath); - json_decref(sprite_list); return -1; } @@ -790,7 +757,6 @@ int32_t cmdline_for_sprite(const char** argv, int32_t argc) if (!sprite_file_save(spriteFilePath)) { fprintf(stderr, "Could not save sprite file: %s\nCanceling\n", imagePath.c_str()); - json_decref(sprite_list); return -1; } @@ -800,7 +766,6 @@ int32_t cmdline_for_sprite(const char** argv, int32_t argc) sprite_file_close(); } - json_decref(sprite_list); free(directoryPath); fprintf(stdout, "Finished\n"); diff --git a/src/openrct2/Version.cpp b/src/openrct2/Version.cpp index 3311a1e6c1..94e07095f5 100644 --- a/src/openrct2/Version.cpp +++ b/src/openrct2/Version.cpp @@ -81,24 +81,12 @@ NewVersionInfo get_latest_version() return {}; } - json_t* root = Json::FromString(res.body); + json_t root = Json::FromString(res.body); - auto get_as_string = [root](std::string name) { - std::string value; - json_t* json_value = json_object_get(root, name.c_str()); - if (json_is_string(json_value)) - { - value = (json_string_value(json_value)); - } - return value; - }; - - verinfo.tag = get_as_string("tag_name"); - verinfo.name = get_as_string("name"); - verinfo.changelog = get_as_string("body"); - verinfo.url = get_as_string("html_url"); - - json_decref(root); + verinfo.tag = Json::GetString(root["tag_name"]); + verinfo.name = Json::GetString(root["name"]); + verinfo.changelog = Json::GetString(root["body"]); + verinfo.url = Json::GetString(root["html_url"]); gConfigGeneral.last_version_check_time = now; config_save_default(); diff --git a/src/openrct2/core/Json.cpp b/src/openrct2/core/Json.cpp index e996d9e420..eb128106e0 100644 --- a/src/openrct2/core/Json.cpp +++ b/src/openrct2/core/Json.cpp @@ -15,9 +15,8 @@ namespace Json { - json_t* ReadFromFile(const utf8* path, size_t maxSize) + json_t ReadFromFile(const utf8* path, size_t maxSize) { - json_t* json = nullptr; auto fs = OpenRCT2::FileStream(path, OpenRCT2::FILE_MODE_OPEN); size_t fileLength = static_cast(fs.GetLength()); @@ -26,42 +25,101 @@ namespace Json throw IOException("Json file too large."); } - utf8* fileData = Memory::Allocate(fileLength + 1); - fs.Read(fileData, fileLength); - fileData[fileLength] = '\0'; + auto fileData = std::string(static_cast(fileLength) + 1, '\0'); + fs.Read(static_cast(fileData.data()), fileLength); - json_error_t jsonLoadError; - json = json_loads(fileData, 0, &jsonLoadError); - Memory::Free(fileData); + json_t json; - if (json == nullptr) + try { - throw JsonException(&jsonLoadError); + json = json_t::parse(fileData, nullptr, true, true); + } + catch (const json_t::exception& e) + { + throw JsonException(String::Format("Unable to parse JSON file (%s)\n\t%s", path, e.what())); } return json; } - void WriteToFile(const utf8* path, const json_t* json, size_t flags) + void WriteToFile(const utf8* path, const json_t& jsonData, int indentSize) { // Serialise JSON - const char* jsonOutput = json_dumps(json, flags); + std::string jsonOutput = jsonData.dump(indentSize); // Write to file auto fs = OpenRCT2::FileStream(path, OpenRCT2::FILE_MODE_WRITE); - size_t jsonOutputSize = String::SizeOf(jsonOutput); - fs.Write(jsonOutput, jsonOutputSize); + fs.Write(jsonOutput.data(), jsonOutput.size()); } - json_t* FromString(std::string_view raw) + json_t FromString(std::string_view raw) { - json_t* root; - json_error_t error; - root = json_loadb(raw.data(), raw.size(), 0, &error); - if (root == nullptr) + json_t json; + + try { - throw JsonException(&error); + json = json_t::parse(raw, nullptr, true, true); } - return root; + catch (const json_t::exception& e) + { + log_error("Unable to parse JSON string (%s)\n\t%s", raw, e.what()); + } + + return json; + } + + json_t FromVector(const std::vector& vec) + { + json_t json; + + try + { + json = json_t::parse(vec.begin(), vec.end()); + } + catch (const json_t::exception& e) + { + log_error("Unable to parse JSON vector (%s)\n\t%s", vec.data(), e.what()); + } + + return json; + } + + std::string GetString(const json_t& jsonObj, const std::string& defaultValue) + { + return jsonObj.is_string() ? jsonObj.get() : defaultValue; + } + + bool GetBoolean(const json_t& jsonObj, bool defaultValue) + { + return jsonObj.is_boolean() ? jsonObj.get() : defaultValue; + } + + json_t AsObject(const json_t& jsonObj) + { + return jsonObj.is_object() ? jsonObj : json_t::object(); + } + + json_t AsArray(const json_t& jsonObj) + { + if (jsonObj.is_array()) + { + return jsonObj; + } + + json_t retVal = json_t::array(); + + if (jsonObj.is_object()) + { + for (const auto& jItem : jsonObj) + { + retVal.push_back(jItem); + } + } + else if (!jsonObj.is_null()) + { + retVal.push_back(jsonObj); + } + + return retVal; } } // namespace Json diff --git a/src/openrct2/core/Json.hpp b/src/openrct2/core/Json.hpp index 1059590f7c..69899b12a5 100644 --- a/src/openrct2/core/Json.hpp +++ b/src/openrct2/core/Json.hpp @@ -11,36 +11,181 @@ #include "../common.h" -#include -#include +#include #include #include +using json_t = nlohmann::json; + namespace Json { // Don't try to load JSON files that exceed 64 MiB constexpr uint64_t MAX_JSON_SIZE = 64 * 1024 * 1024; - json_t* ReadFromFile(const utf8* path, size_t maxSize = MAX_JSON_SIZE); - void WriteToFile(const utf8* path, const json_t* json, size_t flags = 0); + /** + * Read JSON file and parse contents + * @param path Path to the source file + * @param maxSize Max file size in bytes allowed + * @return A JSON representation of the file + * @note This function will throw an exception if the JSON file cannot be parsed + */ + json_t ReadFromFile(const utf8* path, size_t maxSize = MAX_JSON_SIZE); - json_t* FromString(std::string_view raw); + /** + * Read JSON file and parse the contents + * @param path Path to the destination file + * @param jsonData A JSON object + * @param indentSize The number of spaces in an indent, or removes whitespace on -1 + */ + void WriteToFile(const utf8* path, const json_t& jsonData, int indentSize = 4); + + /** + * Parse JSON from a string + * @param raw JSON string + * @return A JSON representation of the string + * @note This function will throw an exception if the JSON string cannot be parsed + */ + json_t FromString(std::string_view raw); + + /** + * Parse JSON from a vector of characters + * @param vec Vector of characters containing JSON + * @return A JSON representation of the vector + * @note This function will throw an exception if the JSON vector cannot be parsed + */ + json_t FromVector(const std::vector& vec); + + /** + * Explicit type conversion between a JSON object and a compatible number value + * @param T Destination numeric type + * @param jsonObj JSON object holding the value + * @param defaultValue Default value to return if the JSON object is not a number type + * @return Copy of the JSON value converted to the given type + */ + template T GetNumber(const json_t& jsonObj, T defaultValue = 0) + { + static_assert(std::is_arithmetic::value, "GetNumber template parameter must be arithmetic"); + + return jsonObj.is_number() ? jsonObj.get() : defaultValue; + } + + /** + * Explicit type conversion between a JSON object and a compatible enum value + * @param T Destination enum + * @param jsonObj JSON object holding the value + * @param defaultValue Default value to return if the JSON object is not an enum type + * @return Copy of the JSON value converted to the given enum type + */ + template T GetEnum(const json_t& jsonObj, T defaultValue) + { + static_assert(std::is_enum::value, "GetEnum template parameter must be an enum"); + + return jsonObj.is_number_integer() ? jsonObj.get() : defaultValue; + } + + /** + * Explicit type conversion between a JSON object and an std::string + * @param jsonObj JSON object holding the value + * @param defaultValue Default value to return if the JSON object is not a string + * @return Copy of the JSON value converted to std::string + */ + std::string GetString(const json_t& jsonObj, const std::string& defaultValue = std::string()); + + /** + * Explicit type conversion between a JSON object and a boolean + * @param jsonObj JSON object holding the value + * @param defaultValue Default value to return if the JSON object is not a boolean + * @return Copy of the JSON value converted to bool + */ + bool GetBoolean(const json_t& jsonObj, bool defaultValue = false); + + /** + * Ensures a given JSON object is an object type + * @param jsonObj JSON object + * @return The JSON object if it is an object type, or an empty object otherwise + */ + json_t AsObject(const json_t& jsonObj); + + /** + * Ensures a given JSON object is an array type + * @param jsonObj JSON object + * @return The JSON object if it is an array type, or an array containing the JSON object otherwise + */ + json_t AsArray(const json_t& jsonObj); + + /** + * Helper function to convert a json object and an initializer list to binary flags + * @param T Type to return + * @param jsonObj JSON object containing boolean values + * @param list List of pairs of keys and bits to enable if that key in the object is true + * @return Value with relevant bits flipped + */ + template T GetFlags(const json_t& jsonObj, std::initializer_list> list) + { + static_assert(std::is_convertible::value, "GetFlags template parameter must be integral or a weak enum"); + + T flags{}; + for (const auto& item : list) + { + if (jsonObj.contains(item.first) && Json::GetBoolean(jsonObj[item.first])) + { + flags = static_cast(flags | item.second); + } + } + return flags; + } + + /** + * Used by the GetFlags function to allow for inverted values + */ + enum class FlagType : uint8_t + { + // Flag is turned on if the key is true + Normal, + // Flag is turned on if the key is false + Inverted + }; + + /** + * Helper function to convert a json object and an initializer list to binary flags + * @param T Type to return + * @param jsonObj JSON object containing boolean values + * @param list List of tuples of keys, bits to change and flag type + * @return Value with relevant bits flipped + * @note FLAG_NORMAL behaves like the other GetFlags function, but FLAG_INVERTED will turn the flag on when false + */ + template T GetFlags(const json_t& jsonObj, std::initializer_list> list) + { + static_assert(std::is_convertible::value, "GetFlags template parameter must be integral or a weak enum"); + + T flags{}; + for (const auto& item : list) + { + if (std::get<2>(item) == FlagType::Normal) + { + if (jsonObj.contains(std::get<0>(item)) && Json::GetBoolean(jsonObj[std::get<0>(item)])) + { + flags = static_cast(flags | std::get<1>(item)); + } + } + else + { + // if the json flag doesn't exist, assume it's false + if (!jsonObj.contains(std::get<0>(item)) || !Json::GetBoolean(jsonObj[std::get<0>(item)])) + { + flags = static_cast(flags | std::get<1>(item)); + } + } + } + return flags; + } } // namespace Json class JsonException final : public std::runtime_error { -private: - json_error_t _jsonError = {}; - public: explicit JsonException(const std::string& message) : std::runtime_error(message) { } - - explicit JsonException(const json_error_t* jsonError) - : JsonException(std::string(jsonError->text)) - { - _jsonError = *jsonError; - } }; diff --git a/src/openrct2/interface/Colour.cpp b/src/openrct2/interface/Colour.cpp index 9ca0eb1607..5c37dea329 100644 --- a/src/openrct2/interface/Colour.cpp +++ b/src/openrct2/interface/Colour.cpp @@ -57,6 +57,49 @@ void colours_init_maps() } } +namespace Colour +{ + colour_t FromString(const std::string_view& s, colour_t defaultValue) + { + static const std::unordered_map LookupTable{ + { "black", COLOUR_BLACK }, + { "grey", COLOUR_GREY }, + { "white", COLOUR_WHITE }, + { "dark_purple", COLOUR_DARK_PURPLE }, + { "light_purple", COLOUR_LIGHT_PURPLE }, + { "bright_purple", COLOUR_BRIGHT_PURPLE }, + { "dark_blue", COLOUR_DARK_BLUE }, + { "light_blue", COLOUR_LIGHT_BLUE }, + { "icy_blue", COLOUR_ICY_BLUE }, + { "teal", COLOUR_TEAL }, + { "aquamarine", COLOUR_AQUAMARINE }, + { "saturated_green", COLOUR_SATURATED_GREEN }, + { "dark_green", COLOUR_DARK_GREEN }, + { "moss_green", COLOUR_MOSS_GREEN }, + { "bright_green", COLOUR_BRIGHT_GREEN }, + { "olive_green", COLOUR_OLIVE_GREEN }, + { "dark_olive_green", COLOUR_DARK_OLIVE_GREEN }, + { "bright_yellow", COLOUR_BRIGHT_YELLOW }, + { "yellow", COLOUR_YELLOW }, + { "dark_yellow", COLOUR_DARK_YELLOW }, + { "light_orange", COLOUR_LIGHT_ORANGE }, + { "dark_orange", COLOUR_DARK_ORANGE }, + { "light_brown", COLOUR_LIGHT_BROWN }, + { "saturated_brown", COLOUR_SATURATED_BROWN }, + { "dark_brown", COLOUR_DARK_BROWN }, + { "salmon_pink", COLOUR_SALMON_PINK }, + { "bordeaux_red", COLOUR_BORDEAUX_RED }, + { "saturated_red", COLOUR_SATURATED_RED }, + { "bright_red", COLOUR_BRIGHT_RED }, + { "dark_pink", COLOUR_DARK_PINK }, + { "bright_pink", COLOUR_BRIGHT_PINK }, + { "light_pink", COLOUR_LIGHT_PINK }, + }; + auto result = LookupTable.find(s); + return (result != LookupTable.end()) ? result->second : defaultValue; + } +} // namespace Colour + #ifndef NO_TTF static uint8_t BlendColourMap[PALETTE_COUNT][PALETTE_COUNT] = { 0 }; diff --git a/src/openrct2/interface/Colour.h b/src/openrct2/interface/Colour.h index a26abb2ea7..7abdfd961b 100644 --- a/src/openrct2/interface/Colour.h +++ b/src/openrct2/interface/Colour.h @@ -12,6 +12,8 @@ #include "../common.h" +#include + /** * Colour IDs as used by the colour dropdown, NOT palette indices. */ @@ -207,6 +209,11 @@ extern rct_colour_map ColourMapA[COLOUR_COUNT]; void colours_init_maps(); +namespace Colour +{ + colour_t FromString(const std::string_view& s, colour_t defaultValue = COLOUR_BLACK); +} + #ifndef NO_TTF uint8_t blendColours(const uint8_t paletteIndex1, const uint8_t paletteIndex2); #endif diff --git a/src/openrct2/interface/Cursors.cpp b/src/openrct2/interface/Cursors.cpp new file mode 100644 index 0000000000..c245b75907 --- /dev/null +++ b/src/openrct2/interface/Cursors.cpp @@ -0,0 +1,52 @@ +/***************************************************************************** + * Copyright (c) 2014-2020 OpenRCT2 developers + * + * For a complete list of all authors, please refer to contributors.md + * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is licensed under the GNU General Public License version 3. + *****************************************************************************/ + +#include "Cursors.h" + +#include +#include + +namespace Cursor +{ + uint8_t FromString(const std::string& s, uint8_t defaultValue) + { + static const std::unordered_map LookupTable{ + { "CURSOR_BLANK", CURSOR_BLANK }, + { "CURSOR_UP_ARROW", CURSOR_UP_ARROW }, + { "CURSOR_UP_DOWN_ARROW", CURSOR_UP_DOWN_ARROW }, + { "CURSOR_HAND_POINT", CURSOR_HAND_POINT }, + { "CURSOR_ZZZ", CURSOR_ZZZ }, + { "CURSOR_DIAGONAL_ARROWS", CURSOR_DIAGONAL_ARROWS }, + { "CURSOR_PICKER", CURSOR_PICKER }, + { "CURSOR_TREE_DOWN", CURSOR_TREE_DOWN }, + { "CURSOR_FOUNTAIN_DOWN", CURSOR_FOUNTAIN_DOWN }, + { "CURSOR_STATUE_DOWN", CURSOR_STATUE_DOWN }, + { "CURSOR_BENCH_DOWN", CURSOR_BENCH_DOWN }, + { "CURSOR_CROSS_HAIR", CURSOR_CROSS_HAIR }, + { "CURSOR_BIN_DOWN", CURSOR_BIN_DOWN }, + { "CURSOR_LAMPPOST_DOWN", CURSOR_LAMPPOST_DOWN }, + { "CURSOR_FENCE_DOWN", CURSOR_FENCE_DOWN }, + { "CURSOR_FLOWER_DOWN", CURSOR_FLOWER_DOWN }, + { "CURSOR_PATH_DOWN", CURSOR_PATH_DOWN }, + { "CURSOR_DIG_DOWN", CURSOR_DIG_DOWN }, + { "CURSOR_WATER_DOWN", CURSOR_WATER_DOWN }, + { "CURSOR_HOUSE_DOWN", CURSOR_HOUSE_DOWN }, + { "CURSOR_VOLCANO_DOWN", CURSOR_VOLCANO_DOWN }, + { "CURSOR_WALK_DOWN", CURSOR_WALK_DOWN }, + { "CURSOR_PAINT_DOWN", CURSOR_PAINT_DOWN }, + { "CURSOR_ENTRANCE_DOWN", CURSOR_ENTRANCE_DOWN }, + { "CURSOR_HAND_OPEN", CURSOR_HAND_OPEN }, + { "CURSOR_HAND_CLOSED", CURSOR_HAND_CLOSED }, + { "CURSOR_ARROW", CURSOR_ARROW }, + }; + + auto result = LookupTable.find(s); + return (result != LookupTable.end()) ? result->second : defaultValue; + } +} // namespace Cursor diff --git a/src/openrct2/interface/Cursors.h b/src/openrct2/interface/Cursors.h index e2e761102e..c85e8fac23 100644 --- a/src/openrct2/interface/Cursors.h +++ b/src/openrct2/interface/Cursors.h @@ -44,6 +44,11 @@ enum CURSOR_ID CURSOR_COUNT, }; +namespace Cursor +{ + uint8_t FromString(const std::string& s, uint8_t defaultValue); +} + namespace OpenRCT2::Ui { constexpr size_t CURSOR_BIT_WIDTH = 32; diff --git a/src/openrct2/libopenrct2.vcxproj b/src/openrct2/libopenrct2.vcxproj index 4f4a15b34c..0ae5418cf2 100644 --- a/src/openrct2/libopenrct2.vcxproj +++ b/src/openrct2/libopenrct2.vcxproj @@ -251,7 +251,6 @@ - @@ -527,6 +526,7 @@ + @@ -575,7 +575,6 @@ - diff --git a/src/openrct2/network/NetworkBase.cpp b/src/openrct2/network/NetworkBase.cpp index 5798ff4a51..2d41c29de2 100644 --- a/src/openrct2/network/NetworkBase.cpp +++ b/src/openrct2/network/NetworkBase.cpp @@ -960,24 +960,23 @@ void NetworkBase::SaveGroups() platform_get_user_directory(path, nullptr, sizeof(path)); safe_strcat_path(path, "groups.json", sizeof(path)); - json_t* jsonGroupsCfg = json_object(); - json_t* jsonGroups = json_array(); + json_t jsonGroups = json_t::array(); for (auto& group : group_list) { - json_array_append_new(jsonGroups, group->ToJson()); + jsonGroups.push_back(group->ToJson()); } - json_object_set_new(jsonGroupsCfg, "default_group", json_integer(default_group)); - json_object_set_new(jsonGroupsCfg, "groups", jsonGroups); + json_t jsonGroupsCfg = { + { "default_group", default_group }, + { "groups", jsonGroups }, + }; try { - Json::WriteToFile(path, jsonGroupsCfg, JSON_INDENT(4) | JSON_PRESERVE_ORDER); + Json::WriteToFile(path, jsonGroupsCfg); } catch (const std::exception& ex) { log_error("Unable to save %s: %s", path, ex.what()); } - - json_decref(jsonGroupsCfg); } } @@ -1023,12 +1022,12 @@ void NetworkBase::LoadGroups() platform_get_user_directory(path, nullptr, sizeof(path)); safe_strcat_path(path, "groups.json", sizeof(path)); - json_t* json = nullptr; + json_t jsonGroupConfig; if (Platform::FileExists(path)) { try { - json = Json::ReadFromFile(path); + jsonGroupConfig = Json::ReadFromFile(path); } catch (const std::exception& e) { @@ -1036,28 +1035,26 @@ void NetworkBase::LoadGroups() } } - if (json == nullptr) + if (!jsonGroupConfig.is_object()) { SetupDefaultGroups(); } else { - json_t* json_groups = json_object_get(json, "groups"); - size_t groupCount = json_array_size(json_groups); - for (size_t i = 0; i < groupCount; i++) + json_t jsonGroups = jsonGroupConfig["groups"]; + if (jsonGroups.is_array()) { - json_t* jsonGroup = json_array_get(json_groups, i); - - auto newgroup = std::make_unique(NetworkGroup::FromJson(jsonGroup)); - group_list.push_back(std::move(newgroup)); + for (auto& jsonGroup : jsonGroups) + { + group_list.emplace_back(std::make_unique(NetworkGroup::FromJson(jsonGroup))); + } } - json_t* jsonDefaultGroup = json_object_get(json, "default_group"); - default_group = static_cast(json_integer_value(jsonDefaultGroup)); + + default_group = Json::GetNumber(jsonGroupConfig["default_group"]); if (GetGroupByID(default_group) == nullptr) { default_group = 0; } - json_decref(json); } // Host group should always contain all permissions. @@ -1594,37 +1591,35 @@ void NetworkBase::Server_Send_SETDISCONNECTMSG(NetworkConnection& connection, co connection.QueuePacket(std::move(packet)); } -json_t* NetworkBase::GetServerInfoAsJson() const +json_t NetworkBase::GetServerInfoAsJson() const { - json_t* obj = json_object(); - json_object_set_new(obj, "name", json_string(gConfigNetwork.server_name.c_str())); - json_object_set_new(obj, "requiresPassword", json_boolean(_password.size() > 0)); - json_object_set_new(obj, "version", json_string(network_get_version().c_str())); - json_object_set_new(obj, "players", json_integer(player_list.size())); - json_object_set_new(obj, "maxPlayers", json_integer(gConfigNetwork.maxplayers)); - json_object_set_new(obj, "description", json_string(gConfigNetwork.server_description.c_str())); - json_object_set_new(obj, "greeting", json_string(gConfigNetwork.server_greeting.c_str())); - json_object_set_new(obj, "dedicated", json_boolean(gOpenRCT2Headless)); - return obj; + json_t jsonObj = { + { "name", gConfigNetwork.server_name }, { "requiresPassword", _password.size() > 0 }, + { "version", network_get_version() }, { "players", player_list.size() }, + { "maxPlayers", gConfigNetwork.maxplayers }, { "description", gConfigNetwork.server_description }, + { "greeting", gConfigNetwork.server_greeting }, { "dedicated", gOpenRCT2Headless }, + }; + return jsonObj; } void NetworkBase::Server_Send_GAMEINFO(NetworkConnection& connection) { NetworkPacket packet(NetworkCommand::GameInfo); # ifndef DISABLE_HTTP - json_t* obj = GetServerInfoAsJson(); + json_t jsonObj = GetServerInfoAsJson(); // Provider details - json_t* jsonProvider = json_object(); - json_object_set_new(jsonProvider, "name", json_string(gConfigNetwork.provider_name.c_str())); - json_object_set_new(jsonProvider, "email", json_string(gConfigNetwork.provider_email.c_str())); - json_object_set_new(jsonProvider, "website", json_string(gConfigNetwork.provider_website.c_str())); - json_object_set_new(obj, "provider", jsonProvider); + json_t jsonProvider = { + { "name", gConfigNetwork.provider_name }, + { "email", gConfigNetwork.provider_email }, + { "website", gConfigNetwork.provider_website }, + }; - packet.WriteString(json_dumps(obj, 0)); + jsonObj["provider"] = jsonProvider; + + packet.WriteString(jsonObj.dump().c_str()); packet << _serverState.gamestateSnapshotsEnabled; - json_decref(obj); # endif connection.QueuePacket(std::move(packet)); } @@ -3178,32 +3173,27 @@ void NetworkBase::Client_Send_GAMEINFO() _serverConnection->QueuePacket(std::move(packet)); } -static std::string json_stdstring_value(const json_t* string) -{ - const char* cstr = json_string_value(string); - return cstr == nullptr ? std::string() : std::string(cstr); -} - void NetworkBase::Client_Handle_GAMEINFO([[maybe_unused]] NetworkConnection& connection, NetworkPacket& packet) { const char* jsonString = packet.ReadString(); packet >> _serverState.gamestateSnapshotsEnabled; - json_error_t error; - json_t* root = json_loads(jsonString, 0, &error); + json_t jsonData = Json::FromString(jsonString); - ServerName = json_stdstring_value(json_object_get(root, "name")); - ServerDescription = json_stdstring_value(json_object_get(root, "description")); - ServerGreeting = json_stdstring_value(json_object_get(root, "greeting")); - - json_t* jsonProvider = json_object_get(root, "provider"); - if (jsonProvider != nullptr) + if (jsonData.is_object()) { - ServerProviderName = json_stdstring_value(json_object_get(jsonProvider, "name")); - ServerProviderEmail = json_stdstring_value(json_object_get(jsonProvider, "email")); - ServerProviderWebsite = json_stdstring_value(json_object_get(jsonProvider, "website")); + ServerName = Json::GetString(jsonData["name"]); + ServerDescription = Json::GetString(jsonData["description"]); + ServerGreeting = Json::GetString(jsonData["greeting"]); + + json_t jsonProvider = jsonData["provider"]; + if (jsonProvider.is_object()) + { + ServerProviderName = Json::GetString(jsonProvider["name"]); + ServerProviderEmail = Json::GetString(jsonProvider["email"]); + ServerProviderWebsite = Json::GetString(jsonProvider["website"]); + } } - json_decref(root); network_chat_show_server_greeting(); } @@ -3977,7 +3967,7 @@ bool network_gamestate_snapshots_enabled() return network_get_server_state().gamestateSnapshotsEnabled; } -json_t* network_get_server_info_as_json() +json_t network_get_server_info_as_json() { return gNetwork.GetServerInfoAsJson(); } @@ -4242,8 +4232,8 @@ NetworkServerState_t network_get_server_state() { return NetworkServerState_t{}; } -json_t* network_get_server_info_as_json() +json_t network_get_server_info_as_json() { - return nullptr; + return {}; } #endif /* DISABLE_NETWORK */ diff --git a/src/openrct2/network/NetworkBase.h b/src/openrct2/network/NetworkBase.h index 09816cd485..113e9f0056 100644 --- a/src/openrct2/network/NetworkBase.h +++ b/src/openrct2/network/NetworkBase.h @@ -1,6 +1,7 @@ #pragma once #include "../actions/GameAction.h" +#include "../core/Json.hpp" #include "NetworkConnection.h" #include "NetworkGroup.h" #include "NetworkPlayer.h" @@ -42,7 +43,7 @@ public: // Common void AppendChatLog(const std::string& s); void CloseChatLog(); NetworkStats_t GetStats() const; - json_t* GetServerInfoAsJson() const; + json_t GetServerInfoAsJson() const; bool ProcessConnection(NetworkConnection& connection); void CloseConnection(); NetworkPlayer* AddPlayer(const std::string& name, const std::string& keyhash); diff --git a/src/openrct2/network/NetworkGroup.cpp b/src/openrct2/network/NetworkGroup.cpp index 2751ba381f..4f5b4e7419 100644 --- a/src/openrct2/network/NetworkGroup.cpp +++ b/src/openrct2/network/NetworkGroup.cpp @@ -14,31 +14,29 @@ # include "NetworkAction.h" # include "NetworkTypes.h" -NetworkGroup NetworkGroup::FromJson(const json_t* json) +NetworkGroup NetworkGroup::FromJson(json_t& jsonData) { - NetworkGroup group; - json_t* jsonId = json_object_get(json, "id"); - json_t* jsonName = json_object_get(json, "name"); - json_t* jsonPermissions = json_object_get(json, "permissions"); + Guard::Assert(jsonData.is_object(), "NetworkGroup::FromJson expects parameter jsonData to be object"); - if (jsonId == nullptr || jsonName == nullptr || jsonPermissions == nullptr) + NetworkGroup group; + json_t jsonId = jsonData["id"]; + json_t jsonName = jsonData["name"]; + json_t jsonPermissions = jsonData["permissions"]; + + if (jsonId.is_null() || jsonName.is_null() || jsonPermissions.is_null()) { throw std::runtime_error("Missing group data"); } - group.Id = static_cast(json_integer_value(jsonId)); - group._name = std::string(json_string_value(jsonName)); + group.Id = Json::GetNumber(jsonId); + group._name = Json::GetString(jsonName); std::fill(group.ActionsAllowed.begin(), group.ActionsAllowed.end(), 0); - for (size_t i = 0; i < json_array_size(jsonPermissions); i++) + for (const auto& jsonValue : jsonPermissions) { - json_t* jsonPermissionValue = json_array_get(jsonPermissions, i); - const char* perm_name = json_string_value(jsonPermissionValue); - if (perm_name == nullptr) - { - continue; - } - NetworkPermission action_id = NetworkActions::FindCommandByPermissionName(perm_name); + const std::string permission = Json::GetString(jsonValue); + + NetworkPermission action_id = NetworkActions::FindCommandByPermissionName(permission); if (action_id != NetworkPermission::Count) { group.ToggleActionPermission(action_id); @@ -47,21 +45,21 @@ NetworkGroup NetworkGroup::FromJson(const json_t* json) return group; } -json_t* NetworkGroup::ToJson() const +json_t NetworkGroup::ToJson() const { - json_t* jsonGroup = json_object(); - json_object_set_new(jsonGroup, "id", json_integer(Id)); - json_object_set_new(jsonGroup, "name", json_string(GetName().c_str())); - json_t* actionsArray = json_array(); + json_t jsonGroup = { + { "id", Id }, + { "name", GetName() }, + }; + json_t actionsArray = json_t::array(); for (size_t i = 0; i < NetworkActions::Actions.size(); i++) { if (CanPerformAction(static_cast(i))) { - const char* perm_name = NetworkActions::Actions[i].PermissionName.c_str(); - json_array_append_new(actionsArray, json_string(perm_name)); + actionsArray.push_back(NetworkActions::Actions[i].PermissionName); } } - json_object_set_new(jsonGroup, "permissions", actionsArray); + jsonGroup["permissions"] = actionsArray; return jsonGroup; } diff --git a/src/openrct2/network/NetworkGroup.h b/src/openrct2/network/NetworkGroup.h index 5a29d2db8f..bc84e7dc02 100644 --- a/src/openrct2/network/NetworkGroup.h +++ b/src/openrct2/network/NetworkGroup.h @@ -10,10 +10,10 @@ #pragma once #include "../common.h" +#include "../core/Json.hpp" #include "NetworkPacket.h" #include -#include #include enum class NetworkPermission : uint32_t; @@ -24,7 +24,14 @@ public: std::array ActionsAllowed{}; uint8_t Id = 0; - static NetworkGroup FromJson(const json_t* json); + /** + * Creates a NetworkGroup object from a JSON object + * + * @param json JSON data source + * @return A NetworkGroup object + * @note json is deliberately left non-const: json_t behaviour changes when const + */ + static NetworkGroup FromJson(json_t& json); const std::string& GetName() const; void SetName(std::string name); @@ -35,7 +42,12 @@ public: bool CanPerformAction(NetworkPermission index) const; bool CanPerformCommand(int32_t command) const; - json_t* ToJson() const; + /** + * Serialise a NetworkGroup object into a JSON object + * + * @return JSON representation of the NetworkGroup object + */ + json_t ToJson() const; private: std::string _name; diff --git a/src/openrct2/network/NetworkServerAdvertiser.cpp b/src/openrct2/network/NetworkServerAdvertiser.cpp index e62cb51ba1..a3d2761820 100644 --- a/src/openrct2/network/NetworkServerAdvertiser.cpp +++ b/src/openrct2/network/NetworkServerAdvertiser.cpp @@ -13,6 +13,7 @@ # include "../config/Config.h" # include "../core/Console.hpp" +# include "../core/Guard.hpp" # include "../core/Http.h" # include "../core/Json.hpp" # include "../core/String.hpp" @@ -119,12 +120,10 @@ private: if (String::Equals(buffer, NETWORK_LAN_BROADCAST_MSG)) { auto body = GetBroadcastJson(); - auto bodyDump = json_dumps(body, JSON_COMPACT); - size_t sendLen = strlen(bodyDump) + 1; + auto bodyDump = body.dump(); + size_t sendLen = bodyDump.size() + 1; log_verbose("Sending %zu bytes back to %s", sendLen, sender.c_str()); - _lanListener->SendData(*endpoint, bodyDump, sendLen); - free(bodyDump); - json_decref(body); + _lanListener->SendData(*endpoint, bodyDump.c_str(), sendLen); } } } @@ -132,10 +131,10 @@ private: } } - json_t* GetBroadcastJson() + json_t GetBroadcastJson() { - auto root = network_get_server_info_as_json(); - json_object_set(root, "port", json_integer(_port)); + json_t root = network_get_server_info_as_json(); + root["port"] = _port; return root; } @@ -172,20 +171,18 @@ private: request.method = Http::Method::POST; request.forceIPv4 = forceIPv4; - json_t* body = json_object(); - json_object_set_new(body, "key", json_string(_key.c_str())); - json_object_set_new(body, "port", json_integer(_port)); + json_t body = { + { "key", _key }, + { "port", _port }, + }; if (!gConfigNetwork.advertise_address.empty()) { - json_object_set_new(body, "address", json_string(gConfigNetwork.advertise_address.c_str())); + body["address"] = gConfigNetwork.advertise_address; } - char* bodyDump = json_dumps(body, JSON_COMPACT); - request.body = bodyDump; + request.body = body.dump(); request.header["Content-Type"] = "application/json"; - free(bodyDump); - json_decref(body); Http::DoAsync(request, [&](Http::Response response) -> void { if (response.status != Http::Status::OK) @@ -194,9 +191,9 @@ private: return; } - json_t* root = Json::FromString(response.body); + json_t root = Json::FromString(response.body); + root = Json::AsObject(root); this->OnRegistrationResponse(root); - json_decref(root); }); } @@ -206,12 +203,9 @@ private: request.url = GetMasterServerUrl(); request.method = Http::Method::PUT; - json_t* body = GetHeartbeatJson(); - char* bodyDump = json_dumps(body, JSON_COMPACT); - request.body = bodyDump; + json_t body = GetHeartbeatJson(); + request.body = body.dump(); request.header["Content-Type"] = "application/json"; - free(bodyDump); - json_decref(body); _lastHeartbeatTime = platform_get_ticks(); Http::DoAsync(request, [&](Http::Response response) -> void { @@ -221,86 +215,90 @@ private: return; } - json_t* root = Json::FromString(response.body); + json_t root = Json::FromString(response.body); + root = Json::AsObject(root); this->OnHeartbeatResponse(root); - json_decref(root); }); } - void OnRegistrationResponse(json_t* jsonRoot) + /** + * @param jsonRoot must be of JSON type object or null + * @note jsonRoot is deliberately left non-const: json_t behaviour changes when const + */ + void OnRegistrationResponse(json_t& jsonRoot) { - json_t* jsonStatus = json_object_get(jsonRoot, "status"); - if (json_is_integer(jsonStatus)) + Guard::Assert(jsonRoot.is_object(), "OnRegistrationResponse expects parameter jsonRoot to be object"); + + int32_t status = Json::GetNumber(jsonRoot["status"]); + + if (status == MASTER_SERVER_STATUS_OK) { - int32_t status = static_cast(json_integer_value(jsonStatus)); - if (status == MASTER_SERVER_STATUS_OK) + json_t jsonToken = jsonRoot["token"]; + if (jsonToken.is_string()) { - json_t* jsonToken = json_object_get(jsonRoot, "token"); - if (json_is_string(jsonToken)) - { - _token = std::string(json_string_value(jsonToken)); - _status = ADVERTISE_STATUS::REGISTERED; - } + _token = Json::GetString(jsonToken); + _status = ADVERTISE_STATUS::REGISTERED; } - else + } + else + { + std::string message = Json::GetString(jsonRoot["message"]); + if (message.empty()) { - const char* message = "Invalid response from server"; - json_t* jsonMessage = json_object_get(jsonRoot, "message"); - if (json_is_string(jsonMessage)) - { - message = json_string_value(jsonMessage); - } - Console::Error::WriteLine("Unable to advertise (%d): %s", status, message); - // Hack for https://github.com/OpenRCT2/OpenRCT2/issues/6277 - // Master server may not reply correctly if using IPv6, retry forcing IPv4, - // don't wait the full timeout. - if (!_forceIPv4 && status == 500) - { - _forceIPv4 = true; - _lastAdvertiseTime = 0; - log_info("Retry with ipv4 only"); - } + message = "Invalid response from server"; + } + Console::Error::WriteLine("Unable to advertise (%d): %s", status, message.c_str()); + // Hack for https://github.com/OpenRCT2/OpenRCT2/issues/6277 + // Master server may not reply correctly if using IPv6, retry forcing IPv4, + // don't wait the full timeout. + if (!_forceIPv4 && status == 500) + { + _forceIPv4 = true; + _lastAdvertiseTime = 0; + log_info("Retry with ipv4 only"); } } } - void OnHeartbeatResponse(json_t* jsonRoot) + /** + * @param jsonRoot must be of JSON type object or null + * @note jsonRoot is deliberately left non-const: json_t behaviour changes when const + */ + void OnHeartbeatResponse(json_t& jsonRoot) { - json_t* jsonStatus = json_object_get(jsonRoot, "status"); - if (json_is_integer(jsonStatus)) + Guard::Assert(jsonRoot.is_object(), "OnHeartbeatResponse expects parameter jsonRoot to be object"); + + int32_t status = Json::GetNumber(jsonRoot["status"]); + if (status == MASTER_SERVER_STATUS_OK) { - int32_t status = static_cast(json_integer_value(jsonStatus)); - if (status == MASTER_SERVER_STATUS_OK) - { - // Master server has successfully updated our server status - } - else if (status == MASTER_SERVER_STATUS_INVALID_TOKEN) - { - _status = ADVERTISE_STATUS::UNREGISTERED; - Console::WriteLine("Master server heartbeat failed: Invalid Token"); - } + // Master server has successfully updated our server status + } + else if (status == MASTER_SERVER_STATUS_INVALID_TOKEN) + { + _status = ADVERTISE_STATUS::UNREGISTERED; + Console::WriteLine("Master server heartbeat failed: Invalid Token"); } } - json_t* GetHeartbeatJson() + json_t GetHeartbeatJson() { uint32_t numPlayers = network_get_num_players(); - json_t* root = json_object(); - json_object_set_new(root, "token", json_string(_token.c_str())); - json_object_set_new(root, "players", json_integer(numPlayers)); + json_t root = { + { "token", _token }, + { "players", numPlayers }, + }; - json_t* gameInfo = json_object(); - json_object_set_new(gameInfo, "mapSize", json_integer(gMapSize - 2)); - json_object_set_new(gameInfo, "day", json_integer(gDateMonthTicks)); - json_object_set_new(gameInfo, "month", json_integer(gDateMonthsElapsed)); - json_object_set_new(gameInfo, "guests", json_integer(gNumGuestsInPark)); - json_object_set_new(gameInfo, "parkValue", json_integer(gParkValue)); + json_t gameInfo = { + { "mapSize", gMapSize - 2 }, { "day", gDateMonthTicks }, { "month", gDateMonthsElapsed }, + { "guests", gNumGuestsInPark }, { "parkValue", gParkValue }, + }; if (!(gParkFlags & PARK_FLAGS_NO_MONEY)) { - json_object_set_new(gameInfo, "cash", json_integer(gCash)); + gameInfo["cash"] = gCash; } - json_object_set_new(root, "gameInfo", gameInfo); + + root["gameInfo"] = gameInfo; return root; } diff --git a/src/openrct2/network/NetworkUser.cpp b/src/openrct2/network/NetworkUser.cpp index 45369b7e7c..a8c176d371 100644 --- a/src/openrct2/network/NetworkUser.cpp +++ b/src/openrct2/network/NetworkUser.cpp @@ -12,7 +12,7 @@ # include "NetworkUser.h" # include "../core/Console.hpp" -# include "../core/Json.hpp" +# include "../core/Guard.hpp" # include "../core/Path.hpp" # include "../core/String.hpp" # include "../platform/Platform2.h" @@ -21,50 +21,43 @@ constexpr const utf8* USER_STORE_FILENAME = "users.json"; -NetworkUser* NetworkUser::FromJson(json_t* json) +NetworkUser* NetworkUser::FromJson(json_t& jsonData) { - const char* hash = json_string_value(json_object_get(json, "hash")); - const char* name = json_string_value(json_object_get(json, "name")); - const json_t* jsonGroupId = json_object_get(json, "groupId"); + Guard::Assert(jsonData.is_object(), "NetworkUser::FromJson expects parameter jsonData to be object"); + + const std::string hash = Json::GetString(jsonData["hash"]); + const std::string name = Json::GetString(jsonData["name"]); + json_t jsonGroupId = jsonData["groupId"]; NetworkUser* user = nullptr; - if (hash != nullptr && name != nullptr) + if (!hash.empty() && !name.empty()) { user = new NetworkUser(); - user->Hash = std::string(hash); - user->Name = std::string(name); - if (!json_is_null(jsonGroupId)) + user->Hash = hash; + user->Name = name; + if (jsonGroupId.is_number_integer()) { - user->GroupId = static_cast(json_integer_value(jsonGroupId)); + user->GroupId = Json::GetNumber(jsonGroupId); } user->Remove = false; - return user; } return user; } -json_t* NetworkUser::ToJson() const +json_t NetworkUser::ToJson() const { - return ToJson(json_object()); -} + json_t jsonData; + jsonData["hash"] = Hash; + jsonData["name"] = Name; -json_t* NetworkUser::ToJson(json_t* json) const -{ - json_object_set_new(json, "hash", json_string(Hash.c_str())); - json_object_set_new(json, "name", json_string(Name.c_str())); - - json_t* jsonGroupId; + json_t jsonGroupId; if (GroupId.HasValue()) { - jsonGroupId = json_integer(GroupId.GetValue()); + jsonGroupId = GroupId.GetValue(); } - else - { - jsonGroupId = json_null(); - } - json_object_set_new(json, "groupId", jsonGroupId); + jsonData["groupId"] = jsonGroupId; - return json; + return jsonData; } NetworkUserManager::~NetworkUserManager() @@ -92,18 +85,18 @@ void NetworkUserManager::Load() try { - json_t* jsonUsers = Json::ReadFromFile(path); - size_t numUsers = json_array_size(jsonUsers); - for (size_t i = 0; i < numUsers; i++) + json_t jsonUsers = Json::ReadFromFile(path); + for (auto& jsonUser : jsonUsers) { - json_t* jsonUser = json_array_get(jsonUsers, i); - NetworkUser* networkUser = NetworkUser::FromJson(jsonUser); - if (networkUser != nullptr) + if (jsonUser.is_object()) { - _usersByHash[networkUser->Hash] = networkUser; + auto networkUser = NetworkUser::FromJson(jsonUser); + if (networkUser != nullptr) + { + _usersByHash[networkUser->Hash] = networkUser; + } } } - json_decref(jsonUsers); } catch (const std::exception& ex) { @@ -117,7 +110,7 @@ void NetworkUserManager::Save() utf8 path[MAX_PATH]; GetStorePath(path, sizeof(path)); - json_t* jsonUsers = nullptr; + json_t jsonUsers; try { if (Platform::FileExists(path)) @@ -129,36 +122,35 @@ void NetworkUserManager::Save() { } - if (jsonUsers == nullptr) - { - jsonUsers = json_array(); - } - // Update existing users std::unordered_set savedHashes; - size_t numUsers = json_array_size(jsonUsers); - for (size_t i = 0; i < numUsers; i++) + for (auto it = jsonUsers.begin(); it != jsonUsers.end();) { - json_t* jsonUser = json_array_get(jsonUsers, i); - const char* hash = json_string_value(json_object_get(jsonUser, "hash")); - if (hash != nullptr) + json_t jsonUser = *it; + if (!jsonUser.is_object()) { - auto hashString = std::string(hash); - const NetworkUser* networkUser = GetUserByHash(hashString); - if (networkUser != nullptr) + continue; + } + std::string hashString = Json::GetString(jsonUser["hash"]); + + const auto networkUser = GetUserByHash(hashString); + if (networkUser != nullptr) + { + if (networkUser->Remove) { - if (networkUser->Remove) - { - json_array_remove(jsonUsers, i); - i--; - } - else - { - networkUser->ToJson(jsonUser); - savedHashes.insert(hashString); - } + it = jsonUsers.erase(it); + // erase advances the iterator so make sure we don't do it again + continue; + } + else + { + // replace the existing element in jsonUsers + *it = networkUser->ToJson(); + savedHashes.insert(hashString); } } + + it++; } // Add new users @@ -167,13 +159,11 @@ void NetworkUserManager::Save() const NetworkUser* networkUser = kvp.second; if (!networkUser->Remove && savedHashes.find(networkUser->Hash) == savedHashes.end()) { - json_t* jsonUser = networkUser->ToJson(); - json_array_append_new(jsonUsers, jsonUser); + jsonUsers.push_back(networkUser->ToJson()); } } - Json::WriteToFile(path, jsonUsers, JSON_INDENT(4) | JSON_PRESERVE_ORDER); - json_decref(jsonUsers); + Json::WriteToFile(path, jsonUsers); } void NetworkUserManager::UnsetUsersOfGroup(uint8_t groupId) diff --git a/src/openrct2/network/NetworkUser.h b/src/openrct2/network/NetworkUser.h index 318b7a1992..cd4e331267 100644 --- a/src/openrct2/network/NetworkUser.h +++ b/src/openrct2/network/NetworkUser.h @@ -10,9 +10,9 @@ #pragma once #include "../common.h" +#include "../core/Json.hpp" #include "../core/Nullable.hpp" -#include #include #include @@ -24,10 +24,20 @@ public: Nullable GroupId; bool Remove; - static NetworkUser* FromJson(json_t* json); + /** + * Creates a NetworkUser object from a JSON object + * @param jsonData Must be a JSON node of type object + * @return Pointer to a new NetworkUser object + * @note jsonData is deliberately left non-const: json_t behaviour changes when const + */ + static NetworkUser* FromJson(json_t& jsonData); - json_t* ToJson() const; - json_t* ToJson(json_t* json) const; + /** + * Serialise a NetworkUser object into a JSON object + * + * @return JSON representation of the NetworkUser object + */ + json_t ToJson() const; }; class NetworkUserManager final diff --git a/src/openrct2/network/ServerList.cpp b/src/openrct2/network/ServerList.cpp index e0b73491c6..d70c76a200 100644 --- a/src/openrct2/network/ServerList.cpp +++ b/src/openrct2/network/ServerList.cpp @@ -15,6 +15,7 @@ # include "../PlatformEnvironment.h" # include "../config/Config.h" # include "../core/FileStream.hpp" +# include "../core/Guard.hpp" # include "../core/Http.h" # include "../core/Json.hpp" # include "../core/Memory.hpp" @@ -70,35 +71,42 @@ bool ServerListEntry::IsVersionValid() const return Version.empty() || Version == network_get_version(); } -std::optional ServerListEntry::FromJson(const json_t* server) +std::optional ServerListEntry::FromJson(json_t& server) { - auto port = json_object_get(server, "port"); - auto name = json_object_get(server, "name"); - auto description = json_object_get(server, "description"); - auto requiresPassword = json_object_get(server, "requiresPassword"); - auto version = json_object_get(server, "version"); - auto players = json_object_get(server, "players"); - auto maxPlayers = json_object_get(server, "maxPlayers"); - auto ip = json_object_get(server, "ip"); - auto ip4 = json_object_get(ip, "v4"); - auto addressIp = json_array_get(ip4, 0); + Guard::Assert(server.is_object(), "ServerListEntry::FromJson expects parameter server to be object"); - if (name == nullptr || version == nullptr) + const auto port = Json::GetNumber(server["port"]); + const auto name = Json::GetString(server["name"]); + const auto description = Json::GetString(server["description"]); + const auto requiresPassword = Json::GetBoolean(server["requiresPassword"]); + const auto version = Json::GetString(server["version"]); + const auto players = Json::GetNumber(server["players"]); + const auto maxPlayers = Json::GetNumber(server["maxPlayers"]); + std::string ip; + // if server["ip"] or server["ip"]["v4"] are values, this will throw an exception, so check first + if (server["ip"].is_object() && server["ip"]["v4"].is_array()) + { + ip = Json::GetString(server["ip"]["v4"][0]); + } + + if (name.empty() || version.empty()) { log_verbose("Cowardly refusing to add server without name or version specified."); + return std::nullopt; } else { ServerListEntry entry; - entry.Address = String::StdFormat( - "%s:%d", json_string_value(addressIp), static_cast(json_integer_value(port))); - entry.Name = (name == nullptr ? "" : json_string_value(name)); - entry.Description = (description == nullptr ? "" : json_string_value(description)); - entry.Version = json_string_value(version); - entry.RequiresPassword = json_is_true(requiresPassword); - entry.Players = static_cast(json_integer_value(players)); - entry.MaxPlayers = static_cast(json_integer_value(maxPlayers)); + + entry.Address = ip + ":" + std::to_string(port); + entry.Name = name; + entry.Description = description; + entry.Version = version; + entry.RequiresPassword = requiresPassword; + entry.Players = players; + entry.MaxPlayers = maxPlayers; + return entry; } } @@ -263,20 +271,17 @@ std::future> ServerList::FetchLocalServerListAsync( log_verbose("Received %zu bytes back from %s", recievedLen, sender.c_str()); auto jinfo = Json::FromString(std::string_view(buffer)); - auto ip4 = json_array(); - json_array_append_new(ip4, json_string(sender.c_str())); - auto ip = json_object(); - json_object_set_new(ip, "v4", ip4); - json_object_set_new(jinfo, "ip", ip); - - auto entry = ServerListEntry::FromJson(jinfo); - if (entry.has_value()) + if (jinfo.is_object()) { - (*entry).Local = true; - entries.push_back(*entry); - } + jinfo["ip"] = { { "v4", { sender } } }; - json_decref(jinfo); + auto entry = ServerListEntry::FromJson(jinfo); + if (entry.has_value()) + { + (*entry).Local = true; + entries.push_back(*entry); + } + } } } catch (const std::exception& e) @@ -341,7 +346,7 @@ std::future> ServerList::FetchOnlineServerListAsync request.method = Http::Method::GET; request.header["Accept"] = "application/json"; Http::DoAsync(request, [p](Http::Response& response) -> void { - json_t* root{}; + json_t root; try { if (response.status != Http::Status::OK) @@ -350,46 +355,46 @@ std::future> ServerList::FetchOnlineServerListAsync } root = Json::FromString(response.body); - auto jsonStatus = json_object_get(root, "status"); - if (!json_is_number(jsonStatus)) + if (root.is_object()) { - throw MasterServerException(STR_SERVER_LIST_INVALID_RESPONSE_JSON_NUMBER); - } - - auto status = static_cast(json_integer_value(jsonStatus)); - if (status != 200) - { - throw MasterServerException(STR_SERVER_LIST_MASTER_SERVER_FAILED); - } - - auto jServers = json_object_get(root, "servers"); - if (!json_is_array(jServers)) - { - throw MasterServerException(STR_SERVER_LIST_INVALID_RESPONSE_JSON_ARRAY); - } - - std::vector entries; - auto count = json_array_size(jServers); - for (size_t i = 0; i < count; i++) - { - auto jServer = json_array_get(jServers, i); - if (json_is_object(jServer)) + auto jsonStatus = root["status"]; + if (!jsonStatus.is_number_integer()) { - auto entry = ServerListEntry::FromJson(jServer); - if (entry.has_value()) + throw MasterServerException(STR_SERVER_LIST_INVALID_RESPONSE_JSON_NUMBER); + } + + auto status = Json::GetNumber(jsonStatus); + if (status != 200) + { + throw MasterServerException(STR_SERVER_LIST_MASTER_SERVER_FAILED); + } + + auto jServers = root["servers"]; + if (!jServers.is_array()) + { + throw MasterServerException(STR_SERVER_LIST_INVALID_RESPONSE_JSON_ARRAY); + } + + std::vector entries; + for (auto& jServer : jServers) + { + if (jServer.is_object()) { - entries.push_back(*entry); + auto entry = ServerListEntry::FromJson(jServer); + if (entry.has_value()) + { + entries.push_back(*entry); + } } } - } - p->set_value(entries); + p->set_value(entries); + } } catch (...) { p->set_exception(std::current_exception()); } - json_decref(root); }); return f; # endif diff --git a/src/openrct2/network/ServerList.h b/src/openrct2/network/ServerList.h index 8eccdf783f..d4c1109557 100644 --- a/src/openrct2/network/ServerList.h +++ b/src/openrct2/network/ServerList.h @@ -10,6 +10,7 @@ #pragma once #include "../common.h" +#include "../core/Json.hpp" #include #include @@ -17,7 +18,6 @@ #include #include -struct json_t; struct INetworkEndpoint; struct ServerListEntry @@ -35,7 +35,14 @@ struct ServerListEntry int32_t CompareTo(const ServerListEntry& other) const; bool IsVersionValid() const; - static std::optional FromJson(const json_t* root); + /** + * Creates a ServerListEntry object from a JSON object + * + * @param json JSON data source - must be object type + * @return A NetworkGroup object + * @note json is deliberately left non-const: json_t behaviour changes when const + */ + static std::optional FromJson(json_t& server); }; class ServerList diff --git a/src/openrct2/network/network.h b/src/openrct2/network/network.h index 9fea9d4b1c..fbfa0dc24f 100644 --- a/src/openrct2/network/network.h +++ b/src/openrct2/network/network.h @@ -15,6 +15,7 @@ #define MAX_SERVER_DESCRIPTION_LENGTH 256 #include "../common.h" +#include "../core/Json.hpp" #include "../localisation/StringIds.h" #include "NetworkTypes.h" @@ -22,7 +23,6 @@ #include #include -struct json_t; struct GameAction; struct Peep; struct CoordsXYZ; @@ -117,4 +117,4 @@ std::string network_get_version(); NetworkStats_t network_get_stats(); NetworkServerState_t network_get_server_state(); -json_t* network_get_server_info_as_json(); +json_t network_get_server_info_as_json(); diff --git a/src/openrct2/object/BannerObject.cpp b/src/openrct2/object/BannerObject.cpp index 70f0a6ae54..d756b2f568 100644 --- a/src/openrct2/object/BannerObject.cpp +++ b/src/openrct2/object/BannerObject.cpp @@ -14,7 +14,6 @@ #include "../localisation/Language.h" #include "../object/Object.h" #include "../object/ObjectRepository.h" -#include "ObjectJsonHelpers.h" #include "ObjectList.h" void BannerObject::ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) @@ -82,20 +81,23 @@ void BannerObject::DrawPreview(rct_drawpixelinfo* dpi, int32_t width, int32_t he gfx_draw_sprite(dpi, imageId + 1, screenCoords + ScreenCoordsXY{ -12, 8 }, 0); } -void BannerObject::ReadJson(IReadObjectContext* context, const json_t* root) +void BannerObject::ReadJson(IReadObjectContext* context, json_t& root) { - auto properties = json_object_get(root, "properties"); + Guard::Assert(root.is_object(), "BannerObject::ReadJson expects parameter root to be object"); + json_t properties = root["properties"]; - _legacyType.banner.scrolling_mode = json_integer_value(json_object_get(properties, "scrollingMode")); - _legacyType.banner.price = json_integer_value(json_object_get(properties, "price")); - _legacyType.banner.flags = ObjectJsonHelpers::GetFlags( - properties, - { - { "hasPrimaryColour", BANNER_ENTRY_FLAG_HAS_PRIMARY_COLOUR }, - }); + if (properties.is_object()) + { + _legacyType.banner.scrolling_mode = Json::GetNumber(properties["scrollingMode"]); + _legacyType.banner.price = Json::GetNumber(properties["price"]); + _legacyType.banner.flags = Json::GetFlags( + properties, + { + { "hasPrimaryColour", BANNER_ENTRY_FLAG_HAS_PRIMARY_COLOUR }, + }); - SetPrimarySceneryGroup(ObjectJsonHelpers::GetString(json_object_get(properties, "sceneryGroup"))); + SetPrimarySceneryGroup(Json::GetString(properties["sceneryGroup"])); + } - ObjectJsonHelpers::LoadStrings(root, GetStringTable()); - ObjectJsonHelpers::LoadImages(context, root, GetImageTable()); + PopulateTablesFromJson(context, root); } diff --git a/src/openrct2/object/BannerObject.h b/src/openrct2/object/BannerObject.h index ea8ad00418..1877ce6d25 100644 --- a/src/openrct2/object/BannerObject.h +++ b/src/openrct2/object/BannerObject.h @@ -30,7 +30,7 @@ public: } void ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) override; - void ReadJson(IReadObjectContext* context, const json_t* root) override; + void ReadJson(IReadObjectContext* context, json_t& root) override; void Load() override; void Unload() override; diff --git a/src/openrct2/object/EntranceObject.cpp b/src/openrct2/object/EntranceObject.cpp index be979fdcd5..974917bbb1 100644 --- a/src/openrct2/object/EntranceObject.cpp +++ b/src/openrct2/object/EntranceObject.cpp @@ -13,7 +13,6 @@ #include "../core/String.hpp" #include "../drawing/Drawing.h" #include "../localisation/Localisation.h" -#include "ObjectJsonHelpers.h" void EntranceObject::ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) { @@ -51,12 +50,17 @@ void EntranceObject::DrawPreview(rct_drawpixelinfo* dpi, int32_t width, int32_t gfx_draw_sprite(dpi, imageId + 2, screenCoords + ScreenCoordsXY{ 32, 44 }, 0); } -void EntranceObject::ReadJson(IReadObjectContext* context, const json_t* root) +void EntranceObject::ReadJson(IReadObjectContext* context, json_t& root) { - auto properties = json_object_get(root, "properties"); - _legacyType.scrolling_mode = json_integer_value(json_object_get(properties, "scrollingMode")); - _legacyType.text_height = json_integer_value(json_object_get(properties, "textHeight")); + Guard::Assert(root.is_object(), "EntranceObject::ReadJson expects parameter root to be object"); - ObjectJsonHelpers::LoadStrings(root, GetStringTable()); - ObjectJsonHelpers::LoadImages(context, root, GetImageTable()); + json_t properties = root["properties"]; + + if (properties.is_object()) + { + _legacyType.scrolling_mode = Json::GetNumber(properties["scrollingMode"]); + _legacyType.text_height = Json::GetNumber(properties["textHeight"]); + } + + PopulateTablesFromJson(context, root); } diff --git a/src/openrct2/object/EntranceObject.h b/src/openrct2/object/EntranceObject.h index 4a9c48dda1..45e4d8dc72 100644 --- a/src/openrct2/object/EntranceObject.h +++ b/src/openrct2/object/EntranceObject.h @@ -29,7 +29,7 @@ public: } void ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) override; - void ReadJson(IReadObjectContext* context, const json_t* root) override; + void ReadJson(IReadObjectContext* context, json_t& root) override; void Load() override; void Unload() override; diff --git a/src/openrct2/object/FootpathItemObject.cpp b/src/openrct2/object/FootpathItemObject.cpp index 8c9fb14e6d..e3352db258 100644 --- a/src/openrct2/object/FootpathItemObject.cpp +++ b/src/openrct2/object/FootpathItemObject.cpp @@ -15,7 +15,6 @@ #include "../localisation/Localisation.h" #include "../object/Object.h" #include "../object/ObjectRepository.h" -#include "ObjectJsonHelpers.h" #include "ObjectList.h" #include @@ -98,40 +97,36 @@ static uint8_t ParseDrawType(const std::string& s) return PATH_BIT_DRAW_TYPE_LIGHTS; } -void FootpathItemObject::ReadJson(IReadObjectContext* context, const json_t* root) +void FootpathItemObject::ReadJson(IReadObjectContext* context, json_t& root) { - auto properties = json_object_get(root, "properties"); - _legacyType.path_bit.draw_type = ParseDrawType(ObjectJsonHelpers::GetString(properties, "renderAs")); - _legacyType.path_bit.tool_id = ObjectJsonHelpers::ParseCursor( - ObjectJsonHelpers::GetString(properties, "cursor"), CURSOR_LAMPPOST_DOWN); - _legacyType.path_bit.price = json_integer_value(json_object_get(properties, "price")); + Guard::Assert(root.is_object(), "FootpathItemObject::ReadJson expects parameter root to be object"); - SetPrimarySceneryGroup(ObjectJsonHelpers::GetString(json_object_get(properties, "sceneryGroup"))); + json_t properties = root["properties"]; - // Flags - _legacyType.path_bit.flags = ObjectJsonHelpers::GetFlags( - properties, - { - { "isBin", PATH_BIT_FLAG_IS_BIN }, - { "isBench", PATH_BIT_FLAG_IS_BENCH }, - { "isBreakable", PATH_BIT_FLAG_BREAKABLE }, - { "isLamp", PATH_BIT_FLAG_LAMP }, - { "isJumpingFountainWater", PATH_BIT_FLAG_JUMPING_FOUNTAIN_WATER }, - { "isJumpingFountainSnow", PATH_BIT_FLAG_JUMPING_FOUNTAIN_SNOW }, - { "isTelevision", PATH_BIT_FLAG_IS_QUEUE_SCREEN }, - }); - - // HACK To avoid 'negated' properties in JSON, handle these separately until - // flags are inverted in this code base. - if (!ObjectJsonHelpers::GetBoolean(properties, "isAllowedOnQueue", false)) + if (properties.is_object()) { - _legacyType.path_bit.flags |= PATH_BIT_FLAG_DONT_ALLOW_ON_QUEUE; - } - if (!ObjectJsonHelpers::GetBoolean(properties, "isAllowedOnSlope", false)) - { - _legacyType.path_bit.flags |= PATH_BIT_FLAG_DONT_ALLOW_ON_SLOPE; + _legacyType.path_bit.draw_type = ParseDrawType(Json::GetString(properties["renderAs"])); + _legacyType.path_bit.tool_id = Cursor::FromString(Json::GetString(properties["cursor"]), CURSOR_LAMPPOST_DOWN); + _legacyType.path_bit.price = Json::GetNumber(properties["price"]); + + SetPrimarySceneryGroup(Json::GetString(properties["sceneryGroup"])); + + // clang-format off + _legacyType.path_bit.flags = Json::GetFlags( + properties, + { + { "isBin", PATH_BIT_FLAG_IS_BIN, Json::FlagType::Normal }, + { "isBench", PATH_BIT_FLAG_IS_BENCH, Json::FlagType::Normal }, + { "isBreakable", PATH_BIT_FLAG_BREAKABLE, Json::FlagType::Normal }, + { "isLamp", PATH_BIT_FLAG_LAMP, Json::FlagType::Normal }, + { "isJumpingFountainWater", PATH_BIT_FLAG_JUMPING_FOUNTAIN_WATER, Json::FlagType::Normal }, + { "isJumpingFountainSnow", PATH_BIT_FLAG_JUMPING_FOUNTAIN_SNOW, Json::FlagType::Normal }, + { "isAllowedOnQueue", PATH_BIT_FLAG_DONT_ALLOW_ON_QUEUE, Json::FlagType::Inverted }, + { "isAllowedOnSlope", PATH_BIT_FLAG_DONT_ALLOW_ON_SLOPE, Json::FlagType::Inverted }, + { "isTelevision", PATH_BIT_FLAG_IS_QUEUE_SCREEN, Json::FlagType::Normal }, + }); + // clang-format on } - ObjectJsonHelpers::LoadStrings(root, GetStringTable()); - ObjectJsonHelpers::LoadImages(context, root, GetImageTable()); + PopulateTablesFromJson(context, root); } diff --git a/src/openrct2/object/FootpathItemObject.h b/src/openrct2/object/FootpathItemObject.h index 4e6be3eb6c..4bbcc8741a 100644 --- a/src/openrct2/object/FootpathItemObject.h +++ b/src/openrct2/object/FootpathItemObject.h @@ -29,7 +29,7 @@ public: } void ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) override; - void ReadJson(IReadObjectContext* context, const json_t* root) override; + void ReadJson(IReadObjectContext* context, json_t& root) override; void Load() override; void Unload() override; diff --git a/src/openrct2/object/FootpathObject.cpp b/src/openrct2/object/FootpathObject.cpp index 4356a9ceb1..4e3b8bc0a6 100644 --- a/src/openrct2/object/FootpathObject.cpp +++ b/src/openrct2/object/FootpathObject.cpp @@ -10,10 +10,10 @@ #include "FootpathObject.h" #include "../core/IStream.hpp" +#include "../core/Json.hpp" #include "../drawing/Drawing.h" #include "../localisation/Language.h" #include "../world/Footpath.h" -#include "ObjectJsonHelpers.h" void FootpathObject::ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) { @@ -83,21 +83,25 @@ static RailingEntrySupportType ParseSupportType(const std::string& s) return RailingEntrySupportType::Box; } -void FootpathObject::ReadJson(IReadObjectContext* context, const json_t* root) +void FootpathObject::ReadJson(IReadObjectContext* context, json_t& root) { - auto properties = json_object_get(root, "properties"); - _legacyType.support_type = ParseSupportType(ObjectJsonHelpers::GetString(json_object_get(properties, "supportType"))); - _legacyType.scrolling_mode = json_integer_value(json_object_get(properties, "scrollingMode")); + Guard::Assert(root.is_object(), "FootpathObject::ReadJson expects parameter root to be object"); - // Flags - _legacyType.flags = ObjectJsonHelpers::GetFlags( - properties, - { - { "hasSupportImages", RAILING_ENTRY_FLAG_HAS_SUPPORT_BASE_SPRITE }, - { "hasElevatedPathImages", RAILING_ENTRY_FLAG_DRAW_PATH_OVER_SUPPORTS }, - { "editorOnly", FOOTPATH_ENTRY_FLAG_SHOW_ONLY_IN_SCENARIO_EDITOR }, - }); + auto properties = root["properties"]; - ObjectJsonHelpers::LoadStrings(root, GetStringTable()); - ObjectJsonHelpers::LoadImages(context, root, GetImageTable()); + if (properties.is_object()) + { + _legacyType.support_type = ParseSupportType(Json::GetString(properties["supportType"])); + _legacyType.scrolling_mode = Json::GetNumber(properties["scrollingMode"]); + + _legacyType.flags = Json::GetFlags( + properties, + { + { "hasSupportImages", RAILING_ENTRY_FLAG_HAS_SUPPORT_BASE_SPRITE }, + { "hasElevatedPathImages", RAILING_ENTRY_FLAG_DRAW_PATH_OVER_SUPPORTS }, + { "editorOnly", FOOTPATH_ENTRY_FLAG_SHOW_ONLY_IN_SCENARIO_EDITOR }, + }); + } + + PopulateTablesFromJson(context, root); } diff --git a/src/openrct2/object/FootpathObject.h b/src/openrct2/object/FootpathObject.h index 88766b2abe..7fe5274cdb 100644 --- a/src/openrct2/object/FootpathObject.h +++ b/src/openrct2/object/FootpathObject.h @@ -47,7 +47,7 @@ public: } void ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) override; - void ReadJson(IReadObjectContext* context, const json_t* root) override; + void ReadJson(IReadObjectContext* context, json_t& root) override; void Load() override; void Unload() override; diff --git a/src/openrct2/object/ImageTable.cpp b/src/openrct2/object/ImageTable.cpp index 0deabc25df..b755b97a71 100644 --- a/src/openrct2/object/ImageTable.cpp +++ b/src/openrct2/object/ImageTable.cpp @@ -9,14 +9,285 @@ #include "ImageTable.h" +#include "../Context.h" #include "../OpenRCT2.h" +#include "../PlatformEnvironment.h" +#include "../core/File.h" +#include "../core/FileScanner.h" #include "../core/IStream.hpp" +#include "../core/Path.hpp" +#include "../core/String.hpp" +#include "../drawing/ImageImporter.h" +#include "../sprites.h" #include "Object.h" +#include "ObjectFactory.h" #include #include #include +using namespace OpenRCT2; +using namespace OpenRCT2::Drawing; + +struct ImageTable::RequiredImage +{ + rct_g1_element g1{}; + std::unique_ptr next_zoom; + + bool HasData() const + { + return g1.offset != nullptr; + } + + RequiredImage() = default; + RequiredImage(const RequiredImage&) = delete; + + RequiredImage(const rct_g1_element& orig) + { + auto length = g1_calculate_data_size(&orig); + g1 = orig; + g1.offset = new uint8_t[length]; + std::memcpy(g1.offset, orig.offset, length); + g1.flags &= ~G1_FLAG_HAS_ZOOM_SPRITE; + } + + RequiredImage(uint32_t idx, std::function getter) + { + auto orig = getter(idx); + if (orig != nullptr) + { + auto length = g1_calculate_data_size(orig); + g1 = *orig; + g1.offset = new uint8_t[length]; + std::memcpy(g1.offset, orig->offset, length); + if ((g1.flags & G1_FLAG_HAS_ZOOM_SPRITE) && g1.zoomed_offset != 0) + { + // Fetch image for next zoom level + next_zoom = std::make_unique(static_cast(idx - g1.zoomed_offset), getter); + if (!next_zoom->HasData()) + { + next_zoom = nullptr; + g1.flags &= ~G1_FLAG_HAS_ZOOM_SPRITE; + } + } + } + } + + ~RequiredImage() + { + delete[] g1.offset; + } +}; + +std::vector> ImageTable::ParseImages(IReadObjectContext* context, std::string s) +{ + std::vector> result; + if (s.empty()) + { + result.push_back(std::make_unique()); + } + else if (String::StartsWith(s, "$CSG")) + { + if (is_csg_loaded()) + { + auto range = ParseRange(s.substr(4)); + if (!range.empty()) + { + for (auto i : range) + { + result.push_back(std::make_unique( + static_cast(SPR_CSG_BEGIN + i), + [](uint32_t idx) -> const rct_g1_element* { return gfx_get_g1_element(idx); })); + } + } + } + } + else if (String::StartsWith(s, "$G1")) + { + auto range = ParseRange(s.substr(3)); + if (!range.empty()) + { + for (auto i : range) + { + result.push_back(std::make_unique( + static_cast(i), [](uint32_t idx) -> const rct_g1_element* { return gfx_get_g1_element(idx); })); + } + } + } + else if (String::StartsWith(s, "$RCT2:OBJDATA/")) + { + auto name = s.substr(14); + auto rangeStart = name.find('['); + if (rangeStart != std::string::npos) + { + auto rangeString = name.substr(rangeStart); + auto range = ParseRange(name.substr(rangeStart)); + name = name.substr(0, rangeStart); + result = LoadObjectImages(context, name, range); + } + } + else + { + try + { + auto imageData = context->GetData(s); + auto image = Imaging::ReadFromBuffer(imageData, IMAGE_FORMAT::PNG_32); + + ImageImporter importer; + auto importResult = importer.Import(image, 0, 0, ImageImporter::IMPORT_FLAGS::RLE); + + result.push_back(std::make_unique(importResult.Element)); + } + catch (const std::exception& e) + { + auto msg = String::StdFormat("Unable to load image '%s': %s", s.c_str(), e.what()); + context->LogWarning(OBJECT_ERROR_BAD_IMAGE_TABLE, msg.c_str()); + result.push_back(std::make_unique()); + } + } + return result; +} + +std::vector> ImageTable::ParseImages(IReadObjectContext* context, json_t& el) +{ + Guard::Assert(el.is_object(), "ImageTable::ParseImages expects parameter el to be object"); + + auto path = Json::GetString(el["path"]); + auto x = Json::GetNumber(el["x"]); + auto y = Json::GetNumber(el["y"]); + auto raw = Json::GetString(el["format"]) == "raw"; + + std::vector> result; + try + { + auto flags = ImageImporter::IMPORT_FLAGS::NONE; + if (!raw) + { + flags = static_cast(flags | ImageImporter::IMPORT_FLAGS::RLE); + } + auto imageData = context->GetData(path); + auto image = Imaging::ReadFromBuffer(imageData, IMAGE_FORMAT::PNG_32); + + ImageImporter importer; + auto importResult = importer.Import(image, 0, 0, flags); + auto g1Element = importResult.Element; + g1Element.x_offset = x; + g1Element.y_offset = y; + result.push_back(std::make_unique(g1Element)); + } + catch (const std::exception& e) + { + auto msg = String::StdFormat("Unable to load image '%s': %s", path.c_str(), e.what()); + context->LogWarning(OBJECT_ERROR_BAD_IMAGE_TABLE, msg.c_str()); + result.push_back(std::make_unique()); + } + return result; +} + +std::vector> ImageTable::LoadObjectImages( + IReadObjectContext* context, const std::string& name, const std::vector& range) +{ + std::vector> result; + auto objectPath = FindLegacyObject(name); + auto obj = ObjectFactory::CreateObjectFromLegacyFile(context->GetObjectRepository(), objectPath.c_str()); + if (obj != nullptr) + { + auto& imgTable = static_cast(obj)->GetImageTable(); + auto numImages = static_cast(imgTable.GetCount()); + auto images = imgTable.GetImages(); + size_t placeHoldersAdded = 0; + for (auto i : range) + { + if (i >= 0 && i < numImages) + { + result.push_back(std::make_unique( + static_cast(i), [images](uint32_t idx) -> const rct_g1_element* { return &images[idx]; })); + } + else + { + result.push_back(std::make_unique()); + placeHoldersAdded++; + } + } + delete obj; + + // Log place holder information + if (placeHoldersAdded > 0) + { + std::string msg = "Adding " + std::to_string(placeHoldersAdded) + " placeholders"; + context->LogWarning(OBJECT_ERROR_INVALID_PROPERTY, msg.c_str()); + } + } + else + { + std::string msg = "Unable to open '" + objectPath + "'"; + context->LogWarning(OBJECT_ERROR_INVALID_PROPERTY, msg.c_str()); + for (size_t i = 0; i < range.size(); i++) + { + result.push_back(std::make_unique()); + } + } + return result; +} + +std::vector ImageTable::ParseRange(std::string s) +{ + // Currently only supports [###] or [###..###] + std::vector result = {}; + if (s.length() >= 3 && s[0] == '[' && s[s.length() - 1] == ']') + { + s = s.substr(1, s.length() - 2); + auto parts = String::Split(s, ".."); + if (parts.size() == 1) + { + result.push_back(std::stoi(parts[0])); + } + else + { + auto left = std::stoi(parts[0]); + auto right = std::stoi(parts[1]); + if (left <= right) + { + for (auto i = left; i <= right; i++) + { + result.push_back(i); + } + } + else + { + for (auto i = right; i >= left; i--) + { + result.push_back(i); + } + } + } + } + return result; +} + +std::string ImageTable::FindLegacyObject(const std::string& name) +{ + const auto env = GetContext()->GetPlatformEnvironment(); + auto objectsPath = env->GetDirectoryPath(DIRBASE::RCT2, DIRID::OBJECT); + auto objectPath = Path::Combine(objectsPath, name); + if (!File::Exists(objectPath)) + { + // Search recursively for any file with the target name (case insensitive) + auto filter = Path::Combine(objectsPath, "*.dat"); + auto scanner = std::unique_ptr(Path::ScanDirectory(filter, true)); + while (scanner->Next()) + { + auto currentName = Path::GetFileName(scanner->GetPathRelative()); + if (String::Equals(currentName, name, true)) + { + objectPath = scanner->GetPath(); + break; + } + } + } + return objectPath; +} + ImageTable::~ImageTable() { if (_data == nullptr) @@ -97,6 +368,70 @@ void ImageTable::Read(IReadObjectContext* context, OpenRCT2::IStream* stream) } } +void ImageTable::ReadJson(IReadObjectContext* context, json_t& root) +{ + Guard::Assert(root.is_object(), "ImageTable::ReadJson expects parameter root to be object"); + + if (context->ShouldLoadImages()) + { + // First gather all the required images from inspecting the JSON + std::vector> allImages; + auto jsonImages = root["images"]; + + for (auto& jsonImage : jsonImages) + { + if (jsonImage.is_string()) + { + auto strImage = jsonImage.get(); + auto images = ParseImages(context, strImage); + allImages.insert( + allImages.end(), std::make_move_iterator(images.begin()), std::make_move_iterator(images.end())); + } + else if (jsonImage.is_object()) + { + auto images = ParseImages(context, jsonImage); + allImages.insert( + allImages.end(), std::make_move_iterator(images.begin()), std::make_move_iterator(images.end())); + } + } + + // Now add all the images to the image table + auto imagesStartIndex = GetCount(); + for (const auto& img : allImages) + { + const auto& g1 = img->g1; + AddImage(&g1); + } + + // Add all the zoom images at the very end of the image table. + // This way it should not affect the offsets used within the object logic. + for (size_t j = 0; j < allImages.size(); j++) + { + const auto tableIndex = imagesStartIndex + j; + const auto* img = allImages[j].get(); + if (img->next_zoom != nullptr) + { + img = img->next_zoom.get(); + + // Set old image zoom offset to zoom image which we are about to add + auto g1a = const_cast(&GetImages()[tableIndex]); + g1a->zoomed_offset = static_cast(tableIndex) - static_cast(GetCount()); + + while (img != nullptr) + { + auto g1b = img->g1; + if (img->next_zoom != nullptr) + { + g1b.zoomed_offset = -1; + } + AddImage(&g1b); + img = img->next_zoom.get(); + } + } + } + } +} + void ImageTable::AddImage(const rct_g1_element* g1) { rct_g1_element newg1 = *g1; diff --git a/src/openrct2/object/ImageTable.h b/src/openrct2/object/ImageTable.h index a4f92efee7..7499a305ef 100644 --- a/src/openrct2/object/ImageTable.h +++ b/src/openrct2/object/ImageTable.h @@ -10,6 +10,7 @@ #pragma once #include "../common.h" +#include "../core/Json.hpp" #include "../drawing/Drawing.h" #include @@ -27,6 +28,20 @@ private: std::unique_ptr _data; std::vector _entries; + /** + * Container for a G1 image, additional information and RAII. Used by ReadJson + */ + struct RequiredImage; + static std::vector> ParseImages(IReadObjectContext* context, std::string s); + /** + * @note root is deliberately left non-const: json_t behaviour changes when const + */ + static std::vector> ParseImages(IReadObjectContext* context, json_t& el); + static std::vector> LoadObjectImages( + IReadObjectContext* context, const std::string& name, const std::vector& range); + static std::vector ParseRange(std::string s); + static std::string FindLegacyObject(const std::string& name); + public: ImageTable() = default; ImageTable(const ImageTable&) = delete; @@ -34,6 +49,10 @@ public: ~ImageTable(); void Read(IReadObjectContext* context, OpenRCT2::IStream* stream); + /** + * @note root is deliberately left non-const: json_t behaviour changes when const + */ + void ReadJson(IReadObjectContext* context, json_t& root); const rct_g1_element* GetImages() const { return _entries.data(); diff --git a/src/openrct2/object/LargeSceneryObject.cpp b/src/openrct2/object/LargeSceneryObject.cpp index 3358d79f7c..3ab667c39d 100644 --- a/src/openrct2/object/LargeSceneryObject.cpp +++ b/src/openrct2/object/LargeSceneryObject.cpp @@ -12,13 +12,13 @@ #include "LargeSceneryObject.h" #include "../core/IStream.hpp" +#include "../core/Json.hpp" #include "../core/Memory.hpp" #include "../drawing/Drawing.h" #include "../interface/Cursors.h" #include "../localisation/Language.h" #include "../world/Banner.h" #include "../world/Location.hpp" -#include "ObjectJsonHelpers.h" #include #include @@ -121,84 +121,83 @@ std::vector LargeSceneryObject::ReadTiles(OpenRCT2::IStr return tiles; } -void LargeSceneryObject::ReadJson(IReadObjectContext* context, const json_t* root) +void LargeSceneryObject::ReadJson(IReadObjectContext* context, json_t& root) { - auto properties = json_object_get(root, "properties"); + Guard::Assert(root.is_object(), "LargeSceneryObject::ReadJson expects parameter root to be object"); - _legacyType.large_scenery.tool_id = ObjectJsonHelpers::ParseCursor( - ObjectJsonHelpers::GetString(properties, "cursor"), CURSOR_STATUE_DOWN); - _legacyType.large_scenery.price = json_integer_value(json_object_get(properties, "price")); - _legacyType.large_scenery.removal_price = json_integer_value(json_object_get(properties, "removalPrice")); + auto properties = root["properties"]; - auto jScrollingMode = json_object_get(properties, "scrollingMode"); - _legacyType.large_scenery.scrolling_mode = jScrollingMode != nullptr ? json_integer_value(jScrollingMode) - : SCROLLING_MODE_NONE; + if (properties.is_object()) + { + _legacyType.large_scenery.tool_id = Cursor::FromString(Json::GetString(properties["cursor"]), CURSOR_STATUE_DOWN); - // Flags - _legacyType.large_scenery.flags = ObjectJsonHelpers::GetFlags( - properties, + _legacyType.large_scenery.price = Json::GetNumber(properties["price"]); + _legacyType.large_scenery.removal_price = Json::GetNumber(properties["removalPrice"]); + + _legacyType.large_scenery.scrolling_mode = Json::GetNumber(properties["scrollingMode"], SCROLLING_MODE_NONE); + + _legacyType.large_scenery.flags = Json::GetFlags( + properties, + { + { "hasPrimaryColour", LARGE_SCENERY_FLAG_HAS_PRIMARY_COLOUR }, + { "hasSecondaryColour", LARGE_SCENERY_FLAG_HAS_SECONDARY_COLOUR }, + { "isAnimated", LARGE_SCENERY_FLAG_ANIMATED }, + { "isPhotogenic", LARGE_SCENERY_FLAG_PHOTOGENIC }, + }); + + // Tiles + auto jTiles = properties["tiles"]; + if (jTiles.is_array()) { - { "hasPrimaryColour", LARGE_SCENERY_FLAG_HAS_PRIMARY_COLOUR }, - { "hasSecondaryColour", LARGE_SCENERY_FLAG_HAS_SECONDARY_COLOUR }, - { "isAnimated", LARGE_SCENERY_FLAG_ANIMATED }, - { "isPhotogenic", LARGE_SCENERY_FLAG_PHOTOGENIC }, - }); + _tiles = ReadJsonTiles(jTiles); + } - // Tiles - auto jTiles = json_object_get(properties, "tiles"); - if (jTiles != nullptr) - { - _tiles = ReadJsonTiles(jTiles); + // Read text + auto j3dFont = properties["3dFont"]; + if (j3dFont.is_object()) + { + _3dFont = ReadJson3dFont(j3dFont); + _legacyType.large_scenery.flags |= LARGE_SCENERY_FLAG_3D_TEXT; + } + + SetPrimarySceneryGroup(Json::GetString(properties["sceneryGroup"])); } - // Read text - auto j3dFont = json_object_get(properties, "3dFont"); - if (j3dFont != nullptr) - { - _3dFont = ReadJson3dFont(j3dFont); - _legacyType.large_scenery.flags |= LARGE_SCENERY_FLAG_3D_TEXT; - } - - SetPrimarySceneryGroup(ObjectJsonHelpers::GetString(json_object_get(properties, "sceneryGroup"))); - - ObjectJsonHelpers::LoadStrings(root, GetStringTable()); - ObjectJsonHelpers::LoadImages(context, root, GetImageTable()); + PopulateTablesFromJson(context, root); } -std::vector LargeSceneryObject::ReadJsonTiles(const json_t* jTiles) +std::vector LargeSceneryObject::ReadJsonTiles(json_t& jTiles) { std::vector tiles; - size_t index; - const json_t* jTile; - json_array_foreach(jTiles, index, jTile) + + for (auto& jTile : jTiles) { - rct_large_scenery_tile tile = {}; - tile.x_offset = json_integer_value(json_object_get(jTile, "x")); - tile.y_offset = json_integer_value(json_object_get(jTile, "y")); - tile.z_offset = json_integer_value(json_object_get(jTile, "z")); - tile.z_clearance = json_integer_value(json_object_get(jTile, "clearance")); - if (!ObjectJsonHelpers::GetBoolean(jTile, "hasSupports")) + if (jTile.is_object()) { - tile.flags |= LARGE_SCENERY_TILE_FLAG_NO_SUPPORTS; - } - if (ObjectJsonHelpers::GetBoolean(jTile, "allowSupportsAbove")) - { - tile.flags |= LARGE_SCENERY_TILE_FLAG_ALLOW_SUPPORTS_ABOVE; - } + rct_large_scenery_tile tile = {}; + tile.x_offset = Json::GetNumber(jTile["x"]); + tile.y_offset = Json::GetNumber(jTile["y"]); + tile.z_offset = Json::GetNumber(jTile["z"]); + tile.z_clearance = Json::GetNumber(jTile["clearance"]); - // All corners are by default occupied - auto jCorners = json_object_get(jTile, "corners"); - auto corners = 0xF; - if (jCorners != nullptr) - { - corners = json_integer_value(jCorners); + // clang-format off + tile.flags = Json::GetFlags( + jTile, + { + {"hasSupports", LARGE_SCENERY_TILE_FLAG_NO_SUPPORTS, Json::FlagType::Inverted}, + {"allowSupportsAbove", LARGE_SCENERY_TILE_FLAG_ALLOW_SUPPORTS_ABOVE, Json::FlagType::Normal} + }); + // clang-format on + + // All corners are by default occupied + uint16_t corners = Json::GetNumber(jTile["corners"], 0xF); + tile.flags |= (corners & 0xFF) << 12; + + auto walls = Json::GetNumber(jTile["walls"]); + tile.flags |= (walls & 0xFF) << 8; + + tiles.push_back(tile); } - tile.flags |= (corners & 0xFF) << 12; - - auto walls = json_integer_value(json_object_get(jTile, "walls")); - tile.flags |= (walls & 0xFF) << 8; - - tiles.push_back(tile); } // HACK Add end of tiles marker @@ -208,29 +207,32 @@ std::vector LargeSceneryObject::ReadJsonTiles(const json return tiles; } -std::unique_ptr LargeSceneryObject::ReadJson3dFont(const json_t* j3dFont) +std::unique_ptr LargeSceneryObject::ReadJson3dFont(json_t& j3dFont) { + Guard::Assert(j3dFont.is_object(), "LargeSceneryObject::ReadJson3dFont expects parameter j3dFont to be object"); + auto font = std::make_unique(); - auto jOffsets = json_object_get(j3dFont, "offsets"); - if (jOffsets != nullptr) + auto jOffsets = j3dFont["offsets"]; + if (jOffsets.is_array()) { auto offsets = ReadJsonOffsets(jOffsets); auto numOffsets = std::min(std::size(font->offset), offsets.size()); std::copy_n(offsets.data(), numOffsets, font->offset); } - font->max_width = json_integer_value(json_object_get(j3dFont, "maxWidth")); - font->num_images = json_integer_value(json_object_get(j3dFont, "numImages")); - font->flags = ObjectJsonHelpers::GetFlags( + font->max_width = Json::GetNumber(j3dFont["maxWidth"]); + font->num_images = Json::GetNumber(j3dFont["numImages"]); + + font->flags = Json::GetFlags( j3dFont, { { "isVertical", LARGE_SCENERY_TEXT_FLAG_VERTICAL }, { "isTwoLine", LARGE_SCENERY_TEXT_FLAG_TWO_LINE }, }); - auto jGlyphs = json_object_get(j3dFont, "glyphs"); - if (jGlyphs != nullptr) + auto jGlyphs = j3dFont["glyphs"]; + if (jGlyphs.is_array()) { auto glyphs = ReadJsonGlyphs(jGlyphs); auto numGlyphs = std::min(std::size(font->glyphs), glyphs.size()); @@ -240,33 +242,35 @@ std::unique_ptr LargeSceneryObject::ReadJson3dFont(const return font; } -std::vector LargeSceneryObject::ReadJsonOffsets(const json_t* jOffsets) +std::vector LargeSceneryObject::ReadJsonOffsets(json_t& jOffsets) { std::vector offsets; - size_t index; - const json_t* jOffset; - json_array_foreach(jOffsets, index, jOffset) + for (auto& jOffset : jOffsets) { - LocationXY16 offset = {}; - offset.x = json_integer_value(json_object_get(jOffset, "x")); - offset.y = json_integer_value(json_object_get(jOffset, "y")); - offsets.push_back(offset); + if (jOffset.is_object()) + { + LocationXY16 offset = {}; + offset.x = Json::GetNumber(jOffset["x"]); + offset.y = Json::GetNumber(jOffset["y"]); + offsets.push_back(offset); + } } return offsets; } -std::vector LargeSceneryObject::ReadJsonGlyphs(const json_t* jGlpyhs) +std::vector LargeSceneryObject::ReadJsonGlyphs(json_t& jGlyphs) { std::vector glyphs; - size_t index; - const json_t* jGlyph; - json_array_foreach(jGlpyhs, index, jGlyph) + for (auto& jGlyph : jGlyphs) { - rct_large_scenery_text_glyph glyph = {}; - glyph.image_offset = json_integer_value(json_object_get(jGlyph, "image")); - glyph.width = json_integer_value(json_object_get(jGlyph, "width")); - glyph.height = json_integer_value(json_object_get(jGlyph, "height")); - glyphs.push_back(glyph); + if (jGlyph.is_object()) + { + rct_large_scenery_text_glyph glyph = {}; + glyph.image_offset = Json::GetNumber(jGlyph["image"]); + glyph.width = Json::GetNumber(jGlyph["width"]); + glyph.height = Json::GetNumber(jGlyph["height"]); + glyphs.push_back(glyph); + } } return glyphs; } diff --git a/src/openrct2/object/LargeSceneryObject.h b/src/openrct2/object/LargeSceneryObject.h index 9b13c034a9..36ffd7b5c1 100644 --- a/src/openrct2/object/LargeSceneryObject.h +++ b/src/openrct2/object/LargeSceneryObject.h @@ -35,7 +35,7 @@ public: } void ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) override; - void ReadJson(IReadObjectContext* context, const json_t* root) override; + void ReadJson(IReadObjectContext* context, json_t& root) override; void Load() override; void Unload() override; @@ -43,8 +43,8 @@ public: private: static std::vector ReadTiles(OpenRCT2::IStream* stream); - static std::vector ReadJsonTiles(const json_t* jTiles); - static std::unique_ptr ReadJson3dFont(const json_t* j3dFont); - static std::vector ReadJsonOffsets(const json_t* jOffsets); - static std::vector ReadJsonGlyphs(const json_t* jGlpyhs); + static std::vector ReadJsonTiles(json_t& jTiles); + static std::unique_ptr ReadJson3dFont(json_t& j3dFont); + static std::vector ReadJsonOffsets(json_t& jOffsets); + static std::vector ReadJsonGlyphs(json_t& jGlyphs); }; diff --git a/src/openrct2/object/Object.cpp b/src/openrct2/object/Object.cpp index 7d57fc80e5..8aac7bbd42 100644 --- a/src/openrct2/object/Object.cpp +++ b/src/openrct2/object/Object.cpp @@ -37,6 +37,21 @@ void Object::ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) throw std::runtime_error("Not supported."); } +void Object::PopulateTablesFromJson(IReadObjectContext* context, json_t& root) +{ + _stringTable.ReadJson(root); + _imageTable.ReadJson(context, root); +} + +rct_object_entry Object::ParseObjectEntry(const std::string& s) +{ + rct_object_entry entry = {}; + std::fill_n(entry.name, sizeof(entry.name), ' '); + auto copyLen = std::min(8, s.size()); + std::copy_n(s.c_str(), copyLen, entry.name); + return entry; +} + std::string Object::GetOverrideString(uint8_t index) const { auto legacyIdentifier = GetLegacyIdentifier(); diff --git a/src/openrct2/object/Object.h b/src/openrct2/object/Object.h index f7db713dcb..767929f681 100644 --- a/src/openrct2/object/Object.h +++ b/src/openrct2/object/Object.h @@ -10,6 +10,7 @@ #pragma once #include "../common.h" +#include "../core/Json.hpp" #include "ImageTable.h" #include "StringTable.h" @@ -141,7 +142,6 @@ namespace OpenRCT2 } struct ObjectRepositoryItem; struct rct_drawpixelinfo; -struct json_t; struct IReadObjectContext { @@ -186,6 +186,16 @@ protected: return _imageTable; } + /** + * Populates the image and string tables from a JSON object + * @param context + * @param root JSON node of type object containing image and string info + * @note root is deliberately left non-const: json_t behaviour changes when const + */ + void PopulateTablesFromJson(IReadObjectContext* context, json_t& root); + + static rct_object_entry ParseObjectEntry(const std::string& s); + std::string GetOverrideString(uint8_t index) const; std::string GetString(ObjectStringID index) const; std::string GetString(int32_t language, ObjectStringID index) const; @@ -224,7 +234,10 @@ public: } virtual void* GetLegacyData(); - virtual void ReadJson(IReadObjectContext* /*context*/, const json_t* /*root*/) + /** + * @note root is deliberately left non-const: json_t behaviour changes when const + */ + virtual void ReadJson(IReadObjectContext* /*context*/, json_t& /*root*/) { } virtual void ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream); diff --git a/src/openrct2/object/ObjectFactory.cpp b/src/openrct2/object/ObjectFactory.cpp index 91cb63042b..82ba9fd88a 100644 --- a/src/openrct2/object/ObjectFactory.cpp +++ b/src/openrct2/object/ObjectFactory.cpp @@ -160,8 +160,12 @@ public: namespace ObjectFactory { + /** + * @param jRoot Must be JSON node of type object + * @note jRoot is deliberately left non-const: json_t behaviour changes when const + */ static Object* CreateObjectFromJson( - IObjectRepository& objectRepository, const json_t* jRoot, const IFileDataRetriever* fileRetriever); + IObjectRepository& objectRepository, json_t& jRoot, const IFileDataRetriever* fileRetriever); static uint8_t ParseSourceGame(const std::string& s) { @@ -363,16 +367,16 @@ namespace ObjectFactory throw std::runtime_error("Unable to open object.json."); } - json_error_t jsonLoadError; - auto jRoot = json_loadb(reinterpret_cast(jsonBytes.data()), jsonBytes.size(), 0, &jsonLoadError); - if (jRoot == nullptr) + json_t jRoot = Json::FromVector(jsonBytes); + + Object* obj = nullptr; + + if (jRoot.is_object()) { - throw JsonException(&jsonLoadError); + auto fileDataRetriever = ZipDataRetriever(*archive); + obj = CreateObjectFromJson(objectRepository, jRoot, &fileDataRetriever); } - auto fileDataRetriever = ZipDataRetriever(*archive); - Object* obj = CreateObjectFromJson(objectRepository, jRoot, &fileDataRetriever); - json_decref(jRoot); return obj; } catch (const std::exception& e) @@ -392,96 +396,78 @@ namespace ObjectFactory Object* result = nullptr; try { - auto jRoot = Json::ReadFromFile(path.c_str()); + json_t jRoot = Json::ReadFromFile(path.c_str()); auto fileDataRetriever = FileSystemDataRetriever(Path::GetDirectory(path)); result = CreateObjectFromJson(objectRepository, jRoot, &fileDataRetriever); - json_decref(jRoot); } catch (const std::runtime_error& err) { Console::Error::WriteLine("Unable to open or read '%s': %s", path.c_str(), err.what()); - - delete result; - result = nullptr; } + return result; } - Object* CreateObjectFromJson( - IObjectRepository& objectRepository, const json_t* jRoot, const IFileDataRetriever* fileRetriever) + Object* CreateObjectFromJson(IObjectRepository& objectRepository, json_t& jRoot, const IFileDataRetriever* fileRetriever) { + Guard::Assert(jRoot.is_object(), "ObjectFactory::CreateObjectFromJson expects parameter jRoot to be object"); + log_verbose("CreateObjectFromJson(...)"); Object* result = nullptr; - auto jObjectType = json_object_get(jRoot, "objectType"); - if (json_is_string(jObjectType)) + + auto objectType = ParseObjectType(Json::GetString(jRoot["objectType"])); + if (objectType != 0xFF) { - auto objectType = ParseObjectType(json_string_value(jObjectType)); - if (objectType != 0xFF) + auto id = Json::GetString(jRoot["id"]); + + rct_object_entry entry = {}; + auto originalId = Json::GetString(jRoot["originalId"]); + auto originalName = originalId; + if (originalId.length() == 8 + 1 + 8 + 1 + 8) { - auto id = json_string_value(json_object_get(jRoot, "id")); + entry.flags = std::stoul(originalId.substr(0, 8), nullptr, 16); + originalName = originalId.substr(9, 8); + entry.checksum = std::stoul(originalId.substr(18, 8), nullptr, 16); + } + auto minLength = std::min(8, originalName.length()); + std::memcpy(entry.name, originalName.c_str(), minLength); - rct_object_entry entry = {}; - auto originalId = String::ToStd(json_string_value(json_object_get(jRoot, "originalId"))); - auto originalName = originalId; - if (originalId.length() == 8 + 1 + 8 + 1 + 8) - { - entry.flags = std::stoul(originalId.substr(0, 8), nullptr, 16); - originalName = originalId.substr(9, 8); - entry.checksum = std::stoul(originalId.substr(18, 8), nullptr, 16); - } - auto minLength = std::min(8, originalName.length()); - std::memcpy(entry.name, originalName.c_str(), minLength); + result = CreateObject(entry); + result->SetIdentifier(id); + result->MarkAsJsonObject(); + auto readContext = ReadObjectContext(objectRepository, id, !gOpenRCT2NoGraphics, fileRetriever); + result->ReadJson(&readContext, jRoot); + if (readContext.WasError()) + { + throw std::runtime_error("Object has errors"); + } - result = CreateObject(entry); - result->SetIdentifier(id); - result->MarkAsJsonObject(); - auto readContext = ReadObjectContext(objectRepository, id, !gOpenRCT2NoGraphics, fileRetriever); - result->ReadJson(&readContext, jRoot); - if (readContext.WasError()) + auto jAuthors = jRoot["authors"]; + std::vector authorVector; + for (const auto& jAuthor : jAuthors) + { + if (jAuthor.is_string()) { - throw std::runtime_error("Object has errors"); + authorVector.emplace_back(Json::GetString(jAuthor)); } + } + result->SetAuthors(std::move(authorVector)); - auto authors = json_object_get(jRoot, "authors"); - if (json_is_array(authors)) + auto sourceGames = jRoot["sourceGame"]; + if (sourceGames.is_array() || sourceGames.is_string()) + { + std::vector sourceGameVector; + for (const auto& jSourceGame : sourceGames) { - std::vector authorVector; - for (size_t j = 0; j < json_array_size(authors); j++) - { - json_t* tryString = json_array_get(authors, j); - if (json_is_string(tryString)) - { - authorVector.emplace_back(json_string_value(tryString)); - } - } - result->SetAuthors(std::move(authorVector)); - } - else if (json_is_string(authors)) - { - result->SetAuthors({ json_string_value(authors) }); - } - - auto sourceGames = json_object_get(jRoot, "sourceGame"); - if (json_is_array(sourceGames)) - { - std::vector sourceGameVector; - for (size_t j = 0; j < json_array_size(sourceGames); j++) - { - sourceGameVector.push_back(ParseSourceGame(json_string_value(json_array_get(sourceGames, j)))); - } - result->SetSourceGames(sourceGameVector); - } - else if (json_is_string(sourceGames)) - { - auto sourceGame = json_string_value(sourceGames); - result->SetSourceGames({ ParseSourceGame(sourceGame) }); - } - else - { - log_error("Object %s has an incorrect sourceGame parameter.", id); - result->SetSourceGames({ OBJECT_SOURCE_CUSTOM }); + sourceGameVector.push_back(ParseSourceGame(Json::GetString(jSourceGame))); } + result->SetSourceGames(sourceGameVector); + } + else + { + log_error("Object %s has an incorrect sourceGame parameter.", id.c_str()); + result->SetSourceGames({ OBJECT_SOURCE_CUSTOM }); } } return result; diff --git a/src/openrct2/object/ObjectJsonHelpers.cpp b/src/openrct2/object/ObjectJsonHelpers.cpp deleted file mode 100644 index 57867cf17d..0000000000 --- a/src/openrct2/object/ObjectJsonHelpers.cpp +++ /dev/null @@ -1,563 +0,0 @@ -/***************************************************************************** - * Copyright (c) 2014-2020 OpenRCT2 developers - * - * For a complete list of all authors, please refer to contributors.md - * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 - * - * OpenRCT2 is licensed under the GNU General Public License version 3. - *****************************************************************************/ - -#pragma warning(disable : 4706) // assignment within conditional expression - -#include "ObjectJsonHelpers.h" - -#include "../Context.h" -#include "../PlatformEnvironment.h" -#include "../core/File.h" -#include "../core/FileScanner.h" -#include "../core/Memory.hpp" -#include "../core/Path.hpp" -#include "../core/String.hpp" -#include "../drawing/ImageImporter.h" -#include "../interface/Cursors.h" -#include "../localisation/Language.h" -#include "../sprites.h" -#include "Object.h" -#include "ObjectFactory.h" - -#include -#include -#include -#include - -using namespace OpenRCT2; -using namespace OpenRCT2::Drawing; - -namespace ObjectJsonHelpers -{ - /** - * Container for a G1 image, additional information and RAII. - */ - struct RequiredImage - { - rct_g1_element g1{}; - std::unique_ptr next_zoom; - - bool HasData() const - { - return g1.offset != nullptr; - } - - RequiredImage() = default; - RequiredImage(const RequiredImage&) = delete; - - RequiredImage(const rct_g1_element& orig) - { - auto length = g1_calculate_data_size(&orig); - g1 = orig; - g1.offset = new uint8_t[length]; - std::memcpy(g1.offset, orig.offset, length); - g1.flags &= ~G1_FLAG_HAS_ZOOM_SPRITE; - } - - RequiredImage(uint32_t idx, std::function getter) - { - auto orig = getter(idx); - if (orig != nullptr) - { - auto length = g1_calculate_data_size(orig); - g1 = *orig; - g1.offset = new uint8_t[length]; - std::memcpy(g1.offset, orig->offset, length); - if ((g1.flags & G1_FLAG_HAS_ZOOM_SPRITE) && g1.zoomed_offset != 0) - { - // Fetch image for next zoom level - next_zoom = std::make_unique(static_cast(idx - g1.zoomed_offset), getter); - if (!next_zoom->HasData()) - { - next_zoom = nullptr; - g1.flags &= ~G1_FLAG_HAS_ZOOM_SPRITE; - } - } - } - } - - ~RequiredImage() - { - delete[] g1.offset; - } - }; - - bool GetBoolean(const json_t* obj, const std::string& name, bool defaultValue) - { - auto value = json_object_get(obj, name.c_str()); - return json_is_boolean(value) ? json_boolean_value(value) : defaultValue; - } - - std::string GetString(const json_t* value) - { - return json_is_string(value) ? std::string(json_string_value(value)) : std::string(); - } - - std::string GetString(const json_t* obj, const std::string& name, const std::string& defaultValue) - { - auto value = json_object_get(obj, name.c_str()); - return json_is_string(value) ? json_string_value(value) : defaultValue; - } - - int32_t GetInteger(const json_t* obj, const std::string& name, const int32_t& defaultValue) - { - auto value = json_object_get(obj, name.c_str()); - if (json_is_integer(value)) - { - int64_t val = json_integer_value(value); - if (val >= std::numeric_limits::min() && val <= std::numeric_limits::max()) - { - return static_cast(val); - } - } - return defaultValue; - } - - float GetFloat(const json_t* obj, const std::string& name, const float& defaultValue) - { - auto value = json_object_get(obj, name.c_str()); - return json_is_number(value) ? json_number_value(value) : defaultValue; - } - - std::vector GetJsonStringArray(const json_t* arr) - { - std::vector result; - if (json_is_array(arr)) - { - auto count = json_array_size(arr); - result.reserve(count); - for (size_t i = 0; i < count; i++) - { - auto element = json_string_value(json_array_get(arr, i)); - result.push_back(element); - } - } - else if (json_is_string(arr)) - { - result.push_back(json_string_value(arr)); - } - return result; - } - - std::vector GetJsonIntegerArray(const json_t* arr) - { - std::vector result; - if (json_is_array(arr)) - { - auto count = json_array_size(arr); - result.reserve(count); - for (size_t i = 0; i < count; i++) - { - auto element = json_integer_value(json_array_get(arr, i)); - result.push_back(element); - } - } - else if (json_is_integer(arr)) - { - result.push_back(json_integer_value(arr)); - } - return result; - } - - colour_t ParseColour(const std::string_view& s, colour_t defaultValue) - { - static const std::unordered_map LookupTable{ - { "black", COLOUR_BLACK }, - { "grey", COLOUR_GREY }, - { "white", COLOUR_WHITE }, - { "dark_purple", COLOUR_DARK_PURPLE }, - { "light_purple", COLOUR_LIGHT_PURPLE }, - { "bright_purple", COLOUR_BRIGHT_PURPLE }, - { "dark_blue", COLOUR_DARK_BLUE }, - { "light_blue", COLOUR_LIGHT_BLUE }, - { "icy_blue", COLOUR_ICY_BLUE }, - { "teal", COLOUR_TEAL }, - { "aquamarine", COLOUR_AQUAMARINE }, - { "saturated_green", COLOUR_SATURATED_GREEN }, - { "dark_green", COLOUR_DARK_GREEN }, - { "moss_green", COLOUR_MOSS_GREEN }, - { "bright_green", COLOUR_BRIGHT_GREEN }, - { "olive_green", COLOUR_OLIVE_GREEN }, - { "dark_olive_green", COLOUR_DARK_OLIVE_GREEN }, - { "bright_yellow", COLOUR_BRIGHT_YELLOW }, - { "yellow", COLOUR_YELLOW }, - { "dark_yellow", COLOUR_DARK_YELLOW }, - { "light_orange", COLOUR_LIGHT_ORANGE }, - { "dark_orange", COLOUR_DARK_ORANGE }, - { "light_brown", COLOUR_LIGHT_BROWN }, - { "saturated_brown", COLOUR_SATURATED_BROWN }, - { "dark_brown", COLOUR_DARK_BROWN }, - { "salmon_pink", COLOUR_SALMON_PINK }, - { "bordeaux_red", COLOUR_BORDEAUX_RED }, - { "saturated_red", COLOUR_SATURATED_RED }, - { "bright_red", COLOUR_BRIGHT_RED }, - { "dark_pink", COLOUR_DARK_PINK }, - { "bright_pink", COLOUR_BRIGHT_PINK }, - { "light_pink", COLOUR_LIGHT_PINK }, - }; - auto result = LookupTable.find(s); - return (result != LookupTable.end()) ? result->second : defaultValue; - } - - uint8_t ParseCursor(const std::string& s, uint8_t defaultValue) - { - static const std::unordered_map LookupTable{ - { "CURSOR_BLANK", CURSOR_BLANK }, - { "CURSOR_UP_ARROW", CURSOR_UP_ARROW }, - { "CURSOR_UP_DOWN_ARROW", CURSOR_UP_DOWN_ARROW }, - { "CURSOR_HAND_POINT", CURSOR_HAND_POINT }, - { "CURSOR_ZZZ", CURSOR_ZZZ }, - { "CURSOR_DIAGONAL_ARROWS", CURSOR_DIAGONAL_ARROWS }, - { "CURSOR_PICKER", CURSOR_PICKER }, - { "CURSOR_TREE_DOWN", CURSOR_TREE_DOWN }, - { "CURSOR_FOUNTAIN_DOWN", CURSOR_FOUNTAIN_DOWN }, - { "CURSOR_STATUE_DOWN", CURSOR_STATUE_DOWN }, - { "CURSOR_BENCH_DOWN", CURSOR_BENCH_DOWN }, - { "CURSOR_CROSS_HAIR", CURSOR_CROSS_HAIR }, - { "CURSOR_BIN_DOWN", CURSOR_BIN_DOWN }, - { "CURSOR_LAMPPOST_DOWN", CURSOR_LAMPPOST_DOWN }, - { "CURSOR_FENCE_DOWN", CURSOR_FENCE_DOWN }, - { "CURSOR_FLOWER_DOWN", CURSOR_FLOWER_DOWN }, - { "CURSOR_PATH_DOWN", CURSOR_PATH_DOWN }, - { "CURSOR_DIG_DOWN", CURSOR_DIG_DOWN }, - { "CURSOR_WATER_DOWN", CURSOR_WATER_DOWN }, - { "CURSOR_HOUSE_DOWN", CURSOR_HOUSE_DOWN }, - { "CURSOR_VOLCANO_DOWN", CURSOR_VOLCANO_DOWN }, - { "CURSOR_WALK_DOWN", CURSOR_WALK_DOWN }, - { "CURSOR_PAINT_DOWN", CURSOR_PAINT_DOWN }, - { "CURSOR_ENTRANCE_DOWN", CURSOR_ENTRANCE_DOWN }, - { "CURSOR_HAND_OPEN", CURSOR_HAND_OPEN }, - { "CURSOR_HAND_CLOSED", CURSOR_HAND_CLOSED }, - { "CURSOR_ARROW", CURSOR_ARROW }, - }; - - auto result = LookupTable.find(s); - return (result != LookupTable.end()) ? result->second : defaultValue; - } - - rct_object_entry ParseObjectEntry(const std::string& s) - { - rct_object_entry entry = {}; - std::fill_n(entry.name, sizeof(entry.name), ' '); - auto copyLen = std::min(8, s.size()); - std::copy_n(s.c_str(), copyLen, entry.name); - return entry; - } - - static std::vector ParseRange(std::string s) - { - // Currently only supports [###] or [###..###] - std::vector result = {}; - if (s.length() >= 3 && s[0] == '[' && s[s.length() - 1] == ']') - { - s = s.substr(1, s.length() - 2); - auto parts = String::Split(s, ".."); - if (parts.size() == 1) - { - result.push_back(std::stoi(parts[0])); - } - else - { - auto left = std::stoi(parts[0]); - auto right = std::stoi(parts[1]); - if (left <= right) - { - for (auto i = left; i <= right; i++) - { - result.push_back(i); - } - } - else - { - for (auto i = right; i >= left; i--) - { - result.push_back(i); - } - } - } - } - return result; - } - - static std::string FindLegacyObject(const std::string& name) - { - const auto env = GetContext()->GetPlatformEnvironment(); - auto objectsPath = env->GetDirectoryPath(DIRBASE::RCT2, DIRID::OBJECT); - auto objectPath = Path::Combine(objectsPath, name); - if (!File::Exists(objectPath)) - { - // Search recursively for any file with the target name (case insensitive) - auto filter = Path::Combine(objectsPath, "*.dat"); - auto scanner = std::unique_ptr(Path::ScanDirectory(filter, true)); - while (scanner->Next()) - { - auto currentName = Path::GetFileName(scanner->GetPathRelative()); - if (String::Equals(currentName, name, true)) - { - objectPath = scanner->GetPath(); - break; - } - } - } - return objectPath; - } - - static std::vector> LoadObjectImages( - IReadObjectContext* context, const std::string& name, const std::vector& range) - { - std::vector> result; - auto objectPath = FindLegacyObject(name); - auto obj = ObjectFactory::CreateObjectFromLegacyFile(context->GetObjectRepository(), objectPath.c_str()); - if (obj != nullptr) - { - auto& imgTable = static_cast(obj)->GetImageTable(); - auto numImages = static_cast(imgTable.GetCount()); - auto images = imgTable.GetImages(); - size_t placeHoldersAdded = 0; - for (auto i : range) - { - if (i >= 0 && i < numImages) - { - result.push_back(std::make_unique( - static_cast(i), [images](uint32_t idx) -> const rct_g1_element* { return &images[idx]; })); - } - else - { - result.push_back(std::make_unique()); - placeHoldersAdded++; - } - } - delete obj; - - // Log place holder information - if (placeHoldersAdded > 0) - { - std::string msg = "Adding " + std::to_string(placeHoldersAdded) + " placeholders"; - context->LogWarning(OBJECT_ERROR_INVALID_PROPERTY, msg.c_str()); - } - } - else - { - std::string msg = "Unable to open '" + objectPath + "'"; - context->LogWarning(OBJECT_ERROR_INVALID_PROPERTY, msg.c_str()); - for (size_t i = 0; i < range.size(); i++) - { - result.push_back(std::make_unique()); - } - } - return result; - } - - static std::vector> ParseImages(IReadObjectContext* context, std::string s) - { - std::vector> result; - if (s.empty()) - { - result.push_back(std::make_unique()); - } - else if (String::StartsWith(s, "$CSG")) - { - if (is_csg_loaded()) - { - auto range = ParseRange(s.substr(4)); - if (!range.empty()) - { - for (auto i : range) - { - result.push_back(std::make_unique( - static_cast(SPR_CSG_BEGIN + i), - [](uint32_t idx) -> const rct_g1_element* { return gfx_get_g1_element(idx); })); - } - } - } - } - else if (String::StartsWith(s, "$G1")) - { - auto range = ParseRange(s.substr(3)); - if (!range.empty()) - { - for (auto i : range) - { - result.push_back( - std::make_unique(static_cast(i), [](uint32_t idx) -> const rct_g1_element* { - return gfx_get_g1_element(idx); - })); - } - } - } - else if (String::StartsWith(s, "$RCT2:OBJDATA/")) - { - auto name = s.substr(14); - auto rangeStart = name.find('['); - if (rangeStart != std::string::npos) - { - auto rangeString = name.substr(rangeStart); - auto range = ParseRange(name.substr(rangeStart)); - name = name.substr(0, rangeStart); - result = LoadObjectImages(context, name, range); - } - } - else - { - try - { - auto imageData = context->GetData(s); - auto image = Imaging::ReadFromBuffer(imageData, IMAGE_FORMAT::PNG_32); - - ImageImporter importer; - auto importResult = importer.Import(image, 0, 0, ImageImporter::IMPORT_FLAGS::RLE); - - result.push_back(std::make_unique(importResult.Element)); - } - catch (const std::exception& e) - { - auto msg = String::StdFormat("Unable to load image '%s': %s", s.c_str(), e.what()); - context->LogWarning(OBJECT_ERROR_BAD_IMAGE_TABLE, msg.c_str()); - result.push_back(std::make_unique()); - } - } - return result; - } - - static std::vector> ParseImages(IReadObjectContext* context, json_t* el) - { - auto path = GetString(el, "path"); - auto x = GetInteger(el, "x"); - auto y = GetInteger(el, "y"); - auto raw = (GetString(el, "format") == "raw"); - - std::vector> result; - try - { - auto flags = ImageImporter::IMPORT_FLAGS::NONE; - if (!raw) - { - flags = static_cast(flags | ImageImporter::IMPORT_FLAGS::RLE); - } - auto imageData = context->GetData(path); - auto image = Imaging::ReadFromBuffer(imageData, IMAGE_FORMAT::PNG_32); - - ImageImporter importer; - auto importResult = importer.Import(image, 0, 0, flags); - auto g1Element = importResult.Element; - g1Element.x_offset = x; - g1Element.y_offset = y; - result.push_back(std::make_unique(g1Element)); - } - catch (const std::exception& e) - { - auto msg = String::StdFormat("Unable to load image '%s': %s", path.c_str(), e.what()); - context->LogWarning(OBJECT_ERROR_BAD_IMAGE_TABLE, msg.c_str()); - result.push_back(std::make_unique()); - } - return result; - } - - static ObjectStringID ParseStringId(const std::string& s) - { - if (s == "name") - return ObjectStringID::NAME; - if (s == "description") - return ObjectStringID::DESCRIPTION; - if (s == "capacity") - return ObjectStringID::CAPACITY; - if (s == "vehicleName") - return ObjectStringID::VEHICLE_NAME; - return ObjectStringID::UNKNOWN; - } - - void LoadStrings(const json_t* root, StringTable& stringTable) - { - auto jsonStrings = json_object_get(root, "strings"); - const char* key; - json_t* jlanguages; - json_object_foreach(jsonStrings, key, jlanguages) - { - auto stringId = ParseStringId(key); - if (stringId != ObjectStringID::UNKNOWN) - { - const char* locale; - json_t* jstring; - json_object_foreach(jlanguages, locale, jstring) - { - auto langId = language_get_id_from_locale(locale); - if (langId != LANGUAGE_UNDEFINED) - { - auto string = json_string_value(jstring); - stringTable.SetString(stringId, langId, string); - } - } - } - } - stringTable.Sort(); - } - - void LoadImages(IReadObjectContext* context, const json_t* root, ImageTable& imageTable) - { - if (context->ShouldLoadImages()) - { - // First gather all the required images from inspecting the JSON - std::vector> allImages; - auto jsonImages = json_object_get(root, "images"); - size_t i; - json_t* el; - json_array_foreach(jsonImages, i, el) - { - if (json_is_string(el)) - { - auto s = json_string_value(el); - auto images = ParseImages(context, s); - allImages.insert( - allImages.end(), std::make_move_iterator(images.begin()), std::make_move_iterator(images.end())); - } - else if (json_is_object(el)) - { - auto images = ParseImages(context, el); - allImages.insert( - allImages.end(), std::make_move_iterator(images.begin()), std::make_move_iterator(images.end())); - } - } - - // Now add all the images to the image table - auto imagesStartIndex = imageTable.GetCount(); - for (const auto& img : allImages) - { - const auto& g1 = img->g1; - imageTable.AddImage(&g1); - } - - // Add all the zoom images at the very end of the image table. - // This way it should not affect the offsets used within the object logic. - for (size_t j = 0; j < allImages.size(); j++) - { - const auto tableIndex = imagesStartIndex + j; - const auto* img = allImages[j].get(); - if (img->next_zoom != nullptr) - { - img = img->next_zoom.get(); - - // Set old image zoom offset to zoom image which we are about to add - auto g1a = const_cast(&imageTable.GetImages()[tableIndex]); - g1a->zoomed_offset = static_cast(tableIndex) - static_cast(imageTable.GetCount()); - - while (img != nullptr) - { - auto g1b = img->g1; - if (img->next_zoom != nullptr) - { - g1b.zoomed_offset = -1; - } - imageTable.AddImage(&g1b); - img = img->next_zoom.get(); - } - } - } - } - } -} // namespace ObjectJsonHelpers diff --git a/src/openrct2/object/ObjectJsonHelpers.h b/src/openrct2/object/ObjectJsonHelpers.h deleted file mode 100644 index a8e2f5dbd3..0000000000 --- a/src/openrct2/object/ObjectJsonHelpers.h +++ /dev/null @@ -1,52 +0,0 @@ -/***************************************************************************** - * Copyright (c) 2014-2020 OpenRCT2 developers - * - * For a complete list of all authors, please refer to contributors.md - * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 - * - * OpenRCT2 is licensed under the GNU General Public License version 3. - *****************************************************************************/ - -#pragma once - -#include "../common.h" -#include "../core/Json.hpp" -#include "../drawing/Drawing.h" -#include "../interface/Colour.h" -#include "../object/Object.h" -#include "ImageTable.h" -#include "StringTable.h" - -#include -#include -#include -#include - -namespace ObjectJsonHelpers -{ - bool GetBoolean(const json_t* obj, const std::string& name, bool defaultValue = false); - std::string GetString(const json_t* value); - std::string GetString(const json_t* obj, const std::string& name, const std::string& defaultValue = ""); - int32_t GetInteger(const json_t* obj, const std::string& name, const int32_t& defaultValue = 0); - float GetFloat(const json_t* obj, const std::string& name, const float& defaultValue = 0); - std::vector GetJsonStringArray(const json_t* arr); - std::vector GetJsonIntegerArray(const json_t* arr); - colour_t ParseColour(const std::string_view& s, colour_t defaultValue = COLOUR_BLACK); - uint8_t ParseCursor(const std::string& s, uint8_t defaultValue); - rct_object_entry ParseObjectEntry(const std::string& s); - void LoadStrings(const json_t* root, StringTable& stringTable); - void LoadImages(IReadObjectContext* context, const json_t* root, ImageTable& imageTable); - - template static T GetFlags(const json_t* obj, std::initializer_list> list) - { - T flags{}; - for (const auto& item : list) - { - if (GetBoolean(obj, item.first)) - { - flags = static_cast(flags | item.second); - } - } - return flags; - } -} // namespace ObjectJsonHelpers diff --git a/src/openrct2/object/RideObject.cpp b/src/openrct2/object/RideObject.cpp index 3e2e7cac68..9955f0616e 100644 --- a/src/openrct2/object/RideObject.cpp +++ b/src/openrct2/object/RideObject.cpp @@ -23,7 +23,6 @@ #include "../ride/RideData.h" #include "../ride/ShopItem.h" #include "../ride/Track.h" -#include "ObjectJsonHelpers.h" #include "ObjectRepository.h" #include @@ -523,175 +522,161 @@ uint8_t RideObject::CalculateNumHorizontalFrames(const rct_ride_entry_vehicle* v return numHorizontalFrames; } -void RideObject::ReadJson(IReadObjectContext* context, const json_t* root) +void RideObject::ReadJson(IReadObjectContext* context, json_t& root) { - auto properties = json_object_get(root, "properties"); + Guard::Assert(root.is_object(), "RideObject::ReadJson expects parameter root to be object"); - auto rideTypes = ObjectJsonHelpers::GetJsonStringArray(json_object_get(properties, "type")); - for (size_t i = 0; i < MAX_RIDE_TYPES_PER_RIDE_ENTRY; i++) + json_t properties = root["properties"]; + + if (properties.is_object()) { - uint8_t rideType = RIDE_TYPE_NULL; - if (i < rideTypes.size()) + // This will convert a string to an array + json_t rideTypes = Json::AsArray(properties["type"]); + size_t numRideTypes = rideTypes.size(); + + for (size_t i = 0; i < MAX_RIDE_TYPES_PER_RIDE_ENTRY; i++) { - rideType = ParseRideType(rideTypes[i]); - if (rideType == RIDE_TYPE_NULL) + uint8_t rideType = RIDE_TYPE_NULL; + + if (i < numRideTypes) { - context->LogError(OBJECT_ERROR_INVALID_PROPERTY, "Unknown ride type"); + rideType = ParseRideType(Json::GetString(rideTypes[i])); + + if (rideType == RIDE_TYPE_NULL) + { + context->LogError(OBJECT_ERROR_INVALID_PROPERTY, "Unknown ride type"); + } + } + + _legacyType.ride_type[i] = rideType; + } + + _legacyType.max_height = Json::GetNumber(properties["maxHeight"]); + + // This needs to be set for both shops/facilities _and_ regular rides. + for (auto& item : _legacyType.shop_item) + { + item = SHOP_ITEM_NONE; + } + + auto carColours = Json::AsArray(properties["carColours"]); + _presetColours = ReadJsonCarColours(carColours); + + if (IsRideTypeShopOrFacility(_legacyType.ride_type[0])) + { + // Standard car info for a shop + auto& car = _legacyType.vehicles[0]; + car.spacing = 544; + car.sprite_flags = VEHICLE_SPRITE_FLAG_FLAT; + car.sprite_width = 1; + car.sprite_height_negative = 1; + car.sprite_height_positive = 1; + car.flags = VEHICLE_ENTRY_FLAG_SPINNING; + car.car_visual = VEHICLE_VISUAL_FLAT_RIDE_OR_CAR_RIDE; + car.friction_sound_id = SoundId::Null; + car.sound_range = 0xFF; + car.draw_order = 6; + + // Shop item + auto rideSells = Json::AsArray(properties["sells"]); + auto numShopItems = std::min(static_cast(NUM_SHOP_ITEMS_PER_RIDE), rideSells.size()); + for (size_t i = 0; i < numShopItems; i++) + { + auto shopItem = ParseShopItem(Json::GetString(rideSells[i])); + if (shopItem == SHOP_ITEM_NONE) + { + context->LogWarning(OBJECT_ERROR_INVALID_PROPERTY, "Unknown shop item"); + } + + _legacyType.shop_item[i] = shopItem; + } + } + else + { + ReadJsonVehicleInfo(context, properties); + + auto swingMode = Json::GetNumber(properties["swingMode"]); + if (swingMode == 1) + { + _legacyType.flags |= RIDE_ENTRY_FLAG_ALTERNATIVE_SWING_MODE_1; + } + else if (swingMode == 2) + { + _legacyType.flags |= RIDE_ENTRY_FLAG_ALTERNATIVE_SWING_MODE_1; + _legacyType.flags |= RIDE_ENTRY_FLAG_ALTERNATIVE_SWING_MODE_2; + } + + auto rotationMode = Json::GetNumber(properties["rotationMode"]); + if (rotationMode == 1) + { + _legacyType.flags |= RIDE_ENTRY_FLAG_ALTERNATIVE_ROTATION_MODE_1; + } + else if (rotationMode == 2) + { + _legacyType.flags |= RIDE_ENTRY_FLAG_ALTERNATIVE_ROTATION_MODE_2; + } + + auto ratingMultiplier = properties["ratingMultipler"]; + if (ratingMultiplier.is_object()) + { + _legacyType.excitement_multiplier = Json::GetNumber(ratingMultiplier["excitement"]); + _legacyType.intensity_multiplier = Json::GetNumber(ratingMultiplier["intensity"]); + _legacyType.nausea_multiplier = Json::GetNumber(ratingMultiplier["nausea"]); } } - _legacyType.ride_type[i] = rideType; - } - - _legacyType.max_height = ObjectJsonHelpers::GetInteger(properties, "maxHeight"); - - // This needs to be set for both shops/facilities _and_ regular rides. - for (auto& item : _legacyType.shop_item) - { - item = SHOP_ITEM_NONE; - } - - _presetColours = ReadJsonCarColours(json_object_get(properties, "carColours")); - - if (IsRideTypeShopOrFacility(_legacyType.ride_type[0])) - { - // Standard car info for a shop - auto& car = _legacyType.vehicles[0]; - car.spacing = 544; - car.sprite_flags = VEHICLE_SPRITE_FLAG_FLAT; - car.sprite_width = 1; - car.sprite_height_negative = 1; - car.sprite_height_positive = 1; - car.flags = VEHICLE_ENTRY_FLAG_SPINNING; - car.car_visual = VEHICLE_VISUAL_FLAT_RIDE_OR_CAR_RIDE; - car.friction_sound_id = SoundId::Null; - car.sound_range = 0xFF; - car.draw_order = 6; - - // Shop item - auto rideSells = ObjectJsonHelpers::GetJsonStringArray(json_object_get(json_object_get(root, "properties"), "sells")); - auto numShopItems = std::min(static_cast(NUM_SHOP_ITEMS_PER_RIDE), rideSells.size()); - for (size_t i = 0; i < numShopItems; i++) - { - auto shopItem = ParseShopItem(rideSells[i]); - if (shopItem == SHOP_ITEM_NONE) + _legacyType.BuildMenuPriority = Json::GetNumber(properties["buildMenuPriority"]); + _legacyType.flags |= Json::GetFlags( + properties, { - context->LogWarning(OBJECT_ERROR_INVALID_PROPERTY, "Unknown shop item"); - } - - _legacyType.shop_item[i] = ParseShopItem(rideSells[i]); - } + { "noInversions", RIDE_ENTRY_FLAG_NO_INVERSIONS }, + { "noBanking", RIDE_ENTRY_FLAG_NO_BANKED_TRACK }, + { "playDepartSound", RIDE_ENTRY_FLAG_PLAY_DEPART_SOUND }, + // Skipping "disallowWandering", no vehicle sets this flag. + { "playSplashSound", RIDE_ENTRY_FLAG_PLAY_SPLASH_SOUND }, + { "playSplashSoundSlide", RIDE_ENTRY_FLAG_PLAY_SPLASH_SOUND_SLIDE }, + { "hasShelter", RIDE_ENTRY_FLAG_COVERED_RIDE }, + { "limitAirTimeBonus", RIDE_ENTRY_FLAG_LIMIT_AIRTIME_BONUS }, + { "disableBreakdown", RIDE_ENTRY_FLAG_CANNOT_BREAK_DOWN }, + // Skipping noDoorsOverTrack, moved to ride groups. + { "noCollisionCrashes", RIDE_ENTRY_FLAG_DISABLE_COLLISION_CRASHES }, + { "disablePainting", RIDE_ENTRY_FLAG_DISABLE_COLOUR_TAB }, + }); } - else - { - ReadJsonVehicleInfo(context, properties); - - auto swingMode = ObjectJsonHelpers::GetInteger(properties, "swingMode"); - if (swingMode == 1) - { - _legacyType.flags |= RIDE_ENTRY_FLAG_ALTERNATIVE_SWING_MODE_1; - } - else if (swingMode == 2) - { - _legacyType.flags |= RIDE_ENTRY_FLAG_ALTERNATIVE_SWING_MODE_1; - _legacyType.flags |= RIDE_ENTRY_FLAG_ALTERNATIVE_SWING_MODE_2; - } - - auto rotationMode = ObjectJsonHelpers::GetInteger(properties, "rotationMode"); - if (rotationMode == 1) - { - _legacyType.flags |= RIDE_ENTRY_FLAG_ALTERNATIVE_ROTATION_MODE_1; - } - else if (rotationMode == 2) - { - _legacyType.flags |= RIDE_ENTRY_FLAG_ALTERNATIVE_ROTATION_MODE_2; - } - - auto ratingMultiplier = json_object_get(properties, "ratingMultipler"); - if (ratingMultiplier != nullptr) - { - _legacyType.excitement_multiplier = ObjectJsonHelpers::GetInteger(ratingMultiplier, "excitement"); - _legacyType.intensity_multiplier = ObjectJsonHelpers::GetInteger(ratingMultiplier, "intensity"); - _legacyType.nausea_multiplier = ObjectJsonHelpers::GetInteger(ratingMultiplier, "nausea"); - } - - auto availableTrackPieces = ObjectJsonHelpers::GetJsonStringArray(json_object_get(properties, "availableTrackPieces")); - } - - _legacyType.BuildMenuPriority = ObjectJsonHelpers::GetInteger(properties, "buildMenuPriority", 0); - _legacyType.flags |= ObjectJsonHelpers::GetFlags( - properties, - { - { "noInversions", RIDE_ENTRY_FLAG_NO_INVERSIONS }, - { "noBanking", RIDE_ENTRY_FLAG_NO_BANKED_TRACK }, - { "playDepartSound", RIDE_ENTRY_FLAG_PLAY_DEPART_SOUND }, - // Skipping "disallowWandering", no vehicle sets this flag. - { "playSplashSound", RIDE_ENTRY_FLAG_PLAY_SPLASH_SOUND }, - { "playSplashSoundSlide", RIDE_ENTRY_FLAG_PLAY_SPLASH_SOUND_SLIDE }, - { "hasShelter", RIDE_ENTRY_FLAG_COVERED_RIDE }, - { "limitAirTimeBonus", RIDE_ENTRY_FLAG_LIMIT_AIRTIME_BONUS }, - { "disableBreakdown", RIDE_ENTRY_FLAG_CANNOT_BREAK_DOWN }, - // Skipping noDoorsOverTrack, moved to ride groups. - { "noCollisionCrashes", RIDE_ENTRY_FLAG_DISABLE_COLLISION_CRASHES }, - { "disablePainting", RIDE_ENTRY_FLAG_DISABLE_COLOUR_TAB }, - }); RideObjectUpdateRideType(&_legacyType); - ObjectJsonHelpers::LoadStrings(root, GetStringTable()); - ObjectJsonHelpers::LoadImages(context, root, GetImageTable()); + PopulateTablesFromJson(context, root); } -void RideObject::ReadJsonVehicleInfo([[maybe_unused]] IReadObjectContext* context, const json_t* properties) +void RideObject::ReadJsonVehicleInfo([[maybe_unused]] IReadObjectContext* context, json_t& properties) { - _legacyType.min_cars_in_train = ObjectJsonHelpers::GetInteger(properties, "minCarsPerTrain", 1); - _legacyType.max_cars_in_train = ObjectJsonHelpers::GetInteger(properties, "maxCarsPerTrain", 1); - _legacyType.cars_per_flat_ride = ObjectJsonHelpers::GetInteger(properties, "carsPerFlatRide", 255); - _legacyType.zero_cars = json_integer_value(json_object_get(properties, "numEmptyCars")); + Guard::Assert(properties.is_object(), "RideObject::ReadJsonVehicleInfo expects parameter properties to be object"); + + _legacyType.min_cars_in_train = Json::GetNumber(properties["minCarsPerTrain"], 1); + _legacyType.max_cars_in_train = Json::GetNumber(properties["maxCarsPerTrain"], 1); + _legacyType.cars_per_flat_ride = Json::GetNumber(properties["carsPerFlatRide"], 255); + _legacyType.zero_cars = Json::GetNumber(properties["numEmptyCars"]); // Train formation from car indices - _legacyType.default_vehicle = json_integer_value(json_object_get(properties, "defaultCar")); - _legacyType.tab_vehicle = json_integer_value(json_object_get(properties, "tabCar")); - auto tabScale = ObjectJsonHelpers::GetFloat(properties, "tabScale"); - if (tabScale != 0 && ObjectJsonHelpers::GetFloat(properties, "tabScale") <= 0.5f) + _legacyType.default_vehicle = Json::GetNumber(properties["defaultCar"]); + _legacyType.tab_vehicle = Json::GetNumber(properties["tabCar"]); + + float tabScale = Json::GetNumber(properties["tabScale"]); + if (tabScale != 0 && tabScale <= 0.5f) { _legacyType.flags |= RIDE_ENTRY_FLAG_VEHICLE_TAB_SCALE_HALF; } + json_t headCars = Json::AsArray(properties["headCars"]); + json_t tailCars = Json::AsArray(properties["tailCars"]); + // 0xFF means N/A. - _legacyType.front_vehicle = 0xFF; - _legacyType.second_vehicle = 0xFF; - _legacyType.third_vehicle = 0xFF; - _legacyType.rear_vehicle = 0xFF; + _legacyType.front_vehicle = Json::GetNumber(headCars[0], 0xFF); + _legacyType.second_vehicle = Json::GetNumber(headCars[1], 0xFF); + _legacyType.third_vehicle = Json::GetNumber(headCars[2], 0xFF); + _legacyType.rear_vehicle = Json::GetNumber(tailCars[0], 0xFF); - auto headCars = ObjectJsonHelpers::GetJsonIntegerArray(json_object_get(properties, "headCars")); - if (headCars.size() >= 1) - { - _legacyType.front_vehicle = headCars[0]; - } - if (headCars.size() >= 2) - { - _legacyType.second_vehicle = headCars[1]; - } - if (headCars.size() >= 3) - { - _legacyType.third_vehicle = headCars[2]; - } - if (headCars.size() >= 4) - { - // More than 3 head cars not supported yet! - } - - auto tailCars = ObjectJsonHelpers::GetJsonIntegerArray(json_object_get(properties, "tailCars")); - if (tailCars.size() >= 1) - { - _legacyType.rear_vehicle = tailCars[0]; - } - if (tailCars.size() >= 2) - { - // More than 1 tail car not supported yet! - } - - auto cars = ReadJsonCars(json_object_get(properties, "cars")); + auto cars = ReadJsonCars(properties["cars"]); auto numCars = std::min(std::size(_legacyType.vehicles), cars.size()); for (size_t i = 0; i < numCars; i++) { @@ -699,129 +684,131 @@ void RideObject::ReadJsonVehicleInfo([[maybe_unused]] IReadObjectContext* contex } } -std::vector RideObject::ReadJsonCars(const json_t* jCars) +std::vector RideObject::ReadJsonCars(json_t& jCars) { std::vector cars; - if (json_is_array(jCars)) + if (jCars.is_array()) { - json_t* jCar; - size_t index; - json_array_foreach(jCars, index, jCar) + for (auto& jCar : jCars) { - auto car = ReadJsonCar(jCar); - cars.push_back(car); + if (jCar.is_object()) + { + auto car = ReadJsonCar(jCar); + cars.push_back(car); + } } } - else if (json_is_object(jCars)) + else if (jCars.is_object()) { auto car = ReadJsonCar(jCars); cars.push_back(car); } + return cars; } -rct_ride_entry_vehicle RideObject::ReadJsonCar(const json_t* jCar) +rct_ride_entry_vehicle RideObject::ReadJsonCar(json_t& jCar) { + Guard::Assert(jCar.is_object(), "RideObject::ReadJsonCar expects parameter jCar to be object"); + rct_ride_entry_vehicle car = {}; - car.rotation_frame_mask = ObjectJsonHelpers::GetInteger(jCar, "rotationFrameMask"); - car.spacing = ObjectJsonHelpers::GetInteger(jCar, "spacing"); - car.car_mass = ObjectJsonHelpers::GetInteger(jCar, "mass"); - car.tab_height = ObjectJsonHelpers::GetInteger(jCar, "tabOffset"); - car.num_seats = ObjectJsonHelpers::GetInteger(jCar, "numSeats"); - if (ObjectJsonHelpers::GetBoolean(jCar, "seatsInPairs", true) && car.num_seats > 1) + car.rotation_frame_mask = Json::GetNumber(jCar["rotationFrameMask"]); + car.spacing = Json::GetNumber(jCar["spacing"]); + car.car_mass = Json::GetNumber(jCar["mass"]); + car.tab_height = Json::GetNumber(jCar["tabOffset"]); + car.num_seats = Json::GetNumber(jCar["numSeats"]); + if (Json::GetBoolean(jCar["seatsInPairs"], true) && car.num_seats > 1) { car.num_seats |= VEHICLE_SEAT_PAIR_FLAG; } - car.sprite_width = ObjectJsonHelpers::GetInteger(jCar, "spriteWidth"); - car.sprite_height_negative = ObjectJsonHelpers::GetInteger(jCar, "spriteHeightNegative"); - car.sprite_height_positive = ObjectJsonHelpers::GetInteger(jCar, "spriteHeightPositive"); - car.animation = ObjectJsonHelpers::GetInteger(jCar, "animation"); - car.base_num_frames = ObjectJsonHelpers::GetInteger(jCar, "baseNumFrames"); - car.no_vehicle_images = ObjectJsonHelpers::GetInteger(jCar, "numImages"); - car.no_seating_rows = ObjectJsonHelpers::GetInteger(jCar, "numSeatRows"); - car.spinning_inertia = ObjectJsonHelpers::GetInteger(jCar, "spinningInertia"); - car.spinning_friction = ObjectJsonHelpers::GetInteger(jCar, "spinningFriction"); - car.friction_sound_id = static_cast(ObjectJsonHelpers::GetInteger(jCar, "frictionSoundId", 255)); - car.log_flume_reverser_vehicle_type = ObjectJsonHelpers::GetInteger(jCar, "logFlumeReverserVehicleType"); - car.sound_range = ObjectJsonHelpers::GetInteger(jCar, "soundRange", 255); - car.double_sound_frequency = ObjectJsonHelpers::GetInteger(jCar, "doubleSoundFrequency"); - car.powered_acceleration = ObjectJsonHelpers::GetInteger(jCar, "poweredAcceleration"); - car.powered_max_speed = ObjectJsonHelpers::GetInteger(jCar, "poweredMaxSpeed"); - car.car_visual = ObjectJsonHelpers::GetInteger(jCar, "carVisual"); - car.effect_visual = ObjectJsonHelpers::GetInteger(jCar, "effectVisual", 1); - car.draw_order = ObjectJsonHelpers::GetInteger(jCar, "drawOrder"); - car.num_vertical_frames_override = ObjectJsonHelpers::GetInteger(jCar, "numVerticalFramesOverride"); + car.sprite_width = Json::GetNumber(jCar["spriteWidth"]); + car.sprite_height_negative = Json::GetNumber(jCar["spriteHeightNegative"]); + car.sprite_height_positive = Json::GetNumber(jCar["spriteHeightPositive"]); + car.animation = Json::GetNumber(jCar["animation"]); + car.base_num_frames = Json::GetNumber(jCar["baseNumFrames"]); + car.no_vehicle_images = Json::GetNumber(jCar["numImages"]); + car.no_seating_rows = Json::GetNumber(jCar["numSeatRows"]); + car.spinning_inertia = Json::GetNumber(jCar["spinningInertia"]); + car.spinning_friction = Json::GetNumber(jCar["spinningFriction"]); + car.friction_sound_id = Json::GetEnum(jCar["frictionSoundId"], SoundId::Null); + car.log_flume_reverser_vehicle_type = Json::GetNumber(jCar["logFlumeReverserVehicleType"]); + car.sound_range = Json::GetNumber(jCar["soundRange"], 255); + car.double_sound_frequency = Json::GetNumber(jCar["doubleSoundFrequency"]); + car.powered_acceleration = Json::GetNumber(jCar["poweredAcceleration"]); + car.powered_max_speed = Json::GetNumber(jCar["poweredMaxSpeed"]); + car.car_visual = Json::GetNumber(jCar["carVisual"]); + car.effect_visual = Json::GetNumber(jCar["effectVisual"], 1); + car.draw_order = Json::GetNumber(jCar["drawOrder"]); + car.num_vertical_frames_override = Json::GetNumber(jCar["numVerticalFramesOverride"]); - auto& peepLoadingPositions = car.peep_loading_positions; - auto jLoadingPositions = json_object_get(jCar, "loadingPositions"); - if (json_is_array(jLoadingPositions)) + auto jLoadingPositions = jCar["loadingPositions"]; + if (jLoadingPositions.is_array()) { - auto arr = ObjectJsonHelpers::GetJsonIntegerArray(jLoadingPositions); - for (auto x : arr) + for (auto& jPos : jLoadingPositions) { - peepLoadingPositions.push_back(x); + car.peep_loading_positions.push_back(Json::GetNumber(jPos)); } } else { - auto& peepLoadingWaypoints = car.peep_loading_waypoints; - auto jLoadingWaypoints = json_object_get(jCar, "loadingWaypoints"); - if (json_is_array(jLoadingWaypoints)) + auto jLoadingWaypoints = jCar["loadingWaypoints"]; + if (jLoadingWaypoints.is_array()) { car.flags |= VEHICLE_ENTRY_FLAG_LOADING_WAYPOINTS; + car.peep_loading_waypoint_segments = Json::GetNumber(jCar["numSegments"]); - auto numSegments = ObjectJsonHelpers::GetInteger(jCar, "numSegments"); - car.peep_loading_waypoint_segments = numSegments; - - size_t i; - json_t* route; - json_array_foreach(jLoadingWaypoints, i, route) + for (auto& jRoute : jLoadingWaypoints) { - if (json_is_array(route)) + if (jRoute.is_array()) { - size_t j; - json_t* waypoint; std::array entry; - json_array_foreach(route, j, waypoint) + + for (size_t j = 0; j < 3; ++j) { - if (json_is_array(waypoint) && json_array_size(waypoint) >= 2) + auto jWaypoint = jRoute[j]; + if (jWaypoint.is_array() && jWaypoint.size() >= 2) { - int32_t x = json_integer_value(json_array_get(waypoint, 0)); - int32_t y = json_integer_value(json_array_get(waypoint, 1)); + int32_t x = Json::GetNumber(jWaypoint[0]); + int32_t y = Json::GetNumber(jWaypoint[1]); entry[j] = { x, y }; } } - peepLoadingWaypoints.push_back(entry); + + car.peep_loading_waypoints.push_back(entry); } } } } - auto jFrames = json_object_get(jCar, "frames"); - car.sprite_flags = ObjectJsonHelpers::GetFlags( - jFrames, - { - { "flat", VEHICLE_SPRITE_FLAG_FLAT }, - { "gentleSlopes", VEHICLE_SPRITE_FLAG_GENTLE_SLOPES }, - { "steepSlopes", VEHICLE_SPRITE_FLAG_STEEP_SLOPES }, - { "verticalSlopes", VEHICLE_SPRITE_FLAG_VERTICAL_SLOPES }, - { "diagonalSlopes", VEHICLE_SPRITE_FLAG_DIAGONAL_SLOPES }, - { "flatBanked", VEHICLE_SPRITE_FLAG_FLAT_BANKED }, - { "inlineTwists", VEHICLE_SPRITE_FLAG_INLINE_TWISTS }, - { "flatToGentleSlopeBankedTransitions", VEHICLE_SPRITE_FLAG_FLAT_TO_GENTLE_SLOPE_BANKED_TRANSITIONS }, - { "diagonalGentleSlopeBankedTransitions", VEHICLE_SPRITE_FLAG_DIAGONAL_GENTLE_SLOPE_BANKED_TRANSITIONS }, - { "gentleSlopeBankedTransitions", VEHICLE_SPRITE_FLAG_GENTLE_SLOPE_BANKED_TRANSITIONS }, - { "gentleSlopeBankedTurns", VEHICLE_SPRITE_FLAG_GENTLE_SLOPE_BANKED_TURNS }, - { "flatToGentleSlopeWhileBankedTransitions", VEHICLE_SPRITE_FLAG_FLAT_TO_GENTLE_SLOPE_WHILE_BANKED_TRANSITIONS }, - { "corkscrews", VEHICLE_SPRITE_FLAG_CORKSCREWS }, - { "restraintAnimation", VEHICLE_SPRITE_FLAG_RESTRAINT_ANIMATION }, - { "curvedLiftHill", VEHICLE_SPRITE_FLAG_CURVED_LIFT_HILL }, - { "VEHICLE_SPRITE_FLAG_15", VEHICLE_SPRITE_FLAG_15 }, - }); + auto jFrames = jCar["frames"]; + if (jFrames.is_object()) + { + car.sprite_flags = Json::GetFlags( + jFrames, + { + { "flat", VEHICLE_SPRITE_FLAG_FLAT }, + { "gentleSlopes", VEHICLE_SPRITE_FLAG_GENTLE_SLOPES }, + { "steepSlopes", VEHICLE_SPRITE_FLAG_STEEP_SLOPES }, + { "verticalSlopes", VEHICLE_SPRITE_FLAG_VERTICAL_SLOPES }, + { "diagonalSlopes", VEHICLE_SPRITE_FLAG_DIAGONAL_SLOPES }, + { "flatBanked", VEHICLE_SPRITE_FLAG_FLAT_BANKED }, + { "inlineTwists", VEHICLE_SPRITE_FLAG_INLINE_TWISTS }, + { "flatToGentleSlopeBankedTransitions", VEHICLE_SPRITE_FLAG_FLAT_TO_GENTLE_SLOPE_BANKED_TRANSITIONS }, + { "diagonalGentleSlopeBankedTransitions", VEHICLE_SPRITE_FLAG_DIAGONAL_GENTLE_SLOPE_BANKED_TRANSITIONS }, + { "gentleSlopeBankedTransitions", VEHICLE_SPRITE_FLAG_GENTLE_SLOPE_BANKED_TRANSITIONS }, + { "gentleSlopeBankedTurns", VEHICLE_SPRITE_FLAG_GENTLE_SLOPE_BANKED_TURNS }, + { "flatToGentleSlopeWhileBankedTransitions", + VEHICLE_SPRITE_FLAG_FLAT_TO_GENTLE_SLOPE_WHILE_BANKED_TRANSITIONS }, + { "corkscrews", VEHICLE_SPRITE_FLAG_CORKSCREWS }, + { "restraintAnimation", VEHICLE_SPRITE_FLAG_RESTRAINT_ANIMATION }, + { "curvedLiftHill", VEHICLE_SPRITE_FLAG_CURVED_LIFT_HILL }, + { "VEHICLE_SPRITE_FLAG_15", VEHICLE_SPRITE_FLAG_15 }, + }); + } - car.flags |= ObjectJsonHelpers::GetFlags( + car.flags |= Json::GetFlags( jCar, { { "VEHICLE_ENTRY_FLAG_POWERED_RIDE_UNRESTRICTED_GRAVITY", VEHICLE_ENTRY_FLAG_POWERED_RIDE_UNRESTRICTED_GRAVITY }, @@ -856,18 +843,21 @@ rct_ride_entry_vehicle RideObject::ReadJsonCar(const json_t* jCar) { "VEHICLE_ENTRY_FLAG_GO_KART", VEHICLE_ENTRY_FLAG_GO_KART }, { "VEHICLE_ENTRY_FLAG_DODGEM_CAR_PLACEMENT", VEHICLE_ENTRY_FLAG_DODGEM_CAR_PLACEMENT }, }); + return car; } -vehicle_colour_preset_list RideObject::ReadJsonCarColours(const json_t* jCarColours) +vehicle_colour_preset_list RideObject::ReadJsonCarColours(json_t& jCarColours) { + Guard::Assert(jCarColours.is_array(), "RideObject::ReadJsonCarColours expects parameter jCarColours to be array"); + // The JSON supports multiple configurations of per car colours, but // the ride entry structure currently doesn't allow for it. Assume that // a single configuration with multiple colour entries is per car scheme. - if (json_array_size(jCarColours) == 1) + if (jCarColours.size() == 1) { - auto firstElement = json_array_get(jCarColours, 0); - auto numColours = json_array_size(firstElement); + auto firstElement = Json::AsArray(jCarColours[0]); + auto numColours = firstElement.size(); if (numColours >= 2) { // Read all colours from first config @@ -881,11 +871,9 @@ vehicle_colour_preset_list RideObject::ReadJsonCarColours(const json_t* jCarColo // Read first colour for each config vehicle_colour_preset_list list = {}; - size_t index; - const json_t* jConfiguration; - json_array_foreach(jCarColours, index, jConfiguration) + for (size_t index = 0; index < jCarColours.size(); index++) { - auto config = ReadJsonColourConfiguration(jConfiguration); + auto config = ReadJsonColourConfiguration(jCarColours[index]); if (config.size() >= 1) { list.list[index] = config[0]; @@ -901,27 +889,27 @@ vehicle_colour_preset_list RideObject::ReadJsonCarColours(const json_t* jCarColo return list; } -std::vector RideObject::ReadJsonColourConfiguration(const json_t* jColourConfig) +std::vector RideObject::ReadJsonColourConfiguration(json_t& jColourConfig) { std::vector config; - size_t index; - const json_t* jColours; - json_array_foreach(jColourConfig, index, jColours) + + for (auto& jColours : jColourConfig) { vehicle_colour carColour = {}; - auto colours = ObjectJsonHelpers::GetJsonStringArray(jColours); + + auto colours = Json::AsArray(jColours); if (colours.size() >= 1) { - carColour.main = ObjectJsonHelpers::ParseColour(colours[0]); + carColour.main = Colour::FromString(Json::GetString(colours[0])); carColour.additional_1 = carColour.main; carColour.additional_2 = carColour.main; if (colours.size() >= 2) { - carColour.additional_1 = ObjectJsonHelpers::ParseColour(colours[1]); + carColour.additional_1 = Colour::FromString(Json::GetString(colours[1])); } if (colours.size() >= 3) { - carColour.additional_2 = ObjectJsonHelpers::ParseColour(colours[2]); + carColour.additional_2 = Colour::FromString(Json::GetString(colours[2])); } } config.push_back(carColour); diff --git a/src/openrct2/object/RideObject.h b/src/openrct2/object/RideObject.h index 2448539cbc..c2217259bd 100644 --- a/src/openrct2/object/RideObject.h +++ b/src/openrct2/object/RideObject.h @@ -10,6 +10,7 @@ #pragma once #include "../core/IStream.hpp" +#include "../core/Json.hpp" #include "../ride/Ride.h" #include "Object.h" @@ -34,7 +35,7 @@ public: return &_legacyType; } - void ReadJson(IReadObjectContext* context, const json_t* root) override; + void ReadJson(IReadObjectContext* context, json_t& root) override; void ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) override; void Load() override; void Unload() override; @@ -49,11 +50,11 @@ public: private: void ReadLegacyVehicle(IReadObjectContext* context, OpenRCT2::IStream* stream, rct_ride_entry_vehicle* vehicle); - void ReadJsonVehicleInfo(IReadObjectContext* context, const json_t* properties); - std::vector ReadJsonCars(const json_t* jCars); - rct_ride_entry_vehicle ReadJsonCar(const json_t* jCar); - vehicle_colour_preset_list ReadJsonCarColours(const json_t* jCarColours); - std::vector ReadJsonColourConfiguration(const json_t* jColourConfig); + void ReadJsonVehicleInfo(IReadObjectContext* context, json_t& properties); + std::vector ReadJsonCars(json_t& jCars); + rct_ride_entry_vehicle ReadJsonCar(json_t& jCar); + vehicle_colour_preset_list ReadJsonCarColours(json_t& jCarColours); + std::vector ReadJsonColourConfiguration(json_t& jColourConfig); static uint8_t CalculateNumVerticalFrames(const rct_ride_entry_vehicle* vehicleEntry); static uint8_t CalculateNumHorizontalFrames(const rct_ride_entry_vehicle* vehicleEntry); diff --git a/src/openrct2/object/SceneryGroupObject.cpp b/src/openrct2/object/SceneryGroupObject.cpp index 7c94a29969..5936bb0296 100644 --- a/src/openrct2/object/SceneryGroupObject.cpp +++ b/src/openrct2/object/SceneryGroupObject.cpp @@ -18,7 +18,6 @@ #include "../drawing/Drawing.h" #include "../localisation/Language.h" #include "../peep/Staff.h" -#include "ObjectJsonHelpers.h" #include "ObjectManager.h" #include "ObjectRepository.h" @@ -110,35 +109,29 @@ std::vector SceneryGroupObject::ReadItems(IStream* stream) return items; } -void SceneryGroupObject::ReadJson(IReadObjectContext* context, const json_t* root) +void SceneryGroupObject::ReadJson(IReadObjectContext* context, json_t& root) { - auto properties = json_object_get(root, "properties"); - _legacyType.priority = json_integer_value(json_object_get(properties, "priority")); + Guard::Assert(root.is_object(), "SceneryGroupObject::ReadJson expects parameter root to be object"); - // Entertainer costumes - auto jCostumes = json_object_get(properties, "entertainerCostumes"); - if (jCostumes != nullptr) + auto properties = root["properties"]; + + if (properties.is_object()) { - _legacyType.entertainer_costumes = ReadJsonEntertainerCostumes(jCostumes); + _legacyType.priority = Json::GetNumber(properties["priority"]); + _legacyType.entertainer_costumes = ReadJsonEntertainerCostumes(properties["entertainerCostumes"]); + + _items = ReadJsonEntries(properties["entries"]); } - auto jEntries = json_object_get(properties, "entries"); - if (jEntries != nullptr) - { - _items = ReadJsonEntries(jEntries); - } - - ObjectJsonHelpers::LoadStrings(root, GetStringTable()); - ObjectJsonHelpers::LoadImages(context, root, GetImageTable()); + PopulateTablesFromJson(context, root); } -uint32_t SceneryGroupObject::ReadJsonEntertainerCostumes(const json_t* jCostumes) +uint32_t SceneryGroupObject::ReadJsonEntertainerCostumes(json_t& jCostumes) { uint32_t costumes = 0; - auto szCostumes = ObjectJsonHelpers::GetJsonStringArray(jCostumes); - for (const auto& szCostume : szCostumes) + for (auto& jCostume : jCostumes) { - auto entertainer = ParseEntertainerCostume(szCostume); + auto entertainer = ParseEntertainerCostume(Json::GetString(jCostume)); auto peepSprite = EntertainerCostumeToSprite(entertainer); costumes |= 1 << (static_cast(peepSprite)); } @@ -172,19 +165,14 @@ EntertainerCostume SceneryGroupObject::ParseEntertainerCostume(const std::string return EntertainerCostume::Panda; } -std::vector SceneryGroupObject::ReadJsonEntries(const json_t* jEntries) +std::vector SceneryGroupObject::ReadJsonEntries(json_t& jEntries) { std::vector entries; - size_t index; - json_t* jEntry; - json_array_foreach(jEntries, index, jEntry) + + for (auto& jEntry : jEntries) { - auto entryId = json_string_value(jEntry); - if (entryId != nullptr) - { - auto entry = ObjectJsonHelpers::ParseObjectEntry(entryId); - entries.push_back(entry); - } + auto entry = ParseObjectEntry(Json::GetString(jEntry)); + entries.push_back(entry); } return entries; } diff --git a/src/openrct2/object/SceneryGroupObject.h b/src/openrct2/object/SceneryGroupObject.h index f2c9a6780e..7361e2ef5b 100644 --- a/src/openrct2/object/SceneryGroupObject.h +++ b/src/openrct2/object/SceneryGroupObject.h @@ -34,7 +34,7 @@ public: { return &_legacyType; } - void ReadJson(IReadObjectContext* context, const json_t* root) override; + void ReadJson(IReadObjectContext* context, json_t& root) override; void ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) override; void Load() override; @@ -47,7 +47,7 @@ public: private: static std::vector ReadItems(OpenRCT2::IStream* stream); - static uint32_t ReadJsonEntertainerCostumes(const json_t* jCostumes); + static uint32_t ReadJsonEntertainerCostumes(json_t& jCostumes); static EntertainerCostume ParseEntertainerCostume(const std::string& s); - static std::vector ReadJsonEntries(const json_t* jEntries); + static std::vector ReadJsonEntries(json_t& jEntries); }; diff --git a/src/openrct2/object/SceneryObject.cpp b/src/openrct2/object/SceneryObject.cpp index 184abd91fa..2314162a2b 100644 --- a/src/openrct2/object/SceneryObject.cpp +++ b/src/openrct2/object/SceneryObject.cpp @@ -9,13 +9,11 @@ #include "SceneryObject.h" -#include "ObjectJsonHelpers.h" - void SceneryObject::SetPrimarySceneryGroup(const std::string& s) { if (!s.empty()) { - auto sgEntry = ObjectJsonHelpers::ParseObjectEntry(s); + auto sgEntry = ParseObjectEntry(s); SetPrimarySceneryGroup(&sgEntry); } } diff --git a/src/openrct2/object/SmallSceneryObject.cpp b/src/openrct2/object/SmallSceneryObject.cpp index d2d89c75c9..9cef4276f1 100644 --- a/src/openrct2/object/SmallSceneryObject.cpp +++ b/src/openrct2/object/SmallSceneryObject.cpp @@ -19,7 +19,6 @@ #include "../localisation/Language.h" #include "../world/Scenery.h" #include "../world/SmallScenery.h" -#include "ObjectJsonHelpers.h" #include @@ -234,96 +233,97 @@ rct_object_entry SmallSceneryObject::GetScgAbstrHeader() return Object::CreateHeader("SCGABSTR", 207140231, 932253451); } -void SmallSceneryObject::ReadJson(IReadObjectContext* context, const json_t* root) +void SmallSceneryObject::ReadJson(IReadObjectContext* context, json_t& root) { - auto properties = json_object_get(root, "properties"); + Guard::Assert(root.is_object(), "SmallSceneryObject::ReadJson expects parameter root to be object"); - _legacyType.small_scenery.height = json_integer_value(json_object_get(properties, "height")); - _legacyType.small_scenery.tool_id = ObjectJsonHelpers::ParseCursor( - ObjectJsonHelpers::GetString(properties, "cursor"), CURSOR_STATUE_DOWN); - _legacyType.small_scenery.price = json_integer_value(json_object_get(properties, "price")); - _legacyType.small_scenery.removal_price = json_integer_value(json_object_get(properties, "removalPrice")); - _legacyType.small_scenery.animation_delay = json_integer_value(json_object_get(properties, "animationDelay")); - _legacyType.small_scenery.animation_mask = json_integer_value(json_object_get(properties, "animationMask")); - _legacyType.small_scenery.num_frames = json_integer_value(json_object_get(properties, "numFrames")); + auto properties = root["properties"]; - // Flags - _legacyType.small_scenery.flags = ObjectJsonHelpers::GetFlags( - properties, - { - { "SMALL_SCENERY_FLAG_VOFFSET_CENTRE", SMALL_SCENERY_FLAG_VOFFSET_CENTRE }, - { "requiresFlatSurface", SMALL_SCENERY_FLAG_REQUIRE_FLAT_SURFACE }, - { "isRotatable", SMALL_SCENERY_FLAG_ROTATABLE }, - { "isAnimated", SMALL_SCENERY_FLAG_ANIMATED }, - { "canWither", SMALL_SCENERY_FLAG_CAN_WITHER }, - { "canBeWatered", SMALL_SCENERY_FLAG_CAN_BE_WATERED }, - { "hasOverlayImage", SMALL_SCENERY_FLAG_ANIMATED_FG }, - { "hasGlass", SMALL_SCENERY_FLAG_HAS_GLASS }, - { "hasPrimaryColour", SMALL_SCENERY_FLAG_HAS_PRIMARY_COLOUR }, - { "SMALL_SCENERY_FLAG_FOUNTAIN_SPRAY_1", SMALL_SCENERY_FLAG_FOUNTAIN_SPRAY_1 }, - { "SMALL_SCENERY_FLAG_FOUNTAIN_SPRAY_4", SMALL_SCENERY_FLAG_FOUNTAIN_SPRAY_4 }, - { "isClock", SMALL_SCENERY_FLAG_IS_CLOCK }, - { "SMALL_SCENERY_FLAG_SWAMP_GOO", SMALL_SCENERY_FLAG_SWAMP_GOO }, - { "SMALL_SCENERY_FLAG17", SMALL_SCENERY_FLAG17 }, - { "isStackable", SMALL_SCENERY_FLAG_STACKABLE }, - { "prohibitWalls", SMALL_SCENERY_FLAG_NO_WALLS }, - { "hasSecondaryColour", SMALL_SCENERY_FLAG_HAS_SECONDARY_COLOUR }, - { "hasNoSupports", SMALL_SCENERY_FLAG_NO_SUPPORTS }, - { "SMALL_SCENERY_FLAG_VISIBLE_WHEN_ZOOMED", SMALL_SCENERY_FLAG_VISIBLE_WHEN_ZOOMED }, - { "SMALL_SCENERY_FLAG_COG", SMALL_SCENERY_FLAG_COG }, - { "allowSupportsAbove", SMALL_SCENERY_FLAG_BUILD_DIRECTLY_ONTOP }, - { "supportsHavePrimaryColour", SMALL_SCENERY_FLAG_PAINT_SUPPORTS }, - { "SMALL_SCENERY_FLAG27", SMALL_SCENERY_FLAG27 }, - { "isTree", SMALL_SCENERY_FLAG_IS_TREE }, - }); - - // Determine shape flags from a shape string - auto shape = ObjectJsonHelpers::GetString(properties, "shape"); - if (!shape.empty()) + if (properties.is_object()) { - auto quarters = shape.substr(0, 3); - if (quarters == "2/4") - { - _legacyType.small_scenery.flags |= SMALL_SCENERY_FLAG_FULL_TILE | SMALL_SCENERY_FLAG_HALF_SPACE; - } - else if (quarters == "3/4") - { - _legacyType.small_scenery.flags |= SMALL_SCENERY_FLAG_FULL_TILE | SMALL_SCENERY_FLAG_THREE_QUARTERS; - } - else if (quarters == "4/4") - { - _legacyType.small_scenery.flags |= SMALL_SCENERY_FLAG_FULL_TILE; - } - if (shape.size() >= 5) - { - if ((shape.substr(3) == "+D")) + _legacyType.small_scenery.height = Json::GetNumber(properties["height"]); + _legacyType.small_scenery.tool_id = Cursor::FromString(Json::GetString(properties["cursor"]), CURSOR_STATUE_DOWN); + _legacyType.small_scenery.price = Json::GetNumber(properties["price"]); + _legacyType.small_scenery.removal_price = Json::GetNumber(properties["removalPrice"]); + _legacyType.small_scenery.animation_delay = Json::GetNumber(properties["animationDelay"]); + _legacyType.small_scenery.animation_mask = Json::GetNumber(properties["animationMask"]); + _legacyType.small_scenery.num_frames = Json::GetNumber(properties["numFrames"]); + + _legacyType.small_scenery.flags = Json::GetFlags( + properties, { - _legacyType.small_scenery.flags |= SMALL_SCENERY_FLAG_DIAGONAL; + { "SMALL_SCENERY_FLAG_VOFFSET_CENTRE", SMALL_SCENERY_FLAG_VOFFSET_CENTRE }, + { "requiresFlatSurface", SMALL_SCENERY_FLAG_REQUIRE_FLAT_SURFACE }, + { "isRotatable", SMALL_SCENERY_FLAG_ROTATABLE }, + { "isAnimated", SMALL_SCENERY_FLAG_ANIMATED }, + { "canWither", SMALL_SCENERY_FLAG_CAN_WITHER }, + { "canBeWatered", SMALL_SCENERY_FLAG_CAN_BE_WATERED }, + { "hasOverlayImage", SMALL_SCENERY_FLAG_ANIMATED_FG }, + { "hasGlass", SMALL_SCENERY_FLAG_HAS_GLASS }, + { "hasPrimaryColour", SMALL_SCENERY_FLAG_HAS_PRIMARY_COLOUR }, + { "SMALL_SCENERY_FLAG_FOUNTAIN_SPRAY_1", SMALL_SCENERY_FLAG_FOUNTAIN_SPRAY_1 }, + { "SMALL_SCENERY_FLAG_FOUNTAIN_SPRAY_4", SMALL_SCENERY_FLAG_FOUNTAIN_SPRAY_4 }, + { "isClock", SMALL_SCENERY_FLAG_IS_CLOCK }, + { "SMALL_SCENERY_FLAG_SWAMP_GOO", SMALL_SCENERY_FLAG_SWAMP_GOO }, + { "SMALL_SCENERY_FLAG17", SMALL_SCENERY_FLAG17 }, + { "isStackable", SMALL_SCENERY_FLAG_STACKABLE }, + { "prohibitWalls", SMALL_SCENERY_FLAG_NO_WALLS }, + { "hasSecondaryColour", SMALL_SCENERY_FLAG_HAS_SECONDARY_COLOUR }, + { "hasNoSupports", SMALL_SCENERY_FLAG_NO_SUPPORTS }, + { "SMALL_SCENERY_FLAG_VISIBLE_WHEN_ZOOMED", SMALL_SCENERY_FLAG_VISIBLE_WHEN_ZOOMED }, + { "SMALL_SCENERY_FLAG_COG", SMALL_SCENERY_FLAG_COG }, + { "allowSupportsAbove", SMALL_SCENERY_FLAG_BUILD_DIRECTLY_ONTOP }, + { "supportsHavePrimaryColour", SMALL_SCENERY_FLAG_PAINT_SUPPORTS }, + { "SMALL_SCENERY_FLAG27", SMALL_SCENERY_FLAG27 }, + { "isTree", SMALL_SCENERY_FLAG_IS_TREE }, + }); + + // Determine shape flags from a shape string + auto shape = Json::GetString(properties["shape"]); + if (!shape.empty()) + { + auto quarters = shape.substr(0, 3); + if (quarters == "2/4") + { + _legacyType.small_scenery.flags |= SMALL_SCENERY_FLAG_FULL_TILE | SMALL_SCENERY_FLAG_HALF_SPACE; + } + else if (quarters == "3/4") + { + _legacyType.small_scenery.flags |= SMALL_SCENERY_FLAG_FULL_TILE | SMALL_SCENERY_FLAG_THREE_QUARTERS; + } + else if (quarters == "4/4") + { + _legacyType.small_scenery.flags |= SMALL_SCENERY_FLAG_FULL_TILE; + } + if (shape.size() >= 5) + { + if ((shape.substr(3) == "+D")) + { + _legacyType.small_scenery.flags |= SMALL_SCENERY_FLAG_DIAGONAL; + } } } + + auto jFrameOffsets = properties["frameOffsets"]; + if (jFrameOffsets.is_array()) + { + _frameOffsets = ReadJsonFrameOffsets(jFrameOffsets); + _legacyType.small_scenery.flags |= SMALL_SCENERY_FLAG_HAS_FRAME_OFFSETS; + } + + SetPrimarySceneryGroup(Json::GetString(properties["sceneryGroup"])); } - auto jFrameOffsets = json_object_get(properties, "frameOffsets"); - if (jFrameOffsets != nullptr) - { - _frameOffsets = ReadJsonFrameOffsets(jFrameOffsets); - _legacyType.small_scenery.flags |= SMALL_SCENERY_FLAG_HAS_FRAME_OFFSETS; - } - - SetPrimarySceneryGroup(ObjectJsonHelpers::GetString(json_object_get(properties, "sceneryGroup"))); - - ObjectJsonHelpers::LoadStrings(root, GetStringTable()); - ObjectJsonHelpers::LoadImages(context, root, GetImageTable()); + PopulateTablesFromJson(context, root); } -std::vector SmallSceneryObject::ReadJsonFrameOffsets(const json_t* jFrameOffsets) +std::vector SmallSceneryObject::ReadJsonFrameOffsets(json_t& jFrameOffsets) { std::vector offsets; - size_t index; - const json_t* jOffset; - json_array_foreach(jFrameOffsets, index, jOffset) + + for (auto& jOffset : jFrameOffsets) { - offsets.push_back(json_integer_value(jOffset)); + offsets.push_back(Json::GetNumber(jOffset)); } return offsets; } diff --git a/src/openrct2/object/SmallSceneryObject.h b/src/openrct2/object/SmallSceneryObject.h index 0c6e950f1a..8018bf8b72 100644 --- a/src/openrct2/object/SmallSceneryObject.h +++ b/src/openrct2/object/SmallSceneryObject.h @@ -32,7 +32,7 @@ public: } void ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) override; - void ReadJson(IReadObjectContext* context, const json_t* root) override; + void ReadJson(IReadObjectContext* context, json_t& root) override; void Load() override; void Unload() override; @@ -40,7 +40,7 @@ public: private: static std::vector ReadFrameOffsets(OpenRCT2::IStream* stream); - static std::vector ReadJsonFrameOffsets(const json_t* jFrameOffsets); + static std::vector ReadJsonFrameOffsets(json_t& jFrameOffsets); void PerformFixes(); rct_object_entry GetScgPiratHeader(); rct_object_entry GetScgMineHeader(); diff --git a/src/openrct2/object/StationObject.cpp b/src/openrct2/object/StationObject.cpp index dc2d989aef..2b8c37c938 100644 --- a/src/openrct2/object/StationObject.cpp +++ b/src/openrct2/object/StationObject.cpp @@ -14,7 +14,6 @@ #include "../drawing/Drawing.h" #include "../localisation/Localisation.h" #include "../world/Banner.h" -#include "ObjectJsonHelpers.h" void StationObject::Load() { @@ -78,17 +77,24 @@ void StationObject::DrawPreview(rct_drawpixelinfo* dpi, int32_t width, int32_t h } } -void StationObject::ReadJson(IReadObjectContext* context, const json_t* root) +void StationObject::ReadJson(IReadObjectContext* context, json_t& root) { - auto properties = json_object_get(root, "properties"); - Height = ObjectJsonHelpers::GetInteger(properties, "height", 0); - ScrollingMode = ObjectJsonHelpers::GetInteger(properties, "scrollingMode", SCROLLING_MODE_NONE); - Flags = ObjectJsonHelpers::GetFlags( - properties, - { { "hasPrimaryColour", STATION_OBJECT_FLAGS::HAS_PRIMARY_COLOUR }, - { "hasSecondaryColour", STATION_OBJECT_FLAGS::HAS_SECONDARY_COLOUR }, - { "isTransparent", STATION_OBJECT_FLAGS::IS_TRANSPARENT } }); + Guard::Assert(root.is_object(), "StationObject::ReadJson expects parameter root to be object"); - ObjectJsonHelpers::LoadStrings(root, GetStringTable()); - ObjectJsonHelpers::LoadImages(context, root, GetImageTable()); + auto properties = root["properties"]; + + if (properties.is_object()) + { + Height = Json::GetNumber(properties["height"]); + ScrollingMode = Json::GetNumber(properties["scrollingMode"], SCROLLING_MODE_NONE); + Flags = Json::GetFlags( + properties, + { + { "hasPrimaryColour", STATION_OBJECT_FLAGS::HAS_PRIMARY_COLOUR }, + { "hasSecondaryColour", STATION_OBJECT_FLAGS::HAS_SECONDARY_COLOUR }, + { "isTransparent", STATION_OBJECT_FLAGS::IS_TRANSPARENT }, + }); + } + + PopulateTablesFromJson(context, root); } diff --git a/src/openrct2/object/StationObject.h b/src/openrct2/object/StationObject.h index cf982033fe..66300dc11e 100644 --- a/src/openrct2/object/StationObject.h +++ b/src/openrct2/object/StationObject.h @@ -33,7 +33,7 @@ public: { } - void ReadJson(IReadObjectContext* context, const json_t* root) override; + void ReadJson(IReadObjectContext* context, json_t& root) override; void Load() override; void Unload() override; diff --git a/src/openrct2/object/StringTable.cpp b/src/openrct2/object/StringTable.cpp index 02eb7abc90..1994cd9f1f 100644 --- a/src/openrct2/object/StringTable.cpp +++ b/src/openrct2/object/StringTable.cpp @@ -78,6 +78,45 @@ void StringTable::Read(IReadObjectContext* context, OpenRCT2::IStream* stream, O Sort(); } +ObjectStringID StringTable::ParseStringId(const std::string& s) +{ + if (s == "name") + return ObjectStringID::NAME; + if (s == "description") + return ObjectStringID::DESCRIPTION; + if (s == "capacity") + return ObjectStringID::CAPACITY; + if (s == "vehicleName") + return ObjectStringID::VEHICLE_NAME; + return ObjectStringID::UNKNOWN; +} + +void StringTable::ReadJson(json_t& root) +{ + Guard::Assert(root.is_object(), "StringTable::ReadJson expects parameter root to be object"); + + // We trust the JSON type of root is object + auto jsonStrings = root["strings"]; + + for (auto& [key, jsonLanguages] : jsonStrings.items()) + { + auto stringId = ParseStringId(key); + if (stringId != ObjectStringID::UNKNOWN) + { + for (auto& [locale, jsonString] : jsonLanguages.items()) + { + auto langId = language_get_id_from_locale(locale.c_str()); + if (langId != LANGUAGE_UNDEFINED) + { + auto string = Json::GetString(jsonString); + SetString(stringId, langId, string); + } + } + } + } + Sort(); +} + std::string StringTable::GetString(ObjectStringID id) const { for (auto& string : _strings) diff --git a/src/openrct2/object/StringTable.h b/src/openrct2/object/StringTable.h index a6f4a2b124..9f75b3a0ed 100644 --- a/src/openrct2/object/StringTable.h +++ b/src/openrct2/object/StringTable.h @@ -10,6 +10,7 @@ #pragma once #include "../common.h" +#include "../core/Json.hpp" #include "../localisation/Language.h" #include @@ -44,6 +45,7 @@ class StringTable { private: std::vector _strings; + static ObjectStringID ParseStringId(const std::string& s); public: StringTable() = default; @@ -51,6 +53,10 @@ public: StringTable& operator=(const StringTable&) = delete; void Read(IReadObjectContext* context, OpenRCT2::IStream* stream, ObjectStringID id); + /** + * @note root is deliberately left non-const: json_t behaviour changes when const + */ + void ReadJson(json_t& root); void Sort(); std::string GetString(ObjectStringID id) const; std::string GetString(uint8_t language, ObjectStringID id) const; diff --git a/src/openrct2/object/TerrainEdgeObject.cpp b/src/openrct2/object/TerrainEdgeObject.cpp index ac8c7f5147..77ca225822 100644 --- a/src/openrct2/object/TerrainEdgeObject.cpp +++ b/src/openrct2/object/TerrainEdgeObject.cpp @@ -13,7 +13,6 @@ #include "../core/String.hpp" #include "../drawing/Drawing.h" #include "../localisation/Localisation.h" -#include "ObjectJsonHelpers.h" void TerrainEdgeObject::Load() { @@ -44,12 +43,17 @@ void TerrainEdgeObject::DrawPreview(rct_drawpixelinfo* dpi, int32_t width, int32 gfx_draw_sprite(dpi, imageId + 5, screenCoords + ScreenCoordsXY{ 8, 8 }, 0); } -void TerrainEdgeObject::ReadJson(IReadObjectContext* context, const json_t* root) +void TerrainEdgeObject::ReadJson(IReadObjectContext* context, json_t& root) { - auto properties = json_object_get(root, "properties"); - HasDoors = ObjectJsonHelpers::GetBoolean(properties, "hasDoors", false); + Guard::Assert(root.is_object(), "TerrainEdgeObject::ReadJson expects parameter root to be object"); - ObjectJsonHelpers::LoadStrings(root, GetStringTable()); - ObjectJsonHelpers::LoadImages(context, root, GetImageTable()); + auto properties = root["properties"]; + + if (properties.is_object()) + { + HasDoors = Json::GetBoolean(properties["hasDoors"]); + } + + PopulateTablesFromJson(context, root); NumImagesLoaded = GetImageTable().GetCount(); } diff --git a/src/openrct2/object/TerrainEdgeObject.h b/src/openrct2/object/TerrainEdgeObject.h index 20c19b641f..f275736b7e 100644 --- a/src/openrct2/object/TerrainEdgeObject.h +++ b/src/openrct2/object/TerrainEdgeObject.h @@ -26,7 +26,7 @@ public: { } - void ReadJson(IReadObjectContext* context, const json_t* root) override; + void ReadJson(IReadObjectContext* context, json_t& root) override; void Load() override; void Unload() override; diff --git a/src/openrct2/object/TerrainSurfaceObject.cpp b/src/openrct2/object/TerrainSurfaceObject.cpp index 03e9e679ba..cfba7585c9 100644 --- a/src/openrct2/object/TerrainSurfaceObject.cpp +++ b/src/openrct2/object/TerrainSurfaceObject.cpp @@ -16,7 +16,6 @@ #include "../drawing/Drawing.h" #include "../localisation/Localisation.h" #include "../world/Location.hpp" -#include "ObjectJsonHelpers.h" void TerrainSurfaceObject::Load() { @@ -74,24 +73,45 @@ void TerrainSurfaceObject::DrawPreview(rct_drawpixelinfo* dpi, int32_t width, in } } -void TerrainSurfaceObject::ReadJson(IReadObjectContext* context, const json_t* root) +void TerrainSurfaceObject::ReadJson(IReadObjectContext* context, json_t& root) { - auto properties = json_object_get(root, "properties"); - Colour = ObjectJsonHelpers::ParseColour(ObjectJsonHelpers::GetString(properties, "colour"), 255); - Rotations = ObjectJsonHelpers::GetInteger(properties, "rotations", 1); - Price = ObjectJsonHelpers::GetInteger(properties, "price", 0); - Flags = ObjectJsonHelpers::GetFlags( - properties, - { { "smoothWithSelf", TERRAIN_SURFACE_FLAGS::SMOOTH_WITH_SELF }, - { "smoothWithOther", TERRAIN_SURFACE_FLAGS::SMOOTH_WITH_OTHER }, - { "canGrow", TERRAIN_SURFACE_FLAGS::CAN_GROW } }); + Guard::Assert(root.is_object(), "TerrainSurfaceObject::ReadJson expects parameter root to be object"); - auto jDefault = json_object_get(root, "default"); - if (json_is_object(jDefault)) + auto properties = root["properties"]; + + if (properties.is_object()) { - DefaultEntry = ObjectJsonHelpers::GetInteger(properties, "normal"); - DefaultGridEntry = ObjectJsonHelpers::GetInteger(properties, "grid"); - DefaultUndergroundEntry = ObjectJsonHelpers::GetInteger(properties, "underground"); + Colour = Colour::FromString(Json::GetString(properties["colour"]), 255); + Rotations = Json::GetNumber(properties["rotations"], 1); + Price = Json::GetNumber(properties["price"]); + Flags = Json::GetFlags( + properties, + { { "smoothWithSelf", TERRAIN_SURFACE_FLAGS::SMOOTH_WITH_SELF }, + { "smoothWithOther", TERRAIN_SURFACE_FLAGS::SMOOTH_WITH_OTHER }, + { "canGrow", TERRAIN_SURFACE_FLAGS::CAN_GROW } }); + + for (auto& el : properties["special"]) + { + if (el.is_object()) + { + SpecialEntry entry; + entry.Index = Json::GetNumber(el["index"]); + entry.Length = Json::GetNumber(el["length"], -1); + entry.Rotation = Json::GetNumber(el["rotation"], -1); + entry.Variation = Json::GetNumber(el["variation"], -1); + entry.Grid = Json::GetBoolean(el["grid"]); + entry.Underground = Json::GetBoolean(el["underground"]); + SpecialEntries.push_back(std::move(entry)); + } + } + } + + auto jDefault = root["default"]; + if (jDefault.is_object()) + { + DefaultEntry = Json::GetNumber(jDefault["normal"]); + DefaultGridEntry = Json::GetNumber(jDefault["grid"]); + DefaultUndergroundEntry = Json::GetNumber(jDefault["underground"]); } else { @@ -100,26 +120,7 @@ void TerrainSurfaceObject::ReadJson(IReadObjectContext* context, const json_t* r DefaultUndergroundEntry = 2; } - auto jSpecialArray = json_object_get(properties, "special"); - if (json_is_array(jSpecialArray)) - { - size_t i; - json_t* el; - json_array_foreach(jSpecialArray, i, el) - { - SpecialEntry entry; - entry.Index = ObjectJsonHelpers::GetInteger(el, "index"); - entry.Length = ObjectJsonHelpers::GetInteger(el, "length", -1); - entry.Rotation = ObjectJsonHelpers::GetInteger(el, "rotation", -1); - entry.Variation = ObjectJsonHelpers::GetInteger(el, "variation", -1); - entry.Grid = ObjectJsonHelpers::GetBoolean(el, "grid"); - entry.Underground = ObjectJsonHelpers::GetBoolean(el, "underground"); - SpecialEntries.push_back(std::move(entry)); - } - } - - ObjectJsonHelpers::LoadStrings(root, GetStringTable()); - ObjectJsonHelpers::LoadImages(context, root, GetImageTable()); + PopulateTablesFromJson(context, root); } uint32_t TerrainSurfaceObject::GetImageId( diff --git a/src/openrct2/object/TerrainSurfaceObject.h b/src/openrct2/object/TerrainSurfaceObject.h index 5914da776b..d9fc29aa93 100644 --- a/src/openrct2/object/TerrainSurfaceObject.h +++ b/src/openrct2/object/TerrainSurfaceObject.h @@ -59,7 +59,7 @@ public: { } - void ReadJson(IReadObjectContext* context, const json_t* root) override; + void ReadJson(IReadObjectContext* context, json_t& root) override; void Load() override; void Unload() override; diff --git a/src/openrct2/object/WallObject.cpp b/src/openrct2/object/WallObject.cpp index 806c27b789..686ea03e75 100644 --- a/src/openrct2/object/WallObject.cpp +++ b/src/openrct2/object/WallObject.cpp @@ -15,7 +15,6 @@ #include "../interface/Cursors.h" #include "../localisation/Language.h" #include "../world/Banner.h" -#include "ObjectJsonHelpers.h" void WallObject::ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) { @@ -94,66 +93,63 @@ void WallObject::DrawPreview(rct_drawpixelinfo* dpi, int32_t width, int32_t heig } } -void WallObject::ReadJson(IReadObjectContext* context, const json_t* root) +void WallObject::ReadJson(IReadObjectContext* context, json_t& root) { - auto properties = json_object_get(root, "properties"); + Guard::Assert(root.is_object(), "WallObject::ReadJson expects parameter root to be object"); - _legacyType.wall.tool_id = ObjectJsonHelpers::ParseCursor( - ObjectJsonHelpers::GetString(properties, "cursor"), CURSOR_FENCE_DOWN); - _legacyType.wall.height = json_integer_value(json_object_get(properties, "height")); - _legacyType.wall.price = json_integer_value(json_object_get(properties, "price")); + auto properties = root["properties"]; - auto jScrollingMode = json_object_get(properties, "scrollingMode"); - _legacyType.wall.scrolling_mode = jScrollingMode != nullptr ? json_integer_value(jScrollingMode) : SCROLLING_MODE_NONE; - - SetPrimarySceneryGroup(ObjectJsonHelpers::GetString(json_object_get(properties, "sceneryGroup"))); - - // Flags - _legacyType.wall.flags = ObjectJsonHelpers::GetFlags( - properties, - { - { "hasPrimaryColour", WALL_SCENERY_HAS_PRIMARY_COLOUR }, - { "hasSecondaryColour", WALL_SCENERY_HAS_SECONDARY_COLOUR }, - { "hasTernaryColour", WALL_SCENERY_HAS_TERNARY_COLOUR }, - { "hasGlass", WALL_SCENERY_HAS_GLASS }, - { "isBanner", WALL_SCENERY_IS_BANNER }, - { "isDoor", WALL_SCENERY_IS_DOOR }, - { "isLongDoorAnimation", WALL_SCENERY_LONG_DOOR_ANIMATION }, - }); - _legacyType.wall.flags2 = ObjectJsonHelpers::GetFlags( - properties, - { - { "isOpaque", WALL_SCENERY_2_IS_OPAQUE }, - { "isAnimated", WALL_SCENERY_2_ANIMATED }, - }); - - // HACK To avoid 'negated' properties in JSON, handle this separately until - // flag is inverted in this code base. - if (!ObjectJsonHelpers::GetBoolean(properties, "isAllowedOnSlope", false)) + if (properties.is_object()) { - _legacyType.wall.flags |= WALL_SCENERY_CANT_BUILD_ON_SLOPE; - } + _legacyType.wall.tool_id = Cursor::FromString(Json::GetString(properties["cursor"]), CURSOR_FENCE_DOWN); + _legacyType.wall.height = Json::GetNumber(properties["height"]); + _legacyType.wall.price = Json::GetNumber(properties["price"]); - // HACK WALL_SCENERY_HAS_PRIMARY_COLOUR actually means, has any colour but we simplify the - // JSON and handle this on load. We should change code base in future to reflect the JSON. - if (!(_legacyType.wall.flags & WALL_SCENERY_HAS_PRIMARY_COLOUR)) - { - if ((_legacyType.wall.flags & WALL_SCENERY_HAS_SECONDARY_COLOUR) - || (_legacyType.wall.flags & WALL_SCENERY_HAS_TERNARY_COLOUR)) + _legacyType.wall.scrolling_mode = Json::GetNumber(properties["scrollingMode"], SCROLLING_MODE_NONE); + + SetPrimarySceneryGroup(Json::GetString(properties["sceneryGroup"])); + + // clang-format off + _legacyType.wall.flags = Json::GetFlags( + properties, + { + { "hasPrimaryColour", WALL_SCENERY_HAS_PRIMARY_COLOUR, Json::FlagType::Normal }, + { "IsAllowedOnSlope", WALL_SCENERY_CANT_BUILD_ON_SLOPE, Json::FlagType::Inverted }, + { "hasSecondaryColour", WALL_SCENERY_HAS_SECONDARY_COLOUR, Json::FlagType::Normal }, + { "hasTernaryColour", WALL_SCENERY_HAS_TERNARY_COLOUR, Json::FlagType::Normal }, + { "hasGlass", WALL_SCENERY_HAS_GLASS, Json::FlagType::Normal }, + { "isBanner", WALL_SCENERY_IS_BANNER, Json::FlagType::Normal }, + { "isDoor", WALL_SCENERY_IS_DOOR, Json::FlagType::Normal }, + { "isLongDoorAnimation", WALL_SCENERY_LONG_DOOR_ANIMATION, Json::FlagType::Normal }, + }); + // clang-format on + + _legacyType.wall.flags2 = Json::GetFlags( + properties, + { + { "isOpaque", WALL_SCENERY_2_IS_OPAQUE }, + { "isAnimated", WALL_SCENERY_2_ANIMATED }, + }); + + // HACK WALL_SCENERY_HAS_PRIMARY_COLOUR actually means, has any colour but we simplify the + // JSON and handle this on load. We should change code base in future to reflect the JSON. + if (!(_legacyType.wall.flags & WALL_SCENERY_HAS_PRIMARY_COLOUR)) { - _legacyType.wall.flags |= WALL_SCENERY_HAS_PRIMARY_COLOUR; - _legacyType.wall.flags2 |= WALL_SCENERY_2_NO_SELECT_PRIMARY_COLOUR; + if (_legacyType.wall.flags & (WALL_SCENERY_HAS_SECONDARY_COLOUR | WALL_SCENERY_HAS_TERNARY_COLOUR)) + { + _legacyType.wall.flags |= WALL_SCENERY_HAS_PRIMARY_COLOUR; + _legacyType.wall.flags2 |= WALL_SCENERY_2_NO_SELECT_PRIMARY_COLOUR; + } + } + + // Door sound + auto jDoorSound = properties["doorSound"]; + if (jDoorSound.is_number()) + { + auto doorSound = Json::GetNumber(jDoorSound); + _legacyType.wall.flags2 |= (doorSound << WALL_SCENERY_2_DOOR_SOUND_SHIFT) & WALL_SCENERY_2_DOOR_SOUND_MASK; } } - // Door sound - auto jDoorSound = json_object_get(properties, "doorSound"); - if (jDoorSound != nullptr) - { - auto doorSound = json_integer_value(jDoorSound); - _legacyType.wall.flags2 |= (doorSound << WALL_SCENERY_2_DOOR_SOUND_SHIFT) & WALL_SCENERY_2_DOOR_SOUND_MASK; - } - - ObjectJsonHelpers::LoadStrings(root, GetStringTable()); - ObjectJsonHelpers::LoadImages(context, root, GetImageTable()); + PopulateTablesFromJson(context, root); } diff --git a/src/openrct2/object/WallObject.h b/src/openrct2/object/WallObject.h index 403ecd81ca..82b89ef7fa 100644 --- a/src/openrct2/object/WallObject.h +++ b/src/openrct2/object/WallObject.h @@ -29,7 +29,7 @@ public: } void ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) override; - void ReadJson(IReadObjectContext* context, const json_t* root) override; + void ReadJson(IReadObjectContext* context, json_t& root) override; void Load() override; void Unload() override; diff --git a/src/openrct2/object/WaterObject.cpp b/src/openrct2/object/WaterObject.cpp index 9925372db0..69df434eb9 100644 --- a/src/openrct2/object/WaterObject.cpp +++ b/src/openrct2/object/WaterObject.cpp @@ -16,7 +16,6 @@ #include "../localisation/Language.h" #include "../localisation/StringIds.h" #include "../world/Location.hpp" -#include "ObjectJsonHelpers.h" #include @@ -55,52 +54,57 @@ void WaterObject::DrawPreview(rct_drawpixelinfo* dpi, int32_t width, int32_t hei gfx_draw_string_centred(dpi, STR_WINDOW_NO_IMAGE, screenCoords, COLOUR_BLACK, nullptr); } -void WaterObject::ReadJson([[maybe_unused]] IReadObjectContext* context, const json_t* root) +void WaterObject::ReadJson([[maybe_unused]] IReadObjectContext* context, json_t& root) { - auto properties = json_object_get(root, "properties"); - _legacyType.flags = ObjectJsonHelpers::GetFlags( - properties, - { - { "allowDucks", WATER_FLAGS_ALLOW_DUCKS }, - }); + Guard::Assert(root.is_object(), "WaterObject::ReadJson expects parameter root to be object"); - ObjectJsonHelpers::LoadStrings(root, GetStringTable()); + auto properties = root["properties"]; - // Images which are actually palette data - static const char* paletteNames[] = { - "general", "waves-0", "waves-1", "waves-2", "sparkles-0", "sparkles-1", "sparkles-2", - }; - for (auto paletteName : paletteNames) + PopulateTablesFromJson(context, root); + + if (properties.is_object()) { - auto jPalettes = json_object_get(properties, "palettes"); - if (jPalettes != nullptr) - { - auto jPalette = json_object_get(jPalettes, paletteName); - if (jPalette != nullptr) + _legacyType.flags = Json::GetFlags( + properties, { - ReadJsonPalette(jPalette); + { "allowDucks", WATER_FLAGS_ALLOW_DUCKS }, + }); + + auto jPalettes = properties["palettes"]; + if (jPalettes.is_object()) + { + // Images which are actually palette data + static const char* paletteNames[] = { + "general", "waves-0", "waves-1", "waves-2", "sparkles-0", "sparkles-1", "sparkles-2", + }; + for (auto paletteName : paletteNames) + { + auto jPalette = jPalettes[paletteName]; + if (jPalette.is_object()) + { + ReadJsonPalette(jPalette); + } } } } } -void WaterObject::ReadJsonPalette(const json_t* jPalette) +void WaterObject::ReadJsonPalette(json_t& jPalette) { - auto paletteStartIndex = json_integer_value(json_object_get(jPalette, "index")); - auto jColours = json_object_get(jPalette, "colours"); - auto numColours = json_array_size(jColours); + Guard::Assert(jPalette.is_object(), "WaterObject::ReadJsonPalette expects parameter jPalette to be object"); + auto jColours = jPalette["colours"]; + auto numColours = jColours.size(); + + // This pointer gets memcopied in ImageTable::AddImage so it's fine for the unique_ptr to go out of scope auto data = std::make_unique(numColours * 3); size_t dataIndex = 0; - size_t index; - const json_t* jColour; - json_array_foreach(jColours, index, jColour) + for (auto& jColour : jColours) { - auto szColour = json_string_value(jColour); - if (szColour != nullptr) + if (jColour.is_string()) { - auto colour = ParseColour(szColour); + auto colour = ParseColour(Json::GetString(jColour)); data[dataIndex + 0] = (colour >> 16) & 0xFF; data[dataIndex + 1] = (colour >> 8) & 0xFF; data[dataIndex + 2] = colour & 0xFF; @@ -111,7 +115,7 @@ void WaterObject::ReadJsonPalette(const json_t* jPalette) rct_g1_element g1 = {}; g1.offset = data.get(); g1.width = static_cast(numColours); - g1.x_offset = static_cast(paletteStartIndex); + g1.x_offset = Json::GetNumber(jPalette["index"]); g1.flags = G1_FLAG_PALETTE; auto& imageTable = GetImageTable(); diff --git a/src/openrct2/object/WaterObject.h b/src/openrct2/object/WaterObject.h index b7dcfad231..bac5e274b5 100644 --- a/src/openrct2/object/WaterObject.h +++ b/src/openrct2/object/WaterObject.h @@ -30,7 +30,7 @@ public: return &_legacyType; } - void ReadJson(IReadObjectContext* context, const json_t* root) override; + void ReadJson(IReadObjectContext* context, json_t& root) override; void ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) override; void Load() override; void Unload() override; @@ -38,6 +38,6 @@ public: void DrawPreview(rct_drawpixelinfo* dpi, int32_t width, int32_t height) const override; private: - void ReadJsonPalette(const json_t* jPalette); + void ReadJsonPalette(json_t& jPalette); uint32_t ParseColour(const std::string& s) const; };