#include "suic_net.h" #include #include #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include #include #pragma comment(lib, "ws2_32.lib") typedef int socklen_t; #define CLOSESOCK closesocket #else #include #include #include #include #include #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; }