1#![doc = include_str!("../README.md")]
2
3mod components;
4use std::collections::BTreeSet;
5
6use bevy_app::prelude::*;
7use bevy_ecs::prelude::*;
8pub use components::*;
9use tracing::{debug, warn};
10use valence_server::client::{Client, OldVisibleEntityLayers, VisibleEntityLayers};
11use valence_server::entity::EntityLayerId;
12use valence_server::layer::UpdateLayersPreClientSet;
13use valence_server::protocol::packets::play::scoreboard_display_s2c::ScoreboardPosition;
14use valence_server::protocol::packets::play::scoreboard_objective_update_s2c::{
15 ObjectiveMode, ObjectiveRenderType,
16};
17use valence_server::protocol::packets::play::scoreboard_player_update_s2c::ScoreboardPlayerUpdateAction;
18use valence_server::protocol::packets::play::{
19 ScoreboardDisplayS2c, ScoreboardObjectiveUpdateS2c, ScoreboardPlayerUpdateS2c,
20};
21use valence_server::protocol::{VarInt, WritePacket};
22use valence_server::text::IntoText;
23use valence_server::{Despawned, EntityLayer};
24
25pub struct ScoreboardPlugin;
27
28impl Plugin for ScoreboardPlugin {
29 fn build(&self, app: &mut App) {
30 app.configure_sets(PostUpdate, ScoreboardSet.before(UpdateLayersPreClientSet));
31
32 app.add_systems(
33 PostUpdate,
34 (
35 create_or_update_objectives,
36 display_objectives.after(create_or_update_objectives),
37 )
38 .in_set(ScoreboardSet),
39 )
40 .add_systems(
41 PostUpdate,
42 remove_despawned_objectives.in_set(ScoreboardSet),
43 )
44 .add_systems(PostUpdate, handle_new_clients.in_set(ScoreboardSet))
45 .add_systems(
46 PostUpdate,
47 update_scores
48 .after(create_or_update_objectives)
49 .after(handle_new_clients)
50 .in_set(ScoreboardSet),
51 );
52 }
53}
54
55#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)]
56pub struct ScoreboardSet;
57
58fn create_or_update_objectives(
59 objectives: Query<
60 (
61 Ref<Objective>,
62 &ObjectiveDisplay,
63 &ObjectiveRenderType,
64 &EntityLayerId,
65 ),
66 Or<(Changed<ObjectiveDisplay>, Changed<ObjectiveRenderType>)>,
67 >,
68 mut layers: Query<&mut EntityLayer>,
69) {
70 for (objective, display, render_type, entity_layer) in objectives.iter() {
71 if objective.name().is_empty() {
72 warn!("Objective name is empty");
73 }
74 let mode = if objective.is_added() {
75 ObjectiveMode::Create {
76 objective_display_name: (&display.0).into_cow_text(),
77 render_type: *render_type,
78 }
79 } else {
80 ObjectiveMode::Update {
81 objective_display_name: (&display.0).into_cow_text(),
82 render_type: *render_type,
83 }
84 };
85
86 let Ok(mut layer) = layers.get_mut(entity_layer.0) else {
87 warn!(
88 "No layer found for entity layer ID {:?}, can't update scoreboard objective",
89 entity_layer
90 );
91 continue;
92 };
93
94 layer.write_packet(&ScoreboardObjectiveUpdateS2c {
95 objective_name: &objective.0,
96 mode,
97 });
98 }
99}
100
101fn display_objectives(
103 objectives: Query<
104 (&Objective, Ref<ScoreboardPosition>, &EntityLayerId),
105 Changed<ScoreboardPosition>,
106 >,
107 mut layers: Query<&mut EntityLayer>,
108) {
109 for (objective, position, entity_layer) in objectives.iter() {
110 let packet = ScoreboardDisplayS2c {
111 score_name: &objective.0,
112 position: *position,
113 };
114
115 let Ok(mut layer) = layers.get_mut(entity_layer.0) else {
116 warn!(
117 "No layer found for entity layer ID {:?}, can't update scoreboard display",
118 entity_layer
119 );
120 continue;
121 };
122
123 layer.write_packet(&packet);
124 }
125}
126
127fn remove_despawned_objectives(
128 mut commands: Commands,
129 objectives: Query<(Entity, &Objective, &EntityLayerId), With<Despawned>>,
130 mut layers: Query<&mut EntityLayer>,
131) {
132 for (entity, objective, entity_layer) in objectives.iter() {
133 commands.entity(entity).despawn();
134 let Ok(mut layer) = layers.get_mut(entity_layer.0) else {
135 warn!(
136 "No layer found for entity layer ID {:?}, can't remove scoreboard objective",
137 entity_layer
138 );
139 continue;
140 };
141
142 layer.write_packet(&ScoreboardObjectiveUpdateS2c {
143 objective_name: &objective.0,
144 mode: ObjectiveMode::Remove,
145 });
146 }
147}
148
149fn handle_new_clients(
150 mut clients: Query<
151 (&mut Client, &VisibleEntityLayers, &OldVisibleEntityLayers),
152 Or<(Added<Client>, Changed<VisibleEntityLayers>)>,
153 >,
154 objectives: Query<
155 (
156 &Objective,
157 &ObjectiveDisplay,
158 &ObjectiveRenderType,
159 &ScoreboardPosition,
160 &ObjectiveScores,
161 &EntityLayerId,
162 ),
163 Without<Despawned>,
164 >,
165) {
166 for (mut client, visible_layers, old_visible_layers) in &mut clients {
169 let removed_layers: BTreeSet<_> = old_visible_layers
170 .get()
171 .difference(&visible_layers.0)
172 .collect();
173
174 for (objective, _, _, _, _, layer) in objectives.iter() {
175 if !removed_layers.contains(&layer.0) {
176 continue;
177 }
178 client.write_packet(&ScoreboardObjectiveUpdateS2c {
179 objective_name: &objective.0,
180 mode: ObjectiveMode::Remove,
181 });
182 }
183 }
184
185 for (mut client, visible_layers, old_visible_layers) in &mut clients {
188 let added_layers = if client.is_added() {
190 debug!("client is new, sending all objectives");
191 visible_layers.0.clone()
192 } else {
193 visible_layers
194 .0
195 .difference(old_visible_layers.get())
196 .copied()
197 .collect::<BTreeSet<_>>()
198 };
199
200 for (objective, display, render_type, position, scores, layer) in objectives.iter() {
201 if !added_layers.contains(&layer.0) {
202 continue;
203 }
204
205 client.write_packet(&ScoreboardObjectiveUpdateS2c {
206 objective_name: &objective.0,
207 mode: ObjectiveMode::Create {
208 objective_display_name: (&display.0).into_cow_text(),
209 render_type: *render_type,
210 },
211 });
212 client.write_packet(&ScoreboardDisplayS2c {
213 score_name: &objective.0,
214 position: *position,
215 });
216
217 for (key, score) in &scores.0 {
218 let packet = ScoreboardPlayerUpdateS2c {
219 entity_name: key,
220 action: ScoreboardPlayerUpdateAction::Update {
221 objective_name: &objective.0,
222 objective_score: VarInt(*score),
223 },
224 };
225
226 client.write_packet(&packet);
227 }
228 }
229 }
230}
231
232fn update_scores(
233 mut objectives: Query<
234 (
235 &Objective,
236 &ObjectiveScores,
237 &mut OldObjectiveScores,
238 &EntityLayerId,
239 ),
240 (Changed<ObjectiveScores>, Without<Despawned>),
241 >,
242 mut layers: Query<&mut EntityLayer>,
243) {
244 for (objective, scores, mut old_scores, entity_layer) in &mut objectives {
245 let Ok(mut layer) = layers.get_mut(entity_layer.0) else {
246 warn!(
247 "No layer found for entity layer ID {:?}, can't update scores",
248 entity_layer
249 );
250 continue;
251 };
252
253 for changed_key in old_scores.diff(scores) {
254 let action = match scores.0.get(changed_key) {
255 Some(score) => ScoreboardPlayerUpdateAction::Update {
256 objective_name: &objective.0,
257 objective_score: VarInt(*score),
258 },
259 None => ScoreboardPlayerUpdateAction::Remove {
260 objective_name: &objective.0,
261 },
262 };
263
264 let packet = ScoreboardPlayerUpdateS2c {
265 entity_name: changed_key,
266 action,
267 };
268
269 layer.write_packet(&packet);
270 }
271
272 old_scores.0.clone_from(&scores.0);
273 }
274}