1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-06 06:32:56 +01:00

Merge pull request #12667 from mwnciau/json-refactor

Migrate JSON library to JSON for Modern C++
This commit is contained in:
Michael Steenbeek
2020-09-17 21:11:06 +02:00
committed by GitHub
70 changed files with 1961 additions and 1906 deletions

View File

@@ -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

View File

@@ -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})

View File

@@ -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 = "<group>"; };
4CE462481FD1613D0001CD98 /* Platform.Posix.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Platform.Posix.cpp; sourceTree = "<group>"; };
4CE462491FD1613D0001CD98 /* Platform.Win32.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Platform.Win32.cpp; sourceTree = "<group>"; };
4CE9AAAB1FDA7B14004093C6 /* ObjectJsonHelpers.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ObjectJsonHelpers.cpp; sourceTree = "<group>"; };
4CE9AAAC1FDA7B14004093C6 /* ObjectJsonHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ObjectJsonHelpers.h; sourceTree = "<group>"; };
4CF67196206B7E720034ADDD /* object */ = {isa = PBXFileReference; lastKnownFileType = folder; name = object; path = data/object; sourceTree = "<group>"; };
4CFE4E7B1F90A3F1005243C2 /* Peep.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Peep.cpp; sourceTree = "<group>"; };
4CFE4E7C1F90A3F1005243C2 /* Peep.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Peep.h; sourceTree = "<group>"; };
@@ -1368,6 +1361,8 @@
93F76EFC20BFF77A00D4512C /* Paint.Banner.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Paint.Banner.cpp; sourceTree = "<group>"; };
93F76EFD20BFF77A00D4512C /* Paint.TileElement.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Paint.TileElement.cpp; sourceTree = "<group>"; };
93F76EFE20BFF77A00D4512C /* Paint.Entrance.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Paint.Entrance.cpp; sourceTree = "<group>"; };
93FB271E24ED32B7008241C9 /* json.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = json.hpp; sourceTree = "<group>"; };
93FB272024ED3601008241C9 /* Cursors.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Cursors.cpp; sourceTree = "<group>"; };
93FC08FD2418F3ED00CA3054 /* duktape.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = duktape.h; sourceTree = "<group>"; };
93FC08FE2418F3ED00CA3054 /* duk_config.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = duk_config.h; sourceTree = "<group>"; };
93FC09012418F3F500CA3054 /* libduktape.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libduktape.dylib; sourceTree = "<group>"; };
@@ -1478,11 +1473,8 @@
D43BAB921F8C2B2B00A9E362 /* OpenGLAPIProc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OpenGLAPIProc.h; sourceTree = "<group>"; };
D45A38B31CF3006400659A24 /* libcrypto.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libcrypto.dylib; sourceTree = "<group>"; };
D45A38B41CF3006400659A24 /* libfreetype.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libfreetype.dylib; sourceTree = "<group>"; };
D45A38B51CF3006400659A24 /* libjansson.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libjansson.dylib; sourceTree = "<group>"; };
D45A38B81CF3006400659A24 /* libSDL2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libSDL2.dylib; sourceTree = "<group>"; };
D45A38B91CF3006400659A24 /* libspeexdsp.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libspeexdsp.dylib; sourceTree = "<group>"; };
D45A38C41CF3007A00659A24 /* jansson_config.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = jansson_config.h; sourceTree = "<group>"; };
D45A38C51CF3007A00659A24 /* jansson.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = jansson.h; sourceTree = "<group>"; };
D45A38C71CF3007A00659A24 /* png.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = png.h; sourceTree = "<group>"; };
D45A38C81CF3007A00659A24 /* pngconf.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pngconf.h; sourceTree = "<group>"; };
D45A38C91CF3007A00659A24 /* pnglibconf.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pnglibconf.h; sourceTree = "<group>"; };
@@ -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 = "<group>";
};
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 */,

2
debian/control vendored
View File

@@ -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

View File

@@ -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.

View File

@@ -76,7 +76,7 @@
</ClCompile>
<Link>
<GenerateDebugInformation>DebugFull</GenerateDebugInformation>
<AdditionalDependencies>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)</AdditionalDependencies>
<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)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
@@ -94,7 +94,7 @@
<GenerateDebugInformation>DebugFull</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>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)</AdditionalDependencies>
<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)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>

View File

