Pumpkin/crates/pumpkin/src/entity/attributes.rs
2026-08-26 14:18:00 +02:00

264 lines
8.2 KiB
Rust

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<Modifier>,
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<Attributes>,
) {
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<JeProperty> = Vec::with_capacity(attributes.len());
let mut be_attributes: Vec<BeAttribute> = 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`<attribute.id, f64> for O(1) lookup.
#[derive(Default)]
pub struct AttributeRegistry {
map: HashMap<u16, HashMap<u8, f64>>,
}
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<Vec<(u8, f64)>> {
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);
}
}
}