Implement world border (#364)

## Description

Basic implementation of world border
World border is not enabled by default. It can be enabled by inserting
`WorldBorderBundle` bundle. Currently, this PR only implements world
borders per instance, I'm considering expanding this per client.
However, the same functionality can be achieved by Visibility Layers
#362

<details>
<summary>Playground:</summary>

```rust
fn border_controls(
    mut events: EventReader<ChatMessageEvent>,
    mut instances: Query<(Entity, &WorldBorderDiameter, &mut WorldBorderCenter), With<Instance>>,
    mut event_writer: EventWriter<SetWorldBorderSizeEvent>,
) {
    for x in events.iter() {
        let parts: Vec<&str> = x.message.split(' ').collect();
        match parts[0] {
            "add" => {
                let Ok(value) = parts[1].parse::<f64>() else {
                    return;
                };

                let Ok(speed) = parts[2].parse::<i64>() else {
                    return;
                };

                let Ok((entity, diameter, _)) = instances.get_single_mut() else {
                    return;
                };

                event_writer.send(SetWorldBorderSizeEvent {
                    instance: entity,
                    new_diameter: diameter.diameter() + value,
                    speed,
                })
            }
            "center" => {
                let Ok(x) = parts[1].parse::<f64>() else {
                    return;
                };

                let Ok(z) = parts[2].parse::<f64>() else {
                    return;
                };

                instances.single_mut().2 .0 = DVec2 { x, y: z };
            }
            _ => (),
        }
    }
}
``` 
</details>

example: `cargo run --package valence --example world_border`
tests: `cargo test --package valence --lib -- tests::world_border`


**Related**
part of #210
This commit is contained in:
Tachibana Yui 2023-06-15 12:15:47 +07:00 committed by GitHub
parent 09fbd9b7e7
commit 61f2279831
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 769 additions and 47 deletions

View file

@ -89,7 +89,9 @@ valence_nbt = { path = "crates/valence_nbt", features = ["uuid"] }
valence_network.path = "crates/valence_network" valence_network.path = "crates/valence_network"
valence_player_list.path = "crates/valence_player_list" valence_player_list.path = "crates/valence_player_list"
valence_registry.path = "crates/valence_registry" valence_registry.path = "crates/valence_registry"
valence_world_border.path = "crates/valence_world_border"
valence.path = "crates/valence" valence.path = "crates/valence"
zip = "0.6.3" zip = "0.6.3"
[profile.dev.package."*"] [profile.dev.package."*"]

View file

@ -22,4 +22,5 @@ graph TD
anvil --> instance anvil --> instance
entity --> block entity --> block
advancement --> client advancement --> client
world_border --> client
``` ```

View file

@ -11,12 +11,13 @@ keywords = ["minecraft", "gamedev", "server", "ecs"]
categories = ["game-engines"] categories = ["game-engines"]
[features] [features]
default = ["network", "player_list", "inventory", "anvil", "advancement"] default = ["network", "player_list", "inventory", "anvil", "advancement", "world_border"]
network = ["dep:valence_network"] network = ["dep:valence_network"]
player_list = ["dep:valence_player_list"] player_list = ["dep:valence_player_list"]
inventory = ["dep:valence_inventory"] inventory = ["dep:valence_inventory"]
anvil = ["dep:valence_anvil"] anvil = ["dep:valence_anvil"]
advancement = ["dep:valence_advancement"] advancement = ["dep:valence_advancement"]
world_border = ["dep:valence_world_border"]
[dependencies] [dependencies]
bevy_app.workspace = true bevy_app.workspace = true
@ -37,6 +38,8 @@ valence_player_list = { workspace = true, optional = true }
valence_inventory = { workspace = true, optional = true } valence_inventory = { workspace = true, optional = true }
valence_anvil = { workspace = true, optional = true } valence_anvil = { workspace = true, optional = true }
valence_advancement = { workspace = true, optional = true } valence_advancement = { workspace = true, optional = true }
valence_world_border = { workspace = true, optional = true }
[dev-dependencies] [dev-dependencies]
anyhow.workspace = true anyhow.workspace = true

View file

@ -0,0 +1,161 @@
use std::time::Duration;
use bevy_app::App;
use valence::client::chat::ChatMessageEvent;
use valence::client::despawn_disconnected_clients;
use valence::inventory::HeldItem;
use valence::prelude::*;
use valence::world_border::*;
const SPAWN_Y: i32 = 64;
fn main() {
tracing_subscriber::fmt().init();
App::new()
.add_plugins(DefaultPlugins)
.add_startup_system(setup)
.add_system(init_clients)
.add_system(despawn_disconnected_clients)
.add_system(border_center_avg)
.add_system(border_expand)
.add_system(border_controls)
.run();
}
fn setup(
mut commands: Commands,
server: Res<Server>,
biomes: Res<BiomeRegistry>,
dimensions: Res<DimensionTypeRegistry>,
) {
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);
for z in -5..5 {
for x in -5..5 {
instance.insert_chunk([x, z], Chunk::default());
}
}
for z in -25..25 {
for x in -25..25 {
instance.set_block([x, SPAWN_Y, z], BlockState::MOSSY_COBBLESTONE);
}
}
commands
.spawn(instance)
.insert(WorldBorderBundle::new([0.0, 0.0], 1.0));
}
fn init_clients(
mut clients: Query<
(
&mut Client,
&mut Location,
&mut Position,
&mut Inventory,
&HeldItem,
),
Added<Client>,
>,
instances: Query<Entity, With<Instance>>,
) {
for (mut client, mut loc, mut pos, mut inv, main_slot) in &mut clients {
loc.0 = instances.single();
pos.set([0.5, SPAWN_Y as f64 + 1.0, 0.5]);
let pickaxe = Some(ItemStack::new(ItemKind::WoodenPickaxe, 1, None));
inv.set_slot(main_slot.slot(), pickaxe);
client.send_message("Break block to increase border size!");
}
}
fn border_center_avg(
clients: Query<(&Location, &Position)>,
mut instances: Query<(Entity, &mut WorldBorderCenter), With<Instance>>,
) {
for (entity, mut center) in instances.iter_mut() {
let new_center = {
let (count, x, z) = clients
.iter()
.filter(|(loc, _)| loc.0 == entity)
.fold((0, 0.0, 0.0), |(count, x, z), (_, pos)| {
(count + 1, x + pos.0.x, z + pos.0.z)
});
DVec2 {
x: x / count.max(1) as f64,
y: z / count.max(1) as f64,
}
};
center.0 = new_center;
}
}
fn border_expand(
mut events: EventReader<DiggingEvent>,
clients: Query<&Location, With<Client>>,
wbs: Query<&WorldBorderDiameter, With<Instance>>,
mut event_writer: EventWriter<SetWorldBorderSizeEvent>,
) {
for digging in events.iter().filter(|d| d.state == DiggingState::Stop) {
let Ok(loc) = clients.get(digging.client) else {
continue;
};
let Ok(size) = wbs.get(loc.0) else {
continue;
};
event_writer.send(SetWorldBorderSizeEvent {
instance: loc.0,
new_diameter: size.get() + 1.0,
duration: Duration::from_secs(1),
});
}
}
// Not needed for this demo, but useful for debugging
fn border_controls(
mut events: EventReader<ChatMessageEvent>,
mut instances: Query<(Entity, &WorldBorderDiameter, &mut WorldBorderCenter), With<Instance>>,
mut event_writer: EventWriter<SetWorldBorderSizeEvent>,
) {
for x in events.iter() {
let parts: Vec<&str> = x.message.split(' ').collect();
match parts[0] {
"add" => {
let Ok(value) = parts[1].parse::<f64>() else {
return;
};
let Ok(speed) = parts[2].parse::<i64>() else {
return;
};
let Ok((entity, diameter, _)) = instances.get_single_mut() else {
return;
};
event_writer.send(SetWorldBorderSizeEvent {
instance: entity,
new_diameter: diameter.get() + value,
duration: Duration::from_millis(speed as u64),
})
}
"center" => {
let Ok(x) = parts[1].parse::<f64>() else {
return;
};
let Ok(z) = parts[2].parse::<f64>() else {
return;
};
instances.single_mut().2 .0 = DVec2 { x, y: z };
}
_ => (),
}
}
}

