1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-22 14:24:33 +01:00

Merge pull request #12667 from mwnciau/json-refactor

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

View File

@@ -32,7 +32,6 @@ ExternalProject_Add(libs
BUILD_BYPRODUCTS
${CMAKE_BINARY_DIR}/libs/lib/${CMAKE_SHARED_LIBRARY_PREFIX}freetype${CMAKE_SHARED_LIBRARY_SUFFIX}
${CMAKE_BINARY_DIR}/libs/lib/${CMAKE_SHARED_LIBRARY_PREFIX}jansson${CMAKE_SHARED_LIBRARY_SUFFIX}
${CMAKE_BINARY_DIR}/libs/lib/${CMAKE_SHARED_LIBRARY_PREFIX}png16${CMAKE_SHARED_LIBRARY_SUFFIX}
${CMAKE_BINARY_DIR}/libs/lib/${CMAKE_SHARED_LIBRARY_PREFIX}SDL2-2.0${CMAKE_SHARED_LIBRARY_SUFFIX}
${CMAKE_BINARY_DIR}/libs/lib/${CMAKE_STATIC_LIBRARY_PREFIX}SDL2main${CMAKE_STATIC_LIBRARY_SUFFIX}
@@ -58,7 +57,6 @@ add_custom_command(TARGET libs POST_BUILD
# COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_BINARY_DIR}/libs/lib/*.so" ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_BINARY_DIR}/libs/lib/libcrypto.so" ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_BINARY_DIR}/libs/lib/libfreetype.so" ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_BINARY_DIR}/libs/lib/libjansson.so" ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_BINARY_DIR}/libs/lib/libpng16.so" ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_BINARY_DIR}/libs/lib/libSDL2-2.0.so" ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_BINARY_DIR}/libs/lib/libspeexdsp.so" ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
@@ -71,12 +69,6 @@ set_target_properties(freetype PROPERTIES IMPORTED_LOCATION
)
add_dependencies(freetype libs)
add_library(jansson SHARED IMPORTED)
set_target_properties(jansson PROPERTIES IMPORTED_LOCATION
${CMAKE_BINARY_DIR}/libs/lib/${CMAKE_SHARED_LIBRARY_PREFIX}jansson${CMAKE_SHARED_LIBRARY_SUFFIX}
)
add_dependencies(jansson libs)
add_library(png SHARED IMPORTED)
set_target_properties(png PROPERTIES IMPORTED_LOCATION
${CMAKE_BINARY_DIR}/libs/lib/${CMAKE_SHARED_LIBRARY_PREFIX}png16${CMAKE_SHARED_LIBRARY_SUFFIX}
@@ -175,7 +167,7 @@ file(GLOB_RECURSE OPENRCT2_CLI_SOURCES
"${ORCT2_ROOT}/src/openrct2-cli/*.hpp")
add_library(openrct2 SHARED ${LIBOPENRCT2_SOURCES})
target_link_libraries(openrct2 android stdc++ log dl z SDL2 png jansson icu icuuc icudata ssl crypto)
target_link_libraries(openrct2 android stdc++ log dl z SDL2 png icu icuuc icudata ssl crypto)
add_library(openrct2-ui SHARED ${OPENRCT2_GUI_SOURCES})
target_link_libraries(openrct2-ui openrct2 android stdc++ GLESv1_CM GLESv2 SDL2main speexdsp)
@@ -187,3 +179,8 @@ target_include_directories(openrct2 SYSTEM PRIVATE "${ORCT2_ROOT}/src/thirdparty
target_include_directories(openrct2-ui PRIVATE "${ORCT2_ROOT}/src")
target_include_directories(openrct2-ui SYSTEM PRIVATE "${ORCT2_ROOT}/src/thirdparty")
target_include_directories(openrct2-cli PRIVATE "${ORCT2_ROOT}/src")
# Header-only nlohmann library is installed in CI images at /nlohmann.
# To be tiny bit more specific, use '/nlohmann/../' instead of just '/'
target_include_directories(openrct2 PRIVATE "/nlohmann/../")
target_include_directories(openrct2-ui PRIVATE "/nlohmann/../")

View File

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

View File

@@ -9,11 +9,9 @@ option(DISABLE_OPENGL "Disable OpenGL support.")
# Third party libraries
if (MSVC)
find_package(jansson REQUIRED)
find_package(SDL2 REQUIRED)
find_library(SPEEX_LDFLAGS libspeexdsp)
else ()
PKG_CHECK_MODULES(JANSSON REQUIRED jansson>=2.5)
PKG_CHECK_MODULES(SDL2 REQUIRED sdl2)
PKG_CHECK_MODULES(SPEEX REQUIRED speexdsp)
endif ()

View File

@@ -14,7 +14,6 @@
#include "Window.h"
#include <algorithm>
#include <jansson.h>
#include <memory>
#include <openrct2/Context.h>
#include <openrct2/PlatformEnvironment.h>
@@ -53,8 +52,11 @@ struct UIThemeWindowEntry
rct_windowclass WindowClass;
WindowTheme Theme;
json_t* ToJson() const;
static UIThemeWindowEntry FromJson(const WindowThemeDesc* wtDesc, const json_t* json);
json_t ToJson() const;
/**
* @note json is deliberately left non-const: json_t behaviour changes when const
*/
static UIThemeWindowEntry FromJson(const WindowThemeDesc* wtDesc, json_t& json);
};
/**
@@ -76,10 +78,13 @@ public:
void SetEntry(const UIThemeWindowEntry* entry);
void RemoveEntry(rct_windowclass windowClass);
json_t* ToJson() const;
json_t ToJson() const;
bool WriteToFile(const std::string& path) const;
static UITheme* FromJson(const json_t* json);
/**
* @note json is deliberately left non-const: json_t behaviour changes when const
*/
static UITheme* FromJson(json_t& json);
static UITheme* FromFile(const std::string& path);
static UITheme CreatePredefined(const std::string& name, const UIThemeWindowEntry* entries, uint8_t flags);
};
@@ -266,7 +271,7 @@ static void ThrowThemeLoadException()
#pragma region UIThemeEntry
json_t* UIThemeWindowEntry::ToJson() const
json_t UIThemeWindowEntry::ToJson() const
{
const WindowThemeDesc* wtDesc = GetWindowThemeDescriptor(WindowClass);
if (wtDesc == nullptr)
@@ -274,37 +279,41 @@ json_t* UIThemeWindowEntry::ToJson() const
return nullptr;
}
json_t* jsonColours = json_array();
json_t jsonColours = json_t::array();
for (uint8_t i = 0; i < wtDesc->NumColours; i++)
{
colour_t colour = Theme.Colours[i];
json_array_append_new(jsonColours, json_integer(colour));
jsonColours.push_back(colour);
}
json_t* jsonEntry = json_object();
json_object_set_new(jsonEntry, "colours", jsonColours);
json_t jsonEntry = {
{ "colours", jsonColours },
};
return jsonEntry;
}
UIThemeWindowEntry UIThemeWindowEntry::FromJson(const WindowThemeDesc* wtDesc, const json_t* json)
UIThemeWindowEntry UIThemeWindowEntry::FromJson(const WindowThemeDesc* wtDesc, json_t& jsonData)
{
json_t* jsonColours = json_object_get(json, "colours");
if (jsonColours == nullptr)
Guard::Assert(jsonData.is_object(), "UIThemeWindowEntry::FromJson expects parameter jsonData to be object");
auto jsonColours = Json::AsArray(jsonData["colours"]);
if (jsonColours.empty())
{
ThrowThemeLoadException();
}
uint8_t numColours = static_cast<uint8_t>(json_array_size(jsonColours));
numColours = std::min(numColours, wtDesc->NumColours);
UIThemeWindowEntry result{};
result.WindowClass = wtDesc->WindowClass;
result.Theme = wtDesc->DefaultTheme;
for (uint8_t i = 0; i < numColours; i++)
// result.Theme.Colours only has 6 values
size_t colourCount = std::min(jsonColours.size(), static_cast<size_t>(wtDesc->NumColours));
for (size_t i = 0; i < colourCount; i++)
{
result.Theme.Colours[i] = static_cast<colour_t>(json_integer_value(json_array_get(jsonColours, i)));
result.Theme.Colours[i] = Json::GetNumber<colour_t>(jsonColours[i]);
}
return result;
@@ -355,10 +364,10 @@ void UITheme::RemoveEntry(rct_windowclass windowClass)
}
}
json_t* UITheme::ToJson() const
json_t UITheme::ToJson() const
{
// Create entries
json_t* jsonEntries = json_object();
json_t jsonEntries;
for (const UIThemeWindowEntry& entry : Entries)
{
const WindowThemeDesc* wtDesc = GetWindowThemeDescriptor(entry.WindowClass);
@@ -366,30 +375,29 @@ json_t* UITheme::ToJson() const
{
return nullptr;
}
json_object_set_new(jsonEntries, wtDesc->WindowClassSZ, entry.ToJson());
jsonEntries[wtDesc->WindowClassSZ] = entry.ToJson();
}
// Create theme object
json_t* jsonTheme = json_object();
json_object_set_new(jsonTheme, "name", json_string(Name.c_str()));
json_object_set_new(jsonTheme, "entries", jsonEntries);
json_object_set_new(jsonTheme, "useLightsRide", json_boolean(Flags & UITHEME_FLAG_USE_LIGHTS_RIDE));
json_object_set_new(jsonTheme, "useLightsPark", json_boolean(Flags & UITHEME_FLAG_USE_LIGHTS_PARK));
json_object_set_new(
jsonTheme, "useAltScenarioSelectFont", json_boolean(Flags & UITHEME_FLAG_USE_ALTERNATIVE_SCENARIO_SELECT_FONT));
json_object_set_new(jsonTheme, "useFullBottomToolbar", json_boolean(Flags & UITHEME_FLAG_USE_FULL_BOTTOM_TOOLBAR));
json_t jsonTheme = {
{ "name", Name },
{ "entries", jsonEntries },
{ "useLightsRide", (Flags & UITHEME_FLAG_USE_LIGHTS_RIDE) != 0 },
{ "useLightsPark", (Flags & UITHEME_FLAG_USE_LIGHTS_PARK) != 0 },
{ "useAltScenarioSelectFont", (Flags & UITHEME_FLAG_USE_ALTERNATIVE_SCENARIO_SELECT_FONT) != 0 },
{ "useFullBottomToolbar", (Flags & UITHEME_FLAG_USE_FULL_BOTTOM_TOOLBAR) != 0 },
};
return jsonTheme;
}
bool UITheme::WriteToFile(const std::string& path) const
{
json_t* jsonTheme = ToJson();
auto jsonTheme = ToJson();
bool result;
try
{
Json::WriteToFile(path.c_str(), jsonTheme, JSON_INDENT(4) | JSON_PRESERVE_ORDER);
Json::WriteToFile(path.c_str(), jsonTheme);
result = true;
}
catch (const std::exception& ex)
@@ -398,52 +406,51 @@ bool UITheme::WriteToFile(const std::string& path) const
result = false;
}
json_decref(jsonTheme);
return result;
}
UITheme* UITheme::FromJson(const json_t* json)
UITheme* UITheme::FromJson(json_t& jsonObj)
{
const char* themeName = json_string_value(json_object_get(json, "name"));
if (themeName == nullptr)
Guard::Assert(jsonObj.is_object(), "UITheme::FromJson expects parameter jsonObj to be object");
const std::string themeName = Json::GetString(jsonObj["name"]);
if (themeName.empty())
{
ThrowThemeLoadException();
}
json_t* jsonEntries = json_object_get(json, "entries");
json_t jsonEntries = jsonObj["entries"];
UITheme* result = nullptr;
try
{
result = new UITheme(themeName);
if (json_is_true(json_object_get(json, "useLightsRide")))
{
result->Flags |= UITHEME_FLAG_USE_LIGHTS_RIDE;
}
if (json_is_true(json_object_get(json, "useLightsPark")))
{
result->Flags |= UITHEME_FLAG_USE_LIGHTS_PARK;
}
if (json_is_true(json_object_get(json, "useAltScenarioSelectFont")))
{
result->Flags |= UITHEME_FLAG_USE_ALTERNATIVE_SCENARIO_SELECT_FONT;
}
if (json_is_true(json_object_get(json, "useFullBottomToolbar")))
{
result->Flags |= UITHEME_FLAG_USE_FULL_BOTTOM_TOOLBAR;
}
result->Flags = Json::GetFlags<uint8_t>(
jsonObj,
{
{ "useLightsRide", UITHEME_FLAG_USE_LIGHTS_RIDE },
{ "useLightsPark", UITHEME_FLAG_USE_LIGHTS_PARK },
{ "useAltScenarioSelectFont", UITHEME_FLAG_USE_ALTERNATIVE_SCENARIO_SELECT_FONT },
{ "useFullBottomToolbar", UITHEME_FLAG_USE_FULL_BOTTOM_TOOLBAR },
});
const char* jkey;
json_t* jvalue;
json_object_foreach(jsonEntries, jkey, jvalue)
if (jsonEntries.is_object())
{
const WindowThemeDesc* wtDesc = GetWindowThemeDescriptor(jkey);
if (wtDesc == nullptr)
continue;
for (auto& [jsonKey, jsonValue] : jsonEntries.items())
{
if (jsonValue.is_object())
{
const WindowThemeDesc* wtDesc = GetWindowThemeDescriptor(jsonKey.data());
if (wtDesc == nullptr)
{
continue;
}
UIThemeWindowEntry entry = UIThemeWindowEntry::FromJson(wtDesc, jvalue);
result->SetEntry(&entry);
UIThemeWindowEntry entry = UIThemeWindowEntry::FromJson(wtDesc, jsonValue);
result->SetEntry(&entry);
}
}
}
return result;
@@ -457,8 +464,8 @@ UITheme* UITheme::FromJson(const json_t* json)
UITheme* UITheme::FromFile(const std::string& path)
{
json_t* json = nullptr;
UITheme* result = nullptr;
json_t json;
try
{
json = Json::ReadFromFile(path.c_str());
@@ -469,8 +476,6 @@ UITheme* UITheme::FromFile(const std::string& path)
log_error("Unable to read theme: %s", path.c_str());
result = nullptr;
}
json_decref(json);
return result;
}

View File

@@ -211,18 +211,17 @@ private:
if (response.status == Http::Status::OK)
{
auto jresponse = Json::FromString(response.body);
if (jresponse != nullptr)
if (jresponse.is_object())
{
auto objName = json_string_value(json_object_get(jresponse, "name"));
auto source = json_string_value(json_object_get(jresponse, "source"));
auto downloadLink = json_string_value(json_object_get(jresponse, "download"));
if (downloadLink != nullptr)
auto objName = Json::GetString(jresponse["name"]);
auto source = Json::GetString(jresponse["source"]);
auto downloadLink = Json::GetString(jresponse["download"]);
if (!downloadLink.empty())
{
_lastDownloadSource = source;
UpdateProgress({ name, source, _currentDownloadIndex, _entries.size() });
DownloadObject(entry, objName, downloadLink);
}
json_decref(jresponse);
}
}
else if (response.status == Http::Status::NotFound)

View File

@@ -112,15 +112,12 @@ endif ()
# Third party libraries
if (MSVC)
find_package(jansson CONFIG REQUIRED)
set(JANSSON_LIBRARIES "jansson::jansson")
find_package(png 1.6 REQUIRED)
find_package(zlib REQUIRED)
find_path(LIBZIP_INCLUDE_DIRS zip.h)
find_library(LIBZIP_LIBRARIES zip)
else ()
PKG_CHECK_MODULES(JANSSON REQUIRED jansson>=2.5)
PKG_CHECK_MODULES(LIBZIP REQUIRED libzip>=1.0)
PKG_CHECK_MODULES(ZLIB REQUIRED zlib)
@@ -137,12 +134,12 @@ else ()
endif ()
if (STATIC)
target_link_libraries(${PROJECT_NAME} ${JANSSON_STATIC_LIBRARIES}
target_link_libraries(${PROJECT_NAME}
${PNG_STATIC_LIBRARIES}
${ZLIB_STATIC_LIBRARIES}
${LIBZIP_STATIC_LIBRARIES})
else ()
target_link_libraries(${PROJECT_NAME} ${JANSSON_LIBRARIES}
target_link_libraries(${PROJECT_NAME}
${PNG_LIBRARIES}
${ZLIB_LIBRARIES}
${LIBZIP_LIBRARIES})
@@ -201,7 +198,6 @@ endif()
# Includes
target_include_directories(${PROJECT_NAME} PRIVATE ${LIBZIP_INCLUDE_DIRS})
target_include_directories(${PROJECT_NAME} PUBLIC ${JANSSON_INCLUDE_DIRS})
target_include_directories(${PROJECT_NAME} PRIVATE ${PNG_INCLUDE_DIRS}
${ZLIB_INCLUDE_DIRS})
include_directories(${PROJECT_NAME} SYSTEM ${CMAKE_CURRENT_LIST_DIR}/../thirdparty)

View File

@@ -14,6 +14,7 @@
#include "Context.h"
#include "OpenRCT2.h"
#include "core/Imaging.h"
#include "core/Json.hpp"
#include "drawing/Drawing.h"
#include "drawing/ImageImporter.h"
#include "object/ObjectLimits.h"
@@ -26,7 +27,6 @@
#include <cmath>
#include <cstring>
#include <jansson.h>
#ifdef _WIN32
# include "core/String.hpp"
@@ -675,28 +675,16 @@ int32_t cmdline_for_sprite(const char** argv, int32_t argc)
const char* spriteDescriptionPath = argv[2];
char* directoryPath = path_get_directory(spriteDescriptionPath);
json_error_t error;
auto fp = fopen(spriteDescriptionPath, "rb");
if (fp == nullptr)
json_t jsonSprites = Json::ReadFromFile(spriteDescriptionPath);
if (jsonSprites.is_null())
{
fprintf(stderr, "Unable to read sprite description file: %s\n", spriteDescriptionPath);
return -1;
}
json_t* sprite_list = json_loadf(fp, JSON_REJECT_DUPLICATES, &error);
fclose(fp);
if (sprite_list == nullptr)
{
fprintf(
stderr, "Error parsing sprite description file: %s at line %d column %d\n", error.text, error.line,
error.column);
return -1;
}
if (!json_is_array(sprite_list))
if (!jsonSprites.is_array())
{
fprintf(stderr, "Error: expected array\n");
json_decref(sprite_list);
return -1;
}
@@ -708,66 +696,45 @@ int32_t cmdline_for_sprite(const char** argv, int32_t argc)
fprintf(stdout, "Building: %s\n", spriteFilePath);
size_t i;
json_t* sprite_description;
json_t sprite_description;
json_array_foreach(sprite_list, i, sprite_description)
// Note: jsonSprite is deliberately left non-const: json_t behaviour changes when const
for (auto& [jsonKey, jsonSprite] : jsonSprites.items())
{
if (!json_is_object(sprite_description))
if (!jsonSprite.is_object())
{
fprintf(stderr, "Error: expected object for sprite %lu\n", static_cast<unsigned long>(i));
json_decref(sprite_list);
fprintf(stderr, "Error: expected object for sprite %s\n", jsonKey.c_str());
return -1;
}
json_t* path = json_object_get(sprite_description, "path");
if (!json_is_string(path))
json_t path = jsonSprite["path"];
if (!path.is_string())
{
fprintf(stderr, "Error: no path provided for sprite %lu\n", static_cast<unsigned long>(i));
json_decref(sprite_list);
fprintf(stderr, "Error: no path provided for sprite %s\n", jsonKey.c_str());
return -1;
}
// Get x and y offsets, if present
json_t* x_offset = json_object_get(sprite_description, "x_offset");
json_t* y_offset = json_object_get(sprite_description, "y_offset");
std::string strPath = Json::GetString(path);
// Get palette option, if present
bool keep_palette = false;
json_t* palette = json_object_get(sprite_description, "palette");
if (json_is_string(palette))
{
const char* option = json_string_value(palette);
if (strncmp(option, "keep", 4) == 0)
{
keep_palette = true;
}
}
json_t x_offset = jsonSprite["x_offset"];
json_t y_offset = jsonSprite["y_offset"];
// Get forcebmp option, if present
bool forceBmp = false;
json_t* forceBmpObject = json_object_get(sprite_description, "forceBmp");
if (palette && json_is_boolean(forceBmpObject))
{
forceBmp = json_boolean_value(forceBmpObject);
}
bool keep_palette = Json::GetString(jsonSprite["palette"]) == "keep";
bool forceBmp = !jsonSprite["palette"].is_null() && Json::GetBoolean(jsonSprite["forceBmp"]);
// Resolve absolute sprite path
auto imagePath = platform_get_absolute_path(json_string_value(path), directoryPath);
auto imagePath = platform_get_absolute_path(strPath.c_str(), directoryPath);
auto importResult = sprite_file_import(
imagePath.c_str(), x_offset == nullptr ? 0 : json_integer_value(x_offset),
y_offset == nullptr ? 0 : json_integer_value(y_offset), keep_palette, forceBmp, gSpriteMode);
imagePath.c_str(), Json::GetNumber<int16_t>(x_offset), Json::GetNumber<int16_t>(y_offset), keep_palette,
forceBmp, gSpriteMode);
if (importResult == std::nullopt)
{
fprintf(stderr, "Could not import image file: %s\nCanceling\n", imagePath.c_str());
json_decref(sprite_list);
return -1;
}
if (!sprite_file_open(spriteFilePath))
{
fprintf(stderr, "Unable to open sprite file: %s\nCanceling\n", spriteFilePath);
json_decref(sprite_list);
return -1;
}
@@ -790,7 +757,6 @@ int32_t cmdline_for_sprite(const char** argv, int32_t argc)
if (!sprite_file_save(spriteFilePath))
{
fprintf(stderr, "Could not save sprite file: %s\nCanceling\n", imagePath.c_str());
json_decref(sprite_list);
return -1;
}
@@ -800,7 +766,6 @@ int32_t cmdline_for_sprite(const char** argv, int32_t argc)
sprite_file_close();
}
json_decref(sprite_list);
free(directoryPath);
fprintf(stdout, "Finished\n");

View File

@@ -81,24 +81,12 @@ NewVersionInfo get_latest_version()
return {};
}
json_t* root = Json::FromString(res.body);
json_t root = Json::FromString(res.body);
auto get_as_string = [root](std::string name) {
std::string value;
json_t* json_value = json_object_get(root, name.c_str());
if (json_is_string(json_value))
{
value = (json_string_value(json_value));
}
return value;
};
verinfo.tag = get_as_string("tag_name");
verinfo.name = get_as_string("name");
verinfo.changelog = get_as_string("body");
verinfo.url = get_as_string("html_url");
json_decref(root);
verinfo.tag = Json::GetString(root["tag_name"]);
verinfo.name = Json::GetString(root["name"]);
verinfo.changelog = Json::GetString(root["body"]);
verinfo.url = Json::GetString(root["html_url"]);
gConfigGeneral.last_version_check_time = now;
config_save_default();

View File

@@ -15,9 +15,8 @@
namespace Json
{
json_t* ReadFromFile(const utf8* path, size_t maxSize)
json_t ReadFromFile(const utf8* path, size_t maxSize)
{
json_t* json = nullptr;
auto fs = OpenRCT2::FileStream(path, OpenRCT2::FILE_MODE_OPEN);
size_t fileLength = static_cast<size_t>(fs.GetLength());
@@ -26,42 +25,101 @@ namespace Json
throw IOException("Json file too large.");
}
utf8* fileData = Memory::Allocate<utf8>(fileLength + 1);
fs.Read(fileData, fileLength);
fileData[fileLength] = '\0';
auto fileData = std::string(static_cast<size_t>(fileLength) + 1, '\0');
fs.Read(static_cast<void*>(fileData.data()), fileLength);
json_error_t jsonLoadError;
json = json_loads(fileData, 0, &jsonLoadError);
Memory::Free(fileData);
json_t json;
if (json == nullptr)
try
{
throw JsonException(&jsonLoadError);
json = json_t::parse(fileData, nullptr, true, true);
}
catch (const json_t::exception& e)
{
throw JsonException(String::Format("Unable to parse JSON file (%s)\n\t%s", path, e.what()));
}
return json;
}
void WriteToFile(const utf8* path, const json_t* json, size_t flags)
void WriteToFile(const utf8* path, const json_t& jsonData, int indentSize)
{
// Serialise JSON
const char* jsonOutput = json_dumps(json, flags);
std::string jsonOutput = jsonData.dump(indentSize);
// Write to file
auto fs = OpenRCT2::FileStream(path, OpenRCT2::FILE_MODE_WRITE);
size_t jsonOutputSize = String::SizeOf(jsonOutput);
fs.Write(jsonOutput, jsonOutputSize);
fs.Write(jsonOutput.data(), jsonOutput.size());
}
json_t* FromString(std::string_view raw)
json_t FromString(std::string_view raw)
{
json_t* root;
json_error_t error;
root = json_loadb(raw.data(), raw.size(), 0, &error);
if (root == nullptr)
json_t json;
try
{
throw JsonException(&error);
json = json_t::parse(raw, nullptr, true, true);
}
return root;
catch (const json_t::exception& e)
{
log_error("Unable to parse JSON string (%s)\n\t%s", raw, e.what());
}
return json;
}
json_t FromVector(const std::vector<uint8_t>& vec)
{
json_t json;
try
{
json = json_t::parse(vec.begin(), vec.end());
}
catch (const json_t::exception& e)
{
log_error("Unable to parse JSON vector (%s)\n\t%s", vec.data(), e.what());
}
return json;
}
std::string GetString(const json_t& jsonObj, const std::string& defaultValue)
{
return jsonObj.is_string() ? jsonObj.get<std::string>() : defaultValue;
}
bool GetBoolean(const json_t& jsonObj, bool defaultValue)
{
return jsonObj.is_boolean() ? jsonObj.get<bool>() : defaultValue;
}
json_t AsObject(const json_t& jsonObj)
{
return jsonObj.is_object() ? jsonObj : json_t::object();
}
json_t AsArray(const json_t& jsonObj)
{
if (jsonObj.is_array())
{
return jsonObj;
}
json_t retVal = json_t::array();
if (jsonObj.is_object())
{
for (const auto& jItem : jsonObj)
{
retVal.push_back(jItem);
}
}
else if (!jsonObj.is_null())
{
retVal.push_back(jsonObj);
}
return retVal;
}
} // namespace Json

View File

@@ -11,36 +11,181 @@
#include "../common.h"
#include <jansson.h>
#include <stdexcept>
#include <nlohmann/json.hpp>
#include <string>
#include <string_view>
using json_t = nlohmann::json;
namespace Json
{
// Don't try to load JSON files that exceed 64 MiB
constexpr uint64_t MAX_JSON_SIZE = 64 * 1024 * 1024;
json_t* ReadFromFile(const utf8* path, size_t maxSize = MAX_JSON_SIZE);
void WriteToFile(const utf8* path, const json_t* json, size_t flags = 0);
/**
* Read JSON file and parse contents
* @param path Path to the source file
* @param maxSize Max file size in bytes allowed
* @return A JSON representation of the file
* @note This function will throw an exception if the JSON file cannot be parsed
*/
json_t ReadFromFile(const utf8* path, size_t maxSize = MAX_JSON_SIZE);
json_t* FromString(std::string_view raw);
/**
* Read JSON file and parse the contents
* @param path Path to the destination file
* @param jsonData A JSON object
* @param indentSize The number of spaces in an indent, or removes whitespace on -1
*/
void WriteToFile(const utf8* path, const json_t& jsonData, int indentSize = 4);
/**
* Parse JSON from a string
* @param raw JSON string
* @return A JSON representation of the string
* @note This function will throw an exception if the JSON string cannot be parsed
*/
json_t FromString(std::string_view raw);
/**
* Parse JSON from a vector of characters
* @param vec Vector of characters containing JSON
* @return A JSON representation of the vector
* @note This function will throw an exception if the JSON vector cannot be parsed
*/
json_t FromVector(const std::vector<uint8_t>& vec);
/**
* Explicit type conversion between a JSON object and a compatible number value
* @param T Destination numeric type
* @param jsonObj JSON object holding the value
* @param defaultValue Default value to return if the JSON object is not a number type
* @return Copy of the JSON value converted to the given type
*/
template<typename T> T GetNumber(const json_t& jsonObj, T defaultValue = 0)
{
static_assert(std::is_arithmetic<T>::value, "GetNumber template parameter must be arithmetic");
return jsonObj.is_number() ? jsonObj.get<T>() : defaultValue;
}
/**
* Explicit type conversion between a JSON object and a compatible enum value
* @param T Destination enum
* @param jsonObj JSON object holding the value
* @param defaultValue Default value to return if the JSON object is not an enum type
* @return Copy of the JSON value converted to the given enum type
*/
template<typename T> T GetEnum(const json_t& jsonObj, T defaultValue)
{
static_assert(std::is_enum<T>::value, "GetEnum template parameter must be an enum");
return jsonObj.is_number_integer() ? jsonObj.get<T>() : defaultValue;
}
/**
* Explicit type conversion between a JSON object and an std::string
* @param jsonObj JSON object holding the value
* @param defaultValue Default value to return if the JSON object is not a string
* @return Copy of the JSON value converted to std::string
*/
std::string GetString(const json_t& jsonObj, const std::string& defaultValue = std::string());
/**
* Explicit type conversion between a JSON object and a boolean
* @param jsonObj JSON object holding the value
* @param defaultValue Default value to return if the JSON object is not a boolean
* @return Copy of the JSON value converted to bool
*/
bool GetBoolean(const json_t& jsonObj, bool defaultValue = false);
/**
* Ensures a given JSON object is an object type
* @param jsonObj JSON object
* @return The JSON object if it is an object type, or an empty object otherwise
*/
json_t AsObject(const json_t& jsonObj);
/**
* Ensures a given JSON object is an array type
* @param jsonObj JSON object
* @return The JSON object if it is an array type, or an array containing the JSON object otherwise
*/
json_t AsArray(const json_t& jsonObj);
/**
* Helper function to convert a json object and an initializer list to binary flags
* @param T Type to return
* @param jsonObj JSON object containing boolean values
* @param list List of pairs of keys and bits to enable if that key in the object is true
* @return Value with relevant bits flipped
*/
template<typename T> T GetFlags(const json_t& jsonObj, std::initializer_list<std::pair<std::string, T>> list)
{
static_assert(std::is_convertible<T, int>::value, "GetFlags template parameter must be integral or a weak enum");
T flags{};
for (const auto& item : list)
{
if (jsonObj.contains(item.first) && Json::GetBoolean(jsonObj[item.first]))
{
flags = static_cast<T>(flags | item.second);
}
}
return flags;
}
/**
* Used by the GetFlags function to allow for inverted values
*/
enum class FlagType : uint8_t
{
// Flag is turned on if the key is true
Normal,
// Flag is turned on if the key is false
Inverted
};
/**
* Helper function to convert a json object and an initializer list to binary flags
* @param T Type to return
* @param jsonObj JSON object containing boolean values
* @param list List of tuples of keys, bits to change and flag type
* @return Value with relevant bits flipped
* @note FLAG_NORMAL behaves like the other GetFlags function, but FLAG_INVERTED will turn the flag on when false
*/
template<typename T> T GetFlags(const json_t& jsonObj, std::initializer_list<std::tuple<std::string, T, FlagType>> list)
{
static_assert(std::is_convertible<T, int>::value, "GetFlags template parameter must be integral or a weak enum");
T flags{};
for (const auto& item : list)
{
if (std::get<2>(item) == FlagType::Normal)
{
if (jsonObj.contains(std::get<0>(item)) && Json::GetBoolean(jsonObj[std::get<0>(item)]))
{
flags = static_cast<T>(flags | std::get<1>(item));
}
}
else
{
// if the json flag doesn't exist, assume it's false
if (!jsonObj.contains(std::get<0>(item)) || !Json::GetBoolean(jsonObj[std::get<0>(item)]))
{
flags = static_cast<T>(flags | std::get<1>(item));
}
}
}
return flags;
}
} // namespace Json
class JsonException final : public std::runtime_error
{
private:
json_error_t _jsonError = {};
public:
explicit JsonException(const std::string& message)
: std::runtime_error(message)
{
}
explicit JsonException(const json_error_t* jsonError)
: JsonException(std::string(jsonError->text))
{
_jsonError = *jsonError;
}
};

View File

@@ -57,6 +57,49 @@ void colours_init_maps()
}
}
namespace Colour
{
colour_t FromString(const std::string_view& s, colour_t defaultValue)
{
static const std::unordered_map<std::string_view, colour_t> LookupTable{
{ "black", COLOUR_BLACK },
{ "grey", COLOUR_GREY },
{ "white", COLOUR_WHITE },
{ "dark_purple", COLOUR_DARK_PURPLE },
{ "light_purple", COLOUR_LIGHT_PURPLE },
{ "bright_purple", COLOUR_BRIGHT_PURPLE },
{ "dark_blue", COLOUR_DARK_BLUE },
{ "light_blue", COLOUR_LIGHT_BLUE },
{ "icy_blue", COLOUR_ICY_BLUE },
{ "teal", COLOUR_TEAL },
{ "aquamarine", COLOUR_AQUAMARINE },
{ "saturated_green", COLOUR_SATURATED_GREEN },
{ "dark_green", COLOUR_DARK_GREEN },
{ "moss_green", COLOUR_MOSS_GREEN },
{ "bright_green", COLOUR_BRIGHT_GREEN },
{ "olive_green", COLOUR_OLIVE_GREEN },
{ "dark_olive_green", COLOUR_DARK_OLIVE_GREEN },
{ "bright_yellow", COLOUR_BRIGHT_YELLOW },
{ "yellow", COLOUR_YELLOW },
{ "dark_yellow", COLOUR_DARK_YELLOW },
{ "light_orange", COLOUR_LIGHT_ORANGE },
{ "dark_orange", COLOUR_DARK_ORANGE },
{ "light_brown", COLOUR_LIGHT_BROWN },
{ "saturated_brown", COLOUR_SATURATED_BROWN },
{ "dark_brown", COLOUR_DARK_BROWN },
{ "salmon_pink", COLOUR_SALMON_PINK },
{ "bordeaux_red", COLOUR_BORDEAUX_RED },
{ "saturated_red", COLOUR_SATURATED_RED },
{ "bright_red", COLOUR_BRIGHT_RED },
{ "dark_pink", COLOUR_DARK_PINK },
{ "bright_pink", COLOUR_BRIGHT_PINK },
{ "light_pink", COLOUR_LIGHT_PINK },
};
auto result = LookupTable.find(s);
return (result != LookupTable.end()) ? result->second : defaultValue;
}
} // namespace Colour
#ifndef NO_TTF
static uint8_t BlendColourMap[PALETTE_COUNT][PALETTE_COUNT] = { 0 };

