diff options
Diffstat (limited to 'client/client.c')
| -rw-r--r-- | client/client.c | 257 |
1 files changed, 257 insertions, 0 deletions
diff --git a/client/client.c b/client/client.c new file mode 100644 index 0000000..13810f8 --- /dev/null +++ b/client/client.c @@ -0,0 +1,257 @@ +#include "queue.h" +#include "raylib.h" +#include "raymath.h" +#include "shared.h" +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#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); +} |