Merge branch 'keep-body' into 'next'

Let clients rejoin the server without losing their status

See merge request STJr/SRB2!722
This commit is contained in:
LJ Sonic 2020-01-24 18:50:03 -05:00
commit 53e2cfbde2
19 changed files with 356 additions and 221 deletions

View file

@ -1458,7 +1458,7 @@ static void Got_NetVar(UINT8 **p, INT32 playernum)
// not from server or remote admin, must be hacked/buggy client // not from server or remote admin, must be hacked/buggy client
CONS_Alert(CONS_WARNING, M_GetText("Illegal netvar command received from %s\n"), player_names[playernum]); CONS_Alert(CONS_WARNING, M_GetText("Illegal netvar command received from %s\n"), player_names[playernum]);
if (server) if (server)
SendKick(playernum, KICK_MSG_CON_FAIL); SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
return; return;
} }
netid = READUINT16(*p); netid = READUINT16(*p);

View file

@ -21,6 +21,7 @@
#include "d_net.h" #include "d_net.h"
#include "d_main.h" #include "d_main.h"
#include "g_game.h" #include "g_game.h"
#include "st_stuff.h"
#include "hu_stuff.h" #include "hu_stuff.h"
#include "keys.h" #include "keys.h"
#include "g_input.h" // JOY1 #include "g_input.h" // JOY1
@ -76,6 +77,7 @@ char motd[254], server_context[8]; // Message of the Day, Unique Context (even w
// Server specific vars // Server specific vars
UINT8 playernode[MAXPLAYERS]; UINT8 playernode[MAXPLAYERS];
char playeraddress[MAXPLAYERS][64];
// Minimum timeout for sending the savegame // Minimum timeout for sending the savegame
// The actual timeout will be longer depending on the savegame length // The actual timeout will be longer depending on the savegame length
@ -391,7 +393,7 @@ static void ExtraDataTicker(void)
{ {
if (server) if (server)
{ {
SendKick(i, KICK_MSG_CON_FAIL); SendKick(i, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
DEBFILE(va("player %d kicked [gametic=%u] reason as follows:\n", i, gametic)); 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]); CONS_Alert(CONS_WARNING, M_GetText("Got unknown net command [%s]=%d (max %d)\n"), sizeu1(curpos - bufferstart), *curpos, bufferstart[0]);
@ -437,6 +439,9 @@ void SendKick(UINT8 playernum, UINT8 msg)
{ {
UINT8 buf[2]; UINT8 buf[2];
if (!(server && cv_rejointimeout.value))
msg &= ~KICK_MSG_KEEP_BODY;
buf[0] = playernum; buf[0] = playernum;
buf[1] = msg; buf[1] = msg;
SendNetXCmd(XD_KICK, &buf, 2); SendNetXCmd(XD_KICK, &buf, 2);
@ -1058,7 +1063,7 @@ static void SV_SendResynch(INT32 node)
if (resynch_score[node] > (unsigned)cv_resynchattempts.value*250) if (resynch_score[node] > (unsigned)cv_resynchattempts.value*250)
{ {
SendKick(nodetoplayer[node], KICK_MSG_CON_FAIL); SendKick(nodetoplayer[node], KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
resynch_score[node] = 0; resynch_score[node] = 0;
} }
} }
@ -2413,6 +2418,7 @@ void CL_ClearPlayer(INT32 playernum)
if (players[playernum].mo) if (players[playernum].mo)
P_RemoveMobj(players[playernum].mo); P_RemoveMobj(players[playernum].mo);
memset(&players[playernum], 0, sizeof (player_t)); memset(&players[playernum], 0, sizeof (player_t));
memset(playeraddress[playernum], 0, sizeof(*playeraddress));
} }
// //
@ -2420,7 +2426,7 @@ void CL_ClearPlayer(INT32 playernum)
// //
// Removes a player from the current game // Removes a player from the current game
// //
static void CL_RemovePlayer(INT32 playernum, INT32 reason) static void CL_RemovePlayer(INT32 playernum, kickreason_t reason)
{ {
// Sanity check: exceptional cases (i.e. c-fails) can cause multiple // Sanity check: exceptional cases (i.e. c-fails) can cause multiple
// kick commands to be issued for the same player. // kick commands to be issued for the same player.
@ -2433,9 +2439,6 @@ static void CL_RemovePlayer(INT32 playernum, INT32 reason)
playerpernode[node]--; playerpernode[node]--;
if (playerpernode[node] <= 0) if (playerpernode[node] <= 0)
{ {
// If a resynch was in progress, well, it no longer needs to be.
SV_InitResynchVars(playernode[playernum]);
nodeingame[playernode[playernum]] = false; nodeingame[playernode[playernum]] = false;
Net_CloseConnection(playernode[playernum]); Net_CloseConnection(playernode[playernum]);
ResetNode(node); ResetNode(node);
@ -2831,9 +2834,12 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
char buf[3 + MAX_REASONLENGTH]; char buf[3 + MAX_REASONLENGTH];
char *reason = buf; char *reason = buf;
kickreason_t kickreason = KR_KICK; kickreason_t kickreason = KR_KICK;
boolean keepbody;
pnum = READUINT8(*p); pnum = READUINT8(*p);
msg = READUINT8(*p); msg = READUINT8(*p);
keepbody = (msg & KICK_MSG_KEEP_BODY) != 0;
msg &= ~KICK_MSG_KEEP_BODY;
if (pnum == serverplayer && IsPlayerAdmin(playernum)) if (pnum == serverplayer && IsPlayerAdmin(playernum))
{ {
@ -2893,6 +2899,7 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
*/ */
pnum = playernum; pnum = playernum;
msg = KICK_MSG_CON_FAIL; msg = KICK_MSG_CON_FAIL;
keepbody = true;
} }
//CONS_Printf("\x82%s ", player_names[pnum]); //CONS_Printf("\x82%s ", player_names[pnum]);
@ -2912,7 +2919,8 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
switch (msg) switch (msg)
{ {
case KICK_MSG_GO_AWAY: case KICK_MSG_GO_AWAY:
HU_AddChatText(va("\x82*%s has been kicked (Go away)", player_names[pnum]), false); if (!players[pnum].quittime)
HU_AddChatText(va("\x82*%s has been kicked (Go away)", player_names[pnum]), false);
kickreason = KR_KICK; kickreason = KR_KICK;
break; break;
case KICK_MSG_PING_HIGH: case KICK_MSG_PING_HIGH:
@ -2961,7 +2969,7 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
kickreason = KR_TIMEOUT; kickreason = KR_TIMEOUT;
break; break;
case KICK_MSG_PLAYER_QUIT: case KICK_MSG_PLAYER_QUIT:
if (netgame) // not splitscreen/bots if (netgame && !players[pnum].quittime) // not splitscreen/bots or soulless body
HU_AddChatText(va("\x82*%s left the game", player_names[pnum]), false); HU_AddChatText(va("\x82*%s left the game", player_names[pnum]), false);
kickreason = KR_LEAVE; kickreason = KR_LEAVE;
break; break;
@ -3002,6 +3010,24 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
else else
M_StartMessage(M_GetText("You have been kicked by the server\n\nPress ESC\n"), NULL, MM_NOTHING); M_StartMessage(M_GetText("You have been kicked by the server\n\nPress ESC\n"), NULL, MM_NOTHING);
} }
else if (keepbody)
{
if (server && !demoplayback)
{
INT32 node = playernode[pnum];
playerpernode[node]--;
if (playerpernode[node] <= 0)
{
nodeingame[node] = false;
Net_CloseConnection(node);
ResetNode(node);
}
}
playernode[pnum] = UINT8_MAX;
players[pnum].quittime = 1;
}
else else
CL_RemovePlayer(pnum, kickreason); CL_RemovePlayer(pnum, kickreason);
} }
@ -3010,6 +3036,9 @@ consvar_t cv_allownewplayer = {"allowjoin", "On", CV_NETVAR, CV_OnOff, NULL, 0,
consvar_t cv_joinnextround = {"joinnextround", "Off", CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; /// \todo not done consvar_t cv_joinnextround = {"joinnextround", "Off", CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; /// \todo not done
static CV_PossibleValue_t maxplayers_cons_t[] = {{2, "MIN"}, {32, "MAX"}, {0, NULL}}; static CV_PossibleValue_t maxplayers_cons_t[] = {{2, "MIN"}, {32, "MAX"}, {0, NULL}};
consvar_t cv_maxplayers = {"maxplayers", "8", CV_SAVE, maxplayers_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_maxplayers = {"maxplayers", "8", CV_SAVE, maxplayers_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
static CV_PossibleValue_t rejointimeout_cons_t[] = {{1, "MIN"}, {60 * FRACUNIT, "MAX"}, {0, "Off"}, {0, NULL}};
consvar_t cv_rejointimeout = {"rejointimeout", "Off", CV_SAVE|CV_FLOAT, rejointimeout_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
static CV_PossibleValue_t resynchattempts_cons_t[] = {{1, "MIN"}, {20, "MAX"}, {0, "No"}, {0, NULL}}; static CV_PossibleValue_t resynchattempts_cons_t[] = {{1, "MIN"}, {20, "MAX"}, {0, "No"}, {0, NULL}};
consvar_t cv_resynchattempts = {"resynchattempts", "10", CV_SAVE, resynchattempts_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL }; consvar_t cv_resynchattempts = {"resynchattempts", "10", CV_SAVE, resynchattempts_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL };
consvar_t cv_blamecfail = {"blamecfail", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL }; consvar_t cv_blamecfail = {"blamecfail", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL };
@ -3079,6 +3108,7 @@ static void ResetNode(INT32 node)
nodewaiting[node] = 0; nodewaiting[node] = 0;
playerpernode[node] = 0; playerpernode[node] = 0;
sendingsavegame[node] = false; sendingsavegame[node] = false;
SV_InitResynchVars(node);
} }
void SV_ResetServer(void) void SV_ResetServer(void)
@ -3094,13 +3124,8 @@ void SV_ResetServer(void)
tictoclear = maketic; tictoclear = maketic;
for (i = 0; i < MAXNETNODES; i++) for (i = 0; i < MAXNETNODES; i++)
{
ResetNode(i); ResetNode(i);
// Make sure resynch status doesn't get carried over!
SV_InitResynchVars(i);
}
for (i = 0; i < MAXPLAYERS; i++) for (i = 0; i < MAXPLAYERS; i++)
{ {
#ifdef HAVE_BLUA #ifdef HAVE_BLUA
@ -3108,6 +3133,7 @@ void SV_ResetServer(void)
#endif #endif
playeringame[i] = false; playeringame[i] = false;
playernode[i] = UINT8_MAX; playernode[i] = UINT8_MAX;
memset(playeraddress[i], 0, sizeof(*playeraddress));
sprintf(player_names[i], "Player %d", i + 1); sprintf(player_names[i], "Player %d", i + 1);
adminplayers[i] = -1; // Populate the entire adminplayers array with -1. adminplayers[i] = -1; // Populate the entire adminplayers array with -1.
} }
@ -3198,6 +3224,37 @@ void D_QuitNetGame(void)
#endif #endif
} }
static INT32 FindRejoinerNum(SINT8 node)
{
char strippednodeaddress[64];
const char *nodeaddress;
char *port;
INT32 i;
// Make sure there is no dead dress before proceeding to the stripping
if (!I_GetNodeAddress)
return -1;
nodeaddress = I_GetNodeAddress(node);
if (!nodeaddress)
return -1;
// Strip the address of its port
strcpy(strippednodeaddress, nodeaddress);
port = strchr(strippednodeaddress, ':');
if (port)
*port = '\0';
// Check if any player matches the stripped address
for (i = 0; i < MAXPLAYERS; i++)
{
if (playeringame[i] && playeraddress[i][0] && playernode[i] == UINT8_MAX
&& !strcmp(playeraddress[i], strippednodeaddress))
return i;
}
return -1;
}
// Adds a node to the game (player will follow at map change or at savegame....) // Adds a node to the game (player will follow at map change or at savegame....)
static inline void SV_AddNode(INT32 node) static inline void SV_AddNode(INT32 node)
{ {
@ -3214,13 +3271,16 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum)
{ {
INT16 node, newplayernum; INT16 node, newplayernum;
boolean splitscreenplayer; boolean splitscreenplayer;
boolean rejoined;
player_t *newplayer;
char *port;
if (playernum != serverplayer && !IsPlayerAdmin(playernum)) if (playernum != serverplayer && !IsPlayerAdmin(playernum))
{ {
// protect against hacked/buggy client // protect against hacked/buggy client
CONS_Alert(CONS_WARNING, M_GetText("Illegal add player command received from %s\n"), player_names[playernum]); CONS_Alert(CONS_WARNING, M_GetText("Illegal add player command received from %s\n"), player_names[playernum]);
if (server) if (server)
SendKick(playernum, KICK_MSG_CON_FAIL); SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
return; return;
} }
@ -3229,15 +3289,34 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum)
splitscreenplayer = newplayernum & 0x80; splitscreenplayer = newplayernum & 0x80;
newplayernum &= ~0x80; newplayernum &= ~0x80;
// Clear player before joining, lest some things get set incorrectly rejoined = playeringame[newplayernum];
// HACK: don't do this for splitscreen, it relies on preset values
if (!splitscreen && !botingame) if (!rejoined)
CL_ClearPlayer(newplayernum); {
playeringame[newplayernum] = true; // Clear player before joining, lest some things get set incorrectly
// HACK: don't do this for splitscreen, it relies on preset values
if (!splitscreen && !botingame)
CL_ClearPlayer(newplayernum);
playeringame[newplayernum] = true;
G_AddPlayer(newplayernum);
if (newplayernum+1 > doomcom->numslots)
doomcom->numslots = (INT16)(newplayernum+1);
if (server && I_GetNodeAddress)
{
strcpy(playeraddress[newplayernum], I_GetNodeAddress(node));
port = strchr(playeraddress[newplayernum], ':');
if (port)
*port = '\0';
}
}
newplayer = &players[newplayernum];
newplayer->jointime = 0;
newplayer->quittime = 0;
READSTRINGN(*p, player_names[newplayernum], MAXPLAYERNAME); READSTRINGN(*p, player_names[newplayernum], MAXPLAYERNAME);
G_AddPlayer(newplayernum);
if (newplayernum+1 > doomcom->numslots)
doomcom->numslots = (INT16)(newplayernum+1);
// the server is creating my player // the server is creating my player
if (node == mynode) if (node == mynode)
@ -3255,29 +3334,67 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum)
secondarydisplayplayer = newplayernum; secondarydisplayplayer = newplayernum;
DEBFILE("spawning my brother\n"); DEBFILE("spawning my brother\n");
if (botingame) if (botingame)
players[newplayernum].bot = 1; newplayer->bot = 1;
} }
D_SendPlayerConfig(); D_SendPlayerConfig();
addedtogame = true; addedtogame = true;
if (rejoined)
{
if (newplayer->mo)
{
if (!splitscreenplayer)
localangle = newplayer->mo->angle;
else
localangle2 = newplayer->mo->angle;
newplayer->viewheight = 41*newplayer->height/48;
if (newplayer->mo->eflags & MFE_VERTICALFLIP)
newplayer->viewz = newplayer->mo->z + newplayer->mo->height - newplayer->viewheight;
else
newplayer->viewz = newplayer->mo->z + newplayer->viewheight;
}
// wake up the status bar
ST_Start();
// wake up the heads up text
HU_Start();
if (camera.chase && !splitscreenplayer)
P_ResetCamera(newplayer, &camera);
if (camera2.chase && splitscreenplayer)
P_ResetCamera(newplayer, &camera2);
}
} }
if (netgame) if (netgame)
{ {
if (server && cv_showjoinaddress.value) char joinmsg[256];
{
const char *address; if (rejoined)
if (I_GetNodeAddress && (address = I_GetNodeAddress(node)) != NULL) strcpy(joinmsg, M_GetText("\x82*%s has rejoined the game (player %d)"));
HU_AddChatText(va("\x82*%s has joined the game (player %d) (%s)", player_names[newplayernum], newplayernum, address), false); // merge join notification + IP to avoid clogging console/chat.
}
else else
HU_AddChatText(va("\x82*%s has joined the game (player %d)", player_names[newplayernum], newplayernum), false); // if you don't wanna see the join address. strcpy(joinmsg, M_GetText("\x82*%s has joined the game (player %d)"));
strcpy(joinmsg, va(joinmsg, player_names[newplayernum], newplayernum));
// Merge join notification + IP to avoid clogging console/chat
if (server && cv_showjoinaddress.value && I_GetNodeAddress)
{
const char *address = I_GetNodeAddress(node);
if (address)
strcat(joinmsg, va(" (%s)", address));
}
HU_AddChatText(joinmsg, false);
} }
if (server && multiplayer && motd[0] != '\0') if (server && multiplayer && motd[0] != '\0')
COM_BufAddText(va("sayto %d %s\n", newplayernum, motd)); COM_BufAddText(va("sayto %d %s\n", newplayernum, motd));
#ifdef HAVE_BLUA #ifdef HAVE_BLUA
LUAh_PlayerJoin(newplayernum); if (!rejoined)
LUAh_PlayerJoin(newplayernum);
#endif #endif
} }
@ -3286,11 +3403,7 @@ static boolean SV_AddWaitingPlayers(const char *name, const char *name2)
INT32 node, n, newplayer = false; INT32 node, n, newplayer = false;
UINT8 buf[2 + MAXPLAYERNAME]; UINT8 buf[2 + MAXPLAYERNAME];
UINT8 *p; UINT8 *p;
UINT8 newplayernum = 0; INT32 newplayernum;
// What is the reason for this? Why can't newplayernum always be 0?
if (dedicated)
newplayernum = 1;
for (node = 0; node < MAXNETNODES; node++) for (node = 0; node < MAXNETNODES; node++)
{ {
@ -3299,68 +3412,22 @@ static boolean SV_AddWaitingPlayers(const char *name, const char *name2)
{ {
newplayer = true; newplayer = true;
if (netgame) newplayernum = FindRejoinerNum(node);
// !!!!!!!!! EXTREMELY SUPER MEGA GIGA ULTRA ULTIMATELY TERRIBLY IMPORTANT !!!!!!!!! if (newplayernum == -1)
// {
// The line just after that comment is an awful, horrible, terrible, TERRIBLE hack.
//
// Basically, the fix I did in order to fix the download freezes happens
// to cause situations in which a player number does not match
// the node number associated to that player.
// That is totally normal, there is absolutely *nothing* wrong with that.
// Really. Player 7 being tied to node 29, for instance, is totally fine.
//
// HOWEVER. A few (broken) parts of the netcode do the TERRIBLE mistake
// of mixing up the concepts of node and player, resulting in
// incorrect handling of cases where a player is tied to a node that has
// a different number (which is a totally normal case, or at least should be).
// This incorrect handling can go as far as literally
// anyone from joining your server at all, forever.
//
// Given those two facts, there are two options available
// in order to let this download freeze fix be:
// 1) Fix the broken parts that assume a node is a player or similar bullshit.
// 2) Change the part this comment is located at, so that any player who joins
// is given the same number as their associated node.
//
// No need to say, 1) is by far the obvious best, whereas 2) is a terrible hack.
// Unfortunately, after trying 1), I most likely didn't manage to find all
// of those broken parts, and thus 2) has become the only safe option that remains.
//
// So I did this hack.
//
// If it isn't clear enough, in order to get rid of this ugly hack,
// you will have to fix all parts of the netcode that
// make a confusion between nodes and players.
//
// And if it STILL isn't clear enough, a node and a player
// is NOT the same thing. Never. NEVER. *NEVER*.
//
// And if someday you make the terrible mistake of
// daring to have the unforgivable idea to try thinking
// that a node might possibly be the same as a player,
// or that a player should have the same number as its node,
// be sure that I will somehow know about it and
// hunt you down tirelessly and make you regret it,
// even if you live on the other side of the world.
//
// TODO: vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// \todo >>>>>>>>>> Remove this horrible hack as soon as possible <<<<<<<<<<
// TODO: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//
// !!!!!!!!! EXTREMELY SUPER MEGA GIGA ULTRA ULTIMATELY TERRIBLY IMPORTANT !!!!!!!!!
newplayernum = node; // OMFG SAY WELCOME TO TEH NEW HACK FOR FIX FIL DOWNLOAD!!1!
else // Don't use the hack if we don't have to
// search for a free playernum // search for a free playernum
// we can't use playeringame since it is not updated here // we can't use playeringame since it is not updated here
for (; newplayernum < MAXPLAYERS; newplayernum++) for (newplayernum = dedicated ? 1 : 0; newplayernum < MAXPLAYERS; newplayernum++)
{ {
if (playeringame[newplayernum])
continue;
for (n = 0; n < MAXNETNODES; n++) for (n = 0; n < MAXNETNODES; n++)
if (nodetoplayer[n] == newplayernum || nodetoplayer2[n] == newplayernum) if (nodetoplayer[n] == newplayernum || nodetoplayer2[n] == newplayernum)
break; break;
if (n == MAXNETNODES) if (n == MAXNETNODES)
break; break;
} }
}
// should never happen since we check the playernum // should never happen since we check the playernum
// before accepting the join // before accepting the join
@ -3387,8 +3454,6 @@ static boolean SV_AddWaitingPlayers(const char *name, const char *name2)
SendNetXCmd(XD_ADDPLAYER, &buf, p - buf); SendNetXCmd(XD_ADDPLAYER, &buf, p - buf);
DEBFILE(va("Server added player %d node %d\n", newplayernum, node)); DEBFILE(va("Server added player %d node %d\n", newplayernum, node));
// use the next free slot (we can't put playeringame[newplayernum] = true here)
newplayernum++;
} }
} }
@ -3514,8 +3579,11 @@ static size_t TotalTextCmdPerTic(tic_t tic)
static void HandleConnect(SINT8 node) static void HandleConnect(SINT8 node)
{ {
char names[MAXSPLITSCREENPLAYERS][MAXPLAYERNAME + 1]; char names[MAXSPLITSCREENPLAYERS][MAXPLAYERNAME + 1];
INT32 rejoinernum;
INT32 i; INT32 i;
rejoinernum = FindRejoinerNum(node);
if (bannednode && bannednode[node]) if (bannednode && bannednode[node])
SV_SendRefuse(node, M_GetText("You have been banned\nfrom the server")); SV_SendRefuse(node, M_GetText("You have been banned\nfrom the server"));
else if (netbuffer->u.clientcfg._255 != 255 || else if (netbuffer->u.clientcfg._255 != 255 ||
@ -3527,9 +3595,9 @@ static void HandleConnect(SINT8 node)
else if (netbuffer->u.clientcfg.version != VERSION else if (netbuffer->u.clientcfg.version != VERSION
|| netbuffer->u.clientcfg.subversion != SUBVERSION) || netbuffer->u.clientcfg.subversion != SUBVERSION)
SV_SendRefuse(node, va(M_GetText("Different SRB2 versions cannot\nplay a netgame!\n(server version %d.%d.%d)"), VERSION/100, VERSION%100, SUBVERSION)); SV_SendRefuse(node, va(M_GetText("Different SRB2 versions cannot\nplay a netgame!\n(server version %d.%d.%d)"), VERSION/100, VERSION%100, SUBVERSION));
else if (!cv_allownewplayer.value && node) else if (!cv_allownewplayer.value && node && rejoinernum == -1)
SV_SendRefuse(node, M_GetText("The server is not accepting\njoins for the moment")); SV_SendRefuse(node, M_GetText("The server is not accepting\njoins for the moment"));
else if (D_NumPlayers() >= cv_maxplayers.value) else if (D_NumPlayers() >= cv_maxplayers.value && rejoinernum == -1)
SV_SendRefuse(node, va(M_GetText("Maximum players reached: %d"), cv_maxplayers.value)); SV_SendRefuse(node, va(M_GetText("Maximum players reached: %d"), cv_maxplayers.value));
else if (netgame && netbuffer->u.clientcfg.localplayers > 1) // Hacked client? else if (netgame && netbuffer->u.clientcfg.localplayers > 1) // Hacked client?
SV_SendRefuse(node, M_GetText("Too many players from\nthis node.")); SV_SendRefuse(node, M_GetText("Too many players from\nthis node."));
@ -3544,7 +3612,7 @@ static void HandleConnect(SINT8 node)
for (i = 0; i < netbuffer->u.clientcfg.localplayers - playerpernode[node]; i++) for (i = 0; i < netbuffer->u.clientcfg.localplayers - playerpernode[node]; i++)
{ {
strlcpy(names[i], netbuffer->u.clientcfg.names[i], MAXPLAYERNAME + 1); strlcpy(names[i], netbuffer->u.clientcfg.names[i], MAXPLAYERNAME + 1);
if (!EnsurePlayerNameIsGood(names[i], -1)) if (!EnsurePlayerNameIsGood(names[i], rejoinernum))
{ {
SV_SendRefuse(node, "Bad player name"); SV_SendRefuse(node, "Bad player name");
return; return;
@ -3985,7 +4053,7 @@ static void HandlePacketFromPlayer(SINT8 node)
} }
else else
{ {
SendKick(netconsole, KICK_MSG_CON_FAIL); SendKick(netconsole, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
DEBFILE(va("player %d kicked (synch failure) [%u] %d!=%d\n", DEBFILE(va("player %d kicked (synch failure) [%u] %d!=%d\n",
netconsole, realstart, consistancy[realstart%BACKUPTICS], netconsole, realstart, consistancy[realstart%BACKUPTICS],
SHORT(netbuffer->u.clientpak.consistancy))); SHORT(netbuffer->u.clientpak.consistancy)));
@ -4104,6 +4172,7 @@ static void HandlePacketFromPlayer(SINT8 node)
kickmsg = KICK_MSG_TIMEOUT; kickmsg = KICK_MSG_TIMEOUT;
else else
kickmsg = KICK_MSG_PLAYER_QUIT; kickmsg = KICK_MSG_PLAYER_QUIT;
kickmsg |= KICK_MSG_KEEP_BODY;
SendKick(netconsole, kickmsg); SendKick(netconsole, kickmsg);
nodetoplayer[node] = -1; nodetoplayer[node] = -1;
@ -4125,7 +4194,7 @@ static void HandlePacketFromPlayer(SINT8 node)
{ {
CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_RESYNCHEND", node); CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_RESYNCHEND", node);
if (server) if (server)
SendKick(netconsole, KICK_MSG_CON_FAIL); SendKick(netconsole, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
break; break;
} }
resynch_local_inprogress = false; resynch_local_inprogress = false;
@ -4143,7 +4212,7 @@ static void HandlePacketFromPlayer(SINT8 node)
{ {
CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_SERVERTICS", node); CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_SERVERTICS", node);
if (server) if (server)
SendKick(netconsole, KICK_MSG_CON_FAIL); SendKick(netconsole, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
break; break;
} }
@ -4203,7 +4272,7 @@ static void HandlePacketFromPlayer(SINT8 node)
{ {
CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_RESYNCHING", node); CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_RESYNCHING", node);
if (server) if (server)
SendKick(netconsole, KICK_MSG_CON_FAIL); SendKick(netconsole, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
break; break;
} }
resynch_local_inprogress = true; resynch_local_inprogress = true;
@ -4215,7 +4284,7 @@ static void HandlePacketFromPlayer(SINT8 node)
{ {
CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_PING", node); CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_PING", node);
if (server) if (server)
SendKick(netconsole, KICK_MSG_CON_FAIL); SendKick(netconsole, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
break; break;
} }
@ -4239,7 +4308,7 @@ static void HandlePacketFromPlayer(SINT8 node)
{ {
CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_FILEFRAGMENT", node); CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_FILEFRAGMENT", node);
if (server) if (server)
SendKick(netconsole, KICK_MSG_CON_FAIL); SendKick(netconsole, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
break; break;
} }
if (client) if (client)
@ -4435,7 +4504,7 @@ static void CL_SendClientCmd(void)
packetsize = sizeof (clientcmd_pak) - sizeof (ticcmd_t) - sizeof (INT16); packetsize = sizeof (clientcmd_pak) - sizeof (ticcmd_t) - sizeof (INT16);
HSendPacket(servernode, false, 0, packetsize); HSendPacket(servernode, false, 0, packetsize);
} }
else if (gamestate != GS_NULL) else if (gamestate != GS_NULL && addedtogame)
{ {
G_MoveTiccmd(&netbuffer->u.clientpak.cmd, &localcmds, 1); G_MoveTiccmd(&netbuffer->u.clientpak.cmd, &localcmds, 1);
netbuffer->u.clientpak.consistancy = SHORT(consistancy[gametic%BACKUPTICS]); netbuffer->u.clientpak.consistancy = SHORT(consistancy[gametic%BACKUPTICS]);
@ -4611,6 +4680,11 @@ static void Local_Maketic(INT32 realtics)
localcmds.angleturn |= TICCMD_RECEIVED; localcmds.angleturn |= TICCMD_RECEIVED;
} }
// This function is utter bullshit and is responsible for
// the random desynch that happens when a player spawns.
// This is because ticcmds are resent to clients if a packet
// was dropped, and thus modifying them can lead to several
// clients having their ticcmds set to different values.
void SV_SpawnPlayer(INT32 playernum, INT32 x, INT32 y, angle_t angle) void SV_SpawnPlayer(INT32 playernum, INT32 x, INT32 y, angle_t angle)
{ {
tic_t tic; tic_t tic;
@ -4644,28 +4718,36 @@ void SV_SpawnPlayer(INT32 playernum, INT32 x, INT32 y, angle_t angle)
// create missed tic // create missed tic
static void SV_Maketic(void) static void SV_Maketic(void)
{ {
INT32 j; INT32 i;
for (j = 0; j < MAXNETNODES; j++) for (i = 0; i < MAXPLAYERS; i++)
if (playerpernode[j]) {
if (!playeringame[i])
continue;
// We didn't receive this tic
if ((netcmds[maketic % BACKUPTICS][i].angleturn & TICCMD_RECEIVED) == 0)
{ {
INT32 player = nodetoplayer[j]; ticcmd_t * ticcmd = &netcmds[(maketic ) % BACKUPTICS][i];
if ((netcmds[maketic%BACKUPTICS][player].angleturn & TICCMD_RECEIVED) == 0) ticcmd_t *prevticcmd = &netcmds[(maketic - 1) % BACKUPTICS][i];
{ // we didn't receive this tic
INT32 i;
DEBFILE(va("MISS tic%4d for node %d\n", maketic, j)); if (players[i].quittime)
#if defined(PARANOIA) && 0 {
CONS_Debug(DBG_NETPLAY, "Client Misstic %d\n", maketic); // Copy the angle/aiming from the previous tic
#endif // and empty the other inputs
// copy the old tic memset(ticcmd, 0, sizeof(netcmds[0][0]));
for (i = 0; i < playerpernode[j]; i++, player = nodetoplayer2[j]) ticcmd->angleturn = prevticcmd->angleturn | TICCMD_RECEIVED;
{ ticcmd->aiming = prevticcmd->aiming;
netcmds[maketic%BACKUPTICS][player] = netcmds[(maketic-1)%BACKUPTICS][player]; }
netcmds[maketic%BACKUPTICS][player].angleturn &= ~TICCMD_RECEIVED; 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 // all tic are now proceed make the next
maketic++; maketic++;
@ -4787,7 +4869,7 @@ static inline void PingUpdate(void)
// ok your net has been bad for too long, you deserve to die. // ok your net has been bad for too long, you deserve to die.
{ {
pingtimeout[i] = 0; pingtimeout[i] = 0;
SendKick(i, KICK_MSG_PING_HIGH); SendKick(i, KICK_MSG_PING_HIGH | KICK_MSG_KEEP_BODY);
} }
} }
/* /*

View file

@ -485,6 +485,7 @@ extern consvar_t cv_playbackspeed;
#define KICK_MSG_PING_HIGH 6 #define KICK_MSG_PING_HIGH 6
#define KICK_MSG_CUSTOM_KICK 7 #define KICK_MSG_CUSTOM_KICK 7
#define KICK_MSG_CUSTOM_BAN 8 #define KICK_MSG_CUSTOM_BAN 8
#define KICK_MSG_KEEP_BODY 0x80
typedef enum typedef enum
{ {
@ -512,7 +513,9 @@ extern UINT32 realpingtable[MAXPLAYERS];
extern UINT32 playerpingtable[MAXPLAYERS]; extern UINT32 playerpingtable[MAXPLAYERS];
extern tic_t servermaxping; extern tic_t servermaxping;
extern consvar_t cv_joinnextround, cv_allownewplayer, cv_maxplayers, cv_resynchattempts, cv_blamecfail, cv_maxsend, cv_noticedownload, cv_downloadspeed; extern consvar_t cv_allownewplayer, cv_joinnextround, cv_maxplayers, cv_rejointimeout;
extern consvar_t cv_resynchattempts, cv_blamecfail;
extern consvar_t cv_maxsend, cv_noticedownload, cv_downloadspeed;
// Used in d_net, the only dependence // Used in d_net, the only dependence
tic_t ExpandTics(INT32 low); tic_t ExpandTics(INT32 low);

View file

@ -579,6 +579,7 @@ void D_RegisterServerCommands(void)
// d_clisrv // d_clisrv
CV_RegisterVar(&cv_maxplayers); CV_RegisterVar(&cv_maxplayers);
CV_RegisterVar(&cv_rejointimeout);
CV_RegisterVar(&cv_resynchattempts); CV_RegisterVar(&cv_resynchattempts);
CV_RegisterVar(&cv_maxsend); CV_RegisterVar(&cv_maxsend);
CV_RegisterVar(&cv_noticedownload); CV_RegisterVar(&cv_noticedownload);
@ -1124,7 +1125,7 @@ static void SetPlayerName(INT32 playernum, char *newname)
{ {
CONS_Printf(M_GetText("Player %d sent a bad name change\n"), playernum+1); CONS_Printf(M_GetText("Player %d sent a bad name change\n"), playernum+1);
if (server && netgame) if (server && netgame)
SendKick(playernum, KICK_MSG_CON_FAIL); SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
} }
} }
@ -1482,7 +1483,7 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum)
if (kick) if (kick)
{ {
CONS_Alert(CONS_WARNING, M_GetText("Illegal color change received from %s (team: %d), color: %d)\n"), player_names[playernum], p->ctfteam, p->skincolor); CONS_Alert(CONS_WARNING, M_GetText("Illegal color change received from %s (team: %d), color: %d)\n"), player_names[playernum], p->ctfteam, p->skincolor);
SendKick(playernum, KICK_MSG_CON_FAIL); SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
return; return;
} }
} }
@ -2022,7 +2023,7 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum)
{ {
CONS_Alert(CONS_WARNING, M_GetText("Illegal map change received from %s\n"), player_names[playernum]); CONS_Alert(CONS_WARNING, M_GetText("Illegal map change received from %s\n"), player_names[playernum]);
if (server) if (server)
SendKick(playernum, KICK_MSG_CON_FAIL); SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
return; return;
} }
@ -2133,7 +2134,7 @@ static void Got_Pause(UINT8 **cp, INT32 playernum)
{ {
CONS_Alert(CONS_WARNING, M_GetText("Illegal pause command received from %s\n"), player_names[playernum]); CONS_Alert(CONS_WARNING, M_GetText("Illegal pause command received from %s\n"), player_names[playernum]);
if (server) if (server)
SendKick(playernum, KICK_MSG_CON_FAIL); SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
return; return;
} }
@ -2208,7 +2209,7 @@ static void Got_Suicide(UINT8 **cp, INT32 playernum)
{ {
CONS_Alert(CONS_WARNING, M_GetText("Illegal suicide command received from %s\n"), player_names[playernum]); CONS_Alert(CONS_WARNING, M_GetText("Illegal suicide command received from %s\n"), player_names[playernum]);
if (server) if (server)
SendKick(playernum, KICK_MSG_CON_FAIL); SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
return; return;
} }
@ -2271,7 +2272,7 @@ static void Got_Clearscores(UINT8 **cp, INT32 playernum)
{ {
CONS_Alert(CONS_WARNING, M_GetText("Illegal clear scores command received from %s\n"), player_names[playernum]); CONS_Alert(CONS_WARNING, M_GetText("Illegal clear scores command received from %s\n"), player_names[playernum]);
if (server) if (server)
SendKick(playernum, KICK_MSG_CON_FAIL); SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
return; return;
} }
@ -2618,7 +2619,7 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum)
// this should never happen unless the client is hacked/buggy // this should never happen unless the client is hacked/buggy
CONS_Alert(CONS_WARNING, M_GetText("Illegal team change received from player %s\n"), player_names[playernum]); CONS_Alert(CONS_WARNING, M_GetText("Illegal team change received from player %s\n"), player_names[playernum]);
if (server) if (server)
SendKick(playernum, KICK_MSG_CON_FAIL); SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
} }
if (NetPacket.packet.verification) // Special marker that the server sent the request if (NetPacket.packet.verification) // Special marker that the server sent the request
@ -2627,7 +2628,7 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum)
{ {
CONS_Alert(CONS_WARNING, M_GetText("Illegal team change received from player %s\n"), player_names[playernum]); CONS_Alert(CONS_WARNING, M_GetText("Illegal team change received from player %s\n"), player_names[playernum]);
if (server) if (server)
SendKick(playernum, KICK_MSG_CON_FAIL); SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
return; return;
} }
playernum = NetPacket.packet.playernum; playernum = NetPacket.packet.playernum;
@ -2660,7 +2661,7 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum)
{ {
CONS_Alert(CONS_WARNING, M_GetText("Illegal team change received from player %s\n"), player_names[playernum]); CONS_Alert(CONS_WARNING, M_GetText("Illegal team change received from player %s\n"), player_names[playernum]);
if (server) if (server)
SendKick(playernum, KICK_MSG_CON_FAIL); SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
} }
return; return;
} }
@ -2719,7 +2720,7 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum)
if (server && ((NetPacket.packet.newteam < 0 || NetPacket.packet.newteam > 3) || error)) if (server && ((NetPacket.packet.newteam < 0 || NetPacket.packet.newteam > 3) || error))
{ {
CONS_Alert(CONS_WARNING, M_GetText("Illegal team change received from player %s\n"), player_names[playernum]); CONS_Alert(CONS_WARNING, M_GetText("Illegal team change received from player %s\n"), player_names[playernum]);
SendKick(playernum, KICK_MSG_CON_FAIL); SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
} }
//Safety first! //Safety first!
@ -3011,7 +3012,7 @@ static void Got_Verification(UINT8 **cp, INT32 playernum)
{ {
CONS_Alert(CONS_WARNING, M_GetText("Illegal verification received from %s (serverplayer is %s)\n"), player_names[playernum], player_names[serverplayer]); CONS_Alert(CONS_WARNING, M_GetText("Illegal verification received from %s (serverplayer is %s)\n"), player_names[playernum], player_names[serverplayer]);
if (server) if (server)
SendKick(playernum, KICK_MSG_CON_FAIL); SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
return; return;
} }
@ -3061,7 +3062,7 @@ static void Got_Removal(UINT8 **cp, INT32 playernum)
{ {
CONS_Alert(CONS_WARNING, M_GetText("Illegal demotion received from %s (serverplayer is %s)\n"), player_names[playernum], player_names[serverplayer]); CONS_Alert(CONS_WARNING, M_GetText("Illegal demotion received from %s (serverplayer is %s)\n"), player_names[playernum], player_names[serverplayer]);
if (server) if (server)
SendKick(playernum, KICK_MSG_CON_FAIL); SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
return; return;
} }
@ -3135,7 +3136,7 @@ static void Got_MotD_f(UINT8 **cp, INT32 playernum)
{ {
CONS_Alert(CONS_WARNING, M_GetText("Illegal motd change received from %s\n"), player_names[playernum]); CONS_Alert(CONS_WARNING, M_GetText("Illegal motd change received from %s\n"), player_names[playernum]);
if (server) if (server)
SendKick(playernum, KICK_MSG_CON_FAIL); SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
Z_Free(mymotd); Z_Free(mymotd);
return; return;
} }
@ -3191,7 +3192,7 @@ static void Got_RunSOCcmd(UINT8 **cp, INT32 playernum)
{ {
CONS_Alert(CONS_WARNING, M_GetText("Illegal runsoc command received from %s\n"), player_names[playernum]); CONS_Alert(CONS_WARNING, M_GetText("Illegal runsoc command received from %s\n"), player_names[playernum]);
if (server) if (server)
SendKick(playernum, KICK_MSG_CON_FAIL); SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
return; return;
} }
@ -3349,7 +3350,7 @@ static void Got_RequestAddfilecmd(UINT8 **cp, INT32 playernum)
if ((playernum != serverplayer && !IsPlayerAdmin(playernum)) || kick) if ((playernum != serverplayer && !IsPlayerAdmin(playernum)) || kick)
{ {
CONS_Alert(CONS_WARNING, M_GetText("Illegal addfile command received from %s\n"), player_names[playernum]); CONS_Alert(CONS_WARNING, M_GetText("Illegal addfile command received from %s\n"), player_names[playernum]);
SendKick(playernum, KICK_MSG_CON_FAIL); SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
return; return;
} }
@ -3398,7 +3399,7 @@ static void Got_Addfilecmd(UINT8 **cp, INT32 playernum)
{ {
CONS_Alert(CONS_WARNING, M_GetText("Illegal addfile command received from %s\n"), player_names[playernum]); CONS_Alert(CONS_WARNING, M_GetText("Illegal addfile command received from %s\n"), player_names[playernum]);
if (server) if (server)
SendKick(playernum, KICK_MSG_CON_FAIL); SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
return; return;
} }
@ -4191,7 +4192,7 @@ static void Got_ExitLevelcmd(UINT8 **cp, INT32 playernum)
{ {
CONS_Alert(CONS_WARNING, M_GetText("Illegal exitlevel command received from %s\n"), player_names[playernum]); CONS_Alert(CONS_WARNING, M_GetText("Illegal exitlevel command received from %s\n"), player_names[playernum]);
if (server) if (server)
SendKick(playernum, KICK_MSG_CON_FAIL); SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
return; return;
} }

View file

@ -510,6 +510,7 @@ typedef struct player_s
UINT8 bot; UINT8 bot;
tic_t jointime; // Timer when player joins game to change skin/color tic_t jointime; // Timer when player joins game to change skin/color
tic_t quittime; // Time elapsed since user disconnected, zero if connected
#ifdef HWRENDER #ifdef HWRENDER
fixed_t fovadd; // adjust FOV for hw rendering fixed_t fovadd; // adjust FOV for hw rendering
#endif #endif

View file

@ -2414,6 +2414,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
INT32 skin; INT32 skin;
UINT32 availabilities; UINT32 availabilities;
tic_t jointime; tic_t jointime;
tic_t quittime;
boolean spectator; boolean spectator;
boolean outofcoop; boolean outofcoop;
INT16 bot; INT16 bot;
@ -2427,6 +2428,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
ctfteam = players[player].ctfteam; ctfteam = players[player].ctfteam;
exiting = players[player].exiting; exiting = players[player].exiting;
jointime = players[player].jointime; jointime = players[player].jointime;
quittime = players[player].quittime;
spectator = players[player].spectator; spectator = players[player].spectator;
outofcoop = players[player].outofcoop; outofcoop = players[player].outofcoop;
pflags = (players[player].pflags & (PF_FLIPCAM|PF_ANALOGMODE|PF_DIRECTIONCHAR|PF_AUTOBRAKE|PF_TAGIT|PF_GAMETYPEOVER)); pflags = (players[player].pflags & (PF_FLIPCAM|PF_ANALOGMODE|PF_DIRECTIONCHAR|PF_AUTOBRAKE|PF_TAGIT|PF_GAMETYPEOVER));
@ -2498,6 +2500,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
p->pflags = pflags; p->pflags = pflags;
p->ctfteam = ctfteam; p->ctfteam = ctfteam;
p->jointime = jointime; p->jointime = jointime;
p->quittime = quittime;
p->spectator = spectator; p->spectator = spectator;
p->outofcoop = outofcoop; p->outofcoop = outofcoop;
@ -2651,73 +2654,24 @@ static boolean G_CheckSpot(INT32 playernum, mapthing_t *mthing)
// or a not-so-appropriate spot, if it initially fails // or a not-so-appropriate spot, if it initially fails
// due to a lack of starts open or something. // due to a lack of starts open or something.
// //
void G_SpawnPlayer(INT32 playernum, boolean starpost) void G_SpawnPlayer(INT32 playernum)
{ {
mapthing_t *spawnpoint;
if (!playeringame[playernum]) if (!playeringame[playernum])
return; return;
P_SpawnPlayer(playernum); P_SpawnPlayer(playernum);
G_MovePlayerToSpawnOrStarpost(playernum);
if (starpost) //Don't even bother with looking for a place to spawn.
{
P_MovePlayerToStarpost(playernum);
#ifdef HAVE_BLUA
LUAh_PlayerSpawn(&players[playernum]); // Lua hook for player spawning :)
#endif
return;
}
// -- CTF --
// Order: CTF->DM->Coop
if ((gametyperules & (GTR_TEAMFLAGS|GTR_TEAMS)) && players[playernum].ctfteam)
{
if (!(spawnpoint = G_FindCTFStart(playernum)) // find a CTF start
&& !(spawnpoint = G_FindMatchStart(playernum))) // find a DM start
spawnpoint = G_FindCoopStart(playernum); // fallback
}
// -- DM/Tag/CTF-spectator/etc --
// Order: DM->CTF->Coop
else if ((gametyperules & GTR_DEATHMATCHSTARTS) && !(players[playernum].pflags & PF_TAGIT))
{
if (!(spawnpoint = G_FindMatchStart(playernum)) // find a DM start
&& !(spawnpoint = G_FindCTFStart(playernum))) // find a CTF start
spawnpoint = G_FindCoopStart(playernum); // fallback
}
// -- Other game modes --
// Order: Coop->DM->CTF
else
{
if (!(spawnpoint = G_FindCoopStart(playernum)) // find a Co-op start
&& !(spawnpoint = G_FindMatchStart(playernum))) // find a DM start
spawnpoint = G_FindCTFStart(playernum); // fallback
}
//No spawns found. ANYWHERE.
if (!spawnpoint)
{
if (nummapthings)
{
if (playernum == consoleplayer || (splitscreen && playernum == secondarydisplayplayer))
CONS_Alert(CONS_ERROR, M_GetText("No player spawns found, spawning at the first mapthing!\n"));
spawnpoint = &mapthings[0];
}
else
{
if (playernum == consoleplayer || (splitscreen && playernum == secondarydisplayplayer))
CONS_Alert(CONS_ERROR, M_GetText("No player spawns found, spawning at the origin!\n"));
//P_MovePlayerToSpawn handles this fine if the spawnpoint is NULL.
}
}
P_MovePlayerToSpawn(playernum, spawnpoint);
#ifdef HAVE_BLUA #ifdef HAVE_BLUA
LUAh_PlayerSpawn(&players[playernum]); // Lua hook for player spawning :) LUAh_PlayerSpawn(&players[playernum]); // Lua hook for player spawning :)
#endif #endif
}
void G_MovePlayerToSpawnOrStarpost(INT32 playernum)
{
if (players[playernum].starposttime)
P_MovePlayerToStarpost(playernum);
else
P_MovePlayerToSpawn(playernum, G_FindMapStart(playernum));
} }
mapthing_t *G_FindCTFStart(INT32 playernum) mapthing_t *G_FindCTFStart(INT32 playernum)
@ -2814,6 +2768,59 @@ mapthing_t *G_FindCoopStart(INT32 playernum)
return NULL; return NULL;
} }
mapthing_t *G_FindMapStart(INT32 playernum)
{
mapthing_t *spawnpoint;
if (!playeringame[playernum])
return NULL;
// -- CTF --
// Order: CTF->DM->Coop
if ((gametyperules & (GTR_TEAMFLAGS|GTR_TEAMS)) && players[playernum].ctfteam)
{
if (!(spawnpoint = G_FindCTFStart(playernum)) // find a CTF start
&& !(spawnpoint = G_FindMatchStart(playernum))) // find a DM start
spawnpoint = G_FindCoopStart(playernum); // fallback
}
// -- DM/Tag/CTF-spectator/etc --
// Order: DM->CTF->Coop
else if ((gametyperules & GTR_DEATHMATCHSTARTS) && !(players[playernum].pflags & PF_TAGIT))
{
if (!(spawnpoint = G_FindMatchStart(playernum)) // find a DM start
&& !(spawnpoint = G_FindCTFStart(playernum))) // find a CTF start
spawnpoint = G_FindCoopStart(playernum); // fallback
}
// -- Other game modes --
// Order: Coop->DM->CTF
else
{
if (!(spawnpoint = G_FindCoopStart(playernum)) // find a Co-op start
&& !(spawnpoint = G_FindMatchStart(playernum))) // find a DM start
spawnpoint = G_FindCTFStart(playernum); // fallback
}
//No spawns found. ANYWHERE.
if (!spawnpoint)
{
if (nummapthings)
{
if (playernum == consoleplayer || (splitscreen && playernum == secondarydisplayplayer))
CONS_Alert(CONS_ERROR, M_GetText("No player spawns found, spawning at the first mapthing!\n"));
spawnpoint = &mapthings[0];
}
else
{
if (playernum == consoleplayer || (splitscreen && playernum == secondarydisplayplayer))
CONS_Alert(CONS_ERROR, M_GetText("No player spawns found, spawning at the origin!\n"));
}
}
return spawnpoint;
}
// Go back through all the projectiles and remove all references to the old // Go back through all the projectiles and remove all references to the old
// player mobj, replacing them with the new one. // player mobj, replacing them with the new one.
void G_ChangePlayerReferences(mobj_t *oldmo, mobj_t *newmo) void G_ChangePlayerReferences(mobj_t *oldmo, mobj_t *newmo)
@ -2992,7 +2999,7 @@ void G_DoReborn(INT32 playernum)
{ {
if (!playeringame[i]) if (!playeringame[i])
continue; continue;
G_SpawnPlayer(i, (players[i].starposttime)); G_SpawnPlayer(i);
} }
// restore time in netgame (see also p_setup.c) // restore time in netgame (see also p_setup.c)
@ -3038,7 +3045,7 @@ void G_DoReborn(INT32 playernum)
P_RemoveMobj(player->mo); P_RemoveMobj(player->mo);
} }
G_SpawnPlayer(playernum, (player->starposttime)); G_SpawnPlayer(playernum);
if (oldmo) if (oldmo)
G_ChangePlayerReferences(oldmo, players[playernum].mo); G_ChangePlayerReferences(oldmo, players[playernum].mo);
} }
@ -3080,7 +3087,6 @@ void G_AddPlayer(INT32 playernum)
} }
} }
p->jointime = 0;
p->playerstate = PST_REBORN; p->playerstate = PST_REBORN;
p->height = mobjinfo[MT_PLAYER].height; p->height = mobjinfo[MT_PLAYER].height;
@ -3103,6 +3109,8 @@ boolean G_EnoughPlayersFinished(void)
{ {
if (!playeringame[i] || players[i].spectator || players[i].bot) if (!playeringame[i] || players[i].spectator || players[i].bot)
continue; continue;
if (players[i].quittime > 30 * TICRATE)
continue;
if (players[i].lives <= 0) if (players[i].lives <= 0)
continue; continue;

View file

@ -161,7 +161,9 @@ INT32 G_FindMapByNameOrCode(const char *query, char **foundmapnamep);
mapthing_t *G_FindCTFStart(INT32 playernum); mapthing_t *G_FindCTFStart(INT32 playernum);
mapthing_t *G_FindMatchStart(INT32 playernum); mapthing_t *G_FindMatchStart(INT32 playernum);
mapthing_t *G_FindCoopStart(INT32 playernum); mapthing_t *G_FindCoopStart(INT32 playernum);
void G_SpawnPlayer(INT32 playernum, boolean starpost); mapthing_t *G_FindMapStart(INT32 playernum);
void G_MovePlayerToSpawnOrStarpost(INT32 playernum);
void G_SpawnPlayer(INT32 playernum);
// Can be called by the startup code or M_Responder. // Can be called by the startup code or M_Responder.
// A normal game starts at map 1, but a warp test can start elsewhere // A normal game starts at map 1, but a warp test can start elsewhere

View file

@ -654,7 +654,7 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum)
M_GetText("Illegal say command received from %s while muted\n") : M_GetText("Illegal csay command received from non-admin %s\n"), M_GetText("Illegal say command received from %s while muted\n") : M_GetText("Illegal csay command received from non-admin %s\n"),
player_names[playernum]); player_names[playernum]);
if (server) if (server)
SendKick(playernum, KICK_MSG_CON_FAIL); SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
return; return;
} }
@ -668,7 +668,7 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum)
{ {
CONS_Alert(CONS_WARNING, M_GetText("Illegal say command received from %s containing invalid characters\n"), player_names[playernum]); CONS_Alert(CONS_WARNING, M_GetText("Illegal say command received from %s containing invalid characters\n"), player_names[playernum]);
if (server) if (server)
SendKick(playernum, KICK_MSG_CON_FAIL); SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
return; return;
} }
} }
@ -2369,7 +2369,7 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I
if (!splitscreen) // don't draw it on splitscreen, if (!splitscreen) // don't draw it on splitscreen,
{ {
if (!(tab[i].num == serverplayer)) if (!(tab[i].num == serverplayer || players[tab[i].num].quittime))
HU_drawPing(x+ 253, y, playerpingtable[tab[i].num], false, 0); HU_drawPing(x+ 253, y, playerpingtable[tab[i].num], false, 0);
//else //else
// V_DrawSmallString(x+ 246, y+4, V_YELLOWMAP, "SERVER"); // V_DrawSmallString(x+ 246, y+4, V_YELLOWMAP, "SERVER");
@ -2568,7 +2568,7 @@ static void HU_Draw32TeamTabRankings(playersort_t *tab, INT32 whiteplayer)
V_DrawRightAlignedThinString(x+128, y, ((players[tab[i].num].spectator || players[tab[i].num].playerstate == PST_DEAD) ? 0 : V_TRANSLUCENT), va("%u", tab[i].count)); V_DrawRightAlignedThinString(x+128, y, ((players[tab[i].num].spectator || players[tab[i].num].playerstate == PST_DEAD) ? 0 : V_TRANSLUCENT), va("%u", tab[i].count));
if (!splitscreen) if (!splitscreen)
{ {
if (!(tab[i].num == serverplayer)) if (!(tab[i].num == serverplayer || players[tab[i].num].quittime))
HU_drawPing(x+ 135, y+1, playerpingtable[tab[i].num], true, 0); HU_drawPing(x+ 135, y+1, playerpingtable[tab[i].num], true, 0);
//else //else
//V_DrawSmallString(x+ 129, y+4, V_YELLOWMAP, "HOST"); //V_DrawSmallString(x+ 129, y+4, V_YELLOWMAP, "HOST");
@ -2692,7 +2692,7 @@ void HU_DrawTeamTabRankings(playersort_t *tab, INT32 whiteplayer)
V_DrawRightAlignedThinString(x+100, y, (greycheck ? V_TRANSLUCENT : 0), va("%u", tab[i].count)); V_DrawRightAlignedThinString(x+100, y, (greycheck ? V_TRANSLUCENT : 0), va("%u", tab[i].count));
if (!splitscreen) if (!splitscreen)
{ {
if (!(tab[i].num == serverplayer)) if (!(tab[i].num == serverplayer || players[tab[i].num].quittime))
HU_drawPing(x+ 113, y, playerpingtable[tab[i].num], false, 0); HU_drawPing(x+ 113, y, playerpingtable[tab[i].num], false, 0);
//else //else
// V_DrawSmallString(x+ 94, y+4, V_YELLOWMAP, "SERVER"); // V_DrawSmallString(x+ 94, y+4, V_YELLOWMAP, "SERVER");
@ -2723,7 +2723,7 @@ void HU_DrawDualTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scoreline
supercheck = supercheckdef; supercheck = supercheckdef;
strlcpy(name, tab[i].name, 7); strlcpy(name, tab[i].name, 7);
if (!(tab[i].num == serverplayer)) if (!(tab[i].num == serverplayer || players[tab[i].num].quittime))
HU_drawPing(x+ 113, y, playerpingtable[tab[i].num], false, 0); HU_drawPing(x+ 113, y, playerpingtable[tab[i].num], false, 0);
//else //else
// V_DrawSmallString(x+ 94, y+4, V_YELLOWMAP, "SERVER"); // V_DrawSmallString(x+ 94, y+4, V_YELLOWMAP, "SERVER");
@ -2831,7 +2831,7 @@ static void HU_Draw32TabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scor
strlcpy(name, tab[i].name, 7); strlcpy(name, tab[i].name, 7);
if (!splitscreen) // don't draw it on splitscreen, if (!splitscreen) // don't draw it on splitscreen,
{ {
if (!(tab[i].num == serverplayer)) if (!(tab[i].num == serverplayer || players[tab[i].num].quittime))
HU_drawPing(x+ 135, y+1, playerpingtable[tab[i].num], true, 0); HU_drawPing(x+ 135, y+1, playerpingtable[tab[i].num], true, 0);
//else //else
// V_DrawSmallString(x+ 129, y+4, V_YELLOWMAP, "HOST"); // V_DrawSmallString(x+ 129, y+4, V_YELLOWMAP, "HOST");

View file

@ -87,7 +87,7 @@ deny:
CONS_Alert(CONS_WARNING, M_GetText("Illegal lua command received from %s\n"), player_names[playernum]); CONS_Alert(CONS_WARNING, M_GetText("Illegal lua command received from %s\n"), player_names[playernum]);
if (server) if (server)
SendKick(playernum, KICK_MSG_CON_FAIL); SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
} }
// Wrapper for COM_AddCommand commands // Wrapper for COM_AddCommand commands

View file

@ -102,7 +102,7 @@ boolean LUAh_HurtMsg(player_t *player, mobj_t *inflictor, mobj_t *source, UINT8
boolean LUAh_MapThingSpawn(mobj_t *mo, mapthing_t *mthing); // Hook for P_SpawnMapThing by mobj type boolean LUAh_MapThingSpawn(mobj_t *mo, mapthing_t *mthing); // Hook for P_SpawnMapThing by mobj type
boolean LUAh_FollowMobj(player_t *player, mobj_t *mobj); // Hook for P_PlayerAfterThink Smiles mobj-following boolean LUAh_FollowMobj(player_t *player, mobj_t *mobj); // Hook for P_PlayerAfterThink Smiles mobj-following
UINT8 LUAh_PlayerCanDamage(player_t *player, mobj_t *mobj); // Hook for P_PlayerCanDamage UINT8 LUAh_PlayerCanDamage(player_t *player, mobj_t *mobj); // Hook for P_PlayerCanDamage
void LUAh_PlayerQuit(player_t *plr, int reason); // Hook for player quitting void LUAh_PlayerQuit(player_t *plr, kickreason_t reason); // Hook for player quitting
void LUAh_IntermissionThinker(void); // Hook for Y_Ticker void LUAh_IntermissionThinker(void); // Hook for Y_Ticker
boolean LUAh_TeamSwitch(player_t *player, int newteam, boolean fromspectators, boolean tryingautobalance, boolean tryingscramble); // Hook for team switching in... uh.... boolean LUAh_TeamSwitch(player_t *player, int newteam, boolean fromspectators, boolean tryingautobalance, boolean tryingscramble); // Hook for team switching in... uh....
UINT8 LUAh_ViewpointSwitch(player_t *player, player_t *newdisplayplayer, boolean forced); // Hook for spy mode UINT8 LUAh_ViewpointSwitch(player_t *player, player_t *newdisplayplayer, boolean forced); // Hook for spy mode

View file

@ -1439,7 +1439,7 @@ UINT8 LUAh_PlayerCanDamage(player_t *player, mobj_t *mobj)
return shouldCollide; return shouldCollide;
} }
void LUAh_PlayerQuit(player_t *plr, int reason) void LUAh_PlayerQuit(player_t *plr, kickreason_t reason)
{ {
hook_p hookp; hook_p hookp;
if (!gL || !(hooksAvailable[hook_PlayerQuit/8] & (1<<(hook_PlayerQuit%8)))) if (!gL || !(hooksAvailable[hook_PlayerQuit/8] & (1<<(hook_PlayerQuit%8))))

View file

@ -362,6 +362,8 @@ static int player_get(lua_State *L)
lua_pushinteger(L, plr->bot); lua_pushinteger(L, plr->bot);
else if (fastcmp(field,"jointime")) else if (fastcmp(field,"jointime"))
lua_pushinteger(L, plr->jointime); lua_pushinteger(L, plr->jointime);
else if (fastcmp(field,"quittime"))
lua_pushinteger(L, plr->quittime);
#ifdef HWRENDER #ifdef HWRENDER
else if (fastcmp(field,"fovadd")) else if (fastcmp(field,"fovadd"))
lua_pushfixed(L, plr->fovadd); lua_pushfixed(L, plr->fovadd);
@ -701,6 +703,8 @@ static int player_set(lua_State *L)
return NOSET; return NOSET;
else if (fastcmp(field,"jointime")) else if (fastcmp(field,"jointime"))
plr->jointime = (tic_t)luaL_checkinteger(L, 3); plr->jointime = (tic_t)luaL_checkinteger(L, 3);
else if (fastcmp(field,"quittime"))
plr->quittime = (tic_t)luaL_checkinteger(L, 3);
#ifdef HWRENDER #ifdef HWRENDER
else if (fastcmp(field,"fovadd")) else if (fastcmp(field,"fovadd"))
plr->fovadd = luaL_checkfixed(L, 3); plr->fovadd = luaL_checkfixed(L, 3);

View file

@ -746,6 +746,9 @@ boolean P_LookForPlayers(mobj_t *actor, boolean allaround, boolean tracer, fixed
if (player->bot) if (player->bot)
continue; // ignore bots continue; // ignore bots
if (player->quittime)
continue; // Ignore uncontrolled bodies
if (dist > 0 if (dist > 0
&& P_AproxDistance(P_AproxDistance(player->mo->x - actor->x, player->mo->y - actor->y), player->mo->z - actor->z) > dist) && P_AproxDistance(P_AproxDistance(player->mo->x - actor->x, player->mo->y - actor->y), player->mo->z - actor->z) > dist)
continue; // Too far away continue; // Too far away

View file

@ -2257,9 +2257,9 @@ void P_CheckSurvivors(void)
{ {
if (players[i].spectator) if (players[i].spectator)
spectators++; spectators++;
else if (players[i].pflags & PF_TAGIT) else if ((players[i].pflags & PF_TAGIT) && players[i].quittime < 30 * TICRATE)
taggers++; taggers++;
else if (!(players[i].pflags & PF_GAMETYPEOVER)) else if (!(players[i].pflags & PF_GAMETYPEOVER) && players[i].quittime < 30 * TICRATE)
{ {
survivorarray[survivors] = i; survivorarray[survivors] = i;
survivors++; survivors++;

View file

@ -255,6 +255,7 @@ static void P_NetArchivePlayers(void)
WRITEINT32(save_p, players[i].onconveyor); WRITEINT32(save_p, players[i].onconveyor);
WRITEUINT32(save_p, players[i].jointime); WRITEUINT32(save_p, players[i].jointime);
WRITEUINT32(save_p, players[i].quittime);
WRITEUINT16(save_p, flags); WRITEUINT16(save_p, flags);
@ -446,6 +447,7 @@ static void P_NetUnArchivePlayers(void)
players[i].onconveyor = READINT32(save_p); players[i].onconveyor = READINT32(save_p);
players[i].jointime = READUINT32(save_p); players[i].jointime = READUINT32(save_p);
players[i].quittime = READUINT32(save_p);
flags = READUINT16(save_p); flags = READUINT16(save_p);

View file

@ -3097,7 +3097,7 @@ static void P_InitTagGametype(void)
//Also, you'd never have to loop through all 32 players slots to find anything ever again. //Also, you'd never have to loop through all 32 players slots to find anything ever again.
for (i = 0; i < MAXPLAYERS; i++) for (i = 0; i < MAXPLAYERS; i++)
{ {
if (playeringame[i] && !players[i].spectator) if (playeringame[i] && !(players[i].spectator && players[i].quittime))
{ {
playersactive[realnumplayers] = i; //stores the player's node in the array. playersactive[realnumplayers] = i; //stores the player's node in the array.
realnumplayers++; realnumplayers++;
@ -3119,7 +3119,7 @@ static void P_InitTagGametype(void)
if (players[playersactive[i]].mo) if (players[playersactive[i]].mo)
P_RemoveMobj(players[playersactive[i]].mo); P_RemoveMobj(players[playersactive[i]].mo);
G_SpawnPlayer(playersactive[i], false); //respawn the lucky player in his dedicated spawn location. G_SpawnPlayer(playersactive[i]); //respawn the lucky player in his dedicated spawn location.
} }
static void P_SetupCamera(void) static void P_SetupCamera(void)
@ -3297,7 +3297,7 @@ static void P_InitPlayers(void)
G_DoReborn(i); G_DoReborn(i);
else // gametype is GT_COOP or GT_RACE else // gametype is GT_COOP or GT_RACE
{ {
G_SpawnPlayer(i, players[i].starposttime); G_SpawnPlayer(i);
if (players[i].starposttime) if (players[i].starposttime)
P_ClearStarPost(players[i].starpostnum); P_ClearStarPost(players[i].starpostnum);
} }

View file

@ -4426,10 +4426,18 @@ void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *rovers
case 6: // Death Pit (Camera Mod) case 6: // Death Pit (Camera Mod)
case 7: // Death Pit (No Camera Mod) case 7: // Death Pit (No Camera Mod)
if (roversector || P_MobjReadyToTrigger(player->mo, sector)) if (roversector || P_MobjReadyToTrigger(player->mo, sector))
P_DamageMobj(player->mo, NULL, NULL, 1, DMG_DEATHPIT); {
if (player->quittime)
G_MovePlayerToSpawnOrStarpost(player - players);
else
P_DamageMobj(player->mo, NULL, NULL, 1, DMG_DEATHPIT);
}
break; break;
case 8: // Instant Kill case 8: // Instant Kill
P_DamageMobj(player->mo, NULL, NULL, 1, DMG_INSTAKILL); if (player->quittime)
G_MovePlayerToSpawnOrStarpost(player - players);
else
P_DamageMobj(player->mo, NULL, NULL, 1, DMG_INSTAKILL);
break; break;
case 9: // Ring Drainer (Floor Touch) case 9: // Ring Drainer (Floor Touch)
case 10: // Ring Drainer (No Floor Touch) case 10: // Ring Drainer (No Floor Touch)

View file

@ -590,10 +590,24 @@ void P_Ticker(boolean run)
{ {
INT32 i; INT32 i;
//Increment jointime even if paused. // Increment jointime and quittime even if paused
for (i = 0; i < MAXPLAYERS; i++) for (i = 0; i < MAXPLAYERS; i++)
if (playeringame[i]) if (playeringame[i])
++players[i].jointime; {
players[i].jointime++;
if (players[i].quittime)
{
players[i].quittime++;
if (players[i].quittime == 30 * TICRATE)
P_CheckSurvivors();
if (server && players[i].quittime >= FixedMul(cv_rejointimeout.value, 60 * TICRATE)
&& !(players[i].quittime % TICRATE))
SendKick(i, KICK_MSG_PLAYER_QUIT);
}
}
if (objectplacing) if (objectplacing)
{ {

View file

@ -11756,6 +11756,8 @@ void P_PlayerThink(player_t *player)
{ {
if (!playeringame[i] || players[i].spectator || players[i].bot) if (!playeringame[i] || players[i].spectator || players[i].bot)
continue; continue;
if (players[i].quittime > 30 * TICRATE)
continue;
if (players[i].lives <= 0) if (players[i].lives <= 0)
continue; continue;
@ -12208,6 +12210,11 @@ void P_PlayerThink(player_t *player)
player->pflags &= ~PF_USEDOWN; player->pflags &= ~PF_USEDOWN;
} }
// IF PLAYER NOT HERE THEN FLASH END IF
if (player->quittime && player->powers[pw_flashing] < flashingtics - 1
&& !(G_TagGametype() && !(player->pflags & PF_TAGIT)) && !player->gotflag)
player->powers[pw_flashing] = flashingtics - 1;
// Counters, time dependent power ups. // Counters, time dependent power ups.
// Time Bonus & Ring Bonus count settings // Time Bonus & Ring Bonus count settings
@ -12251,12 +12258,12 @@ void P_PlayerThink(player_t *player)
else else
player->powers[pw_underwater] = 0; player->powers[pw_underwater] = 0;
} }
else if (player->powers[pw_underwater] && !(maptol & TOL_NIGHTS) && !((netgame || multiplayer) && player->spectator)) // underwater timer else if (player->powers[pw_underwater] && !(maptol & TOL_NIGHTS) && !((netgame || multiplayer) && (player->spectator || player->quittime))) // underwater timer
player->powers[pw_underwater]--; player->powers[pw_underwater]--;
if (player->powers[pw_spacetime] && (player->pflags & PF_GODMODE || (player->powers[pw_shield] & SH_PROTECTWATER))) if (player->powers[pw_spacetime] && (player->pflags & PF_GODMODE || (player->powers[pw_shield] & SH_PROTECTWATER)))
player->powers[pw_spacetime] = 0; player->powers[pw_spacetime] = 0;
else if (player->powers[pw_spacetime] && !(maptol & TOL_NIGHTS) && !((netgame || multiplayer) && player->spectator)) // underwater timer else if (player->powers[pw_spacetime] && !(maptol & TOL_NIGHTS) && !((netgame || multiplayer) && (player->spectator || player->quittime))) // underwater timer
player->powers[pw_spacetime]--; player->powers[pw_spacetime]--;
if (player->powers[pw_gravityboots] && player->powers[pw_gravityboots] < UINT16_MAX) if (player->powers[pw_gravityboots] && player->powers[pw_gravityboots] < UINT16_MAX)