1use crate::ecs::components::*;
22use crate::coordinates::{Distance, Neighbors};
23use crate::pathfind::astar;
24use std::collections::HashMap;
25
26pub struct MovementSystem;
35
36impl MovementSystem {
37 pub fn process_movement<C>(
42 world: &mut hecs::World,
43 movement_requests: &HashMap<hecs::Entity, C>,
44 ) -> Vec<MovementResult<C>>
45 where
46 C: Distance + Neighbors + Clone + PartialEq + Eq + std::hash::Hash + Send + Sync + 'static,
47 {
48 let mut results = Vec::new();
49
50 for (entity, target) in movement_requests {
51 if let Ok((pos, movable)) = world.query_one_mut::<(&mut Position<C>, &Movable)>(*entity) {
52 let movement_result = Self::calculate_movement(&pos.coord, target, movable);
53
54 match movement_result
55 {
56 MovementResult::Success { path, new_position } =>
57 {
58 pos.coord = new_position.clone();
59 results.push(MovementResult::Success { path, new_position });
60 }
61 other => results.push(other),
62 }
63 }
64 }
65
66 results
67 }
68
69 fn calculate_movement<C>(
71 current: &C,
72 target: &C,
73 movable: &Movable,
74 ) -> MovementResult<C>
75 where
76 C: Distance + Neighbors + Clone + PartialEq + Eq + std::hash::Hash,
77 {
78 let distance = current.distance(target);
80 if distance > movable.range {
81 return MovementResult::OutOfRange {
82 requested_distance: distance,
83 maximum_range: movable.range,
84 };
85 }
86
87 let path_result = astar(
89 current,
90 target,
91 |_coord| true, |_coord| 1, );
94
95 match path_result
96 {
97 Some((path, cost)) =>
98 {
99 if cost <= movable.range {
100 MovementResult::Success {
101 path: path.clone(),
102 new_position: target.clone(),
103 }
104 } else {
105 MovementResult::PathTooLong {
106 path_length: cost,
107 maximum_range: movable.range,
108 }
109 }
110 }
111 None => MovementResult::NoPathFound,
112 }
113 }
114}
115
116#[derive(Debug, Clone, PartialEq)]
118pub enum MovementResult<C> {
119 Success {
121 path: Vec<C>,
123 new_position: C,
125 },
126 OutOfRange {
128 requested_distance: u32,
130 maximum_range: u32,
132 },
133 PathTooLong {
135 path_length: u32,
137 maximum_range: u32,
139 },
140 NoPathFound,
142}
143
144pub struct CombatSystem;
150
151impl CombatSystem {
152 pub fn process_combat(world: &mut hecs::World) -> Vec<CombatEvent> {
155 let mut combat_events = Vec::new();
156
157 for (entity, health) in world.query::<&Health>().iter() {
162 if !health.is_alive() {
163 combat_events.push(CombatEvent::Defeated { entity });
164 }
165 }
166
167 combat_events
168 }
169
170 }
172
173#[derive(Debug, Clone, Copy, PartialEq, Eq)]
175pub enum CombatEvent {
176 Damage {
178 attacker: hecs::Entity,
180 target: hecs::Entity,
182 damage: u32,
184 },
185 Defeated {
187 entity: hecs::Entity,
189 },
190}
191
192pub struct AISystem;
198
199impl AISystem {
200 pub fn update_ai(world: &mut hecs::World, dt: f32) {
203 for (_entity, ai) in world.query_mut::<&mut AI>() {
204 ai.update(dt);
205
206 if ai.should_make_decision() {
207 ai.reset_decision_timer();
209 }
210 }
211 }
212
213 }
215
216#[derive(Debug, Clone, PartialEq)]
218pub enum AIAction<C> {
219 StartPursuit {
221 entity: hecs::Entity,
223 target: hecs::Entity,
225 target_position: C,
227 },
228 StartPatrol {
230 entity: hecs::Entity,
232 },
233 MoveToward {
235 entity: hecs::Entity,
237 target_position: C,
239 },
240 Attack {
242 entity: hecs::Entity,
244 target: hecs::Entity,
246 },
247}
248
249pub struct AnimationSystem;
255
256impl AnimationSystem {
257 pub fn update_animations(world: &mut hecs::World, dt: f32) {
259 for (_entity, animation) in world.query_mut::<&mut Animation>() {
260 animation.update(dt);
261 }
262 }
263}
264
265pub struct CleanupSystem;
271
272impl CleanupSystem {
273 pub fn cleanup_defeated_entities(world: &mut hecs::World) -> Vec<hecs::Entity> {
275 let mut entities_to_remove = Vec::new();
276
277 for (entity, health) in world.query::<&Health>().iter() {
279 if !health.is_alive() {
280 entities_to_remove.push(entity);
281 }
282 }
283
284 for entity in &entities_to_remove {
286 if world.despawn(*entity).is_ok() {
287 }
289 }
290
291 entities_to_remove
292 }
293}
294
295pub fn find_entities_in_range<C>(
301 world: &hecs::World,
302 center: &Position<C>,
303 range: u32,
304) -> Vec<(hecs::Entity, Position<C>)>
305where
306 C: Distance + Clone + Send + Sync + 'static,
307{
308 let mut entities = Vec::new();
309
310 for (entity, pos) in world.query::<&Position<C>>().iter() {
311 if center.distance_to(pos) <= range {
312 entities.push((entity, pos.clone()));
313 }
314 }
315
316 entities
317}
318
319pub fn find_nearest_entity<C>(
321 world: &hecs::World,
322 center: &Position<C>,
323) -> Option<(hecs::Entity, Position<C>, u32)>
324where
325 C: Distance + Clone + Send + Sync + 'static,
326{
327 let mut nearest = None;
328 let mut nearest_distance = u32::MAX;
329
330 for (entity, pos) in world.query::<&Position<C>>().iter() {
331 let distance = center.distance_to(pos);
332 if distance < nearest_distance {
333 nearest_distance = distance;
334 nearest = Some((entity, pos.clone(), distance));
335 }
336 }
337
338 nearest
339}
340
341pub struct CollisionSystem;
347
348impl CollisionSystem {
349 pub fn detect_collisions<C>(
351 world: &hecs::World,
352 ) -> Vec<CollisionEvent<C>>
353 where
354 C: Distance + Clone + PartialEq + Send + Sync + 'static,
355 {
356 let mut collisions = Vec::new();
357 let mut query = world.query::<(&Position<C>, &Collision)>();
358 let entities_with_collision: Vec<_> = query.iter().collect();
359
360 for i in 0..entities_with_collision.len() {
362 for j in (i + 1)..entities_with_collision.len() {
363 let (entity1, (pos1, collision1)) = entities_with_collision[i];
364 let (entity2, (pos2, collision2)) = entities_with_collision[j];
365
366 if Self::check_collision(pos1, collision1, pos2, collision2) {
367 collisions.push(CollisionEvent {
368 entity1,
369 entity2,
370 position1: pos1.clone(),
371 position2: pos2.clone(),
372 });
373 }
374 }
375 }
376
377 collisions
378 }
379
380 fn check_collision<C>(
382 pos1: &Position<C>,
383 collision1: &Collision,
384 pos2: &Position<C>,
385 collision2: &Collision,
386 ) -> bool
387 where
388 C: Distance,
389 {
390 let distance = pos1.distance_to(pos2);
391 let collision_distance = collision1.radius + collision2.radius;
392 distance <= collision_distance
393 }
394
395 pub fn resolve_collisions<C>(
397 world: &mut hecs::World,
398 collisions: &[CollisionEvent<C>],
399 )
400 where
401 C: Distance + Neighbors + Clone + Send + Sync + 'static,
402 {
403 for collision in collisions {
404 if let Ok(pos1) = world.query_one_mut::<&mut Position<C>>(collision.entity1) {
406 let neighbors1 = pos1.coord.neighbors();
407 if let Some(best_pos1) = neighbors1.iter()
408 .max_by_key(|neighbor| collision.position2.coord.distance(neighbor))
409 {
410 pos1.coord = best_pos1.clone();
411 }
412 }
413
414 if let Ok(pos2) = world.query_one_mut::<&mut Position<C>>(collision.entity2) {
415 let neighbors2 = pos2.coord.neighbors();
416 if let Some(best_pos2) = neighbors2.iter()
417 .max_by_key(|neighbor| collision.position1.coord.distance(neighbor))
418 {
419 pos2.coord = best_pos2.clone();
420 }
421 }
422 }
423 }
424}
425
426#[derive(Debug, Clone)]
428pub struct CollisionEvent<C> {
429 pub entity1: hecs::Entity,
431 pub entity2: hecs::Entity,
433 pub position1: Position<C>,
435 pub position2: Position<C>,
437}
438
439#[derive(Debug, Clone)]
441pub struct Collision {
442 pub radius: u32,
444 pub solid: bool,
446 pub layer: u32,
448}
449
450impl Collision {
451 pub fn new(radius: u32) -> Self {
453 Self {
454 radius,
455 solid: true,
456 layer: 0,
457 }
458 }
459
460 pub fn non_solid(mut self) -> Self {
462 self.solid = false;
463 self
464 }
465
466 pub fn with_layer(mut self, layer: u32) -> Self {
468 self.layer = layer;
469 self
470 }
471}
472
473pub struct SpatialQuerySystem;
479
480impl SpatialQuerySystem {
481 pub fn query_circle<C>(
483 world: &hecs::World,
484 center: &Position<C>,
485 radius: u32,
486 ) -> Vec<(hecs::Entity, Position<C>)>
487 where
488 C: Distance + Clone + Send + Sync + 'static,
489 {
490 find_entities_in_range(world, center, radius)
491 }
492
493 pub fn query_line<C>(
495 world: &hecs::World,
496 start: &Position<C>,
497 end: &Position<C>,
498 ) -> Vec<(hecs::Entity, Position<C>)>
499 where
500 C: Distance + Neighbors + Clone + PartialEq + std::hash::Hash + Send + Sync + 'static,
501 {
502 let mut entities = Vec::new();
503
504 let line_positions = Self::trace_line(&start.coord, &end.coord);
506
507 for line_pos in line_positions {
509 for (entity, pos) in world.query::<&Position<C>>().iter() {
510 if pos.coord == line_pos {
511 entities.push((entity, pos.clone()));
512 }
513 }
514 }
515
516 entities
517 }
518
519 pub fn query_rectangle<C>(
521 world: &hecs::World,
522 center: &Position<C>,
523 width: u32,
524 height: u32,
525 ) -> Vec<(hecs::Entity, Position<C>)>
526 where
527 C: Distance + Clone + Send + Sync + 'static,
528 {
529 let mut entities = Vec::new();
530 let max_distance = ((width * width + height * height) as f32).sqrt() as u32;
531
532 for (entity, pos) in world.query::<&Position<C>>().iter() {
533 let distance = center.distance_to(pos);
534 if distance <= max_distance {
535 entities.push((entity, pos.clone()));
537 }
538 }
539
540 entities
541 }
542
543 pub fn query_by_team<C>(
545 world: &hecs::World,
546 center: &Position<C>,
547 radius: u32,
548 team_filter: impl Fn(&Team) -> bool,
549 ) -> Vec<(hecs::Entity, Position<C>, Team)>
550 where
551 C: Distance + Clone + Send + Sync + 'static,
552 {
553 let mut entities = Vec::new();
554
555 for (entity, (pos, team)) in world.query::<(&Position<C>, &Team)>().iter() {
556 if center.distance_to(pos) <= radius && team_filter(team) {
557 entities.push((entity, pos.clone(), team.clone()));
558 }
559 }
560
561 entities
562 }
563
564 fn trace_line<C>(start: &C, end: &C) -> Vec<C>
566 where
567 C: Distance + Neighbors + Clone + PartialEq,
568 {
569 let mut line_positions = Vec::new();
570 let mut current = start.clone();
571 line_positions.push(current.clone());
572
573 while current != *end && line_positions.len() < 100 {
574 let neighbors = current.neighbors();
575 if let Some(next) = neighbors.iter()
576 .min_by_key(|neighbor| neighbor.distance(end))
577 {
578 if next == ¤t {
579 break; }
581 current = next.clone();
582 line_positions.push(current.clone());
583 } else {
584 break;
585 }
586 }
587
588 line_positions
589 }
590}