1#![doc = include_str!("../README.md")]
2
3use std::borrow::Cow;
4
5use bevy_app::prelude::*;
6use bevy_ecs::prelude::*;
7use valence_server::client::{
8 Client, OldViewDistance, OldVisibleEntityLayers, ViewDistance, VisibleEntityLayers,
9};
10use valence_server::layer::UpdateLayersPreClientSet;
11pub use valence_server::protocol::packets::play::boss_bar_s2c::{
12 BossBarAction, BossBarColor, BossBarDivision, BossBarFlags,
13};
14use valence_server::protocol::packets::play::BossBarS2c;
15use valence_server::protocol::WritePacket;
16use valence_server::{ChunkView, Despawned, EntityLayer, Layer, UniqueId};
17
18mod components;
19pub use components::*;
20use valence_entity::{EntityLayerId, OldPosition, Position};
21
22pub struct BossBarPlugin;
23
24impl Plugin for BossBarPlugin {
25 fn build(&self, app: &mut bevy_app::App) {
26 app.add_systems(
27 PostUpdate,
28 (
29 update_boss_bar::<BossBarTitle>,
30 update_boss_bar::<BossBarHealth>,
31 update_boss_bar::<BossBarStyle>,
32 update_boss_bar::<BossBarFlags>,
33 update_boss_bar_layer_view,
34 update_boss_bar_chunk_view,
35 boss_bar_despawn,
36 )
37 .before(UpdateLayersPreClientSet),
38 );
39 }
40}
41
42fn update_boss_bar<T: Component + ToPacketAction>(
43 boss_bars_query: Query<(&UniqueId, &T, &EntityLayerId, Option<&Position>), Changed<T>>,
44 mut entity_layers_query: Query<&mut EntityLayer>,
45) {
46 for (id, part, entity_layer_id, pos) in boss_bars_query.iter() {
47 if let Ok(mut entity_layer) = entity_layers_query.get_mut(entity_layer_id.0) {
48 let packet = BossBarS2c {
49 id: id.0,
50 action: part.to_packet_action(),
51 };
52 if let Some(pos) = pos {
53 entity_layer.view_writer(pos.0).write_packet(&packet);
54 } else {
55 entity_layer.write_packet(&packet);
56 }
57 }
58 }
59}
60
61fn update_boss_bar_layer_view(
62 mut clients_query: Query<
63 (
64 &mut Client,
65 &VisibleEntityLayers,
66 &OldVisibleEntityLayers,
67 &Position,
68 &OldPosition,
69 &ViewDistance,
70 &OldViewDistance,
71 ),
72 Changed<VisibleEntityLayers>,
73 >,
74 boss_bars_query: Query<(
75 &UniqueId,
76 &BossBarTitle,
77 &BossBarHealth,
78 &BossBarStyle,
79 &BossBarFlags,
80 &EntityLayerId,
81 Option<&Position>,
82 )>,
83) {
84 for (
85 mut client,
86 visible_entity_layers,
87 old_visible_entity_layers,
88 position,
89 _old_position,
90 view_distance,
91 _old_view_distance,
92 ) in &mut clients_query
93 {
94 let view = ChunkView::new(position.0.into(), view_distance.get());
95
96 let old_layers = old_visible_entity_layers.get();
97 let current_layers = &visible_entity_layers.0;
98
99 for &added_layer in current_layers.difference(old_layers) {
100 for (id, title, health, style, flags, _, boss_bar_position) in boss_bars_query
101 .iter()
102 .filter(|(_, _, _, _, _, layer_id, _)| layer_id.0 == added_layer)
103 {
104 if let Some(position) = boss_bar_position {
105 if view.contains(position.0.into()) {
106 client.write_packet(&BossBarS2c {
107 id: id.0,
108 action: BossBarAction::Add {
109 title: Cow::Borrowed(&title.0),
110 health: health.0,
111 color: style.color,
112 division: style.division,
113 flags: *flags,
114 },
115 });
116 }
117 } else {
118 client.write_packet(&BossBarS2c {
119 id: id.0,
120 action: BossBarAction::Add {
121 title: Cow::Borrowed(&title.0),
122 health: health.0,
123 color: style.color,
124 division: style.division,
125 flags: *flags,
126 },
127 });
128 }
129 }
130 }
131
132 for &removed_layer in old_layers.difference(current_layers) {
133 for (id, _, _, _, _, _, boss_bar_position) in boss_bars_query
134 .iter()
135 .filter(|(_, _, _, _, _, layer_id, _)| layer_id.0 == removed_layer)
136 {
137 if let Some(position) = boss_bar_position {
138 if view.contains(position.0.into()) {
139 client.write_packet(&BossBarS2c {
140 id: id.0,
141 action: BossBarAction::Remove,
142 });
143 }
144 } else {
145 client.write_packet(&BossBarS2c {
146 id: id.0,
147 action: BossBarAction::Remove,
148 });
149 }
150 }
151 }
152 }
153}
154
155fn update_boss_bar_chunk_view(
156 mut clients_query: Query<
157 (
158 &mut Client,
159 &VisibleEntityLayers,
160 &OldVisibleEntityLayers,
161 &Position,
162 &OldPosition,
163 &ViewDistance,
164 &OldViewDistance,
165 ),
166 Changed<Position>,
167 >,
168 boss_bars_query: Query<(
169 &UniqueId,
170 &BossBarTitle,
171 &BossBarHealth,
172 &BossBarStyle,
173 &BossBarFlags,
174 &EntityLayerId,
175 &Position,
176 )>,
177) {
178 for (
179 mut client,
180 visible_entity_layers,
181 _old_visible_entity_layers,
182 position,
183 old_position,
184 view_distance,
185 old_view_distance,
186 ) in &mut clients_query
187 {
188 let view = ChunkView::new(position.0.into(), view_distance.get());
189 let old_view = ChunkView::new(old_position.get().into(), old_view_distance.get());
190
191 for layer in &visible_entity_layers.0 {
192 for (id, title, health, style, flags, _, boss_bar_position) in boss_bars_query
193 .iter()
194 .filter(|(_, _, _, _, _, layer_id, _)| layer_id.0 == *layer)
195 {
196 if view.contains(boss_bar_position.0.into())
197 && !old_view.contains(boss_bar_position.0.into())
198 {
199 client.write_packet(&BossBarS2c {
200 id: id.0,
201 action: BossBarAction::Add {
202 title: Cow::Borrowed(&title.0),
203 health: health.0,
204 color: style.color,
205 division: style.division,
206 flags: *flags,
207 },
208 });
209 } else if !view.contains(boss_bar_position.0.into())
210 && old_view.contains(boss_bar_position.0.into())
211 {
212 client.write_packet(&BossBarS2c {
213 id: id.0,
214 action: BossBarAction::Remove,
215 });
216 }
217 }
218 }
219 }
220}
221
222fn boss_bar_despawn(
223 boss_bars_query: Query<(&UniqueId, &EntityLayerId, Option<&Position>), With<Despawned>>,
224 mut entity_layer_query: Query<&mut EntityLayer>,
225) {
226 for (id, entity_layer_id, position) in boss_bars_query.iter() {
227 if let Ok(mut entity_layer) = entity_layer_query.get_mut(entity_layer_id.0) {
228 let packet = BossBarS2c {
229 id: id.0,
230 action: BossBarAction::Remove,
231 };
232 if let Some(pos) = position {
233 entity_layer.view_writer(pos.0).write_packet(&packet);
234 } else {
235 entity_layer.write_packet(&packet);
236 }
237 }
238 }
239}