From 66ab597ad026789add9def622fe9cde1b762fef0 Mon Sep 17 00:00:00 2001 From: p2r3 Date: Wed, 20 Aug 2025 04:30:29 +0300 Subject: [PATCH] add player entities --- src/globals.c | 2 + src/globals.h | 10 ++++ src/main.c | 74 ++++++++++++++++++++---- src/packets.c | 155 +++++++++++++++++++++++++++++++++++++++++++++++--- src/packets.h | 11 +++- src/tools.c | 6 +- src/tools.h | 2 +- 7 files changed, 238 insertions(+), 22 deletions(-) diff --git a/src/globals.c b/src/globals.c index dfce411..831946c 100644 --- a/src/globals.c +++ b/src/globals.c @@ -11,6 +11,8 @@ uint8_t recv_buffer[256] = {0}; uint32_t world_seed = 0xA103DE6C; uint32_t rng_seed = 0xE2B9419; +uint16_t client_count; + BlockChange block_changes[20000]; int block_changes_count = 0; diff --git a/src/globals.h b/src/globals.h index cc5388b..4411307 100644 --- a/src/globals.h +++ b/src/globals.h @@ -24,6 +24,10 @@ // How many visited chunks to "remember" // The server will not re-send chunks that the player has recently been in #define VISITED_HISTORY 4 +// 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. +#define SCALE_MOVEMENT_UPDATES_TO_PLAYER_COUNT #define STATE_NONE 0 #define STATE_STATUS 1 @@ -38,6 +42,8 @@ extern uint8_t recv_buffer[256]; extern uint32_t world_seed; extern uint32_t rng_seed; +extern uint16_t client_count; + #pragma pack(push, 1) typedef struct { @@ -49,12 +55,16 @@ typedef struct { typedef struct { uint8_t uuid[16]; + char name[16]; int client_fd; short x; short y; short z; short visited_x[VISITED_HISTORY]; short visited_z[VISITED_HISTORY]; + #ifdef SCALE_MOVEMENT_UPDATES_TO_PLAYER_COUNT + uint16_t packets_since_update; + #endif int8_t yaw; int8_t pitch; uint8_t hotbar; diff --git a/src/main.c b/src/main.c index 729e0ec..be47150 100644 --- a/src/main.c +++ b/src/main.c @@ -43,9 +43,11 @@ void handlePacket (int client_fd, int length, int packet_id) { if (cs_handshake(client_fd)) break; return; } else if (state == STATE_LOGIN) { - if (cs_loginStart(client_fd)) break; - if (reservePlayerData(client_fd, (char *)(recv_buffer + 17))) break; - if (sc_loginSuccess(client_fd, (char *)recv_buffer, (char *)(recv_buffer + 17))) break; + uint8_t uuid[16]; + char name[16]; + if (cs_loginStart(client_fd, uuid, name)) break; + if (reservePlayerData(client_fd, uuid, name)) break; + if (sc_loginSuccess(client_fd, uuid, name)) break; return; } else if (state == STATE_CONFIGURATION) { if (cs_clientInformation(client_fd)) break; @@ -122,6 +124,17 @@ void handlePacket (int client_fd, int length, int packet_id) { // Re-synchronize player position after all chunks have been sent sc_synchronizePlayerPosition(client_fd, spawn_x, spawn_y, spawn_z, spawn_yaw, spawn_pitch); + // Register all existing players and spawn their entities, and broadcast + // information about the joining player to all existing players. + for (int i = 0; i < MAX_PLAYERS; i ++) { + if (player_data[i].client_fd == -1) continue; + sc_playerInfoUpdateAddPlayer(client_fd, player_data[i]); + if (player_data[i].client_fd == client_fd) continue; + sc_playerInfoUpdateAddPlayer(player_data[i].client_fd, *player); + sc_spawnEntityPlayer(client_fd, player_data[i]); + sc_spawnEntityPlayer(player_data[i].client_fd, *player); + } + return; } break; @@ -157,6 +170,7 @@ void handlePacket (int client_fd, int length, int packet_id) { case 0x1D: case 0x1E: + case 0x1F: if (state == STATE_PLAY) { double x, y, z; @@ -164,27 +178,63 @@ void handlePacket (int client_fd, int length, int packet_id) { // Read player position (and rotation) if (packet_id == 0x1D) cs_setPlayerPosition(client_fd, &x, &y, &z); + else if (packet_id == 0x1F) cs_setPlayerRotation (client_fd, &yaw, &pitch); else cs_setPlayerPositionAndRotation(client_fd, &x, &y, &z, &yaw, &pitch); - // Cast the values to short to get integer position - short cx = x, cy = y, cz = z; PlayerData *player; if (getPlayerData(client_fd, &player)) break; + // Update rotation in player data (if applicable) + if (packet_id != 0x1D) { + player->yaw = ((short)(yaw + 540) % 360 - 180) * 127 / 180; + player->pitch = pitch / 90.0f * 127.0f; + } + + // Broadcast player position to other players + #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; + 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) { + yaw = player->yaw * 180 / 127; + pitch = player->pitch * 90 / 127; + } + // Send current position data to all connected players + for (int i = 0; i < MAX_PLAYERS; i ++) { + if (player_data[i].client_fd == -1) continue; + if (player_data[i].client_fd == client_fd) continue; + if (packet_id == 0x1F) { + sc_updateEntityRotation(player_data[i].client_fd, client_fd, player->yaw, player->pitch); + } else { + sc_teleportEntity(player_data[i].client_fd, client_fd, x, y, z, yaw, pitch); + } + sc_setHeadRotation(player_data[i].client_fd, client_fd, player->yaw); + } + } + + // Don't continue if all we got was rotation data + if (packet_id == 0x1F) return; + + // Cast the values to short to get integer position + short cx = x, cy = y, cz = z; // Determine the player's chunk coordinates short _x = (cx < 0 ? cx - 16 : cx) / 16, _z = (cz < 0 ? cz - 16 : cz) / 16; // Calculate distance between previous and current chunk coordinates short dx = _x - (player->x < 0 ? player->x - 16 : player->x) / 16; short dz = _z - (player->z < 0 ? player->z - 16 : player->z) / 16; - // Update position (and rotation) in player data + // Update position in player data player->x = cx; player->y = cy; player->z = cz; - if (packet_id == 0x1E) { - player->yaw = ((short)(yaw + 540) % 360 - 180) * 127 / 180; - player->pitch = pitch / 90.0f * 127.0f; - } // Exit early if no chunk borders were crossed if (dx == 0 && dz == 0) return; @@ -269,6 +319,8 @@ void handlePacket (int client_fd, int length, int packet_id) { } void disconnectClient (int *client_fd, int cause) { + if (*client_fd == -1) return; + client_count --; setClientState(*client_fd, STATE_NONE); clearPlayerFD(*client_fd); close(*client_fd); @@ -290,6 +342,7 @@ int main () { for (int i = 0; i < MAX_PLAYERS; i ++) { clients[i] = -1; client_states[i * 2] = -1; + player_data[i].client_fd = -1; } // Create socket @@ -350,6 +403,7 @@ int main () { printf("New client, fd: %d\n", clients[i]); int flags = fcntl(clients[i], F_GETFL, 0); fcntl(clients[i], F_SETFL, flags | O_NONBLOCK); + client_count ++; } break; } diff --git a/src/packets.c b/src/packets.c index 5628025..69fc20a 100644 --- a/src/packets.c +++ b/src/packets.c @@ -36,27 +36,28 @@ int cs_handshake (int client_fd) { } // C->S Login Start -// Leaves player name and UUID at indexes 0 and 17 of recv_buffer, respectively -int cs_loginStart (int client_fd) { +int cs_loginStart (int client_fd, uint8_t *uuid, char *name) { printf("Received Login Start:\n"); readString(client_fd); if (recv_count == -1) return 1; - printf(" Player name: %s\n", recv_buffer); - recv_count = recv_all(client_fd, recv_buffer + 17, 16, false); + memcpy(name, recv_buffer, strlen((char *)recv_buffer) + 1); + printf(" Player name: %s\n", name); + recv_count = recv_all(client_fd, recv_buffer, 16, false); if (recv_count == -1) return 1; + memcpy(uuid, recv_buffer, 16); printf(" Player UUID: "); - for (int i = 17; i < 33; i ++) printf("%x", recv_buffer[i]); + for (int i = 0; i < 16; i ++) printf("%x", uuid[i]); printf("\n\n"); return 0; } // S->C Login Success -int sc_loginSuccess (int client_fd, char *name, char *uuid) { +int sc_loginSuccess (int client_fd, uint8_t *uuid, char *name) { printf("Sending Login Success...\n\n"); - int name_length = strlen(name); + uint8_t name_length = strlen(name); writeVarInt(client_fd, 1 + 16 + sizeVarInt(name_length) + name_length + 1); writeVarInt(client_fd, 0x02); send(client_fd, uuid, 16, 0); @@ -649,6 +650,19 @@ int cs_setPlayerPosition (int client_fd, double *x, double *y, double *z) { } +// C->S Set Player Rotation +int cs_setPlayerRotation (int client_fd, float *yaw, float *pitch) { + + *yaw = readFloat(client_fd); + *pitch = readFloat(client_fd); + + // ignore flags + readByte(client_fd); + + return 0; + +} + // C->S Set Held Item (serverbound) int cs_setHeldItem (int client_fd) { @@ -691,6 +705,133 @@ int cs_closeContainer (int client_fd) { return 0; } +// S->C Player Info Update, "Add Player" action +int sc_playerInfoUpdateAddPlayer (int client_fd, PlayerData player) { + + writeVarInt(client_fd, 21 + strlen(player.name)); // Packet length + writeByte(client_fd, 0x3F); // Packet ID + + writeByte(client_fd, 0x01); // EnumSet: Add Player + writeByte(client_fd, 1); // Player count (1 per packet) + + // Player UUID + send(client_fd, player.uuid, 16, 0); + // Player name + writeByte(client_fd, strlen(player.name)); + send(client_fd, player.name, strlen(player.name), 0); + // Properties (don't send any) + writeByte(client_fd, 0); + + return 0; +} + +// S->C Spawn Entity +int sc_spawnEntity ( + int client_fd, + int id, uint8_t *uuid, int type, + double x, double y, double z, + uint8_t yaw, uint8_t pitch +) { + + writeVarInt(client_fd, 51 + sizeVarInt(id) + sizeVarInt(type)); + writeByte(client_fd, 0x01); + + writeVarInt(client_fd, id); // Entity ID + send(client_fd, uuid, 16, 0); // Entity UUID + writeVarInt(client_fd, type); // Entity type + + // Position + writeDouble(client_fd, x); + writeDouble(client_fd, y); + writeDouble(client_fd, z); + + // Angles + writeByte(client_fd, yaw); + writeByte(client_fd, pitch); + writeByte(client_fd, yaw); + + // Data - mostly unused + writeByte(client_fd, 0); + + // Velocity + writeUint16(client_fd, 0); + writeUint16(client_fd, 0); + writeUint16(client_fd, 0); + + return 0; +} + +// S->C Spawn Entity (from PlayerData) +int sc_spawnEntityPlayer (int client_fd, PlayerData player) { + return sc_spawnEntity( + client_fd, + player.client_fd, player.uuid, 149, + player.x > 0 ? (double)player.x + 0.5 : (double)player.x - 0.5, + player.y, + player.z > 0 ? (double)player.z + 0.5 : (float)player.z - 0.5, + player.yaw, player.pitch + ); +} + +// S->C Teleport Entity +int sc_teleportEntity ( + int client_fd, int id, + double x, double y, double z, + float yaw, float pitch +) { + + // Packet length and ID + writeVarInt(client_fd, 58 + sizeVarInt(id)); + writeByte(client_fd, 0x1F); + + // Entity ID + writeVarInt(client_fd, id); + // Position + writeDouble(client_fd, x); + writeDouble(client_fd, y); + writeDouble(client_fd, z); + // Velocity + writeUint64(client_fd, 0); + writeUint64(client_fd, 0); + writeUint64(client_fd, 0); + // Angles + writeFloat(client_fd, yaw); + writeFloat(client_fd, pitch); + // On ground flag + writeByte(client_fd, 1); + + return 0; +} + +int sc_setHeadRotation (int client_fd, int id, uint8_t yaw) { + + // Packet length and ID + writeByte(client_fd, 2 + sizeVarInt(id)); + writeByte(client_fd, 0x4C); + // Entity ID + writeVarInt(client_fd, id); + // Head yaw + writeByte(client_fd, yaw); + + return 0; +} + +int sc_updateEntityRotation (int client_fd, int id, uint8_t yaw, uint8_t pitch) { + + // Packet length and ID + writeByte(client_fd, 4 + sizeVarInt(id)); + writeByte(client_fd, 0x31); + // Entity ID + writeVarInt(client_fd, id); + // Angles + writeByte(client_fd, yaw); + writeByte(client_fd, pitch); + // "On ground" flag + writeByte(client_fd, 1); + + return 0; +} + // S->C Registry Data (multiple packets) and Update Tags (configuration, multiple packets) int sc_registries (int client_fd) { diff --git a/src/packets.h b/src/packets.h index 9f1dbc5..ca60182 100644 --- a/src/packets.h +++ b/src/packets.h @@ -2,18 +2,19 @@ #define H_PACKETS int cs_handshake (int client_fd); -int cs_loginStart (int client_fd); +int cs_loginStart (int client_fd, uint8_t *uuid, char *name); int cs_clientInformation (int client_fd); int cs_pluginMessage (int client_fd); int cs_playerAction (int client_fd); int cs_useItemOn (int client_fd); int cs_setPlayerPositionAndRotation (int client_fd, double *x, double *y, double *z, float *yaw, float *pitch); int cs_setPlayerPosition (int client_fd, double *x, double *y, double *z); +int cs_setPlayerRotation(int client_fd, float *yaw, float *pitch); int cs_setHeldItem (int client_fd); int cs_clickContainer (int client_fd); int cs_closeContainer (int client_fd); -int sc_loginSuccess (int client_fd, char *name, char *uuid); +int sc_loginSuccess (int client_fd, uint8_t *uuid, char *name); int sc_knownPacks (int client_fd); int sc_finishConfiguration (int client_fd); int sc_loginPlay (int client_fd); @@ -30,6 +31,12 @@ 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_acknowledgeBlockChange (int client_fd, int sequence); +int sc_playerInfoUpdateAddPlayer (int client_fd, PlayerData player); +int sc_spawnEntity (int client_fd, int id, uint8_t *uuid, int type, double x, double y, double z, double yaw, double pitch); +int sc_spawnEntityPlayer (int client_fd, PlayerData player); +int sc_teleportEntity (int client_fd, int id, double x, double y, double z, float yaw, float pitch); +int sc_setHeadRotation (int client_fd, int id, uint8_t yaw); +int sc_updateEntityRotation (int client_fd, int id, uint8_t yaw, uint8_t pitch); int sc_registries(int client_fd); #endif diff --git a/src/tools.c b/src/tools.c index f0d2b82..0d45fe9 100644 --- a/src/tools.c +++ b/src/tools.c @@ -195,11 +195,12 @@ int getClientIndex (int client_fd) { return -1; } -int reservePlayerData (int client_fd, char *uuid) { +int reservePlayerData (int client_fd, uint8_t *uuid, char *name) { for (int i = 0; i < MAX_PLAYERS; i ++) { if (memcmp(player_data[i].uuid, uuid, 16) == 0) { player_data[i].client_fd = client_fd; + memcpy(player_data[i].name, name, 16); return 0; } uint8_t empty = true; @@ -212,6 +213,7 @@ int reservePlayerData (int client_fd, char *uuid) { if (empty) { player_data[i].client_fd = client_fd; memcpy(player_data[i].uuid, uuid, 16); + memcpy(player_data[i].name, name, 16); player_data[i].y = -32767; return 0; } @@ -234,7 +236,7 @@ int getPlayerData (int client_fd, PlayerData **output) { void clearPlayerFD (int client_fd) { for (int i = 0; i < MAX_PLAYERS; i ++) { if (player_data[i].client_fd == client_fd) { - player_data[i].client_fd = 0; + player_data[i].client_fd = -1; return; } } diff --git a/src/tools.h b/src/tools.h index ae6c78e..03db642 100644 --- a/src/tools.h +++ b/src/tools.h @@ -34,7 +34,7 @@ void setClientState (int client_fd, int new_state); int getClientState (int client_fd); int getClientIndex (int client_fd); -int reservePlayerData (int client_fd, char *uuid); +int reservePlayerData (int client_fd, uint8_t *uuid, char* name); int getPlayerData (int client_fd, PlayerData **output); void clearPlayerFD (int client_fd); int givePlayerItem (PlayerData *player, uint16_t item, uint8_t count);