1#![doc = include_str!("../README.md")]
2
3pub mod message;
4
5use std::borrow::Cow;
6use std::io::Write;
7use std::time::{SystemTime, UNIX_EPOCH};
8
9use bevy_app::prelude::*;
10use bevy_ecs::hierarchy::{ChildOf, Children};
11use bevy_ecs::prelude::*;
12use bevy_ecs::system::SystemParam;
13use chunkedge_binary::{Encode, RawBytes};
14use chunkedge_generated::packet_id;
15use chunkedge_server::client::{Client, FlushPacketsSet, SpawnClientsSet};
16use chunkedge_server::protocol::packets::play::{
17 SelectAdvancementsTabS2c, update_advancements_s2c as packet,
18};
19use chunkedge_server::protocol::{
20 IntoTextComponent, Packet, PacketSide, PacketState, VarInt, WritePacket, anyhow,
21};
22use chunkedge_server::{Ident, ItemStack, Text};
23use derive_more::{Deref, DerefMut};
24use message::{AdvancementTabChangeMessage, handle_advancement_tab_change};
25use rustc_hash::FxHashMap;
26
27pub struct AdvancementPlugin;
28
29#[derive(SystemSet, Clone, Copy, Eq, PartialEq, Hash, Debug)]
30pub struct WriteAdvancementPacketToClientsSet;
31
32#[derive(SystemSet, Clone, Copy, Eq, PartialEq, Hash, Debug)]
33pub struct WriteAdvancementToCacheSet;
34
35impl Plugin for AdvancementPlugin {
36 fn build(&self, app: &mut bevy_app::App) {
37 app.configure_sets(
38 PostUpdate,
39 (
40 WriteAdvancementPacketToClientsSet.before(FlushPacketsSet),
41 WriteAdvancementToCacheSet.before(WriteAdvancementPacketToClientsSet),
42 ),
43 )
44 .add_message::<AdvancementTabChangeMessage>()
45 .add_systems(
46 PreUpdate,
47 (
48 add_advancement_update_component_to_new_clients.after(SpawnClientsSet),
49 handle_advancement_tab_change,
50 ),
51 )
52 .add_systems(
53 PostUpdate,
54 (
55 update_advancement_cached_bytes.in_set(WriteAdvancementToCacheSet),
56 send_advancement_update_packet.in_set(WriteAdvancementPacketToClientsSet),
57 ),
58 );
59 }
60}
61
62#[derive(Bundle)]
67pub struct AdvancementBundle {
68 pub advancement: Advancement,
69 pub requirements: AdvancementRequirements,
70 pub cached_bytes: AdvancementCachedBytes,
71}
72
73fn add_advancement_update_component_to_new_clients(
74 mut commands: Commands,
75 query: Query<Entity, Added<Client>>,
76) {
77 for client in query.iter() {
78 commands
79 .entity(client)
80 .insert(AdvancementClientUpdate::default());
81 }
82}
83
84#[derive(SystemParam, Debug)]
85struct UpdateAdvancementCachedBytesQuery<'w, 's> {
86 advancement_id_query: Query<'w, 's, &'static Advancement>,
87 criteria_query: Query<'w, 's, &'static AdvancementCriteria>,
88}
89
90impl UpdateAdvancementCachedBytesQuery<'_, '_> {
91 fn write(
92 &self,
93 a_identifier: &Advancement,
94 a_requirements: &AdvancementRequirements,
95 a_display: Option<&AdvancementDisplay>,
96 _a_children: Option<&Children>,
97 a_parent: Option<&ChildOf>,
98 w: impl Write,
99 ) -> anyhow::Result<()> {
100 let Self {
101 advancement_id_query,
102 criteria_query,
103 } = self;
104
105 let mut pkt = packet::Advancement {
106 parent_id: None,
107 display_data: None,
108 requirements: vec![],
109 sends_telemetry_data: false,
110 };
111
112 if let Some(a_parent) = a_parent {
113 let a_identifier = advancement_id_query.get(a_parent.parent())?;
114 pkt.parent_id = Some(a_identifier.0.borrowed());
115 }
116
117 if let Some(a_display) = a_display {
118 pkt.display_data = Some(packet::AdvancementDisplay {
119 title: (&a_display.title).into_cow_text_component(),
120 description: (&a_display.description).into_cow_text_component(),
121 icon: &a_display.icon,
122 frame_type: VarInt(a_display.frame_type as i32),
123 flags: a_display.flags(),
124 background_texture: a_display.background_texture.as_ref().map(|v| v.borrowed()),
125 x_coord: a_display.x_coord,
126 y_coord: a_display.y_coord,
127 });
128 }
129
130 for requirements in &a_requirements.0 {
131 let mut requirements_p = vec![];
132 for requirement in requirements {
133 let c_identifier = criteria_query.get(*requirement)?;
134 requirements_p.push(c_identifier.0.as_str());
135 }
136 pkt.requirements.push(packet::AdvancementRequirements {
137 requirement: requirements_p,
138 });
139 }
140
141 (&a_identifier.0, pkt).encode(w)
142 }
143}
144
145fn update_advancement_cached_bytes(
146 mut query: Query<
147 (
148 &Advancement,
149 &AdvancementRequirements,
150 &mut AdvancementCachedBytes,
151 Option<&AdvancementDisplay>,
152 Option<&Children>,
153 Option<&ChildOf>,
154 ),
155 Or<(
156 Changed<AdvancementDisplay>,
157 Changed<Children>,
158 Changed<ChildOf>,
159 Changed<AdvancementRequirements>,
160 )>,
161 >,
162 update_advancement_cached_bytes_query: UpdateAdvancementCachedBytesQuery,
163) {
164 for (a_identifier, a_requirements, mut a_bytes, a_display, a_children, a_parent) in &mut query {
165 a_bytes.0.clear();
166 update_advancement_cached_bytes_query
167 .write(
168 a_identifier,
169 a_requirements,
170 a_display,
171 a_children,
172 a_parent,
173 &mut a_bytes.0,
174 )
175 .expect("Failed to write an advancement");
176 }
177}
178
179#[derive(SystemParam, Debug)]
180#[allow(clippy::type_complexity)]
181pub(crate) struct SingleAdvancementUpdateQuery<'w, 's> {
182 advancement_bytes: Query<'w, 's, &'static AdvancementCachedBytes>,
183 advancement_id: Query<'w, 's, &'static Advancement>,
184 criteria: Query<'w, 's, &'static AdvancementCriteria>,
185 parent: Query<'w, 's, &'static ChildOf>,
186}
187
188#[derive(Debug)]
189pub(crate) struct AdvancementUpdateEncodeS2c<'w, 's, 'a> {
190 client_update: AdvancementClientUpdate,
191 queries: &'a SingleAdvancementUpdateQuery<'w, 's>,
192}
193
194impl Encode for AdvancementUpdateEncodeS2c<'_, '_, '_> {
195 fn encode(&self, w: impl Write) -> anyhow::Result<()> {
196 let SingleAdvancementUpdateQuery {
197 advancement_bytes: advancement_bytes_query,
198 advancement_id: advancement_id_query,
199 criteria: criteria_query,
200 parent: parent_query,
201 } = self.queries;
202
203 let AdvancementClientUpdate {
204 new_advancements,
205 remove_advancements,
206 progress,
207 reset,
208 ..
209 } = &self.client_update;
210
211 let mut pkt = packet::GenericUpdateAdvancementsS2c {
212 reset: *reset,
213 advancement_mapping: vec![],
214 identifiers: vec![],
215 progress_mapping: vec![],
216 show_advancements: false,
217 };
218
219 for new_advancement in new_advancements {
220 let a_cached_bytes = advancement_bytes_query.get(*new_advancement)?;
221 pkt.advancement_mapping
222 .push(RawBytes(a_cached_bytes.0.as_slice()));
223 }
224
225 for remove_advancement in remove_advancements {
226 let a_identifier = advancement_id_query.get(*remove_advancement)?;
227 pkt.identifiers.push(a_identifier.0.borrowed());
228 }
229
230 let mut progress_mapping: FxHashMap<Entity, Vec<(Entity, Option<i64>)>> =
231 FxHashMap::default();
232 for progress in progress {
233 let a = parent_query.get(progress.0)?;
234 progress_mapping
235 .entry(a.parent())
236 .and_modify(|v| v.push(*progress))
237 .or_insert(vec![*progress]);
238 }
239
240 for (a, c_progresses) in progress_mapping {
241 let a_identifier = advancement_id_query.get(a)?;
242 let mut c_progresses_p = vec![];
243 for (c, c_progress) in c_progresses {
244 let c_identifier = criteria_query.get(c)?;
245 c_progresses_p.push(packet::AdvancementCriteria {
246 criterion_identifier: c_identifier.0.borrowed(),
247 criterion_progress: c_progress,
248 });
249 }
250 pkt.progress_mapping
251 .push((a_identifier.0.borrowed(), c_progresses_p));
252 }
253
254 pkt.encode(w)
255 }
256}
257
258impl Packet for AdvancementUpdateEncodeS2c<'_, '_, '_> {
259 const ID: i32 = packet_id::PLAY_UPDATE_ADVANCEMENTS_S2C;
260 const NAME: &'static str = "AdvancementUpdateEncodeS2c";
261 const SIDE: PacketSide = PacketSide::Clientbound;
262 const STATE: PacketState = PacketState::Play;
263}
264
265#[allow(clippy::type_complexity)]
266fn send_advancement_update_packet(
267 mut client: Query<(&mut AdvancementClientUpdate, &mut Client)>,
268 update_single_query: SingleAdvancementUpdateQuery,
269) {
270 for (mut advancement_client_update, mut client) in &mut client {
271 match advancement_client_update.force_tab_update {
272 ForceTabUpdate::None => {}
273 ForceTabUpdate::First => {
274 client.write_packet(&SelectAdvancementsTabS2c { identifier: None })
275 }
276 ForceTabUpdate::Spec(spec) => {
277 if let Ok(a_identifier) = update_single_query.advancement_id.get(spec) {
278 client.write_packet(&SelectAdvancementsTabS2c {
279 identifier: Some(a_identifier.0.borrowed()),
280 });
281 }
282 }
283 }
284
285 if ForceTabUpdate::None != advancement_client_update.force_tab_update {
286 advancement_client_update.force_tab_update = ForceTabUpdate::None;
287 }
288
289 if advancement_client_update.new_advancements.is_empty()
290 && advancement_client_update.progress.is_empty()
291 && advancement_client_update.remove_advancements.is_empty()
292 && !advancement_client_update.reset
293 {
294 continue;
295 }
296
297 let advancement_client_update = std::mem::replace(
298 advancement_client_update.as_mut(),
299 AdvancementClientUpdate {
300 reset: false,
301 ..Default::default()
302 },
303 );
304
305 client.write_packet(&AdvancementUpdateEncodeS2c {
306 queries: &update_single_query,
307 client_update: advancement_client_update,
308 });
309 }
310}
311
312#[derive(Component, Deref)]
314pub struct Advancement(Ident<Cow<'static, str>>);
315
316impl Advancement {
317 pub fn new(ident: Ident<Cow<'static, str>>) -> Advancement {
318 Self(ident)
319 }
320
321 pub fn get(&self) -> &Ident<Cow<'static, str>> {
322 &self.0
323 }
324}
325
326#[derive(Clone, Copy)]
327pub enum AdvancementFrameType {
328 Task,
329 Challenge,
330 Goal,
331}
332
333#[derive(Component)]
335pub struct AdvancementDisplay {
336 pub title: Text,
337 pub description: Text,
338 pub icon: ItemStack,
339 pub frame_type: AdvancementFrameType,
340 pub show_toast: bool,
341 pub hidden: bool,
342 pub background_texture: Option<Ident<Cow<'static, str>>>,
343 pub x_coord: f32,
344 pub y_coord: f32,
345}
346
347impl AdvancementDisplay {
348 pub(crate) fn flags(&self) -> i32 {
349 let mut flags = 0;
350 flags |= i32::from(self.background_texture.is_some());
351 flags |= i32::from(self.show_toast) << 1;
352 flags |= i32::from(self.hidden) << 2;
353 flags
354 }
355}
356
357#[derive(Component, Deref)]
359pub struct AdvancementCriteria(Ident<Cow<'static, str>>);
360
361impl AdvancementCriteria {
362 pub fn new(ident: Ident<Cow<'static, str>>) -> Self {
363 Self(ident)
364 }
365
366 pub fn get(&self) -> &Ident<Cow<'static, str>> {
367 &self.0
368 }
369}
370
371#[derive(Component, Default, Deref, DerefMut)]
375pub struct AdvancementRequirements(pub Vec<Vec<Entity>>);
376
377#[derive(Component, Default)]
378pub struct AdvancementCachedBytes(pub(crate) Vec<u8>);
379
380#[derive(Default, Debug, PartialEq)]
381pub enum ForceTabUpdate {
382 #[default]
383 None,
384 First,
385 Spec(Entity),
387}
388
389#[derive(Component, Debug)]
390pub struct AdvancementClientUpdate {
391 pub new_advancements: Vec<Entity>,
393 pub remove_advancements: Vec<Entity>,
395 pub progress: Vec<(Entity, Option<i64>)>,
398 pub force_tab_update: ForceTabUpdate,
400 pub reset: bool,
404 }
406
407impl Default for AdvancementClientUpdate {
408 fn default() -> Self {
409 Self {
410 new_advancements: vec![],
411 remove_advancements: vec![],
412 progress: vec![],
413 force_tab_update: ForceTabUpdate::default(),
414 reset: true,
415 }
416 }
417}
418
419impl AdvancementClientUpdate {
420 pub(crate) fn walk_advancements(
421 root: Entity,
422 children_query: &Query<&Children>,
423 advancement_check_query: &Query<(), With<Advancement>>,
424 func: &mut impl FnMut(Entity),
425 ) {
426 func(root);
427 if let Ok(children) = children_query.get(root) {
428 for child in children {
429 let child = *child;
430 if advancement_check_query.get(child).is_ok() {
431 Self::walk_advancements(child, children_query, advancement_check_query, func);
432 }
433 }
434 }
435 }
436
437 pub fn send_advancements(
439 &mut self,
440 root: Entity,
441 children_query: &Query<&Children>,
442 advancement_check_query: &Query<(), With<Advancement>>,
443 ) {
444 Self::walk_advancements(root, children_query, advancement_check_query, &mut |e| {
445 self.new_advancements.push(e)
446 });
447 }
448
449 pub fn remove_advancements(
451 &mut self,
452 root: Entity,
453 children_query: &Query<&Children>,
454 advancement_check_query: &Query<(), With<Advancement>>,
455 ) {
456 Self::walk_advancements(root, children_query, advancement_check_query, &mut |e| {
457 self.remove_advancements.push(e)
458 });
459 }
460
461 pub fn criteria_done(&mut self, criteria: Entity) {
463 self.progress.push((
464 criteria,
465 Some(
466 SystemTime::now()
467 .duration_since(UNIX_EPOCH)
468 .unwrap()
469 .as_millis() as i64,
470 ),
471 ))
472 }
473
474 pub fn criteria_undone(&mut self, criteria: Entity) {
476 self.progress.push((criteria, None))
477 }
478}