Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions server/block/container_kind.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
module block

// container_default_slots is how many slots every container in this file
// holds. A double chest is two of them side by side rather than a container
// with its own size.
pub const container_default_slots = 27

// ContainerKind describes a block that stores items: how it is opened, where
// its contents live and whether two of them join into one bigger container.
pub struct ContainerKind {
pub:
identifier string
slots int = container_default_slots
// player_scoped moves the contents onto the player instead of the block,
// which is what makes an ender chest an ender chest.
player_scoped bool
}

const shulker_box_suffix = '_shulker_box'

// container_kind returns what kind of container a block identifier names, or
// none when the block stores nothing.
pub fn container_kind(identifier string) ?ContainerKind {
if identifier.ends_with(shulker_box_suffix) {
return ContainerKind{
identifier: identifier
}
}
return match identifier {
'minecraft:chest', 'minecraft:trapped_chest', 'minecraft:barrel' {
ContainerKind{
identifier: identifier
}
}
'minecraft:ender_chest' {
ContainerKind{
identifier: identifier
player_scoped: true
}
}
else {
none
}
}
}

// container_kind_of returns the kind for a block runtime id.
pub fn container_kind_of(block_id int) ?ContainerKind {
b := get(block_id) or { return none }
return container_kind(b.identifier())
}
43 changes: 43 additions & 0 deletions server/block/container_kind_test.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
module block

fn test_chests_and_barrels_store_in_the_world() {
for name in ['minecraft:chest', 'minecraft:trapped_chest', 'minecraft:barrel'] {
kind := container_kind(name) or {
assert false, '${name} is not a container'
continue
}
assert kind.slots == container_default_slots
assert !kind.player_scoped
}
}

fn test_an_ender_chest_stores_on_the_player() {
kind := container_kind('minecraft:ender_chest') or {
assert false, 'ender chest is not a container'
return
}
assert kind.player_scoped
assert kind.slots == container_default_slots
}

fn test_every_shulker_box_colour_is_a_container() {
for colour in shulker_colors {
if _ := container_kind('minecraft:${colour}_shulker_box') {
} else {
assert false, '${colour} shulker box is not a container'
}
}
if _ := container_kind('minecraft:undyed_shulker_box') {
} else {
assert false, 'undyed shulker box is not a container'
}
}

fn test_a_block_that_stores_nothing_is_not_a_container() {
if _ := container_kind('minecraft:stone') {
assert false, 'stone reported itself as a container'
}
if _ := container_kind('minecraft:crafting_table') {
assert false, 'a crafting table stores nothing'
}
}
78 changes: 78 additions & 0 deletions server/player/ender_chest.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
module player

import bedrock_v.protocol.types
import server.player.playerdb

// ender_slot_count is how many slots an ender chest shows. Every ender chest
// in the world is the same one for a given player, so its contents live here
// rather than at any of the blocks.
pub const ender_slot_count = 27

// ender_slots returns the whole ender chest in slot order, with an empty stack
// for every slot holding nothing.
pub fn (p &Player) ender_slots() []types.ItemStack {
mut m := p.state_mutex
m.lock()
defer {
m.unlock()
}
mut out := []types.ItemStack{len: ender_slot_count}
for slot, stack in p.ender_items {
if slot >= 0 && slot < ender_slot_count {
out[slot] = stack
}
}
return out
}

// set_ender_slot stores one slot, or empties it when the stack is nothing.
pub fn (mut p Player) set_ender_slot(slot int, stack types.ItemStack) {
if slot < 0 || slot >= ender_slot_count {
return
}
p.state_mutex.lock()
if stack.count > 0 && stack.id != 0 {
p.ender_items[slot] = stack
} else {
p.ender_items.delete(slot)
}
p.state_mutex.unlock()
}

