Unify tunnel build mode into a single Tunnel button

This commit is contained in:
2026-07-21 21:12:06 +02:00
parent 4ca5b332cd
commit 9b63af6ccb
14 changed files with 522 additions and 63 deletions

View File

@@ -0,0 +1,115 @@
#include "TunnelCompletion.h"
namespace
{
QPoint stepTile(QPoint tile, Rotation dir)
{
switch (dir)
{
case Rotation::North: return {tile.x(), tile.y() - 1};
case Rotation::East: return {tile.x() + 1, tile.y() };
case Rotation::South: return {tile.x(), tile.y() + 1};
case Rotation::West: return {tile.x() - 1, tile.y() };
}
return tile;
}
Rotation oppositeRotation(Rotation dir)
{
switch (dir)
{
case Rotation::North: return Rotation::South;
case Rotation::East: return Rotation::West;
case Rotation::South: return Rotation::North;
case Rotation::West: return Rotation::East;
}
return dir;
}
float tileCenterDistanceSq(QPoint tile, QVector2D cursorWorldPos)
{
const QVector2D center(static_cast<float>(tile.x()) + 0.5f,
static_cast<float>(tile.y()) + 0.5f);
return (center - cursorWorldPos).lengthSquared();
}
} // namespace
std::optional<QPoint> firstTunnelFacing(const TunnelLookup& lookup, QPoint start,
Rotation stepDir, Rotation targetFacing,
int maxDistance)
{
QPoint probe = start;
for (int distance = 1; distance <= maxDistance; ++distance)
{
probe = stepTile(probe, stepDir);
const std::optional<TunnelTileInfo> info = lookup(probe);
if (info.has_value() && info->rotation == targetFacing)
{
return probe;
}
}
return std::nullopt;
}
TunnelCompletion resolveTunnelCompletion(const TunnelLookup& lookup, QPoint hoverTile,
Rotation rotation, int maxDistance,
QVector2D cursorWorldPos)
{
// Entry-completion: a hypothetical entry at the hovered tile searches ahead (its
// facing direction) for a same-direction exit to pair with. The first
// same-direction tunnel ahead completes the entry only if it is an exit.
std::optional<QPoint> exitPartner;
if (const std::optional<QPoint> ahead =
firstTunnelFacing(lookup, hoverTile, rotation, rotation, maxDistance);
ahead.has_value())
{
const std::optional<TunnelTileInfo> info = lookup(*ahead);
if (info.has_value() && info->type == BuildingType::TunnelExit)
{
exitPartner = ahead;
}
}
// Exit-completion: a hypothetical exit at the hovered tile pairs with an existing
// entry behind it — one whose forward search (its facing direction) reaches the
// hovered tile as its first same-direction tunnel. Scanning backwards, the first
// same-direction tunnel completes the exit only if it is an entry.
std::optional<QPoint> entryPartner;
if (const std::optional<QPoint> behind =
firstTunnelFacing(lookup, hoverTile, oppositeRotation(rotation), rotation,
maxDistance);
behind.has_value())
{
const std::optional<TunnelTileInfo> info = lookup(*behind);
if (info.has_value() && info->type == BuildingType::TunnelEntry)
{
entryPartner = behind;
}
}
// Default: a TunnelEntry with no partner.
if (!entryPartner.has_value() && !exitPartner.has_value())
{
return TunnelCompletion{BuildingType::TunnelEntry, std::nullopt};
}
if (entryPartner.has_value() && !exitPartner.has_value())
{
return TunnelCompletion{BuildingType::TunnelExit, entryPartner};
}
if (exitPartner.has_value() && !entryPartner.has_value())
{
return TunnelCompletion{BuildingType::TunnelEntry, exitPartner};
}
// Both apply: complete whichever existing partner is closer to the sub-tile
// cursor position (REQ-BLD-TUNNEL-MODE).
const float entryDistanceSq = tileCenterDistanceSq(*entryPartner, cursorWorldPos);
const float exitDistanceSq = tileCenterDistanceSq(*exitPartner, cursorWorldPos);
if (entryDistanceSq <= exitDistanceSq)
{
return TunnelCompletion{BuildingType::TunnelExit, entryPartner};
}
return TunnelCompletion{BuildingType::TunnelEntry, exitPartner};
}