summaryrefslogtreecommitdiff
path: root/client/client.c
diff options
context:
space:
mode:
Diffstat (limited to 'client/client.c')
-rw-r--r--client/client.c257
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);
+}