View file

@ -37,6 +37,8 @@ pub use valence_inventory as inventory;
pub use valence_network as network; pub use valence_network as network;
#[cfg(feature = "player_list")] #[cfg(feature = "player_list")]
pub use valence_player_list as player_list; pub use valence_player_list as player_list;
#[cfg(feature = "world_border")]
pub use valence_world_border as world_border;
pub use { pub use {
bevy_app as app, bevy_ecs as ecs, glam, valence_biome as biome, valence_block as block, bevy_app as app, bevy_ecs as ecs, glam, valence_biome as biome, valence_block as block,
valence_client as client, valence_dimension as dimension, valence_entity as entity, valence_client as client, valence_dimension as dimension, valence_entity as entity,
@ -162,6 +164,11 @@ impl PluginGroup for DefaultPlugins {
.add(valence_advancement::bevy_hierarchy::HierarchyPlugin); .add(valence_advancement::bevy_hierarchy::HierarchyPlugin);
} }
#[cfg(feature = "world_border")]
{
group = group.add(valence_world_border::WorldBorderPlugin);
}
group group
} }
} }

View file

@ -284,3 +284,4 @@ mod client;
mod example; mod example;
mod inventory; mod inventory;
mod weather; mod weather;
mod world_border;

View file

@ -0,0 +1,133 @@
use std::time::Duration;
use bevy_app::App;
use valence_entity::Location;
use valence_instance::Instance;
use valence_registry::{Entity, Mut};
use valence_world_border::packet::*;
use valence_world_border::*;
use super::{create_mock_client, scenario_single_client, MockClientHelper};
#[test]
fn test_intialize_on_join() {
let mut app = App::new();
let (_, instance_ent) = prepare(&mut app);
let (client, mut client_helper) = create_mock_client();
let client_ent = app.world.spawn(client).id();
app.world.get_mut::<Location>(client_ent).unwrap().0 = instance_ent;
app.update();
client_helper
.collect_sent()
.assert_count::<WorldBorderInitializeS2c>(1);
}
#[test]
fn test_resizing() {
let mut app = App::new();
let (mut client_helper, instance_ent) = prepare(&mut app);
app.world.send_event(SetWorldBorderSizeEvent {
new_diameter: 20.0,
duration: Duration::ZERO,
instance: instance_ent,
});
app.update();
let frames = client_helper.collect_sent();
frames.assert_count::<WorldBorderSizeChangedS2c>(1);
}
#[test]
fn test_center() {
let mut app = App::new();
let (mut client_helper, instance_ent) = prepare(&mut app);
let mut ins_mut = app.world.entity_mut(instance_ent);
let mut center: Mut<WorldBorderCenter> = ins_mut
.get_mut()
.expect("Expect world border to be present!");
center.0 = [10.0, 10.0].into();
app.update();
let frames = client_helper.collect_sent();
frames.assert_count::<WorldBorderCenterChangedS2c>(1);
}
#[test]
fn test_warn_time() {
let mut app = App::new();
let (mut client_helper, instance_ent) = prepare(&mut app);
let mut ins_mut = app.world.entity_mut(instance_ent);
let mut wt: Mut<WorldBorderWarnTime> = ins_mut
.get_mut()
.expect("Expect world border to be present!");
wt.0 = 100;
app.update();
let frames = client_helper.collect_sent();
frames.assert_count::<WorldBorderWarningTimeChangedS2c>(1);
}
#[test]
fn test_warn_blocks() {
let mut app = App::new();
let (mut client_helper, instance_ent) = prepare(&mut app);
let mut ins_mut = app.world.entity_mut(instance_ent);
let mut wb: Mut<WorldBorderWarnBlocks> = ins_mut
.get_mut()
.expect("Expect world border to be present!");
wb.0 = 100;
app.update();
let frames = client_helper.collect_sent();
frames.assert_count::<WorldBorderWarningBlocksChangedS2c>(1);
}
#[test]
fn test_portal_tp_boundary() {
let mut app = App::new();
let (mut client_helper, instance_ent) = prepare(&mut app);
let mut ins_mut = app.world.entity_mut(instance_ent);
let mut tp: Mut<WorldBorderPortalTpBoundary> = ins_mut
.get_mut()
.expect("Expect world border to be present!");
tp.0 = 100;
app.update();
let frames = client_helper.collect_sent();
frames.assert_count::<WorldBorderInitializeS2c>(1);
}
fn prepare(app: &mut App) -> (MockClientHelper, Entity) {
let (_, mut client_helper) = scenario_single_client(app);
// Process a tick to get past the "on join" logic.
app.update();
client_helper.clear_sent();
// Get the instance entity.
let instance_ent = app
.world
.iter_entities()
.find(|e| e.contains::<Instance>())
.expect("could not find instance")
.id();
// Insert a the world border bundle to the instance.
app.world
.entity_mut(instance_ent)
.insert(WorldBorderBundle::new([0.0, 0.0], 10.0));
for _ in 0..2 {
app.update();
}
client_helper.clear_sent();
(client_helper, instance_ent)
}

