mirror of
https://github.com/italicsjenga/valence.git
synced 2024-12-23 22:41:30 +11:00
Store entities and clients per server rather than per world
This commit is contained in:
parent
985ecf3922
commit
79cb4c159a
|
@ -10,8 +10,8 @@ use valence::client::{Event, GameMode};
|
||||||
use valence::config::{Config, ServerListPing};
|
use valence::config::{Config, ServerListPing};
|
||||||
use valence::text::Color;
|
use valence::text::Color;
|
||||||
use valence::{
|
use valence::{
|
||||||
async_trait, ident, Biome, BlockState, Client, Dimension, DimensionId, Server, ShutdownResult,
|
async_trait, ident, Biome, BlockState, Dimension, DimensionId, Server, SharedServer,
|
||||||
Text, TextFormat, WorldId, Worlds,
|
ShutdownResult, TextFormat,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn main() -> ShutdownResult {
|
pub fn main() -> ShutdownResult {
|
||||||
|
@ -57,6 +57,13 @@ impl Config for Game {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn dimensions(&self) -> Vec<Dimension> {
|
||||||
|
vec![Dimension {
|
||||||
|
fixed_time: Some(6000),
|
||||||
|
..Dimension::default()
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
fn biomes(&self) -> Vec<Biome> {
|
fn biomes(&self) -> Vec<Biome> {
|
||||||
vec![Biome {
|
vec![Biome {
|
||||||
name: ident!("valence:default_biome"),
|
name: ident!("valence:default_biome"),
|
||||||
|
@ -65,14 +72,11 @@ impl Config for Game {
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dimensions(&self) -> Vec<Dimension> {
|
async fn server_list_ping(
|
||||||
vec![Dimension {
|
&self,
|
||||||
fixed_time: Some(6000),
|
_server: &SharedServer,
|
||||||
..Dimension::default()
|
_remote_addr: SocketAddr,
|
||||||
}]
|
) -> ServerListPing {
|
||||||
}
|
|
||||||
|
|
||||||
async fn server_list_ping(&self, _server: &Server, _remote_addr: SocketAddr) -> ServerListPing {
|
|
||||||
ServerListPing::Respond {
|
ServerListPing::Respond {
|
||||||
online_players: self.player_count.load(Ordering::SeqCst) as i32,
|
online_players: self.player_count.load(Ordering::SeqCst) as i32,
|
||||||
max_players: MAX_PLAYERS as i32,
|
max_players: MAX_PLAYERS as i32,
|
||||||
|
@ -81,26 +85,8 @@ impl Config for Game {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn join(
|
fn init(&self, server: &mut Server) {
|
||||||
&self,
|
let world = server.worlds.create(DimensionId::default()).1;
|
||||||
_server: &Server,
|
|
||||||
_client: &mut Client,
|
|
||||||
worlds: &mut Worlds,
|
|
||||||
) -> Result<WorldId, Text> {
|
|
||||||
if let Ok(_) = self
|
|
||||||
.player_count
|
|
||||||
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| {
|
|
||||||
(count < MAX_PLAYERS).then(|| count + 1)
|
|
||||||
})
|
|
||||||
{
|
|
||||||
Ok(worlds.iter().next().unwrap().0)
|
|
||||||
} else {
|
|
||||||
Err("The server is full!".into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init(&self, _server: &Server, worlds: &mut Worlds) {
|
|
||||||
let world = worlds.create(DimensionId::default()).1;
|
|
||||||
world.meta.set_flat(true);
|
world.meta.set_flat(true);
|
||||||
|
|
||||||
for chunk_z in -2..Integer::div_ceil(&(SIZE_X as i32), &16) + 2 {
|
for chunk_z in -2..Integer::div_ceil(&(SIZE_X as i32), &16) + 2 {
|
||||||
|
@ -110,8 +96,8 @@ impl Config for Game {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&self, server: &Server, worlds: &mut Worlds) {
|
fn update(&self, server: &mut Server) {
|
||||||
let world = worlds.iter_mut().next().unwrap().1;
|
let (world_id, world) = server.worlds.iter_mut().next().unwrap();
|
||||||
|
|
||||||
let spawn_pos = [
|
let spawn_pos = [
|
||||||
SIZE_X as f64 / 2.0,
|
SIZE_X as f64 / 2.0,
|
||||||
|
@ -119,10 +105,21 @@ impl Config for Game {
|
||||||
SIZE_Z as f64 / 2.0,
|
SIZE_Z as f64 / 2.0,
|
||||||
];
|
];
|
||||||
|
|
||||||
world.clients.retain(|_, client| {
|
server.clients.retain(|_, client| {
|
||||||
if client.created_tick() == server.current_tick() {
|
if client.created_tick() == server.shared.current_tick() {
|
||||||
client.set_game_mode(GameMode::Survival);
|
if self
|
||||||
|
.player_count
|
||||||
|
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| {
|
||||||
|
(count < MAX_PLAYERS).then_some(count + 1)
|
||||||
|
})
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
client.disconnect("The server is full!".color(Color::RED));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
client.set_world(world_id);
|
||||||
|
client.set_game_mode(GameMode::Survival);
|
||||||
client.teleport(spawn_pos, 0.0, 0.0);
|
client.teleport(spawn_pos, 0.0, 0.0);
|
||||||
|
|
||||||
world.meta.player_list_mut().insert(
|
world.meta.player_list_mut().insert(
|
||||||
|
@ -148,7 +145,7 @@ impl Config for Game {
|
||||||
|
|
||||||
let State { board, board_buf } = &mut *self.state.lock().unwrap();
|
let State { board, board_buf } = &mut *self.state.lock().unwrap();
|
||||||
|
|
||||||
for (_, client) in world.clients.iter_mut() {
|
for (_, client) in server.clients.iter_mut() {
|
||||||
while let Some(event) = client.pop_event() {
|
while let Some(event) = client.pop_event() {
|
||||||
match event {
|
match event {
|
||||||
Event::Digging(e) => {
|
Event::Digging(e) => {
|
||||||
|
@ -171,7 +168,7 @@ impl Config for Game {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if server.current_tick() % 4 != 0 {
|
if server.shared.current_tick() % 4 != 0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,7 +198,7 @@ impl Config for Game {
|
||||||
|
|
||||||
mem::swap(board, board_buf);
|
mem::swap(board, board_buf);
|
||||||
|
|
||||||
let min_y = server.dimensions().next().unwrap().1.min_y;
|
let min_y = server.shared.dimensions().next().unwrap().1.min_y;
|
||||||
|
|
||||||
for chunk_x in 0..Integer::div_ceil(&SIZE_X, &16) {
|
for chunk_x in 0..Integer::div_ceil(&SIZE_X, &16) {
|
||||||
for chunk_z in 0..Integer::div_ceil(&SIZE_Z, &16) {
|
for chunk_z in 0..Integer::div_ceil(&SIZE_Z, &16) {
|
||||||
|
|
|
@ -9,8 +9,8 @@ use valence::config::{Config, ServerListPing};
|
||||||
use valence::text::Color;
|
use valence::text::Color;
|
||||||
use valence::util::to_yaw_and_pitch;
|
use valence::util::to_yaw_and_pitch;
|
||||||
use valence::{
|
use valence::{
|
||||||
async_trait, Client, DimensionId, EntityId, EntityType, Server, ShutdownResult, Text,
|
async_trait, DimensionId, EntityId, EntityType, Server, SharedServer, ShutdownResult,
|
||||||
TextFormat, WorldId, Worlds,
|
TextFormat,
|
||||||
};
|
};
|
||||||
use vek::{Mat3, Vec3};
|
use vek::{Mat3, Vec3};
|
||||||
|
|
||||||
|
@ -45,7 +45,11 @@ impl Config for Game {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn server_list_ping(&self, _server: &Server, _remote_addr: SocketAddr) -> ServerListPing {
|
async fn server_list_ping(
|
||||||
|
&self,
|
||||||
|
_server: &SharedServer,
|
||||||
|
_remote_addr: SocketAddr,
|
||||||
|
) -> ServerListPing {
|
||||||
ServerListPing::Respond {
|
ServerListPing::Respond {
|
||||||
online_players: self.player_count.load(Ordering::SeqCst) as i32,
|
online_players: self.player_count.load(Ordering::SeqCst) as i32,
|
||||||
max_players: MAX_PLAYERS as i32,
|
max_players: MAX_PLAYERS as i32,
|
||||||
|
@ -54,26 +58,8 @@ impl Config for Game {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn join(
|
fn init(&self, server: &mut Server) {
|
||||||
&self,
|
let (world_id, world) = server.worlds.create(DimensionId::default());
|
||||||
_server: &Server,
|
|
||||||
_client: &mut Client,
|
|
||||||
worlds: &mut Worlds,
|
|
||||||
) -> Result<WorldId, Text> {
|
|
||||||
if let Ok(_) = self
|
|
||||||
.player_count
|
|
||||||
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| {
|
|
||||||
(count < MAX_PLAYERS).then(|| count + 1)
|
|
||||||
})
|
|
||||||
{
|
|
||||||
Ok(worlds.iter().next().unwrap().0)
|
|
||||||
} else {
|
|
||||||
Err("The server is full!".into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init(&self, _server: &Server, worlds: &mut Worlds) {
|
|
||||||
let world = worlds.create(DimensionId::default()).1;
|
|
||||||
world.meta.set_flat(true);
|
world.meta.set_flat(true);
|
||||||
|
|
||||||
let size = 5;
|
let size = 5;
|
||||||
|
@ -84,17 +70,30 @@ impl Config for Game {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.cows.lock().unwrap().extend((0..200).map(|_| {
|
self.cows.lock().unwrap().extend((0..200).map(|_| {
|
||||||
let (id, e) = world.entities.create();
|
let (id, e) = server.entities.create();
|
||||||
|
e.set_world(world_id);
|
||||||
e.set_type(EntityType::Cow);
|
e.set_type(EntityType::Cow);
|
||||||
id
|
id
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&self, server: &Server, worlds: &mut Worlds) {
|
fn update(&self, server: &mut Server) {
|
||||||
let world = worlds.iter_mut().next().unwrap().1;
|
let (world_id, world) = server.worlds.iter_mut().next().unwrap();
|
||||||
|
|
||||||
world.clients.retain(|_, client| {
|
server.clients.retain(|_, client| {
|
||||||
if client.created_tick() == server.current_tick() {
|
if client.created_tick() == server.shared.current_tick() {
|
||||||
|
if self
|
||||||
|
.player_count
|
||||||
|
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| {
|
||||||
|
(count < MAX_PLAYERS).then_some(count + 1)
|
||||||
|
})
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
client.disconnect("The server is full!".color(Color::RED));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
client.set_world(world_id);
|
||||||
client.set_game_mode(GameMode::Creative);
|
client.set_game_mode(GameMode::Creative);
|
||||||
client.teleport([0.0, 200.0, 0.0], 0.0, 0.0);
|
client.teleport([0.0, 200.0, 0.0], 0.0, 0.0);
|
||||||
|
|
||||||
|
@ -116,7 +115,7 @@ impl Config for Game {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let time = server.current_tick() as f64 / server.tick_rate() as f64;
|
let time = server.shared.current_tick() as f64 / server.shared.tick_rate() as f64;
|
||||||
|
|
||||||
let rot = Mat3::rotation_x(time * TAU * 0.1)
|
let rot = Mat3::rotation_x(time * TAU * 0.1)
|
||||||
.rotated_y(time * TAU * 0.2)
|
.rotated_y(time * TAU * 0.2)
|
||||||
|
@ -128,7 +127,7 @@ impl Config for Game {
|
||||||
let radius = 6.0 + ((time * TAU / 2.5).sin() + 1.0) / 2.0 * 10.0;
|
let radius = 6.0 + ((time * TAU / 2.5).sin() + 1.0) / 2.0 * 10.0;
|
||||||
|
|
||||||
// TODO: use eye position.
|
// TODO: use eye position.
|
||||||
let player_pos = world
|
let player_pos = server
|
||||||
.clients
|
.clients
|
||||||
.iter()
|
.iter()
|
||||||
.next()
|
.next()
|
||||||
|
@ -136,7 +135,7 @@ impl Config for Game {
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
for (cow_id, p) in cows.iter().cloned().zip(fibonacci_spiral(cow_count)) {
|
for (cow_id, p) in cows.iter().cloned().zip(fibonacci_spiral(cow_count)) {
|
||||||
let cow = world.entities.get_mut(cow_id).expect("missing cow");
|
let cow = server.entities.get_mut(cow_id).expect("missing cow");
|
||||||
let rotated = p * rot;
|
let rotated = p * rot;
|
||||||
let transformed = rotated * radius + [0.0, 100.0, 0.0];
|
let transformed = rotated * radius + [0.0, 100.0, 0.0];
|
||||||
|
|
||||||
|
|
|
@ -11,8 +11,7 @@ use valence::config::{Config, ServerListPing};
|
||||||
use valence::text::Color;
|
use valence::text::Color;
|
||||||
use valence::util::chunks_in_view_distance;
|
use valence::util::chunks_in_view_distance;
|
||||||
use valence::{
|
use valence::{
|
||||||
async_trait, ChunkPos, Client, DimensionId, Server, ShutdownResult, Text, TextFormat, WorldId,
|
async_trait, ChunkPos, DimensionId, Server, SharedServer, ShutdownResult, TextFormat,
|
||||||
Worlds,
|
|
||||||
};
|
};
|
||||||
use vek::Lerp;
|
use vek::Lerp;
|
||||||
|
|
||||||
|
@ -57,7 +56,11 @@ impl Config for Game {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn server_list_ping(&self, _server: &Server, _remote_addr: SocketAddr) -> ServerListPing {
|
async fn server_list_ping(
|
||||||
|
&self,
|
||||||
|
_server: &SharedServer,
|
||||||
|
_remote_addr: SocketAddr,
|
||||||
|
) -> ServerListPing {
|
||||||
ServerListPing::Respond {
|
ServerListPing::Respond {
|
||||||
online_players: self.player_count.load(Ordering::SeqCst) as i32,
|
online_players: self.player_count.load(Ordering::SeqCst) as i32,
|
||||||
max_players: MAX_PLAYERS as i32,
|
max_players: MAX_PLAYERS as i32,
|
||||||
|
@ -66,41 +69,35 @@ impl Config for Game {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn join(
|
fn init(&self, server: &mut Server) {
|
||||||
&self,
|
let (_, world) = server.worlds.create(DimensionId::default());
|
||||||
_server: &Server,
|
|
||||||
_client: &mut Client,
|
|
||||||
worlds: &mut Worlds,
|
|
||||||
) -> Result<WorldId, Text> {
|
|
||||||
if let Ok(_) = self
|
|
||||||
.player_count
|
|
||||||
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| {
|
|
||||||
(count < MAX_PLAYERS).then(|| count + 1)
|
|
||||||
})
|
|
||||||
{
|
|
||||||
Ok(worlds.iter().next().unwrap().0)
|
|
||||||
} else {
|
|
||||||
Err("The server is full!".into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init(&self, _server: &Server, worlds: &mut Worlds) {
|
|
||||||
let (_, world) = worlds.create(DimensionId::default());
|
|
||||||
world.meta.set_flat(true);
|
world.meta.set_flat(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&self, server: &Server, worlds: &mut Worlds) {
|
fn update(&self, server: &mut Server) {
|
||||||
let world = worlds.iter_mut().next().unwrap().1;
|
let (world_id, world) = server.worlds.iter_mut().next().unwrap();
|
||||||
|
|
||||||
let mut chunks_to_unload = HashSet::<_>::from_iter(world.chunks.iter().map(|t| t.0));
|
let mut chunks_to_unload = HashSet::<_>::from_iter(world.chunks.iter().map(|t| t.0));
|
||||||
|
|
||||||
world.clients.retain(|_, client| {
|
server.clients.retain(|_, client| {
|
||||||
if client.is_disconnected() {
|
if client.is_disconnected() {
|
||||||
self.player_count.fetch_sub(1, Ordering::SeqCst);
|
self.player_count.fetch_sub(1, Ordering::SeqCst);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if client.created_tick() == server.current_tick() {
|
if client.created_tick() == server.shared.current_tick() {
|
||||||
|
if self
|
||||||
|
.player_count
|
||||||
|
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| {
|
||||||
|
(count < MAX_PLAYERS).then_some(count + 1)
|
||||||
|
})
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
client.disconnect("The server is full!".color(Color::RED));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
client.set_world(world_id);
|
||||||
client.set_game_mode(GameMode::Creative);
|
client.set_game_mode(GameMode::Creative);
|
||||||
client.set_max_view_distance(32);
|
client.set_max_view_distance(32);
|
||||||
client.teleport([0.0, 200.0, 0.0], 0.0, 0.0);
|
client.teleport([0.0, 200.0, 0.0], 0.0, 0.0);
|
||||||
|
@ -115,6 +112,10 @@ impl Config for Game {
|
||||||
);
|
);
|
||||||
|
|
||||||
client.send_message("Welcome to the terrain example!".italic());
|
client.send_message("Welcome to the terrain example!".italic());
|
||||||
|
client.send_message(
|
||||||
|
"This demonstrates how to create infinite procedurally generated terrain."
|
||||||
|
.italic(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let dist = client.view_distance();
|
let dist = client.view_distance();
|
||||||
|
@ -135,7 +136,10 @@ impl Config for Game {
|
||||||
}
|
}
|
||||||
|
|
||||||
world.chunks.par_iter_mut().for_each(|(pos, chunk)| {
|
world.chunks.par_iter_mut().for_each(|(pos, chunk)| {
|
||||||
if chunk.created_tick() == server.current_tick() {
|
if chunk.created_tick() != server.shared.current_tick() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for z in 0..16 {
|
for z in 0..16 {
|
||||||
for x in 0..16 {
|
for x in 0..16 {
|
||||||
let block_x = x as i64 + pos.x as i64 * 16;
|
let block_x = x as i64 + pos.x as i64 * 16;
|
||||||
|
@ -170,8 +174,7 @@ impl Config for Game {
|
||||||
);
|
);
|
||||||
|
|
||||||
if density > 0.55 {
|
if density > 0.55 {
|
||||||
if density > 0.7 && chunk.get_block_state(x, y + 1, z).is_air()
|
if density > 0.7 && chunk.get_block_state(x, y + 1, z).is_air() {
|
||||||
{
|
|
||||||
let upper = BlockState::TALL_GRASS
|
let upper = BlockState::TALL_GRASS
|
||||||
.set(PropName::Half, PropValue::Upper);
|
.set(PropName::Half, PropValue::Upper);
|
||||||
let lower = BlockState::TALL_GRASS
|
let lower = BlockState::TALL_GRASS
|
||||||
|
@ -187,7 +190,6 @@ impl Config for Game {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -257,8 +259,8 @@ fn has_terrain_at(g: &Game, x: i64, y: i64, z: i64) -> bool {
|
||||||
noise01(&g.hilly_noise, [x, y, z].map(|a| a as f64 / 400.0)).powi(2),
|
noise01(&g.hilly_noise, [x, y, z].map(|a| a as f64 / 400.0)).powi(2),
|
||||||
);
|
);
|
||||||
|
|
||||||
let lower = 15.0 + 75.0 * hilly;
|
let lower = 15.0 + 100.0 * hilly;
|
||||||
let upper = lower + 125.0 * hilly;
|
let upper = lower + 100.0 * hilly;
|
||||||
|
|
||||||
if y as f64 <= lower {
|
if y as f64 <= lower {
|
||||||
return true;
|
return true;
|
||||||
|
@ -266,7 +268,7 @@ fn has_terrain_at(g: &Game, x: i64, y: i64, z: i64) -> bool {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let density = (1.0 - lerpstep(lower, upper, y as f64)).sqrt();
|
let density = 1.0 - lerpstep(lower, upper, y as f64);
|
||||||
|
|
||||||
let n = fbm(
|
let n = fbm(
|
||||||
&g.density_noise,
|
&g.density_noise,
|
||||||
|
|
|
@ -13,16 +13,16 @@ use crate::protocol::packets::play::s2c::{
|
||||||
BlockUpdate, LevelChunkHeightmaps, LevelChunkWithLight, S2cPlayPacket, SectionBlocksUpdate,
|
BlockUpdate, LevelChunkHeightmaps, LevelChunkWithLight, S2cPlayPacket, SectionBlocksUpdate,
|
||||||
};
|
};
|
||||||
use crate::protocol::{Encode, Nbt, VarInt, VarLong};
|
use crate::protocol::{Encode, Nbt, VarInt, VarLong};
|
||||||
use crate::{BiomeId, BlockPos, ChunkPos, DimensionId, Server, Ticks};
|
use crate::{BiomeId, BlockPos, ChunkPos, DimensionId, SharedServer, Ticks};
|
||||||
|
|
||||||
pub struct Chunks {
|
pub struct Chunks {
|
||||||
chunks: HashMap<ChunkPos, Chunk>,
|
chunks: HashMap<ChunkPos, Chunk>,
|
||||||
server: Server,
|
server: SharedServer,
|
||||||
dimension: DimensionId,
|
dimension: DimensionId,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Chunks {
|
impl Chunks {
|
||||||
pub(crate) fn new(server: Server, dimension: DimensionId) -> Self {
|
pub(crate) fn new(server: SharedServer, dimension: DimensionId) -> Self {
|
||||||
Self {
|
Self {
|
||||||
chunks: HashMap::new(),
|
chunks: HashMap::new(),
|
||||||
server,
|
server,
|
||||||
|
|
|
@ -31,8 +31,8 @@ use crate::server::C2sPacketChannels;
|
||||||
use crate::slotmap::{Key, SlotMap};
|
use crate::slotmap::{Key, SlotMap};
|
||||||
use crate::util::{chunks_in_view_distance, is_chunk_in_view_distance};
|
use crate::util::{chunks_in_view_distance, is_chunk_in_view_distance};
|
||||||
use crate::{
|
use crate::{
|
||||||
ident, BlockPos, ChunkPos, Chunks, DimensionId, Entities, EntityId, NewClientData, Server,
|
ident, BlockPos, ChunkPos, DimensionId, Entities, EntityId, NewClientData, SharedServer, Text,
|
||||||
SpatialIndex, Text, Ticks, WorldMeta, LIBRARY_NAMESPACE,
|
Ticks, WorldId, Worlds, LIBRARY_NAMESPACE,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Clients {
|
pub struct Clients {
|
||||||
|
@ -44,7 +44,7 @@ impl Clients {
|
||||||
Self { sm: SlotMap::new() }
|
Self { sm: SlotMap::new() }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn create(&mut self, client: Client) -> (ClientId, &mut Client) {
|
pub(crate) fn insert(&mut self, client: Client) -> (ClientId, &mut Client) {
|
||||||
let (id, client) = self.sm.insert(client);
|
let (id, client) = self.sm.insert(client);
|
||||||
(ClientId(id), client)
|
(ClientId(id), client)
|
||||||
}
|
}
|
||||||
|
@ -98,6 +98,8 @@ pub struct Client {
|
||||||
uuid: Uuid,
|
uuid: Uuid,
|
||||||
username: String,
|
username: String,
|
||||||
textures: Option<SignedPlayerTextures>,
|
textures: Option<SignedPlayerTextures>,
|
||||||
|
new_world: Option<WorldId>,
|
||||||
|
old_world: Option<WorldId>,
|
||||||
on_ground: bool,
|
on_ground: bool,
|
||||||
new_position: Vec3<f64>,
|
new_position: Vec3<f64>,
|
||||||
old_position: Vec3<f64>,
|
old_position: Vec3<f64>,
|
||||||
|
@ -143,7 +145,7 @@ pub struct Client {
|
||||||
impl Client {
|
impl Client {
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
packet_channels: C2sPacketChannels,
|
packet_channels: C2sPacketChannels,
|
||||||
server: &Server,
|
server: &SharedServer,
|
||||||
ncd: NewClientData,
|
ncd: NewClientData,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let (send, recv) = packet_channels;
|
let (send, recv) = packet_channels;
|
||||||
|
@ -155,6 +157,8 @@ impl Client {
|
||||||
uuid: ncd.uuid,
|
uuid: ncd.uuid,
|
||||||
username: ncd.username,
|
username: ncd.username,
|
||||||
textures: ncd.textures,
|
textures: ncd.textures,
|
||||||
|
new_world: None,
|
||||||
|
old_world: None,
|
||||||
on_ground: false,
|
on_ground: false,
|
||||||
new_position: Vec3::default(),
|
new_position: Vec3::default(),
|
||||||
old_position: Vec3::default(),
|
old_position: Vec3::default(),
|
||||||
|
@ -199,6 +203,14 @@ impl Client {
|
||||||
self.textures.as_ref()
|
self.textures.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn world(&self) -> Option<WorldId> {
|
||||||
|
self.new_world
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_world(&mut self, world: WorldId) {
|
||||||
|
self.new_world = Some(world);
|
||||||
|
}
|
||||||
|
|
||||||
/// Sends a system message to the player.
|
/// Sends a system message to the player.
|
||||||
pub fn send_message(&mut self, msg: impl Into<Text>) {
|
pub fn send_message(&mut self, msg: impl Into<Text>) {
|
||||||
self.msgs_to_send.push(msg.into());
|
self.msgs_to_send.push(msg.into());
|
||||||
|
@ -554,26 +566,33 @@ impl Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn update(
|
pub(crate) fn update(&mut self, shared: &SharedServer, entities: &Entities, worlds: &Worlds) {
|
||||||
&mut self,
|
|
||||||
server: &Server,
|
|
||||||
entities: &Entities,
|
|
||||||
spatial_index: &SpatialIndex,
|
|
||||||
chunks: &Chunks,
|
|
||||||
meta: &WorldMeta,
|
|
||||||
) {
|
|
||||||
// Mark the client as disconnected when appropriate.
|
// Mark the client as disconnected when appropriate.
|
||||||
if self.recv.is_disconnected() || self.send.as_ref().map_or(true, |s| s.is_disconnected()) {
|
if self.recv.is_disconnected() || self.send.as_ref().map_or(true, |s| s.is_disconnected()) {
|
||||||
self.send = None;
|
self.send = None;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let current_tick = server.current_tick();
|
let world = match self.new_world.and_then(|id| worlds.get(id)) {
|
||||||
|
Some(world) => world,
|
||||||
|
None => {
|
||||||
|
log::warn!(
|
||||||
|
"client {} is in an invalid world and must be disconnected",
|
||||||
|
self.username()
|
||||||
|
);
|
||||||
|
self.disconnect_no_reason();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let current_tick = shared.current_tick();
|
||||||
|
|
||||||
// Send the join game packet and other initial packets. We defer this until now
|
// Send the join game packet and other initial packets. We defer this until now
|
||||||
// so that the user can set the client's location, game mode, etc.
|
// so that the user can set the client's location, game mode, etc.
|
||||||
if self.created_tick == current_tick {
|
if self.created_tick == current_tick {
|
||||||
meta.player_list()
|
world
|
||||||
|
.meta
|
||||||
|
.player_list()
|
||||||
.initial_packets(|pkt| self.send_packet(pkt));
|
.initial_packets(|pkt| self.send_packet(pkt));
|
||||||
|
|
||||||
self.send_packet(Login {
|
self.send_packet(Login {
|
||||||
|
@ -581,16 +600,19 @@ impl Client {
|
||||||
is_hardcore: false, // TODO
|
is_hardcore: false, // TODO
|
||||||
gamemode: self.new_game_mode,
|
gamemode: self.new_game_mode,
|
||||||
previous_gamemode: self.old_game_mode,
|
previous_gamemode: self.old_game_mode,
|
||||||
dimension_names: server
|
dimension_names: shared
|
||||||
.dimensions()
|
.dimensions()
|
||||||
.map(|(id, _)| ident!("{LIBRARY_NAMESPACE}:dimension_{}", id.0))
|
.map(|(id, _)| ident!("{LIBRARY_NAMESPACE}:dimension_{}", id.0))
|
||||||
.collect(),
|
.collect(),
|
||||||
registry_codec: Nbt(make_dimension_codec(server)),
|
registry_codec: Nbt(make_dimension_codec(shared)),
|
||||||
dimension_type_name: ident!(
|
dimension_type_name: ident!(
|
||||||
"{LIBRARY_NAMESPACE}:dimension_type_{}",
|
"{LIBRARY_NAMESPACE}:dimension_type_{}",
|
||||||
meta.dimension().0
|
world.meta.dimension().0
|
||||||
|
),
|
||||||
|
dimension_name: ident!(
|
||||||
|
"{LIBRARY_NAMESPACE}:dimension_{}",
|
||||||
|
world.meta.dimension().0
|
||||||
),
|
),
|
||||||
dimension_name: ident!("{LIBRARY_NAMESPACE}:dimension_{}", meta.dimension().0),
|
|
||||||
hashed_seed: 0,
|
hashed_seed: 0,
|
||||||
max_players: VarInt(0),
|
max_players: VarInt(0),
|
||||||
view_distance: BoundedInt(VarInt(self.new_max_view_distance as i32)),
|
view_distance: BoundedInt(VarInt(self.new_max_view_distance as i32)),
|
||||||
|
@ -598,7 +620,7 @@ impl Client {
|
||||||
reduced_debug_info: false,
|
reduced_debug_info: false,
|
||||||
enable_respawn_screen: false,
|
enable_respawn_screen: false,
|
||||||
is_debug: false,
|
is_debug: false,
|
||||||
is_flat: meta.is_flat(),
|
is_flat: world.meta.is_flat(),
|
||||||
last_death_location: self
|
last_death_location: self
|
||||||
.death_location
|
.death_location
|
||||||
.map(|(id, pos)| (ident!("{LIBRARY_NAMESPACE}:dimension_{}", id.0), pos)),
|
.map(|(id, pos)| (ident!("{LIBRARY_NAMESPACE}:dimension_{}", id.0), pos)),
|
||||||
|
@ -606,6 +628,13 @@ impl Client {
|
||||||
|
|
||||||
self.teleport(self.position(), self.yaw(), self.pitch());
|
self.teleport(self.position(), self.yaw(), self.pitch());
|
||||||
} else {
|
} else {
|
||||||
|
if self.new_world != self.old_world {
|
||||||
|
self.loaded_entities.clear();
|
||||||
|
self.loaded_chunks.clear();
|
||||||
|
|
||||||
|
todo!("send respawn packet");
|
||||||
|
}
|
||||||
|
|
||||||
if self.old_game_mode != self.new_game_mode {
|
if self.old_game_mode != self.new_game_mode {
|
||||||
self.old_game_mode = self.new_game_mode;
|
self.old_game_mode = self.new_game_mode;
|
||||||
self.send_packet(GameEvent {
|
self.send_packet(GameEvent {
|
||||||
|
@ -614,7 +643,10 @@ impl Client {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
meta.player_list().diff_packets(|pkt| self.send_packet(pkt));
|
world
|
||||||
|
.meta
|
||||||
|
.player_list()
|
||||||
|
.diff_packets(|pkt| self.send_packet(pkt));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the players spawn position (compass position)
|
// Update the players spawn position (compass position)
|
||||||
|
@ -638,7 +670,7 @@ impl Client {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if it's time to send another keepalive.
|
// Check if it's time to send another keepalive.
|
||||||
if current_tick % (server.tick_rate() * 8) == 0 {
|
if current_tick % (shared.tick_rate() * 8) == 0 {
|
||||||
if self.got_keepalive {
|
if self.got_keepalive {
|
||||||
let id = rand::random();
|
let id = rand::random();
|
||||||
self.send_packet(KeepAlive { id });
|
self.send_packet(KeepAlive { id });
|
||||||
|
@ -671,7 +703,7 @@ impl Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let dimension = server.dimension(meta.dimension());
|
let dimension = shared.dimension(world.meta.dimension());
|
||||||
|
|
||||||
// Update existing chunks and unload those outside the view distance. Chunks
|
// Update existing chunks and unload those outside the view distance. Chunks
|
||||||
// that have been overwritten also need to be unloaded.
|
// that have been overwritten also need to be unloaded.
|
||||||
|
@ -680,7 +712,7 @@ impl Client {
|
||||||
// moves to an adjacent chunk and back to the original.
|
// moves to an adjacent chunk and back to the original.
|
||||||
let cache = 2;
|
let cache = 2;
|
||||||
|
|
||||||
if let Some(chunk) = chunks.get(pos) {
|
if let Some(chunk) = world.chunks.get(pos) {
|
||||||
if is_chunk_in_view_distance(center, pos, view_dist + cache)
|
if is_chunk_in_view_distance(center, pos, view_dist + cache)
|
||||||
&& chunk.created_tick() != current_tick
|
&& chunk.created_tick() != current_tick
|
||||||
{
|
{
|
||||||
|
@ -703,7 +735,7 @@ impl Client {
|
||||||
|
|
||||||
// Load new chunks within the view distance
|
// Load new chunks within the view distance
|
||||||
for pos in chunks_in_view_distance(center, view_dist) {
|
for pos in chunks_in_view_distance(center, view_dist) {
|
||||||
if let Some(chunk) = chunks.get(pos) {
|
if let Some(chunk) = world.chunks.get(pos) {
|
||||||
if self.loaded_chunks.insert(pos) {
|
if self.loaded_chunks.insert(pos) {
|
||||||
self.send_packet(chunk.chunk_data_packet(pos));
|
self.send_packet(chunk.chunk_data_packet(pos));
|
||||||
chunk.block_change_packets(pos, dimension.min_y, |pkt| self.send_packet(pkt));
|
chunk.block_change_packets(pos, dimension.min_y, |pkt| self.send_packet(pkt));
|
||||||
|
@ -866,7 +898,7 @@ impl Client {
|
||||||
|
|
||||||
// Spawn new entities within the view distance.
|
// Spawn new entities within the view distance.
|
||||||
let pos = self.position();
|
let pos = self.position();
|
||||||
spatial_index.query::<_, _, ()>(
|
world.spatial_index.query::<_, _, ()>(
|
||||||
|bb| bb.projected_point(pos).distance(pos) <= view_dist as f64 * 16.0,
|
|bb| bb.projected_point(pos).distance(pos) <= view_dist as f64 * 16.0,
|
||||||
|id, _| {
|
|id, _| {
|
||||||
if self.loaded_entities.insert(id) {
|
if self.loaded_entities.insert(id) {
|
||||||
|
@ -888,6 +920,7 @@ impl Client {
|
||||||
);
|
);
|
||||||
|
|
||||||
self.old_position = self.new_position;
|
self.old_position = self.new_position;
|
||||||
|
self.old_world = self.new_world;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -906,9 +939,9 @@ fn send_packet(send_opt: &mut Option<Sender<S2cPlayPacket>>, pkt: impl Into<S2cP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_dimension_codec(server: &Server) -> RegistryCodec {
|
fn make_dimension_codec(shared: &SharedServer) -> RegistryCodec {
|
||||||
let mut dims = Vec::new();
|
let mut dims = Vec::new();
|
||||||
for (id, dim) in server.dimensions() {
|
for (id, dim) in shared.dimensions() {
|
||||||
let id = id.0 as i32;
|
let id = id.0 as i32;
|
||||||
dims.push(DimensionTypeRegistryEntry {
|
dims.push(DimensionTypeRegistryEntry {
|
||||||
name: ident!("{LIBRARY_NAMESPACE}:dimension_type_{id}"),
|
name: ident!("{LIBRARY_NAMESPACE}:dimension_type_{id}"),
|
||||||
|
@ -918,7 +951,7 @@ fn make_dimension_codec(server: &Server) -> RegistryCodec {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut biomes = Vec::new();
|
let mut biomes = Vec::new();
|
||||||
for (id, biome) in server.biomes() {
|
for (id, biome) in shared.biomes() {
|
||||||
biomes.push(to_biome_registry_item(biome, id.0 as i32));
|
biomes.push(to_biome_registry_item(biome, id.0 as i32));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ use std::panic::{RefUnwindSafe, UnwindSafe};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use tokio::runtime::Handle as TokioHandle;
|
use tokio::runtime::Handle as TokioHandle;
|
||||||
|
|
||||||
use crate::{Biome, Client, Dimension, NewClientData, Server, Text, Ticks, WorldId, Worlds};
|
use crate::{Biome, Dimension, NewClientData, Server, SharedServer, Text, Ticks};
|
||||||
|
|
||||||
/// A trait containing callbacks which are invoked by the running Minecraft
|
/// A trait containing callbacks which are invoked by the running Minecraft
|
||||||
/// server.
|
/// server.
|
||||||
|
@ -151,7 +151,11 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
|
||||||
///
|
///
|
||||||
/// # Default Implementation
|
/// # Default Implementation
|
||||||
/// The query is ignored.
|
/// The query is ignored.
|
||||||
async fn server_list_ping(&self, server: &Server, remote_addr: SocketAddr) -> ServerListPing {
|
async fn server_list_ping(
|
||||||
|
&self,
|
||||||
|
shared: &SharedServer,
|
||||||
|
remote_addr: SocketAddr,
|
||||||
|
) -> ServerListPing {
|
||||||
ServerListPing::Ignore
|
ServerListPing::Ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,31 +167,16 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
|
||||||
///
|
///
|
||||||
/// This method is the appropriate place to perform asynchronous
|
/// This method is the appropriate place to perform asynchronous
|
||||||
/// operations such as database queries which may take some time to
|
/// operations such as database queries which may take some time to
|
||||||
/// complete. If you need access to the worlds on the server and don't need
|
/// complete.
|
||||||
/// async, see [`Config::join`].
|
|
||||||
///
|
///
|
||||||
/// This method is called from within a tokio runtime.
|
/// This method is called from within a tokio runtime.
|
||||||
///
|
///
|
||||||
/// # Default Implementation
|
/// # Default Implementation
|
||||||
/// The client is allowed to join unconditionally.
|
/// The client is allowed to join unconditionally.
|
||||||
async fn login(&self, server: &Server, ncd: &NewClientData) -> Result<(), Text> {
|
async fn login(&self, shared: &SharedServer, ncd: &NewClientData) -> Result<(), Text> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called after a successful [`Config::login`] to determine what world the
|
|
||||||
/// new client should join. If this method returns with `Err(reason)`, then
|
|
||||||
/// the client is immediately disconnected with the given reason.
|
|
||||||
///
|
|
||||||
/// If the returned [`WorldId`] is invalid, then the client is disconnected.
|
|
||||||
///
|
|
||||||
/// This method is called from within a tokio runtime.
|
|
||||||
fn join(
|
|
||||||
&self,
|
|
||||||
server: &Server,
|
|
||||||
client: &mut Client,
|
|
||||||
worlds: &mut Worlds,
|
|
||||||
) -> Result<WorldId, Text>;
|
|
||||||
|
|
||||||
/// Called after the server is created, but prior to accepting connections
|
/// Called after the server is created, but prior to accepting connections
|
||||||
/// and entering the update loop.
|
/// and entering the update loop.
|
||||||
///
|
///
|
||||||
|
@ -195,7 +184,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
|
||||||
/// no connections to the server will be made until this function returns.
|
/// no connections to the server will be made until this function returns.
|
||||||
///
|
///
|
||||||
/// This method is called from within a tokio runtime.
|
/// This method is called from within a tokio runtime.
|
||||||
fn init(&self, server: &Server, worlds: &mut Worlds) {}
|
fn init(&self, server: &mut Server) {}
|
||||||
|
|
||||||
/// Called once at the beginning of every server update (also known as
|
/// Called once at the beginning of every server update (also known as
|
||||||
/// a "tick").
|
/// a "tick").
|
||||||
|
@ -207,7 +196,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
|
||||||
///
|
///
|
||||||
/// # Default Implementation
|
/// # Default Implementation
|
||||||
/// The default implementation does nothing.
|
/// The default implementation does nothing.
|
||||||
fn update(&self, server: &Server, worlds: &mut Worlds);
|
fn update(&self, server: &mut Server);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The result of the [`server_list_ping`](Handler::server_list_ping) callback.
|
/// The result of the [`server_list_ping`](Handler::server_list_ping) callback.
|
||||||
|
|
|
@ -18,6 +18,7 @@ use crate::protocol::packets::play::s2c::{
|
||||||
use crate::protocol::{ByteAngle, RawBytes, VarInt};
|
use crate::protocol::{ByteAngle, RawBytes, VarInt};
|
||||||
use crate::slotmap::{Key, SlotMap};
|
use crate::slotmap::{Key, SlotMap};
|
||||||
use crate::util::aabb_from_bottom_and_size;
|
use crate::util::aabb_from_bottom_and_size;
|
||||||
|
use crate::WorldId;
|
||||||
|
|
||||||
pub struct Entities {
|
pub struct Entities {
|
||||||
sm: SlotMap<Entity>,
|
sm: SlotMap<Entity>,
|
||||||
|
@ -56,6 +57,7 @@ impl Entities {
|
||||||
let (k, e) = self.sm.insert(Entity {
|
let (k, e) = self.sm.insert(Entity {
|
||||||
flags: EntityFlags(0),
|
flags: EntityFlags(0),
|
||||||
meta: EntityMeta::new(EntityType::Marker),
|
meta: EntityMeta::new(EntityType::Marker),
|
||||||
|
world: None,
|
||||||
new_position: Vec3::default(),
|
new_position: Vec3::default(),
|
||||||
old_position: Vec3::default(),
|
old_position: Vec3::default(),
|
||||||
yaw: 0.0,
|
yaw: 0.0,
|
||||||
|
@ -174,6 +176,7 @@ impl EntityId {
|
||||||
pub struct Entity {
|
pub struct Entity {
|
||||||
flags: EntityFlags,
|
flags: EntityFlags,
|
||||||
meta: EntityMeta,
|
meta: EntityMeta,
|
||||||
|
world: Option<WorldId>,
|
||||||
new_position: Vec3<f64>,
|
new_position: Vec3<f64>,
|
||||||
old_position: Vec3<f64>,
|
old_position: Vec3<f64>,
|
||||||
yaw: f32,
|
yaw: f32,
|
||||||
|
@ -226,6 +229,14 @@ impl Entity {
|
||||||
self.flags.set_type_modified(true);
|
self.flags.set_type_modified(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn world(&self) -> Option<WorldId> {
|
||||||
|
self.world
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_world(&mut self, world: impl Into<Option<WorldId>>) {
|
||||||
|
self.world = world.into();
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the position of this entity in the world it inhabits.
|
/// Returns the position of this entity in the world it inhabits.
|
||||||
pub fn position(&self) -> Vec3<f64> {
|
pub fn position(&self) -> Vec3<f64> {
|
||||||
self.new_position
|
self.new_position
|
||||||
|
|
|
@ -43,7 +43,7 @@ pub use config::Config;
|
||||||
pub use dimension::{Dimension, DimensionId};
|
pub use dimension::{Dimension, DimensionId};
|
||||||
pub use entity::{Entities, Entity, EntityId, EntityType};
|
pub use entity::{Entities, Entity, EntityId, EntityType};
|
||||||
pub use ident::Ident;
|
pub use ident::Ident;
|
||||||
pub use server::{start_server, NewClientData, Server, ShutdownResult};
|
pub use server::{start_server, NewClientData, Server, SharedServer, ShutdownResult};
|
||||||
pub use spatial_index::SpatialIndex;
|
pub use spatial_index::SpatialIndex;
|
||||||
pub use text::{Text, TextFormat};
|
pub use text::{Text, TextFormat};
|
||||||
pub use uuid::Uuid;
|
pub use uuid::Uuid;
|
||||||
|
|
173
src/server.rs
173
src/server.rs
|
@ -40,16 +40,24 @@ use crate::protocol::{BoundedArray, BoundedString, VarInt};
|
||||||
use crate::util::valid_username;
|
use crate::util::valid_username;
|
||||||
use crate::world::Worlds;
|
use crate::world::Worlds;
|
||||||
use crate::{
|
use crate::{
|
||||||
Biome, BiomeId, Client, Dimension, DimensionId, Ticks, PROTOCOL_VERSION, VERSION_NAME,
|
Biome, BiomeId, Client, Clients, Dimension, DimensionId, Entities, Ticks, PROTOCOL_VERSION,
|
||||||
|
VERSION_NAME,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub struct Server {
|
||||||
|
pub shared: SharedServer,
|
||||||
|
pub clients: Clients,
|
||||||
|
pub entities: Entities,
|
||||||
|
pub worlds: Worlds,
|
||||||
|
}
|
||||||
|
|
||||||
/// A handle to a running Minecraft server containing state which is accessible
|
/// A handle to a running Minecraft server containing state which is accessible
|
||||||
/// outside the update loop. Servers are internally refcounted and can be shared
|
/// outside the update loop. Servers are internally refcounted and can be shared
|
||||||
/// between threads.
|
/// between threads.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Server(Arc<ServerInner>);
|
pub struct SharedServer(Arc<SharedServerInner>);
|
||||||
|
|
||||||
struct ServerInner {
|
struct SharedServerInner {
|
||||||
cfg: Box<dyn Config>,
|
cfg: Box<dyn Config>,
|
||||||
address: SocketAddr,
|
address: SocketAddr,
|
||||||
tick_rate: Ticks,
|
tick_rate: Ticks,
|
||||||
|
@ -104,7 +112,7 @@ pub type ShutdownError = Box<dyn Error + Send + Sync + 'static>;
|
||||||
pub(crate) type S2cPacketChannels = (Sender<C2sPlayPacket>, Receiver<S2cPlayPacket>);
|
pub(crate) type S2cPacketChannels = (Sender<C2sPlayPacket>, Receiver<S2cPlayPacket>);
|
||||||
pub(crate) type C2sPacketChannels = (Sender<S2cPlayPacket>, Receiver<C2sPlayPacket>);
|
pub(crate) type C2sPacketChannels = (Sender<S2cPlayPacket>, Receiver<C2sPlayPacket>);
|
||||||
|
|
||||||
impl Server {
|
impl SharedServer {
|
||||||
pub fn config(&self) -> &(impl Config + ?Sized) {
|
pub fn config(&self) -> &(impl Config + ?Sized) {
|
||||||
self.0.cfg.as_ref()
|
self.0.cfg.as_ref()
|
||||||
}
|
}
|
||||||
|
@ -207,20 +215,25 @@ impl Server {
|
||||||
/// The function returns when the server has shut down, a runtime error
|
/// The function returns when the server has shut down, a runtime error
|
||||||
/// occurs, or the configuration is invalid.
|
/// occurs, or the configuration is invalid.
|
||||||
pub fn start_server(config: impl Config) -> ShutdownResult {
|
pub fn start_server(config: impl Config) -> ShutdownResult {
|
||||||
let server = setup_server(config).map_err(ShutdownError::from)?;
|
let shared = setup_server(config).map_err(ShutdownError::from)?;
|
||||||
|
|
||||||
let _guard = server.tokio_handle().enter();
|
let _guard = shared.tokio_handle().enter();
|
||||||
|
|
||||||
let mut worlds = Worlds::new(server.clone());
|
let mut server = Server {
|
||||||
|
shared: shared.clone(),
|
||||||
|
clients: Clients::new(),
|
||||||
|
entities: Entities::new(),
|
||||||
|
worlds: Worlds::new(shared.clone()),
|
||||||
|
};
|
||||||
|
|
||||||
server.config().init(&server, &mut worlds);
|
shared.config().init(&mut server);
|
||||||
|
|
||||||
tokio::spawn(do_accept_loop(server.clone()));
|
tokio::spawn(do_accept_loop(shared));
|
||||||
|
|
||||||
do_update_loop(server, &mut worlds)
|
do_update_loop(&mut server)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_server(cfg: impl Config) -> anyhow::Result<Server> {
|
fn setup_server(cfg: impl Config) -> anyhow::Result<SharedServer> {
|
||||||
let max_connections = cfg.max_connections();
|
let max_connections = cfg.max_connections();
|
||||||
let address = cfg.address();
|
let address = cfg.address();
|
||||||
let tick_rate = cfg.tick_rate();
|
let tick_rate = cfg.tick_rate();
|
||||||
|
@ -320,7 +333,7 @@ fn setup_server(cfg: impl Config) -> anyhow::Result<Server> {
|
||||||
None => tokio_handle.unwrap(),
|
None => tokio_handle.unwrap(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let server = ServerInner {
|
let server = SharedServerInner {
|
||||||
cfg: Box::new(cfg),
|
cfg: Box::new(cfg),
|
||||||
address,
|
address,
|
||||||
tick_rate,
|
tick_rate,
|
||||||
|
@ -343,33 +356,32 @@ fn setup_server(cfg: impl Config) -> anyhow::Result<Server> {
|
||||||
http_client: HttpClient::new(),
|
http_client: HttpClient::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Server(Arc::new(server)))
|
Ok(SharedServer(Arc::new(server)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn do_update_loop(server: Server, worlds: &mut Worlds) -> ShutdownResult {
|
fn do_update_loop(server: &mut Server) -> ShutdownResult {
|
||||||
let mut tick_start = Instant::now();
|
let mut tick_start = Instant::now();
|
||||||
|
|
||||||
|
let shared = server.shared.clone();
|
||||||
loop {
|
loop {
|
||||||
if let Some(res) = server.0.shutdown_result.lock().unwrap().take() {
|
if let Some(res) = shared.0.shutdown_result.lock().unwrap().take() {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
while let Ok(msg) = server.0.new_clients_rx.try_recv() {
|
while let Ok(msg) = shared.0.new_clients_rx.try_recv() {
|
||||||
join_player(&server, worlds, msg);
|
join_player(server, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get serverbound packets first so they are not dealt with a tick late.
|
// Get serverbound packets first so they are not dealt with a tick late.
|
||||||
worlds.par_iter_mut().for_each(|(_, world)| {
|
server.clients.par_iter_mut().for_each(|(_, client)| {
|
||||||
world.clients.par_iter_mut().for_each(|(_, client)| {
|
client.handle_serverbound_packets(&server.entities);
|
||||||
client.handle_serverbound_packets(&world.entities);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
server.config().update(&server, worlds);
|
shared.config().update(server);
|
||||||
|
|
||||||
worlds.par_iter_mut().for_each(|(_, world)| {
|
server.worlds.par_iter_mut().for_each(|(id, world)| {
|
||||||
world.chunks.par_iter_mut().for_each(|(_, chunk)| {
|
world.chunks.par_iter_mut().for_each(|(_, chunk)| {
|
||||||
if chunk.created_tick() == server.current_tick() {
|
if chunk.created_tick() == shared.current_tick() {
|
||||||
// Chunks created this tick can have their changes applied immediately because
|
// Chunks created this tick can have their changes applied immediately because
|
||||||
// they have not been observed by clients yet. Clients will not have to be sent
|
// they have not been observed by clients yet. Clients will not have to be sent
|
||||||
// the block change packet in this case.
|
// the block change packet in this case.
|
||||||
|
@ -377,20 +389,16 @@ fn do_update_loop(server: Server, worlds: &mut Worlds) -> ShutdownResult {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
world.spatial_index.update(&world.entities);
|
world.spatial_index.update(&server.entities, id);
|
||||||
|
|
||||||
world.clients.par_iter_mut().for_each(|(_, client)| {
|
|
||||||
client.update(
|
|
||||||
&server,
|
|
||||||
&world.entities,
|
|
||||||
&world.spatial_index,
|
|
||||||
&world.chunks,
|
|
||||||
&world.meta,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
world.entities.update();
|
server.clients.par_iter_mut().for_each(|(_, client)| {
|
||||||
|
client.update(&shared, &server.entities, &server.worlds);
|
||||||
|
});
|
||||||
|
|
||||||
|
server.entities.update();
|
||||||
|
|
||||||
|
server.worlds.par_iter_mut().for_each(|(_, world)| {
|
||||||
world.chunks.par_iter_mut().for_each(|(_, chunk)| {
|
world.chunks.par_iter_mut().for_each(|(_, chunk)| {
|
||||||
chunk.apply_modifications();
|
chunk.apply_modifications();
|
||||||
});
|
});
|
||||||
|
@ -399,53 +407,43 @@ fn do_update_loop(server: Server, worlds: &mut Worlds) -> ShutdownResult {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Sleep for the remainder of the tick.
|
// Sleep for the remainder of the tick.
|
||||||
let tick_duration = Duration::from_secs_f64((server.0.tick_rate as f64).recip());
|
let tick_duration = Duration::from_secs_f64((shared.0.tick_rate as f64).recip());
|
||||||
thread::sleep(tick_duration.saturating_sub(tick_start.elapsed()));
|
thread::sleep(tick_duration.saturating_sub(tick_start.elapsed()));
|
||||||
|
|
||||||
tick_start = Instant::now();
|
tick_start = Instant::now();
|
||||||
server.0.tick_counter.fetch_add(1, Ordering::SeqCst);
|
shared.0.tick_counter.fetch_add(1, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn join_player(server: &Server, worlds: &mut Worlds, msg: NewClientMessage) {
|
fn join_player(server: &mut Server, msg: NewClientMessage) {
|
||||||
let (clientbound_tx, clientbound_rx) = flume::bounded(server.0.outgoing_packet_capacity);
|
let (clientbound_tx, clientbound_rx) = flume::bounded(server.shared.0.outgoing_packet_capacity);
|
||||||
let (serverbound_tx, serverbound_rx) = flume::bounded(server.0.incoming_packet_capacity);
|
let (serverbound_tx, serverbound_rx) = flume::bounded(server.shared.0.incoming_packet_capacity);
|
||||||
|
|
||||||
let s2c_packet_channels: S2cPacketChannels = (serverbound_tx, clientbound_rx);
|
let s2c_packet_channels: S2cPacketChannels = (serverbound_tx, clientbound_rx);
|
||||||
let c2s_packet_channels: C2sPacketChannels = (clientbound_tx, serverbound_rx);
|
let c2s_packet_channels: C2sPacketChannels = (clientbound_tx, serverbound_rx);
|
||||||
|
|
||||||
let _ = msg.reply.send(s2c_packet_channels);
|
let _ = msg.reply.send(s2c_packet_channels);
|
||||||
|
|
||||||
let mut client = Client::new(c2s_packet_channels, server, msg.ncd);
|
let client = Client::new(c2s_packet_channels, &server.shared, msg.ncd);
|
||||||
|
|
||||||
match server.0.cfg.join(server, &mut client, worlds) {
|
if server.entities.get_with_uuid(client.uuid()).is_none() {
|
||||||
Ok(world_id) => {
|
server.clients.insert(client);
|
||||||
if let Some(world) = worlds.get_mut(world_id) {
|
|
||||||
if world.entities.get_with_uuid(client.uuid()).is_none() {
|
|
||||||
world.clients.create(client);
|
|
||||||
} else {
|
} else {
|
||||||
log::warn!(
|
log::warn!(
|
||||||
"client '{}' cannot join the server because their UUID ({}) conflicts \
|
"client '{}' cannot join the server because their UUID ({}) conflicts with an \
|
||||||
with an existing entity",
|
existing entity",
|
||||||
client.username(),
|
client.username(),
|
||||||
client.uuid()
|
client.uuid()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
log::warn!(
|
|
||||||
"client '{}' cannot join the server because the WorldId returned by \
|
|
||||||
Config::join is invalid.",
|
|
||||||
client.username()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(errmsg) => client.disconnect(errmsg),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Codec = (Encoder<OwnedWriteHalf>, Decoder<OwnedReadHalf>);
|
struct Codec {
|
||||||
|
enc: Encoder<OwnedWriteHalf>,
|
||||||
|
dec: Decoder<OwnedReadHalf>,
|
||||||
|
}
|
||||||
|
|
||||||
async fn do_accept_loop(server: Server) {
|
async fn do_accept_loop(server: SharedServer) {
|
||||||
log::trace!("entering accept loop");
|
log::trace!("entering accept loop");
|
||||||
|
|
||||||
let listener = match TcpListener::bind(server.0.address).await {
|
let listener = match TcpListener::bind(server.0.address).await {
|
||||||
|
@ -485,18 +483,21 @@ async fn do_accept_loop(server: Server) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_connection(
|
async fn handle_connection(
|
||||||
server: Server,
|
server: SharedServer,
|
||||||
stream: TcpStream,
|
stream: TcpStream,
|
||||||
remote_addr: SocketAddr,
|
remote_addr: SocketAddr,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let timeout = Duration::from_secs(10);
|
let timeout = Duration::from_secs(10);
|
||||||
|
|
||||||
let (read, write) = stream.into_split();
|
let (read, write) = stream.into_split();
|
||||||
let mut c: Codec = (Encoder::new(write, timeout), Decoder::new(read, timeout));
|
let mut c = Codec {
|
||||||
|
enc: Encoder::new(write, timeout),
|
||||||
|
dec: Decoder::new(read, timeout),
|
||||||
|
};
|
||||||
|
|
||||||
// TODO: peek stream for 0xFE legacy ping
|
// TODO: peek stream for 0xFE legacy ping
|
||||||
|
|
||||||
match c.1.read_packet::<Handshake>().await?.next_state {
|
match c.dec.read_packet::<Handshake>().await?.next_state {
|
||||||
HandshakeNextState::Status => handle_status(server, &mut c, remote_addr)
|
HandshakeNextState::Status => handle_status(server, &mut c, remote_addr)
|
||||||
.await
|
.await
|
||||||
.context("error during status"),
|
.context("error during status"),
|
||||||
|
@ -513,11 +514,11 @@ async fn handle_connection(
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_status(
|
async fn handle_status(
|
||||||
server: Server,
|
server: SharedServer,
|
||||||
c: &mut Codec,
|
c: &mut Codec,
|
||||||
remote_addr: SocketAddr,
|
remote_addr: SocketAddr,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
c.1.read_packet::<StatusRequest>().await?;
|
c.dec.read_packet::<StatusRequest>().await?;
|
||||||
|
|
||||||
match server.0.cfg.server_list_ping(&server, remote_addr).await {
|
match server.0.cfg.server_list_ping(&server, remote_addr).await {
|
||||||
ServerListPing::Respond {
|
ServerListPing::Respond {
|
||||||
|
@ -547,7 +548,8 @@ async fn handle_status(
|
||||||
.insert("favicon".to_string(), Value::String(buf));
|
.insert("favicon".to_string(), Value::String(buf));
|
||||||
}
|
}
|
||||||
|
|
||||||
c.0.write_packet(&StatusResponse {
|
c.enc
|
||||||
|
.write_packet(&StatusResponse {
|
||||||
json_response: json.to_string(),
|
json_response: json.to_string(),
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -555,30 +557,31 @@ async fn handle_status(
|
||||||
ServerListPing::Ignore => return Ok(()),
|
ServerListPing::Ignore => return Ok(()),
|
||||||
}
|
}
|
||||||
|
|
||||||
let PingRequest { payload } = c.1.read_packet().await?;
|
let PingRequest { payload } = c.dec.read_packet().await?;
|
||||||
|
|
||||||
c.0.write_packet(&PongResponse { payload }).await?;
|
c.enc.write_packet(&PongResponse { payload }).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle the login process and return the new player's data if successful.
|
/// Handle the login process and return the new player's data if successful.
|
||||||
async fn handle_login(
|
async fn handle_login(
|
||||||
server: &Server,
|
server: &SharedServer,
|
||||||
c: &mut Codec,
|
c: &mut Codec,
|
||||||
remote_addr: SocketAddr,
|
remote_addr: SocketAddr,
|
||||||
) -> anyhow::Result<Option<NewClientData>> {
|
) -> anyhow::Result<Option<NewClientData>> {
|
||||||
let LoginStart {
|
let LoginStart {
|
||||||
username: BoundedString(username),
|
username: BoundedString(username),
|
||||||
sig_data: _, // TODO
|
sig_data: _, // TODO
|
||||||
} = c.1.read_packet().await?;
|
} = c.dec.read_packet().await?;
|
||||||
|
|
||||||
ensure!(valid_username(&username), "invalid username '{username}'");
|
ensure!(valid_username(&username), "invalid username '{username}'");
|
||||||
|
|
||||||
let (uuid, textures) = if server.0.online_mode {
|
let (uuid, textures) = if server.0.online_mode {
|
||||||
let my_verify_token: [u8; 16] = rand::random();
|
let my_verify_token: [u8; 16] = rand::random();
|
||||||
|
|
||||||
c.0.write_packet(&EncryptionRequest {
|
c.enc
|
||||||
|
.write_packet(&EncryptionRequest {
|
||||||
server_id: Default::default(), // Always empty
|
server_id: Default::default(), // Always empty
|
||||||
public_key: server.0.public_key_der.to_vec(),
|
public_key: server.0.public_key_der.to_vec(),
|
||||||
verify_token: my_verify_token.to_vec().into(),
|
verify_token: my_verify_token.to_vec().into(),
|
||||||
|
@ -588,7 +591,7 @@ async fn handle_login(
|
||||||
let EncryptionResponse {
|
let EncryptionResponse {
|
||||||
shared_secret: BoundedArray(encrypted_shared_secret),
|
shared_secret: BoundedArray(encrypted_shared_secret),
|
||||||
token_or_sig,
|
token_or_sig,
|
||||||
} = c.1.read_packet().await?;
|
} = c.dec.read_packet().await?;
|
||||||
|
|
||||||
let shared_secret = server
|
let shared_secret = server
|
||||||
.0
|
.0
|
||||||
|
@ -618,8 +621,8 @@ async fn handle_login(
|
||||||
.try_into()
|
.try_into()
|
||||||
.context("shared secret has the wrong length")?;
|
.context("shared secret has the wrong length")?;
|
||||||
|
|
||||||
c.0.enable_encryption(&crypt_key);
|
c.enc.enable_encryption(&crypt_key);
|
||||||
c.1.enable_encryption(&crypt_key);
|
c.dec.enable_encryption(&crypt_key);
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct AuthResponse {
|
struct AuthResponse {
|
||||||
|
@ -667,13 +670,14 @@ async fn handle_login(
|
||||||
};
|
};
|
||||||
|
|
||||||
let compression_threshold = 256;
|
let compression_threshold = 256;
|
||||||
c.0.write_packet(&SetCompression {
|
c.enc
|
||||||
|
.write_packet(&SetCompression {
|
||||||
threshold: VarInt(compression_threshold as i32),
|
threshold: VarInt(compression_threshold as i32),
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
c.0.enable_compression(compression_threshold);
|
c.enc.enable_compression(compression_threshold);
|
||||||
c.1.enable_compression(compression_threshold);
|
c.dec.enable_compression(compression_threshold);
|
||||||
|
|
||||||
let npd = NewClientData {
|
let npd = NewClientData {
|
||||||
uuid,
|
uuid,
|
||||||
|
@ -684,11 +688,14 @@ async fn handle_login(
|
||||||
|
|
||||||
if let Err(reason) = server.0.cfg.login(server, &npd).await {
|
if let Err(reason) = server.0.cfg.login(server, &npd).await {
|
||||||
log::info!("Disconnect at login: \"{reason}\"");
|
log::info!("Disconnect at login: \"{reason}\"");
|
||||||
c.0.write_packet(&login::s2c::Disconnect { reason }).await?;
|
c.enc
|
||||||
|
.write_packet(&login::s2c::Disconnect { reason })
|
||||||
|
.await?;
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
c.0.write_packet(&LoginSuccess {
|
c.enc
|
||||||
|
.write_packet(&LoginSuccess {
|
||||||
uuid: npd.uuid,
|
uuid: npd.uuid,
|
||||||
username: npd.username.clone().into(),
|
username: npd.username.clone().into(),
|
||||||
properties: Vec::new(),
|
properties: Vec::new(),
|
||||||
|
@ -698,7 +705,7 @@ async fn handle_login(
|
||||||
Ok(Some(npd))
|
Ok(Some(npd))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_play(server: &Server, c: Codec, ncd: NewClientData) -> anyhow::Result<()> {
|
async fn handle_play(server: &SharedServer, c: Codec, ncd: NewClientData) -> anyhow::Result<()> {
|
||||||
let (reply_tx, reply_rx) = oneshot::channel();
|
let (reply_tx, reply_rx) = oneshot::channel();
|
||||||
|
|
||||||
server
|
server
|
||||||
|
@ -715,11 +722,11 @@ async fn handle_play(server: &Server, c: Codec, ncd: NewClientData) -> anyhow::R
|
||||||
Err(_) => return Ok(()), // Server closed
|
Err(_) => return Ok(()), // Server closed
|
||||||
};
|
};
|
||||||
|
|
||||||
let (mut encoder, mut decoder) = c;
|
let Codec { mut enc, mut dec } = c;
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
while let Ok(pkt) = packet_rx.recv_async().await {
|
while let Ok(pkt) = packet_rx.recv_async().await {
|
||||||
if let Err(e) = encoder.write_packet(&pkt).await {
|
if let Err(e) = enc.write_packet(&pkt).await {
|
||||||
log::debug!("error while sending play packet: {e:#}");
|
log::debug!("error while sending play packet: {e:#}");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -727,7 +734,7 @@ async fn handle_play(server: &Server, c: Codec, ncd: NewClientData) -> anyhow::R
|
||||||
});
|
});
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let pkt = decoder.read_packet().await?;
|
let pkt = dec.read_packet().await?;
|
||||||
if packet_tx.send_async(pkt).await.is_err() {
|
if packet_tx.send_async(pkt).await.is_err() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use vek::{Aabb, Vec3};
|
||||||
|
|
||||||
use crate::bvh::Bvh;
|
use crate::bvh::Bvh;
|
||||||
pub use crate::bvh::TraverseStep;
|
pub use crate::bvh::TraverseStep;
|
||||||
use crate::{Entities, EntityId};
|
use crate::{Entities, EntityId, WorldId};
|
||||||
|
|
||||||
pub struct SpatialIndex {
|
pub struct SpatialIndex {
|
||||||
bvh: Bvh<EntityId>,
|
bvh: Bvh<EntityId>,
|
||||||
|
@ -96,9 +96,13 @@ impl SpatialIndex {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn update(&mut self, entities: &Entities) {
|
pub(crate) fn update(&mut self, entities: &Entities, id: WorldId) {
|
||||||
self.bvh
|
self.bvh.build(
|
||||||
.build(entities.iter().map(|(id, e)| (id, e.hitbox())))
|
entities
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, e)| e.world() == Some(id))
|
||||||
|
.map(|(id, e)| (id, e.hitbox())),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -66,8 +66,8 @@ pub fn to_yaw_and_pitch(d: Vec3<f64>) -> (f32, f32) {
|
||||||
(yaw, pitch)
|
(yaw, pitch)
|
||||||
}
|
}
|
||||||
|
|
||||||
// /// Takes yaw and pitch angles (in degrees) and returns a normalized direction
|
// /// Takes yaw and pitch angles (in degrees) and returns a normalized
|
||||||
// /// vector.
|
// direction /// vector.
|
||||||
// ///
|
// ///
|
||||||
// /// This function is the inverse of [`to_yaw_and_pitch`].
|
// /// This function is the inverse of [`to_yaw_and_pitch`].
|
||||||
// pub fn from_yaw_and_pitch(yaw: f32, pitch: f32) -> Vec3<f64> {
|
// pub fn from_yaw_and_pitch(yaw: f32, pitch: f32) -> Vec3<f64> {
|
||||||
|
|
10
src/world.rs
10
src/world.rs
|
@ -4,18 +4,18 @@ use rayon::iter::ParallelIterator;
|
||||||
|
|
||||||
use crate::player_list::PlayerList;
|
use crate::player_list::PlayerList;
|
||||||
use crate::slotmap::{Key, SlotMap};
|
use crate::slotmap::{Key, SlotMap};
|
||||||
use crate::{Chunks, Clients, DimensionId, Entities, Server, SpatialIndex};
|
use crate::{Chunks, DimensionId, SharedServer, SpatialIndex};
|
||||||
|
|
||||||
pub struct Worlds {
|
pub struct Worlds {
|
||||||
sm: SlotMap<World>,
|
sm: SlotMap<World>,
|
||||||
server: Server,
|
server: SharedServer,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||||
pub struct WorldId(Key);
|
pub struct WorldId(Key);
|
||||||
|
|
||||||
impl Worlds {
|
impl Worlds {
|
||||||
pub(crate) fn new(server: Server) -> Self {
|
pub(crate) fn new(server: SharedServer) -> Self {
|
||||||
Self {
|
Self {
|
||||||
sm: SlotMap::new(),
|
sm: SlotMap::new(),
|
||||||
server,
|
server,
|
||||||
|
@ -24,8 +24,6 @@ impl Worlds {
|
||||||
|
|
||||||
pub fn create(&mut self, dim: DimensionId) -> (WorldId, &mut World) {
|
pub fn create(&mut self, dim: DimensionId) -> (WorldId, &mut World) {
|
||||||
let (id, world) = self.sm.insert(World {
|
let (id, world) = self.sm.insert(World {
|
||||||
clients: Clients::new(),
|
|
||||||
entities: Entities::new(),
|
|
||||||
spatial_index: SpatialIndex::new(),
|
spatial_index: SpatialIndex::new(),
|
||||||
chunks: Chunks::new(self.server.clone(), dim),
|
chunks: Chunks::new(self.server.clone(), dim),
|
||||||
meta: WorldMeta {
|
meta: WorldMeta {
|
||||||
|
@ -81,8 +79,6 @@ impl Worlds {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct World {
|
pub struct World {
|
||||||
pub clients: Clients,
|
|
||||||
pub entities: Entities,
|
|
||||||
pub spatial_index: SpatialIndex,
|
pub spatial_index: SpatialIndex,
|
||||||
pub chunks: Chunks,
|
pub chunks: Chunks,
|
||||||
pub meta: WorldMeta,
|
pub meta: WorldMeta,
|
||||||
|
|
Loading…
Reference in a new issue