mirror of
https://github.com/italicsjenga/valence.git
synced 2024-12-23 22:41:30 +11:00
Add custom data
This commit is contained in:
parent
0ef05bb0d0
commit
865ab76699
|
@ -2470,11 +2470,11 @@ pub fn build() -> anyhow::Result<()> {
|
|||
#(#entity_structs)*
|
||||
|
||||
/// An enum encoding the type of an entity along with any data specific to that entity type.
|
||||
pub enum EntityData {
|
||||
pub enum EntityState {
|
||||
#(#entity_kind_variants(#entity_kind_variants),)*
|
||||
}
|
||||
|
||||
impl EntityData {
|
||||
impl EntityState {
|
||||
pub(super) fn new(kind: EntityKind) -> Self {
|
||||
match kind {
|
||||
#(EntityKind::#entity_kind_variants => Self::#entity_kind_variants(#entity_kind_variants::new()),)*
|
||||
|
|
|
@ -1,19 +1,17 @@
|
|||
use std::collections::HashMap;
|
||||
use std::mem;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Mutex;
|
||||
|
||||
use log::LevelFilter;
|
||||
use num::Integer;
|
||||
use rayon::iter::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator};
|
||||
use valence::biome::Biome;
|
||||
use valence::block::BlockState;
|
||||
use valence::client::{ClientId, Event, Hand};
|
||||
use valence::client::{Event, Hand};
|
||||
use valence::config::{Config, ServerListPing};
|
||||
use valence::dimension::{Dimension, DimensionId};
|
||||
use valence::entity::data::Pose;
|
||||
use valence::entity::{EntityData, EntityId, EntityKind};
|
||||
use valence::entity::state::Pose;
|
||||
use valence::entity::{EntityId, EntityKind, EntityState};
|
||||
use valence::server::{Server, SharedServer, ShutdownResult};
|
||||
use valence::text::{Color, TextFormat};
|
||||
use valence::{async_trait, ident};
|
||||
|
@ -24,27 +22,32 @@ pub fn main() -> ShutdownResult {
|
|||
.parse_default_env()
|
||||
.init();
|
||||
|
||||
valence::start_server(Game {
|
||||
valence::start_server(
|
||||
Game {
|
||||
player_count: AtomicUsize::new(0),
|
||||
state: Mutex::new(State {
|
||||
player_entities: HashMap::new(),
|
||||
},
|
||||
ServerData {
|
||||
board: vec![false; SIZE_X * SIZE_Z].into_boxed_slice(),
|
||||
board_buf: vec![false; SIZE_X * SIZE_Z].into_boxed_slice(),
|
||||
}),
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
struct Game {
|
||||
player_count: AtomicUsize,
|
||||
state: Mutex<State>,
|
||||
}
|
||||
|
||||
struct State {
|
||||
player_entities: HashMap<ClientId, EntityId>,
|
||||
struct ServerData {
|
||||
board: Box<[bool]>,
|
||||
board_buf: Box<[bool]>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ClientData {
|
||||
/// The client's player entity.
|
||||
player: EntityId,
|
||||
}
|
||||
|
||||
const MAX_PLAYERS: usize = 10;
|
||||
|
||||
const SIZE_X: usize = 100;
|
||||
|
@ -53,6 +56,12 @@ const BOARD_Y: i32 = 50;
|
|||
|
||||
#[async_trait]
|
||||
impl Config for Game {
|
||||
type ChunkData = ();
|
||||
type ClientData = ClientData;
|
||||
type EntityData = ();
|
||||
type ServerData = ServerData;
|
||||
type WorldData = ();
|
||||
|
||||
fn max_connections(&self) -> usize {
|
||||
// We want status pings to be successful even if the server is full.
|
||||
MAX_PLAYERS + 64
|
||||
|
@ -80,7 +89,7 @@ impl Config for Game {
|
|||
|
||||
async fn server_list_ping(
|
||||
&self,
|
||||
_server: &SharedServer,
|
||||
_server: &SharedServer<Self>,
|
||||
_remote_addr: SocketAddr,
|
||||
) -> ServerListPing {
|
||||
ServerListPing::Respond {
|
||||
|
@ -91,18 +100,18 @@ impl Config for Game {
|
|||
}
|
||||
}
|
||||
|
||||
fn init(&self, server: &mut Server) {
|
||||
let world = server.worlds.create(DimensionId::default()).1;
|
||||
fn init(&self, server: &mut Server<Self>) {
|
||||
let world = server.worlds.create(DimensionId::default(), ()).1;
|
||||
world.meta.set_flat(true);
|
||||
|
||||
for chunk_z in -2..Integer::div_ceil(&(SIZE_X as i32), &16) + 2 {
|
||||
for chunk_x in -2..Integer::div_ceil(&(SIZE_Z as i32), &16) + 2 {
|
||||
world.chunks.create((chunk_x as i32, chunk_z as i32));
|
||||
world.chunks.create((chunk_x as i32, chunk_z as i32), ());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&self, server: &mut Server) {
|
||||
fn update(&self, server: &mut Server<Self>) {
|
||||
let (world_id, world) = server.worlds.iter_mut().next().unwrap();
|
||||
|
||||
let spawn_pos = [
|
||||
|
@ -111,13 +120,7 @@ impl Config for Game {
|
|||
SIZE_Z as f64 / 2.0,
|
||||
];
|
||||
|
||||
let State {
|
||||
player_entities,
|
||||
board,
|
||||
board_buf,
|
||||
} = &mut *self.state.lock().unwrap();
|
||||
|
||||
server.clients.retain(|client_id, client| {
|
||||
server.clients.retain(|_, client| {
|
||||
if client.created_tick() == server.shared.current_tick() {
|
||||
if self
|
||||
.player_count
|
||||
|
@ -142,14 +145,13 @@ impl Config for Game {
|
|||
None,
|
||||
);
|
||||
|
||||
player_entities.insert(
|
||||
client_id,
|
||||
server
|
||||
let player_id = server
|
||||
.entities
|
||||
.create_with_uuid(EntityKind::Player, client.uuid())
|
||||
.create_with_uuid(EntityKind::Player, client.uuid(), ())
|
||||
.unwrap()
|
||||
.0,
|
||||
);
|
||||
.0;
|
||||
|
||||
client.data = ClientData { player: player_id };
|
||||
|
||||
client.send_message("Welcome to Conway's game of life in Minecraft!".italic());
|
||||
client.send_message("Hold the left mouse button to bring blocks to life.".italic());
|
||||
|
@ -157,8 +159,7 @@ impl Config for Game {
|
|||
|
||||
if client.is_disconnected() {
|
||||
self.player_count.fetch_sub(1, Ordering::SeqCst);
|
||||
let id = player_entities.remove(&client_id).unwrap();
|
||||
server.entities.delete(id);
|
||||
server.entities.delete(client.data.player);
|
||||
world.meta.player_list_mut().remove(client.uuid());
|
||||
return false;
|
||||
}
|
||||
|
@ -166,11 +167,8 @@ impl Config for Game {
|
|||
true
|
||||
});
|
||||
|
||||
for (client_id, client) in server.clients.iter_mut() {
|
||||
let player = server
|
||||
.entities
|
||||
.get_mut(player_entities[&client_id])
|
||||
.unwrap();
|
||||
for (_, client) in server.clients.iter_mut() {
|
||||
let player = server.entities.get_mut(client.data.player).unwrap();
|
||||
|
||||
while let Some(event) = client.pop_event() {
|
||||
match event {
|
||||
|
@ -179,7 +177,8 @@ impl Config for Game {
|
|||
&& (0..SIZE_Z as i32).contains(&position.z)
|
||||
&& position.y == BOARD_Y
|
||||
{
|
||||
board[position.x as usize + position.z as usize * SIZE_X] = true;
|
||||
server.data.board[position.x as usize + position.z as usize * SIZE_X] =
|
||||
true;
|
||||
}
|
||||
}
|
||||
Event::Movement { .. } => {
|
||||
|
@ -195,29 +194,29 @@ impl Config for Game {
|
|||
player.set_on_ground(client.on_ground());
|
||||
}
|
||||
Event::StartSneaking => {
|
||||
if let EntityData::Player(e) = player.data_mut() {
|
||||
if let EntityState::Player(e) = &mut player.state {
|
||||
e.set_crouching(true);
|
||||
e.set_pose(Pose::Sneaking);
|
||||
}
|
||||
}
|
||||
Event::StopSneaking => {
|
||||
if let EntityData::Player(e) = player.data_mut() {
|
||||
if let EntityState::Player(e) = &mut player.state {
|
||||
e.set_pose(Pose::Standing);
|
||||
e.set_crouching(false);
|
||||
}
|
||||
}
|
||||
Event::StartSprinting => {
|
||||
if let EntityData::Player(e) = player.data_mut() {
|
||||
if let EntityState::Player(e) = &mut player.state {
|
||||
e.set_sprinting(true);
|
||||
}
|
||||
}
|
||||
Event::StopSprinting => {
|
||||
if let EntityData::Player(e) = player.data_mut() {
|
||||
if let EntityState::Player(e) = &mut player.state {
|
||||
e.set_sprinting(false);
|
||||
}
|
||||
}
|
||||
Event::ArmSwing(hand) => {
|
||||
if let EntityData::Player(e) = player.data_mut() {
|
||||
if let EntityState::Player(e) = &mut player.state {
|
||||
match hand {
|
||||
Hand::Main => e.trigger_swing_main_arm(),
|
||||
Hand::Off => e.trigger_swing_offhand(),
|
||||
|
@ -233,7 +232,12 @@ impl Config for Game {
|
|||
return;
|
||||
}
|
||||
|
||||
board_buf.par_iter_mut().enumerate().for_each(|(i, cell)| {
|
||||
server
|
||||
.data
|
||||
.board_buf
|
||||
.par_iter_mut()
|
||||
.enumerate()
|
||||
.for_each(|(i, cell)| {
|
||||
let cx = (i % SIZE_X) as i32;
|
||||
let cz = (i / SIZE_Z) as i32;
|
||||
|
||||
|
@ -243,21 +247,21 @@ impl Config for Game {
|
|||
if !(x == cx && z == cz) {
|
||||
let i = x.rem_euclid(SIZE_X as i32) as usize
|
||||
+ z.rem_euclid(SIZE_Z as i32) as usize * SIZE_X;
|
||||
if board[i] {
|
||||
if server.data.board[i] {
|
||||
live_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if board[cx as usize + cz as usize * SIZE_X] {
|
||||
if server.data.board[cx as usize + cz as usize * SIZE_X] {
|
||||
*cell = (2..=3).contains(&live_count);
|
||||
} else {
|
||||
*cell = live_count == 3;
|
||||
}
|
||||
});
|
||||
|
||||
mem::swap(board, board_buf);
|
||||
mem::swap(&mut server.data.board, &mut server.data.board_buf);
|
||||
|
||||
let min_y = server.shared.dimensions().next().unwrap().1.min_y;
|
||||
|
||||
|
@ -273,7 +277,7 @@ impl Config for Game {
|
|||
let cell_z = chunk_z * 16 + z;
|
||||
|
||||
if cell_x < SIZE_X && cell_z < SIZE_Z {
|
||||
let b = if board[cell_x + cell_z * SIZE_X] {
|
||||
let b = if server.data.board[cell_x + cell_z * SIZE_X] {
|
||||
BlockState::GRASS_BLOCK
|
||||
} else {
|
||||
BlockState::DIRT
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use std::f64::consts::TAU;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Mutex;
|
||||
|
||||
use log::LevelFilter;
|
||||
use valence::async_trait;
|
||||
|
@ -21,23 +20,34 @@ pub fn main() -> ShutdownResult {
|
|||
.parse_default_env()
|
||||
.init();
|
||||
|
||||
valence::start_server(Game {
|
||||
valence::start_server(
|
||||
Game {
|
||||
player_count: AtomicUsize::new(0),
|
||||
cows: Mutex::new(Vec::new()),
|
||||
})
|
||||
},
|
||||
ServerData { cows: Vec::new() },
|
||||
)
|
||||
}
|
||||
|
||||
struct Game {
|
||||
player_count: AtomicUsize,
|
||||
cows: Mutex<Vec<EntityId>>,
|
||||
}
|
||||
|
||||
struct ServerData {
|
||||
cows: Vec<EntityId>,
|
||||
}
|
||||
|
||||
const MAX_PLAYERS: usize = 10;
|
||||
|
||||
const SPAWN_POS: BlockPos = BlockPos::new(0, 100, -35);
|
||||
const SPAWN_POS: BlockPos = BlockPos::new(0, 100, -25);
|
||||
|
||||
#[async_trait]
|
||||
impl Config for Game {
|
||||
type ChunkData = ();
|
||||
type ClientData = ();
|
||||
type EntityData = ();
|
||||
type ServerData = ServerData;
|
||||
type WorldData = ();
|
||||
|
||||
fn max_connections(&self) -> usize {
|
||||
// We want status pings to be successful even if the server is full.
|
||||
MAX_PLAYERS + 64
|
||||
|
@ -50,7 +60,7 @@ impl Config for Game {
|
|||
|
||||
async fn server_list_ping(
|
||||
&self,
|
||||
_server: &SharedServer,
|
||||
_server: &SharedServer<Self>,
|
||||
_remote_addr: SocketAddr,
|
||||
) -> ServerListPing {
|
||||
ServerListPing::Respond {
|
||||
|
@ -61,27 +71,27 @@ impl Config for Game {
|
|||
}
|
||||
}
|
||||
|
||||
fn init(&self, server: &mut Server) {
|
||||
let (world_id, world) = server.worlds.create(DimensionId::default());
|
||||
fn init(&self, server: &mut Server<Self>) {
|
||||
let (world_id, world) = server.worlds.create(DimensionId::default(), ());
|
||||
world.meta.set_flat(true);
|
||||
|
||||
let size = 5;
|
||||
for z in -size..size {
|
||||
for x in -size..size {
|
||||
world.chunks.create([x, z]);
|
||||
world.chunks.create([x, z], ());
|
||||
}
|
||||
}
|
||||
|
||||
world.chunks.set_block_state(SPAWN_POS, BlockState::BEDROCK);
|
||||
|
||||
self.cows.lock().unwrap().extend((0..200).map(|_| {
|
||||
let (id, e) = server.entities.create(EntityKind::Cow);
|
||||
server.data.cows.extend((0..200).map(|_| {
|
||||
let (id, e) = server.entities.create(EntityKind::Cow, ());
|
||||
e.set_world(world_id);
|
||||
id
|
||||
}));
|
||||
}
|
||||
|
||||
fn update(&self, server: &mut Server) {
|
||||
fn update(&self, server: &mut Server<Self>) {
|
||||
let (world_id, world) = server.worlds.iter_mut().next().unwrap();
|
||||
|
||||
server.clients.retain(|_, client| {
|
||||
|
@ -122,10 +132,10 @@ impl Config for Game {
|
|||
if client.is_disconnected() {
|
||||
self.player_count.fetch_sub(1, Ordering::SeqCst);
|
||||
world.meta.player_list_mut().remove(client.uuid());
|
||||
false
|
||||
} else {
|
||||
true
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
});
|
||||
|
||||
let time = server.shared.current_tick() as f64 / server.shared.tick_rate() as f64;
|
||||
|
@ -134,9 +144,6 @@ impl Config for Game {
|
|||
.rotated_y(time * TAU * 0.2)
|
||||
.rotated_z(time * TAU * 0.3);
|
||||
|
||||
let cows = self.cows.lock().unwrap();
|
||||
let cow_count = cows.len();
|
||||
|
||||
let radius = 6.0 + ((time * TAU / 2.5).sin() + 1.0) / 2.0 * 10.0;
|
||||
|
||||
let player_pos = server
|
||||
|
@ -149,11 +156,16 @@ impl Config for Game {
|
|||
// TODO: hardcoded eye pos.
|
||||
let eye_pos = Vec3::new(player_pos.x, player_pos.y + 1.6, player_pos.z);
|
||||
|
||||
#[allow(clippy::significant_drop_in_scrutinee)]
|
||||
for (cow_id, p) in cows.iter().cloned().zip(fibonacci_spiral(cow_count)) {
|
||||
for (cow_id, p) in server
|
||||
.data
|
||||
.cows
|
||||
.iter()
|
||||
.cloned()
|
||||
.zip(fibonacci_spiral(server.data.cows.len()))
|
||||
{
|
||||
let cow = server.entities.get_mut(cow_id).expect("missing cow");
|
||||
let rotated = p * rot;
|
||||
let transformed = rotated * radius + [0.5, SPAWN_POS.y as f64 + 1.0, 0.5];
|
||||
let transformed = rotated * radius + [0.5, SPAWN_POS.y as f64 + 2.0, 0.5];
|
||||
|
||||
let yaw = f32::atan2(rotated.z as f32, rotated.x as f32).to_degrees() - 90.0;
|
||||
let (looking_yaw, looking_pitch) =
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use std::collections::HashSet;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
|
@ -8,8 +7,9 @@ use valence::block::{BlockPos, BlockState};
|
|||
use valence::client::GameMode;
|
||||
use valence::config::{Config, ServerListPing};
|
||||
use valence::dimension::DimensionId;
|
||||
use valence::entity::{EntityData, EntityKind};
|
||||
use valence::entity::{EntityKind, EntityState};
|
||||
use valence::server::{Server, SharedServer, ShutdownResult};
|
||||
use valence::spatial_index::RaycastHit;
|
||||
use valence::text::{Color, TextFormat};
|
||||
use valence::util::from_yaw_and_pitch;
|
||||
use vek::Vec3;
|
||||
|
@ -20,9 +20,12 @@ pub fn main() -> ShutdownResult {
|
|||
.parse_default_env()
|
||||
.init();
|
||||
|
||||
valence::start_server(Game {
|
||||
valence::start_server(
|
||||
Game {
|
||||
player_count: AtomicUsize::new(0),
|
||||
})
|
||||
},
|
||||
(),
|
||||
)
|
||||
}
|
||||
|
||||
struct Game {
|
||||
|
@ -31,12 +34,19 @@ struct Game {
|
|||
|
||||
const MAX_PLAYERS: usize = 10;
|
||||
|
||||
const SPAWN_POS: BlockPos = BlockPos::new(0, 100, -8);
|
||||
const SPAWN_POS: BlockPos = BlockPos::new(0, 100, -5);
|
||||
|
||||
const PLAYER_EYE_HEIGHT: f64 = 1.6;
|
||||
|
||||
#[async_trait]
|
||||
impl Config for Game {
|
||||
type ChunkData = ();
|
||||
type ClientData = ();
|
||||
/// `true` for entities that have been intersected with.
|
||||
type EntityData = bool;
|
||||
type ServerData = ();
|
||||
type WorldData = ();
|
||||
|
||||
fn max_connections(&self) -> usize {
|
||||
// We want status pings to be successful even if the server is full.
|
||||
MAX_PLAYERS + 64
|
||||
|
@ -49,7 +59,7 @@ impl Config for Game {
|
|||
|
||||
async fn server_list_ping(
|
||||
&self,
|
||||
_server: &SharedServer,
|
||||
_server: &SharedServer<Self>,
|
||||
_remote_addr: SocketAddr,
|
||||
) -> ServerListPing {
|
||||
ServerListPing::Respond {
|
||||
|
@ -60,14 +70,14 @@ impl Config for Game {
|
|||
}
|
||||
}
|
||||
|
||||
fn init(&self, server: &mut Server) {
|
||||
let (world_id, world) = server.worlds.create(DimensionId::default());
|
||||
fn init(&self, server: &mut Server<Self>) {
|
||||
let (world_id, world) = server.worlds.create(DimensionId::default(), ());
|
||||
world.meta.set_flat(true);
|
||||
|
||||
let size = 5;
|
||||
for z in -size..size {
|
||||
for x in -size..size {
|
||||
world.chunks.create([x, z]);
|
||||
world.chunks.create([x, z], ());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,24 +87,17 @@ impl Config for Game {
|
|||
for i in 0..SHEEP_COUNT {
|
||||
let offset = (i as f64 - (SHEEP_COUNT - 1) as f64 / 2.0) * 1.25;
|
||||
|
||||
let (_, sheep) = server.entities.create(EntityKind::Sheep);
|
||||
let (_, sheep) = server.entities.create(EntityKind::Sheep, false);
|
||||
sheep.set_world(world_id);
|
||||
sheep.set_position([offset + 0.5, SPAWN_POS.y as f64 + 1.0, 0.0]);
|
||||
sheep.set_yaw(180.0);
|
||||
sheep.set_head_yaw(180.0);
|
||||
|
||||
if let EntityData::Sheep(sheep) = sheep.data_mut() {
|
||||
// Shear the sheep.
|
||||
sheep.set_sheep_state(0b1_0000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&self, server: &mut Server) {
|
||||
fn update(&self, server: &mut Server<Self>) {
|
||||
let (world_id, world) = server.worlds.iter_mut().next().unwrap();
|
||||
|
||||
let mut hit_entities = HashSet::new();
|
||||
|
||||
server.clients.retain(|_, client| {
|
||||
if client.created_tick() == server.shared.current_tick() {
|
||||
if self
|
||||
|
@ -133,7 +136,7 @@ impl Config for Game {
|
|||
"Look at a sheep to change its ".italic()
|
||||
+ "color".italic().color(Color::GREEN)
|
||||
+ ".",
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if client.is_disconnected() {
|
||||
|
@ -146,27 +149,31 @@ impl Config for Game {
|
|||
|
||||
let origin = Vec3::new(client_pos.x, client_pos.y + PLAYER_EYE_HEIGHT, client_pos.z);
|
||||
let direction = from_yaw_and_pitch(client.yaw() as f64, client.pitch() as f64);
|
||||
|
||||
if let Some(hit) = world.spatial_index.raycast(origin, direction, |hit| {
|
||||
let only_sheep = |hit: &RaycastHit| {
|
||||
server
|
||||
.entities
|
||||
.get(hit.entity)
|
||||
.map_or(false, |e| e.kind() == EntityKind::Sheep)
|
||||
}) {
|
||||
hit_entities.insert(hit.entity);
|
||||
};
|
||||
|
||||
if let Some(hit) = world.spatial_index.raycast(origin, direction, only_sheep) {
|
||||
if let Some(e) = server.entities.get_mut(hit.entity) {
|
||||
e.data = true;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
});
|
||||
|
||||
for (id, e) in server.entities.iter_mut() {
|
||||
if let EntityData::Sheep(sheep) = e.data_mut() {
|
||||
if hit_entities.contains(&id) {
|
||||
for (_, e) in server.entities.iter_mut() {
|
||||
if let EntityState::Sheep(sheep) = &mut e.state {
|
||||
if e.data {
|
||||
sheep.set_sheep_state(5);
|
||||
} else {
|
||||
sheep.set_sheep_state(0);
|
||||
}
|
||||
}
|
||||
e.data = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,14 +24,17 @@ pub fn main() -> ShutdownResult {
|
|||
|
||||
let seed = rand::random();
|
||||
|
||||
valence::start_server(Game {
|
||||
valence::start_server(
|
||||
Game {
|
||||
player_count: AtomicUsize::new(0),
|
||||
density_noise: SuperSimplex::new().set_seed(seed),
|
||||
hilly_noise: SuperSimplex::new().set_seed(seed.wrapping_add(1)),
|
||||
stone_noise: SuperSimplex::new().set_seed(seed.wrapping_add(2)),
|
||||
gravel_noise: SuperSimplex::new().set_seed(seed.wrapping_add(3)),
|
||||
grass_noise: SuperSimplex::new().set_seed(seed.wrapping_add(4)),
|
||||
})
|
||||
},
|
||||
(),
|
||||
)
|
||||
}
|
||||
|
||||
struct Game {
|
||||
|
@ -47,6 +50,12 @@ const MAX_PLAYERS: usize = 10;
|
|||
|
||||
#[async_trait]
|
||||
impl Config for Game {
|
||||
type ChunkData = ();
|
||||
type ClientData = ();
|
||||
type EntityData = ();
|
||||
type ServerData = ();
|
||||
type WorldData = ();
|
||||
|
||||
fn max_connections(&self) -> usize {
|
||||
// We want status pings to be successful even if the server is full.
|
||||
MAX_PLAYERS + 64
|
||||
|
@ -59,7 +68,7 @@ impl Config for Game {
|
|||
|
||||
async fn server_list_ping(
|
||||
&self,
|
||||
_server: &SharedServer,
|
||||
_server: &SharedServer<Self>,
|
||||
_remote_addr: SocketAddr,
|
||||
) -> ServerListPing {
|
||||
ServerListPing::Respond {
|
||||
|
@ -70,12 +79,12 @@ impl Config for Game {
|
|||
}
|
||||
}
|
||||
|
||||
fn init(&self, server: &mut Server) {
|
||||
let (_, world) = server.worlds.create(DimensionId::default());
|
||||
fn init(&self, server: &mut Server<Self>) {
|
||||
let (_, world) = server.worlds.create(DimensionId::default(), ());
|
||||
world.meta.set_flat(true);
|
||||
}
|
||||
|
||||
fn update(&self, server: &mut Server) {
|
||||
fn update(&self, server: &mut Server<Self>) {
|
||||
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));
|
||||
|
@ -126,7 +135,7 @@ impl Config for Game {
|
|||
for pos in chunks_in_view_distance(ChunkPos::at(p.x, p.z), dist) {
|
||||
chunks_to_unload.remove(&pos);
|
||||
if world.chunks.get(pos).is_none() {
|
||||
world.chunks.create(pos);
|
||||
world.chunks.create(pos, ());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
68
src/chunk.rs
68
src/chunk.rs
|
@ -15,6 +15,7 @@ use crate::biome::BiomeId;
|
|||
use crate::block::BlockState;
|
||||
use crate::block_pos::BlockPos;
|
||||
pub use crate::chunk_pos::ChunkPos;
|
||||
use crate::config::Config;
|
||||
use crate::dimension::DimensionId;
|
||||
use crate::protocol_inner::packets::play::s2c::{
|
||||
BlockUpdate, LevelChunkHeightmaps, LevelChunkWithLight, S2cPlayPacket, SectionBlocksUpdate,
|
||||
|
@ -24,14 +25,14 @@ use crate::server::SharedServer;
|
|||
use crate::Ticks;
|
||||
|
||||
/// A container for all [`Chunks`]s in a [`World`](crate::world::World).
|
||||
pub struct Chunks {
|
||||
chunks: HashMap<ChunkPos, Chunk>,
|
||||
server: SharedServer,
|
||||
pub struct Chunks<C: Config> {
|
||||
chunks: HashMap<ChunkPos, Chunk<C>>,
|
||||
server: SharedServer<C>,
|
||||
dimension: DimensionId,
|
||||
}
|
||||
|
||||
impl Chunks {
|
||||
pub(crate) fn new(server: SharedServer, dimension: DimensionId) -> Self {
|
||||
impl<C: Config> Chunks<C> {
|
||||
pub(crate) fn new(server: SharedServer<C>, dimension: DimensionId) -> Self {
|
||||
Self {
|
||||
chunks: HashMap::new(),
|
||||
server,
|
||||
|
@ -49,9 +50,9 @@ impl Chunks {
|
|||
/// adjacent to it must also be loaded. It is also important that clients
|
||||
/// are not spawned within unloaded chunks via
|
||||
/// [`spawn`](crate::client::Client::spawn).
|
||||
pub fn create(&mut self, pos: impl Into<ChunkPos>) -> &mut Chunk {
|
||||
pub fn create(&mut self, pos: impl Into<ChunkPos>, data: C::ChunkData) -> &mut Chunk<C> {
|
||||
let section_count = (self.server.dimension(self.dimension).height / 16) as u32;
|
||||
let chunk = Chunk::new(section_count, self.server.current_tick());
|
||||
let chunk = Chunk::new(section_count, self.server.current_tick(), data);
|
||||
|
||||
match self.chunks.entry(pos.into()) {
|
||||
Entry::Occupied(mut oe) => {
|
||||
|
@ -78,14 +79,14 @@ impl Chunks {
|
|||
/// Gets a shared reference to the chunk at the provided position.
|
||||
///
|
||||
/// If there is no chunk at the position, then `None` is returned.
|
||||
pub fn get(&self, pos: impl Into<ChunkPos>) -> Option<&Chunk> {
|
||||
pub fn get(&self, pos: impl Into<ChunkPos>) -> Option<&Chunk<C>> {
|
||||
self.chunks.get(&pos.into())
|
||||
}
|
||||
|
||||
/// Gets an exclusive reference to the chunk at the provided position.
|
||||
///
|
||||
/// If there is no chunk at the position, then `None` is returned.
|
||||
pub fn get_mut(&mut self, pos: impl Into<ChunkPos>) -> Option<&mut Chunk> {
|
||||
pub fn get_mut(&mut self, pos: impl Into<ChunkPos>) -> Option<&mut Chunk<C>> {
|
||||
self.chunks.get_mut(&pos.into())
|
||||
}
|
||||
|
||||
|
@ -96,25 +97,25 @@ impl Chunks {
|
|||
|
||||
/// Returns an immutable iterator over all chunks in the world in an
|
||||
/// unspecified order.
|
||||
pub fn iter(&self) -> impl FusedIterator<Item = (ChunkPos, &Chunk)> + Clone + '_ {
|
||||
pub fn iter(&self) -> impl FusedIterator<Item = (ChunkPos, &Chunk<C>)> + Clone + '_ {
|
||||
self.chunks.iter().map(|(&pos, chunk)| (pos, chunk))
|
||||
}
|
||||
|
||||
/// Returns a mutable iterator over all chunks in the world in an
|
||||
/// unspecified order.
|
||||
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (ChunkPos, &mut Chunk)> + '_ {
|
||||
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (ChunkPos, &mut Chunk<C>)> + '_ {
|
||||
self.chunks.iter_mut().map(|(&pos, chunk)| (pos, chunk))
|
||||
}
|
||||
|
||||
/// Returns a parallel immutable iterator over all chunks in the world in an
|
||||
/// unspecified order.
|
||||
pub fn par_iter(&self) -> impl ParallelIterator<Item = (ChunkPos, &Chunk)> + Clone + '_ {
|
||||
pub fn par_iter(&self) -> impl ParallelIterator<Item = (ChunkPos, &Chunk<C>)> + Clone + '_ {
|
||||
self.chunks.par_iter().map(|(&pos, chunk)| (pos, chunk))
|
||||
}
|
||||
|
||||
/// Returns a parallel mutable iterator over all chunks in the world in an
|
||||
/// unspecified order.
|
||||
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (ChunkPos, &mut Chunk)> + '_ {
|
||||
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (ChunkPos, &mut Chunk<C>)> + '_ {
|
||||
self.chunks.par_iter_mut().map(|(&pos, chunk)| (pos, chunk))
|
||||
}
|
||||
|
||||
|
@ -183,7 +184,9 @@ impl Chunks {
|
|||
///
|
||||
/// In addition to blocks, chunks also contain [biomes](crate::biome::Biome).
|
||||
/// Every 4x4x4 segment of blocks in a chunk corresponds to a biome.
|
||||
pub struct Chunk {
|
||||
pub struct Chunk<C: Config> {
|
||||
/// Custom data.
|
||||
pub data: C::ChunkData,
|
||||
sections: Box<[ChunkSection]>,
|
||||
// TODO block_entities: HashMap<u32, BlockEntity>,
|
||||
/// The MOTION_BLOCKING heightmap
|
||||
|
@ -191,8 +194,8 @@ pub struct Chunk {
|
|||
created_tick: Ticks,
|
||||
}
|
||||
|
||||
impl Chunk {
|
||||
pub(crate) fn new(section_count: u32, current_tick: Ticks) -> Self {
|
||||
impl<C: Config> Chunk<C> {
|
||||
pub(crate) fn new(section_count: u32, current_tick: Ticks, data: C::ChunkData) -> Self {
|
||||
let sect = ChunkSection {
|
||||
blocks: [BlockState::AIR.to_raw(); 4096],
|
||||
modified_count: 1, // Must be >0 so the chunk is initialized.
|
||||
|
@ -201,6 +204,7 @@ impl Chunk {
|
|||
};
|
||||
|
||||
let mut chunk = Self {
|
||||
data,
|
||||
sections: vec![sect; section_count as usize].into(),
|
||||
heightmap: Vec::new(),
|
||||
created_tick: current_tick,
|
||||
|
@ -547,35 +551,3 @@ fn encode_paletted_container(
|
|||
fn log2_ceil(n: usize) -> usize {
|
||||
n.next_power_of_two().trailing_zeros() as usize
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn set_get() {
|
||||
let mut chunk = Chunk::new(16, 0);
|
||||
|
||||
chunk.set_block_state(1, 2, 3, BlockState::CAKE);
|
||||
assert_eq!(chunk.get_block_state(1, 2, 3), BlockState::CAKE);
|
||||
|
||||
chunk.set_biome(1, 2, 3, BiomeId(7));
|
||||
assert_eq!(chunk.get_biome(1, 2, 3), BiomeId(7));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn block_state_oob() {
|
||||
let mut chunk = Chunk::new(16, 0);
|
||||
|
||||
chunk.set_block_state(16, 0, 0, BlockState::CAKE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn biome_oob() {
|
||||
let mut chunk = Chunk::new(16, 0);
|
||||
|
||||
chunk.set_biome(4, 0, 0, BiomeId(0));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ use vek::Vec3;
|
|||
use crate::biome::Biome;
|
||||
use crate::block_pos::BlockPos;
|
||||
use crate::chunk_pos::ChunkPos;
|
||||
use crate::config::Config;
|
||||
use crate::dimension::DimensionId;
|
||||
use crate::entity::types::Player;
|
||||
use crate::entity::{velocity_to_packet_units, Entities, Entity, EntityId, EntityKind};
|
||||
|
@ -47,16 +48,16 @@ use crate::{ident, Ticks, LIBRARY_NAMESPACE};
|
|||
/// New clients are automatically inserted into this container but
|
||||
/// are not automatically deleted. It is your responsibility to delete them once
|
||||
/// they disconnect. This can be checked with [`Client::is_disconnected`].
|
||||
pub struct Clients {
|
||||
sm: SlotMap<Client>,
|
||||
pub struct Clients<C: Config> {
|
||||
sm: SlotMap<Client<C>>,
|
||||
}
|
||||
|
||||
impl Clients {
|
||||
impl<C: Config> Clients<C> {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self { sm: SlotMap::new() }
|
||||
}
|
||||
|
||||
pub(crate) fn insert(&mut self, client: Client) -> (ClientId, &mut Client) {
|
||||
pub(crate) fn insert(&mut self, client: Client<C>) -> (ClientId, &mut Client<C>) {
|
||||
let (id, client) = self.sm.insert(client);
|
||||
(ClientId(id), client)
|
||||
}
|
||||
|
@ -73,7 +74,7 @@ impl Clients {
|
|||
/// which `f` returns `true`.
|
||||
///
|
||||
/// All clients are visited in an unspecified order.
|
||||
pub fn retain(&mut self, mut f: impl FnMut(ClientId, &mut Client) -> bool) {
|
||||
pub fn retain(&mut self, mut f: impl FnMut(ClientId, &mut Client<C>) -> bool) {
|
||||
self.sm.retain(|k, v| f(ClientId(k), v))
|
||||
}
|
||||
|
||||
|
@ -85,37 +86,39 @@ impl Clients {
|
|||
|
||||
/// Returns a shared reference to the client with the given ID. If
|
||||
/// the ID is invalid, then `None` is returned.
|
||||
pub fn get(&self, client: ClientId) -> Option<&Client> {
|
||||
pub fn get(&self, client: ClientId) -> Option<&Client<C>> {
|
||||
self.sm.get(client.0)
|
||||
}
|
||||
|
||||
/// Returns an exclusive reference to the client with the given ID. If the
|
||||
/// ID is invalid, then `None` is returned.
|
||||
pub fn get_mut(&mut self, client: ClientId) -> Option<&mut Client> {
|
||||
pub fn get_mut(&mut self, client: ClientId) -> Option<&mut Client<C>> {
|
||||
self.sm.get_mut(client.0)
|
||||
}
|
||||
|
||||
/// Returns an immutable iterator over all clients on the server in an
|
||||
/// unspecified order.
|
||||
pub fn iter(&self) -> impl FusedIterator<Item = (ClientId, &Client)> + Clone + '_ {
|
||||
pub fn iter(&self) -> impl FusedIterator<Item = (ClientId, &Client<C>)> + Clone + '_ {
|
||||
self.sm.iter().map(|(k, v)| (ClientId(k), v))
|
||||
}
|
||||
|
||||
/// Returns a mutable iterator over all clients on the server in an
|
||||
/// unspecified order.
|
||||
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (ClientId, &mut Client)> + '_ {
|
||||
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (ClientId, &mut Client<C>)> + '_ {
|
||||
self.sm.iter_mut().map(|(k, v)| (ClientId(k), v))
|
||||
}
|
||||
|
||||
/// Returns a parallel immutable iterator over all clients on the server in
|
||||
/// an unspecified order.
|
||||
pub fn par_iter(&self) -> impl ParallelIterator<Item = (ClientId, &Client)> + Clone + '_ {
|
||||
pub fn par_iter(&self) -> impl ParallelIterator<Item = (ClientId, &Client<C>)> + Clone + '_ {
|
||||
self.sm.par_iter().map(|(k, v)| (ClientId(k), v))
|
||||
}
|
||||
|
||||
/// Returns a parallel mutable iterator over all clients on the server in an
|
||||
/// unspecified order.
|
||||
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (ClientId, &mut Client)> + '_ {
|
||||
pub fn par_iter_mut(
|
||||
&mut self,
|
||||
) -> impl ParallelIterator<Item = (ClientId, &mut Client<C>)> + '_ {
|
||||
self.sm.par_iter_mut().map(|(k, v)| (ClientId(k), v))
|
||||
}
|
||||
}
|
||||
|
@ -158,7 +161,9 @@ impl ClientId {
|
|||
///
|
||||
/// In Valence however, clients and players have been decoupled. This separation
|
||||
/// was done primarily to enable multithreaded client updates.
|
||||
pub struct Client {
|
||||
pub struct Client<C: Config> {
|
||||
/// Custom data.
|
||||
pub data: C::ClientData,
|
||||
/// Setting this to `None` disconnects the client.
|
||||
send: SendOpt,
|
||||
recv: Receiver<C2sPlayPacket>,
|
||||
|
@ -227,15 +232,17 @@ pub(crate) struct ClientFlags {
|
|||
_pad: u8,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
impl<C: Config> Client<C> {
|
||||
pub(crate) fn new(
|
||||
packet_channels: C2sPacketChannels,
|
||||
server: &SharedServer,
|
||||
server: &SharedServer<C>,
|
||||
ncd: NewClientData,
|
||||
data: C::ClientData,
|
||||
) -> Self {
|
||||
let (send, recv) = packet_channels;
|
||||
|
||||
Self {
|
||||
data,
|
||||
send: Some(send),
|
||||
recv,
|
||||
created_tick: server.current_tick(),
|
||||
|
@ -584,16 +591,16 @@ impl Client {
|
|||
send_packet(&mut self.send, packet);
|
||||
}
|
||||
|
||||
pub(crate) fn handle_serverbound_packets(&mut self, entities: &Entities) {
|
||||
pub(crate) fn handle_serverbound_packets(&mut self, entities: &Entities<C>) {
|
||||
self.events.clear();
|
||||
for _ in 0..self.recv.len() {
|
||||
self.handle_serverbound_packet(entities, self.recv.try_recv().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_serverbound_packet(&mut self, entities: &Entities, pkt: C2sPlayPacket) {
|
||||
fn handle_movement_packet(
|
||||
client: &mut Client,
|
||||
fn handle_serverbound_packet(&mut self, entities: &Entities<C>, pkt: C2sPlayPacket) {
|
||||
fn handle_movement_packet<C: Config>(
|
||||
client: &mut Client<C>,
|
||||
_vehicle: bool,
|
||||
new_position: Vec3<f64>,
|
||||
new_yaw: f32,
|
||||
|
@ -835,7 +842,12 @@ impl Client {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn update(&mut self, shared: &SharedServer, entities: &Entities, worlds: &Worlds) {
|
||||
pub(crate) fn update(
|
||||
&mut self,
|
||||
shared: &SharedServer<C>,
|
||||
entities: &Entities<C>,
|
||||
worlds: &Worlds<C>,
|
||||
) {
|
||||
// Mark the client as disconnected when appropriate.
|
||||
if self.recv.is_disconnected() || self.send.as_ref().map_or(true, |s| s.is_disconnected()) {
|
||||
self.send = None;
|
||||
|
@ -1298,8 +1310,8 @@ fn send_packet(send_opt: &mut SendOpt, pkt: impl Into<S2cPlayPacket>) {
|
|||
}
|
||||
}
|
||||
|
||||
fn send_entity_events(send_opt: &mut SendOpt, id: EntityId, entity: &Entity) {
|
||||
for &code in entity.data().event_codes() {
|
||||
fn send_entity_events<C: Config>(send_opt: &mut SendOpt, id: EntityId, entity: &Entity<C>) {
|
||||
for &code in entity.state.event_codes() {
|
||||
if code <= ENTITY_EVENT_MAX_BOUND as u8 {
|
||||
send_packet(
|
||||
send_opt,
|
||||
|
@ -1320,7 +1332,7 @@ fn send_entity_events(send_opt: &mut SendOpt, id: EntityId, entity: &Entity) {
|
|||
}
|
||||
}
|
||||
|
||||
fn make_registry_codec(shared: &SharedServer) -> RegistryCodec {
|
||||
fn make_registry_codec<C: Config>(shared: &SharedServer<C>) -> RegistryCodec {
|
||||
let mut dims = Vec::new();
|
||||
for (id, dim) in shared.dimensions() {
|
||||
let id = id.0 as i32;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
//! Configuration for the server.
|
||||
|
||||
use std::any::Any;
|
||||
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
|
||||
use std::panic::{RefUnwindSafe, UnwindSafe};
|
||||
|
||||
|
@ -13,14 +12,7 @@ use crate::server::{NewClientData, Server, SharedServer};
|
|||
use crate::text::Text;
|
||||
use crate::Ticks;
|
||||
|
||||
/// A trait containing callbacks which are invoked by the running Minecraft
|
||||
/// server.
|
||||
///
|
||||
/// The config is used from multiple threads and must therefore implement
|
||||
/// [`Send`] and [`Sync`]. From within a single thread, methods are never
|
||||
/// invoked recursively by the library. In other words, a mutex can be aquired
|
||||
/// at the beginning of a method and released at the end without risk of
|
||||
/// deadlocking.
|
||||
/// A trait for the configuration of a server.
|
||||
///
|
||||
/// This trait uses the [async_trait] attribute macro. It is exported at the
|
||||
/// root of this crate.
|
||||
|
@ -28,10 +20,21 @@ use crate::Ticks;
|
|||
/// [async_trait]: https://docs.rs/async-trait/latest/async_trait/
|
||||
#[async_trait]
|
||||
#[allow(unused_variables)]
|
||||
pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
|
||||
/// Called once at startup to get the maximum number of connections allowed
|
||||
/// to the server. Note that this includes all connections, not just those
|
||||
/// past the login stage.
|
||||
pub trait Config: 'static + Sized + Send + Sync + UnwindSafe + RefUnwindSafe {
|
||||
/// Custom data to store with the [`Server`].
|
||||
type ServerData: Send + Sync;
|
||||
/// Custom data to store with every [`Client`](crate::client::Client).
|
||||
type ClientData: Default + Send + Sync;
|
||||
/// Custom data to store with every [`Entity`](crate::entity::Entity).
|
||||
type EntityData: Send + Sync;
|
||||
/// Custom data to store with every [`World`](crate::world::World).
|
||||
type WorldData: Send + Sync;
|
||||
/// Custom data to store with every [`Chunk`](crate::chunk::Chunk).
|
||||
type ChunkData: Send + Sync;
|
||||
|
||||
/// Called once at startup to get the maximum number of simultaneous
|
||||
/// connections allowed to the server. This includes all
|
||||
/// connections, not just those past the login stage.
|
||||
///
|
||||
/// You will want this value to be somewhere above the maximum number of
|
||||
/// players, since status pings should still succeed even when the server is
|
||||
|
@ -169,7 +172,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
|
|||
/// The query is ignored.
|
||||
async fn server_list_ping(
|
||||
&self,
|
||||
shared: &SharedServer,
|
||||
shared: &SharedServer<Self>,
|
||||
remote_addr: SocketAddr,
|
||||
) -> ServerListPing {
|
||||
ServerListPing::Ignore
|
||||
|
@ -192,7 +195,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
|
|||
/// The client is allowed to join unconditionally.
|
||||
///
|
||||
/// [`Clients`]: crate::client::Clients
|
||||
async fn login(&self, shared: &SharedServer, ncd: &NewClientData) -> Result<(), Text> {
|
||||
async fn login(&self, shared: &SharedServer<Self>, ncd: &NewClientData) -> Result<(), Text> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -203,7 +206,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
|
|||
/// no connections to the server will be made until this function returns.
|
||||
///
|
||||
/// This method is called from within a tokio runtime.
|
||||
fn init(&self, server: &mut Server) {}
|
||||
fn init(&self, server: &mut Server<Self>) {}
|
||||
|
||||
/// Called once at the beginning of every server update (also known as
|
||||
/// "tick"). This is likely where the majority of your code will be.
|
||||
|
@ -215,7 +218,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
|
|||
/// # Default Implementation
|
||||
///
|
||||
/// The default implementation does nothing.
|
||||
fn update(&self, server: &mut Server);
|
||||
fn update(&self, server: &mut Server<Self>);
|
||||
}
|
||||
|
||||
/// The result of the [`server_list_ping`](Config::server_list_ping) callback.
|
||||
|
|
311
src/entity.rs
311
src/entity.rs
|
@ -1,6 +1,6 @@
|
|||
//! Dynamic actors in a world.
|
||||
|
||||
pub mod data;
|
||||
pub mod state;
|
||||
pub mod types;
|
||||
|
||||
use std::collections::hash_map::Entry;
|
||||
|
@ -10,10 +10,11 @@ use std::num::NonZeroU32;
|
|||
|
||||
use bitfield_struct::bitfield;
|
||||
use rayon::iter::ParallelIterator;
|
||||
pub use types::{EntityData, EntityKind};
|
||||
pub use types::{EntityKind, EntityState};
|
||||
use uuid::Uuid;
|
||||
use vek::{Aabb, Vec3};
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::protocol_inner::packets::play::s2c::{
|
||||
AddEntity, AddExperienceOrb, AddPlayer, S2cPlayPacket, SetEntityMetadata,
|
||||
};
|
||||
|
@ -32,13 +33,13 @@ use crate::world::WorldId;
|
|||
///
|
||||
/// [`Player`]: crate::entity::types::Player
|
||||
/// [`PlayerList`]: crate::player_list::PlayerList
|
||||
pub struct Entities {
|
||||
sm: SlotMap<Entity>,
|
||||
pub struct Entities<C: Config> {
|
||||
sm: SlotMap<Entity<C>>,
|
||||
uuid_to_entity: HashMap<Uuid, EntityId>,
|
||||
network_id_to_entity: HashMap<NonZeroU32, u32>,
|
||||
}
|
||||
|
||||
impl Entities {
|
||||
impl<C: Config> Entities<C> {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
sm: SlotMap::new(),
|
||||
|
@ -49,8 +50,8 @@ impl Entities {
|
|||
|
||||
/// Spawns a new entity with a random UUID. A reference to the entity along
|
||||
/// with its ID is returned.
|
||||
pub fn create(&mut self, kind: EntityKind) -> (EntityId, &mut Entity) {
|
||||
self.create_with_uuid(kind, Uuid::from_bytes(rand::random()))
|
||||
pub fn create(&mut self, kind: EntityKind, data: C::EntityData) -> (EntityId, &mut Entity<C>) {
|
||||
self.create_with_uuid(kind, Uuid::from_bytes(rand::random()), data)
|
||||
.expect("UUID collision")
|
||||
}
|
||||
|
||||
|
@ -63,13 +64,15 @@ impl Entities {
|
|||
&mut self,
|
||||
kind: EntityKind,
|
||||
uuid: Uuid,
|
||||
) -> Option<(EntityId, &mut Entity)> {
|
||||
data: C::EntityData,
|
||||
) -> Option<(EntityId, &mut Entity<C>)> {
|
||||
match self.uuid_to_entity.entry(uuid) {
|
||||
Entry::Occupied(_) => None,
|
||||
Entry::Vacant(ve) => {
|
||||
let (k, e) = self.sm.insert(Entity {
|
||||
data,
|
||||
state: EntityState::new(kind),
|
||||
flags: EntityFlags(0),
|
||||
data: EntityData::new(kind),
|
||||
world: WorldId::NULL,
|
||||
new_position: Vec3::default(),
|
||||
old_position: Vec3::default(),
|
||||
|
@ -113,7 +116,7 @@ impl Entities {
|
|||
/// Removes all entities from the server for which `f` returns `true`.
|
||||
///
|
||||
/// All entities are visited in an unspecified order.
|
||||
pub fn retain(&mut self, mut f: impl FnMut(EntityId, &mut Entity) -> bool) {
|
||||
pub fn retain(&mut self, mut f: impl FnMut(EntityId, &mut Entity<C>) -> bool) {
|
||||
self.sm.retain(|k, v| {
|
||||
if f(EntityId(k), v) {
|
||||
true
|
||||
|
@ -147,14 +150,14 @@ impl Entities {
|
|||
/// Gets a shared reference to the entity with the given [`EntityId`].
|
||||
///
|
||||
/// If the ID is invalid, `None` is returned.
|
||||
pub fn get(&self, entity: EntityId) -> Option<&Entity> {
|
||||
pub fn get(&self, entity: EntityId) -> Option<&Entity<C>> {
|
||||
self.sm.get(entity.0)
|
||||
}
|
||||
|
||||
/// Gets an exclusive reference to the entity with the given [`EntityId`].
|
||||
///
|
||||
/// If the ID is invalid, `None` is returned.
|
||||
pub fn get_mut(&mut self, entity: EntityId) -> Option<&mut Entity> {
|
||||
pub fn get_mut(&mut self, entity: EntityId) -> Option<&mut Entity<C>> {
|
||||
self.sm.get_mut(entity.0)
|
||||
}
|
||||
|
||||
|
@ -166,32 +169,34 @@ impl Entities {
|
|||
|
||||
/// Returns an immutable iterator over all entities on the server in an
|
||||
/// unspecified order.
|
||||
pub fn iter(&self) -> impl FusedIterator<Item = (EntityId, &Entity)> + Clone + '_ {
|
||||
pub fn iter(&self) -> impl FusedIterator<Item = (EntityId, &Entity<C>)> + Clone + '_ {
|
||||
self.sm.iter().map(|(k, v)| (EntityId(k), v))
|
||||
}
|
||||
|
||||
/// Returns a mutable iterator over all entities on the server in an
|
||||
/// unspecified order.
|
||||
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (EntityId, &mut Entity)> + '_ {
|
||||
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (EntityId, &mut Entity<C>)> + '_ {
|
||||
self.sm.iter_mut().map(|(k, v)| (EntityId(k), v))
|
||||
}
|
||||
|
||||
/// Returns a parallel immutable iterator over all entities on the server in
|
||||
/// an unspecified order.
|
||||
pub fn par_iter(&self) -> impl ParallelIterator<Item = (EntityId, &Entity)> + Clone + '_ {
|
||||
pub fn par_iter(&self) -> impl ParallelIterator<Item = (EntityId, &Entity<C>)> + Clone + '_ {
|
||||
self.sm.par_iter().map(|(k, v)| (EntityId(k), v))
|
||||
}
|
||||
|
||||
/// Returns a parallel mutable iterator over all clients on the server in an
|
||||
/// unspecified order.
|
||||
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (EntityId, &mut Entity)> + '_ {
|
||||
pub fn par_iter_mut(
|
||||
&mut self,
|
||||
) -> impl ParallelIterator<Item = (EntityId, &mut Entity<C>)> + '_ {
|
||||
self.sm.par_iter_mut().map(|(k, v)| (EntityId(k), v))
|
||||
}
|
||||
|
||||
pub(crate) fn update(&mut self) {
|
||||
for (_, e) in self.iter_mut() {
|
||||
e.old_position = e.new_position;
|
||||
e.data.clear_modifications();
|
||||
e.state.clear_modifications();
|
||||
|
||||
e.flags.set_yaw_or_pitch_modified(false);
|
||||
e.flags.set_head_yaw_modified(false);
|
||||
|
@ -230,9 +235,12 @@ impl EntityId {
|
|||
/// this struct. This includes position, rotation, velocity, UUID, and hitbox.
|
||||
/// To access data that is not common to every kind of entity, see
|
||||
/// [`Self::data`].
|
||||
pub struct Entity {
|
||||
pub struct Entity<C: Config> {
|
||||
/// Custom data.
|
||||
pub data: C::EntityData,
|
||||
/// Kind-specific state for this entity.
|
||||
pub state: EntityState,
|
||||
flags: EntityFlags,
|
||||
data: EntityData,
|
||||
world: WorldId,
|
||||
new_position: Vec3<f64>,
|
||||
old_position: Vec3<f64>,
|
||||
|
@ -253,25 +261,14 @@ pub(crate) struct EntityFlags {
|
|||
_pad: u8,
|
||||
}
|
||||
|
||||
impl Entity {
|
||||
impl<C: Config> Entity<C> {
|
||||
pub(crate) fn flags(&self) -> EntityFlags {
|
||||
self.flags
|
||||
}
|
||||
|
||||
/// Gets a reference to this entity's [`EntityData`].
|
||||
pub fn data(&self) -> &EntityData {
|
||||
&self.data
|
||||
}
|
||||
|
||||
/// Gets a mutable reference to this entity's
|
||||
/// [`EntityData`].
|
||||
pub fn data_mut(&mut self) -> &mut EntityData {
|
||||
&mut self.data
|
||||
}
|
||||
|
||||
/// Gets the [`EntityKind`] of this entity.
|
||||
pub fn kind(&self) -> EntityKind {
|
||||
self.data.kind()
|
||||
self.state.kind()
|
||||
}
|
||||
|
||||
/// Gets the [`WorldId`](crate::world::WorldId) of the world this entity is
|
||||
|
@ -389,18 +386,18 @@ impl Entity {
|
|||
///
|
||||
/// [interact event]: crate::client::Event::InteractWithEntity
|
||||
pub fn hitbox(&self) -> Aabb<f64> {
|
||||
let dims = match &self.data {
|
||||
EntityData::Allay(_) => [0.6, 0.35, 0.6],
|
||||
EntityData::ChestBoat(_) => [1.375, 0.5625, 1.375],
|
||||
EntityData::Frog(_) => [0.5, 0.5, 0.5],
|
||||
EntityData::Tadpole(_) => [0.4, 0.3, 0.4],
|
||||
EntityData::Warden(_) => [0.9, 2.9, 0.9],
|
||||
EntityData::AreaEffectCloud(e) => [
|
||||
let dims = match &self.state {
|
||||
EntityState::Allay(_) => [0.6, 0.35, 0.6],
|
||||
EntityState::ChestBoat(_) => [1.375, 0.5625, 1.375],
|
||||
EntityState::Frog(_) => [0.5, 0.5, 0.5],
|
||||
EntityState::Tadpole(_) => [0.4, 0.3, 0.4],
|
||||
EntityState::Warden(_) => [0.9, 2.9, 0.9],
|
||||
EntityState::AreaEffectCloud(e) => [
|
||||
e.get_radius() as f64 * 2.0,
|
||||
0.5,
|
||||
e.get_radius() as f64 * 2.0,
|
||||
],
|
||||
EntityData::ArmorStand(e) => {
|
||||
EntityState::ArmorStand(e) => {
|
||||
if e.get_marker() {
|
||||
[0.0, 0.0, 0.0]
|
||||
} else if e.get_small() {
|
||||
|
@ -409,123 +406,123 @@ impl Entity {
|
|||
[0.5, 1.975, 0.5]
|
||||
}
|
||||
}
|
||||
EntityData::Arrow(_) => [0.5, 0.5, 0.5],
|
||||
EntityData::Axolotl(_) => [1.3, 0.6, 1.3],
|
||||
EntityData::Bat(_) => [0.5, 0.9, 0.5],
|
||||
EntityData::Bee(_) => [0.7, 0.6, 0.7], // TODO: baby size?
|
||||
EntityData::Blaze(_) => [0.6, 1.8, 0.6],
|
||||
EntityData::Boat(_) => [1.375, 0.5625, 1.375],
|
||||
EntityData::Cat(_) => [0.6, 0.7, 0.6],
|
||||
EntityData::CaveSpider(_) => [0.7, 0.5, 0.7],
|
||||
EntityData::Chicken(_) => [0.4, 0.7, 0.4], // TODO: baby size?
|
||||
EntityData::Cod(_) => [0.5, 0.3, 0.5],
|
||||
EntityData::Cow(_) => [0.9, 1.4, 0.9], // TODO: baby size?
|
||||
EntityData::Creeper(_) => [0.6, 1.7, 0.6],
|
||||
EntityData::Dolphin(_) => [0.9, 0.6, 0.9],
|
||||
EntityData::Donkey(_) => [1.5, 1.39648, 1.5], // TODO: baby size?
|
||||
EntityData::DragonFireball(_) => [1.0, 1.0, 1.0],
|
||||
EntityData::Drowned(_) => [0.6, 1.95, 0.6], // TODO: baby size?
|
||||
EntityData::ElderGuardian(_) => [1.9975, 1.9975, 1.9975],
|
||||
EntityData::EndCrystal(_) => [2.0, 2.0, 2.0],
|
||||
EntityData::EnderDragon(_) => [16.0, 8.0, 16.0],
|
||||
EntityData::Enderman(_) => [0.6, 2.9, 0.6],
|
||||
EntityData::Endermite(_) => [0.4, 0.3, 0.4],
|
||||
EntityData::Evoker(_) => [0.6, 1.95, 0.6],
|
||||
EntityData::EvokerFangs(_) => [0.5, 0.8, 0.5],
|
||||
EntityData::ExperienceOrb(_) => [0.5, 0.5, 0.5],
|
||||
EntityData::EyeOfEnder(_) => [0.25, 0.25, 0.25],
|
||||
EntityData::FallingBlock(_) => [0.98, 0.98, 0.98],
|
||||
EntityData::FireworkRocket(_) => [0.25, 0.25, 0.25],
|
||||
EntityData::Fox(_) => [0.6, 0.7, 0.6], // TODO: baby size?
|
||||
EntityData::Ghast(_) => [4.0, 4.0, 4.0],
|
||||
EntityData::Giant(_) => [3.6, 12.0, 3.6],
|
||||
EntityData::GlowItemFrame(_) => todo!("account for rotation"),
|
||||
EntityData::GlowSquid(_) => [0.8, 0.8, 0.8],
|
||||
EntityData::Goat(_) => [1.3, 0.9, 1.3], // TODO: baby size?
|
||||
EntityData::Guardian(_) => [0.85, 0.85, 0.85],
|
||||
EntityData::Hoglin(_) => [1.39648, 1.4, 1.39648], // TODO: baby size?
|
||||
EntityData::Horse(_) => [1.39648, 1.6, 1.39648], // TODO: baby size?
|
||||
EntityData::Husk(_) => [0.6, 1.95, 0.6], // TODO: baby size?
|
||||
EntityData::Illusioner(_) => [0.6, 1.95, 0.6],
|
||||
EntityData::IronGolem(_) => [1.4, 2.7, 1.4],
|
||||
EntityData::Item(_) => [0.25, 0.25, 0.25],
|
||||
EntityData::ItemFrame(_) => todo!("account for rotation"),
|
||||
EntityData::Fireball(_) => [1.0, 1.0, 1.0],
|
||||
EntityData::LeashKnot(_) => [0.375, 0.5, 0.375],
|
||||
EntityData::LightningBolt(_) => [0.0, 0.0, 0.0],
|
||||
EntityData::Llama(_) => [0.9, 1.87, 0.9], // TODO: baby size?
|
||||
EntityData::LlamaSpit(_) => [0.25, 0.25, 0.25],
|
||||
EntityData::MagmaCube(e) => {
|
||||
EntityState::Arrow(_) => [0.5, 0.5, 0.5],
|
||||
EntityState::Axolotl(_) => [1.3, 0.6, 1.3],
|
||||
EntityState::Bat(_) => [0.5, 0.9, 0.5],
|
||||
EntityState::Bee(_) => [0.7, 0.6, 0.7], // TODO: baby size?
|
||||
EntityState::Blaze(_) => [0.6, 1.8, 0.6],
|
||||
EntityState::Boat(_) => [1.375, 0.5625, 1.375],
|
||||
EntityState::Cat(_) => [0.6, 0.7, 0.6],
|
||||
EntityState::CaveSpider(_) => [0.7, 0.5, 0.7],
|
||||
EntityState::Chicken(_) => [0.4, 0.7, 0.4], // TODO: baby size?
|
||||
EntityState::Cod(_) => [0.5, 0.3, 0.5],
|
||||
EntityState::Cow(_) => [0.9, 1.4, 0.9], // TODO: baby size?
|
||||
EntityState::Creeper(_) => [0.6, 1.7, 0.6],
|
||||
EntityState::Dolphin(_) => [0.9, 0.6, 0.9],
|
||||
EntityState::Donkey(_) => [1.5, 1.39648, 1.5], // TODO: baby size?
|
||||
EntityState::DragonFireball(_) => [1.0, 1.0, 1.0],
|
||||
EntityState::Drowned(_) => [0.6, 1.95, 0.6], // TODO: baby size?
|
||||
EntityState::ElderGuardian(_) => [1.9975, 1.9975, 1.9975],
|
||||
EntityState::EndCrystal(_) => [2.0, 2.0, 2.0],
|
||||
EntityState::EnderDragon(_) => [16.0, 8.0, 16.0],
|
||||
EntityState::Enderman(_) => [0.6, 2.9, 0.6],
|
||||
EntityState::Endermite(_) => [0.4, 0.3, 0.4],
|
||||
EntityState::Evoker(_) => [0.6, 1.95, 0.6],
|
||||
EntityState::EvokerFangs(_) => [0.5, 0.8, 0.5],
|
||||
EntityState::ExperienceOrb(_) => [0.5, 0.5, 0.5],
|
||||
EntityState::EyeOfEnder(_) => [0.25, 0.25, 0.25],
|
||||
EntityState::FallingBlock(_) => [0.98, 0.98, 0.98],
|
||||
EntityState::FireworkRocket(_) => [0.25, 0.25, 0.25],
|
||||
EntityState::Fox(_) => [0.6, 0.7, 0.6], // TODO: baby size?
|
||||
EntityState::Ghast(_) => [4.0, 4.0, 4.0],
|
||||
EntityState::Giant(_) => [3.6, 12.0, 3.6],
|
||||
EntityState::GlowItemFrame(_) => todo!("account for rotation"),
|
||||
EntityState::GlowSquid(_) => [0.8, 0.8, 0.8],
|
||||
EntityState::Goat(_) => [1.3, 0.9, 1.3], // TODO: baby size?
|
||||
EntityState::Guardian(_) => [0.85, 0.85, 0.85],
|
||||
EntityState::Hoglin(_) => [1.39648, 1.4, 1.39648], // TODO: baby size?
|
||||
EntityState::Horse(_) => [1.39648, 1.6, 1.39648], // TODO: baby size?
|
||||
EntityState::Husk(_) => [0.6, 1.95, 0.6], // TODO: baby size?
|
||||
EntityState::Illusioner(_) => [0.6, 1.95, 0.6],
|
||||
EntityState::IronGolem(_) => [1.4, 2.7, 1.4],
|
||||
EntityState::Item(_) => [0.25, 0.25, 0.25],
|
||||
EntityState::ItemFrame(_) => todo!("account for rotation"),
|
||||
EntityState::Fireball(_) => [1.0, 1.0, 1.0],
|
||||
EntityState::LeashKnot(_) => [0.375, 0.5, 0.375],
|
||||
EntityState::LightningBolt(_) => [0.0, 0.0, 0.0],
|
||||
EntityState::Llama(_) => [0.9, 1.87, 0.9], // TODO: baby size?
|
||||
EntityState::LlamaSpit(_) => [0.25, 0.25, 0.25],
|
||||
EntityState::MagmaCube(e) => {
|
||||
let s = e.get_size() as f64 * 0.51000005;
|
||||
[s, s, s]
|
||||
}
|
||||
EntityData::Marker(_) => [0.0, 0.0, 0.0],
|
||||
EntityData::Minecart(_) => [0.98, 0.7, 0.98],
|
||||
EntityData::ChestMinecart(_) => [0.98, 0.7, 0.98],
|
||||
EntityData::CommandBlockMinecart(_) => [0.98, 0.7, 0.98],
|
||||
EntityData::FurnaceMinecart(_) => [0.98, 0.7, 0.98],
|
||||
EntityData::HopperMinecart(_) => [0.98, 0.7, 0.98],
|
||||
EntityData::SpawnerMinecart(_) => [0.98, 0.7, 0.98],
|
||||
EntityData::TntMinecart(_) => [0.98, 0.7, 0.98],
|
||||
EntityData::Mule(_) => [1.39648, 1.6, 1.39648], // TODO: baby size?
|
||||
EntityData::Mooshroom(_) => [0.9, 1.4, 0.9], // TODO: baby size?
|
||||
EntityData::Ocelot(_) => [0.6, 0.7, 0.6], // TODO: baby size?
|
||||
EntityData::Painting(_) => todo!("account for rotation and type"),
|
||||
EntityData::Panda(_) => [0.6, 0.7, 0.6], // TODO: baby size?
|
||||
EntityData::Parrot(_) => [0.5, 0.9, 0.5],
|
||||
EntityData::Phantom(_) => [0.9, 0.5, 0.9],
|
||||
EntityData::Pig(_) => [0.9, 0.9, 0.9], // TODO: baby size?
|
||||
EntityData::Piglin(_) => [0.6, 1.95, 0.6], // TODO: baby size?
|
||||
EntityData::PiglinBrute(_) => [0.6, 1.95, 0.6],
|
||||
EntityData::Pillager(_) => [0.6, 1.95, 0.6],
|
||||
EntityData::PolarBear(_) => [1.4, 1.4, 1.4], // TODO: baby size?
|
||||
EntityData::Tnt(_) => [0.98, 0.98, 0.98],
|
||||
EntityData::Pufferfish(_) => [0.7, 0.7, 0.7],
|
||||
EntityData::Rabbit(_) => [0.4, 0.5, 0.4], // TODO: baby size?
|
||||
EntityData::Ravager(_) => [1.95, 2.2, 1.95],
|
||||
EntityData::Salmon(_) => [0.7, 0.4, 0.7],
|
||||
EntityData::Sheep(_) => [0.9, 1.3, 0.9], // TODO: baby size?
|
||||
EntityData::Shulker(_) => [1.0, 1.0, 1.0], // TODO: how is height calculated?
|
||||
EntityData::ShulkerBullet(_) => [0.3125, 0.3125, 0.3125],
|
||||
EntityData::Silverfish(_) => [0.4, 0.3, 0.4],
|
||||
EntityData::Skeleton(_) => [0.6, 1.99, 0.6],
|
||||
EntityData::SkeletonHorse(_) => [1.39648, 1.6, 1.39648], // TODO: baby size?
|
||||
EntityData::Slime(e) => {
|
||||
EntityState::Marker(_) => [0.0, 0.0, 0.0],
|
||||
EntityState::Minecart(_) => [0.98, 0.7, 0.98],
|
||||
EntityState::ChestMinecart(_) => [0.98, 0.7, 0.98],
|
||||
EntityState::CommandBlockMinecart(_) => [0.98, 0.7, 0.98],
|
||||
EntityState::FurnaceMinecart(_) => [0.98, 0.7, 0.98],
|
||||
EntityState::HopperMinecart(_) => [0.98, 0.7, 0.98],
|
||||
EntityState::SpawnerMinecart(_) => [0.98, 0.7, 0.98],
|
||||
EntityState::TntMinecart(_) => [0.98, 0.7, 0.98],
|
||||
EntityState::Mule(_) => [1.39648, 1.6, 1.39648], // TODO: baby size?
|
||||
EntityState::Mooshroom(_) => [0.9, 1.4, 0.9], // TODO: baby size?
|
||||
EntityState::Ocelot(_) => [0.6, 0.7, 0.6], // TODO: baby size?
|
||||
EntityState::Painting(_) => todo!("account for rotation and type"),
|
||||
EntityState::Panda(_) => [0.6, 0.7, 0.6], // TODO: baby size?
|
||||
EntityState::Parrot(_) => [0.5, 0.9, 0.5],
|
||||
EntityState::Phantom(_) => [0.9, 0.5, 0.9],
|
||||
EntityState::Pig(_) => [0.9, 0.9, 0.9], // TODO: baby size?
|
||||
EntityState::Piglin(_) => [0.6, 1.95, 0.6], // TODO: baby size?
|
||||
EntityState::PiglinBrute(_) => [0.6, 1.95, 0.6],
|
||||
EntityState::Pillager(_) => [0.6, 1.95, 0.6],
|
||||
EntityState::PolarBear(_) => [1.4, 1.4, 1.4], // TODO: baby size?
|
||||
EntityState::Tnt(_) => [0.98, 0.98, 0.98],
|
||||
EntityState::Pufferfish(_) => [0.7, 0.7, 0.7],
|
||||
EntityState::Rabbit(_) => [0.4, 0.5, 0.4], // TODO: baby size?
|
||||
EntityState::Ravager(_) => [1.95, 2.2, 1.95],
|
||||
EntityState::Salmon(_) => [0.7, 0.4, 0.7],
|
||||
EntityState::Sheep(_) => [0.9, 1.3, 0.9], // TODO: baby size?
|
||||
EntityState::Shulker(_) => [1.0, 1.0, 1.0], // TODO: how is height calculated?
|
||||
EntityState::ShulkerBullet(_) => [0.3125, 0.3125, 0.3125],
|
||||
EntityState::Silverfish(_) => [0.4, 0.3, 0.4],
|
||||
EntityState::Skeleton(_) => [0.6, 1.99, 0.6],
|
||||
EntityState::SkeletonHorse(_) => [1.39648, 1.6, 1.39648], // TODO: baby size?
|
||||
EntityState::Slime(e) => {
|
||||
let s = 0.51000005 * e.get_size() as f64;
|
||||
[s, s, s]
|
||||
}
|
||||
EntityData::SmallFireball(_) => [0.3125, 0.3125, 0.3125],
|
||||
EntityData::SnowGolem(_) => [0.7, 1.9, 0.7],
|
||||
EntityData::Snowball(_) => [0.25, 0.25, 0.25],
|
||||
EntityData::SpectralArrow(_) => [0.5, 0.5, 0.5],
|
||||
EntityData::Spider(_) => [1.4, 0.9, 1.4],
|
||||
EntityData::Squid(_) => [0.8, 0.8, 0.8],
|
||||
EntityData::Stray(_) => [0.6, 1.99, 0.6],
|
||||
EntityData::Strider(_) => [0.9, 1.7, 0.9], // TODO: baby size?
|
||||
EntityData::Egg(_) => [0.25, 0.25, 0.25],
|
||||
EntityData::EnderPearl(_) => [0.25, 0.25, 0.25],
|
||||
EntityData::ExperienceBottle(_) => [0.25, 0.25, 0.25],
|
||||
EntityData::Potion(_) => [0.25, 0.25, 0.25],
|
||||
EntityData::Trident(_) => [0.5, 0.5, 0.5],
|
||||
EntityData::TraderLlama(_) => [0.9, 1.87, 0.9],
|
||||
EntityData::TropicalFish(_) => [0.5, 0.4, 0.5],
|
||||
EntityData::Turtle(_) => [1.2, 0.4, 1.2], // TODO: baby size?
|
||||
EntityData::Vex(_) => [0.4, 0.8, 0.4],
|
||||
EntityData::Villager(_) => [0.6, 1.95, 0.6], // TODO: baby size?
|
||||
EntityData::Vindicator(_) => [0.6, 1.95, 0.6],
|
||||
EntityData::WanderingTrader(_) => [0.6, 1.95, 0.6],
|
||||
EntityData::Witch(_) => [0.6, 1.95, 0.6],
|
||||
EntityData::Wither(_) => [0.9, 3.5, 0.9],
|
||||
EntityData::WitherSkeleton(_) => [0.7, 2.4, 0.7],
|
||||
EntityData::WitherSkull(_) => [0.3125, 0.3125, 0.3125],
|
||||
EntityData::Wolf(_) => [0.6, 0.85, 0.6], // TODO: baby size?
|
||||
EntityData::Zoglin(_) => [1.39648, 1.4, 1.39648], // TODO: baby size?
|
||||
EntityData::Zombie(_) => [0.6, 1.95, 0.6], // TODO: baby size?
|
||||
EntityData::ZombieHorse(_) => [1.39648, 1.6, 1.39648], // TODO: baby size?
|
||||
EntityData::ZombieVillager(_) => [0.6, 1.95, 0.6], // TODO: baby size?
|
||||
EntityData::ZombifiedPiglin(_) => [0.6, 1.95, 0.6], // TODO: baby size?
|
||||
EntityData::Player(_) => [0.6, 1.8, 0.6], // TODO: changes depending on the pose.
|
||||
EntityData::FishingBobber(_) => [0.25, 0.25, 0.25],
|
||||
EntityState::SmallFireball(_) => [0.3125, 0.3125, 0.3125],
|
||||
EntityState::SnowGolem(_) => [0.7, 1.9, 0.7],
|
||||
EntityState::Snowball(_) => [0.25, 0.25, 0.25],
|
||||
EntityState::SpectralArrow(_) => [0.5, 0.5, 0.5],
|
||||
EntityState::Spider(_) => [1.4, 0.9, 1.4],
|
||||
EntityState::Squid(_) => [0.8, 0.8, 0.8],
|
||||
EntityState::Stray(_) => [0.6, 1.99, 0.6],
|
||||
EntityState::Strider(_) => [0.9, 1.7, 0.9], // TODO: baby size?
|
||||
EntityState::Egg(_) => [0.25, 0.25, 0.25],
|
||||
EntityState::EnderPearl(_) => [0.25, 0.25, 0.25],
|
||||
EntityState::ExperienceBottle(_) => [0.25, 0.25, 0.25],
|
||||
EntityState::Potion(_) => [0.25, 0.25, 0.25],
|
||||
EntityState::Trident(_) => [0.5, 0.5, 0.5],
|
||||
EntityState::TraderLlama(_) => [0.9, 1.87, 0.9],
|
||||
EntityState::TropicalFish(_) => [0.5, 0.4, 0.5],
|
||||
EntityState::Turtle(_) => [1.2, 0.4, 1.2], // TODO: baby size?
|
||||
EntityState::Vex(_) => [0.4, 0.8, 0.4],
|
||||
EntityState::Villager(_) => [0.6, 1.95, 0.6], // TODO: baby size?
|
||||
EntityState::Vindicator(_) => [0.6, 1.95, 0.6],
|
||||
EntityState::WanderingTrader(_) => [0.6, 1.95, 0.6],
|
||||
EntityState::Witch(_) => [0.6, 1.95, 0.6],
|
||||
EntityState::Wither(_) => [0.9, 3.5, 0.9],
|
||||
EntityState::WitherSkeleton(_) => [0.7, 2.4, 0.7],
|
||||
EntityState::WitherSkull(_) => [0.3125, 0.3125, 0.3125],
|
||||
EntityState::Wolf(_) => [0.6, 0.85, 0.6], // TODO: baby size?
|
||||
EntityState::Zoglin(_) => [1.39648, 1.4, 1.39648], // TODO: baby size?
|
||||
EntityState::Zombie(_) => [0.6, 1.95, 0.6], // TODO: baby size?
|
||||
EntityState::ZombieHorse(_) => [1.39648, 1.6, 1.39648], // TODO: baby size?
|
||||
EntityState::ZombieVillager(_) => [0.6, 1.95, 0.6], // TODO: baby size?
|
||||
EntityState::ZombifiedPiglin(_) => [0.6, 1.95, 0.6], // TODO: baby size?
|
||||
EntityState::Player(_) => [0.6, 1.8, 0.6], // TODO: changes depending on the pose.
|
||||
EntityState::FishingBobber(_) => [0.25, 0.25, 0.25],
|
||||
};
|
||||
|
||||
aabb_from_bottom_and_size(self.new_position, dims.into())
|
||||
|
@ -536,7 +533,7 @@ impl Entity {
|
|||
///
|
||||
/// Is `None` if there is no initial metadata.
|
||||
pub(crate) fn initial_metadata_packet(&self, this_id: EntityId) -> Option<SetEntityMetadata> {
|
||||
self.data.initial_metadata().map(|meta| SetEntityMetadata {
|
||||
self.state.initial_metadata().map(|meta| SetEntityMetadata {
|
||||
entity_id: VarInt(this_id.to_network_id()),
|
||||
metadata: RawBytes(meta),
|
||||
})
|
||||
|
@ -546,23 +543,23 @@ impl Entity {
|
|||
///
|
||||
/// Is `None` if this entity's metadata has not been modified.
|
||||
pub(crate) fn updated_metadata_packet(&self, this_id: EntityId) -> Option<SetEntityMetadata> {
|
||||
self.data.updated_metadata().map(|meta| SetEntityMetadata {
|
||||
self.state.updated_metadata().map(|meta| SetEntityMetadata {
|
||||
entity_id: VarInt(this_id.to_network_id()),
|
||||
metadata: RawBytes(meta),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn spawn_packet(&self, this_id: EntityId) -> Option<EntitySpawnPacket> {
|
||||
match &self.data {
|
||||
EntityData::Marker(_) => None,
|
||||
EntityData::ExperienceOrb(_) => {
|
||||
match &self.state {
|
||||
EntityState::Marker(_) => None,
|
||||
EntityState::ExperienceOrb(_) => {
|
||||
Some(EntitySpawnPacket::ExperienceOrb(AddExperienceOrb {
|
||||
entity_id: VarInt(this_id.to_network_id()),
|
||||
position: self.new_position,
|
||||
count: 0, // TODO
|
||||
}))
|
||||
}
|
||||
EntityData::Player(_) => Some(EntitySpawnPacket::Player(AddPlayer {
|
||||
EntityState::Player(_) => Some(EntitySpawnPacket::Player(AddPlayer {
|
||||
entity_id: VarInt(this_id.to_network_id()),
|
||||
player_uuid: self.uuid,
|
||||
position: self.new_position,
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#![allow(clippy::all, missing_docs)]
|
||||
|
||||
use crate::block::{BlockPos, BlockState};
|
||||
use crate::entity::data::*;
|
||||
use crate::entity::state::*;
|
||||
use crate::entity::EntityId;
|
||||
use crate::protocol_inner::{Encode, VarInt};
|
||||
use crate::text::Text;
|
||||
|
|
10
src/lib.rs
10
src/lib.rs
|
@ -52,17 +52,23 @@
|
|||
//! use valence::server::{Server, ShutdownResult};
|
||||
//!
|
||||
//! pub fn main() -> ShutdownResult {
|
||||
//! valence::start_server(Game)
|
||||
//! valence::start_server(Game, ())
|
||||
//! }
|
||||
//!
|
||||
//! struct Game;
|
||||
//!
|
||||
//! impl Config for Game {
|
||||
//! type ChunkData = ();
|
||||
//! type ClientData = ();
|
||||
//! type EntityData = ();
|
||||
//! type ServerData = ();
|
||||
//! type WorldData = ();
|
||||
//!
|
||||
//! fn max_connections(&self) -> usize {
|
||||
//! 256
|
||||
//! }
|
||||
//!
|
||||
//! fn update(&self, server: &mut Server) {
|
||||
//! fn update(&self, server: &mut Server<Self>) {
|
||||
//! server.clients.retain(|_, client| {
|
||||
//! if client.created_tick() == server.shared.current_tick() {
|
||||
//! println!("{} joined!", client.username());
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
/// Reading and writing whole packets.
|
||||
|
||||
use std::io::Read;
|
||||
use std::time::Duration;
|
||||
|
||||
|
|
|
@ -51,15 +51,17 @@ use crate::{Ticks, PROTOCOL_VERSION, VERSION_NAME};
|
|||
|
||||
/// Contains the entire state of a running Minecraft server, accessible from
|
||||
/// within the [update](crate::config::Config::update) loop.
|
||||
pub struct Server {
|
||||
pub struct Server<C: Config> {
|
||||
/// Custom data.
|
||||
pub data: C::ServerData,
|
||||
/// A handle to this server's [`SharedServer`].
|
||||
pub shared: SharedServer,
|
||||
pub shared: SharedServer<C>,
|
||||
/// All of the clients in the server.
|
||||
pub clients: Clients,
|
||||
pub clients: Clients<C>,
|
||||
/// All of entities in the server.
|
||||
pub entities: Entities,
|
||||
pub entities: Entities<C>,
|
||||
/// All of the worlds in the server.
|
||||
pub worlds: Worlds,
|
||||
pub worlds: Worlds<C>,
|
||||
}
|
||||
|
||||
/// A handle to a Minecraft server containing the subset of functionality which
|
||||
|
@ -69,11 +71,17 @@ pub struct Server {
|
|||
/// be shared between threads.
|
||||
///
|
||||
/// [update]: crate::config::Config::update
|
||||
#[derive(Clone)]
|
||||
pub struct SharedServer(Arc<SharedServerInner>);
|
||||
|
||||
struct SharedServerInner {
|
||||
cfg: Box<dyn Config>,
|
||||
pub struct SharedServer<C: Config>(Arc<SharedServerInner<C>>);
|
||||
|
||||
impl<C: Config> Clone for SharedServer<C> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
struct SharedServerInner<C: Config> {
|
||||
cfg: C,
|
||||
address: SocketAddr,
|
||||
tick_rate: Ticks,
|
||||
online_mode: bool,
|
||||
|
@ -130,10 +138,10 @@ pub type ShutdownResult = Result<(), Box<dyn Error + Send + Sync + 'static>>;
|
|||
pub(crate) type S2cPacketChannels = (Sender<C2sPlayPacket>, Receiver<S2cPlayPacket>);
|
||||
pub(crate) type C2sPacketChannels = (Sender<S2cPlayPacket>, Receiver<C2sPlayPacket>);
|
||||
|
||||
impl SharedServer {
|
||||
impl<C: Config> SharedServer<C> {
|
||||
/// Gets a reference to the config object used to start the server.
|
||||
pub fn config(&self) -> &(impl Config + ?Sized) {
|
||||
self.0.cfg.as_ref()
|
||||
pub fn config(&self) -> &C {
|
||||
&self.0.cfg
|
||||
}
|
||||
|
||||
/// Gets the socket address this server is bound to.
|
||||
|
@ -240,12 +248,13 @@ impl SharedServer {
|
|||
///
|
||||
/// The function returns once the server has shut down, a runtime error
|
||||
/// occurs, or the configuration is found to be invalid.
|
||||
pub fn start_server(config: impl Config) -> ShutdownResult {
|
||||
pub fn start_server<C: Config>(config: C, data: C::ServerData) -> ShutdownResult {
|
||||
let shared = setup_server(config).map_err(Box::<dyn Error + Send + Sync + 'static>::from)?;
|
||||
|
||||
let _guard = shared.tokio_handle().enter();
|
||||
|
||||
let mut server = Server {
|
||||
data,
|
||||
shared: shared.clone(),
|
||||
clients: Clients::new(),
|
||||
entities: Entities::new(),
|
||||
|
@ -259,7 +268,7 @@ pub fn start_server(config: impl Config) -> ShutdownResult {
|
|||
do_update_loop(&mut server)
|
||||
}
|
||||
|
||||
fn setup_server(cfg: impl Config) -> anyhow::Result<SharedServer> {
|
||||
fn setup_server<C: Config>(cfg: C) -> anyhow::Result<SharedServer<C>> {
|
||||
let max_connections = cfg.max_connections();
|
||||
let address = cfg.address();
|
||||
let tick_rate = cfg.tick_rate();
|
||||
|
@ -360,7 +369,7 @@ fn setup_server(cfg: impl Config) -> anyhow::Result<SharedServer> {
|
|||
};
|
||||
|
||||
let server = SharedServerInner {
|
||||
cfg: Box::new(cfg),
|
||||
cfg,
|
||||
address,
|
||||
tick_rate,
|
||||
online_mode,
|
||||
|
@ -385,7 +394,7 @@ fn setup_server(cfg: impl Config) -> anyhow::Result<SharedServer> {
|
|||
Ok(SharedServer(Arc::new(server)))
|
||||
}
|
||||
|
||||
fn do_update_loop(server: &mut Server) -> ShutdownResult {
|
||||
fn do_update_loop<C: Config>(server: &mut Server<C>) -> ShutdownResult {
|
||||
let mut tick_start = Instant::now();
|
||||
|
||||
let shared = server.shared.clone();
|
||||
|
@ -441,7 +450,7 @@ fn do_update_loop(server: &mut Server) -> ShutdownResult {
|
|||
}
|
||||
}
|
||||
|
||||
fn join_player(server: &mut Server, msg: NewClientMessage) {
|
||||
fn join_player<C: Config>(server: &mut Server<C>, msg: NewClientMessage) {
|
||||
let (clientbound_tx, clientbound_rx) = flume::bounded(server.shared.0.outgoing_packet_capacity);
|
||||
let (serverbound_tx, serverbound_rx) = flume::bounded(server.shared.0.incoming_packet_capacity);
|
||||
|
||||
|
@ -450,7 +459,12 @@ fn join_player(server: &mut Server, msg: NewClientMessage) {
|
|||
|
||||
let _ = msg.reply.send(s2c_packet_channels);
|
||||
|
||||
let client = Client::new(c2s_packet_channels, &server.shared, msg.ncd);
|
||||
let client = Client::new(
|
||||
c2s_packet_channels,
|
||||
&server.shared,
|
||||
msg.ncd,
|
||||
C::ClientData::default(),
|
||||
);
|
||||
|
||||
server.clients.insert(client);
|
||||
}
|
||||
|
@ -460,7 +474,7 @@ struct Codec {
|
|||
dec: Decoder<OwnedReadHalf>,
|
||||
}
|
||||
|
||||
async fn do_accept_loop(server: SharedServer) {
|
||||
async fn do_accept_loop<C: Config>(server: SharedServer<C>) {
|
||||
log::trace!("entering accept loop");
|
||||
|
||||
let listener = match TcpListener::bind(server.0.address).await {
|
||||
|
@ -502,8 +516,8 @@ async fn do_accept_loop(server: SharedServer) {
|
|||
}
|
||||
}
|
||||
|
||||
async fn handle_connection(
|
||||
server: SharedServer,
|
||||
async fn handle_connection<C: Config>(
|
||||
server: SharedServer<C>,
|
||||
stream: TcpStream,
|
||||
remote_addr: SocketAddr,
|
||||
) -> anyhow::Result<()> {
|
||||
|
@ -533,8 +547,8 @@ async fn handle_connection(
|
|||
}
|
||||
}
|
||||
|
||||
async fn handle_status(
|
||||
server: SharedServer,
|
||||
async fn handle_status<C: Config>(
|
||||
server: SharedServer<C>,
|
||||
c: &mut Codec,
|
||||
remote_addr: SocketAddr,
|
||||
) -> anyhow::Result<()> {
|
||||
|
@ -585,8 +599,8 @@ async fn handle_status(
|
|||
}
|
||||
|
||||
/// Handle the login process and return the new player's data if successful.
|
||||
async fn handle_login(
|
||||
server: &SharedServer,
|
||||
async fn handle_login<C: Config>(
|
||||
server: &SharedServer<C>,
|
||||
c: &mut Codec,
|
||||
remote_addr: SocketAddr,
|
||||
) -> anyhow::Result<Option<NewClientData>> {
|
||||
|
@ -725,7 +739,11 @@ async fn handle_login(
|
|||
Ok(Some(npd))
|
||||
}
|
||||
|
||||
async fn handle_play(server: &SharedServer, c: Codec, ncd: NewClientData) -> anyhow::Result<()> {
|
||||
async fn handle_play<C: Config>(
|
||||
server: &SharedServer<C>,
|
||||
c: Codec,
|
||||
ncd: NewClientData,
|
||||
) -> anyhow::Result<()> {
|
||||
let (reply_tx, reply_rx) = oneshot::channel();
|
||||
|
||||
server
|
||||
|
|
|
@ -6,6 +6,7 @@ use rayon::iter::{IndexedParallelIterator, ParallelIterator};
|
|||
use vek::{Aabb, Vec3};
|
||||
|
||||
use crate::bvh::{Bvh, Node};
|
||||
use crate::config::Config;
|
||||
use crate::entity::{Entities, EntityId};
|
||||
use crate::util::ray_box_intersect;
|
||||
use crate::world::WorldId;
|
||||
|
@ -225,7 +226,7 @@ impl SpatialIndex {
|
|||
self.bvh.par_iter().map(|(&id, bb)| (id, bb))
|
||||
}
|
||||
|
||||
pub(crate) fn update(&mut self, entities: &Entities, id: WorldId) {
|
||||
pub(crate) fn update<C: Config>(&mut self, entities: &Entities<C>, id: WorldId) {
|
||||
self.bvh.build(
|
||||
entities
|
||||
.iter()
|
||||
|
|
34
src/world.rs
34
src/world.rs
|
@ -5,6 +5,7 @@ use std::iter::FusedIterator;
|
|||
use rayon::iter::ParallelIterator;
|
||||
|
||||
use crate::chunk::Chunks;
|
||||
use crate::config::Config;
|
||||
use crate::dimension::DimensionId;
|
||||
use crate::player_list::PlayerList;
|
||||
use crate::server::SharedServer;
|
||||
|
@ -12,9 +13,9 @@ use crate::slotmap::{Key, SlotMap};
|
|||
use crate::spatial_index::SpatialIndex;
|
||||
|
||||
/// A container for all [`World`]s on a [`Server`](crate::server::Server).
|
||||
pub struct Worlds {
|
||||
sm: SlotMap<World>,
|
||||
server: SharedServer,
|
||||
pub struct Worlds<C: Config> {
|
||||
sm: SlotMap<World<C>>,
|
||||
server: SharedServer<C>,
|
||||
}
|
||||
|
||||
/// An identifier for a [`World`] on the server.
|
||||
|
@ -34,8 +35,8 @@ impl WorldId {
|
|||
pub const NULL: Self = Self(Key::NULL);
|
||||
}
|
||||
|
||||
impl Worlds {
|
||||
pub(crate) fn new(server: SharedServer) -> Self {
|
||||
impl<C: Config> Worlds<C> {
|
||||
pub(crate) fn new(server: SharedServer<C>) -> Self {
|
||||
Self {
|
||||
sm: SlotMap::new(),
|
||||
server,
|
||||
|
@ -44,8 +45,9 @@ impl Worlds {
|
|||
|
||||
/// Creates a new world on the server with the provided dimension. A
|
||||
/// reference to the world along with its ID is returned.
|
||||
pub fn create(&mut self, dim: DimensionId) -> (WorldId, &mut World) {
|
||||
pub fn create(&mut self, dim: DimensionId, data: C::WorldData) -> (WorldId, &mut World<C>) {
|
||||
let (id, world) = self.sm.insert(World {
|
||||
data,
|
||||
spatial_index: SpatialIndex::new(),
|
||||
chunks: Chunks::new(self.server.clone(), dim),
|
||||
meta: WorldMeta {
|
||||
|
@ -71,7 +73,7 @@ impl Worlds {
|
|||
/// `f` returns `true`.
|
||||
///
|
||||
/// All worlds are visited in an unspecified order.
|
||||
pub fn retain(&mut self, mut f: impl FnMut(WorldId, &mut World) -> bool) {
|
||||
pub fn retain(&mut self, mut f: impl FnMut(WorldId, &mut World<C>) -> bool) {
|
||||
self.sm.retain(|k, v| f(WorldId(k), v))
|
||||
}
|
||||
|
||||
|
@ -82,47 +84,49 @@ impl Worlds {
|
|||
|
||||
/// Returns a shared reference to the world with the given ID. If
|
||||
/// the ID is invalid, then `None` is returned.
|
||||
pub fn get(&self, world: WorldId) -> Option<&World> {
|
||||
pub fn get(&self, world: WorldId) -> Option<&World<C>> {
|
||||
self.sm.get(world.0)
|
||||
}
|
||||
|
||||
/// Returns an exclusive reference to the world with the given ID. If the
|
||||
/// ID is invalid, then `None` is returned.
|
||||
pub fn get_mut(&mut self, world: WorldId) -> Option<&mut World> {
|
||||
pub fn get_mut(&mut self, world: WorldId) -> Option<&mut World<C>> {
|
||||
self.sm.get_mut(world.0)
|
||||
}
|
||||
|
||||
/// Returns an immutable iterator over all worlds on the server in an
|
||||
/// unspecified order.
|
||||
pub fn iter(&self) -> impl FusedIterator<Item = (WorldId, &World)> + Clone + '_ {
|
||||
pub fn iter(&self) -> impl FusedIterator<Item = (WorldId, &World<C>)> + Clone + '_ {
|
||||
self.sm.iter().map(|(k, v)| (WorldId(k), v))
|
||||
}
|
||||
|
||||
/// Returns a mutable iterator over all worlds on the server in an
|
||||
/// unspecified ordder.
|
||||
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (WorldId, &mut World)> + '_ {
|
||||
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (WorldId, &mut World<C>)> + '_ {
|
||||
self.sm.iter_mut().map(|(k, v)| (WorldId(k), v))
|
||||
}
|
||||
|
||||
/// Returns a parallel immutable iterator over all worlds on the server in
|
||||
/// an unspecified order.
|
||||
pub fn par_iter(&self) -> impl ParallelIterator<Item = (WorldId, &World)> + Clone + '_ {
|
||||
pub fn par_iter(&self) -> impl ParallelIterator<Item = (WorldId, &World<C>)> + Clone + '_ {
|
||||
self.sm.par_iter().map(|(k, v)| (WorldId(k), v))
|
||||
}
|
||||
|
||||
/// Returns a parallel mutable iterator over all worlds on the server in an
|
||||
/// unspecified order.
|
||||
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (WorldId, &mut World)> + '_ {
|
||||
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (WorldId, &mut World<C>)> + '_ {
|
||||
self.sm.par_iter_mut().map(|(k, v)| (WorldId(k), v))
|
||||
}
|
||||
}
|
||||
|
||||
/// A space for chunks, entities, and clients to occupy.
|
||||
pub struct World {
|
||||
pub struct World<C: Config> {
|
||||
/// Custom data.
|
||||
pub data: C::WorldData,
|
||||
/// Contains all of the entities in this world.
|
||||
pub spatial_index: SpatialIndex,
|
||||
/// All of the chunks in this world.
|
||||
pub chunks: Chunks,
|
||||
pub chunks: Chunks<C>,
|
||||
/// This world's metadata.
|
||||
pub meta: WorldMeta,
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue