Implement unit action queueing and busy state validation
This commit is contained in:
@@ -1,5 +1,11 @@
|
|||||||
movement_tick_duration: 30.0
|
movement_tick_duration: 30.0
|
||||||
|
|
||||||
|
# Action queue configuration
|
||||||
|
action_queue:
|
||||||
|
max_size: 5
|
||||||
|
auto_queue_move_when_busy: true
|
||||||
|
allow_action_queueing: true
|
||||||
|
|
||||||
# Camera settings
|
# Camera settings
|
||||||
camera:
|
camera:
|
||||||
min_zoom: 2.0
|
min_zoom: 2.0
|
||||||
|
|||||||
@@ -45,6 +45,27 @@ ui:
|
|||||||
|
|
||||||
unit:
|
unit:
|
||||||
moving: "Moving"
|
moving: "Moving"
|
||||||
|
move_queued: "Movement queued"
|
||||||
|
queue_cleared: "Queued actions cancelled"
|
||||||
|
queue_full: "Action queue full!"
|
||||||
|
action_queued: "Action queued: %1"
|
||||||
|
action_cancelled: "%1 cancelled"
|
||||||
|
farm_queued: "Build Farm queued on arrival"
|
||||||
|
farm_started: "Constructing Farm..."
|
||||||
|
farm_completed: "Farm completed!"
|
||||||
|
farm_completed_consumed: "Farm completed! (Builder consumed)"
|
||||||
|
farm_completed_charges: "Farm completed! (%1 %2 remaining)"
|
||||||
|
city_queued: "Found City queued on arrival"
|
||||||
|
city_founded: "%1 founded!"
|
||||||
|
already_building: "Already busy building!"
|
||||||
|
busy: "Unit is busy!"
|
||||||
|
cannot_act_busy: "Unit cannot perform action while busy!"
|
||||||
|
tile_occupied: "Tile already has an improvement!"
|
||||||
|
cannot_build_here: "Cannot build here! Allowed on: %1"
|
||||||
|
cannot_build_destination: "Cannot build on destination terrain! Allowed on: %1"
|
||||||
|
no_build_charges: "No build charges remaining!"
|
||||||
|
charge_singular: "charge"
|
||||||
|
charge_plural: "charges"
|
||||||
impassable_terrain: "Impassable terrain!"
|
impassable_terrain: "Impassable terrain!"
|
||||||
naval_only_water: "Ships can only sail on water!"
|
naval_only_water: "Ships can only sail on water!"
|
||||||
no_path: "No reachable path!"
|
no_path: "No reachable path!"
|
||||||
@@ -53,6 +74,26 @@ ui:
|
|||||||
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!"
|
||||||
|
civilian_unit: "Civilian unit"
|
||||||
|
military_unit: "Military unit"
|
||||||
|
speed_format: "Speed: %1 hex/tick"
|
||||||
|
cost_format: "Cost: %1 pts"
|
||||||
|
combat_format: "Attack: %1 | Range: %2 hex"
|
||||||
|
building_pct: "Building... %1%"
|
||||||
|
charges_format: "Charges: %1"
|
||||||
|
status_queued: "Queued: %1"
|
||||||
|
btn_move: "Move [M]"
|
||||||
|
btn_attack: "Attack [A]"
|
||||||
|
btn_build_city: "Settle [B]"
|
||||||
|
btn_build_farm: "Farm [F]"
|
||||||
|
btn_farm_queued: "Farm (Queued)"
|
||||||
|
btn_farm_building: "Building %1%"
|
||||||
|
tip_move: "Move (M)"
|
||||||
|
tip_attack: "Attack (A) [Range: %1]"
|
||||||
|
tip_build_city: "Found City (B)"
|
||||||
|
tip_build_farm: "Build Farm (F)"
|
||||||
|
tip_farm_building: "Busy building... %1%"
|
||||||
|
tip_farm_queued: "Build Farm (Queued on arrival - click to cancel)"
|
||||||
|
|
||||||
city:
|
city:
|
||||||
center: "City Center"
|
center: "City Center"
|
||||||
|
|||||||
+285
-19
@@ -16,7 +16,9 @@ GameManager& GameManager::instance()
|
|||||||
GameManager::GameManager()
|
GameManager::GameManager()
|
||||||
: m_selectedUnitId(-1), m_activeCityViewId(-1), m_actionMode(ActionMode::None),
|
: m_selectedUnitId(-1), m_activeCityViewId(-1), m_actionMode(ActionMode::None),
|
||||||
m_movementTickDuration(30.0f), m_tickTimer(0.0f),
|
m_movementTickDuration(30.0f), m_tickTimer(0.0f),
|
||||||
m_simSpeedMultiplier(1.0f), m_nextUnitId(0), m_nextCityId(0),
|
m_simSpeedMultiplier(1.0f),
|
||||||
|
m_maxActionQueueSize(5), m_autoQueueMoveWhenBusy(true), m_allowActionQueueing(true),
|
||||||
|
m_nextUnitId(0), m_nextCityId(0),
|
||||||
m_initialized(false)
|
m_initialized(false)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -37,6 +39,12 @@ void GameManager::init(const QString &baseDir, Matrix2D<TerrainPoint> *mapPoints
|
|||||||
if (gameRoot.hasChild("movement_tick_duration")) {
|
if (gameRoot.hasChild("movement_tick_duration")) {
|
||||||
m_movementTickDuration = gameRoot["movement_tick_duration"].asFloat(30.0f);
|
m_movementTickDuration = gameRoot["movement_tick_duration"].asFloat(30.0f);
|
||||||
}
|
}
|
||||||
|
if (gameRoot.hasChild("action_queue")) {
|
||||||
|
const YamlNode &aqNode = gameRoot["action_queue"];
|
||||||
|
m_maxActionQueueSize = aqNode["max_size"].asInt(5);
|
||||||
|
m_autoQueueMoveWhenBusy = aqNode["auto_queue_move_when_busy"].asBool(true);
|
||||||
|
m_allowActionQueueing = aqNode["allow_action_queueing"].asBool(true);
|
||||||
|
}
|
||||||
|
|
||||||
// Load unit YAML configurations
|
// Load unit YAML configurations
|
||||||
QString unitsConfigDir = baseDir + "/config/units";
|
QString unitsConfigDir = baseDir + "/config/units";
|
||||||
@@ -102,6 +110,7 @@ Unit* GameManager::spawnUnit(const QString &configId, int playerId, int tileX, i
|
|||||||
|
|
||||||
int id = getNextUnitId();
|
int id = getNextUnitId();
|
||||||
auto unit = std::make_shared<Unit>(id, playerId, *cfg, tileX, tileY, gz);
|
auto unit = std::make_shared<Unit>(id, playerId, *cfg, tileX, tileY, gz);
|
||||||
|
unit->maxQueueSize = m_maxActionQueueSize;
|
||||||
m_units.push_back(unit);
|
m_units.push_back(unit);
|
||||||
return unit.get();
|
return unit.get();
|
||||||
}
|
}
|
||||||
@@ -122,6 +131,9 @@ void GameManager::update(float deltaTime, Matrix2D<TerrainPoint> *mapPoints, int
|
|||||||
if (u->isBuilding() && u->buildProgress >= 1.0f) {
|
if (u->isBuilding() && u->buildProgress >= 1.0f) {
|
||||||
completeUnitBuilding(*u, mapPoints, numPoints05);
|
completeUnitBuilding(*u, mapPoints, numPoints05);
|
||||||
}
|
}
|
||||||
|
if (!u->isDead() && u->state == UnitState::Idle && u->hasQueuedAction()) {
|
||||||
|
processUnitActionQueue(*u, mapPoints, numPoints05);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -504,12 +516,30 @@ void GameManager::commandMoveSelected(int targetX, int targetY, Matrix2D<Terrain
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If unit is busy building, it CANNOT walk right now. Queue the move action!
|
||||||
|
if (selected->isBuilding()) {
|
||||||
|
if (m_allowActionQueueing && m_autoQueueMoveWhenBusy) {
|
||||||
|
if (selected->queueAction(UnitAction(UnitActionType::Move, targetX, targetY))) {
|
||||||
|
addFloatingText(Localization::instance().get("ui.unit.move_queued", "Movement queued"),
|
||||||
|
selected->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 230, 80));
|
||||||
|
} else {
|
||||||
|
addFloatingText(Localization::instance().get("ui.unit.queue_full", "Action queue full!"),
|
||||||
|
selected->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 100, 100));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addFloatingText(Localization::instance().get("ui.unit.busy", "Unit is busy!"),
|
||||||
|
selected->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 100, 100));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<std::pair<int, int>> path = Pathfinder::findPath(
|
std::vector<std::pair<int, int>> path = Pathfinder::findPath(
|
||||||
selected->tileX, selected->tileY, targetX, targetY, mapPoints, numPoints05,
|
selected->tileX, selected->tileY, targetX, targetY, mapPoints, numPoints05,
|
||||||
selected->config.movementDomain, &selected->config
|
selected->config.movementDomain, &selected->config
|
||||||
);
|
);
|
||||||
|
|
||||||
if (path.size() > 1) {
|
if (path.size() > 1) {
|
||||||
|
selected->clearActionQueue();
|
||||||
selected->setPath(path);
|
selected->setPath(path);
|
||||||
addFloatingText(Localization::instance().get("ui.unit.moving", "Moving"),
|
addFloatingText(Localization::instance().get("ui.unit.moving", "Moving"),
|
||||||
selected->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 230, 80));
|
selected->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 230, 80));
|
||||||
@@ -524,8 +554,15 @@ void GameManager::commandAttack(Unit *target)
|
|||||||
Unit *selected = getSelectedUnit();
|
Unit *selected = getSelectedUnit();
|
||||||
if (!selected || !target) return;
|
if (!selected || !target) return;
|
||||||
|
|
||||||
|
if (selected->isBuilding()) {
|
||||||
|
addFloatingText(Localization::instance().get("ui.unit.cannot_act_busy", "Unit cannot perform action while busy!"),
|
||||||
|
selected->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 90, 90));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!selected->canAttack(*target)) {
|
if (!selected->canAttack(*target)) {
|
||||||
addFloatingText("Buiten bereik!", selected->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 80, 80));
|
addFloatingText(Localization::instance().get("ui.unit.out_of_range", "Out of range!"),
|
||||||
|
selected->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 80, 80));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -536,7 +573,8 @@ void GameManager::commandAttack(Unit *target)
|
|||||||
addFloatingText(dmgText, target->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 50, 50));
|
addFloatingText(dmgText, target->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 50, 50));
|
||||||
|
|
||||||
if (target->isDead()) {
|
if (target->isDead()) {
|
||||||
addFloatingText("Verslagen!", target->worldPos + QVector3D(0, 0, 1.2f), QColor(255, 200, 50), 2.2f);
|
addFloatingText(Localization::instance().get("ui.unit.defeated", "Defeated!"),
|
||||||
|
target->worldPos + QVector3D(0, 0, 1.2f), QColor(255, 200, 50), 2.2f);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_actionMode = ActionMode::None;
|
m_actionMode = ActionMode::None;
|
||||||
@@ -547,32 +585,62 @@ void GameManager::commandBuildCitySelected(Matrix2D<TerrainPoint> *mapPoints, in
|
|||||||
Unit *selected = getSelectedUnit();
|
Unit *selected = getSelectedUnit();
|
||||||
if (!selected || !selected->config.canBuildCity()) return;
|
if (!selected || !selected->config.canBuildCity()) return;
|
||||||
|
|
||||||
|
if (selected->isBuilding()) {
|
||||||
|
addFloatingText(Localization::instance().get("ui.unit.cannot_act_busy", "Unit cannot perform action while busy!"),
|
||||||
|
selected->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 90, 90));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If unit is moving, queue city founding upon arrival
|
||||||
|
if (selected->state == UnitState::Moving) {
|
||||||
|
if (selected->hasQueuedActionType(UnitActionType::BuildCity)) {
|
||||||
|
selected->removeQueuedActionsOfType(UnitActionType::BuildCity);
|
||||||
|
addFloatingText(Localization::instance().get("ui.unit.action_cancelled", "%1 cancelled").arg("Found City"),
|
||||||
|
selected->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 180, 80));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selected->queueAction(UnitAction(UnitActionType::BuildCity, selected->targetTileX, selected->targetTileY))) {
|
||||||
|
addFloatingText(Localization::instance().get("ui.unit.city_queued", "Found City queued on arrival"),
|
||||||
|
selected->worldPos + QVector3D(0, 0, 0.8f), QColor(80, 220, 120), 2.2f);
|
||||||
|
} else {
|
||||||
|
addFloatingText(Localization::instance().get("ui.unit.queue_full", "Action queue full!"),
|
||||||
|
selected->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 100, 100));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int tx = selected->tileX;
|
||||||
|
int ty = selected->tileY;
|
||||||
|
|
||||||
// Check if tile already has a city
|
// Check if tile already has a city
|
||||||
for (const auto &c : m_cities) {
|
for (const auto &c : m_cities) {
|
||||||
if (c.tileX == selected->tileX && c.tileY == selected->tileY) {
|
if (c.tileX == tx && c.tileY == ty) {
|
||||||
addFloatingText("Al een stad hier!", selected->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 80, 80));
|
addFloatingText(Localization::instance().get("ui.unit.city_already_here", "A city already exists here!"),
|
||||||
|
selected->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 80, 80));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const CityConfig &cCfg = TerrainYieldManager::instance().getCityConfig();
|
const CityConfig &cCfg = TerrainYieldManager::instance().getCityConfig();
|
||||||
int cId = getNextCityId();
|
int cId = getNextCityId();
|
||||||
QString cityName = QString("Stad %1").arg(cId);
|
QString cityName = QString("City %1").arg(cId);
|
||||||
QVector2D w = Pathfinder::hexToWorld(selected->tileX, selected->tileY);
|
QVector2D w = Pathfinder::hexToWorld(tx, ty);
|
||||||
float gz = Unit::getTerrainHeight(w.x(), w.y(), mapPoints, numPoints05);
|
float gz = Unit::getTerrainHeight(w.x(), w.y(), mapPoints, numPoints05);
|
||||||
|
|
||||||
City newCity(cId, selected->playerId, cityName, selected->tileX, selected->tileY,
|
City newCity(cId, selected->playerId, cityName, tx, ty,
|
||||||
QVector3D(w.x(), w.y(), gz),
|
QVector3D(w.x(), w.y(), gz),
|
||||||
cCfg.initialInhabitants, cCfg.inhabitantsPerPop, cCfg.initialBorderRadius);
|
cCfg.initialInhabitants, cCfg.inhabitantsPerPop, cCfg.initialBorderRadius);
|
||||||
|
|
||||||
// Assign initial worker to center tile
|
// Assign initial worker to center tile
|
||||||
QString tType = TerrainYieldManager::instance().getTerrainType(selected->tileX, selected->tileY, mapPoints, numPoints05);
|
QString tType = TerrainYieldManager::instance().getTerrainType(tx, ty, mapPoints, numPoints05);
|
||||||
const TerrainYieldConfig *yCfg = TerrainYieldManager::instance().getYieldConfig(tType);
|
const TerrainYieldConfig *yCfg = TerrainYieldManager::instance().getYieldConfig(tType);
|
||||||
QString defRes = yCfg ? yCfg->primaryResource : "voedsel";
|
QString defRes = yCfg ? yCfg->primaryResource : "voedsel";
|
||||||
newCity.assignWorker(selected->tileX, selected->tileY, defRes);
|
newCity.assignWorker(tx, ty, defRes);
|
||||||
|
|
||||||
m_cities.push_back(newCity);
|
m_cities.push_back(newCity);
|
||||||
addFloatingText(QString("%1 gesticht!").arg(cityName), QVector3D(w.x(), w.y(), gz + 1.2f), QColor(80, 255, 120), 2.5f);
|
addFloatingText(Localization::instance().get("ui.unit.city_founded", "%1 founded!").arg(cityName),
|
||||||
|
QVector3D(w.x(), w.y(), gz + 1.2f), QColor(80, 255, 120), 2.5f);
|
||||||
|
|
||||||
// Consuming settler
|
// Consuming settler
|
||||||
selected->takeDamage(99999.0f);
|
selected->takeDamage(99999.0f);
|
||||||
@@ -605,7 +673,55 @@ void GameManager::commandBuildFarmSelected(Matrix2D<TerrainPoint> *mapPoints, in
|
|||||||
if (!selected || !selected->config.canBuildFarm()) return;
|
if (!selected || !selected->config.canBuildFarm()) return;
|
||||||
|
|
||||||
if (selected->isBuilding()) {
|
if (selected->isBuilding()) {
|
||||||
addFloatingText("Al bezig met bouwen!", selected->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 200, 70));
|
addFloatingText(Localization::instance().get("ui.unit.already_building", "Already busy building!"),
|
||||||
|
selected->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 200, 70));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selected->config.infiniteCharges && selected->buildCharges <= 0) {
|
||||||
|
addFloatingText(Localization::instance().get("ui.unit.no_build_charges", "No build charges remaining!"),
|
||||||
|
selected->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 90, 90));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If unit is currently moving: toggle or queue build action on arrival!
|
||||||
|
if (selected->state == UnitState::Moving) {
|
||||||
|
if (selected->hasQueuedActionType(UnitActionType::BuildImprovement)) {
|
||||||
|
selected->removeQueuedActionsOfType(UnitActionType::BuildImprovement);
|
||||||
|
addFloatingText(Localization::instance().get("ui.unit.action_cancelled", "%1 cancelled").arg("Build Farm"),
|
||||||
|
selected->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 180, 80));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int destX = selected->targetTileX;
|
||||||
|
int destY = selected->targetTileY;
|
||||||
|
|
||||||
|
// Check if destination tile already has an improvement
|
||||||
|
if (hasImprovementAt(destX, destY)) {
|
||||||
|
addFloatingText(Localization::instance().get("ui.unit.tile_occupied", "Tile already has an improvement!"),
|
||||||
|
selected->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 90, 90));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check destination terrain type against allowed terrains
|
||||||
|
QString tType = TerrainYieldManager::instance().getTerrainType(destX, destY, mapPoints, numPoints05);
|
||||||
|
const ImprovementConfig *farmCfg = TerrainYieldManager::instance().getImprovementConfig("farm");
|
||||||
|
QStringList validTerrains = farmCfg ? farmCfg->validTerrains : QStringList{"grasland", "tundra"};
|
||||||
|
|
||||||
|
if (!validTerrains.contains(tType)) {
|
||||||
|
QString validStr = validTerrains.join(", ");
|
||||||
|
addFloatingText(Localization::instance().get("ui.unit.cannot_build_destination", "Cannot build on destination terrain! Allowed on: %1").arg(validStr),
|
||||||
|
selected->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 80, 80));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selected->queueAction(UnitAction(UnitActionType::BuildImprovement, destX, destY, "farm"))) {
|
||||||
|
addFloatingText(Localization::instance().get("ui.unit.farm_queued", "Build Farm queued on arrival"),
|
||||||
|
selected->worldPos + QVector3D(0, 0, 0.8f), QColor(80, 220, 120), 2.2f);
|
||||||
|
} else {
|
||||||
|
addFloatingText(Localization::instance().get("ui.unit.queue_full", "Action queue full!"),
|
||||||
|
selected->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 100, 100));
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -614,7 +730,8 @@ void GameManager::commandBuildFarmSelected(Matrix2D<TerrainPoint> *mapPoints, in
|
|||||||
|
|
||||||
// Check if tile already has an improvement
|
// Check if tile already has an improvement
|
||||||
if (hasImprovementAt(tx, ty)) {
|
if (hasImprovementAt(tx, ty)) {
|
||||||
addFloatingText("Er staat hier al een boerderij!", selected->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 90, 90));
|
addFloatingText(Localization::instance().get("ui.unit.tile_occupied", "Tile already has an improvement!"),
|
||||||
|
selected->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 90, 90));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -624,14 +741,17 @@ void GameManager::commandBuildFarmSelected(Matrix2D<TerrainPoint> *mapPoints, in
|
|||||||
QStringList validTerrains = farmCfg ? farmCfg->validTerrains : QStringList{"grasland", "tundra"};
|
QStringList validTerrains = farmCfg ? farmCfg->validTerrains : QStringList{"grasland", "tundra"};
|
||||||
|
|
||||||
if (!validTerrains.contains(tType)) {
|
if (!validTerrains.contains(tType)) {
|
||||||
addFloatingText("Kan hier niet bouwen! Alleen op gras en toendra.", selected->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 80, 80));
|
QString validStr = validTerrains.join(", ");
|
||||||
|
addFloatingText(Localization::instance().get("ui.unit.cannot_build_here", "Cannot build here! Allowed on: %1").arg(validStr),
|
||||||
|
selected->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 80, 80));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start building process (duration comes from the builder unit's action speed)
|
// Start building process (duration comes from the builder unit's action speed)
|
||||||
float duration = selected->config.getActionDuration("boerderij_bouwen", 10.0f);
|
float duration = selected->config.getActionDuration("boerderij_bouwen", 10.0f);
|
||||||
selected->startBuilding("farm", duration);
|
selected->startBuilding("farm", duration);
|
||||||
addFloatingText("Boerderij in aanbouw...", selected->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 220, 80), 2.0f);
|
addFloatingText(Localization::instance().get("ui.unit.farm_started", "Constructing Farm..."),
|
||||||
|
selected->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 220, 80), 2.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameManager::completeUnitBuilding(Unit &unit, Matrix2D<TerrainPoint> *mapPoints, int numPoints05)
|
void GameManager::completeUnitBuilding(Unit &unit, Matrix2D<TerrainPoint> *mapPoints, int numPoints05)
|
||||||
@@ -662,7 +782,8 @@ void GameManager::completeUnitBuilding(Unit &unit, Matrix2D<TerrainPoint> *mapPo
|
|||||||
if (!unit.config.infiniteCharges && unit.buildCharges > 0) {
|
if (!unit.config.infiniteCharges && unit.buildCharges > 0) {
|
||||||
unit.buildCharges -= 1;
|
unit.buildCharges -= 1;
|
||||||
if (unit.buildCharges <= 0) {
|
if (unit.buildCharges <= 0) {
|
||||||
addFloatingText("Boerderij voltooid! (Builder verbruikt)", QVector3D(w.x(), w.y(), gz + 1.2f), QColor(80, 255, 120), 2.8f);
|
addFloatingText(Localization::instance().get("ui.unit.farm_completed_consumed", "Farm completed! (Builder consumed)"),
|
||||||
|
QVector3D(w.x(), w.y(), gz + 1.2f), QColor(80, 255, 120), 2.8f);
|
||||||
unit.takeDamage(99999.0f);
|
unit.takeDamage(99999.0f);
|
||||||
if (unit.id == m_selectedUnitId) {
|
if (unit.id == m_selectedUnitId) {
|
||||||
m_selectedUnitId = -1;
|
m_selectedUnitId = -1;
|
||||||
@@ -670,14 +791,159 @@ void GameManager::completeUnitBuilding(Unit &unit, Matrix2D<TerrainPoint> *mapPo
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
QString msg = QString("Boerderij voltooid! (%1 %2 over)")
|
QString chargeWord = unit.buildCharges == 1
|
||||||
|
? Localization::instance().get("ui.unit.charge_singular", "charge")
|
||||||
|
: Localization::instance().get("ui.unit.charge_plural", "charges");
|
||||||
|
QString msg = Localization::instance().get("ui.unit.farm_completed_charges", "Farm completed! (%1 %2 remaining)")
|
||||||
.arg(unit.buildCharges)
|
.arg(unit.buildCharges)
|
||||||
.arg(unit.buildCharges == 1 ? "lading" : "ladingen");
|
.arg(chargeWord);
|
||||||
addFloatingText(msg, QVector3D(w.x(), w.y(), gz + 1.2f), QColor(80, 255, 120), 2.8f);
|
addFloatingText(msg, QVector3D(w.x(), w.y(), gz + 1.2f), QColor(80, 255, 120), 2.8f);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addFloatingText("Boerderij voltooid!", QVector3D(w.x(), w.y(), gz + 1.2f), QColor(80, 255, 120), 2.8f);
|
addFloatingText(Localization::instance().get("ui.unit.farm_completed", "Farm completed!"),
|
||||||
|
QVector3D(w.x(), w.y(), gz + 1.2f), QColor(80, 255, 120), 2.8f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameManager::processUnitActionQueue(Unit &unit, Matrix2D<TerrainPoint> *mapPoints, int numPoints05)
|
||||||
|
{
|
||||||
|
if (unit.isDead() || unit.state != UnitState::Idle || !unit.hasQueuedAction()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UnitAction action = unit.popNextQueuedAction();
|
||||||
|
|
||||||
|
switch (action.type) {
|
||||||
|
case UnitActionType::Move: {
|
||||||
|
int tx = action.targetX;
|
||||||
|
int ty = action.targetY;
|
||||||
|
if (tx == unit.tileX && ty == unit.tileY) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!TerrainMovementManager::instance().isHexPassable(
|
||||||
|
tx, ty, unit.config.movementDomain, mapPoints, numPoints05, &unit.config)) {
|
||||||
|
addFloatingText(Localization::instance().get("ui.unit.impassable_terrain", "Impassable terrain!"),
|
||||||
|
unit.worldPos + QVector3D(0, 0, 0.8f), QColor(255, 100, 100), 2.5f);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<int, int>> path = Pathfinder::findPath(
|
||||||
|
unit.tileX, unit.tileY, tx, ty, mapPoints, numPoints05,
|
||||||
|
unit.config.movementDomain, &unit.config
|
||||||
|
);
|
||||||
|
|
||||||
|
if (path.size() > 1) {
|
||||||
|
unit.setPath(path);
|
||||||
|
addFloatingText(Localization::instance().get("ui.unit.moving", "Moving"),
|
||||||
|
unit.worldPos + QVector3D(0, 0, 0.8f), QColor(255, 230, 80));
|
||||||
|
} else {
|
||||||
|
addFloatingText(Localization::instance().get("ui.unit.no_path", "No reachable path!"),
|
||||||
|
unit.worldPos + QVector3D(0, 0, 0.8f), QColor(255, 100, 100), 2.5f);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case UnitActionType::BuildImprovement: {
|
||||||
|
QString impId = action.extraData.isEmpty() ? "farm" : action.extraData;
|
||||||
|
int tx = unit.tileX;
|
||||||
|
int ty = unit.tileY;
|
||||||
|
|
||||||
|
if (!unit.config.canBuildFarm()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!unit.config.infiniteCharges && unit.buildCharges <= 0) {
|
||||||
|
addFloatingText(Localization::instance().get("ui.unit.no_build_charges", "No build charges remaining!"),
|
||||||
|
unit.worldPos + QVector3D(0, 0, 0.8f), QColor(255, 90, 90));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasImprovementAt(tx, ty)) {
|
||||||
|
addFloatingText(Localization::instance().get("ui.unit.tile_occupied", "Tile already has an improvement!"),
|
||||||
|
unit.worldPos + QVector3D(0, 0, 0.8f), QColor(255, 90, 90));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString tType = TerrainYieldManager::instance().getTerrainType(tx, ty, mapPoints, numPoints05);
|
||||||
|
const ImprovementConfig *farmCfg = TerrainYieldManager::instance().getImprovementConfig(impId);
|
||||||
|
QStringList validTerrains = farmCfg ? farmCfg->validTerrains : QStringList{"grasland", "tundra"};
|
||||||
|
|
||||||
|
if (!validTerrains.contains(tType)) {
|
||||||
|
QString validStr = validTerrains.join(", ");
|
||||||
|
addFloatingText(Localization::instance().get("ui.unit.cannot_build_here", "Cannot build here! Allowed on: %1").arg(validStr),
|
||||||
|
unit.worldPos + QVector3D(0, 0, 0.8f), QColor(255, 80, 80));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float duration = unit.config.getActionDuration("boerderij_bouwen", 10.0f);
|
||||||
|
unit.startBuilding(impId, duration);
|
||||||
|
addFloatingText(Localization::instance().get("ui.unit.farm_started", "Constructing Farm..."),
|
||||||
|
unit.worldPos + QVector3D(0, 0, 0.8f), QColor(255, 220, 80), 2.0f);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case UnitActionType::BuildCity: {
|
||||||
|
if (!unit.config.canBuildCity()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int tx = unit.tileX;
|
||||||
|
int ty = unit.tileY;
|
||||||
|
|
||||||
|
// Check if tile already has a city
|
||||||
|
for (const auto &c : m_cities) {
|
||||||
|
if (c.tileX == tx && c.tileY == ty) {
|
||||||
|
addFloatingText(Localization::instance().get("ui.unit.city_already_here", "A city already exists here!"),
|
||||||
|
unit.worldPos + QVector3D(0, 0, 0.8f), QColor(255, 80, 80));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const CityConfig &cCfg = TerrainYieldManager::instance().getCityConfig();
|
||||||
|
int cId = getNextCityId();
|
||||||
|
QString cityName = QString("City %1").arg(cId);
|
||||||
|
QVector2D w = Pathfinder::hexToWorld(tx, ty);
|
||||||
|
float gz = Unit::getTerrainHeight(w.x(), w.y(), mapPoints, numPoints05);
|
||||||
|
|
||||||
|
City newCity(cId, unit.playerId, cityName, tx, ty,
|
||||||
|
QVector3D(w.x(), w.y(), gz),
|
||||||
|
cCfg.initialInhabitants, cCfg.inhabitantsPerPop, cCfg.initialBorderRadius);
|
||||||
|
|
||||||
|
QString tType = TerrainYieldManager::instance().getTerrainType(tx, ty, mapPoints, numPoints05);
|
||||||
|
const TerrainYieldConfig *yCfg = TerrainYieldManager::instance().getYieldConfig(tType);
|
||||||
|
QString defRes = yCfg ? yCfg->primaryResource : "voedsel";
|
||||||
|
newCity.assignWorker(tx, ty, defRes);
|
||||||
|
|
||||||
|
m_cities.push_back(newCity);
|
||||||
|
addFloatingText(Localization::instance().get("ui.unit.city_founded", "%1 founded!").arg(cityName),
|
||||||
|
QVector3D(w.x(), w.y(), gz + 1.2f), QColor(80, 255, 120), 2.5f);
|
||||||
|
|
||||||
|
unit.takeDamage(99999.0f);
|
||||||
|
if (unit.id == m_selectedUnitId) {
|
||||||
|
m_selectedUnitId = -1;
|
||||||
|
m_actionMode = ActionMode::None;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case UnitActionType::Attack: {
|
||||||
|
Unit *target = nullptr;
|
||||||
|
for (auto &other : m_units) {
|
||||||
|
if (other->id == action.targetUnitId && !other->isDead()) {
|
||||||
|
target = other.get();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (target && unit.canAttack(*target)) {
|
||||||
|
unit.performAttack(*target);
|
||||||
|
addFloatingText(QString("-%1 HP").arg(unit.config.attackDamage),
|
||||||
|
target->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 50, 50));
|
||||||
|
if (target->isDead()) {
|
||||||
|
addFloatingText(Localization::instance().get("ui.unit.defeated", "Defeated!"),
|
||||||
|
target->worldPos + QVector3D(0, 0, 1.2f), QColor(255, 200, 50), 2.2f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ public:
|
|||||||
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 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);
|
||||||
|
|
||||||
const std::map<std::pair<int, int>, TileImprovement>& getTileImprovements() const { return m_tileImprovements; }
|
const std::map<std::pair<int, int>, TileImprovement>& getTileImprovements() const { return m_tileImprovements; }
|
||||||
const TileImprovement* getImprovementAt(int tx, int ty) const;
|
const TileImprovement* getImprovementAt(int tx, int ty) const;
|
||||||
@@ -103,6 +104,10 @@ public:
|
|||||||
float getSimSpeed() const { return m_simSpeedMultiplier; }
|
float getSimSpeed() const { return m_simSpeedMultiplier; }
|
||||||
void setSimSpeed(float s) { m_simSpeedMultiplier = std::max(0.2f, std::min(10.0f, s)); }
|
void setSimSpeed(float s) { m_simSpeedMultiplier = std::max(0.2f, std::min(10.0f, s)); }
|
||||||
|
|
||||||
|
int getMaxActionQueueSize() const { return m_maxActionQueueSize; }
|
||||||
|
bool isActionQueueingAllowed() const { return m_allowActionQueueing; }
|
||||||
|
bool isAutoQueueMoveWhenBusy() const { return m_autoQueueMoveWhenBusy; }
|
||||||
|
|
||||||
void addFloatingText(const QString &text, const QVector3D &worldPos, const QColor &color, float duration = 1.8f);
|
void addFloatingText(const QString &text, const QVector3D &worldPos, const QColor &color, float duration = 1.8f);
|
||||||
|
|
||||||
int getNextUnitId() { return ++m_nextUnitId; }
|
int getNextUnitId() { return ++m_nextUnitId; }
|
||||||
@@ -131,6 +136,10 @@ private:
|
|||||||
float m_tickTimer;
|
float m_tickTimer;
|
||||||
float m_simSpeedMultiplier; // 1.0x, 2.0x, etc.
|
float m_simSpeedMultiplier; // 1.0x, 2.0x, etc.
|
||||||
|
|
||||||
|
int m_maxActionQueueSize;
|
||||||
|
bool m_autoQueueMoveWhenBusy;
|
||||||
|
bool m_allowActionQueueing;
|
||||||
|
|
||||||
int m_nextUnitId;
|
int m_nextUnitId;
|
||||||
int m_nextCityId;
|
int m_nextCityId;
|
||||||
bool m_initialized;
|
bool m_initialized;
|
||||||
|
|||||||
+56
-27
@@ -715,19 +715,25 @@ void QSceneRenderer::drawUnitsOverlay(QPainter &painter, const QMatrix4x4 &vMatr
|
|||||||
} else if (act == "aanvallen") {
|
} else if (act == "aanvallen") {
|
||||||
isActive = (GameManager::instance().currentActionMode() == ActionMode::Attack);
|
isActive = (GameManager::instance().currentActionMode() == ActionMode::Attack);
|
||||||
iconFile = "images/actions/attack.png";
|
iconFile = "images/actions/attack.png";
|
||||||
tip = QString("Aanvallen (A) [Bereik: %1]").arg(unit->config.attackRange);
|
tip = Localization::instance().get("ui.unit.tip_attack", "Attack (A) [Range: %1]").arg(unit->config.attackRange);
|
||||||
} else if (act == "steden_bouwen" || act == "build_city") {
|
} else if (act == "steden_bouwen" || act == "build_city") {
|
||||||
isActive = (GameManager::instance().currentActionMode() == ActionMode::BuildCity);
|
bool isQueued = unit->hasQueuedActionType(UnitActionType::BuildCity);
|
||||||
|
isActive = (GameManager::instance().currentActionMode() == ActionMode::BuildCity) || isQueued;
|
||||||
iconFile = "images/actions/build_city.png";
|
iconFile = "images/actions/build_city.png";
|
||||||
tip = "Steden bouwen (B)";
|
tip = isQueued
|
||||||
|
? Localization::instance().get("ui.unit.city_queued", "Found City queued on arrival")
|
||||||
|
: Localization::instance().get("ui.unit.tip_build_city", "Found City (B)");
|
||||||
} else if (act == "boerderij_bouwen" || act == "build_farm") {
|
} else if (act == "boerderij_bouwen" || act == "build_farm") {
|
||||||
isActive = unit->isBuilding();
|
bool isQueued = unit->hasQueuedActionType(UnitActionType::BuildImprovement);
|
||||||
|
isActive = unit->isBuilding() || isQueued;
|
||||||
iconFile = "images/actions/farm.png";
|
iconFile = "images/actions/farm.png";
|
||||||
if (unit->isBuilding()) {
|
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);
|
||||||
tip = QString("Aan het bouwen... %1%").arg(pct);
|
tip = Localization::instance().get("ui.unit.tip_farm_building", "Busy building... %1%").arg(pct);
|
||||||
|
} else if (isQueued) {
|
||||||
|
tip = Localization::instance().get("ui.unit.tip_farm_queued", "Build Farm (Queued on arrival - click to cancel)");
|
||||||
} else {
|
} else {
|
||||||
tip = "Boerderij bouwen (F)";
|
tip = Localization::instance().get("ui.unit.tip_build_farm", "Build Farm (F)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -918,28 +924,39 @@ void QSceneRenderer::drawUnitCard(QPainter &painter, Unit *unit)
|
|||||||
painter.setFont(QFont("sans-serif", 8));
|
painter.setFont(QFont("sans-serif", 8));
|
||||||
painter.setPen(QColor(200, 220, 240));
|
painter.setPen(QColor(200, 220, 240));
|
||||||
|
|
||||||
QString speedInfo = QString("Snelheid: %1 hex/tick").arg(unit->config.movementSpeed, 0, 'f', 1);
|
QString speedInfo = Localization::instance().get("ui.unit.speed_format", "Speed: %1 hex/tick").arg(unit->config.movementSpeed, 0, 'f', 1);
|
||||||
painter.drawText(QRect(cardRect.left() + 12, cardRect.top() + 76, 140, 14), Qt::AlignLeft | Qt::AlignVCenter, speedInfo);
|
painter.drawText(QRect(cardRect.left() + 12, cardRect.top() + 76, 140, 14), Qt::AlignLeft | Qt::AlignVCenter, speedInfo);
|
||||||
|
|
||||||
QString costInfo = QString("Kosten: %1 pnt").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->config.isMilitary()) {
|
||||||
QString combatInfo = QString("Aanval: %1 | Bereik: %2 hex").arg(unit->config.attackDamage).arg(unit->config.attackRange);
|
QString combatInfo = Localization::instance().get("ui.unit.combat_format", "Attack: %1 | Range: %2 hex")
|
||||||
|
.arg(unit->config.attackDamage).arg(unit->config.attackRange);
|
||||||
painter.drawText(QRect(cardRect.left() + 12, cardRect.top() + 94, 280, 14), Qt::AlignLeft | Qt::AlignVCenter, combatInfo);
|
painter.drawText(QRect(cardRect.left() + 12, cardRect.top() + 94, 280, 14), Qt::AlignLeft | Qt::AlignVCenter, combatInfo);
|
||||||
} 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 = QString("Aan het bouwen... %1%").arg(pct);
|
QString buildInfo = Localization::instance().get("ui.unit.building_pct", "Building... %1%").arg(pct);
|
||||||
if (!unit->config.infiniteCharges && unit->buildCharges > 0) {
|
if (!unit->config.infiniteCharges && unit->buildCharges > 0) {
|
||||||
buildInfo += QString(" | Ladingen: %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, 280, 14), Qt::AlignLeft | Qt::AlignVCenter, buildInfo);
|
||||||
|
} else if (unit->hasQueuedAction()) {
|
||||||
|
QString qSummary = unit->getQueuedActionSummary();
|
||||||
|
QString queuedInfo = Localization::instance().get("ui.unit.status_queued", "Queued: %1").arg(qSummary);
|
||||||
|
if (unit->config.canBuildFarm() && !unit->config.infiniteCharges && unit->buildCharges > 0) {
|
||||||
|
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.canBuildFarm() && !unit->config.infiniteCharges && unit->buildCharges > 0) {
|
} else if (unit->config.canBuildFarm() && !unit->config.infiniteCharges && unit->buildCharges > 0) {
|
||||||
QString builderInfo = QString("Civiele eenheid | Ladingen: %1").arg(unit->buildCharges);
|
QString builderInfo = Localization::instance().get("ui.unit.civilian_unit", "Civilian unit") + QString(" | ") +
|
||||||
|
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, 280, 14), Qt::AlignLeft | Qt::AlignVCenter, builderInfo);
|
||||||
} else {
|
} else {
|
||||||
painter.drawText(QRect(cardRect.left() + 12, cardRect.top() + 94, 280, 14), Qt::AlignLeft | Qt::AlignVCenter, "Civiele eenheid");
|
painter.drawText(QRect(cardRect.left() + 12, cardRect.top() + 94, 280, 14), Qt::AlignLeft | Qt::AlignVCenter,
|
||||||
|
Localization::instance().get("ui.unit.civilian_unit", "Civilian unit"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Action Bubbles Row at bottom of card
|
// Action Bubbles Row at bottom of card
|
||||||
@@ -958,34 +975,41 @@ void QSceneRenderer::drawUnitCard(QPainter &painter, Unit *unit)
|
|||||||
|
|
||||||
bool isActive = false;
|
bool isActive = false;
|
||||||
QString iconFile = "images/actions/move.png";
|
QString iconFile = "images/actions/move.png";
|
||||||
QString label = "Verplaats";
|
QString label = Localization::instance().get("ui.unit.btn_move", "Move [M]");
|
||||||
QString tip = "Verplaatsen (M)";
|
QString tip = Localization::instance().get("ui.unit.tip_move", "Move (M)");
|
||||||
|
|
||||||
if (act == "verplaatsen") {
|
if (act == "verplaatsen") {
|
||||||
isActive = (GameManager::instance().currentActionMode() == ActionMode::Move);
|
isActive = (GameManager::instance().currentActionMode() == ActionMode::Move);
|
||||||
iconFile = "images/actions/move.png";
|
iconFile = "images/actions/move.png";
|
||||||
label = "Beweeg [M]";
|
label = Localization::instance().get("ui.unit.btn_move", "Move [M]");
|
||||||
tip = "Verplaatsen (M)";
|
tip = Localization::instance().get("ui.unit.tip_move", "Move (M)");
|
||||||
} else if (act == "aanvallen") {
|
} else if (act == "aanvallen") {
|
||||||
isActive = (GameManager::instance().currentActionMode() == ActionMode::Attack);
|
isActive = (GameManager::instance().currentActionMode() == ActionMode::Attack);
|
||||||
iconFile = "images/actions/attack.png";
|
iconFile = "images/actions/attack.png";
|
||||||
label = "Aanval [A]";
|
label = Localization::instance().get("ui.unit.btn_attack", "Attack [A]");
|
||||||
tip = QString("Aanvallen (A) [Bereik: %1]").arg(unit->config.attackRange);
|
tip = Localization::instance().get("ui.unit.tip_attack", "Attack (A) [Range: %1]").arg(unit->config.attackRange);
|
||||||
} else if (act == "steden_bouwen" || act == "build_city") {
|
} else if (act == "steden_bouwen" || act == "build_city") {
|
||||||
isActive = (GameManager::instance().currentActionMode() == ActionMode::BuildCity);
|
bool isQueued = unit->hasQueuedActionType(UnitActionType::BuildCity);
|
||||||
|
isActive = (GameManager::instance().currentActionMode() == ActionMode::BuildCity) || isQueued;
|
||||||
iconFile = "images/actions/build_city.png";
|
iconFile = "images/actions/build_city.png";
|
||||||
label = "Sticht [B]";
|
label = isQueued ? "Queued" : Localization::instance().get("ui.unit.btn_build_city", "Settle [B]");
|
||||||
tip = "Steden bouwen (B)";
|
tip = isQueued
|
||||||
|
? Localization::instance().get("ui.unit.city_queued", "Found City queued on arrival")
|
||||||
|
: Localization::instance().get("ui.unit.tip_build_city", "Found City (B)");
|
||||||
} else if (act == "boerderij_bouwen" || act == "build_farm") {
|
} else if (act == "boerderij_bouwen" || act == "build_farm") {
|
||||||
isActive = unit->isBuilding();
|
bool isQueued = unit->hasQueuedActionType(UnitActionType::BuildImprovement);
|
||||||
|
isActive = unit->isBuilding() || isQueued;
|
||||||
iconFile = "images/actions/farm.png";
|
iconFile = "images/actions/farm.png";
|
||||||
if (unit->isBuilding()) {
|
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);
|
||||||
label = QString("Bouwt %1%").arg(pct);
|
label = Localization::instance().get("ui.unit.btn_farm_building", "Building %1%").arg(pct);
|
||||||
tip = QString("Bezig met bouwen... %1%").arg(pct);
|
tip = Localization::instance().get("ui.unit.tip_farm_building", "Busy building... %1%").arg(pct);
|
||||||
|
} else if (isQueued) {
|
||||||
|
label = Localization::instance().get("ui.unit.btn_farm_queued", "Farm (Queued)");
|
||||||
|
tip = Localization::instance().get("ui.unit.tip_farm_queued", "Build Farm (Queued on arrival - click to cancel)");
|
||||||
} else {
|
} else {
|
||||||
label = "Boerderij [F]";
|
label = Localization::instance().get("ui.unit.btn_build_farm", "Farm [F]");
|
||||||
tip = "Boerderij bouwen (F)";
|
tip = Localization::instance().get("ui.unit.tip_build_farm", "Build Farm (F)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2046,6 +2070,11 @@ void QSceneRenderer::keyPressEvent(QKeyEvent *e)
|
|||||||
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->hasQueuedAction()) {
|
||||||
|
sel->clearActionQueue();
|
||||||
|
GameManager::instance().addFloatingText(
|
||||||
|
Localization::instance().get("ui.unit.queue_cleared", "Queued actions cancelled"),
|
||||||
|
sel->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 180, 80));
|
||||||
} else {
|
} else {
|
||||||
GameManager::instance().deselectUnit();
|
GameManager::instance().deselectUnit();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,12 +35,72 @@ float Unit::getTerrainHeight(float worldX, float worldY, Matrix2D<TerrainPoint>
|
|||||||
return (h / 255.0f) * 3.5f + 1.0f;
|
return (h / 255.0f) * 3.5f + 1.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Unit::setPath(const std::vector<std::pair<int, int>> &newPath)
|
bool Unit::queueAction(const UnitAction &action)
|
||||||
{
|
{
|
||||||
if (state == UnitState::Building) {
|
if ((int)actionQueue.size() >= maxQueueSize) {
|
||||||
cancelBuilding();
|
return false;
|
||||||
|
}
|
||||||
|
actionQueue.push_back(action);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Unit::clearActionQueue()
|
||||||
|
{
|
||||||
|
actionQueue.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Unit::hasQueuedActionType(UnitActionType type) const
|
||||||
|
{
|
||||||
|
for (const auto &act : actionQueue) {
|
||||||
|
if (act.type == type) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Unit::removeQueuedActionsOfType(UnitActionType type)
|
||||||
|
{
|
||||||
|
for (auto it = actionQueue.begin(); it != actionQueue.end(); ) {
|
||||||
|
if (it->type == type) {
|
||||||
|
it = actionQueue.erase(it);
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UnitAction Unit::popNextQueuedAction()
|
||||||
|
{
|
||||||
|
if (actionQueue.empty()) return UnitAction();
|
||||||
|
UnitAction act = actionQueue.front();
|
||||||
|
actionQueue.pop_front();
|
||||||
|
return act;
|
||||||
|
}
|
||||||
|
|
||||||
|
const UnitAction* Unit::peekNextQueuedAction() const
|
||||||
|
{
|
||||||
|
if (actionQueue.empty()) return nullptr;
|
||||||
|
return &actionQueue.front();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Unit::getQueuedActionSummary() const
|
||||||
|
{
|
||||||
|
if (actionQueue.empty()) return "";
|
||||||
|
switch (actionQueue.front().type) {
|
||||||
|
case UnitActionType::Move:
|
||||||
|
return "Move";
|
||||||
|
case UnitActionType::BuildImprovement:
|
||||||
|
return "Build Farm";
|
||||||
|
case UnitActionType::BuildCity:
|
||||||
|
return "Found City";
|
||||||
|
case UnitActionType::Attack:
|
||||||
|
return "Attack";
|
||||||
|
default:
|
||||||
|
return "Action";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Unit::setPath(const std::vector<std::pair<int, int>> &newPath)
|
||||||
|
{
|
||||||
path = newPath;
|
path = newPath;
|
||||||
pathIndex = 0;
|
pathIndex = 0;
|
||||||
moveProgress = 0.0f;
|
moveProgress = 0.0f;
|
||||||
@@ -60,6 +120,7 @@ void Unit::stopMovement()
|
|||||||
pathIndex = 0;
|
pathIndex = 0;
|
||||||
moveProgress = 0.0f;
|
moveProgress = 0.0f;
|
||||||
state = UnitState::Idle;
|
state = UnitState::Idle;
|
||||||
|
clearActionQueue();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Unit::startBuilding(const QString &impId, float duration)
|
void Unit::startBuilding(const QString &impId, float duration)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QVector3D>
|
#include <QVector3D>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <deque>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include "unitconfig.h"
|
#include "unitconfig.h"
|
||||||
#include "terrain.h"
|
#include "terrain.h"
|
||||||
@@ -16,6 +17,31 @@ enum class UnitState {
|
|||||||
Building
|
Building
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class UnitActionType {
|
||||||
|
None,
|
||||||
|
Move,
|
||||||
|
BuildImprovement,
|
||||||
|
BuildCity,
|
||||||
|
Attack
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UnitAction {
|
||||||
|
UnitActionType type = UnitActionType::None;
|
||||||
|
int targetX = -1;
|
||||||
|
int targetY = -1;
|
||||||
|
int targetUnitId = -1;
|
||||||
|
QString extraData = ""; // e.g. improvementId ("farm")
|
||||||
|
|
||||||
|
UnitAction() = default;
|
||||||
|
UnitAction(UnitActionType t) : type(t) {}
|
||||||
|
UnitAction(UnitActionType t, int x, int y, const QString &data = "")
|
||||||
|
: type(t), targetX(x), targetY(y), extraData(data) {}
|
||||||
|
UnitAction(UnitActionType t, int targetId)
|
||||||
|
: type(t), targetUnitId(targetId) {}
|
||||||
|
|
||||||
|
bool isValid() const { return type != UnitActionType::None; }
|
||||||
|
};
|
||||||
|
|
||||||
class Unit {
|
class Unit {
|
||||||
public:
|
public:
|
||||||
Unit(int uId, int pId, const UnitConfig &cfg, int startX, int startY, float groundZ);
|
Unit(int uId, int pId, const UnitConfig &cfg, int startX, int startY, float groundZ);
|
||||||
@@ -38,6 +64,19 @@ public:
|
|||||||
int targetTileX;
|
int targetTileX;
|
||||||
int targetTileY;
|
int targetTileY;
|
||||||
|
|
||||||
|
// Action Queue
|
||||||
|
std::deque<UnitAction> actionQueue;
|
||||||
|
int maxQueueSize = 5;
|
||||||
|
|
||||||
|
bool queueAction(const UnitAction &action);
|
||||||
|
void clearActionQueue();
|
||||||
|
bool hasQueuedAction() const { return !actionQueue.empty(); }
|
||||||
|
bool hasQueuedActionType(UnitActionType type) const;
|
||||||
|
void removeQueuedActionsOfType(UnitActionType type);
|
||||||
|
UnitAction popNextQueuedAction();
|
||||||
|
const UnitAction* peekNextQueuedAction() const;
|
||||||
|
QString getQueuedActionSummary() const;
|
||||||
|
|
||||||
// Attack cooldown / flash
|
// Attack cooldown / flash
|
||||||
float attackFlashTimer;
|
float attackFlashTimer;
|
||||||
|
|
||||||
@@ -54,6 +93,7 @@ public:
|
|||||||
void startBuilding(const QString &impId, float duration);
|
void startBuilding(const QString &impId, float duration);
|
||||||
void cancelBuilding();
|
void cancelBuilding();
|
||||||
bool isBuilding() const { return state == UnitState::Building; }
|
bool isBuilding() const { return state == UnitState::Building; }
|
||||||
|
bool isBusy() const { return state == UnitState::Building; }
|
||||||
|
|
||||||
bool canAttack(const Unit &other) const;
|
bool canAttack(const Unit &other) const;
|
||||||
void performAttack(Unit &target);
|
void performAttack(Unit &target);
|
||||||
|
|||||||
Reference in New Issue
Block a user