azalea_entity/
attributes.rs

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