implement building system

This commit is contained in:
2026-04-19 20:50:42 +02:00
parent c70b5c8f08
commit bf29cc40e3
19 changed files with 1818 additions and 7 deletions

View File

@@ -8,6 +8,7 @@ SET(HDRS
${CMAKE_CURRENT_SOURCE_DIR}/StationsConfig.h
${CMAKE_CURRENT_SOURCE_DIR}/GameConfig.h
${CMAKE_CURRENT_SOURCE_DIR}/ConfigLoader.h
${CMAKE_CURRENT_SOURCE_DIR}/SurfaceMask.h
PARENT_SCOPE
)
@@ -15,6 +16,7 @@ SET(SRCS
${SRCS}
${CMAKE_CURRENT_SOURCE_DIR}/Formula.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ConfigLoader.cpp
${CMAKE_CURRENT_SOURCE_DIR}/SurfaceMask.cpp
PARENT_SCOPE
)

View File

@@ -221,6 +221,7 @@ WorldConfig ConfigLoader::loadWorld(const std::string& path)
cfg.heightTiles = static_cast<int>(requireInt(tbl["world"]["height_tiles"], file, "world.height_tiles"));
cfg.refundPercentage = static_cast<int>(requireInt(tbl["world"]["refund_percentage"], file, "world.refund_percentage"));
cfg.startingBuildingBlocks = static_cast<int>(requireInt(tbl["world"]["starting_building_blocks"], file, "world.starting_building_blocks"));
cfg.scrapDespawnSeconds = requireDouble(tbl["world"]["scrap_despawn_seconds"], file, "world.scrap_despawn_seconds");
cfg.beltSpeedTilesPerSecond = requireDouble(tbl["world"]["belt_speed_tiles_per_second"], file, "world.belt_speed_tiles_per_second");

View File

