valence_boss_bar/
lib.rs

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}