From 83334091ba0b11f62e71df0fb4ccbdde4299cbbf Mon Sep 17 00:00:00 2001 From: Masashi Date: Thu, 18 Dec 2025 19:07:53 +0530 Subject: [PATCH] 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 } }