1#![doc = include_str!("../README.md")]
2
3mod components;
4use std::collections::BTreeSet;
5
6use bevy_app::prelude::*;
7use bevy_ecs::prelude::*;
8use chunkedge_server::client::{Client, OldVisibleEntityLayers, VisibleEntityLayers};
9use chunkedge_server::entity::EntityLayerId;
10use chunkedge_server::layer::UpdateLayersPreClientSet;
11use chunkedge_server::protocol::packets::play::set_display_objective_s2c::ScoreboardPosition;
12use chunkedge_server::protocol::packets::play::set_objective_s2c::{
13 ObjectiveMode, ObjectiveRenderType,
14};
15use chunkedge_server::protocol::packets::play::{
16 ResetScoreS2c, SetDisplayObjectiveS2c, SetObjectiveS2c, SetScoreS2c,
17};
18use chunkedge_server::protocol::{IntoTextComponent, VarInt, WritePacket};
19use chunkedge_server::{Despawned, EntityLayer};
20pub use components::*;
21use tracing::{debug, warn};
22
23pub struct ScoreboardPlugin;
25
26impl Plugin for ScoreboardPlugin {
27 fn build(&self, app: &mut App) {
28 app.configure_sets(PostUpdate, ScoreboardSet.before(UpdateLayersPreClientSet));
29
30 app.add_systems(
31 PostUpdate,
32 (
33 create_or_update_objectives,
34 display_objectives.after(create_or_update_objectives),
35 )
36 .in_set(ScoreboardSet),
37 )
38 .add_systems(
39 PostUpdate,
40 remove_despawned_objectives.in_set(ScoreboardSet),
41 )
42 .add_systems(PostUpdate, handle_new_clients.in_set(ScoreboardSet))
43 .add_systems(
44 PostUpdate,
45 update_scores
46 .after(create_or_update_objectives)
47 .after(handle_new_clients)
48 .in_set(ScoreboardSet),
49 );
50 }
51}
52
53#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)]
54pub struct ScoreboardSet;
55
56fn create_or_update_objectives(
57 objectives: Query<
58 (
59 Ref<Objective>,
60 &ObjectiveDisplay,
61 &ObjectiveRenderType,
62 &EntityLayerId,
63 ),
64 Or<(Changed<ObjectiveDisplay>, Changed<ObjectiveRenderType>)>,
65 >,
66 mut layers: Query<&mut EntityLayer>,
67) {
68 for (objective, display, render_type, entity_layer) in objectives.iter() {
69 if objective.name().is_empty() {
70 warn!("Objective name is empty");
71 }
72 let mode = if objective.is_added() {
73 ObjectiveMode::Create {
74 objective_display_name: (&display.0).into_cow_text_component(),
75 render_type: *render_type,
76 number_format: None,
77 }
78 } else {
79 ObjectiveMode::Update {
80 objective_display_name: (&display.0).into_cow_text_component(),
81 render_type: *render_type,
82 number_format: None,
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(&SetObjectiveS2c {
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 = SetDisplayObjectiveS2c {
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(&SetObjectiveS2c {
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(&SetObjectiveS2c {
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(&SetObjectiveS2c {
206 objective_name: &objective.0,
207 mode: ObjectiveMode::Create {
208 objective_display_name: (&display.0).into_cow_text_component(),
209 render_type: *render_type,
210 number_format: None,
211 },
212 });
213 client.write_packet(&SetDisplayObjectiveS2c {
214 score_name: &objective.0,
215 position: *position,
216 });
217
218 for (key, score) in &scores.0 {
219 let packet = SetScoreS2c {
220 entity_name: key,
221 objective_name: &objective.0,
222 value: VarInt(*score),
223 display_name: None,
224 number_format: None,
225 };
226
227 client.write_packet(&packet);
228 }
229 }
230 }
231}
232
233fn update_scores(
234 mut objectives: Query<
235 (
236 &Objective,
237 &ObjectiveScores,
238 &mut OldObjectiveScores,
239 &EntityLayerId,
240 ),
241 (Changed<ObjectiveScores>, Without<Despawned>),
242 >,
243 mut layers: Query<&mut EntityLayer>,
244) {
245 for (objective, scores, mut old_scores, entity_layer) in &mut objectives {
246 let Ok(mut layer) = layers.get_mut(entity_layer.0) else {
247 warn!(
248 "No layer found for entity layer ID {:?}, can't update scores",
249 entity_layer
250 );
251 continue;
252 };
253
254 for changed_key in old_scores.diff(scores) {
255 match scores.0.get(changed_key) {
256 Some(score) => {
257 let packet = SetScoreS2c {
258 entity_name: changed_key,
259 objective_name: &objective.0,
260 value: VarInt(*score),
261 display_name: None,
262 number_format: None,
263 };
264
265 layer.write_packet(&packet);
266 }
267 None => {
268 let packet = ResetScoreS2c {
269 entity_name: changed_key,
270 objective_name: Some(&objective.0),
271 };
272
273 layer.write_packet(&packet);
274 }
275 };
276 }
277
278 old_scores.0.clone_from(&scores.0);
279 }
280}