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