Files
perfect_dark/port/src/net/net.c
T

987 lines
26 KiB
C

#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <ctype.h>
#include <string.h>
#include "platform.h"
#include "net/netenet.h"
#include "net/net.h"
#include "net/netbuf.h"
#include "net/netmsg.h"
#include "types.h"
#include "constants.h"
#include "data.h"
#include "bss.h"
#include "game/hudmsg.h"
#include "game/playermgr.h"
#include "game/bondgun.h"
#include "game/game_1531a0.h"
#include "game/game_0b0fd0.h"
#include "lib/main.h"
#include "lib/vi.h"
#include "config.h"
#include "system.h"
#include "console.h"
#include "utils.h"
s32 g_NetMode = NETMODE_NONE;
u32 g_NetServerUpdateRate = 1;
u32 g_NetServerInRate = 128 * 1024;
u32 g_NetServerOutRate = 128 * 1024;
u32 g_NetServerPort = NET_DEFAULT_PORT;
u32 g_NetClientUpdateRate = 1;
u32 g_NetClientInRate = 128 * 1024;
u32 g_NetClientOutRate = 128 * 1024;
u32 g_NetInterpTicks = 3;
char g_NetLastJoinAddr[NET_MAX_ADDR + 1] = "127.0.0.1:27100";
u32 g_NetTick = 0;
u32 g_NetNextSyncId = 1;
s32 g_NetSimPacketLoss = 0;
s32 g_NetDebugDraw = 0;
u64 g_NetRngSeeds[2];
u32 g_NetRngLatch = 0;
s32 g_NetMaxClients = NET_MAX_CLIENTS;
s32 g_NetNumClients = 0;
struct netclient g_NetClients[NET_MAX_CLIENTS + 1]; // last is an extra temporary client
struct netclient *g_NetLocalClient = &g_NetClients[NET_MAX_CLIENTS];
static u8 g_NetMsgBuf[NET_BUFSIZE];
struct netbuf g_NetMsg = { .data = g_NetMsgBuf, .size = sizeof(g_NetMsgBuf) };
static u8 g_NetMsgRelBuf[NET_BUFSIZE * 4]; // reliable buffer can be reliably fragmented
struct netbuf g_NetMsgRel = { .data = g_NetMsgRelBuf, .size = sizeof(g_NetMsgRelBuf) };
static s32 g_NetInit = false;
static ENetHost *g_NetHost;
static ENetAddress g_NetLocalAddr;
static ENetAddress g_NetRemoteAddr;
static u32 g_NetNextUpdate = 0;
static u32 g_NetReliableFrameLen = 0;
static u32 g_NetUnreliableFrameLen = 0;
static s32 netParseAddr(ENetAddress *out, const char *str)
{
char tmp[256] = { 0 };
if (!str || !str[0]) {
return false;
}
strncpy(tmp, str, sizeof(tmp) - 1);
char *host = tmp;
char *port = NULL;
if (tmp[0] == '[') {
// ipv6 with port: [ADDR]:PORT
host = tmp + 1; // skip [
port = strrchr(host, ']'); // find ]
if (port) {
if (port[1] != ':' || !isdigit(port[2])) {
return false;
}
*port = '\0'; // terminate ip
port += 2; // skip ]:
}
} else {
// ipv4 or hostname
port = strrchr(host, ':');
if (port) {
if (!isdigit(port[1])) {
return false;
}
*port = '\0'; // terminate address
++port; // skip :
}
}
if (!host[0]) {
return false;
}
const s32 portval = port ? atoi(port) : NET_DEFAULT_PORT;
if (portval < 0 || portval > 0xFFFF) {
return false;
}
memset(out, 0, sizeof(*out));
out->port = portval;
if (isdigit(host[0]) || strchr(host, ':')) {
// we stripped off the :PORT at this point, now check if this is an IP address
if (enet_address_set_ip(out, host) == 0) {
return true;
}
}
// must be a domain name; do a lookup
return (enet_address_set_hostname(out, host) == 0);
}
static const char *netFormatAddr(const ENetAddress *addr)
{
static char str[256];
char tmp[256];
if (addr && enet_address_get_ip(addr, tmp, sizeof(tmp) - 1) == 0) {
if (tmp[0]) {
if (strchr(tmp, ':')) {
// ipv6
snprintf(str, sizeof(str) - 1, "[%s]:%u", tmp, addr->port);
} else {
// ipv4
snprintf(str, sizeof(str) - 1, "%s:%u", tmp, addr->port);
}
return str;
}
}
return NULL;
}
static inline const char *netFormatPeerAddr(const ENetPeer *peer)
{
return netFormatAddr(&peer->address);
}
const char *netFormatClientAddr(const struct netclient *cl)
{
return cl->peer ? netFormatPeerAddr(cl->peer) : "<local>";
}
static inline void netClientReset(struct netclient *cl)
{
if (cl->state >= CLSTATE_GAME && cl->player) {
cl->player->client = NULL;
cl->player->isremote = false;
}
memset(cl, 0, sizeof(*cl));
cl->out.data = cl->out_data;
cl->out.size = sizeof(cl->out_data);
cl->id = cl - g_NetClients;
cl->settings.team = 0xff;
}
static inline void netClientResetAll(void)
{
g_NetMaxClients = NET_MAX_CLIENTS;
g_NetNumClients = 1; // always at least one client, which is us
for (u32 i = 0; i < NET_MAX_CLIENTS + 1; ++i) {
netClientReset(&g_NetClients[i]);
}
}
static inline void netClientRecordMove(struct netclient *cl, const struct player *pl)
{
// make space in the move stack
memmove(cl->outmove + 1, cl->outmove, sizeof(cl->outmove) - sizeof(*cl->outmove));
struct netplayermove *move = &cl->outmove[0];
move->tick = g_NetTick;
move->crouchofs = pl->crouchoffset;
move->leanofs = pl->swaytarget / 75.f;
move->movespeed[0] = pl->speedforwards;
move->movespeed[1] = pl->speedsideways;
move->angles[0] = pl->vv_theta;
move->angles[1] = pl->vv_verta;
move->pos = (pl->prop) ? pl->prop->pos : pl->cam_pos;
move->crosspos[0] = pl->crosspos[0];
move->crosspos[1] = pl->crosspos[1];
if (!pl->isremote) {
// normalize crosspos x
move->crosspos[0] -= (f32)(SCREEN_WIDTH_LO / 2);
move->crosspos[0] = (f32)(SCREEN_WIDTH_LO / 2) + move->crosspos[0] * pl->aspect / SCREEN_ASPECT;
}
move->ucmd = pl->ucmd;
if (g_NetMode == NETMODE_SERVER && pl->isremote && cl->inmove[0].tick) {
// carry some of the client inputs over to the outmove
move->ucmd |= (cl->inmove[0].ucmd & (UCMD_FIRE | UCMD_RELOAD | UCMD_AIMMODE | UCMD_EYESSHUT | UCMD_SELECT | UCMD_SELECT_DUAL));
move->crosspos[0] = cl->inmove[0].crosspos[0];
move->crosspos[1] = cl->inmove[0].crosspos[1];
}
if (pl->crouchpos == CROUCHPOS_DUCK) {
move->ucmd |= UCMD_DUCK;
} else if (pl->crouchpos == CROUCHPOS_SQUAT) {
move->ucmd |= UCMD_SQUAT;
}
if (pl->gunctrl.switchtoweaponnum >= 0) {
move->ucmd |= UCMD_SELECT;
move->weaponnum = pl->gunctrl.switchtoweaponnum;
} else {
move->weaponnum = -1;
}
if (pl->gunctrl.dualwielding) {
move->ucmd |= UCMD_SELECT_DUAL;
if ((move->ucmd ^ cl->outmove[1].ucmd) & UCMD_SELECT_DUAL) {
move->ucmd |= UCMD_SELECT;
}
}
const s32 oldnum = g_Vars.currentplayernum;
setCurrentPlayerNum(cl->playernum);
if (bgunIsUsingSecondaryFunction()) {
move->ucmd |= UCMD_SECONDARY;
}
if (pl->insightaimmode) {
move->ucmd |= UCMD_AIMMODE;
move->zoomfov = currentPlayerGetGunZoomFov();
} else {
move->zoomfov = 0.f;
}
setCurrentPlayerNum(oldnum);
if (cl != g_NetLocalClient && !cl->forcetick && (move->ucmd & UCMD_FL_FORCEMASK)) {
cl->forcetick = move->tick;
sysLogPrintf(LOG_NOTE, "NET: forcing client %u to move at tick %u", cl->id, cl->forcetick);
}
}
static inline s32 netClientNeedReliableMove(const struct netclient *cl)
{
const struct netplayermove *move = &cl->outmove[0];
const struct netplayermove *moveprev = &cl->outmove[1];
return !moveprev->tick || (g_NetMode == NETMODE_SERVER && cl->forcetick) ||
(moveprev->ucmd & UCMD_IMPORTANT_MASK) != (move->ucmd & UCMD_IMPORTANT_MASK);
}
static inline s32 netClientNeedMove(const struct netclient *cl)
{
if (g_NetTick < g_NetNextUpdate) {
return false;
}
const struct netplayermove *move = &cl->outmove[0];
const struct netplayermove *moveprev = &cl->outmove[1];
if (move->tick && cl->outmoveack >= move->tick) {
return false;
}
const u8 *cmpa = (const u8 *)move + sizeof(move->tick);
const u8 *cmpb = (const u8 *)moveprev + sizeof(move->tick);
return (memcmp(cmpa, cmpb, sizeof(*move) - sizeof(move->tick)) != 0);
}
static inline void netClientReadConfig(struct netclient *cl, const s32 playernum)
{
cl->settings.options = g_PlayerConfigsArray[playernum].options;
cl->settings.bodynum = g_PlayerConfigsArray[playernum].base.mpbodynum;
cl->settings.headnum = g_PlayerConfigsArray[playernum].base.mpheadnum;
cl->settings.fovy = g_PlayerExtCfg[playernum].fovy;
cl->settings.fovzoommult = g_PlayerExtCfg[playernum].fovzoommult;
memcpy(cl->settings.name, g_PlayerConfigsArray[playernum].base.name, sizeof(cl->settings.name));
// the \n will be readded in the playerconfig
char *newline = strrchr(g_NetLocalClient->settings.name, '\n');
if (newline) {
*newline = '\0';
}
}
static inline void netFlushSendBuffers(void)
{
if (g_NetMsgRel.wp) {
if (g_NetMsgRel.error) {
sysLogPrintf(LOG_WARNING, "NET: reliable out buffer overflow");
}
g_NetReliableFrameLen += g_NetMsgRel.wp;
netSend(NULL, &g_NetMsgRel, true, NETCHAN_DEFAULT);
}
if (g_NetMsg.wp) {
if (g_NetMsg.error) {
sysLogPrintf(LOG_WARNING, "NET: unreliable out buffer overflow");
}
g_NetUnreliableFrameLen += g_NetMsg.wp;
netSend(NULL, &g_NetMsg, false, NETCHAN_DEFAULT);
}
}
static inline const char *netGetDisconnectReason(const u32 reason)
{
static const char *msgs[] = {
"Unknown",
"Server is shutting down",
"Protocol or version mismatch",
"Kicked by console",
"You are banned on this server",
"Connection timed out",
"Server is full",
"The game is already in progress",
"Your files differ from the server's"
};
if (reason < (u32)ARRAYCOUNT(msgs)) {
return msgs[reason];
}
return msgs[0];
}
void netInit(void)
{
if (enet_initialize() < 0) {
sysLogPrintf(LOG_ERROR, "NET: could not init ENet, disabling networking");
return;
}
g_NetInit = true;
}
s32 netStartServer(u16 port, s32 maxclients)
{
if (g_NetMode || !g_NetInit) {
return -1;
}
memset(&g_NetLocalAddr, 0, sizeof(g_NetLocalAddr));
g_NetLocalAddr.port = port;
g_NetHost = enet_host_create(&g_NetLocalAddr, maxclients, NETCHAN_COUNT, g_NetServerInRate, g_NetServerOutRate, 0);
if (!g_NetHost) {
sysLogPrintf(LOG_ERROR, "NET: could not create ENet host");
return -2;
}
netClientResetAll();
g_NetMaxClients = maxclients;
// the server's local client is client 0
g_NetLocalClient = &g_NetClients[0];
g_NetLocalClient->state = CLSTATE_LOBBY; // local client doesn't need auth
netClientReadConfig(g_NetLocalClient, 0);
g_NetMode = NETMODE_SERVER;
g_NetTick = 0;
g_NetNextUpdate = 0;
g_NetNextSyncId = 1;
sysLogPrintf(LOG_NOTE, "NET: using protocol version %d", NET_PROTOCOL_VER);
sysLogPrintf(LOG_NOTE, "NET: created server on port %u", port);
return 0;
}
void netServerStageStart(void)
{
if (g_NetMode != NETMODE_SERVER) {
return;
}
if (g_StageNum == STAGE_TITLE || g_StageNum == STAGE_CITRAINING) {
g_NetLocalClient->state = CLSTATE_LOBBY;
return;
}
// re-read the player config in case it changed
netClientReadConfig(g_NetLocalClient, 0);
g_NetLocalClient->state = CLSTATE_GAME;
netbufStartWrite(&g_NetMsgRel);
netmsgSvcStageStartWrite(&g_NetMsgRel);
netSend(NULL, &g_NetMsgRel, true, NETCHAN_DEFAULT);
}
void netServerStageEnd(void)
{
if (g_NetMode != NETMODE_SERVER) {
return;
}
g_NetLocalClient->state = CLSTATE_LOBBY;
netbufStartWrite(&g_NetMsgRel);
netmsgSvcStageEndWrite(&g_NetMsgRel);
netSend(NULL, &g_NetMsgRel, true, NETCHAN_DEFAULT);
}
void netServerKick(struct netclient *cl, const u32 reason)
{
if (g_NetMode != NETMODE_SERVER) {
return;
}
if (!cl || !cl->state || !cl->peer) {
return;
}
enet_peer_disconnect(cl->peer, reason);
}
s32 netStartClient(const char *addr)
{
if (g_NetMode || !g_NetInit) {
return -1;
}
if (!netParseAddr(&g_NetRemoteAddr, addr)) {
sysLogPrintf(LOG_ERROR, "NET: `%s` is not a valid address", addr);
return -2;
}
memset(&g_NetLocalAddr, 0, sizeof(g_NetLocalAddr));
g_NetHost = enet_host_create(&g_NetLocalAddr, 1, NETCHAN_COUNT, g_NetClientInRate, g_NetClientOutRate, 0);
if (!g_NetHost) {
sysLogPrintf(LOG_ERROR, "NET: could not create ENet host");
return -3;
}
// save the address since it appears to be valid
strncpy(g_NetLastJoinAddr, addr, NET_MAX_ADDR);
// we'll use the whole array to store what we know of other clients
netClientResetAll();
// for now use last client struct
g_NetLocalClient = &g_NetClients[NET_MAX_CLIENTS];
sysLogPrintf(LOG_NOTE, "NET: using protocol version %d", NET_PROTOCOL_VER);
sysLogPrintf(LOG_NOTE, "NET: connecting to %s...", addr);
g_NetLocalClient->peer = enet_host_connect(g_NetHost, &g_NetRemoteAddr, NETCHAN_COUNT, NET_PROTOCOL_VER);
if (!g_NetLocalClient->peer) {
sysLogPrintf(LOG_WARNING, "NET: could not connect to %s", addr);
enet_host_destroy(g_NetHost);
g_NetHost = NULL;
return -4;
}
g_NetLocalClient->state = CLSTATE_CONNECTING;
netClientReadConfig(g_NetLocalClient, 0);
g_NetMode = NETMODE_CLIENT;
g_NetTick = 0;
g_NetNextUpdate = 0;
g_NetNextSyncId = 1;
sysLogPrintf(LOG_NOTE, "NET: waiting for response from %s...", addr);
return 0;
}
s32 netDisconnect(void)
{
if (!g_NetMode) {
return -1;
}
const bool wasingame = (g_NetLocalClient->state >= CLSTATE_GAME);
for (s32 i = 0; i < NET_MAX_CLIENTS + 1; ++i) {
if (g_NetClients[i].peer) {
enet_peer_disconnect_now(g_NetClients[i].peer, DISCONNECT_SHUTDOWN);
}
netClientReset(&g_NetClients[i]);
}
g_NetLocalClient = &g_NetClients[NET_MAX_CLIENTS];
// flush pending packets
enet_host_flush(g_NetHost);
// service for a bit just to ensure disconnect gets to peer(s)
enet_host_service(g_NetHost, NULL, 10);
enet_host_destroy(g_NetHost);
g_NetHost = NULL;
g_NetMode = NETMODE_NONE;
sysLogPrintf(LOG_CHAT, "NET: disconnected");
if (wasingame) {
mainEndStage();
g_MpSetup.chrslots = 1;
}
return 0;
}
static void netServerEvConnect(ENetPeer *peer, const u32 data)
{
const char *addrstr = netFormatPeerAddr(peer);
sysLogPrintf(LOG_NOTE | LOGFLAG_NOCON, "NET: connection attempt from %s", addrstr);
++g_NetNumClients;
if (data != NET_PROTOCOL_VER) {
sysLogPrintf(LOG_NOTE | LOGFLAG_NOCON, "NET: %s rejected: protocol mismatch", addrstr);
enet_peer_disconnect(peer, DISCONNECT_VERSION);
return;
}
if (g_NetLocalClient && g_NetLocalClient->state > CLSTATE_LOBBY) {
sysLogPrintf(LOG_NOTE | LOGFLAG_NOCON, "NET: %s rejected: late joins not allowed", addrstr);
enet_peer_disconnect(peer, DISCONNECT_LATE);
return;
}
struct netclient *cl = NULL;
// id 0 is the local client
for (s32 i = 1; i < g_NetMaxClients; ++i) {
if (!g_NetClients[i].state) {
cl = &g_NetClients[i];
break;
}
}
if (!cl) {
sysLogPrintf(LOG_NOTE | LOGFLAG_NOCON, "NET: %s rejected: server is full", addrstr);
enet_peer_disconnect(peer, DISCONNECT_FULL);
return;
}
netClientReset(cl);
cl->state = CLSTATE_AUTH; // skip CLSTATE_CONNECTING, since we already know it connected
cl->peer = peer;
enet_peer_set_data(peer, cl);
}
static void netServerEvDisconnect(struct netclient *cl)
{
sysLogPrintf(LOG_NOTE | LOGFLAG_NOCON, "NET: disconnect event from %s", netFormatClientAddr(cl));
if (cl->peer) {
enet_peer_reset(cl->peer);
}
if (cl->settings.name[0]) {
sysLogPrintf(LOG_NOTE, "NET: client %u (%s) disconnected", cl->id, cl->settings.name);
netChatPrintf(NULL, "%s disconnected", cl->settings.name);
} else {
sysLogPrintf(LOG_CHAT, "NET: client %u disconnected", cl->id);
}
netClientReset(cl);
--g_NetNumClients;
}
static void netServerEvReceive(struct netclient *cl)
{
u32 rc = 0;
u8 msgid = 0;
while (!rc && netbufReadLeft(&cl->in) > 0) {
msgid = netbufReadU8(&cl->in);
switch (msgid) {
case CLC_NOP: rc = 0; break;
case CLC_AUTH: rc = netmsgClcAuthRead(&cl->in, cl); break;
case CLC_CHAT: rc = netmsgClcChatRead(&cl->in, cl); break;
case CLC_MOVE: rc = netmsgClcMoveRead(&cl->in, cl); break;
case CLC_SETTINGS: rc = netmsgClcSettingsRead(&cl->in, cl); break;
default:
rc = 1;
break;
}
}
if (rc) {
sysLogPrintf(LOG_WARNING , "NET: malformed or unknown message 0x%02x from client %u", msgid, cl->id);
}
}
static void netClientEvConnect(const u32 data)
{
sysLogPrintf(LOG_NOTE, "NET: connected to server, sending CLC_AUTH");
g_NetLocalClient->state = CLSTATE_AUTH;
// send auth request
netbufStartWrite(&g_NetMsgRel);
netmsgClcAuthWrite(&g_NetMsgRel);
netmsgClcSettingsWrite(&g_NetMsgRel);
netSend(g_NetLocalClient, &g_NetMsgRel, true, NETCHAN_CONTROL);
}
static void netClientEvDisconnect(const u32 reason)
{
sysLogPrintf(LOG_CHAT, "NET: disconnected from server: %s (%u)", netGetDisconnectReason(reason), reason);
netDisconnect();
}
static void netClientEvReceive(struct netclient *cl)
{
u32 rc = 0;
u8 msgid = 0;
while (!rc && netbufReadLeft(&cl->in) > 0) {
msgid = netbufReadU8(&cl->in);
switch (msgid) {
case SVC_NOP: rc = 0; break;
case SVC_AUTH: rc = netmsgSvcAuthRead(&cl->in, cl); break;
case SVC_CHAT: rc = netmsgSvcChatRead(&cl->in, cl); break;
case SVC_STAGE_START: rc = netmsgSvcStageStartRead(&cl->in, cl); break;
case SVC_STAGE_END: rc = netmsgSvcStageEndRead(&cl->in, cl); break;
case SVC_PLAYER_MOVE: rc = netmsgSvcPlayerMoveRead(&cl->in, cl); break;
case SVC_PLAYER_STATS: rc = netmsgSvcPlayerStatsRead(&cl->in, cl); break;
case SVC_PROP_MOVE: rc = netmsgSvcPropMoveRead(&cl->in, cl); break;
case SVC_PROP_SPAWN: rc = netmsgSvcPropSpawnRead(&cl->in, cl); break;
case SVC_PROP_DAMAGE: rc = netmsgSvcPropDamageRead(&cl->in, cl); break;
case SVC_PROP_PICKUP: rc = netmsgSvcPropPickupRead(&cl->in, cl); break;
case SVC_PROP_USE: rc = netmsgSvcPropUseRead(&cl->in, cl); break;
case SVC_PROP_DOOR: rc = netmsgSvcPropDoorRead(&cl->in, cl); break;
case SVC_PROP_LIFT: rc = netmsgSvcPropLiftRead(&cl->in, cl); break;
case SVC_CHR_DAMAGE: rc = netmsgSvcChrDamageRead(&cl->in, cl); break;
case SVC_CHR_DISARM: rc = netmsgSvcChrDisarmRead(&cl->in, cl); break;
default:
rc = 1;
break;
}
}
if (rc) {
sysLogPrintf(LOG_WARNING, "NET: malformed or unknown message 0x%02x from server", msgid);
}
}
void netClientSyncRng(void)
{
if (g_NetMode == NETMODE_CLIENT && g_NetRngLatch) {
g_NetRngLatch = 0;
g_RngSeed = g_NetRngSeeds[0];
g_Rng2Seed = g_NetRngSeeds[1];
}
}
void netClientSettingsChanged(void)
{
if (g_NetMode != NETMODE_CLIENT || !g_NetLocalClient) {
return;
}
netClientReadConfig(g_NetLocalClient, 0);
netbufStartWrite(&g_NetMsgRel);
netmsgClcSettingsWrite(&g_NetMsgRel);
netSend(NULL, &g_NetMsgRel, true, NETCHAN_CONTROL);
}
void netStartFrame(void)
{
if (!g_NetMode) {
return;
}
++g_NetTick;
const bool isClient = (g_NetMode == NETMODE_CLIENT);
s32 polled = false;
ENetEvent ev = { .type = ENET_EVENT_TYPE_NONE };
while (!polled) {
if (enet_host_check_events(g_NetHost, &ev) <= 0) {
if (enet_host_service(g_NetHost, &ev, 1) <= 0) {
break;
}
polled = true;
}
switch (ev.type) {
case ENET_EVENT_TYPE_CONNECT:
if (isClient) {
netClientEvConnect(ev.data);
} else if (ev.peer) {
netServerEvConnect(ev.peer, ev.data);
}
break;
case ENET_EVENT_TYPE_DISCONNECT:
case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT:
if (isClient) {
netClientEvDisconnect(ev.type == ENET_EVENT_TYPE_DISCONNECT_TIMEOUT ? DISCONNECT_TIMEOUT : ev.data);
} else if (ev.peer) {
struct netclient *cl = enet_peer_get_data(ev.peer);
if (cl) {
netServerEvDisconnect(cl);
} else {
sysLogPrintf(LOG_WARNING, "NET: disconnect from %s without attached client", netFormatPeerAddr(ev.peer));
--g_NetNumClients;
}
}
break;
case ENET_EVENT_TYPE_RECEIVE:
if (ev.peer) {
struct netclient *cl = (g_NetMode == NETMODE_CLIENT) ? g_NetLocalClient : enet_peer_get_data(ev.peer);
if (cl && cl->state) {
if (ev.packet->data && ev.packet->dataLength) {
netbufStartReadData(&cl->in, ev.packet->data, ev.packet->dataLength);
if (isClient) {
netClientEvReceive(cl);
} else {
netServerEvReceive(cl);
}
netbufReset(&cl->in);
}
} else if (!isClient) {
sysLogPrintf(LOG_WARNING, "NET: receive from %s without attached client", netFormatPeerAddr(ev.peer));
}
}
enet_packet_dispose(ev.packet);
break;
default:
break;
}
}
netbufStartWrite(&g_NetMsg);
netbufStartWrite(&g_NetMsgRel);
}
void netEndFrame(void)
{
if (!g_NetMode) {
return;
}
g_NetReliableFrameLen = 0;
g_NetUnreliableFrameLen = 0;
// send whatever messages have accumulated so far
netFlushSendBuffers();
if (g_NetLocalClient->state == CLSTATE_GAME && g_NetLocalClient->player && g_NetLocalClient->player->prop) {
if (g_NetMode == NETMODE_CLIENT) {
if (g_NetTick > 100) {
netClientRecordMove(g_NetLocalClient, g_NetLocalClient->player);
const bool needrel = netClientNeedReliableMove(g_NetLocalClient);
if (needrel || netClientNeedMove(g_NetLocalClient)) {
netmsgClcMoveWrite(needrel ? &g_NetMsgRel : &g_NetMsg);
}
}
if (g_NetNextUpdate <= g_NetTick) {
g_NetNextUpdate = g_NetTick + g_NetClientUpdateRate;
}
} else {
for (s32 i = 0; i < g_NetMaxClients; ++i) {
struct netclient *cl = &g_NetClients[i];
if (cl->state >= CLSTATE_GAME && cl->player) {
netClientRecordMove(cl, cl->player);
const bool needrel = netClientNeedReliableMove(cl);
if (needrel || netClientNeedMove(cl)) {
netmsgSvcPlayerMoveWrite(needrel ? &g_NetMsgRel : &g_NetMsg, cl);
}
}
}
if (g_NetNextUpdate <= g_NetTick) {
g_NetNextUpdate = g_NetTick + g_NetServerUpdateRate;
}
}
}
// send position updates
netFlushSendBuffers();
enet_host_flush(g_NetHost);
}
u32 netSend(struct netclient *dstcl, struct netbuf *buf, const s32 reliable, const s32 chan)
{
if (g_NetMode == NETMODE_CLIENT) {
dstcl = g_NetLocalClient;
}
if (buf == NULL) {
if (dstcl) {
buf = &dstcl->out;
} else {
buf = reliable ? &g_NetMsgRel : &g_NetMsg;
}
}
if (reliable || !g_NetSimPacketLoss || (rand() % g_NetSimPacketLoss) == 0) {
const u32 flags = (reliable ? ENET_PACKET_FLAG_RELIABLE : 0);
ENetPacket *p = enet_packet_create(buf->data, buf->wp, flags);
if (!p) {
sysLogPrintf(LOG_ERROR, "NET: could not alloc %u bytes for packet", buf->wp);
return 0;
}
if (dstcl == NULL) {
enet_host_broadcast(g_NetHost, chan, p);
} else {
enet_peer_send(dstcl->peer, chan, p);
}
}
const u32 ret = buf->wp;
netbufStartWrite(buf);
return ret;
}
void netPlayersAllocate(void)
{
for (s32 i = 0; i < g_NetMaxClients; ++i) {
struct netclient *cl = &g_NetClients[i];
if (cl->state < CLSTATE_LOBBY) {
continue;
}
if (cl == g_NetLocalClient) {
// we always put the local player at index 0, even client-side
cl->playernum = 0;
} else {
// which means that clientside we have to put the server's player into our slot
cl->playernum = (g_NetMode == NETMODE_CLIENT && i == 0) ? g_NetLocalClient->id : cl->id;
// disable controls for the remote pawns and set their settings
// TODO: backup the player configs or something
struct mpplayerconfig *cfg = &g_PlayerConfigsArray[cl->playernum];
cfg->controlmode = CONTROLMODE_NA;
cfg->base.mpbodynum = cl->settings.bodynum;
cfg->base.mpheadnum = cl->settings.headnum;
snprintf(cfg->base.name, sizeof(cfg->base.name), "%s\n", cl->settings.name);
// take some of the options from our local player and others from the client
cfg->options = g_PlayerConfigsArray[0].options & OPTION_PAINTBALL;
cfg->options |= cl->settings.options & ~OPTION_PAINTBALL;
// don't enable toggle aim, invert pitch or lookahead for remote players
cfg->options &= ~(OPTION_AIMCONTROL | OPTION_LOOKAHEAD | OPTION_ASKEDSAVEPLAYER);
cfg->options |= OPTION_FORWARDPITCH;
}
cl->config = &g_PlayerConfigsArray[cl->playernum];
cl->config->client = cl;
cl->config->handicap = 0x80;
cl->player = g_Vars.players[cl->playernum];
if (cl->player) {
cl->player->client = cl;
cl->player->isremote = (cl != g_NetLocalClient);
}
}
}
void netSyncIdsAllocate(void)
{
// allocate sync ids sequentially for all active or paused props
g_NetNextSyncId = 1;
// don't allocate anything else if we're in lobby
if (g_StageNum == STAGE_TITLE || g_StageNum == STAGE_CITRAINING) {
return;
}
// iterate active props first
struct prop *prop = g_Vars.activeprops;
while (prop && prop != g_Vars.pausedprops) {
prop->syncid = prop - g_Vars.props + 1;
if (prop->syncid > g_NetNextSyncId) {
g_NetNextSyncId = prop->syncid;
}
prop = prop->next;
}
// then the paused props
prop = g_Vars.pausedprops;
while (prop) {
prop->syncid = prop - g_Vars.props + 1;
if (prop->syncid > g_NetNextSyncId) {
g_NetNextSyncId = prop->syncid;
}
prop = prop->next;
}
// HACK: when we're a client, we'll need to swap our player and server player's props
// because of what we do in netPlayersAllocate
if (g_NetMode == NETMODE_CLIENT) {
const u16 sid = g_Vars.players[g_NetLocalClient->id]->prop->syncid;
g_Vars.players[g_NetLocalClient->id]->prop->syncid = g_Vars.players[0]->prop->syncid;
g_Vars.players[0]->prop->syncid = sid;
}
sysLogPrintf(LOG_NOTE, "NET: last initial syncid: %u", g_NetNextSyncId);
}
void netChatPrintf(struct netclient *dst, const char *fmt, ...)
{
char tmp[512];
u8 bufdata[600];
struct netbuf buf = { NULL };
if (!g_NetMode || !g_NetLocalClient || g_NetLocalClient->state < CLSTATE_LOBBY) {
return;
}
va_list args;
va_start(args, fmt);
vsnprintf(tmp, sizeof(tmp) - 1, fmt, args);
va_end(args);
buf.data = bufdata;
buf.size = sizeof(bufdata);
if (g_NetMode == NETMODE_SERVER) {
sysLogPrintf(LOG_CHAT, "%s", tmp);
netmsgSvcChatWrite(&buf, tmp);
} else {
netmsgClcChatWrite(&buf, tmp);
}
netSend(dst, &buf, true, NETCHAN_CONTROL);
}
void netChat(struct netclient *dst, const char *text)
{
if (g_NetMode && g_NetLocalClient) {
netChatPrintf(dst, "%s: %s", g_NetLocalClient->settings.name, text);
}
}
Gfx *netDebugRender(Gfx *gdl)
{
char tmp[384];
if (!g_NetMode || !g_NetDebugDraw) {
return gdl;
}
if (!g_CharsHandelGothicXs || !g_FontHandelGothicXs) {
return gdl;
}
gdl = text0f153628(gdl);
gSPSetExtraGeometryModeEXT(gdl++, G_ASPECT_LEFT_EXT);
s32 x = 2;
s32 y = viGetHeight() - 1 - 6*8;
snprintf(tmp, sizeof(tmp), "Nettick: %u\nPing: %u\nSent: %u\nRecv: %u\nReliable frame: %u\nUnreliable frame: %u\n",
g_NetTick, g_NetLocalClient->peer ? enet_peer_get_rtt(g_NetLocalClient->peer) : 0,
enet_host_get_bytes_sent(g_NetHost), enet_host_get_bytes_received(g_NetHost),
g_NetReliableFrameLen, g_NetUnreliableFrameLen);
gdl = textRenderProjected(gdl, &x, &y, tmp, g_CharsHandelGothicXs, g_FontHandelGothicXs, 0x00ff00ff, viGetWidth(), viGetHeight(), 0, 0);
gSPClearExtraGeometryModeEXT(gdl++, G_ASPECT_CENTER_EXT);
gdl = text0f153780(gdl);
return gdl;
}
PD_CONSTRUCTOR static void netConfigInit(void)
{
configRegisterUInt("Net.LerpTicks", &g_NetInterpTicks, 0, 600);
configRegisterString("Net.Client.LastJoinAddr", g_NetLastJoinAddr, NET_MAX_ADDR);
configRegisterUInt("Net.Client.InRate", &g_NetClientInRate, 0, 10 * 1024 * 1024);
configRegisterUInt("Net.Client.OutRate", &g_NetClientOutRate, 0, 10 * 1024 * 1024);
configRegisterUInt("Net.Client.UpdateFrames", &g_NetClientUpdateRate, 0, 60);
configRegisterUInt("Net.Server.Port", &g_NetServerPort, 0, 0xFFFF);
configRegisterUInt("Net.Server.InRate", &g_NetServerInRate, 0, 10 * 1024 * 1024);
configRegisterUInt("Net.Server.OutRate", &g_NetServerOutRate, 0, 10 * 1024 * 1024);
configRegisterUInt("Net.Server.UpdateFrames", &g_NetServerUpdateRate, 0, 60);
}