2
0
djeada 1 сар өмнө
parent
commit
2a5647ad28

+ 6 - 10
CMakeLists.txt

@@ -8,16 +8,12 @@ option(ENABLE_CLANG_TIDY "Enable clang-tidy analysis" ON)
 if (ENABLE_CLANG_TIDY AND CLANG_TIDY_EXE)
     message(STATUS "Found clang-tidy: ${CLANG_TIDY_EXE}")
     # Prefer using .clang-tidy config, but define fallback checks for safety
-    set(CMAKE_CXX_CLANG_TIDY
-        ${CLANG_TIDY_EXE};
-        -checks=bugprone-*,
-                performance-*,
-                readability-*,
-                modernize-*,
-                cppcoreguidelines-*,
-                clang-analyzer-*;
-        -warnings-as-errors=*;
-        -header-filter=.*)
+    set(CLANG_TIDY_ARGS "-header-filter=.*")
+    if(NOT EXISTS "${CMAKE_SOURCE_DIR}/.clang-tidy")
+        list(APPEND CLANG_TIDY_ARGS
+            "-checks=bugprone-*,performance-*,readability-*,modernize-*,cppcoreguidelines-*,clang-analyzer-*")
+    endif()
+    set(CMAKE_CXX_CLANG_TIDY ${CLANG_TIDY_EXE} ${CLANG_TIDY_ARGS})
 elseif (ENABLE_CLANG_TIDY)
     message(WARNING "clang-tidy requested but not found! Static analysis skipped.")
 else()

+ 3 - 2
game/core/serialization.cpp

@@ -6,6 +6,7 @@
 #include "component.h"
 #include "entity.h"
 #include "world.h"
+#include <QByteArray>
 #include <QDebug>
 #include <QFile>
 #include <QJsonArray>
@@ -748,9 +749,9 @@ QJsonDocument Serialization::loadFromFile(const QString &filename) {
   QFile file(filename);
   if (!file.open(QIODevice::ReadOnly)) {
     qWarning() << "Could not open file for reading:" << filename;
-    return QJsonDocument();
+    return {};
   }
-  QByteArray data = file.readAll();
+  const QByteArray data = file.readAll();
   return QJsonDocument::fromJson(data);
 }
 

+ 2 - 4
game/core/serialization.h

@@ -4,12 +4,10 @@
 #include <QJsonObject>
 #include <QString>
 
