/***************************************************************************** * Copyright (c) 2014-2023 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 "../Paint.h" #include "../../Context.h" #include "../../Game.h" #include "../../config/Config.h" #include "../../core/Numerics.hpp" #include "../../drawing/LightFX.h" #include "../../entity/EntityRegistry.h" #include "../../entity/PatrolArea.h" #include "../../entity/Peep.h" #include "../../entity/Staff.h" #include "../../interface/Viewport.h" #include "../../localisation/Formatter.h" #include "../../localisation/Formatting.h" #include "../../localisation/Localisation.h" #include "../../object/FootpathItemEntry.h" #include "../../object/FootpathObject.h" #include "../../object/FootpathRailingsObject.h" #include "../../object/FootpathSurfaceObject.h" #include "../../object/ObjectList.h" #include "../../object/ObjectManager.h" #include "../../profiling/Profiling.h" #include "../../ride/Ride.h" #include "../../ride/Track.h" #include "../../ride/TrackDesign.h" #include "../../ride/TrackPaint.h" #include "../../world/Footpath.h" #include "../../world/Map.h" #include "../../world/Scenery.h" #include "../../world/Surface.h" #include "../../world/TileInspector.h" #include "../Boundbox.h" #include "../Paint.SessionFlags.h" #include "../Supports.h" #include "Paint.Surface.h" #include "Paint.TileElement.h" using namespace OpenRCT2; bool gPaintWidePathsAsGhost = false; const uint8_t PathSlopeToLandSlope[] = { TILE_ELEMENT_SLOPE_SW_SIDE_UP, TILE_ELEMENT_SLOPE_NW_SIDE_UP, TILE_ELEMENT_SLOPE_NE_SIDE_UP, TILE_ELEMENT_SLOPE_SE_SIDE_UP, }; static constexpr const uint8_t Byte98D6E0[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 20, 4, 5, 6, 22, 8, 9, 10, 26, 12, 13, 14, 36, 0, 1, 2, 3, 4, 5, 21, 23, 8, 9, 10, 11, 12, 13, 33, 37, 0, 1, 2, 3, 4, 5, 6, 24, 8, 9, 10, 11, 12, 13, 14, 38, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 29, 30, 34, 39, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 40, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 35, 41, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 42, 0, 1, 2, 3, 4, 5, 6, 7, 8, 25, 10, 27, 12, 31, 14, 43, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 28, 12, 13, 14, 44, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 45, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 46, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 32, 14, 47, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 48, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 49, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 50, }; // clang-format off static constexpr const BoundBoxXY stru_98D804[] = { { { 3, 3 }, { 26, 26 } }, { { 0, 3 }, { 29, 26 } }, { { 3, 3 }, { 26, 29 } }, { { 0, 3 }, { 29, 29 } }, { { 3, 3 }, { 29, 26 } }, { { 0, 3 }, { 32, 26 } }, { { 3, 3 }, { 29, 29 } }, { { 0, 3 }, { 32, 29 } }, { { 3, 0 }, { 26, 29 } }, { { 0, 0 }, { 29, 29 } }, { { 3, 0 }, { 26, 32 } }, { { 0, 0 }, { 29, 32 } }, { { 3, 0 }, { 29, 29 } }, { { 0, 0 }, { 32, 29 } }, { { 3, 0 }, { 29, 32 } }, { { 0, 0 }, { 32, 32 } }, }; static constexpr const uint8_t Byte98D8A4[] = { 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0 }; // clang-format on void PathPaintBoxSupport( PaintSession& session, const PathElement& pathElement, int32_t height, const FootpathPaintInfo& pathPaintInfo, bool hasSupports, ImageId imageTemplate, ImageId sceneryImageTemplate); void PathPaintPoleSupport( PaintSession& session, const PathElement& pathElement, int16_t height, const FootpathPaintInfo& pathPaintInfo, bool hasSupports, ImageId imageTemplate, ImageId sceneryImageTemplate); static ImageIndex GetEdgeImageOffset(edge_t edge) { switch (edge) { case EDGE_NE: return 1; case EDGE_SE: return 2; case EDGE_SW: return 3; case EDGE_NW: return 4; default: return 0; } } static ImageIndex GetFootpathLampImage(const PathBitEntry& pathBitEntry, edge_t edge, bool isBroken) { auto offset = GetEdgeImageOffset(edge); if (offset == 0) return ImageIndexUndefined; return pathBitEntry.image + offset + (isBroken ? 4 : 0); } static ImageIndex GetFootpathBinImage(const PathBitEntry& pathBitEntry, edge_t edge, bool isBroken, bool isFull) { auto offset = GetEdgeImageOffset(edge); if (offset == 0) return ImageIndexUndefined; auto stateOffset = isBroken ? 4 : (isFull ? 8 : 0); return pathBitEntry.image + offset + stateOffset; } static ImageIndex GetFootpathBenchImage(const PathBitEntry& pathBitEntry, edge_t edge, bool isBroken) { auto offset = GetEdgeImageOffset(edge); if (offset == 0) return ImageIndexUndefined; return pathBitEntry.image + offset + (isBroken ? 4 : 0); } /* rct2: 0x006A5AE5 */ static void PathBitLightsPaint( PaintSession& session, const PathBitEntry& pathBitEntry, const PathElement& pathElement, int32_t height, uint8_t edges, ImageId imageTemplate) { if (pathElement.IsSloped()) height += 8; auto isBroken = pathElement.IsBroken(); if (!(edges & EDGE_NE)) { auto imageIndex = GetFootpathLampImage(pathBitEntry, EDGE_NE, isBroken); PaintAddImageAsParent( session, imageTemplate.WithIndex(imageIndex), { 2, 16, height }, { { 6, 8, height + 2 }, { 0, 16, 23 } }); } if (!(edges & EDGE_SE)) { auto imageIndex = GetFootpathLampImage(pathBitEntry, EDGE_SE, isBroken); PaintAddImageAsParent( session, imageTemplate.WithIndex(imageIndex), { 16, 30, height }, { { 8, 23, height + 2 }, { 16, 0, 23 } }); } if (!(edges & EDGE_SW)) { auto imageIndex = GetFootpathLampImage(pathBitEntry, EDGE_SW, isBroken); PaintAddImageAsParent( session, imageTemplate.WithIndex(imageIndex), { 30, 16, height }, { { 23, 8, height + 2 }, { 0, 16, 23 } }); } if (!(edges & EDGE_NW)) { auto imageIndex = GetFootpathLampImage(pathBitEntry, EDGE_NW, isBroken); PaintAddImageAsParent( session, imageTemplate.WithIndex(imageIndex), { 16, 2, height }, { { 8, 6, height + 2 }, { 16, 0, 23 } }); } } static bool IsBinFull(PaintSession& session, const PathElement& pathElement, edge_t edge) { switch (edge) { case EDGE_NE: return !(pathElement.GetAdditionStatus() & Numerics::ror8(0x03, (2 * session.CurrentRotation))); case EDGE_SE: return !(pathElement.GetAdditionStatus() & Numerics::ror8(0x0C, (2 * session.CurrentRotation))); case EDGE_SW: return !(pathElement.GetAdditionStatus() & Numerics::ror8(0x30, (2 * session.CurrentRotation))); case EDGE_NW: return !(pathElement.GetAdditionStatus() & Numerics::ror8(0xC0, (2 * session.CurrentRotation))); default: return false; } } /* rct2: 0x006A5C94 */ static void PathBitBinsPaint( PaintSession& session, const PathBitEntry& pathBitEntry, const PathElement& pathElement, int32_t height, uint8_t edges, ImageId imageTemplate) { if (pathElement.IsSloped()) height += 8; bool binsAreVandalised = pathElement.IsBroken(); auto highlightPathIssues = (session.ViewFlags & VIEWPORT_FLAG_HIGHLIGHT_PATH_ISSUES) != 0; if (!(edges & EDGE_NE)) { auto binIsFull = IsBinFull(session, pathElement, EDGE_NE); auto imageIndex = GetFootpathBinImage(pathBitEntry, EDGE_NE, binsAreVandalised, binIsFull); if (!highlightPathIssues || binIsFull || binsAreVandalised) PaintAddImageAsParent( session, imageTemplate.WithIndex(imageIndex), { 7, 16, height }, { { 6, 8, height + 2 }, { 0, 16, 7 } }); } if (!(edges & EDGE_SE)) { auto binIsFull = IsBinFull(session, pathElement, EDGE_SE); auto imageIndex = GetFootpathBinImage(pathBitEntry, EDGE_SE, binsAreVandalised, binIsFull); if (!highlightPathIssues || binIsFull || binsAreVandalised) PaintAddImageAsParent( session, imageTemplate.WithIndex(imageIndex), { 16, 25, height }, { { 8, 23, height + 2 }, { 16, 0, 7 } }); } if (!(edges & EDGE_SW)) { auto binIsFull = IsBinFull(session, pathElement, EDGE_SW); auto imageIndex = GetFootpathBinImage(pathBitEntry, EDGE_SW, binsAreVandalised, binIsFull); if (!highlightPathIssues || binIsFull || binsAreVandalised) PaintAddImageAsParent( session, imageTemplate.WithIndex(imageIndex), { 25, 16, height }, { { 23, 8, height + 2 }, { 0, 16, 7 } }); } if (!(edges & EDGE_NW)) { auto binIsFull = IsBinFull(session, pathElement, EDGE_NW); auto imageIndex = GetFootpathBinImage(pathBitEntry, EDGE_NW, binsAreVandalised, binIsFull); if (!highlightPathIssues || binIsFull || binsAreVandalised) PaintAddImageAsParent( session, imageTemplate.WithIndex(imageIndex), { 16, 7, height }, { { 8, 6, height + 2 }, { 16, 0, 7 } }); } } /* rct2: 0x006A5E81 */ static void PathBitBenchesPaint( PaintSession& session, const PathBitEntry& pathBitEntry, const PathElement& pathElement, int32_t height, uint8_t edges, ImageId imageTemplate) { auto isBroken = pathElement.IsBroken(); if (!(edges & EDGE_NE)) { auto imageIndex = GetFootpathBenchImage(pathBitEntry, EDGE_NE, isBroken); PaintAddImageAsParent( session, imageTemplate.WithIndex(imageIndex), { 7, 16, height }, { { 6, 8, height + 2 }, { 0, 16, 7 } }); } if (!(edges & EDGE_SE)) { auto imageIndex = GetFootpathBenchImage(pathBitEntry, EDGE_SE, isBroken); PaintAddImageAsParent( session, imageTemplate.WithIndex(imageIndex), { 16, 25, height }, { { 8, 23, height + 2 }, { 16, 0, 7 } }); } if (!(edges & EDGE_SW)) { auto imageIndex = GetFootpathBenchImage(pathBitEntry, EDGE_SW, isBroken); PaintAddImageAsParent( session, imageTemplate.WithIndex(imageIndex), { 25, 16, height }, { { 23, 8, height + 2 }, { 0, 16, 7 } }); } if (!(edges & EDGE_NW)) { auto imageIndex = GetFootpathBenchImage(pathBitEntry, EDGE_NW, isBroken); PaintAddImageAsParent( session, imageTemplate.WithIndex(imageIndex), { 16, 7, height }, { { 8, 6, height + 2 }, { 16, 0, 7 } }); } } /* rct2: 0x006A6008 */ static void PathBitJumpingFountainsPaint( PaintSession& session, const PathBitEntry& pathBitEntry, int32_t height, ImageId imageTemplate, DrawPixelInfo* dpi) { if (dpi->zoom_level > ZoomLevel{ 0 }) return; auto imageId = imageTemplate.WithIndex(pathBitEntry.image); PaintAddImageAsParent(session, imageId.WithIndexOffset(1), { 0, 0, height }, { { 6, 8, height + 2 }, { 1, 1, 2 } }); PaintAddImageAsParent(session, imageId.WithIndexOffset(2), { 0, 0, height }, { { 8, 23, height + 2 }, { 1, 1, 2 } }); PaintAddImageAsParent(session, imageId.WithIndexOffset(3), { 0, 0, height }, { { 23, 8, height + 2 }, { 1, 1, 2 } }); PaintAddImageAsParent(session, imageId.WithIndexOffset(4), { 0, 0, height }, { { 8, 6, height + 2 }, { 1, 1, 2 } }); } /** * rct2: 0x006A4101 * @param tile_element (esi) */ static void PathPaintFencesAndQueueBanners( PaintSession& session, const PathElement& pathElement, uint16_t height, uint32_t connectedEdges, bool hasSupports, const FootpathPaintInfo& pathPaintInfo, ImageId imageTemplate) { PROFILED_FUNCTION(); auto imageId = imageTemplate.WithIndex(pathPaintInfo.RailingsImageId); if (pathElement.IsQueue()) { if (pathElement.IsSloped()) { switch ((pathElement.GetSlopeDirection() + session.CurrentRotation) & FOOTPATH_PROPERTIES_SLOPE_DIRECTION_MASK) { case 0: PaintAddImageAsParent( session, imageId.WithIndexOffset(22), { 0, 4, height }, { { 0, 4, height + 2 }, { 32, 1, 23 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(22), { 0, 28, height }, { { 0, 28, height + 2 }, { 32, 1, 23 } }); break; case 1: PaintAddImageAsParent( session, imageId.WithIndexOffset(21), { 4, 0, height }, { { 4, 0, height + 2 }, { 1, 32, 23 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(21), { 28, 0, height }, { { 28, 0, height + 2 }, { 1, 32, 23 } }); break; case 2: PaintAddImageAsParent( session, imageId.WithIndexOffset(23), { 0, 4, height }, { { 0, 4, height + 2 }, { 32, 1, 23 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(23), { 0, 28, height }, { { 0, 28, height + 2 }, { 32, 1, 23 } }); break; case 3: PaintAddImageAsParent( session, imageId.WithIndexOffset(20), { 4, 0, height }, { { 4, 0, height + 2 }, { 1, 32, 23 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(20), { 28, 0, height }, { { 28, 0, height + 2 }, { 1, 32, 23 } }); break; } } else { const auto pathEdges = connectedEdges & FOOTPATH_PROPERTIES_EDGES_EDGES_MASK; switch (pathEdges) { case 0b0001: PaintAddImageAsParent( session, imageId.WithIndexOffset(17), { 0, 4, height }, { { 0, 4, height + 2 }, { 28, 1, 7 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(17), { 0, 28, height }, { { 0, 28, height + 2 }, { 28, 1, 7 } }); break; case 0b0010: PaintAddImageAsParent( session, imageId.WithIndexOffset(18), { 4, 0, height }, { { 4, 0, height + 2 }, { 1, 28, 7 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(18), { 28, 0, height }, { { 28, 0, height + 2 }, { 1, 28, 7 } }); break; case 0b0011: PaintAddImageAsParent( session, imageId.WithIndexOffset(17), { 0, 4, height }, { { 0, 4, height + 2 }, { 28, 1, 7 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(18), { 28, 0, height }, { { 28, 4, height + 2 }, { 1, 28, 7 } }); // bound_box_offset_y seems to be a bug PaintAddImageAsParent( session, imageId.WithIndexOffset(25), { 0, 0, height }, { { 0, 28, height + 2 }, { 4, 4, 7 } }); break; case 0b0100: PaintAddImageAsParent( session, imageId.WithIndexOffset(19), { 0, 4, height }, { { 0, 4, height + 2 }, { 28, 1, 7 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(19), { 0, 28, height }, { { 0, 28, height + 2 }, { 28, 1, 7 } }); break; case 0b0101: PaintAddImageAsParent( session, imageId.WithIndexOffset(15), { 0, 4, height }, { { 0, 4, height + 2 }, { 32, 1, 7 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(15), { 0, 28, height }, { { 0, 28, height + 2 }, { 32, 1, 7 } }); break; case 0b0110: PaintAddImageAsParent( session, imageId.WithIndexOffset(18), { 4, 0, height }, { { 4, 0, height + 2 }, { 1, 28, 7 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(19), { 0, 4, height }, { { 0, 4, height + 2 }, { 28, 1, 7 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(26), { 0, 0, height }, { { 28, 28, height + 2 }, { 4, 4, 7 } }); break; case 0b0111: if (pathElement.HasJunctionRailings()) { PaintAddImageAsParent( session, imageId.WithIndexOffset(15), { 0, 4, height }, { { 0, 4, height + 2 }, { 32, 1, 7 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(25), { 0, 0, height }, { { 0, 28, height + 2 }, { 4, 4, 7 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(26), { 0, 0, height }, { { 28, 28, height + 2 }, { 4, 4, 7 } }); } break; case 0b1000: PaintAddImageAsParent( session, imageId.WithIndexOffset(16), { 4, 0, height }, { { 4, 0, height + 2 }, { 1, 28, 7 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(16), { 28, 0, height }, { { 28, 0, height + 2 }, { 1, 28, 7 } }); break; case 0b1001: PaintAddImageAsParent( session, imageId.WithIndexOffset(16), { 28, 0, height }, { { 28, 0, height + 2 }, { 1, 28, 7 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(17), { 0, 28, height }, { { 0, 28, height + 2 }, { 28, 1, 7 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(24), { 0, 0, height }, { { 0, 0, height + 2 }, { 4, 4, 7 } }); break; case 0b1010: PaintAddImageAsParent( session, imageId.WithIndexOffset(14), { 4, 0, height }, { { 4, 0, height + 2 }, { 1, 32, 7 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(14), { 28, 0, height }, { { 28, 0, height + 2 }, { 1, 32, 7 } }); break; case 0b1011: if (pathElement.HasJunctionRailings()) { PaintAddImageAsParent( session, imageId.WithIndexOffset(14), { 28, 0, height }, { { 28, 0, height + 2 }, { 1, 32, 7 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(24), { 0, 0, height }, { { 0, 0, height + 2 }, { 4, 4, 7 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(25), { 0, 0, height }, { { 0, 28, height + 2 }, { 4, 4, 7 } }); } break; case 0b1100: PaintAddImageAsParent( session, imageId.WithIndexOffset(16), { 4, 0, height }, { { 4, 0, height + 2 }, { 1, 28, 7 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(19), { 0, 28, height }, { { 4, 28, height + 2 }, { 28, 1, 7 } }); // bound_box_offset_x seems to be a bug PaintAddImageAsParent( session, imageId.WithIndexOffset(27), { 0, 0, height }, { { 28, 0, height + 2 }, { 4, 4, 7 } }); break; case 0b1101: if (pathElement.HasJunctionRailings()) { PaintAddImageAsParent( session, imageId.WithIndexOffset(15), { 0, 28, height }, { { 0, 28, height + 2 }, { 32, 1, 7 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(24), { 0, 0, height }, { { 0, 0, height + 2 }, { 4, 4, 7 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(27), { 0, 0, height }, { { 28, 0, height + 2 }, { 4, 4, 7 } }); } break; case 0b1110: if (pathElement.HasJunctionRailings()) { PaintAddImageAsParent( session, imageId.WithIndexOffset(14), { 4, 0, height }, { { 4, 0, height + 2 }, { 1, 32, 7 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(26), { 0, 0, height }, { { 28, 28, height + 2 }, { 4, 4, 7 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(27), { 0, 0, height }, { { 28, 0, height + 2 }, { 4, 4, 7 } }); } break; case 0b1111: if (pathElement.HasJunctionRailings()) { PaintAddImageAsParent( session, imageId.WithIndexOffset(24), { 0, 0, height }, { { 0, 0, height + 2 }, { 4, 4, 7 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(25), { 0, 0, height }, { { 0, 28, height + 2 }, { 4, 4, 7 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(26), { 0, 0, height }, { { 28, 28, height + 2 }, { 4, 4, 7 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(27), { 0, 0, height }, { { 28, 0, height + 2 }, { 4, 4, 7 } }); } } } if (!pathElement.HasQueueBanner() || (pathPaintInfo.RailingFlags & RAILING_ENTRY_FLAG_NO_QUEUE_BANNER)) { return; } uint8_t direction = pathElement.GetQueueBannerDirection(); // Draw ride sign session.InteractionType = ViewportInteractionItem::Ride; if (pathElement.IsSloped()) { if (pathElement.GetSlopeDirection() == direction) height += COORDS_Z_STEP * 2; } direction += session.CurrentRotation; direction &= 3; CoordsXYZ boundBoxOffsets = CoordsXYZ(BannerBoundBoxes[direction][0], height + 2); imageId = imageId.WithIndexOffset(28 + (direction << 1)); // Draw pole in the back PaintAddImageAsParent(session, imageId, { 0, 0, height }, { boundBoxOffsets, { 1, 1, 21 } }); // Draw pole in the front and banner boundBoxOffsets.x = BannerBoundBoxes[direction][1].x; boundBoxOffsets.y = BannerBoundBoxes[direction][1].y; imageId = imageId.WithIndexOffset(1); PaintAddImageAsParent(session, imageId, { 0, 0, height }, { boundBoxOffsets, { 1, 1, 21 } }); direction--; // If text shown auto ride = GetRide(pathElement.GetRideIndex()); if (direction < 2 && ride != nullptr && !imageTemplate.IsRemap()) { uint16_t scrollingMode = pathPaintInfo.ScrollingMode; scrollingMode += direction; auto ft = Formatter(); if (ride->status == RideStatus::Open && !(ride->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN)) { ft.Add(STR_RIDE_ENTRANCE_NAME); ride->FormatNameTo(ft); } else { ft.Add(STR_RIDE_ENTRANCE_CLOSED); } if (gConfigGeneral.UpperCaseBanners) { FormatStringToUpper( gCommonStringFormatBuffer, sizeof(gCommonStringFormatBuffer), STR_BANNER_TEXT_FORMAT, ft.Data()); } else { FormatStringLegacy( gCommonStringFormatBuffer, sizeof(gCommonStringFormatBuffer), STR_BANNER_TEXT_FORMAT, ft.Data()); } uint16_t stringWidth = GfxGetStringWidth(gCommonStringFormatBuffer, FontStyle::Tiny); uint16_t scroll = stringWidth > 0 ? (gCurrentTicks / 2) % stringWidth : 0; PaintAddImageAsChild( session, ScrollingTextSetup(session, STR_BANNER_TEXT_FORMAT, ft, scroll, scrollingMode, COLOUR_BLACK), { 0, 0, height + 7 }, { boundBoxOffsets, { 1, 1, 21 } }); } session.InteractionType = ViewportInteractionItem::Footpath; if (imageTemplate.IsRemap()) { session.InteractionType = ViewportInteractionItem::None; } return; } uint32_t drawnCorners = 0; // If the path is not drawn over the supports, then no corner sprites will be drawn (making double-width paths // look like connected series of intersections). if (pathPaintInfo.RailingFlags & RAILING_ENTRY_FLAG_DRAW_PATH_OVER_SUPPORTS) { drawnCorners = (connectedEdges & FOOTPATH_PROPERTIES_EDGES_CORNERS_MASK) >> 4; } auto slopeRailingsSupported = !(pathPaintInfo.SurfaceFlags & FOOTPATH_ENTRY_FLAG_NO_SLOPE_RAILINGS); if ((hasSupports || slopeRailingsSupported) && pathElement.IsSloped()) { switch ((pathElement.GetSlopeDirection() + session.CurrentRotation) & FOOTPATH_PROPERTIES_SLOPE_DIRECTION_MASK) { case 0: PaintAddImageAsParent( session, imageId.WithIndexOffset(8), { 0, 4, height }, { { 0, 4, height + 2 }, { 32, 1, 23 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(8), { 0, 28, height }, { { 0, 28, height + 2 }, { 32, 1, 23 } }); break; case 1: PaintAddImageAsParent( session, imageId.WithIndexOffset(7), { 4, 0, height }, { { 4, 0, height + 2 }, { 1, 32, 23 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(7), { 28, 0, height }, { { 28, 0, height + 2 }, { 1, 32, 23 } }); break; case 2: PaintAddImageAsParent( session, imageId.WithIndexOffset(9), { 0, 4, height }, { { 0, 4, height + 2 }, { 32, 1, 23 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(9), { 0, 28, height }, { { 0, 28, height + 2 }, { 32, 1, 23 } }); break; case 3: PaintAddImageAsParent( session, imageId.WithIndexOffset(6), { 4, 0, height }, { { 4, 0, height + 2 }, { 1, 32, 23 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(6), { 28, 0, height }, { { 28, 0, height + 2 }, { 1, 32, 23 } }); break; } } else { if (!hasSupports) { return; } switch (connectedEdges & FOOTPATH_PROPERTIES_EDGES_EDGES_MASK) { case 0: // purposely left empty break; case 1: PaintAddImageAsParent( session, imageId.WithIndexOffset(3), { 0, 4, height }, { { 0, 4, height + 2 }, { 28, 1, 7 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(3), { 0, 28, height }, { { 0, 28, height + 2 }, { 28, 1, 7 } }); break; case 2: PaintAddImageAsParent( session, imageId.WithIndexOffset(4), { 4, 0, height }, { { 4, 0, height + 2 }, { 1, 28, 7 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(4), { 28, 0, height }, { { 28, 0, height + 2 }, { 1, 28, 7 } }); break; case 4: PaintAddImageAsParent( session, imageId.WithIndexOffset(5), { 0, 4, height }, { { 0, 4, height + 2 }, { 28, 1, 7 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(5), { 0, 28, height }, { { 0, 28, height + 2 }, { 28, 1, 7 } }); break; case 5: PaintAddImageAsParent( session, imageId.WithIndexOffset(1), { 0, 4, height }, { { 0, 4, height + 2 }, { 32, 1, 7 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(1), { 0, 28, height }, { { 0, 28, height + 2 }, { 32, 1, 7 } }); break; case 8: PaintAddImageAsParent( session, imageId.WithIndexOffset(2), { 4, 0, height }, { { 4, 0, height + 2 }, { 1, 28, 7 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(2), { 28, 0, height }, { { 28, 0, height + 2 }, { 1, 28, 7 } }); break; case 10: PaintAddImageAsParent( session, imageId.WithIndexOffset(0), { 4, 0, height }, { { 4, 0, height + 2 }, { 1, 32, 7 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(0), { 28, 0, height }, { { 28, 0, height + 2 }, { 1, 32, 7 } }); break; case 3: PaintAddImageAsParent( session, imageId.WithIndexOffset(3), { 0, 4, height }, { { 0, 4, height + 2 }, { 28, 1, 7 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(4), { 28, 0, height }, { { 28, 4, height + 2 }, { 1, 28, 7 } }); // bound_box_offset_y seems to be a bug if (!(drawnCorners & FOOTPATH_CORNER_0)) { PaintAddImageAsParent( session, imageId.WithIndexOffset(11), { 0, 0, height }, { { 0, 28, height + 2 }, { 4, 4, 7 } }); } break; case 6: PaintAddImageAsParent( session, imageId.WithIndexOffset(4), { 4, 0, height }, { { 4, 0, height + 2 }, { 1, 28, 7 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(5), { 0, 4, height }, { { 0, 4, height + 2 }, { 28, 1, 7 } }); if (!(drawnCorners & FOOTPATH_CORNER_1)) { PaintAddImageAsParent( session, imageId.WithIndexOffset(12), { 0, 0, height }, { { 28, 28, height + 2 }, { 4, 4, 7 } }); } break; case 9: PaintAddImageAsParent( session, imageId.WithIndexOffset(2), { 28, 0, height }, { { 28, 0, height + 2 }, { 1, 28, 7 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(3), { 0, 28, height }, { { 0, 28, height + 2 }, { 28, 1, 7 } }); if (!(drawnCorners & FOOTPATH_CORNER_3)) { PaintAddImageAsParent( session, imageId.WithIndexOffset(10), { 0, 0, height }, { { 0, 0, height + 2 }, { 4, 4, 7 } }); } break; case 12: PaintAddImageAsParent( session, imageId.WithIndexOffset(2), { 4, 0, height }, { { 4, 0, height + 2 }, { 1, 28, 7 } }); PaintAddImageAsParent( session, imageId.WithIndexOffset(5), { 0, 28, height }, { { 4, 28, height + 2 }, { 28, 1, 7 } }); // bound_box_offset_x seems to be a bug if (!(drawnCorners & FOOTPATH_CORNER_2)) { PaintAddImageAsParent( session, imageId.WithIndexOffset(13), { 0, 0, height }, { { 28, 0, height + 2 }, { 4, 4, 7 } }); } break; case 7: PaintAddImageAsParent( session, imageId.WithIndexOffset(1), { 0, 4, height }, { { 0, 4, height + 2 }, { 32, 1, 7 } }); if (!(drawnCorners & FOOTPATH_CORNER_0)) { PaintAddImageAsParent( session, imageId.WithIndexOffset(11), { 0, 0, height }, { { 0, 28, height + 2 }, { 4, 4, 7 } }); } if (!(drawnCorners & FOOTPATH_CORNER_1)) { PaintAddImageAsParent( session, imageId.WithIndexOffset(12), { 0, 0, height }, { { 28, 28, height + 2 }, { 4, 4, 7 } }); } break; case 13: PaintAddImageAsParent( session, imageId.WithIndexOffset(1), { 0, 28, height }, { { 0, 28, height + 2 }, { 32, 1, 7 } }); if (!(drawnCorners & FOOTPATH_CORNER_2)) { PaintAddImageAsParent( session, imageId.WithIndexOffset(13), { 0, 0, height }, { { 28, 0, height + 2 }, { 4, 4, 7 } }); } if (!(drawnCorners & FOOTPATH_CORNER_3)) { PaintAddImageAsParent( session, imageId.WithIndexOffset(10), { 0, 0, height }, { { 0, 0, height + 2 }, { 4, 4, 7 } }); } break; case 14: PaintAddImageAsParent( session, imageId.WithIndexOffset(0), { 4, 0, height }, { { 4, 0, height + 2 }, { 1, 32, 7 } }); if (!(drawnCorners & FOOTPATH_CORNER_1)) { PaintAddImageAsParent( session, imageId.WithIndexOffset(12), { 0, 0, height }, { { 28, 28, height + 2 }, { 4, 4, 7 } }); } if (!(drawnCorners & FOOTPATH_CORNER_2)) { PaintAddImageAsParent( session, imageId.WithIndexOffset(13), { 0, 0, height }, { { 28, 0, height + 2 }, { 4, 4, 7 } }); } break; case 11: PaintAddImageAsParent( session, imageId.WithIndexOffset(0), { 28, 0, height }, { { 28, 0, height + 2 }, { 1, 32, 7 } }); if (!(drawnCorners & FOOTPATH_CORNER_0)) { PaintAddImageAsParent( session, imageId.WithIndexOffset(11), { 0, 0, height }, { { 0, 28, height + 2 }, { 4, 4, 7 } }); } if (!(drawnCorners & FOOTPATH_CORNER_3)) { PaintAddImageAsParent( session, imageId.WithIndexOffset(10), { 0, 0, height }, { { 0, 0, height + 2 }, { 4, 4, 7 } }); } break; case 15: if (!(drawnCorners & FOOTPATH_CORNER_0)) { PaintAddImageAsParent( session, imageId.WithIndexOffset(11), { 0, 0, height }, { { 0, 28, height + 2 }, { 4, 4, 7 } }); } if (!(drawnCorners & FOOTPATH_CORNER_1)) { PaintAddImageAsParent( session, imageId.WithIndexOffset(12), { 0, 0, height }, { { 28, 28, height + 2 }, { 4, 4, 7 } }); } if (!(drawnCorners & FOOTPATH_CORNER_2)) { PaintAddImageAsParent( session, imageId.WithIndexOffset(13), { 0, 0, height }, { { 28, 0, height + 2 }, { 4, 4, 7 } }); } if (!(drawnCorners & FOOTPATH_CORNER_3)) { PaintAddImageAsParent( session, imageId.WithIndexOffset(10), { 0, 0, height }, { { 0, 0, height + 2 }, { 4, 4, 7 } }); } break; } } } /** * rct2: 0x006A3F61 * @param pathElement (esp[0]) * @param connectedEdges (bp) (relative to the camera's rotation) * @param height (dx) * @param pathPaintInfo (0x00F3EF6C) * @param imageFlags (0x00F3EF70) * @param sceneryImageFlags (0x00F3EF74) */ static void Sub6A3F61( PaintSession& session, const PathElement& pathElement, uint16_t connectedEdges, uint16_t height, const FootpathPaintInfo& pathPaintInfo, ImageId imageTemplate, ImageId sceneryImageTemplate, bool hasSupports) { // eax -- // ebx -- // ecx // edx // esi -- // edi -- // ebp // esp: [ esi, ???, 000] // Probably drawing benches etc. PROFILED_FUNCTION(); DrawPixelInfo* dpi = &session.DPI; if (dpi->zoom_level <= ZoomLevel{ 1 }) { if (!gTrackDesignSaveMode) { if (pathElement.HasAddition()) { session.InteractionType = ViewportInteractionItem::FootpathItem; if (sceneryImageTemplate.IsRemap()) { session.InteractionType = ViewportInteractionItem::None; } // Draw additional path bits (bins, benches, lamps, queue screens) auto* pathAddEntry = pathElement.GetAdditionEntry(); bool drawAddition = true; // Can be null if the object is not loaded. if (pathAddEntry == nullptr || ((session.ViewFlags & VIEWPORT_FLAG_HIGHLIGHT_PATH_ISSUES) && !(pathElement.IsBroken()) && pathAddEntry->draw_type != PathBitDrawType::Bin)) { drawAddition = false; } if (drawAddition) { switch (pathAddEntry->draw_type) { case PathBitDrawType::Light: PathBitLightsPaint( session, *pathAddEntry, pathElement, height, static_cast(connectedEdges), sceneryImageTemplate); break; case PathBitDrawType::Bin: PathBitBinsPaint( session, *pathAddEntry, pathElement, height, static_cast(connectedEdges), sceneryImageTemplate); break; case PathBitDrawType::Bench: PathBitBenchesPaint( session, *pathAddEntry, pathElement, height, static_cast(connectedEdges), sceneryImageTemplate); break; case PathBitDrawType::JumpingFountain: PathBitJumpingFountainsPaint(session, *pathAddEntry, height, sceneryImageTemplate, dpi); break; } session.InteractionType = ViewportInteractionItem::Footpath; if (sceneryImageTemplate.IsRemap()) { session.InteractionType = ViewportInteractionItem::None; } } } } // Redundant zoom-level check removed PathPaintFencesAndQueueBanners(session, pathElement, height, connectedEdges, hasSupports, pathPaintInfo, imageTemplate); } // This is about tunnel drawing uint8_t direction = (pathElement.GetSlopeDirection() + session.CurrentRotation) & FOOTPATH_PROPERTIES_SLOPE_DIRECTION_MASK; bool sloped = pathElement.IsSloped(); if (connectedEdges & EDGE_SE) { // Bottom right of tile is a tunnel if (sloped && direction == EDGE_NE) { // Path going down into the tunnel PaintUtilPushTunnelRight(session, height + 16, TUNNEL_PATH_AND_MINI_GOLF); } else if (connectedEdges & EDGE_NE) { // Flat path with edge to the right (north-east) PaintUtilPushTunnelRight(session, height, TUNNEL_PATH_11); } else { // Path going up, or flat and not connected to the right PaintUtilPushTunnelRight(session, height, TUNNEL_PATH_AND_MINI_GOLF); } } if (!(connectedEdges & EDGE_SW)) { return; } // Bottom left of the tile is a tunnel if (sloped && direction == EDGE_SE) { // Path going down into the tunnel PaintUtilPushTunnelLeft(session, height + 16, TUNNEL_PATH_AND_MINI_GOLF); } else if (connectedEdges & EDGE_NW) { // Flat path with edge to the left (north-west) PaintUtilPushTunnelLeft(session, height, TUNNEL_PATH_11); } else { // Path going up, or flat and not connected to the left PaintUtilPushTunnelLeft(session, height, TUNNEL_PATH_AND_MINI_GOLF); } } static FootpathPaintInfo GetFootpathPaintInfo(const PathElement& pathEl) { FootpathPaintInfo pathPaintInfo; const auto* surfaceDescriptor = pathEl.GetSurfaceDescriptor(); if (surfaceDescriptor != nullptr) { pathPaintInfo.SurfaceImageId = surfaceDescriptor->Image; pathPaintInfo.SurfaceFlags = surfaceDescriptor->Flags; } const auto* railingsDescriptor = pathEl.GetRailingsDescriptor(); if (railingsDescriptor != nullptr) { pathPaintInfo.BridgeImageId = railingsDescriptor->BridgeImage; pathPaintInfo.RailingsImageId = railingsDescriptor->RailingsImage; pathPaintInfo.RailingFlags = railingsDescriptor->Flags; pathPaintInfo.ScrollingMode = railingsDescriptor->ScrollingMode; pathPaintInfo.SupportType = railingsDescriptor->SupportType; pathPaintInfo.SupportColour = railingsDescriptor->SupportColour; } return pathPaintInfo; } static bool ShouldDrawSupports(PaintSession& session, const PathElement& pathEl, uint16_t height) { auto surface = MapGetSurfaceElementAt(session.MapPosition); if (surface == nullptr) { return true; } else if (surface->GetBaseZ() != height) { const auto* surfaceEntry = pathEl.GetSurfaceEntry(); const bool showUndergroundRailings = surfaceEntry == nullptr || !(surfaceEntry->Flags & FOOTPATH_ENTRY_FLAG_NO_SLOPE_RAILINGS); if (surface->GetBaseZ() < height || showUndergroundRailings) return true; } else if (pathEl.IsSloped()) { // Diagonal path if (surface->GetSlope() != PathSlopeToLandSlope[pathEl.GetSlopeDirection()]) { return true; } } else if (surface->GetSlope() != TILE_ELEMENT_SLOPE_FLAT) { return true; } return false; } static void PaintPatrolAreas(PaintSession& session, const PathElement& pathEl) { auto colour = GetPatrolAreaTileColour(session.MapPosition); if (colour) { uint32_t baseImageIndex = SPR_TERRAIN_STAFF; auto patrolAreaBaseZ = pathEl.GetBaseZ(); if (pathEl.IsSloped()) { baseImageIndex = SPR_TERRAIN_STAFF_SLOPED + ((pathEl.GetSlopeDirection() + session.CurrentRotation) & 3); patrolAreaBaseZ += 16; } auto imageId = ImageId(baseImageIndex, *colour); PaintAddImageAsParent(session, imageId, { 16, 16, patrolAreaBaseZ + 2 }, { 1, 1, 0 }); } } static void PaintHeightMarkers(PaintSession& session, const PathElement& pathEl) { PROFILED_FUNCTION(); if (PaintShouldShowHeightMarkers(session, VIEWPORT_FLAG_PATH_HEIGHTS)) { uint16_t heightMarkerBaseZ = pathEl.GetBaseZ() + 3; if (pathEl.IsSloped()) { heightMarkerBaseZ += 8; } uint32_t baseImageIndex = SPR_HEIGHT_MARKER_BASE; baseImageIndex += heightMarkerBaseZ / 16; baseImageIndex += GetHeightMarkerOffset(); baseImageIndex -= gMapBaseZ; auto imageId = ImageId(baseImageIndex, COLOUR_GREY); PaintAddImageAsParent(session, imageId, { 16, 16, heightMarkerBaseZ }, { 1, 1, 0 }); } } static void PaintLampLightEffects(PaintSession& session, const PathElement& pathEl, uint16_t height) { PROFILED_FUNCTION(); if (LightFXIsAvailable()) { if (pathEl.HasAddition() && !(pathEl.IsBroken())) { auto* pathAddEntry = pathEl.GetAdditionEntry(); if (pathAddEntry != nullptr && pathAddEntry->flags & PATH_BIT_FLAG_LAMP) { if (!(pathEl.GetEdges() & EDGE_NE)) { LightFXAdd3DLightMagicFromDrawingTile(session.MapPosition, -16, 0, height + 23, LightType::Lantern3); } if (!(pathEl.GetEdges() & EDGE_SE)) { LightFXAdd3DLightMagicFromDrawingTile(session.MapPosition, 0, 16, height + 23, LightType::Lantern3); } if (!(pathEl.GetEdges() & EDGE_SW)) { LightFXAdd3DLightMagicFromDrawingTile(session.MapPosition, 16, 0, height + 23, LightType::Lantern3); } if (!(pathEl.GetEdges() & EDGE_NW)) { LightFXAdd3DLightMagicFromDrawingTile(session.MapPosition, 0, -16, height + 23, LightType::Lantern3); } } } } } /** * rct2: 0x0006A3590 */ void PaintPath(PaintSession& session, uint16_t height, const PathElement& tileElement) { PROFILED_FUNCTION(); session.InteractionType = ViewportInteractionItem::Footpath; ImageId imageTemplate, sceneryImageTemplate; if (gTrackDesignSaveMode) { // Do not display queues for other rides if (tileElement.IsQueue() && tileElement.GetRideIndex() != gTrackDesignSaveRideIndex) { return; } if (!TrackDesignSaveContainsTileElement(reinterpret_cast(&tileElement))) { imageTemplate = ImageId().WithRemap(FilterPaletteID::Palette46); } } if (session.ViewFlags & VIEWPORT_FLAG_HIGHLIGHT_PATH_ISSUES) { imageTemplate = ImageId().WithRemap(FilterPaletteID::Palette46); } if (tileElement.AdditionIsGhost()) { sceneryImageTemplate = ImageId().WithRemap(FilterPaletteID::Palette44); } if (tileElement.IsGhost()) { session.InteractionType = ViewportInteractionItem::None; imageTemplate = ImageId().WithRemap(FilterPaletteID::Palette44); } else if (TileInspector::IsElementSelected(reinterpret_cast(&tileElement))) { imageTemplate = ImageId().WithRemap(FilterPaletteID::Palette44); sceneryImageTemplate = ImageId().WithRemap(FilterPaletteID::Palette44); } // For debugging purpose, show blocked tiles with a colour if (gPaintBlockedTiles && tileElement.IsBlockedByVehicle()) { imageTemplate = ImageId().WithRemap(FilterPaletteID::Palette46); } // Draw wide flags as ghosts, leaving only the "walkable" paths to be drawn normally if (gPaintWidePathsAsGhost && tileElement.IsWide()) { imageTemplate = ImageId().WithRemap(FilterPaletteID::Palette44); } PaintPatrolAreas(session, tileElement); PaintHeightMarkers(session, tileElement); auto hasSupports = ShouldDrawSupports(session, tileElement, height); auto pathPaintInfo = GetFootpathPaintInfo(tileElement); if (pathPaintInfo.SupportType == RailingEntrySupportType::Pole) { PathPaintPoleSupport(session, tileElement, height, pathPaintInfo, hasSupports, imageTemplate, sceneryImageTemplate); } else { PathPaintBoxSupport(session, tileElement, height, pathPaintInfo, hasSupports, imageTemplate, sceneryImageTemplate); } PaintLampLightEffects(session, tileElement, height); } void PathPaintBoxSupport( PaintSession& session, const PathElement& pathElement, int32_t height, const FootpathPaintInfo& pathPaintInfo, bool hasSupports, ImageId imageTemplate, ImageId sceneryImageTemplate) { PROFILED_FUNCTION(); // Rol edges around rotation uint8_t edges = ((pathElement.GetEdges() << session.CurrentRotation) & 0xF) | (((pathElement.GetEdges()) << session.CurrentRotation) >> 4); uint8_t corners = (((pathElement.GetCorners()) << session.CurrentRotation) & 0xF) | (((pathElement.GetCorners()) << session.CurrentRotation) >> 4); CoordsXY boundBoxOffset = stru_98D804[edges].offset; CoordsXY boundBoxSize = stru_98D804[edges].length; uint16_t edi = edges | (corners << 4); ImageIndex surfaceBaseImageIndex = pathPaintInfo.SurfaceImageId; if (pathElement.IsSloped()) { auto directionOffset = (pathElement.GetSlopeDirection() + session.CurrentRotation) & FOOTPATH_PROPERTIES_SLOPE_DIRECTION_MASK; surfaceBaseImageIndex += 16 + directionOffset; } else { surfaceBaseImageIndex += Byte98D6E0[edi]; } const bool hasPassedSurface = (session.Flags & PaintSessionFlags::PassedSurface) != 0; if (!hasPassedSurface) { boundBoxOffset.x = 3; boundBoxOffset.y = 3; boundBoxSize.x = 26; boundBoxSize.y = 26; } // By default, add 1 to the z bounding box to always clip above the surface uint8_t boundingBoxZOffset = 1; // If we are on the same tile as a straight track, add the offset 2 so we // can clip above gravel part of the track sprite if (session.TrackElementOnSameHeight != nullptr) { if (session.TrackElementOnSameHeight->AsTrack()->GetTrackType() == TrackElemType::Flat) { boundingBoxZOffset = 2; } } if (!hasSupports || !hasPassedSurface) { PaintAddImageAsParent( session, imageTemplate.WithIndex(surfaceBaseImageIndex), { 0, 0, height }, { { boundBoxOffset, height + boundingBoxZOffset }, { boundBoxSize, 0 } }); } else { ImageIndex bridgeBaseImageIndex; if (pathElement.IsSloped()) { auto directionOffset = ((pathElement.GetSlopeDirection() + session.CurrentRotation) & FOOTPATH_PROPERTIES_SLOPE_DIRECTION_MASK); bridgeBaseImageIndex = pathPaintInfo.BridgeImageId + 51 + directionOffset; } else { bridgeBaseImageIndex = Byte98D8A4[edges] + pathPaintInfo.BridgeImageId + 49; } PaintAddImageAsParent( session, imageTemplate.WithIndex(bridgeBaseImageIndex), { 0, 0, height }, { { boundBoxOffset, height + boundingBoxZOffset }, { boundBoxSize, 0 } }); if (pathElement.IsQueue() || (pathPaintInfo.RailingFlags & RAILING_ENTRY_FLAG_DRAW_PATH_OVER_SUPPORTS)) { PaintAddImageAsChild( session, imageTemplate.WithIndex(surfaceBaseImageIndex), { 0, 0, height }, { { boundBoxOffset, height + boundingBoxZOffset }, { boundBoxSize, 0 } }); } } Sub6A3F61(session, pathElement, edi, height, pathPaintInfo, imageTemplate, sceneryImageTemplate, hasSupports); uint16_t ax = 0; if (pathElement.IsSloped()) { ax = ((pathElement.GetSlopeDirection() + session.CurrentRotation) & 0x3) + 1; } auto supportType = Byte98D8A4[edges] == 0 ? 0 : 1; PathASupportsPaintSetup(session, supportType, ax, height, imageTemplate, pathPaintInfo, nullptr); height += 32; if (pathElement.IsSloped()) { height += 16; } PaintUtilSetGeneralSupportHeight(session, height, 0x20); if (pathElement.IsQueue() || (pathElement.GetEdgesAndCorners() != 0xFF && hasSupports)) { PaintUtilSetSegmentSupportHeight(session, SEGMENTS_ALL, 0xFFFF, 0); return; } if (pathElement.GetEdgesAndCorners() == 0xFF) { PaintUtilSetSegmentSupportHeight(session, SEGMENT_C8 | SEGMENT_CC | SEGMENT_D0 | SEGMENT_D4, 0xFFFF, 0); return; } PaintUtilSetSegmentSupportHeight(session, SEGMENT_C4, 0xFFFF, 0); if (edges & 1) { PaintUtilSetSegmentSupportHeight(session, SEGMENT_CC, 0xFFFF, 0); } if (edges & 2) { PaintUtilSetSegmentSupportHeight(session, SEGMENT_D4, 0xFFFF, 0); } if (edges & 4) { PaintUtilSetSegmentSupportHeight(session, SEGMENT_D0, 0xFFFF, 0); } if (edges & 8) { PaintUtilSetSegmentSupportHeight(session, SEGMENT_C8, 0xFFFF, 0); } } void PathPaintPoleSupport( PaintSession& session, const PathElement& pathElement, int16_t height, const FootpathPaintInfo& pathPaintInfo, bool hasSupports, ImageId imageTemplate, ImageId sceneryImageTemplate) { PROFILED_FUNCTION(); // Rol edges around rotation uint8_t edges = ((pathElement.GetEdges() << session.CurrentRotation) & 0xF) | (((pathElement.GetEdges()) << session.CurrentRotation) >> 4); CoordsXY boundBoxOffset = stru_98D804[edges].offset; CoordsXY boundBoxSize = stru_98D804[edges].length; uint8_t corners = (((pathElement.GetCorners()) << session.CurrentRotation) & 0xF) | (((pathElement.GetCorners()) << session.CurrentRotation) >> 4); uint16_t edi = edges | (corners << 4); ImageIndex surfaceBaseImageIndex = pathPaintInfo.SurfaceImageId; if (pathElement.IsSloped()) { auto directionOffset = ((pathElement.GetSlopeDirection() + session.CurrentRotation) & FOOTPATH_PROPERTIES_SLOPE_DIRECTION_MASK); surfaceBaseImageIndex += 16 + directionOffset; } else { surfaceBaseImageIndex += Byte98D6E0[edi]; } // Below Surface const bool hasPassedSurface = (session.Flags & PaintSessionFlags::PassedSurface) != 0; if (!hasPassedSurface) { boundBoxOffset.x = 3; boundBoxOffset.y = 3; boundBoxSize.x = 26; boundBoxSize.y = 26; } // By default, add 1 to the z bounding box to always clip above the surface uint8_t boundingBoxZOffset = 1; // If we are on the same tile as a straight track, add the offset 2 so we // can clip above gravel part of the track sprite if (session.TrackElementOnSameHeight != nullptr) { if (session.TrackElementOnSameHeight->AsTrack()->GetTrackType() == TrackElemType::Flat) { boundingBoxZOffset = 2; } } if (!hasSupports || !hasPassedSurface) { PaintAddImageAsParent( session, imageTemplate.WithIndex(surfaceBaseImageIndex), { 0, 0, height }, { { boundBoxOffset.x, boundBoxOffset.y, height + boundingBoxZOffset }, { boundBoxSize.x, boundBoxSize.y, 0 } }); } else { ImageIndex bridgeBaseImageIndex; if (pathElement.IsSloped()) { bridgeBaseImageIndex = ((pathElement.GetSlopeDirection() + session.CurrentRotation) & FOOTPATH_PROPERTIES_SLOPE_DIRECTION_MASK) + pathPaintInfo.BridgeImageId + 16; } else { bridgeBaseImageIndex = edges + pathPaintInfo.BridgeImageId; } PaintAddImageAsParent( session, imageTemplate.WithIndex(bridgeBaseImageIndex), { 0, 0, height }, { { boundBoxOffset, height + boundingBoxZOffset }, { boundBoxSize, 0 } }); if (pathElement.IsQueue() || (pathPaintInfo.RailingFlags & RAILING_ENTRY_FLAG_DRAW_PATH_OVER_SUPPORTS)) { PaintAddImageAsChild( session, imageTemplate.WithIndex(surfaceBaseImageIndex), { 0, 0, height }, { { boundBoxOffset, height + boundingBoxZOffset }, { boundBoxSize, 0 } }); } } Sub6A3F61( session, pathElement, edi, height, pathPaintInfo, imageTemplate, sceneryImageTemplate, hasSupports); // TODO: arguments uint16_t ax = 0; if (pathElement.IsSloped()) { ax = 8; } uint8_t supports[] = { 6, 8, 7, 5, }; for (int8_t i = 3; i > -1; --i) { if (!(edges & (1 << i))) { // Only colour the supports if not already remapped (e.g. ghost remap) auto supportColour = pathPaintInfo.SupportColour; if (supportColour != COLOUR_NULL && !imageTemplate.IsRemap()) { imageTemplate = ImageId().WithPrimary(supportColour); } PathBSupportsPaintSetup(session, supports[i], ax, height, imageTemplate, pathPaintInfo); } } height += 32; if (pathElement.IsSloped()) { height += 16; } PaintUtilSetGeneralSupportHeight(session, height, 0x20); if (pathElement.IsQueue() || (pathElement.GetEdgesAndCorners() != 0xFF && hasSupports)) { PaintUtilSetSegmentSupportHeight(session, SEGMENTS_ALL, 0xFFFF, 0); return; } if (pathElement.GetEdgesAndCorners() == 0xFF) { PaintUtilSetSegmentSupportHeight(session, SEGMENT_C8 | SEGMENT_CC | SEGMENT_D0 | SEGMENT_D4, 0xFFFF, 0); return; } PaintUtilSetSegmentSupportHeight(session, SEGMENT_C4, 0xFFFF, 0); if (edges & EDGE_NE) { PaintUtilSetSegmentSupportHeight(session, SEGMENT_CC, 0xFFFF, 0); } if (edges & EDGE_SE) { PaintUtilSetSegmentSupportHeight(session, SEGMENT_D4, 0xFFFF, 0); } if (edges & EDGE_SW) { PaintUtilSetSegmentSupportHeight(session, SEGMENT_D0, 0xFFFF, 0); } if (edges & EDGE_NW) { PaintUtilSetSegmentSupportHeight(session, SEGMENT_C8, 0xFFFF, 0); } }