azalea_protocol/packets/game/
c_update_advancements.rs

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