This commit is contained in:
bart
2026-11-08 16:15:49 +01:00
parent 5af4581182
commit 1140dd562b
34 changed files with 940 additions and 44 deletions
+321 -18
View File
@@ -503,27 +503,33 @@ void QSceneRenderer::drawTickBar(QPainter &painter)
void QSceneRenderer::drawUnitsOverlay(QPainter &painter, const QMatrix4x4 &vMatrix)
{
m_activeActionBubbles.clear();
m_cityBannerHits.clear();
painter.setRenderHint(QPainter::Antialiasing, true);
Unit *selectedUnit = GameManager::instance().getSelectedUnit();
// 1. Draw City Banners
for (const auto &c : GameManager::instance().getCities()) {
bool inFront = false;
QPointF screenP = UnitRenderer::projectToScreen(c.worldPos + QVector3D(0, 0, 0.65f), Projection, vMatrix, width(), height(), inFront);
if (!inFront) continue;
// 1. Draw City Banners (if not in city view)
if (!GameManager::instance().isInCityView()) {
for (const auto &c : GameManager::instance().getCities()) {
bool inFront = false;
QPointF screenP = UnitRenderer::projectToScreen(c.worldPos + QVector3D(0, 0, 0.65f), Projection, vMatrix, width(), height(), inFront);
if (!inFront) continue;
const Player *pl = GameManager::instance().players().getPlayer(c.playerId);
QColor col = pl ? pl->primaryColor : Qt::blue;
const Player *pl = GameManager::instance().players().getPlayer(c.playerId);
QColor col = pl ? pl->primaryColor : Qt::blue;
QRect bannerRect((int)screenP.x() - 40, (int)screenP.y() - 14, 80, 20);
painter.setBrush(QColor(20, 24, 34, 230));
painter.setPen(QPen(col, 1.8));
painter.drawRoundedRect(bannerRect, 4, 4);
QRect bannerRect((int)screenP.x() - 55, (int)screenP.y() - 14, 110, 22);
painter.setBrush(QColor(20, 24, 34, 235));
painter.setPen(QPen(col, 2.0));
painter.drawRoundedRect(bannerRect, 4, 4);
painter.setFont(QFont("sans-serif", 8, QFont::Bold));
painter.setPen(Qt::white);
painter.drawText(bannerRect, Qt::AlignCenter, c.name);
painter.setFont(QFont("sans-serif", 8, QFont::Bold));
painter.setPen(Qt::white);
QString bannerText = QString("%1 (%2 Pop)").arg(c.name).arg(c.getTotalPop());
painter.drawText(bannerRect, Qt::AlignCenter, bannerText);
m_cityBannerHits.push_back({bannerRect, c.id});
}
}
// 2. Draw Units: Teardrop (Druppel in secondary color) + Icon (in primary color)
@@ -874,8 +880,293 @@ void QSceneRenderer::drawUnitCard(QPainter &painter, Unit *unit)
}
}
void QSceneRenderer::drawCityBorders(QPainter &painter, const QMatrix4x4 &vMatrix)
{
painter.setRenderHint(QPainter::Antialiasing, true);
for (const auto &city : GameManager::instance().getCities()) {
const Player *player = GameManager::instance().players().getPlayer(city.playerId);
QColor ribbonColor = player ? player->primaryColor : QColor(255, 215, 60);
for (const auto &t : city.territory) {
int tx = t.first;
int ty = t.second;
QVector2D center = Pathfinder::hexToWorld(tx, ty);
for (int e = 0; e < 6; ++e) {
int nx, ny;
getHexNeighbor(tx, ty, e, nx, ny);
if (!city.ownsTile(nx, ny)) {
QVector2D p1, p2;
getHexEdgeEndpoints(e, p1, p2);
float wx1 = center.x() + p1.x();
float wy1 = center.y() + p1.y();
float wz1 = Unit::getTerrainHeight(wx1, wy1, m_point, m_numPoints05) + 0.035f;
float wx2 = center.x() + p2.x();
float wy2 = center.y() + p2.y();
float wz2 = Unit::getTerrainHeight(wx2, wy2, m_point, m_numPoints05) + 0.035f;
bool inFront1 = false, inFront2 = false;
QPointF s1 = UnitRenderer::projectToScreen(QVector3D(wx1, wy1, wz1), Projection, vMatrix, width(), height(), inFront1);
QPointF s2 = UnitRenderer::projectToScreen(QVector3D(wx2, wy2, wz2), Projection, vMatrix, width(), height(), inFront2);
if (inFront1 && inFront2) {
QColor glow = ribbonColor;
glow.setAlpha(90);
painter.setPen(QPen(glow, 5.5f, Qt::SolidLine, Qt::RoundCap));
painter.drawLine(s1, s2);
painter.setPen(QPen(ribbonColor, 2.6f, Qt::SolidLine, Qt::RoundCap));
painter.drawLine(s1, s2);
}
}
}
}
}
}
void QSceneRenderer::drawCityViewOverlay(QPainter &painter, const QMatrix4x4 &vMatrix)
{
m_citySlotHits.clear();
City *city = GameManager::instance().getActiveCityView();
if (!city) return;
painter.setRenderHint(QPainter::Antialiasing, true);
const Player *player = GameManager::instance().players().getPlayer(city->playerId);
QColor primaryCol = player ? player->primaryColor : Qt::blue;
QColor secondaryCol = player ? player->secondaryColor : Qt::white;
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) {
int hx = city->tileX + dx;
int hy = city->tileY + dy;
if (hx < 0 || hx >= MAP_WIDTH || hy < 0 || hy >= MAP_HEIGHT) continue;
int dist = Pathfinder::hexDistance(city->tileX, city->tileY, hx, hy);
if (dist > maxRadius) continue;
QVector2D center = Pathfinder::hexToWorld(hx, hy);
float gz = Unit::getTerrainHeight(center.x(), center.y(), m_point, m_numPoints05);
bool inFront = false;
QPointF sCenter = UnitRenderer::projectToScreen(QVector3D(center.x(), center.y(), gz + 0.05f), Projection, vMatrix, width(), height(), inFront);
if (!inFront) continue;
bool isOwned = city->ownsTile(hx, hy);
// Draw hexagon boundary outline
QPolygonF hexPoly;
bool polyValid = true;
for (int e = 0; e < 6; ++e) {
QVector2D p1, p2;
getHexEdgeEndpoints(e, p1, p2);
float px = center.x() + p1.x();
float py = center.y() + p1.y();
float pz = Unit::getTerrainHeight(px, py, m_point, m_numPoints05) + 0.02f;
bool pInFront = false;
QPointF sp = UnitRenderer::projectToScreen(QVector3D(px, py, pz), Projection, vMatrix, width(), height(), pInFront);
if (!pInFront) { polyValid = false; break; }
hexPoly << sp;
}
if (polyValid) {
if (isOwned) {
painter.setPen(QPen(QColor(80, 200, 255, 140), 1.4, Qt::SolidLine));
painter.setBrush(QColor(40, 120, 200, 30));
} else {
painter.setPen(QPen(QColor(120, 130, 145, 90), 1.0, Qt::DashLine));
painter.setBrush(QColor(20, 25, 35, 40));
}
painter.drawPolygon(hexPoly);
}
// Check workability
QString tType = TerrainYieldManager::instance().getTerrainType(hx, hy, m_point, m_numPoints05);
const TerrainYieldConfig *yCfg = TerrainYieldManager::instance().getYieldConfig(tType);
if (isOwned) {
if (yCfg && yCfg->workable) {
// Tile has 1 slot!
bool isWorked = city->isTileWorked(hx, hy);
QString res = isWorked ? city->getWorkedResource(hx, hy) : yCfg->primaryResource;
int amount = yCfg->getYield(res);
float slotR = 15.0f;
QRect slotRect((int)(sCenter.x() - slotR), (int)(sCenter.y() - slotR), (int)(slotR * 2), (int)(slotR * 2));
m_citySlotHits.push_back({slotRect, hx, hy, res});
// Slot circle background
painter.setBrush(isWorked ? QColor(25, 55, 38, 245) : QColor(18, 25, 36, 225));
painter.setPen(QPen(isWorked ? QColor(80, 240, 120) : QColor(140, 175, 215), isWorked ? 2.2 : 1.2));
painter.drawEllipse(sCenter, slotR, slotR);
// Resource icon
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);
// Yield pill below slot
QRect pillRect((int)sCenter.x() - 26, (int)sCenter.y() + 16, 52, 14);
painter.setBrush(QColor(15, 20, 28, 220));
painter.setPen(QPen(isWorked ? QColor(80, 240, 120, 160) : QColor(80, 110, 140, 120), 1.0));
painter.drawRoundedRect(pillRect, 3, 3);
painter.setFont(QFont("sans-serif", 7, QFont::Bold));
painter.setPen(isWorked ? QColor(100, 255, 140) : QColor(180, 205, 235));
painter.drawText(pillRect, Qt::AlignCenter, QString("+%1").arg(amount));
// If unworked and pop is available: show small green +
if (!isWorked && city->getAvailablePop() > 0) {
float pulse = (std::sin(m_elapsedTimer.elapsed() * 0.008f) + 1.0f) * 0.5f;
painter.setBrush(QColor(60, 220, 90, (int)(180 + pulse * 75)));
painter.setPen(Qt::NoPen);
painter.drawEllipse(QPointF(sCenter.x() + 10, sCenter.y() - 10), 4.5, 4.5);
}
}
// 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");
}
}
}
// 2. City Management Top Card
int cardW = 500, cardH = 145;
QRect cardRect((width() - cardW) / 2, 16, cardW, cardH);
painter.setBrush(QColor(16, 22, 32, 242));
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");
// Close button [X]
m_closeCityViewBtnRect = QRect(cardRect.right() - 88, cardRect.top() + 10, 74, 24);
painter.setBrush(QColor(40, 50, 68, 220));
painter.setPen(QPen(secondaryCol, 1.2));
painter.drawRoundedRect(m_closeCityViewBtnRect, 4, 4);
painter.setFont(QFont("sans-serif", 8, QFont::Bold));
painter.setPen(QColor(220, 235, 255));
painter.drawText(m_closeCityViewBtnRect, Qt::AlignCenter, "[X] Sluiten");
// Population Stats Row
int totalPop = city->getTotalPop();
int assigned = city->getAssignedPop();
int avail = city->getAvailablePop();
painter.setFont(QFont("sans-serif", 9));
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);
// 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);
// Total Yields Summary Row
CityYieldSummary summary = GameManager::instance().calculateCityYields(*city, m_point, m_numPoints05);
struct YieldItem { QString name; int val; QString icon; QColor col; };
std::vector<YieldItem> yields = {
{"Voedsel", summary.food, "images/resources/voedsel.png", QColor(100, 230, 110)},
{"Arbeid", summary.labor, "images/resources/arbeid.png", QColor(240, 140, 50)},
{"Kennis", summary.science, "images/resources/kennis.png", QColor(60, 190, 255)},
{"Cultuur", summary.culture, "images/resources/cultuur.png", QColor(200, 110, 240)},
{"Geloof", summary.faith, "images/resources/geloof.png", QColor(255, 230, 80)},
{"Goud", summary.gold, "images/resources/goud.png", QColor(255, 210, 50)}
};
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);
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.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.setFont(QFont("sans-serif", 9, 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));
}
// 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.");
}
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())) {
GameManager::instance().exitCityView();
update();
return;
}
for (const auto &slot : m_citySlotHits) {
if (slot.rect.contains(event->pos())) {
City *city = GameManager::instance().getActiveCityView();
if (city) {
QVector2D w = Pathfinder::hexToWorld(slot.tx, slot.ty);
float gz = Unit::getTerrainHeight(w.x(), w.y(), m_point, m_numPoints05);
if (city->isTileWorked(slot.tx, slot.ty)) {
city->unassignWorker(slot.tx, slot.ty);
GameManager::instance().addFloatingText("Pop vrijgemaakt", QVector3D(w.x(), w.y(), gz + 0.8f), QColor(255, 220, 80));
} else {
if (city->getAvailablePop() > 0) {
city->assignWorker(slot.tx, slot.ty, slot.defaultRes);
GameManager::instance().addFloatingText("+1 Pop aan het werk", QVector3D(w.x(), w.y(), gz + 0.8f), QColor(80, 255, 120));
} else {
GameManager::instance().addFloatingText("Geen vrije Pop!", QVector3D(w.x(), w.y(), gz + 0.8f), QColor(255, 80, 80));
}
}
update();
return;
}
}
}
}
// Check if clicked on a City Banner to open City View
for (const auto &cb : m_cityBannerHits) {
if (cb.rect.contains(event->pos())) {
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;
}
}
// 1. Zoom Out / Reset / In buttons
if (getZoomOutButtonRect().contains(event->pos())) {
m_minimapZoom = std::max(1.0f, m_minimapZoom / 1.5f);
@@ -1010,8 +1301,15 @@ void QSceneRenderer::mousePressEvent(QMouseEvent *event)
Unit *unitOnTile = GameManager::instance().getUnitAt(hitX, hitY);
if (unitOnTile) {
GameManager::instance().selectUnit(unitOnTile->id);
} else if (!m_unitCardRect.contains(event->pos())) {
GameManager::instance().deselectUnit();
} else {
City *cityOnTile = GameManager::instance().getCityAt(hitX, hitY);
if (cityOnTile) {
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();
}
}
update();
return;
@@ -1103,7 +1401,9 @@ void QSceneRenderer::keyPressEvent(QKeyEvent *e)
GameManager::instance().commandBuildCitySelected(m_point, m_numPoints05);
}
else if (e->key() == Qt::Key_Escape) {
if (GameManager::instance().currentActionMode() != ActionMode::None) {
if (GameManager::instance().isInCityView()) {
GameManager::instance().exitCityView();
} else if (GameManager::instance().currentActionMode() != ActionMode::None) {
GameManager::instance().setActionMode(ActionMode::None);
} else {
GameManager::instance().deselectUnit();
@@ -1243,13 +1543,16 @@ void QSceneRenderer::paintGL()
// 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 (selected) {
if (GameManager::instance().isInCityView()) {
drawCityViewOverlay(painter, vMatrix);
} else if (selected) {
drawUnitCard(painter, selected);
}
painter.end();