1use common::{
2 CachedSpatialGrid,
3 comp::{
4 self, GizmoSubscriber,
5 gizmos::{GizmoSubscription, Gizmos, RtsimGizmos},
6 },
7 resources::Time,
8 rtsim,
9 uid::IdMaps,
10};
11use common_ecs::{Job, Origin, Phase, System};
12use common_net::msg::ServerGeneral;
13use hashbrown::HashSet;
14use specs::{Entity, Join, Read, ReadExpect, ReadStorage, WriteExpect, shred};
15use vek::{Rgb, Rgba, Vec3};
16
17use crate::client::Client;
18
19#[derive(specs::SystemData)]
20pub struct ReadData<'a> {
21 id_maps: Read<'a, IdMaps>,
22 time: Read<'a, Time>,
23 spatial_grid: ReadExpect<'a, CachedSpatialGrid>,
24 subscribers: ReadStorage<'a, GizmoSubscriber>,
25 agents: ReadStorage<'a, comp::Agent>,
26 position: ReadStorage<'a, comp::Pos>,
27 rtsim_actors: ReadStorage<'a, rtsim::ActorId>,
28 client: ReadStorage<'a, Client>,
29}
30
31struct RtsimGizmoTracker<'a> {
32 gizmos: &'a mut RtsimGizmos,
33 request: HashSet<rtsim::ActorId>,
34}
35
36impl RtsimGizmoTracker<'_> {
37 fn get(&mut self, actor: rtsim::ActorId) -> impl Iterator<Item = Gizmos> + use<'_> {
38 self.request.insert(actor);
39
40 self.gizmos
41 .tracked
42 .get(actor)
43 .into_iter()
44 .flatten()
45 .cloned()
46 }
47}
48
49#[derive(Default)]
50pub struct Sys;
51
52impl<'a> System<'a> for Sys {
53 type SystemData = (ReadData<'a>, WriteExpect<'a, RtsimGizmos>);
54
55 const NAME: &'static str = "msg::gizmos";
56 const ORIGIN: Origin = Origin::Server;
57 const PHASE: Phase = Phase::Create;
58
59 fn run(_job: &mut Job<Self>, (data, mut rtsim_gizmos): Self::SystemData) {
60 let mut tracker = RtsimGizmoTracker {
61 gizmos: &mut rtsim_gizmos,
62 request: HashSet::new(),
63 };
64
65 for (subscriber, client, pos) in (&data.subscribers, &data.client, &data.position).join() {
66 let mut gizmos = Vec::new();
67 for (kind, target) in subscriber.gizmos.iter() {
68 match target {
69 comp::gizmos::GizmoContext::Disabled => {},
70 comp::gizmos::GizmoContext::Enabled => {
71 gizmos_for(
72 &mut gizmos,
73 kind,
74 *pos,
75 subscriber.range,
76 &data,
77 &mut tracker,
78 );
79 },
80 comp::gizmos::GizmoContext::EnabledWithTarget(uid) => {
81 if let Some(target) = data.id_maps.uid_entity(*uid) {
82 gizmos_for_target(
83 &mut gizmos,
84 kind,
85 target,
86 subscriber.range,
87 &data,
88 &mut tracker,
89 )
90 }
91 },
92 }
93 }
94
95 if !gizmos.is_empty() {
96 client.send_fallible(ServerGeneral::Gizmos(gizmos));
97 }
98 }
99
100 tracker.gizmos.tracked.retain(|id, buffer| {
101 buffer.clear();
102 tracker.request.remove(&id)
103 });
104
105 for actor in tracker.request {
106 tracker.gizmos.tracked.insert(actor, Vec::new());
107 }
108 }
109}
110
111fn pathfind_gizmos(gizmos: &mut Vec<Gizmos>, pos: &comp::Pos, agent: &comp::Agent, time: &Time) {
112 if time.0 - agent.chaser.last_update_time().0 > 1.0 {
113 return;
114 }
115 if let Some(route) = agent.chaser.get_route() {
116 if let Some(traversed) = route
117 .get_path()
118 .nodes
119 .get(..route.next_idx())
120 .filter(|n| n.len() >= 2)
121 {
122 gizmos.push(Gizmos::line_strip(
123 traversed.iter().map(|p| p.as_() + 0.5).collect(),
124 Rgba::new(255, 255, 255, 100),
125 ));
126 }
127 if let Some(to_traverse) = route
128 .get_path()
129 .nodes
130 .get(route.next_idx().saturating_sub(1)..)
131 .filter(|n| n.len() >= 2)
132 {
133 gizmos.push(Gizmos::line_strip(
134 to_traverse.iter().map(|p| p.as_() + 0.5).collect(),
135 Rgb::red(),
136 ));
137 }
138 }
139 let above = pos.0 + Vec3::unit_z() * 2.0;
140 if let Some(target) = agent.chaser.last_target() {
141 gizmos.push(Gizmos::line(above, target, Rgba::new(0, 0, 255, 200)));
142 gizmos.push(Gizmos::sphere(target, 0.3, Rgba::new(255, 0, 0, 200)));
143 }
144 let (length, state) = agent.chaser.state();
145
146 let size = match length {
147 common::path::PathLength::Small => 0.1,
148 common::path::PathLength::Medium => 0.3,
149 common::path::PathLength::Long => 0.6,
150 common::path::PathLength::Longest => 0.9,
151 };
152
153 let color = match state {
154 common::path::PathState::None => Rgba::new(255, 0, 0, 200),
155 common::path::PathState::Exhausted => Rgba::new(255, 255, 0, 200),
156 common::path::PathState::Pending => Rgba::new(255, 0, 255, 200),
157 common::path::PathState::Path => Rgba::new(0, 255, 0, 200),
158 };
159 gizmos.push(Gizmos::sphere(above, size, color));
160}
161
162fn rtsim_gizmos(
163 gizmos: &mut Vec<Gizmos>,
164 actor: rtsim::ActorId,
165 rtsim_tracker: &mut RtsimGizmoTracker,
166) {
167 gizmos.extend(rtsim_tracker.get(actor));
168}
169
170fn gizmos_for_target(
171 gizmos: &mut Vec<Gizmos>,
172 subscription: GizmoSubscription,
173 target: Entity,
174 _range: f32,
175 data: &ReadData,
176 rtsim_tracker: &mut RtsimGizmoTracker,
177) {
178 match subscription {
179 GizmoSubscription::PathFinding => {
180 if let Some(agent) = data.agents.get(target)
181 && let Some(pos) = data.position.get(target)
182 {
183 pathfind_gizmos(gizmos, pos, agent, &data.time);
184 }
185 },
186 GizmoSubscription::Rtsim => {
187 if let Some(actor) = data.rtsim_actors.get(target) {
188 rtsim_gizmos(gizmos, *actor, rtsim_tracker);
189 }
190 },
191 }
192}
193
194fn gizmos_for(
195 gizmos: &mut Vec<Gizmos>,
196 subscription: GizmoSubscription,
197 pos: comp::Pos,
198 range: f32,
199 data: &ReadData,
200 rtsim_tracker: &mut RtsimGizmoTracker,
201) {
202 for target in data.spatial_grid.0.in_circle_aabr(pos.0.xy(), range) {
203 gizmos_for_target(gizmos, subscription, target, range, data, rtsim_tracker);
204 }
205}