Skip to main content

rusterix/server/
item.rs

1use theframework::prelude::*;
2
3use crate::prelude::*;
4
5#[derive(Serialize, Deserialize, Clone, Debug)]
6pub struct Item {
7    /// The unique ID of the item
8    pub id: u32,
9
10    /// The position in the map (if not on an entity).
11    pub position: Vec3<f32>,
12
13    /// Maps the item to a creator ID
14    pub creator_id: Uuid,
15
16    /// The item's type or identifier (e.g., "Potion", "Sword")
17    pub item_type: String,
18
19    /// Maximum capacity of this container/stack (e.g., max stack size for stackable items)
20    pub max_capacity: u32,
21
22    /// Container: Holds nested items if this item can act as a container
23    pub container: Option<Vec<Item>>,
24
25    /// Attributes: Dynamic properties of the item
26    pub attributes: ValueContainer,
27
28    /// Dirty flags for static attributes
29    pub dirty_flags: u8,
30
31    /// Dirty dynamic attributes
32    pub dirty_attributes: FxHashSet<String>,
33}
34
35impl Default for Item {
36    fn default() -> Self {
37        Self::new()
38    }
39}
40
41impl Item {
42    pub fn new() -> Self {
43        Self {
44            id: 0,
45            position: Vec3::new(0.0, 1.0, 0.0),
46            creator_id: Uuid::new_v4(),
47            item_type: String::new(),
48            max_capacity: 1, // Default to 1 for non-stackable, non-container items
49            container: None,
50            attributes: ValueContainer::default(),
51            dirty_flags: 0,
52            dirty_attributes: FxHashSet::default(),
53        }
54    }
55
56    /// Get the XZ position.
57    pub fn get_pos_xz(&self) -> Vec2<f32> {
58        Vec2::new(self.position.x, self.position.z)
59    }
60
61    /// Set the position and mark it as dirty
62    pub fn set_position(&mut self, new_position: Vec3<f32>) {
63        if self.position != new_position {
64            self.position = new_position;
65            self.mark_dirty_field(0b0100);
66        }
67    }
68
69    /// Set the position and mark it as dirty
70    pub fn set_max_capacity(&mut self, new_max_capacity: u32) {
71        if self.max_capacity != new_max_capacity {
72            self.max_capacity = new_max_capacity;
73            self.mark_dirty_field(0b0010);
74        }
75    }
76
77    /// Check if the item is a container or stackable
78    pub fn is_container(&self) -> bool {
79        self.container.is_some()
80    }
81
82    /// Check if there's space in the container
83    pub fn has_space(&self) -> bool {
84        if let Some(container) = &self.container {
85            container.len() < self.max_capacity as usize
86        } else {
87            false
88        }
89    }
90
91    /// Add an item to the container
92    pub fn add_to_container(&mut self, item: Item) -> Result<(), String> {
93        if let Some(container) = self.container.as_mut() {
94            if container.len() < self.max_capacity as usize {
95                container.push(item);
96                self.mark_dirty_field(0b0001);
97                Ok(())
98            } else {
99                Err("Container is full.".to_string())
100            }
101        } else {
102            Err("This item is not a container.".to_string())
103        }
104    }
105
106    /// Remove an item from the container by ID
107    pub fn remove_from_container(&mut self, item_id: u32) -> Result<Item, String> {
108        self.mark_dirty_field(0b0001);
109        if let Some(container) = self.container.as_mut() {
110            if let Some(index) = container.iter().position(|item| item.id == item_id) {
111                return Ok(container.remove(index));
112            }
113            Err("Item not found in container.".to_string())
114        } else {
115            Err("This item is not a container.".to_string())
116        }
117    }
118
119    /// Set a dynamic attribute and mark it as dirty
120    pub fn set_attribute(&mut self, key: &str, value: Value) {
121        self.attributes.set(key, value);
122        self.mark_dirty_attribute(key);
123    }
124
125    /// Get a dynamic attribute
126    pub fn get_attribute(&self, key: &str) -> Option<&Value> {
127        self.attributes.get(key)
128    }
129
130    /// Get the given String
131    pub fn get_attr_string(&self, key: &str) -> Option<String> {
132        self.attributes.get(key).map(|value| value.to_string())
133    }
134
135    /// Get the given Uuid
136    pub fn get_attr_uuid(&self, key: &str) -> Option<Uuid> {
137        if let Some(Value::Id(value)) = self.attributes.get(key) {
138            Some(*value)
139        } else {
140            None
141        }
142    }
143
144    /// Mark a static field as dirty
145    fn mark_dirty_field(&mut self, field: u8) {
146        self.dirty_flags |= field;
147    }
148
149    /// Mark a dynamic attribute as dirty
150    pub fn mark_dirty_attribute(&mut self, key: &str) {
151        self.dirty_attributes.insert(key.to_string());
152    }
153
154    /// Mark all fields and attributes as dirty
155    pub fn mark_all_dirty(&mut self) {
156        self.dirty_flags = 0b0111; // Mark all fields as dirty
157        for key in self.attributes.keys() {
158            self.dirty_attributes.insert(key.clone());
159        }
160        // Recursively mark all items in the container as dirty
161        if let Some(container) = &mut self.container {
162            for item in container.iter_mut() {
163                item.mark_all_dirty();
164            }
165        }
166    }
167
168    /// Clear all dirty flags and attributes
169    pub fn clear_dirty(&mut self) {
170        self.dirty_flags = 0;
171        self.dirty_attributes.clear();
172        // Recursively clear dirty flags for all items in the container
173        if let Some(container) = &mut self.container {
174            for item in container.iter_mut() {
175                item.clear_dirty();
176            }
177        }
178    }
179
180    /// Check if the item is dirty
181    pub fn is_dirty(&self) -> bool {
182        self.dirty_flags != 0
183            || !self.dirty_attributes.is_empty()
184            || self
185                .container
186                .as_ref()
187                .map(|c| c.iter().any(|item| item.is_dirty()))
188                .unwrap_or(false)
189    }
190
191    /// Generate an `ItemUpdate` containing only dirty fields and attributes
192    pub fn get_update(&self) -> ItemUpdate {
193        let mut updated_attributes = FxHashMap::default();
194        for key in &self.dirty_attributes {
195            if let Some(value) = self.attributes.get(key) {
196                updated_attributes.insert(key.clone(), value.clone());
197            }
198        }
199
200        let container_updates = self.container.as_ref().map(|container| {
201            container
202                .iter()
203                .filter(|item| item.is_dirty())
204                .flat_map(|item| [item.get_update()])
205                .collect()
206        });
207
208        ItemUpdate {
209            id: self.id,
210            creator_id: self.creator_id,
211            item_type: if self.dirty_flags & 0b0001 != 0 {
212                Some(self.item_type.clone())
213            } else {
214                None
215            },
216            max_capacity: if self.dirty_flags & 0b0010 != 0 {
217                Some(self.max_capacity)
218            } else {
219                None
220            },
221            position: if self.dirty_flags & 0b0100 != 0 {
222                Some(self.position)
223            } else {
224                None
225            },
226            attributes: updated_attributes,
227            container_updates,
228        }
229    }
230
231    /// Apply an `ItemUpdate` to this item
232    pub fn apply_update(&mut self, update: ItemUpdate) {
233        // Validate ID matches
234        if self.id != update.id {
235            eprintln!("Update ID does not match Item ID!");
236            return;
237        }
238
239        self.creator_id = update.creator_id;
240
241        // Update static fields
242        if let Some(new_item_type) = update.item_type {
243            self.item_type = new_item_type;
244        }
245        if let Some(new_max_capacity) = update.max_capacity {
246            self.max_capacity = new_max_capacity;
247        }
248        if let Some(new_position) = update.position {
249            self.position = new_position;
250        }
251
252        // Update dynamic attributes
253        for (key, value) in update.attributes {
254            self.attributes.set(&key, value.clone());
255        }
256
257        // Recursively apply updates to items in the container
258        if let Some(container_updates) = update.container_updates {
259            if let Some(container) = &mut self.container {
260                for update in container_updates {
261                    if let Some(item) = container.iter_mut().find(|item| item.id == update.id) {
262                        item.apply_update(update);
263                    }
264                }
265            }
266        }
267    }
268}
269
270/// Represents a partial update for an `Item`
271#[derive(Debug, Clone, Serialize, Deserialize)]
272pub struct ItemUpdate {
273    pub id: u32,
274    pub creator_id: Uuid,
275    pub item_type: Option<String>,
276    pub max_capacity: Option<u32>,
277    pub position: Option<Vec3<f32>>,
278    pub attributes: FxHashMap<String, Value>,
279    pub container_updates: Option<Vec<ItemUpdate>>,
280}
281
282impl ItemUpdate {
283    /// Serialize (pack) an `ItemUpdate` into a `Vec<u8>` using bincode, discarding errors
284    pub fn pack(&self) -> Vec<u8> {
285        bincode::serialize(self).unwrap_or_else(|_| Vec::new())
286    }
287
288    /// Deserialize (unpack) a `Vec<u8>` into an `ItemUpdate` using bincode, discarding errors
289    pub fn unpack(data: &[u8]) -> Self {
290        bincode::deserialize(data).unwrap_or_else(|_| Self {
291            id: 0,
292            creator_id: Uuid::nil(),
293            item_type: None,
294            max_capacity: None,
295            position: None,
296            attributes: FxHashMap::default(),
297            container_updates: None,
298        })
299    }
300}