/***************************************************************************** * 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 "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 #include void LargeSceneryObject::ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) { stream->Seek(6, OpenRCT2::STREAM_SEEK_CURRENT); _legacyType.large_scenery.tool_id = static_cast(stream->ReadValue()); _legacyType.large_scenery.flags = stream->ReadValue(); _legacyType.large_scenery.price = stream->ReadValue(); _legacyType.large_scenery.removal_price = stream->ReadValue(); stream->Seek(5, OpenRCT2::STREAM_SEEK_CURRENT); _legacyType.large_scenery.scenery_tab_id = OBJECT_ENTRY_INDEX_NULL; _legacyType.large_scenery.scrolling_mode = stream->ReadValue(); stream->Seek(4, OpenRCT2::STREAM_SEEK_CURRENT); GetStringTable().Read(context, stream, ObjectStringID::NAME); rct_object_entry sgEntry = stream->ReadValue(); SetPrimarySceneryGroup(&sgEntry); if (_legacyType.large_scenery.flags & LARGE_SCENERY_FLAG_3D_TEXT) { _3dFont = std::make_unique(); stream->Read(_3dFont.get()); _legacyType.large_scenery.text = _3dFont.get(); } _tiles = ReadTiles(stream); GetImageTable().Read(context, stream); // Validate properties if (_legacyType.large_scenery.price <= 0) { context->LogError(ObjectError::InvalidProperty, "Price can not be free or negative."); } if (_legacyType.large_scenery.removal_price <= 0) { // Make sure you don't make a profit when placing then removing. money16 reimbursement = _legacyType.large_scenery.removal_price; if (reimbursement > _legacyType.large_scenery.price) { context->LogError(ObjectError::InvalidProperty, "Sell price can not be more than buy price."); } } } void LargeSceneryObject::Load() { GetStringTable().Sort(); _legacyType.name = language_allocate_object_string(GetName()); _baseImageId = gfx_object_allocate_images(GetImageTable().GetImages(), GetImageTable().GetCount()); _legacyType.image = _baseImageId; _legacyType.large_scenery.tiles = _tiles.data(); if (_legacyType.large_scenery.flags & LARGE_SCENERY_FLAG_3D_TEXT) { _legacyType.large_scenery.text_image = _legacyType.image; if (_3dFont->flags & LARGE_SCENERY_TEXT_FLAG_VERTICAL) { _legacyType.image += _3dFont->num_images * 2; } else { _legacyType.image += _3dFont->num_images * 4; } _legacyType.large_scenery.text = _3dFont.get(); } } void LargeSceneryObject::Unload() { language_free_object_string(_legacyType.name); gfx_object_free_images(_baseImageId, GetImageTable().GetCount()); _legacyType.name = 0; _legacyType.image = 0; } void LargeSceneryObject::DrawPreview(rct_drawpixelinfo* dpi, int32_t width, int32_t height) const { auto screenCoords = ScreenCoordsXY{ width / 2, (height / 2) - 39 }; uint32_t imageId = 0xB2D00000 | _legacyType.image; gfx_draw_sprite(dpi, imageId, screenCoords, 0); } std::vector LargeSceneryObject::ReadTiles(OpenRCT2::IStream* stream) { auto tiles = std::vector(); while (stream->ReadValue() != 0xFFFF) { stream->Seek(-2, OpenRCT2::STREAM_SEEK_CURRENT); auto tile = stream->ReadValue(); tiles.push_back(tile); } tiles.push_back({ -1, -1, -1, 255, 0xFFFF }); return tiles; } void LargeSceneryObject::ReadJson(IReadObjectContext* context, json_t& root) { Guard::Assert(root.is_object(), "LargeSceneryObject::ReadJson expects parameter root to be object"); auto properties = root["properties"]; if (properties.is_object()) { _legacyType.large_scenery.tool_id = Cursor::FromString(Json::GetString(properties["cursor"]), CursorID::StatueDown); _legacyType.large_scenery.price = Json::GetNumber(properties["price"]); _legacyType.large_scenery.removal_price = Json::GetNumber(properties["removalPrice"]); _legacyType.large_scenery.scrolling_mode = Json::GetNumber(properties["scrollingMode"], SCROLLING_MODE_NONE); _legacyType.large_scenery.flags = Json::GetFlags( properties, { { "hasPrimaryColour", LARGE_SCENERY_FLAG_HAS_PRIMARY_COLOUR }, { "hasSecondaryColour", LARGE_SCENERY_FLAG_HAS_SECONDARY_COLOUR }, { "isAnimated", LARGE_SCENERY_FLAG_ANIMATED }, { "isPhotogenic", LARGE_SCENERY_FLAG_PHOTOGENIC }, }); // Tiles auto jTiles = properties["tiles"]; if (jTiles.is_array()) { _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"])); } PopulateTablesFromJson(context, root); } std::vector LargeSceneryObject::ReadJsonTiles(json_t& jTiles) { std::vector tiles; for (auto& jTile : jTiles) { if (jTile.is_object()) { rct_large_scenery_tile tile = {}; tile.x_offset = Json::GetNumber(jTile["x"]); tile.y_offset = Json::GetNumber(jTile["y"]); tile.z_offset = Json::GetNumber(jTile["z"]); tile.z_clearance = Json::GetNumber(jTile["clearance"]); // clang-format off tile.flags = Json::GetFlags( jTile, { {"hasSupports", LARGE_SCENERY_TILE_FLAG_NO_SUPPORTS, Json::FlagType::Inverted}, {"allowSupportsAbove", LARGE_SCENERY_TILE_FLAG_ALLOW_SUPPORTS_ABOVE, Json::FlagType::Normal} }); // clang-format on // All corners are by default occupied uint16_t corners = Json::GetNumber(jTile["corners"], 0xF); tile.flags |= (corners & 0xFF) << 12; auto walls = Json::GetNumber(jTile["walls"]); tile.flags |= (walls & 0xFF) << 8; tiles.push_back(tile); } } // HACK Add end of tiles marker // We should remove this later by improving the code base to use tiles array length tiles.push_back({ -1, -1, -1, 0xFF, 0xFFFF }); return tiles; } std::unique_ptr LargeSceneryObject::ReadJson3dFont(json_t& j3dFont) { Guard::Assert(j3dFont.is_object(), "LargeSceneryObject::ReadJson3dFont expects parameter j3dFont to be object"); auto font = std::make_unique(); 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::GetNumber(j3dFont["maxWidth"]); font->num_images = Json::GetNumber(j3dFont["numImages"]); font->flags = Json::GetFlags( j3dFont, { { "isVertical", LARGE_SCENERY_TEXT_FLAG_VERTICAL }, { "isTwoLine", LARGE_SCENERY_TEXT_FLAG_TWO_LINE }, }); auto jGlyphs = j3dFont["glyphs"]; if (jGlyphs.is_array()) { auto glyphs = ReadJsonGlyphs(jGlyphs); auto numGlyphs = std::min(std::size(font->glyphs), glyphs.size()); std::copy_n(glyphs.data(), numGlyphs, font->glyphs); } return font; } std::vector LargeSceneryObject::ReadJsonOffsets(json_t& jOffsets) { std::vector offsets; for (auto& jOffset : jOffsets) { if (jOffset.is_object()) { LocationXY16 offset = {}; offset.x = Json::GetNumber(jOffset["x"]); offset.y = Json::GetNumber(jOffset["y"]); offsets.push_back(offset); } } return offsets; } std::vector LargeSceneryObject::ReadJsonGlyphs(json_t& jGlyphs) { std::vector glyphs; for (auto& jGlyph : jGlyphs) { if (jGlyph.is_object()) { rct_large_scenery_text_glyph glyph = {}; glyph.image_offset = Json::GetNumber(jGlyph["image"]); glyph.width = Json::GetNumber(jGlyph["width"]); glyph.height = Json::GetNumber(jGlyph["height"]); glyphs.push_back(glyph); } } return glyphs; }