Client Cleanup (#159)

# Noteworthy Changes
- Simplified the `client` module and update procedure. Sending packets
is not deferred unless necessary.
- Client events are no longer buffered in `Client` before reaching the
user.
- Expanded `ClientEvent` to account for most packets.
- Most types containing custom `state` now implement `Deref` and
`DerefMut`. This means you don't have to write `.state` all over the
place. `Server` was excluded from this because it does not play well
with the borrow checker.
- Fixed bugs related to entity visibility.
- Client now correctly holds the semaphore permit from the initial
connection.
- Other miscellaneous API changes throughout the project. 

# Known Issues
- Inventory stuff is still incomplete. The inventory examples have been
temporarily disabled.
This commit is contained in:
Ryan Johnson 2022-11-29 03:37:32 -08:00 committed by GitHub
parent 6437381339
commit 58f8197913
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 2162 additions and 1946 deletions

View file

@ -40,6 +40,7 @@ impl Config for Game {
type WorldState = ();
type ChunkState = ();
type PlayerListState = ();
type InventoryState = ();
fn dimensions(&self) -> Vec<Dimension> {
vec![Dimension {
@ -152,14 +153,17 @@ impl Config for Game {
.entities
.insert_with_uuid(EntityKind::Player, client.uuid(), ())
{
Some((id, _)) => client.state.entity_id = id,
Some((id, entity)) => {
entity.set_world(world_id);
client.entity_id = id
}
None => {
client.disconnect("Conflicting UUID");
return false;
}
}
client.spawn(world_id);
client.respawn(world_id);
client.set_flat(true);
client.teleport(spawn_pos, 0.0, 0.0);
client.set_player_list(server.state.player_list.clone());
@ -178,9 +182,11 @@ impl Config for Game {
client.set_game_mode(GameMode::Creative);
}
while client.next_event().is_some() {}
if client.is_disconnected() {
self.player_count.fetch_sub(1, Ordering::SeqCst);
server.entities.remove(client.state.entity_id);
server.entities.remove(client.entity_id);
if let Some(id) = &server.state.player_list {
server.player_lists.get_mut(id).remove(client.uuid());
}

View file

@ -1,8 +1,6 @@
use std::net::SocketAddr;
use std::sync::atomic::{AtomicUsize, Ordering};
use num::Integer;
use valence::client::DiggingStatus;
use valence::prelude::*;
pub fn main() -> ShutdownResult {
@ -31,8 +29,8 @@ struct ClientState {
const MAX_PLAYERS: usize = 10;
const SIZE_X: usize = 100;
const SIZE_Z: usize = 100;
const SIZE_X: i32 = 100;
const SIZE_Z: i32 = 100;
#[async_trait]
impl Config for Game {
@ -42,6 +40,7 @@ impl Config for Game {
type WorldState = ();
type ChunkState = ();
type PlayerListState = ();
type InventoryState = ();
fn dimensions(&self) -> Vec<Dimension> {
vec![Dimension {
@ -70,33 +69,11 @@ impl Config for Game {
server.state.player_list = Some(server.player_lists.insert(()).0);
// initialize chunks
for chunk_z in -2..Integer::div_ceil(&(SIZE_Z as i32), &16) + 2 {
for chunk_x in -2..Integer::div_ceil(&(SIZE_X as i32), &16) + 2 {
world.chunks.insert(
[chunk_x as i32, chunk_z as i32],
UnloadedChunk::default(),
(),
);
}
}
// initialize blocks in the chunks
for chunk_x in 0..Integer::div_ceil(&SIZE_X, &16) {
for chunk_z in 0..Integer::div_ceil(&SIZE_Z, &16) {
let chunk = world
for z in 0..SIZE_Z {
for x in 0..SIZE_X {
world
.chunks
.get_mut((chunk_x as i32, chunk_z as i32))
.unwrap();
for x in 0..16 {
for z in 0..16 {
let cell_x = chunk_x * 16 + x;
let cell_z = chunk_z * 16 + z;
if cell_x < SIZE_X && cell_z < SIZE_Z {
chunk.set_block_state(x, 63, z, BlockState::GRASS_BLOCK);
}
}
}
.set_block_state([x, 0, z], BlockState::GRASS_BLOCK);
}
}
}
@ -123,14 +100,17 @@ impl Config for Game {
.entities
.insert_with_uuid(EntityKind::Player, client.uuid(), ())
{
Some((id, _)) => client.state.entity_id = id,
Some((id, entity)) => {
entity.set_world(world_id);
client.entity_id = id
}
None => {
client.disconnect("Conflicting UUID");
return false;
}
}
client.spawn(world_id);
client.respawn(world_id);
client.set_flat(true);
client.teleport(spawn_pos, 0.0, 0.0);
client.set_player_list(server.state.player_list.clone());
@ -150,46 +130,24 @@ impl Config for Game {
client.send_message("Welcome to Valence! Build something cool.".italic());
}
if client.is_disconnected() {
self.player_count.fetch_sub(1, Ordering::SeqCst);
server.entities.remove(client.state.entity_id);
if let Some(id) = &server.state.player_list {
server.player_lists.get_mut(id).remove(client.uuid());
}
return false;
}
let player = server.entities.get_mut(client.entity_id).unwrap();
let player = server.entities.get_mut(client.state.entity_id).unwrap();
if client.position().y <= -20.0 {
client.teleport(spawn_pos, client.yaw(), client.pitch());
}
while let Some(event) = handle_event_default(client, player) {
while let Some(event) = client.next_event() {
event.handle_default(client, player);
match event {
ClientEvent::Digging {
position, status, ..
} => {
match status {
DiggingStatus::Start => {
// Allows clients in creative mode to break blocks.
if client.game_mode() == GameMode::Creative {
world.chunks.set_block_state(position, BlockState::AIR);
}
}
DiggingStatus::Finish => {
// Allows clients in survival mode to break blocks.
world.chunks.set_block_state(position, BlockState::AIR);
}
_ => {}
ClientEvent::StartDigging { position, .. } => {
// Allows clients in creative mode to break blocks.
if client.game_mode() == GameMode::Creative {
world.chunks.set_block_state(position, BlockState::AIR);
}
}
ClientEvent::InteractWithBlock {
hand,
location,
face,
..
} => {
ClientEvent::FinishDigging { position, .. } => {
// Allows clients in survival mode to break blocks.
world.chunks.set_block_state(position, BlockState::AIR);
}
ClientEvent::UseItemOnBlock { .. } => {
// TODO: reimplement when inventories are re-added.
/*
if hand == Hand::Main {
if let Some(stack) = client.held_item() {
if let Some(held_block_kind) = stack.item.to_block_kind() {
@ -200,24 +158,38 @@ impl Config for Game {
{
if world
.chunks
.block_state(location)
.block_state(position)
.map(|s| s.is_replaceable())
.unwrap_or(false)
{
world.chunks.set_block_state(location, block_to_place);
world.chunks.set_block_state(position, block_to_place);
} else {
let place_at = location.get_in_direction(face);
let place_at = position.get_in_direction(face);
world.chunks.set_block_state(place_at, block_to_place);
}
}
}
}
}
*/
}
_ => {}
}
}
if client.is_disconnected() {
self.player_count.fetch_sub(1, Ordering::SeqCst);
server.entities.remove(client.entity_id);
if let Some(id) = &server.state.player_list {
server.player_lists.get_mut(id).remove(client.uuid());
}
return false;
}
if client.position().y <= -20.0 {
client.teleport(spawn_pos, client.yaw(), client.pitch());
}
true
});
}

View file

@ -1,3 +1,8 @@
pub fn main() {
todo!("reimplement when inventories are re-added");
}
/*
use std::net::SocketAddr;
use std::sync::atomic::{AtomicUsize, Ordering};
@ -49,6 +54,7 @@ impl Config for Game {
type WorldState = ();
type ChunkState = ();
type PlayerListState = ();
type InventoryState = ();
fn dimensions(&self) -> Vec<Dimension> {
vec![Dimension {
@ -101,7 +107,11 @@ impl Config for Game {
// create chest inventory
let inv = ConfigurableInventory::new(27, VarInt(2), None);
let (id, _inv) = server.inventories.insert(inv);
let title = "Extra".italic()
+ " Chesty".not_italic().bold().color(Color::RED)
+ " Chest".not_italic();
let (id, _inv) = server.inventories.insert(inv, title, ());
server.state.chest = id;
}
@ -137,14 +147,17 @@ impl Config for Game {
.entities
.insert_with_uuid(EntityKind::Player, client.uuid(), ())
{
Some((id, _)) => client.state.entity_id = id,
Some((id, entity)) => {
entity.set_world(world_id);
client.state.entity_id = id
}
None => {
client.disconnect("Conflicting UUID");
return false;
}
}
client.spawn(world_id);
client.respawn(world_id);
client.set_flat(true);
client.teleport(spawn_pos, 0.0, 0.0);
client.set_player_list(server.state.player_list.clone());
@ -163,35 +176,17 @@ impl Config for Game {
client.send_message("Welcome to Valence! Sneak to give yourself an item.".italic());
}
if client.is_disconnected() {
self.player_count.fetch_sub(1, Ordering::SeqCst);
server.entities.remove(client.state.entity_id);
if let Some(id) = &server.state.player_list {
server.player_lists.get_mut(id).remove(client.uuid());
}
return false;
}
let player = server.entities.get_mut(client.state.entity_id).unwrap();
if client.position().y <= -20.0 {
client.teleport(spawn_pos, client.yaw(), client.pitch());
}
while let Some(event) = handle_event_default(client, player) {
while let Some(event) = client.next_event() {
event.handle_default(client, player);
match event {
ClientEvent::InteractWithBlock { hand, location, .. } => {
ClientEvent::UseItemOnBlock { hand, position, .. } => {
if hand == Hand::Main
&& world.chunks.block_state(location) == Some(BlockState::CHEST)
&& world.chunks.block_state(position) == Some(BlockState::CHEST)
{
client.send_message("Opening chest!");
client.open_inventory(
&server.inventories,
server.state.chest,
"Extra".italic()
+ " Chesty".not_italic().bold().color(Color::RED)
+ " Chest".not_italic(),
);
client.open_inventory(server.state.chest);
}
}
ClientEvent::CloseScreen { window_id } => {
@ -207,6 +202,7 @@ impl Config for Game {
mode,
slot_changes,
carried_item,
..
} => {
println!(
"window_id: {:?}, state_id: {:?}, slot_id: {:?}, mode: {:?}, \
@ -244,6 +240,19 @@ impl Config for Game {
}
}
if client.is_disconnected() {
self.player_count.fetch_sub(1, Ordering::SeqCst);
server.entities.remove(client.state.entity_id);
if let Some(id) = &server.state.player_list {
server.player_lists.get_mut(id).remove(client.uuid());
}
return false;
}
if client.position().y <= -20.0 {
client.teleport(spawn_pos, client.yaw(), client.pitch());
}
true
});
}
@ -256,3 +265,4 @@ fn rotate_items(inv: &mut ConfigurableInventory) {
inv.set_slot((i - 1) as SlotId, b);
}
}
*/

View file

@ -47,6 +47,7 @@ impl Config for Game {
type WorldState = ();
type ChunkState = ();
type PlayerListState = ();
type InventoryState = ();
async fn server_list_ping(
&self,
@ -128,12 +129,12 @@ impl Config for Game {
}
};
player.state.client = client_id;
player.set_world(world_id);
player.client = client_id;
client.state.player = player_id;
client.state.extra_knockback = true;
client.player = player_id;
client.spawn(world_id);
client.respawn(world_id);
client.set_flat(true);
client.set_game_mode(GameMode::Survival);
client.teleport(
@ -164,9 +165,42 @@ impl Config for Game {
}
}
while let Some(event) = client.next_event() {
let player = server
.entities
.get_mut(client.player)
.expect("missing player entity");
event.handle_default(client, player);
match event {
ClientEvent::StartSprinting => {
client.extra_knockback = true;
}
ClientEvent::StopSprinting => {
client.extra_knockback = false;
}
ClientEvent::InteractWithEntity { entity_id, .. } => {
if let Some((id, target)) = server.entities.get_with_raw_id_mut(entity_id) {
if !target.attacked
&& current_tick - target.last_attack_time >= 10
&& id != client.player
{
target.attacked = true;
target.attacker_pos = client.position();
target.extra_knockback = client.extra_knockback;
target.last_attack_time = current_tick;
client.extra_knockback = false;
}
}
}
_ => {}
}
}
if client.is_disconnected() {
self.player_count.fetch_sub(1, Ordering::SeqCst);
server.entities.remove(client.state.player);
server.entities.remove(client.player);
if let Some(id) = &server.state {
server.player_lists.get_mut(id).remove(client.uuid());
}
@ -185,70 +219,28 @@ impl Config for Game {
);
}
loop {
let player = server
.entities
.get_mut(client.state.player)
.expect("missing player entity");
match handle_event_default(client, player) {
Some(ClientEvent::StartSprinting) => {
client.state.extra_knockback = true;
}
Some(ClientEvent::StopSprinting) => {
client.state.extra_knockback = false;
}
Some(ClientEvent::InteractWithEntity { id, .. }) => {
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;
}
}
}
Some(_) => {}
None => break,
}
}
true
});
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) {
if entity.attacked {
entity.attacked = false;
if let Some(victim) = server.clients.get_mut(entity.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 attacker_pos = Vec2::new(entity.attacker_pos.x, entity.attacker_pos.z);
let dir = (victim_pos - attacker_pos).normalized();
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
};
let knockback_xz = if entity.extra_knockback { 18.0 } else { 8.0 };
let knockback_y = if entity.extra_knockback { 8.432 } else { 6.432 };
let vel = Vec3::new(dir.x * knockback_xz, knockback_y, dir.y * knockback_xz);
victim.set_velocity(vel.as_());
entity.push_event(EntityEvent::DamageFromGenericSource);
entity.push_event(EntityEvent::Damage);
victim.push_entity_event(EntityEvent::DamageFromGenericSource);
victim.push_entity_event(EntityEvent::Damage);
victim.send_entity_event(EntityEvent::DamageFromGenericSource);
victim.send_entity_event(EntityEvent::Damage);
}
}
}

View file

@ -1,5 +1,5 @@
use std::mem;
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
use std::net::SocketAddr;
use std::sync::atomic::{AtomicUsize, Ordering};
use num::Integer;
@ -52,10 +52,7 @@ impl Config for Game {
type WorldState = ();
type ChunkState = ();
type PlayerListState = ();
fn address(&self) -> SocketAddr {
SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), 25565).into() // TODO remove
}
type InventoryState = ();
fn dimensions(&self) -> Vec<Dimension> {
vec![Dimension {
@ -128,14 +125,17 @@ impl Config for Game {
.entities
.insert_with_uuid(EntityKind::Player, client.uuid(), ())
{
Some((id, _)) => client.state.entity_id = id,
Some((id, entity)) => {
entity.set_world(world_id);
client.entity_id = id
}
None => {
client.disconnect("Conflicting UUID");
return false;
}
}
client.spawn(world_id);
client.respawn(world_id);
client.set_flat(true);
client.teleport(spawn_pos, 0.0, 0.0);
client.set_player_list(server.state.player_list.clone());
@ -157,25 +157,12 @@ impl Config for Game {
);
}
if client.is_disconnected() {
self.player_count.fetch_sub(1, Ordering::SeqCst);
server.entities.remove(client.state.entity_id);
if let Some(id) = &server.state.player_list {
server.player_lists.get_mut(id).remove(client.uuid());
}
return false;
}
let player = server.entities.get_mut(client.entity_id).unwrap();
let player = server.entities.get_mut(client.state.entity_id).unwrap();
if client.position().y <= 0.0 {
client.teleport(spawn_pos, client.yaw(), client.pitch());
server.state.board.fill(false);
}
while let Some(event) = handle_event_default(client, player) {
while let Some(event) = client.next_event() {
event.handle_default(client, player);
match event {
ClientEvent::Digging { position, .. } => {
ClientEvent::StartDigging { position, .. } => {
if (0..SIZE_X as i32).contains(&position.x)
&& (0..SIZE_Z as i32).contains(&position.z)
&& position.y == BOARD_Y
@ -195,7 +182,7 @@ impl Config for Game {
server.state.board[index] = true;
}
}
ClientEvent::InteractWithBlock { hand, .. } => {
ClientEvent::UseItemOnBlock { hand, .. } => {
if hand == Hand::Main {
client.send_message("I said left click, not right click!".italic());
}
@ -204,6 +191,20 @@ impl Config for Game {
}
}
if client.is_disconnected() {
self.player_count.fetch_sub(1, Ordering::SeqCst);
server.entities.remove(client.entity_id);
if let Some(id) = &server.state.player_list {
server.player_lists.get_mut(id).remove(client.uuid());
}
return false;
}
if client.position().y <= 0.0 {
client.teleport(spawn_pos, client.yaw(), client.pitch());
server.state.board.fill(false);
}
if let TrackedData::Player(data) = player.data() {
let sneaking = data.get_pose() == Pose::Sneaking;
if sneaking != server.state.paused {
@ -212,8 +213,8 @@ impl Config for Game {
Ident::new("block.note_block.pling").unwrap(),
SoundCategory::Block,
client.position(),
0.5f32,
if sneaking { 0.5f32 } else { 1f32 },
0.5,
if sneaking { 0.5 } else { 1.0 },
);
}
}

View file

@ -3,7 +3,6 @@ use std::f64::consts::TAU;
use std::net::SocketAddr;
use std::sync::atomic::{AtomicUsize, Ordering};
use uuid::Uuid;
use valence::prelude::*;
pub fn main() -> ShutdownResult {
@ -29,6 +28,11 @@ struct ServerState {
cows: Vec<EntityId>,
}
#[derive(Default)]
struct ClientState {
entity_id: EntityId,
}
const MAX_PLAYERS: usize = 10;
const SPAWN_POS: BlockPos = BlockPos::new(0, 100, -25);
@ -36,11 +40,12 @@ const SPAWN_POS: BlockPos = BlockPos::new(0, 100, -25);
#[async_trait]
impl Config for Game {
type ServerState = ServerState;
type ClientState = EntityId;
type ClientState = ClientState;
type EntityState = ();
type WorldState = ();
type ChunkState = ();
type PlayerListState = ();
type InventoryState = ();
async fn server_list_ping(
&self,
@ -108,14 +113,17 @@ impl Config for Game {
.entities
.insert_with_uuid(EntityKind::Player, client.uuid(), ())
{
Some((id, _)) => client.state = id,
Some((id, entity)) => {
entity.set_world(world_id);
client.entity_id = id
}
None => {
client.disconnect("Conflicting UUID");
return false;
}
}
client.spawn(world_id);
client.respawn(world_id);
client.set_flat(true);
client.set_game_mode(GameMode::Creative);
client.teleport(
@ -146,17 +154,19 @@ impl Config for Game {
if let Some(id) = &server.state.player_list {
server.player_lists.get_mut(id).remove(client.uuid());
}
server.entities.remove(client.state);
server.entities.remove(client.entity_id);
return false;
}
let entity = server
.entities
.get_mut(client.state)
.get_mut(client.entity_id)
.expect("missing player entity");
while handle_event_default(client, entity).is_some() {}
while let Some(event) = client.next_event() {
event.handle_default(client, entity);
}
true
});
@ -204,9 +214,9 @@ impl Config for Game {
/// Distributes N points on the surface of a unit sphere.
fn fibonacci_spiral(n: usize) -> impl Iterator<Item = Vec3<f64>> {
(0..n).map(move |i| {
let golden_ratio = (1.0 + 5_f64.sqrt()) / 2.0;
let golden_ratio = (1.0 + 5_f64.sqrt()) / 2.0;
(0..n).map(move |i| {
// Map to unit square
let x = i as f64 / golden_ratio % 1.0;
let y = i as f64 / n as f64;

View file

@ -27,12 +27,9 @@ struct ClientState {
can_respawn: bool,
}
struct WorldState {
player_list: PlayerListId,
}
#[derive(Default)]
struct ServerState {
player_list: Option<PlayerListId>,
first_world: WorldId,
second_world: WorldId,
third_world: WorldId,
@ -71,9 +68,10 @@ impl Config for Game {
type ServerState = ServerState;
type ClientState = ClientState;
type EntityState = ();
type WorldState = WorldState;
type WorldState = ();
type ChunkState = ();
type PlayerListState = ();
type InventoryState = ();
fn dimensions(&self) -> Vec<Dimension> {
vec![
@ -107,6 +105,7 @@ impl Config for Game {
// We created server with meaningless default state.
// Let's create three worlds and create new ServerState.
server.state = ServerState {
player_list: Some(server.player_lists.insert(()).0),
first_world: create_world(server, FIRST_WORLD_SPAWN_BLOCK, WhichWorld::First),
second_world: create_world(server, SECOND_WORLD_SPAWN_BLOCK, WhichWorld::Second),
third_world: create_world(server, THIRD_WORLD_SPAWN_BLOCK, WhichWorld::Third),
@ -131,17 +130,17 @@ impl Config for Game {
.entities
.insert_with_uuid(EntityKind::Player, client.uuid(), ())
{
Some((id, _)) => client.state.entity_id = id,
Some((id, entity)) => {
entity.set_world(server.state.first_world);
client.entity_id = id
}
None => {
client.disconnect("Conflicting UUID");
return false;
}
}
let first_world_id = server.state.first_world;
let first_world = server.worlds.get(first_world_id).unwrap();
client.state.respawn_location = (
client.respawn_location = (
server.state.first_world,
block_pos_to_vec(FIRST_WORLD_SPAWN_BLOCK),
);
@ -150,14 +149,14 @@ impl Config for Game {
client.set_spawn_position(FIRST_WORLD_SPAWN_BLOCK, 0.0);
client.set_flat(true);
client.spawn(first_world_id);
client.teleport(client.state.respawn_location.1, 0.0, 0.0);
client.respawn(server.state.first_world);
client.teleport(client.respawn_location.1, 0.0, 0.0);
client.set_player_list(first_world.state.player_list.clone());
client.set_player_list(server.state.player_list.clone());
server
.player_lists
.get_mut(&first_world.state.player_list)
.get_mut(server.state.player_list.as_ref().unwrap())
.insert(
client.uuid(),
client.username(),
@ -178,28 +177,17 @@ impl Config for Game {
// TODO after inventory support is added, show interaction with compass.
if client.is_disconnected() {
self.player_count.fetch_sub(1, Ordering::SeqCst);
server.entities.remove(client.state.entity_id);
if let Some(list) = client.player_list() {
server.player_lists.get_mut(list).remove(client.uuid());
}
return false;
}
// Handling respawn locations
if !client.state.can_respawn {
if !client.can_respawn {
if client.position().y < 0.0 {
client.state.can_respawn = true;
client.can_respawn = true;
client.kill(None, "You fell");
// You could have also killed the player with `Client::set_health_and_food`,
// however you cannot send a message to the death screen
// that way
if client.world() == server.state.third_world {
// Falling in third world gets you back to the first world
client.state.respawn_location = (
client.respawn_location = (
server.state.first_world,
block_pos_to_vec(FIRST_WORLD_SPAWN_BLOCK),
);
@ -207,7 +195,7 @@ impl Config for Game {
} else {
// falling in first and second world will cause player to spawn in third
// world
client.state.respawn_location = (
client.respawn_location = (
server.state.third_world,
block_pos_to_vec(THIRD_WORLD_SPAWN_BLOCK),
);
@ -222,15 +210,15 @@ impl Config for Game {
if client.position().x >= LEFT_DEATH_LINE as f64 {
// Client went to the left, he dies
client.state.can_respawn = true;
client.can_respawn = true;
client.kill(None, death_msg);
}
if client.position().x <= RIGHT_DEATH_LINE as f64 {
// Client went to the right, he dies and spawns in world2
client.state.can_respawn = true;
client.can_respawn = true;
client.kill(None, death_msg);
client.state.respawn_location = (
client.respawn_location = (
server.state.second_world,
block_pos_to_vec(SECOND_WORLD_SPAWN_BLOCK),
);
@ -239,33 +227,46 @@ impl Config for Game {
}
}
let player = server.entities.get_mut(client.state.entity_id).unwrap();
let player = server.entities.get_mut(client.entity_id).unwrap();
while let Some(event) = handle_event_default(client, player) {
while let Some(event) = client.next_event() {
event.handle_default(client, player);
match event {
ClientEvent::RespawnRequest => {
if !client.state.can_respawn {
client.disconnect("Unexpected RespawnRequest");
ClientEvent::PerformRespawn => {
if !client.can_respawn {
client.disconnect("Unexpected PerformRespawn");
return false;
}
// Let's respawn our player. `spawn` will load the world, but we are
// responsible for teleporting the player.
// You can store respawn however you want, for example in `Client`'s state.
let spawn = client.state.respawn_location;
client.spawn(spawn.0);
let spawn = client.respawn_location;
client.respawn(spawn.0);
player.set_world(spawn.0);
client.teleport(spawn.1, 0.0, 0.0);
client.state.can_respawn = false;
client.can_respawn = false;
}
ClientEvent::StartSneaking => {
// Roll the credits, respawn after
client.state.can_respawn = true;
client.can_respawn = true;
client.win_game(true);
}
_ => {}
}
}
if client.is_disconnected() {
self.player_count.fetch_sub(1, Ordering::SeqCst);
server.entities.remove(client.entity_id);
if let Some(list) = client.player_list() {
server.player_lists.get_mut(list).remove(client.uuid());
}
return false;
}
true
});
}
@ -279,10 +280,7 @@ fn create_world(server: &mut Server<Game>, spawn_pos: BlockPos, world_type: Whic
WhichWorld::Third => server.shared.dimensions().nth(1).unwrap(),
};
let player_list = server.player_lists.insert(()).0;
let (world_id, world) = server
.worlds
.insert(dimension.0, WorldState { player_list });
let (world_id, world) = server.worlds.insert(dimension.0, ());
// Create chunks
for chunk_z in -3..3 {

View file

@ -42,6 +42,7 @@ impl Config for Game {
type WorldState = ();
type ChunkState = ();
type PlayerListState = ();
type InventoryState = ();
async fn server_list_ping(
&self,
@ -305,14 +306,17 @@ impl Config for Game {
.entities
.insert_with_uuid(EntityKind::Player, client.uuid(), ())
{
Some((id, _)) => client.state.player = id,
Some((id, entity)) => {
entity.set_world(world_id);
client.player = id
}
None => {
client.disconnect("Conflicting UUID");
return false;
}
}
client.spawn(world_id);
client.respawn(world_id);
client.set_flat(true);
client.set_game_mode(GameMode::Creative);
client.teleport(
@ -344,12 +348,17 @@ impl Config for Game {
);
}
let entity = server.entities.get_mut(client.player).unwrap();
while let Some(event) = client.next_event() {
event.handle_default(client, entity);
}
if client.is_disconnected() {
self.player_count.fetch_sub(1, Ordering::SeqCst);
if let Some(id) = &server.state {
server.player_lists.get_mut(id).remove(client.uuid());
}
server.entities.remove(client.state.player);
server.entities.remove(client.player);
return false;
}
@ -359,22 +368,21 @@ impl Config for Game {
let origin = Vec3::new(client_pos.x, client_pos.y + PLAYER_EYE_HEIGHT, client_pos.z);
let direction = from_yaw_and_pitch(client.yaw() as f64, client.pitch() as f64);
let not_self_or_bullet = |hit: &RaycastHit| {
hit.entity != client.state.player && hit.entity != client.state.shulker_bullet
hit.entity != client.player && hit.entity != client.shulker_bullet
};
if let Some(hit) = world
.spatial_index
.raycast(origin, direction, not_self_or_bullet)
{
let bullet =
if let Some(bullet) = server.entities.get_mut(client.state.shulker_bullet) {
bullet
} else {
let (id, bullet) = server.entities.insert(EntityKind::ShulkerBullet, ());
client.state.shulker_bullet = id;
bullet.set_world(world_id);
bullet
};
let bullet = if let Some(bullet) = server.entities.get_mut(client.shulker_bullet) {
bullet
} else {
let (id, bullet) = server.entities.insert(EntityKind::ShulkerBullet, ());
client.shulker_bullet = id;
bullet.set_world(world_id);
bullet
};
let mut hit_pos = origin + direction * hit.near;
let hitbox = bullet.hitbox();
@ -385,17 +393,10 @@ impl Config for Game {
client.set_action_bar("Intersection".color(Color::GREEN));
} else {
server.entities.remove(client.state.shulker_bullet);
server.entities.remove(client.shulker_bullet);
client.set_action_bar("No Intersection".color(Color::RED));
}
while handle_event_default(
client,
server.entities.get_mut(client.state.player).unwrap(),
)
.is_some()
{}
true
});
}

View file

@ -1,3 +1,8 @@
pub fn main() {
todo!("reimplement when inventories are re-added");
}
/*
use std::net::SocketAddr;
use std::sync::atomic::{AtomicUsize, Ordering};
@ -47,6 +52,7 @@ impl Config for Game {
type WorldState = ();
type ChunkState = ();
type PlayerListState = ();
type InventoryState = ();
fn dimensions(&self) -> Vec<Dimension> {
vec![Dimension {
@ -135,7 +141,7 @@ impl Config for Game {
}
}
client.spawn(world_id);
client.respawn(world_id);
client.set_flat(true);
client.teleport(spawn_pos, 0.0, 0.0);
client.set_player_list(server.state.player_list.clone());
@ -227,3 +233,4 @@ fn play_note(client: &mut Client<Game>, player: &mut Entity<Game>, clicked_slot:
});
}
}
*/

View file

@ -26,6 +26,11 @@ struct ServerState {
player_list: Option<PlayerListId>,
}
#[derive(Default)]
struct ChunkState {
keep_loaded: bool,
}
#[derive(Default)]
struct ClientState {
entity_id: EntityId,
@ -56,9 +61,9 @@ impl Config for Game {
type ClientState = ClientState;
type EntityState = ();
type WorldState = ();
/// If the chunk should stay loaded at the end of the tick.
type ChunkState = bool;
type ChunkState = ChunkState;
type PlayerListState = ();
type InventoryState = ();
async fn server_list_ping(
&self,
@ -80,8 +85,6 @@ impl Config for Game {
}
fn update(&self, server: &mut Server<Self>) {
//let (world_id, world) = server.worlds.iter_mut().next().unwrap();
server.clients.retain(|_, client| {
if client.created_this_tick() {
if self
@ -95,11 +98,15 @@ impl Config for Game {
return false;
}
let (world_id, world) = server.worlds.insert(DimensionId::default(), ());
match server
.entities
.insert_with_uuid(EntityKind::Player, client.uuid(), ())
{
Some((id, _)) => {
Some((id, entity)) => {
entity.set_world(world_id);
// create client state
client.state = ClientState {
entity_id: id,
@ -108,32 +115,28 @@ impl Config for Game {
combo: 0,
last_block_timestamp: 0,
target_y: 0,
world_id: WorldId::NULL,
world_id,
};
}
None => {
client.disconnect("Conflicting UUID");
server.worlds.remove(world_id);
return false;
}
}
let (world_id, world) = server.worlds.insert(DimensionId::default(), ());
client.state.world_id = world_id;
for chunk_z in -1..3 {
for chunk_x in -2..2 {
world.chunks.insert(
(chunk_x as i32, chunk_z as i32),
UnloadedChunk::default(),
true,
ChunkState { keep_loaded: true },
);
}
}
client.spawn(world_id);
client.respawn(world_id);
client.set_flat(true);
// client.teleport(spawn_pos, 0.0, 0.0);
client.set_player_list(server.state.player_list.clone());
if let Some(id) = &server.state.player_list {
@ -152,34 +155,19 @@ impl Config for Game {
reset(client, world);
}
let (world_id, world) = server
.worlds
.iter_mut()
.find(|w| w.0 == client.state.world_id)
.unwrap();
if client.is_disconnected() {
self.player_count.fetch_sub(1, Ordering::SeqCst);
server.entities.remove(client.state.entity_id);
if let Some(id) = &server.state.player_list {
server.player_lists.get_mut(id).remove(client.uuid());
}
for block in &client.state.blocks {
world.chunks.set_block_state(*block, BlockState::AIR);
}
client.state.blocks.clear();
client.state.score = 0;
server.worlds.remove(world_id);
return false;
}
let world_id = client.world_id;
let world = server.worlds.get_mut(world_id).unwrap();
let p = client.position();
for pos in chunks_in_view_distance(ChunkPos::at(p.x, p.z), 3) {
if let Some(chunk) = world.chunks.get_mut(pos) {
chunk.state = true;
chunk.keep_loaded = true;
} else {
world.chunks.insert(pos, UnloadedChunk::default(), true);
world.chunks.insert(
pos,
UnloadedChunk::default(),
ChunkState { keep_loaded: true },
);
}
}
@ -187,7 +175,6 @@ impl Config for Game {
client.send_message(
"Your score was ".italic()
+ client
.state
.score
.to_string()
.color(Color::GOLD)
@ -199,32 +186,31 @@ impl Config for Game {
}
let pos_under_player = BlockPos::new(
(client.position().x - 0.5f64).round() as i32,
(client.position().x - 0.5).round() as i32,
client.position().y as i32 - 1,
(client.position().z - 0.5f64).round() as i32,
(client.position().z - 0.5).round() as i32,
);
if let Some(index) = client
.state
.blocks
.iter()
.position(|block| *block == pos_under_player)
{
if index > 0 {
let power_result = 2.0f32.powf((client.state.combo as f32) / 45.0);
let power_result = 2.0f32.powf((client.combo as f32) / 45.0);
let max_time_taken = (1000.0f32 * (index as f32) / power_result) as u128;
let current_time_millis = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis();
if current_time_millis - client.state.last_block_timestamp < max_time_taken {
client.state.combo += index as u32
if current_time_millis - client.last_block_timestamp < max_time_taken {
client.combo += index as u32
} else {
client.state.combo = 0
client.combo = 0
}
let pitch = 0.9 + ((client.state.combo as f32) - 1.0) * 0.05;
let pitch = 0.9 + ((client.combo as f32) - 1.0) * 0.05;
for _ in 0..index {
generate_next_block(client, world, true)
@ -239,12 +225,7 @@ impl Config for Game {
);
client.set_title(
"",
client
.state
.score
.to_string()
.color(Color::LIGHT_PURPLE)
.bold(),
client.score.to_string().color(Color::LIGHT_PURPLE).bold(),
SetTitleAnimationTimes {
fade_in: 0,
stay: 7,
@ -254,23 +235,33 @@ impl Config for Game {
}
}
while handle_event_default(
client,
server.entities.get_mut(client.state.entity_id).unwrap(),
)
.is_some()
{}
let player = server.entities.get_mut(client.entity_id).unwrap();
while let Some(event) = client.next_event() {
event.handle_default(client, player);
}
// Remove chunks outside the view distance of players.
world.chunks.retain(|_, chunk| {
if chunk.state {
chunk.state = false;
if chunk.keep_loaded {
chunk.keep_loaded = false;
true
} else {
false
}
});
if client.is_disconnected() {
self.player_count.fetch_sub(1, Ordering::SeqCst);
server.entities.remove(client.entity_id);
if let Some(id) = &server.state.player_list {
server.player_lists.get_mut(id).remove(client.uuid());
}
server.worlds.remove(world_id);
return false;
}
true
});
}
@ -283,19 +274,19 @@ fn reset(client: &mut Client<Game>, world: &mut World<Game>) {
world.chunks.insert(
(chunk_x as i32, chunk_z as i32),
UnloadedChunk::default(),
true,
ChunkState { keep_loaded: true },
);
}
}
client.state.score = 0;
client.state.combo = 0;
client.score = 0;
client.combo = 0;
for block in &client.state.blocks {
for block in &client.blocks {
world.chunks.set_block_state(*block, BlockState::AIR);
}
client.state.blocks.clear();
client.state.blocks.push_back(START_POS);
client.blocks.clear();
client.blocks.push_back(START_POS);
world.chunks.set_block_state(START_POS, BlockState::STONE);
for _ in 0..10 {
@ -315,19 +306,19 @@ fn reset(client: &mut Client<Game>, world: &mut World<Game>) {
fn generate_next_block(client: &mut Client<Game>, world: &mut World<Game>, in_game: bool) {
if in_game {
let removed_block = client.state.blocks.pop_front().unwrap();
let removed_block = client.blocks.pop_front().unwrap();
world.chunks.set_block_state(removed_block, BlockState::AIR);
client.state.score += 1
client.score += 1
}
let last_pos = *client.state.blocks.back().unwrap();
let block_pos = generate_random_block(last_pos, client.state.target_y);
let last_pos = *client.blocks.back().unwrap();
let block_pos = generate_random_block(last_pos, client.target_y);
if last_pos.y == START_POS.y {
client.state.target_y = 0
client.target_y = 0
} else if last_pos.y < START_POS.y - 30 || last_pos.y > START_POS.y + 30 {
client.state.target_y = START_POS.y;
client.target_y = START_POS.y;
}
let mut rng = rand::thread_rng();
@ -335,10 +326,10 @@ fn generate_next_block(client: &mut Client<Game>, world: &mut World<Game>, in_ga
world
.chunks
.set_block_state(block_pos, *BLOCK_TYPES.choose(&mut rng).unwrap());
client.state.blocks.push_back(block_pos);
client.blocks.push_back(block_pos);
// Combo System
client.state.last_block_timestamp = SystemTime::now()
client.last_block_timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis();

View file

@ -2,7 +2,6 @@ use std::net::SocketAddr;
use std::sync::atomic::{AtomicUsize, Ordering};
use valence::prelude::*;
use valence_protocol::packets::c2s::play::ResourcePackC2s;
use valence_protocol::types::EntityInteraction;
pub fn main() -> ShutdownResult {
@ -14,7 +13,7 @@ pub fn main() -> ShutdownResult {
},
ServerState {
player_list: None,
sheep_id: None,
sheep_id: EntityId::NULL,
},
)
}
@ -25,7 +24,7 @@ struct Game {
struct ServerState {
player_list: Option<PlayerListId>,
sheep_id: Option<EntityId>,
sheep_id: EntityId,
}
#[derive(Default)]
@ -45,6 +44,7 @@ impl Config for Game {
type WorldState = ();
type ChunkState = ();
type PlayerListState = ();
type InventoryState = ();
async fn server_list_ping(
&self,
@ -73,7 +73,7 @@ impl Config for Game {
}
let (sheep_id, sheep) = server.entities.insert(EntityKind::Sheep, ());
server.state.sheep_id = Some(sheep_id);
server.state.sheep_id = sheep_id;
sheep.set_world(world_id);
sheep.set_position([
SPAWN_POS.x as f64 + 0.5,
@ -108,14 +108,14 @@ impl Config for Game {
.entities
.insert_with_uuid(EntityKind::Player, client.uuid(), ())
{
Some((id, _)) => client.state.entity_id = id,
Some((id, _)) => client.entity_id = id,
None => {
client.disconnect("Conflicting UUID");
return false;
}
}
client.spawn(world_id);
client.respawn(world_id);
client.set_flat(true);
client.set_game_mode(GameMode::Creative);
client.teleport(
@ -140,6 +140,10 @@ impl Config for Game {
);
}
client.send_message(
"Hit the sheep above you to prompt for the resource pack again.".italic(),
);
set_example_pack(client);
}
@ -148,44 +152,37 @@ impl Config for Game {
if let Some(id) = &server.state.player_list {
server.player_lists.get_mut(id).remove(client.uuid());
}
server.entities.remove(client.state.entity_id);
server.entities.remove(client.entity_id);
return false;
}
let player = server.entities.get_mut(client.state.entity_id).unwrap();
let player = server.entities.get_mut(client.entity_id).unwrap();
while let Some(event) = handle_event_default(client, player) {
while let Some(event) = client.next_event() {
event.handle_default(client, player);
match event {
ClientEvent::InteractWithEntity { id, interact, .. } => {
ClientEvent::InteractWithEntity {
entity_id,
interact,
..
} => {
if interact == EntityInteraction::Attack
&& Some(id) == server.state.sheep_id
&& entity_id == server.state.sheep_id.to_raw()
{
set_example_pack(client);
}
}
ClientEvent::ResourcePackStatusChanged(s) => {
let message = match s {
ResourcePackC2s::SuccessfullyLoaded => {
"The resource pack was successfully loaded!".color(Color::GREEN)
}
ResourcePackC2s::Declined => {
"You declined the resource pack :(".color(Color::RED)
}
ResourcePackC2s::FailedDownload => {
"The resource pack download failed.".color(Color::RED)
}
_ => continue,
};
client.send_message(message.italic());
client.send_message(
"Hit the sheep above you to prompt the resource pack again."
.color(Color::GRAY)
.italic(),
);
ClientEvent::ResourcePackLoaded => {
client.send_message("Resource pack loaded!".color(Color::GREEN));
}
_ => (),
ClientEvent::ResourcePackDeclined => {
client.send_message("Resource pack declined.".color(Color::RED));
}
ClientEvent::ResourcePackFailedDownload => {
client.send_message("Resource pack download failed.".color(Color::RED));
}
_ => {}
}
}

View file

@ -50,6 +50,7 @@ impl Config for Game {
/// If the chunk should stay loaded at the end of the tick.
type ChunkState = bool;
type PlayerListState = ();
type InventoryState = ();
async fn server_list_ping(
&self,
@ -91,14 +92,17 @@ impl Config for Game {
.entities
.insert_with_uuid(EntityKind::Player, client.uuid(), ())
{
Some((id, _)) => client.state = id,
Some((id, entity)) => {
entity.set_world(world_id);
client.state = id
}
None => {
client.disconnect("Conflicting UUID");
return false;
}
}
client.spawn(world_id);
client.respawn(world_id);
client.set_flat(true);
client.set_game_mode(GameMode::Creative);
client.teleport([0.0, 200.0, 0.0], 0.0, 0.0);
@ -118,18 +122,9 @@ impl Config for Game {
client.send_message("Welcome to the terrain example!".italic());
}
if client.is_disconnected() {
self.player_count.fetch_sub(1, Ordering::SeqCst);
if let Some(id) = &server.state {
server.player_lists.get_mut(id).remove(client.uuid());
}
server.entities.remove(client.state);
return false;
}
if let Some(entity) = server.entities.get_mut(client.state) {
while handle_event_default(client, entity).is_some() {}
let player = server.entities.get_mut(client.state).unwrap();
while let Some(event) = client.next_event() {
event.handle_default(client, player);
}
let dist = client.view_distance();
@ -143,6 +138,16 @@ impl Config for Game {
}
}
if client.is_disconnected() {
self.player_count.fetch_sub(1, Ordering::SeqCst);
if let Some(id) = &server.state {
server.player_lists.get_mut(id).remove(client.uuid());
}
server.entities.remove(client.state);
return false;
}
true
});

View file

@ -34,10 +34,7 @@ impl Config for Game {
type WorldState = ();
type ChunkState = ();
type PlayerListState = ();
fn max_connections(&self) -> usize {
64
}
type InventoryState = ();
async fn server_list_ping(
&self,
@ -68,7 +65,7 @@ impl Config for Game {
.entities
.insert_with_uuid(EntityKind::Player, client.uuid(), ())
{
Some((id, _)) => client.state.entity_id = id,
Some((id, _)) => client.entity_id = id,
None => {
client.disconnect("Conflicting UUID");
return false;
@ -78,7 +75,7 @@ impl Config for Game {
let world_id = server.state.world;
client.set_flat(true);
client.spawn(world_id);
client.respawn(world_id);
client.teleport(SPAWN_POS, -90.0, 0.0);
client.set_game_mode(GameMode::Creative);
@ -164,18 +161,20 @@ impl Config for Game {
);
}
if client.is_disconnected() {
server.entities.remove(client.state.entity_id);
return false;
}
if client.position().y < 0.0 {
client.teleport(SPAWN_POS, 0.0, 0.0);
}
let player = server.entities.get_mut(client.state.entity_id).unwrap();
let player = server.entities.get_mut(client.entity_id).unwrap();
while handle_event_default(client, player).is_some() {}
while let Some(event) = client.next_event() {
event.handle_default(client, player);
}
if client.is_disconnected() {
server.entities.remove(client.entity_id);
return false;
}
true
});

View file

@ -35,6 +35,7 @@ impl Config for Game {
type WorldState = ();
type ChunkState = ();
type PlayerListState = ();
type InventoryState = ();
fn max_connections(&self) -> usize {
MAX_PLAYERS + 64
@ -115,7 +116,7 @@ impl Config for Game {
server.clients.retain(|_, client| {
if client.created_this_tick() {
client.spawn(world_id);
client.respawn(world_id);
client.set_flat(true);
client.teleport([0.0, 1.0, 0.0], 0.0, 0.0);
@ -157,12 +158,14 @@ impl Config for Game {
}
if WITH_PLAYER_ENTITIES {
if let Some(entity) = server.entities.get_mut(client.state) {
while handle_event_default(client, entity).is_some() {}
if let Some(player) = server.entities.get_mut(client.state) {
while let Some(event) = client.next_event() {
event.handle_default(client, player);
}
}
} else {
while let Some(event) = client.pop_event() {
if let ClientEvent::SettingsChanged { view_distance, .. } = event {
while let Some(event) = client.next_event() {
if let ClientEvent::UpdateSettings { view_distance, .. } = event {
client.set_view_distance(view_distance);
}
}

View file

@ -10,6 +10,7 @@ use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::io::Write;
use std::iter::FusedIterator;
use std::ops::{Deref, DerefMut};
use paletted_container::PalettedContainer;
use rayon::iter::{IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator};
@ -22,7 +23,7 @@ use valence_protocol::{BlockPos, BlockState, Encode, VarInt, VarLong};
use crate::biome::BiomeId;
pub use crate::chunk_pos::ChunkPos;
use crate::config::Config;
use crate::server::PlayPacketController;
use crate::server::PlayPacketSender;
use crate::util::bits_needed;
mod paletted_container;
@ -54,7 +55,7 @@ impl<C: Config> Chunks<C> {
///
/// **Note**: For the vanilla Minecraft client to see a chunk, all chunks
/// adjacent to it must also be loaded. Clients should not be spawned within
/// unloaded chunks via [`spawn`](crate::client::Client::spawn).
/// unloaded chunks via [`respawn`](crate::client::Client::respawn).
pub fn insert(
&mut self,
pos: impl Into<ChunkPos>,
@ -200,38 +201,46 @@ impl<C: Config> Chunks<C> {
}
}
/// Sets the block state at an absolute block position in world space.
/// Sets the block state at an absolute block position in world space. The
/// previous block state at the position is returned.
///
/// If the position is inside of a chunk, then `true` is returned and the
/// block is set. Otherwise, `false` is returned and the function has no
/// effect.
/// If the given position is not inside of a loaded chunk, then a new chunk
/// is created at the position before the block is set.
///
/// **Note**: if you need to set a large number of blocks, it may be more
/// efficient write to the chunks directly with
/// [`Chunk::set_block_state`].
pub fn set_block_state(&mut self, pos: impl Into<BlockPos>, block: BlockState) -> bool {
/// If the position is completely out of bounds, then no new chunk is
/// created and [`BlockState::AIR`] is returned.
pub fn set_block_state(&mut self, pos: impl Into<BlockPos>, block: BlockState) -> BlockState
where
C::ChunkState: Default,
{
let pos = pos.into();
let chunk_pos = ChunkPos::from(pos);
if let Some(chunk) = self.chunks.get_mut(&chunk_pos) {
if let Some(y) = pos
.y
.checked_sub(self.dimension_min_y)
.and_then(|y| y.try_into().ok())
{
if y < chunk.height() {
chunk.set_block_state(
pos.x.rem_euclid(16) as usize,
y,
pos.z.rem_euclid(16) as usize,
block,
);
return true;
}
}
let Some(y) = pos.y.checked_sub(self.dimension_min_y).and_then(|y| y.try_into().ok()) else {
return BlockState::AIR;
};
if y >= self.dimension_height as usize {
return BlockState::AIR;
}
false
let chunk = match self.chunks.entry(ChunkPos::from(pos)) {
Entry::Occupied(oe) => oe.into_mut(),
Entry::Vacant(ve) => {
let dimension_section_count = (self.dimension_height / 16) as usize;
ve.insert(LoadedChunk::new(
UnloadedChunk::default(),
dimension_section_count,
Default::default(),
))
}
};
chunk.set_block_state(
pos.x.rem_euclid(16) as usize,
y,
pos.z.rem_euclid(16) as usize,
block,
)
}
pub(crate) fn update(&mut self) {
@ -472,6 +481,20 @@ pub struct LoadedChunk<C: Config> {
created_this_tick: bool,
}
impl<C: Config> Deref for LoadedChunk<C> {
type Target = C::ChunkState;
fn deref(&self) -> &Self::Target {
&self.state
}
}
impl<C: Config> DerefMut for LoadedChunk<C> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.state
}
}
/// A 16x16x16 meter volume of blocks, biomes, and light in a chunk.
#[derive(Clone)]
struct ChunkSection {
@ -540,7 +563,7 @@ impl<C: Config> LoadedChunk<C> {
/// Queues the chunk data packet for this chunk with the given position.
pub(crate) fn chunk_data_packet(
&self,
ctrl: &mut PlayPacketController,
send: &mut PlayPacketSender,
scratch: &mut Vec<u8>,
pos: ChunkPos,
biome_registry_len: usize,
@ -567,7 +590,7 @@ impl<C: Config> LoadedChunk<C> {
)?;
}
ctrl.append_packet(&ChunkDataAndUpdateLight {
send.append_packet(&ChunkDataAndUpdateLight {
chunk_x: pos.x,
chunk_z: pos.z,
heightmaps: compound! {
@ -590,7 +613,7 @@ impl<C: Config> LoadedChunk<C> {
&self,
pos: ChunkPos,
min_y: i32,
ctrl: &mut PlayPacketController,
send: &mut PlayPacketSender,
) -> anyhow::Result<()> {
for (sect_y, sect) in self.sections.iter().enumerate() {
if sect.modified_blocks_count == 1 {
@ -611,8 +634,8 @@ impl<C: Config> LoadedChunk<C> {
let global_y = sect_y as i32 * 16 + (idx / (16 * 16)) as i32 + min_y;
let global_z = pos.z * 16 + (idx / 16 % 16) as i32;
ctrl.append_packet(&BlockUpdate {
location: BlockPos::new(global_x, global_y, global_z),
send.append_packet(&BlockUpdate {
position: BlockPos::new(global_x, global_y, global_z),
block_id: VarInt(block.to_raw() as _),
})?;
} else if sect.modified_blocks_count > 1 {
@ -637,7 +660,7 @@ impl<C: Config> LoadedChunk<C> {
| (pos.z as i64 & 0x3fffff) << 20
| (sect_y as i64 + min_y.div_euclid(16) as i64) & 0xfffff;
ctrl.append_packet(&UpdateSectionBlocks {
send.append_packet(&UpdateSectionBlocks {
chunk_section_position,
invert_trust_edges: false,
blocks,

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -40,6 +40,9 @@ pub trait Config: Sized + Send + Sync + 'static {
/// Custom state to store with every
/// [`PlayerList`](crate::player_list::PlayerList).
type PlayerListState: Send + Sync;
/// Custom state to store with every
/// [`Inventory`](crate::inventory::Inventory).
type InventoryState: Send + Sync;
/// Called once at startup to get the maximum number of simultaneous
/// connections allowed to the server. This includes all
@ -305,7 +308,6 @@ pub trait Config: Sized + Send + Sync + 'static {
}
/// The result of the [`server_list_ping`](Config::server_list_ping) callback.
#[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug)]
pub enum ServerListPing<'a> {
/// Responds to the server list ping with the given information.
@ -331,6 +333,18 @@ pub enum ServerListPing<'a> {
Ignore,
}
/// Represents an individual entry in the player sample.
#[derive(Clone, Debug, Serialize)]
pub struct PlayerSampleEntry<'a> {
/// The name of the player.
///
/// This string can contain
/// [legacy formatting codes](https://minecraft.fandom.com/wiki/Formatting_codes).
pub name: Cow<'a, str>,
/// The player UUID.
pub id: Uuid,
}
/// Describes how new connections to the server are handled.
#[non_exhaustive]
#[derive(Clone, PartialEq, Default)]
@ -386,26 +400,14 @@ pub enum ConnectionMode {
},
}
/// Represents an individual entry in the player sample.
#[derive(Clone, Debug, Serialize)]
pub struct PlayerSampleEntry<'a> {
/// The name of the player.
///
/// This string can contain
/// [legacy formatting codes](https://minecraft.fandom.com/wiki/Formatting_codes).
pub name: Cow<'a, str>,
/// The player UUID.
pub id: Uuid,
}
/// A minimal `Config` implementation for testing purposes.
#[cfg(test)]
pub(crate) struct MockConfig<S = (), Cl = (), E = (), W = (), Ch = (), P = ()> {
_marker: std::marker::PhantomData<(S, Cl, E, W, Ch, P)>,
pub(crate) struct MockConfig<S = (), Cl = (), E = (), W = (), Ch = (), P = (), I = ()> {
_marker: std::marker::PhantomData<(S, Cl, E, W, Ch, P, I)>,
}
#[cfg(test)]
impl<S, Cl, E, W, Ch, P> Config for MockConfig<S, Cl, E, W, Ch, P>
impl<S, Cl, E, W, Ch, P, I> Config for MockConfig<S, Cl, E, W, Ch, P, I>
where
S: Send + Sync + 'static,
Cl: Default + Send + Sync + 'static,
@ -413,6 +415,7 @@ where
W: Send + Sync + 'static,
Ch: Send + Sync + 'static,
P: Send + Sync + 'static,
I: Send + Sync + 'static,
{
type ServerState = S;
type ClientState = Cl;
@ -420,4 +423,5 @@ where
type WorldState = W;
type ChunkState = Ch;
type PlayerListState = P;
type InventoryState = I;
}

View file

@ -4,6 +4,7 @@ use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::iter::FusedIterator;
use std::num::NonZeroU32;
use std::ops::{Deref, DerefMut};
use bitfield_struct::bitfield;
pub use data::{EntityKind, TrackedData};
@ -17,7 +18,7 @@ use valence_protocol::{ByteAngle, RawBytes, VarInt};
use vek::{Aabb, Vec3};
use crate::config::Config;
use crate::server::PlayPacketController;
use crate::server::PlayPacketSender;
use crate::slab_versioned::{Key, VersionedSlab};
use crate::util::aabb_from_bottom_and_size;
use crate::world::WorldId;
@ -40,7 +41,7 @@ include!(concat!(env!("OUT_DIR"), "/entity_event.rs"));
pub struct Entities<C: Config> {
slab: VersionedSlab<Entity<C>>,
uuid_to_entity: HashMap<Uuid, EntityId>,
network_id_to_entity: HashMap<NonZeroU32, u32>,
raw_id_to_entity: HashMap<NonZeroU32, u32>,
}
impl<C: Config> Entities<C> {
@ -48,7 +49,7 @@ impl<C: Config> Entities<C> {
Self {
slab: VersionedSlab::new(),
uuid_to_entity: HashMap::new(),
network_id_to_entity: HashMap::new(),
raw_id_to_entity: HashMap::new(),
}
}
@ -93,7 +94,7 @@ impl<C: Config> Entities<C> {
});
// TODO check for overflowing version?
self.network_id_to_entity.insert(k.version(), k.index());
self.raw_id_to_entity.insert(k.version(), k.index());
ve.insert(EntityId(k));
@ -113,7 +114,7 @@ impl<C: Config> Entities<C> {
.remove(&e.uuid)
.expect("UUID should have been in UUID map");
self.network_id_to_entity
self.raw_id_to_entity
.remove(&entity.0.version())
.expect("network ID should have been in the network ID map");
@ -133,7 +134,7 @@ impl<C: Config> Entities<C> {
.remove(&v.uuid)
.expect("UUID should have been in UUID map");
self.network_id_to_entity
self.raw_id_to_entity
.remove(&k.version())
.expect("network ID should have been in the network ID map");
@ -174,10 +175,22 @@ impl<C: Config> Entities<C> {
self.slab.get_mut(entity.0)
}
pub(crate) fn get_with_network_id(&self, network_id: i32) -> Option<EntityId> {
let version = NonZeroU32::new(network_id as u32)?;
let index = *self.network_id_to_entity.get(&version)?;
Some(EntityId(Key::new(index, version)))
pub fn get_with_raw_id(&self, raw_id: i32) -> Option<(EntityId, &Entity<C>)> {
let version = NonZeroU32::new(raw_id as u32)?;
let index = *self.raw_id_to_entity.get(&version)?;
let id = EntityId(Key::new(index, version));
let entity = self.get(id)?;
Some((id, entity))
}
pub fn get_with_raw_id_mut(&mut self, raw_id: i32) -> Option<(EntityId, &mut Entity<C>)> {
let version = NonZeroU32::new(raw_id as u32)?;
let index = *self.raw_id_to_entity.get(&version)?;
let id = EntityId(Key::new(index, version));
let entity = self.get_mut(id)?;
Some((id, entity))
}
/// Returns an iterator over all entities on the server in an unspecified
@ -239,7 +252,7 @@ impl EntityId {
/// The value of the default entity ID which is always invalid.
pub const NULL: Self = Self(Key::NULL);
pub fn to_network_id(self) -> i32 {
pub fn to_raw(self) -> i32 {
self.0.version().get() as i32
}
}
@ -279,6 +292,20 @@ pub(crate) struct EntityBits {
_pad: u8,
}
impl<C: Config> Deref for Entity<C> {
type Target = C::EntityState;
fn deref(&self) -> &Self::Target {
&self.state
}
}
impl<C: Config> DerefMut for Entity<C> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.state
}
}
impl<C: Config> Entity<C> {
pub(crate) fn bits(&self) -> EntityBits {
self.bits
@ -709,13 +736,13 @@ impl<C: Config> Entity<C> {
/// been spawned.
pub(crate) fn send_initial_tracked_data(
&self,
ctrl: &mut PlayPacketController,
send: &mut PlayPacketSender,
this_id: EntityId,
) -> anyhow::Result<()> {
// TODO: cache metadata buffer?
if let Some(metadata) = self.variants.initial_tracked_data() {
ctrl.append_packet(&SetEntityMetadata {
entity_id: VarInt(this_id.to_network_id()),
send.append_packet(&SetEntityMetadata {
entity_id: VarInt(this_id.to_raw()),
metadata: RawBytes(&metadata),
})?;
}
@ -727,13 +754,13 @@ impl<C: Config> Entity<C> {
/// modified.
pub(crate) fn send_updated_tracked_data(
&self,
ctrl: &mut PlayPacketController,
send: &mut PlayPacketSender,
this_id: EntityId,
) -> anyhow::Result<()> {
// TODO: cache metadata buffer?
if let Some(metadata) = self.variants.updated_tracked_data() {
ctrl.append_packet(&SetEntityMetadata {
entity_id: VarInt(this_id.to_network_id()),
send.append_packet(&SetEntityMetadata {
entity_id: VarInt(this_id.to_raw()),
metadata: RawBytes(&metadata),
})?;
}
@ -745,10 +772,10 @@ impl<C: Config> Entity<C> {
pub(crate) fn send_spawn_packets(
&self,
this_id: EntityId,
ctrl: &mut PlayPacketController,
send: &mut PlayPacketSender,
) -> anyhow::Result<()> {
let with_object_data = |data| SpawnEntity {
entity_id: VarInt(this_id.to_network_id()),
entity_id: VarInt(this_id.to_raw()),
object_uuid: self.uuid,
kind: VarInt(self.kind() as i32),
position: self.new_position.into_array(),
@ -761,14 +788,14 @@ impl<C: Config> Entity<C> {
match &self.variants {
TrackedData::Marker(_) => {}
TrackedData::ExperienceOrb(_) => ctrl.append_packet(&SpawnExperienceOrb {
entity_id: VarInt(this_id.to_network_id()),
TrackedData::ExperienceOrb(_) => send.append_packet(&SpawnExperienceOrb {
entity_id: VarInt(this_id.to_raw()),
position: self.new_position.into_array(),
count: 0, // TODO
})?,
TrackedData::Player(_) => {
ctrl.append_packet(&SpawnPlayer {
entity_id: VarInt(this_id.to_network_id()),
send.append_packet(&SpawnPlayer {
entity_id: VarInt(this_id.to_raw()),
player_uuid: self.uuid,
position: self.new_position.into_array(),
yaw: ByteAngle::from_degrees(self.yaw),
@ -776,17 +803,17 @@ impl<C: Config> Entity<C> {
})?;
// Player spawn packet doesn't include head yaw for some reason.
ctrl.append_packet(&SetHeadRotation {
entity_id: VarInt(this_id.to_network_id()),
send.append_packet(&SetHeadRotation {
entity_id: VarInt(this_id.to_raw()),
head_yaw: ByteAngle::from_degrees(self.head_yaw),
})?;
}
TrackedData::ItemFrame(e) => ctrl.append_packet(&with_object_data(e.get_rotation()))?,
TrackedData::ItemFrame(e) => send.append_packet(&with_object_data(e.get_rotation()))?,
TrackedData::GlowItemFrame(e) => {
ctrl.append_packet(&with_object_data(e.get_rotation()))?
send.append_packet(&with_object_data(e.get_rotation()))?
}
TrackedData::Painting(_) => ctrl.append_packet(&with_object_data(
TrackedData::Painting(_) => send.append_packet(&with_object_data(
match ((self.yaw + 45.0).rem_euclid(360.0) / 90.0) as u8 {
0 => 3,
1 => 4,
@ -795,14 +822,14 @@ impl<C: Config> Entity<C> {
},
))?,
// TODO: set block state ID for falling block.
TrackedData::FallingBlock(_) => ctrl.append_packet(&with_object_data(1))?,
TrackedData::FallingBlock(_) => send.append_packet(&with_object_data(1))?,
TrackedData::FishingBobber(e) => {
ctrl.append_packet(&with_object_data(e.get_hook_entity_id()))?
send.append_packet(&with_object_data(e.get_hook_entity_id()))?
}
TrackedData::Warden(e) => {
ctrl.append_packet(&with_object_data((e.get_pose() == Pose::Emerging).into()))?
send.append_packet(&with_object_data((e.get_pose() == Pose::Emerging).into()))?
}
_ => ctrl.append_packet(&with_object_data(0))?,
_ => send.append_packet(&with_object_data(0))?,
}
Ok(())
@ -828,17 +855,17 @@ mod tests {
#[test]
fn entities_has_valid_new_state() {
let mut entities: Entities<MockConfig> = Entities::new();
let network_id: i32 = 8675309;
let raw_id: i32 = 8675309;
let entity_id = EntityId(Key::new(
202298,
NonZeroU32::new(network_id as u32).expect("Value given should never be zero!"),
NonZeroU32::new(raw_id as u32).expect("value given should never be zero!"),
));
let uuid = Uuid::from_bytes([2; 16]);
assert!(entities.is_empty());
assert!(entities.get(entity_id).is_none());
assert!(entities.get_mut(entity_id).is_none());
assert!(entities.get_with_uuid(uuid).is_none());
assert!(entities.get_with_network_id(network_id).is_none());
assert!(entities.get_with_raw_id(raw_id).is_none());
}
#[test]
@ -850,7 +877,7 @@ mod tests {
assert_eq!(entities.get(player_id).unwrap().state, 1);
let mut_player_entity = entities
.get_mut(player_id)
.expect("Failed to get mutable reference");
.expect("failed to get mutable reference");
mut_player_entity.state = 100;
assert_eq!(entities.get(player_id).unwrap().state, 100);
assert_eq!(entities.len(), 1);
@ -863,17 +890,17 @@ mod tests {
assert!(entities.is_empty());
let (zombie_id, zombie_entity) = entities
.insert_with_uuid(EntityKind::Zombie, uuid, 1)
.expect("Unexpected Uuid collision when inserting to an empty collection");
.expect("unexpected Uuid collision when inserting to an empty collection");
assert_eq!(zombie_entity.state, 1);
let maybe_zombie = entities
.get_with_uuid(uuid)
.expect("Uuid lookup failed on item already added to this collection");
.expect("UUID lookup failed on item already added to this collection");
assert_eq!(zombie_id, maybe_zombie);
assert_eq!(entities.len(), 1);
}
#[test]
fn entities_can_be_set_and_get_with_network_id() {
fn entities_can_be_set_and_get_with_raw_id() {
let mut entities: Entities<MockConfig> = Entities::new();
assert!(entities.is_empty());
let (boat_id, boat_entity) = entities.insert(EntityKind::Boat, 12);
@ -881,18 +908,20 @@ mod tests {
let (cat_id, cat_entity) = entities.insert(EntityKind::Cat, 75);
assert_eq!(cat_entity.state, 75);
let maybe_boat_id = entities
.get_with_network_id(boat_id.0.version.get() as i32)
.expect("Network id lookup failed on item already added to this collection");
.get_with_raw_id(boat_id.0.version.get() as i32)
.expect("raw id lookup failed on item already added to this collection")
.0;
let maybe_boat = entities
.get(maybe_boat_id)
.expect("Failed to look up item already added to collection");
.expect("failed to look up item already added to collection");
assert_eq!(maybe_boat.state, 12);
let maybe_cat_id = entities
.get_with_network_id(cat_id.0.version.get() as i32)
.expect("Network id lookup failed on item already added to this collection");
.get_with_raw_id(cat_id.0.version.get() as i32)
.expect("raw id lookup failed on item already added to this collection")
.0;
let maybe_cat = entities
.get(maybe_cat_id)
.expect("Failed to look up item already added to collection");
.expect("failed to look up item already added to collection");
assert_eq!(maybe_cat.state, 75);
assert_eq!(entities.len(), 2);
}
@ -904,7 +933,7 @@ mod tests {
let (player_id, _) = entities.insert(EntityKind::Player, 1);
let player_state = entities
.remove(player_id)
.expect("Failed to remove an item from the collection");
.expect("failed to remove an item from the collection");
assert_eq!(player_state, 1);
}

View file

@ -1,305 +1,183 @@
use std::ops::Range;
use std::iter::FusedIterator;
use std::mem;
use std::num::Wrapping;
use std::ops::{Deref, DerefMut};
use thiserror::Error;
use valence_protocol::{ItemStack, VarInt};
use valence_protocol::packets::s2c::play::SetContainerSlotEncode;
use valence_protocol::{InventoryKind, ItemStack, Text, VarInt};
use crate::config::Config;
use crate::server::PlayPacketSender;
use crate::slab_versioned::{Key, VersionedSlab};
pub type SlotId = i16;
pub trait Inventory {
fn slot(&self, slot_id: SlotId) -> Option<&ItemStack>;
/// Sets the slot to the desired contents. Returns the previous contents of
/// the slot.
fn set_slot(&mut self, slot_id: SlotId, slot: Option<ItemStack>) -> Option<ItemStack>;
fn slot_range(&self) -> Range<SlotId>;
fn slot_count(&self) -> usize {
self.slot_range().count()
}
// TODO: `entry()` style api
fn slots(&self) -> Vec<Option<&ItemStack>> {
(0..self.slot_count())
.map(|s| self.slot(s as SlotId))
.collect()
}
/// Decreases the count for stack in the slot by amount. If there is not
/// enough items in the stack to perform the operation, then it will fail.
///
/// Returns `Ok` if the stack had enough items, and the operation was
/// carried out. Otherwise, it returns `Err` if `amount > stack.count()`,
/// and no changes were made to the inventory.
#[allow(clippy::unnecessary_unwrap)]
fn consume(&mut self, slot_id: SlotId, amount: impl Into<u8>) -> Result<(), InventoryError> {
let amount: u8 = amount.into();
let slot = self.slot(slot_id).cloned();
if slot.is_some() {
// Intentionally not using `if let` so stack can be moved out of the slot as mut
// to avoid another clone later.
let mut stack = slot.unwrap();
if amount > stack.count() {
return Err(InventoryError);
}
let slot = if amount == stack.count() {
None
} else {
stack.set_count(stack.count() - amount);
Some(stack)
};
self.set_slot(slot_id, slot);
}
Ok(())
}
pub struct Inventories<C: Config> {
slab: VersionedSlab<Inventory<C>>,
}
pub(crate) trait InventoryDirtyable {
fn mark_dirty(&mut self, dirty: bool);
fn is_dirty(&self) -> bool;
}
/// Represents a player's Inventory.
#[derive(Debug, Clone)]
pub struct PlayerInventory {
pub(crate) slots: Box<[Option<ItemStack>; 46]>,
dirty: bool,
pub(crate) state_id: i32,
}
impl PlayerInventory {
/// General slots are the slots that can hold all items, including the
/// hotbar, excluding offhand. These slots are shown when the player is
/// looking at another inventory.
pub const GENERAL_SLOTS: Range<SlotId> = 9..45;
pub const HOTBAR_SLOTS: Range<SlotId> = 36..45;
pub fn hotbar_to_slot(hotbar_slot: i16) -> Option<SlotId> {
if !(0..=8).contains(&hotbar_slot) {
return None;
}
Some(Self::HOTBAR_SLOTS.start + hotbar_slot)
}
pub(crate) fn new() -> Self {
Self {
// Can't do the shorthand because Option<ItemStack> is not Copy.
slots: Box::new(std::array::from_fn(|_| None)),
dirty: true,
state_id: Default::default(),
}
}
}
impl Inventory for PlayerInventory {
fn slot(&self, slot_id: SlotId) -> Option<&ItemStack> {
if !self.slot_range().contains(&slot_id) {
return None;
}
self.slots[slot_id as usize].as_ref()
}
fn set_slot(&mut self, slot_id: SlotId, slot: Option<ItemStack>) -> Option<ItemStack> {
if !self.slot_range().contains(&slot_id) {
return None;
}
self.mark_dirty(true);
std::mem::replace(&mut self.slots[slot_id as usize], slot)
}
fn slot_range(&self) -> Range<SlotId> {
0..(self.slots.len() as SlotId)
}
}
impl InventoryDirtyable for PlayerInventory {
fn mark_dirty(&mut self, dirty: bool) {
self.dirty = dirty
}
fn is_dirty(&self) -> bool {
self.dirty
}
}
#[derive(Debug, Clone)]
pub struct ConfigurableInventory {
slots: Vec<Option<ItemStack>>,
/// The slots that the player can place items into for crafting. The
/// crafting result slot is always zero, and should not be included in this
/// range.
#[allow(dead_code)] // TODO: implement crafting
crafting_slots: Option<Range<SlotId>>,
/// The type of window that should be used to display this inventory.
pub window_type: VarInt,
dirty: bool,
}
impl ConfigurableInventory {
pub fn new(size: usize, window_type: VarInt, crafting_slots: Option<Range<SlotId>>) -> Self {
ConfigurableInventory {
slots: vec![None; size],
crafting_slots,
window_type,
dirty: false,
}
}
}
impl Inventory for ConfigurableInventory {
fn slot(&self, slot_id: SlotId) -> Option<&ItemStack> {
if !self.slot_range().contains(&slot_id) {
return None;
}
self.slots[slot_id as usize].as_ref()
}
fn set_slot(&mut self, slot_id: SlotId, slot: Option<ItemStack>) -> Option<ItemStack> {
if !self.slot_range().contains(&slot_id) {
return None;
}
self.mark_dirty(true);
std::mem::replace(&mut self.slots[slot_id as usize], slot)
}
fn slot_range(&self) -> Range<SlotId> {
0..(self.slots.len() as SlotId)
}
}
impl InventoryDirtyable for ConfigurableInventory {
fn mark_dirty(&mut self, dirty: bool) {
self.dirty = dirty
}
fn is_dirty(&self) -> bool {
self.dirty
}
}
/// Represents what the player sees when they open an object's Inventory.
///
/// This exists because when an object inventory screen is being shown to the
/// player, it also shows part of the player's inventory so they can move items
/// between the inventories.
pub struct WindowInventory {
pub window_id: u8,
pub object_inventory: InventoryId,
}
impl WindowInventory {
pub fn new(window_id: impl Into<u8>, object_inventory: InventoryId) -> Self {
WindowInventory {
window_id: window_id.into(),
object_inventory,
}
}
pub fn slots<'a>(
&self,
obj_inventory: &'a ConfigurableInventory,
player_inventory: &'a PlayerInventory,
) -> Vec<Option<&'a ItemStack>> {
let total_slots = obj_inventory.slots.len() + PlayerInventory::GENERAL_SLOTS.len();
(0..total_slots)
.map(|s| {
if s < obj_inventory.slot_count() {
return obj_inventory.slot(s as SlotId);
}
let offset = obj_inventory.slot_count();
player_inventory.slot((s - offset) as SlotId + PlayerInventory::GENERAL_SLOTS.start)
})
.collect()
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub struct InventoryId(Key);
/// Manages all inventories that are present in the server.
pub struct Inventories {
slab: VersionedSlab<ConfigurableInventory>,
}
impl Inventories {
impl<C: Config> Inventories<C> {
pub(crate) fn new() -> Self {
Self {
slab: VersionedSlab::new(),
}
}
/// Creates a new inventory on a server.
pub fn insert(
&mut self,
inv: ConfigurableInventory,
) -> (InventoryId, &mut ConfigurableInventory) {
let (key, value) = self.slab.insert(inv);
(InventoryId(key), value)
kind: InventoryKind,
title: impl Into<Text>,
state: C::InventoryState,
) -> (InventoryId, &mut Inventory<C>) {
let (id, inv) = self.slab.insert(Inventory {
state,
title: title.into(),
kind,
slots: vec![None; kind.slot_count()].into(),
modified: 0,
});
(InventoryId(id), inv)
}
/// Removes an inventory from the server.
pub fn remove(&mut self, inv: InventoryId) -> Option<ConfigurableInventory> {
self.slab.remove(inv.0)
pub fn remove(&mut self, id: InventoryId) -> Option<C::InventoryState> {
self.slab.remove(id.0).map(|inv| inv.state)
}
/// Returns the number of inventories in this container.
pub fn len(&self) -> usize {
self.slab.len()
pub fn get(&self, id: InventoryId) -> Option<&Inventory<C>> {
self.slab.get(id.0)
}
/// Returns `true` if there are no inventories.
pub fn is_empty(&self) -> bool {
self.slab.len() == 0
pub fn get_mut(&mut self, id: InventoryId) -> Option<&mut Inventory<C>> {
self.slab.get_mut(id.0)
}
pub fn get(&self, inv: InventoryId) -> Option<&ConfigurableInventory> {
self.slab.get(inv.0)
pub fn iter(
&self,
) -> impl ExactSizeIterator<Item = (InventoryId, &Inventory<C>)> + FusedIterator + Clone + '_
{
self.slab.iter().map(|(k, inv)| (InventoryId(k), inv))
}
pub fn get_mut(&mut self, inv: InventoryId) -> Option<&mut ConfigurableInventory> {
self.slab.get_mut(inv.0)
pub fn iter_mut(
&mut self,
) -> impl ExactSizeIterator<Item = (InventoryId, &mut Inventory<C>)> + FusedIterator + '_ {
self.slab.iter_mut().map(|(k, inv)| (InventoryId(k), inv))
}
pub(crate) fn update(&mut self) {
// now that we have synced all the dirty inventories, mark them as clean
for (_, inv) in self.slab.iter_mut() {
inv.mark_dirty(false);
for (_, inv) in self.iter_mut() {
inv.modified = 0;
}
}
}
#[derive(Copy, Clone, Debug, Error)]
#[error("InventoryError")]
pub struct InventoryError;
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub struct InventoryId(Key);
#[cfg(test)]
mod test {
use valence_protocol::{ItemKind, ItemStack};
impl InventoryId {
pub const NULL: Self = Self(Key::NULL);
}
use super::*;
pub struct Inventory<C: Config> {
/// Custom state
pub state: C::InventoryState,
title: Text,
kind: InventoryKind,
slots: Box<[Option<ItemStack>]>,
/// Contains a set bit for each modified slot in `slots`.
modified: u64,
}
#[test]
fn test_get_set_slots() {
let mut inv = PlayerInventory::new();
let slot = Some(ItemStack::new(ItemKind::Bone, 12, None));
let prev = inv.set_slot(9, slot.clone());
assert_eq!(inv.slot(9), slot.as_ref());
assert_eq!(prev, None);
}
impl<C: Config> Deref for Inventory<C> {
type Target = C::InventoryState;
#[test]
fn test_consume() {
let mut inv = PlayerInventory::new();
let slot_id = 9;
let slot = Some(ItemStack::new(ItemKind::Bone, 12, None));
inv.set_slot(slot_id, slot);
assert!(matches!(inv.consume(slot_id, 2), Ok(_)));
assert_eq!(inv.slot(slot_id).unwrap().count(), 10);
assert!(matches!(inv.consume(slot_id, 20), Err(_)));
assert_eq!(inv.slot(slot_id).unwrap().count(), 10);
assert!(matches!(inv.consume(slot_id, 10), Ok(_)));
assert_eq!(inv.slot(slot_id), None);
fn deref(&self) -> &Self::Target {
&self.state
}
}
impl<C: Config> DerefMut for Inventory<C> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.state
}
}
impl<C: Config> Inventory<C> {
pub fn slot(&self, idx: u16) -> Option<&ItemStack> {
self.slots
.get(idx as usize)
.expect("slot index out of range")
.as_ref()
}
pub fn replace_slot(
&mut self,
idx: u16,
item: impl Into<Option<ItemStack>>,
) -> Option<ItemStack> {
assert!(idx < self.slot_count(), "slot index out of range");
let new = item.into();
let old = &mut self.slots[idx as usize];
if new != *old {
self.modified |= 1 << idx;
}
mem::replace(old, new)
}
pub fn slot_count(&self) -> u16 {
self.slots.len() as u16
}
pub fn slots(
&self,
) -> impl ExactSizeIterator<Item = Option<&ItemStack>>
+ DoubleEndedIterator
+ FusedIterator
+ Clone
+ '_ {
self.slots.iter().map(|item| item.as_ref())
}
pub fn kind(&self) -> InventoryKind {
self.kind
}
pub fn title(&self) -> &Text {
&self.title
}
pub fn replace_title(&mut self, title: impl Into<Text>) -> Text {
// TODO: set title modified flag
mem::replace(&mut self.title, title.into())
}
pub(crate) fn slot_slice(&self) -> &[Option<ItemStack>] {
self.slots.as_ref()
}
pub(crate) fn send_update(
&self,
send: &mut PlayPacketSender,
window_id: u8,
state_id: &mut Wrapping<i32>,
) -> anyhow::Result<()> {
if self.modified != 0 {
for (idx, slot) in self.slots.iter().enumerate() {
if (self.modified >> idx) & 1 == 1 {
*state_id += 1;
send.append_packet(&SetContainerSlotEncode {
window_id: window_id as i8,
state_id: VarInt(state_id.0),
slot_idx: idx as i16,
slot_data: slot.as_ref(),
})?;
}
}
}
Ok(())
}
}

View file

@ -111,7 +111,6 @@ pub mod entity;
pub mod inventory;
pub mod player_list;
pub mod player_textures;
#[doc(hidden)]
pub mod server;
mod slab;
mod slab_rc;
@ -125,13 +124,11 @@ pub mod world;
pub mod prelude {
pub use biome::{Biome, BiomeId};
pub use chunk::{Chunk, ChunkPos, Chunks, LoadedChunk, UnloadedChunk};
pub use client::{handle_event_default, Client, ClientEvent, ClientId, Clients};
pub use client::{Client, ClientEvent, ClientId, Clients};
pub use config::{Config, ConnectionMode, PlayerSampleEntry, ServerListPing};
pub use dimension::{Dimension, DimensionId};
pub use entity::{Entities, Entity, EntityEvent, EntityId, EntityKind, TrackedData};
pub use inventory::{
ConfigurableInventory, Inventories, Inventory, InventoryId, PlayerInventory, SlotId,
};
pub use inventory::{Inventories, Inventory, InventoryId};
pub use player_list::{PlayerList, PlayerListEntry, PlayerListId, PlayerLists};
pub use server::{NewClientData, Server, SharedServer, ShutdownResult};
pub use spatial_index::{RaycastHit, SpatialIndex};
@ -147,8 +144,8 @@ pub mod prelude {
pub use valence_protocol::text::Color;
pub use valence_protocol::types::{GameMode, Hand, SoundCategory};
pub use valence_protocol::{
ident, translation_key, BlockKind, BlockPos, BlockState, Ident, ItemKind, ItemStack, Text,
TextFormat, Username, MINECRAFT_VERSION, PROTOCOL_VERSION,
ident, translation_key, BlockKind, BlockPos, BlockState, Ident, InventoryKind, ItemKind,
ItemStack, Text, TextFormat, Username, MINECRAFT_VERSION, PROTOCOL_VERSION,
};
pub use vek::{Aabb, Mat2, Mat3, Mat4, Vec2, Vec3, Vec4};
pub use world::{World, WorldId, WorldMeta, Worlds};

View file

@ -2,6 +2,7 @@
use std::collections::hash_map::Entry;
use std::collections::{HashMap, HashSet};
use std::ops::{Deref, DerefMut};
use bitfield_struct::bitfield;
use uuid::Uuid;
@ -11,12 +12,12 @@ use valence_protocol::{Text, VarInt};
use crate::config::Config;
use crate::player_textures::SignedPlayerTextures;
use crate::server::PlayPacketController;
use crate::slab_rc::{Key, SlabRc};
use crate::server::PlayPacketSender;
use crate::slab_rc::{Key, RcSlab};
/// A container for all [`PlayerList`]s on a server.
pub struct PlayerLists<C: Config> {
slab: SlabRc<PlayerList<C>>,
slab: RcSlab<PlayerList<C>>,
}
/// An identifier for a [`PlayerList`] on the server.
@ -33,7 +34,7 @@ pub struct PlayerListId(Key);
impl<C: Config> PlayerLists<C> {
pub(crate) fn new() -> Self {
Self {
slab: SlabRc::new(),
slab: RcSlab::new(),
}
}
@ -110,6 +111,20 @@ pub struct PlayerList<C: Config> {
modified_header_or_footer: bool,
}
impl<C: Config> Deref for PlayerList<C> {
type Target = C::PlayerListState;
fn deref(&self) -> &Self::Target {
&self.state
}
}
impl<C: Config> DerefMut for PlayerList<C> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.state
}
}
impl<C: Config> PlayerList<C> {
/// Inserts a player into the player list.
///
@ -243,10 +258,7 @@ impl<C: Config> PlayerList<C> {
self.entries.iter_mut().map(|(k, v)| (*k, v))
}
pub(crate) fn send_initial_packets(
&self,
ctrl: &mut PlayPacketController,
) -> anyhow::Result<()> {
pub(crate) fn send_initial_packets(&self, send: &mut PlayPacketSender) -> anyhow::Result<()> {
let add_player: Vec<_> = self
.entries
.iter()
@ -272,11 +284,11 @@ impl<C: Config> PlayerList<C> {
.collect();
if !add_player.is_empty() {
ctrl.append_packet(&PlayerInfo::AddPlayer(add_player))?;
send.append_packet(&PlayerInfo::AddPlayer(add_player))?;
}
if self.header != Text::default() || self.footer != Text::default() {
ctrl.append_packet(&SetTabListHeaderAndFooter {
send.append_packet(&SetTabListHeaderAndFooter {
header: self.header.clone(),
footer: self.footer.clone(),
})?;
@ -285,12 +297,9 @@ impl<C: Config> PlayerList<C> {
Ok(())
}
pub(crate) fn send_update_packets(
&self,
ctrl: &mut PlayPacketController,
) -> anyhow::Result<()> {
pub(crate) fn send_update_packets(&self, send: &mut PlayPacketSender) -> anyhow::Result<()> {
if !self.removed.is_empty() {
ctrl.append_packet(&PlayerInfo::RemovePlayer(
send.append_packet(&PlayerInfo::RemovePlayer(
self.removed.iter().cloned().collect(),
))?;
}
@ -338,23 +347,23 @@ impl<C: Config> PlayerList<C> {
}
if !add_player.is_empty() {
ctrl.append_packet(&PlayerInfo::AddPlayer(add_player))?;
send.append_packet(&PlayerInfo::AddPlayer(add_player))?;
}
if !game_mode.is_empty() {
ctrl.append_packet(&PlayerInfo::UpdateGameMode(game_mode))?;
send.append_packet(&PlayerInfo::UpdateGameMode(game_mode))?;
}
if !ping.is_empty() {
ctrl.append_packet(&PlayerInfo::UpdateLatency(ping))?;
send.append_packet(&PlayerInfo::UpdateLatency(ping))?;
}
if !display_name.is_empty() {
ctrl.append_packet(&PlayerInfo::UpdateDisplayName(display_name))?;
send.append_packet(&PlayerInfo::UpdateDisplayName(display_name))?;
}
if self.modified_header_or_footer {
ctrl.append_packet(&SetTabListHeaderAndFooter {
send.append_packet(&SetTabListHeaderAndFooter {
header: self.header.clone(),
footer: self.footer.clone(),
})?;
@ -363,10 +372,7 @@ impl<C: Config> PlayerList<C> {
Ok(())
}
pub(crate) fn queue_clear_packets(
&self,
ctrl: &mut PlayPacketController,
) -> anyhow::Result<()> {
pub(crate) fn queue_clear_packets(&self, ctrl: &mut PlayPacketSender) -> anyhow::Result<()> {
ctrl.append_packet(&PlayerInfo::RemovePlayer(
self.entries.keys().cloned().collect(),
))

View file

@ -10,16 +10,16 @@ use std::{io, thread};
use anyhow::{ensure, Context};
use flume::{Receiver, Sender};
pub(crate) use packet_controller::PlayPacketController;
pub(crate) use packet_manager::{PlayPacketReceiver, PlayPacketSender};
use rand::rngs::OsRng;
use rayon::iter::ParallelIterator;
use reqwest::Client as HttpClient;
use reqwest::Client as ReqwestClient;
use rsa::{PublicKeyParts, RsaPrivateKey};
use serde_json::{json, Value};
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
use tokio::net::{TcpListener, TcpStream};
use tokio::runtime::{Handle, Runtime};
use tokio::sync::Semaphore;
use tokio::sync::{OwnedSemaphorePermit, Semaphore};
use tracing::{error, info, info_span, instrument, trace, warn};
use uuid::Uuid;
use valence_nbt::{compound, Compound, List};
@ -41,16 +41,20 @@ use crate::entity::Entities;
use crate::inventory::Inventories;
use crate::player_list::PlayerLists;
use crate::player_textures::SignedPlayerTextures;
use crate::server::packet_controller::InitialPacketController;
use crate::server::packet_manager::InitialPacketManager;
use crate::world::Worlds;
use crate::Ticks;
mod byte_channel;
mod login;
mod packet_controller;
mod packet_manager;
/// Contains the entire state of a running Minecraft server, accessible from
/// within the [update](crate::config::Config::update) loop.
/// within the [init] and [update] functions.
///
/// [init]: crate::config::Config::init
/// [update]: crate::config::Config::update
#[non_exhaustive]
pub struct Server<C: Config> {
/// Custom state.
pub state: C::ServerState,
@ -65,7 +69,7 @@ pub struct Server<C: Config> {
/// All of the player lists on the server.
pub player_lists: PlayerLists<C>,
/// All of the inventories on the server.
pub inventories: Inventories,
pub inventories: Inventories<C>,
}
/// A handle to a Minecraft server containing the subset of functionality which
@ -119,26 +123,28 @@ struct SharedServerInner<C: Config> {
/// This is sent to clients during the authentication process.
public_key_der: Box<[u8]>,
/// For session server requests.
http_client: HttpClient,
http_client: ReqwestClient,
}
/// Contains information about a new client.
/// Contains information about a new client joining the server.
#[non_exhaustive]
pub struct NewClientData {
/// The UUID of the new client.
pub uuid: Uuid,
/// The username of the new client.
pub username: Username<String>,
/// The UUID of the new client.
pub uuid: Uuid,
/// The remote address of the new client.
pub ip: IpAddr,
/// The new client's player textures. May be `None` if the client does not
/// have a skin or cape.
pub textures: Option<SignedPlayerTextures>,
/// The remote address of the new client.
pub remote_addr: IpAddr,
}
struct NewClientMessage {
ncd: NewClientData,
ctrl: PlayPacketController,
send: PlayPacketSender,
recv: PlayPacketReceiver,
permit: OwnedSemaphorePermit,
}
/// The result type returned from [`start_server`].
@ -359,7 +365,7 @@ fn setup_server<C: Config>(cfg: C) -> anyhow::Result<SharedServer<C>> {
shutdown_result: Mutex::new(None),
rsa_key,
public_key_der,
http_client: HttpClient::new(),
http_client: ReqwestClient::new(),
};
Ok(SharedServer(Arc::new(server)))
@ -408,20 +414,23 @@ fn do_update_loop(server: &mut Server<impl Config>) -> ShutdownResult {
info!(
username = %msg.ncd.username,
uuid = %msg.ncd.uuid,
ip = %msg.ncd.remote_addr,
ip = %msg.ncd.ip,
"inserting client"
);
server
.clients
.insert(Client::new(msg.ctrl, msg.ncd, Default::default()));
server.clients.insert(Client::new(
msg.send,
msg.recv,
msg.permit,
msg.ncd,
Default::default(),
));
}
// Get serverbound packets first so they are not dealt with a tick late.
server.clients.par_iter_mut().for_each(|(_, client)| {
client.handle_serverbound_packets(&server.entities);
});
for (_, client) in server.clients.iter_mut() {
client.prepare_c2s_packets();
}
info_span!("configured_update").in_scope(|| shared.config().update(server));
@ -472,11 +481,12 @@ async fn do_accept_loop(server: SharedServer<impl Config>) {
match server.0.connection_sema.clone().acquire_owned().await {
Ok(permit) => match listener.accept().await {
Ok((stream, remote_addr)) => {
let server = server.clone();
tokio::spawn(async move {
handle_connection(server, stream, remote_addr).await;
drop(permit);
});
tokio::spawn(handle_connection(
server.clone(),
stream,
remote_addr,
permit,
));
}
Err(e) => {
error!("failed to accept incoming connection: {e}");
@ -493,6 +503,7 @@ async fn handle_connection(
server: SharedServer<impl Config>,
stream: TcpStream,
remote_addr: SocketAddr,
permit: OwnedSemaphorePermit,
) {
trace!("handling connection");
@ -502,17 +513,18 @@ async fn handle_connection(
let (read, write) = stream.into_split();
let ctrl = InitialPacketController::new(
let mngr = InitialPacketManager::new(
read,
write,
PacketEncoder::new(),
PacketDecoder::new(),
Duration::from_secs(5),
permit,
);
// TODO: peek stream for 0xFE legacy ping
if let Err(e) = handle_handshake(server, ctrl, remote_addr).await {
if let Err(e) = handle_handshake(server, mngr, remote_addr).await {
// EOF can happen if the client disconnects while joining, which isn't
// very erroneous.
if let Some(e) = e.downcast_ref::<io::Error>() {
@ -526,10 +538,10 @@ async fn handle_connection(
async fn handle_handshake(
server: SharedServer<impl Config>,
mut ctrl: InitialPacketController<OwnedReadHalf, OwnedWriteHalf>,
mut mngr: InitialPacketManager<OwnedReadHalf, OwnedWriteHalf>,
remote_addr: SocketAddr,
) -> anyhow::Result<()> {
let handshake = ctrl.recv_packet::<HandshakeOwned>().await?;
let handshake = mngr.recv_packet::<HandshakeOwned>().await?;
ensure!(
matches!(server.connection_mode(), ConnectionMode::BungeeCord)
@ -538,21 +550,25 @@ async fn handle_handshake(
);
match handshake.next_state {
HandshakeNextState::Status => handle_status(server, ctrl, remote_addr, handshake)
HandshakeNextState::Status => handle_status(server, mngr, remote_addr, handshake)
.await
.context("error handling status"),
HandshakeNextState::Login => match handle_login(&server, &mut ctrl, remote_addr, handshake)
HandshakeNextState::Login => match handle_login(&server, &mut mngr, remote_addr, handshake)
.await
.context("error handling login")?
{
Some(ncd) => {
let (send, recv, permit) = mngr.into_play(
server.0.incoming_capacity,
server.0.outgoing_capacity,
server.tokio_handle().clone(),
);
let msg = NewClientMessage {
ncd,
ctrl: ctrl.into_play_packet_controller(
server.0.incoming_capacity,
server.0.outgoing_capacity,
server.tokio_handle().clone(),
),
send,
recv,
permit,
};
let _ = server.0.new_clients_tx.send_async(msg).await;
@ -565,11 +581,11 @@ async fn handle_handshake(
async fn handle_status(
server: SharedServer<impl Config>,
mut ctrl: InitialPacketController<OwnedReadHalf, OwnedWriteHalf>,
mut mngr: InitialPacketManager<OwnedReadHalf, OwnedWriteHalf>,
remote_addr: SocketAddr,
handshake: HandshakeOwned,
) -> anyhow::Result<()> {
ctrl.recv_packet::<StatusRequest>().await?;
mngr.recv_packet::<StatusRequest>().await?;
match server
.0
@ -605,7 +621,7 @@ async fn handle_status(
.insert("favicon".to_owned(), Value::String(buf));
}
ctrl.send_packet(&StatusResponse {
mngr.send_packet(&StatusResponse {
json: &json.to_string(),
})
.await?;
@ -613,9 +629,9 @@ async fn handle_status(
ServerListPing::Ignore => return Ok(()),
}
let PingRequest { payload } = ctrl.recv_packet().await?;
let PingRequest { payload } = mngr.recv_packet().await?;
ctrl.send_packet(&PingResponse { payload }).await?;
mngr.send_packet(&PingResponse { payload }).await?;
Ok(())
}
@ -623,7 +639,7 @@ async fn handle_status(
/// Handle the login process and return the new client's data if successful.
async fn handle_login(
server: &SharedServer<impl Config>,
ctrl: &mut InitialPacketController<OwnedReadHalf, OwnedWriteHalf>,
mngr: &mut InitialPacketManager<OwnedReadHalf, OwnedWriteHalf>,
remote_addr: SocketAddr,
handshake: HandshakeOwned,
) -> anyhow::Result<Option<NewClientData>> {
@ -636,33 +652,33 @@ async fn handle_login(
username,
sig_data: _, // TODO
profile_id: _, // TODO
} = ctrl.recv_packet().await?;
} = mngr.recv_packet().await?;
let username = username.to_owned_username();
let ncd = match server.connection_mode() {
ConnectionMode::Online => login::online(server, ctrl, remote_addr, username).await?,
ConnectionMode::Online => login::online(server, mngr, remote_addr, username).await?,
ConnectionMode::Offline => login::offline(remote_addr, username)?,
ConnectionMode::BungeeCord => login::bungeecord(&handshake.server_address, username)?,
ConnectionMode::Velocity { secret } => login::velocity(ctrl, username, secret).await?,
ConnectionMode::Velocity { secret } => login::velocity(mngr, username, secret).await?,
};
if let Some(threshold) = server.0.cfg.compression_threshold() {
ctrl.send_packet(&SetCompression {
mngr.send_packet(&SetCompression {
threshold: VarInt(threshold as i32),
})
.await?;
ctrl.set_compression(Some(threshold));
mngr.set_compression(Some(threshold));
}
if let Err(reason) = server.0.cfg.login(server, &ncd).await {
info!("disconnect at login: \"{reason}\"");
ctrl.send_packet(&DisconnectLogin { reason }).await?;
mngr.send_packet(&DisconnectLogin { reason }).await?;
return Ok(None);
}
ctrl.send_packet(&LoginSuccess {
mngr.send_packet(&LoginSuccess {
uuid: ncd.uuid,
username: ncd.username.as_str_username(),
properties: Vec::new(),

View file

@ -24,14 +24,14 @@ use valence_protocol::{translation_key, Decode, Ident, RawBytes, Text, Username,
use crate::config::Config;
use crate::player_textures::SignedPlayerTextures;
use crate::server::packet_controller::InitialPacketController;
use crate::server::packet_manager::InitialPacketManager;
use crate::server::{NewClientData, SharedServer};
/// Login sequence for
/// [`ConnectionMode::Online`](crate::config::ConnectionMode).
pub(super) async fn online(
server: &SharedServer<impl Config>,
ctrl: &mut InitialPacketController<OwnedReadHalf, OwnedWriteHalf>,
ctrl: &mut InitialPacketManager<OwnedReadHalf, OwnedWriteHalf>,
remote_addr: SocketAddr,
username: Username<String>,
) -> anyhow::Result<NewClientData> {
@ -132,7 +132,7 @@ pub(super) async fn online(
uuid,
username,
textures: Some(textures),
remote_addr: remote_addr.ip(),
ip: remote_addr.ip(),
})
}
@ -147,7 +147,7 @@ pub(super) fn offline(
uuid: Uuid::from_slice(&Sha256::digest(username.as_str())[..16])?,
username,
textures: None,
remote_addr: remote_addr.ip(),
ip: remote_addr.ip(),
})
}
@ -188,7 +188,7 @@ pub(super) fn bungeecord(
uuid: uuid.parse()?,
username,
textures,
remote_addr: client_ip.parse()?,
ip: client_ip.parse()?,
})
}
@ -197,7 +197,7 @@ fn auth_digest(bytes: &[u8]) -> String {
}
pub(super) async fn velocity(
ctrl: &mut InitialPacketController<OwnedReadHalf, OwnedWriteHalf>,
ctrl: &mut InitialPacketManager<OwnedReadHalf, OwnedWriteHalf>,
username: Username<String>,
velocity_secret: &str,
) -> anyhow::Result<NewClientData> {
@ -279,7 +279,7 @@ pub(super) async fn velocity(
uuid,
username,
textures,
remote_addr,
ip: remote_addr,
})
}

View file

@ -5,6 +5,7 @@ use anyhow::Result;
use tokio::io;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
use tokio::runtime::Handle;
use tokio::sync::OwnedSemaphorePermit;
use tokio::task::JoinHandle;
use tokio::time::timeout;
use tracing::debug;
@ -12,17 +13,18 @@ use valence_protocol::{Decode, Encode, Packet, PacketDecoder, PacketEncoder};
use crate::server::byte_channel::{byte_channel, ByteReceiver, ByteSender, TryRecvError};
pub struct InitialPacketController<R, W> {
pub struct InitialPacketManager<R, W> {
reader: R,
writer: W,
enc: PacketEncoder,
dec: PacketDecoder,
timeout: Duration,
permit: OwnedSemaphorePermit,
}
const READ_BUF_SIZE: usize = 4096;
impl<R, W> InitialPacketController<R, W>
impl<R, W> InitialPacketManager<R, W>
where
R: AsyncRead + Unpin,
W: AsyncWrite + Unpin,
@ -33,6 +35,7 @@ where
enc: PacketEncoder,
dec: PacketDecoder,
timeout: Duration,
permit: OwnedSemaphorePermit,
) -> Self {
Self {
reader,
@ -40,6 +43,7 @@ where
enc,
dec,
timeout,
permit,
}
}
@ -74,6 +78,7 @@ where
Ok(self
.dec
.try_next_packet()?
// TODO: this panicked after a timeout.
.expect("decoder said it had another packet"))
// The following is what I want to write but can't due to borrow
@ -111,12 +116,12 @@ where
self.dec.enable_encryption(key);
}
pub fn into_play_packet_controller(
pub fn into_play(
mut self,
incoming_limit: usize,
outgoing_limit: usize,
handle: Handle,
) -> PlayPacketController
) -> (PlayPacketSender, PlayPacketReceiver, OwnedSemaphorePermit)
where
R: Send + 'static,
W: Send + 'static,
@ -162,32 +167,33 @@ where
}
});
PlayPacketController {
enc: self.enc,
dec: self.dec,
send: outgoing_sender,
recv: incoming_receiver,
reader_task,
writer_task: Some(writer_task),
handle,
}
(
PlayPacketSender {
enc: self.enc,
send: outgoing_sender,
writer_task: Some(writer_task),
handle,
},
PlayPacketReceiver {
dec: self.dec,
recv: incoming_receiver,
reader_task,
},
self.permit,
)
}
}
/// A convenience structure for managing a pair of packet encoder/decoders and
/// the byte channels from which to send and receive the packet data during the
/// play state.
pub struct PlayPacketController {
/// Manages a packet encoder and a byte channel to send the encoded packets
/// through.
pub struct PlayPacketSender {
enc: PacketEncoder,
dec: PacketDecoder,
send: ByteSender,
recv: ByteReceiver,
reader_task: JoinHandle<()>,
writer_task: Option<JoinHandle<()>>,
handle: Handle,
}
impl PlayPacketController {
impl PlayPacketSender {
pub fn append_packet<P>(&mut self, pkt: &P) -> Result<()>
where
P: Encode + Packet + ?Sized,
@ -202,6 +208,42 @@ impl PlayPacketController {
self.enc.prepend_packet(pkt)
}
#[allow(dead_code)]
pub fn set_compression(&mut self, threshold: Option<u32>) {
self.enc.set_compression(threshold)
}
pub fn flush(&mut self) -> Result<()> {
let bytes = self.enc.take();
self.send.try_send(bytes)?;
Ok(())
}
}
impl Drop for PlayPacketSender {
fn drop(&mut self) {
let _ = self.flush();
if let Some(writer_task) = self.writer_task.take() {
if !writer_task.is_finished() {
let _guard = self.handle.enter();
// Give any unsent packets a moment to send before we cut the connection.
self.handle
.spawn(timeout(Duration::from_secs(1), writer_task));
}
}
}
}
/// Manages a packet decoder and a byte channel to receive the encoded packets.
pub struct PlayPacketReceiver {
dec: PacketDecoder,
recv: ByteReceiver,
reader_task: JoinHandle<()>,
}
impl PlayPacketReceiver {
pub fn try_next_packet<'a, P>(&'a mut self) -> Result<Option<P>>
where
P: Decode<'a> + Packet,
@ -220,33 +262,10 @@ impl PlayPacketController {
Err(TryRecvError::Disconnected) => false,
}
}
#[allow(dead_code)]
pub fn set_compression(&mut self, threshold: Option<u32>) {
self.enc.set_compression(threshold)
}
pub fn flush(&mut self) -> Result<()> {
let bytes = self.enc.take();
self.send.try_send(bytes)?;
Ok(())
}
}
impl Drop for PlayPacketController {
impl Drop for PlayPacketReceiver {
fn drop(&mut self) {
self.reader_task.abort();
let _ = self.flush();
if let Some(writer_task) = self.writer_task.take() {
if !writer_task.is_finished() {
let _guard = self.handle.enter();
// Give any unsent packets a moment to send before we cut the connection.
self.handle
.spawn(timeout(Duration::from_secs(1), writer_task));
}
}
}
}

View file

@ -10,7 +10,7 @@ use rayon::iter::{IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelI
use crate::slab::Slab;
#[derive(Clone, Debug)]
pub struct SlabRc<T> {
pub struct RcSlab<T> {
slab: Slab<Slot<T>>,
}
@ -56,7 +56,7 @@ impl Hash for Key {
}
}
impl<T> SlabRc<T> {
impl<T> RcSlab<T> {
pub const fn new() -> Self {
Self { slab: Slab::new() }
}

View file

@ -1,6 +1,7 @@
//! A space on a server for objects to occupy.
use std::iter::FusedIterator;
use std::ops::{Deref, DerefMut};
use rayon::iter::ParallelIterator;
@ -66,11 +67,8 @@ impl<C: Config> Worlds<C> {
/// Note that any entities located in the world are not deleted.
/// Additionally, clients that are still in the deleted world at the end
/// of the tick are disconnected.
///
/// Returns `true` if the world was deleted. Otherwise, `false` is returned
/// and the function has no effect.
pub fn remove(&mut self, world: WorldId) -> bool {
self.slab.remove(world.0).is_some()
pub fn remove(&mut self, world: WorldId) -> Option<C::WorldState> {
self.slab.remove(world.0).map(|w| w.state)
}
/// Removes all worlds from the server for which `f` returns `false`.
@ -143,6 +141,20 @@ pub struct World<C: Config> {
pub meta: WorldMeta,
}
impl<C: Config> Deref for World<C> {
type Target = C::WorldState;
fn deref(&self) -> &Self::Target {
&self.state
}
}
impl<C: Config> DerefMut for World<C> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.state
}
}
/// Contains miscellaneous data about the world.
pub struct WorldMeta {
dimension: DimensionId,

View file

@ -1,5 +1,6 @@
//! Resource identifiers.
use std::borrow::Cow;
use std::cmp::Ordering;
use std::error::Error;
use std::fmt;
@ -101,11 +102,6 @@ impl<S: AsRef<str>> Ident<S> {
pub fn into_inner(self) -> S {
self.string
}
/// Consumes the identifier and returns the underlying string.
pub fn get(self) -> S {
self.string
}
}
impl<'a, S: ?Sized> Ident<&'a S> {
@ -124,6 +120,39 @@ impl<'a, S: ?Sized> Ident<&'a S> {
}
}
impl<'a> From<Ident<&'a str>> for Ident<String> {
fn from(value: Ident<&'a str>) -> Self {
value.to_owned_ident()
}
}
impl<'a> From<Ident<&'a str>> for Ident<Box<str>> {
fn from(value: Ident<&'a str>) -> Self {
Ident {
string: value.string.into(),
path_start: value.path_start,
}
}
}
impl<'a> From<Ident<&'a str>> for Ident<Cow<'a, str>> {
fn from(value: Ident<&'a str>) -> Self {
Ident {
string: Cow::Borrowed(value.string),
path_start: value.path_start,
}
}
}
impl<'a> From<Ident<Cow<'a, str>>> for Ident<String> {
fn from(value: Ident<Cow<'a, str>>) -> Self {
Ident {
string: value.string.into_owned(),
path_start: value.path_start,
}
}
}
impl FromStr for Ident<String> {
type Err = IdentError<String>;

View file

@ -0,0 +1,62 @@
use valence_derive::{Decode, Encode};
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
pub enum InventoryKind {
Generic9x1,
Generic9x2,
Generic9x3,
Generic9x4,
Generic9x5,
Generic9x6,
Generic3x3,
Anvil,
Beacon,
BlastFurnace,
BrewingStand,
Crafting,
Enchantment,
Furnace,
Grindstone,
Hopper,
Lectern,
Loom,
Merchant,
ShulkerBox,
Smithing,
Smoker,
Cartography,
Stonecutter,
}
impl InventoryKind {
/// The number of slots in this inventory, not counting the player's main
/// inventory slots.
pub const fn slot_count(self) -> usize {
match self {
InventoryKind::Generic9x1 => 9,
InventoryKind::Generic9x2 => 9 * 2,
InventoryKind::Generic9x3 => 9 * 3,
InventoryKind::Generic9x4 => 9 * 4,
InventoryKind::Generic9x5 => 9 * 5,
InventoryKind::Generic9x6 => 9 * 6,
InventoryKind::Generic3x3 => 3 * 3,
InventoryKind::Anvil => 4,
InventoryKind::Beacon => 1,
InventoryKind::BlastFurnace => 3,
InventoryKind::BrewingStand => 5,
InventoryKind::Crafting => 10,
InventoryKind::Enchantment => 2,
InventoryKind::Furnace => 3,
InventoryKind::Grindstone => 3,
InventoryKind::Hopper => 5,
InventoryKind::Lectern => 1,
InventoryKind::Loom => 4,
InventoryKind::Merchant => 3,
InventoryKind::ShulkerBox => 27,
InventoryKind::Smithing => 3,
InventoryKind::Smoker => 3,
InventoryKind::Cartography => 3,
InventoryKind::Stonecutter => 2,
}
}
}

View file

@ -39,8 +39,18 @@ impl ItemStack {
}
impl Encode for Option<ItemStack> {
fn encode(&self, w: impl Write) -> Result<()> {
self.as_ref().encode(w)
}
fn encoded_len(&self) -> usize {
self.as_ref().encoded_len()
}
}
impl<'a> Encode for Option<&'a ItemStack> {
fn encode(&self, mut w: impl Write) -> Result<()> {
match self {
match *self {
None => false.encode(w),
Some(s) => {
true.encode(&mut w)?;
@ -55,7 +65,7 @@ impl Encode for Option<ItemStack> {
}
fn encoded_len(&self) -> usize {
match self {
match *self {
None => 1,
Some(s) => {
1 + s.item.encoded_len()

View file

@ -78,6 +78,7 @@ pub use byte_angle::ByteAngle;
pub use cache::{Cached, EncodedBuf};
pub use codec::{PacketDecoder, PacketEncoder};
pub use ident::Ident;
pub use inventory::InventoryKind;
pub use item::{ItemKind, ItemStack};
pub use raw_bytes::RawBytes;
pub use text::{Text, TextFormat};
@ -108,6 +109,7 @@ pub mod enchant;
pub mod entity_meta;
pub mod ident;
mod impls;
mod inventory;
mod item;
pub mod packets;
mod raw_bytes;

View file

@ -7,7 +7,7 @@ use crate::item::ItemStack;
use crate::raw_bytes::RawBytes;
use crate::types::{
Action, ChatMode, ClickContainerMode, CommandArgumentSignature, CommandBlockFlags,
CommandBlockMode, DiggingStatus, DisplayedSkinParts, EntityInteraction, Hand,
CommandBlockMode, Difficulty, DiggingStatus, DisplayedSkinParts, EntityInteraction, Hand,
HandshakeNextState, MainHand, MessageAcknowledgment, MsgSigOrVerifyToken, PlayerInputFlags,
PublicKeyData, RecipeBookId, StructureBlockAction, StructureBlockFlags, StructureBlockMirror,
StructureBlockMode, StructureBlockRotation,
@ -116,17 +116,12 @@ pub mod play {
#[packet_id = 0x01]
pub struct QueryBlockEntityTag {
pub transaction_id: VarInt,
pub location: BlockPos,
pub position: BlockPos,
}
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet_id = 0x02]
pub enum ChangeDifficulty {
Peaceful,
Easy,
Normal,
Hard,
}
pub struct ChangeDifficulty(pub Difficulty);
#[derive(Clone, Debug, Encode, Decode, Packet)]
#[packet_id = 0x03]
@ -164,7 +159,7 @@ pub mod play {
#[packet_id = 0x07]
pub enum ClientCommand {
PerformRespawn,
RequestStatus,
RequestStats,
}
#[derive(Clone, Debug, Encode, Decode, Packet)]
@ -209,7 +204,7 @@ pub mod play {
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet_id = 0x0c]
pub struct CloseContainerC2s {
pub window_id: u8,
pub window_id: i8,
}
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
@ -245,7 +240,7 @@ pub mod play {
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet_id = 0x11]
pub struct JigsawGenerate {
pub location: BlockPos,
pub position: BlockPos,
pub levels: VarInt,
pub keep_jigsaws: bool,
}
@ -258,9 +253,7 @@ pub mod play {
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet_id = 0x13]
pub struct LockDifficulty {
pub locked: bool,
}
pub struct LockDifficulty(pub bool);
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet_id = 0x14]
@ -332,7 +325,7 @@ pub mod play {
#[packet_id = 0x1d]
pub struct PlayerAction {
pub status: DiggingStatus,
pub location: BlockPos,
pub position: BlockPos,
pub face: BlockFace,
pub sequence: VarInt,
}
@ -417,7 +410,7 @@ pub mod play {
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet_id = 0x29]
pub struct ProgramCommandBlock<'a> {
pub location: BlockPos,
pub position: BlockPos,
pub command: &'a str,
pub mode: CommandBlockMode,
pub flags: CommandBlockFlags,
@ -441,7 +434,7 @@ pub mod play {
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet_id = 0x2c]
pub struct ProgramJigsawBlock<'a> {
pub location: BlockPos,
pub position: BlockPos,
pub name: Ident<&'a str>,
pub target: Ident<&'a str>,
pub pool: Ident<&'a str>,
@ -452,7 +445,7 @@ pub mod play {
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet_id = 0x2d]
pub struct ProgramStructureBlock<'a> {
pub location: BlockPos,
pub position: BlockPos,
pub action: StructureBlockAction,
pub mode: StructureBlockMode,
pub name: &'a str,
@ -469,7 +462,7 @@ pub mod play {
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet_id = 0x2e]
pub struct UpdateSign<'a> {
pub location: BlockPos,
pub position: BlockPos,
pub lines: [&'a str; 4],
}
@ -487,7 +480,7 @@ pub mod play {
#[packet_id = 0x31]
pub struct UseItemOn {
pub hand: Hand,
pub location: BlockPos,
pub position: BlockPos,
pub face: BlockFace,
pub cursor_pos: [f32; 3],
pub head_inside_block: bool,

View file

@ -145,14 +145,14 @@ pub mod play {
#[packet_id = 0x06]
pub struct SetBlockDestroyStage {
pub entity_id: VarInt,
pub location: BlockPos,
pub position: BlockPos,
pub destroy_stage: u8,
}
#[derive(Clone, Debug, Encode, Decode, Packet)]
#[packet_id = 0x07]
pub struct BlockEntityData {
pub location: BlockPos,
pub position: BlockPos,
// TODO: BlockEntityKind enum?
pub kind: VarInt,
pub data: Compound,
@ -161,7 +161,7 @@ pub mod play {
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet_id = 0x09]
pub struct BlockUpdate {
pub location: BlockPos,
pub position: BlockPos,
pub block_id: VarInt,
}
@ -194,6 +194,15 @@ pub mod play {
pub carried_item: Option<ItemStack>,
}
#[derive(Copy, Clone, Debug, Encode, Packet)]
#[packet_id = 0x11]
pub struct SetContainerContentEncode<'a> {
pub window_id: u8,
pub state_id: VarInt,
pub slots: &'a [Option<ItemStack>],
pub carried_item: &'a Option<ItemStack>,
}
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet_id = 0x12]
pub struct SetContainerProperty {
@ -211,6 +220,15 @@ pub mod play {
pub slot_data: Option<ItemStack>,
}
#[derive(Clone, Debug, Encode, Packet)]
#[packet_id = 0x13]
pub struct SetContainerSlotEncode<'a> {
pub window_id: i8,
pub state_id: VarInt,
pub slot_idx: i16,
pub slot_data: Option<&'a ItemStack>,
}
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet_id = 0x14]
pub struct SetCooldown {
@ -512,7 +530,7 @@ pub mod play {
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet_id = 0x4d]
pub struct SetDefaultSpawnPosition {
pub location: BlockPos,
pub position: BlockPos,
pub angle: f32,
}

View file

@ -91,7 +91,7 @@ pub enum DiggingStatus {
FinishedDigging,
DropItemStack,
DropItem,
ShootArrowOrFinishEating,
UpdateHeldItemState,
SwapItemInHand,
}