Implement city production projects for units and buildings
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
@@ -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:
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5,6 +5,7 @@ domain: land
|
||||
health: 100
|
||||
movement_speed: 2.0
|
||||
cost: 80
|
||||
labor_cost: 2000
|
||||
icon: images/units/settler.png
|
||||
actions:
|
||||
- verplaatsen
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+133
-8
@@ -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:
|
||||
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<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();
|
||||
|
||||
@@ -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
@@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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; }
|
||||
|
||||
Reference in New Issue
Block a user