use pumpkin_data::attributes::Attributes; use pumpkin_data::entity::EntityType; use std::collections::HashMap; use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicU64; use std::sync::atomic::Ordering; #[derive(Clone, Debug, Copy)] #[repr(i8)] pub enum ModifierOperation { Add = 0, // add value MultiplyBase = 1, // multiply base (base * (1 + x)) MultiplyTotal = 2, // multiply total (applied last) } #[derive(Clone, Debug)] pub struct Modifier { pub id: String, pub amount: f64, pub operation: ModifierOperation, } /// Per-entity attribute instance used at runtime. #[derive(Debug)] pub struct AttributeInstance { pub base_value: f64, pub modifiers: Vec, pub cached_value: AtomicU64, pub dirty: AtomicBool, } impl AttributeInstance { #[must_use] pub const fn new(base_value: f64) -> Self { Self { base_value, modifiers: Vec::new(), cached_value: AtomicU64::new(base_value.to_bits()), dirty: AtomicBool::new(false), } } pub fn value(&self) -> f64 { if !self.dirty.load(Ordering::Relaxed) { return f64::from_bits(self.cached_value.load(Ordering::Relaxed)); } let mut value = self.base_value; let mut add_sum = 0.0; let mut mul_base = 0.0; let mut mul_total = 1.0; for m in &self.modifiers { match m.operation { ModifierOperation::Add => add_sum += m.amount, ModifierOperation::MultiplyBase => mul_base += m.amount, ModifierOperation::MultiplyTotal => mul_total *= 1.0 + m.amount, } } value += add_sum; value *= 1.0 + mul_base; value *= mul_total; if value.is_nan() || value.is_infinite() { value = self.base_value; } self.cached_value.store(value.to_bits(), Ordering::Relaxed); self.dirty.store(false, Ordering::Relaxed); value } pub fn add_or_replace_modifier(&mut self, modifier: Modifier) { if let Some(pos) = self.modifiers.iter().position(|m| m.id == modifier.id) { self.modifiers.remove(pos); } self.modifiers.push(modifier); self.dirty.store(true, Ordering::Relaxed); } pub fn remove_modifier(&mut self, id: &str) { if let Some(pos) = self.modifiers.iter().position(|m| m.id == id) { self.modifiers.swap_remove(pos); } self.dirty.store(true, Ordering::Relaxed); } } /// Send updates for multiple attributes in a single packet for the given living entity. pub fn send_attribute_updates_for_living( living: &crate::entity::living::LivingEntity, attributes: Vec, ) { use pumpkin_protocol::bedrock::client::update_attributes::{ AttributeData as BeAttribute, CUpdateAttributes as BePacket, }; use pumpkin_protocol::codec::var_int::VarInt; use pumpkin_protocol::codec::var_ulong::VarULong; use pumpkin_protocol::java::client::play::AttributeModifier as JeAttrMod; use pumpkin_protocol::java::client::play::CUpdateAttributes as JePacket; use pumpkin_protocol::java::client::play::Property as JeProperty; let mut je_properties: Vec = Vec::with_capacity(attributes.len()); let mut be_attributes: Vec = Vec::with_capacity(attributes.len()); for attribute in attributes { let base_value = living.get_attribute_base(&attribute); let effective_value = living.get_attribute_value(&attribute); // Pull modifiers for this attribute let mut modifiers = Vec::new(); if let Some(inst) = living .attributes .read() .unwrap_or_else(std::sync::PoisonError::into_inner) .get(&attribute.id) { for mod_inst in &inst.modifiers { modifiers.push(JeAttrMod::new( mod_inst.id.clone(), mod_inst.amount, mod_inst.operation as i8, )); } } // Move modifiers into the property je_properties.push(JeProperty::new( VarInt(i32::from(attribute.id)), base_value, modifiers, )); let name = match attribute.id { 22 => "minecraft:movement".to_string(), 19 => "minecraft:health".to_string(), 18 => "minecraft:absorption".to_string(), 2 => "minecraft:attack_damage".to_string(), 0 => "minecraft:armor".to_string(), 16 => "minecraft:knockback_resistance".to_string(), 17 => "minecraft:luck".to_string(), 13 => "minecraft:follow_range".to_string(), 15 => "minecraft:horse.jump_strength".to_string(), // Fallback for others _ => format!("minecraft:attribute.{}", attribute.id), }; let be_attribute = BeAttribute { min_value: 0.0, max_value: 3.402_823_5E38, current_value: effective_value as f32, default_min_value: 0.0, default_max_value: 3.402_823_5E38, default_value: base_value as f32, name, // Bedrock receives the already-computed effective value above. Do not advertise // modifier entries until their payload is encoded as well. modifiers: Vec::new(), }; be_attributes.push(be_attribute); } let je_packet = JePacket::new(living.entity.entity_id.into(), je_properties); let runtime_id = living.entity.entity_id as u64; let be_packet = BePacket { target_runtime_id: VarULong(runtime_id), attribute_list: be_attributes, tick: VarULong(0), }; living .entity .world .load() .broadcast_editioned(&je_packet, &be_packet); } impl Clone for AttributeInstance { fn clone(&self) -> Self { Self { base_value: self.base_value, modifiers: self.modifiers.clone(), cached_value: AtomicU64::new(self.cached_value.load(Ordering::Relaxed)), dirty: AtomicBool::new(self.dirty.load(Ordering::Relaxed)), } } } /// Registry storing per-entity-type base attribute overrides. /// Internally stores a map from `entity_type.id` -> `HashMap` for O(1) lookup. #[derive(Default)] pub struct AttributeRegistry { map: HashMap>, } impl AttributeRegistry { #[must_use] pub fn new() -> Self { Self::default() } /// Get the base value for `attribute` for the given entity type id. /// If no override exists, returns `attribute.default_value`. #[must_use] pub fn get_base_value(&self, entity_type_id: u16, attribute: &Attributes) -> f64 { self.map .get(&entity_type_id) .and_then(|map| map.get(&attribute.id)) .copied() .unwrap_or(attribute.default_value) } /// Return a vector of overrides for the given entity type id. /// This allows populating per-entity local attribute instances at spawn time. #[must_use] pub fn get_overrides_for_entity(&self, entity_type_id: u16) -> Option> { self.map .get(&entity_type_id) .map(|m| m.iter().map(|(&k, &v)| (k, v)).collect()) } } /// Builder to declaratively assemble attribute overrides for an entity type. #[derive(Default)] pub struct AttributeBuilder { entries: Vec<(Attributes, f64)>, } impl AttributeBuilder { #[must_use] pub fn new() -> Self { Self::default() } #[must_use] pub fn add(mut self, attribute: Attributes, base: f64) -> Self { self.entries.push((attribute, base)); self } #[must_use] pub fn build(self) -> Vec<(Attributes, f64)> { self.entries } } impl AttributeRegistry { /// Register overrides created by an `AttributeBuilder` for `entity_type`. pub fn register_builder( &mut self, entity_type: &'static EntityType, builder: AttributeBuilder, ) { let inner = self.map.entry(entity_type.id).or_default(); for (attr, val) in builder.build() { inner.insert(attr.id, val); } } }