Move tic and net command handling to new files

This commit is contained in:
LJ Sonic 2023-01-05 22:51:17 +01:00
parent fc41dd78f4
commit 0ade3ae0bc
21 changed files with 955 additions and 877 deletions

View file

@ -20,6 +20,7 @@
#include "../i_system.h"
#include "../g_game.h"
#include "../netcode/d_netfil.h"
#include "../netcode/net_command.h"
#include "../lua_libs.h"
#include "../byteptr.h"
#include "../lua_script.h"

View file

@ -29,6 +29,7 @@
#include "p_saveg.h"
#include "g_game.h" // for player_names
#include "netcode/d_netcmd.h"
#include "netcode/net_command.h"
#include "hu_stuff.h"
#include "p_setup.h"
#include "lua_script.h"

View file

@ -16,6 +16,7 @@
#include "d_main.h"
#include "d_player.h"
#include "netcode/d_clisrv.h"
#include "netcode/net_command.h"
#include "f_finale.h"
#include "p_setup.h"
#include "p_saveg.h"

View file

@ -20,6 +20,7 @@
#include "m_misc.h" // word jumping
#include "netcode/d_clisrv.h"
#include "netcode/net_command.h"
#include "g_game.h"
#include "g_input.h"

View file

@ -16,6 +16,7 @@
#include "g_game.h"
#include "byteptr.h"
#include "z_zone.h"
#include "netcode/net_command.h"
#include "lua_script.h"
#include "lua_libs.h"

View file

@ -1,6 +1,8 @@
d_clisrv.c
server_connection.c
client_connection.c
tic_command.c
net_command.c
d_net.c
d_netcmd.c
d_netfil.c

View file