-namespace Game {
-namespace Map {
+namespace Game::Map {
 class TerrainHeightMap;
 struct BiomeSettings;
-} // namespace Map
-} // namespace Game
+} // namespace Game::Map
 
 namespace Engine::Core {
 

+ 7 - 1
game/core/system.cpp

@@ -1,3 +1,9 @@
 #include "system.h"
 
-namespace Engine::Core {}
+namespace Engine::Core {
+namespace {
+[[maybe_unused]] void systemTypeAnchor() {
+  (void)sizeof(System *);
+}
+} // namespace
+} // namespace Engine::Core

+ 5 - 0
game/core/system.h

@@ -8,6 +8,11 @@ class World;
 
 class System {
 public:
+  System() = default;
+  System(const System &) = default;
+  System(System &&) noexcept = default;
+  System &operator=(const System &) = default;
+  System &operator=(System &&) noexcept = default;
   virtual ~System() = default;
   virtual void update(World *world, float deltaTime) = 0;
 };

+ 25 - 25
game/core/world.cpp

@@ -10,7 +10,7 @@ World::World() = default;
 World::~World() = default;
 
 Entity *World::createEntity() {
-  std::lock_guard<std::recursive_mutex> lock(m_entityMutex);
+  const std::lock_guard<std::recursive_mutex> lock(m_entityMutex);
   EntityID id = m_nextEntityId++;
   auto entity = std::make_unique<Entity>(id);
   auto ptr = entity.get();
@@ -18,37 +18,37 @@ Entity *World::createEntity() {
   return ptr;
 }
 
-Entity *World::createEntityWithId(EntityID id) {
-  std::lock_guard<std::recursive_mutex> lock(m_entityMutex);
-  if (id == NULL_ENTITY) {
+Entity *World::createEntityWithId(EntityID entityId) {
+  const std::lock_guard<std::recursive_mutex> lock(m_entityMutex);
+  if (entityId == NULL_ENTITY) {
     return nullptr;
   }
 
-  auto entity = std::make_unique<Entity>(id);
+  auto entity = std::make_unique<Entity>(entityId);
   auto ptr = entity.get();
-  m_entities[id] = std::move(entity);
+  m_entities[entityId] = std::move(entity);
 
-  if (id >= m_nextEntityId) {
-    m_nextEntityId = id + 1;
+  if (entityId >= m_nextEntityId) {
+    m_nextEntityId = entityId + 1;
   }
 
   return ptr;
 }
 
-void World::destroyEntity(EntityID id) {
-  std::lock_guard<std::recursive_mutex> lock(m_entityMutex);
-  m_entities.erase(id);
+void World::destroyEntity(EntityID entityId) {
+  const std::lock_guard<std::recursive_mutex> lock(m_entityMutex);
+  m_entities.erase(entityId);
 }
 
 void World::clear() {
-  std::lock_guard<std::recursive_mutex> lock(m_entityMutex);
+  const std::lock_guard<std::recursive_mutex> lock(m_entityMutex);
   m_entities.clear();
   m_nextEntityId = 1;
 }
 
-Entity *World::getEntity(EntityID id) {
-  std::lock_guard<std::recursive_mutex> lock(m_entityMutex);
-  auto it = m_entities.find(id);
+Entity *World::getEntity(EntityID entityId) {
+  const std::lock_guard<std::recursive_mutex> lock(m_entityMutex);
+  auto it = m_entities.find(entityId);
   return it != m_entities.end() ? it->second.get() : nullptr;
 }
 
@@ -63,10 +63,10 @@ void World::update(float deltaTime) {
 }
 
 std::vector<Entity *> World::getUnitsOwnedBy(int ownerId) const {
-  std::lock_guard<std::recursive_mutex> lock(m_entityMutex);
+  const std::lock_guard<std::recursive_mutex> lock(m_entityMutex);
   std::vector<Entity *> result;
   result.reserve(m_entities.size());
-  for (auto &[id, entity] : m_entities) {
+  for (auto &[entityId, entity] : m_entities) {
     auto *unit = entity->getComponent<UnitComponent>();
     if (!unit)
       continue;
@@ -78,10 +78,10 @@ std::vector<Entity *> World::getUnitsOwnedBy(int ownerId) const {
 }
 
 std::vector<Entity *> World::getUnitsNotOwnedBy(int ownerId) const {
-  std::lock_guard<std::recursive_mutex> lock(m_entityMutex);
+  const std::lock_guard<std::recursive_mutex> lock(m_entityMutex);
   std::vector<Entity *> result;
   result.reserve(m_entities.size());
-  for (auto &[id, entity] : m_entities) {
+  for (auto &[entityId, entity] : m_entities) {
     auto *unit = entity->getComponent<UnitComponent>();
     if (!unit)
       continue;
@@ -93,12 +93,12 @@ std::vector<Entity *> World::getUnitsNotOwnedBy(int ownerId) const {
 }
 
 std::vector<Entity *> World::getAlliedUnits(int ownerId) const {
-  std::lock_guard<std::recursive_mutex> lock(m_entityMutex);
+  const std::lock_guard<std::recursive_mutex> lock(m_entityMutex);
   std::vector<Entity *> result;
   result.reserve(m_entities.size());
   auto &ownerRegistry = Game::Systems::OwnerRegistry::instance();
 
-  for (auto &[id, entity] : m_entities) {
+  for (auto &[entityId, entity] : m_entities) {
     auto *unit = entity->getComponent<UnitComponent>();
     if (!unit)
       continue;
@@ -112,12 +112,12 @@ std::vector<Entity *> World::getAlliedUnits(int ownerId) const {
 }
 
 std::vector<Entity *> World::getEnemyUnits(int ownerId) const {
-  std::lock_guard<std::recursive_mutex> lock(m_entityMutex);
+  const std::lock_guard<std::recursive_mutex> lock(m_entityMutex);
   std::vector<Entity *> result;
   result.reserve(m_entities.size());
   auto &ownerRegistry = Game::Systems::OwnerRegistry::instance();
 
-  for (auto &[id, entity] : m_entities) {
+  for (auto &[entityId, entity] : m_entities) {
     auto *unit = entity->getComponent<UnitComponent>();
     if (!unit)
       continue;
@@ -134,12 +134,12 @@ int World::countTroopsForPlayer(int ownerId) const {
 }
 
 EntityID World::getNextEntityId() const {
-  std::lock_guard<std::recursive_mutex> lock(m_entityMutex);
+  const std::lock_guard<std::recursive_mutex> lock(m_entityMutex);
   return m_nextEntityId;
 }
 
 void World::setNextEntityId(EntityID nextId) {
-  std::lock_guard<std::recursive_mutex> lock(m_entityMutex);
+  const std::lock_guard<std::recursive_mutex> lock(m_entityMutex);
   m_nextEntityId = std::max(nextId, m_nextEntityId);
 }
 

+ 10 - 5
game/core/world.h

@@ -14,10 +14,15 @@ public:
   World();
   ~World();
 
+  World(const World &) = delete;
+  World(World &&) = delete;
+  World &operator=(const World &) = delete;
+  World &operator=(World &&) = delete;
+
   Entity *createEntity();
-  Entity *createEntityWithId(EntityID id);
-  void destroyEntity(EntityID id);
-  Entity *getEntity(EntityID id);
+  Entity *createEntityWithId(EntityID entityId);
+  void destroyEntity(EntityID entityId);
+  Entity *getEntity(EntityID entityId);
   void clear();
 
   void addSystem(std::unique_ptr<System> system);
@@ -35,9 +40,9 @@ public:
   }
 
   template <typename T> std::vector<Entity *> getEntitiesWith() {
-    std::lock_guard<std::recursive_mutex> lock(m_entityMutex);
+    const std::lock_guard<std::recursive_mutex> lock(m_entityMutex);
     std::vector<Entity *> result;
-    for (auto &[id, entity] : m_entities) {
+    for (auto &[entityId, entity] : m_entities) {
       if (entity->template hasComponent<T>()) {
         result.push_back(entity.get());
       }

+ 145 - 2
game/systems/command_service.cpp

@@ -321,6 +321,9 @@ void CommandService::moveGroup(Engine::Core::World &world,
     Engine::Core::MovementComponent *movement;
     QVector3D target;
     bool isEngaged;
+    float speed;
+    Game::Units::SpawnType spawnType;
+    float distanceToTarget;
   };
 
   std::vector<MemberInfo> members;
@@ -355,8 +358,22 @@ void CommandService::moveGroup(Engine::Core::World &world,
       engaged = false;
     }
 
-    members.push_back(
-        {units[i], entity, transform, movement, targets[i], engaged});
+    auto *unitComponent = entity->getComponent<Engine::Core::UnitComponent>();
+    float memberSpeed =
+        unitComponent ? std::max(0.1f, unitComponent->speed) : 1.0f;
+    Game::Units::SpawnType spawnType =
+        unitComponent ? unitComponent->spawnType
+                      : Game::Units::SpawnType::Archer;
+
+    members.push_back({units[i],
+                       entity,
+                       transform,
+                       movement,
+                       targets[i],
+                       engaged,
+                       memberSpeed,
+                       spawnType,
+                       0.0f});
   }
 
   if (members.empty())
@@ -409,6 +426,132 @@ void CommandService::moveGroup(Engine::Core::World &world,
 
   members = movingMembers;
 
+  if (members.empty())
+    return;
+
+  QVector3D targetCentroid(0.0f, 0.0f, 0.0f);
+  QVector3D positionCentroid(0.0f, 0.0f, 0.0f);
+  float speedSum = 0.0f;
+  for (auto &member : members) {
+    targetCentroid += member.target;
+    positionCentroid +=
+        QVector3D(member.transform->position.x, 0.0f, member.transform->position.z);
+    speedSum += member.speed;
+  }
+
+  targetCentroid /= static_cast<float>(members.size());
+  positionCentroid /= static_cast<float>(members.size());
+
+  float targetDistanceSum = 0.0f;
+  float maxTargetDistance = 0.0f;
+  float centroidDistanceSum = 0.0f;
+  for (auto &member : members) {
+    QVector3D currentPos(member.transform->position.x, 0.0f,
+                         member.transform->position.z);
+    float toTarget = (currentPos - member.target).length();
+    float toCentroid = (currentPos - positionCentroid).length();
+
+    member.distanceToTarget = toTarget;
+    targetDistanceSum += toTarget;
+    centroidDistanceSum += toCentroid;
+    maxTargetDistance = std::max(maxTargetDistance, toTarget);
+  }
+
+  float avgTargetDistance =
+      members.empty()
+          ? 0.0f
+          : targetDistanceSum / static_cast<float>(members.size());
+  float avgScatter =
+      members.empty()
+          ? 0.0f
+          : centroidDistanceSum / static_cast<float>(members.size());
+  float avgSpeed =
+      members.empty() ? 0.0f : speedSum / static_cast<float>(members.size());
+
+  float nearThreshold =
+      std::clamp(avgTargetDistance * 0.5f, 4.0f, 12.0f);
+  if (maxTargetDistance <= nearThreshold) {
+    MoveOptions directOptions = options;
+    directOptions.groupMove = false;
+
+    std::vector<Engine::Core::EntityID> directIds;
+    std::vector<QVector3D> directTargets;
+    directIds.reserve(members.size());
+    directTargets.reserve(members.size());
+
+    for (const auto &member : members) {
+      directIds.push_back(member.id);
+      directTargets.push_back(member.target);
+    }
+
+    moveUnits(world, directIds, directTargets, directOptions);
+    return;
+  }
+
+  float scatterThreshold = std::max(avgScatter, 2.5f);
+
+  std::vector<MemberInfo> regroupMembers;
+  std::vector<MemberInfo> directMembers;
+  regroupMembers.reserve(members.size());
+  directMembers.reserve(members.size());
+
+  for (const auto &member : members) {
+    QVector3D currentPos(member.transform->position.x, 0.0f,
+                         member.transform->position.z);
+    float toTarget = member.distanceToTarget;
+    float toCentroid = (currentPos - positionCentroid).length();
+    bool nearDestination = toTarget <= nearThreshold;
+    bool farFromGroup = toCentroid > scatterThreshold * 1.5f;
+    bool fastUnit = member.speed >= avgSpeed + 0.5f ||
+                    member.spawnType == Game::Units::SpawnType::MountedKnight;
+
+    bool shouldAdvance = nearDestination;
+    if (!shouldAdvance && fastUnit && toTarget <= nearThreshold * 1.5f) {
+      shouldAdvance = true;
+    }
+    if (!shouldAdvance && farFromGroup && toTarget <= nearThreshold * 2.0f) {
+      shouldAdvance = true;
+    }
+
+    if (shouldAdvance) {
+      directMembers.push_back(member);
+    } else {
+      regroupMembers.push_back(member);
+    }
+  }
+
+  if (!directMembers.empty()) {
+    MoveOptions directOptions = options;
+    directOptions.groupMove = false;
+
+    std::vector<Engine::Core::EntityID> directIds;
+    std::vector<QVector3D> directTargets;
+    directIds.reserve(directMembers.size());
+    directTargets.reserve(directMembers.size());
+
+    for (const auto &member : directMembers) {
+      directIds.push_back(member.id);
+      directTargets.push_back(member.target);
+    }
+
+    moveUnits(world, directIds, directTargets, directOptions);
+  }
+
+  if (regroupMembers.size() <= 1) {
+    if (!regroupMembers.empty()) {
+      MoveOptions directOptions = options;
+      directOptions.groupMove = false;
+      std::vector<Engine::Core::EntityID> singleIds = {
+          regroupMembers.front().id};
+      std::vector<QVector3D> singleTargets = {
+          regroupMembers.front().target};
+      moveUnits(world, singleIds, singleTargets, directOptions);
+    }
+    return;
+  }
+
+  members = std::move(regroupMembers);
+
   QVector3D average(0.0f, 0.0f, 0.0f);
   for (const auto &member : members)
     average += member.target;

+ 20 - 7
game/units/spawn_type.h

@@ -2,12 +2,20 @@
 
 #include "troop_type.h"
 #include <QString>
+#include <cstdint>
 #include <optional>
 #include <string>
+#include <functional>
 
 namespace Game::Units {
 
-enum class SpawnType { Archer, Knight, Spearman, MountedKnight, Barracks };
+enum class SpawnType : std::uint8_t {
+  Archer,
+  Knight,
+  Spearman,
+  MountedKnight,
+  Barracks
+};
 
 inline QString spawnTypeToQString(SpawnType type) {
   switch (type) {
@@ -55,16 +63,21 @@ inline bool tryParseSpawnType(const QString &value, SpawnType &out) {
 }
 
 inline std::optional<SpawnType> spawnTypeFromString(const std::string &str) {
-  if (str == "archer")
+  if (str == "archer") {
     return SpawnType::Archer;
-  if (str == "knight")
+  }
+  if (str == "knight") {
     return SpawnType::Knight;
-  if (str == "spearman")
+  }
+  if (str == "spearman") {
     return SpawnType::Spearman;
-  if (str == "mounted_knight")
+  }
+  if (str == "mounted_knight") {
     return SpawnType::MountedKnight;
-  if (str == "barracks")
+  }
+  if (str == "barracks") {
     return SpawnType::Barracks;
+  }
   return std::nullopt;
 }
 
@@ -109,7 +122,7 @@ inline SpawnType spawnTypeFromTroopType(TroopType type) {
 namespace std {
 template <> struct hash<Game::Units::SpawnType> {
   size_t operator()(Game::Units::SpawnType type) const noexcept {
-    return hash<int>()(static_cast<int>(type));
+    return hash<std::uint8_t>()(static_cast<std::uint8_t>(type));
   }
 };
 } // namespace std

+ 3 - 4
game/units/troop_type.h

@@ -3,11 +3,11 @@
 #include <QString>
 #include <algorithm>
 #include <cctype>
+#include <functional>
 #include <optional>
 #include <string>
 
-namespace Game {
-namespace Units {
+namespace Game::Units {
 
 enum class TroopType { Archer, Knight, Spearman, MountedKnight };
 
@@ -67,8 +67,7 @@ inline std::optional<TroopType> tryParseTroopType(const std::string &str) {
   return std::nullopt;
 }
 
-} // namespace Units
-} // namespace Game
+} // namespace Game::Units
 
 namespace std {
 template <> struct hash<Game::Units::TroopType> {