From a83acbda675f190e2a678cf2f480ceca1c779037 Mon Sep 17 00:00:00 2001 From: p2r3 Date: Thu, 11 Sep 2025 22:00:32 +0300 Subject: [PATCH] add option to tie movement updates to tickrate --- include/globals.h | 10 ++++++++++ src/main.c | 26 ++++++++++++++++++++------ src/procedures.c | 5 +++++ 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/include/globals.h b/include/globals.h index c481640..5db7421 100644 --- a/include/globals.h +++ b/include/globals.h @@ -102,9 +102,18 @@ // clients from Keep Alive packets. #define NETWORK_TIMEOUT_TIME 15000000 +// If defined, rebroadcasts ALL incoming movement updates, disconnecting +// movement from the server's tickrate. This makes movement much smoother +// on very low tickrates, at the cost of potential network instability when +// hosting more than just a couple of players. When disabling this on low +// tickrates, consider disabling SCALE_MOVEMENT_UPDATES_TO_PLAYER_COUNT too. +#define BROADCAST_ALL_MOVEMENT + // If defined, scales the frequency at which player movement updates are // broadcast based on the amount of players, reducing overhead for higher // player counts. For very many players, makes movement look jittery. +// It is not recommended to use this if BROADCAST_ALL_MOVEMENT is disabled +// on low tickrates, as that might drastically decrease the update rate. #define SCALE_MOVEMENT_UPDATES_TO_PLAYER_COUNT // If defined, calculates fluid flow when blocks are updated near fluids @@ -207,6 +216,7 @@ typedef struct { // 0x08 - sprinting // 0x10 - eating, makes flagval_16 act as eating timer // 0x20 - client loading, uses flagval_16 as fallback timer + // 0x40 - movement update cooldown uint8_t flags; } PlayerData; diff --git a/src/main.c b/src/main.c index 331f960..b2f99e7 100644 --- a/src/main.c +++ b/src/main.c @@ -233,17 +233,31 @@ void handlePacket (int client_fd, int length, int packet_id, int state) { player->pitch = pitch / 90.0f * 127.0f; } - // Broadcast player position to other players + // Whether to broadcast player position to other players + uint8_t should_broadcast = true; + + #ifndef BROADCAST_ALL_MOVEMENT + // If applicable, tie movement updates to the tickrate by using + // a flag that gets reset on every tick. It might sound better + // to just make the tick handler broadcast position updates, but + // then we lose precision. While position is stored using integers, + // here the client gives us doubles and floats directly. + should_broadcast = !(player->flags & 0x40); + if (should_broadcast) player->flags |= 0x40; + #endif + #ifdef SCALE_MOVEMENT_UPDATES_TO_PLAYER_COUNT // If applicable, broadcast only every client_count-th movement update - uint8_t should_broadcast = false; - if (++player->packets_since_update >= client_count) { - should_broadcast = true; + if (++player->packets_since_update < client_count) { + should_broadcast = false; + } else { + // Note that this does not explicitly set should_broadcast to true + // This allows the above BROADCAST_ALL_MOVEMENT check to compound + // Whether that's ever favorable is up for debate player->packets_since_update = 0; } - #else - #define should_broadcast (client_count > 0) #endif + if (should_broadcast) { // If the packet had no rotation data, calculate it from player data if (packet_id == 0x1D) { diff --git a/src/procedures.c b/src/procedures.c index bab819c..24068dd 100644 --- a/src/procedures.c +++ b/src/procedures.c @@ -1424,6 +1424,11 @@ void handleServerTick (int64_t time_since_last_tick) { player->flagval_16 = 0; } else player->flagval_16 ++; } + // Reset movement update cooldown if not broadcasting every update + // Effectively ties player movement updates to the tickrate + #ifndef BROADCAST_ALL_MOVEMENT + player->flags &= ~0x40; + #endif // Below this, process events that happen once per second if (server_ticks % (uint32_t)TICKS_PER_SECOND != 0) continue; // Send Keep Alive and Update Time packets