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