Browse Source

fix spawn type being tied to strings instead of enums

djeada 1 month ago
parent
commit
83925f9d62

+ 12 - 7
game/map/level_loader.cpp

@@ -68,10 +68,11 @@ LevelLoadResult LevelLoader::loadFromAssets(const QString &mapPath,
         Game::Units::SpawnParams sp;
         sp.position = QVector3D(0.0f, 0.0f, 0.0f);
         sp.playerId = 0;
-        sp.unitType = "archer";
+        sp.spawnType = Game::Units::SpawnType::Archer;
+        sp.unitType = Game::Units::spawnTypeToString(sp.spawnType);
         sp.aiControlled = !owners.isPlayer(sp.playerId);
         if (auto unit =
-                reg->create(Game::Units::TroopType::Archer, world, sp)) {
+                reg->create(Game::Units::SpawnType::Archer, world, sp)) {
           res.playerUnitId = unit->id();
         } else {
           qWarning() << "LevelLoader: Fallback archer spawn failed";
@@ -82,7 +83,9 @@ LevelLoadResult LevelLoader::loadFromAssets(const QString &mapPath,
     bool hasBarracks = false;
     for (auto *e : world.getEntitiesWith<Engine::Core::UnitComponent>()) {
       if (auto *u = e->getComponent<Engine::Core::UnitComponent>()) {
-        if (u->unitType == "barracks" && owners.isPlayer(u->ownerId)) {
+        if (u->unitType ==
+                Game::Units::spawnTypeToString(Game::Units::SpawnType::Barracks) &&
+            owners.isPlayer(u->ownerId)) {
           hasBarracks = true;
           break;
         }
@@ -94,9 +97,10 @@ LevelLoadResult LevelLoader::loadFromAssets(const QString &mapPath,
         Game::Units::SpawnParams sp;
         sp.position = QVector3D(-4.0f, 0.0f, -3.0f);
         sp.playerId = owners.getLocalPlayerId();
-        sp.unitType = "barracks";
+        sp.spawnType = Game::Units::SpawnType::Barracks;
+        sp.unitType = Game::Units::spawnTypeToString(sp.spawnType);
         sp.aiControlled = !owners.isPlayer(sp.playerId);
-        reg2->create("barracks", world, sp);
+        reg2->create(Game::Units::SpawnType::Barracks, world, sp);
       }
     }
   } else {
@@ -118,9 +122,10 @@ LevelLoadResult LevelLoader::loadFromAssets(const QString &mapPath,
       Game::Units::SpawnParams sp;
       sp.position = QVector3D(0.0f, 0.0f, 0.0f);
       sp.playerId = 0;
-      sp.unitType = "archer";
+      sp.spawnType = Game::Units::SpawnType::Archer;
+      sp.unitType = Game::Units::spawnTypeToString(sp.spawnType);
       sp.aiControlled = !owners.isPlayer(sp.playerId);
-      if (auto unit = reg->create(Game::Units::TroopType::Archer, world, sp)) {
+      if (auto unit = reg->create(Game::Units::SpawnType::Archer, world, sp)) {
         res.playerUnitId = unit->id();
       }
     }

+ 2 - 1
game/map/map_definition.h

@@ -1,5 +1,6 @@
 #pragma once
 
+#include "../units/spawn_type.h"
 #include "terrain.h"
 #include <QString>
 #include <QVector3D>
@@ -26,7 +27,7 @@ struct CameraDefinition {
 };
 
 struct UnitSpawn {
-  QString type;
+  Game::Units::SpawnType type = Game::Units::SpawnType::Archer;
   float x = 0.0f;
   float z = 0.0f;
   int playerId = 0;

+ 6 - 1
game/map/map_loader.cpp

@@ -1,5 +1,6 @@
 #include "map_loader.h"
 
+#include <QDebug>
 #include <QFile>
 #include <QJsonArray>
 #include <QJsonDocument>
@@ -187,7 +188,11 @@ static void readSpawns(const QJsonArray &arr, std::vector<UnitSpawn> &out) {
   for (const auto &v : arr) {
     auto o = v.toObject();
     UnitSpawn s;
-    s.type = o.value("type").toString();
+    const QString typeStr = o.value("type").toString();
+    if (!Game::Units::tryParseSpawnType(typeStr, s.type)) {
+      qWarning() << "MapLoader: unknown spawn type" << typeStr << "- skipping";
+      continue;
+    }
     s.x = float(o.value("x").toDouble(0.0));
     s.z = float(o.value("z").toDouble(0.0));
 

+ 17 - 63
game/map/map_transformer.cpp

@@ -5,6 +5,7 @@
 #include "../core/world.h"
 #include "../systems/owner_registry.h"
 #include "../units/factory.h"
+#include "../units/spawn_type.h"
 #include "../visuals/team_colors.h"
 #include "terrain_service.h"
 #include <QDebug>
@@ -145,80 +146,32 @@ MapTransformer::applyToWorld(const MapDefinition &def,
       Game::Units::SpawnParams sp;
       sp.position = QVector3D(worldX, 0.0f, worldZ);
       sp.playerId = s.playerId;
-      sp.unitType = s.type.toStdString();
+      sp.spawnType = s.type;
+      sp.unitType = Game::Units::spawnTypeToString(s.type);
       sp.aiControlled = !ownerRegistry.isPlayer(s.playerId);
       sp.maxPopulation = s.maxPopulation;
-      auto obj = s_registry->create(s.type.toStdString(), world, sp);
+      auto obj = s_registry->create(s.type, world, sp);
       if (obj) {
         e = world.getEntity(obj->id());
         rt.unitIds.push_back(obj->id());
-      }
-    }
-    if (!e) {
-
-      e = world.createEntity();
-      if (!e)
-        continue;
-      auto *t = e->addComponent<Engine::Core::TransformComponent>();
-      t->position = {worldX, 0.0f, worldZ};
-      t->scale = {0.5f, 0.5f, 0.5f};
-      auto *r = e->addComponent<Engine::Core::RenderableComponent>("", "");
-      r->visible = true;
-      auto *u = e->addComponent<Engine::Core::UnitComponent>();
-      u->unitType = s.type.toStdString();
-      u->ownerId = s.playerId;
-      u->visionRange = 14.0f;
-
-      bool isAI = !ownerRegistry.isPlayer(s.playerId);
-      if (isAI) {
-        e->addComponent<Engine::Core::AIControlledComponent>();
       } else {
+        qWarning() << "MapTransformer: no factory for spawn type"
+                   << Game::Units::spawnTypeToQString(s.type)
+                   << "- skipping spawn at" << worldX << worldZ;
+        continue;
       }
-
-      if (auto *existingMv =
-              e->getComponent<Engine::Core::MovementComponent>()) {
-        existingMv->goalX = worldX;
-        existingMv->goalY = worldZ;
-        existingMv->targetX = worldX;
-        existingMv->targetY = worldZ;
-      } else if (auto *mv =
-                     e->addComponent<Engine::Core::MovementComponent>()) {
-        mv->goalX = worldX;
-        mv->goalY = worldZ;
-        mv->targetX = worldX;
-        mv->targetY = worldZ;
-      }
-
-      QVector3D tc = Game::Visuals::teamColorForOwner(u->ownerId);
-      r->color[0] = tc.x();
-      r->color[1] = tc.y();
-      r->color[2] = tc.z();
-      if (s.type == "archer") {
-        u->health = 80;
-        u->maxHealth = 80;
-        u->speed = 3.0f;
-        u->visionRange = 16.0f;
-        auto *atk = e->addComponent<Engine::Core::AttackComponent>();
-        atk->range = 6.0f;
-        atk->damage = 12;
-        atk->cooldown = 1.2f;
-      }
-      if (!e->getComponent<Engine::Core::MovementComponent>()) {
-        auto *mv = e->addComponent<Engine::Core::MovementComponent>();
-        if (mv) {
-          mv->goalX = worldX;
-          mv->goalY = worldZ;
-          mv->targetX = worldX;
-          mv->targetY = worldZ;
-        }
-      }
-      rt.unitIds.push_back(e->getId());
+    } else {
+      qWarning() << "MapTransformer: no factory registry set; skipping spawn";
+      continue;
     }
 
+    if (!e)
+      continue;
+
     if (auto *r = e->getComponent<Engine::Core::RenderableComponent>()) {
       if (visuals) {
         Game::Visuals::VisualDef defv;
-        if (visuals->lookup(s.type.toStdString(), defv)) {
+        if (visuals->lookup(Game::Units::spawnTypeToString(s.type), defv)) {
           Game::Visuals::applyToRenderable(defv, *r);
         }
       }
@@ -228,7 +181,8 @@ MapTransformer::applyToWorld(const MapDefinition &def,
     }
 
     if (auto *t = e->getComponent<Engine::Core::TransformComponent>()) {
-      qInfo() << "Spawned" << s.type << "id=" << e->getId() << "at"
+      qInfo() << "Spawned" << Game::Units::spawnTypeToQString(s.type)
+              << "id=" << e->getId() << "at"
               << QVector3D(t->position.x, t->position.y, t->position.z)
               << "(coordSystem="
               << (def.coordSystem == CoordSystem::Grid ? "Grid" : "World")

+ 3 - 2
game/systems/production_system.cpp

@@ -62,10 +62,11 @@ void ProductionSystem::update(Engine::Core::World *world, float deltaTime) {
           Game::Units::SpawnParams sp;
           sp.position = exitPos;
           sp.playerId = u->ownerId;
-          sp.unitType = Game::Units::troopTypeToString(prod->productType);
+          sp.spawnType = Game::Units::spawnTypeFromTroopType(prod->productType);
+          sp.unitType = Game::Units::spawnTypeToString(sp.spawnType);
           sp.aiControlled =
               e->hasComponent<Engine::Core::AIControlledComponent>();
-          auto unit = reg->create(prod->productType, *world, sp);
+          auto unit = reg->create(sp.spawnType, *world, sp);
 
           if (unit && prod->rallySet) {
             unit->moveTo(prod->rallyX, prod->rallyZ);

+ 24 - 20
game/units/factory.cpp

@@ -9,26 +9,30 @@ namespace Game {
 namespace Units {
 
 void registerBuiltInUnits(UnitFactoryRegistry &reg) {
-  reg.registerFactory(TroopType::Archer, [](Engine::Core::World &world,
-                                            const SpawnParams &params) {
-    return Archer::Create(world, params);
-  });
-  reg.registerFactory(TroopType::Knight, [](Engine::Core::World &world,
-                                            const SpawnParams &params) {
-    return Knight::Create(world, params);
-  });
-  reg.registerFactory(TroopType::MountedKnight, [](Engine::Core::World &world,
-                                                   const SpawnParams &params) {
-    return MountedKnight::Create(world, params);
-  });
-  reg.registerFactory(TroopType::Spearman, [](Engine::Core::World &world,
-                                              const SpawnParams &params) {
-    return Spearman::Create(world, params);
-  });
-  reg.registerFactory(
-      "barracks", [](Engine::Core::World &world, const SpawnParams &params) {
-        return Barracks::Create(world, params);
-      });
+  reg.registerFactory(SpawnType::Archer,
+                      [](Engine::Core::World &world, const SpawnParams &params) {
+                        return Archer::Create(world, params);
+                      });
+
+  reg.registerFactory(SpawnType::Knight,
+                      [](Engine::Core::World &world, const SpawnParams &params) {
+                        return Knight::Create(world, params);
+                      });
+
+  reg.registerFactory(SpawnType::MountedKnight,
+                      [](Engine::Core::World &world, const SpawnParams &params) {
+                        return MountedKnight::Create(world, params);
+                      });
+
+  reg.registerFactory(SpawnType::Spearman,
+                      [](Engine::Core::World &world, const SpawnParams &params) {
+                        return Spearman::Create(world, params);
+                      });
+
+  reg.registerFactory(SpawnType::Barracks,
+                      [](Engine::Core::World &world, const SpawnParams &params) {
+                        return Barracks::Create(world, params);
+                      });
 }
 
 } // namespace Units

+ 10 - 18
game/units/factory.h

@@ -1,7 +1,7 @@
 #pragma once
 
-#include "../units/troop_type.h"
 #include "unit.h"
+#include "spawn_type.h"
 #include <functional>
 #include <memory>
 #include <string>
@@ -15,34 +15,26 @@ public:
   using Factory = std::function<std::unique_ptr<Unit>(Engine::Core::World &,
                                                       const SpawnParams &)>;
 
-  void registerFactory(TroopType type, Factory f) {
-    m_enumMap[type] = std::move(f);
+  void registerFactory(SpawnType type, Factory f) {
+    m_factories[type] = std::move(f);
   }
 
-  void registerFactory(const std::string &type, Factory f) {
-    m_stringMap[type] = std::move(f);
-  }
-
-  std::unique_ptr<Unit> create(TroopType type, Engine::Core::World &world,
+  std::unique_ptr<Unit> create(SpawnType type, Engine::Core::World &world,
                                const SpawnParams &params) const {
-    auto it = m_enumMap.find(type);
-    if (it == m_enumMap.end())
+    auto it = m_factories.find(type);
+    if (it == m_factories.end())
       return nullptr;
     return it->second(world, params);
   }
 
-  std::unique_ptr<Unit> create(const std::string &type,
-                               Engine::Core::World &world,
+  std::unique_ptr<Unit> create(TroopType type, Engine::Core::World &world,
                                const SpawnParams &params) const {
-    auto it = m_stringMap.find(type);
-    if (it == m_stringMap.end())
-      return nullptr;
-    return it->second(world, params);
+    const SpawnType spawnType = spawnTypeFromTroopType(type);
+    return create(spawnType, world, params);
   }
 
 private:
-  std::unordered_map<TroopType, Factory> m_enumMap;
-  std::unordered_map<std::string, Factory> m_stringMap;
+  std::unordered_map<SpawnType, Factory> m_factories;
 };
 
 void registerBuiltInUnits(UnitFactoryRegistry &reg);

+ 1 - 1
game/units/mounted_knight.cpp

@@ -49,7 +49,7 @@ void MountedKnight::init(const SpawnParams &params) {
   m_u->unitType = m_typeString;
   m_u->health = 200;
   m_u->maxHealth = 200;
-  m_u->speed = 30.f;
+  m_u->speed = 8.0f;
   m_u->ownerId = params.playerId;
   m_u->visionRange = 16.0f;
 

+ 99 - 0
game/units/spawn_type.h

@@ -0,0 +1,99 @@
+#pragma once
+
+#include "troop_type.h"
+#include <QString>
+#include <optional>
+#include <string>
+
+namespace Game::Units {
+
+enum class SpawnType { Archer, Knight, Spearman, MountedKnight, Barracks };
+
+inline QString spawnTypeToQString(SpawnType type) {
+  switch (type) {
+  case SpawnType::Archer:
+    return QStringLiteral("archer");
+  case SpawnType::Knight:
+    return QStringLiteral("knight");
+  case SpawnType::Spearman:
+    return QStringLiteral("spearman");
+  case SpawnType::MountedKnight:
+    return QStringLiteral("mounted_knight");
+  case SpawnType::Barracks:
+    return QStringLiteral("barracks");
+  }
+  return QStringLiteral("archer");
+}
+
+inline std::string spawnTypeToString(SpawnType type) {
+  return spawnTypeToQString(type).toStdString();
+}
+
+inline bool tryParseSpawnType(const QString &value, SpawnType &out) {
+  const QString lowered = value.trimmed().toLower();
+  if (lowered == QStringLiteral("archer")) {
+    out = SpawnType::Archer;
+    return true;
+  }
+  if (lowered == QStringLiteral("knight")) {
+    out = SpawnType::Knight;
+    return true;
+  }
+  if (lowered == QStringLiteral("spearman")) {
+    out = SpawnType::Spearman;
+    return true;
+  }
+  if (lowered == QStringLiteral("mounted_knight")) {
+    out = SpawnType::MountedKnight;
+    return true;
+  }
+  if (lowered == QStringLiteral("barracks")) {
+    out = SpawnType::Barracks;
+    return true;
+  }
+  return false;
+}
+
+inline bool isTroopSpawn(SpawnType type) {
+  return type != SpawnType::Barracks;
+}
+
+inline std::optional<TroopType> spawnTypeToTroopType(SpawnType type) {
+  switch (type) {
+  case SpawnType::Archer:
+    return TroopType::Archer;
+  case SpawnType::Knight:
+    return TroopType::Knight;
+  case SpawnType::Spearman:
+    return TroopType::Spearman;
+  case SpawnType::MountedKnight:
+    return TroopType::MountedKnight;
+  case SpawnType::Barracks:
+    return std::nullopt;
+  }
+  return std::nullopt;
+}
+
+inline SpawnType spawnTypeFromTroopType(TroopType type) {
+  switch (type) {
+  case TroopType::Archer:
+    return SpawnType::Archer;
+  case TroopType::Knight:
+    return SpawnType::Knight;
+  case TroopType::Spearman:
+    return SpawnType::Spearman;
+  case TroopType::MountedKnight:
+    return SpawnType::MountedKnight;
+  }
+  return SpawnType::Archer;
+}
+
+} // namespace Game::Units
+
+namespace std {
+template <> struct hash<Game::Units::SpawnType> {
+  size_t operator()(Game::Units::SpawnType type) const noexcept {
+    return hash<int>()(static_cast<int>(type));
+  }
+};
+} // namespace std

+ 21 - 0
game/units/troop_config.h

@@ -38,6 +38,14 @@ public:
     return 0.5f;
   }
 
+  float getSelectionRingYOffset(TroopType unitType) const {
+    auto it = m_selectionRingYOffset.find(unitType);
+    if (it != m_selectionRingYOffset.end()) {
+      return it->second;
+    }
+    return 0.0f;
+  }
+
   int getIndividualsPerUnit(const std::string &unitType) const {
     return getIndividualsPerUnit(troopTypeFromString(unitType));
   }
@@ -50,6 +58,10 @@ public:
     return getSelectionRingSize(troopTypeFromString(unitType));
   }
 
+  float getSelectionRingYOffset(const std::string &unitType) const {
+    return getSelectionRingYOffset(troopTypeFromString(unitType));
+  }
+
   void registerTroopType(TroopType unitType, int individualsPerUnit) {
     m_individualsPerUnit[unitType] = individualsPerUnit;
   }
@@ -62,28 +74,37 @@ public:
     m_selectionRingSize[unitType] = selectionRingSize;
   }
 
+  void registerSelectionRingYOffset(TroopType unitType, float offset) {
+    m_selectionRingYOffset[unitType] = offset;
+  }
+
 private:
   TroopConfig() {
     m_individualsPerUnit[TroopType::Archer] = 20;
     m_maxUnitsPerRow[TroopType::Archer] = 5;
     m_selectionRingSize[TroopType::Archer] = 1.2f;
+    m_selectionRingYOffset[TroopType::Archer] = 0.0f;
 
     m_individualsPerUnit[TroopType::Knight] = 15;
     m_maxUnitsPerRow[TroopType::Knight] = 5;
     m_selectionRingSize[TroopType::Knight] = 1.1f;
+    m_selectionRingYOffset[TroopType::Knight] = 0.0f;
 
     m_individualsPerUnit[TroopType::Spearman] = 24;
     m_maxUnitsPerRow[TroopType::Spearman] = 6;
     m_selectionRingSize[TroopType::Spearman] = 1.4f;
+    m_selectionRingYOffset[TroopType::Spearman] = 0.0f;
 
     m_individualsPerUnit[TroopType::MountedKnight] = 9;
     m_maxUnitsPerRow[TroopType::MountedKnight] = 3;
     m_selectionRingSize[TroopType::MountedKnight] = 2.0f;
+    m_selectionRingYOffset[TroopType::MountedKnight] = 0.0f;
   }
 
   std::unordered_map<TroopType, int> m_individualsPerUnit;
   std::unordered_map<TroopType, int> m_maxUnitsPerRow;
   std::unordered_map<TroopType, float> m_selectionRingSize;
+  std::unordered_map<TroopType, float> m_selectionRingYOffset;
 };
 
 } // namespace Units

+ 2 - 0
game/units/unit.h

@@ -1,5 +1,6 @@
 #pragma once
 
+#include "spawn_type.h"
 #include "troop_type.h"
 #include <QVector3D>
 #include <memory>
@@ -26,6 +27,7 @@ struct SpawnParams {
 
   QVector3D position{0, 0, 0};
   int playerId = 0;
+  SpawnType spawnType = SpawnType::Archer;
   std::string unitType = "archer";
   bool aiControlled = false;
   int maxPopulation = 100;

+ 0 - 1
render/gl/backend.cpp

@@ -853,7 +853,6 @@ void Backend::execute(const DrawQueue &queue, const Camera &cam) {
       m_basicShader->setUniform(m_basicUniforms.color, sc.color);
 
       DepthMaskScope depthMask(false);
-      DepthTestScope depthTest(false);
       PolygonOffsetScope poly(-1.0f, -1.0f);
       BlendScope blend(true);
 

+ 31 - 2
render/humanoid_base.cpp

@@ -2,6 +2,7 @@
 #include "../game/core/component.h"
 #include "../game/core/entity.h"
 #include "../game/core/world.h"
+#include "../game/map/terrain_service.h"
 #include "../game/units/troop_config.h"
 #include "../game/visuals/team_colors.h"
 #include "geom/math_utils.h"
@@ -504,17 +505,45 @@ void HumanoidRendererBase::drawSelectionFX(const DrawContext &ctx,
                                            ISubmitter &out) {
   if (ctx.selected || ctx.hovered) {
     float ringSize = 0.5f;
+    float ringOffset = 0.05f;
     if (ctx.entity) {
       auto *unit = ctx.entity->getComponent<Engine::Core::UnitComponent>();
       if (unit && !unit->unitType.empty()) {
         ringSize = Game::Units::TroopConfig::instance().getSelectionRingSize(
             unit->unitType);
+        ringOffset += Game::Units::TroopConfig::instance()
+                          .getSelectionRingYOffset(unit->unitType);
       }
     }
 
-    QMatrix4x4 ringM;
     QVector3D pos = ctx.model.column(3).toVector3D();
-    ringM.translate(pos.x(), pos.y() + 0.05f, pos.z());
+    float groundY = pos.y();
+    bool haveTransformPosition = false;
+    auto &terrainService = Game::Map::TerrainService::instance();
+    if (terrainService.isInitialized()) {
+      groundY = terrainService.getTerrainHeight(pos.x(), pos.z());
+    } else if (ctx.entity) {
+      if (auto *transform =
+              ctx.entity->getComponent<Engine::Core::TransformComponent>()) {
+        groundY = transform->position.y;
+        pos.setX(transform->position.x);
+        pos.setZ(transform->position.z);
+        haveTransformPosition = true;
+      }
+    }
+
+    if (!haveTransformPosition && ctx.entity) {
+      if (auto *transform =
+              ctx.entity->getComponent<Engine::Core::TransformComponent>()) {
+        pos.setX(transform->position.x);
+        pos.setZ(transform->position.z);
+      }
+    }
+
+    pos.setY(groundY);
+
+    QMatrix4x4 ringM;
+    ringM.translate(pos.x(), pos.y() + ringOffset, pos.z());
     ringM.scale(ringSize, 1.0f, ringSize);
     if (ctx.selected)
       out.selectionRing(ringM, 0.6f, 0.25f, QVector3D(0.2f, 0.4f, 1.0f));