azalea_protocol/packets/game/
c_update_advancements.rs

1use std::{
2    collections::HashMap,
3    io::{self, Cursor, Write},
4};
5
6use azalea_buf::AzBuf;
7use azalea_chat::FormattedText;
8use azalea_core::resource_location::ResourceLocation;
9use azalea_inventory::ItemStack;
10use azalea_protocol_macros::ClientboundGamePacket;
11use indexmap::IndexMap;
12
13#[derive(Clone, Debug, AzBuf, PartialEq, ClientboundGamePacket)]
14pub struct ClientboundUpdateAdvancements {
15    pub reset: bool,
16    pub added: Vec<AdvancementHolder>,
17    pub removed: Vec<ResourceLocation>,
18    pub progress: IndexMap<ResourceLocation, AdvancementProgress>,
19    pub show_advancements: bool,
20}
21
22#[derive(Clone, Debug, AzBuf, PartialEq)]
23pub struct Advancement {
24    pub parent_id: Option<ResourceLocation>,
25    pub display: Option<DisplayInfo>,
26    pub requirements: Vec<Vec<String>>,
27    pub sends_telemetry_event: bool,
28}
29
30#[derive(Clone, Debug, PartialEq)]
31pub struct DisplayInfo {
32    pub title: FormattedText,
33    pub description: FormattedText,
34    pub icon: ItemStack,
35    pub frame: FrameType,
36    pub show_toast: bool,
37    pub hidden: bool,
38    pub background: Option<ResourceLocation>,
39    pub x: f32,
40    pub y: f32,
41}
42
43impl azalea_buf::AzaleaWrite for DisplayInfo {
44    fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
45        self.title.azalea_write(buf)?;
46        self.description.azalea_write(buf)?;
47        self.icon.azalea_write(buf)?;
48        self.frame.azalea_write(buf)?;
49
50        let mut data: u32 = 0;
51        if self.background.is_some() {
52            data |= 0b001;
53        }
54        if self.show_toast {
55            data |= 0b010;
56        }
57        if self.hidden {
58            data |= 0b100;
59        }
60        data.azalea_write(buf)?;
61
62        if let Some(background) = &self.background {
63            background.azalea_write(buf)?;
64        }
65        self.x.azalea_write(buf)?;
66        self.y.azalea_write(buf)?;
67        Ok(())
68    }
69}
70impl azalea_buf::AzaleaRead for DisplayInfo {
71    fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, azalea_buf::BufReadError> {
72        let title = azalea_buf::AzaleaRead::azalea_read(buf)?;
73        let description = azalea_buf::AzaleaRead::azalea_read(buf)?;
74        let icon = azalea_buf::AzaleaRead::azalea_read(buf)?;
75        let frame = azalea_buf::AzaleaRead::azalea_read(buf)?;
76
77        let data = u32::azalea_read(buf)?;
78        let has_background = (data & 0b1) != 0;
79        let show_toast = (data & 0b10) != 0;
80        let hidden = (data & 0b100) != 0;
81
82        let background = if has_background {
83            Some(ResourceLocation::azalea_read(buf)?)
84        } else {
85            None
86        };
87        let x = azalea_buf::AzaleaRead::azalea_read(buf)?;
88        let y = azalea_buf::AzaleaRead::azalea_read(buf)?;
89        Ok(DisplayInfo {
90            title,
91            description,
92            icon,
93            frame,
94            show_toast,
95            hidden,
96            background,
97            x,
98            y,
99        })
100    }
101}
102
103#[derive(Clone, Debug, Copy, AzBuf, PartialEq)]
104pub enum FrameType {
105    Task = 0,
106    Challenge = 1,
107    Goal = 2,
108}
109
110pub type AdvancementProgress = HashMap<String, CriterionProgress>;
111
112#[derive(Clone, Debug, AzBuf, PartialEq)]
113pub struct CriterionProgress {
114    pub date: Option<u64>,
115}
116
117#[derive(Clone, Debug, AzBuf, PartialEq)]
118pub struct AdvancementHolder {
119    pub id: ResourceLocation,
120    pub value: Advancement,
121}
122
123#[cfg(test)]
124mod tests {
125    use azalea_buf::{AzaleaRead, AzaleaWrite};
126
127    use super::*;
128
129    #[test]
130    fn test() {
131        let packet = ClientboundUpdateAdvancements {
132            reset: true,
133            added: [AdvancementHolder {
134                id: ResourceLocation::new("minecraft:test"),
135                value: Advancement {
136                    parent_id: None,
137                    display: Some(DisplayInfo {
138                        title: FormattedText::from("title".to_string()),
139                        description: FormattedText::from("description".to_string()),
140                        icon: ItemStack::Empty,
141                        frame: FrameType::Task,
142                        show_toast: true,
143                        hidden: false,
144                        background: None,
145                        x: 0.0,
146                        y: 0.0,
147                    }),
148                    requirements: Vec::new(),
149                    sends_telemetry_event: false,
150                },
151            }]
152            .into_iter()
153            .collect(),
154            removed: vec![ResourceLocation::new("minecraft:test2")],
155            progress: [(
156                ResourceLocation::new("minecraft:test3"),
157                [(
158                    "minecraft:test4".to_string(),
159                    CriterionProgress {
160                        date: Some(123456789),
161                    },
162                )]
163                .into_iter()
164                .collect(),
165            )]
166            .into_iter()
167            .collect(),
168            show_advancements: false,
169        };
170
171        let mut data = Vec::new();
172        packet.azalea_write(&mut data).unwrap();
173        let mut buf: Cursor<&[u8]> = Cursor::new(&data);
174
175        let read_packet = ClientboundUpdateAdvancements::azalea_read(&mut buf).unwrap();
176        assert_eq!(packet.reset, read_packet.reset);
177        assert_eq!(packet.removed, read_packet.removed);
178
179        let advancement = packet
180            .added
181            .into_iter()
182            .find_map(|a| {
183                if a.id == ResourceLocation::new("minecraft:test") {
184                    Some(a.value)
185                } else {
186                    None
187                }
188            })
189            .unwrap()
190            .clone();
191        let read_advancement = read_packet
192            .added
193            .into_iter()
194            .find_map(|a| {
195                if a.id == ResourceLocation::new("minecraft:test") {
196                    Some(a.value)
197                } else {
198                    None
199                }
200            })
201            .unwrap()
202            .clone();
203        assert_eq!(advancement.parent_id, read_advancement.parent_id);
204
205        let display = advancement.display.unwrap();
206        let read_display = read_advancement.display.unwrap();
207        assert_eq!(display.title, read_display.title);
208        assert_eq!(display.description, read_display.description);
209    }
210}