REAL GAM
This commit is contained in:
parent
c880f9dff0
commit
83334091ba
6 changed files with 260 additions and 430 deletions
BIN
game/client
Executable file
BIN
game/client
Executable file
Binary file not shown.
245
game/client.c
245
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 <math.h>
|
||||
#include <stdint.h>
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
7
game/run.sh
Executable file
7
game/run.sh
Executable file
|
|
@ -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
|
||||
BIN
game/server
Executable file
BIN
game/server
Executable file
Binary file not shown.
454
game/server.c
454
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 <math.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue