diff --git a/src/Makefile b/src/Makefile index cf9e38bf3..c182a88cf 100644 --- a/src/Makefile +++ b/src/Makefile @@ -60,6 +60,7 @@ # Compile with GCC 4.6x version, add 'GCC46=1' # Compile a profile version, add 'PROFILEMODE=1' # Compile a debug version, add 'DEBUGMODE=1' +# Compile for the testers group (they don't get to play unless we're watching *wink*), add 'TESTERS=1' # Compile with extra warnings, add 'WARNINGMODE=1' # Compile without NASM's tmap.nas, add 'NOASM=1' # Compile without 3D hardware support, add 'NOHW=1' @@ -434,6 +435,10 @@ else endif CFLAGS+=-g $(OPTS) $(ARCHOPTS) $(WINDRESFLAGS) +ifdef TESTERS + OPTS+=-DTESTERS +endif + ifdef YASM ifdef STABS NASMOPTS?= -g stabs @@ -492,6 +497,7 @@ OBJS:=$(i_main_o) \ $(OBJDIR)/k_waypoint.o\ $(OBJDIR)/k_pathfind.o\ $(OBJDIR)/k_bheap.o \ + $(OBJDIR)/k_pwrlv.o \ $(OBJDIR)/m_aatree.o \ $(OBJDIR)/m_anigif.o \ $(OBJDIR)/m_argv.o \ diff --git a/src/command.c b/src/command.c index 6d9c86d3e..664eb950c 100644 --- a/src/command.c +++ b/src/command.c @@ -70,8 +70,12 @@ CV_PossibleValue_t CV_Natural[] = {{1, "MIN"}, {999999999, "MAX"}, {0, NULL}}; //SRB2kart CV_PossibleValue_t kartspeed_cons_t[] = { - {0, "Easy"}, {1, "Normal"}, {2, "Hard"}, - {0, NULL}}; + {-1, "Auto"}, + {0, "Easy"}, + {1, "Normal"}, + {2, "Hard"}, + {0, NULL} +}; // Filter consvars by EXECVERSION // First implementation is 2 (1.0.2), so earlier configs default at 1 (1.0.0) @@ -1769,7 +1773,7 @@ void CV_AddValue(consvar_t *var, INT32 increment) { newvalue = var->value + 1; if (newvalue > maxspeed) - newvalue = 0; + newvalue = -1; var->value = newvalue; var->string = var->PossibleValue[var->value].strvalue; var->func(); @@ -1778,7 +1782,7 @@ void CV_AddValue(consvar_t *var, INT32 increment) else if (increment < 0) // Going down! { newvalue = var->value - 1; - if (newvalue < 0) + if (newvalue < -1) newvalue = maxspeed; var->value = newvalue; var->string = var->PossibleValue[var->value].strvalue; diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 4d61194c4..d67f6b48e 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -47,6 +47,7 @@ #include "lua_script.h" #include "lua_hook.h" #include "k_kart.h" +#include "k_pwrlv.h" #ifdef CLIENT_LOADINGSCREEN // cl loading screen @@ -1350,7 +1351,7 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime) netbuffer->u.serverinfo.cheatsenabled = CV_CheatsEnabled(); netbuffer->u.serverinfo.kartvars = (UINT8) ( - (cv_kartspeed.value & SV_SPEEDMASK) | + (gamespeed & SV_SPEEDMASK) | (dedicated ? SV_DEDICATED : 0) | (D_IsJoinPasswordOn() ? SV_PASSWORD : 0) ); @@ -1420,6 +1421,11 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime) p = PutFileNeeded(0); + if (cv_kartusepwrlv.value) + netbuffer->u.serverinfo.avgpwrlv = K_CalculatePowerLevelAvg(); + else + netbuffer->u.serverinfo.avgpwrlv = -1; + HSendPacket(node, false, 0, p - ((UINT8 *)&netbuffer->u)); } @@ -1494,7 +1500,7 @@ static void SV_SendPlayerInfo(INT32 node) */ static boolean SV_SendServerConfig(INT32 node) { - INT32 i; + INT32 i, j; UINT8 *p, *op; boolean waspacketsent; @@ -1517,10 +1523,13 @@ static boolean SV_SendServerConfig(INT32 node) memset(netbuffer->u.servercfg.playercolor, 0xFF, sizeof(netbuffer->u.servercfg.playercolor)); memset(netbuffer->u.servercfg.adminplayers, -1, sizeof(netbuffer->u.servercfg.adminplayers)); + memset(netbuffer->u.servercfg.powerlevels, 0, sizeof(netbuffer->u.servercfg.powerlevels)); for (i = 0; i < MAXPLAYERS; i++) { netbuffer->u.servercfg.adminplayers[i] = (SINT8)adminplayers[i]; + for (j = 0; j < PWRLV_NUMTYPES; j++) + netbuffer->u.servercfg.powerlevels[i][j] = clientpowerlevels[i][j]; if (!playeringame[i]) continue; @@ -2306,6 +2315,7 @@ static void CL_ConnectToServer(boolean viams) wipegamestate = GS_WAITINGPLAYERS; ClearAdminPlayers(); + K_ClearClientPowerLevels(); pnumnodes = 1; oldtic = I_GetTime() - 1; #ifndef NONET @@ -3156,6 +3166,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 @@ -3324,6 +3349,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. + K_ClearClientPowerLevels(); } mynode = 0; @@ -3399,6 +3425,7 @@ void D_QuitNetGame(void) D_CloseConnection(); ClearAdminPlayers(); + K_ClearClientPowerLevels(); DEBFILE("===========================================================================\n" " Log finish\n" @@ -3445,11 +3472,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_Debug(DBG_NETPLAY, "addplayer: %d %d %d\n", node, newplayernum, splitscreenplayer); + // Clear player before joining, lest some things get set incorrectly CL_ClearPlayer(newplayernum); @@ -3466,7 +3494,7 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum) if (splitscreenplayer) { displayplayers[splitscreenplayer] = newplayernum; - DEBFILE(va("spawning one of my sister number %d\n", splitscreenplayer)); + DEBFILE(va("spawning sister # %d\n", splitscreenplayer)); if (splitscreenplayer == 1 && botingame) players[newplayernum].bot = 1; } @@ -3477,6 +3505,7 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum) displayplayers[i] = newplayernum; DEBFILE("spawning me\n"); } + D_SendPlayerConfig(); addedtogame = true; } @@ -3532,7 +3561,6 @@ 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; // What is the reason for this? Why can't newplayernum always be 0? @@ -3545,6 +3573,9 @@ static boolean SV_AddWaitingPlayers(void) // splitscreen can allow 2+ players in one node for (; nodewaiting[node] > 0; nodewaiting[node]--) { + UINT8 buf[3]; + UINT8 *buf_p = buf; + newplayer = true; // search for a free playernum @@ -3552,8 +3583,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; @@ -3565,28 +3598,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) @@ -3623,6 +3651,11 @@ boolean Playing(void) boolean SV_SpawnServer(void) { +#ifdef TESTERS + /* Just don't let the testers play. Easy. */ + I_Error("What do you think you're doing?"); + return false; +#else if (demo.playback) G_StopDemo(); // reset engine parameter if (metalplayback) @@ -3649,6 +3682,7 @@ boolean SV_SpawnServer(void) } return SV_AddWaitingPlayers(); +#endif } void SV_StopServer(void) @@ -4075,7 +4109,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) @@ -4098,7 +4132,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 < PWRLV_NUMTYPES; 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 66d9e73ea..0bd85b614 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -18,6 +18,7 @@ #include "d_netcmd.h" #include "tables.h" #include "d_player.h" +#include "k_pwrlv.h" // PWRLV_NUMTYPES #include "md5.h" @@ -325,6 +326,7 @@ typedef struct UINT8 gametype; UINT8 modifiedgame; SINT8 adminplayers[MAXPLAYERS]; // Needs to be signed + UINT16 powerlevels[MAXPLAYERS][PWRLV_NUMTYPES]; // SRB2kart: player power levels char server_context[8]; // Unique context id, generated at server startup. @@ -387,6 +389,10 @@ typedef struct UINT8 actnum; UINT8 iszone; UINT8 fileneeded[MAXFILENEEDED]; // is filled with writexxx (byteptr.h) + // Anything beyond this point won't be read by the normal SRB2 Master Server display. + // The MS uses a simple unpack, so the size of the packet above shouldn't be changed, either. + // As long as those two conditions are met, we can add as much information as we want to the end. + INT16 avgpwrlv; // Kart avg power level } ATTRPACK serverinfo_pak; typedef struct diff --git a/src/d_main.c b/src/d_main.c index 30d82b256..597eea87c 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -308,13 +308,6 @@ static void D_Display(void) wipedefindex = gamestate; // wipe_xxx_toblack if (gamestate == GS_TITLESCREEN && wipegamestate != GS_INTRO) wipedefindex = wipe_timeattack_toblack; - else if (gamestate == GS_INTERMISSION) - { - if (intertype == int_spec) // Special Stage - wipedefindex = wipe_specinter_toblack; - else //if (intertype != int_coop) // Multiplayer - wipedefindex = wipe_multinter_toblack; - } if (rendermode != render_none) { @@ -325,7 +318,7 @@ static void D_Display(void) F_WipeStartScreen(); V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); F_WipeEndScreen(); - F_RunWipe(wipedefs[wipedefindex], gamestate != GS_TIMEATTACK); + F_RunWipe(wipedefs[wipedefindex], gamestate != GS_TIMEATTACK, "FADEMAP0", false, false); } if (gamestate != GS_LEVEL && rendermode != render_none) @@ -556,7 +549,7 @@ static void D_Display(void) if (rendermode != render_none) { F_WipeEndScreen(); - F_RunWipe(wipedefs[wipedefindex], gamestate != GS_TIMEATTACK); + F_RunWipe(wipedefs[wipedefindex], gamestate != GS_TIMEATTACK, "FADEMAP0", true, false); } } @@ -1566,7 +1559,7 @@ void D_SRB2Main(void) else if (!dedicated && M_MapLocked(pstartmap)) I_Error("You need to unlock this level before you can warp to it!\n"); else - D_MapChange(pstartmap, gametype, (boolean)cv_kartencore.value, true, 0, false, false); + D_MapChange(pstartmap, gametype, (cv_kartencore.value == 1), true, 0, false, false); } } else if (M_CheckParm("-skipintro")) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 90ba1b137..535fbded8 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -47,6 +47,7 @@ #include "m_cond.h" #include "m_anigif.h" #include "k_kart.h" // SRB2kart +#include "k_pwrlv.h" #include "y_inter.h" #ifdef NETGAME_DEVMODE @@ -61,6 +62,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); @@ -358,12 +360,13 @@ consvar_t cv_kartminimap = {"kartminimap", "4", CV_SAVE, kartminimap_cons_t, NUL consvar_t cv_kartcheck = {"kartcheck", "Yes", CV_SAVE, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL}; static CV_PossibleValue_t kartinvinsfx_cons_t[] = {{0, "Music"}, {1, "SFX"}, {0, NULL}}; consvar_t cv_kartinvinsfx = {"kartinvinsfx", "SFX", CV_SAVE, kartinvinsfx_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; -consvar_t cv_kartspeed = {"kartspeed", "Normal", CV_NETVAR|CV_CALL|CV_NOINIT, kartspeed_cons_t, KartSpeed_OnChange, 0, NULL, NULL, 0, 0, NULL}; +consvar_t cv_kartspeed = {"kartspeed", "Auto", CV_NETVAR|CV_CALL|CV_NOINIT, kartspeed_cons_t, KartSpeed_OnChange, 0, NULL, NULL, 0, 0, NULL}; static CV_PossibleValue_t kartbumpers_cons_t[] = {{1, "MIN"}, {12, "MAX"}, {0, NULL}}; consvar_t cv_kartbumpers = {"kartbumpers", "3", CV_NETVAR|CV_CHEAT, kartbumpers_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_kartfrantic = {"kartfrantic", "Off", CV_NETVAR|CV_CHEAT|CV_CALL|CV_NOINIT, CV_OnOff, KartFrantic_OnChange, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_kartcomeback = {"kartcomeback", "On", CV_NETVAR|CV_CHEAT|CV_CALL|CV_NOINIT, CV_OnOff, KartComeback_OnChange, 0, NULL, NULL, 0, 0, NULL}; -consvar_t cv_kartencore = {"kartencore", "Off", CV_NETVAR|CV_CALL|CV_NOINIT, CV_OnOff, KartEncore_OnChange, 0, NULL, NULL, 0, 0, NULL}; +static CV_PossibleValue_t kartencore_cons_t[] = {{-1, "Auto"}, {0, "Off"}, {1, "On"}, {0, NULL}}; +consvar_t cv_kartencore = {"kartencore", "Auto", CV_NETVAR|CV_CALL|CV_NOINIT, kartencore_cons_t, KartEncore_OnChange, 0, NULL, NULL, 0, 0, NULL}; static CV_PossibleValue_t kartvoterulechanges_cons_t[] = {{0, "Never"}, {1, "Sometimes"}, {2, "Frequent"}, {3, "Always"}, {0, NULL}}; consvar_t cv_kartvoterulechanges = {"kartvoterulechanges", "Frequent", CV_NETVAR, kartvoterulechanges_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; static CV_PossibleValue_t kartspeedometer_cons_t[] = {{0, "Off"}, {1, "Percentage"}, {2, "Kilometers"}, {3, "Miles"}, {4, "Fracunits"}, {0, NULL}}; @@ -373,6 +376,8 @@ consvar_t cv_kartvoices = {"kartvoices", "Tasteful", CV_SAVE, kartvoices_cons_t, consvar_t cv_karteliminatelast = {"karteliminatelast", "Yes", CV_NETVAR|CV_CHEAT|CV_CALL|CV_NOSHOWHELP, CV_YesNo, KartEliminateLast_OnChange, 0, NULL, NULL, 0, 0, NULL}; +consvar_t cv_kartusepwrlv = {"kartusepwrlv", "Yes", CV_NETVAR|CV_CHEAT, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL}; + static CV_PossibleValue_t kartdebugitem_cons_t[] = {{-1, "MIN"}, {NUMKARTITEMS-1, "MAX"}, {0, NULL}}; consvar_t cv_kartdebugitem = {"kartdebugitem", "0", CV_NETVAR|CV_CHEAT|CV_NOSHOWHELP, kartdebugitem_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; static CV_PossibleValue_t kartdebugamount_cons_t[] = {{1, "MIN"}, {255, "MAX"}, {0, NULL}}; @@ -411,7 +416,8 @@ consvar_t cv_itemfinder = {"itemfinder", "Off", CV_CALL|CV_NOSHOWHELP, CV_OnOff, // Scoring type options consvar_t cv_match_scoring = {"matchscoring", "Normal", CV_NETVAR|CV_CHEAT|CV_NOSHOWHELP, match_scoring_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; -consvar_t cv_overtime = {"overtime", "Yes", CV_NETVAR, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL}; +static CV_PossibleValue_t overtime_cons_t[] = {{0, "No"}, {1, "Yes"}, {2, "Super"}, {0, NULL}}; +consvar_t cv_overtime = {"overtime", "Yes", CV_NETVAR|CV_CHEAT, overtime_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_rollingdemos = {"rollingdemos", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; @@ -501,6 +507,7 @@ const char *netxcmdnames[MAXNETXCMD - 1] = "MODIFYVOTE", "PICKVOTE", "REMOVEPLAYER", + "POWERLEVEL", #ifdef HAVE_BLUA "LUACMD", "LUAVAR" @@ -538,6 +545,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); @@ -1915,6 +1923,17 @@ static void Got_WeaponPref(UINT8 **cp,INT32 playernum) players[playernum].pflags |= PF_FLIPCAM; } +static void Got_PowerLevel(UINT8 **cp,INT32 playernum) +{ + UINT16 race = (UINT16)READUINT16(*cp); + UINT16 battle = (UINT16)READUINT16(*cp); + + clientpowerlevels[playernum][PWRLV_RACE] = min(PWRLVRECORD_MAX, race); + clientpowerlevels[playernum][PWRLV_BATTLE] = min(PWRLVRECORD_MAX, battle); + + CONS_Debug(DBG_GAMELOGIC, "set player %d to power %d\n", playernum, race); +} + void D_SendPlayerConfig(void) { SendNameAndColor(); @@ -1931,6 +1950,31 @@ void D_SendPlayerConfig(void) SendWeaponPref3(); if (splitscreen > 2) SendWeaponPref4(); + + { + UINT8 buf[4]; + UINT8 *buf_p = buf; + + WRITEUINT16(buf_p, vspowerlevel[PWRLV_RACE]); + WRITEUINT16(buf_p, vspowerlevel[PWRLV_BATTLE]); + + 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! @@ -2343,7 +2387,7 @@ void D_SetupVote(void) UINT8 secondgt = G_SometimesGetDifferentGametype(); INT16 votebuffer[3] = {-1,-1,-1}; - if (cv_kartencore.value && G_RaceGametype()) + if ((cv_kartencore.value == 1) && G_RaceGametype()) WRITEUINT8(p, (gametype|0x80)); else WRITEUINT8(p, gametype); @@ -2514,7 +2558,7 @@ static void Command_Map_f(void) // new encoremode value // use cvar by default - newencoremode = (boolean)cv_kartencore.value; + newencoremode = (cv_kartencore.value == 1); if (COM_CheckParm("-encore")) { @@ -3641,6 +3685,9 @@ 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; @@ -4735,8 +4782,8 @@ static void TimeLimit_OnChange(void) if (cv_timelimit.value != 0) { - CONS_Printf(M_GetText("Levels will end after %d minute%s.\n"),cv_timelimit.value,cv_timelimit.value == 1 ? "" : "s"); // Graue 11-17-2003 - timelimitintics = cv_timelimit.value * 60 * TICRATE; + CONS_Printf(M_GetText("Levels will end after %d second%s.\n"),cv_timelimit.value,cv_timelimit.value == 1 ? "" : "s"); // Graue 11-17-2003 + timelimitintics = cv_timelimit.value * TICRATE; //add hidetime for tag too! if (G_TagGametype()) @@ -4786,9 +4833,9 @@ void D_GameTypeChanged(INT32 lastgametype) case GT_TEAMMATCH: if (!cv_timelimit.changed && !cv_pointlimit.changed) // user hasn't changed limits { - // default settings for match: no timelimit, no pointlimit - CV_SetValue(&cv_pointlimit, 0); - CV_SetValue(&cv_timelimit, 0); + // default settings for match: 2 mins, no pointlimit + CV_SetValue(&cv_pointlimit, 0); + CV_SetValue(&cv_timelimit, 120); } if (!cv_itemrespawntime.changed) CV_Set(&cv_itemrespawntime, cv_itemrespawntime.defaultvalue); // respawn normally @@ -5094,7 +5141,7 @@ static void Hidetime_OnChange(void) //uh oh, gotta change timelimitintics now too if (G_TagGametype()) - timelimitintics = (cv_timelimit.value * 60 * TICRATE) + (hidetime * TICRATE); + timelimitintics = (cv_timelimit.value * TICRATE) + (hidetime * TICRATE); } static void Command_Showmap_f(void) @@ -5727,7 +5774,7 @@ static void KartSpeed_OnChange(void) if (!M_SecretUnlocked(SECRET_HARDSPEED) && cv_kartspeed.value == 2) { CONS_Printf(M_GetText("You haven't earned this yet.\n")); - CV_StealthSetValue(&cv_kartspeed, 1); + CV_StealthSet(&cv_kartspeed, cv_kartspeed.defaultvalue); return; } @@ -5747,10 +5794,10 @@ static void KartEncore_OnChange(void) { if (G_RaceGametype()) { - if ((boolean)cv_kartencore.value != encoremode && gamestate == GS_LEVEL /*&& leveltime > starttime*/) - CONS_Printf(M_GetText("Encore Mode will be turned %s next round.\n"), cv_kartencore.value ? M_GetText("on") : M_GetText("off")); + if ((cv_kartencore.value == 1) != encoremode && gamestate == GS_LEVEL /*&& leveltime > starttime*/) + CONS_Printf(M_GetText("Encore Mode will be set to %s next round.\n"), cv_kartencore.string); else - CONS_Printf(M_GetText("Encore Mode has been turned %s.\n"), cv_kartencore.value ? M_GetText("on") : M_GetText("off")); + CONS_Printf(M_GetText("Encore Mode has been set to %s.\n"), cv_kartencore.string); } } diff --git a/src/d_netcmd.h b/src/d_netcmd.h index 00ddc2254..74273fc90 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -120,8 +120,8 @@ extern consvar_t cv_kartencore; extern consvar_t cv_kartvoterulechanges; extern consvar_t cv_kartspeedometer; extern consvar_t cv_kartvoices; - extern consvar_t cv_karteliminatelast; +extern consvar_t cv_kartusepwrlv; extern consvar_t cv_votetime; @@ -179,9 +179,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; diff --git a/src/d_player.h b/src/d_player.h index 1d9f77c60..163e9b7cf 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -329,6 +329,7 @@ typedef enum k_tiregrease, // Reduced friction timer after hitting a horizontal spring k_springstars, // Spawn stars around a player when they hit a spring k_springcolor, // Color of spring stars + k_killfield, // How long have you been in the kill field, stay in too long and lose a bumper NUMKARTSTUFF } kartstufftype_t; diff --git a/src/dehacked.c b/src/dehacked.c index 355aa86bd..94b009803 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -2553,6 +2553,19 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) ty = UC_PLAYTIME + offset; re = atoi(params[1]); } + else if ((offset=0) || fastcmp(params[0], "POWERLEVEL")) + { + PARAMCHECK(2); + ty = UC_POWERLEVEL; + re = atoi(params[1]); + x1 = atoi(params[2]); + + if (x1 < 0 || x1 >= PWRLV_NUMTYPES) + { + deh_warning("Power level type %d out of range (0 - %d)", x1, PWRLV_NUMTYPES-1); + return; + } + } else if ((offset=0) || fastcmp(params[0], "GAMECLEAR") || (++offset && fastcmp(params[0], "ALLEMERALDS"))) //|| (++offset && fastcmp(params[0], "ULTIMATECLEAR"))) @@ -2605,7 +2618,7 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) if (x1 < 0 || x1 >= NUMMAPS) { - deh_warning("Level number %d out of range (1 - %d)", re, NUMMAPS); + deh_warning("Level number %d out of range (1 - %d)", x1, NUMMAPS); return; } } @@ -3257,29 +3270,13 @@ static void readwipes(MYFILE *f) else if (fastcmp(pword, "FINAL")) wipeoffset = wipe_intermission_final; } - else if (fastncmp(word, "SPECINTER_", 10)) - { - pword = word + 10; - if (fastcmp(pword, "TOBLACK")) - wipeoffset = wipe_specinter_toblack; - else if (fastcmp(pword, "FINAL")) - wipeoffset = wipe_specinter_final; - } else if (fastncmp(word, "VOTING_", 7)) { pword = word + 7; if (fastcmp(pword, "TOBLACK")) - wipeoffset = wipe_specinter_toblack; + wipeoffset = wipe_voting_toblack; else if (fastcmp(pword, "FINAL")) - wipeoffset = wipe_specinter_final; - } - else if (fastncmp(word, "MULTINTER_", 10)) - { - pword = word + 10; - if (fastcmp(pword, "TOBLACK")) - wipeoffset = wipe_multinter_toblack; - else if (fastcmp(pword, "FINAL")) - wipeoffset = wipe_multinter_final; + wipeoffset = wipe_voting_final; } else if (fastncmp(word, "CONTINUING_", 11)) { @@ -3331,11 +3328,13 @@ static void readwipes(MYFILE *f) else if (fastcmp(pword, "FINAL")) wipeoffset = wipe_gameend_final; } - else if (fastncmp(word, "SPECLEVEL_", 10)) + else if (fastncmp(word, "ENCORE_", 7)) { - pword = word + 10; - if (fastcmp(pword, "TOWHITE")) - wipeoffset = wipe_speclevel_towhite; + pword = word + 7; + if (fastcmp(pword, "TOINVERT")) + wipeoffset = wipe_encore_toinvert; + else if (fastcmp(pword, "TOWHITE")) + wipeoffset = wipe_encore_towhite; } if (wipeoffset < 0) @@ -3345,10 +3344,10 @@ static void readwipes(MYFILE *f) } if (value == UINT8_MAX - && (wipeoffset <= wipe_level_toblack || wipeoffset >= wipe_speclevel_towhite)) + && (wipeoffset <= wipe_level_toblack || wipeoffset >= wipe_encore_toinvert)) { // Cannot disable non-toblack wipes - // (or the level toblack wipe, or the special towhite wipe) + // (or the level toblack wipe, or the special encore wipe) deh_warning("Wipes: can't disable wipe of type '%s'", word); continue; } @@ -7215,6 +7214,10 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit "S_TIREGREASE", + "S_OVERTIMEFOG", + "S_OVERTIMEORB", + "S_OVERTIMEBEAM", + #ifdef SEENAMES "S_NAMECHECK", #endif @@ -8009,6 +8012,10 @@ static const char *const MOBJTYPE_LIST[] = { // array length left dynamic for s "MT_DRAFTDUST", "MT_TIREGREASE", + "MT_OVERTIMEFOG", + "MT_OVERTIMEORB", + "MT_OVERTIMEBEAM", + #ifdef SEENAMES "MT_NAMECHECK", #endif @@ -8493,7 +8500,8 @@ static const char *const KARTSTUFF_LIST[] = { "GROWCANCEL", "TIREGREASE", "SPRINGSTARS", - "SPRINGCOLOR" + "SPRINGCOLOR", + "KILLFIELD" }; #endif diff --git a/src/djgppdos/i_system.c b/src/djgppdos/i_system.c index bc3c8b2bc..b5785bb36 100644 --- a/src/djgppdos/i_system.c +++ b/src/djgppdos/i_system.c @@ -59,6 +59,8 @@ #include "../z_zone.h" #include "../g_input.h" +#include "../k_pwrlv.h" + #include "../console.h" #ifdef __GNUG__ @@ -618,6 +620,11 @@ void I_Quit (void) #ifndef NONET D_SaveBan(); // save the ban list #endif + + // Make sure you lose points for ALT-F4 + if (Playing()) + K_PlayerForfeit(consoleplayer, true); + G_SaveGameData(); // Tails 12-08-2002 if (demorecording) G_CheckDemoStatus(); diff --git a/src/doomdef.h b/src/doomdef.h index 645864257..e1e9ab13d 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -158,9 +158,6 @@ extern FILE *logstream; // AND appveyor.yml, for the build bots! #endif -// Maintain compatibility with 1.0.x record attack replays? -//#define DEMO_COMPAT_100 - // Does this version require an added patch file? // Comment or uncomment this as necessary. #define USE_PATCH_FILE diff --git a/src/doomstat.h b/src/doomstat.h index 1f0182c7d..59e2bd5c4 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -475,7 +475,6 @@ extern tic_t wantedcalcdelay; extern tic_t indirectitemcooldown; extern tic_t hyubgone; extern tic_t mapreset; -extern UINT8 nospectategrief; extern boolean thwompsactive; extern SINT8 spbplace; @@ -487,6 +486,15 @@ extern INT16 votelevels[5][2]; extern SINT8 votes[MAXPLAYERS]; extern SINT8 pickedvote; +/** Battle overtime information + */ +extern struct battleovertime +{ + UINT16 enabled; ///< Has this been initalized yet? + fixed_t radius, minradius; ///< Radius of kill field + fixed_t x, y, z; ///< Position to center on +} battleovertime; + extern tic_t hidetime; extern UINT32 timesBeaten; // # of times the game has been beaten. diff --git a/src/f_finale.c b/src/f_finale.c index 9a4be070c..c620c3ffd 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -220,7 +220,7 @@ void F_StartIntro(void) F_WipeStartScreen(); V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); F_WipeEndScreen(); - F_RunWipe(wipedefs[wipe_level_final], false); + F_RunWipe(wipedefs[wipe_intro_toblack], false, "FADEMAP0", false, false); } if (introtoplay) @@ -306,7 +306,7 @@ void F_IntroDrawer(void) F_WipeStartScreen(); V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); F_WipeEndScreen(); - F_RunWipe(99,true); + F_RunWipe(99, true, "FADEMAP0", false, false); } // Stay on black for a bit. =) @@ -1420,7 +1420,7 @@ void F_CutsceneDrawer(void) V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, cutscenes[cutnum]->scene[scenenum].fadecolor); F_WipeEndScreen(); - F_RunWipe(cutscenes[cutnum]->scene[scenenum].fadeinid, true); + F_RunWipe(cutscenes[cutnum]->scene[scenenum].fadeinid, true, NULL, false, false); F_WipeStartScreen(); } @@ -1440,7 +1440,7 @@ void F_CutsceneDrawer(void) if (dofadenow && rendermode != render_none) { F_WipeEndScreen(); - F_RunWipe(cutscenes[cutnum]->scene[scenenum].fadeoutid, true); + F_RunWipe(cutscenes[cutnum]->scene[scenenum].fadeoutid, true, NULL, false, false); } V_DrawString(textxpos, textypos, 0, cutscene_disptext); diff --git a/src/f_finale.h b/src/f_finale.h index 45166e03d..d1efa6ce2 100644 --- a/src/f_finale.h +++ b/src/f_finale.h @@ -74,12 +74,13 @@ extern INT32 lastwipetic; void F_WipeStartScreen(void); void F_WipeEndScreen(void); -void F_RunWipe(UINT8 wipetype, boolean drawMenu); +void F_RunWipe(UINT8 wipetype, boolean drawMenu, const char *colormap, boolean reverse, boolean encorewiggle); enum { wipe_credits_intermediate, // makes a good 0 I guess. + // Gamestate wipes wipe_level_toblack, wipe_intermission_toblack, wipe_voting_toblack, @@ -92,11 +93,11 @@ enum wipe_intro_toblack, wipe_cutscene_toblack, - // custom intermissions - wipe_specinter_toblack, - wipe_multinter_toblack, - wipe_speclevel_towhite, + // Specialized wipes + wipe_encore_toinvert, + wipe_encore_towhite, + // "From black" wipes wipe_level_final, wipe_intermission_final, wipe_voting_final, @@ -109,10 +110,6 @@ enum wipe_intro_final, wipe_cutscene_final, - // custom intermissions - wipe_specinter_final, - wipe_multinter_final, - NUMWIPEDEFS, WIPEFINALSHIFT = wipe_level_final - wipe_level_toblack }; diff --git a/src/f_wipe.c b/src/f_wipe.c index 3c8713d18..8d73c1fe7 100644 --- a/src/f_wipe.c +++ b/src/f_wipe.c @@ -16,8 +16,10 @@ #include "i_video.h" #include "v_video.h" +#include "r_data.h" // NearestColor #include "r_draw.h" // transtable #include "p_pspr.h" // tr_transxxx + #include "w_wad.h" #include "z_zone.h" @@ -47,35 +49,31 @@ UINT8 wipedefs[NUMWIPEDEFS] = { 99, // wipe_credits_intermediate (0) 0, // wipe_level_toblack - UINT8_MAX, // wipe_intermission_toblack + 0, // wipe_intermission_toblack 0, // wipe_voting_toblack, - UINT8_MAX, // wipe_continuing_toblack - 3, // wipe_titlescreen_toblack + 0, // wipe_continuing_toblack + 0, // wipe_titlescreen_toblack 0, // wipe_timeattack_toblack 99, // wipe_credits_toblack 0, // wipe_evaluation_toblack 0, // wipe_gameend_toblack UINT8_MAX, // wipe_intro_toblack (hardcoded) - UINT8_MAX, // wipe_cutscene_toblack (hardcoded) + 99, // wipe_cutscene_toblack (hardcoded) - UINT8_MAX, // wipe_specinter_toblack - UINT8_MAX, // wipe_multinter_toblack - 99, // wipe_speclevel_towhite + 72, // wipe_encore_toinvert + 99, // wipe_encore_towhite - 3, // wipe_level_final + UINT8_MAX, // wipe_level_final 0, // wipe_intermission_final 0, // wipe_voting_final 0, // wipe_continuing_final - 3, // wipe_titlescreen_final + 0, // wipe_titlescreen_final 0, // wipe_timeattack_final 99, // wipe_credits_final 0, // wipe_evaluation_final 0, // wipe_gameend_final 99, // wipe_intro_final (hardcoded) - 99, // wipe_cutscene_final (hardcoded) - - 0, // wipe_specinter_final - 0 // wipe_multinter_final + 99 // wipe_cutscene_final (hardcoded) }; //-------------------------------------------------------------------------- @@ -86,9 +84,13 @@ boolean WipeInAction = false; INT32 lastwipetic = 0; #ifndef NOWIPE + +#define GENLEN 31 + static UINT8 *wipe_scr_start; //screen 3 static UINT8 *wipe_scr_end; //screen 4 static UINT8 *wipe_scr; //screen 0 (main drawing) +static UINT8 pallen; static fixed_t paldiv; /** Create fademask_t from lump @@ -181,7 +183,7 @@ static fademask_t *F_GetFadeMask(UINT8 masknum, UINT8 scrnnum) { * * \param fademask pixels to change */ -static void F_DoWipe(fademask_t *fademask) +static void F_DoWipe(fademask_t *fademask, lighttable_t *fadecolormap, boolean reverse) { // Software mask wipe -- optimized; though it might not look like it! // Okay, to save you wondering *how* this is more optimized than the simpler @@ -199,6 +201,10 @@ static void F_DoWipe(fademask_t *fademask) // look a little messy; sorry!) but it simultaneously runs at twice the speed. // In addition, we precalculate all the X and Y positions that we need to draw // from and to, so it uses a little extra memory, but again, helps it run faster. + // --- + // Sal: I kinda destroyed some of this code by introducing Genesis-style fades. + // A colormap can be provided in F_RunWipe, which the white/black values will be + // remapped to the appropriate entry in the fade colormap. { // wipe screen, start, end UINT8 *w = wipe_scr; @@ -242,6 +248,8 @@ static void F_DoWipe(fademask_t *fademask) maskx = masky = 0; do { + UINT8 m = *mask; + draw_rowstart = scrxpos[maskx]; draw_rowend = scrxpos[maskx + 1]; draw_linestart = scrypos[masky]; @@ -250,28 +258,31 @@ static void F_DoWipe(fademask_t *fademask) relativepos = (draw_linestart * vid.width) + draw_rowstart; draw_linestogo = draw_lineend - draw_linestart; - if (*mask == 0) + if (reverse) + m = ((pallen-1) - m); + + if (m == 0) { // shortcut - memcpy source to work while (draw_linestogo--) { - M_Memcpy(w_base+relativepos, s_base+relativepos, draw_rowend-draw_rowstart); + M_Memcpy(w_base+relativepos, (reverse ? e_base : s_base)+relativepos, draw_rowend-draw_rowstart); relativepos += vid.width; } } - else if (*mask == 10) + else if (m >= (pallen-1)) { // shortcut - memcpy target to work while (draw_linestogo--) { - M_Memcpy(w_base+relativepos, e_base+relativepos, draw_rowend-draw_rowstart); + M_Memcpy(w_base+relativepos, (reverse ? s_base : e_base)+relativepos, draw_rowend-draw_rowstart); relativepos += vid.width; } } else { // pointer to transtable that this mask would use - transtbl = transtables + ((9 - *mask)<>FRACBITS; + scanline = y / vid.dupy; + if (scanline & 1) + sine = -sine; + newpix = abs(sine); + + if (sine < 0) + { + M_Memcpy(&tmpscr[(y*vid.width)+newpix], &srcscr[(y*vid.width)], vid.width-newpix); + + // Cleanup edge + while (newpix) + { + tmpscr[(y*vid.width)+newpix] = srcscr[(y*vid.width)]; + newpix--; + } + } + else + { + M_Memcpy(&tmpscr[(y*vid.width)], &srcscr[(y*vid.width) + sine], vid.width-newpix); + + // Cleanup edge + while (newpix) + { + tmpscr[(y*vid.width) + vid.width - newpix] = srcscr[(y*vid.width) + (vid.width-1)]; + newpix--; + } + } + + disStart += (time*8); //the offset into the displacement map, increment each game loop + disStart &= FINEMASK; //clip it to FINEMASK + } +} + /** After setting up the screens you want to wipe, * calling this will do a 'typical' wipe. */ -void F_RunWipe(UINT8 wipetype, boolean drawMenu) +void F_RunWipe(UINT8 wipetype, boolean drawMenu, const char *colormap, boolean reverse, boolean encorewiggle) { #ifdef NOWIPE (void)wipetype; (void)drawMenu; + (void)colormap; + (void)reverse; + (void)encorewiggle; #else tic_t nowtime; UINT8 wipeframe = 0; fademask_t *fmask; - paldiv = FixedDiv(257<buttons & BT_FORWARD) - players[i].kartstuff[k_throwdir] = 1; - else if (cmd->buttons & BT_BACKWARD) - players[i].kartstuff[k_throwdir] = -1; - else - players[i].kartstuff[k_throwdir] = 0; - G_CopyTiccmd(cmd, &netcmds[buf][i], 1); - // Use the leveltime sent in the player's ticcmd to determine control lag cmd->latency = modeattacking ? 0 : min(((leveltime & 0xFF) - cmd->latency) & 0xFF, MAXPREDICTTICS-1); //@TODO add a cvar to allow setting this max } @@ -3042,8 +3031,17 @@ mapthing_t *G_FindRaceStart(INT32 playernum) continue; if (j == i) continue; - if (players[j].score == players[i].score) - num++; + + if (netgame && cv_kartusepwrlv.value) + { + if (clientpowerlevels[j][PWRLV_RACE] == clientpowerlevels[i][PWRLV_RACE]) + num++; + } + else + { + if (players[j].score == players[i].score) + num++; + } } if (num > 1) // found dupes @@ -3051,8 +3049,21 @@ mapthing_t *G_FindRaceStart(INT32 playernum) } else { - if (players[i].score > players[playernum].score || i < playernum) + if (i < playernum) pos++; + else + { + if (netgame && cv_kartusepwrlv.value) + { + if (clientpowerlevels[i][PWRLV_RACE] > clientpowerlevels[playernum][PWRLV_RACE]) + pos++; + } + else + { + if (players[i].score > players[playernum].score) + pos++; + } + } } } @@ -3382,9 +3393,10 @@ boolean G_BattleGametype(void) // INT16 G_SometimesGetDifferentGametype(void) { - boolean encorepossible = (M_SecretUnlocked(SECRET_ENCORE) && G_RaceGametype()); + boolean encorepossible = ((M_SecretUnlocked(SECRET_ENCORE) || encorescramble == 1) && G_RaceGametype()); - if (!cv_kartvoterulechanges.value) // never + if (!cv_kartvoterulechanges.value // never + && encorescramble != 1) // destroying the code for this one instance return gametype; if (randmapbuffer[NUMMAPS] > 0 && (encorepossible || cv_kartvoterulechanges.value != 3)) @@ -3392,25 +3404,33 @@ INT16 G_SometimesGetDifferentGametype(void) randmapbuffer[NUMMAPS]--; if (encorepossible) { - switch (cv_kartvoterulechanges.value) + if (encorescramble != -1) + encorepossible = (boolean)encorescramble; // FORCE to what was scrambled on intermission + else { - case 3: // always - randmapbuffer[NUMMAPS] = 0; // gotta prep this in case it isn't already set - break; - case 2: // frequent - encorepossible = M_RandomChance(FRACUNIT>>1); - break; - case 1: // sometimes - default: - encorepossible = M_RandomChance(FRACUNIT>>2); - break; + switch (cv_kartvoterulechanges.value) + { + case 3: // always + randmapbuffer[NUMMAPS] = 0; // gotta prep this in case it isn't already set + break; + case 2: // frequent + encorepossible = M_RandomChance(FRACUNIT>>1); + break; + case 1: // sometimes + default: + encorepossible = M_RandomChance(FRACUNIT>>2); + break; + } } - if (encorepossible != (boolean)cv_kartencore.value) + if (encorepossible != (cv_kartencore.value == 1)) return (gametype|0x80); } return gametype; } + if (!cv_kartvoterulechanges.value) // never (again) + return gametype; + switch (cv_kartvoterulechanges.value) // okay, we're having a gametype change! when's the next one, luv? { case 3: // always @@ -3879,7 +3899,7 @@ void G_NextLevel(void) } forceresetplayers = false; - deferencoremode = (boolean)cv_kartencore.value; + deferencoremode = (cv_kartencore.value == 1); } gameaction = ga_worlddone; @@ -4042,9 +4062,13 @@ void G_LoadGameData(void) // to new gamedata G_ClearRecords(); // main and nights records M_ClearSecrets(); // emblems, unlocks, maps visited, etc + totalplaytime = 0; // total play time (separate from all) matchesplayed = 0; // SRB2Kart: matches played & finished + for (i = 0; i < PWRLV_NUMTYPES; i++) // SRB2Kart: online rank system + vspowerlevel[i] = PWRLVRECORD_START; + if (M_CheckParm("-nodata")) return; // Don't load. @@ -4075,6 +4099,9 @@ void G_LoadGameData(void) totalplaytime = READUINT32(save_p); matchesplayed = READUINT32(save_p); + for (i = 0; i < PWRLV_NUMTYPES; i++) + vspowerlevel[i] = READUINT16(save_p); + modded = READUINT8(save_p); // Aha! Someone's been screwing with the save file! @@ -4220,6 +4247,9 @@ void G_SaveGameData(boolean force) WRITEUINT32(save_p, totalplaytime); WRITEUINT32(save_p, matchesplayed); + for (i = 0; i < PWRLV_NUMTYPES; i++) + WRITEUINT16(save_p, vspowerlevel[i]); + btemp = (UINT8)(savemoddata); // what used to be here was profoundly dunderheaded WRITEUINT8(save_p, btemp); @@ -4732,7 +4762,7 @@ char *G_BuildMapTitle(INT32 mapnum) // DEMO RECORDING // -#define DEMOVERSION 0x0002 +#define DEMOVERSION 0x0003 #define DEMOHEADER "\xF0" "KartReplay" "\x0F" #define DF_GHOST 0x01 // This demo contains ghost data too! @@ -4743,12 +4773,6 @@ char *G_BuildMapTitle(INT32 mapnum) #define DF_ENCORE 0x40 #define DF_MULTIPLAYER 0x80 // This demo was recorded in multiplayer mode! -#ifdef DEMO_COMPAT_100 -#define DF_FILELIST 0x08 // This demo contains an extra files list -#define DF_GAMETYPEMASK 0x30 -#define DF_GAMESHIFT 4 -#endif - #define DEMO_SPECTATOR 0x40 // For demos @@ -4890,7 +4914,6 @@ void G_ReadDemoExtraData(void) kartspeed = READUINT8(demo_p); kartweight = READUINT8(demo_p); - if (stricmp(skins[players[p].skin].name, name) != 0) FindClosestSkinForStats(p, kartspeed, kartweight); @@ -5608,16 +5631,9 @@ void G_ConsGhostTic(INT32 playernum) else ghostext[playernum].desyncframes = 0; - if ( -#ifdef DEMO_COMPAT_100 - demo.version != 0x0001 && -#endif - ( - players[playernum].kartstuff[k_itemtype] != ghostext[playernum].kartitem || - players[playernum].kartstuff[k_itemamount] != ghostext[playernum].kartamount || - players[playernum].kartstuff[k_bumper] != ghostext[playernum].kartbumpers - ) - ) + if (players[playernum].kartstuff[k_itemtype] != ghostext[playernum].kartitem + || players[playernum].kartstuff[k_itemamount] != ghostext[playernum].kartamount + || players[playernum].kartstuff[k_bumper] != ghostext[playernum].kartbumpers) { if (demosynced) CONS_Alert(CONS_WARNING, M_GetText("Demo playback has desynced!\n")); @@ -5645,10 +5661,6 @@ void G_GhostTicker(void) // Skip normal demo data. UINT8 ziptic = READUINT8(g->p); -#ifdef DEMO_COMPAT_100 - if (g->version != 0x0001) - { -#endif while (ziptic != DW_END) // Get rid of extradata stuff { if (ziptic == 0) // Only support player 0 info for now @@ -5672,9 +5684,6 @@ void G_GhostTicker(void) } ziptic = READUINT8(g->p); // Back to actual ziptic stuff -#ifdef DEMO_COMPAT_100 - } -#endif if (ziptic & ZT_FWD) g->p++; @@ -5694,18 +5703,12 @@ void G_GhostTicker(void) // Grab ghost data. ziptic = READUINT8(g->p); -#ifdef DEMO_COMPAT_100 - if (g->version != 0x0001) - { -#endif if (ziptic == 0xFF) goto skippedghosttic; // Didn't write ghost info this frame else if (ziptic != 0) I_Error("Ghost is not a record attack ghost"); //@TODO lmao don't blow up like this ziptic = READUINT8(g->p); -#ifdef DEMO_COMPAT_100 - } -#endif + if (ziptic & GZT_XYZ) { g->oldmo.x = READFIXED(g->p); @@ -5846,15 +5849,8 @@ void G_GhostTicker(void) g->p += 12; // kartitem, kartamount, kartbumpers } -#ifdef DEMO_COMPAT_100 - if (g->version != 0x0001) - { -#endif if (READUINT8(g->p) != 0xFF) // Make sure there isn't other ghost data here. I_Error("Ghost is not a record attack ghost"); //@TODO lmao don't blow up like this -#ifdef DEMO_COMPAT_100 - } -#endif skippedghosttic: // Tick ghost colors (Super and Mario Invincibility flashing) @@ -6364,20 +6360,15 @@ void G_BeginRecording(void) switch ((demoflags & DF_ATTACKMASK)>>DF_ATTACKSHIFT) { - case ATTACKING_NONE: // 0 - break; - case ATTACKING_RECORD: // 1 - demotime_p = demo_p; - WRITEUINT32(demo_p,UINT32_MAX); // time - WRITEUINT32(demo_p,UINT32_MAX); // lap - break; - /*case ATTACKING_NIGHTS: // 2 - demotime_p = demo_p; - WRITEUINT32(demo_p,UINT32_MAX); // time - WRITEUINT32(demo_p,0); // score - break;*/ - default: // 3 - break; + case ATTACKING_NONE: // 0 + break; + case ATTACKING_RECORD: // 1 + demotime_p = demo_p; + WRITEUINT32(demo_p,UINT32_MAX); // time + WRITEUINT32(demo_p,UINT32_MAX); // lap + break; + default: // 3 + break; } WRITEUINT32(demo_p,P_GetInitSeed()); @@ -6417,6 +6408,9 @@ void G_BeginRecording(void) // Score, since Kart uses this to determine where you start on the map WRITEUINT32(demo_p, player->score); + // Power Levels + WRITEUINT16(demo_p, clientpowerlevels[p][G_BattleGametype() ? PWRLV_BATTLE : PWRLV_RACE]); + // Kart speed and weight WRITEUINT8(demo_p, skins[player->skin].kartspeed); WRITEUINT8(demo_p, skins[player->skin].kartweight); @@ -6515,18 +6509,13 @@ void G_SetDemoTime(UINT32 ptime, UINT32 plap) { if (!demo.recording || !demotime_p) return; + if (demoflags & DF_RECORDATTACK) { WRITEUINT32(demotime_p, ptime); WRITEUINT32(demotime_p, plap); demotime_p = NULL; } - /*else if (demoflags & DF_NIGHTSATTACK) - { - WRITEUINT32(demotime_p, ptime); - WRITEUINT32(demotime_p, pscore); - demotime_p = NULL; - }*/ } static void G_LoadDemoExtraFiles(UINT8 **pp) @@ -6769,13 +6758,6 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname) case DEMOVERSION: // latest always supported p += 64; // full demo title break; -#ifdef DEMO_COMPAT_100 - case 0x0001: - // Old replays gotta go :] - CONS_Alert(CONS_NOTICE, M_GetText("File '%s' outdated version. It will be overwritten. Nyeheheh.\n"), oldname); - Z_Free(buffer); - return UINT8_MAX; -#endif // too old, cannot support. default: CONS_Alert(CONS_NOTICE, M_GetText("File '%s' invalid format. It will be overwritten.\n"), oldname); @@ -6867,12 +6849,6 @@ void G_LoadDemoInfo(menudemo_t *pdemo) info_p += 64; break; -#ifdef DEMO_COMPAT_100 - case 0x0001: - pdemo->type = MD_OUTDATED; - sprintf(pdemo->title, "Legacy Replay"); - break; -#endif // too old, cannot support. default: CONS_Alert(CONS_ERROR, M_GetText("%s is an incompatible replay format and cannot be played.\n"), pdemo->filepath); @@ -6907,16 +6883,6 @@ void G_LoadDemoInfo(menudemo_t *pdemo) Z_Free(infobuffer); return; } -#ifdef DEMO_COMPAT_100 - else if (pdemoversion == 0x0001) - { - CONS_Alert(CONS_ERROR, M_GetText("%s is a legacy multiplayer replay and cannot be played.\n"), pdemo->filepath); - pdemo->type = MD_INVALID; - sprintf(pdemo->title, "INVALID REPLAY"); - Z_Free(infobuffer); - return; - } -#endif pdemo->gametype = READUINT8(info_p); @@ -7117,10 +7083,6 @@ void G_DoPlayDemo(char *defdemoname) demo_p += 64; break; -#ifdef DEMO_COMPAT_100 - case 0x0001: - break; -#endif // too old, cannot support. default: snprintf(msg, 1024, M_GetText("%s is an incompatible replay format and cannot be played.\n"), pdemoname); @@ -7149,24 +7111,6 @@ void G_DoPlayDemo(char *defdemoname) demo_p += 16; // mapmd5 demoflags = READUINT8(demo_p); -#ifdef DEMO_COMPAT_100 - if (demo.version == 0x0001) - { - if (demoflags & DF_MULTIPLAYER) - { - snprintf(msg, 1024, M_GetText("%s is an alpha multiplayer replay and cannot be played.\n"), pdemoname); - CONS_Alert(CONS_ERROR, "%s", msg); - M_StartMessage(msg, NULL, MM_NOTHING); - Z_Free(pdemoname); - Z_Free(demobuffer); - demo.playback = false; - demo.title = false; - return; - } - } - else - { -#endif gametype = READUINT8(demo_p); if (demo.title) // Titledemos should always play and ought to always be compatible with whatever wadlist is running. @@ -7224,9 +7168,6 @@ void G_DoPlayDemo(char *defdemoname) return; } } -#ifdef DEMO_COMPAT_100 - } -#endif modeattacking = (demoflags & DF_ATTACKMASK)>>DF_ATTACKSHIFT; multiplayer = !!(demoflags & DF_MULTIPLAYER); @@ -7254,109 +7195,20 @@ void G_DoPlayDemo(char *defdemoname) // Random seed randseed = READUINT32(demo_p); -#ifdef DEMO_COMPAT_100 - if (demo.version != 0x0001) -#endif demo_p += 4; // Extrainfo location -#ifdef DEMO_COMPAT_100 - if (demo.version == 0x0001) + // ...*map* not loaded? + if (!gamemap || (gamemap > NUMMAPS) || !mapheaderinfo[gamemap-1] || !(mapheaderinfo[gamemap-1]->menuflags & LF2_EXISTSHACK)) { - // Player name - M_Memcpy(player_names[0],demo_p,16); - demo_p += 16; - - // Skin - M_Memcpy(skin,demo_p,16); - demo_p += 16; - - // Color - M_Memcpy(color,demo_p,16); - demo_p += 16; - - demo_p += 5; // Backwards compat - some stats - // SRB2kart - kartspeed[0] = READUINT8(demo_p); - kartweight[0] = READUINT8(demo_p); - // - demo_p += 9; // Backwards compat - more stats - - // Skin not loaded? - if (!SetPlayerSkin(0, skin)) - { - snprintf(msg, 1024, M_GetText("%s features a character that is not currently loaded.\n"), pdemoname); - CONS_Alert(CONS_ERROR, "%s", msg); - M_StartMessage(msg, NULL, MM_NOTHING); - Z_Free(pdemoname); - Z_Free(demobuffer); - demo.playback = false; - demo.title = false; - return; - } - - // ...*map* not loaded? - if (!gamemap || (gamemap > NUMMAPS) || !mapheaderinfo[gamemap-1] || !(mapheaderinfo[gamemap-1]->menuflags & LF2_EXISTSHACK)) - { - snprintf(msg, 1024, M_GetText("%s features a course that is not currently loaded.\n"), pdemoname); - CONS_Alert(CONS_ERROR, "%s", msg); - M_StartMessage(msg, NULL, MM_NOTHING); - Z_Free(pdemoname); - Z_Free(demobuffer); - demo.playback = false; - demo.title = false; - return; - } - - // Set color - for (i = 0; i < MAXSKINCOLORS; i++) - if (!stricmp(KartColor_Names[i],color)) // SRB2kart - { - players[0].skincolor = i; - break; - } - - // net var data - CV_LoadNetVars(&demo_p); - - // Sigh ... it's an empty demo. - if (*demo_p == DEMOMARKER) - { - snprintf(msg, 1024, M_GetText("%s contains no data to be played.\n"), pdemoname); - CONS_Alert(CONS_ERROR, "%s", msg); - M_StartMessage(msg, NULL, MM_NOTHING); - Z_Free(pdemoname); - Z_Free(demobuffer); - demo.playback = false; - demo.title = false; - return; - } - + snprintf(msg, 1024, M_GetText("%s features a course that is not currently loaded.\n"), pdemoname); + CONS_Alert(CONS_ERROR, "%s", msg); + M_StartMessage(msg, NULL, MM_NOTHING); Z_Free(pdemoname); - - memset(&oldcmd,0,sizeof(oldcmd)); - memset(&oldghost,0,sizeof(oldghost)); - memset(&ghostext,0,sizeof(ghostext)); - - CONS_Alert(CONS_WARNING, M_GetText("Demo version does not match game version. Desyncs may occur.\n")); - - // console warning messages -#if defined(SKIPERRORS) && !defined(DEVELOP) - demosynced = (!skiperrors); -#else - demosynced = true; -#endif - - // didn't start recording right away. - demo.deferstart = false; - - consoleplayer = 0; - memset(displayplayers, 0, sizeof(displayplayers)); - memset(playeringame, 0, sizeof(playeringame)); - playeringame[0] = true; - - goto post_compat; + Z_Free(demobuffer); + demo.playback = false; + demo.title = false; + return; } -#endif // net var data CV_LoadNetVars(&demo_p); @@ -7471,6 +7323,9 @@ void G_DoPlayDemo(char *defdemoname) // Score, since Kart uses this to determine where you start on the map players[p].score = READUINT32(demo_p); + // Power Levels + clientpowerlevels[p][G_BattleGametype() ? PWRLV_BATTLE : PWRLV_RACE] = READUINT16(demo_p); + // Kart stats, temporarily kartspeed[p] = READUINT8(demo_p); kartweight[p] = READUINT8(demo_p); @@ -7495,10 +7350,6 @@ void G_DoPlayDemo(char *defdemoname) R_ExecuteSetViewSize(); -#ifdef DEMO_COMPAT_100 -post_compat: -#endif - P_SetRandSeed(randseed); G_InitNew(demoflags & DF_ENCORE, G_BuildMapName(gamemap), true, true); // Doesn't matter whether you reset or not here, given changes to resetplayer. @@ -7589,10 +7440,6 @@ void G_AddGhost(char *defdemoname) case DEMOVERSION: // latest always supported p += 64; // title break; -#ifdef DEMO_COMPAT_100 - case 0x0001: - break; -#endif // too old, cannot support. default: CONS_Alert(CONS_NOTICE, M_GetText("Ghost %s: Demo version incompatible.\n"), pdemoname); @@ -7633,15 +7480,9 @@ void G_AddGhost(char *defdemoname) return; } -#ifdef DEMO_COMPAT_100 - if (ghostversion != 0x0001) -#endif - p++; // gametype + p++; // gametype + G_SkipDemoExtraFiles(&p); // Don't wanna modify the file list for ghosts. -#ifdef DEMO_COMPAT_100 - if (ghostversion != 0x0001) -#endif - G_SkipDemoExtraFiles(&p); // Don't wanna modify the file list for ghosts. switch ((flags & DF_ATTACKMASK)>>DF_ATTACKSHIFT) { case ATTACKING_NONE: // 0 @@ -7657,41 +7498,6 @@ void G_AddGhost(char *defdemoname) } p += 4; // random seed - -#ifdef DEMO_COMPAT_100 - if (ghostversion == 0x0001) - { - // Player name (TODO: Display this somehow if it doesn't match cv_playername!) - M_Memcpy(name, p,16); - p += 16; - - // Skin - M_Memcpy(skin, p,16); - p += 16; - - // Color - M_Memcpy(color, p,16); - p += 16; - - // Ghosts do not have a player structure to put this in. - p++; // charability - p++; // charability2 - p++; // actionspd - p++; // mindash - p++; // maxdash - // SRB2kart - p++; // kartspeed - p++; // kartweight - // - p++; // normalspeed - p++; // runspeed - p++; // thrustfactor - p++; // accelstart - p++; // acceleration - p += 4; // jumpfactor - } - else -#endif p += 4; // Extra data location reference // net var data @@ -7711,10 +7517,6 @@ void G_AddGhost(char *defdemoname) return; } -#ifdef DEMO_COMPAT_100 - if (ghostversion != 0x0001) - { -#endif if (READUINT8(p) != 0) { CONS_Alert(CONS_NOTICE, M_GetText("Failed to add ghost %s: Invalid player slot.\n"), pdemoname); @@ -7736,6 +7538,7 @@ void G_AddGhost(char *defdemoname) p += 16; p += 4; // score + p += 2; // powerlevel kartspeed = READUINT8(p); kartweight = READUINT8(p); @@ -7747,9 +7550,6 @@ void G_AddGhost(char *defdemoname) Z_Free(buffer); return; } -#ifdef DEMO_COMPAT_100 - } -#endif for (i = 0; i < numskins; i++) if (!stricmp(skins[i].name,skin)) @@ -7849,18 +7649,13 @@ void G_UpdateStaffGhostName(lumpnum_t l) ghostversion = READUINT16(p); switch(ghostversion) { - case DEMOVERSION: // latest always supported - p += 64; // full demo title - break; + case DEMOVERSION: // latest always supported + p += 64; // full demo title + break; -#ifdef DEMO_COMPAT_100 - case 0x0001: - break; -#endif - - // too old, cannot support. - default: - goto fail; + // too old, cannot support. + default: + goto fail; } p += 16; // demo checksum @@ -7880,43 +7675,22 @@ void G_UpdateStaffGhostName(lumpnum_t l) goto fail; // we don't NEED to do it here, but whatever } -#ifdef DEMO_COMPAT_100 - if (ghostversion != 0x0001) -#endif p++; // Gametype -#ifdef DEMO_COMPAT_100 - if (ghostversion != 0x0001) -#endif G_SkipDemoExtraFiles(&p); switch ((flags & DF_ATTACKMASK)>>DF_ATTACKSHIFT) { - case ATTACKING_NONE: // 0 - break; - case ATTACKING_RECORD: // 1 - p += 8; // demo time, lap - break; - /*case ATTACKING_NIGHTS: // 2 - p += 8; // demo time left, score - break;*/ - default: // 3 - break; + case ATTACKING_NONE: // 0 + break; + case ATTACKING_RECORD: // 1 + p += 8; // demo time, lap + break; + default: // 3 + break; } p += 4; // random seed - - -#ifdef DEMO_COMPAT_100 - if (ghostversion == 0x0001) - { - // Player name - M_Memcpy(dummystaffname, p,16); - dummystaffname[16] = '\0'; - goto fail; // Not really a failure but whatever - } -#endif - p += 4; // Extrainfo location marker // Ehhhh don't need ghostversion here (?) so I'll reuse the var here @@ -8001,10 +7775,6 @@ void G_DoPlayMetal(void) { case DEMOVERSION: // latest always supported break; -#ifdef DEMO_COMPAT_100 - case 0x0001: - I_Error("You need to implement demo compat here, doofus! %s:%d", __FILE__, __LINE__); -#endif // too old, cannot support. default: CONS_Alert(CONS_WARNING, M_GetText("Failed to load bot recording for this map, format version incompatible.\n")); diff --git a/src/hu_stuff.c b/src/hu_stuff.c index 38324dbd9..6126f7bec 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -3017,7 +3017,7 @@ static void HU_DrawRankings(void) } V_DrawCenteredString(256, 8, 0, "GAME SPEED"); - V_DrawCenteredString(256, 16, hilicol, cv_kartspeed.string); + V_DrawCenteredString(256, 16, hilicol, kartspeed_cons_t[gamespeed].strvalue); } // When you play, you quickly see your score because your name is displayed in white. diff --git a/src/info.c b/src/info.c index 124601d6d..60ffbc626 100644 --- a/src/info.c +++ b/src/info.c @@ -70,7 +70,7 @@ char sprnames[NUMSPRITES + 1][5] = "DUCK","GTRE","CHES","CHIM","DRGN","LZMN","PGSS","ZTCH","MKMA","MKMP", "RTCH","BOWL","BOWH","BRRL","BRRR","HRSE","TOAH","BFRT","OFRT","RFRT", "PFRT","ASPK","HBST","HBSO","HBSF","WBLZ","WBLN","FWRK","MXCL","RGSP", - "DRAF","GRES","XMS4","XMS5","VIEW" + "DRAF","GRES","OTFG","XMS4","XMS5","VIEW" }; // Doesn't work with g++, needs actionf_p1 (don't modify this comment) @@ -3454,6 +3454,10 @@ state_t states[NUMSTATES] = {SPR_GRES, FF_ANIMATE|FF_PAPERSPRITE, -1, {NULL}, 2, 4, S_NULL}, // S_TIREGREASE + {SPR_OTFG, FF_FULLBRIGHT|FF_TRANS50, TICRATE, {NULL}, 0, 0, S_NULL}, // S_OVERTIMEFOG + {SPR_OTFG, 2|FF_FULLBRIGHT|FF_PAPERSPRITE, 1, {NULL}, 0, 0, S_NULL}, // S_OVERTIMEORB + {SPR_OTFG, 1|FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_NULL}, // S_OVERTIMEBEAM + #ifdef SEENAMES {SPR_NULL, 0, 1, {NULL}, 0, 0, S_NULL}, // S_NAMECHECK #endif @@ -20300,6 +20304,87 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_NULL // raisestate }, + { // MT_OVERTIMEFOG + -1, // doomednum + S_OVERTIMEFOG, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16<mo->colorized = false; player->mo->color = player->skincolor; } + } + else if (player->kartstuff[k_killfield]) // You're gonna REALLY diiiiie + { + const INT32 flashtime = 4<<(4-(player->kartstuff[k_killfield]/TICRATE)); + if (player->kartstuff[k_killfield] == 1 || (player->kartstuff[k_killfield] % (flashtime/2) != 0)) + { + player->mo->colorized = false; + player->mo->color = player->skincolor; + } + else if (player->kartstuff[k_killfield] % flashtime == 0) + { + player->mo->colorized = true; + player->mo->color = SKINCOLOR_BYZANTIUM; + } + else + { + player->mo->colorized = true; + player->mo->color = SKINCOLOR_RUBY; + } } else if (player->kartstuff[k_ringboost] && (leveltime & 1)) // ring boosting { @@ -5312,8 +5331,28 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) K_KartPlayerHUDUpdate(player); - if (G_BattleGametype() && player->kartstuff[k_bumper] > 0) - player->kartstuff[k_wanted]++; + if (G_BattleGametype() && player->kartstuff[k_bumper] > 0 + && !player->kartstuff[k_spinouttimer] && !player->kartstuff[k_squishedtimer] + && !player->kartstuff[k_respawn] && !player->powers[pw_flashing]) + { + player->kartstuff[k_wanted]++; + if (battleovertime.enabled >= 10*TICRATE) + { + if (P_AproxDistance(player->mo->x - battleovertime.x, player->mo->y - battleovertime.y) > battleovertime.radius) + { + player->kartstuff[k_killfield]++; + if (player->kartstuff[k_killfield] > 4*TICRATE) + { + K_SpinPlayer(player, NULL, 0, NULL, false); + //player->kartstuff[k_killfield] = 1; + } + } + else if (player->kartstuff[k_killfield] > 0) + player->kartstuff[k_killfield]--; + } + } + else if (player->kartstuff[k_killfield] > 0) + player->kartstuff[k_killfield]--; if (P_IsObjectOnGround(player->mo)) player->kartstuff[k_waterskip] = 0; @@ -6175,7 +6214,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) && NO_HYUDORO && !(HOLDING_ITEM || player->kartstuff[k_itemamount] || player->kartstuff[k_itemroulette] - || player->kartstuff[k_growshrinktimer] // Being disabled during Shrink was unintended but people seemed to be okay with it sooo... + || player->kartstuff[k_growshrinktimer] > 0 || player->kartstuff[k_rocketsneakertimer] || player->kartstuff[k_eggmanexplode])) player->kartstuff[k_userings] = 1; @@ -7265,7 +7304,8 @@ static patch_t *kp_lapanim_number[10][3]; static patch_t *kp_lapanim_emblem[2]; static patch_t *kp_lapanim_hand[3]; -static patch_t *kp_yougotem; +static patch_t *kp_yougotem; +static patch_t *kp_itemminimap; void K_LoadKartHUDGraphics(void) { @@ -7565,7 +7605,8 @@ void K_LoadKartHUDGraphics(void) kp_lapanim_hand[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); } - kp_yougotem = (patch_t *) W_CachePatchName("YOUGOTEM", PU_HUDGFX); + kp_yougotem = (patch_t *) W_CachePatchName("YOUGOTEM", PU_HUDGFX); + kp_itemminimap = (patch_t *) W_CachePatchName("MMAPITEM", PU_HUDGFX); } // For the item toggle menu @@ -9089,229 +9130,279 @@ static void K_drawKartPlayerCheck(void) V_DrawMappedPatch(x, CHEK_Y, V_HUDTRANS|splitflags, kp_check[pnum], colormap); } } -} +} -static void K_drawKartMinimapHead(mobj_t *mo, INT32 x, INT32 y, INT32 flags, patch_t *AutomapPic) -{ - // amnum xpos & ypos are the icon's speed around the HUD. - // The number being divided by is for how fast it moves. - // The higher the number, the slower it moves. +static void K_drawKartMinimapIcon(fixed_t objx, fixed_t objy, INT32 hudx, INT32 hudy, INT32 flags, patch_t *icon, UINT8 *colormap, patch_t *AutomapPic) +{ + // amnum xpos & ypos are the icon's speed around the HUD. + // The number being divided by is for how fast it moves. + // The higher the number, the slower it moves. + + // am xpos & ypos are the icon's starting position. Withouht + // it, they wouldn't 'spawn' on the top-right side of the HUD. + + fixed_t amnumxpos, amnumypos; + INT32 amxpos, amypos; + + node_t *bsp = &nodes[numnodes-1]; + fixed_t maxx, minx, maxy, miny; + + fixed_t mapwidth, mapheight; + fixed_t xoffset, yoffset; + fixed_t xscale, yscale, zoom; + + maxx = maxy = INT32_MAX; + minx = miny = INT32_MIN; + minx = bsp->bbox[0][BOXLEFT]; + maxx = bsp->bbox[0][BOXRIGHT]; + miny = bsp->bbox[0][BOXBOTTOM]; + maxy = bsp->bbox[0][BOXTOP]; + + if (bsp->bbox[1][BOXLEFT] < minx) + minx = bsp->bbox[1][BOXLEFT]; + if (bsp->bbox[1][BOXRIGHT] > maxx) + maxx = bsp->bbox[1][BOXRIGHT]; + if (bsp->bbox[1][BOXBOTTOM] < miny) + miny = bsp->bbox[1][BOXBOTTOM]; + if (bsp->bbox[1][BOXTOP] > maxy) + maxy = bsp->bbox[1][BOXTOP]; + + // You might be wondering why these are being bitshift here + // it's because mapwidth and height would otherwise overflow for maps larger than half the size possible... + // map boundaries and sizes will ALWAYS be whole numbers thankfully + // later calculations take into consideration that these are actually not in terms of FRACUNIT though + minx >>= FRACBITS; + maxx >>= FRACBITS; + miny >>= FRACBITS; + maxy >>= FRACBITS; + + mapwidth = maxx - minx; + mapheight = maxy - miny; + + // These should always be small enough to be bitshift back right now + xoffset = (minx + mapwidth/2)<width, mapwidth); + yscale = FixedDiv(AutomapPic->height, mapheight); + zoom = FixedMul(min(xscale, yscale), FRACUNIT-FRACUNIT/20); + + amnumxpos = (FixedMul(objx, zoom) - FixedMul(xoffset, zoom)); + amnumypos = -(FixedMul(objy, zoom) - FixedMul(yoffset, zoom)); + + if (encoremode) + amnumxpos = -amnumxpos; + + amxpos = amnumxpos + ((hudx + AutomapPic->width/2 - (icon->width/2))<height/2 - (icon->height/2))<width/2 + (icon->width/2))<width/2); + y = MINI_Y - (AutomapPic->height/2); + + if (timeinmap > 105) + { + minimaptrans = cv_kartminimap.value; + if (timeinmap <= 113) + minimaptrans = ((((INT32)timeinmap) - 105)*minimaptrans)/(113-105); + if (!minimaptrans) + return; + } + else + return; + + minimaptrans = ((10-minimaptrans)<width), y, splitflags|V_FLIP, AutomapPic); + else + V_DrawScaledPatch(x, y, splitflags, AutomapPic); + + if (!(splitscreen == 2)) + { + splitflags &= ~minimaptrans; + splitflags |= V_HUDTRANSHALF; + } + + // let offsets transfer to the heads, too! + if (encoremode) + x += SHORT(AutomapPic->leftoffset); + else + x -= SHORT(AutomapPic->leftoffset); + y -= SHORT(AutomapPic->topoffset); + + // Draw the super item in Battle + if (G_BattleGametype() && battleovertime.enabled) + { + if (battleovertime.enabled >= 10*TICRATE || (battleovertime.enabled & 1)) + { + const INT32 prevsplitflags = splitflags; + splitflags &= ~V_HUDTRANSHALF; + splitflags |= V_HUDTRANS; + colormap = R_GetTranslationColormap(TC_RAINBOW, (UINT8)(1 + (leveltime % (MAXSKINCOLORS-1))), GTC_CACHE); + K_drawKartMinimapIcon(battleovertime.x, battleovertime.y, x, y, splitflags, kp_itemminimap, colormap, AutomapPic); + splitflags = prevsplitflags; + } + } + + // initialize + for (i = 0; i < 4; i++) + localplayers[i] = -1; + + // Player's tiny icons on the Automap. (drawn opposite direction so player 1 is drawn last in splitscreen) + if (ghosts) + { + demoghost *g = ghosts; + while (g) + { + if (g->mo->skin) + skin = ((skin_t*)g->mo->skin)-skins; + else + skin = 0; + if (g->mo->color) + { + if (g->mo->colorized) + colormap = R_GetTranslationColormap(TC_RAINBOW, g->mo->color, GTC_CACHE); + else + colormap = R_GetTranslationColormap(skin, g->mo->color, GTC_CACHE); + } + else + colormap = NULL; + K_drawKartMinimapIcon(g->mo->x, g->mo->y, x, y, splitflags, facemmapprefix[skin], colormap, AutomapPic); + g = g->next; + } + + if (!stplyr->mo || stplyr->spectator) // do we need the latter..? + return; + + localplayers[numlocalplayers] = stplyr-players; + numlocalplayers++; + } + else + { + for (i = MAXPLAYERS-1; i >= 0; i--) + { + if (!playeringame[i]) + continue; + if (!players[i].mo || players[i].spectator) + continue; + + if (i != displayplayers[0] || splitscreen) + { + if (G_BattleGametype() && players[i].kartstuff[k_bumper] <= 0) + continue; + + if (players[i].kartstuff[k_hyudorotimer] > 0) + { + if (!((players[i].kartstuff[k_hyudorotimer] < 1*TICRATE/2 + || players[i].kartstuff[k_hyudorotimer] > hyudorotime-(1*TICRATE/2)) + && !(leveltime & 1))) + continue; + } + } + + if (i == displayplayers[0] || i == displayplayers[1] || i == displayplayers[2] || i == displayplayers[3]) + { + // Draw display players on top of everything else + localplayers[numlocalplayers] = i; + numlocalplayers++; + continue; + } + + if (players[i].mo->skin) + skin = ((skin_t*)players[i].mo->skin)-skins; + else + skin = 0; + + if (players[i].mo->color) + { + if (players[i].mo->colorized) + colormap = R_GetTranslationColormap(TC_RAINBOW, players[i].mo->color, GTC_CACHE); + else + colormap = R_GetTranslationColormap(skin, players[i].mo->color, GTC_CACHE); + } + else + colormap = NULL; + + K_drawKartMinimapIcon(players[i].mo->x, players[i].mo->y, x, y, splitflags, facemmapprefix[skin], colormap, AutomapPic); + // Target reticule + if ((G_RaceGametype() && players[i].kartstuff[k_position] == spbplace) + || (G_BattleGametype() && K_IsPlayerWanted(&players[i]))) + K_drawKartMinimapIcon(players[i].mo->x, players[i].mo->y, x, y, splitflags, kp_wantedreticle, NULL, AutomapPic); + } + } + + // draw our local players here, opaque. + splitflags &= ~V_HUDTRANSHALF; + splitflags |= V_HUDTRANS; + + for (i = 0; i < numlocalplayers; i++) + { + if (i == -1) + continue; // this doesn't interest us + + if (players[localplayers[i]].mo->skin) + skin = ((skin_t*)players[localplayers[i]].mo->skin)-skins; + else + skin = 0; + + if (players[localplayers[i]].mo->color) + { + if (players[localplayers[i]].mo->colorized) + colormap = R_GetTranslationColormap(TC_RAINBOW, players[localplayers[i]].mo->color, GTC_CACHE); + else + colormap = R_GetTranslationColormap(skin, players[localplayers[i]].mo->color, GTC_CACHE); + } + else + colormap = NULL; + + K_drawKartMinimapIcon(players[localplayers[i]].mo->x, players[localplayers[i]].mo->y, x, y, splitflags, facemmapprefix[skin], colormap, AutomapPic); - UINT8 skin = 0; - - fixed_t amnumxpos, amnumypos; - INT32 amxpos, amypos; - - node_t *bsp = &nodes[numnodes-1]; - fixed_t maxx, minx, maxy, miny; - - fixed_t mapwidth, mapheight; - fixed_t xoffset, yoffset; - fixed_t xscale, yscale, zoom; - - if (mo->skin) - skin = ((skin_t*)mo->skin)-skins; - - maxx = maxy = INT32_MAX; - minx = miny = INT32_MIN; - minx = bsp->bbox[0][BOXLEFT]; - maxx = bsp->bbox[0][BOXRIGHT]; - miny = bsp->bbox[0][BOXBOTTOM]; - maxy = bsp->bbox[0][BOXTOP]; - - if (bsp->bbox[1][BOXLEFT] < minx) - minx = bsp->bbox[1][BOXLEFT]; - if (bsp->bbox[1][BOXRIGHT] > maxx) - maxx = bsp->bbox[1][BOXRIGHT]; - if (bsp->bbox[1][BOXBOTTOM] < miny) - miny = bsp->bbox[1][BOXBOTTOM]; - if (bsp->bbox[1][BOXTOP] > maxy) - maxy = bsp->bbox[1][BOXTOP]; - - // You might be wondering why these are being bitshift here - // it's because mapwidth and height would otherwise overflow for maps larger than half the size possible... - // map boundaries and sizes will ALWAYS be whole numbers thankfully - // later calculations take into consideration that these are actually not in terms of FRACUNIT though - minx >>= FRACBITS; - maxx >>= FRACBITS; - miny >>= FRACBITS; - maxy >>= FRACBITS; - - mapwidth = maxx - minx; - mapheight = maxy - miny; - - // These should always be small enough to be bitshift back right now - xoffset = (minx + mapwidth/2)<width, mapwidth); - yscale = FixedDiv(AutomapPic->height, mapheight); - zoom = FixedMul(min(xscale, yscale), FRACUNIT-FRACUNIT/20); - - amnumxpos = (FixedMul(mo->x, zoom) - FixedMul(xoffset, zoom)); - amnumypos = -(FixedMul(mo->y, zoom) - FixedMul(yoffset, zoom)); - - if (encoremode) - amnumxpos = -amnumxpos; - - amxpos = amnumxpos + ((x + AutomapPic->width/2 - (facemmapprefix[skin]->width/2))<height/2 - (facemmapprefix[skin]->height/2))<width/2 + (facemmapprefix[skin]->width/2))<color) // 'default' color - V_DrawSciencePatch(amxpos, amypos, flags, facemmapprefix[skin], FRACUNIT); - else - { - UINT8 *colormap; - if (mo->colorized) - colormap = R_GetTranslationColormap(TC_RAINBOW, mo->color, GTC_CACHE); - else - colormap = R_GetTranslationColormap(skin, mo->color, GTC_CACHE); - V_DrawFixedPatch(amxpos, amypos, FRACUNIT, flags, facemmapprefix[skin], colormap); - if (mo->player - && ((G_RaceGametype() && mo->player->kartstuff[k_position] == spbplace) - || (G_BattleGametype() && K_IsPlayerWanted(mo->player)))) - { - V_DrawFixedPatch(amxpos - (4<width/2); - y = MINI_Y - (AutomapPic->height/2); - - if (timeinmap > 105) - { - minimaptrans = cv_kartminimap.value; - if (timeinmap <= 113) - minimaptrans = ((((INT32)timeinmap) - 105)*minimaptrans)/(113-105); - if (!minimaptrans) - return; - } - else - return; - - minimaptrans = ((10-minimaptrans)<width), y, splitflags|V_FLIP, AutomapPic); - else - V_DrawScaledPatch(x, y, splitflags, AutomapPic); - - if (!(splitscreen == 2)) - { - splitflags &= ~minimaptrans; - splitflags |= V_HUDTRANSHALF; - } - - // let offsets transfer to the heads, too! - if (encoremode) - x += SHORT(AutomapPic->leftoffset); - else - x -= SHORT(AutomapPic->leftoffset); - y -= SHORT(AutomapPic->topoffset); - - // initialize - for (i = 0; i < 4; i++) - localplayers[i] = -1; - - // Player's tiny icons on the Automap. (drawn opposite direction so player 1 is drawn last in splitscreen) - if (ghosts) - { - demoghost *g = ghosts; - while (g) - { - K_drawKartMinimapHead(g->mo, x, y, splitflags, AutomapPic); - g = g->next; - } - - if (!stplyr->mo || stplyr->spectator) // do we need the latter..? - return; - - localplayers[numlocalplayers] = stplyr-players; - numlocalplayers++; - } - else - { - for (i = MAXPLAYERS-1; i >= 0; i--) - { - if (!playeringame[i]) - continue; - if (!players[i].mo || players[i].spectator) - continue; - - if (i != displayplayers[0] || splitscreen) - { - if (G_BattleGametype() && players[i].kartstuff[k_bumper] <= 0) - continue; - - if (players[i].kartstuff[k_hyudorotimer] > 0) - { - if (!((players[i].kartstuff[k_hyudorotimer] < 1*TICRATE/2 - || players[i].kartstuff[k_hyudorotimer] > hyudorotime-(1*TICRATE/2)) - && !(leveltime & 1))) - continue; - } - } - - if (P_IsDisplayPlayer(&players[i])) - { - // Draw display players on top of everything else - localplayers[numlocalplayers] = i; - numlocalplayers++; - continue; - } - - K_drawKartMinimapHead(players[i].mo, x, y, splitflags, AutomapPic); - } - } - - // draw our local players here, opaque. - splitflags &= ~V_HUDTRANSHALF; - splitflags |= V_HUDTRANS; - - for (i = 0; i < numlocalplayers; i++) - { - if (i == -1) - continue; // this doesn't interest us - K_drawKartMinimapHead(players[localplayers[i]].mo, x, y, splitflags, AutomapPic); - } -} + // Target reticule + if ((G_RaceGametype() && players[localplayers[i]].kartstuff[k_position] == spbplace) + || (G_BattleGametype() && K_IsPlayerWanted(&players[localplayers[i]]))) + K_drawKartMinimapIcon(players[localplayers[i]].mo->x, players[localplayers[i]].mo->y, x, y, splitflags, kp_wantedreticle, NULL, AutomapPic); + } +} static void K_drawKartStartCountdown(void) { diff --git a/src/k_pwrlv.c b/src/k_pwrlv.c new file mode 100644 index 000000000..257b8ad06 --- /dev/null +++ b/src/k_pwrlv.c @@ -0,0 +1,301 @@ +/// \file k_pwrlv.c +/// \brief SRB2Kart Power Levels + +#include "k_pwrlv.h" +#include "d_netcmd.h" +#include "g_game.h" +#include "s_sound.h" +#include "m_random.h" +#include "m_cond.h" // M_UpdateUnlockablesAndExtraEmblems +#include "p_tick.h" // leveltime + +// Online rankings for the main gametypes. +// This array is saved to the gamedata. +UINT16 vspowerlevel[PWRLV_NUMTYPES]; + +// Client-sided calculations done for Power Levels. +// This is done so that clients will never be able to hack someone else's score over the server. +UINT16 clientpowerlevels[MAXPLAYERS][PWRLV_NUMTYPES]; + +// Which players spec-scummed, and their power level before scumming. +// On race finish, everyone is considered to have "won" against these people. +INT16 nospectategrief[MAXPLAYERS]; + +// Game setting scrambles based on server Power Level +SINT8 speedscramble = -1; +SINT8 encorescramble = -1; + +void K_ClearClientPowerLevels(void) +{ + UINT8 i, j; + for (i = 0; i < MAXPLAYERS; i++) + for (j = 0; j < PWRLV_NUMTYPES; j++) + clientpowerlevels[i][j] = 0; +} + +// 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 (PWRLVRECORD_MAX - 1) + if (diff > MAXDIFF) + diff = MAXDIFF; + if (diff < -MAXDIFF) + diff = -MAXDIFF; +#undef MAXDIFF + + x = ((diff-2)<= (2<= (1<> FRACBITS); +} + +INT16 K_CalculatePowerLevelAvg(void) +{ + fixed_t avg = 0; + UINT8 div = 0; + SINT8 t = PWRLV_DISABLED; + UINT8 i; + + if (!netgame || !cv_kartusepwrlv.value) + return 0; // No average. + + if (G_RaceGametype()) + t = PWRLV_RACE; + else if (G_BattleGametype()) + t = PWRLV_BATTLE; + + if (t == PWRLV_DISABLED) + return 0; // Hmm?! + + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator + || clientpowerlevels[i][t] == 0) // splitscreen player + continue; + + avg += clientpowerlevels[i][t]; + div++; + } + + if (!div) + return 0; // No average. + + avg /= div; + + return (INT16)(avg >> FRACBITS); +} + +// -- K_UpdatePowerLevels could not be moved here due to usage of y_data, unfortunately. -- + +void K_SetPowerLevelScrambles(SINT8 powertype) +{ + switch (powertype) + { + case PWRLV_RACE: + if (cv_kartspeed.value == -1 || cv_kartencore.value == -1) + { + UINT8 speed = atoi(cv_kartspeed.defaultvalue); + boolean encore = false; + INT16 avg = 0, min = 0; + UINT8 i, t = 0; + + avg = K_CalculatePowerLevelAvg(); + + for (i = 0; i < MAXPLAYERS; i++) + { + if (min == 0 || clientpowerlevels[i][0] < min) + min = clientpowerlevels[i][0]; + } + + if (min >= 7800) + { + if (avg >= 8200) + t = 5; + else + t = 4; + } + else if (min >= 6800) + { + if (avg >= 7200) + t = 4; + else + t = 3; + } + else if (min >= 5800) + { + if (avg >= 6200) + t = 3; + else + t = 2; + } + else if (min >= 3800) + { + if (avg >= 4200) + t = 2; + else + t = 1; + } + else if (min >= 1800) + { + if (avg >= 2200) + t = 1; + else + t = 0; + } + else + t = 0; + + switch (t) + { + case 5: + speed = 2; + encore = true; + break; + case 4: + speed = M_RandomChance((7<>1); + break; + case 3: + speed = M_RandomChance((3<>2); + break; + case 2: + speed = 1; + encore = M_RandomChance(FRACUNIT>>3); + break; + case 1: default: + speed = 1; + encore = false; + break; + case 0: + speed = M_RandomChance((3< PWRLVRECORD_MAX) // I mean... we're subtracting... but y'know how it is :V + inc -= ((yourpower + inc) - PWRLVRECORD_MAX); + if (yourpower + inc < PWRLVRECORD_MIN) + inc -= ((yourpower + inc) - PWRLVRECORD_MIN); + + 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! + } +} diff --git a/src/k_pwrlv.h b/src/k_pwrlv.h new file mode 100644 index 000000000..05fbf1c8a --- /dev/null +++ b/src/k_pwrlv.h @@ -0,0 +1,31 @@ +#ifndef __K_PWRLV__ +#define __K_PWRLV__ + +#include "doomtype.h" +#include "doomdef.h" + +#define PWRLV_DISABLED -1 +#define PWRLV_RACE 0 +#define PWRLV_BATTLE 1 +#define PWRLV_NUMTYPES 2 + +#define PWRLVRECORD_START 1000 // 5000? +#define PWRLVRECORD_DEF 5000 +#define PWRLVRECORD_MIN 1 +#define PWRLVRECORD_MAX 9999 + +extern SINT8 speedscramble; +extern SINT8 encorescramble; + +extern UINT16 vspowerlevel[PWRLV_NUMTYPES]; +extern UINT16 clientpowerlevels[MAXPLAYERS][PWRLV_NUMTYPES]; +extern INT16 nospectategrief[MAXPLAYERS]; + +void K_ClearClientPowerLevels(void); +INT16 K_CalculatePowerLevelInc(INT16 diff); +INT16 K_CalculatePowerLevelAvg(void); +//void K_UpdatePowerLevels(void); +void K_SetPowerLevelScrambles(SINT8 powertype); +void K_PlayerForfeit(UINT8 playernum, boolean nopointloss); + +#endif diff --git a/src/m_anigif.c b/src/m_anigif.c index 4e68819bc..4dfc77cb3 100644 --- a/src/m_anigif.c +++ b/src/m_anigif.c @@ -422,7 +422,7 @@ static void GIF_headwrite(void) WRITEUINT16(p, rheight); // colors, aspect, etc - WRITEUINT8(p, 0xF7); + WRITEUINT8(p, 0xFF); // TRANSPARENTPIXEL WRITEUINT8(p, 0x00); WRITEUINT8(p, 0x00); diff --git a/src/m_cond.c b/src/m_cond.c index b777e7d22..b56882c90 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -21,6 +21,7 @@ #include "r_things.h" // numskins //#include "r_draw.h" // R_GetColorByName #include "k_kart.h" // K_GetKartColorByName +#include "k_pwrlv.h" // Map triggers for linedef executors // 32 triggers, one bit each @@ -289,6 +290,8 @@ UINT8 M_CheckCondition(condition_t *cn) return (totalplaytime >= (unsigned)cn->requirement); case UC_MATCHESPLAYED: // Requires any level completed >= x times return (matchesplayed >= (unsigned)cn->requirement); + case UC_POWERLEVEL: // Requires power level >= x on a certain gametype + return (vspowerlevel[cn->extrainfo1] >= (unsigned)cn->requirement); case UC_GAMECLEAR: // Requires game beaten >= x times return (timesBeaten >= (unsigned)cn->requirement); case UC_ALLEMERALDS: // Requires game beaten with all 7 emeralds >= x times diff --git a/src/m_cond.h b/src/m_cond.h index 07e4480d1..c6362ec5d 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -22,28 +22,29 @@ typedef enum { UC_PLAYTIME, // PLAYTIME [tics] UC_MATCHESPLAYED, // SRB2Kart: MATCHESPLAYED [x played] - UC_GAMECLEAR, // GAMECLEAR - UC_ALLEMERALDS, // ALLEMERALDS - //UC_ULTIMATECLEAR, // ULTIMATECLEAR - //UC_OVERALLSCORE, // OVERALLSCORE [score to beat] - UC_OVERALLTIME, // OVERALLTIME [time to beat, tics] - //UC_OVERALLRINGS, // OVERALLRINGS [rings to beat] - UC_MAPVISITED, // MAPVISITED [map number] - UC_MAPBEATEN, // MAPBEATEN [map number] - UC_MAPALLEMERALDS, // MAPALLEMERALDS [map number] - //UC_MAPULTIMATE, // MAPULTIMATE [map number] - //UC_MAPPERFECT, // MAPPERFECT [map number] - //UC_MAPSCORE, // MAPSCORE [map number] [score to beat] - UC_MAPTIME, // MAPTIME [map number] [time to beat, tics] - //UC_MAPRINGS, // MAPRINGS [map number] [rings to beat] - //UC_NIGHTSSCORE, // NIGHTSSCORE [map number] [score to beat] - //UC_NIGHTSTIME, // NIGHTSTIME [map number] [time to beat, tics] - //UC_NIGHTSGRADE, // NIGHTSGRADE [map number] [grade] - UC_TRIGGER, // TRIGGER [trigger number] - UC_TOTALEMBLEMS, // TOTALEMBLEMS [number of emblems] - UC_EMBLEM, // EMBLEM [emblem number] - UC_EXTRAEMBLEM, // EXTRAEMBLEM [extra emblem number] - UC_CONDITIONSET, // CONDITIONSET [condition set number] + UC_POWERLEVEL, // SRB2Kart: POWERLEVEL [power level to reach] [gametype, "0" for race, "1" for battle] + UC_GAMECLEAR, // GAMECLEAR + UC_ALLEMERALDS, // ALLEMERALDS + //UC_ULTIMATECLEAR, // ULTIMATECLEAR + //UC_OVERALLSCORE, // OVERALLSCORE [score to beat] + UC_OVERALLTIME, // OVERALLTIME [time to beat, tics] + //UC_OVERALLRINGS, // OVERALLRINGS [rings to beat] + UC_MAPVISITED, // MAPVISITED [map number] + UC_MAPBEATEN, // MAPBEATEN [map number] + UC_MAPALLEMERALDS, // MAPALLEMERALDS [map number] + //UC_MAPULTIMATE, // MAPULTIMATE [map number] + //UC_MAPPERFECT, // MAPPERFECT [map number] + //UC_MAPSCORE, // MAPSCORE [map number] [score to beat] + UC_MAPTIME, // MAPTIME [map number] [time to beat, tics] + //UC_MAPRINGS, // MAPRINGS [map number] [rings to beat] + //UC_NIGHTSSCORE, // NIGHTSSCORE [map number] [score to beat] + //UC_NIGHTSTIME, // NIGHTSTIME [map number] [time to beat, tics] + //UC_NIGHTSGRADE, // NIGHTSGRADE [map number] [grade] + UC_TRIGGER, // TRIGGER [trigger number] + UC_TOTALEMBLEMS, // TOTALEMBLEMS [number of emblems] + UC_EMBLEM, // EMBLEM [emblem number] + UC_EXTRAEMBLEM, // EXTRAEMBLEM [extra emblem number] + UC_CONDITIONSET, // CONDITIONSET [condition set number] } conditiontype_t; // Condition Set information diff --git a/src/m_menu.c b/src/m_menu.c index 166b115d6..d0d818db6 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -57,6 +57,7 @@ #include "st_stuff.h" #include "i_sound.h" #include "k_kart.h" // SRB2kart +#include "k_pwrlv.h" #include "d_player.h" // KITEM_ constants #include "i_joy.h" // for joystick menu controls @@ -89,6 +90,10 @@ int snprintf(char *str, size_t n, const char *fmt, ...); #define SLIDER_WIDTH (8*SLIDER_RANGE+6) #define SERVERS_PER_PAGE 11 +#if defined (NONET) || defined (TESTERS) +#define NOMENUHOST +#endif + typedef enum { QUITMSG = 0, @@ -241,14 +246,18 @@ static menu_t SP_TimeAttackDef, SP_ReplayDef, SP_GuestReplayDef, SP_GhostDef; // Multiplayer #ifndef NONET +#ifndef TESTERS static void M_StartServerMenu(INT32 choice); +#endif static void M_ConnectMenu(INT32 choice); static void M_ConnectMenuModChecks(INT32 choice); static void M_Refresh(INT32 choice); static void M_Connect(INT32 choice); static void M_ChooseRoom(INT32 choice); #endif +#ifndef TESTERS static void M_StartOfflineServerMenu(INT32 choice); +#endif static void M_StartServer(INT32 choice); static void M_SetupMultiPlayer(INT32 choice); static void M_SetupMultiPlayer2(INT32 choice); @@ -474,9 +483,14 @@ static menuitem_t MainMenu[] = { {IT_SUBMENU|IT_STRING, NULL, "Extras", &SR_MainDef, 76}, //{IT_CALL |IT_STRING, NULL, "1 Player", M_SinglePlayerMenu, 84}, +#ifdef TESTERS + {IT_GRAYEDOUT, NULL, "Time Attack", NULL, 84}, +#else {IT_CALL |IT_STRING, NULL, "Time Attack", M_TimeAttack, 84}, +#endif {IT_SUBMENU|IT_STRING, NULL, "Multiplayer", &MP_MainDef, 92}, {IT_CALL |IT_STRING, NULL, "Options", M_Options, 100}, + /* I don't think is useful at all... */ {IT_CALL |IT_STRING, NULL, "Addons", M_Addons, 108}, {IT_CALL |IT_STRING, NULL, "Quit Game", M_QuitSRB2, 116}, }; @@ -727,7 +741,9 @@ static menuitem_t SR_PandorasBox[] = // Sky Room Custom Unlocks static menuitem_t SR_MainMenu[] = { +#ifndef TESTERS {IT_STRING|IT_SUBMENU, NULL, "Unlockables", &SR_UnlockChecklistDef, 100}, +#endif {IT_CALL|IT_STRING|IT_CALL_NOTMODIFIED, NULL, "Statistics", M_Statistics, 108}, {IT_CALL|IT_STRING, NULL, "Replay Hut", M_ReplayHut, 116}, {IT_DISABLED, NULL, "", NULL, 0}, // Custom1 @@ -961,12 +977,16 @@ static menuitem_t MP_MainMenu[] = {IT_STRING|IT_KEYHANDLER,NULL, "Player setup...", M_SetupMultiHandler,18}, {IT_HEADER, NULL, "Host a game", NULL, 100-24}, -#ifndef NONET +#ifndef NOMENUHOST {IT_STRING|IT_CALL, NULL, "Internet/LAN...", M_StartServerMenu, 110-24}, #else {IT_GRAYEDOUT, NULL, "Internet/LAN...", NULL, 110-24}, #endif +#ifdef TESTERS + {IT_GRAYEDOUT, NULL, "Offline...", NULL, 118-24}, +#else {IT_STRING|IT_CALL, NULL, "Offline...", M_StartOfflineServerMenu, 118-24}, +#endif {IT_HEADER, NULL, "Join a game", NULL, 132-24}, #ifndef NONET @@ -1482,8 +1502,7 @@ static menuitem_t OP_GameOptionsMenu[] = {IT_STRING | IT_CVAR, NULL, "Starting Bumpers", &cv_kartbumpers, 110}, {IT_STRING | IT_CVAR, NULL, "Karma Comeback", &cv_kartcomeback, 120}, - {IT_STRING | IT_CVAR, NULL, "Force Character", &cv_forceskin, 140}, - {IT_STRING | IT_CVAR, NULL, "Restrict Character Changes", &cv_restrictskinchange, 150}, + {IT_STRING | IT_CVAR, NULL, "Track Power Levels", &cv_kartusepwrlv, 140}, }; static menuitem_t OP_ServerOptionsMenu[] = @@ -3029,7 +3048,11 @@ void M_StartControlPanel(void) //MainMenu[secrets].status = (M_AnySecretUnlocked()) ? (IT_STRING | IT_CALL) : (IT_DISABLED); currentMenu = &MainDef; +#ifdef TESTERS + itemOn = multiplr; +#else itemOn = singleplr; +#endif } else if (modeattacking) { @@ -4092,6 +4115,14 @@ static void M_DrawCenteredMenu(void) W_CachePatchName(currentMenu->menuitems[i].patch,PU_CACHE), graymap); y += LINEHEIGHT; break; + case IT_TRANSTEXT: + if (currentMenu->menuitems[i].alphaKey) + y = currentMenu->y+currentMenu->menuitems[i].alphaKey; + /* FALLTHRU */ + case IT_TRANSTEXT2: + V_DrawCenteredString(x, y, V_TRANSLUCENT, currentMenu->menuitems[i].text); + y += SMALLLINEHEIGHT; + break; } } @@ -6210,6 +6241,8 @@ static char *M_GetConditionString(condition_t cond) G_TicsToSeconds(cond.requirement)); case UC_MATCHESPLAYED: return va("Play %d matches", cond.requirement); + case UC_POWERLEVEL: + return va("Reach power level %d in %s", cond.requirement, (cond.extrainfo1 == PWRLV_BATTLE ? "Battle" : "Race")); case UC_GAMECLEAR: if (cond.requirement > 1) return va("Beat game %d times", cond.requirement); @@ -7294,7 +7327,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); @@ -7403,9 +7436,14 @@ static void M_DrawLevelStats(void) G_TicsToHours(totalplaytime), G_TicsToMinutes(totalplaytime, false), G_TicsToSeconds(totalplaytime))); + V_DrawString(20, 42, highlightflags, "Total Matches:"); V_DrawRightAlignedString(BASEVIDWIDTH-16, 42, 0, va("%i played", matchesplayed)); + V_DrawString(20, 52, highlightflags, "Online Power Level:"); + V_DrawRightAlignedString(BASEVIDWIDTH-16, 52, 0, va("Race: %i", vspowerlevel[PWRLV_RACE])); + V_DrawRightAlignedString(BASEVIDWIDTH-16, 60, 0, va("Battle: %i", vspowerlevel[PWRLV_BATTLE])); + for (i = 0; i < NUMMAPS; i++) { if (!mapheaderinfo[i] || !(mapheaderinfo[i]->menuflags & LF2_RECORDATTACK)) @@ -7420,18 +7458,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); } @@ -8261,6 +8299,7 @@ static void M_DrawConnectMenu(void) UINT16 i; const char *gt = "Unknown"; const char *spd = ""; + const char *pwr = "----"; INT32 numPages = (serverlistcount+(SERVERS_PER_PAGE-1))/SERVERS_PER_PAGE; for (i = FIRSTSERVERLINE; i < min(localservercount, SERVERS_PER_PAGE)+FIRSTSERVERLINE; i++) @@ -8295,33 +8334,38 @@ static void M_DrawConnectMenu(void) V_DrawString(currentMenu->x, S_LINEY(i), globalflags, serverlist[slindex].info.servername); - // Don't use color flags intentionally, the global yellow color will auto override the text color code - if (serverlist[slindex].info.modifiedgame) - V_DrawSmallString(currentMenu->x+202, S_LINEY(i)+8, globalflags, "\x85" "Mod"); - if (serverlist[slindex].info.cheatsenabled) - V_DrawSmallString(currentMenu->x+222, S_LINEY(i)+8, globalflags, "\x83" "Cheats"); + if (serverlist[slindex].info.kartvars & SV_PASSWORD) + V_DrawFixedPatch((currentMenu->x - 9) << FRACBITS, (S_LINEY(i)) << FRACBITS, FRACUNIT, globalflags & (~V_ALLOWLOWERCASE), W_CachePatchName("SERVLOCK", PU_CACHE), NULL); V_DrawSmallString(currentMenu->x, S_LINEY(i)+8, globalflags, va("Ping: %u", (UINT32)LONG(serverlist[slindex].info.time))); + V_DrawSmallString(currentMenu->x+44,S_LINEY(i)+8, globalflags, + va("Players: %02d/%02d", serverlist[slindex].info.numberofplayer, serverlist[slindex].info.maxplayer)); + gt = "Unknown"; if (serverlist[slindex].info.gametype < NUMGAMETYPES) gt = Gametype_Names[serverlist[slindex].info.gametype]; - - V_DrawSmallString(currentMenu->x+46,S_LINEY(i)+8, globalflags, - va("Players: %02d/%02d", serverlist[slindex].info.numberofplayer, serverlist[slindex].info.maxplayer)); - - V_DrawSmallString(currentMenu->x+112, S_LINEY(i)+8, globalflags, gt); + V_DrawSmallString(currentMenu->x+108, S_LINEY(i)+8, globalflags, gt); if (serverlist[slindex].info.gametype == GT_RACE) { spd = kartspeed_cons_t[serverlist[slindex].info.kartvars & SV_SPEEDMASK].strvalue; - - V_DrawSmallString(currentMenu->x+132, S_LINEY(i)+8, globalflags, va("(%s Speed)", spd)); + V_DrawSmallString(currentMenu->x+128, S_LINEY(i)+8, globalflags, va("(%s)", spd)); } - if (serverlist[slindex].info.kartvars & SV_PASSWORD) - V_DrawFixedPatch((currentMenu->x - 9) << FRACBITS, (S_LINEY(i)) << FRACBITS, FRACUNIT, globalflags & (~V_ALLOWLOWERCASE), W_CachePatchName("SERVLOCK", PU_CACHE), NULL); + pwr = "----"; + if (serverlist[slindex].info.avgpwrlv == -1) + pwr = "Off"; + else if (serverlist[slindex].info.avgpwrlv > 0) + pwr = va("%04d", serverlist[slindex].info.avgpwrlv); + V_DrawSmallString(currentMenu->x+171, S_LINEY(i)+8, globalflags, va("Power Level: %s", pwr)); + + // Don't use color flags intentionally, the global yellow color will auto override the text color code + if (serverlist[slindex].info.modifiedgame) + V_DrawSmallString(currentMenu->x+245, S_LINEY(i)+8, globalflags, "\x85" "Mod"); + if (serverlist[slindex].info.cheatsenabled) + V_DrawSmallString(currentMenu->x+265, S_LINEY(i)+8, globalflags, "\x83" "Cheats"); MP_ConnectMenu[i+FIRSTSERVERLINE].status = IT_STRING | IT_CALL; } @@ -8600,11 +8644,11 @@ static void M_StartServer(INT32 choice) paused = false; SV_StartSinglePlayerServer(); multiplayer = true; // yeah, SV_StartSinglePlayerServer clobbers this... - D_MapChange(cv_nextmap.value, cv_newgametype.value, (boolean)cv_kartencore.value, 1, 1, false, false); + D_MapChange(cv_nextmap.value, cv_newgametype.value, (cv_kartencore.value == 1), 1, 1, false, false); } else { - D_MapChange(cv_nextmap.value, cv_newgametype.value, (boolean)cv_kartencore.value, 1, 1, false, false); + D_MapChange(cv_nextmap.value, cv_newgametype.value, (cv_kartencore.value == 1), 1, 1, false, false); COM_BufAddText("dummyconsvar 1\n"); } @@ -8641,7 +8685,7 @@ static void M_DrawLevelSelectOnly(boolean leftfade, boolean rightfade) V_DrawFill(x-1, y-1, w+2, i+2, trans); // variable reuse... - if (!cv_kartencore.value || gamestate == GS_TIMEATTACK || cv_newgametype.value != GT_RACE) + if ((cv_kartencore.value != 1) || gamestate == GS_TIMEATTACK || cv_newgametype.value != GT_RACE) V_DrawSmallScaledPatch(x, y, 0, PictureOfLevel); else { @@ -8774,6 +8818,7 @@ static void M_MapChange(INT32 choice) M_SetupNextMenu(&MISC_ChangeLevelDef); } +#ifndef TESTERS static void M_StartOfflineServerMenu(INT32 choice) { (void)choice; @@ -8781,8 +8826,10 @@ static void M_StartOfflineServerMenu(INT32 choice) M_PrepareLevelSelect(); M_SetupNextMenu(&MP_OfflineServerDef); } +#endif #ifndef NONET +#ifndef TESTERS static void M_StartServerMenu(INT32 choice) { (void)choice; @@ -8792,6 +8839,7 @@ static void M_StartServerMenu(INT32 choice) M_SetupNextMenu(&MP_ServerDef); } +#endif // ============== // CONNECT VIA IP @@ -8811,7 +8859,7 @@ static void M_DrawMPMainMenu(void) // use generic drawer for cursor, items and title M_DrawGenericMenu(); -#ifndef NONET +#ifndef NOMENUHOST #if MAXPLAYERS != 16 Update the maxplayers label... #endif @@ -8819,10 +8867,12 @@ Update the maxplayers label... ((itemOn == 4) ? highlightflags : 0), "(2-16 players)"); #endif +#ifndef TESTERS V_DrawRightAlignedString(BASEVIDWIDTH-x, y+MP_MainMenu[5].alphaKey, ((itemOn == 5) ? highlightflags : 0), "(2-4 players)" ); +#endif #ifndef NONET y += MP_MainMenu[8].alphaKey; @@ -9583,6 +9633,8 @@ static UINT8 erasecontext = 0; static void M_EraseDataResponse(INT32 ch) { + UINT8 i; + if (ch != 'y' && ch != KEY_ENTER) return; @@ -9594,6 +9646,8 @@ static void M_EraseDataResponse(INT32 ch) // SRB2Kart: This actually needs to be done FIRST, so that you don't immediately regain playtime/matches secrets totalplaytime = 0; matchesplayed = 0; + for (i = 0; i < PWRLV_NUMTYPES; i++) + vspowerlevel[i] = PWRLVRECORD_START; F_StartIntro(); } if (erasecontext != 1) diff --git a/src/p_enemy.c b/src/p_enemy.c index c3f316225..e185add81 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -3559,8 +3559,6 @@ void A_BubbleCheck(mobj_t *actor) // void A_AttractChase(mobj_t *actor) { - fixed_t z; - #ifdef HAVE_BLUA if (LUA_CallAction("A_AttractChase", actor)) return; @@ -3604,12 +3602,11 @@ void A_AttractChase(mobj_t *actor) { fixed_t offz = FixedMul(80*actor->target->scale, FINESINE(FixedAngle((90 - (9 * abs(10 - actor->extravalue1))) << FRACBITS) >> ANGLETOFINESHIFT)); //P_SetScale(actor, (actor->destscale = actor->target->scale)); - z = actor->target->z; - if (( actor->eflags & MFE_VERTICALFLIP )) - z -= actor->height + offz; - else - z += actor->target->height + offz; - P_TeleportMove(actor, actor->target->x, actor->target->y, z); + actor->z = actor->target->z; + K_MatchGenericExtraFlags(actor, actor->target); + P_TeleportMove(actor, actor->target->x, actor->target->y, + actor->z + + ( actor->target->height + offz )* P_MobjFlip(actor)); actor->extravalue1++; } } @@ -3636,15 +3633,12 @@ void A_AttractChase(mobj_t *actor) fixed_t dist = (actor->target->radius/4) * (16 - actor->extravalue1); P_SetScale(actor, (actor->destscale = actor->target->scale - ((actor->target->scale/14) * actor->extravalue1))); - z = actor->target->z; - if (( actor->eflags & MFE_VERTICALFLIP )) - z += actor->target->height - actor->height - 24 * actor->target->scale; - else - z += 24 * actor->target->scale; + actor->z = actor->target->z; + K_MatchGenericExtraFlags(actor, actor->target); P_TeleportMove(actor, actor->target->x + FixedMul(dist, FINECOSINE(actor->angle >> ANGLETOFINESHIFT)), actor->target->y + FixedMul(dist, FINESINE(actor->angle >> ANGLETOFINESHIFT)), - z); + actor->z + actor->target->scale * 24 * P_MobjFlip(actor)); actor->angle += ANG30; actor->extravalue1++; @@ -8300,7 +8294,7 @@ void A_ItemPop(mobj_t *actor) remains->flags = actor->flags; // Transfer flags remains->flags2 = actor->flags2; // Transfer flags2 remains->fuse = actor->fuse; // Transfer respawn timer - remains->threshold = (actor->threshold == 69 ? 69 : 68); + remains->threshold = (actor->threshold == 70 ? 70 : (actor->threshold == 69 ? 69 : 68)); remains->skin = NULL; remains->spawnpoint = actor->spawnpoint; @@ -8314,7 +8308,7 @@ void A_ItemPop(mobj_t *actor) remains->flags2 &= ~MF2_AMBUSH; - if (G_BattleGametype() && actor->threshold != 69) + if (G_BattleGametype() && (actor->threshold != 69 && actor->threshold != 70)) numgotboxes++; P_RemoveMobj(actor); diff --git a/src/p_inter.c b/src/p_inter.c index 63e091828..b370682f1 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -27,6 +27,7 @@ #include "m_misc.h" #include "v_video.h" // video flags for CEchos #include "k_kart.h" // SRB2kart +#include "k_pwrlv.h" // CTF player names #define CTFTEAMCODE(pl) pl->ctfteam ? (pl->ctfteam == 1 ? "\x85" : "\x84") : "" @@ -1763,6 +1764,9 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) } } +// Easily make it so that overtime works offline +//#define TESTOVERTIMEINFREEPLAY + /** Checks if the level timer is over the timelimit and the round should end, * unless you are in overtime. In which case leveltime may stretch out beyond * timelimitintics and overtime's status will be checked here each tick. @@ -1773,7 +1777,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) */ void P_CheckTimeLimit(void) { - INT32 i, k; + INT32 i; if (!cv_timelimit.value) return; @@ -1807,71 +1811,76 @@ void P_CheckTimeLimit(void) } } } + else*/ //Optional tie-breaker for Match/CTF - else*/ if (cv_overtime.value) + if (cv_overtime.value) { - INT32 playerarray[MAXPLAYERS]; - INT32 tempplayer = 0; - INT32 spectators = 0; - INT32 playercount = 0; - - //Figure out if we have enough participating players to care. +#ifndef TESTOVERTIMEINFREEPLAY + boolean foundone = false; // Overtime is used for closing off down to a specific item. for (i = 0; i < MAXPLAYERS; i++) { - if (players[i].exiting) - return; - if (playeringame[i] && players[i].spectator) - spectators++; - } - - if ((D_NumPlayers() - spectators) > 1) - { - // Play the starpost sfx after the first second of overtime. - if (gamestate == GS_LEVEL && (leveltime == (timelimitintics + TICRATE))) - S_StartSound(NULL, sfx_strpst); - - // Normal Match - if (!G_GametypeHasTeams()) + if (!playeringame[i] || players[i].spectator) + continue; + if (foundone) { - //Store the nodes of participating players in an array. - for (i = 0; i < MAXPLAYERS; i++) +#endif + // Initiate the kill zone + if (!battleovertime.enabled) { - if (playeringame[i] && !players[i].spectator) + INT32 b = 0; + thinker_t *th; + mobj_t *item = NULL; + + P_RespawnBattleBoxes(); // FORCE THESE TO BE RESPAWNED FOR THIS!!!!!!! + + // Find us an item box to center on. + for (th = thinkercap.next; th != &thinkercap; th = th->next) { - playerarray[playercount] = i; - playercount++; + mobj_t *thismo; + if (th->function.acp1 != (actionf_p1)P_MobjThinker) + continue; + thismo = (mobj_t *)th; + + if (thismo->type != MT_RANDOMITEM) + continue; + if (thismo->threshold == 69) // Disappears + continue; + + b++; + + // Only select items that are on the ground, ignore ones in the air. Ambush flag inverts this rule. + if ((!P_IsObjectOnGround(thismo)) != (thismo->flags2 & MF2_AMBUSH)) + continue; + + if (item == NULL || (b < nummapboxes && P_RandomChance(((nummapboxes-b)*FRACUNIT)/nummapboxes))) // This is to throw off the RNG some + item = thismo; + if (b >= nummapboxes) // end early if we've found them all already + break; } - } - if (playercount > MAXPLAYERS) - playercount = MAXPLAYERS; - - //Sort 'em. - for (i = 1; i < playercount; i++) - { - for (k = i; k < playercount; k++) + if (item == NULL) // no item found, could happen if every item is in the air or has ambush flag, or the map has none { - if (players[playerarray[i-1]].marescore < players[playerarray[k]].marescore) - { - tempplayer = playerarray[i-1]; - playerarray[i-1] = playerarray[k]; - playerarray[k] = tempplayer; - } + CONS_Alert(CONS_WARNING, "No usuable items for Battle overtime!\n"); + return; } - } - //End the round if the top players aren't tied. - if (players[playerarray[0]].marescore == players[playerarray[1]].marescore) - return; + item->threshold = 70; // Set constant respawn + battleovertime.x = item->x; + battleovertime.y = item->y; + battleovertime.z = item->z; + battleovertime.radius = 4096*mapobjectscale; + battleovertime.minradius = (cv_overtime.value == 2 ? 40 : 512) * mapobjectscale; + battleovertime.enabled = 1; + S_StartSound(NULL, sfx_kc47); + } + return; +#ifndef TESTOVERTIMEINFREEPLAY } else - { - //In team match and CTF, determining a tie is much simpler. =P - if (redscore == bluescore) - return; - } + foundone = true; } +#endif } for (i = 0; i < MAXPLAYERS; i++) @@ -1882,9 +1891,6 @@ void P_CheckTimeLimit(void) return; P_DoPlayerExit(&players[i]); } - - /*if (server) - SendNetXCmd(XD_EXITLEVEL, NULL, 0);*/ } /** Checks if a player's score is over the pointlimit and the round should end. @@ -2030,7 +2036,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++) @@ -2047,57 +2054,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_local.h b/src/p_local.h index 220ee1c3d..dde84d4f0 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -210,6 +210,7 @@ extern tic_t itemrespawntime[ITEMQUESIZE]; extern size_t iquehead, iquetail; extern consvar_t cv_gravity/*, cv_viewheight*/; +void P_RespawnBattleBoxes(void); void P_RespawnSpecials(void); mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type); @@ -228,6 +229,7 @@ boolean P_SetMobjState(mobj_t *mobj, statenum_t state); void P_RunOverlays(void); fixed_t P_CalculateShadowFloor(mobj_t *mobj, fixed_t x, fixed_t y, fixed_t z, fixed_t radius, fixed_t height, boolean flip, boolean player); void P_RunShadows(void); +void P_RunBattleOvertime(void); void P_MobjThinker(mobj_t *mobj); boolean P_RailThinker(mobj_t *mobj); void P_PushableThinker(mobj_t *mobj); diff --git a/src/p_mobj.c b/src/p_mobj.c index 773428857..cbe7238b3 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -6486,6 +6486,216 @@ static void P_RemoveShadow(mobj_t *thing) } } +// SAL'S KART BATTLE MODE OVERTIME HANDLER +#define MAXPLANESPERSECTOR (MAXFFLOORS+1)*2 +static void P_SpawnOvertimeParticles(fixed_t x, fixed_t y, fixed_t scale, mobjtype_t type, boolean ceiling) +{ + UINT8 i; + fixed_t flatz[MAXPLANESPERSECTOR]; + boolean flip[MAXPLANESPERSECTOR]; + UINT8 numflats = 0; + mobj_t *mo; + subsector_t *ss = R_IsPointInSubsector(x, y); + sector_t *sec; + + if (!ss) + return; + sec = ss->sector; + + // convoluted stuff JUST to get all of the planes we need to draw orbs on :V + + for (i = 0; i < MAXPLANESPERSECTOR; i++) + flip[i] = false; + + if (sec->floorpic != skyflatnum) + { +#ifdef ESLOPE + flatz[numflats] = (sec->f_slope ? P_GetZAt(sec->f_slope, x, y) : sec->floorheight); +#else + flatz[numflats] = (sec->floorheight); +#endif + numflats++; + } + if (sec->ceilingpic != skyflatnum && ceiling) + { +#ifdef ESLOPE + flatz[numflats] = (sec->c_slope ? P_GetZAt(sec->c_slope, x, y) : sec->ceilingheight) - FixedMul(mobjinfo[type].height, scale); +#else + flatz[numflats] = (sec->ceilingheight) - FixedMul(mobjinfo[type].height, scale); +#endif + flip[numflats] = true; + numflats++; + } + + if (sec->ffloors) + { + ffloor_t *rover; + for (rover = sec->ffloors; rover; rover = rover->next) + { + if (!(rover->flags & FF_EXISTS) || !(rover->flags & FF_BLOCKPLAYER)) + continue; + if (*rover->toppic != skyflatnum) + { +#ifdef ESLOPE + flatz[numflats] = (*rover->t_slope ? P_GetZAt(*rover->t_slope, x, y) : *rover->topheight); +#else + flatz[numflats] = (*rover->topheight); +#endif + numflats++; + } + if (*rover->bottompic != skyflatnum && ceiling) + { +#ifdef ESLOPE + flatz[numflats] = (*rover->b_slope ? P_GetZAt(*rover->b_slope, x, y) : *rover->bottomheight) - FixedMul(mobjinfo[type].height, scale); +#else + flatz[numflats] = (*rover->bottomheight) - FixedMul(mobjinfo[type].height, scale); +#endif + flip[numflats] = true; + numflats++; + } + } + } + + if (numflats <= 0) // no flats + return; + + for (i = 0; i < numflats; i++) + { + mo = P_SpawnMobj(x, y, flatz[i], type); + + // Lastly, if this can see the skybox mobj, then... we just wasted our time :V + if (skyboxmo[0] && !P_MobjWasRemoved(skyboxmo[0])) + { + const fixed_t sbz = skyboxmo[0]->z; + fixed_t checkz = sec->floorheight; + + while (checkz < sec->ceilingheight) + { + P_TeleportMove(skyboxmo[0], skyboxmo[0]->x, skyboxmo[0]->y, checkz); + if (P_CheckSight(skyboxmo[0], mo)) + { + P_RemoveMobj(mo); + break; + } + else + checkz += 32*mapobjectscale; + } + + P_TeleportMove(skyboxmo[0], skyboxmo[0]->x, skyboxmo[0]->y, sbz); + + if (P_MobjWasRemoved(mo)) + continue; + } + + P_SetScale(mo, scale); + + if (flip[i]) + { + mo->flags2 |= MF2_OBJECTFLIP; + mo->eflags |= MFE_VERTICALFLIP; + } + + switch(type) + { + case MT_OVERTIMEFOG: + mo->destscale = 8*mo->scale; + mo->momz = P_RandomRange(1,8)*mo->scale; + break; + case MT_OVERTIMEORB: + //mo->destscale = mo->scale/4; + mo->frame += ((leveltime/4) % 8); + /*if (battleovertime.enabled < 10*TICRATE) + mo->flags2 |= MF2_SHADOW;*/ + mo->angle = R_PointToAngle2(mo->x, mo->y, battleovertime.x, battleovertime.y) + ANGLE_90; + mo->z += P_RandomRange(0,48) * mo->scale; + break; + default: + break; + } + } +} +#undef MAXPLANESPERSECTOR + +void P_RunBattleOvertime(void) +{ + UINT16 i, j; + + if (battleovertime.enabled < 10*TICRATE) + { + battleovertime.enabled++; + if (battleovertime.enabled == TICRATE) + S_StartSound(NULL, sfx_bhurry); + if (battleovertime.enabled == 10*TICRATE) + S_StartSound(NULL, sfx_kc40); + } + else + { + if (battleovertime.radius > battleovertime.minradius) + battleovertime.radius -= mapobjectscale; + else + battleovertime.radius = battleovertime.minradius; + } + + if (leveltime & 1) + { + UINT8 transparency = tr_trans50; + + if (!splitscreen && players[displayplayers[0]].mo) + { + INT32 dist = P_AproxDistance(battleovertime.x-players[displayplayers[0]].mo->x, battleovertime.y-players[displayplayers[0]].mo->y); + transparency = max(0, NUMTRANSMAPS - ((256 + (dist>>FRACBITS)) / 256)); + } + + if (transparency < NUMTRANSMAPS) + { + mobj_t *beam = P_SpawnMobj(battleovertime.x, battleovertime.y, battleovertime.z + (mobjinfo[MT_RANDOMITEM].height/2), MT_OVERTIMEBEAM); + P_SetScale(beam, beam->scale*2); + if (transparency > 0) + beam->frame |= transparency<>FRACBITS / 2));*/ + + for (i = 0; i < 16; i++) + { + j = 0; + while (j < 32) // max attempts + { + fixed_t x = battleovertime.x + ((P_RandomRange(-64,64) * 128)<x = mobj->target->x; mobj->y = mobj->target->y; - if (!splitscreen) + if (!splitscreen && players[displayplayers[0]].mo) { scale = mobj->target->scale + FixedMul(FixedDiv(abs(P_AproxDistance(players[displayplayers[0]].mo->x-mobj->target->x, players[displayplayers[0]].mo->y-mobj->target->y)), RING_DIST), mobj->target->scale); @@ -8534,18 +8744,13 @@ void P_MobjThinker(mobj_t *mobj) return; } + mobj->z = mobj->target->z; + K_MatchGenericExtraFlags(mobj, mobj->target); - { - fixed_t z; - z = mobj->target->z; - if (( mobj->eflags & MFE_VERTICALFLIP )) - z -= mobj->height; - else - z += mobj->target->height; - P_TeleportMove(mobj, mobj->target->x + FINECOSINE(mobj->angle >> ANGLETOFINESHIFT), - mobj->target->y + FINESINE(mobj->angle >> ANGLETOFINESHIFT), - z); - } + + P_TeleportMove(mobj, mobj->target->x + FINECOSINE(mobj->angle >> ANGLETOFINESHIFT), + mobj->target->y + FINESINE(mobj->angle >> ANGLETOFINESHIFT), + mobj->z + mobj->target->height * P_MobjFlip(mobj)); break; case MT_TIREGREASE: if (!mobj->target || P_MobjWasRemoved(mobj->target) || !mobj->target->player @@ -9317,6 +9522,40 @@ void P_MobjThinker(mobj_t *mobj) trail->color = mobj->color; } break; + case MT_RANDOMITEM: + if (G_BattleGametype() && mobj->threshold == 70) + { + mobj->color = (UINT8)(1 + (leveltime % (MAXSKINCOLORS-1))); + mobj->colorized = true; + + if (battleovertime.enabled) + { + angle_t ang = FixedAngle((leveltime % 360) << FRACBITS); + fixed_t z = battleovertime.z; + fixed_t dist; + mobj_t *ghost; + + /*if (z < mobj->subsector->sector->floorheight) + z = mobj->subsector->sector->floorheight;*/ + + if (mobj->extravalue1 < 512) + mobj->extravalue1++; + dist = mobj->extravalue1 * mapobjectscale; + + P_TeleportMove(mobj, battleovertime.x + P_ReturnThrustX(NULL, ang, dist), + battleovertime.y + P_ReturnThrustY(NULL, ang, dist), z); + + ghost = P_SpawnGhostMobj(mobj); + ghost->fuse = 4; + ghost->frame |= FF_FULLBRIGHT; + } + } + else + { + mobj->color = SKINCOLOR_NONE; + mobj->colorized = false; + } + break; //} case MT_TURRET: P_MobjCheckWater(mobj); @@ -9581,7 +9820,7 @@ for (i = ((mobj->flags2 & MF2_STRONGBOX) ? strongboxamt : weakboxamt); i; --i) s P_RemoveMobj(mobj); // make sure they disappear return; case MT_RANDOMITEM: - if (G_BattleGametype()) + if (G_BattleGametype() && (mobj->threshold != 70)) { if (mobj->threshold != 69) break; @@ -9597,8 +9836,11 @@ for (i = ((mobj->flags2 & MF2_STRONGBOX) ? strongboxamt : weakboxamt); i; --i) s else newmobj = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->type); + P_SpawnMobj(newmobj->x, newmobj->y, newmobj->z, MT_EXPLODE); // poof into existance // Transfer flags2 (strongbox, objectflip) newmobj->flags2 = mobj->flags2 & ~MF2_DONTDRAW; + if (mobj->threshold == 70) + newmobj->threshold = 70; } P_RemoveMobj(mobj); // make sure they disappear return; @@ -10958,6 +11200,50 @@ void P_PrecipitationEffects(void) } } +void P_RespawnBattleBoxes(void) +{ + thinker_t *th; + + if (!G_BattleGametype()) + return; + + for (th = thinkercap.next; th != &thinkercap; th = th->next) + { + mobj_t *box; + mobj_t *newmobj; + + if (th->function.acp1 != (actionf_p1)P_MobjThinker) // not a mobj + continue; + + box = (mobj_t *)th; + + if (box->type != MT_RANDOMITEM || box->threshold != 68 || box->fuse) // only popped items + continue; + + // Respawn from mapthing if you have one! + if (box->spawnpoint) + { + P_SpawnMapThing(box->spawnpoint); + newmobj = box->spawnpoint->mobj; // this is set to the new mobj in P_SpawnMapThing + P_SpawnMobj(box->spawnpoint->mobj->x, box->spawnpoint->mobj->y, box->spawnpoint->mobj->z, MT_EXPLODE); // poof into existance + } + else + { + newmobj = P_SpawnMobj(box->x, box->y, box->z, box->type); + P_SpawnMobj(newmobj->x, newmobj->y, newmobj->z, MT_EXPLODE); // poof into existance + } + + // Transfer flags2 (strongbox, objectflip) + newmobj->flags2 = box->flags2; + P_RemoveMobj(box); // make sure they disappear + numgotboxes--; // you've restored a box, remove it from the count + //continue; -- irrelevant? + } + + if (numgotboxes < 0) + numgotboxes = 0; +} + // // P_RespawnSpecials // @@ -10971,45 +11257,7 @@ void P_RespawnSpecials(void) mapthing_t *mthing = NULL; if (G_BattleGametype() && numgotboxes >= (4*nummapboxes/5)) // Battle Mode respawns all boxes in a different way - { - thinker_t *th; - - for (th = thinkercap.next; th != &thinkercap; th = th->next) - { - mobj_t *box; - mobj_t *newmobj; - - if (th->function.acp1 != (actionf_p1)P_MobjThinker) // not a mobj - continue; - - box = (mobj_t *)th; - - if (box->type != MT_RANDOMITEM || box->threshold != 68 || box->fuse) // only popped items - continue; - - // Respawn from mapthing if you have one! - if (box->spawnpoint) - { - P_SpawnMapThing(box->spawnpoint); - newmobj = box->spawnpoint->mobj; // this is set to the new mobj in P_SpawnMapThing - P_SpawnMobj(box->spawnpoint->mobj->x, box->spawnpoint->mobj->y, box->spawnpoint->mobj->z, MT_EXPLODE); // poof into existance - } - else - { - newmobj = P_SpawnMobj(box->x, box->y, box->z, box->type); - P_SpawnMobj(newmobj->x, newmobj->y, newmobj->z, MT_EXPLODE); // poof into existance - } - - // Transfer flags2 (strongbox, objectflip) - newmobj->flags2 = box->flags2; - P_RemoveMobj(box); // make sure they disappear - numgotboxes--; // you've restored a box, remove it from the count - //continue; -- irrelevant? - } - - if (numgotboxes < 0) - numgotboxes = 0; - } + P_RespawnBattleBoxes(); // wait time depends on player count for (p = 0; p < MAXPLAYERS; p++) @@ -11021,8 +11269,16 @@ void P_RespawnSpecials(void) if (pcount == 1) // No respawn when alone return; else if (pcount > 1) + { time = (180 - (pcount * 10))*TICRATE; + // If the map is longer or shorter than 3 laps, then adjust ring respawn to account for this. + // 5 lap courses would have more retreaded ground, while 2 lap courses would have less. + if ((mapheaderinfo[gamemap-1]->numlaps != 3) + && !(mapheaderinfo[gamemap-1]->levelflags & LF_SECTIONRACE)) + time = (time * 3) / max(1, mapheaderinfo[gamemap-1]->numlaps); + } + // only respawn items when cv_itemrespawn is on //if (!cv_itemrespawn.value) // TODO: remove this cvar //return; diff --git a/src/p_saveg.c b/src/p_saveg.c index 6ac402bdc..0a54a5d23 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -34,6 +34,9 @@ #include "p_slopes.h" #endif +// SRB2Kart +#include "k_pwrlv.h" + savedata_t savedata; UINT8 *save_p; @@ -953,8 +956,8 @@ typedef enum MD2_EXTVAL2 = 1<<6, MD2_HNEXT = 1<<7, MD2_HPREV = 1<<8, - MD2_COLORIZED = 1<<9, - MD2_WAYPOINTCAP = 1<<10 + MD2_COLORIZED = 1<<9, + MD2_WAYPOINTCAP = 1<<10 #ifdef ESLOPE , MD2_SLOPE = 1<<11 #endif @@ -3305,11 +3308,22 @@ static void P_NetArchiveMisc(void) for (i = 0; i < 4; i++) WRITESINT8(save_p, battlewanted[i]); + // battleovertime_t + WRITEUINT16(save_p, battleovertime.enabled); + WRITEFIXED(save_p, battleovertime.radius); + WRITEFIXED(save_p, battleovertime.minradius); + WRITEFIXED(save_p, battleovertime.x); + WRITEFIXED(save_p, battleovertime.y); + WRITEFIXED(save_p, battleovertime.z); + WRITEUINT32(save_p, wantedcalcdelay); WRITEUINT32(save_p, indirectitemcooldown); WRITEUINT32(save_p, hyubgone); 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); @@ -3414,11 +3428,22 @@ static inline boolean P_NetUnArchiveMisc(void) for (i = 0; i < 4; i++) battlewanted[i] = READSINT8(save_p); + // battleovertime_t + battleovertime.enabled = READUINT16(save_p); + battleovertime.radius = READFIXED(save_p); + battleovertime.minradius = READFIXED(save_p); + battleovertime.x = READFIXED(save_p); + battleovertime.y = READFIXED(save_p); + battleovertime.z = READFIXED(save_p); + wantedcalcdelay = READUINT32(save_p); indirectitemcooldown = READUINT32(save_p); hyubgone = 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 eb79aaa95..620c22a9e 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -85,6 +85,7 @@ // SRB2Kart #include "k_kart.h" #include "k_waypoint.h" +#include "k_pwrlv.h" // // Map MD5, calculated on level load. @@ -2397,13 +2398,21 @@ static void P_LevelInitStuff(void) if (G_BattleGametype()) gamespeed = 0; else - gamespeed = (UINT8)cv_kartspeed.value; + { + if (cv_kartspeed.value == -1) + gamespeed = ((speedscramble == -1) ? atoi(cv_kartspeed.defaultvalue) : (UINT8)speedscramble); + else + gamespeed = (UINT8)cv_kartspeed.value; + } franticitems = (boolean)cv_kartfrantic.value; comeback = (boolean)cv_kartcomeback.value; } for (i = 0; i < 4; i++) battlewanted[i] = -1; + + memset(&battleovertime, 0, sizeof(struct battleovertime)); + speedscramble = encorescramble = -1; } // @@ -2781,7 +2790,7 @@ boolean P_SetupLevel(boolean skipprecip) // use gamemap to get map number. // 99% of the things already did, so. // Map header should always be in place at this point - INT32 i, loadprecip = 1, ranspecialwipe = 0; + INT32 i, loadprecip = 1; INT32 loademblems = 1; INT32 fromnetsave = 0; boolean loadedbm = false; @@ -2860,36 +2869,50 @@ boolean P_SetupLevel(boolean skipprecip) S_StartSound(NULL, sfx_ruby1); + // Fade to an inverted screen, with a circle fade... F_WipeStartScreen(); - V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 209); + V_EncoreInvertScreen(); F_WipeEndScreen(); - F_RunWipe(wipedefs[wipe_speclevel_towhite], false); + F_RunWipe(wipedefs[wipe_encore_toinvert], false, NULL, false, false); + + // Hold on invert for extra effect. + // (This define might be useful for other areas of code? Not sure) +#define WAIT(timetowait) \ + locstarttime = nowtime = lastwipetic; \ + endtime = locstarttime + timetowait; \ + while (nowtime < endtime) \ + { \ + while (!((nowtime = I_GetTime()) - lastwipetic)) \ + I_Sleep(); \ + lastwipetic = nowtime; \ + if (moviemode) \ + M_SaveFrame(); \ + NetKeepAlive(); \ + } \ + + WAIT((3*TICRATE)/2); + S_StartSound(NULL, sfx_ruby2); + + // Then fade to a white screen F_WipeStartScreen(); + V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 0); - F_WipeEndScreen(); - F_RunWipe(wipedefs[wipe_level_final], false); - locstarttime = nowtime = lastwipetic; - endtime = locstarttime + (3*TICRATE)/2; + F_RunWipe(wipedefs[wipe_encore_towhite], false, "FADEMAP1", false, true); // wiggle the screen during this! - // Hold on white for extra effect. - while (nowtime < endtime) - { - // wait loop - while (!((nowtime = I_GetTime()) - lastwipetic)) - I_Sleep(); - lastwipetic = nowtime; - if (moviemode) // make sure we save frames for the white hold too - M_SaveFrame(); + // THEN fade to a black screen. + F_WipeStartScreen(); - // Keep the network alive - NetKeepAlive(); - } + V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); + F_WipeEndScreen(); - ranspecialwipe = 1; + F_RunWipe(wipedefs[wipe_level_toblack], false, "FADEMAP0", false, false); + + // Wait a bit longer. + WAIT((3*TICRATE)/4); } // Make sure all sounds are stopped before Z_FreeTags. @@ -2900,17 +2923,18 @@ boolean P_SetupLevel(boolean skipprecip) // We should be fine starting it here. S_Start(); - levelfadecol = (encoremode && !ranspecialwipe ? 209 : 0); + levelfadecol = (encoremode ? 0 : 31); // Let's fade to white here // But only if we didn't do the encore startup wipe - if (rendermode != render_none && !ranspecialwipe && !demo.rewinding) + if (rendermode != render_none && !demo.rewinding) { F_WipeStartScreen(); - V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, levelfadecol); + V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, levelfadecol); F_WipeEndScreen(); - F_RunWipe(wipedefs[(encoremode ? wipe_level_final : wipe_level_toblack)], false); + + F_RunWipe(wipedefs[wipe_level_toblack], false, ((levelfadecol == 0) ? "FADEMAP1" : "FADEMAP0"), false, false); } // Reset the palette now all fades have been done @@ -3285,7 +3309,10 @@ boolean P_SetupLevel(boolean skipprecip) indirectitemcooldown = 0; hyubgone = 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 6150aa391..e3dea0f17 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -3788,10 +3788,9 @@ void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *rovers break; case 9: // Ring Drainer (Floor Touch) case 10: // Ring Drainer (No Floor Touch) - if (leveltime % (TICRATE/2) == 0 && player->mo->health > 1) + if (leveltime % (TICRATE/2) == 0 && player->kartstuff[k_rings] > 0) { - player->mo->health--; - player->health--; + player->kartstuff[k_rings]--; S_StartSound(player->mo, sfx_itemup); } break; diff --git a/src/p_tick.c b/src/p_tick.c index 0a340d80c..2e3b17b69 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -618,33 +618,10 @@ void P_Ticker(boolean run) } if (demo.playback) { - -#ifdef DEMO_COMPAT_100 - if (demo.version == 0x0001) - { - G_ReadDemoTiccmd(&players[consoleplayer].cmd, 0); - } - else - { -#endif - G_ReadDemoExtraData(); - for (i = 0; i < MAXPLAYERS; i++) - if (playeringame[i]) - { - //@TODO all this throwdir stuff shouldn't be here! But it's added to maintain 1.0.4 compat for now... - // Remove for 1.1! - if (players[i].cmd.buttons & BT_FORWARD) - players[i].kartstuff[k_throwdir] = 1; - else if (players[i].cmd.buttons & BT_BACKWARD) - players[i].kartstuff[k_throwdir] = -1; - else - players[i].kartstuff[k_throwdir] = 0; - - G_ReadDemoTiccmd(&players[i].cmd, i); - } -#ifdef DEMO_COMPAT_100 - } -#endif + G_ReadDemoExtraData(); + for (i = 0; i < MAXPLAYERS; i++) + if (playeringame[i]) + G_ReadDemoTiccmd(&players[i].cmd, i); } for (i = 0; i < MAXPLAYERS; i++) @@ -665,6 +642,8 @@ void P_Ticker(boolean run) if (run) { P_RunThinkers(); + if (G_BattleGametype() && battleovertime.enabled) + P_RunBattleOvertime(); // Run any "after all the other thinkers" stuff for (i = 0; i < MAXPLAYERS; i++) @@ -760,11 +739,6 @@ void P_Ticker(boolean run) } else if (demo.playback) // Use Ghost data for consistency checks. { -#ifdef DEMO_COMPAT_100 - if (demo.version == 0x0001) - G_ConsGhostTic(0); - else -#endif G_ConsAllGhostTics(); } @@ -827,6 +801,8 @@ void P_PreTicker(INT32 frames) } P_RunThinkers(); + if (G_BattleGametype() && battleovertime.enabled) + P_RunBattleOvertime(); // Run any "after all the other thinkers" stuff for (i = 0; i < MAXPLAYERS; i++) diff --git a/src/p_user.c b/src/p_user.c index cffba20bb..16aeeb054 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -8055,8 +8055,6 @@ void P_PlayerThink(player_t *player) cmd = &player->cmd; - //@TODO This fixes a one-tic latency on direction handling, AND makes behavior consistent while paused, but is not BC with 1.0.4. Do this for 1.1! -#if 0 // SRB2kart // Save the dir the player is holding // to allow items to be thrown forward or backward. @@ -8066,7 +8064,6 @@ void P_PlayerThink(player_t *player) player->kartstuff[k_throwdir] = -1; else player->kartstuff[k_throwdir] = 0; -#endif // Add some extra randomization. if (cmd->forwardmove) diff --git a/src/r_data.c b/src/r_data.c index d710359d1..922e907e2 100644 --- a/src/r_data.c +++ b/src/r_data.c @@ -267,7 +267,7 @@ static UINT8 *R_GenerateTexture(size_t texnum) texturememory += blocksize; block = Z_Malloc(blocksize+1, PU_STATIC, &texturecache[texnum]); - memset(block, 0xF7, blocksize+1); // Transparency hack + memset(block, 0xFF, blocksize+1); // TRANSPARENTPIXEL // columns lookup table colofs = (UINT32 *)(void *)block; @@ -1177,7 +1177,6 @@ void R_ClearColormaps(void) // static double deltas[256][3], map[256][3]; -static UINT8 NearestColor(UINT8 r, UINT8 g, UINT8 b); static int RoundUp(double number); #ifdef HASINVERT @@ -1403,7 +1402,7 @@ INT32 R_CreateColormap(char *p1, char *p2, char *p3) // Thanks to quake2 source! // utils3/qdata/images.c -static UINT8 NearestColor(UINT8 r, UINT8 g, UINT8 b) +UINT8 NearestColor(UINT8 r, UINT8 g, UINT8 b) { int dr, dg, db; int distortion, bestdistortion = 256 * 256 * 4, bestcolor = 0, i; diff --git a/src/r_data.h b/src/r_data.h index 640b30309..cd61bbaac 100644 --- a/src/r_data.h +++ b/src/r_data.h @@ -93,6 +93,7 @@ void R_ReInitColormaps(UINT16 num, lumpnum_t newencoremap); void R_ClearColormaps(void); INT32 R_ColormapNumForName(char *name); INT32 R_CreateColormap(char *p1, char *p2, char *p3); +UINT8 NearestColor(UINT8 r, UINT8 g, UINT8 b); #ifdef HASINVERT void R_MakeInvertmap(void); #endif diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c index d24bd5ade..f75a42117 100644 --- a/src/sdl/i_system.c +++ b/src/sdl/i_system.c @@ -165,6 +165,7 @@ static char returnWadPath[256]; #include "../d_net.h" #include "../g_game.h" #include "../filesrch.h" +#include "../k_pwrlv.h" #include "endtxt.h" #include "sdlmain.h" @@ -3056,6 +3057,11 @@ void I_Quit(void) #ifndef NONET D_SaveBan(); // save the ban list #endif + + // Make sure you lose points for ALT-F4 + if (Playing()) + K_PlayerForfeit(consoleplayer, true); + G_SaveGameData(false); // Tails 12-08-2002 //added:16-02-98: when recording a demo, should exit using 'q' key, // but sometimes we forget and use 'F10'.. so save here too. @@ -3177,11 +3183,14 @@ void I_Error(const char *error, ...) #endif G_SaveGameData(false); // Tails 12-08-2002 + /* Prevent segmentation fault if testers go to Record Attack... */ +#ifndef TESTERS // Shutdown. Here might be other errors. if (demo.recording) G_CheckDemoStatus(); if (metalrecording) G_StopMetalRecording(); +#endif D_QuitNetGame(); I_ShutdownMusic(); diff --git a/src/sdl12/i_system.c b/src/sdl12/i_system.c index 62256f3a5..14d183748 100644 --- a/src/sdl12/i_system.c +++ b/src/sdl12/i_system.c @@ -233,6 +233,7 @@ static char returnWadPath[256]; #include "../d_net.h" #include "../g_game.h" #include "../filesrch.h" +#include "../k_pwrlv.h" #include "endtxt.h" #include "sdlmain.h" @@ -2977,6 +2978,11 @@ void I_Quit(void) #ifndef NONET D_SaveBan(); // save the ban list #endif + + // Make sure you lose points for ALT-F4 + if (Playing()) + K_PlayerForfeit(consoleplayer, true); + G_SaveGameData(); // Tails 12-08-2002 //added:16-02-98: when recording a demo, should exit using 'q' key, // but sometimes we forget and use 'F10'.. so save here too. diff --git a/src/sounds.c b/src/sounds.c index 61fddb76f..ef150d7a5 100644 --- a/src/sounds.c +++ b/src/sounds.c @@ -815,6 +815,7 @@ sfxinfo_t S_sfx[NUMSFX] = {"chain", false, 255, 8, -1, NULL, 0, -1, -1, LUMPERROR}, // Mementos Reaper {"mkuma", false, 96, 8, -1, NULL, 0, -1, -1, LUMPERROR}, // Trigger Happy Havoc Monokuma {"toada", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR}, // Arid Sands Toad scream + {"bhurry", false, 255, 0, -1, NULL, 0, -1, -1, LUMPERROR}, // v1.0.2 Battle overtime {"bsnipe", false, 96, 8, -1, NULL, 0, -1, -1, LUMPERROR}, // Banana sniping {"itfree", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR}, // :shitsfree: {"dbgsal", false, 255, 8, -1, NULL, 0, -1, -1, LUMPERROR}, // Debug notification diff --git a/src/sounds.h b/src/sounds.h index bb46ea9d8..8b9ca609e 100644 --- a/src/sounds.h +++ b/src/sounds.h @@ -890,6 +890,7 @@ typedef enum sfx_chain, sfx_mkuma, sfx_toada, + sfx_bhurry, sfx_bsnipe, sfx_itfree, sfx_dbgsal, diff --git a/src/st_stuff.c b/src/st_stuff.c index c55724359..5ab079182 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -2134,12 +2134,7 @@ void ST_Drawer(void) ST_MayonakaStatic(); } - // Draw a white fade on level opening - if (timeinmap < 15) - { - if (timeinmap <= 5) - V_DrawFill(0,0,BASEVIDWIDTH,BASEVIDHEIGHT,0); // Pure white on first few frames, to hide SRB2's awful level load artifacts - else - V_DrawFadeScreen(0, 15-timeinmap); // Then gradually fade out from there - } + // Draw a fade on level opening + if (timeinmap < 16) + V_DrawCustomFadeScreen(((levelfadecol == 0) ? "FADEMAP1" : "FADEMAP0"), 32-(timeinmap*2)); // Then gradually fade out from there } diff --git a/src/v_video.c b/src/v_video.c index 1b1d03baa..297eae9c4 100644 --- a/src/v_video.c +++ b/src/v_video.c @@ -1287,28 +1287,75 @@ void V_DrawVhsEffect(boolean rewind) void V_DrawFadeScreen(UINT16 color, UINT8 strength) { #ifdef HWRENDER - if (rendermode != render_soft && rendermode != render_none) - { - HWR_FadeScreenMenuBack(color, strength); - return; - } + if (rendermode != render_soft && rendermode != render_none) + { + HWR_FadeScreenMenuBack(color, strength); + return; + } #endif - { - const UINT8 *fadetable = + { + const UINT8 *fadetable = (color > 0xFFF0) // Grab a specific colormap palette? ? R_GetTranslationColormap(color | 0xFFFF0000, strength, GTC_CACHE) : ((color & 0xFF00) // Color is not palette index? ? ((UINT8 *)colormaps + strength*256) // Do COLORMAP fade. : ((UINT8 *)transtables + ((9-strength)< 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 || (score - increase) > data.match.val[data.match.numplayers])) return; - data.match.val[data.match.numplayers] = (players[i].score - increase); + data.match.val[data.match.numplayers] = (score - increase); data.match.num[data.match.numplayers] = i; } @@ -205,7 +209,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) @@ -257,14 +261,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++; } @@ -276,8 +283,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]) @@ -299,9 +304,10 @@ 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]; } @@ -431,7 +437,7 @@ void Y_IntermissionDrawer(void) const char *timeheader; if (data.match.rankingsmode) - timeheader = "RANK"; + timeheader = "PWR.LV"; else timeheader = (intertype == int_race ? "TIME" : "SCORE"); @@ -493,21 +499,23 @@ void Y_IntermissionDrawer(void) if (data.match.rankingsmode) { - if (data.match.increase[data.match.num[i]] != UINT8_MAX) + if (!clientpowerlevels[data.match.num[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) + { + snprintf(strtime, sizeof strtime, "(%d)", data.match.increase[data.match.num[i]]); - if (data.match.numplayers > NUMFORNEWCOLUMN) - V_DrawRightAlignedThinString(x+135+gutter, y-1, V_6WIDTHSPACE, strtime); - else - V_DrawRightAlignedString(x+120+gutter, y, 0, strtime); + if (data.match.numplayers > NUMFORNEWCOLUMN) + V_DrawRightAlignedThinString(x+133+gutter, y-1, V_6WIDTHSPACE, strtime); + else + 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]); - if (data.match.numplayers > NUMFORNEWCOLUMN) V_DrawRightAlignedThinString(x+152+gutter, y-1, V_6WIDTHSPACE, strtime); else @@ -591,9 +599,14 @@ dotimer: break; } - // Make it obvious that scrambling is happening next round. - if (cv_scrambleonchange.value && cv_teamscramble.value && (intertic/TICRATE % 2 == 0)) - V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, hilicol, M_GetText("Teams will be scrambled next round!")); + //if ((intertic/TICRATE) & 1) // Make it obvious that scrambling is happening next round. (OR NOT, I GUESS) + //{ + /*if (cv_scrambleonchange.value && cv_teamscramble.value) + V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, hilicol, M_GetText("Teams will be scrambled next round!"));*/ + if (speedscramble != -1 && speedscramble != gamespeed) + V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-24, hilicol|V_ALLOWLOWERCASE|V_SNAPTOBOTTOM, + va(M_GetText("Next race will be %s Speed!"), kartspeed_cons_t[speedscramble].strvalue)); + //} } // @@ -676,13 +689,46 @@ 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++; data.match.jitter[data.match.num[q]] = 1; - if (--data.match.increase[data.match.num[q]]) - kaching = false; + + if (powertype != -1) + { + // Power Levels + if (abs(data.match.increase[data.match.num[q]]) < 10) + { + // Not a lot of point increase left, just set to 0 instantly + data.match.increase[data.match.num[q]] = 0; + } + else + { + SINT8 remove = 0; // default (should not happen) + + if (data.match.increase[data.match.num[q]] < 0) + remove = -10; + else if (data.match.increase[data.match.num[q]] > 0) + remove = 10; + + // Remove 10 points at a time + data.match.increase[data.match.num[q]] -= remove; + + // Still not zero, no kaching yet + if (data.match.increase[data.match.num[q]] != 0) + kaching = false; + } + } + else + { + // Basic bitch points + if (data.match.increase[data.match.num[q]]) + { + if (--data.match.increase[data.match.num[q]]) + kaching = false; + } + } } if (r) @@ -782,6 +828,153 @@ static void Y_UpdateRecordReplays(void) CV_AddValue(&cv_nextmap, -1); } +static void K_UpdatePowerLevels(void) +{ + INT32 i, j; + INT32 numplayersingame = 0, numgriefers = 0; + INT16 increment[MAXPLAYERS]; + + // 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. + + for (i = 0; i < MAXPLAYERS; i++) + { + increment[i] = 0; + + if (nospectategrief[i] != -1) + numgriefers++; + + if (!playeringame[i] || players[i].spectator) + continue; + + numplayersingame++; + } + + for (i = 0; i < numplayersingame; i++) + { + UINT16 yourpower = PWRLVRECORD_DEF; + UINT16 theirpower = PWRLVRECORD_DEF; + INT16 diff = 0; // Loser PWR.LV - Winner PWR.LV + INT16 inc = 0; // Total pt increment + UINT8 ipnum = data.match.num[i]; + UINT8 jpnum; + + CONS_Debug(DBG_GAMELOGIC, "Power Level Gain for player %d:\n", ipnum); + + if (clientpowerlevels[ipnum][powertype] == 0) // splitscreen guests don't record power level changes + continue; + yourpower = clientpowerlevels[ipnum][powertype]; + + CONS_Debug(DBG_GAMELOGIC, "Player %d's PWR.LV: %d\n", ipnum, yourpower); + + for (j = 0; j < numplayersingame; j++) + { + boolean won = false; + + jpnum = data.match.num[j]; + + if (i == j || ipnum == jpnum) // Same person + continue; + + CONS_Debug(DBG_GAMELOGIC, "Player %d VS Player %d:\n", ipnum, jpnum); + + if (data.match.val[i] == data.match.val[j]) // Tie -- neither get any points for this match up. + { + CONS_Debug(DBG_GAMELOGIC, "TIE, no change.\n"); + continue; + } + + theirpower = PWRLVRECORD_DEF; + if (clientpowerlevels[jpnum][powertype] != 0) // No power level acts as 5000 (used for splitscreen guests) + theirpower = clientpowerlevels[jpnum][powertype]; + CONS_Debug(DBG_GAMELOGIC, "Player %d's PWR.LV: %d\n", jpnum, theirpower); + + if (G_RaceGametype()) + { + if (data.match.val[i] < data.match.val[j]) + won = true; + } + else + { + if (data.match.val[i] > data.match.val[j]) + won = true; + } + + if (won) // This player won! + { + diff = theirpower - yourpower; + inc += K_CalculatePowerLevelInc(diff); + CONS_Debug(DBG_GAMELOGIC, "WON! Diff is %d, total increment is %d\n", diff, inc); + } + else // This player lost... + { + diff = yourpower - theirpower; + inc -= K_CalculatePowerLevelInc(diff); + CONS_Debug(DBG_GAMELOGIC, "LOST... Diff is %d, total increment is %d\n", diff, inc); + } + } + + if (numgriefers != 0) // Automatic win against quitters. + { + for (jpnum = 0; jpnum < MAXPLAYERS; jpnum++) + { + if (nospectategrief[jpnum] == -1) // Empty slot + continue; + + if (ipnum == jpnum) // Same person + continue; + + CONS_Debug(DBG_GAMELOGIC, "Player %d VS Player %d (griefer):\n", ipnum, jpnum); + + theirpower = PWRLVRECORD_DEF; + if (nospectategrief[jpnum] != 0) // No power level acts as 5000 (used for splitscreen guests) + theirpower = nospectategrief[jpnum]; + CONS_Debug(DBG_GAMELOGIC, "Player %d's PWR.LV: %d\n", jpnum, theirpower); + + diff = theirpower - yourpower; + inc += K_CalculatePowerLevelInc(diff); + CONS_Debug(DBG_GAMELOGIC, "AUTO-WON! Diff is %d, total increment is %d\n", diff, inc); + } + } + + if (inc == 0) + { + data.match.increase[ipnum] = INT16_MIN; + CONS_Debug(DBG_GAMELOGIC, "Total Result: No increment, no change.\n"); + continue; + } + + if (yourpower + inc > PWRLVRECORD_MAX) + inc -= ((yourpower + inc) - PWRLVRECORD_MAX); + if (yourpower + inc < PWRLVRECORD_MIN) + inc -= ((yourpower + inc) - PWRLVRECORD_MIN); + + CONS_Debug(DBG_GAMELOGIC, "Total Result: Increment of %d.\n", inc); + increment[ipnum] = inc; + } + + CONS_Debug(DBG_GAMELOGIC, "Setting final power levels...\n"); + for (i = 0; i < MAXPLAYERS; i++) + { + if (increment[i] == 0) + continue; + + data.match.increase[i] = increment[i]; + clientpowerlevels[i][powertype] += data.match.increase[i]; + + if (i == consoleplayer) + { + CONS_Debug(DBG_GAMELOGIC, "Player %d is you! Saving...\n", i); + vspowerlevel[powertype] = clientpowerlevels[i][powertype]; + if (M_UpdateUnlockablesAndExtraEmblems(true)) + S_StartSound(NULL, sfx_ncitem); + G_SaveGameData(true); + } + } +} + // // Y_StartIntermission // @@ -796,6 +989,19 @@ void Y_StartIntermission(void) I_Error("endtic is dirty"); #endif + // set player Power Level type + powertype = PWRLV_DISABLED; + + if (netgame && cv_kartusepwrlv.value) + { + if (G_RaceGametype()) + powertype = PWRLV_RACE; + else if (G_BattleGametype()) + powertype = PWRLV_BATTLE; + } + + K_SetPowerLevelScrambles(powertype); + if (!multiplayer) { timer = 0; @@ -872,6 +1078,9 @@ void Y_StartIntermission(void) break; } + if (powertype != -1) + K_UpdatePowerLevels(); + //if (intertype == int_race || intertype == int_match) { //bgtile = W_CachePatchName("SRB2BACK", PU_STATIC);