diff --git a/.vscode/launch.json b/.vscode/launch.json index 25c3c7a2bf..d3df614e53 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,19 +1,22 @@ { "version": "0.2.0", "configurations": [ + { "name": "C++ Launch", "type": "cppdbg", "request": "launch", - "program": "${workspaceRoot}/bin/openrct2", + "program": "${workspaceFolder}/bin/openrct2", "args": [], "stopAtEntry": false, - "cwd": "${workspaceRoot}/bin", + "cwd": "${workspaceFolder}/bin", "environment": [], "externalConsole": true, "setupCommands": [ { - "text": "-enable-pretty-printing" + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true } ], "linux": { @@ -30,7 +33,7 @@ "name": "C++ Attach", "type": "cppdbg", "request": "attach", - "program": "${workspaceRoot}/bin/openrct2", + "program": "${workspaceFolder}/bin/openrct2", "processId": "${command:pickProcess}", "setupCommands": [ { diff --git a/CMakeLists.txt b/CMakeLists.txt index 6c4f683b1f..26cf27eeb2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,10 +16,14 @@ set(CMAKE_MACOSX_RPATH 1) set(TITLE_SEQUENCE_URL "https://github.com/OpenRCT2/title-sequences/releases/download/v0.1.2/title-sequence-v0.1.2.zip") set(TITLE_SEQUENCE_SHA1 "1136ef92bfb05cd1cba9831ba6dc4a653d87a246") +set(OBJECTS_URL "https://github.com/OpenRCT2/objects/releases/download/v1.0/objects.zip") +set(OBJECTS_SHA1 "4f670859b5f37f85e0c3d755b1cb75e839c989a6") + option(FORCE32 "Force 32-bit build. It will add `-m32` to compiler flags.") option(WITH_TESTS "Build tests") option(PORTABLE "Create a portable build (-rpath=$ORIGIN)" OFF) option(DOWNLOAD_TITLE_SEQUENCES "Download title sequences during installation." ON) +option(DOWNLOAD_OBJECTS "Download objects during installation." ON) # Options option(STATIC "Create a static build.") @@ -208,6 +212,17 @@ if (DOWNLOAD_TITLE_SEQUENCES) file(REMOVE \$ENV{DESTDIR}${CMAKE_INSTALL_FULL_DATADIR}/${PROJECT}/title/title-sequences.zip)\n\ endif ()") endif () +if (DOWNLOAD_OBJECTS) + # If rct2.wtrcyan.json or data/object/ exists, assume all the objects are already present + install(CODE + "if (EXISTS \"\$ENV{DESTDIR}/${CMAKE_INSTALL_FULL_DATADIR}/${PROJECT}/object/rct2/water/rct2.wtrcyan.json\" OR EXISTS ${CMAKE_SOURCE_DIR}/data/object/)\n\ + message(\"Using cached objects\")\n\ + else () \n\ + file(DOWNLOAD ${OBJECTS_URL} \$ENV{DESTDIR}/${CMAKE_INSTALL_FULL_DATADIR}/${PROJECT}/object/objects.zip EXPECTED_HASH SHA1=${OBJECTS_SHA1} SHOW_PROGRESS)\n\ + execute_process(COMMAND \"${CMAKE_COMMAND}\" -E chdir \$ENV{DESTDIR}/${CMAKE_INSTALL_FULL_DATADIR}/${PROJECT}/object/ \"${CMAKE_COMMAND}\" -E tar xvf objects.zip)\n\ + file(REMOVE \$ENV{DESTDIR}/${CMAKE_INSTALL_FULL_DATADIR}/${PROJECT}/object/objects.zip)\n\ + endif ()") +endif () install(TARGETS "libopenrct2" LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}") install(TARGETS "openrct2" RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") diff --git a/OpenRCT2.xcodeproj/project.pbxproj b/OpenRCT2.xcodeproj/project.pbxproj index 15b08849d4..e75854dc5d 100644 --- a/OpenRCT2.xcodeproj/project.pbxproj +++ b/OpenRCT2.xcodeproj/project.pbxproj @@ -21,10 +21,13 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 4C1A53ED205FD1A0000F8EF5 /* SceneryObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4C1A53EC205FD19F000F8EF5 /* SceneryObject.cpp */; }; 4C3B4236205914F7000C5BB7 /* InGameConsole.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4C3B4234205914F7000C5BB7 /* InGameConsole.cpp */; }; 4C3B423820591513000C5BB7 /* StdInOutConsole.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4C3B423720591513000C5BB7 /* StdInOutConsole.cpp */; }; 4C93F1AD1F8CD9F000A9330D /* Input.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4C93F1AC1F8CD9F000A9330D /* Input.cpp */; }; 4C93F1AF1F8CD9F600A9330D /* KeyboardShortcut.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4C93F1AE1F8CD9F600A9330D /* KeyboardShortcut.cpp */; }; + 4CE9AAAD1FDA7B14004093C6 /* ObjectJsonHelpers.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4CE9AAAB1FDA7B14004093C6 /* ObjectJsonHelpers.cpp */; }; + 4CF67197206B7E720034ADDD /* object in Resources */ = {isa = PBXBuildFile; fileRef = 4CF67196206B7E720034ADDD /* object */; }; C61ADB1F1FB6A0A70024F2EF /* TopToolbar.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C61ADB1E1FB6A0A60024F2EF /* TopToolbar.cpp */; }; C61ADB211FB7DC060024F2EF /* Scenery.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C61ADB201FB7DC060024F2EF /* Scenery.cpp */; }; C61ADB231FBBCB8B0024F2EF /* GameBottomToolbar.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C61ADB221FBBCB8A0024F2EF /* GameBottomToolbar.cpp */; }; @@ -586,6 +589,7 @@ /* Begin PBXFileReference section */ 4C04D69F2056AA9600F82EBA /* linenoise.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = linenoise.hpp; sourceTree = ""; }; + 4C1A53EC205FD19F000F8EF5 /* SceneryObject.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SceneryObject.cpp; sourceTree = ""; }; 4C3B4234205914F7000C5BB7 /* InGameConsole.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InGameConsole.cpp; sourceTree = ""; }; 4C3B4235205914F7000C5BB7 /* InGameConsole.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InGameConsole.h; sourceTree = ""; }; 4C3B423720591513000C5BB7 /* StdInOutConsole.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StdInOutConsole.cpp; sourceTree = ""; }; @@ -847,6 +851,9 @@ 4CE462461FD1613D0001CD98 /* Platform.Linux.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Platform.Linux.cpp; sourceTree = ""; }; 4CE462481FD1613D0001CD98 /* Platform.Posix.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Platform.Posix.cpp; sourceTree = ""; }; 4CE462491FD1613D0001CD98 /* Platform.Win32.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Platform.Win32.cpp; sourceTree = ""; }; + 4CE9AAAB1FDA7B14004093C6 /* ObjectJsonHelpers.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ObjectJsonHelpers.cpp; sourceTree = ""; }; + 4CE9AAAC1FDA7B14004093C6 /* ObjectJsonHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ObjectJsonHelpers.h; sourceTree = ""; }; + 4CF67196206B7E720034ADDD /* object */ = {isa = PBXFileReference; lastKnownFileType = folder; name = object; path = data/object; sourceTree = ""; }; 4CFE4E7B1F90A3F1005243C2 /* Peep.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Peep.cpp; sourceTree = ""; }; 4CFE4E7C1F90A3F1005243C2 /* Peep.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Peep.h; sourceTree = ""; }; 4CFE4E7D1F90A3F1005243C2 /* PeepData.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PeepData.cpp; sourceTree = ""; }; @@ -1676,6 +1683,7 @@ D497D06F1C20FD52002BF46A = { isa = PBXGroup; children = ( + 4CF67196206B7E720034ADDD /* object */, D41B72431C21015A0080A7B9 /* Sources */, D497D07A1C20FD52002BF46A /* Resources */, D41B73ED1C21017D0080A7B9 /* Libraries */, @@ -2083,6 +2091,8 @@ 4C7B53A21FFC15ED00A52E21 /* ObjectLimits.h */, 4C7B53A31FFC180400A52E21 /* ObjectList.cpp */, 4C7B53A41FFC180400A52E21 /* ObjectList.h */, + 4CE9AAAB1FDA7B14004093C6 /* ObjectJsonHelpers.cpp */, + 4CE9AAAC1FDA7B14004093C6 /* ObjectJsonHelpers.h */, F76C84221EC4E7CC00FA49E2 /* ObjectManager.cpp */, F76C84231EC4E7CC00FA49E2 /* ObjectManager.h */, F76C84241EC4E7CC00FA49E2 /* ObjectRepository.cpp */, @@ -2091,6 +2101,7 @@ F76C84271EC4E7CC00FA49E2 /* RideObject.h */, F76C84281EC4E7CC00FA49E2 /* SceneryGroupObject.cpp */, F76C84291EC4E7CC00FA49E2 /* SceneryGroupObject.h */, + 4C1A53EC205FD19F000F8EF5 /* SceneryObject.cpp */, F76C842A1EC4E7CC00FA49E2 /* SceneryObject.h */, F76C842B1EC4E7CC00FA49E2 /* SmallSceneryObject.cpp */, F76C842C1EC4E7CC00FA49E2 /* SmallSceneryObject.h */, @@ -2717,6 +2728,7 @@ isa = PBXNativeTarget; buildConfigurationList = D497D0891C20FD53002BF46A /* Build configuration list for PBXNativeTarget "OpenRCT2" */; buildPhases = ( + 4CF67195206B7BEF0034ADDD /* Download JSON objects */, D4E09E831E049C0600F53CE3 /* Download Title Sequences */, D4EC012A1C25532B00DAFE69 /* Setup AppIcon */, D4CA88671D4E962100060C11 /* Get Git Variables */, @@ -2828,6 +2840,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 4CF67197206B7E720034ADDD /* object in Resources */, D41B74731C2125E50080A7B9 /* Assets.xcassets in Resources */, D4EC48E61C2637710024B507 /* g2.dat in Resources */, D4EC48E71C2637710024B507 /* language in Resources */, @@ -2847,6 +2860,20 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 4CF67195206B7BEF0034ADDD /* Download JSON objects */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Download JSON objects"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "version=\"1.0\"\nzipname=\"objects.zip\"\nliburl=\"https://github.com/OpenRCT2/objects/releases/download/v$version/$zipname\"\n\n[[ ! -d \"${SRCROOT}/data/object\" || ! -e \"${SRCROOT}/objectsversion\" || $(head -n 1 \"${SRCROOT}/objectsversion\") != $version ]]\noutdated=$?\n\nif [[ $outdated -eq 0 ]]; then\nif [[ -d \"${SRCROOT}/data/object\" ]]; then rm -r \"${SRCROOT}/data/object\"; fi\nmkdir -p \"${SRCROOT}/data/object\"\n\ncurl -L -o \"${SRCROOT}/data/object/$zipname\" \"$liburl\"\nunzip -uaq -d \"${SRCROOT}/data/object\" \"${SRCROOT}/data/object/$zipname\"\nrm \"${SRCROOT}/data/object/$zipname\"\n\necho $version > \"${SRCROOT}/objectsversion\"\nfi"; + }; C68B2D471EC790710020651C /* Download Libraries */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -3003,6 +3030,7 @@ C68313C81FDB4ED4006DB3D8 /* MouseInput.cpp in Sources */, C68878C920289B710084B384 /* TextureCache.cpp in Sources */, C61ADB1F1FB6A0A70024F2EF /* TopToolbar.cpp in Sources */, + 4C1A53ED205FD1A0000F8EF5 /* SceneryObject.cpp in Sources */, F76C887B1EC5324E00FA49E2 /* FileAudioSource.cpp in Sources */, C68878CA20289B710084B384 /* TransparencyDepth.cpp in Sources */, C64644FD1F3FA4120026AC2D /* Land.cpp in Sources */, @@ -3043,6 +3071,7 @@ C68313D51FDB4F4C006DB3D8 /* Graph.cpp in Sources */, C685E51D1F8907850090598F /* Research.cpp in Sources */, C64644FB1F3FA4120026AC2D /* EditorScenarioOptions.cpp in Sources */, + 4CE9AAAD1FDA7B14004093C6 /* ObjectJsonHelpers.cpp in Sources */, C654DF321F69C0430040F43D /* InstallTrack.cpp in Sources */, C64644FF1F3FA4120026AC2D /* StaffList.cpp in Sources */, C6D2BEE81F9BAACE008B557C /* MazeConstruction.cpp in Sources */, diff --git a/data/language/en-GB.txt b/data/language/en-GB.txt index 5778b901dd..78d75c204b 100644 --- a/data/language/en-GB.txt +++ b/data/language/en-GB.txt @@ -1600,7 +1600,7 @@ STR_1594 :{SMALLFONT}{OPENQUOTES}This wonton soup from {STRINGID} is really g STR_1595 :{SMALLFONT}{OPENQUOTES}This meatball soup from {STRINGID} is really good value{ENDQUOTES} STR_1596 :{SMALLFONT}{OPENQUOTES}This fruit juice from {STRINGID} is really good value{ENDQUOTES} STR_1597 :{SMALLFONT}{OPENQUOTES}This soybean milk from {STRINGID} is really good value{ENDQUOTES} -STR_1598 :{SMALLFONT}{OPENQUOTES}This sujongkwa from {STRINGID} is really good value{ENDQUOTES} +STR_1598 :{SMALLFONT}{OPENQUOTES}This sujeonggwa from {STRINGID} is really good value{ENDQUOTES} STR_1599 :{SMALLFONT}{OPENQUOTES}This sub sandwich from {STRINGID} is really good value{ENDQUOTES} STR_1600 :{SMALLFONT}{OPENQUOTES}This cookie from {STRINGID} is really good value{ENDQUOTES} STR_1601 : @@ -1632,7 +1632,7 @@ STR_1626 :{SMALLFONT}{OPENQUOTES}I'm not paying that much for wonton soup fro STR_1627 :{SMALLFONT}{OPENQUOTES}I'm not paying that much for meatball soup from {STRINGID}{ENDQUOTES} STR_1628 :{SMALLFONT}{OPENQUOTES}I'm not paying that much for fruit juice from {STRINGID}{ENDQUOTES} STR_1629 :{SMALLFONT}{OPENQUOTES}I'm not paying that much for soybean milk from {STRINGID}{ENDQUOTES} -STR_1630 :{SMALLFONT}{OPENQUOTES}I'm not paying that much for sujongkwa from {STRINGID}{ENDQUOTES} +STR_1630 :{SMALLFONT}{OPENQUOTES}I'm not paying that much for sujeonggwa from {STRINGID}{ENDQUOTES} STR_1631 :{SMALLFONT}{OPENQUOTES}I'm not paying that much for a sub sandwich from {STRINGID}{ENDQUOTES} STR_1632 :{SMALLFONT}{OPENQUOTES}I'm not paying that much for a cookie from {STRINGID}{ENDQUOTES} STR_1633 : @@ -2118,7 +2118,7 @@ STR_2110 :{WINDOW_COLOUR_2}Wonton Soup price: STR_2111 :{WINDOW_COLOUR_2}Meatball Soup price: STR_2112 :{WINDOW_COLOUR_2}Fruit Juice price: STR_2113 :{WINDOW_COLOUR_2}Soybean Milk price: -STR_2114 :{WINDOW_COLOUR_2}Sujongkwa price: +STR_2114 :{WINDOW_COLOUR_2}Sujeonggwa price: STR_2115 :{WINDOW_COLOUR_2}Sub Sandwich price: STR_2116 :{WINDOW_COLOUR_2}Cookie price: STR_2117 :{WINDOW_COLOUR_2} @@ -2140,7 +2140,7 @@ STR_2132 :Wonton Soup STR_2133 :Meatball Soup STR_2134 :Fruit Juice STR_2135 :Soybean Milk -STR_2136 :Sujongkwa +STR_2136 :Sujeonggwa STR_2137 :Sub Sandwich STR_2138 :Cookie STR_2139 :Empty Bowl @@ -2162,7 +2162,7 @@ STR_2154 :Wonton Soups STR_2155 :Meatball Soups STR_2156 :Fruit Juices STR_2157 :Soybean Milks -STR_2158 :Sujongkwa +STR_2158 :Sujeonggwa STR_2159 :Sub Sandwiches STR_2160 :Cookies STR_2161 :Empty Bowls @@ -2184,7 +2184,7 @@ STR_2176 :some Wonton Soup STR_2177 :some Meatball Soup STR_2178 :a Fruit Juice STR_2179 :some Soybean Milk -STR_2180 :some Sujongkwa +STR_2180 :some Sujeonggwa STR_2181 :a Sub Sandwich STR_2182 :a Cookie STR_2183 :an Empty Bowl @@ -2206,7 +2206,7 @@ STR_2198 :Wonton Soup STR_2199 :Meatball Soup STR_2200 :Fruit Juice STR_2201 :Soybean Milk -STR_2202 :Sujongkwa +STR_2202 :Sujeonggwa STR_2203 :Sub Sandwich STR_2204 :Cookie STR_2205 :Empty Bowl @@ -4988,411 +4988,10 @@ STR_SCNR :Fort Anachronism STR_PARK :Fort Anachronism STR_DTLS : -##################### -# Rides/attractions # -##################### - -#RCT2 -[TOPSP1] -STR_NAME :Top Spin -STR_DESC :Passengers ride in a gondola suspended by large rotating arms, rotating forwards and backwards head-over-heels -STR_CPTY :8 passengers - -[BMSD] -STR_NAME :Twister Trains -STR_DESC :A spacious train with shoulder restraints - -[BMSU] -STR_NAME :Stand-up Twister Trains -STR_DESC :A train with shoulder restraints, in which the riders stand up - -[BMFL] -STR_NAME :Floorless Twister Trains -STR_DESC :A spacious train with shoulder restraints and no floor, making for a more exciting ride - -[WMMINE] -STR_NAME :Mine Cars -STR_DESC :Cars shaped like an old mine cart - -[WMOUSE] -STR_NAME :Mouse Cars -STR_DESC :Indivual cars shaped like a mouse - -[STEEP1] -STR_NAME :Horses -STR_DESC :Single cars shaped like a horse - -[STEEP2] -STR_NAME :Motorbikes -STR_DESC :Single cars shaped like a motorbike - -[SBOX] -STR_NAME :Soap boxes -STR_DESC :Single cars shaped like a soap box - -[BOB1] -STR_NAME :Bobsleigh Trains -STR_DESC :A train consisting of 2-seater cars where the riders are behind each other - -[INTBOB] -STR_NAME :6-seater Bobsleighs -STR_DESC :Bobsleighs with three seating rows, with room for two people on each - -[LIFT1] -STR_NAME :Lift Cabin -STR_DESC :Steel lift cabin - -[ARRT1] -STR_NAME :Corkscrew Roller Coaster Trains -STR_DESC :Roller coaster train with shoulder restraints - -[ARRT2] -STR_NAME :Hypercoaster Trains -STR_DESC :Comfortable trains with only lap bar restraints - -[MONBK] -STR_NAME :Bicycles - -[OBS1] -STR_NAME :Single-deck Cabin - -[OBS2] -STR_NAME :Double-deck Cabin - -[GTC] -STR_NAME :Ghost Train Cars - -[HMCAR] -STR_NAME :Haunted Mansion Cars - -[VREEL] -STR_NAME :Virginia Reel tubs - -[WMSPIN] -STR_NAME :Spinning Mouse Cars - -[AMT1] -STR_NAME :Mine Trains - -[SLCT] -STR_NAME :Compact Inverted Coaster Trains - -[SLCFO] -STR_NAME :Face-off Cars - -[VEKDV] -STR_NAME :Vertical Shuttle Cars - -[THCAR] -STR_NAME :Air Powered Vertical Coaster Trains - -[SSC1] -STR_NAME :Launched Freefall car - -[DING1] -STR_NAME :Dinghies - -[LFB1] -STR_NAME :Logs - -[RFTBOAT] -STR_NAME :Rafts - -[IVMC1] -STR_NAME :Four-seater Cars - -[SPDRCR] -STR_NAME :Spiral Roller Coaster Trains - -[TOGST] -STR_NAME :Stand-up Roller Coaster Trains - -[PREMT1] -STR_NAME :LIM Launched Roller Coaster Trains - -[PMT1] -STR_NAME :Powered mine train - -[BMAIR] -STR_NAME :Flying Roller Coaster Trains - -[INTINV] -STR_NAME :Impulse Trains - -[INTST] -STR_NAME :Giga Coaster Trains - -[NEMT] -STR_NAME :4-across Inverted Roller Coaster Trains - -[VEKST] -STR_NAME :Lay-down Roller Coaster Trains - -[REVCAR] -STR_NAME :Reverser Cars -STR_DESC :Bogied cars capable of turning around on special reversing sections - -[SKYTR] -STR_NAME :Lay-down Cars - -[BMVD] -STR_NAME :Six-seater Twister Trains - -[SUBMAR] -STR_NAME :Submarines - -#WW -[CONDORRD] -STR_NAME :Condor Trains -STR_DESC :Riding in special harnesses below the track, riders experience the feeling of flight as they swoop through the air in Condor-shaped trains -STR_CPTY :4 passengers per car - -#WW -[CONGAEEL] -STR_NAME :Conger Eel Trains -STR_DESC :Trains with shoulder restraints, in the shape of a Conger Eel. - -#WW -[MINELIFT] -STR_NAME :Mine Lift Cabin -STR_DESC :A steel lift cabin commonly used in mines - -#WW -[ANACONDA] -STR_NAME :Anaconda Trains - -#WW -[CROCFLUM] -STR_NAME :Crocodile boats - -#WW -[WHICGRUB] -STR_NAME :Witchity Grub Trains - -#WW -[GRATWHTE] -STR_NAME :Great White Shark Trains -STR_DESC :Trains with shoulder restraints, in the shape of a Great White Shark - -#WW -[BOMERANG] -STR_NAME :Boomerang Trains - -#WW -[KOLARIDE] -STR_NAME :Koala car - -#WW -[MANTARAY] -STR_NAME :Manta Ray boats -STR_DESC :Coaster boats in the shape of a Manta Ray - -#WW -[TUTLBOAT] -STR_NAME :Turtle boats - -#WW, for the Wooden RC -[MINECART] -STR_NAME :Mine Cart Trains - -#WW -[LIONRIDE] -STR_NAME :Lion Cars - -#WW -[RHINORID] -STR_NAME :Rhino Trains - -#WW -[OSTRICH] -STR_NAME :Ostrich Trains - -#WW -[GORILLA] -STR_NAME :Gorilla Trains - -#WW -[FOOTBALL] -STR_NAME :Football Trains - -#WW -[TIGRTWST] -STR_NAME :Bengal Tiger Cars - -#WW -[TAXICSTR] -STR_NAME :Yellow Taxi Trains - -#WW -[OUTRIGGR] -STR_NAME :Outrigger canoes - -#WW -[SANFTRAM] -STR_NAME :San Francisco Trams - -#WW -[PENGUINB] -STR_NAME :Penguin Trains - -#WW -[POLARBER] -STR_NAME :Polar Bear Trains - -#WW -[LONDONBS] -STR_NAME :Routemaster buses -STR_DESC :Replicas of the famous London Routemaster bus - -#WW -[BLACKCAB] -STR_NAME :Black Cabs - -#WW -[TGVTRAIN] -STR_NAME :TGV Trains - -#WW -[ROCKET] -STR_NAME :1950's Rockets - -#WW -[SPUTNIKR] -STR_NAME :Sputnik Cars - -#WW -[DHOWWATR] -STR_NAME :Dhow boats - -#WW -[SURFBRDC] -STR_NAME :Surfing Trains - -#WW -[KILLWHAL] -STR_NAME :Killer Whale Submarines - -#WW -[HIPPORID] -STR_NAME :Hippo Submarines - -#WW -[DOLPHINR] -STR_NAME :Dolphin boats - -#WW -[MANDARIN] -STR_NAME :Mandarin Duck Boats -STR_DESC :Duck shaped boat, propelled by the pedalling front seat passengers - -#TT -[BATTRRAM] -STR_NAME :Battering Ram Trains - -#TT -[BLCKDETH] -STR_NAME :Black Death Trains - -#TT -[JOUSTING] -STR_NAME :Jousting Knights - -#TT -[OAKBAREL] -STR_NAME :Oak Barrels - -#TT -[STAMPHRD] -STR_NAME :Stampeding Herd Trains - -#TT -[DRAGNFLY] -STR_NAME :Dragonfly Cars - -#TT -[PTERODAC] -STR_NAME :Pterodactyl Trains - -#TT -[PEGASUSX] -STR_NAME :Pegasus Cars - -#TT -[CERBERUS] -STR_NAME :Cerberus Trains - -#TT -[HARPIESX] -STR_NAME :Harpies Trains - -#TT -[VALKYRIE] -STR_NAME :Valkyries Trains - -#TT -[RIVRSTYX] -STR_NAME :River Styx boats - -#TT -[TELEPTER] -STR_NAME :Teleporter Cabin - -#TT -[HOVERCAR] -STR_NAME :Hover Cars - -#TT -[HOVRBORD] -STR_NAME :Hoverboards - -#TT -[HOVERBKE] -STR_NAME :Hover Bikes - -#TT, Reverser RC -[POLICECR] -STR_NAME :Police Cars - -#TT, Looping RC -[POLCHASE] -STR_NAME :Police Car Trains - -#TT -[GANSTRCR] -STR_NAME :Gangster Cars - -#TT -[SEAPLANE] -STR_NAME :Suspended Seaplane Cars - -#TT -[BARNSTRM] -STR_NAME :BarnStorming Cars - -#TT -[FLYGBOAT] -STR_NAME :Flying boats - -#TT -[BMVOCTPS] -STR_NAME :Blob from Outer Space - -#TT -[JETPLANE] -STR_NAME :Jet Plane Cars - -#TT -[HOTRODXX] -STR_NAME :Hot Rod Cars - ########### # Scenery # ########### -#Bulrushes was spelt incorrectly -[TBR] -STR_NAME :Bulrushes - ## Start OpenRCT2 Official [XXBBBR01] STR_NAME :Base Block diff --git a/openrct2.proj b/openrct2.proj index f2ea557a4e..8854ba62a6 100644 --- a/openrct2.proj +++ b/openrct2.proj @@ -71,6 +71,8 @@ 667f873ab7a4d246062565fad32fb6d8e203ee73 https://github.com/OpenRCT2/title-sequences/releases/download/v0.1.2/title-sequence-v0.1.2.zip 1136ef92bfb05cd1cba9831ba6dc4a653d87a246 + https://github.com/OpenRCT2/objects/releases/download/v1.0/objects.zip + 4f670859b5f37f85e0c3d755b1cb75e839c989a6 @@ -214,6 +216,15 @@ OutputDirectory="$(TargetDir)data\title" /> + + + + + from zipTree(new File(buildDir, 'title-sequence.zip')) into "$variant.mergeAssets.outputDir/data/title" } + download { + src 'https://github.com/OpenRCT2/objects/releases/download/v1.0/objects.zip' + dest new File(buildDir, 'objects.zip') + } + copy { + from zipTree(new File(buildDir, 'objects.zip')) + into "$variant.mergeAssets.outputDir/data/object" + } } } diff --git a/src/openrct2-ui/windows/EditorObjectSelection.cpp b/src/openrct2-ui/windows/EditorObjectSelection.cpp index 234b7059a5..314e12a24a 100644 --- a/src/openrct2-ui/windows/EditorObjectSelection.cpp +++ b/src/openrct2-ui/windows/EditorObjectSelection.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -304,7 +305,7 @@ static bool visible_list_sort_ride_type(const list_item &a, const list_item &b) { auto rideTypeA = language_get_string(get_ride_type_string_id(a.repositoryItem)); auto rideTypeB = language_get_string(get_ride_type_string_id(b.repositoryItem)); - sint32 result = strcmp(rideTypeA, rideTypeB); + sint32 result = String::Compare(rideTypeA, rideTypeB); return result != 0 ? result < 0 : visible_list_sort_ride_name(a, b); diff --git a/src/openrct2-ui/windows/Ride.cpp b/src/openrct2-ui/windows/Ride.cpp index 93259633a0..0aae7da632 100644 --- a/src/openrct2-ui/windows/Ride.cpp +++ b/src/openrct2-ui/windows/Ride.cpp @@ -3370,7 +3370,6 @@ static void window_ride_operating_invalidate(rct_window *w) { rct_widget *widgets; Ride *ride; - rct_ride_entry *rideEntry; rct_string_id format, caption, tooltip; widgets = window_ride_page_widgets[w->page]; @@ -3382,7 +3381,6 @@ static void window_ride_operating_invalidate(rct_window *w) window_ride_set_pressed_tab(w); ride = get_ride(w->number); - rideEntry = get_ride_entry_by_ride(ride); set_format_arg(0, rct_string_id, ride->name); set_format_arg(2, uint32, ride->name_arguments); @@ -3397,8 +3395,8 @@ static void window_ride_operating_invalidate(rct_window *w) ); // Lift hill speed - if ((rideEntry->enabledTrackPieces & (1ULL << TRACK_LIFT_HILL)) && - track_piece_is_available_for_ride_type(ride->type, TRACK_LIFT_HILL)) { + if (track_piece_is_available_for_ride_type(ride->type, TRACK_LIFT_HILL)) + { window_ride_operating_widgets[WIDX_LIFT_HILL_SPEED_LABEL].type = WWT_LABEL; window_ride_operating_widgets[WIDX_LIFT_HILL_SPEED].type = WWT_SPINNER; window_ride_operating_widgets[WIDX_LIFT_HILL_SPEED_INCREASE].type = WWT_BUTTON; diff --git a/src/openrct2/Context.cpp b/src/openrct2/Context.cpp index afb410c89b..2dd6e4b040 100644 --- a/src/openrct2/Context.cpp +++ b/src/openrct2/Context.cpp @@ -301,12 +301,15 @@ namespace OpenRCT2 // return false; // } //This comment was relocated so it would stay where it was in relation to the following lines of code. - auto rct2InstallPath = GetOrPromptRCT2Path(); - if (rct2InstallPath.empty()) + if (!gOpenRCT2Headless) { - return false; + auto rct2InstallPath = GetOrPromptRCT2Path(); + if (rct2InstallPath.empty()) + { + return false; + } + _env->SetBasePath(DIRBASE::RCT2, rct2InstallPath); } - _env->SetBasePath(DIRBASE::RCT2, rct2InstallPath); _objectRepository = CreateObjectRepository(_env); _objectManager = CreateObjectManager(_objectRepository); diff --git a/src/openrct2/core/String.cpp b/src/openrct2/core/String.cpp index ff347f67c9..d9742c4436 100644 --- a/src/openrct2/core/String.cpp +++ b/src/openrct2/core/String.cpp @@ -80,9 +80,9 @@ namespace String sint32 Compare(const utf8 * a, const utf8 * b, bool ignoreCase) { - if (a == b) return true; - if (a == nullptr || b == nullptr) return false; - + if (a == b) return 0; + if (a == nullptr) a = ""; + if (b == nullptr) b = ""; if (ignoreCase) { return _stricmp(a, b); diff --git a/src/openrct2/drawing/Drawing.h b/src/openrct2/drawing/Drawing.h index b240e59843..54d7d5d647 100644 --- a/src/openrct2/drawing/Drawing.h +++ b/src/openrct2/drawing/Drawing.h @@ -56,6 +56,7 @@ enum { G1_FLAG_BMP = (1 << 0), // Image data is encoded as raw pixels (no transparency) G1_FLAG_1 = (1 << 1), G1_FLAG_RLE_COMPRESSION = (1 << 2), // Image data is encoded using RCT2's form of run length encoding + G1_FLAG_PALETTE = (1 << 3), // Image data is a sequence of palette entries R8G8B8 G1_FLAG_HAS_ZOOM_SPRITE = (1 << 4), // Use a different sprite for higher zoom levels G1_FLAG_NO_ZOOM_DRAW = (1 << 5), // Does not get drawn at higher zoom levels (only zoom 0) }; @@ -339,6 +340,7 @@ void scrolling_text_initialise_bitmaps(); sint32 scrolling_text_setup(struct paint_session * session, rct_string_id stringId, uint16 scroll, uint16 scrollingMode); rct_size16 FASTCALL gfx_get_sprite_size(uint32 image_id); +size_t g1_calculate_data_size(const rct_g1_element * g1); void mask_scalar(sint32 width, sint32 height, const uint8 * RESTRICT maskSrc, const uint8 * RESTRICT colourSrc, uint8 * RESTRICT dst, sint32 maskWrap, sint32 colourWrap, sint32 dstWrap); diff --git a/src/openrct2/drawing/Sprite.cpp b/src/openrct2/drawing/Sprite.cpp index 0fc7f5c991..0f34364295 100644 --- a/src/openrct2/drawing/Sprite.cpp +++ b/src/openrct2/drawing/Sprite.cpp @@ -379,7 +379,10 @@ bool gfx_load_csg() { _csg.elements[i].offset += (uintptr_t)_csg.data; // RCT1 used zoomed offsets that counted from the beginning of the file, rather than from the current sprite. - _csg.elements[i].zoomed_offset = i - (SPR_CSG_BEGIN + _csg.elements[i].zoomed_offset); + if (_csg.elements[i].zoomed_offset != 0) + { + _csg.elements[i].zoomed_offset = i - (SPR_CSG_BEGIN + _csg.elements[i].zoomed_offset); + } } _csgLoaded = true; return true; @@ -858,3 +861,36 @@ rct_size16 FASTCALL gfx_get_sprite_size(uint32 image_id) return size; } +size_t g1_calculate_data_size(const rct_g1_element * g1) +{ + if (g1->flags & G1_FLAG_PALETTE) + { + return g1->width * 3; + } + else if (g1->flags & G1_FLAG_RLE_COMPRESSION) + { + if (g1->offset == nullptr) + { + return 0; + } + else + { + uint16 * offsets = (uint16 *)g1->offset; + uint8 * ptr = g1->offset + offsets[g1->height - 1]; + bool endOfLine = false; + do + { + uint8 chunk0 = *ptr++; + ptr++; // offset + uint8 chunkSize = chunk0 & 0x7F; + ptr += chunkSize; + endOfLine = (chunk0 & 0x80) != 0; + } while (!endOfLine); + return ptr - g1->offset; + } + } + else + { + return g1->width * g1->height; + } +} diff --git a/src/openrct2/localisation/Language.cpp b/src/openrct2/localisation/Language.cpp index d1ab538d8f..caba5e3289 100644 --- a/src/openrct2/localisation/Language.cpp +++ b/src/openrct2/localisation/Language.cpp @@ -83,6 +83,20 @@ void utf8_remove_format_codes(utf8 * text, bool allowcolours) *dstCh = 0; } +uint8 language_get_id_from_locale(const char * locale) +{ + uint8 i = 0; + for (const auto &langDesc : LanguagesDescriptors) + { + if (String::Equals(locale, langDesc.locale)) + { + return i; + } + i++; + } + return LANGUAGE_UNDEFINED; +} + const char * language_get_string(rct_string_id id) { const char * result = nullptr; diff --git a/src/openrct2/localisation/Language.h b/src/openrct2/localisation/Language.h index 9bccd0be60..2188080e0b 100644 --- a/src/openrct2/localisation/Language.h +++ b/src/openrct2/localisation/Language.h @@ -94,6 +94,7 @@ extern const utf8 BlackLeftArrowString[]; extern const utf8 BlackRightArrowString[]; extern const utf8 CheckBoxMarkString[]; +uint8 language_get_id_from_locale(const char * locale); const char *language_get_string(rct_string_id id); bool language_open(sint32 id); void language_close_all(); diff --git a/src/openrct2/object/BannerObject.cpp b/src/openrct2/object/BannerObject.cpp index 146fa47adb..749c919f6b 100644 --- a/src/openrct2/object/BannerObject.cpp +++ b/src/openrct2/object/BannerObject.cpp @@ -15,11 +15,11 @@ #pragma endregion #include "../core/IStream.hpp" -#include "BannerObject.h" - #include "../drawing/Drawing.h" #include "../localisation/Language.h" #include "../object/Object.h" +#include "BannerObject.h" +#include "ObjectJsonHelpers.h" #include "ObjectList.h" void BannerObject::ReadLegacy(IReadObjectContext * context, IStream * stream) @@ -31,12 +31,12 @@ void BannerObject::ReadLegacy(IReadObjectContext * context, IStream * stream) _legacyType.banner.scenery_tab_id = stream->ReadValue(); stream->Seek(1, STREAM_SEEK_CURRENT); - GetStringTable()->Read(context, stream, OBJ_STRING_ID_NAME); + GetStringTable().Read(context, stream, OBJ_STRING_ID_NAME); rct_object_entry sgEntry = stream->ReadValue(); SetPrimarySceneryGroup(&sgEntry); - GetImageTable()->Read(context, stream); + GetImageTable().Read(context, stream); // Validate properties if (_legacyType.large_scenery.price <= 0) @@ -61,15 +61,15 @@ void BannerObject::ReadLegacy(IReadObjectContext * context, IStream * stream) void BannerObject::Load() { - GetStringTable()->Sort(); + GetStringTable().Sort(); _legacyType.name = language_allocate_object_string(GetName()); - _legacyType.image = gfx_object_allocate_images(GetImageTable()->GetImages(), GetImageTable()->GetCount()); + _legacyType.image = gfx_object_allocate_images(GetImageTable().GetImages(), GetImageTable().GetCount()); } void BannerObject::Unload() { language_free_object_string(_legacyType.name); - gfx_object_free_images(_legacyType.image, GetImageTable()->GetCount()); + gfx_object_free_images(_legacyType.image, GetImageTable().GetCount()); _legacyType.name = 0; _legacyType.image = 0; @@ -84,3 +84,18 @@ void BannerObject::DrawPreview(rct_drawpixelinfo * dpi, sint32 width, sint32 hei gfx_draw_sprite(dpi, imageId + 0, x - 12, y + 8, 0); gfx_draw_sprite(dpi, imageId + 1, x - 12, y + 8, 0); } + +void BannerObject::ReadJson(IReadObjectContext * context, const json_t * root) +{ + auto properties = json_object_get(root, "properties"); + + _legacyType.banner.scrolling_mode = json_integer_value(json_object_get(properties, "scrollingMode")); + _legacyType.banner.price = json_integer_value(json_object_get(properties, "price")); + _legacyType.banner.flags = ObjectJsonHelpers::GetFlags(properties, { + { "hasPrimaryColour", BANNER_ENTRY_FLAG_HAS_PRIMARY_COLOUR }}); + + SetPrimarySceneryGroup(ObjectJsonHelpers::GetString(json_object_get(properties, "sceneryGroup"))); + + ObjectJsonHelpers::LoadStrings(root, GetStringTable()); + ObjectJsonHelpers::LoadImages(context, root, GetImageTable()); +} diff --git a/src/openrct2/object/BannerObject.h b/src/openrct2/object/BannerObject.h index 1dedd329d2..0f1148e753 100644 --- a/src/openrct2/object/BannerObject.h +++ b/src/openrct2/object/BannerObject.h @@ -31,6 +31,7 @@ public: void * GetLegacyData() override { return &_legacyType; } void ReadLegacy(IReadObjectContext * context, IStream * stream) override; + void ReadJson(IReadObjectContext * context, const json_t * root) override; void Load() override; void Unload() override; diff --git a/src/openrct2/object/EntranceObject.cpp b/src/openrct2/object/EntranceObject.cpp index 1d31029372..fc1737a63d 100644 --- a/src/openrct2/object/EntranceObject.cpp +++ b/src/openrct2/object/EntranceObject.cpp @@ -16,10 +16,10 @@ #include "../core/IStream.hpp" #include "../core/String.hpp" -#include "EntranceObject.h" - #include "../drawing/Drawing.h" #include "../localisation/Localisation.h" +#include "EntranceObject.h" +#include "ObjectJsonHelpers.h" void EntranceObject::ReadLegacy(IReadObjectContext * context, IStream * stream) { @@ -27,8 +27,8 @@ void EntranceObject::ReadLegacy(IReadObjectContext * context, IStream * stream) _legacyType.scrolling_mode = stream->ReadValue(); _legacyType.text_height = stream->ReadValue(); - GetStringTable()->Read(context, stream, OBJ_STRING_ID_NAME); - GetImageTable()->Read(context, stream); + GetStringTable().Read(context, stream, OBJ_STRING_ID_NAME); + GetImageTable().Read(context, stream); // Fix issue #1705: The Medieval entrance from Time Twister has a straight banner, // but scrolls its text as if it a curved one. @@ -41,15 +41,15 @@ void EntranceObject::ReadLegacy(IReadObjectContext * context, IStream * stream) void EntranceObject::Load() { - GetStringTable()->Sort(); + GetStringTable().Sort(); _legacyType.string_idx = language_allocate_object_string(GetName()); - _legacyType.image_id = gfx_object_allocate_images(GetImageTable()->GetImages(), GetImageTable()->GetCount()); + _legacyType.image_id = gfx_object_allocate_images(GetImageTable().GetImages(), GetImageTable().GetCount()); } void EntranceObject::Unload() { language_free_object_string(_legacyType.string_idx); - gfx_object_free_images(_legacyType.image_id, GetImageTable()->GetCount()); + gfx_object_free_images(_legacyType.image_id, GetImageTable().GetCount()); _legacyType.string_idx = 0; _legacyType.image_id = 0; @@ -65,3 +65,13 @@ void EntranceObject::DrawPreview(rct_drawpixelinfo * dpi, sint32 width, sint32 h gfx_draw_sprite(dpi, imageId + 0, x + 0, y + 28, 0); gfx_draw_sprite(dpi, imageId + 2, x + 32, y + 44, 0); } + +void EntranceObject::ReadJson(IReadObjectContext * context, const 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")); + + ObjectJsonHelpers::LoadStrings(root, GetStringTable()); + ObjectJsonHelpers::LoadImages(context, root, GetImageTable()); +} diff --git a/src/openrct2/object/EntranceObject.h b/src/openrct2/object/EntranceObject.h index 4af4a02985..6d6c0b65cb 100644 --- a/src/openrct2/object/EntranceObject.h +++ b/src/openrct2/object/EntranceObject.h @@ -31,6 +31,7 @@ public: void * GetLegacyData() override { return &_legacyType; } void ReadLegacy(IReadObjectContext * context, IStream * stream) override; + void ReadJson(IReadObjectContext * context, const json_t * root) override; void Load() override; void Unload() override; diff --git a/src/openrct2/object/FootpathItemObject.cpp b/src/openrct2/object/FootpathItemObject.cpp index bb3f6ec528..97a5fee55e 100644 --- a/src/openrct2/object/FootpathItemObject.cpp +++ b/src/openrct2/object/FootpathItemObject.cpp @@ -14,13 +14,15 @@ *****************************************************************************/ #pragma endregion +#include #include "../core/IStream.hpp" -#include "FootpathItemObject.h" - #include "../drawing/Drawing.h" +#include "../interface/Cursors.h" #include "../localisation/Localisation.h" #include "../object/Object.h" #include "ObjectList.h" +#include "FootpathItemObject.h" +#include "ObjectJsonHelpers.h" void FootpathItemObject::ReadLegacy(IReadObjectContext * context, IStream * stream) { @@ -32,12 +34,12 @@ void FootpathItemObject::ReadLegacy(IReadObjectContext * context, IStream * stre _legacyType.path_bit.scenery_tab_id = stream->ReadValue(); stream->Seek(1, STREAM_SEEK_CURRENT); - GetStringTable()->Read(context, stream, OBJ_STRING_ID_NAME); + GetStringTable().Read(context, stream, OBJ_STRING_ID_NAME); rct_object_entry sgEntry = stream->ReadValue(); SetPrimarySceneryGroup(&sgEntry); - GetImageTable()->Read(context, stream); + GetImageTable().Read(context, stream); // Validate properties if (_legacyType.large_scenery.price <= 0) @@ -62,9 +64,9 @@ void FootpathItemObject::ReadLegacy(IReadObjectContext * context, IStream * stre void FootpathItemObject::Load() { - GetStringTable()->Sort(); + GetStringTable().Sort(); _legacyType.name = language_allocate_object_string(GetName()); - _legacyType.image = gfx_object_allocate_images(GetImageTable()->GetImages(), GetImageTable()->GetCount()); + _legacyType.image = gfx_object_allocate_images(GetImageTable().GetImages(), GetImageTable().GetCount()); _legacyType.path_bit.scenery_tab_id = 0xFF; } @@ -72,7 +74,7 @@ void FootpathItemObject::Load() void FootpathItemObject::Unload() { language_free_object_string(_legacyType.name); - gfx_object_free_images(_legacyType.image, GetImageTable()->GetCount()); + gfx_object_free_images(_legacyType.image, GetImageTable().GetCount()); _legacyType.name = 0; _legacyType.image = 0; @@ -84,3 +86,37 @@ void FootpathItemObject::DrawPreview(rct_drawpixelinfo * dpi, sint32 width, sint sint32 y = height / 2; gfx_draw_sprite(dpi, _legacyType.image, x - 22, y - 24, 0); } + +static uint8 ParseDrawType(const std::string &s) +{ + if (s == "lamp") return PATH_BIT_DRAW_TYPE_LIGHTS; + if (s == "bin") return PATH_BIT_DRAW_TYPE_BINS; + if (s == "bench") return PATH_BIT_DRAW_TYPE_BENCHES; + if (s == "fountain") return PATH_BIT_DRAW_TYPE_JUMPING_FOUNTAINS; + return PATH_BIT_DRAW_TYPE_LIGHTS; +} + +void FootpathItemObject::ReadJson(IReadObjectContext * context, const 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")); + + SetPrimarySceneryGroup(ObjectJsonHelpers::GetString(json_object_get(properties, "sceneryGroup"))); + + // Flags + _legacyType.path_bit.flags = ObjectJsonHelpers::GetFlags(properties, { + { "isBin", PATH_BIT_FLAG_IS_BIN }, + { "isBench", PATH_BIT_FLAG_IS_BENCH }, + { "isBreakable", PATH_BIT_FLAG_BREAKABLE }, + { "isLamp", PATH_BIT_FLAG_LAMP }, + { "isJumpingFountainWater", PATH_BIT_FLAG_JUMPING_FOUNTAIN_WATER }, + { "isJumpingFountainSnow", PATH_BIT_FLAG_JUMPING_FOUNTAIN_SNOW }, + { "isAllowedOnQueue", PATH_BIT_FLAG_DONT_ALLOW_ON_QUEUE }, + { "isAllowedOnSlope", PATH_BIT_FLAG_DONT_ALLOW_ON_SLOPE }, + { "isTelevision", PATH_BIT_FLAG_IS_QUEUE_SCREEN }}); + + ObjectJsonHelpers::LoadStrings(root, GetStringTable()); + ObjectJsonHelpers::LoadImages(context, root, GetImageTable()); +} diff --git a/src/openrct2/object/FootpathItemObject.h b/src/openrct2/object/FootpathItemObject.h index 14d2c7fefc..d2099b569d 100644 --- a/src/openrct2/object/FootpathItemObject.h +++ b/src/openrct2/object/FootpathItemObject.h @@ -31,6 +31,7 @@ public: void * GetLegacyData() override { return &_legacyType; } void ReadLegacy(IReadObjectContext * context, IStream * stream) override; + void ReadJson(IReadObjectContext * context, const json_t * root) override; void Load() override; void Unload() override; diff --git a/src/openrct2/object/FootpathObject.cpp b/src/openrct2/object/FootpathObject.cpp index a57ee453ae..3fd27b6c5a 100644 --- a/src/openrct2/object/FootpathObject.cpp +++ b/src/openrct2/object/FootpathObject.cpp @@ -15,11 +15,11 @@ #pragma endregion #include "../core/IStream.hpp" -#include "FootpathObject.h" - #include "../drawing/Drawing.h" #include "../localisation/Language.h" #include "../world/Footpath.h" +#include "FootpathObject.h" +#include "ObjectJsonHelpers.h" void FootpathObject::ReadLegacy(IReadObjectContext * context, IStream * stream) { @@ -29,8 +29,8 @@ void FootpathObject::ReadLegacy(IReadObjectContext * context, IStream * stream) _legacyType.scrolling_mode = stream->ReadValue(); stream->Seek(1, STREAM_SEEK_CURRENT); - GetStringTable()->Read(context, stream, OBJ_STRING_ID_NAME); - GetImageTable()->Read(context, stream); + GetStringTable().Read(context, stream, OBJ_STRING_ID_NAME); + GetImageTable().Read(context, stream); // Validate properties if (_legacyType.support_type >= FOOTPATH_ENTRY_SUPPORT_TYPE_COUNT) @@ -41,16 +41,16 @@ void FootpathObject::ReadLegacy(IReadObjectContext * context, IStream * stream) void FootpathObject::Load() { - GetStringTable()->Sort(); + GetStringTable().Sort(); _legacyType.string_idx = language_allocate_object_string(GetName()); - _legacyType.image = gfx_object_allocate_images(GetImageTable()->GetImages(), GetImageTable()->GetCount()); + _legacyType.image = gfx_object_allocate_images(GetImageTable().GetImages(), GetImageTable().GetCount()); _legacyType.bridge_image = _legacyType.image + 109; } void FootpathObject::Unload() { language_free_object_string(_legacyType.string_idx); - gfx_object_free_images(_legacyType.image, GetImageTable()->GetCount()); + gfx_object_free_images(_legacyType.image, GetImageTable().GetCount()); _legacyType.string_idx = 0; _legacyType.image = 0; @@ -63,3 +63,25 @@ void FootpathObject::DrawPreview(rct_drawpixelinfo * dpi, sint32 width, sint32 h gfx_draw_sprite(dpi, _legacyType.image + 71, x - 49, y - 17, 0); gfx_draw_sprite(dpi, _legacyType.image + 72, x + 4, y - 17, 0); } + +static uint8 ParseSupportType(const std::string &s) +{ + if (s == "pole") return FOOTPATH_ENTRY_SUPPORT_TYPE_POLE; + else /* if (s == "box") */ return FOOTPATH_ENTRY_SUPPORT_TYPE_BOX; +} + +void FootpathObject::ReadJson(IReadObjectContext * context, const 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")); + + // Flags + _legacyType.flags = ObjectJsonHelpers::GetFlags(properties, { + { "hasSupportImages", FOOTPATH_ENTRY_FLAG_HAS_SUPPORT_BASE_SPRITE }, + { "hasElevatedPathImages", FOOTPATH_ENTRY_FLAG_HAS_PATH_BASE_SPRITE }, + { "editorOnly", FOOTPATH_ENTRY_FLAG_SHOW_ONLY_IN_SCENARIO_EDITOR } }); + + ObjectJsonHelpers::LoadStrings(root, GetStringTable()); + ObjectJsonHelpers::LoadImages(context, root, GetImageTable()); +} diff --git a/src/openrct2/object/FootpathObject.h b/src/openrct2/object/FootpathObject.h index b1513ab491..8d4cbecf00 100644 --- a/src/openrct2/object/FootpathObject.h +++ b/src/openrct2/object/FootpathObject.h @@ -31,6 +31,7 @@ public: void * GetLegacyData() override { return &_legacyType; } void ReadLegacy(IReadObjectContext * context, IStream * stream) override; + void ReadJson(IReadObjectContext * context, const json_t * root) override; void Load() override; void Unload() override; diff --git a/src/openrct2/object/ImageTable.cpp b/src/openrct2/object/ImageTable.cpp index d80bf172ae..29236b6811 100644 --- a/src/openrct2/object/ImageTable.cpp +++ b/src/openrct2/object/ImageTable.cpp @@ -15,18 +15,22 @@ #pragma endregion #include +#include #include #include "../core/IStream.hpp" -#include "../core/Memory.hpp" #include "../OpenRCT2.h" #include "ImageTable.h" #include "Object.h" ImageTable::~ImageTable() { - Memory::Free(_data); - _data = nullptr; - _dataSize = 0; + if (_data == nullptr) + { + for (auto &entry : _entries) + { + delete [] entry.offset; + } + } } void ImageTable::Read(IReadObjectContext * context, IStream * stream) @@ -49,16 +53,17 @@ void ImageTable::Read(IReadObjectContext * context, IStream * stream) imageDataSize = (uint32)remainingBytes; } - _dataSize = imageDataSize; - _data = Memory::Reallocate(_data, _dataSize); - if (_data == nullptr) + auto dataSize = (size_t)imageDataSize; + auto data = std::make_unique(dataSize); + if (data == nullptr) { context->LogError(OBJECT_ERROR_BAD_IMAGE_TABLE, "Image table too large."); throw std::runtime_error("Image table too large."); } // Read g1 element headers - uintptr_t imageDataBase = (uintptr_t)_data; + uintptr_t imageDataBase = (uintptr_t)data.get(); + std::vector newEntries; for (uint32 i = 0; i < numImages; i++) { rct_g1_element g1Element; @@ -73,23 +78,22 @@ void ImageTable::Read(IReadObjectContext * context, IStream * stream) g1Element.flags = stream->ReadValue(); g1Element.zoomed_offset = stream->ReadValue(); - _entries.push_back(g1Element); + newEntries.push_back(g1Element); } // Read g1 element data - size_t readBytes = (size_t)stream->TryRead(_data, _dataSize); + size_t readBytes = (size_t)stream->TryRead(data.get(), dataSize); // If data is shorter than expected (some custom objects are unfortunately like that) - size_t unreadBytes = _dataSize - readBytes; + size_t unreadBytes = dataSize - readBytes; if (unreadBytes > 0) { - uint8 * ptr = (uint8*)(((uintptr_t)_data) + readBytes); - std::fill_n(ptr, unreadBytes, 0); - + std::fill_n(data.get() + readBytes, unreadBytes, 0); context->LogWarning(OBJECT_ERROR_BAD_IMAGE_TABLE, "Image table size shorter than expected."); } - // TODO validate the image data to prevent crashes in-game + _data = std::move(data); + _entries.insert(_entries.end(), newEntries.begin(), newEntries.end()); } catch (const std::exception &) { @@ -97,3 +101,19 @@ void ImageTable::Read(IReadObjectContext * context, IStream * stream) throw; } } + +void ImageTable::AddImage(const rct_g1_element * g1) +{ + rct_g1_element newg1 = *g1; + auto length = g1_calculate_data_size(g1); + if (length == 0) + { + newg1.offset = nullptr; + } + else + { + newg1.offset = new uint8[length]; + std::copy_n(g1->offset, length, newg1.offset); + } + _entries.push_back(newg1); +} diff --git a/src/openrct2/object/ImageTable.h b/src/openrct2/object/ImageTable.h index 9edfa20391..4d90b946f4 100644 --- a/src/openrct2/object/ImageTable.h +++ b/src/openrct2/object/ImageTable.h @@ -16,9 +16,9 @@ #pragma once +#include #include #include "../common.h" - #include "../drawing/Drawing.h" interface IReadObjectContext; @@ -27,14 +27,17 @@ interface IStream; class ImageTable { private: + std::unique_ptr _data; std::vector _entries; - void * _data = nullptr; - size_t _dataSize = 0; public: + ImageTable() = default; + ImageTable(const ImageTable &) = delete; + ImageTable & operator=(const ImageTable &) = delete; ~ImageTable(); void Read(IReadObjectContext * context, IStream * stream); const rct_g1_element * GetImages() const { return _entries.data(); } uint32 GetCount() const { return (uint32)_entries.size(); } + void AddImage(const rct_g1_element * g1); }; diff --git a/src/openrct2/object/LargeSceneryObject.cpp b/src/openrct2/object/LargeSceneryObject.cpp index a11698e276..2c1aa6fcd0 100644 --- a/src/openrct2/object/LargeSceneryObject.cpp +++ b/src/openrct2/object/LargeSceneryObject.cpp @@ -14,12 +14,17 @@ *****************************************************************************/ #pragma endregion +#pragma warning(disable : 4706) // assignment within conditional expression + +#include #include "../core/IStream.hpp" #include "../core/Memory.hpp" -#include "LargeSceneryObject.h" - +#include "../core/Util.hpp" #include "../drawing/Drawing.h" +#include "../interface/Cursors.h" #include "../localisation/Language.h" +#include "LargeSceneryObject.h" +#include "ObjectJsonHelpers.h" void LargeSceneryObject::ReadLegacy(IReadObjectContext * context, IStream * stream) { @@ -33,7 +38,7 @@ void LargeSceneryObject::ReadLegacy(IReadObjectContext * context, IStream * stre _legacyType.large_scenery.scrolling_mode = stream->ReadValue(); stream->Seek(4, STREAM_SEEK_CURRENT); - GetStringTable()->Read(context, stream, OBJ_STRING_ID_NAME); + GetStringTable().Read(context, stream, OBJ_STRING_ID_NAME); rct_object_entry sgEntry = stream->ReadValue(); SetPrimarySceneryGroup(&sgEntry); @@ -47,7 +52,7 @@ void LargeSceneryObject::ReadLegacy(IReadObjectContext * context, IStream * stre _tiles = ReadTiles(stream); - GetImageTable()->Read(context, stream); + GetImageTable().Read(context, stream); // Validate properties if (_legacyType.large_scenery.price <= 0) @@ -67,9 +72,9 @@ void LargeSceneryObject::ReadLegacy(IReadObjectContext * context, IStream * stre void LargeSceneryObject::Load() { - GetStringTable()->Sort(); + GetStringTable().Sort(); _legacyType.name = language_allocate_object_string(GetName()); - _baseImageId = gfx_object_allocate_images(GetImageTable()->GetImages(), GetImageTable()->GetCount()); + _baseImageId = gfx_object_allocate_images(GetImageTable().GetImages(), GetImageTable().GetCount()); _legacyType.image = _baseImageId; _legacyType.large_scenery.tiles = _tiles.data(); @@ -79,19 +84,20 @@ void LargeSceneryObject::Load() _legacyType.large_scenery.text_image = _legacyType.image; if (_3dFont->flags & LARGE_SCENERY_TEXT_FLAG_VERTICAL) { - _legacyType.image += _3dFont->var_D * 2; + _legacyType.image += _3dFont->num_images * 2; } else { - _legacyType.image += _3dFont->var_D * 4; + _legacyType.image += _3dFont->num_images * 4; } + _legacyType.large_scenery.text = _3dFont.get(); } } void LargeSceneryObject::Unload() { language_free_object_string(_legacyType.name); - gfx_object_free_images(_baseImageId, GetImageTable()->GetCount()); + gfx_object_free_images(_baseImageId, GetImageTable().GetCount()); _legacyType.name = 0; _legacyType.image = 0; @@ -118,3 +124,147 @@ std::vector LargeSceneryObject::ReadTiles(IStream * stre tiles.push_back({ -1, -1, -1, 255, 0xFFFF }); return tiles; } + +void LargeSceneryObject::ReadJson(IReadObjectContext * context, const json_t * root) +{ + auto properties = json_object_get(root, "properties"); + + _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 jScrollingMode = json_object_get(properties, "scrollingMode"); + _legacyType.large_scenery.scrolling_mode = jScrollingMode != nullptr ? + json_integer_value(jScrollingMode) : + -1; + + // Flags + _legacyType.large_scenery.flags = ObjectJsonHelpers::GetFlags(properties, { + { "hasPrimaryColour", LARGE_SCENERY_FLAG_HAS_PRIMARY_COLOUR }, + { "hasSecondaryColour", LARGE_SCENERY_FLAG_HAS_SECONDARY_COLOUR }, + { "isAnimated", LARGE_SCENERY_FLAG_ANIMATED }, + { "isPhotogenic", LARGE_SCENERY_FLAG_PHOTOGENIC } }); + + // Tiles + auto jTiles = json_object_get(properties, "tiles"); + if (jTiles != nullptr) + { + _tiles = ReadJsonTiles(jTiles); + } + + // 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()); +} + +std::vector LargeSceneryObject::ReadJsonTiles(const json_t * jTiles) +{ + std::vector tiles; + size_t index; + const json_t * jTile; + json_array_foreach(jTiles, index, jTile) + { + rct_large_scenery_tile tile = { 0 }; + 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")) + { + tile.flags |= LARGE_SCENERY_TILE_FLAG_NO_SUPPORTS; + } + if (ObjectJsonHelpers::GetBoolean(jTile, "allowSupportsAbove")) + { + tile.flags |= LARGE_SCENERY_TILE_FLAG_ALLOW_SUPPORTS_ABOVE; + } + + // All corners are by default occupied + auto jCorners = json_object_get(jTile, "corners"); + auto corners = 0xF; + if (jCorners != nullptr) + { + corners = json_integer_value(jCorners); + } + 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 + // We should remove this later by improving the code base to use tiles array length + tiles.push_back({ -1, -1, -1, 0xFF, 0xFFFF }); + + return tiles; +} + +std::unique_ptr LargeSceneryObject::ReadJson3dFont(const json_t * j3dFont) +{ + auto font = std::make_unique(); + + auto jOffsets = json_object_get(j3dFont, "offsets"); + if (jOffsets != nullptr) + { + auto offsets = ReadJsonOffsets(jOffsets); + auto numOffsets = std::min(Util::CountOf(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(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 glyphs = ReadJsonGlyphs(jGlyphs); + auto numGlyphs = std::min(Util::CountOf(font->glyphs), glyphs.size()); + std::copy_n(glyphs.data(), numGlyphs, font->glyphs); + } + + return font; +} + +std::vector LargeSceneryObject::ReadJsonOffsets(const json_t * jOffsets) +{ + std::vector offsets; + size_t index; + const json_t * jOffset; + json_array_foreach(jOffsets, index, jOffset) + { + LocationXY16 offset = { 0 }; + offset.x = json_integer_value(json_object_get(jOffset, "x")); + offset.y = json_integer_value(json_object_get(jOffset, "y")); + offsets.push_back(offset); + } + return offsets; +} + +std::vector LargeSceneryObject::ReadJsonGlyphs(const json_t * jGlpyhs) +{ + std::vector glyphs; + size_t index; + const json_t * jGlyph; + json_array_foreach(jGlpyhs, index, jGlyph) + { + 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); + } + return glyphs; +} diff --git a/src/openrct2/object/LargeSceneryObject.h b/src/openrct2/object/LargeSceneryObject.h index e28eb5d1a3..27d6a232df 100644 --- a/src/openrct2/object/LargeSceneryObject.h +++ b/src/openrct2/object/LargeSceneryObject.h @@ -35,6 +35,7 @@ public: void * GetLegacyData() override { return &_legacyType; } void ReadLegacy(IReadObjectContext * context, IStream * stream) override; + void ReadJson(IReadObjectContext * context, const json_t * root) override; void Load() override; void Unload() override; @@ -42,4 +43,8 @@ public: private: static std::vector ReadTiles(IStream * stream); + static std::vector ReadJsonTiles(const json_t * jTiles); + static std::unique_ptr ReadJson3dFont(const json_t * j3dFont); + static std::vector ReadJsonOffsets(const json_t * jOffsets); + static std::vector ReadJsonGlyphs(const json_t * jGlpyhs); }; diff --git a/src/openrct2/object/Object.cpp b/src/openrct2/object/Object.cpp index b5d301e6b6..fa83f26d0a 100644 --- a/src/openrct2/object/Object.cpp +++ b/src/openrct2/object/Object.cpp @@ -71,7 +71,7 @@ std::string Object::GetString(uint8 index) const auto sz = GetOverrideString(index); if (sz.empty()) { - sz = GetStringTable()->GetString(index); + sz = GetStringTable().GetString(index); } return sz; } diff --git a/src/openrct2/object/Object.h b/src/openrct2/object/Object.h index aa361cdf33..e86a191ce7 100644 --- a/src/openrct2/object/Object.h +++ b/src/openrct2/object/Object.h @@ -17,6 +17,7 @@ #pragma once #include "../common.h" +#include "../core/Json.hpp" #include "ImageTable.h" #include "StringTable.h" @@ -110,16 +111,6 @@ struct rct_object_filters { assert_struct_size(rct_object_filters, 3); #pragma pack(pop) -enum OBJ_STRING_ID -{ - OBJ_STRING_ID_NAME, - OBJ_STRING_ID_DESCRIPTION, - OBJ_STRING_ID_SCENARIO_NAME = 0, - OBJ_STRING_ID_PARK_NAME = 1, - OBJ_STRING_ID_SCENARIO_DETAILS = 2, - OBJ_STRING_ID_CAPACITY = 2, -}; - interface IStream; struct ObjectRepositoryItem; struct rct_drawpixelinfo; @@ -128,6 +119,8 @@ interface IReadObjectContext { virtual ~IReadObjectContext() = default; + virtual bool ShouldLoadImages() abstract; + virtual void LogWarning(uint32 code, const utf8 * text) abstract; virtual void LogError(uint32 code, const utf8 * text) abstract; }; @@ -146,9 +139,9 @@ private: ImageTable _imageTable; protected: - StringTable * GetStringTable() { return &_stringTable; } - const StringTable * GetStringTable() const { return &_stringTable; } - ImageTable * GetImageTable() { return &_imageTable; } + StringTable & GetStringTable() { return _stringTable; } + const StringTable & GetStringTable() const { return _stringTable; } + ImageTable & GetImageTable() { return _imageTable; } std::string GetOverrideString(uint8 index) const; std::string GetString(uint8 index) const; @@ -168,6 +161,7 @@ public: const rct_object_entry * GetObjectEntry() const { return &_objectEntry; } virtual void * GetLegacyData() abstract; + virtual void ReadJson(IReadObjectContext * context, const json_t * root) { } virtual void ReadLegacy(IReadObjectContext * context, IStream * stream) abstract; virtual void Load() abstract; virtual void Unload() abstract; @@ -179,6 +173,8 @@ public: virtual void SetRepositoryItem(ObjectRepositoryItem * item) const { } + const ImageTable & GetImageTable() const { return _imageTable; } + rct_object_entry GetScgWallsHeader(); rct_object_entry GetScgPathXHeader(); rct_object_entry CreateHeader(const char name[9], uint32 flags, uint32 checksum); diff --git a/src/openrct2/object/ObjectFactory.cpp b/src/openrct2/object/ObjectFactory.cpp index 163be81b37..2956864330 100644 --- a/src/openrct2/object/ObjectFactory.cpp +++ b/src/openrct2/object/ObjectFactory.cpp @@ -16,9 +16,11 @@ #include "../core/Console.hpp" #include "../core/FileStream.hpp" +#include "../core/Json.hpp" #include "../core/Memory.hpp" #include "../core/MemoryStream.h" #include "../core/String.hpp" +#include "../OpenRCT2.h" #include "../rct12/SawyerChunkReader.h" #include "BannerObject.h" #include "EntranceObject.h" @@ -39,23 +41,24 @@ class ReadObjectContext : public IReadObjectContext { private: - utf8 * _objectName; - bool _wasWarning = false; - bool _wasError = false; + std::string _objectName; + bool _loadImages; + bool _wasWarning = false; + bool _wasError = false; public: bool WasWarning() const { return _wasWarning; } bool WasError() const { return _wasError; } - explicit ReadObjectContext(const utf8 * objectFileName) + ReadObjectContext(const std::string &objectName, bool loadImages) + : _objectName(objectName), + _loadImages(loadImages) { - _objectName = String::Duplicate(objectFileName); } - ~ReadObjectContext() override + bool ShouldLoadImages() override { - Memory::Free(_objectName); - _objectName = nullptr; + return _loadImages; } void LogWarning(uint32 code, const utf8 * text) override @@ -64,7 +67,7 @@ public: if (!String::IsNullOrEmpty(text)) { - log_verbose("[%s] Warning: %s", _objectName, text); + Console::Error::WriteLine("[%s] Warning: %s", _objectName.c_str(), text); } } @@ -74,7 +77,7 @@ public: if (!String::IsNullOrEmpty(text)) { - Console::Error::WriteLine("[%s] Error: %s", _objectName, text); + Console::Error::WriteLine("[%s] Error: %s", _objectName.c_str(), text); } } }; @@ -119,7 +122,7 @@ namespace ObjectFactory log_verbose(" size: %zu", chunk->GetLength()); auto chunkStream = MemoryStream(chunk->GetData(), chunk->GetLength()); - auto readContext = ReadObjectContext(objectName); + auto readContext = ReadObjectContext(objectName, !gOpenRCT2Headless); ReadObjectLegacy(result, &readContext, &chunkStream); if (readContext.WasError()) { @@ -128,8 +131,6 @@ namespace ObjectFactory } catch (const std::exception &) { - Console::Error::WriteLine("Unable to open or read '%s'", path); - delete result; result = nullptr; } @@ -147,7 +148,7 @@ namespace ObjectFactory utf8 objectName[DAT_NAME_LENGTH + 1]; object_entry_get_name_fixed(objectName, sizeof(objectName), entry); - auto readContext = ReadObjectContext(objectName); + auto readContext = ReadObjectContext(objectName, !gOpenRCT2Headless); auto chunkStream = MemoryStream(data, dataSize); ReadObjectLegacy(result, &readContext, &chunkStream); @@ -203,4 +204,68 @@ namespace ObjectFactory } return result; } + + static uint8 ParseObjectType(const std::string &s) + { + if (s == "ride") return OBJECT_TYPE_RIDE; + if (s == "footpath") return OBJECT_TYPE_PATHS; + if (s == "footpath_banner") return OBJECT_TYPE_BANNERS; + if (s == "footpath_item") return OBJECT_TYPE_PATH_BITS; + if (s == "scenery_small") return OBJECT_TYPE_SMALL_SCENERY; + if (s == "scenery_large") return OBJECT_TYPE_LARGE_SCENERY; + if (s == "scenery_wall") return OBJECT_TYPE_WALLS; + if (s == "scenery_group") return OBJECT_TYPE_SCENERY_GROUP; + if (s == "park_entrance") return OBJECT_TYPE_PARK_ENTRANCE; + if (s == "water") return OBJECT_TYPE_WATER; + return 0xFF; + } + + Object * CreateObjectFromJsonFile(const std::string &path) + { + log_verbose("CreateObjectFromJsonFile(\"%s\")", path.c_str()); + + Object * result = nullptr; + try + { + auto jRoot = Json::ReadFromFile(path.c_str()); + auto jObjectType = json_object_get(jRoot, "objectType"); + if (json_is_string(jObjectType)) + { + auto objectType = ParseObjectType(json_string_value(jObjectType)); + if (objectType != 0xFF) + { + auto id = json_string_value(json_object_get(jRoot, "id")); + + rct_object_entry entry = { 0 }; + 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), 0, 16); + originalName = originalId.substr(9, 8); + entry.checksum = std::stoul(originalId.substr(18, 8), 0, 16); + } + auto minLength = std::min(8, originalName.length()); + memcpy(entry.name, originalName.c_str(), minLength); + + result = CreateObject(entry); + auto readContext = ReadObjectContext(id, !gOpenRCT2Headless); + result->ReadJson(&readContext, jRoot); + if (readContext.WasError()) + { + throw std::runtime_error("Object has errors"); + } + } + } + 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; + } } diff --git a/src/openrct2/object/ObjectFactory.h b/src/openrct2/object/ObjectFactory.h index 5db5444e41..4e88179058 100644 --- a/src/openrct2/object/ObjectFactory.h +++ b/src/openrct2/object/ObjectFactory.h @@ -26,4 +26,6 @@ namespace ObjectFactory Object * CreateObjectFromLegacyFile(const utf8 * path); Object * CreateObjectFromLegacyData(const rct_object_entry * entry, const void * data, size_t dataSize); Object * CreateObject(const rct_object_entry &entry); + + Object * CreateObjectFromJsonFile(const std::string &path); } diff --git a/src/openrct2/object/ObjectJsonHelpers.cpp b/src/openrct2/object/ObjectJsonHelpers.cpp new file mode 100644 index 0000000000..277f003ac1 --- /dev/null +++ b/src/openrct2/object/ObjectJsonHelpers.cpp @@ -0,0 +1,377 @@ +#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#pragma warning(disable : 4706) // assignment within conditional expression + +#include +#include +#include +#include "../Context.h" +#include "../core/File.h" +#include "../core/FileScanner.h" +#include "../core/Math.hpp" +#include "../core/Memory.hpp" +#include "../core/Path.hpp" +#include "../core/String.hpp" +#include "../interface/Cursors.h" +#include "../localisation/Language.h" +#include "../PlatformEnvironment.h" +#include "../sprites.h" +#include "Object.h" +#include "ObjectFactory.h" +#include "ObjectJsonHelpers.h" + +using namespace OpenRCT2; + +namespace ObjectJsonHelpers +{ + 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; + } + + sint32 GetInteger(const json_t * obj, const std::string &name, const sint32 &defaultValue) + { + auto value = json_object_get(obj, name.c_str()); + if (json_is_integer(value)) + { + sint64 val = json_integer_value(value); + if (val >= std::numeric_limits::min() && + val <= std::numeric_limits::max()) + { + return static_cast(val); + } + } + return defaultValue; + } + + float GetFloat(const json_t * obj, const std::string &name, const float &defaultValue) + { + auto value = json_object_get(obj, name.c_str()); + return json_is_number(value) ? + json_number_value(value) : + defaultValue; + } + + std::vector GetJsonStringArray(const json_t * arr) + { + std::vector result; + if (json_is_array(arr)) + { + auto count = json_array_size(arr); + result.reserve(count); + for (size_t i = 0; i < count; i++) + { + auto element = json_string_value(json_array_get(arr, i)); + result.push_back(element); + } + } + else if (json_is_string(arr)) + { + result.push_back(json_string_value(arr)); + } + return result; + } + + std::vector GetJsonIntegerArray(const json_t * arr) + { + std::vector result; + if (json_is_array(arr)) + { + auto count = json_array_size(arr); + result.reserve(count); + for (size_t i = 0; i < count; i++) + { + auto element = json_integer_value(json_array_get(arr, i)); + result.push_back(element); + } + } + else if (json_is_integer(arr)) + { + result.push_back(json_integer_value(arr)); + } + return result; + } + + uint8 ParseCursor(const std::string &s, uint8 defaultValue) + { + static const std::unordered_map LookupTable + { + { "CURSOR_BLANK", CURSOR_BLANK }, + { "CURSOR_UP_ARROW", CURSOR_UP_ARROW }, + { "CURSOR_UP_DOWN_ARROW", CURSOR_UP_DOWN_ARROW }, + { "CURSOR_HAND_POINT", CURSOR_HAND_POINT }, + { "CURSOR_ZZZ", CURSOR_ZZZ }, + { "CURSOR_DIAGONAL_ARROWS", CURSOR_DIAGONAL_ARROWS }, + { "CURSOR_PICKER", CURSOR_PICKER }, + { "CURSOR_TREE_DOWN", CURSOR_TREE_DOWN }, + { "CURSOR_FOUNTAIN_DOWN", CURSOR_FOUNTAIN_DOWN }, + { "CURSOR_STATUE_DOWN", CURSOR_STATUE_DOWN }, + { "CURSOR_BENCH_DOWN", CURSOR_BENCH_DOWN }, + { "CURSOR_CROSS_HAIR", CURSOR_CROSS_HAIR }, + { "CURSOR_BIN_DOWN", CURSOR_BIN_DOWN }, + { "CURSOR_LAMPPOST_DOWN", CURSOR_LAMPPOST_DOWN }, + { "CURSOR_FENCE_DOWN", CURSOR_FENCE_DOWN }, + { "CURSOR_FLOWER_DOWN", CURSOR_FLOWER_DOWN }, + { "CURSOR_PATH_DOWN", CURSOR_PATH_DOWN }, + { "CURSOR_DIG_DOWN", CURSOR_DIG_DOWN }, + { "CURSOR_WATER_DOWN", CURSOR_WATER_DOWN }, + { "CURSOR_HOUSE_DOWN", CURSOR_HOUSE_DOWN }, + { "CURSOR_VOLCANO_DOWN", CURSOR_VOLCANO_DOWN }, + { "CURSOR_WALK_DOWN", CURSOR_WALK_DOWN }, + { "CURSOR_PAINT_DOWN", CURSOR_PAINT_DOWN }, + { "CURSOR_ENTRANCE_DOWN", CURSOR_ENTRANCE_DOWN }, + { "CURSOR_HAND_OPEN", CURSOR_HAND_OPEN }, + { "CURSOR_HAND_CLOSED", CURSOR_HAND_CLOSED }, + { "CURSOR_ARROW", CURSOR_ARROW }, + }; + + auto result = LookupTable.find(s); + return (result != LookupTable.end()) ? + result->second : + defaultValue; + } + + rct_object_entry ParseObjectEntry(const std::string & s) + { + rct_object_entry entry = { 0 }; + std::fill_n(entry.name, sizeof(entry.name), ' '); + auto copyLen = std::min(8, s.size()); + std::copy_n(s.c_str(), copyLen, entry.name); + return entry; + } + + static std::vector ParseRange(std::string s) + { + // Currently only supports [###] or [###..###] + std::vector result = { }; + if (s.length() >= 3 && s[0] == '[' && s[s.length() - 1] == ']') + { + s = s.substr(1, s.length() - 2); + auto parts = String::Split(s, ".."); + if (parts.size() == 1) + { + result.push_back(std::stoi(parts[0])); + } + else + { + auto left = std::stoi(parts[0]); + auto right = std::stoi(parts[1]); + if (left <= right) + { + for (auto i = left; i <= right; i++) + { + result.push_back(i); + } + } + else + { + for (auto i = right; i >= left; i--) + { + result.push_back(i); + } + } + } + } + return result; + } + + static std::string FindLegacyObject(const std::string &name) + { + const auto env = GetContext()->GetPlatformEnvironment(); + auto objectsPath = env->GetDirectoryPath(DIRBASE::RCT2, DIRID::OBJECT); + auto objectPath = Path::Combine(objectsPath, name); +#ifndef _WIN32 + if (!File::Exists(objectPath)) + { + // UNIX based systems need to search for any files with the same name + // due to case sensitivity. + auto filter = Path::Combine(objectsPath, "*.dat"); + auto scanner = std::unique_ptr(Path::ScanDirectory(filter, false)); + while (scanner->Next()) + { + auto relativePath = scanner->GetPathRelative(); + if (String::Equals(relativePath, name, true)) + { + objectPath = scanner->GetPath(); + break; + } + } + } +#endif + return objectPath; + } + + static std::vector LoadObjectImages(IReadObjectContext * context, const std::string &name, const std::vector &range) + { + std::vector result; + auto objectPath = FindLegacyObject(name); + auto obj = ObjectFactory::CreateObjectFromLegacyFile(objectPath.c_str()); + if (obj != nullptr) + { + auto &imgTable = static_cast(obj)->GetImageTable(); + auto numImages = (sint32)imgTable.GetCount(); + auto images = imgTable.GetImages(); + size_t placeHoldersAdded = 0; + for (auto i : range) + { + if (i >= 0 && i < numImages) + { + auto &objg1 = images[i]; + auto length = g1_calculate_data_size(&objg1); + auto g1 = objg1; + g1.offset = (uint8 *)std::malloc(length); + std::memcpy(g1.offset, objg1.offset, length); + result.push_back(g1); + } + else + { + auto g1 = rct_g1_element{}; + result.push_back(g1); + 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()); + result.resize(range.size()); + } + return result; + } + + static std::vector ParseImages(IReadObjectContext * context, std::string s) + { + std::vector result; + if (s.empty()) + { + rct_g1_element emptyg1 = { 0 }; + result.push_back(emptyg1); + } + else if (String::StartsWith(s, "$CSG")) + { + if (is_csg_loaded()) + { + auto range = ParseRange(s.substr(4)); + if (range.size() > 0) + { + for (auto i : range) + { + auto &csg1 = *gfx_get_g1_element(SPR_CSG_BEGIN + i); + auto length = g1_calculate_data_size(&csg1); + auto g1 = csg1; + g1.offset = (uint8 *)std::malloc(length); + std::memcpy(g1.offset, csg1.offset, length); + result.push_back(g1); + } + } + } + } + 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); + } + } + return result; + } + + static uint8 ParseStringId(const std::string &s) + { + if (s == "name") return OBJ_STRING_ID_NAME; + if (s == "description") return OBJ_STRING_ID_DESCRIPTION; + if (s == "capacity") return OBJ_STRING_ID_CAPACITY; + if (s == "vehicleName") return OBJ_STRING_ID_VEHICLE_NAME; + return OBJ_STRING_ID_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 != OBJ_STRING_ID_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()) + { + auto jsonImages = json_object_get(root, "images"); + auto imageElements = GetJsonStringArray(jsonImages); + for (const auto &ie : imageElements) + { + auto images = ParseImages(context, ie); + for (const auto &g1 : images) + { + imageTable.AddImage(&g1); + std::free(g1.offset); + } + } + } + } +} diff --git a/src/openrct2/object/ObjectJsonHelpers.h b/src/openrct2/object/ObjectJsonHelpers.h new file mode 100644 index 0000000000..5f56a523bb --- /dev/null +++ b/src/openrct2/object/ObjectJsonHelpers.h @@ -0,0 +1,57 @@ +#pragma region Copyright (c) 2014-2018 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#pragma once + +#include +#include +#include +#include +#include "../common.h" +#include "../core/Json.hpp" +#include "../drawing/Drawing.h" +#include "../object/Object.h" +#include "ImageTable.h" +#include "StringTable.h" + +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 = ""); + sint32 GetInteger(const json_t * obj, const std::string &name, const sint32 &defaultValue = 0); + float GetFloat(const json_t * obj, const std::string &name, const float &defaultValue = 0); + std::vector GetJsonStringArray(const json_t * arr); + std::vector GetJsonIntegerArray(const json_t * arr); + uint8 ParseCursor(const std::string &s, uint8 defaultValue); + rct_object_entry ParseObjectEntry(const std::string & s); + void LoadStrings(const json_t * root, StringTable &stringTable); + void LoadImages(IReadObjectContext * context, const json_t * root, ImageTable &imageTable); + + template + static T GetFlags(const json_t * obj, std::initializer_list> list) + { + T flags = 0; + for (const auto &item : list) + { + if (GetBoolean(obj, item.first)) + { + flags |= item.second; + } + } + return flags; + } +}; diff --git a/src/openrct2/object/ObjectRepository.cpp b/src/openrct2/object/ObjectRepository.cpp index 0b423d38ac..0df230554c 100644 --- a/src/openrct2/object/ObjectRepository.cpp +++ b/src/openrct2/object/ObjectRepository.cpp @@ -80,7 +80,7 @@ class ObjectFileIndex final : public FileIndex private: static constexpr uint32 MAGIC_NUMBER = 0x5844494F; // OIDX static constexpr uint16 VERSION = 17; - static constexpr auto PATTERN = "*.dat;*.pob"; + static constexpr auto PATTERN = "*.dat;*.pob;*.json"; public: explicit ObjectFileIndex(IPlatformEnvironment * env) : @@ -90,7 +90,7 @@ public: env->GetFilePath(PATHID::CACHE_OBJECTS), std::string(PATTERN), std::vector({ - env->GetDirectoryPath(DIRBASE::RCT2, DIRID::OBJECT), + env->GetDirectoryPath(DIRBASE::OPENRCT2, DIRID::OBJECT), env->GetDirectoryPath(DIRBASE::USER, DIRID::OBJECT) })) { } @@ -98,21 +98,36 @@ public: public: std::tuple Create(const std::string &path) const override { - auto object = ObjectFactory::CreateObjectFromLegacyFile(path.c_str()); - if (object != nullptr) + auto extension = Path::GetExtension(path); + if (String::Equals(extension, ".json", true)) { - ObjectRepositoryItem item = { 0 }; - item.ObjectEntry = *object->GetObjectEntry(); - item.Path = String::Duplicate(path); - item.Name = String::Duplicate(object->GetName()); - object->SetRepositoryItem(&item); - delete object; - return std::make_tuple(true, item); + auto object = ObjectFactory::CreateObjectFromJsonFile(path); + if (object != nullptr) + { + ObjectRepositoryItem item = { 0 }; + item.ObjectEntry = *object->GetObjectEntry(); + item.Path = String::Duplicate(path); + item.Name = String::Duplicate(object->GetName()); + object->SetRepositoryItem(&item); + delete object; + return std::make_tuple(true, item); + } } else { - return std::make_tuple(false, ObjectRepositoryItem()); + auto object = ObjectFactory::CreateObjectFromLegacyFile(path.c_str()); + if (object != nullptr) + { + ObjectRepositoryItem item = { 0 }; + item.ObjectEntry = *object->GetObjectEntry(); + item.Path = String::Duplicate(path); + item.Name = String::Duplicate(object->GetName()); + object->SetRepositoryItem(&item); + delete object; + return std::make_tuple(true, item); + } } + return std::make_tuple(false, ObjectRepositoryItem()); } protected: @@ -260,8 +275,15 @@ public: { Guard::ArgumentNotNull(ori, GUARD_LINE); - Object * object = ObjectFactory::CreateObjectFromLegacyFile(ori->Path); - return object; + auto extension = Path::GetExtension(ori->Path); + if (String::Equals(extension, ".json", true)) + { + return ObjectFactory::CreateObjectFromJsonFile(ori->Path); + } + else + { + return ObjectFactory::CreateObjectFromLegacyFile(ori->Path); + } } void RegisterLoadedObject(const ObjectRepositoryItem * ori, Object * object) override diff --git a/src/openrct2/object/RideObject.cpp b/src/openrct2/object/RideObject.cpp index f194ce4ed9..da7dde5826 100644 --- a/src/openrct2/object/RideObject.cpp +++ b/src/openrct2/object/RideObject.cpp @@ -14,27 +14,27 @@ *****************************************************************************/ #pragma endregion +#pragma warning(disable : 4706) // assignment within conditional expression + +#include +#include #include "../core/IStream.hpp" +#include "../core/Math.hpp" #include "../core/Memory.hpp" #include "../core/String.hpp" -#include "../OpenRCT2.h" -#include "ObjectRepository.h" -#include "RideObject.h" +#include "../core/Util.hpp" #include "../ride/RideGroupManager.h" - #include "../drawing/Drawing.h" #include "../localisation/Language.h" #include "../rct2/RCT2.h" #include "../ride/Ride.h" #include "../ride/Track.h" +#include "../OpenRCT2.h" +#include "ObjectJsonHelpers.h" +#include "ObjectRepository.h" +#include "RideObject.h" -RideObject::~RideObject() -{ - for (auto &peepLoadingPosition : _peepLoadingPositions) - { - Memory::Free(peepLoadingPosition); - } -} +using namespace OpenRCT2; void RideObject::ReadLegacy(IReadObjectContext * context, IStream * stream) { @@ -70,9 +70,9 @@ void RideObject::ReadLegacy(IReadObjectContext * context, IStream * stream) _legacyType.shop_item = stream->ReadValue(); _legacyType.shop_item_secondary = stream->ReadValue(); - GetStringTable()->Read(context, stream, OBJ_STRING_ID_NAME); - GetStringTable()->Read(context, stream, OBJ_STRING_ID_DESCRIPTION); - GetStringTable()->Read(context, stream, OBJ_STRING_ID_CAPACITY); + GetStringTable().Read(context, stream, OBJ_STRING_ID_NAME); + GetStringTable().Read(context, stream, OBJ_STRING_ID_DESCRIPTION); + GetStringTable().Read(context, stream, OBJ_STRING_ID_CAPACITY); // Read preset colours, by default there are 32 _presetColours.count = stream->ReadValue(); @@ -98,11 +98,13 @@ void RideObject::ReadLegacy(IReadObjectContext * context, IStream * stream) { numPeepLoadingPositions = stream->ReadValue(); } - _peepLoadingPositions[i] = stream->ReadArray(numPeepLoadingPositions); - _peepLoadingPositionsCount[i] = numPeepLoadingPositions; + + auto data = stream->ReadArray(numPeepLoadingPositions); + _peepLoadingPositions[i] = std::vector(data, data + numPeepLoadingPositions); + Memory::Free(data); } - GetImageTable()->Read(context, stream); + GetImageTable().Read(context, stream); // Validate properties if (_legacyType.excitement_multiplier > 75) @@ -117,17 +119,17 @@ void RideObject::ReadLegacy(IReadObjectContext * context, IStream * stream) { context->LogError(OBJECT_ERROR_INVALID_PROPERTY, "Nausea multiplier too high."); } - - PerformFixes(); } void RideObject::Load() { - GetStringTable()->Sort(); + _legacyType.obj = this; + + GetStringTable().Sort(); _legacyType.naming.name = language_allocate_object_string(GetName()); _legacyType.naming.description = language_allocate_object_string(GetDescription()); _legacyType.capacity = language_allocate_object_string(GetCapacity()); - _legacyType.images_offset = gfx_object_allocate_images(GetImageTable()->GetImages(), GetImageTable()->GetCount()); + _legacyType.images_offset = gfx_object_allocate_images(GetImageTable().GetImages(), GetImageTable().GetCount()); _legacyType.vehicle_preset_list = &_presetColours; sint32 cur_vehicle_images_offset = _legacyType.images_offset + MAX_RIDE_TYPES_PER_RIDE_ENTRY; @@ -281,8 +283,11 @@ void RideObject::Load() set_vehicle_type_image_max_sizes(vehicleEntry, num_images); } } - vehicleEntry->peep_loading_positions = _peepLoadingPositions[i]; - vehicleEntry->peep_loading_positions_count = _peepLoadingPositionsCount[i]; + + if (!_peepLoadingPositions[i].empty()) + { + vehicleEntry->peep_loading_positions = std::move(_peepLoadingPositions[i]); + } } } } @@ -292,7 +297,7 @@ void RideObject::Unload() language_free_object_string(_legacyType.naming.name); language_free_object_string(_legacyType.naming.description); language_free_object_string(_legacyType.capacity); - gfx_object_free_images(_legacyType.images_offset, GetImageTable()->GetCount()); + gfx_object_free_images(_legacyType.images_offset, GetImageTable().GetCount()); _legacyType.naming.name = 0; _legacyType.naming.description = 0; @@ -371,8 +376,7 @@ void RideObject::SetRepositoryItem(ObjectRepositoryItem * item) const void RideObject::ReadLegacyVehicle(IReadObjectContext * context, IStream * stream, rct_ride_entry_vehicle * vehicle) { vehicle->rotation_frame_mask = stream->ReadValue(); - vehicle->num_vertical_frames = stream->ReadValue(); - vehicle->num_horizontal_frames = stream->ReadValue(); + stream->Seek(2 * 1, STREAM_SEEK_CURRENT); vehicle->spacing = stream->ReadValue(); vehicle->car_mass = stream->ReadValue(); vehicle->tab_height = stream->ReadValue(); @@ -384,21 +388,7 @@ void RideObject::ReadLegacyVehicle(IReadObjectContext * context, IStream * strea vehicle->animation = stream->ReadValue(); vehicle->flags = stream->ReadValue(); vehicle->base_num_frames = stream->ReadValue(); - stream->Seek(4, STREAM_SEEK_CURRENT); - vehicle->restraint_image_id = stream->ReadValue(); - vehicle->gentle_slope_image_id = stream->ReadValue(); - vehicle->steep_slope_image_id = stream->ReadValue(); - vehicle->vertical_slope_image_id = stream->ReadValue(); - vehicle->diagonal_slope_image_id = stream->ReadValue(); - vehicle->banked_image_id = stream->ReadValue(); - vehicle->inline_twist_image_id = stream->ReadValue(); - vehicle->flat_to_gentle_bank_image_id = stream->ReadValue(); - vehicle->diagonal_to_gentle_slope_bank_image_id = stream->ReadValue(); - vehicle->gentle_slope_to_bank_image_id = stream->ReadValue(); - vehicle->gentle_slope_bank_turn_image_id = stream->ReadValue(); - vehicle->flat_bank_to_gentle_slope_image_id = stream->ReadValue(); - vehicle->corkscrew_image_id = stream->ReadValue(); - vehicle->no_vehicle_images = stream->ReadValue(); + stream->Seek(15 * 4, STREAM_SEEK_CURRENT); vehicle->no_seating_rows = stream->ReadValue(); vehicle->spinning_inertia = stream->ReadValue(); vehicle->spinning_friction = stream->ReadValue(); @@ -415,54 +405,6 @@ void RideObject::ReadLegacyVehicle(IReadObjectContext * context, IStream * strea stream->Seek(4, STREAM_SEEK_CURRENT); } -void RideObject::PerformFixes() -{ - std::string identifier = GetIdentifier(); - - // Add boosters if the track type is eligible - for (auto rideType : _legacyType.ride_type) - { - if (ride_type_supports_boosters(rideType)) - { - _legacyType.enabledTrackPieces |= (1ULL << TRACK_BOOSTER); - } - } - - // The rocket cars could take 3 cars per train in RCT1. Restore this. - if (String::Equals(identifier, "RCKC ")) - { - _legacyType.max_cars_in_train = 3 + _legacyType.zero_cars; - } - // The Wooden Roller Coaster could take 7 cars per train in RCT1. - else if (String::Equals(identifier, "PTCT1 ")) - { - _legacyType.max_cars_in_train = 7 + _legacyType.zero_cars; - } - // The Looping Roller Coaster could take 8 cars per train in RCT1. - else if (String::Equals(identifier, "SCHT1 ")) - { - _legacyType.max_cars_in_train = 8 + _legacyType.zero_cars; - } - // The Steel Twister could take 8 cars per train in RCT1. (The other two vehicles are already correct.) - else if (String::Equals(identifier, "BMSD ") || String::Equals(identifier, "BMSU ")) - { - _legacyType.max_cars_in_train = 8 + _legacyType.zero_cars; - } - // Wacky Worlds' Crocodile Ride (a log flume vehicle) is incorrectly locked to 5 cars. - else if (String::Equals(identifier, "CROCFLUM")) - { - _legacyType.cars_per_flat_ride = 0xFF; - } - // All vanilla/WW/OCC Junior RC vehicles incorrectly have this flag set - else if (String::Equals(identifier, "ZLDB ") || - String::Equals(identifier, "ZLOG ") || - String::Equals(identifier, "ZPANDA ") || - String::Equals(identifier, "WHICGRUB")) - { - _legacyType.enabledTrackPieces &= ~(1ULL << TRACK_SLOPE_STEEP); - } -} - uint8 RideObject::CalculateNumVerticalFrames(const rct_ride_entry_vehicle * vehicleEntry) { // 0x6DE90B @@ -532,3 +474,642 @@ uint8 RideObject::CalculateNumHorizontalFrames(const rct_ride_entry_vehicle * ve return numHorizontalFrames; } + +void RideObject::ReadJson(IReadObjectContext * context, const json_t * root) +{ + auto properties = json_object_get(root, "properties"); + + auto rideTypes = ObjectJsonHelpers::GetJsonStringArray(json_object_get(properties, "type")); + for (size_t i = 0; i < MAX_RIDE_TYPES_PER_RIDE_ENTRY; i++) + { + uint8 rideType = RIDE_TYPE_NULL; + if (i < rideTypes.size()) + { + rideType = ParseRideType(rideTypes[i]); + if (rideType == RIDE_TYPE_NULL) + { + context->LogError(OBJECT_ERROR_INVALID_PROPERTY, "Unknown ride type"); + } + } + + _legacyType.ride_type[i] = rideType; + } + + auto rideCategories = ObjectJsonHelpers::GetJsonStringArray(json_object_get(properties, "category")); + if (rideCategories.size() >= 1) + { + _legacyType.category[0] = ParseRideCategory(rideCategories[0]); + _legacyType.category[1] = _legacyType.category[0]; + } + if (rideCategories.size() >= 2) + { + _legacyType.category[1] = ParseRideCategory(rideCategories[1]); + } + + _legacyType.max_height = ObjectJsonHelpers::GetInteger(properties, "maxHeight"); + + // This needs to be set for both shops/facilities _and_ regular rides. + _legacyType.shop_item = SHOP_ITEM_NONE; + _legacyType.shop_item_secondary = SHOP_ITEM_NONE; + + 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 = 0xFF; + car.sound_range = 0xFF; + car.draw_order = 6; + + // Shop item + auto rideSells = ObjectJsonHelpers::GetJsonStringArray(json_object_get(json_object_get(root, "properties"), "sells")); + for (size_t i = 0; i < rideSells.size(); i++) + { + auto shopItem = ParseShopItem(rideSells[i]); + if (shopItem == SHOP_ITEM_NONE) + { + context->LogWarning(OBJECT_ERROR_INVALID_PROPERTY, "Unknown shop item"); + } + + if (i == 0) + { + _legacyType.shop_item = ParseShopItem(rideSells[0]); + } + else if (i == 1) + { + _legacyType.shop_item_secondary = ParseShopItem(rideSells[1]); + } + else + { + // More than 2 shop items not supported yet! + } + } + } + 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")); + _presetColours = ReadJsonCarColours(json_object_get(properties, "carColours")); + } + + _legacyType.flags |= ObjectJsonHelpers::GetFlags(properties, { + { "noInversions", RIDE_ENTRY_FLAG_NO_INVERSIONS }, + { "noBanking", RIDE_ENTRY_FLAG_NO_BANKED_TRACK }, + { "playDepartSound", RIDE_ENTRY_FLAG_PLAY_DEPART_SOUND }, + { "RIDE_ENTRY_FLAG_7", RIDE_ENTRY_FLAG_7 }, + { "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 }, + { "RIDE_ENTRY_FLAG_16", RIDE_ENTRY_FLAG_16 }, + { "RIDE_ENTRY_FLAG_18", RIDE_ENTRY_FLAG_18 }, + { "disablePainting", RIDE_ENTRY_FLAG_DISABLE_COLOUR_TAB } }); + + ObjectJsonHelpers::LoadStrings(root, GetStringTable()); + ObjectJsonHelpers::LoadImages(context, root, GetImageTable()); +} + +void RideObject::ReadJsonVehicleInfo(IReadObjectContext * context, const 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")); + + // 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.flags |= RIDE_ENTRY_FLAG_VEHICLE_TAB_SCALE_HALF; + } + + // 0xFF means N/A. + _legacyType.front_vehicle = 0xFF; + _legacyType.second_vehicle = 0xFF; + _legacyType.third_vehicle = 0xFF; + _legacyType.rear_vehicle = 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 numCars = std::min(Util::CountOf(_legacyType.vehicles), cars.size()); + for (size_t i = 0; i < numCars; i++) + { + _legacyType.vehicles[i] = cars[i]; + } +} + +std::vector RideObject::ReadJsonCars(const json_t * jCars) +{ + std::vector cars; + + if (json_is_array(jCars)) + { + json_t * jCar; + size_t index; + json_array_foreach(jCars, index, jCar) + { + auto car = ReadJsonCar(jCar); + cars.push_back(car); + } + } + else if (json_is_object(jCars)) + { + auto car = ReadJsonCar(jCars); + cars.push_back(car); + } + return cars; +} + +rct_ride_entry_vehicle RideObject::ReadJsonCar(const json_t * jCar) +{ + rct_ride_entry_vehicle car = { 0 }; + 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")) + { + car.num_seats |= 0x80; + } + + 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 = 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"); + + auto& peepLoadingPositions = car.peep_loading_positions; + auto jLoadingPositions = json_object_get(jCar, "loadingPositions"); + if (json_is_array(jLoadingPositions)) + { + auto arr = ObjectJsonHelpers::GetJsonIntegerArray(jLoadingPositions); + for (auto x : arr) + { + peepLoadingPositions.push_back(x); + } + } + else + { + auto jLoadingWaypoints = json_object_get(jCar, "loadingWaypoints"); + if (json_is_array(jLoadingWaypoints)) + { + car.flags |= VEHICLE_ENTRY_FLAG_26; + + auto numSegments = ObjectJsonHelpers::GetInteger(jCar, "numSegments"); + if (numSegments == 8) + { + peepLoadingPositions.push_back(1); + } + else if (numSegments == 4) + { + peepLoadingPositions.push_back(1); + } + else + { + peepLoadingPositions.push_back(0); + } + + size_t i; + json_t * route; + json_array_foreach(jLoadingWaypoints, i, route) + { + if (json_is_array(route)) + { + size_t j; + json_t * waypoint; + json_array_foreach(route, j, waypoint) + { + if (json_is_array(waypoint) && json_array_size(waypoint) >= 2) + { + auto x = json_integer_value(json_array_get(waypoint, 0)); + auto y = json_integer_value(json_array_get(waypoint, 1)); + peepLoadingPositions.push_back(x); + peepLoadingPositions.push_back(y); + } + } + peepLoadingPositions.push_back(0); + peepLoadingPositions.push_back(0); + } + } + } + } + + auto jFrames = json_object_get(jCar, "frames"); + car.sprite_flags = ObjectJsonHelpers::GetFlags(jFrames, { + { "flat", VEHICLE_SPRITE_FLAG_FLAT }, + { "gentleSlopes", VEHICLE_SPRITE_FLAG_GENTLE_SLOPES }, + { "steepSlopes", VEHICLE_SPRITE_FLAG_STEEP_SLOPES }, + { "verticalSlopes", VEHICLE_SPRITE_FLAG_VERTICAL_SLOPES }, + { "diagonalSlopes", VEHICLE_SPRITE_FLAG_DIAGONAL_SLOPES }, + { "flatBanked", VEHICLE_SPRITE_FLAG_FLAT_BANKED }, + { "inlineTwists", VEHICLE_SPRITE_FLAG_INLINE_TWISTS }, + { "flatToGentleSlopeBankedTransitions", VEHICLE_SPRITE_FLAG_FLAT_TO_GENTLE_SLOPE_BANKED_TRANSITIONS }, + { "diagonalGentleSlopeBankedTransitions", VEHICLE_SPRITE_FLAG_DIAGONAL_GENTLE_SLOPE_BANKED_TRANSITIONS }, + { "gentleSlopeBankedTransitions", VEHICLE_SPRITE_FLAG_GENTLE_SLOPE_BANKED_TRANSITIONS }, + { "gentleSlopeBankedTurns", VEHICLE_SPRITE_FLAG_GENTLE_SLOPE_BANKED_TURNS }, + { "flatToGentleSlopeWhileBankedTransitions", VEHICLE_SPRITE_FLAG_FLAT_TO_GENTLE_SLOPE_WHILE_BANKED_TRANSITIONS }, + { "corkscrews", VEHICLE_SPRITE_FLAG_CORKSCREWS }, + { "restraintAnimation", VEHICLE_SPRITE_FLAG_RESTRAINT_ANIMATION }, + { "curvedLiftHill", VEHICLE_SPRITE_FLAG_CURVED_LIFT_HILL }, + { "VEHICLE_SPRITE_FLAG_15", VEHICLE_SPRITE_FLAG_15 } }); + + car.flags |= ObjectJsonHelpers::GetFlags(jCar, { + { "VEHICLE_ENTRY_FLAG_POWERED_RIDE_UNRESTRICTED_GRAVITY", VEHICLE_ENTRY_FLAG_POWERED_RIDE_UNRESTRICTED_GRAVITY }, + { "VEHICLE_ENTRY_FLAG_NO_UPSTOP_WHEELS", VEHICLE_ENTRY_FLAG_NO_UPSTOP_WHEELS }, + { "VEHICLE_ENTRY_FLAG_NO_UPSTOP_BOBSLEIGH", VEHICLE_ENTRY_FLAG_NO_UPSTOP_BOBSLEIGH }, + { "VEHICLE_ENTRY_FLAG_MINI_GOLF", VEHICLE_ENTRY_FLAG_MINI_GOLF }, + { "VEHICLE_ENTRY_FLAG_4", VEHICLE_ENTRY_FLAG_4 }, + { "VEHICLE_ENTRY_FLAG_5", VEHICLE_ENTRY_FLAG_5 }, + { "VEHICLE_ENTRY_FLAG_HAS_INVERTED_SPRITE_SET", VEHICLE_ENTRY_FLAG_HAS_INVERTED_SPRITE_SET }, + { "VEHICLE_ENTRY_FLAG_DODGEM_INUSE_LIGHTS", VEHICLE_ENTRY_FLAG_DODGEM_INUSE_LIGHTS }, + { "VEHICLE_ENTRY_FLAG_ALLOW_DOORS_DEPRECATED", VEHICLE_ENTRY_FLAG_ALLOW_DOORS_DEPRECATED }, + { "VEHICLE_ENTRY_FLAG_ENABLE_ADDITIONAL_COLOUR_2", VEHICLE_ENTRY_FLAG_ENABLE_ADDITIONAL_COLOUR_2 }, + { "VEHICLE_ENTRY_FLAG_10", VEHICLE_ENTRY_FLAG_10 }, + { "VEHICLE_ENTRY_FLAG_11", VEHICLE_ENTRY_FLAG_11 }, + { "VEHICLE_ENTRY_FLAG_OVERRIDE_NUM_VERTICAL_FRAMES", VEHICLE_ENTRY_FLAG_OVERRIDE_NUM_VERTICAL_FRAMES }, + { "VEHICLE_ENTRY_FLAG_13", VEHICLE_ENTRY_FLAG_13 }, + { "VEHICLE_ENTRY_FLAG_SPINNING_ADDITIONAL_FRAMES", VEHICLE_ENTRY_FLAG_SPINNING_ADDITIONAL_FRAMES }, + { "VEHICLE_ENTRY_FLAG_15", VEHICLE_ENTRY_FLAG_15 }, + { "VEHICLE_ENTRY_FLAG_ENABLE_ADDITIONAL_COLOUR_1", VEHICLE_ENTRY_FLAG_ENABLE_ADDITIONAL_COLOUR_1 }, + { "VEHICLE_ENTRY_FLAG_SWINGING", VEHICLE_ENTRY_FLAG_SWINGING }, + { "VEHICLE_ENTRY_FLAG_SPINNING", VEHICLE_ENTRY_FLAG_SPINNING }, + { "VEHICLE_ENTRY_FLAG_POWERED", VEHICLE_ENTRY_FLAG_POWERED }, + { "VEHICLE_ENTRY_FLAG_RIDERS_SCREAM", VEHICLE_ENTRY_FLAG_RIDERS_SCREAM }, + { "VEHICLE_ENTRY_FLAG_21", VEHICLE_ENTRY_FLAG_21 }, + { "VEHICLE_ENTRY_FLAG_BOAT_HIRE_COLLISION_DETECTION", VEHICLE_ENTRY_FLAG_BOAT_HIRE_COLLISION_DETECTION }, + { "VEHICLE_ENTRY_FLAG_VEHICLE_ANIMATION", VEHICLE_ENTRY_FLAG_VEHICLE_ANIMATION }, + { "VEHICLE_ENTRY_FLAG_RIDER_ANIMATION", VEHICLE_ENTRY_FLAG_RIDER_ANIMATION }, + { "VEHICLE_ENTRY_FLAG_25", VEHICLE_ENTRY_FLAG_25 }, + { "VEHICLE_ENTRY_FLAG_SLIDE_SWING", VEHICLE_ENTRY_FLAG_SLIDE_SWING }, + { "VEHICLE_ENTRY_FLAG_CHAIRLIFT", VEHICLE_ENTRY_FLAG_CHAIRLIFT }, + { "VEHICLE_ENTRY_FLAG_WATER_RIDE", VEHICLE_ENTRY_FLAG_WATER_RIDE }, + { "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) +{ + // 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) + { + auto firstElement = json_array_get(jCarColours, 0); + auto numColours = json_array_size(firstElement); + if (numColours >= 2) + { + // Read all colours from first config + auto config = ReadJsonColourConfiguration(firstElement); + vehicle_colour_preset_list list = { 0 }; + list.count = 255; + std::copy_n(config.data(), std::min(numColours, 32), list.list); + return list; + } + } + + // Read first colour for each config + vehicle_colour_preset_list list = { 0 }; + size_t index; + const json_t * jConfiguration; + json_array_foreach(jCarColours, index, jConfiguration) + { + auto config = ReadJsonColourConfiguration(jConfiguration); + if (config.size() >= 1) + { + list.list[index] = config[0]; + list.count++; + + if (list.count == 254) + { + // Reached maximum number of configurations + break; + } + } + } + return list; +} + +std::vector RideObject::ReadJsonColourConfiguration(const json_t * jColourConfig) +{ + std::vector config; + size_t index; + const json_t * jColours; + json_array_foreach(jColourConfig, index, jColours) + { + vehicle_colour carColour = { 0 }; + auto colours = ObjectJsonHelpers::GetJsonStringArray(jColours); + if (colours.size() >= 1) + { + carColour.main = ParseColour(colours[0]); + carColour.additional_1 = carColour.main; + carColour.additional_2 = carColour.main; + if (colours.size() >= 2) + { + carColour.additional_1 = ParseColour(colours[1]); + } + if (colours.size() >= 3) + { + carColour.additional_2 = ParseColour(colours[2]); + } + } + config.push_back(carColour); + } + return config; +} + +bool RideObject::IsRideTypeShopOrFacility(uint8 rideType) +{ + switch (rideType) + { + case RIDE_TYPE_TOILETS: + case RIDE_TYPE_SHOP: + case RIDE_TYPE_DRINK_STALL: + case RIDE_TYPE_FOOD_STALL: + case RIDE_TYPE_INFORMATION_KIOSK: + case RIDE_TYPE_CASH_MACHINE: + case RIDE_TYPE_FIRST_AID: + return true; + default: + return false; + } +} + +uint8 RideObject::ParseRideType(const std::string &s) +{ + static const std::unordered_map LookupTable + { + { "spiral_rc", RIDE_TYPE_SPIRAL_ROLLER_COASTER }, + { "stand_up_rc", RIDE_TYPE_STAND_UP_ROLLER_COASTER }, + { "suspended_swinging_rc", RIDE_TYPE_SUSPENDED_SWINGING_COASTER }, + { "inverted_rc", RIDE_TYPE_INVERTED_ROLLER_COASTER }, + { "junior_rc", RIDE_TYPE_JUNIOR_ROLLER_COASTER }, + { "miniature_railway", RIDE_TYPE_MINIATURE_RAILWAY }, + { "monorail", RIDE_TYPE_MONORAIL }, + { "mini_suspended_rc", RIDE_TYPE_MINI_SUSPENDED_COASTER }, + { "boat_hire", RIDE_TYPE_BOAT_HIRE }, + { "wooden_wild_mouse", RIDE_TYPE_WOODEN_WILD_MOUSE }, + { "steeplechase", RIDE_TYPE_STEEPLECHASE }, + { "car_ride", RIDE_TYPE_CAR_RIDE }, + { "launched_freefall", RIDE_TYPE_LAUNCHED_FREEFALL }, + { "bobsleigh_rc", RIDE_TYPE_BOBSLEIGH_COASTER }, + { "observation_tower", RIDE_TYPE_OBSERVATION_TOWER }, + { "looping_rc", RIDE_TYPE_LOOPING_ROLLER_COASTER }, + { "dinghy_slide", RIDE_TYPE_DINGHY_SLIDE }, + { "mine_train_rc", RIDE_TYPE_MINE_TRAIN_COASTER }, + { "chairlift", RIDE_TYPE_CHAIRLIFT }, + { "corkscrew_rc", RIDE_TYPE_CORKSCREW_ROLLER_COASTER }, + { "maze", RIDE_TYPE_MAZE }, + { "spiral_slide", RIDE_TYPE_SPIRAL_SLIDE }, + { "go_karts", RIDE_TYPE_GO_KARTS }, + { "log_flume", RIDE_TYPE_LOG_FLUME }, + { "river_rapids", RIDE_TYPE_RIVER_RAPIDS }, + { "dodgems", RIDE_TYPE_DODGEMS }, + { "swinging_ship", RIDE_TYPE_SWINGING_SHIP }, + { "swinging_inverter_ship", RIDE_TYPE_SWINGING_INVERTER_SHIP }, + { "food_stall", RIDE_TYPE_FOOD_STALL }, + { "drink_stall", RIDE_TYPE_DRINK_STALL }, + { "shop", RIDE_TYPE_SHOP }, + { "merry_go_round", RIDE_TYPE_MERRY_GO_ROUND }, + { "information_kiosk", RIDE_TYPE_INFORMATION_KIOSK }, + { "toilets", RIDE_TYPE_TOILETS }, + { "ferris_wheel", RIDE_TYPE_FERRIS_WHEEL }, + { "motion_simulator", RIDE_TYPE_MOTION_SIMULATOR }, + { "3d_cinema", RIDE_TYPE_3D_CINEMA }, + { "top_spin", RIDE_TYPE_TOP_SPIN }, + { "space_rings", RIDE_TYPE_SPACE_RINGS }, + { "reverse_freefall_rc", RIDE_TYPE_REVERSE_FREEFALL_COASTER }, + { "lift", RIDE_TYPE_LIFT }, + { "vertical_drop_rc", RIDE_TYPE_VERTICAL_DROP_ROLLER_COASTER }, + { "cash_machine", RIDE_TYPE_CASH_MACHINE }, + { "twist", RIDE_TYPE_TWIST }, + { "haunted_house", RIDE_TYPE_HAUNTED_HOUSE }, + { "first_aid", RIDE_TYPE_FIRST_AID }, + { "circus", RIDE_TYPE_CIRCUS }, + { "ghost_train", RIDE_TYPE_GHOST_TRAIN }, + { "twister_rc", RIDE_TYPE_TWISTER_ROLLER_COASTER }, + { "wooden_rc", RIDE_TYPE_WOODEN_ROLLER_COASTER }, + { "side_friction_rc", RIDE_TYPE_SIDE_FRICTION_ROLLER_COASTER }, + { "steel_wild_mouse", RIDE_TYPE_STEEL_WILD_MOUSE }, + { "multi_dimension_rc", RIDE_TYPE_MULTI_DIMENSION_ROLLER_COASTER }, + { "flying_rc", RIDE_TYPE_FLYING_ROLLER_COASTER }, + { "virginia_reel", RIDE_TYPE_VIRGINIA_REEL }, + { "splash_boats", RIDE_TYPE_SPLASH_BOATS }, + { "mini_helicopters", RIDE_TYPE_MINI_HELICOPTERS }, + { "lay_down_rc", RIDE_TYPE_LAY_DOWN_ROLLER_COASTER }, + { "suspended_monorail", RIDE_TYPE_SUSPENDED_MONORAIL }, + { "reverser_rc", RIDE_TYPE_REVERSER_ROLLER_COASTER }, + { "heartline_twister_rc", RIDE_TYPE_HEARTLINE_TWISTER_COASTER }, + { "mini_golf", RIDE_TYPE_MINI_GOLF }, + { "giga_rc", RIDE_TYPE_GIGA_COASTER }, + { "roto_drop", RIDE_TYPE_ROTO_DROP }, + { "flying_saucers", RIDE_TYPE_FLYING_SAUCERS }, + { "crooked_house", RIDE_TYPE_CROOKED_HOUSE }, + { "monorail_cycles", RIDE_TYPE_MONORAIL_CYCLES }, + { "compact_inverted_rc", RIDE_TYPE_COMPACT_INVERTED_COASTER }, + { "water_coaster", RIDE_TYPE_WATER_COASTER }, + { "air_powered_vertical_rc", RIDE_TYPE_AIR_POWERED_VERTICAL_COASTER }, + { "inverted_hairpin_rc", RIDE_TYPE_INVERTED_HAIRPIN_COASTER }, + { "magic_carpet", RIDE_TYPE_MAGIC_CARPET }, + { "submarine_ride", RIDE_TYPE_SUBMARINE_RIDE }, + { "river_rafts", RIDE_TYPE_RIVER_RAFTS }, + { "enterprise", RIDE_TYPE_ENTERPRISE }, + { "inverted_impulse_rc", RIDE_TYPE_INVERTED_IMPULSE_COASTER }, + { "mini_rc", RIDE_TYPE_MINI_ROLLER_COASTER }, + { "mine_ride", RIDE_TYPE_MINE_RIDE }, + { "lim_launched_rc", RIDE_TYPE_LIM_LAUNCHED_ROLLER_COASTER }, + }; + auto result = LookupTable.find(s); + return (result != LookupTable.end()) ? + result->second : + RIDE_TYPE_NULL; +} + +uint8 RideObject::ParseRideCategory(const std::string &s) +{ + static const std::unordered_map LookupTable + { + { "transport", RIDE_CATEGORY_TRANSPORT }, + { "gentle", RIDE_CATEGORY_GENTLE }, + { "rollercoaster", RIDE_CATEGORY_ROLLERCOASTER }, + { "thrill", RIDE_CATEGORY_THRILL }, + { "water", RIDE_CATEGORY_WATER }, + { "stall", RIDE_CATEGORY_SHOP }, + }; + auto result = LookupTable.find(s); + return (result != LookupTable.end()) ? + result->second : + RIDE_CATEGORY_TRANSPORT; +} + +uint8 RideObject::ParseShopItem(const std::string &s) +{ + static const std::unordered_map LookupTable + { + { "burger", SHOP_ITEM_BURGER }, + { "chips", SHOP_ITEM_CHIPS }, + { "ice_cream", SHOP_ITEM_ICE_CREAM }, + { "candyfloss", SHOP_ITEM_CANDYFLOSS }, + { "pizza", SHOP_ITEM_PIZZA }, + { "popcorn", SHOP_ITEM_POPCORN }, + { "hot_dog", SHOP_ITEM_HOT_DOG }, + { "tentacle", SHOP_ITEM_TENTACLE }, + { "toffee_apple", SHOP_ITEM_TOFFEE_APPLE }, + { "doughnut", SHOP_ITEM_DOUGHNUT }, + { "chicken", SHOP_ITEM_CHICKEN }, + { "pretzel", SHOP_ITEM_PRETZEL }, + { "funnel_cake", SHOP_ITEM_FUNNEL_CAKE }, + { "beef_noodles", SHOP_ITEM_BEEF_NOODLES }, + { "fried_rice_noodles", SHOP_ITEM_FRIED_RICE_NOODLES }, + { "wonton_soup", SHOP_ITEM_WONTON_SOUP }, + { "meatball_soup", SHOP_ITEM_MEATBALL_SOUP }, + { "sub_sandwich", SHOP_ITEM_SUB_SANDWICH }, + { "cookie", SHOP_ITEM_COOKIE }, + { "roast_sausage", SHOP_ITEM_ROAST_SAUSAGE }, + { "drink", SHOP_ITEM_DRINK }, + { "coffee", SHOP_ITEM_COFFEE }, + { "lemonade", SHOP_ITEM_LEMONADE }, + { "chocolate", SHOP_ITEM_CHOCOLATE }, + { "iced_tea", SHOP_ITEM_ICED_TEA }, + { "fruit_juice", SHOP_ITEM_FRUIT_JUICE }, + { "soybean_milk", SHOP_ITEM_SOYBEAN_MILK }, + { "sujeonggwa", SHOP_ITEM_SUJEONGGWA }, + { "balloon", SHOP_ITEM_BALLOON }, + { "toy", SHOP_ITEM_TOY }, + { "map", SHOP_ITEM_MAP }, + { "photo", SHOP_ITEM_PHOTO }, + { "umbrella", SHOP_ITEM_UMBRELLA }, + { "voucher", SHOP_ITEM_VOUCHER }, + { "hat", SHOP_ITEM_HAT }, + { "tshirt", SHOP_ITEM_TSHIRT }, + { "sunglasses", SHOP_ITEM_SUNGLASSES }, + }; + auto result = LookupTable.find(s); + return (result != LookupTable.end()) ? + result->second : + SHOP_ITEM_NONE; +} + +colour_t RideObject::ParseColour(const std::string &s) +{ + static const std::unordered_map LookupTable + { + { "black", COLOUR_BLACK }, + { "grey", COLOUR_GREY }, + { "white", COLOUR_WHITE }, + { "dark_purple", COLOUR_DARK_PURPLE }, + { "light_purple", COLOUR_LIGHT_PURPLE }, + { "bright_purple", COLOUR_BRIGHT_PURPLE }, + { "dark_blue", COLOUR_DARK_BLUE }, + { "light_blue", COLOUR_LIGHT_BLUE }, + { "icy_blue", COLOUR_ICY_BLUE }, + { "teal", COLOUR_TEAL }, + { "aquamarine", COLOUR_AQUAMARINE }, + { "saturated_green", COLOUR_SATURATED_GREEN }, + { "dark_green", COLOUR_DARK_GREEN }, + { "moss_green", COLOUR_MOSS_GREEN }, + { "bright_green", COLOUR_BRIGHT_GREEN }, + { "olive_green", COLOUR_OLIVE_GREEN }, + { "dark_olive_green", COLOUR_DARK_OLIVE_GREEN }, + { "bright_yellow", COLOUR_BRIGHT_YELLOW }, + { "yellow", COLOUR_YELLOW }, + { "dark_yellow", COLOUR_DARK_YELLOW }, + { "light_orange", COLOUR_LIGHT_ORANGE }, + { "dark_orange", COLOUR_DARK_ORANGE }, + { "light_brown", COLOUR_LIGHT_BROWN }, + { "saturated_brown", COLOUR_SATURATED_BROWN }, + { "dark_brown", COLOUR_DARK_BROWN }, + { "salmon_pink", COLOUR_SALMON_PINK }, + { "bordeaux_red", COLOUR_BORDEAUX_RED }, + { "saturated_red", COLOUR_SATURATED_RED }, + { "bright_red", COLOUR_BRIGHT_RED }, + { "dark_pink", COLOUR_DARK_PINK }, + { "bright_pink", COLOUR_BRIGHT_PINK }, + { "light_pink", COLOUR_LIGHT_PINK }, + }; + auto result = LookupTable.find(s); + return (result != LookupTable.end()) ? + result->second : + COLOUR_BLACK; +} diff --git a/src/openrct2/object/RideObject.h b/src/openrct2/object/RideObject.h index 27c467667f..35bb401c58 100644 --- a/src/openrct2/object/RideObject.h +++ b/src/openrct2/object/RideObject.h @@ -16,24 +16,23 @@ #pragma once -#include "Object.h" - +#include #include "../ride/Ride.h" +#include "Object.h" class RideObject final : public Object { private: rct_ride_entry _legacyType = { }; vehicle_colour_preset_list _presetColours = { 0 }; - sint8 * _peepLoadingPositions[4] = { nullptr }; - uint16 _peepLoadingPositionsCount[4] = { 0 }; + std::vector _peepLoadingPositions[MAX_VEHICLES_PER_RIDE_ENTRY]; public: explicit RideObject(const rct_object_entry &entry) : Object(entry) { } - ~RideObject(); void * GetLegacyData() override { return &_legacyType; } + void ReadJson(IReadObjectContext * context, const json_t * root) override; void ReadLegacy(IReadObjectContext * context, IStream * stream) override; void Load() override; void Unload() override; @@ -47,8 +46,19 @@ public: private: void ReadLegacyVehicle(IReadObjectContext * context, IStream * stream, rct_ride_entry_vehicle * vehicle); - void PerformFixes(); + + void ReadJsonVehicleInfo(IReadObjectContext * context, const json_t * properties); + std::vector ReadJsonCars(const json_t * jCars); + rct_ride_entry_vehicle ReadJsonCar(const json_t * jCar); + vehicle_colour_preset_list ReadJsonCarColours(const json_t * jCarColours); + std::vector ReadJsonColourConfiguration(const json_t * jColourConfig); static uint8 CalculateNumVerticalFrames(const rct_ride_entry_vehicle * vehicleEntry); static uint8 CalculateNumHorizontalFrames(const rct_ride_entry_vehicle * vehicleEntry); + + static bool IsRideTypeShopOrFacility(uint8 rideType); + static uint8 ParseRideType(const std::string &s); + static uint8 ParseRideCategory(const std::string &s); + static uint8 ParseShopItem(const std::string &s); + static colour_t ParseColour(const std::string &s); }; diff --git a/src/openrct2/object/SceneryGroupObject.cpp b/src/openrct2/object/SceneryGroupObject.cpp index 8db99f469f..7d88c4171f 100644 --- a/src/openrct2/object/SceneryGroupObject.cpp +++ b/src/openrct2/object/SceneryGroupObject.cpp @@ -14,15 +14,20 @@ *****************************************************************************/ #pragma endregion +#pragma warning(disable : 4706) // assignment within conditional expression + +#include #include "../core/IStream.hpp" #include "../core/Memory.hpp" +#include "../core/String.hpp" +#include "../drawing/Drawing.h" +#include "../localisation/Language.h" +#include "../peep/Staff.h" +#include "ObjectJsonHelpers.h" #include "ObjectManager.h" #include "ObjectRepository.h" #include "SceneryGroupObject.h" -#include "../drawing/Drawing.h" -#include "../localisation/Language.h" - void SceneryGroupObject::ReadLegacy(IReadObjectContext * context, IStream * stream) { stream->Seek(6, STREAM_SEEK_CURRENT); @@ -33,23 +38,23 @@ void SceneryGroupObject::ReadLegacy(IReadObjectContext * context, IStream * stre _legacyType.pad_109 = stream->ReadValue(); _legacyType.entertainer_costumes = stream->ReadValue(); - GetStringTable()->Read(context, stream, OBJ_STRING_ID_NAME); + GetStringTable().Read(context, stream, OBJ_STRING_ID_NAME); _items = ReadItems(stream); - GetImageTable()->Read(context, stream); + GetImageTable().Read(context, stream); } void SceneryGroupObject::Load() { - GetStringTable()->Sort(); + GetStringTable().Sort(); _legacyType.name = language_allocate_object_string(GetName()); - _legacyType.image = gfx_object_allocate_images(GetImageTable()->GetImages(), GetImageTable()->GetCount()); + _legacyType.image = gfx_object_allocate_images(GetImageTable().GetImages(), GetImageTable().GetCount()); _legacyType.entry_count = 0; } void SceneryGroupObject::Unload() { language_free_object_string(_legacyType.name); - gfx_object_free_images(_legacyType.image, GetImageTable()->GetCount()); + gfx_object_free_images(_legacyType.image, GetImageTable().GetCount()); _legacyType.name = 0; _legacyType.image = 0; @@ -79,17 +84,20 @@ void SceneryGroupObject::UpdateEntryIndexes() uint16 sceneryEntry = objectManager->GetLoadedObjectEntryIndex(ori->LoadedObject); Guard::Assert(sceneryEntry != UINT8_MAX, GUARD_LINE); - uint8 objectType = objectEntry.flags & 0x0F; + auto objectType = ori->ObjectEntry.flags & 0x0F; switch (objectType) { case OBJECT_TYPE_SMALL_SCENERY: break; - case OBJECT_TYPE_LARGE_SCENERY: sceneryEntry |= 0x300; break; - case OBJECT_TYPE_WALLS: sceneryEntry |= 0x200; break; case OBJECT_TYPE_PATH_BITS: sceneryEntry |= 0x100; break; - default: sceneryEntry |= 0x400; break; + case OBJECT_TYPE_WALLS: sceneryEntry |= 0x200; break; + case OBJECT_TYPE_LARGE_SCENERY: sceneryEntry |= 0x300; break; + case OBJECT_TYPE_BANNERS: sceneryEntry |= 0x400; break; + default: sceneryEntry = 0xFFFF; break; + } + if (sceneryEntry != 0xFFFF) + { + _legacyType.scenery_entries[_legacyType.entry_count] = sceneryEntry; + _legacyType.entry_count++; } - - _legacyType.scenery_entries[_legacyType.entry_count] = sceneryEntry; - _legacyType.entry_count++; } } @@ -111,8 +119,80 @@ std::vector SceneryGroupObject::ReadItems(IStream * stream) while (stream->ReadValue() != 0xFF) { stream->Seek(-1, STREAM_SEEK_CURRENT); - rct_object_entry entry = stream->ReadValue(); + auto entry = stream->ReadValue(); items.push_back(entry); } return items; } + +void SceneryGroupObject::ReadJson(IReadObjectContext * context, const json_t * root) +{ + auto properties = json_object_get(root, "properties"); + _legacyType.priority = json_integer_value(json_object_get(properties, "priority")); + + // Entertainer cosumes + auto jCostumes = json_object_get(properties, "entertainerCostumes"); + if (jCostumes != nullptr) + { + _legacyType.entertainer_costumes = ReadJsonEntertainerCostumes(jCostumes); + } + + auto jEntries = json_object_get(properties, "entries"); + if (jEntries != nullptr) + { + _items = ReadJsonEntries(jEntries); + } + + ObjectJsonHelpers::LoadStrings(root, GetStringTable()); + ObjectJsonHelpers::LoadImages(context, root, GetImageTable()); +} + +uint32 SceneryGroupObject::ReadJsonEntertainerCostumes(const json_t * jCostumes) +{ + uint32 costumes = 0; + size_t index; + json_t * jCostume; + json_array_foreach(jCostumes, index, jCostume) + { + auto costume = ObjectJsonHelpers::GetString(jCostume); + auto entertainer = ParseEntertainerCostume(costume); + + // For some reason the flags are +4 from the actual costume IDs + // See staff_get_available_entertainer_costumes + costumes |= 1 << (entertainer + 4); + } + return costumes; +} + +uint32 SceneryGroupObject::ParseEntertainerCostume(const std::string &s) +{ + if (s == "panda") return ENTERTAINER_COSTUME_PANDA; + if (s == "tiger") return ENTERTAINER_COSTUME_TIGER; + if (s == "elephant") return ENTERTAINER_COSTUME_ELEPHANT; + if (s == "roman") return ENTERTAINER_COSTUME_ROMAN; + if (s == "gorilla") return ENTERTAINER_COSTUME_GORILLA; + if (s == "snowman") return ENTERTAINER_COSTUME_SNOWMAN; + if (s == "knight") return ENTERTAINER_COSTUME_KNIGHT; + if (s == "astronaut") return ENTERTAINER_COSTUME_ASTRONAUT; + if (s == "bandit") return ENTERTAINER_COSTUME_BANDIT; + if (s == "sheriff") return ENTERTAINER_COSTUME_SHERIFF; + if (s == "pirate") return ENTERTAINER_COSTUME_PIRATE; + return ENTERTAINER_COSTUME_PANDA; +} + +std::vector SceneryGroupObject::ReadJsonEntries(const json_t * jEntries) +{ + std::vector entries; + size_t index; + json_t * jEntry; + json_array_foreach(jEntries, index, jEntry) + { + auto entryId = json_string_value(jEntry); + if (entryId != nullptr) + { + auto entry = ObjectJsonHelpers::ParseObjectEntry(entryId); + entries.push_back(entry); + } + } + return entries; +} diff --git a/src/openrct2/object/SceneryGroupObject.h b/src/openrct2/object/SceneryGroupObject.h index a4e6b9c5de..6c6cb47f75 100644 --- a/src/openrct2/object/SceneryGroupObject.h +++ b/src/openrct2/object/SceneryGroupObject.h @@ -32,6 +32,7 @@ public: explicit SceneryGroupObject(const rct_object_entry &entry) : Object(entry) { } void * GetLegacyData() override { return &_legacyType; } + void ReadJson(IReadObjectContext * context, const json_t * root) override; void ReadLegacy(IReadObjectContext * context, IStream * stream) override; void Load() override; @@ -44,4 +45,7 @@ public: private: static std::vector ReadItems(IStream * stream); + static uint32 ReadJsonEntertainerCostumes(const json_t * jCostumes); + static uint32 ParseEntertainerCostume(const std::string &s); + static std::vector ReadJsonEntries(const json_t * jEntries); }; diff --git a/src/openrct2/object/SceneryObject.cpp b/src/openrct2/object/SceneryObject.cpp new file mode 100644 index 0000000000..140239a8a6 --- /dev/null +++ b/src/openrct2/object/SceneryObject.cpp @@ -0,0 +1,27 @@ +#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers +/***************************************************************************** +* OpenRCT2, an open source clone of Roller Coaster Tycoon 2. +* +* OpenRCT2 is the work of many authors, a full list can be found in contributors.md +* For more information, visit https://github.com/OpenRCT2/OpenRCT2 +* +* OpenRCT2 is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* A full copy of the GNU General Public License can be found in licence.txt +*****************************************************************************/ +#pragma endregion + +#include "ObjectJsonHelpers.h" +#include "SceneryObject.h" + +void SceneryObject::SetPrimarySceneryGroup(const std::string &s) +{ + if (!s.empty()) + { + auto sgEntry = ObjectJsonHelpers::ParseObjectEntry(s); + SetPrimarySceneryGroup(&sgEntry); + } +} diff --git a/src/openrct2/object/SceneryObject.h b/src/openrct2/object/SceneryObject.h index 18a1386ad7..53d40de185 100644 --- a/src/openrct2/object/SceneryObject.h +++ b/src/openrct2/object/SceneryObject.h @@ -16,6 +16,7 @@ #pragma once +#include #include "Object.h" class SceneryObject : public Object @@ -31,4 +32,5 @@ public: protected: void SetPrimarySceneryGroup(const rct_object_entry * entry) { _primarySceneryGroupEntry = *entry; } + void SetPrimarySceneryGroup(const std::string &s); }; diff --git a/src/openrct2/object/SmallSceneryObject.cpp b/src/openrct2/object/SmallSceneryObject.cpp index 357bf3d243..b01b13939e 100644 --- a/src/openrct2/object/SmallSceneryObject.cpp +++ b/src/openrct2/object/SmallSceneryObject.cpp @@ -14,16 +14,19 @@ *****************************************************************************/ #pragma endregion +#pragma warning(disable : 4706) // assignment within conditional expression + #include "../core/IStream.hpp" #include "../core/Math.hpp" #include "../core/Memory.hpp" #include "../core/String.hpp" -#include "SmallSceneryObject.h" - #include "../drawing/Drawing.h" +#include "../interface/Cursors.h" #include "../localisation/Language.h" #include "../world/Scenery.h" #include "../world/SmallScenery.h" +#include "SmallSceneryObject.h" +#include "ObjectJsonHelpers.h" void SmallSceneryObject::ReadLegacy(IReadObjectContext * context, IStream * stream) { @@ -39,7 +42,7 @@ void SmallSceneryObject::ReadLegacy(IReadObjectContext * context, IStream * stre _legacyType.small_scenery.num_frames = stream->ReadValue(); _legacyType.small_scenery.scenery_tab_id = 0xFF; - GetStringTable()->Read(context, stream, OBJ_STRING_ID_NAME); + GetStringTable().Read(context, stream, OBJ_STRING_ID_NAME); rct_object_entry sgEntry = stream->ReadValue(); SetPrimarySceneryGroup(&sgEntry); @@ -49,7 +52,7 @@ void SmallSceneryObject::ReadLegacy(IReadObjectContext * context, IStream * stre _frameOffsets = ReadFrameOffsets(stream); } - GetImageTable()->Read(context, stream); + GetImageTable().Read(context, stream); // Validate properties if (_legacyType.small_scenery.price <= 0) @@ -69,9 +72,9 @@ void SmallSceneryObject::ReadLegacy(IReadObjectContext * context, IStream * stre void SmallSceneryObject::Load() { - GetStringTable()->Sort(); + GetStringTable().Sort(); _legacyType.name = language_allocate_object_string(GetName()); - _legacyType.image = gfx_object_allocate_images(GetImageTable()->GetImages(), GetImageTable()->GetCount()); + _legacyType.image = gfx_object_allocate_images(GetImageTable().GetImages(), GetImageTable().GetCount()); _legacyType.small_scenery.scenery_tab_id = 0xFF; @@ -86,7 +89,7 @@ void SmallSceneryObject::Load() void SmallSceneryObject::Unload() { language_free_object_string(_legacyType.name); - gfx_object_free_images(_legacyType.image, GetImageTable()->GetCount()); + gfx_object_free_images(_legacyType.image, GetImageTable().GetCount()); _legacyType.name = 0; _legacyType.image = 0; @@ -231,3 +234,92 @@ rct_object_entry SmallSceneryObject::GetScgAbstrHeader() { return Object::CreateHeader("SCGABSTR", 207140231, 932253451); } + +void SmallSceneryObject::ReadJson(IReadObjectContext * context, const json_t * root) +{ + auto properties = json_object_get(root, "properties"); + + _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")); + + // Flags + _legacyType.small_scenery.flags = ObjectJsonHelpers::GetFlags(properties, { + { "SMALL_SCENERY_FLAG_VOFFSET_CENTRE", SMALL_SCENERY_FLAG_VOFFSET_CENTRE }, + { "requiresFlatSurface", SMALL_SCENERY_FLAG_REQUIRE_FLAT_SURFACE }, + { "isRotatable", SMALL_SCENERY_FLAG_ROTATABLE }, + { "isAnimated", SMALL_SCENERY_FLAG_ANIMATED }, + { "canWither", SMALL_SCENERY_FLAG_CAN_WITHER }, + { "canBeWatered", SMALL_SCENERY_FLAG_CAN_BE_WATERED }, + { "hasOverlayImage", SMALL_SCENERY_FLAG_ANIMATED_FG }, + { "hasGlass", SMALL_SCENERY_FLAG_HAS_GLASS }, + { "hasPrimaryColour", SMALL_SCENERY_FLAG_HAS_PRIMARY_COLOUR }, + { "SMALL_SCENERY_FLAG_FOUNTAIN_SPRAY_1", SMALL_SCENERY_FLAG_FOUNTAIN_SPRAY_1 }, + { "SMALL_SCENERY_FLAG_FOUNTAIN_SPRAY_4", SMALL_SCENERY_FLAG_FOUNTAIN_SPRAY_4 }, + { "isClock", SMALL_SCENERY_FLAG_IS_CLOCK }, + { "SMALL_SCENERY_FLAG_SWAMP_GOO", SMALL_SCENERY_FLAG_SWAMP_GOO }, + { "SMALL_SCENERY_FLAG17", SMALL_SCENERY_FLAG17 }, + { "isStackable", SMALL_SCENERY_FLAG_STACKABLE }, + { "prohibitWalls", SMALL_SCENERY_FLAG_NO_WALLS }, + { "hasSecondaryColour", SMALL_SCENERY_FLAG_HAS_SECONDARY_COLOUR }, + { "hasNoSupports", SMALL_SCENERY_FLAG_NO_SUPPORTS }, + { "SMALL_SCENERY_FLAG_VISIBLE_WHEN_ZOOMED", SMALL_SCENERY_FLAG_VISIBLE_WHEN_ZOOMED }, + { "SMALL_SCENERY_FLAG_COG", SMALL_SCENERY_FLAG_COG }, + { "allowSupportsAbove", SMALL_SCENERY_FLAG_BUILD_DIRECTLY_ONTOP }, + { "supportsHavePrimaryColour", SMALL_SCENERY_FLAG_PAINT_SUPPORTS }, + { "SMALL_SCENERY_FLAG27", SMALL_SCENERY_FLAG27 } }); + + // Determine shape flags from a shape string + auto shape = ObjectJsonHelpers::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 = 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()); +} + +std::vector SmallSceneryObject::ReadJsonFrameOffsets(const json_t * jFrameOffsets) +{ + std::vector offsets; + size_t index; + const json_t * jOffset; + json_array_foreach(jFrameOffsets, index, jOffset) + { + offsets.push_back(json_integer_value(jOffset)); + } + return offsets; +} diff --git a/src/openrct2/object/SmallSceneryObject.h b/src/openrct2/object/SmallSceneryObject.h index d7cbe04031..15043e6e9c 100644 --- a/src/openrct2/object/SmallSceneryObject.h +++ b/src/openrct2/object/SmallSceneryObject.h @@ -32,6 +32,7 @@ public: void * GetLegacyData() override { return &_legacyType; } void ReadLegacy(IReadObjectContext * context, IStream * stream) override; + void ReadJson(IReadObjectContext * context, const json_t * root) override; void Load() override; void Unload() override; @@ -39,6 +40,7 @@ public: private: static std::vector ReadFrameOffsets(IStream * stream); + static std::vector ReadJsonFrameOffsets(const json_t * jFrameOffsets); void PerformFixes(); rct_object_entry GetScgPiratHeader(); rct_object_entry GetScgMineHeader(); diff --git a/src/openrct2/object/StexObject.cpp b/src/openrct2/object/StexObject.cpp index 88137fb98f..810834e099 100644 --- a/src/openrct2/object/StexObject.cpp +++ b/src/openrct2/object/StexObject.cpp @@ -25,14 +25,14 @@ void StexObject::ReadLegacy(IReadObjectContext * context, IStream * stream) _legacyType.var_06 = stream->ReadValue(); stream->Seek(1, STREAM_SEEK_CURRENT); - GetStringTable()->Read(context, stream, OBJ_STRING_ID_SCENARIO_NAME); - GetStringTable()->Read(context, stream, OBJ_STRING_ID_PARK_NAME); - GetStringTable()->Read(context, stream, OBJ_STRING_ID_SCENARIO_DETAILS); + GetStringTable().Read(context, stream, OBJ_STRING_ID_SCENARIO_NAME); + GetStringTable().Read(context, stream, OBJ_STRING_ID_PARK_NAME); + GetStringTable().Read(context, stream, OBJ_STRING_ID_SCENARIO_DETAILS); } void StexObject::Load() { - GetStringTable()->Sort(); + GetStringTable().Sort(); _legacyType.scenario_name = language_allocate_object_string(GetScenarioName()); _legacyType.park_name = language_allocate_object_string(GetParkName()); _legacyType.details = language_allocate_object_string(GetScenarioDetails()); @@ -64,15 +64,15 @@ std::string StexObject::GetName() const std::string StexObject::GetScenarioName() const { - return GetStringTable()->GetString(OBJ_STRING_ID_SCENARIO_NAME); + return GetStringTable().GetString(OBJ_STRING_ID_SCENARIO_NAME); } std::string StexObject::GetScenarioDetails() const { - return GetStringTable()->GetString(OBJ_STRING_ID_SCENARIO_DETAILS); + return GetStringTable().GetString(OBJ_STRING_ID_SCENARIO_DETAILS); } std::string StexObject::GetParkName() const { - return GetStringTable()->GetString(OBJ_STRING_ID_PARK_NAME); + return GetStringTable().GetString(OBJ_STRING_ID_PARK_NAME); } diff --git a/src/openrct2/object/StringTable.cpp b/src/openrct2/object/StringTable.cpp index 5b7ee08dd3..521124a7ae 100644 --- a/src/openrct2/object/StringTable.cpp +++ b/src/openrct2/object/StringTable.cpp @@ -97,7 +97,7 @@ std::string StringTable::GetString(uint8 id) const return string.Text; } } - return nullptr; + return std::string(); } void StringTable::SetString(uint8 id, uint8 language, const std::string &text) diff --git a/src/openrct2/object/StringTable.h b/src/openrct2/object/StringTable.h index c370971bbe..34fa8de21a 100644 --- a/src/openrct2/object/StringTable.h +++ b/src/openrct2/object/StringTable.h @@ -19,14 +19,27 @@ #include #include #include "../common.h" +#include "../localisation/Language.h" interface IReadObjectContext; interface IStream; +enum OBJ_STRING_ID : uint8 +{ + OBJ_STRING_ID_UNKNOWN = 255, + OBJ_STRING_ID_NAME = 0, + OBJ_STRING_ID_DESCRIPTION, + OBJ_STRING_ID_SCENARIO_NAME = 0, + OBJ_STRING_ID_PARK_NAME = 1, + OBJ_STRING_ID_SCENARIO_DETAILS = 2, + OBJ_STRING_ID_CAPACITY = 2, + OBJ_STRING_ID_VEHICLE_NAME = 3, +}; + struct StringTableEntry { - uint8 Id; - uint8 LanguageId; + uint8 Id = OBJ_STRING_ID_UNKNOWN; + uint8 LanguageId = LANGUAGE_UNDEFINED; std::string Text; }; @@ -36,6 +49,10 @@ private: std::vector _strings; public: + StringTable() = default; + StringTable(const StringTable &) = delete; + StringTable & operator=(const StringTable &) = delete; + void Read(IReadObjectContext * context, IStream * stream, uint8 id); void Sort(); std::string GetString(uint8 id) const; diff --git a/src/openrct2/object/WallObject.cpp b/src/openrct2/object/WallObject.cpp index d6100b2d21..c82da14b82 100644 --- a/src/openrct2/object/WallObject.cpp +++ b/src/openrct2/object/WallObject.cpp @@ -15,10 +15,11 @@ #pragma endregion #include "../core/IStream.hpp" -#include "WallObject.h" - #include "../drawing/Drawing.h" +#include "../interface/Cursors.h" #include "../localisation/Language.h" +#include "ObjectJsonHelpers.h" +#include "WallObject.h" void WallObject::ReadLegacy(IReadObjectContext * context, IStream * stream) { @@ -31,12 +32,12 @@ void WallObject::ReadLegacy(IReadObjectContext * context, IStream * stream) _legacyType.wall.scenery_tab_id = stream->ReadValue(); _legacyType.wall.scrolling_mode = stream->ReadValue(); - GetStringTable()->Read(context, stream, OBJ_STRING_ID_NAME); + GetStringTable().Read(context, stream, OBJ_STRING_ID_NAME); rct_object_entry sgEntry = stream->ReadValue(); SetPrimarySceneryGroup(&sgEntry); - GetImageTable()->Read(context, stream); + GetImageTable().Read(context, stream); // Validate properties if (_legacyType.wall.price <= 0) @@ -47,15 +48,15 @@ void WallObject::ReadLegacy(IReadObjectContext * context, IStream * stream) void WallObject::Load() { - GetStringTable()->Sort(); + GetStringTable().Sort(); _legacyType.name = language_allocate_object_string(GetName()); - _legacyType.image = gfx_object_allocate_images(GetImageTable()->GetImages(), GetImageTable()->GetCount()); + _legacyType.image = gfx_object_allocate_images(GetImageTable().GetImages(), GetImageTable().GetCount()); } void WallObject::Unload() { language_free_object_string(_legacyType.name); - gfx_object_free_images(_legacyType.image, GetImageTable()->GetCount()); + gfx_object_free_images(_legacyType.image, GetImageTable().GetCount()); _legacyType.name = 0; _legacyType.image = 0; @@ -88,3 +89,61 @@ void WallObject::DrawPreview(rct_drawpixelinfo * dpi, sint32 width, sint32 heigh gfx_draw_sprite(dpi, imageId, x, y, 0); } } + +void WallObject::ReadJson(IReadObjectContext * context, const json_t * root) +{ + auto properties = json_object_get(root, "properties"); + + _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 jScrollingMode = json_object_get(properties, "scrollingMode"); + _legacyType.wall.scrolling_mode = jScrollingMode != nullptr ? + json_integer_value(jScrollingMode) : + -1; + + SetPrimarySceneryGroup(ObjectJsonHelpers::GetString(json_object_get(properties, "sceneryGroup"))); + + // Flags + _legacyType.wall.flags = ObjectJsonHelpers::GetFlags(properties, { + { "hasPrimaryColour", WALL_SCENERY_HAS_PRIMARY_COLOUR }, + { "hasSecondaryColour", WALL_SCENERY_HAS_SECONDARY_COLOUR }, + { "hasTernaryColour", WALL_SCENERY_HAS_TERNARY_COLOUR }, + { "hasGlass", WALL_SCENERY_HAS_GLASS }, + { "isBanner", WALL_SCENERY_IS_BANNER }, + { "isDoor", WALL_SCENERY_IS_DOOR }, + { "isLongDoorAnimation", WALL_SCENERY_LONG_DOOR_ANIMATION }}); + _legacyType.wall.flags2 = ObjectJsonHelpers::GetFlags(properties, { + { "isOpaque", WALL_SCENERY_2_IS_OPAQUE }, + { "isAnimated", WALL_SCENERY_2_ANIMATED }}); + + // HACK To avoid 'negated' properties in JSON, handle this separately until + // flag is inverted in this code base. + if (!ObjectJsonHelpers::GetBoolean(properties, "isAllowedOnSlope", false)) + { + _legacyType.wall.flags |= WALL_SCENERY_CANT_BUILD_ON_SLOPE; + } + + // 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.flags2 |= WALL_SCENERY_2_NO_SELECT_PRIMARY_COLOUR; + } + } + + // Door sound + auto jDoorSound = json_object_get(properties, "scrollingMode"); + 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()); +} diff --git a/src/openrct2/object/WallObject.h b/src/openrct2/object/WallObject.h index 605443e0c0..388685f905 100644 --- a/src/openrct2/object/WallObject.h +++ b/src/openrct2/object/WallObject.h @@ -31,6 +31,7 @@ public: void * GetLegacyData() override { return &_legacyType; } void ReadLegacy(IReadObjectContext * context, IStream * stream) override; + void ReadJson(IReadObjectContext * context, const json_t * root) override; void Load() override; void Unload() override; diff --git a/src/openrct2/object/WaterObject.cpp b/src/openrct2/object/WaterObject.cpp index e90e107c47..2edcc3c338 100644 --- a/src/openrct2/object/WaterObject.cpp +++ b/src/openrct2/object/WaterObject.cpp @@ -14,10 +14,14 @@ *****************************************************************************/ #pragma endregion +#pragma warning(disable : 4706) // assignment within conditional expression + +#include #include "../core/IStream.hpp" #include "../localisation/Language.h" #include "../localisation/StringIds.h" #include "../OpenRCT2.h" +#include "ObjectJsonHelpers.h" #include "WaterObject.h" void WaterObject::ReadLegacy(IReadObjectContext * context, IStream * stream) @@ -25,15 +29,15 @@ void WaterObject::ReadLegacy(IReadObjectContext * context, IStream * stream) stream->Seek(14, STREAM_SEEK_CURRENT); _legacyType.flags = stream->ReadValue(); - GetStringTable()->Read(context, stream, OBJ_STRING_ID_NAME); - GetImageTable()->Read(context, stream); + GetStringTable().Read(context, stream, OBJ_STRING_ID_NAME); + GetImageTable().Read(context, stream); } void WaterObject::Load() { - GetStringTable()->Sort(); + GetStringTable().Sort(); _legacyType.string_idx = language_allocate_object_string(GetName()); - _legacyType.image_id = gfx_object_allocate_images(GetImageTable()->GetImages(), GetImageTable()->GetCount()); + _legacyType.image_id = gfx_object_allocate_images(GetImageTable().GetImages(), GetImageTable().GetCount()); _legacyType.palette_index_1 = _legacyType.image_id + 1; _legacyType.palette_index_2 = _legacyType.image_id + 4; @@ -42,7 +46,7 @@ void WaterObject::Load() void WaterObject::Unload() { - gfx_object_free_images(_legacyType.image_id, GetImageTable()->GetCount()); + gfx_object_free_images(_legacyType.image_id, GetImageTable().GetCount()); language_free_object_string(_legacyType.string_idx); _legacyType.string_idx = 0; @@ -55,3 +59,85 @@ void WaterObject::DrawPreview(rct_drawpixelinfo * dpi, sint32 width, sint32 heig sint32 y = height / 2; gfx_draw_string_centred(dpi, STR_WINDOW_NO_IMAGE, x, y, COLOUR_BLACK, nullptr); } + +void WaterObject::ReadJson(IReadObjectContext * context, const json_t * root) +{ + auto properties = json_object_get(root, "properties"); + _legacyType.flags = ObjectJsonHelpers::GetFlags(properties, { + { "allowDucks", WATER_FLAGS_ALLOW_DUCKS }}); + + ObjectJsonHelpers::LoadStrings(root, GetStringTable()); + + // 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 jPalettes = json_object_get(properties, "palettes"); + if (jPalettes != nullptr) + { + auto jPalette = json_object_get(jPalettes, paletteName); + if (jPalette != nullptr) + { + ReadJsonPalette(jPalette); + } + } + } +} + +void WaterObject::ReadJsonPalette(const 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); + + auto data = std::make_unique(numColours * 3); + size_t dataIndex = 0; + + size_t index; + const json_t * jColour; + json_array_foreach(jColours, index, jColour) + { + auto szColour = json_string_value(jColour); + if (szColour != nullptr) + { + auto colour = ParseColour(szColour); + data[dataIndex + 0] = (colour >> 16) & 0xFF; + data[dataIndex + 1] = (colour >> 8) & 0xFF; + data[dataIndex + 2] = colour & 0xFF; + } + dataIndex += 3; + } + + rct_g1_element g1 = { 0 }; + g1.offset = data.get(); + g1.width = (sint16)numColours; + g1.x_offset = (sint16)paletteStartIndex; + g1.flags = G1_FLAG_PALETTE; + + auto &imageTable = GetImageTable(); + imageTable.AddImage(&g1); +} + +uint32 WaterObject::ParseColour(const std::string &s) const +{ + uint8 r = 0; + uint8 g = 0; + uint8 b = 0; + if (s[0] == '#' && s.size() == 7) + { + // Expect #RRGGBB + r = std::stoul(s.substr(1, 2), nullptr, 16) & 0xFF; + g = std::stoul(s.substr(3, 2), nullptr, 16) & 0xFF; + b = std::stoul(s.substr(5, 2), nullptr, 16) & 0xFF; + } + return (b << 16) | (g << 8) | r; +} diff --git a/src/openrct2/object/WaterObject.h b/src/openrct2/object/WaterObject.h index e1c7a6ec1e..7b30929d26 100644 --- a/src/openrct2/object/WaterObject.h +++ b/src/openrct2/object/WaterObject.h @@ -16,9 +16,9 @@ #pragma once -#include "Object.h" - +#include #include "../world/Water.h" +#include "Object.h" class WaterObject final : public Object { @@ -30,9 +30,14 @@ public: void * GetLegacyData() override { return &_legacyType; } + void ReadJson(IReadObjectContext * context, const json_t * root) override; void ReadLegacy(IReadObjectContext * context, IStream * stream) override; void Load() override; void Unload() override; void DrawPreview(rct_drawpixelinfo * dpi, sint32 width, sint32 height) const override; + +private: + void ReadJsonPalette(const json_t * jPalette); + uint32 ParseColour(const std::string &s) const; }; diff --git a/src/openrct2/paint/tile_element/LargeScenery.cpp b/src/openrct2/paint/tile_element/LargeScenery.cpp index d5b2775eed..072bbe2a9e 100644 --- a/src/openrct2/paint/tile_element/LargeScenery.cpp +++ b/src/openrct2/paint/tile_element/LargeScenery.cpp @@ -39,7 +39,7 @@ static void large_scenery_paint_supports( uint32 dword_F4387C, rct_large_scenery_tile * tile) { - if (tile->var_7 & 0x20) { + if (tile->flags & LARGE_SCENERY_TILE_FLAG_NO_SUPPORTS) { return; } @@ -61,7 +61,7 @@ static void large_scenery_paint_supports( sint32 clearanceHeight = ceil2(tileElement->clearance_height * 8 + 15, 16); - if (tile->var_7 & 0x40) { + if (tile->flags & LARGE_SCENERY_TILE_FLAG_ALLOW_SUPPORTS_ABOVE) { paint_util_set_segment_support_height(session, SEGMENTS_ALL, clearanceHeight, 0x20); } else { paint_util_set_segment_support_height(session, SEGMENTS_ALL, 0xFFFF, 0); @@ -232,7 +232,7 @@ void large_scenery_paint(paint_session * session, uint8 direction, uint16 height ah = 0x80; } ah -= 3; - uint16 edi = tile->var_7; + uint16 edi = tile->flags; sint32 esi = 16; if (edi & 0xF00) { edi &= 0xF000; diff --git a/src/openrct2/peep/Peep.cpp b/src/openrct2/peep/Peep.cpp index 505dee189f..69ffcf3c0b 100644 --- a/src/openrct2/peep/Peep.cpp +++ b/src/openrct2/peep/Peep.cpp @@ -850,7 +850,7 @@ static constexpr const uint8 byte_9822F4[] = { 110, // SHOP_ITEM_MEATBALL_SOUP 110, // SHOP_ITEM_FRUIT_JUICE 90, // SHOP_ITEM_SOYBEAN_MILK - 100, // SHOP_ITEM_SU_JONGKWA + 100, // SHOP_ITEM_SUJEONGGWA 130, // SHOP_ITEM_SUB_SANDWICH 75, // SHOP_ITEM_COOKIE 0, // SHOP_ITEM_EMPTY_BOWL_RED @@ -3021,7 +3021,7 @@ static void peep_update_ride_sub_state_1(rct_peep * peep) if (ride->type != RIDE_TYPE_ENTERPRISE) direction_track *= 2; - if (*vehicle_type->peep_loading_positions == 0) + if (vehicle_type->peep_loading_positions[0] == 0) { direction_track /= 2; cl = 0; @@ -3063,7 +3063,7 @@ static void peep_update_ride_sub_state_1(rct_peep * peep) sint8 load_position = 0; // Safe, in case current seat > number of loading positions - uint16 numSeatPositions = vehicle_type->peep_loading_positions_count; + auto numSeatPositions = vehicle_type->peep_loading_positions.size(); if (numSeatPositions != 0) { size_t loadPositionIndex = numSeatPositions - 1; @@ -3563,7 +3563,7 @@ static void peep_update_ride_sub_state_7(rct_peep * peep) if (ride->type != RIDE_TYPE_ENTERPRISE) station_direction *= 2; - if (*vehicle_type->peep_loading_positions == 0) + if (vehicle_type->peep_loading_positions[0] == 0) { station_direction /= 2; cl = 0; diff --git a/src/openrct2/ride/Ride.cpp b/src/openrct2/ride/Ride.cpp index c8314f89b3..4bae2ed945 100644 --- a/src/openrct2/ride/Ride.cpp +++ b/src/openrct2/ride/Ride.cpp @@ -7709,7 +7709,7 @@ bool shop_item_is_food_or_drink(sint32 shopItem) case SHOP_ITEM_MEATBALL_SOUP: case SHOP_ITEM_FRUIT_JUICE: case SHOP_ITEM_SOYBEAN_MILK: - case SHOP_ITEM_SU_JONGKWA: + case SHOP_ITEM_SUJEONGGWA: case SHOP_ITEM_SUB_SANDWICH: case SHOP_ITEM_COOKIE: case SHOP_ITEM_ROAST_SAUSAGE: @@ -7758,7 +7758,7 @@ bool shop_item_is_drink(sint32 shopItem) case SHOP_ITEM_ICED_TEA: case SHOP_ITEM_FRUIT_JUICE: case SHOP_ITEM_SOYBEAN_MILK: - case SHOP_ITEM_SU_JONGKWA: + case SHOP_ITEM_SUJEONGGWA: return true; default: return false; diff --git a/src/openrct2/ride/Ride.h b/src/openrct2/ride/Ride.h index 67ff350652..c8da9c85f0 100644 --- a/src/openrct2/ride/Ride.h +++ b/src/openrct2/ride/Ride.h @@ -126,6 +126,7 @@ struct rct_ride_entry { uint8 shop_item; // 0x1C0 uint8 shop_item_secondary; // 0x1C1 rct_string_id capacity; + void * obj; }; #pragma pack(pop) @@ -856,7 +857,7 @@ enum { SHOP_ITEM_MEATBALL_SOUP, SHOP_ITEM_FRUIT_JUICE, SHOP_ITEM_SOYBEAN_MILK, - SHOP_ITEM_SU_JONGKWA, + SHOP_ITEM_SUJEONGGWA, SHOP_ITEM_SUB_SANDWICH, SHOP_ITEM_COOKIE, SHOP_ITEM_EMPTY_BOWL_RED, diff --git a/src/openrct2/ride/RideData.cpp b/src/openrct2/ride/RideData.cpp index df604d8573..5625ffb4da 100644 --- a/src/openrct2/ride/RideData.cpp +++ b/src/openrct2/ride/RideData.cpp @@ -1460,7 +1460,7 @@ const money8 DefaultShopItemPrice[SHOP_ITEM_COUNT] = { MONEY(1,50), // SHOP_ITEM_MEATBALL_SOUP MONEY(1,20), // SHOP_ITEM_FRUIT_JUICE MONEY(1,20), // SHOP_ITEM_SOYBEAN_MILK - MONEY(1,20), // SHOP_ITEM_SU_JONGKWA + MONEY(1,20), // SHOP_ITEM_SUJEONGGWA MONEY(1,50), // SHOP_ITEM_SUB_SANDWICH MONEY(0,70), // SHOP_ITEM_COOKIE MONEY(0,00), // SHOP_ITEM_EMPTY_BOWL_RED @@ -1576,7 +1576,7 @@ const uint32 ShopItemImage[SHOP_ITEM_COUNT] = { SPR_SHOP_ITEM_MEATBALL_SOUP, SPR_SHOP_ITEM_FRUIT_JUICE, SPR_SHOP_ITEM_SOYBEAN_MILK, - SPR_SHOP_ITEM_SU_JONGKWA, + SPR_SHOP_ITEM_SUJEONGGWA, SPR_SHOP_ITEM_SUB_SANDWICH, SPR_SHOP_ITEM_COOKIE, SPR_SHOP_ITEM_EMPTY_BOWL_RED, @@ -1629,7 +1629,7 @@ const rct_ride_entry_vehicle CableLiftVehicle = { /* .effect_visual = */ 1, /* .draw_order = */ 14, /* .num_vertical_frames_override = */ 0, - /* .peep_loading_positions = */ nullptr + /* .peep_loading_positions = */ }; /* rct2: 0x009A0AA0 */ @@ -2482,7 +2482,7 @@ const rct_shop_item_stats ShopItemStats[SHOP_ITEM_COUNT] = { { 5, 14, 14, 16 }, // SHOP_ITEM_MEATBALL_SOUP { 4, 11, 19, 11 }, // SHOP_ITEM_FRUIT_JUICE { 4, 10, 14, 10 }, // SHOP_ITEM_SOYBEAN_MILK - { 3, 11, 14, 11 }, // SHOP_ITEM_SU_JONGKWA + { 3, 11, 14, 11 }, // SHOP_ITEM_SUJEONGGWA { 5, 19, 19, 17 }, // SHOP_ITEM_SUB_SANDWICH { 4, 8, 8, 8 }, // SHOP_ITEM_COOKIE { 0, 0, 0, 0 }, // SHOP_ITEM_EMPTY_BOWL_RED diff --git a/src/openrct2/ride/RideGroupManager.cpp b/src/openrct2/ride/RideGroupManager.cpp index 99eac84177..ac282d41c8 100644 --- a/src/openrct2/ride/RideGroupManager.cpp +++ b/src/openrct2/ride/RideGroupManager.cpp @@ -108,7 +108,7 @@ const RideGroup * RideGroupManager::GetRideGroup(const uint8 rideType, const rct switch (rideType) { case RIDE_TYPE_CORKSCREW_ROLLER_COASTER: - if (rideEntry->enabledTrackPieces & (1ULL << TRACK_VERTICAL_LOOP)) + if (ride_entry_get_supported_track_pieces(rideEntry) & (1ULL << TRACK_VERTICAL_LOOP)) return &ride_group_corkscrew_rc; else return &ride_group_hypercoaster; @@ -118,17 +118,17 @@ const RideGroup * RideGroupManager::GetRideGroup(const uint8 rideType, const rct else return &ride_group_junior_rc; case RIDE_TYPE_CAR_RIDE: - if (rideEntry->enabledTrackPieces & (1ULL << TRACK_SLOPE_STEEP)) + if (ride_entry_get_supported_track_pieces(rideEntry) & (1ULL << TRACK_SLOPE_STEEP)) return &ride_group_monster_trucks; else return &ride_group_car_ride; case RIDE_TYPE_TWISTER_ROLLER_COASTER: - if (rideEntry->enabledTrackPieces & (1ULL << TRACK_VERTICAL_LOOP)) + if (!(rideEntry->flags & RIDE_ENTRY_FLAG_NO_INVERSIONS)) return &ride_group_steel_twister_rc; else return &ride_group_hyper_twister; case RIDE_TYPE_STEEL_WILD_MOUSE: - if (rideEntry->enabledTrackPieces & (1ULL << TRACK_SLOPE_STEEP)) + if (ride_entry_get_supported_track_pieces(rideEntry) & (1ULL << TRACK_SLOPE_STEEP)) return &ride_group_steel_wild_mouse; else return &ride_group_spinning_wild_mouse; diff --git a/src/openrct2/ride/Track.cpp b/src/openrct2/ride/Track.cpp index a27ba9f964..2cf90bc0e8 100644 --- a/src/openrct2/ride/Track.cpp +++ b/src/openrct2/ride/Track.cpp @@ -1004,7 +1004,6 @@ static money32 track_place(sint32 rideIndex, direction &= 3; gTrackGroundFlags = 0; - uint64 enabledTrackPieces = rideEntry->enabledTrackPieces & RideTypePossibleTrackConfigurations[ride->type]; uint32 rideTypeFlags = RideProperties[ride->type].flags; if ((ride->lifecycle_flags & RIDE_LIFECYCLE_INDESTRUCTIBLE_TRACK) && type == TRACK_ELEM_END_STATION) @@ -1047,7 +1046,7 @@ static money32 track_place(sint32 rideIndex, } // Backwards steep lift hills are allowed, even on roller coasters that do not support forwards steep lift hills. if ((liftHillAndAlternativeState & CONSTRUCTION_LIFT_HILL_SELECTED) && - !(enabledTrackPieces & (1ULL << TRACK_LIFT_HILL_STEEP)) && + !(RideTypePossibleTrackConfigurations[ride->type] & (1ULL << TRACK_LIFT_HILL_STEEP)) && !gCheatsEnableChainLiftOnAllTrack) { if (TrackFlags[type] & TRACK_ELEM_FLAG_IS_STEEP_UP) diff --git a/src/openrct2/ride/Vehicle.h b/src/openrct2/ride/Vehicle.h index 828c173376..86ea757a21 100644 --- a/src/openrct2/ride/Vehicle.h +++ b/src/openrct2/ride/Vehicle.h @@ -17,6 +17,8 @@ #ifndef _VEHICLE_H_ #define _VEHICLE_H_ +#include +#include #include "../common.h" #include "../world/Location.hpp" @@ -25,7 +27,9 @@ struct rct_vehicle_colour { uint8 trim_colour; }; +#ifdef __TESTPAINT__ #pragma pack(push, 1) +#endif // __TESTPAINT__ /** * Ride type vehicle structure. * size: 0x65 @@ -76,10 +80,20 @@ struct rct_ride_entry_vehicle { uint8 effect_visual; uint8 draw_order; uint8 num_vertical_frames_override; // 0x60 , 0x7A, A custom number that can be used rather than letting RCT2 determine it. Needs the VEHICLE_ENTRY_FLAG_OVERRIDE_NUM_VERTICAL_FRAMES flag to be set. - sint8* peep_loading_positions; // 0x61 , 0x7B - uint16 peep_loading_positions_count; + uint8 pad_61[7]; // 0x61 , 0x7B + + std::vector peep_loading_positions; }; +#ifdef __TESTPAINT__ #pragma pack(pop) +#endif // __TESTPAINT__ +#ifdef PLATFORM_32BIT +static_assert(offsetof(rct_ride_entry_vehicle, peep_loading_positions) % 4 == 0, "Invalid struct layout"); +static_assert(sizeof(rct_ride_entry_vehicle) % 4 == 0, "Invalid struct size"); +#else +static_assert(offsetof(rct_ride_entry_vehicle, peep_loading_positions) % 8 == 0, "Invalid struct layout"); +static_assert(sizeof(rct_ride_entry_vehicle) % 8 == 0, "Invalid struct size"); +#endif struct rct_vehicle { uint8 sprite_identifier; // 0x00 diff --git a/src/openrct2/sprites.h b/src/openrct2/sprites.h index 462ff6799b..b843134406 100644 --- a/src/openrct2/sprites.h +++ b/src/openrct2/sprites.h @@ -17,6 +17,8 @@ #ifndef _SPRITES_H_ #define _SPRITES_H_ +#include "rct1/RCT1.h" + enum { SPR_NONE = -1, @@ -99,7 +101,7 @@ enum { SPR_SHOP_ITEM_MEATBALL_SOUP = 5100, SPR_SHOP_ITEM_FRUIT_JUICE = 5101, SPR_SHOP_ITEM_SOYBEAN_MILK = 5102, - SPR_SHOP_ITEM_SU_JONGKWA = 5103, + SPR_SHOP_ITEM_SUJEONGGWA = 5103, SPR_SHOP_ITEM_SUB_SANDWICH = 5104, SPR_SHOP_ITEM_COOKIE = 5105, SPR_SHOP_ITEM_EMPTY_BOWL_RED = 5106, @@ -872,6 +874,13 @@ enum { SPR_CSG_WALL_TEXTURE_STONE_GREY = SPR_CSG_BEGIN + 47376, SPR_CSG_WALL_TEXTURE_SKYSCRAPER_A = SPR_CSG_BEGIN + 47377, SPR_CSG_WALL_TEXTURE_SKYSCRAPER_B = SPR_CSG_BEGIN + 47378, + + SPR_CSG_ICE_CREAM_STALL_BEGIN = SPR_CSG_BEGIN + 60625, + SPR_CSG_TOILETS_BEGIN = SPR_CSG_BEGIN + 61289, + + SPR_CSG_RIDE_PREVIEWS_BEGIN = SPR_CSG_BEGIN + 64195, + SPR_CSG_RIDE_PREVIEW_ICE_CREAM_STALL = SPR_CSG_RIDE_PREVIEWS_BEGIN + RCT1_RIDE_TYPE_ICE_CREAM_STALL, + SPR_CSG_RIDE_PREVIEW_TOILETS = SPR_CSG_RIDE_PREVIEWS_BEGIN + RCT1_RIDE_TYPE_TOILETS, }; #endif diff --git a/src/openrct2/world/Map.cpp b/src/openrct2/world/Map.cpp index 833c0d97a7..d9e0131f5e 100644 --- a/src/openrct2/world/Map.cpp +++ b/src/openrct2/world/Map.cpp @@ -2822,7 +2822,7 @@ void game_command_place_large_scenery(sint32* eax, sint32* ebx, sint32* ecx, sin sint32 zLow = (tile->z_offset + maxHeight) / 8; sint32 zHigh = (tile->z_clearance / 8) + zLow; - sint32 bx = tile->var_7 >> 12; + sint32 bx = tile->flags >> 12; bx <<= rotation; uint8 bl = bx; uint8 bh = bl >> 4; diff --git a/src/openrct2/world/Scenery.h b/src/openrct2/world/Scenery.h index f61bed122b..afd723e395 100644 --- a/src/openrct2/world/Scenery.h +++ b/src/openrct2/world/Scenery.h @@ -56,15 +56,22 @@ struct rct_large_scenery_tile { sint16 y_offset; sint16 z_offset; uint8 z_clearance; - uint16 var_7; + // CCCC WWWW 0SS0 0000 + uint16 flags; }; assert_struct_size(rct_large_scenery_tile, 9); +enum +{ + LARGE_SCENERY_TILE_FLAG_NO_SUPPORTS = 0x20, + LARGE_SCENERY_TILE_FLAG_ALLOW_SUPPORTS_ABOVE = 0x40, +}; + struct rct_large_scenery_text_glyph { uint8 image_offset; uint8 width; uint8 height; - uint8 var_3; + uint8 pad_3; }; assert_struct_size(rct_large_scenery_text_glyph, 4); @@ -73,7 +80,7 @@ struct rct_large_scenery_text { uint16 max_width; // 0x8 uint16 pad_A; // 0xA uint8 flags; // 0xC - uint8 var_D; // 0xD + uint8 num_images; // 0xD rct_large_scenery_text_glyph glyphs[256]; // 0xE }; assert_struct_size(rct_large_scenery_text, 14 + 4 * 256); diff --git a/src/openrct2/world/Wall.cpp b/src/openrct2/world/Wall.cpp index 73255989a9..bfc911dc74 100644 --- a/src/openrct2/world/Wall.cpp +++ b/src/openrct2/world/Wall.cpp @@ -222,7 +222,7 @@ static bool WallCheckObstruction(rct_scenery_entry * wall, tile = &entry->large_scenery.tiles[sequence]; { sint32 direction = ((edge - tile_element_get_direction(tileElement)) & TILE_ELEMENT_DIRECTION_MASK) + 8; - if (!(tile->var_7 & (1 << direction))) + if (!(tile->flags & (1 << direction))) { map_obstruction_set_error_text(tileElement); return false;