Files
Ascent-of-Man/unit.cpp
T

247 lines
7.2 KiB
C++

#include "unit.h"
#include "pathfinder.h"
#include "terrainmovementmanager.h"
#include <algorithm>
#include <cmath>
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<TerrainPoint> *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::Attack:
return "Attack";
default:
return "Action";
}
}
void Unit::setPath(const std::vector<std::pair<int, int>> &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();
}
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<TerrainPoint> *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;
}