implement chests

This commit is contained in:
p2r3
2025-08-28 23:51:00 +03:00
parent 1ae481ed3d
commit ca6f8afbea
7 changed files with 173 additions and 20 deletions

View File

@@ -261,16 +261,14 @@ void getCraftingOutput (PlayerData *player, uint8_t *count, uint16_t *item) {
break;
case 8:
switch (first_item) {
case I_cobblestone:
if (identical && player->craft_items[first + 4] == 0) {
*item = I_furnace;
*count = 1;
return;
}
break;
default: break;
if (identical && player->craft_items[first + 4] == 0) {
switch (first_item) {
case I_cobblestone: *item = I_furnace; *count = 1; return;
#ifdef ALLOW_CHESTS
case I_oak_planks: *item = I_chest; *count = 1; return;
#endif
default: break;
}
}
break;

View File

@@ -392,10 +392,14 @@ int sc_chunkDataAndUpdateLight (int client_fd, int _x, int _z) {
// be overlayed here. This seems to be cheaper than sending actual
// block light data.
for (int i = 0; i < block_changes_count; i ++) {
if (block_changes[i].block != B_torch) continue;
#ifdef ALLOW_CHESTS
if (block_changes[i].block != B_torch && block_changes[i].block != B_chest) continue;
#else
if (block_changes[i].block != B_torch) continue;
#endif
if (block_changes[i].x < x || block_changes[i].x >= x + 16) continue;
if (block_changes[i].z < z || block_changes[i].z >= z + 16) continue;
sc_blockUpdate(client_fd, block_changes[i].x, block_changes[i].y, block_changes[i].z, B_torch);
sc_blockUpdate(client_fd, block_changes[i].x, block_changes[i].y, block_changes[i].z, block_changes[i].block);
}
return 0;
@@ -587,16 +591,43 @@ int cs_clickContainer (int client_fd) {
uint16_t item;
int tmp;
uint16_t *p_item;
uint8_t *p_count;
#ifdef ALLOW_CHESTS
// See the handlePlayerUseItem function for more info on this hack
uint8_t *storage_ptr;
memcpy(&storage_ptr, player->craft_items, sizeof(storage_ptr));
#endif
for (int i = 0; i < changes_count; i ++) {
slot = clientSlotToServerSlot(window_id, readUint16(client_fd));
// slots outside of the inventory overflow into the crafting buffer
if (slot > 40 && apply_changes) craft = true;
#ifdef ALLOW_CHESTS
if (window_id == 2 && slot > 40) {
// Get item pointers from the player's storage pointer
// See the handlePlayerUseItem function for more info on this hack
p_item = (uint16_t *)(storage_ptr + (slot - 41) * 3);
p_count = storage_ptr + (slot - 41) * 3 + 2;
} else
#endif
{
p_item = &player->inventory_items[slot];
p_count = &player->inventory_count[slot];
}
if (!readByte(client_fd)) { // no item?
if (slot != 255 && apply_changes) {
player->inventory_items[slot] = 0;
player->inventory_count[slot] = 0;
*p_item = 0;
*p_count = 0;
#ifdef ALLOW_CHESTS
if (window_id == 2 && slot > 40) {
broadcastChestUpdate(client_fd, storage_ptr, 0, 0, slot - 41);
}
#endif
}
continue;
}
@@ -611,8 +642,13 @@ int cs_clickContainer (int client_fd) {
recv_all(client_fd, recv_buffer, tmp, false);
if (count > 0 && apply_changes) {
player->inventory_items[slot] = item;
player->inventory_count[slot] = count;
*p_item = item;
*p_count = count;
#ifdef ALLOW_CHESTS
if (window_id == 2 && slot > 40) {
broadcastChestUpdate(client_fd, storage_ptr, item, count, slot - 41);
}
#endif
}
}
@@ -740,12 +776,15 @@ int cs_closeContainer (int client_fd) {
if (getPlayerData(client_fd, &player)) return 1;
// return all items in crafting slots to the player
// or, in the case of chests, simply clear the storage pointer
for (uint8_t i = 0; i < 9; i ++) {
givePlayerItem(player, player->craft_items[i], player->craft_count[i]);
if (window_id != 2) {
givePlayerItem(player, player->craft_items[i], player->craft_count[i]);
uint8_t client_slot = serverSlotToClientSlot(window_id, 41 + i);
if (client_slot != 255) sc_setContainerSlot(player->client_fd, window_id, client_slot, 0, 0);
}
player->craft_items[i] = 0;
player->craft_count[i] = 0;
uint8_t client_slot = serverSlotToClientSlot(window_id, 41 + i);
if (client_slot != 255) sc_setContainerSlot(player->client_fd, window_id, client_slot, 0, 0);
}
givePlayerItem(player, player->flagval_16, player->flagval_8);

View File

@@ -181,6 +181,17 @@ uint8_t clientSlotToServerSlot (int window_id, uint8_t slot) {
if (slot >= 3 && slot <= 38) return clientSlotToServerSlot(0, slot + 6);
}
#ifdef ALLOW_CHESTS
else if (window_id == 2) { // chest
// overflow chest slots into crafting grid
// technically invalid, expected to be handled on a per-case basis
if (slot >= 0 && slot <= 26) return 41 + slot;
// the rest of the slots are identical, just shifted by 18
if (slot >= 27 && slot <= 62) return clientSlotToServerSlot(0, slot - 18);
}
#endif
return 255;
}
@@ -297,6 +308,10 @@ uint8_t getBlockChange (short x, uint8_t y, short z) {
block_changes[i].y == y &&
block_changes[i].z == z
) return block_changes[i].block;
#ifdef ALLOW_CHESTS
// Skip chest contents
if (block_changes[i].block == B_chest) i += 14;
#endif
}
return 0xFF;
}
@@ -342,6 +357,12 @@ void makeBlockChange (short x, uint8_t y, short z, uint8_t block) {
block_changes[i].y == y &&
block_changes[i].z == z
) {
#ifdef ALLOW_CHESTS
// When replacing chests, clear following 14 entries too (item data)
if (block_changes[i].block == B_chest) {
for (int j = 1; j < 15; j ++) block_changes[i + j].block = 0xFF;
}
#endif
if (is_base_block) block_changes[i].block = 0xFF;
else block_changes[i].block = block;
return;
@@ -351,6 +372,41 @@ void makeBlockChange (short x, uint8_t y, short z, uint8_t block) {
// Don't create a new entry if it contains the base terrain block
if (is_base_block) return;
#ifdef ALLOW_CHESTS
if (block == B_chest) {
// Chests require 15 entries total, so for maximum space-efficiency,
// we have to find a continuous gap that's at least 15 slots wide.
// By design, this loop also continues past the current search range,
// which naturally appends the chest to the end if a gap isn't found.
int last_real_entry = first_gap - 1;
for (int i = first_gap; i <= block_changes_count + 15; i ++) {
if (block_changes[i].block != 0xFF) {
last_real_entry = i;
continue;
}
if (i - last_real_entry != 15) continue;
// A wide enough gap has been found, assign the chest
block_changes[last_real_entry + 1].x = x;
block_changes[last_real_entry + 1].y = y;
block_changes[last_real_entry + 1].z = z;
block_changes[last_real_entry + 1].block = block;
// Zero out the following 14 entries for item data
for (int i = 2; i <= 15; i ++) {
block_changes[last_real_entry + i].x = 0;
block_changes[last_real_entry + i].y = 0;
block_changes[last_real_entry + i].z = 0;
block_changes[last_real_entry + i].block = 0;
}
// Extend future search range if necessary
if (i >= block_changes_count) {
block_changes_count = i + 1;
}
break;
}
return;
}
#endif
// Fall back to storing the change at the first possible gap
block_changes[first_gap].x = x;
block_changes[first_gap].y = y;
@@ -841,6 +897,37 @@ void handlePlayerUseItem (PlayerData *player, short x, short y, short z, uint8_t
return;
}
}
#ifdef ALLOW_CHESTS
else if (target == B_chest) {
// Get a pointer to the entry following this chest in block_changes
uint8_t *storage_ptr;
for (int i = 0; i < block_changes_count; i ++) {
if (block_changes[i].block != B_chest) continue;
if (block_changes[i].x != x || block_changes[i].y != y || block_changes[i].z != z) continue;
storage_ptr = (uint8_t *)(&block_changes[i + 1]);
break;
}
// Terrible memory hack!!
// Copy the pointer into the player's crafting table item array.
// This allows us to save some memory by repurposing a feature that
// is mutually exclusive with chests, though it is otherwise a
// terrible idea for obvious reasons.
memcpy(player->craft_items, &storage_ptr, sizeof(storage_ptr));
// Show the player the chest UI
sc_openScreen(player->client_fd, 2, "Chest", 5);
// Load the slots of the chest from the block_changes array.
// This is a similarly dubious memcpy hack, but at least we're not
// mixing data types? Kind of?
for (int i = 0; i < 27; i ++) {
uint16_t item;
uint8_t count;
memcpy(&item, storage_ptr + i * 3, 2);
memcpy(&count, storage_ptr + i * 3 + 2, 1);
sc_setContainerSlot(player->client_fd, 2, i, count, item);
}
return;
}
#endif
}
// If the selected slot doesn't hold any items, exit
@@ -1214,3 +1301,20 @@ void handleServerTick (int64_t time_since_last_tick) {
}
}
#ifdef ALLOW_CHESTS
// Broadcasts a chest slot update to all clients who have that chest open,
// except for the client who initiated the update.
void broadcastChestUpdate (int origin_fd, uint8_t *storage_ptr, uint16_t item, uint8_t count, uint8_t slot) {
for (int i = 0; i < MAX_PLAYERS; i ++) {
if (player_data[i].client_fd == -1) continue;
if (player_data[i].flags & 0x20) continue;
// Filter for players that have this chest open
if (memcmp(player_data[i].craft_items, &storage_ptr, sizeof(storage_ptr)) != 0) continue;
// Send slot update packet
sc_setContainerSlot(player_data[i].client_fd, 2, slot, count, item);
}
}
#endif

View File

@@ -380,7 +380,11 @@ uint8_t buildChunkSection (int cx, int cy, int cz) {
// runs per block, as this is more expensive than terrain generation.
for (int i = 0; i < block_changes_count; i ++) {
if (block_changes[i].block == 0xFF) continue;
// Skip blocks that behave better when sent using a block update
if (block_changes[i].block == B_torch) continue;
#ifdef ALLOW_CHESTS
if (block_changes[i].block == B_chest) continue;
#endif
if ( // Check if block is within this chunk section
block_changes[i].x >= cx && block_changes[i].x < cx + 16 &&
block_changes[i].y >= cy && block_changes[i].y < cy + 16 &&