Clean up client and fix names

This commit is contained in:
Ryan 2022-08-05 12:36:34 -07:00
parent 6b5e795f81
commit 49d63a39c0
18 changed files with 1312 additions and 774 deletions

View file

@ -44,11 +44,11 @@ pub fn build() -> anyhow::Result<TokenStream> {
Ok(quote! { Ok(quote! {
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum Event { pub enum EntityEvent {
#(#event_variants,)* #(#event_variants,)*
} }
impl Event { impl EntityEvent {
pub(crate) fn status_or_animation(self) -> StatusOrAnimation { pub(crate) fn status_or_animation(self) -> StatusOrAnimation {
match self { match self {
#(#status_arms)* #(#status_arms)*

View file

@ -16,11 +16,13 @@ pub fn main() -> anyhow::Result<()> {
(block::build, "block.rs"), (block::build, "block.rs"),
]; ];
for (_, file_name) in generators {
println!("cargo:rerun-if-changed=extracted/{file_name}");
}
let out_dir = env::var_os("OUT_DIR").context("can't get OUT_DIR env var")?; let out_dir = env::var_os("OUT_DIR").context("can't get OUT_DIR env var")?;
for (g, file_name) in generators { for (g, file_name) in generators {
println!("cargo:rerun-if-changed=extracted/{file_name}");
let path = Path::new(&out_dir).join(file_name); let path = Path::new(&out_dir).join(file_name);
let code = g()?.to_string(); let code = g()?.to_string();
fs::write(&path, &code)?; fs::write(&path, &code)?;

View file

@ -3,15 +3,15 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use log::LevelFilter; use log::LevelFilter;
use valence::block::{BlockPos, BlockState}; use valence::block::{BlockPos, BlockState};
use valence::client::{ClientId, Event, GameMode, Hand, InteractWithEntityKind}; use valence::client::{Client, ClientEvent, ClientId, GameMode, Hand, InteractWithEntityKind};
use valence::config::{Config, ServerListPing}; use valence::config::{Config, ServerListPing};
use valence::dimension::DimensionId; use valence::dimension::DimensionId;
use valence::entity::data::Pose; use valence::entity::types::Pose;
use valence::entity::{TrackedData, EntityId, EntityKind, Event as EntityEvent}; use valence::entity::{Entity, EntityEvent, EntityId, EntityKind, TrackedData};
use valence::server::{Server, SharedServer, ShutdownResult}; use valence::server::{Server, SharedServer, ShutdownResult};
use valence::text::{Color, TextFormat}; use valence::text::{Color, TextFormat};
use valence::{async_trait, Ticks}; use valence::{async_trait, Ticks};
use vek::Vec3; use vek::{Vec2, Vec3};
pub fn main() -> ShutdownResult { pub fn main() -> ShutdownResult {
env_logger::Builder::new() env_logger::Builder::new()
@ -119,7 +119,7 @@ impl Config for Game {
let current_tick = server.shared.current_tick(); let current_tick = server.shared.current_tick();
server.clients.retain(|client_id, client| { server.clients.retain(|client_id, client| {
if client.created_tick() == current_tick { if client.created_this_tick() {
if self if self
.player_count .player_count
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| { .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| {
@ -131,6 +131,23 @@ impl Config for Game {
return false; return false;
} }
let (player_id, player) = match server.entities.create_with_uuid(
EntityKind::Player,
client.uuid(),
EntityState::default(),
) {
Some(e) => e,
None => {
client.disconnect("Conflicting UUID");
return false;
}
};
player.state.client = client_id;
client.state.player = player_id;
client.state.extra_knockback = true;
client.spawn(world_id); client.spawn(world_id);
client.set_game_mode(GameMode::Survival); client.set_game_mode(GameMode::Survival);
client.teleport( client.teleport(
@ -152,17 +169,6 @@ impl Config for Game {
None, None,
); );
let (player_id, player) = server
.entities
.create_with_uuid(EntityKind::Player, client.uuid(), EntityState::default())
.unwrap();
client.state.player = player_id;
client.state.extra_knockback = true;
player.state.client = client_id;
player.state.last_attack_time = 0;
client.send_message("Welcome to the arena.".italic()); client.send_message("Welcome to the arena.".italic());
if self.player_count.load(Ordering::SeqCst) <= 1 { if self.player_count.load(Ordering::SeqCst) <= 1 {
client.send_message("Have another player join the game with you.".italic()); client.send_message("Have another player join the game with you.".italic());
@ -176,41 +182,6 @@ impl Config for Game {
return false; return false;
} }
while let Some(event) = client.pop_event() {
match event {
Event::StartSprinting => {
client.state.extra_knockback = true;
}
Event::InteractWithEntity {
id,
kind: InteractWithEntityKind::Attack,
..
} => {
if let Some(target) = server.entities.get_mut(id) {
if !target.state.attacked
&& current_tick - target.state.last_attack_time >= 10
&& id != client.state.player
{
target.state.attacked = true;
target.state.attacker_pos = client.position();
target.state.extra_knockback = client.state.extra_knockback;
target.state.last_attack_time = current_tick;
client.state.extra_knockback = false;
}
}
}
Event::ArmSwing(hand) => {
let player = server.entities.get_mut(client.state.player).unwrap();
match hand {
Hand::Main => player.trigger_event(EntityEvent::SwingMainHand),
Hand::Off => player.trigger_event(EntityEvent::SwingOffHand),
}
}
_ => (),
}
}
if client.position().y <= 0.0 { if client.position().y <= 0.0 {
client.teleport( client.teleport(
[ [
@ -223,53 +194,189 @@ impl Config for Game {
); );
} }
let player = server.entities.get_mut(client.state.player).unwrap(); loop {
let player = server
.entities
.get_mut(client.state.player)
.expect("missing player entity");
player.set_world(client.world()); match client_event_boilerplate(client, player) {
player.set_position(client.position()); Some(ClientEvent::StartSprinting) => {
player.set_yaw(client.yaw()); client.state.extra_knockback = true;
player.set_head_yaw(client.yaw());
player.set_pitch(client.pitch());
player.set_on_ground(client.on_ground());
if let TrackedData::Player(player) = player.view_mut() {
if client.is_sneaking() {
player.set_pose(Pose::Sneaking);
} else {
player.set_pose(Pose::Standing);
} }
Some(ClientEvent::InteractWithEntity {
id,
kind: InteractWithEntityKind::Attack,
..
}) => {
if let Some(target) = server.entities.get_mut(id) {
if !target.state.attacked
&& current_tick - target.state.last_attack_time >= 10
&& id != client.state.player
{
target.state.attacked = true;
target.state.attacker_pos = client.position();
target.state.extra_knockback = client.state.extra_knockback;
target.state.last_attack_time = current_tick;
player.set_sprinting(client.is_sprinting()); client.state.extra_knockback = false;
}
}
}
Some(_) => {}
None => break,
}
} }
true true
}); });
for (_, e) in server.entities.iter_mut() { for (_, entity) in server.entities.iter_mut() {
if e.state.attacked { if entity.state.attacked {
e.state.attacked = false; entity.state.attacked = false;
let victim = server.clients.get_mut(e.state.client).unwrap(); if let Some(victim) = server.clients.get_mut(entity.state.client) {
let victim_pos = Vec2::new(victim.position().x, victim.position().z);
let attacker_pos =
Vec2::new(entity.state.attacker_pos.x, entity.state.attacker_pos.z);
let mut vel = (victim.position() - e.state.attacker_pos).normalized(); let dir = (victim_pos - attacker_pos).normalized();
let knockback_xz = if e.state.extra_knockback { 18.0 } else { 8.0 }; let knockback_xz = if entity.state.extra_knockback {
let knockback_y = if e.state.extra_knockback { 18.0
} else {
8.0
};
let knockback_y = if entity.state.extra_knockback {
8.432 8.432
} else { } else {
6.432 6.432
}; };
vel.x *= knockback_xz; let vel = Vec3::new(dir.x * knockback_xz, knockback_y, dir.y * knockback_xz);
vel.y = knockback_y; victim.set_velocity(vel.as_());
vel.z *= knockback_xz;
victim.set_velocity(victim.velocity() / 2.0 + vel.as_()); entity.push_event(EntityEvent::DamageFromGenericSource);
entity.push_event(EntityEvent::Damage);
victim.push_entity_event(EntityEvent::DamageFromGenericSource);
victim.push_entity_event(EntityEvent::Damage);
}
}
}
}
}
e.trigger_event(EntityEvent::DamageFromGenericSource); fn client_event_boilerplate(
e.trigger_event(EntityEvent::Damage); client: &mut Client<Game>,
victim.trigger_entity_event(EntityEvent::DamageFromGenericSource); entity: &mut Entity<Game>,
victim.trigger_entity_event(EntityEvent::Damage); ) -> Option<ClientEvent> {
let event = client.pop_event()?;
match &event {
ClientEvent::ChatMessage { .. } => {}
ClientEvent::SettingsChanged {
view_distance,
main_hand,
displayed_skin_parts,
..
} => {
client.set_view_distance(*view_distance);
let player = client.player_mut();
player.set_cape(displayed_skin_parts.cape());
player.set_jacket(displayed_skin_parts.jacket());
player.set_left_sleeve(displayed_skin_parts.left_sleeve());
player.set_right_sleeve(displayed_skin_parts.right_sleeve());
player.set_left_pants_leg(displayed_skin_parts.left_pants_leg());
player.set_right_pants_leg(displayed_skin_parts.right_pants_leg());
player.set_hat(displayed_skin_parts.hat());
player.set_main_arm(*main_hand as u8);
if let TrackedData::Player(player) = entity.data_mut() {
player.set_cape(displayed_skin_parts.cape());
player.set_jacket(displayed_skin_parts.jacket());
player.set_left_sleeve(displayed_skin_parts.left_sleeve());
player.set_right_sleeve(displayed_skin_parts.right_sleeve());
player.set_left_pants_leg(displayed_skin_parts.left_pants_leg());
player.set_right_pants_leg(displayed_skin_parts.right_pants_leg());
player.set_hat(displayed_skin_parts.hat());
player.set_main_arm(*main_hand as u8);
}
}
ClientEvent::MovePosition {
position,
on_ground,
} => {
entity.set_position(*position);
entity.set_on_ground(*on_ground);
}
ClientEvent::MovePositionAndRotation {
position,
yaw,
pitch,
on_ground,
} => {
entity.set_position(*position);
entity.set_yaw(*yaw);
entity.set_head_yaw(*yaw);
entity.set_pitch(*pitch);
entity.set_on_ground(*on_ground);
}
ClientEvent::MoveRotation {
yaw,
pitch,
on_ground,
} => {
entity.set_yaw(*yaw);
entity.set_head_yaw(*yaw);
entity.set_pitch(*pitch);
entity.set_on_ground(*on_ground);
}
ClientEvent::MoveOnGround { on_ground } => {
entity.set_on_ground(*on_ground);
}
ClientEvent::MoveVehicle { .. } => {}
ClientEvent::StartSneaking => {
if let TrackedData::Player(player) = entity.data_mut() {
if player.get_pose() == Pose::Standing {
player.set_pose(Pose::Sneaking);
} }
} }
} }
ClientEvent::StopSneaking => {
if let TrackedData::Player(player) = entity.data_mut() {
if player.get_pose() == Pose::Sneaking {
player.set_pose(Pose::Standing);
}
}
}
ClientEvent::StartSprinting => {
if let TrackedData::Player(player) = entity.data_mut() {
player.set_sprinting(true);
}
}
ClientEvent::StopSprinting => {
if let TrackedData::Player(player) = entity.data_mut() {
player.set_sprinting(false);
}
}
ClientEvent::StartJumpWithHorse { .. } => {}
ClientEvent::StopJumpWithHorse => {}
ClientEvent::LeaveBed => {}
ClientEvent::OpenHorseInventory => {}
ClientEvent::StartFlyingWithElytra => {}
ClientEvent::ArmSwing(hand) => {
entity.push_event(match hand {
Hand::Main => EntityEvent::SwingMainHand,
Hand::Off => EntityEvent::SwingOffHand,
});
}
ClientEvent::InteractWithEntity { .. } => {}
ClientEvent::SteerBoat { .. } => {}
ClientEvent::Digging { .. } => {}
}
entity.set_world(client.world());
Some(event)
} }

View file

@ -7,11 +7,11 @@ use num::Integer;
use rayon::iter::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator}; use rayon::iter::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator};
use valence::biome::Biome; use valence::biome::Biome;
use valence::block::BlockState; use valence::block::BlockState;
use valence::client::{Event, Hand}; use valence::client::{Client, ClientEvent, Hand};
use valence::config::{Config, ServerListPing}; use valence::config::{Config, ServerListPing};
use valence::dimension::{Dimension, DimensionId}; use valence::dimension::{Dimension, DimensionId};
use valence::entity::data::Pose; use valence::entity::types::Pose;
use valence::entity::{TrackedData, EntityId, EntityKind, Event as EntityEvent}; use valence::entity::{Entity, EntityEvent, EntityId, EntityKind, TrackedData};
use valence::server::{Server, SharedServer, ShutdownResult}; use valence::server::{Server, SharedServer, ShutdownResult};
use valence::text::{Color, TextFormat}; use valence::text::{Color, TextFormat};
use valence::{async_trait, ident}; use valence::{async_trait, ident};
@ -42,12 +42,6 @@ struct ServerState {
board_buf: Box<[bool]>, board_buf: Box<[bool]>,
} }
#[derive(Default)]
struct ClientState {
/// The client's player entity.
player: EntityId,
}
const MAX_PLAYERS: usize = 10; const MAX_PLAYERS: usize = 10;
const SIZE_X: usize = 100; const SIZE_X: usize = 100;
@ -57,7 +51,7 @@ const BOARD_Y: i32 = 50;
#[async_trait] #[async_trait]
impl Config for Game { impl Config for Game {
type ChunkState = (); type ChunkState = ();
type ClientState = ClientState; type ClientState = EntityId;
type EntityState = (); type EntityState = ();
type ServerState = ServerState; type ServerState = ServerState;
type WorldState = (); type WorldState = ();
@ -121,7 +115,7 @@ impl Config for Game {
]; ];
server.clients.retain(|_, client| { server.clients.retain(|_, client| {
if client.created_tick() == server.shared.current_tick() { if client.created_this_tick() {
if self if self
.player_count .player_count
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| { .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| {
@ -133,6 +127,17 @@ impl Config for Game {
return false; return false;
} }
match server
.entities
.create_with_uuid(EntityKind::Player, client.uuid(), ())
{
Some((id, _)) => client.state = id,
None => {
client.disconnect("Conflicting UUID");
return false;
}
}
client.spawn(world_id); client.spawn(world_id);
client.teleport(spawn_pos, 0.0, 0.0); client.teleport(spawn_pos, 0.0, 0.0);
@ -145,63 +150,33 @@ impl Config for Game {
None, None,
); );
client.state.player = server
.entities
.create_with_uuid(EntityKind::Player, client.uuid(), ())
.unwrap()
.0;
client.send_message("Welcome to Conway's game of life in Minecraft!".italic()); 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()); client.send_message("Hold the left mouse button to bring blocks to life.".italic());
} }
if client.is_disconnected() { if client.is_disconnected() {
self.player_count.fetch_sub(1, Ordering::SeqCst); self.player_count.fetch_sub(1, Ordering::SeqCst);
server.entities.delete(client.state.player); server.entities.delete(client.state);
world.meta.player_list_mut().remove(client.uuid()); world.meta.player_list_mut().remove(client.uuid());
return false; return false;
} }
let player = server.entities.get_mut(client.state.player).unwrap(); let player = server.entities.get_mut(client.state).unwrap();
if client.position().y <= 0.0 { if client.position().y <= 0.0 {
client.teleport(spawn_pos, client.yaw(), client.pitch()); client.teleport(spawn_pos, client.yaw(), client.pitch());
} }
while let Some(event) = client.pop_event() { while let Some(event) = client_event_boilerplate(client, player) {
match event { if let ClientEvent::Digging { position, .. } = event {
Event::Digging { position, .. } => {
if (0..SIZE_X as i32).contains(&position.x) if (0..SIZE_X as i32).contains(&position.x)
&& (0..SIZE_Z as i32).contains(&position.z) && (0..SIZE_Z as i32).contains(&position.z)
&& position.y == BOARD_Y && position.y == BOARD_Y
{ {
server.state.board server.state.board[position.x as usize + position.z as usize * SIZE_X] =
[position.x as usize + position.z as usize * SIZE_X] = true; true;
} }
} }
Event::ArmSwing(hand) => match hand {
Hand::Main => player.trigger_event(EntityEvent::SwingMainHand),
Hand::Off => player.trigger_event(EntityEvent::SwingOffHand),
},
_ => {}
}
}
player.set_world(client.world());
player.set_position(client.position());
player.set_yaw(client.yaw());
player.set_head_yaw(client.yaw());
player.set_pitch(client.pitch());
player.set_on_ground(client.on_ground());
if let TrackedData::Player(player) = player.view_mut() {
if client.is_sneaking() {
player.set_pose(Pose::Sneaking);
} else {
player.set_pose(Pose::Standing);
}
player.set_sprinting(client.is_sprinting());
} }
true true
@ -269,3 +244,119 @@ impl Config for Game {
} }
} }
} }
fn client_event_boilerplate(
client: &mut Client<Game>,
entity: &mut Entity<Game>,
) -> Option<ClientEvent> {
let event = client.pop_event()?;
match &event {
ClientEvent::ChatMessage { .. } => {}
ClientEvent::SettingsChanged {
view_distance,
main_hand,
displayed_skin_parts,
..
} => {
client.set_view_distance(*view_distance);
let player = client.player_mut();
player.set_cape(displayed_skin_parts.cape());
player.set_jacket(displayed_skin_parts.jacket());
player.set_left_sleeve(displayed_skin_parts.left_sleeve());
player.set_right_sleeve(displayed_skin_parts.right_sleeve());
player.set_left_pants_leg(displayed_skin_parts.left_pants_leg());
player.set_right_pants_leg(displayed_skin_parts.right_pants_leg());
player.set_hat(displayed_skin_parts.hat());
player.set_main_arm(*main_hand as u8);
if let TrackedData::Player(player) = entity.data_mut() {
player.set_cape(displayed_skin_parts.cape());
player.set_jacket(displayed_skin_parts.jacket());
player.set_left_sleeve(displayed_skin_parts.left_sleeve());
player.set_right_sleeve(displayed_skin_parts.right_sleeve());
player.set_left_pants_leg(displayed_skin_parts.left_pants_leg());
player.set_right_pants_leg(displayed_skin_parts.right_pants_leg());
player.set_hat(displayed_skin_parts.hat());
player.set_main_arm(*main_hand as u8);
}
}
ClientEvent::MovePosition {
position,
on_ground,
} => {
entity.set_position(*position);
entity.set_on_ground(*on_ground);
}
ClientEvent::MovePositionAndRotation {
position,
yaw,
pitch,
on_ground,
} => {
entity.set_position(*position);
entity.set_yaw(*yaw);
entity.set_head_yaw(*yaw);
entity.set_pitch(*pitch);
entity.set_on_ground(*on_ground);
}
ClientEvent::MoveRotation {
yaw,
pitch,
on_ground,
} => {
entity.set_yaw(*yaw);
entity.set_head_yaw(*yaw);
entity.set_pitch(*pitch);
entity.set_on_ground(*on_ground);
}
ClientEvent::MoveOnGround { on_ground } => {
entity.set_on_ground(*on_ground);
}
ClientEvent::MoveVehicle { .. } => {}
ClientEvent::StartSneaking => {
if let TrackedData::Player(player) = entity.data_mut() {
if player.get_pose() == Pose::Standing {
player.set_pose(Pose::Sneaking);
}
}
}
ClientEvent::StopSneaking => {
if let TrackedData::Player(player) = entity.data_mut() {
if player.get_pose() == Pose::Sneaking {
player.set_pose(Pose::Standing);
}
}
}
ClientEvent::StartSprinting => {
if let TrackedData::Player(player) = entity.data_mut() {
player.set_sprinting(true);
}
}
ClientEvent::StopSprinting => {
if let TrackedData::Player(player) = entity.data_mut() {
player.set_sprinting(false);
}
}
ClientEvent::StartJumpWithHorse { .. } => {}
ClientEvent::StopJumpWithHorse => {}
ClientEvent::LeaveBed => {}
ClientEvent::OpenHorseInventory => {}
ClientEvent::StartFlyingWithElytra => {}
ClientEvent::ArmSwing(hand) => {
entity.push_event(match hand {
Hand::Main => EntityEvent::SwingMainHand,
Hand::Off => EntityEvent::SwingOffHand,
});
}
ClientEvent::InteractWithEntity { .. } => {}
ClientEvent::SteerBoat { .. } => {}
ClientEvent::Digging { .. } => {}
}
entity.set_world(client.world());
Some(event)
}

View file

@ -5,10 +5,11 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use log::LevelFilter; use log::LevelFilter;
use valence::async_trait; use valence::async_trait;
use valence::block::{BlockPos, BlockState}; use valence::block::{BlockPos, BlockState};
use valence::client::GameMode; use valence::client::{Client, ClientEvent, GameMode, Hand};
use valence::config::{Config, ServerListPing}; use valence::config::{Config, ServerListPing};
use valence::dimension::DimensionId; use valence::dimension::DimensionId;
use valence::entity::{EntityId, EntityKind}; use valence::entity::types::Pose;
use valence::entity::{Entity, EntityEvent, EntityId, EntityKind, TrackedData};
use valence::server::{Server, SharedServer, ShutdownResult}; use valence::server::{Server, SharedServer, ShutdownResult};
use valence::text::{Color, TextFormat}; use valence::text::{Color, TextFormat};
use valence::util::to_yaw_and_pitch; use valence::util::to_yaw_and_pitch;
@ -43,7 +44,7 @@ const SPAWN_POS: BlockPos = BlockPos::new(0, 100, -25);
#[async_trait] #[async_trait]
impl Config for Game { impl Config for Game {
type ChunkState = (); type ChunkState = ();
type ClientState = (); type ClientState = EntityId;
type EntityState = (); type EntityState = ();
type ServerState = ServerState; type ServerState = ServerState;
type WorldState = (); type WorldState = ();
@ -92,10 +93,10 @@ impl Config for Game {
} }
fn update(&self, server: &mut Server<Self>) { fn update(&self, server: &mut Server<Self>) {
let (world_id, world) = server.worlds.iter_mut().next().unwrap(); let (world_id, world) = server.worlds.iter_mut().next().expect("missing world");
server.clients.retain(|_, client| { server.clients.retain(|_, client| {
if client.created_tick() == server.shared.current_tick() { if client.created_this_tick() {
if self if self
.player_count .player_count
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| { .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| {
@ -107,6 +108,17 @@ impl Config for Game {
return false; return false;
} }
match server
.entities
.create_with_uuid(EntityKind::Player, client.uuid(), ())
{
Some((id, _)) => client.state = id,
None => {
client.disconnect("Conflicting UUID");
return false;
}
}
client.spawn(world_id); client.spawn(world_id);
client.set_game_mode(GameMode::Creative); client.set_game_mode(GameMode::Creative);
client.teleport( client.teleport(
@ -132,9 +144,18 @@ impl Config for Game {
if client.is_disconnected() { if client.is_disconnected() {
self.player_count.fetch_sub(1, Ordering::SeqCst); self.player_count.fetch_sub(1, Ordering::SeqCst);
world.meta.player_list_mut().remove(client.uuid()); world.meta.player_list_mut().remove(client.uuid());
server.entities.delete(client.state);
return false; return false;
} }
let entity = server
.entities
.get_mut(client.state)
.expect("missing player entity");
while client_event_boilerplate(client, entity).is_some() {}
true true
}); });
@ -153,7 +174,7 @@ impl Config for Game {
.map(|c| c.1.position()) .map(|c| c.1.position())
.unwrap_or_default(); .unwrap_or_default();
// TODO: hardcoded eye pos. // TODO: remove hardcoded eye pos.
let eye_pos = Vec3::new(player_pos.x, player_pos.y + 1.6, player_pos.z); let eye_pos = Vec3::new(player_pos.x, player_pos.y + 1.6, player_pos.z);
for (cow_id, p) in server for (cow_id, p) in server
@ -194,3 +215,119 @@ fn fibonacci_spiral(n: usize) -> impl Iterator<Item = Vec3<f64>> {
Vec3::new(theta.cos() * phi.sin(), theta.sin() * phi.sin(), phi.cos()) Vec3::new(theta.cos() * phi.sin(), theta.sin() * phi.sin(), phi.cos())
}) })
} }
fn client_event_boilerplate(
client: &mut Client<Game>,
entity: &mut Entity<Game>,
) -> Option<ClientEvent> {
let event = client.pop_event()?;
match &event {
ClientEvent::ChatMessage { .. } => {}
ClientEvent::SettingsChanged {
view_distance,
main_hand,
displayed_skin_parts,
..
} => {
client.set_view_distance(*view_distance);
let player = client.player_mut();
player.set_cape(displayed_skin_parts.cape());
player.set_jacket(displayed_skin_parts.jacket());
player.set_left_sleeve(displayed_skin_parts.left_sleeve());
player.set_right_sleeve(displayed_skin_parts.right_sleeve());
player.set_left_pants_leg(displayed_skin_parts.left_pants_leg());
player.set_right_pants_leg(displayed_skin_parts.right_pants_leg());
player.set_hat(displayed_skin_parts.hat());
player.set_main_arm(*main_hand as u8);
if let TrackedData::Player(player) = entity.data_mut() {
player.set_cape(displayed_skin_parts.cape());
player.set_jacket(displayed_skin_parts.jacket());
player.set_left_sleeve(displayed_skin_parts.left_sleeve());
player.set_right_sleeve(displayed_skin_parts.right_sleeve());
player.set_left_pants_leg(displayed_skin_parts.left_pants_leg());
player.set_right_pants_leg(displayed_skin_parts.right_pants_leg());
player.set_hat(displayed_skin_parts.hat());
player.set_main_arm(*main_hand as u8);
}
}
ClientEvent::MovePosition {
position,
on_ground,
} => {
entity.set_position(*position);
entity.set_on_ground(*on_ground);
}
ClientEvent::MovePositionAndRotation {
position,
yaw,
pitch,
on_ground,
} => {
entity.set_position(*position);
entity.set_yaw(*yaw);
entity.set_head_yaw(*yaw);
entity.set_pitch(*pitch);
entity.set_on_ground(*on_ground);
}
ClientEvent::MoveRotation {
yaw,
pitch,
on_ground,
} => {
entity.set_yaw(*yaw);
entity.set_head_yaw(*yaw);
entity.set_pitch(*pitch);
entity.set_on_ground(*on_ground);
}
ClientEvent::MoveOnGround { on_ground } => {
entity.set_on_ground(*on_ground);
}
ClientEvent::MoveVehicle { .. } => {}
ClientEvent::StartSneaking => {
if let TrackedData::Player(player) = entity.data_mut() {
if player.get_pose() == Pose::Standing {
player.set_pose(Pose::Sneaking);
}
}
}
ClientEvent::StopSneaking => {
if let TrackedData::Player(player) = entity.data_mut() {
if player.get_pose() == Pose::Sneaking {
player.set_pose(Pose::Standing);
}
}
}
ClientEvent::StartSprinting => {
if let TrackedData::Player(player) = entity.data_mut() {
player.set_sprinting(true);
}
}
ClientEvent::StopSprinting => {
if let TrackedData::Player(player) = entity.data_mut() {
player.set_sprinting(false);
}
}
ClientEvent::StartJumpWithHorse { .. } => {}
ClientEvent::StopJumpWithHorse => {}
ClientEvent::LeaveBed => {}
ClientEvent::OpenHorseInventory => {}
ClientEvent::StartFlyingWithElytra => {}
ClientEvent::ArmSwing(hand) => {
entity.push_event(match hand {
Hand::Main => EntityEvent::SwingMainHand,
Hand::Off => EntityEvent::SwingOffHand,
});
}
ClientEvent::InteractWithEntity { .. } => {}
ClientEvent::SteerBoat { .. } => {}
ClientEvent::Digging { .. } => {}
}
entity.set_world(client.world());
Some(event)
}

View file

@ -4,10 +4,11 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use log::LevelFilter; use log::LevelFilter;
use valence::async_trait; use valence::async_trait;
use valence::block::{BlockPos, BlockState}; use valence::block::{BlockPos, BlockState};
use valence::client::GameMode; use valence::client::{Client, ClientEvent, GameMode, Hand};
use valence::config::{Config, ServerListPing}; use valence::config::{Config, ServerListPing};
use valence::dimension::DimensionId; use valence::dimension::DimensionId;
use valence::entity::{TrackedData, EntityKind}; use valence::entity::types::Pose;
use valence::entity::{Entity, EntityEvent, EntityId, EntityKind, TrackedData};
use valence::server::{Server, SharedServer, ShutdownResult}; use valence::server::{Server, SharedServer, ShutdownResult};
use valence::spatial_index::RaycastHit; use valence::spatial_index::RaycastHit;
use valence::text::{Color, TextFormat}; use valence::text::{Color, TextFormat};
@ -41,7 +42,7 @@ const PLAYER_EYE_HEIGHT: f64 = 1.6;
#[async_trait] #[async_trait]
impl Config for Game { impl Config for Game {
type ChunkState = (); type ChunkState = ();
type ClientState = (); type ClientState = EntityId;
/// `true` for entities that have been intersected with. /// `true` for entities that have been intersected with.
type EntityState = bool; type EntityState = bool;
type ServerState = (); type ServerState = ();
@ -99,7 +100,7 @@ impl Config for Game {
let (world_id, world) = server.worlds.iter_mut().next().unwrap(); let (world_id, world) = server.worlds.iter_mut().next().unwrap();
server.clients.retain(|_, client| { server.clients.retain(|_, client| {
if client.created_tick() == server.shared.current_tick() { if client.created_this_tick() {
if self if self
.player_count .player_count
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| { .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| {
@ -111,6 +112,17 @@ impl Config for Game {
return false; return false;
} }
match server
.entities
.create_with_uuid(EntityKind::Player, client.uuid(), false)
{
Some((id, _)) => client.state = id,
None => {
client.disconnect("Conflicting UUID");
return false;
}
}
client.spawn(world_id); client.spawn(world_id);
client.set_game_mode(GameMode::Creative); client.set_game_mode(GameMode::Creative);
client.teleport( client.teleport(
@ -142,6 +154,8 @@ impl Config for Game {
if client.is_disconnected() { if client.is_disconnected() {
self.player_count.fetch_sub(1, Ordering::SeqCst); self.player_count.fetch_sub(1, Ordering::SeqCst);
world.meta.player_list_mut().remove(client.uuid()); world.meta.player_list_mut().remove(client.uuid());
server.entities.delete(client.state);
return false; return false;
} }
@ -162,12 +176,16 @@ impl Config for Game {
} }
} }
while client_event_boilerplate(client, server.entities.get_mut(client.state).unwrap())
.is_some()
{}
true true
}); });
for (_, e) in server.entities.iter_mut() { for (_, e) in server.entities.iter_mut() {
let intersected = e.state; let intersected = e.state;
if let TrackedData::Sheep(sheep) = &mut e.view_mut() { if let TrackedData::Sheep(sheep) = &mut e.data_mut() {
if intersected { if intersected {
sheep.set_color(5); sheep.set_color(5);
} else { } else {
@ -178,3 +196,119 @@ impl Config for Game {
} }
} }
} }
fn client_event_boilerplate(
client: &mut Client<Game>,
entity: &mut Entity<Game>,
) -> Option<ClientEvent> {
let event = client.pop_event()?;
match &event {
ClientEvent::ChatMessage { .. } => {}
ClientEvent::SettingsChanged {
view_distance,
main_hand,
displayed_skin_parts,
..
} => {
client.set_view_distance(*view_distance);
let player = client.player_mut();
player.set_cape(displayed_skin_parts.cape());
player.set_jacket(displayed_skin_parts.jacket());
player.set_left_sleeve(displayed_skin_parts.left_sleeve());
player.set_right_sleeve(displayed_skin_parts.right_sleeve());
player.set_left_pants_leg(displayed_skin_parts.left_pants_leg());
player.set_right_pants_leg(displayed_skin_parts.right_pants_leg());
player.set_hat(displayed_skin_parts.hat());
player.set_main_arm(*main_hand as u8);
if let TrackedData::Player(player) = entity.data_mut() {
player.set_cape(displayed_skin_parts.cape());
player.set_jacket(displayed_skin_parts.jacket());
player.set_left_sleeve(displayed_skin_parts.left_sleeve());
player.set_right_sleeve(displayed_skin_parts.right_sleeve());
player.set_left_pants_leg(displayed_skin_parts.left_pants_leg());
player.set_right_pants_leg(displayed_skin_parts.right_pants_leg());
player.set_hat(displayed_skin_parts.hat());
player.set_main_arm(*main_hand as u8);
}
}
ClientEvent::MovePosition {
position,
on_ground,
} => {
entity.set_position(*position);
entity.set_on_ground(*on_ground);
}
ClientEvent::MovePositionAndRotation {
position,
yaw,
pitch,
on_ground,
} => {
entity.set_position(*position);
entity.set_yaw(*yaw);
entity.set_head_yaw(*yaw);
entity.set_pitch(*pitch);
entity.set_on_ground(*on_ground);
}
ClientEvent::MoveRotation {
yaw,
pitch,
on_ground,
} => {
entity.set_yaw(*yaw);
entity.set_head_yaw(*yaw);
entity.set_pitch(*pitch);
entity.set_on_ground(*on_ground);
}
ClientEvent::MoveOnGround { on_ground } => {
entity.set_on_ground(*on_ground);
}
ClientEvent::MoveVehicle { .. } => {}
ClientEvent::StartSneaking => {
if let TrackedData::Player(player) = entity.data_mut() {
if player.get_pose() == Pose::Standing {
player.set_pose(Pose::Sneaking);
}
}
}
ClientEvent::StopSneaking => {
if let TrackedData::Player(player) = entity.data_mut() {
if player.get_pose() == Pose::Sneaking {
player.set_pose(Pose::Standing);
}
}
}
ClientEvent::StartSprinting => {
if let TrackedData::Player(player) = entity.data_mut() {
player.set_sprinting(true);
}
}
ClientEvent::StopSprinting => {
if let TrackedData::Player(player) = entity.data_mut() {
player.set_sprinting(false);
}
}
ClientEvent::StartJumpWithHorse { .. } => {}
ClientEvent::StopJumpWithHorse => {}
ClientEvent::LeaveBed => {}
ClientEvent::OpenHorseInventory => {}
ClientEvent::StartFlyingWithElytra => {}
ClientEvent::ArmSwing(hand) => {
entity.push_event(match hand {
Hand::Main => EntityEvent::SwingMainHand,
Hand::Off => EntityEvent::SwingOffHand,
});
}
ClientEvent::InteractWithEntity { .. } => {}
ClientEvent::SteerBoat { .. } => {}
ClientEvent::Digging { .. } => {}
}
entity.set_world(client.world());
Some(event)
}

View file

@ -8,9 +8,11 @@ use rayon::iter::ParallelIterator;
use valence::async_trait; use valence::async_trait;
use valence::block::{BlockState, PropName, PropValue}; use valence::block::{BlockState, PropName, PropValue};
use valence::chunk::ChunkPos; use valence::chunk::ChunkPos;
use valence::client::GameMode; use valence::client::{Client, ClientEvent, GameMode, Hand};
use valence::config::{Config, ServerListPing}; use valence::config::{Config, ServerListPing};
use valence::dimension::DimensionId; use valence::dimension::DimensionId;
use valence::entity::types::Pose;
use valence::entity::{Entity, EntityEvent, EntityId, EntityKind, TrackedData};
use valence::server::{Server, SharedServer, ShutdownResult}; use valence::server::{Server, SharedServer, ShutdownResult};
use valence::text::{Color, TextFormat}; use valence::text::{Color, TextFormat};
use valence::util::chunks_in_view_distance; use valence::util::chunks_in_view_distance;
@ -51,7 +53,7 @@ const MAX_PLAYERS: usize = 10;
#[async_trait] #[async_trait]
impl Config for Game { impl Config for Game {
type ChunkState = (); type ChunkState = ();
type ClientState = (); type ClientState = EntityId;
type EntityState = (); type EntityState = ();
type ServerState = (); type ServerState = ();
type WorldState = (); type WorldState = ();
@ -90,7 +92,7 @@ impl Config for Game {
let mut chunks_to_unload = HashSet::<_>::from_iter(world.chunks.iter().map(|t| t.0)); let mut chunks_to_unload = HashSet::<_>::from_iter(world.chunks.iter().map(|t| t.0));
server.clients.retain(|_, client| { server.clients.retain(|_, client| {
if client.created_tick() == server.shared.current_tick() { if client.created_this_tick() {
if self if self
.player_count .player_count
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| { .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| {
@ -102,9 +104,19 @@ impl Config for Game {
return false; return false;
} }
match server
.entities
.create_with_uuid(EntityKind::Player, client.uuid(), ())
{
Some((id, _)) => client.state = id,
None => {
client.disconnect("Conflicting UUID");
return false;
}
}
client.spawn(world_id); client.spawn(world_id);
client.set_game_mode(GameMode::Creative); client.set_game_mode(GameMode::Creative);
client.set_max_view_distance(32);
client.teleport([0.0, 200.0, 0.0], 0.0, 0.0); client.teleport([0.0, 200.0, 0.0], 0.0, 0.0);
world.meta.player_list_mut().insert( world.meta.player_list_mut().insert(
@ -117,16 +129,20 @@ impl Config for Game {
); );
client.send_message("Welcome to the terrain example!".italic()); client.send_message("Welcome to the terrain example!".italic());
client
.send_message("Explore this infinite procedurally generated terrain.".italic());
} }
if client.is_disconnected() { if client.is_disconnected() {
self.player_count.fetch_sub(1, Ordering::SeqCst); self.player_count.fetch_sub(1, Ordering::SeqCst);
world.meta.player_list_mut().remove(client.uuid()); world.meta.player_list_mut().remove(client.uuid());
server.entities.delete(client.state);
return false; return false;
} }
if let Some(entity) = server.entities.get_mut(client.state) {
while client_event_boilerplate(client, entity).is_some() {}
}
let dist = client.view_distance(); let dist = client.view_distance();
let p = client.position(); let p = client.position();
@ -321,3 +337,119 @@ fn fbm(noise: &SuperSimplex, p: [f64; 3], octaves: u32, lacunarity: f64, persist
fn noise01(noise: &SuperSimplex, xyz: [f64; 3]) -> f64 { fn noise01(noise: &SuperSimplex, xyz: [f64; 3]) -> f64 {
(noise.get(xyz) + 1.0) / 2.0 (noise.get(xyz) + 1.0) / 2.0
} }
fn client_event_boilerplate(
client: &mut Client<Game>,
entity: &mut Entity<Game>,
) -> Option<ClientEvent> {
let event = client.pop_event()?;
match &event {
ClientEvent::ChatMessage { .. } => {}
ClientEvent::SettingsChanged {
view_distance,
main_hand,
displayed_skin_parts,
..
} => {
client.set_view_distance(*view_distance);
let player = client.player_mut();
player.set_cape(displayed_skin_parts.cape());
player.set_jacket(displayed_skin_parts.jacket());
player.set_left_sleeve(displayed_skin_parts.left_sleeve());
player.set_right_sleeve(displayed_skin_parts.right_sleeve());
player.set_left_pants_leg(displayed_skin_parts.left_pants_leg());
player.set_right_pants_leg(displayed_skin_parts.right_pants_leg());
player.set_hat(displayed_skin_parts.hat());
player.set_main_arm(*main_hand as u8);
if let TrackedData::Player(player) = entity.data_mut() {
player.set_cape(displayed_skin_parts.cape());
player.set_jacket(displayed_skin_parts.jacket());
player.set_left_sleeve(displayed_skin_parts.left_sleeve());
player.set_right_sleeve(displayed_skin_parts.right_sleeve());
player.set_left_pants_leg(displayed_skin_parts.left_pants_leg());
player.set_right_pants_leg(displayed_skin_parts.right_pants_leg());
player.set_hat(displayed_skin_parts.hat());
player.set_main_arm(*main_hand as u8);
}
}
ClientEvent::MovePosition {
position,
on_ground,
} => {
entity.set_position(*position);
entity.set_on_ground(*on_ground);
}
ClientEvent::MovePositionAndRotation {
position,
yaw,
pitch,
on_ground,
} => {
entity.set_position(*position);
entity.set_yaw(*yaw);
entity.set_head_yaw(*yaw);
entity.set_pitch(*pitch);
entity.set_on_ground(*on_ground);
}
ClientEvent::MoveRotation {
yaw,
pitch,
on_ground,
} => {
entity.set_yaw(*yaw);
entity.set_head_yaw(*yaw);
entity.set_pitch(*pitch);
entity.set_on_ground(*on_ground);
}
ClientEvent::MoveOnGround { on_ground } => {
entity.set_on_ground(*on_ground);
}
ClientEvent::MoveVehicle { .. } => {}
ClientEvent::StartSneaking => {
if let TrackedData::Player(player) = entity.data_mut() {
if player.get_pose() == Pose::Standing {
player.set_pose(Pose::Sneaking);
}
}
}
ClientEvent::StopSneaking => {
if let TrackedData::Player(player) = entity.data_mut() {
if player.get_pose() == Pose::Sneaking {
player.set_pose(Pose::Standing);
}
}
}
ClientEvent::StartSprinting => {
if let TrackedData::Player(player) = entity.data_mut() {
player.set_sprinting(true);
}
}
ClientEvent::StopSprinting => {
if let TrackedData::Player(player) = entity.data_mut() {
player.set_sprinting(false);
}
}
ClientEvent::StartJumpWithHorse { .. } => {}
ClientEvent::StopJumpWithHorse => {}
ClientEvent::LeaveBed => {}
ClientEvent::OpenHorseInventory => {}
ClientEvent::StartFlyingWithElytra => {}
ClientEvent::ArmSwing(hand) => {
entity.push_event(match hand {
Hand::Main => EntityEvent::SwingMainHand,
Hand::Off => EntityEvent::SwingOffHand,
});
}
ClientEvent::InteractWithEntity { .. } => {}
ClientEvent::SteerBoat { .. } => {}
ClientEvent::Digging { .. } => {}
}
entity.set_world(client.world());
Some(event)
}

View file

@ -13,13 +13,13 @@ use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
use tokio::net::{TcpListener, TcpStream}; use tokio::net::{TcpListener, TcpStream};
use tokio::sync::Semaphore; use tokio::sync::Semaphore;
use valence::protocol::codec::Decoder; use valence::protocol::codec::Decoder;
use valence::protocol::packets::handshake::{Handshake, HandshakeNextState}; use valence::protocol::packets::c2s::handshake::{Handshake, HandshakeNextState};
use valence::protocol::packets::login::c2s::{EncryptionResponse, LoginStart}; use valence::protocol::packets::c2s::login::{EncryptionResponse, LoginStart};
use valence::protocol::packets::login::s2c::{LoginSuccess, S2cLoginPacket};
use valence::protocol::packets::c2s::play::C2sPlayPacket; use valence::protocol::packets::c2s::play::C2sPlayPacket;
use valence::protocol::packets::c2s::status::{QueryPing, QueryRequest};
use valence::protocol::packets::s2c::login::{LoginSuccess, S2cLoginPacket};
use valence::protocol::packets::s2c::play::S2cPlayPacket; use valence::protocol::packets::s2c::play::S2cPlayPacket;
use valence::protocol::packets::status::c2s::{QueryPing, QueryRequest}; use valence::protocol::packets::s2c::status::{QueryPong, QueryResponse};
use valence::protocol::packets::status::s2c::{QueryPong, QueryResponse};
use valence::protocol::packets::{DecodePacket, EncodePacket}; use valence::protocol::packets::{DecodePacket, EncodePacket};
use valence::protocol::{Encode, VarInt}; use valence::protocol::{Encode, VarInt};

View file

@ -4,7 +4,7 @@ use std::collections::{HashSet, VecDeque};
use std::iter::FusedIterator; use std::iter::FusedIterator;
use std::time::Duration; use std::time::Duration;
use bitfield_struct::bitfield; pub use bitfield_struct::bitfield;
pub use event::*; pub use event::*;
use flume::{Receiver, Sender, TrySendError}; use flume::{Receiver, Sender, TrySendError};
use rayon::iter::ParallelIterator; use rayon::iter::ParallelIterator;
@ -16,10 +16,9 @@ use crate::block_pos::BlockPos;
use crate::chunk_pos::ChunkPos; use crate::chunk_pos::ChunkPos;
use crate::config::Config; use crate::config::Config;
use crate::dimension::DimensionId; use crate::dimension::DimensionId;
use crate::entity::kinds::Player; use crate::entity::data::Player;
use crate::entity::{ use crate::entity::{
velocity_to_packet_units, Entities, EntityId, EntityKind, Event as EntityEvent, velocity_to_packet_units, Entities, EntityEvent, EntityId, EntityKind, StatusOrAnimation,
StatusOrAnimation,
}; };
use crate::player_textures::SignedPlayerTextures; use crate::player_textures::SignedPlayerTextures;
use crate::protocol_inner::packets::c2s::play::{ use crate::protocol_inner::packets::c2s::play::{
@ -42,9 +41,9 @@ use crate::slotmap::{Key, SlotMap};
use crate::text::Text; use crate::text::Text;
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::world::{WorldId, Worlds}; use crate::world::{WorldId, Worlds};
use crate::{ident, Ticks, LIBRARY_NAMESPACE, STANDARD_TPS}; use crate::{ident, LIBRARY_NAMESPACE};
/// Contains the [`Event`] enum and related data types. /// Contains the [`ClientEvent`] enum and related data types.
mod event; mod event;
/// A container for all [`Client`]s on a [`Server`](crate::server::Server). /// A container for all [`Client`]s on a [`Server`](crate::server::Server).
@ -62,8 +61,8 @@ impl<C: Config> Clients<C> {
} }
pub(crate) fn insert(&mut self, client: Client<C>) -> (ClientId, &mut Client<C>) { pub(crate) fn insert(&mut self, client: Client<C>) -> (ClientId, &mut Client<C>) {
let (id, client) = self.sm.insert(client); let (k, client) = self.sm.insert(client);
(ClientId(id), client) (ClientId(k), client)
} }
/// Removes a client from the server. /// Removes a client from the server.
@ -74,7 +73,7 @@ impl<C: Config> Clients<C> {
self.sm.remove(client.0).is_some() self.sm.remove(client.0).is_some()
} }
/// Deletes all clients from the server (as if by [`Self::delete`]) for /// Deletes all clients from the server for
/// which `f` returns `true`. /// which `f` returns `true`.
/// ///
/// All clients are visited in an unspecified order. /// All clients are visited in an unspecified order.
@ -146,7 +145,7 @@ impl ClientId {
/// Represents a remote connection to a client after successfully logging in. /// Represents a remote connection to a client after successfully logging in.
/// ///
/// Much like an [`Entity`], clients posess a location, rotation, and UUID. /// Much like an [`Entity`], clients possess a location, rotation, and UUID.
/// However, clients are handled separately from entities and are partially /// However, clients are handled separately from entities and are partially
/// managed by the library. /// managed by the library.
/// ///
@ -154,30 +153,28 @@ impl ClientId {
/// cannot break blocks, hurt entities, or see other clients. Interactions with /// cannot break blocks, hurt entities, or see other clients. Interactions with
/// the server must be handled explicitly with [`Self::pop_event`]. /// the server must be handled explicitly with [`Self::pop_event`].
/// ///
/// Additionally, clients posess [`Player`] entity data which is only visible to /// Additionally, clients possess [`Player`] entity data which is only visible
/// themselves. This can be accessed with [`Self::player`] and /// to themselves. This can be accessed with [`Self::player`] and
/// [`Self::player_mut`]. /// [`Self::player_mut`].
/// ///
/// # The Difference Between a "Client" and a "Player" /// # The Difference Between a "Client" and a "Player"
/// ///
/// Normally in Minecraft, players and clients are one and the same. Players are /// Normally in Minecraft, players and clients are one and the same. Players are
/// simply a special type of entity which is backed by a remote connection. /// simply a subtype of the entity base class backed by a remote connection.
/// ///
/// In Valence however, clients and players have been decoupled. This separation /// In Valence however, clients and players are decoupled. This separation
/// was done primarily to enable multithreaded client updates. /// allows for greater flexibility and parallelism.
pub struct Client<C: Config> { pub struct Client<C: Config> {
/// Custom state. /// Custom state.
pub state: C::ClientState, pub state: C::ClientState,
/// Setting this to `None` disconnects the client. /// Setting this to `None` disconnects the client.
send: SendOpt, send: SendOpt,
recv: Receiver<C2sPlayPacket>, recv: Receiver<C2sPlayPacket>,
/// The tick this client was created.
created_tick: Ticks,
uuid: Uuid, uuid: Uuid,
username: String, username: String,
textures: Option<SignedPlayerTextures>, textures: Option<SignedPlayerTextures>,
world: WorldId, world: WorldId,
new_position: Vec3<f64>, position: Vec3<f64>,
old_position: Vec3<f64>, old_position: Vec3<f64>,
/// Measured in m/s. /// Measured in m/s.
velocity: Vec3<f32>, velocity: Vec3<f32>,
@ -185,6 +182,7 @@ pub struct Client<C: Config> {
yaw: f32, yaw: f32,
/// Measured in degrees /// Measured in degrees
pitch: f32, pitch: f32,
view_distance: u8,
/// Counts up as teleports are made. /// Counts up as teleports are made.
teleport_id_counter: u32, teleport_id_counter: u32,
/// The number of pending client teleports that have yet to receive a /// The number of pending client teleports that have yet to receive a
@ -194,11 +192,9 @@ pub struct Client<C: Config> {
spawn_position: BlockPos, spawn_position: BlockPos,
spawn_position_yaw: f32, spawn_position_yaw: f32,
death_location: Option<(DimensionId, BlockPos)>, death_location: Option<(DimensionId, BlockPos)>,
events: VecDeque<Event>, events: VecDeque<ClientEvent>,
/// The ID of the last keepalive sent. /// The ID of the last keepalive sent.
last_keepalive_id: i64, last_keepalive_id: i64,
new_max_view_distance: u8,
old_max_view_distance: u8,
/// Entities that were visible to this client at the end of the last tick. /// Entities that were visible to this client at the end of the last tick.
/// This is used to determine what entity create/destroy packets should be /// This is used to determine what entity create/destroy packets should be
/// sent. /// sent.
@ -212,21 +208,15 @@ pub struct Client<C: Config> {
msgs_to_send: Vec<Text>, msgs_to_send: Vec<Text>,
attack_speed: f64, attack_speed: f64,
movement_speed: f64, movement_speed: f64,
flags: ClientFlags, bits: ClientBits,
/// The data for the client's own player entity. /// The data for the client's own player entity.
player_data: Player, player_data: Player,
entity_events: Vec<EntityEvent>, entity_events: Vec<EntityEvent>,
} }
#[bitfield(u16)] #[bitfield(u16)]
pub(crate) struct ClientFlags { struct ClientBits {
spawn: bool, spawn: bool,
sneaking: bool,
sprinting: bool,
jumping_with_horse: bool,
on_ground: bool,
/// If any of position, yaw, or pitch were modified by the
/// user this tick.
teleported_this_tick: bool, teleported_this_tick: bool,
/// If spawn_position or spawn_position_yaw were modified this tick. /// If spawn_position or spawn_position_yaw were modified this tick.
modified_spawn_position: bool, modified_spawn_position: bool,
@ -236,33 +226,34 @@ pub(crate) struct ClientFlags {
attack_speed_modified: bool, attack_speed_modified: bool,
movement_speed_modified: bool, movement_speed_modified: bool,
velocity_modified: bool, velocity_modified: bool,
#[bits(4)] created_this_tick: bool,
view_distance_modified: bool,
#[bits(6)]
_pad: u8, _pad: u8,
} }
impl<C: Config> Client<C> { impl<C: Config> Client<C> {
pub(crate) fn new( pub(crate) fn new(
packet_channels: C2sPacketChannels, packet_channels: C2sPacketChannels,
server: &SharedServer<C>,
ncd: NewClientData, ncd: NewClientData,
data: C::ClientState, state: C::ClientState,
) -> Self { ) -> Self {
let (send, recv) = packet_channels; let (send, recv) = packet_channels;
Self { Self {
state: data, state,
send: Some(send), send: Some(send),
recv, recv,
created_tick: server.current_tick(),
uuid: ncd.uuid, uuid: ncd.uuid,
username: ncd.username, username: ncd.username,
textures: ncd.textures, textures: ncd.textures,
world: WorldId::default(), world: WorldId::default(),
new_position: Vec3::default(), position: Vec3::default(),
old_position: Vec3::default(), old_position: Vec3::default(),
velocity: Vec3::default(), velocity: Vec3::default(),
yaw: 0.0, yaw: 0.0,
pitch: 0.0, pitch: 0.0,
view_distance: 8,
teleport_id_counter: 0, teleport_id_counter: 0,
pending_teleports: 0, pending_teleports: 0,
spawn_position: BlockPos::default(), spawn_position: BlockPos::default(),
@ -270,8 +261,6 @@ impl<C: Config> Client<C> {
death_location: None, death_location: None,
events: VecDeque::new(), events: VecDeque::new(),
last_keepalive_id: 0, last_keepalive_id: 0,
new_max_view_distance: 16,
old_max_view_distance: 0,
loaded_entities: HashSet::new(), loaded_entities: HashSet::new(),
loaded_chunks: HashSet::new(), loaded_chunks: HashSet::new(),
new_game_mode: GameMode::Survival, new_game_mode: GameMode::Survival,
@ -281,17 +270,18 @@ impl<C: Config> Client<C> {
msgs_to_send: Vec::new(), msgs_to_send: Vec::new(),
attack_speed: 4.0, attack_speed: 4.0,
movement_speed: 0.7, movement_speed: 0.7,
flags: ClientFlags::new() bits: ClientBits::new()
.with_modified_spawn_position(true) .with_modified_spawn_position(true)
.with_got_keepalive(true), .with_got_keepalive(true)
.with_created_this_tick(true),
player_data: Player::new(), player_data: Player::new(),
entity_events: Vec::new(), entity_events: Vec::new(),
} }
} }
/// Gets the tick that this client was created. /// If the client joined the game this tick.
pub fn created_tick(&self) -> Ticks { pub fn created_this_tick(&self) -> bool {
self.created_tick self.bits.created_this_tick()
} }
/// Gets the client's UUID. /// Gets the client's UUID.
@ -304,16 +294,6 @@ impl<C: Config> Client<C> {
&self.username &self.username
} }
/// Returns the sneaking state of this client.
pub fn is_sneaking(&self) -> bool {
self.flags.sneaking()
}
/// Returns the sprinting state of this client.
pub fn is_sprinting(&self) -> bool {
self.flags.sprinting()
}
/// Gets the player textures of this client. If the client does not have /// Gets the player textures of this client. If the client does not have
/// a skin, then `None` is returned. /// a skin, then `None` is returned.
pub fn textures(&self) -> Option<&SignedPlayerTextures> { pub fn textures(&self) -> Option<&SignedPlayerTextures> {
@ -325,13 +305,14 @@ impl<C: Config> Client<C> {
self.world self.world
} }
/// Changes the world this client is located in. /// Changes the world this client is located in and respawns the client.
/// This can be used to respawn the client after death.
/// ///
/// The given [`WorldId`] must be valid. Otherwise, the client is /// The given [`WorldId`] must be valid. Otherwise, the client is
/// disconnected. /// disconnected.
pub fn spawn(&mut self, world: WorldId) { pub fn spawn(&mut self, world: WorldId) {
self.world = world; self.world = world;
self.flags.set_spawn(true); self.bits.set_spawn(true);
} }
/// Sends a system message to the player which is visible in the chat. /// Sends a system message to the player which is visible in the chat.
@ -344,7 +325,7 @@ impl<C: Config> Client<C> {
/// Gets the absolute position of this client in the world it is located /// Gets the absolute position of this client in the world it is located
/// in. /// in.
pub fn position(&self) -> Vec3<f64> { pub fn position(&self) -> Vec3<f64> {
self.new_position self.position
} }
/// Changes the position and rotation of this client in the world it is /// Changes the position and rotation of this client in the world it is
@ -352,31 +333,14 @@ impl<C: Config> Client<C> {
/// ///
/// If you want to change the client's world, use [`Self::spawn`]. /// If you want to change the client's world, use [`Self::spawn`].
pub fn teleport(&mut self, pos: impl Into<Vec3<f64>>, yaw: f32, pitch: f32) { pub fn teleport(&mut self, pos: impl Into<Vec3<f64>>, yaw: f32, pitch: f32) {
self.new_position = pos.into(); self.position = pos.into();
self.yaw = yaw; self.yaw = yaw;
self.pitch = pitch; self.pitch = pitch;
self.velocity = Vec3::default();
if !self.flags.teleported_this_tick() { self.bits.set_teleported_this_tick(true);
self.flags.set_teleported_this_tick(true);
self.pending_teleports = match self.pending_teleports.checked_add(1) {
Some(n) => n,
None => {
log::warn!("too many pending teleports for {}", self.username());
self.disconnect_no_reason();
return;
}
};
self.teleport_id_counter = self.teleport_id_counter.wrapping_add(1);
}
} }
/// Gets the velocity of this client in m/s. /// Gets the most recently set velocity of this client in m/s.
///
/// The velocity of a client is derived from their current and previous
/// position.
pub fn velocity(&self) -> Vec3<f32> { pub fn velocity(&self) -> Vec3<f32> {
self.velocity self.velocity
} }
@ -384,7 +348,7 @@ impl<C: Config> Client<C> {
/// Sets the client's velocity in m/s. /// Sets the client's velocity in m/s.
pub fn set_velocity(&mut self, velocity: impl Into<Vec3<f32>>) { pub fn set_velocity(&mut self, velocity: impl Into<Vec3<f32>>) {
self.velocity = velocity.into(); self.velocity = velocity.into();
self.flags.set_velocity_modified(true); self.bits.set_velocity_modified(true);
} }
/// Gets this client's yaw. /// Gets this client's yaw.
@ -410,12 +374,13 @@ impl<C: Config> Client<C> {
if pos != self.spawn_position || yaw_degrees != self.spawn_position_yaw { if pos != self.spawn_position || yaw_degrees != self.spawn_position_yaw {
self.spawn_position = pos; self.spawn_position = pos;
self.spawn_position_yaw = yaw_degrees; self.spawn_position_yaw = yaw_degrees;
self.flags.set_modified_spawn_position(true); self.bits.set_modified_spawn_position(true);
} }
} }
/// Gets the last death location of this client. The client will see /// Gets the last death location of this client. The client will see
/// `minecraft:recovery_compass` items point at the returned position. /// `minecraft:recovery_compass` items point at the returned position.
///
/// If the client's current dimension differs from the returned /// If the client's current dimension differs from the returned
/// dimension or the location is `None` then the compass will spin /// dimension or the location is `None` then the compass will spin
/// randomly. /// randomly.
@ -482,7 +447,7 @@ impl<C: Config> Client<C> {
pub fn set_attack_speed(&mut self, speed: f64) { pub fn set_attack_speed(&mut self, speed: f64) {
if self.attack_speed != speed { if self.attack_speed != speed {
self.attack_speed = speed; self.attack_speed = speed;
self.flags.set_attack_speed_modified(true); self.bits.set_attack_speed_modified(true);
} }
} }
@ -495,7 +460,7 @@ impl<C: Config> Client<C> {
pub fn set_movement_speed(&mut self, speed: f64) { pub fn set_movement_speed(&mut self, speed: f64) {
if self.movement_speed != speed { if self.movement_speed != speed {
self.movement_speed = speed; self.movement_speed = speed;
self.flags.set_movement_speed_modified(true); self.bits.set_movement_speed_modified(true);
} }
} }
@ -504,11 +469,6 @@ impl<C: Config> Client<C> {
self.send_packet(ClearTitles { reset: true }); self.send_packet(ClearTitles { reset: true });
} }
/// Gets if the client is on the ground, as determined by the client.
pub fn on_ground(&self) -> bool {
self.flags.on_ground()
}
/// Gets whether or not the client is connected to the server. /// Gets whether or not the client is connected to the server.
/// ///
/// A disconnected client object will never become reconnected. It is your /// A disconnected client object will never become reconnected. It is your
@ -518,42 +478,46 @@ impl<C: Config> Client<C> {
self.send.is_none() self.send.is_none()
} }
pub fn events(
&self,
) -> impl DoubleEndedIterator<Item = &ClientEvent> + ExactSizeIterator + FusedIterator + Clone + '_
{
self.events.iter()
}
/// Removes an [`Event`] from the event queue. /// Removes an [`Event`] from the event queue.
/// ///
/// If there are no remaining events, `None` is returned. /// If there are no remaining events, `None` is returned.
/// ///
/// Any remaining client events are deleted at the end of the /// Any remaining client events are deleted at the end of the
/// current tick. /// current tick.
pub fn pop_event(&mut self) -> Option<Event> { pub fn pop_event(&mut self) -> Option<ClientEvent> {
self.events.pop_front() self.events.pop_front()
} }
pub fn trigger_entity_event(&mut self, event: EntityEvent) { pub fn push_entity_event(&mut self, event: EntityEvent) {
self.entity_events.push(event); self.entity_events.push(event);
} }
/// The current view distance of this client measured in chunks. /// The current view distance of this client measured in chunks. The client
pub fn view_distance(&self) -> u8 { /// will not be able to see chunks and entities past this distance.
self.settings
.as_ref()
.map_or(2, |s| s.view_distance)
.min(self.max_view_distance())
}
/// Gets the maximum view distance. The client will not be able to see
/// chunks and entities past this distance.
/// ///
/// The value returned is measured in chunks. /// The result is in `2..=32`.
pub fn max_view_distance(&self) -> u8 { pub fn view_distance(&self) -> u8 {
self.new_max_view_distance self.view_distance
} }
/// Sets the maximum view distance. The client will not be able to see /// Sets the view distance. The client will not be able to see chunks and
/// chunks and entities past this distance. /// entities past this distance.
/// ///
/// The new view distance is measured in chunks and is clamped to `2..=32`. /// The new view distance is measured in chunks and is clamped to `2..=32`.
pub fn set_max_view_distance(&mut self, dist: u8) { pub fn set_view_distance(&mut self, dist: u8) {
self.new_max_view_distance = dist.clamp(2, 32); let dist = dist.clamp(2, 32);
if self.view_distance != dist {
self.view_distance = dist;
self.bits.set_view_distance_modified(true);
}
} }
/// Enables hardcore mode. This changes the design of the client's hearts. /// Enables hardcore mode. This changes the design of the client's hearts.
@ -561,12 +525,12 @@ impl<C: Config> Client<C> {
/// To have any visible effect, this function must be called on the same /// To have any visible effect, this function must be called on the same
/// tick the client joins the server. /// tick the client joins the server.
pub fn set_hardcore(&mut self, hardcore: bool) { pub fn set_hardcore(&mut self, hardcore: bool) {
self.flags.set_hardcore(hardcore); self.bits.set_hardcore(hardcore);
} }
/// Gets if hardcore mode is enabled. /// Gets if hardcore mode is enabled.
pub fn is_hardcore(&self) -> bool { pub fn is_hardcore(&self) -> bool {
self.flags.hardcore() self.bits.hardcore()
} }
/// Gets the client's current settings. /// Gets the client's current settings.
@ -630,44 +594,6 @@ impl<C: Config> Client<C> {
} }
fn handle_serverbound_packet(&mut self, entities: &Entities<C>, pkt: C2sPlayPacket) { 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,
new_pitch: f32,
new_on_ground: bool,
) {
if client.pending_teleports == 0 {
// TODO: validate movement using swept AABB collision with the blocks.
// TODO: validate that the client is actually inside/outside the vehicle?
// Movement packets should be coming in at a rate of STANDARD_TPS.
let new_velocity = (new_position - client.new_position).as_() * STANDARD_TPS as f32;
let event = Event::Movement {
old_position: client.new_position,
old_velocity: client.velocity,
old_yaw: client.yaw,
old_pitch: client.pitch,
old_on_ground: client.flags.on_ground(),
new_position,
new_velocity,
new_yaw,
new_pitch,
new_on_ground,
};
client.new_position = new_position;
client.velocity = new_velocity;
client.yaw = new_yaw;
client.pitch = new_pitch;
client.flags.set_on_ground(new_on_ground);
client.events.push_back(event);
}
}
match pkt { match pkt {
C2sPlayPacket::TeleportConfirm(p) => { C2sPlayPacket::TeleportConfirm(p) => {
if self.pending_teleports == 0 { if self.pending_teleports == 0 {
@ -694,14 +620,14 @@ impl<C: Config> Client<C> {
C2sPlayPacket::QueryBlockNbt(_) => {} C2sPlayPacket::QueryBlockNbt(_) => {}
C2sPlayPacket::UpdateDifficulty(_) => {} C2sPlayPacket::UpdateDifficulty(_) => {}
C2sPlayPacket::CommandExecution(_) => {} C2sPlayPacket::CommandExecution(_) => {}
C2sPlayPacket::ChatMessage(p) => self.events.push_back(Event::ChatMessage { C2sPlayPacket::ChatMessage(p) => self.events.push_back(ClientEvent::ChatMessage {
message: p.message.0, message: p.message.0,
timestamp: Duration::from_millis(p.timestamp), timestamp: Duration::from_millis(p.timestamp),
}), }),
C2sPlayPacket::RequestChatPreview(_) => {} C2sPlayPacket::RequestChatPreview(_) => {}
C2sPlayPacket::ClientStatus(_) => {} C2sPlayPacket::ClientStatus(_) => {}
C2sPlayPacket::ClientSettings(p) => { C2sPlayPacket::ClientSettings(p) => {
let old = self.settings.replace(Settings { self.events.push_back(ClientEvent::SettingsChanged {
locale: p.locale.0, locale: p.locale.0,
view_distance: p.view_distance.0, view_distance: p.view_distance.0,
chat_mode: p.chat_mode, chat_mode: p.chat_mode,
@ -709,9 +635,7 @@ impl<C: Config> Client<C> {
main_hand: p.main_hand, main_hand: p.main_hand,
displayed_skin_parts: p.displayed_skin_parts, displayed_skin_parts: p.displayed_skin_parts,
allow_server_listings: p.allow_server_listings, allow_server_listings: p.allow_server_listings,
}); })
self.events.push_back(Event::SettingsChanged(old));
} }
C2sPlayPacket::RequestCommandCompletion(_) => {} C2sPlayPacket::RequestCommandCompletion(_) => {}
C2sPlayPacket::ButtonClick(_) => {} C2sPlayPacket::ButtonClick(_) => {}
@ -725,7 +649,7 @@ impl<C: Config> Client<C> {
// TODO: verify that the client has line of sight to the targeted entity and // TODO: verify that the client has line of sight to the targeted entity and
// that the distance is <=4 blocks. // that the distance is <=4 blocks.
self.events.push_back(Event::InteractWithEntity { self.events.push_back(ClientEvent::InteractWithEntity {
id, id,
sneaking: p.sneaking, sneaking: p.sneaking,
kind: match p.kind { kind: match p.kind {
@ -741,7 +665,7 @@ impl<C: Config> Client<C> {
C2sPlayPacket::JigsawGenerate(_) => {} C2sPlayPacket::JigsawGenerate(_) => {}
C2sPlayPacket::KeepAlive(p) => { C2sPlayPacket::KeepAlive(p) => {
let last_keepalive_id = self.last_keepalive_id; let last_keepalive_id = self.last_keepalive_id;
if self.flags.got_keepalive() { if self.bits.got_keepalive() {
log::warn!("unexpected keepalive from player {}", self.username()); log::warn!("unexpected keepalive from player {}", self.username());
self.disconnect_no_reason(); self.disconnect_no_reason();
} else if p.id != last_keepalive_id { } else if p.id != last_keepalive_id {
@ -753,39 +677,68 @@ impl<C: Config> Client<C> {
); );
self.disconnect_no_reason(); self.disconnect_no_reason();
} else { } else {
self.flags.set_got_keepalive(true); self.bits.set_got_keepalive(true);
} }
} }
C2sPlayPacket::UpdateDifficultyLock(_) => {} C2sPlayPacket::UpdateDifficultyLock(_) => {}
C2sPlayPacket::MovePlayerPosition(p) => { C2sPlayPacket::MovePlayerPosition(p) => {
handle_movement_packet(self, false, p.position, self.yaw, self.pitch, p.on_ground) if self.pending_teleports == 0 {
self.position = p.position;
self.events.push_back(ClientEvent::MovePosition {
position: p.position,
on_ground: p.on_ground,
});
}
} }
C2sPlayPacket::MovePlayerPositionAndRotation(p) => { C2sPlayPacket::MovePlayerPositionAndRotation(p) => {
handle_movement_packet(self, false, p.position, p.yaw, p.pitch, p.on_ground) if self.pending_teleports == 0 {
self.position = p.position;
self.yaw = p.yaw;
self.pitch = p.pitch;
self.events.push_back(ClientEvent::MovePositionAndRotation {
position: p.position,
yaw: p.yaw,
pitch: p.pitch,
on_ground: p.on_ground,
});
}
} }
C2sPlayPacket::MovePlayerRotation(p) => { C2sPlayPacket::MovePlayerRotation(p) => {
handle_movement_packet(self, false, self.new_position, p.yaw, p.pitch, p.on_ground) if self.pending_teleports == 0 {
self.yaw = p.yaw;
self.pitch = p.pitch;
self.events.push_back(ClientEvent::MoveRotation {
yaw: p.yaw,
pitch: p.pitch,
on_ground: p.on_ground,
});
}
}
C2sPlayPacket::MovePlayerOnGround(p) => {
if self.pending_teleports == 0 {
self.events.push_back(ClientEvent::MoveOnGround {
on_ground: p.on_ground,
});
}
} }
C2sPlayPacket::MovePlayerOnGround(p) => handle_movement_packet(
self,
false,
self.new_position,
self.yaw,
self.pitch,
p.on_ground,
),
C2sPlayPacket::MoveVehicle(p) => { C2sPlayPacket::MoveVehicle(p) => {
handle_movement_packet( if self.pending_teleports == 0 {
self, self.position = p.position;
true, self.yaw = p.yaw;
p.position, self.pitch = p.pitch;
p.yaw,
p.pitch, self.events.push_back(ClientEvent::MoveVehicle {
self.flags.on_ground(), position: p.position,
); yaw: p.yaw,
pitch: p.pitch,
});
}
} }
C2sPlayPacket::BoatPaddleState(p) => { C2sPlayPacket::BoatPaddleState(p) => {
self.events.push_back(Event::SteerBoat { self.events.push_back(ClientEvent::SteerBoat {
left_paddle_turning: p.left_paddle_turning, left_paddle_turning: p.left_paddle_turning,
right_paddle_turning: p.right_paddle_turning, right_paddle_turning: p.right_paddle_turning,
}); });
@ -802,17 +755,17 @@ impl<C: Config> Client<C> {
} }
self.events.push_back(match p.status { self.events.push_back(match p.status {
DiggingStatus::StartedDigging => Event::Digging { DiggingStatus::StartedDigging => ClientEvent::Digging {
status: event::DiggingStatus::Start, status: event::DiggingStatus::Start,
position: p.location, position: p.location,
face: p.face, face: p.face,
}, },
DiggingStatus::CancelledDigging => Event::Digging { DiggingStatus::CancelledDigging => ClientEvent::Digging {
status: event::DiggingStatus::Cancel, status: event::DiggingStatus::Cancel,
position: p.location, position: p.location,
face: p.face, face: p.face,
}, },
DiggingStatus::FinishedDigging => Event::Digging { DiggingStatus::FinishedDigging => ClientEvent::Digging {
status: event::DiggingStatus::Finish, status: event::DiggingStatus::Finish,
position: p.location, position: p.location,
face: p.face, face: p.face,
@ -823,56 +776,19 @@ impl<C: Config> Client<C> {
DiggingStatus::SwapItemInHand => return, DiggingStatus::SwapItemInHand => return,
}); });
} }
C2sPlayPacket::PlayerCommand(e) => { C2sPlayPacket::PlayerCommand(c) => {
// TODO: validate: self.events.push_back(match c.action_id {
// - Can't sprint and sneak at the same time PlayerCommandId::StartSneaking => ClientEvent::StartSneaking,
// - Can't leave bed while not in a bed. PlayerCommandId::StopSneaking => ClientEvent::StopSneaking,
// - Can't jump with a horse if not on a horse PlayerCommandId::LeaveBed => ClientEvent::LeaveBed,
// - Can't open horse inventory if not on a horse. PlayerCommandId::StartSprinting => ClientEvent::StartSprinting,
// - Can't fly with elytra if not wearing an elytra. PlayerCommandId::StopSprinting => ClientEvent::StopSprinting,
// - Can't jump with horse while already jumping & vice versa? PlayerCommandId::StartJumpWithHorse => ClientEvent::StartJumpWithHorse {
self.events.push_back(match e.action_id { jump_boost: c.jump_boost.0 .0 as u8,
PlayerCommandId::StartSneaking => { },
if self.flags.sneaking() { PlayerCommandId::StopJumpWithHorse => ClientEvent::StopJumpWithHorse,
return; PlayerCommandId::OpenHorseInventory => ClientEvent::OpenHorseInventory,
} PlayerCommandId::StartFlyingWithElytra => ClientEvent::StartFlyingWithElytra,
self.flags.set_sneaking(true);
Event::StartSneaking
}
PlayerCommandId::StopSneaking => {
if !self.flags.sneaking() {
return;
}
self.flags.set_sneaking(false);
Event::StopSneaking
}
PlayerCommandId::LeaveBed => Event::LeaveBed,
PlayerCommandId::StartSprinting => {
if self.flags.sprinting() {
return;
}
self.flags.set_sprinting(true);
Event::StartSprinting
}
PlayerCommandId::StopSprinting => {
if !self.flags.sprinting() {
return;
}
self.flags.set_sprinting(false);
Event::StopSprinting
}
PlayerCommandId::StartJumpWithHorse => {
self.flags.set_jumping_with_horse(true);
Event::StartJumpWithHorse {
jump_boost: e.jump_boost.0 .0 as u8,
}
}
PlayerCommandId::StopJumpWithHorse => {
self.flags.set_jumping_with_horse(false);
Event::StopJumpWithHorse
}
PlayerCommandId::OpenHorseInventory => Event::OpenHorseInventory,
PlayerCommandId::StartFlyingWithElytra => Event::StartFlyingWithElytra,
}); });
} }
C2sPlayPacket::PlayerInput(_) => {} C2sPlayPacket::PlayerInput(_) => {}
@ -891,7 +807,7 @@ impl<C: Config> Client<C> {
C2sPlayPacket::UpdateJigsaw(_) => {} C2sPlayPacket::UpdateJigsaw(_) => {}
C2sPlayPacket::UpdateStructureBlock(_) => {} C2sPlayPacket::UpdateStructureBlock(_) => {}
C2sPlayPacket::UpdateSign(_) => {} C2sPlayPacket::UpdateSign(_) => {}
C2sPlayPacket::HandSwing(p) => self.events.push_back(Event::ArmSwing(p.hand)), C2sPlayPacket::HandSwing(p) => self.events.push_back(ClientEvent::ArmSwing(p.hand)),
C2sPlayPacket::SpectatorTeleport(_) => {} C2sPlayPacket::SpectatorTeleport(_) => {}
C2sPlayPacket::PlayerInteractBlock(_) => {} C2sPlayPacket::PlayerInteractBlock(_) => {}
C2sPlayPacket::PlayerInteractItem(_) => {} C2sPlayPacket::PlayerInteractItem(_) => {}
@ -926,7 +842,7 @@ impl<C: Config> Client<C> {
// Send the join game packet and other initial packets. We defer this until now // Send the join game packet and other initial packets. We defer this until now
// so that the user can set the client's location, game mode, etc. // so that the user can set the client's location, game mode, etc.
if self.created_tick == current_tick { if self.created_this_tick() {
world world
.meta .meta
.player_list() .player_list()
@ -941,7 +857,7 @@ impl<C: Config> Client<C> {
self.send_packet(GameJoin { self.send_packet(GameJoin {
entity_id: 0, // EntityId 0 is reserved for clients. entity_id: 0, // EntityId 0 is reserved for clients.
is_hardcore: self.flags.hardcore(), is_hardcore: self.bits.hardcore(),
gamemode: self.new_game_mode, gamemode: self.new_game_mode,
previous_gamemode: self.old_game_mode, previous_gamemode: self.old_game_mode,
dimension_names, dimension_names,
@ -956,7 +872,7 @@ impl<C: Config> Client<C> {
), ),
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.view_distance() as i32)),
simulation_distance: VarInt(16), simulation_distance: VarInt(16),
reduced_debug_info: false, reduced_debug_info: false,
enable_respawn_screen: false, enable_respawn_screen: false,
@ -969,14 +885,15 @@ impl<C: Config> Client<C> {
self.teleport(self.position(), self.yaw(), self.pitch()); self.teleport(self.position(), self.yaw(), self.pitch());
} else { } else {
if self.flags.spawn() { if self.bits.spawn() {
self.flags.set_spawn(false); self.bits.set_spawn(false);
self.loaded_entities.clear(); self.loaded_entities.clear();
self.loaded_chunks.clear(); self.loaded_chunks.clear();
// TODO: clear player list. // TODO: clear player list.
// Client bug workaround: send the client to a dummy dimension first. // Client bug workaround: send the client to a dummy dimension first.
// TODO: is there actually a bug?
self.send_packet(PlayerRespawn { self.send_packet(PlayerRespawn {
dimension_type_name: ident!("{LIBRARY_NAMESPACE}:dimension_type_0"), dimension_type_name: ident!("{LIBRARY_NAMESPACE}:dimension_type_0"),
dimension_name: ident!("{LIBRARY_NAMESPACE}:dummy_dimension"), dimension_name: ident!("{LIBRARY_NAMESPACE}:dummy_dimension"),
@ -1027,8 +944,8 @@ impl<C: Config> Client<C> {
} }
// Set player attributes // Set player attributes
if self.flags.attack_speed_modified() { if self.bits.attack_speed_modified() {
self.flags.set_attack_speed_modified(false); self.bits.set_attack_speed_modified(false);
self.send_packet(EntityAttributes { self.send_packet(EntityAttributes {
entity_id: VarInt(0), entity_id: VarInt(0),
@ -1040,8 +957,8 @@ impl<C: Config> Client<C> {
}); });
} }
if self.flags.movement_speed_modified() { if self.bits.movement_speed_modified() {
self.flags.set_movement_speed_modified(false); self.bits.set_movement_speed_modified(false);
self.send_packet(EntityAttributes { self.send_packet(EntityAttributes {
entity_id: VarInt(0), entity_id: VarInt(0),
@ -1054,8 +971,8 @@ impl<C: Config> Client<C> {
} }
// Update the players spawn position (compass position) // Update the players spawn position (compass position)
if self.flags.modified_spawn_position() { if self.bits.modified_spawn_position() {
self.flags.set_modified_spawn_position(false); self.bits.set_modified_spawn_position(false);
self.send_packet(PlayerSpawnPosition { self.send_packet(PlayerSpawnPosition {
location: self.spawn_position, location: self.spawn_position,
@ -1063,23 +980,24 @@ impl<C: Config> Client<C> {
}) })
} }
// Update view distance fog on the client if necessary. // Update view distance fog on the client.
if self.old_max_view_distance != self.new_max_view_distance { if self.bits.view_distance_modified() {
self.old_max_view_distance = self.new_max_view_distance; self.bits.set_view_distance_modified(false);
if self.created_tick != current_tick {
if !self.created_this_tick() {
self.send_packet(ChunkLoadDistance { self.send_packet(ChunkLoadDistance {
view_distance: BoundedInt(VarInt(self.new_max_view_distance as i32)), view_distance: BoundedInt(VarInt(self.view_distance() as i32)),
}) });
} }
} }
// Check if it's time to send another keepalive. // Check if it's time to send another keepalive.
if current_tick % (shared.tick_rate() * 8) == 0 { if current_tick % (shared.tick_rate() * 8) == 0 {
if self.flags.got_keepalive() { if self.bits.got_keepalive() {
let id = rand::random(); let id = rand::random();
self.send_packet(KeepAlive { id }); self.send_packet(KeepAlive { id });
self.last_keepalive_id = id; self.last_keepalive_id = id;
self.flags.set_got_keepalive(false); self.bits.set_got_keepalive(false);
} else { } else {
log::warn!( log::warn!(
"player {} timed out (no keepalive response)", "player {} timed out (no keepalive response)",
@ -1091,13 +1009,13 @@ impl<C: Config> Client<C> {
let view_dist = self.view_distance(); let view_dist = self.view_distance();
let center = ChunkPos::at(self.new_position.x, self.new_position.z); let center = ChunkPos::at(self.position.x, self.position.z);
// Send the update view position packet if the client changes the chunk section // Send the update view position packet if the client changes the chunk section
// they're in. // they're in.
{ {
let old_section = self.old_position.map(|n| (n / 16.0).floor() as i32); let old_section = self.old_position.map(|n| (n / 16.0).floor() as i32);
let new_section = self.new_position.map(|n| (n / 16.0).floor() as i32); let new_section = self.position.map(|n| (n / 16.0).floor() as i32);
if old_section != new_section { if old_section != new_section {
self.send_packet(ChunkRenderDistanceCenter { self.send_packet(ChunkRenderDistanceCenter {
@ -1161,23 +1079,33 @@ impl<C: Config> Client<C> {
// //
// 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.flags.teleported_this_tick() { if self.bits.teleported_this_tick() {
self.flags.set_teleported_this_tick(false); self.bits.set_teleported_this_tick(false);
self.send_packet(PlayerPositionLook { self.send_packet(PlayerPositionLook {
position: self.new_position, position: self.position,
yaw: self.yaw, yaw: self.yaw,
pitch: self.pitch, pitch: self.pitch,
flags: PlayerPositionLookFlags::new(false, false, false, false, false), flags: PlayerPositionLookFlags::new(false, false, false, false, false),
teleport_id: VarInt((self.teleport_id_counter - 1) as i32), teleport_id: VarInt(self.teleport_id_counter as i32),
dismount_vehicle: false, dismount_vehicle: false,
}); });
self.pending_teleports = self.pending_teleports.wrapping_add(1);
if self.pending_teleports == 0 {
log::warn!("too many pending teleports for {}", self.username());
self.disconnect_no_reason();
return;
}
self.teleport_id_counter = self.teleport_id_counter.wrapping_add(1);
} }
// Set velocity. Do this after teleporting since teleporting sets velocity to // Set velocity. Do this after teleporting since teleporting sets velocity to
// zero. // zero.
if self.flags.velocity_modified() { if self.bits.velocity_modified() {
self.flags.set_velocity_modified(false); self.bits.set_velocity_modified(false);
self.send_packet(EntityVelocityUpdate { self.send_packet(EntityVelocityUpdate {
entity_id: VarInt(0), entity_id: VarInt(0),
@ -1203,14 +1131,14 @@ impl<C: Config> Client<C> {
self.loaded_entities.retain(|&id| { self.loaded_entities.retain(|&id| {
if let Some(entity) = entities.get(id) { if let Some(entity) = entities.get(id) {
debug_assert!(entity.kind() != EntityKind::Marker); debug_assert!(entity.kind() != EntityKind::Marker);
if self.new_position.distance(entity.position()) <= view_dist as f64 * 16.0 { if self.position.distance(entity.position()) <= view_dist as f64 * 16.0 {
if let Some(meta) = entity.updated_tracked_data_packet(id) { if let Some(meta) = entity.updated_tracked_data_packet(id) {
send_packet(&mut self.send, meta); send_packet(&mut self.send, meta);
} }
let position_delta = entity.position() - entity.old_position(); let position_delta = entity.position() - entity.old_position();
let needs_teleport = position_delta.map(f64::abs).reduce_partial_max() >= 8.0; let needs_teleport = position_delta.map(f64::abs).reduce_partial_max() >= 8.0;
let flags = entity.flags(); let flags = entity.bits();
if entity.position() != entity.old_position() if entity.position() != entity.old_position()
&& !needs_teleport && !needs_teleport
@ -1345,7 +1273,8 @@ impl<C: Config> Client<C> {
self.entity_events.clear(); self.entity_events.clear();
self.player_data.clear_modifications(); self.player_data.clear_modifications();
self.old_position = self.new_position; self.old_position = self.position;
self.bits.set_created_this_tick(false);
} }
} }

View file

@ -13,7 +13,7 @@ pub use crate::protocol_inner::packets::s2c::play::GameMode;
/// Client events can be obtained from /// Client events can be obtained from
/// [`pop_event`](crate::client::Client::pop_event). /// [`pop_event`](crate::client::Client::pop_event).
#[derive(Debug)] #[derive(Debug)]
pub enum Event { pub enum ClientEvent {
/// A regular message was sent to the chat. /// A regular message was sent to the chat.
ChatMessage { ChatMessage {
/// The content of the message /// The content of the message
@ -21,31 +21,44 @@ pub enum Event {
/// The time the message was sent. /// The time the message was sent.
timestamp: Duration, timestamp: Duration,
}, },
/// Settings were changed. The value in this variant is the _previous_ /// Settings were changed. This is always sent once after joining by the
/// client settings. /// vanilla client.
SettingsChanged(Option<Settings>), SettingsChanged {
/// The client moved. /// e.g. en_US
Movement { locale: String,
/// Absolute coordinates of the previous position. /// The client side render distance, in chunks.
old_position: Vec3<f64>, ///
/// Previous velocity in m/s. /// The value is always in `2..=32`.
old_velocity: Vec3<f32>, view_distance: u8,
/// The previous yaw (in degrees). chat_mode: ChatMode,
old_yaw: f32, /// `true` if the client has chat colors enabled, `false` otherwise.
/// The previous pitch (in degrees). chat_colors: bool,
old_pitch: f32, main_hand: MainHand,
/// If the client was previously on the ground. displayed_skin_parts: DisplayedSkinParts,
old_on_ground: bool, allow_server_listings: bool,
/// Absolute coodinates of the new position. },
new_position: Vec3<f64>, MovePosition {
/// New velocity in m/s. position: Vec3<f64>,
new_velocity: Vec3<f32>, on_ground: bool,
/// The new yaw (in degrees). },
new_yaw: f32, MovePositionAndRotation {
/// The new pitch (in degrees). position: Vec3<f64>,
new_pitch: f32, yaw: f32,
/// If the client is now on the ground. pitch: f32,
new_on_ground: bool, on_ground: bool,
},
MoveRotation {
yaw: f32,
pitch: f32,
on_ground: bool,
},
MoveOnGround {
on_ground: bool,
},
MoveVehicle {
position: Vec3<f64>,
yaw: f32,
pitch: f32,
}, },
StartSneaking, StartSneaking,
StopSneaking, StopSneaking,

View file

@ -90,7 +90,7 @@ pub trait Config: 'static + Sized + Send + Sync + UnwindSafe + RefUnwindSafe {
/// Called once at startup to get the capacity of the buffer used to /// Called once at startup to get the capacity of the buffer used to
/// hold incoming packets. /// hold incoming packets.
/// ///
/// A larger capcity reduces the chance of packet loss but increases /// A larger capacity reduces the chance of packet loss but increases
/// potential memory usage. /// potential memory usage.
/// ///
/// # Default Implementation /// # Default Implementation
@ -104,7 +104,7 @@ pub trait Config: 'static + Sized + Send + Sync + UnwindSafe + RefUnwindSafe {
/// Called once at startup to get the capacity of the buffer used to /// Called once at startup to get the capacity of the buffer used to
/// hold outgoing packets. /// hold outgoing packets.
/// ///
/// A larger capcity reduces the chance of packet loss due to a full buffer /// A larger capacity reduces the chance of packet loss due to a full buffer
/// but increases potential memory usage. /// but increases potential memory usage.
/// ///
/// # Default Implementation /// # Default Implementation

View file

@ -6,7 +6,7 @@ use std::iter::FusedIterator;
use std::num::NonZeroU32; use std::num::NonZeroU32;
use bitfield_struct::bitfield; use bitfield_struct::bitfield;
pub use kinds::{TrackedData, EntityKind}; pub use data::{EntityKind, TrackedData};
use rayon::iter::ParallelIterator; use rayon::iter::ParallelIterator;
use uuid::Uuid; use uuid::Uuid;
use vek::{Aabb, Vec3}; use vek::{Aabb, Vec3};
@ -22,7 +22,7 @@ use crate::world::WorldId;
use crate::STANDARD_TPS; use crate::STANDARD_TPS;
pub mod data; pub mod data;
pub mod kinds; pub mod types;
include!(concat!(env!("OUT_DIR"), "/entity_event.rs")); include!(concat!(env!("OUT_DIR"), "/entity_event.rs"));
@ -76,7 +76,7 @@ impl<C: Config> Entities<C> {
state: data, state: data,
variants: TrackedData::new(kind), variants: TrackedData::new(kind),
events: Vec::new(), events: Vec::new(),
flags: EntityFlags(0), bits: EntityBits::new(),
world: WorldId::NULL, world: WorldId::NULL,
new_position: Vec3::default(), new_position: Vec3::default(),
old_position: Vec3::default(), old_position: Vec3::default(),
@ -203,9 +203,9 @@ impl<C: Config> Entities<C> {
e.variants.clear_modifications(); e.variants.clear_modifications();
e.events.clear(); e.events.clear();
e.flags.set_yaw_or_pitch_modified(false); e.bits.set_yaw_or_pitch_modified(false);
e.flags.set_head_yaw_modified(false); e.bits.set_head_yaw_modified(false);
e.flags.set_velocity_modified(false); e.bits.set_velocity_modified(false);
} }
} }
} }
@ -244,8 +244,8 @@ pub struct Entity<C: Config> {
/// Custom data. /// Custom data.
pub state: C::EntityState, pub state: C::EntityState,
variants: TrackedData, variants: TrackedData,
flags: EntityFlags, bits: EntityBits,
events: Vec<Event>, events: Vec<EntityEvent>,
world: WorldId, world: WorldId,
new_position: Vec3<f64>, new_position: Vec3<f64>,
old_position: Vec3<f64>, old_position: Vec3<f64>,
@ -257,7 +257,7 @@ pub struct Entity<C: Config> {
} }
#[bitfield(u8)] #[bitfield(u8)]
pub(crate) struct EntityFlags { pub(crate) struct EntityBits {
pub yaw_or_pitch_modified: bool, pub yaw_or_pitch_modified: bool,
pub head_yaw_modified: bool, pub head_yaw_modified: bool,
pub velocity_modified: bool, pub velocity_modified: bool,
@ -267,15 +267,15 @@ pub(crate) struct EntityFlags {
} }
impl<C: Config> Entity<C> { impl<C: Config> Entity<C> {
pub(crate) fn flags(&self) -> EntityFlags { pub(crate) fn bits(&self) -> EntityBits {
self.flags self.bits
} }
pub fn view(&self) -> &TrackedData { pub fn data(&self) -> &TrackedData {
&self.variants &self.variants
} }
pub fn view_mut(&mut self) -> &mut TrackedData { pub fn data_mut(&mut self) -> &mut TrackedData {
&mut self.variants &mut self.variants
} }
@ -284,11 +284,11 @@ impl<C: Config> Entity<C> {
self.variants.kind() self.variants.kind()
} }
pub fn trigger_event(&mut self, event: Event) { pub fn push_event(&mut self, event: EntityEvent) {
self.events.push(event); self.events.push(event);
} }
pub(crate) fn events(&self) -> &[Event] { pub(crate) fn events(&self) -> &[EntityEvent] {
&self.events &self.events
} }
@ -337,7 +337,7 @@ impl<C: Config> Entity<C> {
pub fn set_yaw(&mut self, yaw: f32) { pub fn set_yaw(&mut self, yaw: f32) {
if self.yaw != yaw { if self.yaw != yaw {
self.yaw = yaw; self.yaw = yaw;
self.flags.set_yaw_or_pitch_modified(true); self.bits.set_yaw_or_pitch_modified(true);
} }
} }
@ -350,7 +350,7 @@ impl<C: Config> Entity<C> {
pub fn set_pitch(&mut self, pitch: f32) { pub fn set_pitch(&mut self, pitch: f32) {
if self.pitch != pitch { if self.pitch != pitch {
self.pitch = pitch; self.pitch = pitch;
self.flags.set_yaw_or_pitch_modified(true); self.bits.set_yaw_or_pitch_modified(true);
} }
} }
@ -363,7 +363,7 @@ impl<C: Config> Entity<C> {
pub fn set_head_yaw(&mut self, head_yaw: f32) { pub fn set_head_yaw(&mut self, head_yaw: f32) {
if self.head_yaw != head_yaw { if self.head_yaw != head_yaw {
self.head_yaw = head_yaw; self.head_yaw = head_yaw;
self.flags.set_head_yaw_modified(true); self.bits.set_head_yaw_modified(true);
} }
} }
@ -378,18 +378,18 @@ impl<C: Config> Entity<C> {
if self.velocity != new_vel { if self.velocity != new_vel {
self.velocity = new_vel; self.velocity = new_vel;
self.flags.set_velocity_modified(true); self.bits.set_velocity_modified(true);
} }
} }
/// Gets the value of the "on ground" flag. /// Gets the value of the "on ground" flag.
pub fn on_ground(&self) -> bool { pub fn on_ground(&self) -> bool {
self.flags.on_ground() self.bits.on_ground()
} }
/// Sets the value of the "on ground" flag. /// Sets the value of the "on ground" flag.
pub fn set_on_ground(&mut self, on_ground: bool) { pub fn set_on_ground(&mut self, on_ground: bool) {
self.flags.set_on_ground(on_ground); self.bits.set_on_ground(on_ground);
} }
/// Gets the UUID of this entity. /// Gets the UUID of this entity.
@ -405,7 +405,7 @@ impl<C: Config> Entity<C> {
/// The hitbox of an entity is determined by its position, entity type, and /// The hitbox of an entity is determined by its position, entity type, and
/// other state specific to that type. /// other state specific to that type.
/// ///
/// [interact event]: crate::client::Event::InteractWithEntity /// [interact event]: crate::client::EntityEvent::InteractWithEntity
pub fn hitbox(&self) -> Aabb<f64> { pub fn hitbox(&self) -> Aabb<f64> {
let dims = match &self.variants { let dims = match &self.variants {
TrackedData::Allay(_) => [0.6, 0.35, 0.6], TrackedData::Allay(_) => [0.6, 0.35, 0.6],

View file

@ -1,256 +1,11 @@
//! Primitive types used in getters and setters on entities. //! Contains the [`TrackedData`] and the types for each variant.
use std::io::{Read, Write}; #![allow(clippy::all, missing_docs, trivial_numeric_casts)]
use crate::protocol_inner::{Decode, Encode, VarInt}; use crate::block::{BlockPos, BlockState};
use crate::entity::types::*;
use crate::protocol_inner::{Encode, VarInt};
use crate::text::Text;
use crate::uuid::Uuid;
/// Represents an optional `u32` value excluding [`u32::MAX`]. include!(concat!(env!("OUT_DIR"), "/entity.rs"));
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub struct OptionalInt(u32);
impl OptionalInt {
/// Returns `None` iff `n` is Some(u32::MAX).
pub fn new(n: impl Into<Option<u32>>) -> Option<Self> {
match n.into() {
None => Some(Self(0)),
Some(u32::MAX) => None,
Some(n) => Some(Self(n + 1)),
}
}
pub fn get(self) -> Option<u32> {
self.0.checked_sub(1)
}
}
impl Encode for OptionalInt {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
VarInt(self.0 as i32).encode(w)
}
}
impl Decode for OptionalInt {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
Ok(Self(VarInt::decode(r)?.0 as u32))
}
}
#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)]
pub struct EulerAngle {
pub pitch: f32,
pub yaw: f32,
pub roll: f32,
}
impl EulerAngle {
pub fn new(pitch: f32, yaw: f32, roll: f32) -> Self {
Self { pitch, yaw, roll }
}
}
impl Encode for EulerAngle {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
self.pitch.encode(w)?;
self.yaw.encode(w)?;
self.roll.encode(w)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum Facing {
Down,
Up,
North,
South,
West,
East,
}
impl Encode for Facing {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
VarInt(*self as i32).encode(w)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct VillagerData {
pub kind: VillagerKind,
pub profession: VillagerProfession,
pub level: i32,
}
impl VillagerData {
pub const fn new(kind: VillagerKind, profession: VillagerProfession, level: i32) -> Self {
Self {
kind,
profession,
level,
}
}
}
impl Default for VillagerData {
fn default() -> Self {
Self {
kind: Default::default(),
profession: Default::default(),
level: 1,
}
}
}
impl Encode for VillagerData {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
VarInt(self.kind as i32).encode(w)?;
VarInt(self.profession as i32).encode(w)?;
VarInt(self.level).encode(w)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub enum VillagerKind {
Desert,
Jungle,
#[default]
Plains,
Savanna,
Snow,
Swamp,
Taiga,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub enum VillagerProfession {
#[default]
None,
Armorer,
Butcher,
Cartographer,
Cleric,
Farmer,
Fisherman,
Fletcher,
Leatherworker,
Librarian,
Mason,
Nitwit,
Shepherd,
Toolsmith,
Weaponsmith,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub enum Pose {
#[default]
Standing,
FallFlying,
Sleeping,
Swimming,
SpinAttack,
Sneaking,
LongJumping,
Dying,
Croaking,
UsingTongue,
Roaring,
Sniffing,
Emerging,
Digging,
}
impl Encode for Pose {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
VarInt(*self as i32).encode(w)
}
}
/// The main hand of a player.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub enum MainHand {
Left,
#[default]
Right,
}
impl Encode for MainHand {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
(*self as u8).encode(w)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub enum BoatKind {
#[default]
Oak,
Spruce,
Birch,
Jungle,
Acacia,
DarkOak,
}
impl Encode for BoatKind {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
VarInt(*self as i32).encode(w)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub enum CatKind {
Tabby,
#[default]
Black,
Red,
Siamese,
BritishShorthair,
Calico,
Persian,
Ragdoll,
White,
Jellie,
AllBlack,
}
impl Encode for CatKind {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
VarInt(*self as i32).encode(w)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub enum FrogKind {
#[default]
Temperate,
Warm,
Cold,
}
impl Encode for FrogKind {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
VarInt(*self as i32).encode(w)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub enum PaintingKind {
#[default]
Kebab, // TODO
}
impl Encode for PaintingKind {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
VarInt(*self as i32).encode(w)
}
}
// TODO
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum Particle {
EntityEffect = 21,
}
impl Encode for Particle {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
VarInt(*self as i32).encode(w)
}
}

View file

@ -1,11 +0,0 @@
//! Contains the [`TrackedData`] and the types for each variant.
#![allow(clippy::all, missing_docs, trivial_numeric_casts)]
use crate::block::{BlockPos, BlockState};
use crate::entity::data::*;
use crate::protocol_inner::{Encode, VarInt};
use crate::text::Text;
use crate::uuid::Uuid;
include!(concat!(env!("OUT_DIR"), "/entity.rs"));

256
src/entity/types.rs Normal file
View file

@ -0,0 +1,256 @@
//! Primitive types used in getters and setters on entities.
use std::io::{Read, Write};
use crate::protocol_inner::{Decode, Encode, VarInt};
/// Represents an optional `u32` value excluding [`u32::MAX`].
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub struct OptionalInt(u32);
impl OptionalInt {
/// Returns `None` iff `n` is Some(u32::MAX).
pub fn new(n: impl Into<Option<u32>>) -> Option<Self> {
match n.into() {
None => Some(Self(0)),
Some(u32::MAX) => None,
Some(n) => Some(Self(n + 1)),
}
}
pub fn get(self) -> Option<u32> {
self.0.checked_sub(1)
}
}
impl Encode for OptionalInt {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
VarInt(self.0 as i32).encode(w)
}
}
impl Decode for OptionalInt {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
Ok(Self(VarInt::decode(r)?.0 as u32))
}
}
#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)]
pub struct EulerAngle {
pub pitch: f32,
pub yaw: f32,
pub roll: f32,
}
impl EulerAngle {
pub fn new(pitch: f32, yaw: f32, roll: f32) -> Self {
Self { pitch, yaw, roll }
}
}
impl Encode for EulerAngle {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
self.pitch.encode(w)?;
self.yaw.encode(w)?;
self.roll.encode(w)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum Facing {
Down,
Up,
North,
South,
West,
East,
}
impl Encode for Facing {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
VarInt(*self as i32).encode(w)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct VillagerData {
pub kind: VillagerKind,
pub profession: VillagerProfession,
pub level: i32,
}
impl VillagerData {
pub const fn new(kind: VillagerKind, profession: VillagerProfession, level: i32) -> Self {
Self {
kind,
profession,
level,
}
}
}
impl Default for VillagerData {
fn default() -> Self {
Self {
kind: Default::default(),
profession: Default::default(),
level: 1,
}
}
}
impl Encode for VillagerData {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
VarInt(self.kind as i32).encode(w)?;
VarInt(self.profession as i32).encode(w)?;
VarInt(self.level).encode(w)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub enum VillagerKind {
Desert,
Jungle,
#[default]
Plains,
Savanna,
Snow,
Swamp,
Taiga,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub enum VillagerProfession {
#[default]
None,
Armorer,
Butcher,
Cartographer,
Cleric,
Farmer,
Fisherman,
Fletcher,
Leatherworker,
Librarian,
Mason,
Nitwit,
Shepherd,
Toolsmith,
Weaponsmith,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub enum Pose {
#[default]
Standing,
FallFlying,
Sleeping,
Swimming,
SpinAttack,
Sneaking,
LongJumping,
Dying,
Croaking,
UsingTongue,
Roaring,
Sniffing,
Emerging,
Digging,
}
impl Encode for Pose {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
VarInt(*self as i32).encode(w)
}
}
/// The main hand of a player.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub enum MainArm {
Left,
#[default]
Right,
}
impl Encode for MainArm {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
(*self as u8).encode(w)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub enum BoatKind {
#[default]
Oak,
Spruce,
Birch,
Jungle,
Acacia,
DarkOak,
}
impl Encode for BoatKind {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
VarInt(*self as i32).encode(w)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub enum CatKind {
Tabby,
#[default]
Black,
Red,
Siamese,
BritishShorthair,
Calico,
Persian,
Ragdoll,
White,
Jellie,
AllBlack,
}
impl Encode for CatKind {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
VarInt(*self as i32).encode(w)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub enum FrogKind {
#[default]
Temperate,
Warm,
Cold,
}
impl Encode for FrogKind {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
VarInt(*self as i32).encode(w)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub enum PaintingKind {
#[default]
Kebab, // TODO
}
impl Encode for PaintingKind {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
VarInt(*self as i32).encode(w)
}
}
// TODO
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum Particle {
EntityEffect = 21,
}
impl Encode for Particle {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
VarInt(*self as i32).encode(w)
}
}

View file

@ -68,7 +68,7 @@ impl PlayerList {
game_mode, game_mode,
ping, ping,
display_name: display_name.into(), display_name: display_name.into(),
flags: EntryFlags::new().with_created_this_tick(true), bits: EntryBits::new().with_created_this_tick(true),
}); });
} else { } else {
e.set_game_mode(game_mode); e.set_game_mode(game_mode);
@ -84,7 +84,7 @@ impl PlayerList {
game_mode, game_mode,
ping, ping,
display_name: display_name.into(), display_name: display_name.into(),
flags: EntryFlags::new().with_created_this_tick(true), bits: EntryBits::new().with_created_this_tick(true),
}); });
true true
} }
@ -211,7 +211,7 @@ impl PlayerList {
let mut display_name = Vec::new(); let mut display_name = Vec::new();
for (&uuid, e) in self.entries.iter() { for (&uuid, e) in self.entries.iter() {
if e.flags.created_this_tick() { if e.bits.created_this_tick() {
let mut properties = Vec::new(); let mut properties = Vec::new();
if let Some(textures) = &e.textures { if let Some(textures) = &e.textures {
properties.push(Property { properties.push(Property {
@ -234,15 +234,15 @@ impl PlayerList {
continue; continue;
} }
if e.flags.modified_game_mode() { if e.bits.modified_game_mode() {
game_mode.push((uuid, e.game_mode)); game_mode.push((uuid, e.game_mode));
} }
if e.flags.modified_ping() { if e.bits.modified_ping() {
ping.push((uuid, VarInt(e.ping))); ping.push((uuid, VarInt(e.ping)));
} }
if e.flags.modified_display_name() { if e.bits.modified_display_name() {
display_name.push((uuid, e.display_name.clone())); display_name.push((uuid, e.display_name.clone()));
} }
} }
@ -276,7 +276,7 @@ impl PlayerList {
pub(crate) fn update(&mut self) { pub(crate) fn update(&mut self) {
for e in self.entries.values_mut() { for e in self.entries.values_mut() {
e.flags = EntryFlags(0); e.bits = EntryBits::new();
} }
self.removed.clear(); self.removed.clear();
self.modified_header_or_footer = false; self.modified_header_or_footer = false;
@ -290,7 +290,17 @@ pub struct PlayerListEntry {
game_mode: GameMode, game_mode: GameMode,
ping: i32, ping: i32,
display_name: Option<Text>, display_name: Option<Text>,
flags: EntryFlags, bits: EntryBits,
}
#[bitfield(u8)]
struct EntryBits {
created_this_tick: bool,
modified_game_mode: bool,
modified_ping: bool,
modified_display_name: bool,
#[bits(4)]
_pad: u8,
} }
impl PlayerListEntry { impl PlayerListEntry {
@ -313,7 +323,7 @@ impl PlayerListEntry {
pub fn set_game_mode(&mut self, game_mode: GameMode) { pub fn set_game_mode(&mut self, game_mode: GameMode) {
if self.game_mode != game_mode { if self.game_mode != game_mode {
self.game_mode = game_mode; self.game_mode = game_mode;
self.flags.set_modified_game_mode(true); self.bits.set_modified_game_mode(true);
} }
} }
@ -326,7 +336,7 @@ impl PlayerListEntry {
pub fn set_ping(&mut self, ping: i32) { pub fn set_ping(&mut self, ping: i32) {
if self.ping != ping { if self.ping != ping {
self.ping = ping; self.ping = ping;
self.flags.set_modified_ping(true); self.bits.set_modified_ping(true);
} }
} }
@ -340,17 +350,7 @@ impl PlayerListEntry {
let display_name = display_name.into(); let display_name = display_name.into();
if self.display_name != display_name { if self.display_name != display_name {
self.display_name = display_name; self.display_name = display_name;
self.flags.set_modified_display_name(true); self.bits.set_modified_display_name(true);
} }
} }
} }
#[bitfield(u8)]
struct EntryFlags {
created_this_tick: bool,
modified_game_mode: bool,
modified_ping: bool,
modified_display_name: bool,
#[bits(4)]
_pad: u8,
}

View file

@ -14,8 +14,8 @@ use paste::paste;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use uuid::Uuid; use uuid::Uuid;
use vek::Vec3; use vek::Vec3;
// use {def_bitfield, def_enum, def_struct};
// use {def_bitfield, def_enum, def_struct};
use crate::block_pos::BlockPos; use crate::block_pos::BlockPos;
use crate::ident::Ident; use crate::ident::Ident;
use crate::protocol_inner::{ use crate::protocol_inner::{
@ -121,8 +121,7 @@ macro_rules! def_struct {
} }
impl $name { impl $name {
#[allow(unused)] pub const PACKET_ID: i32 = $id;
const PACKET_ID: i32 = $id;
} }
)* )*
@ -231,8 +230,7 @@ macro_rules! def_enum {
} }
impl $name { impl $name {
#[allow(unused)] pub const PACKET_ID: i32 = $id;
const PACKET_ID: i32 = $id;
} }
)* )*
} }
@ -289,7 +287,7 @@ macro_rules! def_bitfield {
$( $(
#[doc = "Gets the " $bit " bit on this bitfield.\n"] #[doc = "Gets the " $bit " bit on this bitfield.\n"]
$(#[$bit_attrs])* $(#[$bit_attrs])*
pub fn [<get_ $bit:snake>](self) -> bool { pub fn $bit(self) -> bool {
self.0 & <$inner_ty>::one() << <$inner_ty>::from($offset) != <$inner_ty>::zero() self.0 & <$inner_ty>::one() << <$inner_ty>::from($offset) != <$inner_ty>::zero()
} }
@ -313,7 +311,7 @@ macro_rules! def_bitfield {
let mut s = f.debug_struct(stringify!($name)); let mut s = f.debug_struct(stringify!($name));
paste! { paste! {
$( $(
s.field(stringify!($bit), &self. [<get_ $bit:snake>]()); s.field(stringify!($bit), &self. $bit());
)* )*
} }
s.finish() s.finish()

View file

@ -40,7 +40,9 @@ use crate::protocol_inner::packets::c2s::login::{
}; };
use crate::protocol_inner::packets::c2s::play::C2sPlayPacket; use crate::protocol_inner::packets::c2s::play::C2sPlayPacket;
use crate::protocol_inner::packets::c2s::status::{QueryPing, QueryRequest}; use crate::protocol_inner::packets::c2s::status::{QueryPing, QueryRequest};
use crate::protocol_inner::packets::s2c::login::{EncryptionRequest, LoginCompression, LoginDisconnect, LoginSuccess}; use crate::protocol_inner::packets::s2c::login::{
EncryptionRequest, LoginCompression, LoginDisconnect, LoginSuccess,
};
use crate::protocol_inner::packets::s2c::play::S2cPlayPacket; use crate::protocol_inner::packets::s2c::play::S2cPlayPacket;
use crate::protocol_inner::packets::s2c::status::{QueryPong, QueryResponse}; use crate::protocol_inner::packets::s2c::status::{QueryPong, QueryResponse};
use crate::protocol_inner::packets::Property; use crate::protocol_inner::packets::Property;
@ -459,12 +461,7 @@ fn join_player<C: Config>(server: &mut Server<C>, msg: NewClientMessage) {
let _ = msg.reply.send(s2c_packet_channels); let _ = msg.reply.send(s2c_packet_channels);
let client = Client::new( let client = Client::new(c2s_packet_channels, msg.ncd, C::ClientState::default());
c2s_packet_channels,
&server.shared,
msg.ncd,
C::ClientState::default(),
);
server.clients.insert(client); server.clients.insert(client);
} }
@ -713,30 +710,28 @@ async fn handle_login<C: Config>(
c.enc.enable_compression(compression_threshold); c.enc.enable_compression(compression_threshold);
c.dec.enable_compression(compression_threshold); c.dec.enable_compression(compression_threshold);
let npd = NewClientData { let ncd = NewClientData {
uuid, uuid,
username, username,
textures, textures,
remote_addr, remote_addr,
}; };
if let Err(reason) = server.0.cfg.login(server, &npd).await { if let Err(reason) = server.0.cfg.login(server, &ncd).await {
log::info!("Disconnect at login: \"{reason}\""); log::info!("Disconnect at login: \"{reason}\"");
c.enc c.enc.write_packet(&LoginDisconnect { reason }).await?;
.write_packet(&LoginDisconnect { reason })
.await?;
return Ok(None); return Ok(None);
} }
c.enc c.enc
.write_packet(&LoginSuccess { .write_packet(&LoginSuccess {
uuid: npd.uuid, uuid: ncd.uuid,
username: npd.username.clone().into(), username: ncd.username.clone().into(),
properties: Vec::new(), properties: Vec::new(),
}) })
.await?; .await?;
Ok(Some(npd)) Ok(Some(ncd))
} }
async fn handle_play<C: Config>( async fn handle_play<C: Config>(