Files
Ascent-of-Man/terrainmovementmanager.cpp
T
2026-11-08 17:06:47 +01:00

223 lines
7.2 KiB
C++

#include "terrainmovementmanager.h"
#include "unitconfig.h"
#include "pathfinder.h"
#include "yamlparser.h"
#include <QDebug>
#include <cmath>
#include <algorithm>
TerrainMovementConfig::TerrainMovementConfig()
: waterHeightThreshold(6),
mountainRockHeightKm(2.3f),
snowHeightKm(3.1f),
polarSnowLineEnabled(true),
polarSnowLineBaseKm(2.5f),
polarSnowFactorStart(0.40f),
polarSnowFactorEnd(0.85f)
{
movementCosts["grasland"] = 1.0f;
movementCosts["bos"] = 2.0f;
movementCosts["steen"] = 2.5f;
movementCosts["sahara"] = 1.5f;
movementCosts["tundra"] = 1.5f;
movementCosts["red_canyon"] = 2.0f;
movementCosts["sneeuw"] = 3.0f;
movementCosts["water"] = 1.0f;
DomainMovementRule landRule;
landRule.impassableTerrains = QStringList{"water", "sneeuw"};
domainRules["land"] = landRule;
DomainMovementRule navalRule;
navalRule.passableTerrains = QStringList{"water"};
domainRules["naval"] = navalRule;
}
TerrainMovementManager& TerrainMovementManager::instance()
{
static TerrainMovementManager s_instance;
return s_instance;
}
TerrainMovementManager::TerrainMovementManager()
: m_loaded(false)
{
}
bool TerrainMovementManager::loadConfig(const QString &filePath)
{
YamlNode root = YamlParser::parseFile(filePath);
if (root.children.isEmpty()) {
qWarning() << "Failed to parse terrain movement config YAML or empty:" << filePath;
return false;
}
if (root.hasChild("water_height_threshold")) {
m_config.waterHeightThreshold = root["water_height_threshold"].asInt(6);
}
if (root.hasChild("mountain_rock_height_km")) {
m_config.mountainRockHeightKm = root["mountain_rock_height_km"].asFloat(2.3f);
}
if (root.hasChild("snow_height_km")) {
m_config.snowHeightKm = root["snow_height_km"].asFloat(3.1f);
}
if (root.hasChild("polar_snow_line_enabled")) {
m_config.polarSnowLineEnabled = root["polar_snow_line_enabled"].asBool(true);
}
if (root.hasChild("polar_snow_line_base_km")) {
m_config.polarSnowLineBaseKm = root["polar_snow_line_base_km"].asFloat(2.5f);
}
if (root.hasChild("polar_snow_factor_start")) {
m_config.polarSnowFactorStart = root["polar_snow_factor_start"].asFloat(0.40f);
}
if (root.hasChild("polar_snow_factor_end")) {
m_config.polarSnowFactorEnd = root["polar_snow_factor_end"].asFloat(0.85f);
}
if (root.hasChild("movement_costs")) {
const YamlNode &costsNode = root["movement_costs"];
for (auto it = costsNode.children.begin(); it != costsNode.children.end(); ++it) {
m_config.movementCosts[it.key()] = it.value().asFloat(1.0f);
}
}
if (root.hasChild("domains")) {
const YamlNode &domainsNode = root["domains"];
for (auto it = domainsNode.children.begin(); it != domainsNode.children.end(); ++it) {
QString domainName = it.key().trimmed().toLower();
const YamlNode &dNode = it.value();
DomainMovementRule rule;
if (dNode.hasChild("impassable_terrains")) {
rule.impassableTerrains = dNode["impassable_terrains"].listValues;
}
if (dNode.hasChild("passable_terrains")) {
rule.passableTerrains = dNode["passable_terrains"].listValues;
}
m_config.domainRules[domainName] = rule;
}
}
m_loaded = true;
qDebug() << "TerrainMovementManager successfully loaded movement config from:" << filePath;
return true;
}
QString TerrainMovementManager::getTerrainType(int tx, int ty, Matrix2D<TerrainPoint> *mapPoints, int numPoints05) const
{
if (!mapPoints || numPoints05 <= 0) return "grasland";
QVector2D w = Pathfinder::hexToWorld(tx, ty);
float scaleX = numPoints05 * 2.0f;
float scaleY = numPoints05 / LOD_0_SIZE_Y;
int mapX = (int) std::round(w.x() * scaleX);
int mapY = (int) std::round(w.y() * 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;
TerrainPoint tp = mapPoints->operator()(mapX, mapY);
// 1. Water
if (tp.height < m_config.waterHeightThreshold) {
return "water";
}
float heightKm = (tp.height / 255.0f) * 3.5f + 1.0f;
// 2. High peak snow
if (heightKm >= m_config.snowHeightKm) {
return "sneeuw";
}
// 3. Polar latitude snowline
if (m_config.polarSnowLineEnabled) {
float normY = std::max(0.0f, std::min(1.0f, w.y() / 108.25f));
float distFromCenter = std::abs(normY - 0.5f) * 2.0f;
if (distFromCenter >= m_config.polarSnowFactorStart) {
float t = std::max(0.0f, std::min(1.0f,
(distFromCenter - m_config.polarSnowFactorStart) /
(m_config.polarSnowFactorEnd - m_config.polarSnowFactorStart)));
float poleFactor = t * t * (3.0f - 2.0f * t);
float snowLine = m_config.polarSnowLineBaseKm * (1.0f - poleFactor);
if (heightKm >= snowLine) {
return "sneeuw";
}
}
}
// 4. Rocky mountain
if (heightKm >= m_config.mountainRockHeightKm) {
return "steen";
}
// 5. Typemap biomes
switch (tp.type) {
case 0: return "grasland";
case 1: return "sahara";
case 2: return "tundra";
case 3: return "red_canyon";
case 4: return "bos";
default: return "grasland";
}
}
bool TerrainMovementManager::isTerrainPassable(
const QString &terrainType,
MovementDomain domain,
const UnitConfig *unitConfig) const
{
// Unit-specific whitelist overrides
if (unitConfig && !unitConfig->passableTerrains.isEmpty()) {
return unitConfig->passableTerrains.contains(terrainType);
}
// Unit-specific blacklist overrides
if (unitConfig && unitConfig->impassableTerrains.contains(terrainType)) {
return false;
}
// Domain-level rules
QString domainStr = movementDomainToString(domain);
auto it = m_config.domainRules.find(domainStr);
if (it != m_config.domainRules.end()) {
const DomainMovementRule &rule = it.value();
if (!rule.passableTerrains.isEmpty()) {
return rule.passableTerrains.contains(terrainType);
}
if (rule.impassableTerrains.contains(terrainType)) {
return false;
}
}
return true;
}
bool TerrainMovementManager::isHexPassable(
int tx, int ty,
MovementDomain domain,
Matrix2D<TerrainPoint> *mapPoints,
int numPoints05,
const UnitConfig *unitConfig) const
{
if (tx < 0 || tx >= MAP_WIDTH || ty < 0 || ty >= MAP_HEIGHT) {
return false;
}
QString tType = getTerrainType(tx, ty, mapPoints, numPoints05);
return isTerrainPassable(tType, domain, unitConfig);
}
float TerrainMovementManager::getTerrainMovementCost(const QString &terrainType) const
{
return m_config.movementCosts.value(terrainType, 1.0f);
}
float TerrainMovementManager::getHexMovementCost(int tx, int ty, Matrix2D<TerrainPoint> *mapPoints, int numPoints05) const
{
QString tType = getTerrainType(tx, ty, mapPoints, numPoints05);
return getTerrainMovementCost(tType);
}