Get entity movement working

This commit is contained in:
Ryan 2022-05-17 02:58:43 -07:00
parent 8da32d0b8b
commit 1570c95ac8
9 changed files with 362 additions and 70 deletions

View file

@ -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) {

119
examples/cow_sphere.rs Normal file
View 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);
}
}
}
}

View file

@ -418,7 +418,8 @@ fn encode_paletted_container(
let palette_idx = palette
.iter()
.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);
}

View file

@ -8,25 +8,29 @@ use uuid::Uuid;
use vek::Vec3;
use crate::block_pos::BlockPos;
use crate::byte_angle::ByteAngle;
use crate::config::{
Biome, BiomeGrassColorModifier, BiomePrecipitation, Dimension, DimensionEffects, DimensionId,
};
use crate::entity::EntityType;
use crate::entity::{velocity_to_packet_units, EntityType};
pub use crate::packets::play::GameMode;
use crate::packets::play::{
Biome as BiomeRegistryBiome, BiomeAdditionsSound, BiomeEffects, BiomeMoodSound, BiomeMusic,
BiomeParticle, BiomeParticleOptions, BiomeProperty, BiomeRegistry, ChangeGameState,
ChangeGameStateReason, ClientPlayPacket, DestroyEntities, DimensionCodec, DimensionType,
DimensionTypeRegistry, DimensionTypeRegistryEntry, Disconnect, JoinGame, KeepAliveClientbound,
PlayerPositionAndLook, PlayerPositionAndLookFlags, ServerPlayPacket, SpawnPosition,
UnloadChunk, UpdateViewDistance, UpdateViewPosition,
DimensionTypeRegistry, DimensionTypeRegistryEntry, Disconnect, EntityHeadLook, EntityPosition,
EntityPositionAndRotation, EntityRotation, EntityTeleport, EntityVelocity, JoinGame,
KeepAliveClientbound, PlayerPositionAndLook, PlayerPositionAndLookFlags, ServerPlayPacket,
SpawnPosition, UnloadChunk, UpdateViewDistance, UpdateViewPosition,
};
use crate::protocol::{BoundedInt, Nbt};
use crate::server::ServerPacketChannels;
use crate::slotmap::{Key, SlotMap};
use crate::util::{chunks_in_view_distance, is_chunk_in_view_distance};
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 {
sm: SlotMap<Client>,
@ -328,7 +332,7 @@ impl<'a> ClientMut<'a> {
server: &Server,
entities: &Entities,
chunks: &Chunks,
dimension_id: DimensionId,
meta: &WorldMeta,
) {
self.0.events.clear();
@ -347,11 +351,13 @@ impl<'a> ClientMut<'a> {
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
// 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 {
entity_id: 0, // EntityId 0 is reserved for clients.
is_hardcore: false, // TODO
@ -363,7 +369,7 @@ impl<'a> ClientMut<'a> {
.collect(),
dimension_codec: Nbt(make_dimension_codec(server)),
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,
max_players: VarInt(0),
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.
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;
if self.0.created_tick != server.current_tick() {
if self.0.created_tick != current_tick {
self.send_packet(UpdateViewDistance {
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.
if server.current_tick() % (server.tick_rate() * 8) == 0 {
if current_tick % (server.tick_rate() * 8) == 0 {
if self.0.got_keepalive {
let id = rand::random();
self.send_packet(KeepAliveClientbound { id });
@ -451,7 +457,7 @@ impl<'a> ClientMut<'a> {
if let Some(chunk) = chunks.get(pos) {
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) {
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"
// screen is closed at the appropriate time.
if self.0.teleported_this_tick {
self.0.teleported_this_tick = false;
@ -504,10 +509,90 @@ impl<'a> ClientMut<'a> {
// longer visible.
self.0.loaded_entities.retain(|&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) {
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;
}
}
@ -523,6 +608,7 @@ impl<'a> ClientMut<'a> {
}
// Spawn new entities within the view distance.
// TODO: use BVH
for (id, entity) in entities.iter() {
if self.position().distance(entity.position()) <= view_dist as f64 * 16.0
&& entity.typ() != EntityType::Marker

View file

@ -162,7 +162,10 @@ impl<'a> EntitiesMut<'a> {
for (_, e) in self.iter_mut() {
e.0.old_position = e.new_position;
e.0.meta.clear_modifications();
let on_ground = e.0.flags.on_ground();
e.0.flags = EntityFlags(0);
e.0.flags.set_on_ground(on_ground);
}
}
}
@ -204,11 +207,12 @@ impl<'a> Deref for EntityMut<'a> {
/// modified.
#[bitfield(u8)]
pub(crate) struct EntityFlags {
meta_modified: bool,
yaw_or_pitch_modified: bool,
head_yaw_modified: bool,
head_pitch_modified: bool,
velocity_modified: bool,
/// When the type of this entity changes.
pub type_modified: bool,
pub yaw_or_pitch_modified: bool,
pub head_yaw_modified: bool,
pub velocity_modified: bool,
pub on_ground: bool,
#[bits(3)]
_pad: u8,
}
@ -254,16 +258,15 @@ impl Entity {
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.
pub fn velocity(&self) -> Vec3<f32> {
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
/// spawned.
///
@ -361,7 +364,7 @@ impl Entity {
position: self.new_position,
yaw: ByteAngle::from_degrees(self.yaw),
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),
}))
}
@ -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.
(vel * 400.0).as_()
}
@ -528,8 +531,8 @@ impl<'a> EntityMut<'a> {
/// All metadata of this entity is reset to the default values.
pub fn set_type(&mut self, typ: EntityType) {
self.0.meta = EntityMeta::new(typ);
// All metadata is lost, so we must mark it as modified unconditionally.
self.0.flags.set_meta_modified(true);
// All metadata is lost so we must mark it as modified unconditionally.
self.0.flags.set_type_modified(true);
}
/// 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).
pub fn set_yaw(&mut self, yaw: f32) {
self.0.yaw = yaw;
if ByteAngle::from_degrees(self.yaw) != ByteAngle::from_degrees(yaw) {
if self.0.yaw != yaw {
self.0.yaw = yaw;
self.0.flags.set_yaw_or_pitch_modified(true);
}
}
/// Sets the pitch of this entity (in degrees).
pub fn set_pitch(&mut self, pitch: f32) {
self.0.pitch = pitch;
if ByteAngle::from_degrees(self.pitch) != ByteAngle::from_degrees(pitch) {
if self.0.pitch != pitch {
self.0.pitch = pitch;
self.0.flags.set_yaw_or_pitch_modified(true);
}
}
/// Sets the head yaw of this entity (in degrees).
pub fn set_head_yaw(&mut self, head_yaw: f32) {
self.0.head_yaw = head_yaw;
if ByteAngle::from_degrees(self.head_yaw) != ByteAngle::from_degrees(head_yaw) {
if self.0.head_yaw != head_yaw {
self.0.head_yaw = head_yaw;
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>>) {
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);
}
}
pub fn set_on_ground(&mut self, on_ground: bool) {
self.0.flags.set_on_ground(on_ground);
}
}
pub(crate) enum EntitySpawnPacket {

View file

@ -36,7 +36,7 @@ pub use identifier::Identifier;
pub use server::{start_server, NewClientData, Server, ShutdownResult};
pub use text::{Text, TextFormat};
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};
/// The Minecraft protocol version that this library targets.

View file

@ -13,7 +13,7 @@ use num::{One, Zero};
use paste::paste;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use vek::{Vec2, Vec3};
use vek::Vec3;
use crate::block_pos::BlockPos;
use crate::byte_angle::ByteAngle;
@ -469,7 +469,7 @@ pub mod play {
position: Vec3<f64>,
yaw: ByteAngle,
pitch: ByteAngle,
head_pitch: ByteAngle,
head_yaw: ByteAngle,
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! {
PlayerPositionAndLook 0x38 {
position: Vec3<f64>,
@ -991,6 +1018,13 @@ pub mod play {
}
}
def_struct! {
EntityHeadLook 0x3e {
entity_id: VarInt,
head_yaw: ByteAngle,
}
}
def_struct! {
MultiBlockChange 0x3f {
chunk_section_position: u64,
@ -1032,6 +1066,13 @@ pub mod play {
}
}
def_struct! {
EntityVelocity 0x4f {
entity_id: VarInt,
velocity: Vec3<i16>,
}
}
def_struct! {
TimeUpdate 0x59 {
/// 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 {
{
$($packet:ident),* $(,)?
@ -1115,14 +1166,20 @@ pub mod play {
KeepAliveClientbound,
ChunkDataAndUpdateLight,
JoinGame,
EntityPosition,
EntityPositionAndRotation,
EntityRotation,
PlayerPositionAndLook,
DestroyEntities,
EntityHeadLook,
MultiBlockChange,
HeldItemChangeClientbound,
UpdateViewPosition,
UpdateViewDistance,
SpawnPosition,
EntityMetadata,
EntityVelocity,
EntityTeleport,
TimeUpdate,
}

View file

@ -368,7 +368,12 @@ fn do_update_loop(server: Server, mut worlds: WorldsMut) -> ShutdownResult {
});
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();

View file

@ -1,15 +1,11 @@
use std::collections::{HashMap, HashSet};
use std::iter::FusedIterator;
use std::ops::Deref;
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use rayon::iter::ParallelIterator;
use crate::chunk::ChunkPos;
use crate::config::DimensionId;
use crate::slotmap::{Key, SlotMap};
use crate::{
Chunks, ChunksMut, Clients, ClientsMut, Entities, EntitiesMut, Entity, EntityId, Server,
};
use crate::{Chunks, ChunksMut, Clients, ClientsMut, Entities, EntitiesMut, Server};
pub struct Worlds {
sm: SlotMap<World>,
@ -67,7 +63,10 @@ impl<'a> WorldsMut<'a> {
self.server.clone(),
(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,
entities: Entities,
chunks: Chunks,
dimension: DimensionId,
meta: WorldMeta,
}
/// A bag of immutable references to the components of a world.
@ -131,7 +130,7 @@ pub struct WorldRef<'a> {
pub clients: &'a Clients,
pub entities: &'a Entities,
pub chunks: &'a Chunks,
pub dimension: DimensionId,
pub meta: &'a WorldMeta,
}
impl<'a> WorldRef<'a> {
@ -140,7 +139,7 @@ impl<'a> WorldRef<'a> {
clients: &w.clients,
entities: &w.entities,
chunks: &w.chunks,
dimension: w.dimension,
meta: &w.meta,
}
}
}
@ -150,7 +149,7 @@ pub struct WorldMut<'a> {
pub clients: ClientsMut<'a>,
pub entities: EntitiesMut<'a>,
pub chunks: ChunksMut<'a>,
pub dimension: DimensionId,
pub meta: WorldMetaMut<'a>,
}
impl<'a> WorldMut<'a> {
@ -159,7 +158,7 @@ impl<'a> WorldMut<'a> {
clients: ClientsMut::new(&mut w.clients),
entities: EntitiesMut::new(&mut w.entities),
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,
entities: &self.entities,
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;
}
}