@@ -37,10 +37,10 @@
<!-- 3rd party libraries / dependencies -->
<PropertyGroup>
<DependenciesCheckFile>$(RootDir).dependencies</DependenciesCheckFile>
<LibsUrl Condition="'$(Platform)'=='Win32'">https://github.com/OpenRCT2/Dependencies/releases/download/v24/openrct2-libs-v21-x86-windows-static.zip</LibsUrl>
<LibsSha1 Condition="'$(Platform)'=='Win32'">21eef7db74fd1c886f3a1ef3f7989721e42a726b</LibsSha1>
<LibsUrl Condition="'$(Platform)'=='x64'">https://github.com/OpenRCT2/Dependencies/releases/download/v24/openrct2-libs-v21-x64-windows-static.zip</LibsUrl>
<LibsSha1 Condition="'$(Platform)'=='x64'">6367d76f6b95859f8b45cbf03b222b8b5200d8b5</LibsSha1>
<LibsUrl Condition="'$(Platform)'=='Win32'">https://github.com/OpenRCT2/Dependencies/releases/download/v25/openrct2-libs-v25-x86-windows-static.zip</LibsUrl>
<LibsSha1 Condition="'$(Platform)'=='Win32'">c8c1a3052721c7dedde8c3cab73ed09b793a9b8e</LibsSha1>
<LibsUrl Condition="'$(Platform)'=='x64'">https://github.com/OpenRCT2/Dependencies/releases/download/v25/openrct2-libs-v25-x64-windows-static.zip</LibsUrl>
<LibsSha1 Condition="'$(Platform)'=='x64'">5384dffd21b4e87c36bb91c6882e277023285612</LibsSha1>
<GtestVersion>2fe3bd994b3189899d93f1d5a881e725e046fdc2</GtestVersion>
<GtestUrl>https://github.com/google/googletest/archive/$(GtestVersion).zip</GtestUrl>
<GtestSha1>058b9df80244c03f1633cb06e9f70471a29ebb8e</GtestSha1>

View File

@@ -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:

View File

@@ -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.

View File

@@ -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/../")

View File