View File

@@ -12,6 +12,8 @@
#include "../common.h"
#include <string_view>
/**
* Colour IDs as used by the colour dropdown, NOT palette indices.
*/
@@ -207,6 +209,11 @@ extern rct_colour_map ColourMapA[COLOUR_COUNT];
void colours_init_maps();
namespace Colour
{
colour_t FromString(const std::string_view& s, colour_t defaultValue = COLOUR_BLACK);
}
#ifndef NO_TTF
uint8_t blendColours(const uint8_t paletteIndex1, const uint8_t paletteIndex2);
#endif

View File

@@ -0,0 +1,52 @@
/*****************************************************************************
* Copyright (c) 2014-2020 OpenRCT2 developers
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
*****************************************************************************/
#include "Cursors.h"
#include <string>
#include <unordered_map>
namespace Cursor
{
uint8_t FromString(const std::string& s, uint8_t defaultValue)
{
static const std::unordered_map<std::string, uint8_t> LookupTable{
{ "CURSOR_BLANK", CURSOR_BLANK },
{ "CURSOR_UP_ARROW", CURSOR_UP_ARROW },
{ "CURSOR_UP_DOWN_ARROW", CURSOR_UP_DOWN_ARROW },
{ "CURSOR_HAND_POINT", CURSOR_HAND_POINT },
{ "CURSOR_ZZZ", CURSOR_ZZZ },
{ "CURSOR_DIAGONAL_ARROWS", CURSOR_DIAGONAL_ARROWS },
{ "CURSOR_PICKER", CURSOR_PICKER },
{ "CURSOR_TREE_DOWN", CURSOR_TREE_DOWN },
{ "CURSOR_FOUNTAIN_DOWN", CURSOR_FOUNTAIN_DOWN },
{ "CURSOR_STATUE_DOWN", CURSOR_STATUE_DOWN },
{ "CURSOR_BENCH_DOWN", CURSOR_BENCH_DOWN },
{ "CURSOR_CROSS_HAIR", CURSOR_CROSS_HAIR },
{ "CURSOR_BIN_DOWN", CURSOR_BIN_DOWN },
{ "CURSOR_LAMPPOST_DOWN", CURSOR_LAMPPOST_DOWN },
{ "CURSOR_FENCE_DOWN", CURSOR_FENCE_DOWN },
{ "CURSOR_FLOWER_DOWN", CURSOR_FLOWER_DOWN },
{ "CURSOR_PATH_DOWN", CURSOR_PATH_DOWN },
{ "CURSOR_DIG_DOWN", CURSOR_DIG_DOWN },
{ "CURSOR_WATER_DOWN", CURSOR_WATER_DOWN },
{ "CURSOR_HOUSE_DOWN", CURSOR_HOUSE_DOWN },
{ "CURSOR_VOLCANO_DOWN", CURSOR_VOLCANO_DOWN },
{ "CURSOR_WALK_DOWN", CURSOR_WALK_DOWN },
{ "CURSOR_PAINT_DOWN", CURSOR_PAINT_DOWN },
{ "CURSOR_ENTRANCE_DOWN", CURSOR_ENTRANCE_DOWN },
{ "CURSOR_HAND_OPEN", CURSOR_HAND_OPEN },
{ "CURSOR_HAND_CLOSED", CURSOR_HAND_CLOSED },
{ "CURSOR_ARROW", CURSOR_ARROW },
};
auto result = LookupTable.find(s);
return (result != LookupTable.end()) ? result->second : defaultValue;
}
} // namespace Cursor

View File

@@ -44,6 +44,11 @@ enum CURSOR_ID
CURSOR_COUNT,
};
namespace Cursor
{
uint8_t FromString(const std::string& s, uint8_t defaultValue);
}
namespace OpenRCT2::Ui
{
constexpr size_t CURSOR_BIT_WIDTH = 32;

View File

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

View File

@@ -960,24 +960,23 @@ void NetworkBase::SaveGroups()
platform_get_user_directory(path, nullptr, sizeof(path));
safe_strcat_path(path, "groups.json", sizeof(path));
json_t* jsonGroupsCfg = json_object();
json_t* jsonGroups = json_array();
json_t jsonGroups = json_t::array();
for (auto& group : group_list)
{
json_array_append_new(jsonGroups, group->ToJson());
jsonGroups.push_back(group->ToJson());
}
json_object_set_new(jsonGroupsCfg, "default_group", json_integer(default_group));
json_object_set_new(jsonGroupsCfg, "groups", jsonGroups);
json_t jsonGroupsCfg = {
{ "default_group", default_group },
{ "groups", jsonGroups },
};
try
{
Json::WriteToFile(path, jsonGroupsCfg, JSON_INDENT(4) | JSON_PRESERVE_ORDER);
Json::WriteToFile(path, jsonGroupsCfg);
}
catch (const std::exception& ex)
{
log_error("Unable to save %s: %s", path, ex.what());
}
json_decref(jsonGroupsCfg);
}
}
@@ -1023,12 +1022,12 @@ void NetworkBase::LoadGroups()
platform_get_user_directory(path, nullptr, sizeof(path));
safe_strcat_path(path, "groups.json", sizeof(path));
json_t* json = nullptr;
json_t jsonGroupConfig;
if (Platform::FileExists(path))
{
try
{
json = Json::ReadFromFile(path);
jsonGroupConfig = Json::ReadFromFile(path);
}
catch (const std::exception& e)
{
@@ -1036,28 +1035,26 @@ void NetworkBase::LoadGroups()
}
}
if (json == nullptr)
if (!jsonGroupConfig.is_object())
{
SetupDefaultGroups();
}
else
{
json_t* json_groups = json_object_get(json, "groups");
size_t groupCount = json_array_size(json_groups);
for (size_t i = 0; i < groupCount; i++)
json_t jsonGroups = jsonGroupConfig["groups"];
if (jsonGroups.is_array())
{
json_t* jsonGroup = json_array_get(json_groups, i);
auto newgroup = std::make_unique<NetworkGroup>(NetworkGroup::FromJson(jsonGroup));
group_list.push_back(std::move(newgroup));
for (auto& jsonGroup : jsonGroups)
{
group_list.emplace_back(std::make_unique<NetworkGroup>(NetworkGroup::FromJson(jsonGroup)));
}
}
json_t* jsonDefaultGroup = json_object_get(json, "default_group");
default_group = static_cast<uint8_t>(json_integer_value(jsonDefaultGroup));
default_group = Json::GetNumber<uint8_t>(jsonGroupConfig["default_group"]);
if (GetGroupByID(default_group) == nullptr)
{
default_group = 0;
}
json_decref(json);
}
// Host group should always contain all permissions.
@@ -1594,37 +1591,35 @@ void NetworkBase::Server_Send_SETDISCONNECTMSG(NetworkConnection& connection, co
connection.QueuePacket(std::move(packet));
}
json_t* NetworkBase::GetServerInfoAsJson() const
json_t NetworkBase::GetServerInfoAsJson() const
{
json_t* obj = json_object();
json_object_set_new(obj, "name", json_string(gConfigNetwork.server_name.c_str()));
json_object_set_new(obj, "requiresPassword", json_boolean(_password.size() > 0));
json_object_set_new(obj, "version", json_string(network_get_version().c_str()));
json_object_set_new(obj, "players", json_integer(player_list.size()));
json_object_set_new(obj, "maxPlayers", json_integer(gConfigNetwork.maxplayers));
json_object_set_new(obj, "description", json_string(gConfigNetwork.server_description.c_str()));
json_object_set_new(obj, "greeting", json_string(gConfigNetwork.server_greeting.c_str()));
json_object_set_new(obj, "dedicated", json_boolean(gOpenRCT2Headless));
return obj;
json_t jsonObj = {
{ "name", gConfigNetwork.server_name }, { "requiresPassword", _password.size() > 0 },
{ "version", network_get_version() }, { "players", player_list.size() },
{ "maxPlayers", gConfigNetwork.maxplayers }, { "description", gConfigNetwork.server_description },
{ "greeting", gConfigNetwork.server_greeting }, { "dedicated", gOpenRCT2Headless },
};
return jsonObj;
}
void NetworkBase::Server_Send_GAMEINFO(NetworkConnection& connection)
{
NetworkPacket packet(NetworkCommand::GameInfo);
# ifndef DISABLE_HTTP
json_t* obj = GetServerInfoAsJson();
json_t jsonObj = GetServerInfoAsJson();
// Provider details
json_t* jsonProvider = json_object();
json_object_set_new(jsonProvider, "name", json_string(gConfigNetwork.provider_name.c_str()));
json_object_set_new(jsonProvider, "email", json_string(gConfigNetwork.provider_email.c_str()));
json_object_set_new(jsonProvider, "website", json_string(gConfigNetwork.provider_website.c_str()));
json_object_set_new(obj, "provider", jsonProvider);
json_t jsonProvider = {
{ "name", gConfigNetwork.provider_name },
{ "email", gConfigNetwork.provider_email },
{ "website", gConfigNetwork.provider_website },
};
packet.WriteString(json_dumps(obj, 0));
jsonObj["provider"] = jsonProvider;
packet.WriteString(jsonObj.dump().c_str());
packet << _serverState.gamestateSnapshotsEnabled;
json_decref(obj);
# endif
connection.QueuePacket(std::move(packet));
}
@@ -3178,32 +3173,27 @@ void NetworkBase::Client_Send_GAMEINFO()
_serverConnection->QueuePacket(std::move(packet));
}
static std::string json_stdstring_value(const json_t* string)
{
const char* cstr = json_string_value(string);
return cstr == nullptr ? std::string() : std::string(cstr);
}
void NetworkBase::Client_Handle_GAMEINFO([[maybe_unused]] NetworkConnection& connection, NetworkPacket& packet)
{
const char* jsonString = packet.ReadString();
packet >> _serverState.gamestateSnapshotsEnabled;
json_error_t error;
json_t* root = json_loads(jsonString, 0, &error);
json_t jsonData = Json::FromString(jsonString);
ServerName = json_stdstring_value(json_object_get(root, "name"));
ServerDescription = json_stdstring_value(json_object_get(root, "description"));
ServerGreeting = json_stdstring_value(json_object_get(root, "greeting"));
json_t* jsonProvider = json_object_get(root, "provider");
if (jsonProvider != nullptr)
if (jsonData.is_object())
{
ServerProviderName = json_stdstring_value(json_object_get(jsonProvider, "name"));
ServerProviderEmail = json_stdstring_value(json_object_get(jsonProvider, "email"));
ServerProviderWebsite = json_stdstring_value(json_object_get(jsonProvider, "website"));
ServerName = Json::GetString(jsonData["name"]);
ServerDescription = Json::GetString(jsonData["description"]);
ServerGreeting = Json::GetString(jsonData["greeting"]);
json_t jsonProvider = jsonData["provider"];
if (jsonProvider.is_object())
{
ServerProviderName = Json::GetString(jsonProvider["name"]);
ServerProviderEmail = Json::GetString(jsonProvider["email"]);
ServerProviderWebsite = Json::GetString(jsonProvider["website"]);
}
}
json_decref(root);
network_chat_show_server_greeting();
}
@@ -3977,7 +3967,7 @@ bool network_gamestate_snapshots_enabled()
return network_get_server_state().gamestateSnapshotsEnabled;
}
json_t* network_get_server_info_as_json()
json_t network_get_server_info_as_json()
{
return gNetwork.GetServerInfoAsJson();
}
@@ -4242,8 +4232,8 @@ NetworkServerState_t network_get_server_state()
{
return NetworkServerState_t{};
}
json_t* network_get_server_info_as_json()
json_t network_get_server_info_as_json()
{
return nullptr;
return {};
}
#endif /* DISABLE_NETWORK */

View File

@@ -1,6 +1,7 @@
#pragma once
#include "../actions/GameAction.h"
#include "../core/Json.hpp"
#include "NetworkConnection.h"
#include "NetworkGroup.h"
#include "NetworkPlayer.h"
@@ -42,7 +43,7 @@ public: // Common
void AppendChatLog(const std::string& s);
void CloseChatLog();
NetworkStats_t GetStats() const;
json_t* GetServerInfoAsJson() const;
json_t GetServerInfoAsJson() const;
bool ProcessConnection(NetworkConnection& connection);
void CloseConnection();
NetworkPlayer* AddPlayer(const std::string& name, const std::string& keyhash);

View File

