Skip to main content

chunkedge_advancement/
lib.rs

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/// Components for advancement that are required
63/// Optional components:
64/// [`AdvancementDisplay`]
65/// [`ChildOf`] - parent advancement
66#[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/// Advancement's id. May not be updated.
313#[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/// Advancement display. Optional component
334#[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/// Criteria's identifier. May not be updated
358#[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/// Requirements for advancement to be completed.
372/// All columns should be completed, column is completed when any of criteria in
373/// this column is completed.
374#[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    /// Should contain only root advancement otherwise the first will be chosen
386    Spec(Entity),
387}
388
389#[derive(Component, Debug)]
390pub struct AdvancementClientUpdate {
391    /// Which advancement's descriptions send to client
392    pub new_advancements: Vec<Entity>,
393    /// Which advancements remove from client
394    pub remove_advancements: Vec<Entity>,
395    /// Criteria progress update.
396    /// If None then criteria is not done otherwise it is done
397    pub progress: Vec<(Entity, Option<i64>)>,
398    /// Forces client to open a tab
399    pub force_tab_update: ForceTabUpdate,
400    /// Defines if other advancements should be removed.
401    /// Also with this flag, client will not show a toast for advancements,
402    /// which are completed. When the packet is sent, turns to false
403    pub reset: bool,
404    // TODO handle toasts
405}
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    /// Sends all advancements from the root
438    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    /// Removes all advancements from the root
450    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    /// Marks criteria as done
462    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    /// Marks criteria as undone
475    pub fn criteria_undone(&mut self, criteria: Entity) {
476        self.progress.push((criteria, None))
477    }
478}