mirror of
https://github.com/p2r3/bareiron.git
synced 2025-10-02 07:35:08 +02:00
implement chests
This commit is contained in:
@@ -374,7 +374,8 @@ async function convert () {
|
||||
itemsAndBlocks.blockRegistry["oak_planks"],
|
||||
itemsAndBlocks.blockRegistry["oak_wood"],
|
||||
itemsAndBlocks.blockRegistry["oak_slab"],
|
||||
itemsAndBlocks.blockRegistry["crafting_table"]
|
||||
itemsAndBlocks.blockRegistry["crafting_table"],
|
||||
itemsAndBlocks.blockRegistry["chest"]
|
||||
],
|
||||
"mineable/shovel": [
|
||||
itemsAndBlocks.blockRegistry["grass_block"],
|
||||
|
@@ -41,6 +41,11 @@
|
||||
#define SCALE_MOVEMENT_UPDATES_TO_PLAYER_COUNT
|
||||
// If defined, calculates fluid flow when blocks are updated near fluids
|
||||
#define DO_FLUID_FLOW
|
||||
// If defined, allows players to craft and use chests.
|
||||
// Chests take up 15 block change slots each, require additional checks,
|
||||
// and use some terrible memory hacks to function. On some platforms, this
|
||||
// could cause bad performance or even crashes during gameplay.
|
||||
#define ALLOW_CHESTS
|
||||
// If defined, enables flight for all players
|
||||
#define ENABLE_PLAYER_FLIGHT
|
||||
|
||||
|
@@ -43,4 +43,6 @@ void spawnMob (uint8_t type, short x, uint8_t y, short z, uint8_t health);
|
||||
void hurtEntity (int entity_id, int attacker_id, uint8_t damage_type, uint8_t damage);
|
||||
void handleServerTick (int64_t time_since_last_tick);
|
||||
|
||||
void broadcastChestUpdate (int origin_fd, uint8_t *storage_ptr, uint16_t item, uint8_t count, uint8_t slot);
|
||||
|
||||
#endif
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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);
|
||||
|
104
src/procedures.c
104
src/procedures.c
@@ -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
|
||||
|
@@ -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 &&
|
||||
|
Reference in New Issue
Block a user