From a1874036f107d59bade973e945110bb5f943e018 Mon Sep 17 00:00:00 2001 From: Alug Date: Tue, 17 Sep 2024 16:21:57 +0200 Subject: [PATCH] Resend Gamestate instead of Resynching port port of https://git.do.srb2.org/STJr/SRB2/-/merge_requests/829 With a few changes accomidating karts code --- src/d_clisrv.c | 946 ++++++++++--------------------------------------- src/d_clisrv.h | 162 +-------- src/d_main.c | 2 +- src/d_net.c | 6 +- src/g_game.c | 6 +- src/hu_stuff.c | 4 +- src/p_saveg.c | 36 +- src/p_saveg.h | 4 +- src/p_setup.c | 133 +++---- src/p_setup.h | 2 +- 10 files changed, 276 insertions(+), 1025 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 2656d997..3d0a4818 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -94,6 +94,8 @@ UINT8 playernode[MAXPLAYERS]; tic_t jointimeout = (3*TICRATE); static boolean sendingsavegame[MAXNETNODES]; // Are we sending the savegame? static tic_t freezetimeout[MAXNETNODES]; // Until when can this node freeze the server before getting a timeout? +static boolean resendingsavegame[MAXNETNODES]; // Are we resending the savegame? +static tic_t savegameresendcooldown[MAXNETNODES]; // How long before we can resend again? UINT16 pingmeasurecount = 1; UINT32 realpingtable[MAXPLAYERS]; //the base table of ping where an average will be sent to everyone. @@ -115,15 +117,8 @@ static tic_t maketic; static INT16 consistancy[TICQUEUE]; -// Resynching shit! -static UINT32 resynch_score[MAXNETNODES]; // "score" for kicking -- if this gets too high then cfail kick -static UINT16 resynch_delay[MAXNETNODES]; // delay time before the player can be considered to have desynched -static UINT32 resynch_status[MAXNETNODES]; // 0 bit means synched for that player, 1 means possibly desynched -static UINT8 resynch_sent[MAXNETNODES][MAXPLAYERS]; // what synch packets have we attempted to send to the player -static UINT8 resynch_inprogress[MAXNETNODES]; -static UINT8 resynch_local_inprogress = false; // WE are desynched and getting packets to fix it. static UINT8 player_joining = false; -UINT8 hu_resynching = 0; +UINT8 hu_redownloadinggamestate = 0; // kart, true when a player is connecting or disconnecting so that the gameplay has stopped in its tracks UINT8 hu_stopped = 0; @@ -136,6 +131,7 @@ static ticcmd_t localcmds4; static boolean cl_packetmissed; // here it is for the secondary local player (splitscreen) static UINT8 mynode; // my address pointofview server +static boolean cl_redownloadinggamestate = false; static UINT8 localtextcmd[MAXTEXTCMD]; static UINT8 localtextcmd2[MAXTEXTCMD]; // splitscreen @@ -555,554 +551,6 @@ void ReadLmpExtraData(UINT8 **demo_pointer, INT32 playernum) // end extra data function for lmps // ----------------------------------------------------------------- -// ----------------------------------------------------------------- -// resynch player data -// ----------------------------------------------------------------- -static inline void resynch_write_player(resynch_pak *rsp, const size_t i) -{ - size_t j; - - rsp->playernum = (UINT8)i; - - // Do not send anything visual related. - // Only send data that we need to know for physics. - rsp->playerstate = (UINT8)players[i].playerstate; //playerstate_t - rsp->pflags = (UINT32)LONG(players[i].pflags); //pflags_t - rsp->panim = (UINT8)players[i].panim; //panim_t - - rsp->aiming = (angle_t)LONG(players[i].aiming); - rsp->currentweapon = LONG(players[i].currentweapon); - rsp->ringweapons = LONG(players[i].ringweapons); - - for (j = 0; j < NUMPOWERS; ++j) - rsp->powers[j] = (UINT16)SHORT(players[i].powers[j]); - for (j = 0; j < NUMKARTSTUFF; ++j) - rsp->kartstuff[j] = LONG(players[i].kartstuff[j]); // SRB2kart - - rsp->frameangle = (angle_t)LONG(players[i].frameangle); // SRB2kart - - // Score is resynched in the rspfirm resync packet - rsp->health = 0; // resynched with mo health - rsp->lives = players[i].lives; - rsp->continues = players[i].continues; - rsp->scoreadd = players[i].scoreadd; - rsp->xtralife = players[i].xtralife; - rsp->pity = players[i].pity; - - rsp->skincolor = players[i].skincolor; - rsp->skin = LONG(players[i].skin); - // Just in case Lua does something like - // modify these at runtime - // SRB2kart - rsp->kartspeed = (UINT8)players[i].kartspeed; - rsp->kartweight = (UINT8)players[i].kartweight; - // - rsp->charflags = (UINT32)LONG(players[i].charflags); - - rsp->speed = (fixed_t)LONG(players[i].speed); - rsp->jumping = players[i].jumping; - rsp->secondjump = players[i].secondjump; - rsp->fly1 = players[i].fly1; - rsp->glidetime = (tic_t)LONG(players[i].glidetime); - rsp->climbing = players[i].climbing; - rsp->deadtimer = players[i].deadtimer; - rsp->exiting = (tic_t)LONG(players[i].exiting); - rsp->homing = players[i].homing; - rsp->skidtime = (tic_t)LONG(players[i].skidtime); - rsp->cmomx = (fixed_t)LONG(players[i].cmomx); - rsp->cmomy = (fixed_t)LONG(players[i].cmomy); - rsp->rmomx = (fixed_t)LONG(players[i].rmomx); - rsp->rmomy = (fixed_t)LONG(players[i].rmomy); - - rsp->weapondelay = LONG(players[i].weapondelay); - rsp->tossdelay = LONG(players[i].tossdelay); - - rsp->starpostx = SHORT(players[i].starpostx); - rsp->starposty = SHORT(players[i].starposty); - rsp->starpostz = SHORT(players[i].starpostz); - rsp->starpostnum = LONG(players[i].starpostnum); - rsp->starposttime = (tic_t)LONG(players[i].starposttime); - rsp->starpostangle = (angle_t)LONG(players[i].starpostangle); - - rsp->maxlink = LONG(players[i].maxlink); - rsp->dashspeed = (fixed_t)LONG(players[i].dashspeed); - rsp->dashtime = LONG(players[i].dashtime); - rsp->angle_pos = (angle_t)LONG(players[i].angle_pos); - rsp->old_angle_pos = (angle_t)LONG(players[i].old_angle_pos); - rsp->bumpertime = (tic_t)LONG(players[i].bumpertime); - rsp->flyangle = LONG(players[i].flyangle); - rsp->drilltimer = (tic_t)LONG(players[i].drilltimer); - rsp->linkcount = LONG(players[i].linkcount); - rsp->linktimer = (tic_t)LONG(players[i].linktimer); - rsp->anotherflyangle = LONG(players[i].anotherflyangle); - rsp->nightstime = (tic_t)LONG(players[i].nightstime); - rsp->drillmeter = LONG(players[i].drillmeter); - rsp->drilldelay = players[i].drilldelay; - rsp->bonustime = players[i].bonustime; - rsp->mare = players[i].mare; - rsp->lastsidehit = SHORT(players[i].lastsidehit); - rsp->lastlinehit = SHORT(players[i].lastlinehit); - - rsp->losstime = (tic_t)LONG(players[i].losstime); - rsp->timeshit = players[i].timeshit; - rsp->onconveyor = LONG(players[i].onconveyor); - - rsp->jointime = (tic_t)LONG(players[i].jointime); - rsp->spectatorreentry = (tic_t)LONG(players[i].spectatorreentry); - - rsp->grieftime = (tic_t)LONG(players[i].grieftime); - rsp->griefstrikes = players[i].griefstrikes; - - rsp->splitscreenindex = players[i].splitscreenindex; - - rsp->hasmo = false; - //Transfer important mo information if the player has a body. - //This lets us resync players even if they are dead. - if (!players[i].mo) - return; - rsp->hasmo = true; - - rsp->health = LONG(players[i].mo->health); - - rsp->angle = (angle_t)LONG(players[i].mo->angle); - rsp->x = (fixed_t)LONG(players[i].mo->x); - rsp->y = (fixed_t)LONG(players[i].mo->y); - rsp->z = (fixed_t)LONG(players[i].mo->z); - rsp->momx = (fixed_t)LONG(players[i].mo->momx); - rsp->momy = (fixed_t)LONG(players[i].mo->momy); - rsp->momz = (fixed_t)LONG(players[i].mo->momz); - rsp->friction = (fixed_t)LONG(players[i].mo->friction); - rsp->movefactor = (fixed_t)LONG(players[i].mo->movefactor); - - rsp->tics = LONG(players[i].mo->tics); - rsp->statenum = (statenum_t)LONG(players[i].mo->state-states); // :( - rsp->flags = (UINT32)LONG(players[i].mo->flags); - rsp->flags2 = (UINT32)LONG(players[i].mo->flags2); - rsp->eflags = (UINT16)SHORT(players[i].mo->eflags); - - rsp->radius = (fixed_t)LONG(players[i].mo->radius); - rsp->height = (fixed_t)LONG(players[i].mo->height); - rsp->scale = (fixed_t)LONG(players[i].mo->scale); - rsp->destscale = (fixed_t)LONG(players[i].mo->destscale); - rsp->scalespeed = (fixed_t)LONG(players[i].mo->scalespeed); -} - -static void resynch_read_player(resynch_pak *rsp) -{ - INT32 i = rsp->playernum, j; - mobj_t *savedmo = players[i].mo; - - // Do not send anything visual related. - // Only send data that we need to know for physics. - players[i].playerstate = (UINT8)rsp->playerstate; //playerstate_t - players[i].pflags = (UINT32)LONG(rsp->pflags); //pflags_t - players[i].panim = (UINT8)rsp->panim; //panim_t - - players[i].aiming = (angle_t)LONG(rsp->aiming); - players[i].currentweapon = LONG(rsp->currentweapon); - players[i].ringweapons = LONG(rsp->ringweapons); - - for (j = 0; j < NUMPOWERS; ++j) - players[i].powers[j] = (UINT16)SHORT(rsp->powers[j]); - for (j = 0; j < NUMKARTSTUFF; ++j) - players[i].kartstuff[j] = LONG(rsp->kartstuff[j]); // SRB2kart - - players[i].frameangle = (angle_t)LONG(rsp->frameangle); // SRB2kart - - // Score is resynched in the rspfirm resync packet - players[i].health = rsp->health; - players[i].lives = rsp->lives; - players[i].continues = rsp->continues; - players[i].scoreadd = rsp->scoreadd; - players[i].xtralife = rsp->xtralife; - players[i].pity = rsp->pity; - - players[i].skincolor = rsp->skincolor; - players[i].skin = LONG(rsp->skin); - // Just in case Lua does something like - // modify these at runtime - players[i].kartspeed = (UINT8)rsp->kartspeed; - players[i].kartweight = (UINT8)rsp->kartweight; - - players[i].charflags = (UINT32)LONG(rsp->charflags); - - players[i].speed = (fixed_t)LONG(rsp->speed); - players[i].jumping = rsp->jumping; - players[i].secondjump = rsp->secondjump; - players[i].fly1 = rsp->fly1; - players[i].glidetime = (tic_t)LONG(rsp->glidetime); - players[i].climbing = rsp->climbing; - players[i].deadtimer = rsp->deadtimer; - players[i].exiting = (tic_t)LONG(rsp->exiting); - players[i].homing = rsp->homing; - players[i].skidtime = (tic_t)LONG(rsp->skidtime); - players[i].cmomx = (fixed_t)LONG(rsp->cmomx); - players[i].cmomy = (fixed_t)LONG(rsp->cmomy); - players[i].rmomx = (fixed_t)LONG(rsp->rmomx); - players[i].rmomy = (fixed_t)LONG(rsp->rmomy); - - players[i].weapondelay = LONG(rsp->weapondelay); - players[i].tossdelay = LONG(rsp->tossdelay); - - players[i].starpostx = SHORT(rsp->starpostx); - players[i].starposty = SHORT(rsp->starposty); - players[i].starpostz = SHORT(rsp->starpostz); - players[i].starpostnum = LONG(rsp->starpostnum); - players[i].starposttime = (tic_t)LONG(rsp->starposttime); - players[i].starpostangle = (angle_t)LONG(rsp->starpostangle); - - players[i].maxlink = LONG(rsp->maxlink); - players[i].dashspeed = (fixed_t)LONG(rsp->dashspeed); - players[i].dashtime = LONG(rsp->dashtime); - players[i].angle_pos = (angle_t)LONG(rsp->angle_pos); - players[i].old_angle_pos = (angle_t)LONG(rsp->old_angle_pos); - players[i].bumpertime = (tic_t)LONG(rsp->bumpertime); - players[i].flyangle = LONG(rsp->flyangle); - players[i].drilltimer = (tic_t)LONG(rsp->drilltimer); - players[i].linkcount = LONG(rsp->linkcount); - players[i].linktimer = (tic_t)LONG(rsp->linktimer); - players[i].anotherflyangle = LONG(rsp->anotherflyangle); - players[i].nightstime = (tic_t)LONG(rsp->nightstime); - players[i].drillmeter = LONG(rsp->drillmeter); - players[i].drilldelay = rsp->drilldelay; - players[i].bonustime = rsp->bonustime; - players[i].mare = rsp->mare; - players[i].lastsidehit = SHORT(rsp->lastsidehit); - players[i].lastlinehit = SHORT(rsp->lastlinehit); - - players[i].losstime = (tic_t)LONG(rsp->losstime); - players[i].timeshit = rsp->timeshit; - players[i].onconveyor = LONG(rsp->onconveyor); - - players[i].jointime = (tic_t)LONG(rsp->jointime); - players[i].spectatorreentry = (tic_t)LONG(rsp->spectatorreentry); - - players[i].grieftime = (tic_t)LONG(rsp->grieftime); - players[i].griefstrikes = rsp->griefstrikes; - - players[i].splitscreenindex = rsp->splitscreenindex; - - //We get a packet for each player in game. - if (!playeringame[i]) - return; - - //...but keep old mo even if it is corrupt or null! - players[i].mo = savedmo; - - //Transfer important mo information if they have a valid mo. - if (!rsp->hasmo) - return; - - //server thinks player has a body. - //Give them a new body that can be then manipulated by the server's info. - if (!players[i].mo) //client thinks it has no body. - P_SpawnPlayer(i); - - //At this point, the player should have a body, whether they were respawned or not. - P_UnsetThingPosition(players[i].mo); - players[i].mo->health = LONG(rsp->health); - - players[i].mo->angle = (angle_t)LONG(rsp->angle); - players[i].mo->x = (fixed_t)LONG(rsp->x); - players[i].mo->y = (fixed_t)LONG(rsp->y); - players[i].mo->z = (fixed_t)LONG(rsp->z); - players[i].mo->momx = (fixed_t)LONG(rsp->momx); - players[i].mo->momy = (fixed_t)LONG(rsp->momy); - players[i].mo->momz = (fixed_t)LONG(rsp->momz); - players[i].mo->friction = (fixed_t)LONG(rsp->friction); - players[i].mo->movefactor = (fixed_t)LONG(rsp->movefactor); - - players[i].mo->tics = LONG(rsp->tics); - P_SetMobjStateNF(players[i].mo, (statenum_t)LONG(rsp->statenum)); - players[i].mo->flags = (UINT32)LONG(rsp->flags); - players[i].mo->flags2 = (UINT32)LONG(rsp->flags2); - players[i].mo->eflags = (UINT16)SHORT(rsp->eflags); - - players[i].mo->radius = (fixed_t)LONG(rsp->radius); - players[i].mo->height = (fixed_t)LONG(rsp->height); - // P_SetScale is redundant for this, as all related variables are already restored properly. - players[i].mo->scale = (fixed_t)LONG(rsp->scale); - players[i].mo->destscale = (fixed_t)LONG(rsp->destscale); - players[i].mo->scalespeed = (fixed_t)LONG(rsp->scalespeed); - - // And finally, SET THE MOBJ SKIN damn it. - players[i].mo->skin = &skins[players[i].skin]; - players[i].mo->color = players[i].skincolor; - - P_SetThingPosition(players[i].mo); -} - -static inline void resynch_write_ctf(resynchend_pak *rst) -{ - mobj_t *mflag; - UINT8 i, j; - - for (i = 0, mflag = redflag; i < 2; ++i, mflag = blueflag) - { - rst->flagx[i] = rst->flagy[i] = rst->flagz[i] = 0; - rst->flagloose[i] = rst->flagflags[i] = 0; - rst->flagplayer[i] = -1; - - if (!mflag) - { - // Should be held by a player - for (j = 0; j < MAXPLAYERS; ++j) - { - // GF_REDFLAG is 1, GF_BLUEFLAG is 2 - // redflag handling is i=0, blueflag is i=1 - // so check for gotflag == (i+1) - if (!playeringame[j] || players[j].gotflag != (i+1)) - continue; - rst->flagplayer[i] = (SINT8)j; - break; - } - if (j == MAXPLAYERS) // fine, no I_Error - { - CONS_Alert(CONS_ERROR, "One of the flags has gone completely missing...\n"); - rst->flagplayer[i] = -2; - } - continue; - } - - rst->flagx[i] = (fixed_t)LONG(mflag->x); - rst->flagy[i] = (fixed_t)LONG(mflag->y); - rst->flagz[i] = (fixed_t)LONG(mflag->z); - rst->flagflags[i] = LONG(mflag->flags2); - rst->flagloose[i] = LONG(mflag->fuse); // Dropped or not? - } -} - -static inline void resynch_read_ctf(resynchend_pak *p) -{ - UINT8 i; - - for (i = 0; i < MAXPLAYERS; ++i) - players[i].gotflag = 0; - - // Red flag - if (p->flagplayer[0] == -2) - ; // The server doesn't even know what happened to it... - else if (p->flagplayer[0] != -1) // Held by a player - { - if (!playeringame[p->flagplayer[0]]) - I_Error("Invalid red flag player %d who isn't in the game!", (INT32)p->flagplayer[0]); - players[p->flagplayer[0]].gotflag = GF_REDFLAG; - if (redflag) - { - P_RemoveMobj(redflag); - redflag = NULL; - } - } - else - { - if (!redflag) - redflag = P_SpawnMobj(0,0,0,MT_REDFLAG); - - P_UnsetThingPosition(redflag); - redflag->x = (fixed_t)LONG(p->flagx[0]); - redflag->y = (fixed_t)LONG(p->flagy[0]); - redflag->z = (fixed_t)LONG(p->flagz[0]); - redflag->flags2 = LONG(p->flagflags[0]); - redflag->fuse = LONG(p->flagloose[0]); - P_SetThingPosition(redflag); - } - - // Blue flag - if (p->flagplayer[1] == -2) - ; // The server doesn't even know what happened to it... - else if (p->flagplayer[1] != -1) // Held by a player - { - if (!playeringame[p->flagplayer[1]]) - I_Error("Invalid blue flag player %d who isn't in the game!", (INT32)p->flagplayer[1]); - players[p->flagplayer[1]].gotflag = GF_BLUEFLAG; - if (blueflag) - { - P_RemoveMobj(blueflag); - blueflag = NULL; - } - } - else - { - if (!blueflag) - blueflag = P_SpawnMobj(0,0,0,MT_BLUEFLAG); - - P_UnsetThingPosition(blueflag); - blueflag->x = (fixed_t)LONG(p->flagx[1]); - blueflag->y = (fixed_t)LONG(p->flagy[1]); - blueflag->z = (fixed_t)LONG(p->flagz[1]); - blueflag->flags2 = LONG(p->flagflags[1]); - blueflag->fuse = LONG(p->flagloose[1]); - P_SetThingPosition(blueflag); - } -} - -static inline void resynch_write_others(resynchend_pak *rst) -{ - UINT8 i; - - rst->ingame = 0; - - for (i = 0; i < MAXPLAYERS; ++i) - { - if (!playeringame[i]) - { - rst->ctfteam[i] = 0; - rst->score[i] = 0; - rst->marescore[i] = 0; - rst->realtime[i] = 0; - rst->laps[i] = 0; - continue; - } - - if (!players[i].spectator) - rst->ingame |= (1<ctfteam[i] = (INT32)LONG(players[i].ctfteam); - rst->score[i] = (UINT32)LONG(players[i].score); - rst->marescore[i] = (UINT32)LONG(players[i].marescore); - rst->realtime[i] = (tic_t)LONG(players[i].realtime); - rst->laps[i] = players[i].laps; - } - - // endian safeness - rst->ingame = (UINT32)LONG(rst->ingame); -} - -static inline void resynch_read_others(resynchend_pak *p) -{ - UINT8 i; - UINT32 loc_ingame = (UINT32)LONG(p->ingame); - - for (i = 0; i < MAXPLAYERS; ++i) - { - // We don't care if they're in the game or not, just write all the data. - players[i].spectator = !(loc_ingame & (1<ctfteam[i]); // no, 0 does not mean spectator, at least not in Match - players[i].score = (UINT32)LONG(p->score[i]); - players[i].marescore = (UINT32)LONG(p->marescore[i]); - players[i].realtime = (tic_t)LONG(p->realtime[i]); - players[i].laps = p->laps[i]; - } -} - -static void SV_InitResynchVars(INT32 node) -{ - resynch_delay[node] = TICRATE; // initial one second delay - resynch_score[node] = 0; // clean slate - resynch_status[node] = 0x00; - resynch_inprogress[node] = false; - memset(resynch_sent[node], 0, MAXPLAYERS); -} - -static void SV_RequireResynch(INT32 node) -{ - INT32 i; - - resynch_delay[node] = 10; // Delay before you can fail sync again - resynch_score[node] += 200; // Add score for initial desynch - for (i = 0; i < MAXPLAYERS; ++i) - resynch_status[node] |= (1<>1)+1; - } -} - -static void SV_SendResynch(INT32 node) -{ - INT32 i, j; - - if (!nodeingame[node]) - { - // player left during resynch - // so obviously we don't need to do any of this anymore - resynch_inprogress[node] = false; - return; - } - - // resynched? - if (!resynch_status[node]) - { - // you are now synched - resynch_inprogress[node] = false; - - netbuffer->packettype = PT_RESYNCHEND; - - netbuffer->u.resynchend.randomseed = P_GetRandSeed(); - if (gametype == GT_CTF) - resynch_write_ctf(&netbuffer->u.resynchend); - resynch_write_others(&netbuffer->u.resynchend); - - HSendPacket(node, true, 0, (sizeof(resynchend_pak))); - return; - } - - netbuffer->packettype = PT_RESYNCHING; - for (i = 0, j = 0; i < MAXPLAYERS; ++i) - { - // if already synched don't bother - if (!(resynch_status[node] & 1<u.resynchpak, i); - HSendPacket(node, false, 0, (sizeof(resynch_pak))); - - resynch_sent[node][i] = TICRATE; - resynch_score[node] += 2; // penalty for send - - if (++j > 3) - break; - } - - if (resynch_score[node] > (unsigned)cv_resynchattempts.value*250) - { - XBOXSTATIC UINT8 buf[2]; - buf[0] = (UINT8)nodetoplayer[node]; - buf[1] = KICK_MSG_CON_FAIL; - SendNetXCmd(XD_KICK, &buf, 2); - resynch_score[node] = 0; - } -} - -static void CL_AcknowledgeResynch(resynch_pak *rsp) -{ - resynch_read_player(rsp); - - netbuffer->packettype = PT_RESYNCHGET; - netbuffer->u.resynchgot = rsp->playernum; - HSendPacket(servernode, true, 0, sizeof(UINT8)); -} - -static void SV_AcknowledgeResynchAck(INT32 node, UINT8 rsg) -{ - if (rsg >= MAXPLAYERS) - resynch_score[node] += 16384; // lol. - else - { - resynch_status[node] &= ~(1<packettype = PT_SERVERCFG; @@ -1693,46 +1105,23 @@ static boolean SV_SendServerConfig(INT32 node) netbuffer->u.servercfg.gametype = (UINT8)gametype; netbuffer->u.servercfg.modifiedgame = (UINT8)modifiedgame; - // we fill these structs with FFs so that any players not in game get sent as 0xFFFF - // which is nice and easy for us to detect - memset(netbuffer->u.servercfg.playerskins, 0xFF, sizeof(netbuffer->u.servercfg.playerskins)); - memset(netbuffer->u.servercfg.playercolor, 0xFF, sizeof(netbuffer->u.servercfg.playercolor)); - - memset(netbuffer->u.servercfg.adminplayers, -1, sizeof(netbuffer->u.servercfg.adminplayers)); - - for (i = 0; i < MAXPLAYERS; i++) - { - netbuffer->u.servercfg.adminplayers[i] = (SINT8)adminplayers[i]; - - if (!playeringame[i]) - continue; - - netbuffer->u.servercfg.playerskins[i] = (UINT8)players[i].skin; - netbuffer->u.servercfg.playercolor[i] = (UINT8)players[i].skincolor; - } - netbuffer->u.servercfg.maxplayer = (UINT8)(min((dedicated ? MAXPLAYERS-1 : MAXPLAYERS), cv_maxplayers.value)); netbuffer->u.servercfg.allownewplayer = cv_allownewplayer.value; netbuffer->u.servercfg.discordinvites = (boolean)cv_discordinvites.value; memcpy(netbuffer->u.servercfg.server_context, server_context, 8); - op = p = netbuffer->u.servercfg.varlengthinputs; - CV_SavePlayerNames(&p); - CV_SaveNetVars(&p, false); - { - const size_t len = sizeof (serverconfig_pak) + (size_t)(p - op); + const size_t len = sizeof (serverconfig_pak); #ifdef DEBUGFILE - if (debugfile) - { - fprintf(debugfile, "ServerConfig Packet about to be sent, size of packet:%s to node:%d\n", - sizeu1(len), node); - } + if (debugfile) + { + fprintf(debugfile, "ServerConfig Packet about to be sent, size of packet:%s to node:%d\n", + sizeu1(len), node); + } #endif - waspacketsent = HSendPacket(node, true, 0, len); - } + waspacketsent = HSendPacket(node, true, 0, len); #ifdef DEBUGFILE if (debugfile) @@ -1754,7 +1143,18 @@ static boolean SV_SendServerConfig(INT32 node) #ifdef JOININGAME #define SAVEGAMESIZE (768*1024) -static void SV_SendSaveGame(INT32 node) + +static boolean SV_ResendingSavegameToAnyone(void) +{ + INT32 i; + + for (i = 0; i < MAXNETNODES; i++) + if (resendingsavegame[i]) + return true; + return false; +} + +static void SV_SendSaveGame(INT32 node, boolean resending) { size_t length, compressedlen; UINT8 *savebuffer; @@ -1772,7 +1172,7 @@ static void SV_SendSaveGame(INT32 node) // Leave room for the uncompressed length. save_p = savebuffer + sizeof(UINT32); - P_SaveNetGame(); + P_SaveNetGame(resending); length = save_p - savebuffer; if (length > SAVEGAMESIZE) @@ -1845,7 +1245,7 @@ static void SV_SavedGame(void) return; } - P_SaveNetGame(); + P_SaveNetGame(false); length = save_p - savebuffer; if (length > SAVEGAMESIZE) @@ -1868,7 +1268,7 @@ static void SV_SavedGame(void) #define TMPSAVENAME "$$$.sav" -static void CL_LoadReceivedSavegame(void) +static void CL_LoadReceivedSavegame(boolean reloading) { UINT8 *savebuffer = NULL; size_t length, decompressedlen; @@ -1903,7 +1303,7 @@ static void CL_LoadReceivedSavegame(void) automapactive = false; // load a base level - if (P_LoadNetGame()) + if (P_LoadNetGame(reloading)) { CON_LogMessage(va(M_GetText("Map is now \"%s"), G_BuildMapName(gamemap))); if (strlen(mapheaderinfo[gamemap-1]->lvlttl) > 0) @@ -1935,6 +1335,41 @@ static void CL_LoadReceivedSavegame(void) CONS_Alert(CONS_ERROR, M_GetText("Can't delete %s\n"), tmpsave); consistancy[gametic%TICQUEUE] = Consistancy(); CON_ToggleOff(); + + // Tell the server we have received and reloaded the gamestate + // so they know they can resume the game + netbuffer->packettype = PT_RECEIVEDGAMESTATE; + HSendPacket(servernode, true, 0, 0); +} + +static void CL_ReloadReceivedSavegame(void) +{ + INT32 i; + + for (i = 0; i < MAXPLAYERS; i++) + { +#ifdef HAVE_BLUA + LUA_InvalidatePlayer(&players[i]); +#endif + sprintf(player_names[i], "Player %d", i + 1); + } + + CL_LoadReceivedSavegame(true); + + if (neededtic < gametic) + neededtic = gametic; + maketic = neededtic; + + // we dont have P_ForceLocalAngle so were setting it manually here + for (i = 0; i <= splitscreen; i++) + { + localangle[i] = (angle_t)(players[displayplayers[i]].cmd.angleturn<<16); + camera[i].subsector = R_PointInSubsector(camera[i].x, camera[i].y); + } + + cl_redownloadinggamestate = false; + + CONS_Printf(M_GetText("Game state reloaded\n")); } #endif @@ -2545,7 +1980,7 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic if (fileneeded[0].status == FS_FOUND) { // Gamestate is now handled within CL_LoadReceivedSavegame() - CL_LoadReceivedSavegame(); + CL_LoadReceivedSavegame(false); cl_mode = CL_CONNECTED; break; } // don't break case continue to CL_CONNECTED @@ -3130,8 +2565,8 @@ void CL_RemovePlayer(INT32 playernum, INT32 reason) playerpernode[node]--; if (playerpernode[node] <= 0) { - // If a resynch was in progress, well, it no longer needs to be. - SV_InitResynchVars(node); + resendingsavegame[node] = false; + savegameresendcooldown[node] = 0; nodeingame[node] = false; Net_CloseConnection(node); @@ -3785,6 +3220,34 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum) } } +static void Command_ResendGamestate(void) +{ + SINT8 playernum; + + if (COM_Argc() == 1) + { + CONS_Printf(M_GetText("resendgamestate : resend the game state to a player\n")); + return; + } + else if (client) + { + CONS_Printf(M_GetText("Only the server can use this.\n")); + return; + } + + playernum = nametonum(COM_Argv(1)); + if (playernum == -1 || playernum == 0) + return; + + // Send a PT_WILLRESENDGAMESTATE packet to the client so they know what's going on + netbuffer->packettype = PT_WILLRESENDGAMESTATE; + if (!HSendPacket(playernode[playernum], true, 0, 0)) + { + CONS_Alert(CONS_ERROR, M_GetText("A problem occured, please try again.\n")); + return; + } +} + #ifdef HAVE_CURL /** Add a login for HTTP downloads. If the * user/password is missing, remove it. @@ -3873,8 +3336,7 @@ consvar_t cv_maxplayers = {"maxplayers", "8", CV_SAVE|CV_CALL, maxplayers_cons_t static CV_PossibleValue_t discordinvites_cons_t[] = {{0, "Admins Only"}, {1, "Everyone"}, {0, NULL}}; consvar_t cv_discordinvites = {"discordinvites", "Everyone", CV_SAVE|CV_CALL, discordinvites_cons_t, Joinable_OnChange, 0, NULL, NULL, 0, 0, NULL}; -static CV_PossibleValue_t resynchattempts_cons_t[] = {{0, "MIN"}, {20, "MAX"}, {0, NULL}}; -consvar_t cv_resynchattempts = {"resynchattempts", "2", CV_SAVE, resynchattempts_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL }; +consvar_t cv_resynchattempts = {"AllowGamestateresend", "ON", 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 }; // max file size to send to a player (in kilobytes) @@ -3923,6 +3385,7 @@ void D_ClientServerInit(void) COM_AddCommand("reloadbans", Command_ReloadBan); COM_AddCommand("connect", Command_connect); COM_AddCommand("nodes", Command_Nodes); + COM_AddCommand("resendgamestate", Command_ResendGamestate); #ifdef HAVE_CURL COM_AddCommand("set_http_login", Command_set_http_login); COM_AddCommand("list_http_logins", Command_list_http_logins); @@ -3959,15 +3422,21 @@ void D_ClientServerInit(void) static void ResetNode(INT32 node) { nodeingame[node] = false; + nodewaiting[node] = 0; + + nettics[node] = gametic; + supposedtics[node] = gametic; + nodetoplayer[node] = -1; nodetoplayer2[node] = -1; nodetoplayer3[node] = -1; nodetoplayer4[node] = -1; - nettics[node] = gametic; - supposedtics[node] = gametic; - nodewaiting[node] = 0; playerpernode[node] = 0; + sendingsavegame[node] = false; + resendingsavegame[node] = false; + savegameresendcooldown[node] = 0; + bannednode[node].banid = SIZE_MAX; bannednode[node].timeleft = NO_BAN_TIME; } @@ -3987,9 +3456,6 @@ void SV_ResetServer(void) for (i = 0; i < MAXNETNODES; i++) { ResetNode(i); - - // Make sure resynch status doesn't get carried over! - SV_InitResynchVars(i); } for (i = 0; i < MAXPLAYERS; i++) @@ -4007,6 +3473,7 @@ void SV_ResetServer(void) mynode = 0; cl_packetmissed = false; + cl_redownloadinggamestate = false; if (dedicated) { @@ -4511,12 +3978,6 @@ static void HandleConnect(SINT8 node) SV_AddNode(node); - /// \note Wait what??? - /// What if the gamestate takes more than one second to get downloaded? - /// Or if a lagspike happens? - // you get a free second before desynch checks. use it wisely. - SV_InitResynchVars(node); - #ifdef VANILLAJOINNEXTROUND if (cv_joinnextround.value && gameaction == ga_nothing) G_SetGamestate(GS_WAITINGPLAYERS); @@ -4540,7 +4001,7 @@ static void HandleConnect(SINT8 node) { if (node && newnode) { - SV_SendSaveGame(node); // send a complete game state + SV_SendSaveGame(node, false); // send a complete game state DEBFILE("send savegame\n"); } SV_AddWaitingPlayers(); @@ -4609,6 +4070,43 @@ static void HandleServerInfo(SINT8 node) } #endif +static void PT_WillResendGamestate(void) +{ + char tmpsave[256]; + + if (server || cl_redownloadinggamestate) + return; + + // Send back a PT_CANRECEIVEGAMESTATE packet to the server + // so they know they can start sending the game state + netbuffer->packettype = PT_CANRECEIVEGAMESTATE; + if (!HSendPacket(servernode, true, 0, 0)) + return; + + CONS_Printf(M_GetText("Reloading game state...\n")); + + sprintf(tmpsave, "%s" PATHSEP TMPSAVENAME, srb2home); + + // Don't get a corrupt savegame error because tmpsave already exists + if (FIL_FileExists(tmpsave) && unlink(tmpsave) == -1) + I_Error("Can't delete %s\n", tmpsave); + + CL_PrepareDownloadSaveGame(tmpsave); + + cl_redownloadinggamestate = true; +} + +static void PT_CanReceiveGamestate(SINT8 node) +{ + if (client || sendingsavegame[node]) + return; + + CONS_Printf(M_GetText("Resending game state to %s...\n"), player_names[nodetoplayer[node]]); + + SV_SendSaveGame(node, true); // Resend a complete game state + resendingsavegame[node] = true; +} + /** Handles a packet received from a node that isn't in game * * \param node The packet sender @@ -4752,9 +4250,6 @@ static void HandlePacketFromAwayNode(SINT8 node) case PT_SERVERCFG: // Positive response of client join request { - INT32 j; - UINT8 *scp; - if (server && serverrunning && node != servernode) { // but wait I thought I'm the server? Net_CloseConnection(node); @@ -4771,8 +4266,6 @@ static void HandlePacketFromAwayNode(SINT8 node) if ((gametype = netbuffer->u.servercfg.gametype) >= NUMGAMETYPES) I_Error("Bad gametype in cliserv!"); modifiedgame = netbuffer->u.servercfg.modifiedgame; - for (j = 0; j < MAXPLAYERS; j++) - adminplayers[j] = netbuffer->u.servercfg.adminplayers[j]; memcpy(server_context, netbuffer->u.servercfg.server_context, 8); } @@ -4797,21 +4290,6 @@ static void HandlePacketFromAwayNode(SINT8 node) #endif DEBFILE(va("Server accept join gametic=%u mynode=%d\n", gametic, mynode)); - memset(playeringame, 0, sizeof(playeringame)); - for (j = 0; j < MAXPLAYERS; j++) - { - if (netbuffer->u.servercfg.playerskins[j] == 0xFF - && netbuffer->u.servercfg.playercolor[j] == 0xFF) - continue; // not in game - - playeringame[j] = true; - SetPlayerSkinByNum(j, (INT32)netbuffer->u.servercfg.playerskins[j]); - players[j].skincolor = netbuffer->u.servercfg.playercolor[j]; - } - - scp = netbuffer->u.servercfg.varlengthinputs; - CV_LoadPlayerNames(&scp); - CV_LoadNetVars(&scp); #ifdef JOININGAME /// \note Wait. What if a Lua script uses some global custom variables synched with the NetVars hook? /// Shouldn't them be downloaded even at intermission time? @@ -4925,11 +4403,6 @@ FILESTAMP switch (netbuffer->packettype) { // -------------------------------------------- SERVER RECEIVE ---------- - case PT_RESYNCHGET: - if (client) - break; - SV_AcknowledgeResynchAck(node, netbuffer->u.resynchgot); - break; case PT_CLIENTCMD: case PT_CLIENT2CMD: case PT_CLIENT3CMD: @@ -4943,10 +4416,6 @@ FILESTAMP if (client) break; - // Ignore tics from those not synched - if (resynch_inprogress[node]) - break; - // 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, nettics[node]); @@ -4973,9 +4442,6 @@ FILESTAMP if (netconsole == -1) break; - // If a client sends a ticcmd it should mean they are done receiving the savegame - sendingsavegame[node] = false; - // 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? freezetimeout[node] = I_GetTime() + connectiontimeout; @@ -5027,21 +4493,20 @@ FILESTAMP break; } - // A delay before we check resynching - // Used on join or just after a synch fail - if (resynch_delay[node]) - { - --resynch_delay[node]; - break; - } // Check player consistancy during the level - if (realstart <= gametic && realstart > gametic - TICQUEUE+1 && gamestate == GS_LEVEL - && consistancy[realstart%TICQUEUE] != SHORT(netbuffer->u.clientpak.consistancy)) + if (realstart <= gametic && realstart + TICQUEUE - 1 > gametic && gamestate == GS_LEVEL + && consistancy[realstart%TICQUEUE] != SHORT(netbuffer->u.clientpak.consistancy) + && !resendingsavegame[node] && savegameresendcooldown[node] <= I_GetTime() + && !SV_ResendingSavegameToAnyone()) { - SV_RequireResynch(node); - - if (cv_resynchattempts.value && resynch_score[node] <= (unsigned)cv_resynchattempts.value*250) + if (cv_resynchattempts.value) { + // Tell the client we are about to resend them the gamestate + netbuffer->packettype = PT_WILLRESENDGAMESTATE; + HSendPacket(node, true, 0, 0); + + resendingsavegame[node] = 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], @@ -5065,8 +4530,6 @@ FILESTAMP break; } } - else if (resynch_score[node]) - --resynch_score[node]; break; case PT_BASICKEEPALIVE: if (client) @@ -5203,32 +4666,15 @@ FILESTAMP Net_CloseConnection(node); nodeingame[node] = false; break; -// -------------------------------------------- CLIENT RECEIVE ---------- - case PT_RESYNCHEND: - // Only accept PT_RESYNCHEND from the server. - if (node != servernode) - { - CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_RESYNCHEND", node); - - if (server) - { - XBOXSTATIC UINT8 buf[2]; - buf[0] = (UINT8)node; - buf[1] = KICK_MSG_CON_FAIL; - SendNetXCmd(XD_KICK, &buf, 2); - } - - break; - } - resynch_local_inprogress = false; - - P_SetRandSeed(netbuffer->u.resynchend.randomseed); - - if (gametype == GT_CTF) - resynch_read_ctf(&netbuffer->u.resynchend); - resynch_read_others(&netbuffer->u.resynchend); - + case PT_CANRECEIVEGAMESTATE: + PT_CanReceiveGamestate(node); break; + case PT_RECEIVEDGAMESTATE: + sendingsavegame[node] = false; + resendingsavegame[node] = false; + savegameresendcooldown[node] = I_GetTime() + 5 * TICRATE; + break; +// -------------------------------------------- CLIENT RECEIVE ---------- case PT_SERVERTICS: // Only accept PT_SERVERTICS from the server. if (node != servernode) @@ -5296,25 +4742,6 @@ FILESTAMP "IRC or Discord so it can be fixed.\n", (INT32)realstart, (INT32)realend, (INT32)neededtic);*/ } break; - case PT_RESYNCHING: - // Only accept PT_RESYNCHING from the server. - if (node != servernode) - { - CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_RESYNCHING", node); - - if (server) - { - XBOXSTATIC char buf[2]; - buf[0] = (char)node; - buf[1] = KICK_MSG_CON_FAIL; - SendNetXCmd(XD_KICK, &buf, 2); - } - - break; - } - resynch_local_inprogress = true; - CL_AcknowledgeResynch(&netbuffer->u.resynchpak); - break; case PT_PING: // Only accept PT_PING from the server. if (node != servernode) @@ -5365,6 +4792,9 @@ FILESTAMP if (client) Got_Filetxpak(); break; + case PT_WILLRESENDGAMESTATE: + PT_WillResendGamestate(); + break; default: DEBFILE(va("UNKNOWN PACKET TYPE RECEIVED %d from host %d\n", netbuffer->packettype, node)); @@ -6304,9 +5734,12 @@ FILESTAMP if (client) { - if (!resynch_local_inprogress) - CL_SendClientCmd(); // Send tic cmd - hu_resynching = resynch_local_inprogress; + // If the client just finished redownloading the game state, load it + if (cl_redownloadinggamestate && fileneeded[0].status == FS_FOUND) + CL_ReloadReceivedSavegame(); + + CL_SendClientCmd(); // Send tic cmd + hu_redownloadinggamestate = cl_redownloadinggamestate; } else { @@ -6314,7 +5747,7 @@ FILESTAMP { INT32 counts; - hu_resynching = false; + hu_redownloadinggamestate = false; firstticstosend = gametic; for (i = 0; i < MAXNETNODES; i++) @@ -6324,32 +5757,19 @@ FILESTAMP // Don't erase tics not acknowledged counts = realtics; - for (i = 0; i < MAXNETNODES; ++i) - if (resynch_inprogress[i]) - { - SV_SendResynch(i); - counts = -666; - } + // See also PingUpdate + if (maketic + counts >= firstticstosend + TICQUEUE) + counts = firstticstosend+TICQUEUE-maketic-1; - // Do not make tics while resynching - if (counts != -666) - { - // See also PingUpdate - if (maketic + counts >= firstticstosend + TICQUEUE) - counts = firstticstosend+TICQUEUE-maketic-1; + for (i = 0; i < counts; i++) + SV_Maketic(); // Create missed tics and increment maketic - for (i = 0; i < counts; i++) - SV_Maketic(); // Create missed tics and increment maketic + for (; tictoclear < firstticstosend; tictoclear++) // Clear only when acknowledged + D_Clearticcmd(tictoclear); // Clear the maketic the new tic - for (; tictoclear < firstticstosend; tictoclear++) // Clear only when acknowledged - D_Clearticcmd(tictoclear); // Clear the maketic the new tic + SV_SendTics(); - SV_SendTics(); - - neededtic = maketic; // The server is a client too - } - else - hu_resynching = true; + neededtic = maketic; // The server is a client too } } Net_AckTicker(); @@ -6416,7 +5836,7 @@ rewind_t *CL_SaveRewindPoint(size_t demopos) return NULL; save_p = rewind->savebuffer; - P_SaveNetGame(); + P_SaveNetGame(false); rewind->leveltime = leveltime; rewind->next = rewindhead; rewind->demopos = demopos; @@ -6440,7 +5860,7 @@ rewind_t *CL_RewindToTime(tic_t time) return NULL; save_p = rewindhead->savebuffer; - P_LoadNetGame(); + P_LoadNetGame(false); wipegamestate = gamestate; // No fading back in! timeinmap = leveltime; diff --git a/src/d_clisrv.h b/src/d_clisrv.h index 4f8d098e..4071044f 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -67,8 +67,9 @@ typedef enum PT_REQUESTFILE, // Client requests a file transfer PT_ASKINFOVIAMS, // Packet from the MS requesting info be sent to new client. // If this ID changes, update masterserver definition. - PT_RESYNCHEND, // Player is now resynched and is being requested to remake the gametic - PT_RESYNCHGET, // Player got resynch packet + PT_WILLRESENDGAMESTATE, // Hey Client, I am about to resend you the gamestate! + PT_CANRECEIVEGAMESTATE, // Okay Server, I'm ready to receive it, you can go ahead. + PT_RECEIVEDGAMESTATE, // Thank you Server, I am ready to play again! // Add non-PT_CANFAIL packet types here to avoid breaking MS compatibility. @@ -91,8 +92,6 @@ typedef enum PT_TEXTCMD4, // 4P PT_CLIENTJOIN, // Client wants to join; used in start game. PT_NODETIMEOUT, // Packet sent to self if the connection times out. - PT_RESYNCHING, // Packet sent to resync players. - // Blocks game advance until synched. PT_TELLFILESNEEDED, // Client, to server: "what other files do I need starting from this number?" PT_MOREFILESNEEDED, // Server, to client: "you need these (+ more on top of those)" @@ -166,149 +165,6 @@ typedef struct ticcmd_t cmds[45]; // Normally [BACKUPTIC][MAXPLAYERS] but too large } ATTRPACK servertics_pak; -// Sent to client when all consistency data -// for players has been restored -typedef struct -{ - UINT32 randomseed; - - // CTF flag stuff - SINT8 flagplayer[2]; - INT32 flagloose[2]; - INT32 flagflags[2]; - fixed_t flagx[2]; - fixed_t flagy[2]; - fixed_t flagz[2]; - - UINT32 ingame; // Spectator bit for each player - INT32 ctfteam[MAXPLAYERS]; // Which team? (can't be 1 bit, since in regular Match there are no teams) - - // Resynch game scores and the like all at once - UINT32 score[MAXPLAYERS]; // Everyone's score - UINT32 marescore[MAXPLAYERS]; // SRB2kart: Battle score - tic_t realtime[MAXPLAYERS]; - UINT8 laps[MAXPLAYERS]; -} ATTRPACK resynchend_pak; - -typedef struct -{ - // Player stuff - UINT8 playernum; - - // Do not send anything visual related. - // Only send data that we need to know for physics. - UINT8 playerstate; // playerstate_t - UINT32 pflags; // pflags_t - UINT8 panim; // panim_t - - angle_t aiming; - INT32 currentweapon; - INT32 ringweapons; - - UINT16 powers[NUMPOWERS]; - - INT32 kartstuff[NUMKARTSTUFF]; // SRB2kart - angle_t frameangle; // SRB2kart - - // Score is resynched in the confirm resync packet - INT32 health; - SINT8 lives; - SINT8 continues; - UINT8 scoreadd; - SINT8 xtralife; - SINT8 pity; - - UINT8 skincolor; - INT32 skin; - // Just in case Lua does something like - // modify these at runtime - // SRB2kart - UINT8 kartspeed; - UINT8 kartweight; - // - UINT32 charflags; - - fixed_t speed; - UINT8 jumping; - UINT8 secondjump; - UINT8 fly1; - tic_t glidetime; - UINT8 climbing; - INT32 deadtimer; - tic_t exiting; - UINT8 homing; - tic_t skidtime; - fixed_t cmomx; - fixed_t cmomy; - fixed_t rmomx; - fixed_t rmomy; - - INT32 weapondelay; - INT32 tossdelay; - - INT16 starpostx; - INT16 starposty; - INT16 starpostz; - INT32 starpostnum; - tic_t starposttime; - angle_t starpostangle; - - INT32 maxlink; - fixed_t dashspeed; - INT32 dashtime; - angle_t angle_pos; - angle_t old_angle_pos; - tic_t bumpertime; - INT32 flyangle; - tic_t drilltimer; - INT32 linkcount; - tic_t linktimer; - INT32 anotherflyangle; - tic_t nightstime; - INT32 drillmeter; - UINT8 drilldelay; - UINT8 bonustime; - UINT8 mare; - INT16 lastsidehit, lastlinehit; - - tic_t losstime; - UINT8 timeshit; - INT32 onconveyor; - - tic_t jointime; - tic_t spectatorreentry; - - tic_t grieftime; - UINT8 griefstrikes; - - UINT8 splitscreenindex; - - //player->mo stuff - UINT8 hasmo; // Boolean - - angle_t angle; - fixed_t x; - fixed_t y; - fixed_t z; - fixed_t momx; - fixed_t momy; - fixed_t momz; - fixed_t friction; - fixed_t movefactor; - - INT32 tics; - statenum_t statenum; - UINT32 flags; - UINT32 flags2; - UINT16 eflags; - - fixed_t radius; - fixed_t height; - fixed_t scale; - fixed_t destscale; - fixed_t scalespeed; -} ATTRPACK resynch_pak; - typedef struct { UINT8 version; // Different versions don't work @@ -322,13 +178,8 @@ typedef struct UINT8 clientnode; UINT8 gamestate; - // 0xFF == not in game; else player skin num - UINT8 playerskins[MAXPLAYERS]; - UINT8 playercolor[MAXPLAYERS]; - UINT8 gametype; UINT8 modifiedgame; - SINT8 adminplayers[MAXPLAYERS]; // Needs to be signed char server_context[8]; // Unique context id, generated at server startup. @@ -336,8 +187,6 @@ typedef struct UINT8 maxplayer; boolean allownewplayer; boolean discordinvites; - - UINT8 varlengthinputs[0]; // Playernames and netvars } ATTRPACK serverconfig_pak; typedef struct { @@ -471,9 +320,6 @@ typedef struct client4cmd_pak client4pak; // 316 bytes(?) servertics_pak serverpak; // 132495 bytes (more around 360, no?) serverconfig_pak servercfg; // 773 bytes - resynchend_pak resynchend; // - resynch_pak resynchpak; // - UINT8 resynchgot; // UINT8 textcmd[MAXTEXTCMD+1]; // 66049 bytes (wut??? 64k??? More like 257 bytes...) filetx_pak filetxpak; // 139 bytes clientconfig_pak clientcfg; // 153 bytes @@ -626,7 +472,7 @@ void D_ResetTiccmds(void); tic_t GetLag(INT32 node); UINT8 GetFreeXCmdSize(void); -extern UINT8 hu_resynching; +extern UINT8 hu_redownloadinggamestate; extern UINT8 hu_stopped; // kart, true when the game is stopped for players due to a disconnecting or connecting player typedef struct rewind_s { diff --git a/src/d_main.c b/src/d_main.c index 0d36fc8e..57bd29ca 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -1683,7 +1683,7 @@ void D_SRB2Main(void) pagename = "TITLESKY"; levelstarttic = gametic; G_SetGamestate(GS_LEVEL); - if (!P_SetupLevel(false)) + if (!P_SetupLevel(false, false)) I_Quit(); // fail so reset game stuff } diff --git a/src/d_net.c b/src/d_net.c index 1dcbb379..35145f41 100644 --- a/src/d_net.c +++ b/src/d_net.c @@ -810,8 +810,9 @@ static const char *packettypename[NUMPACKETTYPE] = "REQUESTFILE", "ASKINFOVIAMS", - "RESYNCHEND", - "RESYNCHGET", + "WILLRESENDGAMESTATE", + "CANRECEIVEGAMESTATE", + "RECEIVEDGAMESTATE", "CLIENT3CMD", "CLIENT3MIS", @@ -826,7 +827,6 @@ static const char *packettypename[NUMPACKETTYPE] = "TEXTCMD4", "CLIENTJOIN", "NODETIMEOUT", - "RESYNCHING", "PING" }; diff --git a/src/g_game.c b/src/g_game.c index 9e5fabd4..fb2c9b3e 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -1371,8 +1371,8 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) // why build a ticcmd if we're paused? // Or, for that matter, if we're being reborn. - // Kart, don't build a ticcmd if someone is resynching or the server is stopped too so we don't fly off course in bad conditions - if (paused || P_AutoPause() || (gamestate == GS_LEVEL && player->playerstate == PST_REBORN) || hu_resynching) + // Kart, don't build a ticcmd if the server is stopped too so we don't fly off course in bad conditions + if (paused || P_AutoPause() || (gamestate == GS_LEVEL && player->playerstate == PST_REBORN)) { cmd->angleturn = (INT16)(lang >> 16); cmd->aiming = G_ClipAimingPitch(&laim); @@ -1821,7 +1821,7 @@ void G_DoLoadLevel(boolean resetplayer) } // Setup the level. - if (!P_SetupLevel(false)) + if (!P_SetupLevel(false, false)) // this never returns false? { // fail so reset game stuff Command_ExitGame_f(); diff --git a/src/hu_stuff.c b/src/hu_stuff.c index a639b5a8..bf8a4a91 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -1118,7 +1118,7 @@ void HU_Ticker(void) if (cechotimer > 0) --cechotimer; // Animate the desynch dots - if (hu_resynching) + if (hu_redownloadinggamestate) resynch_ticker++; //tic tic tic tic tic HU_TickSongCredits(); @@ -2442,7 +2442,7 @@ void HU_Drawer(void) HU_DrawSongCredits(); // draw desynch text - if (hu_resynching) + if (hu_redownloadinggamestate) { char resynch_text[14]; UINT32 i; diff --git a/src/p_saveg.c b/src/p_saveg.c index f553ddf6..9ea3740b 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -117,13 +117,16 @@ static void P_NetArchivePlayers(void) for (i = 0; i < MAXPLAYERS; i++) { + WRITESINT8(save_p, (SINT8)adminplayers[i]); + if (!playeringame[i]) continue; flags = 0; - // no longer send ticcmds, player name, skin, or color + // no longer send ticcmds + WRITESTRINGN(save_p, player_names[i], MAXPLAYERNAME); WRITEANGLE(save_p, players[i].aiming); WRITEANGLE(save_p, players[i].awayviewaiming); WRITEINT32(save_p, players[i].awayviewtics); @@ -148,6 +151,8 @@ static void P_NetArchivePlayers(void) WRITEUINT16(save_p, players[i].flashpal); WRITEUINT16(save_p, players[i].flashcount); + WRITEUINT8(save_p, players[i].skincolor); + WRITEINT32(save_p, players[i].skin); WRITEUINT32(save_p, players[i].score); WRITEFIXED(save_p, players[i].dashspeed); WRITEINT32(save_p, players[i].dashtime); @@ -295,6 +300,8 @@ static void P_NetUnArchivePlayers(void) for (i = 0; i < MAXPLAYERS; i++) { + adminplayers[i] = (INT32)READSINT8(save_p); + // Do NOT memset player struct to 0 // other areas may initialize data elsewhere //memset(&players[i], 0, sizeof (player_t)); @@ -302,9 +309,8 @@ static void P_NetUnArchivePlayers(void) continue; // NOTE: sending tics should (hopefully) no longer be necessary - // sending player names, skin and color should not be necessary at all! - // (that data is handled in the server config now) + READSTRINGN(save_p, player_names[i], MAXPLAYERNAME); players[i].aiming = READANGLE(save_p); players[i].awayviewaiming = READANGLE(save_p); players[i].awayviewtics = READINT32(save_p); @@ -329,6 +335,8 @@ static void P_NetUnArchivePlayers(void) players[i].flashpal = READUINT16(save_p); players[i].flashcount = READUINT16(save_p); + players[i].skincolor = READUINT8(save_p); + players[i].skin = READINT32(save_p); players[i].score = READUINT32(save_p); players[i].dashspeed = READFIXED(save_p); // dashing speed players[i].dashtime = READINT32(save_p); // dashing speed @@ -3209,18 +3217,21 @@ static inline void P_UnArchiveSPGame(INT16 mapoverride) playeringame[consoleplayer] = true; } -static void P_NetArchiveMisc(void) +static void P_NetArchiveMisc(boolean resending) { UINT32 pig = 0; INT32 i; WRITEUINT32(save_p, ARCHIVEBLOCK_MISC); + if (resending) + WRITEUINT32(save_p, gametic); WRITEINT16(save_p, gamemap); if (gamestate != GS_LEVEL) WRITEINT16(save_p, GS_WAITINGPLAYERS); // nice hack to put people back into waitingplayers else WRITEINT16(save_p, gamestate); + WRITEINT16(save_p, gametype); for (i = 0; i < MAXPLAYERS; i++) pig |= (playeringame[i] != 0)<mobj_scale; @@ -2349,7 +2347,10 @@ static void P_LevelInitStuff(void) // circuit, race and competition stuff circuitmap = false; numstarposts = 0; - totalrings = timeinmap = 0; + totalrings = 0; + + if (!reloadinggamestate) + timeinmap = 0; // special stage stagefailed = false; @@ -2458,7 +2459,7 @@ void P_LoadThingsOnly(void) P_RemoveMobj(mo); } - P_LevelInitStuff(); + P_LevelInitStuff(false); if (W_IsLumpWad(lastloadedmaplumpnum)) // welp it's a map wad in a pk3 { // HACK: Open wad file rather quickly so we can use the things lump @@ -2470,6 +2471,9 @@ void P_LoadThingsOnly(void) } else // phew it's just a WAD P_PrepareThings(lastloadedmaplumpnum + ML_THINGS); + + memset(localaiming, 0, sizeof(localaiming)); + P_LoadThings(); P_SpawnSecretItems(true); @@ -2801,12 +2805,39 @@ static boolean P_CanSave(void) #endif } +static void P_InitCamera(void) +{ + INT32 i; + + if (!dedicated) + { + if (!demo.freecam) + for (i = 0; i <= splitscreen; i++) + P_SetupCamera(displayplayers[i], &camera[i]); + + // Though, I don't think anyone would care about cam_rotate being reset back to the only value that makes sense :P + if (!cv_cam_rotate.changed) + CV_Set(&cv_cam_rotate, cv_cam_rotate.defaultvalue); + + if (!cv_cam2_rotate.changed) + CV_Set(&cv_cam2_rotate, cv_cam2_rotate.defaultvalue); + + if (!cv_cam3_rotate.changed) + CV_Set(&cv_cam3_rotate, cv_cam3_rotate.defaultvalue); + + if (!cv_cam4_rotate.changed) + CV_Set(&cv_cam4_rotate, cv_cam4_rotate.defaultvalue); + + displayplayers[0] = consoleplayer; // Start with your OWN view, please! + } +} + /** Loads a level from a lump or external wad. * * \param skipprecip If true, don't spawn precipitation. * \todo Clean up, refactor, split up; get rid of the bloat. */ -boolean P_SetupLevel(boolean skipprecip) +boolean P_SetupLevel(boolean skipprecip, boolean reloadinggamestate) { // use gamemap to get map number. // 99% of the things already did, so. @@ -2844,7 +2875,7 @@ boolean P_SetupLevel(boolean skipprecip) if (cv_runscripts.value && mapheaderinfo[gamemap-1]->scriptname[0] != '#') P_RunLevelScript(mapheaderinfo[gamemap-1]->scriptname); - P_LevelInitStuff(); + P_LevelInitStuff(reloadinggamestate); for (i = 0; i <= splitscreen; i++) postimgtype[i] = postimg_none; @@ -2857,12 +2888,8 @@ boolean P_SetupLevel(boolean skipprecip) // chasecam off in match, tag, capture the flag chase = true; // srb2kart: always on - if (!dedicated) + if (!dedicated && !reloadinggamestate) { - // Salt: CV_ClearChangedFlags() messes with your settings :( - /*if (!cv_cam_speed.changed) - CV_Set(&cv_cam_speed, cv_cam_speed.defaultvalue);*/ - if (!cv_chasecam.changed) CV_SetValue(&cv_chasecam, chase); @@ -2881,9 +2908,12 @@ boolean P_SetupLevel(boolean skipprecip) // will be set by player think. players[consoleplayer].viewz = 1; + // Cancel all d_main.c fadeouts (keep fade in though). + if (reloadinggamestate) + wipegamestate = gamestate; // Don't fade if reloading the gamestate // Encore mode fade to pink to white // This is handled BEFORE sounds are stopped. - if (encoremode && !prevencoremode && !demo.rewinding) + else if (encoremode && !prevencoremode && !demo.rewinding) { tic_t locstarttime, endtime, nowtime; @@ -2940,15 +2970,16 @@ boolean P_SetupLevel(boolean skipprecip) // As oddly named as this is, this handles music only. // We should be fine starting it here. - S_Start(); + if (!reloadinggamestate) + S_Start(); levelfadecol = (encoremode && !ranspecialwipe ? 122 : 120); // Let's fade to white here // But only if we didn't do the encore startup wipe - if (!ranspecialwipe && !demo.rewinding) + if (!ranspecialwipe && !demo.rewinding && !reloadinggamestate) { - if(rendermode != render_none) + if (rendermode != render_none) { F_WipeStartScreen(); V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, levelfadecol); @@ -3282,74 +3313,12 @@ boolean P_SetupLevel(boolean skipprecip) // landing point for netgames. netgameskip: - if (!dedicated) + if (!reloadinggamestate) { - if (!demo.freecam) - for (i = 0; i <= splitscreen; i++) - P_SetupCamera(displayplayers[i], &camera[i]); - - // Salt: CV_ClearChangedFlags() messes with your settings :( - /*if (!cv_cam_height.changed) - CV_Set(&cv_cam_height, cv_cam_height.defaultvalue); - - if (!cv_cam_dist.changed) - CV_Set(&cv_cam_dist, cv_cam_dist.defaultvalue); - - if (!cv_cam2_height.changed) - CV_Set(&cv_cam2_height, cv_cam2_height.defaultvalue); - - if (!cv_cam2_dist.changed) - CV_Set(&cv_cam2_dist, cv_cam2_dist.defaultvalue);*/ - - // Though, I don't think anyone would care about cam_rotate being reset back to the only value that makes sense :P - if (!cv_cam_rotate.changed) - CV_Set(&cv_cam_rotate, cv_cam_rotate.defaultvalue); - - if (!cv_cam2_rotate.changed) - CV_Set(&cv_cam2_rotate, cv_cam2_rotate.defaultvalue); - - if (!cv_cam3_rotate.changed) - CV_Set(&cv_cam3_rotate, cv_cam3_rotate.defaultvalue); - - if (!cv_cam4_rotate.changed) - CV_Set(&cv_cam4_rotate, cv_cam4_rotate.defaultvalue); - - /*if (!cv_analog.changed) - CV_SetValue(&cv_analog, 0); - if (!cv_analog2.changed) - CV_SetValue(&cv_analog2, 0); - if (!cv_analog3.changed) - CV_SetValue(&cv_analog3, 0); - if (!cv_analog4.changed) - CV_SetValue(&cv_analog4, 0);*/ - - // Shouldn't be necessary with render parity? - /*if (rendermode != render_none) - CV_Set(&cv_fov, cv_fov.defaultvalue);*/ - - displayplayers[0] = consoleplayer; // Start with your OWN view, please! + P_InitCamera(); + memset(localaiming, 0, sizeof(localaiming)); } - /*if (cv_useranalog.value) - CV_SetValue(&cv_analog, true); - - if ((splitscreen && cv_useranalog2.value) || botingame) - CV_SetValue(&cv_analog2, true); - - if (splitscreen > 1 && cv_useranalog3.value) - CV_SetValue(&cv_analog3, true); - - if (splitscreen > 2 && cv_useranalog4.value) - CV_SetValue(&cv_analog4, true); - - if (twodlevel) - { - CV_SetValue(&cv_analog4, false); - CV_SetValue(&cv_analog3, false); - CV_SetValue(&cv_analog2, false); - CV_SetValue(&cv_analog, false); - }*/ - // clear special respawning que iquehead = iquetail = 0; @@ -3367,7 +3336,7 @@ boolean P_SetupLevel(boolean skipprecip) P_MapEnd(); // Remove the loading shit from the screen - if (rendermode != render_none) + if (rendermode != render_none && !reloadinggamestate) V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, levelfadecol); if (precache || dedicated) diff --git a/src/p_setup.h b/src/p_setup.h index 5ff53e13..eadaba12 100644 --- a/src/p_setup.h +++ b/src/p_setup.h @@ -59,7 +59,7 @@ void P_SetupLevelSky(INT32 skynum, boolean global); void P_ScanThings(INT16 mapnum, INT16 wadnum, INT16 lumpnum); #endif void P_LoadThingsOnly(void); -boolean P_SetupLevel(boolean skipprecip); +boolean P_SetupLevel(boolean skipprecip, boolean reloadinggamestate); boolean P_AddWadFile(const char *wadfilename); #ifdef DELFILE boolean P_DelWadFile(void);