suicmez/game/inc/suic_net.c
2025-12-20 15:09:29 +05:30

337 lines
11 KiB
C

#include "suic_net.h"
#include <stdio.h>
#include <string.h>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
typedef int socklen_t;
#define CLOSESOCK closesocket
#else
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <unistd.h>
#define CLOSESOCK close
#endif
#define PROTOCOL_VERSION 67
/* ===== MESSAGE TYPES ===== */
#pragma pack(push, 1)
typedef enum MsgType : uint8_t {
MSG_HELLO = 1,
MSG_WELCOME = 2,
MSG_INPUT = 3,
MSG_SNAPSHOT = 4,
MSG_SHOOT = 5,
MSG_ITEMS = 6,
MSG_ROOM_STATE = 7
} MsgType;
typedef struct MsgHello {
uint8_t type;
uint32_t protocol;
char username[SUIC_NET_USERNAME_MAX];
} MsgHello;
typedef struct MsgWelcome {
uint8_t type;
uint8_t playerId;
uint32_t serverTick;
} MsgWelcome;
typedef struct MsgInput {
uint8_t type;
uint8_t playerId;
uint32_t clientTick;
float moveX, moveZ, yaw, pitch;
uint8_t buttons;
} MsgInput;
typedef struct MsgShoot {
uint8_t type;
uint8_t playerId;
uint32_t clientTick;
} MsgShoot;
typedef struct PlayerStateNet {
uint8_t id, alive;
int16_t hp;
float x, y, z, yaw, pitch;
uint8_t weapon;
int16_t pistolMag, rifleMag, pistolAmmo, rifleAmmo, medkits;
int16_t reloadTimeLeft;
char username[SUIC_NET_USERNAME_MAX];
} PlayerStateNet;
typedef struct MsgSnapshot {
uint8_t type;
uint32_t serverTick;
uint8_t count;
PlayerStateNet p[SUIC_NET_MAX_PLAYERS];
} MsgSnapshot;
typedef struct ItemNet {
uint16_t id;
uint8_t type;
int16_t qty;
float x, y, z;
} ItemNet;
typedef struct MsgItems {
uint8_t type;
uint32_t serverTick;
uint8_t count;
ItemNet items[SUIC_NET_MAX_ITEMS];
} MsgItems;
typedef struct MsgRoomState {
uint8_t type;
uint8_t state;
float countdownRemaining;
uint8_t winnerId;
char winnerName[SUIC_NET_USERNAME_MAX];
} MsgRoomState;
#pragma pack(pop)
/* ===== LOCAL PLAYER DATA ===== */
typedef struct RemotePlayer {
int present, alive, hp;
float x, y, z;
float prevX, prevY, prevZ;
float yaw, pitch;
uint8_t weapon;
int16_t pistolMag, rifleMag, pistolAmmo, rifleAmmo, medkits;
int16_t reloadTimeLeft;
char username[SUIC_NET_USERNAME_MAX];
} RemotePlayer;
typedef struct WorldItem {
int present;
uint16_t id;
uint8_t type;
int16_t qty;
float x, y, z;
} WorldItem;
/* ===== STATIC STATE ===== */
static int g_sock = -1;
static struct sockaddr_in g_server = {0};
static uint8_t g_myId = 255;
static RemotePlayer g_players[SUIC_NET_MAX_PLAYERS] = {0};
static WorldItem g_items[SUIC_NET_MAX_ITEMS] = {0};
static int g_roomState = 0;
static float g_countdown = 0.0f;
static char g_winnerName[SUIC_NET_USERNAME_MAX] = {0};
static void set_nonblocking(int sock) {
#ifdef _WIN32
u_long mode = 1;
ioctlsocket(sock, FIONBIO, &mode);
#else
int flags = fcntl(sock, F_GETFL, 0);
fcntl(sock, F_SETFL, flags | O_NONBLOCK);
#endif
}
/* ===== PUBLIC API ===== */
int suic_net_init(const char* server_ip, int port) {
#ifdef _WIN32
WSADATA wsa;
WSAStartup(MAKEWORD(2, 2), &wsa);
#endif
g_sock = (int)socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (g_sock < 0) {
perror("socket");
return -1;
}
set_nonblocking(g_sock);
memset(&g_server, 0, sizeof(g_server));
g_server.sin_family = AF_INET;
g_server.sin_port = htons(port);
inet_pton(AF_INET, server_ip, &g_server.sin_addr);
g_myId = 255;
memset(g_players, 0, sizeof(g_players));
memset(g_items, 0, sizeof(g_items));
return 0;
}
void suic_net_shutdown(void) {
if (g_sock >= 0) {
CLOSESOCK(g_sock);
g_sock = -1;
}
#ifdef _WIN32
WSACleanup();
#endif
}
bool suic_net_is_connected(void) {
return g_myId != 255;
}
void suic_net_send_hello(const char* username) {
MsgHello hello = {0};
hello.type = MSG_HELLO;
hello.protocol = PROTOCOL_VERSION;
strncpy(hello.username, username, SUIC_NET_USERNAME_MAX - 1);
sendto(g_sock, (const char*)&hello, (int)sizeof(hello), 0,
(const struct sockaddr*)&g_server, sizeof(g_server));
}
void suic_net_send_input(uint8_t playerId, uint32_t clientTick,
float moveX, float moveZ, float yaw, float pitch,
uint8_t buttons) {
MsgInput in = {0};
in.type = MSG_INPUT;
in.playerId = playerId;
in.clientTick = clientTick;
in.moveX = moveX;
in.moveZ = moveZ;
in.yaw = yaw;
in.pitch = pitch;
in.buttons = buttons;
sendto(g_sock, (const char*)&in, (int)sizeof(in), 0,
(const struct sockaddr*)&g_server, sizeof(g_server));
}
void suic_net_send_shoot(uint8_t playerId, uint32_t clientTick) {
MsgShoot msg = {0};
msg.type = MSG_SHOOT;
msg.playerId = playerId;
msg.clientTick = clientTick;
sendto(g_sock, (const char*)&msg, (int)sizeof(msg), 0,
(const struct sockaddr*)&g_server, sizeof(g_server));
}
bool suic_net_poll(void) {
bool receivedAny = false;
for (;;) {
uint8_t buf[1400];
struct sockaddr_in from = {0};
socklen_t fromLen = sizeof(from);
int n = (int)recvfrom(g_sock, (char*)buf, (int)sizeof(buf), 0,
(struct sockaddr*)&from, &fromLen);
if (n <= 0) {
#ifdef _WIN32
if (WSAGetLastError() == WSAEWOULDBLOCK) break;
#else
if (errno == EWOULDBLOCK || errno == EAGAIN) break;
#endif
break;
}
receivedAny = true;
uint8_t type = buf[0];
if (type == MSG_WELCOME && n >= (int)sizeof(MsgWelcome)) {
MsgWelcome* w = (MsgWelcome*)buf;
g_myId = w->playerId;
} else if (type == MSG_SNAPSHOT && n >= (int)sizeof(MsgSnapshot)) {
MsgSnapshot* s = (MsgSnapshot*)buf;
for (int i = 0; i < SUIC_NET_MAX_PLAYERS; i++)
g_players[i].present = 0;
for (int i = 0; i < (int)s->count && i < SUIC_NET_MAX_PLAYERS; i++) {
PlayerStateNet* ps = &s->p[i];
if (ps->id >= SUIC_NET_MAX_PLAYERS) continue;
RemotePlayer* p = &g_players[ps->id];
p->prevX = p->x;
p->prevY = p->y;
p->prevZ = p->z;
p->present = 1;
p->alive = ps->alive;
p->hp = ps->hp;
p->x = ps->x;
p->y = ps->y;
p->z = ps->z;
p->yaw = ps->yaw;
p->pitch = ps->pitch;
p->weapon = ps->weapon;
p->pistolMag = ps->pistolMag;
p->rifleMag = ps->rifleMag;
p->pistolAmmo = ps->pistolAmmo;
p->rifleAmmo = ps->rifleAmmo;
p->medkits = ps->medkits;
p->reloadTimeLeft = ps->reloadTimeLeft;
memset(p->username, 0, SUIC_NET_USERNAME_MAX);
strncpy(p->username, ps->username, SUIC_NET_USERNAME_MAX - 1);
}
} else if (type == MSG_ITEMS && n >= (int)sizeof(MsgItems)) {
MsgItems* m = (MsgItems*)buf;
for (int i = 0; i < SUIC_NET_MAX_ITEMS; i++)
g_items[i].present = 0;
for (int i = 0; i < (int)m->count && i < SUIC_NET_MAX_ITEMS; i++) {
g_items[i].present = 1;
g_items[i].id = m->items[i].id;
g_items[i].type = m->items[i].type;
g_items[i].qty = m->items[i].qty;
g_items[i].x = m->items[i].x;
g_items[i].y = m->items[i].y;
g_items[i].z = m->items[i].z;
}
} else if (type == MSG_ROOM_STATE && n >= (int)sizeof(MsgRoomState)) {
const MsgRoomState* rs = (const MsgRoomState*)buf;
g_roomState = rs->state;
g_countdown = rs->countdownRemaining;
memset(g_winnerName, 0, SUIC_NET_USERNAME_MAX);
if (rs->winnerId < 255) {
strncpy(g_winnerName, rs->winnerName, SUIC_NET_USERNAME_MAX - 1);
}
}
}
return receivedAny;
}
uint8_t suic_net_get_my_id(void) { return g_myId; }
/* Player accessors */
bool suic_net_player_present(int i) { return i >= 0 && i < SUIC_NET_MAX_PLAYERS && g_players[i].present; }
bool suic_net_player_alive(int i) { return suic_net_player_present(i) && g_players[i].alive; }
int suic_net_player_hp(int i) { return suic_net_player_present(i) ? g_players[i].hp : 0; }
float suic_net_player_x(int i) { return suic_net_player_present(i) ? g_players[i].x : 0; }
float suic_net_player_y(int i) { return suic_net_player_present(i) ? g_players[i].y : 0; }
float suic_net_player_z(int i) { return suic_net_player_present(i) ? g_players[i].z : 0; }
float suic_net_player_prev_x(int i) { return suic_net_player_present(i) ? g_players[i].prevX : 0; }
float suic_net_player_prev_y(int i) { return suic_net_player_present(i) ? g_players[i].prevY : 0; }
float suic_net_player_prev_z(int i) { return suic_net_player_present(i) ? g_players[i].prevZ : 0; }
float suic_net_player_yaw(int i) { return suic_net_player_present(i) ? g_players[i].yaw : 0; }
float suic_net_player_pitch(int i) { return suic_net_player_present(i) ? g_players[i].pitch : 0; }
uint8_t suic_net_player_weapon(int i) { return suic_net_player_present(i) ? g_players[i].weapon : 0; }
int16_t suic_net_player_pistol_mag(int i) { return suic_net_player_present(i) ? g_players[i].pistolMag : 0; }
int16_t suic_net_player_rifle_mag(int i) { return suic_net_player_present(i) ? g_players[i].rifleMag : 0; }
int16_t suic_net_player_pistol_ammo(int i) { return suic_net_player_present(i) ? g_players[i].pistolAmmo : 0; }
int16_t suic_net_player_rifle_ammo(int i) { return suic_net_player_present(i) ? g_players[i].rifleAmmo : 0; }
int16_t suic_net_player_medkits(int i) { return suic_net_player_present(i) ? g_players[i].medkits : 0; }
int16_t suic_net_player_reload_time(int i) { return suic_net_player_present(i) ? g_players[i].reloadTimeLeft : 0; }
const char* suic_net_player_username(int i) { return suic_net_player_present(i) ? g_players[i].username : ""; }
/* Item accessors */
bool suic_net_item_present(int i) { return i >= 0 && i < SUIC_NET_MAX_ITEMS && g_items[i].present; }
uint16_t suic_net_item_id(int i) { return suic_net_item_present(i) ? g_items[i].id : 0; }
uint8_t suic_net_item_type(int i) { return suic_net_item_present(i) ? g_items[i].type : 0; }
int16_t suic_net_item_qty(int i) { return suic_net_item_present(i) ? g_items[i].qty : 0; }
float suic_net_item_x(int i) { return suic_net_item_present(i) ? g_items[i].x : 0; }
float suic_net_item_y(int i) { return suic_net_item_present(i) ? g_items[i].y : 0; }
float suic_net_item_z(int i) { return suic_net_item_present(i) ? g_items[i].z : 0; }
/* Room state */
int suic_net_room_state(void) { return g_roomState; }
float suic_net_countdown(void) { return g_countdown; }
const char* suic_net_winner_name(void) { return g_winnerName; }