movement_system.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. #include "movement_system.h"
  2. #include "../map/terrain_service.h"
  3. #include "building_collision_registry.h"
  4. #include "command_service.h"
  5. #include "core/component.h"
  6. #include "map/terrain.h"
  7. #include "pathfinding.h"
  8. #include <QVector3D>
  9. #include <algorithm>
  10. #include <cmath>
  11. #include <numbers>
  12. #include <qvectornd.h>
  13. #include <vector>
  14. namespace Game::Systems {
  15. static constexpr int max_waypoint_skip_count = 4;
  16. static constexpr float repath_cooldown_seconds = 0.4F;
  17. namespace {
  18. auto isPointAllowed(const QVector3D &pos,
  19. Engine::Core::EntityID ignoreEntity) -> bool {
  20. auto &registry = BuildingCollisionRegistry::instance();
  21. auto &terrain_service = Game::Map::TerrainService::instance();
  22. Pathfinding *pathfinder = CommandService::getPathfinder();
  23. if (registry.isPointInBuilding(pos.x(), pos.z(), ignoreEntity)) {
  24. return false;
  25. }
  26. if (pathfinder != nullptr) {
  27. int const grid_x =
  28. static_cast<int>(std::round(pos.x() - pathfinder->getGridOffsetX()));
  29. int const grid_z =
  30. static_cast<int>(std::round(pos.z() - pathfinder->getGridOffsetZ()));
  31. if (!pathfinder->isWalkable(grid_x, grid_z)) {
  32. return false;
  33. }
  34. } else if (terrain_service.isInitialized()) {
  35. int const grid_x = static_cast<int>(std::round(pos.x()));
  36. int const grid_z = static_cast<int>(std::round(pos.z()));
  37. if (!terrain_service.isWalkable(grid_x, grid_z)) {
  38. return false;
  39. }
  40. }
  41. return true;
  42. }
  43. auto isSegmentWalkable(const QVector3D &from, const QVector3D &to,
  44. Engine::Core::EntityID ignoreEntity) -> bool {
  45. QVector3D const delta = to - from;
  46. float const distance_squared = delta.lengthSquared();
  47. bool const start_allowed = isPointAllowed(from, ignoreEntity);
  48. bool const end_allowed = isPointAllowed(to, ignoreEntity);
  49. if (distance_squared < 0.000001F) {
  50. return end_allowed;
  51. }
  52. float const distance = std::sqrt(distance_squared);
  53. int const steps = std::max(1, static_cast<int>(std::ceil(distance)) * 2);
  54. QVector3D const step = delta / static_cast<float>(steps);
  55. bool exited_blocked_zone = start_allowed;
  56. for (int i = 1; i <= steps; ++i) {
  57. QVector3D const pos = from + step * static_cast<float>(i);
  58. bool const allowed = isPointAllowed(pos, ignoreEntity);
  59. if (!exited_blocked_zone) {
  60. if (allowed) {
  61. exited_blocked_zone = true;
  62. }
  63. continue;
  64. }
  65. if (!allowed) {
  66. return false;
  67. }
  68. }
  69. return end_allowed && exited_blocked_zone;
  70. }
  71. } // namespace
  72. void MovementSystem::update(Engine::Core::World *world, float deltaTime) {
  73. CommandService::processPathResults(*world);
  74. auto entities = world->getEntitiesWith<Engine::Core::MovementComponent>();
  75. for (auto *entity : entities) {
  76. moveUnit(entity, world, deltaTime);
  77. }
  78. }
  79. void MovementSystem::moveUnit(Engine::Core::Entity *entity,
  80. Engine::Core::World *world, float deltaTime) {
  81. auto *transform = entity->getComponent<Engine::Core::TransformComponent>();
  82. auto *movement = entity->getComponent<Engine::Core::MovementComponent>();
  83. auto *unit = entity->getComponent<Engine::Core::UnitComponent>();
  84. if ((transform == nullptr) || (movement == nullptr) || (unit == nullptr)) {
  85. return;
  86. }
  87. if (unit->health <= 0 ||
  88. entity->hasComponent<Engine::Core::PendingRemovalComponent>()) {
  89. return;
  90. }
  91. auto *hold_mode = entity->getComponent<Engine::Core::HoldModeComponent>();
  92. bool in_hold_mode = false;
  93. if (hold_mode != nullptr) {
  94. if (hold_mode->exitCooldown > 0.0F) {
  95. hold_mode->exitCooldown =
  96. std::max(0.0F, hold_mode->exitCooldown - deltaTime);
  97. }
  98. if (hold_mode->active) {
  99. movement->hasTarget = false;
  100. movement->vx = 0.0F;
  101. movement->vz = 0.0F;
  102. movement->path.clear();
  103. movement->pathPending = false;
  104. in_hold_mode = true;
  105. }
  106. if (hold_mode->exitCooldown > 0.0F && !in_hold_mode) {
  107. movement->vx = 0.0F;
  108. movement->vz = 0.0F;
  109. return;
  110. }
  111. }
  112. if (in_hold_mode) {
  113. if (!entity->hasComponent<Engine::Core::BuildingComponent>()) {
  114. if (transform->hasDesiredYaw) {
  115. float const current = transform->rotation.y;
  116. float const target_yaw = transform->desiredYaw;
  117. float const diff =
  118. std::fmod((target_yaw - current + 540.0F), 360.0F) - 180.0F;
  119. float const turn_speed = 180.0F;
  120. float const step =
  121. std::clamp(diff, -turn_speed * deltaTime, turn_speed * deltaTime);
  122. transform->rotation.y = current + step;
  123. if (std::fabs(diff) < 0.5F) {
  124. transform->hasDesiredYaw = false;
  125. }
  126. }
  127. }
  128. return;
  129. }
  130. auto *atk = entity->getComponent<Engine::Core::AttackComponent>();
  131. if ((atk != nullptr) && atk->inMeleeLock) {
  132. movement->hasTarget = false;
  133. movement->vx = 0.0F;
  134. movement->vz = 0.0F;
  135. movement->path.clear();
  136. movement->pathPending = false;
  137. return;
  138. }
  139. QVector3D const final_goal(movement->goalX, 0.0F, movement->goalY);
  140. bool const destination_allowed = isPointAllowed(final_goal, entity->getId());
  141. if (movement->hasTarget && !destination_allowed) {
  142. movement->path.clear();
  143. movement->hasTarget = false;
  144. movement->pathPending = false;
  145. movement->pendingRequestId = 0;
  146. movement->vx = 0.0F;
  147. movement->vz = 0.0F;
  148. return;
  149. }
  150. if (movement->repathCooldown > 0.0F) {
  151. movement->repathCooldown =
  152. std::max(0.0F, movement->repathCooldown - deltaTime);
  153. }
  154. if (movement->timeSinceLastPathRequest < 10.0F) {
  155. movement->timeSinceLastPathRequest += deltaTime;
  156. }
  157. const float max_speed = std::max(0.1F, unit->speed);
  158. const float accel = max_speed * 4.0F;
  159. const float damping = 6.0F;
  160. if (!movement->hasTarget) {
  161. QVector3D const current_pos(transform->position.x, 0.0F,
  162. transform->position.z);
  163. float const goal_dist_sq = (final_goal - current_pos).lengthSquared();
  164. constexpr float k_stuck_distance_sq = 0.6F * 0.6F;
  165. bool requested_recovery_move = false;
  166. if (!movement->pathPending && movement->repathCooldown <= 0.0F &&
  167. goal_dist_sq > k_stuck_distance_sq && std::isfinite(goal_dist_sq) &&
  168. destination_allowed) {
  169. CommandService::MoveOptions opts;
  170. opts.clearAttackIntent = false;
  171. opts.allowDirectFallback = true;
  172. std::vector<Engine::Core::EntityID> const ids = {entity->getId()};
  173. std::vector<QVector3D> const targets = {final_goal};
  174. CommandService::moveUnits(*world, ids, targets, opts);
  175. movement->repathCooldown = repath_cooldown_seconds;
  176. requested_recovery_move = true;
  177. }
  178. if (!requested_recovery_move) {
  179. movement->vx *= std::max(0.0F, 1.0F - damping * deltaTime);
  180. movement->vz *= std::max(0.0F, 1.0F - damping * deltaTime);
  181. }
  182. } else {
  183. QVector3D current_pos(transform->position.x, 0.0F, transform->position.z);
  184. QVector3D segment_target(movement->target_x, 0.0F, movement->target_y);
  185. if (!movement->path.empty()) {
  186. segment_target = QVector3D(movement->path.front().first, 0.0F,
  187. movement->path.front().second);
  188. }
  189. auto refresh_segment_target = [&]() {
  190. if (!movement->path.empty()) {
  191. movement->target_x = movement->path.front().first;
  192. movement->target_y = movement->path.front().second;
  193. segment_target =
  194. QVector3D(movement->target_x, 0.0F, movement->target_y);
  195. } else {
  196. segment_target =
  197. QVector3D(movement->target_x, 0.0F, movement->target_y);
  198. }
  199. };
  200. auto try_advance_past_blocked_segment = [&]() {
  201. bool recovered = false;
  202. int skips_remaining = max_waypoint_skip_count;
  203. while (!movement->path.empty() && skips_remaining-- > 0) {
  204. movement->path.erase(movement->path.begin());
  205. refresh_segment_target();
  206. if (isSegmentWalkable(current_pos, segment_target, entity->getId())) {
  207. recovered = true;
  208. break;
  209. }
  210. }
  211. if (!recovered && movement->path.empty()) {
  212. refresh_segment_target();
  213. if (isSegmentWalkable(current_pos, segment_target, entity->getId())) {
  214. recovered = true;
  215. }
  216. }
  217. return recovered;
  218. };
  219. if (!isSegmentWalkable(current_pos, segment_target, entity->getId())) {
  220. if (try_advance_past_blocked_segment()) {
  221. } else {
  222. bool issued_path_request = false;
  223. if (!movement->pathPending && movement->repathCooldown <= 0.0F) {
  224. float const goal_dist_sq = (final_goal - current_pos).lengthSquared();
  225. if (goal_dist_sq > 0.01F && destination_allowed) {
  226. CommandService::MoveOptions opts;
  227. opts.clearAttackIntent = false;
  228. opts.allowDirectFallback = false;
  229. std::vector<Engine::Core::EntityID> const ids = {entity->getId()};
  230. std::vector<QVector3D> const targets = {
  231. QVector3D(movement->goalX, 0.0F, movement->goalY)};
  232. CommandService::moveUnits(*world, ids, targets, opts);
  233. movement->repathCooldown = repath_cooldown_seconds;
  234. issued_path_request = true;
  235. }
  236. }
  237. if (!issued_path_request) {
  238. movement->pathPending = false;
  239. movement->pendingRequestId = 0;
  240. }
  241. movement->path.clear();
  242. movement->hasTarget = false;
  243. movement->vx = 0.0F;
  244. movement->vz = 0.0F;
  245. return;
  246. }
  247. }
  248. float const arrive_radius =
  249. std::clamp(max_speed * deltaTime * 2.0F, 0.05F, 0.25F);
  250. float const arrive_radius_sq = arrive_radius * arrive_radius;
  251. float dx = movement->target_x - transform->position.x;
  252. float dz = movement->target_y - transform->position.z;
  253. float dist2 = dx * dx + dz * dz;
  254. int safety_counter = max_waypoint_skip_count;
  255. while (movement->hasTarget && dist2 < arrive_radius_sq &&
  256. safety_counter-- > 0) {
  257. if (!movement->path.empty()) {
  258. movement->path.erase(movement->path.begin());
  259. if (!movement->path.empty()) {
  260. movement->target_x = movement->path.front().first;
  261. movement->target_y = movement->path.front().second;
  262. dx = movement->target_x - transform->position.x;
  263. dz = movement->target_y - transform->position.z;
  264. dist2 = dx * dx + dz * dz;
  265. continue;
  266. }
  267. }
  268. transform->position.x = movement->target_x;
  269. transform->position.z = movement->target_y;
  270. movement->hasTarget = false;
  271. movement->vx = movement->vz = 0.0F;
  272. break;
  273. }
  274. if (!movement->hasTarget) {
  275. movement->vx *= std::max(0.0F, 1.0F - damping * deltaTime);
  276. movement->vz *= std::max(0.0F, 1.0F - damping * deltaTime);
  277. } else {
  278. float const distance = std::sqrt(std::max(dist2, 0.0F));
  279. float const nx = dx / std::max(0.0001F, distance);
  280. float const nz = dz / std::max(0.0001F, distance);
  281. float desired_speed = max_speed;
  282. float const slow_radius = arrive_radius * 4.0F;
  283. if (distance < slow_radius) {
  284. desired_speed = max_speed * (distance / slow_radius);
  285. }
  286. float const desired_vx = nx * desired_speed;
  287. float const desired_vz = nz * desired_speed;
  288. float const ax = (desired_vx - movement->vx) * accel;
  289. float const az = (desired_vz - movement->vz) * accel;
  290. movement->vx += ax * deltaTime;
  291. movement->vz += az * deltaTime;
  292. movement->vx *= std::max(0.0F, 1.0F - 0.5F * damping * deltaTime);
  293. movement->vz *= std::max(0.0F, 1.0F - 0.5F * damping * deltaTime);
  294. }
  295. }
  296. transform->position.x += movement->vx * deltaTime;
  297. transform->position.z += movement->vz * deltaTime;
  298. auto &terrain = Game::Map::TerrainService::instance();
  299. if (terrain.isInitialized()) {
  300. const Game::Map::TerrainHeightMap *hm = terrain.getHeightMap();
  301. if (hm != nullptr) {
  302. const float tile = hm->getTileSize();
  303. const int w = hm->getWidth();
  304. const int h = hm->getHeight();
  305. if (w > 0 && h > 0) {
  306. const float half_w = w * 0.5F - 0.5F;
  307. const float half_h = h * 0.5F - 0.5F;
  308. const float min_x = -half_w * tile;
  309. const float max_x = half_w * tile;
  310. const float min_z = -half_h * tile;
  311. const float max_z = half_h * tile;
  312. transform->position.x = std::clamp(transform->position.x, min_x, max_x);
  313. transform->position.z = std::clamp(transform->position.z, min_z, max_z);
  314. }
  315. }
  316. }
  317. if (!entity->hasComponent<Engine::Core::BuildingComponent>()) {
  318. float const speed2 =
  319. movement->vx * movement->vx + movement->vz * movement->vz;
  320. if (speed2 > 1e-5F) {
  321. float const target_yaw = std::atan2(movement->vx, movement->vz) * 180.0F /
  322. std::numbers::pi_v<float>;
  323. float const current = transform->rotation.y;
  324. float const diff =
  325. std::fmod((target_yaw - current + 540.0F), 360.0F) - 180.0F;
  326. float const turn_speed = 720.0F;
  327. float const step =
  328. std::clamp(diff, -turn_speed * deltaTime, turn_speed * deltaTime);
  329. transform->rotation.y = current + step;
  330. } else if (transform->hasDesiredYaw) {
  331. float const current = transform->rotation.y;
  332. float const target_yaw = transform->desiredYaw;
  333. float const diff =
  334. std::fmod((target_yaw - current + 540.0F), 360.0F) - 180.0F;
  335. float const turn_speed = 180.0F;
  336. float const step =
  337. std::clamp(diff, -turn_speed * deltaTime, turn_speed * deltaTime);
  338. transform->rotation.y = current + step;
  339. if (std::fabs(diff) < 0.5F) {
  340. transform->hasDesiredYaw = false;
  341. }
  342. }
  343. }
  344. }
  345. auto MovementSystem::hasReachedTarget(
  346. const Engine::Core::TransformComponent *transform,
  347. const Engine::Core::MovementComponent *movement) -> bool {
  348. float const dx = movement->target_x - transform->position.x;
  349. float const dz = movement->target_y - transform->position.z;
  350. float const distance_squared = dx * dx + dz * dz;
  351. const float threshold = 0.1F;
  352. return distance_squared < threshold * threshold;
  353. }
  354. } // namespace Game::Systems