@ -51,6 +51,8 @@
#include "../m_perfstats.h"
#include "server_connection.h"
#include "client_connection.h"
#include "tic_command.h"
#include "net_command.h"
//
// NETWORKING
@ -86,11 +88,9 @@ UINT32 realpingtable[MAXPLAYERS]; //the base table of ping where an average will
UINT32 playerpingtable[MAXPLAYERS]; //table of player latency values.
tic_t servermaxping = 800; // server's max ping. Defaults to 800
static tic_t firstticstosend; // min of the nettics
static tic_t tictoclear = 0; // optimize d_clearticcmd
tic_t maketic;
static INT16 consistancy[BACKUPTICS];
INT16 consistancy[BACKUPTICS];
UINT8 hu_redownloadinggamestate = 0;
@ -101,14 +101,9 @@ UINT8 adminpassmd5[16];
boolean adminpasswordset = false;
// Client specific
static ticcmd_t localcmds;
static ticcmd_t localcmds2;
static boolean cl_packetmissed;
// here it is for the secondary local player (splitscreen)
static boolean cl_redownloadinggamestate = false;
static UINT8 localtextcmd[MAXTEXTCMD];
static UINT8 localtextcmd2[MAXTEXTCMD]; // splitscreen
tic_t neededtic;
SINT8 servernode = 0; // the number of the server node
@ -116,385 +111,19 @@ SINT8 servernode = 0; // the number of the server node
/// \todo WORK!
boolean acceptnewnode = true;
// engine
// Must be a power of two
#define TEXTCMD_HASH_SIZE 4
typedef struct textcmdplayer_s
{
INT32 playernum;
UINT8 cmd[MAXTEXTCMD];
struct textcmdplayer_s *next;
} textcmdplayer_t;
typedef struct textcmdtic_s
{
tic_t tic;
textcmdplayer_t *playercmds[TEXTCMD_HASH_SIZE];
struct textcmdtic_s *next;
} textcmdtic_t;
ticcmd_t netcmds[BACKUPTICS][MAXPLAYERS];
static textcmdtic_t *textcmds[TEXTCMD_HASH_SIZE] = {NULL};
consvar_t cv_showjoinaddress = CVAR_INIT ("showjoinaddress", "Off", CV_SAVE|CV_NETVAR, CV_OnOff, NULL);
static CV_PossibleValue_t playbackspeed_cons_t[] = {{1, "MIN"}, {10, "MAX"}, {0, NULL}};
consvar_t cv_playbackspeed = CVAR_INIT ("playbackspeed", "1", 0, playbackspeed_cons_t, NULL);
static inline void *G_DcpyTiccmd(void* dest, const ticcmd_t* src, const size_t n)
{
const size_t d = n / sizeof(ticcmd_t);
const size_t r = n % sizeof(ticcmd_t);
UINT8 *ret = dest;
if (r)
M_Memcpy(dest, src, n);
else if (d)
G_MoveTiccmd(dest, src, d);
return ret+n;
}
static inline void *G_ScpyTiccmd(ticcmd_t* dest, void* src, const size_t n)
{
const size_t d = n / sizeof(ticcmd_t);
const size_t r = n % sizeof(ticcmd_t);
UINT8 *ret = src;
if (r)
M_Memcpy(dest, src, n);
else if (d)
G_MoveTiccmd(dest, src, d);
return ret+n;
}
// Some software don't support largest packet
// (original sersetup, not exactely, but the probability of sending a packet
// of 512 bytes is like 0.1)
UINT16 software_MAXPACKETLENGTH;
/** Guesses the full value of a tic from its lowest byte, for a specific node
*
* \param low The lowest byte of the tic value
* \param node The node to deduce the tic for
* \return The full tic value
*
*/
tic_t ExpandTics(INT32 low, INT32 node)
{
INT32 delta;
delta = low - (netnodes[node].tic & UINT8_MAX);
if (delta >= -64 && delta <= 64)
return (netnodes[node].tic & ~UINT8_MAX) + low;
else if (delta > 64)
return (netnodes[node].tic & ~UINT8_MAX) - 256 + low;
else //if (delta < -64)
return (netnodes[node].tic & ~UINT8_MAX) + 256 + low;
}
// -----------------------------------------------------------------
// Some extra data function for handle textcmd buffer
// -----------------------------------------------------------------
static void (*listnetxcmd[MAXNETXCMD])(UINT8 **p, INT32 playernum);
void RegisterNetXCmd(netxcmd_t id, void (*cmd_f)(UINT8 **p, INT32 playernum))
{
#ifdef PARANOIA
if (id >= MAXNETXCMD)
I_Error("Command id %d too big", id);
if (listnetxcmd[id] != 0)
I_Error("Command id %d already used", id);
#endif
listnetxcmd[id] = cmd_f;
}
void SendNetXCmd(netxcmd_t id, const void *param, size_t nparam)
{
if (localtextcmd[0]+2+nparam > MAXTEXTCMD)
{
// for future reference: if (cv_debug) != debug disabled.
CONS_Alert(CONS_ERROR, M_GetText("NetXCmd buffer full, cannot add netcmd %d! (size: %d, needed: %s)\n"), id, localtextcmd[0], sizeu1(nparam));
return;
}
localtextcmd[0]++;
localtextcmd[localtextcmd[0]] = (UINT8)id;
if (param && nparam)
{
M_Memcpy(&localtextcmd[localtextcmd[0]+1], param, nparam);
localtextcmd[0] = (UINT8)(localtextcmd[0] + (UINT8)nparam);
}
}
// splitscreen player
void SendNetXCmd2(netxcmd_t id, const void *param, size_t nparam)
{
if (localtextcmd2[0]+2+nparam > MAXTEXTCMD)
{
I_Error("No more place in the buffer for netcmd %d\n",id);
return;
}
localtextcmd2[0]++;
localtextcmd2[localtextcmd2[0]] = (UINT8)id;
if (param && nparam)
{
M_Memcpy(&localtextcmd2[localtextcmd2[0]+1], param, nparam);
localtextcmd2[0] = (UINT8)(localtextcmd2[0] + (UINT8)nparam);
}
}
UINT8 GetFreeXCmdSize(void)
{
// -1 for the size and another -1 for the ID.
return (UINT8)(localtextcmd[0] - 2);
}
// Frees all textcmd memory for the specified tic
static void D_FreeTextcmd(tic_t tic)
{
textcmdtic_t **tctprev = &textcmds[tic & (TEXTCMD_HASH_SIZE - 1)];
textcmdtic_t *textcmdtic = *tctprev;
while (textcmdtic && textcmdtic->tic != tic)
{
tctprev = &textcmdtic->next;
textcmdtic = textcmdtic->next;
}
if (textcmdtic)
{
INT32 i;
// Remove this tic from the list.
*tctprev = textcmdtic->next;
// Free all players.
for (i = 0; i < TEXTCMD_HASH_SIZE; i++)
{
textcmdplayer_t *textcmdplayer = textcmdtic->playercmds[i];
while (textcmdplayer)
{
textcmdplayer_t *tcpnext = textcmdplayer->next;
Z_Free(textcmdplayer);
textcmdplayer = tcpnext;
}
}
// Free this tic's own memory.
Z_Free(textcmdtic);
}
}
// Gets the buffer for the specified ticcmd, or NULL if there isn't one
static UINT8* D_GetExistingTextcmd(tic_t tic, INT32 playernum)
{
textcmdtic_t *textcmdtic = textcmds[tic & (TEXTCMD_HASH_SIZE - 1)];
while (textcmdtic && textcmdtic->tic != tic) textcmdtic = textcmdtic->next;
// Do we have an entry for the tic? If so, look for player.
if (textcmdtic)
{
textcmdplayer_t *textcmdplayer = textcmdtic->playercmds[playernum & (TEXTCMD_HASH_SIZE - 1)];
while (textcmdplayer && textcmdplayer->playernum != playernum) textcmdplayer = textcmdplayer->next;
if (textcmdplayer) return textcmdplayer->cmd;
}
return NULL;
}
// Gets the buffer for the specified ticcmd, creating one if necessary
static UINT8* D_GetTextcmd(tic_t tic, INT32 playernum)
{
textcmdtic_t *textcmdtic = textcmds[tic & (TEXTCMD_HASH_SIZE - 1)];
textcmdtic_t **tctprev = &textcmds[tic & (TEXTCMD_HASH_SIZE - 1)];
textcmdplayer_t *textcmdplayer, **tcpprev;
// Look for the tic.
while (textcmdtic && textcmdtic->tic != tic)
{
tctprev = &textcmdtic->next;
textcmdtic = textcmdtic->next;
}
// If we don't have an entry for the tic, make it.
if (!textcmdtic)
{
textcmdtic = *tctprev = Z_Calloc(sizeof (textcmdtic_t), PU_STATIC, NULL);
textcmdtic->tic = tic;
}
tcpprev = &textcmdtic->playercmds[playernum & (TEXTCMD_HASH_SIZE - 1)];
textcmdplayer = *tcpprev;
// Look for the player.
while (textcmdplayer && textcmdplayer->playernum != playernum)
{
tcpprev = &textcmdplayer->next;
textcmdplayer = textcmdplayer->next;
}
// If we don't have an entry for the player, make it.
if (!textcmdplayer)
{
textcmdplayer = *tcpprev = Z_Calloc(sizeof (textcmdplayer_t), PU_STATIC, NULL);
textcmdplayer->playernum = playernum;
}
return textcmdplayer->cmd;
}
static void ExtraDataTicker(void)
{
INT32 i;
for (i = 0; i < MAXPLAYERS; i++)
if (playeringame[i] || i == 0)
{
UINT8 *bufferstart = D_GetExistingTextcmd(gametic, i);
if (bufferstart)
{
UINT8 *curpos = bufferstart;
UINT8 *bufferend = &curpos[curpos[0]+1];
curpos++;
while (curpos < bufferend)
{
if (*curpos < MAXNETXCMD && listnetxcmd[*curpos])
{
const UINT8 id = *curpos;
curpos++;
DEBFILE(va("executing x_cmd %s ply %u ", netxcmdnames[id - 1], i));
(listnetxcmd[id])(&curpos, i);
DEBFILE("done\n");
}
else
{
if (server)
{
SendKick(i, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
DEBFILE(va("player %d kicked [gametic=%u] reason as follows:\n", i, gametic));
}
CONS_Alert(CONS_WARNING, M_GetText("Got unknown net command [%s]=%d (max %d)\n"), sizeu1(curpos - bufferstart), *curpos, bufferstart[0]);
break;
}
}
}
}
// If you are a client, you can safely forget the net commands for this tic
// If you are the server, you need to remember them until every client has been acknowledged,
// because if you need to resend a PT_SERVERTICS packet, you will need to put the commands in it
if (client)
D_FreeTextcmd(gametic);
}
static void D_Clearticcmd(tic_t tic)
{
INT32 i;
D_FreeTextcmd(tic);
for (i = 0; i < MAXPLAYERS; i++)
netcmds[tic%BACKUPTICS][i].angleturn = 0;
DEBFILE(va("clear tic %5u (%2u)\n", tic, tic%BACKUPTICS));
}
void D_ResetTiccmds(void)
{
INT32 i;
memset(&localcmds, 0, sizeof(ticcmd_t));
memset(&localcmds2, 0, sizeof(ticcmd_t));
// Reset the net command list
for (i = 0; i < TEXTCMD_HASH_SIZE; i++)
while (textcmds[i])
D_Clearticcmd(textcmds[i]->tic);
}
void SendKick(UINT8 playernum, UINT8 msg)
{
UINT8 buf[2];
if (!(server && cv_rejointimeout.value))
msg &= ~KICK_MSG_KEEP_BODY;
buf[0] = playernum;
buf[1] = msg;
SendNetXCmd(XD_KICK, &buf, 2);
}
// -----------------------------------------------------------------
// end of extra data function
// -----------------------------------------------------------------
// -----------------------------------------------------------------
// extra data function for lmps
// -----------------------------------------------------------------
// if extradatabit is set, after the ziped tic you find this:
//
// type | description
// ---------+--------------
// byte | size of the extradata
// byte | the extradata (xd) bits: see XD_...
// with this byte you know what parameter folow
// if (xd & XDNAMEANDCOLOR)
// byte | color
// char[MAXPLAYERNAME] | name of the player
// endif
// if (xd & XD_WEAPON_PREF)
// byte | original weapon switch: boolean, true if use the old
// | weapon switch methode
// char[NUMWEAPONS] | the weapon switch priority
// byte | autoaim: true if use the old autoaim system
// endif
/*boolean AddLmpExtradata(UINT8 **demo_point, INT32 playernum)
{
UINT8 *textcmd = D_GetExistingTextcmd(gametic, playernum);
if (!textcmd)
return false;
M_Memcpy(*demo_point, textcmd, textcmd[0]+1);
*demo_point += textcmd[0]+1;
return true;
}
void ReadLmpExtraData(UINT8 **demo_pointer, INT32 playernum)
{
UINT8 nextra;
UINT8 *textcmd;
if (!demo_pointer)
return;
textcmd = D_GetTextcmd(gametic, playernum);
nextra = **demo_pointer;
M_Memcpy(textcmd, *demo_pointer, nextra + 1);
// increment demo pointer
*demo_pointer += nextra + 1;
}*/
// -----------------------------------------------------------------
// end extra data function for lmps
// -----------------------------------------------------------------
static INT16 Consistancy(void);
#define SAVEGAMESIZE (768*1024)
static boolean SV_ResendingSavegameToAnyone(void)
boolean SV_ResendingSavegameToAnyone(void)
{
INT32 i;
@ -2005,22 +1634,6 @@ void SV_StartSinglePlayerServer(void)
multiplayer = true;
}
// used at txtcmds received to check packetsize bound
static size_t TotalTextCmdPerTic(tic_t tic)
{
INT32 i;
size_t total = 1; // num of textcmds in the tic (ntextcmd byte)
for (i = 0; i < MAXPLAYERS; i++)
{
UINT8 *textcmd = D_GetExistingTextcmd(tic, i);
if ((!i || playeringame[i]) && textcmd)
total += 2 + textcmd[0]; // "+2" for size and playernum
}
return total;
}
/** Called when a PT_SERVERSHUTDOWN packet is received
*
* \param node The packet sender (should be the server)
@ -2051,168 +1664,6 @@ static void HandleTimeout(SINT8 node)
M_StartMessage(M_GetText("Server Timeout\n\nPress Esc\n"), NULL, MM_NOTHING);
}
static void PT_ClientCmd(SINT8 node, INT32 netconsole)
{
tic_t realend, realstart;
if (client)
return;
// To save bytes, only the low byte of tic numbers are sent
// Use ExpandTics to figure out what the rest of the bytes are
realstart = ExpandTics(netbuffer->u.clientpak.client_tic, node);
realend = ExpandTics(netbuffer->u.clientpak.resendfrom, node);
if (netbuffer->packettype == PT_CLIENTMIS || netbuffer->packettype == PT_CLIENT2MIS
|| netbuffer->packettype == PT_NODEKEEPALIVEMIS
|| netnodes[node].supposedtic < realend)
{
netnodes[node].supposedtic = realend;
}
// Discard out of order packet
if (netnodes[node].tic > realend)
{
DEBFILE(va("out of order ticcmd discarded nettics = %u\n", netnodes[node].tic));
return;
}
// Update the nettics
netnodes[node].tic = realend;
// Don't do anything for packets of type NODEKEEPALIVE?
if (netconsole == -1 || netbuffer->packettype == PT_NODEKEEPALIVE
|| netbuffer->packettype == PT_NODEKEEPALIVEMIS)
return;
// As long as clients send valid ticcmds, the server can keep running, so reset the timeout
/// \todo Use a separate cvar for that kind of timeout?
netnodes[node].freezetimeout = I_GetTime() + connectiontimeout;
// Copy ticcmd
G_MoveTiccmd(&netcmds[maketic%BACKUPTICS][netconsole], &netbuffer->u.clientpak.cmd, 1);
// Check ticcmd for "speed hacks"
if (netcmds[maketic%BACKUPTICS][netconsole].forwardmove > MAXPLMOVE || netcmds[maketic%BACKUPTICS][netconsole].forwardmove < -MAXPLMOVE
|| netcmds[maketic%BACKUPTICS][netconsole].sidemove > MAXPLMOVE || netcmds[maketic%BACKUPTICS][netconsole].sidemove < -MAXPLMOVE)
{
CONS_Alert(CONS_WARNING, M_GetText("Illegal movement value received from node %d\n"), netconsole);
//D_Clearticcmd(k);
SendKick(netconsole, KICK_MSG_CON_FAIL);
return;
}
// Splitscreen cmd
if ((netbuffer->packettype == PT_CLIENT2CMD || netbuffer->packettype == PT_CLIENT2MIS)
&& netnodes[node].player2 >= 0)
G_MoveTiccmd(&netcmds[maketic%BACKUPTICS][(UINT8)netnodes[node].player2],
&netbuffer->u.client2pak.cmd2, 1);
// Check player consistancy during the level
if (realstart <= gametic && realstart + BACKUPTICS - 1 > gametic && gamestate == GS_LEVEL
&& consistancy[realstart%BACKUPTICS] != SHORT(netbuffer->u.clientpak.consistancy)
&& !SV_ResendingSavegameToAnyone()
&& !netnodes[node].resendingsavegame && netnodes[node].savegameresendcooldown <= I_GetTime())
{
if (cv_resynchattempts.value)
{
// Tell the client we are about to resend them the gamestate
netbuffer->packettype = PT_WILLRESENDGAMESTATE;
HSendPacket(node, true, 0, 0);
netnodes[node].resendingsavegame = true;
if (cv_blamecfail.value)
CONS_Printf(M_GetText("Synch failure for player %d (%s); expected %hd, got %hd\n"),
netconsole+1, player_names[netconsole],
consistancy[realstart%BACKUPTICS],
SHORT(netbuffer->u.clientpak.consistancy));
DEBFILE(va("Restoring player %d (synch failure) [%update] %d!=%d\n",
netconsole, realstart, consistancy[realstart%BACKUPTICS],
SHORT(netbuffer->u.clientpak.consistancy)));
return;
}
else
{
SendKick(netconsole, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
DEBFILE(va("player %d kicked (synch failure) [%u] %d!=%d\n",
netconsole, realstart, consistancy[realstart%BACKUPTICS],
SHORT(netbuffer->u.clientpak.consistancy)));
return;
}
}
}
static void PT_TextCmd(SINT8 node, INT32 netconsole)
{
if (client)
return;
// splitscreen special
if (netbuffer->packettype == PT_TEXTCMD2)
netconsole = netnodes[node].player2;
if (netconsole < 0 || netconsole >= MAXPLAYERS)
Net_UnAcknowledgePacket(node);
else
{
size_t j;
tic_t tic = maketic;
UINT8 *textcmd;
// ignore if the textcmd has a reported size of zero
// this shouldn't be sent at all
if (!netbuffer->u.textcmd[0])
{
DEBFILE(va("GetPacket: Textcmd with size 0 detected! (node %u, player %d)\n",
node, netconsole));
Net_UnAcknowledgePacket(node);
return;
}
// ignore if the textcmd size var is actually larger than it should be
// BASEPACKETSIZE + 1 (for size) + textcmd[0] should == datalength
if (netbuffer->u.textcmd[0] > (size_t)doomcom->datalength-BASEPACKETSIZE-1)
{
DEBFILE(va("GetPacket: Bad Textcmd packet size! (expected %d, actual %s, node %u, player %d)\n",
netbuffer->u.textcmd[0], sizeu1((size_t)doomcom->datalength-BASEPACKETSIZE-1),
node, netconsole));
Net_UnAcknowledgePacket(node);
return;
}
// check if tic that we are making isn't too large else we cannot send it :(
// doomcom->numslots+1 "+1" since doomcom->numslots can change within this time and sent time
j = software_MAXPACKETLENGTH
- (netbuffer->u.textcmd[0]+2+BASESERVERTICSSIZE
+ (doomcom->numslots+1)*sizeof(ticcmd_t));
// search a tic that have enougth space in the ticcmd
while ((textcmd = D_GetExistingTextcmd(tic, netconsole)),
(TotalTextCmdPerTic(tic) > j || netbuffer->u.textcmd[0] + (textcmd ? textcmd[0] : 0) > MAXTEXTCMD)
&& tic < firstticstosend + BACKUPTICS)
tic++;
if (tic >= firstticstosend + BACKUPTICS)
{
DEBFILE(va("GetPacket: Textcmd too long (max %s, used %s, mak %d, "
"tosend %u, node %u, player %d)\n", sizeu1(j), sizeu2(TotalTextCmdPerTic(maketic)),
maketic, firstticstosend, node, netconsole));
Net_UnAcknowledgePacket(node);
return;
}
// Make sure we have a buffer
if (!textcmd) textcmd = D_GetTextcmd(tic, netconsole);
DEBFILE(va("textcmd put in tic %u at position %d (player %d) ftts %u mk %u\n",
tic, textcmd[0]+1, netconsole, firstticstosend, maketic));
M_Memcpy(&textcmd[textcmd[0]+1], netbuffer->u.textcmd+1, netbuffer->u.textcmd[0]);
textcmd[0] += (UINT8)netbuffer->u.textcmd[0];
}
}
static void PT_Login(SINT8 node, INT32 netconsole)
{
(void)node;
@ -2314,81 +1765,6 @@ static void PT_ReceivedGamestate(SINT8 node)
netnodes[node].savegameresendcooldown = I_GetTime() + 5 * TICRATE;
}
static void PT_ServerTics(SINT8 node, INT32 netconsole)
{
UINT8 *pak, *txtpak, numtxtpak;
tic_t realend, realstart;
if (!netnodes[node].ingame)
{
// Do not remove my own server (we have just get a out of order packet)
if (node != servernode)
{
DEBFILE(va("unknown packet received (%d) from unknown host\n",netbuffer->packettype));
Net_CloseConnection(node);
}
return;
}
// Only accept PT_SERVERTICS from the server.
if (node != servernode)
{
CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_SERVERTICS", node);
if (server)
SendKick(netconsole, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
return;
}
realstart = netbuffer->u.serverpak.starttic;
realend = realstart + netbuffer->u.serverpak.numtics;
txtpak = (UINT8 *)&netbuffer->u.serverpak.cmds[netbuffer->u.serverpak.numslots
* netbuffer->u.serverpak.numtics];
if (realend > gametic + CLIENTBACKUPTICS)
realend = gametic + CLIENTBACKUPTICS;
cl_packetmissed = realstart > neededtic;
if (realstart <= neededtic && realend > neededtic)
{
tic_t i, j;
pak = (UINT8 *)&netbuffer->u.serverpak.cmds;
for (i = realstart; i < realend; i++)
{
// clear first
D_Clearticcmd(i);
// copy the tics
pak = G_ScpyTiccmd(netcmds[i%BACKUPTICS], pak,
netbuffer->u.serverpak.numslots*sizeof (ticcmd_t));
// copy the textcmds
numtxtpak = *txtpak++;
for (j = 0; j < numtxtpak; j++)
{
INT32 k = *txtpak++; // playernum
const size_t txtsize = txtpak[0]+1;
if (i >= gametic) // Don't copy old net commands
M_Memcpy(D_GetTextcmd(i, k), txtpak, txtsize);
txtpak += txtsize;
}
}
neededtic = realend;
}
else
{
DEBFILE(va("frame not in bound: %u\n", neededtic));
/*if (realend < neededtic - 2 * TICRATE || neededtic + 2 * TICRATE < realstart)
I_Error("Received an out of order PT_SERVERTICS packet!\n"
"Got tics %d-%d, needed tic %d\n\n"
"Please report this crash on the Master Board,\n"
"IRC or Discord so it can be fixed.\n", (INT32)realstart, (INT32)realend, (INT32)neededtic);*/
}
}
static void PT_Ping(SINT8 node, INT32 netconsole)
{
// Only accept PT_PING from the server.
@ -2602,7 +1978,7 @@ void GetPackets(void)
// no more use random generator, because at very first tic isn't yet synchronized
// Note: It is called consistAncy on purpose.
//
static INT16 Consistancy(void)
INT16 Consistancy(void)
{
INT32 i;
UINT32 ret = 0;
@ -2708,239 +2084,11 @@ static INT16 Consistancy(void)
return (INT16)(ret & 0xFFFF);
}
// send the client packet to the server
static void CL_SendClientCmd(void)
{
size_t packetsize = 0;
netbuffer->packettype = PT_CLIENTCMD;
if (cl_packetmissed)
netbuffer->packettype++;
netbuffer->u.clientpak.resendfrom = (UINT8)(neededtic & UINT8_MAX);
netbuffer->u.clientpak.client_tic = (UINT8)(gametic & UINT8_MAX);
if (gamestate == GS_WAITINGPLAYERS)
{
// Send PT_NODEKEEPALIVE packet
netbuffer->packettype += 4;
packetsize = sizeof (clientcmd_pak) - sizeof (ticcmd_t) - sizeof (INT16);
HSendPacket(servernode, false, 0, packetsize);
}
else if (gamestate != GS_NULL && (addedtogame || dedicated))
{
G_MoveTiccmd(&netbuffer->u.clientpak.cmd, &localcmds, 1);
netbuffer->u.clientpak.consistancy = SHORT(consistancy[gametic%BACKUPTICS]);
// Send a special packet with 2 cmd for splitscreen
if (splitscreen || botingame)
{
netbuffer->packettype += 2;
G_MoveTiccmd(&netbuffer->u.client2pak.cmd2, &localcmds2, 1);
packetsize = sizeof (client2cmd_pak);
}
else
packetsize = sizeof (clientcmd_pak);
HSendPacket(servernode, false, 0, packetsize);
}
if (cl_mode == CL_CONNECTED || dedicated)
{
// Send extra data if needed
if (localtextcmd[0])
{
netbuffer->packettype = PT_TEXTCMD;
M_Memcpy(netbuffer->u.textcmd,localtextcmd, localtextcmd[0]+1);
// All extra data have been sent
if (HSendPacket(servernode, true, 0, localtextcmd[0]+1)) // Send can fail...
localtextcmd[0] = 0;
}
// Send extra data if needed for player 2 (splitscreen)
if (localtextcmd2[0])
{
netbuffer->packettype = PT_TEXTCMD2;
M_Memcpy(netbuffer->u.textcmd, localtextcmd2, localtextcmd2[0]+1);
// All extra data have been sent
if (HSendPacket(servernode, true, 0, localtextcmd2[0]+1)) // Send can fail...
localtextcmd2[0] = 0;
}
}
}
// send the server packet
// send tic from firstticstosend to maketic-1
static void SV_SendTics(void)
{
tic_t realfirsttic, lasttictosend, i;
UINT32 n;
INT32 j;
size_t packsize;
UINT8 *bufpos;
UINT8 *ntextcmd;
// send to all client but not to me
// for each node create a packet with x tics and send it
// x is computed using netnodes[n].supposedtic, max packet size and maketic
for (n = 1; n < MAXNETNODES; n++)
if (netnodes[n].ingame)
{
// assert netnodes[n].supposedtic>=netnodes[n].tic
realfirsttic = netnodes[n].supposedtic;
lasttictosend = min(maketic, netnodes[n].tic + CLIENTBACKUPTICS);
if (realfirsttic >= lasttictosend)
{
// well we have sent all tics we will so use extrabandwidth
// to resent packet that are supposed lost (this is necessary since lost
// packet detection work when we have received packet with firsttic > neededtic
// (getpacket servertics case)
DEBFILE(va("Nothing to send node %u mak=%u sup=%u net=%u \n",
n, maketic, netnodes[n].supposedtic, netnodes[n].tic));
realfirsttic = netnodes[n].tic;
if (realfirsttic >= lasttictosend || (I_GetTime() + n)&3)
// all tic are ok
continue;
DEBFILE(va("Sent %d anyway\n", realfirsttic));
}
if (realfirsttic < firstticstosend)
realfirsttic = firstticstosend;
// compute the length of the packet and cut it if too large
packsize = BASESERVERTICSSIZE;
for (i = realfirsttic; i < lasttictosend; i++)
{
packsize += sizeof (ticcmd_t) * doomcom->numslots;
packsize += TotalTextCmdPerTic(i);
if (packsize > software_MAXPACKETLENGTH)
{
DEBFILE(va("packet too large (%s) at tic %d (should be from %d to %d)\n",
sizeu1(packsize), i, realfirsttic, lasttictosend));
lasttictosend = i;
// too bad: too much player have send extradata and there is too
// much data in one tic.
// To avoid it put the data on the next tic. (see getpacket
// textcmd case) but when numplayer changes the computation can be different
if (lasttictosend == realfirsttic)
{
if (packsize > MAXPACKETLENGTH)
I_Error("Too many players: can't send %s data for %d players to node %d\n"
"Well sorry nobody is perfect....\n",
sizeu1(packsize), doomcom->numslots, n);
else
{
lasttictosend++; // send it anyway!
DEBFILE("sending it anyway\n");
}
}
break;
}
}
// Send the tics
netbuffer->packettype = PT_SERVERTICS;
netbuffer->u.serverpak.starttic = realfirsttic;
netbuffer->u.serverpak.numtics = (UINT8)(lasttictosend - realfirsttic);
netbuffer->u.serverpak.numslots = (UINT8)SHORT(doomcom->numslots);
bufpos = (UINT8 *)&netbuffer->u.serverpak.cmds;
for (i = realfirsttic; i < lasttictosend; i++)
{
bufpos = G_DcpyTiccmd(bufpos, netcmds[i%BACKUPTICS], doomcom->numslots * sizeof (ticcmd_t));
}
// add textcmds
for (i = realfirsttic; i < lasttictosend; i++)
{
ntextcmd = bufpos++;
*ntextcmd = 0;
for (j = 0; j < MAXPLAYERS; j++)
{
UINT8 *textcmd = D_GetExistingTextcmd(i, j);
INT32 size = textcmd ? textcmd[0] : 0;
if ((!j || playeringame[j]) && size)
{
(*ntextcmd)++;
WRITEUINT8(bufpos, j);
M_Memcpy(bufpos, textcmd, size + 1);
bufpos += size + 1;
}
}
}
packsize = bufpos - (UINT8 *)&(netbuffer->u);
HSendPacket(n, false, 0, packsize);
// when tic are too large, only one tic is sent so don't go backward!
if (lasttictosend-doomcom->extratics > realfirsttic)
netnodes[n].supposedtic = lasttictosend-doomcom->extratics;
else
netnodes[n].supposedtic = lasttictosend;
if (netnodes[n].supposedtic < netnodes[n].tic) netnodes[n].supposedtic = netnodes[n].tic;
}
// node 0 is me!
netnodes[0].supposedtic = maketic;
}
//
// TryRunTics
//
static void Local_Maketic(INT32 realtics)
{
I_OsPolling(); // I_Getevent
D_ProcessEvents(); // menu responder, cons responder,
// game responder calls HU_Responder, AM_Responder,
// and G_MapEventsToControls
if (!dedicated) rendergametic = gametic;
// translate inputs (keyboard/mouse/gamepad) into game controls
G_BuildTiccmd(&localcmds, realtics, 1);
if (splitscreen || botingame)
G_BuildTiccmd(&localcmds2, realtics, 2);
localcmds.angleturn |= TICCMD_RECEIVED;
localcmds2.angleturn |= TICCMD_RECEIVED;
}
// create missed tic
static void SV_Maketic(void)
{
INT32 i;
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i])
continue;
// We didn't receive this tic
if ((netcmds[maketic % BACKUPTICS][i].angleturn & TICCMD_RECEIVED) == 0)
{
ticcmd_t * ticcmd = &netcmds[(maketic ) % BACKUPTICS][i];
ticcmd_t *prevticcmd = &netcmds[(maketic - 1) % BACKUPTICS][i];
if (players[i].quittime)
{
// Copy the angle/aiming from the previous tic
// and empty the other inputs
memset(ticcmd, 0, sizeof(netcmds[0][0]));
ticcmd->angleturn = prevticcmd->angleturn | TICCMD_RECEIVED;
ticcmd->aiming = prevticcmd->aiming;
}
else
{
DEBFILE(va("MISS tic%4d for player %d\n", maketic, i));
// Copy the input from the previous tic
*ticcmd = *prevticcmd;
ticcmd->angleturn &= ~TICCMD_RECEIVED;
}
}
}
// all tic are now proceed make the next
maketic++;
}
/*
Ping Update except better:
We call this once per second and check for people's pings. If their ping happens to be too high, we increment some timer and kick them out.
If they're not lagging, decrement the timer by 1. Of course, reset all of this if they leave.
*/
boolean TryRunTics(tic_t realtics)
{
@ -3049,12 +2197,6 @@ boolean TryRunTics(tic_t realtics)
return ticking;
}
/*
Ping Update except better:
We call this once per second and check for people's pings. If their ping happens to be too high, we increment some timer and kick them out.
If they're not lagging, decrement the timer by 1. Of course, reset all of this if they leave.
*/
static INT32 pingtimeout[MAXPLAYERS];
static inline void PingUpdate(void)

