elephant_attack_system.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. #include "elephant_attack_system.h"
  2. #include "../core/component.h"
  3. #include "../core/world.h"
  4. #include "../units/spawn_type.h"
  5. #include <algorithm>
  6. #include <array>
  7. #include <cmath>
  8. #include <cstdlib>
  9. #include <vector>
  10. namespace Game::Systems {
  11. namespace {
  12. struct FootOffset {
  13. float x;
  14. float z;
  15. };
  16. constexpr float k_pi = 3.14159265F;
  17. auto pick_stomp_position(Engine::Core::TransformComponent *transform,
  18. Engine::Core::ElephantComponent *elephant_comp)
  19. -> FootOffset {
  20. float scale =
  21. std::max(1.0F, (transform->scale.x + transform->scale.z) * 0.5F);
  22. float forward = 0.6F * scale;
  23. float side = 0.45F * scale;
  24. float const max_offset = elephant_comp->trample_radius * 0.95F;
  25. if (max_offset > 0.0F) {
  26. forward = std::min(forward, max_offset);
  27. side = std::min(side, max_offset);
  28. }
  29. std::array<FootOffset, 4> const offsets = {
  30. FootOffset{side, forward},
  31. FootOffset{-side, forward},
  32. FootOffset{side, -forward},
  33. FootOffset{-side, -forward},
  34. };
  35. int const index = std::rand() % static_cast<int>(offsets.size());
  36. FootOffset const local = offsets[index];
  37. float const yaw = transform->rotation.y * (k_pi / 180.0F);
  38. float const cos_y = std::cos(yaw);
  39. float const sin_y = std::sin(yaw);
  40. FootOffset world;
  41. world.x = transform->position.x + local.x * cos_y + local.z * sin_y;
  42. world.z = transform->position.z - local.x * sin_y + local.z * cos_y;
  43. return world;
  44. }
  45. } // namespace
  46. void ElephantAttackSystem::update(Engine::Core::World *world,
  47. float delta_time) {
  48. process_elephant_behavior(world, delta_time);
  49. }
  50. void ElephantAttackSystem::process_elephant_behavior(Engine::Core::World *world,
  51. float delta_time) {
  52. auto entities = world->get_entities_with<Engine::Core::UnitComponent>();
  53. for (auto *entity : entities) {
  54. auto *unit = entity->get_component<Engine::Core::UnitComponent>();
  55. if (unit == nullptr || unit->health <= 0) {
  56. continue;
  57. }
  58. if (unit->spawn_type != Game::Units::SpawnType::Elephant) {
  59. continue;
  60. }
  61. if (entity->has_component<Engine::Core::PendingRemovalComponent>()) {
  62. continue;
  63. }
  64. auto *elephant = entity->get_component<Engine::Core::ElephantComponent>();
  65. if (elephant == nullptr) {
  66. elephant = entity->add_component<Engine::Core::ElephantComponent>();
  67. }
  68. float const health_ratio =
  69. static_cast<float>(unit->health) / static_cast<float>(unit->max_health);
  70. if (health_ratio < 0.3F && !elephant->is_panicked) {
  71. if (std::rand() % 100 < 50) {
  72. elephant->is_panicked = true;
  73. elephant->panic_duration = 10.0F;
  74. }
  75. }
  76. if (elephant->is_panicked) {
  77. process_panic_mechanic(entity, world, delta_time);
  78. }
  79. if (elephant->charge_cooldown > 0.0F) {
  80. elephant->charge_cooldown -= delta_time;
  81. }
  82. process_charge_attack(entity, world, delta_time);
  83. process_trample_damage(entity, world, delta_time);
  84. process_melee_attack(entity, world, delta_time);
  85. }
  86. }
  87. void ElephantAttackSystem::process_charge_attack(Engine::Core::Entity *elephant,
  88. Engine::Core::World *world,
  89. float delta_time) {
  90. auto *elephant_comp =
  91. elephant->get_component<Engine::Core::ElephantComponent>();
  92. auto *unit = elephant->get_component<Engine::Core::UnitComponent>();
  93. auto *transform = elephant->get_component<Engine::Core::TransformComponent>();
  94. auto *movement = elephant->get_component<Engine::Core::MovementComponent>();
  95. auto *attack_target =
  96. elephant->get_component<Engine::Core::AttackTargetComponent>();
  97. if (elephant_comp == nullptr || unit == nullptr || transform == nullptr ||
  98. movement == nullptr) {
  99. return;
  100. }
  101. switch (elephant_comp->charge_state) {
  102. case Engine::Core::ElephantComponent::ChargeState::Idle: {
  103. if (attack_target != nullptr && attack_target->target_id != 0 &&
  104. elephant_comp->charge_cooldown <= 0.0F && !elephant_comp->is_panicked) {
  105. auto *target = world->get_entity(attack_target->target_id);
  106. if (target != nullptr) {
  107. auto *target_transform =
  108. target->get_component<Engine::Core::TransformComponent>();
  109. if (target_transform != nullptr) {
  110. float const dx = target_transform->position.x - transform->position.x;
  111. float const dz = target_transform->position.z - transform->position.z;
  112. float const dist = std::sqrt(dx * dx + dz * dz);
  113. if (dist >= 5.0F && dist <= 15.0F) {
  114. elephant_comp->charge_state =
  115. Engine::Core::ElephantComponent::ChargeState::Charging;
  116. elephant_comp->charge_duration = 3.0F;
  117. }
  118. }
  119. }
  120. }
  121. break;
  122. }
  123. case Engine::Core::ElephantComponent::ChargeState::Charging: {
  124. elephant_comp->charge_duration -= delta_time;
  125. if (elephant_comp->charge_duration <= 0.0F) {
  126. elephant_comp->charge_state =
  127. Engine::Core::ElephantComponent::ChargeState::Recovering;
  128. elephant_comp->charge_cooldown = 8.0F;
  129. }
  130. break;
  131. }
  132. case Engine::Core::ElephantComponent::ChargeState::Recovering: {
  133. elephant_comp->charge_state =
  134. Engine::Core::ElephantComponent::ChargeState::Idle;
  135. break;
  136. }
  137. default:
  138. break;
  139. }
  140. }
  141. void ElephantAttackSystem::process_trample_damage(
  142. Engine::Core::Entity *elephant, Engine::Core::World *world,
  143. float delta_time) {
  144. auto *elephant_comp =
  145. elephant->get_component<Engine::Core::ElephantComponent>();
  146. auto *unit = elephant->get_component<Engine::Core::UnitComponent>();
  147. auto *transform = elephant->get_component<Engine::Core::TransformComponent>();
  148. auto *movement = elephant->get_component<Engine::Core::MovementComponent>();
  149. auto *attack = elephant->get_component<Engine::Core::AttackComponent>();
  150. auto *attack_target =
  151. elephant->get_component<Engine::Core::AttackTargetComponent>();
  152. if (elephant_comp == nullptr || unit == nullptr || transform == nullptr ||
  153. movement == nullptr) {
  154. return;
  155. }
  156. constexpr float k_movement_threshold = 0.1F;
  157. bool const is_moving = (std::abs(movement->vx) > k_movement_threshold ||
  158. std::abs(movement->vz) > k_movement_threshold);
  159. bool has_close_target = false;
  160. if (!is_moving && attack_target != nullptr && attack_target->target_id != 0) {
  161. auto *target = world->get_entity(attack_target->target_id);
  162. if (target != nullptr) {
  163. auto *target_transform =
  164. target->get_component<Engine::Core::TransformComponent>();
  165. if (target_transform != nullptr) {
  166. float const dx = target_transform->position.x - transform->position.x;
  167. float const dz = target_transform->position.z - transform->position.z;
  168. float const dist = std::sqrt(dx * dx + dz * dz);
  169. float const engage_range =
  170. (attack != nullptr)
  171. ? std::max(elephant_comp->trample_radius, attack->melee_range)
  172. : elephant_comp->trample_radius;
  173. has_close_target = (dist <= engage_range);
  174. }
  175. }
  176. }
  177. if (!is_moving && !has_close_target) {
  178. elephant_comp->trample_damage_accumulator = 0.0F;
  179. return;
  180. }
  181. elephant_comp->trample_damage_accumulator +=
  182. static_cast<float>(elephant_comp->trample_damage) * delta_time;
  183. int const damage =
  184. static_cast<int>(elephant_comp->trample_damage_accumulator);
  185. if (damage <= 0) {
  186. return;
  187. }
  188. auto *stomp_impact =
  189. elephant->get_component<Engine::Core::ElephantStompImpactComponent>();
  190. if (stomp_impact == nullptr) {
  191. stomp_impact =
  192. elephant->add_component<Engine::Core::ElephantStompImpactComponent>();
  193. }
  194. bool hit_any = false;
  195. auto entities = world->get_entities_with<Engine::Core::UnitComponent>();
  196. for (auto *other_entity : entities) {
  197. if (other_entity == elephant) {
  198. continue;
  199. }
  200. auto *other_unit =
  201. other_entity->get_component<Engine::Core::UnitComponent>();
  202. auto *other_transform =
  203. other_entity->get_component<Engine::Core::TransformComponent>();
  204. if (other_unit == nullptr || other_transform == nullptr ||
  205. other_unit->health <= 0) {
  206. continue;
  207. }
  208. bool const is_enemy = other_unit->owner_id != unit->owner_id;
  209. bool const should_damage = is_enemy || elephant_comp->is_panicked;
  210. if (!should_damage) {
  211. continue;
  212. }
  213. float const dx = other_transform->position.x - transform->position.x;
  214. float const dz = other_transform->position.z - transform->position.z;
  215. float const dist = std::sqrt(dx * dx + dz * dz);
  216. if (dist <= elephant_comp->trample_radius) {
  217. int const old_health = other_unit->health;
  218. other_unit->health -= damage;
  219. if (other_unit->health < 0) {
  220. other_unit->health = 0;
  221. }
  222. if (old_health > 0 && other_unit->health < old_health) {
  223. FootOffset const stomp_pos =
  224. pick_stomp_position(transform, elephant_comp);
  225. Engine::Core::ElephantStompImpactComponent::ImpactRecord impact;
  226. impact.x = stomp_pos.x;
  227. impact.z = stomp_pos.z;
  228. impact.time = 0.0F;
  229. stomp_impact->impacts.push_back(impact);
  230. hit_any = true;
  231. }
  232. }
  233. }
  234. if (hit_any) {
  235. elephant_comp->trample_damage_accumulator -= damage;
  236. } else {
  237. elephant_comp->trample_damage_accumulator = 0.0F;
  238. }
  239. }
  240. void ElephantAttackSystem::process_panic_mechanic(
  241. Engine::Core::Entity *elephant, Engine::Core::World *world,
  242. float delta_time) {
  243. auto *elephant_comp =
  244. elephant->get_component<Engine::Core::ElephantComponent>();
  245. auto *movement = elephant->get_component<Engine::Core::MovementComponent>();
  246. if (elephant_comp == nullptr || movement == nullptr) {
  247. return;
  248. }
  249. elephant_comp->panic_duration -= delta_time;
  250. if (elephant_comp->panic_duration <= 0.0F) {
  251. elephant_comp->is_panicked = false;
  252. elephant_comp->panic_duration = 0.0F;
  253. return;
  254. }
  255. static float random_target_timer = 0.0F;
  256. random_target_timer += delta_time;
  257. if (random_target_timer >= 2.0F) {
  258. random_target_timer = 0.0F;
  259. auto *transform =
  260. elephant->get_component<Engine::Core::TransformComponent>();
  261. if (transform != nullptr) {
  262. float const angle = static_cast<float>(std::rand()) /
  263. static_cast<float>(RAND_MAX) * 3.14159F * 2.0F;
  264. float const distance = 10.0F;
  265. movement->target_x = transform->position.x + std::cos(angle) * distance;
  266. movement->target_y = transform->position.z + std::sin(angle) * distance;
  267. movement->has_target = true;
  268. }
  269. }
  270. }
  271. void ElephantAttackSystem::process_melee_attack(Engine::Core::Entity *elephant,
  272. Engine::Core::World *world,
  273. float delta_time) {}
  274. } // namespace Game::Systems