forked from EXTERNAL/bareiron
Compare commits
5 Commits
fix/max-re
...
main
Author | SHA1 | Date | |
---|---|---|---|
7e23cabfce | |||
![]() |
d02608c69d | ||
7e7a98bd41 | |||
90a7a8b48e | |||
9fb3e870bf |
2
.clang-format-ignore
Normal file
2
.clang-format-ignore
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
src/
|
||||||
|
include/
|
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,3 +1,8 @@
|
|||||||
|
a.out
|
||||||
|
*.exe
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
core
|
||||||
.vscode
|
.vscode
|
||||||
notchian
|
notchian
|
||||||
bareiron
|
bareiron
|
||||||
|
1
compile_flags.txt
Normal file
1
compile_flags.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
-Iinclude
|
@@ -34,20 +34,12 @@
|
|||||||
// Max render distance, determines how many chunks to send
|
// Max render distance, determines how many chunks to send
|
||||||
#define VIEW_DISTANCE 2
|
#define VIEW_DISTANCE 2
|
||||||
|
|
||||||
// Time between server ticks in microseconds (default = 1s)
|
// Time between server ticks in microseconds (default = 0.05s)
|
||||||
#define TIME_BETWEEN_TICKS 1000000
|
#define TIME_BETWEEN_TICKS 50000
|
||||||
|
|
||||||
// Calculated from TIME_BETWEEN_TICKS
|
// Calculated from TIME_BETWEEN_TICKS
|
||||||
#define TICKS_PER_SECOND ((float)1000000 / 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")
|
// Size of each bilinearly interpolated area ("minichunk")
|
||||||
// For best performance, CHUNK_SIZE should be a power of 2
|
// For best performance, CHUNK_SIZE should be a power of 2
|
||||||
#define CHUNK_SIZE 8
|
#define CHUNK_SIZE 8
|
||||||
@@ -74,6 +66,10 @@
|
|||||||
// Determines the fixed amount of memory allocated to blocks
|
// Determines the fixed amount of memory allocated to blocks
|
||||||
#define MAX_BLOCK_CHANGES 20000
|
#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).
|
// If defined, writes and reads world data to/from disk (or flash).
|
||||||
// This is a synchronous operation, and can cause performance issues if
|
// This is a synchronous operation, and can cause performance issues if
|
||||||
// frequent random disk access is slow. Data is still stored in and
|
// frequent random disk access is slow. Data is still stored in and
|
||||||
@@ -170,7 +166,7 @@
|
|||||||
#define STATE_PLAY 5
|
#define STATE_PLAY 5
|
||||||
|
|
||||||
extern ssize_t recv_count;
|
extern ssize_t recv_count;
|
||||||
extern uint8_t recv_buffer[256];
|
extern uint8_t recv_buffer[MAX_RECV_BUF_LEN];
|
||||||
|
|
||||||
extern uint32_t world_seed;
|
extern uint32_t world_seed;
|
||||||
extern uint32_t rng_seed;
|
extern uint32_t rng_seed;
|
||||||
@@ -195,6 +191,24 @@ typedef struct {
|
|||||||
uint8_t block;
|
uint8_t block;
|
||||||
} BlockChange;
|
} 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)
|
#pragma pack(push, 1)
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@@ -267,6 +281,10 @@ typedef struct {
|
|||||||
extern BlockChange block_changes[MAX_BLOCK_CHANGES];
|
extern BlockChange block_changes[MAX_BLOCK_CHANGES];
|
||||||
extern int block_changes_count;
|
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 PlayerData player_data[MAX_PLAYERS];
|
||||||
extern int player_data_count;
|
extern int player_data_count;
|
||||||
|
|
||||||
|
@@ -32,9 +32,11 @@ uint8_t makeBlockChange (short x, uint8_t y, short z, uint8_t block);
|
|||||||
|
|
||||||
uint8_t isInstantlyMined (PlayerData *player, uint8_t block);
|
uint8_t isInstantlyMined (PlayerData *player, uint8_t block);
|
||||||
uint8_t isColumnBlock (uint8_t block);
|
uint8_t isColumnBlock (uint8_t block);
|
||||||
|
uint8_t isFallingBlock (uint8_t block);
|
||||||
uint8_t isPassableBlock (uint8_t block);
|
uint8_t isPassableBlock (uint8_t block);
|
||||||
uint8_t isPassableSpawnBlock (uint8_t block);
|
uint8_t isPassableSpawnBlock (uint8_t block);
|
||||||
uint8_t isReplaceableBlock (uint8_t block);
|
uint8_t isReplaceableBlock (uint8_t block);
|
||||||
|
uint8_t getFluid (uint8_t block);
|
||||||
uint32_t isCompostItem (uint16_t item);
|
uint32_t isCompostItem (uint16_t item);
|
||||||
uint8_t getItemStackSize (uint16_t item);
|
uint8_t getItemStackSize (uint16_t item);
|
||||||
|
|
||||||
@@ -43,7 +45,10 @@ void bumpToolDurability (PlayerData *player);
|
|||||||
void handlePlayerAction (PlayerData *player, int action, short x, short y, short z);
|
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 handlePlayerUseItem (PlayerData *player, short x, short y, short z, uint8_t face);
|
||||||
|
|
||||||
void checkFluidUpdate (short x, uint8_t y, short z, uint8_t block);
|
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 spawnMob (uint8_t type, short x, uint8_t y, short z, uint8_t health);
|
void spawnMob (uint8_t type, short x, uint8_t y, short z, uint8_t health);
|
||||||
void interactEntity (int entity_id, int interactor_id);
|
void interactEntity (int entity_id, int interactor_id);
|
||||||
|
@@ -31,8 +31,8 @@
|
|||||||
ssize_t recv_count;
|
ssize_t recv_count;
|
||||||
uint8_t recv_buffer[MAX_RECV_BUF_LEN] = {0};
|
uint8_t recv_buffer[MAX_RECV_BUF_LEN] = {0};
|
||||||
|
|
||||||
uint32_t world_seed = INITIAL_WORLD_SEED;
|
uint32_t world_seed;
|
||||||
uint32_t rng_seed = INITIAL_RNG_SEED;
|
uint32_t rng_seed;
|
||||||
|
|
||||||
uint16_t world_time = 0;
|
uint16_t world_time = 0;
|
||||||
uint32_t server_ticks = 0;
|
uint32_t server_ticks = 0;
|
||||||
@@ -50,6 +50,10 @@ uint16_t client_count;
|
|||||||
BlockChange block_changes[MAX_BLOCK_CHANGES];
|
BlockChange block_changes[MAX_BLOCK_CHANGES];
|
||||||
int block_changes_count = 0;
|
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];
|
PlayerData player_data[MAX_PLAYERS];
|
||||||
int player_data_count = 0;
|
int player_data_count = 0;
|
||||||
|
|
||||||
|
284
src/main.c
284
src/main.c
@@ -26,6 +26,8 @@
|
|||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
|
#include <sys/select.h>
|
||||||
|
#include <sys/random.h>
|
||||||
#endif
|
#endif
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
@@ -505,15 +507,16 @@ int main () {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Hash the seeds to ensure they're random enough
|
if (0) { // TODO: manual seed
|
||||||
world_seed = splitmix64(world_seed);
|
world_seed = splitmix64(world_seed);
|
||||||
printf("World seed (hashed): ");
|
} else {
|
||||||
|
getrandom(&world_seed, 4, GRND_RANDOM);
|
||||||
|
}
|
||||||
|
printf("World seed: ");
|
||||||
for (int i = 3; i >= 0; i --) printf("%X", (unsigned int)((world_seed >> (8 * i)) & 255));
|
for (int i = 3; i >= 0; i --) printf("%X", (unsigned int)((world_seed >> (8 * i)) & 255));
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
rng_seed = splitmix64(rng_seed);
|
getrandom(&rng_seed, 4, GRND_RANDOM);
|
||||||
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
|
// Initialize block changes entries as unallocated
|
||||||
for (int i = 0; i < MAX_BLOCK_CHANGES; i ++) {
|
for (int i = 0; i < MAX_BLOCK_CHANGES; i ++) {
|
||||||
@@ -586,137 +589,168 @@ int main () {
|
|||||||
// Track time of last server tick (in microseconds)
|
// Track time of last server tick (in microseconds)
|
||||||
int64_t last_tick_time = get_program_time();
|
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) {
|
while (true) {
|
||||||
// Check if it's time to yield to the idle task
|
fd_set read_fds;
|
||||||
task_yield();
|
FD_ZERO(&read_fds);
|
||||||
|
FD_SET(server_fd, &read_fds);
|
||||||
|
|
||||||
// Attempt to accept a new connection
|
if (deferred_block_updates_count == MAX_DEFERRED_BLOCK_UPDATES) {
|
||||||
for (int i = 0; i < MAX_PLAYERS; i ++) {
|
printf("WARNING: Deferred block update queue maxed out\n");
|
||||||
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) {
|
|
||||||
printf("New client, fd: %d\n", clients[i]);
|
|
||||||
#ifdef _WIN32
|
|
||||||
u_long mode = 1;
|
|
||||||
ioctlsocket(clients[i], FIONBIO, &mode);
|
|
||||||
#else
|
|
||||||
int flags = fcntl(clients[i], F_GETFL, 0);
|
|
||||||
fcntl(clients[i], F_SETFL, flags | O_NONBLOCK);
|
|
||||||
#endif
|
|
||||||
client_count ++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look for valid connected clients
|
// Attempt to accept a new connection
|
||||||
client_index ++;
|
int max_fd = server_fd;
|
||||||
if (client_index == MAX_PLAYERS) client_index = 0;
|
for (int i = 0; i < MAX_PLAYERS; i ++) {
|
||||||
if (clients[client_index] == -1) continue;
|
if (clients[i] != -1) {
|
||||||
|
FD_SET(clients[i], &read_fds);
|
||||||
|
if (clients[i] > max_fd) max_fd = clients[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Handle periodic events (server ticks)
|
// Compute timeout until next server tick
|
||||||
int64_t time_since_last_tick = get_program_time() - last_tick_time;
|
struct timeval timeout;
|
||||||
if (time_since_last_tick > TIME_BETWEEN_TICKS) {
|
int64_t now = get_program_time();
|
||||||
handleServerTick(time_since_last_tick);
|
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();
|
last_tick_time = get_program_time();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle this individual client
|
if (activity <= 0) {
|
||||||
int client_fd = clients[client_index];
|
// timeout or error already handled; continue loop
|
||||||
|
|
||||||
// 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[client_index], 1);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (recv_count == SOCKET_ERROR) {
|
|
||||||
int err = WSAGetLastError();
|
if (FD_ISSET(server_fd, &read_fds)) {
|
||||||
if (err == WSAEWOULDBLOCK) {
|
while (true) {
|
||||||
continue; // no data yet, keep client alive
|
int slot = -1;
|
||||||
} else {
|
for (int i = 0; i < MAX_PLAYERS; i ++) {
|
||||||
disconnectClient(&clients[client_index], 1);
|
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]);
|
||||||
|
#ifdef _WIN32
|
||||||
|
u_long mode = 1;
|
||||||
|
ioctlsocket(clients[slot], FIONBIO, &mode);
|
||||||
|
#else
|
||||||
|
int flags = fcntl(clients[slot], F_GETFL, 0);
|
||||||
|
fcntl(clients[slot], F_SETFL, flags | O_NONBLOCK);
|
||||||
|
#endif
|
||||||
|
client_count ++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
#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);
|
||||||
continue;
|
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);
|
close(server_fd);
|
||||||
|
259
src/procedures.c
259
src/procedures.c
@@ -1,3 +1,4 @@
|
|||||||
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@@ -761,6 +762,13 @@ 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
|
// Checks whether the given block has to have something beneath it
|
||||||
uint8_t isColumnBlock (uint8_t block) {
|
uint8_t isColumnBlock (uint8_t block) {
|
||||||
return (
|
return (
|
||||||
@@ -790,8 +798,7 @@ uint8_t isPassableBlock (uint8_t block) {
|
|||||||
}
|
}
|
||||||
// Checks whether the given block is non-solid and spawnable
|
// Checks whether the given block is non-solid and spawnable
|
||||||
uint8_t isPassableSpawnBlock (uint8_t block) {
|
uint8_t isPassableSpawnBlock (uint8_t block) {
|
||||||
if ((block >= B_water && block < B_water + 8) ||
|
if (getFluid(block))
|
||||||
(block >= B_lava && block < B_lava + 4))
|
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -1037,6 +1044,17 @@ 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
|
// a higher fluid "level" means the fluid has traveled farther
|
||||||
uint8_t level = block - fluid;
|
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
|
// Query blocks adjacent to this fluid stream
|
||||||
uint8_t adjacent[4] = {
|
uint8_t adjacent[4] = {
|
||||||
getBlockAt(x + 1, y, z),
|
getBlockAt(x + 1, y, z),
|
||||||
@@ -1058,10 +1076,7 @@ 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 not connected, clear this block and recalculate surrounding flow
|
||||||
if (!connected) {
|
if (!connected) {
|
||||||
makeBlockChange(x, y, z, B_air);
|
makeBlockChange(x, y, z, B_air);
|
||||||
checkFluidUpdate(x + 1, y, z, adjacent[0]);
|
updateXYZNeighbors(x, y, z, UPDATE_NOW);
|
||||||
checkFluidUpdate(x - 1, y, z, adjacent[1]);
|
|
||||||
checkFluidUpdate(x, y, z + 1, adjacent[2]);
|
|
||||||
checkFluidUpdate(x, y, z - 1, adjacent[3]);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1070,42 +1085,38 @@ void handleFluidMovement (short x, uint8_t y, short z, uint8_t fluid, uint8_t bl
|
|||||||
uint8_t block_below = getBlockAt(x, y - 1, z);
|
uint8_t block_below = getBlockAt(x, y - 1, z);
|
||||||
if (isReplaceableBlock(block_below)) {
|
if (isReplaceableBlock(block_below)) {
|
||||||
makeBlockChange(x, y - 1, z, fluid);
|
makeBlockChange(x, y - 1, z, fluid);
|
||||||
return handleFluidMovement(x, y - 1, z, fluid, fluid);
|
updateXYZNeighbors(x, y, z, UPDATE_NOW);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop flowing laterally at the maximum level
|
// Stop flowing laterally at the maximum level
|
||||||
if (level == 3 && fluid == B_lava) return;
|
if (level == max_level) return;
|
||||||
if (level == 7) return;
|
|
||||||
|
|
||||||
// Handle lateral water flow, increasing level by 1
|
// Handle lateral water flow, increasing level by 1
|
||||||
if (isReplaceableFluid(adjacent[0], level, fluid)) {
|
if (isReplaceableFluid(adjacent[0], level, fluid)) {
|
||||||
makeBlockChange(x + 1, y, z, block + 1);
|
makeBlockChange(x + 1, y, z, block + 1);
|
||||||
handleFluidMovement(x + 1, y, z, fluid, block + 1);
|
updateXYZNeighbors(x, y, z, UPDATE_NOW);
|
||||||
}
|
}
|
||||||
if (isReplaceableFluid(adjacent[1], level, fluid)) {
|
if (isReplaceableFluid(adjacent[1], level, fluid)) {
|
||||||
makeBlockChange(x - 1, y, z, block + 1);
|
makeBlockChange(x - 1, y, z, block + 1);
|
||||||
handleFluidMovement(x - 1, y, z, fluid, block + 1);
|
updateXYZNeighbors(x, y, z, UPDATE_NOW);
|
||||||
}
|
}
|
||||||
if (isReplaceableFluid(adjacent[2], level, fluid)) {
|
if (isReplaceableFluid(adjacent[2], level, fluid)) {
|
||||||
makeBlockChange(x, y, z + 1, block + 1);
|
makeBlockChange(x, y, z + 1, block + 1);
|
||||||
handleFluidMovement(x, y, z + 1, fluid, block + 1);
|
updateXYZNeighbors(x, y, z, UPDATE_NOW);
|
||||||
}
|
}
|
||||||
if (isReplaceableFluid(adjacent[3], level, fluid)) {
|
if (isReplaceableFluid(adjacent[3], level, fluid)) {
|
||||||
makeBlockChange(x, y, z - 1, block + 1);
|
makeBlockChange(x, y, z - 1, block + 1);
|
||||||
handleFluidMovement(x, y, z - 1, fluid, block + 1);
|
updateXYZNeighbors(x, y, z, UPDATE_NOW);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void checkFluidUpdate (short x, uint8_t y, short z, uint8_t block) {
|
uint8_t getFluid(uint8_t block) {
|
||||||
|
if (block >= B_water && block < B_water + 8)
|
||||||
uint8_t fluid;
|
return B_water;
|
||||||
if (block >= B_water && block < B_water + 8) fluid = B_water;
|
else if (block >= B_lava && block < B_lava + 4)
|
||||||
else if (block >= B_lava && block < B_lava + 4) fluid = B_lava;
|
return B_lava;
|
||||||
else return;
|
return 0;
|
||||||
|
|
||||||
handleFluidMovement(x, y, z, fluid, block);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef ENABLE_PICKUP_ANIMATION
|
#ifdef ENABLE_PICKUP_ANIMATION
|
||||||
@@ -1143,6 +1154,111 @@ void playPickupAnimation (PlayerData *player, uint16_t item, double x, double y,
|
|||||||
}
|
}
|
||||||
#endif
|
#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) {
|
void handlePlayerAction (PlayerData *player, int action, short x, short y, short z) {
|
||||||
|
|
||||||
// Re-sync slot when player drops an item
|
// Re-sync slot when player drops an item
|
||||||
@@ -1166,55 +1282,34 @@ void handlePlayerAction (PlayerData *player, int action, short x, short y, short
|
|||||||
// Ignore further actions not pertaining to mining blocks
|
// Ignore further actions not pertaining to mining blocks
|
||||||
if (action != 0 && action != 2) return;
|
if (action != 0 && action != 2) return;
|
||||||
|
|
||||||
|
uint8_t block = getBlockAt(x, y, z);
|
||||||
|
|
||||||
// In creative, only the "start mining" action is sent
|
// In creative, only the "start mining" action is sent
|
||||||
// No additional verification is performed, the block is simply removed
|
// No additional verification is performed, the block is simply removed
|
||||||
if (action == 0 && GAMEMODE == 1) {
|
if (action == 0 && GAMEMODE == 1) {
|
||||||
makeBlockChange(x, y, z, 0);
|
makeBlockChange(x, y, z, B_air);
|
||||||
return;
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t block = getBlockAt(x, y, z);
|
// Update nearby blocks
|
||||||
|
updateXYZNeighbors(x, y, z, UPDATE_NOW);
|
||||||
// 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) {
|
void handlePlayerUseItem (PlayerData *player, short x, short y, short z, uint8_t face) {
|
||||||
@@ -1350,8 +1445,7 @@ void handlePlayerUseItem (PlayerData *player, short x, short y, short z, uint8_t
|
|||||||
(y == player->y || y == player->y + 1) &&
|
(y == player->y || y == player->y + 1) &&
|
||||||
z == player->z
|
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
|
// Apply server-side block change
|
||||||
if (makeBlockChange(x, y, z, block)) return;
|
if (makeBlockChange(x, y, z, block)) return;
|
||||||
@@ -1359,14 +1453,9 @@ void handlePlayerUseItem (PlayerData *player, short x, short y, short z, uint8_t
|
|||||||
*count -= 1;
|
*count -= 1;
|
||||||
// Clear item id in slot if amount is zero
|
// Clear item id in slot if amount is zero
|
||||||
if (*count == 0) *item = 0;
|
if (*count == 0) *item = 0;
|
||||||
// Calculate fluid flow
|
// Send updates
|
||||||
#ifdef DO_FLUID_FLOW
|
processBlockUpdate(x, y, z, block, UPDATE_NOW);
|
||||||
checkFluidUpdate(x, y + 1, z, getBlockAt(x, y + 1, z));
|
updateXYZNeighbors(x, y, z, UPDATE_NOW);
|
||||||
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
|
// Sync hotbar contents to player
|
||||||
@@ -1685,6 +1774,22 @@ void handleServerTick (int64_t time_since_last_tick) {
|
|||||||
*/
|
*/
|
||||||
if (rng_seed == 0) rng_seed = world_seed;
|
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
|
// Tick mob behavior
|
||||||
for (int i = 0; i < MAX_MOBS; i ++) {
|
for (int i = 0; i < MAX_MOBS; i ++) {
|
||||||
if (mob_data[i].type == 0) continue;
|
if (mob_data[i].type == 0) continue;
|
||||||
|
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
int64_t last_disk_sync_time = 0;
|
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
|
// Restores world data from disk, or writes world file if it doesn't exist
|
||||||
int initSerializer () {
|
int initSerializer () {
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user