diff --git a/src/openrct2/interface/Colour.cpp b/src/openrct2/interface/Colour.cpp index 9ca0eb1607..5c37dea329 100644 --- a/src/openrct2/interface/Colour.cpp +++ b/src/openrct2/interface/Colour.cpp @@ -57,6 +57,49 @@ void colours_init_maps() } } +namespace Colour +{ + colour_t FromString(const std::string_view& s, colour_t defaultValue) + { + static const std::unordered_map LookupTable{ + { "black", COLOUR_BLACK }, + { "grey", COLOUR_GREY }, + { "white", COLOUR_WHITE }, + { "dark_purple", COLOUR_DARK_PURPLE }, + { "light_purple", COLOUR_LIGHT_PURPLE }, + { "bright_purple", COLOUR_BRIGHT_PURPLE }, + { "dark_blue", COLOUR_DARK_BLUE }, + { "light_blue", COLOUR_LIGHT_BLUE }, + { "icy_blue", COLOUR_ICY_BLUE }, + { "teal", COLOUR_TEAL }, + { "aquamarine", COLOUR_AQUAMARINE }, + { "saturated_green", COLOUR_SATURATED_GREEN }, + { "dark_green", COLOUR_DARK_GREEN }, + { "moss_green", COLOUR_MOSS_GREEN }, + { "bright_green", COLOUR_BRIGHT_GREEN }, + { "olive_green", COLOUR_OLIVE_GREEN }, + { "dark_olive_green", COLOUR_DARK_OLIVE_GREEN }, + { "bright_yellow", COLOUR_BRIGHT_YELLOW }, + { "yellow", COLOUR_YELLOW }, + { "dark_yellow", COLOUR_DARK_YELLOW }, + { "light_orange", COLOUR_LIGHT_ORANGE }, + { "dark_orange", COLOUR_DARK_ORANGE }, + { "light_brown", COLOUR_LIGHT_BROWN }, + { "saturated_brown", COLOUR_SATURATED_BROWN }, + { "dark_brown", COLOUR_DARK_BROWN }, + { "salmon_pink", COLOUR_SALMON_PINK }, + { "bordeaux_red", COLOUR_BORDEAUX_RED }, + { "saturated_red", COLOUR_SATURATED_RED }, + { "bright_red", COLOUR_BRIGHT_RED }, + { "dark_pink", COLOUR_DARK_PINK }, + { "bright_pink", COLOUR_BRIGHT_PINK }, + { "light_pink", COLOUR_LIGHT_PINK }, + }; + auto result = LookupTable.find(s); + return (result != LookupTable.end()) ? result->second : defaultValue; + } +} // namespace Colour + #ifndef NO_TTF static uint8_t BlendColourMap[PALETTE_COUNT][PALETTE_COUNT] = { 0 }; diff --git a/src/openrct2/interface/Colour.h b/src/openrct2/interface/Colour.h index a26abb2ea7..7abdfd961b 100644 --- a/src/openrct2/interface/Colour.h +++ b/src/openrct2/interface/Colour.h @@ -12,6 +12,8 @@ #include "../common.h" +#include + /** * Colour IDs as used by the colour dropdown, NOT palette indices. */ @@ -207,6 +209,11 @@ extern rct_colour_map ColourMapA[COLOUR_COUNT]; void colours_init_maps(); +namespace Colour +{ + colour_t FromString(const std::string_view& s, colour_t defaultValue = COLOUR_BLACK); +} + #ifndef NO_TTF uint8_t blendColours(const uint8_t paletteIndex1, const uint8_t paletteIndex2); #endif diff --git a/src/openrct2/interface/Cursors.cpp b/src/openrct2/interface/Cursors.cpp new file mode 100644 index 0000000000..c245b75907 --- /dev/null +++ b/src/openrct2/interface/Cursors.cpp @@ -0,0 +1,52 @@ +/***************************************************************************** + * Copyright (c) 2014-2020 OpenRCT2 developers + * + * For a complete list of all authors, please refer to contributors.md + * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is licensed under the GNU General Public License version 3. + *****************************************************************************/ + +#include "Cursors.h" + +#include +#include + +namespace Cursor +{ + uint8_t FromString(const std::string& s, uint8_t defaultValue) + { + static const std::unordered_map LookupTable{ + { "CURSOR_BLANK", CURSOR_BLANK }, + { "CURSOR_UP_ARROW", CURSOR_UP_ARROW }, + { "CURSOR_UP_DOWN_ARROW", CURSOR_UP_DOWN_ARROW }, + { "CURSOR_HAND_POINT", CURSOR_HAND_POINT }, + { "CURSOR_ZZZ", CURSOR_ZZZ }, + { "CURSOR_DIAGONAL_ARROWS", CURSOR_DIAGONAL_ARROWS }, + { "CURSOR_PICKER", CURSOR_PICKER }, + { "CURSOR_TREE_DOWN", CURSOR_TREE_DOWN }, + { "CURSOR_FOUNTAIN_DOWN", CURSOR_FOUNTAIN_DOWN }, + { "CURSOR_STATUE_DOWN", CURSOR_STATUE_DOWN }, + { "CURSOR_BENCH_DOWN", CURSOR_BENCH_DOWN }, + { "CURSOR_CROSS_HAIR", CURSOR_CROSS_HAIR }, + { "CURSOR_BIN_DOWN", CURSOR_BIN_DOWN }, + { "CURSOR_LAMPPOST_DOWN", CURSOR_LAMPPOST_DOWN }, + { "CURSOR_FENCE_DOWN", CURSOR_FENCE_DOWN }, + { "CURSOR_FLOWER_DOWN", CURSOR_FLOWER_DOWN }, + { "CURSOR_PATH_DOWN", CURSOR_PATH_DOWN }, + { "CURSOR_DIG_DOWN", CURSOR_DIG_DOWN }, + { "CURSOR_WATER_DOWN", CURSOR_WATER_DOWN }, + { "CURSOR_HOUSE_DOWN", CURSOR_HOUSE_DOWN }, + { "CURSOR_VOLCANO_DOWN", CURSOR_VOLCANO_DOWN }, + { "CURSOR_WALK_DOWN", CURSOR_WALK_DOWN }, + { "CURSOR_PAINT_DOWN", CURSOR_PAINT_DOWN }, + { "CURSOR_ENTRANCE_DOWN", CURSOR_ENTRANCE_DOWN }, + { "CURSOR_HAND_OPEN", CURSOR_HAND_OPEN }, + { "CURSOR_HAND_CLOSED", CURSOR_HAND_CLOSED }, + { "CURSOR_ARROW", CURSOR_ARROW }, + }; + + auto result = LookupTable.find(s); + return (result != LookupTable.end()) ? result->second : defaultValue; + } +} // namespace Cursor diff --git a/src/openrct2/interface/Cursors.h b/src/openrct2/interface/Cursors.h index e2e761102e..c85e8fac23 100644 --- a/src/openrct2/interface/Cursors.h +++ b/src/openrct2/interface/Cursors.h @@ -44,6 +44,11 @@ enum CURSOR_ID CURSOR_COUNT, }; +namespace Cursor +{ + uint8_t FromString(const std::string& s, uint8_t defaultValue); +} + namespace OpenRCT2::Ui { constexpr size_t CURSOR_BIT_WIDTH = 32; diff --git a/src/openrct2/libopenrct2.vcxproj b/src/openrct2/libopenrct2.vcxproj index dd2b045f3a..54a610e311 100644 --- a/src/openrct2/libopenrct2.vcxproj +++ b/src/openrct2/libopenrct2.vcxproj @@ -252,7 +252,6 @@ - @@ -528,6 +527,7 @@ + @@ -576,7 +576,6 @@ - diff --git a/src/openrct2/object/ImageTable.cpp b/src/openrct2/object/ImageTable.cpp index 0deabc25df..b755b97a71 100644 --- a/src/openrct2/object/ImageTable.cpp +++ b/src/openrct2/object/ImageTable.cpp @@ -9,14 +9,285 @@ #include "ImageTable.h" +#include "../Context.h" #include "../OpenRCT2.h" +#include "../PlatformEnvironment.h" +#include "../core/File.h" +#include "../core/FileScanner.h" #include "../core/IStream.hpp" +#include "../core/Path.hpp" +#include "../core/String.hpp" +#include "../drawing/ImageImporter.h" +#include "../sprites.h" #include "Object.h" +#include "ObjectFactory.h" #include #include #include +using namespace OpenRCT2; +using namespace OpenRCT2::Drawing; + +struct ImageTable::RequiredImage +{ + rct_g1_element g1{}; + std::unique_ptr next_zoom; + + bool HasData() const + { + return g1.offset != nullptr; + } + + RequiredImage() = default; + RequiredImage(const RequiredImage&) = delete; + + RequiredImage(const rct_g1_element& orig) + { + auto length = g1_calculate_data_size(&orig); + g1 = orig; + g1.offset = new uint8_t[length]; + std::memcpy(g1.offset, orig.offset, length); + g1.flags &= ~G1_FLAG_HAS_ZOOM_SPRITE; + } + + RequiredImage(uint32_t idx, std::function getter) + { + auto orig = getter(idx); + if (orig != nullptr) + { + auto length = g1_calculate_data_size(orig); + g1 = *orig; + g1.offset = new uint8_t[length]; + std::memcpy(g1.offset, orig->offset, length); + if ((g1.flags & G1_FLAG_HAS_ZOOM_SPRITE) && g1.zoomed_offset != 0) + { + // Fetch image for next zoom level + next_zoom = std::make_unique(static_cast(idx - g1.zoomed_offset), getter); + if (!next_zoom->HasData()) + { + next_zoom = nullptr; + g1.flags &= ~G1_FLAG_HAS_ZOOM_SPRITE; + } + } + } + } + + ~RequiredImage() + { + delete[] g1.offset; + } +}; + +std::vector> ImageTable::ParseImages(IReadObjectContext* context, std::string s) +{ + std::vector> result; + if (s.empty()) + { + result.push_back(std::make_unique()); + } + else if (String::StartsWith(s, "$CSG")) + { + if (is_csg_loaded()) + { + auto range = ParseRange(s.substr(4)); + if (!range.empty()) + { + for (auto i : range) + { + result.push_back(std::make_unique( + static_cast(SPR_CSG_BEGIN + i), + [](uint32_t idx) -> const rct_g1_element* { return gfx_get_g1_element(idx); })); + } + } + } + } + else if (String::StartsWith(s, "$G1")) + { + auto range = ParseRange(s.substr(3)); + if (!range.empty()) + { + for (auto i : range) + { + result.push_back(std::make_unique( + static_cast(i), [](uint32_t idx) -> const rct_g1_element* { return gfx_get_g1_element(idx); })); + } + } + } + else if (String::StartsWith(s, "$RCT2:OBJDATA/")) + { + auto name = s.substr(14); + auto rangeStart = name.find('['); + if (rangeStart != std::string::npos) + { + auto rangeString = name.substr(rangeStart); + auto range = ParseRange(name.substr(rangeStart)); + name = name.substr(0, rangeStart); + result = LoadObjectImages(context, name, range); + } + } + else + { + try + { + auto imageData = context->GetData(s); + auto image = Imaging::ReadFromBuffer(imageData, IMAGE_FORMAT::PNG_32); + + ImageImporter importer; + auto importResult = importer.Import(image, 0, 0, ImageImporter::IMPORT_FLAGS::RLE); + + result.push_back(std::make_unique(importResult.Element)); + } + catch (const std::exception& e) + { + auto msg = String::StdFormat("Unable to load image '%s': %s", s.c_str(), e.what()); + context->LogWarning(OBJECT_ERROR_BAD_IMAGE_TABLE, msg.c_str()); + result.push_back(std::make_unique()); + } + } + return result; +} + +std::vector> ImageTable::ParseImages(IReadObjectContext* context, json_t& el) +{ + Guard::Assert(el.is_object(), "ImageTable::ParseImages expects parameter el to be object"); + + auto path = Json::GetString(el["path"]); + auto x = Json::GetNumber(el["x"]); + auto y = Json::GetNumber(el["y"]); + auto raw = Json::GetString(el["format"]) == "raw"; + + std::vector> result; + try + { + auto flags = ImageImporter::IMPORT_FLAGS::NONE; + if (!raw) + { + flags = static_cast(flags | ImageImporter::IMPORT_FLAGS::RLE); + } + auto imageData = context->GetData(path); + auto image = Imaging::ReadFromBuffer(imageData, IMAGE_FORMAT::PNG_32); + + ImageImporter importer; + auto importResult = importer.Import(image, 0, 0, flags); + auto g1Element = importResult.Element; + g1Element.x_offset = x; + g1Element.y_offset = y; + result.push_back(std::make_unique(g1Element)); + } + catch (const std::exception& e) + { + auto msg = String::StdFormat("Unable to load image '%s': %s", path.c_str(), e.what()); + context->LogWarning(OBJECT_ERROR_BAD_IMAGE_TABLE, msg.c_str()); + result.push_back(std::make_unique()); + } + return result; +} + +std::vector> ImageTable::LoadObjectImages( + IReadObjectContext* context, const std::string& name, const std::vector& range) +{ + std::vector> result; + auto objectPath = FindLegacyObject(name); + auto obj = ObjectFactory::CreateObjectFromLegacyFile(context->GetObjectRepository(), objectPath.c_str()); + if (obj != nullptr) + { + auto& imgTable = static_cast(obj)->GetImageTable(); + auto numImages = static_cast(imgTable.GetCount()); + auto images = imgTable.GetImages(); + size_t placeHoldersAdded = 0; + for (auto i : range) + { + if (i >= 0 && i < numImages) + { + result.push_back(std::make_unique( + static_cast(i), [images](uint32_t idx) -> const rct_g1_element* { return &images[idx]; })); + } + else + { + result.push_back(std::make_unique()); + placeHoldersAdded++; + } + } + delete obj; + + // Log place holder information + if (placeHoldersAdded > 0) + { + std::string msg = "Adding " + std::to_string(placeHoldersAdded) + " placeholders"; + context->LogWarning(OBJECT_ERROR_INVALID_PROPERTY, msg.c_str()); + } + } + else + { + std::string msg = "Unable to open '" + objectPath + "'"; + context->LogWarning(OBJECT_ERROR_INVALID_PROPERTY, msg.c_str()); + for (size_t i = 0; i < range.size(); i++) + { + result.push_back(std::make_unique()); + } + } + return result; +} + +std::vector ImageTable::ParseRange(std::string s) +{ + // Currently only supports [###] or [###..###] + std::vector result = {}; + if (s.length() >= 3 && s[0] == '[' && s[s.length() - 1] == ']') + { + s = s.substr(1, s.length() - 2); + auto parts = String::Split(s, ".."); + if (parts.size() == 1) + { + result.push_back(std::stoi(parts[0])); + } + else + { + auto left = std::stoi(parts[0]); + auto right = std::stoi(parts[1]); + if (left <= right) + { + for (auto i = left; i <= right; i++) + { + result.push_back(i); + } + } + else + { + for (auto i = right; i >= left; i--) + { + result.push_back(i); + } + } + } + } + return result; +} + +std::string ImageTable::FindLegacyObject(const std::string& name) +{ + const auto env = GetContext()->GetPlatformEnvironment(); + auto objectsPath = env->GetDirectoryPath(DIRBASE::RCT2, DIRID::OBJECT); + auto objectPath = Path::Combine(objectsPath, name); + if (!File::Exists(objectPath)) + { + // Search recursively for any file with the target name (case insensitive) + auto filter = Path::Combine(objectsPath, "*.dat"); + auto scanner = std::unique_ptr(Path::ScanDirectory(filter, true)); + while (scanner->Next()) + { + auto currentName = Path::GetFileName(scanner->GetPathRelative()); + if (String::Equals(currentName, name, true)) + { + objectPath = scanner->GetPath(); + break; + } + } + } + return objectPath; +} + ImageTable::~ImageTable() { if (_data == nullptr) @@ -97,6 +368,70 @@ void ImageTable::Read(IReadObjectContext* context, OpenRCT2::IStream* stream) } } +void ImageTable::ReadJson(IReadObjectContext* context, json_t& root) +{ + Guard::Assert(root.is_object(), "ImageTable::ReadJson expects parameter root to be object"); + + if (context->ShouldLoadImages()) + { + // First gather all the required images from inspecting the JSON + std::vector> allImages; + auto jsonImages = root["images"]; + + for (auto& jsonImage : jsonImages) + { + if (jsonImage.is_string()) + { + auto strImage = jsonImage.get(); + auto images = ParseImages(context, strImage); + allImages.insert( + allImages.end(), std::make_move_iterator(images.begin()), std::make_move_iterator(images.end())); + } + else if (jsonImage.is_object()) + { + auto images = ParseImages(context, jsonImage); + allImages.insert( + allImages.end(), std::make_move_iterator(images.begin()), std::make_move_iterator(images.end())); + } + } + + // Now add all the images to the image table + auto imagesStartIndex = GetCount(); + for (const auto& img : allImages) + { + const auto& g1 = img->g1; + AddImage(&g1); + } + + // Add all the zoom images at the very end of the image table. + // This way it should not affect the offsets used within the object logic. + for (size_t j = 0; j < allImages.size(); j++) + { + const auto tableIndex = imagesStartIndex + j; + const auto* img = allImages[j].get(); + if (img->next_zoom != nullptr) + { + img = img->next_zoom.get(); + + // Set old image zoom offset to zoom image which we are about to add + auto g1a = const_cast(&GetImages()[tableIndex]); + g1a->zoomed_offset = static_cast(tableIndex) - static_cast(GetCount()); + + while (img != nullptr) + { + auto g1b = img->g1; + if (img->next_zoom != nullptr) + { + g1b.zoomed_offset = -1; + } + AddImage(&g1b); + img = img->next_zoom.get(); + } + } + } + } +} + void ImageTable::AddImage(const rct_g1_element* g1) { rct_g1_element newg1 = *g1; diff --git a/src/openrct2/object/ImageTable.h b/src/openrct2/object/ImageTable.h index a4f92efee7..7499a305ef 100644 --- a/src/openrct2/object/ImageTable.h +++ b/src/openrct2/object/ImageTable.h @@ -10,6 +10,7 @@ #pragma once #include "../common.h" +#include "../core/Json.hpp" #include "../drawing/Drawing.h" #include @@ -27,6 +28,20 @@ private: std::unique_ptr _data; std::vector _entries; + /** + * Container for a G1 image, additional information and RAII. Used by ReadJson + */ + struct RequiredImage; + static std::vector> ParseImages(IReadObjectContext* context, std::string s); + /** + * @note root is deliberately left non-const: json_t behaviour changes when const + */ + static std::vector> ParseImages(IReadObjectContext* context, json_t& el); + static std::vector> LoadObjectImages( + IReadObjectContext* context, const std::string& name, const std::vector& range); + static std::vector ParseRange(std::string s); + static std::string FindLegacyObject(const std::string& name); + public: ImageTable() = default; ImageTable(const ImageTable&) = delete; @@ -34,6 +49,10 @@ public: ~ImageTable(); void Read(IReadObjectContext* context, OpenRCT2::IStream* stream); + /** + * @note root is deliberately left non-const: json_t behaviour changes when const + */ + void ReadJson(IReadObjectContext* context, json_t& root); const rct_g1_element* GetImages() const { return _entries.data(); diff --git a/src/openrct2/object/Object.cpp b/src/openrct2/object/Object.cpp index 7d57fc80e5..8aac7bbd42 100644 --- a/src/openrct2/object/Object.cpp +++ b/src/openrct2/object/Object.cpp @@ -37,6 +37,21 @@ void Object::ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) throw std::runtime_error("Not supported."); } +void Object::PopulateTablesFromJson(IReadObjectContext* context, json_t& root) +{ + _stringTable.ReadJson(root); + _imageTable.ReadJson(context, root); +} + +rct_object_entry Object::ParseObjectEntry(const std::string& s) +{ + rct_object_entry entry = {}; + std::fill_n(entry.name, sizeof(entry.name), ' '); + auto copyLen = std::min(8, s.size()); + std::copy_n(s.c_str(), copyLen, entry.name); + return entry; +} + std::string Object::GetOverrideString(uint8_t index) const { auto legacyIdentifier = GetLegacyIdentifier(); diff --git a/src/openrct2/object/Object.h b/src/openrct2/object/Object.h index f7db713dcb..767929f681 100644 --- a/src/openrct2/object/Object.h +++ b/src/openrct2/object/Object.h @@ -10,6 +10,7 @@ #pragma once #include "../common.h" +#include "../core/Json.hpp" #include "ImageTable.h" #include "StringTable.h" @@ -141,7 +142,6 @@ namespace OpenRCT2 } struct ObjectRepositoryItem; struct rct_drawpixelinfo; -struct json_t; struct IReadObjectContext { @@ -186,6 +186,16 @@ protected: return _imageTable; } + /** + * Populates the image and string tables from a JSON object + * @param context + * @param root JSON node of type object containing image and string info + * @note root is deliberately left non-const: json_t behaviour changes when const + */ + void PopulateTablesFromJson(IReadObjectContext* context, json_t& root); + + static rct_object_entry ParseObjectEntry(const std::string& s); + std::string GetOverrideString(uint8_t index) const; std::string GetString(ObjectStringID index) const; std::string GetString(int32_t language, ObjectStringID index) const; @@ -224,7 +234,10 @@ public: } virtual void* GetLegacyData(); - virtual void ReadJson(IReadObjectContext* /*context*/, const json_t* /*root*/) + /** + * @note root is deliberately left non-const: json_t behaviour changes when const + */ + virtual void ReadJson(IReadObjectContext* /*context*/, json_t& /*root*/) { } virtual void ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream); diff --git a/src/openrct2/object/ObjectJsonHelpers.cpp b/src/openrct2/object/ObjectJsonHelpers.cpp deleted file mode 100644 index 57867cf17d..0000000000 --- a/src/openrct2/object/ObjectJsonHelpers.cpp +++ /dev/null @@ -1,563 +0,0 @@ -/***************************************************************************** - * Copyright (c) 2014-2020 OpenRCT2 developers - * - * For a complete list of all authors, please refer to contributors.md - * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 - * - * OpenRCT2 is licensed under the GNU General Public License version 3. - *****************************************************************************/ - -#pragma warning(disable : 4706) // assignment within conditional expression - -#include "ObjectJsonHelpers.h" - -#include "../Context.h" -#include "../PlatformEnvironment.h" -#include "../core/File.h" -#include "../core/FileScanner.h" -#include "../core/Memory.hpp" -#include "../core/Path.hpp" -#include "../core/String.hpp" -#include "../drawing/ImageImporter.h" -#include "../interface/Cursors.h" -#include "../localisation/Language.h" -#include "../sprites.h" -#include "Object.h" -#include "ObjectFactory.h" - -#include -#include -#include -#include - -using namespace OpenRCT2; -using namespace OpenRCT2::Drawing; - -namespace ObjectJsonHelpers -{ - /** - * Container for a G1 image, additional information and RAII. - */ - struct RequiredImage - { - rct_g1_element g1{}; - std::unique_ptr next_zoom; - - bool HasData() const - { - return g1.offset != nullptr; - } - - RequiredImage() = default; - RequiredImage(const RequiredImage&) = delete; - - RequiredImage(const rct_g1_element& orig) - { - auto length = g1_calculate_data_size(&orig); - g1 = orig; - g1.offset = new uint8_t[length]; - std::memcpy(g1.offset, orig.offset, length); - g1.flags &= ~G1_FLAG_HAS_ZOOM_SPRITE; - } - - RequiredImage(uint32_t idx, std::function getter) - { - auto orig = getter(idx); - if (orig != nullptr) - { - auto length = g1_calculate_data_size(orig); - g1 = *orig; - g1.offset = new uint8_t[length]; - std::memcpy(g1.offset, orig->offset, length); - if ((g1.flags & G1_FLAG_HAS_ZOOM_SPRITE) && g1.zoomed_offset != 0) - { - // Fetch image for next zoom level - next_zoom = std::make_unique(static_cast(idx - g1.zoomed_offset), getter); - if (!next_zoom->HasData()) - { - next_zoom = nullptr; - g1.flags &= ~G1_FLAG_HAS_ZOOM_SPRITE; - } - } - } - } - - ~RequiredImage() - { - delete[] g1.offset; - } - }; - - bool GetBoolean(const json_t* obj, const std::string& name, bool defaultValue) - { - auto value = json_object_get(obj, name.c_str()); - return json_is_boolean(value) ? json_boolean_value(value) : defaultValue; - } - - std::string GetString(const json_t* value) - { - return json_is_string(value) ? std::string(json_string_value(value)) : std::string(); - } - - std::string GetString(const json_t* obj, const std::string& name, const std::string& defaultValue) - { - auto value = json_object_get(obj, name.c_str()); - return json_is_string(value) ? json_string_value(value) : defaultValue; - } - - int32_t GetInteger(const json_t* obj, const std::string& name, const int32_t& defaultValue) - { - auto value = json_object_get(obj, name.c_str()); - if (json_is_integer(value)) - { - int64_t val = json_integer_value(value); - if (val >= std::numeric_limits::min() && val <= std::numeric_limits::max()) - { - return static_cast(val); - } - } - return defaultValue; - } - - float GetFloat(const json_t* obj, const std::string& name, const float& defaultValue) - { - auto value = json_object_get(obj, name.c_str()); - return json_is_number(value) ? json_number_value(value) : defaultValue; - } - - std::vector GetJsonStringArray(const json_t* arr) - { - std::vector result; - if (json_is_array(arr)) - { - auto count = json_array_size(arr); - result.reserve(count); - for (size_t i = 0; i < count; i++) - { - auto element = json_string_value(json_array_get(arr, i)); - result.push_back(element); - } - } - else if (json_is_string(arr)) - { - result.push_back(json_string_value(arr)); - } - return result; - } - - std::vector GetJsonIntegerArray(const json_t* arr) - { - std::vector result; - if (json_is_array(arr)) - { - auto count = json_array_size(arr); - result.reserve(count); - for (size_t i = 0; i < count; i++) - { - auto element = json_integer_value(json_array_get(arr, i)); - result.push_back(element); - } - } - else if (json_is_integer(arr)) - { - result.push_back(json_integer_value(arr)); - } - return result; - } - - colour_t ParseColour(const std::string_view& s, colour_t defaultValue) - { - static const std::unordered_map LookupTable{ - { "black", COLOUR_BLACK }, - { "grey", COLOUR_GREY }, - { "white", COLOUR_WHITE }, - { "dark_purple", COLOUR_DARK_PURPLE }, - { "light_purple", COLOUR_LIGHT_PURPLE }, - { "bright_purple", COLOUR_BRIGHT_PURPLE }, - { "dark_blue", COLOUR_DARK_BLUE }, - { "light_blue", COLOUR_LIGHT_BLUE }, - { "icy_blue", COLOUR_ICY_BLUE }, - { "teal", COLOUR_TEAL }, - { "aquamarine", COLOUR_AQUAMARINE }, - { "saturated_green", COLOUR_SATURATED_GREEN }, - { "dark_green", COLOUR_DARK_GREEN }, - { "moss_green", COLOUR_MOSS_GREEN }, - { "bright_green", COLOUR_BRIGHT_GREEN }, - { "olive_green", COLOUR_OLIVE_GREEN }, - { "dark_olive_green", COLOUR_DARK_OLIVE_GREEN }, - { "bright_yellow", COLOUR_BRIGHT_YELLOW }, - { "yellow", COLOUR_YELLOW }, - { "dark_yellow", COLOUR_DARK_YELLOW }, - { "light_orange", COLOUR_LIGHT_ORANGE }, - { "dark_orange", COLOUR_DARK_ORANGE }, - { "light_brown", COLOUR_LIGHT_BROWN }, - { "saturated_brown", COLOUR_SATURATED_BROWN }, - { "dark_brown", COLOUR_DARK_BROWN }, - { "salmon_pink", COLOUR_SALMON_PINK }, - { "bordeaux_red", COLOUR_BORDEAUX_RED }, - { "saturated_red", COLOUR_SATURATED_RED }, - { "bright_red", COLOUR_BRIGHT_RED }, - { "dark_pink", COLOUR_DARK_PINK }, - { "bright_pink", COLOUR_BRIGHT_PINK }, - { "light_pink", COLOUR_LIGHT_PINK }, - }; - auto result = LookupTable.find(s); - return (result != LookupTable.end()) ? result->second : defaultValue; - } - - uint8_t ParseCursor(const std::string& s, uint8_t defaultValue) - { - static const std::unordered_map LookupTable{ - { "CURSOR_BLANK", CURSOR_BLANK }, - { "CURSOR_UP_ARROW", CURSOR_UP_ARROW }, - { "CURSOR_UP_DOWN_ARROW", CURSOR_UP_DOWN_ARROW }, - { "CURSOR_HAND_POINT", CURSOR_HAND_POINT }, - { "CURSOR_ZZZ", CURSOR_ZZZ }, - { "CURSOR_DIAGONAL_ARROWS", CURSOR_DIAGONAL_ARROWS }, - { "CURSOR_PICKER", CURSOR_PICKER }, - { "CURSOR_TREE_DOWN", CURSOR_TREE_DOWN }, - { "CURSOR_FOUNTAIN_DOWN", CURSOR_FOUNTAIN_DOWN }, - { "CURSOR_STATUE_DOWN", CURSOR_STATUE_DOWN }, - { "CURSOR_BENCH_DOWN", CURSOR_BENCH_DOWN }, - { "CURSOR_CROSS_HAIR", CURSOR_CROSS_HAIR }, - { "CURSOR_BIN_DOWN", CURSOR_BIN_DOWN }, - { "CURSOR_LAMPPOST_DOWN", CURSOR_LAMPPOST_DOWN }, - { "CURSOR_FENCE_DOWN", CURSOR_FENCE_DOWN }, - { "CURSOR_FLOWER_DOWN", CURSOR_FLOWER_DOWN }, - { "CURSOR_PATH_DOWN", CURSOR_PATH_DOWN }, - { "CURSOR_DIG_DOWN", CURSOR_DIG_DOWN }, - { "CURSOR_WATER_DOWN", CURSOR_WATER_DOWN }, - { "CURSOR_HOUSE_DOWN", CURSOR_HOUSE_DOWN }, - { "CURSOR_VOLCANO_DOWN", CURSOR_VOLCANO_DOWN }, - { "CURSOR_WALK_DOWN", CURSOR_WALK_DOWN }, - { "CURSOR_PAINT_DOWN", CURSOR_PAINT_DOWN }, - { "CURSOR_ENTRANCE_DOWN", CURSOR_ENTRANCE_DOWN }, - { "CURSOR_HAND_OPEN", CURSOR_HAND_OPEN }, - { "CURSOR_HAND_CLOSED", CURSOR_HAND_CLOSED }, - { "CURSOR_ARROW", CURSOR_ARROW }, - }; - - auto result = LookupTable.find(s); - return (result != LookupTable.end()) ? result->second : defaultValue; - } - - rct_object_entry ParseObjectEntry(const std::string& s) - { - rct_object_entry entry = {}; - std::fill_n(entry.name, sizeof(entry.name), ' '); - auto copyLen = std::min(8, s.size()); - std::copy_n(s.c_str(), copyLen, entry.name); - return entry; - } - - static std::vector ParseRange(std::string s) - { - // Currently only supports [###] or [###..###] - std::vector result = {}; - if (s.length() >= 3 && s[0] == '[' && s[s.length() - 1] == ']') - { - s = s.substr(1, s.length() - 2); - auto parts = String::Split(s, ".."); - if (parts.size() == 1) - { - result.push_back(std::stoi(parts[0])); - } - else - { - auto left = std::stoi(parts[0]); - auto right = std::stoi(parts[1]); - if (left <= right) - { - for (auto i = left; i <= right; i++) - { - result.push_back(i); - } - } - else - { - for (auto i = right; i >= left; i--) - { - result.push_back(i); - } - } - } - } - return result; - } - - static std::string FindLegacyObject(const std::string& name) - { - const auto env = GetContext()->GetPlatformEnvironment(); - auto objectsPath = env->GetDirectoryPath(DIRBASE::RCT2, DIRID::OBJECT); - auto objectPath = Path::Combine(objectsPath, name); - if (!File::Exists(objectPath)) - { - // Search recursively for any file with the target name (case insensitive) - auto filter = Path::Combine(objectsPath, "*.dat"); - auto scanner = std::unique_ptr(Path::ScanDirectory(filter, true)); - while (scanner->Next()) - { - auto currentName = Path::GetFileName(scanner->GetPathRelative()); - if (String::Equals(currentName, name, true)) - { - objectPath = scanner->GetPath(); - break; - } - } - } - return objectPath; - } - - static std::vector> LoadObjectImages( - IReadObjectContext* context, const std::string& name, const std::vector& range) - { - std::vector> result; - auto objectPath = FindLegacyObject(name); - auto obj = ObjectFactory::CreateObjectFromLegacyFile(context->GetObjectRepository(), objectPath.c_str()); - if (obj != nullptr) - { - auto& imgTable = static_cast(obj)->GetImageTable(); - auto numImages = static_cast(imgTable.GetCount()); - auto images = imgTable.GetImages(); - size_t placeHoldersAdded = 0; - for (auto i : range) - { - if (i >= 0 && i < numImages) - { - result.push_back(std::make_unique( - static_cast(i), [images](uint32_t idx) -> const rct_g1_element* { return &images[idx]; })); - } - else - { - result.push_back(std::make_unique()); - placeHoldersAdded++; - } - } - delete obj; - - // Log place holder information - if (placeHoldersAdded > 0) - { - std::string msg = "Adding " + std::to_string(placeHoldersAdded) + " placeholders"; - context->LogWarning(OBJECT_ERROR_INVALID_PROPERTY, msg.c_str()); - } - } - else - { - std::string msg = "Unable to open '" + objectPath + "'"; - context->LogWarning(OBJECT_ERROR_INVALID_PROPERTY, msg.c_str()); - for (size_t i = 0; i < range.size(); i++) - { - result.push_back(std::make_unique()); - } - } - return result; - } - - static std::vector> ParseImages(IReadObjectContext* context, std::string s) - { - std::vector> result; - if (s.empty()) - { - result.push_back(std::make_unique()); - } - else if (String::StartsWith(s, "$CSG")) - { - if (is_csg_loaded()) - { - auto range = ParseRange(s.substr(4)); - if (!range.empty()) - { - for (auto i : range) - { - result.push_back(std::make_unique( - static_cast(SPR_CSG_BEGIN + i), - [](uint32_t idx) -> const rct_g1_element* { return gfx_get_g1_element(idx); })); - } - } - } - } - else if (String::StartsWith(s, "$G1")) - { - auto range = ParseRange(s.substr(3)); - if (!range.empty()) - { - for (auto i : range) - { - result.push_back( - std::make_unique(static_cast(i), [](uint32_t idx) -> const rct_g1_element* { - return gfx_get_g1_element(idx); - })); - } - } - } - else if (String::StartsWith(s, "$RCT2:OBJDATA/")) - { - auto name = s.substr(14); - auto rangeStart = name.find('['); - if (rangeStart != std::string::npos) - { - auto rangeString = name.substr(rangeStart); - auto range = ParseRange(name.substr(rangeStart)); - name = name.substr(0, rangeStart); - result = LoadObjectImages(context, name, range); - } - } - else - { - try - { - auto imageData = context->GetData(s); - auto image = Imaging::ReadFromBuffer(imageData, IMAGE_FORMAT::PNG_32); - - ImageImporter importer; - auto importResult = importer.Import(image, 0, 0, ImageImporter::IMPORT_FLAGS::RLE); - - result.push_back(std::make_unique(importResult.Element)); - } - catch (const std::exception& e) - { - auto msg = String::StdFormat("Unable to load image '%s': %s", s.c_str(), e.what()); - context->LogWarning(OBJECT_ERROR_BAD_IMAGE_TABLE, msg.c_str()); - result.push_back(std::make_unique()); - } - } - return result; - } - - static std::vector> ParseImages(IReadObjectContext* context, json_t* el) - { - auto path = GetString(el, "path"); - auto x = GetInteger(el, "x"); - auto y = GetInteger(el, "y"); - auto raw = (GetString(el, "format") == "raw"); - - std::vector> result; - try - { - auto flags = ImageImporter::IMPORT_FLAGS::NONE; - if (!raw) - { - flags = static_cast(flags | ImageImporter::IMPORT_FLAGS::RLE); - } - auto imageData = context->GetData(path); - auto image = Imaging::ReadFromBuffer(imageData, IMAGE_FORMAT::PNG_32); - - ImageImporter importer; - auto importResult = importer.Import(image, 0, 0, flags); - auto g1Element = importResult.Element; - g1Element.x_offset = x; - g1Element.y_offset = y; - result.push_back(std::make_unique(g1Element)); - } - catch (const std::exception& e) - { - auto msg = String::StdFormat("Unable to load image '%s': %s", path.c_str(), e.what()); - context->LogWarning(OBJECT_ERROR_BAD_IMAGE_TABLE, msg.c_str()); - result.push_back(std::make_unique()); - } - return result; - } - - static ObjectStringID ParseStringId(const std::string& s) - { - if (s == "name") - return ObjectStringID::NAME; - if (s == "description") - return ObjectStringID::DESCRIPTION; - if (s == "capacity") - return ObjectStringID::CAPACITY; - if (s == "vehicleName") - return ObjectStringID::VEHICLE_NAME; - return ObjectStringID::UNKNOWN; - } - - void LoadStrings(const json_t* root, StringTable& stringTable) - { - auto jsonStrings = json_object_get(root, "strings"); - const char* key; - json_t* jlanguages; - json_object_foreach(jsonStrings, key, jlanguages) - { - auto stringId = ParseStringId(key); - if (stringId != ObjectStringID::UNKNOWN) - { - const char* locale; - json_t* jstring; - json_object_foreach(jlanguages, locale, jstring) - { - auto langId = language_get_id_from_locale(locale); - if (langId != LANGUAGE_UNDEFINED) - { - auto string = json_string_value(jstring); - stringTable.SetString(stringId, langId, string); - } - } - } - } - stringTable.Sort(); - } - - void LoadImages(IReadObjectContext* context, const json_t* root, ImageTable& imageTable) - { - if (context->ShouldLoadImages()) - { - // First gather all the required images from inspecting the JSON - std::vector> allImages; - auto jsonImages = json_object_get(root, "images"); - size_t i; - json_t* el; - json_array_foreach(jsonImages, i, el) - { - if (json_is_string(el)) - { - auto s = json_string_value(el); - auto images = ParseImages(context, s); - allImages.insert( - allImages.end(), std::make_move_iterator(images.begin()), std::make_move_iterator(images.end())); - } - else if (json_is_object(el)) - { - auto images = ParseImages(context, el); - allImages.insert( - allImages.end(), std::make_move_iterator(images.begin()), std::make_move_iterator(images.end())); - } - } - - // Now add all the images to the image table - auto imagesStartIndex = imageTable.GetCount(); - for (const auto& img : allImages) - { - const auto& g1 = img->g1; - imageTable.AddImage(&g1); - } - - // Add all the zoom images at the very end of the image table. - // This way it should not affect the offsets used within the object logic. - for (size_t j = 0; j < allImages.size(); j++) - { - const auto tableIndex = imagesStartIndex + j; - const auto* img = allImages[j].get(); - if (img->next_zoom != nullptr) - { - img = img->next_zoom.get(); - - // Set old image zoom offset to zoom image which we are about to add - auto g1a = const_cast(&imageTable.GetImages()[tableIndex]); - g1a->zoomed_offset = static_cast(tableIndex) - static_cast(imageTable.GetCount()); - - while (img != nullptr) - { - auto g1b = img->g1; - if (img->next_zoom != nullptr) - { - g1b.zoomed_offset = -1; - } - imageTable.AddImage(&g1b); - img = img->next_zoom.get(); - } - } - } - } - } -} // namespace ObjectJsonHelpers diff --git a/src/openrct2/object/ObjectJsonHelpers.h b/src/openrct2/object/ObjectJsonHelpers.h deleted file mode 100644 index a8e2f5dbd3..0000000000 --- a/src/openrct2/object/ObjectJsonHelpers.h +++ /dev/null @@ -1,52 +0,0 @@ -/***************************************************************************** - * Copyright (c) 2014-2020 OpenRCT2 developers - * - * For a complete list of all authors, please refer to contributors.md - * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 - * - * OpenRCT2 is licensed under the GNU General Public License version 3. - *****************************************************************************/ - -#pragma once - -#include "../common.h" -#include "../core/Json.hpp" -#include "../drawing/Drawing.h" -#include "../interface/Colour.h" -#include "../object/Object.h" -#include "ImageTable.h" -#include "StringTable.h" - -#include -#include -#include -#include - -namespace ObjectJsonHelpers -{ - bool GetBoolean(const json_t* obj, const std::string& name, bool defaultValue = false); - std::string GetString(const json_t* value); - std::string GetString(const json_t* obj, const std::string& name, const std::string& defaultValue = ""); - int32_t GetInteger(const json_t* obj, const std::string& name, const int32_t& defaultValue = 0); - float GetFloat(const json_t* obj, const std::string& name, const float& defaultValue = 0); - std::vector GetJsonStringArray(const json_t* arr); - std::vector GetJsonIntegerArray(const json_t* arr); - colour_t ParseColour(const std::string_view& s, colour_t defaultValue = COLOUR_BLACK); - uint8_t ParseCursor(const std::string& s, uint8_t defaultValue); - rct_object_entry ParseObjectEntry(const std::string& s); - void LoadStrings(const json_t* root, StringTable& stringTable); - void LoadImages(IReadObjectContext* context, const json_t* root, ImageTable& imageTable); - - template static T GetFlags(const json_t* obj, std::initializer_list> list) - { - T flags{}; - for (const auto& item : list) - { - if (GetBoolean(obj, item.first)) - { - flags = static_cast(flags | item.second); - } - } - return flags; - } -} // namespace ObjectJsonHelpers diff --git a/src/openrct2/object/StringTable.cpp b/src/openrct2/object/StringTable.cpp index 02eb7abc90..1994cd9f1f 100644 --- a/src/openrct2/object/StringTable.cpp +++ b/src/openrct2/object/StringTable.cpp @@ -78,6 +78,45 @@ void StringTable::Read(IReadObjectContext* context, OpenRCT2::IStream* stream, O Sort(); } +ObjectStringID StringTable::ParseStringId(const std::string& s) +{ + if (s == "name") + return ObjectStringID::NAME; + if (s == "description") + return ObjectStringID::DESCRIPTION; + if (s == "capacity") + return ObjectStringID::CAPACITY; + if (s == "vehicleName") + return ObjectStringID::VEHICLE_NAME; + return ObjectStringID::UNKNOWN; +} + +void StringTable::ReadJson(json_t& root) +{ + Guard::Assert(root.is_object(), "StringTable::ReadJson expects parameter root to be object"); + + // We trust the JSON type of root is object + auto jsonStrings = root["strings"]; + + for (auto& [key, jsonLanguages] : jsonStrings.items()) + { + auto stringId = ParseStringId(key); + if (stringId != ObjectStringID::UNKNOWN) + { + for (auto& [locale, jsonString] : jsonLanguages.items()) + { + auto langId = language_get_id_from_locale(locale.c_str()); + if (langId != LANGUAGE_UNDEFINED) + { + auto string = Json::GetString(jsonString); + SetString(stringId, langId, string); + } + } + } + } + Sort(); +} + std::string StringTable::GetString(ObjectStringID id) const { for (auto& string : _strings) diff --git a/src/openrct2/object/StringTable.h b/src/openrct2/object/StringTable.h index a6f4a2b124..9f75b3a0ed 100644 --- a/src/openrct2/object/StringTable.h +++ b/src/openrct2/object/StringTable.h @@ -10,6 +10,7 @@ #pragma once #include "../common.h" +#include "../core/Json.hpp" #include "../localisation/Language.h" #include @@ -44,6 +45,7 @@ class StringTable { private: std::vector _strings; + static ObjectStringID ParseStringId(const std::string& s); public: StringTable() = default; @@ -51,6 +53,10 @@ public: StringTable& operator=(const StringTable&) = delete; void Read(IReadObjectContext* context, OpenRCT2::IStream* stream, ObjectStringID id); + /** + * @note root is deliberately left non-const: json_t behaviour changes when const + */ + void ReadJson(json_t& root); void Sort(); std::string GetString(ObjectStringID id) const; std::string GetString(uint8_t language, ObjectStringID id) const;