chunkedge_server/
status_effect.rs1use 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#[derive(Event, Clone, PartialEq, Eq, Debug)]
20pub struct StatusEffectAdded {
21 pub entity: Entity,
22 pub status_effect: StatusEffect,
23}
24
25#[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), 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 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
144fn _get_color(effects: &ActiveStatusEffects) -> i32 {
148 if effects.no_effects() {
149 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 ((0xFF_u32 << 24) | (r << 16) | (g << 8) | b) as i32
183}