// 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); }