1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
//! See <https://minecraft.fandom.com/wiki/Attribute>.

use std::{
    collections::HashMap,
    io::{Cursor, Write},
};

use azalea_buf::{BufReadError, McBuf, McBufReadable, McBufWritable};
use bevy_ecs::component::Component;
use thiserror::Error;
use uuid::{uuid, Uuid};

#[derive(Clone, Debug, Component)]
pub struct Attributes {
    pub speed: AttributeInstance,
    pub attack_speed: AttributeInstance,
}

#[derive(Clone, Debug)]
pub struct AttributeInstance {
    pub base: f64,
    modifiers_by_uuid: HashMap<Uuid, AttributeModifier>,
}

#[derive(Clone, Debug, Error)]
#[error("A modifier with this UUID is already present.")]
pub struct AlreadyPresentError;

impl AttributeInstance {
    pub fn new(base: f64) -> Self {
        Self {
            base,
            modifiers_by_uuid: HashMap::new(),
        }
    }

    pub fn calculate(&self) -> f64 {
        let mut total = self.base;
        for modifier in self.modifiers_by_uuid.values() {
            match modifier.operation {
                AttributeModifierOperation::Addition => total += modifier.amount,
                AttributeModifierOperation::MultiplyBase => total += self.base * modifier.amount,
                _ => {}
            }
            if let AttributeModifierOperation::MultiplyTotal = modifier.operation {
                total *= 1.0 + modifier.amount;
            }
        }
        total
    }

    /// Add a new modifier to this attribute.
    pub fn insert(&mut self, modifier: AttributeModifier) -> Result<(), AlreadyPresentError> {
        if self
            .modifiers_by_uuid
            .insert(modifier.uuid, modifier)
            .is_some()
        {
            Err(AlreadyPresentError)
        } else {
            Ok(())
        }
    }

    /// Remove the modifier with the given UUID from this attribute, returning
    /// the previous modifier is present.
    pub fn remove(&mut self, uuid: &Uuid) -> Option<AttributeModifier> {
        self.modifiers_by_uuid.remove(uuid)
    }
}

#[derive(Clone, Debug)]
pub struct AttributeModifier {
    pub uuid: Uuid,
    pub name: String,
    pub amount: f64,
    pub operation: AttributeModifierOperation,
}

#[derive(Clone, Debug, Copy, McBuf)]
pub enum AttributeModifierOperation {
    Addition,
    MultiplyBase,
    MultiplyTotal,
}

pub fn sprinting_modifier() -> AttributeModifier {
    AttributeModifier {
        uuid: uuid!("662A6B8D-DA3E-4C1C-8813-96EA6097278D"),
        name: "Sprinting speed boost".to_string(),
        amount: 0.30000001192092896,
        operation: AttributeModifierOperation::MultiplyTotal,
    }
}

pub static BASE_ATTACK_SPEED_UUID: Uuid = uuid!("FA233E1C-4180-4865-B01B-BCCE9785ACA3");
pub fn weapon_attack_speed_modifier(amount: f64) -> AttributeModifier {
    AttributeModifier {
        uuid: BASE_ATTACK_SPEED_UUID,
        name: "Weapon modifier".to_string(),
        amount,
        operation: AttributeModifierOperation::Addition,
    }
}
pub fn tool_attack_speed_modifier(amount: f64) -> AttributeModifier {
    AttributeModifier {
        uuid: BASE_ATTACK_SPEED_UUID,
        name: "Tool modifier".to_string(),
        amount,
        operation: AttributeModifierOperation::Addition,
    }
}

impl McBufReadable for AttributeModifier {
    fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
        let uuid = Uuid::read_from(buf)?;
        let amount = f64::read_from(buf)?;
        let operation = AttributeModifierOperation::read_from(buf)?;
        Ok(Self {
            uuid,
            name: "Unknown synced attribute modifier".to_string(),
            amount,
            operation,
        })
    }
}

impl McBufWritable for AttributeModifier {
    fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
        self.uuid.write_into(buf)?;
        self.amount.write_into(buf)?;
        self.operation.write_into(buf)?;
        Ok(())
    }
}