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