diff --git a/include/blocks.h b/include/blocks.h new file mode 100644 index 00000000..3901d9c9 --- /dev/null +++ b/include/blocks.h @@ -0,0 +1,33 @@ +#ifndef H_BLOCKS +#define H_BLOCKS + +#include +#include "globals.h" + +typedef struct { + short x; + short z; + uint8_t y; + uint8_t block; +} BlockChange; + +typedef struct { + int ri; // run index + int li; // run-local block index +} BlockRef; + +typedef struct { + BlockChange runs[MAX_BLOCK_CHANGES]; + short lens[MAX_BLOCK_CHANGES]; // 2b x-mode, 2b y-mode, 2b z-mode, 10b run length +} BlockBuf; + +BlockRef nextBlockChange (BlockRef curr); +BlockChange derefBlockChange (BlockRef ref); + +uint8_t getBlockChange (short x, uint8_t y, short z); +uint8_t makeBlockChange (short x, uint8_t y, short z, uint8_t block); + +extern BlockBuf block_changes; +extern int block_changes_count; + +#endif diff --git a/include/globals.h b/include/globals.h index 7d749cf6..c5bd240d 100644 --- a/include/globals.h +++ b/include/globals.h @@ -70,7 +70,9 @@ // Must be at least 1, otherwise chunks will be sent on each position update #define VISITED_HISTORY 4 -// How many player-made block changes to allow +// The minimum number of player-made block changes to allow +// Note that the number of changes will be higher than this due to opportunistic compression +// but in the worse case scenario this will be the limit // Determines the fixed amount of memory allocated to blocks #define MAX_BLOCK_CHANGES 20000 @@ -132,7 +134,11 @@ // 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 +// #define ALLOW_CHESTS + +#ifdef ALLOW_CHESTS + #error "Chests are not yet supported with RLE" +#endif // If defined, enables flight for all players. As a side-effect, allows // players to sprint when starving. @@ -162,6 +168,9 @@ // Doesn't implement authentication, hence disabled by default. // #define DEV_ENABLE_BEEF_DUMPS +// Log the block changes storage usage on every allocation +#define DEV_LOG_BLOCK_STORAGE_STATS + #define STATE_NONE 0 #define STATE_STATUS 1 #define STATE_LOGIN 2 @@ -188,13 +197,6 @@ extern uint8_t motd_len; extern uint16_t client_count; -typedef struct { - short x; - short z; - uint8_t y; - uint8_t block; -} BlockChange; - #pragma pack(push, 1) typedef struct { @@ -264,9 +266,6 @@ typedef struct { union EntityDataValue value; } EntityData; -extern BlockChange block_changes[MAX_BLOCK_CHANGES]; -extern int block_changes_count; - extern PlayerData player_data[MAX_PLAYERS]; extern int player_data_count; diff --git a/include/procedures.h b/include/procedures.h index b7273c8c..ce8b9bb4 100644 --- a/include/procedures.h +++ b/include/procedures.h @@ -27,9 +27,6 @@ void broadcastMobMetadata (int client_fd, int entity_id); uint8_t serverSlotToClientSlot (int window_id, uint8_t slot); uint8_t clientSlotToServerSlot (int window_id, uint8_t slot); -uint8_t getBlockChange (short x, uint8_t y, short z); -uint8_t makeBlockChange (short x, uint8_t y, short z, uint8_t block); - uint8_t isInstantlyMined (PlayerData *player, uint8_t block); uint8_t isColumnBlock (uint8_t block); uint8_t isPassableBlock (uint8_t block); diff --git a/src/blocks.c b/src/blocks.c new file mode 100644 index 00000000..8781816a --- /dev/null +++ b/src/blocks.c @@ -0,0 +1,293 @@ +#include "globals.h" +#include "packets.h" +#include "worldgen.h" +#include "blocks.h" + +#include +#include + +BlockBuf block_changes; +int block_changes_count; + +BlockRef nextBlockChange (BlockRef curr) { + if (curr.ri >= block_changes_count) return (BlockRef){-1, -1}; + + short rlen = block_changes.lens[curr.ri]; + rlen &= 0x3FF; // mask out run flags from length + + curr.li ++; + if (curr.li == rlen) { + // end of run, load the next one + curr.ri ++; + curr.li = 0; + + if (curr.ri == block_changes_count) return (BlockRef){-1, -1}; + return curr; + } + + // return next block in run + return curr; +} + +static inline int modeToOffset (uint8_t mode) { + switch (mode & 3) { + case 0 /* 0b00 */: return 0; + case 1 /* 0b01 */: // reserved + case 2 /* 0b10 */: return 1; + case 3 /* 0b11 */: return -1; + } +} + +static inline uint8_t offsetToMode (int offset) { + switch (offset) { + case 0: return 0; + case 1: return 2; + case -1: return 3; + } +} + +static inline int isInRunBounds (uint8_t mode, short base, int len, short val) { + switch (mode) { + case 0: return base == val; + case 2: return base <= val && base + len > val; + case 3: return base >= val && base - len < val; + } +} + +BlockChange derefBlockChange (BlockRef ref) { + if (ref.ri == -1) return (BlockChange){0, 0, 0, 0xFF}; + + // load len and axis modes + short rlen = block_changes.lens[ref.ri]; + uint8_t x_mode = (rlen >> 14) & 3; + uint8_t y_mode = (rlen >> 12) & 3; + uint8_t z_mode = (rlen >> 10) & 3; + rlen &= 0x3FF; + + // load base change and apply axis offsets + BlockChange change = block_changes.runs[ref.ri]; + change.x += modeToOffset(x_mode) * ref.li; + change.y += modeToOffset(y_mode) * ref.li; + change.z += modeToOffset(z_mode) * ref.li; + + return change; +} + +uint8_t getBlockChange(short x, uint8_t y, short z) { + for (BlockRef ref = {0}; ref.ri != -1; ref = nextBlockChange(ref)) { + BlockChange change = derefBlockChange(ref); + if (change.block == 0xFF) continue; + if (change.x == x && + change.y == y && + change.z == z + ) return change.block; + } + return 0xFF; +} + +// Handle running out of memory for new block changes +void failBlockChange (short x, uint8_t y, short z, uint8_t block) { + + // Get previous block at this location + uint8_t before = getBlockAt(x, y, z); + + // Broadcast a new update to all players + for (int i = 0; i < MAX_PLAYERS; i ++) { + if (player_data[i].client_fd == -1) continue; + if (player_data[i].flags & 0x20) continue; + // Reset the block they tried to change + sc_blockUpdate(player_data[i].client_fd, x, y, z, before); + // Broadcast a chat message warning about the limit + sc_systemChat(player_data[i].client_fd, "Block changes limit exceeded. Restore original terrain to continue.", 67); + } + +} + +uint8_t makeBlockChange (short x, uint8_t y, short z, uint8_t block) { + + // Transmit block update to all in-game clients + for (int i = 0; i < MAX_PLAYERS; i ++) { + if (player_data[i].client_fd == -1) continue; + if (player_data[i].flags & 0x20) continue; + sc_blockUpdate(player_data[i].client_fd, x, y, z, block); + } + + // Calculate terrain at these coordinates and compare it to the input block. + // Since block changes get overlayed on top of terrain, we don't want to + // store blocks that don't differ from the base terrain. + ChunkAnchor anchor = { + x / CHUNK_SIZE, + z / CHUNK_SIZE, + }; + if (x % CHUNK_SIZE < 0) anchor.x --; + if (z % CHUNK_SIZE < 0) anchor.z --; + anchor.hash = getChunkHash(anchor.x, anchor.z); + anchor.biome = getChunkBiome(anchor.x, anchor.z); + + uint8_t is_base_block = block == getTerrainAt(x, y, z, anchor); + + // Prioritize inserting changes into already existing runs + // to compress changes as much as possible + for (int i = 0; i < block_changes_count; i ++) { + BlockChange change = block_changes.runs[i]; + + short rlen = block_changes.lens[i]; + uint8_t x_mode = (rlen >> 14) & 3; + uint8_t y_mode = (rlen >> 12) & 3; + uint8_t z_mode = (rlen >> 10) & 3; + rlen &= 0x3FF; + + // check if the coords are covered by the run and remove the block from the run if found + if (isInRunBounds(x_mode, change.x, rlen, x) && + isInRunBounds(y_mode, change.y, rlen, y) && + isInRunBounds(z_mode, change.z, rlen, z) + ) { + // if (rlen == 1) continue; // should never happen + + // figure out the local-index of the block + int li; + if (x_mode != 0) li = (x - change.x) / modeToOffset(x_mode); + else if (y_mode != 0) li = (y - change.y) / modeToOffset(y_mode); + else if (z_mode != 0) li = (z - change.z) / modeToOffset(z_mode); + + if (li == 0) { + if (rlen == 1) { + // no blocks in run, fill gap with a different run + block_changes_count --; + if (block_changes_count == 0) break; + + block_changes.runs[i] = block_changes.runs[block_changes_count]; + block_changes.lens[i] = block_changes.lens[block_changes_count]; + + #ifdef DEV_LOG_BLOCK_STORAGE_STATS + printf("Block storage stats\n"); + printf(" Block run usage: %d runs - %dB\n\n", block_changes_count, block_changes_count * (sizeof(BlockChange) + sizeof(int))); + #endif + i --; // rescan the currently moved run + continue; + } + + // push run start by one to remove the block + change.x += modeToOffset(x_mode); + change.y += modeToOffset(y_mode); + change.z += modeToOffset(z_mode); + block_changes.runs[i] = change; + block_changes.lens[i] --; + + continue; + } else if (li == rlen - 1) { + if (rlen == 1) { + // no blocks in run, fill gap with a different run + block_changes_count --; + if (block_changes_count == 0) break; + + block_changes.runs[i] = block_changes.runs[block_changes_count]; + block_changes.lens[i] = block_changes.lens[block_changes_count]; + + #ifdef DEV_LOG_BLOCK_STORAGE_STATS + printf("Block storage stats\n"); + printf(" Block run usage: %d runs - %dB\n\n", block_changes_count, block_changes_count * (sizeof(BlockChange) + sizeof(int))); + #endif + i --; // rescan the currently moved run + continue; + } + + // shorten run by one end by one + block_changes.lens[i] --; + continue; + } else { + // block is in the middle of the run, split + // the runs into two + + if (block_changes_count == MAX_BLOCK_CHANGES) { + failBlockChange(x, y, z, block); + return 1; + } + + // create the first half by just shortening the current run + short first_len = li; + first_len |= (x_mode << 14); + first_len |= (y_mode << 12); + first_len |= (z_mode << 10); + block_changes.lens[i] = first_len; + + // push the second half as a new run to the end of the buffer + BlockChange second_change = change; + second_change.x += modeToOffset(x_mode) * (li + 1); + second_change.y += modeToOffset(y_mode) * (li + 1); + second_change.z += modeToOffset(z_mode) * (li + 1); + + short second_len = rlen - li - 1; + second_len |= (x_mode << 14); + second_len |= (y_mode << 12); + second_len |= (z_mode << 10); + + block_changes.runs[block_changes_count] = second_change; + block_changes.lens[block_changes_count] = second_len; + block_changes_count ++; + } + } + + // try to add a block only if the run matches + if (change.block != block || is_base_block) continue; + + if (rlen == 1) { + // check if starting a new run is possible + int x_off = x - change.x; + int y_off = y - change.y; + int z_off = z - change.z; + + if ((x_off <= 1 && x_off >= -1) && + (y_off <= 1 && y_off >= -1) && + (z_off <= 1 && z_off >= -1) + ) { + rlen = 2; + rlen |= (offsetToMode(x_off) << 14); + rlen |= (offsetToMode(y_off) << 12); + rlen |= (offsetToMode(z_off) << 10); + + // insert into run + block_changes.lens[i] = rlen; + return 0; + } + } else { + // check if change can be appended to run + if (change.x + modeToOffset(x_mode) * rlen == x && + change.y + modeToOffset(y_mode) * rlen == y && + change.z + modeToOffset(z_mode) * rlen == z + ) { + if (rlen == 0x3FF) continue; // do not overflow the len int + block_changes.lens[i] ++; // increment the run length to include the new change + return 0; + } + } + } + + // Don't create a new entry if it contains the base terrain block + if (is_base_block) return 0; + + // Handle running out of memory for new block changes + if (block_changes_count == MAX_BLOCK_CHANGES) { + failBlockChange(x, y, z, block); + return 1; + } + + // Fall back to storing the change at the end of the buffer + block_changes.runs[block_changes_count] = (BlockChange){ + x, z, y, + block, + }; + block_changes.lens[block_changes_count] = 1; + block_changes_count ++; + + #ifdef DEV_LOG_BLOCK_STORAGE_STATS + printf("Block storage stats\n"); + printf(" Block run usage: %d runs - %dB\n\n", block_changes_count, block_changes_count * (sizeof(BlockChange) + sizeof(int))); + #endif + + // Write change to disk (if applicable) + // FIXME: sync writes + + return 0; + +} diff --git a/src/globals.c b/src/globals.c index 1b5b462f..5cb8d866 100644 --- a/src/globals.c +++ b/src/globals.c @@ -47,9 +47,6 @@ uint8_t motd_len = sizeof(motd) - 1; uint16_t client_count; -BlockChange block_changes[MAX_BLOCK_CHANGES]; -int block_changes_count = 0; - PlayerData player_data[MAX_PLAYERS]; int player_data_count = 0; diff --git a/src/main.c b/src/main.c index 1d8bddbc..08514187 100644 --- a/src/main.c +++ b/src/main.c @@ -515,11 +515,6 @@ int main () { for (int i = 3; i >= 0; i --) printf("%X", (unsigned int)((rng_seed >> (8 * i)) & 255)); printf("\n\n"); - // Initialize block changes entries as unallocated - for (int i = 0; i < MAX_BLOCK_CHANGES; i ++) { - block_changes[i].block = 0xFF; - } - // Start the disk/flash serializer (if applicable) if (initSerializer()) exit(EXIT_FAILURE); diff --git a/src/packets.c b/src/packets.c index 4f323b3d..adeaadc3 100644 --- a/src/packets.c +++ b/src/packets.c @@ -16,6 +16,7 @@ #endif #include "globals.h" +#include "blocks.h" #include "tools.h" #include "varnum.h" #include "registries.h" @@ -413,15 +414,16 @@ int sc_chunkDataAndUpdateLight (int client_fd, int _x, int _z) { // Light-emitting blocks are omitted from chunk data so that they can // be overlayed here. This seems to be cheaper than sending actual // block light data. - for (int i = 0; i < block_changes_count; i ++) { + for (BlockRef ref = {0}; ref.ri != -1; ref = nextBlockChange(ref)) { + BlockChange change = derefBlockChange(ref); #ifdef ALLOW_CHESTS - if (block_changes[i].block != B_torch && block_changes[i].block != B_chest) continue; + if (change.block != B_torch && change.block != B_chest) continue; #else - if (block_changes[i].block != B_torch) continue; + if (change.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, block_changes[i].block); + if (change.x < x || change.x >= x + 16) continue; + if (change.z < z || change.z >= z + 16) continue; + sc_blockUpdate(client_fd, change.x, change.y, change.z, change.block); } return 0; diff --git a/src/procedures.c b/src/procedures.c index 372d2fb6..4ec394d6 100644 --- a/src/procedures.c +++ b/src/procedures.c @@ -7,6 +7,7 @@ #endif #include "globals.h" +#include "blocks.h" #include "tools.h" #include "varnum.h" #include "packets.h" @@ -465,176 +466,6 @@ void broadcastMobMetadata (int client_fd, int entity_id) { free(metadata); } -uint8_t getBlockChange (short x, uint8_t y, short z) { - for (int i = 0; i < block_changes_count; i ++) { - if (block_changes[i].block == 0xFF) continue; - if ( - block_changes[i].x == x && - 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; -} - -// Handle running out of memory for new block changes -void failBlockChange (short x, uint8_t y, short z, uint8_t block) { - - // Get previous block at this location - uint8_t before = getBlockAt(x, y, z); - - // Broadcast a new update to all players - for (int i = 0; i < MAX_PLAYERS; i ++) { - if (player_data[i].client_fd == -1) continue; - if (player_data[i].flags & 0x20) continue; - // Reset the block they tried to change - sc_blockUpdate(player_data[i].client_fd, x, y, z, before); - // Broadcast a chat message warning about the limit - sc_systemChat(player_data[i].client_fd, "Block changes limit exceeded. Restore original terrain to continue.", 67); - } - -} - -uint8_t makeBlockChange (short x, uint8_t y, short z, uint8_t block) { - - // Transmit block update to all in-game clients - for (int i = 0; i < MAX_PLAYERS; i ++) { - if (player_data[i].client_fd == -1) continue; - if (player_data[i].flags & 0x20) continue; - sc_blockUpdate(player_data[i].client_fd, x, y, z, block); - } - - // Calculate terrain at these coordinates and compare it to the input block. - // Since block changes get overlayed on top of terrain, we don't want to - // store blocks that don't differ from the base terrain. - ChunkAnchor anchor = { - x / CHUNK_SIZE, - z / CHUNK_SIZE - }; - if (x % CHUNK_SIZE < 0) anchor.x --; - if (z % CHUNK_SIZE < 0) anchor.z --; - anchor.hash = getChunkHash(anchor.x, anchor.z); - anchor.biome = getChunkBiome(anchor.x, anchor.z); - - uint8_t is_base_block = block == getTerrainAt(x, y, z, anchor); - - // In the block_changes array, 0xFF indicates a missing/restored entry. - // We track the position of the first such "gap" for when the operation - // isn't replacing an existing block change. - int first_gap = block_changes_count; - - // Prioritize replacing entries with matching coordinates - // This prevents having conflicting entries for one set of coordinates - for (int i = 0; i < block_changes_count; i ++) { - if (block_changes[i].block == 0xFF) { - if (first_gap == block_changes_count) first_gap = i; - continue; - } - if ( - block_changes[i].x == x && - 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 { - #ifdef ALLOW_CHESTS - // When placing chests, just unallocate the target block and fall - // through to the chest-specific routine below. - if (block == B_chest) { - block_changes[i].block = 0xFF; - if (first_gap > i) first_gap = i; - #ifndef DISK_SYNC_BLOCKS_ON_INTERVAL - writeBlockChangesToDisk(i, i); - #endif - break; - } - #endif - block_changes[i].block = block; - } - #ifndef DISK_SYNC_BLOCKS_ON_INTERVAL - writeBlockChangesToDisk(i, i); - #endif - return 0; - } - } - - // Don't create a new entry if it contains the base terrain block - if (is_base_block) return 0; - - #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; - } - // Write changes to disk (if applicable) - #ifndef DISK_SYNC_BLOCKS_ON_INTERVAL - writeBlockChangesToDisk(last_real_entry + 1, last_real_entry + 15); - #endif - return 0; - } - // If we're here, no changes were made - failBlockChange(x, y, z, block); - return 1; - } - #endif - - // Handle running out of memory for new block changes - if (first_gap == MAX_BLOCK_CHANGES) { - failBlockChange(x, y, z, block); - return 1; - } - - // Fall back to storing the change at the first possible gap - block_changes[first_gap].x = x; - block_changes[first_gap].y = y; - block_changes[first_gap].z = z; - block_changes[first_gap].block = block; - // Write change to disk (if applicable) - #ifndef DISK_SYNC_BLOCKS_ON_INTERVAL - writeBlockChangesToDisk(first_gap, first_gap); - #endif - // Extend future search range if we've appended to the end - if (first_gap == block_changes_count) { - block_changes_count ++; - } - - return 0; -} - // Returns the result of mining a block, taking into account the block type and tools // Probability numbers obtained with this formula: N = floor(P * 32 ^ 2) uint16_t getMiningResult (uint16_t held_item, uint8_t block) { diff --git a/src/serialize.c b/src/serialize.c index 35ab74bc..54418403 100644 --- a/src/serialize.c +++ b/src/serialize.c @@ -42,26 +42,26 @@ int initSerializer () { if (file) { // Read block changes from the start of the file directly into memory - size_t read = fread(block_changes, 1, sizeof(block_changes), file); - if (read != sizeof(block_changes)) { - printf("Read %u bytes from \"world.bin\", expected %u (block changes). Aborting.\n", read, sizeof(block_changes)); - fclose(file); - return 1; - } + // size_t read = fread(block_changes, 1, sizeof(block_changes), file); + // if (read != sizeof(block_changes)) { + // printf("Read %u bytes from \"world.bin\", expected %u (block changes). Aborting.\n", read, sizeof(block_changes)); + // fclose(file); + // return 1; + // } // Find the index of the last occupied entry to recover block_changes_count - for (int i = 0; i < MAX_BLOCK_CHANGES; i ++) { - if (block_changes[i].block == 0xFF) continue; - if (block_changes[i].block == B_chest) i += 14; - if (i >= block_changes_count) block_changes_count = i + 1; - } + // for (int i = 0; i < MAX_BLOCK_CHANGES; i ++) { + // if (block_changes[i].block == 0xFF) continue; + // if (block_changes[i].block == B_chest) i += 14; + // if (i >= block_changes_count) block_changes_count = i + 1; + // } // Seek past block changes to start reading player data - if (fseek(file, sizeof(block_changes), SEEK_SET) != 0) { - perror("Failed to seek to player data in \"world.bin\". Aborting."); - fclose(file); - return 1; - } + // if (fseek(file, sizeof(block_changes), SEEK_SET) != 0) { + // perror("Failed to seek to player data in \"world.bin\". Aborting."); + // fclose(file); + // return 1; + // } // Read player data directly into memory - read = fread(player_data, 1, sizeof(player_data), file); + size_t read = fread(player_data, 1, sizeof(player_data), file); fclose(file); if (read != sizeof(player_data)) { printf("Read %u bytes from \"world.bin\", expected %u (player data). Aborting.\n", read, sizeof(player_data)); @@ -82,26 +82,26 @@ int initSerializer () { } // Write initial block changes array // This should be done after all entries have had `block` set to 0xFF - size_t written = fwrite(block_changes, 1, sizeof(block_changes), file); - if (written != sizeof(block_changes)) { - perror( - "Failed to write initial block data to \"world.bin\".\n" - "Consider checking permissions or disabling SYNC_WORLD_TO_DISK in \"globals.h\"." - ); - fclose(file); - return 1; - } + // size_t written = fwrite(block_changes, 1, sizeof(block_changes), file); + // if (written != sizeof(block_changes)) { + // perror( + // "Failed to write initial block data to \"world.bin\".\n" + // "Consider checking permissions or disabling SYNC_WORLD_TO_DISK in \"globals.h\"." + // ); + // fclose(file); + // return 1; + // } // Seek past written block changes to start writing player data - if (fseek(file, sizeof(block_changes), SEEK_SET) != 0) { - perror( - "Failed to seek past block changes in \"world.bin\"." - "Consider checking permissions or disabling SYNC_WORLD_TO_DISK in \"globals.h\"." - ); - fclose(file); - return 1; - } + // if (fseek(file, sizeof(block_changes), SEEK_SET) != 0) { + // perror( + // "Failed to seek past block changes in \"world.bin\"." + // "Consider checking permissions or disabling SYNC_WORLD_TO_DISK in \"globals.h\"." + // ); + // fclose(file); + // return 1; + // } // Write initial player data to disk (should be just nulls?) - written = fwrite(player_data, 1, sizeof(player_data), file); + size_t written = fwrite(player_data, 1, sizeof(player_data), file); fclose(file); if (written != sizeof(player_data)) { perror( @@ -128,17 +128,17 @@ void writeBlockChangesToDisk (int from, int to) { for (int i = from; i <= to; i ++) { // Seek to relevant offset in file - if (fseek(file, i * sizeof(BlockChange), SEEK_SET) != 0) { - fclose(file); - perror("Failed to seek in \"world.bin\". Block updates have been dropped."); - return; - } + // if (fseek(file, i * sizeof(BlockChange), SEEK_SET) != 0) { + // fclose(file); + // perror("Failed to seek in \"world.bin\". Block updates have been dropped."); + // return; + // } // Write block change entry to file - if (fwrite(&block_changes[i], 1, sizeof(BlockChange), file) != sizeof(BlockChange)) { - fclose(file); - perror("Failed to write to \"world.bin\". Block updates have been dropped."); - return; - } + // if (fwrite(&block_changes[i], 1, sizeof(BlockChange), file) != sizeof(BlockChange)) { + // fclose(file); + // perror("Failed to write to \"world.bin\". Block updates have been dropped."); + // return; + // } } fclose(file); @@ -154,11 +154,11 @@ void writePlayerDataToDisk () { return; } // Seek past block changes in file - if (fseek(file, sizeof(block_changes), SEEK_SET) != 0) { - fclose(file); - perror("Failed to seek in \"world.bin\". Player updates have been dropped."); - return; - } + // if (fseek(file, sizeof(block_changes), SEEK_SET) != 0) { + // fclose(file); + // perror("Failed to seek in \"world.bin\". Player updates have been dropped."); + // return; + // } // Write full player data array to file // Since this is a bigger write, it should ideally be done infrequently if (fwrite(&player_data, 1, sizeof(player_data), file) != sizeof(player_data)) { diff --git a/src/structures.c b/src/structures.c index 6c9128ff..e5cca39b 100644 --- a/src/structures.c +++ b/src/structures.c @@ -1,5 +1,6 @@ #include "globals.h" +#include "blocks.h" #include "tools.h" #include "registries.h" #include "worldgen.h" diff --git a/src/worldgen.c b/src/worldgen.c index 959cf848..0a6a311a 100644 --- a/src/worldgen.c +++ b/src/worldgen.c @@ -5,6 +5,7 @@ #include #include "globals.h" +#include "blocks.h" #include "tools.h" #include "registries.h" #include "procedures.h" @@ -461,26 +462,27 @@ uint8_t buildChunkSection (int cx, int cy, int cz) { // This does mean that we're generating some terrain only to replace it, // but it's better to apply changes in one run rather than in individual // 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; + for (BlockRef ref = {0}; ref.ri != -1; ref = nextBlockChange(ref)) { + BlockChange change = derefBlockChange(ref); + if (change.block == 0xFF) continue; // Skip blocks that behave better when sent using a block update - if (block_changes[i].block == B_torch) continue; + if (change.block == B_torch) continue; #ifdef ALLOW_CHESTS - if (block_changes[i].block == B_chest) continue; + if (change.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 && - block_changes[i].z >= cz && block_changes[i].z < cz + 16 + change.x >= cx && change.x < cx + 16 && + change.y >= cy && change.y < cy + 16 && + change.z >= cz && change.z < cz + 16 ) { - int dx = block_changes[i].x - cx; - int dy = block_changes[i].y - cy; - int dz = block_changes[i].z - cz; + int dx = change.x - cx; + int dy = change.y - cy; + int dz = change.z - cz; // Same 8-block sequence reversal as before, this time 10x dirtier // because we're working with specific indexes. unsigned address = (unsigned)(dx + (dz << 4) + (dy << 8)); unsigned index = (address & ~7u) | (7u - (address & 7u)); - chunk_section[index] = block_changes[i].block; + chunk_section[index] = change.block; } }