valence_server/
status_effect.rs

1use bevy_app::prelude::*;
2use bevy_ecs::prelude::*;
3use bevy_ecs::query::QueryData;
4use bevy_ecs::system::SystemState;
5use valence_entity::active_status_effects::{ActiveStatusEffect, ActiveStatusEffects};
6use valence_entity::entity::Flags;
7use valence_entity::living::{PotionSwirlsAmbient, PotionSwirlsColor};
8use valence_protocol::packets::play::{
9    entity_status_effect_s2c, EntityStatusEffectS2c, RemoveEntityStatusEffectS2c,
10};
11use valence_protocol::status_effects::StatusEffect;
12use valence_protocol::{VarInt, WritePacket};
13
14use crate::client::Client;
15use crate::EventLoopPostUpdate;
16
17/// Event for when a status effect is added to an entity or the amplifier or
18/// duration of an existing status effect is changed.
19#[derive(Event, Clone, PartialEq, Eq, Debug)]
20pub struct StatusEffectAdded {
21    pub entity: Entity,
22    pub status_effect: StatusEffect,
23}
24
25/// Event for when a status effect is removed from an entity.
26#[derive(Event, Clone, PartialEq, Eq, Debug)]
27pub struct StatusEffectRemoved {
28    pub entity: Entity,
29    pub status_effect: ActiveStatusEffect,
30}
31
32pub struct StatusEffectPlugin;
33
34impl Plugin for StatusEffectPlugin {
35    fn build(&self, app: &mut App) {
36        app.add_event::<StatusEffectAdded>()
37            .add_event::<StatusEffectRemoved>()
38            .add_systems(
39                EventLoopPostUpdate,
40                (
41                    add_status_effects,
42                    update_active_status_effects,
43                    add_status_effects,
44                ),
45            );
46    }
47}
48
49fn update_active_status_effects(
50    world: &mut World,
51    state: &mut SystemState<Query<&mut ActiveStatusEffects>>,
52) {
53    let mut query = state.get_mut(world);
54    for mut active_status_effects in &mut query {
55        active_status_effects.increment_active_ticks();
56    }
57}
58
59fn create_packet(effect: &ActiveStatusEffect) -> EntityStatusEffectS2c {
60    EntityStatusEffectS2c {
61        entity_id: VarInt(0), // We reserve ID 0 for clients.
62        effect_id: VarInt(i32::from(effect.status_effect().to_raw())),
63        amplifier: effect.amplifier(),
64        duration: VarInt(effect.remaining_duration().unwrap_or(-1)),
65        flags: entity_status_effect_s2c::Flags::new()
66            .with_is_ambient(effect.ambient())
67            .with_show_particles(effect.show_particles())
68            .with_show_icon(effect.show_icon()),
69        factor_codec: None,
70    }
71}
72
73#[derive(QueryData)]
74#[query_data(mutable)]
75struct StatusEffectQuery {
76    entity: Entity,
77    active_effects: &'static mut ActiveStatusEffects,
78    client: Option<&'static mut Client>,
79    entity_flags: Option<&'static mut Flags>,
80    swirl_color: Option<&'static mut PotionSwirlsColor>,
81    swirl_ambient: Option<&'static mut PotionSwirlsAmbient>,
82}
83
84fn add_status_effects(
85    mut query: Query<StatusEffectQuery>,
86    mut add_events: EventWriter<StatusEffectAdded>,
87    mut remove_events: EventWriter<StatusEffectRemoved>,
88) {
89    for mut query in &mut query {
90        let updated = query.active_effects.apply_changes();
91
92        if updated.is_empty() {
93            continue;
94        }
95
96        set_swirl(
97            &query.active_effects,
98            &mut query.swirl_color,
99            &mut query.swirl_ambient,
100        );
101
102        for (status_effect, prev) in updated {
103            if query.active_effects.has_effect(status_effect) {
104                add_events.send(StatusEffectAdded {
105                    entity: query.entity,
106                    status_effect,
107                });
108            } else if let Some(prev) = prev {
109                remove_events.send(StatusEffectRemoved {
110                    entity: query.entity,
111                    status_effect: prev,
112                });
113            } else {
114                // this should never happen
115                panic!("status effect was removed but was never added");
116            }
117
118            update_status_effect(&mut query, status_effect);
119        }
120    }
121}
122
123fn update_status_effect(query: &mut StatusEffectQueryItem, status_effect: StatusEffect) {
124    let current_effect = query.active_effects.get_current_effect(status_effect);
125
126    if let Some(ref mut client) = query.client {
127        if let Some(updated_effect) = current_effect {
128            client.write_packet(&create_packet(updated_effect));
129        } else {
130            client.write_packet(&RemoveEntityStatusEffectS2c {
131                entity_id: VarInt(0),
132                effect_id: VarInt(i32::from(status_effect.to_raw())),
133            });
134        }
135    }
136}
137
138fn set_swirl(
139    active_status_effects: &ActiveStatusEffects,
140    swirl_color: &mut Option<Mut<'_, PotionSwirlsColor>>,
141    swirl_ambient: &mut Option<Mut<'_, PotionSwirlsAmbient>>,
142) {
143    if let Some(ref mut swirl_ambient) = swirl_ambient {
144        swirl_ambient.0 = active_status_effects
145            .get_current_effects()
146            .iter()
147            .any(|effect| effect.ambient());
148    }
149
150    if let Some(ref mut swirl_color) = swirl_color {
151        swirl_color.0 = get_color(active_status_effects);
152    }
153}
154
155/// Used to set the color of the swirls in the potion effect.
156///
157/// Equivalent to net.minecraft.potion.PotionUtil#getColor
158fn get_color(effects: &ActiveStatusEffects) -> i32 {
159    if effects.no_effects() {
160        // vanilla mc seems to return 0x385dc6 if there are no effects
161        // dunno why
162        // imma just say to return 0 to remove the swirls
163        return 0;
164    }
165
166    let effects = effects.get_current_effects();
167    let mut f = 0.0;
168    let mut g = 0.0;
169    let mut h = 0.0;
170    let mut j = 0.0;
171
172    for status_effect_instance in effects {
173        if !status_effect_instance.show_particles() {
174            continue;
175        }
176
177        let k = status_effect_instance.status_effect().color();
178        let l = f32::from(status_effect_instance.amplifier() + 1);
179        f += (l * ((k >> 16) & 0xff) as f32) / 255.0;
180        g += (l * ((k >> 8) & 0xff) as f32) / 255.0;
181        h += (l * ((k) & 0xff) as f32) / 255.0;
182        j += l;
183    }
184
185    if j == 0.0 {
186        return 0;
187    }
188
189    f = f / j * 255.0;
190    g = g / j * 255.0;
191    h = h / j * 255.0;
192
193    ((f as i32) << 16) | ((g as i32) << 8) | (h as i32)
194}