use std::collections::HashMap;
use bevy_ecs::prelude::*;
use indexmap::IndexMap;
use uuid::Uuid;
pub use valence_generated::attributes::{EntityAttribute, EntityAttributeOperation};
use valence_protocol::packets::play::entity_attributes_s2c::*;
use valence_protocol::Ident;
#[derive(Component, Clone, PartialEq, Debug)]
pub struct EntityAttributeInstance {
attribute: EntityAttribute,
base_value: f64,
add_modifiers: IndexMap<Uuid, f64>,
multiply_base_modifiers: IndexMap<Uuid, f64>,
multiply_total_modifiers: IndexMap<Uuid, f64>,
}
impl EntityAttributeInstance {
pub fn new(attribute: EntityAttribute) -> Self {
Self {
attribute,
base_value: attribute.default_value(),
add_modifiers: IndexMap::new(),
multiply_base_modifiers: IndexMap::new(),
multiply_total_modifiers: IndexMap::new(),
}
}
pub fn new_with_value(attribute: EntityAttribute, base_value: f64) -> Self {
Self {
attribute,
base_value,
add_modifiers: IndexMap::new(),
multiply_base_modifiers: IndexMap::new(),
multiply_total_modifiers: IndexMap::new(),
}
}
pub fn attribute(&self) -> EntityAttribute {
self.attribute
}
pub fn base_value(&self) -> f64 {
self.base_value
}
pub fn compute_value(&self) -> f64 {
let mut value = self.base_value;
for (_, modifier) in &self.add_modifiers {
value += modifier;
}
let v = value;
for (_, modifier) in &self.multiply_base_modifiers {
value += v * modifier;
}
for (_, modifier) in &self.multiply_total_modifiers {
value += value * modifier;
}
value.clamp(self.attribute.min_value(), self.attribute.max_value())
}
pub fn with_add_modifier(&mut self, uuid: Uuid, modifier: f64) -> &mut Self {
self.add_modifiers.insert(uuid, modifier);
self
}
pub fn with_multiply_base_modifier(&mut self, uuid: Uuid, modifier: f64) -> &mut Self {
self.multiply_base_modifiers.insert(uuid, modifier);
self
}
pub fn with_multiply_total_modifier(&mut self, uuid: Uuid, modifier: f64) -> &mut Self {
self.multiply_total_modifiers.insert(uuid, modifier);
self
}
pub fn with_modifier(
&mut self,
uuid: Uuid,
modifier: f64,
operation: EntityAttributeOperation,
) -> &mut Self {
match operation {
EntityAttributeOperation::Add => self.with_add_modifier(uuid, modifier),
EntityAttributeOperation::MultiplyBase => {
self.with_multiply_base_modifier(uuid, modifier)
}
EntityAttributeOperation::MultiplyTotal => {
self.with_multiply_total_modifier(uuid, modifier)
}
}
}
pub fn remove_modifier(&mut self, uuid: Uuid) {
self.add_modifiers.swap_remove(&uuid);
self.multiply_base_modifiers.swap_remove(&uuid);
self.multiply_total_modifiers.swap_remove(&uuid);
}
pub fn clear_modifiers(&mut self) {
self.add_modifiers.clear();
self.multiply_base_modifiers.clear();
self.multiply_total_modifiers.clear();
}
pub fn has_modifier(&self, uuid: Uuid) -> bool {
self.add_modifiers.contains_key(&uuid)
|| self.multiply_base_modifiers.contains_key(&uuid)
|| self.multiply_total_modifiers.contains_key(&uuid)
}
pub(crate) fn to_property(&self) -> TrackedEntityProperty {
TrackedEntityProperty {
key: self.attribute.name().into(),
value: self.base_value(),
modifiers: self
.add_modifiers
.iter()
.map(|(&uuid, &amount)| TrackedAttributeModifier {
uuid,
amount,
operation: 0,
})
.chain(self.multiply_base_modifiers.iter().map(|(&uuid, &amount)| {
TrackedAttributeModifier {
uuid,
amount,
operation: 1,
}
}))
.chain(
self.multiply_total_modifiers
.iter()
.map(|(&uuid, &amount)| TrackedAttributeModifier {
uuid,
amount,
operation: 2,
}),
)
.collect(),
}
}
}
#[derive(Component, Clone, PartialEq, Debug, Default)]
pub struct EntityAttributes {
attributes: HashMap<EntityAttribute, EntityAttributeInstance>,
recently_changed: Vec<EntityAttribute>,
}
impl EntityAttributes {
pub(crate) fn take_recently_changed(&mut self) -> Vec<EntityAttribute> {
std::mem::take(&mut self.recently_changed)
}
pub(crate) fn mark_recently_changed(&mut self, attribute: EntityAttribute) {
if attribute.tracked() && !self.recently_changed.contains(&attribute) {
self.recently_changed.push(attribute);
}
}
}
impl EntityAttributes {
pub fn new() -> Self {
Self {
attributes: HashMap::new(),
recently_changed: Vec::new(),
}
}
pub fn get(&self, attribute: EntityAttribute) -> Option<&EntityAttributeInstance> {
self.attributes.get(&attribute)
}
pub fn get_base_value(&self, attribute: EntityAttribute) -> Option<f64> {
self.get(attribute).map(|instance| instance.base_value())
}
pub fn get_compute_value(&self, attribute: EntityAttribute) -> Option<f64> {
self.get(attribute).map(|instance| instance.compute_value())
}
pub fn has_attribute(&self, attribute: EntityAttribute) -> bool {
self.attributes.contains_key(&attribute)
}
pub fn create_attribute(&mut self, attribute: EntityAttribute) {
self.mark_recently_changed(attribute);
self.attributes
.entry(attribute)
.or_insert_with(|| EntityAttributeInstance::new(attribute));
}
pub(crate) fn with_attribute_and_value(
mut self,
attribute: EntityAttribute,
base_value: f64,
) -> Self {
self.attributes
.entry(attribute)
.or_insert_with(|| EntityAttributeInstance::new_with_value(attribute, base_value))
.base_value = base_value;
self
}
pub fn set_base_value(&mut self, attribute: EntityAttribute, value: f64) {
self.mark_recently_changed(attribute);
self.attributes
.entry(attribute)
.or_insert_with(|| EntityAttributeInstance::new(attribute))
.base_value = value;
}
pub fn set_add_modifier(&mut self, attribute: EntityAttribute, uuid: Uuid, modifier: f64) {
self.mark_recently_changed(attribute);
self.attributes
.entry(attribute)
.or_insert_with(|| EntityAttributeInstance::new(attribute))
.with_add_modifier(uuid, modifier);
}
pub fn set_multiply_base_modifier(
&mut self,
attribute: EntityAttribute,
uuid: Uuid,
modifier: f64,
) {
self.mark_recently_changed(attribute);
self.attributes
.entry(attribute)
.or_insert_with(|| EntityAttributeInstance::new(attribute))
.with_multiply_base_modifier(uuid, modifier);
}
pub fn set_multiply_total_modifier(
&mut self,
attribute: EntityAttribute,
uuid: Uuid,
modifier: f64,
) {
self.mark_recently_changed(attribute);
self.attributes
.entry(attribute)
.or_insert_with(|| EntityAttributeInstance::new(attribute))
.with_multiply_total_modifier(uuid, modifier);
}
pub fn set_modifier(
&mut self,
attribute: EntityAttribute,
uuid: Uuid,
modifier: f64,
operation: EntityAttributeOperation,
) {
self.mark_recently_changed(attribute);
self.attributes
.entry(attribute)
.or_insert_with(|| EntityAttributeInstance::new(attribute))
.with_modifier(uuid, modifier, operation);
}
pub fn remove_modifier(&mut self, attribute: EntityAttribute, uuid: Uuid) {
self.mark_recently_changed(attribute);
if let Some(instance) = self.attributes.get_mut(&attribute) {
instance.remove_modifier(uuid);
}
}
pub fn clear_modifiers(&mut self, attribute: EntityAttribute) {
self.mark_recently_changed(attribute);
if let Some(instance) = self.attributes.get_mut(&attribute) {
instance.clear_modifiers();
}
}
pub fn has_modifier(&self, attribute: EntityAttribute, uuid: Uuid) -> bool {
self.attributes
.get(&attribute)
.is_some_and(|inst| inst.has_modifier(uuid))
}
pub fn to_properties(&self) -> Vec<AttributeProperty> {
self.attributes
.iter()
.filter(|(_, instance)| instance.attribute().tracked())
.map(|(_, instance)| instance.to_property().to_property())
.collect()
}
}
#[derive(Component, Clone, Debug, Default)]
pub struct TrackedEntityAttributes {
modified: IndexMap<EntityAttribute, TrackedEntityProperty>,
}
#[derive(Clone, Debug)]
pub(crate) struct TrackedEntityProperty {
key: String,
value: f64,
modifiers: Vec<TrackedAttributeModifier>,
}
#[derive(Clone, Debug)]
pub(crate) struct TrackedAttributeModifier {
uuid: Uuid,
amount: f64,
operation: u8,
}
impl TrackedEntityProperty {
fn to_property(&self) -> AttributeProperty<'static> {
AttributeProperty {
key: Ident::new(self.key.clone()).unwrap(),
value: self.value,
modifiers: self
.modifiers
.iter()
.map(|modifier| AttributeModifier {
uuid: modifier.uuid,
amount: modifier.amount,
operation: modifier.operation,
})
.collect(),
}
}
}
impl TrackedEntityAttributes {
pub fn new() -> Self {
Self {
modified: IndexMap::new(),
}
}
pub fn mark_modified(&mut self, attributes: &EntityAttributes, attribute: EntityAttribute) {
if let Some(instance) = attributes.get(attribute) {
self.modified.insert(attribute, instance.to_property());
}
}
pub fn get_properties(&self) -> Vec<AttributeProperty<'static>> {
self.modified
.iter()
.map(|(_, property)| property.to_property())
.collect()
}
pub fn clear(&mut self) {
self.modified.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compute_value() {
let add_uuid = Uuid::new_v4();
let mut attributes = EntityAttributes::new();
attributes.set_base_value(EntityAttribute::GenericMaxHealth, 20.0);
attributes.set_add_modifier(EntityAttribute::GenericMaxHealth, add_uuid, 10.0);
attributes.set_multiply_base_modifier(
EntityAttribute::GenericMaxHealth,
Uuid::new_v4(),
0.2,
);
attributes.set_multiply_base_modifier(
EntityAttribute::GenericMaxHealth,
Uuid::new_v4(),
0.2,
);
attributes.set_multiply_total_modifier(
EntityAttribute::GenericMaxHealth,
Uuid::new_v4(),
0.5,
);
assert_eq!(
attributes.get_compute_value(EntityAttribute::GenericMaxHealth),
Some(63.0) );
attributes.remove_modifier(EntityAttribute::GenericMaxHealth, add_uuid);
assert_eq!(
attributes.get_compute_value(EntityAttribute::GenericMaxHealth),
Some(42.0) );
}
}