diff --git a/buildingconfig.cpp b/buildingconfig.cpp new file mode 100644 index 0000000..8682ff2 --- /dev/null +++ b/buildingconfig.cpp @@ -0,0 +1,82 @@ +#include "buildingconfig.h" +#include "yamlparser.h" +#include +#include + +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; +} diff --git a/buildingconfig.h b/buildingconfig.h new file mode 100644 index 0000000..415f388 --- /dev/null +++ b/buildingconfig.h @@ -0,0 +1,55 @@ +#ifndef BUILDINGCONFIG_H +#define BUILDINGCONFIG_H + +#include +#include +#include + +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& getAllBuildings() const { return m_buildings; } + const QMap& getAllDistricts() const { return m_districts; } + +private: + QMap m_buildings; + QMap m_districts; +}; + +#endif // BUILDINGCONFIG_H diff --git a/city.h b/city.h index 1b0df9a..6dc0b89 100644 --- a/city.h +++ b/city.h @@ -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 completedBuildings; // IDs of completed buildings in the city + std::vector> territory; // Owned hex coordinates std::map, 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); diff --git a/config/buildings.yaml b/config/buildings.yaml new file mode 100644 index 0000000..6770656 --- /dev/null +++ b/config/buildings.yaml @@ -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" diff --git a/config/city.yaml b/config/city.yaml index 0169586..88cf169 100644 --- a/config/city.yaml +++ b/config/city.yaml @@ -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: diff --git a/config/strings_en.yaml b/config/strings_en.yaml index 3d555a5..6c48e13 100644 --- a/config/strings_en.yaml +++ b/config/strings_en.yaml @@ -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" diff --git a/config/units/builder.yaml b/config/units/builder.yaml index 541696b..d2a8762 100644 --- a/config/units/builder.yaml +++ b/config/units/builder.yaml @@ -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 diff --git a/config/units/galley.yaml b/config/units/galley.yaml index 787b96a..0eab5bf 100644 --- a/config/units/galley.yaml +++ b/config/units/galley.yaml @@ -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 diff --git a/config/units/settler.yaml b/config/units/settler.yaml index 0e4511c..0b247d7 100644 --- a/config/units/settler.yaml +++ b/config/units/settler.yaml @@ -5,6 +5,7 @@ domain: land health: 100 movement_speed: 2.0 cost: 80 +labor_cost: 2000 icon: images/units/settler.png actions: - verplaatsen diff --git a/config/units/slinger.yaml b/config/units/slinger.yaml index b45065e..6cdd99c 100644 --- a/config/units/slinger.yaml +++ b/config/units/slinger.yaml @@ -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 diff --git a/config/units/warrior.yaml b/config/units/warrior.yaml index 7dc6acb..f596f47 100644 --- a/config/units/warrior.yaml +++ b/config/units/warrior.yaml @@ -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 diff --git a/gamemanager.cpp b/gamemanager.cpp index dce51be..3dea832 100644 --- a/gamemanager.cpp +++ b/gamemanager.cpp @@ -42,6 +42,10 @@ void GameManager::init(const QString &baseDir, Matrix2D *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, Matrix2DfoodBonus; + 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 *mapPoints, int numPo int foodGrown = (int)std::round(yields.food * cfg.foodToInhabitantsRatio); city.inhabitants += foodGrown; - // 2. Labor is accumulated locally in the city: - city.accumulatedLabor += yields.labor; + // 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 *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 *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 *mapPoints, int numPoints05) { Unit *selected = getSelectedUnit(); diff --git a/gamemanager.h b/gamemanager.h index 0825ad2..16f66fc 100644 --- a/gamemanager.h +++ b/gamemanager.h @@ -6,6 +6,7 @@ #include #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>& getUnits() const { return m_units; } const std::vector& 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 *mapPoints, int numPoints05); + void commandMoveSelected(int targetX, int targetY, Matrix2D *mapPoints, int numPoints05); void commandAttack(Unit *target); void commandBuildCitySelected(Matrix2D *mapPoints, int numPoints05); @@ -110,6 +117,7 @@ private: PlayerManager m_playerManager; UnitConfigManager m_unitConfigManager; + BuildingConfigManager m_buildingConfigManager; std::vector> m_units; std::vector m_cities; std::vector m_floatingTexts; diff --git a/images/improvements/granary.png b/images/improvements/granary.png new file mode 100644 index 0000000..d59fe5e Binary files /dev/null and b/images/improvements/granary.png differ diff --git a/images/improvements/monument.png b/images/improvements/monument.png new file mode 100644 index 0000000..18cac1a Binary files /dev/null and b/images/improvements/monument.png differ diff --git a/qscenerenderer.cpp b/qscenerenderer.cpp index 25d104f..fe8f165 100644 --- a/qscenerenderer.cpp +++ b/qscenerenderer.cpp @@ -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")); } } } diff --git a/qscenerenderer.h b/qscenerenderer.h index 070affc..4b01bbd 100644 --- a/qscenerenderer.h +++ b/qscenerenderer.h @@ -134,6 +134,18 @@ protected: int cityId; }; std::vector m_cityBannerHits; + + struct CityProjectHit { + QRect rect; + CityProjectType type; + QString id; + }; + std::vector m_cityProjectHits; + + QRect m_cancelProjectBtnRect; + int m_cityProjectTab = 0; // 0 = Buildings, 1 = Units + QRect m_tabBuildingsRect; + QRect m_tabUnitsRect; }; #endif // QSCENERENDERER_H diff --git a/unitconfig.cpp b/unitconfig.cpp index b71bc2a..a70d49c 100644 --- a/unitconfig.cpp +++ b/unitconfig.cpp @@ -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; diff --git a/unitconfig.h b/unitconfig.h index 7e3ae19..e8c29bd 100644 --- a/unitconfig.h +++ b/unitconfig.h @@ -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; }