223 lines
7.2 KiB
C++
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);
|
|
}
|