View file

@ -378,6 +378,7 @@ extern boolean acceptnewnode;
extern SINT8 servernode;
extern tic_t maketic;
extern tic_t neededtic;
extern INT16 consistancy[BACKUPTICS];
void Command_Ping_f(void);
extern tic_t connectiontimeout;
@ -391,20 +392,15 @@ extern consvar_t cv_resynchattempts, cv_blamecfail;
extern consvar_t cv_maxsend, cv_noticedownload, cv_downloadspeed;
// Used in d_net, the only dependence
tic_t ExpandTics(INT32 low, INT32 node);
void D_ClientServerInit(void);
// Initialise the other field
void RegisterNetXCmd(netxcmd_t id, void (*cmd_f)(UINT8 **p, INT32 playernum));
void SendNetXCmd(netxcmd_t id, const void *param, size_t nparam);
void SendNetXCmd2(netxcmd_t id, const void *param, size_t nparam); // splitsreen player
void SendKick(UINT8 playernum, UINT8 msg);
// Create any new ticcmds and broadcast to other players.
void NetUpdate(void);
void GetPackets(void);
void ResetNode(INT32 node);
INT16 Consistancy(void);
boolean SV_ResendingSavegameToAnyone(void);
void SV_StartSinglePlayerServer(void);
void SV_SpawnServer(void);
@ -440,10 +436,8 @@ extern char motd[254], server_context[8];
extern UINT8 playernode[MAXPLAYERS];
INT32 D_NumPlayers(void);
void D_ResetTiccmds(void);
tic_t GetLag(INT32 node);
UINT8 GetFreeXCmdSize(void);
void D_MD5PasswordPass(const UINT8 *buffer, size_t len, const char *salt, void *dest);

