diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 3dc641cdd..9c79d8ef6 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -1461,7 +1461,7 @@ static void SV_SendPlayerInfo(INT32 node) */ static boolean SV_SendServerConfig(INT32 node) { - INT32 i; + INT32 i, j; UINT8 *p, *op; boolean waspacketsent; @@ -1485,9 +1485,15 @@ static boolean SV_SendServerConfig(INT32 node) memset(netbuffer->u.servercfg.adminplayers, -1, sizeof(netbuffer->u.servercfg.adminplayers)); + for (i = 0; i < MAXPLAYERS; i++) + for (j = 0; j < 2; j++) + netbuffer->u.servercfg.powerlevels[i][j] = 0; // Not sure if memset works on something like this + for (i = 0; i < MAXPLAYERS; i++) { netbuffer->u.servercfg.adminplayers[i] = (SINT8)adminplayers[i]; + for (j = 0; j < 2; j++) + netbuffer->u.servercfg.powerlevels[i][j] = clientpowerlevels[i][j]; if (!playeringame[i]) continue; @@ -2163,6 +2169,7 @@ static void CL_ConnectToServer(boolean viams) wipegamestate = GS_WAITINGPLAYERS; ClearAdminPlayers(); + ClearClientPowerLevels(); pnumnodes = 1; oldtic = I_GetTime() - 1; #ifndef NONET @@ -2971,6 +2978,21 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum) break; } + // SRB2Kart: kicks count as forfeit + switch (kickreason) + { + case KR_KICK: + case KR_BAN: + case KR_LEAVE: + // Intentional removals should be hit with a true forfeit. + K_PlayerForfeit(pnum, true); + break; + default: + // Otherwise, give remaining players the point compensation, but doesn't penalize who left. + K_PlayerForfeit(pnum, false); + break; + } + if (playernode[pnum] == playernode[consoleplayer]) { #ifdef DUMPCONSISTENCY @@ -3145,6 +3167,7 @@ void SV_ResetServer(void) playernode[i] = UINT8_MAX; sprintf(player_names[i], "Player %d", i + 1); adminplayers[i] = -1; // Populate the entire adminplayers array with -1. + ClearClientPowerLevels(); } mynode = 0; @@ -3220,6 +3243,7 @@ void D_QuitNetGame(void) D_CloseConnection(); ClearAdminPlayers(); + ClearClientPowerLevels(); DEBFILE("===========================================================================\n" " Log finish\n" @@ -3247,8 +3271,7 @@ static inline void SV_AddNode(INT32 node) // Xcmd XD_ADDPLAYER static void Got_AddPlayer(UINT8 **p, INT32 playernum) { - INT16 node, newplayernum; - UINT8 splitscreenplayer = 0; + UINT8 node, newplayernum, splitscreenplayer; if (playernum != serverplayer && !IsPlayerAdmin(playernum)) { @@ -3265,11 +3288,12 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum) return; } - node = READUINT8(*p); - newplayernum = READUINT8(*p); - splitscreenplayer = newplayernum/MAXPLAYERS; - newplayernum %= MAXPLAYERS; + node = (UINT8)READUINT8(*p); + newplayernum = (UINT8)READUINT8(*p); + splitscreenplayer = (UINT8)READUINT8(*p); + CONS_Printf("addplayer: %d %d %d\n", node, newplayernum, splitscreenplayer); + // Clear player before joining, lest some things get set incorrectly CL_ClearPlayer(newplayernum); @@ -3282,28 +3306,8 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum) if (node == mynode) { playernode[newplayernum] = 0; // for information only - if (splitscreenplayer) - { - if (splitscreenplayer == 1) - { - secondarydisplayplayer = newplayernum; - DEBFILE("spawning my brother\n"); - if (botingame) - players[newplayernum].bot = 1; - // Same goes for player 2 when relevant - } - else if (splitscreenplayer == 2) - { - thirddisplayplayer = newplayernum; - DEBFILE("spawning my sister\n"); - } - else if (splitscreenplayer == 3) - { - fourthdisplayplayer = newplayernum; - DEBFILE("spawning my trusty pet dog\n"); - } - } - else + + if (splitscreenplayer == 0) { consoleplayer = newplayernum; displayplayer = newplayernum; @@ -3312,6 +3316,25 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum) fourthdisplayplayer = newplayernum; DEBFILE("spawning me\n"); } + else if (splitscreenplayer == 1) + { + secondarydisplayplayer = newplayernum; + DEBFILE("spawning my brother\n"); + if (botingame) + players[newplayernum].bot = 1; + // Same goes for player 2 when relevant + } + else if (splitscreenplayer == 2) + { + thirddisplayplayer = newplayernum; + DEBFILE("spawning my sister\n"); + } + else if (splitscreenplayer == 3) + { + fourthdisplayplayer = newplayernum; + DEBFILE("spawning my trusty pet dog\n"); + } + D_SendPlayerConfig(); addedtogame = true; } @@ -3365,8 +3388,9 @@ static void Got_RemovePlayer(UINT8 **p, INT32 playernum) static boolean SV_AddWaitingPlayers(void) { INT32 node, n, newplayer = false; - XBOXSTATIC UINT8 buf[2]; UINT8 newplayernum = 0; + static UINT8 buf[3]; + static UINT8 *buf_p = buf; // What is the reason for this? Why can't newplayernum always be 0? // Sal: Because the dedicated player is stupidly forced into players[0]..... @@ -3385,8 +3409,10 @@ static boolean SV_AddWaitingPlayers(void) for (; newplayernum < MAXPLAYERS; newplayernum++) { for (n = 0; n < MAXNETNODES; n++) - if (nodetoplayer[n] == newplayernum || nodetoplayer2[n] == newplayernum - || nodetoplayer3[n] == newplayernum || nodetoplayer4[n] == newplayernum) + if (nodetoplayer[n] == newplayernum + || nodetoplayer2[n] == newplayernum + || nodetoplayer3[n] == newplayernum + || nodetoplayer4[n] == newplayernum) break; if (n == MAXNETNODES) break; @@ -3398,28 +3424,23 @@ static boolean SV_AddWaitingPlayers(void) playernode[newplayernum] = (UINT8)node; - buf[0] = (UINT8)node; - buf[1] = newplayernum; + WRITEUINT8(buf_p, (UINT8)node); + WRITEUINT8(buf_p, newplayernum); + if (playerpernode[node] < 1) nodetoplayer[node] = newplayernum; else if (playerpernode[node] < 2) - { nodetoplayer2[node] = newplayernum; - buf[1] += MAXPLAYERS; - } else if (playerpernode[node] < 3) - { nodetoplayer3[node] = newplayernum; - buf[1] += MAXPLAYERS*2; - } - else - { + else if (playerpernode[node] < 4) nodetoplayer4[node] = newplayernum; - buf[1] += MAXPLAYERS*3; - } + + WRITEUINT8(buf_p, playerpernode[node]); // splitscreen num + playerpernode[node]++; - SendNetXCmd(XD_ADDPLAYER, &buf, 2); + SendNetXCmd(XD_ADDPLAYER, buf, buf_p - buf); DEBFILE(va("Server added player %d node %d\n", newplayernum, node)); // use the next free slot (we can't put playeringame[newplayernum] = true here) @@ -3775,7 +3796,7 @@ static void HandlePacketFromAwayNode(SINT8 node) case PT_SERVERCFG: // Positive response of client join request { - INT32 j; + INT32 j, k; UINT8 *scp; if (server && serverrunning && node != servernode) @@ -3795,7 +3816,11 @@ static void HandlePacketFromAwayNode(SINT8 node) I_Error("Bad gametype in cliserv!"); modifiedgame = netbuffer->u.servercfg.modifiedgame; for (j = 0; j < MAXPLAYERS; j++) + { adminplayers[j] = netbuffer->u.servercfg.adminplayers[j]; + for (k = 0; k < 2; k++) + clientpowerlevels[j][k] = netbuffer->u.servercfg.powerlevels[j][k]; + } memcpy(server_context, netbuffer->u.servercfg.server_context, 8); } diff --git a/src/d_clisrv.h b/src/d_clisrv.h index 5cf72bbc7..1c85144eb 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -328,6 +328,7 @@ typedef struct UINT8 gametype; UINT8 modifiedgame; SINT8 adminplayers[MAXPLAYERS]; // Needs to be signed + UINT16 powerlevels[MAXPLAYERS][2]; // SRB2kart: player power levels char server_context[8]; // Unique context id, generated at server startup. diff --git a/src/d_netcmd.c b/src/d_netcmd.c index e15ed9aac..4f42a6bef 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -61,6 +61,7 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum); static void Got_WeaponPref(UINT8 **cp, INT32 playernum); +static void Got_PowerLevel(UINT8 **cp, INT32 playernum); static void Got_Mapcmd(UINT8 **cp, INT32 playernum); static void Got_ExitLevelcmd(UINT8 **cp, INT32 playernum); static void Got_SetupVotecmd(UINT8 **cp, INT32 playernum); @@ -455,6 +456,7 @@ boolean deferencoremode = false; UINT8 splitscreen = 0; boolean circuitmap = true; // SRB2kart INT32 adminplayers[MAXPLAYERS]; +UINT16 clientpowerlevels[MAXPLAYERS][2]; /// \warning Keep this up-to-date if you add/remove/rename net text commands const char *netxcmdnames[MAXNETXCMD - 1] = @@ -483,6 +485,8 @@ const char *netxcmdnames[MAXNETXCMD - 1] = "SETUPVOTE", "MODIFYVOTE", "PICKVOTE", + "REMOVEPLAYER", + "POWERLEVEL", #ifdef HAVE_BLUA "LUACMD", "LUAVAR" @@ -502,6 +506,7 @@ void D_RegisterServerCommands(void) { RegisterNetXCmd(XD_NAMEANDCOLOR, Got_NameAndColor); RegisterNetXCmd(XD_WEAPONPREF, Got_WeaponPref); + RegisterNetXCmd(XD_POWERLEVEL, Got_PowerLevel); RegisterNetXCmd(XD_MAP, Got_Mapcmd); RegisterNetXCmd(XD_EXITLEVEL, Got_ExitLevelcmd); RegisterNetXCmd(XD_ADDFILE, Got_Addfilecmd); @@ -1864,6 +1869,16 @@ static void Got_WeaponPref(UINT8 **cp,INT32 playernum) players[playernum].pflags |= PF_ANALOGMODE; } +static void Got_PowerLevel(UINT8 **cp,INT32 playernum) +{ + UINT16 race = (UINT16)READUINT16(*cp); + UINT16 battle = (UINT16)READUINT16(*cp); + + clientpowerlevels[playernum][0] = min(9999, race); + clientpowerlevels[playernum][1] = min(9999, battle); + CONS_Printf("set player %d to power %d\n", playernum, race); +} + void D_SendPlayerConfig(void) { SendNameAndColor(); @@ -1880,6 +1895,31 @@ void D_SendPlayerConfig(void) SendWeaponPref3(); if (splitscreen > 2) SendWeaponPref4(); + + { + UINT8 buf[4]; + UINT8 *buf_p = buf; + + WRITEUINT16(buf_p, vspowerlevel[0]); + WRITEUINT16(buf_p, vspowerlevel[1]); + + SendNetXCmd(XD_POWERLEVEL, buf, 4); + } + + if (splitscreen) + { + UINT8 buf[4]; + UINT8 *buf_p = buf; + + WRITEUINT16(buf_p, 0); + WRITEUINT16(buf_p, 0); + + SendNetXCmd2(XD_POWERLEVEL, buf, 4); + if (splitscreen > 1) + SendNetXCmd3(XD_POWERLEVEL, buf, 4); + if (splitscreen > 2) + SendNetXCmd4(XD_POWERLEVEL, buf, 4); + } } // Only works for displayplayer, sorry! @@ -3373,6 +3413,8 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum) if (K_IsPlayerWanted(&players[playernum])) K_CalculateBattleWanted(); } + K_PlayerForfeit(playernum, true); + players[playernum].health = 1; if (players[playernum].mo) players[playernum].mo->health = 1; @@ -3521,6 +3563,14 @@ static void Got_Login(UINT8 **cp, INT32 playernum) #endif } +void ClearClientPowerLevels(void) +{ + INT32 i, j; + for (i = 0; i < MAXPLAYERS; i++) + for (j = 0; j < 2; j++) + clientpowerlevels[i][j] = 0; +} + boolean IsPlayerAdmin(INT32 playernum) { INT32 i; diff --git a/src/d_netcmd.h b/src/d_netcmd.h index 7927aea00..22fc80da2 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -176,9 +176,10 @@ typedef enum XD_MODIFYVOTE, // 23 XD_PICKVOTE, // 24 XD_REMOVEPLAYER,// 25 + XD_POWERLEVEL, // 26 #ifdef HAVE_BLUA - XD_LUACMD, // 26 - XD_LUAVAR, // 27 + XD_LUACMD, // 27 + XD_LUAVAR, // 28 #endif MAXNETXCMD } netxcmd_t; @@ -237,6 +238,7 @@ void D_SetupVote(void); void D_ModifyClientVote(SINT8 voted, UINT8 splitplayer); void D_PickVote(void); void ObjectPlace_OnChange(void); +void ClearClientPowerLevels(void); boolean IsPlayerAdmin(INT32 playernum); void SetAdminPlayer(INT32 playernum); void ClearAdminPlayers(void); diff --git a/src/doomstat.h b/src/doomstat.h index 5831d4f47..4826e94fd 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -332,7 +332,7 @@ enum GameType // SRB2Kart extern tic_t totalplaytime; extern UINT32 matchesplayed; -extern UINT16 versusrecord[2]; +extern UINT16 vspowerlevel[2]; extern UINT8 stagefailed; @@ -467,7 +467,7 @@ extern SINT8 battlewanted[4]; extern tic_t wantedcalcdelay; extern tic_t indirectitemcooldown; extern tic_t mapreset; -extern UINT8 nospectategrief; +extern INT16 nospectategrief[MAXPLAYERS]; extern boolean thwompsactive; extern SINT8 spbplace; @@ -547,6 +547,7 @@ extern consvar_t cv_maxping; extern ticcmd_t netcmds[BACKUPTICS][MAXPLAYERS]; extern INT32 serverplayer; extern INT32 adminplayers[MAXPLAYERS]; +extern UINT16 clientpowerlevels[MAXPLAYERS][2]; /// \note put these in d_clisrv outright? diff --git a/src/g_game.c b/src/g_game.c index 8292004a6..ec085914d 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -169,7 +169,7 @@ INT32 sstimer; // Time allotted in the special stage tic_t totalplaytime; UINT32 matchesplayed; // SRB2Kart -UINT16 versusrecord[2]; // SRB2Kart: Online rankings +UINT16 vspowerlevel[2]; // SRB2Kart: Online rankings for each gametype boolean gamedataloaded = false; // Time attack data for levels @@ -267,7 +267,7 @@ SINT8 battlewanted[4]; // WANTED players in battle, worth x2 points tic_t wantedcalcdelay; // Time before it recalculates WANTED tic_t indirectitemcooldown; // Cooldown before any more Shrink, SPB, or any other item that works indirectly is awarded tic_t mapreset; // Map reset delay when enough players have joined an empty game -UINT8 nospectategrief; // How many players need to be in-game to eliminate last; for preventing spectate griefing +INT16 nospectategrief[MAXPLAYERS]; // Which players spec-scummed, and their power level before scumming. boolean thwompsactive; // Thwomps activate on lap 2 SINT8 spbplace; // SPB exists, give the person behind better items @@ -3782,8 +3782,8 @@ void G_LoadGameData(void) totalplaytime = 0; // total play time (separate from all) matchesplayed = 0; // SRB2Kart: matches played & finished - for (i = 0; i < 2; i++) // SRB2Kart: online VR system - versusrecord[i] = 5000; + for (i = 0; i < 2; i++) // SRB2Kart: online rank system + vspowerlevel[i] = 5000; if (M_CheckParm("-nodata")) return; // Don't load. @@ -3816,7 +3816,7 @@ void G_LoadGameData(void) matchesplayed = READUINT32(save_p); for (i = 0; i < 2; i++) - versusrecord[i] = READUINT16(save_p); + vspowerlevel[i] = READUINT16(save_p); modded = READUINT8(save_p); @@ -3967,7 +3967,7 @@ void G_SaveGameData(boolean force) WRITEUINT32(save_p, matchesplayed); for (i = 0; i < 2; i++) - WRITEUINT16(save_p, versusrecord[i]); + WRITEUINT16(save_p, vspowerlevel[i]); btemp = (UINT8)(savemoddata || modifiedgame); WRITEUINT8(save_p, btemp); diff --git a/src/k_kart.c b/src/k_kart.c index 476bff462..fdfdc4933 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -31,7 +31,7 @@ // battlewanted is an array of the WANTED player nums, -1 for no player in that slot // indirectitemcooldown is timer before anyone's allowed another Shrink/SPB // mapreset is set when enough players fill an empty server -// nospectategrief is the players in-game needed to eliminate the person in last +// nospectategrief is a list of griefers' power levels //{ SRB2kart Color Code @@ -5741,6 +5741,115 @@ void K_CheckBumpers(void) P_DoPlayerExit(&players[i]); } +// Adapted from this: http://wiki.tockdom.com/wiki/Player_Rating +INT16 K_CalculatePowerLevelInc(INT16 diff) +{ + INT16 control[10] = {0,0,0,1,8,50,125,125,125,125}; + fixed_t increment = 0; + fixed_t x; + UINT8 j; + +#define MAXDIFF 9998 + if (diff > MAXDIFF) + diff = MAXDIFF; + if (diff < -MAXDIFF) + diff = -MAXDIFF; +#undef MAXDIFF + + x = ((diff-2)<= (2<= (1<> FRACBITS); +} + +void K_PlayerForfeit(UINT8 playernum, boolean nopointloss) +{ + INT32 powertype = -1; + UINT16 yourpower = 5000; + UINT16 theirpower = 5000; + INT16 diff = 0; // Loser PWR.LV - Winner PWR.LV + INT16 inc = 0; + UINT8 i; + + // power level is netgames only + if (!netgame) + return; + + // 20 sec into the match counts as a forfeit -- automatic loss against every other player in the match. + if (gamestate != GS_LEVEL || leveltime <= starttime+(20*TICRATE)) + return; + + if (G_RaceGametype()) + powertype = 0; + else if (G_BattleGametype()) + powertype = 1; + + if (powertype == -1) // Not using power levels + return; + + if (clientpowerlevels[playernum][powertype] == 0) // splitscreen guests don't record power level changes + return; + yourpower = clientpowerlevels[playernum][powertype]; + + // Set up the point compensation. + nospectategrief[playernum] = yourpower; + + if (nopointloss) // This is set for stuff like sync-outs, which shouldn't be so harsh on the victim! + return; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (i == playernum) + continue; + + theirpower = 5000; + if (clientpowerlevels[i][powertype] != 0) // No power level acts as 5000 (used for splitscreen guests) + theirpower = clientpowerlevels[i][powertype]; + + diff = yourpower - theirpower; + inc -= K_CalculatePowerLevelInc(diff); + } + + if (inc == 0) // No change. + return; + + if (yourpower + inc > 9999) // I mean... we're subtracting... but y'know how it is :V + inc -= ((yourpower + inc) - 9999); + if (yourpower + inc < 1) + inc -= ((yourpower + inc) - 1); + + clientpowerlevels[playernum][powertype] += inc; + + if (playernum == consoleplayer) + { + vspowerlevel[powertype] = clientpowerlevels[playernum][powertype]; + if (M_UpdateUnlockablesAndExtraEmblems(true)) + S_StartSound(NULL, sfx_ncitem); + G_SaveGameData(true); // save your punishment! + } +} + void K_CheckSpectateStatus(void) { UINT8 respawnlist[MAXPLAYERS]; diff --git a/src/k_kart.h b/src/k_kart.h index 3f7120913..ce5203675 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -63,6 +63,8 @@ fixed_t K_3dKartMovement(player_t *player, boolean onground, fixed_t forwardmove void K_MoveKartPlayer(player_t *player, boolean onground); void K_CalculateBattleWanted(void); void K_CheckBumpers(void); +INT16 K_CalculatePowerLevelInc(INT16 diff); +void K_PlayerForfeit(UINT8 playernum, boolean nopointloss); void K_CheckSpectateStatus(void); const char *K_GetItemPatch(UINT8 item, boolean tiny); diff --git a/src/m_menu.c b/src/m_menu.c index a8c5ad486..bcbc6cb78 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -6252,7 +6252,7 @@ static void M_Statistics(INT32 choice) static void M_DrawStatsMaps(int location) { - INT32 y = 80, i = -1; + INT32 y = 88, i = -1; INT16 mnum; extraemblem_t *exemblem; boolean dotopname = true, dobottomarrow = (location < statsMax); @@ -6365,11 +6365,9 @@ static void M_DrawLevelStats(void) V_DrawString(20, 42, highlightflags, "Total Matches:"); V_DrawRightAlignedString(BASEVIDWIDTH-16, 42, 0, va("%i played", matchesplayed)); - V_DrawString(20, 50, highlightflags, "Race Versus Record:"); - V_DrawRightAlignedString(BASEVIDWIDTH-16, 50, 0, va("%i", versusrecord[0])); - - V_DrawString(20, 58, highlightflags, "Battle Versus Record:"); - V_DrawRightAlignedString(BASEVIDWIDTH-16, 58, 0, va("%i", versusrecord[1])); + V_DrawString(20, 52, highlightflags, "Online Power Level:"); + V_DrawRightAlignedString(BASEVIDWIDTH-16, 52, 0, va("Race: %i", vspowerlevel[0])); + V_DrawRightAlignedString(BASEVIDWIDTH-16, 60, 0, va("Battle: %i", vspowerlevel[1])); for (i = 0; i < NUMMAPS; i++) { @@ -6385,18 +6383,18 @@ static void M_DrawLevelStats(void) besttime += mainrecords[i]->time; } - V_DrawString(20, 62, highlightflags, "Combined time records:"); + V_DrawString(20, 70, highlightflags, "Combined time records:"); sprintf(beststr, "%i:%02i:%02i.%02i", G_TicsToHours(besttime), G_TicsToMinutes(besttime, false), G_TicsToSeconds(besttime), G_TicsToCentiseconds(besttime)); - V_DrawRightAlignedString(BASEVIDWIDTH-16, 62, (mapsunfinished ? warningflags : 0), beststr); + V_DrawRightAlignedString(BASEVIDWIDTH-16, 70, (mapsunfinished ? warningflags : 0), beststr); if (mapsunfinished) - V_DrawRightAlignedString(BASEVIDWIDTH-16, 70, warningflags, va("(%d unfinished)", mapsunfinished)); + V_DrawRightAlignedString(BASEVIDWIDTH-16, 78, warningflags, va("(%d unfinished)", mapsunfinished)); else - V_DrawRightAlignedString(BASEVIDWIDTH-16, 70, recommendedflags, "(complete)"); + V_DrawRightAlignedString(BASEVIDWIDTH-16, 78, recommendedflags, "(complete)"); - V_DrawString(32, 70, 0, va("x %d/%d", M_CountEmblems(), numemblems+numextraemblems)); - V_DrawSmallScaledPatch(20, 70, 0, W_CachePatchName("GOTITA", PU_STATIC)); + V_DrawString(32, 78, V_ALLOWLOWERCASE, va("x %d/%d", M_CountEmblems(), numemblems+numextraemblems)); + V_DrawSmallScaledPatch(20, 78, 0, W_CachePatchName("GOTITA", PU_STATIC)); M_DrawStatsMaps(statsLocation); } @@ -8515,7 +8513,7 @@ static void M_EraseDataResponse(INT32 ch) totalplaytime = 0; matchesplayed = 0; for (i = 0; i < 2; i++) - versusrecord[i] = 5000; + vspowerlevel[i] = 5000; F_StartIntro(); } if (erasecontext != 1) diff --git a/src/p_inter.c b/src/p_inter.c index dd27858fc..a4f1fe84b 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -2010,7 +2010,8 @@ void P_CheckPointLimit(void) // Checks whether or not to end a race netgame. boolean P_CheckRacers(void) { - INT32 i, j, numplayersingame = 0; + INT32 i, j, numplayersingame = 0, numexiting = 0; + boolean griefed = false; // Check if all the players in the race have finished. If so, end the level. for (i = 0; i < MAXPLAYERS; i++) @@ -2027,57 +2028,53 @@ boolean P_CheckRacers(void) return true; } - if (cv_karteliminatelast.value) + for (j = 0; j < MAXPLAYERS; j++) { - for (j = 0; j < MAXPLAYERS; j++) + if (nospectategrief[j] != -1) // prevent spectate griefing + griefed = true; + if (!playeringame[j] || players[j].spectator) + continue; + numplayersingame++; + if (players[j].exiting) + numexiting++; + } + + if (cv_karteliminatelast.value && numplayersingame > 1 && !griefed) + { + // check if we just got unlucky and there was only one guy who was a problem + for (j = i+1; j < MAXPLAYERS; j++) { - if (!playeringame[j] || players[j].spectator) + if (!playeringame[j] || players[j].spectator || players[j].exiting || !players[j].lives) continue; - numplayersingame++; + break; } - if (numplayersingame > 1 && nospectategrief > 0 && numplayersingame >= nospectategrief) // prevent spectate griefing + if (j == MAXPLAYERS) // finish anyways, force a time over { - // check if we just got unlucky and there was only one guy who was a problem - for (j = i+1; j < MAXPLAYERS; j++) - { - if (!playeringame[j] || players[j].spectator || players[j].exiting || !players[j].lives) - continue; - - break; - } - - if (j == MAXPLAYERS) // finish anyways, force a time over - { - P_DoTimeOver(&players[i]); - countdown = countdown2 = 0; - return true; - } + P_DoTimeOver(&players[i]); + countdown = countdown2 = 0; + return true; } } if (!countdown) // Check to see if the winners have finished, to set countdown. { - UINT8 numingame = 0, numexiting = 0; UINT8 winningpos = 1; - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i] || players[i].spectator) - continue; - numingame++; - if (players[i].exiting) - numexiting++; - } - - winningpos = max(1, numingame/2); - if (numingame % 2) // any remainder? + winningpos = max(1, numplayersingame/2); + if (numplayersingame % 2) // any remainder? winningpos++; if (numexiting >= winningpos) countdown = (((netgame || multiplayer) ? cv_countdowntime.value : 30)*TICRATE) + 1; // 30 seconds to finish, get going! } + if (numplayersingame < 2) // reset nospectategrief in free play + { + for (j = 0; j < MAXPLAYERS; j++) + nospectategrief[j] = -1; + } + return false; } diff --git a/src/p_saveg.c b/src/p_saveg.c index 975a4a5d2..075b2ccb2 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -3314,7 +3314,10 @@ static void P_NetArchiveMisc(void) WRITEUINT32(save_p, wantedcalcdelay); WRITEUINT32(save_p, indirectitemcooldown); WRITEUINT32(save_p, mapreset); - WRITEUINT8(save_p, nospectategrief); + + for (i = 0; i < MAXPLAYERS; i++) + WRITEINT16(save_p, nospectategrief[i]); + WRITEUINT8(save_p, thwompsactive); WRITESINT8(save_p, spbplace); @@ -3422,7 +3425,10 @@ static inline boolean P_NetUnArchiveMisc(void) wantedcalcdelay = READUINT32(save_p); indirectitemcooldown = READUINT32(save_p); mapreset = READUINT32(save_p); - nospectategrief = READUINT8(save_p); + + for (i = 0; i < MAXPLAYERS; i++) + nospectategrief[i] = READINT16(save_p); + thwompsactive = (boolean)READUINT8(save_p); spbplace = READSINT8(save_p); diff --git a/src/p_setup.c b/src/p_setup.c index 49b22184e..d1c0b1092 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -3242,7 +3242,10 @@ boolean P_SetupLevel(boolean skipprecip) wantedcalcdelay = wantedfrequency*2; indirectitemcooldown = 0; mapreset = 0; - nospectategrief = 0; + + for (i = 0; i < MAXPLAYERS; i++) + nospectategrief[i] = -1; + thwompsactive = false; spbplace = -1; diff --git a/src/p_spec.c b/src/p_spec.c index ca4967ce3..a11cc5c44 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -4278,10 +4278,6 @@ DoneSection2: // Play the starpost sound for 'consistency' // S_StartSound(player->mo, sfx_strpst); - // Figure out how many are playing on the last lap, to prevent spectate griefing - if (!nospectategrief && player->laps >= (UINT8)(cv_numlaps.value - 1)) - nospectategrief = nump; - thwompsactive = true; // Lap 2 effects } else if (player->starpostnum) diff --git a/src/y_inter.c b/src/y_inter.c index 021519e3b..8000fd2b1 100644 --- a/src/y_inter.c +++ b/src/y_inter.c @@ -89,7 +89,7 @@ typedef union INT32 numplayers; // Number of players being displayed char levelstring[64]; // holds levelnames up to 64 characters // SRB2kart - UINT8 increase[MAXPLAYERS]; // how much did the score increase by? + INT16 increase[MAXPLAYERS]; // how much did the score increase by? UINT8 jitter[MAXPLAYERS]; // wiggle UINT32 val[MAXPLAYERS]; // Gametype-specific value UINT8 pos[MAXPLAYERS]; // player positions. used for ties @@ -109,6 +109,7 @@ static boolean usetile; boolean usebuffer = false; static boolean useinterpic; static INT32 timer; +static INT32 powertype = 0; static INT32 intertic; static INT32 endtic = -1; @@ -192,11 +193,16 @@ static void Y_CompareBattle(INT32 i) static void Y_CompareRank(INT32 i) { - UINT8 increase = ((data.match.increase[i] == UINT8_MAX) ? 0 : data.match.increase[i]); - if (!(data.match.val[data.match.numplayers] == UINT32_MAX || (players[i].score - increase) > data.match.val[data.match.numplayers])) + INT16 increase = ((data.match.increase[i] == INT16_MIN) ? 0 : data.match.increase[i]); + UINT32 score = (powertype != -1 ? clientpowerlevels[i][powertype] : players[i].score); + + if (!(data.match.val[data.match.numplayers] == UINT32_MAX)) return; - data.match.val[data.match.numplayers] = (players[i].score - increase); + if (powertype == -1 && (players[i].score - increase) > data.match.val[data.match.numplayers]) + return; + + data.match.val[data.match.numplayers] = (score - increase); data.match.num[data.match.numplayers] = i; } @@ -204,7 +210,7 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32)) { INT32 i, j; boolean completed[MAXPLAYERS]; - INT32 numplayersingame = 0; + INT32 numplayersingame = 0, numgriefers = 0; // Initialize variables if (rankingsmode > 1) @@ -256,14 +262,17 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32)) { data.match.val[i] = UINT32_MAX; + if (nospectategrief[i] != -1) + numgriefers++; + if (!playeringame[i] || players[i].spectator) { - data.match.increase[i] = UINT8_MAX; + data.match.increase[i] = INT16_MIN; continue; } if (!rankingsmode) - data.match.increase[i] = UINT8_MAX; + data.match.increase[i] = INT16_MIN; numplayersingame++; } @@ -275,8 +284,6 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32)) for (j = 0; j < numplayersingame; j++) { - INT32 nump = ((G_RaceGametype() && nospectategrief > 0) ? nospectategrief : numplayersingame); - for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i] || players[i].spectator || completed[i]) @@ -298,14 +305,98 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32)) else data.match.pos[data.match.numplayers] = data.match.numplayers+1; - if (!rankingsmode && !(players[i].pflags & PF_TIMEOVER) && (data.match.pos[data.match.numplayers] < nump)) + if ((!rankingsmode && powertype == -1) // Single player rankings (grand prix). Online rank is handled below. + && !(players[i].pflags & PF_TIMEOVER) && (data.match.pos[data.match.numplayers] < (numplayersingame + numgriefers))) { - data.match.increase[i] = nump - data.match.pos[data.match.numplayers]; + data.match.increase[i] = (numplayersingame + numgriefers) - data.match.pos[data.match.numplayers]; players[i].score += data.match.increase[i]; } data.match.numplayers++; } + + // Compare every single player against each other for power level increases. + // Every player you won against gives you more points, and vice versa. + // The amount of points won per match-up depends on the difference between the loser's power and the winner's power. + // See K_CalculatePowerLevelInc for more info. + // (I'm bad at understanding this code, so I had no idea how to incorporate it with the above loop properly.) + + if (!rankingsmode && powertype != -1) + { + for (i = 0; i < data.match.numplayers; i++) + { + UINT16 yourpower = 5000; + UINT16 theirpower = 5000; + INT16 diff = 0; // Loser PWR.LV - Winner PWR.LV + INT16 inc = 0; // Total pt increment + + if (clientpowerlevels[i][powertype] == 0) // splitscreen guests don't record power level changes + continue; + yourpower = clientpowerlevels[i][powertype]; + + for (j = 0; j < data.match.numplayers; j++) + { + if (i == j || data.match.pos[i] == data.match.pos[j]) // Tie -- neither get any points for this match up. + continue; + + theirpower = 5000; + if (clientpowerlevels[j][powertype] != 0) // No power level acts as 5000 (used for splitscreen guests) + theirpower = clientpowerlevels[j][powertype]; + + if (data.match.pos[i] > data.match.pos[j]) // This player won! + { + diff = theirpower - yourpower; + inc += K_CalculatePowerLevelInc(diff); + } + else if (data.match.pos[i] < data.match.pos[j]) // This player lost... + { + diff = yourpower - theirpower; + inc -= K_CalculatePowerLevelInc(diff); + } + } + + if (numgriefers != 0) // Automatic win against quitters. + { + for (j = 0; j < MAXPLAYERS; j++) + { + if (nospectategrief[j] == -1) // Empty slot + continue; + + if (i == j) // Yourself?? + continue; + + theirpower = 5000; + if (nospectategrief[j] != 0) // No power level acts as 5000 (used for splitscreen guests) + theirpower = nospectategrief[j]; + + diff = theirpower - yourpower; + inc += K_CalculatePowerLevelInc(diff); + } + } + + if (inc == 0) + { + data.match.increase[i] = INT16_MIN; + continue; + } + + if (yourpower + inc > 9999) + inc -= ((yourpower + inc) - 9999); + if (yourpower + inc < 1) + inc -= ((yourpower + inc) - 1); + + data.match.increase[i] = inc; + clientpowerlevels[i][powertype] += data.match.increase[i]; + + if (i == consoleplayer) + { + vspowerlevel[powertype] = clientpowerlevels[i][powertype]; + if (M_UpdateUnlockablesAndExtraEmblems(true)) + S_StartSound(NULL, sfx_ncitem); + G_SaveGameData(true); + } + } + } } // @@ -421,7 +512,7 @@ void Y_IntermissionDrawer(void) const char *timeheader; if (data.match.rankingsmode) - timeheader = "RANK"; + timeheader = "PWR.LV"; else timeheader = (intertype == int_race ? "TIME" : "SCORE"); @@ -487,18 +578,23 @@ void Y_IntermissionDrawer(void) if (data.match.rankingsmode) { - if (data.match.increase[data.match.num[i]] != UINT8_MAX) + if (!clientpowerlevels[i][powertype]) // No power level (splitscreen guests) + STRBUFCPY(strtime, "----"); + else { - if (data.match.increase[data.match.num[i]] > 9) - snprintf(strtime, sizeof strtime, "(+%02d)", data.match.increase[data.match.num[i]]); - else - snprintf(strtime, sizeof strtime, "(+ %d)", data.match.increase[data.match.num[i]]); + if (data.match.increase[data.match.num[i]] != INT16_MIN) + { + if (data.match.increase[data.match.num[i]] > 9) + snprintf(strtime, sizeof strtime, "(+%02d)", data.match.increase[data.match.num[i]]); + else + snprintf(strtime, sizeof strtime, "(+ %d)", data.match.increase[data.match.num[i]]); - V_DrawRightAlignedString(x+120+gutter, y, 0, strtime); + V_DrawRightAlignedString(x+118+gutter, y, 0, strtime); + } + + snprintf(strtime, sizeof strtime, "%d", data.match.val[i]); } - snprintf(strtime, sizeof strtime, "%d", data.match.val[i]); - V_DrawRightAlignedString(x+152+gutter, y, 0, strtime); } else @@ -617,7 +713,7 @@ void Y_Ticker(void) { if (data.match.num[q] == MAXPLAYERS || !data.match.increase[data.match.num[q]] - || data.match.increase[data.match.num[q]] == UINT8_MAX) + || data.match.increase[data.match.num[q]] == INT16_MIN) continue; r++; @@ -737,6 +833,17 @@ void Y_StartIntermission(void) I_Error("endtic is dirty"); #endif + // set player Power Level type + powertype = -1; + + if (netgame) + { + if (G_RaceGametype()) + powertype = 0; + else if (G_BattleGametype()) + powertype = 1; + } + if (!multiplayer) { timer = 0;