units
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
#include "unit.h"
|
||||
#include "pathfinder.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),
|
||||
state(UnitState::Idle), pathIndex(0), moveProgress(0.0f),
|
||||
targetTileX(startX), targetTileY(startY), attackFlashTimer(0.0f)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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::Moving || path.size() <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Speed in hexes per second: movementSpeed points per tickDuration seconds
|
||||
float effectiveTickDuration = std::max(0.1f, tickDuration);
|
||||
float hexesPerSec = (config.movementSpeed / effectiveTickDuration) * 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;
|
||||
}
|
||||
Reference in New Issue
Block a user