-
-
Notifications
You must be signed in to change notification settings - Fork 145
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
## 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
- Loading branch information
1 parent
09fbd9b
commit 61f2279
Showing
11 changed files
with
769 additions
and
47 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,4 +22,5 @@ graph TD | |
anvil --> instance | ||
entity --> block | ||
advancement --> client | ||
world_border --> client | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 }; | ||
} | ||
_ => (), | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -284,3 +284,4 @@ mod client; | |
mod example; | ||
mod inventory; | ||
mod weather; | ||
mod world_border; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} |
Oops, something went wrong.