Refactor farm building speed to builder unit action_speeds and enable infinite charges
This commit is contained in:
+241
-3
@@ -2,6 +2,8 @@
|
||||
#include "pathfinder.h"
|
||||
#include "yamlparser.h"
|
||||
#include "terrainyieldmanager.h"
|
||||
#include "terrainmovementmanager.h"
|
||||
#include "localization.h"
|
||||
#include <QDebug>
|
||||
#include <cmath>
|
||||
|
||||
@@ -23,6 +25,9 @@ void GameManager::init(const QString &baseDir, Matrix2D<TerrainPoint> *mapPoints
|
||||
{
|
||||
if (m_initialized) return;
|
||||
|
||||
// Load localization
|
||||
Localization::instance().loadFile(baseDir + "/config/strings_en.yaml");
|
||||
|
||||
// Load terrain yields & city configuration
|
||||
TerrainYieldManager::instance().loadConfigs(baseDir);
|
||||
|
||||
@@ -37,8 +42,17 @@ void GameManager::init(const QString &baseDir, Matrix2D<TerrainPoint> *mapPoints
|
||||
QString unitsConfigDir = baseDir + "/config/units";
|
||||
m_unitConfigManager.loadFromDirectory(unitsConfigDir);
|
||||
|
||||
// Spawn an initial established city for Player 1 at (35, 17)
|
||||
const CityConfig &cCfg = TerrainYieldManager::instance().getCityConfig();
|
||||
|
||||
// Initialize player national resources from configuration
|
||||
for (auto &player : m_playerManager.getPlayersMutable()) {
|
||||
player.culture = cCfg.initialNationalResources.value("cultuur", 10000);
|
||||
player.science = cCfg.initialNationalResources.value("kennis", 0);
|
||||
player.faith = cCfg.initialNationalResources.value("geloof", 0);
|
||||
player.gold = cCfg.initialNationalResources.value("goud", 0);
|
||||
}
|
||||
|
||||
// Spawn an initial established city for Player 1 at (35, 17)
|
||||
int cId = getNextCityId();
|
||||
QVector2D w = Pathfinder::hexToWorld(35, 17);
|
||||
float gz = Unit::getTerrainHeight(w.x(), w.y(), mapPoints, numPoints05);
|
||||
@@ -57,8 +71,10 @@ void GameManager::init(const QString &baseDir, Matrix2D<TerrainPoint> *mapPoints
|
||||
// Spawn initial demo units near starting camera position
|
||||
// Player 1 (Blue & Gold)
|
||||
spawnUnit("settler", 1, 36, 18, mapPoints, numPoints05);
|
||||
spawnUnit("builder", 1, 35, 18, mapPoints, numPoints05);
|
||||
spawnUnit("warrior", 1, 37, 18, mapPoints, numPoints05);
|
||||
spawnUnit("slinger", 1, 37, 19, mapPoints, numPoints05);
|
||||
spawnUnit("galley", 1, 41, 92, mapPoints, numPoints05);
|
||||
|
||||
// Player 2 (Red & White) - Opponent units
|
||||
spawnUnit("warrior", 2, 40, 18, mapPoints, numPoints05);
|
||||
@@ -92,12 +108,16 @@ void GameManager::update(float deltaTime, Matrix2D<TerrainPoint> *mapPoints, int
|
||||
m_tickTimer += deltaTime * m_simSpeedMultiplier;
|
||||
if (m_tickTimer >= m_movementTickDuration) {
|
||||
m_tickTimer = std::fmod(m_tickTimer, m_movementTickDuration);
|
||||
processTickYields(mapPoints, numPoints05);
|
||||
}
|
||||
|
||||
// Update units
|
||||
for (auto &u : m_units) {
|
||||
if (!u->isDead()) {
|
||||
u->update(deltaTime, m_movementTickDuration, m_simSpeedMultiplier, mapPoints, numPoints05);
|
||||
if (u->isBuilding() && u->buildProgress >= 1.0f) {
|
||||
completeUnitBuilding(*u, mapPoints, numPoints05);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,6 +234,17 @@ void GameManager::exitCityView()
|
||||
CityYieldSummary GameManager::calculateCityYields(const City &city, Matrix2D<TerrainPoint> *mapPoints, int numPoints05) const
|
||||
{
|
||||
CityYieldSummary summary;
|
||||
const CityConfig &cCfg = TerrainYieldManager::instance().getCityConfig();
|
||||
|
||||
// Base yields for every city per tick
|
||||
summary.food += cCfg.getBaseYield("voedsel");
|
||||
summary.labor += cCfg.getBaseYield("arbeid");
|
||||
summary.science += cCfg.getBaseYield("kennis");
|
||||
summary.culture += cCfg.getBaseYield("cultuur");
|
||||
summary.faith += cCfg.getBaseYield("geloof");
|
||||
summary.gold += cCfg.getBaseYield("goud");
|
||||
|
||||
// Additional yields from worked tiles
|
||||
for (const auto &pair : city.workedTiles) {
|
||||
int tx = pair.first.first;
|
||||
int ty = pair.first.second;
|
||||
@@ -224,6 +255,16 @@ CityYieldSummary GameManager::calculateCityYields(const City &city, Matrix2D<Ter
|
||||
if (!yCfg) continue;
|
||||
|
||||
int amount = yCfg->getYield(assignedRes);
|
||||
|
||||
// Add bonus from tile improvements (e.g. farm bonus)
|
||||
const TileImprovement *imp = getImprovementAt(tx, ty);
|
||||
if (imp) {
|
||||
const ImprovementConfig *iCfg = TerrainYieldManager::instance().getImprovementConfig(imp->improvementId);
|
||||
if (iCfg && iCfg->yieldBonus.contains(assignedRes)) {
|
||||
amount += iCfg->yieldBonus.value(assignedRes, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (assignedRes == "voedsel") summary.food += amount;
|
||||
else if (assignedRes == "arbeid") summary.labor += amount;
|
||||
else if (assignedRes == "kennis") summary.science += amount;
|
||||
@@ -234,6 +275,88 @@ CityYieldSummary GameManager::calculateCityYields(const City &city, Matrix2D<Ter
|
||||
return summary;
|
||||
}
|
||||
|
||||
void GameManager::processTickYields(Matrix2D<TerrainPoint> *mapPoints, int numPoints05)
|
||||
{
|
||||
const CityConfig &cfg = TerrainYieldManager::instance().getCityConfig();
|
||||
|
||||
for (auto &city : m_cities) {
|
||||
CityYieldSummary yields = calculateCityYields(city, mapPoints, numPoints05);
|
||||
|
||||
// 1. Food is converted into inhabitants:
|
||||
int foodGrown = (int)std::round(yields.food * cfg.foodToInhabitantsRatio);
|
||||
city.inhabitants += foodGrown;
|
||||
|
||||
// 2. Labor is accumulated locally in the city:
|
||||
city.accumulatedLabor += yields.labor;
|
||||
|
||||
// 3. National resources go to the owner player:
|
||||
Player *owner = m_playerManager.getPlayer(city.playerId);
|
||||
if (owner) {
|
||||
owner->culture += yields.culture;
|
||||
owner->science += yields.science;
|
||||
owner->faith += yields.faith;
|
||||
owner->gold += yields.gold;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
bool GameManager::isTileOwned(int tx, int ty, int *outOwnerCityId) const
|
||||
{
|
||||
for (const auto &c : m_cities) {
|
||||
if (c.ownsTile(tx, ty)) {
|
||||
if (outOwnerCityId) *outOwnerCityId = c.id;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GameManager::buyTileForCity(int cityId, int tx, int ty)
|
||||
{
|
||||
City *city = getCityById(cityId);
|
||||
if (!city) return false;
|
||||
|
||||
// Check if tile is already owned by any city
|
||||
int existingOwnerId = -1;
|
||||
if (isTileOwned(tx, ty, &existingOwnerId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const CityConfig &cfg = TerrainYieldManager::instance().getCityConfig();
|
||||
|
||||
// Check distance (must be within max workable radius)
|
||||
int dist = Pathfinder::hexDistance(city->tileX, city->tileY, tx, ty);
|
||||
if (dist > cfg.maxWorkableRadius) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check culture cost
|
||||
Player *player = m_playerManager.getPlayer(city->playerId);
|
||||
if (!player) return false;
|
||||
|
||||
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);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Deduct culture and claim tile
|
||||
player->culture -= cost;
|
||||
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);
|
||||
return true;
|
||||
}
|
||||
|
||||
void GameManager::commandMoveSelected(int targetX, int targetY, Matrix2D<TerrainPoint> *mapPoints, int numPoints05)
|
||||
{
|
||||
Unit *selected = getSelectedUnit();
|
||||
@@ -243,13 +366,31 @@ void GameManager::commandMoveSelected(int targetX, int targetY, Matrix2D<Terrain
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if target tile is passable for this unit's movement domain
|
||||
if (!TerrainMovementManager::instance().isHexPassable(
|
||||
targetX, targetY, selected->config.movementDomain, mapPoints, numPoints05, &selected->config)) {
|
||||
if (selected->isNaval()) {
|
||||
addFloatingText(Localization::instance().get("ui.unit.naval_only_water", "Ships can only sail on water!"),
|
||||
selected->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 100, 100), 2.5f);
|
||||
} else {
|
||||
addFloatingText(Localization::instance().get("ui.unit.impassable_terrain", "Impassable terrain!"),
|
||||
selected->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 100, 100), 2.5f);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<std::pair<int, int>> path = Pathfinder::findPath(
|
||||
selected->tileX, selected->tileY, targetX, targetY, mapPoints, numPoints05
|
||||
selected->tileX, selected->tileY, targetX, targetY, mapPoints, numPoints05,
|
||||
selected->config.movementDomain, &selected->config
|
||||
);
|
||||
|
||||
if (path.size() > 1) {
|
||||
selected->setPath(path);
|
||||
addFloatingText("Verplaatsen", selected->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 230, 80));
|
||||
addFloatingText(Localization::instance().get("ui.unit.moving", "Moving"),
|
||||
selected->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 230, 80));
|
||||
} else {
|
||||
addFloatingText(Localization::instance().get("ui.unit.no_path", "No reachable path!"),
|
||||
selected->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 100, 100), 2.5f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,3 +459,100 @@ void GameManager::addFloatingText(const QString &text, const QVector3D &worldPos
|
||||
{
|
||||
m_floatingTexts.push_back({text, worldPos, duration, duration, color});
|
||||
}
|
||||
|
||||
const TileImprovement* GameManager::getImprovementAt(int tx, int ty) const
|
||||
{
|
||||
auto it = m_tileImprovements.find({tx, ty});
|
||||
if (it != m_tileImprovements.end()) {
|
||||
return &it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool GameManager::hasImprovementAt(int tx, int ty) const
|
||||
{
|
||||
return m_tileImprovements.find({tx, ty}) != m_tileImprovements.end();
|
||||
}
|
||||
|
||||
void GameManager::commandBuildFarmSelected(Matrix2D<TerrainPoint> *mapPoints, int numPoints05)
|
||||
{
|
||||
Unit *selected = getSelectedUnit();
|
||||
if (!selected || !selected->config.canBuildFarm()) return;
|
||||
|
||||
if (selected->isBuilding()) {
|
||||
addFloatingText("Al bezig met bouwen!", selected->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 200, 70));
|
||||
return;
|
||||
}
|
||||
|
||||
int tx = selected->tileX;
|
||||
int ty = selected->tileY;
|
||||
|
||||
// Check if tile already has an improvement
|
||||
if (hasImprovementAt(tx, ty)) {
|
||||
addFloatingText("Er staat hier al een boerderij!", selected->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 90, 90));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check terrain type against allowed terrains loaded from improvements.yaml
|
||||
QString tType = TerrainYieldManager::instance().getTerrainType(tx, ty, mapPoints, numPoints05);
|
||||
const ImprovementConfig *farmCfg = TerrainYieldManager::instance().getImprovementConfig("farm");
|
||||
QStringList validTerrains = farmCfg ? farmCfg->validTerrains : QStringList{"grasland", "tundra"};
|
||||
|
||||
if (!validTerrains.contains(tType)) {
|
||||
addFloatingText("Kan hier niet bouwen! Alleen op gras en toendra.", selected->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 80, 80));
|
||||
return;
|
||||
}
|
||||
|
||||
// Start building process (duration comes from the builder unit's action speed)
|
||||
float duration = selected->config.getActionDuration("boerderij_bouwen", 10.0f);
|
||||
selected->startBuilding("farm", duration);
|
||||
addFloatingText("Boerderij in aanbouw...", selected->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 220, 80), 2.0f);
|
||||
}
|
||||
|
||||
void GameManager::completeUnitBuilding(Unit &unit, Matrix2D<TerrainPoint> *mapPoints, int numPoints05)
|
||||
{
|
||||
int tx = unit.tileX;
|
||||
int ty = unit.tileY;
|
||||
QString impId = unit.buildingImprovementId.isEmpty() ? "farm" : unit.buildingImprovementId;
|
||||
|
||||
if (hasImprovementAt(tx, ty)) {
|
||||
unit.cancelBuilding();
|
||||
return;
|
||||
}
|
||||
|
||||
// Place improvement now that it is completed!
|
||||
QVector2D w = Pathfinder::hexToWorld(tx, ty);
|
||||
float gz = Unit::getTerrainHeight(w.x(), w.y(), mapPoints, numPoints05);
|
||||
|
||||
TileImprovement imp;
|
||||
imp.improvementId = impId;
|
||||
imp.tileX = tx;
|
||||
imp.tileY = ty;
|
||||
imp.playerId = unit.playerId;
|
||||
imp.worldPos = QVector3D(w.x(), w.y(), gz);
|
||||
m_tileImprovements[{tx, ty}] = imp;
|
||||
|
||||
unit.cancelBuilding();
|
||||
|
||||
if (!unit.config.infiniteCharges && unit.buildCharges > 0) {
|
||||
unit.buildCharges -= 1;
|
||||
if (unit.buildCharges <= 0) {
|
||||
addFloatingText("Boerderij voltooid! (Builder verbruikt)", QVector3D(w.x(), w.y(), gz + 1.2f), QColor(80, 255, 120), 2.8f);
|
||||
unit.takeDamage(99999.0f);
|
||||
if (unit.id == m_selectedUnitId) {
|
||||
m_selectedUnitId = -1;
|
||||
m_actionMode = ActionMode::None;
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
QString msg = QString("Boerderij voltooid! (%1 %2 over)")
|
||||
.arg(unit.buildCharges)
|
||||
.arg(unit.buildCharges == 1 ? "lading" : "ladingen");
|
||||
addFloatingText(msg, QVector3D(w.x(), w.y(), gz + 1.2f), QColor(80, 255, 120), 2.8f);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
addFloatingText("Boerderij voltooid!", QVector3D(w.x(), w.y(), gz + 1.2f), QColor(80, 255, 120), 2.8f);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user