gam
This commit is contained in:
parent
7b8e798662
commit
d1782e1265
4 changed files with 221 additions and 27 deletions
BIN
game/client
BIN
game/client
Binary file not shown.
|
|
@ -43,7 +43,6 @@ typedef int socklen_t;
|
||||||
#define BTN_USE_MEDKIT (1u << 4)
|
#define BTN_USE_MEDKIT (1u << 4)
|
||||||
#define BTN_JUMP (1u << 5)
|
#define BTN_JUMP (1u << 5)
|
||||||
|
|
||||||
// ============== DIRECTIONAL LIGHT ==============
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
Vector3 direction; // Normalized light direction
|
Vector3 direction; // Normalized light direction
|
||||||
Vector3 color; // RGB color (0-1 range)
|
Vector3 color; // RGB color (0-1 range)
|
||||||
|
|
@ -53,7 +52,6 @@ typedef struct {
|
||||||
float shadowIntensity; // How dark shadows are (0-1)
|
float shadowIntensity; // How dark shadows are (0-1)
|
||||||
} DirectionalLight;
|
} DirectionalLight;
|
||||||
|
|
||||||
// ============== TERRAIN SHADER ==============
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
Shader shader;
|
Shader shader;
|
||||||
int locViewPos;
|
int locViewPos;
|
||||||
|
|
@ -86,7 +84,6 @@ static void unload_terrain_shader(TerrainShader *ts) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============== END TERRAIN SHADER ==============
|
|
||||||
static const uint8_t perm[512] = {
|
static const uint8_t perm[512] = {
|
||||||
151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7,
|
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,
|
225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190,
|
||||||
|
|
@ -284,9 +281,7 @@ static Mesh generate_terrain_mesh(void) {
|
||||||
UploadMesh(&mesh, false);
|
UploadMesh(&mesh, false);
|
||||||
return mesh;
|
return mesh;
|
||||||
}
|
}
|
||||||
// ============== END SIMPLEX NOISE ==============
|
|
||||||
|
|
||||||
// ============== LIGHTING HELPERS ==============
|
|
||||||
// apply_directional_lighting: Basic Lambertian diffuse lighting
|
// apply_directional_lighting: Basic Lambertian diffuse lighting
|
||||||
// Applies directional light with diffuse component based on surface normal
|
// Applies directional light with diffuse component based on surface normal
|
||||||
static Color apply_directional_lighting(Color baseColor, Vector3 normal,
|
static Color apply_directional_lighting(Color baseColor, Vector3 normal,
|
||||||
|
|
@ -389,7 +384,6 @@ static Vector3 v3_normalize(Vector3 v) {
|
||||||
return (Vector3){v.x / len, v.y / len, v.z / len};
|
return (Vector3){v.x / len, v.y / len, v.z / len};
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============== VECTOR3 HELPERS ==============
|
|
||||||
static Vector3 v3(float x, float y, float z) {
|
static Vector3 v3(float x, float y, float z) {
|
||||||
Vector3 v = {x, y, z};
|
Vector3 v = {x, y, z};
|
||||||
return v;
|
return v;
|
||||||
|
|
@ -412,9 +406,6 @@ static Vector3 v3_norm(Vector3 a) {
|
||||||
return v3(0, 0, 1);
|
return v3(0, 0, 1);
|
||||||
return v3(a.x / l, a.y / l, a.z / l);
|
return v3(a.x / l, a.y / l, a.z / l);
|
||||||
}
|
}
|
||||||
// ============== END VECTOR3 HELPERS ==============
|
|
||||||
|
|
||||||
// ============== ENHANCED LIGHTING & AMBIENT OCCLUSION ==============
|
|
||||||
|
|
||||||
// calculate_ambient_occlusion: Estimates how occluded a point is based on
|
// calculate_ambient_occlusion: Estimates how occluded a point is based on
|
||||||
// terrain curvature Samples heights in cardinal directions and computes horizon
|
// terrain curvature Samples heights in cardinal directions and computes horizon
|
||||||
|
|
@ -504,8 +495,6 @@ static Color apply_phong_lighting_per_pixel(Color baseColor, Vector3 normal,
|
||||||
return (Color){r, g, b, baseColor.a};
|
return (Color){r, g, b, baseColor.a};
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============== END ENHANCED LIGHTING & AMBIENT OCCLUSION ==============
|
|
||||||
|
|
||||||
static void set_nonblocking(int sock) {
|
static void set_nonblocking(int sock) {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
u_long mode = 1;
|
u_long mode = 1;
|
||||||
|
|
@ -523,7 +512,8 @@ typedef enum MsgType : uint8_t {
|
||||||
MSG_INPUT = 3,
|
MSG_INPUT = 3,
|
||||||
MSG_SNAPSHOT = 4,
|
MSG_SNAPSHOT = 4,
|
||||||
MSG_SHOOT = 5,
|
MSG_SHOOT = 5,
|
||||||
MSG_ITEMS = 6
|
MSG_ITEMS = 6,
|
||||||
|
MSG_ROOM_STATE = 7
|
||||||
} MsgType;
|
} MsgType;
|
||||||
|
|
||||||
typedef struct MsgHello {
|
typedef struct MsgHello {
|
||||||
|
|
@ -582,6 +572,14 @@ typedef struct MsgItems {
|
||||||
uint8_t count;
|
uint8_t count;
|
||||||
ItemNet items[MAX_ITEMS];
|
ItemNet items[MAX_ITEMS];
|
||||||
} MsgItems;
|
} MsgItems;
|
||||||
|
|
||||||
|
typedef struct MsgRoomState {
|
||||||
|
uint8_t type;
|
||||||
|
uint8_t state;
|
||||||
|
float countdownRemaining;
|
||||||
|
uint8_t winnerId;
|
||||||
|
char winnerName[USERNAME_MAX];
|
||||||
|
} MsgRoomState;
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
|
||||||
typedef struct RemotePlayer {
|
typedef struct RemotePlayer {
|
||||||
|
|
@ -711,12 +709,17 @@ int main(void) {
|
||||||
RemotePlayer rp[MAX_PLAYERS] = {0};
|
RemotePlayer rp[MAX_PLAYERS] = {0};
|
||||||
WorldItem wi[MAX_ITEMS] = {0};
|
WorldItem wi[MAX_ITEMS] = {0};
|
||||||
|
|
||||||
|
// Room state
|
||||||
|
int roomState = 0;
|
||||||
|
float countdownRemaining = 0.0f;
|
||||||
|
char winnerName[USERNAME_MAX] = {0};
|
||||||
|
|
||||||
Vector3 camPos = v3(0, 5.0f, 6);
|
Vector3 camPos = v3(0, 5.0f, 6);
|
||||||
float yaw = 0.0f, pitch = 0.0f;
|
float yaw = 0.0f, pitch = 0.0f;
|
||||||
|
|
||||||
float recoilYaw = 0.0f, recoilPitch = 0.0f, crossSpread = 0.0f;
|
float recoilYaw = 0.0f, recoilPitch = 0.0f, crossSpread = 0.0f;
|
||||||
int scoped = 0;
|
int scoped = 0;
|
||||||
float fireCooldown = 0.0f;
|
float fireCooldown = 0.0f;
|
||||||
int shotsInBurst = 0;
|
int shotsInBurst = 0;
|
||||||
float burstResetTimer = 0.0f;
|
float burstResetTimer = 0.0f;
|
||||||
|
|
||||||
|
|
@ -829,6 +832,14 @@ int main(void) {
|
||||||
wi[i].qty = m->items[i].qty;
|
wi[i].qty = m->items[i].qty;
|
||||||
wi[i].pos = v3(m->items[i].x, m->items[i].y, m->items[i].z);
|
wi[i].pos = v3(m->items[i].x, m->items[i].y, m->items[i].z);
|
||||||
}
|
}
|
||||||
|
} else if (type == MSG_ROOM_STATE && n >= (int)sizeof(MsgRoomState)) {
|
||||||
|
const MsgRoomState *rs = (const MsgRoomState *)buf;
|
||||||
|
roomState = rs->state;
|
||||||
|
countdownRemaining = rs->countdownRemaining;
|
||||||
|
memset(winnerName, 0, USERNAME_MAX);
|
||||||
|
if (rs->winnerId < 255) {
|
||||||
|
strncpy(winnerName, rs->winnerName, USERNAME_MAX - 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1068,6 +1079,30 @@ int main(void) {
|
||||||
|
|
||||||
DrawFPS(sw - 90, 10);
|
DrawFPS(sw - 90, 10);
|
||||||
|
|
||||||
|
// Room state overlay
|
||||||
|
if (roomState == 0) { // Waiting
|
||||||
|
DrawRectangle(sw / 2 - 150, sh / 2 - 50, 300, 100, (Color){0, 0, 0, 200});
|
||||||
|
DrawText("WAITING FOR PLAYERS", sw / 2 - 120, sh / 2 - 30, 24, RAYWHITE);
|
||||||
|
DrawText("Need at least 2 players", sw / 2 - 100, sh / 2 - 5, 18,
|
||||||
|
(Color){200, 200, 200, 255});
|
||||||
|
} else if (roomState == 1) { // Counting down
|
||||||
|
DrawRectangle(sw / 2 - 150, sh / 2 - 50, 300, 100, (Color){0, 0, 0, 200});
|
||||||
|
DrawText("GAME STARTING SOON", sw / 2 - 120, sh / 2 - 30, 24,
|
||||||
|
(Color){255, 255, 80, 255});
|
||||||
|
DrawText(TextFormat("%.1f seconds", countdownRemaining), sw / 2 - 60,
|
||||||
|
sh / 2 - 5, 20, RAYWHITE);
|
||||||
|
} else if (roomState == 3) { // Finished
|
||||||
|
DrawRectangle(sw / 2 - 200, sh / 2 - 50, 400, 100, (Color){0, 0, 0, 200});
|
||||||
|
DrawText("GAME FINISHED", sw / 2 - 80, sh / 2 - 30, 28,
|
||||||
|
(Color){255, 80, 80, 255});
|
||||||
|
if (winnerName[0]) {
|
||||||
|
DrawText(TextFormat("Winner: %s", winnerName), sw / 2 - 100, sh / 2 - 5,
|
||||||
|
24, (Color){255, 255, 80, 255});
|
||||||
|
} else {
|
||||||
|
DrawText("No winner", sw / 2 - 50, sh / 2 - 5, 24, RAYWHITE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Crosshair
|
// Crosshair
|
||||||
{
|
{
|
||||||
int cx = sw / 2, cy = sh / 2;
|
int cx = sw / 2, cy = sh / 2;
|
||||||
|
|
@ -1078,7 +1113,8 @@ int main(void) {
|
||||||
DrawRectangle(cx + gap, 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);
|
DrawRectangle(cx - thick / 2, cy - gap - len, thick, len, col);
|
||||||
DrawRectangle(cx - thick / 2, cy + gap, thick, len, col);
|
DrawRectangle(cx - thick / 2, cy + gap, thick, len, col);
|
||||||
DrawCircleLines(cx, cy, scoped ? 1.5f : 3.0f, (Color){240, 240, 245, 160});
|
DrawCircleLines(cx, cy, scoped ? 1.5f : 3.0f,
|
||||||
|
(Color){240, 240, 245, 160});
|
||||||
}
|
}
|
||||||
|
|
||||||
EndDrawing();
|
EndDrawing();
|
||||||
|
|
|
||||||
BIN
game/server
BIN
game/server
Binary file not shown.
180
game/server.c
180
game/server.c
|
|
@ -29,7 +29,7 @@ typedef int socklen_t;
|
||||||
#define MAX_PLAYERS 16
|
#define MAX_PLAYERS 16
|
||||||
#define USERNAME_MAX 16
|
#define USERNAME_MAX 16
|
||||||
|
|
||||||
#define TICK_HZ 30
|
#define TICK_HZ 20
|
||||||
#define DT (1.0f / (float)TICK_HZ)
|
#define DT (1.0f / (float)TICK_HZ)
|
||||||
|
|
||||||
#define WEAPON_PISTOL 0
|
#define WEAPON_PISTOL 0
|
||||||
|
|
@ -49,11 +49,15 @@ typedef int socklen_t;
|
||||||
#define BTN_USE_MEDKIT (1u << 4)
|
#define BTN_USE_MEDKIT (1u << 4)
|
||||||
#define BTN_JUMP (1u << 5)
|
#define BTN_JUMP (1u << 5)
|
||||||
|
|
||||||
|
#define ROOM_STATE_WAITING 0
|
||||||
|
#define ROOM_STATE_COUNTING_DOWN 1
|
||||||
|
#define ROOM_STATE_RUNNING 2
|
||||||
|
#define ROOM_STATE_FINISHED 3
|
||||||
|
|
||||||
#define CAT_WORLD (1u << 0)
|
#define CAT_WORLD (1u << 0)
|
||||||
#define CAT_PLAYER (1u << 1)
|
#define CAT_PLAYER (1u << 1)
|
||||||
#define CAT_ITEM (1u << 2)
|
#define CAT_ITEM (1u << 2)
|
||||||
|
|
||||||
// ============== SIMPLEX NOISE ==============
|
|
||||||
static const uint8_t perm[512] = {
|
static const uint8_t perm[512] = {
|
||||||
151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7,
|
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,
|
225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190,
|
||||||
|
|
@ -167,7 +171,6 @@ static void generate_smooth_terrain(void) {
|
||||||
|
|
||||||
printf("Generated smooth heightmap terrain %dx%d\n", size, size);
|
printf("Generated smooth heightmap terrain %dx%d\n", size, size);
|
||||||
}
|
}
|
||||||
// ============== END SIMPLEX NOISE ==============
|
|
||||||
|
|
||||||
static double now_seconds(void) {
|
static double now_seconds(void) {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
|
@ -204,7 +207,8 @@ typedef enum MsgType : uint8_t {
|
||||||
MSG_INPUT = 3,
|
MSG_INPUT = 3,
|
||||||
MSG_SNAPSHOT = 4,
|
MSG_SNAPSHOT = 4,
|
||||||
MSG_SHOOT = 5,
|
MSG_SHOOT = 5,
|
||||||
MSG_ITEMS = 6
|
MSG_ITEMS = 6,
|
||||||
|
MSG_ROOM_STATE = 7
|
||||||
} MsgType;
|
} MsgType;
|
||||||
|
|
||||||
typedef struct MsgHello {
|
typedef struct MsgHello {
|
||||||
|
|
@ -263,6 +267,14 @@ typedef struct MsgItems {
|
||||||
uint8_t count;
|
uint8_t count;
|
||||||
ItemNet items[MAX_ITEMS];
|
ItemNet items[MAX_ITEMS];
|
||||||
} MsgItems;
|
} MsgItems;
|
||||||
|
|
||||||
|
typedef struct MsgRoomState {
|
||||||
|
uint8_t type;
|
||||||
|
uint8_t state;
|
||||||
|
float countdownRemaining;
|
||||||
|
uint8_t winnerId;
|
||||||
|
char winnerName[USERNAME_MAX];
|
||||||
|
} MsgRoomState;
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
|
||||||
typedef struct Item {
|
typedef struct Item {
|
||||||
|
|
@ -291,6 +303,13 @@ typedef struct Player {
|
||||||
dGeomID geom;
|
dGeomID geom;
|
||||||
} Player;
|
} Player;
|
||||||
|
|
||||||
|
typedef struct RoomState {
|
||||||
|
int state;
|
||||||
|
double countdownEndTime;
|
||||||
|
int winnerId;
|
||||||
|
int playerCount;
|
||||||
|
} RoomState;
|
||||||
|
|
||||||
static int addr_equal(const struct sockaddr_in *a,
|
static int addr_equal(const struct sockaddr_in *a,
|
||||||
const struct sockaddr_in *b) {
|
const struct sockaddr_in *b) {
|
||||||
return (a->sin_family == b->sin_family) && (a->sin_port == b->sin_port) &&
|
return (a->sin_family == b->sin_family) && (a->sin_port == b->sin_port) &&
|
||||||
|
|
@ -319,6 +338,7 @@ static dSpaceID gSpace;
|
||||||
static dJointGroupID gContactGroup;
|
static dJointGroupID gContactGroup;
|
||||||
static Item gItems[MAX_ITEMS];
|
static Item gItems[MAX_ITEMS];
|
||||||
static uint16_t gNextItemId = 1;
|
static uint16_t gNextItemId = 1;
|
||||||
|
static RoomState gRoomState;
|
||||||
|
|
||||||
static void item_destroy(Item *it) {
|
static void item_destroy(Item *it) {
|
||||||
if (it->geom) {
|
if (it->geom) {
|
||||||
|
|
@ -397,10 +417,25 @@ static void ode_init_world(void) {
|
||||||
dGeomSetCategoryBits(gTerrainGeom, CAT_WORLD);
|
dGeomSetCategoryBits(gTerrainGeom, CAT_WORLD);
|
||||||
dGeomSetCollideBits(gTerrainGeom, CAT_PLAYER);
|
dGeomSetCollideBits(gTerrainGeom, CAT_PLAYER);
|
||||||
|
|
||||||
item_spawn(ITEM_MEDKIT, 1, 10.0f, 10.0f);
|
// Spawn initial random loot
|
||||||
item_spawn(ITEM_MEDKIT, 1, -15.0f, 20.0f);
|
for (int i = 0; i < 4; i++) {
|
||||||
item_spawn(ITEM_AMMO_RIFLE, 30, 5.0f, -10.0f);
|
float x = (float)(rand() % 200 - 100);
|
||||||
item_spawn(ITEM_AMMO_PISTOL, 12, -10.0f, -15.0f);
|
float z = (float)(rand() % 200 - 100);
|
||||||
|
uint8_t type;
|
||||||
|
int qty;
|
||||||
|
int type_idx = rand() % 3;
|
||||||
|
if (type_idx == 0) {
|
||||||
|
type = ITEM_MEDKIT;
|
||||||
|
qty = 1;
|
||||||
|
} else if (type_idx == 1) {
|
||||||
|
type = ITEM_AMMO_PISTOL;
|
||||||
|
qty = 10 + (rand() % 6);
|
||||||
|
} else {
|
||||||
|
type = ITEM_AMMO_RIFLE;
|
||||||
|
qty = 20 + (rand() % 11);
|
||||||
|
}
|
||||||
|
item_spawn(type, qty, x, z);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ode_shutdown_world(void) {
|
static void ode_shutdown_world(void) {
|
||||||
|
|
@ -422,8 +457,9 @@ static void player_ode_create(Player *p, int spawnIndex) {
|
||||||
dBodySetLinearDamping(p->body, 0.05);
|
dBodySetLinearDamping(p->body, 0.05);
|
||||||
dBodySetAngularDamping(p->body, 0.99);
|
dBodySetAngularDamping(p->body, 0.99);
|
||||||
|
|
||||||
float sx = (float)(spawnIndex * 4 - 8);
|
// Random spawn position
|
||||||
float sz = (float)(spawnIndex * 3 - 6);
|
float sx = (float)(rand() % 200 - 100); // -100 to 99
|
||||||
|
float sz = (float)(rand() % 200 - 100);
|
||||||
float sy = get_terrain_height(sx, sz) + 3.0f;
|
float sy = get_terrain_height(sx, sz) + 3.0f;
|
||||||
dBodySetPosition(p->body, sx, sy, sz);
|
dBodySetPosition(p->body, sx, sy, sz);
|
||||||
|
|
||||||
|
|
@ -553,6 +589,12 @@ int main(void) {
|
||||||
|
|
||||||
srand((unsigned)time(NULL));
|
srand((unsigned)time(NULL));
|
||||||
|
|
||||||
|
// Initialize room state
|
||||||
|
gRoomState.state = ROOM_STATE_WAITING;
|
||||||
|
gRoomState.countdownEndTime = 0.0;
|
||||||
|
gRoomState.winnerId = -1;
|
||||||
|
gRoomState.playerCount = 0;
|
||||||
|
|
||||||
ode_init_world();
|
ode_init_world();
|
||||||
|
|
||||||
int sock = (int)socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
int sock = (int)socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||||
|
|
@ -612,6 +654,9 @@ int main(void) {
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (pid < 0) {
|
if (pid < 0) {
|
||||||
|
// Only allow joining if room is not running
|
||||||
|
if (gRoomState.state == ROOM_STATE_RUNNING)
|
||||||
|
continue;
|
||||||
for (int i = 0; i < MAX_PLAYERS; i++)
|
for (int i = 0; i < MAX_PLAYERS; i++)
|
||||||
if (!players[i].inUse) {
|
if (!players[i].inUse) {
|
||||||
pid = i;
|
pid = i;
|
||||||
|
|
@ -631,6 +676,7 @@ int main(void) {
|
||||||
p->medkits = 2;
|
p->medkits = 2;
|
||||||
p->rng = 0xA341316Cu ^ (uint32_t)(i * 2654435761u);
|
p->rng = 0xA341316Cu ^ (uint32_t)(i * 2654435761u);
|
||||||
player_ode_create(p, i);
|
player_ode_create(p, i);
|
||||||
|
gRoomState.playerCount++;
|
||||||
printf("Player %d joined as '%s'\n", pid, p->username);
|
printf("Player %d joined as '%s'\n", pid, p->username);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -715,7 +761,7 @@ int main(void) {
|
||||||
} else if (type == MSG_SHOOT && n >= (int)sizeof(MsgShoot)) {
|
} else if (type == MSG_SHOOT && n >= (int)sizeof(MsgShoot)) {
|
||||||
const MsgShoot *m = (const MsgShoot *)buf;
|
const MsgShoot *m = (const MsgShoot *)buf;
|
||||||
if (pid >= 0 && m->playerId == (uint8_t)pid && players[pid].inUse &&
|
if (pid >= 0 && m->playerId == (uint8_t)pid && players[pid].inUse &&
|
||||||
players[pid].alive) {
|
players[pid].alive && gRoomState.state == ROOM_STATE_RUNNING) {
|
||||||
Player *sh = &players[pid];
|
Player *sh = &players[pid];
|
||||||
const double now = now_seconds();
|
const double now = now_seconds();
|
||||||
double fireInterval =
|
double fireInterval =
|
||||||
|
|
@ -783,6 +829,37 @@ int main(void) {
|
||||||
if (players[victim].hp <= 0) {
|
if (players[victim].hp <= 0) {
|
||||||
players[victim].hp = 0;
|
players[victim].hp = 0;
|
||||||
players[victim].alive = 0;
|
players[victim].alive = 0;
|
||||||
|
// Drop loot
|
||||||
|
const dReal *pos = dBodyGetPosition(players[victim].body);
|
||||||
|
float x = (float)pos[0], z = (float)pos[2];
|
||||||
|
if (players[victim].pistolAmmo > 0) {
|
||||||
|
int qty = players[victim].pistolAmmo;
|
||||||
|
if (qty > 12)
|
||||||
|
qty = 12; // Max per drop
|
||||||
|
item_spawn(ITEM_AMMO_PISTOL, qty,
|
||||||
|
x + frand_signed(&players[victim].rng) * 2.0f,
|
||||||
|
z + frand_signed(&players[victim].rng) * 2.0f);
|
||||||
|
players[victim].pistolAmmo -= qty;
|
||||||
|
}
|
||||||
|
if (players[victim].rifleAmmo > 0) {
|
||||||
|
int qty = players[victim].rifleAmmo;
|
||||||
|
if (qty > 30)
|
||||||
|
qty = 30;
|
||||||
|
item_spawn(ITEM_AMMO_RIFLE, qty,
|
||||||
|
x + frand_signed(&players[victim].rng) * 2.0f,
|
||||||
|
z + frand_signed(&players[victim].rng) * 2.0f);
|
||||||
|
players[victim].rifleAmmo -= qty;
|
||||||
|
}
|
||||||
|
if (players[victim].medkits > 0) {
|
||||||
|
int qty = players[victim].medkits;
|
||||||
|
if (qty > 2)
|
||||||
|
qty = 2;
|
||||||
|
item_spawn(ITEM_MEDKIT, qty,
|
||||||
|
x + frand_signed(&players[victim].rng) * 2.0f,
|
||||||
|
z + frand_signed(&players[victim].rng) * 2.0f);
|
||||||
|
players[victim].medkits -= qty;
|
||||||
|
}
|
||||||
|
printf("Player %d died and dropped loot\n", victim);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -790,6 +867,66 @@ int main(void) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Room state management
|
||||||
|
{
|
||||||
|
double now = now_seconds();
|
||||||
|
if (gRoomState.state == ROOM_STATE_WAITING) {
|
||||||
|
// Count active players
|
||||||
|
int activePlayers = 0;
|
||||||
|
for (int i = 0; i < MAX_PLAYERS; i++) {
|
||||||
|
if (players[i].inUse)
|
||||||
|
activePlayers++;
|
||||||
|
}
|
||||||
|
gRoomState.playerCount = activePlayers;
|
||||||
|
if (activePlayers >= 2) {
|
||||||
|
gRoomState.state = ROOM_STATE_COUNTING_DOWN;
|
||||||
|
gRoomState.countdownEndTime = now + 10.0; // 10 seconds
|
||||||
|
printf("Starting countdown for game start\n");
|
||||||
|
}
|
||||||
|
} else if (gRoomState.state == ROOM_STATE_COUNTING_DOWN) {
|
||||||
|
if (now >= gRoomState.countdownEndTime) {
|
||||||
|
gRoomState.state = ROOM_STATE_RUNNING;
|
||||||
|
// Respawn and heal all players
|
||||||
|
for (int i = 0; i < MAX_PLAYERS; i++) {
|
||||||
|
if (players[i].inUse) {
|
||||||
|
players[i].alive = 1;
|
||||||
|
players[i].hp = 100;
|
||||||
|
player_ode_create(&players[i], i); // Recreate body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
printf("Game started!\n");
|
||||||
|
}
|
||||||
|
} else if (gRoomState.state == ROOM_STATE_RUNNING) {
|
||||||
|
// Check win condition
|
||||||
|
int aliveCount = 0;
|
||||||
|
int lastAlive = -1;
|
||||||
|
for (int i = 0; i < MAX_PLAYERS; i++) {
|
||||||
|
if (players[i].inUse && players[i].alive) {
|
||||||
|
aliveCount++;
|
||||||
|
lastAlive = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (aliveCount <= 1) {
|
||||||
|
gRoomState.state = ROOM_STATE_FINISHED;
|
||||||
|
gRoomState.winnerId = lastAlive;
|
||||||
|
printf("Game finished! Winner: %s\n",
|
||||||
|
lastAlive >= 0 ? players[lastAlive].username : "None");
|
||||||
|
}
|
||||||
|
} else if (gRoomState.state == ROOM_STATE_FINISHED) {
|
||||||
|
// Wait a bit, then reset
|
||||||
|
static double finishTime = 0.0;
|
||||||
|
if (finishTime == 0.0)
|
||||||
|
finishTime = now;
|
||||||
|
if (now - finishTime > 5.0) { // 5 seconds after finish
|
||||||
|
gRoomState.state = ROOM_STATE_WAITING;
|
||||||
|
gRoomState.winnerId = -1;
|
||||||
|
finishTime = 0.0;
|
||||||
|
// Remove dead players or something? For now, keep them
|
||||||
|
printf("Room resetting to waiting\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
double t = now_seconds();
|
double t = now_seconds();
|
||||||
if (t >= nextTick) {
|
if (t >= nextTick) {
|
||||||
nextTick += DT;
|
nextTick += DT;
|
||||||
|
|
@ -923,6 +1060,27 @@ int main(void) {
|
||||||
}
|
}
|
||||||
broadcast(sock, players, &im, (int)sizeof(im));
|
broadcast(sock, players, &im, (int)sizeof(im));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send room state every few ticks
|
||||||
|
static uint32_t roomStateTickDiv = 0;
|
||||||
|
roomStateTickDiv++;
|
||||||
|
if (roomStateTickDiv >= 30) { // Every 30 ticks, about 0.5s
|
||||||
|
roomStateTickDiv = 0;
|
||||||
|
MsgRoomState rs = {0};
|
||||||
|
rs.type = MSG_ROOM_STATE;
|
||||||
|
rs.state = (uint8_t)gRoomState.state;
|
||||||
|
rs.countdownRemaining =
|
||||||
|
(gRoomState.state == ROOM_STATE_COUNTING_DOWN)
|
||||||
|
? (float)(gRoomState.countdownEndTime - now_seconds())
|
||||||
|
: 0.0f;
|
||||||
|
rs.winnerId =
|
||||||
|
(gRoomState.winnerId >= 0) ? (uint8_t)gRoomState.winnerId : 255;
|
||||||
|
if (gRoomState.winnerId >= 0) {
|
||||||
|
strncpy(rs.winnerName, players[gRoomState.winnerId].username,
|
||||||
|
USERNAME_MAX - 1);
|
||||||
|
}
|
||||||
|
broadcast(sock, players, &rs, (int)sizeof(rs));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
double now = now_seconds();
|
double now = now_seconds();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue