#include "unit.h" #include "pathfinder.h" #include "terrainmovementmanager.h" #include #include Unit::Unit(int uId, int pId, const UnitConfig &cfg, int startX, int startY, float groundZ) : id(uId), playerId(pId), config(cfg), tileX(startX), tileY(startY), health((float)cfg.health), maxHealth((float)cfg.health), buildCharges(cfg.buildCharges), state(UnitState::Idle), pathIndex(0), moveProgress(0.0f), targetTileX(startX), targetTileY(startY), attackFlashTimer(0.0f), buildProgress(0.0f), buildDuration(0.0f), buildingImprovementId("") { QVector2D w = Pathfinder::hexToWorld(startX, startY); worldPos = QVector3D(w.x(), w.y(), groundZ); } float Unit::getTerrainHeight(float worldX, float worldY, Matrix2D *mapPoints, int numPoints05) { if (!mapPoints || numPoints05 <= 0) return 1.0f; float scaleX = numPoints05 * 2.0f; float scaleY = numPoints05 / LOD_0_SIZE_Y; int mapX = (int) std::round(worldX * scaleX); int mapY = (int) std::round(worldY * scaleY); if (mapX < 0) mapX = 0; if (mapX >= (int)mapPoints->getWidth()) mapX = (int)mapPoints->getWidth() - 1; if (mapY < 0) mapY = 0; if (mapY >= (int)mapPoints->getHeight()) mapY = (int)mapPoints->getHeight() - 1; unsigned char h = mapPoints->operator()(mapX, mapY).height; return (h / 255.0f) * 3.5f + 1.0f; } bool Unit::queueAction(const UnitAction &action) { if ((int)actionQueue.size() >= maxQueueSize) { return false; } actionQueue.push_back(action); return true; } void Unit::clearActionQueue() { actionQueue.clear(); } bool Unit::hasQueuedActionType(UnitActionType type) const { for (const auto &act : actionQueue) { if (act.type == type) return true; } return false; } void Unit::removeQueuedActionsOfType(UnitActionType type) { for (auto it = actionQueue.begin(); it != actionQueue.end(); ) { if (it->type == type) { it = actionQueue.erase(it); } else { ++it; } } } UnitAction Unit::popNextQueuedAction() { if (actionQueue.empty()) return UnitAction(); UnitAction act = actionQueue.front(); actionQueue.pop_front(); return act; } const UnitAction* Unit::peekNextQueuedAction() const { if (actionQueue.empty()) return nullptr; return &actionQueue.front(); } QString Unit::getQueuedActionSummary() const { if (actionQueue.empty()) return ""; switch (actionQueue.front().type) { case UnitActionType::Move: return "Move"; case UnitActionType::BuildImprovement: return "Build Farm"; case UnitActionType::BuildCity: return "Found City"; case UnitActionType::BuildRoad: return "Build Road"; case UnitActionType::Attack: return "Attack"; default: return "Action"; } } void Unit::setPath(const std::vector> &newPath) { path = newPath; pathIndex = 0; moveProgress = 0.0f; if (path.size() > 1) { state = UnitState::Moving; targetTileX = path.back().first; targetTileY = path.back().second; } else { state = UnitState::Idle; } } void Unit::stopMovement() { path.clear(); pathIndex = 0; moveProgress = 0.0f; state = UnitState::Idle; clearActionQueue(); clearRoadRoute(); } void Unit::startBuilding(const QString &impId, float duration) { path.clear(); pathIndex = 0; moveProgress = 0.0f; state = UnitState::Building; buildingImprovementId = impId; buildDuration = std::max(0.1f, duration); buildProgress = 0.0f; } void Unit::cancelBuilding() { if (state == UnitState::Building) { state = UnitState::Idle; buildProgress = 0.0f; buildDuration = 0.0f; buildingImprovementId = ""; } } void Unit::update(float deltaTime, float tickDuration, float simSpeedMultiplier, Matrix2D *mapPoints, int numPoints05) { if (attackFlashTimer > 0.0f) { attackFlashTimer = std::max(0.0f, attackFlashTimer - deltaTime); } if (state == UnitState::Building) { if (buildDuration > 0.001f) { buildProgress += (deltaTime * simSpeedMultiplier) / buildDuration; } return; } if (state != UnitState::Moving || path.size() <= 1) { return; } // Current segment movement step cost (considering terrain type and slope) float stepCost = 1.0f; if (pathIndex + 1 < path.size()) { int fromX = path[pathIndex].first; int fromY = path[pathIndex].second; int toX = path[pathIndex + 1].first; int toY = path[pathIndex + 1].second; stepCost = TerrainMovementManager::instance().calculateStepCost( fromX, fromY, toX, toY, config.movementDomain, mapPoints, numPoints05, &config ); if (stepCost <= 0.05f) stepCost = 1.0f; } // Speed in hexes per second: movementSpeed points per tickDuration seconds divided by stepCost float effectiveTickDuration = std::max(0.1f, tickDuration); float hexesPerSec = (config.movementSpeed / (effectiveTickDuration * stepCost)) * simSpeedMultiplier; moveProgress += deltaTime * hexesPerSec; while (moveProgress >= 1.0f && pathIndex + 1 < path.size()) { moveProgress -= 1.0f; pathIndex++; tileX = path[pathIndex].first; tileY = path[pathIndex].second; } if (pathIndex + 1 >= path.size()) { // Reached destination tileX = path.back().first; tileY = path.back().second; QVector2D dest = Pathfinder::hexToWorld(tileX, tileY); float gz = getTerrainHeight(dest.x(), dest.y(), mapPoints, numPoints05); worldPos = QVector3D(dest.x(), dest.y(), gz); state = UnitState::Idle; path.clear(); moveProgress = 0.0f; return; } // Smoothly interpolate between current waypoint and next waypoint QVector2D from = Pathfinder::hexToWorld(path[pathIndex].first, path[pathIndex].second); QVector2D to = Pathfinder::hexToWorld(path[pathIndex + 1].first, path[pathIndex + 1].second); float t = std::max(0.0f, std::min(1.0f, moveProgress)); float curX = from.x() + t * (to.x() - from.x()); float curY = from.y() + t * (to.y() - from.y()); float curZ = getTerrainHeight(curX, curY, mapPoints, numPoints05); worldPos = QVector3D(curX, curY, curZ); } bool Unit::canAttack(const Unit &other) const { if (other.playerId == playerId) return false; if (other.isDead() || isDead()) return false; if (!config.canAttack()) return false; int dist = Pathfinder::hexDistance(tileX, tileY, other.tileX, other.tileY); return dist <= config.attackRange; } void Unit::performAttack(Unit &target) { if (!canAttack(target)) return; attackFlashTimer = 0.5f; // Visual flash duration target.takeDamage((float)config.attackDamage); // Retaliation if target is adjacent melee military unit if (!target.isDead() && target.config.isMilitary() && !config.isRanged()) { int dist = Pathfinder::hexDistance(tileX, tileY, target.tileX, target.tileY); if (dist <= target.config.attackRange) { takeDamage(target.config.attackDamage * 0.5f); // 50% counterattack damage } } } void Unit::takeDamage(float damage) { health = std::max(0.0f, health - damage); attackFlashTimer = 0.4f; }