Skip to content
This repository was archived by the owner on Jul 1, 2024. It is now read-only.

Inventory #14

Draft
wants to merge 14 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/rust.yml') }}
key: ${{ runner.os }}-cargo-${{ hashFiles('**/rust.yml') }}-${{ hashFiles('**/Cargo.toml') }}
- name: Build
run: cargo build
- name: Run tests
Expand Down
14 changes: 13 additions & 1 deletion minecraft-protocol/build/blocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,13 @@ impl Block {{
unsafe {{*TEXT_IDS.get_unchecked((self as u32) as usize)}}
}}

pub fn from_text_id(text_id: &str) -> Option<Self> {{
match text_id {{
{text_id_match}
_ => None,
}}
}}

#[inline]
pub fn default_state_id(self) -> u32 {{
unsafe {{*DEFAULT_STATE_IDS.get_unchecked((self as u32) as usize)}}
Expand Down Expand Up @@ -419,6 +426,11 @@ const AIR_BLOCKS: [bool; {max_value}] = {air_blocks:?};
max_value = expected,
state_id_match_arms = state_id_match_arms,
text_ids = blocks.iter().map(|b| &b.text_id).collect::<Vec<_>>(),
text_id_match = blocks
.iter()
.map(|b| format!("\"{}\" => Some(Block::{}),", b.text_id, b.text_id.from_case(Case::Snake).to_case(Case::UpperCamel)))
.collect::<Vec<_>>()
.join("\n "),
display_names = blocks.iter().map(|b| &b.display_name).collect::<Vec<_>>(),
state_id_ranges = blocks
.iter()
Expand All @@ -427,7 +439,7 @@ const AIR_BLOCKS: [bool; {max_value}] = {air_blocks:?};
default_state_ids = blocks.iter().map(|b| b.default_state).collect::<Vec<_>>(),
item_ids = blocks
.iter()
.map(|b| b.drops.get(0).copied().unwrap_or(0))
.map(|b| b.drops.first().copied().unwrap_or(0))
.collect::<Vec<_>>(),
materials = materials,
resistances = blocks.iter().map(|b| b.resistance).collect::<Vec<_>>(),
Expand Down
2 changes: 1 addition & 1 deletion minecraft-protocol/build/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
use std::io::{ErrorKind, Read, Write};
use std::{collections::HashMap, fs::File};

const VERSION: &str = "1.20.1";
const VERSION: &str = "1.20.2";

fn get_data(url: &str, cache: &str) -> serde_json::Value {
match File::open(cache) {
Expand Down
12 changes: 12 additions & 0 deletions minecraft-protocol/build/items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ impl Item {{
unsafe {{*TEXT_IDS.get_unchecked((self as u32) as usize)}}
}}

pub fn from_text_id(text_id: &str) -> Option<Self> {{
match text_id {{
{text_id_match}
_ => None,
}}
}}

#[inline]
pub fn display_name(self) -> &'static str {{
unsafe {{*DISPLAY_NAMES.get_unchecked((self as u32) as usize)}}
Expand Down Expand Up @@ -125,6 +132,11 @@ const TEXT_IDS: [&str; {max_value}] = {text_ids:?};
durabilities = items.iter().map(|i| i.max_durability).collect::<Vec<_>>(),
display_names = items.iter().map(|i| &i.display_name).collect::<Vec<_>>(),
text_ids = items.iter().map(|i| &i.text_id).collect::<Vec<_>>(),
text_id_match = items
.iter()
.map(|i| format!("\"{}\" => Some(Item::{}),", i.text_id, i.text_id.from_case(Case::Snake).to_case(Case::UpperCamel)))
.collect::<Vec<_>>()
.join("\n "),
);

File::create("src/ids/items.rs")
Expand Down
27 changes: 27 additions & 0 deletions minecraft-server/src/inventory.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use crate::prelude::*;

pub struct PlayerInventory {
slots: [Slot; 46],
}

impl PlayerInventory {
pub fn new() -> PlayerInventory {
const EMPTY_SLOT: Slot = Slot {item: None};
PlayerInventory {
slots: [EMPTY_SLOT; 46],
}
}

pub fn get_slot(&self, slot: usize) -> Option<&Slot> {
self.slots.get(slot)
}

pub fn get_slot_mut(&mut self, slot: usize) -> Option<&mut Slot> {
self.slots.get_mut(slot)
}

pub fn set_slot(&mut self, slot: usize, item: Slot) {
let Some(slot) = self.get_slot_mut(slot) else {error!("Tried to set invalid slot {slot}"); return};
*slot = item;
}
}
1 change: 1 addition & 0 deletions minecraft-server/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#![allow(clippy::uninit_vec)]

mod player_handler;
mod inventory;
mod server_behavior;
mod prelude;
mod ecs;
Expand Down
5 changes: 1 addition & 4 deletions minecraft-server/src/player_handler/connect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,7 @@ pub async fn handle_connection(
world.remove_loader(uuid).await;
r
},
ConnectionState::Status => {
status(&mut stream).await;
Ok(())
},
ConnectionState::Status => status(&mut stream).await,
_ => {
error!("Unexpected next state: {next_state:?}");
Err(())
Expand Down
58 changes: 58 additions & 0 deletions minecraft-server/src/player_handler/play.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ struct PlayerHandler {
on_ground: bool,
packet_sender: MpscSender<Vec<u8>>,

inventory: PlayerInventory,
held_item: usize,

render_distance: i32,
loaded_chunks: HashSet<ChunkColumnPosition>,
center_chunk: ChunkPosition,
Expand Down Expand Up @@ -143,13 +146,65 @@ impl PlayerHandler {
self.on_move().await;
// TODO: make sure the movement is allowed
},
SetHeldItem { mut slot } => {
slot = slot.clamp(0, 8);
self.held_item = slot as usize;
}
SetCreativeModeSlot { id, clicked_item } => {
if self.game_mode != Gamemode::Creative {
warn!("Received SetCreativeModeSlot packet in non-creative gamemode");
return;
}
match id {
-1 => (), // TODO drop item
0..=45 => self.inventory.set_slot(id as usize, clicked_item),
id => error!("Invalid creative mode slot: {id}"),
}
}
DigBlock { status, location, face: _, sequence: _ } => {
use minecraft_protocol::components::blocks::DiggingState;
// TODO: Check legitimacy
if self.game_mode == Gamemode::Creative || status == DiggingState::Finished {
self.world.set_block(location.into(), BlockWithState::Air).await;
}
}
PlaceBlock { hand, location, face, sequence, .. } => {
use minecraft_protocol::components::blocks::BlockFace;

// TODO: use cursor position

// TODO: check legitimacy

let slot_id = match hand {
Hand::MainHand => self.held_item + 36,
Hand::OffHand => 45,
};
let block_location = match face {
BlockFace::Bottom => { let mut location = location; location.y -= 1; location }
BlockFace::Top => { let mut location = location; location.y += 1; location }
BlockFace::North => { let mut location = location; location.z -= 1; location }
BlockFace::South => { let mut location = location; location.z += 1; location }
BlockFace::West => { let mut location = location; location.x -= 1; location }
BlockFace::East => { let mut location = location; location.x += 1; location }
};

let Some(slot) = self.inventory.get_slot_mut(slot_id) else {return};
let Some(item) = &mut slot.item else {return};

if item.item_count <= 0 {
return;
}

let text_id = item.item_id.text_id();
let Some(block) = Block::from_text_id(text_id) else {return};
let Some(block) = BlockWithState::from_state_id(block.default_state_id()) else {return};

if self.game_mode != Gamemode::Creative {
item.item_count -= 1;
}
self.world.set_block_by(block_location.into(), block, self.info.uuid).await;
self.send_packet(PlayClientbound::AcknowledgeBlockChange { id: sequence }).await;
}
packet => warn!("Unsupported packet received: {packet:?}"),
}
}
Expand All @@ -167,6 +222,9 @@ pub async fn handle_player(stream: TcpStream, player_info: PlayerInfo, mut serve
on_ground: false,
packet_sender,

inventory: PlayerInventory::new(),
held_item: 0,

center_chunk: ChunkPosition { cx: 0, cy: 11, cz: 0 },
render_distance: player_info.render_distance.clamp(4, 15) as i32,
loaded_chunks: HashSet::new(),
Expand Down
3 changes: 0 additions & 3 deletions minecraft-server/src/player_handler/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@ pub async fn status(stream: &mut TcpStream) -> Result<(), ()> {
debug!("Pong sent");
return Ok(());
},
_ => {
debug!("Unexpected packet: {packet:?}");
}
};
}
}
6 changes: 3 additions & 3 deletions minecraft-server/src/prelude.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pub use crate::{ecs::*, player_handler::*, server_behavior::*, world::*};
pub use crate::{ecs::*, player_handler::*, server_behavior::*, world::*, inventory::*};
pub use futures::FutureExt;
pub use log::{debug, error, info, trace, warn};
pub use minecraft_protocol::{
Expand All @@ -9,7 +9,7 @@ pub use minecraft_protocol::{
entity::{EntityAttribute, EntityMetadata, EntityMetadataValue},
gamemode::{Gamemode, PreviousGamemode},
players::MainHand,
slots::Slot,
slots::{Slot, Hand},
},
nbt::NbtTag,
packets::{
Expand All @@ -22,7 +22,7 @@ pub use minecraft_protocol::{
status::{ClientboundPacket as StatusClientbound, ServerboundPacket as StatusServerbound},
Array, ConnectionState, Map, RawBytes, VarInt, VarLong, UUID, Position as NetworkPosition
},
ids::block_states::BlockWithState,
ids::{block_states::BlockWithState, blocks::Block, items::Item},
MinecraftPacketPart,
};
pub use std::{
Expand Down
35 changes: 35 additions & 0 deletions minecraft-server/src/world/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ impl Chunk {
match &mut self.data.blocks {
PalettedData::Paletted { palette, indexed } => {
let data_position = position.by as usize * 16 * 16 + position.bz as usize * 16 + position.bx as usize;
let previous_was_air = palette[indexed[data_position] as usize] == 0;

// Decrease count of previous block
let prev_palette_index = indexed[data_position] as usize;
Expand Down Expand Up @@ -132,6 +133,14 @@ impl Chunk {
if let Some(count) = self.palette_block_counts.get_mut(palette_position) {
*count += 1;
}

// Update the number of non-air blocks
match (previous_was_air, palette[palette_position] == 0) {
(true, false) => self.data.block_count += 1,
(false, true) => self.data.block_count -= 1,
(true, true) => (),
(false, false) => (),
}
},
None => {
// Turn to raw
Expand All @@ -143,6 +152,14 @@ impl Chunk {
values[data_position] = block_state_id;
self.data.blocks = PalettedData::Raw { values };
self.palette_block_counts.clear();

// Update the number of non-air blocks
match (previous_was_air, block_state_id == 0) {
(true, false) => self.data.block_count += 1,
(false, true) => self.data.block_count -= 1,
(true, true) => (),
(false, false) => (),
}
}
}
},
Expand All @@ -156,11 +173,29 @@ impl Chunk {
let mut indexed = vec![0; 4096];
let data_position = position.by as usize * 16 * 16 + position.bz as usize * 16 + position.bx as usize;
indexed[data_position] = 1;

// Compute the number of non-air blocks
self.data.block_count = match (*value == 0, block_state_id == 0) {
(true, false) => 1,
(false, true) => 4095,
(true, true) => 0,
(false, false) => 4096,
};

self.data.blocks = PalettedData::Paletted { palette, indexed };
self.palette_block_counts = vec![4095, 1];
}
PalettedData::Raw { values } => {
let data_position = position.by as usize * 16 * 16 + position.bz as usize * 16 + position.bx as usize;

// Update the number of non-air blocks
match (values[data_position] == 0, block_state_id == 0) {
(true, false) => self.data.block_count += 1,
(false, true) => self.data.block_count -= 1,
(true, true) => (),
(false, false) => (),
}

values[data_position] = block_state_id;
}
}
Expand Down
15 changes: 15 additions & 0 deletions minecraft-server/src/world/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ impl World {
self.notify(&position.chunk_column(), WorldChange::BlockChange(position, block)).await;
}

/// Sets a block without notifying the entity with the given UUID.
pub async fn set_block_by(&self, position: BlockPosition, block: BlockWithState, uuid: UUID) {
self.map.set_block(position.clone(), block.clone()).await;
self.notify_but(&position.chunk_column(), WorldChange::BlockChange(position, block), uuid).await;
}

pub async fn add_loader(&self, uuid: UUID) -> MpscReceiver<WorldChange> {
let (sender, receiver) = mpsc_channel(100);
self.change_senders.write().await.insert(uuid, sender);
Expand All @@ -67,11 +73,20 @@ impl World {
}
}

/// Notifies all entities observing the chunk.
async fn notify(&self, position: &ChunkColumnPosition, change: WorldChange) {
self.notify_but(position, change, u128::MAX).await;
}

/// Notifies all entities observing the chunk except the one with the given UUID.
async fn notify_but(&self, position: &ChunkColumnPosition, change: WorldChange, uuid_to_skip: UUID) {
let loading_manager = self.loading_manager.read().await;
let mut senders = self.change_senders.write().await;
let Some(loaders) = loading_manager.get_loaders(position) else {return};
for loader in loaders {
if *loader == uuid_to_skip {
continue;
}
if let Some(sender) = senders.get_mut(loader) {
let _ = sender.send(change.clone()).await;
}
Expand Down