Refactor farm building speed to builder unit action_speeds and enable infinite charges
This commit is contained in:
+367
-79
@@ -6,6 +6,7 @@
|
||||
#include <QToolTip>
|
||||
#include <QDebug>
|
||||
#include "pathfinder.h"
|
||||
#include "yamlparser.h"
|
||||
|
||||
GLuint vertexbuffer;
|
||||
GLuint VertexArrayID;
|
||||
@@ -46,6 +47,7 @@ QSceneRenderer::QSceneRenderer(QWidget* parent): QOpenGLWidget(parent)
|
||||
}
|
||||
}
|
||||
|
||||
loadCameraConfig();
|
||||
zRot = 90.0f; // Default looking North (+Y)
|
||||
m_zoom = 8.0f;
|
||||
cameraUpDirection = cameraTransformation * QVector3D(0, 1, 0);
|
||||
@@ -72,6 +74,46 @@ QSceneRenderer::QSceneRenderer(QWidget* parent): QOpenGLWidget(parent)
|
||||
animTimer->start(16);
|
||||
}
|
||||
|
||||
void QSceneRenderer::loadCameraConfig()
|
||||
{
|
||||
QString configPath = "/home/bart/project/tiletest/config/game.yaml";
|
||||
YamlNode root = YamlParser::parseFile(configPath);
|
||||
if (root.hasChild("camera")) {
|
||||
YamlNode cam = root["camera"];
|
||||
m_cameraMinZoom = cam["min_zoom"].asFloat(m_cameraMinZoom);
|
||||
m_cameraMaxZoom = cam["max_zoom"].asFloat(m_cameraMaxZoom);
|
||||
m_cameraPitchMinDeg = cam["pitch_min_deg"].asFloat(m_cameraPitchMinDeg);
|
||||
m_cameraPitchMaxDeg = cam["pitch_max_deg"].asFloat(m_cameraPitchMaxDeg);
|
||||
}
|
||||
}
|
||||
|
||||
float QSceneRenderer::getCameraPitchDeg() const
|
||||
{
|
||||
float range = m_cameraMaxZoom - m_cameraMinZoom;
|
||||
float zoomT = range > 1e-4f ? std::max(0.0f, std::min(1.0f, (m_zoom - m_cameraMinZoom) / range)) : 0.0f;
|
||||
return m_cameraPitchMinDeg + zoomT * (m_cameraPitchMaxDeg - m_cameraPitchMinDeg);
|
||||
}
|
||||
|
||||
float QSceneRenderer::getCameraForwardDist() const
|
||||
{
|
||||
float pitchRad = getCameraPitchDeg() * PI / 180.0f;
|
||||
float tanPitch = std::tan(pitchRad);
|
||||
if (tanPitch < 1e-4f) tanPitch = 1e-4f;
|
||||
return m_zoom / tanPitch;
|
||||
}
|
||||
|
||||
QVector3D QSceneRenderer::getCameraLookAt() const
|
||||
{
|
||||
float forwardDist = getCameraForwardDist();
|
||||
float rad = zRot * PI / 180.0f;
|
||||
return QVector3D(
|
||||
cameraPosition.x() + forwardDist * cos(rad),
|
||||
cameraPosition.y() + forwardDist * sin(rad),
|
||||
0.0f
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
void QSceneRenderer::loadRiverMap(const QImage &riverImg)
|
||||
{
|
||||
if (riverImg.isNull()) return;
|
||||
@@ -323,7 +365,9 @@ void QSceneRenderer::drawMinimap(QPainter &painter)
|
||||
|
||||
float rad = zRot * PI / 180.0f;
|
||||
float fovHalf = 25.0f * PI / 180.0f;
|
||||
float viewDist = std::max(12.0f, m_zoom * 2.2f);
|
||||
float pitchDeg = getCameraPitchDeg();
|
||||
float farPitchRad = (pitchDeg - 22.5f) * PI / 180.0f;
|
||||
float viewDist = std::max(4.0f, (float)(m_zoom / std::tan(farPitchRad)));
|
||||
|
||||
float lx = cameraPosition.x() + viewDist * cos(rad - fovHalf);
|
||||
float ly = cameraPosition.y() + viewDist * sin(rad - fovHalf);
|
||||
@@ -374,10 +418,11 @@ void QSceneRenderer::drawMinimap(QPainter &painter)
|
||||
QRect bottomBar(cardRect.left() + 6, mapRect.bottom() + 4, cardRect.width() - 12, 16);
|
||||
painter.setFont(QFont("sans-serif", 8));
|
||||
painter.setPen(QColor(180, 200, 220));
|
||||
QString info = QString("Cam: %1, %2 km | Alt: %3 km")
|
||||
QString info = QString("Cam: %1, %2 km | Alt: %3 km | Hoek: %4°")
|
||||
.arg(cameraPosition.x(), 0, 'f', 1)
|
||||
.arg(cameraPosition.y(), 0, 'f', 1)
|
||||
.arg(m_zoom, 0, 'f', 1);
|
||||
.arg(m_zoom, 0, 'f', 1)
|
||||
.arg(pitchDeg, 0, 'f', 0);
|
||||
painter.drawText(bottomBar, Qt::AlignLeft | Qt::AlignVCenter, info);
|
||||
}
|
||||
|
||||
@@ -452,15 +497,15 @@ void QSceneRenderer::drawTickBar(QPainter &painter)
|
||||
{
|
||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
|
||||
int barW = 340, barH = 34;
|
||||
int barW = 690, barH = 34;
|
||||
QRect barRect(16, 16, barW, barH);
|
||||
|
||||
// Dark glass card
|
||||
painter.setBrush(QColor(18, 24, 34, 230));
|
||||
painter.setBrush(QColor(18, 24, 34, 235));
|
||||
painter.setPen(QPen(QColor(70, 100, 140, 180), 1.2));
|
||||
painter.drawRoundedRect(barRect, 6, 6);
|
||||
|
||||
// Movement Tick Info
|
||||
// 1. Movement Tick Info
|
||||
float tickDur = GameManager::instance().getTickDuration();
|
||||
float curTick = GameManager::instance().getTickTimer();
|
||||
float progress = curTick / tickDur;
|
||||
@@ -471,16 +516,40 @@ void QSceneRenderer::drawTickBar(QPainter &painter)
|
||||
painter.drawText(QRect(barRect.left() + 10, barRect.top() + 4, 120, 14), Qt::AlignLeft | Qt::AlignVCenter, tickLabel);
|
||||
|
||||
// Progress bar
|
||||
QRect progBg(barRect.left() + 10, barRect.top() + 20, 140, 6);
|
||||
QRect progBg(barRect.left() + 10, barRect.top() + 20, 130, 6);
|
||||
painter.setBrush(QColor(30, 40, 55));
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.drawRoundedRect(progBg, 3, 3);
|
||||
|
||||
QRect progFg(barRect.left() + 10, barRect.top() + 20, (int)(140 * progress), 6);
|
||||
QRect progFg(barRect.left() + 10, barRect.top() + 20, (int)(130 * progress), 6);
|
||||
painter.setBrush(QColor(60, 180, 255));
|
||||
painter.drawRoundedRect(progFg, 3, 3);
|
||||
|
||||
// Sim Speed Buttons [1x] [2x] [5x]
|
||||
// 2. Player 1 National Resources (Empire-wide)
|
||||
const Player *p1 = GameManager::instance().players().getPlayer(1);
|
||||
if (p1) {
|
||||
struct ResItem { QString icon; int val; QColor col; };
|
||||
std::vector<ResItem> natRes = {
|
||||
{"images/resources/cultuur.png", p1->culture, QColor(215, 110, 255)},
|
||||
{"images/resources/kennis.png", p1->science, QColor(60, 190, 255)},
|
||||
{"images/resources/goud.png", p1->gold, QColor(255, 210, 50)},
|
||||
{"images/resources/geloof.png", p1->faith, QColor(255, 230, 80)}
|
||||
};
|
||||
|
||||
int startX = barRect.left() + 155;
|
||||
int itemW = 86;
|
||||
for (size_t i = 0; i < natRes.size(); ++i) {
|
||||
int ix = startX + (int)i * itemW;
|
||||
QImage icon = getTintedIcon(natRes[i].icon, natRes[i].col);
|
||||
painter.drawImage(QRect(ix, barRect.top() + 9, 16, 16), icon);
|
||||
|
||||
painter.setFont(QFont("sans-serif", 8, QFont::Bold));
|
||||
painter.setPen(natRes[i].col);
|
||||
painter.drawText(QRect(ix + 20, barRect.top() + 8, itemW - 22, 18), Qt::AlignLeft | Qt::AlignVCenter, QString("%L1").arg(natRes[i].val));
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Sim Speed Buttons [1x] [2x] [5x]
|
||||
float curSpeed = GameManager::instance().getSimSpeed();
|
||||
m_speed1xRect = QRect(barRect.right() - 150, barRect.top() + 6, 42, 22);
|
||||
m_speed2xRect = QRect(barRect.right() - 102, barRect.top() + 6, 42, 22);
|
||||
@@ -534,9 +603,18 @@ void QSceneRenderer::drawUnitsOverlay(QPainter &painter, const QMatrix4x4 &vMatr
|
||||
|
||||
// 2. Draw Units: Teardrop (Druppel in secondary color) + Icon (in primary color)
|
||||
const float R = 14.0f; // Head circle radius
|
||||
City *activeCityView = GameManager::instance().getActiveCityView();
|
||||
int maxCityRadius = TerrainYieldManager::instance().getCityConfig().maxWorkableRadius;
|
||||
|
||||
for (const auto &unit : GameManager::instance().getUnits()) {
|
||||
if (unit->isDead()) continue;
|
||||
|
||||
if (activeCityView) {
|
||||
if (Pathfinder::hexDistance(activeCityView->tileX, activeCityView->tileY, unit->tileX, unit->tileY) > maxCityRadius) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
bool inFront = false;
|
||||
QVector3D topCylinder = unit->worldPos + QVector3D(0.0f, 0.0f, 0.38f);
|
||||
QPointF tipPos = UnitRenderer::projectToScreen(topCylinder, Projection, vMatrix, width(), height(), inFront);
|
||||
@@ -589,6 +667,19 @@ void QSceneRenderer::drawUnitsOverlay(QPainter &painter, const QMatrix4x4 &vMatr
|
||||
painter.setBrush(hpFrac > 0.5f ? QColor(70, 220, 90) : (hpFrac > 0.25f ? QColor(255, 190, 40) : QColor(240, 50, 50)));
|
||||
painter.drawRoundedRect(hpFg, 1.5, 1.5);
|
||||
|
||||
// Building progress bar if unit is constructing
|
||||
if (unit->isBuilding()) {
|
||||
QRectF bpBg(headCenter.x() - 14.0f, headCenter.y() - R - 1.5f, 28.0f, 3.5f);
|
||||
painter.setBrush(QColor(30, 30, 30, 220));
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.drawRoundedRect(bpBg, 1.5, 1.5);
|
||||
|
||||
float bpFrac = std::max(0.0f, std::min(1.0f, unit->buildProgress));
|
||||
QRectF bpFg(headCenter.x() - 14.0f, headCenter.y() - R - 1.5f, 28.0f * bpFrac, 3.5f);
|
||||
painter.setBrush(QColor(255, 185, 45));
|
||||
painter.drawRoundedRect(bpFg, 1.5, 1.5);
|
||||
}
|
||||
|
||||
// If unit is selected: pulsing selection halo & floating action bubbles
|
||||
if (unit.get() == selectedUnit) {
|
||||
float pulse = (std::sin(m_elapsedTimer.elapsed() * 0.008f) + 1.0f) * 0.5f;
|
||||
@@ -624,6 +715,15 @@ void QSceneRenderer::drawUnitsOverlay(QPainter &painter, const QMatrix4x4 &vMatr
|
||||
isActive = (GameManager::instance().currentActionMode() == ActionMode::BuildCity);
|
||||
iconFile = "images/actions/build_city.png";
|
||||
tip = "Steden bouwen (B)";
|
||||
} else if (act == "boerderij_bouwen" || act == "build_farm") {
|
||||
isActive = unit->isBuilding();
|
||||
iconFile = "images/actions/farm.png";
|
||||
if (unit->isBuilding()) {
|
||||
int pct = (int)(std::min(1.0f, unit->buildProgress) * 100.0f);
|
||||
tip = QString("Aan het bouwen... %1%").arg(pct);
|
||||
} else {
|
||||
tip = "Boerderij bouwen (F)";
|
||||
}
|
||||
}
|
||||
|
||||
// Draw action bubble
|
||||
@@ -822,6 +922,17 @@ void QSceneRenderer::drawUnitCard(QPainter &painter, Unit *unit)
|
||||
if (unit->config.isMilitary()) {
|
||||
QString combatInfo = QString("Aanval: %1 | Bereik: %2 hex").arg(unit->config.attackDamage).arg(unit->config.attackRange);
|
||||
painter.drawText(QRect(cardRect.left() + 12, cardRect.top() + 94, 280, 14), Qt::AlignLeft | Qt::AlignVCenter, combatInfo);
|
||||
} else if (unit->isBuilding()) {
|
||||
int pct = (int)(std::min(1.0f, unit->buildProgress) * 100.0f);
|
||||
QString buildInfo = QString("Aan het bouwen... %1%").arg(pct);
|
||||
if (!unit->config.infiniteCharges && unit->buildCharges > 0) {
|
||||
buildInfo += QString(" | Ladingen: %1").arg(unit->buildCharges);
|
||||
}
|
||||
painter.setPen(QColor(255, 210, 80));
|
||||
painter.drawText(QRect(cardRect.left() + 12, cardRect.top() + 94, 280, 14), Qt::AlignLeft | Qt::AlignVCenter, buildInfo);
|
||||
} else if (unit->config.canBuildFarm() && !unit->config.infiniteCharges && unit->buildCharges > 0) {
|
||||
QString builderInfo = QString("Civiele eenheid | Ladingen: %1").arg(unit->buildCharges);
|
||||
painter.drawText(QRect(cardRect.left() + 12, cardRect.top() + 94, 280, 14), Qt::AlignLeft | Qt::AlignVCenter, builderInfo);
|
||||
} else {
|
||||
painter.drawText(QRect(cardRect.left() + 12, cardRect.top() + 94, 280, 14), Qt::AlignLeft | Qt::AlignVCenter, "Civiele eenheid");
|
||||
}
|
||||
@@ -860,6 +971,17 @@ void QSceneRenderer::drawUnitCard(QPainter &painter, Unit *unit)
|
||||
iconFile = "images/actions/build_city.png";
|
||||
label = "Sticht [B]";
|
||||
tip = "Steden bouwen (B)";
|
||||
} else if (act == "boerderij_bouwen" || act == "build_farm") {
|
||||
isActive = unit->isBuilding();
|
||||
iconFile = "images/actions/farm.png";
|
||||
if (unit->isBuilding()) {
|
||||
int pct = (int)(std::min(1.0f, unit->buildProgress) * 100.0f);
|
||||
label = QString("Bouwt %1%").arg(pct);
|
||||
tip = QString("Bezig met bouwen... %1%").arg(pct);
|
||||
} else {
|
||||
label = "Boerderij [F]";
|
||||
tip = "Boerderij bouwen (F)";
|
||||
}
|
||||
}
|
||||
|
||||
// Circular bubble button
|
||||
@@ -883,8 +1005,11 @@ void QSceneRenderer::drawUnitCard(QPainter &painter, Unit *unit)
|
||||
void QSceneRenderer::drawCityBorders(QPainter &painter, const QMatrix4x4 &vMatrix)
|
||||
{
|
||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
City *activeCity = GameManager::instance().getActiveCityView();
|
||||
|
||||
for (const auto &city : GameManager::instance().getCities()) {
|
||||
if (activeCity && city.id != activeCity->id) continue;
|
||||
|
||||
const Player *player = GameManager::instance().players().getPlayer(city.playerId);
|
||||
QColor ribbonColor = player ? player->primaryColor : QColor(255, 215, 60);
|
||||
|
||||
@@ -928,9 +1053,47 @@ void QSceneRenderer::drawCityBorders(QPainter &painter, const QMatrix4x4 &vMatri
|
||||
}
|
||||
}
|
||||
|
||||
void QSceneRenderer::drawImprovementsOverlay(QPainter &painter, const QMatrix4x4 &vMatrix)
|
||||
{
|
||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
|
||||
const auto &improvements = GameManager::instance().getTileImprovements();
|
||||
if (improvements.empty()) return;
|
||||
|
||||
for (const auto &pair : improvements) {
|
||||
const TileImprovement &imp = pair.second;
|
||||
bool inFront = false;
|
||||
QPointF screenP = UnitRenderer::projectToScreen(imp.worldPos + QVector3D(0, 0, 0.20f), Projection, vMatrix, width(), height(), inFront);
|
||||
if (!inFront) continue;
|
||||
|
||||
// Draw styled badge for improvement (Farm)
|
||||
float r = 14.0f;
|
||||
QRect badgeRect((int)(screenP.x() - r), (int)(screenP.y() - r), (int)(r * 2), (int)(r * 2));
|
||||
|
||||
// Background disc
|
||||
painter.setBrush(QColor(24, 30, 20, 235));
|
||||
painter.setPen(QPen(QColor(235, 195, 60), 1.6));
|
||||
painter.drawEllipse(badgeRect);
|
||||
|
||||
// Icon inside disc
|
||||
QImage farmIcon = getTintedIcon("images/actions/farm.png", QColor(255, 220, 75));
|
||||
painter.drawImage(QRectF(screenP.x() - 8, screenP.y() - 8, 16, 16), farmIcon);
|
||||
|
||||
// Label pill under badge
|
||||
QRect labelRect((int)screenP.x() - 26, (int)screenP.y() + 13, 52, 13);
|
||||
painter.setBrush(QColor(15, 20, 28, 220));
|
||||
painter.setPen(QPen(QColor(200, 165, 50, 150), 1.0));
|
||||
painter.drawRoundedRect(labelRect, 3, 3);
|
||||
painter.setFont(QFont("sans-serif", 6, QFont::Bold));
|
||||
painter.setPen(QColor(255, 230, 130));
|
||||
painter.drawText(labelRect, Qt::AlignCenter, "Boerderij");
|
||||
}
|
||||
}
|
||||
|
||||
void QSceneRenderer::drawCityViewOverlay(QPainter &painter, const QMatrix4x4 &vMatrix)
|
||||
{
|
||||
m_citySlotHits.clear();
|
||||
m_cityBuyTileHits.clear();
|
||||
City *city = GameManager::instance().getActiveCityView();
|
||||
if (!city) return;
|
||||
|
||||
@@ -943,8 +1106,8 @@ void QSceneRenderer::drawCityViewOverlay(QPainter &painter, const QMatrix4x4 &vM
|
||||
int maxRadius = TerrainYieldManager::instance().getCityConfig().maxWorkableRadius; // 3 deep hexagons
|
||||
|
||||
// 1. Render all hexes up to 3 distance around the city
|
||||
for (int dx = -maxRadius - 1; dx <= maxRadius + 1; ++dx) {
|
||||
for (int dy = -maxRadius - 1; dy <= maxRadius + 1; ++dy) {
|
||||
for (int dx = -maxRadius - 2; dx <= maxRadius + 2; ++dx) {
|
||||
for (int dy = -maxRadius - 2; dy <= maxRadius + 2; ++dy) {
|
||||
int hx = city->tileX + dx;
|
||||
int hy = city->tileY + dy;
|
||||
if (hx < 0 || hx >= MAP_WIDTH || hy < 0 || hy >= MAP_HEIGHT) continue;
|
||||
@@ -996,6 +1159,13 @@ void QSceneRenderer::drawCityViewOverlay(QPainter &painter, const QMatrix4x4 &vM
|
||||
bool isWorked = city->isTileWorked(hx, hy);
|
||||
QString res = isWorked ? city->getWorkedResource(hx, hy) : yCfg->primaryResource;
|
||||
int amount = yCfg->getYield(res);
|
||||
const TileImprovement *imp = GameManager::instance().getImprovementAt(hx, hy);
|
||||
if (imp) {
|
||||
const ImprovementConfig *iCfg = TerrainYieldManager::instance().getImprovementConfig(imp->improvementId);
|
||||
if (iCfg && iCfg->yieldBonus.contains(res)) {
|
||||
amount += iCfg->yieldBonus.value(res, 0);
|
||||
}
|
||||
}
|
||||
|
||||
float slotR = 15.0f;
|
||||
QRect slotRect((int)(sCenter.x() - slotR), (int)(sCenter.y() - slotR), (int)(slotR * 2), (int)(slotR * 2));
|
||||
@@ -1010,6 +1180,12 @@ void QSceneRenderer::drawCityViewOverlay(QPainter &painter, const QMatrix4x4 &vM
|
||||
QImage rIcon = getTintedIcon("images/resources/" + res + ".png", isWorked ? QColor(80, 255, 120) : QColor(180, 210, 245));
|
||||
painter.drawImage(QRectF(sCenter.x() - 8.5, sCenter.y() - 8.5, 17, 17), rIcon);
|
||||
|
||||
// If tile has farm/improvement, draw small icon on upper right of slot
|
||||
if (imp) {
|
||||
QImage fIcon = getTintedIcon("images/actions/farm.png", QColor(255, 215, 60));
|
||||
painter.drawImage(QRectF(sCenter.x() + 8, sCenter.y() - 16, 14, 14), fIcon);
|
||||
}
|
||||
|
||||
// Yield pill below slot
|
||||
QRect pillRect((int)sCenter.x() - 26, (int)sCenter.y() + 16, 52, 14);
|
||||
painter.setBrush(QColor(15, 20, 28, 220));
|
||||
@@ -1029,33 +1205,64 @@ void QSceneRenderer::drawCityViewOverlay(QPainter &painter, const QMatrix4x4 &vM
|
||||
}
|
||||
// If not workable (e.g. Sneeuw / Snow): no slot is shown, as requested!
|
||||
} else {
|
||||
// Outside borders: draw subtle locked badge
|
||||
QRect lockRect((int)sCenter.x() - 12, (int)sCenter.y() - 7, 24, 14);
|
||||
painter.setBrush(QColor(20, 24, 32, 180));
|
||||
painter.setPen(QPen(QColor(100, 120, 140, 100), 1.0));
|
||||
painter.drawRoundedRect(lockRect, 3, 3);
|
||||
painter.setFont(QFont("sans-serif", 6, QFont::Bold));
|
||||
painter.setPen(QColor(140, 155, 175));
|
||||
painter.drawText(lockRect, Qt::AlignCenter, "SLOT");
|
||||
int otherCityId = -1;
|
||||
bool isOwnedByOther = GameManager::instance().isTileOwned(hx, hy, &otherCityId);
|
||||
if (!isOwnedByOther) {
|
||||
// Unowned tile: Can be bought with culture!
|
||||
int cultureCost = TerrainYieldManager::instance().getCityConfig().tileCultureCost;
|
||||
bool canAfford = (player && player->culture >= cultureCost);
|
||||
|
||||
QRect buyRect((int)sCenter.x() - 34, (int)sCenter.y() - 14, 68, 28);
|
||||
m_cityBuyTileHits.push_back({buyRect, hx, hy});
|
||||
|
||||
// Background & border
|
||||
painter.setBrush(canAfford ? QColor(40, 20, 55, 235) : QColor(24, 18, 30, 215));
|
||||
painter.setPen(QPen(canAfford ? QColor(220, 110, 255) : QColor(120, 80, 140), canAfford ? 1.8 : 1.0));
|
||||
painter.drawRoundedRect(buyRect, 5, 5);
|
||||
|
||||
// Culture icon
|
||||
QImage cultIcon = getTintedIcon("images/resources/cultuur.png", canAfford ? QColor(225, 120, 255) : QColor(150, 110, 170));
|
||||
painter.drawImage(QRect(buyRect.left() + 4, buyRect.top() + 6, 16, 16), cultIcon);
|
||||
|
||||
// Price text
|
||||
painter.setFont(QFont("sans-serif", 7, QFont::Bold));
|
||||
painter.setPen(canAfford ? QColor(255, 230, 255) : QColor(165, 145, 180));
|
||||
QString costStr = QString("%L1").arg(cultureCost);
|
||||
painter.drawText(QRect(buyRect.left() + 22, buyRect.top() + 2, buyRect.width() - 24, 13), Qt::AlignLeft | Qt::AlignVCenter, costStr);
|
||||
|
||||
// "Koop" subtitle
|
||||
painter.setFont(QFont("sans-serif", 6, QFont::Bold));
|
||||
painter.setPen(canAfford ? QColor(220, 140, 255) : QColor(140, 120, 155));
|
||||
painter.drawText(QRect(buyRect.left() + 22, buyRect.top() + 14, buyRect.width() - 24, 12), Qt::AlignLeft | Qt::AlignVCenter, "KOOP");
|
||||
} else {
|
||||
// Owned by another city
|
||||
QRect lockRect((int)sCenter.x() - 16, (int)sCenter.y() - 8, 32, 16);
|
||||
painter.setBrush(QColor(40, 20, 20, 190));
|
||||
painter.setPen(QPen(QColor(160, 60, 60, 140), 1.0));
|
||||
painter.drawRoundedRect(lockRect, 3, 3);
|
||||
painter.setFont(QFont("sans-serif", 6, QFont::Bold));
|
||||
painter.setPen(QColor(230, 130, 130));
|
||||
painter.drawText(lockRect, Qt::AlignCenter, "BEZET");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. City Management Top Card
|
||||
int cardW = 500, cardH = 145;
|
||||
QRect cardRect((width() - cardW) / 2, 16, cardW, cardH);
|
||||
int cardW = 560, cardH = 174;
|
||||
QRect cardRect((width() - cardW) / 2, 14, cardW, cardH);
|
||||
|
||||
painter.setBrush(QColor(16, 22, 32, 242));
|
||||
painter.setBrush(QColor(16, 22, 32, 245));
|
||||
painter.setPen(QPen(primaryCol, 1.8));
|
||||
painter.drawRoundedRect(cardRect, 10, 10);
|
||||
|
||||
// City Title & Close Button
|
||||
painter.setFont(QFont("sans-serif", 13, QFont::Bold));
|
||||
painter.setPen(QColor(245, 250, 255));
|
||||
painter.drawText(QRect(cardRect.left() + 16, cardRect.top() + 10, 260, 24), Qt::AlignLeft | Qt::AlignVCenter, city->name.toUpper() + " - STADSBEHEER");
|
||||
painter.drawText(QRect(cardRect.left() + 16, cardRect.top() + 8, 300, 24), Qt::AlignLeft | Qt::AlignVCenter, city->name.toUpper() + " - STADSBEHEER");
|
||||
|
||||
// Close button [X]
|
||||
m_closeCityViewBtnRect = QRect(cardRect.right() - 88, cardRect.top() + 10, 74, 24);
|
||||
m_closeCityViewBtnRect = QRect(cardRect.right() - 88, cardRect.top() + 8, 74, 24);
|
||||
painter.setBrush(QColor(40, 50, 68, 220));
|
||||
painter.setPen(QPen(secondaryCol, 1.2));
|
||||
painter.drawRoundedRect(m_closeCityViewBtnRect, 4, 4);
|
||||
@@ -1063,22 +1270,34 @@ void QSceneRenderer::drawCityViewOverlay(QPainter &painter, const QMatrix4x4 &vM
|
||||
painter.setPen(QColor(220, 235, 255));
|
||||
painter.drawText(m_closeCityViewBtnRect, Qt::AlignCenter, "[X] Sluiten");
|
||||
|
||||
// Population Stats Row
|
||||
// Row 1: Population & Local Resources (Food & Labor are local to the city)
|
||||
int totalPop = city->getTotalPop();
|
||||
int assigned = city->getAssignedPop();
|
||||
int avail = city->getAvailablePop();
|
||||
|
||||
painter.setFont(QFont("sans-serif", 9));
|
||||
painter.setFont(QFont("sans-serif", 8));
|
||||
painter.setPen(QColor(180, 205, 235));
|
||||
QString popInfo = QString("Inwoners: %L1 | Totale Pop: %2 | Aan het werk: %3 | Beschikbaar: %4")
|
||||
.arg(city->inhabitants).arg(totalPop).arg(assigned).arg(avail);
|
||||
painter.drawText(QRect(cardRect.left() + 16, cardRect.top() + 38, cardRect.width() - 32, 18), Qt::AlignLeft | Qt::AlignVCenter, popInfo);
|
||||
QString popInfo = QString("Lokaal: Inwoners: %L1 (%2 Pop: %3 aan het werk, %4 vrij) | Arbeid: %L5")
|
||||
.arg(city->inhabitants).arg(totalPop).arg(assigned).arg(avail).arg(city->accumulatedLabor);
|
||||
painter.drawText(QRect(cardRect.left() + 16, cardRect.top() + 34, cardRect.width() - 32, 16), Qt::AlignLeft | Qt::AlignVCenter, popInfo);
|
||||
|
||||
// Row 2: National Resources (Culture, Science, Gold, Faith belong to the nation)
|
||||
int cult = player ? player->culture : 0;
|
||||
int sci = player ? player->science : 0;
|
||||
int gld = player ? player->gold : 0;
|
||||
int fth = player ? player->faith : 0;
|
||||
|
||||
painter.setFont(QFont("sans-serif", 8, QFont::Bold));
|
||||
painter.setPen(QColor(220, 160, 255));
|
||||
QString natInfo = QString("Nationaal: Cultuur: %L1 | Kennis: %L2 | Goud: %L3 | Geloof: %L4")
|
||||
.arg(cult).arg(sci).arg(gld).arg(fth);
|
||||
painter.drawText(QRect(cardRect.left() + 16, cardRect.top() + 52, cardRect.width() - 32, 16), Qt::AlignLeft | Qt::AlignVCenter, natInfo);
|
||||
|
||||
// Separator line
|
||||
painter.setPen(QPen(QColor(60, 85, 120, 150), 1.0));
|
||||
painter.drawLine(cardRect.left() + 16, cardRect.top() + 60, cardRect.right() - 16, cardRect.top() + 60);
|
||||
painter.drawLine(cardRect.left() + 16, cardRect.top() + 72, cardRect.right() - 16, cardRect.top() + 72);
|
||||
|
||||
// Total Yields Summary Row
|
||||
// Row 3: Total Yields Summary Row (Base 500 + worked tiles)
|
||||
CityYieldSummary summary = GameManager::instance().calculateCityYields(*city, m_point, m_numPoints05);
|
||||
|
||||
struct YieldItem { QString name; int val; QString icon; QColor col; };
|
||||
@@ -1094,29 +1313,29 @@ void QSceneRenderer::drawCityViewOverlay(QPainter &painter, const QMatrix4x4 &vM
|
||||
int colW = (cardRect.width() - 32) / 6;
|
||||
for (size_t i = 0; i < yields.size(); ++i) {
|
||||
int cx = cardRect.left() + 16 + (int)i * colW;
|
||||
QRect yRect(cx, cardRect.top() + 68, colW - 6, 44);
|
||||
QRect yRect(cx, cardRect.top() + 78, colW - 6, 42);
|
||||
|
||||
painter.setBrush(QColor(12, 16, 24, 180));
|
||||
painter.setPen(QPen(QColor(40, 60, 85, 120), 1.0));
|
||||
painter.drawRoundedRect(yRect, 4, 4);
|
||||
|
||||
QImage icon = getTintedIcon(yields[i].icon, yields[i].col);
|
||||
painter.drawImage(QRect(yRect.left() + 6, yRect.top() + 6, 16, 16), icon);
|
||||
painter.drawImage(QRect(yRect.left() + 6, yRect.top() + 5, 16, 16), icon);
|
||||
|
||||
painter.setFont(QFont("sans-serif", 7));
|
||||
painter.setPen(QColor(160, 180, 205));
|
||||
painter.drawText(QRect(yRect.left() + 26, yRect.top() + 6, yRect.width() - 28, 16), Qt::AlignLeft | Qt::AlignVCenter, yields[i].name);
|
||||
painter.drawText(QRect(yRect.left() + 26, yRect.top() + 5, yRect.width() - 28, 16), Qt::AlignLeft | Qt::AlignVCenter, yields[i].name);
|
||||
|
||||
painter.setFont(QFont("sans-serif", 9, QFont::Bold));
|
||||
painter.setFont(QFont("sans-serif", 8, QFont::Bold));
|
||||
painter.setPen(yields[i].col);
|
||||
painter.drawText(QRect(yRect.left() + 6, yRect.top() + 24, yRect.width() - 12, 16), Qt::AlignLeft | Qt::AlignVCenter, QString("+%1").arg(yields[i].val));
|
||||
painter.drawText(QRect(yRect.left() + 6, yRect.top() + 22, yRect.width() - 12, 16), Qt::AlignLeft | Qt::AlignVCenter, QString("+%1/tick").arg(yields[i].val));
|
||||
}
|
||||
|
||||
// Help instruction at bottom
|
||||
painter.setFont(QFont("sans-serif", 7, QFont::StyleItalic));
|
||||
painter.setPen(QColor(140, 170, 200));
|
||||
painter.drawText(QRect(cardRect.left() + 16, cardRect.bottom() - 20, cardRect.width() - 32, 16), Qt::AlignCenter,
|
||||
"Klik op een tegel binnen de grenzen om 1 Pop toe te wijzen of vrij te maken.");
|
||||
painter.drawText(QRect(cardRect.left() + 16, cardRect.bottom() - 18, cardRect.width() - 32, 16), Qt::AlignCenter,
|
||||
"Klik op een tegel om 1 Pop toe te wijzen | Klik op een vrije tegel om deze te kopen met Cultuur.");
|
||||
}
|
||||
|
||||
void QSceneRenderer::mousePressEvent(QMouseEvent *event)
|
||||
@@ -1124,6 +1343,9 @@ void QSceneRenderer::mousePressEvent(QMouseEvent *event)
|
||||
// 0. If in City View: handle close button and slot clicks
|
||||
if (GameManager::instance().isInCityView()) {
|
||||
if (m_closeCityViewBtnRect.contains(event->pos())) {
|
||||
cameraPosition = m_savedCamPos;
|
||||
m_zoom = m_savedZoom;
|
||||
zRot = m_savedZRot;
|
||||
GameManager::instance().exitCityView();
|
||||
update();
|
||||
return;
|
||||
@@ -1151,17 +1373,28 @@ void QSceneRenderer::mousePressEvent(QMouseEvent *event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Buy tile clicks
|
||||
for (const auto &buyHit : m_cityBuyTileHits) {
|
||||
if (buyHit.rect.contains(event->pos())) {
|
||||
City *city = GameManager::instance().getActiveCityView();
|
||||
if (city) {
|
||||
GameManager::instance().buyTileForCity(city->id, buyHit.tx, buyHit.ty);
|
||||
update();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
return; // In City View, ignore all other clicks
|
||||
}
|
||||
|
||||
// Check if clicked on a City Banner to open City View
|
||||
for (const auto &cb : m_cityBannerHits) {
|
||||
if (cb.rect.contains(event->pos())) {
|
||||
m_savedCamPos = cameraPosition;
|
||||
m_savedZoom = m_zoom;
|
||||
m_savedZRot = zRot;
|
||||
GameManager::instance().enterCityView(cb.cityId);
|
||||
City *c = GameManager::instance().getCityById(cb.cityId);
|
||||
if (c) {
|
||||
cameraPosition.setX(c->worldPos.x());
|
||||
cameraPosition.setY(c->worldPos.y() - 2.8f);
|
||||
}
|
||||
update();
|
||||
return;
|
||||
}
|
||||
@@ -1229,6 +1462,8 @@ void QSceneRenderer::mousePressEvent(QMouseEvent *event)
|
||||
}
|
||||
} else if (ab.actionId == "steden_bouwen" || ab.actionId == "build_city") {
|
||||
GameManager::instance().commandBuildCitySelected(m_point, m_numPoints05);
|
||||
} else if (ab.actionId == "boerderij_bouwen" || ab.actionId == "build_farm") {
|
||||
GameManager::instance().commandBuildFarmSelected(m_point, m_numPoints05);
|
||||
}
|
||||
update();
|
||||
return;
|
||||
@@ -1238,11 +1473,7 @@ void QSceneRenderer::mousePressEvent(QMouseEvent *event)
|
||||
// Compute view matrix for raycasting and 3D screen tests
|
||||
QMatrix4x4 vMatrix;
|
||||
cameraUpDirection = cameraTransformation * QVector3D(0, 0, 1);
|
||||
QVector3D lookat = QVector3D(
|
||||
cameraPosition.x() + m_zoom * m_zoom / 5.0f * cos(PI * zRot / 180.0f),
|
||||
cameraPosition.y() + m_zoom * m_zoom / 5.0f * sin(PI * zRot / 180.0f),
|
||||
0
|
||||
);
|
||||
QVector3D lookat = getCameraLookAt();
|
||||
vMatrix.lookAt(cameraPosition, lookat, cameraUpDirection);
|
||||
|
||||
// 5. Left Click: Check if directly clicked on any unit's Druppel pin
|
||||
@@ -1304,9 +1535,10 @@ void QSceneRenderer::mousePressEvent(QMouseEvent *event)
|
||||
} else {
|
||||
City *cityOnTile = GameManager::instance().getCityAt(hitX, hitY);
|
||||
if (cityOnTile) {
|
||||
m_savedCamPos = cameraPosition;
|
||||
m_savedZoom = m_zoom;
|
||||
m_savedZRot = zRot;
|
||||
GameManager::instance().enterCityView(cityOnTile->id);
|
||||
cameraPosition.setX(cityOnTile->worldPos.x());
|
||||
cameraPosition.setY(cityOnTile->worldPos.y() - 2.8f);
|
||||
} else if (!m_unitCardRect.contains(event->pos())) {
|
||||
GameManager::instance().deselectUnit();
|
||||
}
|
||||
@@ -1330,6 +1562,12 @@ void QSceneRenderer::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
m_mouseCurrentPos = event->pos();
|
||||
|
||||
// Disable panning, rotation and minimap drag in City View
|
||||
if (GameManager::instance().isInCityView()) {
|
||||
update();
|
||||
return;
|
||||
}
|
||||
|
||||
QRect mapRect = getMinimapRect();
|
||||
if (m_draggingMinimap && (event->buttons() & Qt::LeftButton)) {
|
||||
QVector2D wPos = minimapToWorld(event->pos(), mapRect);
|
||||
@@ -1357,6 +1595,18 @@ void QSceneRenderer::mouseReleaseEvent(QMouseEvent *event)
|
||||
|
||||
void QSceneRenderer::keyPressEvent(QKeyEvent *e)
|
||||
{
|
||||
// In City View: disable WASD panning and Q/E rotation; only allow Escape to exit
|
||||
if (GameManager::instance().isInCityView()) {
|
||||
if (e->key() == Qt::Key_Escape) {
|
||||
cameraPosition = m_savedCamPos;
|
||||
m_zoom = m_savedZoom;
|
||||
zRot = m_savedZRot;
|
||||
GameManager::instance().exitCityView();
|
||||
update();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
float speed = (e->modifiers() & Qt::ShiftModifier) ? 1.2f : 0.4f;
|
||||
|
||||
// Movement & camera controls
|
||||
@@ -1400,10 +1650,11 @@ void QSceneRenderer::keyPressEvent(QKeyEvent *e)
|
||||
else if (e->key() == Qt::Key_B && sel && sel->config.canBuildCity()) {
|
||||
GameManager::instance().commandBuildCitySelected(m_point, m_numPoints05);
|
||||
}
|
||||
else if (e->key() == Qt::Key_F && sel && sel->config.canBuildFarm()) {
|
||||
GameManager::instance().commandBuildFarmSelected(m_point, m_numPoints05);
|
||||
}
|
||||
else if (e->key() == Qt::Key_Escape) {
|
||||
if (GameManager::instance().isInCityView()) {
|
||||
GameManager::instance().exitCityView();
|
||||
} else if (GameManager::instance().currentActionMode() != ActionMode::None) {
|
||||
if (GameManager::instance().currentActionMode() != ActionMode::None) {
|
||||
GameManager::instance().setActionMode(ActionMode::None);
|
||||
} else {
|
||||
GameManager::instance().deselectUnit();
|
||||
@@ -1425,6 +1676,11 @@ void QSceneRenderer::keyPressEvent(QKeyEvent *e)
|
||||
|
||||
void QSceneRenderer::wheelEvent(QWheelEvent *e)
|
||||
{
|
||||
// Disable zooming in City View
|
||||
if (GameManager::instance().isInCityView()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QRect card = getMinimapCardRect();
|
||||
if (card.contains(e->pos()) || getZoomOutButtonRect().contains(e->pos()) || getZoomInButtonRect().contains(e->pos())) {
|
||||
if (e->delta() > 0) {
|
||||
@@ -1437,8 +1693,8 @@ void QSceneRenderer::wheelEvent(QWheelEvent *e)
|
||||
}
|
||||
|
||||
m_zoom -= e->delta() / 100.0f;
|
||||
if (m_zoom < 2.0f) m_zoom = 2.0f;
|
||||
if (m_zoom > 16.0f) m_zoom = 16.0f;
|
||||
if (m_zoom < m_cameraMinZoom) m_zoom = m_cameraMinZoom;
|
||||
if (m_zoom > m_cameraMaxZoom) m_zoom = m_cameraMaxZoom;
|
||||
update();
|
||||
}
|
||||
|
||||
@@ -1462,17 +1718,38 @@ void QSceneRenderer::paintGL()
|
||||
QMatrix4x4 cameraTrans;
|
||||
QMatrix4x4 vMatrix;
|
||||
|
||||
City *cityView = GameManager::instance().getActiveCityView();
|
||||
int cityViewRadius = cityView ? TerrainYieldManager::instance().getCityConfig().maxWorkableRadius : -1;
|
||||
|
||||
cameraTrans.rotate(zRot, 0, 0, 1);
|
||||
cameraUpDirection = cameraTrans * QVector3D(0, 0, 1);
|
||||
cameraPosition.setZ(m_zoom);
|
||||
|
||||
QVector3D lookat = QVector3D(
|
||||
cameraPosition.x() + m_zoom * m_zoom / 5.0f * cos(PI * zRot / 180.0f),
|
||||
cameraPosition.y() + m_zoom * m_zoom / 5.0f * sin(PI * zRot / 180.0f),
|
||||
0.0f
|
||||
);
|
||||
vMatrix.lookAt(cameraPosition, lookat, cameraUpDirection);
|
||||
QVector3D eyePos;
|
||||
QVector3D lookat;
|
||||
|
||||
if (cityView) {
|
||||
float effectiveZoom = TerrainYieldManager::instance().getCityConfig().cityViewZoom;
|
||||
float pitchDist = effectiveZoom * effectiveZoom / 5.0f;
|
||||
float rad = zRot * PI / 180.0f;
|
||||
lookat = cityView->worldPos;
|
||||
eyePos = QVector3D(
|
||||
lookat.x() - pitchDist * cos(rad),
|
||||
lookat.y() - pitchDist * sin(rad),
|
||||
effectiveZoom
|
||||
);
|
||||
cameraPosition = eyePos;
|
||||
} else {
|
||||
cameraPosition.setZ(m_zoom);
|
||||
eyePos = cameraPosition;
|
||||
lookat = getCameraLookAt();
|
||||
}
|
||||
vMatrix.lookAt(eyePos, lookat, cameraUpDirection);
|
||||
|
||||
if (cityView) {
|
||||
glClearColor(0.06f, 0.08f, 0.12f, 1.0f);
|
||||
} else {
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
}
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
// Update simulation
|
||||
@@ -1482,12 +1759,13 @@ void QSceneRenderer::paintGL()
|
||||
GameManager::instance().update(deltaTime, m_point, m_numPoints05);
|
||||
|
||||
// Visible terrain tiles bounding box
|
||||
float minWorldX = std::min(cameraPosition.x(), lookat.x());
|
||||
float maxWorldX = std::max(cameraPosition.x(), lookat.x());
|
||||
float minWorldY = std::min(cameraPosition.y(), lookat.y());
|
||||
float maxWorldY = std::max(cameraPosition.y(), lookat.y());
|
||||
float minWorldX = std::min(eyePos.x(), lookat.x());
|
||||
float maxWorldX = std::max(eyePos.x(), lookat.x());
|
||||
float minWorldY = std::min(eyePos.y(), lookat.y());
|
||||
float maxWorldY = std::max(eyePos.y(), lookat.y());
|
||||
|
||||
float margin = std::max(m_zoom * 3.0f + 15.0f, 35.0f);
|
||||
// Margin around visible focal area (provides ample buffer for mountains and rotating view)
|
||||
float margin = cityView ? 20.0f : std::max(10.0f, m_zoom * 1.4f + 4.0f);
|
||||
minWorldX -= margin;
|
||||
maxWorldX += margin;
|
||||
minWorldY -= margin;
|
||||
@@ -1518,10 +1796,13 @@ void QSceneRenderer::paintGL()
|
||||
}
|
||||
}
|
||||
|
||||
// 1. Render terrain tiles
|
||||
// 1. Render terrain tiles (in city view: ONLY render tiles within cityViewRadius!)
|
||||
for (int y = minTileY; y <= maxTileY; ++y) {
|
||||
for (int x = minTileX; x <= maxTileX; ++x) {
|
||||
tiles->operator()(x, y).paint(Projection, cameraPosition, cameraUpDirection, vMatrix, currentTime);
|
||||
if (cityView && Pathfinder::hexDistance(cityView->tileX, cityView->tileY, x, y) > cityViewRadius) {
|
||||
continue;
|
||||
}
|
||||
tiles->operator()(x, y).paint(Projection, eyePos, cameraUpDirection, vMatrix, currentTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1538,22 +1819,29 @@ void QSceneRenderer::paintGL()
|
||||
Projection,
|
||||
vMatrix,
|
||||
vSunPos,
|
||||
selected ? selected->id : -1
|
||||
selected ? selected->id : -1,
|
||||
cityViewRadius,
|
||||
cityView ? cityView->tileX : -1,
|
||||
cityView ? cityView->tileY : -1
|
||||
);
|
||||
|
||||
// 3. Render 2D UI & HUD Overlays via QPainter
|
||||
QPainter painter(this);
|
||||
drawCityBorders(painter, vMatrix);
|
||||
drawPathAndRange(painter, selected, vMatrix);
|
||||
drawMinimap(painter);
|
||||
drawTickBar(painter);
|
||||
drawUnitsOverlay(painter, vMatrix);
|
||||
drawFloatingTexts(painter, vMatrix);
|
||||
|
||||
if (GameManager::instance().isInCityView()) {
|
||||
drawImprovementsOverlay(painter, vMatrix);
|
||||
if (!cityView) {
|
||||
drawPathAndRange(painter, selected, vMatrix);
|
||||
drawMinimap(painter);
|
||||
drawTickBar(painter);
|
||||
drawUnitsOverlay(painter, vMatrix);
|
||||
drawFloatingTexts(painter, vMatrix);
|
||||
if (selected) {
|
||||
drawUnitCard(painter, selected);
|
||||
}
|
||||
} else {
|
||||
drawUnitsOverlay(painter, vMatrix);
|
||||
drawFloatingTexts(painter, vMatrix);
|
||||
drawCityViewOverlay(painter, vMatrix);
|
||||
} else if (selected) {
|
||||
drawUnitCard(painter, selected);
|
||||
}
|
||||
painter.end();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user