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! {
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum Event {
pub enum EntityEvent {
#(#event_variants,)*
}
impl Event {
impl EntityEvent {
pub(crate) fn status_or_animation(self) -> StatusOrAnimation {
match self {
#(#status_arms)*

View file

@ -16,11 +16,13 @@ pub fn main() -> anyhow::Result<()> {
(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")?;
for (g, file_name) in generators {
println!("cargo:rerun-if-changed=extracted/{file_name}");
let path = Path::new(&out_dir).join(file_name);
let code = g()?.to_string();
fs::write(&path, &code)?;

View file

@ -3,15 +3,15 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use log::LevelFilter;
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::dimension::DimensionId;
use valence::entity::data::Pose;
use valence::entity::{TrackedData, EntityId, EntityKind, Event as EntityEvent};
use valence::entity::types::Pose;
use valence::entity::{Entity, EntityEvent, EntityId, EntityKind, TrackedData};
use valence::server::{Server, SharedServer, ShutdownResult};
use valence::text::{Color, TextFormat};
use valence::{async_trait, Ticks};
use vek::Vec3;
use vek::{Vec2, Vec3};
pub fn main() -> ShutdownResult {
env_logger::Builder::new()
@ -119,7 +119,7 @@ impl Config for Game {
let current_tick = server.shared.current_tick();
server.clients.retain(|client_id, client| {
if client.created_tick() == current_tick {
if client.created_this_tick() {
if self
.player_count
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| {
@ -131,6 +131,23 @@ impl Config for Game {
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.set_game_mode(GameMode::Survival);
client.teleport(
@ -152,17 +169,6 @@ impl Config for Game {
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());
if self.player_count.load(Ordering::SeqCst) <= 1 {
client.send_message("Have another player join the game with you.".italic());
@ -176,41 +182,6 @@ impl Config for Game {
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 {
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());
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());
match client_event_boilerplate(client, player) {
Some(ClientEvent::StartSprinting) => {
client.state.extra_knockback = true;
}
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;
if let TrackedData::Player(player) = player.view_mut() {
if client.is_sneaking() {
player.set_pose(Pose::Sneaking);
} else {
player.set_pose(Pose::Standing);
client.state.extra_knockback = false;
}
}
}
Some(_) => {}
None => break,
}
player.set_sprinting(client.is_sprinting());
}
true
});
for (_, e) in server.entities.iter_mut() {
if e.state.attacked {
e.state.attacked = false;
let victim = server.clients.get_mut(e.state.client).unwrap();
for (_, entity) in server.entities.iter_mut() {
if entity.state.attacked {
entity.state.attacked = false;
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_y = if e.state.extra_knockback {
8.432
} else {
6.432
};
let knockback_xz = if entity.state.extra_knockback {
18.0
} else {
8.0
};
let knockback_y = if entity.state.extra_knockback {
8.432
} else {
6.432
};
vel.x *= knockback_xz;
vel.y = knockback_y;
vel.z *= knockback_xz;
let vel = Vec3::new(dir.x * knockback_xz, knockback_y, dir.y * knockback_xz);
victim.set_velocity(vel.as_());
victim.set_velocity(victim.velocity() / 2.0 + vel.as_());
e.trigger_event(EntityEvent::DamageFromGenericSource);
e.trigger_event(EntityEvent::Damage);
victim.trigger_entity_event(EntityEvent::DamageFromGenericSource);
victim.trigger_entity_event(EntityEvent::Damage);
entity.push_event(EntityEvent::DamageFromGenericSource);
entity.push_event(EntityEvent::Damage);
victim.push_entity_event(EntityEvent::DamageFromGenericSource);
victim.push_entity_event(EntityEvent::Damage);
}
}
}
}
}
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

@ -7,11 +7,11 @@ use num::Integer;
use rayon::iter::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator};
use valence::biome::Biome;
use valence::block::BlockState;
use valence::client::{Event, Hand};
use valence::client::{Client, ClientEvent, Hand};
use valence::config::{Config, ServerListPing};
use valence::dimension::{Dimension, DimensionId};
use valence::entity::data::Pose;
use valence::entity::{TrackedData, EntityId, EntityKind, Event as EntityEvent};
use valence::entity::types::Pose;
use valence::entity::{Entity, EntityEvent, EntityId, EntityKind, TrackedData};
use valence::server::{Server, SharedServer, ShutdownResult};
use valence::text::{Color, TextFormat};
use valence::{async_trait, ident};
@ -42,12 +42,6 @@ struct ServerState {
board_buf: Box<[bool]>,
}
#[derive(Default)]
struct ClientState {
/// The client's player entity.
player: EntityId,
}
const MAX_PLAYERS: usize = 10;
const SIZE_X: usize = 100;
@ -57,7 +51,7 @@ const BOARD_Y: i32 = 50;
#[async_trait]
impl Config for Game {
type ChunkState = ();
type ClientState = ClientState;
type ClientState = EntityId;
type EntityState = ();
type ServerState = ServerState;
type WorldState = ();
@ -121,7 +115,7 @@ impl Config for Game {
];
server.clients.retain(|_, client| {
if client.created_tick() == server.shared.current_tick() {
if client.created_this_tick() {
if self
.player_count
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| {
@ -133,6 +127,17 @@ impl Config for Game {
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.teleport(spawn_pos, 0.0, 0.0);
@ -145,65 +150,35 @@ impl Config for Game {
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("Hold the left mouse button to bring blocks to life.".italic());
}
if client.is_disconnected() {
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());
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 {
client.teleport(spawn_pos, client.yaw(), client.pitch());
}
while let Some(event) = client.pop_event() {
match event {
Event::Digging { position, .. } => {
if (0..SIZE_X as i32).contains(&position.x)
&& (0..SIZE_Z as i32).contains(&position.z)
&& position.y == BOARD_Y
{
server.state.board
[position.x as usize + position.z as usize * SIZE_X] = true;
}
while let Some(event) = client_event_boilerplate(client, player) {
if let ClientEvent::Digging { position, .. } = event {
if (0..SIZE_X as i32).contains(&position.x)
&& (0..SIZE_Z as i32).contains(&position.z)
&& position.y == BOARD_Y
{
server.state.board[position.x as usize + position.z as usize * SIZE_X] =
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
});
@ -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 valence::async_trait;
use valence::block::{BlockPos, BlockState};
use valence::client::GameMode;
use valence::client::{Client, ClientEvent, GameMode, Hand};
use valence::config::{Config, ServerListPing};
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::text::{Color, TextFormat};
use valence::util::to_yaw_and_pitch;
@ -43,7 +44,7 @@ const SPAWN_POS: BlockPos = BlockPos::new(0, 100, -25);
#[async_trait]
impl Config for Game {
type ChunkState = ();
type ClientState = ();
type ClientState = EntityId;
type EntityState = ();
type ServerState = ServerState;
type WorldState = ();
@ -92,10 +93,10 @@ impl Config for Game {
}
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| {
if client.created_tick() == server.shared.current_tick() {
if client.created_this_tick() {
if self
.player_count
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| {
@ -107,6 +108,17 @@ impl Config for Game {
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.set_game_mode(GameMode::Creative);
client.teleport(
@ -132,9 +144,18 @@ impl Config for Game {
if client.is_disconnected() {
self.player_count.fetch_sub(1, Ordering::SeqCst);
world.meta.player_list_mut().remove(client.uuid());
server.entities.delete(client.state);
return false;
}
let entity = server
.entities
.get_mut(client.state)
.expect("missing player entity");
while client_event_boilerplate(client, entity).is_some() {}
true
});
@ -153,7 +174,7 @@ impl Config for Game {
.map(|c| c.1.position())
.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);
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())
})
}
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 valence::async_trait;
use valence::block::{BlockPos, BlockState};
use valence::client::GameMode;
use valence::client::{Client, ClientEvent, GameMode, Hand};
use valence::config::{Config, ServerListPing};
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::spatial_index::RaycastHit;
use valence::text::{Color, TextFormat};
@ -41,7 +42,7 @@ const PLAYER_EYE_HEIGHT: f64 = 1.6;
#[async_trait]
impl Config for Game {
type ChunkState = ();
type ClientState = ();
type ClientState = EntityId;
/// `true` for entities that have been intersected with.
type EntityState = bool;
type ServerState = ();
@ -99,7 +100,7 @@ impl Config for Game {
let (world_id, world) = server.worlds.iter_mut().next().unwrap();
server.clients.retain(|_, client| {
if client.created_tick() == server.shared.current_tick() {
if client.created_this_tick() {
if self
.player_count
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| {
@ -111,6 +112,17 @@ impl Config for Game {
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.set_game_mode(GameMode::Creative);
client.teleport(
@ -142,6 +154,8 @@ impl Config for Game {
if client.is_disconnected() {
self.player_count.fetch_sub(1, Ordering::SeqCst);
world.meta.player_list_mut().remove(client.uuid());
server.entities.delete(client.state);
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
});
for (_, e) in server.entities.iter_mut() {
let intersected = e.state;
if let TrackedData::Sheep(sheep) = &mut e.view_mut() {
if let TrackedData::Sheep(sheep) = &mut e.data_mut() {
if intersected {
sheep.set_color(5);
} 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::block::{BlockState, PropName, PropValue};
use valence::chunk::ChunkPos;
use valence::client::GameMode;
use valence::client::{Client, ClientEvent, GameMode, Hand};
use valence::config::{Config, ServerListPing};
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::text::{Color, TextFormat};
use valence::util::chunks_in_view_distance;
@ -51,7 +53,7 @@ const MAX_PLAYERS: usize = 10;
#[async_trait]
impl Config for Game {
type ChunkState = ();
type ClientState = ();
type ClientState = EntityId;
type EntityState = ();
type ServerState = ();
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));
server.clients.retain(|_, client| {
if client.created_tick() == server.shared.current_tick() {
if client.created_this_tick() {
if self
.player_count
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| {
@ -102,9 +104,19 @@ impl Config for Game {
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.set_game_mode(GameMode::Creative);
client.set_max_view_distance(32);
client.teleport([0.0, 200.0, 0.0], 0.0, 0.0);
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("Explore this infinite procedurally generated terrain.".italic());
}
if client.is_disconnected() {
self.player_count.fetch_sub(1, Ordering::SeqCst);
world.meta.player_list_mut().remove(client.uuid());
server.entities.delete(client.state);
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 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 {
(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::sync::Semaphore;
use valence::protocol::codec::Decoder;
use valence::protocol::packets::handshake::{Handshake, HandshakeNextState};
use valence::protocol::packets::login::c2s::{EncryptionResponse, LoginStart};
use valence::protocol::packets::login::s2c::{LoginSuccess, S2cLoginPacket};
use valence::protocol::packets::c2s::handshake::{Handshake, HandshakeNextState};
use valence::protocol::packets::c2s::login::{EncryptionResponse, LoginStart};
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::status::c2s::{QueryPing, QueryRequest};
use valence::protocol::packets::status::s2c::{QueryPong, QueryResponse};
use valence::protocol::packets::s2c::status::{QueryPong, QueryResponse};
use valence::protocol::packets::{DecodePacket, EncodePacket};
use valence::protocol::{Encode, VarInt};

View file

@ -4,7 +4,7 @@ use std::collections::{HashSet, VecDeque};
use std::iter::FusedIterator;
use std::time::Duration;
use bitfield_struct::bitfield;
pub use bitfield_struct::bitfield;
pub use event::*;
use flume::{Receiver, Sender, TrySendError};
use rayon::iter::ParallelIterator;
@ -16,10 +16,9 @@ use crate::block_pos::BlockPos;
use crate::chunk_pos::ChunkPos;
use crate::config::Config;
use crate::dimension::DimensionId;
use crate::entity::kinds::Player;
use crate::entity::data::Player;
use crate::entity::{
velocity_to_packet_units, Entities, EntityId, EntityKind, Event as EntityEvent,
StatusOrAnimation,
velocity_to_packet_units, Entities, EntityEvent, EntityId, EntityKind, StatusOrAnimation,
};
use crate::player_textures::SignedPlayerTextures;
use crate::protocol_inner::packets::c2s::play::{
@ -42,9 +41,9 @@ use crate::slotmap::{Key, SlotMap};
use crate::text::Text;
use crate::util::{chunks_in_view_distance, is_chunk_in_view_distance};
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;
/// 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>) {
let (id, client) = self.sm.insert(client);
(ClientId(id), client)
let (k, client) = self.sm.insert(client);
(ClientId(k), client)
}
/// Removes a client from the server.
@ -74,7 +73,7 @@ impl<C: Config> Clients<C> {
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`.
///
/// 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.
///
/// 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
/// managed by the library.
///
@ -154,30 +153,28 @@ impl ClientId {
/// cannot break blocks, hurt entities, or see other clients. Interactions with
/// the server must be handled explicitly with [`Self::pop_event`].
///
/// Additionally, clients posess [`Player`] entity data which is only visible to
/// themselves. This can be accessed with [`Self::player`] and
/// Additionally, clients possess [`Player`] entity data which is only visible
/// to themselves. This can be accessed with [`Self::player`] and
/// [`Self::player_mut`].
///
/// # The Difference Between a "Client" and a "Player"
///
/// 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
/// was done primarily to enable multithreaded client updates.
/// In Valence however, clients and players are decoupled. This separation
/// allows for greater flexibility and parallelism.
pub struct Client<C: Config> {
/// Custom state.
pub state: C::ClientState,
/// Setting this to `None` disconnects the client.
send: SendOpt,
recv: Receiver<C2sPlayPacket>,
/// The tick this client was created.
created_tick: Ticks,
uuid: Uuid,
username: String,
textures: Option<SignedPlayerTextures>,
world: WorldId,
new_position: Vec3<f64>,
position: Vec3<f64>,
old_position: Vec3<f64>,
/// Measured in m/s.
velocity: Vec3<f32>,
@ -185,6 +182,7 @@ pub struct Client<C: Config> {
yaw: f32,
/// Measured in degrees
pitch: f32,
view_distance: u8,
/// Counts up as teleports are made.
teleport_id_counter: u32,
/// 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_yaw: f32,
death_location: Option<(DimensionId, BlockPos)>,
events: VecDeque<Event>,
events: VecDeque<ClientEvent>,
/// The ID of the last keepalive sent.
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.
/// This is used to determine what entity create/destroy packets should be
/// sent.
@ -212,21 +208,15 @@ pub struct Client<C: Config> {
msgs_to_send: Vec<Text>,
attack_speed: f64,
movement_speed: f64,
flags: ClientFlags,
bits: ClientBits,
/// The data for the client's own player entity.
player_data: Player,
entity_events: Vec<EntityEvent>,
}
#[bitfield(u16)]
pub(crate) struct ClientFlags {
struct ClientBits {
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,
/// If spawn_position or spawn_position_yaw were modified this tick.
modified_spawn_position: bool,
@ -236,33 +226,34 @@ pub(crate) struct ClientFlags {
attack_speed_modified: bool,
movement_speed_modified: bool,
velocity_modified: bool,
#[bits(4)]
created_this_tick: bool,
view_distance_modified: bool,
#[bits(6)]
_pad: u8,
}
impl<C: Config> Client<C> {
pub(crate) fn new(
packet_channels: C2sPacketChannels,
server: &SharedServer<C>,
ncd: NewClientData,
data: C::ClientState,
state: C::ClientState,
) -> Self {
let (send, recv) = packet_channels;
Self {
state: data,
state,
send: Some(send),
recv,
created_tick: server.current_tick(),
uuid: ncd.uuid,
username: ncd.username,
textures: ncd.textures,
world: WorldId::default(),
new_position: Vec3::default(),
position: Vec3::default(),
old_position: Vec3::default(),
velocity: Vec3::default(),
yaw: 0.0,
pitch: 0.0,
view_distance: 8,
teleport_id_counter: 0,
pending_teleports: 0,
spawn_position: BlockPos::default(),
@ -270,8 +261,6 @@ impl<C: Config> Client<C> {
death_location: None,
events: VecDeque::new(),
last_keepalive_id: 0,
new_max_view_distance: 16,
old_max_view_distance: 0,
loaded_entities: HashSet::new(),
loaded_chunks: HashSet::new(),
new_game_mode: GameMode::Survival,
@ -281,17 +270,18 @@ impl<C: Config> Client<C> {
msgs_to_send: Vec::new(),
attack_speed: 4.0,
movement_speed: 0.7,
flags: ClientFlags::new()
bits: ClientBits::new()
.with_modified_spawn_position(true)
.with_got_keepalive(true),
.with_got_keepalive(true)
.with_created_this_tick(true),
player_data: Player::new(),
entity_events: Vec::new(),
}
}
/// Gets the tick that this client was created.
pub fn created_tick(&self) -> Ticks {
self.created_tick
/// If the client joined the game this tick.
pub fn created_this_tick(&self) -> bool {
self.bits.created_this_tick()
}
/// Gets the client's UUID.
@ -304,16 +294,6 @@ impl<C: Config> Client<C> {
&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
/// a skin, then `None` is returned.
pub fn textures(&self) -> Option<&SignedPlayerTextures> {
@ -325,13 +305,14 @@ impl<C: Config> Client<C> {
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
/// disconnected.
pub fn spawn(&mut self, world: WorldId) {
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.
@ -344,7 +325,7 @@ impl<C: Config> Client<C> {
/// Gets the absolute position of this client in the world it is located
/// in.
pub fn position(&self) -> Vec3<f64> {
self.new_position
self.position
}
/// 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`].
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.pitch = pitch;
self.velocity = Vec3::default();
if !self.flags.teleported_this_tick() {
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);
}
self.bits.set_teleported_this_tick(true);
}
/// Gets the velocity of this client in m/s.
///
/// The velocity of a client is derived from their current and previous
/// position.
/// Gets the most recently set velocity of this client in m/s.
pub fn velocity(&self) -> Vec3<f32> {
self.velocity
}
@ -384,7 +348,7 @@ impl<C: Config> Client<C> {
/// Sets the client's velocity in m/s.
pub fn set_velocity(&mut self, velocity: impl Into<Vec3<f32>>) {
self.velocity = velocity.into();
self.flags.set_velocity_modified(true);
self.bits.set_velocity_modified(true);
}
/// 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 {
self.spawn_position = pos;
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
/// `minecraft:recovery_compass` items point at the returned position.
///
/// If the client's current dimension differs from the returned
/// dimension or the location is `None` then the compass will spin
/// randomly.
@ -482,7 +447,7 @@ impl<C: Config> Client<C> {
pub fn set_attack_speed(&mut self, speed: f64) {
if 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) {
if 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 });
}
/// 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.
///
/// A disconnected client object will never become reconnected. It is your
@ -518,42 +478,46 @@ impl<C: Config> Client<C> {
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.
///
/// If there are no remaining events, `None` is returned.
///
/// Any remaining client events are deleted at the end of the
/// current tick.
pub fn pop_event(&mut self) -> Option<Event> {
pub fn pop_event(&mut self) -> Option<ClientEvent> {
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);
}
/// The current view distance of this client measured in chunks.
pub fn view_distance(&self) -> u8 {
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 current view distance of this client measured in chunks. The client
/// will not be able to see chunks and entities past this distance.
///
/// The value returned is measured in chunks.
pub fn max_view_distance(&self) -> u8 {
self.new_max_view_distance
/// The result is in `2..=32`.
pub fn view_distance(&self) -> u8 {
self.view_distance
}
/// Sets the maximum view distance. The client will not be able to see
/// chunks and entities past this distance.
/// Sets the view distance. The client will not be able to see chunks and
/// entities past this distance.
///
/// The new view distance is measured in chunks and is clamped to `2..=32`.
pub fn set_max_view_distance(&mut self, dist: u8) {
self.new_max_view_distance = dist.clamp(2, 32);
pub fn set_view_distance(&mut self, dist: u8) {
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.
@ -561,12 +525,12 @@ impl<C: Config> Client<C> {
/// To have any visible effect, this function must be called on the same
/// tick the client joins the server.
pub fn set_hardcore(&mut self, hardcore: bool) {
self.flags.set_hardcore(hardcore);
self.bits.set_hardcore(hardcore);
}
/// Gets if hardcore mode is enabled.
pub fn is_hardcore(&self) -> bool {
self.flags.hardcore()
self.bits.hardcore()
}
/// 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_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 {
C2sPlayPacket::TeleportConfirm(p) => {
if self.pending_teleports == 0 {
@ -694,14 +620,14 @@ impl<C: Config> Client<C> {
C2sPlayPacket::QueryBlockNbt(_) => {}
C2sPlayPacket::UpdateDifficulty(_) => {}
C2sPlayPacket::CommandExecution(_) => {}
C2sPlayPacket::ChatMessage(p) => self.events.push_back(Event::ChatMessage {
C2sPlayPacket::ChatMessage(p) => self.events.push_back(ClientEvent::ChatMessage {
message: p.message.0,
timestamp: Duration::from_millis(p.timestamp),
}),
C2sPlayPacket::RequestChatPreview(_) => {}
C2sPlayPacket::ClientStatus(_) => {}
C2sPlayPacket::ClientSettings(p) => {
let old = self.settings.replace(Settings {
self.events.push_back(ClientEvent::SettingsChanged {
locale: p.locale.0,
view_distance: p.view_distance.0,
chat_mode: p.chat_mode,
@ -709,9 +635,7 @@ impl<C: Config> Client<C> {
main_hand: p.main_hand,
displayed_skin_parts: p.displayed_skin_parts,
allow_server_listings: p.allow_server_listings,
});
self.events.push_back(Event::SettingsChanged(old));
})
}
C2sPlayPacket::RequestCommandCompletion(_) => {}
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
// that the distance is <=4 blocks.
self.events.push_back(Event::InteractWithEntity {
self.events.push_back(ClientEvent::InteractWithEntity {
id,
sneaking: p.sneaking,
kind: match p.kind {
@ -741,7 +665,7 @@ impl<C: Config> Client<C> {
C2sPlayPacket::JigsawGenerate(_) => {}
C2sPlayPacket::KeepAlive(p) => {
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());
self.disconnect_no_reason();
} else if p.id != last_keepalive_id {
@ -753,39 +677,68 @@ impl<C: Config> Client<C> {
);
self.disconnect_no_reason();
} else {
self.flags.set_got_keepalive(true);
self.bits.set_got_keepalive(true);
}
}
C2sPlayPacket::UpdateDifficultyLock(_) => {}
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) => {
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) => {
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) => {
handle_movement_packet(
self,
true,
p.position,
p.yaw,
p.pitch,
self.flags.on_ground(),
);
if self.pending_teleports == 0 {
self.position = p.position;
self.yaw = p.yaw;
self.pitch = p.pitch;
self.events.push_back(ClientEvent::MoveVehicle {
position: p.position,
yaw: p.yaw,
pitch: p.pitch,
});
}
}
C2sPlayPacket::BoatPaddleState(p) => {
self.events.push_back(Event::SteerBoat {
self.events.push_back(ClientEvent::SteerBoat {
left_paddle_turning: p.left_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 {
DiggingStatus::StartedDigging => Event::Digging {
DiggingStatus::StartedDigging => ClientEvent::Digging {
status: event::DiggingStatus::Start,
position: p.location,
face: p.face,
},
DiggingStatus::CancelledDigging => Event::Digging {
DiggingStatus::CancelledDigging => ClientEvent::Digging {
status: event::DiggingStatus::Cancel,
position: p.location,
face: p.face,
},
DiggingStatus::FinishedDigging => Event::Digging {
DiggingStatus::FinishedDigging => ClientEvent::Digging {
status: event::DiggingStatus::Finish,
position: p.location,
face: p.face,
@ -823,56 +776,19 @@ impl<C: Config> Client<C> {
DiggingStatus::SwapItemInHand => return,
});
}
C2sPlayPacket::PlayerCommand(e) => {
// TODO: validate:
// - Can't sprint and sneak at the same time
// - Can't leave bed while not in a bed.
// - Can't jump with a horse if not on a horse
// - Can't open horse inventory if not on a horse.
// - Can't fly with elytra if not wearing an elytra.
// - Can't jump with horse while already jumping & vice versa?
self.events.push_back(match e.action_id {
PlayerCommandId::StartSneaking => {
if self.flags.sneaking() {
return;
}
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::PlayerCommand(c) => {
self.events.push_back(match c.action_id {
PlayerCommandId::StartSneaking => ClientEvent::StartSneaking,
PlayerCommandId::StopSneaking => ClientEvent::StopSneaking,
PlayerCommandId::LeaveBed => ClientEvent::LeaveBed,
PlayerCommandId::StartSprinting => ClientEvent::StartSprinting,
PlayerCommandId::StopSprinting => ClientEvent::StopSprinting,
PlayerCommandId::StartJumpWithHorse => ClientEvent::StartJumpWithHorse {
jump_boost: c.jump_boost.0 .0 as u8,
},
PlayerCommandId::StopJumpWithHorse => ClientEvent::StopJumpWithHorse,
PlayerCommandId::OpenHorseInventory => ClientEvent::OpenHorseInventory,
PlayerCommandId::StartFlyingWithElytra => ClientEvent::StartFlyingWithElytra,
});
}
C2sPlayPacket::PlayerInput(_) => {}
@ -891,7 +807,7 @@ impl<C: Config> Client<C> {
C2sPlayPacket::UpdateJigsaw(_) => {}
C2sPlayPacket::UpdateStructureBlock(_) => {}
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::PlayerInteractBlock(_) => {}
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
// 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
.meta
.player_list()
@ -941,7 +857,7 @@ impl<C: Config> Client<C> {
self.send_packet(GameJoin {
entity_id: 0, // EntityId 0 is reserved for clients.
is_hardcore: self.flags.hardcore(),
is_hardcore: self.bits.hardcore(),
gamemode: self.new_game_mode,
previous_gamemode: self.old_game_mode,
dimension_names,
@ -956,7 +872,7 @@ impl<C: Config> Client<C> {
),
hashed_seed: 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),
reduced_debug_info: false,
enable_respawn_screen: false,
@ -969,14 +885,15 @@ impl<C: Config> Client<C> {
self.teleport(self.position(), self.yaw(), self.pitch());
} else {
if self.flags.spawn() {
self.flags.set_spawn(false);
if self.bits.spawn() {
self.bits.set_spawn(false);
self.loaded_entities.clear();
self.loaded_chunks.clear();
// TODO: clear player list.
// Client bug workaround: send the client to a dummy dimension first.
// TODO: is there actually a bug?
self.send_packet(PlayerRespawn {
dimension_type_name: ident!("{LIBRARY_NAMESPACE}:dimension_type_0"),
dimension_name: ident!("{LIBRARY_NAMESPACE}:dummy_dimension"),
@ -1027,8 +944,8 @@ impl<C: Config> Client<C> {
}
// Set player attributes
if self.flags.attack_speed_modified() {
self.flags.set_attack_speed_modified(false);
if self.bits.attack_speed_modified() {
self.bits.set_attack_speed_modified(false);
self.send_packet(EntityAttributes {
entity_id: VarInt(0),
@ -1040,8 +957,8 @@ impl<C: Config> Client<C> {
});
}
if self.flags.movement_speed_modified() {
self.flags.set_movement_speed_modified(false);
if self.bits.movement_speed_modified() {
self.bits.set_movement_speed_modified(false);
self.send_packet(EntityAttributes {
entity_id: VarInt(0),
@ -1054,8 +971,8 @@ impl<C: Config> Client<C> {
}
// Update the players spawn position (compass position)
if self.flags.modified_spawn_position() {
self.flags.set_modified_spawn_position(false);
if self.bits.modified_spawn_position() {
self.bits.set_modified_spawn_position(false);
self.send_packet(PlayerSpawnPosition {
location: self.spawn_position,
@ -1063,23 +980,24 @@ impl<C: Config> Client<C> {
})
}
// Update view distance fog on the client if necessary.
if self.old_max_view_distance != self.new_max_view_distance {
self.old_max_view_distance = self.new_max_view_distance;
if self.created_tick != current_tick {
// Update view distance fog on the client.
if self.bits.view_distance_modified() {
self.bits.set_view_distance_modified(false);
if !self.created_this_tick() {
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.
if current_tick % (shared.tick_rate() * 8) == 0 {
if self.flags.got_keepalive() {
if self.bits.got_keepalive() {
let id = rand::random();
self.send_packet(KeepAlive { id });
self.last_keepalive_id = id;
self.flags.set_got_keepalive(false);
self.bits.set_got_keepalive(false);
} else {
log::warn!(
"player {} timed out (no keepalive response)",
@ -1091,13 +1009,13 @@ impl<C: Config> Client<C> {
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
// they're in.
{
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 {
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"
// screen is closed at the appropriate time.
if self.flags.teleported_this_tick() {
self.flags.set_teleported_this_tick(false);
if self.bits.teleported_this_tick() {
self.bits.set_teleported_this_tick(false);
self.send_packet(PlayerPositionLook {
position: self.new_position,
position: self.position,
yaw: self.yaw,
pitch: self.pitch,
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,
});
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
// zero.
if self.flags.velocity_modified() {
self.flags.set_velocity_modified(false);
if self.bits.velocity_modified() {
self.bits.set_velocity_modified(false);
self.send_packet(EntityVelocityUpdate {
entity_id: VarInt(0),
@ -1203,14 +1131,14 @@ impl<C: Config> Client<C> {
self.loaded_entities.retain(|&id| {
if let Some(entity) = entities.get(id) {
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) {
send_packet(&mut self.send, meta);
}
let position_delta = entity.position() - entity.old_position();
let needs_teleport = position_delta.map(f64::abs).reduce_partial_max() >= 8.0;
let flags = entity.flags();
let flags = entity.bits();
if entity.position() != entity.old_position()
&& !needs_teleport
@ -1345,7 +1273,8 @@ impl<C: Config> Client<C> {
self.entity_events.clear();
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
/// [`pop_event`](crate::client::Client::pop_event).
#[derive(Debug)]
pub enum Event {
pub enum ClientEvent {
/// A regular message was sent to the chat.
ChatMessage {
/// The content of the message
@ -21,31 +21,44 @@ pub enum Event {
/// The time the message was sent.
timestamp: Duration,
},
/// Settings were changed. The value in this variant is the _previous_
/// client settings.
SettingsChanged(Option<Settings>),
/// The client moved.
Movement {
/// Absolute coordinates of the previous position.
old_position: Vec3<f64>,
/// Previous velocity in m/s.
old_velocity: Vec3<f32>,
/// The previous yaw (in degrees).
old_yaw: f32,
/// The previous pitch (in degrees).
old_pitch: f32,
/// If the client was previously on the ground.
old_on_ground: bool,
/// Absolute coodinates of the new position.
new_position: Vec3<f64>,
/// New velocity in m/s.
new_velocity: Vec3<f32>,
/// The new yaw (in degrees).
new_yaw: f32,
/// The new pitch (in degrees).
new_pitch: f32,
/// If the client is now on the ground.
new_on_ground: bool,
/// Settings were changed. This is always sent once after joining by the
/// vanilla client.
SettingsChanged {
/// e.g. en_US
locale: String,
/// The client side render distance, in chunks.
///
/// The value is always in `2..=32`.
view_distance: u8,
chat_mode: ChatMode,
/// `true` if the client has chat colors enabled, `false` otherwise.
chat_colors: bool,
main_hand: MainHand,
displayed_skin_parts: DisplayedSkinParts,
allow_server_listings: bool,
},
MovePosition {
position: Vec3<f64>,
on_ground: bool,
},
MovePositionAndRotation {
position: Vec3<f64>,
yaw: f32,
pitch: f32,
on_ground: bool,
},
MoveRotation {
yaw: f32,
pitch: f32,
on_ground: bool,
},
MoveOnGround {
on_ground: bool,
},
MoveVehicle {
position: Vec3<f64>,
yaw: f32,
pitch: f32,
},
StartSneaking,
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
/// 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.
///
/// # 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
/// 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.
///
/// # Default Implementation

View file

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

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,
ping,
display_name: display_name.into(),
flags: EntryFlags::new().with_created_this_tick(true),
bits: EntryBits::new().with_created_this_tick(true),
});
} else {
e.set_game_mode(game_mode);
@ -84,7 +84,7 @@ impl PlayerList {
game_mode,
ping,
display_name: display_name.into(),
flags: EntryFlags::new().with_created_this_tick(true),
bits: EntryBits::new().with_created_this_tick(true),
});
true
}
@ -211,7 +211,7 @@ impl PlayerList {
let mut display_name = Vec::new();
for (&uuid, e) in self.entries.iter() {
if e.flags.created_this_tick() {
if e.bits.created_this_tick() {
let mut properties = Vec::new();
if let Some(textures) = &e.textures {
properties.push(Property {
@ -234,15 +234,15 @@ impl PlayerList {
continue;
}
if e.flags.modified_game_mode() {
if e.bits.modified_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)));
}
if e.flags.modified_display_name() {
if e.bits.modified_display_name() {
display_name.push((uuid, e.display_name.clone()));
}
}
@ -276,7 +276,7 @@ impl PlayerList {
pub(crate) fn update(&mut self) {
for e in self.entries.values_mut() {
e.flags = EntryFlags(0);
e.bits = EntryBits::new();
}
self.removed.clear();
self.modified_header_or_footer = false;
@ -290,7 +290,17 @@ pub struct PlayerListEntry {
game_mode: GameMode,
ping: i32,
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 {
@ -313,7 +323,7 @@ impl PlayerListEntry {
pub fn set_game_mode(&mut self, game_mode: GameMode) {
if 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) {
if 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();
if 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 uuid::Uuid;
use vek::Vec3;
// use {def_bitfield, def_enum, def_struct};
// use {def_bitfield, def_enum, def_struct};
use crate::block_pos::BlockPos;
use crate::ident::Ident;
use crate::protocol_inner::{
@ -121,8 +121,7 @@ macro_rules! def_struct {
}
impl $name {
#[allow(unused)]
const PACKET_ID: i32 = $id;
pub const PACKET_ID: i32 = $id;
}
)*
@ -231,8 +230,7 @@ macro_rules! def_enum {
}
impl $name {
#[allow(unused)]
const PACKET_ID: i32 = $id;
pub const PACKET_ID: i32 = $id;
}
)*
}
@ -289,7 +287,7 @@ macro_rules! def_bitfield {
$(
#[doc = "Gets the " $bit " bit on this bitfield.\n"]
$(#[$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()
}
@ -313,7 +311,7 @@ macro_rules! def_bitfield {
let mut s = f.debug_struct(stringify!($name));
paste! {
$(
s.field(stringify!($bit), &self. [<get_ $bit:snake>]());
s.field(stringify!($bit), &self. $bit());
)*
}
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::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::status::{QueryPong, QueryResponse};
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 client = Client::new(
c2s_packet_channels,
&server.shared,
msg.ncd,
C::ClientState::default(),
);
let client = Client::new(c2s_packet_channels, msg.ncd, C::ClientState::default());
server.clients.insert(client);
}
@ -713,30 +710,28 @@ async fn handle_login<C: Config>(
c.enc.enable_compression(compression_threshold);
c.dec.enable_compression(compression_threshold);
let npd = NewClientData {
let ncd = NewClientData {
uuid,
username,
textures,
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}\"");
c.enc
.write_packet(&LoginDisconnect { reason })
.await?;
c.enc.write_packet(&LoginDisconnect { reason }).await?;
return Ok(None);
}
c.enc
.write_packet(&LoginSuccess {
uuid: npd.uuid,
username: npd.username.clone().into(),
uuid: ncd.uuid,
username: ncd.username.clone().into(),
properties: Vec::new(),
})
.await?;
Ok(Some(npd))
Ok(Some(ncd))
}
async fn handle_play<C: Config>(