mirror of
https://github.com/italicsjenga/valence.git
synced 2024-12-23 22:41:30 +11:00
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:
parent
6437381339
commit
58f8197913
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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:
|
|||
});
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
});
|
||||
|
||||
|
|
|
@ -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
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
91
src/chunk.rs
91
src/chunk.rs
|
@ -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,
|
||||
|
|
1160
src/client.rs
1160
src/client.rs
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -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;
|
||||
}
|
||||
|
|
121
src/entity.rs
121
src/entity.rs
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
414
src/inventory.rs
414
src/inventory.rs
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
11
src/lib.rs
11
src/lib.rs
|
@ -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};
|
||||
|
|
|
@ -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(),
|
||||
))
|
||||
|
|
120
src/server.rs
120
src/server.rs
|
@ -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(),
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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() }
|
||||
}
|
||||
|
|
22
src/world.rs
22
src/world.rs
|
@ -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,
|
||||
|
|
|
@ -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>;
|
||||
|
||||
|
|
62
valence_protocol/src/inventory.rs
Normal file
62
valence_protocol/src/inventory.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
@ -91,7 +91,7 @@ pub enum DiggingStatus {
|
|||
FinishedDigging,
|
||||
DropItemStack,
|
||||
DropItem,
|
||||
ShootArrowOrFinishEating,
|
||||
UpdateHeldItemState,
|
||||
SwapItemInHand,
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue