Compare commits
3 commits
57d8c7a2b7
...
2d98574830
| Author | SHA1 | Date | |
|---|---|---|---|
| 2d98574830 | |||
| fcb0280549 | |||
| 7d1a5a3653 |
6 changed files with 127 additions and 37 deletions
BIN
game/client
BIN
game/client
Binary file not shown.
|
|
@ -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
BIN
game/client.o
Normal file
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
../libfishsoup
|
|
||||||
BIN
game/server
BIN
game/server
Binary file not shown.
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue