From 83334091ba0b11f62e71df0fb4ccbdde4299cbbf Mon Sep 17 00:00:00 2001 From: Masashi Date: Thu, 18 Dec 2025 19:07:53 +0530 Subject: [PATCH 1/4] REAL GAM --- game/client | Bin 0 -> 25488 bytes game/client.c | 245 ++++++++++---------------- game/output.md | 0 game/run.sh | 7 + game/server | Bin 0 -> 30640 bytes game/server.c | 454 ++++++++++++++++++------------------------------- 6 files changed, 268 insertions(+), 438 deletions(-) create mode 100755 game/client delete mode 100644 game/output.md create mode 100755 game/run.sh create mode 100755 game/server diff --git a/game/client b/game/client new file mode 100755 index 0000000000000000000000000000000000000000..739731c1938e18f0956cfe6479f7ee32af701aa0 GIT binary patch literal 25488 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 literal 0 HcmV?d00001 diff --git a/game/client.c b/game/client.c index 42833be..4db9112 100644 --- a/game/client.c +++ b/game/client.c @@ -1,9 +1,3 @@ -// client.c - raylib client (C99) -// Features: username prompt, inverted A/D, inverted pitch, recoil, crosshair -// widening, spray, inventory UI toggle on E, pick/drop/use items, renders -// dropped items from server. Build (Linux example): -// gcc client.c -O2 -o client -lraylib -lm -lpthread -ldl -lrt -lX11 - #include "raylib.h" #include #include @@ -26,32 +20,28 @@ typedef int socklen_t; #define CLOSESOCK close #endif -#define PROTOCOL_VERSION 4 +#define PROTOCOL_VERSION 5 #define SERVER_PORT 27015 #define MAX_PLAYERS 16 #define USERNAME_MAX 16 #define WEAPON_PISTOL 0 #define WEAPON_RIFLE 1 -#define WEAPON_COUNT 2 -#define INV_SLOTS 6 -#define MAX_ITEMS 64 - -// Inventory item types (must match server) +// Item types #define ITEM_NONE 0 #define ITEM_MEDKIT 1 -#define ITEM_ARMORPLATE 2 -#define ITEM_AMMO_PISTOL 3 -#define ITEM_AMMO_RIFLE 4 +#define ITEM_AMMO_PISTOL 2 +#define ITEM_AMMO_RIFLE 3 -// MsgInput.buttons bits +#define MAX_ITEMS 64 + +// Input buttons #define BTN_RELOAD (1u << 0) -#define BTN_WPN1 (1u << 1) -#define BTN_WPN2 (1u << 2) +#define BTN_SWITCH_PISTOL (1u << 1) +#define BTN_SWITCH_RIFLE (1u << 2) #define BTN_PICK (1u << 3) -#define BTN_DROP (1u << 4) -#define BTN_USE (1u << 5) +#define BTN_USE_MEDKIT (1u << 4) static void set_nonblocking(int sock) { #ifdef _WIN32 @@ -70,8 +60,7 @@ typedef enum MsgType : uint8_t { MSG_INPUT = 3, MSG_SNAPSHOT = 4, MSG_SHOOT = 5, - MSG_HIT = 6, - MSG_ITEMS = 7 + MSG_ITEMS = 6 } MsgType; typedef struct MsgHello { @@ -94,8 +83,7 @@ typedef struct MsgInput { float moveZ; float yaw; float pitch; - uint8_t buttons; // BTN_* - uint8_t slotIndex; // 0..INV_SLOTS-1 (for DROP/USE) + uint8_t buttons; } MsgInput; typedef struct MsgShoot { @@ -108,16 +96,15 @@ typedef struct PlayerStateNet { uint8_t id; uint8_t alive; int16_t hp; - int16_t armor; // NEW float x, y, z; float yaw, pitch; uint8_t weapon; - int16_t ammoMag[WEAPON_COUNT]; - int16_t ammoRes[WEAPON_COUNT]; - - uint8_t invType[INV_SLOTS]; - int16_t invQty[INV_SLOTS]; + int16_t pistolMag; + int16_t rifleMag; + int16_t pistolAmmo; + int16_t rifleAmmo; + int16_t medkits; char username[USERNAME_MAX]; } PlayerStateNet; @@ -129,13 +116,6 @@ typedef struct MsgSnapshot { PlayerStateNet p[MAX_PLAYERS]; } MsgSnapshot; -typedef struct MsgHit { - uint8_t type; - uint8_t shooterId; - uint8_t victimId; - int16_t victimHp; -} MsgHit; - typedef struct ItemNet { uint16_t id; uint8_t type; @@ -155,16 +135,15 @@ typedef struct RemotePlayer { int present; int alive; int hp; - int armor; Vector3 pos; float yaw, pitch; uint8_t weapon; - int16_t ammoMag[WEAPON_COUNT]; - int16_t ammoRes[WEAPON_COUNT]; - - uint8_t invType[INV_SLOTS]; - int16_t invQty[INV_SLOTS]; + int16_t pistolMag; + int16_t rifleMag; + int16_t pistolAmmo; + int16_t rifleAmmo; + int16_t medkits; char username[USERNAME_MAX]; } RemotePlayer; @@ -207,8 +186,6 @@ static const char *ItemName(uint8_t t) { switch (t) { case ITEM_MEDKIT: return "Medkit"; - case ITEM_ARMORPLATE: - return "Armor plate"; case ITEM_AMMO_PISTOL: return "Pistol ammo"; case ITEM_AMMO_RIFLE: @@ -218,7 +195,6 @@ static const char *ItemName(uint8_t t) { } } -// Simple username input (ASCII-ish) static void ui_username_prompt(char outName[USERNAME_MAX]) { memset(outName, 0, USERNAME_MAX); while (!WindowShouldClose()) { @@ -285,28 +261,17 @@ int main(void) { RemotePlayer rp[MAX_PLAYERS] = {0}; WorldItem wi[MAX_ITEMS] = {0}; - // view/camera Vector3 camPos = v3(0, 1.7f, 6); float yaw = 0.0f; float pitch = 0.0f; - // recoil/crosshair float recoilYaw = 0.0f, recoilPitch = 0.0f; float crossSpread = 0.0f; - // firing float fireCooldown = 0.0f; int shotsInBurst = 0; float burstResetTimer = 0.0f; - // inventory UI - int invOpen = 0; - int selectedSlot = 0; - - // desired weapon - int desiredWeapon = WEAPON_RIFLE; - - // tuning const float pistolFireRate = 4.0f; const float rifleFireRate = 12.0f; const float recoilReturn = 18.0f; @@ -325,7 +290,6 @@ int main(void) { const float burstResetTime = 0.18f; - // HELLO MsgHello hello = {0}; hello.type = MSG_HELLO; hello.protocol = PROTOCOL_VERSION; @@ -337,7 +301,6 @@ int main(void) { clientTick++; float dt = GetFrameTime(); - // timers fireCooldown -= dt; if (fireCooldown < 0.0f) fireCooldown = 0.0f; @@ -345,13 +308,11 @@ int main(void) { if (burstResetTimer <= 0.0f) shotsInBurst = 0; - // recoil return { float k = 1.0f - expf(-recoilReturn * dt); recoilYaw += (0.0f - recoilYaw) * k; recoilPitch += (0.0f - recoilPitch) * k; } - // crosshair return { float k = 1.0f - expf(-crossReturn * dt); crossSpread += (0.0f - crossSpread) * k; @@ -359,7 +320,7 @@ int main(void) { crossSpread = 0.0f; } - // receive packets + // Receive packets for (;;) { uint8_t buf[1400]; struct sockaddr_in from = {0}; @@ -395,20 +356,15 @@ int main(void) { p->present = 1; p->alive = ps->alive; p->hp = ps->hp; - p->armor = ps->armor; p->pos = v3(ps->x, ps->y, ps->z); p->yaw = ps->yaw; p->pitch = ps->pitch; p->weapon = ps->weapon; - - for (int w = 0; w < WEAPON_COUNT; w++) { - p->ammoMag[w] = ps->ammoMag[w]; - p->ammoRes[w] = ps->ammoRes[w]; - } - for (int sidx = 0; sidx < INV_SLOTS; sidx++) { - p->invType[sidx] = ps->invType[sidx]; - p->invQty[sidx] = ps->invQty[sidx]; - } + p->pistolMag = ps->pistolMag; + p->rifleMag = ps->rifleMag; + p->pistolAmmo = ps->pistolAmmo; + p->rifleAmmo = ps->rifleAmmo; + p->medkits = ps->medkits; memset(p->username, 0, USERNAME_MAX); strncpy(p->username, ps->username, USERNAME_MAX - 1); @@ -418,7 +374,6 @@ int main(void) { Vector3 body = rp[myId].pos; camPos.x = body.x; camPos.z = body.z + 0.0001f; - desiredWeapon = rp[myId].weapon; } } else if (type == MSG_ITEMS && n >= (int)sizeof(MsgItems)) { MsgItems *m = (MsgItems *)buf; @@ -435,25 +390,7 @@ int main(void) { } } - // toggle inventory UI on E - if (IsKeyPressed(KEY_E)) - invOpen = !invOpen; - - // select slot 1..6 - if (IsKeyPressed(KEY_ONE)) - selectedSlot = 0; - if (IsKeyPressed(KEY_TWO)) - selectedSlot = 1; - if (IsKeyPressed(KEY_THREE)) - selectedSlot = 2; - if (IsKeyPressed(KEY_FOUR)) - selectedSlot = 3; - if (IsKeyPressed(KEY_FIVE)) - selectedSlot = 4; - if (IsKeyPressed(KEY_SIX)) - selectedSlot = 5; - - // mouse look: yaw fixed, pitch inverted + // Mouse look Vector2 md = GetMouseDelta(); const float sens = 0.0025f; yaw -= md.x * sens; @@ -463,7 +400,6 @@ int main(void) { if (pitch > 1.5f) pitch = 1.5f; - // apply recoil to view float viewYaw = yaw + recoilYaw; float viewPitch = pitch + recoilPitch; if (viewPitch < -1.5f) @@ -471,7 +407,7 @@ int main(void) { if (viewPitch > 1.5f) viewPitch = 1.5f; - // movement (A/D inverted) + // Movement float moveX = 0.0f, moveZ = 0.0f; if (IsKeyDown(KEY_A)) moveX += 1.0f; @@ -482,32 +418,21 @@ int main(void) { if (IsKeyDown(KEY_S)) moveZ -= 1.0f; - // input buttons + // Input buttons uint8_t buttons = 0; - // weapon switch (separate from slot select) - if (IsKeyPressed(KEY_Z)) { - desiredWeapon = WEAPON_PISTOL; - buttons |= BTN_WPN1; - } - if (IsKeyPressed(KEY_X)) { - desiredWeapon = WEAPON_RIFLE; - buttons |= BTN_WPN2; - } - + if (IsKeyPressed(KEY_ONE)) + buttons |= BTN_SWITCH_PISTOL; + if (IsKeyPressed(KEY_TWO)) + buttons |= BTN_SWITCH_RIFLE; if (IsKeyPressed(KEY_R)) buttons |= BTN_RELOAD; if (IsKeyPressed(KEY_F)) buttons |= BTN_PICK; - if (IsKeyPressed(KEY_G)) - buttons |= BTN_DROP; - if (IsKeyPressed(KEY_C)) - buttons |= BTN_USE; // use armor/medkit depending on selected slot - if (IsKeyPressed(KEY_V)) - buttons |= - BTN_USE; // alternate use key (also triggers, server uses slot type) + if (IsKeyPressed(KEY_H)) + buttons |= BTN_USE_MEDKIT; - // send INPUT + // Send INPUT if (myId != 255) { MsgInput in = {0}; in.type = MSG_INPUT; @@ -518,23 +443,22 @@ int main(void) { in.yaw = viewYaw; in.pitch = viewPitch; in.buttons = buttons; - in.slotIndex = (uint8_t)selectedSlot; sendto(sock, (const char *)&in, (int)sizeof(in), 0, (const struct sockaddr *)&srv, sizeof(srv)); } - // shoot (hold LMB) + // Shoot if (myId != 255 && IsMouseButtonDown(MOUSE_BUTTON_LEFT) && - fireCooldown <= 0.0f) { - float rate = - (desiredWeapon == WEAPON_PISTOL) ? pistolFireRate : rifleFireRate; + fireCooldown <= 0.0f && rp[myId].present) { + int wpn = rp[myId].weapon; + float rate = (wpn == WEAPON_PISTOL) ? pistolFireRate : rifleFireRate; float interval = 1.0f / rate; fireCooldown = interval; shotsInBurst++; burstResetTimer = burstResetTime; - if (desiredWeapon == WEAPON_PISTOL) { + if (wpn == WEAPON_PISTOL) { recoilPitch += pistolKickPitch; recoilYaw += (((float)GetRandomValue(-1000, 1000)) / 1000.0f) * pistolKickYaw; @@ -554,7 +478,7 @@ int main(void) { (const struct sockaddr *)&srv, sizeof(srv)); } - // camera + // Camera Vector3 forward = v3(sinf(viewYaw) * cosf(viewPitch), sinf(viewPitch), cosf(viewYaw) * cosf(viewPitch)); @@ -570,7 +494,7 @@ int main(void) { BeginMode3D(cam); - // ground + // Ground for (int z = -16; z <= 16; z++) { for (int x = -16; x <= 16; x++) { DrawCubeV(v3((float)x, 0.0f, (float)z), v3(1, 1, 1), @@ -578,21 +502,19 @@ int main(void) { } } - // blocks + // Blocks DrawCubeV(v3(2, 1, 2), v3(1, 1, 1), (Color){120, 90, 60, 255}); DrawCubeV(v3(2, 2, 2), v3(1, 1, 1), (Color){120, 90, 60, 255}); DrawCubeV(v3(-3, 1, -1), v3(1, 1, 1), (Color){90, 120, 60, 255}); DrawCubeV(v3(0, 1, 4), v3(2, 1, 2), (Color){90, 90, 130, 255}); - // items (dropped/pickups) + // Items for (int i = 0; i < MAX_ITEMS; i++) { if (!wi[i].present) continue; Color ic = (Color){220, 220, 220, 255}; if (wi[i].type == ITEM_MEDKIT) ic = (Color){120, 255, 120, 255}; - if (wi[i].type == ITEM_ARMORPLATE) - ic = (Color){120, 180, 255, 255}; if (wi[i].type == ITEM_AMMO_PISTOL) ic = (Color){255, 220, 120, 255}; if (wi[i].type == ITEM_AMMO_RIFLE) @@ -601,7 +523,7 @@ int main(void) { v3(0.3f, 0.3f, 0.3f), ic); } - // players + // Players for (int i = 0; i < MAX_PLAYERS; i++) { if (!rp[i].present) continue; @@ -616,7 +538,7 @@ int main(void) { EndMode3D(); - // nameplates + // Nameplates for (int i = 0; i < MAX_PLAYERS; i++) { if (!rp[i].present) continue; @@ -644,47 +566,62 @@ int main(void) { if (myId == 255 || !rp[myId].present) { DrawText("Connecting...", 10, 10, 20, RAYWHITE); } else { - const char *wname = + // Top left - Health and Medkits + DrawRectangle(10, 10, 280, 110, (Color){0, 0, 0, 120}); + + DrawText("HP", 20, 20, 20, RAYWHITE); + int healthBarWidth = (rp[myId].hp * 220) / 100; + Color healthColor = (rp[myId].hp > 60) ? (Color){80, 255, 80, 120} + : (rp[myId].hp > 30) ? (Color){255, 200, 80, 120} + : (Color){255, 80, 80, 120}; + DrawRectangle(20, 45, 220, 20, (Color){40, 40, 40, 255}); + DrawRectangle(20, 45, healthBarWidth, 20, healthColor); + DrawText(TextFormat("%d", rp[myId].hp), 250, 47, 18, RAYWHITE); + + DrawText(TextFormat("Medkits: %d (H to use)", rp[myId].medkits), 20, 75, + 18, + (rp[myId].medkits > 0) ? (Color){120, 255, 120, 120} + : (Color){120, 120, 120, 120}); + + DrawText("1=Pistol 2=Rifle R=Reload F=Pick", 20, 95, 14, + (Color){150, 150, 150, 150}); + + // Bottom right - Weapon and Ammo + const char *weaponName = (rp[myId].weapon == WEAPON_PISTOL) ? "PISTOL" : "RIFLE"; - DrawText(TextFormat("Name: %s | HP: %d | Armor: %d | Weapon: %s", myName, - rp[myId].hp, rp[myId].armor, wname), - 10, 10, 20, RAYWHITE); + Color weaponColor = (rp[myId].weapon == WEAPON_PISTOL) + ? (Color){100, 200, 255, 255} + : (Color){255, 150, 100, 255}; - DrawText(TextFormat("Ammo: P %d/%d | R %d/%d (E inv, F pick, G drop, " - "C/V use, R reload, Z/X switch)", - rp[myId].ammoMag[WEAPON_PISTOL], - rp[myId].ammoRes[WEAPON_PISTOL], - rp[myId].ammoMag[WEAPON_RIFLE], - rp[myId].ammoRes[WEAPON_RIFLE]), - 10, 36, 18, (Color){200, 200, 210, 255}); + int currentMag = (rp[myId].weapon == WEAPON_PISTOL) ? rp[myId].pistolMag + : rp[myId].rifleMag; + int reserveAmmo = (rp[myId].weapon == WEAPON_PISTOL) ? rp[myId].pistolAmmo + : rp[myId].rifleAmmo; - if (invOpen) { - DrawRectangle(20, 70, 420, 190, (Color){10, 10, 10, 180}); - DrawRectangleLines(20, 70, 420, 190, (Color){200, 200, 210, 210}); - DrawText("Inventory (slots 1-6)", 32, 80, 22, RAYWHITE); + DrawRectangle(sw - 260, sh - 120, 250, 110, (Color){0, 0, 0, 180}); + DrawText(weaponName, sw - 250, sh - 110, 28, weaponColor); - for (int sidx = 0; sidx < INV_SLOTS; sidx++) { - Color sc = (sidx == selectedSlot) ? (Color){80, 180, 255, 255} - : (Color){220, 220, 230, 255}; - const char *nm = ItemName(rp[myId].invType[sidx]); - int qty = rp[myId].invQty[sidx]; - DrawText(TextFormat("[%d] %s x%d", sidx + 1, nm, qty), 32, - 110 + sidx * 22, 18, sc); - } - DrawText("Use: C or V | Drop: G | Pick: F", 32, - 110 + INV_SLOTS * 22 + 6, 18, (Color){210, 210, 220, 255}); + char magText[16]; + snprintf(magText, sizeof(magText), "%d", currentMag); + DrawText(magText, sw - 250, sh - 75, 40, RAYWHITE); + + char reserveText[32]; + snprintf(reserveText, sizeof(reserveText), "/ %d", reserveAmmo); + DrawText(reserveText, sw - 140, sh - 65, 24, (Color){180, 180, 180, 255}); + + if (currentMag == 0) { + DrawText("RELOAD!", sw - 250, sh - 30, 20, (Color){255, 80, 80, 255}); } } DrawFPS(sw - 90, 10); - // crosshair + // Crosshair { int cx = sw / 2, cy = sh / 2; int gap = 6 + (int)crossSpread; int len = 10, thick = 2; Color col = (Color){240, 240, 245, 220}; - DrawRectangle(cx - gap - len, cy - thick / 2, len, thick, col); DrawRectangle(cx + gap, cy - thick / 2, len, thick, col); DrawRectangle(cx - thick / 2, cy - gap - len, thick, len, col); diff --git a/game/output.md b/game/output.md deleted file mode 100644 index e69de29..0000000 diff --git a/game/run.sh b/game/run.sh new file mode 100755 index 0000000..ecc5d8d --- /dev/null +++ b/game/run.sh @@ -0,0 +1,7 @@ +gcc -O3 client.c -o client -lm -lraylib +gcc -O3 server.c -o server -lm -lraylib -lode + +./server & +server_pid=$! +./client +kill $server_pid diff --git a/game/server b/game/server new file mode 100755 index 0000000000000000000000000000000000000000..4bb23844d50566ad20ba94aa98d1ca430c1adc1d GIT binary patch literal 30640 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 literal 0 HcmV?d00001 diff --git a/game/server.c b/game/server.c index 9b2e6f7..4869de1 100644 --- a/game/server.c +++ b/game/server.c @@ -1,7 +1,3 @@ -// server.c - UDP authoritative server + ODE physics + ODE raycast bullets (C99) -// Adds: true spray, inventory capacity (6 slots), world pickups, drop/pick/use, -// armor+medkits. Build (Linux example): gcc server.c -O2 -o server -lode -lm - #include #include #include @@ -26,7 +22,7 @@ typedef int socklen_t; #define CLOSESOCK close #endif -#define PROTOCOL_VERSION 4 +#define PROTOCOL_VERSION 5 #define SERVER_PORT 27015 #define MAX_PLAYERS 16 #define USERNAME_MAX 16 @@ -36,27 +32,20 @@ typedef int socklen_t; #define WEAPON_PISTOL 0 #define WEAPON_RIFLE 1 -#define WEAPON_COUNT 2 -#define INV_SLOTS 6 #define MAX_ITEMS 64 -// inventory item types #define ITEM_NONE 0 #define ITEM_MEDKIT 1 -#define ITEM_ARMORPLATE 2 -#define ITEM_AMMO_PISTOL 3 -#define ITEM_AMMO_RIFLE 4 +#define ITEM_AMMO_PISTOL 2 +#define ITEM_AMMO_RIFLE 3 -// MsgInput.buttons bits #define BTN_RELOAD (1u << 0) -#define BTN_WPN1 (1u << 1) -#define BTN_WPN2 (1u << 2) +#define BTN_SWITCH_PISTOL (1u << 1) +#define BTN_SWITCH_RIFLE (1u << 2) #define BTN_PICK (1u << 3) -#define BTN_DROP (1u << 4) -#define BTN_USE (1u << 5) +#define BTN_USE_MEDKIT (1u << 4) -// Collision categories #define CAT_WORLD (1u << 0) #define CAT_PLAYER (1u << 1) #define CAT_ITEM (1u << 2) @@ -96,8 +85,7 @@ typedef enum MsgType : uint8_t { MSG_INPUT = 3, MSG_SNAPSHOT = 4, MSG_SHOOT = 5, - MSG_HIT = 6, - MSG_ITEMS = 7 + MSG_ITEMS = 6 } MsgType; typedef struct MsgHello { @@ -121,7 +109,6 @@ typedef struct MsgInput { float yaw; float pitch; uint8_t buttons; - uint8_t slotIndex; } MsgInput; typedef struct MsgShoot { @@ -134,16 +121,15 @@ typedef struct PlayerStateNet { uint8_t id; uint8_t alive; int16_t hp; - int16_t armor; float x, y, z; float yaw, pitch; uint8_t weapon; - int16_t ammoMag[WEAPON_COUNT]; - int16_t ammoRes[WEAPON_COUNT]; - - uint8_t invType[INV_SLOTS]; - int16_t invQty[INV_SLOTS]; + int16_t pistolMag; + int16_t rifleMag; + int16_t pistolAmmo; + int16_t rifleAmmo; + int16_t medkits; char username[USERNAME_MAX]; } PlayerStateNet; @@ -155,13 +141,6 @@ typedef struct MsgSnapshot { PlayerStateNet p[MAX_PLAYERS]; } MsgSnapshot; -typedef struct MsgHit { - uint8_t type; - uint8_t shooterId; - uint8_t victimId; - int16_t victimHp; -} MsgHit; - typedef struct ItemNet { uint16_t id; uint8_t type; @@ -183,7 +162,7 @@ typedef struct Item { uint8_t type; int qty; float x, y, z; - dGeomID geom; // static pickup geom (no body) + dGeomID geom; } Item; typedef struct Player { @@ -198,14 +177,13 @@ typedef struct Player { int alive; int hp; - int armor; int weapon; - int ammoMag[WEAPON_COUNT]; - int ammoRes[WEAPON_COUNT]; - - uint8_t invType[INV_SLOTS]; - int invQty[INV_SLOTS]; + int pistolMag; + int rifleMag; + int pistolAmmo; + int rifleAmmo; + int medkits; double lastShotTime; int shotsInBurst; @@ -241,12 +219,10 @@ static void broadcast(int sock, const Player players[MAX_PLAYERS], sendto_player(sock, &players[i], data, len); } -// ----- ODE globals ----- static dWorldID gWorld; static dSpaceID gSpace; static dJointGroupID gContactGroup; -// items static Item gItems[MAX_ITEMS]; static uint16_t gNextItemId = 1; @@ -257,6 +233,7 @@ static void item_destroy(Item *it) { } memset(it, 0, sizeof(*it)); } + static int item_spawn(uint8_t type, int qty, float x, float y, float z) { for (int i = 0; i < MAX_ITEMS; i++) { if (!gItems[i].active) { @@ -272,11 +249,8 @@ static int item_spawn(uint8_t type, int qty, float x, float y, float z) { it->geom = dCreateSphere(gSpace, 0.30); dGeomSetPosition(it->geom, x, y + 0.3f, z); - // Mark as item and avoid physical contacts; also make raycasts ignore it. dGeomSetCategoryBits(it->geom, CAT_ITEM); dGeomSetCollideBits(it->geom, 0); - - // Attach "user data" pointing to Item for identification if needed. dGeomSetData(it->geom, it); return i; @@ -285,55 +259,6 @@ static int item_spawn(uint8_t type, int qty, float x, float y, float z) { return -1; } -static int inv_add(Player *p, uint8_t type, int qty) { - // stacking limits - int maxStack = 99; - if (type == ITEM_MEDKIT) - maxStack = 3; - if (type == ITEM_ARMORPLATE) - maxStack = 5; - - // try stack - for (int i = 0; i < INV_SLOTS; i++) { - if (p->invType[i] == type && p->invQty[i] < maxStack) { - int can = maxStack - p->invQty[i]; - int take = (qty < can) ? qty : can; - p->invQty[i] += take; - qty -= take; - if (qty <= 0) - return 1; - } - } - - // place into empty slots - for (int i = 0; i < INV_SLOTS; i++) { - if (p->invType[i] == ITEM_NONE) { - int take = (qty < maxStack) ? qty : maxStack; - p->invType[i] = type; - p->invQty[i] = take; - qty -= take; - if (qty <= 0) - return 1; - } - } - - // no space - return 0; -} - -static int inv_remove_one(Player *p, int slot) { - if (slot < 0 || slot >= INV_SLOTS) - return 0; - if (p->invType[slot] == ITEM_NONE || p->invQty[slot] <= 0) - return 0; - p->invQty[slot]--; - if (p->invQty[slot] <= 0) { - p->invQty[slot] = 0; - p->invType[slot] = ITEM_NONE; - } - return 1; -} - static void nearCallback(void *user, dGeomID o1, dGeomID o2) { (void)user; @@ -342,7 +267,6 @@ static void nearCallback(void *user, dGeomID o1, dGeomID o2) { return; } - // Do not create contact joints involving item pickups unsigned long c1 = dGeomGetCategoryBits(o1); unsigned long c2 = dGeomGetCategoryBits(o2); if ((c1 & CAT_ITEM) || (c2 & CAT_ITEM)) @@ -396,10 +320,9 @@ static void ode_init_world(void) { dGeomSetCollideBits(b, CAT_PLAYER); } - // spawn some test pickups + // Spawn test items item_spawn(ITEM_MEDKIT, 1, 1.0f, 0.0f, 1.0f); item_spawn(ITEM_MEDKIT, 1, -2.0f, 0.0f, 3.0f); - item_spawn(ITEM_ARMORPLATE, 1, 3.0f, 0.0f, -1.0f); item_spawn(ITEM_AMMO_RIFLE, 30, 0.0f, 0.0f, 2.0f); item_spawn(ITEM_AMMO_PISTOL, 12, -1.0f, 0.0f, -2.0f); } @@ -445,7 +368,6 @@ static void player_ode_destroy(Player *p) { } } -// ---- ODE raycast ---- typedef struct RaycastCtx { dGeomID ray; dGeomID hitGeom; @@ -493,7 +415,6 @@ static int raycast_space(float ox, float oy, float oz, float dx, float dy, return 1; } -// ---- spray math helpers ---- static uint32_t xorshift32(uint32_t *s) { uint32_t x = *s; x ^= x << 13; @@ -582,7 +503,7 @@ int main(void) { double nextTick = now_seconds(); for (;;) { - // receive + // Receive for (;;) { uint8_t buf[1400]; struct sockaddr_in from = {0}; @@ -629,18 +550,13 @@ int main(void) { p->alive = 1; p->hp = 100; - p->armor = 0; p->weapon = WEAPON_RIFLE; - p->ammoMag[WEAPON_PISTOL] = 12; - p->ammoRes[WEAPON_PISTOL] = 48; - p->ammoMag[WEAPON_RIFLE] = 30; - p->ammoRes[WEAPON_RIFLE] = 90; - - for (int sidx = 0; sidx < INV_SLOTS; sidx++) { - p->invType[sidx] = ITEM_NONE; - p->invQty[sidx] = 0; - } + p->pistolMag = 12; + p->rifleMag = 30; + p->pistolAmmo = 48; + p->rifleAmmo = 90; + p->medkits = 2; p->lastShotTime = 0.0; p->shotsInBurst = 0; @@ -673,24 +589,32 @@ int main(void) { p->yaw = m->yaw; p->pitch = clampf(m->pitch, -1.5f, 1.5f); - if (m->buttons & BTN_WPN1) + // Weapon switch + if (m->buttons & BTN_SWITCH_PISTOL) p->weapon = WEAPON_PISTOL; - if (m->buttons & BTN_WPN2) + if (m->buttons & BTN_SWITCH_RIFLE) p->weapon = WEAPON_RIFLE; - // reload + // Reload if (m->buttons & BTN_RELOAD) { - int wpn = p->weapon; - int magSize = (wpn == WEAPON_PISTOL) ? 12 : 30; - int need = magSize - p->ammoMag[wpn]; - if (need > 0 && p->ammoRes[wpn] > 0) { - int take = (p->ammoRes[wpn] < need) ? p->ammoRes[wpn] : need; - p->ammoRes[wpn] -= take; - p->ammoMag[wpn] += take; + if (p->weapon == WEAPON_PISTOL) { + int need = 12 - p->pistolMag; + if (need > 0 && p->pistolAmmo > 0) { + int take = (p->pistolAmmo < need) ? p->pistolAmmo : need; + p->pistolAmmo -= take; + p->pistolMag += take; + } + } else { + int need = 30 - p->rifleMag; + if (need > 0 && p->rifleAmmo > 0) { + int take = (p->rifleAmmo < need) ? p->rifleAmmo : need; + p->rifleAmmo -= take; + p->rifleMag += take; + } } } - // pick + // Pick item if (m->buttons & BTN_PICK) { const dReal *pos = dBodyGetPosition(p->body); int itIdx = find_nearest_item((float)pos[0], (float)pos[1] + 0.7f, @@ -700,13 +624,20 @@ int main(void) { int taken = 0; if (it->type == ITEM_AMMO_PISTOL) { - p->ammoRes[WEAPON_PISTOL] += it->qty; + p->pistolAmmo += it->qty; + if (p->pistolAmmo > 120) + p->pistolAmmo = 120; taken = 1; } else if (it->type == ITEM_AMMO_RIFLE) { - p->ammoRes[WEAPON_RIFLE] += it->qty; + p->rifleAmmo += it->qty; + if (p->rifleAmmo > 300) + p->rifleAmmo = 300; + taken = 1; + } else if (it->type == ITEM_MEDKIT) { + p->medkits += it->qty; + if (p->medkits > 10) + p->medkits = 10; taken = 1; - } else { - taken = inv_add(p, it->type, it->qty); } if (taken) @@ -714,38 +645,13 @@ int main(void) { } } - // drop selected slot - if (m->buttons & BTN_DROP) { - int slot = (m->slotIndex < INV_SLOTS) ? (int)m->slotIndex : 0; - uint8_t t = p->invType[slot]; - if (t != ITEM_NONE && p->invQty[slot] > 0) { - const dReal *pos = dBodyGetPosition(p->body); - float ox = (float)pos[0]; - float oy = (float)pos[1]; - float oz = (float)pos[2]; - - // drop 1 unit - if (inv_remove_one(p, slot)) { - item_spawn(t, 1, ox, oy, oz); - } - } - } - - // use selected slot (medkit/armor plate) - if (m->buttons & BTN_USE) { - int slot = (m->slotIndex < INV_SLOTS) ? (int)m->slotIndex : 0; - uint8_t t = p->invType[slot]; - if (t == ITEM_MEDKIT && p->hp < 100 && p->invQty[slot] > 0) { - inv_remove_one(p, slot); + // Use medkit + if (m->buttons & BTN_USE_MEDKIT) { + if (p->medkits > 0 && p->hp < 100) { + p->medkits--; p->hp += 25; if (p->hp > 100) p->hp = 100; - } else if (t == ITEM_ARMORPLATE && p->armor < 100 && - p->invQty[slot] > 0) { - inv_remove_one(p, slot); - p->armor += 25; - if (p->armor > 100) - p->armor = 100; } } } @@ -763,11 +669,12 @@ int main(void) { if (now - sh->lastShotTime > 0.18) sh->shotsInBurst = 0; - int wpn = sh->weapon; - if (sh->ammoMag[wpn] <= 0) + int *mag = + (sh->weapon == WEAPON_PISTOL) ? &sh->pistolMag : &sh->rifleMag; + if (*mag <= 0) continue; - sh->ammoMag[wpn]--; + (*mag)--; sh->shotsInBurst++; sh->lastShotTime = now; @@ -780,8 +687,8 @@ int main(void) { dir_from_yaw_pitch(sh->yaw, sh->pitch, &fx, &fy, &fz); v3_normf(&fx, &fy, &fz); - float base = (wpn == WEAPON_PISTOL) ? 0.0035f : 0.0080f; - float grow = (wpn == WEAPON_PISTOL) ? 0.0010f : 0.0025f; + float base = (sh->weapon == WEAPON_PISTOL) ? 0.0035f : 0.0080f; + float grow = (sh->weapon == WEAPON_PISTOL) ? 0.0010f : 0.0025f; float spread = base + grow * (float)sh->shotsInBurst; if (spread > 0.08f) spread = 0.08f; @@ -817,148 +724,127 @@ int main(void) { if (victim >= 0 && victim != pid && players[victim].alive) { int dmg = 25; - - // armor absorbs first (simple model) - int absorb = - (players[victim].armor < dmg) ? players[victim].armor : dmg; - players[victim].armor -= absorb; - dmg -= absorb; - players[victim].hp -= dmg; if (players[victim].hp <= 0) { players[victim].hp = 0; players[victim].alive = 0; } - - MsgHit ev = {0}; - ev.type = MSG_HIT; - ev.shooterId = (uint8_t)pid; - ev.victimId = (uint8_t)victim; - ev.victimHp = (int16_t)players[victim].hp; - broadcast(sock, players, &ev, (int)sizeof(ev)); } } } } } - // fixed tick + // Tick double t = now_seconds(); - if (t < nextTick) { -#ifdef _WIN32 - Sleep(1); -#else - struct timespec ts = {0, 1000000}; - nanosleep(&ts, NULL); -#endif - continue; - } - nextTick += (1.0 / (double)TICK_HZ); - serverTick++; + if (t >= nextTick) { + nextTick += DT; + serverTick++; - // timeouts + movement - const float speed = 6.0f; - for (int i = 0; i < MAX_PLAYERS; i++) { - Player *p = &players[i]; - if (!p->inUse) - continue; + dSpaceCollide(gSpace, 0, &nearCallback); + dWorldQuickStep(gWorld, DT); + dJointGroupEmpty(gContactGroup); - if (now_seconds() - p->lastHeard > 10.0) { - printf("Player %d timed out\n", i); - player_ode_destroy(p); - memset(p, 0, sizeof(*p)); - continue; - } - - if (!p->alive) { - dBodySetLinearVel(p->body, 0, 0, 0); - continue; - } - - float cy = cosf(p->yaw), sy = sinf(p->yaw); - float fwdx = sy, fwdz = cy; - float rgtx = cy, rgtz = -sy; - - float vx = (fwdx * p->moveZ + rgtx * p->moveX) * speed; - float vz = (fwdz * p->moveZ + rgtz * p->moveX) * speed; - - const dReal *vcur = dBodyGetLinearVel(p->body); - dBodySetLinearVel(p->body, vx, vcur[1], vz); - - dQuaternion q; - dQFromAxisAndAngle(q, 0, 1, 0, p->yaw); - dBodySetQuaternion(p->body, q); - dBodySetAngularVel(p->body, 0, 0, 0); - } - - // step ODE - dJointGroupEmpty(gContactGroup); - dSpaceCollide(gSpace, 0, &nearCallback); - dWorldQuickStep(gWorld, DT); - - // snapshot players - MsgSnapshot snap = {0}; - snap.type = MSG_SNAPSHOT; - snap.serverTick = serverTick; - snap.count = 0; - - for (int i = 0; i < MAX_PLAYERS; i++) { - if (!players[i].inUse) - continue; - - const dReal *pos = dBodyGetPosition(players[i].body); - PlayerStateNet *ps = &snap.p[snap.count++]; - - ps->id = (uint8_t)i; - ps->alive = (uint8_t)players[i].alive; - ps->hp = (int16_t)players[i].hp; - ps->armor = (int16_t)players[i].armor; - - ps->x = (float)pos[0]; - ps->y = (float)pos[1]; - ps->z = (float)pos[2]; - ps->yaw = players[i].yaw; - ps->pitch = players[i].pitch; - - ps->weapon = (uint8_t)players[i].weapon; - for (int w = 0; w < WEAPON_COUNT; w++) { - ps->ammoMag[w] = (int16_t)players[i].ammoMag[w]; - ps->ammoRes[w] = (int16_t)players[i].ammoRes[w]; - } - for (int sidx = 0; sidx < INV_SLOTS; sidx++) { - ps->invType[sidx] = players[i].invType[sidx]; - ps->invQty[sidx] = (int16_t)players[i].invQty[sidx]; - } - - memset(ps->username, 0, USERNAME_MAX); - strncpy(ps->username, players[i].username, USERNAME_MAX - 1); - } - - broadcast(sock, players, &snap, (int)sizeof(MsgSnapshot)); - - // broadcast items at ~10Hz - itemsTickDiv++; - if (itemsTickDiv >= 6) { - itemsTickDiv = 0; - - MsgItems im = {0}; - im.type = MSG_ITEMS; - im.serverTick = serverTick; - im.count = 0; - - for (int i = 0; i < MAX_ITEMS && im.count < MAX_ITEMS; i++) { - if (!gItems[i].active) + for (int i = 0; i < MAX_PLAYERS; i++) { + Player *p = &players[i]; + if (!p->inUse || !p->alive) continue; - ItemNet *on = &im.items[im.count++]; - on->id = gItems[i].id; - on->type = gItems[i].type; - on->qty = (int16_t)gItems[i].qty; - on->x = gItems[i].x; - on->y = gItems[i].y; - on->z = gItems[i].z; + + const dReal *lvel = dBodyGetLinearVel(p->body); + const dReal *avel = dBodyGetAngularVel(p->body); + + float dx = p->moveX; + float dz = p->moveZ; + float len = sqrtf(dx * dx + dz * dz); + if (len > 1e-6f) { + dx /= len; + dz /= len; + } + + float speed = 5.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]); } - broadcast(sock, players, &im, (int)sizeof(MsgItems)); + // Send snapshot + MsgSnapshot snap = {0}; + snap.type = MSG_SNAPSHOT; + snap.serverTick = serverTick; + snap.count = 0; + + for (int i = 0; i < MAX_PLAYERS; i++) { + if (!players[i].inUse) + continue; + + const dReal *pos = dBodyGetPosition(players[i].body); + + PlayerStateNet *ps = &snap.p[snap.count++]; + ps->id = (uint8_t)i; + ps->alive = players[i].alive; + ps->hp = (int16_t)players[i].hp; + ps->x = (float)pos[0]; + ps->y = (float)pos[1]; + ps->z = (float)pos[2]; + ps->yaw = players[i].yaw; + ps->pitch = players[i].pitch; + ps->weapon = players[i].weapon; + ps->pistolMag = (int16_t)players[i].pistolMag; + ps->rifleMag = (int16_t)players[i].rifleMag; + ps->pistolAmmo = (int16_t)players[i].pistolAmmo; + ps->rifleAmmo = (int16_t)players[i].rifleAmmo; + ps->medkits = (int16_t)players[i].medkits; + strncpy(ps->username, players[i].username, USERNAME_MAX - 1); + } + + broadcast(sock, players, &snap, (int)sizeof(snap)); + + // Send items periodically + itemsTickDiv++; + if (itemsTickDiv >= 6) { + itemsTickDiv = 0; + + MsgItems im = {0}; + im.type = MSG_ITEMS; + im.serverTick = serverTick; + im.count = 0; + + for (int i = 0; i < MAX_ITEMS; i++) { + if (!gItems[i].active) + continue; + ItemNet *it = &im.items[im.count++]; + it->id = gItems[i].id; + it->type = gItems[i].type; + it->qty = (int16_t)gItems[i].qty; + it->x = gItems[i].x; + it->y = gItems[i].y; + it->z = gItems[i].z; + } + + broadcast(sock, players, &im, (int)sizeof(im)); + } + } + + // Timeout check + double now = now_seconds(); + for (int i = 0; i < MAX_PLAYERS; i++) { + if (players[i].inUse && (now - players[i].lastHeard) > 10.0) { + printf("Player %d timed out\n", i); + player_ode_destroy(&players[i]); + memset(&players[i], 0, sizeof(Player)); + } + } + + // Sleep + double wait = nextTick - now_seconds(); + if (wait > 0.0) { +#ifdef _WIN32 + Sleep((DWORD)(wait * 1000.0)); +#else + usleep((useconds_t)(wait * 1e6)); +#endif } } From 453e9fd5e8e0fe99ada16090709f1a8c1b7dc14c Mon Sep 17 00:00:00 2001 From: Masashi Date: Thu, 18 Dec 2025 20:39:46 +0530 Subject: [PATCH 2/4] REAL GAM --- game/client | Bin 25488 -> 29920 bytes game/client.c | 353 +++++++++++++++++++++++++++++++++++--------------- game/server | Bin 30640 -> 35160 bytes game/server.c | 308 ++++++++++++++++++++++++------------------- 4 files changed, 421 insertions(+), 240 deletions(-) diff --git a/game/client b/game/client index 739731c1938e18f0956cfe6479f7ee32af701aa0..2164e961c07d9c3ec33b1bdec0bbc0c8aa77690a 100755 GIT binary patch literal 29920 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 literal 25488 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 diff --git a/game/client.c b/game/client.c index 4db9112..33a9dc0 100644 --- a/game/client.c +++ b/game/client.c @@ -1,3 +1,4 @@ +// game/client.c - SMOOTH TERRAIN VERSION #include "raylib.h" #include #include @@ -28,7 +29,6 @@ typedef int socklen_t; #define WEAPON_PISTOL 0 #define WEAPON_RIFLE 1 -// Item types #define ITEM_NONE 0 #define ITEM_MEDKIT 1 #define ITEM_AMMO_PISTOL 2 @@ -36,13 +36,226 @@ typedef int socklen_t; #define MAX_ITEMS 64 -// Input buttons #define BTN_RELOAD (1u << 0) #define BTN_SWITCH_PISTOL (1u << 1) #define BTN_SWITCH_RIFLE (1u << 2) #define BTN_PICK (1u << 3) #define BTN_USE_MEDKIT (1u << 4) +// ============== SIMPLEX NOISE ============== +static const uint8_t perm[512] = { + 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, + 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, + 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, + 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, + 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, + 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, + 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, + 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, + 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, + 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, + 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, + 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, + 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, + 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, + 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, + 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, + 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, + 180, 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, + 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, + 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, + 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, + 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, + 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, + 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, + 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, + 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, + 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, + 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, + 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, + 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, + 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, + 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, + 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, + 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, + 156, 180}; + +static float grad2(int hash, float x, float y) { + int h = hash & 7; + float u = h < 4 ? x : y; + float v = h < 4 ? y : x; + return ((h & 1) ? -u : u) + ((h & 2) ? -2.0f * v : 2.0f * v); +} + +static float simplex_noise_2d(float x, float y) { + const float F2 = 0.366025403f; + const float G2 = 0.211324865f; + float s = (x + y) * F2; + int i = (int)floorf(x + s); + int j = (int)floorf(y + s); + float t = (i + j) * G2; + float X0 = i - t, Y0 = j - t; + float x0 = x - X0, y0 = y - Y0; + int i1 = (x0 > y0) ? 1 : 0; + int j1 = (x0 > y0) ? 0 : 1; + float x1 = x0 - i1 + G2, y1 = y0 - j1 + G2; + float x2 = x0 - 1.0f + 2.0f * G2, y2 = y0 - 1.0f + 2.0f * G2; + int ii = i & 255, jj = j & 255; + float n0 = 0.0f, n1 = 0.0f, n2 = 0.0f; + float t0 = 0.5f - x0 * x0 - y0 * y0; + if (t0 >= 0.0f) { + t0 *= t0; + n0 = t0 * t0 * grad2(perm[ii + perm[jj]], x0, y0); + } + float t1 = 0.5f - x1 * x1 - y1 * y1; + if (t1 >= 0.0f) { + t1 *= t1; + n1 = t1 * t1 * grad2(perm[ii + i1 + perm[jj + j1]], x1, y1); + } + float t2 = 0.5f - x2 * x2 - y2 * y2; + if (t2 >= 0.0f) { + t2 *= t2; + n2 = t2 * t2 * grad2(perm[ii + 1 + perm[jj + 1]], x2, y2); + } + return 45.0f * (n0 + n1 + n2); +} + +static float fbm_noise(float x, float y, int octaves) { + float value = 0.0f, amplitude = 1.0f, frequency = 1.0f, max_value = 0.0f; + for (int i = 0; i < octaves; i++) { + value += simplex_noise_2d(x * frequency, y * frequency) * amplitude; + max_value += amplitude; + amplitude *= 0.5f; + frequency *= 2.0f; + } + return value / max_value; +} + +float get_terrain_height(float x, float z) { + float height = fbm_noise(x * 0.03f, z * 0.03f, 4); + height += fbm_noise(x * 0.01f, z * 0.01f, 2) * 1.5f; + return height * 6.0f + 2.0f; +} + +#define TERRAIN_SIZE 64 +#define TERRAIN_SCALE 2.0f + +static Mesh generate_terrain_mesh(void) { + int size = TERRAIN_SIZE; + Mesh mesh = {0}; + + int vertexCount = size * size; + int triangleCount = (size - 1) * (size - 1) * 2; + + mesh.vertexCount = vertexCount; + mesh.triangleCount = triangleCount; + + mesh.vertices = (float *)MemAlloc(vertexCount * 3 * sizeof(float)); + mesh.texcoords = (float *)MemAlloc(vertexCount * 2 * sizeof(float)); + mesh.normals = (float *)MemAlloc(vertexCount * 3 * sizeof(float)); + mesh.indices = + (unsigned short *)MemAlloc(triangleCount * 3 * sizeof(unsigned short)); + + // Generate vertices + for (int z = 0; z < size; z++) { + for (int x = 0; x < size; x++) { + int idx = z * size + x; + float wx = ((float)x - size / 2.0f) * TERRAIN_SCALE; + float wz = ((float)z - size / 2.0f) * TERRAIN_SCALE; + float wy = get_terrain_height(wx, wz); + + mesh.vertices[idx * 3 + 0] = wx; + mesh.vertices[idx * 3 + 1] = wy; + mesh.vertices[idx * 3 + 2] = wz; + + mesh.texcoords[idx * 2 + 0] = (float)x / (float)(size - 1); + mesh.texcoords[idx * 2 + 1] = (float)z / (float)(size - 1); + } + } + + // Generate indices + int triIdx = 0; + for (int z = 0; z < size - 1; z++) { + for (int x = 0; x < size - 1; x++) { + int i0 = z * size + x; + int i1 = z * size + (x + 1); + int i2 = (z + 1) * size + x; + int i3 = (z + 1) * size + (x + 1); + + mesh.indices[triIdx * 3 + 0] = i0; + mesh.indices[triIdx * 3 + 1] = i2; + mesh.indices[triIdx * 3 + 2] = i1; + triIdx++; + + mesh.indices[triIdx * 3 + 0] = i1; + mesh.indices[triIdx * 3 + 1] = i2; + mesh.indices[triIdx * 3 + 2] = i3; + triIdx++; + } + } + + // Calculate normals + for (int i = 0; i < vertexCount; i++) { + mesh.normals[i * 3 + 0] = 0; + mesh.normals[i * 3 + 1] = 1; + mesh.normals[i * 3 + 2] = 0; + } + + for (int t = 0; t < triangleCount; t++) { + int i0 = mesh.indices[t * 3 + 0]; + int i1 = mesh.indices[t * 3 + 1]; + int i2 = mesh.indices[t * 3 + 2]; + + float v0x = mesh.vertices[i0 * 3 + 0]; + float v0y = mesh.vertices[i0 * 3 + 1]; + float v0z = mesh.vertices[i0 * 3 + 2]; + + float v1x = mesh.vertices[i1 * 3 + 0]; + float v1y = mesh.vertices[i1 * 3 + 1]; + float v1z = mesh.vertices[i1 * 3 + 2]; + + float v2x = mesh.vertices[i2 * 3 + 0]; + float v2y = mesh.vertices[i2 * 3 + 1]; + float v2z = mesh.vertices[i2 * 3 + 2]; + + float e1x = v1x - v0x, e1y = v1y - v0y, e1z = v1z - v0z; + float e2x = v2x - v0x, e2y = v2y - v0y, e2z = v2z - v0z; + + float nx = e1y * e2z - e1z * e2y; + float ny = e1z * e2x - e1x * e2z; + float nz = e1x * e2y - e1y * e2x; + + mesh.normals[i0 * 3 + 0] += nx; + mesh.normals[i0 * 3 + 1] += ny; + mesh.normals[i0 * 3 + 2] += nz; + + mesh.normals[i1 * 3 + 0] += nx; + mesh.normals[i1 * 3 + 1] += ny; + mesh.normals[i1 * 3 + 2] += nz; + + mesh.normals[i2 * 3 + 0] += nx; + mesh.normals[i2 * 3 + 1] += ny; + mesh.normals[i2 * 3 + 2] += nz; + } + + // Normalize normals + for (int i = 0; i < vertexCount; i++) { + float nx = mesh.normals[i * 3 + 0]; + float ny = mesh.normals[i * 3 + 1]; + float nz = mesh.normals[i * 3 + 2]; + float len = sqrtf(nx * nx + ny * ny + nz * nz); + if (len > 0.0001f) { + mesh.normals[i * 3 + 0] = nx / len; + mesh.normals[i * 3 + 1] = ny / len; + mesh.normals[i * 3 + 2] = nz / len; + } + } + + UploadMesh(&mesh, false); + return mesh; +} +// ============== END SIMPLEX NOISE ============== + static void set_nonblocking(int sock) { #ifdef _WIN32 u_long mode = 1; @@ -79,10 +292,7 @@ typedef struct MsgInput { uint8_t type; uint8_t playerId; uint32_t clientTick; - float moveX; - float moveZ; - float yaw; - float pitch; + float moveX, moveZ, yaw, pitch; uint8_t buttons; } MsgInput; @@ -93,19 +303,11 @@ typedef struct MsgShoot { } MsgShoot; typedef struct PlayerStateNet { - uint8_t id; - uint8_t alive; + uint8_t id, alive; int16_t hp; - float x, y, z; - float yaw, pitch; - + float x, y, z, yaw, pitch; uint8_t weapon; - int16_t pistolMag; - int16_t rifleMag; - int16_t pistolAmmo; - int16_t rifleAmmo; - int16_t medkits; - + int16_t pistolMag, rifleMag, pistolAmmo, rifleAmmo, medkits; char username[USERNAME_MAX]; } PlayerStateNet; @@ -132,19 +334,11 @@ typedef struct MsgItems { #pragma pack(pop) typedef struct RemotePlayer { - int present; - int alive; - int hp; + int present, alive, hp; Vector3 pos; float yaw, pitch; - uint8_t weapon; - int16_t pistolMag; - int16_t rifleMag; - int16_t pistolAmmo; - int16_t rifleAmmo; - int16_t medkits; - + int16_t pistolMag, rifleMag, pistolAmmo, rifleAmmo, medkits; char username[USERNAME_MAX]; } RemotePlayer; @@ -172,14 +366,11 @@ static float v3_dot(Vector3 a, Vector3 b) { static float v3_len(Vector3 a) { return sqrtf(a.x * a.x + a.y * a.y + a.z * a.z); } -static Vector3 v3_scale(Vector3 a, float s) { - return v3(a.x * s, a.y * s, a.z * s); -} static Vector3 v3_norm(Vector3 a) { float l = v3_len(a); if (l <= 1e-6f) return v3(0, 0, 1); - return v3_scale(a, 1.0f / l); + return v3(a.x / l, a.y / l, a.z / l); } static const char *ItemName(uint8_t t) { @@ -235,7 +426,7 @@ int main(void) { #endif const int sw = 1280, sh = 720; - InitWindow(sw, sh, "Voxel Shooter - Client"); + InitWindow(sw, sh, "Voxel Shooter - Client (SMOOTH TERRAIN)"); SetTargetFPS(120); char myName[USERNAME_MAX]; @@ -243,6 +434,12 @@ int main(void) { DisableCursor(); + // Generate terrain mesh + Mesh terrainMesh = generate_terrain_mesh(); + Model terrainModel = LoadModelFromMesh(terrainMesh); + terrainModel.materials[0].maps[MATERIAL_MAP_DIFFUSE].color = + (Color){80, 140, 70, 255}; + int sock = (int)socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (sock < 0) { perror("socket"); @@ -261,33 +458,20 @@ int main(void) { RemotePlayer rp[MAX_PLAYERS] = {0}; WorldItem wi[MAX_ITEMS] = {0}; - Vector3 camPos = v3(0, 1.7f, 6); - float yaw = 0.0f; - float pitch = 0.0f; - - float recoilYaw = 0.0f, recoilPitch = 0.0f; - float crossSpread = 0.0f; + Vector3 camPos = v3(0, 5.0f, 6); + float yaw = 0.0f, pitch = 0.0f; + float recoilYaw = 0.0f, recoilPitch = 0.0f, crossSpread = 0.0f; float fireCooldown = 0.0f; int shotsInBurst = 0; float burstResetTimer = 0.0f; - const float pistolFireRate = 4.0f; - const float rifleFireRate = 12.0f; - const float recoilReturn = 18.0f; - const float crossReturn = 14.0f; - - const float pistolKickPitch = 0.010f; - const float pistolKickYaw = 0.004f; - const float rifleKickPitch = 0.018f; - const float rifleKickYaw = 0.010f; - - const float pistolCrossKick = 2.0f; - const float rifleCrossKick = 4.0f; - - const float pistolSprayGrow = 0.4f; - const float rifleSprayGrow = 1.2f; - + const float pistolFireRate = 4.0f, rifleFireRate = 12.0f; + const float recoilReturn = 18.0f, crossReturn = 14.0f; + const float pistolKickPitch = 0.010f, pistolKickYaw = 0.004f; + const float rifleKickPitch = 0.018f, rifleKickYaw = 0.010f; + const float pistolCrossKick = 2.0f, rifleCrossKick = 4.0f; + const float pistolSprayGrow = 0.4f, rifleSprayGrow = 1.2f; const float burstResetTime = 0.18f; MsgHello hello = {0}; @@ -320,7 +504,6 @@ int main(void) { crossSpread = 0.0f; } - // Receive packets for (;;) { uint8_t buf[1400]; struct sockaddr_in from = {0}; @@ -365,7 +548,6 @@ int main(void) { p->pistolAmmo = ps->pistolAmmo; p->rifleAmmo = ps->rifleAmmo; p->medkits = ps->medkits; - memset(p->username, 0, USERNAME_MAX); strncpy(p->username, ps->username, USERNAME_MAX - 1); } @@ -390,7 +572,6 @@ int main(void) { } } - // Mouse look Vector2 md = GetMouseDelta(); const float sens = 0.0025f; yaw -= md.x * sens; @@ -407,7 +588,6 @@ int main(void) { if (viewPitch > 1.5f) viewPitch = 1.5f; - // Movement float moveX = 0.0f, moveZ = 0.0f; if (IsKeyDown(KEY_A)) moveX += 1.0f; @@ -418,9 +598,7 @@ int main(void) { if (IsKeyDown(KEY_S)) moveZ -= 1.0f; - // Input buttons uint8_t buttons = 0; - if (IsKeyPressed(KEY_ONE)) buttons |= BTN_SWITCH_PISTOL; if (IsKeyPressed(KEY_TWO)) @@ -432,7 +610,6 @@ int main(void) { if (IsKeyPressed(KEY_H)) buttons |= BTN_USE_MEDKIT; - // Send INPUT if (myId != 255) { MsgInput in = {0}; in.type = MSG_INPUT; @@ -447,13 +624,11 @@ int main(void) { (const struct sockaddr *)&srv, sizeof(srv)); } - // Shoot if (myId != 255 && IsMouseButtonDown(MOUSE_BUTTON_LEFT) && fireCooldown <= 0.0f && rp[myId].present) { int wpn = rp[myId].weapon; float rate = (wpn == WEAPON_PISTOL) ? pistolFireRate : rifleFireRate; - float interval = 1.0f / rate; - fireCooldown = interval; + fireCooldown = 1.0f / rate; shotsInBurst++; burstResetTimer = burstResetTime; @@ -478,7 +653,6 @@ int main(void) { (const struct sockaddr *)&srv, sizeof(srv)); } - // Camera Vector3 forward = v3(sinf(viewYaw) * cosf(viewPitch), sinf(viewPitch), cosf(viewYaw) * cosf(viewPitch)); @@ -490,23 +664,12 @@ int main(void) { cam.projection = CAMERA_PERSPECTIVE; BeginDrawing(); - ClearBackground((Color){20, 24, 32, 255}); + ClearBackground((Color){135, 206, 235, 255}); BeginMode3D(cam); - // Ground - for (int z = -16; z <= 16; z++) { - for (int x = -16; x <= 16; x++) { - DrawCubeV(v3((float)x, 0.0f, (float)z), v3(1, 1, 1), - (Color){50, 60, 75, 255}); - } - } - - // Blocks - DrawCubeV(v3(2, 1, 2), v3(1, 1, 1), (Color){120, 90, 60, 255}); - DrawCubeV(v3(2, 2, 2), v3(1, 1, 1), (Color){120, 90, 60, 255}); - DrawCubeV(v3(-3, 1, -1), v3(1, 1, 1), (Color){90, 120, 60, 255}); - DrawCubeV(v3(0, 1, 4), v3(2, 1, 2), (Color){90, 90, 130, 255}); + // Draw smooth terrain + DrawModel(terrainModel, (Vector3){0, 0, 0}, 1.0f, WHITE); // Items for (int i = 0; i < MAX_ITEMS; i++) { @@ -519,8 +682,7 @@ int main(void) { ic = (Color){255, 220, 120, 255}; if (wi[i].type == ITEM_AMMO_RIFLE) ic = (Color){255, 180, 120, 255}; - DrawCubeV(v3(wi[i].pos.x, wi[i].pos.y + 0.15f, wi[i].pos.z), - v3(0.3f, 0.3f, 0.3f), ic); + DrawSphere(wi[i].pos, 0.3f, ic); } // Players @@ -540,21 +702,16 @@ int main(void) { // Nameplates for (int i = 0; i < MAX_PLAYERS; i++) { - if (!rp[i].present) + if (!rp[i].present || !rp[i].username[0]) continue; - if (!rp[i].username[0]) - continue; - Vector3 head = v3(rp[i].pos.x, rp[i].pos.y + 1.2f, rp[i].pos.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) continue; - Vector2 s = GetWorldToScreen(head, cam); if (s.x < -200 || s.x > sw + 200 || s.y < -200 || s.y > sh + 200) continue; - int fontSize = 18; int w = MeasureText(rp[i].username, fontSize); Color tc = (i == myId) ? (Color){180, 230, 255, 255} : RAYWHITE; @@ -566,9 +723,7 @@ int main(void) { if (myId == 255 || !rp[myId].present) { DrawText("Connecting...", 10, 10, 20, RAYWHITE); } else { - // Top left - Health and Medkits DrawRectangle(10, 10, 280, 110, (Color){0, 0, 0, 120}); - DrawText("HP", 20, 20, 20, RAYWHITE); int healthBarWidth = (rp[myId].hp * 220) / 100; Color healthColor = (rp[myId].hp > 60) ? (Color){80, 255, 80, 120} @@ -577,22 +732,18 @@ int main(void) { DrawRectangle(20, 45, 220, 20, (Color){40, 40, 40, 255}); DrawRectangle(20, 45, healthBarWidth, 20, healthColor); DrawText(TextFormat("%d", rp[myId].hp), 250, 47, 18, RAYWHITE); - DrawText(TextFormat("Medkits: %d (H to use)", rp[myId].medkits), 20, 75, 18, (rp[myId].medkits > 0) ? (Color){120, 255, 120, 120} : (Color){120, 120, 120, 120}); - DrawText("1=Pistol 2=Rifle R=Reload F=Pick", 20, 95, 14, (Color){150, 150, 150, 150}); - // Bottom right - Weapon and Ammo const char *weaponName = (rp[myId].weapon == WEAPON_PISTOL) ? "PISTOL" : "RIFLE"; Color weaponColor = (rp[myId].weapon == WEAPON_PISTOL) ? (Color){100, 200, 255, 255} : (Color){255, 150, 100, 255}; - int currentMag = (rp[myId].weapon == WEAPON_PISTOL) ? rp[myId].pistolMag : rp[myId].rifleMag; int reserveAmmo = (rp[myId].weapon == WEAPON_PISTOL) ? rp[myId].pistolAmmo @@ -600,18 +751,11 @@ int main(void) { DrawRectangle(sw - 260, sh - 120, 250, 110, (Color){0, 0, 0, 180}); DrawText(weaponName, sw - 250, sh - 110, 28, weaponColor); - - char magText[16]; - snprintf(magText, sizeof(magText), "%d", currentMag); - DrawText(magText, sw - 250, sh - 75, 40, RAYWHITE); - - char reserveText[32]; - snprintf(reserveText, sizeof(reserveText), "/ %d", reserveAmmo); - DrawText(reserveText, sw - 140, sh - 65, 24, (Color){180, 180, 180, 255}); - - if (currentMag == 0) { + DrawText(TextFormat("%d", currentMag), sw - 250, sh - 75, 40, RAYWHITE); + DrawText(TextFormat("/ %d", reserveAmmo), sw - 140, sh - 65, 24, + (Color){180, 180, 180, 255}); + if (currentMag == 0) DrawText("RELOAD!", sw - 250, sh - 30, 20, (Color){255, 80, 80, 255}); - } } DrawFPS(sw - 90, 10); @@ -632,6 +776,7 @@ int main(void) { EndDrawing(); } + UnloadModel(terrainModel); CloseWindow(); CLOSESOCK(sock); #ifdef _WIN32 diff --git a/game/server b/game/server index 4bb23844d50566ad20ba94aa98d1ca430c1adc1d..80cf392ba1f08227f5e82b9cf734f1647e34db6a 100755 GIT binary patch literal 35160 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 literal 30640 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 diff --git a/game/server.c b/game/server.c index 4869de1..cbc6aeb 100644 --- a/game/server.c +++ b/game/server.c @@ -1,3 +1,4 @@ +// game/server.c - SMOOTH TERRAIN VERSION #include #include #include @@ -50,6 +51,122 @@ typedef int socklen_t; #define CAT_PLAYER (1u << 1) #define CAT_ITEM (1u << 2) +// ============== SIMPLEX NOISE ============== +static const uint8_t perm[512] = { + 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, + 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, + 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, + 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, + 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, + 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, + 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, + 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, + 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, + 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, + 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, + 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, + 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, + 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, + 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, + 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, + 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, + 180, 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, + 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, + 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, + 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, + 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, + 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, + 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, + 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, + 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, + 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, + 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, + 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, + 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, + 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, + 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, + 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, + 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, + 156, 180}; + +static float grad2(int hash, float x, float y) { + int h = hash & 7; + float u = h < 4 ? x : y; + float v = h < 4 ? y : x; + return ((h & 1) ? -u : u) + ((h & 2) ? -2.0f * v : 2.0f * v); +} + +static float simplex_noise_2d(float x, float y) { + const float F2 = 0.366025403f; + const float G2 = 0.211324865f; + float s = (x + y) * F2; + int i = (int)floorf(x + s); + int j = (int)floorf(y + s); + float t = (i + j) * G2; + float X0 = i - t, Y0 = j - t; + float x0 = x - X0, y0 = y - Y0; + int i1 = (x0 > y0) ? 1 : 0; + int j1 = (x0 > y0) ? 0 : 1; + float x1 = x0 - i1 + G2, y1 = y0 - j1 + G2; + float x2 = x0 - 1.0f + 2.0f * G2, y2 = y0 - 1.0f + 2.0f * G2; + int ii = i & 255, jj = j & 255; + float n0 = 0.0f, n1 = 0.0f, n2 = 0.0f; + float t0 = 0.5f - x0 * x0 - y0 * y0; + if (t0 >= 0.0f) { + t0 *= t0; + n0 = t0 * t0 * grad2(perm[ii + perm[jj]], x0, y0); + } + float t1 = 0.5f - x1 * x1 - y1 * y1; + if (t1 >= 0.0f) { + t1 *= t1; + n1 = t1 * t1 * grad2(perm[ii + i1 + perm[jj + j1]], x1, y1); + } + float t2 = 0.5f - x2 * x2 - y2 * y2; + if (t2 >= 0.0f) { + t2 *= t2; + n2 = t2 * t2 * grad2(perm[ii + 1 + perm[jj + 1]], x2, y2); + } + return 45.0f * (n0 + n1 + n2); +} + +static float fbm_noise(float x, float y, int octaves) { + float value = 0.0f, amplitude = 1.0f, frequency = 1.0f, max_value = 0.0f; + for (int i = 0; i < octaves; i++) { + value += simplex_noise_2d(x * frequency, y * frequency) * amplitude; + max_value += amplitude; + amplitude *= 0.5f; + frequency *= 2.0f; + } + return value / max_value; +} + +float get_terrain_height(float x, float z) { + float height = fbm_noise(x * 0.03f, z * 0.03f, 4); + height += fbm_noise(x * 0.01f, z * 0.01f, 2) * 1.5f; + return height * 6.0f + 2.0f; +} + +#define TERRAIN_SIZE 64 +#define TERRAIN_SCALE 2.0f +static dReal *gHeightData = NULL; +static dGeomID gTerrainGeom = NULL; + +static void generate_smooth_terrain(void) { + int size = TERRAIN_SIZE; + gHeightData = (dReal *)malloc(size * size * sizeof(dReal)); + + for (int z = 0; z < size; z++) { + for (int x = 0; x < size; x++) { + float wx = ((float)x - size / 2.0f) * TERRAIN_SCALE; + float wz = ((float)z - size / 2.0f) * TERRAIN_SCALE; + gHeightData[z * size + x] = (dReal)get_terrain_height(wx, wz); + } + } + + printf("Generated smooth heightmap terrain %dx%d\n", size, size); +} +// ============== END SIMPLEX NOISE ============== + static double now_seconds(void) { #ifdef _WIN32 static LARGE_INTEGER freq; @@ -104,10 +221,7 @@ typedef struct MsgInput { uint8_t type; uint8_t playerId; uint32_t clientTick; - float moveX; - float moveZ; - float yaw; - float pitch; + float moveX, moveZ, yaw, pitch; uint8_t buttons; } MsgInput; @@ -118,19 +232,11 @@ typedef struct MsgShoot { } MsgShoot; typedef struct PlayerStateNet { - uint8_t id; - uint8_t alive; + uint8_t id, alive; int16_t hp; - float x, y, z; - float yaw, pitch; - + float x, y, z, yaw, pitch; uint8_t weapon; - int16_t pistolMag; - int16_t rifleMag; - int16_t pistolAmmo; - int16_t rifleAmmo; - int16_t medkits; - + int16_t pistolMag, rifleMag, pistolAmmo, rifleAmmo, medkits; char username[USERNAME_MAX]; } PlayerStateNet; @@ -169,26 +275,13 @@ typedef struct Player { int inUse; struct sockaddr_in addr; double lastHeard; - char username[USERNAME_MAX]; - - float moveX, moveZ; - float yaw, pitch; - - int alive; - int hp; - - int weapon; - int pistolMag; - int rifleMag; - int pistolAmmo; - int rifleAmmo; - int medkits; - + float moveX, moveZ, yaw, pitch; + int alive, hp, weapon; + int pistolMag, rifleMag, pistolAmmo, rifleAmmo, medkits; double lastShotTime; int shotsInBurst; uint32_t rng; - dBodyID body; dGeomID geom; } Player; @@ -200,11 +293,7 @@ static int addr_equal(const struct sockaddr_in *a, } static float clampf(float v, float lo, float hi) { - if (v < lo) - return lo; - if (v > hi) - return hi; - return v; + return (v < lo) ? lo : (v > hi) ? hi : v; } static void sendto_player(int sock, const Player *pl, const void *data, @@ -212,6 +301,7 @@ static void sendto_player(int sock, const Player *pl, const void *data, sendto(sock, (const char *)data, len, 0, (const struct sockaddr *)&pl->addr, sizeof(pl->addr)); } + static void broadcast(int sock, const Player players[MAX_PLAYERS], const void *data, int len) { for (int i = 0; i < MAX_PLAYERS; i++) @@ -222,7 +312,6 @@ static void broadcast(int sock, const Player players[MAX_PLAYERS], static dWorldID gWorld; static dSpaceID gSpace; static dJointGroupID gContactGroup; - static Item gItems[MAX_ITEMS]; static uint16_t gNextItemId = 1; @@ -234,7 +323,8 @@ static void item_destroy(Item *it) { memset(it, 0, sizeof(*it)); } -static int item_spawn(uint8_t type, int qty, float x, float y, float z) { +static int item_spawn(uint8_t type, int qty, float x, float z) { + float y = get_terrain_height(x, z) + 0.5f; for (int i = 0; i < MAX_ITEMS; i++) { if (!gItems[i].active) { Item *it = &gItems[i]; @@ -245,14 +335,11 @@ static int item_spawn(uint8_t type, int qty, float x, float y, float z) { it->x = x; it->y = y; it->z = z; - it->geom = dCreateSphere(gSpace, 0.30); - dGeomSetPosition(it->geom, x, y + 0.3f, z); - + dGeomSetPosition(it->geom, x, y, z); dGeomSetCategoryBits(it->geom, CAT_ITEM); dGeomSetCollideBits(it->geom, 0); dGeomSetData(it->geom, it); - return i; } } @@ -261,12 +348,10 @@ static int item_spawn(uint8_t type, int qty, float x, float y, float z) { static void nearCallback(void *user, dGeomID o1, dGeomID o2) { (void)user; - if (dGeomIsSpace(o1) || dGeomIsSpace(o2)) { dSpaceCollide2(o1, o2, user, &nearCallback); return; } - unsigned long c1 = dGeomGetCategoryBits(o1); unsigned long c2 = dGeomGetCategoryBits(o2); if ((c1 & CAT_ITEM) || (c2 & CAT_ITEM)) @@ -275,19 +360,12 @@ static void nearCallback(void *user, dGeomID o1, dGeomID o2) { const int MAXC = 8; dContact c[MAXC]; int n = dCollide(o1, o2, MAXC, &c[0].geom, sizeof(dContact)); - if (n <= 0) - return; - for (int i = 0; i < n; i++) { c[i].surface.mode = dContactApprox1; - c[i].surface.mu = 1.0; + c[i].surface.mu = 1.5; c[i].surface.bounce = 0.0; - c[i].surface.bounce_vel = 0.0; - dJointID j = dJointCreateContact(gWorld, gContactGroup, &c[i]); - dBodyID b1 = dGeomGetBody(o1); - dBodyID b2 = dGeomGetBody(o2); - dJointAttach(j, b1, b2); + dJointAttach(j, dGeomGetBody(o1), dGeomGetBody(o2)); } } @@ -296,38 +374,35 @@ static void ode_init_world(void) { gWorld = dWorldCreate(); gSpace = dHashSpaceCreate(0); gContactGroup = dJointGroupCreate(0); - - dWorldSetGravity(gWorld, 0, -9.81, 0); + dWorldSetGravity(gWorld, 0, -20.0, 0); dWorldSetERP(gWorld, 0.2); dWorldSetCFM(gWorld, 1e-5); - dGeomID ground = dCreatePlane(gSpace, 0, 1, 0, 0); - dGeomSetCategoryBits(ground, CAT_WORLD); - dGeomSetCollideBits(ground, CAT_PLAYER); + generate_smooth_terrain(); - const struct { - float x, y, z, sx, sy, sz; - } blocks[] = { - {2, 0.5f, 2, 1, 1, 1}, - {2, 1.5f, 2, 1, 1, 1}, - {-3, 0.5f, -1, 1, 1, 1}, - {0, 0.5f, 4, 2, 1, 2}, - }; - for (int i = 0; i < (int)(sizeof(blocks) / sizeof(blocks[0])); i++) { - dGeomID b = dCreateBox(gSpace, blocks[i].sx, blocks[i].sy, blocks[i].sz); - dGeomSetPosition(b, blocks[i].x, blocks[i].y, blocks[i].z); - dGeomSetCategoryBits(b, CAT_WORLD); - dGeomSetCollideBits(b, CAT_PLAYER); - } + dHeightfieldDataID hfData = dGeomHeightfieldDataCreate(); + dGeomHeightfieldDataBuildDouble(hfData, gHeightData, 0, + TERRAIN_SCALE * TERRAIN_SIZE, + TERRAIN_SCALE * TERRAIN_SIZE, TERRAIN_SIZE, + TERRAIN_SIZE, 1.0, 0.0, 0.0, 0); + dGeomHeightfieldDataSetBounds(hfData, -10.0, 20.0); - // Spawn test items - item_spawn(ITEM_MEDKIT, 1, 1.0f, 0.0f, 1.0f); - item_spawn(ITEM_MEDKIT, 1, -2.0f, 0.0f, 3.0f); - item_spawn(ITEM_AMMO_RIFLE, 30, 0.0f, 0.0f, 2.0f); - item_spawn(ITEM_AMMO_PISTOL, 12, -1.0f, 0.0f, -2.0f); + gTerrainGeom = dCreateHeightfield(gSpace, hfData, 1); + dGeomSetPosition(gTerrainGeom, 0, 0, 0); + dGeomSetCategoryBits(gTerrainGeom, CAT_WORLD); + dGeomSetCollideBits(gTerrainGeom, CAT_PLAYER); + + item_spawn(ITEM_MEDKIT, 1, 10.0f, 10.0f); + item_spawn(ITEM_MEDKIT, 1, -15.0f, 20.0f); + item_spawn(ITEM_AMMO_RIFLE, 30, 5.0f, -10.0f); + item_spawn(ITEM_AMMO_PISTOL, 12, -10.0f, -15.0f); } static void ode_shutdown_world(void) { + if (gHeightData) { + free(gHeightData); + gHeightData = NULL; + } dJointGroupDestroy(gContactGroup); dSpaceDestroy(gSpace); dWorldDestroy(gWorld); @@ -335,24 +410,20 @@ static void ode_shutdown_world(void) { } static void player_ode_create(Player *p, int spawnIndex) { - const dReal radius = 0.35; - const dReal length = 1.0; - p->body = dBodyCreate(gWorld); - dMass m; - dMassSetCapsuleTotal(&m, 80.0, 3, radius, length); + dMassSetCapsuleTotal(&m, 80.0, 3, 0.35, 1.0); dBodySetMass(p->body, &m); - dBodySetLinearDamping(p->body, 0.05); dBodySetAngularDamping(p->body, 0.99); - dBodySetPosition(p->body, (dReal)(spawnIndex * 2), 2.0, 0.0); - dBodySetAngularVel(p->body, 0, 0, 0); + float sx = (float)(spawnIndex * 4 - 8); + float sz = (float)(spawnIndex * 3 - 6); + float sy = get_terrain_height(sx, sz) + 3.0f; + dBodySetPosition(p->body, sx, sy, sz); - p->geom = dCreateCapsule(gSpace, radius, length); + p->geom = dCreateCapsule(gSpace, 0.35, 1.0); dGeomSetBody(p->geom, p->body); - dGeomSetCategoryBits(p->geom, CAT_PLAYER); dGeomSetCollideBits(p->geom, CAT_WORLD | CAT_PLAYER); } @@ -369,8 +440,7 @@ static void player_ode_destroy(Player *p) { } typedef struct RaycastCtx { - dGeomID ray; - dGeomID hitGeom; + dGeomID ray, hitGeom; dContactGeom hit; int hasHit; } RaycastCtx; @@ -381,7 +451,6 @@ static void rayCallback(void *data, dGeomID o1, dGeomID o2) { dGeomID other = (o1 == ray) ? o2 : o1; if (other == ray) return; - dContact c[4]; int n = dCollide(ray, other, 4, &c[0].geom, sizeof(dContact)); for (int i = 0; i < n; i++) { @@ -400,12 +469,10 @@ static int raycast_space(float ox, float oy, float oz, float dx, float dy, dGeomRaySet(ray, ox, oy, oz, dx, dy, dz); dGeomSetCategoryBits(ray, 0xFFFFFFFFu); dGeomSetCollideBits(ray, CAT_WORLD | CAT_PLAYER); - RaycastCtx rc = {0}; rc.ray = ray; dSpaceCollide2(ray, (dGeomID)gSpace, &rc, &rayCallback); dGeomDestroy(ray); - if (!rc.hasHit) return 0; if (outHit) @@ -423,21 +490,25 @@ static uint32_t xorshift32(uint32_t *s) { *s = x; return x; } + static float frand_signed(uint32_t *s) { return ((xorshift32(s) & 0xFFFFFF) / (float)0x7FFFFF) - 1.0f; } + static void dir_from_yaw_pitch(float yaw, float pitch, float *dx, float *dy, float *dz) { *dx = sinf(yaw) * cosf(pitch); *dy = sinf(pitch); *dz = cosf(yaw) * cosf(pitch); } + static void v3_cross(float ax, float ay, float az, float bx, float by, float bz, float *ox, float *oy, float *oz) { *ox = ay * bz - az * by; *oy = az * bx - ax * bz; *oz = ax * by - ay * bx; } + static void v3_normf(float *x, float *y, float *z) { float l = sqrtf((*x) * (*x) + (*y) * (*y) + (*z) * (*z)); if (l < 1e-6f) { @@ -458,7 +529,7 @@ static int find_nearest_item(float x, float y, float z, float maxDist) { if (!gItems[i].active) continue; float dx = gItems[i].x - x; - float dy = (gItems[i].y + 0.3f) - y; + float dy = (gItems[i].y) - y; float dz = gItems[i].z - z; float d2 = dx * dx + dy * dy + dz * dz; if (d2 < bestD2) { @@ -496,19 +567,16 @@ int main(void) { } Player players[MAX_PLAYERS] = {0}; - uint32_t serverTick = 0; - uint32_t itemsTickDiv = 0; + uint32_t serverTick = 0, itemsTickDiv = 0; printf("Server listening UDP %d\n", SERVER_PORT); double nextTick = now_seconds(); for (;;) { - // Receive for (;;) { uint8_t buf[1400]; struct sockaddr_in from = {0}; socklen_t fromLen = sizeof(from); - int n = (int)recvfrom(sock, (char *)buf, (int)sizeof(buf), 0, (struct sockaddr *)&from, &fromLen); if (n <= 0) { @@ -523,7 +591,6 @@ int main(void) { } uint8_t type = buf[0]; - int pid = -1; for (int i = 0; i < MAX_PLAYERS; i++) { if (players[i].inUse && addr_equal(&players[i].addr, &from)) { @@ -547,21 +614,15 @@ int main(void) { p->addr = from; p->lastHeard = now_seconds(); strncpy(p->username, m->username, USERNAME_MAX - 1); - p->alive = 1; p->hp = 100; - p->weapon = WEAPON_RIFLE; p->pistolMag = 12; p->rifleMag = 30; p->pistolAmmo = 48; p->rifleAmmo = 90; p->medkits = 2; - - p->lastShotTime = 0.0; - p->shotsInBurst = 0; p->rng = 0xA341316Cu ^ (uint32_t)(i * 2654435761u); - player_ode_create(p, i); printf("Player %d joined as '%s'\n", pid, p->username); break; @@ -583,19 +644,16 @@ int main(void) { if (pid >= 0 && m->playerId == (uint8_t)pid) { Player *p = &players[pid]; p->lastHeard = now_seconds(); - p->moveX = clampf(m->moveX, -1.0f, 1.0f); p->moveZ = clampf(m->moveZ, -1.0f, 1.0f); p->yaw = m->yaw; p->pitch = clampf(m->pitch, -1.5f, 1.5f); - // Weapon switch if (m->buttons & BTN_SWITCH_PISTOL) p->weapon = WEAPON_PISTOL; if (m->buttons & BTN_SWITCH_RIFLE) p->weapon = WEAPON_RIFLE; - // Reload if (m->buttons & BTN_RELOAD) { if (p->weapon == WEAPON_PISTOL) { int need = 12 - p->pistolMag; @@ -614,15 +672,13 @@ int main(void) { } } - // Pick item if (m->buttons & BTN_PICK) { const dReal *pos = dBodyGetPosition(p->body); - int itIdx = find_nearest_item((float)pos[0], (float)pos[1] + 0.7f, - (float)pos[2], 1.6f); + int itIdx = find_nearest_item((float)pos[0], (float)pos[1], + (float)pos[2], 2.0f); if (itIdx >= 0) { Item *it = &gItems[itIdx]; int taken = 0; - if (it->type == ITEM_AMMO_PISTOL) { p->pistolAmmo += it->qty; if (p->pistolAmmo > 120) @@ -639,13 +695,11 @@ int main(void) { p->medkits = 10; taken = 1; } - if (taken) item_destroy(it); } } - // Use medkit if (m->buttons & BTN_USE_MEDKIT) { if (p->medkits > 0 && p->hp < 100) { p->medkits--; @@ -661,7 +715,6 @@ int main(void) { players[pid].alive) { Player *sh = &players[pid]; const double now = now_seconds(); - double fireInterval = (sh->weapon == WEAPON_PISTOL) ? (1.0 / 4.0) : (1.0 / 12.0); if (now - sh->lastShotTime < fireInterval) @@ -679,10 +732,8 @@ int main(void) { sh->lastShotTime = now; const dReal *pos = dBodyGetPosition(sh->body); - float ox = (float)pos[0]; - float oy = (float)pos[1] + 0.7f; - float oz = (float)pos[2]; - + float ox = (float)pos[0], oy = (float)pos[1] + 0.7f, + oz = (float)pos[2]; float fx, fy, fz; dir_from_yaw_pitch(sh->yaw, sh->pitch, &fx, &fy, &fz); v3_normf(&fx, &fy, &fz); @@ -703,7 +754,6 @@ int main(void) { float ax = frand_signed(&sh->rng) * spread; float ay = frand_signed(&sh->rng) * spread; - float dx = fx + rx * ax + ux * ay; float dy = fy + ry * ax + uy * ay; float dz = fz + rz * ax + uz * ay; @@ -711,7 +761,7 @@ int main(void) { dContactGeom hit; dGeomID hitGeom = 0; - if (raycast_space(ox, oy, oz, dx, dy, dz, 80.0f, &hit, &hitGeom)) { + if (raycast_space(ox, oy, oz, dx, dy, dz, 100.0f, &hit, &hitGeom)) { int victim = -1; for (int i = 0; i < MAX_PLAYERS; i++) { if (!players[i].inUse) @@ -721,7 +771,6 @@ int main(void) { break; } } - if (victim >= 0 && victim != pid && players[victim].alive) { int dmg = 25; players[victim].hp -= dmg; @@ -735,7 +784,6 @@ int main(void) { } } - // Tick double t = now_seconds(); if (t >= nextTick) { nextTick += DT; @@ -753,23 +801,20 @@ int main(void) { const dReal *lvel = dBodyGetLinearVel(p->body); const dReal *avel = dBodyGetAngularVel(p->body); - float dx = p->moveX; - float dz = p->moveZ; + float dx = p->moveX, dz = p->moveZ; float len = sqrtf(dx * dx + dz * dz); if (len > 1e-6f) { dx /= len; dz /= len; } - float speed = 5.0f; + float speed = 6.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]); } - // Send snapshot MsgSnapshot snap = {0}; snap.type = MSG_SNAPSHOT; snap.serverTick = serverTick; @@ -778,9 +823,7 @@ int main(void) { for (int i = 0; i < MAX_PLAYERS; i++) { if (!players[i].inUse) continue; - const dReal *pos = dBodyGetPosition(players[i].body); - PlayerStateNet *ps = &snap.p[snap.count++]; ps->id = (uint8_t)i; ps->alive = players[i].alive; @@ -798,19 +841,15 @@ int main(void) { ps->medkits = (int16_t)players[i].medkits; strncpy(ps->username, players[i].username, USERNAME_MAX - 1); } - broadcast(sock, players, &snap, (int)sizeof(snap)); - // Send items periodically itemsTickDiv++; if (itemsTickDiv >= 6) { itemsTickDiv = 0; - MsgItems im = {0}; im.type = MSG_ITEMS; im.serverTick = serverTick; im.count = 0; - for (int i = 0; i < MAX_ITEMS; i++) { if (!gItems[i].active) continue; @@ -822,12 +861,10 @@ int main(void) { it->y = gItems[i].y; it->z = gItems[i].z; } - broadcast(sock, players, &im, (int)sizeof(im)); } } - // Timeout check double now = now_seconds(); for (int i = 0; i < MAX_PLAYERS; i++) { if (players[i].inUse && (now - players[i].lastHeard) > 10.0) { @@ -837,7 +874,6 @@ int main(void) { } } - // Sleep double wait = nextTick - now_seconds(); if (wait > 0.0) { #ifdef _WIN32 From 70c860e1ca6cb0616b02ca7ed284bb004d97f980 Mon Sep 17 00:00:00 2001 From: Masashi Date: Thu, 18 Dec 2025 20:42:54 +0530 Subject: [PATCH 3/4] REAL GAM --- game/client | Bin 29920 -> 38744 bytes game/client.c | 6 +++++- game/server | Bin 35160 -> 36424 bytes game/server.c | 7 +++++++ 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/game/client b/game/client index 2164e961c07d9c3ec33b1bdec0bbc0c8aa77690a..dd120e67ad8cfe63762cc04683146e234cf7c4f1 100755 GIT binary patch literal 38744 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 D{ynK0 literal 29920 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 diff --git a/game/client.c b/game/client.c index 33a9dc0..c9ac211 100644 --- a/game/client.c +++ b/game/client.c @@ -41,6 +41,7 @@ typedef int socklen_t; #define BTN_SWITCH_RIFLE (1u << 2) #define BTN_PICK (1u << 3) #define BTN_USE_MEDKIT (1u << 4) +#define BTN_JUMP (1u << 5) // ============== SIMPLEX NOISE ============== static const uint8_t perm[512] = { @@ -555,6 +556,7 @@ 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; } } else if (type == MSG_ITEMS && n >= (int)sizeof(MsgItems)) { @@ -609,6 +611,8 @@ int main(void) { buttons |= BTN_PICK; if (IsKeyPressed(KEY_H)) buttons |= BTN_USE_MEDKIT; + if (IsKeyPressed(KEY_SPACE)) + buttons |= BTN_JUMP; if (myId != 255) { MsgInput in = {0}; @@ -736,7 +740,7 @@ int main(void) { 18, (rp[myId].medkits > 0) ? (Color){120, 255, 120, 120} : (Color){120, 120, 120, 120}); - DrawText("1=Pistol 2=Rifle R=Reload F=Pick", 20, 95, 14, + DrawText("1=Pistol 2=Rifle R=Reload F=Pick SPACE=Jump", 20, 95, 12, (Color){150, 150, 150, 150}); const char *weaponName = diff --git a/game/server b/game/server index 80cf392ba1f08227f5e82b9cf734f1647e34db6a..c460ae13d219541e91533893bf27f2e6f839b19e 100755 GIT binary patch literal 36424 zcmeHw3wTu3x%SRw0FgL}f`ZpkLGgy+DuR{?Ffdr6sSphmOhPUsnp<?&+|Xe z|Nm^By}$Ln-?zT?t?OQE?IG)iz~Tj&85x$jvaD}do~Zj?g$^#(P4`)9Hy?&rstbDdV|aTaQx`L#REaPp}@wV!ChUQWM zm!YOSX^wZ1F3)u8hm4nBo|cs!Zukw~t(A^JYw&)Y3Re`mps^+DI^XeKKs+N|os;ja}OIO!7Hk7usmN&PSmMYaD6|3tS8=I@GmhUvTR$CQ~ zEeO^&R9lq`s~YPUR<+J;tX!*@U{&qvn%3&ts=CVg<*nsQC~Iq|Y_Te-1J$C&mfF_Z z#s;MhIn}JnD;t~ZD#0ymE?-mI3S;w{tIAue9OhJn`Bg2g&5e*tgj5dE1D6+Bl}nn+ zE2?Y}T@*LyBAo!UxVE9Hy!nc%I;(O~Lv8D2^8>c1pkZ}eU3v5T^7^LQhSgT(C5^QW ztp%;E^uz0)V#*Jy4uRBxwWmxxl&%zRD(*eqs&9@RyQ`URVvD; zilkgIDQJ(ADVUO)x1bQF>l$0CZ1+*T+I`8IxUTHXYivLcTJ2n_06?Kgj*OacVYHh8pXD>qs17A^dO=)#`ZJkx! zTvcUNS2VQNS*vOrDy^E<#)dk1wpcBQiE3!Bs#sIq+*oh5wbWH1SM}v}unAL`5?AOB zTVGz=V6{{=RJJxkP|;pqT3y>vURQfvl~rF=-vTS}qovAfYDN>Th6l3ldxzSlP_ga`K~VYq4@2W|e5{N8i5_%PfzzO7l+$pKaA^UfIjE?V0tk zS>H8HpjFFyM6Y)Z?$dbwO-gQX!^dt=I1*18|MxlYQ!Hse=}-*uQ7S0UvJS-% zH(MM;YC8B-_K#9Qc{X(@hWcI=lxKT~Vu+iyIgySIK9!w36_n@34#iOa7!{P~rVhmr zAFYD&ysJYo#K)+hJhyZxhWN26D9=Ya6hmBZH3_iQfsa)x^4#vg$2str13%t@?{nZM zIPh)!+=2TYc%K8G>%jXR_&f)0>Gu))#e4@o!hr`Ic&-Cq=)lK1@I?;X=fE#<;D>{U zOW<$`94>*wC2+U|4wt~;68PUOfzQ1se--RJFd`W7yjY6mUntg^InW*Kd||}%VtQcu zOMnL^yo{&!6hA`bYbZ3 z6Ptv;hdh_Ii4Ng^K%UdAM3eB}BhRI5qD=Uk$a4vsC=&iU@?5$m{K7Yr=aMy%FZ|Wy zN0IjlzluDUsEJ(Rmy_qxG+_yUIe9Kg6MbI;m~aVsE=3bP!p|koC1|2s_*vw+6`hC) zKZQJ(qKU1-pG|%=`7OeqMxIO0#3td7C(k8kqC@zj$#bchXcB%Tc`h*%Wx{8Y=h8A! zB>Y!bf#;Gk;TQfh@?1(L@`eAHJeQCOpYZRK=h88eEBrg;PbO~(|2p|o$oKt&`G1ML zk9?2td&r+kzFYX6LvWbHVZsBK<=Ta>Z6MhPLF3}QOg+H4-mu87A z!k{x{~38M zu@d>he@vcBtAtPZ_sL&OK3Dj6$bW;pCH(8;=aBFFyVO5!59YgYxs?*}_S=qp+pm>iq@VlaB~bTrz5F{gjq zvJO^H@}9B*Q7}M7FAl>%FzUI9kyr$4!Ci>GaCpqiu zV7M>1`;$4r-Thg?jJ?6vzHS{21HWCa42&4)srH@%?@WC9*KzR}#I@R{76&^o{vI@~ zVEDt8_bV9<1o|V0+LM7&IGXwg@yK}v z_3XWJU#|yD;XzTzgu*dki~I5L?1IAQ-er4wj{p|{R1(9zd*$v8SM!hf1=vEK345*h{YfKidBAYEF9QQDI$~vlG594 z=`)ey?UBGU8Qs<1zrv%lapx8yA!Ql11f`J`0m$+KFBs?<8``JSUlDkL8ZYYvR|Fu) z3v`ox*?WGV+xw%K_eps4rW5Jx%Mfrs?7j#G3Y8SbkvDOrL^AoVk?&`07)_J+h*3Po zTs0eYF%fC6c!?M?qsp4jpfV+L?wFbXL@OQ%Y!{~vplBq}r*fSa*kS??n!qEHuy-O{ zTE@J3-p82WNWa?_t$4{hQS!nj6FA5WdC%Y7Tqzu4OZh69;qHh8`hs@_qho%Lfyw)1 zcCPoyZnWQwZUOh%Age9V9Vvbp38Vh^guCL|7*t^~iNq2gV`-Y)}_6m`L>Oo9O zMjqlYOQEDVS-VP<0EaYo84iPiLnx<1vjcBle-kCJaaEw-&XbQy!14;?w8n?0mz6y6 zHKgB7jH!Cw1_l7-w|?Jq*~p7`;!842^6z&%a_obwt3KY;>dHT$I)t=8#4G{KM2G3E}T0QY!BX%+! zyqDqKPP*_cS1#c$3eiRKilw9VfEsl&8?BLR7 zQFz}D5acXA`?F-GIuGQdXkY@xUc5hC7&k+}T*Q0vG*y@^Osvzv?!xGJZ1+%D2M*)@LX1W zzxT=F0~?EmF&IGw6=6*-qoGXw{G$wqiqG@2MakQ)ha6|HKNo9b#>4sQAYQc`DgO8= z`$REX;@q*SdoM3!2SJBlE-(7uA+YiT=&yf=23gY87$kA;vyEJbK4^r}+-do@eGbr+ zc!vX2$-YJ1FbtsRdXM2K(7CQ}j`y}-V&vG4ey{sn!O|s|YnGwHA3@{Vg2r{1v#ZC^ z)x$lD!Y{xL2)p9PAzz_b8w=qs^23F1^4a@_7=39C4N{0mnl>`1hGWw5sph z1uN_kF*kk)CgQhZ0uUC#tLMwB_I36bya>0G zHGOX$lZ$vpNsKX%IYl#RJ>vQFQW_g`k5jY`vEqmDf{*`DTwtG^z8#84E~g!uOqLW8 zzau2jUm!y@%KHP{zhnO`*zQLzcJN_$Wz}d<&^wU?t;|wZW@{_9A}ga>Ie(>iSlGW4 zZQs;WuPT~zMdXcoge|-@yJvqry0tL!QCRCnP2D;++9gwVlQV5LmB__5A7{=n9 z9f%z9{E8+p97`1SYQIpHg}FKRio!jE6~=0XPEokgD4dK0p5yyF{_f~bo*Wsyz#(HOk2?c_hZHr3~%GUT`;_PABeLh z^0gZYDV@Bs1kyN2?@{2DZQ;QY4k~T$hDChCMnl|X3`UDk z$lF955o6|c_1aF(u$i6vsFc^;=P0gVWTjpdv(on=pX`|{r0eBH zIU<1H%>D~{Uy-5@EmrigprYHjKtp4hDP&tdNLnjFZ-jy6yQs(>*w5}3Q*Y8+KVoF* z=1{{v*YbyG7h&nNObg-Hfp2E|2umlYPY7cqSuQ;b;V%*1M&FslXDFN+(V2*a>%>3d zerHf;w^R?L-cH=`L&m&ubV)^*k&=z;*B=5?Bja`yleNRkMiGAd^We|5Dl#GCW@LH+ ze!qj6=2mI9YA{vSXy_Ml+c?>OfW?#rlhu)8H?fI!JEbtK)@s<6VvQ7bpsqFs!y{1f zBZ84aROd>}Dow*G+yWkRW7Dt-w}RgeUN@}57TKewp$(bx`;N9^&9q92OAUef| z!ns)T*%K_}M=#<~7>^^(33MbJ!(i2Zq*O3h=e^^&qI&IPY+nbD*kj;5YL6l}=Usda z!oCGJQF6vgctvuTBp*#zFN={@AJz}?V7M6TI_$Y&QCEkGrN+A$a0U4L2LzS{-i5!| zRpJCy3FbnRdp<0UaHfg>vZy=aKkbO$!}xkwO}c>)SLb;vX}*fvQ1x(WT@PGCl@w!B zME1LuaeE)T@^FT`Q7dF_3uR#QG`M(YxaWR~qi7FN-3z_8i@7gB|^n-;<2*3 zD}X9uCCiq&+P=n4#R2x~#A|q9;{opndmVrGJhP79@c3ZqEs(-gb|#8glEMy6YW!NK z27L=)Gb<&9Z#p&Tq*}Z8Vamf=pr>pGF%~_Ue$B|PyllJ0rF8k-M%p@hBQ{S0A7I?) zR$Aw}16WkEYhgRXo@U05weVkaQg+PZ*;oqi3%rOwRxe@%<2qQa&`iCQ`tf`6+J^ACTWT@W z{@owZ&BCGg0R*E_##c7gDmVNJZ1+PY6kB@+ym32TZnC}|{}!^12uCSfVM(Vqw^J-B~+uMIEPdG$@+ItH|qFPxBAhMaY8Vna? z2gBbQ6~7yXRRf4@7By^{>HJ_L#iK1@W5c&oO$}tz;$%l6em|VXIH!{I&#!cH!4%oz zQBGMA*Qs{ArTI>-WVw@D!L_O?w%XXguf5=3rq@j_cp!`o6-hK)R2SJSC8T+o?%cx7 zEd`(f_##rtn7@1n$vU>$r@o76@iy>XAs-4C&RQ4^#!GOoK$$A}jiFNkMx={|L#MGK?4HD!swbJfXx*8H z!5li%qrby$-dRXQHOnk-=w9?P)hv(o-gY~fWV0-SG264RQ0yoPV%{hH87eO`yS<_J zC@_5{C4=2)+9lABul;kfeGuT5sqR5$na+P;vkcB1_ip`7>o||t+ z;1~Tdg*t-AL^v)z!ZaNL+I}vkXa;W&|4iEuKlP6ps}03KCFNJ>tTW|{bOdDkSbR9{ z?BTBfjKwa0kTsRAlAWre8?Y`sWd%Pr@NPI2675^Wk9nOpbepn9ZzKN2JK5OJ+7sT~ zgO<~|i%-jYTdz}RDPrCBpD9R0)GyrQy>t&2eQa|o`w{=rbA*>xFXdXa)X0h!M?}Ug zMk%at(c;97PP;`r+1cpLe({D+Ykj#f;4cZq)-OqN5mD1#yzBI$GUKBx=QgS0$a<3;#4mJ=wYrL z9@7xmTxk6T(8s8KPOeV!oWO6AubI7-7r*4oI4)V|)ADXCgcDepGjEPLQD_z_m0vGH zHE^XXw1vv5APJxy4ZA3_Z81`oT9zuHAmSh4$hKWE{-vG$?dy{bZfQGgDXU0ceZPb) z+jLgjH*M1_*TJUk()O2Jd@gJ_jF*s48t=WrX*r{9p>27^kd}$6rn{oEf270i3jU}t zX}5Ei6iTaCTVeGTU|I||rK)q$k7|-O(ZH75-2Be|iW}^#`d37YOgj%3CHdMnfPd-W0`ig?Ja3%O{$#c^pz_H(e7Au~GW!m;X&^0t~-z4u!T)Hmaon`)rOjNoB3KQ&N$ytmyACOJ^I!xMX; zUQbaZL@afnu7W@($HtmE@9}O}FqGJ@r$yckXX+GXYBXq=??7ttA7Gwo=7Aek;SiUB zt?g(Q!!^hxK6`}n0r#?JddeLLyrw?jqB1-}w85Rl&+w}s@{A?BPPeq+b(Yi)+S<_E z4GqsRu)2mlhW5JrHE4TAUwMO%ej>x1WHI{bOsMr9oy3YX281F`486Byt3(x`8XLo5 z*0suK4ZclkCS1g3tFt3HLB9QE8Z#~Fn{P==+5|I@kRYEjEa6QRI>2#7!TP(AJ&<8| z^2|7n?@aksM5r%CRmMCHZnSbxiRcPng?yVSZ)alKP)T8QTbYpP3X<@vX25%!-yn=m zROy72j!fX*=o9~xHN3pq_(JtOfRz)|eGtZabBSzrAa?4u{&%(X5nI3Bf!O*rYGKU% z@ns?E!g7>pm?HRv!a}5I0_VcW8cF^%ZH@xHx(@X#WzR7Dc9_l{a>y)IQc;!-5(zQm z5tVOja)CjC)d`8#D3ynFl>2A~Q8v@O6yyPHuVHu1@fOKZg{tgCm7Ngn=+AQNA%}^F zbn5ppb(&bLXziqVfM{gBd^Dz)UZlj$0D z9F`lgozH|>d?Y-cC=GFtB~jl@-{Yr^hx0j;+<5@L9WMPTccL81{{X+$_yiEH?kj92M|#dL4prcSYj*J%&+bE|N5_;XQI4qbk1A2%6Ww%5vP!TELa))SCwK3MOhVB{|pJj)YOWB=Wx6 zu$bx^@cY@4W+f5x21@(}vBIHPGP04(c}*nyk`gple?GhaGfZ1qCN_;&3>aHDWvbV4 zH%`N9ch24bJ&0^_i4_jz!aCJRF=$WC#h{HOlo{+}Y6X>K(q0nR$$E;hu|PLQC*6(s zXUc-^VU#yh1H-++T`Ef6uEoh*7}XP!s_=eUl$l|wROK!)Qy4oqg%41OI)y$&+on_4 zHbe^Jk}1fWOr=nwQ^*UQs0&2VWs)DfwD^su8$~Ylq<%v(f{xJ{4r0tU)DT|I4@m33 z($&Mtv|HC>tZRH_WCrxY$gnans1}3>_#rNgy=X_AhKSrPm8OURf^84`8~iUN7~}e2 zPbRH9s-=a?cc1WA;mly?p6qHNnR{j9EZb~ae2OT0Lto&DHNgw8ChhwcG|z~zj={og z%y~DI4JFQ%VNH6=*Du4JaOe?O#SS2iV>-5RGXq$*Ye!-En%GpX&N^empuH7Rm1dah zUa}i=2RWgJwxmW8J|og?ho5RtvsJ3Y{E?zKW2SW4Jlc>yTZ_!JPA^26I>lT^FdL?p z(!`?%B_2d6pgFL+8ykZ&F@Lbq=s}idLJ1$M?6N2tJ4j_(stR&CZjes?Ae|Ei>6|-A z=cGY869?&>GDv6qAe~d~{V1s<{c`Et;|oSd@eRl_LfM|~+>PAyqHg1if;LWqHeQ!( zZJat!{%mijH_hU1}t#?N@t_!*GKAEkBvQZ?L-wO=+n z0Dfw$`oI_*v#|)T?R4*MfDXKqCZ!A&WewI^ z2`#kXY?BguCMN-o$j_ z>DQ5a{duDsCD9dn+0+L=R78Yzj0DLM>xV;$x#f!?fg4aEnlo$G+mFDzuToo!j+u=a zhnPB?PcYjxW02|OQ;+-Av!k&D*xR>pf22ctlXydamuZ;f|uY0xF z&XqX~-Qs*^zqptyR9*`B*~c0mXFpov`2#%t3=0GNRviB~sVjcPK5Sj|%Vth7f2U)A z8o#fLzte#?_Wzr;|EDa#!`I;wI9vjUOW^;a65wAF{AbsXwuHaE+}oM+Vrkj*J&ED( z-8z2t$${HOR7~g_GxE4+hu!hF_K&}59slcZTy^Mo|Iv2(k-iK6{nV1bY@T`LMNK6f2`&jGObX8T*SC zy|#G6)7{IC{@K14Zn>%TnV($oU}gQ@$987vRr zHSc^`{=wf*`)v0ouWuUl+^_Ec^jlNE-}_%j{q>s3vA0gy)^+uL+n2t)wPo#;^KQt# z^Xu;&_|3{cG|xL}Z1D4F$McuYef#2jpZNbh{|l=cs+#dPSu1@l^^J|KHNG18OSbjp zO+Nh9+-Cf#P2Ysd_6e0E&0px0zudW`s(DRSv#*YSTDJjzFVs!Zj@D;bYCW;Nq>Yh_9*S0w$-+rL!;*ni*;QpMsWmU5qkIuTN@#xN3 z$m?v#F1D4ujtv~k8jpJ#15XbOl+ngfxeJaOdx`hRYe#fgbB>>V_SEsG zQOvk2a8E`V{)9lmQMor~&dV8=c@I)h^6LR_h5UW^Y|FELpG5`6zZZA&3j+ghg4*)T zSMB)E06qhg=e;TN-M0K);4ed-hy2*_vqohY`-cGULLJtm#ILa9pNM!K)a`;4d4Vmz z2=a3vUz#Gn%$Bc!ycBZjBqslBMJ)Nd9`bdN&vV9KpOoJV`4-5pamq8RZTrta-V6Dq zDe^_O{9VY$p-pZ9P3Hf{l7AndL%{Dwn_Po7XUiAfn$?+|b#*4JoCw$lS?_-g3>@dw z$vT>1$%7yG``AM&g|2NQbGhw98RVzFGB7|pwmjr&PWa)1oyV*d?5?zEu`9BJGCI#k;YE!1;p=b-94>*wCGh`J37Ef&ZvGBB$30&B zTkX6IKOv2uk;c!}Jb#QQmroJwY)IIq(%louk!&bZY??|nWU%$wOA2+02I+_)~LBV_#@@gNZ zDCcv$@UtVi@BHmE|BE{nsp(!#-_rCW zO~2H1*r}FW$7*_prZY5Mpy?HwR%^OO)0;KDQ`3hveNxlCn!cs!N1A@A>9Esu`kJ1h z=?qO5XnKXF)tau+^kz-()bwFZpVV}(rf+Hbk)~g2I_z|vzNTkrIz!V1nqHx4wWez{ zy;;*cHGNpqCpF!x>06q9r0JKM4jZr2*YpfcXK1=W(psDwQ%0N zS-y#wgPrA@JRN6WO!iI5pE7;&=1J&kW+h2#1JH}!91Mc|UP80g<|6aSlTHOJ`$Mj{^Zqoz|E>q|wf zlQdqhai7*dU*pjWlw!HopR4gtG%o8a$S&3RpDt92vZg|Cg~m^2!@wo$DA3g!FVXla zky=)(#&>F5)=iMzqVWKBWO&JX36y&d%-35Qmvs{GyEXo_ZV0kIVnkd+^_QPRpDQ7A z4kkHX{Idxe=pE_feV5?J+3!uIZCP&Ta=D%7mxJ;?+C+BHQ(W*Vg6CMToUN4i>No)x z{VQGYN*BD%1;5D!zXN!#HP)JSlf!ugf9#_FxC_3=1%JZ@|APyD5ECRH{F!)Dsy}~* zejZOkDN}g17Junt=g6#~>*Zt@{9NFtr6wudLKpoK!E>xP=cq`sK1Xn+i@w`Ah}XI3 zb6;SncJ*W6d6{GF_l|Ly`vgPj?{vZ4&V&4&i+;idcRLqzI4bDq%%dzbk2Zeh5I2m2 zp6r6pcEJl>@Kr8&n+v|l1%JQ=f5HXd>w^E$1wZJ5_oMwEjq%W&H?3dimPc?$f$89} zF8Dbv_ysQb<-{|w+VG{OQ39Op&7amED_!h-*9G6`g5T|eKkS0l{U%e zL~B)ZV|isoc}uIcT24K*R;$w#SL^c~<>W+b^`cfB|7Wefw5q+8!9|r;EeH+)RcB`k z)6!IaZG**QFz4ZD!Bse)(87s-rL{N?v-DaX#wlz|Ob>r zkCOy#lL*t@QlwTcUQ=>?eHb-o#6D(5{N=qwS8cS=?aB!Na9G{sYR)whYYM*y1Hc=RIFsza}4`Q{C zBTYZhlSgURT0H4CC5=JnZ`x8Ec`3(u+5nrBImI&tG>6bS&oE{2tAWHmI#)Lwb+TxR z0Uo`Z0vLV(uGqNE)Si^h<8`YoCj zdY3v=)hfk!Yx^gM)2ahlQ|%5>T`+4-I+nuI~)}q^9OZ^bMT7JIRJt@wDOd zYAdZtIPkWrxycg68XVeeO{!em0HcbwHY*9<^DR7zTGTXC3R!biT{$D@P*YtiE!N`M zidU*NNydgr&5bfDPpYcH%d5P;sv7Dt>7>en_KCiZ!XCvA({lp*3~d`oGNaI|rsd*qc#T5!Qda4D}0_e3<=1gK)dc z8Gn6`c_|6#}&e=e19 zneR=8n(tGLe;n@g`23wc%Y^>P z264tW>v2Q3&CrUPn0N-|zPvNO*#|K+$0%x==8pA{j&I7}D$|OZns%sB%Vz?bJ_gG4 zO?|F_mCiJ?BDsA+ooZva0Ew@Jo) zf8A2ABBZBhctigPHGV5G@y++yNB*1fdl8py&%`(T$6Iy$TwSoHeVTMlJ7D>;{7rmw zE_bXEAg+mu*iRGR(EozO8Q*;0jqCV+Q?c5DiDzggB0J-oeU6FEDnj~RhKXk!8iB~f zjQ?i;VU~_xmKMQhB5InW)6Y-CP5ku!OJ8|8X<8OVUZT_Q##R+Cj^k(IHtv}5=LlfN jcLeQg;(8U_pvP|`FzFhepT~!a|BH~)I5sUp8f*Ozv($a! literal 35160 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 diff --git a/game/server.c b/game/server.c index cbc6aeb..c497376 100644 --- a/game/server.c +++ b/game/server.c @@ -46,6 +46,7 @@ typedef int socklen_t; #define BTN_SWITCH_RIFLE (1u << 2) #define BTN_PICK (1u << 3) #define BTN_USE_MEDKIT (1u << 4) +#define BTN_JUMP (1u << 5) #define CAT_WORLD (1u << 0) #define CAT_PLAYER (1u << 1) @@ -708,6 +709,12 @@ int main(void) { p->hp = 100; } } + + if (m->buttons & BTN_JUMP) { + const dReal *lvel = dBodyGetLinearVel(p->body); + dBodySetLinearVel(p->body, lvel[0], 12.0f, lvel[2]); // Set upward velocity + printf("Player %d jumped\n", pid); + } } } else if (type == MSG_SHOOT && n >= (int)sizeof(MsgShoot)) { const MsgShoot *m = (const MsgShoot *)buf; From 0151eb65ef177b918b1570d0c66797a10adf6e04 Mon Sep 17 00:00:00 2001 From: Masashi Date: Thu, 18 Dec 2025 21:16:16 +0530 Subject: [PATCH 4/4] REAL GAM --- game/client | Bin 38744 -> 0 bytes game/client.c | 156 +++++++++++++++++++++++++++++++++++++++++++++++--- game/server | Bin 36424 -> 0 bytes 3 files changed, 149 insertions(+), 7 deletions(-) delete mode 100755 game/client delete mode 100755 game/server diff --git a/game/client b/game/client deleted file mode 100755 index dd120e67ad8cfe63762cc04683146e234cf7c4f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 38744 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 D{ynK0 diff --git a/game/client.c b/game/client.c index c9ac211..c7eb9e5 100644 --- a/game/client.c +++ b/game/client.c @@ -1,4 +1,4 @@ -// game/client.c - SMOOTH TERRAIN VERSION +// game/client.c - SMOOTH TERRAIN VERSION WITH DIRECTIONAL LIGHT & SHADOWS #include "raylib.h" #include #include @@ -43,6 +43,16 @@ typedef int socklen_t; #define BTN_USE_MEDKIT (1u << 4) #define BTN_JUMP (1u << 5) +// ============== DIRECTIONAL LIGHT ============== +typedef struct { + Vector3 direction; // Normalized light direction + Vector3 color; // RGB color (0-1 range) + float intensity; // Light intensity multiplier + float ambientIntensity; // Ambient light strength + float shadowBias; // Shadow bias to prevent z-fighting + float shadowIntensity; // How dark shadows are (0-1) +} DirectionalLight; + // ============== SIMPLEX NOISE ============== static const uint8_t perm[512] = { 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, @@ -257,6 +267,110 @@ static Mesh generate_terrain_mesh(void) { } // ============== END SIMPLEX NOISE ============== +// ============== LIGHTING HELPERS ============== +// apply_directional_lighting: Basic Lambertian diffuse lighting +// Applies directional light with diffuse component based on surface normal +static Color apply_directional_lighting(Color baseColor, Vector3 normal, + DirectionalLight light) { + // Normalize light direction (it's already normalized, but for safety) + Vector3 lightDir = light.direction; + + // Calculate diffuse component: dot product of negative light direction and + // surface normal We use negative because light travels opposite to its + // direction vector + float diff = fmaxf(0.0f, -(lightDir.x * normal.x + lightDir.y * normal.y + + lightDir.z * normal.z)); + + // Combine diffuse with ambient light + // Ambient provides minimum brightness even in shadow + float brightness = light.ambientIntensity + + (diff * light.intensity * (1.0f - light.ambientIntensity)); + + // Apply brightness multiplier to base color + int r = (int)(baseColor.r * brightness); + int g = (int)(baseColor.g * brightness); + int b = (int)(baseColor.b * brightness); + + // Clamp RGB to valid range [0, 255] + if (r > 255) + r = 255; + if (g > 255) + g = 255; + if (b > 255) + b = 255; + + return (Color){r, g, b, baseColor.a}; +} + +// calculate_shadow_factor: Distance-based shadow softening +// Objects at higher elevations or farther from ground get softer, less +// pronounced shadows This simulates how shadows fade with distance and +// atmospheric scattering +static float calculate_shadow_factor(Vector3 worldPos, Vector3 lightDir, + float maxShadowDistance) { + // Calculate height above ground (approximate) + float distFromLight = fmaxf(0.0f, worldPos.y - 2.0f); + + // Fade out shadow strength with distance (max 15% darkening) + float shadowIntensity = + fmaxf(0.0f, 1.0f - (distFromLight / maxShadowDistance)); + return 1.0f - (shadowIntensity * 0.15f); +} + +// calculate_temporal_shadow: Time-based shadow variance for anti-aliasing +// Simulates subtle shadow movement to avoid banding artifacts +static float calculate_temporal_shadow(Vector3 worldPos, float timePhase) { + // Add subtle time-based variation to shadow boundaries + float noiseVal = + sinf(worldPos.x * 0.5f + timePhase) * cosf(worldPos.z * 0.5f + timePhase); + return 1.0f + (noiseVal * 0.02f); // Very subtle variation +} + +// apply_lighting_with_shadows: Full lighting calculation with shadows +// Combines directional light, ambient light, shadows, and temporal variation +static Color apply_lighting_with_shadows(Color baseColor, Vector3 normal, + Vector3 worldPos, + DirectionalLight light) { + // Calculate base shadow factor (distance-based) + float shadowFactor = + calculate_shadow_factor(worldPos, light.direction, 10.0f); + + // Apply shadow to intensity + float adjustedIntensity = light.intensity * shadowFactor; + + // Calculate diffuse component with adjusted intensity + Vector3 lightDir = light.direction; + float diff = fmaxf(0.0f, -(lightDir.x * normal.x + lightDir.y * normal.y + + lightDir.z * normal.z)); + + // Combine with ambient using adjusted intensity + float brightness = light.ambientIntensity + (diff * adjustedIntensity * + (1.0f - light.ambientIntensity)); + + // Apply brightness to base color + int r = (int)(baseColor.r * brightness); + int g = (int)(baseColor.g * brightness); + int b = (int)(baseColor.b * brightness); + + // Clamp values to valid RGB range + if (r > 255) + r = 255; + if (g > 255) + g = 255; + if (b > 255) + b = 255; + + return (Color){r, g, b, baseColor.a}; +} + +static Vector3 v3_normalize(Vector3 v) { + float len = sqrtf(v.x * v.x + v.y * v.y + v.z * v.z); + if (len < 0.0001f) + return (Vector3){0, 1, 0}; + return (Vector3){v.x / len, v.y / len, v.z / len}; +} +// ============== END LIGHTING HELPERS ============== + static void set_nonblocking(int sock) { #ifdef _WIN32 u_long mode = 1; @@ -427,7 +541,7 @@ int main(void) { #endif const int sw = 1280, sh = 720; - InitWindow(sw, sh, "Voxel Shooter - Client (SMOOTH TERRAIN)"); + InitWindow(sw, sh, "Voxel Shooter - Client (SMOOTH TERRAIN WITH SHADOWS)"); SetTargetFPS(120); char myName[USERNAME_MAX]; @@ -441,6 +555,16 @@ int main(void) { terrainModel.materials[0].maps[MATERIAL_MAP_DIFFUSE].color = (Color){80, 140, 70, 255}; + // Setup directional light + DirectionalLight dirLight = {0}; + dirLight.direction = + v3_normalize(v3(-0.8f, -1.0f, -0.6f)); // Coming from upper-left-back + dirLight.color = v3(1.0f, 1.0f, 1.0f); + dirLight.intensity = 1.2f; + dirLight.ambientIntensity = 0.3f; + dirLight.shadowBias = 0.005f; + dirLight.shadowIntensity = 0.4f; + int sock = (int)socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (sock < 0) { perror("socket"); @@ -672,10 +796,20 @@ int main(void) { BeginMode3D(cam); - // Draw smooth terrain + // Calculate terrain normal at camera position for lighting + Vector3 terrainNormal = v3(0, 1, 0); // Default up normal + + // Draw smooth terrain with directional lighting + shadows + // For Raylib, we apply lighting by adjusting material colors + Color terrainBaseColor = (Color){80, 140, 70, 255}; + Color litTerrainColor = apply_lighting_with_shadows( + terrainBaseColor, terrainNormal, v3(0, 2, 0), dirLight); + + terrainModel.materials[0].maps[MATERIAL_MAP_DIFFUSE].color = + litTerrainColor; DrawModel(terrainModel, (Vector3){0, 0, 0}, 1.0f, WHITE); - // Items + // Items with lighting and shadows for (int i = 0; i < MAX_ITEMS; i++) { if (!wi[i].present) continue; @@ -686,10 +820,14 @@ int main(void) { ic = (Color){255, 220, 120, 255}; if (wi[i].type == ITEM_AMMO_RIFLE) ic = (Color){255, 180, 120, 255}; - DrawSphere(wi[i].pos, 0.3f, ic); + + // Apply lighting with shadows to items + Color litColor = + apply_lighting_with_shadows(ic, terrainNormal, wi[i].pos, dirLight); + DrawSphere(wi[i].pos, 0.3f, litColor); } - // Players + // Players with lighting and shadows for (int i = 0; i < MAX_PLAYERS; i++) { if (!rp[i].present) continue; @@ -698,8 +836,12 @@ int main(void) { (i == myId) ? (Color){80, 180, 255, 255} : (Color){255, 80, 80, 255}; if (!rp[i].alive) c = (Color){120, 120, 120, 255}; + + // Apply lighting with shadows to player models + Color litPlayerColor = + apply_lighting_with_shadows(c, terrainNormal, p, dirLight); DrawCapsule(v3(p.x, p.y - 0.5f, p.z), v3(p.x, p.y + 0.5f, p.z), 0.35f, 8, - 8, c); + 8, litPlayerColor); } EndMode3D(); diff --git a/game/server b/game/server deleted file mode 100755 index c460ae13d219541e91533893bf27f2e6f839b19e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36424 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