valence_world_border/
lib.rs

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