1use std::{marker::PhantomData, ops::Deref};
2
3use bevy_ecs::query::Has;
4use bevy_ecs::system::Commands;
5use bevy_ecs::{
6 bundle::Bundle, change_detection::DetectChanges, component::Component,
7 entity::Entity, query::With, system::Query, world::Ref,
8};
9use bevy_log::warn_once;
10use bevy_platform::collections::HashMap;
11use bevy_transform::{components::Transform, helper::TransformHelper};
12use landmass::AnimationLinkId;
13
14use crate::{
15 AgentState, Archipelago, TargetReachedCondition, Velocity,
16 coords::{CoordinateSystem, ThreeD, TwoD},
17};
18use crate::{ArchipelagoRef, PermittedAnimationLinks};
19
20#[derive(Bundle)]
24pub struct AgentBundle<CS: CoordinateSystem> {
25 pub agent: Agent<CS>,
27 pub settings: AgentSettings,
29 pub archipelago_ref: ArchipelagoRef<CS>,
31}
32
33pub type Agent2dBundle = AgentBundle<TwoD>;
34pub type Agent3dBundle = AgentBundle<ThreeD>;
35
36#[derive(Component)]
38#[require(Transform, Velocity<CS>, AgentTarget<CS>, AgentState, AgentDesiredVelocity<CS>)]
39pub struct Agent<CS: CoordinateSystem>(PhantomData<CS>);
40
41pub type Agent2d = Agent<TwoD>;
42pub type Agent3d = Agent<ThreeD>;
43
44impl<CS: CoordinateSystem> Default for Agent<CS> {
45 fn default() -> Self {
46 Self(Default::default())
47 }
48}
49
50#[derive(Component, Debug)]
53pub struct AgentSettings {
54 pub radius: f32,
56 pub desired_speed: f32,
60 pub max_speed: f32,
62}
63
64#[derive(Component, Debug)]
70pub struct AnimationLinkReachedDistance(pub f32);
71
72#[derive(Component, Default, Debug)]
73pub struct AgentTypeIndexCostOverrides(HashMap<usize, f32>);
74
75impl Deref for AgentTypeIndexCostOverrides {
76 type Target = HashMap<usize, f32>;
77 fn deref(&self) -> &Self::Target {
78 &self.0
79 }
80}
81
82impl AgentTypeIndexCostOverrides {
83 pub fn set_type_index_cost(&mut self, type_index: usize, cost: f32) -> bool {
86 if cost <= 0.0 {
87 return false;
88 }
89 self.0.insert(type_index, cost);
90 true
91 }
92}
93
94#[derive(Component)]
107pub enum AgentTarget<CS: CoordinateSystem> {
108 None,
109 Point(CS::Coordinate),
110 Entity(Entity),
111}
112
113pub type AgentTarget2d = AgentTarget<TwoD>;
114pub type AgentTarget3d = AgentTarget<ThreeD>;
115
116impl<CS: CoordinateSystem> Default for AgentTarget<CS> {
117 fn default() -> Self {
118 Self::None
119 }
120}
121
122impl<CS: CoordinateSystem<Coordinate: std::fmt::Debug>> std::fmt::Debug
123 for AgentTarget<CS>
124{
125 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126 match self {
127 Self::None => write!(f, "None"),
128 Self::Point(arg0) => f.debug_tuple("Point").field(arg0).finish(),
129 Self::Entity(arg0) => f.debug_tuple("Entity").field(arg0).finish(),
130 }
131 }
132}
133
134impl<CS: CoordinateSystem<Coordinate: PartialEq>> PartialEq
135 for AgentTarget<CS>
136{
137 fn eq(&self, other: &Self) -> bool {
138 match (self, other) {
139 (Self::Point(l0), Self::Point(r0)) => l0 == r0,
140 (Self::Entity(l0), Self::Entity(r0)) => l0 == r0,
141 _ => core::mem::discriminant(self) == core::mem::discriminant(other),
142 }
143 }
144}
145
146impl<CS: CoordinateSystem<Coordinate: Eq>> Eq for AgentTarget<CS> {}
147
148impl<CS: CoordinateSystem> AgentTarget<CS> {
149 fn to_point(
151 &self,
152 transform_helper: &TransformHelper,
153 ) -> Option<CS::Coordinate> {
154 match self {
155 Self::Point(point) => Some(point.clone()),
156 &Self::Entity(entity) => transform_helper
157 .compute_global_transform(entity)
158 .ok()
159 .map(|transform| CS::from_bevy_position(transform.translation())),
160 _ => None,
161 }
162 }
163}
164
165#[derive(Component)]
168pub struct AgentDesiredVelocity<CS: CoordinateSystem>(CS::Coordinate);
169
170pub type AgentDesiredVelocity2d = AgentDesiredVelocity<TwoD>;
171pub type AgentDesiredVelocity3d = AgentDesiredVelocity<ThreeD>;
172
173impl<CS: CoordinateSystem> Default for AgentDesiredVelocity<CS> {
174 fn default() -> Self {
175 Self(Default::default())
176 }
177}
178
179impl<CS: CoordinateSystem<Coordinate: std::fmt::Debug>> std::fmt::Debug
180 for AgentDesiredVelocity<CS>
181{
182 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183 f.debug_tuple("AgentDesiredVelocity").field(&self.0).finish()
184 }
185}
186
187impl<CS: CoordinateSystem> AgentDesiredVelocity<CS> {
188 pub fn velocity(&self) -> CS::Coordinate {
190 self.0.clone()
191 }
192}
193
194#[derive(Component)]
196pub struct ReachedAnimationLink<CS: CoordinateSystem> {
197 pub link_entity: Entity,
199 pub start_point: CS::Coordinate,
201 pub end_point: CS::Coordinate,
203}
204
205pub type ReachedAnimationLink2d = ReachedAnimationLink<TwoD>;
206pub type ReachedAnimationLink3d = ReachedAnimationLink<ThreeD>;
207
208impl<CS: CoordinateSystem<Coordinate: std::fmt::Debug>> std::fmt::Debug
209 for ReachedAnimationLink<CS>
210{
211 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
212 f.debug_struct("ReachedAnimationLink")
213 .field("link_entity", &self.link_entity)
214 .field("start_point", &self.start_point)
215 .field("end_point", &self.end_point)
216 .finish()
217 }
218}
219
220#[derive(Component, Default, Clone, Copy, Debug)]
227pub struct PauseAgent;
228
229#[derive(Component, Default, Clone, Copy, Debug)]
233pub struct UsingAnimationLink;
234
235#[cfg(feature = "debug-avoidance")]
236#[derive(Component, Clone, Copy, Debug)]
239pub struct KeepAvoidanceData;
240
241pub(crate) fn add_agents_to_archipelagos<CS: CoordinateSystem>(
243 mut archipelago_query: Query<(Entity, &mut Archipelago<CS>)>,
244 agent_query: Query<
245 (Entity, &AgentSettings, &ArchipelagoRef<CS>),
246 With<Transform>,
247 >,
248) {
249 let mut archipelago_to_agents = HashMap::<_, HashMap<_, _>>::default();
250 for (entity, agent, archipleago_ref) in agent_query.iter() {
251 archipelago_to_agents
252 .entry(archipleago_ref.entity)
253 .or_default()
254 .insert(entity, agent);
255 }
256
257 for (archipelago_entity, mut archipelago) in archipelago_query.iter_mut() {
258 let mut new_agent_map = archipelago_to_agents
259 .remove(&archipelago_entity)
260 .unwrap_or_else(HashMap::default);
261 let archipelago = archipelago.as_mut();
262
263 archipelago.agents.retain(|agent_entity, agent_id| {
266 match new_agent_map.remove(agent_entity) {
267 None => {
268 archipelago.archipelago.remove_agent(*agent_id);
269 archipelago.reverse_agents.remove(agent_id);
270 false
271 }
272 Some(_) => true,
273 }
274 });
275
276 for (new_agent_entity, new_agent) in new_agent_map.drain() {
277 let agent_id =
278 archipelago.archipelago.add_agent(landmass::Agent::create(
279 CS::from_landmass(&landmass::Vec3::ZERO),
280 CS::from_landmass(&landmass::Vec3::ZERO),
281 new_agent.radius,
282 new_agent.desired_speed,
283 new_agent.max_speed,
284 ));
285 archipelago.agents.insert(new_agent_entity, agent_id);
286 archipelago.reverse_agents.insert(agent_id, new_agent_entity);
287 }
288 }
289}
290
291#[cfg(feature = "debug-avoidance")]
292type HasKeepAvoidanceData = Has<KeepAvoidanceData>;
293#[cfg(not(feature = "debug-avoidance"))]
294type HasKeepAvoidanceData = ();
295
296pub(crate) fn sync_agent_input_state<CS: CoordinateSystem>(
299 agent_query: Query<
300 (
301 Entity,
302 &AgentSettings,
303 &ArchipelagoRef<CS>,
304 Option<&Velocity<CS>>,
305 Option<&AgentTarget<CS>>,
306 Option<&TargetReachedCondition>,
307 Option<&AnimationLinkReachedDistance>,
308 Option<&PermittedAnimationLinks>,
309 Option<Ref<AgentTypeIndexCostOverrides>>,
310 Has<PauseAgent>,
311 Has<UsingAnimationLink>,
312 HasKeepAvoidanceData,
313 ),
314 With<Transform>,
315 >,
316 transform_helper: TransformHelper,
317 mut archipelago_query: Query<&mut Archipelago<CS>>,
318) {
319 for (
320 agent_entity,
321 agent,
322 &ArchipelagoRef { entity: arch_entity, .. },
323 velocity,
324 target,
325 target_reached_condition,
326 animation_link_reached_distance,
327 permitted_animation_links,
328 type_index_cost_overrides,
329 has_pause_agent,
330 has_using_animation_link,
331 keep_avoidance_data,
332 ) in agent_query.iter()
333 {
334 let mut archipelago = match archipelago_query.get_mut(arch_entity) {
335 Err(_) => continue,
336 Ok(arch) => arch,
337 };
338
339 let Ok(transform) = transform_helper.compute_global_transform(agent_entity)
340 else {
341 continue;
342 };
343
344 let landmass_agent = archipelago
345 .get_agent_mut(agent_entity)
346 .expect("this agent is in the archipelago");
347 landmass_agent.position = CS::from_bevy_position(transform.translation());
348 if let Some(Velocity { velocity }) = velocity {
349 landmass_agent.velocity = velocity.clone();
350 }
351 landmass_agent.radius = agent.radius;
352 landmass_agent.desired_speed = agent.desired_speed;
353 landmass_agent.max_speed = agent.max_speed;
354 landmass_agent.current_target =
355 target.and_then(|target| target.to_point(&transform_helper));
356 landmass_agent.target_reached_condition =
357 if let Some(target_reached_condition) = target_reached_condition {
358 target_reached_condition.to_landmass()
359 } else {
360 landmass::TargetReachedCondition::Distance(None)
361 };
362 landmass_agent.animation_link_reached_distance =
363 animation_link_reached_distance.map(|distance| distance.0);
364 landmass_agent.permitted_animation_links = permitted_animation_links
365 .map(PermittedAnimationLinks::to_landmass)
366 .unwrap_or(landmass::PermittedAnimationLinks::All);
367 match type_index_cost_overrides {
368 None => {
369 for (type_index, _) in
370 landmass_agent.get_type_index_cost_overrides().collect::<Vec<_>>()
371 {
372 landmass_agent.remove_overridden_type_index_cost(type_index);
373 }
374 }
375 Some(type_index_cost_overrides) => {
376 if !type_index_cost_overrides.is_changed() {
377 continue;
378 }
379
380 for (type_index, _) in
381 landmass_agent.get_type_index_cost_overrides().collect::<Vec<_>>()
382 {
383 if type_index_cost_overrides.0.contains_key(&type_index) {
384 continue;
385 }
386 landmass_agent.remove_overridden_type_index_cost(type_index);
387 }
388
389 for (&type_index, &cost) in type_index_cost_overrides.0.iter() {
390 assert!(landmass_agent.override_type_index_cost(type_index, cost));
391 }
392 }
393 }
394 landmass_agent.paused = has_pause_agent;
395 match (landmass_agent.is_using_animation_link(), has_using_animation_link) {
396 (true, false) => {
397 landmass_agent.end_animation_link().unwrap();
398 }
399 (false, true) => match landmass_agent.start_animation_link() {
400 Ok(()) => {}
401 Err(err) => {
402 warn_once!("Failed to start animation link: {err}");
403 }
404 },
405 (true, true) | (false, false) => {}
406 }
407
408 #[cfg(feature = "debug-avoidance")]
409 {
410 landmass_agent.keep_avoidance_data = keep_avoidance_data;
411 }
412 #[cfg(not(feature = "debug-avoidance"))]
413 #[expect(clippy::let_unit_value)]
414 let _ = keep_avoidance_data;
415 }
416}
417
418pub(crate) fn sync_agent_state<CS: CoordinateSystem>(
420 mut agent_query: Query<
421 (Entity, &ArchipelagoRef<CS>, &mut AgentState),
422 With<AgentSettings>,
423 >,
424 archipelago_query: Query<&Archipelago<CS>>,
425) {
426 for (agent_entity, &ArchipelagoRef { entity: arch_entity, .. }, mut state) in
427 agent_query.iter_mut()
428 {
429 let archipelago = match archipelago_query.get(arch_entity).ok() {
430 None => continue,
431 Some(arch) => arch,
432 };
433
434 *state = AgentState::from_landmass(
435 &archipelago
436 .get_agent(agent_entity)
437 .expect("the agent is in the archipelago")
438 .state(),
439 );
440 }
441}
442
443pub(crate) fn sync_desired_velocity<CS: CoordinateSystem>(
446 mut agent_query: Query<
447 (Entity, &ArchipelagoRef<CS>, &mut AgentDesiredVelocity<CS>),
448 With<AgentSettings>,
449 >,
450 archipelago_query: Query<&Archipelago<CS>>,
451) {
452 for (
453 agent_entity,
454 &ArchipelagoRef { entity: arch_entity, .. },
455 mut desired_velocity,
456 ) in agent_query.iter_mut()
457 {
458 let archipelago = match archipelago_query.get(arch_entity).ok() {
459 None => continue,
460 Some(arch) => arch,
461 };
462
463 desired_velocity.0 = archipelago
464 .get_agent(agent_entity)
465 .expect("the agent is in the archipelago")
466 .get_desired_velocity()
467 .clone();
468 }
469}
470
471impl<CS: CoordinateSystem> ReachedAnimationLink<CS> {
472 pub(crate) fn from_landmass(
475 animation_link: &landmass::ReachedAnimationLink<CS>,
476 link_id_to_entity: &HashMap<AnimationLinkId, Entity>,
477 ) -> Self {
478 Self {
479 start_point: animation_link.start_point.clone(),
480 end_point: animation_link.end_point.clone(),
481 link_entity: *link_id_to_entity.get(&animation_link.link_id).unwrap(),
482 }
483 }
484}
485
486pub(crate) fn sync_agent_reached_animation_link<CS: CoordinateSystem>(
489 mut agents: Query<
490 (Entity, &ArchipelagoRef<CS>, Option<&mut ReachedAnimationLink<CS>>),
491 With<Agent<CS>>,
492 >,
493 archipelagos: Query<&Archipelago<CS>>,
494 mut commands: Commands,
495) {
496 for (agent_entity, archipelago_ref, reached_animation_link) in
497 agents.iter_mut()
498 {
499 let Ok(archipelago) = archipelagos.get(archipelago_ref.entity) else {
500 continue;
501 };
502 let Some(agent) = archipelago.get_agent(agent_entity) else {
503 continue;
504 };
505
506 let new_reached_animation_link = match agent.reached_animation_link() {
507 None => {
508 if reached_animation_link.is_some() {
509 commands.entity(agent_entity).remove::<ReachedAnimationLink<CS>>();
510 }
511 continue;
512 }
513 Some(new_reached_animation_link) => new_reached_animation_link,
514 };
515 let new_reached_animation_link = ReachedAnimationLink::from_landmass(
516 new_reached_animation_link,
517 &archipelago.reverse_animation_links,
518 );
519 match reached_animation_link {
520 None => {
521 commands.entity(agent_entity).insert(new_reached_animation_link);
522 }
523 Some(mut reached_animation_link) => {
524 *reached_animation_link = new_reached_animation_link;
525 }
526 }
527 }
528}