Implement target-tile selection and Shift-queueing for all unit actions
This commit is contained in:
@@ -127,6 +127,7 @@ void ActionNeededManager::update(float deltaTime, int activePlayerId,
|
|||||||
|
|
||||||
bool isIdle = (u->state == UnitState::Idle)
|
bool isIdle = (u->state == UnitState::Idle)
|
||||||
&& (u->path.empty() || u->pathIndex >= u->path.size())
|
&& (u->path.empty() || u->pathIndex >= u->path.size())
|
||||||
|
&& !u->currentAction.isValid()
|
||||||
&& !u->hasQueuedAction()
|
&& !u->hasQueuedAction()
|
||||||
&& !u->hasRoadRoute()
|
&& !u->hasRoadRoute()
|
||||||
&& !u->isBuilding();
|
&& !u->isBuilding();
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@ movement_tick_duration: 30.0
|
|||||||
|
|
||||||
# Action queue configuration
|
# Action queue configuration
|
||||||
action_queue:
|
action_queue:
|
||||||
max_size: 5
|
max_size: 15
|
||||||
auto_queue_move_when_busy: true
|
auto_queue_move_when_busy: true
|
||||||
allow_action_queueing: true
|
allow_action_queueing: true
|
||||||
|
|
||||||
|
|||||||
+37
-21
@@ -49,25 +49,30 @@ ui:
|
|||||||
queue_cleared: "Queued actions cancelled"
|
queue_cleared: "Queued actions cancelled"
|
||||||
queue_full: "Action queue full!"
|
queue_full: "Action queue full!"
|
||||||
action_queued: "Action queued: %1"
|
action_queued: "Action queued: %1"
|
||||||
|
action_queued_count: "%1 added to queue (#%2)"
|
||||||
|
action_scheduled_after_current: "%1 scheduled after current action"
|
||||||
action_cancelled: "%1 cancelled"
|
action_cancelled: "%1 cancelled"
|
||||||
farm_queued: "Build Farm queued on arrival"
|
farm_queued: "Build Farm queued"
|
||||||
farm_started: "Constructing Farm..."
|
farm_started: "Constructing Farm..."
|
||||||
farm_completed: "Farm completed!"
|
farm_completed: "Farm completed!"
|
||||||
farm_completed_consumed: "Farm completed! (Builder consumed)"
|
farm_completed_consumed: "Farm completed! (Builder consumed)"
|
||||||
farm_completed_charges: "Farm completed! (%1 %2 remaining)"
|
farm_completed_charges: "Farm completed! (%1 %2 remaining)"
|
||||||
road_queued: "Build Road queued on arrival"
|
moving_to_build_farm: "Moving to build Farm at (%1, %2)..."
|
||||||
|
moving_to_build_city: "Moving to found City at (%1, %2)..."
|
||||||
|
moving_to_attack: "Moving into attack range..."
|
||||||
|
road_queued: "Build Road queued"
|
||||||
road_started: "Constructing %1... (%2 ticks)"
|
road_started: "Constructing %1... (%2 ticks)"
|
||||||
road_completed: "%1 completed!"
|
road_completed: "%1 completed!"
|
||||||
road_already_here: "Tile already has a road!"
|
road_already_here: "Tile already has a road!"
|
||||||
cannot_build_road_water: "Roads cannot be built on water!"
|
cannot_build_road_water: "Roads cannot be built on water!"
|
||||||
city_queued: "Found City queued on arrival"
|
city_queued: "Found City queued"
|
||||||
city_founded: "%1 founded!"
|
city_founded: "%1 founded!"
|
||||||
already_building: "Already busy building!"
|
already_building: "Already busy building!"
|
||||||
busy: "Unit is busy!"
|
busy: "Unit is busy!"
|
||||||
cannot_act_busy: "Unit cannot perform action while busy!"
|
cannot_act_busy: "Unit cannot perform action while busy!"
|
||||||
tile_occupied: "Tile already has an improvement!"
|
tile_occupied: "Tile already has an improvement!"
|
||||||
cannot_build_here: "Cannot build here! Allowed on: %1"
|
cannot_build_here: "Cannot build here! Allowed on: %1"
|
||||||
cannot_build_destination: "Cannot build on destination terrain! Allowed on: %1"
|
cannot_build_destination: "Cannot build on target terrain! Allowed on: %1"
|
||||||
no_build_charges: "No build charges remaining!"
|
no_build_charges: "No build charges remaining!"
|
||||||
charge_singular: "charge"
|
charge_singular: "charge"
|
||||||
charge_plural: "charges"
|
charge_plural: "charges"
|
||||||
@@ -76,6 +81,7 @@ ui:
|
|||||||
no_path: "No reachable path!"
|
no_path: "No reachable path!"
|
||||||
tile_purchased: "Tile purchased! (-%1 Culture)"
|
tile_purchased: "Tile purchased! (-%1 Culture)"
|
||||||
out_of_range: "Out of range!"
|
out_of_range: "Out of range!"
|
||||||
|
no_enemy_on_tile: "Select a tile with an enemy unit!"
|
||||||
defeated: "Defeated!"
|
defeated: "Defeated!"
|
||||||
city_already_here: "A city already exists here!"
|
city_already_here: "A city already exists here!"
|
||||||
unsuitable_terrain: "Unsuitable terrain!"
|
unsuitable_terrain: "Unsuitable terrain!"
|
||||||
@@ -86,35 +92,45 @@ ui:
|
|||||||
combat_format: "Attack: %1 | Range: %2 hex"
|
combat_format: "Attack: %1 | Range: %2 hex"
|
||||||
building_pct: "Building... %1%"
|
building_pct: "Building... %1%"
|
||||||
charges_format: "Charges: %1"
|
charges_format: "Charges: %1"
|
||||||
status_queued: "Queued: %1"
|
status_queued: "Queue (%1): %2"
|
||||||
btn_move: "Move [M]"
|
btn_move: "Move [M]"
|
||||||
btn_attack: "Attack [A]"
|
btn_attack: "Attack [A]"
|
||||||
btn_build_city: "Settle [B]"
|
btn_build_city: "Settle [B]"
|
||||||
btn_build_farm: "Farm [F]"
|
btn_build_farm: "Farm [F]"
|
||||||
btn_farm_queued: "Farm (Queued)"
|
btn_farm_queued: "Farm [F]"
|
||||||
btn_farm_building: "Building %1%"
|
btn_farm_building: "Building %1%"
|
||||||
btn_build_road: "Road [R]"
|
btn_build_road: "Road [R]"
|
||||||
btn_road_queued: "Road (Queued)"
|
btn_road_queued: "Road [R]"
|
||||||
btn_road_building: "Building %1%"
|
btn_road_building: "Building %1%"
|
||||||
tip_move: "Move (M)"
|
tip_move: "Move (M) - Click target tile (Shift+Click to queue)"
|
||||||
tip_attack: "Attack (A) [Range: %1]"
|
tip_attack: "Attack (A) [Range: %1] - Click enemy (Shift+Click to queue)"
|
||||||
tip_build_city: "Found City (B)"
|
tip_build_city: "Found City (B) - Click target tile (Shift+Click to queue)"
|
||||||
tip_build_farm: "Build Farm (F)"
|
tip_build_farm: "Build Farm (F) - Click target tile (Shift+Click to queue)"
|
||||||
tip_farm_building: "Busy building... %1%"
|
tip_farm_building: "Busy building... %1% (Click to plan next target)"
|
||||||
tip_farm_queued: "Build Farm (Queued on arrival - click to cancel)"
|
tip_farm_queued: "Build Farm (Queued - Click to plan target)"
|
||||||
tip_build_road: "Build Road (R) - Click target tile to build route"
|
tip_build_road: "Build Road (R) - Click target tile (Shift+Click to queue)"
|
||||||
tip_road_building: "Busy building road... %1%"
|
tip_road_building: "Busy building road... %1% (Click to plan next target)"
|
||||||
tip_road_queued: "Build Road (Queued on arrival - click to cancel)"
|
tip_road_queued: "Build Road (Queued - Click to plan target)"
|
||||||
road_started: "Constructing %1... (%2 ticks)"
|
|
||||||
road_completed: "%1 completed!"
|
|
||||||
road_queued: "Build Road queued on arrival"
|
|
||||||
road_already_here: "Tile already has a road!"
|
|
||||||
cannot_build_road_water: "Roads cannot be built on water!"
|
|
||||||
road_route_started: "Building road route to (%1, %2)..."
|
road_route_started: "Building road route to (%1, %2)..."
|
||||||
road_route_completed: "Road route completed!"
|
road_route_completed: "Road route completed!"
|
||||||
road_route_cancelled: "Road route cancelled"
|
road_route_cancelled: "Road route cancelled"
|
||||||
status_road_route: "Road Route: %1/%2"
|
status_road_route: "Road Route: %1/%2"
|
||||||
btn_road_route: "Route (%1/%2)"
|
btn_road_route: "Route (%1/%2)"
|
||||||
|
preview_move: "Move (%1 hexes)"
|
||||||
|
preview_farm: "Build Farm"
|
||||||
|
preview_farm_move: "Build Farm (%1 hexes)"
|
||||||
|
preview_city: "Found City"
|
||||||
|
preview_city_move: "Found City (%1 hexes)"
|
||||||
|
preview_road: "Road Route (%1 hexes)"
|
||||||
|
preview_road_here: "Build Road"
|
||||||
|
preview_attack: "Attack"
|
||||||
|
preview_attack_move: "Attack (%1 hexes)"
|
||||||
|
preview_queue_prefix: "+Queue: %1"
|
||||||
|
queue_badge_move: "#%1 Move"
|
||||||
|
queue_badge_farm: "#%1 Farm"
|
||||||
|
queue_badge_city: "#%1 Settle"
|
||||||
|
queue_badge_road: "#%1 Road"
|
||||||
|
queue_badge_attack: "#%1 Attack"
|
||||||
|
|
||||||
improvement:
|
improvement:
|
||||||
farm: "Farm"
|
farm: "Farm"
|
||||||
|
|||||||
+567
-428
File diff suppressed because it is too large
Load Diff
+13
-3
@@ -119,12 +119,22 @@ public:
|
|||||||
void removeQueuedCityProject(int cityId, int queueIndex);
|
void removeQueuedCityProject(int cityId, int queueIndex);
|
||||||
void completeCityProject(City &city, Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
|
void completeCityProject(City &city, Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
|
||||||
|
|
||||||
void commandMoveSelected(int targetX, int targetY, Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
|
void getPlannedOriginTile(const Unit &unit, bool appendWithShift, int &outX, int &outY) const;
|
||||||
void commandAttack(Unit *target);
|
bool scheduleOrStartUnitAction(Unit &unit, const UnitAction &action, bool appendWithShift,
|
||||||
|
Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
|
||||||
|
bool startUnitAction(Unit &unit, const UnitAction &action,
|
||||||
|
Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
|
||||||
|
void continueCurrentUnitAction(Unit &unit, Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
|
||||||
|
|
||||||
|
bool commandMoveSelected(int targetX, int targetY, Matrix2D<TerrainPoint> *mapPoints, int numPoints05, bool appendWithShift = false);
|
||||||
|
bool commandAttack(Unit *target, Matrix2D<TerrainPoint> *mapPoints = nullptr, int numPoints05 = 0, bool appendWithShift = false);
|
||||||
|
bool commandBuildCityAtTileSelected(int targetX, int targetY, Matrix2D<TerrainPoint> *mapPoints, int numPoints05, bool appendWithShift = false);
|
||||||
|
bool commandBuildFarmAtTileSelected(int targetX, int targetY, Matrix2D<TerrainPoint> *mapPoints, int numPoints05, bool appendWithShift = false);
|
||||||
|
bool commandBuildRoadToTileSelected(int targetX, int targetY, Matrix2D<TerrainPoint> *mapPoints, int numPoints05, bool appendWithShift = false);
|
||||||
|
|
||||||
void commandBuildCitySelected(Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
|
void commandBuildCitySelected(Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
|
||||||
void commandBuildFarmSelected(Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
|
void commandBuildFarmSelected(Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
|
||||||
void commandBuildRoadSelected(Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
|
void commandBuildRoadSelected(Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
|
||||||
void commandBuildRoadToTileSelected(int targetX, int targetY, Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
|
|
||||||
void advanceRoadRoute(Unit &unit, Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
|
void advanceRoadRoute(Unit &unit, Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
|
||||||
void completeUnitBuilding(Unit &unit, Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
|
void completeUnitBuilding(Unit &unit, Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
|
||||||
void processUnitActionQueue(Unit &unit, Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
|
void processUnitActionQueue(Unit &unit, Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
|
||||||
|
|||||||
+468
-223
@@ -3,6 +3,7 @@
|
|||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QPainterPath>
|
#include <QPainterPath>
|
||||||
|
#include <QGuiApplication>
|
||||||
#include <QToolTip>
|
#include <QToolTip>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include "pathfinder.h"
|
#include "pathfinder.h"
|
||||||
@@ -788,64 +789,71 @@ void QSceneRenderer::drawUnitsOverlay(QPainter &painter, const QMatrix4x4 &vMatr
|
|||||||
QPointF bCenter(startX + a * spacing, bubbleY);
|
QPointF bCenter(startX + a * spacing, bubbleY);
|
||||||
QRect bRect((int)(bCenter.x() - bubbleR), (int)(bCenter.y() - bubbleR), (int)(bubbleR * 2), (int)(bubbleR * 2));
|
QRect bRect((int)(bCenter.x() - bubbleR), (int)(bCenter.y() - bubbleR), (int)(bubbleR * 2), (int)(bubbleR * 2));
|
||||||
|
|
||||||
|
bool isMode = false;
|
||||||
bool isActive = false;
|
bool isActive = false;
|
||||||
QString iconFile = "images/actions/move.png";
|
QString iconFile = "images/actions/move.png";
|
||||||
QString tip = "Verplaatsen (M)";
|
QString tip = Localization::instance().get("ui.unit.tip_move", "Move (M) - Click target tile (Shift+Click to queue)");
|
||||||
if (act == "verplaatsen") {
|
if (act == "verplaatsen") {
|
||||||
isActive = (GameManager::instance().currentActionMode() == ActionMode::Move);
|
isMode = (GameManager::instance().currentActionMode() == ActionMode::Move);
|
||||||
|
isActive = isMode;
|
||||||
iconFile = "images/actions/move.png";
|
iconFile = "images/actions/move.png";
|
||||||
tip = "Verplaatsen (M)";
|
tip = Localization::instance().get("ui.unit.tip_move", "Move (M) - Click target tile (Shift+Click to queue)");
|
||||||
} else if (act == "aanvallen") {
|
} else if (act == "aanvallen") {
|
||||||
isActive = (GameManager::instance().currentActionMode() == ActionMode::Attack);
|
isMode = (GameManager::instance().currentActionMode() == ActionMode::Attack);
|
||||||
|
bool isQueued = (unit->currentAction.type == UnitActionType::Attack) || unit->hasQueuedActionType(UnitActionType::Attack);
|
||||||
|
isActive = isMode || isQueued;
|
||||||
iconFile = "images/actions/attack.png";
|
iconFile = "images/actions/attack.png";
|
||||||
tip = Localization::instance().get("ui.unit.tip_attack", "Attack (A) [Range: %1]").arg(unit->config.attackRange);
|
tip = Localization::instance().get("ui.unit.tip_attack", "Attack (A) [Range: %1] - Click enemy (Shift+Click to queue)").arg(unit->config.attackRange);
|
||||||
} else if (act == "steden_bouwen" || act == "build_city") {
|
} else if (act == "steden_bouwen" || act == "build_city") {
|
||||||
bool isQueued = unit->hasQueuedActionType(UnitActionType::BuildCity);
|
isMode = (GameManager::instance().currentActionMode() == ActionMode::BuildCity);
|
||||||
isActive = (GameManager::instance().currentActionMode() == ActionMode::BuildCity) || isQueued;
|
bool isQueued = (unit->currentAction.type == UnitActionType::BuildCity) || unit->hasQueuedActionType(UnitActionType::BuildCity);
|
||||||
|
isActive = isMode || isQueued;
|
||||||
iconFile = "images/actions/build_city.png";
|
iconFile = "images/actions/build_city.png";
|
||||||
tip = isQueued
|
tip = isQueued
|
||||||
? Localization::instance().get("ui.unit.city_queued", "Found City queued on arrival")
|
? Localization::instance().get("ui.unit.city_queued", "Found City queued")
|
||||||
: Localization::instance().get("ui.unit.tip_build_city", "Found City (B)");
|
: Localization::instance().get("ui.unit.tip_build_city", "Found City (B) - Click target tile (Shift+Click to queue)");
|
||||||
} else if (act == "boerderij_bouwen" || act == "build_farm") {
|
} else if (act == "boerderij_bouwen" || act == "build_farm") {
|
||||||
bool isQueued = unit->hasQueuedActionType(UnitActionType::BuildImprovement);
|
isMode = (GameManager::instance().currentActionMode() == ActionMode::BuildFarm);
|
||||||
isActive = (unit->isBuilding() && unit->buildingImprovementId != "road") || isQueued;
|
bool isBuildingFarm = unit->isBuilding() && !unit->buildingImprovementId.startsWith("road");
|
||||||
|
bool isQueued = (unit->currentAction.type == UnitActionType::BuildImprovement) || unit->hasQueuedActionType(UnitActionType::BuildImprovement);
|
||||||
|
isActive = isMode || isBuildingFarm || isQueued;
|
||||||
iconFile = "images/actions/farm.png";
|
iconFile = "images/actions/farm.png";
|
||||||
if (unit->isBuilding() && unit->buildingImprovementId != "road") {
|
if (isBuildingFarm) {
|
||||||
int pct = (int)(std::min(1.0f, unit->buildProgress) * 100.0f);
|
int pct = (int)(std::min(1.0f, unit->buildProgress) * 100.0f);
|
||||||
tip = Localization::instance().get("ui.unit.tip_farm_building", "Busy building... %1%").arg(pct);
|
tip = Localization::instance().get("ui.unit.tip_farm_building", "Busy building... %1% (Click to plan next target)").arg(pct);
|
||||||
} else if (isQueued) {
|
} else if (isQueued) {
|
||||||
tip = Localization::instance().get("ui.unit.tip_farm_queued", "Build Farm (Queued on arrival - click to cancel)");
|
tip = Localization::instance().get("ui.unit.tip_farm_queued", "Build Farm (Queued - Click to plan target)");
|
||||||
} else {
|
} else {
|
||||||
tip = Localization::instance().get("ui.unit.tip_build_farm", "Build Farm (F)");
|
tip = Localization::instance().get("ui.unit.tip_build_farm", "Build Farm (F) - Click target tile (Shift+Click to queue)");
|
||||||
}
|
}
|
||||||
} else if (act == "build_road" || act == "weg_bouwen") {
|
} else if (act == "build_road" || act == "weg_bouwen") {
|
||||||
bool isMode = (GameManager::instance().currentActionMode() == ActionMode::BuildRoad);
|
isMode = (GameManager::instance().currentActionMode() == ActionMode::BuildRoad);
|
||||||
bool isBuildingRoad = unit->isBuilding() && unit->buildingImprovementId.startsWith("road");
|
bool isBuildingRoad = unit->isBuilding() && unit->buildingImprovementId.startsWith("road");
|
||||||
bool hasRoute = unit->hasRoadRoute();
|
bool hasRoute = unit->hasRoadRoute();
|
||||||
bool isQueued = unit->hasQueuedActionType(UnitActionType::BuildRoad);
|
bool isQueued = (unit->currentAction.type == UnitActionType::BuildRoad) || unit->hasQueuedActionType(UnitActionType::BuildRoad);
|
||||||
isActive = isMode || isBuildingRoad || hasRoute || isQueued;
|
isActive = isMode || isBuildingRoad || hasRoute || isQueued;
|
||||||
iconFile = "images/actions/road.png";
|
iconFile = "images/actions/road.png";
|
||||||
if (isBuildingRoad) {
|
if (isBuildingRoad) {
|
||||||
int pct = (int)(std::min(1.0f, unit->buildProgress) * 100.0f);
|
int pct = (int)(std::min(1.0f, unit->buildProgress) * 100.0f);
|
||||||
tip = Localization::instance().get("ui.unit.tip_road_building", "Busy building road... %1%").arg(pct);
|
tip = Localization::instance().get("ui.unit.tip_road_building", "Busy building road... %1% (Click to plan next target)").arg(pct);
|
||||||
} else if (hasRoute) {
|
} else if (hasRoute) {
|
||||||
int cur = (int)unit->roadRouteIndex + 1;
|
int cur = (int)unit->roadRouteIndex + 1;
|
||||||
int total = (int)unit->roadRoute.size();
|
int total = (int)unit->roadRoute.size();
|
||||||
tip = Localization::instance().get("ui.unit.status_road_route", "Road Route: %1/%2").arg(cur).arg(total);
|
tip = Localization::instance().get("ui.unit.status_road_route", "Road Route: %1/%2").arg(cur).arg(total);
|
||||||
} else if (isQueued) {
|
} else if (isQueued) {
|
||||||
tip = Localization::instance().get("ui.unit.tip_road_queued", "Build Road (Queued on arrival - click to cancel)");
|
tip = Localization::instance().get("ui.unit.tip_road_queued", "Build Road (Queued - Click to plan target)");
|
||||||
} else {
|
} else {
|
||||||
tip = Localization::instance().get("ui.unit.tip_build_road", "Build Road (R) - Click target tile to build route");
|
tip = Localization::instance().get("ui.unit.tip_build_road", "Build Road (R) - Click target tile (Shift+Click to queue)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw action bubble
|
// Draw action bubble
|
||||||
painter.setBrush(isActive ? QColor(40, 80, 140, 240) : QColor(20, 26, 38, 230));
|
painter.setBrush(isMode ? QColor(55, 105, 175, 250) : (isActive ? QColor(40, 80, 140, 240) : QColor(20, 26, 38, 230)));
|
||||||
painter.setPen(QPen(isActive ? QColor(255, 220, 50) : secondaryCol, isActive ? 2.0 : 1.2));
|
painter.setPen(QPen(isMode ? QColor(100, 235, 255) : (isActive ? QColor(255, 220, 50) : secondaryCol), (isMode || isActive) ? 2.0 : 1.2));
|
||||||
painter.drawEllipse(bCenter, bubbleR, bubbleR);
|
painter.drawEllipse(bCenter, bubbleR, bubbleR);
|
||||||
|
|
||||||
// Action icon inside bubble
|
// Action icon inside bubble
|
||||||
QImage aIcon = getTintedIcon(iconFile, isActive ? QColor(255, 230, 80) : primaryCol);
|
QImage aIcon = getTintedIcon(iconFile, (isMode || isActive) ? QColor(255, 230, 80) : primaryCol);
|
||||||
painter.drawImage(QRectF(bCenter.x() - 7, bCenter.y() - 7, 14, 14), aIcon);
|
painter.drawImage(QRectF(bCenter.x() - 7, bCenter.y() - 7, 14, 14), aIcon);
|
||||||
|
|
||||||
// Register for click detection
|
// Register for click detection
|
||||||
@@ -853,11 +861,12 @@ void QSceneRenderer::drawUnitsOverlay(QPainter &painter, const QMatrix4x4 &vMatr
|
|||||||
|
|
||||||
// Tooltip on hover
|
// Tooltip on hover
|
||||||
if (bRect.contains(m_mouseCurrentPos)) {
|
if (bRect.contains(m_mouseCurrentPos)) {
|
||||||
QRect tipRect((int)bCenter.x() - 55, (int)bCenter.y() - 26, 110, 18);
|
painter.setFont(QFont("sans-serif", 7, QFont::Bold));
|
||||||
|
int tw = std::max(110, painter.fontMetrics().horizontalAdvance(tip) + 14);
|
||||||
|
QRect tipRect((int)bCenter.x() - tw / 2, (int)bCenter.y() - 28, tw, 18);
|
||||||
painter.setBrush(QColor(15, 20, 30, 240));
|
painter.setBrush(QColor(15, 20, 30, 240));
|
||||||
painter.setPen(QPen(QColor(120, 160, 210), 1.0));
|
painter.setPen(QPen(QColor(120, 160, 210), 1.0));
|
||||||
painter.drawRoundedRect(tipRect, 3, 3);
|
painter.drawRoundedRect(tipRect, 3, 3);
|
||||||
painter.setFont(QFont("sans-serif", 7, QFont::Bold));
|
|
||||||
painter.setPen(Qt::white);
|
painter.setPen(Qt::white);
|
||||||
painter.drawText(tipRect, Qt::AlignCenter, tip);
|
painter.drawText(tipRect, Qt::AlignCenter, tip);
|
||||||
}
|
}
|
||||||
@@ -871,6 +880,17 @@ void QSceneRenderer::drawPathAndRange(QPainter &painter, Unit *unit, const QMatr
|
|||||||
if (!unit) return;
|
if (!unit) return;
|
||||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||||
|
|
||||||
|
auto drawFloatingBadge = [&](const QPointF &sPos, const QString &text, const QColor &borderCol, const QColor &textCol, int yOffset = -28) {
|
||||||
|
painter.setFont(QFont("sans-serif", 7, QFont::Bold));
|
||||||
|
int tw = painter.fontMetrics().horizontalAdvance(text) + 14;
|
||||||
|
QRect badgeRect((int)sPos.x() - tw / 2, (int)sPos.y() + yOffset, tw, 18);
|
||||||
|
painter.setBrush(QColor(16, 22, 32, 235));
|
||||||
|
painter.setPen(QPen(borderCol, 1.2));
|
||||||
|
painter.drawRoundedRect(badgeRect, 4, 4);
|
||||||
|
painter.setPen(textCol);
|
||||||
|
painter.drawText(badgeRect, Qt::AlignCenter, text);
|
||||||
|
};
|
||||||
|
|
||||||
// 1. If unit is moving or has a path: draw path line and destination reticle
|
// 1. If unit is moving or has a path: draw path line and destination reticle
|
||||||
if (unit->path.size() > 1) {
|
if (unit->path.size() > 1) {
|
||||||
QPainterPath pathLine;
|
QPainterPath pathLine;
|
||||||
@@ -948,85 +968,292 @@ void QSceneRenderer::drawPathAndRange(QPainter &painter, Unit *unit, const QMatr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Road Build targeting preview when in ActionMode::BuildRoad
|
// 2b. Queued Action Chain visualization (shows all actions in unit->actionQueue chained from previous target tile)
|
||||||
if (GameManager::instance().currentActionMode() == ActionMode::BuildRoad && unit->config.canBuildRoad()) {
|
if (!unit->actionQueue.empty()) {
|
||||||
int hx = -1, hy = -1;
|
int prevX = unit->tileX;
|
||||||
if (screenToHex(m_mouseCurrentPos, vMatrix, hx, hy) && (hx != unit->tileX || hy != unit->tileY)) {
|
int prevY = unit->tileY;
|
||||||
if (TerrainMovementManager::instance().isHexPassable(hx, hy, MovementDomain::Land, m_point, m_numPoints05, &unit->config)) {
|
GameManager::instance().getPlannedOriginTile(*unit, false, prevX, prevY);
|
||||||
auto preview = Pathfinder::findPath(unit->tileX, unit->tileY, hx, hy, m_point, m_numPoints05, MovementDomain::Land, &unit->config);
|
|
||||||
if (preview.size() > 1) {
|
for (size_t qi = 0; qi < unit->actionQueue.size(); ++qi) {
|
||||||
QPainterPath previewLine;
|
const UnitAction &qAct = unit->actionQueue[qi];
|
||||||
|
if (qAct.targetX < 0 || qAct.targetY < 0) continue;
|
||||||
|
|
||||||
|
QColor qCol(100, 210, 255, 210);
|
||||||
|
QString qBadge;
|
||||||
|
if (qAct.type == UnitActionType::Move) {
|
||||||
|
qCol = QColor(80, 210, 255, 210);
|
||||||
|
qBadge = Localization::instance().get("ui.unit.queue_badge_move", "#%1 Move").arg(qi + 1);
|
||||||
|
} else if (qAct.type == UnitActionType::BuildImprovement) {
|
||||||
|
qCol = QColor(90, 235, 130, 220);
|
||||||
|
qBadge = Localization::instance().get("ui.unit.queue_badge_farm", "#%1 Farm").arg(qi + 1);
|
||||||
|
} else if (qAct.type == UnitActionType::BuildCity) {
|
||||||
|
qCol = QColor(120, 245, 160, 220);
|
||||||
|
qBadge = Localization::instance().get("ui.unit.queue_badge_city", "#%1 Settle").arg(qi + 1);
|
||||||
|
} else if (qAct.type == UnitActionType::BuildRoad) {
|
||||||
|
qCol = QColor(255, 200, 65, 220);
|
||||||
|
qBadge = Localization::instance().get("ui.unit.queue_badge_road", "#%1 Road").arg(qi + 1);
|
||||||
|
} else if (qAct.type == UnitActionType::Attack) {
|
||||||
|
qCol = QColor(255, 85, 85, 220);
|
||||||
|
qBadge = Localization::instance().get("ui.unit.queue_badge_attack", "#%1 Attack").arg(qi + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qAct.targetX != prevX || qAct.targetY != prevY) {
|
||||||
|
MovementDomain dom = (qAct.type == UnitActionType::BuildRoad) ? MovementDomain::Land : unit->config.movementDomain;
|
||||||
|
auto qPath = Pathfinder::findPath(prevX, prevY, qAct.targetX, qAct.targetY, m_point, m_numPoints05, dom, &unit->config);
|
||||||
|
if (qPath.size() > 1) {
|
||||||
|
QPainterPath qLine;
|
||||||
bool first = true;
|
bool first = true;
|
||||||
for (const auto &p : preview) {
|
for (const auto &wp : qPath) {
|
||||||
QVector2D w = Pathfinder::hexToWorld(p.first, p.second);
|
QVector2D w = Pathfinder::hexToWorld(wp.first, wp.second);
|
||||||
float z = Unit::getTerrainHeight(w.x(), w.y(), m_point, m_numPoints05);
|
float z = Unit::getTerrainHeight(w.x(), w.y(), m_point, m_numPoints05);
|
||||||
bool inFront = false;
|
bool inFront = false;
|
||||||
QPointF sPos = UnitRenderer::projectToScreen(QVector3D(w.x(), w.y(), z + 0.06f), Projection, vMatrix, width(), height(), inFront);
|
QPointF sPos = UnitRenderer::projectToScreen(QVector3D(w.x(), w.y(), z + 0.06f), Projection, vMatrix, width(), height(), inFront);
|
||||||
if (inFront) {
|
if (inFront) {
|
||||||
if (first) {
|
if (first) {
|
||||||
previewLine.moveTo(sPos);
|
qLine.moveTo(sPos);
|
||||||
first = false;
|
first = false;
|
||||||
} else {
|
} else {
|
||||||
previewLine.lineTo(sPos);
|
qLine.lineTo(sPos);
|
||||||
|
}
|
||||||
|
if (qAct.type == UnitActionType::BuildRoad) {
|
||||||
|
painter.setPen(QPen(QColor(140, 95, 30, 180), 1.0));
|
||||||
|
painter.setBrush(qCol);
|
||||||
|
painter.drawEllipse(sPos, 3.0, 3.0);
|
||||||
}
|
}
|
||||||
painter.setPen(QPen(QColor(160, 110, 40, 200), 1.0));
|
|
||||||
painter.setBrush(QColor(255, 230, 90, 230));
|
|
||||||
painter.drawEllipse(sPos, 3.5, 3.5);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
painter.setPen(QPen(QColor(255, 220, 60, 240), 3.0, Qt::DashLine));
|
painter.setPen(QPen(qCol, 2.2, Qt::DotLine));
|
||||||
painter.setBrush(Qt::NoBrush);
|
painter.setBrush(Qt::NoBrush);
|
||||||
painter.drawPath(previewLine);
|
painter.drawPath(qLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Destination reticle
|
QVector2D destW = Pathfinder::hexToWorld(qAct.targetX, qAct.targetY);
|
||||||
QVector2D destW = Pathfinder::hexToWorld(hx, hy);
|
float destZ = Unit::getTerrainHeight(destW.x(), destW.y(), m_point, m_numPoints05);
|
||||||
float destZ = Unit::getTerrainHeight(destW.x(), destW.y(), m_point, m_numPoints05);
|
bool inFront = false;
|
||||||
bool inFront = false;
|
QPointF sDest = UnitRenderer::projectToScreen(QVector3D(destW.x(), destW.y(), destZ + 0.06f), Projection, vMatrix, width(), height(), inFront);
|
||||||
QPointF sDest = UnitRenderer::projectToScreen(QVector3D(destW.x(), destW.y(), destZ + 0.06f), Projection, vMatrix, width(), height(), inFront);
|
if (inFront) {
|
||||||
if (inFront) {
|
painter.setPen(QPen(qCol, 1.8));
|
||||||
float pulse = (std::sin(m_elapsedTimer.elapsed() * 0.01f) + 1.0f) * 0.5f;
|
painter.setBrush(QColor(qCol.red(), qCol.green(), qCol.blue(), 40));
|
||||||
painter.setPen(QPen(QColor(255, 225, 70, (int)(180 + pulse * 75)), 2.5));
|
painter.drawEllipse(sDest, 11.0, 11.0);
|
||||||
painter.setBrush(QColor(255, 210, 50, 50));
|
drawFloatingBadge(sDest, qBadge, qCol, qCol.lighter(130), -26);
|
||||||
painter.drawEllipse(sDest, 16.0f, 16.0f);
|
}
|
||||||
painter.drawLine(sDest.x() - 22, sDest.y(), sDest.x() + 22, sDest.y());
|
|
||||||
painter.drawLine(sDest.x(), sDest.y() - 22, sDest.x(), sDest.y() + 22);
|
|
||||||
|
|
||||||
QString targetTip = Localization::instance().get("ui.unit.btn_road_route", "Route (%1 hexes)").arg(preview.size() - 1);
|
if (qAct.type == UnitActionType::Attack && qAct.targetUnitId >= 0) {
|
||||||
painter.setFont(QFont("sans-serif", 8, QFont::Bold));
|
Unit *enemy = GameManager::instance().getUnitById(qAct.targetUnitId);
|
||||||
QFontMetrics fm = painter.fontMetrics();
|
if (enemy && !enemy->isDead() && inFront) {
|
||||||
int tw = fm.horizontalAdvance(targetTip) + 16;
|
bool eInFront = false;
|
||||||
QRect tipRect((int)sDest.x() - tw / 2, (int)sDest.y() - 34, tw, 20);
|
QPointF sEnemy = UnitRenderer::projectToScreen(enemy->worldPos + QVector3D(0, 0, 0.35f), Projection, vMatrix, width(), height(), eInFront);
|
||||||
painter.setBrush(QColor(20, 24, 34, 230));
|
if (eInFront) {
|
||||||
painter.setPen(QPen(QColor(255, 215, 60), 1.2));
|
painter.setPen(QPen(QColor(255, 70, 70, 210), 2.0, Qt::DashLine));
|
||||||
painter.drawRoundedRect(tipRect, 4, 4);
|
painter.drawLine(sDest, sEnemy);
|
||||||
painter.setPen(QColor(255, 235, 120));
|
|
||||||
painter.drawText(tipRect, Qt::AlignCenter, targetTip);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prevX = qAct.targetX;
|
||||||
|
prevY = qAct.targetY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Attack range visualization if Attack mode is active
|
// 3. Target Tile Hover Preview when any ActionMode is active
|
||||||
if (GameManager::instance().currentActionMode() == ActionMode::Attack) {
|
ActionMode mode = GameManager::instance().currentActionMode();
|
||||||
int range = unit->config.attackRange;
|
if (mode != ActionMode::None) {
|
||||||
for (const auto &u : GameManager::instance().getUnits()) {
|
bool shiftHeld = (QGuiApplication::keyboardModifiers() & Qt::ShiftModifier) != 0;
|
||||||
if (u->isDead() || u->playerId == unit->playerId) continue;
|
int originX = unit->tileX;
|
||||||
|
int originY = unit->tileY;
|
||||||
|
GameManager::instance().getPlannedOriginTile(*unit, shiftHeld, originX, originY);
|
||||||
|
|
||||||
int d = Pathfinder::hexDistance(unit->tileX, unit->tileY, u->tileX, u->tileY);
|
int hx = -1, hy = -1;
|
||||||
if (d <= range) {
|
bool hasHoverHex = screenToHex(m_mouseCurrentPos, vMatrix, hx, hy);
|
||||||
// Target reticle over enemy unit
|
|
||||||
|
auto wrapQueueText = [&](const QString &baseText) -> QString {
|
||||||
|
if (shiftHeld && (unit->hasActiveAction() || unit->hasQueuedAction())) {
|
||||||
|
return Localization::instance().get("ui.unit.preview_queue_prefix", "+Queue: %1").arg(baseText);
|
||||||
|
}
|
||||||
|
return baseText;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto drawPreviewPathAndReticle = [&](const std::vector<std::pair<int, int>> &previewPath,
|
||||||
|
int destX, int destY,
|
||||||
|
const QColor &col, const QString &label,
|
||||||
|
bool validTarget, bool drawWaypoints) {
|
||||||
|
if (previewPath.size() > 1) {
|
||||||
|
QPainterPath previewLine;
|
||||||
|
bool first = true;
|
||||||
|
for (const auto &p : previewPath) {
|
||||||
|
QVector2D w = Pathfinder::hexToWorld(p.first, p.second);
|
||||||
|
float z = Unit::getTerrainHeight(w.x(), w.y(), m_point, m_numPoints05);
|
||||||
|
bool inFront = false;
|
||||||
|
QPointF sPos = UnitRenderer::projectToScreen(QVector3D(w.x(), w.y(), z + 0.06f), Projection, vMatrix, width(), height(), inFront);
|
||||||
|
if (inFront) {
|
||||||
|
if (first) {
|
||||||
|
previewLine.moveTo(sPos);
|
||||||
|
first = false;
|
||||||
|
} else {
|
||||||
|
previewLine.lineTo(sPos);
|
||||||
|
}
|
||||||
|
if (drawWaypoints) {
|
||||||
|
painter.setPen(QPen(QColor(30, 30, 30, 200), 1.0));
|
||||||
|
painter.setBrush(col);
|
||||||
|
painter.drawEllipse(sPos, 3.5, 3.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
painter.setPen(QPen(col, 2.8, Qt::DashLine));
|
||||||
|
painter.setBrush(Qt::NoBrush);
|
||||||
|
painter.drawPath(previewLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
QVector2D destW = Pathfinder::hexToWorld(destX, destY);
|
||||||
|
float destZ = Unit::getTerrainHeight(destW.x(), destW.y(), m_point, m_numPoints05);
|
||||||
|
bool inFront = false;
|
||||||
|
QPointF sDest = UnitRenderer::projectToScreen(QVector3D(destW.x(), destW.y(), destZ + 0.06f), Projection, vMatrix, width(), height(), inFront);
|
||||||
|
if (inFront) {
|
||||||
|
float pulse = (std::sin(m_elapsedTimer.elapsed() * 0.01f) + 1.0f) * 0.5f;
|
||||||
|
QColor rCol = validTarget ? col : QColor(255, 75, 75);
|
||||||
|
painter.setPen(QPen(QColor(rCol.red(), rCol.green(), rCol.blue(), (int)(180 + pulse * 75)), 2.5));
|
||||||
|
painter.setBrush(QColor(rCol.red(), rCol.green(), rCol.blue(), 50));
|
||||||
|
painter.drawEllipse(sDest, 16.0f, 16.0f);
|
||||||
|
painter.drawLine(sDest.x() - 22, sDest.y(), sDest.x() + 22, sDest.y());
|
||||||
|
painter.drawLine(sDest.x(), sDest.y() - 22, sDest.x(), sDest.y() + 22);
|
||||||
|
|
||||||
|
if (!label.isEmpty()) {
|
||||||
|
drawFloatingBadge(sDest, label, rCol, rCol.lighter(135), -34);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (mode == ActionMode::Move && hasHoverHex && (hx != originX || hy != originY)) {
|
||||||
|
bool passable = TerrainMovementManager::instance().isHexPassable(hx, hy, unit->config.movementDomain, m_point, m_numPoints05, &unit->config);
|
||||||
|
std::vector<std::pair<int, int>> preview;
|
||||||
|
if (passable) {
|
||||||
|
preview = Pathfinder::findPath(originX, originY, hx, hy, m_point, m_numPoints05, unit->config.movementDomain, &unit->config);
|
||||||
|
}
|
||||||
|
bool valid = preview.size() > 1;
|
||||||
|
QString label = valid
|
||||||
|
? wrapQueueText(Localization::instance().get("ui.unit.preview_move", "Move (%1 hexes)").arg(preview.size() - 1))
|
||||||
|
: Localization::instance().get("ui.unit.impassable_terrain", "Impassable terrain!");
|
||||||
|
drawPreviewPathAndReticle(preview, hx, hy, QColor(80, 220, 255, 240), label, valid, false);
|
||||||
|
}
|
||||||
|
else if (mode == ActionMode::BuildRoad && unit->config.canBuildRoad() && hasHoverHex) {
|
||||||
|
bool passable = TerrainMovementManager::instance().isHexPassable(hx, hy, MovementDomain::Land, m_point, m_numPoints05, &unit->config);
|
||||||
|
std::vector<std::pair<int, int>> preview;
|
||||||
|
bool valid = false;
|
||||||
|
QString label;
|
||||||
|
if (passable) {
|
||||||
|
if (hx == originX && hy == originY) {
|
||||||
|
valid = !GameManager::instance().hasRoadAt(hx, hy);
|
||||||
|
label = valid
|
||||||
|
? wrapQueueText(Localization::instance().get("ui.unit.preview_road_here", "Build Road"))
|
||||||
|
: Localization::instance().get("ui.unit.road_already_here", "Tile already has a road!");
|
||||||
|
} else {
|
||||||
|
preview = Pathfinder::findPath(originX, originY, hx, hy, m_point, m_numPoints05, MovementDomain::Land, &unit->config);
|
||||||
|
valid = preview.size() > 1;
|
||||||
|
label = valid
|
||||||
|
? wrapQueueText(Localization::instance().get("ui.unit.preview_road", "Road Route (%1 hexes)").arg(preview.size() - 1))
|
||||||
|
: Localization::instance().get("ui.unit.no_path", "No reachable path!");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
label = Localization::instance().get("ui.unit.cannot_build_road_water", "Roads cannot be built on water!");
|
||||||
|
}
|
||||||
|
drawPreviewPathAndReticle(preview, hx, hy, QColor(255, 220, 60, 240), label, valid, true);
|
||||||
|
}
|
||||||
|
else if (mode == ActionMode::BuildFarm && unit->config.canBuildFarm() && hasHoverHex) {
|
||||||
|
QString tType = TerrainYieldManager::instance().getTerrainType(hx, hy, m_point, m_numPoints05);
|
||||||
|
const ImprovementConfig *farmCfg = TerrainYieldManager::instance().getImprovementConfig("farm");
|
||||||
|
QStringList validTerrains = farmCfg ? farmCfg->validTerrains : QStringList{"grasland", "tundra"};
|
||||||
|
bool alreadyOccupied = GameManager::instance().hasImprovementAt(hx, hy) ||
|
||||||
|
(shiftHeld && unit->hasQueuedImprovementAt(hx, hy));
|
||||||
|
bool terrainValid = validTerrains.contains(tType) && !alreadyOccupied;
|
||||||
|
|
||||||
|
std::vector<std::pair<int, int>> preview;
|
||||||
|
bool pathValid = true;
|
||||||
|
if (hx != originX || hy != originY) {
|
||||||
|
preview = Pathfinder::findPath(originX, originY, hx, hy, m_point, m_numPoints05, unit->config.movementDomain, &unit->config);
|
||||||
|
pathValid = preview.size() > 1;
|
||||||
|
}
|
||||||
|
bool valid = terrainValid && pathValid;
|
||||||
|
QString label;
|
||||||
|
if (!valid) {
|
||||||
|
if (alreadyOccupied) {
|
||||||
|
label = Localization::instance().get("ui.unit.tile_occupied", "Tile already has an improvement!");
|
||||||
|
} else if (!validTerrains.contains(tType)) {
|
||||||
|
label = Localization::instance().get("ui.unit.unsuitable_terrain", "Unsuitable terrain!");
|
||||||
|
} else {
|
||||||
|
label = Localization::instance().get("ui.unit.no_path", "No reachable path!");
|
||||||
|
}
|
||||||
|
} else if (preview.size() > 1) {
|
||||||
|
label = wrapQueueText(Localization::instance().get("ui.unit.preview_farm_move", "Build Farm (%1 hexes)").arg(preview.size() - 1));
|
||||||
|
} else {
|
||||||
|
label = wrapQueueText(Localization::instance().get("ui.unit.preview_farm", "Build Farm"));
|
||||||
|
}
|
||||||
|
drawPreviewPathAndReticle(preview, hx, hy, QColor(90, 235, 125, 240), label, valid, false);
|
||||||
|
}
|
||||||
|
else if (mode == ActionMode::BuildCity && unit->config.canBuildCity() && hasHoverHex) {
|
||||||
|
bool passable = TerrainMovementManager::instance().isHexPassable(hx, hy, unit->config.movementDomain, m_point, m_numPoints05, &unit->config);
|
||||||
|
bool noCity = (GameManager::instance().getCityAt(hx, hy) == nullptr);
|
||||||
|
std::vector<std::pair<int, int>> preview;
|
||||||
|
bool pathValid = true;
|
||||||
|
if (hx != originX || hy != originY) {
|
||||||
|
preview = Pathfinder::findPath(originX, originY, hx, hy, m_point, m_numPoints05, unit->config.movementDomain, &unit->config);
|
||||||
|
pathValid = preview.size() > 1;
|
||||||
|
}
|
||||||
|
bool valid = passable && noCity && pathValid;
|
||||||
|
QString label;
|
||||||
|
if (!valid) {
|
||||||
|
if (!noCity) {
|
||||||
|
label = Localization::instance().get("ui.unit.city_already_here", "A city already exists here!");
|
||||||
|
} else {
|
||||||
|
label = Localization::instance().get("ui.unit.impassable_terrain", "Impassable terrain!");
|
||||||
|
}
|
||||||
|
} else if (preview.size() > 1) {
|
||||||
|
label = wrapQueueText(Localization::instance().get("ui.unit.preview_city_move", "Found City (%1 hexes)").arg(preview.size() - 1));
|
||||||
|
} else {
|
||||||
|
label = wrapQueueText(Localization::instance().get("ui.unit.preview_city", "Found City"));
|
||||||
|
}
|
||||||
|
drawPreviewPathAndReticle(preview, hx, hy, QColor(110, 245, 160, 240), label, valid, false);
|
||||||
|
}
|
||||||
|
else if (mode == ActionMode::Attack) {
|
||||||
|
int range = unit->config.attackRange;
|
||||||
|
for (const auto &u : GameManager::instance().getUnits()) {
|
||||||
|
if (u->isDead() || u->playerId == unit->playerId) continue;
|
||||||
|
|
||||||
|
int d = Pathfinder::hexDistance(originX, originY, u->tileX, u->tileY);
|
||||||
bool inFront = false;
|
bool inFront = false;
|
||||||
QPointF sTarget = UnitRenderer::projectToScreen(u->worldPos + QVector3D(0, 0, 0.4f), Projection, vMatrix, width(), height(), inFront);
|
QPointF sTarget = UnitRenderer::projectToScreen(u->worldPos + QVector3D(0, 0, 0.4f), Projection, vMatrix, width(), height(), inFront);
|
||||||
if (inFront) {
|
if (inFront) {
|
||||||
float pulse = (std::sin(m_elapsedTimer.elapsed() * 0.01f) + 1.0f) * 0.5f;
|
float pulse = (std::sin(m_elapsedTimer.elapsed() * 0.01f) + 1.0f) * 0.5f;
|
||||||
painter.setPen(QPen(QColor(255, 50, 50, (int)(180 + pulse * 75)), 2.2));
|
QColor rCol = (d <= range) ? QColor(255, 50, 50, (int)(180 + pulse * 75)) : QColor(255, 140, 60, (int)(150 + pulse * 70));
|
||||||
|
painter.setPen(QPen(rCol, 2.2));
|
||||||
painter.setBrush(QColor(255, 40, 40, 40));
|
painter.setBrush(QColor(255, 40, 40, 40));
|
||||||
painter.drawEllipse(sTarget, 16.0f, 16.0f);
|
painter.drawEllipse(sTarget, 16.0f, 16.0f);
|
||||||
painter.drawLine(sTarget.x() - 20, sTarget.y(), sTarget.x() + 20, sTarget.y());
|
painter.drawLine(sTarget.x() - 20, sTarget.y(), sTarget.x() + 20, sTarget.y());
|
||||||
painter.drawLine(sTarget.x(), sTarget.y() - 20, sTarget.x(), sTarget.y() + 20);
|
painter.drawLine(sTarget.x(), sTarget.y() - 20, sTarget.x(), sTarget.y() + 20);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasHoverHex) {
|
||||||
|
Unit *hoveredEnemy = GameManager::instance().getUnitAt(hx, hy);
|
||||||
|
if (hoveredEnemy && hoveredEnemy->playerId != unit->playerId && !hoveredEnemy->isDead()) {
|
||||||
|
int d = Pathfinder::hexDistance(originX, originY, hoveredEnemy->tileX, hoveredEnemy->tileY);
|
||||||
|
std::vector<std::pair<int, int>> approachPath;
|
||||||
|
if (d > range) {
|
||||||
|
auto fullPath = Pathfinder::findPath(originX, originY, hoveredEnemy->tileX, hoveredEnemy->tileY,
|
||||||
|
m_point, m_numPoints05, unit->config.movementDomain, &unit->config);
|
||||||
|
for (const auto &wp : fullPath) {
|
||||||
|
approachPath.push_back(wp);
|
||||||
|
if (Pathfinder::hexDistance(wp.first, wp.second, hoveredEnemy->tileX, hoveredEnemy->tileY) <= range) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QString label = (approachPath.size() > 1)
|
||||||
|
? wrapQueueText(Localization::instance().get("ui.unit.preview_attack_move", "Attack (%1 hexes)").arg(approachPath.size() - 1))
|
||||||
|
: wrapQueueText(Localization::instance().get("ui.unit.preview_attack", "Attack"));
|
||||||
|
drawPreviewPathAndReticle(approachPath, hoveredEnemy->tileX, hoveredEnemy->tileY, QColor(255, 65, 65, 240), label, true, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1088,7 +1315,7 @@ void QSceneRenderer::drawUnitCard(QPainter &painter, Unit *unit)
|
|||||||
|
|
||||||
painter.setFont(QFont("sans-serif", 8));
|
painter.setFont(QFont("sans-serif", 8));
|
||||||
painter.setPen(QColor(160, 185, 215));
|
painter.setPen(QColor(160, 185, 215));
|
||||||
QString playerInfo = QString("%1 | Hex: (%2, %3)").arg(player ? player->name : "Onbekend").arg(unit->tileX).arg(unit->tileY);
|
QString playerInfo = QString("%1 | Hex: (%2, %3)").arg(player ? player->name : "Unknown").arg(unit->tileX).arg(unit->tileY);
|
||||||
painter.drawText(QRect(badgeRect.right() + 10, cardRect.top() + 28, 200, 14), Qt::AlignLeft | Qt::AlignVCenter, playerInfo);
|
painter.drawText(QRect(badgeRect.right() + 10, cardRect.top() + 28, 200, 14), Qt::AlignLeft | Qt::AlignVCenter, playerInfo);
|
||||||
|
|
||||||
// Category Pill
|
// Category Pill
|
||||||
@@ -1132,10 +1359,12 @@ void QSceneRenderer::drawUnitCard(QPainter &painter, Unit *unit)
|
|||||||
QString costInfo = Localization::instance().get("ui.unit.cost_format", "Cost: %1 pts").arg(unit->config.cost);
|
QString costInfo = Localization::instance().get("ui.unit.cost_format", "Cost: %1 pts").arg(unit->config.cost);
|
||||||
painter.drawText(QRect(cardRect.left() + 160, cardRect.top() + 76, 130, 14), Qt::AlignLeft | Qt::AlignVCenter, costInfo);
|
painter.drawText(QRect(cardRect.left() + 160, cardRect.top() + 76, 130, 14), Qt::AlignLeft | Qt::AlignVCenter, costInfo);
|
||||||
|
|
||||||
if (unit->config.isMilitary()) {
|
if (unit->hasQueuedAction()) {
|
||||||
QString combatInfo = Localization::instance().get("ui.unit.combat_format", "Attack: %1 | Range: %2 hex")
|
QString qSummary = unit->getQueuedActionSummary();
|
||||||
.arg(unit->config.attackDamage).arg(unit->config.attackRange);
|
QString queuedInfo = Localization::instance().get("ui.unit.status_queued", "Queue (%1): %2")
|
||||||
painter.drawText(QRect(cardRect.left() + 12, cardRect.top() + 94, 280, 14), Qt::AlignLeft | Qt::AlignVCenter, combatInfo);
|
.arg(unit->actionQueue.size()).arg(qSummary);
|
||||||
|
painter.setPen(QColor(100, 220, 140));
|
||||||
|
painter.drawText(QRect(cardRect.left() + 12, cardRect.top() + 94, 286, 14), Qt::AlignLeft | Qt::AlignVCenter, queuedInfo);
|
||||||
} else if (unit->isBuilding()) {
|
} else if (unit->isBuilding()) {
|
||||||
int pct = (int)(std::min(1.0f, unit->buildProgress) * 100.0f);
|
int pct = (int)(std::min(1.0f, unit->buildProgress) * 100.0f);
|
||||||
QString buildInfo = Localization::instance().get("ui.unit.building_pct", "Building... %1%").arg(pct);
|
QString buildInfo = Localization::instance().get("ui.unit.building_pct", "Building... %1%").arg(pct);
|
||||||
@@ -1147,27 +1376,23 @@ void QSceneRenderer::drawUnitCard(QPainter &painter, Unit *unit)
|
|||||||
buildInfo += QString(" | ") + Localization::instance().get("ui.unit.charges_format", "Charges: %1").arg(unit->buildCharges);
|
buildInfo += QString(" | ") + Localization::instance().get("ui.unit.charges_format", "Charges: %1").arg(unit->buildCharges);
|
||||||
}
|
}
|
||||||
painter.setPen(QColor(255, 210, 80));
|
painter.setPen(QColor(255, 210, 80));
|
||||||
painter.drawText(QRect(cardRect.left() + 12, cardRect.top() + 94, 280, 14), Qt::AlignLeft | Qt::AlignVCenter, buildInfo);
|
painter.drawText(QRect(cardRect.left() + 12, cardRect.top() + 94, 286, 14), Qt::AlignLeft | Qt::AlignVCenter, buildInfo);
|
||||||
} else if (unit->hasRoadRoute()) {
|
} else if (unit->hasRoadRoute()) {
|
||||||
int cur = (int)unit->roadRouteIndex + 1;
|
int cur = (int)unit->roadRouteIndex + 1;
|
||||||
int total = (int)unit->roadRoute.size();
|
int total = (int)unit->roadRoute.size();
|
||||||
QString routeInfo = Localization::instance().get("ui.unit.status_road_route", "Road Route: %1/%2").arg(cur).arg(total);
|
QString routeInfo = Localization::instance().get("ui.unit.status_road_route", "Road Route: %1/%2").arg(cur).arg(total);
|
||||||
painter.setPen(QColor(255, 200, 70));
|
painter.setPen(QColor(255, 200, 70));
|
||||||
painter.drawText(QRect(cardRect.left() + 12, cardRect.top() + 94, 280, 14), Qt::AlignLeft | Qt::AlignVCenter, routeInfo);
|
painter.drawText(QRect(cardRect.left() + 12, cardRect.top() + 94, 286, 14), Qt::AlignLeft | Qt::AlignVCenter, routeInfo);
|
||||||
} else if (unit->hasQueuedAction()) {
|
} else if (unit->config.isMilitary()) {
|
||||||
QString qSummary = unit->getQueuedActionSummary();
|
QString combatInfo = Localization::instance().get("ui.unit.combat_format", "Attack: %1 | Range: %2 hex")
|
||||||
QString queuedInfo = Localization::instance().get("ui.unit.status_queued", "Queued: %1").arg(qSummary);
|
.arg(unit->config.attackDamage).arg(unit->config.attackRange);
|
||||||
if (unit->config.isBuilder() && !unit->config.infiniteCharges && unit->buildCharges > 0) {
|
painter.drawText(QRect(cardRect.left() + 12, cardRect.top() + 94, 286, 14), Qt::AlignLeft | Qt::AlignVCenter, combatInfo);
|
||||||
queuedInfo += QString(" | ") + Localization::instance().get("ui.unit.charges_format", "Charges: %1").arg(unit->buildCharges);
|
|
||||||
}
|
|
||||||
painter.setPen(QColor(100, 220, 140));
|
|
||||||
painter.drawText(QRect(cardRect.left() + 12, cardRect.top() + 94, 280, 14), Qt::AlignLeft | Qt::AlignVCenter, queuedInfo);
|
|
||||||
} else if (unit->config.isBuilder() && !unit->config.infiniteCharges && unit->buildCharges > 0) {
|
} else if (unit->config.isBuilder() && !unit->config.infiniteCharges && unit->buildCharges > 0) {
|
||||||
QString builderInfo = Localization::instance().get("ui.unit.civilian_unit", "Civilian unit") + QString(" | ") +
|
QString builderInfo = Localization::instance().get("ui.unit.civilian_unit", "Civilian unit") + QString(" | ") +
|
||||||
Localization::instance().get("ui.unit.charges_format", "Charges: %1").arg(unit->buildCharges);
|
Localization::instance().get("ui.unit.charges_format", "Charges: %1").arg(unit->buildCharges);
|
||||||
painter.drawText(QRect(cardRect.left() + 12, cardRect.top() + 94, 280, 14), Qt::AlignLeft | Qt::AlignVCenter, builderInfo);
|
painter.drawText(QRect(cardRect.left() + 12, cardRect.top() + 94, 286, 14), Qt::AlignLeft | Qt::AlignVCenter, builderInfo);
|
||||||
} else {
|
} else {
|
||||||
painter.drawText(QRect(cardRect.left() + 12, cardRect.top() + 94, 280, 14), Qt::AlignLeft | Qt::AlignVCenter,
|
painter.drawText(QRect(cardRect.left() + 12, cardRect.top() + 94, 286, 14), Qt::AlignLeft | Qt::AlignVCenter,
|
||||||
Localization::instance().get("ui.unit.civilian_unit", "Civilian unit"));
|
Localization::instance().get("ui.unit.civilian_unit", "Civilian unit"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1185,81 +1410,88 @@ void QSceneRenderer::drawUnitCard(QPainter &painter, Unit *unit)
|
|||||||
QString act = actions[a];
|
QString act = actions[a];
|
||||||
QRect bRect(actionArea.left() + 8 + a * (btnW + 8), actionArea.top() + 6, btnW, 40);
|
QRect bRect(actionArea.left() + 8 + a * (btnW + 8), actionArea.top() + 6, btnW, 40);
|
||||||
|
|
||||||
|
bool isMode = false;
|
||||||
bool isActive = false;
|
bool isActive = false;
|
||||||
QString iconFile = "images/actions/move.png";
|
QString iconFile = "images/actions/move.png";
|
||||||
QString label = Localization::instance().get("ui.unit.btn_move", "Move [M]");
|
QString label = Localization::instance().get("ui.unit.btn_move", "Move [M]");
|
||||||
QString tip = Localization::instance().get("ui.unit.tip_move", "Move (M)");
|
QString tip = Localization::instance().get("ui.unit.tip_move", "Move (M) - Click target tile (Shift+Click to queue)");
|
||||||
|
|
||||||
if (act == "verplaatsen") {
|
if (act == "verplaatsen") {
|
||||||
isActive = (GameManager::instance().currentActionMode() == ActionMode::Move);
|
isMode = (GameManager::instance().currentActionMode() == ActionMode::Move);
|
||||||
|
isActive = isMode;
|
||||||
iconFile = "images/actions/move.png";
|
iconFile = "images/actions/move.png";
|
||||||
label = Localization::instance().get("ui.unit.btn_move", "Move [M]");
|
label = Localization::instance().get("ui.unit.btn_move", "Move [M]");
|
||||||
tip = Localization::instance().get("ui.unit.tip_move", "Move (M)");
|
tip = Localization::instance().get("ui.unit.tip_move", "Move (M) - Click target tile (Shift+Click to queue)");
|
||||||
} else if (act == "aanvallen") {
|
} else if (act == "aanvallen") {
|
||||||
isActive = (GameManager::instance().currentActionMode() == ActionMode::Attack);
|
isMode = (GameManager::instance().currentActionMode() == ActionMode::Attack);
|
||||||
|
bool isQueued = (unit->currentAction.type == UnitActionType::Attack) || unit->hasQueuedActionType(UnitActionType::Attack);
|
||||||
|
isActive = isMode || isQueued;
|
||||||
iconFile = "images/actions/attack.png";
|
iconFile = "images/actions/attack.png";
|
||||||
label = Localization::instance().get("ui.unit.btn_attack", "Attack [A]");
|
label = Localization::instance().get("ui.unit.btn_attack", "Attack [A]");
|
||||||
tip = Localization::instance().get("ui.unit.tip_attack", "Attack (A) [Range: %1]").arg(unit->config.attackRange);
|
tip = Localization::instance().get("ui.unit.tip_attack", "Attack (A) [Range: %1] - Click enemy (Shift+Click to queue)").arg(unit->config.attackRange);
|
||||||
} else if (act == "steden_bouwen" || act == "build_city") {
|
} else if (act == "steden_bouwen" || act == "build_city") {
|
||||||
bool isQueued = unit->hasQueuedActionType(UnitActionType::BuildCity);
|
isMode = (GameManager::instance().currentActionMode() == ActionMode::BuildCity);
|
||||||
isActive = (GameManager::instance().currentActionMode() == ActionMode::BuildCity) || isQueued;
|
bool isQueued = (unit->currentAction.type == UnitActionType::BuildCity) || unit->hasQueuedActionType(UnitActionType::BuildCity);
|
||||||
|
isActive = isMode || isQueued;
|
||||||
iconFile = "images/actions/build_city.png";
|
iconFile = "images/actions/build_city.png";
|
||||||
label = isQueued ? "Queued" : Localization::instance().get("ui.unit.btn_build_city", "Settle [B]");
|
label = Localization::instance().get("ui.unit.btn_build_city", "Settle [B]");
|
||||||
tip = isQueued
|
tip = isQueued
|
||||||
? Localization::instance().get("ui.unit.city_queued", "Found City queued on arrival")
|
? Localization::instance().get("ui.unit.city_queued", "Found City queued")
|
||||||
: Localization::instance().get("ui.unit.tip_build_city", "Found City (B)");
|
: Localization::instance().get("ui.unit.tip_build_city", "Found City (B) - Click target tile (Shift+Click to queue)");
|
||||||
} else if (act == "boerderij_bouwen" || act == "build_farm") {
|
} else if (act == "boerderij_bouwen" || act == "build_farm") {
|
||||||
bool isQueued = unit->hasQueuedActionType(UnitActionType::BuildImprovement);
|
isMode = (GameManager::instance().currentActionMode() == ActionMode::BuildFarm);
|
||||||
isActive = (unit->isBuilding() && unit->buildingImprovementId != "road") || isQueued;
|
bool isBuildingFarm = unit->isBuilding() && !unit->buildingImprovementId.startsWith("road");
|
||||||
|
bool isQueued = (unit->currentAction.type == UnitActionType::BuildImprovement) || unit->hasQueuedActionType(UnitActionType::BuildImprovement);
|
||||||
|
isActive = isMode || isBuildingFarm || isQueued;
|
||||||
iconFile = "images/actions/farm.png";
|
iconFile = "images/actions/farm.png";
|
||||||
if (unit->isBuilding() && unit->buildingImprovementId != "road") {
|
if (isBuildingFarm) {
|
||||||
int pct = (int)(std::min(1.0f, unit->buildProgress) * 100.0f);
|
int pct = (int)(std::min(1.0f, unit->buildProgress) * 100.0f);
|
||||||
label = Localization::instance().get("ui.unit.btn_farm_building", "Building %1%").arg(pct);
|
label = Localization::instance().get("ui.unit.btn_farm_building", "Building %1%").arg(pct);
|
||||||
tip = Localization::instance().get("ui.unit.tip_farm_building", "Busy building... %1%").arg(pct);
|
tip = Localization::instance().get("ui.unit.tip_farm_building", "Busy building... %1% (Click to plan next target)").arg(pct);
|
||||||
} else if (isQueued) {
|
} else if (isQueued) {
|
||||||
label = Localization::instance().get("ui.unit.btn_farm_queued", "Farm (Queued)");
|
label = Localization::instance().get("ui.unit.btn_farm_queued", "Farm [F]");
|
||||||
tip = Localization::instance().get("ui.unit.tip_farm_queued", "Build Farm (Queued on arrival - click to cancel)");
|
tip = Localization::instance().get("ui.unit.tip_farm_queued", "Build Farm (Queued - Click to plan target)");
|
||||||
} else {
|
} else {
|
||||||
label = Localization::instance().get("ui.unit.btn_build_farm", "Farm [F]");
|
label = Localization::instance().get("ui.unit.btn_build_farm", "Farm [F]");
|
||||||
tip = Localization::instance().get("ui.unit.tip_build_farm", "Build Farm (F)");
|
tip = Localization::instance().get("ui.unit.tip_build_farm", "Build Farm (F) - Click target tile (Shift+Click to queue)");
|
||||||
}
|
}
|
||||||
} else if (act == "build_road" || act == "weg_bouwen") {
|
} else if (act == "build_road" || act == "weg_bouwen") {
|
||||||
bool isMode = (GameManager::instance().currentActionMode() == ActionMode::BuildRoad);
|
isMode = (GameManager::instance().currentActionMode() == ActionMode::BuildRoad);
|
||||||
bool isBuildingRoad = unit->isBuilding() && unit->buildingImprovementId.startsWith("road");
|
bool isBuildingRoad = unit->isBuilding() && unit->buildingImprovementId.startsWith("road");
|
||||||
bool hasRoute = unit->hasRoadRoute();
|
bool hasRoute = unit->hasRoadRoute();
|
||||||
bool isQueued = unit->hasQueuedActionType(UnitActionType::BuildRoad);
|
bool isQueued = (unit->currentAction.type == UnitActionType::BuildRoad) || unit->hasQueuedActionType(UnitActionType::BuildRoad);
|
||||||
isActive = isMode || isBuildingRoad || hasRoute || isQueued;
|
isActive = isMode || isBuildingRoad || hasRoute || isQueued;
|
||||||
iconFile = "images/actions/road.png";
|
iconFile = "images/actions/road.png";
|
||||||
if (isBuildingRoad) {
|
if (isBuildingRoad) {
|
||||||
int pct = (int)(std::min(1.0f, unit->buildProgress) * 100.0f);
|
int pct = (int)(std::min(1.0f, unit->buildProgress) * 100.0f);
|
||||||
label = Localization::instance().get("ui.unit.btn_road_building", "Building %1%").arg(pct);
|
label = Localization::instance().get("ui.unit.btn_road_building", "Building %1%").arg(pct);
|
||||||
tip = Localization::instance().get("ui.unit.tip_road_building", "Busy building road... %1%").arg(pct);
|
tip = Localization::instance().get("ui.unit.tip_road_building", "Busy building road... %1% (Click to plan next target)").arg(pct);
|
||||||
} else if (hasRoute) {
|
} else if (hasRoute) {
|
||||||
int cur = (int)unit->roadRouteIndex + 1;
|
int cur = (int)unit->roadRouteIndex + 1;
|
||||||
int total = (int)unit->roadRoute.size();
|
int total = (int)unit->roadRoute.size();
|
||||||
label = Localization::instance().get("ui.unit.btn_road_route", "Route (%1/%2)").arg(cur).arg(total);
|
label = Localization::instance().get("ui.unit.btn_road_route", "Route (%1/%2)").arg(cur).arg(total);
|
||||||
tip = Localization::instance().get("ui.unit.status_road_route", "Road Route: %1/%2").arg(cur).arg(total);
|
tip = Localization::instance().get("ui.unit.status_road_route", "Road Route: %1/%2").arg(cur).arg(total);
|
||||||
} else if (isQueued) {
|
} else if (isQueued) {
|
||||||
label = Localization::instance().get("ui.unit.btn_road_queued", "Road (Queued)");
|
label = Localization::instance().get("ui.unit.btn_road_queued", "Road [R]");
|
||||||
tip = Localization::instance().get("ui.unit.tip_road_queued", "Build Road (Queued on arrival - click to cancel)");
|
tip = Localization::instance().get("ui.unit.tip_road_queued", "Build Road (Queued - Click to plan target)");
|
||||||
} else {
|
} else {
|
||||||
label = Localization::instance().get("ui.unit.btn_build_road", "Road [R]");
|
label = Localization::instance().get("ui.unit.btn_build_road", "Road [R]");
|
||||||
tip = Localization::instance().get("ui.unit.tip_build_road", "Build Road (R) - Click target tile to build route");
|
tip = Localization::instance().get("ui.unit.tip_build_road", "Build Road (R) - Click target tile (Shift+Click to queue)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Circular bubble button
|
// Button styling
|
||||||
painter.setBrush(isActive ? QColor(45, 85, 145, 250) : QColor(24, 32, 46, 220));
|
painter.setBrush(isMode ? QColor(55, 105, 175, 250) : (isActive ? QColor(45, 85, 145, 240) : QColor(24, 32, 46, 220)));
|
||||||
painter.setPen(QPen(isActive ? QColor(255, 220, 50) : QColor(80, 110, 150), isActive ? 1.8 : 1.0));
|
painter.setPen(QPen(isMode ? QColor(100, 235, 255) : (isActive ? QColor(255, 220, 50) : QColor(80, 110, 150)), (isMode || isActive) ? 1.8 : 1.0));
|
||||||
painter.drawRoundedRect(bRect, 6, 6);
|
painter.drawRoundedRect(bRect, 6, 6);
|
||||||
|
|
||||||
// Icon inside button
|
// Icon inside button
|
||||||
QImage aIcon = getTintedIcon(iconFile, isActive ? QColor(255, 230, 80) : primaryCol);
|
QImage aIcon = getTintedIcon(iconFile, (isMode || isActive) ? QColor(255, 230, 80) : primaryCol);
|
||||||
painter.drawImage(QRect(bRect.left() + 8, bRect.top() + 10, 20, 20), aIcon);
|
painter.drawImage(QRect(bRect.left() + 8, bRect.top() + 10, 20, 20), aIcon);
|
||||||
|
|
||||||
// Label
|
// Label
|
||||||
painter.setFont(QFont("sans-serif", 7, QFont::Bold));
|
painter.setFont(QFont("sans-serif", 7, QFont::Bold));
|
||||||
painter.setPen(isActive ? QColor(255, 235, 90) : QColor(210, 225, 245));
|
painter.setPen((isMode || isActive) ? QColor(255, 235, 90) : QColor(210, 225, 245));
|
||||||
painter.drawText(QRect(bRect.left() + 32, bRect.top(), bRect.width() - 36, bRect.height()), Qt::AlignLeft | Qt::AlignVCenter, label);
|
painter.drawText(QRect(bRect.left() + 32, bRect.top(), bRect.width() - 36, bRect.height()), Qt::AlignLeft | Qt::AlignVCenter, label);
|
||||||
|
|
||||||
m_activeActionBubbles.push_back({bRect, act, tip});
|
m_activeActionBubbles.push_back({bRect, act, tip});
|
||||||
@@ -2541,39 +2773,29 @@ void QSceneRenderer::mousePressEvent(QMouseEvent *event)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Action Bubbles clicked
|
// 4. Action Bubbles clicked (all unit actions enter target-tile selection mode)
|
||||||
for (const auto &ab : m_activeActionBubbles) {
|
for (const auto &ab : m_activeActionBubbles) {
|
||||||
if (ab.rect.contains(event->pos())) {
|
if (ab.rect.contains(event->pos())) {
|
||||||
if (ab.actionId == "verplaatsen") {
|
if (ab.actionId == "verplaatsen") {
|
||||||
if (GameManager::instance().currentActionMode() == ActionMode::Move) {
|
GameManager::instance().setActionMode(
|
||||||
GameManager::instance().setActionMode(ActionMode::None);
|
GameManager::instance().currentActionMode() == ActionMode::Move ? ActionMode::None : ActionMode::Move
|
||||||
} else {
|
);
|
||||||
GameManager::instance().setActionMode(ActionMode::Move);
|
|
||||||
}
|
|
||||||
} else if (ab.actionId == "aanvallen") {
|
} else if (ab.actionId == "aanvallen") {
|
||||||
if (GameManager::instance().currentActionMode() == ActionMode::Attack) {
|
GameManager::instance().setActionMode(
|
||||||
GameManager::instance().setActionMode(ActionMode::None);
|
GameManager::instance().currentActionMode() == ActionMode::Attack ? ActionMode::None : ActionMode::Attack
|
||||||
} else {
|
);
|
||||||
GameManager::instance().setActionMode(ActionMode::Attack);
|
|
||||||
}
|
|
||||||
} else if (ab.actionId == "steden_bouwen" || ab.actionId == "build_city") {
|
} else if (ab.actionId == "steden_bouwen" || ab.actionId == "build_city") {
|
||||||
GameManager::instance().commandBuildCitySelected(m_point, m_numPoints05);
|
GameManager::instance().setActionMode(
|
||||||
|
GameManager::instance().currentActionMode() == ActionMode::BuildCity ? ActionMode::None : ActionMode::BuildCity
|
||||||
|
);
|
||||||
} else if (ab.actionId == "boerderij_bouwen" || ab.actionId == "build_farm") {
|
} else if (ab.actionId == "boerderij_bouwen" || ab.actionId == "build_farm") {
|
||||||
GameManager::instance().commandBuildFarmSelected(m_point, m_numPoints05);
|
GameManager::instance().setActionMode(
|
||||||
|
GameManager::instance().currentActionMode() == ActionMode::BuildFarm ? ActionMode::None : ActionMode::BuildFarm
|
||||||
|
);
|
||||||
} else if (ab.actionId == "build_road" || ab.actionId == "weg_bouwen") {
|
} else if (ab.actionId == "build_road" || ab.actionId == "weg_bouwen") {
|
||||||
Unit *sel = GameManager::instance().getSelectedUnit();
|
GameManager::instance().setActionMode(
|
||||||
if (sel && sel->hasRoadRoute()) {
|
GameManager::instance().currentActionMode() == ActionMode::BuildRoad ? ActionMode::None : ActionMode::BuildRoad
|
||||||
sel->clearRoadRoute();
|
);
|
||||||
sel->cancelBuilding();
|
|
||||||
GameManager::instance().setActionMode(ActionMode::None);
|
|
||||||
GameManager::instance().addFloatingText(
|
|
||||||
Localization::instance().get("ui.unit.road_route_cancelled", "Road route cancelled"),
|
|
||||||
sel->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 120, 100));
|
|
||||||
} else if (GameManager::instance().currentActionMode() == ActionMode::BuildRoad) {
|
|
||||||
GameManager::instance().setActionMode(ActionMode::None);
|
|
||||||
} else {
|
|
||||||
GameManager::instance().setActionMode(ActionMode::BuildRoad);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
update();
|
update();
|
||||||
return;
|
return;
|
||||||
@@ -2586,63 +2808,88 @@ void QSceneRenderer::mousePressEvent(QMouseEvent *event)
|
|||||||
QVector3D lookat = getCameraLookAt();
|
QVector3D lookat = getCameraLookAt();
|
||||||
vMatrix.lookAt(cameraPosition, lookat, cameraUpDirection);
|
vMatrix.lookAt(cameraPosition, lookat, cameraUpDirection);
|
||||||
|
|
||||||
// 5. Left Click: Check if directly clicked on any unit's Druppel pin
|
// 5. Check if directly clicked on any unit's Druppel pin
|
||||||
if (event->button() == Qt::LeftButton) {
|
Unit *clickedPinUnit = nullptr;
|
||||||
for (const auto &u : GameManager::instance().getUnits()) {
|
for (const auto &u : GameManager::instance().getUnits()) {
|
||||||
if (u->isDead()) continue;
|
if (u->isDead()) continue;
|
||||||
|
|
||||||
bool inFront = false;
|
bool inFront = false;
|
||||||
QVector3D topPoint = u->worldPos + QVector3D(0, 0, 0.38f);
|
QVector3D topPoint = u->worldPos + QVector3D(0, 0, 0.38f);
|
||||||
QPointF tipPos = UnitRenderer::projectToScreen(topPoint, Projection, vMatrix, width(), height(), inFront);
|
QPointF tipPos = UnitRenderer::projectToScreen(topPoint, Projection, vMatrix, width(), height(), inFront);
|
||||||
if (!inFront) continue;
|
if (!inFront) continue;
|
||||||
|
|
||||||
QPointF headCenter(tipPos.x(), tipPos.y() - 25.0f);
|
QPointF headCenter(tipPos.x(), tipPos.y() - 25.0f);
|
||||||
float distSq = (event->x() - headCenter.x()) * (event->x() - headCenter.x()) +
|
float distSq = (event->x() - headCenter.x()) * (event->x() - headCenter.x()) +
|
||||||
(event->y() - headCenter.y()) * (event->y() - headCenter.y());
|
(event->y() - headCenter.y()) * (event->y() - headCenter.y());
|
||||||
|
|
||||||
if (distSq <= 18.0f * 18.0f) {
|
if (distSq <= 18.0f * 18.0f) {
|
||||||
// If in attack mode and clicked enemy: attack!
|
clickedPinUnit = u.get();
|
||||||
Unit *sel = GameManager::instance().getSelectedUnit();
|
break;
|
||||||
if (sel && GameManager::instance().currentActionMode() == ActionMode::Attack && u->playerId != sel->playerId) {
|
|
||||||
GameManager::instance().commandAttack(u.get());
|
|
||||||
} else {
|
|
||||||
GameManager::instance().selectUnit(u->id);
|
|
||||||
}
|
|
||||||
update();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. Terrain hex picking via Raycast
|
// 6. Terrain hex picking via Raycast
|
||||||
int hitX = -1, hitY = -1;
|
int hitX = -1, hitY = -1;
|
||||||
bool hitTerrain = screenToHex(event->pos(), vMatrix, hitX, hitY);
|
bool hitTerrain = screenToHex(event->pos(), vMatrix, hitX, hitY);
|
||||||
|
if (!hitTerrain && clickedPinUnit) {
|
||||||
|
hitX = clickedPinUnit->tileX;
|
||||||
|
hitY = clickedPinUnit->tileY;
|
||||||
|
hitTerrain = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (hitTerrain) {
|
Unit *selected = GameManager::instance().getSelectedUnit();
|
||||||
Unit *selected = GameManager::instance().getSelectedUnit();
|
bool shiftHeld = (event->modifiers() & Qt::ShiftModifier) != 0;
|
||||||
|
|
||||||
if (event->button() == Qt::LeftButton) {
|
if (event->button() == Qt::LeftButton) {
|
||||||
if (selected) {
|
// When an ActionMode is active for the selected unit, clicking selects the target tile
|
||||||
if (GameManager::instance().currentActionMode() == ActionMode::Move) {
|
if (selected && GameManager::instance().currentActionMode() != ActionMode::None) {
|
||||||
GameManager::instance().commandMoveSelected(hitX, hitY, m_point, m_numPoints05);
|
ActionMode mode = GameManager::instance().currentActionMode();
|
||||||
GameManager::instance().setActionMode(ActionMode::None);
|
if (mode == ActionMode::Attack) {
|
||||||
update();
|
Unit *enemy = (clickedPinUnit && clickedPinUnit->playerId != selected->playerId)
|
||||||
return;
|
? clickedPinUnit
|
||||||
} else if (GameManager::instance().currentActionMode() == ActionMode::BuildRoad) {
|
: (hitTerrain ? GameManager::instance().getUnitAt(hitX, hitY) : nullptr);
|
||||||
GameManager::instance().commandBuildRoadToTileSelected(hitX, hitY, m_point, m_numPoints05);
|
if (enemy && enemy->playerId != selected->playerId) {
|
||||||
GameManager::instance().setActionMode(ActionMode::None);
|
bool ok = GameManager::instance().commandAttack(enemy, m_point, m_numPoints05, shiftHeld);
|
||||||
update();
|
if (ok && !shiftHeld) {
|
||||||
return;
|
GameManager::instance().setActionMode(ActionMode::None);
|
||||||
} else if (GameManager::instance().currentActionMode() == ActionMode::Attack) {
|
|
||||||
Unit *enemy = GameManager::instance().getUnitAt(hitX, hitY);
|
|
||||||
if (enemy && enemy->playerId != selected->playerId) {
|
|
||||||
GameManager::instance().commandAttack(enemy);
|
|
||||||
}
|
}
|
||||||
update();
|
} else if (hitTerrain) {
|
||||||
return;
|
QVector2D w = Pathfinder::hexToWorld(hitX, hitY);
|
||||||
|
float gz = Unit::getTerrainHeight(w.x(), w.y(), m_point, m_numPoints05);
|
||||||
|
GameManager::instance().addFloatingText(
|
||||||
|
Localization::instance().get("ui.unit.no_enemy_on_tile", "No enemy on target tile!"),
|
||||||
|
QVector3D(w.x(), w.y(), gz + 0.8f), QColor(255, 80, 80));
|
||||||
}
|
}
|
||||||
|
update();
|
||||||
|
return;
|
||||||
|
} else if (hitTerrain) {
|
||||||
|
bool ok = false;
|
||||||
|
if (mode == ActionMode::Move) {
|
||||||
|
ok = GameManager::instance().commandMoveSelected(hitX, hitY, m_point, m_numPoints05, shiftHeld);
|
||||||
|
} else if (mode == ActionMode::BuildFarm) {
|
||||||
|
ok = GameManager::instance().commandBuildFarmAtTileSelected(hitX, hitY, m_point, m_numPoints05, shiftHeld);
|
||||||
|
} else if (mode == ActionMode::BuildCity) {
|
||||||
|
ok = GameManager::instance().commandBuildCityAtTileSelected(hitX, hitY, m_point, m_numPoints05, shiftHeld);
|
||||||
|
} else if (mode == ActionMode::BuildRoad) {
|
||||||
|
ok = GameManager::instance().commandBuildRoadToTileSelected(hitX, hitY, m_point, m_numPoints05, shiftHeld);
|
||||||
|
}
|
||||||
|
// Keep ActionMode active if Shift is held so the player can keep chaining from the previous target tile
|
||||||
|
if (ok && !shiftHeld) {
|
||||||
|
GameManager::instance().setActionMode(ActionMode::None);
|
||||||
|
}
|
||||||
|
update();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Standard unit selection when no ActionMode is active
|
||||||
|
if (clickedPinUnit) {
|
||||||
|
GameManager::instance().selectUnit(clickedPinUnit->id);
|
||||||
|
update();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hitTerrain) {
|
||||||
// Check if there is a unit on hit tile to select
|
// Check if there is a unit on hit tile to select
|
||||||
Unit *unitOnTile = GameManager::instance().getUnitAt(hitX, hitY);
|
Unit *unitOnTile = GameManager::instance().getUnitAt(hitX, hitY);
|
||||||
if (unitOnTile) {
|
if (unitOnTile) {
|
||||||
@@ -2660,23 +2907,25 @@ void QSceneRenderer::mousePressEvent(QMouseEvent *event)
|
|||||||
}
|
}
|
||||||
update();
|
update();
|
||||||
return;
|
return;
|
||||||
} else if (event->button() == Qt::RightButton) {
|
}
|
||||||
if (GameManager::instance().currentActionMode() == ActionMode::BuildRoad) {
|
} else if (event->button() == Qt::RightButton) {
|
||||||
GameManager::instance().setActionMode(ActionMode::None);
|
if (GameManager::instance().currentActionMode() != ActionMode::None) {
|
||||||
update();
|
GameManager::instance().setActionMode(ActionMode::None);
|
||||||
return;
|
update();
|
||||||
}
|
return;
|
||||||
// Right click: Attack enemy unit or move selected unit directly to hex!
|
}
|
||||||
if (selected) {
|
// Right click: Attack enemy unit or move selected unit to target hex (supports Shift-queueing)
|
||||||
Unit *enemy = GameManager::instance().getUnitAt(hitX, hitY);
|
if (selected && hitTerrain) {
|
||||||
if (enemy && enemy->playerId != selected->playerId) {
|
Unit *enemy = (clickedPinUnit && clickedPinUnit->playerId != selected->playerId)
|
||||||
GameManager::instance().commandAttack(enemy);
|
? clickedPinUnit
|
||||||
} else {
|
: GameManager::instance().getUnitAt(hitX, hitY);
|
||||||
GameManager::instance().commandMoveSelected(hitX, hitY, m_point, m_numPoints05);
|
if (enemy && enemy->playerId != selected->playerId) {
|
||||||
}
|
GameManager::instance().commandAttack(enemy, m_point, m_numPoints05, shiftHeld);
|
||||||
update();
|
} else {
|
||||||
return;
|
GameManager::instance().commandMoveSelected(hitX, hitY, m_point, m_numPoints05, shiftHeld);
|
||||||
}
|
}
|
||||||
|
update();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2873,7 +3122,7 @@ void QSceneRenderer::keyPressEvent(QKeyEvent *e)
|
|||||||
if (zRot >= 360.0f) zRot -= 360.0f;
|
if (zRot >= 360.0f) zRot -= 360.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unit action hotkeys
|
// Unit action hotkeys (all toggle target-tile selection mode)
|
||||||
Unit *sel = GameManager::instance().getSelectedUnit();
|
Unit *sel = GameManager::instance().getSelectedUnit();
|
||||||
if (e->key() == Qt::Key_M && sel) {
|
if (e->key() == Qt::Key_M && sel) {
|
||||||
GameManager::instance().setActionMode(
|
GameManager::instance().setActionMode(
|
||||||
@@ -2886,39 +3135,35 @@ void QSceneRenderer::keyPressEvent(QKeyEvent *e)
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
else if (e->key() == Qt::Key_B && sel && sel->config.canBuildCity()) {
|
else if (e->key() == Qt::Key_B && sel && sel->config.canBuildCity()) {
|
||||||
GameManager::instance().commandBuildCitySelected(m_point, m_numPoints05);
|
GameManager::instance().setActionMode(
|
||||||
|
GameManager::instance().currentActionMode() == ActionMode::BuildCity ? ActionMode::None : ActionMode::BuildCity
|
||||||
|
);
|
||||||
}
|
}
|
||||||
else if (e->key() == Qt::Key_F && sel && sel->config.canBuildFarm()) {
|
else if (e->key() == Qt::Key_F && sel && sel->config.canBuildFarm()) {
|
||||||
GameManager::instance().commandBuildFarmSelected(m_point, m_numPoints05);
|
GameManager::instance().setActionMode(
|
||||||
|
GameManager::instance().currentActionMode() == ActionMode::BuildFarm ? ActionMode::None : ActionMode::BuildFarm
|
||||||
|
);
|
||||||
}
|
}
|
||||||
else if (e->key() == Qt::Key_R && sel && sel->config.canBuildRoad()) {
|
else if (e->key() == Qt::Key_R && sel && sel->config.canBuildRoad()) {
|
||||||
if (sel->hasRoadRoute()) {
|
GameManager::instance().setActionMode(
|
||||||
sel->clearRoadRoute();
|
GameManager::instance().currentActionMode() == ActionMode::BuildRoad ? ActionMode::None : ActionMode::BuildRoad
|
||||||
sel->cancelBuilding();
|
);
|
||||||
GameManager::instance().setActionMode(ActionMode::None);
|
|
||||||
GameManager::instance().addFloatingText(
|
|
||||||
Localization::instance().get("ui.unit.road_route_cancelled", "Road route cancelled"),
|
|
||||||
sel->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 120, 100));
|
|
||||||
} else {
|
|
||||||
GameManager::instance().setActionMode(
|
|
||||||
GameManager::instance().currentActionMode() == ActionMode::BuildRoad ? ActionMode::None : ActionMode::BuildRoad
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (e->key() == Qt::Key_Escape) {
|
else if (e->key() == Qt::Key_Escape) {
|
||||||
if (GameManager::instance().currentActionMode() != ActionMode::None) {
|
if (GameManager::instance().currentActionMode() != ActionMode::None) {
|
||||||
GameManager::instance().setActionMode(ActionMode::None);
|
GameManager::instance().setActionMode(ActionMode::None);
|
||||||
} else if (sel && sel->hasRoadRoute()) {
|
|
||||||
sel->clearRoadRoute();
|
|
||||||
sel->cancelBuilding();
|
|
||||||
GameManager::instance().addFloatingText(
|
|
||||||
Localization::instance().get("ui.unit.road_route_cancelled", "Road route cancelled"),
|
|
||||||
sel->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 120, 100));
|
|
||||||
} else if (sel && sel->hasQueuedAction()) {
|
} else if (sel && sel->hasQueuedAction()) {
|
||||||
sel->clearActionQueue();
|
sel->clearActionQueue();
|
||||||
GameManager::instance().addFloatingText(
|
GameManager::instance().addFloatingText(
|
||||||
Localization::instance().get("ui.unit.queue_cleared", "Queued actions cancelled"),
|
Localization::instance().get("ui.unit.queue_cleared", "Queued actions cancelled"),
|
||||||
sel->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 180, 80));
|
sel->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 180, 80));
|
||||||
|
} else if (sel && sel->hasRoadRoute()) {
|
||||||
|
sel->clearRoadRoute();
|
||||||
|
sel->cancelBuilding();
|
||||||
|
sel->currentAction = UnitAction();
|
||||||
|
GameManager::instance().addFloatingText(
|
||||||
|
Localization::instance().get("ui.unit.road_route_cancelled", "Road route cancelled"),
|
||||||
|
sel->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 120, 100));
|
||||||
} else {
|
} else {
|
||||||
GameManager::instance().deselectUnit();
|
GameManager::instance().deselectUnit();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
#include "unit.h"
|
#include "unit.h"
|
||||||
#include "pathfinder.h"
|
#include "pathfinder.h"
|
||||||
#include "terrainmovementmanager.h"
|
#include "terrainmovementmanager.h"
|
||||||
|
#include "localization.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
Unit::Unit(int uId, int pId, const UnitConfig &cfg, int startX, int startY, float groundZ)
|
Unit::Unit(int uId, int pId, const UnitConfig &cfg, int startX, int startY, float groundZ)
|
||||||
: id(uId), playerId(pId), config(cfg), tileX(startX), tileY(startY),
|
: id(uId), playerId(pId), config(cfg), tileX(startX), tileY(startY),
|
||||||
@@ -57,6 +59,36 @@ bool Unit::hasQueuedActionType(UnitActionType type) const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Unit::hasQueuedImprovementAt(int tx, int ty) const
|
||||||
|
{
|
||||||
|
if (currentAction.type == UnitActionType::BuildImprovement &&
|
||||||
|
currentAction.targetX == tx && currentAction.targetY == ty) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for (const auto &act : actionQueue) {
|
||||||
|
if (act.type == UnitActionType::BuildImprovement &&
|
||||||
|
act.targetX == tx && act.targetY == ty) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Unit::hasQueuedCityAt(int tx, int ty) const
|
||||||
|
{
|
||||||
|
if (currentAction.type == UnitActionType::BuildCity &&
|
||||||
|
currentAction.targetX == tx && currentAction.targetY == ty) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for (const auto &act : actionQueue) {
|
||||||
|
if (act.type == UnitActionType::BuildCity &&
|
||||||
|
act.targetX == tx && act.targetY == ty) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void Unit::removeQueuedActionsOfType(UnitActionType type)
|
void Unit::removeQueuedActionsOfType(UnitActionType type)
|
||||||
{
|
{
|
||||||
for (auto it = actionQueue.begin(); it != actionQueue.end(); ) {
|
for (auto it = actionQueue.begin(); it != actionQueue.end(); ) {
|
||||||
@@ -82,23 +114,35 @@ const UnitAction* Unit::peekNextQueuedAction() const
|
|||||||
return &actionQueue.front();
|
return &actionQueue.front();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString Unit::getActionSummary(const UnitAction &act) const
|
||||||
|
{
|
||||||
|
switch (act.type) {
|
||||||
|
case UnitActionType::Move:
|
||||||
|
return Localization::instance().get("ui.actions.move", "Move");
|
||||||
|
case UnitActionType::BuildImprovement:
|
||||||
|
return Localization::instance().get("ui.actions.build_farm", "Build Farm");
|
||||||
|
case UnitActionType::BuildCity:
|
||||||
|
return Localization::instance().get("ui.actions.found_city", "Found City");
|
||||||
|
case UnitActionType::BuildRoad:
|
||||||
|
return Localization::instance().get("ui.actions.build_road", "Build Road");
|
||||||
|
case UnitActionType::Attack:
|
||||||
|
return Localization::instance().get("ui.actions.attack", "Attack");
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QString Unit::getQueuedActionSummary() const
|
QString Unit::getQueuedActionSummary() const
|
||||||
{
|
{
|
||||||
if (actionQueue.empty()) return "";
|
if (actionQueue.empty()) return "";
|
||||||
switch (actionQueue.front().type) {
|
QStringList parts;
|
||||||
case UnitActionType::Move:
|
for (size_t i = 0; i < actionQueue.size() && i < 3; ++i) {
|
||||||
return "Move";
|
parts.append(getActionSummary(actionQueue[i]));
|
||||||
case UnitActionType::BuildImprovement:
|
|
||||||
return "Build Farm";
|
|
||||||
case UnitActionType::BuildCity:
|
|
||||||
return "Found City";
|
|
||||||
case UnitActionType::BuildRoad:
|
|
||||||
return "Build Road";
|
|
||||||
case UnitActionType::Attack:
|
|
||||||
return "Attack";
|
|
||||||
default:
|
|
||||||
return "Action";
|
|
||||||
}
|
}
|
||||||
|
if (actionQueue.size() > 3) {
|
||||||
|
parts.append(QString("+%1").arg(actionQueue.size() - 3));
|
||||||
|
}
|
||||||
|
return parts.join(" -> ");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Unit::setPath(const std::vector<std::pair<int, int>> &newPath)
|
void Unit::setPath(const std::vector<std::pair<int, int>> &newPath)
|
||||||
@@ -122,6 +166,7 @@ void Unit::stopMovement()
|
|||||||
pathIndex = 0;
|
pathIndex = 0;
|
||||||
moveProgress = 0.0f;
|
moveProgress = 0.0f;
|
||||||
state = UnitState::Idle;
|
state = UnitState::Idle;
|
||||||
|
currentAction = UnitAction();
|
||||||
clearActionQueue();
|
clearActionQueue();
|
||||||
clearRoadRoute();
|
clearRoadRoute();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ struct UnitAction {
|
|||||||
|
|
||||||
UnitAction() = default;
|
UnitAction() = default;
|
||||||
UnitAction(UnitActionType t) : type(t) {}
|
UnitAction(UnitActionType t) : type(t) {}
|
||||||
UnitAction(UnitActionType t, int x, int y, const QString &data = "")
|
UnitAction(UnitActionType t, int x, int y, const QString &data = "", int uId = -1)
|
||||||
: type(t), targetX(x), targetY(y), extraData(data) {}
|
: type(t), targetX(x), targetY(y), targetUnitId(uId), extraData(data) {}
|
||||||
UnitAction(UnitActionType t, int targetId)
|
UnitAction(UnitActionType t, int targetId)
|
||||||
: type(t), targetUnitId(targetId) {}
|
: type(t), targetUnitId(targetId) {}
|
||||||
|
|
||||||
@@ -65,9 +65,10 @@ public:
|
|||||||
int targetTileX;
|
int targetTileX;
|
||||||
int targetTileY;
|
int targetTileY;
|
||||||
|
|
||||||
// Action Queue
|
// Currently executing action & subsequent Action Queue
|
||||||
|
UnitAction currentAction;
|
||||||
std::deque<UnitAction> actionQueue;
|
std::deque<UnitAction> actionQueue;
|
||||||
int maxQueueSize = 5;
|
int maxQueueSize = 15;
|
||||||
|
|
||||||
// Road Construction Route (tile-by-tile road paving)
|
// Road Construction Route (tile-by-tile road paving)
|
||||||
std::vector<std::pair<int, int>> roadRoute;
|
std::vector<std::pair<int, int>> roadRoute;
|
||||||
@@ -88,13 +89,20 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool hasActiveAction() const {
|
||||||
|
return currentAction.isValid() || state != UnitState::Idle || hasRoadRoute();
|
||||||
|
}
|
||||||
|
|
||||||
bool queueAction(const UnitAction &action);
|
bool queueAction(const UnitAction &action);
|
||||||
void clearActionQueue();
|
void clearActionQueue();
|
||||||
bool hasQueuedAction() const { return !actionQueue.empty(); }
|
bool hasQueuedAction() const { return !actionQueue.empty(); }
|
||||||
bool hasQueuedActionType(UnitActionType type) const;
|
bool hasQueuedActionType(UnitActionType type) const;
|
||||||
|
bool hasQueuedImprovementAt(int tx, int ty) const;
|
||||||
|
bool hasQueuedCityAt(int tx, int ty) const;
|
||||||
void removeQueuedActionsOfType(UnitActionType type);
|
void removeQueuedActionsOfType(UnitActionType type);
|
||||||
UnitAction popNextQueuedAction();
|
UnitAction popNextQueuedAction();
|
||||||
const UnitAction* peekNextQueuedAction() const;
|
const UnitAction* peekNextQueuedAction() const;
|
||||||
|
QString getActionSummary(const UnitAction &act) const;
|
||||||
QString getQueuedActionSummary() const;
|
QString getQueuedActionSummary() const;
|
||||||
|
|
||||||
// Attack cooldown / flash
|
// Attack cooldown / flash
|
||||||
|
|||||||
Reference in New Issue
Block a user