Compare commits

...

3 commits

Author SHA1 Message Date
2d98574830 world border 2025-12-19 11:20:00 +05:30
fcb0280549 camera position fixing 2025-12-19 11:06:12 +05:30
7d1a5a3653 wider 2025-12-19 10:49:53 +05:30
6 changed files with 127 additions and 37 deletions

Binary file not shown.

View file

@ -1,4 +1,3 @@
// game/client.c - SMOOTH TERRAIN VERSION WITH DIRECTIONAL LIGHT & SHADOWS
#include "raylib.h" #include "raylib.h"
#include <math.h> #include <math.h>
#include <stdint.h> #include <stdint.h>
@ -180,6 +179,8 @@ float get_terrain_height(float x, float z) {
#define TERRAIN_SIZE 256 #define TERRAIN_SIZE 256
#define TERRAIN_SCALE 1.0f #define TERRAIN_SCALE 1.0f
#define TERRAIN_MIN (-TERRAIN_SIZE * TERRAIN_SCALE / 2.0f)
#define TERRAIN_MAX (TERRAIN_SIZE * TERRAIN_SCALE / 2.0f)
static Mesh generate_terrain_mesh(void) { static Mesh generate_terrain_mesh(void) {
int size = TERRAIN_SIZE; int size = TERRAIN_SIZE;
@ -394,6 +395,9 @@ static Vector3 v3_add(Vector3 a, Vector3 b) {
static Vector3 v3_sub(Vector3 a, Vector3 b) { static Vector3 v3_sub(Vector3 a, Vector3 b) {
return v3(a.x - b.x, a.y - b.y, a.z - b.z); return v3(a.x - b.x, a.y - b.y, a.z - b.z);
} }
static Vector3 v3_mul(Vector3 a, float s) {
return v3(a.x * s, a.y * s, a.z * s);
}
static float v3_dot(Vector3 a, Vector3 b) { static float v3_dot(Vector3 a, Vector3 b) {
return a.x * b.x + a.y * b.y + a.z * b.z; return a.x * b.x + a.y * b.y + a.z * b.z;
} }
@ -585,6 +589,7 @@ typedef struct MsgRoomState {
typedef struct RemotePlayer { typedef struct RemotePlayer {
int present, alive, hp; int present, alive, hp;
Vector3 pos; Vector3 pos;
Vector3 prevPos;
float yaw, pitch; float yaw, pitch;
uint8_t weapon; uint8_t weapon;
int16_t pistolMag, rifleMag, pistolAmmo, rifleAmmo, medkits; int16_t pistolMag, rifleMag, pistolAmmo, rifleAmmo, medkits;
@ -717,6 +722,10 @@ int main(void) {
Vector3 camPos = v3(0, 5.0f, 6); Vector3 camPos = v3(0, 5.0f, 6);
float yaw = 0.0f, pitch = 0.0f; float yaw = 0.0f, pitch = 0.0f;
Vector3 prevBodyPos = v3(0, 5.0f, 6);
Vector3 currentTargetBody = v3(0, 5.0f, 6);
float interpTimer = 0.0f;
float recoilYaw = 0.0f, recoilPitch = 0.0f, crossSpread = 0.0f; float recoilYaw = 0.0f, recoilPitch = 0.0f, crossSpread = 0.0f;
int scoped = 0; int scoped = 0;
float fireCooldown = 0.0f; float fireCooldown = 0.0f;
@ -793,6 +802,7 @@ int main(void) {
continue; continue;
RemotePlayer *p = &rp[ps->id]; RemotePlayer *p = &rp[ps->id];
p->prevPos = p->pos;
p->present = 1; p->present = 1;
p->alive = ps->alive; p->alive = ps->alive;
p->hp = ps->hp; p->hp = ps->hp;
@ -811,14 +821,9 @@ int main(void) {
} }
if (myId != 255 && rp[myId].present) { if (myId != 255 && rp[myId].present) {
Vector3 body = rp[myId].pos; prevBodyPos = currentTargetBody;
camPos.x = body.x; currentTargetBody = rp[myId].pos;
camPos.y = body.y + 1.0f; interpTimer = 0.0f;
camPos.z = body.z + 0.0001f;
// Prevent camera from clipping into terrain
float terrain_h = get_terrain_height(camPos.x, camPos.z);
camPos.y = fmaxf(camPos.y, terrain_h + 1.5f);
} }
} else if (type == MSG_ITEMS && n >= (int)sizeof(MsgItems)) { } else if (type == MSG_ITEMS && n >= (int)sizeof(MsgItems)) {
MsgItems *m = (MsgItems *)buf; MsgItems *m = (MsgItems *)buf;
@ -843,6 +848,23 @@ int main(void) {
} }
} }
// Interpolate camera position
if (myId != 255 && rp[myId].present) {
float interpFactor = interpTimer / (1.0f / 20.0f);
if (interpFactor > 1.0f)
interpFactor = 1.0f;
Vector3 interpBody = v3_add(v3_mul(prevBodyPos, 1.0f - interpFactor),
v3_mul(currentTargetBody, interpFactor));
camPos.x = interpBody.x;
camPos.y = interpBody.y + 1.0f;
camPos.z = interpBody.z + 0.0001f;
// Prevent camera from clipping into terrain
float terrain_h = get_terrain_height(camPos.x, camPos.z);
camPos.y = fmaxf(camPos.y, terrain_h + 1.5f);
}
interpTimer += dt;
Vector2 md = GetMouseDelta(); Vector2 md = GetMouseDelta();
const float sens = 0.0025f; const float sens = 0.0025f;
yaw -= md.x * sens; yaw -= md.x * sens;
@ -931,6 +953,14 @@ int main(void) {
Vector3 forward = v3(sinf(viewYaw) * cosf(viewPitch), sinf(viewPitch), Vector3 forward = v3(sinf(viewYaw) * cosf(viewPitch), sinf(viewPitch),
cosf(viewYaw) * cosf(viewPitch)); cosf(viewYaw) * cosf(viewPitch));
// Offset camera position: 0.3m in front, 0.2m below
camPos = v3_add(camPos, v3_mul(forward, 0.3f));
camPos.y -= 0.2f;
// Ensure camera doesn't clip into terrain after offset
float terrain_h = get_terrain_height(camPos.x, camPos.z);
camPos.y = fmaxf(camPos.y, terrain_h + 1.5f);
Camera3D cam = {0}; Camera3D cam = {0};
cam.position = camPos; cam.position = camPos;
cam.target = v3_add(camPos, forward); cam.target = v3_add(camPos, forward);
@ -977,6 +1007,10 @@ int main(void) {
(Color){80, 140, 70, 255}; (Color){80, 140, 70, 255};
DrawModel(terrainModel, (Vector3){0, 0, 0}, 1.0f, WHITE); DrawModel(terrainModel, (Vector3){0, 0, 0}, 1.0f, WHITE);
// Draw border
float borderSize = TERRAIN_MAX - TERRAIN_MIN;
DrawCube((Vector3){0, 0, 0}, borderSize, 1000, borderSize, (Color){255, 0, 0, 100});
// Items with basic lighting (no shader for now to keep it simple) // Items with basic lighting (no shader for now to keep it simple)
for (int i = 0; i < MAX_ITEMS; i++) { for (int i = 0; i < MAX_ITEMS; i++) {
if (!wi[i].present) if (!wi[i].present)
@ -1000,7 +1034,11 @@ int main(void) {
for (int i = 0; i < MAX_PLAYERS; i++) { for (int i = 0; i < MAX_PLAYERS; i++) {
if (!rp[i].present) if (!rp[i].present)
continue; continue;
Vector3 p = rp[i].pos; float interpFactor = interpTimer / (1.0f / 20.0f);
if (interpFactor > 1.0f)
interpFactor = 1.0f;
Vector3 p = v3_add(v3_mul(rp[i].prevPos, 1.0f - interpFactor),
v3_mul(rp[i].pos, interpFactor));
Color c = Color c =
(i == myId) ? (Color){80, 180, 255, 255} : (Color){255, 80, 80, 255}; (i == myId) ? (Color){80, 180, 255, 255} : (Color){255, 80, 80, 255};
if (!rp[i].alive) if (!rp[i].alive)
@ -1020,7 +1058,12 @@ int main(void) {
for (int i = 0; i < MAX_PLAYERS; i++) { for (int i = 0; i < MAX_PLAYERS; i++) {
if (!rp[i].present || !rp[i].username[0]) if (!rp[i].present || !rp[i].username[0])
continue; continue;
Vector3 head = v3(rp[i].pos.x, rp[i].pos.y + 1.2f, rp[i].pos.z); float interpFactor = interpTimer / (1.0f / 20.0f);
if (interpFactor > 1.0f)
interpFactor = 1.0f;
Vector3 p = v3_add(v3_mul(rp[i].prevPos, 1.0f - interpFactor),
v3_mul(rp[i].pos, interpFactor));
Vector3 head = v3(p.x, p.y + 1.2f, p.z);
Vector3 camForward = v3_norm(v3_sub(cam.target, cam.position)); Vector3 camForward = v3_norm(v3_sub(cam.target, cam.position));
Vector3 toHead = v3_sub(head, cam.position); Vector3 toHead = v3_sub(head, cam.position);
if (v3_dot(camForward, toHead) <= 0.0f) if (v3_dot(camForward, toHead) <= 0.0f)
@ -1081,18 +1124,18 @@ int main(void) {
// Room state overlay // Room state overlay
if (roomState == 0) { // Waiting if (roomState == 0) { // Waiting
DrawRectangle(sw / 2 - 150, sh / 2 - 50, 300, 100, (Color){0, 0, 0, 200}); DrawRectangle(sw / 2 - 150, sh / 2 - 50, 350, 100, (Color){0, 0, 0, 200});
DrawText("WAITING FOR PLAYERS", sw / 2 - 120, sh / 2 - 30, 24, RAYWHITE); DrawText("WAITING FOR PLAYERS", sw / 2 - 120, sh / 2 - 30, 24, RAYWHITE);
DrawText("Need at least 2 players", sw / 2 - 100, sh / 2 - 5, 18, DrawText("Need at least 2 players", sw / 2 - 100, sh / 2 - 5, 18,
(Color){200, 200, 200, 255}); (Color){200, 200, 200, 255});
} else if (roomState == 1) { // Counting down } else if (roomState == 1) { // Counting down
DrawRectangle(sw / 2 - 150, sh / 2 - 50, 300, 100, (Color){0, 0, 0, 200}); DrawRectangle(sw / 2 - 150, sh / 2 - 50, 350, 100, (Color){0, 0, 0, 200});
DrawText("GAME STARTING SOON", sw / 2 - 120, sh / 2 - 30, 24, DrawText("GAME STARTING SOON", sw / 2 - 120, sh / 2 - 30, 24,
(Color){255, 255, 80, 255}); (Color){255, 255, 80, 255});
DrawText(TextFormat("%.1f seconds", countdownRemaining), sw / 2 - 60, DrawText(TextFormat("%.1f seconds", countdownRemaining), sw / 2 - 60,
sh / 2 - 5, 20, RAYWHITE); sh / 2 - 5, 20, RAYWHITE);
} else if (roomState == 3) { // Finished } else if (roomState == 3) { // Finished
DrawRectangle(sw / 2 - 200, sh / 2 - 50, 400, 100, (Color){0, 0, 0, 200}); DrawRectangle(sw / 2 - 200, sh / 2 - 50, 450, 100, (Color){0, 0, 0, 200});
DrawText("GAME FINISHED", sw / 2 - 80, sh / 2 - 30, 28, DrawText("GAME FINISHED", sw / 2 - 80, sh / 2 - 30, 28,
(Color){255, 80, 80, 255}); (Color){255, 80, 80, 255});
if (winnerName[0]) { if (winnerName[0]) {

BIN
game/client.o Normal file

Binary file not shown.

View file

@ -1 +0,0 @@
../libfishsoup

Binary file not shown.

View file

@ -1,4 +1,3 @@
// game/server.c - SMOOTH TERRAIN VERSION
#include <math.h> #include <math.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
@ -154,6 +153,8 @@ float get_terrain_height(float x, float z) {
#define TERRAIN_SIZE 256 #define TERRAIN_SIZE 256
#define TERRAIN_SCALE 1.0f #define TERRAIN_SCALE 1.0f
#define TERRAIN_MIN (-TERRAIN_SIZE * TERRAIN_SCALE / 2.0f)
#define TERRAIN_MAX (TERRAIN_SIZE * TERRAIN_SCALE / 2.0f)
static dReal *gHeightData = NULL; static dReal *gHeightData = NULL;
static dGeomID gTerrainGeom = NULL; static dGeomID gTerrainGeom = NULL;
@ -299,6 +300,7 @@ typedef struct Player {
uint32_t rng; uint32_t rng;
float reloadTimer; float reloadTimer;
int reloadWeapon; int reloadWeapon;
float borderDamageTimer;
dBodyID body; dBodyID body;
dGeomID geom; dGeomID geom;
} Player; } Player;
@ -467,6 +469,8 @@ static void player_ode_create(Player *p, int spawnIndex) {
dGeomSetBody(p->geom, p->body); dGeomSetBody(p->geom, p->body);
dGeomSetCategoryBits(p->geom, CAT_PLAYER); dGeomSetCategoryBits(p->geom, CAT_PLAYER);
dGeomSetCollideBits(p->geom, CAT_WORLD | CAT_PLAYER); dGeomSetCollideBits(p->geom, CAT_WORLD | CAT_PLAYER);
p->borderDamageTimer = 0.0f;
} }
static void player_ode_destroy(Player *p) { static void player_ode_destroy(Player *p) {
@ -1011,6 +1015,50 @@ int main(void) {
dBodySetAngularVel(p->body, avel[0], 0, avel[2]); dBodySetAngularVel(p->body, avel[0], 0, avel[2]);
} }
// Border damage
for (int i = 0; i < MAX_PLAYERS; i++) {
Player *p = &players[i];
if (!p->inUse || !p->alive)
continue;
const dReal *pos = dBodyGetPosition(p->body);
float px = (float)pos[0], pz = (float)pos[2];
if (px < TERRAIN_MIN || px > TERRAIN_MAX || pz < TERRAIN_MIN || pz > TERRAIN_MAX) {
p->borderDamageTimer += DT;
if (p->borderDamageTimer >= 0.5f) {
p->borderDamageTimer -= 0.5f;
p->hp -= 1;
if (p->hp <= 0) {
p->hp = 0;
p->alive = 0;
// Drop loot as in shooting death
const dReal *pos2 = dBodyGetPosition(p->body);
float x = (float)pos2[0], z = (float)pos2[2];
if (p->pistolAmmo > 0) {
int qty = p->pistolAmmo;
if (qty > 12) qty = 12;
item_spawn(ITEM_AMMO_PISTOL, qty, x + frand_signed(&p->rng) * 2.0f, z + frand_signed(&p->rng) * 2.0f);
p->pistolAmmo -= qty;
}
if (p->rifleAmmo > 0) {
int qty = p->rifleAmmo;
if (qty > 30) qty = 30;
item_spawn(ITEM_AMMO_RIFLE, qty, x + frand_signed(&p->rng) * 2.0f, z + frand_signed(&p->rng) * 2.0f);
p->rifleAmmo -= qty;
}
if (p->medkits > 0) {
int qty = p->medkits;
if (qty > 2) qty = 2;
item_spawn(ITEM_MEDKIT, qty, x + frand_signed(&p->rng) * 2.0f, z + frand_signed(&p->rng) * 2.0f);
p->medkits -= qty;
}
printf("Player %d died from border damage and dropped loot\n", i);
}
}
} else {
p->borderDamageTimer = 0.0f;
}
}
MsgSnapshot snap = {0}; MsgSnapshot snap = {0};
snap.type = MSG_SNAPSHOT; snap.type = MSG_SNAPSHOT;
snap.serverTick = serverTick; snap.serverTick = serverTick;