@@ -0,0 +1,172 @@
#include "SurfaceMask.h"
#include <algorithm>
#include <climits>
namespace
{
// Rotate direction character 90° clockwise.
char rotateDirCharCW(char c)
{
switch (c)
{
case '>': return 'v';
case 'v': return '<';
case '<': return '^';
case '^': return '>';
default: return c;
}
}
// Rotate the character grid 90° clockwise.
// Input grid[row][col]. Returns a new grid with swapped dimensions.
std::vector<std::string> rotateCW(const std::vector<std::string>& grid)
{
if (grid.empty())
{
return {};
}
const int srcH = static_cast<int>(grid.size());
// Pad all rows to the same width.
int srcW = 0;
for (const std::string& row : grid)
{
const int w = static_cast<int>(row.size());
if (w > srcW)
{
srcW = w;
}
}
// After 90° CW: new width = srcH, new height = srcW.
const int dstW = srcH;
const int dstH = srcW;
std::vector<std::string> dst(dstH, std::string(dstW, ' '));
for (int row = 0; row < srcH; ++row)
{
for (int col = 0; col < srcW; ++col)
{
const char ch = (col < static_cast<int>(grid[row].size()))
? grid[row][col]
: ' ';
// 90° CW mapping: (col, row) -> (dstCol = srcH-1-row, dstRow = col)
const int dstCol = srcH - 1 - row;
const int dstRow = col;
dst[dstRow][dstCol] = rotateDirCharCW(ch);
}
}
return dst;
}
} // namespace
ParsedSurfaceMask parseSurfaceMask(const std::vector<std::string>& rows,
Rotation rotation)
{
// Number of 90° CW steps to apply.
int cwSteps = 0;
switch (rotation)
{
case Rotation::East: cwSteps = 0; break;
case Rotation::South: cwSteps = 1; break;
case Rotation::West: cwSteps = 2; break;
case Rotation::North: cwSteps = 3; break;
}
// Apply rotations.
std::vector<std::string> grid = rows;
for (int i = 0; i < cwSteps; ++i)
{
grid = rotateCW(grid);
}
// Scan grid: collect body cells, ship dock cells, and output port indicators.
std::vector<QPoint> rawBodyCells;
std::vector<QPoint> rawShipDockCells;
struct RawPort
{
QPoint tile;
Rotation direction;
};
std::vector<RawPort> rawPorts;
for (int row = 0; row < static_cast<int>(grid.size()); ++row)
{
for (int col = 0; col < static_cast<int>(grid[row].size()); ++col)
{
const char ch = grid[row][col];
if (ch == 'A')
{
rawBodyCells.push_back(QPoint(col, row));
}
else if (ch == 'S')
{
rawBodyCells.push_back(QPoint(col, row));
rawShipDockCells.push_back(QPoint(col, row));
}
else if (ch == '>')
{
rawPorts.push_back({QPoint(col, row), Rotation::East});
}
else if (ch == '<')
{
rawPorts.push_back({QPoint(col, row), Rotation::West});
}
else if (ch == '^')
{
rawPorts.push_back({QPoint(col, row), Rotation::North});
}
else if (ch == 'v')
{
rawPorts.push_back({QPoint(col, row), Rotation::South});
}
}
}
// Compute bounding box of body cells and normalize to (0,0).
int minCol = INT_MAX;
int minRow = INT_MAX;
int maxCol = INT_MIN;
int maxRow = INT_MIN;
for (const QPoint& pt : rawBodyCells)
{
if (pt.x() < minCol) { minCol = pt.x(); }
if (pt.x() > maxCol) { maxCol = pt.x(); }
if (pt.y() < minRow) { minRow = pt.y(); }
if (pt.y() > maxRow) { maxRow = pt.y(); }
}
// If there are no body cells, return an empty mask.
if (rawBodyCells.empty())
{
return ParsedSurfaceMask{};
}
const QPoint offset(-minCol, -minRow);
ParsedSurfaceMask result;
result.footprint = QSize(maxCol - minCol + 1, maxRow - minRow + 1);
for (const QPoint& pt : rawBodyCells)
{
result.bodyCells.push_back(pt + offset);
}
for (const QPoint& pt : rawShipDockCells)
{
result.shipDockCells.push_back(pt + offset);
}
for (const RawPort& rp : rawPorts)
{
Port port;
port.tile = rp.tile + offset;
port.direction = rp.direction;
result.outputPorts.push_back(port);
}
return result;
}

View File

@@ -0,0 +1,28 @@
#pragma once
#include <string>
#include <vector>
#include <QPoint>
#include <QSize>
#include "Port.h"
#include "Rotation.h"
// Parsed representation of a building's surface_mask after applying rotation.
// All coordinates are relative to the building's anchor tile (the point passed
// to BuildingSystem::place), which corresponds to (0,0) in this system.
struct ParsedSurfaceMask
{
QSize footprint; // bounding box of body cells (A + S tiles)
std::vector<QPoint> bodyCells; // relative positions of A and S tiles
std::vector<Port> outputPorts; // port.tile = cell adjacent to body, outside footprint;
// port.direction = flow direction away from building
std::vector<QPoint> shipDockCells; // relative positions of S tiles (subset of bodyCells)
};
// Parse a surface_mask definition (as loaded from TOML) and apply the given
// rotation. The canonical mask orientation corresponds to Rotation::East.
// Rotations are applied clockwise (East=0, South=90°, West=180°, North=270°).
ParsedSurfaceMask parseSurfaceMask(const std::vector<std::string>& rows,
Rotation rotation);

View File

@@ -39,6 +39,7 @@ struct WorldConfig
{
int heightTiles; // REQ-GW-HEIGHT
int refundPercentage; // REQ-BLD-DEMOLISH
int startingBuildingBlocks; // REQ-HQ-STARTING-BLOCKS
double scrapDespawnSeconds; // REQ-RES-SCRAP-DROP
double beltSpeedTilesPerSecond; // REQ-GW-BELT-SPEED