chunkedge_world_border/
lib.rs

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
14// https://minecraft.wiki/w/World_border
15pub 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/// A bundle containing necessary components to enable world border
44/// functionality. Add this to an entity with the [`ChunkLayer`] component.
45#[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/// Component containing information to linearly interpolate the world border.
68/// Contains the world border's diameter.
69#[derive(Component, Clone, Copy, Debug)]
70pub struct WorldBorderLerp {
71    /// The current diameter of the world border. This is updated automatically
72    /// as the remaining ticks count down.
73    pub current_diameter: f64,
74    /// The desired diameter of the world border after lerping has finished.
75    /// Modify this if you want to change the world border diameter.
76    pub target_diameter: f64,
77    /// Server ticks until the target diameter is reached. This counts down
78    /// automatically.
79    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}