dfraw_json_parser/parser/plant/
raw.rs

1use serde::{Deserialize, Serialize};
2use slug::slugify;
3use tracing::{debug, warn};
4
5use crate::parser::{
6    biome,
7    helpers::parse_min_max_range,
8    material::{Material, PROPERTY_TOKEN_MAP, USAGE_TOKEN_MAP},
9    plant_growth::{
10        PlantGrowth, Token as GrowthToken, TypeToken as GrowthTypeToken,
11        TOKEN_MAP as GROWTH_TOKEN_MAP, TYPE_TOKEN_MAP as GROWTH_TYPE_TOKEN_MAP,
12    },
13    serializer_helper,
14    shrub::{Shrub, TOKEN_MAP as SHRUB_TOKEN_MAP},
15    tree::{Tree, TOKEN_MAP as TREE_TOKEN_MAP},
16    Name, ObjectType, {clean_search_vec, Searchable}, {RawMetadata, RawObject},
17};
18
19use super::{phf_table::PLANT_TOKENS, tokens::PlantTag};
20
21/// A struct representing a plant
22#[allow(clippy::module_name_repetitions)]
23#[derive(Serialize, Deserialize, Debug, Clone, Default, specta::Type)]
24#[serde(rename_all = "camelCase")]
25pub struct Plant {
26    /// Common Raw file Things
27    #[serde(skip_serializing_if = "Option::is_none")]
28    metadata: Option<RawMetadata>,
29    identifier: String,
30    object_id: String,
31
32    // Basic Tokens
33    name: Name,
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pref_strings: Option<Vec<String>>,
36    #[serde(skip_serializing_if = "Option::is_none")]
37    tags: Option<Vec<PlantTag>>,
38
39    // Environment Tokens
40    /// Default [0, 0] (aboveground)
41    #[serde(skip_serializing_if = "Option::is_none")]
42    underground_depth: Option<[u32; 2]>,
43    /// Default frequency is 50
44    #[serde(skip_serializing_if = "Option::is_none")]
45    frequency: Option<u32>,
46    /// List of biomes this plant can grow in
47    #[serde(skip_serializing_if = "Option::is_none")]
48    biomes: Option<Vec<biome::Token>>,
49
50    /// Growth Tokens define the growths of the plant (leaves, fruit, etc.)
51    #[serde(skip_serializing_if = "Option::is_none")]
52    growths: Option<Vec<PlantGrowth>>,
53    /// If plant is a tree, it will have details about the tree.
54    #[serde(skip_serializing_if = "Option::is_none")]
55    tree_details: Option<Tree>,
56    /// If plant is a shrub, it will have details about the shrub.
57    #[serde(skip_serializing_if = "Option::is_none")]
58    shrub_details: Option<Shrub>,
59
60    #[serde(skip_serializing_if = "Option::is_none")]
61    materials: Option<Vec<Material>>,
62}
63
64impl Plant {
65    /// Create a new empty plant
66    ///
67    /// # Returns
68    ///
69    /// A new empty plant
70    #[must_use]
71    pub fn empty() -> Self {
72        Self {
73            metadata: Some(
74                RawMetadata::default()
75                    .with_object_type(ObjectType::Plant)
76                    .with_hidden(true),
77            ),
78            frequency: Some(50),
79            ..Self::default()
80        }
81    }
82    /// Create a new plant based on an identifier and metadata
83    ///
84    /// # Arguments
85    ///
86    /// * `identifier` - The identifier of the plant
87    /// * `metadata` - The metadata of the plant
88    ///
89    /// # Returns
90    ///
91    /// A new plant
92    #[must_use]
93    pub fn new(identifier: &str, metadata: &RawMetadata) -> Self {
94        Self {
95            identifier: String::from(identifier),
96            metadata: Some(metadata.clone()),
97            frequency: Some(50),
98            object_id: format!(
99                "{}-{}-{}",
100                metadata.get_raw_identifier(),
101                "PLANT",
102                slugify(identifier)
103            ),
104            ..Self::default()
105        }
106    }
107    /// Get the biomes the plant can grow in
108    ///
109    /// # Returns
110    ///
111    /// A vector of biomes the plant can grow in
112    #[must_use]
113    pub fn get_biomes(&self) -> Vec<biome::Token> {
114        self.biomes
115            .as_ref()
116            .map_or_else(Vec::new, std::clone::Clone::clone)
117    }
118
119    /// Function to "clean" the raw. This is used to remove any empty list or strings,
120    /// and to remove any default values. By "removing" it means setting the value to None.
121    ///
122    /// This also will remove the metadata if `is_metadata_hidden` is true.
123    ///
124    /// Steps for all "Option" fields:
125    /// - Set any metadata to None if `is_metadata_hidden` is true.
126    /// - Set any empty string to None.
127    /// - Set any empty list to None.
128    /// - Set any default values to None.
129    ///
130    /// # Returns
131    ///
132    /// A new plant with all empty or default values removed.
133    #[must_use]
134    pub fn cleaned(&self) -> Self {
135        let mut cleaned = self.clone();
136
137        if let Some(metadata) = &cleaned.metadata {
138            if metadata.is_hidden() {
139                cleaned.metadata = None;
140            }
141        }
142
143        if let Some(pref_strings) = &cleaned.pref_strings {
144            if pref_strings.is_empty() {
145                cleaned.pref_strings = None;
146            }
147        }
148
149        if let Some(tags) = &cleaned.tags {
150            if tags.is_empty() {
151                cleaned.tags = None;
152            }
153        }
154
155        if serializer_helper::min_max_is_zeroes(&cleaned.underground_depth) {
156            cleaned.underground_depth = None;
157        }
158
159        if serializer_helper::is_default_frequency(cleaned.frequency) {
160            cleaned.frequency = None;
161        }
162
163        if let Some(biomes) = &cleaned.biomes {
164            if biomes.is_empty() {
165                cleaned.biomes = None;
166            }
167        }
168
169        if let Some(growths) = &cleaned.growths {
170            let mut cleaned_growths = Vec::new();
171            for growth in growths {
172                cleaned_growths.push(growth.cleaned());
173            }
174            cleaned.growths = Some(cleaned_growths);
175        }
176
177        if let Some(materials) = &cleaned.materials {
178            let mut cleaned_materials = Vec::new();
179            for material in materials {
180                cleaned_materials.push(material.cleaned());
181            }
182            if cleaned_materials.is_empty() {
183                cleaned.materials = None;
184            }
185            cleaned.materials = Some(cleaned_materials);
186        }
187
188        cleaned
189    }
190    /// Add a tag to the plant.
191    ///
192    /// This handles making sure the tags vector is initialized.
193    ///
194    /// # Arguments
195    ///
196    /// * `tag` - The tag to add to the plant
197    pub fn add_tag(&mut self, tag: PlantTag) {
198        if self.tags.is_none() {
199            self.tags = Some(Vec::new());
200        }
201        if let Some(tags) = self.tags.as_mut() {
202            tags.push(tag);
203        } else {
204            warn!(
205                "Plant::add_tag: ({}) Failed to add tag {:?}",
206                self.identifier, tag
207            );
208        }
209    }
210}
211
212#[typetag::serde]
213impl RawObject for Plant {
214    fn get_metadata(&self) -> RawMetadata {
215        self.metadata.as_ref().map_or_else(
216            || {
217                warn!(
218                    "PlantParsing: Failed to get metadata for plant {}",
219                    self.identifier
220                );
221                RawMetadata::default()
222                    .with_object_type(ObjectType::Plant)
223                    .with_hidden(true)
224            },
225            std::clone::Clone::clone,
226        )
227    }
228    fn get_identifier(&self) -> &str {
229        &self.identifier
230    }
231    fn get_name(&self) -> &str {
232        self.name.get_singular()
233    }
234    fn is_empty(&self) -> bool {
235        self.identifier.is_empty()
236    }
237
238    fn clean_self(&mut self) {
239        *self = self.cleaned();
240    }
241    fn get_type(&self) -> &ObjectType {
242        &ObjectType::Plant
243    }
244    #[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
245    fn parse_tag(&mut self, key: &str, value: &str) {
246        if (PROPERTY_TOKEN_MAP.contains_key(key) || USAGE_TOKEN_MAP.contains_key(key))
247            && !key.eq("USE_MATERIAL_TEMPLATE")
248        {
249            // have our latest material parse the tag
250            if let Some(materials) = self.materials.as_mut() {
251                if let Some(material) = materials.last_mut() {
252                    material.parse_tag(key, value);
253                } else {
254                    warn!(
255                        "PlantParsing: Failed to find material to add tag {} with value {}",
256                        key, value
257                    );
258                }
259            }
260            return;
261        }
262
263        if TREE_TOKEN_MAP.contains_key(key) {
264            if self.tree_details.is_none() {
265                self.tree_details = Some(Tree::new(value));
266            }
267            #[allow(clippy::unwrap_used)]
268            let tree = self.tree_details.as_mut().unwrap();
269            tree.parse_tag(key, value);
270            return;
271        }
272
273        if GROWTH_TOKEN_MAP.contains_key(key) {
274            if self.growths.is_none() {
275                self.growths = Some(Vec::new());
276            }
277            let token = GROWTH_TOKEN_MAP.get(key).unwrap_or(&GrowthToken::Unknown);
278            if token == &GrowthToken::Growth {
279                // If we are defining a new growth, we need to create a new PlantGrowth
280                let growth_type = GROWTH_TYPE_TOKEN_MAP
281                    .get(value)
282                    .unwrap_or(&GrowthTypeToken::None)
283                    .clone();
284                let growth = PlantGrowth::new(growth_type);
285                if let Some(growths) = self.growths.as_mut() {
286                    growths.push(growth);
287                }
288                return;
289            }
290            // Otherwise, we are defining a tag for the current growth (most recently added)
291            if let Some(growths) = self.growths.as_mut() {
292                if let Some(growth) = growths.last_mut() {
293                    growth.parse_tag(key, value);
294                } else {
295                    warn!(
296                        "PlantParsing: Failed to find growth to add tag {} with value {}",
297                        key, value
298                    );
299                }
300            }
301            return;
302        }
303
304        if SHRUB_TOKEN_MAP.contains_key(key) {
305            if self.shrub_details.is_none() {
306                self.shrub_details = Some(Shrub::new());
307            }
308            self.shrub_details
309                .as_mut()
310                .unwrap_or(&mut Shrub::default())
311                .parse_tag(key, value);
312            return;
313        }
314
315        if !PLANT_TOKENS.contains_key(key) {
316            debug!("PlantParsing: Unknown tag {} with value {}", key, value);
317            return;
318        }
319
320        let Some(tag) = PLANT_TOKENS.get(key) else {
321            warn!(
322                "PlantParsing: called `Option::unwrap()` on a `None` value for presumed plant tag: {}",
323                key
324            );
325            return;
326        };
327
328        match tag {
329            PlantTag::NameSingular => {
330                self.name.update_singular(value);
331            }
332            PlantTag::NamePlural => {
333                self.name.update_plural(value);
334            }
335            PlantTag::NameAdjective => {
336                self.name.update_adjective(value);
337            }
338            PlantTag::AllNames => {
339                self.name = Name::from_value(value);
340            }
341            PlantTag::PrefString => {
342                if self.pref_strings.is_none() {
343                    self.pref_strings = Some(Vec::new());
344                }
345                if let Some(pref_strings) = &mut self.pref_strings {
346                    pref_strings.push(String::from(value));
347                }
348            }
349            PlantTag::Biome => {
350                let Some(biome) = biome::TOKEN_MAP.get(value) else {
351                    warn!(
352                        "PlantParsing: called `Option::unwrap()` on a `None` value for presumed biome: {}",
353                        value
354                    );
355                    return;
356                };
357                if self.biomes.is_none() {
358                    self.biomes = Some(Vec::new());
359                }
360                if let Some(biomes) = &mut self.biomes {
361                    biomes.push(biome.clone());
362                }
363            }
364            PlantTag::UndergroundDepth => {
365                self.underground_depth = Some(parse_min_max_range(value).unwrap_or([0, 0]));
366            }
367            PlantTag::Frequency => {
368                self.frequency = Some(value.parse::<u32>().unwrap_or(50));
369            }
370            PlantTag::UseMaterialTemplate => {
371                if self.materials.is_none() {
372                    self.materials = Some(Vec::new());
373                }
374                if let Some(materials) = self.materials.as_mut() {
375                    materials.push(Material::use_material_template_from_value(value));
376                }
377            }
378            PlantTag::UseMaterial => {
379                if self.materials.is_none() {
380                    self.materials = Some(Vec::new());
381                }
382                if let Some(materials) = self.materials.as_mut() {
383                    materials.push(Material::use_material_from_value(value));
384                }
385            }
386            PlantTag::BasicMaterial => {
387                if self.materials.is_none() {
388                    self.materials = Some(Vec::new());
389                }
390                if let Some(materials) = self.materials.as_mut() {
391                    materials.push(Material::basic_material_from_value(value));
392                }
393            }
394            PlantTag::Material => {
395                if self.materials.is_none() {
396                    self.materials = Some(Vec::new());
397                }
398                if let Some(materials) = self.materials.as_mut() {
399                    materials.push(Material::from_value(value));
400                }
401            }
402            _ => {
403                self.add_tag(tag.clone());
404            }
405        }
406    }
407
408    fn get_object_id(&self) -> &str {
409        &self.object_id
410    }
411}
412
413impl Searchable for Plant {
414    fn get_search_vec(&self) -> Vec<String> {
415        let mut vec = Vec::new();
416
417        vec.push(self.get_identifier().to_string());
418        vec.extend(self.name.as_vec());
419        if let Some(pref_strings) = &self.pref_strings {
420            vec.extend(pref_strings.clone());
421        }
422        if let Some(biomes) = &self.biomes {
423            vec.extend(biomes.iter().map(std::string::ToString::to_string));
424        }
425        if let Some(tags) = &self.tags {
426            vec.extend(tags.iter().map(std::string::ToString::to_string));
427        }
428        if let Some(growths) = &self.growths {
429            vec.extend(growths.iter().flat_map(Searchable::get_search_vec));
430        }
431        if let Some(materials) = &self.materials {
432            vec.extend(materials.iter().flat_map(Searchable::get_search_vec));
433        }
434
435        clean_search_vec(vec.as_slice())
436    }
437}