// set_ender_items restores a saved ender chest.
pub fn (mut p Player) set_ender_items(items []playerdb.InvItem) {
p.state_mutex.lock()
p.ender_items = map[int]types.ItemStack{}
for item in items {
if item.slot < 0 || item.slot >= ender_slot_count || item.count <= 0 {
continue
}
p.ender_items[item.slot] = types.ItemStack{
id: item.id
meta: item.meta
count: item.count
block_runtime_id: item.block_runtime_id
raw_extra_data: item.raw_extra_data.clone()
}
}
p.state_mutex.unlock()
}

// ender_items_for_save is the ender chest in the shape the save file wants.
pub fn (p &Player) ender_items_for_save() []playerdb.InvItem {
mut out := []playerdb.InvItem{}
for slot, stack in p.ender_slots() {
if stack.count <= 0 || stack.id == 0 {
continue
}
out << playerdb.InvItem{
slot: slot
id: stack.id
meta: stack.meta
count: stack.count
block_runtime_id: stack.block_runtime_id
raw_extra_data: stack.raw_extra_data.clone()
}
}
return out
}
52 changes: 52 additions & 0 deletions server/player/ender_chest_test.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
module player

import bedrock_v.protocol.types

fn diamond_stack(count int) types.ItemStack {
return types.ItemStack{
id: 264
count: count
}
}

fn test_an_ender_chest_starts_empty() {
p := new_player()
slots := p.ender_slots()
assert slots.len == ender_slot_count
for stack in slots {
assert stack.count == 0
}
}

fn test_storing_and_clearing_a_slot() {
mut p := new_player()
p.set_ender_slot(3, diamond_stack(5))
assert p.ender_slots()[3].count == 5

p.set_ender_slot(3, types.ItemStack{})
assert p.ender_slots()[3].count == 0
}

fn test_slots_outside_the_chest_are_ignored() {
mut p := new_player()
p.set_ender_slot(-1, diamond_stack(1))
p.set_ender_slot(ender_slot_count, diamond_stack(1))
for stack in p.ender_slots() {
assert stack.count == 0
}
}

fn test_an_ender_chest_survives_a_save_and_load() {
mut p := new_player()
p.set_ender_slot(0, diamond_stack(2))
p.set_ender_slot(26, diamond_stack(7))
saved := p.ender_items_for_save()
assert saved.len == 2

mut restored := new_player()
restored.set_ender_items(saved)
slots := restored.ender_slots()
assert slots[0].count == 2
assert slots[26].count == 7
assert slots[1].count == 0
}
3 changes: 3 additions & 0 deletions server/player/player.v
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ mut:
pending_creative ?types.ItemStack
loaded_items []playerdb.InvItem
effects effect.Manager
// ender_items is the player's ender chest, keyed by slot. It belongs to
// them rather than to any of the blocks that show it.
ender_items map[int]types.ItemStack
has_last_death bool
last_death_pos types.Vector3
air_supply_ticks i64 = max_air_supply_ticks
Expand Down
1 change: 1 addition & 0 deletions server/player/playerdb/player.v
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub mut:
pitch f32
gamemode int
items []InvItem
ender_items []InvItem
has_last_death bool
last_death_x f32
last_death_y f32
Expand Down
6 changes: 4 additions & 2 deletions server/session/blocks.v
Original file line number Diff line number Diff line change
Expand Up @@ -476,8 +476,10 @@ fn complete_block_break(mut tx worldrt.WorldTx, mut s NetworkSession, pos types.
}

if b := block.get(old_id) {
if b is block.ChestBlock {
drop_chest_contents(mut tx, mut s, pos.x, pos.y, pos.z)
if kind := block.container_kind(b.identifier()) {
if !kind.player_scoped {
drop_container_contents(mut tx, mut s, pos.x, pos.y, pos.z)
}
}
if b is block.JukeboxBlock {
drop_jukebox_disc(mut tx, pos.x, pos.y, pos.z)
Expand Down
128 changes: 0 additions & 128 deletions server/session/chest_container.v

This file was deleted.

Loading
Loading