diff --git a/src/Makefile b/src/Makefile index fee849d6c..d0e66e269 100644 --- a/src/Makefile +++ b/src/Makefile @@ -487,6 +487,7 @@ OBJS:=$(i_main_o) \ $(OBJDIR)/f_wipe.o \ $(OBJDIR)/g_game.o \ $(OBJDIR)/g_input.o \ + $(OBJDIR)/g_splitscreen.o\ $(OBJDIR)/am_map.o \ $(OBJDIR)/command.o \ $(OBJDIR)/console.o \ diff --git a/src/d_clisrv.c b/src/d_clisrv.c index a53a61cf0..b397c83ae 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -84,6 +84,8 @@ static boolean serverrunning = false; INT32 serverplayer = 0; char motd[254], server_context[8]; // Message of the Day, Unique Context (even without Mumble support) +int playerconsole[MAXPLAYERS]; + // Server specific vars UINT8 playernode[MAXPLAYERS]; @@ -1505,6 +1507,8 @@ static boolean SV_SendServerConfig(INT32 node) UINT8 *p, *op; boolean waspacketsent; + memset(&netbuffer->u.servercfg, 0, sizeof netbuffer->u.servercfg); + netbuffer->packettype = PT_SERVERCFG; netbuffer->u.servercfg.version = VERSION; @@ -1524,7 +1528,6 @@ static boolean SV_SendServerConfig(INT32 node) memset(netbuffer->u.servercfg.playercolor, 0xFF, sizeof(netbuffer->u.servercfg.playercolor)); memset(netbuffer->u.servercfg.adminplayers, -1, sizeof(netbuffer->u.servercfg.adminplayers)); - memset(netbuffer->u.servercfg.powerlevels, 0, sizeof(netbuffer->u.servercfg.powerlevels)); for (i = 0; i < MAXPLAYERS; i++) { @@ -1535,6 +1538,19 @@ static boolean SV_SendServerConfig(INT32 node) if (!playeringame[i]) continue; + netbuffer->u.servercfg.consoleplayers[i] = playerconsole[i]; + netbuffer->u.servercfg.invitations[i] = splitscreen_invitations[i]; + netbuffer->u.servercfg.party_size[i] = splitscreen_party_size[i]; + netbuffer->u.servercfg.original_party_size[i] = + splitscreen_original_party_size[i]; + + for (j = 0; j < MAXSPLITSCREENPLAYERS; ++j) + { + netbuffer->u.servercfg.party[i][j] = splitscreen_party[i][j]; + netbuffer->u.servercfg.original_party[i][j] = + splitscreen_original_party[i][j]; + } + netbuffer->u.servercfg.playerskins[i] = (UINT8)players[i].skin; netbuffer->u.servercfg.playercolor[i] = (UINT8)players[i].skincolor; } @@ -2619,6 +2635,8 @@ static void ResetNode(INT32 node); // void CL_ClearPlayer(INT32 playernum) { + int i; + if (players[playernum].mo) { // Don't leave a NiGHTS ghost! @@ -2626,6 +2644,16 @@ void CL_ClearPlayer(INT32 playernum) P_RemoveMobj(players[playernum].mo->tracer); P_RemoveMobj(players[playernum].mo); } + + for (i = 0; i < MAXPLAYERS; ++i) + { + if (splitscreen_invitations[i] == playernum) + splitscreen_invitations[i] = -1; + } + + splitscreen_invitations[playernum] = -1; + splitscreen_partied[playernum] = false; + memset(&players[playernum], 0, sizeof (player_t)); } @@ -3351,6 +3379,7 @@ void SV_ResetServer(void) sprintf(player_names[i], "Player %d", i + 1); adminplayers[i] = -1; // Populate the entire adminplayers array with -1. K_ClearClientPowerLevels(); + splitscreen_invitations[i] = -1; } mynode = 0; @@ -3455,6 +3484,7 @@ static inline void SV_AddNode(INT32 node) static void Got_AddPlayer(UINT8 **p, INT32 playernum) { INT16 node, newplayernum; + int console; UINT8 splitscreenplayer = 0; UINT8 i; @@ -3475,6 +3505,7 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum) node = (UINT8)READUINT8(*p); newplayernum = (UINT8)READUINT8(*p); + console = (UINT8)READUINT8(*p); splitscreenplayer = (UINT8)READUINT8(*p); CONS_Debug(DBG_NETPLAY, "addplayer: %d %d %d\n", node, newplayernum, splitscreenplayer); @@ -3507,6 +3538,7 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum) displayplayers[i] = newplayernum; localdisplayplayers[i] = i; } + splitscreen_partied[newplayernum] = true; DEBFILE("spawning me\n"); } @@ -3516,6 +3548,12 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum) players[newplayernum].splitscreenindex = splitscreenplayer; + playerconsole[newplayernum] = console; + splitscreen_original_party_size[console] = + ++splitscreen_party_size[console]; + splitscreen_original_party[console][splitscreenplayer] = + splitscreen_party[console][splitscreenplayer] = newplayernum; + if (netgame) { if (server && cv_showjoinaddress.value) @@ -3577,7 +3615,7 @@ static boolean SV_AddWaitingPlayers(void) // splitscreen can allow 2+ players in one node for (; nodewaiting[node] > 0; nodewaiting[node]--) { - UINT8 buf[3]; + UINT8 buf[4]; UINT8 *buf_p = buf; newplayer = true; @@ -3614,6 +3652,7 @@ static boolean SV_AddWaitingPlayers(void) else if (playerpernode[node] < 4) nodetoplayer4[node] = newplayernum; + WRITEUINT8(buf_p, nodetoplayer[node]); // consoleplayer WRITEUINT8(buf_p, playerpernode[node]); // splitscreen num playerpernode[node]++; @@ -4141,6 +4180,21 @@ static void HandlePacketFromAwayNode(SINT8 node) adminplayers[j] = netbuffer->u.servercfg.adminplayers[j]; for (k = 0; k < PWRLV_NUMTYPES; k++) clientpowerlevels[j][k] = netbuffer->u.servercfg.powerlevels[j][k]; + + /* all spitscreen related */ + playerconsole[j] = netbuffer->u.servercfg.consoleplayers[j]; + splitscreen_invitations[j] = netbuffer->u.servercfg.invitations[j]; + splitscreen_original_party_size[j] = + netbuffer->u.servercfg.original_party_size[j]; + splitscreen_party_size[j] = + netbuffer->u.servercfg.party_size[j]; + for (k = 0; k < MAXSPLITSCREENPLAYERS; ++k) + { + splitscreen_original_party[j][k] = + netbuffer->u.servercfg.original_party[j][k]; + splitscreen_party[j][k] = + netbuffer->u.servercfg.party[j][k]; + } } memcpy(server_context, netbuffer->u.servercfg.server_context, 8); } diff --git a/src/d_clisrv.h b/src/d_clisrv.h index 0bd85b614..c13df4f5e 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -328,6 +328,14 @@ typedef struct SINT8 adminplayers[MAXPLAYERS]; // Needs to be signed UINT16 powerlevels[MAXPLAYERS][PWRLV_NUMTYPES]; // SRB2kart: player power levels + UINT8 consoleplayers[MAXPLAYERS]; + /* splitscreen */ + SINT8 invitations[MAXPLAYERS]; + UINT8 party_size[MAXPLAYERS]; + UINT8 party[MAXPLAYERS][MAXSPLITSCREENPLAYERS]; + UINT8 original_party_size[MAXPLAYERS]; + UINT8 original_party[MAXPLAYERS][MAXSPLITSCREENPLAYERS]; + char server_context[8]; // Unique context id, generated at server startup. UINT8 varlengthinputs[0]; // Playernames and netvars @@ -599,6 +607,8 @@ SINT8 nametonum(const char *name); extern char motd[254], server_context[8]; extern UINT8 playernode[MAXPLAYERS]; +/* consoleplayer of this player (splitscreen) */ +extern int playerconsole[MAXPLAYERS]; INT32 D_NumPlayers(void); void D_ResetTiccmds(void); diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 334509389..51a4b822d 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -63,6 +63,9 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum); static void Got_WeaponPref(UINT8 **cp, INT32 playernum); static void Got_PowerLevel(UINT8 **cp, INT32 playernum); +static void Got_PartyInvite(UINT8 **cp, INT32 playernum); +static void Got_AcceptPartyInvite(UINT8 **cp, INT32 playernum); +static void Got_LeaveParty(UINT8 **cp, INT32 playernum); static void Got_Mapcmd(UINT8 **cp, INT32 playernum); static void Got_ExitLevelcmd(UINT8 **cp, INT32 playernum); static void Got_SetupVotecmd(UINT8 **cp, INT32 playernum); @@ -134,6 +137,11 @@ static void Command_ResetCamera_f(void); static void Command_View_f (void); static void Command_SetViews_f(void); +static void Command_Invite_f(void); +static void Command_AcceptInvite_f(void); +static void Command_RejectInvite_f(void); +static void Command_LeaveParty_f(void); + static void Command_Addfile(void); static void Command_ListWADS_f(void); #ifdef DELFILE @@ -544,6 +552,9 @@ void D_RegisterServerCommands(void) RegisterNetXCmd(XD_NAMEANDCOLOR, Got_NameAndColor); RegisterNetXCmd(XD_WEAPONPREF, Got_WeaponPref); RegisterNetXCmd(XD_POWERLEVEL, Got_PowerLevel); + RegisterNetXCmd(XD_PARTYINVITE, Got_PartyInvite); + RegisterNetXCmd(XD_ACCEPTPARTYINVITE, Got_AcceptPartyInvite); + RegisterNetXCmd(XD_LEAVEPARTY, Got_LeaveParty); RegisterNetXCmd(XD_MAP, Got_Mapcmd); RegisterNetXCmd(XD_EXITLEVEL, Got_ExitLevelcmd); RegisterNetXCmd(XD_ADDFILE, Got_Addfilecmd); @@ -753,6 +764,11 @@ void D_RegisterClientCommands(void) COM_AddCommand("changeteam3", Command_Teamchange3_f); COM_AddCommand("changeteam4", Command_Teamchange4_f); + COM_AddCommand("invite", Command_Invite_f); + COM_AddCommand("acceptinvite", Command_AcceptInvite_f); + COM_AddCommand("rejectinvite", Command_RejectInvite_f); + COM_AddCommand("leaveparty", Command_LeaveParty_f); + COM_AddCommand("playdemo", Command_Playdemo_f); COM_AddCommand("timedemo", Command_Timedemo_f); COM_AddCommand("stopdemo", Command_Stopdemo_f); @@ -1942,6 +1958,104 @@ static void Got_PowerLevel(UINT8 **cp,INT32 playernum) CONS_Debug(DBG_GAMELOGIC, "set player %d to power %d\n", playernum, race); } +static void Got_PartyInvite(UINT8 **cp,INT32 playernum) +{ + int invitee; + + boolean kick = false; + + invitee = READUINT8 (*cp); + + if ( + invitee >= 0 && + invitee < MAXPLAYERS && + playeringame[invitee] && + playerconsole[playernum] == playernum/* only consoleplayer may! */ + ){ + invitee = playerconsole[invitee]; + /* you cannot invite yourself or your computer */ + if (invitee == playernum) + kick = true; + } + else + kick = true; + + if (kick) + { + CONS_Alert(CONS_WARNING, M_GetText("Illegal splitscreen invitation received from %s\n"), player_names[playernum]); + if (server) + { + XBOXSTATIC UINT8 buf[2]; + + buf[0] = (UINT8)playernum; + buf[1] = KICK_MSG_CON_FAIL; + SendNetXCmd(XD_KICK, &buf, 2); + } + return; + } + + if (splitscreen_invitations[invitee] < 0) + { + splitscreen_invitations[invitee] = playernum; + + if (invitee == consoleplayer)/* hey that's me! */ + { + CONS_Printf( + "You have been invited to join %s", + player_names[playernum] + ); + if (splitscreen_party_size[playernum] > 1) + { + CONS_Printf( + " and %d others", + ( splitscreen_party_size[playernum] - 1 ) + ); + } + CONS_Printf(".\n"); + } + } +} + +static void Got_AcceptPartyInvite(UINT8 **cp,INT32 playernum) +{ + int invitation; + int old_party_size; + int views; + + if (playerconsole[playernum] != playernum) + { + CONS_Alert(CONS_WARNING, M_GetText("Illegal accept splitscreen invite received from %s\n"), player_names[playernum]); + if (server) + { + XBOXSTATIC UINT8 buf[2]; + + buf[0] = (UINT8)playernum; + buf[1] = KICK_MSG_CON_FAIL; + SendNetXCmd(XD_KICK, &buf, 2); + } + return; + } + + invitation = splitscreen_invitations[playernum]; + + if (invitation >= 0) + { + old_party_size = splitscreen_party_size[invitation]; + views = splitscreen_original_party_size[playernum]; + + if (( old_party_size + views ) <= MAXSPLITSCREENPLAYERS) + { + G_RemovePartyMember(playernum); + G_AddPartyMember(invitation, playernum); + } + } +} + +static void Got_LeaveParty(UINT8 **cp,INT32 playernum) +{ + splitscreen_invitations[playernum] = -1; +} + void D_SendPlayerConfig(void) { SendNameAndColor(); @@ -2182,6 +2296,85 @@ static void Command_SetViews_f(void) } } +static void +Command_Invite_f (void) +{ + UINT8 invitee; + + if (COM_Argc() != 2) + { + CONS_Printf("invite : Invite a player to your party.\n"); + return; + } + + if (r_splitscreen >= MAXSPLITSCREENPLAYERS) + { + CONS_Alert(CONS_WARNING, "Your party is full!\n"); + return; + } + + invitee = LookupPlayer(COM_Argv(1)); + + if (invitee == -1) + { + CONS_Alert(CONS_WARNING, "There is no player by that name!\n"); + return; + } + if (!playeringame[invitee]) + { + CONS_Alert(CONS_WARNING, "There is no player using that slot!\n"); + return; + } + + if (invitee == consoleplayer) + { + CONS_Alert(CONS_WARNING, "You cannot invite yourself! Bruh!\n"); + return; + } + + if (splitscreen_invitations[invitee] >= 0) + { + CONS_Alert(CONS_WARNING, + "That player has already been invited to join another party.\n"); + } + + SendNetXCmd(XD_PARTYINVITE, &invitee, 1); +} + +static boolean +CheckPartyInvite (void) +{ + if (splitscreen_invitations[consoleplayer] < 0) + { + CONS_Alert(CONS_WARNING, "There is no open party invitation.\n"); + return false; + } + return true; +} + +static void +Command_AcceptInvite_f (void) +{ + if (CheckPartyInvite()) + SendNetXCmd(XD_ACCEPTPARTYINVITE, NULL, 0); +} + +static void +Command_RejectInvite_f (void) +{ + if (CheckPartyInvite()) + SendNetXCmd(XD_LEAVEPARTY, NULL, 0); +} + +static void +Command_LeaveParty_f (void) +{ + if (r_splitscreen > splitscreen) + { + SendNetXCmd(XD_LEAVEPARTY, NULL, 0); + } +} + // ======================================================================== // play a demo, add .lmp for external demos @@ -5299,7 +5492,29 @@ static void Got_PickVotecmd(UINT8 **cp, INT32 playernum) */ static void Command_Displayplayer_f(void) { - CONS_Printf(M_GetText("Displayplayer is %d\n"), displayplayers[localdisplayplayers[0]]); + int playernum; + int i; + for (i = 0; i <= splitscreen; ++i) + { + playernum = displayplayers[localdisplayplayers[i]]; + CONS_Printf( + "local player %d: \x84(%d) \x83%s\x80\n", + i, + playernum, + player_names[playernum] + ); + } + CONS_Printf("\x83----------------------------------------\x80\n"); + for (i = 0; i <= r_splitscreen; ++i) + { + playernum = displayplayers[i]; + CONS_Printf( + "display player %d: \x84(%d) \x83%s\x80\n", + i, + playernum, + player_names[playernum] + ); + } } /** Quits a game and returns to the title screen. diff --git a/src/d_netcmd.h b/src/d_netcmd.h index d1f28665c..fe1f59ad2 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -179,9 +179,12 @@ typedef enum XD_PICKVOTE, // 24 XD_REMOVEPLAYER,// 25 XD_POWERLEVEL, // 26 + XD_PARTYINVITE, // 27 + XD_ACCEPTPARTYINVITE, // 28 + XD_LEAVEPARTY, // 29 #ifdef HAVE_BLUA - XD_LUACMD, // 27 - XD_LUAVAR, // 28 + XD_LUACMD, // 30 + XD_LUAVAR, // 31 #endif MAXNETXCMD } netxcmd_t; diff --git a/src/doomdef.h b/src/doomdef.h index e1e9ab13d..2aa505744 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -242,6 +242,7 @@ extern FILE *logstream; // NOTE: it needs more than this to increase the number of players... #define MAXPLAYERS 16 +#define MAXSPLITSCREENPLAYERS 4 // Max number of players on a single computer #define MAXSKINS 128 #define PLAYERSMASK (MAXPLAYERS-1) #define MAXPLAYERNAME 21 diff --git a/src/doomstat.h b/src/doomstat.h index 1d43b4986..fbda4df2a 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -80,7 +80,6 @@ extern boolean multiplayer; extern INT16 gametype; -#define MAXSPLITSCREENPLAYERS 4 // Max number of players on a single computer extern UINT8 splitscreen; extern int r_splitscreen; @@ -126,6 +125,18 @@ extern INT32 displayplayers[MAXSPLITSCREENPLAYERS]; /* displayplayers[localdisplayplayers[0]] = consoleplayer */ extern INT32 localdisplayplayers[MAXSPLITSCREENPLAYERS]; +/* spitscreen players sync */ +extern int splitscreen_original_party_size[MAXPLAYERS]; +extern int splitscreen_original_party[MAXPLAYERS][MAXSPLITSCREENPLAYERS]; + +/* parties */ +extern int splitscreen_invitations[MAXPLAYERS]; +extern int splitscreen_party_size[MAXPLAYERS]; +extern int splitscreen_party[MAXPLAYERS][MAXSPLITSCREENPLAYERS]; + +/* the only local one */ +extern boolean splitscreen_partied[MAXPLAYERS]; + // Maps of special importance extern INT16 spstage_start; extern INT16 sstage_start; diff --git a/src/g_game.h b/src/g_game.h index de482fe7f..e96c0d480 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -308,6 +308,9 @@ void G_ResetViews(void); void G_ResetView(UINT8 viewnum, INT32 playernum, boolean onlyactive); void G_AdjustView(UINT8 viewnum, INT32 offset, boolean onlyactive); +void G_AddPartyMember (int party_member, int new_party_member); +void G_RemovePartyMember (int party_member); + void G_AddPlayer(INT32 playernum); void G_SetExitGameFlag(void); diff --git a/src/g_splitscreen.c b/src/g_splitscreen.c new file mode 100644 index 000000000..271a83569 --- /dev/null +++ b/src/g_splitscreen.c @@ -0,0 +1,209 @@ +// SONIC ROBO BLAST 2 KART +//----------------------------------------------------------------------------- +// Copyright (C) 2020 by James R. +// +// 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 g_splitscreen.c +/// \brief some splitscreen stuff + +#include "doomdef.h" +#include "g_game.h" +#include "r_local.h" + +int splitscreen_original_party_size[MAXPLAYERS]; +int splitscreen_original_party[MAXPLAYERS][MAXSPLITSCREENPLAYERS]; + +int splitscreen_invitations[MAXPLAYERS]; +int splitscreen_party_size[MAXPLAYERS]; +int splitscreen_party[MAXPLAYERS][MAXSPLITSCREENPLAYERS]; + +boolean splitscreen_partied[MAXPLAYERS]; + +static void +ResetParty (int playernum) +{ + INT32 old_displayplayers[MAXSPLITSCREENPLAYERS]; + + int i; + + splitscreen_party_size[playernum] = + splitscreen_original_party_size[playernum]; + + memcpy(splitscreen_party[playernum], splitscreen_original_party[playernum], + sizeof splitscreen_party[playernum]); + + if (playernum == consoleplayer) + { + memset(splitscreen_partied, 0, sizeof splitscreen_partied); + splitscreen_partied[consoleplayer] = true; + + memcpy(old_displayplayers, displayplayers, sizeof old_displayplayers); + + /* easier to just rebuild displayplayers with local players */ + for (i = 0; i <= splitscreen; ++i) + { + displayplayers[i] = old_displayplayers[localdisplayplayers[i]]; + localdisplayplayers[i] = i; + } + while (i < MAXSPLITSCREENPLAYERS) + { + displayplayers[i] = consoleplayer; + localdisplayplayers[i] = i; + + i++; + } + + r_splitscreen = splitscreen; + + R_ExecuteSetViewSize(); + } +} + +void +G_RemovePartyMember (int playernum) +{ + int old_party[MAXSPLITSCREENPLAYERS]; + int new_party[MAXSPLITSCREENPLAYERS]; + + int old_party_size; + int before; + int after; + int views; + + int i; + int n; + + old_party_size = splitscreen_party_size[playernum]; + + for (i = 0; i < old_party_size; ++i) + { + /* exploit that splitscreen players keep order */ + if (splitscreen_party[playernum][i] == playernum) + { + before = i; + + views = splitscreen_original_party_size[playernum]; + after = ( before + views ); + + memcpy(old_party, splitscreen_party[playernum], sizeof old_party); + memcpy(new_party, old_party, before * sizeof *old_party); + + while (i < after) + { + splitscreen_partied[old_party[i]] = false; + + i++; + } + + memcpy(&new_party[before], &old_party[after], + ( old_party_size - after ) * sizeof *new_party); + + views = ( old_party_size - views ); + + for (i = 0; i < old_party_size; ++i) + { + n = old_party[i]; + if (n != playernum && playerconsole[n] == n) + { + splitscreen_party_size[n] = views; + memcpy(splitscreen_party[n], new_party, + sizeof splitscreen_party[n]); + } + } + + ResetParty(playernum); + + if (playernum != consoleplayer && splitscreen_partied[playernum]) + { + splitscreen_partied[playernum] = false; + + for (i = 0; i < views; ++i) + { + displayplayers[i] = new_party[i]; + } + while (i < MAXSPLITSCREENPLAYERS) + { + displayplayers[i] = consoleplayer; + + i++; + } + + r_splitscreen = ( views - 1 ); + + R_ExecuteSetViewSize(); + } + + break; + } + } +} + +void +G_AddPartyMember (int invitation, int playernum) +{ + int * party; + int *add_party; + + int old_party_size; + int new_party_size; + + int views; + + int i; + int n; + + views = splitscreen_original_party_size[playernum]; + + old_party_size = splitscreen_party_size[invitation]; + new_party_size = ( old_party_size + views ); + + party = splitscreen_party[invitation]; + add_party = splitscreen_original_party[playernum]; + + for (i = 0; i < old_party_size; ++i) + { + n = party[i]; + if (playerconsole[n] == n) + { + splitscreen_party_size[n] = new_party_size; + memcpy(&splitscreen_party[n][old_party_size], add_party, + views * sizeof *splitscreen_party[n]); + } + } + + splitscreen_party_size[playernum] = new_party_size; + memcpy(splitscreen_party[playernum], party, + sizeof splitscreen_party[playernum]); + + /* in my party or adding me? */ + if (splitscreen_partied[invitation]) + { + for (i = old_party_size; i < new_party_size; ++i) + { + displayplayers[i] = party[i]; + } + + r_splitscreen += views; + + R_ExecuteSetViewSize(); + } + else if (playernum == consoleplayer) + { + for (i = 0; i <= splitscreen; ++i) + { + localdisplayplayers[i] = ( old_party_size + i ); + displayplayers[i] = party[i]; + } + while (++i < new_party_size) + { + displayplayers[i] = party[i]; + } + + r_splitscreen = ( new_party_size - 1 ); + + R_ExecuteSetViewSize(); + } +}