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 RemoveMobEffectS2c, UpdateMobEffectS2c, update_mob_effect_s2c,
10};
11use chunkedge_protocol::status_effects::StatusEffect;
12use chunkedge_protocol::{VarInt, WritePacket};
13
14use crate::EventLoopPostUpdate;
15use crate::client::Client;
16
17#[derive(Message, Clone, PartialEq, Eq, Debug)]
20pub struct StatusEffectAddedMessage {
21 pub entity: Entity,
22 pub status_effect: StatusEffect,
23}
24
25#[derive(Message, Clone, PartialEq, Eq, Debug)]
27pub struct StatusEffectRemovedMessage {
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_message::<StatusEffectAddedMessage>()
37 .add_message::<StatusEffectRemovedMessage>()
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).unwrap();
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_messages: MessageWriter<StatusEffectAddedMessage>,
85 mut remove_messages: MessageWriter<StatusEffectRemovedMessage>,
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_messages.write(StatusEffectAddedMessage {
99 entity: query.entity,
100 status_effect,
101 });
102 } else if let Some(prev) = prev {
103 remove_messages.write(StatusEffectRemovedMessage {
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(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 {
149 if effects.no_effects() {
150 return 0;
154 }
155
156 let effects = effects.get_current_effects();
157 let mut r = 0;
158 let mut g = 0;
159 let mut b = 0;
160 let mut total = 0;
161
162 for status_effect_instance in effects {
163 if !status_effect_instance.show_particles() {
164 continue;
165 }
166
167 let color: u32 = status_effect_instance.status_effect().color();
168 let weight = (status_effect_instance.amplifier() + 1) as u32;
169 r += weight * ((color >> 16) & 0xff);
170 g += weight * ((color >> 8) & 0xff);
171 b += weight * ((color) & 0xff);
172 total += weight;
173 }
174
175 if total == 0 {
176 return 0;
177 }
178
179 let r = r / total;
180 let g = g / total;
181 let b = b / total;
182 ((0xff_u32 << 24) | (r << 16) | (g << 8) | b) as i32
184}