1 Commits

Author SHA1 Message Date
8821ee9c37 FIX: MAX_RECV_BUFFER_LEN not used in header 2025-09-27 22:00:09 +02:00
9 changed files with 210 additions and 386 deletions

View File

@@ -1,2 +0,0 @@
src/
include/

5
.gitignore vendored
View File

@@ -1,8 +1,3 @@
a.out
*.exe
*.o
*.a
core
.vscode
notchian
bareiron

View File

@@ -1 +0,0 @@
-Iinclude

View File

@@ -34,12 +34,20 @@
// Max render distance, determines how many chunks to send
#define VIEW_DISTANCE 2
// Time between server ticks in microseconds (default = 0.05s)
#define TIME_BETWEEN_TICKS 50000
// Time between server ticks in microseconds (default = 1s)
#define TIME_BETWEEN_TICKS 1000000
// Calculated from TIME_BETWEEN_TICKS
#define TICKS_PER_SECOND ((float)1000000 / TIME_BETWEEN_TICKS)
// Initial world generation seed, will be hashed on startup
// Used in generating terrain and biomes
#define INITIAL_WORLD_SEED 0xA103DE6C
// Initial general RNG seed, will be hashed on startup
// Used in random game events like item drops and mob behavior
#define INITIAL_RNG_SEED 0xE2B9419
// Size of each bilinearly interpolated area ("minichunk")
// For best performance, CHUNK_SIZE should be a power of 2
#define CHUNK_SIZE 8
@@ -66,10 +74,6 @@
// Determines the fixed amount of memory allocated to blocks
#define MAX_BLOCK_CHANGES 20000
// How many "deferred" (happen at a later time than triggered) block updates to store.
// Determines the fixed amount of memory allocated to that list
#define MAX_DEFERRED_BLOCK_UPDATES 64
// If defined, writes and reads world data to/from disk (or flash).
// This is a synchronous operation, and can cause performance issues if
// frequent random disk access is slow. Data is still stored in and
@@ -191,24 +195,6 @@ typedef struct {
uint8_t block;
} BlockChange;
#define UPDATE_BASIC (1 << 0)
// the sand at this position will be moved down immediately when this is processed
#define UPDATE_FALL_SAND (1 << 1)
#define UPDATE_FLOW_WATER (1 << 2)
// the sand below this block will fall soon
#define UPDATE_CHECK_SAND_FALL (1 << 3)
#define UPDATE_CHECK_WATER (1 << 4)
#define UPDATE_NOW (UPDATE_BASIC | UPDATE_CHECK_SAND_FALL | UPDATE_CHECK_WATER)
typedef struct {
short update_kind;
short x;
short z;
uint8_t y;
uint8_t await_ticks;
} DeferredBlockUpdate;
#pragma pack(push, 1)
typedef struct {
@@ -281,10 +267,6 @@ typedef struct {
extern BlockChange block_changes[MAX_BLOCK_CHANGES];
extern int block_changes_count;
extern DeferredBlockUpdate deferred_block_updates[MAX_DEFERRED_BLOCK_UPDATES];
extern int deferred_block_updates_count;
extern uint8_t is_processing_deferred_block_updates;
extern PlayerData player_data[MAX_PLAYERS];
extern int player_data_count;

View File

@@ -32,11 +32,9 @@ 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 isFallingBlock (uint8_t block);
uint8_t isPassableBlock (uint8_t block);
uint8_t isPassableSpawnBlock (uint8_t block);
uint8_t isReplaceableBlock (uint8_t block);
uint8_t getFluid (uint8_t block);
uint32_t isCompostItem (uint16_t item);
uint8_t getItemStackSize (uint16_t item);
@@ -45,10 +43,7 @@ void bumpToolDurability (PlayerData *player);
void handlePlayerAction (PlayerData *player, int action, short x, short y, short z);
void handlePlayerUseItem (PlayerData *player, short x, short y, short z, uint8_t face);
void processBlockUpdate (short x, uint8_t y, short z, uint8_t block, short update_kind);
void updateXZNeighbors (short x, uint8_t y, short z, short update_kind);
void updateXYZNeighbors (short x, uint8_t y, short z, short update_kind);
void deferBlockUpdate (short x, uint8_t y, short z, uint8_t await_ticks, short update_kind);
void checkFluidUpdate (short x, uint8_t y, short z, uint8_t block);
void spawnMob (uint8_t type, short x, uint8_t y, short z, uint8_t health);
void interactEntity (int entity_id, int interactor_id);

View File

@@ -31,8 +31,8 @@
ssize_t recv_count;
uint8_t recv_buffer[MAX_RECV_BUF_LEN] = {0};
uint32_t world_seed;
uint32_t rng_seed;
uint32_t world_seed = INITIAL_WORLD_SEED;
uint32_t rng_seed = INITIAL_RNG_SEED;
uint16_t world_time = 0;
uint32_t server_ticks = 0;
@@ -50,10 +50,6 @@ uint16_t client_count;
BlockChange block_changes[MAX_BLOCK_CHANGES];
int block_changes_count = 0;
DeferredBlockUpdate deferred_block_updates[MAX_DEFERRED_BLOCK_UPDATES];
int deferred_block_updates_count = 0;
uint8_t is_processing_deferred_block_updates = 0;
PlayerData player_data[MAX_PLAYERS];
int player_data_count = 0;

View File

@@ -26,8 +26,6 @@
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <sys/random.h>
#endif
#include <unistd.h>
#include <time.h>
@@ -507,16 +505,15 @@ int main () {
}
#endif
if (0) { // TODO: manual seed
world_seed = splitmix64(world_seed);
} else {
getrandom(&world_seed, 4, GRND_RANDOM);
}
printf("World seed: ");
// Hash the seeds to ensure they're random enough
world_seed = splitmix64(world_seed);
printf("World seed (hashed): ");
for (int i = 3; i >= 0; i --) printf("%X", (unsigned int)((world_seed >> (8 * i)) & 255));
printf("\n");
getrandom(&rng_seed, 4, GRND_RANDOM);
rng_seed = splitmix64(rng_seed);
printf("\nRNG seed (hashed): ");
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 ++) {
@@ -589,168 +586,137 @@ int main () {
// Track time of last server tick (in microseconds)
int64_t last_tick_time = get_program_time();
// Main server loop: use select() to sleep until I/O or next tick
/**
* Cycles through all connected clients, handling one packet at a time
* from each player. With every iteration, attempts to accept a new
* client connection.
*/
while (true) {
fd_set read_fds;
FD_ZERO(&read_fds);
FD_SET(server_fd, &read_fds);
if (deferred_block_updates_count == MAX_DEFERRED_BLOCK_UPDATES) {
printf("WARNING: Deferred block update queue maxed out\n");
}
// Check if it's time to yield to the idle task
task_yield();
// Attempt to accept a new connection
int max_fd = server_fd;
for (int i = 0; i < MAX_PLAYERS; i ++) {
if (clients[i] != -1) continue;
clients[i] = accept(server_fd, (struct sockaddr *)&client_addr, &addr_len);
// If the accept was successful, make the client non-blocking too
if (clients[i] != -1) {
FD_SET(clients[i], &read_fds);
if (clients[i] > max_fd) max_fd = clients[i];
}
}
// Compute timeout until next server tick
struct timeval timeout;
int64_t now = get_program_time();
int64_t elapsed = now - last_tick_time;
int64_t to_next = TIME_BETWEEN_TICKS - elapsed;
if (to_next < 0) to_next = 0;
timeout.tv_sec = (time_t)(to_next / 1000000);
timeout.tv_usec = (suseconds_t)(to_next % 1000000);
int activity;
#ifdef _WIN32
activity = select(0, &read_fds, NULL, NULL, &timeout);
if (activity == SOCKET_ERROR) {
int err = WSAGetLastError();
if (err == WSAEINTR) continue;
if (err == WSAETIMEDOUT) activity = 0;
}
#else
activity = select(max_fd + 1, &read_fds, NULL, NULL, &timeout);
if (activity < 0) {
if (errno == EINTR) continue; // interrupted, retry
}
#endif
now = get_program_time();
elapsed = now - last_tick_time;
if (elapsed >= TIME_BETWEEN_TICKS) {
handleServerTick(elapsed);
last_tick_time = get_program_time();
}
if (activity <= 0) {
// timeout or error already handled; continue loop
continue;
}
if (FD_ISSET(server_fd, &read_fds)) {
while (true) {
int slot = -1;
for (int i = 0; i < MAX_PLAYERS; i ++) {
if (clients[i] == -1) { slot = i; break; }
}
if (slot == -1) {
int tmp = accept(server_fd, (struct sockaddr *)&client_addr, &addr_len);
if (tmp == -1) break;
#ifdef _WIN32
closesocket(tmp);
#else
close(tmp);
#endif
break;
}
clients[slot] = accept(server_fd, (struct sockaddr *)&client_addr, &addr_len);
if (clients[slot] == -1) break; // no more pending
printf("New client, fd: %d\n", clients[slot]);
printf("New client, fd: %d\n", clients[i]);
#ifdef _WIN32
u_long mode = 1;
ioctlsocket(clients[slot], FIONBIO, &mode);
ioctlsocket(clients[i], FIONBIO, &mode);
#else
int flags = fcntl(clients[slot], F_GETFL, 0);
fcntl(clients[slot], F_SETFL, flags | O_NONBLOCK);
int flags = fcntl(clients[i], F_GETFL, 0);
fcntl(clients[i], F_SETFL, flags | O_NONBLOCK);
#endif
client_count ++;
}
break;
}
for (int i = 0; i < MAX_PLAYERS; i ++) {
int client_fd = clients[i];
if (client_fd == -1) continue;
if (!FD_ISSET(client_fd, &read_fds)) continue;
// Look for valid connected clients
client_index ++;
if (client_index == MAX_PLAYERS) client_index = 0;
if (clients[client_index] == -1) continue;
// Handle periodic events (server ticks)
int64_t time_since_last_tick = get_program_time() - last_tick_time;
if (time_since_last_tick > TIME_BETWEEN_TICKS) {
handleServerTick(time_since_last_tick);
last_tick_time = get_program_time();
}
// Handle this individual client
int client_fd = clients[client_index];
// Check if at least 2 bytes are available for reading
#ifdef _WIN32
recv_count = recv(client_fd, recv_buffer, 2, MSG_PEEK);
if (recv_count == 0) {
disconnectClient(&clients[i], 1);
continue;
}
if (recv_count == SOCKET_ERROR) {
int err = WSAGetLastError();
if (err == WSAEWOULDBLOCK) {
continue; // no data yet, keep client alive
} else {
disconnectClient(&clients[i], 1);
continue;
}
}
#else
recv_count = recv(client_fd, &recv_buffer, 2, MSG_PEEK);
if (recv_count < 2) {
if (recv_count == 0 || (recv_count < 0 && errno != EAGAIN && errno != EWOULDBLOCK)) {
disconnectClient(&clients[i], 1);
}
continue;
}
#endif
#ifdef DEV_ENABLE_BEEF_DUMPS
if (recv_buffer[0] == 0xBE && recv_buffer[1] == 0xEF && getClientState(client_fd) == STATE_NONE) {
send_all(client_fd, block_changes, sizeof(block_changes));
send_all(client_fd, player_data, sizeof(player_data));
shutdown(client_fd, SHUT_WR);
recv_all(client_fd, recv_buffer, sizeof(recv_buffer), false);
disconnectClient(&clients[i], 6);
continue;
}
if (recv_buffer[0] == 0xFE && recv_buffer[1] == 0xED && getClientState(client_fd) == STATE_NONE) {
recv_all(client_fd, recv_buffer, 2, false);
recv_all(client_fd, block_changes, sizeof(block_changes), false);
recv_all(client_fd, player_data, sizeof(player_data), false);
for (int j = 0; j < MAX_BLOCK_CHANGES; j ++) {
if (block_changes[j].block == 0xFF) continue;
if (block_changes[j].block == B_chest) j += 14;
if (j >= block_changes_count) block_changes_count = j + 1;
}
writeBlockChangesToDisk(0, block_changes_count);
writePlayerDataToDisk();
disconnectClient(&clients[i], 7);
continue;
}
#endif
int length = readVarInt(client_fd);
if (length == VARNUM_ERROR) {
disconnectClient(&clients[i], 2);
continue;
}
int packet_id = readVarInt(client_fd);
if (packet_id == VARNUM_ERROR) {
disconnectClient(&clients[i], 3);
continue;
}
int state = getClientState(client_fd);
if (state == STATE_NONE && length == 254 && packet_id == 122) {
disconnectClient(&clients[i], 5);
continue;
}
handlePacket(client_fd, length - sizeVarInt(packet_id), packet_id, state);
if (recv_count == 0 || (recv_count == -1 && errno != EAGAIN && errno != EWOULDBLOCK)) {
disconnectClient(&clients[i], 4);
recv_count = recv(client_fd, recv_buffer, 2, MSG_PEEK);
if (recv_count == 0) {
disconnectClient(&clients[client_index], 1);
continue;
}
if (recv_count == SOCKET_ERROR) {
int err = WSAGetLastError();
if (err == WSAEWOULDBLOCK) {
continue; // no data yet, keep client alive
} else {
disconnectClient(&clients[client_index], 1);
continue;
}
}
#else
recv_count = recv(client_fd, &recv_buffer, 2, MSG_PEEK);
if (recv_count < 2) {
if (recv_count == 0 || (recv_count < 0 && errno != EAGAIN && errno != EWOULDBLOCK)) {
disconnectClient(&clients[client_index], 1);
}
continue;
}
#endif
// Handle 0xBEEF and 0xFEED packets for dumping/uploading world data
#ifdef DEV_ENABLE_BEEF_DUMPS
// Received BEEF packet, dump world data and disconnect
if (recv_buffer[0] == 0xBE && recv_buffer[1] == 0xEF && getClientState(client_fd) == STATE_NONE) {
// Send block changes and player data back to back
// The client is expected to know (or calculate) the size of these buffers
send_all(client_fd, block_changes, sizeof(block_changes));
send_all(client_fd, player_data, sizeof(player_data));
// Flush the socket and receive everything left on the wire
shutdown(client_fd, SHUT_WR);
recv_all(client_fd, recv_buffer, sizeof(recv_buffer), false);
// Kick the client
disconnectClient(&clients[client_index], 6);
continue;
}
// Received FEED packet, load world data from socket and disconnect
if (recv_buffer[0] == 0xFE && recv_buffer[1] == 0xED && getClientState(client_fd) == STATE_NONE) {
// Consume 0xFEED bytes (previous read was just a peek)
recv_all(client_fd, recv_buffer, 2, false);
// Write full buffers straight into memory
recv_all(client_fd, block_changes, sizeof(block_changes), false);
recv_all(client_fd, player_data, sizeof(player_data), false);
// 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;
}
// Update data on disk
writeBlockChangesToDisk(0, block_changes_count);
writePlayerDataToDisk();
// Kick the client
disconnectClient(&clients[client_index], 7);
continue;
}
#endif
// Read packet length
int length = readVarInt(client_fd);
if (length == VARNUM_ERROR) {
disconnectClient(&clients[client_index], 2);
continue;
}
// Read packet ID
int packet_id = readVarInt(client_fd);
if (packet_id == VARNUM_ERROR) {
disconnectClient(&clients[client_index], 3);
continue;
}
// Get client connection state
int state = getClientState(client_fd);
// Disconnect on legacy server list ping
if (state == STATE_NONE && length == 254 && packet_id == 122) {
disconnectClient(&clients[client_index], 5);
continue;
}
// Handle packet data
handlePacket(client_fd, length - sizeVarInt(packet_id), packet_id, state);
if (recv_count == 0 || (recv_count == -1 && errno != EAGAIN && errno != EWOULDBLOCK)) {
disconnectClient(&clients[client_index], 4);
continue;
}
}
close(server_fd);

View File

@@ -1,4 +1,3 @@
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -762,13 +761,6 @@ uint8_t isInstantlyMined (PlayerData *player, uint8_t block) {
}
// Checks whether the given block can fall down (like sand, anvils, ...)
uint8_t isFallingBlock (uint8_t block) {
return (
block == B_sand
);
}
// Checks whether the given block has to have something beneath it
uint8_t isColumnBlock (uint8_t block) {
return (
@@ -798,7 +790,8 @@ uint8_t isPassableBlock (uint8_t block) {
}
// Checks whether the given block is non-solid and spawnable
uint8_t isPassableSpawnBlock (uint8_t block) {
if (getFluid(block))
if ((block >= B_water && block < B_water + 8) ||
(block >= B_lava && block < B_lava + 4))
{
return 0;
}
@@ -1044,17 +1037,6 @@ void handleFluidMovement (short x, uint8_t y, short z, uint8_t fluid, uint8_t bl
// a higher fluid "level" means the fluid has traveled farther
uint8_t level = block - fluid;
uint8_t max_level;
switch (fluid) {
case B_lava:
max_level = 3;
break;
case B_water:
max_level = 7;
break;
}
// Query blocks adjacent to this fluid stream
uint8_t adjacent[4] = {
getBlockAt(x + 1, y, z),
@@ -1076,7 +1058,10 @@ void handleFluidMovement (short x, uint8_t y, short z, uint8_t fluid, uint8_t bl
// If not connected, clear this block and recalculate surrounding flow
if (!connected) {
makeBlockChange(x, y, z, B_air);
updateXYZNeighbors(x, y, z, UPDATE_NOW);
checkFluidUpdate(x + 1, y, z, adjacent[0]);
checkFluidUpdate(x - 1, y, z, adjacent[1]);
checkFluidUpdate(x, y, z + 1, adjacent[2]);
checkFluidUpdate(x, y, z - 1, adjacent[3]);
return;
}
}
@@ -1085,38 +1070,42 @@ void handleFluidMovement (short x, uint8_t y, short z, uint8_t fluid, uint8_t bl
uint8_t block_below = getBlockAt(x, y - 1, z);
if (isReplaceableBlock(block_below)) {
makeBlockChange(x, y - 1, z, fluid);
updateXYZNeighbors(x, y, z, UPDATE_NOW);
return;
return handleFluidMovement(x, y - 1, z, fluid, fluid);
}
// Stop flowing laterally at the maximum level
if (level == max_level) return;
if (level == 3 && fluid == B_lava) return;
if (level == 7) return;
// Handle lateral water flow, increasing level by 1
if (isReplaceableFluid(adjacent[0], level, fluid)) {
makeBlockChange(x + 1, y, z, block + 1);
updateXYZNeighbors(x, y, z, UPDATE_NOW);
handleFluidMovement(x + 1, y, z, fluid, block + 1);
}
if (isReplaceableFluid(adjacent[1], level, fluid)) {
makeBlockChange(x - 1, y, z, block + 1);
updateXYZNeighbors(x, y, z, UPDATE_NOW);
handleFluidMovement(x - 1, y, z, fluid, block + 1);
}
if (isReplaceableFluid(adjacent[2], level, fluid)) {
makeBlockChange(x, y, z + 1, block + 1);
updateXYZNeighbors(x, y, z, UPDATE_NOW);
handleFluidMovement(x, y, z + 1, fluid, block + 1);
}
if (isReplaceableFluid(adjacent[3], level, fluid)) {
makeBlockChange(x, y, z - 1, block + 1);
updateXYZNeighbors(x, y, z, UPDATE_NOW);
handleFluidMovement(x, y, z - 1, fluid, block + 1);
}
}
uint8_t getFluid(uint8_t block) {
if (block >= B_water && block < B_water + 8)
return B_water;
else if (block >= B_lava && block < B_lava + 4)
return B_lava;
return 0;
void checkFluidUpdate (short x, uint8_t y, short z, uint8_t block) {
uint8_t fluid;
if (block >= B_water && block < B_water + 8) fluid = B_water;
else if (block >= B_lava && block < B_lava + 4) fluid = B_lava;
else return;
handleFluidMovement(x, y, z, fluid, block);
}
#ifdef ENABLE_PICKUP_ANIMATION
@@ -1154,111 +1143,6 @@ void playPickupAnimation (PlayerData *player, uint16_t item, double x, double y,
}
#endif
// can not be zero!!
#define SAND_FALL_T 15
#define SAND_FALL_PROPAGATE_T 2
void processBlockUpdate (short x, uint8_t y, short z, uint8_t block, short update_kind) {
if (update_kind & UPDATE_BASIC) {
// "normal" block update
}
if (update_kind & UPDATE_CHECK_WATER) {
uint8_t fluid = getFluid(block);
if (fluid) {
uint8_t flow_speed;
switch (fluid) {
case B_lava:
flow_speed = 30;
break;
case B_water:
flow_speed = 5;
break;
}
deferBlockUpdate(x, y, z, flow_speed, UPDATE_FLOW_WATER);
}
}
if (update_kind & UPDATE_FLOW_WATER) {
uint8_t fluid = getFluid(block);
if (fluid) {
handleFluidMovement(x, y, z, fluid, block);
}
}
if (update_kind & UPDATE_CHECK_SAND_FALL) {
// we have a separate UPDATE_CHECK_SAND_FALL,
// to make chains of falling sand fall as one,
// with less delay between each
if (isFallingBlock(block) && y) {
uint8_t below = getBlockAt(x, y - 1, z);
if (isReplaceableBlock(below)) {
// move the sand down in 15 ticks
deferBlockUpdate(x, y, z, SAND_FALL_T - 1, UPDATE_FALL_SAND);
if (y != 255 && isFallingBlock(getBlockAt(x, y + 1, z))) {
// also tell the block above that the sand will be gone soon
deferBlockUpdate(x, y + 1, z, SAND_FALL_PROPAGATE_T, UPDATE_CHECK_SAND_FALL);
}
}
}
}
if (update_kind & UPDATE_FALL_SAND) {
// make sure that is a fallable:tm: block, and we are not in the floor
if (isFallingBlock(block) && y) {
uint8_t below = getBlockAt(x, y - 1, z);
// TODO: what to do if below block breaks sand
if (isReplaceableBlock(below)) {
// TODO: drop item of below block
makeBlockChange(x, y, z, B_air);
makeBlockChange(x, y - 1, z, B_air);
makeBlockChange(x, y - 1, z, block);
// update this block after moved
processBlockUpdate (x, y - 1, z, block, UPDATE_NOW);
// also update the neighbors
updateXZNeighbors(x, y, z, UPDATE_NOW);
updateXZNeighbors(x, y - 1, z, UPDATE_NOW);
if (y != 255) {
processBlockUpdate(x, y + 1, z, getBlockAt(x, y + 1, z), UPDATE_NOW);
}
}
}
}
}
void updateXZNeighbors (short x, uint8_t y, short z, short update_kind) {
processBlockUpdate(x - 1, y, z, getBlockAt(x - 1, y, z), update_kind);
processBlockUpdate(x + 1, y, z, getBlockAt(x + 1, y, z), update_kind);
processBlockUpdate(x, y, z - 1, getBlockAt(x, y, z - 1), update_kind);
processBlockUpdate(x, y, z + 1, getBlockAt(x, y, z + 1), update_kind);
}
void updateXYZNeighbors (short x, uint8_t y, short z, short update_kind) {
processBlockUpdate(x - 1, y, z, getBlockAt(x - 1, y, z), update_kind);
processBlockUpdate(x + 1, y, z, getBlockAt(x + 1, y, z), update_kind);
processBlockUpdate(x, y, z - 1, getBlockAt(x, y, z - 1), update_kind);
processBlockUpdate(x, y, z + 1, getBlockAt(x, y, z + 1), update_kind);
processBlockUpdate(x, y - 1, z, getBlockAt(x, y - 1, z), update_kind);
processBlockUpdate(x, y + 1, z, getBlockAt(x, y + 1, z), update_kind);
}
void deferBlockUpdate (short x, uint8_t y, short z, uint8_t await_ticks, short update_kind) {
if (deferred_block_updates_count == MAX_DEFERRED_BLOCK_UPDATES) {
return;
}
DeferredBlockUpdate *u = &deferred_block_updates[deferred_block_updates_count ++];
u->x = x;
u->y = y;
u->z = z;
u->await_ticks = await_ticks + is_processing_deferred_block_updates;
u->update_kind = update_kind;
}
void handlePlayerAction (PlayerData *player, int action, short x, short y, short z) {
// Re-sync slot when player drops an item
@@ -1282,34 +1166,55 @@ void handlePlayerAction (PlayerData *player, int action, short x, short y, short
// Ignore further actions not pertaining to mining blocks
if (action != 0 && action != 2) return;
uint8_t block = getBlockAt(x, y, z);
// In creative, only the "start mining" action is sent
// No additional verification is performed, the block is simply removed
if (action == 0 && GAMEMODE == 1) {
makeBlockChange(x, y, z, B_air);
}
else {
// If this is a "start mining" packet, the block must be instamine
if (action == 0 && !isInstantlyMined(player, block)) return;
// Don't continue if the block change failed
if (makeBlockChange(x, y, z, B_air)) return;
uint16_t held_item = player->inventory_items[player->hotbar];
uint16_t item = getMiningResult(held_item, block);
bumpToolDurability(player);
if (item) {
#ifdef ENABLE_PICKUP_ANIMATION
playPickupAnimation(player, item, x, y, z);
#endif
givePlayerItem(player, item, 1);
}
makeBlockChange(x, y, z, 0);
return;
}
// Update nearby blocks
updateXYZNeighbors(x, y, z, UPDATE_NOW);
uint8_t block = getBlockAt(x, y, z);
// If this is a "start mining" packet, the block must be instamine
if (action == 0 && !isInstantlyMined(player, block)) return;
// Don't continue if the block change failed
if (makeBlockChange(x, y, z, 0)) return;
uint16_t held_item = player->inventory_items[player->hotbar];
uint16_t item = getMiningResult(held_item, block);
bumpToolDurability(player);
if (item) {
#ifdef ENABLE_PICKUP_ANIMATION
playPickupAnimation(player, item, x, y, z);
#endif
givePlayerItem(player, item, 1);
}
// Update nearby fluids
uint8_t block_above = getBlockAt(x, y + 1, z);
#ifdef DO_FLUID_FLOW
checkFluidUpdate(x, y + 1, z, block_above);
checkFluidUpdate(x - 1, y, z, getBlockAt(x - 1, y, z));
checkFluidUpdate(x + 1, y, z, getBlockAt(x + 1, y, z));
checkFluidUpdate(x, y, z - 1, getBlockAt(x, y, z - 1));
checkFluidUpdate(x, y, z + 1, getBlockAt(x, y, z + 1));
#endif
// Check if any blocks above this should break, and if so,
// iterate upward over all blocks in the column and break them
uint8_t y_offset = 1;
while (isColumnBlock(block_above)) {
// Destroy the next block
makeBlockChange(x, y + y_offset, z, 0);
// Check for item drops *without a tool*
uint16_t item = getMiningResult(0, block_above);
if (item) givePlayerItem(player, item, 1);
// Select the next block in the column
y_offset ++;
block_above = getBlockAt(x, y + y_offset, z);
}
}
void handlePlayerUseItem (PlayerData *player, short x, short y, short z, uint8_t face) {
@@ -1445,7 +1350,8 @@ void handlePlayerUseItem (PlayerData *player, short x, short y, short z, uint8_t
(y == player->y || y == player->y + 1) &&
z == player->z
) &&
isReplaceableBlock(getBlockAt(x, y, z))
isReplaceableBlock(getBlockAt(x, y, z)) &&
(!isColumnBlock(block) || getBlockAt(x, y - 1, z) != B_air)
) {
// Apply server-side block change
if (makeBlockChange(x, y, z, block)) return;
@@ -1453,9 +1359,14 @@ void handlePlayerUseItem (PlayerData *player, short x, short y, short z, uint8_t
*count -= 1;
// Clear item id in slot if amount is zero
if (*count == 0) *item = 0;
// Send updates
processBlockUpdate(x, y, z, block, UPDATE_NOW);
updateXYZNeighbors(x, y, z, UPDATE_NOW);
// Calculate fluid flow
#ifdef DO_FLUID_FLOW
checkFluidUpdate(x, y + 1, z, getBlockAt(x, y + 1, z));
checkFluidUpdate(x - 1, y, z, getBlockAt(x - 1, y, z));
checkFluidUpdate(x + 1, y, z, getBlockAt(x + 1, y, z));
checkFluidUpdate(x, y, z - 1, getBlockAt(x, y, z - 1));
checkFluidUpdate(x, y, z + 1, getBlockAt(x, y, z + 1));
#endif
}
// Sync hotbar contents to player
@@ -1774,22 +1685,6 @@ void handleServerTick (int64_t time_since_last_tick) {
*/
if (rng_seed == 0) rng_seed = world_seed;
// block updates might add more deferred block updates,
// so we temporarily make all new block updates add one more tick to the defer tick counter
is_processing_deferred_block_updates = 1;
int next_update_idx = 0;
for (int i = 0; i < deferred_block_updates_count; i ++) {
DeferredBlockUpdate *u = &deferred_block_updates[i];
if (u->await_ticks) {
u->await_ticks --;
deferred_block_updates[next_update_idx ++] = *u;
} else {
processBlockUpdate(u->x, u->y, u->z, getBlockAt(u->x, u->y, u->z), u->update_kind);
}
}
deferred_block_updates_count = next_update_idx;
is_processing_deferred_block_updates = 0;
// Tick mob behavior
for (int i = 0; i < MAX_MOBS; i ++) {
if (mob_data[i].type == 0) continue;

View File

@@ -16,8 +16,6 @@
int64_t last_disk_sync_time = 0;
// TODO: store deferred block updates
// Restores world data from disk, or writes world file if it doesn't exist
int initSerializer () {