diff --git a/distribution/changelog.txt b/distribution/changelog.txt index a51c10f4f850..d878a66582f9 100644 --- a/distribution/changelog.txt +++ b/distribution/changelog.txt @@ -1,6 +1,7 @@ 0.4.11 (in development) ------------------------------------------------------------------------ - Feature: [#11512] Coloured usernames by group on multiplayer servers. +- Feature: [#15750] Allow using different types of park entrance in one park. - Feature: [#21734] Park admittance price can now be set via text input. - Improved: [#21769] Expose “animation is backwards” wall property in Tile Inspector. - Improved: [#21855] Add a separator between “Load Game” and “Save Game”, to avoid accidental overwriting. diff --git a/src/openrct2-ui/windows/Map.cpp b/src/openrct2-ui/windows/Map.cpp index 56c22bb2ad36..9dd8f7763598 100644 --- a/src/openrct2-ui/windows/Map.cpp +++ b/src/openrct2-ui/windows/Map.cpp @@ -8,6 +8,7 @@ *****************************************************************************/ #include +#include #include #include #include @@ -29,6 +30,8 @@ #include #include #include +#include +#include #include #include #include @@ -194,6 +197,8 @@ static constexpr ScreenCoordsXY MiniMapOffsets[] = { std::vector _mapImageData; bool _mapWidthAndHeightLinked{ true }; bool _recalculateScrollbars = false; + std::vector _entranceTypeDropdown{}; + ObjectEntryIndex _selectedEntranceType = 0; enum class ResizeDirection { Both, @@ -237,6 +242,8 @@ static constexpr ScreenCoordsXY MiniMapOffsets[] = { // Reset land rights tool size _landRightsToolSize = 1; + + InitParkEntranceDropdownItems(); } void OnClose() override @@ -301,16 +308,7 @@ static constexpr ScreenCoordsXY MiniMapOffsets[] = { Invalidate(); break; case WIDX_BUILD_PARK_ENTRANCE: - Invalidate(); - if (ToolSet(*this, widgetIndex, Tool::UpArrow)) - break; - - gParkEntranceGhostExists = false; - InputSetFlag(INPUT_FLAG_6, true); - - ShowGridlines(); - ShowLandRights(); - ShowConstructionRights(); + BuildParkEntranceToggle(); break; case WIDX_ROTATE_90: gWindowSceneryRotation = (gWindowSceneryRotation + 1) & 3; @@ -382,6 +380,9 @@ static constexpr ScreenCoordsXY MiniMapOffsets[] = { Invalidate(); break; + case WIDX_BUILD_PARK_ENTRANCE: + ShowParkEntranceDropdown(widgets[widgetIndex]); + break; } } @@ -585,7 +586,7 @@ static constexpr ScreenCoordsXY MiniMapOffsets[] = { ParkEntranceRemoveGhost(); - auto gameAction = ParkEntrancePlaceAction(parkEntrancePosition, gFootpathSelectedId); + auto gameAction = ParkEntrancePlaceAction(parkEntrancePosition, gFootpathSelectedId, _selectedEntranceType); gameAction.SetFlags(GAME_COMMAND_FLAG_GHOST); auto result = GameActions::Execute(&gameAction); @@ -603,7 +604,7 @@ static constexpr ScreenCoordsXY MiniMapOffsets[] = { CoordsXYZD parkEntrancePosition = PlaceParkEntranceGetMapPosition(screenCoords); if (!parkEntrancePosition.IsNull()) { - auto gameAction = ParkEntrancePlaceAction(parkEntrancePosition, gFootpathSelectedId); + auto gameAction = ParkEntrancePlaceAction(parkEntrancePosition, gFootpathSelectedId, _selectedEntranceType); auto result = GameActions::Execute(&gameAction); if (result.Error == GameActions::Status::Ok) { @@ -991,6 +992,22 @@ static constexpr ScreenCoordsXY MiniMapOffsets[] = { CentreMapOnViewPoint(); } + void OnDropdown(WidgetIndex widgetIndex, int32_t selectedIndex) override + { + if (selectedIndex == -1) + { + return; + } + + switch (widgetIndex) + { + case WIDX_BUILD_PARK_ENTRANCE: + BuildParkEntranceToggle(); + BuildParkEntranceClick(selectedIndex); + break; + } + } + private: void InitMap() { @@ -1494,6 +1511,56 @@ static constexpr ScreenCoordsXY MiniMapOffsets[] = { width = std::max(min_width, width); _recalculateScrollbars = true; } + + void InitParkEntranceDropdownItems() + { + _entranceTypeDropdown.clear(); + for (auto objectIndex = 0; objectIndex < OBJECT_ENTRY_INDEX_NULL; objectIndex++) + { + auto& objManager = GetContext()->GetObjectManager(); + auto* object = static_cast(objManager.GetLoadedObject(ObjectType::ParkEntrance, objectIndex)); + if (object != nullptr) + { + const auto* legacyData = reinterpret_cast(object->GetLegacyData()); + _entranceTypeDropdown.push_back({ .Format = legacyData->string_idx, .Args = objectIndex, .Flags = 0 }); + } + } + } + + void ShowParkEntranceDropdown(Widget& widget) + { + gDropdownDefaultIndex = 0; + auto numItems = _entranceTypeDropdown.size(); + for (auto dropdownIndex = 0u; dropdownIndex < numItems; dropdownIndex++) + { + gDropdownItems[dropdownIndex] = _entranceTypeDropdown[dropdownIndex]; + + if (gDropdownItems[dropdownIndex].Args == _selectedEntranceType) + gDropdownDefaultIndex = dropdownIndex; + } + + WindowDropdownShowText( + { windowPos.x + widget.left, windowPos.y + widget.top }, widget.height() + 1, colours[1] | 0x80, 0, numItems); + } + + void BuildParkEntranceToggle() + { + Invalidate(); + if (ToolSet(*this, WIDX_BUILD_PARK_ENTRANCE, Tool::UpArrow)) + return; + + gParkEntranceGhostExists = false; + InputSetFlag(INPUT_FLAG_6, true); + + ShowGridlines(); + ShowLandRights(); + ShowConstructionRights(); + } + + void BuildParkEntranceClick(int16_t dropdownIndex) + { + _selectedEntranceType = gDropdownItems[dropdownIndex].Args; + } }; WindowBase* MapOpen() diff --git a/src/openrct2/EditorObjectSelectionSession.cpp b/src/openrct2/EditorObjectSelectionSession.cpp index 869084f58783..23efc4ebd458 100644 --- a/src/openrct2/EditorObjectSelectionSession.cpp +++ b/src/openrct2/EditorObjectSelectionSession.cpp @@ -192,7 +192,8 @@ void SetupInUseSelectionFlags() if (parkEntranceEl->GetEntranceType() != ENTRANCE_TYPE_PARK_ENTRANCE) break; - Editor::SetSelectedObject(ObjectType::ParkEntrance, 0, ObjectSelectionFlags::InUse); + type = iter.element->AsEntrance()->getEntryIndex(); + Editor::SetSelectedObject(ObjectType::ParkEntrance, type, ObjectSelectionFlags::InUse); // Skip if not the middle part if (parkEntranceEl->GetSequenceIndex() != 0) @@ -687,9 +688,8 @@ int32_t EditorRemoveUnusedObjects() if (ObjectTypeIsIntransient(objectType)) continue; - // These object types require exactly one object to be selected at all times. - // Removing that object can badly break the game state. - if (objectType == ObjectType::ParkEntrance || objectType == ObjectType::Water) + // The water type controls the entire palette. Removing that object can badly break the game state. + if (objectType == ObjectType::Water) continue; // It’s hard to determine exactly if a scenery group is used, so do not remove these automatically. diff --git a/src/openrct2/actions/ParkEntrancePlaceAction.cpp b/src/openrct2/actions/ParkEntrancePlaceAction.cpp index 8cfdf87d27d5..fe9ec2795de5 100644 --- a/src/openrct2/actions/ParkEntrancePlaceAction.cpp +++ b/src/openrct2/actions/ParkEntrancePlaceAction.cpp @@ -24,9 +24,11 @@ using namespace OpenRCT2; -ParkEntrancePlaceAction::ParkEntrancePlaceAction(const CoordsXYZD& location, ObjectEntryIndex pathType) +ParkEntrancePlaceAction::ParkEntrancePlaceAction( + const CoordsXYZD& location, ObjectEntryIndex pathType, ObjectEntryIndex entranceType) : _loc(location) , _pathType(pathType) + , _entranceType(entranceType) { } @@ -34,6 +36,7 @@ void ParkEntrancePlaceAction::AcceptParameters(GameActionParameterVisitor& visit { visitor.Visit(_loc); visitor.Visit("footpathSurfaceObject", _pathType); + visitor.Visit("entranceObject", _entranceType); } uint16_t ParkEntrancePlaceAction::GetActionFlags() const @@ -47,6 +50,7 @@ void ParkEntrancePlaceAction::Serialise(DataSerialiser& stream) stream << DS_TAG(_loc); stream << DS_TAG(_pathType); + stream << DS_TAG(_entranceType); } GameActions::Result ParkEntrancePlaceAction::Query() const @@ -156,6 +160,7 @@ GameActions::Result ParkEntrancePlaceAction::Execute() const entranceElement->SetDirection(_loc.direction); entranceElement->SetSequenceIndex(index); entranceElement->SetEntranceType(ENTRANCE_TYPE_PARK_ENTRANCE); + entranceElement->setEntryIndex(_entranceType); if (gFootpathSelection.LegacyPath == OBJECT_ENTRY_INDEX_NULL) { entranceElement->SetSurfaceEntryIndex(gFootpathSelection.NormalSurface); diff --git a/src/openrct2/actions/ParkEntrancePlaceAction.h b/src/openrct2/actions/ParkEntrancePlaceAction.h index 6afed853b59d..06f8ce397dd0 100644 --- a/src/openrct2/actions/ParkEntrancePlaceAction.h +++ b/src/openrct2/actions/ParkEntrancePlaceAction.h @@ -16,10 +16,11 @@ class ParkEntrancePlaceAction final : public GameActionBase + diff --git a/src/openrct2/object/ObjectLimits.h b/src/openrct2/object/ObjectLimits.h index 7ee211c21bdc..563d54a3c386 100644 --- a/src/openrct2/object/ObjectLimits.h +++ b/src/openrct2/object/ObjectLimits.h @@ -21,7 +21,7 @@ constexpr uint16_t MAX_BANNER_OBJECTS = 255; constexpr uint16_t MAX_PATH_OBJECTS = 255; constexpr uint16_t MAX_PATH_ADDITION_OBJECTS = 255; constexpr uint16_t MAX_SCENERY_GROUP_OBJECTS = 255; -constexpr uint16_t MAX_PARK_ENTRANCE_OBJECTS = 1; +constexpr uint16_t MAX_PARK_ENTRANCE_OBJECTS = 4; constexpr uint16_t MAX_WATER_OBJECTS = 1; constexpr uint16_t MAX_SCENARIO_TEXT_OBJECTS = 0; constexpr uint16_t MAX_TERRAIN_SURFACE_OBJECTS = 255; diff --git a/src/openrct2/paint/tile_element/Paint.Entrance.cpp b/src/openrct2/paint/tile_element/Paint.Entrance.cpp index 40a3ac289bbc..14dfa7251b04 100644 --- a/src/openrct2/paint/tile_element/Paint.Entrance.cpp +++ b/src/openrct2/paint/tile_element/Paint.Entrance.cpp @@ -284,7 +284,8 @@ static void PaintParkEntrance(PaintSession& session, uint8_t direction, int32_t } auto& objManager = GetContext()->GetObjectManager(); - auto entrance = reinterpret_cast(objManager.GetLoadedObject(ObjectType::ParkEntrance, 0)); + auto entrance = reinterpret_cast( + objManager.GetLoadedObject(ObjectType::ParkEntrance, entranceEl.getEntryIndex())); auto sequence = entranceEl.GetSequenceIndex(); switch (sequence) { diff --git a/src/openrct2/world/Entrance.cpp b/src/openrct2/world/Entrance.cpp index a05a8e55b50f..956d3f40679f 100644 --- a/src/openrct2/world/Entrance.cpp +++ b/src/openrct2/world/Entrance.cpp @@ -238,107 +238,3 @@ void ParkEntranceUpdateLocations() } } } - -StationIndex EntranceElement::GetStationIndex() const -{ - return stationIndex; -} - -void EntranceElement::SetStationIndex(StationIndex newStationIndex) -{ - stationIndex = newStationIndex; -} - -uint8_t EntranceElement::GetEntranceType() const -{ - return entranceType; -} - -void EntranceElement::SetEntranceType(uint8_t newType) -{ - entranceType = newType; -} - -RideId EntranceElement::GetRideIndex() const -{ - return rideIndex; -} - -void EntranceElement::SetRideIndex(RideId newRideIndex) -{ - rideIndex = newRideIndex; -} - -uint8_t EntranceElement::GetSequenceIndex() const -{ - return SequenceIndex & 0xF; -} - -void EntranceElement::SetSequenceIndex(uint8_t newSequenceIndex) -{ - SequenceIndex &= ~0xF; - SequenceIndex |= (newSequenceIndex & 0xF); -} - -bool EntranceElement::HasLegacyPathEntry() const -{ - return (flags2 & ENTRANCE_ELEMENT_FLAGS2_LEGACY_PATH_ENTRY) != 0; -} - -ObjectEntryIndex EntranceElement::GetLegacyPathEntryIndex() const -{ - if (HasLegacyPathEntry()) - return PathType; - - return OBJECT_ENTRY_INDEX_NULL; -} - -const FootpathObject* EntranceElement::GetLegacyPathEntry() const -{ - auto& objMgr = OpenRCT2::GetContext()->GetObjectManager(); - return static_cast(objMgr.GetLoadedObject(ObjectType::Paths, GetLegacyPathEntryIndex())); -} - -void EntranceElement::SetLegacyPathEntryIndex(ObjectEntryIndex newPathType) -{ - PathType = newPathType; - flags2 |= ENTRANCE_ELEMENT_FLAGS2_LEGACY_PATH_ENTRY; -} - -ObjectEntryIndex EntranceElement::GetSurfaceEntryIndex() const -{ - if (HasLegacyPathEntry()) - return OBJECT_ENTRY_INDEX_NULL; - - return PathType; -} - -const FootpathSurfaceObject* EntranceElement::GetSurfaceEntry() const -{ - auto& objMgr = OpenRCT2::GetContext()->GetObjectManager(); - return static_cast(objMgr.GetLoadedObject(ObjectType::FootpathSurface, GetSurfaceEntryIndex())); -} - -void EntranceElement::SetSurfaceEntryIndex(ObjectEntryIndex newIndex) -{ - PathType = newIndex; - flags2 &= ~ENTRANCE_ELEMENT_FLAGS2_LEGACY_PATH_ENTRY; -} - -const PathSurfaceDescriptor* EntranceElement::GetPathSurfaceDescriptor() const -{ - if (HasLegacyPathEntry()) - { - const auto* legacyPathEntry = GetLegacyPathEntry(); - if (legacyPathEntry == nullptr) - return nullptr; - - return &legacyPathEntry->GetPathSurfaceDescriptor(); - } - - const auto* surfaceEntry = GetSurfaceEntry(); - if (surfaceEntry == nullptr) - return nullptr; - - return &surfaceEntry->GetDescriptor(); -} diff --git a/src/openrct2/world/Footpath.cpp b/src/openrct2/world/Footpath.cpp index 69f02d856023..986babae6fda 100644 --- a/src/openrct2/world/Footpath.cpp +++ b/src/openrct2/world/Footpath.cpp @@ -83,13 +83,6 @@ const std::array DirectionOffsets = { { 0, -1 }, }; -// rct2: 0x0097B974 -static constexpr uint16_t EntranceDirections[] = { - (4), 0, 0, 0, 0, 0, 0, 0, // ENTRANCE_TYPE_RIDE_ENTRANCE, - (4), 0, 0, 0, 0, 0, 0, 0, // ENTRANCE_TYPE_RIDE_EXIT, - (4 | 1), 0, 0, 0, 0, 0, 0, 0, // ENTRANCE_TYPE_PARK_ENTRANCE -}; - /** rct2: 0x0098D7F0 */ static constexpr uint8_t connected_path_count[] = { 0, // 0b0000 @@ -110,11 +103,6 @@ static constexpr uint8_t connected_path_count[] = { 4, // 0b1111 }; -int32_t EntranceElement::GetDirections() const -{ - return EntranceDirections[(GetEntranceType() * 8) + GetSequenceIndex()]; -} - static bool entrance_has_direction(const EntranceElement& entranceElement, int32_t direction) { return entranceElement.GetDirections() & (1 << (direction & 3)); diff --git a/src/openrct2/world/TileElement.h b/src/openrct2/world/TileElement.h index 52ce7c4a2221..56cd8caa94b1 100644 --- a/src/openrct2/world/TileElement.h +++ b/src/openrct2/world/TileElement.h @@ -553,15 +553,16 @@ struct EntranceElement : TileElementBase static constexpr TileElementType ElementType = TileElementType::Entrance; private: - uint8_t entranceType; // 5 - uint8_t SequenceIndex; // 6. Only uses the lower nibble. - StationIndex stationIndex; // 7 - ObjectEntryIndex PathType; // 8 - RideId rideIndex; // A - uint8_t flags2; // C + uint8_t entranceType; // 5 + uint8_t SequenceIndex; // 6. Only uses the lower nibble. + StationIndex stationIndex; // 7 + ObjectEntryIndex PathType; // 8 + RideId rideIndex; // A + uint8_t flags2; // C + ObjectEntryIndex entryIndex; // D #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-private-field" - uint8_t Pad0D[3]; + uint8_t Pad0F[1]; #pragma clang diagnostic pop public: @@ -590,6 +591,9 @@ struct EntranceElement : TileElementBase const PathSurfaceDescriptor* GetPathSurfaceDescriptor() const; int32_t GetDirections() const; + + ObjectEntryIndex getEntryIndex() const; + void setEntryIndex(ObjectEntryIndex newIndex); }; assert_struct_size(EntranceElement, 16); diff --git a/src/openrct2/world/tile_element/Entrance.cpp b/src/openrct2/world/tile_element/Entrance.cpp new file mode 100644 index 000000000000..6bdf5fb07bc3 --- /dev/null +++ b/src/openrct2/world/tile_element/Entrance.cpp @@ -0,0 +1,143 @@ +/***************************************************************************** + * Copyright (c) 2014-2024 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 "../Entrance.h" + +#include "../../Context.h" +#include "../../object/EntranceObject.h" +#include "../../object/FootpathObject.h" +#include "../../object/FootpathSurfaceObject.h" +#include "../../object/ObjectManager.h" +#include "../TileElement.h" + +// rct2: 0x0097B974 +static constexpr uint16_t EntranceDirections[] = { + (4), 0, 0, 0, 0, 0, 0, 0, // ENTRANCE_TYPE_RIDE_ENTRANCE, + (4), 0, 0, 0, 0, 0, 0, 0, // ENTRANCE_TYPE_RIDE_EXIT, + (4 | 1), 0, 0, 0, 0, 0, 0, 0, // ENTRANCE_TYPE_PARK_ENTRANCE +}; + +uint8_t EntranceElement::GetEntranceType() const +{ + return entranceType; +} + +void EntranceElement::SetEntranceType(uint8_t newType) +{ + entranceType = newType; +} + +RideId EntranceElement::GetRideIndex() const +{ + return rideIndex; +} + +void EntranceElement::SetRideIndex(RideId newRideIndex) +{ + rideIndex = newRideIndex; +} + +StationIndex EntranceElement::GetStationIndex() const +{ + return stationIndex; +} + +void EntranceElement::SetStationIndex(StationIndex newStationIndex) +{ + stationIndex = newStationIndex; +} + +uint8_t EntranceElement::GetSequenceIndex() const +{ + return SequenceIndex & 0xF; +} + +void EntranceElement::SetSequenceIndex(uint8_t newSequenceIndex) +{ + SequenceIndex &= ~0xF; + SequenceIndex |= (newSequenceIndex & 0xF); +} + +bool EntranceElement::HasLegacyPathEntry() const +{ + return (flags2 & ENTRANCE_ELEMENT_FLAGS2_LEGACY_PATH_ENTRY) != 0; +} + +ObjectEntryIndex EntranceElement::GetLegacyPathEntryIndex() const +{ + if (HasLegacyPathEntry()) + return PathType; + + return OBJECT_ENTRY_INDEX_NULL; +} + +const FootpathObject* EntranceElement::GetLegacyPathEntry() const +{ + auto& objMgr = OpenRCT2::GetContext()->GetObjectManager(); + return static_cast(objMgr.GetLoadedObject(ObjectType::Paths, GetLegacyPathEntryIndex())); +} + +void EntranceElement::SetLegacyPathEntryIndex(ObjectEntryIndex newPathType) +{ + PathType = newPathType; + flags2 |= ENTRANCE_ELEMENT_FLAGS2_LEGACY_PATH_ENTRY; +} + +ObjectEntryIndex EntranceElement::GetSurfaceEntryIndex() const +{ + if (HasLegacyPathEntry()) + return OBJECT_ENTRY_INDEX_NULL; + + return PathType; +} + +const FootpathSurfaceObject* EntranceElement::GetSurfaceEntry() const +{ + auto& objMgr = OpenRCT2::GetContext()->GetObjectManager(); + return static_cast(objMgr.GetLoadedObject(ObjectType::FootpathSurface, GetSurfaceEntryIndex())); +} + +void EntranceElement::SetSurfaceEntryIndex(ObjectEntryIndex newIndex) +{ + PathType = newIndex; + flags2 &= ~ENTRANCE_ELEMENT_FLAGS2_LEGACY_PATH_ENTRY; +} + +const PathSurfaceDescriptor* EntranceElement::GetPathSurfaceDescriptor() const +{ + if (HasLegacyPathEntry()) + { + const auto* legacyPathEntry = GetLegacyPathEntry(); + if (legacyPathEntry == nullptr) + return nullptr; + + return &legacyPathEntry->GetPathSurfaceDescriptor(); + } + + const auto* surfaceEntry = GetSurfaceEntry(); + if (surfaceEntry == nullptr) + return nullptr; + + return &surfaceEntry->GetDescriptor(); +} + +int32_t EntranceElement::GetDirections() const +{ + return EntranceDirections[(GetEntranceType() * 8) + GetSequenceIndex()]; +} + +ObjectEntryIndex EntranceElement::getEntryIndex() const +{ + return entryIndex; +} + +void EntranceElement::setEntryIndex(ObjectEntryIndex newIndex) +{ + entryIndex = newIndex; +}