Implement city production projects for units and buildings

This commit is contained in:
bart
2026-11-08 21:22:04 +01:00
parent ae04619dae
commit f8cd221e37
19 changed files with 444 additions and 12 deletions
+82
View File
@@ -0,0 +1,82 @@
#include "buildingconfig.h"
#include "yamlparser.h"
#include <QFileInfo>
#include <QDebug>
BuildingConfigManager::BuildingConfigManager()
{
}
bool BuildingConfigManager::loadConfigFile(const QString &filePath)
{
YamlNode root = YamlParser::parseFile(filePath);
if (root.children.isEmpty()) {
qWarning() << "Failed to parse buildings config YAML:" << filePath;
return false;
}
// Parse districts
if (root.hasChild("districts")) {
const YamlNode &districtsNode = root["districts"];
for (auto it = districtsNode.children.begin(); it != districtsNode.children.end(); ++it) {
DistrictConfig dCfg;
dCfg.id = it.value().hasChild("id") ? it.value()["id"].asString() : it.key();
dCfg.name = it.value().hasChild("name") ? it.value()["name"].asString() : dCfg.id;
dCfg.maxBuildings = it.value().hasChild("max_buildings") ? it.value()["max_buildings"].asInt(10) : 10;
dCfg.description = it.value().hasChild("description") ? it.value()["description"].asString() : "";
m_districts[dCfg.id] = dCfg;
}
}
// Ensure default center district exists
if (!m_districts.contains("center")) {
DistrictConfig defaultCenter;
defaultCenter.id = "center";
defaultCenter.name = "City Center";
defaultCenter.maxBuildings = 10;
m_districts["center"] = defaultCenter;
}
// Parse buildings
if (root.hasChild("buildings")) {
const YamlNode &buildingsNode = root["buildings"];
for (auto it = buildingsNode.children.begin(); it != buildingsNode.children.end(); ++it) {
BuildingConfig bCfg;
bCfg.id = it.value().hasChild("id") ? it.value()["id"].asString() : it.key();
bCfg.name = it.value().hasChild("name") ? it.value()["name"].asString() : bCfg.id;
bCfg.district = it.value().hasChild("district") ? it.value()["district"].asString("center") : "center";
bCfg.laborCost = it.value().hasChild("labor_cost") ? it.value()["labor_cost"].asInt(1000) : 1000;
bCfg.foodModifier = it.value().hasChild("food_modifier") ? it.value()["food_modifier"].asFloat(0.0f) : 0.0f;
bCfg.foodBonus = it.value().hasChild("food_bonus") ? it.value()["food_bonus"].asInt(0) : 0;
bCfg.laborBonus = it.value().hasChild("labor_bonus") ? it.value()["labor_bonus"].asInt(0) : 0;
bCfg.cultureBonus = it.value().hasChild("culture_bonus") ? it.value()["culture_bonus"].asInt(0) : 0;
bCfg.scienceBonus = it.value().hasChild("science_bonus") ? it.value()["science_bonus"].asInt(0) : 0;
bCfg.faithBonus = it.value().hasChild("faith_bonus") ? it.value()["faith_bonus"].asInt(0) : 0;
bCfg.goldBonus = it.value().hasChild("gold_bonus") ? it.value()["gold_bonus"].asInt(0) : 0;
bCfg.iconPath = it.value().hasChild("icon") ? it.value()["icon"].asString() : "";
bCfg.description = it.value().hasChild("description") ? it.value()["description"].asString() : "";
m_buildings[bCfg.id] = bCfg;
}
}
qDebug() << "BuildingConfigManager loaded" << m_districts.size() << "districts and" << m_buildings.size() << "buildings from" << filePath;
return true;
}
const BuildingConfig* BuildingConfigManager::getBuilding(const QString &id) const
{
auto it = m_buildings.find(id);
if (it != m_buildings.end()) {
return &it.value();
}
return nullptr;
}
const DistrictConfig* BuildingConfigManager::getDistrict(const QString &id) const
{
auto it = m_districts.find(id);
if (it != m_districts.end()) {
return &it.value();
}
return nullptr;
}
+55
View File
@@ -0,0 +1,55 @@
#ifndef BUILDINGCONFIG_H
#define BUILDINGCONFIG_H
#include <QString>
#include <QMap>
#include <vector>
struct BuildingConfig {
QString id;
QString name;
QString district; // e.g. "center"
int laborCost;
float foodModifier; // e.g. 0.50f for +50% food production
int foodBonus;
int laborBonus;
int cultureBonus;
int scienceBonus;
int faithBonus;
int goldBonus;
QString iconPath;
QString description;
BuildingConfig()
: id(""), name(""), district("center"), laborCost(1000),
foodModifier(0.0f), foodBonus(0), laborBonus(0), cultureBonus(0),
scienceBonus(0), faithBonus(0), goldBonus(0),
iconPath(""), description("") {}
};
struct DistrictConfig {
QString id;
QString name;
int maxBuildings;
QString description;
DistrictConfig()
: id("center"), name("City Center"), maxBuildings(10), description("") {}
};
class BuildingConfigManager {
public:
BuildingConfigManager();
bool loadConfigFile(const QString &filePath);
const BuildingConfig* getBuilding(const QString &id) const;
const DistrictConfig* getDistrict(const QString &id) const;
const QMap<QString, BuildingConfig>& getAllBuildings() const { return m_buildings; }
const QMap<QString, DistrictConfig>& getAllDistricts() const { return m_districts; }
private:
QMap<QString, BuildingConfig> m_buildings;
QMap<QString, DistrictConfig> m_districts;
};
#endif // BUILDINGCONFIG_H
+56
View File
@@ -18,6 +18,29 @@ struct CityYieldSummary {
int gold = 0;
};
enum class CityProjectType {
None,
Unit,
Building
};
struct CityProject {
CityProjectType type = CityProjectType::None;
QString id = "";
QString name = "";
int laborCost = 0;
int currentLabor = 0;
QString district = "center";
bool isValid() const { return type != CityProjectType::None && !id.isEmpty(); }
float getProgress() const {
return laborCost > 0 ? std::max(0.0f, std::min(1.0f, (float)currentLabor / (float)laborCost)) : 0.0f;
}
int getRemainingLabor() const {
return std::max(0, laborCost - currentLabor);
}
};
class City {
public:
int id;
@@ -31,12 +54,45 @@ public:
int inhabitantsPerPop;
int accumulatedLabor = 0; // Local resource (Arbeid)
CityProject currentProject;
std::vector<QString> completedBuildings; // IDs of completed buildings in the city
std::vector<std::pair<int, int>> territory; // Owned hex coordinates
std::map<std::pair<int, int>, QString> workedTiles; // (x, y) -> resource name
City(int cId, int pId, const QString &cName, int tx, int ty, const QVector3D &pos,
int initInhabitants = 1000, int initInhabitantsPerPop = 100000, int initRadius = 1);
bool hasBuilding(const QString &buildingId) const {
for (const auto &b : completedBuildings) {
if (b == buildingId) return true;
}
return false;
}
bool addBuilding(const QString &buildingId) {
if (hasBuilding(buildingId)) return false;
completedBuildings.push_back(buildingId);
return true;
}
bool hasActiveProject() const {
return currentProject.isValid();
}
void startProject(CityProjectType type, const QString &projId, const QString &projName, int cost, const QString &district = "center") {
currentProject.type = type;
currentProject.id = projId;
currentProject.name = projName;
currentProject.laborCost = std::max(1, cost);
currentProject.currentLabor = 0;
currentProject.district = district.isEmpty() ? "center" : district;
}
void cancelProject() {
currentProject = CityProject();
}
bool addTerritory(int tx, int ty) {
if (ownsTile(tx, ty)) return false;
territory.emplace_back(tx, ty);
+40
View File
@@ -0,0 +1,40 @@
# City Buildings & Extensions configuration
# All gameplay values are configurable via YAML
districts:
center:
id: "center"
name: "City Center"
max_buildings: 10
description: "The urban core and administrative center of the city."
buildings:
granary:
id: "granary"
name: "Granary"
district: "center"
labor_cost: 1000
food_modifier: 0.50
food_bonus: 0
labor_bonus: 0
culture_bonus: 0
science_bonus: 0
faith_bonus: 0
gold_bonus: 0
icon: "images/improvements/granary.png"
description: "+50% Food production in city"
monument:
id: "monument"
name: "Monument"
district: "center"
labor_cost: 800
food_modifier: 0.0
food_bonus: 0
labor_bonus: 0
culture_bonus: 250
science_bonus: 0
faith_bonus: 0
gold_bonus: 0
icon: "images/improvements/monument.png"
description: "+250 Culture per tick"
+2
View File
@@ -12,6 +12,8 @@ city_view_zoom: 4.8
base_resource_yield_per_tick: 500
tile_culture_cost: 10000
food_to_inhabitants_ratio: 1.0
default_unit_labor_cost: 1000
unit_labor_cost_multiplier: 20
# Base yield breakdown per resource (default 500 per tick)
base_yields:
+32
View File
@@ -59,3 +59,35 @@ ui:
produce_unit: "Produce Unit"
build_district: "Construct District"
population_growth: "Growth in {turns}s"
management_title: "%1 - CITY MANAGEMENT"
close_btn: "[X] Close"
local_info: "Local: Population: %L1 (%2 Pop: %3 working, %4 free) | Labor: %L5"
national_info: "National: Culture: %L1 | Science: %L2 | Gold: %L3 | Faith: %L4"
help_text: "Click a tile to assign 1 Pop | Click an unowned tile to buy with Culture."
current_production: "CURRENT PRODUCTION"
no_project: "No active project"
select_prompt: "Select a unit or building to construct"
ticks_remaining: "%1 ticks remaining (%2 / %3 Labor)"
cancel_btn: "Cancel"
districts_header: "DISTRICT: CITY CENTER"
no_buildings: "No extensions built yet"
choose_project: "CHOOSE CITY PROJECT"
tab_buildings: "Buildings & Extensions"
tab_units: "Units"
btn_build: "Build"
btn_train: "Train"
status_built: "BUILT"
status_in_progress: "ACTIVE"
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"
already_built: "%1 already built in this city!"
growth_float: "+%1 Inhabitants | +%2 Culture"
not_enough_culture: "Not enough Culture! (%1 needed)"
worker_freed: "Pop worker freed"
worker_assigned: "+1 Pop working"
no_free_pop: "No free Pop!"
buy_tile: "BUY"
occupied: "OCCUPIED"
+1
View File
@@ -5,6 +5,7 @@ domain: land
health: 100
movement_speed: 2.0
cost: 50
labor_cost: 1000
build_charges: -1 # -1 voor oneindig doorwerken
infinite_charges: true
icon: images/units/builder.png
+1
View File
@@ -5,6 +5,7 @@ domain: naval
health: 120
movement_speed: 3.0
cost: 60
labor_cost: 1500
attack_damage: 25
attack_range: 1
icon: images/units/galley.png
+1
View File
@@ -5,6 +5,7 @@ domain: land
health: 100
movement_speed: 2.0
cost: 80
labor_cost: 2000
icon: images/units/settler.png
actions:
- verplaatsen
+1
View File
@@ -5,6 +5,7 @@ domain: land
health: 70
movement_speed: 2.0
cost: 35
labor_cost: 800
attack_damage: 15
attack_range: 2
icon: images/units/slinger.png
+1
View File
@@ -5,6 +5,7 @@ domain: land
health: 100
movement_speed: 2.0
cost: 40
labor_cost: 1000
attack_damage: 20
attack_range: 1
icon: images/units/warrior.png
+132 -7
View File
@@ -42,6 +42,10 @@ void GameManager::init(const QString &baseDir, Matrix2D<TerrainPoint> *mapPoints
QString unitsConfigDir = baseDir + "/config/units";
m_unitConfigManager.loadFromDirectory(unitsConfigDir);
// Load building & district YAML configurations
QString buildingsConfigPath = baseDir + "/config/buildings.yaml";
m_buildingConfigManager.loadConfigFile(buildingsConfigPath);
const CityConfig &cCfg = TerrainYieldManager::instance().getCityConfig();
// Initialize player national resources from configuration
@@ -272,6 +276,29 @@ CityYieldSummary GameManager::calculateCityYields(const City &city, Matrix2D<Ter
else if (assignedRes == "geloof") summary.faith += amount;
else if (assignedRes == "goud") summary.gold += amount;
}
// Additional yields and multipliers from completed city buildings
float totalFoodMultiplier = 1.0f;
for (const QString &bId : city.completedBuildings) {
const BuildingConfig *bCfg = m_buildingConfigManager.getBuilding(bId);
if (!bCfg) continue;
summary.food += bCfg->foodBonus;
summary.labor += bCfg->laborBonus;
summary.science += bCfg->scienceBonus;
summary.culture += bCfg->cultureBonus;
summary.faith += bCfg->faithBonus;
summary.gold += bCfg->goldBonus;
if (bCfg->foodModifier > 0.0f) {
totalFoodMultiplier += bCfg->foodModifier;
}
}
if (totalFoodMultiplier != 1.0f) {
summary.food = (int)std::round(summary.food * totalFoodMultiplier);
}
return summary;
}
@@ -286,8 +313,15 @@ void GameManager::processTickYields(Matrix2D<TerrainPoint> *mapPoints, int numPo
int foodGrown = (int)std::round(yields.food * cfg.foodToInhabitantsRatio);
city.inhabitants += foodGrown;
// 2. Labor is accumulated locally in the city:
// 2. Labor contributes to active project or accumulates locally:
if (city.hasActiveProject()) {
city.currentProject.currentLabor += yields.labor;
if (city.currentProject.currentLabor >= city.currentProject.laborCost) {
completeCityProject(city, mapPoints, numPoints05);
}
} else {
city.accumulatedLabor += yields.labor;
}
// 3. National resources go to the owner player:
Player *owner = m_playerManager.getPlayer(city.playerId);
@@ -300,8 +334,10 @@ void GameManager::processTickYields(Matrix2D<TerrainPoint> *mapPoints, int numPo
// Floating growth text over city
QVector3D ftPos = city.worldPos + QVector3D(0.0f, 0.0f, 1.1f);
addFloatingText(QString("+%1 Inwoners | +%2 Cultuur").arg(foodGrown).arg(yields.culture),
ftPos, QColor(100, 255, 140), 2.5f);
QString growthMsg = Localization::instance().get(
"ui.city.growth_float", "+%1 Inhabitants | +%2 Culture")
.arg(foodGrown).arg(yields.culture);
addFloatingText(growthMsg, ftPos, QColor(100, 255, 140), 2.5f);
}
}
@@ -342,8 +378,8 @@ bool GameManager::buyTileForCity(int cityId, int tx, int ty)
int cost = cfg.tileCultureCost;
if (player->culture < cost) {
QVector2D w = Pathfinder::hexToWorld(tx, ty);
addFloatingText(QString("Niet genoeg Cultuur! (%1 nodig)").arg(cost),
QVector3D(w.x(), w.y(), 0.8f), QColor(255, 90, 90), 2.0f);
QString notEnough = Localization::instance().get("ui.city.not_enough_culture", "Not enough Culture! (%1 needed)").arg(cost);
addFloatingText(notEnough, QVector3D(w.x(), w.y(), 0.8f), QColor(255, 90, 90), 2.0f);
return false;
}
@@ -352,11 +388,100 @@ bool GameManager::buyTileForCity(int cityId, int tx, int ty)
city->addTerritory(tx, ty);
QVector2D w = Pathfinder::hexToWorld(tx, ty);
addFloatingText(QString("Tegel gekocht! (-%1 Cultuur)").arg(cost),
QVector3D(w.x(), w.y(), 0.8f), QColor(210, 120, 255), 2.5f);
QString boughtMsg = Localization::instance().get("ui.unit.tile_purchased", "Tile purchased! (-%1 Culture)").arg(cost);
addFloatingText(boughtMsg, QVector3D(w.x(), w.y(), 0.8f), QColor(210, 120, 255), 2.5f);
return true;
}
void GameManager::setCityProject(int cityId, CityProjectType type, const QString &id)
{
City *city = getCityById(cityId);
if (!city) return;
if (type == CityProjectType::Unit) {
const UnitConfig *uCfg = m_unitConfigManager.getConfig(id);
if (!uCfg) return;
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 (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);
addFloatingText(msg, city->worldPos + QVector3D(0.0f, 0.0f, 1.2f), QColor(255, 100, 100), 2.2f);
return;
}
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);
}
}
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);
addFloatingText(msg, city->worldPos + QVector3D(0.0f, 0.0f, 1.2f), QColor(255, 180, 80), 2.0f);
city->cancelProject();
}
void GameManager::completeCityProject(City &city, Matrix2D<TerrainPoint> *mapPoints, int numPoints05)
{
if (!city.hasActiveProject()) return;
if (city.currentProject.type == CityProjectType::Unit) {
QString unitId = city.currentProject.id;
const UnitConfig *uCfg = m_unitConfigManager.getConfig(unitId);
// Find spawn location: city center or adjacent empty hex
int spawnX = city.tileX;
int spawnY = city.tileY;
if (getUnitAt(spawnX, spawnY) != nullptr) {
for (int e = 0; e < 6; ++e) {
int nx, ny;
getHexNeighbor(city.tileX, city.tileY, e, nx, ny);
if (nx >= 0 && nx < MAP_WIDTH && ny >= 0 && ny < MAP_HEIGHT) {
if (getUnitAt(nx, ny) == nullptr &&
TerrainMovementManager::instance().isHexPassable(nx, ny, uCfg ? uCfg->movementDomain : MovementDomain::Land, mapPoints, numPoints05, uCfg)) {
spawnX = nx;
spawnY = ny;
break;
}
}
}
}
Unit *spawned = spawnUnit(unitId, city.playerId, spawnX, spawnY, mapPoints, numPoints05);
QString unitName = spawned ? spawned->config.name : (uCfg ? uCfg->name : unitId);
QString msg = Localization::instance().get("ui.city.unit_trained", "%1: %2 trained!")
.arg(city.name).arg(unitName);
addFloatingText(msg, city.worldPos + QVector3D(0.0f, 0.0f, 1.3f), QColor(80, 255, 140), 3.0f);
} else if (city.currentProject.type == CityProjectType::Building) {
QString bId = city.currentProject.id;
city.addBuilding(bId);
const BuildingConfig *bCfg = m_buildingConfigManager.getBuilding(bId);
QString bName = bCfg ? bCfg->name : bId;
const DistrictConfig *dCfg = m_buildingConfigManager.getDistrict(bCfg ? bCfg->district : "center");
QString dName = dCfg ? dCfg->name : "City Center";
QString msg = Localization::instance().get("ui.city.building_completed", "%1: %2 completed in %3!")
.arg(city.name).arg(bName).arg(dName);
addFloatingText(msg, city.worldPos + QVector3D(0.0f, 0.0f, 1.3f), QColor(80, 240, 255), 3.5f);
}
int excess = city.currentProject.currentLabor - city.currentProject.laborCost;
city.accumulatedLabor += std::max(0, excess);
city.cancelProject();
}
void GameManager::commandMoveSelected(int targetX, int targetY, Matrix2D<TerrainPoint> *mapPoints, int numPoints05)
{
Unit *selected = getSelectedUnit();
+8
View File
@@ -6,6 +6,7 @@
#include <QString>
#include "player.h"
#include "unitconfig.h"
#include "buildingconfig.h"
#include "unit.h"
#include "city.h"
#include "matrix2d.h"
@@ -45,6 +46,8 @@ public:
PlayerManager& players() { return m_playerManager; }
UnitConfigManager& unitConfigs() { return m_unitConfigManager; }
BuildingConfigManager& buildingConfigs() { return m_buildingConfigManager; }
const BuildingConfigManager& buildingConfigs() const { return m_buildingConfigManager; }
const std::vector<std::shared_ptr<Unit>>& getUnits() const { return m_units; }
const std::vector<City>& getCities() const { return m_cities; }
@@ -79,6 +82,10 @@ public:
bool isTileOwned(int tx, int ty, int *outOwnerCityId = nullptr) const;
bool buyTileForCity(int cityId, int tx, int ty);
void setCityProject(int cityId, CityProjectType type, const QString &id);
void cancelCityProject(int cityId);
void completeCityProject(City &city, Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
void commandMoveSelected(int targetX, int targetY, Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
void commandAttack(Unit *target);
void commandBuildCitySelected(Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
@@ -110,6 +117,7 @@ private:
PlayerManager m_playerManager;
UnitConfigManager m_unitConfigManager;
BuildingConfigManager m_buildingConfigManager;
std::vector<std::shared_ptr<Unit>> m_units;
std::vector<City> m_cities;
std::vector<FloatingText> m_floatingTexts;
Binary file not shown.

After

Width:  |  Height:  |  Size: 940 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 650 B

+10 -3
View File
@@ -1094,6 +1094,10 @@ void QSceneRenderer::drawCityViewOverlay(QPainter &painter, const QMatrix4x4 &vM
{
m_citySlotHits.clear();
m_cityBuyTileHits.clear();
m_cityProjectHits.clear();
m_cancelProjectBtnRect = QRect();
m_tabBuildingsRect = QRect();
m_tabUnitsRect = QRect();
City *city = GameManager::instance().getActiveCityView();
if (!city) return;
@@ -1230,10 +1234,12 @@ void QSceneRenderer::drawCityViewOverlay(QPainter &painter, const QMatrix4x4 &vM
QString costStr = QString("%L1").arg(cultureCost);
painter.drawText(QRect(buyRect.left() + 22, buyRect.top() + 2, buyRect.width() - 24, 13), Qt::AlignLeft | Qt::AlignVCenter, costStr);
// "Koop" subtitle
// "Buy" subtitle
painter.setFont(QFont("sans-serif", 6, QFont::Bold));
painter.setPen(canAfford ? QColor(220, 140, 255) : QColor(140, 120, 155));
painter.drawText(QRect(buyRect.left() + 22, buyRect.top() + 14, buyRect.width() - 24, 12), Qt::AlignLeft | Qt::AlignVCenter, "KOOP");
painter.drawText(QRect(buyRect.left() + 22, buyRect.top() + 14, buyRect.width() - 24, 12),
Qt::AlignLeft | Qt::AlignVCenter,
Localization::instance().get("ui.city.buy_tile", "BUY"));
} else {
// Owned by another city
QRect lockRect((int)sCenter.x() - 16, (int)sCenter.y() - 8, 32, 16);
@@ -1242,7 +1248,8 @@ void QSceneRenderer::drawCityViewOverlay(QPainter &painter, const QMatrix4x4 &vM
painter.drawRoundedRect(lockRect, 3, 3);
painter.setFont(QFont("sans-serif", 6, QFont::Bold));
painter.setPen(QColor(230, 130, 130));
painter.drawText(lockRect, Qt::AlignCenter, "BEZET");
painter.drawText(lockRect, Qt::AlignCenter,
Localization::instance().get("ui.city.occupied", "OCCUPIED"));
}
}
}
+12
View File
@@ -134,6 +134,18 @@ protected:
int cityId;
};
std::vector<CityBannerHit> m_cityBannerHits;
struct CityProjectHit {
QRect rect;
CityProjectType type;
QString id;
};
std::vector<CityProjectHit> m_cityProjectHits;
QRect m_cancelProjectBtnRect;
int m_cityProjectTab = 0; // 0 = Buildings, 1 = Units
QRect m_tabBuildingsRect;
QRect m_tabUnitsRect;
};
#endif // QSCENERENDERER_H
+7
View File
@@ -42,6 +42,13 @@ bool UnitConfigManager::loadConfigFile(const QString &filePath)
cfg.health = root.hasChild("health") ? root["health"].asInt(100) : 100;
cfg.movementSpeed = root.hasChild("movement_speed") ? root["movement_speed"].asFloat(2.0f) : 2.0f;
cfg.cost = root.hasChild("cost") ? root["cost"].asInt(20) : 20;
if (root.hasChild("labor_cost")) {
cfg.laborCost = root["labor_cost"].asInt(1000);
} else if (root.hasChild("production_cost")) {
cfg.laborCost = root["production_cost"].asInt(1000);
} else {
cfg.laborCost = cfg.cost * 20;
}
cfg.attackDamage = root.hasChild("attack_damage") ? root["attack_damage"].asInt(0) : 0;
cfg.attackRange = root.hasChild("attack_range") ? root["attack_range"].asInt(1) : 1;
cfg.buildCharges = root.hasChild("build_charges") ? root["build_charges"].asInt(-1) : -1;
+2 -1
View File
@@ -39,6 +39,7 @@ struct UnitConfig {
int health;
float movementSpeed;
int cost;
int laborCost;
int attackDamage;
int attackRange; // 1 for melee, >1 for ranged
int buildCharges;
@@ -52,7 +53,7 @@ struct UnitConfig {
UnitConfig()
: id(""), name(""), category(UnitCategory::Builder),
movementDomain(MovementDomain::Land), health(100),
movementSpeed(2.0f), cost(10), attackDamage(0), attackRange(1),
movementSpeed(2.0f), cost(10), laborCost(1000), attackDamage(0), attackRange(1),
buildCharges(-1), infiniteCharges(true), iconPath("") {}
bool isRanged() const { return attackRange > 1; }