this repo has no description
1use bevy::prelude::*;
2use bevy_bae::prelude::*;
3use fake::{Fake, locales::EN};
4
5use crate::{
6 berries::{Berry, NewBerry},
7 sample_arena,
8};
9
10const SPEED: f32 = 100.0;
11
12pub fn ghost_plugin(app: &mut App) {
13 app.add_systems(Startup, setup);
14}
15
16#[derive(Component)]
17pub struct Ghost;
18
19pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
20 commands.spawn(Camera2d);
21 let mut rng = rand::rng();
22
23 commands.spawn((
24 Sprite::from_image(asset_server.load("arena.png")),
25 Transform::from_scale(Vec2::splat(18.0).extend(0.0)),
26 ));
27
28 for _ in 1..100 {
29 commands.spawn((
30 Plan::new(),
31 Ghost,
32 BerriesEaten(0),
33 Name::new(fake::faker::name::raw::FirstName(EN).fake::<String>()),
34 Sprite::from_image(asset_server.load("ghost.png")),
35 Sequence,
36 tasks!(
37 Operator::new(find_closest_berry),
38 Operator::new(go_to_berry),
39 Operator::new(collect_berry)
40 ),
41 Transform::from_translation(sample_arena(&mut rng).extend(0.1)),
42 ));
43 }
44}
45
46#[derive(Component)]
47#[relationship(relationship_target = TargetedBerry)]
48pub struct TargetBerry(pub Entity);
49
50#[derive(Component)]
51#[relationship_target(relationship = TargetBerry)]
52pub struct TargetedBerry(Entity);
53
54#[derive(Component)]
55pub struct BerriesEaten(pub usize);
56
57fn find_closest_berry(
58 In(input): In<OperatorInput>,
59 mut commands: Commands,
60 berries: Query<(Entity, &Transform), (With<Berry>, Without<TargetedBerry>)>,
61 ghosts: Query<&Transform, With<Ghost>>,
62) -> OperatorStatus {
63 let pos = ghosts.get(input.entity).unwrap().translation.xy();
64 let mut closest: Option<Entity> = None;
65 let mut closest_dist = 0.0;
66 for (entity, transform) in berries {
67 let dist = transform.translation.xy().distance_squared(pos);
68 if closest.is_none() || dist < closest_dist {
69 closest = Some(entity);
70 closest_dist = dist;
71 }
72 }
73
74 if let Some(entity) = closest {
75 commands.entity(input.entity).insert(TargetBerry(entity));
76 return OperatorStatus::Success;
77 }
78 OperatorStatus::Ongoing
79}
80
81fn go_to_berry(
82 In(input): In<OperatorInput>,
83 mut ghosts: Query<(&mut Transform, &TargetBerry, &BerriesEaten), With<Ghost>>,
84 berries: Query<&Transform, (With<Berry>, Without<Ghost>)>,
85 new_berries: Query<&Transform, (With<Berry>, Without<TargetedBerry>, Without<Ghost>)>,
86 time: Res<Time>,
87 mut news: MessageReader<NewBerry>,
88 mut commands: Commands,
89) -> Result<OperatorStatus> {
90 let (mut trans, target_entity, eaten) = ghosts.get_mut(input.entity)?;
91
92 let target = berries.get(target_entity.0)?;
93
94 for new in news.read() {
95 let Ok(new_trans) = new_berries.get(new.0) else {
96 continue;
97 };
98
99 if (new_trans
100 .translation
101 .xy()
102 .distance_squared(trans.translation.xy())
103 - target
104 .translation
105 .xy()
106 .distance_squared(trans.translation.xy()))
107 < 30.0
108 {
109 commands.entity(input.entity).insert(TargetBerry(new.0));
110 return Ok(OperatorStatus::Ongoing);
111 }
112 }
113
114 let dir = (target.translation.xy() - trans.translation.xy()).normalize();
115 let mov = dir * (SPEED * (1.0 + (eaten.0 as f32 + 1.0).log10())) * time.delta_secs();
116 if (target.translation.xy() - (trans.translation.xy() + mov)).length() < mov.length() {
117 trans.translation = target.translation;
118 return Ok(OperatorStatus::Success);
119 }
120 trans.translation += mov.extend(0.0);
121
122 Ok(OperatorStatus::Ongoing)
123}
124
125fn collect_berry(
126 In(input): In<OperatorInput>,
127 mut ghosts: Query<(&TargetBerry, &mut BerriesEaten), With<Ghost>>,
128 mut commands: Commands,
129) -> Result<OperatorStatus> {
130 let (berry, mut eaten) = ghosts.get_mut(input.entity)?;
131 eaten.0 += 1;
132 commands.entity(berry.0).despawn();
133 commands.entity(input.entity).remove::<TargetBerry>();
134 Ok(OperatorStatus::Success)
135}