View file

@ -26,6 +26,7 @@
#include "../w_wad.h"
#include "d_netfil.h"
#include "d_clisrv.h"
#include "tic_command.h"
#include "../z_zone.h"
#include "i_tcp.h"
#include "../d_main.h" // srb2home

View file

@ -37,6 +37,7 @@
#include "../m_cheat.h"
#include "d_clisrv.h"
#include "server_connection.h"
#include "net_command.h"
#include "d_net.h"
#include "../v_video.h"
#include "../d_main.h"

View file

@ -42,6 +42,7 @@
#include "d_net.h"
#include "../w_wad.h"
#include "d_netfil.h"
#include "net_command.h"
#include "../z_zone.h"
#include "../byteptr.h"
#include "../p_setup.h"

315
src/netcode/net_command.c Normal file
View file

@ -0,0 +1,315 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1998-2000 by DooM Legacy Team.
// Copyright (C) 1999-2022 by Sonic Team Junior.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file net_command.c
/// \brief Net command handling
#include "net_command.h"
#include "tic_command.h"
#include "d_clisrv.h"
#include "i_net.h"
#include "../g_game.h"
#include "../z_zone.h"
#include "../doomtype.h"
textcmdtic_t *textcmds[TEXTCMD_HASH_SIZE] = {NULL};
UINT8 localtextcmd[MAXTEXTCMD];
UINT8 localtextcmd2[MAXTEXTCMD]; // splitscreen
static void (*listnetxcmd[MAXNETXCMD])(UINT8 **p, INT32 playernum);
void RegisterNetXCmd(netxcmd_t id, void (*cmd_f)(UINT8 **p, INT32 playernum))
{
#ifdef PARANOIA
if (id >= MAXNETXCMD)
I_Error("Command id %d too big", id);
if (listnetxcmd[id] != 0)
I_Error("Command id %d already used", id);
#endif
listnetxcmd[id] = cmd_f;
}
void SendNetXCmd(netxcmd_t id, const void *param, size_t nparam)
{
if (localtextcmd[0]+2+nparam > MAXTEXTCMD)
{
// for future reference: if (cv_debug) != debug disabled.
CONS_Alert(CONS_ERROR, M_GetText("NetXCmd buffer full, cannot add netcmd %d! (size: %d, needed: %s)\n"), id, localtextcmd[0], sizeu1(nparam));
return;
}
localtextcmd[0]++;
localtextcmd[localtextcmd[0]] = (UINT8)id;
if (param && nparam)
{
M_Memcpy(&localtextcmd[localtextcmd[0]+1], param, nparam);
localtextcmd[0] = (UINT8)(localtextcmd[0] + (UINT8)nparam);
}
}
// splitscreen player
void SendNetXCmd2(netxcmd_t id, const void *param, size_t nparam)
{
if (localtextcmd2[0]+2+nparam > MAXTEXTCMD)
{
I_Error("No more place in the buffer for netcmd %d\n",id);
return;
}
localtextcmd2[0]++;
localtextcmd2[localtextcmd2[0]] = (UINT8)id;
if (param && nparam)
{
M_Memcpy(&localtextcmd2[localtextcmd2[0]+1], param, nparam);
localtextcmd2[0] = (UINT8)(localtextcmd2[0] + (UINT8)nparam);
}
}
UINT8 GetFreeXCmdSize(void)
{
// -1 for the size and another -1 for the ID.
return (UINT8)(localtextcmd[0] - 2);
}
// Frees all textcmd memory for the specified tic
void D_FreeTextcmd(tic_t tic)
{
textcmdtic_t **tctprev = &textcmds[tic & (TEXTCMD_HASH_SIZE - 1)];
textcmdtic_t *textcmdtic = *tctprev;
while (textcmdtic && textcmdtic->tic != tic)
{
tctprev = &textcmdtic->next;
textcmdtic = textcmdtic->next;
}
if (textcmdtic)
{
INT32 i;
// Remove this tic from the list.
*tctprev = textcmdtic->next;
// Free all players.
for (i = 0; i < TEXTCMD_HASH_SIZE; i++)
{
textcmdplayer_t *textcmdplayer = textcmdtic->playercmds[i];
while (textcmdplayer)
{
textcmdplayer_t *tcpnext = textcmdplayer->next;
Z_Free(textcmdplayer);
textcmdplayer = tcpnext;
}
}
// Free this tic's own memory.
Z_Free(textcmdtic);
}
}
// Gets the buffer for the specified ticcmd, or NULL if there isn't one
UINT8* D_GetExistingTextcmd(tic_t tic, INT32 playernum)
{
textcmdtic_t *textcmdtic = textcmds[tic & (TEXTCMD_HASH_SIZE - 1)];
while (textcmdtic && textcmdtic->tic != tic) textcmdtic = textcmdtic->next;
// Do we have an entry for the tic? If so, look for player.
if (textcmdtic)
{
textcmdplayer_t *textcmdplayer = textcmdtic->playercmds[playernum & (TEXTCMD_HASH_SIZE - 1)];
while (textcmdplayer && textcmdplayer->playernum != playernum) textcmdplayer = textcmdplayer->next;
if (textcmdplayer) return textcmdplayer->cmd;
}
return NULL;
}
// Gets the buffer for the specified ticcmd, creating one if necessary
UINT8* D_GetTextcmd(tic_t tic, INT32 playernum)
{
textcmdtic_t *textcmdtic = textcmds[tic & (TEXTCMD_HASH_SIZE - 1)];
textcmdtic_t **tctprev = &textcmds[tic & (TEXTCMD_HASH_SIZE - 1)];
textcmdplayer_t *textcmdplayer, **tcpprev;
// Look for the tic.
while (textcmdtic && textcmdtic->tic != tic)
{
tctprev = &textcmdtic->next;
textcmdtic = textcmdtic->next;
}
// If we don't have an entry for the tic, make it.
if (!textcmdtic)
{
textcmdtic = *tctprev = Z_Calloc(sizeof (textcmdtic_t), PU_STATIC, NULL);
textcmdtic->tic = tic;
}
tcpprev = &textcmdtic->playercmds[playernum & (TEXTCMD_HASH_SIZE - 1)];
textcmdplayer = *tcpprev;
// Look for the player.
while (textcmdplayer && textcmdplayer->playernum != playernum)
{
tcpprev = &textcmdplayer->next;
textcmdplayer = textcmdplayer->next;
}
// If we don't have an entry for the player, make it.
if (!textcmdplayer)
{
textcmdplayer = *tcpprev = Z_Calloc(sizeof (textcmdplayer_t), PU_STATIC, NULL);
textcmdplayer->playernum = playernum;
}
return textcmdplayer->cmd;
}
void ExtraDataTicker(void)
{
INT32 i;
for (i = 0; i < MAXPLAYERS; i++)
if (playeringame[i] || i == 0)
{
UINT8 *bufferstart = D_GetExistingTextcmd(gametic, i);
if (bufferstart)
{
UINT8 *curpos = bufferstart;
UINT8 *bufferend = &curpos[curpos[0]+1];
curpos++;
while (curpos < bufferend)
{
if (*curpos < MAXNETXCMD && listnetxcmd[*curpos])
{
const UINT8 id = *curpos;
curpos++;
DEBFILE(va("executing x_cmd %s ply %u ", netxcmdnames[id - 1], i));
(listnetxcmd[id])(&curpos, i);
DEBFILE("done\n");
}
else
{
if (server)
{
SendKick(i, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
DEBFILE(va("player %d kicked [gametic=%u] reason as follows:\n", i, gametic));
}
CONS_Alert(CONS_WARNING, M_GetText("Got unknown net command [%s]=%d (max %d)\n"), sizeu1(curpos - bufferstart), *curpos, bufferstart[0]);
break;
}
}
}
}
// If you are a client, you can safely forget the net commands for this tic
// If you are the server, you need to remember them until every client has been acknowledged,
// because if you need to resend a PT_SERVERTICS packet, you will need to put the commands in it
if (client)
D_FreeTextcmd(gametic);
}
// used at txtcmds received to check packetsize bound
size_t TotalTextCmdPerTic(tic_t tic)
{
INT32 i;
size_t total = 1; // num of textcmds in the tic (ntextcmd byte)
for (i = 0; i < MAXPLAYERS; i++)
{
UINT8 *textcmd = D_GetExistingTextcmd(tic, i);
if ((!i || playeringame[i]) && textcmd)
total += 2 + textcmd[0]; // "+2" for size and playernum
}
return total;
}
void PT_TextCmd(SINT8 node, INT32 netconsole)
{
if (client)
return;
// splitscreen special
if (netbuffer->packettype == PT_TEXTCMD2)
netconsole = netnodes[node].player2;
if (netconsole < 0 || netconsole >= MAXPLAYERS)
Net_UnAcknowledgePacket(node);
else
{
size_t j;
tic_t tic = maketic;
UINT8 *textcmd;
// ignore if the textcmd has a reported size of zero
// this shouldn't be sent at all
if (!netbuffer->u.textcmd[0])
{
DEBFILE(va("GetPacket: Textcmd with size 0 detected! (node %u, player %d)\n",
node, netconsole));
Net_UnAcknowledgePacket(node);
return;
}
// ignore if the textcmd size var is actually larger than it should be
// BASEPACKETSIZE + 1 (for size) + textcmd[0] should == datalength
if (netbuffer->u.textcmd[0] > (size_t)doomcom->datalength-BASEPACKETSIZE-1)
{
DEBFILE(va("GetPacket: Bad Textcmd packet size! (expected %d, actual %s, node %u, player %d)\n",
netbuffer->u.textcmd[0], sizeu1((size_t)doomcom->datalength-BASEPACKETSIZE-1),
node, netconsole));
Net_UnAcknowledgePacket(node);
return;
}
// check if tic that we are making isn't too large else we cannot send it :(
// doomcom->numslots+1 "+1" since doomcom->numslots can change within this time and sent time
j = software_MAXPACKETLENGTH
- (netbuffer->u.textcmd[0]+2+BASESERVERTICSSIZE
+ (doomcom->numslots+1)*sizeof(ticcmd_t));
// search a tic that have enougth space in the ticcmd
while ((textcmd = D_GetExistingTextcmd(tic, netconsole)),
(TotalTextCmdPerTic(tic) > j || netbuffer->u.textcmd[0] + (textcmd ? textcmd[0] : 0) > MAXTEXTCMD)
&& tic < firstticstosend + BACKUPTICS)
tic++;
if (tic >= firstticstosend + BACKUPTICS)
{
DEBFILE(va("GetPacket: Textcmd too long (max %s, used %s, mak %d, "
"tosend %u, node %u, player %d)\n", sizeu1(j), sizeu2(TotalTextCmdPerTic(maketic)),
maketic, firstticstosend, node, netconsole));
Net_UnAcknowledgePacket(node);
return;
}
// Make sure we have a buffer
if (!textcmd) textcmd = D_GetTextcmd(tic, netconsole);
DEBFILE(va("textcmd put in tic %u at position %d (player %d) ftts %u mk %u\n",
tic, textcmd[0]+1, netconsole, firstticstosend, maketic));
M_Memcpy(&textcmd[textcmd[0]+1], netbuffer->u.textcmd+1, netbuffer->u.textcmd[0]);
textcmd[0] += (UINT8)netbuffer->u.textcmd[0];
}
}
void SendKick(UINT8 playernum, UINT8 msg)
{
UINT8 buf[2];
if (!(server && cv_rejointimeout.value))
msg &= ~KICK_MSG_KEEP_BODY;
buf[0] = playernum;
buf[1] = msg;
SendNetXCmd(XD_KICK, &buf, 2);
}

62
src/netcode/net_command.h Normal file
View file

@ -0,0 +1,62 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1998-2000 by DooM Legacy Team.
// Copyright (C) 1999-2022 by Sonic Team Junior.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file net_command.h
/// \brief Net command handling
#ifndef __D_NET_COMMAND__
#define __D_NET_COMMAND__
#include "d_clisrv.h"
#include "../doomtype.h"
// Must be a power of two
#define TEXTCMD_HASH_SIZE 4
typedef struct textcmdplayer_s
{
INT32 playernum;
UINT8 cmd[MAXTEXTCMD];
struct textcmdplayer_s *next;
} textcmdplayer_t;
typedef struct textcmdtic_s
{
tic_t tic;
textcmdplayer_t *playercmds[TEXTCMD_HASH_SIZE];
struct textcmdtic_s *next;
} textcmdtic_t;
extern textcmdtic_t *textcmds[TEXTCMD_HASH_SIZE];
extern UINT8 localtextcmd[MAXTEXTCMD];
extern UINT8 localtextcmd2[MAXTEXTCMD]; // splitscreen
void RegisterNetXCmd(netxcmd_t id, void (*cmd_f)(UINT8 **p, INT32 playernum));
void SendNetXCmd(netxcmd_t id, const void *param, size_t nparam);
void SendNetXCmd2(netxcmd_t id, const void *param, size_t nparam); // splitsreen player
UINT8 GetFreeXCmdSize(void);
void D_FreeTextcmd(tic_t tic);
// Gets the buffer for the specified ticcmd, or NULL if there isn't one
UINT8* D_GetExistingTextcmd(tic_t tic, INT32 playernum);
// Gets the buffer for the specified ticcmd, creating one if necessary
UINT8* D_GetTextcmd(tic_t tic, INT32 playernum);
void ExtraDataTicker(void);
// used at txtcmds received to check packetsize bound
size_t TotalTextCmdPerTic(tic_t tic);
void PT_TextCmd(SINT8 node, INT32 netconsole);
void SendKick(UINT8 playernum, UINT8 msg);
#endif

View file

@ -15,6 +15,7 @@
#include "d_clisrv.h"
#include "d_netfil.h"
#include "mserv.h"
#include "net_command.h"
#include "../byteptr.h"
#include "../g_game.h"
#include "../g_state.h"

504
src/netcode/tic_command.c Normal file
View file

@ -0,0 +1,504 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1998-2000 by DooM Legacy Team.
// Copyright (C) 1999-2022 by Sonic Team Junior.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file tic_command.c
/// \brief Tic command handling
#include "tic_command.h"
#include "d_clisrv.h"
#include "net_command.h"
#include "client_connection.h"
#include "i_net.h"
#include "../d_main.h"
#include "../g_game.h"
#include "../i_system.h"
#include "../i_time.h"
#include "../byteptr.h"
#include "../doomstat.h"
#include "../doomtype.h"
tic_t firstticstosend; // min of the nettics
tic_t tictoclear = 0; // optimize d_clearticcmd
ticcmd_t localcmds;
ticcmd_t localcmds2;
boolean cl_packetmissed;
ticcmd_t netcmds[BACKUPTICS][MAXPLAYERS];
static inline void *G_DcpyTiccmd(void* dest, const ticcmd_t* src, const size_t n)
{
const size_t d = n / sizeof(ticcmd_t);
const size_t r = n % sizeof(ticcmd_t);
UINT8 *ret = dest;
if (r)
M_Memcpy(dest, src, n);
else if (d)
G_MoveTiccmd(dest, src, d);
return ret+n;
}
static inline void *G_ScpyTiccmd(ticcmd_t* dest, void* src, const size_t n)
{
const size_t d = n / sizeof(ticcmd_t);
const size_t r = n % sizeof(ticcmd_t);
UINT8 *ret = src;
if (r)
M_Memcpy(dest, src, n);
else if (d)
G_MoveTiccmd(dest, src, d);
return ret+n;
}
/** Guesses the full value of a tic from its lowest byte, for a specific node
*
* \param low The lowest byte of the tic value
* \param node The node to deduce the tic for
* \return The full tic value
*
*/
tic_t ExpandTics(INT32 low, INT32 node)
{
INT32 delta;
delta = low - (netnodes[node].tic & UINT8_MAX);
if (delta >= -64 && delta <= 64)
return (netnodes[node].tic & ~UINT8_MAX) + low;
else if (delta > 64)
return (netnodes[node].tic & ~UINT8_MAX) - 256 + low;
else //if (delta < -64)
return (netnodes[node].tic & ~UINT8_MAX) + 256 + low;
}
void D_Clearticcmd(tic_t tic)
{
INT32 i;
D_FreeTextcmd(tic);
for (i = 0; i < MAXPLAYERS; i++)
netcmds[tic%BACKUPTICS][i].angleturn = 0;
DEBFILE(va("clear tic %5u (%2u)\n", tic, tic%BACKUPTICS));
}
void D_ResetTiccmds(void)
{
INT32 i;
memset(&localcmds, 0, sizeof(ticcmd_t));
memset(&localcmds2, 0, sizeof(ticcmd_t));
// Reset the net command list
for (i = 0; i < TEXTCMD_HASH_SIZE; i++)
while (textcmds[i])
D_Clearticcmd(textcmds[i]->tic);
}
void PT_ClientCmd(SINT8 node, INT32 netconsole)
{
tic_t realend, realstart;
if (client)
return;
// To save bytes, only the low byte of tic numbers are sent
// Use ExpandTics to figure out what the rest of the bytes are
realstart = ExpandTics(netbuffer->u.clientpak.client_tic, node);
realend = ExpandTics(netbuffer->u.clientpak.resendfrom, node);
if (netbuffer->packettype == PT_CLIENTMIS || netbuffer->packettype == PT_CLIENT2MIS
|| netbuffer->packettype == PT_NODEKEEPALIVEMIS
|| netnodes[node].supposedtic < realend)
{
netnodes[node].supposedtic = realend;
}
// Discard out of order packet
if (netnodes[node].tic > realend)
{
DEBFILE(va("out of order ticcmd discarded nettics = %u\n", netnodes[node].tic));
return;
}
// Update the nettics
netnodes[node].tic = realend;
// Don't do anything for packets of type NODEKEEPALIVE?
if (netconsole == -1 || netbuffer->packettype == PT_NODEKEEPALIVE
|| netbuffer->packettype == PT_NODEKEEPALIVEMIS)
return;
// As long as clients send valid ticcmds, the server can keep running, so reset the timeout
/// \todo Use a separate cvar for that kind of timeout?
netnodes[node].freezetimeout = I_GetTime() + connectiontimeout;
// Copy ticcmd
G_MoveTiccmd(&netcmds[maketic%BACKUPTICS][netconsole], &netbuffer->u.clientpak.cmd, 1);
// Check ticcmd for "speed hacks"
if (netcmds[maketic%BACKUPTICS][netconsole].forwardmove > MAXPLMOVE || netcmds[maketic%BACKUPTICS][netconsole].forwardmove < -MAXPLMOVE
|| netcmds[maketic%BACKUPTICS][netconsole].sidemove > MAXPLMOVE || netcmds[maketic%BACKUPTICS][netconsole].sidemove < -MAXPLMOVE)
{
CONS_Alert(CONS_WARNING, M_GetText("Illegal movement value received from node %d\n"), netconsole);
//D_Clearticcmd(k);
SendKick(netconsole, KICK_MSG_CON_FAIL);
return;
}
// Splitscreen cmd
if ((netbuffer->packettype == PT_CLIENT2CMD || netbuffer->packettype == PT_CLIENT2MIS)
&& netnodes[node].player2 >= 0)
G_MoveTiccmd(&netcmds[maketic%BACKUPTICS][(UINT8)netnodes[node].player2],
&netbuffer->u.client2pak.cmd2, 1);
// Check player consistancy during the level
if (realstart <= gametic && realstart + BACKUPTICS - 1 > gametic && gamestate == GS_LEVEL
&& consistancy[realstart%BACKUPTICS] != SHORT(netbuffer->u.clientpak.consistancy)
&& !SV_ResendingSavegameToAnyone()
&& !netnodes[node].resendingsavegame && netnodes[node].savegameresendcooldown <= I_GetTime())
{
if (cv_resynchattempts.value)
{
// Tell the client we are about to resend them the gamestate
netbuffer->packettype = PT_WILLRESENDGAMESTATE;
HSendPacket(node, true, 0, 0);
netnodes[node].resendingsavegame = true;
if (cv_blamecfail.value)
CONS_Printf(M_GetText("Synch failure for player %d (%s); expected %hd, got %hd\n"),
netconsole+1, player_names[netconsole],
consistancy[realstart%BACKUPTICS],
SHORT(netbuffer->u.clientpak.consistancy));
DEBFILE(va("Restoring player %d (synch failure) [%update] %d!=%d\n",
netconsole, realstart, consistancy[realstart%BACKUPTICS],
SHORT(netbuffer->u.clientpak.consistancy)));
return;
}
else
{
SendKick(netconsole, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
DEBFILE(va("player %d kicked (synch failure) [%u] %d!=%d\n",
netconsole, realstart, consistancy[realstart%BACKUPTICS],
SHORT(netbuffer->u.clientpak.consistancy)));
return;
}
}
}
void PT_ServerTics(SINT8 node, INT32 netconsole)
{
UINT8 *pak, *txtpak, numtxtpak;
tic_t realend, realstart;
if (!netnodes[node].ingame)
{
// Do not remove my own server (we have just get a out of order packet)
if (node != servernode)
{
DEBFILE(va("unknown packet received (%d) from unknown host\n",netbuffer->packettype));
Net_CloseConnection(node);
}
return;
}
// Only accept PT_SERVERTICS from the server.
if (node != servernode)
{
CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_SERVERTICS", node);
if (server)
SendKick(netconsole, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
return;
}
realstart = netbuffer->u.serverpak.starttic;
realend = realstart + netbuffer->u.serverpak.numtics;
txtpak = (UINT8 *)&netbuffer->u.serverpak.cmds[netbuffer->u.serverpak.numslots
* netbuffer->u.serverpak.numtics];
if (realend > gametic + CLIENTBACKUPTICS)
realend = gametic + CLIENTBACKUPTICS;
cl_packetmissed = realstart > neededtic;
if (realstart <= neededtic && realend > neededtic)
{
tic_t i, j;
pak = (UINT8 *)&netbuffer->u.serverpak.cmds;
for (i = realstart; i < realend; i++)
{
// clear first
D_Clearticcmd(i);
// copy the tics
pak = G_ScpyTiccmd(netcmds[i%BACKUPTICS], pak,
netbuffer->u.serverpak.numslots*sizeof (ticcmd_t));
// copy the textcmds
numtxtpak = *txtpak++;
for (j = 0; j < numtxtpak; j++)
{
INT32 k = *txtpak++; // playernum
const size_t txtsize = txtpak[0]+1;
if (i >= gametic) // Don't copy old net commands
M_Memcpy(D_GetTextcmd(i, k), txtpak, txtsize);
txtpak += txtsize;
}
}
neededtic = realend;
}
else
{
DEBFILE(va("frame not in bound: %u\n", neededtic));
/*if (realend < neededtic - 2 * TICRATE || neededtic + 2 * TICRATE < realstart)
I_Error("Received an out of order PT_SERVERTICS packet!\n"
"Got tics %d-%d, needed tic %d\n\n"
"Please report this crash on the Master Board,\n"
"IRC or Discord so it can be fixed.\n", (INT32)realstart, (INT32)realend, (INT32)neededtic);*/
}
}
// send the client packet to the server
void CL_SendClientCmd(void)
{
size_t packetsize = 0;
netbuffer->packettype = PT_CLIENTCMD;
if (cl_packetmissed)
netbuffer->packettype++;
netbuffer->u.clientpak.resendfrom = (UINT8)(neededtic & UINT8_MAX);
netbuffer->u.clientpak.client_tic = (UINT8)(gametic & UINT8_MAX);
if (gamestate == GS_WAITINGPLAYERS)
{
// Send PT_NODEKEEPALIVE packet
netbuffer->packettype += 4;
packetsize = sizeof (clientcmd_pak) - sizeof (ticcmd_t) - sizeof (INT16);
HSendPacket(servernode, false, 0, packetsize);
}
else if (gamestate != GS_NULL && (addedtogame || dedicated))
{
G_MoveTiccmd(&netbuffer->u.clientpak.cmd, &localcmds, 1);
netbuffer->u.clientpak.consistancy = SHORT(consistancy[gametic%BACKUPTICS]);
// Send a special packet with 2 cmd for splitscreen
if (splitscreen || botingame)
{
netbuffer->packettype += 2;
G_MoveTiccmd(&netbuffer->u.client2pak.cmd2, &localcmds2, 1);
packetsize = sizeof (client2cmd_pak);
}
else
packetsize = sizeof (clientcmd_pak);
HSendPacket(servernode, false, 0, packetsize);
}
if (cl_mode == CL_CONNECTED || dedicated)
{
// Send extra data if needed
if (localtextcmd[0])
{
netbuffer->packettype = PT_TEXTCMD;
M_Memcpy(netbuffer->u.textcmd,localtextcmd, localtextcmd[0]+1);
// All extra data have been sent
if (HSendPacket(servernode, true, 0, localtextcmd[0]+1)) // Send can fail...
localtextcmd[0] = 0;
}
// Send extra data if needed for player 2 (splitscreen)
if (localtextcmd2[0])
{
netbuffer->packettype = PT_TEXTCMD2;
M_Memcpy(netbuffer->u.textcmd, localtextcmd2, localtextcmd2[0]+1);
// All extra data have been sent
if (HSendPacket(servernode, true, 0, localtextcmd2[0]+1)) // Send can fail...
localtextcmd2[0] = 0;
}
}
}
// send the server packet
// send tic from firstticstosend to maketic-1
void SV_SendTics(void)
{
tic_t realfirsttic, lasttictosend, i;
UINT32 n;
INT32 j;
size_t packsize;
UINT8 *bufpos;
UINT8 *ntextcmd;
// send to all client but not to me
// for each node create a packet with x tics and send it
// x is computed using netnodes[n].supposedtic, max packet size and maketic
for (n = 1; n < MAXNETNODES; n++)
if (netnodes[n].ingame)
{
// assert netnodes[n].supposedtic>=netnodes[n].tic
realfirsttic = netnodes[n].supposedtic;
lasttictosend = min(maketic, netnodes[n].tic + CLIENTBACKUPTICS);
if (realfirsttic >= lasttictosend)
{
// well we have sent all tics we will so use extrabandwidth
// to resent packet that are supposed lost (this is necessary since lost
// packet detection work when we have received packet with firsttic > neededtic
// (getpacket servertics case)
DEBFILE(va("Nothing to send node %u mak=%u sup=%u net=%u \n",
n, maketic, netnodes[n].supposedtic, netnodes[n].tic));
realfirsttic = netnodes[n].tic;
if (realfirsttic >= lasttictosend || (I_GetTime() + n)&3)
// all tic are ok
continue;
DEBFILE(va("Sent %d anyway\n", realfirsttic));
}
if (realfirsttic < firstticstosend)
realfirsttic = firstticstosend;
// compute the length of the packet and cut it if too large
packsize = BASESERVERTICSSIZE;
for (i = realfirsttic; i < lasttictosend; i++)
{
packsize += sizeof (ticcmd_t) * doomcom->numslots;
packsize += TotalTextCmdPerTic(i);
if (packsize > software_MAXPACKETLENGTH)
{
DEBFILE(va("packet too large (%s) at tic %d (should be from %d to %d)\n",
sizeu1(packsize), i, realfirsttic, lasttictosend));
lasttictosend = i;
// too bad: too much player have send extradata and there is too
// much data in one tic.
// To avoid it put the data on the next tic. (see getpacket
// textcmd case) but when numplayer changes the computation can be different
if (lasttictosend == realfirsttic)
{
if (packsize > MAXPACKETLENGTH)
I_Error("Too many players: can't send %s data for %d players to node %d\n"
"Well sorry nobody is perfect....\n",
sizeu1(packsize), doomcom->numslots, n);
else
{
lasttictosend++; // send it anyway!
DEBFILE("sending it anyway\n");
}
}
break;
}
}
// Send the tics
netbuffer->packettype = PT_SERVERTICS;
netbuffer->u.serverpak.starttic = realfirsttic;
netbuffer->u.serverpak.numtics = (UINT8)(lasttictosend - realfirsttic);
netbuffer->u.serverpak.numslots = (UINT8)SHORT(doomcom->numslots);
bufpos = (UINT8 *)&netbuffer->u.serverpak.cmds;
for (i = realfirsttic; i < lasttictosend; i++)
{
bufpos = G_DcpyTiccmd(bufpos, netcmds[i%BACKUPTICS], doomcom->numslots * sizeof (ticcmd_t));
}
// add textcmds
for (i = realfirsttic; i < lasttictosend; i++)
{
ntextcmd = bufpos++;
*ntextcmd = 0;
for (j = 0; j < MAXPLAYERS; j++)
{
UINT8 *textcmd = D_GetExistingTextcmd(i, j);
INT32 size = textcmd ? textcmd[0] : 0;
if ((!j || playeringame[j]) && size)
{
(*ntextcmd)++;
WRITEUINT8(bufpos, j);
M_Memcpy(bufpos, textcmd, size + 1);
bufpos += size + 1;
}
}
}
packsize = bufpos - (UINT8 *)&(netbuffer->u);
HSendPacket(n, false, 0, packsize);
// when tic are too large, only one tic is sent so don't go backward!
if (lasttictosend-doomcom->extratics > realfirsttic)
netnodes[n].supposedtic = lasttictosend-doomcom->extratics;
else
netnodes[n].supposedtic = lasttictosend;
if (netnodes[n].supposedtic < netnodes[n].tic) netnodes[n].supposedtic = netnodes[n].tic;
}
// node 0 is me!
netnodes[0].supposedtic = maketic;
}
//
// TryRunTics
//
void Local_Maketic(INT32 realtics)
{
I_OsPolling(); // I_Getevent
D_ProcessEvents(); // menu responder, cons responder,
// game responder calls HU_Responder, AM_Responder,
// and G_MapEventsToControls
if (!dedicated) rendergametic = gametic;
// translate inputs (keyboard/mouse/gamepad) into game controls
G_BuildTiccmd(&localcmds, realtics, 1);
if (splitscreen || botingame)
G_BuildTiccmd(&localcmds2, realtics, 2);
localcmds.angleturn |= TICCMD_RECEIVED;
localcmds2.angleturn |= TICCMD_RECEIVED;
}
// create missed tic
void SV_Maketic(void)
{
INT32 i;
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i])
continue;
// We didn't receive this tic
if ((netcmds[maketic % BACKUPTICS][i].angleturn & TICCMD_RECEIVED) == 0)
{
ticcmd_t * ticcmd = &netcmds[(maketic ) % BACKUPTICS][i];
ticcmd_t *prevticcmd = &netcmds[(maketic - 1) % BACKUPTICS][i];
if (players[i].quittime)
{
// Copy the angle/aiming from the previous tic
// and empty the other inputs
memset(ticcmd, 0, sizeof(netcmds[0][0]));
ticcmd->angleturn = prevticcmd->angleturn | TICCMD_RECEIVED;
ticcmd->aiming = prevticcmd->aiming;
}
else
{
DEBFILE(va("MISS tic%4d for player %d\n", maketic, i));
// Copy the input from the previous tic
*ticcmd = *prevticcmd;
ticcmd->angleturn &= ~TICCMD_RECEIVED;
}
}
}
// all tic are now proceed make the next
maketic++;
}