@@ -14,31 +14,29 @@
# include "NetworkAction.h"
# include "NetworkTypes.h"
NetworkGroup NetworkGroup::FromJson(const json_t* json)
NetworkGroup NetworkGroup::FromJson(json_t& jsonData)
{
NetworkGroup group;
json_t* jsonId = json_object_get(json, "id");
json_t* jsonName = json_object_get(json, "name");
json_t* jsonPermissions = json_object_get(json, "permissions");
Guard::Assert(jsonData.is_object(), "NetworkGroup::FromJson expects parameter jsonData to be object");
if (jsonId == nullptr || jsonName == nullptr || jsonPermissions == nullptr)
NetworkGroup group;
json_t jsonId = jsonData["id"];
json_t jsonName = jsonData["name"];
json_t jsonPermissions = jsonData["permissions"];
if (jsonId.is_null() || jsonName.is_null() || jsonPermissions.is_null())
{
throw std::runtime_error("Missing group data");
}
group.Id = static_cast<uint8_t>(json_integer_value(jsonId));
group._name = std::string(json_string_value(jsonName));
group.Id = Json::GetNumber<uint8_t>(jsonId);
group._name = Json::GetString(jsonName);
std::fill(group.ActionsAllowed.begin(), group.ActionsAllowed.end(), 0);
for (size_t i = 0; i < json_array_size(jsonPermissions); i++)
for (const auto& jsonValue : jsonPermissions)
{
json_t* jsonPermissionValue = json_array_get(jsonPermissions, i);
const char* perm_name = json_string_value(jsonPermissionValue);
if (perm_name == nullptr)
{
continue;
}
NetworkPermission action_id = NetworkActions::FindCommandByPermissionName(perm_name);
const std::string permission = Json::GetString(jsonValue);
NetworkPermission action_id = NetworkActions::FindCommandByPermissionName(permission);
if (action_id != NetworkPermission::Count)
{
group.ToggleActionPermission(action_id);
@@ -47,21 +45,21 @@ NetworkGroup NetworkGroup::FromJson(const json_t* json)
return group;
}
json_t* NetworkGroup::ToJson() const
json_t NetworkGroup::ToJson() const
{
json_t* jsonGroup = json_object();
json_object_set_new(jsonGroup, "id", json_integer(Id));
json_object_set_new(jsonGroup, "name", json_string(GetName().c_str()));
json_t* actionsArray = json_array();
json_t jsonGroup = {
{ "id", Id },
{ "name", GetName() },
};
json_t actionsArray = json_t::array();
for (size_t i = 0; i < NetworkActions::Actions.size(); i++)
{
if (CanPerformAction(static_cast<NetworkPermission>(i)))
{
const char* perm_name = NetworkActions::Actions[i].PermissionName.c_str();
json_array_append_new(actionsArray, json_string(perm_name));
actionsArray.push_back(NetworkActions::Actions[i].PermissionName);
}
}
json_object_set_new(jsonGroup, "permissions", actionsArray);
jsonGroup["permissions"] = actionsArray;
return jsonGroup;
}

View File

@@ -10,10 +10,10 @@
#pragma once
#include "../common.h"
#include "../core/Json.hpp"
#include "NetworkPacket.h"
#include <array>
#include <jansson.h>
#include <string>
enum class NetworkPermission : uint32_t;
@@ -24,7 +24,14 @@ public:
std::array<uint8_t, 8> ActionsAllowed{};
uint8_t Id = 0;
static NetworkGroup FromJson(const json_t* json);
/**
* Creates a NetworkGroup object from a JSON object
*
* @param json JSON data source
* @return A NetworkGroup object
* @note json is deliberately left non-const: json_t behaviour changes when const
*/
static NetworkGroup FromJson(json_t& json);
const std::string& GetName() const;
void SetName(std::string name);
@@ -35,7 +42,12 @@ public:
bool CanPerformAction(NetworkPermission index) const;
bool CanPerformCommand(int32_t command) const;
json_t* ToJson() const;
/**
* Serialise a NetworkGroup object into a JSON object
*
* @return JSON representation of the NetworkGroup object
*/
json_t ToJson() const;
private:
std::string _name;

View File

@@ -13,6 +13,7 @@
# include "../config/Config.h"
# include "../core/Console.hpp"
# include "../core/Guard.hpp"
# include "../core/Http.h"
# include "../core/Json.hpp"
# include "../core/String.hpp"
@@ -119,12 +120,10 @@ private:
if (String::Equals(buffer, NETWORK_LAN_BROADCAST_MSG))
{
auto body = GetBroadcastJson();
auto bodyDump = json_dumps(body, JSON_COMPACT);
size_t sendLen = strlen(bodyDump) + 1;
auto bodyDump = body.dump();
size_t sendLen = bodyDump.size() + 1;
log_verbose("Sending %zu bytes back to %s", sendLen, sender.c_str());
_lanListener->SendData(*endpoint, bodyDump, sendLen);
free(bodyDump);
json_decref(body);
_lanListener->SendData(*endpoint, bodyDump.c_str(), sendLen);
}
}
}
@@ -132,10 +131,10 @@ private:
}
}
json_t* GetBroadcastJson()
json_t GetBroadcastJson()
{
auto root = network_get_server_info_as_json();
json_object_set(root, "port", json_integer(_port));
json_t root = network_get_server_info_as_json();
root["port"] = _port;
return root;
}
@@ -172,20 +171,18 @@ private:
request.method = Http::Method::POST;
request.forceIPv4 = forceIPv4;
json_t* body = json_object();
json_object_set_new(body, "key", json_string(_key.c_str()));
json_object_set_new(body, "port", json_integer(_port));
json_t body = {
{ "key", _key },
{ "port", _port },
};
if (!gConfigNetwork.advertise_address.empty())
{
json_object_set_new(body, "address", json_string(gConfigNetwork.advertise_address.c_str()));
body["address"] = gConfigNetwork.advertise_address;
}
char* bodyDump = json_dumps(body, JSON_COMPACT);
request.body = bodyDump;
request.body = body.dump();
request.header["Content-Type"] = "application/json";
free(bodyDump);
json_decref(body);
Http::DoAsync(request, [&](Http::Response response) -> void {
if (response.status != Http::Status::OK)
@@ -194,9 +191,9 @@ private:
return;
}
json_t* root = Json::FromString(response.body);
json_t root = Json::FromString(response.body);
root = Json::AsObject(root);
this->OnRegistrationResponse(root);
json_decref(root);
});
}
@@ -206,12 +203,9 @@ private:
request.url = GetMasterServerUrl();
request.method = Http::Method::PUT;
json_t* body = GetHeartbeatJson();
char* bodyDump = json_dumps(body, JSON_COMPACT);
request.body = bodyDump;
json_t body = GetHeartbeatJson();
request.body = body.dump();
request.header["Content-Type"] = "application/json";
free(bodyDump);
json_decref(body);
_lastHeartbeatTime = platform_get_ticks();
Http::DoAsync(request, [&](Http::Response response) -> void {
@@ -221,86 +215,90 @@ private:
return;
}
json_t* root = Json::FromString(response.body);
json_t root = Json::FromString(response.body);
root = Json::AsObject(root);
this->OnHeartbeatResponse(root);
json_decref(root);
});
}
void OnRegistrationResponse(json_t* jsonRoot)
/**
* @param jsonRoot must be of JSON type object or null
* @note jsonRoot is deliberately left non-const: json_t behaviour changes when const
*/
void OnRegistrationResponse(json_t& jsonRoot)
{
json_t* jsonStatus = json_object_get(jsonRoot, "status");
if (json_is_integer(jsonStatus))
Guard::Assert(jsonRoot.is_object(), "OnRegistrationResponse expects parameter jsonRoot to be object");
int32_t status = Json::GetNumber<int32_t>(jsonRoot["status"]);
if (status == MASTER_SERVER_STATUS_OK)
{
int32_t status = static_cast<int32_t>(json_integer_value(jsonStatus));
if (status == MASTER_SERVER_STATUS_OK)
json_t jsonToken = jsonRoot["token"];
if (jsonToken.is_string())
{
json_t* jsonToken = json_object_get(jsonRoot, "token");
if (json_is_string(jsonToken))
{
_token = std::string(json_string_value(jsonToken));
_status = ADVERTISE_STATUS::REGISTERED;
}
_token = Json::GetString(jsonToken);
_status = ADVERTISE_STATUS::REGISTERED;
}
else
}
else
{
std::string message = Json::GetString(jsonRoot["message"]);
if (message.empty())
{
const char* message = "Invalid response from server";
json_t* jsonMessage = json_object_get(jsonRoot, "message");
if (json_is_string(jsonMessage))
{
message = json_string_value(jsonMessage);
}
Console::Error::WriteLine("Unable to advertise (%d): %s", status, message);
// Hack for https://github.com/OpenRCT2/OpenRCT2/issues/6277
// Master server may not reply correctly if using IPv6, retry forcing IPv4,
// don't wait the full timeout.
if (!_forceIPv4 && status == 500)
{
_forceIPv4 = true;
_lastAdvertiseTime = 0;
log_info("Retry with ipv4 only");
}
message = "Invalid response from server";
}
Console::Error::WriteLine("Unable to advertise (%d): %s", status, message.c_str());
// Hack for https://github.com/OpenRCT2/OpenRCT2/issues/6277
// Master server may not reply correctly if using IPv6, retry forcing IPv4,
// don't wait the full timeout.
if (!_forceIPv4 && status == 500)
{
_forceIPv4 = true;
_lastAdvertiseTime = 0;
log_info("Retry with ipv4 only");
}
}
}
void OnHeartbeatResponse(json_t* jsonRoot)
/**
* @param jsonRoot must be of JSON type object or null
* @note jsonRoot is deliberately left non-const: json_t behaviour changes when const
*/
void OnHeartbeatResponse(json_t& jsonRoot)
{
json_t* jsonStatus = json_object_get(jsonRoot, "status");
if (json_is_integer(jsonStatus))
Guard::Assert(jsonRoot.is_object(), "OnHeartbeatResponse expects parameter jsonRoot to be object");
int32_t status = Json::GetNumber<int32_t>(jsonRoot["status"]);
if (status == MASTER_SERVER_STATUS_OK)
{
int32_t status = static_cast<int32_t>(json_integer_value(jsonStatus));
if (status == MASTER_SERVER_STATUS_OK)
{
// Master server has successfully updated our server status
}
else if (status == MASTER_SERVER_STATUS_INVALID_TOKEN)
{
_status = ADVERTISE_STATUS::UNREGISTERED;
Console::WriteLine("Master server heartbeat failed: Invalid Token");
}
// Master server has successfully updated our server status
}
else if (status == MASTER_SERVER_STATUS_INVALID_TOKEN)
{
_status = ADVERTISE_STATUS::UNREGISTERED;
Console::WriteLine("Master server heartbeat failed: Invalid Token");
}
}
json_t* GetHeartbeatJson()
json_t GetHeartbeatJson()
{
uint32_t numPlayers = network_get_num_players();
json_t* root = json_object();
json_object_set_new(root, "token", json_string(_token.c_str()));
json_object_set_new(root, "players", json_integer(numPlayers));
json_t root = {
{ "token", _token },
{ "players", numPlayers },
};
json_t* gameInfo = json_object();
json_object_set_new(gameInfo, "mapSize", json_integer(gMapSize - 2));
json_object_set_new(gameInfo, "day", json_integer(gDateMonthTicks));
json_object_set_new(gameInfo, "month", json_integer(gDateMonthsElapsed));
json_object_set_new(gameInfo, "guests", json_integer(gNumGuestsInPark));
json_object_set_new(gameInfo, "parkValue", json_integer(gParkValue));
json_t gameInfo = {
{ "mapSize", gMapSize - 2 }, { "day", gDateMonthTicks }, { "month", gDateMonthsElapsed },
{ "guests", gNumGuestsInPark }, { "parkValue", gParkValue },
};
if (!(gParkFlags & PARK_FLAGS_NO_MONEY))
{
json_object_set_new(gameInfo, "cash", json_integer(gCash));
gameInfo["cash"] = gCash;
}
json_object_set_new(root, "gameInfo", gameInfo);
root["gameInfo"] = gameInfo;
return root;
}

View File

@@ -12,7 +12,7 @@
# include "NetworkUser.h"
# include "../core/Console.hpp"
# include "../core/Json.hpp"
# include "../core/Guard.hpp"
# include "../core/Path.hpp"
# include "../core/String.hpp"
# include "../platform/Platform2.h"
@@ -21,50 +21,43 @@
constexpr const utf8* USER_STORE_FILENAME = "users.json";
NetworkUser* NetworkUser::FromJson(json_t* json)
NetworkUser* NetworkUser::FromJson(json_t& jsonData)
{
const char* hash = json_string_value(json_object_get(json, "hash"));
const char* name = json_string_value(json_object_get(json, "name"));
const json_t* jsonGroupId = json_object_get(json, "groupId");
Guard::Assert(jsonData.is_object(), "NetworkUser::FromJson expects parameter jsonData to be object");
const std::string hash = Json::GetString(jsonData["hash"]);
const std::string name = Json::GetString(jsonData["name"]);
json_t jsonGroupId = jsonData["groupId"];
NetworkUser* user = nullptr;
if (hash != nullptr && name != nullptr)
if (!hash.empty() && !name.empty())
{
user = new NetworkUser();
user->Hash = std::string(hash);
user->Name = std::string(name);
if (!json_is_null(jsonGroupId))
user->Hash = hash;
user->Name = name;
if (jsonGroupId.is_number_integer())
{
user->GroupId = static_cast<uint8_t>(json_integer_value(jsonGroupId));
user->GroupId = Json::GetNumber<uint8_t>(jsonGroupId);
}
user->Remove = false;
return user;
}
return user;
}
json_t* NetworkUser::ToJson() const
json_t NetworkUser::ToJson() const
{
return ToJson(json_object());
}
json_t jsonData;
jsonData["hash"] = Hash;
jsonData["name"] = Name;
json_t* NetworkUser::ToJson(json_t* json) const
{
json_object_set_new(json, "hash", json_string(Hash.c_str()));
json_object_set_new(json, "name", json_string(Name.c_str()));
json_t* jsonGroupId;
json_t jsonGroupId;
if (GroupId.HasValue())
{
jsonGroupId = json_integer(GroupId.GetValue());
jsonGroupId = GroupId.GetValue();
}
else
{
jsonGroupId = json_null();
}
json_object_set_new(json, "groupId", jsonGroupId);
jsonData["groupId"] = jsonGroupId;
return json;
return jsonData;
}
NetworkUserManager::~NetworkUserManager()
@@ -92,18 +85,18 @@ void NetworkUserManager::Load()
try
{
json_t* jsonUsers = Json::ReadFromFile(path);
size_t numUsers = json_array_size(jsonUsers);
for (size_t i = 0; i < numUsers; i++)
json_t jsonUsers = Json::ReadFromFile(path);
for (auto& jsonUser : jsonUsers)
{
json_t* jsonUser = json_array_get(jsonUsers, i);
NetworkUser* networkUser = NetworkUser::FromJson(jsonUser);
if (networkUser != nullptr)
if (jsonUser.is_object())
{
_usersByHash[networkUser->Hash] = networkUser;
auto networkUser = NetworkUser::FromJson(jsonUser);
if (networkUser != nullptr)
{
_usersByHash[networkUser->Hash] = networkUser;
}
}
}
json_decref(jsonUsers);
}
catch (const std::exception& ex)
{
@@ -117,7 +110,7 @@ void NetworkUserManager::Save()
utf8 path[MAX_PATH];
GetStorePath(path, sizeof(path));
json_t* jsonUsers = nullptr;
json_t jsonUsers;
try
{
if (Platform::FileExists(path))
@@ -129,36 +122,35 @@ void NetworkUserManager::Save()
{
}
if (jsonUsers == nullptr)
{
jsonUsers = json_array();
}
// Update existing users
std::unordered_set<std::string> savedHashes;
size_t numUsers = json_array_size(jsonUsers);
for (size_t i = 0; i < numUsers; i++)
for (auto it = jsonUsers.begin(); it != jsonUsers.end();)
{
json_t* jsonUser = json_array_get(jsonUsers, i);
const char* hash = json_string_value(json_object_get(jsonUser, "hash"));
if (hash != nullptr)
json_t jsonUser = *it;
if (!jsonUser.is_object())
{
auto hashString = std::string(hash);
const NetworkUser* networkUser = GetUserByHash(hashString);
if (networkUser != nullptr)
continue;
}
std::string hashString = Json::GetString(jsonUser["hash"]);
const auto networkUser = GetUserByHash(hashString);
if (networkUser != nullptr)
{
if (networkUser->Remove)
{
if (networkUser->Remove)
{
json_array_remove(jsonUsers, i);
i--;
}
else
{
networkUser->ToJson(jsonUser);
savedHashes.insert(hashString);
}
it = jsonUsers.erase(it);
// erase advances the iterator so make sure we don't do it again
continue;
}
else
{
// replace the existing element in jsonUsers
*it = networkUser->ToJson();
savedHashes.insert(hashString);
}
}
it++;
}
// Add new users
@@ -167,13 +159,11 @@ void NetworkUserManager::Save()
const NetworkUser* networkUser = kvp.second;
if (!networkUser->Remove && savedHashes.find(networkUser->Hash) == savedHashes.end())
{
json_t* jsonUser = networkUser->ToJson();
json_array_append_new(jsonUsers, jsonUser);
jsonUsers.push_back(networkUser->ToJson());
}
}
Json::WriteToFile(path, jsonUsers, JSON_INDENT(4) | JSON_PRESERVE_ORDER);
json_decref(jsonUsers);
Json::WriteToFile(path, jsonUsers);
}
void NetworkUserManager::UnsetUsersOfGroup(uint8_t groupId)

View File

@@ -10,9 +10,9 @@
#pragma once
#include "../common.h"
#include "../core/Json.hpp"
#include "../core/Nullable.hpp"
#include <jansson.h>
#include <map>
#include <string>
@@ -24,10 +24,20 @@ public:
Nullable<uint8_t> GroupId;
bool Remove;
static NetworkUser* FromJson(json_t* json);
/**
* Creates a NetworkUser object from a JSON object
* @param jsonData Must be a JSON node of type object
* @return Pointer to a new NetworkUser object
* @note jsonData is deliberately left non-const: json_t behaviour changes when const
*/
static NetworkUser* FromJson(json_t& jsonData);
json_t* ToJson() const;
json_t* ToJson(json_t* json) const;
/**
* Serialise a NetworkUser object into a JSON object
*
* @return JSON representation of the NetworkUser object
*/
json_t ToJson() const;
};
class NetworkUserManager final

View File

@@ -15,6 +15,7 @@
# include "../PlatformEnvironment.h"
# include "../config/Config.h"
# include "../core/FileStream.hpp"
# include "../core/Guard.hpp"
# include "../core/Http.h"
# include "../core/Json.hpp"
# include "../core/Memory.hpp"
@@ -70,35 +71,42 @@ bool ServerListEntry::IsVersionValid() const
return Version.empty() || Version == network_get_version();
}
std::optional<ServerListEntry> ServerListEntry::FromJson(const json_t* server)
std::optional<ServerListEntry> ServerListEntry::FromJson(json_t& server)
{
auto port = json_object_get(server, "port");
auto name = json_object_get(server, "name");
auto description = json_object_get(server, "description");
auto requiresPassword = json_object_get(server, "requiresPassword");
auto version = json_object_get(server, "version");
auto players = json_object_get(server, "players");
auto maxPlayers = json_object_get(server, "maxPlayers");
auto ip = json_object_get(server, "ip");
auto ip4 = json_object_get(ip, "v4");
auto addressIp = json_array_get(ip4, 0);
Guard::Assert(server.is_object(), "ServerListEntry::FromJson expects parameter server to be object");
if (name == nullptr || version == nullptr)
const auto port = Json::GetNumber<int32_t>(server["port"]);
const auto name = Json::GetString(server["name"]);
const auto description = Json::GetString(server["description"]);
const auto requiresPassword = Json::GetBoolean(server["requiresPassword"]);
const auto version = Json::GetString(server["version"]);
const auto players = Json::GetNumber<uint8_t>(server["players"]);
const auto maxPlayers = Json::GetNumber<uint8_t>(server["maxPlayers"]);
std::string ip;
// if server["ip"] or server["ip"]["v4"] are values, this will throw an exception, so check first
if (server["ip"].is_object() && server["ip"]["v4"].is_array())
{
ip = Json::GetString(server["ip"]["v4"][0]);
}
if (name.empty() || version.empty())
{
log_verbose("Cowardly refusing to add server without name or version specified.");
return std::nullopt;
}
else
{
ServerListEntry entry;
entry.Address = String::StdFormat(
"%s:%d", json_string_value(addressIp), static_cast<int32_t>(json_integer_value(port)));
entry.Name = (name == nullptr ? "" : json_string_value(name));
entry.Description = (description == nullptr ? "" : json_string_value(description));
entry.Version = json_string_value(version);
entry.RequiresPassword = json_is_true(requiresPassword);
entry.Players = static_cast<uint8_t>(json_integer_value(players));
entry.MaxPlayers = static_cast<uint8_t>(json_integer_value(maxPlayers));
entry.Address = ip + ":" + std::to_string(port);
entry.Name = name;
entry.Description = description;
entry.Version = version;
entry.RequiresPassword = requiresPassword;
entry.Players = players;
entry.MaxPlayers = maxPlayers;
return entry;
}
}
@@ -263,20 +271,17 @@ std::future<std::vector<ServerListEntry>> ServerList::FetchLocalServerListAsync(
log_verbose("Received %zu bytes back from %s", recievedLen, sender.c_str());
auto jinfo = Json::FromString(std::string_view(buffer));
auto ip4 = json_array();
json_array_append_new(ip4, json_string(sender.c_str()));
auto ip = json_object();
json_object_set_new(ip, "v4", ip4);
json_object_set_new(jinfo, "ip", ip);
auto entry = ServerListEntry::FromJson(jinfo);
if (entry.has_value())
if (jinfo.is_object())
{
(*entry).Local = true;
entries.push_back(*entry);
}
jinfo["ip"] = { { "v4", { sender } } };
json_decref(jinfo);
auto entry = ServerListEntry::FromJson(jinfo);
if (entry.has_value())
{
(*entry).Local = true;
entries.push_back(*entry);
}
}
}
}
catch (const std::exception& e)
@@ -341,7 +346,7 @@ std::future<std::vector<ServerListEntry>> ServerList::FetchOnlineServerListAsync
request.method = Http::Method::GET;
request.header["Accept"] = "application/json";
Http::DoAsync(request, [p](Http::Response& response) -> void {
json_t* root{};
json_t root;
try
{
if (response.status != Http::Status::OK)
@@ -350,46 +355,46 @@ std::future<std::vector<ServerListEntry>> ServerList::FetchOnlineServerListAsync
}
root = Json::FromString(response.body);
auto jsonStatus = json_object_get(root, "status");
if (!json_is_number(jsonStatus))
if (root.is_object())
{
throw MasterServerException(STR_SERVER_LIST_INVALID_RESPONSE_JSON_NUMBER);
}
auto status = static_cast<int32_t>(json_integer_value(jsonStatus));
if (status != 200)
{
throw MasterServerException(STR_SERVER_LIST_MASTER_SERVER_FAILED);
}
auto jServers = json_object_get(root, "servers");
if (!json_is_array(jServers))
{
throw MasterServerException(STR_SERVER_LIST_INVALID_RESPONSE_JSON_ARRAY);
}
std::vector<ServerListEntry> entries;
auto count = json_array_size(jServers);
for (size_t i = 0; i < count; i++)
{
auto jServer = json_array_get(jServers, i);
if (json_is_object(jServer))
auto jsonStatus = root["status"];
if (!jsonStatus.is_number_integer())
{
auto entry = ServerListEntry::FromJson(jServer);
if (entry.has_value())
throw MasterServerException(STR_SERVER_LIST_INVALID_RESPONSE_JSON_NUMBER);
}
auto status = Json::GetNumber<int32_t>(jsonStatus);
if (status != 200)
{
throw MasterServerException(STR_SERVER_LIST_MASTER_SERVER_FAILED);
}
auto jServers = root["servers"];
if (!jServers.is_array())
{
throw MasterServerException(STR_SERVER_LIST_INVALID_RESPONSE_JSON_ARRAY);
}
std::vector<ServerListEntry> entries;
for (auto& jServer : jServers)
{
if (jServer.is_object())
{
entries.push_back(*entry);
auto entry = ServerListEntry::FromJson(jServer);
if (entry.has_value())
{
entries.push_back(*entry);
}
}
}
}
p->set_value(entries);
p->set_value(entries);
}
}
catch (...)
{
p->set_exception(std::current_exception());
}
json_decref(root);
});
return f;
# endif

View File

@@ -10,6 +10,7 @@
#pragma once
#include "../common.h"
#include "../core/Json.hpp"
#include <future>
#include <optional>
@@ -17,7 +18,6 @@
#include <string>
#include <vector>
struct json_t;
struct INetworkEndpoint;
struct ServerListEntry
@@ -35,7 +35,14 @@ struct ServerListEntry
int32_t CompareTo(const ServerListEntry& other) const;
bool IsVersionValid() const;
static std::optional<ServerListEntry> FromJson(const json_t* root);
/**
* Creates a ServerListEntry object from a JSON object
*
* @param json JSON data source - must be object type
* @return A NetworkGroup object
* @note json is deliberately left non-const: json_t behaviour changes when const
*/
static std::optional<ServerListEntry> FromJson(json_t& server);
};
class ServerList

View File

@@ -15,6 +15,7 @@
#define MAX_SERVER_DESCRIPTION_LENGTH 256
#include "../common.h"
#include "../core/Json.hpp"
#include "../localisation/StringIds.h"
#include "NetworkTypes.h"
@@ -22,7 +23,6 @@
#include <string>
#include <vector>
struct json_t;
struct GameAction;
struct Peep;
struct CoordsXYZ;
@@ -117,4 +117,4 @@ std::string network_get_version();
NetworkStats_t network_get_stats();
NetworkServerState_t network_get_server_state();
json_t* network_get_server_info_as_json();
json_t network_get_server_info_as_json();

View File

@@ -14,7 +14,6 @@
#include "../localisation/Language.h"
#include "../object/Object.h"
#include "../object/ObjectRepository.h"
#include "ObjectJsonHelpers.h"
#include "ObjectList.h"
void BannerObject::ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream)
@@ -82,20 +81,23 @@ void BannerObject::DrawPreview(rct_drawpixelinfo* dpi, int32_t width, int32_t he
gfx_draw_sprite(dpi, imageId + 1, screenCoords + ScreenCoordsXY{ -12, 8 }, 0);
}
void BannerObject::ReadJson(IReadObjectContext* context, const json_t* root)
void BannerObject::ReadJson(IReadObjectContext* context, json_t& root)
{
auto properties = json_object_get(root, "properties");
Guard::Assert(root.is_object(), "BannerObject::ReadJson expects parameter root to be object");
json_t properties = root["properties"];
_legacyType.banner.scrolling_mode = json_integer_value(json_object_get(properties, "scrollingMode"));
_legacyType.banner.price = json_integer_value(json_object_get(properties, "price"));
_legacyType.banner.flags = ObjectJsonHelpers::GetFlags<uint8_t>(
properties,
{
{ "hasPrimaryColour", BANNER_ENTRY_FLAG_HAS_PRIMARY_COLOUR },
});
if (properties.is_object())
{
_legacyType.banner.scrolling_mode = Json::GetNumber<uint8_t>(properties["scrollingMode"]);
_legacyType.banner.price = Json::GetNumber<int16_t>(properties["price"]);
_legacyType.banner.flags = Json::GetFlags<uint8_t>(
properties,
{
{ "hasPrimaryColour", BANNER_ENTRY_FLAG_HAS_PRIMARY_COLOUR },
});
SetPrimarySceneryGroup(ObjectJsonHelpers::GetString(json_object_get(properties, "sceneryGroup")));
SetPrimarySceneryGroup(Json::GetString(properties["sceneryGroup"]));
}
ObjectJsonHelpers::LoadStrings(root, GetStringTable());
ObjectJsonHelpers::LoadImages(context, root, GetImageTable());
PopulateTablesFromJson(context, root);
}

View File

@@ -30,7 +30,7 @@ public:
}
void ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) override;
void ReadJson(IReadObjectContext* context, const json_t* root) override;
void ReadJson(IReadObjectContext* context, json_t& root) override;
void Load() override;
void Unload() override;

View File

@@ -13,7 +13,6 @@
#include "../core/String.hpp"
#include "../drawing/Drawing.h"
#include "../localisation/Localisation.h"
#include "ObjectJsonHelpers.h"
void EntranceObject::ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream)
{
@@ -51,12 +50,17 @@ void EntranceObject::DrawPreview(rct_drawpixelinfo* dpi, int32_t width, int32_t
gfx_draw_sprite(dpi, imageId + 2, screenCoords + ScreenCoordsXY{ 32, 44 }, 0);
}
void EntranceObject::ReadJson(IReadObjectContext* context, const json_t* root)
void EntranceObject::ReadJson(IReadObjectContext* context, json_t& root)
{
auto properties = json_object_get(root, "properties");
_legacyType.scrolling_mode = json_integer_value(json_object_get(properties, "scrollingMode"));
_legacyType.text_height = json_integer_value(json_object_get(properties, "textHeight"));
Guard::Assert(root.is_object(), "EntranceObject::ReadJson expects parameter root to be object");
ObjectJsonHelpers::LoadStrings(root, GetStringTable());
ObjectJsonHelpers::LoadImages(context, root, GetImageTable());
json_t properties = root["properties"];
if (properties.is_object())
{
_legacyType.scrolling_mode = Json::GetNumber<uint8_t>(properties["scrollingMode"]);
_legacyType.text_height = Json::GetNumber<uint8_t>(properties["textHeight"]);
}
PopulateTablesFromJson(context, root);
}

View File

@@ -29,7 +29,7 @@ public:
}
void ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) override;
void ReadJson(IReadObjectContext* context, const json_t* root) override;
void ReadJson(IReadObjectContext* context, json_t& root) override;
void Load() override;
void Unload() override;

View File

