summaryrefslogtreecommitdiff
path: root/client/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'client/main.c')
-rw-r--r--client/main.c843
1 files changed, 812 insertions, 31 deletions
diff --git a/client/main.c b/client/main.c
index c0ed27e..dd079b0 100644
--- a/client/main.c
+++ b/client/main.c
@@ -1,63 +1,844 @@
#include "raylib.h"
+#include "raymath.h"
+#include "rlgl.h"
+#include <math.h>
#include <stdio.h>
+#include <time.h>
+#include "shared.h"
+#include "client.h"
#include "net/net.h"
+#define FPS_SAMPLES 120
+double fpsSamples[FPS_SAMPLES] = {0};
+int fpsSampleIndex = 0;
+double avgFps = 0;
+
+#define HUD_FPS_X 10
+#define HUD_PING_X 120
+#define HUD_COORDS_X 300
+
+typedef struct {
+ Vector3 position;
+ Vector3 spawnPosition; // remember where they started
+ Vector3 target; // where they're walking to
+ float rotation;
+ float scale;
+ float moveTimer; // how long to walk
+ float waitTimer; // how long to wait
+ bool isMoving;
+} NPC;
+
+#define MAX_PARTICLES 15
+
+typedef struct {
+ Vector3 position;
+ Vector3 velocity;
+ float life;
+ float size;
+} FireParticle;
+
+FireParticle particles[MAX_PARTICLES];
+
+Vector3 firePos = {0.0f, -0.10f, 1.5f};
+
+int selectedChar = -1;
+
+void InitParticles() {
+ for (int i = 0; i < MAX_PARTICLES; i++) {
+ particles[i].life = 0;
+ }
+}
+
+void UpdateParticles(float dt) {
+ for (int i = 0; i < MAX_PARTICLES; i++) {
+
+ if (particles[i].life <= 0) {
+ // Respawn at fire base
+ particles[i].position = (Vector3){
+ firePos.x + ((float)GetRandomValue(-10,10)/100.0f),
+ firePos.y + 0.1f,
+ firePos.z + ((float)GetRandomValue(-10,10)/100.0f)
+ };
+
+ particles[i].velocity = (Vector3){
+ ((float)GetRandomValue(-5,5)/100.0f), // slight sideways drift
+ 0.6f + (float)GetRandomValue(0,40)/100.0f,
+ ((float)GetRandomValue(-5,5)/100.0f)
+ };
+
+ particles[i].life = 0.6f + (float)GetRandomValue(0,40)/100.0f;
+ particles[i].size = 0.15f + (float)GetRandomValue(0,1)/100.0f;
+ }
+
+ // Movement
+ particles[i].position.x += particles[i].velocity.x * dt;
+ particles[i].position.y += particles[i].velocity.y * dt;
+ particles[i].position.z += particles[i].velocity.z * dt;
+
+ // subtle swirl (makes flame feel alive)
+ particles[i].position.x += sinf(GetTime()*5 + i) * 0.002f;
+ particles[i].position.z += cosf(GetTime()*5 + i) * 0.002f;
+
+ // Fade
+ particles[i].life -= dt;
+
+ // Clamp height (keeps fire tight)
+ if (particles[i].position.y > firePos.y + 0.5f) {
+ particles[i].life = 0;
+ }
+ }
+}
+
+void DrawParticles() {
+ for (int i = 0; i < MAX_PARTICLES; i++) {
+ if (particles[i].life > 0) {
+
+ float t = particles[i].life;
+
+ // Color gradient (yellow → orange → red)
+ Color color;
+ if (t > 0.5f) {
+ color = (Color){255, 200, 50, (unsigned char)(255 * t)};
+ } else if (t > 0.25f) {
+ color = (Color){255, 120, 20, (unsigned char)(255 * t)};
+ } else {
+ color = (Color){200, 40, 10, (unsigned char)(255 * t)};
+ }
+
+ float size = particles[i].size * t;
+
+ DrawCubeV(particles[i].position,
+ (Vector3){size, size, size},
+ color);
+ }
+ }
+}
+
+int BuildInputMask() {
+ int inputs = 0;
+ if (IsKeyDown(KEY_W)) inputs |= INPUT_FORWARD;
+ if (IsKeyDown(KEY_S)) inputs |= INPUT_BACK;
+ if (IsKeyDown(KEY_A)) inputs |= INPUT_LEFT;
+ if (IsKeyDown(KEY_D)) inputs |= INPUT_RIGHT;
+ if (IsKeyDown(KEY_SPACE)) inputs |= INPUT_JUMP;
+ if (IsKeyDown(KEY_LEFT_SHIFT)) inputs |= INPUT_SPRINT;
+ return inputs;
+}
+
+void UpdateNPC(NPC *npc, Mesh terrainMesh, Matrix terrainTransform, float dt) {
+ if (npc->isMoving) {
+ // walk toward target
+ Vector3 dir = Vector3Subtract(npc->target, npc->position);
+ dir.y = 0;
+ float dist = Vector3Length(dir);
+
+ if (dist > 1.0f) {
+ dir = Vector3Normalize(dir);
+ npc->position.x += dir.x * 20.0f * dt; // npc walk speed
+ npc->position.z += dir.z * 20.0f * dt;
+
+ // snap to terrain height
+ npc->position.y = GetTerrainHeight(
+ npc->position.x, npc->position.z);
+
+ // face direction of travel
+ npc->rotation = atan2f(dir.x, dir.z) * RAD2DEG;
+ }
+
+ npc->moveTimer -= dt;
+ if (npc->moveTimer <= 0.0f || dist <= 1.0f) {
+ // stop walking, start waiting
+ npc->isMoving = false;
+ npc->waitTimer = (float)GetRandomValue(200, 500) / 100.0f; // wait 2-5 sec
+ }
+
+ } else {
+ // waiting
+ npc->waitTimer -= dt;
+ if (npc->waitTimer <= 0.0f) {
+ // pick a new random target near spawn
+ float range = 100.0f; // how far they wander from spawn
+ float tx = npc->spawnPosition.x + (float)GetRandomValue(-range, range);
+ float tz = npc->spawnPosition.z + (float)GetRandomValue(-range, range);
+ npc->target = (Vector3){tx, 0, tz};
+ npc->isMoving = true;
+ npc->moveTimer = (float)GetRandomValue(300, 800) / 100.0f; // walk 3-8 sec
+ }
+ }
+}
+
+void UpdatePlayer(Player *p, Camera3D *cam, Mesh terrainMesh, Matrix terrainTransform, float dt) {
+
+ Vector2 mouseDelta = GetMouseDelta();
+ p->yaw -= mouseDelta.x * 0.003f;
+ p->pitch -= mouseDelta.y * 0.003f;
+ p->pitch = Clamp(p->pitch, -1.4f, 1.4f); // prevent flipping
+
+ // --- Movement direction (relative to yaw) ---
+ Vector3 forward = {
+ sinf(p->yaw),
+ 0,
+ cosf(p->yaw)
+ };
+ Vector3 right = {
+ cosf(p->yaw),
+ 0,
+ -sinf(p->yaw)
+ };
+
+
+ // --- Jump ---
+ if (IsKeyPressed(KEY_SPACE) && p->onGround) {
+ p->velocity.y = 500.0f;
+ p->onGround = false;
+ }
+
+ Vector3 moveDir = {0};
+ if (IsKeyDown(KEY_W)) moveDir = Vector3Add(moveDir, forward);
+ if (IsKeyDown(KEY_S)) moveDir = Vector3Subtract(moveDir, forward);
+ if (IsKeyDown(KEY_D)) moveDir = Vector3Subtract(moveDir, right);
+ if (IsKeyDown(KEY_A)) moveDir = Vector3Add(moveDir, right);
+
+ if (IsKeyDown(KEY_LEFT_SHIFT)) {
+ if (p->onGround) {
+ p->sprint = 5;
+ } else {
+ p->sprint = 10;
+ }
+ } else {
+ p->sprint = 1;
+ }
+
+ // normalize so diagonals aren't faster
+ if (Vector3Length(moveDir) > 0)
+ moveDir = Vector3Normalize(moveDir);
+
+ p->position.x += moveDir.x * p->speed * p-> sprint * dt;
+ p->position.z += moveDir.z * p->speed * p-> sprint * dt;
+
+ // --- Gravity ---
+ if (!p->onGround) p->velocity.y -= 300.0f * dt;
+ p->position.y += p->velocity.y * dt;
+
+ // --- Terrain Collision (downward ray) ---
+ Vector3 rayOrigin = { p->position.x, p->position.y + 2000.0f, p->position.z };
+ Ray downRay = { rayOrigin, (Vector3){0, -1, 0} };
+ RayCollision hit = GetRayCollisionMesh(downRay, terrainMesh, terrainTransform);
+
+ if (hit.hit) {
+ float groundY = hit.point.y;
+
+ if (p->position.y <= groundY + 0.5f) {
+ // snap to ground
+ p->position.y = groundY;
+ p->velocity.y = 0;
+ p->onGround = true;
+ } else {
+ p->onGround = false;
+ }
+ } else {
+ p->onGround = false;
+ }
+
+
+ // --- Attach Camera to Player Head ---
+ Vector3 headPos = { p->position.x, p->position.y + p->height, p->position.z };
+ cam->position = headPos;
+
+ // camera looks in yaw+pitch direction
+ cam->target = (Vector3){
+ headPos.x + sinf(p->yaw) * cosf(p->pitch),
+ headPos.y + sinf(p->pitch),
+ headPos.z + cosf(p->yaw) * cosf(p->pitch)
+ };
+}
+
+Player player = {.position = (Vector3){600, 3000.0f, -1200},
+ .velocity = {0},
+ .yaw = 0.0f,
+ .pitch = 0.0f,
+ .height = 5.0f,
+ .speed = 30.0f,
+ .sprint = 1.0f,
+ .onGround = false};
+
+Mesh terrainMesh;
+Matrix terrainTransform;
+int scene = 0;
+
int main() {
- const int screenWidth = 800;
- const int screenHeight = 450;
+ SetConfigFlags(FLAG_VSYNC_HINT);
+ InitWindow(0, 0, "Pants - MMO");
+ SetTargetFPS(0);
+ SetRandomSeed(1234567890);
+ Model terrain = LoadModel("assets/terrain.glb");
- InitWindow(screenWidth, screenHeight, "Pants - MMO");
+ if (terrain.meshCount == 0) {
+ TraceLog(LOG_ERROR, "Terrain failed to load!");
+ }
- SetTargetFPS(60);
+// Bake scale into vertices so rendering and collision match
+ float scaleXZ = 10.0f;
+ float scaleY = 10.0f;
+ for (int i = 0; i < terrain.meshes[0].vertexCount; i++) {
+ terrain.meshes[0].vertices[i * 3 + 0] *= scaleXZ;
+ terrain.meshes[0].vertices[i * 3 + 1] *= scaleY;
+ terrain.meshes[0].vertices[i * 3 + 2] *= scaleXZ;
+ }
+ UpdateMeshBuffer(terrain.meshes[0], 0, terrain.meshes[0].vertices,
+ terrain.meshes[0].vertexCount * 3 * sizeof(float), 0);
- char* message = "Welcome to Pants a game by Matthew & Devyn Challman";
- int fontSize = 25;
+ terrain.transform = MatrixIdentity();
+ terrainMesh = terrain.meshes[0]; // first mesh in the model
+ terrainTransform = terrain.transform; // world transform of the model
+
+ InitRemotePlayers();
+
+ NetworkInit();
+ QueueInit(&incomingQueue);
+
+ pthread_t recvThread, sendThread;
+ int r1 = pthread_create(&recvThread, NULL, RecvThread, NULL);
+ int r2 = pthread_create(&sendThread, NULL, SendThread, NULL);
+
+ int fontSize = 25;
+ char* message = "Welcome to Pants a game by Matthew & Devyn Challman";
int messageWidth = MeasureText(message, fontSize);
- int messagePosX = (screenWidth - messageWidth) / 2;
- int messagePosY = (screenHeight - fontSize) / 2;
+ int messagePosX = (GetScreenWidth() - messageWidth) / 2;
+ int messagePosY = (GetScreenHeight() - fontSize) / 2 - 200;
+
+ char* subMessage = "Select a character to connect";
+ int subMessageWidth = MeasureText(subMessage, fontSize);
+ int subMessagePosX = (GetScreenWidth() - subMessageWidth) / 2;
+ int subMessagePosY = (GetScreenHeight() - fontSize) / 2 - 150;
int buttonW = 150;
int buttonH = 50;
- int buttonPosX = (screenWidth - buttonW) / 2;
- int buttonPosY = (screenHeight - buttonH) / 2 + 100;
+ int buttonPosX = (GetScreenWidth() - buttonW) / 2;
+ int buttonPosY = (GetScreenHeight() - buttonH) / 2 + 100;
char *connect = "Connect";
int connectWidth = MeasureText(connect, fontSize);
- int connectPosX = (screenWidth - connectWidth) / 2;
- int connectPosY = (screenHeight - fontSize) / 2 + 100;
+ int connectPosX = (GetScreenWidth() - connectWidth) / 2;
+ int connectPosY = (GetScreenHeight() - fontSize) / 2 + 100;
+
+ Camera3D camera = {0};
+ camera.up = (Vector3){0, 1, 0};
+ camera.fovy = 70.0f;
+ camera.projection = CAMERA_PERSPECTIVE;
+
+ Camera3D startCamera = {0};
+
+
+ startCamera.position = (Vector3){ 0.0f, 1.5f, -1.0f };
+ startCamera.target = (Vector3){ 0.0f, -0.5f, 10.0f }; // look toward your model
+ startCamera.up = (Vector3){ 0.0f, 1.0f, 0.0f };
+ startCamera.fovy = 70.0f;
+ startCamera.projection = CAMERA_PERSPECTIVE;
+
+ Model npcModels[47];
+ char *paths[47];
+
+ paths[0] = "assets/characters/Models/GLB format/character-a.glb";
+ paths[1] = "assets/characters/Models/GLB format/character-b.glb";
+ paths[2] = "assets/characters/Models/GLB format/character-c.glb";
+ paths[3] = "assets/characters/Models/GLB format/character-d.glb";
+ paths[4] = "assets/characters/Models/GLB format/character-e.glb";
+ paths[5] = "assets/characters/Models/GLB format/character-f.glb";
+ paths[6] = "assets/characters/Models/GLB format/character-g.glb";
+ paths[7] = "assets/characters/Models/GLB format/character-h.glb";
+ paths[8] = "assets/characters/Models/GLB format/character-i.glb";
+ paths[9] = "assets/characters/Models/GLB format/character-j.glb";
+ paths[10] = "assets/characters/Models/GLB format/character-k.glb";
+ paths[11] = "assets/characters/Models/GLB format/character-l.glb";
+ paths[12] = "assets/characters/Models/GLB format/character-m.glb";
+ paths[13] = "assets/characters/Models/GLB format/character-n.glb";
+ paths[14] = "assets/characters/Models/GLB format/character-o.glb";
+ paths[15] = "assets/characters/Models/GLB format/character-p.glb";
+ paths[16] = "assets/characters/Models/GLB format/character-q.glb";
+ paths[17] = "assets/characters/Models/GLB format/character-r.glb";
+ paths[18] = "assets/graveyard/Models/GLB format/character-ghost.glb";
+ paths[19] = "assets/graveyard/Models/GLB format/character-skeleton.glb";
+ paths[20] = "assets/graveyard/Models/GLB format/character-zombie.glb";
+ paths[21] = "assets/graveyard/Models/GLB format/character-keeper.glb";
+ paths[22] = "assets/graveyard/Models/GLB format/character-vampire.glb";
+ paths[23] = "assets/pets/Models/GLB format/animal-beaver.glb";
+ paths[24] = "assets/pets/Models/GLB format/animal-caterpillar.glb";
+ paths[25] = "assets/pets/Models/GLB format/animal-deer.glb";
+ paths[26] = "assets/pets/Models/GLB format/animal-fox.glb";
+ paths[27] = "assets/pets/Models/GLB format/animal-lion.glb";
+ paths[28] = "assets/pets/Models/GLB format/animal-penguin.glb";
+ paths[29] = "assets/pets/Models/GLB format/animal-bee.glb";
+ paths[30] = "assets/pets/Models/GLB format/animal-chick.glb";
+ paths[31] = "assets/pets/Models/GLB format/animal-dog.glb";
+ paths[32] = "assets/pets/Models/GLB format/animal-giraffe.glb";
+ paths[33] = "assets/pets/Models/GLB format/animal-monkey.glb";
+ paths[34] = "assets/pets/Models/GLB format/animal-pig.glb";
+ paths[35] = "assets/pets/Models/GLB format/animal-bunny.glb";
+ paths[36] = "assets/pets/Models/GLB format/animal-cow.glb";
+ paths[37] = "assets/pets/Models/GLB format/animal-elephant.glb";
+ paths[38] = "assets/pets/Models/GLB format/animal-hog.glb";
+ paths[39] = "assets/pets/Models/GLB format/animal-panda.glb";
+ paths[40] = "assets/pets/Models/GLB format/animal-polar.glb";
+ paths[41] = "assets/pets/Models/GLB format/animal-cat.glb";
+ paths[42] = "assets/pets/Models/GLB format/animal-crab.glb";
+ paths[43] = "assets/pets/Models/GLB format/animal-fish.glb";
+ paths[44] = "assets/pets/Models/GLB format/animal-koala.glb";
+ paths[45] = "assets/pets/Models/GLB format/animal-parrot.glb";
+ paths[46] = "assets/pets/Models/GLB format/animal-tiger.glb";
+
+ for (int i = 0; i < 47; ++i) {
+ npcModels[i] = LoadModel(paths[i]);
+ }
+
+ Vector3 npcPositions[1000];
+
+ float npcRotations[1000];
+
+ for (int i = 0; i < 1000; ++i) {
+ float npcX = (float)GetRandomValue(-500, 500);
+ float npcZ = (float)GetRandomValue(-500, 500);
+ float npcY = GetTerrainHeight(npcX, npcZ);
+ npcPositions[i] = (Vector3){ npcX, npcY, npcZ };
+ npcRotations[i] = (float)GetRandomValue(0, 360); // random degrees
+ }
+
+ Vector3 petPositions[1000];
+
+ float petRotations[1000];
+
+ for (int i = 0; i < 1000; ++i) {
+ float npcX = (float)GetRandomValue(-500, 500);
+ float npcZ = (float)GetRandomValue(-500, 500);
+ float npcY = GetTerrainHeight(npcX, npcZ);
+ petPositions[i] = (Vector3){ npcX, npcY, npcZ };
+ petRotations[i] = (float)GetRandomValue(0, 360); // random degrees
+ }
+
+
+ Model treeModels[5];
+ char *treePaths[5];
+
+ treePaths[0] = "assets/world/Models/GLB format/tree-crooked.glb";
+ treePaths[1] = "assets/world/Models/GLB format/tree-high-crooked.glb";
+ treePaths[2] = "assets/world/Models/GLB format/tree-high-round.glb";
+ treePaths[3] = "assets/world/Models/GLB format/tree-high.glb";
+ treePaths[4] = "assets/world/Models/GLB format/tree.glb";
+
+ Vector3 treePositions[1000];
+
+ float treeRotations[1000];
+
+ float treeScales[1000];
+
+ for (int i = 0; i < 1000; ++i) {
+ float npcX = (float)GetRandomValue(-500, 500);
+ float npcZ = (float)GetRandomValue(-500, 500);
+ float npcY = GetTerrainHeight(npcX, npcZ);
+ treePositions[i] = (Vector3){ npcX, npcY - 1, npcZ };
+ treeRotations[i] = (float)GetRandomValue(0, 360); // random degrees
+ treeScales[i] = (float)GetRandomValue(10, 400) / 10.0f; // 0.8 to 1.5
+ }
+
+
+ for (int i = 0; i < 5; ++i) {
+ treeModels[i] = LoadModel(treePaths[i]);
+ }
+
+ Model rockModels[3];
+ char *rockPaths[3];
+
+ rockPaths[0] = "assets/world/Models/GLB format/rock-large.glb";
+ rockPaths[1] = "assets/world/Models/GLB format/rock-small.glb";
+ rockPaths[2] = "assets/world/Models/GLB format/rock-wide.glb";
+
+ Vector3 rockPositions[100];
+
+ float rockRotations[100];
+
+ float rockScales[100];
+
+ for (int i = 0; i < 100; ++i) {
+ float npcX = (float)GetRandomValue(-500, 500);
+ float npcZ = (float)GetRandomValue(-500, 500);
+ float npcY = GetTerrainHeight(npcX, npcZ);
+ rockPositions[i] = (Vector3){ npcX, npcY - 5, npcZ };
+ rockRotations[i] = (float)GetRandomValue(0, 360); // random degrees
+ rockScales[i] = (float)GetRandomValue(10, 200) / 10.0f; // 0.8 to 1.5
+ }
+
+
+ for (int i = 0; i < 3; ++i) {
+ rockModels[i] = LoadModel(rockPaths[i]);
+ }
+
+ Model houseModels[2];
+ char *housePaths[2];
+
+ housePaths[0] = "assets/house.glb";
+ housePaths[1] = "assets/house2.glb";
+
+ Vector3 housePositions[20];
+
+ float houseRotations[20];
+
+ float houseScales[20];
+
+ for (int i = 0; i < 20; ++i) {
+ float npcX = (float)GetRandomValue(600, 1200);
+ float npcZ = (float)GetRandomValue(-1300, -2000);
+ float npcY = GetTerrainHeight(npcX, npcZ);
+ housePositions[i] = (Vector3){ npcX, npcY - 1, npcZ };
+ houseRotations[i] = (float)GetRandomValue(0, 360); // random degrees
+ houseScales[i] = (float)GetRandomValue(100, 200) / 10.0f; // 0.8 to 1.5
+ }
+
+ for (int i = 0; i < 2; ++i) {
+ houseModels[i] = LoadModel(housePaths[i]);
+ }
+
+ Model playerModel =
+ LoadModel("assets/dungeon/Models/GLB format/character-human.glb");
+
+ int animCount;
+ ModelAnimation *anims = LoadModelAnimations(
+ "assets/arena/Models/GLB format/character-soldier.glb", &animCount);
+
+ Model orcModel =
+ LoadModel("assets/dungeon/Models/GLB format/character-orc-f.glb");
+
+ Model campFireModel =
+ LoadModel("assets/survival/Models/GLB format/campfire-pit.glb");
+
+ Model soldierModel =
+ LoadModel("assets/arena/Models/GLB format/character-soldier.glb");
+
+ if (animCount > 0) {
+ printf("This GLB has %d animation(s)\n", animCount);
+ } else {
+ printf("No animations found\n");
+ }
+
+ for (int i = 0; i < animCount; i++) {
+ printf("Animation %d: %s, frames: %d\n", i, anims[i].name, anims[i].frameCount);
+ }
+
+// Let's assume idle is animation index 1
+ int orcIdleAnim = 7;
+ int playerIdleAnim = 7;
+ int soldierIdleAnim = 7;
+ float orcFrame = 0.0f;
+ float playerFrame = 0.0f;
+ float soldierFrame = 0.0f;
while (!WindowShouldClose()) {
+ if (scene == 0) {
+ float dt = GetFrameTime();
+
+ float animSpeed = 24.0f; // frames per second of the idle animation
+ orcFrame += animSpeed * dt;
+ playerFrame += animSpeed * dt;
+ soldierFrame += animSpeed * dt;
+
+ Ray ray = GetMouseRay(GetMousePosition(), startCamera);
+
+ BoundingBox playerBox = GetModelBoundingBox(playerModel);
+ playerBox.min = Vector3Add(playerBox.min, (Vector3){0.0, 0, 3});
+ playerBox.max = Vector3Add(playerBox.max, (Vector3){0.0, 0, 3});
+
+ RayCollision hitPlayer = GetRayCollisionBox(ray, playerBox);
+
+ BoundingBox soldierBox = GetModelBoundingBox(soldierModel);
+ soldierBox.min = Vector3Add(soldierBox.min, (Vector3){-1.25, 0, 2.5});
+ soldierBox.max = Vector3Add(soldierBox.max, (Vector3){-1.25, 0, 2.5});
- bool connecting = false;
- if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
- struct Vector2 mousePos = GetMousePosition();
+ RayCollision hitSoldier = GetRayCollisionBox(ray, soldierBox);
- if (mousePos.x >= buttonPosX && mousePos.x <= buttonPosX + buttonW &&
- mousePos.y >= buttonPosY && mousePos.y <= buttonPosY + buttonH) {
+ BoundingBox orcBox = GetModelBoundingBox(orcModel);
+ orcBox.min = Vector3Add(orcBox.min, (Vector3){1.25, 0, 2.5});
+ orcBox.max = Vector3Add(orcBox.max, (Vector3){1.25, 0, 2.5});
- connecting = true;
+ RayCollision hitOrc = GetRayCollisionBox(ray, orcBox);
+
+ int hitIndex = -1;
+
+ if (hitOrc.hit) hitIndex = 0;
+ if (hitPlayer.hit) hitIndex = 1;
+ if (hitSoldier.hit) hitIndex = 2;
+
+ if (hitOrc.hit || hitPlayer.hit || hitSoldier.hit) {
+ SetMouseCursor(MOUSE_CURSOR_POINTING_HAND);
+ } else {
+ SetMouseCursor(MOUSE_CURSOR_DEFAULT);
}
- }
- BeginDrawing();
- ClearBackground(RAYWHITE);
- DrawText(message, messagePosX, messagePosY, fontSize, LIGHTGRAY);
+ if (hitIndex > -1 && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {
+ pthread_mutex_lock(&packetLock);
+ latestPacket.inputs = BuildInputMask();
+ latestPacket.yaw = player.yaw;
+ latestPacket.type = PACKET_HANDSHAKE;
+ latestPacket.playerId = player.id;
+ latestPacket.characterClass = hitIndex;
+ pthread_mutex_unlock(&packetLock);
+ if (hitIndex == 0) player.height = 7.0f;
+ }
+
+ if (hitOrc.hit) {
+ orcIdleAnim = 19;
+ } else {
+ orcIdleAnim = 7;
+ }
+
+ if (hitPlayer.hit) {
+ playerIdleAnim = 19;
+ } else {
+ playerIdleAnim = 7;
+ }
+
+ if (hitSoldier.hit) {
+ soldierIdleAnim = 19;
+ } else {
+ soldierIdleAnim = 7;
+ }
+
+ // Loop the animation
+
+ if (orcFrame >= anims[orcIdleAnim].frameCount) orcFrame = 0.0f;
+ if (playerFrame >= anims[playerIdleAnim].frameCount) playerFrame = 0.0f;
+ if (soldierFrame >= anims[soldierIdleAnim].frameCount) soldierFrame = 0.0f;
+
+ UpdateModelAnimation(playerModel, anims[playerIdleAnim], playerFrame);
+ UpdateModelAnimation(orcModel, anims[orcIdleAnim], orcFrame);
+ UpdateModelAnimation(soldierModel, anims[soldierIdleAnim], soldierFrame);
+ UpdateParticles(dt);
+
+ BeginDrawing();
+ ClearBackground(RAYWHITE);
+
+ BeginMode3D(startCamera);
+
+ DrawGrid(20, 1.0f);
+
+ DrawModelEx(orcModel, (Vector3){1.25, 0, 2.5}, (Vector3){0, 1, 0}, 200,
+ (Vector3){1.5, 1.5, 1.5}, WHITE);
+
+ if (hitOrc.hit) {
+ BeginBlendMode(BLEND_ADDITIVE);
+ DrawCylinder((Vector3){1.25f, 0.0f, 2.5f},
+ 0.6f, 0.6f, 4.0f, 32,
+ (Color){255, 120, 30, 30});
+ DrawCylinder((Vector3){1.25f, 0.0f, 2.5f}, 0.4f, 0.4f, 4.0f, 32,
+ (Color){255, 160, 60, 80});
+ EndBlendMode();
+ }
+
+ DrawModelEx(playerModel, (Vector3){0.0, 0, 3}, (Vector3){0, 1, 0}, 180,
+ (Vector3){1, 1, 1}, WHITE);
+
+ if (hitPlayer.hit) {
+ BeginBlendMode(BLEND_ADDITIVE);
+ DrawCylinder((Vector3){0.0f, 0.0f, 3.0f},
+ 0.5f, 0.5f, 4.0f, 32,
+ (Color){255, 120, 30, 30});
+ DrawCylinder((Vector3){0.0f, 0.0f, 3.0f}, 0.3f, 0.3f, 4.0f, 32,
+ (Color){255, 160, 60, 80});
+ EndBlendMode();
+ }
+
+
+ DrawModelEx(soldierModel, (Vector3){-1.15, 0, 2.5}, (Vector3){0, 1, 0},
+ 160, (Vector3){1, 1, 1}, WHITE);
+
+ if (hitSoldier.hit) {
+ BeginBlendMode(BLEND_ADDITIVE);
+ DrawCylinder((Vector3){-1.15f, 0.0f, 2.5f},
+ 0.5f, 0.5f, 4.0f, 32,
+ (Color){255, 120, 30, 30});
+ DrawCylinder((Vector3){-1.15f, 0.0f, 2.5f}, 0.3f, 0.3f, 4.0f, 32,
+ (Color){255, 160, 60, 80});
+ EndBlendMode();
+ }
+
+ DrawModelEx(campFireModel, (Vector3){0.0, -0.10, 1.5},
+ (Vector3){0, 1, 0},
+ 180, (Vector3){1.5, 1.5, 1.5}, WHITE);
+
+ DrawParticles();
+
+ EndMode3D();
+
+ DrawText(message, messagePosX, messagePosY, fontSize, BLACK);
+ DrawText(subMessage, subMessagePosX, subMessagePosY, fontSize, BLACK);
+
+ EndDrawing();
+
+ ProcessIncoming();
+ } else {
+ float dt = GetFrameTime();
+ static double lastPingTime = 0;
+ fpsSamples[fpsSampleIndex % FPS_SAMPLES] = 1.0 / dt;
+ fpsSampleIndex++;
- if(connecting) {
- DrawRectangle(buttonPosX, buttonPosY, buttonW, buttonH, RED);
- } else {
- DrawRectangle(buttonPosX, buttonPosY, buttonW, buttonH, GREEN);
+ double total = 0;
+ int count = fpsSampleIndex < FPS_SAMPLES ? fpsSampleIndex : FPS_SAMPLES;
+ for (int i = 0; i < count; i++) total += fpsSamples[i];
+ avgFps = total / count;
+ if (GetWallTime() - lastPingTime > 1.0) {
+ SendPing();
+ lastPingTime = GetWallTime();
+ }
+
+
+ pthread_mutex_lock(&packetLock);
+ latestPacket.inputs = BuildInputMask();
+ latestPacket.yaw = player.yaw;
+ latestPacket.type = PACKET_CLIENT_INPUT;
+ latestPacket.playerId = player.id;
+ pthread_mutex_unlock(&packetLock);
+
+ UpdatePlayer(&player, &camera, terrainMesh, terrainTransform, dt);
+
+ for (int i = 0; i < MAX_PLAYERS; i++) {
+ if (!remotePlayers[i].active) continue;
+ if (i == player.id)
+ continue; // skip yourself
+ UpdateRemotePlayer(&remotePlayers[i], dt);
+ }
+
+ bool connecting = false;
+ if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
+ struct Vector2 mousePos = GetMousePosition();
+
+ if (mousePos.x >= buttonPosX && mousePos.x <= buttonPosX + buttonW &&
+ mousePos.y >= buttonPosY && mousePos.y <= buttonPosY + buttonH) {
+
+ connecting = true;
}
+ }
+
+ BeginDrawing();
+ ClearBackground(RAYWHITE);
+
+ BeginMode3D(camera);
+
+ DrawModel(terrain, (Vector3){0, 0, 0}, 1.0f, WHITE);
- DrawText(connect, connectPosX, connectPosY, fontSize, BLUE);
+ DrawGrid(20, 1.0f);
- DrawFPS(0,0);
- EndDrawing();
+ for (int i = 0; i < MAX_PLAYERS; i++) {
+ if (!remotePlayers[i].active) continue;
+ if (i == player.id) continue;
- if (connecting)
- ServerConnect();
+ float dist =
+ Vector3Distance(player.position, remotePlayers[i].currentPosition);
+
+ Vector3 scale = {10.0f, 10.0f, 10.0f};
+
+ if (remotePlayers[i].characterClass == 0) {
+ scale.x = 15.0f;
+ scale.y = 15.0f;
+ scale.z = 15.0;
+ }
+
+ // beam disappears when close
+ if (dist > 50.0f) {
+ Vector3 pos = remotePlayers[i].currentPosition;
+ Vector3 beamBottom = { pos.x, pos.y, pos.z };
+ Vector3 beamTop = { pos.x, pos.y + 2000, pos.z };
+ DrawCylinderEx((Vector3){ pos.x, pos.y+13, pos.z }, beamTop, 4.0f, 4.0f, 8, (Color){255, 255, 255, 220});
+ DrawCylinderEx(beamBottom, beamTop, 8.0f, 8.0f, 8, (Color){255, 255, 0, 80});
+ }
+
+ DrawModelEx(remotePlayers[i].model, remotePlayers[i].currentPosition,
+ (Vector3){0, 1, 0},
+ remotePlayers[i].currentYaw * RAD2DEG,
+ scale, WHITE);
+
+ }
+
+ for (int i = 0; i < MAX_VIEW_NPCS; ++i) {
+ DrawModelEx(
+ npcModels[clientNPCs[i].model],
+ clientNPCs[i].serverPosition,
+ (Vector3){0, 1, 0}, // rotate around Y axis (vertical)
+ clientNPCs[i].serverRotation, // random angle in degrees
+ clientNPCs[i].scale,
+ WHITE
+ );
+ }
+
+ for (int i = 0; i < 1000; ++i) {
+ DrawModelEx(
+ treeModels[i % 5],
+ treePositions[i],
+ (Vector3){0, 1, 0}, // rotate around Y axis (vertical)
+ treeRotations[i], // random angle in degrees
+ (Vector3){treeScales[i], treeScales[i], treeScales[i]}, // scale
+ WHITE
+ );
+ }
+
+ for (int i = 0; i < 100; ++i) {
+ DrawModelEx(
+ rockModels[i % 3],
+ rockPositions[i],
+ (Vector3){0, 1, 0}, // rotate around Y axis (vertical)
+ rockRotations[i], // random angle in degrees
+ (Vector3){rockScales[i], rockScales[i], rockScales[i]}, // scale
+ WHITE
+ );
+ }
+
+ for (int i = 0; i < 20; ++i) {
+ DrawModelEx(
+ houseModels[i % 2],
+ housePositions[i],
+ (Vector3){0, 1, 0}, // rotate around Y axis (vertical)
+ houseRotations[i], // random angle in degrees
+ (Vector3){houseScales[i], houseScales[i], houseScales[i]}, // scale
+ WHITE
+ );
+ }
+
+ EndMode3D();
+
+// fixed x positions - never move regardless of content
+
+
+int y = 10;
+int padY = 5;
+int totalW = 700;
+int totalH = fontSize + padY * 2;
+
+// background
+DrawRectangle(0, y - padY, totalW, totalH, (Color){0, 0, 0, 140});
+
+// fps - always at same x
+char fpsBuf[32];
+snprintf(fpsBuf, sizeof(fpsBuf), "FPS: %3.0f", avgFps);
+Color fpsColor = avgFps > 50 ? GREEN : avgFps > 30 ? YELLOW : RED;
+DrawText(fpsBuf, HUD_FPS_X, y, fontSize, fpsColor);
+
+// ping - always at same x
+char pingBuf[32];
+snprintf(pingBuf, sizeof(pingBuf), "PING: %5.1f ms", pingMs);
+Color pingColor = pingMs < 50 ? GREEN : pingMs < 100 ? YELLOW : RED;
+DrawText(pingBuf, HUD_PING_X, y, fontSize, pingColor);
+
+// coords - always at same x
+char coordsBuf[64];
+snprintf(coordsBuf, sizeof(coordsBuf), "X:%8.1f Y:%8.1f Z:%8.1f",
+ player.position.x, player.position.y, player.position.z);
+DrawText(coordsBuf, HUD_COORDS_X, y, fontSize, WHITE);
+
+ EndDrawing();
+
+ ProcessIncoming();
+ }
}
+ NetworkShutdown();
+ CloseWindow();
return 0;
}