#include "queue.h" #include "raylib.h" #include "raymath.h" #include "shared.h" #include #include #include #include "client.h" PacketQueue incomingQueue; ClientPacket latestPacket; pthread_mutex_t packetLock = PTHREAD_MUTEX_INITIALIZER; double lastSendTime = 0; RemotePlayer remotePlayers[MAX_PLAYERS]; // add here ClientNPC clientNPCs[MAX_VIEW_NPCS]; #define PING_SAMPLES 20 double pingSamples[PING_SAMPLES] = {0}; int pingSampleIndex = 0; double pingMs = 0; void SendPing() { PingPacket ping = { .type = PACKET_PING, .sentTime = GetWallTime() }; sendto(networkFd, &ping, sizeof(ping), 0, (struct sockaddr *)&server, sizeof(server)); } float GetTerrainHeight(float x, float z) { Vector3 rayOrigin = { x, 99999.0f, z }; Ray ray = { rayOrigin, (Vector3){0, -1, 0} }; RayCollision hit = GetRayCollisionMesh(ray, terrainMesh, terrainTransform); if (hit.hit) return hit.point.y; return 0.0f; // fallback if miss } void PrintAddress(struct sockaddr_in *addr) { char ip[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &addr->sin_addr, ip, sizeof(ip)); printf("IP: %s, Port: %d\n", ip, ntohs(addr->sin_port)); } Vector3 ToRaylib(Vec3 v) { return (Vector3){ v.x, v.y, v.z }; } Vec3 FromRaylib(Vector3 v) { return (Vec3){ v.x, v.y, v.z }; } void NetworkInit() { net_init(); networkFd = socket(AF_INET, SOCK_DGRAM, 0); if (networkFd == -1) err(EXIT_FAILURE, "socket failed"); server.sin_family = AF_INET; server.sin_port = htons(8080); inet_pton(AF_INET, "10.148.10.109", &server.sin_addr); } void NetworkShutdown() { close(networkFd); net_cleanup(); } void *RecvThread(void *arg) { uint8_t buffer[PACKET_MAX_SIZE]; struct sockaddr_in from; socklen_t fromLen = sizeof(from); while (1) { int size = recvfrom(networkFd, (char *)buffer, sizeof(buffer), 0, (struct sockaddr *)&from, &fromLen); if (size <= 0) continue; PacketType type = *(PacketType *)buffer; // handle pong immediately if (type == PACKET_PONG) { PingPacket *pong = (PingPacket *)buffer; double sample = (GetWallTime() - pong->sentTime) * 1000.0; // update rolling average pingSamples[pingSampleIndex % PING_SAMPLES] = sample; pingSampleIndex++; double total = 0; int count = pingSampleIndex < PING_SAMPLES ? pingSampleIndex : PING_SAMPLES; for (int i = 0; i < count; i++) total += pingSamples[i]; pingMs = total / count; continue; } QueuePush(&incomingQueue, buffer, size); } return NULL; } // send thread - fires every 50ms void *SendThread(void *arg) { double accumulator = 0; double previousTime = GetWallTime(); while (1) { double currentTime = GetWallTime(); double elapsed = currentTime - previousTime; previousTime = currentTime; accumulator += elapsed; while (accumulator >= NETWORK_RATE) { pthread_mutex_lock(&packetLock); lastSendTime = GetWallTime(); sendto(networkFd, (const char *)&latestPacket, sizeof(latestPacket), 0, (struct sockaddr *)&server, sizeof(server)); pthread_mutex_unlock(&packetLock); accumulator -= NETWORK_RATE; } sleep_us(1000); } return NULL; } // main thread processes incoming queue each frame void ProcessIncoming() { uint8_t buffer[PACKET_MAX_SIZE]; int size; while (QueuePop(&incomingQueue, buffer, &size)) { PacketType type = *(PacketType *)buffer; switch (type) { case PACKET_SERVER_SNAPSHOT: HandleSnapshot((ServerSnapshot *)buffer); break; case PACKET_PLAYER_JOINED: HandlePlayerJoined((PlayerJoinedPacket *)buffer); break; case PACKET_PLAYER_LEFT: HandlePlayerLeft((PlayerJoinedPacket *)buffer); break; case PACKET_HANDSHAKE_RESPONSE: HandleHandshakeResponse((HandshakeResponsePacket *)buffer); break; } } } void HandleSnapshot(ServerSnapshot *snapshot) { for (int i = 0; i < snapshot->playerCount; i++) { int id = snapshot->players[i].id; if (id == player.id) { ReconcilePlayer(&player, ToRaylib(snapshot->players[i].position)); } else { remotePlayers[id].active = true; remotePlayers[id].serverPosition = ToRaylib(snapshot->players[i].position); remotePlayers[id].serverYaw = snapshot->players[i].yaw; remotePlayers[id].serverVelocityY = snapshot->players[i].velocity.y; remotePlayers[id].serverOnGround = snapshot->players[i].onGround; // add this } } for (int i = 0; i < snapshot->npcCount; i++) { clientNPCs[i].id = snapshot->npcs[i].id; clientNPCs[i].active = true; clientNPCs[i].serverPosition = ToRaylib(snapshot->npcs[i].position); clientNPCs[i].serverRotation = snapshot->npcs[i].rotation; clientNPCs[i].health = snapshot->npcs[i].health; clientNPCs[i].state = snapshot->npcs[i].state; clientNPCs[i].model = snapshot->npcs[i].model; clientNPCs[i].scale = (Vector3){snapshot->npcs[i].scale, snapshot->npcs[i].scale, snapshot->npcs[i].scale}; } } void HandlePlayerJoined(PlayerJoinedPacket *packet) { int id = packet->playerId; remotePlayers[id].active = true; remotePlayers[id].id = id; // set initial position so they don't lerp from 0,0,0 remotePlayers[id].currentPosition = ToRaylib(packet->position); remotePlayers[id].serverPosition = ToRaylib(packet->position); remotePlayers[id].serverYaw = 0; remotePlayers[id].currentYaw = 0; printf("remote character model %d\n", packet->characterClass); remotePlayers[id].characterClass = packet->characterClass; // load correct model based on their class switch (packet->characterClass) { case 0: remotePlayers[id].model = LoadModel("assets/dungeon/Models/GLB format/character-orc-f.glb"); break; case 1: remotePlayers[id].model = LoadModel("assets/dungeon/Models/GLB format/character-human.glb"); break; case 2: remotePlayers[id].model = LoadModel("assets/arena/Models/GLB format/character-soldier.glb"); break; } TraceLog(LOG_INFO, "Player %d joined", id); } void HandlePlayerLeft(PlayerJoinedPacket *packet) { int id = packet->playerId; remotePlayers[id].active = false; // unload their model to free memory /* UnloadModel(remotePlayers[id].model); */ TraceLog(LOG_INFO, "Player %d left", id); } void HandleHandshakeResponse(HandshakeResponsePacket *packet) { player.id = packet->playerId; latestPacket.playerId = packet->playerId; printf("assigned player id: %d\n", player.id); scene = 1; DisableCursor(); } void ReconcilePlayer(Player *p, Vector3 serverPosition) { p->position.x = Lerp(p->position.x, serverPosition.x, 0.3f); p->position.z = Lerp(p->position.z, serverPosition.z, 0.3f); } void InitRemotePlayers() { for (int i = 0; i < MAX_PLAYERS; i++) { remotePlayers[i].active = false; remotePlayers[i].currentPosition = (Vector3){600, GetTerrainHeight(600,-1200), -1200}; remotePlayers[i].serverPosition = (Vector3){600, GetTerrainHeight(600,-1200), -1200}; remotePlayers[i].currentYaw = 0; remotePlayers[i].serverYaw = 0; } } // client.c void UpdateRemotePlayer(RemotePlayer *p, float dt) { p->currentPosition.x = Lerp(p->currentPosition.x, p->serverPosition.x, 25.0f * dt); p->currentPosition.z = Lerp(p->currentPosition.z, p->serverPosition.z, 25.0f * dt); if (p->serverOnGround) { // on ground - snap Y to terrain float localY = GetTerrainHeight(p->currentPosition.x, p->currentPosition.z); if (localY != 0.0f) p->currentPosition.y = localY; } else { // in air - lerp Y toward server position for smooth arc p->currentPosition.y = Lerp(p->currentPosition.y, p->serverPosition.y, 15.0f * dt); } p->currentYaw = Lerp(p->currentYaw, p->serverYaw, 25.0f * dt); }