@@ -15,7 +15,6 @@
#include "../localisation/Localisation.h"
#include "../object/Object.h"
#include "../object/ObjectRepository.h"
#include "ObjectJsonHelpers.h"
#include "ObjectList.h"
#include <unordered_map>
@@ -98,40 +97,36 @@ static uint8_t ParseDrawType(const std::string& s)
return PATH_BIT_DRAW_TYPE_LIGHTS;
}
void FootpathItemObject::ReadJson(IReadObjectContext* context, const json_t* root)
void FootpathItemObject::ReadJson(IReadObjectContext* context, json_t& root)
{
auto properties = json_object_get(root, "properties");
_legacyType.path_bit.draw_type = ParseDrawType(ObjectJsonHelpers::GetString(properties, "renderAs"));
_legacyType.path_bit.tool_id = ObjectJsonHelpers::ParseCursor(
ObjectJsonHelpers::GetString(properties, "cursor"), CURSOR_LAMPPOST_DOWN);
_legacyType.path_bit.price = json_integer_value(json_object_get(properties, "price"));
Guard::Assert(root.is_object(), "FootpathItemObject::ReadJson expects parameter root to be object");
SetPrimarySceneryGroup(ObjectJsonHelpers::GetString(json_object_get(properties, "sceneryGroup")));
json_t properties = root["properties"];
// Flags
_legacyType.path_bit.flags = ObjectJsonHelpers::GetFlags<uint16_t>(
properties,
{
{ "isBin", PATH_BIT_FLAG_IS_BIN },
{ "isBench", PATH_BIT_FLAG_IS_BENCH },
{ "isBreakable", PATH_BIT_FLAG_BREAKABLE },
{ "isLamp", PATH_BIT_FLAG_LAMP },
{ "isJumpingFountainWater", PATH_BIT_FLAG_JUMPING_FOUNTAIN_WATER },
{ "isJumpingFountainSnow", PATH_BIT_FLAG_JUMPING_FOUNTAIN_SNOW },
{ "isTelevision", PATH_BIT_FLAG_IS_QUEUE_SCREEN },
});
// HACK To avoid 'negated' properties in JSON, handle these separately until
// flags are inverted in this code base.
if (!ObjectJsonHelpers::GetBoolean(properties, "isAllowedOnQueue", false))
if (properties.is_object())
{
_legacyType.path_bit.flags |= PATH_BIT_FLAG_DONT_ALLOW_ON_QUEUE;
}
if (!ObjectJsonHelpers::GetBoolean(properties, "isAllowedOnSlope", false))
{
_legacyType.path_bit.flags |= PATH_BIT_FLAG_DONT_ALLOW_ON_SLOPE;
_legacyType.path_bit.draw_type = ParseDrawType(Json::GetString(properties["renderAs"]));
_legacyType.path_bit.tool_id = Cursor::FromString(Json::GetString(properties["cursor"]), CURSOR_LAMPPOST_DOWN);
_legacyType.path_bit.price = Json::GetNumber<int16_t>(properties["price"]);
SetPrimarySceneryGroup(Json::GetString(properties["sceneryGroup"]));
// clang-format off
_legacyType.path_bit.flags = Json::GetFlags<uint16_t>(
properties,
{
{ "isBin", PATH_BIT_FLAG_IS_BIN, Json::FlagType::Normal },
{ "isBench", PATH_BIT_FLAG_IS_BENCH, Json::FlagType::Normal },
{ "isBreakable", PATH_BIT_FLAG_BREAKABLE, Json::FlagType::Normal },
{ "isLamp", PATH_BIT_FLAG_LAMP, Json::FlagType::Normal },
{ "isJumpingFountainWater", PATH_BIT_FLAG_JUMPING_FOUNTAIN_WATER, Json::FlagType::Normal },
{ "isJumpingFountainSnow", PATH_BIT_FLAG_JUMPING_FOUNTAIN_SNOW, Json::FlagType::Normal },
{ "isAllowedOnQueue", PATH_BIT_FLAG_DONT_ALLOW_ON_QUEUE, Json::FlagType::Inverted },
{ "isAllowedOnSlope", PATH_BIT_FLAG_DONT_ALLOW_ON_SLOPE, Json::FlagType::Inverted },
{ "isTelevision", PATH_BIT_FLAG_IS_QUEUE_SCREEN, Json::FlagType::Normal },
});
// clang-format on
}
ObjectJsonHelpers::LoadStrings(root, GetStringTable());
ObjectJsonHelpers::LoadImages(context, root, GetImageTable());
PopulateTablesFromJson(context, root);
}

View File

@@ -29,7 +29,7 @@ public:
}
void ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) override;
void ReadJson(IReadObjectContext* context, const json_t* root) override;
void ReadJson(IReadObjectContext* context, json_t& root) override;
void Load() override;
void Unload() override;

View File

@@ -10,10 +10,10 @@
#include "FootpathObject.h"
#include "../core/IStream.hpp"
#include "../core/Json.hpp"
#include "../drawing/Drawing.h"
#include "../localisation/Language.h"
#include "../world/Footpath.h"
#include "ObjectJsonHelpers.h"
void FootpathObject::ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream)
{
@@ -83,21 +83,25 @@ static RailingEntrySupportType ParseSupportType(const std::string& s)
return RailingEntrySupportType::Box;
}
void FootpathObject::ReadJson(IReadObjectContext* context, const json_t* root)
void FootpathObject::ReadJson(IReadObjectContext* context, json_t& root)
{
auto properties = json_object_get(root, "properties");
_legacyType.support_type = ParseSupportType(ObjectJsonHelpers::GetString(json_object_get(properties, "supportType")));
_legacyType.scrolling_mode = json_integer_value(json_object_get(properties, "scrollingMode"));
Guard::Assert(root.is_object(), "FootpathObject::ReadJson expects parameter root to be object");
// Flags
_legacyType.flags = ObjectJsonHelpers::GetFlags<uint8_t>(
properties,
{
{ "hasSupportImages", RAILING_ENTRY_FLAG_HAS_SUPPORT_BASE_SPRITE },
{ "hasElevatedPathImages", RAILING_ENTRY_FLAG_DRAW_PATH_OVER_SUPPORTS },
{ "editorOnly", FOOTPATH_ENTRY_FLAG_SHOW_ONLY_IN_SCENARIO_EDITOR },
});
auto properties = root["properties"];
ObjectJsonHelpers::LoadStrings(root, GetStringTable());
ObjectJsonHelpers::LoadImages(context, root, GetImageTable());
if (properties.is_object())
{
_legacyType.support_type = ParseSupportType(Json::GetString(properties["supportType"]));
_legacyType.scrolling_mode = Json::GetNumber<uint8_t>(properties["scrollingMode"]);
_legacyType.flags = Json::GetFlags<uint8_t>(
properties,
{
{ "hasSupportImages", RAILING_ENTRY_FLAG_HAS_SUPPORT_BASE_SPRITE },
{ "hasElevatedPathImages", RAILING_ENTRY_FLAG_DRAW_PATH_OVER_SUPPORTS },
{ "editorOnly", FOOTPATH_ENTRY_FLAG_SHOW_ONLY_IN_SCENARIO_EDITOR },
});
}
PopulateTablesFromJson(context, root);
}

View File

@@ -47,7 +47,7 @@ public:
}
void ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) override;
void ReadJson(IReadObjectContext* context, const json_t* root) override;
void ReadJson(IReadObjectContext* context, json_t& root) override;
void Load() override;
void Unload() override;

View File

@@ -9,14 +9,285 @@
#include "ImageTable.h"
#include "../Context.h"
#include "../OpenRCT2.h"
#include "../PlatformEnvironment.h"
#include "../core/File.h"
#include "../core/FileScanner.h"
#include "../core/IStream.hpp"
#include "../core/Path.hpp"
#include "../core/String.hpp"
#include "../drawing/ImageImporter.h"
#include "../sprites.h"
#include "Object.h"
#include "ObjectFactory.h"
#include <algorithm>
#include <memory>
#include <stdexcept>
using namespace OpenRCT2;
using namespace OpenRCT2::Drawing;
struct ImageTable::RequiredImage
{
rct_g1_element g1{};
std::unique_ptr<RequiredImage> next_zoom;
bool HasData() const
{
return g1.offset != nullptr;
}
RequiredImage() = default;
RequiredImage(const RequiredImage&) = delete;
RequiredImage(const rct_g1_element& orig)
{
auto length = g1_calculate_data_size(&orig);
g1 = orig;
g1.offset = new uint8_t[length];
std::memcpy(g1.offset, orig.offset, length);
g1.flags &= ~G1_FLAG_HAS_ZOOM_SPRITE;
}
RequiredImage(uint32_t idx, std::function<const rct_g1_element*(uint32_t)> getter)
{
auto orig = getter(idx);
if (orig != nullptr)
{
auto length = g1_calculate_data_size(orig);
g1 = *orig;
g1.offset = new uint8_t[length];
std::memcpy(g1.offset, orig->offset, length);
if ((g1.flags & G1_FLAG_HAS_ZOOM_SPRITE) && g1.zoomed_offset != 0)
{
// Fetch image for next zoom level
next_zoom = std::make_unique<RequiredImage>(static_cast<uint32_t>(idx - g1.zoomed_offset), getter);
if (!next_zoom->HasData())
{
next_zoom = nullptr;
g1.flags &= ~G1_FLAG_HAS_ZOOM_SPRITE;
}
}
}
}
~RequiredImage()
{
delete[] g1.offset;
}
};
std::vector<std::unique_ptr<ImageTable::RequiredImage>> ImageTable::ParseImages(IReadObjectContext* context, std::string s)
{
std::vector<std::unique_ptr<RequiredImage>> result;
if (s.empty())
{
result.push_back(std::make_unique<RequiredImage>());
}
else if (String::StartsWith(s, "$CSG"))
{
if (is_csg_loaded())
{
auto range = ParseRange(s.substr(4));
if (!range.empty())
{
for (auto i : range)
{
result.push_back(std::make_unique<RequiredImage>(
static_cast<uint32_t>(SPR_CSG_BEGIN + i),
[](uint32_t idx) -> const rct_g1_element* { return gfx_get_g1_element(idx); }));
}
}
}
}
else if (String::StartsWith(s, "$G1"))
{
auto range = ParseRange(s.substr(3));
if (!range.empty())
{
for (auto i : range)
{
result.push_back(std::make_unique<RequiredImage>(
static_cast<uint32_t>(i), [](uint32_t idx) -> const rct_g1_element* { return gfx_get_g1_element(idx); }));
}
}
}
else if (String::StartsWith(s, "$RCT2:OBJDATA/"))
{
auto name = s.substr(14);
auto rangeStart = name.find('[');
if (rangeStart != std::string::npos)
{
auto rangeString = name.substr(rangeStart);
auto range = ParseRange(name.substr(rangeStart));
name = name.substr(0, rangeStart);
result = LoadObjectImages(context, name, range);
}
}
else
{
try
{
auto imageData = context->GetData(s);
auto image = Imaging::ReadFromBuffer(imageData, IMAGE_FORMAT::PNG_32);
ImageImporter importer;
auto importResult = importer.Import(image, 0, 0, ImageImporter::IMPORT_FLAGS::RLE);
result.push_back(std::make_unique<RequiredImage>(importResult.Element));
}
catch (const std::exception& e)
{
auto msg = String::StdFormat("Unable to load image '%s': %s", s.c_str(), e.what());
context->LogWarning(OBJECT_ERROR_BAD_IMAGE_TABLE, msg.c_str());
result.push_back(std::make_unique<RequiredImage>());
}
}
return result;
}
std::vector<std::unique_ptr<ImageTable::RequiredImage>> ImageTable::ParseImages(IReadObjectContext* context, json_t& el)
{
Guard::Assert(el.is_object(), "ImageTable::ParseImages expects parameter el to be object");
auto path = Json::GetString(el["path"]);
auto x = Json::GetNumber<int16_t>(el["x"]);
auto y = Json::GetNumber<int16_t>(el["y"]);
auto raw = Json::GetString(el["format"]) == "raw";
std::vector<std::unique_ptr<RequiredImage>> result;
try
{
auto flags = ImageImporter::IMPORT_FLAGS::NONE;
if (!raw)
{
flags = static_cast<ImageImporter::IMPORT_FLAGS>(flags | ImageImporter::IMPORT_FLAGS::RLE);
}
auto imageData = context->GetData(path);
auto image = Imaging::ReadFromBuffer(imageData, IMAGE_FORMAT::PNG_32);
ImageImporter importer;
auto importResult = importer.Import(image, 0, 0, flags);
auto g1Element = importResult.Element;
g1Element.x_offset = x;
g1Element.y_offset = y;
result.push_back(std::make_unique<RequiredImage>(g1Element));
}
catch (const std::exception& e)
{
auto msg = String::StdFormat("Unable to load image '%s': %s", path.c_str(), e.what());
context->LogWarning(OBJECT_ERROR_BAD_IMAGE_TABLE, msg.c_str());
result.push_back(std::make_unique<RequiredImage>());
}
return result;
}
std::vector<std::unique_ptr<ImageTable::RequiredImage>> ImageTable::LoadObjectImages(
IReadObjectContext* context, const std::string& name, const std::vector<int32_t>& range)
{
std::vector<std::unique_ptr<RequiredImage>> result;
auto objectPath = FindLegacyObject(name);
auto obj = ObjectFactory::CreateObjectFromLegacyFile(context->GetObjectRepository(), objectPath.c_str());
if (obj != nullptr)
{
auto& imgTable = static_cast<const Object*>(obj)->GetImageTable();
auto numImages = static_cast<int32_t>(imgTable.GetCount());
auto images = imgTable.GetImages();
size_t placeHoldersAdded = 0;
for (auto i : range)
{
if (i >= 0 && i < numImages)
{
result.push_back(std::make_unique<RequiredImage>(
static_cast<uint32_t>(i), [images](uint32_t idx) -> const rct_g1_element* { return &images[idx]; }));
}
else
{
result.push_back(std::make_unique<RequiredImage>());
placeHoldersAdded++;
}
}
delete obj;
// Log place holder information
if (placeHoldersAdded > 0)
{
std::string msg = "Adding " + std::to_string(placeHoldersAdded) + " placeholders";
context->LogWarning(OBJECT_ERROR_INVALID_PROPERTY, msg.c_str());
}
}
else
{
std::string msg = "Unable to open '" + objectPath + "'";
context->LogWarning(OBJECT_ERROR_INVALID_PROPERTY, msg.c_str());
for (size_t i = 0; i < range.size(); i++)
{
result.push_back(std::make_unique<RequiredImage>());
}
}
return result;
}
std::vector<int32_t> ImageTable::ParseRange(std::string s)
{
// Currently only supports [###] or [###..###]
std::vector<int32_t> result = {};
if (s.length() >= 3 && s[0] == '[' && s[s.length() - 1] == ']')
{
s = s.substr(1, s.length() - 2);
auto parts = String::Split(s, "..");
if (parts.size() == 1)
{
result.push_back(std::stoi(parts[0]));
}
else
{
auto left = std::stoi(parts[0]);
auto right = std::stoi(parts[1]);
if (left <= right)
{
for (auto i = left; i <= right; i++)
{
result.push_back(i);
}
}
else
{
for (auto i = right; i >= left; i--)
{
result.push_back(i);
}
}
}
}
return result;
}
std::string ImageTable::FindLegacyObject(const std::string& name)
{
const auto env = GetContext()->GetPlatformEnvironment();
auto objectsPath = env->GetDirectoryPath(DIRBASE::RCT2, DIRID::OBJECT);
auto objectPath = Path::Combine(objectsPath, name);
if (!File::Exists(objectPath))
{
// Search recursively for any file with the target name (case insensitive)
auto filter = Path::Combine(objectsPath, "*.dat");
auto scanner = std::unique_ptr<IFileScanner>(Path::ScanDirectory(filter, true));
while (scanner->Next())
{
auto currentName = Path::GetFileName(scanner->GetPathRelative());
if (String::Equals(currentName, name, true))
{
objectPath = scanner->GetPath();
break;
}
}
}
return objectPath;
}
ImageTable::~ImageTable()
{
if (_data == nullptr)
@@ -97,6 +368,70 @@ void ImageTable::Read(IReadObjectContext* context, OpenRCT2::IStream* stream)
}
}
void ImageTable::ReadJson(IReadObjectContext* context, json_t& root)
{
Guard::Assert(root.is_object(), "ImageTable::ReadJson expects parameter root to be object");
if (context->ShouldLoadImages())
{
// First gather all the required images from inspecting the JSON
std::vector<std::unique_ptr<RequiredImage>> allImages;
auto jsonImages = root["images"];
for (auto& jsonImage : jsonImages)
{
if (jsonImage.is_string())
{
auto strImage = jsonImage.get<std::string>();
auto images = ParseImages(context, strImage);
allImages.insert(
allImages.end(), std::make_move_iterator(images.begin()), std::make_move_iterator(images.end()));
}
else if (jsonImage.is_object())
{
auto images = ParseImages(context, jsonImage);
allImages.insert(
allImages.end(), std::make_move_iterator(images.begin()), std::make_move_iterator(images.end()));
}
}
// Now add all the images to the image table
auto imagesStartIndex = GetCount();
for (const auto& img : allImages)
{
const auto& g1 = img->g1;
AddImage(&g1);
}
// Add all the zoom images at the very end of the image table.
// This way it should not affect the offsets used within the object logic.
for (size_t j = 0; j < allImages.size(); j++)
{
const auto tableIndex = imagesStartIndex + j;
const auto* img = allImages[j].get();
if (img->next_zoom != nullptr)
{
img = img->next_zoom.get();
// Set old image zoom offset to zoom image which we are about to add
auto g1a = const_cast<rct_g1_element*>(&GetImages()[tableIndex]);
g1a->zoomed_offset = static_cast<int32_t>(tableIndex) - static_cast<int32_t>(GetCount());
while (img != nullptr)
{
auto g1b = img->g1;
if (img->next_zoom != nullptr)
{
g1b.zoomed_offset = -1;
}
AddImage(&g1b);
img = img->next_zoom.get();
}
}
}
}
}
void ImageTable::AddImage(const rct_g1_element* g1)
{
rct_g1_element newg1 = *g1;

View File

@@ -10,6 +10,7 @@
#pragma once
#include "../common.h"
#include "../core/Json.hpp"
#include "../drawing/Drawing.h"
#include <memory>
@@ -27,6 +28,20 @@ private:
std::unique_ptr<uint8_t[]> _data;
std::vector<rct_g1_element> _entries;
/**
* Container for a G1 image, additional information and RAII. Used by ReadJson
*/
struct RequiredImage;
static std::vector<std::unique_ptr<ImageTable::RequiredImage>> ParseImages(IReadObjectContext* context, std::string s);
/**
* @note root is deliberately left non-const: json_t behaviour changes when const
*/
static std::vector<std::unique_ptr<ImageTable::RequiredImage>> ParseImages(IReadObjectContext* context, json_t& el);
static std::vector<std::unique_ptr<ImageTable::RequiredImage>> LoadObjectImages(
IReadObjectContext* context, const std::string& name, const std::vector<int32_t>& range);
static std::vector<int32_t> ParseRange(std::string s);
static std::string FindLegacyObject(const std::string& name);
public:
ImageTable() = default;
ImageTable(const ImageTable&) = delete;
@@ -34,6 +49,10 @@ public:
~ImageTable();
void Read(IReadObjectContext* context, OpenRCT2::IStream* stream);
/**
* @note root is deliberately left non-const: json_t behaviour changes when const
*/
void ReadJson(IReadObjectContext* context, json_t& root);
const rct_g1_element* GetImages() const
{
return _entries.data();

View File

@@ -12,13 +12,13 @@
#include "LargeSceneryObject.h"
#include "../core/IStream.hpp"
#include "../core/Json.hpp"
#include "../core/Memory.hpp"
#include "../drawing/Drawing.h"
#include "../interface/Cursors.h"
#include "../localisation/Language.h"
#include "../world/Banner.h"
#include "../world/Location.hpp"
#include "ObjectJsonHelpers.h"
#include <algorithm>
#include <iterator>
@@ -121,84 +121,83 @@ std::vector<rct_large_scenery_tile> LargeSceneryObject::ReadTiles(OpenRCT2::IStr
return tiles;
}
void LargeSceneryObject::ReadJson(IReadObjectContext* context, const json_t* root)
void LargeSceneryObject::ReadJson(IReadObjectContext* context, json_t& root)
{
auto properties = json_object_get(root, "properties");
Guard::Assert(root.is_object(), "LargeSceneryObject::ReadJson expects parameter root to be object");
_legacyType.large_scenery.tool_id = ObjectJsonHelpers::ParseCursor(
ObjectJsonHelpers::GetString(properties, "cursor"), CURSOR_STATUE_DOWN);
_legacyType.large_scenery.price = json_integer_value(json_object_get(properties, "price"));
_legacyType.large_scenery.removal_price = json_integer_value(json_object_get(properties, "removalPrice"));
auto properties = root["properties"];
auto jScrollingMode = json_object_get(properties, "scrollingMode");
_legacyType.large_scenery.scrolling_mode = jScrollingMode != nullptr ? json_integer_value(jScrollingMode)
: SCROLLING_MODE_NONE;
if (properties.is_object())
{
_legacyType.large_scenery.tool_id = Cursor::FromString(Json::GetString(properties["cursor"]), CURSOR_STATUE_DOWN);
// Flags
_legacyType.large_scenery.flags = ObjectJsonHelpers::GetFlags<uint8_t>(
properties,
_legacyType.large_scenery.price = Json::GetNumber<int16_t>(properties["price"]);
_legacyType.large_scenery.removal_price = Json::GetNumber<int16_t>(properties["removalPrice"]);
_legacyType.large_scenery.scrolling_mode = Json::GetNumber<uint8_t>(properties["scrollingMode"], SCROLLING_MODE_NONE);
_legacyType.large_scenery.flags = Json::GetFlags<uint8_t>(
properties,
{
{ "hasPrimaryColour", LARGE_SCENERY_FLAG_HAS_PRIMARY_COLOUR },
{ "hasSecondaryColour", LARGE_SCENERY_FLAG_HAS_SECONDARY_COLOUR },
{ "isAnimated", LARGE_SCENERY_FLAG_ANIMATED },
{ "isPhotogenic", LARGE_SCENERY_FLAG_PHOTOGENIC },
});
// Tiles
auto jTiles = properties["tiles"];
if (jTiles.is_array())
{
{ "hasPrimaryColour", LARGE_SCENERY_FLAG_HAS_PRIMARY_COLOUR },
{ "hasSecondaryColour", LARGE_SCENERY_FLAG_HAS_SECONDARY_COLOUR },
{ "isAnimated", LARGE_SCENERY_FLAG_ANIMATED },
{ "isPhotogenic", LARGE_SCENERY_FLAG_PHOTOGENIC },
});
_tiles = ReadJsonTiles(jTiles);
}
// Tiles
auto jTiles = json_object_get(properties, "tiles");
if (jTiles != nullptr)
{
_tiles = ReadJsonTiles(jTiles);
// Read text
auto j3dFont = properties["3dFont"];
if (j3dFont.is_object())
{
_3dFont = ReadJson3dFont(j3dFont);
_legacyType.large_scenery.flags |= LARGE_SCENERY_FLAG_3D_TEXT;
}
SetPrimarySceneryGroup(Json::GetString(properties["sceneryGroup"]));
}
// Read text
auto j3dFont = json_object_get(properties, "3dFont");
if (j3dFont != nullptr)
{
_3dFont = ReadJson3dFont(j3dFont);
_legacyType.large_scenery.flags |= LARGE_SCENERY_FLAG_3D_TEXT;
}
SetPrimarySceneryGroup(ObjectJsonHelpers::GetString(json_object_get(properties, "sceneryGroup")));
ObjectJsonHelpers::LoadStrings(root, GetStringTable());
ObjectJsonHelpers::LoadImages(context, root, GetImageTable());
PopulateTablesFromJson(context, root);
}
std::vector<rct_large_scenery_tile> LargeSceneryObject::ReadJsonTiles(const json_t* jTiles)
std::vector<rct_large_scenery_tile> LargeSceneryObject::ReadJsonTiles(json_t& jTiles)
{
std::vector<rct_large_scenery_tile> tiles;
size_t index;
const json_t* jTile;
json_array_foreach(jTiles, index, jTile)
for (auto& jTile : jTiles)
{
rct_large_scenery_tile tile = {};
tile.x_offset = json_integer_value(json_object_get(jTile, "x"));
tile.y_offset = json_integer_value(json_object_get(jTile, "y"));
tile.z_offset = json_integer_value(json_object_get(jTile, "z"));
tile.z_clearance = json_integer_value(json_object_get(jTile, "clearance"));
if (!ObjectJsonHelpers::GetBoolean(jTile, "hasSupports"))
if (jTile.is_object())
{
tile.flags |= LARGE_SCENERY_TILE_FLAG_NO_SUPPORTS;
}
if (ObjectJsonHelpers::GetBoolean(jTile, "allowSupportsAbove"))
{
tile.flags |= LARGE_SCENERY_TILE_FLAG_ALLOW_SUPPORTS_ABOVE;
}
rct_large_scenery_tile tile = {};
tile.x_offset = Json::GetNumber<int16_t>(jTile["x"]);
tile.y_offset = Json::GetNumber<int16_t>(jTile["y"]);
tile.z_offset = Json::GetNumber<int16_t>(jTile["z"]);
tile.z_clearance = Json::GetNumber<int8_t>(jTile["clearance"]);
// All corners are by default occupied
auto jCorners = json_object_get(jTile, "corners");
auto corners = 0xF;
if (jCorners != nullptr)
{
corners = json_integer_value(jCorners);
// clang-format off
tile.flags = Json::GetFlags<uint16_t>(
jTile,
{
{"hasSupports", LARGE_SCENERY_TILE_FLAG_NO_SUPPORTS, Json::FlagType::Inverted},
{"allowSupportsAbove", LARGE_SCENERY_TILE_FLAG_ALLOW_SUPPORTS_ABOVE, Json::FlagType::Normal}
});
// clang-format on
// All corners are by default occupied
uint16_t corners = Json::GetNumber<uint16_t>(jTile["corners"], 0xF);
tile.flags |= (corners & 0xFF) << 12;
auto walls = Json::GetNumber<int16_t>(jTile["walls"]);
tile.flags |= (walls & 0xFF) << 8;
tiles.push_back(tile);
}
tile.flags |= (corners & 0xFF) << 12;
auto walls = json_integer_value(json_object_get(jTile, "walls"));
tile.flags |= (walls & 0xFF) << 8;
tiles.push_back(tile);
}
// HACK Add end of tiles marker
@@ -208,29 +207,32 @@ std::vector<rct_large_scenery_tile> LargeSceneryObject::ReadJsonTiles(const json
return tiles;
}
std::unique_ptr<rct_large_scenery_text> LargeSceneryObject::ReadJson3dFont(const json_t* j3dFont)
std::unique_ptr<rct_large_scenery_text> LargeSceneryObject::ReadJson3dFont(json_t& j3dFont)
{
Guard::Assert(j3dFont.is_object(), "LargeSceneryObject::ReadJson3dFont expects parameter j3dFont to be object");
auto font = std::make_unique<rct_large_scenery_text>();
auto jOffsets = json_object_get(j3dFont, "offsets");
if (jOffsets != nullptr)
auto jOffsets = j3dFont["offsets"];
if (jOffsets.is_array())
{
auto offsets = ReadJsonOffsets(jOffsets);
auto numOffsets = std::min(std::size(font->offset), offsets.size());
std::copy_n(offsets.data(), numOffsets, font->offset);
}
font->max_width = json_integer_value(json_object_get(j3dFont, "maxWidth"));
font->num_images = json_integer_value(json_object_get(j3dFont, "numImages"));
font->flags = ObjectJsonHelpers::GetFlags<uint8_t>(
font->max_width = Json::GetNumber<uint16_t>(j3dFont["maxWidth"]);
font->num_images = Json::GetNumber<uint8_t>(j3dFont["numImages"]);
font->flags = Json::GetFlags<uint8_t>(
j3dFont,
{
{ "isVertical", LARGE_SCENERY_TEXT_FLAG_VERTICAL },
{ "isTwoLine", LARGE_SCENERY_TEXT_FLAG_TWO_LINE },
});
auto jGlyphs = json_object_get(j3dFont, "glyphs");
if (jGlyphs != nullptr)
auto jGlyphs = j3dFont["glyphs"];
if (jGlyphs.is_array())
{
auto glyphs = ReadJsonGlyphs(jGlyphs);
auto numGlyphs = std::min(std::size(font->glyphs), glyphs.size());
@@ -240,33 +242,35 @@ std::unique_ptr<rct_large_scenery_text> LargeSceneryObject::ReadJson3dFont(const
return font;
}
std::vector<LocationXY16> LargeSceneryObject::ReadJsonOffsets(const json_t* jOffsets)
std::vector<LocationXY16> LargeSceneryObject::ReadJsonOffsets(json_t& jOffsets)
{
std::vector<LocationXY16> offsets;
size_t index;
const json_t* jOffset;
json_array_foreach(jOffsets, index, jOffset)
for (auto& jOffset : jOffsets)
{
LocationXY16 offset = {};
offset.x = json_integer_value(json_object_get(jOffset, "x"));
offset.y = json_integer_value(json_object_get(jOffset, "y"));
offsets.push_back(offset);
if (jOffset.is_object())
{
LocationXY16 offset = {};
offset.x = Json::GetNumber<int16_t>(jOffset["x"]);
offset.y = Json::GetNumber<int16_t>(jOffset["y"]);
offsets.push_back(offset);
}
}
return offsets;
}
std::vector<rct_large_scenery_text_glyph> LargeSceneryObject::ReadJsonGlyphs(const json_t* jGlpyhs)
std::vector<rct_large_scenery_text_glyph> LargeSceneryObject::ReadJsonGlyphs(json_t& jGlyphs)
{
std::vector<rct_large_scenery_text_glyph> glyphs;
size_t index;
const json_t* jGlyph;
json_array_foreach(jGlpyhs, index, jGlyph)
for (auto& jGlyph : jGlyphs)
{
rct_large_scenery_text_glyph glyph = {};
glyph.image_offset = json_integer_value(json_object_get(jGlyph, "image"));
glyph.width = json_integer_value(json_object_get(jGlyph, "width"));
glyph.height = json_integer_value(json_object_get(jGlyph, "height"));
glyphs.push_back(glyph);
if (jGlyph.is_object())
{
rct_large_scenery_text_glyph glyph = {};
glyph.image_offset = Json::GetNumber<uint8_t>(jGlyph["image"]);
glyph.width = Json::GetNumber<uint8_t>(jGlyph["width"]);
glyph.height = Json::GetNumber<uint8_t>(jGlyph["height"]);
glyphs.push_back(glyph);
}
}
return glyphs;
}

View File

@@ -35,7 +35,7 @@ public:
}
void ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) override;
void ReadJson(IReadObjectContext* context, const json_t* root) override;
void ReadJson(IReadObjectContext* context, json_t& root) override;
void Load() override;
void Unload() override;
@@ -43,8 +43,8 @@ public:
private:
static std::vector<rct_large_scenery_tile> ReadTiles(OpenRCT2::IStream* stream);
static std::vector<rct_large_scenery_tile> ReadJsonTiles(const json_t* jTiles);
static std::unique_ptr<rct_large_scenery_text> ReadJson3dFont(const json_t* j3dFont);
static std::vector<LocationXY16> ReadJsonOffsets(const json_t* jOffsets);
static std::vector<rct_large_scenery_text_glyph> ReadJsonGlyphs(const json_t* jGlpyhs);
static std::vector<rct_large_scenery_tile> ReadJsonTiles(json_t& jTiles);
static std::unique_ptr<rct_large_scenery_text> ReadJson3dFont(json_t& j3dFont);
static std::vector<LocationXY16> ReadJsonOffsets(json_t& jOffsets);
static std::vector<rct_large_scenery_text_glyph> ReadJsonGlyphs(json_t& jGlyphs);
};

View File

@@ -37,6 +37,21 @@ void Object::ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream)
throw std::runtime_error("Not supported.");
}
void Object::PopulateTablesFromJson(IReadObjectContext* context, json_t& root)
{
_stringTable.ReadJson(root);
_imageTable.ReadJson(context, root);
}
rct_object_entry Object::ParseObjectEntry(const std::string& s)
{
rct_object_entry entry = {};
std::fill_n(entry.name, sizeof(entry.name), ' ');
auto copyLen = std::min<size_t>(8, s.size());
std::copy_n(s.c_str(), copyLen, entry.name);
return entry;
}
std::string Object::GetOverrideString(uint8_t index) const
{
auto legacyIdentifier = GetLegacyIdentifier();

View File

@@ -10,6 +10,7 @@
#pragma once
#include "../common.h"
#include "../core/Json.hpp"
#include "ImageTable.h"
#include "StringTable.h"
@@ -141,7 +142,6 @@ namespace OpenRCT2
}
struct ObjectRepositoryItem;
struct rct_drawpixelinfo;
struct json_t;
struct IReadObjectContext
{
@@ -186,6 +186,16 @@ protected:
return _imageTable;
}
/**
* Populates the image and string tables from a JSON object
* @param context
* @param root JSON node of type object containing image and string info
* @note root is deliberately left non-const: json_t behaviour changes when const
*/
void PopulateTablesFromJson(IReadObjectContext* context, json_t& root);
static rct_object_entry ParseObjectEntry(const std::string& s);
std::string GetOverrideString(uint8_t index) const;
std::string GetString(ObjectStringID index) const;
std::string GetString(int32_t language, ObjectStringID index) const;
@@ -224,7 +234,10 @@ public:
}
virtual void* GetLegacyData();
virtual void ReadJson(IReadObjectContext* /*context*/, const json_t* /*root*/)
/**
* @note root is deliberately left non-const: json_t behaviour changes when const
*/
virtual void ReadJson(IReadObjectContext* /*context*/, json_t& /*root*/)
{
}
virtual void ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream);

