1#![doc = include_str!("../README.md")]
2
3use bevy_app::prelude::*;
4use bevy_ecs::prelude::*;
5use derive_more::{Deref, DerefMut};
6use valence_server::client::{Client, UpdateClientsSet, VisibleChunkLayer};
7use valence_server::protocol::packets::play::{
8 WorldBorderCenterChangedS2c, WorldBorderInitializeS2c, WorldBorderInterpolateSizeS2c,
9 WorldBorderSizeChangedS2c, WorldBorderWarningBlocksChangedS2c,
10 WorldBorderWarningTimeChangedS2c,
11};
12use valence_server::protocol::WritePacket;
13use valence_server::{ChunkLayer, Server};
14
15pub const DEFAULT_PORTAL_LIMIT: i32 = 29999984;
17pub const DEFAULT_DIAMETER: f64 = (DEFAULT_PORTAL_LIMIT * 2) as f64;
18pub const DEFAULT_WARN_TIME: i32 = 15;
19pub const DEFAULT_WARN_BLOCKS: i32 = 5;
20
21pub struct WorldBorderPlugin;
22
23#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)]
24pub struct UpdateWorldBorderSet;
25
26impl Plugin for WorldBorderPlugin {
27 fn build(&self, app: &mut App) {
28 app.configure_sets(PostUpdate, UpdateWorldBorderSet.before(UpdateClientsSet))
29 .add_systems(
30 PostUpdate,
31 (
32 init_world_border_for_new_clients,
33 tick_world_border_lerp,
34 change_world_border_center,
35 change_world_border_warning_blocks,
36 change_world_border_warning_time,
37 change_world_border_portal_tp_boundary,
38 )
39 .in_set(UpdateWorldBorderSet),
40 );
41 }
42}
43
44#[derive(Bundle, Default, Debug)]
47pub struct WorldBorderBundle {
48 pub center: WorldBorderCenter,
49 pub lerp: WorldBorderLerp,
50 pub portal_teleport_boundary: WorldBorderPortalTpBoundary,
51 pub warn_time: WorldBorderWarnTime,
52 pub warn_blocks: WorldBorderWarnBlocks,
53}
54
55#[derive(Component, Default, Copy, Clone, PartialEq, Debug)]
56pub struct WorldBorderCenter {
57 pub x: f64,
58 pub z: f64,
59}
60
61impl WorldBorderCenter {
62 pub fn set(&mut self, x: f64, z: f64) {
63 self.x = x;
64 self.z = z;
65 }
66}
67
68#[derive(Component, Clone, Copy, Debug)]
71pub struct WorldBorderLerp {
72 pub current_diameter: f64,
75 pub target_diameter: f64,
78 pub remaining_ticks: u64,
81}
82#[derive(Component, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deref, DerefMut)]
83pub struct WorldBorderWarnTime(pub i32);
84
85impl Default for WorldBorderWarnTime {
86 fn default() -> Self {
87 Self(DEFAULT_WARN_TIME)
88 }
89}
90
91#[derive(Component, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deref, DerefMut)]
92pub struct WorldBorderWarnBlocks(pub i32);
93
94impl Default for WorldBorderWarnBlocks {
95 fn default() -> Self {
96 Self(DEFAULT_WARN_BLOCKS)
97 }
98}
99
100#[derive(Component, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deref, DerefMut)]
101pub struct WorldBorderPortalTpBoundary(pub i32);
102
103impl Default for WorldBorderPortalTpBoundary {
104 fn default() -> Self {
105 Self(DEFAULT_PORTAL_LIMIT)
106 }
107}
108
109impl Default for WorldBorderLerp {
110 fn default() -> Self {
111 Self {
112 current_diameter: DEFAULT_DIAMETER,
113 target_diameter: DEFAULT_DIAMETER,
114 remaining_ticks: 0,
115 }
116 }
117}
118
119fn init_world_border_for_new_clients(
120 mut clients: Query<(&mut Client, &VisibleChunkLayer), Changed<VisibleChunkLayer>>,
121 wbs: Query<(
122 &WorldBorderCenter,
123 &WorldBorderLerp,
124 &WorldBorderPortalTpBoundary,
125 &WorldBorderWarnTime,
126 &WorldBorderWarnBlocks,
127 )>,
128 server: Res<Server>,
129) {
130 for (mut client, layer) in &mut clients {
131 if let Ok((center, lerp, portal_tp_boundary, warn_time, warn_blocks)) = wbs.get(layer.0) {
132 let millis = lerp.remaining_ticks as i64 * 1000 / i64::from(server.tick_rate().get());
133
134 client.write_packet(&WorldBorderInitializeS2c {
135 x: center.x,
136 z: center.z,
137 old_diameter: lerp.current_diameter,
138 new_diameter: lerp.target_diameter,
139 duration_millis: millis.into(),
140 portal_teleport_boundary: portal_tp_boundary.0.into(),
141 warning_blocks: warn_blocks.0.into(),
142 warning_time: warn_time.0.into(),
143 });
144 }
145 }
146}
147
148fn tick_world_border_lerp(
149 mut wbs: Query<(&mut ChunkLayer, &mut WorldBorderLerp)>,
150 server: Res<Server>,
151) {
152 for (mut layer, mut lerp) in &mut wbs {
153 if lerp.is_changed() {
154 if lerp.remaining_ticks == 0 {
155 layer.write_packet(&WorldBorderSizeChangedS2c {
156 diameter: lerp.target_diameter,
157 });
158
159 lerp.current_diameter = lerp.target_diameter;
160 } else {
161 let millis =
162 lerp.remaining_ticks as i64 * 1000 / i64::from(server.tick_rate().get());
163
164 layer.write_packet(&WorldBorderInterpolateSizeS2c {
165 old_diameter: lerp.current_diameter,
166 new_diameter: lerp.target_diameter,
167 duration_millis: millis.into(),
168 });
169 }
170 }
171
172 if lerp.remaining_ticks > 0 {
173 let diff = lerp.target_diameter - lerp.current_diameter;
174 lerp.current_diameter += diff / lerp.remaining_ticks as f64;
175
176 lerp.remaining_ticks -= 1;
177 }
178 }
179}
180
181fn change_world_border_center(
182 mut wbs: Query<(&mut ChunkLayer, &WorldBorderCenter), Changed<WorldBorderCenter>>,
183) {
184 for (mut layer, center) in &mut wbs {
185 layer.write_packet(&WorldBorderCenterChangedS2c {
186 x_pos: center.x,
187 z_pos: center.z,
188 });
189 }
190}
191
192fn change_world_border_warning_blocks(
193 mut wbs: Query<(&mut ChunkLayer, &WorldBorderWarnBlocks), Changed<WorldBorderWarnBlocks>>,
194) {
195 for (mut layer, warn_blocks) in &mut wbs {
196 layer.write_packet(&WorldBorderWarningBlocksChangedS2c {
197 warning_blocks: warn_blocks.0.into(),
198 });
199 }
200}
201
202fn change_world_border_warning_time(
203 mut wbs: Query<(&mut ChunkLayer, &WorldBorderWarnTime), Changed<WorldBorderWarnTime>>,
204) {
205 for (mut layer, warn_time) in &mut wbs {
206 layer.write_packet(&WorldBorderWarningTimeChangedS2c {
207 warning_time: warn_time.0.into(),
208 });
209 }
210}
211
212fn change_world_border_portal_tp_boundary(
213 mut wbs: Query<
214 (
215 &mut ChunkLayer,
216 &WorldBorderCenter,
217 &WorldBorderLerp,
218 &WorldBorderPortalTpBoundary,
219 &WorldBorderWarnTime,
220 &WorldBorderWarnBlocks,
221 ),
222 Changed<WorldBorderPortalTpBoundary>,
223 >,
224 server: Res<Server>,
225) {
226 for (mut layer, center, lerp, portal_tp_boundary, warn_time, warn_blocks) in &mut wbs {
227 let millis = lerp.remaining_ticks as i64 * 1000 / i64::from(server.tick_rate().get());
228
229 layer.write_packet(&WorldBorderInitializeS2c {
230 x: center.x,
231 z: center.z,
232 old_diameter: lerp.current_diameter,
233 new_diameter: lerp.target_diameter,
234 duration_millis: millis.into(),
235 portal_teleport_boundary: portal_tp_boundary.0.into(),
236 warning_blocks: warn_blocks.0.into(),
237 warning_time: warn_time.0.into(),
238 });
239 }
240}