diff --git a/game/client b/game/client index 0c15c78..673d436 100755 Binary files a/game/client and b/game/client differ diff --git a/game/client.c b/game/client.c index 316bb36..eb6df46 100644 --- a/game/client.c +++ b/game/client.c @@ -1,4 +1,3 @@ -// game/client.c - SMOOTH TERRAIN VERSION WITH DIRECTIONAL LIGHT & SHADOWS #include "raylib.h" #include #include @@ -180,6 +179,8 @@ float get_terrain_height(float x, float z) { #define TERRAIN_SIZE 256 #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) { int size = TERRAIN_SIZE; @@ -394,6 +395,9 @@ static Vector3 v3_add(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); } +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) { return a.x * b.x + a.y * b.y + a.z * b.z; } @@ -585,6 +589,7 @@ typedef struct MsgRoomState { typedef struct RemotePlayer { int present, alive, hp; Vector3 pos; + Vector3 prevPos; float yaw, pitch; uint8_t weapon; int16_t pistolMag, rifleMag, pistolAmmo, rifleAmmo, medkits; @@ -717,6 +722,10 @@ int main(void) { Vector3 camPos = v3(0, 5.0f, 6); 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; int scoped = 0; float fireCooldown = 0.0f; @@ -793,6 +802,7 @@ int main(void) { continue; RemotePlayer *p = &rp[ps->id]; + p->prevPos = p->pos; p->present = 1; p->alive = ps->alive; p->hp = ps->hp; @@ -811,14 +821,9 @@ int main(void) { } if (myId != 255 && rp[myId].present) { - Vector3 body = rp[myId].pos; - camPos.x = body.x; - camPos.y = body.y + 1.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); + prevBodyPos = currentTargetBody; + currentTargetBody = rp[myId].pos; + interpTimer = 0.0f; } } else if (type == MSG_ITEMS && n >= (int)sizeof(MsgItems)) { 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(); const float sens = 0.0025f; yaw -= md.x * sens; @@ -931,6 +953,14 @@ int main(void) { Vector3 forward = v3(sinf(viewYaw) * cosf(viewPitch), sinf(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}; cam.position = camPos; cam.target = v3_add(camPos, forward); @@ -977,6 +1007,10 @@ int main(void) { (Color){80, 140, 70, 255}; 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) for (int i = 0; i < MAX_ITEMS; i++) { if (!wi[i].present) @@ -1000,7 +1034,11 @@ int main(void) { for (int i = 0; i < MAX_PLAYERS; i++) { if (!rp[i].present) 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 = (i == myId) ? (Color){80, 180, 255, 255} : (Color){255, 80, 80, 255}; if (!rp[i].alive) @@ -1020,7 +1058,12 @@ int main(void) { for (int i = 0; i < MAX_PLAYERS; i++) { if (!rp[i].present || !rp[i].username[0]) 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 toHead = v3_sub(head, cam.position); if (v3_dot(camForward, toHead) <= 0.0f) @@ -1081,18 +1124,18 @@ int main(void) { // Room state overlay 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("Need at least 2 players", sw / 2 - 100, sh / 2 - 5, 18, (Color){200, 200, 200, 255}); } 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, (Color){255, 255, 80, 255}); DrawText(TextFormat("%.1f seconds", countdownRemaining), sw / 2 - 60, sh / 2 - 5, 20, RAYWHITE); } 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, (Color){255, 80, 80, 255}); if (winnerName[0]) { diff --git a/game/client.o b/game/client.o new file mode 100644 index 0000000..c6e1085 Binary files /dev/null and b/game/client.o differ diff --git a/game/libfishsoup b/game/libfishsoup deleted file mode 120000 index 9b31e29..0000000 --- a/game/libfishsoup +++ /dev/null @@ -1 +0,0 @@ -../libfishsoup \ No newline at end of file diff --git a/game/server b/game/server index 03a0748..30f4f0b 100755 Binary files a/game/server and b/game/server differ diff --git a/game/server.c b/game/server.c index b3c61b9..2e5c2b1 100644 --- a/game/server.c +++ b/game/server.c @@ -1,4 +1,3 @@ -// game/server.c - SMOOTH TERRAIN VERSION #include #include #include @@ -154,6 +153,8 @@ float get_terrain_height(float x, float z) { #define TERRAIN_SIZE 256 #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 dGeomID gTerrainGeom = NULL; @@ -299,6 +300,7 @@ typedef struct Player { uint32_t rng; float reloadTimer; int reloadWeapon; + float borderDamageTimer; dBodyID body; dGeomID geom; } Player; @@ -467,6 +469,8 @@ static void player_ode_create(Player *p, int spawnIndex) { dGeomSetBody(p->geom, p->body); dGeomSetCategoryBits(p->geom, CAT_PLAYER); dGeomSetCollideBits(p->geom, CAT_WORLD | CAT_PLAYER); + + p->borderDamageTimer = 0.0f; } static void player_ode_destroy(Player *p) { @@ -989,27 +993,71 @@ int main(void) { } } - for (int i = 0; i < MAX_PLAYERS; i++) { - Player *p = &players[i]; - if (!p->inUse || !p->alive) - continue; + for (int i = 0; i < MAX_PLAYERS; i++) { + Player *p = &players[i]; + if (!p->inUse || !p->alive) + continue; - const dReal *lvel = dBodyGetLinearVel(p->body); - const dReal *avel = dBodyGetAngularVel(p->body); + const dReal *lvel = dBodyGetLinearVel(p->body); + const dReal *avel = dBodyGetAngularVel(p->body); - float dx = p->moveX, dz = p->moveZ; - float len = sqrtf(dx * dx + dz * dz); - if (len > 1e-6f) { - dx /= len; - dz /= len; - } + float dx = p->moveX, dz = p->moveZ; + float len = sqrtf(dx * dx + dz * dz); + if (len > 1e-6f) { + dx /= len; + dz /= len; + } - float speed = 10.0f; - float fx = sinf(p->yaw) * dz - cosf(p->yaw) * dx; - float fz = cosf(p->yaw) * dz + sinf(p->yaw) * dx; - dBodySetLinearVel(p->body, fx * speed, lvel[1], fz * speed); - dBodySetAngularVel(p->body, avel[0], 0, avel[2]); - } + float speed = 10.0f; + float fx = sinf(p->yaw) * dz - cosf(p->yaw) * dx; + float fz = cosf(p->yaw) * dz + sinf(p->yaw) * dx; + dBodySetLinearVel(p->body, fx * speed, lvel[1], fz * speed); + 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}; snap.type = MSG_SNAPSHOT;