Skip to main content

tiles_tools/ecs/
systems.rs

1//! Game systems for processing entities and components.
2//!
3//! This module contains systems that implement game logic by operating on
4//! entities with specific component combinations. Systems are the "behavior"
5//! part of the ECS architecture.
6//!
7//! # System Categories
8//!
9//! - **Movement Systems**: Handle entity movement and pathfinding
10//! - **Combat Systems**: Process damage, healing, and combat resolution
11//! - **AI Systems**: Update computer-controlled entity behavior
12//! - **Animation Systems**: Update visual animations and effects
13//! - **Trigger Systems**: Process trigger activation and effects
14//!
15//! # Grid-Aware Systems
16//!
17//! Many systems are designed to work with the coordinate system abstractions,
18//! allowing them to function correctly regardless of the underlying grid type
19//! (hexagonal, square, triangular, or isometric).
20
21use crate::ecs::components::*;
22use crate::coordinates::{Distance, Neighbors};
23use crate::pathfind::astar;
24use std::collections::HashMap;
25
26// =============================================================================
27// Movement Systems
28// =============================================================================
29
30/// System for processing entity movement requests.
31///
32/// This system handles movement validation, pathfinding, and position updates
33/// for entities with movement capabilities.
34pub struct MovementSystem;
35
36impl MovementSystem {
37  /// Processes movement for all movable entities.
38  ///
39  /// This method validates movement requests, performs pathfinding when needed,
40  /// and updates entity positions based on their movement capabilities.
41  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  /// Calculates movement path and validates movement request.
70  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    // Check if target is within movement range
79    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    // Use pathfinding to find valid path
88    let path_result = astar(
89      current,
90      target,
91      |_coord| true, // TODO: Add obstacle checking
92      |_coord| 1,    // TODO: Add terrain cost calculation
93    );
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/// Result of a movement attempt.
117#[derive(Debug, Clone, PartialEq)]
118pub enum MovementResult<C> {
119  /// Movement was successful
120  Success {
121    /// The computed path taken to reach the destination
122    path: Vec<C>,
123    /// The final position after movement
124    new_position: C,
125  },
126  /// Target is out of movement range
127  OutOfRange {
128    /// The distance to the requested target
129    requested_distance: u32,
130    /// The maximum movement range for this entity
131    maximum_range: u32,
132  },
133  /// Path exists but is too long
134  PathTooLong {
135    /// The length of the computed path
136    path_length: u32,
137    /// The maximum movement range for this entity
138    maximum_range: u32,
139  },
140  /// No valid path to target
141  NoPathFound,
142}
143
144// =============================================================================
145// Combat Systems  
146// =============================================================================
147
148/// System for processing combat interactions between entities.
149pub struct CombatSystem;
150
151impl CombatSystem {
152  /// Processes combat between all entities within attack range.
153  /// Note: Simplified implementation for demonstration
154  pub fn process_combat(world: &mut hecs::World) -> Vec<CombatEvent> {
155    let mut combat_events = Vec::new();
156    
157    // Simplified combat processing - in a real game this would handle
158    // position-based combat with specific coordinate systems
159    // For now, we just check for defeated entities
160    
161    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  // Combat range checking would be implemented here in a full system
171}
172
173/// Events generated by combat system.
174#[derive(Debug, Clone, Copy, PartialEq, Eq)]
175pub enum CombatEvent {
176  /// Damage was dealt
177  Damage {
178    /// Entity that initiated the attack
179    attacker: hecs::Entity,
180    /// Entity that received the damage
181    target: hecs::Entity,
182    /// Amount of damage dealt
183    damage: u32,
184  },
185  /// Entity was defeated
186  Defeated {
187    /// Entity that was defeated and should be removed
188    entity: hecs::Entity,
189  },
190}
191
192// =============================================================================
193// AI Systems
194// =============================================================================
195
196/// System for updating AI-controlled entities.
197pub struct AISystem;
198
199impl AISystem {
200  /// Updates AI for all AI-controlled entities.
201  /// Note: Simplified implementation for demonstration
202  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        // Simplified AI decision making
208        ai.reset_decision_timer();
209      }
210    }
211  }
212
213  // AI decision making would be implemented here with specific coordinate types
214}
215
216/// Actions that AI can take.
217#[derive(Debug, Clone, PartialEq)]
218pub enum AIAction<C> {
219  /// Start pursuing a target
220  StartPursuit {
221    /// The AI entity that will start pursuing
222    entity: hecs::Entity,
223    /// The entity to pursue
224    target: hecs::Entity,
225    /// Last known position of the target
226    target_position: C,
227  },
228  /// Start patrolling
229  StartPatrol {
230    /// The AI entity that will start patrolling
231    entity: hecs::Entity,
232  },
233  /// Move toward a position
234  MoveToward {
235    /// The AI entity that should move
236    entity: hecs::Entity,
237    /// The position to move toward
238    target_position: C,
239  },
240  /// Attack a target
241  Attack {
242    /// The AI entity performing the attack
243    entity: hecs::Entity,
244    /// The target being attacked
245    target: hecs::Entity,
246  },
247}
248
249// =============================================================================
250// Animation Systems
251// =============================================================================
252
253/// System for updating entity animations.
254pub struct AnimationSystem;
255
256impl AnimationSystem {
257  /// Updates all animations by the specified time delta.
258  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
265// =============================================================================
266// Cleanup Systems
267// =============================================================================
268
269/// System for removing defeated entities and cleaning up resources.
270pub struct CleanupSystem;
271
272impl CleanupSystem {
273  /// Removes entities that have died or should be cleaned up.
274  pub fn cleanup_defeated_entities(world: &mut hecs::World) -> Vec<hecs::Entity> {
275    let mut entities_to_remove = Vec::new();
276
277    // Find entities with 0 health
278    for (entity, health) in world.query::<&Health>().iter() {
279      if !health.is_alive() {
280        entities_to_remove.push(entity);
281      }
282    }
283
284    // Remove the entities
285    for entity in &entities_to_remove {
286      if world.despawn(*entity).is_ok() {
287        // Entity successfully removed
288      }
289    }
290
291    entities_to_remove
292  }
293}
294
295// =============================================================================
296// Utility Functions
297// =============================================================================
298
299/// Finds all entities within a specified range of a position.
300pub 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
319/// Finds the nearest entity to a given position.
320pub 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
341// =============================================================================
342// Collision Detection Systems
343// =============================================================================
344
345/// System for handling collision detection between entities.
346pub struct CollisionSystem;
347
348impl CollisionSystem {
349  /// Detects collisions between all entities with collision components.
350  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    // Check all pairs of entities for collisions
361    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  /// Checks if two entities are colliding based on their positions and collision properties.
381  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  /// Resolves collisions by separating overlapping entities.
396  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      // Handle each collision separately to avoid borrowing conflicts
405      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/// Event representing a collision between two entities.
427#[derive(Debug, Clone)]
428pub struct CollisionEvent<C> {
429  /// First entity in collision
430  pub entity1: hecs::Entity,
431  /// Second entity in collision  
432  pub entity2: hecs::Entity,
433  /// Position of first entity
434  pub position1: Position<C>,
435  /// Position of second entity
436  pub position2: Position<C>,
437}
438
439/// Collision component for entities that can collide.
440#[derive(Debug, Clone)]
441pub struct Collision {
442  /// Collision radius (distance at which collision occurs)
443  pub radius: u32,
444  /// Whether this entity can pass through other entities
445  pub solid: bool,
446  /// Collision layer for filtering collision detection
447  pub layer: u32,
448}
449
450impl Collision {
451  /// Creates a new collision component.
452  pub fn new(radius: u32) -> Self {
453    Self {
454      radius,
455      solid: true,
456      layer: 0,
457    }
458  }
459
460  /// Sets the collision as non-solid (can overlap).
461  pub fn non_solid(mut self) -> Self {
462    self.solid = false;
463    self
464  }
465
466  /// Sets the collision layer.
467  pub fn with_layer(mut self, layer: u32) -> Self {
468    self.layer = layer;
469    self
470  }
471}
472
473// =============================================================================
474// Spatial Query Systems
475// =============================================================================
476
477/// System for efficient spatial queries and neighbor finding.
478pub struct SpatialQuerySystem;
479
480impl SpatialQuerySystem {
481  /// Finds all entities within a circular area.
482  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  /// Finds all entities along a line between two points.
494  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    // Get line positions using simplified line tracing
505    let line_positions = Self::trace_line(&start.coord, &end.coord);
506    
507    // Find entities at each position along the line
508    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  /// Finds all entities within a rectangular area.
520  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        // Additional filtering could be added here for precise rectangular bounds
536        entities.push((entity, pos.clone()));
537      }
538    }
539
540    entities
541  }
542
543  /// Finds entities by team affiliation within a range.
544  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  /// Simplified line tracing for spatial queries.
565  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 == &current {
579          break; // Prevent infinite loop
580        }
581        current = next.clone();
582        line_positions.push(current.clone());
583      } else {
584        break;
585      }
586    }
587
588    line_positions
589  }
590}