chunkedge_server/
status_effect.rs

1use bevy_app::prelude::*;
2use bevy_ecs::prelude::*;
3use bevy_ecs::query::QueryData;
4use bevy_ecs::system::SystemState;
5use chunkedge_entity::active_status_effects::{ActiveStatusEffect, ActiveStatusEffects};
6use chunkedge_entity::entity::Flags;
7use chunkedge_entity::living::PotionSwirlsAmbient;
8use chunkedge_protocol::packets::play::{
9    update_mob_effect_s2c, RemoveMobEffectS2c, UpdateMobEffectS2c,
10};
11use chunkedge_protocol::status_effects::StatusEffect;
12use chunkedge_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) -> UpdateMobEffectS2c {
60    UpdateMobEffectS2c {
61        entity_id: VarInt(0), // We reserve ID 0 for clients.
62        effect_id: VarInt(i32::from(effect.status_effect().to_raw())),
63        amplifier: VarInt(effect.amplifier()),
64        duration: VarInt(effect.remaining_duration().unwrap_or(-1)),
65        flags: update_mob_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    }
70}
71
72#[derive(QueryData)]
73#[query_data(mutable)]
74struct StatusEffectQuery {
75    entity: Entity,
76    active_effects: &'static mut ActiveStatusEffects,
77    client: Option<&'static mut Client>,
78    entity_flags: Option<&'static mut Flags>,
79    swirl_ambient: Option<&'static mut PotionSwirlsAmbient>,
80}
81
82fn add_status_effects(
83    mut query: Query<StatusEffectQuery>,
84    mut add_events: EventWriter<StatusEffectAdded>,
85    mut remove_events: EventWriter<StatusEffectRemoved>,
86) {
87    for mut query in &mut query {
88        let updated = query.active_effects.apply_changes();
89
90        if updated.is_empty() {
91            continue;
92        }
93
94        set_swirl(&query.active_effects, &mut query.swirl_ambient);
95
96        for (status_effect, prev) in updated {
97            if query.active_effects.has_effect(status_effect) {
98                add_events.send(StatusEffectAdded {
99                    entity: query.entity,
100                    status_effect,
101                });
102            } else if let Some(prev) = prev {
103                remove_events.send(StatusEffectRemoved {
104                    entity: query.entity,
105                    status_effect: prev,
106                });
107            } else {
108                // this should never happen
109                panic!("status effect was removed but was never added");
110            }
111
112            update_status_effect(&mut query, status_effect);
113        }
114    }
115}
116
117fn update_status_effect(query: &mut StatusEffectQueryItem, status_effect: StatusEffect) {
118    let current_effect = query.active_effects.get_current_effect(status_effect);
119
120    if let Some(ref mut client) = query.client {
121        if let Some(updated_effect) = current_effect {
122            client.write_packet(&create_packet(updated_effect));
123        } else {
124            client.write_packet(&RemoveMobEffectS2c {
125                entity_id: VarInt(0),
126                effect_id: VarInt(i32::from(status_effect.to_raw())),
127            });
128        }
129    }
130}
131
132fn set_swirl(
133    active_status_effects: &ActiveStatusEffects,
134    swirl_ambient: &mut Option<Mut<'_, PotionSwirlsAmbient>>,
135) {
136    if let Some(ref mut swirl_ambient) = swirl_ambient {
137        swirl_ambient.0 = active_status_effects
138            .get_current_effects()
139            .iter()
140            .any(|effect| effect.ambient());
141    }
142}
143
144/// Used to set the color of the swirls in the potion effect.
145///
146/// Equivalent to net.minecraft.component.type.PotionContentsComponent#mixColors (Yarn mapping).
147fn _get_color(effects: &ActiveStatusEffects) -> i32 {
148    if effects.no_effects() {
149        // vanilla mc seems to return 0xFF385DC6 (i32), decimal: -13083194 if there are no effects
150        // dunno why
151        // imma just say to return 0 to remove the swirls
152        return 0;
153    }
154
155    let effects = effects.get_current_effects();
156    let mut r = 0;
157    let mut g = 0;
158    let mut b = 0;
159    let mut total = 0;
160
161    for status_effect_instance in effects {
162        if !status_effect_instance.show_particles() {
163            continue;
164        }
165
166        let color: u32 = status_effect_instance.status_effect().color();
167        let weight = (status_effect_instance.amplifier() + 1) as u32;
168        r += weight * ((color >> 16) & 0xff);
169        g += weight * ((color >> 8) & 0xff);
170        b += weight * ((color) & 0xff);
171        total += weight;
172    }
173
174    if total == 0 {
175        return 0;
176    }
177
178    let r = r / total;
179    let g = g / total;
180    let b = b / total;
181    // Alpha is always 255
182    ((0xFF_u32 << 24) | (r << 16) | (g << 8) | b) as i32
183}