azalea_entity/
attributes.rs

1//! See <https://minecraft.wiki/w/Attribute>.
2
3use std::collections::{HashMap, hash_map};
4
5use azalea_buf::AzBuf;
6use azalea_core::resource_location::ResourceLocation;
7use bevy_ecs::component::Component;
8use thiserror::Error;
9
10#[derive(Clone, Debug, Component)]
11pub struct Attributes {
12    pub speed: AttributeInstance,
13    pub sneaking_speed: AttributeInstance,
14    pub attack_speed: AttributeInstance,
15    pub water_movement_efficiency: AttributeInstance,
16
17    pub block_interaction_range: AttributeInstance,
18    pub entity_interaction_range: AttributeInstance,
19}
20
21#[derive(Clone, Debug)]
22pub struct AttributeInstance {
23    pub base: f64,
24    modifiers_by_id: HashMap<ResourceLocation, AttributeModifier>,
25}
26
27#[derive(Clone, Debug, Error)]
28#[error("A modifier with this UUID is already present.")]
29pub struct AlreadyPresentError;
30
31impl AttributeInstance {
32    pub fn new(base: f64) -> Self {
33        Self {
34            base,
35            modifiers_by_id: HashMap::new(),
36        }
37    }
38
39    pub fn calculate(&self) -> f64 {
40        let mut total = self.base;
41        for modifier in self.modifiers_by_id.values() {
42            match modifier.operation {
43                AttributeModifierOperation::Addition => total += modifier.amount,
44                AttributeModifierOperation::MultiplyBase => total += self.base * modifier.amount,
45                _ => {}
46            }
47            if let AttributeModifierOperation::MultiplyTotal = modifier.operation {
48                total *= 1.0 + modifier.amount;
49            }
50        }
51        total
52    }
53
54    /// Add a new modifier to this attribute and return the previous value, if
55    /// present.
56    pub fn insert(&mut self, modifier: AttributeModifier) -> Option<AttributeModifier> {
57        self.modifiers_by_id.insert(modifier.id.clone(), modifier)
58    }
59
60    /// Insert the given modifier if it's not already present, otherwise returns
61    /// [`AlreadyPresentError`].
62    pub fn try_insert(&mut self, modifier: AttributeModifier) -> Result<(), AlreadyPresentError> {
63        match self.modifiers_by_id.entry(modifier.id.clone()) {
64            hash_map::Entry::Occupied(_) => Err(AlreadyPresentError),
65            hash_map::Entry::Vacant(entry) => {
66                entry.insert(modifier);
67                Ok(())
68            }
69        }
70    }
71
72    /// Remove the modifier with the given ID from this attribute, returning
73    /// the previous modifier is present.
74    pub fn remove(&mut self, id: &ResourceLocation) -> Option<AttributeModifier> {
75        self.modifiers_by_id.remove(id)
76    }
77}
78
79#[derive(Clone, Debug, AzBuf)]
80pub struct AttributeModifier {
81    pub id: ResourceLocation,
82    pub amount: f64,
83    pub operation: AttributeModifierOperation,
84}
85
86#[derive(Clone, Debug, Copy, AzBuf)]
87pub enum AttributeModifierOperation {
88    Addition,
89    MultiplyBase,
90    MultiplyTotal,
91}
92
93pub fn sprinting_modifier() -> AttributeModifier {
94    AttributeModifier {
95        id: ResourceLocation::new("sprinting"),
96        amount: 0.3f32 as f64,
97        operation: AttributeModifierOperation::MultiplyTotal,
98    }
99}
100pub fn base_attack_speed_modifier(amount: f64) -> AttributeModifier {
101    AttributeModifier {
102        id: ResourceLocation::new("base_attack_speed"),
103        amount,
104        operation: AttributeModifierOperation::Addition,
105    }
106}
107pub fn creative_block_interaction_range_modifier() -> AttributeModifier {
108    AttributeModifier {
109        id: ResourceLocation::new("creative_mode_block_range"),
110        amount: 0.5,
111        operation: AttributeModifierOperation::Addition,
112    }
113}
114
115pub fn creative_entity_interaction_range_modifier() -> AttributeModifier {
116    AttributeModifier {
117        id: ResourceLocation::new("creative_mode_entity_range"),
118        amount: 2.0,
119        operation: AttributeModifierOperation::Addition,
120    }
121}