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