azalea_client/plugins/inventory/
equipment_effects.rs1use std::collections::HashMap;
4
5use azalea_core::{data_registry::ResolvableDataRegistry, registry_holder::value::AttributeEffect};
6use azalea_entity::{Attributes, inventory::Inventory};
7use azalea_inventory::{
8 ItemStack,
9 components::{self, AttributeModifier, EquipmentSlot},
10};
11use azalea_registry::identifier::Identifier;
12use bevy_ecs::{
13 component::Component,
14 entity::Entity,
15 event::EntityEvent,
16 observer::On,
17 query::With,
18 system::{Commands, Query},
19};
20use tracing::{debug, error, warn};
21
22use crate::local_player::InstanceHolder;
23
24#[derive(Component, Debug, Default)]
28pub struct LastEquipmentItems {
29 pub map: HashMap<EquipmentSlot, ItemStack>,
30}
31
32pub fn collect_equipment_changes(
33 mut commands: Commands,
34 mut query: Query<(Entity, &Inventory, Option<&LastEquipmentItems>), With<Attributes>>,
35) {
36 for (entity, inventory, last_equipment_items) in &mut query {
37 let last_equipment_items = if let Some(e) = last_equipment_items {
38 e
39 } else {
40 commands
41 .entity(entity)
42 .insert(LastEquipmentItems::default());
43 continue;
44 };
45
46 let mut changes = HashMap::new();
47
48 for equipment_slot in EquipmentSlot::values() {
49 let current_item = inventory
50 .get_equipment(equipment_slot)
51 .unwrap_or(&ItemStack::Empty);
52 let last_item = last_equipment_items
53 .map
54 .get(&equipment_slot)
55 .unwrap_or(&ItemStack::Empty);
56
57 if current_item == last_item {
58 continue;
60 }
61
62 changes.insert(
63 equipment_slot,
64 EquipmentChange {
65 old: last_item.clone(),
66 new: current_item.clone(),
67 },
68 );
69 }
70
71 if changes.is_empty() {
72 continue;
73 }
74 commands.trigger(EquipmentChangesEvent {
75 entity,
76 map: changes,
77 });
78 }
79}
80
81#[derive(Debug, EntityEvent)]
82pub struct EquipmentChangesEvent {
83 pub entity: Entity,
84 pub map: HashMap<EquipmentSlot, EquipmentChange>,
85}
86#[derive(Debug)]
87pub struct EquipmentChange {
88 pub old: ItemStack,
89 pub new: ItemStack,
90}
91
92pub fn handle_equipment_changes(
93 equipment_changes: On<EquipmentChangesEvent>,
94 mut query: Query<(&InstanceHolder, &mut LastEquipmentItems, &mut Attributes)>,
95) {
96 let Ok((instance_holder, mut last_equipment_items, mut attributes)) =
97 query.get_mut(equipment_changes.entity)
98 else {
99 error!(
100 "got EquipmentChangesEvent with unknown entity {}",
101 equipment_changes.entity
102 );
103 return;
104 };
105
106 if !equipment_changes.map.is_empty() {
107 debug!("equipment changes: {:?}", equipment_changes.map);
108 }
109
110 for (&slot, change) in &equipment_changes.map {
111 if change.old.is_present() {
112 for (attribute, modifier) in
115 collect_attribute_modifiers_from_item(slot, &change.old, instance_holder)
116 {
117 if let Some(attribute) = attributes.get_mut(attribute) {
118 attribute.remove(&modifier.id);
119 }
120 }
121
122 last_equipment_items.map.remove(&slot);
123 }
124
125 if change.new.is_present() {
126 for (attribute, modifier) in
129 collect_attribute_modifiers_from_item(slot, &change.new, instance_holder)
130 {
131 if let Some(attribute) = attributes.get_mut(attribute) {
132 attribute.remove(&modifier.id);
133 attribute.insert(modifier);
134 }
135 }
136
137 last_equipment_items.map.insert(slot, change.new.clone());
140 }
141 }
142}
143
144fn collect_attribute_modifiers_from_item(
145 slot: EquipmentSlot,
146 item: &ItemStack,
147 instance_holder: &InstanceHolder,
148) -> Vec<(azalea_registry::builtin::Attribute, AttributeModifier)> {
149 let mut modifiers = Vec::new();
150
151 let attribute_modifiers = item
153 .get_component::<components::AttributeModifiers>()
154 .unwrap_or_default();
155 for modifier in &attribute_modifiers.modifiers {
156 modifiers.push((modifier.kind, modifier.modifier.clone()));
157 }
158
159 let enchants = item
161 .get_component::<components::Enchantments>()
162 .unwrap_or_default();
163 if !enchants.levels.is_empty() {
164 let registry_holder = &instance_holder.instance.read().registries;
165 for (enchant, &level) in &enchants.levels {
166 let Some((_enchant_id, enchant_definition)) = enchant.resolve(registry_holder) else {
167 warn!(
168 "Got equipment with an enchantment that wasn't in the registry, so it couldn't be resolved to an ID"
169 );
170 continue;
171 };
172
173 let effects = enchant_definition.get::<AttributeEffect>();
174 for effect in effects.unwrap_or_default() {
175 let modifier = AttributeModifier {
178 id: Identifier::new(format!("{}/{slot}", effect.id)),
179 amount: effect.amount.calculate(level) as f64,
180 operation: effect.operation,
181 };
182
183 modifiers.push((effect.attribute, modifier));
184 }
185 }
186 }
187
188 modifiers
189}