diff --git a/src/main.c b/src/main.c index d3e5f58..dd8676a 100644 --- a/src/main.c +++ b/src/main.c @@ -78,10 +78,10 @@ void handlePacket (int client_fd, int length, int packet_id) { float spawn_yaw = 0.0f, spawn_pitch = 0.0f; if (player->y == -32767) { // is this a new player? - int _x = 8 / chunk_size; - int _z = 8 / chunk_size; - int rx = 8 % chunk_size; - int rz = 8 % chunk_size; + int _x = 8 / CHUNK_SIZE; + int _z = 8 / CHUNK_SIZE; + int rx = 8 % CHUNK_SIZE; + int rz = 8 % CHUNK_SIZE; spawn_y = getHeightAt(rx, rz, _x, _z, getChunkHash(_x, _z)) + 1; } else { spawn_x = player->x > 0 ? (float)player->x + 0.5 : (float)player->x - 0.5; diff --git a/src/packets.c b/src/packets.c index fb19f9e..dd077de 100644 --- a/src/packets.c +++ b/src/packets.c @@ -405,6 +405,14 @@ int sc_setContainerSlot (int client_fd, int window_id, uint16_t slot, uint8_t co } +// S->C Block Update +int sc_blockUpdate (int client_fd, int64_t x, int64_t y, int64_t z, uint8_t block) { + writeVarInt(client_fd, 9 + sizeVarInt(block_palette[block])); + writeByte(client_fd, 0x08); + writeUint64(client_fd, ((x & 0x3FFFFFF) << 38) | ((z & 0x3FFFFFF) << 12) | (y & 0xFFF)); + writeVarInt(client_fd, block_palette[block]); +} + // C->S Player Action int cs_playerAction (int client_fd) { @@ -490,31 +498,37 @@ int cs_useItemOn (int client_fd) { return 0; } + switch (face) { + case 0: y -= 1; break; + case 1: y += 1; break; + case 2: z -= 1; break; + case 3: z += 1; break; + case 4: x -= 1; break; + case 5: x += 1; break; + default: break; + } + PlayerData *player; if (getPlayerData(client_fd, &player)) return 1; - uint16_t item = player->inventory_items[player->hotbar]; - uint8_t block = I_to_B(item); + // check if the player is in the way + if (x == player->x && (y == player->y || y == player->y + 1) && z == player->z) return 0; + + uint16_t *item = &player->inventory_items[player->hotbar]; + uint8_t *count = &player->inventory_count[player->hotbar]; + uint8_t block = I_to_B(*item); // if the selected item doesn't correspond to a block, exit if (block == 0) return 0; // if the selected slot doesn't hold any items, exit - if (player->inventory_count[player->hotbar] == 0) return 0; + if (*count == 0) return 0; // decrease item amount in selected slot - player->inventory_count[player->hotbar] --; + *count -= 1; // clear item id in slot if amount is zero - if (player->inventory_count[player->hotbar] == 0) - player->inventory_items[player->hotbar] = 0; + if (*count == 0) *item = 0; - switch (face) { - case 0: makeBlockChange(x, y - 1, z, block); break; - case 1: makeBlockChange(x, y + 1, z, block); break; - case 2: makeBlockChange(x, y, z - 1, block); break; - case 3: makeBlockChange(x, y, z + 1, block); break; - case 4: makeBlockChange(x - 1, y, z, block); break; - case 5: makeBlockChange(x + 1, y, z, block); break; - default: break; - } + makeBlockChange(x, y, z, block); + sc_setContainerSlot(client_fd, 0, serverSlotToClientSlot(0, player->hotbar), *count, *item); return 0; diff --git a/src/packets.h b/src/packets.h index 6f27f3e..ce4cd92 100644 --- a/src/packets.h +++ b/src/packets.h @@ -27,6 +27,7 @@ int sc_chunkDataAndUpdateLight (int client_fd, int _x, int _z); int sc_keepAlive (int client_fd); int sc_setContainerSlot (int client_fd, int window_id, uint16_t slot, uint8_t count, uint16_t item); int sc_setHeldItem (int client_fd, uint8_t slot); +int sc_blockUpdate (int client_fd, int64_t x, int64_t y, int64_t z, uint8_t block); int sc_openScreen (int client_fd, uint8_t window, const char *title, uint16_t length); int sc_registries(int client_fd); diff --git a/src/tools.c b/src/tools.c index 2989706..106f018 100644 --- a/src/tools.c +++ b/src/tools.c @@ -14,6 +14,7 @@ #include "registries.h" #include "varnum.h" #include "packets.h" +#include "worldgen.h" #include "tools.h" static uint64_t htonll (uint64_t value) { @@ -298,17 +299,47 @@ uint8_t getBlockChange (short x, short y, short z) { void makeBlockChange (short x, short y, short z, uint8_t block) { + // Transmit block update to all managed clients + for (int i = 0; i < MAX_PLAYERS; i ++) { + 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); + uint8_t is_base_block = block == getTerrainAt(x, y, z, anchor); + + // Look for existing block change entries and replace them + // 0xFF indicates a missing/restored entry for (int i = 0; i < block_changes_count; i ++) { + if (block_changes[i].block == 0xFF && !is_base_block) { + block_changes[i].x = x; + block_changes[i].y = y; + block_changes[i].z = z; + block_changes[i].block = block; + return; + } if ( block_changes[i].x == x && block_changes[i].y == y && block_changes[i].z == z ) { - block_changes[i].block = block; + if (is_base_block) block_changes[i].block = 0xFF; + else block_changes[i].block = block; return; } } + // Don't create a new entry if it contains the base terrain block + if (is_base_block) return; + block_changes[block_changes_count].x = x; block_changes[block_changes_count].y = y; block_changes[block_changes_count].z = z; diff --git a/src/worldgen.c b/src/worldgen.c index 1164816..73ca521 100644 --- a/src/worldgen.c +++ b/src/worldgen.c @@ -25,7 +25,7 @@ int getCornerHeight (uint32_t hash) { // Use parts of the hash as random values for the height variation. // We stack multiple different numbers to stabilize the distribution // while allowing for occasional variances. - int height = terrain_base_height + ( + int height = TERRAIN_BASE_HEIGHT + ( (hash & 3) + (hash >> 4 & 3) + (hash >> 8 & 3) + @@ -41,9 +41,9 @@ int getCornerHeight (uint32_t hash) { } int interpolate (int a, int b, int c, int d, int x, int z) { - int top = a * (chunk_size - x) + b * x; - int bottom = c * (chunk_size - x) + d * x; - return (top * (chunk_size - z) + bottom * z) / (chunk_size * chunk_size); + int top = a * (CHUNK_SIZE - x) + b * x; + int bottom = c * (CHUNK_SIZE - x) + d * x; + return (top * (CHUNK_SIZE - z) + bottom * z) / (CHUNK_SIZE * CHUNK_SIZE); } int getHeightAt (int rx, int rz, int _x, int _z, uint32_t chunk_hash) { @@ -62,40 +62,34 @@ int getHeightAt (int rx, int rz, int _x, int _z, uint32_t chunk_hash) { } -typedef struct { - short x; - short z; - uint32_t hash; -} ChunkAnchor; - uint8_t getTerrainAt (int x, int y, int z, ChunkAnchor anchor) { if (y > 80) return B_air; - int rx = x % chunk_size; - int rz = z % chunk_size; - if (rx < 0) rx += chunk_size; - if (rz < 0) rz += chunk_size; + int rx = x % CHUNK_SIZE; + int rz = z % CHUNK_SIZE; + if (rx < 0) rx += CHUNK_SIZE; + if (rz < 0) rz += CHUNK_SIZE; int height = getHeightAt(rx, rz, anchor.x, anchor.z, anchor.hash); if (y >= 64 && y >= height) { - uint8_t tree_position = anchor.hash % (chunk_size * chunk_size); + uint8_t tree_position = anchor.hash % (CHUNK_SIZE * CHUNK_SIZE); - short tree_x = tree_position % chunk_size; - if (tree_x < 3 || tree_x > chunk_size - 3) goto skip_tree; - short tree_z = tree_position / chunk_size; - if (tree_z < 3 || tree_z > chunk_size - 3) goto skip_tree; + short tree_x = tree_position % CHUNK_SIZE; + if (tree_x < 3 || tree_x > CHUNK_SIZE - 3) goto skip_tree; + short tree_z = tree_position / CHUNK_SIZE; + if (tree_z < 3 || tree_z > CHUNK_SIZE - 3) goto skip_tree; uint8_t tree_short = (anchor.hash >> (tree_x + tree_z)) & 1; - tree_x += anchor.x * chunk_size; - tree_z += anchor.z * chunk_size; + tree_x += anchor.x * CHUNK_SIZE; + tree_z += anchor.z * CHUNK_SIZE; uint8_t tree_y = getHeightAt( - tree_x < 0 ? tree_x % chunk_size + chunk_size : tree_x % chunk_size, - tree_z < 0 ? tree_z % chunk_size + chunk_size : tree_z % chunk_size, + tree_x < 0 ? tree_x % CHUNK_SIZE + CHUNK_SIZE : tree_x % CHUNK_SIZE, + tree_z < 0 ? tree_z % CHUNK_SIZE + CHUNK_SIZE : tree_z % CHUNK_SIZE, anchor.x, anchor.z, anchor.hash ) + 1; if (tree_y < 64) goto skip_tree; @@ -128,8 +122,8 @@ skip_tree: // Starting at 4 blocks below terrain level, generate minerals and caves if (y <= height - 4) { // Caves use the same shape as surface terrain, just mirrored - int8_t gap = height - terrain_base_height; - if (y < cave_base_depth + gap && y > cave_base_depth - gap) return B_air; + int8_t gap = height - TERRAIN_BASE_HEIGHT; + if (y < CAVE_BASE_DEPTH + gap && y > CAVE_BASE_DEPTH - gap) return B_air; // The chunk-relative X and Z coordinates are used in a bit shift on the hash // The sum of these is then used to get the Y coordinate of the ore in this column @@ -171,11 +165,11 @@ uint8_t getBlockAt (int x, int y, int z) { if (block_change != 0xFF) return block_change; ChunkAnchor anchor = { - x / chunk_size, - z / chunk_size + x / CHUNK_SIZE, + z / CHUNK_SIZE }; - if (x % chunk_size < 0) anchor.x --; - if (z % chunk_size < 0) anchor.z --; + if (x % CHUNK_SIZE < 0) anchor.x --; + if (z % CHUNK_SIZE < 0) anchor.z --; anchor.hash = getChunkHash(anchor.x, anchor.z); return getTerrainAt(x, y, z, anchor); @@ -183,19 +177,19 @@ uint8_t getBlockAt (int x, int y, int z) { } uint8_t chunk_section[4096]; -ChunkAnchor chunk_anchors[256 / (chunk_size * chunk_size)]; +ChunkAnchor chunk_anchors[256 / (CHUNK_SIZE * CHUNK_SIZE)]; void buildChunkSection (int cx, int cy, int cz) { // Precompute the hashes and anchors for each minichunk int anchor_index = 0; - for (int i = cz; i < cz + 16; i += chunk_size) { - for (int j = cx; j < cx + 16; j += chunk_size) { + for (int i = cz; i < cz + 16; i += CHUNK_SIZE) { + for (int j = cx; j < cx + 16; j += CHUNK_SIZE) { ChunkAnchor *anchor = chunk_anchors + anchor_index; - anchor->x = j / chunk_size; - anchor->z = i / chunk_size; + anchor->x = j / CHUNK_SIZE; + anchor->z = i / CHUNK_SIZE; anchor->hash = getChunkHash(anchor->x, anchor->z); anchor_index ++; @@ -211,7 +205,7 @@ void buildChunkSection (int cx, int cy, int cz) { // The client expects "big-endian longs", which in our // case means reversing the order in which we store/send // each 8 block sequence. - anchor_index = (j % 16) / chunk_size + (j / 16 % 16) / chunk_size * 2; + anchor_index = (j % 16) / CHUNK_SIZE + (j / 16 % 16) / CHUNK_SIZE * 2; for (int offset = 7; offset >= 0; offset--) { int k = j + offset; int x = k % 16 + cx; diff --git a/src/worldgen.h b/src/worldgen.h index 23ec2e5..a684441 100644 --- a/src/worldgen.h +++ b/src/worldgen.h @@ -3,15 +3,22 @@ #include -// For best performance, chunk_size should be a power of 2 -#define chunk_size 8 +// For best performance, CHUNK_SIZE should be a power of 2 +#define CHUNK_SIZE 8 // Terrain low point - should start a bit below sea level for rivers/lakes -#define terrain_base_height 60 +#define TERRAIN_BASE_HEIGHT 60 // Center point of cave generation -#define cave_base_depth 24 +#define CAVE_BASE_DEPTH 24 + +typedef struct { + short x; + short z; + uint32_t hash; +} ChunkAnchor; uint32_t getChunkHash (short x, short z); int getHeightAt (int rx, int rz, int _x, int _z, uint32_t chunk_hash); +uint8_t getTerrainAt (int x, int y, int z, ChunkAnchor anchor); uint8_t getBlockAt (int x, int y, int z); extern uint8_t chunk_section[4096];