View file

@ -9,52 +9,6 @@ use valence_core::protocol::var_long::VarLong;
use valence_core::protocol::{packet_id, Decode, Encode, Packet}; use valence_core::protocol::{packet_id, Decode, Encode, Packet};
use valence_nbt::Compound; use valence_nbt::Compound;
#[derive(Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::WORLD_BORDER_CENTER_CHANGED_S2C)]
pub struct WorldBorderCenterChangedS2c {
pub x_pos: f64,
pub z_pos: f64,
}
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::WORLD_BORDER_INITIALIZE_S2C)]
pub struct WorldBorderInitializeS2c {
pub x: f64,
pub z: f64,
pub old_diameter: f64,
pub new_diameter: f64,
pub speed: VarLong,
pub portal_teleport_boundary: VarInt,
pub warning_blocks: VarInt,
pub warning_time: VarInt,
}
#[derive(Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::WORLD_BORDER_INTERPOLATE_SIZE_S2C)]
pub struct WorldBorderInterpolateSizeS2c {
pub old_diameter: f64,
pub new_diameter: f64,
pub speed: VarLong,
}
#[derive(Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::WORLD_BORDER_SIZE_CHANGED_S2C)]
pub struct WorldBorderSizeChangedS2c {
pub diameter: f64,
}
#[derive(Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::WORLD_BORDER_WARNING_BLOCKS_CHANGED_S2C)]
pub struct WorldBorderWarningBlocksChangedS2c {
pub warning_blocks: VarInt,
}
#[derive(Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::WORLD_BORDER_WARNING_TIME_CHANGED_S2C)]
pub struct WorldBorderWarningTimeChangedS2c {
pub warning_time: VarInt,
}
#[derive(Clone, Debug, Encode, Decode, Packet)] #[derive(Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::WORLD_EVENT_S2C)] #[packet(id = packet_id::WORLD_EVENT_S2C)]
pub struct WorldEventS2c { pub struct WorldEventS2c {

View file

@ -0,0 +1,14 @@
[package]
name = "valence_world_border"
version.workspace = true
edition.workspace = true
[dependencies]
bevy_app.workspace = true
bevy_ecs.workspace = true
glam.workspace = true
valence_client.workspace = true
valence_core.workspace = true
valence_entity.workspace = true
valence_instance.workspace = true
valence_registry.workspace = true

View file

@ -0,0 +1,397 @@
//! # World border
//! This module contains Components and Systems needed to handle world border.
//!
//! The world border is the current edge of a Minecraft dimension. It appears as
//! a series of animated, diagonal, narrow stripes. For more information, refer to the [wiki](https://minecraft.fandom.com/wiki/World_border)
//!
//! ## Enable world border per instance
//! By default, world border is not enabled. It can be enabled by inserting the
//! [`WorldBorderBundle`] bundle into a [`Instance`].
//! Use [`WorldBorderBundle::default()`] to use Minecraft Vanilla border default
//! ```
//! commands
//! .entity(instance_entity)
//! .insert(WorldBorderBundle::new([0.0, 0.0], 10.0));
//! ```
//!
//!
//! ## Modify world border diameter
//! World border diameter can be changed using [`SetWorldBorderSizeEvent`].
//! Setting duration to 0 will move the border to `new_diameter` immediately,
//! otherwise, it will interpolate to `new_diameter` over `duration` time.
//! ```
//! fn change_diameter(
//! event_writer: EventWriter<SetWorldBorderSizeEvent>,
//! diameter: f64,
//! duration: Duration,
//! ) {
//! event_writer.send(SetWorldBorderSizeEvent {
//! instance: entity,
//! new_diameter: diameter,
//! duration,
//! })
//! }
//! ```
//!
//! You can also modify the [`MovingWorldBorder`] if you want more control. But
//! it is not recommended.
//!
//! ## Querying world border diameter
//! World border diameter can be read by querying
//! [`WorldBorderDiameter::get()`]. Note: If you want to modify the
//! diameter size, do not modify the value directly! Use
//! [`SetWorldBorderSizeEvent`] instead.
//!
//! ## Access other world border properties.
//! Access to the rest of the world border properties is fairly straightforward
//! by querying their respective component. [`WorldBorderBundle`] contains
//! references for all properties of the world border and their respective component
#![allow(clippy::type_complexity)]
#![deny(
rustdoc::broken_intra_doc_links,
rustdoc::private_intra_doc_links,
rustdoc::missing_crate_level_docs,
rustdoc::invalid_codeblock_attributes,
rustdoc::invalid_rust_codeblocks,
rustdoc::bare_urls,
rustdoc::invalid_html_tags
)]
#![warn(
trivial_casts,
trivial_numeric_casts,
unused_lifetimes,
unused_import_braces,
unreachable_pub,
clippy::dbg_macro
)]
pub mod packet;
use std::time::{Duration, Instant};
use bevy_app::{App, CoreSet, Plugin};
use glam::DVec2;
use packet::*;
use valence_client::{Client, FlushPacketsSet};
use valence_core::protocol::encode::WritePacket;
use valence_core::protocol::var_int::VarInt;
use valence_core::protocol::var_long::VarLong;
use valence_entity::Location;
use valence_instance::{Instance, WriteUpdatePacketsToInstancesSet};
use valence_registry::*;
// https://minecraft.fandom.com/wiki/World_border
pub const DEFAULT_PORTAL_LIMIT: i32 = 29999984;
pub const DEFAULT_DIAMETER: f64 = (DEFAULT_PORTAL_LIMIT * 2) as f64;
pub const DEFAULT_WARN_TIME: i32 = 15;
pub const DEFAULT_WARN_BLOCKS: i32 = 5;
#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct UpdateWorldBorderPerInstanceSet;
#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct UpdateWorldBorderPerClientSet;
pub struct WorldBorderPlugin;
impl Plugin for WorldBorderPlugin {
fn build(&self, app: &mut App) {
app.configure_set(
UpdateWorldBorderPerInstanceSet
.in_base_set(CoreSet::PostUpdate)
.before(WriteUpdatePacketsToInstancesSet),
)
.configure_set(
UpdateWorldBorderPerClientSet
.in_base_set(CoreSet::PostUpdate)
.before(FlushPacketsSet),
)
.add_event::<SetWorldBorderSizeEvent>()
.add_systems(
(
handle_wb_size_change.before(handle_diameter_change),
handle_diameter_change,
handle_lerp_transition,
handle_center_change,
handle_warn_time_change,
handle_warn_blocks_change,
handle_portal_teleport_bounary_change,
)
.in_set(UpdateWorldBorderPerInstanceSet),
)
.add_system(handle_border_for_player.in_set(UpdateWorldBorderPerClientSet));
}
}
/// A bundle contains necessary component to enable world border.
/// This struct implements [`Default`] trait that returns a bundle using
/// Minecraft Vanilla defaults.
#[derive(Bundle)]
pub struct WorldBorderBundle {
pub center: WorldBorderCenter,
pub diameter: WorldBorderDiameter,
pub portal_teleport_boundary: WorldBorderPortalTpBoundary,
pub warning_time: WorldBorderWarnTime,
pub warning_blocks: WorldBorderWarnBlocks,
pub moving: MovingWorldBorder,
}
impl WorldBorderBundle {
/// Create a new world border with specified center and diameter
pub fn new(center: impl Into<DVec2>, diameter: f64) -> Self {
Self {
center: WorldBorderCenter(center.into()),
diameter: WorldBorderDiameter(diameter),
portal_teleport_boundary: WorldBorderPortalTpBoundary(DEFAULT_PORTAL_LIMIT),
warning_time: WorldBorderWarnTime(DEFAULT_WARN_TIME),
warning_blocks: WorldBorderWarnBlocks(DEFAULT_WARN_BLOCKS),
moving: MovingWorldBorder {
old_diameter: diameter,
new_diameter: diameter,
duration: 0,
timestamp: Instant::now(),
},
}
}
}
impl Default for WorldBorderBundle {
fn default() -> Self {
Self::new([0.0, 0.0], DEFAULT_DIAMETER)
}
}
#[derive(Component)]
pub struct WorldBorderCenter(pub DVec2);
#[derive(Component)]
pub struct WorldBorderWarnTime(pub i32);
#[derive(Component)]
pub struct WorldBorderWarnBlocks(pub i32);
#[derive(Component)]
pub struct WorldBorderPortalTpBoundary(pub i32);
/// The world border diameter can be read by calling
/// [`WorldBorderDiameter::get()`]. If you want to modify the diameter
/// size, do not modify the value directly! Use [`SetWorldBorderSizeEvent`]
/// instead.
#[derive(Component)]
pub struct WorldBorderDiameter(f64);
impl WorldBorderDiameter {
pub fn get(&self) -> f64 {
self.0
}
}
/// This component represents the `Set Border Lerp Size` packet with timestamp.
/// It is used for actually lerping the world border diameter.
/// If you need to set the diameter, it is much better to use the
/// [`SetWorldBorderSizeEvent`] event
#[derive(Component)]
pub struct MovingWorldBorder {
pub old_diameter: f64,
pub new_diameter: f64,
/// equivalent to `speed` on wiki.vg
pub duration: i64,
pub timestamp: Instant,
}
impl MovingWorldBorder {
pub fn current_diameter(&self) -> f64 {
if self.duration == 0 {
self.new_diameter
} else {
let t = self.current_duration() as f64 / self.duration as f64;
lerp(self.new_diameter, self.old_diameter, t)
}
}
pub fn current_duration(&self) -> i64 {
let speed = self.duration - self.timestamp.elapsed().as_millis() as i64;
speed.max(0)
}
}
/// An event for controlling world border diameter.
/// Setting duration to 0 will move the border to `new_diameter` immediately,
/// otherwise it will interpolate to `new_diameter` over `duration` time.
/// ```
/// fn change_diameter(
/// event_writer: EventWriter<SetWorldBorderSizeEvent>,
/// diameter: f64,
/// duration: Duration,
/// ) {
/// event_writer.send(SetWorldBorderSizeEvent {
/// instance: entity,
/// new_diameter: diameter,
/// duration,
/// })
/// }
/// ```
pub struct SetWorldBorderSizeEvent {
/// The instance to change border size. Note that this instance must contain
/// the [`WorldBorderBundle`] bundle
pub instance: Entity,
/// The new diameter of the world border
pub new_diameter: f64,
/// How long the border takes to reach it new_diameter in millisecond. Set
/// to 0 to move immediately.
pub duration: Duration,
}
fn handle_wb_size_change(
mut events: EventReader<SetWorldBorderSizeEvent>,
mut instances: Query<(&WorldBorderDiameter, Option<&mut MovingWorldBorder>)>,
) {
for SetWorldBorderSizeEvent {
instance,
new_diameter,
duration,
} in events.iter()
{
let Ok((diameter, mwb_opt)) = instances.get_mut(*instance) else {
continue;
};
if let Some(mut mvb) = mwb_opt {
mvb.new_diameter = *new_diameter;
mvb.old_diameter = diameter.get();
mvb.duration = duration.as_millis() as i64;
mvb.timestamp = Instant::now();
}
}
}
fn handle_border_for_player(
mut clients: Query<(&mut Client, &Location), Changed<Location>>,
wbs: Query<
(
&WorldBorderCenter,
&WorldBorderWarnTime,
&WorldBorderWarnBlocks,
&WorldBorderDiameter,
&WorldBorderPortalTpBoundary,
Option<&MovingWorldBorder>,
),
With<Instance>,
>,
) {
for (mut client, location) in clients.iter_mut() {
if let Ok((c, wt, wb, diameter, ptb, wbl)) = wbs.get(location.0) {
let (new_diameter, speed) = if let Some(lerping) = wbl {
(lerping.new_diameter, lerping.current_duration())
} else {
(diameter.0, 0)
};
client.write_packet(&WorldBorderInitializeS2c {
x: c.0.x,
z: c.0.y,
old_diameter: diameter.0,
new_diameter,
portal_teleport_boundary: VarInt(ptb.0),
speed: VarLong(speed),
warning_blocks: VarInt(wb.0),
warning_time: VarInt(wt.0),
});
}
}
}
fn handle_diameter_change(
mut wbs: Query<(&mut Instance, &MovingWorldBorder), Changed<MovingWorldBorder>>,
) {
for (mut ins, lerping) in wbs.iter_mut() {
if lerping.duration == 0 {
ins.write_packet(&WorldBorderSizeChangedS2c {
diameter: lerping.new_diameter,
})
} else {
ins.write_packet(&WorldBorderInterpolateSizeS2c {
old_diameter: lerping.current_diameter(),
new_diameter: lerping.new_diameter,
speed: VarLong(lerping.current_duration()),
});
}
}
}
fn handle_lerp_transition(mut wbs: Query<(&mut WorldBorderDiameter, &MovingWorldBorder)>) {
for (mut diameter, moving_wb) in wbs.iter_mut() {
if diameter.0 != moving_wb.new_diameter {
diameter.0 = moving_wb.current_diameter();
}
}
}
fn handle_center_change(
mut wbs: Query<(&mut Instance, &WorldBorderCenter), Changed<WorldBorderCenter>>,
) {
for (mut ins, center) in wbs.iter_mut() {
ins.write_packet(&WorldBorderCenterChangedS2c {
x_pos: center.0.x,
z_pos: center.0.y,
})
}
}
fn handle_warn_time_change(
mut wb_query: Query<(&mut Instance, &WorldBorderWarnTime), Changed<WorldBorderWarnTime>>,
) {
for (mut ins, wt) in wb_query.iter_mut() {
ins.write_packet(&WorldBorderWarningTimeChangedS2c {
warning_time: VarInt(wt.0),
})
}
}
fn handle_warn_blocks_change(
mut wb_query: Query<(&mut Instance, &WorldBorderWarnBlocks), Changed<WorldBorderWarnBlocks>>,
) {
for (mut ins, wb) in wb_query.iter_mut() {
ins.write_packet(&WorldBorderWarningBlocksChangedS2c {
warning_blocks: VarInt(wb.0),
})
}
}
fn handle_portal_teleport_bounary_change(
mut wbs: Query<
(
&mut Instance,
&WorldBorderCenter,
&WorldBorderWarnTime,
&WorldBorderWarnBlocks,
&WorldBorderDiameter,
&WorldBorderPortalTpBoundary,
Option<&MovingWorldBorder>,
),
Changed<WorldBorderPortalTpBoundary>,
>,
) {
for (mut ins, c, wt, wb, diameter, ptb, wbl) in wbs.iter_mut() {
let (new_diameter, speed) = if let Some(lerping) = wbl {
(lerping.new_diameter, lerping.current_duration())
} else {
(diameter.0, 0)
};
ins.write_packet(&WorldBorderInitializeS2c {
x: c.0.x,
z: c.0.y,
old_diameter: diameter.0,
new_diameter,
portal_teleport_boundary: VarInt(ptb.0),
speed: VarLong(speed),
warning_blocks: VarInt(wb.0),
warning_time: VarInt(wt.0),
});
}
}
fn lerp(start: f64, end: f64, t: f64) -> f64 {
start + (end - start) * t
}

