diff --git a/crates/mc173-module/src/inventory.rs b/crates/mc173-module/src/inventory.rs index a95a794..5f0d442 100644 --- a/crates/mc173-module/src/inventory.rs +++ b/crates/mc173-module/src/inventory.rs @@ -1,42 +1,90 @@ //! Inventory data structure storing item stacks. use std::ops::Range; - +use spacetimedb::{query, spacetimedb}; use crate::item::ItemStack; use crate::item; +#[spacetimedb(table(public))] +pub struct StdbHandSlot { + #[primarykey] + pub player_id: u32, + pub slot_id: u32, +} -/// An inventory handle is used to assists item insertion into inventory. It also record -/// stack indices that have changed and therefore allows selective events. -pub struct InventoryHandle<'a> { - inv: &'a mut [ItemStack], - changes: u64, +#[spacetimedb(table(public))] +pub struct StdbInventory { + // The inventory ID can be one of the following things: + // - player_id: it's the inventory for the player + // - entity_id: it's the inventory for whatever entity (StorageChests, Furnaces, Dispensers) + #[primarykey] + #[autoinc] + pub inventory_id: u32, + pub size: u32, } -impl<'a> InventoryHandle<'a> { +#[spacetimedb(table(public))] +#[spacetimedb(index(btree, inventory_id, index))] +pub struct StdbItemStack { + #[primarykey] + #[autoinc] + pub stack_id: u32, + pub inventory_id: u32, + pub index: u32, + pub stack: ItemStack +} - /// Construct a new inventory handle to a slice of item stacks. This functions panics - /// if the given slice is bigger than 64 stacks. - pub fn new(inv: &'a mut [ItemStack]) -> Self { - assert!(inv.len() <= 64); - Self { - inv, - changes: 0, +impl StdbInventory { + /// Get the item stack at the given index. + pub fn get(inventory_id: u32, index: u32) -> ItemStack { + let inventory = StdbInventory::filter_by_inventory_id(&inventory_id).expect( + format!("Could not find inventory with id: {}", inventory_id).as_str()); + if index >= inventory.size { + panic!("Requested inventory slot outside of inventory: inventory_id={} index={}", inventory_id, index); } - } - /// Get the item stack at the given index. - #[inline] - pub fn get(&self, index: usize) -> ItemStack { - self.inv[index] + match query!(|stack: StdbItemStack| stack.inventory_id == inventory_id && stack.index == index).next() { + None => { + ItemStack { + id: 0, + size: 0, + damage: 0, + } + }, Some(stack) => { + stack.stack + } + } } /// Set the item stack at the given index. - #[inline] - pub fn set(&mut self, index: usize, stack: ItemStack) { - if self.inv[index] != stack { - self.inv[index] = stack; - self.changes |= 1 << index; + pub fn set(inventory_id: u32, index: u32, stack: ItemStack) { + let inventory = StdbInventory::filter_by_inventory_id(&inventory_id).expect( + format!("Could not find inventory with id: {}", inventory_id).as_str()); + if index >= inventory.size { + panic!("Requested inventory slot outside of inventory: inventory_id={} index={}", + inventory_id, index); + } + + let existing_stack = query!(|item: StdbItemStack| item.inventory_id == inventory_id && item.index == index).next(); + match existing_stack { + None => { + // Insert a new item stack + StdbItemStack::insert(StdbItemStack { + stack_id: 0, + inventory_id, + index, + stack, + }).expect("Insert item stack violated unique constraint"); + } + Some(existing_stack) => { + // Update the existing stack + StdbItemStack::update_by_stack_id(&existing_stack.stack_id, StdbItemStack { + stack_id: existing_stack.stack_id, + inventory_id, + index, + stack, + }); + } } } @@ -44,33 +92,36 @@ impl<'a> InventoryHandle<'a> { /// /// The given item stack is modified according to the amount of items actually added /// to the inventory, its size will be set to zero if fully consumed. - pub fn push_front(&mut self, stack: &mut ItemStack) { - self.push(stack, 0..self.inv.len(), false); + pub fn push_front(inventory_id: u32, stack: &mut ItemStack) { + let inventory = StdbInventory::filter_by_inventory_id(&inventory_id).expect( + format!("Could not find inventory with id: {}", inventory_id).as_str()); + Self::push(inventory_id, stack, 0..inventory.size, false); } /// Add an item to the inventory, starting from the last slots. /// /// The given item stack is modified according to the amount of items actually added /// to the inventory, its size will be set to zero if fully consumed. - pub fn push_back(&mut self, stack: &mut ItemStack) { - self.push(stack, 0..self.inv.len(), true); + pub fn push_back(inventory_id: u32, stack: &mut ItemStack) { + let inventory = StdbInventory::filter_by_inventory_id(&inventory_id).expect( + format!("Inventory with inventory id not found: {}", inventory_id).as_str()); + Self::push(inventory_id, stack, 0..inventory.size, true); } /// Same as [`push_front`](Self::push_front), but this work in a slice of inventory. - pub fn push_front_in(&mut self, stack: &mut ItemStack, range: Range) { - self.push(stack, range, false); + pub fn push_front_in(inventory_id: u32, stack: &mut ItemStack, range: Range) { + Self::push(inventory_id, stack, range, false); } /// Same as [`push_back`](Self::push_back), but this work in a slice of inventory. - pub fn push_back_in(&mut self, stack: &mut ItemStack, range: Range) { - self.push(stack, range, true); + pub fn push_back_in(inventory_id: u32, stack: &mut ItemStack, range: Range) { + Self::push(inventory_id, stack, range, true); } /// Add an item to the inventory. The given item stack is modified according to the /// amount of items actually added to the inventory, its size will be set to zero if /// fully consumed. - fn push(&mut self, stack: &mut ItemStack, range: Range, back: bool) { - + fn push(inventory_id: u32, stack: &mut ItemStack, range: Range, back: bool) { // Do nothing if stack size is 0 or the item is air. if stack.is_empty() { return; @@ -83,16 +134,19 @@ impl<'a> InventoryHandle<'a> { let mut range = range.clone(); while let Some(index) = if back { range.next_back() } else { range.next() } { - let slot = &mut self.inv[index]; + let mut slot = Self::get(inventory_id, index); // If the slot is of the same item and has space left in the stack size. if slot.size != 0 && slot.id == stack.id && slot.damage == stack.damage && slot.size < item.max_stack_size { let available = item.max_stack_size - slot.size; let to_add = available.min(stack.size); slot.size += to_add; + // Immediately update this slot in stdb + Self::set(inventory_id, index, slot); stack.size -= to_add; // NOTE: We requires that size must be less than 64, so the index fit // in the 64 bits of changes integer. - self.changes |= 1 << index; + // TODO(jdetter): minecraft optimizes inventory updates by only sending the changes + // self.changes |= 1 << index; if stack.size == 0 { return; } @@ -105,12 +159,13 @@ impl<'a> InventoryHandle<'a> { // We can also land here if the item has damage value. We search empty slots. let mut range = range.clone(); while let Some(index) = if back { range.next_back() } else { range.next() } { - let slot = &mut self.inv[index]; + let mut slot = Self::get(inventory_id, index); if slot.is_empty() { + Self::set(inventory_id, index, stack.clone()); // We found an empty slot, insert the whole remaining stack size. - *slot = *stack; + // *slot = *stack; stack.size = 0; - self.changes |= 1 << index; + // self.changes |= 1 << index; return; } } @@ -119,7 +174,7 @@ impl<'a> InventoryHandle<'a> { /// Test if the given item can be pushed in this inventory. If true is returned, a /// call to `push_*` function is guaranteed to fully consume the stack. - pub fn can_push(&self, mut stack: ItemStack) -> bool { + pub fn can_push(inventory_id: u32, mut stack: ItemStack) -> bool { // Do nothing if stack size is 0 or the item is air. if stack.is_empty() { @@ -127,8 +182,9 @@ impl<'a> InventoryHandle<'a> { } let item = item::from_id(stack.id); + let mut slots = Self::get_inventory_vec(inventory_id); - for slot in &self.inv[..] { + for slot in slots { if slot.is_empty() { return true; } else if slot.size != 0 && slot.id == stack.id && slot.damage == stack.damage && slot.size < item.max_stack_size { @@ -145,55 +201,41 @@ impl<'a> InventoryHandle<'a> { } - /// Consume the equivalent of the given item stack, returning true if successful. - pub fn consume(&mut self, stack: ItemStack) -> bool { - - for (index, slot) in self.inv.iter_mut().enumerate() { - if slot.id == stack.id && slot.damage == stack.damage && slot.size >= stack.size { - slot.size -= stack.size; - self.changes |= 1 << index; - return true; + fn get_inventory_vec(inventory_id: u32) -> Vec { + let inventory = StdbInventory::filter_by_inventory_id(&inventory_id).expect( + format!("Failed to find inventory with inventory id: {}", inventory_id).as_str() + ); + let mut slots = Vec::::new(); + for index in 0..inventory.size { + let slot = query!(|stack: StdbItemStack| stack.inventory_id == inventory_id && stack.index == index).next(); + if slot.is_none() { + slots.push(ItemStack { + id: 0, + size: 0, + damage: 0, + }); + } else { + slots.push(slot.unwrap().stack); } } - - false + slots } - /// Get an iterator for changes that happened in this inventory. - pub fn iter_changes(&self) -> ChangesIter { - ChangesIter { - changes: self.changes, - count: 0, - } - } - -} - - -/// An iterator of changes that happened to an inventory. -pub struct ChangesIter { - changes: u64, - count: u8, -} - -impl Iterator for ChangesIter { - - type Item = usize; + /// Consume the equivalent of the given item stack, returning true if successful. + pub fn consume(inventory_id: u32, stack: ItemStack) -> bool { + let mut slots = Self::get_inventory_vec(inventory_id); - fn next(&mut self) -> Option { - - while self.count < 64 { - let ret = ((self.changes & 1) != 0).then_some(self.count as usize); - self.changes >>= 1; - self.count += 1; - if let Some(ret) = ret { - return Some(ret); + for (index, slot) in slots.iter_mut().enumerate() { + if slot.id == stack.id && slot.damage == stack.damage && slot.size >= stack.size { + slot.size -= stack.size; + Self::set(inventory_id, index as u32, slot.clone()); + // self.changes |= 1 << index; + return true; } } - - None + + false } - } diff --git a/crates/mc173-module/src/item/mod.rs b/crates/mc173-module/src/item/mod.rs index 6b0b831..051813e 100644 --- a/crates/mc173-module/src/item/mod.rs +++ b/crates/mc173-module/src/item/mod.rs @@ -1,5 +1,6 @@ //! Item enumeration and behaviors. +use spacetimedb::SpacetimeType; use crate::block; pub mod attack; @@ -201,7 +202,7 @@ impl Item { /// An item stack defines the actual number of items and their damage value. -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, SpacetimeType)] pub struct ItemStack { /// The item id. pub id: u16, diff --git a/crates/mc173-module/src/world/use.rs b/crates/mc173-module/src/world/use.rs index 8bd23f6..b5df6c5 100644 --- a/crates/mc173-module/src/world/use.rs +++ b/crates/mc173-module/src/world/use.rs @@ -3,7 +3,6 @@ use glam::{IVec3, DVec3, Vec3}; use crate::entity::{Arrow, Entity, Snowball, Tnt, Bobber, BaseKind, ProjectileKind, Item}; -use crate::inventory::InventoryHandle; use crate::gen::tree::TreeGenerator; use crate::block::sapling::TreeKind; use crate::item::{ItemStack, self}; @@ -17,42 +16,42 @@ use super::bound::RayTraceKind; /// Methods related to item usage in the world. impl StdbWorld { - /// Use an item stack on a given block, this is basically the action of left click. - /// This function returns the item stack after, if used, this may return an item stack - /// with size of 0. The face is where the click has hit on the target block. - pub fn use_stack(&mut self, inv: &mut InventoryHandle, index: usize, pos: IVec3, face: Face, entity_id: u32, cache: &mut ChunkCache) { - - let stack = inv.get(index); - if stack.is_empty() { - return; - } - - let success = match stack.id { - 0 => false, - 1..=255 => self.use_block_stack(stack.id as u8, stack.damage as u8, pos, face, entity_id, cache), - item::SUGAR_CANES => self.use_block_stack(block::SUGAR_CANES, 0, pos, face, entity_id, cache), - item::CAKE => self.use_block_stack(block::CAKE, 0, pos, face, entity_id, cache), - item::REPEATER => self.use_block_stack(block::REPEATER, 0, pos, face, entity_id, cache), - item::REDSTONE => self.use_block_stack(block::REDSTONE, 0, pos, face, entity_id, cache), - item::WOOD_DOOR => self.use_door_stack(block::WOOD_DOOR, pos, face, entity_id, cache), - item::IRON_DOOR => self.use_door_stack(block::IRON_DOOR, pos, face, entity_id, cache), - item::BED => self.use_bed_stack(pos, face, entity_id, cache), - item::DIAMOND_HOE | - item::IRON_HOE | - item::STONE_HOE | - item::GOLD_HOE | - item::WOOD_HOE => self.use_hoe_stack(pos, face, cache), - item::WHEAT_SEEDS => self.use_wheat_seeds_stack(pos, face, cache), - item::DYE if stack.damage == 15 => self.use_bone_meal_stack(pos, cache), - item::FLINT_AND_STEEL => self.use_flint_and_steel(pos, face, cache), - _ => false - }; - - if success { - inv.set(index, stack.inc_damage(1)); - } - - } + // /// Use an item stack on a given block, this is basically the action of left click. + // /// This function returns the item stack after, if used, this may return an item stack + // /// with size of 0. The face is where the click has hit on the target block. + // pub fn use_stack(&mut self, inv: &mut InventoryHandle, index: usize, pos: IVec3, face: Face, entity_id: u32, cache: &mut ChunkCache) { + // + // let stack = inv.get(index); + // if stack.is_empty() { + // return; + // } + // + // let success = match stack.id { + // 0 => false, + // 1..=255 => self.use_block_stack(stack.id as u8, stack.damage as u8, pos, face, entity_id, cache), + // item::SUGAR_CANES => self.use_block_stack(block::SUGAR_CANES, 0, pos, face, entity_id, cache), + // item::CAKE => self.use_block_stack(block::CAKE, 0, pos, face, entity_id, cache), + // item::REPEATER => self.use_block_stack(block::REPEATER, 0, pos, face, entity_id, cache), + // item::REDSTONE => self.use_block_stack(block::REDSTONE, 0, pos, face, entity_id, cache), + // item::WOOD_DOOR => self.use_door_stack(block::WOOD_DOOR, pos, face, entity_id, cache), + // item::IRON_DOOR => self.use_door_stack(block::IRON_DOOR, pos, face, entity_id, cache), + // item::BED => self.use_bed_stack(pos, face, entity_id, cache), + // item::DIAMOND_HOE | + // item::IRON_HOE | + // item::STONE_HOE | + // item::GOLD_HOE | + // item::WOOD_HOE => self.use_hoe_stack(pos, face, cache), + // item::WHEAT_SEEDS => self.use_wheat_seeds_stack(pos, face, cache), + // item::DYE if stack.damage == 15 => self.use_bone_meal_stack(pos, cache), + // item::FLINT_AND_STEEL => self.use_flint_and_steel(pos, face, cache), + // _ => false + // }; + // + // if success { + // inv.set(index, stack.inc_damage(1)); + // } + // + // } // /// Use an item that is not meant to be used on blocks. Such as buckets, boats, bows or // /// food items... diff --git a/crates/mc173-server/src/autogen/hand_slot_packet.rs b/crates/mc173-server/src/autogen/hand_slot_packet.rs new file mode 100644 index 0000000..708a918 --- /dev/null +++ b/crates/mc173-server/src/autogen/hand_slot_packet.rs @@ -0,0 +1,18 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN RUST INSTEAD. + +#![allow(unused_imports)] +use spacetimedb_sdk::{ + anyhow::{anyhow, Result}, + identity::Identity, + reducer::{Reducer, ReducerCallbackId, Status}, + sats::{de::Deserialize, ser::Serialize}, + spacetimedb_lib, + table::{TableIter, TableType, TableWithPrimaryKey}, + Address, +}; + +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] +pub struct HandSlotPacket { + pub slot: i16, +} diff --git a/crates/mc173-server/src/autogen/item_stack.rs b/crates/mc173-server/src/autogen/item_stack.rs new file mode 100644 index 0000000..ac092eb --- /dev/null +++ b/crates/mc173-server/src/autogen/item_stack.rs @@ -0,0 +1,20 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN RUST INSTEAD. + +#![allow(unused_imports)] +use spacetimedb_sdk::{ + anyhow::{anyhow, Result}, + identity::Identity, + reducer::{Reducer, ReducerCallbackId, Status}, + sats::{de::Deserialize, ser::Serialize}, + spacetimedb_lib, + table::{TableIter, TableType, TableWithPrimaryKey}, + Address, +}; + +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] +pub struct ItemStack { + pub id: u16, + pub size: u16, + pub damage: u16, +} diff --git a/crates/mc173-server/src/autogen/mod.rs b/crates/mc173-server/src/autogen/mod.rs index 67950a0..f6ddc6b 100644 --- a/crates/mc173-server/src/autogen/mod.rs +++ b/crates/mc173-server/src/autogen/mod.rs @@ -28,10 +28,11 @@ pub mod chunk_nibble_array_3; pub mod chunk_update_type; pub mod generate_chunk_reducer; pub mod generate_chunks_reducer; -pub mod handle_break_block_reducer; +pub mod hand_slot_packet; pub mod handle_look_reducer; pub mod handle_position_look_reducer; pub mod handle_position_reducer; +pub mod item_stack; pub mod java_random; pub mod light_kind; pub mod light_update; @@ -50,17 +51,27 @@ pub mod stdb_entity; pub mod stdb_entity_tracker; pub mod stdb_entity_tracker_update_type; pub mod stdb_entity_view; +pub mod stdb_give_item_reducer; +pub mod stdb_hand_slot; pub mod stdb_handle_accept_reducer; +pub mod stdb_handle_break_block_reducer; +pub mod stdb_handle_hand_slot_reducer; pub mod stdb_handle_login_reducer; pub mod stdb_handle_lost_reducer; +pub mod stdb_handle_place_block_reducer; pub mod stdb_human; pub mod stdb_i_16_vec_3; pub mod stdb_i_32_vec_3; pub mod stdb_i_8_vec_2; pub mod stdb_in_login_packet; +pub mod stdb_inventory; +pub mod stdb_item_stack; pub mod stdb_look_packet; pub mod stdb_offline_player; pub mod stdb_offline_server_player; +pub mod stdb_place_block_packet; +pub mod stdb_player_window; +pub mod stdb_player_window_chest; pub mod stdb_playing_state; pub mod stdb_position_look_packet; pub mod stdb_position_packet; @@ -73,6 +84,7 @@ pub mod stdb_time; pub mod stdb_tracked_player; pub mod stdb_vec_2; pub mod stdb_weather; +pub mod stdb_window_kind; pub mod stdb_world; pub mod tick_reducer; pub mod weather; @@ -85,10 +97,11 @@ pub use chunk_nibble_array_3::*; pub use chunk_update_type::*; pub use generate_chunk_reducer::*; pub use generate_chunks_reducer::*; -pub use handle_break_block_reducer::*; +pub use hand_slot_packet::*; pub use handle_look_reducer::*; pub use handle_position_look_reducer::*; pub use handle_position_reducer::*; +pub use item_stack::*; pub use java_random::*; pub use light_kind::*; pub use light_update::*; @@ -107,17 +120,27 @@ pub use stdb_entity::*; pub use stdb_entity_tracker::*; pub use stdb_entity_tracker_update_type::*; pub use stdb_entity_view::*; +pub use stdb_give_item_reducer::*; +pub use stdb_hand_slot::*; pub use stdb_handle_accept_reducer::*; +pub use stdb_handle_break_block_reducer::*; +pub use stdb_handle_hand_slot_reducer::*; pub use stdb_handle_login_reducer::*; pub use stdb_handle_lost_reducer::*; +pub use stdb_handle_place_block_reducer::*; pub use stdb_human::*; pub use stdb_i_16_vec_3::*; pub use stdb_i_32_vec_3::*; pub use stdb_i_8_vec_2::*; pub use stdb_in_login_packet::*; +pub use stdb_inventory::*; +pub use stdb_item_stack::*; pub use stdb_look_packet::*; pub use stdb_offline_player::*; pub use stdb_offline_server_player::*; +pub use stdb_place_block_packet::*; +pub use stdb_player_window::*; +pub use stdb_player_window_chest::*; pub use stdb_playing_state::*; pub use stdb_position_look_packet::*; pub use stdb_position_packet::*; @@ -130,6 +153,7 @@ pub use stdb_time::*; pub use stdb_tracked_player::*; pub use stdb_vec_2::*; pub use stdb_weather::*; +pub use stdb_window_kind::*; pub use stdb_world::*; pub use tick_reducer::*; pub use weather::*; @@ -139,14 +163,17 @@ pub use weather::*; pub enum ReducerEvent { GenerateChunk(generate_chunk_reducer::GenerateChunkArgs), GenerateChunks(generate_chunks_reducer::GenerateChunksArgs), - HandleBreakBlock(handle_break_block_reducer::HandleBreakBlockArgs), HandleLook(handle_look_reducer::HandleLookArgs), HandlePosition(handle_position_reducer::HandlePositionArgs), HandlePositionLook(handle_position_look_reducer::HandlePositionLookArgs), SetWeather(set_weather_reducer::SetWeatherArgs), + StdbGiveItem(stdb_give_item_reducer::StdbGiveItemArgs), StdbHandleAccept(stdb_handle_accept_reducer::StdbHandleAcceptArgs), + StdbHandleBreakBlock(stdb_handle_break_block_reducer::StdbHandleBreakBlockArgs), + StdbHandleHandSlot(stdb_handle_hand_slot_reducer::StdbHandleHandSlotArgs), StdbHandleLogin(stdb_handle_login_reducer::StdbHandleLoginArgs), StdbHandleLost(stdb_handle_lost_reducer::StdbHandleLostArgs), + StdbHandlePlaceBlock(stdb_handle_place_block_reducer::StdbHandlePlaceBlockArgs), Tick(tick_reducer::TickArgs), } @@ -171,9 +198,14 @@ impl SpacetimeModule for Module { "StdbEntity" => client_cache.handle_table_update_with_primary_key::(callbacks, table_update), "StdbEntityTracker" => client_cache.handle_table_update_with_primary_key::(callbacks, table_update), "StdbEntityView" => client_cache.handle_table_update_with_primary_key::(callbacks, table_update), + "StdbHandSlot" => client_cache.handle_table_update_with_primary_key::(callbacks, table_update), "StdbHuman" => client_cache.handle_table_update_with_primary_key::(callbacks, table_update), + "StdbInventory" => client_cache.handle_table_update_with_primary_key::(callbacks, table_update), + "StdbItemStack" => client_cache.handle_table_update_with_primary_key::(callbacks, table_update), "StdbOfflinePlayer" => client_cache.handle_table_update_no_primary_key::(callbacks, table_update), "StdbOfflineServerPlayer" => client_cache.handle_table_update_with_primary_key::(callbacks, table_update), + "StdbPlayerWindow" => client_cache.handle_table_update_no_primary_key::(callbacks, table_update), + "StdbPlayerWindowChest" => client_cache.handle_table_update_no_primary_key::(callbacks, table_update), "StdbRand" => client_cache.handle_table_update_no_primary_key::(callbacks, table_update), "StdbServerPlayer" => client_cache.handle_table_update_with_primary_key::(callbacks, table_update), "StdbServerWorld" => client_cache.handle_table_update_with_primary_key::(callbacks, table_update), @@ -230,7 +262,10 @@ impl SpacetimeModule for Module { &reducer_event, state, ); + reminders.invoke_callbacks::(worker, &reducer_event, state); reminders.invoke_callbacks::(worker, &reducer_event, state); + reminders.invoke_callbacks::(worker, &reducer_event, state); + reminders.invoke_callbacks::(worker, &reducer_event, state); reminders.invoke_callbacks::( worker, &reducer_event, @@ -241,6 +276,16 @@ impl SpacetimeModule for Module { &reducer_event, state, ); + reminders.invoke_callbacks::( + worker, + &reducer_event, + state, + ); + reminders.invoke_callbacks::( + worker, + &reducer_event, + state, + ); reminders.invoke_callbacks::(worker, &reducer_event, state); reminders.invoke_callbacks::( worker, @@ -280,14 +325,17 @@ impl SpacetimeModule for Module { match &function_call.reducer[..] { "generate_chunk" => _reducer_callbacks.handle_event_of_type::(event, _state, ReducerEvent::GenerateChunk), "generate_chunks" => _reducer_callbacks.handle_event_of_type::(event, _state, ReducerEvent::GenerateChunks), - "handle_break_block" => _reducer_callbacks.handle_event_of_type::(event, _state, ReducerEvent::HandleBreakBlock), "handle_look" => _reducer_callbacks.handle_event_of_type::(event, _state, ReducerEvent::HandleLook), "handle_position" => _reducer_callbacks.handle_event_of_type::(event, _state, ReducerEvent::HandlePosition), "handle_position_look" => _reducer_callbacks.handle_event_of_type::(event, _state, ReducerEvent::HandlePositionLook), "set_weather" => _reducer_callbacks.handle_event_of_type::(event, _state, ReducerEvent::SetWeather), + "stdb_give_item" => _reducer_callbacks.handle_event_of_type::(event, _state, ReducerEvent::StdbGiveItem), "stdb_handle_accept" => _reducer_callbacks.handle_event_of_type::(event, _state, ReducerEvent::StdbHandleAccept), + "stdb_handle_break_block" => _reducer_callbacks.handle_event_of_type::(event, _state, ReducerEvent::StdbHandleBreakBlock), + "stdb_handle_hand_slot" => _reducer_callbacks.handle_event_of_type::(event, _state, ReducerEvent::StdbHandleHandSlot), "stdb_handle_login" => _reducer_callbacks.handle_event_of_type::(event, _state, ReducerEvent::StdbHandleLogin), "stdb_handle_lost" => _reducer_callbacks.handle_event_of_type::(event, _state, ReducerEvent::StdbHandleLost), + "stdb_handle_place_block" => _reducer_callbacks.handle_event_of_type::(event, _state, ReducerEvent::StdbHandlePlaceBlock), "tick" => _reducer_callbacks.handle_event_of_type::(event, _state, ReducerEvent::Tick), unknown => { spacetimedb_sdk::log::error!("Event on an unknown reducer: {:?}", unknown); None } } @@ -334,8 +382,14 @@ match &function_call.reducer[..] { .handle_resubscribe_for_type::( callbacks, new_subs, ), + "StdbHandSlot" => client_cache + .handle_resubscribe_for_type::(callbacks, new_subs), "StdbHuman" => client_cache .handle_resubscribe_for_type::(callbacks, new_subs), + "StdbInventory" => client_cache + .handle_resubscribe_for_type::(callbacks, new_subs), + "StdbItemStack" => client_cache + .handle_resubscribe_for_type::(callbacks, new_subs), "StdbOfflinePlayer" => client_cache .handle_resubscribe_for_type::( callbacks, new_subs, @@ -344,6 +398,14 @@ match &function_call.reducer[..] { .handle_resubscribe_for_type::( callbacks, new_subs, ), + "StdbPlayerWindow" => client_cache + .handle_resubscribe_for_type::( + callbacks, new_subs, + ), + "StdbPlayerWindowChest" => client_cache + .handle_resubscribe_for_type::( + callbacks, new_subs, + ), "StdbRand" => { client_cache.handle_resubscribe_for_type::(callbacks, new_subs) } diff --git a/crates/mc173-server/src/autogen/stdb_entity_tracker.rs b/crates/mc173-server/src/autogen/stdb_entity_tracker.rs index 9cae10f..9081f99 100644 --- a/crates/mc173-server/src/autogen/stdb_entity_tracker.rs +++ b/crates/mc173-server/src/autogen/stdb_entity_tracker.rs @@ -24,12 +24,12 @@ pub struct StdbEntityTracker { pub time: u16, pub absolute_countdown_time: u16, pub vel_enable: bool, - // pub pos: StdbI32Vec3, + pub pos: StdbI32Vec3, pub vel: StdbI16Vec3, - // pub look: StdbI8Vec2, - // pub sent_pos: StdbI32Vec3, - // pub sent_vel: StdbI16Vec3, - // pub sent_look: StdbI8Vec2, + pub look: StdbI8Vec2, + pub sent_pos: StdbI32Vec3, + pub sent_vel: StdbI16Vec3, + pub sent_look: StdbI8Vec2, pub last_update_type: StdbEntityTrackerUpdateType, pub was_velocity_update: bool, } diff --git a/crates/mc173-server/src/autogen/stdb_give_item_reducer.rs b/crates/mc173-server/src/autogen/stdb_give_item_reducer.rs new file mode 100644 index 0000000..3f409dc --- /dev/null +++ b/crates/mc173-server/src/autogen/stdb_give_item_reducer.rs @@ -0,0 +1,75 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN RUST INSTEAD. + +#![allow(unused_imports)] +use spacetimedb_sdk::{ + anyhow::{anyhow, Result}, + identity::Identity, + reducer::{Reducer, ReducerCallbackId, Status}, + sats::{de::Deserialize, ser::Serialize}, + spacetimedb_lib, + table::{TableIter, TableType, TableWithPrimaryKey}, + Address, +}; + +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] +pub struct StdbGiveItemArgs { + pub player_id: u32, + pub id: u16, + pub damage: u16, + pub size: u16, +} + +impl Reducer for StdbGiveItemArgs { + const REDUCER_NAME: &'static str = "stdb_give_item"; +} + +#[allow(unused)] +pub fn stdb_give_item(player_id: u32, id: u16, damage: u16, size: u16) { + StdbGiveItemArgs { + player_id, + id, + damage, + size, + } + .invoke(); +} + +#[allow(unused)] +pub fn on_stdb_give_item( + mut __callback: impl FnMut(&Identity, Option
, &Status, &u32, &u16, &u16, &u16) + + Send + + 'static, +) -> ReducerCallbackId { + StdbGiveItemArgs::on_reducer(move |__identity, __addr, __status, __args| { + let StdbGiveItemArgs { + player_id, + id, + damage, + size, + } = __args; + __callback(__identity, __addr, __status, player_id, id, damage, size); + }) +} + +#[allow(unused)] +pub fn once_on_stdb_give_item( + __callback: impl FnOnce(&Identity, Option
, &Status, &u32, &u16, &u16, &u16) + + Send + + 'static, +) -> ReducerCallbackId { + StdbGiveItemArgs::once_on_reducer(move |__identity, __addr, __status, __args| { + let StdbGiveItemArgs { + player_id, + id, + damage, + size, + } = __args; + __callback(__identity, __addr, __status, player_id, id, damage, size); + }) +} + +#[allow(unused)] +pub fn remove_on_stdb_give_item(id: ReducerCallbackId) { + StdbGiveItemArgs::remove_on_reducer(id); +} diff --git a/crates/mc173-server/src/autogen/stdb_hand_slot.rs b/crates/mc173-server/src/autogen/stdb_hand_slot.rs new file mode 100644 index 0000000..217c39f --- /dev/null +++ b/crates/mc173-server/src/autogen/stdb_hand_slot.rs @@ -0,0 +1,46 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN RUST INSTEAD. + +#![allow(unused_imports)] +use spacetimedb_sdk::{ + anyhow::{anyhow, Result}, + identity::Identity, + reducer::{Reducer, ReducerCallbackId, Status}, + sats::{de::Deserialize, ser::Serialize}, + spacetimedb_lib, + table::{TableIter, TableType, TableWithPrimaryKey}, + Address, +}; + +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] +pub struct StdbHandSlot { + pub player_id: u32, + pub slot_id: u32, +} + +impl TableType for StdbHandSlot { + const TABLE_NAME: &'static str = "StdbHandSlot"; + type ReducerEvent = super::ReducerEvent; +} + +impl TableWithPrimaryKey for StdbHandSlot { + type PrimaryKey = u32; + fn primary_key(&self) -> &Self::PrimaryKey { + &self.player_id + } +} + +impl StdbHandSlot { + #[allow(unused)] + pub fn filter_by_player_id(player_id: u32) -> TableIter { + Self::filter(|row| row.player_id == player_id) + } + #[allow(unused)] + pub fn find_by_player_id(player_id: u32) -> Option { + Self::find(|row| row.player_id == player_id) + } + #[allow(unused)] + pub fn filter_by_slot_id(slot_id: u32) -> TableIter { + Self::filter(|row| row.slot_id == slot_id) + } +} diff --git a/crates/mc173-server/src/autogen/handle_break_block_reducer.rs b/crates/mc173-server/src/autogen/stdb_handle_break_block_reducer.rs similarity index 54% rename from crates/mc173-server/src/autogen/handle_break_block_reducer.rs rename to crates/mc173-server/src/autogen/stdb_handle_break_block_reducer.rs index 2550276..1636c5e 100644 --- a/crates/mc173-server/src/autogen/handle_break_block_reducer.rs +++ b/crates/mc173-server/src/autogen/stdb_handle_break_block_reducer.rs @@ -14,45 +14,45 @@ use spacetimedb_sdk::{ }; #[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] -pub struct HandleBreakBlockArgs { +pub struct StdbHandleBreakBlockArgs { pub entity_id: u32, pub packet: StdbBreakBlockPacket, } -impl Reducer for HandleBreakBlockArgs { - const REDUCER_NAME: &'static str = "handle_break_block"; +impl Reducer for StdbHandleBreakBlockArgs { + const REDUCER_NAME: &'static str = "stdb_handle_break_block"; } #[allow(unused)] -pub fn handle_break_block(entity_id: u32, packet: StdbBreakBlockPacket) { - HandleBreakBlockArgs { entity_id, packet }.invoke(); +pub fn stdb_handle_break_block(entity_id: u32, packet: StdbBreakBlockPacket) { + StdbHandleBreakBlockArgs { entity_id, packet }.invoke(); } #[allow(unused)] -pub fn on_handle_break_block( +pub fn on_stdb_handle_break_block( mut __callback: impl FnMut(&Identity, Option
, &Status, &u32, &StdbBreakBlockPacket) + Send + 'static, -) -> ReducerCallbackId { - HandleBreakBlockArgs::on_reducer(move |__identity, __addr, __status, __args| { - let HandleBreakBlockArgs { entity_id, packet } = __args; +) -> ReducerCallbackId { + StdbHandleBreakBlockArgs::on_reducer(move |__identity, __addr, __status, __args| { + let StdbHandleBreakBlockArgs { entity_id, packet } = __args; __callback(__identity, __addr, __status, entity_id, packet); }) } #[allow(unused)] -pub fn once_on_handle_break_block( +pub fn once_on_stdb_handle_break_block( __callback: impl FnOnce(&Identity, Option
, &Status, &u32, &StdbBreakBlockPacket) + Send + 'static, -) -> ReducerCallbackId { - HandleBreakBlockArgs::once_on_reducer(move |__identity, __addr, __status, __args| { - let HandleBreakBlockArgs { entity_id, packet } = __args; +) -> ReducerCallbackId { + StdbHandleBreakBlockArgs::once_on_reducer(move |__identity, __addr, __status, __args| { + let StdbHandleBreakBlockArgs { entity_id, packet } = __args; __callback(__identity, __addr, __status, entity_id, packet); }) } #[allow(unused)] -pub fn remove_on_handle_break_block(id: ReducerCallbackId) { - HandleBreakBlockArgs::remove_on_reducer(id); +pub fn remove_on_stdb_handle_break_block(id: ReducerCallbackId) { + StdbHandleBreakBlockArgs::remove_on_reducer(id); } diff --git a/crates/mc173-server/src/autogen/stdb_handle_hand_slot_reducer.rs b/crates/mc173-server/src/autogen/stdb_handle_hand_slot_reducer.rs new file mode 100644 index 0000000..0234e46 --- /dev/null +++ b/crates/mc173-server/src/autogen/stdb_handle_hand_slot_reducer.rs @@ -0,0 +1,56 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN RUST INSTEAD. + +#![allow(unused_imports)] +use super::hand_slot_packet::HandSlotPacket; +use spacetimedb_sdk::{ + anyhow::{anyhow, Result}, + identity::Identity, + reducer::{Reducer, ReducerCallbackId, Status}, + sats::{de::Deserialize, ser::Serialize}, + spacetimedb_lib, + table::{TableIter, TableType, TableWithPrimaryKey}, + Address, +}; + +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] +pub struct StdbHandleHandSlotArgs { + pub player_id: u32, + pub packet: HandSlotPacket, +} + +impl Reducer for StdbHandleHandSlotArgs { + const REDUCER_NAME: &'static str = "stdb_handle_hand_slot"; +} + +#[allow(unused)] +pub fn stdb_handle_hand_slot(player_id: u32, packet: HandSlotPacket) { + StdbHandleHandSlotArgs { player_id, packet }.invoke(); +} + +#[allow(unused)] +pub fn on_stdb_handle_hand_slot( + mut __callback: impl FnMut(&Identity, Option
, &Status, &u32, &HandSlotPacket) + + Send + + 'static, +) -> ReducerCallbackId { + StdbHandleHandSlotArgs::on_reducer(move |__identity, __addr, __status, __args| { + let StdbHandleHandSlotArgs { player_id, packet } = __args; + __callback(__identity, __addr, __status, player_id, packet); + }) +} + +#[allow(unused)] +pub fn once_on_stdb_handle_hand_slot( + __callback: impl FnOnce(&Identity, Option
, &Status, &u32, &HandSlotPacket) + Send + 'static, +) -> ReducerCallbackId { + StdbHandleHandSlotArgs::once_on_reducer(move |__identity, __addr, __status, __args| { + let StdbHandleHandSlotArgs { player_id, packet } = __args; + __callback(__identity, __addr, __status, player_id, packet); + }) +} + +#[allow(unused)] +pub fn remove_on_stdb_handle_hand_slot(id: ReducerCallbackId) { + StdbHandleHandSlotArgs::remove_on_reducer(id); +} diff --git a/crates/mc173-server/src/autogen/stdb_handle_place_block_reducer.rs b/crates/mc173-server/src/autogen/stdb_handle_place_block_reducer.rs new file mode 100644 index 0000000..bf9e2b3 --- /dev/null +++ b/crates/mc173-server/src/autogen/stdb_handle_place_block_reducer.rs @@ -0,0 +1,58 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN RUST INSTEAD. + +#![allow(unused_imports)] +use super::stdb_place_block_packet::StdbPlaceBlockPacket; +use spacetimedb_sdk::{ + anyhow::{anyhow, Result}, + identity::Identity, + reducer::{Reducer, ReducerCallbackId, Status}, + sats::{de::Deserialize, ser::Serialize}, + spacetimedb_lib, + table::{TableIter, TableType, TableWithPrimaryKey}, + Address, +}; + +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] +pub struct StdbHandlePlaceBlockArgs { + pub player_id: u32, + pub packet: StdbPlaceBlockPacket, +} + +impl Reducer for StdbHandlePlaceBlockArgs { + const REDUCER_NAME: &'static str = "stdb_handle_place_block"; +} + +#[allow(unused)] +pub fn stdb_handle_place_block(player_id: u32, packet: StdbPlaceBlockPacket) { + StdbHandlePlaceBlockArgs { player_id, packet }.invoke(); +} + +#[allow(unused)] +pub fn on_stdb_handle_place_block( + mut __callback: impl FnMut(&Identity, Option
, &Status, &u32, &StdbPlaceBlockPacket) + + Send + + 'static, +) -> ReducerCallbackId { + StdbHandlePlaceBlockArgs::on_reducer(move |__identity, __addr, __status, __args| { + let StdbHandlePlaceBlockArgs { player_id, packet } = __args; + __callback(__identity, __addr, __status, player_id, packet); + }) +} + +#[allow(unused)] +pub fn once_on_stdb_handle_place_block( + __callback: impl FnOnce(&Identity, Option
, &Status, &u32, &StdbPlaceBlockPacket) + + Send + + 'static, +) -> ReducerCallbackId { + StdbHandlePlaceBlockArgs::once_on_reducer(move |__identity, __addr, __status, __args| { + let StdbHandlePlaceBlockArgs { player_id, packet } = __args; + __callback(__identity, __addr, __status, player_id, packet); + }) +} + +#[allow(unused)] +pub fn remove_on_stdb_handle_place_block(id: ReducerCallbackId) { + StdbHandlePlaceBlockArgs::remove_on_reducer(id); +} diff --git a/crates/mc173-server/src/autogen/stdb_human.rs b/crates/mc173-server/src/autogen/stdb_human.rs index ab03fd5..28ca7ea 100644 --- a/crates/mc173-server/src/autogen/stdb_human.rs +++ b/crates/mc173-server/src/autogen/stdb_human.rs @@ -46,10 +46,6 @@ impl StdbHuman { Self::filter(|row| row.username == username) } #[allow(unused)] - pub fn find_by_username(username: String) -> Option { - Self::find(|row| row.username == username) - } - #[allow(unused)] pub fn filter_by_sleeping(sleeping: bool) -> TableIter { Self::filter(|row| row.sleeping == sleeping) } diff --git a/crates/mc173-server/src/autogen/stdb_inventory.rs b/crates/mc173-server/src/autogen/stdb_inventory.rs new file mode 100644 index 0000000..3c612ac --- /dev/null +++ b/crates/mc173-server/src/autogen/stdb_inventory.rs @@ -0,0 +1,46 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN RUST INSTEAD. + +#![allow(unused_imports)] +use spacetimedb_sdk::{ + anyhow::{anyhow, Result}, + identity::Identity, + reducer::{Reducer, ReducerCallbackId, Status}, + sats::{de::Deserialize, ser::Serialize}, + spacetimedb_lib, + table::{TableIter, TableType, TableWithPrimaryKey}, + Address, +}; + +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] +pub struct StdbInventory { + pub inventory_id: u32, + pub size: u32, +} + +impl TableType for StdbInventory { + const TABLE_NAME: &'static str = "StdbInventory"; + type ReducerEvent = super::ReducerEvent; +} + +impl TableWithPrimaryKey for StdbInventory { + type PrimaryKey = u32; + fn primary_key(&self) -> &Self::PrimaryKey { + &self.inventory_id + } +} + +impl StdbInventory { + #[allow(unused)] + pub fn filter_by_inventory_id(inventory_id: u32) -> TableIter { + Self::filter(|row| row.inventory_id == inventory_id) + } + #[allow(unused)] + pub fn find_by_inventory_id(inventory_id: u32) -> Option { + Self::find(|row| row.inventory_id == inventory_id) + } + #[allow(unused)] + pub fn filter_by_size(size: u32) -> TableIter { + Self::filter(|row| row.size == size) + } +} diff --git a/crates/mc173-server/src/autogen/stdb_item_stack.rs b/crates/mc173-server/src/autogen/stdb_item_stack.rs new file mode 100644 index 0000000..5d66e5b --- /dev/null +++ b/crates/mc173-server/src/autogen/stdb_item_stack.rs @@ -0,0 +1,53 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN RUST INSTEAD. + +#![allow(unused_imports)] +use super::item_stack::ItemStack; +use spacetimedb_sdk::{ + anyhow::{anyhow, Result}, + identity::Identity, + reducer::{Reducer, ReducerCallbackId, Status}, + sats::{de::Deserialize, ser::Serialize}, + spacetimedb_lib, + table::{TableIter, TableType, TableWithPrimaryKey}, + Address, +}; + +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] +pub struct StdbItemStack { + pub stack_id: u32, + pub inventory_id: u32, + pub index: u32, + pub stack: ItemStack, +} + +impl TableType for StdbItemStack { + const TABLE_NAME: &'static str = "StdbItemStack"; + type ReducerEvent = super::ReducerEvent; +} + +impl TableWithPrimaryKey for StdbItemStack { + type PrimaryKey = u32; + fn primary_key(&self) -> &Self::PrimaryKey { + &self.stack_id + } +} + +impl StdbItemStack { + #[allow(unused)] + pub fn filter_by_stack_id(stack_id: u32) -> TableIter { + Self::filter(|row| row.stack_id == stack_id) + } + #[allow(unused)] + pub fn find_by_stack_id(stack_id: u32) -> Option { + Self::find(|row| row.stack_id == stack_id) + } + #[allow(unused)] + pub fn filter_by_inventory_id(inventory_id: u32) -> TableIter { + Self::filter(|row| row.inventory_id == inventory_id) + } + #[allow(unused)] + pub fn filter_by_index(index: u32) -> TableIter { + Self::filter(|row| row.index == index) + } +} diff --git a/crates/mc173-server/src/autogen/stdb_offline_server_player.rs b/crates/mc173-server/src/autogen/stdb_offline_server_player.rs index f3427eb..67fbea0 100644 --- a/crates/mc173-server/src/autogen/stdb_offline_server_player.rs +++ b/crates/mc173-server/src/autogen/stdb_offline_server_player.rs @@ -15,6 +15,7 @@ use spacetimedb_sdk::{ #[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] pub struct StdbOfflineServerPlayer { + pub connection_id: u64, pub username: String, pub player: StdbServerPlayer, } @@ -25,19 +26,23 @@ impl TableType for StdbOfflineServerPlayer { } impl TableWithPrimaryKey for StdbOfflineServerPlayer { - type PrimaryKey = String; + type PrimaryKey = u64; fn primary_key(&self) -> &Self::PrimaryKey { - &self.username + &self.connection_id } } impl StdbOfflineServerPlayer { #[allow(unused)] - pub fn filter_by_username(username: String) -> TableIter { - Self::filter(|row| row.username == username) + pub fn filter_by_connection_id(connection_id: u64) -> TableIter { + Self::filter(|row| row.connection_id == connection_id) + } + #[allow(unused)] + pub fn find_by_connection_id(connection_id: u64) -> Option { + Self::find(|row| row.connection_id == connection_id) } #[allow(unused)] - pub fn find_by_username(username: String) -> Option { - Self::find(|row| row.username == username) + pub fn filter_by_username(username: String) -> TableIter { + Self::filter(|row| row.username == username) } } diff --git a/crates/mc173-server/src/autogen/stdb_place_block_packet.rs b/crates/mc173-server/src/autogen/stdb_place_block_packet.rs new file mode 100644 index 0000000..5c17f50 --- /dev/null +++ b/crates/mc173-server/src/autogen/stdb_place_block_packet.rs @@ -0,0 +1,23 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN RUST INSTEAD. + +#![allow(unused_imports)] +use super::item_stack::ItemStack; +use spacetimedb_sdk::{ + anyhow::{anyhow, Result}, + identity::Identity, + reducer::{Reducer, ReducerCallbackId, Status}, + sats::{de::Deserialize, ser::Serialize}, + spacetimedb_lib, + table::{TableIter, TableType, TableWithPrimaryKey}, + Address, +}; + +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] +pub struct StdbPlaceBlockPacket { + pub x: i32, + pub y: i8, + pub z: i32, + pub direction: u8, + pub stack: Option, +} diff --git a/crates/mc173-server/src/autogen/stdb_player_window.rs b/crates/mc173-server/src/autogen/stdb_player_window.rs new file mode 100644 index 0000000..7454b89 --- /dev/null +++ b/crates/mc173-server/src/autogen/stdb_player_window.rs @@ -0,0 +1,34 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN RUST INSTEAD. + +#![allow(unused_imports)] +use super::stdb_i_32_vec_3::StdbI32Vec3; +use super::stdb_window_kind::StdbWindowKind; +use spacetimedb_sdk::{ + anyhow::{anyhow, Result}, + identity::Identity, + reducer::{Reducer, ReducerCallbackId, Status}, + sats::{de::Deserialize, ser::Serialize}, + spacetimedb_lib, + table::{TableIter, TableType, TableWithPrimaryKey}, + Address, +}; + +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] +pub struct StdbPlayerWindow { + pub player_id: u32, + pub kind: StdbWindowKind, + pub pos: StdbI32Vec3, +} + +impl TableType for StdbPlayerWindow { + const TABLE_NAME: &'static str = "StdbPlayerWindow"; + type ReducerEvent = super::ReducerEvent; +} + +impl StdbPlayerWindow { + #[allow(unused)] + pub fn filter_by_player_id(player_id: u32) -> TableIter { + Self::filter(|row| row.player_id == player_id) + } +} diff --git a/crates/mc173-server/src/autogen/stdb_player_window_chest.rs b/crates/mc173-server/src/autogen/stdb_player_window_chest.rs new file mode 100644 index 0000000..d4c9acc --- /dev/null +++ b/crates/mc173-server/src/autogen/stdb_player_window_chest.rs @@ -0,0 +1,32 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN RUST INSTEAD. + +#![allow(unused_imports)] +use super::stdb_i_32_vec_3::StdbI32Vec3; +use spacetimedb_sdk::{ + anyhow::{anyhow, Result}, + identity::Identity, + reducer::{Reducer, ReducerCallbackId, Status}, + sats::{de::Deserialize, ser::Serialize}, + spacetimedb_lib, + table::{TableIter, TableType, TableWithPrimaryKey}, + Address, +}; + +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] +pub struct StdbPlayerWindowChest { + pub player_id: u32, + pub pos: Vec, +} + +impl TableType for StdbPlayerWindowChest { + const TABLE_NAME: &'static str = "StdbPlayerWindowChest"; + type ReducerEvent = super::ReducerEvent; +} + +impl StdbPlayerWindowChest { + #[allow(unused)] + pub fn filter_by_player_id(player_id: u32) -> TableIter { + Self::filter(|row| row.player_id == player_id) + } +} diff --git a/crates/mc173-server/src/autogen/stdb_window_kind.rs b/crates/mc173-server/src/autogen/stdb_window_kind.rs new file mode 100644 index 0000000..92785af --- /dev/null +++ b/crates/mc173-server/src/autogen/stdb_window_kind.rs @@ -0,0 +1,26 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN RUST INSTEAD. + +#![allow(unused_imports)] +use spacetimedb_sdk::{ + anyhow::{anyhow, Result}, + identity::Identity, + reducer::{Reducer, ReducerCallbackId, Status}, + sats::{de::Deserialize, ser::Serialize}, + spacetimedb_lib, + table::{TableIter, TableType, TableWithPrimaryKey}, + Address, +}; + +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] +pub enum StdbWindowKind { + Player, + + CraftingTable, + + Chest, + + Furnace, + + Dispenser, +} diff --git a/crates/mc173-server/src/command.rs b/crates/mc173-server/src/command.rs index 06d99ba..4cefc99 100644 --- a/crates/mc173-server/src/command.rs +++ b/crates/mc173-server/src/command.rs @@ -80,12 +80,12 @@ const COMMANDS: &'static [Command] = &[ description: "Print all available commands", handler: cmd_help }, - // Command { - // name: "give", - // usage: "[:] []", - // description: "Give item to a player", - // handler: cmd_give - // }, + Command { + name: "give", + usage: "[:] []", + description: "Give item to a player", + handler: cmd_give + }, // Command { // name: "spawn", // usage: " [...]", @@ -188,52 +188,53 @@ fn cmd_help(ctx: CommandContext) -> CommandResult { } -// fn cmd_give(ctx: CommandContext) -> CommandResult { -// -// if ctx.parts.len() != 1 && ctx.parts.len() != 2 { -// return Err(None); -// } -// -// let item_raw = ctx.parts[0]; -// -// let ( -// id_raw, -// metadata_raw -// ) = item_raw.split_once(':').unwrap_or((item_raw, "")); -// -// let id; -// if let Ok(direct_id) = id_raw.parse::() { -// id = direct_id; -// } else if let Some(name_id) = item::from_name(id_raw.trim_start_matches("i/")) { -// id = name_id; -// } else if let Some(block_id) = block::from_name(id_raw.trim_start_matches("b/")) { -// id = block_id as u16; -// } else { -// return Err(Some(format!("§cError: unknown item name or id:§r {id_raw}"))); -// } -// -// let item = item::from_id(id); -// if item.name.is_empty() { -// return Err(Some(format!("§cError: unknown item id:§r {id_raw}"))); -// } -// -// let mut stack = ItemStack::new_sized(id, 0, item.max_stack_size); -// -// if !metadata_raw.is_empty() { -// stack.damage = metadata_raw.parse::() -// .map_err(|_| format!("§cError: invalid item damage:§r {metadata_raw}"))?; -// } -// -// if let Some(size_raw) = ctx.parts.get(1) { -// stack.size = size_raw.parse::() -// .map_err(|_| format!("§cError: invalid stack size:§r {size_raw}"))?; -// } -// -// ServerPlayer::send_chat(ctx.server, ctx.connection_id, format!("§aGiving §r{}§a (§r{}:{}§a) x§r{}§a to §r{}", item.name, stack.id, stack.damage, stack.size, ctx.player.username)); -// ctx.player.pickup_stack(&mut stack); -// Ok(()) -// -// } +fn cmd_give(ctx: CommandContext) -> CommandResult { + + if ctx.parts.len() != 1 && ctx.parts.len() != 2 { + return Err(None); + } + + let item_raw = ctx.parts[0]; + + let ( + id_raw, + metadata_raw + ) = item_raw.split_once(':').unwrap_or((item_raw, "")); + + let id; + if let Ok(direct_id) = id_raw.parse::() { + id = direct_id; + } else if let Some(name_id) = item::from_name(id_raw.trim_start_matches("i/")) { + id = name_id; + } else if let Some(block_id) = block::from_name(id_raw.trim_start_matches("b/")) { + id = block_id as u16; + } else { + return Err(Some(format!("§cError: unknown item name or id:§r {id_raw}"))); + } + + let item = item::from_id(id); + if item.name.is_empty() { + return Err(Some(format!("§cError: unknown item id:§r {id_raw}"))); + } + + let mut stack = ItemStack::new_sized(id, 0, item.max_stack_size); + + if !metadata_raw.is_empty() { + stack.damage = metadata_raw.parse::() + .map_err(|_| format!("§cError: invalid item damage:§r {metadata_raw}"))?; + } + + if let Some(size_raw) = ctx.parts.get(1) { + stack.size = size_raw.parse::() + .map_err(|_| format!("§cError: invalid stack size:§r {size_raw}"))?; + } + + let connection_id = ctx.connection_id; + let player = StdbServerPlayer::find_by_connection_id(connection_id).unwrap(); + ServerPlayer::send_chat(ctx.server, ctx.connection_id, format!("§aGiving §r{}§a (§r{}:{}§a) x§r{}§a to §r{}", item.name, stack.id, stack.damage, stack.size, player.username)); + autogen::stdb_give_item(player.entity_id, stack.id, stack.damage, stack.size); + Ok(()) +} // fn cmd_spawn(ctx: CommandContext) -> CommandResult { // let entity = StdbEntity::find_by_entity_id(ctx.player.entity_id).unwrap(); diff --git a/crates/mc173-server/src/item/mod.rs b/crates/mc173-server/src/item/mod.rs index 03c8dd2..1546d6a 100644 --- a/crates/mc173-server/src/item/mod.rs +++ b/crates/mc173-server/src/item/mod.rs @@ -1,6 +1,7 @@ //! Item enumeration and behaviors. -use crate::block; +use crate::{autogen, block}; +use crate::proto::PlaceBlockPacket; pub mod attack; @@ -211,6 +212,18 @@ pub struct ItemStack { pub damage: u16, } +impl From for autogen::ItemStack { + fn from(value: ItemStack) -> Self { + autogen::ItemStack { + id: value.id, + size: value.size, + damage: value.damage, + } + } +} + + + impl ItemStack { pub const EMPTY: Self = Self { id: block::AIR as u16, size: 0, damage: 0 }; diff --git a/crates/mc173-server/src/main.rs b/crates/mc173-server/src/main.rs index c4bc0be..31cb6bb 100644 --- a/crates/mc173-server/src/main.rs +++ b/crates/mc173-server/src/main.rs @@ -13,12 +13,13 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; use tracing::warn; -use crate::autogen::{connect, on_handle_look, on_handle_position, on_handle_position_look, on_stdb_handle_accept, on_stdb_handle_login, ChunkUpdateType, ReducerEvent, StdbBlockSetUpdate, StdbChunk, StdbChunkUpdate, StdbChunkView, StdbEntity, StdbEntityTracker, StdbEntityView, StdbHuman, StdbInLoginPacket, StdbLookPacket, StdbPositionLookPacket, StdbPositionPacket, StdbServerPlayer, StdbSetBlockEvent, StdbWeather}; +use crate::autogen::{connect, on_handle_look, on_handle_position, on_handle_position_look, on_stdb_handle_accept, on_stdb_handle_login, ChunkUpdateType, ReducerEvent, StdbBlockSetUpdate, StdbChunk, StdbChunkUpdate, StdbChunkView, StdbEntity, StdbEntityTracker, StdbEntityView, StdbHuman, StdbInLoginPacket, StdbItemStack, StdbLookPacket, StdbPositionLookPacket, StdbPositionPacket, StdbServerPlayer, StdbSetBlockEvent, StdbWeather}; use crate::player::ServerPlayer; use crate::proto::{InLoginPacket, OutPacket}; use crate::server::Server; use crate::types::Event; use crate::autogen::Weather; +use crate::proto::MetadataKind::ItemStack; // The common configuration of the server. pub mod config; @@ -70,6 +71,25 @@ fn on_weather_updated(old_weather: &StdbWeather, new_weather: &StdbWeather, _red } } +fn on_stdb_item_stack_inserted(slot: &StdbItemStack, reducer_event: Option<&ReducerEvent>) { + let mut s = SERVER.lock().unwrap(); + let server = s.as_ref().unwrap(); + let player = StdbServerPlayer::find_by_entity_id(slot.inventory_id).unwrap(); + let mut client = server.clients[&player.connection_id]; + + let mc_item_stack = item::ItemStack { + id: slot.stack.id, + size: slot.stack.size, + damage: slot.stack.damage, + }; + + server.net.send(client, OutPacket::WindowSetItem(proto::WindowSetItemPacket { + window_id: 0, + slot: slot.index as i16, + stack: Some(mc_item_stack), + })); +} + fn on_chunk_inserted(chunk: &StdbChunk, _reducer_event: Option<&ReducerEvent>) { // println!("Received chunk inserted!"); let mut s = SERVER.lock().unwrap(); @@ -389,6 +409,7 @@ pub fn main() { init_tracing(); // ctrlc::set_handler(|| RUNNING.store(false, Ordering::Relaxed)).unwrap(); + StdbItemStack::on_insert(on_stdb_item_stack_inserted); StdbChunk::on_insert(on_chunk_inserted); StdbChunk::on_update(on_chunk_update); StdbWeather::on_update(on_weather_updated); diff --git a/crates/mc173-server/src/player.rs b/crates/mc173-server/src/player.rs index 36c00b8..7f5fc77 100644 --- a/crates/mc173-server/src/player.rs +++ b/crates/mc173-server/src/player.rs @@ -153,8 +153,8 @@ impl ServerPlayer { InPacket::Disconnect(_) => ServerPlayer::handle_disconnect(server, connection_id), // TODO(jdetter): Add support for chat! - // InPacket::Chat(packet) => - // ServerPlayer::handle_chat(world, state, packet.message), + InPacket::Chat(packet) => + ServerPlayer::handle_chat(server, connection_id, packet.message), InPacket::Position(packet) => ServerPlayer::handle_position(connection_id, packet), InPacket::Look(packet) => @@ -163,10 +163,10 @@ impl ServerPlayer { ServerPlayer::handle_position_look(connection_id, packet), InPacket::BreakBlock(packet) => ServerPlayer::handle_break_block(connection_id, packet), - //InPacket::PlaceBlock(packet) => - // self.handle_place_block(world, packet), - // InPacket::HandSlot(packet) => - // self.handle_hand_slot(world, packet.slot), + InPacket::PlaceBlock(packet) => + ServerPlayer::handle_place_block(connection_id, packet), + InPacket::HandSlot(packet) => + ServerPlayer::handle_hand_slot(connection_id, packet), // InPacket::WindowClick(packet) => // self.handle_window_click(world, packet), // InPacket::WindowClose(packet) => @@ -279,98 +279,19 @@ impl ServerPlayer { fn handle_break_block(connection_id: u64, packet: proto::BreakBlockPacket) { let entity = StdbServerPlayer::find_by_connection_id(connection_id).unwrap(); - autogen::handle_break_block(entity.entity_id, packet.into()); + autogen::stdb_handle_break_block(entity.entity_id, packet.into()); + } + + fn handle_place_block(connection_id: u64, packet: proto::PlaceBlockPacket) { + let entity = StdbServerPlayer::find_by_connection_id(connection_id).unwrap(); + autogen::stdb_handle_place_block(entity.entity_id, packet.into()); + } + + fn handle_hand_slot(connection_id: u64, packet: proto::HandSlotPacket) { + let entity = StdbServerPlayer::find_by_connection_id(connection_id).unwrap(); + autogen::stdb_handle_hand_slot(entity.entity_id, packet.into()); } - //// Handle a break block packet. - // fn handle_break_block(&mut self, world: &mut World, packet: proto::BreakBlockPacket) { - // - // let face = match packet.face { - // 0 => Face::NegY, - // 1 => Face::PosY, - // 2 => Face::NegZ, - // 3 => Face::PosZ, - // 4 => Face::NegX, - // 5 => Face::PosX, - // _ => return, - // }; - // - // let Some(entity) = world.get_entity_mut(self.entity_id) else { return }; - // let pos = IVec3::new(packet.x, packet.y as i32, packet.z); - // - // tracing::trace!("packet: {packet:?}"); - // // TODO: Use server time for breaking blocks. - // - // let in_water = entity.0.in_water; - // let on_ground = entity.0.on_ground; - // let mut stack = self.main_inv[self.hand_slot as usize]; - // - // if packet.status == 0 { - // - // // Special case to extinguish fire. - // if world.is_block(pos + face.delta(), block::FIRE) { - // world.set_block_notify(pos + face.delta(), block::AIR, 0); - // } - // - // // We ignore any interaction result for the left click (break block) to - // // avoid opening an inventory when breaking a container. - // // NOTE: Interact before 'get_block': relevant for redstone_ore lit. - // world.interact_block(pos); - // - // // Start breaking a block, ignore if the position is invalid. - // if let Some((id, _)) = world.get_block(pos) { - // - // let break_duration = world.get_break_duration(stack.id, id, in_water, on_ground); - // if break_duration.is_infinite() { - // // Do nothing, the block is unbreakable. - // } else if break_duration == 0.0 { - // world.break_block(pos); - // } else { - // self.breaking_block = Some(BreakingBlock { - // start_time: world.get_time(), // + (break_duration * 0.7) as u64, - // pos, - // id, - // }); - // } - // - // } - // - // } else if packet.status == 2 { - // // Block breaking should be finished. - // if let Some(state) = self.breaking_block.take() { - // if state.pos == pos && world.is_block(pos, state.id) { - // let break_duration = world.get_break_duration(stack.id, state.id, in_water, on_ground); - // let min_time = state.start_time + (break_duration * 0.7) as u64; - // if world.get_time() >= min_time { - // world.break_block(pos); - // } else { - // warn!("from {}, incoherent break time, expected {min_time} but got {}", self.username, world.get_time()); - // } - // } else { - // warn!("from {}, incoherent break position, expected {}, got {}", self.username, pos, state.pos); - // } - // } - // } else if packet.status == 4 { - // // Drop the selected item. - // - // if !stack.is_empty() { - // - // stack.size -= 1; - // self.main_inv[self.hand_slot as usize] = stack.to_non_empty().unwrap_or_default(); - // - // self.send(OutPacket::WindowSetItem(proto::WindowSetItemPacket { - // window_id: 0, - // slot: 36 + self.hand_slot as i16, - // stack: stack.to_non_empty(), - // })); - // - // self.drop_stack(world, stack.with_size(1), false); - // - // } - // - // } - // - // } // Handle a place block packet. // fn handle_place_block(&mut self, world: &mut World, packet: proto::PlaceBlockPacket) { @@ -435,18 +356,18 @@ impl ServerPlayer { // // // If the previous item was a fishing rod, then we ensure that the bobber id // // is unset from the player's entity, so that the bobber will be removed. - // let prev_stack = self.main_inv[self.hand_slot as usize]; - // if prev_stack.size != 0 && prev_stack.id == item::FISHING_ROD { - // if prev_stack.id == item::FISHING_ROD { - // - // let Entity(base, _) = world.get_entity_mut(self.entity_id).expect("incoherent player entity"); - // base.bobber_id = None; - // - // } - // } + // // TODO(jdetter): If the player was holding a fishing rod, we need to destroy their bobber + // // let prev_stack = self.main_inv[self.hand_slot as usize]; + // // if prev_stack.size != 0 && prev_stack.id == item::FISHING_ROD { + // // if prev_stack.id == item::FISHING_ROD { + // // + // // let Entity(base, _) = world.get_entity_mut(self.entity_id).expect("incoherent player entity"); + // // base.bobber_id = None; + // // + // // } + // // } // // self.hand_slot = slot as u8; - // // } else { // warn!("from {}, invalid hand slot: {slot}", self.username); // } diff --git a/crates/mc173-server/src/proto.rs b/crates/mc173-server/src/proto.rs index 06dd9d1..d18b653 100644 --- a/crates/mc173-server/src/proto.rs +++ b/crates/mc173-server/src/proto.rs @@ -316,12 +316,32 @@ pub struct PlaceBlockPacket { pub stack: Option, } +impl From for autogen::StdbPlaceBlockPacket { + fn from(value: PlaceBlockPacket) -> Self { + autogen::StdbPlaceBlockPacket { + x: value.x, + y: value.y, + z: value.z, + direction: value.direction, + stack: value.stack.map(|f| f.into()) + } + } +} + /// Packet 16 #[derive(Debug, Clone)] pub struct HandSlotPacket { pub slot: i16, } +impl From for autogen::HandSlotPacket { + fn from(value: HandSlotPacket) -> Self { + autogen::HandSlotPacket { + slot: value.slot, + } + } +} + /// Packet 17 #[derive(Debug, Clone)] pub struct PlayerSleepPacket { diff --git a/crates/module/src/lib.rs b/crates/module/src/lib.rs index 8772def..a5b78ce 100644 --- a/crates/module/src/lib.rs +++ b/crates/module/src/lib.rs @@ -16,7 +16,7 @@ use std::process::exit; use std::time::Duration; use glam::{DVec3, IVec3}; use mc173_module::world::{StdbWorld, DIMENSION_OVERWORLD}; -use spacetimedb::{ReducerContext, schedule, spacetimedb, SpacetimeType, Timestamp}; +use spacetimedb::{ReducerContext, schedule, spacetimedb, SpacetimeType, Timestamp, query}; use mc173_module::{block, item}; use mc173_module::chunk::calc_entity_chunk_pos; use mc173_module::chunk_cache::ChunkCache; @@ -26,15 +26,18 @@ use mc173_module::geom::Face; use mc173_module::i16vec3::StdbI16Vec3; use mc173_module::i32vec3::StdbI32Vec3; use mc173_module::i8vec3::StdbI8Vec2; +use mc173_module::inventory::{StdbHandSlot, StdbInventory, StdbItemStack}; +use mc173_module::item::ItemStack; use mc173_module::stdb::chunk::{StdbBreakBlockPacket, BreakingBlock, StdbBreakingBlock, StdbTime, StdbChunkUpdate, StdbChunk, ChunkUpdateType}; use mc173_module::stdb::weather::StdbWeather; use mc173_module::storage::ChunkStorage; use mc173_module::vec2::StdbVec2; +use mc173_module::world::interact::Interaction; use crate::config::SPAWN_POS; use crate::entity::{StdbEntityTracker, StdbEntityTrackerUpdateType}; use crate::offline::StdbOfflinePlayer; use crate::player::{StdbClientState, StdbConnectionStatus, StdbEntity, StdbOfflineServerPlayer, StdbPlayingState, StdbServerPlayer}; -use crate::proto::{StdbLookPacket, StdbPositionLookPacket, StdbPositionPacket}; +use crate::proto::{HandSlotPacket, StdbLookPacket, StdbPositionLookPacket, StdbPositionPacket}; use crate::world::{StdbServerWorld, StdbTickMode}; pub mod player; @@ -150,6 +153,17 @@ fn stdb_handle_login(connection_id: u64, packet: proto::StdbInLoginPacket) { sleeping: false, sneaking: false, }).unwrap(); + + StdbInventory::insert(StdbInventory { + inventory_id: new_entity.entity_id.clone(), + size: 36, + }).unwrap(); + + StdbHandSlot::insert(StdbHandSlot { + player_id: new_entity.entity_id.clone(), + slot_id: 0, + }).unwrap(); + (new_entity, player) }; @@ -566,7 +580,7 @@ pub fn generate_chunk(x: i32, z: i32) { // } #[spacetimedb(reducer)] -pub fn handle_break_block(entity_id: u32, packet: StdbBreakBlockPacket) { +pub fn stdb_handle_break_block(entity_id: u32, packet: StdbBreakBlockPacket) { let mut cache = ChunkCache::new(); @@ -695,6 +709,114 @@ pub fn handle_break_block(entity_id: u32, packet: StdbBreakBlockPacket) { cache.apply(); } +#[spacetimedb(reducer)] +fn stdb_give_item(player_id: u32, id: u16, damage: u16, size: u16) { + let mut stack = ItemStack { + id, + size, + damage, + }; + StdbInventory::push_front(player_id, &mut stack); + if stack.size != 0 { + log::warn!("Failed to insert entire stack. Remaining items: {}", stack.size); + } else { + log::info!("Stack successfully given to player."); + } +} + +/// Handle a place block packet. +#[spacetimedb(reducer)] +fn stdb_handle_place_block(player_id: u32, packet: proto::StdbPlaceBlockPacket) { + + let mut cache = ChunkCache::new(); + + let face = match packet.direction { + 0 => Some(Face::NegY), + 1 => Some(Face::PosY), + 2 => Some(Face::NegZ), + 3 => Some(Face::PosZ), + 4 => Some(Face::NegX), + 5 => Some(Face::PosX), + 0xFF => None, + _ => return, + }; + + let pos = IVec3 { + x: packet.x, + y: packet.y as i32, + z: packet.z, + }; + + let mut world = StdbServerWorld::filter_by_dimension_id(&DIMENSION_OVERWORLD).unwrap(); + let inv = StdbInventory::filter_by_inventory_id(&player_id).expect( + format!("Failed to find inventory for player: {}", player_id).as_str()); + let inv_index = StdbHandSlot::filter_by_player_id(&player_id).expect( + format!("Failed to find hand slot for player: {}", player_id).as_str()).slot_id; + + // Check if the player is reasonably near the block. + // TODO(jdetter): Reenable this check + // if face.is_none() || self.pos.distance_squared(pos.as_dvec3() + 0.5) < 64.0 { + // The real action depends on + if let Some(face) = face { + match StdbServerWorld::interact_block(pos, false) { + Interaction::None => { + // No interaction, use the item at that block. + world.use_stack(inv.inventory_id, inv_index, pos, face, player_id, &mut cache); + } + // Interaction::CraftingTable { pos } => { + // return self.open_window(sw, WindowKind::CraftingTable { pos }); + // } + // Interaction::Chest { pos } => { + // return self.open_window(sw, WindowKind::Chest { pos }); + // } + // Interaction::Furnace { pos } => { + // return self.open_window(sw, WindowKind::Furnace { pos }); + // } + // Interaction::Dispenser { pos } => { + // return self.open_window(sw, WindowKind::Dispenser { pos }); + // } + // Interaction::Handled => {} + _ => {} + } + } else { + // world.use_raw_stack(&mut inv, inv_index, self.entity_id); + } + // } + +/* for index in inv.iter_changes() { + self.send_main_inv_item(index); + }*/ + + cache.apply(); +} + +#[spacetimedb(reducer)] +fn stdb_handle_hand_slot(player_id: u32, packet: HandSlotPacket) -> Result<(), String> { + let slot = packet.slot; + let player = StdbServerPlayer::filter_by_entity_id(&player_id).unwrap(); + if slot >= 0 && slot < 9 { + let mut hand_slot = StdbHandSlot::filter_by_player_id(&player_id).expect( + format!("Player hand slot not found: {}", player_id).as_str()); + + // If the previous item was a fishing rod, then we ensure that the bobber id + // is unset from the player's entity, so that the bobber will be removed. + let previous_hand_slot = StdbInventory::get(player_id, hand_slot.slot_id); + // let prev_stack = self.main_inv[self.hand_slot as usize]; + if previous_hand_slot.size != 0 && previous_hand_slot.id == item::FISHING_ROD { + todo!("Handle player fishing rod"); + // let Entity(base, _) = world.get_entity_mut(self.entity_id).expect("incoherent player entity"); + // base.bobber_id = None; + } + + hand_slot.slot_id = slot as u32; + StdbHandSlot::update_by_player_id(&player_id, hand_slot); + log::info!("Player {} changed their slot to index: {}", player.username, packet.slot); + Ok(()) + } else { + Err(format!("from player with ID {}, invalid hand slot: {slot}", player_id)) + } +} + #[spacetimedb(reducer)] fn handle_position(entity_id: u32, packet: StdbPositionPacket) { let mut player = StdbServerPlayer::filter_by_entity_id(&entity_id).expect( diff --git a/crates/module/src/player.rs b/crates/module/src/player.rs index e50e40e..9923cbf 100644 --- a/crates/module/src/player.rs +++ b/crates/module/src/player.rs @@ -135,143 +135,36 @@ pub struct StdbEntity { pub dimension_id: i32, } -/// Describe an opened window and how to handle clicks into it. -// #[derive(Debug, Default)] -// struct Window { -// /// The unique id of the currently opened window. -// id: u8, -// /// Specialization kind of window. -// kind: WindowKind, -// } +#[derive(SpacetimeType)] +pub enum StdbWindowKind { + /// The player inventory is the default window that is always opened if no other + /// window is opened, it also always has the id 0, it contains the armor and craft + /// matrix. + Player, + /// The client-side has a crafting table window opened on the given block pos. + CraftingTable, + /// The client-side has a chest window opened referencing the listed block entities. + Chest, + /// The client-side has a furnace window onto the given block entity. + Furnace, + /// The client-side has a dispenser window onto the given block entity. + Dispenser, +} -/// Describe a kind of opened window on the client side. -// #[derive(Debug, Default)] -// enum WindowKind { -// /// The player inventory is the default window that is always opened if no other -// /// window is opened, it also always has the id 0, it contains the armor and craft -// /// matrix. -// #[default] -// Player, -// /// The client-side has a crafting table window opened on the given block pos. -// CraftingTable { -// pos: IVec3, -// }, -// /// The client-side has a chest window opened referencing the listed block entities. -// Chest { -// pos: Vec, -// }, -// /// The client-side has a furnace window onto the given block entity. -// Furnace { -// pos: IVec3, -// }, -// /// The client-side has a dispenser window onto the given block entity. -// Dispenser { -// pos: IVec3, -// } -// } +#[spacetimedb(table(public))] +pub struct StdbPlayerWindow { + pub player_id: u32, + pub kind: StdbWindowKind, + pub pos: StdbI32Vec3, +} -// /// State of a player breaking a block. -// struct BreakingBlock { -// /// The start time of this block breaking. -// start_time: u64, -// /// The position of the block. -// pos: IVec3, -// /// The block id. -// id: u8, -// } +#[spacetimedb(table(public))] +pub struct StdbPlayerWindowChest { + pub player_id: u32, + pub pos: Vec, +} impl StdbServerPlayer { - - /// Construct a new player with a configured network, an associated entity id and with - /// initial position and look given from its offline serialized data. - // pub fn new(net: &Network, client: NetworkClient, entity_id: u32, username: String, offline: &OfflinePlayer) -> Self { - // Self { - // // net: net.clone(), - // // client, - // entity_id, - // username, - // pos: offline.pos, - // look: offline.look, - // // tracked_chunks: HashSet::new(), - // // tracked_entities: HashSet::new(), - // // main_inv: Box::new([ItemStack::EMPTY; 36]), - // // armor_inv: Box::new([ItemStack::EMPTY; 4]), - // // craft_inv: Box::new([ItemStack::EMPTY; 9]), - // // cursor_stack: ItemStack::EMPTY, - // // hand_slot: 0, - // // window_count: 0, - // // window: Window::default(), - // // craft_tracker: CraftTracker::default(), - // // breaking_block: None, - // } - // } - - /// Send a packet to this player. - // pub fn send(&self, packet: OutPacket) { - // // println!("[NET] Sending packet {packet:?}"); - // self.net.send(self.client, packet); - // } - - /// Send a chat message to this player. - // pub fn send_chat(&self, message: String) { - // self.send(OutPacket::Chat(proto::ChatPacket { message })); - // } - - /// Handle an incoming packet from this player. - // pub fn handle(&mut self, world: &mut World, state: &mut ServerWorldState, packet: InPacket) { - // - // match packet { - // InPacket::KeepAlive => {} - // InPacket::Flying(_) => {}, // Ignore because it doesn't update anything. - // InPacket::Disconnect(_) => - // self.handle_disconnect(), - // InPacket::Chat(packet) => - // self.handle_chat(world, state, packet.message), - // InPacket::Position(packet) => - // self.handle_position(world, packet), - // InPacket::Look(packet) => - // self.handle_look(world, packet), - // InPacket::PositionLook(packet) => - // self.handle_position_look(world, packet), - // InPacket::BreakBlock(packet) => - // self.handle_break_block(packet), - // // InPacket::PlaceBlock(packet) => - // // self.handle_place_block(world, packet), - // InPacket::HandSlot(packet) => - // self.handle_hand_slot(world, packet.slot), - // InPacket::WindowClick(packet) => - // self.handle_window_click(world, packet), - // InPacket::WindowClose(packet) => - // self.handle_window_close(world, packet), - // InPacket::Animation(packet) => - // self.handle_animation(world, packet), - // InPacket::Interact(packet) => - // self.handle_interact(world, packet), - // InPacket::Action(packet) => - // self.handle_action(world, packet), - // _ => warn!("unhandled packet from #{}: {packet:?}", self.client.id()) - // } - // - // } - - /// Just disconnect itself, this will produce a lost event from the network. - // fn handle_disconnect(&mut self) { - // self.net.disconnect(self.client); - // } - - /// Handle a chat message packet. - // fn handle_chat(&mut self, world: &mut World, state: &mut ServerWorldState, message: String) { - // if message.starts_with('/') { - // let parts = message[1..].split_whitespace().collect::>(); - // command::handle_command(CommandContext { - // parts: &parts, - // world, - // state, - // player: self, - // }); - // } - // } - /// Handle a position packet. pub fn handle_position(self, packet: StdbPositionPacket) { self.handle_position_look_inner(Some(packet.pos), None, packet.on_ground); @@ -288,7 +181,6 @@ impl StdbServerPlayer { } pub fn handle_position_look_inner(&self, pos: Option, look: Option, on_ground: bool) { - let mut entity = StdbEntity::filter_by_entity_id(&self.entity_id).expect( format!("Could not find player with id: {}", self.entity_id).as_str()); // let entity = world.get_entity_mut(self.entity_id).expect("incoherent player entity"); @@ -322,1142 +214,14 @@ impl StdbServerPlayer { // } } - // fn handle_break_block(&mut self, packet: proto::BreakBlockPacket) { - // autogen::autogen::handle_break_block(self.entity_id, packet.into()); - // } - - //// Handle a break block packet. - // fn handle_break_block(&mut self, world: &mut World, packet: proto::BreakBlockPacket) { - // - // let face = match packet.face { - // 0 => Face::NegY, - // 1 => Face::PosY, - // 2 => Face::NegZ, - // 3 => Face::PosZ, - // 4 => Face::NegX, - // 5 => Face::PosX, - // _ => return, - // }; - // - // let Some(entity) = world.get_entity_mut(self.entity_id) else { return }; - // let pos = IVec3::new(packet.x, packet.y as i32, packet.z); - // - // tracing::trace!("packet: {packet:?}"); - // // TODO: Use server time for breaking blocks. - // - // let in_water = entity.0.in_water; - // let on_ground = entity.0.on_ground; - // let mut stack = self.main_inv[self.hand_slot as usize]; - // - // if packet.status == 0 { - // - // // Special case to extinguish fire. - // if world.is_block(pos + face.delta(), block::FIRE) { - // world.set_block_notify(pos + face.delta(), block::AIR, 0); - // } - // - // // We ignore any interaction result for the left click (break block) to - // // avoid opening an inventory when breaking a container. - // // NOTE: Interact before 'get_block': relevant for redstone_ore lit. - // world.interact_block(pos); - // - // // Start breaking a block, ignore if the position is invalid. - // if let Some((id, _)) = world.get_block(pos) { - // - // let break_duration = world.get_break_duration(stack.id, id, in_water, on_ground); - // if break_duration.is_infinite() { - // // Do nothing, the block is unbreakable. - // } else if break_duration == 0.0 { - // world.break_block(pos); - // } else { - // self.breaking_block = Some(BreakingBlock { - // start_time: world.get_time(), // + (break_duration * 0.7) as u64, - // pos, - // id, - // }); - // } - // - // } - // - // } else if packet.status == 2 { - // // Block breaking should be finished. - // if let Some(state) = self.breaking_block.take() { - // if state.pos == pos && world.is_block(pos, state.id) { - // let break_duration = world.get_break_duration(stack.id, state.id, in_water, on_ground); - // let min_time = state.start_time + (break_duration * 0.7) as u64; - // if world.get_time() >= min_time { - // world.break_block(pos); - // } else { - // warn!("from {}, incoherent break time, expected {min_time} but got {}", self.username, world.get_time()); - // } - // } else { - // warn!("from {}, incoherent break position, expected {}, got {}", self.username, pos, state.pos); - // } - // } - // } else if packet.status == 4 { - // // Drop the selected item. - // - // if !stack.is_empty() { - // - // stack.size -= 1; - // self.main_inv[self.hand_slot as usize] = stack.to_non_empty().unwrap_or_default(); - // - // self.send(OutPacket::WindowSetItem(proto::WindowSetItemPacket { - // window_id: 0, - // slot: 36 + self.hand_slot as i16, - // stack: stack.to_non_empty(), - // })); - // - // self.drop_stack(world, stack.with_size(1), false); - // - // } - // - // } - // - // } - - // Handle a place block packet. - // fn handle_place_block(&mut self, world: &mut World, packet: proto::PlaceBlockPacket) { - // - // let face = match packet.direction { - // 0 => Some(Face::NegY), - // 1 => Some(Face::PosY), - // 2 => Some(Face::NegZ), - // 3 => Some(Face::PosZ), - // 4 => Some(Face::NegX), - // 5 => Some(Face::PosX), - // 0xFF => None, - // _ => return, - // }; - // - // let pos = IVec3 { - // x: packet.x, - // y: packet.y as i32, - // z: packet.z, - // }; - // - // let mut inv = InventoryHandle::new(&mut self.main_inv[..]); - // let inv_index = self.hand_slot as usize; - // - // // Check if the player is reasonably near the block. - // if face.is_none() || self.pos.distance_squared(pos.as_dvec3() + 0.5) < 64.0 { - // // The real action depends on - // if let Some(face) = face { - // match world.interact_block(pos) { - // Interaction::None => { - // // No interaction, use the item at that block. - // world.use_stack(&mut inv, inv_index, pos, face, self.entity_id); - // } - // Interaction::CraftingTable { pos } => { - // return self.open_window(world, WindowKind::CraftingTable { pos }); - // } - // Interaction::Chest { pos } => { - // return self.open_window(world, WindowKind::Chest { pos }); - // } - // Interaction::Furnace { pos } => { - // return self.open_window(world, WindowKind::Furnace { pos }); - // } - // Interaction::Dispenser { pos } => { - // return self.open_window(world, WindowKind::Dispenser { pos }); - // } - // Interaction::Handled => {} - // } - // } else { - // world.use_raw_stack(&mut inv, inv_index, self.entity_id); - // } - // } - // - // for index in inv.iter_changes() { - // self.send_main_inv_item(index); - // } - // - // } - - //// Handle a hand slot packet. - // fn handle_hand_slot(&mut self, world: &mut World, slot: i16) { - // if slot >= 0 && slot < 9 { - // - // // If the previous item was a fishing rod, then we ensure that the bobber id - // // is unset from the player's entity, so that the bobber will be removed. - // let prev_stack = self.main_inv[self.hand_slot as usize]; - // if prev_stack.size != 0 && prev_stack.id == item::FISHING_ROD { - // if prev_stack.id == item::FISHING_ROD { - // - // let Entity(base, _) = world.get_entity_mut(self.entity_id).expect("incoherent player entity"); - // base.bobber_id = None; - // - // } - // } - // - // self.hand_slot = slot as u8; - // - // } else { - // warn!("from {}, invalid hand slot: {slot}", self.username); - // } - // } - - //// Handle a window click packet. - // fn handle_window_click(&mut self, world: &mut World, packet: proto::WindowClickPacket) { - // - // // Holding the target slot's item stack. - // let mut cursor_stack = self.cursor_stack; - // let slot_stack; - // let slot_notify; - // - // // Check coherency of server/client windows. - // if self.window.id != packet.window_id { - // warn!("from {}, incoherent window id, expected {}, got {} from client", self.username, self.window.id, packet.window_id); - // return; - // } - // - // if packet.slot == -999 { - // slot_stack = ItemStack::EMPTY; - // slot_notify = SlotNotify::None; - // if !cursor_stack.is_empty() { - // - // let mut drop_stack = cursor_stack; - // if packet.right_click { - // drop_stack = drop_stack.with_size(1); - // } - // - // cursor_stack.size -= drop_stack.size; - // self.drop_stack(world, drop_stack, false); - // - // } - // } else if packet.shift_click { - // - // if packet.slot < 0 { - // return; - // } - // - // // TODO: This whole branch should be reworked to use a similar approach to - // // regular clicks. One idea would be to have some kind of "SlotTransfer" - // // structure that references targets for transfers, like current "SlotHandle". - // - // let slot = packet.slot as usize; - // - // // Try to get the main slot, if any. - // let main_slot = match self.window.kind { - // WindowKind::Player => slot.checked_sub(9), - // WindowKind::CraftingTable { .. } => slot.checked_sub(10), - // WindowKind::Chest { ref pos } => slot.checked_sub(pos.len() * 27), - // WindowKind::Furnace { .. } => slot.checked_sub(3), - // WindowKind::Dispenser { .. } => slot.checked_sub(9), - // }; - // - // // From the slot number, we get the index in the main inventory stacks. - // // The the main slot is set by invalid, just abort. - // let main_index = match main_slot { - // Some(n @ 0..=26) => Some(n + 9), - // Some(n @ 27..=35) => Some(n - 27), - // Some(_) => return, - // _ => None - // }; - // - // // Create a handle to the main inventory. - // let mut main_inv = InventoryHandle::new(&mut self.main_inv[..]); - // - // // Each window kind has a different handling of shift click... - // match self.window.kind { - // WindowKind::Player => { - // if let Some(main_index) = main_index { - // // Between hotbar and inventory... - // slot_stack = main_inv.get(main_index); - // let mut stack = slot_stack; - // main_inv.push_front_in(&mut stack, if main_index < 9 { 9..36 } else { 0..9 }); - // main_inv.set(main_index, stack); - // slot_notify = SlotNotify::None; - // } else if slot == 0 { - // - // // Craft result - // if let Some(mut result_stack) = self.craft_tracker.recipe() { - // slot_stack = result_stack; - // if main_inv.can_push(result_stack) { - // - // self.craft_tracker.consume(&mut self.craft_inv); - // - // main_inv.push_back_in(&mut result_stack, 0..9); - // main_inv.push_back_in(&mut result_stack, 9..36); - // assert!(result_stack.is_empty()); - // - // slot_notify = SlotNotify::Craft { - // mapping: Some(&[1, 2, -1, 3, 4, -1, -1, -1, -1]), - // modified: true, - // }; - // - // } else { - // slot_notify = SlotNotify::None; - // } - // } else { - // slot_stack = ItemStack::EMPTY; - // slot_notify = SlotNotify::None; - // } - // - // } else if slot >= 1 && slot <= 4 { - // - // // Craft matrix - // let stack = match slot { - // 1 | 2 => &mut self.craft_inv[slot - 1], - // 3 | 4 => &mut self.craft_inv[slot], - // _ => unreachable!() - // }; - // - // slot_stack = *stack; - // main_inv.push_front_in(stack, 9..36); - // main_inv.push_front_in(stack, 0..9); - // - // slot_notify = SlotNotify::Craft { - // mapping: None, - // modified: true, - // }; - // - // } else { - // // Armor - // let stack = &mut self.armor_inv[slot - 5]; - // slot_stack = *stack; - // main_inv.push_front_in(stack, 9..36); - // main_inv.push_front_in(stack, 0..9); - // slot_notify = SlotNotify::None; - // } - // } - // WindowKind::CraftingTable { .. } => { - // - // if let Some(main_index) = main_index { - // // Between hotbar and inventory... - // slot_stack = main_inv.get(main_index); - // let mut stack = slot_stack; - // main_inv.push_front_in(&mut stack, if main_index < 9 { 9..36 } else { 0..9 }); - // main_inv.set(main_index, stack); - // slot_notify = SlotNotify::None; - // } else if slot == 0 { - // - // // Craft result - // if let Some(mut result_stack) = self.craft_tracker.recipe() { - // slot_stack = result_stack; - // if main_inv.can_push(result_stack) { - // - // self.craft_tracker.consume(&mut self.craft_inv); - // - // main_inv.push_back_in(&mut result_stack, 0..9); - // main_inv.push_back_in(&mut result_stack, 9..36); - // assert!(result_stack.is_empty()); - // - // slot_notify = SlotNotify::Craft { - // mapping: Some(&[1, 2, 3, 4, 5, 6, 7, 8, 9]), - // modified: true, - // }; - // - // } else { - // slot_notify = SlotNotify::None; - // } - // } else { - // slot_stack = ItemStack::EMPTY; - // slot_notify = SlotNotify::None; - // } - // - // } else { - // - // // Craft matrix - // let stack = &mut self.craft_inv[slot - 1]; - // - // slot_stack = *stack; - // main_inv.push_front_in(stack, 9..36); - // main_inv.push_front_in(stack, 0..9); - // - // slot_notify = SlotNotify::Craft { - // mapping: None, - // modified: true, - // }; - // - // } - // - // } - // WindowKind::Chest { ref pos } => { - // - // if let Some(main_index) = main_index { - // - // // From hotbar/inventory to chest. - // slot_stack = main_inv.get(main_index); - // let mut stack = slot_stack; - // - // // Temporarily swap events out to avoid borrowing issues. - // let mut events = world.swap_events(None); - // - // // We try to insert - // for &pos in pos { - // if let Some(BlockEntity::Chest(chest)) = world.get_block_entity_mut(pos) { - // - // let mut chest_inv = InventoryHandle::new(&mut chest.inv[..]); - // chest_inv.push_front(&mut stack); - // - // // Push all changes in the chest inventory as world event. - // if let Some(events) = &mut events { - // for index in chest_inv.iter_changes() { - // events.push(Event::BlockEntity { - // pos, - // inner: BlockEntityEvent::Storage { - // storage: BlockEntityStorage::Standard(index as u8), - // stack: chest_inv.get(index), - // }, - // }); - // } - // } - // - // if stack.is_empty() { - // break; - // } - // - // } - // } - // - // main_inv.set(main_index, stack); - // - // // Swap events back in. - // world.swap_events(events); - // // No notify because we handled the events for all chest entities. - // slot_notify = SlotNotify::None; - // - // } else { - // - // // From the chest to hotbar/inventory - // - // let pos = pos[slot / 27]; - // let Some(BlockEntity::Chest(chest)) = world.get_block_entity_mut(pos) else { - // return; - // }; - // - // let index = slot % 27; - // let stack = &mut chest.inv[index]; - // slot_stack = *stack; - // if !stack.is_empty() { - // main_inv.push_back_in(stack, 0..9); - // main_inv.push_back_in(stack, 9..36); - // } - // - // slot_notify = SlotNotify::BlockEntityStorageEvent { - // pos, - // storage: BlockEntityStorage::Standard(index as u8), - // stack: Some(*stack), - // }; - // - // } - // - // } - // WindowKind::Furnace { pos } => { - // - // if let Some(main_index) = main_index { - // // Between hotbar and inventory... - // slot_stack = main_inv.get(main_index); - // let mut stack = slot_stack; - // main_inv.push_front_in(&mut stack, if main_index < 9 { 9..36 } else { 0..9 }); - // main_inv.set(main_index, stack); - // slot_notify = SlotNotify::None; - // } else { - // - // // From furnace to inventory - // let Some(BlockEntity::Furnace(furnace)) = world.get_block_entity_mut(pos) else { - // return; - // }; - // - // let (stack, storage) = match slot { - // 0 => (&mut furnace.input_stack, BlockEntityStorage::FurnaceInput), - // 1 => (&mut furnace.fuel_stack, BlockEntityStorage::FurnaceFuel), - // 2 => (&mut furnace.output_stack, BlockEntityStorage::FurnaceOutput), - // _ => unreachable!() - // }; - // - // slot_stack = *stack; - // main_inv.push_front_in(stack, 9..36); - // main_inv.push_front_in(stack, 0..9); - // - // slot_notify = SlotNotify::BlockEntityStorageEvent { - // pos, - // storage, - // stack: Some(*stack), - // }; - // - // } - // - // } - // WindowKind::Dispenser { pos } => { - // - // // No shift click possible with dispenser, but we check coherency. - // if let Some(main_index) = main_index { - // slot_stack = main_inv.get(main_index); - // } else if let Some(BlockEntity::Dispenser(dispenser)) = world.get_block_entity_mut(pos) { - // slot_stack = dispenser.inv[slot]; - // } else { - // // No dispenser block entity?? - // return; - // } - // - // slot_notify = SlotNotify::None; - // - // } - // } - // - // } else { - // - // let slot_handle = self.make_window_slot_handle(world, packet.slot); - // let Some(mut slot_handle) = slot_handle else { - // warn!("from {}, cannot find a handle for slot {} in window {}", self.username, packet.slot, packet.window_id); - // return; - // }; - // - // slot_stack = slot_handle.get_stack(); - // let slot_access = slot_handle.get_access(); - // - // if slot_stack.is_empty() { - // if !cursor_stack.is_empty() && slot_access.can_drop(cursor_stack) { - // - // let drop_size = if packet.right_click { 1 } else { cursor_stack.size }; - // let drop_size = drop_size.min(slot_handle.max_stack_size()); - // - // slot_handle.set_stack(cursor_stack.with_size(drop_size)); - // cursor_stack.size -= drop_size; - // - // } - // } else if cursor_stack.is_empty() { - // - // // Here the slot is not empty, but the cursor is. - // - // // NOTE: Splitting is equivalent of taking and then drop (half), we check - // // if the slot would accept that drop by checking validity. - // cursor_stack = slot_stack; - // if packet.right_click && slot_access.can_drop(cursor_stack) { - // cursor_stack.size = (cursor_stack.size + 1) / 2; - // } - // - // let mut new_slot_stack = slot_stack; - // new_slot_stack.size -= cursor_stack.size; - // if new_slot_stack.size == 0 { - // slot_handle.set_stack(ItemStack::EMPTY); - // } else { - // slot_handle.set_stack(new_slot_stack); - // } - // - // } else if slot_access.can_drop(cursor_stack) { - // - // // Here the slot and the cursor are not empty, we check if we can - // // drop some item if compatible, or swap if not. - // - // let cursor_item = item::from_id(cursor_stack.id); - // - // if (slot_stack.id, slot_stack.damage) != (cursor_stack.id, cursor_stack.damage) { - // // Not the same item, we just swap with hand. - // if cursor_stack.size <= slot_handle.max_stack_size() { - // slot_handle.set_stack(cursor_stack); - // cursor_stack = slot_stack; - // } - // } else { - // // Same item, just drop some into the existing stack. - // let max_stack_size = cursor_item.max_stack_size.min(slot_handle.max_stack_size()); - // // Only drop if the stack is not full. - // if slot_stack.size < max_stack_size { - // - // let drop_size = if packet.right_click { 1 } else { cursor_stack.size }; - // let drop_size = drop_size.min(max_stack_size - slot_stack.size); - // cursor_stack.size -= drop_size; - // - // let mut new_slot_stack = slot_stack; - // new_slot_stack.size += drop_size; - // slot_handle.set_stack(new_slot_stack); - // - // } - // } - // - // } else if let SlotAccess::Pickup(min_size) = slot_access { - // - // // This last case is when the slot and the cursor are not empty, but we - // // can't drop the cursor into the slot, in such case we try to pick item. - // - // if (slot_stack.id, slot_stack.damage) == (cursor_stack.id, cursor_stack.damage) { - // let cursor_item = item::from_id(cursor_stack.id); - // if cursor_stack.size < cursor_item.max_stack_size { - // let available_size = cursor_item.max_stack_size - cursor_stack.size; - // if available_size >= min_size { - // let pick_size = slot_stack.size.min(available_size); - // cursor_stack.size += pick_size; - // let new_slot_stack = slot_stack.with_size(slot_stack.size - pick_size); - // slot_handle.set_stack(new_slot_stack.to_non_empty().unwrap_or_default()); - // } - // } - // } - // - // } - // - // slot_notify = slot_handle.notify; - // - // } - // - // // Handle notification if the slot has changed. - // match slot_notify { - // SlotNotify::Craft { - // mapping, - // modified: true, - // } => { - // - // self.craft_tracker.update(&self.craft_inv); - // - // self.net.send(self.client, OutPacket::WindowSetItem(proto::WindowSetItemPacket { - // window_id: packet.window_id, - // slot: 0, - // stack: self.craft_tracker.recipe(), - // })); - // - // if let Some(mapping) = mapping { - // for (index, &slot) in mapping.iter().enumerate() { - // if slot >= 0 { - // self.net.send(self.client, OutPacket::WindowSetItem(proto::WindowSetItemPacket { - // window_id: packet.window_id, - // slot, - // stack: self.craft_inv[index].to_non_empty(), - // })); - // } - // } - // } - // - // } - // SlotNotify::BlockEntityStorageEvent { - // pos, - // storage, - // stack: Some(stack), - // } => { - // world.push_event(Event::BlockEntity { - // pos, - // inner: BlockEntityEvent::Storage { - // storage, - // stack, - // }, - // }); - // } - // _ => {} - // } - // - // // Answer with a transaction packet that is accepted if the packet's stack is - // // the same as the server's slot stack. - // let accepted = slot_stack.to_non_empty() == packet.stack; - // self.send(OutPacket::WindowTransaction(proto::WindowTransactionPacket { - // window_id: packet.window_id, - // transaction_id: packet.transaction_id, - // accepted, - // })); - // - // if !accepted { - // warn!("from {}, incoherent item at {} in window {}", self.username, packet.slot, packet.window_id); - // } - // - // if cursor_stack != self.cursor_stack || !accepted { - // - // // Send the new cursor item. - // if cursor_stack.size == 0 { - // cursor_stack = ItemStack::EMPTY; - // } - // - // self.send(OutPacket::WindowSetItem(proto::WindowSetItemPacket { - // window_id: 0xFF, - // slot: -1, - // stack: cursor_stack.to_non_empty(), - // })); - // - // self.cursor_stack = cursor_stack; - // - // } - // - // } - - //// Handle a window close packet, it just forget the current window. - // fn handle_window_close(&mut self, world: &mut World, packet: proto::WindowClosePacket) { - // self.close_window(world, Some(packet.window_id), false); - // } - - // fn handle_animation(&mut self, _world: &mut World, _packet: proto::AnimationPacket) { - // // TODO: Send animation to other players. - // } - - //// Handle an entity interaction. - // fn handle_interact(&mut self, world: &mut World, packet: proto::InteractPacket) { - // - // if self.entity_id != packet.player_entity_id { - // warn!("from {}, incoherent interact entity: {}, expected: {}", self.username, packet.player_entity_id, self.entity_id); - // } - // - // let Some(Entity(target_base, _)) = world.get_entity_mut(packet.target_entity_id) else { - // warn!("from {}, incoherent interact entity target: {}", self.username, packet.target_entity_id); - // return; - // }; - // - // if self.pos.distance_squared(target_base.pos) >= 36.0 { - // warn!("from {}, incoherent interact entity distance", self.username); - // return; - // } - // - // let hand_stack = self.main_inv[self.hand_slot as usize]; - // - // if packet.left_click { - // - // // TODO: Critical damage if vel.y < 0 - // - // let damage = item::attack::get_base_damage(hand_stack.id); - // target_base.hurt.push(Hurt { - // damage, - // origin_id: Some(self.entity_id), - // }); - // - // } else { - // - // } - // - // } - - //// Handle an action packet from the player. - // fn handle_action(&mut self, world: &mut World, packet: proto::ActionPacket) { - // - // if self.entity_id != packet.entity_id { - // warn!("from {}, incoherent player entity: {}, expected: {}", self.username, packet.entity_id, self.entity_id); - // } - // - // // A player action is only relevant on human entities, ignore if the player is - // // bound to any other entity kind. - // let Some(Entity(_, BaseKind::Living(_, LivingKind::Human(human)))) = world.get_entity_mut(self.entity_id) else { - // return; - // }; - // - // match packet.state { - // 1 | 2 => { - // human.sneaking = packet.state == 1; - // world.push_event(Event::Entity { id: self.entity_id, inner: EntityEvent::Metadata }); - // } - // 3 => todo!("wake up..."), - // _ => warn!("from {}, invalid action state: {}", self.username, packet.state) - // } - // - // } - - //// Open the given window kind on client-side by sending appropriate packet. A new - //// window id is automatically associated to that window. - // fn open_window(&mut self, world: &mut World, kind: WindowKind) { - // - // // Close any already opened window. - // self.close_window(world, None, true); - // - // // NOTE: We should never get a window id of 0 because it is the player inventory. - // let window_id = (self.window_count % 100 + 1) as u8; - // self.window_count += 1; - // - // match kind { - // WindowKind::Player => panic!("cannot force open the player window"), - // WindowKind::CraftingTable { .. } => { - // self.send(OutPacket::WindowOpen(proto::WindowOpenPacket { - // window_id, - // inventory_type: 1, - // title: "Crafting".to_string(), - // slots_count: 9, - // })); - // } - // WindowKind::Chest { ref pos } => { - // - // self.send(OutPacket::WindowOpen(proto::WindowOpenPacket { - // window_id, - // inventory_type: 0, - // title: if pos.len() <= 1 { "Chest" } else { "Large Chest" }.to_string(), - // slots_count: (pos.len() * 27) as u8, // TODO: Checked cast - // })); - // - // let mut stacks = Vec::new(); - // - // for &pos in pos { - // if let Some(BlockEntity::Chest(chest)) = world.get_block_entity(pos) { - // stacks.extend(chest.inv.iter().map(|stack| stack.to_non_empty())); - // } else { - // stacks.extend(std::iter::repeat(None).take(27)); - // } - // } - // - // self.send(OutPacket::WindowItems(proto::WindowItemsPacket { - // window_id, - // stacks, - // })); - // - // } - // WindowKind::Furnace { pos } => { - // - // self.send(OutPacket::WindowOpen(proto::WindowOpenPacket { - // window_id, - // inventory_type: 2, - // title: format!("Furnace"), - // slots_count: 3, - // })); - // - // if let Some(BlockEntity::Furnace(furnace)) = world.get_block_entity(pos) { - // - // self.send(OutPacket::WindowProgressBar(proto::WindowProgressBarPacket { - // window_id, - // bar_id: 0, - // value: furnace.smelt_ticks as i16, - // })); - // - // self.send(OutPacket::WindowProgressBar(proto::WindowProgressBarPacket { - // window_id, - // bar_id: 1, - // value: furnace.burn_remaining_ticks as i16, - // })); - // - // self.send(OutPacket::WindowProgressBar(proto::WindowProgressBarPacket { - // window_id, - // bar_id: 2, - // value: furnace.burn_max_ticks as i16, - // })); - // - // self.send(OutPacket::WindowItems(proto::WindowItemsPacket { - // window_id, - // stacks: vec![ - // furnace.input_stack.to_non_empty(), - // furnace.fuel_stack.to_non_empty(), - // furnace.output_stack.to_non_empty() - // ], - // })); - // - // } - // - // } - // WindowKind::Dispenser { pos } => { - // - // self.send(OutPacket::WindowOpen(proto::WindowOpenPacket { - // window_id, - // inventory_type: 3, - // title: format!("Dispenser"), - // slots_count: 9, - // })); - // - // if let Some(BlockEntity::Dispenser(dispenser)) = world.get_block_entity(pos) { - // self.send(OutPacket::WindowItems(proto::WindowItemsPacket { - // window_id, - // stacks: dispenser.inv.iter().map(|stack| stack.to_non_empty()).collect() - // })); - // } - // - // } - // }; - // - // self.window.id = window_id; - // self.window.kind = kind; - // - // } - - //// Close the current window opened by the player. If the window id argument is - //// provided, then this will only work if the current server-side window is matching. - //// The send boolean indicates if a window close packet must also be sent. - // fn close_window(&mut self, world: &mut World, window_id: Option, send: bool) { - // - // if let Some(window_id) = window_id { - // if self.window.id != window_id { - // return; - // } - // } - // - // // For any closed inventory, we drop the cursor stack and crafting matrix. - // let mut drop_stacks = Vec::new(); - // drop_stacks.extend(self.cursor_stack.take_non_empty()); - // for stack in self.craft_inv.iter_mut() { - // drop_stacks.extend(stack.take_non_empty()); - // } - // - // for drop_stack in drop_stacks { - // self.drop_stack(world, drop_stack, false); - // } - // - // // Closing the player inventory so we clear the crafting matrix. - // if self.window.id == 0 { - // for slot in 1..=4 { - // self.send(OutPacket::WindowSetItem(proto::WindowSetItemPacket { - // window_id: 0, - // slot, - // stack: None, - // })); - // } - // } - // - // // Reset to the default window. - // self.window.id = 0; - // self.window.kind = WindowKind::Player; - // - // if send { - // self.send(OutPacket::WindowClose(proto::WindowClosePacket { - // window_id: self.window.id, - // })); - // } - // - // } - - //// Internal function to create a window slot handle specifically for a player main - //// inventory slot, the offset of the first player inventory slot is also given. - // fn make_player_window_slot_handle(&mut self, slot: i16, offset: i16) -> Option> { - // - // let index = match slot - offset { - // 0..=26 => slot - offset + 9, - // 27..=35 => slot - offset - 27, - // _ => return None, - // } as usize; - // - // Some(SlotHandle { - // kind: SlotKind::Standard { - // stack: &mut self.main_inv[index], - // access: SlotAccess::PickupDrop, - // max_size: 64, - // }, - // notify: SlotNotify::None - // }) - // - // } - - //// Internal function to create a window slot handle. This handle is temporary and - //// own two mutable reference to the player itself and the world, it can only work - //// on the given slot. - // fn make_window_slot_handle<'a>(&'a mut self, world: &'a mut World, slot: i16) -> Option> { - // - // // This avoid temporary cast issues afterward, even if we keep the signed type. - // if slot < 0 { - // return None; - // } - // - // Some(match self.window.kind { - // WindowKind::Player => { - // match slot { - // 0 => SlotHandle { - // kind: SlotKind::CraftingResult { - // craft_inv: &mut self.craft_inv, - // craft_tracker: &mut self.craft_tracker, - // }, - // notify: SlotNotify::Craft { - // mapping: Some(&[1, 2, -1, 3, 4, -1, -1, -1, -1]), - // modified: false, - // }, - // }, - // 1..=4 => SlotHandle { - // kind: SlotKind::Standard { - // stack: &mut self.craft_inv[match slot { - // 1 => 0, - // 2 => 1, - // 3 => 3, - // 4 => 4, - // _ => unreachable!() - // }], - // access: SlotAccess::PickupDrop, - // max_size: 64, - // }, - // notify: SlotNotify::Craft { - // mapping: None, - // modified: false, - // }, - // }, - // 5..=8 => SlotHandle { - // kind: SlotKind::Standard { - // stack: &mut self.armor_inv[slot as usize - 5], - // access: match slot { - // 5 => SlotAccess::ArmorHelmet, - // 6 => SlotAccess::ArmorChestplate, - // 7 => SlotAccess::ArmorLeggings, - // 8 => SlotAccess::ArmorBoots, - // _ => unreachable!(), - // }, max_size: 1, - // }, - // notify: SlotNotify::None, - // }, - // _ => self.make_player_window_slot_handle(slot, 9)? - // } - // } - // WindowKind::CraftingTable { .. } => { - // match slot { - // 0 => SlotHandle { - // kind: SlotKind::CraftingResult { - // craft_inv: &mut self.craft_inv, - // craft_tracker: &mut self.craft_tracker, - // }, - // notify: SlotNotify::Craft { - // mapping: Some(&[1, 2, 3, 4, 5, 6, 7, 8, 9]), - // modified: false, - // }, - // }, - // 1..=9 => SlotHandle { - // kind: SlotKind::Standard { - // stack: &mut self.craft_inv[slot as usize - 1], - // access: SlotAccess::PickupDrop, - // max_size: 64, - // }, - // notify: SlotNotify::Craft { - // mapping: None, - // modified: false, - // }, - // }, - // _ => self.make_player_window_slot_handle(slot, 10)? - // } - // } - // WindowKind::Chest { ref pos } => { - // - // if let Some(&pos) = pos.get(slot as usize / 27) { - // - // // Get the chest tile entity corresponding to the clicked slot, - // // if not found we just ignore. - // let Some(BlockEntity::Chest(chest)) = world.get_block_entity_mut(pos) else { - // return None - // }; - // - // let index = slot as usize % 27; - // - // SlotHandle { - // kind: SlotKind::Standard { - // stack: &mut chest.inv[index], - // access: SlotAccess::PickupDrop, - // max_size: 64, - // }, - // notify: SlotNotify::BlockEntityStorageEvent { - // pos, - // storage: BlockEntityStorage::Standard(index as u8), - // stack: None, - // }, - // } - // - // } else { - // self.make_player_window_slot_handle(slot, pos.len() as i16 * 27)? - // } - // - // } - // WindowKind::Furnace { pos } => { - // - // if slot <= 2 { - // - // let Some(BlockEntity::Furnace(furnace)) = world.get_block_entity_mut(pos) else { - // return None - // }; - // - // let (stack, access, storage) = match slot { - // 0 => (&mut furnace.input_stack, SlotAccess::PickupDrop, BlockEntityStorage::FurnaceInput), - // 1 => (&mut furnace.fuel_stack, SlotAccess::PickupDrop, BlockEntityStorage::FurnaceFuel), - // 2 => (&mut furnace.output_stack, SlotAccess::Pickup(1), BlockEntityStorage::FurnaceOutput), - // _ => unreachable!() - // }; - // - // SlotHandle { - // kind: SlotKind::Standard { - // stack, - // access, - // max_size: 64, - // }, - // notify: SlotNotify::BlockEntityStorageEvent { - // pos, - // storage, - // stack: None, - // }, - // } - // - // } else { - // self.make_player_window_slot_handle(slot, 3)? - // } - // - // } - // WindowKind::Dispenser { pos } => { - // - // if slot < 9 { - // - // let Some(BlockEntity::Dispenser(dispenser)) = world.get_block_entity_mut(pos) else { - // return None - // }; - // - // SlotHandle { - // kind: SlotKind::Standard { - // stack: &mut dispenser.inv[slot as usize], - // access: SlotAccess::PickupDrop, - // max_size: 64, - // }, - // notify: SlotNotify::BlockEntityStorageEvent { - // pos, - // storage: BlockEntityStorage::Standard(slot as u8), - // stack: None, - // }, - // } - // - // } else { - // self.make_player_window_slot_handle(slot, 9)? - // } - // - // } - // }) - // - // } - - //// Send the main inventory item at given index to the client. - // fn send_main_inv_item(&self, index: usize) { - // - // let slot = match index { - // 0..=8 => 36 + index, - // _ => index, - // }; - // - // self.send(OutPacket::WindowSetItem(proto::WindowSetItemPacket { - // window_id: 0, - // slot: slot as i16, - // stack: self.main_inv[index].to_non_empty(), - // })); - // - // } - - //// Drop an item from the player's entity, items are drop in front of the player, but - //// the `on_ground` argument can be set to true in order to drop item on the ground. - // pub fn drop_stack(&mut self, world: &mut World, stack: ItemStack, on_ground: bool) { - // - // let Entity(origin_base, _) = world.get_entity_mut(self.entity_id).expect("incoherent player entity"); - // - // let entity = e::Item::new_with(|base, item| { - // - // base.persistent = true; - // base.pos = origin_base.pos; - // base.pos.y += 1.3; // TODO: Adjust depending on eye height. - // - // if on_ground { - // - // let rand_drop_speed = origin_base.rand.next_float() * 0.5; - // let rand_yaw = origin_base.rand.next_float() * std::f32::consts::TAU; - // - // base.vel.x = (rand_yaw.sin() * rand_drop_speed) as f64; - // base.vel.z = (rand_yaw.cos() * rand_drop_speed) as f64; - // base.vel.y = 0.2; - // - // } else { - // - // let drop_speed = 0.3; - // let rand_yaw = base.rand.next_float() * std::f32::consts::TAU; - // let rand_drop_speed = base.rand.next_float() * 0.02; - // let rand_vel_y = (base.rand.next_float() - base.rand.next_float()) * 0.1; - // - // base.vel.x = (-origin_base.look.x.sin() * origin_base.look.y.cos() * drop_speed) as f64; - // base.vel.z = (origin_base.look.x.cos() * origin_base.look.y.cos() * drop_speed) as f64; - // base.vel.y = (-origin_base.look.y.sin() * drop_speed + 0.1) as f64; - // base.vel.x += (rand_yaw.cos() * rand_drop_speed) as f64; - // base.vel.z += (rand_yaw.sin() * rand_drop_speed) as f64; - // base.vel.y += rand_vel_y as f64; - // - // } - // - // item.frozen_time = 40; - // item.stack = stack; - // - // }); - // - // world.spawn_entity(entity); - // - // } - /// Update the chunks sent to this player. pub fn update_chunks(player_id: u32) { - let player_entity = StdbEntity::filter_by_entity_id(&player_id).unwrap(); let (ocx, ocz) = chunk::calc_entity_chunk_pos(player_entity.pos.as_dvec3()); let view_range = 2; for cx in (ocx - view_range)..(ocx + view_range) { for cz in (ocz - view_range)..(ocz + view_range) { - let chunk_id = StdbChunk::xz_to_chunk_id(cx, cz); let chunk = StdbChunk::filter_by_chunk_id(&chunk_id).unwrap_or_else(|| { generate_chunk(cx, cz); @@ -1498,282 +262,5 @@ impl StdbServerPlayer { } } - } - - //// Make this player pickup an item stack, the stack and its size is modified - //// regarding the amount actually picked up. - // pub fn pickup_stack(&mut self, stack: &mut ItemStack) { - // - // let mut inv = InventoryHandle::new(&mut self.main_inv[..]); - // inv.push_front(stack); - // - // // Update the associated slots in the player inventory. - // for index in inv.iter_changes() { - // self.send_main_inv_item(index); - // } - // - // } - - //// For the given block position, close any window that may be linked to it. This is - //// usually called when the block entity or crafting table is removed. - // pub fn close_block_window(&mut self, world: &mut World, target_pos: IVec3) { - // - // let contains = match self.window.kind { - // WindowKind::Player => false, - // WindowKind::Furnace { pos } | - // WindowKind::Dispenser { pos } | - // WindowKind::CraftingTable { pos } => - // pos == target_pos, - // WindowKind::Chest { ref pos } => - // pos.iter().any(|&pos| pos == target_pos), - // }; - // - // if contains { - // self.close_window(world, None, true); - // } - // - // } - - //// If this player has a window opened for the given position, this will update the - //// displayed storage according to the given storage event. - // pub fn update_block_window_storage(&mut self, target_pos: IVec3, storage: BlockEntityStorage, stack: ItemStack) { - // - // match self.window.kind { - // WindowKind::Chest { ref pos } => { - // if let Some(row) = pos.iter().position(|&pos| pos == target_pos) { - // - // if let BlockEntityStorage::Standard(index) = storage { - // self.send(OutPacket::WindowSetItem(proto::WindowSetItemPacket { - // window_id: self.window.id, - // slot: row as i16 * 27 + index as i16, - // stack: stack.to_non_empty(), - // })); - // } - // - // } - // } - // WindowKind::Furnace { pos } => { - // if pos == target_pos { - // - // let slot = match storage { - // BlockEntityStorage::FurnaceInput => 0, - // BlockEntityStorage::FurnaceFuel => 1, - // BlockEntityStorage::FurnaceOutput => 2, - // _ => return, - // }; - // - // self.send(OutPacket::WindowSetItem(proto::WindowSetItemPacket { - // window_id: self.window.id, - // slot, - // stack: stack.to_non_empty(), - // })); - // - // } - // } - // WindowKind::Dispenser { pos } => { - // if pos == target_pos { - // if let BlockEntityStorage::Standard(index) = storage { - // - // self.send(OutPacket::WindowSetItem(proto::WindowSetItemPacket { - // window_id: self.window.id, - // slot: index as i16, - // stack: stack.to_non_empty(), - // })); - // - // } - // } - // } - // _ => {} // Not handled. - // } - // } - - //// If this player has a window opened for the given position, this will update the - //// displayed storage according to the given storage event. - // pub fn update_block_window_progress(&mut self, target_pos: IVec3, progress: BlockEntityProgress, value: u16) { - // - // if let WindowKind::Furnace { pos } = self.window.kind { - // if pos == target_pos { - // - // let bar_id = match progress { - // BlockEntityProgress::FurnaceSmeltTime => 0, - // BlockEntityProgress::FurnaceBurnRemainingTime => 1, - // BlockEntityProgress::FurnaceBurnMaxTime => 2, - // }; - // - // self.send(OutPacket::WindowProgressBar(proto::WindowProgressBarPacket { - // window_id: self.window.id, - // bar_id, - // value: value as i16, - // })); - // - // } - // } - // - // } - } - -//// A pointer to a slot in an inventory. -// struct SlotHandle<'a> { -// /// True if the client is able to drop item into this stack, if not then it can only -// /// pickup the item stack. -// kind: SlotKind<'a>, -// notify: SlotNotify, -// } - -//// Represent a major slot kind. -// enum SlotKind<'a> { -// /// A standard slot referencing a single item stack. -// Standard { -// /// The stack referenced by this slot handle. -// stack: &'a mut ItemStack, -// /// The access kind to this slot. -// access: SlotAccess, -// /// The maximum stack size this slot can accept. -// max_size: u16, -// }, -// /// The slot represent a crafting result. -// CraftingResult { -// /// The crafting grid item stacks. -// craft_inv: &'a mut [ItemStack; 9], -// /// The crafting tracker for the player. -// craft_tracker: &'a mut CraftTracker, -// }, -// } - -//// Represent the kind of drop rule to apply to this slot. -// #[derive(Clone, Copy)] -// enum SlotAccess { -// /// The cursor is able to pickup and drop items into this slot. -// PickupDrop, -// /// The cursor isn't able to drop items into this slot, it can only pickup. The field -// /// gives the minimum number of items that can be picked up at the same time. -// /// Typically used for crafting because only a full recipe result can be picked up. -// Pickup(u16), -// /// This slot only accepts helmet armor items. -// ArmorHelmet, -// /// This slot only accepts chestplate armor items. -// ArmorChestplate, -// /// This slot only accepts leggings armor items. -// ArmorLeggings, -// /// This slot only accepts boots armor items. -// ArmorBoots, -// } - -//// Type of notification that will be triggered when the slot gets modified. -// enum SlotNotify { -// /// The modification of the slot has no effect. -// None, -// /// The modification of the slot requires the crafting matrix to be resent. -// /// This should only be used for craft matrix windows, where the craft result is in -// /// slot 0. -// Craft { -// /// For each craft inventory stack a client slot number. If not present, this -// /// means that the crafting matrix should not be updated. If the slot should not -// /// be sent to the client, then the value must be negative. -// mapping: Option<&'static [i16; 9]>, -// /// True if the craft result should be updated from matrix and resent. -// modified: bool, -// }, -// /// A block entity storage event need to be pushed to the world. -// BlockEntityStorageEvent { -// /// The position of the block entity. -// pos: IVec3, -// /// The index of the inventory stack that is modified. -// storage: BlockEntityStorage, -// /// If the stack is actually modified, this is the new item stack at the index. -// stack: Option, -// } -// } - -// impl<'a> SlotHandle<'a> { -// -// /// Get the maximum stack size for that slot. -// fn max_stack_size(&self) -> u16 { -// match self.kind { -// SlotKind::Standard { max_size, .. } => max_size, -// SlotKind::CraftingResult { .. } => 64, -// } -// } -// -// /// Get the access rule to this slot. -// fn get_access(&self) -> SlotAccess { -// match self.kind { -// SlotKind::Standard { access, .. } => access, -// SlotKind::CraftingResult { ref craft_tracker, .. } => -// SlotAccess::Pickup(craft_tracker.recipe().map(|stack| stack.size).unwrap_or(0)), -// } -// } -// -// /// Get the stack in this slot. -// fn get_stack(&mut self) -> ItemStack { -// match &self.kind { -// SlotKind::Standard { stack, .. } => **stack, -// SlotKind::CraftingResult { craft_tracker, .. } => -// craft_tracker.recipe().unwrap_or_default() -// } -// } -// -// /// Set the stack in this slot, called if `is_valid` previously returned `true`, if -// /// the latter return `false`, this function can only be called with `EMPTY` stack. -// /// -// /// This function also push the slot changes that happened into `slot_changes` of the -// /// server player temporary vector. -// fn set_stack(&mut self, new_stack: ItemStack) { -// -// match &mut self.kind { -// SlotKind::Standard { stack, .. } => { -// **stack = new_stack; -// } -// SlotKind::CraftingResult { -// craft_inv, -// craft_tracker, -// } => { -// craft_tracker.consume(*craft_inv); -// } -// } -// -// match &mut self.notify { -// SlotNotify::None => {} -// SlotNotify::Craft { modified, .. } => *modified = true, -// SlotNotify::BlockEntityStorageEvent { stack, .. } => *stack = Some(new_stack), -// } -// -// } -// -// } - -// impl SlotAccess { -// -// fn can_drop(self, stack: ItemStack) -> bool { -// match self { -// SlotAccess::PickupDrop => true, -// SlotAccess::Pickup(_) => false, -// SlotAccess::ArmorHelmet => matches!(stack.id, -// item::LEATHER_HELMET | -// item::GOLD_HELMET | -// item::CHAIN_HELMET | -// item::IRON_HELMET | -// item::DIAMOND_HELMET) || stack.id == block::PUMPKIN as u16, -// SlotAccess::ArmorChestplate => matches!(stack.id, -// item::LEATHER_CHESTPLATE | -// item::GOLD_CHESTPLATE | -// item::CHAIN_CHESTPLATE | -// item::IRON_CHESTPLATE | -// item::DIAMOND_CHESTPLATE), -// SlotAccess::ArmorLeggings => matches!(stack.id, -// item::LEATHER_LEGGINGS | -// item::GOLD_LEGGINGS | -// item::CHAIN_LEGGINGS | -// item::IRON_LEGGINGS | -// item::DIAMOND_LEGGINGS), -// SlotAccess::ArmorBoots => matches!(stack.id, -// item::LEATHER_BOOTS | -// item::GOLD_BOOTS | -// item::CHAIN_BOOTS | -// item::IRON_BOOTS | -// item::DIAMOND_BOOTS), -// } -// } -// } diff --git a/crates/module/src/proto.rs b/crates/module/src/proto.rs index 56681e5..2bdc632 100644 --- a/crates/module/src/proto.rs +++ b/crates/module/src/proto.rs @@ -42,7 +42,7 @@ pub enum InPacket { /// The client's player break a block. BreakBlock(BreakBlockPacket), /// The client's player place a block. - PlaceBlock(PlaceBlockPacket), + PlaceBlock(StdbPlaceBlockPacket), /// The client's player change its hand item. HandSlot(HandSlotPacket), /// The client's player has an animation, vanilla client usually only send swing arm. @@ -294,8 +294,8 @@ pub struct BreakBlockPacket { } /// Packet 15 -#[derive(Debug, Clone)] -pub struct PlaceBlockPacket { +#[derive(Debug, Clone, SpacetimeType)] +pub struct StdbPlaceBlockPacket { pub x: i32, pub y: i8, pub z: i32, @@ -304,7 +304,7 @@ pub struct PlaceBlockPacket { } /// Packet 16 -#[derive(Debug, Clone)] +#[derive(Debug, Clone, SpacetimeType)] pub struct HandSlotPacket { pub slot: i16, } diff --git a/crates/module/src/world.rs b/crates/module/src/world.rs index 361c391..61e8b7c 100644 --- a/crates/module/src/world.rs +++ b/crates/module/src/world.rs @@ -1,6 +1,7 @@ //! Server world structure. use std::collections::HashMap; +use std::fmt::format; use std::time::Instant; use glam::{DVec3, IVec3, Vec2}; @@ -9,8 +10,10 @@ use mc173_module::block; use mc173_module::chunk::calc_chunk_pos; use mc173_module::chunk_cache::ChunkCache; use mc173_module::geom::Face; +use mc173_module::inventory::StdbInventory; use mc173_module::stdb::chunk::{ChunkUpdateType, StdbBlockSetUpdate, StdbChunk, StdbChunkUpdate}; use mc173_module::world::{LightKind, StdbWorld}; +use mc173_module::world::interact::Interaction; use crate::proto::{self, OutPacket}; use crate::config; use crate::entity::{StdbEntityTracker, StdbEntityView}; @@ -292,6 +295,269 @@ impl StdbServerWorld { // // } + /// Interact with a block at given position. This function returns the interaction + /// result to indicate if the interaction was handled, or if it was + /// + /// The second argument `breaking` indicates if the interaction originate from a + /// player breaking the block. + pub fn interact_block(pos: IVec3, breaking: bool) -> Interaction { + + if let Some((id, metadata)) = Self::get_block(pos) { + Self::interact_block_unchecked(pos, id, metadata, breaking) + } else { + Interaction::None + } + } + + /// Use an item stack on a given block, this is basically the action of left click. + /// This function returns the item stack after, if used, this may return an item stack + /// with size of 0. The face is where the click has hit on the target block. + pub fn use_stack(&mut self, inventory_id: u32, index: u32, pos: IVec3, face: Face, entity_id: u32, cache: &mut ChunkCache) { + + let stack = StdbInventory::get(inventory_id, index); + if stack.is_empty() { + return; + } + + let success = match stack.id { + 0 => false, + 1..=255 => Self::use_block_stack(stack.id as u8, stack.damage as u8, pos, face, entity_id, cache), + // item::SUGAR_CANES => self.use_block_stack(block::SUGAR_CANES, 0, pos, face, entity_id), + // item::CAKE => self.use_block_stack(block::CAKE, 0, pos, face, entity_id), + // item::REPEATER => self.use_block_stack(block::REPEATER, 0, pos, face, entity_id), + // item::REDSTONE => self.use_block_stack(block::REDSTONE, 0, pos, face, entity_id), + // item::WOOD_DOOR => self.use_door_stack(block::WOOD_DOOR, pos, face, entity_id), + // item::IRON_DOOR => self.use_door_stack(block::IRON_DOOR, pos, face, entity_id), + // item::BED => self.use_bed_stack(pos, face, entity_id), + // item::SIGN => self.use_sign_stack(pos, face, entity_id), + // item::DIAMOND_HOE | + // item::IRON_HOE | + // item::STONE_HOE | + // item::GOLD_HOE | + // item::WOOD_HOE => self.use_hoe_stack(pos, face), + // item::WHEAT_SEEDS => self.use_wheat_seeds_stack(pos, face), + // item::DYE if stack.damage == 15 => self.use_bone_meal_stack(pos), + // item::FLINT_AND_STEEL => self.use_flint_and_steel(pos, face), + // item::PAINTING => self.use_painting(pos, face), + _ => false + }; + + if success { + StdbInventory::set(inventory_id, index, stack.inc_damage(1)); + } + + } + + /// Place a block toward the given face. This is used for single blocks, multi blocks + /// are handled apart by other functions that do not rely on the block placing logic. + fn use_block_stack(id: u8, metadata: u8, mut pos: IVec3, mut face: Face, entity_id: u32, cache: &mut ChunkCache) -> bool { + let look = StdbEntity::filter_by_entity_id(&entity_id).expect( + format!("Player has no entity: {}", entity_id).as_str()); + // let look = self.get_entity(entity_id).unwrap().0.look; + + if let Some((block::SNOW, _)) = Self::get_block(pos) { + // If a block is placed by clicking on a snow block, replace that snow block. + face = Face::NegY; + } else { + // Get position of the block facing the clicked face. + pos += face.delta(); + // The block is oriented toward that clicked face. + face = face.opposite(); + } + + // Some block have special facing when placed. + match id { + // TODO(jdetter): come back to these when we have entities properly implemented + // block::WOOD_STAIR | block::COBBLESTONE_STAIR | + // block::REPEATER | block::REPEATER_LIT => { + // face = Face::from_yaw(look.x); + // } + // block::DISPENSER | + // block::FURNACE | block::FURNACE_LIT | + // block::PUMPKIN | block::PUMPKIN_LIT => { + // face = Face::from_yaw(look.x).opposite(); + // } + // block::PISTON | + // block::STICKY_PISTON => { + // face = Face::from_look(look.x, look.y).opposite(); + // } + _ => {} + } + + if pos.y >= 127 && block::material::get_material(id).is_solid() { + return false; + } + + if !Self::can_place_block(pos, face, id) { + return false; + } + + Self::place_block(pos, face, id, metadata, cache); + true + } + + /// Place the block at the given position in the world oriented toward given face. Note + /// that this function do not check if this is legal, it will do what's asked. Also, the + /// given metadata may be modified to account for the placement. + pub fn place_block(pos: IVec3, face: Face, id: u8, metadata: u8, cache: &mut ChunkCache) { + + match id { + // TODO(jdetter): Implement these when we support entities + // block::BUTTON => self.place_faced(pos, face, id, metadata, block::button::set_face), + // block::TRAPDOOR => self.place_faced(pos, face, id, metadata, block::trapdoor::set_face), + // block::PISTON | + // block::STICKY_PISTON => self.place_faced(pos, face, id, metadata, block::piston::set_face), + // block::WOOD_STAIR | + // block::COBBLESTONE_STAIR => self.place_faced(pos, face, id, metadata, block::stair::set_face), + // block::REPEATER | + // block::REPEATER_LIT => self.place_faced(pos, face, id, metadata, block::repeater::set_face), + // block::PUMPKIN | + // block::PUMPKIN_LIT => self.place_faced(pos, face, id, metadata, block::pumpkin::set_face), + // block::FURNACE | + // block::FURNACE_LIT | + // block::DISPENSER => self.place_faced(pos, face, id, metadata, block::dispenser::set_face), + // block::TORCH | + // block::REDSTONE_TORCH | + // block::REDSTONE_TORCH_LIT => self.place_faced(pos, face, id, metadata, block::torch::set_face), + // block::LEVER => self.place_lever(pos, face, metadata), + // block::LADDER => self.place_ladder(pos, face, metadata), + _ => { + Self::set_block_notify(pos, id, metadata, cache); + } + } + + // match id { + // block::CHEST => self.set_block_entity(pos, BlockEntity::Chest(def())), + // block::FURNACE => self.set_block_entity(pos, BlockEntity::Furnace(def())), + // block::DISPENSER => self.set_block_entity(pos, BlockEntity::Dispenser(def())), + // block::SPAWNER => self.set_block_entity(pos, BlockEntity::Spawner(def())), + // block::NOTE_BLOCK => self.set_block_entity(pos, BlockEntity::NoteBlock(def())), + // block::JUKEBOX => self.set_block_entity(pos, BlockEntity::Jukebox(def())), + // _ => {} + // } + } + + /// This function checks if the given block id can be placed at a particular position in + /// the world, the given face indicates toward which face this block should be oriented. + pub fn can_place_block(pos: IVec3, face: Face, id: u8) -> bool { + + let base = match id { + // TODO(jdetter): Come back to these after we have entities properly implemented + // block::BUTTON if face.is_y() => false, + // block::BUTTON => self.is_block_opaque_cube(pos + face.delta()), + // block::LEVER if face == Face::PosY => false, + // block::LEVER => self.is_block_opaque_cube(pos + face.delta()), + // block::LADDER => self.is_block_opaque_around(pos), + // block::TRAPDOOR if face.is_y() => false, + // block::TRAPDOOR => self.is_block_opaque_cube(pos + face.delta()), + // block::PISTON_EXT | + // block::PISTON_MOVING => false, + // block::DEAD_BUSH => matches!(self.get_block(pos - IVec3::Y), Some((block::SAND, _))), + // // PARITY: Notchian impl checks block light >= 8 or see sky + // block::DANDELION | + // block::POPPY | + // block::SAPLING | + // block::TALL_GRASS => matches!(self.get_block(pos - IVec3::Y), Some((block::GRASS | block::DIRT | block::FARMLAND, _))), + // block::WHEAT => matches!(self.get_block(pos - IVec3::Y), Some((block::FARMLAND, _))), + // block::CACTUS => self.can_place_cactus(pos), + // block::SUGAR_CANES => self.can_place_sugar_canes(pos), + // block::CAKE => self.is_block_solid(pos - IVec3::Y), + // block::CHEST => self.can_place_chest(pos), + // block::WOOD_DOOR | + // block::IRON_DOOR => self.can_place_door(pos), + // block::FENCE => matches!(self.get_block(pos - IVec3::Y), Some((block::FENCE, _))) || self.is_block_solid(pos - IVec3::Y), + // block::FIRE => self.can_place_fire(pos), + // block::TORCH | + // block::REDSTONE_TORCH | + // block::REDSTONE_TORCH_LIT => self.is_block_normal_cube(pos + face.delta()), + // // Common blocks that needs opaque block below. + // block::RED_MUSHROOM | // PARITY: Notchian impl checks block light >= 8 or see sky + // block::BROWN_MUSHROOM => self.is_block_opaque_cube(pos - IVec3::Y), + // block::SNOW => self.is_block_opaque_cube(pos - IVec3::Y), + // block::WOOD_PRESSURE_PLATE | + // block::STONE_PRESSURE_PLATE | + // block::PUMPKIN | + // block::PUMPKIN_LIT | + // block::RAIL | + // block::POWERED_RAIL | + // block::DETECTOR_RAIL | + // block::REPEATER | + // block::REPEATER_LIT | + // block::REDSTONE => self.is_block_normal_cube(pos - IVec3::Y), + _ => true, + }; + + // If the block we are placing has an exclusion box and any hard entity is inside, + // we cancel the prevent the placing. + // TODO(jdetter): Add this back in when we add support for collision boxes in stdb + // if let Some(bb) = self.get_block_exclusion_box(pos, id) { + // if self.has_entity_colliding(bb, true) { + // return false; + // } + // } + + base && Self::is_block_replaceable(pos) + + } + + /// Return true if the block at given position can be replaced. + pub fn is_block_replaceable(pos: IVec3) -> bool { + if let Some((id, _)) = Self::get_block(pos) { + block::material::get_material(id).is_replaceable() + } else { + false + } + } + + /// Internal function to handle block interaction at given position and with known + /// block and metadata. + pub(super) fn interact_block_unchecked(pos: IVec3, id: u8, metadata: u8, breaking: bool) -> Interaction { + match id { + // block::BUTTON => self.interact_button(pos, metadata), + // block::LEVER => self.interact_lever(pos, metadata), + // block::TRAPDOOR => self.interact_trapdoor(pos, metadata), + // block::IRON_DOOR => true, + // block::WOOD_DOOR => self.interact_wood_door(pos, metadata), + // block::REPEATER | + // block::REPEATER_LIT => self.interact_repeater(pos, id, metadata), + // block::REDSTONE_ORE => self.interact_redstone_ore(pos), + // block::CRAFTING_TABLE => return Interaction::CraftingTable { pos }, + // block::CHEST => return self.interact_chest(pos), + // block::FURNACE | + // block::FURNACE_LIT => return self.interact_furnace(pos), + // block::DISPENSER => return self.interact_dispenser(pos), + // block::NOTE_BLOCK => self.interact_note_block(pos, breaking), + block::BUTTON => Interaction::Handled, + block::LEVER => Interaction::Handled, + block::TRAPDOOR => Interaction::Handled, + block::IRON_DOOR => Interaction::Handled, + block::WOOD_DOOR => Interaction::Handled, + block::REPEATER | + block::REPEATER_LIT => Interaction::Handled, + block::REDSTONE_ORE => Interaction::Handled, + block::CRAFTING_TABLE => Interaction::Handled, + block::CHEST => Interaction::Handled, + block::FURNACE | + block::FURNACE_LIT => Interaction::Handled, + block::DISPENSER => Interaction::Handled, + block::NOTE_BLOCK => Interaction::Handled, + _ => return Interaction::None + }.into() + } + + /// Get block and metadata at given position in the world, if the chunk is not + /// loaded, none is returned. + pub fn get_block(pos: IVec3) -> Option<(u8, u8)> { + let (cx, cz) = calc_chunk_pos(pos)?; + let chunk = Self::get_chunk(cx, cz)?; + Some(chunk.chunk.get_block(pos)) + } + + pub fn get_chunk(cx: i32, cz: i32) -> Option { + let chunk_id = StdbChunk::xz_to_chunk_id(cx, cz); + StdbChunk::filter_by_chunk_id(&chunk_id) + } + /// Handle a player joining this world. pub fn handle_player_join(&mut self, player: StdbServerPlayer) { @@ -604,8 +870,8 @@ impl StdbServerWorld { /// are notified of that neighbor change. /// /// [`set_block_self_notify`]: Self::set_block_self_notify - pub fn set_block_notify(&mut self, pos: IVec3, id: u8, metadata: u8, cache: &mut ChunkCache) -> Option<(u8, u8)> { - let (prev_id, prev_metadata) = self.set_block_self_notify(pos, id, metadata, cache)?; + pub fn set_block_notify(pos: IVec3, id: u8, metadata: u8, cache: &mut ChunkCache) -> Option<(u8, u8)> { + let (prev_id, prev_metadata) = Self::set_block_self_notify(pos, id, metadata, cache)?; // self.notify_blocks_around(pos, id, cache); Some((prev_id, prev_metadata)) } @@ -614,8 +880,8 @@ impl StdbServerWorld { /// notified of that removal and addition. /// /// [`set_block`]: Self::set_block - pub fn set_block_self_notify(&mut self, pos: IVec3, id: u8, metadata: u8, cache: &mut ChunkCache) -> Option<(u8, u8)> { - let (prev_id, prev_metadata) = self.set_block(pos, id, metadata, cache)?; + pub fn set_block_self_notify(pos: IVec3, id: u8, metadata: u8, cache: &mut ChunkCache) -> Option<(u8, u8)> { + let (prev_id, prev_metadata) = Self::set_block(pos, id, metadata, cache)?; // self.notify_change_unchecked(pos, prev_id, prev_metadata, id, metadata); Some((prev_id, prev_metadata)) } @@ -624,11 +890,11 @@ impl StdbServerWorld { /// loaded, none is returned, but if it is existing the previous block and metadata /// is returned. This function also push a block change event and update lights /// accordingly. - pub fn set_block(&mut self, pos: IVec3, id: u8, metadata: u8, cache: &mut ChunkCache) -> Option<(u8, u8)> { + pub fn set_block(pos: IVec3, id: u8, metadata: u8, cache: &mut ChunkCache) -> Option<(u8, u8)> { let (cx, cz) = calc_chunk_pos(pos)?; let mut chunk = cache.get_chunk(cx, cz)?; - let result = self.set_block_inner(pos, id, metadata, &mut chunk); + let result = Self::set_block_inner(pos, id, metadata, &mut chunk); if result.0 { let id = chunk.chunk_id; @@ -639,7 +905,7 @@ impl StdbServerWorld { Some((result.1, result.2)) } - pub fn set_block_inner(&mut self, pos: IVec3, id: u8, metadata: u8, chunk: &mut StdbChunk) -> (bool, u8, u8) { + pub fn set_block_inner(pos: IVec3, id: u8, metadata: u8, chunk: &mut StdbChunk) -> (bool, u8, u8) { let (prev_id, prev_metadata) = chunk.chunk.get_block(pos); let mut changed = false;