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