veloren_server/sys/msg/
gizmos.rs

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}