Complete city production queue implementation
This commit is contained in:
@@ -55,6 +55,7 @@ public:
|
||||
int accumulatedLabor = 0; // Local resource (Arbeid)
|
||||
|
||||
CityProject currentProject;
|
||||
std::vector<CityProject> projectQueue;
|
||||
std::vector<QString> completedBuildings; // IDs of completed buildings in the city
|
||||
|
||||
std::vector<std::pair<int, int>> territory; // Owned hex coordinates
|
||||
@@ -77,6 +78,34 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isBuildingQueuedOrBuilt(const QString &buildingId) const {
|
||||
if (hasBuilding(buildingId)) return true;
|
||||
if (currentProject.isValid() && currentProject.type == CityProjectType::Building && currentProject.id == buildingId) return true;
|
||||
for (const auto &q : projectQueue) {
|
||||
if (q.type == CityProjectType::Building && q.id == buildingId) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int getQueuedBuildingPosition(const QString &buildingId) const {
|
||||
for (size_t i = 0; i < projectQueue.size(); ++i) {
|
||||
if (projectQueue[i].type == CityProjectType::Building && projectQueue[i].id == buildingId) {
|
||||
return (int)i + 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int getQueuedUnitCount(const QString &unitId) const {
|
||||
int count = 0;
|
||||
for (const auto &q : projectQueue) {
|
||||
if (q.type == CityProjectType::Unit && q.id == unitId) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
bool hasActiveProject() const {
|
||||
return currentProject.isValid();
|
||||
}
|
||||
@@ -90,9 +119,54 @@ public:
|
||||
currentProject.district = district.isEmpty() ? "center" : district;
|
||||
}
|
||||
|
||||
void cancelProject() {
|
||||
bool queueProject(CityProjectType type, const QString &projId, const QString &projName, int cost, const QString &district = "center", int maxQueueSize = 6) {
|
||||
if (!hasActiveProject()) {
|
||||
startProject(type, projId, projName, cost, district);
|
||||
return true;
|
||||
}
|
||||
if ((int)projectQueue.size() >= maxQueueSize) {
|
||||
return false;
|
||||
}
|
||||
CityProject q;
|
||||
q.type = type;
|
||||
q.id = projId;
|
||||
q.name = projName;
|
||||
q.laborCost = std::max(1, cost);
|
||||
q.currentLabor = 0;
|
||||
q.district = district.isEmpty() ? "center" : district;
|
||||
projectQueue.push_back(q);
|
||||
return true;
|
||||
}
|
||||
|
||||
void cancelActiveProject() {
|
||||
if (!projectQueue.empty()) {
|
||||
currentProject = projectQueue.front();
|
||||
projectQueue.erase(projectQueue.begin());
|
||||
} else {
|
||||
currentProject = CityProject();
|
||||
}
|
||||
}
|
||||
|
||||
void cancelProject() {
|
||||
cancelActiveProject();
|
||||
}
|
||||
|
||||
bool removeQueuedProject(int index) {
|
||||
if (index >= 0 && index < (int)projectQueue.size()) {
|
||||
projectQueue.erase(projectQueue.begin() + index);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void advanceQueue() {
|
||||
if (!projectQueue.empty()) {
|
||||
currentProject = projectQueue.front();
|
||||
projectQueue.erase(projectQueue.begin());
|
||||
} else {
|
||||
currentProject = CityProject();
|
||||
}
|
||||
}
|
||||
|
||||
bool addTerritory(int tx, int ty) {
|
||||
if (ownsTile(tx, ty)) return false;
|
||||
|
||||
@@ -14,6 +14,7 @@ tile_culture_cost: 10000
|
||||
food_to_inhabitants_ratio: 1.0
|
||||
default_unit_labor_cost: 1000
|
||||
unit_labor_cost_multiplier: 20
|
||||
max_production_queue_size: 6
|
||||
|
||||
# Base yield breakdown per resource (default 500 per tick)
|
||||
base_yields:
|
||||
|
||||
@@ -12,6 +12,7 @@ types:
|
||||
color: "#c49a62"
|
||||
border_color: "#6b4b23"
|
||||
width: 3.5
|
||||
world_width: 0.11
|
||||
cost: 5
|
||||
stone:
|
||||
id: "stone"
|
||||
@@ -20,6 +21,7 @@ types:
|
||||
color: "#9aa1a8"
|
||||
border_color: "#464b50"
|
||||
width: 4.0
|
||||
world_width: 0.13
|
||||
cost: 15
|
||||
rail:
|
||||
id: "rail"
|
||||
@@ -28,4 +30,5 @@ types:
|
||||
color: "#3a3c40"
|
||||
border_color: "#181a1c"
|
||||
width: 4.5
|
||||
world_width: 0.15
|
||||
cost: 30
|
||||
|
||||
+20
-5
@@ -102,7 +102,7 @@ ui:
|
||||
tip_build_farm: "Build Farm (F)"
|
||||
tip_farm_building: "Busy building... %1%"
|
||||
tip_farm_queued: "Build Farm (Queued on arrival - click to cancel)"
|
||||
tip_build_road: "Build Road (R)"
|
||||
tip_build_road: "Build Road (R) - Click target tile to build route"
|
||||
tip_road_building: "Busy building road... %1%"
|
||||
tip_road_queued: "Build Road (Queued on arrival - click to cancel)"
|
||||
road_started: "Constructing %1... (%2 ticks)"
|
||||
@@ -110,6 +110,11 @@ ui:
|
||||
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_completed: "Road route completed!"
|
||||
road_route_cancelled: "Road route cancelled"
|
||||
status_road_route: "Road Route: %1/%2"
|
||||
btn_road_route: "Route (%1/%2)"
|
||||
|
||||
improvement:
|
||||
farm: "Farm"
|
||||
@@ -126,25 +131,35 @@ ui:
|
||||
national_info: "National: Culture: %L1 | Science: %L2 | Gold: %L3 | Faith: %L4"
|
||||
help_text: "Click a slot to assign/free Pop | Hold a slot to choose resource | Buy tile with Culture."
|
||||
current_production: "CURRENT PRODUCTION"
|
||||
production_panel_title: "CITY PRODUCTION"
|
||||
no_project: "No active project"
|
||||
select_prompt: "Select a unit or building to construct"
|
||||
select_prompt: "Select an item below to construct"
|
||||
ticks_remaining: "%1 ticks remaining (%2 / %3 Labor)"
|
||||
cancel_btn: "Cancel"
|
||||
queue_header: "QUEUE (%1 / %2)"
|
||||
queue_empty: "Queue is empty"
|
||||
districts_header: "DISTRICT: CITY CENTER"
|
||||
no_buildings: "No extensions built yet"
|
||||
choose_project: "CHOOSE CITY PROJECT"
|
||||
tab_buildings: "Buildings & Extensions"
|
||||
choose_project: "AVAILABLE PROJECTS"
|
||||
tab_buildings: "Buildings"
|
||||
tab_units: "Units"
|
||||
btn_build: "Build"
|
||||
btn_train: "Train"
|
||||
btn_queue: "+ Queue"
|
||||
status_built: "BUILT"
|
||||
status_in_progress: "ACTIVE"
|
||||
status_queued: "QUEUED #%1"
|
||||
status_full: "FULL"
|
||||
queue_full_float: "Production queue is full! (Max %1)"
|
||||
labor_cost_label: "%1 Labor (%2 ticks)"
|
||||
unit_trained: "%1: %2 trained!"
|
||||
building_completed: "%1: %2 completed in %3!"
|
||||
project_started: "%1: Started %2"
|
||||
project_cancelled: "%1: Project cancelled"
|
||||
project_queued: "%1: Added %2 to queue (#%3)"
|
||||
project_cancelled: "%1: Cancelled %2"
|
||||
queued_item_removed: "%1: Removed %2 from queue"
|
||||
already_built: "%1 already built in this city!"
|
||||
already_queued: "%1 is already in the production queue!"
|
||||
growth_float: "+%1 Inhabitants | +%2 Culture"
|
||||
not_enough_culture: "Not enough Culture! (%1 needed)"
|
||||
worker_freed: "Pop worker freed"
|
||||
|
||||
+78
-5
@@ -455,31 +455,74 @@ bool GameManager::buyTileForCity(int cityId, int tx, int ty)
|
||||
return true;
|
||||
}
|
||||
|
||||
void GameManager::setCityProject(int cityId, CityProjectType type, const QString &id)
|
||||
void GameManager::queueCityProject(int cityId, CityProjectType type, const QString &id)
|
||||
{
|
||||
City *city = getCityById(cityId);
|
||||
if (!city) return;
|
||||
|
||||
const CityConfig &cCfg = TerrainYieldManager::instance().getCityConfig();
|
||||
int maxQueue = cCfg.maxProductionQueueSize;
|
||||
|
||||
if (type == CityProjectType::Unit) {
|
||||
const UnitConfig *uCfg = m_unitConfigManager.getConfig(id);
|
||||
if (!uCfg) return;
|
||||
|
||||
if (!city->hasActiveProject()) {
|
||||
city->startProject(CityProjectType::Unit, id, uCfg->name, uCfg->laborCost);
|
||||
QString msg = Localization::instance().get("ui.city.project_started", "%1: Started %2")
|
||||
.arg(city->name).arg(uCfg->name);
|
||||
addFloatingText(msg, city->worldPos + QVector3D(0.0f, 0.0f, 1.2f), QColor(255, 230, 80), 2.5f);
|
||||
} else {
|
||||
if ((int)city->projectQueue.size() >= maxQueue) {
|
||||
QString msg = Localization::instance().get("ui.city.queue_full_float", "Production queue is full! (Max %1)").arg(maxQueue);
|
||||
addFloatingText(msg, city->worldPos + QVector3D(0.0f, 0.0f, 1.2f), QColor(255, 100, 100), 2.0f);
|
||||
return;
|
||||
}
|
||||
city->queueProject(CityProjectType::Unit, id, uCfg->name, uCfg->laborCost, "center", maxQueue);
|
||||
int pos = (int)city->projectQueue.size();
|
||||
QString msg = Localization::instance().get("ui.city.project_queued", "%1: Added %2 to queue (#%3)")
|
||||
.arg(city->name).arg(uCfg->name).arg(pos);
|
||||
addFloatingText(msg, city->worldPos + QVector3D(0.0f, 0.0f, 1.2f), QColor(100, 200, 255), 2.2f);
|
||||
}
|
||||
} else if (type == CityProjectType::Building) {
|
||||
const BuildingConfig *bCfg = m_buildingConfigManager.getBuilding(id);
|
||||
if (!bCfg) return;
|
||||
|
||||
if (city->hasBuilding(id)) {
|
||||
QString msg = Localization::instance().get("ui.city.already_built", "%1 already built!").arg(bCfg->name);
|
||||
QString msg = Localization::instance().get("ui.city.already_built", "%1 already built in this city!").arg(bCfg->name);
|
||||
addFloatingText(msg, city->worldPos + QVector3D(0.0f, 0.0f, 1.2f), QColor(255, 100, 100), 2.2f);
|
||||
return;
|
||||
}
|
||||
|
||||
if (city->isBuildingQueuedOrBuilt(id)) {
|
||||
QString msg = Localization::instance().get("ui.city.already_queued", "%1 is already in the production queue!").arg(bCfg->name);
|
||||
addFloatingText(msg, city->worldPos + QVector3D(0.0f, 0.0f, 1.2f), QColor(255, 160, 60), 2.2f);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!city->hasActiveProject()) {
|
||||
city->startProject(CityProjectType::Building, id, bCfg->name, bCfg->laborCost, bCfg->district);
|
||||
QString msg = Localization::instance().get("ui.city.project_started", "%1: Started %2")
|
||||
.arg(city->name).arg(bCfg->name);
|
||||
addFloatingText(msg, city->worldPos + QVector3D(0.0f, 0.0f, 1.2f), QColor(255, 230, 80), 2.5f);
|
||||
} else {
|
||||
if ((int)city->projectQueue.size() >= maxQueue) {
|
||||
QString msg = Localization::instance().get("ui.city.queue_full_float", "Production queue is full! (Max %1)").arg(maxQueue);
|
||||
addFloatingText(msg, city->worldPos + QVector3D(0.0f, 0.0f, 1.2f), QColor(255, 100, 100), 2.0f);
|
||||
return;
|
||||
}
|
||||
city->queueProject(CityProjectType::Building, id, bCfg->name, bCfg->laborCost, bCfg->district, maxQueue);
|
||||
int pos = (int)city->projectQueue.size();
|
||||
QString msg = Localization::instance().get("ui.city.project_queued", "%1: Added %2 to queue (#%3)")
|
||||
.arg(city->name).arg(bCfg->name).arg(pos);
|
||||
addFloatingText(msg, city->worldPos + QVector3D(0.0f, 0.0f, 1.2f), QColor(100, 200, 255), 2.2f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameManager::setCityProject(int cityId, CityProjectType type, const QString &id)
|
||||
{
|
||||
queueCityProject(cityId, type, id);
|
||||
}
|
||||
|
||||
void GameManager::cancelCityProject(int cityId)
|
||||
@@ -487,9 +530,31 @@ void GameManager::cancelCityProject(int cityId)
|
||||
City *city = getCityById(cityId);
|
||||
if (!city || !city->hasActiveProject()) return;
|
||||
|
||||
QString msg = Localization::instance().get("ui.city.project_cancelled", "%1: Project cancelled").arg(city->name);
|
||||
QString cancelledName = city->currentProject.name;
|
||||
city->cancelActiveProject();
|
||||
|
||||
QString msg = Localization::instance().get("ui.city.project_cancelled", "%1: Cancelled %2").arg(city->name).arg(cancelledName);
|
||||
addFloatingText(msg, city->worldPos + QVector3D(0.0f, 0.0f, 1.2f), QColor(255, 180, 80), 2.0f);
|
||||
city->cancelProject();
|
||||
|
||||
if (city->hasActiveProject()) {
|
||||
QString nextMsg = Localization::instance().get("ui.city.project_started", "%1: Started %2")
|
||||
.arg(city->name).arg(city->currentProject.name);
|
||||
addFloatingText(nextMsg, city->worldPos + QVector3D(0.0f, 0.0f, 1.4f), QColor(255, 230, 80), 2.5f);
|
||||
}
|
||||
}
|
||||
|
||||
void GameManager::removeQueuedCityProject(int cityId, int queueIndex)
|
||||
{
|
||||
City *city = getCityById(cityId);
|
||||
if (!city) return;
|
||||
|
||||
if (queueIndex >= 0 && queueIndex < (int)city->projectQueue.size()) {
|
||||
QString itemName = city->projectQueue[queueIndex].name;
|
||||
city->removeQueuedProject(queueIndex);
|
||||
QString msg = Localization::instance().get("ui.city.queued_item_removed", "%1: Removed %2 from queue")
|
||||
.arg(city->name).arg(itemName);
|
||||
addFloatingText(msg, city->worldPos + QVector3D(0.0f, 0.0f, 1.2f), QColor(255, 170, 80), 2.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void GameManager::completeCityProject(City &city, Matrix2D<TerrainPoint> *mapPoints, int numPoints05)
|
||||
@@ -540,8 +605,16 @@ void GameManager::completeCityProject(City &city, Matrix2D<TerrainPoint> *mapPoi
|
||||
}
|
||||
|
||||
int excess = city.currentProject.currentLabor - city.currentProject.laborCost;
|
||||
city.advanceQueue();
|
||||
|
||||
if (city.hasActiveProject() && excess > 0) {
|
||||
city.currentProject.currentLabor += excess;
|
||||
if (city.currentProject.currentLabor >= city.currentProject.laborCost) {
|
||||
completeCityProject(city, mapPoints, numPoints05);
|
||||
}
|
||||
} else {
|
||||
city.accumulatedLabor += std::max(0, excess);
|
||||
city.cancelProject();
|
||||
}
|
||||
}
|
||||
|
||||
void GameManager::commandMoveSelected(int targetX, int targetY, Matrix2D<TerrainPoint> *mapPoints, int numPoints05)
|
||||
|
||||
@@ -108,8 +108,10 @@ public:
|
||||
bool isTileOwned(int tx, int ty, int *outOwnerCityId = nullptr) const;
|
||||
bool buyTileForCity(int cityId, int tx, int ty);
|
||||
|
||||
void queueCityProject(int cityId, CityProjectType type, const QString &id);
|
||||
void setCityProject(int cityId, CityProjectType type, const QString &id);
|
||||
void cancelCityProject(int cityId);
|
||||
void removeQueuedCityProject(int cityId, int queueIndex);
|
||||
void completeCityProject(City &city, Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
|
||||
|
||||
void commandMoveSelected(int targetX, int targetY, Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.2 MiB |
@@ -143,6 +143,12 @@ protected:
|
||||
};
|
||||
std::vector<CityProjectHit> m_cityProjectHits;
|
||||
|
||||
struct CityQueueItemHit {
|
||||
QRect rect;
|
||||
int queueIndex;
|
||||
};
|
||||
std::vector<CityQueueItemHit> m_cityQueueItemHits;
|
||||
|
||||
QRect m_cancelProjectBtnRect;
|
||||
int m_cityProjectTab = 0; // 0 = Buildings, 1 = Units
|
||||
QRect m_tabBuildingsRect;
|
||||
|
||||
@@ -17,6 +17,7 @@ RoadConfigManager::RoadConfigManager()
|
||||
dirt.colorStr = "#c49a62";
|
||||
dirt.borderColorStr = "#6b4b23";
|
||||
dirt.width = 3.5f;
|
||||
dirt.worldWidth = 0.11f;
|
||||
dirt.cost = 5;
|
||||
m_types["dirt"] = dirt;
|
||||
|
||||
@@ -27,6 +28,7 @@ RoadConfigManager::RoadConfigManager()
|
||||
stone.colorStr = "#9aa1a8";
|
||||
stone.borderColorStr = "#464b50";
|
||||
stone.width = 4.0f;
|
||||
stone.worldWidth = 0.13f;
|
||||
stone.cost = 15;
|
||||
m_types["stone"] = stone;
|
||||
|
||||
@@ -37,6 +39,7 @@ RoadConfigManager::RoadConfigManager()
|
||||
rail.colorStr = "#3a3c40";
|
||||
rail.borderColorStr = "#181a1c";
|
||||
rail.width = 4.5f;
|
||||
rail.worldWidth = 0.15f;
|
||||
rail.cost = 30;
|
||||
m_types["rail"] = rail;
|
||||
}
|
||||
@@ -72,6 +75,7 @@ bool RoadConfigManager::loadConfigFile(const QString &filePath)
|
||||
rCfg.colorStr = it.value().hasChild("color") ? it.value()["color"].asString("#c49a62") : "#c49a62";
|
||||
rCfg.borderColorStr = it.value().hasChild("border_color") ? it.value()["border_color"].asString("#6b4b23") : "#6b4b23";
|
||||
rCfg.width = it.value().hasChild("width") ? it.value()["width"].asFloat(3.5f) : 3.5f;
|
||||
rCfg.worldWidth = it.value().hasChild("world_width") ? it.value()["world_width"].asFloat(0.11f) : 0.11f;
|
||||
rCfg.cost = it.value().hasChild("cost") ? it.value()["cost"].asInt(5) : 5;
|
||||
m_types[rCfg.id] = rCfg;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ struct RoadTypeConfig {
|
||||
QString colorStr = "#c49a62";
|
||||
QString borderColorStr = "#6b4b23";
|
||||
float width = 3.5f;
|
||||
float worldWidth = 0.11f;
|
||||
int cost = 5;
|
||||
|
||||
QColor getColor() const { return QColor(colorStr); }
|
||||
|
||||
@@ -35,6 +35,7 @@ bool TerrainYieldManager::loadConfigs(const QString &baseDir)
|
||||
m_cityConfig.baseResourceYieldPerTick = cityRoot["base_resource_yield_per_tick"].asInt(500);
|
||||
m_cityConfig.tileCultureCost = cityRoot["tile_culture_cost"].asInt(10000);
|
||||
m_cityConfig.foodToInhabitantsRatio = cityRoot["food_to_inhabitants_ratio"].asFloat(1.0f);
|
||||
m_cityConfig.maxProductionQueueSize = cityRoot["max_production_queue_size"].asInt(6);
|
||||
|
||||
if (cityRoot.hasChild("base_yields")) {
|
||||
const YamlNode &bNode = cityRoot["base_yields"];
|
||||
|
||||
@@ -34,6 +34,7 @@ struct CityConfig {
|
||||
int baseResourceYieldPerTick;
|
||||
int tileCultureCost;
|
||||
float foodToInhabitantsRatio;
|
||||
int maxProductionQueueSize;
|
||||
|
||||
int slotLongPressDurationMs;
|
||||
QStringList resourcePriority;
|
||||
@@ -47,6 +48,7 @@ struct CityConfig {
|
||||
cityViewZoom(4.8f),
|
||||
baseResourceYieldPerTick(500), tileCultureCost(10000),
|
||||
foodToInhabitantsRatio(1.0f),
|
||||
maxProductionQueueSize(6),
|
||||
slotLongPressDurationMs(400),
|
||||
resourcePriority({"voedsel", "arbeid", "goud", "kennis", "cultuur", "geloof"})
|
||||
{
|
||||
|
||||
@@ -123,6 +123,7 @@ void Unit::stopMovement()
|
||||
moveProgress = 0.0f;
|
||||
state = UnitState::Idle;
|
||||
clearActionQueue();
|
||||
clearRoadRoute();
|
||||
}
|
||||
|
||||
void Unit::startBuilding(const QString &impId, float duration)
|
||||
|
||||
@@ -69,6 +69,25 @@ public:
|
||||
std::deque<UnitAction> actionQueue;
|
||||
int maxQueueSize = 5;
|
||||
|
||||
// Road Construction Route (tile-by-tile road paving)
|
||||
std::vector<std::pair<int, int>> roadRoute;
|
||||
size_t roadRouteIndex = 0;
|
||||
|
||||
bool hasRoadRoute() const { return !roadRoute.empty() && roadRouteIndex < roadRoute.size(); }
|
||||
void startRoadRoute(const std::vector<std::pair<int, int>> &route) {
|
||||
roadRoute = route;
|
||||
roadRouteIndex = 0;
|
||||
}
|
||||
void clearRoadRoute() {
|
||||
roadRoute.clear();
|
||||
roadRouteIndex = 0;
|
||||
}
|
||||
void advanceRoadRouteStep() {
|
||||
if (!roadRoute.empty()) {
|
||||
roadRouteIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
bool queueAction(const UnitAction &action);
|
||||
void clearActionQueue();
|
||||
bool hasQueuedAction() const { return !actionQueue.empty(); }
|
||||
|
||||
Reference in New Issue
Block a user