@@ -15,7 +15,6 @@ public class GameActivity extends SDLActivity {
return new String[]{
"c++_shared",
"speexdsp",
"jansson",
"png16",
"SDL2-2.0",

View File

@@ -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 ()

View File

@@ -14,7 +14,6 @@
#include "Window.h"
#include <algorithm>
#include <jansson.h>
#include <memory>
#include <openrct2/Context.h>
#include <openrct2/PlatformEnvironment.h>
@@ -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<uint8_t>(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<size_t>(wtDesc->NumColours));
for (size_t i = 0; i < colourCount; i++)
{
result.Theme.Colours[i] = static_cast<colour_t>(json_integer_value(json_array_get(jsonColours, i)));
result.Theme.Colours[i] = Json::GetNumber<colour_t>(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<uint8_t>(
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;
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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 <cmath>
#include <cstring>
#include <jansson.h>
#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<unsigned long>(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<unsigned long>(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<int16_t>(x_offset), Json::GetNumber<int16_t>(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");

View File

@@ -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();

View File

@@ -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<size_t>(fs.GetLength());
@@ -26,42 +25,101 @@ namespace Json
throw IOException("Json file too large.");
}
utf8* fileData = Memory::Allocate<utf8>(fileLength + 1);
fs.Read(fileData, fileLength);
fileData[fileLength] = '\0';
auto fileData = std::string(static_cast<size_t>(fileLength) + 1, '\0');
fs.Read(static_cast<void*>(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<uint8_t>& 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<std::string>() : defaultValue;
}
bool GetBoolean(const json_t& jsonObj, bool defaultValue)
{
return jsonObj.is_boolean() ? jsonObj.get<bool>() : 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

View File

@@ -11,36 +11,181 @@
#include "../common.h"
#include <jansson.h>
#include <stdexcept>
#include <nlohmann/json.hpp>
#include <string>
#include <string_view>
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<uint8_t>& 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<typename T> T GetNumber(const json_t& jsonObj, T defaultValue = 0)
{
static_assert(std::is_arithmetic<T>::value, "GetNumber template parameter must be arithmetic");
return jsonObj.is_number() ? jsonObj.get<T>() : 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<typename T> T GetEnum(const json_t& jsonObj, T defaultValue)
{
static_assert(std::is_enum<T>::value, "GetEnum template parameter must be an enum");
return jsonObj.is_number_integer() ? jsonObj.get<T>() : 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<typename T> T GetFlags(const json_t& jsonObj, std::initializer_list<std::pair<std::string, T>> list)
{
static_assert(std::is_convertible<T, int>::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<T>(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<typename T> T GetFlags(const json_t& jsonObj, std::initializer_list<std::tuple<std::string, T, FlagType>> list)
{
static_assert(std::is_convertible<T, int>::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<T>(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<T>(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;
}
};

View File

@@ -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<std::string_view, colour_t> 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 };

View File

@@ -12,6 +12,8 @@
#include "../common.h"
#include <string_view>
/**
* 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

View File

@@ -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 <string>
#include <unordered_map>
namespace Cursor
{
uint8_t FromString(const std::string& s, uint8_t defaultValue)
{
static const std::unordered_map<std::string, uint8_t> 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

View File

@@ -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;

View File

@@ -251,7 +251,6 @@
<ClInclude Include="object\LargeSceneryObject.h" />
<ClInclude Include="object\Object.h" />
<ClInclude Include="object\ObjectFactory.h" />
<ClInclude Include="object\ObjectJsonHelpers.h" />
<ClInclude Include="object\ObjectLimits.h" />
<ClInclude Include="object\ObjectList.h" />
<ClInclude Include="object\ObjectManager.h" />
@@ -527,6 +526,7 @@
<ClCompile Include="Input.cpp" />
<ClCompile Include="interface\Chat.cpp" />
<ClCompile Include="interface\Colour.cpp" />
<ClCompile Include="interface\Cursors.cpp" />
<ClCompile Include="interface\FontFamilies.cpp" />
<ClCompile Include="interface\Fonts.cpp" />
<ClCompile Include="interface\InteractiveConsole.cpp" />
@@ -575,7 +575,6 @@
<ClCompile Include="object\LargeSceneryObject.cpp" />
<ClCompile Include="object\Object.cpp" />
<ClCompile Include="object\ObjectFactory.cpp" />
<ClCompile Include="object\ObjectJsonHelpers.cpp" />
<ClCompile Include="object\ObjectList.cpp" />
<ClCompile Include="object\ObjectManager.cpp" />
<ClCompile Include="object\ObjectRepository.cpp" />

View File

@@ -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>(NetworkGroup::FromJson(jsonGroup));
group_list.push_back(std::move(newgroup));
for (auto& jsonGroup : jsonGroups)
{
group_list.emplace_back(std::make_unique<NetworkGroup>(NetworkGroup::FromJson(jsonGroup)));
}
}
json_t* jsonDefaultGroup = json_object_get(json, "default_group");
default_group = static_cast<uint8_t>(json_integer_value(jsonDefaultGroup));
default_group = Json::GetNumber<uint8_t>(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 */

View File

@@ -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);

View File

@@ -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<uint8_t>(json_integer_value(jsonId));
group._name = std::string(json_string_value(jsonName));
group.Id = Json::GetNumber<uint8_t>(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<NetworkPermission>(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;
}

View File

@@ -10,10 +10,10 @@
#pragma once
#include "../common.h"
#include "../core/Json.hpp"
#include "NetworkPacket.h"
#include <array>
#include <jansson.h>
#include <string>
enum class NetworkPermission : uint32_t;
@@ -24,7 +24,14 @@ public:
std::array<uint8_t, 8> 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;

View File

@@ -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<int32_t>(jsonRoot["status"]);
if (status == MASTER_SERVER_STATUS_OK)
{
int32_t status = static_cast<int32_t>(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<int32_t>(jsonRoot["status"]);
if (status == MASTER_SERVER_STATUS_OK)
{
int32_t status = static_cast<int32_t>(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;
}

View File

@@ -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<uint8_t>(json_integer_value(jsonGroupId));
user->GroupId = Json::GetNumber<uint8_t>(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<std::string> 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)

View File

@@ -10,9 +10,9 @@
#pragma once
#include "../common.h"
#include "../core/Json.hpp"
#include "../core/Nullable.hpp"
#include <jansson.h>
#include <map>
#include <string>
@@ -24,10 +24,20 @@ public:
Nullable<uint8_t> 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

View File

@@ -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> ServerListEntry::FromJson(const json_t* server)
std::optional<ServerListEntry> 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<int32_t>(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<uint8_t>(server["players"]);
const auto maxPlayers = Json::GetNumber<uint8_t>(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<int32_t>(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<uint8_t>(json_integer_value(players));
entry.MaxPlayers = static_cast<uint8_t>(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<std::vector<ServerListEntry>> 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<std::vector<ServerListEntry>> 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<std::vector<ServerListEntry>> 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<int32_t>(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<ServerListEntry> 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<int32_t>(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<ServerListEntry> 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

View File

@@ -10,6 +10,7 @@
#pragma once
#include "../common.h"
#include "../core/Json.hpp"
#include <future>
#include <optional>
@@ -17,7 +18,6 @@
#include <string>
#include <vector>
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<ServerListEntry> 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<ServerListEntry> FromJson(json_t& server);
};
class ServerList

View File

@@ -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 <string>
#include <vector>
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();

View File

@@ -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<uint8_t>(
properties,
{
{ "hasPrimaryColour", BANNER_ENTRY_FLAG_HAS_PRIMARY_COLOUR },
});
if (properties.is_object())
{
_legacyType.banner.scrolling_mode = Json::GetNumber<uint8_t>(properties["scrollingMode"]);
_legacyType.banner.price = Json::GetNumber<int16_t>(properties["price"]);
_legacyType.banner.flags = Json::GetFlags<uint8_t>(
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);
}

View File

@@ -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;

View File

@@ -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<uint8_t>(properties["scrollingMode"]);
_legacyType.text_height = Json::GetNumber<uint8_t>(properties["textHeight"]);
}
PopulateTablesFromJson(context, root);
}

View File

@@ -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;

View File

@@ -15,7 +15,6 @@
#include "../localisation/Localisation.h"
#include "../object/Object.h"
#include "../object/ObjectRepository.h"
#include "ObjectJsonHelpers.h"
#include "ObjectList.h"
#include <unordered_map>
@@ -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<uint16_t>(
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<int16_t>(properties["price"]);
SetPrimarySceneryGroup(Json::GetString(properties["sceneryGroup"]));
// clang-format off
_legacyType.path_bit.flags = Json::GetFlags<uint16_t>(
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);
}

View File

@@ -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;

View File

@@ -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<uint8_t>(
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<uint8_t>(properties["scrollingMode"]);
_legacyType.flags = Json::GetFlags<uint8_t>(
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);
}

View File

@@ -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;

View File

@@ -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 <algorithm>
#include <memory>
#include <stdexcept>
using namespace OpenRCT2;
using namespace OpenRCT2::Drawing;
struct ImageTable::RequiredImage
{
rct_g1_element g1{};
std::unique_ptr<RequiredImage> 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<const rct_g1_element*(uint32_t)> 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<RequiredImage>(static_cast<uint32_t>(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<std::unique_ptr<ImageTable::RequiredImage>> ImageTable::ParseImages(IReadObjectContext* context, std::string s)
{
std::vector<std::unique_ptr<RequiredImage>> result;
if (s.empty())
{
result.push_back(std::make_unique<RequiredImage>());
}
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<RequiredImage>(
static_cast<uint32_t>(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<RequiredImage>(
static_cast<uint32_t>(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<RequiredImage>(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<RequiredImage>());
}
}
return result;
}
std::vector<std::unique_ptr<ImageTable::RequiredImage>> 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<int16_t>(el["x"]);
auto y = Json::GetNumber<int16_t>(el["y"]);
auto raw = Json::GetString(el["format"]) == "raw";
std::vector<std::unique_ptr<RequiredImage>> result;
try
{
auto flags = ImageImporter::IMPORT_FLAGS::NONE;
if (!raw)
{
flags = static_cast<ImageImporter::IMPORT_FLAGS>(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<RequiredImage>(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<RequiredImage>());
}
return result;
}
std::vector<std::unique_ptr<ImageTable::RequiredImage>> ImageTable::LoadObjectImages(
IReadObjectContext* context, const std::string& name, const std::vector<int32_t>& range)
{
std::vector<std::unique_ptr<RequiredImage>> result;
auto objectPath = FindLegacyObject(name);
auto obj = ObjectFactory::CreateObjectFromLegacyFile(context->GetObjectRepository(), objectPath.c_str());
if (obj != nullptr)
{
auto& imgTable = static_cast<const Object*>(obj)->GetImageTable();
auto numImages = static_cast<int32_t>(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<RequiredImage>(
static_cast<uint32_t>(i), [images](uint32_t idx) -> const rct_g1_element* { return &images[idx]; }));
}
else
{
result.push_back(std::make_unique<RequiredImage>());
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<RequiredImage>());
}
}
return result;
}
std::vector<int32_t> ImageTable::ParseRange(std::string s)
{
// Currently only supports [###] or [###..###]
std::vector<int32_t> 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<IFileScanner>(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<std::unique_ptr<RequiredImage>> allImages;
auto jsonImages = root["images"];
for (auto& jsonImage : jsonImages)
{
if (jsonImage.is_string())
{
auto strImage = jsonImage.get<std::string>();
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<rct_g1_element*>(&GetImages()[tableIndex]);
g1a->zoomed_offset = static_cast<int32_t>(tableIndex) - static_cast<int32_t>(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;

View File

@@ -10,6 +10,7 @@
#pragma once
#include "../common.h"
#include "../core/Json.hpp"
#include "../drawing/Drawing.h"
#include <memory>
@@ -27,6 +28,20 @@ private:
std::unique_ptr<uint8_t[]> _data;
std::vector<rct_g1_element> _entries;
/**
* Container for a G1 image, additional information and RAII. Used by ReadJson
*/
struct RequiredImage;
static std::vector<std::unique_ptr<ImageTable::RequiredImage>> ParseImages(IReadObjectContext* context, std::string s);
/**
* @note root is deliberately left non-const: json_t behaviour changes when const
*/
static std::vector<std::unique_ptr<ImageTable::RequiredImage>> ParseImages(IReadObjectContext* context, json_t& el);
static std::vector<std::unique_ptr<ImageTable::RequiredImage>> LoadObjectImages(
IReadObjectContext* context, const std::string& name, const std::vector<int32_t>& range);
static std::vector<int32_t> 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();

View File

@@ -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 <algorithm>
#include <iterator>
@@ -121,84 +121,83 @@ std::vector<rct_large_scenery_tile> 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<uint8_t>(
properties,
_legacyType.large_scenery.price = Json::GetNumber<int16_t>(properties["price"]);
_legacyType.large_scenery.removal_price = Json::GetNumber<int16_t>(properties["removalPrice"]);
_legacyType.large_scenery.scrolling_mode = Json::GetNumber<uint8_t>(properties["scrollingMode"], SCROLLING_MODE_NONE);
_legacyType.large_scenery.flags = Json::GetFlags<uint8_t>(
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<rct_large_scenery_tile> LargeSceneryObject::ReadJsonTiles(const json_t* jTiles)
std::vector<rct_large_scenery_tile> LargeSceneryObject::ReadJsonTiles(json_t& jTiles)
{
std::vector<rct_large_scenery_tile> 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<int16_t>(jTile["x"]);
tile.y_offset = Json::GetNumber<int16_t>(jTile["y"]);
tile.z_offset = Json::GetNumber<int16_t>(jTile["z"]);
tile.z_clearance = Json::GetNumber<int8_t>(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<uint16_t>(
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<uint16_t>(jTile["corners"], 0xF);
tile.flags |= (corners & 0xFF) << 12;
auto walls = Json::GetNumber<int16_t>(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<rct_large_scenery_tile> LargeSceneryObject::ReadJsonTiles(const json
return tiles;
}
std::unique_ptr<rct_large_scenery_text> LargeSceneryObject::ReadJson3dFont(const json_t* j3dFont)
std::unique_ptr<rct_large_scenery_text> LargeSceneryObject::ReadJson3dFont(json_t& j3dFont)
{
Guard::Assert(j3dFont.is_object(), "LargeSceneryObject::ReadJson3dFont expects parameter j3dFont to be object");
auto font = std::make_unique<rct_large_scenery_text>();
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<uint8_t>(
font->max_width = Json::GetNumber<uint16_t>(j3dFont["maxWidth"]);
font->num_images = Json::GetNumber<uint8_t>(j3dFont["numImages"]);
font->flags = Json::GetFlags<uint8_t>(
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<rct_large_scenery_text> LargeSceneryObject::ReadJson3dFont(const
return font;
}
std::vector<LocationXY16> LargeSceneryObject::ReadJsonOffsets(const json_t* jOffsets)
std::vector<LocationXY16> LargeSceneryObject::ReadJsonOffsets(json_t& jOffsets)
{
std::vector<LocationXY16> 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<int16_t>(jOffset["x"]);
offset.y = Json::GetNumber<int16_t>(jOffset["y"]);
offsets.push_back(offset);
}
}
return offsets;
}
std::vector<rct_large_scenery_text_glyph> LargeSceneryObject::ReadJsonGlyphs(const json_t* jGlpyhs)
std::vector<rct_large_scenery_text_glyph> LargeSceneryObject::ReadJsonGlyphs(json_t& jGlyphs)
{
std::vector<rct_large_scenery_text_glyph> 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<uint8_t>(jGlyph["image"]);
glyph.width = Json::GetNumber<uint8_t>(jGlyph["width"]);
glyph.height = Json::GetNumber<uint8_t>(jGlyph["height"]);
glyphs.push_back(glyph);
}
}
return glyphs;
}

View File

@@ -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<rct_large_scenery_tile> ReadTiles(OpenRCT2::IStream* stream);
static std::vector<rct_large_scenery_tile> ReadJsonTiles(const json_t* jTiles);
static std::unique_ptr<rct_large_scenery_text> ReadJson3dFont(const json_t* j3dFont);
static std::vector<LocationXY16> ReadJsonOffsets(const json_t* jOffsets);
static std::vector<rct_large_scenery_text_glyph> ReadJsonGlyphs(const json_t* jGlpyhs);
static std::vector<rct_large_scenery_tile> ReadJsonTiles(json_t& jTiles);
static std::unique_ptr<rct_large_scenery_text> ReadJson3dFont(json_t& j3dFont);
static std::vector<LocationXY16> ReadJsonOffsets(json_t& jOffsets);
static std::vector<rct_large_scenery_text_glyph> ReadJsonGlyphs(json_t& jGlyphs);
};

View File

@@ -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<size_t>(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();

View File

@@ -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);

View File

@@ -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<const char*>(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<size_t>(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<size_t>(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<std::string> 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<uint8_t> sourceGameVector;
for (const auto& jSourceGame : sourceGames)
{
std::vector<std::string> 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<uint8_t> 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;

View File

@@ -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 <algorithm>
#include <cstdlib>
#include <cstring>
#include <unordered_map>
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<RequiredImage> 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<const rct_g1_element*(uint32_t)> 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<RequiredImage>(static_cast<uint32_t>(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<int32_t>::min() && val <= std::numeric_limits<int32_t>::max())
{
return static_cast<int32_t>(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<std::string> GetJsonStringArray(const json_t* arr)
{
std::vector<std::string> 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<int32_t> GetJsonIntegerArray(const json_t* arr)
{
std::vector<int32_t> 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<std::string_view, colour_t> 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<std::string, uint8_t> 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<size_t>(8, s.size());
std::copy_n(s.c_str(), copyLen, entry.name);
return entry;
}
static std::vector<int32_t> ParseRange(std::string s)
{
// Currently only supports [###] or [###..###]
std::vector<int32_t> 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<IFileScanner>(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<std::unique_ptr<RequiredImage>> LoadObjectImages(
IReadObjectContext* context, const std::string& name, const std::vector<int32_t>& range)
{
std::vector<std::unique_ptr<RequiredImage>> result;
auto objectPath = FindLegacyObject(name);
auto obj = ObjectFactory::CreateObjectFromLegacyFile(context->GetObjectRepository(), objectPath.c_str());
if (obj != nullptr)
{
auto& imgTable = static_cast<const Object*>(obj)->GetImageTable();
auto numImages = static_cast<int32_t>(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<RequiredImage>(
static_cast<uint32_t>(i), [images](uint32_t idx) -> const rct_g1_element* { return &images[idx]; }));
}
else
{
result.push_back(std::make_unique<RequiredImage>());
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<RequiredImage>());
}
}
return result;
}
static std::vector<std::unique_ptr<RequiredImage>> ParseImages(IReadObjectContext* context, std::string s)
{
std::vector<std::unique_ptr<RequiredImage>> result;
if (s.empty())
{
result.push_back(std::make_unique<RequiredImage>());
}
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<RequiredImage>(
static_cast<uint32_t>(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<RequiredImage>(static_cast<uint32_t>(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<RequiredImage>(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<RequiredImage>());
}
}
return result;
}
static std::vector<std::unique_ptr<RequiredImage>> 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<std::unique_ptr<RequiredImage>> result;
try
{
auto flags = ImageImporter::IMPORT_FLAGS::NONE;
if (!raw)
{
flags = static_cast<ImageImporter::IMPORT_FLAGS>(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<RequiredImage>(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<RequiredImage>());
}
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<std::unique_ptr<RequiredImage>> 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<rct_g1_element*>(&imageTable.GetImages()[tableIndex]);
g1a->zoomed_offset = static_cast<int32_t>(tableIndex) - static_cast<int32_t>(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

View File

@@ -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 <initializer_list>
#include <string>
#include <utility>
#include <vector>
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<std::string> GetJsonStringArray(const json_t* arr);
std::vector<int32_t> 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<typename T> static T GetFlags(const json_t* obj, std::initializer_list<std::pair<std::string, T>> list)
{
T flags{};
for (const auto& item : list)
{
if (GetBoolean(obj, item.first))
{
flags = static_cast<T>(flags | item.second);
}
}
return flags;
}
} // namespace ObjectJsonHelpers

View File

@@ -23,7 +23,6 @@
#include "../ride/RideData.h"
#include "../ride/ShopItem.h"
#include "../ride/Track.h"
#include "ObjectJsonHelpers.h"
#include "ObjectRepository.h"
#include <algorithm>
@@ -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<uint8_t>(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<size_t>(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<int32_t>(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<int32_t>(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<int8_t>(ratingMultiplier["excitement"]);
_legacyType.intensity_multiplier = Json::GetNumber<int8_t>(ratingMultiplier["intensity"]);
_legacyType.nausea_multiplier = Json::GetNumber<int8_t>(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<size_t>(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<uint8_t>(properties["buildMenuPriority"]);
_legacyType.flags |= Json::GetFlags<uint32_t>(
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<uint32_t>(
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<uint8_t>(properties["minCarsPerTrain"], 1);
_legacyType.max_cars_in_train = Json::GetNumber<uint8_t>(properties["maxCarsPerTrain"], 1);
_legacyType.cars_per_flat_ride = Json::GetNumber<uint8_t>(properties["carsPerFlatRide"], 255);
_legacyType.zero_cars = Json::GetNumber<uint8_t>(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<uint8_t>(properties["defaultCar"]);
_legacyType.tab_vehicle = Json::GetNumber<uint8_t>(properties["tabCar"]);
float tabScale = Json::GetNumber<float>(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<uint8_t>(headCars[0], 0xFF);
_legacyType.second_vehicle = Json::GetNumber<uint8_t>(headCars[1], 0xFF);
_legacyType.third_vehicle = Json::GetNumber<uint8_t>(headCars[2], 0xFF);
_legacyType.rear_vehicle = Json::GetNumber<uint8_t>(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<rct_ride_entry_vehicle> RideObject::ReadJsonCars(const json_t* jCars)
std::vector<rct_ride_entry_vehicle> RideObject::ReadJsonCars(json_t& jCars)
{
std::vector<rct_ride_entry_vehicle> 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<uint16_t>(jCar["rotationFrameMask"]);
car.spacing = Json::GetNumber<uint32_t>(jCar["spacing"]);
car.car_mass = Json::GetNumber<uint16_t>(jCar["mass"]);
car.tab_height = Json::GetNumber<int8_t>(jCar["tabOffset"]);
car.num_seats = Json::GetNumber<uint8_t>(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<SoundId>(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<uint8_t>(jCar["spriteWidth"]);
car.sprite_height_negative = Json::GetNumber<uint8_t>(jCar["spriteHeightNegative"]);
car.sprite_height_positive = Json::GetNumber<uint8_t>(jCar["spriteHeightPositive"]);
car.animation = Json::GetNumber<uint8_t>(jCar["animation"]);
car.base_num_frames = Json::GetNumber<uint16_t>(jCar["baseNumFrames"]);
car.no_vehicle_images = Json::GetNumber<uint32_t>(jCar["numImages"]);
car.no_seating_rows = Json::GetNumber<uint8_t>(jCar["numSeatRows"]);
car.spinning_inertia = Json::GetNumber<uint8_t>(jCar["spinningInertia"]);
car.spinning_friction = Json::GetNumber<uint8_t>(jCar["spinningFriction"]);
car.friction_sound_id = Json::GetEnum<SoundId>(jCar["frictionSoundId"], SoundId::Null);
car.log_flume_reverser_vehicle_type = Json::GetNumber<uint8_t>(jCar["logFlumeReverserVehicleType"]);
car.sound_range = Json::GetNumber<uint8_t>(jCar["soundRange"], 255);
car.double_sound_frequency = Json::GetNumber<uint8_t>(jCar["doubleSoundFrequency"]);
car.powered_acceleration = Json::GetNumber<uint8_t>(jCar["poweredAcceleration"]);
car.powered_max_speed = Json::GetNumber<uint8_t>(jCar["poweredMaxSpeed"]);
car.car_visual = Json::GetNumber<uint8_t>(jCar["carVisual"]);
car.effect_visual = Json::GetNumber<uint8_t>(jCar["effectVisual"], 1);
car.draw_order = Json::GetNumber<uint8_t>(jCar["drawOrder"]);
car.num_vertical_frames_override = Json::GetNumber<uint8_t>(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<int8_t>(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<uint8_t>(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<CoordsXY, 3> 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<int32_t>(jWaypoint[0]);
int32_t y = Json::GetNumber<int32_t>(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<uint16_t>(
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<uint16_t>(
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<uint32_t>(
car.flags |= Json::GetFlags<uint32_t>(
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<vehicle_colour> RideObject::ReadJsonColourConfiguration(const json_t* jColourConfig)
std::vector<vehicle_colour> RideObject::ReadJsonColourConfiguration(json_t& jColourConfig)
{
std::vector<vehicle_colour> 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);

View File

@@ -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<rct_ride_entry_vehicle> 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<vehicle_colour> ReadJsonColourConfiguration(const json_t* jColourConfig);
void ReadJsonVehicleInfo(IReadObjectContext* context, json_t& properties);
std::vector<rct_ride_entry_vehicle> ReadJsonCars(json_t& jCars);
rct_ride_entry_vehicle ReadJsonCar(json_t& jCar);
vehicle_colour_preset_list ReadJsonCarColours(json_t& jCarColours);
std::vector<vehicle_colour> ReadJsonColourConfiguration(json_t& jColourConfig);
static uint8_t CalculateNumVerticalFrames(const rct_ride_entry_vehicle* vehicleEntry);
static uint8_t CalculateNumHorizontalFrames(const rct_ride_entry_vehicle* vehicleEntry);

View File

@@ -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<rct_object_entry> 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<uint8_t>(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<uint8_t>(peepSprite));
}
@@ -172,19 +165,14 @@ EntertainerCostume SceneryGroupObject::ParseEntertainerCostume(const std::string
return EntertainerCostume::Panda;
}
std::vector<rct_object_entry> SceneryGroupObject::ReadJsonEntries(const json_t* jEntries)
std::vector<rct_object_entry> SceneryGroupObject::ReadJsonEntries(json_t& jEntries)
{
std::vector<rct_object_entry> 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;
}

View File

@@ -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<rct_object_entry> 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<rct_object_entry> ReadJsonEntries(const json_t* jEntries);
static std::vector<rct_object_entry> ReadJsonEntries(json_t& jEntries);
};

View File

@@ -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);
}
}

View File

@@ -19,7 +19,6 @@
#include "../localisation/Language.h"
#include "../world/Scenery.h"
#include "../world/SmallScenery.h"
#include "ObjectJsonHelpers.h"
#include <algorithm>
@@ -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<uint32_t>(
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<uint8_t>(properties["height"]);
_legacyType.small_scenery.tool_id = Cursor::FromString(Json::GetString(properties["cursor"]), CURSOR_STATUE_DOWN);
_legacyType.small_scenery.price = Json::GetNumber<uint16_t>(properties["price"]);
_legacyType.small_scenery.removal_price = Json::GetNumber<uint16_t>(properties["removalPrice"]);
_legacyType.small_scenery.animation_delay = Json::GetNumber<uint16_t>(properties["animationDelay"]);
_legacyType.small_scenery.animation_mask = Json::GetNumber<uint16_t>(properties["animationMask"]);
_legacyType.small_scenery.num_frames = Json::GetNumber<uint16_t>(properties["numFrames"]);
_legacyType.small_scenery.flags = Json::GetFlags<uint32_t>(
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<uint8_t> SmallSceneryObject::ReadJsonFrameOffsets(const json_t* jFrameOffsets)
std::vector<uint8_t> SmallSceneryObject::ReadJsonFrameOffsets(json_t& jFrameOffsets)
{
std::vector<uint8_t> 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<uint8_t>(jOffset));
}
return offsets;
}

View File

@@ -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<uint8_t> ReadFrameOffsets(OpenRCT2::IStream* stream);
static std::vector<uint8_t> ReadJsonFrameOffsets(const json_t* jFrameOffsets);
static std::vector<uint8_t> ReadJsonFrameOffsets(json_t& jFrameOffsets);
void PerformFixes();
rct_object_entry GetScgPiratHeader();
rct_object_entry GetScgMineHeader();

View File

@@ -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<uint32_t>(
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<int32_t>(properties["height"]);
ScrollingMode = Json::GetNumber<uint8_t>(properties["scrollingMode"], SCROLLING_MODE_NONE);
Flags = Json::GetFlags<uint32_t>(
properties,
{
{ "hasPrimaryColour", STATION_OBJECT_FLAGS::HAS_PRIMARY_COLOUR },
{ "hasSecondaryColour", STATION_OBJECT_FLAGS::HAS_SECONDARY_COLOUR },
{ "isTransparent", STATION_OBJECT_FLAGS::IS_TRANSPARENT },
});
}
PopulateTablesFromJson(context, root);
}

View File

@@ -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;

View File

@@ -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)

View File

@@ -10,6 +10,7 @@
#pragma once
#include "../common.h"
#include "../core/Json.hpp"
#include "../localisation/Language.h"
#include <string>
@@ -44,6 +45,7 @@ class StringTable
{
private:
std::vector<StringTableEntry> _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;

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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<TERRAIN_SURFACE_FLAGS>(
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<int8_t>(properties["rotations"], 1);
Price = Json::GetNumber<money32>(properties["price"]);
Flags = Json::GetFlags<TERRAIN_SURFACE_FLAGS>(
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<uint32_t>(el["index"]);
entry.Length = Json::GetNumber<int32_t>(el["length"], -1);
entry.Rotation = Json::GetNumber<int32_t>(el["rotation"], -1);
entry.Variation = Json::GetNumber<int32_t>(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<uint32_t>(jDefault["normal"]);
DefaultGridEntry = Json::GetNumber<uint32_t>(jDefault["grid"]);
DefaultUndergroundEntry = Json::GetNumber<uint32_t>(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(

View File

@@ -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;

View File

@@ -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<uint8_t>(
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<uint8_t>(
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<uint8_t>(properties["height"]);
_legacyType.wall.price = Json::GetNumber<int16_t>(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<uint8_t>(properties["scrollingMode"], SCROLLING_MODE_NONE);
SetPrimarySceneryGroup(Json::GetString(properties["sceneryGroup"]));
// clang-format off
_legacyType.wall.flags = Json::GetFlags<uint8_t>(
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<uint8_t>(
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<uint8_t>(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);
}

View File

@@ -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;

View File

@@ -16,7 +16,6 @@
#include "../localisation/Language.h"
#include "../localisation/StringIds.h"
#include "../world/Location.hpp"
#include "ObjectJsonHelpers.h"
#include <memory>
@@ -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<uint16_t>(
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<uint16_t>(
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<uint8_t[]>(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<int16_t>(numColours);
g1.x_offset = static_cast<int16_t>(paletteStartIndex);
g1.x_offset = Json::GetNumber<int16_t>(jPalette["index"]);
g1.flags = G1_FLAG_PALETTE;
auto& imageTable = GetImageTable();

View File

@@ -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;
};