44
src/netcode/tic_command.h Normal file
View file

@ -0,0 +1,44 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1998-2000 by DooM Legacy Team.
// Copyright (C) 1999-2022 by Sonic Team Junior.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file tic_command.h
/// \brief Tic command handling
#ifndef __D_TIC_COMMAND__
#define __D_TIC_COMMAND__
#include "d_clisrv.h"
#include "../d_ticcmd.h"
#include "../doomdef.h"
#include "../doomtype.h"
extern tic_t firstticstosend; // min of the nettics
extern tic_t tictoclear; // optimize d_clearticcmd
extern ticcmd_t localcmds;
extern ticcmd_t localcmds2;
extern boolean cl_packetmissed;
extern ticcmd_t netcmds[BACKUPTICS][MAXPLAYERS];
tic_t ExpandTics(INT32 low, INT32 node);
void D_Clearticcmd(tic_t tic);
void D_ResetTiccmds(void);
void PT_ClientCmd(SINT8 node, INT32 netconsole);
void PT_ServerTics(SINT8 node, INT32 netconsole);
// send the client packet to the server
void CL_SendClientCmd(void);
void SV_SendTics(void);
void Local_Maketic(INT32 realtics);
void SV_Maketic(void);
#endif

View file

@ -28,6 +28,7 @@
#include "m_misc.h"
#include "v_video.h" // video flags for CEchos
#include "f_finale.h"
#include "netcode/net_command.h"
// CTF player names
#define CTFTEAMCODE(pl) pl->ctfteam ? (pl->ctfteam == 1 ? "\x85" : "\x84") : ""

View file

@ -36,6 +36,7 @@
#include "p_slopes.h"
#include "f_finale.h"
#include "m_cond.h"
#include "netcode/net_command.h"
static CV_PossibleValue_t CV_BobSpeed[] = {{0, "MIN"}, {4*FRACUNIT, "MAX"}, {0, NULL}};
consvar_t cv_movebob = CVAR_INIT ("movebob", "1.0", CV_FLOAT|CV_SAVE, CV_BobSpeed, NULL);

View file

@ -87,6 +87,8 @@
#include "taglist.h"
#include "netcode/net_command.h"
//
// Map MD5, calculated on level load.
// Sent to clients in PT_SERVERINFO.

View file

@ -26,6 +26,7 @@
#include "r_main.h"
#include "r_fps.h"
#include "i_video.h" // rendermode
#include "netcode/net_command.h"
// Object place
#include "m_cheat.h"

View file

@ -18,6 +18,7 @@
#include "i_system.h"
#include "d_event.h"
#include "netcode/d_net.h"
#include "netcode/net_command.h"
#include "g_game.h"
#include "p_local.h"
#include "r_fps.h"