mirror of https://github.com/OpenRCT2/OpenRCT2.git
370 lines
14 KiB
C++
370 lines
14 KiB
C++
/*****************************************************************************
|
|
* 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 "../Paint.h"
|
|
|
|
#include "../../Game.h"
|
|
#include "../../GameState.h"
|
|
#include "../../interface/Viewport.h"
|
|
#include "../../localisation/Date.h"
|
|
#include "../../object/SmallSceneryEntry.h"
|
|
#include "../../profiling/Profiling.h"
|
|
#include "../../ride/TrackDesign.h"
|
|
#include "../../util/Util.h"
|
|
#include "../../world/Map.h"
|
|
#include "../../world/Scenery.h"
|
|
#include "../../world/TileInspector.h"
|
|
#include "../support/WoodenSupports.h"
|
|
#include "Paint.TileElement.h"
|
|
#include "Segment.h"
|
|
|
|
using namespace OpenRCT2;
|
|
|
|
static constexpr CoordsXY lengths[] = {
|
|
{ 12, 26 },
|
|
{ 26, 12 },
|
|
{ 12, 26 },
|
|
{ 26, 12 },
|
|
};
|
|
|
|
static void PaintSmallScenerySupports(
|
|
PaintSession& session, const SmallSceneryEntry& sceneryEntry, const SmallSceneryElement& sceneryElement,
|
|
Direction direction, int32_t height, ImageId imageTemplate)
|
|
{
|
|
PROFILED_FUNCTION();
|
|
|
|
if (!sceneryElement.NeedsSupports())
|
|
return;
|
|
|
|
if (sceneryEntry.HasFlag(SMALL_SCENERY_FLAG_NO_SUPPORTS))
|
|
return;
|
|
|
|
auto transitionType = WoodenSupportTransitionType::None;
|
|
auto supportHeight = height;
|
|
if (supportHeight & 0xF)
|
|
{
|
|
supportHeight &= ~0xF;
|
|
transitionType = WoodenSupportTransitionType::Scenery;
|
|
}
|
|
|
|
auto supportImageTemplate = ImageId().WithRemap(0);
|
|
if (sceneryEntry.HasFlag(SMALL_SCENERY_FLAG_PAINT_SUPPORTS))
|
|
{
|
|
supportImageTemplate = ImageId().WithPrimary(sceneryElement.GetPrimaryColour());
|
|
}
|
|
if (imageTemplate.IsRemap())
|
|
{
|
|
supportImageTemplate = imageTemplate;
|
|
}
|
|
|
|
WoodenBSupportsPaintSetupRotated(
|
|
session, WoodenSupportType::Truss, WoodenSupportSubType::NeSw, direction, supportHeight, supportImageTemplate,
|
|
transitionType);
|
|
}
|
|
|
|
static void SetSupportHeights(
|
|
PaintSession& session, const SmallSceneryEntry& sceneryEntry, const SmallSceneryElement& sceneryElement, int32_t height)
|
|
{
|
|
height += sceneryEntry.height;
|
|
|
|
PaintUtilSetGeneralSupportHeight(session, Ceil2(height, 8), 0x20);
|
|
if (sceneryEntry.HasFlag(SMALL_SCENERY_FLAG_BUILD_DIRECTLY_ONTOP))
|
|
{
|
|
if (sceneryEntry.HasFlag(SMALL_SCENERY_FLAG_FULL_TILE))
|
|
{
|
|
PaintUtilSetSegmentSupportHeight(session, EnumToFlag(PaintSegment::centre), height, 0x20);
|
|
if (sceneryEntry.HasFlag(SMALL_SCENERY_FLAG_VOFFSET_CENTRE))
|
|
{
|
|
PaintUtilSetSegmentSupportHeight(session, kSegmentsAll & ~EnumToFlag(PaintSegment::centre), height, 0x20);
|
|
}
|
|
}
|
|
else if (sceneryEntry.HasFlag(SMALL_SCENERY_FLAG_VOFFSET_CENTRE))
|
|
{
|
|
auto direction = (sceneryElement.GetSceneryQuadrant() + session.CurrentRotation) % 4;
|
|
PaintUtilSetSegmentSupportHeight(
|
|
session,
|
|
PaintUtilRotateSegments(
|
|
EnumsToFlags(PaintSegment::topCorner, PaintSegment::topLeftSide, PaintSegment::topRightSide), direction),
|
|
height, 0x20);
|
|
}
|
|
}
|
|
else if (sceneryEntry.HasFlag(SMALL_SCENERY_FLAG27 | SMALL_SCENERY_FLAG_FULL_TILE))
|
|
{
|
|
PaintUtilSetSegmentSupportHeight(session, EnumToFlag(PaintSegment::centre), 0xFFFF, 0);
|
|
if (sceneryEntry.HasFlag(SMALL_SCENERY_FLAG_VOFFSET_CENTRE))
|
|
{
|
|
PaintUtilSetSegmentSupportHeight(session, kSegmentsAll & ~EnumToFlag(PaintSegment::centre), 0xFFFF, 0);
|
|
}
|
|
}
|
|
else if (sceneryEntry.HasFlag(SMALL_SCENERY_FLAG_VOFFSET_CENTRE))
|
|
{
|
|
auto direction = (sceneryElement.GetSceneryQuadrant() + session.CurrentRotation) % 4;
|
|
PaintUtilSetSegmentSupportHeight(
|
|
session,
|
|
PaintUtilRotateSegments(
|
|
EnumsToFlags(PaintSegment::topCorner, PaintSegment::topLeftSide, PaintSegment::topRightSide), direction),
|
|
0xFFFF, 0);
|
|
}
|
|
}
|
|
|
|
static void PaintSmallSceneryBody(
|
|
PaintSession& session, uint8_t direction, int32_t height, const SmallSceneryElement& sceneryElement,
|
|
const SmallSceneryEntry* sceneryEntry, ImageId imageTemplate)
|
|
{
|
|
PROFILED_FUNCTION();
|
|
|
|
BoundBoxXYZ boundBox = { { 0, 0, height }, { 2, 2, 0 } };
|
|
|
|
CoordsXYZ offset = { 0, 0, height };
|
|
if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_FULL_TILE))
|
|
{
|
|
if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_HALF_SPACE))
|
|
{
|
|
static constexpr CoordsXY sceneryHalfTileOffsets[] = {
|
|
{ 3, 3 },
|
|
{ 3, 17 },
|
|
{ 17, 3 },
|
|
{ 3, 3 },
|
|
};
|
|
boundBox.offset.x = sceneryHalfTileOffsets[direction].x;
|
|
boundBox.offset.y = sceneryHalfTileOffsets[direction].y;
|
|
boundBox.length.x = lengths[direction].x;
|
|
boundBox.length.y = lengths[direction].y;
|
|
offset.x = 3;
|
|
offset.y = 3;
|
|
}
|
|
else
|
|
{
|
|
offset.x = 15;
|
|
offset.y = 15;
|
|
if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_VOFFSET_CENTRE))
|
|
{
|
|
offset.x = 3;
|
|
offset.y = 3;
|
|
boundBox.length.x = 26;
|
|
boundBox.length.y = 26;
|
|
if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_NO_WALLS))
|
|
{
|
|
offset.x = 1;
|
|
offset.y = 1;
|
|
boundBox.length.x = 30;
|
|
boundBox.length.y = 30;
|
|
}
|
|
}
|
|
boundBox.offset.x = offset.x;
|
|
boundBox.offset.y = offset.y;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
uint8_t quadrant = (sceneryElement.GetSceneryQuadrant() + session.CurrentRotation) & 3;
|
|
// -1 to maintain compatibility with existing CSOs in context of issue #17616
|
|
offset.x = SceneryQuadrantOffsets[quadrant].x - 1;
|
|
offset.y = SceneryQuadrantOffsets[quadrant].y - 1;
|
|
boundBox.offset.x = offset.x;
|
|
boundBox.offset.y = offset.y;
|
|
}
|
|
boundBox.length.z = sceneryEntry->height - 4;
|
|
if (boundBox.length.z > 128 || boundBox.length.z < 0)
|
|
{
|
|
boundBox.length.z = 128;
|
|
}
|
|
boundBox.length.z--;
|
|
|
|
ImageIndex baseImageIndex = sceneryEntry->image + direction;
|
|
if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_CAN_WITHER))
|
|
{
|
|
if (sceneryElement.GetAge() >= kSceneryWitherAgeThreshold1)
|
|
{
|
|
baseImageIndex += 4;
|
|
}
|
|
if (sceneryElement.GetAge() >= kSceneryWitherAgeThreshold2)
|
|
{
|
|
baseImageIndex += 4;
|
|
}
|
|
}
|
|
if (!(sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_VISIBLE_WHEN_ZOOMED)))
|
|
{
|
|
auto imageId = imageTemplate.WithIndex(baseImageIndex);
|
|
if (!imageTemplate.IsRemap())
|
|
{
|
|
if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_HAS_PRIMARY_COLOUR))
|
|
{
|
|
imageId = imageId.WithPrimary(sceneryElement.GetPrimaryColour());
|
|
if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_HAS_SECONDARY_COLOUR))
|
|
{
|
|
imageId = imageId.WithSecondary(sceneryElement.GetSecondaryColour());
|
|
}
|
|
}
|
|
if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_HAS_TERTIARY_COLOUR))
|
|
{
|
|
imageId = imageId.WithTertiary(sceneryElement.GetTertiaryColour());
|
|
}
|
|
}
|
|
PaintAddImageAsParent(session, imageId, offset, boundBox);
|
|
}
|
|
|
|
if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_HAS_GLASS) && !imageTemplate.IsRemap())
|
|
{
|
|
auto imageId = ImageId(baseImageIndex + 4).WithTransparency(sceneryElement.GetPrimaryColour());
|
|
PaintAddImageAsChild(session, imageId, offset, boundBox);
|
|
}
|
|
|
|
if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_ANIMATED))
|
|
{
|
|
const auto currentTicks = GetGameState().CurrentTicks;
|
|
|
|
if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_VISIBLE_WHEN_ZOOMED) || (session.DPI.zoom_level <= ZoomLevel{ 1 }))
|
|
{
|
|
if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_FOUNTAIN_SPRAY_1))
|
|
{
|
|
auto imageIndex = sceneryEntry->image + 4 + ((currentTicks / 2) & 0xF);
|
|
auto imageId = imageTemplate.WithIndex(imageIndex);
|
|
PaintAddImageAsChild(session, imageId, offset, boundBox);
|
|
}
|
|
else if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_FOUNTAIN_SPRAY_4))
|
|
{
|
|
auto imageIndex = sceneryEntry->image + 8 + ((currentTicks / 2) & 0xF);
|
|
PaintAddImageAsChild(session, imageTemplate.WithIndex(imageIndex), offset, boundBox);
|
|
|
|
imageIndex = direction + sceneryEntry->image + 4;
|
|
PaintAddImageAsChild(session, imageTemplate.WithIndex(imageIndex), offset, boundBox);
|
|
|
|
imageIndex = sceneryEntry->image + 24 + ((currentTicks / 2) & 0xF);
|
|
PaintAddImageAsChild(session, imageTemplate.WithIndex(imageIndex), offset, boundBox);
|
|
}
|
|
else if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_IS_CLOCK))
|
|
{
|
|
auto minuteImageOffset = ((gRealTimeOfDay.minute + 6) * 17) / 256;
|
|
auto timeImageBase = gRealTimeOfDay.hour;
|
|
while (timeImageBase >= 12)
|
|
{
|
|
timeImageBase -= 12;
|
|
}
|
|
timeImageBase = (timeImageBase * 4) + minuteImageOffset;
|
|
if (timeImageBase >= 48)
|
|
{
|
|
timeImageBase -= 48;
|
|
}
|
|
auto imageIndex = timeImageBase + (direction * 12);
|
|
if (imageIndex >= 48)
|
|
{
|
|
imageIndex -= 48;
|
|
}
|
|
|
|
imageIndex = sceneryEntry->image + 68 + imageIndex;
|
|
PaintAddImageAsChild(session, imageTemplate.WithIndex(imageIndex), offset, boundBox);
|
|
|
|
imageIndex = gRealTimeOfDay.minute + (direction * 15);
|
|
if (imageIndex >= 60)
|
|
{
|
|
imageIndex -= 60;
|
|
}
|
|
imageIndex = sceneryEntry->image + 8 + imageIndex;
|
|
PaintAddImageAsChild(session, imageTemplate.WithIndex(imageIndex), offset, boundBox);
|
|
}
|
|
else if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_SWAMP_GOO))
|
|
{
|
|
auto imageIndex = currentTicks;
|
|
imageIndex += session.SpritePosition.x / 4;
|
|
imageIndex += session.SpritePosition.y / 4;
|
|
imageIndex = sceneryEntry->image + ((imageIndex / 4) % 16);
|
|
PaintAddImageAsChild(session, imageTemplate.WithIndex(imageIndex), offset, boundBox);
|
|
}
|
|
else if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_HAS_FRAME_OFFSETS))
|
|
{
|
|
auto delay = sceneryEntry->animation_delay & 0xFF;
|
|
auto frame = currentTicks;
|
|
if (!(sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_COG)))
|
|
{
|
|
frame += ((session.SpritePosition.x / 4) + (session.SpritePosition.y / 4));
|
|
frame += sceneryElement.GetSceneryQuadrant() << 2;
|
|
}
|
|
frame = (frame >> delay) & sceneryEntry->animation_mask;
|
|
|
|
auto imageIndex = 0;
|
|
if (frame < sceneryEntry->FrameOffsetCount)
|
|
{
|
|
imageIndex = sceneryEntry->frame_offsets[frame];
|
|
}
|
|
imageIndex = (imageIndex * 4) + direction + sceneryEntry->image;
|
|
if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_VISIBLE_WHEN_ZOOMED | SMALL_SCENERY_FLAG17))
|
|
{
|
|
imageIndex += 4;
|
|
}
|
|
|
|
auto imageId = imageTemplate.WithIndex(imageIndex);
|
|
if (!imageTemplate.IsRemap())
|
|
{
|
|
if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_HAS_PRIMARY_COLOUR))
|
|
{
|
|
imageId = ImageId(imageIndex).WithPrimary(sceneryElement.GetPrimaryColour());
|
|
if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_HAS_SECONDARY_COLOUR))
|
|
{
|
|
imageId = imageId.WithSecondary(sceneryElement.GetSecondaryColour());
|
|
}
|
|
}
|
|
if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_HAS_TERTIARY_COLOUR))
|
|
{
|
|
imageId = imageId.WithTertiary(sceneryElement.GetTertiaryColour());
|
|
}
|
|
}
|
|
|
|
if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_VISIBLE_WHEN_ZOOMED))
|
|
{
|
|
PaintAddImageAsParent(session, imageId, offset, boundBox);
|
|
}
|
|
else
|
|
{
|
|
PaintAddImageAsChild(session, imageId, offset, boundBox);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void PaintSmallScenery(PaintSession& session, uint8_t direction, int32_t height, const SmallSceneryElement& sceneryElement)
|
|
{
|
|
PROFILED_FUNCTION();
|
|
|
|
if (session.ViewFlags & VIEWPORT_FLAG_HIGHLIGHT_PATH_ISSUES)
|
|
{
|
|
return;
|
|
}
|
|
|
|
auto* sceneryEntry = sceneryElement.GetEntry();
|
|
if (sceneryEntry == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
session.InteractionType = ViewportInteractionItem::Scenery;
|
|
ImageId imageTemplate;
|
|
if (gTrackDesignSaveMode)
|
|
{
|
|
if (!TrackDesignSaveContainsTileElement(reinterpret_cast<const TileElement*>(&sceneryElement)))
|
|
{
|
|
imageTemplate = ImageId().WithRemap(FilterPaletteID::Palette46);
|
|
}
|
|
}
|
|
if (sceneryElement.IsGhost())
|
|
{
|
|
session.InteractionType = ViewportInteractionItem::None;
|
|
imageTemplate = ImageId().WithRemap(FilterPaletteID::PaletteGhost);
|
|
}
|
|
else if (session.SelectedElement == reinterpret_cast<const TileElement*>(&sceneryElement))
|
|
{
|
|
imageTemplate = ImageId().WithRemap(FilterPaletteID::PaletteGhost);
|
|
}
|
|
|
|
PaintSmallSceneryBody(session, direction, height, sceneryElement, sceneryEntry, imageTemplate);
|
|
PaintSmallScenerySupports(session, *sceneryEntry, sceneryElement, direction, height, imageTemplate);
|
|
SetSupportHeights(session, *sceneryEntry, sceneryElement, height);
|
|
}
|