337 lines
11 KiB
C
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; }
|