View file

@ -0,0 +1,49 @@
use valence_core::protocol::var_int::VarInt;
use valence_core::protocol::var_long::VarLong;
use valence_core::protocol::{packet_id, Decode, Encode, Packet};
#[derive(Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::WORLD_BORDER_CENTER_CHANGED_S2C)]
pub struct WorldBorderCenterChangedS2c {
pub x_pos: f64,
pub z_pos: f64,
}
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::WORLD_BORDER_INITIALIZE_S2C)]
pub struct WorldBorderInitializeS2c {
pub x: f64,
pub z: f64,
pub old_diameter: f64,
pub new_diameter: f64,
pub speed: VarLong,
pub portal_teleport_boundary: VarInt,
pub warning_blocks: VarInt,
pub warning_time: VarInt,
}
#[derive(Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::WORLD_BORDER_INTERPOLATE_SIZE_S2C)]
pub struct WorldBorderInterpolateSizeS2c {
pub old_diameter: f64,
pub new_diameter: f64,
pub speed: VarLong,
}
#[derive(Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::WORLD_BORDER_SIZE_CHANGED_S2C)]
pub struct WorldBorderSizeChangedS2c {
pub diameter: f64,
}
#[derive(Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::WORLD_BORDER_WARNING_BLOCKS_CHANGED_S2C)]
pub struct WorldBorderWarningBlocksChangedS2c {
pub warning_blocks: VarInt,
}
#[derive(Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::WORLD_BORDER_WARNING_TIME_CHANGED_S2C)]
pub struct WorldBorderWarningTimeChangedS2c {
pub warning_time: VarInt,
}