View File

@@ -160,8 +160,12 @@ public:
namespace ObjectFactory
{
/**
* @param jRoot Must be JSON node of type object
* @note jRoot is deliberately left non-const: json_t behaviour changes when const
*/
static Object* CreateObjectFromJson(
IObjectRepository& objectRepository, const json_t* jRoot, const IFileDataRetriever* fileRetriever);
IObjectRepository& objectRepository, json_t& jRoot, const IFileDataRetriever* fileRetriever);
static uint8_t ParseSourceGame(const std::string& s)
{
@@ -363,16 +367,16 @@ namespace ObjectFactory
throw std::runtime_error("Unable to open object.json.");
}
json_error_t jsonLoadError;
auto jRoot = json_loadb(reinterpret_cast<const char*>(jsonBytes.data()), jsonBytes.size(), 0, &jsonLoadError);
if (jRoot == nullptr)
json_t jRoot = Json::FromVector(jsonBytes);
Object* obj = nullptr;
if (jRoot.is_object())
{
throw JsonException(&jsonLoadError);
auto fileDataRetriever = ZipDataRetriever(*archive);
obj = CreateObjectFromJson(objectRepository, jRoot, &fileDataRetriever);
}
auto fileDataRetriever = ZipDataRetriever(*archive);
Object* obj = CreateObjectFromJson(objectRepository, jRoot, &fileDataRetriever);
json_decref(jRoot);
return obj;
}
catch (const std::exception& e)
@@ -392,96 +396,78 @@ namespace ObjectFactory
Object* result = nullptr;
try
{
auto jRoot = Json::ReadFromFile(path.c_str());
json_t jRoot = Json::ReadFromFile(path.c_str());
auto fileDataRetriever = FileSystemDataRetriever(Path::GetDirectory(path));
result = CreateObjectFromJson(objectRepository, jRoot, &fileDataRetriever);
json_decref(jRoot);
}
catch (const std::runtime_error& err)
{
Console::Error::WriteLine("Unable to open or read '%s': %s", path.c_str(), err.what());
delete result;
result = nullptr;
}
return result;
}
Object* CreateObjectFromJson(
IObjectRepository& objectRepository, const json_t* jRoot, const IFileDataRetriever* fileRetriever)
Object* CreateObjectFromJson(IObjectRepository& objectRepository, json_t& jRoot, const IFileDataRetriever* fileRetriever)
{
Guard::Assert(jRoot.is_object(), "ObjectFactory::CreateObjectFromJson expects parameter jRoot to be object");
log_verbose("CreateObjectFromJson(...)");
Object* result = nullptr;
auto jObjectType = json_object_get(jRoot, "objectType");
if (json_is_string(jObjectType))
auto objectType = ParseObjectType(Json::GetString(jRoot["objectType"]));
if (objectType != 0xFF)
{
auto objectType = ParseObjectType(json_string_value(jObjectType));
if (objectType != 0xFF)
auto id = Json::GetString(jRoot["id"]);
rct_object_entry entry = {};
auto originalId = Json::GetString(jRoot["originalId"]);
auto originalName = originalId;
if (originalId.length() == 8 + 1 + 8 + 1 + 8)
{
auto id = json_string_value(json_object_get(jRoot, "id"));
entry.flags = std::stoul(originalId.substr(0, 8), nullptr, 16);
originalName = originalId.substr(9, 8);
entry.checksum = std::stoul(originalId.substr(18, 8), nullptr, 16);
}
auto minLength = std::min<size_t>(8, originalName.length());
std::memcpy(entry.name, originalName.c_str(), minLength);
rct_object_entry entry = {};
auto originalId = String::ToStd(json_string_value(json_object_get(jRoot, "originalId")));
auto originalName = originalId;
if (originalId.length() == 8 + 1 + 8 + 1 + 8)
{
entry.flags = std::stoul(originalId.substr(0, 8), nullptr, 16);
originalName = originalId.substr(9, 8);
entry.checksum = std::stoul(originalId.substr(18, 8), nullptr, 16);
}
auto minLength = std::min<size_t>(8, originalName.length());
std::memcpy(entry.name, originalName.c_str(), minLength);
result = CreateObject(entry);
result->SetIdentifier(id);
result->MarkAsJsonObject();
auto readContext = ReadObjectContext(objectRepository, id, !gOpenRCT2NoGraphics, fileRetriever);
result->ReadJson(&readContext, jRoot);
if (readContext.WasError())
{
throw std::runtime_error("Object has errors");
}
result = CreateObject(entry);
result->SetIdentifier(id);
result->MarkAsJsonObject();
auto readContext = ReadObjectContext(objectRepository, id, !gOpenRCT2NoGraphics, fileRetriever);
result->ReadJson(&readContext, jRoot);
if (readContext.WasError())
auto jAuthors = jRoot["authors"];
std::vector<std::string> authorVector;
for (const auto& jAuthor : jAuthors)
{
if (jAuthor.is_string())
{
throw std::runtime_error("Object has errors");
authorVector.emplace_back(Json::GetString(jAuthor));
}
}
result->SetAuthors(std::move(authorVector));
auto authors = json_object_get(jRoot, "authors");
if (json_is_array(authors))
auto sourceGames = jRoot["sourceGame"];
if (sourceGames.is_array() || sourceGames.is_string())
{
std::vector<uint8_t> sourceGameVector;
for (const auto& jSourceGame : sourceGames)
{
std::vector<std::string> authorVector;
for (size_t j = 0; j < json_array_size(authors); j++)
{
json_t* tryString = json_array_get(authors, j);
if (json_is_string(tryString))
{
authorVector.emplace_back(json_string_value(tryString));
}
}
result->SetAuthors(std::move(authorVector));
}
else if (json_is_string(authors))
{
result->SetAuthors({ json_string_value(authors) });
}
auto sourceGames = json_object_get(jRoot, "sourceGame");
if (json_is_array(sourceGames))
{
std::vector<uint8_t> sourceGameVector;
for (size_t j = 0; j < json_array_size(sourceGames); j++)
{
sourceGameVector.push_back(ParseSourceGame(json_string_value(json_array_get(sourceGames, j))));
}
result->SetSourceGames(sourceGameVector);
}
else if (json_is_string(sourceGames))
{
auto sourceGame = json_string_value(sourceGames);
result->SetSourceGames({ ParseSourceGame(sourceGame) });
}
else
{
log_error("Object %s has an incorrect sourceGame parameter.", id);
result->SetSourceGames({ OBJECT_SOURCE_CUSTOM });
sourceGameVector.push_back(ParseSourceGame(Json::GetString(jSourceGame)));
}
result->SetSourceGames(sourceGameVector);
}
else
{
log_error("Object %s has an incorrect sourceGame parameter.", id.c_str());
result->SetSourceGames({ OBJECT_SOURCE_CUSTOM });
}
}
return result;

View File

@@ -1,563 +0,0 @@
/*****************************************************************************
* Copyright (c) 2014-2020 OpenRCT2 developers
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
*****************************************************************************/
#pragma warning(disable : 4706) // assignment within conditional expression
#include "ObjectJsonHelpers.h"
#include "../Context.h"
#include "../PlatformEnvironment.h"
#include "../core/File.h"
#include "../core/FileScanner.h"
#include "../core/Memory.hpp"
#include "../core/Path.hpp"
#include "../core/String.hpp"
#include "../drawing/ImageImporter.h"
#include "../interface/Cursors.h"
#include "../localisation/Language.h"
#include "../sprites.h"
#include "Object.h"
#include "ObjectFactory.h"
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <unordered_map>
using namespace OpenRCT2;
using namespace OpenRCT2::Drawing;
namespace ObjectJsonHelpers
{
/**
* Container for a G1 image, additional information and RAII.
*/
struct RequiredImage
{
rct_g1_element g1{};
std::unique_ptr<RequiredImage> next_zoom;
bool HasData() const
{
return g1.offset != nullptr;
}
RequiredImage() = default;
RequiredImage(const RequiredImage&) = delete;
RequiredImage(const rct_g1_element& orig)
{
auto length = g1_calculate_data_size(&orig);
g1 = orig;
g1.offset = new uint8_t[length];
std::memcpy(g1.offset, orig.offset, length);
g1.flags &= ~G1_FLAG_HAS_ZOOM_SPRITE;
}
RequiredImage(uint32_t idx, std::function<const rct_g1_element*(uint32_t)> getter)
{
auto orig = getter(idx);
if (orig != nullptr)
{
auto length = g1_calculate_data_size(orig);
g1 = *orig;
g1.offset = new uint8_t[length];
std::memcpy(g1.offset, orig->offset, length);
if ((g1.flags & G1_FLAG_HAS_ZOOM_SPRITE) && g1.zoomed_offset != 0)
{
// Fetch image for next zoom level
next_zoom = std::make_unique<RequiredImage>(static_cast<uint32_t>(idx - g1.zoomed_offset), getter);
if (!next_zoom->HasData())
{
next_zoom = nullptr;
g1.flags &= ~G1_FLAG_HAS_ZOOM_SPRITE;
}
}
}
}
~RequiredImage()
{
delete[] g1.offset;
}
};
bool GetBoolean(const json_t* obj, const std::string& name, bool defaultValue)
{
auto value = json_object_get(obj, name.c_str());
return json_is_boolean(value) ? json_boolean_value(value) : defaultValue;
}
std::string GetString(const json_t* value)
{
return json_is_string(value) ? std::string(json_string_value(value)) : std::string();
}
std::string GetString(const json_t* obj, const std::string& name, const std::string& defaultValue)
{
auto value = json_object_get(obj, name.c_str());
return json_is_string(value) ? json_string_value(value) : defaultValue;
}
int32_t GetInteger(const json_t* obj, const std::string& name, const int32_t& defaultValue)
{
auto value = json_object_get(obj, name.c_str());
if (json_is_integer(value))
{
int64_t val = json_integer_value(value);
if (val >= std::numeric_limits<int32_t>::min() && val <= std::numeric_limits<int32_t>::max())
{
return static_cast<int32_t>(val);
}
}
return defaultValue;
}
float GetFloat(const json_t* obj, const std::string& name, const float& defaultValue)
{
auto value = json_object_get(obj, name.c_str());
return json_is_number(value) ? json_number_value(value) : defaultValue;
}
std::vector<std::string> GetJsonStringArray(const json_t* arr)
{
std::vector<std::string> result;
if (json_is_array(arr))
{
auto count = json_array_size(arr);
result.reserve(count);
for (size_t i = 0; i < count; i++)
{
auto element = json_string_value(json_array_get(arr, i));
result.push_back(element);
}
}
else if (json_is_string(arr))
{
result.push_back(json_string_value(arr));
}
return result;
}
std::vector<int32_t> GetJsonIntegerArray(const json_t* arr)
{
std::vector<int32_t> result;
if (json_is_array(arr))
{
auto count = json_array_size(arr);
result.reserve(count);
for (size_t i = 0; i < count; i++)
{
auto element = json_integer_value(json_array_get(arr, i));
result.push_back(element);
}
}
else if (json_is_integer(arr))
{
result.push_back(json_integer_value(arr));
}
return result;
}
colour_t ParseColour(const std::string_view& s, colour_t defaultValue)
{
static const std::unordered_map<std::string_view, colour_t> LookupTable{
{ "black", COLOUR_BLACK },
{ "grey", COLOUR_GREY },
{ "white", COLOUR_WHITE },
{ "dark_purple", COLOUR_DARK_PURPLE },
{ "light_purple", COLOUR_LIGHT_PURPLE },
{ "bright_purple", COLOUR_BRIGHT_PURPLE },
{ "dark_blue", COLOUR_DARK_BLUE },
{ "light_blue", COLOUR_LIGHT_BLUE },
{ "icy_blue", COLOUR_ICY_BLUE },
{ "teal", COLOUR_TEAL },
{ "aquamarine", COLOUR_AQUAMARINE },
{ "saturated_green", COLOUR_SATURATED_GREEN },
{ "dark_green", COLOUR_DARK_GREEN },
{ "moss_green", COLOUR_MOSS_GREEN },
{ "bright_green", COLOUR_BRIGHT_GREEN },
{ "olive_green", COLOUR_OLIVE_GREEN },
{ "dark_olive_green", COLOUR_DARK_OLIVE_GREEN },
{ "bright_yellow", COLOUR_BRIGHT_YELLOW },
{ "yellow", COLOUR_YELLOW },
{ "dark_yellow", COLOUR_DARK_YELLOW },
{ "light_orange", COLOUR_LIGHT_ORANGE },
{ "dark_orange", COLOUR_DARK_ORANGE },
{ "light_brown", COLOUR_LIGHT_BROWN },
{ "saturated_brown", COLOUR_SATURATED_BROWN },
{ "dark_brown", COLOUR_DARK_BROWN },
{ "salmon_pink", COLOUR_SALMON_PINK },
{ "bordeaux_red", COLOUR_BORDEAUX_RED },
{ "saturated_red", COLOUR_SATURATED_RED },
{ "bright_red", COLOUR_BRIGHT_RED },
{ "dark_pink", COLOUR_DARK_PINK },
{ "bright_pink", COLOUR_BRIGHT_PINK },
{ "light_pink", COLOUR_LIGHT_PINK },
};
auto result = LookupTable.find(s);
return (result != LookupTable.end()) ? result->second : defaultValue;
}
uint8_t ParseCursor(const std::string& s, uint8_t defaultValue)
{
static const std::unordered_map<std::string, uint8_t> LookupTable{
{ "CURSOR_BLANK", CURSOR_BLANK },
{ "CURSOR_UP_ARROW", CURSOR_UP_ARROW },
{ "CURSOR_UP_DOWN_ARROW", CURSOR_UP_DOWN_ARROW },
{ "CURSOR_HAND_POINT", CURSOR_HAND_POINT },
{ "CURSOR_ZZZ", CURSOR_ZZZ },
{ "CURSOR_DIAGONAL_ARROWS", CURSOR_DIAGONAL_ARROWS },
{ "CURSOR_PICKER", CURSOR_PICKER },
{ "CURSOR_TREE_DOWN", CURSOR_TREE_DOWN },
{ "CURSOR_FOUNTAIN_DOWN", CURSOR_FOUNTAIN_DOWN },
{ "CURSOR_STATUE_DOWN", CURSOR_STATUE_DOWN },
{ "CURSOR_BENCH_DOWN", CURSOR_BENCH_DOWN },
{ "CURSOR_CROSS_HAIR", CURSOR_CROSS_HAIR },
{ "CURSOR_BIN_DOWN", CURSOR_BIN_DOWN },
{ "CURSOR_LAMPPOST_DOWN", CURSOR_LAMPPOST_DOWN },
{ "CURSOR_FENCE_DOWN", CURSOR_FENCE_DOWN },
{ "CURSOR_FLOWER_DOWN", CURSOR_FLOWER_DOWN },
{ "CURSOR_PATH_DOWN", CURSOR_PATH_DOWN },
{ "CURSOR_DIG_DOWN", CURSOR_DIG_DOWN },
{ "CURSOR_WATER_DOWN", CURSOR_WATER_DOWN },
{ "CURSOR_HOUSE_DOWN", CURSOR_HOUSE_DOWN },
{ "CURSOR_VOLCANO_DOWN", CURSOR_VOLCANO_DOWN },
{ "CURSOR_WALK_DOWN", CURSOR_WALK_DOWN },
{ "CURSOR_PAINT_DOWN", CURSOR_PAINT_DOWN },
{ "CURSOR_ENTRANCE_DOWN", CURSOR_ENTRANCE_DOWN },
{ "CURSOR_HAND_OPEN", CURSOR_HAND_OPEN },
{ "CURSOR_HAND_CLOSED", CURSOR_HAND_CLOSED },
{ "CURSOR_ARROW", CURSOR_ARROW },
};
auto result = LookupTable.find(s);
return (result != LookupTable.end()) ? result->second : defaultValue;
}
rct_object_entry ParseObjectEntry(const std::string& s)
{
rct_object_entry entry = {};
std::fill_n(entry.name, sizeof(entry.name), ' ');
auto copyLen = std::min<size_t>(8, s.size());
std::copy_n(s.c_str(), copyLen, entry.name);
return entry;
}
static std::vector<int32_t> ParseRange(std::string s)
{
// Currently only supports [###] or [###..###]
std::vector<int32_t> result = {};
if (s.length() >= 3 && s[0] == '[' && s[s.length() - 1] == ']')
{
s = s.substr(1, s.length() - 2);
auto parts = String::Split(s, "..");
if (parts.size() == 1)
{
result.push_back(std::stoi(parts[0]));
}
else
{
auto left = std::stoi(parts[0]);
auto right = std::stoi(parts[1]);
if (left <= right)
{
for (auto i = left; i <= right; i++)
{
result.push_back(i);
}
}
else
{
for (auto i = right; i >= left; i--)
{
result.push_back(i);
}
}
}
}
return result;
}
static std::string FindLegacyObject(const std::string& name)
{
const auto env = GetContext()->GetPlatformEnvironment();
auto objectsPath = env->GetDirectoryPath(DIRBASE::RCT2, DIRID::OBJECT);
auto objectPath = Path::Combine(objectsPath, name);
if (!File::Exists(objectPath))
{
// Search recursively for any file with the target name (case insensitive)
auto filter = Path::Combine(objectsPath, "*.dat");
auto scanner = std::unique_ptr<IFileScanner>(Path::ScanDirectory(filter, true));
while (scanner->Next())
{
auto currentName = Path::GetFileName(scanner->GetPathRelative());
if (String::Equals(currentName, name, true))
{
objectPath = scanner->GetPath();
break;
}
}
}
return objectPath;
}
static std::vector<std::unique_ptr<RequiredImage>> LoadObjectImages(
IReadObjectContext* context, const std::string& name, const std::vector<int32_t>& range)
{
std::vector<std::unique_ptr<RequiredImage>> result;
auto objectPath = FindLegacyObject(name);
auto obj = ObjectFactory::CreateObjectFromLegacyFile(context->GetObjectRepository(), objectPath.c_str());
if (obj != nullptr)
{
auto& imgTable = static_cast<const Object*>(obj)->GetImageTable();
auto numImages = static_cast<int32_t>(imgTable.GetCount());
auto images = imgTable.GetImages();
size_t placeHoldersAdded = 0;
for (auto i : range)
{
if (i >= 0 && i < numImages)
{
result.push_back(std::make_unique<RequiredImage>(
static_cast<uint32_t>(i), [images](uint32_t idx) -> const rct_g1_element* { return &images[idx]; }));
}
else
{
result.push_back(std::make_unique<RequiredImage>());
placeHoldersAdded++;
}
}
delete obj;
// Log place holder information
if (placeHoldersAdded > 0)
{
std::string msg = "Adding " + std::to_string(placeHoldersAdded) + " placeholders";
context->LogWarning(OBJECT_ERROR_INVALID_PROPERTY, msg.c_str());
}
}
else
{
std::string msg = "Unable to open '" + objectPath + "'";
context->LogWarning(OBJECT_ERROR_INVALID_PROPERTY, msg.c_str());
for (size_t i = 0; i < range.size(); i++)
{
result.push_back(std::make_unique<RequiredImage>());
}
}
return result;
}
static std::vector<std::unique_ptr<RequiredImage>> ParseImages(IReadObjectContext* context, std::string s)
{
std::vector<std::unique_ptr<RequiredImage>> result;
if (s.empty())
{
result.push_back(std::make_unique<RequiredImage>());
}
else if (String::StartsWith(s, "$CSG"))
{
if (is_csg_loaded())
{
auto range = ParseRange(s.substr(4));
if (!range.empty())
{
for (auto i : range)
{
result.push_back(std::make_unique<RequiredImage>(
static_cast<uint32_t>(SPR_CSG_BEGIN + i),
[](uint32_t idx) -> const rct_g1_element* { return gfx_get_g1_element(idx); }));
}
}
}
}
else if (String::StartsWith(s, "$G1"))
{
auto range = ParseRange(s.substr(3));
if (!range.empty())
{
for (auto i : range)
{
result.push_back(
std::make_unique<RequiredImage>(static_cast<uint32_t>(i), [](uint32_t idx) -> const rct_g1_element* {
return gfx_get_g1_element(idx);
}));
}
}
}
else if (String::StartsWith(s, "$RCT2:OBJDATA/"))
{
auto name = s.substr(14);
auto rangeStart = name.find('[');
if (rangeStart != std::string::npos)
{
auto rangeString = name.substr(rangeStart);
auto range = ParseRange(name.substr(rangeStart));
name = name.substr(0, rangeStart);
result = LoadObjectImages(context, name, range);
}
}
else
{
try
{
auto imageData = context->GetData(s);
auto image = Imaging::ReadFromBuffer(imageData, IMAGE_FORMAT::PNG_32);
ImageImporter importer;
auto importResult = importer.Import(image, 0, 0, ImageImporter::IMPORT_FLAGS::RLE);
result.push_back(std::make_unique<RequiredImage>(importResult.Element));
}
catch (const std::exception& e)
{
auto msg = String::StdFormat("Unable to load image '%s': %s", s.c_str(), e.what());
context->LogWarning(OBJECT_ERROR_BAD_IMAGE_TABLE, msg.c_str());
result.push_back(std::make_unique<RequiredImage>());
}
}
return result;
}
static std::vector<std::unique_ptr<RequiredImage>> ParseImages(IReadObjectContext* context, json_t* el)
{
auto path = GetString(el, "path");
auto x = GetInteger(el, "x");
auto y = GetInteger(el, "y");
auto raw = (GetString(el, "format") == "raw");
std::vector<std::unique_ptr<RequiredImage>> result;
try
{
auto flags = ImageImporter::IMPORT_FLAGS::NONE;
if (!raw)
{
flags = static_cast<ImageImporter::IMPORT_FLAGS>(flags | ImageImporter::IMPORT_FLAGS::RLE);
}
auto imageData = context->GetData(path);
auto image = Imaging::ReadFromBuffer(imageData, IMAGE_FORMAT::PNG_32);
ImageImporter importer;
auto importResult = importer.Import(image, 0, 0, flags);
auto g1Element = importResult.Element;
g1Element.x_offset = x;
g1Element.y_offset = y;
result.push_back(std::make_unique<RequiredImage>(g1Element));
}
catch (const std::exception& e)
{
auto msg = String::StdFormat("Unable to load image '%s': %s", path.c_str(), e.what());
context->LogWarning(OBJECT_ERROR_BAD_IMAGE_TABLE, msg.c_str());
result.push_back(std::make_unique<RequiredImage>());
}
return result;
}
static ObjectStringID ParseStringId(const std::string& s)
{
if (s == "name")
return ObjectStringID::NAME;
if (s == "description")
return ObjectStringID::DESCRIPTION;
if (s == "capacity")
return ObjectStringID::CAPACITY;
if (s == "vehicleName")
return ObjectStringID::VEHICLE_NAME;
return ObjectStringID::UNKNOWN;
}
void LoadStrings(const json_t* root, StringTable& stringTable)
{
auto jsonStrings = json_object_get(root, "strings");
const char* key;
json_t* jlanguages;
json_object_foreach(jsonStrings, key, jlanguages)
{
auto stringId = ParseStringId(key);
if (stringId != ObjectStringID::UNKNOWN)
{
const char* locale;
json_t* jstring;
json_object_foreach(jlanguages, locale, jstring)
{
auto langId = language_get_id_from_locale(locale);
if (langId != LANGUAGE_UNDEFINED)
{
auto string = json_string_value(jstring);
stringTable.SetString(stringId, langId, string);
}
}
}
}
stringTable.Sort();
}
void LoadImages(IReadObjectContext* context, const json_t* root, ImageTable& imageTable)
{
if (context->ShouldLoadImages())
{
// First gather all the required images from inspecting the JSON
std::vector<std::unique_ptr<RequiredImage>> allImages;
auto jsonImages = json_object_get(root, "images");
size_t i;
json_t* el;
json_array_foreach(jsonImages, i, el)
{
if (json_is_string(el))
{
auto s = json_string_value(el);
auto images = ParseImages(context, s);
allImages.insert(
allImages.end(), std::make_move_iterator(images.begin()), std::make_move_iterator(images.end()));
}
else if (json_is_object(el))
{
auto images = ParseImages(context, el);
allImages.insert(
allImages.end(), std::make_move_iterator(images.begin()), std::make_move_iterator(images.end()));
}
}
// Now add all the images to the image table
auto imagesStartIndex = imageTable.GetCount();
for (const auto& img : allImages)
{
const auto& g1 = img->g1;
imageTable.AddImage(&g1);
}
// Add all the zoom images at the very end of the image table.
// This way it should not affect the offsets used within the object logic.
for (size_t j = 0; j < allImages.size(); j++)
{
const auto tableIndex = imagesStartIndex + j;
const auto* img = allImages[j].get();
if (img->next_zoom != nullptr)
{
img = img->next_zoom.get();
// Set old image zoom offset to zoom image which we are about to add
auto g1a = const_cast<rct_g1_element*>(&imageTable.GetImages()[tableIndex]);
g1a->zoomed_offset = static_cast<int32_t>(tableIndex) - static_cast<int32_t>(imageTable.GetCount());
while (img != nullptr)
{
auto g1b = img->g1;
if (img->next_zoom != nullptr)
{
g1b.zoomed_offset = -1;
}
imageTable.AddImage(&g1b);
img = img->next_zoom.get();
}
}
}
}
}
} // namespace ObjectJsonHelpers

View File

@@ -1,52 +0,0 @@
/*****************************************************************************
* Copyright (c) 2014-2020 OpenRCT2 developers
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
*****************************************************************************/
#pragma once
#include "../common.h"
#include "../core/Json.hpp"
#include "../drawing/Drawing.h"
#include "../interface/Colour.h"
#include "../object/Object.h"
#include "ImageTable.h"
#include "StringTable.h"
#include <initializer_list>
#include <string>
#include <utility>
#include <vector>
namespace ObjectJsonHelpers
{
bool GetBoolean(const json_t* obj, const std::string& name, bool defaultValue = false);
std::string GetString(const json_t* value);
std::string GetString(const json_t* obj, const std::string& name, const std::string& defaultValue = "");
int32_t GetInteger(const json_t* obj, const std::string& name, const int32_t& defaultValue = 0);
float GetFloat(const json_t* obj, const std::string& name, const float& defaultValue = 0);
std::vector<std::string> GetJsonStringArray(const json_t* arr);
std::vector<int32_t> GetJsonIntegerArray(const json_t* arr);
colour_t ParseColour(const std::string_view& s, colour_t defaultValue = COLOUR_BLACK);
uint8_t ParseCursor(const std::string& s, uint8_t defaultValue);
rct_object_entry ParseObjectEntry(const std::string& s);
void LoadStrings(const json_t* root, StringTable& stringTable);
void LoadImages(IReadObjectContext* context, const json_t* root, ImageTable& imageTable);
template<typename T> static T GetFlags(const json_t* obj, std::initializer_list<std::pair<std::string, T>> list)
{
T flags{};
for (const auto& item : list)
{
if (GetBoolean(obj, item.first))
{
flags = static_cast<T>(flags | item.second);
}
}
return flags;
}
} // namespace ObjectJsonHelpers

View File

@@ -23,7 +23,6 @@
#include "../ride/RideData.h"
#include "../ride/ShopItem.h"
#include "../ride/Track.h"
#include "ObjectJsonHelpers.h"
#include "ObjectRepository.h"
#include <algorithm>
@@ -523,175 +522,161 @@ uint8_t RideObject::CalculateNumHorizontalFrames(const rct_ride_entry_vehicle* v
return numHorizontalFrames;
}
void RideObject::ReadJson(IReadObjectContext* context, const json_t* root)
void RideObject::ReadJson(IReadObjectContext* context, json_t& root)
{
auto properties = json_object_get(root, "properties");
Guard::Assert(root.is_object(), "RideObject::ReadJson expects parameter root to be object");
auto rideTypes = ObjectJsonHelpers::GetJsonStringArray(json_object_get(properties, "type"));
for (size_t i = 0; i < MAX_RIDE_TYPES_PER_RIDE_ENTRY; i++)
json_t properties = root["properties"];
if (properties.is_object())
{
uint8_t rideType = RIDE_TYPE_NULL;
if (i < rideTypes.size())
// This will convert a string to an array
json_t rideTypes = Json::AsArray(properties["type"]);
size_t numRideTypes = rideTypes.size();
for (size_t i = 0; i < MAX_RIDE_TYPES_PER_RIDE_ENTRY; i++)
{
rideType = ParseRideType(rideTypes[i]);
if (rideType == RIDE_TYPE_NULL)
uint8_t rideType = RIDE_TYPE_NULL;
if (i < numRideTypes)
{
context->LogError(OBJECT_ERROR_INVALID_PROPERTY, "Unknown ride type");
rideType = ParseRideType(Json::GetString(rideTypes[i]));
if (rideType == RIDE_TYPE_NULL)
{
context->LogError(OBJECT_ERROR_INVALID_PROPERTY, "Unknown ride type");
}
}
_legacyType.ride_type[i] = rideType;
}
_legacyType.max_height = Json::GetNumber<uint8_t>(properties["maxHeight"]);
// This needs to be set for both shops/facilities _and_ regular rides.
for (auto& item : _legacyType.shop_item)
{
item = SHOP_ITEM_NONE;
}
auto carColours = Json::AsArray(properties["carColours"]);
_presetColours = ReadJsonCarColours(carColours);
if (IsRideTypeShopOrFacility(_legacyType.ride_type[0]))
{
// Standard car info for a shop
auto& car = _legacyType.vehicles[0];
car.spacing = 544;
car.sprite_flags = VEHICLE_SPRITE_FLAG_FLAT;
car.sprite_width = 1;
car.sprite_height_negative = 1;
car.sprite_height_positive = 1;
car.flags = VEHICLE_ENTRY_FLAG_SPINNING;
car.car_visual = VEHICLE_VISUAL_FLAT_RIDE_OR_CAR_RIDE;
car.friction_sound_id = SoundId::Null;
car.sound_range = 0xFF;
car.draw_order = 6;
// Shop item
auto rideSells = Json::AsArray(properties["sells"]);
auto numShopItems = std::min(static_cast<size_t>(NUM_SHOP_ITEMS_PER_RIDE), rideSells.size());
for (size_t i = 0; i < numShopItems; i++)
{
auto shopItem = ParseShopItem(Json::GetString(rideSells[i]));
if (shopItem == SHOP_ITEM_NONE)
{
context->LogWarning(OBJECT_ERROR_INVALID_PROPERTY, "Unknown shop item");
}
_legacyType.shop_item[i] = shopItem;
}
}
else
{
ReadJsonVehicleInfo(context, properties);
auto swingMode = Json::GetNumber<int32_t>(properties["swingMode"]);
if (swingMode == 1)
{
_legacyType.flags |= RIDE_ENTRY_FLAG_ALTERNATIVE_SWING_MODE_1;
}
else if (swingMode == 2)
{
_legacyType.flags |= RIDE_ENTRY_FLAG_ALTERNATIVE_SWING_MODE_1;
_legacyType.flags |= RIDE_ENTRY_FLAG_ALTERNATIVE_SWING_MODE_2;
}
auto rotationMode = Json::GetNumber<int32_t>(properties["rotationMode"]);
if (rotationMode == 1)
{
_legacyType.flags |= RIDE_ENTRY_FLAG_ALTERNATIVE_ROTATION_MODE_1;
}
else if (rotationMode == 2)
{
_legacyType.flags |= RIDE_ENTRY_FLAG_ALTERNATIVE_ROTATION_MODE_2;
}
auto ratingMultiplier = properties["ratingMultipler"];
if (ratingMultiplier.is_object())
{
_legacyType.excitement_multiplier = Json::GetNumber<int8_t>(ratingMultiplier["excitement"]);
_legacyType.intensity_multiplier = Json::GetNumber<int8_t>(ratingMultiplier["intensity"]);
_legacyType.nausea_multiplier = Json::GetNumber<int8_t>(ratingMultiplier["nausea"]);
}
}
_legacyType.ride_type[i] = rideType;
}
_legacyType.max_height = ObjectJsonHelpers::GetInteger(properties, "maxHeight");
// This needs to be set for both shops/facilities _and_ regular rides.
for (auto& item : _legacyType.shop_item)
{
item = SHOP_ITEM_NONE;
}
_presetColours = ReadJsonCarColours(json_object_get(properties, "carColours"));
if (IsRideTypeShopOrFacility(_legacyType.ride_type[0]))
{
// Standard car info for a shop
auto& car = _legacyType.vehicles[0];
car.spacing = 544;
car.sprite_flags = VEHICLE_SPRITE_FLAG_FLAT;
car.sprite_width = 1;
car.sprite_height_negative = 1;
car.sprite_height_positive = 1;
car.flags = VEHICLE_ENTRY_FLAG_SPINNING;
car.car_visual = VEHICLE_VISUAL_FLAT_RIDE_OR_CAR_RIDE;
car.friction_sound_id = SoundId::Null;
car.sound_range = 0xFF;
car.draw_order = 6;
// Shop item
auto rideSells = ObjectJsonHelpers::GetJsonStringArray(json_object_get(json_object_get(root, "properties"), "sells"));
auto numShopItems = std::min(static_cast<size_t>(NUM_SHOP_ITEMS_PER_RIDE), rideSells.size());
for (size_t i = 0; i < numShopItems; i++)
{
auto shopItem = ParseShopItem(rideSells[i]);
if (shopItem == SHOP_ITEM_NONE)
_legacyType.BuildMenuPriority = Json::GetNumber<uint8_t>(properties["buildMenuPriority"]);
_legacyType.flags |= Json::GetFlags<uint32_t>(
properties,
{
context->LogWarning(OBJECT_ERROR_INVALID_PROPERTY, "Unknown shop item");
}
_legacyType.shop_item[i] = ParseShopItem(rideSells[i]);
}
{ "noInversions", RIDE_ENTRY_FLAG_NO_INVERSIONS },
{ "noBanking", RIDE_ENTRY_FLAG_NO_BANKED_TRACK },
{ "playDepartSound", RIDE_ENTRY_FLAG_PLAY_DEPART_SOUND },
// Skipping "disallowWandering", no vehicle sets this flag.
{ "playSplashSound", RIDE_ENTRY_FLAG_PLAY_SPLASH_SOUND },
{ "playSplashSoundSlide", RIDE_ENTRY_FLAG_PLAY_SPLASH_SOUND_SLIDE },
{ "hasShelter", RIDE_ENTRY_FLAG_COVERED_RIDE },
{ "limitAirTimeBonus", RIDE_ENTRY_FLAG_LIMIT_AIRTIME_BONUS },
{ "disableBreakdown", RIDE_ENTRY_FLAG_CANNOT_BREAK_DOWN },
// Skipping noDoorsOverTrack, moved to ride groups.
{ "noCollisionCrashes", RIDE_ENTRY_FLAG_DISABLE_COLLISION_CRASHES },
{ "disablePainting", RIDE_ENTRY_FLAG_DISABLE_COLOUR_TAB },
});
}
else
{
ReadJsonVehicleInfo(context, properties);
auto swingMode = ObjectJsonHelpers::GetInteger(properties, "swingMode");
if (swingMode == 1)
{
_legacyType.flags |= RIDE_ENTRY_FLAG_ALTERNATIVE_SWING_MODE_1;
}
else if (swingMode == 2)
{
_legacyType.flags |= RIDE_ENTRY_FLAG_ALTERNATIVE_SWING_MODE_1;
_legacyType.flags |= RIDE_ENTRY_FLAG_ALTERNATIVE_SWING_MODE_2;
}
auto rotationMode = ObjectJsonHelpers::GetInteger(properties, "rotationMode");
if (rotationMode == 1)
{
_legacyType.flags |= RIDE_ENTRY_FLAG_ALTERNATIVE_ROTATION_MODE_1;
}
else if (rotationMode == 2)
{
_legacyType.flags |= RIDE_ENTRY_FLAG_ALTERNATIVE_ROTATION_MODE_2;
}
auto ratingMultiplier = json_object_get(properties, "ratingMultipler");
if (ratingMultiplier != nullptr)
{
_legacyType.excitement_multiplier = ObjectJsonHelpers::GetInteger(ratingMultiplier, "excitement");
_legacyType.intensity_multiplier = ObjectJsonHelpers::GetInteger(ratingMultiplier, "intensity");
_legacyType.nausea_multiplier = ObjectJsonHelpers::GetInteger(ratingMultiplier, "nausea");
}
auto availableTrackPieces = ObjectJsonHelpers::GetJsonStringArray(json_object_get(properties, "availableTrackPieces"));
}
_legacyType.BuildMenuPriority = ObjectJsonHelpers::GetInteger(properties, "buildMenuPriority", 0);
_legacyType.flags |= ObjectJsonHelpers::GetFlags<uint32_t>(
properties,
{
{ "noInversions", RIDE_ENTRY_FLAG_NO_INVERSIONS },
{ "noBanking", RIDE_ENTRY_FLAG_NO_BANKED_TRACK },
{ "playDepartSound", RIDE_ENTRY_FLAG_PLAY_DEPART_SOUND },
// Skipping "disallowWandering", no vehicle sets this flag.
{ "playSplashSound", RIDE_ENTRY_FLAG_PLAY_SPLASH_SOUND },
{ "playSplashSoundSlide", RIDE_ENTRY_FLAG_PLAY_SPLASH_SOUND_SLIDE },
{ "hasShelter", RIDE_ENTRY_FLAG_COVERED_RIDE },
{ "limitAirTimeBonus", RIDE_ENTRY_FLAG_LIMIT_AIRTIME_BONUS },
{ "disableBreakdown", RIDE_ENTRY_FLAG_CANNOT_BREAK_DOWN },
// Skipping noDoorsOverTrack, moved to ride groups.
{ "noCollisionCrashes", RIDE_ENTRY_FLAG_DISABLE_COLLISION_CRASHES },
{ "disablePainting", RIDE_ENTRY_FLAG_DISABLE_COLOUR_TAB },
});
RideObjectUpdateRideType(&_legacyType);
ObjectJsonHelpers::LoadStrings(root, GetStringTable());
ObjectJsonHelpers::LoadImages(context, root, GetImageTable());
PopulateTablesFromJson(context, root);
}
void RideObject::ReadJsonVehicleInfo([[maybe_unused]] IReadObjectContext* context, const json_t* properties)
void RideObject::ReadJsonVehicleInfo([[maybe_unused]] IReadObjectContext* context, json_t& properties)
{
_legacyType.min_cars_in_train = ObjectJsonHelpers::GetInteger(properties, "minCarsPerTrain", 1);
_legacyType.max_cars_in_train = ObjectJsonHelpers::GetInteger(properties, "maxCarsPerTrain", 1);
_legacyType.cars_per_flat_ride = ObjectJsonHelpers::GetInteger(properties, "carsPerFlatRide", 255);
_legacyType.zero_cars = json_integer_value(json_object_get(properties, "numEmptyCars"));
Guard::Assert(properties.is_object(), "RideObject::ReadJsonVehicleInfo expects parameter properties to be object");
_legacyType.min_cars_in_train = Json::GetNumber<uint8_t>(properties["minCarsPerTrain"], 1);
_legacyType.max_cars_in_train = Json::GetNumber<uint8_t>(properties["maxCarsPerTrain"], 1);
_legacyType.cars_per_flat_ride = Json::GetNumber<uint8_t>(properties["carsPerFlatRide"], 255);
_legacyType.zero_cars = Json::GetNumber<uint8_t>(properties["numEmptyCars"]);
// Train formation from car indices
_legacyType.default_vehicle = json_integer_value(json_object_get(properties, "defaultCar"));
_legacyType.tab_vehicle = json_integer_value(json_object_get(properties, "tabCar"));
auto tabScale = ObjectJsonHelpers::GetFloat(properties, "tabScale");
if (tabScale != 0 && ObjectJsonHelpers::GetFloat(properties, "tabScale") <= 0.5f)
_legacyType.default_vehicle = Json::GetNumber<uint8_t>(properties["defaultCar"]);
_legacyType.tab_vehicle = Json::GetNumber<uint8_t>(properties["tabCar"]);
float tabScale = Json::GetNumber<float>(properties["tabScale"]);
if (tabScale != 0 && tabScale <= 0.5f)
{
_legacyType.flags |= RIDE_ENTRY_FLAG_VEHICLE_TAB_SCALE_HALF;
}
json_t headCars = Json::AsArray(properties["headCars"]);
json_t tailCars = Json::AsArray(properties["tailCars"]);
// 0xFF means N/A.
_legacyType.front_vehicle = 0xFF;
_legacyType.second_vehicle = 0xFF;
_legacyType.third_vehicle = 0xFF;
_legacyType.rear_vehicle = 0xFF;
_legacyType.front_vehicle = Json::GetNumber<uint8_t>(headCars[0], 0xFF);
_legacyType.second_vehicle = Json::GetNumber<uint8_t>(headCars[1], 0xFF);
_legacyType.third_vehicle = Json::GetNumber<uint8_t>(headCars[2], 0xFF);
_legacyType.rear_vehicle = Json::GetNumber<uint8_t>(tailCars[0], 0xFF);
auto headCars = ObjectJsonHelpers::GetJsonIntegerArray(json_object_get(properties, "headCars"));
if (headCars.size() >= 1)
{
_legacyType.front_vehicle = headCars[0];
}
if (headCars.size() >= 2)
{
_legacyType.second_vehicle = headCars[1];
}
if (headCars.size() >= 3)
{
_legacyType.third_vehicle = headCars[2];
}
if (headCars.size() >= 4)
{
// More than 3 head cars not supported yet!
}
auto tailCars = ObjectJsonHelpers::GetJsonIntegerArray(json_object_get(properties, "tailCars"));
if (tailCars.size() >= 1)
{
_legacyType.rear_vehicle = tailCars[0];
}
if (tailCars.size() >= 2)
{
// More than 1 tail car not supported yet!
}
auto cars = ReadJsonCars(json_object_get(properties, "cars"));
auto cars = ReadJsonCars(properties["cars"]);
auto numCars = std::min(std::size(_legacyType.vehicles), cars.size());
for (size_t i = 0; i < numCars; i++)
{
@@ -699,129 +684,131 @@ void RideObject::ReadJsonVehicleInfo([[maybe_unused]] IReadObjectContext* contex
}
}
std::vector<rct_ride_entry_vehicle> RideObject::ReadJsonCars(const json_t* jCars)
std::vector<rct_ride_entry_vehicle> RideObject::ReadJsonCars(json_t& jCars)
{
std::vector<rct_ride_entry_vehicle> cars;
if (json_is_array(jCars))
if (jCars.is_array())
{
json_t* jCar;
size_t index;
json_array_foreach(jCars, index, jCar)
for (auto& jCar : jCars)
{
auto car = ReadJsonCar(jCar);
cars.push_back(car);
if (jCar.is_object())
{
auto car = ReadJsonCar(jCar);
cars.push_back(car);
}
}
}
else if (json_is_object(jCars))
else if (jCars.is_object())
{
auto car = ReadJsonCar(jCars);
cars.push_back(car);
}
return cars;
}
rct_ride_entry_vehicle RideObject::ReadJsonCar(const json_t* jCar)
rct_ride_entry_vehicle RideObject::ReadJsonCar(json_t& jCar)
{
Guard::Assert(jCar.is_object(), "RideObject::ReadJsonCar expects parameter jCar to be object");
rct_ride_entry_vehicle car = {};
car.rotation_frame_mask = ObjectJsonHelpers::GetInteger(jCar, "rotationFrameMask");
car.spacing = ObjectJsonHelpers::GetInteger(jCar, "spacing");
car.car_mass = ObjectJsonHelpers::GetInteger(jCar, "mass");
car.tab_height = ObjectJsonHelpers::GetInteger(jCar, "tabOffset");
car.num_seats = ObjectJsonHelpers::GetInteger(jCar, "numSeats");
if (ObjectJsonHelpers::GetBoolean(jCar, "seatsInPairs", true) && car.num_seats > 1)
car.rotation_frame_mask = Json::GetNumber<uint16_t>(jCar["rotationFrameMask"]);
car.spacing = Json::GetNumber<uint32_t>(jCar["spacing"]);
car.car_mass = Json::GetNumber<uint16_t>(jCar["mass"]);
car.tab_height = Json::GetNumber<int8_t>(jCar["tabOffset"]);
car.num_seats = Json::GetNumber<uint8_t>(jCar["numSeats"]);
if (Json::GetBoolean(jCar["seatsInPairs"], true) && car.num_seats > 1)
{
car.num_seats |= VEHICLE_SEAT_PAIR_FLAG;
}
car.sprite_width = ObjectJsonHelpers::GetInteger(jCar, "spriteWidth");
car.sprite_height_negative = ObjectJsonHelpers::GetInteger(jCar, "spriteHeightNegative");
car.sprite_height_positive = ObjectJsonHelpers::GetInteger(jCar, "spriteHeightPositive");
car.animation = ObjectJsonHelpers::GetInteger(jCar, "animation");
car.base_num_frames = ObjectJsonHelpers::GetInteger(jCar, "baseNumFrames");
car.no_vehicle_images = ObjectJsonHelpers::GetInteger(jCar, "numImages");
car.no_seating_rows = ObjectJsonHelpers::GetInteger(jCar, "numSeatRows");
car.spinning_inertia = ObjectJsonHelpers::GetInteger(jCar, "spinningInertia");
car.spinning_friction = ObjectJsonHelpers::GetInteger(jCar, "spinningFriction");
car.friction_sound_id = static_cast<SoundId>(ObjectJsonHelpers::GetInteger(jCar, "frictionSoundId", 255));
car.log_flume_reverser_vehicle_type = ObjectJsonHelpers::GetInteger(jCar, "logFlumeReverserVehicleType");
car.sound_range = ObjectJsonHelpers::GetInteger(jCar, "soundRange", 255);
car.double_sound_frequency = ObjectJsonHelpers::GetInteger(jCar, "doubleSoundFrequency");
car.powered_acceleration = ObjectJsonHelpers::GetInteger(jCar, "poweredAcceleration");
car.powered_max_speed = ObjectJsonHelpers::GetInteger(jCar, "poweredMaxSpeed");
car.car_visual = ObjectJsonHelpers::GetInteger(jCar, "carVisual");
car.effect_visual = ObjectJsonHelpers::GetInteger(jCar, "effectVisual", 1);
car.draw_order = ObjectJsonHelpers::GetInteger(jCar, "drawOrder");
car.num_vertical_frames_override = ObjectJsonHelpers::GetInteger(jCar, "numVerticalFramesOverride");
car.sprite_width = Json::GetNumber<uint8_t>(jCar["spriteWidth"]);
car.sprite_height_negative = Json::GetNumber<uint8_t>(jCar["spriteHeightNegative"]);
car.sprite_height_positive = Json::GetNumber<uint8_t>(jCar["spriteHeightPositive"]);
car.animation = Json::GetNumber<uint8_t>(jCar["animation"]);
car.base_num_frames = Json::GetNumber<uint16_t>(jCar["baseNumFrames"]);
car.no_vehicle_images = Json::GetNumber<uint32_t>(jCar["numImages"]);
car.no_seating_rows = Json::GetNumber<uint8_t>(jCar["numSeatRows"]);
car.spinning_inertia = Json::GetNumber<uint8_t>(jCar["spinningInertia"]);
car.spinning_friction = Json::GetNumber<uint8_t>(jCar["spinningFriction"]);
car.friction_sound_id = Json::GetEnum<SoundId>(jCar["frictionSoundId"], SoundId::Null);
car.log_flume_reverser_vehicle_type = Json::GetNumber<uint8_t>(jCar["logFlumeReverserVehicleType"]);
car.sound_range = Json::GetNumber<uint8_t>(jCar["soundRange"], 255);
car.double_sound_frequency = Json::GetNumber<uint8_t>(jCar["doubleSoundFrequency"]);
car.powered_acceleration = Json::GetNumber<uint8_t>(jCar["poweredAcceleration"]);
car.powered_max_speed = Json::GetNumber<uint8_t>(jCar["poweredMaxSpeed"]);
car.car_visual = Json::GetNumber<uint8_t>(jCar["carVisual"]);
car.effect_visual = Json::GetNumber<uint8_t>(jCar["effectVisual"], 1);
car.draw_order = Json::GetNumber<uint8_t>(jCar["drawOrder"]);
car.num_vertical_frames_override = Json::GetNumber<uint8_t>(jCar["numVerticalFramesOverride"]);
auto& peepLoadingPositions = car.peep_loading_positions;
auto jLoadingPositions = json_object_get(jCar, "loadingPositions");
if (json_is_array(jLoadingPositions))
auto jLoadingPositions = jCar["loadingPositions"];
if (jLoadingPositions.is_array())
{
auto arr = ObjectJsonHelpers::GetJsonIntegerArray(jLoadingPositions);
for (auto x : arr)
for (auto& jPos : jLoadingPositions)
{
peepLoadingPositions.push_back(x);
car.peep_loading_positions.push_back(Json::GetNumber<int8_t>(jPos));
}
}
else
{
auto& peepLoadingWaypoints = car.peep_loading_waypoints;
auto jLoadingWaypoints = json_object_get(jCar, "loadingWaypoints");
if (json_is_array(jLoadingWaypoints))
auto jLoadingWaypoints = jCar["loadingWaypoints"];
if (jLoadingWaypoints.is_array())
{
car.flags |= VEHICLE_ENTRY_FLAG_LOADING_WAYPOINTS;
car.peep_loading_waypoint_segments = Json::GetNumber<uint8_t>(jCar["numSegments"]);
auto numSegments = ObjectJsonHelpers::GetInteger(jCar, "numSegments");
car.peep_loading_waypoint_segments = numSegments;
size_t i;
json_t* route;
json_array_foreach(jLoadingWaypoints, i, route)
for (auto& jRoute : jLoadingWaypoints)
{
if (json_is_array(route))
if (jRoute.is_array())
{
size_t j;
json_t* waypoint;
std::array<CoordsXY, 3> entry;
json_array_foreach(route, j, waypoint)
for (size_t j = 0; j < 3; ++j)
{
if (json_is_array(waypoint) && json_array_size(waypoint) >= 2)
auto jWaypoint = jRoute[j];
if (jWaypoint.is_array() && jWaypoint.size() >= 2)
{
int32_t x = json_integer_value(json_array_get(waypoint, 0));
int32_t y = json_integer_value(json_array_get(waypoint, 1));
int32_t x = Json::GetNumber<int32_t>(jWaypoint[0]);
int32_t y = Json::GetNumber<int32_t>(jWaypoint[1]);
entry[j] = { x, y };
}
}
peepLoadingWaypoints.push_back(entry);
car.peep_loading_waypoints.push_back(entry);
}
}
}
}
auto jFrames = json_object_get(jCar, "frames");
car.sprite_flags = ObjectJsonHelpers::GetFlags<uint16_t>(
jFrames,
{
{ "flat", VEHICLE_SPRITE_FLAG_FLAT },
{ "gentleSlopes", VEHICLE_SPRITE_FLAG_GENTLE_SLOPES },
{ "steepSlopes", VEHICLE_SPRITE_FLAG_STEEP_SLOPES },
{ "verticalSlopes", VEHICLE_SPRITE_FLAG_VERTICAL_SLOPES },
{ "diagonalSlopes", VEHICLE_SPRITE_FLAG_DIAGONAL_SLOPES },
{ "flatBanked", VEHICLE_SPRITE_FLAG_FLAT_BANKED },
{ "inlineTwists", VEHICLE_SPRITE_FLAG_INLINE_TWISTS },
{ "flatToGentleSlopeBankedTransitions", VEHICLE_SPRITE_FLAG_FLAT_TO_GENTLE_SLOPE_BANKED_TRANSITIONS },
{ "diagonalGentleSlopeBankedTransitions", VEHICLE_SPRITE_FLAG_DIAGONAL_GENTLE_SLOPE_BANKED_TRANSITIONS },
{ "gentleSlopeBankedTransitions", VEHICLE_SPRITE_FLAG_GENTLE_SLOPE_BANKED_TRANSITIONS },
{ "gentleSlopeBankedTurns", VEHICLE_SPRITE_FLAG_GENTLE_SLOPE_BANKED_TURNS },
{ "flatToGentleSlopeWhileBankedTransitions", VEHICLE_SPRITE_FLAG_FLAT_TO_GENTLE_SLOPE_WHILE_BANKED_TRANSITIONS },
{ "corkscrews", VEHICLE_SPRITE_FLAG_CORKSCREWS },
{ "restraintAnimation", VEHICLE_SPRITE_FLAG_RESTRAINT_ANIMATION },
{ "curvedLiftHill", VEHICLE_SPRITE_FLAG_CURVED_LIFT_HILL },
{ "VEHICLE_SPRITE_FLAG_15", VEHICLE_SPRITE_FLAG_15 },
});
auto jFrames = jCar["frames"];
if (jFrames.is_object())
{
car.sprite_flags = Json::GetFlags<uint16_t>(
jFrames,
{
{ "flat", VEHICLE_SPRITE_FLAG_FLAT },
{ "gentleSlopes", VEHICLE_SPRITE_FLAG_GENTLE_SLOPES },
{ "steepSlopes", VEHICLE_SPRITE_FLAG_STEEP_SLOPES },
{ "verticalSlopes", VEHICLE_SPRITE_FLAG_VERTICAL_SLOPES },
{ "diagonalSlopes", VEHICLE_SPRITE_FLAG_DIAGONAL_SLOPES },
{ "flatBanked", VEHICLE_SPRITE_FLAG_FLAT_BANKED },
{ "inlineTwists", VEHICLE_SPRITE_FLAG_INLINE_TWISTS },
{ "flatToGentleSlopeBankedTransitions", VEHICLE_SPRITE_FLAG_FLAT_TO_GENTLE_SLOPE_BANKED_TRANSITIONS },
{ "diagonalGentleSlopeBankedTransitions", VEHICLE_SPRITE_FLAG_DIAGONAL_GENTLE_SLOPE_BANKED_TRANSITIONS },
{ "gentleSlopeBankedTransitions", VEHICLE_SPRITE_FLAG_GENTLE_SLOPE_BANKED_TRANSITIONS },
{ "gentleSlopeBankedTurns", VEHICLE_SPRITE_FLAG_GENTLE_SLOPE_BANKED_TURNS },
{ "flatToGentleSlopeWhileBankedTransitions",
VEHICLE_SPRITE_FLAG_FLAT_TO_GENTLE_SLOPE_WHILE_BANKED_TRANSITIONS },
{ "corkscrews", VEHICLE_SPRITE_FLAG_CORKSCREWS },
{ "restraintAnimation", VEHICLE_SPRITE_FLAG_RESTRAINT_ANIMATION },
{ "curvedLiftHill", VEHICLE_SPRITE_FLAG_CURVED_LIFT_HILL },
{ "VEHICLE_SPRITE_FLAG_15", VEHICLE_SPRITE_FLAG_15 },
});
}
car.flags |= ObjectJsonHelpers::GetFlags<uint32_t>(
car.flags |= Json::GetFlags<uint32_t>(
jCar,
{
{ "VEHICLE_ENTRY_FLAG_POWERED_RIDE_UNRESTRICTED_GRAVITY", VEHICLE_ENTRY_FLAG_POWERED_RIDE_UNRESTRICTED_GRAVITY },
@@ -856,18 +843,21 @@ rct_ride_entry_vehicle RideObject::ReadJsonCar(const json_t* jCar)
{ "VEHICLE_ENTRY_FLAG_GO_KART", VEHICLE_ENTRY_FLAG_GO_KART },
{ "VEHICLE_ENTRY_FLAG_DODGEM_CAR_PLACEMENT", VEHICLE_ENTRY_FLAG_DODGEM_CAR_PLACEMENT },
});
return car;
}
vehicle_colour_preset_list RideObject::ReadJsonCarColours(const json_t* jCarColours)
vehicle_colour_preset_list RideObject::ReadJsonCarColours(json_t& jCarColours)
{
Guard::Assert(jCarColours.is_array(), "RideObject::ReadJsonCarColours expects parameter jCarColours to be array");
// The JSON supports multiple configurations of per car colours, but
// the ride entry structure currently doesn't allow for it. Assume that
// a single configuration with multiple colour entries is per car scheme.
if (json_array_size(jCarColours) == 1)
if (jCarColours.size() == 1)
{
auto firstElement = json_array_get(jCarColours, 0);
auto numColours = json_array_size(firstElement);
auto firstElement = Json::AsArray(jCarColours[0]);
auto numColours = firstElement.size();
if (numColours >= 2)
{
// Read all colours from first config
@@ -881,11 +871,9 @@ vehicle_colour_preset_list RideObject::ReadJsonCarColours(const json_t* jCarColo
// Read first colour for each config
vehicle_colour_preset_list list = {};
size_t index;
const json_t* jConfiguration;
json_array_foreach(jCarColours, index, jConfiguration)
for (size_t index = 0; index < jCarColours.size(); index++)
{
auto config = ReadJsonColourConfiguration(jConfiguration);
auto config = ReadJsonColourConfiguration(jCarColours[index]);
if (config.size() >= 1)
{
list.list[index] = config[0];
@@ -901,27 +889,27 @@ vehicle_colour_preset_list RideObject::ReadJsonCarColours(const json_t* jCarColo
return list;
}
std::vector<vehicle_colour> RideObject::ReadJsonColourConfiguration(const json_t* jColourConfig)
std::vector<vehicle_colour> RideObject::ReadJsonColourConfiguration(json_t& jColourConfig)
{
std::vector<vehicle_colour> config;
size_t index;
const json_t* jColours;
json_array_foreach(jColourConfig, index, jColours)
for (auto& jColours : jColourConfig)
{
vehicle_colour carColour = {};
auto colours = ObjectJsonHelpers::GetJsonStringArray(jColours);
auto colours = Json::AsArray(jColours);
if (colours.size() >= 1)
{
carColour.main = ObjectJsonHelpers::ParseColour(colours[0]);
carColour.main = Colour::FromString(Json::GetString(colours[0]));
carColour.additional_1 = carColour.main;
carColour.additional_2 = carColour.main;
if (colours.size() >= 2)
{
carColour.additional_1 = ObjectJsonHelpers::ParseColour(colours[1]);
carColour.additional_1 = Colour::FromString(Json::GetString(colours[1]));
}
if (colours.size() >= 3)
{
carColour.additional_2 = ObjectJsonHelpers::ParseColour(colours[2]);
carColour.additional_2 = Colour::FromString(Json::GetString(colours[2]));
}
}
config.push_back(carColour);

View File

@@ -10,6 +10,7 @@
#pragma once
#include "../core/IStream.hpp"
#include "../core/Json.hpp"
#include "../ride/Ride.h"
#include "Object.h"
@@ -34,7 +35,7 @@ public:
return &_legacyType;
}
void ReadJson(IReadObjectContext* context, const json_t* root) override;
void ReadJson(IReadObjectContext* context, json_t& root) override;
void ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) override;
void Load() override;
void Unload() override;
@@ -49,11 +50,11 @@ public:
private:
void ReadLegacyVehicle(IReadObjectContext* context, OpenRCT2::IStream* stream, rct_ride_entry_vehicle* vehicle);
void ReadJsonVehicleInfo(IReadObjectContext* context, const json_t* properties);
std::vector<rct_ride_entry_vehicle> ReadJsonCars(const json_t* jCars);
rct_ride_entry_vehicle ReadJsonCar(const json_t* jCar);
vehicle_colour_preset_list ReadJsonCarColours(const json_t* jCarColours);
std::vector<vehicle_colour> ReadJsonColourConfiguration(const json_t* jColourConfig);
void ReadJsonVehicleInfo(IReadObjectContext* context, json_t& properties);
std::vector<rct_ride_entry_vehicle> ReadJsonCars(json_t& jCars);
rct_ride_entry_vehicle ReadJsonCar(json_t& jCar);
vehicle_colour_preset_list ReadJsonCarColours(json_t& jCarColours);
std::vector<vehicle_colour> ReadJsonColourConfiguration(json_t& jColourConfig);
static uint8_t CalculateNumVerticalFrames(const rct_ride_entry_vehicle* vehicleEntry);
static uint8_t CalculateNumHorizontalFrames(const rct_ride_entry_vehicle* vehicleEntry);

View File

@@ -18,7 +18,6 @@
#include "../drawing/Drawing.h"
#include "../localisation/Language.h"
#include "../peep/Staff.h"
#include "ObjectJsonHelpers.h"
#include "ObjectManager.h"
#include "ObjectRepository.h"
@@ -110,35 +109,29 @@ std::vector<rct_object_entry> SceneryGroupObject::ReadItems(IStream* stream)
return items;
}
void SceneryGroupObject::ReadJson(IReadObjectContext* context, const json_t* root)
void SceneryGroupObject::ReadJson(IReadObjectContext* context, json_t& root)
{
auto properties = json_object_get(root, "properties");
_legacyType.priority = json_integer_value(json_object_get(properties, "priority"));
Guard::Assert(root.is_object(), "SceneryGroupObject::ReadJson expects parameter root to be object");
// Entertainer costumes
auto jCostumes = json_object_get(properties, "entertainerCostumes");
if (jCostumes != nullptr)
auto properties = root["properties"];
if (properties.is_object())
{
_legacyType.entertainer_costumes = ReadJsonEntertainerCostumes(jCostumes);
_legacyType.priority = Json::GetNumber<uint8_t>(properties["priority"]);
_legacyType.entertainer_costumes = ReadJsonEntertainerCostumes(properties["entertainerCostumes"]);
_items = ReadJsonEntries(properties["entries"]);
}
auto jEntries = json_object_get(properties, "entries");
if (jEntries != nullptr)
{
_items = ReadJsonEntries(jEntries);
}
ObjectJsonHelpers::LoadStrings(root, GetStringTable());
ObjectJsonHelpers::LoadImages(context, root, GetImageTable());
PopulateTablesFromJson(context, root);
}
uint32_t SceneryGroupObject::ReadJsonEntertainerCostumes(const json_t* jCostumes)
uint32_t SceneryGroupObject::ReadJsonEntertainerCostumes(json_t& jCostumes)
{
uint32_t costumes = 0;
auto szCostumes = ObjectJsonHelpers::GetJsonStringArray(jCostumes);
for (const auto& szCostume : szCostumes)
for (auto& jCostume : jCostumes)
{
auto entertainer = ParseEntertainerCostume(szCostume);
auto entertainer = ParseEntertainerCostume(Json::GetString(jCostume));
auto peepSprite = EntertainerCostumeToSprite(entertainer);
costumes |= 1 << (static_cast<uint8_t>(peepSprite));
}
@@ -172,19 +165,14 @@ EntertainerCostume SceneryGroupObject::ParseEntertainerCostume(const std::string
return EntertainerCostume::Panda;
}
std::vector<rct_object_entry> SceneryGroupObject::ReadJsonEntries(const json_t* jEntries)
std::vector<rct_object_entry> SceneryGroupObject::ReadJsonEntries(json_t& jEntries)
{
std::vector<rct_object_entry> entries;
size_t index;
json_t* jEntry;
json_array_foreach(jEntries, index, jEntry)
for (auto& jEntry : jEntries)
{
auto entryId = json_string_value(jEntry);
if (entryId != nullptr)
{
auto entry = ObjectJsonHelpers::ParseObjectEntry(entryId);
entries.push_back(entry);
}
auto entry = ParseObjectEntry(Json::GetString(jEntry));
entries.push_back(entry);
}
return entries;
}

View File

@@ -34,7 +34,7 @@ public:
{
return &_legacyType;
}
void ReadJson(IReadObjectContext* context, const json_t* root) override;
void ReadJson(IReadObjectContext* context, json_t& root) override;
void ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) override;
void Load() override;
@@ -47,7 +47,7 @@ public:
private:
static std::vector<rct_object_entry> ReadItems(OpenRCT2::IStream* stream);
static uint32_t ReadJsonEntertainerCostumes(const json_t* jCostumes);
static uint32_t ReadJsonEntertainerCostumes(json_t& jCostumes);
static EntertainerCostume ParseEntertainerCostume(const std::string& s);
static std::vector<rct_object_entry> ReadJsonEntries(const json_t* jEntries);
static std::vector<rct_object_entry> ReadJsonEntries(json_t& jEntries);
};

View File

@@ -9,13 +9,11 @@
#include "SceneryObject.h"
#include "ObjectJsonHelpers.h"
void SceneryObject::SetPrimarySceneryGroup(const std::string& s)
{
if (!s.empty())
{
auto sgEntry = ObjectJsonHelpers::ParseObjectEntry(s);
auto sgEntry = ParseObjectEntry(s);
SetPrimarySceneryGroup(&sgEntry);
}
}

View File

@@ -19,7 +19,6 @@
#include "../localisation/Language.h"
#include "../world/Scenery.h"
#include "../world/SmallScenery.h"
#include "ObjectJsonHelpers.h"
#include <algorithm>
@@ -234,96 +233,97 @@ rct_object_entry SmallSceneryObject::GetScgAbstrHeader()
return Object::CreateHeader("SCGABSTR", 207140231, 932253451);
}
void SmallSceneryObject::ReadJson(IReadObjectContext* context, const json_t* root)
void SmallSceneryObject::ReadJson(IReadObjectContext* context, json_t& root)
{
auto properties = json_object_get(root, "properties");
Guard::Assert(root.is_object(), "SmallSceneryObject::ReadJson expects parameter root to be object");
_legacyType.small_scenery.height = json_integer_value(json_object_get(properties, "height"));
_legacyType.small_scenery.tool_id = ObjectJsonHelpers::ParseCursor(
ObjectJsonHelpers::GetString(properties, "cursor"), CURSOR_STATUE_DOWN);
_legacyType.small_scenery.price = json_integer_value(json_object_get(properties, "price"));
_legacyType.small_scenery.removal_price = json_integer_value(json_object_get(properties, "removalPrice"));
_legacyType.small_scenery.animation_delay = json_integer_value(json_object_get(properties, "animationDelay"));
_legacyType.small_scenery.animation_mask = json_integer_value(json_object_get(properties, "animationMask"));
_legacyType.small_scenery.num_frames = json_integer_value(json_object_get(properties, "numFrames"));
auto properties = root["properties"];
// Flags
_legacyType.small_scenery.flags = ObjectJsonHelpers::GetFlags<uint32_t>(
properties,
{
{ "SMALL_SCENERY_FLAG_VOFFSET_CENTRE", SMALL_SCENERY_FLAG_VOFFSET_CENTRE },
{ "requiresFlatSurface", SMALL_SCENERY_FLAG_REQUIRE_FLAT_SURFACE },
{ "isRotatable", SMALL_SCENERY_FLAG_ROTATABLE },
{ "isAnimated", SMALL_SCENERY_FLAG_ANIMATED },
{ "canWither", SMALL_SCENERY_FLAG_CAN_WITHER },
{ "canBeWatered", SMALL_SCENERY_FLAG_CAN_BE_WATERED },
{ "hasOverlayImage", SMALL_SCENERY_FLAG_ANIMATED_FG },
{ "hasGlass", SMALL_SCENERY_FLAG_HAS_GLASS },
{ "hasPrimaryColour", SMALL_SCENERY_FLAG_HAS_PRIMARY_COLOUR },
{ "SMALL_SCENERY_FLAG_FOUNTAIN_SPRAY_1", SMALL_SCENERY_FLAG_FOUNTAIN_SPRAY_1 },
{ "SMALL_SCENERY_FLAG_FOUNTAIN_SPRAY_4", SMALL_SCENERY_FLAG_FOUNTAIN_SPRAY_4 },
{ "isClock", SMALL_SCENERY_FLAG_IS_CLOCK },
{ "SMALL_SCENERY_FLAG_SWAMP_GOO", SMALL_SCENERY_FLAG_SWAMP_GOO },
{ "SMALL_SCENERY_FLAG17", SMALL_SCENERY_FLAG17 },
{ "isStackable", SMALL_SCENERY_FLAG_STACKABLE },
{ "prohibitWalls", SMALL_SCENERY_FLAG_NO_WALLS },
{ "hasSecondaryColour", SMALL_SCENERY_FLAG_HAS_SECONDARY_COLOUR },
{ "hasNoSupports", SMALL_SCENERY_FLAG_NO_SUPPORTS },
{ "SMALL_SCENERY_FLAG_VISIBLE_WHEN_ZOOMED", SMALL_SCENERY_FLAG_VISIBLE_WHEN_ZOOMED },
{ "SMALL_SCENERY_FLAG_COG", SMALL_SCENERY_FLAG_COG },
{ "allowSupportsAbove", SMALL_SCENERY_FLAG_BUILD_DIRECTLY_ONTOP },
{ "supportsHavePrimaryColour", SMALL_SCENERY_FLAG_PAINT_SUPPORTS },
{ "SMALL_SCENERY_FLAG27", SMALL_SCENERY_FLAG27 },
{ "isTree", SMALL_SCENERY_FLAG_IS_TREE },
});
// Determine shape flags from a shape string
auto shape = ObjectJsonHelpers::GetString(properties, "shape");
if (!shape.empty())
if (properties.is_object())
{
auto quarters = shape.substr(0, 3);
if (quarters == "2/4")
{
_legacyType.small_scenery.flags |= SMALL_SCENERY_FLAG_FULL_TILE | SMALL_SCENERY_FLAG_HALF_SPACE;
}
else if (quarters == "3/4")
{
_legacyType.small_scenery.flags |= SMALL_SCENERY_FLAG_FULL_TILE | SMALL_SCENERY_FLAG_THREE_QUARTERS;
}
else if (quarters == "4/4")
{
_legacyType.small_scenery.flags |= SMALL_SCENERY_FLAG_FULL_TILE;
}
if (shape.size() >= 5)
{
if ((shape.substr(3) == "+D"))
_legacyType.small_scenery.height = Json::GetNumber<uint8_t>(properties["height"]);
_legacyType.small_scenery.tool_id = Cursor::FromString(Json::GetString(properties["cursor"]), CURSOR_STATUE_DOWN);
_legacyType.small_scenery.price = Json::GetNumber<uint16_t>(properties["price"]);
_legacyType.small_scenery.removal_price = Json::GetNumber<uint16_t>(properties["removalPrice"]);
_legacyType.small_scenery.animation_delay = Json::GetNumber<uint16_t>(properties["animationDelay"]);
_legacyType.small_scenery.animation_mask = Json::GetNumber<uint16_t>(properties["animationMask"]);
_legacyType.small_scenery.num_frames = Json::GetNumber<uint16_t>(properties["numFrames"]);
_legacyType.small_scenery.flags = Json::GetFlags<uint32_t>(
properties,
{
_legacyType.small_scenery.flags |= SMALL_SCENERY_FLAG_DIAGONAL;
{ "SMALL_SCENERY_FLAG_VOFFSET_CENTRE", SMALL_SCENERY_FLAG_VOFFSET_CENTRE },
{ "requiresFlatSurface", SMALL_SCENERY_FLAG_REQUIRE_FLAT_SURFACE },
{ "isRotatable", SMALL_SCENERY_FLAG_ROTATABLE },
{ "isAnimated", SMALL_SCENERY_FLAG_ANIMATED },
{ "canWither", SMALL_SCENERY_FLAG_CAN_WITHER },
{ "canBeWatered", SMALL_SCENERY_FLAG_CAN_BE_WATERED },
{ "hasOverlayImage", SMALL_SCENERY_FLAG_ANIMATED_FG },
{ "hasGlass", SMALL_SCENERY_FLAG_HAS_GLASS },
{ "hasPrimaryColour", SMALL_SCENERY_FLAG_HAS_PRIMARY_COLOUR },
{ "SMALL_SCENERY_FLAG_FOUNTAIN_SPRAY_1", SMALL_SCENERY_FLAG_FOUNTAIN_SPRAY_1 },
{ "SMALL_SCENERY_FLAG_FOUNTAIN_SPRAY_4", SMALL_SCENERY_FLAG_FOUNTAIN_SPRAY_4 },
{ "isClock", SMALL_SCENERY_FLAG_IS_CLOCK },
{ "SMALL_SCENERY_FLAG_SWAMP_GOO", SMALL_SCENERY_FLAG_SWAMP_GOO },
{ "SMALL_SCENERY_FLAG17", SMALL_SCENERY_FLAG17 },
{ "isStackable", SMALL_SCENERY_FLAG_STACKABLE },
{ "prohibitWalls", SMALL_SCENERY_FLAG_NO_WALLS },
{ "hasSecondaryColour", SMALL_SCENERY_FLAG_HAS_SECONDARY_COLOUR },
{ "hasNoSupports", SMALL_SCENERY_FLAG_NO_SUPPORTS },
{ "SMALL_SCENERY_FLAG_VISIBLE_WHEN_ZOOMED", SMALL_SCENERY_FLAG_VISIBLE_WHEN_ZOOMED },
{ "SMALL_SCENERY_FLAG_COG", SMALL_SCENERY_FLAG_COG },
{ "allowSupportsAbove", SMALL_SCENERY_FLAG_BUILD_DIRECTLY_ONTOP },
{ "supportsHavePrimaryColour", SMALL_SCENERY_FLAG_PAINT_SUPPORTS },
{ "SMALL_SCENERY_FLAG27", SMALL_SCENERY_FLAG27 },
{ "isTree", SMALL_SCENERY_FLAG_IS_TREE },
});
// Determine shape flags from a shape string
auto shape = Json::GetString(properties["shape"]);
if (!shape.empty())
{
auto quarters = shape.substr(0, 3);
if (quarters == "2/4")
{
_legacyType.small_scenery.flags |= SMALL_SCENERY_FLAG_FULL_TILE | SMALL_SCENERY_FLAG_HALF_SPACE;
}
else if (quarters == "3/4")
{
_legacyType.small_scenery.flags |= SMALL_SCENERY_FLAG_FULL_TILE | SMALL_SCENERY_FLAG_THREE_QUARTERS;
}
else if (quarters == "4/4")
{
_legacyType.small_scenery.flags |= SMALL_SCENERY_FLAG_FULL_TILE;
}
if (shape.size() >= 5)
{
if ((shape.substr(3) == "+D"))
{
_legacyType.small_scenery.flags |= SMALL_SCENERY_FLAG_DIAGONAL;
}
}
}
auto jFrameOffsets = properties["frameOffsets"];
if (jFrameOffsets.is_array())
{
_frameOffsets = ReadJsonFrameOffsets(jFrameOffsets);
_legacyType.small_scenery.flags |= SMALL_SCENERY_FLAG_HAS_FRAME_OFFSETS;
}
SetPrimarySceneryGroup(Json::GetString(properties["sceneryGroup"]));
}
auto jFrameOffsets = json_object_get(properties, "frameOffsets");
if (jFrameOffsets != nullptr)
{
_frameOffsets = ReadJsonFrameOffsets(jFrameOffsets);
_legacyType.small_scenery.flags |= SMALL_SCENERY_FLAG_HAS_FRAME_OFFSETS;
}
SetPrimarySceneryGroup(ObjectJsonHelpers::GetString(json_object_get(properties, "sceneryGroup")));
ObjectJsonHelpers::LoadStrings(root, GetStringTable());
ObjectJsonHelpers::LoadImages(context, root, GetImageTable());
PopulateTablesFromJson(context, root);
}
std::vector<uint8_t> SmallSceneryObject::ReadJsonFrameOffsets(const json_t* jFrameOffsets)
std::vector<uint8_t> SmallSceneryObject::ReadJsonFrameOffsets(json_t& jFrameOffsets)
{
std::vector<uint8_t> offsets;
size_t index;
const json_t* jOffset;
json_array_foreach(jFrameOffsets, index, jOffset)
for (auto& jOffset : jFrameOffsets)
{
offsets.push_back(json_integer_value(jOffset));
offsets.push_back(Json::GetNumber<uint8_t>(jOffset));
}
return offsets;
}

View File

@@ -32,7 +32,7 @@ public:
}
void ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) override;
void ReadJson(IReadObjectContext* context, const json_t* root) override;
void ReadJson(IReadObjectContext* context, json_t& root) override;
void Load() override;
void Unload() override;
@@ -40,7 +40,7 @@ public:
private:
static std::vector<uint8_t> ReadFrameOffsets(OpenRCT2::IStream* stream);
static std::vector<uint8_t> ReadJsonFrameOffsets(const json_t* jFrameOffsets);
static std::vector<uint8_t> ReadJsonFrameOffsets(json_t& jFrameOffsets);
void PerformFixes();
rct_object_entry GetScgPiratHeader();
rct_object_entry GetScgMineHeader();

View File

@@ -14,7 +14,6 @@
#include "../drawing/Drawing.h"
#include "../localisation/Localisation.h"
#include "../world/Banner.h"
#include "ObjectJsonHelpers.h"
void StationObject::Load()
{
@@ -78,17 +77,24 @@ void StationObject::DrawPreview(rct_drawpixelinfo* dpi, int32_t width, int32_t h
}
}
void StationObject::ReadJson(IReadObjectContext* context, const json_t* root)
void StationObject::ReadJson(IReadObjectContext* context, json_t& root)
{
auto properties = json_object_get(root, "properties");
Height = ObjectJsonHelpers::GetInteger(properties, "height", 0);
ScrollingMode = ObjectJsonHelpers::GetInteger(properties, "scrollingMode", SCROLLING_MODE_NONE);
Flags = ObjectJsonHelpers::GetFlags<uint32_t>(
properties,
{ { "hasPrimaryColour", STATION_OBJECT_FLAGS::HAS_PRIMARY_COLOUR },
{ "hasSecondaryColour", STATION_OBJECT_FLAGS::HAS_SECONDARY_COLOUR },
{ "isTransparent", STATION_OBJECT_FLAGS::IS_TRANSPARENT } });
Guard::Assert(root.is_object(), "StationObject::ReadJson expects parameter root to be object");
ObjectJsonHelpers::LoadStrings(root, GetStringTable());
ObjectJsonHelpers::LoadImages(context, root, GetImageTable());
auto properties = root["properties"];
if (properties.is_object())
{
Height = Json::GetNumber<int32_t>(properties["height"]);
ScrollingMode = Json::GetNumber<uint8_t>(properties["scrollingMode"], SCROLLING_MODE_NONE);
Flags = Json::GetFlags<uint32_t>(
properties,
{
{ "hasPrimaryColour", STATION_OBJECT_FLAGS::HAS_PRIMARY_COLOUR },
{ "hasSecondaryColour", STATION_OBJECT_FLAGS::HAS_SECONDARY_COLOUR },
{ "isTransparent", STATION_OBJECT_FLAGS::IS_TRANSPARENT },
});
}
PopulateTablesFromJson(context, root);
}

View File

@@ -33,7 +33,7 @@ public:
{
}
void ReadJson(IReadObjectContext* context, const json_t* root) override;
void ReadJson(IReadObjectContext* context, json_t& root) override;
void Load() override;
void Unload() override;

View File

@@ -78,6 +78,45 @@ void StringTable::Read(IReadObjectContext* context, OpenRCT2::IStream* stream, O
Sort();
}
ObjectStringID StringTable::ParseStringId(const std::string& s)
{
if (s == "name")
return ObjectStringID::NAME;
if (s == "description")
return ObjectStringID::DESCRIPTION;
if (s == "capacity")
return ObjectStringID::CAPACITY;
if (s == "vehicleName")
return ObjectStringID::VEHICLE_NAME;
return ObjectStringID::UNKNOWN;
}
void StringTable::ReadJson(json_t& root)
{
Guard::Assert(root.is_object(), "StringTable::ReadJson expects parameter root to be object");
// We trust the JSON type of root is object
auto jsonStrings = root["strings"];
for (auto& [key, jsonLanguages] : jsonStrings.items())
{
auto stringId = ParseStringId(key);
if (stringId != ObjectStringID::UNKNOWN)
{
for (auto& [locale, jsonString] : jsonLanguages.items())
{
auto langId = language_get_id_from_locale(locale.c_str());
if (langId != LANGUAGE_UNDEFINED)
{
auto string = Json::GetString(jsonString);
SetString(stringId, langId, string);
}
}
}
}
Sort();
}
std::string StringTable::GetString(ObjectStringID id) const
{
for (auto& string : _strings)

View File

@@ -10,6 +10,7 @@
#pragma once
#include "../common.h"
#include "../core/Json.hpp"
#include "../localisation/Language.h"
#include <string>
@@ -44,6 +45,7 @@ class StringTable
{
private:
std::vector<StringTableEntry> _strings;
static ObjectStringID ParseStringId(const std::string& s);
public:
StringTable() = default;
@@ -51,6 +53,10 @@ public:
StringTable& operator=(const StringTable&) = delete;
void Read(IReadObjectContext* context, OpenRCT2::IStream* stream, ObjectStringID id);
/**
* @note root is deliberately left non-const: json_t behaviour changes when const
*/
void ReadJson(json_t& root);
void Sort();
std::string GetString(ObjectStringID id) const;
std::string GetString(uint8_t language, ObjectStringID id) const;

View File

@@ -13,7 +13,6 @@
#include "../core/String.hpp"
#include "../drawing/Drawing.h"
#include "../localisation/Localisation.h"
#include "ObjectJsonHelpers.h"
void TerrainEdgeObject::Load()
{
@@ -44,12 +43,17 @@ void TerrainEdgeObject::DrawPreview(rct_drawpixelinfo* dpi, int32_t width, int32
gfx_draw_sprite(dpi, imageId + 5, screenCoords + ScreenCoordsXY{ 8, 8 }, 0);
}
void TerrainEdgeObject::ReadJson(IReadObjectContext* context, const json_t* root)
void TerrainEdgeObject::ReadJson(IReadObjectContext* context, json_t& root)
{
auto properties = json_object_get(root, "properties");
HasDoors = ObjectJsonHelpers::GetBoolean(properties, "hasDoors", false);
Guard::Assert(root.is_object(), "TerrainEdgeObject::ReadJson expects parameter root to be object");
ObjectJsonHelpers::LoadStrings(root, GetStringTable());
ObjectJsonHelpers::LoadImages(context, root, GetImageTable());
auto properties = root["properties"];
if (properties.is_object())
{
HasDoors = Json::GetBoolean(properties["hasDoors"]);
}
PopulateTablesFromJson(context, root);
NumImagesLoaded = GetImageTable().GetCount();
}

View File

@@ -26,7 +26,7 @@ public:
{
}
void ReadJson(IReadObjectContext* context, const json_t* root) override;
void ReadJson(IReadObjectContext* context, json_t& root) override;
void Load() override;
void Unload() override;

View File

@@ -16,7 +16,6 @@
#include "../drawing/Drawing.h"
#include "../localisation/Localisation.h"
#include "../world/Location.hpp"
#include "ObjectJsonHelpers.h"
void TerrainSurfaceObject::Load()
{
@@ -74,24 +73,45 @@ void TerrainSurfaceObject::DrawPreview(rct_drawpixelinfo* dpi, int32_t width, in
}
}
void TerrainSurfaceObject::ReadJson(IReadObjectContext* context, const json_t* root)
void TerrainSurfaceObject::ReadJson(IReadObjectContext* context, json_t& root)
{
auto properties = json_object_get(root, "properties");
Colour = ObjectJsonHelpers::ParseColour(ObjectJsonHelpers::GetString(properties, "colour"), 255);
Rotations = ObjectJsonHelpers::GetInteger(properties, "rotations", 1);
Price = ObjectJsonHelpers::GetInteger(properties, "price", 0);
Flags = ObjectJsonHelpers::GetFlags<TERRAIN_SURFACE_FLAGS>(
properties,
{ { "smoothWithSelf", TERRAIN_SURFACE_FLAGS::SMOOTH_WITH_SELF },
{ "smoothWithOther", TERRAIN_SURFACE_FLAGS::SMOOTH_WITH_OTHER },
{ "canGrow", TERRAIN_SURFACE_FLAGS::CAN_GROW } });
Guard::Assert(root.is_object(), "TerrainSurfaceObject::ReadJson expects parameter root to be object");
auto jDefault = json_object_get(root, "default");
if (json_is_object(jDefault))
auto properties = root["properties"];
if (properties.is_object())
{
DefaultEntry = ObjectJsonHelpers::GetInteger(properties, "normal");
DefaultGridEntry = ObjectJsonHelpers::GetInteger(properties, "grid");
DefaultUndergroundEntry = ObjectJsonHelpers::GetInteger(properties, "underground");
Colour = Colour::FromString(Json::GetString(properties["colour"]), 255);
Rotations = Json::GetNumber<int8_t>(properties["rotations"], 1);
Price = Json::GetNumber<money32>(properties["price"]);
Flags = Json::GetFlags<TERRAIN_SURFACE_FLAGS>(
properties,
{ { "smoothWithSelf", TERRAIN_SURFACE_FLAGS::SMOOTH_WITH_SELF },
{ "smoothWithOther", TERRAIN_SURFACE_FLAGS::SMOOTH_WITH_OTHER },
{ "canGrow", TERRAIN_SURFACE_FLAGS::CAN_GROW } });
for (auto& el : properties["special"])
{
if (el.is_object())
{
SpecialEntry entry;
entry.Index = Json::GetNumber<uint32_t>(el["index"]);
entry.Length = Json::GetNumber<int32_t>(el["length"], -1);
entry.Rotation = Json::GetNumber<int32_t>(el["rotation"], -1);
entry.Variation = Json::GetNumber<int32_t>(el["variation"], -1);
entry.Grid = Json::GetBoolean(el["grid"]);
entry.Underground = Json::GetBoolean(el["underground"]);
SpecialEntries.push_back(std::move(entry));
}
}
}
auto jDefault = root["default"];
if (jDefault.is_object())
{
DefaultEntry = Json::GetNumber<uint32_t>(jDefault["normal"]);
DefaultGridEntry = Json::GetNumber<uint32_t>(jDefault["grid"]);
DefaultUndergroundEntry = Json::GetNumber<uint32_t>(jDefault["underground"]);
}
else
{
@@ -100,26 +120,7 @@ void TerrainSurfaceObject::ReadJson(IReadObjectContext* context, const json_t* r
DefaultUndergroundEntry = 2;
}
auto jSpecialArray = json_object_get(properties, "special");
if (json_is_array(jSpecialArray))
{
size_t i;
json_t* el;
json_array_foreach(jSpecialArray, i, el)
{
SpecialEntry entry;
entry.Index = ObjectJsonHelpers::GetInteger(el, "index");
entry.Length = ObjectJsonHelpers::GetInteger(el, "length", -1);
entry.Rotation = ObjectJsonHelpers::GetInteger(el, "rotation", -1);
entry.Variation = ObjectJsonHelpers::GetInteger(el, "variation", -1);
entry.Grid = ObjectJsonHelpers::GetBoolean(el, "grid");
entry.Underground = ObjectJsonHelpers::GetBoolean(el, "underground");
SpecialEntries.push_back(std::move(entry));
}
}
ObjectJsonHelpers::LoadStrings(root, GetStringTable());
ObjectJsonHelpers::LoadImages(context, root, GetImageTable());
PopulateTablesFromJson(context, root);
}
uint32_t TerrainSurfaceObject::GetImageId(

View File

@@ -59,7 +59,7 @@ public:
{
}
void ReadJson(IReadObjectContext* context, const json_t* root) override;
void ReadJson(IReadObjectContext* context, json_t& root) override;
void Load() override;
void Unload() override;

View File

@@ -15,7 +15,6 @@
#include "../interface/Cursors.h"
#include "../localisation/Language.h"
#include "../world/Banner.h"
#include "ObjectJsonHelpers.h"
void WallObject::ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream)
{
@@ -94,66 +93,63 @@ void WallObject::DrawPreview(rct_drawpixelinfo* dpi, int32_t width, int32_t heig
}
}
void WallObject::ReadJson(IReadObjectContext* context, const json_t* root)
void WallObject::ReadJson(IReadObjectContext* context, json_t& root)
{
auto properties = json_object_get(root, "properties");
Guard::Assert(root.is_object(), "WallObject::ReadJson expects parameter root to be object");
_legacyType.wall.tool_id = ObjectJsonHelpers::ParseCursor(
ObjectJsonHelpers::GetString(properties, "cursor"), CURSOR_FENCE_DOWN);
_legacyType.wall.height = json_integer_value(json_object_get(properties, "height"));
_legacyType.wall.price = json_integer_value(json_object_get(properties, "price"));
auto properties = root["properties"];
auto jScrollingMode = json_object_get(properties, "scrollingMode");
_legacyType.wall.scrolling_mode = jScrollingMode != nullptr ? json_integer_value(jScrollingMode) : SCROLLING_MODE_NONE;
SetPrimarySceneryGroup(ObjectJsonHelpers::GetString(json_object_get(properties, "sceneryGroup")));
// Flags
_legacyType.wall.flags = ObjectJsonHelpers::GetFlags<uint8_t>(
properties,
{
{ "hasPrimaryColour", WALL_SCENERY_HAS_PRIMARY_COLOUR },
{ "hasSecondaryColour", WALL_SCENERY_HAS_SECONDARY_COLOUR },
{ "hasTernaryColour", WALL_SCENERY_HAS_TERNARY_COLOUR },
{ "hasGlass", WALL_SCENERY_HAS_GLASS },
{ "isBanner", WALL_SCENERY_IS_BANNER },
{ "isDoor", WALL_SCENERY_IS_DOOR },
{ "isLongDoorAnimation", WALL_SCENERY_LONG_DOOR_ANIMATION },
});
_legacyType.wall.flags2 = ObjectJsonHelpers::GetFlags<uint8_t>(
properties,
{
{ "isOpaque", WALL_SCENERY_2_IS_OPAQUE },
{ "isAnimated", WALL_SCENERY_2_ANIMATED },
});
// HACK To avoid 'negated' properties in JSON, handle this separately until
// flag is inverted in this code base.
if (!ObjectJsonHelpers::GetBoolean(properties, "isAllowedOnSlope", false))
if (properties.is_object())
{
_legacyType.wall.flags |= WALL_SCENERY_CANT_BUILD_ON_SLOPE;
}
_legacyType.wall.tool_id = Cursor::FromString(Json::GetString(properties["cursor"]), CURSOR_FENCE_DOWN);
_legacyType.wall.height = Json::GetNumber<uint8_t>(properties["height"]);
_legacyType.wall.price = Json::GetNumber<int16_t>(properties["price"]);
// HACK WALL_SCENERY_HAS_PRIMARY_COLOUR actually means, has any colour but we simplify the
// JSON and handle this on load. We should change code base in future to reflect the JSON.
if (!(_legacyType.wall.flags & WALL_SCENERY_HAS_PRIMARY_COLOUR))
{
if ((_legacyType.wall.flags & WALL_SCENERY_HAS_SECONDARY_COLOUR)
|| (_legacyType.wall.flags & WALL_SCENERY_HAS_TERNARY_COLOUR))
_legacyType.wall.scrolling_mode = Json::GetNumber<uint8_t>(properties["scrollingMode"], SCROLLING_MODE_NONE);
SetPrimarySceneryGroup(Json::GetString(properties["sceneryGroup"]));
// clang-format off
_legacyType.wall.flags = Json::GetFlags<uint8_t>(
properties,
{
{ "hasPrimaryColour", WALL_SCENERY_HAS_PRIMARY_COLOUR, Json::FlagType::Normal },
{ "IsAllowedOnSlope", WALL_SCENERY_CANT_BUILD_ON_SLOPE, Json::FlagType::Inverted },
{ "hasSecondaryColour", WALL_SCENERY_HAS_SECONDARY_COLOUR, Json::FlagType::Normal },
{ "hasTernaryColour", WALL_SCENERY_HAS_TERNARY_COLOUR, Json::FlagType::Normal },
{ "hasGlass", WALL_SCENERY_HAS_GLASS, Json::FlagType::Normal },
{ "isBanner", WALL_SCENERY_IS_BANNER, Json::FlagType::Normal },
{ "isDoor", WALL_SCENERY_IS_DOOR, Json::FlagType::Normal },
{ "isLongDoorAnimation", WALL_SCENERY_LONG_DOOR_ANIMATION, Json::FlagType::Normal },
});
// clang-format on
_legacyType.wall.flags2 = Json::GetFlags<uint8_t>(
properties,
{
{ "isOpaque", WALL_SCENERY_2_IS_OPAQUE },
{ "isAnimated", WALL_SCENERY_2_ANIMATED },
});
// HACK WALL_SCENERY_HAS_PRIMARY_COLOUR actually means, has any colour but we simplify the
// JSON and handle this on load. We should change code base in future to reflect the JSON.
if (!(_legacyType.wall.flags & WALL_SCENERY_HAS_PRIMARY_COLOUR))
{
_legacyType.wall.flags |= WALL_SCENERY_HAS_PRIMARY_COLOUR;
_legacyType.wall.flags2 |= WALL_SCENERY_2_NO_SELECT_PRIMARY_COLOUR;
if (_legacyType.wall.flags & (WALL_SCENERY_HAS_SECONDARY_COLOUR | WALL_SCENERY_HAS_TERNARY_COLOUR))
{
_legacyType.wall.flags |= WALL_SCENERY_HAS_PRIMARY_COLOUR;
_legacyType.wall.flags2 |= WALL_SCENERY_2_NO_SELECT_PRIMARY_COLOUR;
}
}
// Door sound
auto jDoorSound = properties["doorSound"];
if (jDoorSound.is_number())
{
auto doorSound = Json::GetNumber<uint8_t>(jDoorSound);
_legacyType.wall.flags2 |= (doorSound << WALL_SCENERY_2_DOOR_SOUND_SHIFT) & WALL_SCENERY_2_DOOR_SOUND_MASK;
}
}
// Door sound
auto jDoorSound = json_object_get(properties, "doorSound");
if (jDoorSound != nullptr)
{
auto doorSound = json_integer_value(jDoorSound);
_legacyType.wall.flags2 |= (doorSound << WALL_SCENERY_2_DOOR_SOUND_SHIFT) & WALL_SCENERY_2_DOOR_SOUND_MASK;
}
ObjectJsonHelpers::LoadStrings(root, GetStringTable());
ObjectJsonHelpers::LoadImages(context, root, GetImageTable());
PopulateTablesFromJson(context, root);
}

View File

@@ -29,7 +29,7 @@ public:
}
void ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) override;
void ReadJson(IReadObjectContext* context, const json_t* root) override;
void ReadJson(IReadObjectContext* context, json_t& root) override;
void Load() override;
void Unload() override;

View File

@@ -16,7 +16,6 @@
#include "../localisation/Language.h"
#include "../localisation/StringIds.h"
#include "../world/Location.hpp"
#include "ObjectJsonHelpers.h"
#include <memory>
@@ -55,52 +54,57 @@ void WaterObject::DrawPreview(rct_drawpixelinfo* dpi, int32_t width, int32_t hei
gfx_draw_string_centred(dpi, STR_WINDOW_NO_IMAGE, screenCoords, COLOUR_BLACK, nullptr);
}
void WaterObject::ReadJson([[maybe_unused]] IReadObjectContext* context, const json_t* root)
void WaterObject::ReadJson([[maybe_unused]] IReadObjectContext* context, json_t& root)
{
auto properties = json_object_get(root, "properties");
_legacyType.flags = ObjectJsonHelpers::GetFlags<uint16_t>(
properties,
{
{ "allowDucks", WATER_FLAGS_ALLOW_DUCKS },
});
Guard::Assert(root.is_object(), "WaterObject::ReadJson expects parameter root to be object");
ObjectJsonHelpers::LoadStrings(root, GetStringTable());
auto properties = root["properties"];
// Images which are actually palette data
static const char* paletteNames[] = {
"general", "waves-0", "waves-1", "waves-2", "sparkles-0", "sparkles-1", "sparkles-2",
};
for (auto paletteName : paletteNames)
PopulateTablesFromJson(context, root);
if (properties.is_object())
{
auto jPalettes = json_object_get(properties, "palettes");
if (jPalettes != nullptr)
{
auto jPalette = json_object_get(jPalettes, paletteName);
if (jPalette != nullptr)
_legacyType.flags = Json::GetFlags<uint16_t>(
properties,
{
ReadJsonPalette(jPalette);
{ "allowDucks", WATER_FLAGS_ALLOW_DUCKS },
});
auto jPalettes = properties["palettes"];
if (jPalettes.is_object())
{
// Images which are actually palette data
static const char* paletteNames[] = {
"general", "waves-0", "waves-1", "waves-2", "sparkles-0", "sparkles-1", "sparkles-2",
};
for (auto paletteName : paletteNames)
{
auto jPalette = jPalettes[paletteName];
if (jPalette.is_object())
{
ReadJsonPalette(jPalette);
}
}
}
}
}
void WaterObject::ReadJsonPalette(const json_t* jPalette)
void WaterObject::ReadJsonPalette(json_t& jPalette)
{
auto paletteStartIndex = json_integer_value(json_object_get(jPalette, "index"));
auto jColours = json_object_get(jPalette, "colours");
auto numColours = json_array_size(jColours);
Guard::Assert(jPalette.is_object(), "WaterObject::ReadJsonPalette expects parameter jPalette to be object");
auto jColours = jPalette["colours"];
auto numColours = jColours.size();
// This pointer gets memcopied in ImageTable::AddImage so it's fine for the unique_ptr to go out of scope
auto data = std::make_unique<uint8_t[]>(numColours * 3);
size_t dataIndex = 0;
size_t index;
const json_t* jColour;
json_array_foreach(jColours, index, jColour)
for (auto& jColour : jColours)
{
auto szColour = json_string_value(jColour);
if (szColour != nullptr)
if (jColour.is_string())
{
auto colour = ParseColour(szColour);
auto colour = ParseColour(Json::GetString(jColour));
data[dataIndex + 0] = (colour >> 16) & 0xFF;
data[dataIndex + 1] = (colour >> 8) & 0xFF;
data[dataIndex + 2] = colour & 0xFF;
@@ -111,7 +115,7 @@ void WaterObject::ReadJsonPalette(const json_t* jPalette)
rct_g1_element g1 = {};
g1.offset = data.get();
g1.width = static_cast<int16_t>(numColours);
g1.x_offset = static_cast<int16_t>(paletteStartIndex);
g1.x_offset = Json::GetNumber<int16_t>(jPalette["index"]);
g1.flags = G1_FLAG_PALETTE;
auto& imageTable = GetImageTable();

View File

@@ -30,7 +30,7 @@ public:
return &_legacyType;
}
void ReadJson(IReadObjectContext* context, const json_t* root) override;
void ReadJson(IReadObjectContext* context, json_t& root) override;
void ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) override;
void Load() override;
void Unload() override;
@@ -38,6 +38,6 @@ public:
void DrawPreview(rct_drawpixelinfo* dpi, int32_t width, int32_t height) const override;
private:
void ReadJsonPalette(const json_t* jPalette);
void ReadJsonPalette(json_t& jPalette);
uint32_t ParseColour(const std::string& s) const;
};