mirror of
https://github.com/italicsjenga/valence.git
synced 2024-12-23 22:41:30 +11:00
Get entity movement working
This commit is contained in:
parent
8da32d0b8b
commit
1570c95ac8
|
@ -109,12 +109,6 @@ impl Config for Game {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let entity_id = world.entities.create();
|
|
||||||
let mut entity = world.entities.get_mut(entity_id).unwrap();
|
|
||||||
|
|
||||||
entity.set_type(EntityType::Cow);
|
|
||||||
entity.set_position([0.0, 50.0, 0.0]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&self, server: &Server, mut worlds: WorldsMut) {
|
fn update(&self, server: &Server, mut worlds: WorldsMut) {
|
119
examples/cow_sphere.rs
Normal file
119
examples/cow_sphere.rs
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
|
use log::LevelFilter;
|
||||||
|
use valence::block::BlockState;
|
||||||
|
use valence::client::GameMode;
|
||||||
|
use valence::config::{Config, ServerListPing};
|
||||||
|
use valence::text::Color;
|
||||||
|
use valence::{
|
||||||
|
async_trait, ChunkPos, ClientMut, DimensionId, EntityType, Server, ShutdownResult, Text,
|
||||||
|
TextFormat, WorldId, WorldsMut,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn main() -> ShutdownResult {
|
||||||
|
env_logger::Builder::new()
|
||||||
|
.filter_module("valence", LevelFilter::Trace)
|
||||||
|
.parse_default_env()
|
||||||
|
.init();
|
||||||
|
|
||||||
|
valence::start_server(Game {
|
||||||
|
player_count: AtomicUsize::new(0),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Game {
|
||||||
|
player_count: AtomicUsize,
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAX_PLAYERS: usize = 10;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Config for Game {
|
||||||
|
fn max_connections(&self) -> usize {
|
||||||
|
// We want status pings to be successful even if the server is full.
|
||||||
|
MAX_PLAYERS + 64
|
||||||
|
}
|
||||||
|
|
||||||
|
fn online_mode(&self) -> bool {
|
||||||
|
// You'll want this to be true on real servers.
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn server_list_ping(&self, _server: &Server, _remote_addr: SocketAddr) -> ServerListPing {
|
||||||
|
ServerListPing::Respond {
|
||||||
|
online_players: self.player_count.load(Ordering::SeqCst) as i32,
|
||||||
|
max_players: MAX_PLAYERS as i32,
|
||||||
|
description: "Hello Valence!".color(Color::AQUA),
|
||||||
|
favicon_png: Some(include_bytes!("favicon.png")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn join(
|
||||||
|
&self,
|
||||||
|
_server: &Server,
|
||||||
|
_client: ClientMut,
|
||||||
|
worlds: WorldsMut,
|
||||||
|
) -> 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, mut worlds: WorldsMut) {
|
||||||
|
let world_id = worlds.create(DimensionId::default());
|
||||||
|
let mut world = worlds.get_mut(world_id).unwrap();
|
||||||
|
world.meta.set_flat(true);
|
||||||
|
|
||||||
|
let size = 5;
|
||||||
|
for z in -size..size {
|
||||||
|
for x in -size..size {
|
||||||
|
world.chunks.create([x, z]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let entity_id = world.entities.create();
|
||||||
|
let mut entity = world.entities.get_mut(entity_id).unwrap();
|
||||||
|
|
||||||
|
entity.set_type(EntityType::Cow);
|
||||||
|
entity.set_position([0.0, 100.0, 0.0]);
|
||||||
|
//entity.set_yaw(30.0);
|
||||||
|
//entity.set_pitch(0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&self, server: &Server, mut worlds: WorldsMut) {
|
||||||
|
let mut world = worlds.iter_mut().next().unwrap().1;
|
||||||
|
|
||||||
|
world.clients.retain(|_, mut client| {
|
||||||
|
if client.created_tick() == server.current_tick() {
|
||||||
|
client.set_game_mode(GameMode::Creative);
|
||||||
|
client.teleport([0.0, 200.0, 0.0], 0.0, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if client.is_disconnected() {
|
||||||
|
self.player_count.fetch_sub(1, Ordering::SeqCst);
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (_, mut e) in world.entities.iter_mut() {
|
||||||
|
let time = server.current_tick() as f64 / server.tick_rate() as f64;
|
||||||
|
|
||||||
|
if e.typ() == EntityType::Cow {
|
||||||
|
e.set_position(e.position() + [0.0, 0.0, 0.02]);
|
||||||
|
let yaw = (time % 1.0 * 360.0) as f32;
|
||||||
|
e.set_yaw(yaw);
|
||||||
|
e.set_head_yaw(yaw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -418,7 +418,8 @@ fn encode_paletted_container(
|
||||||
let palette_idx = palette
|
let palette_idx = palette
|
||||||
.iter()
|
.iter()
|
||||||
.position(|&e| e == entry)
|
.position(|&e| e == entry)
|
||||||
.expect("entry should be in the palette") as u64;
|
.expect("entry should be in the palette")
|
||||||
|
as u64;
|
||||||
|
|
||||||
val |= palette_idx << (i * bits_per_idx);
|
val |= palette_idx << (i * bits_per_idx);
|
||||||
}
|
}
|
||||||
|
|
114
src/client.rs
114
src/client.rs
|
@ -8,25 +8,29 @@ use uuid::Uuid;
|
||||||
use vek::Vec3;
|
use vek::Vec3;
|
||||||
|
|
||||||
use crate::block_pos::BlockPos;
|
use crate::block_pos::BlockPos;
|
||||||
|
use crate::byte_angle::ByteAngle;
|
||||||
use crate::config::{
|
use crate::config::{
|
||||||
Biome, BiomeGrassColorModifier, BiomePrecipitation, Dimension, DimensionEffects, DimensionId,
|
Biome, BiomeGrassColorModifier, BiomePrecipitation, Dimension, DimensionEffects, DimensionId,
|
||||||
};
|
};
|
||||||
use crate::entity::EntityType;
|
use crate::entity::{velocity_to_packet_units, EntityType};
|
||||||
pub use crate::packets::play::GameMode;
|
pub use crate::packets::play::GameMode;
|
||||||
use crate::packets::play::{
|
use crate::packets::play::{
|
||||||
Biome as BiomeRegistryBiome, BiomeAdditionsSound, BiomeEffects, BiomeMoodSound, BiomeMusic,
|
Biome as BiomeRegistryBiome, BiomeAdditionsSound, BiomeEffects, BiomeMoodSound, BiomeMusic,
|
||||||
BiomeParticle, BiomeParticleOptions, BiomeProperty, BiomeRegistry, ChangeGameState,
|
BiomeParticle, BiomeParticleOptions, BiomeProperty, BiomeRegistry, ChangeGameState,
|
||||||
ChangeGameStateReason, ClientPlayPacket, DestroyEntities, DimensionCodec, DimensionType,
|
ChangeGameStateReason, ClientPlayPacket, DestroyEntities, DimensionCodec, DimensionType,
|
||||||
DimensionTypeRegistry, DimensionTypeRegistryEntry, Disconnect, JoinGame, KeepAliveClientbound,
|
DimensionTypeRegistry, DimensionTypeRegistryEntry, Disconnect, EntityHeadLook, EntityPosition,
|
||||||
PlayerPositionAndLook, PlayerPositionAndLookFlags, ServerPlayPacket, SpawnPosition,
|
EntityPositionAndRotation, EntityRotation, EntityTeleport, EntityVelocity, JoinGame,
|
||||||
UnloadChunk, UpdateViewDistance, UpdateViewPosition,
|
KeepAliveClientbound, PlayerPositionAndLook, PlayerPositionAndLookFlags, ServerPlayPacket,
|
||||||
|
SpawnPosition, UnloadChunk, UpdateViewDistance, UpdateViewPosition,
|
||||||
};
|
};
|
||||||
use crate::protocol::{BoundedInt, Nbt};
|
use crate::protocol::{BoundedInt, Nbt};
|
||||||
use crate::server::ServerPacketChannels;
|
use crate::server::ServerPacketChannels;
|
||||||
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::var_int::VarInt;
|
use crate::var_int::VarInt;
|
||||||
use crate::{ident, ChunkPos, Chunks, Entities, EntityId, Server, Text, Ticks, LIBRARY_NAMESPACE};
|
use crate::{
|
||||||
|
ident, ChunkPos, Chunks, Entities, EntityId, Server, Text, Ticks, WorldMeta, LIBRARY_NAMESPACE,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct Clients {
|
pub struct Clients {
|
||||||
sm: SlotMap<Client>,
|
sm: SlotMap<Client>,
|
||||||
|
@ -328,7 +332,7 @@ impl<'a> ClientMut<'a> {
|
||||||
server: &Server,
|
server: &Server,
|
||||||
entities: &Entities,
|
entities: &Entities,
|
||||||
chunks: &Chunks,
|
chunks: &Chunks,
|
||||||
dimension_id: DimensionId,
|
meta: &WorldMeta,
|
||||||
) {
|
) {
|
||||||
self.0.events.clear();
|
self.0.events.clear();
|
||||||
|
|
||||||
|
@ -347,11 +351,13 @@ impl<'a> ClientMut<'a> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let dimension = server.dimension(dimension_id);
|
let dimension = server.dimension(meta.dimension());
|
||||||
|
|
||||||
|
let current_tick = server.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 == server.current_tick() {
|
if self.created_tick == current_tick {
|
||||||
self.send_packet(JoinGame {
|
self.send_packet(JoinGame {
|
||||||
entity_id: 0, // EntityId 0 is reserved for clients.
|
entity_id: 0, // EntityId 0 is reserved for clients.
|
||||||
is_hardcore: false, // TODO
|
is_hardcore: false, // TODO
|
||||||
|
@ -363,7 +369,7 @@ impl<'a> ClientMut<'a> {
|
||||||
.collect(),
|
.collect(),
|
||||||
dimension_codec: Nbt(make_dimension_codec(server)),
|
dimension_codec: Nbt(make_dimension_codec(server)),
|
||||||
dimension: Nbt(to_dimension_registry_item(dimension)),
|
dimension: Nbt(to_dimension_registry_item(dimension)),
|
||||||
dimension_name: ident!("{LIBRARY_NAMESPACE}:dimension_{}", dimension_id.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)),
|
||||||
|
@ -396,7 +402,7 @@ impl<'a> ClientMut<'a> {
|
||||||
// Update view distance fog on the client if necessary.
|
// Update view distance fog on the client if necessary.
|
||||||
if self.0.old_max_view_distance != self.0.new_max_view_distance {
|
if self.0.old_max_view_distance != self.0.new_max_view_distance {
|
||||||
self.0.old_max_view_distance = self.0.new_max_view_distance;
|
self.0.old_max_view_distance = self.0.new_max_view_distance;
|
||||||
if self.0.created_tick != server.current_tick() {
|
if self.0.created_tick != current_tick {
|
||||||
self.send_packet(UpdateViewDistance {
|
self.send_packet(UpdateViewDistance {
|
||||||
view_distance: BoundedInt(VarInt(self.0.new_max_view_distance as i32)),
|
view_distance: BoundedInt(VarInt(self.0.new_max_view_distance as i32)),
|
||||||
})
|
})
|
||||||
|
@ -404,7 +410,7 @@ impl<'a> ClientMut<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if it's time to send another keepalive.
|
// Check if it's time to send another keepalive.
|
||||||
if server.current_tick() % (server.tick_rate() * 8) == 0 {
|
if current_tick % (server.tick_rate() * 8) == 0 {
|
||||||
if self.0.got_keepalive {
|
if self.0.got_keepalive {
|
||||||
let id = rand::random();
|
let id = rand::random();
|
||||||
self.send_packet(KeepAliveClientbound { id });
|
self.send_packet(KeepAliveClientbound { id });
|
||||||
|
@ -451,7 +457,7 @@ impl<'a> ClientMut<'a> {
|
||||||
|
|
||||||
if let Some(chunk) = chunks.get(pos) {
|
if let Some(chunk) = 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() != server.current_tick()
|
&& chunk.created_tick() != current_tick
|
||||||
{
|
{
|
||||||
if let Some(pkt) = chunk.block_change_packet(pos) {
|
if let Some(pkt) = chunk.block_change_packet(pos) {
|
||||||
send_packet(&mut self.0.send, pkt);
|
send_packet(&mut self.0.send, pkt);
|
||||||
|
@ -484,7 +490,6 @@ impl<'a> ClientMut<'a> {
|
||||||
|
|
||||||
// This is done after the chunks are loaded so that the "downloading terrain"
|
// This is done after the chunks are loaded so that the "downloading terrain"
|
||||||
// screen is closed at the appropriate time.
|
// screen is closed at the appropriate time.
|
||||||
|
|
||||||
if self.0.teleported_this_tick {
|
if self.0.teleported_this_tick {
|
||||||
self.0.teleported_this_tick = false;
|
self.0.teleported_this_tick = false;
|
||||||
|
|
||||||
|
@ -504,10 +509,90 @@ impl<'a> ClientMut<'a> {
|
||||||
// longer visible.
|
// longer visible.
|
||||||
self.0.loaded_entities.retain(|&id| {
|
self.0.loaded_entities.retain(|&id| {
|
||||||
if let Some(entity) = entities.get(id) {
|
if let Some(entity) = entities.get(id) {
|
||||||
if self.0.new_position.distance(entity.position()) <= view_dist as f64 * 16.0 {
|
debug_assert!(entity.typ() != EntityType::Marker);
|
||||||
|
if self.0.new_position.distance(entity.position()) <= view_dist as f64 * 16.0
|
||||||
|
&& !entity.flags().type_modified()
|
||||||
|
{
|
||||||
if let Some(meta) = entity.updated_metadata_packet(id) {
|
if let Some(meta) = entity.updated_metadata_packet(id) {
|
||||||
send_packet(&mut self.0.send, meta);
|
send_packet(&mut self.0.send, meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let position_delta = entity.position() - entity.old_position();
|
||||||
|
let needs_teleport = position_delta.map(f64::abs).reduce_partial_max() >= 8.0;
|
||||||
|
let flags = entity.flags();
|
||||||
|
|
||||||
|
if entity.position() != entity.old_position()
|
||||||
|
&& !needs_teleport
|
||||||
|
&& flags.yaw_or_pitch_modified()
|
||||||
|
{
|
||||||
|
send_packet(
|
||||||
|
&mut self.0.send,
|
||||||
|
EntityPositionAndRotation {
|
||||||
|
entity_id: VarInt(id.to_network_id()),
|
||||||
|
delta: (position_delta * 4096.0).as_(),
|
||||||
|
yaw: ByteAngle::from_degrees(entity.yaw()),
|
||||||
|
pitch: ByteAngle::from_degrees(entity.pitch()),
|
||||||
|
on_ground: entity.on_ground(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if entity.position() != entity.old_position() && !needs_teleport {
|
||||||
|
send_packet(
|
||||||
|
&mut self.0.send,
|
||||||
|
EntityPosition {
|
||||||
|
entity_id: VarInt(id.to_network_id()),
|
||||||
|
delta: (position_delta * 4096.0).as_(),
|
||||||
|
on_ground: entity.on_ground(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if flags.yaw_or_pitch_modified() {
|
||||||
|
send_packet(
|
||||||
|
&mut self.0.send,
|
||||||
|
EntityRotation {
|
||||||
|
entity_id: VarInt(id.to_network_id()),
|
||||||
|
yaw: ByteAngle::from_degrees(entity.yaw()),
|
||||||
|
pitch: ByteAngle::from_degrees(entity.pitch()),
|
||||||
|
on_ground: entity.on_ground(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if needs_teleport {
|
||||||
|
send_packet(
|
||||||
|
&mut self.0.send,
|
||||||
|
EntityTeleport {
|
||||||
|
entity_id: VarInt(id.to_network_id()),
|
||||||
|
position: entity.position(),
|
||||||
|
yaw: ByteAngle::from_degrees(entity.yaw()),
|
||||||
|
pitch: ByteAngle::from_degrees(entity.pitch()),
|
||||||
|
on_ground: entity.on_ground(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if flags.velocity_modified() {
|
||||||
|
send_packet(
|
||||||
|
&mut self.0.send,
|
||||||
|
EntityVelocity {
|
||||||
|
entity_id: VarInt(id.to_network_id()),
|
||||||
|
velocity: velocity_to_packet_units(entity.velocity()),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if flags.head_yaw_modified() {
|
||||||
|
send_packet(
|
||||||
|
&mut self.0.send,
|
||||||
|
EntityHeadLook {
|
||||||
|
entity_id: VarInt(id.to_network_id()),
|
||||||
|
head_yaw: ByteAngle::from_degrees(entity.head_yaw()),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -523,6 +608,7 @@ impl<'a> ClientMut<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Spawn new entities within the view distance.
|
// Spawn new entities within the view distance.
|
||||||
|
// TODO: use BVH
|
||||||
for (id, entity) in entities.iter() {
|
for (id, entity) in entities.iter() {
|
||||||
if self.position().distance(entity.position()) <= view_dist as f64 * 16.0
|
if self.position().distance(entity.position()) <= view_dist as f64 * 16.0
|
||||||
&& entity.typ() != EntityType::Marker
|
&& entity.typ() != EntityType::Marker
|
||||||
|
|
|
@ -162,7 +162,10 @@ impl<'a> EntitiesMut<'a> {
|
||||||
for (_, e) in self.iter_mut() {
|
for (_, e) in self.iter_mut() {
|
||||||
e.0.old_position = e.new_position;
|
e.0.old_position = e.new_position;
|
||||||
e.0.meta.clear_modifications();
|
e.0.meta.clear_modifications();
|
||||||
|
|
||||||
|
let on_ground = e.0.flags.on_ground();
|
||||||
e.0.flags = EntityFlags(0);
|
e.0.flags = EntityFlags(0);
|
||||||
|
e.0.flags.set_on_ground(on_ground);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -204,11 +207,12 @@ impl<'a> Deref for EntityMut<'a> {
|
||||||
/// modified.
|
/// modified.
|
||||||
#[bitfield(u8)]
|
#[bitfield(u8)]
|
||||||
pub(crate) struct EntityFlags {
|
pub(crate) struct EntityFlags {
|
||||||
meta_modified: bool,
|
/// When the type of this entity changes.
|
||||||
yaw_or_pitch_modified: bool,
|
pub type_modified: bool,
|
||||||
head_yaw_modified: bool,
|
pub yaw_or_pitch_modified: bool,
|
||||||
head_pitch_modified: bool,
|
pub head_yaw_modified: bool,
|
||||||
velocity_modified: bool,
|
pub velocity_modified: bool,
|
||||||
|
pub on_ground: bool,
|
||||||
#[bits(3)]
|
#[bits(3)]
|
||||||
_pad: u8,
|
_pad: u8,
|
||||||
}
|
}
|
||||||
|
@ -254,16 +258,15 @@ impl Entity {
|
||||||
self.head_yaw
|
self.head_yaw
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the head pitch of this entity (in degrees).
|
|
||||||
pub fn head_pitch(&self) -> f32 {
|
|
||||||
self.head_pitch
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the velocity of this entity in meters per second.
|
/// Gets the velocity of this entity in meters per second.
|
||||||
pub fn velocity(&self) -> Vec3<f32> {
|
pub fn velocity(&self) -> Vec3<f32> {
|
||||||
self.velocity
|
self.velocity
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn on_ground(&self) -> bool {
|
||||||
|
self.flags.on_ground()
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets the metadata packet to send to clients after this entity has been
|
/// Gets the metadata packet to send to clients after this entity has been
|
||||||
/// spawned.
|
/// spawned.
|
||||||
///
|
///
|
||||||
|
@ -361,7 +364,7 @@ impl Entity {
|
||||||
position: self.new_position,
|
position: self.new_position,
|
||||||
yaw: ByteAngle::from_degrees(self.yaw),
|
yaw: ByteAngle::from_degrees(self.yaw),
|
||||||
pitch: ByteAngle::from_degrees(self.pitch),
|
pitch: ByteAngle::from_degrees(self.pitch),
|
||||||
head_pitch: ByteAngle::from_degrees(self.head_pitch),
|
head_yaw: ByteAngle::from_degrees(self.head_yaw),
|
||||||
velocity: velocity_to_packet_units(self.velocity),
|
velocity: velocity_to_packet_units(self.velocity),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -507,7 +510,7 @@ impl Entity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn velocity_to_packet_units(vel: Vec3<f32>) -> Vec3<i16> {
|
pub(crate) fn velocity_to_packet_units(vel: Vec3<f32>) -> Vec3<i16> {
|
||||||
// The saturating cast to i16 is desirable.
|
// The saturating cast to i16 is desirable.
|
||||||
(vel * 400.0).as_()
|
(vel * 400.0).as_()
|
||||||
}
|
}
|
||||||
|
@ -528,8 +531,8 @@ impl<'a> EntityMut<'a> {
|
||||||
/// All metadata of this entity is reset to the default values.
|
/// All metadata of this entity is reset to the default values.
|
||||||
pub fn set_type(&mut self, typ: EntityType) {
|
pub fn set_type(&mut self, typ: EntityType) {
|
||||||
self.0.meta = EntityMeta::new(typ);
|
self.0.meta = EntityMeta::new(typ);
|
||||||
// All metadata is lost, so we must mark it as modified unconditionally.
|
// All metadata is lost so we must mark it as modified unconditionally.
|
||||||
self.0.flags.set_meta_modified(true);
|
self.0.flags.set_type_modified(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the position of this entity in the world it inhabits.
|
/// Sets the position of this entity in the world it inhabits.
|
||||||
|
@ -539,43 +542,40 @@ impl<'a> EntityMut<'a> {
|
||||||
|
|
||||||
/// Sets the yaw of this entity (in degrees).
|
/// Sets the yaw of this entity (in degrees).
|
||||||
pub fn set_yaw(&mut self, yaw: f32) {
|
pub fn set_yaw(&mut self, yaw: f32) {
|
||||||
self.0.yaw = yaw;
|
if self.0.yaw != yaw {
|
||||||
if ByteAngle::from_degrees(self.yaw) != ByteAngle::from_degrees(yaw) {
|
self.0.yaw = yaw;
|
||||||
self.0.flags.set_yaw_or_pitch_modified(true);
|
self.0.flags.set_yaw_or_pitch_modified(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the pitch of this entity (in degrees).
|
/// Sets the pitch of this entity (in degrees).
|
||||||
pub fn set_pitch(&mut self, pitch: f32) {
|
pub fn set_pitch(&mut self, pitch: f32) {
|
||||||
self.0.pitch = pitch;
|
if self.0.pitch != pitch {
|
||||||
if ByteAngle::from_degrees(self.pitch) != ByteAngle::from_degrees(pitch) {
|
self.0.pitch = pitch;
|
||||||
self.0.flags.set_yaw_or_pitch_modified(true);
|
self.0.flags.set_yaw_or_pitch_modified(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the head yaw of this entity (in degrees).
|
/// Sets the head yaw of this entity (in degrees).
|
||||||
pub fn set_head_yaw(&mut self, head_yaw: f32) {
|
pub fn set_head_yaw(&mut self, head_yaw: f32) {
|
||||||
self.0.head_yaw = head_yaw;
|
if self.0.head_yaw != head_yaw {
|
||||||
if ByteAngle::from_degrees(self.head_yaw) != ByteAngle::from_degrees(head_yaw) {
|
self.0.head_yaw = head_yaw;
|
||||||
self.0.flags.set_head_yaw_modified(true);
|
self.0.flags.set_head_yaw_modified(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the head pitch of this entity (in degrees).
|
|
||||||
pub fn set_head_pitch(&mut self, head_pitch: f32) {
|
|
||||||
self.0.head_pitch = head_pitch;
|
|
||||||
if ByteAngle::from_degrees(self.head_pitch) != ByteAngle::from_degrees(head_pitch) {
|
|
||||||
self.0.flags.set_head_pitch_modified(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_velocity(&mut self, velocity: impl Into<Vec3<f32>>) {
|
pub fn set_velocity(&mut self, velocity: impl Into<Vec3<f32>>) {
|
||||||
let new_vel = velocity.into();
|
let new_vel = velocity.into();
|
||||||
self.0.velocity = new_vel;
|
|
||||||
if velocity_to_packet_units(self.velocity) != velocity_to_packet_units(new_vel) {
|
if self.0.velocity != new_vel {
|
||||||
|
self.0.velocity = new_vel;
|
||||||
self.0.flags.set_velocity_modified(true);
|
self.0.flags.set_velocity_modified(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_on_ground(&mut self, on_ground: bool) {
|
||||||
|
self.0.flags.set_on_ground(on_ground);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) enum EntitySpawnPacket {
|
pub(crate) enum EntitySpawnPacket {
|
||||||
|
|
|
@ -36,7 +36,7 @@ pub use identifier::Identifier;
|
||||||
pub use server::{start_server, NewClientData, Server, ShutdownResult};
|
pub use server::{start_server, NewClientData, Server, ShutdownResult};
|
||||||
pub use text::{Text, TextFormat};
|
pub use text::{Text, TextFormat};
|
||||||
pub use uuid::Uuid;
|
pub use uuid::Uuid;
|
||||||
pub use world::{WorldId, WorldMut, WorldRef, Worlds, WorldsMut};
|
pub use world::{WorldId, WorldMeta, WorldMetaMut, WorldMut, WorldRef, Worlds, WorldsMut};
|
||||||
pub use {nbt, uuid, vek};
|
pub use {nbt, uuid, vek};
|
||||||
|
|
||||||
/// The Minecraft protocol version that this library targets.
|
/// The Minecraft protocol version that this library targets.
|
||||||
|
|
|
@ -13,7 +13,7 @@ use num::{One, Zero};
|
||||||
use paste::paste;
|
use paste::paste;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use vek::{Vec2, Vec3};
|
use vek::Vec3;
|
||||||
|
|
||||||
use crate::block_pos::BlockPos;
|
use crate::block_pos::BlockPos;
|
||||||
use crate::byte_angle::ByteAngle;
|
use crate::byte_angle::ByteAngle;
|
||||||
|
@ -469,7 +469,7 @@ pub mod play {
|
||||||
position: Vec3<f64>,
|
position: Vec3<f64>,
|
||||||
yaw: ByteAngle,
|
yaw: ByteAngle,
|
||||||
pitch: ByteAngle,
|
pitch: ByteAngle,
|
||||||
head_pitch: ByteAngle,
|
head_yaw: ByteAngle,
|
||||||
velocity: Vec3<i16>,
|
velocity: Vec3<i16>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -964,6 +964,33 @@ pub mod play {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def_struct! {
|
||||||
|
EntityPosition 0x29 {
|
||||||
|
entity_id: VarInt,
|
||||||
|
delta: Vec3<i16>,
|
||||||
|
on_ground: bool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def_struct! {
|
||||||
|
EntityPositionAndRotation 0x2a {
|
||||||
|
entity_id: VarInt,
|
||||||
|
delta: Vec3<i16>,
|
||||||
|
yaw: ByteAngle,
|
||||||
|
pitch: ByteAngle,
|
||||||
|
on_ground: bool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def_struct! {
|
||||||
|
EntityRotation 0x2b {
|
||||||
|
entity_id: VarInt,
|
||||||
|
yaw: ByteAngle,
|
||||||
|
pitch: ByteAngle,
|
||||||
|
on_ground: bool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def_struct! {
|
def_struct! {
|
||||||
PlayerPositionAndLook 0x38 {
|
PlayerPositionAndLook 0x38 {
|
||||||
position: Vec3<f64>,
|
position: Vec3<f64>,
|
||||||
|
@ -991,6 +1018,13 @@ pub mod play {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def_struct! {
|
||||||
|
EntityHeadLook 0x3e {
|
||||||
|
entity_id: VarInt,
|
||||||
|
head_yaw: ByteAngle,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def_struct! {
|
def_struct! {
|
||||||
MultiBlockChange 0x3f {
|
MultiBlockChange 0x3f {
|
||||||
chunk_section_position: u64,
|
chunk_section_position: u64,
|
||||||
|
@ -1032,6 +1066,13 @@ pub mod play {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def_struct! {
|
||||||
|
EntityVelocity 0x4f {
|
||||||
|
entity_id: VarInt,
|
||||||
|
velocity: Vec3<i16>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def_struct! {
|
def_struct! {
|
||||||
TimeUpdate 0x59 {
|
TimeUpdate 0x59 {
|
||||||
/// The age of the world in 1/20ths of a second.
|
/// The age of the world in 1/20ths of a second.
|
||||||
|
@ -1043,6 +1084,16 @@ pub mod play {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def_struct! {
|
||||||
|
EntityTeleport 0x62 {
|
||||||
|
entity_id: VarInt,
|
||||||
|
position: Vec3<f64>,
|
||||||
|
yaw: ByteAngle,
|
||||||
|
pitch: ByteAngle,
|
||||||
|
on_ground: bool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! def_client_play_packet_enum {
|
macro_rules! def_client_play_packet_enum {
|
||||||
{
|
{
|
||||||
$($packet:ident),* $(,)?
|
$($packet:ident),* $(,)?
|
||||||
|
@ -1115,14 +1166,20 @@ pub mod play {
|
||||||
KeepAliveClientbound,
|
KeepAliveClientbound,
|
||||||
ChunkDataAndUpdateLight,
|
ChunkDataAndUpdateLight,
|
||||||
JoinGame,
|
JoinGame,
|
||||||
|
EntityPosition,
|
||||||
|
EntityPositionAndRotation,
|
||||||
|
EntityRotation,
|
||||||
PlayerPositionAndLook,
|
PlayerPositionAndLook,
|
||||||
DestroyEntities,
|
DestroyEntities,
|
||||||
|
EntityHeadLook,
|
||||||
MultiBlockChange,
|
MultiBlockChange,
|
||||||
HeldItemChangeClientbound,
|
HeldItemChangeClientbound,
|
||||||
UpdateViewPosition,
|
UpdateViewPosition,
|
||||||
UpdateViewDistance,
|
UpdateViewDistance,
|
||||||
SpawnPosition,
|
SpawnPosition,
|
||||||
EntityMetadata,
|
EntityMetadata,
|
||||||
|
EntityVelocity,
|
||||||
|
EntityTeleport,
|
||||||
TimeUpdate,
|
TimeUpdate,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -368,7 +368,12 @@ fn do_update_loop(server: Server, mut worlds: WorldsMut) -> ShutdownResult {
|
||||||
});
|
});
|
||||||
|
|
||||||
world.clients.par_iter_mut().for_each(|(_, mut client)| {
|
world.clients.par_iter_mut().for_each(|(_, mut client)| {
|
||||||
client.update(&server, &world.entities, &world.chunks, world.dimension);
|
client.update(
|
||||||
|
&server,
|
||||||
|
&world.entities,
|
||||||
|
&world.chunks,
|
||||||
|
&world.meta,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
world.entities.update();
|
world.entities.update();
|
||||||
|
|
56
src/world.rs
56
src/world.rs
|
@ -1,15 +1,11 @@
|
||||||
use std::collections::{HashMap, HashSet};
|
|
||||||
use std::iter::FusedIterator;
|
use std::iter::FusedIterator;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
use rayon::iter::ParallelIterator;
|
||||||
|
|
||||||
use crate::chunk::ChunkPos;
|
|
||||||
use crate::config::DimensionId;
|
use crate::config::DimensionId;
|
||||||
use crate::slotmap::{Key, SlotMap};
|
use crate::slotmap::{Key, SlotMap};
|
||||||
use crate::{
|
use crate::{Chunks, ChunksMut, Clients, ClientsMut, Entities, EntitiesMut, Server};
|
||||||
Chunks, ChunksMut, Clients, ClientsMut, Entities, EntitiesMut, Entity, EntityId, Server,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct Worlds {
|
pub struct Worlds {
|
||||||
sm: SlotMap<World>,
|
sm: SlotMap<World>,
|
||||||
|
@ -67,7 +63,10 @@ impl<'a> WorldsMut<'a> {
|
||||||
self.server.clone(),
|
self.server.clone(),
|
||||||
(self.server.dimension(dim).height / 16) as u32,
|
(self.server.dimension(dim).height / 16) as u32,
|
||||||
),
|
),
|
||||||
dimension: dim,
|
meta: WorldMeta {
|
||||||
|
dimension: dim,
|
||||||
|
is_flat: false,
|
||||||
|
},
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,7 +122,7 @@ pub(crate) struct World {
|
||||||
clients: Clients,
|
clients: Clients,
|
||||||
entities: Entities,
|
entities: Entities,
|
||||||
chunks: Chunks,
|
chunks: Chunks,
|
||||||
dimension: DimensionId,
|
meta: WorldMeta,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A bag of immutable references to the components of a world.
|
/// A bag of immutable references to the components of a world.
|
||||||
|
@ -131,7 +130,7 @@ pub struct WorldRef<'a> {
|
||||||
pub clients: &'a Clients,
|
pub clients: &'a Clients,
|
||||||
pub entities: &'a Entities,
|
pub entities: &'a Entities,
|
||||||
pub chunks: &'a Chunks,
|
pub chunks: &'a Chunks,
|
||||||
pub dimension: DimensionId,
|
pub meta: &'a WorldMeta,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> WorldRef<'a> {
|
impl<'a> WorldRef<'a> {
|
||||||
|
@ -140,7 +139,7 @@ impl<'a> WorldRef<'a> {
|
||||||
clients: &w.clients,
|
clients: &w.clients,
|
||||||
entities: &w.entities,
|
entities: &w.entities,
|
||||||
chunks: &w.chunks,
|
chunks: &w.chunks,
|
||||||
dimension: w.dimension,
|
meta: &w.meta,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,7 +149,7 @@ pub struct WorldMut<'a> {
|
||||||
pub clients: ClientsMut<'a>,
|
pub clients: ClientsMut<'a>,
|
||||||
pub entities: EntitiesMut<'a>,
|
pub entities: EntitiesMut<'a>,
|
||||||
pub chunks: ChunksMut<'a>,
|
pub chunks: ChunksMut<'a>,
|
||||||
pub dimension: DimensionId,
|
pub meta: WorldMetaMut<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> WorldMut<'a> {
|
impl<'a> WorldMut<'a> {
|
||||||
|
@ -159,7 +158,7 @@ impl<'a> WorldMut<'a> {
|
||||||
clients: ClientsMut::new(&mut w.clients),
|
clients: ClientsMut::new(&mut w.clients),
|
||||||
entities: EntitiesMut::new(&mut w.entities),
|
entities: EntitiesMut::new(&mut w.entities),
|
||||||
chunks: ChunksMut::new(&mut w.chunks),
|
chunks: ChunksMut::new(&mut w.chunks),
|
||||||
dimension: w.dimension,
|
meta: WorldMetaMut(&mut w.meta),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,7 +167,38 @@ impl<'a> WorldMut<'a> {
|
||||||
clients: &self.clients,
|
clients: &self.clients,
|
||||||
entities: &self.entities,
|
entities: &self.entities,
|
||||||
chunks: &self.chunks,
|
chunks: &self.chunks,
|
||||||
dimension: self.dimension,
|
meta: &self.meta,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct WorldMeta {
|
||||||
|
dimension: DimensionId,
|
||||||
|
is_flat: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WorldMeta {
|
||||||
|
pub fn dimension(&self) -> DimensionId {
|
||||||
|
self.dimension
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_flat(&self) -> bool {
|
||||||
|
self.is_flat
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct WorldMetaMut<'a>(&'a mut WorldMeta);
|
||||||
|
|
||||||
|
impl<'a> Deref for WorldMetaMut<'a> {
|
||||||
|
type Target = WorldMeta;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> WorldMetaMut<'a> {
|
||||||
|
pub fn set_flat(&mut self, flat: bool) {
|
||||||
|
self.0.is_flat = flat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue