diff --git a/src/command.c b/src/command.c index f8bce511e..306508a36 100644 --- a/src/command.c +++ b/src/command.c @@ -1398,7 +1398,7 @@ static void Got_NetVar(UINT8 **p, INT32 playernum) Setvalue(cvar, svalue, stealth); } -void CV_SaveNetVars(UINT8 **p) +void CV_SaveNetVars(UINT8 **p, boolean isdemorecording) { consvar_t *cvar; UINT8 *count_p = *p; @@ -1408,10 +1408,32 @@ void CV_SaveNetVars(UINT8 **p) // the client will reset all netvars to default before loading WRITEUINT16(*p, 0x0000); for (cvar = consvar_vars; cvar; cvar = cvar->next) - if ((cvar->flags & CV_NETVAR) && !CV_IsSetToDefault(cvar)) + if (((cvar->flags & CV_NETVAR) && !CV_IsSetToDefault(cvar)) || (isdemorecording && cvar->netid == cv_numlaps.netid)) { WRITEUINT16(*p, cvar->netid); - WRITESTRING(*p, cvar->string); + + // UGLY HACK: Save proper lap count in net replays + if (isdemorecording && cvar->netid == cv_numlaps.netid) + { + if (cv_basenumlaps.value && + (!(mapheaderinfo[gamemap - 1]->levelflags & LF_SECTIONRACE) + || (mapheaderinfo[gamemap - 1]->numlaps > cv_basenumlaps.value)) + ) + { + WRITESTRING(*p, cv_basenumlaps.string); + } + else + { + char buf[9]; + sprintf(buf, "%d", mapheaderinfo[gamemap - 1]->numlaps); + WRITESTRING(*p, buf); + } + } + else + { + WRITESTRING(*p, cvar->string); + } + WRITEUINT8(*p, false); ++count; } diff --git a/src/command.h b/src/command.h index f9d177e2e..55c8700f1 100644 --- a/src/command.h +++ b/src/command.h @@ -161,7 +161,7 @@ void CV_AddValue(consvar_t *var, INT32 increment); void CV_SaveVariables(FILE *f); // load/save gamesate (load and save option and for network join in game) -void CV_SaveNetVars(UINT8 **p); +void CV_SaveNetVars(UINT8 **p, boolean isdemorecording); void CV_LoadNetVars(UINT8 **p); // reset cheat netvars after cheats is deactivated diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 932987902..d66d5cb1e 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -138,6 +138,7 @@ static UINT8 localtextcmd3[MAXTEXTCMD]; // splitscreen == 2 static UINT8 localtextcmd4[MAXTEXTCMD]; // splitscreen == 3 static tic_t neededtic; SINT8 servernode = 0; // the number of the server node +char connectedservername[MAXSERVERNAME]; /// \brief do we accept new players? /// \todo WORK! boolean acceptnewnode = true; @@ -1512,7 +1513,7 @@ static boolean SV_SendServerConfig(INT32 node) op = p = netbuffer->u.servercfg.varlengthinputs; CV_SavePlayerNames(&p); - CV_SaveNetVars(&p); + CV_SaveNetVars(&p, false); { const size_t len = sizeof (serverconfig_pak) + (size_t)(p - op); @@ -1691,8 +1692,8 @@ static void CL_LoadReceivedSavegame(void) } paused = false; - demoplayback = false; - titledemo = false; + demo.playback = false; + demo.title = false; automapactive = false; // load a base level @@ -2270,6 +2271,9 @@ static void CL_ConnectToServer(boolean viams) { INT32 j; const char *gametypestr = NULL; + + strncpy(connectedservername, serverlist[i].info.servername, MAXSERVERNAME); + CONS_Printf(M_GetText("Connecting to: %s\n"), serverlist[i].info.servername); for (j = 0; gametype_cons_t[j].strvalue; j++) { @@ -2315,7 +2319,7 @@ static void CL_ConnectToServer(boolean viams) #endif DEBFILE(va("Synchronisation Finished\n")); - displayplayer = consoleplayer; + displayplayers[0] = consoleplayer; } #ifndef NONET @@ -2487,7 +2491,7 @@ static void Command_connect(void) return; } - if (Playing() || titledemo) + if (Playing() || demo.title) { CONS_Printf(M_GetText("You cannot connect while in a game. End this game first.\n")); return; @@ -2579,14 +2583,16 @@ void CL_ClearPlayer(INT32 playernum) // // Removes a player from the current game // -static void CL_RemovePlayer(INT32 playernum, INT32 reason) +void CL_RemovePlayer(INT32 playernum, INT32 reason) { // Sanity check: exceptional cases (i.e. c-fails) can cause multiple // kick commands to be issued for the same player. if (!playeringame[playernum]) return; - if (server && !demoplayback) + demo_extradata[playernum] |= DXD_PLAYSTATE; + + if (server && !demo.playback) { INT32 node = playernode[playernum]; //playerpernode[node] = 0; // It'd be better to remove them all at once, but ghosting happened, so continue to let CL_RemovePlayer do it one-by-one @@ -2661,8 +2667,8 @@ static void CL_RemovePlayer(INT32 playernum, INT32 reason) RemoveAdminPlayer(playernum); // don't stay admin after you're gone } - if (playernum == displayplayer) - displayplayer = consoleplayer; // don't look through someone's view who isn't there + if (playernum == displayplayers[0] && !demo.playback) + displayplayers[0] = consoleplayer; // don't look through someone's view who isn't there #ifdef HAVE_BLUA LUA_InvalidatePlayer(&players[playernum]); @@ -2682,7 +2688,7 @@ void CL_Reset(void) G_StopMetalRecording(); if (metalplayback) G_StopMetalDemo(); - if (demorecording) + if (demo.recording) G_CheckDemoStatus(); // reset client/server code @@ -3382,6 +3388,7 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum) { INT16 node, newplayernum; UINT8 splitscreenplayer = 0; + UINT8 i; if (playernum != serverplayer && !IsPlayerAdmin(playernum)) { @@ -3415,34 +3422,19 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum) if (node == mynode) { playernode[newplayernum] = 0; // for information only + if (splitscreenplayer) { - if (splitscreenplayer == 1) - { - secondarydisplayplayer = newplayernum; - DEBFILE("spawning my brother\n"); - if (botingame) - players[newplayernum].bot = 1; - // Same goes for player 2 when relevant - } - else if (splitscreenplayer == 2) - { - thirddisplayplayer = newplayernum; - DEBFILE("spawning my sister\n"); - } - else if (splitscreenplayer == 3) - { - fourthdisplayplayer = newplayernum; - DEBFILE("spawning my trusty pet dog\n"); - } + displayplayers[splitscreenplayer] = newplayernum; + DEBFILE(va("spawning one of my sister number %d\n", splitscreenplayer)); + if (splitscreenplayer == 1 && botingame) + players[newplayernum].bot = 1; } else { consoleplayer = newplayernum; - displayplayer = newplayernum; - secondarydisplayplayer = newplayernum; - thirddisplayplayer = newplayernum; - fourthdisplayplayer = newplayernum; + for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) + displayplayers[i] = newplayernum; DEBFILE("spawning me\n"); } D_SendPlayerConfig(); @@ -3591,7 +3583,7 @@ boolean Playing(void) boolean SV_SpawnServer(void) { - if (demoplayback) + if (demo.playback) G_StopDemo(); // reset engine parameter if (metalplayback) G_StopMetalDemo(); @@ -5177,9 +5169,9 @@ void TryRunTics(tic_t realtics) NetUpdate(); - if (demoplayback) + if (demo.playback) { - neededtic = gametic + (realtics * cv_playbackspeed.value); + neededtic = gametic + realtics * (gamestate == GS_LEVEL ? cv_playbackspeed.value : 1); // start a game after a demo maketic += realtics; firstticstosend = maketic; @@ -5432,7 +5424,7 @@ FILESTAMP } else { - if (!demoplayback) + if (!demo.playback) { INT32 counts; diff --git a/src/d_clisrv.h b/src/d_clisrv.h index 087737aa3..44a3f17dd 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -512,6 +512,7 @@ extern boolean dedicated; // For dedicated server extern UINT16 software_MAXPACKETLENGTH; extern boolean acceptnewnode; extern SINT8 servernode; +extern char connectedservername[MAXSERVERNAME]; void Command_Ping_f(void); extern tic_t connectiontimeout; @@ -551,6 +552,7 @@ void CL_AddSplitscreenPlayer(void); void CL_RemoveSplitscreenPlayer(UINT8 p); void CL_Reset(void); void CL_ClearPlayer(INT32 playernum); +void CL_RemovePlayer(INT32 playernum, INT32 reason); void CL_UpdateServerList(boolean internetsearch, INT32 room); boolean CL_Responder(event_t *ev); // Is there a game running diff --git a/src/d_main.c b/src/d_main.c index 811a7249d..f6eefc9bc 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -118,14 +118,8 @@ boolean devparm = false; // started game with -devparm boolean singletics = false; // timedemo boolean lastdraw = false; -postimg_t postimgtype = postimg_none; -INT32 postimgparam; -postimg_t postimgtype2 = postimg_none; -INT32 postimgparam2; -postimg_t postimgtype3 = postimg_none; -INT32 postimgparam3; -postimg_t postimgtype4 = postimg_none; -INT32 postimgparam4; +postimg_t postimgtype[MAXSPLITSCREENPLAYERS]; +INT32 postimgparam[MAXSPLITSCREENPLAYERS]; // These variables are only true if // whether the respective sound system is disabled @@ -248,6 +242,12 @@ void D_ProcessEvents(void) continue; } + if (demo.savemode == DSM_TITLEENTRY) + { + if (G_DemoTitleResponder(ev)) + continue; + } + // Menu input if (M_Responder(ev)) continue; // menu ate the event @@ -274,6 +274,7 @@ static void D_Display(void) boolean forcerefresh = false; static boolean wipe = false; INT32 wipedefindex = 0; + UINT8 i; if (dedicated) return; @@ -422,109 +423,77 @@ static void D_Display(void) // draw the view directly if (cv_renderview.value && !automapactive) { - if (players[displayplayer].mo || players[displayplayer].playerstate == PST_DEAD) + for (i = 0; i <= splitscreen; i++) { - viewwindowy = 0; - viewwindowx = 0; - - topleft = screens[0] + viewwindowy*vid.width + viewwindowx; - objectsdrawn = 0; -#ifdef HWRENDER - if (rendermode != render_soft) - HWR_RenderPlayerView(0, &players[displayplayer]); - else -#endif - if (rendermode != render_none) - R_RenderPlayerView(&players[displayplayer]); - } - - // render the second screen - if (splitscreen && players[secondarydisplayplayer].mo) - { -#ifdef HWRENDER - if (rendermode != render_soft) - HWR_RenderPlayerView(1, &players[secondarydisplayplayer]); - else -#endif - if (rendermode != render_none) + if (players[displayplayers[i]].mo || players[displayplayers[i]].playerstate == PST_DEAD) { - if (splitscreen > 1) + if (i == 0) // Initialize for P1 { - viewwindowx = viewwidth; viewwindowy = 0; - } - else - { viewwindowx = 0; - viewwindowy = viewheight; + + topleft = screens[0] + viewwindowy*vid.width + viewwindowx; + objectsdrawn = 0; } - M_Memcpy(ylookup, ylookup2, viewheight*sizeof (ylookup[0])); + viewssnum = i; - topleft = screens[0] + viewwindowy*vid.width + viewwindowx; - - R_RenderPlayerView(&players[secondarydisplayplayer]); - - viewwindowy = 0; - M_Memcpy(ylookup, ylookup1, viewheight*sizeof (ylookup[0])); - } - } - - // render the third screen - if (splitscreen > 1 && players[thirddisplayplayer].mo) - { #ifdef HWRENDER - if (rendermode != render_soft) - HWR_RenderPlayerView(2, &players[thirddisplayplayer]); - else + if (rendermode != render_soft) + HWR_RenderPlayerView(i, &players[displayplayers[i]]); + else #endif - if (rendermode != render_none) - { - viewwindowx = 0; - viewwindowy = viewheight; - M_Memcpy(ylookup, ylookup3, viewheight*sizeof (ylookup[0])); + if (rendermode != render_none) + { + if (i > 0) // Splitscreen-specific + { + switch (i) + { + case 1: + if (splitscreen > 1) + { + viewwindowx = viewwidth; + viewwindowy = 0; + } + else + { + viewwindowx = 0; + viewwindowy = viewheight; + } + M_Memcpy(ylookup, ylookup2, viewheight*sizeof (ylookup[0])); + break; + case 2: + viewwindowx = 0; + viewwindowy = viewheight; + M_Memcpy(ylookup, ylookup3, viewheight*sizeof (ylookup[0])); + break; + case 3: + viewwindowx = viewwidth; + viewwindowy = viewheight; + M_Memcpy(ylookup, ylookup4, viewheight*sizeof (ylookup[0])); + default: + break; + } - topleft = screens[0] + viewwindowy*vid.width + viewwindowx; + + topleft = screens[0] + viewwindowy*vid.width + viewwindowx; + } - R_RenderPlayerView(&players[thirddisplayplayer]); + R_RenderPlayerView(&players[displayplayers[i]]); - viewwindowy = 0; - M_Memcpy(ylookup, ylookup1, viewheight*sizeof (ylookup[0])); - } - } - - if (splitscreen > 2 && players[fourthdisplayplayer].mo) // render the fourth screen - { -#ifdef HWRENDER - if (rendermode != render_soft) - HWR_RenderPlayerView(3, &players[fourthdisplayplayer]); - else -#endif - if (rendermode != render_none) - { - viewwindowx = viewwidth; - viewwindowy = viewheight; - M_Memcpy(ylookup, ylookup4, viewheight*sizeof (ylookup[0])); - - topleft = screens[0] + viewwindowy*vid.width + viewwindowx; - - R_RenderPlayerView(&players[fourthdisplayplayer]); - - viewwindowy = 0; - M_Memcpy(ylookup, ylookup1, viewheight*sizeof (ylookup[0])); + if (i > 0) + M_Memcpy(ylookup, ylookup1, viewheight*sizeof (ylookup[0])); + } } } if (rendermode == render_soft) { - if (postimgtype) - V_DoPostProcessor(0, postimgtype, postimgparam); - if (postimgtype2) - V_DoPostProcessor(1, postimgtype2, postimgparam2); - if (postimgtype3) - V_DoPostProcessor(2, postimgtype3, postimgparam3); - if (postimgtype4) - V_DoPostProcessor(3, postimgtype4, postimgparam4); + for (i = 0; i <= splitscreen; i++) + { + if (postimgtype[i]) + V_DoPostProcessor(i, postimgtype[i], postimgparam[i]); + } } } @@ -550,7 +519,7 @@ static void D_Display(void) wipegamestate = gamestate; // draw pause pic - if (paused && cv_showhud.value) + if (paused && cv_showhud.value && !demo.playback) { INT32 py; patch_t *patch; @@ -562,6 +531,12 @@ static void D_Display(void) V_DrawScaledPatch(viewwindowx + (BASEVIDWIDTH - SHORT(patch->width))/2, py, 0, patch); } + if (demo.rewinding) + V_DrawFadeScreen(TC_RAINBOW, (leveltime & 0x20) ? SKINCOLOR_PASTEL : SKINCOLOR_MOONSLAM); + + if (cv_vhseffect.value && (paused || (demo.playback && cv_playbackspeed.value > 1))) + V_DrawVhsEffect(demo.rewinding); + // vid size change is now finished if it was on... vid.recalc = 0; @@ -619,6 +594,9 @@ static void D_Display(void) V_DrawRightAlignedString(BASEVIDWIDTH, BASEVIDHEIGHT-ST_HEIGHT-10, V_YELLOWMAP, s); } + if (cv_shittyscreen.value) + V_DrawVhsEffect(cv_shittyscreen.value == 2); + I_FinishUpdate(); // page flip or blit buffer } } @@ -732,7 +710,7 @@ void D_SRB2Loop(void) M_DoScreenShot(); } - // consoleplayer -> displayplayer (hear sounds from viewpoint) + // consoleplayer -> displayplayers (hear sounds from viewpoint) S_UpdateSounds(); // move positional sounds // check for media change, loop music.. @@ -811,7 +789,8 @@ void D_StartTitle(void) maptol = 0; gameaction = ga_nothing; - displayplayer = consoleplayer = 0; + memset(displayplayers, 0, sizeof(displayplayers)); + consoleplayer = 0; //demosequence = -1; gametype = GT_RACE; // SRB2kart paused = false; @@ -1510,7 +1489,7 @@ void D_SRB2Main(void) if (M_CheckParm("-playdemo")) { - singledemo = true; // quit after one demo + demo.quitafterplaying = true; // quit after one demo G_DeferedPlayDemo(tmp); } else @@ -1537,6 +1516,8 @@ void D_SRB2Main(void) // as having been modified for the first game. M_PushSpecialParameters(); // push all "+" parameter at the command buffer + strncpy(connectedservername, cv_servername.string, MAXSERVERNAME); + if (M_CheckParm("-gametype") && M_IsNextParm()) { // from Command_Map_f diff --git a/src/d_net.h b/src/d_net.h index 8e518e404..eb657eec1 100644 --- a/src/d_net.h +++ b/src/d_net.h @@ -21,7 +21,6 @@ // Max computers in a game #define MAXNETNODES (MAXPLAYERS+4) #define BROADCASTADDR MAXNETNODES -#define MAXSPLITSCREENPLAYERS 4 // Max number of players on a single computer #define NETSPLITSCREEN // Kart's splitscreen netgame feature #define STATLENGTH (TICRATE*2) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 4ac7f575d..a49e7a973 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -129,6 +129,9 @@ static void Command_StopMovie_f(void); static void Command_Map_f(void); static void Command_ResetCamera_f(void); +static void Command_View_f (void); +static void Command_SetViews_f(void); + static void Command_Addfile(void); static void Command_ListWADS_f(void); #ifdef DELFILE @@ -741,6 +744,13 @@ void D_RegisterClientCommands(void) COM_AddCommand("resetcamera", Command_ResetCamera_f); + COM_AddCommand("view", Command_View_f); + COM_AddCommand("view2", Command_View_f); + COM_AddCommand("view3", Command_View_f); + COM_AddCommand("view4", Command_View_f); + + COM_AddCommand("setviews", Command_SetViews_f); + COM_AddCommand("setcontrol", Command_Setcontrol_f); COM_AddCommand("setcontrol2", Command_Setcontrol2_f); COM_AddCommand("setcontrol3", Command_Setcontrol3_f); @@ -815,6 +825,9 @@ void D_RegisterClientCommands(void) COM_AddCommand("displayplayer", Command_Displayplayer_f); + CV_RegisterVar(&cv_recordmultiplayerdemos); + CV_RegisterVar(&cv_netdemosyncquality); + // FIXME: not to be here.. but needs be done for config loading CV_RegisterVar(&cv_usegamma); @@ -938,6 +951,8 @@ void D_RegisterClientCommands(void) // screen.c CV_RegisterVar(&cv_fullscreen); CV_RegisterVar(&cv_renderview); + CV_RegisterVar(&cv_vhseffect); + CV_RegisterVar(&cv_shittyscreen); CV_RegisterVar(&cv_scr_depth); CV_RegisterVar(&cv_scr_width); CV_RegisterVar(&cv_scr_height); @@ -1149,11 +1164,11 @@ static void CleanupPlayerName(INT32 playernum, const char *newname) // spaces may have been removed if (playernum == consoleplayer) CV_StealthSet(&cv_playername, tmpname); - else if (playernum == secondarydisplayplayer || (!netgame && playernum == 1)) + else if (playernum == displayplayers[1] || (!netgame && playernum == 1)) CV_StealthSet(&cv_playername2, tmpname); - else if (playernum == thirddisplayplayer || (!netgame && playernum == 2)) + else if (playernum == displayplayers[2] || (!netgame && playernum == 2)) CV_StealthSet(&cv_playername3, tmpname); - else if (playernum == fourthdisplayplayer || (!netgame && playernum == 3)) + else if (playernum == displayplayers[3] || (!netgame && playernum == 3)) CV_StealthSet(&cv_playername4, tmpname); else I_Assert(((void)"CleanupPlayerName used on non-local player", 0)); @@ -1185,6 +1200,7 @@ static void SetPlayerName(INT32 playernum, char *newname) HU_AddChatText(va("\x82*%s renamed to %s", player_names[playernum], newname), false); strcpy(player_names[playernum], newname); + demo_extradata[playernum] |= DXD_NAME; } } else @@ -1260,11 +1276,11 @@ static void ForceAllSkins(INT32 forcedskin) { if (i == consoleplayer) CV_StealthSet(&cv_skin, skins[forcedskin].name); - else if (i == secondarydisplayplayer) + else if (i == displayplayers[1]) CV_StealthSet(&cv_skin2, skins[forcedskin].name); - else if (i == thirddisplayplayer) + else if (i == displayplayers[2]) CV_StealthSet(&cv_skin3, skins[forcedskin].name); - else if (i == fourthdisplayplayer) + else if (i == displayplayers[3]) CV_StealthSet(&cv_skin4, skins[forcedskin].name); } } @@ -1399,8 +1415,8 @@ static void SendNameAndColor2(void) if (splitscreen < 1 && !botingame) return; // can happen if skin2/color2/name2 changed - if (secondarydisplayplayer != consoleplayer) - secondplaya = secondarydisplayplayer; + if (displayplayers[1] != consoleplayer) + secondplaya = displayplayers[1]; else if (!netgame) // HACK secondplaya = 1; @@ -1449,7 +1465,7 @@ static void SendNameAndColor2(void) CleanupPlayerName(secondplaya, cv_playername2.zstring); strcpy(player_names[secondplaya], cv_playername2.zstring); - // don't use secondarydisplayplayer: the second player must be 1 + // don't use displayplayers[1]: the second player must be 1 players[secondplaya].skincolor = cv_playercolor2.value; if (players[secondplaya].mo) players[secondplaya].mo->color = players[secondplaya].skincolor; @@ -1488,14 +1504,14 @@ static void SendNameAndColor2(void) snac2pending++; // Don't change name if muted - if (cv_mute.value && !(server || IsPlayerAdmin(secondarydisplayplayer))) - CV_StealthSet(&cv_playername2, player_names[secondarydisplayplayer]); + if (cv_mute.value && !(server || IsPlayerAdmin(displayplayers[1]))) + CV_StealthSet(&cv_playername2, player_names[displayplayers[1]]); else // Cleanup name if changing it - CleanupPlayerName(secondarydisplayplayer, cv_playername2.zstring); + CleanupPlayerName(displayplayers[1], cv_playername2.zstring); // Don't change skin if the server doesn't want you to. - if (!CanChangeSkin(secondarydisplayplayer)) - CV_StealthSet(&cv_skin2, skins[players[secondarydisplayplayer].skin].name); + if (!CanChangeSkin(displayplayers[1])) + CV_StealthSet(&cv_skin2, skins[players[displayplayers[1]].skin].name); // check if player has the skin loaded (cv_skin2 may have // the name of a skin that was available in the previous game) @@ -1522,8 +1538,8 @@ static void SendNameAndColor3(void) if (splitscreen < 2) return; // can happen if skin3/color3/name3 changed - if (thirddisplayplayer != consoleplayer) - thirdplaya = thirddisplayplayer; + if (displayplayers[2] != consoleplayer) + thirdplaya = displayplayers[2]; else if (!netgame) // HACK thirdplaya = 2; @@ -1564,7 +1580,7 @@ static void SendNameAndColor3(void) CleanupPlayerName(thirdplaya, cv_playername3.zstring); strcpy(player_names[thirdplaya], cv_playername3.zstring); - // don't use thirddisplayplayer: the third player must be 2 + // don't use displayplayers[2]: the third player must be 2 players[thirdplaya].skincolor = cv_playercolor3.value; if (players[thirdplaya].mo) players[thirdplaya].mo->color = players[thirdplaya].skincolor; @@ -1603,14 +1619,14 @@ static void SendNameAndColor3(void) snac3pending++; // Don't change name if muted - if (cv_mute.value && !(server || IsPlayerAdmin(thirddisplayplayer))) - CV_StealthSet(&cv_playername3, player_names[thirddisplayplayer]); + if (cv_mute.value && !(server || IsPlayerAdmin(displayplayers[2]))) + CV_StealthSet(&cv_playername3, player_names[displayplayers[2]]); else // Cleanup name if changing it - CleanupPlayerName(thirddisplayplayer, cv_playername3.zstring); + CleanupPlayerName(displayplayers[2], cv_playername3.zstring); // Don't change skin if the server doesn't want you to. - if (!CanChangeSkin(thirddisplayplayer)) - CV_StealthSet(&cv_skin3, skins[players[thirddisplayplayer].skin].name); + if (!CanChangeSkin(displayplayers[2])) + CV_StealthSet(&cv_skin3, skins[players[displayplayers[2]].skin].name); // check if player has the skin loaded (cv_skin3 may have // the name of a skin that was available in the previous game) @@ -1637,8 +1653,8 @@ static void SendNameAndColor4(void) if (splitscreen < 3) return; // can happen if skin4/color4/name4 changed - if (fourthdisplayplayer != consoleplayer) - fourthplaya = fourthdisplayplayer; + if (displayplayers[3] != consoleplayer) + fourthplaya = displayplayers[3]; else if (!netgame) // HACK fourthplaya = 3; @@ -1687,7 +1703,7 @@ static void SendNameAndColor4(void) CleanupPlayerName(fourthplaya, cv_playername4.zstring); strcpy(player_names[fourthplaya], cv_playername4.zstring); - // don't use fourthdisplayplayer: the second player must be 4 + // don't use displayplayers[3]: the second player must be 4 players[fourthplaya].skincolor = cv_playercolor4.value; if (players[fourthplaya].mo) players[fourthplaya].mo->color = players[fourthplaya].skincolor; @@ -1726,14 +1742,14 @@ static void SendNameAndColor4(void) snac4pending++; // Don't change name if muted - if (cv_mute.value && !(server || IsPlayerAdmin(fourthdisplayplayer))) - CV_StealthSet(&cv_playername4, player_names[fourthdisplayplayer]); + if (cv_mute.value && !(server || IsPlayerAdmin(displayplayers[3]))) + CV_StealthSet(&cv_playername4, player_names[displayplayers[3]]); else // Cleanup name if changing it - CleanupPlayerName(fourthdisplayplayer, cv_playername4.zstring); + CleanupPlayerName(displayplayers[3], cv_playername4.zstring); // Don't change skin if the server doesn't want you to. - if (!CanChangeSkin(fourthdisplayplayer)) - CV_StealthSet(&cv_skin4, skins[players[fourthdisplayplayer].skin].name); + if (!CanChangeSkin(displayplayers[3])) + CV_StealthSet(&cv_skin4, skins[players[displayplayers[3]].skin].name); // check if player has the skin loaded (cv_skin4 may have // the name of a skin that was available in the previous game) @@ -1763,12 +1779,12 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum) #endif if (playernum == consoleplayer) - snacpending--; - else if (playernum == secondarydisplayplayer) + snacpending--; // TODO: make snacpending an array instead of 4 separate vars? + else if (playernum == displayplayers[1]) snac2pending--; - else if (playernum == thirddisplayplayer) + else if (playernum == displayplayers[2]) snac3pending--; - else if (playernum == fourthdisplayplayer) + else if (playernum == displayplayers[3]) snac4pending--; #ifdef PARANOIA @@ -1788,10 +1804,11 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum) p->skincolor = color % MAXSKINCOLORS; if (p->mo) p->mo->color = (UINT8)p->skincolor; + demo_extradata[playernum] |= DXD_COLOR; // normal player colors - if (server && (p != &players[consoleplayer] && p != &players[secondarydisplayplayer] - && p != &players[thirddisplayplayer] && p != &players[fourthdisplayplayer])) + if (server && (p != &players[consoleplayer] && p != &players[displayplayers[1]] + && p != &players[displayplayers[2]] && p != &players[displayplayers[3]])) { boolean kick = false; @@ -1828,11 +1845,11 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum) if (playernum == consoleplayer) CV_StealthSet(&cv_skin, skins[forcedskin].name); - else if (playernum == secondarydisplayplayer) + else if (playernum == displayplayers[1]) CV_StealthSet(&cv_skin2, skins[forcedskin].name); - else if (playernum == thirddisplayplayer) + else if (playernum == displayplayers[2]) CV_StealthSet(&cv_skin3, skins[forcedskin].name); - else if (playernum == fourthdisplayplayer) + else if (playernum == displayplayers[3]) CV_StealthSet(&cv_skin4, skins[forcedskin].name); } else @@ -1919,7 +1936,198 @@ void D_SendPlayerConfig(void) // Only works for displayplayer, sorry! static void Command_ResetCamera_f(void) { - P_ResetCamera(&players[displayplayer], &camera); + P_ResetCamera(&players[displayplayers[0]], &camera[0]); +} + +/* Consider replacing nametonum with this */ +static INT32 LookupPlayer(const char *s) +{ + INT32 playernum; + + if (*s == '0')/* clever way to bypass atoi */ + return 0; + + if (( playernum = atoi(s) )) + { + playernum = max(min(playernum, MAXPLAYERS-1), 0);/* not out of range */ + return playernum; + } + + for (playernum = 0; playernum < MAXPLAYERS; ++playernum) + { + /* Match name case-insensitively: fully, or partially the start. */ + if (playeringame[playernum]) + if (strnicmp(player_names[playernum], s, strlen(s)) == 0) + { + return playernum; + } + } + return -1; +} + +static INT32 FindPlayerByPlace(INT32 place) +{ + INT32 playernum; + for (playernum = 0; playernum < MAXPLAYERS; ++playernum) + if (playeringame[playernum]) + { + if (players[playernum].kartstuff[k_position] == place) + { + return playernum; + } + } + return -1; +} + +// +// GetViewablePlayerPlaceRange +// Return in first and last, that player available to view, sorted by placement +// in the race. +// +static void GetViewablePlayerPlaceRange(INT32 *first, INT32 *last) +{ + INT32 i; + INT32 place; + + (*first) = MAXPLAYERS; + (*last) = 0; + + for (i = 0; i < MAXPLAYERS; ++i) + if (G_CouldView(i)) + { + place = players[i].kartstuff[k_position]; + if (place < (*first)) + (*first) = place; + if (place > (*last)) + (*last) = place; + } +} + +#define PRINTVIEWPOINT( pre,suf ) \ + CONS_Printf(pre"viewing \x84(%d) \x83%s\x80"suf".\n",\ + (*displayplayerp), player_names[(*displayplayerp)]); +static void Command_View_f(void) +{ + INT32 *displayplayerp; + INT32 olddisplayplayer; + int viewnum; + const char *playerparam; + INT32 placenum; + INT32 playernum; + INT32 firstplace, lastplace; + char c; + /* easy peasy */ + c = COM_Argv(0)[strlen(COM_Argv(0))-1];/* may be digit */ + switch (c) + { + case '2': viewnum = 2; break; + case '3': viewnum = 3; break; + case '4': viewnum = 4; break; + default: viewnum = 1; + } + + if (viewnum > 1 && !( multiplayer && demo.playback )) + { + CONS_Alert(CONS_NOTICE, + "You must be viewing a multiplayer replay to use this.\n"); + return; + } + + displayplayerp = &displayplayers[viewnum]; + + if (COM_Argc() > 1)/* switch to player */ + { + playerparam = COM_Argv(1); + if (playerparam[0] == '#')/* search by placement */ + { + placenum = atoi(&playerparam[1]); + playernum = FindPlayerByPlace(placenum); + if (playernum == -1 || !G_CouldView(playernum)) + { + GetViewablePlayerPlaceRange(&firstplace, &lastplace); + if (playernum == -1) + { + CONS_Alert(CONS_WARNING, "There is no player in that place! "); + } + else + { + CONS_Alert(CONS_WARNING, + "That player cannot be viewed currently! " + "The first player that you can view is \x82#%d\x80; ", + firstplace); + } + CONS_Printf("Last place is \x82#%d\x80.\n", lastplace); + return; + } + } + else + { + if (( playernum = LookupPlayer(COM_Argv(1)) ) == -1) + { + CONS_Alert(CONS_WARNING, "There is no player by that name!\n"); + return; + } + if (!playeringame[playernum]) + { + CONS_Alert(CONS_WARNING, "There is no player using that slot!\n"); + return; + } + } + + olddisplayplayer = (*displayplayerp); + G_ResetView(viewnum, playernum, false); + + /* The player we wanted was corrected to who it already was. */ + if ((*displayplayerp) == olddisplayplayer) + return; + + if ((*displayplayerp) != playernum)/* differ parameter */ + { + /* skipped some */ + CONS_Alert(CONS_NOTICE, "That player cannot be viewed currently.\n"); + PRINTVIEWPOINT ("Now "," instead") + } + else + PRINTVIEWPOINT ("Now ",) + } + else/* print current view */ + { + if (splitscreen < viewnum-1)/* We can't see those guys! */ + return; + PRINTVIEWPOINT ("Currently ",) + } +} +#undef PRINTVIEWPOINT + +static void Command_SetViews_f(void) +{ + UINT8 splits; + UINT8 newsplits; + + if (!( demo.playback && multiplayer )) + { + CONS_Alert(CONS_NOTICE, + "You must be viewing a multiplayer replay to use this.\n"); + return; + } + + if (COM_Argc() != 2) + { + CONS_Printf("setviews : set the number of split screens\n"); + return; + } + + splits = splitscreen+1; + + newsplits = atoi(COM_Argv(1)); + newsplits = min(max(newsplits, 1), 4); + if (newsplits > splits) + G_AdjustView(newsplits, 0, true); + else + { + splitscreen = newsplits-1; + R_ExecuteSetViewSize(); + } } // ======================================================================== @@ -1932,9 +2140,14 @@ static void Command_Playdemo_f(void) { char name[256]; - if (COM_Argc() != 2) + if (COM_Argc() < 2) { - CONS_Printf(M_GetText("playdemo : playback a demo\n")); + CONS_Printf("playdemo [-addfiles / -force]:\n"); + CONS_Printf(M_GetText( + "Play back a demo file. The full path from your Kart directory must be given.\n\n" + + "* With \"-addfiles\", any required files are added from a list contained within the demo file.\n" + "* With \"-force\", the demo is played even if the necessary files have not been added.\n")); return; } @@ -1945,7 +2158,7 @@ static void Command_Playdemo_f(void) } // disconnect from server here? - if (demoplayback) + if (demo.playback) G_StopDemo(); if (metalplayback) G_StopMetalDemo(); @@ -1956,6 +2169,9 @@ static void Command_Playdemo_f(void) CONS_Printf(M_GetText("Playing back demo '%s'.\n"), name); + demo.loadfiles = strcmp(COM_Argv(2), "-addfiles") == 0; + demo.ignorefiles = strcmp(COM_Argv(2), "-force") == 0; + // Internal if no extension, external if one exists // If external, convert the file name to a path in SRB2's home directory if (FIL_CheckExtension(name)) @@ -1981,7 +2197,7 @@ static void Command_Timedemo_f(void) } // disconnect from server here? - if (demoplayback) + if (demo.playback) G_StopDemo(); if (metalplayback) G_StopMetalDemo(); @@ -2105,7 +2321,7 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pencoremode, boolean r { //CL_AddSplitscreenPlayer(); botingame = true; - secondarydisplayplayer = 1; + displayplayers[1] = 1; playeringame[1] = true; players[1].bot = 1; SendNameAndColor2(); @@ -2157,12 +2373,8 @@ void D_ModifyClientVote(SINT8 voted, UINT8 splitplayer) char *p = buf; UINT8 player = consoleplayer; - if (splitplayer == 1) - player = secondarydisplayplayer; - else if (splitplayer == 2) - player = thirddisplayplayer; - else if (splitplayer == 3) - player = fourthdisplayplayer; + if (splitplayer > 0) + player = displayplayers[splitplayer]; WRITESINT8(p, voted); WRITEUINT8(p, player); @@ -2432,7 +2644,7 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum) CON_ToggleOff(); CON_ClearHUD(); - if (demoplayback && !timingdemo) + if (demo.playback && !demo.timing) precache = false; if (resetplayer) @@ -2449,17 +2661,19 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum) LUAh_MapChange(mapnumber); #endif*/ + demo.savemode = (cv_recordmultiplayerdemos.value == 2) ? DSM_WILLAUTOSAVE : DSM_NOTSAVING; + demo.savebutton = 0; G_InitNew(pencoremode, mapname, resetplayer, skipprecutscene); - if (demoplayback && !timingdemo) + if (demo.playback && !demo.timing) precache = true; - if (timingdemo) + if (demo.timing) G_DoneLevelLoad(); if (metalrecording) G_BeginMetal(); - if (demorecording) // Okay, level loaded, character spawned and skinned, + if (demo.recording) // Okay, level loaded, character spawned and skinned, G_BeginRecording(); // I AM NOW READY TO RECORD. - demo_start = true; + demo.deferstart = true; } static void Command_Pause(void) @@ -2509,13 +2723,13 @@ static void Got_Pause(UINT8 **cp, INT32 playernum) return; } - if (modeattacking) + if (modeattacking && !demo.playback) return; paused = READUINT8(*cp); dedicatedpause = READUINT8(*cp); - if (!demoplayback) + if (!demo.playback) { if (netgame) { @@ -2602,6 +2816,7 @@ static void Got_Respawn(UINT8 **cp, INT32 playernum) if (players[respawnplayer].mo) P_DamageMobj(players[respawnplayer].mo, NULL, NULL, 10000); + demo_extradata[playernum] |= DXD_RESPAWN; } /** Deals with an ::XD_RANDOMSEED message in a netgame. @@ -2823,11 +3038,11 @@ static void Command_Teamchange2_f(void) return; } - if (players[secondarydisplayplayer].spectator) - error = !(NetPacket.packet.newteam || (players[secondarydisplayplayer].pflags & PF_WANTSTOJOIN)); + if (players[displayplayers[1]].spectator) + error = !(NetPacket.packet.newteam || (players[displayplayers[1]].pflags & PF_WANTSTOJOIN)); else if (G_GametypeHasTeams()) - error = (NetPacket.packet.newteam == (unsigned)players[secondarydisplayplayer].ctfteam); - else if (G_GametypeHasSpectators() && !players[secondarydisplayplayer].spectator) + error = (NetPacket.packet.newteam == (unsigned)players[displayplayers[1]].ctfteam); + else if (G_GametypeHasSpectators() && !players[displayplayers[1]].spectator) error = (NetPacket.packet.newteam == 3); #ifdef PARANOIA else @@ -2914,11 +3129,11 @@ static void Command_Teamchange3_f(void) return; } - if (players[thirddisplayplayer].spectator) - error = !(NetPacket.packet.newteam || (players[thirddisplayplayer].pflags & PF_WANTSTOJOIN)); + if (players[displayplayers[2]].spectator) + error = !(NetPacket.packet.newteam || (players[displayplayers[2]].pflags & PF_WANTSTOJOIN)); else if (G_GametypeHasTeams()) - error = (NetPacket.packet.newteam == (unsigned)players[thirddisplayplayer].ctfteam); - else if (G_GametypeHasSpectators() && !players[thirddisplayplayer].spectator) + error = (NetPacket.packet.newteam == (unsigned)players[displayplayers[2]].ctfteam); + else if (G_GametypeHasSpectators() && !players[displayplayers[2]].spectator) error = (NetPacket.packet.newteam == 3); #ifdef PARANOIA else @@ -3005,11 +3220,11 @@ static void Command_Teamchange4_f(void) return; } - if (players[fourthdisplayplayer].spectator) - error = !(NetPacket.packet.newteam || (players[fourthdisplayplayer].pflags & PF_WANTSTOJOIN)); + if (players[displayplayers[3]].spectator) + error = !(NetPacket.packet.newteam || (players[displayplayers[3]].pflags & PF_WANTSTOJOIN)); else if (G_GametypeHasTeams()) - error = (NetPacket.packet.newteam == (unsigned)players[fourthdisplayplayer].ctfteam); - else if (G_GametypeHasSpectators() && !players[fourthdisplayplayer].spectator) + error = (NetPacket.packet.newteam == (unsigned)players[displayplayers[3]].ctfteam); + else if (G_GametypeHasSpectators() && !players[displayplayers[3]].spectator) error = (NetPacket.packet.newteam == 3); #ifdef PARANOIA else @@ -3406,8 +3621,8 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum) HU_AddChatText(va("\x82*%s became a spectator.", player_names[playernum]), false); // "entered the game" text was moved to P_SpectatorJoinGame //reset view if you are changed, or viewing someone who was changed. - if (playernum == consoleplayer || displayplayer == playernum) - displayplayer = consoleplayer; + if (playernum == consoleplayer || displayplayers[0] == playernum) + displayplayers[0] = consoleplayer; if (G_GametypeHasTeams()) { @@ -3415,11 +3630,11 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum) { if (playernum == consoleplayer) //CTF and Team Match colors. CV_SetValue(&cv_playercolor, NetPacket.packet.newteam + 5); - else if (playernum == secondarydisplayplayer) + else if (playernum == displayplayers[1]) CV_SetValue(&cv_playercolor2, NetPacket.packet.newteam + 5); - else if (playernum == thirddisplayplayer) + else if (playernum == displayplayers[2]) CV_SetValue(&cv_playercolor3, NetPacket.packet.newteam + 5); - else if (playernum == fourthdisplayplayer) + else if (playernum == displayplayers[3]) CV_SetValue(&cv_playercolor4, NetPacket.packet.newteam + 5); } } @@ -3427,6 +3642,8 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum) if (gamestate != GS_LEVEL) return; + demo_extradata[playernum] |= DXD_PLAYSTATE; + // Clear player score and rings if a spectator. if (players[playernum].spectator) { @@ -4485,7 +4702,7 @@ static void PointLimit_OnChange(void) static void NumLaps_OnChange(void) { - if (!G_RaceGametype() || (modeattacking || demoplayback)) + if (!G_RaceGametype() || (modeattacking || demo.playback)) return; if (server && Playing() @@ -4925,7 +5142,7 @@ static void Command_ExitLevel_f(void) CONS_Printf(M_GetText("This only works in a netgame.\n")); else if (!(server || (IsPlayerAdmin(consoleplayer)))) CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n")); - else if (gamestate != GS_LEVEL || demoplayback) + else if (gamestate != GS_LEVEL || demo.playback) CONS_Printf(M_GetText("You must be in a level to use this.\n")); else SendNetXCmd(XD_EXITLEVEL, NULL, 0); @@ -5023,13 +5240,13 @@ static void Got_PickVotecmd(UINT8 **cp, INT32 playernum) Y_SetupVoteFinish(pick, level); } -/** Prints the number of the displayplayer. +/** Prints the number of displayplayers[0]. * * \todo Possibly remove this; it was useful for debugging at one point. */ static void Command_Displayplayer_f(void) { - CONS_Printf(M_GetText("Displayplayer is %d\n"), displayplayer); + CONS_Printf(M_GetText("Displayplayer is %d\n"), displayplayers[0]); } /** Quits a game and returns to the title screen. @@ -5224,7 +5441,7 @@ static void Name2_OnChange(void) if (cv_mute.value) //Secondary player can't be admin. { CONS_Alert(CONS_NOTICE, M_GetText("You may not change your name when chat is muted.\n")); - CV_StealthSet(&cv_playername2, player_names[secondarydisplayplayer]); + CV_StealthSet(&cv_playername2, player_names[displayplayers[1]]); } else SendNameAndColor2(); @@ -5235,7 +5452,7 @@ static void Name3_OnChange(void) if (cv_mute.value) //Third player can't be admin. { CONS_Alert(CONS_NOTICE, M_GetText("You may not change your name when chat is muted.\n")); - CV_StealthSet(&cv_playername3, player_names[thirddisplayplayer]); + CV_StealthSet(&cv_playername3, player_names[displayplayers[2]]); } else SendNameAndColor3(); @@ -5246,7 +5463,7 @@ static void Name4_OnChange(void) if (cv_mute.value) //Secondary player can't be admin. { CONS_Alert(CONS_NOTICE, M_GetText("You may not change your name when chat is muted.\n")); - CV_StealthSet(&cv_playername4, player_names[fourthdisplayplayer]); + CV_StealthSet(&cv_playername4, player_names[displayplayers[3]]); } else SendNameAndColor4(); @@ -5287,12 +5504,12 @@ static void Skin2_OnChange(void) if (!Playing() || !splitscreen) return; // do whatever you want - if (CanChangeSkin(secondarydisplayplayer) && !P_PlayerMoving(secondarydisplayplayer)) + if (CanChangeSkin(displayplayers[1]) && !P_PlayerMoving(displayplayers[1])) SendNameAndColor2(); else { CONS_Alert(CONS_NOTICE, M_GetText("You can't change your skin at the moment.\n")); - CV_StealthSet(&cv_skin2, skins[players[secondarydisplayplayer].skin].name); + CV_StealthSet(&cv_skin2, skins[players[displayplayers[1]].skin].name); } } @@ -5301,12 +5518,12 @@ static void Skin3_OnChange(void) if (!Playing() || splitscreen < 2) return; // do whatever you want - if (CanChangeSkin(thirddisplayplayer) && !P_PlayerMoving(thirddisplayplayer)) + if (CanChangeSkin(displayplayers[2]) && !P_PlayerMoving(displayplayers[2])) SendNameAndColor3(); else { CONS_Alert(CONS_NOTICE, M_GetText("You can't change your skin at the moment.\n")); - CV_StealthSet(&cv_skin3, skins[players[thirddisplayplayer].skin].name); + CV_StealthSet(&cv_skin3, skins[players[displayplayers[2]].skin].name); } } @@ -5315,12 +5532,12 @@ static void Skin4_OnChange(void) if (!Playing() || splitscreen < 3) return; // do whatever you want - if (CanChangeSkin(fourthdisplayplayer) && !P_PlayerMoving(fourthdisplayplayer)) + if (CanChangeSkin(displayplayers[3]) && !P_PlayerMoving(displayplayers[3])) SendNameAndColor4(); else { CONS_Alert(CONS_NOTICE, M_GetText("You can't change your skin at the moment.\n")); - CV_StealthSet(&cv_skin4, skins[players[fourthdisplayplayer].skin].name); + CV_StealthSet(&cv_skin4, skins[players[displayplayers[3]].skin].name); } } @@ -5361,7 +5578,7 @@ static void Color2_OnChange(void) if (!Playing() || !splitscreen) return; // do whatever you want - if (!P_PlayerMoving(secondarydisplayplayer)) + if (!P_PlayerMoving(displayplayers[1])) { // Color change menu scrolling fix is no longer necessary SendNameAndColor2(); @@ -5369,7 +5586,7 @@ static void Color2_OnChange(void) else { CV_StealthSetValue(&cv_playercolor2, - players[secondarydisplayplayer].skincolor); + players[displayplayers[1]].skincolor); } } @@ -5378,7 +5595,7 @@ static void Color3_OnChange(void) if (!Playing() || splitscreen < 2) return; // do whatever you want - if (!P_PlayerMoving(thirddisplayplayer)) + if (!P_PlayerMoving(displayplayers[2])) { // Color change menu scrolling fix is no longer necessary SendNameAndColor3(); @@ -5386,7 +5603,7 @@ static void Color3_OnChange(void) else { CV_StealthSetValue(&cv_playercolor3, - players[thirddisplayplayer].skincolor); + players[displayplayers[2]].skincolor); } } @@ -5395,7 +5612,7 @@ static void Color4_OnChange(void) if (!Playing() || splitscreen < 3) return; // do whatever you want - if (!P_PlayerMoving(fourthdisplayplayer)) + if (!P_PlayerMoving(displayplayers[3])) { // Color change menu scrolling fix is no longer necessary SendNameAndColor4(); @@ -5403,7 +5620,7 @@ static void Color4_OnChange(void) else { CV_StealthSetValue(&cv_playercolor4, - players[fourthdisplayplayer].skincolor); + players[displayplayers[3]].skincolor); } } diff --git a/src/dehacked.c b/src/dehacked.c index 795a6ddaa..6cd9eb77e 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -2142,11 +2142,12 @@ static boolean GoodDataFileName(const char *s) p = s + strlen(s) - strlen(tail); if (p <= s) return false; // too short if (!fasticmp(p, tail)) return false; // doesn't end in .dat -#ifdef DELFILE - if (fasticmp(s, "gamedata.dat") && !disableundo) return false; -#else - if (fasticmp(s, "gamedata.dat")) return false; -#endif + + if (fasticmp(s, "gamedata.dat")) return false; // Vanilla SRB2 gamedata + if (fasticmp(s, "main.dat")) return false; // Vanilla SRB2 time attack replay folder + if (fasticmp(s, "kartdata.dat")) return false; // SRB2Kart gamedata + if (fasticmp(s, "kart.dat")) return false; // SRB2Kart time attack replay folder + if (fasticmp(s, "online.dat")) return false; // SRB2Kart online replay folder return true; } @@ -9785,7 +9786,7 @@ static inline int lib_getenum(lua_State *L) // DYNAMIC variables too!! // Try not to add anything that would break netgames or timeattack replays here. - // You know, like consoleplayer, displayplayer, secondarydisplayplayer, or gametime. + // You know, like consoleplayer, displayplayers, or gametime. if (fastcmp(word,"gamemap")) { lua_pushinteger(L, gamemap); return 1; @@ -9859,7 +9860,7 @@ static inline int lib_getenum(lua_State *L) lua_pushinteger(L, mapmusflags); return 1; } else if (fastcmp(word,"server")) { - if ((!multiplayer || !netgame) && !playeringame[serverplayer]) + if ((!multiplayer || !(netgame || demo.playback)) && !playeringame[serverplayer]) return 0; LUA_PushUserdata(L, &players[serverplayer], META_PLAYER); return 1; diff --git a/src/doomdef.h b/src/doomdef.h index 182dbffdf..b12c42b49 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -157,6 +157,9 @@ extern FILE *logstream; // Otherwise we can't force updates! #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_DTA diff --git a/src/doomstat.h b/src/doomstat.h index 4b0c2e3e8..a9e67d845 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -77,7 +77,10 @@ extern boolean addedtogame; // true after the server has added you extern boolean multiplayer; extern INT16 gametype; + +#define MAXSPLITSCREENPLAYERS 4 // Max number of players on a single computer extern UINT8 splitscreen; + extern boolean circuitmap; // Does this level have 'circuit mode'? extern boolean fromlevelselect; extern boolean forceresetplayers, deferencoremode; @@ -106,14 +109,8 @@ extern UINT8 window_notinfocus; // are we in focus? (backend independant -- hand extern boolean nodrawers; extern boolean noblit; extern boolean lastdraw; -extern postimg_t postimgtype; -extern INT32 postimgparam; -extern postimg_t postimgtype2; -extern INT32 postimgparam2; -extern postimg_t postimgtype3; -extern INT32 postimgparam3; -extern postimg_t postimgtype4; -extern INT32 postimgparam4; +extern postimg_t postimgtype[MAXSPLITSCREENPLAYERS]; +extern INT32 postimgparam[MAXSPLITSCREENPLAYERS]; extern INT32 viewwindowx, viewwindowy; extern INT32 viewwidth, scaledviewwidth; @@ -122,10 +119,7 @@ extern boolean gamedataloaded; // Player taking events, and displaying. extern INT32 consoleplayer; -extern INT32 displayplayer; -extern INT32 secondarydisplayplayer; // for splitscreen -extern INT32 thirddisplayplayer; -extern INT32 fourthdisplayplayer; +extern INT32 displayplayers[MAXSPLITSCREENPLAYERS]; // Maps of special importance extern INT16 spstage_start; diff --git a/src/f_finale.c b/src/f_finale.c index 02d2eed59..4d8d0607f 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -584,6 +584,7 @@ void F_StartCredits(void) S_StopMusic(); S_ChangeMusicInternal("credit", false); + S_ShowMusicCredit(); finalecount = 0; animtimer = 0; @@ -927,9 +928,9 @@ void F_StartTitleScreen(void) // IWAD dependent stuff. // music is started in the ticker - if (!fromtitledemo) // SRB2Kart: Don't reset music if the right track is already playing + if (!demo.fromtitle) // SRB2Kart: Don't reset music if the right track is already playing S_StopMusic(); - fromtitledemo = false; + demo.fromtitle = false; animtimer = 0; @@ -1042,11 +1043,29 @@ void F_TitleScreenTicker(boolean run) // is it time? if (!(--demoIdleLeft)) { + //static boolean use_netreplay = false; + char dname[9]; lumpnum_t l; const char *mapname; UINT8 numstaff; + //@TODO uncomment this when this goes into vanilla + /*if ((use_netreplay = !use_netreplay))*/ + { + numstaff = 1; + while ((l = W_CheckNumForName(va("TDEMO%03u", numstaff))) != LUMPERROR) + numstaff++; + numstaff--; + + if (numstaff) + { + numstaff = M_RandomKey(numstaff)+1; + snprintf(dname, 9, "TDEMO%03u", numstaff); + goto loadreplay; + } + } + // prevent console spam if failed demoIdleLeft = demoIdleTime; @@ -1097,7 +1116,10 @@ void F_TitleScreenTicker(boolean run) return; }*/ - titledemo = fromtitledemo = true; +loadreplay: + demo.title = demo.fromtitle = true; + demo.ignorefiles = true; + demo.loadfiles = false; G_DoPlayDemo(dname); } } diff --git a/src/filesrch.c b/src/filesrch.c index 0276e1c90..6827dc57e 100644 --- a/src/filesrch.c +++ b/src/filesrch.c @@ -368,9 +368,10 @@ void searchfilemenu(char *tempname) return; } -boolean preparefilemenu(boolean samedepth) +boolean preparefilemenu(boolean samedepth, boolean replayhut) { (void)samedepth; + (void)replayhut; return false; } @@ -437,9 +438,10 @@ void searchfilemenu(char *tempname) return; } -boolean preparefilemenu(boolean samedepth) +boolean preparefilemenu(boolean samedepth, boolean replayhut) { (void)samedepth; + (void)replayhut; return false; } @@ -710,7 +712,7 @@ void searchfilemenu(char *tempname) } } -boolean preparefilemenu(boolean samedepth) +boolean preparefilemenu(boolean samedepth, boolean replayhut) { DIR *dirhandle; struct dirent *dent; @@ -759,9 +761,13 @@ boolean preparefilemenu(boolean samedepth) { if (!S_ISDIR(fsstat.st_mode)) // file { - if (!cv_addons_showall.value) + size_t len = strlen(dent->d_name)+1; + if (replayhut) + { + if (strcasecmp(".lmp", dent->d_name+len-5)) continue; // Not a replay + } + else if (!cv_addons_showall.value) { - size_t len = strlen(dent->d_name)+1; UINT8 ext; for (ext = 0; ext < NUM_EXT_TABLE; ext++) if (!strcasecmp(exttable[ext]+1, dent->d_name+len-(exttable[ext][0]))) break; // extension comparison @@ -829,40 +835,49 @@ boolean preparefilemenu(boolean samedepth) if (!S_ISDIR(fsstat.st_mode)) // file { if (!((numfolders+pos) < sizecoredirmenu)) continue; // crash prevention - for (; ext < NUM_EXT_TABLE; ext++) - if (!strcasecmp(exttable[ext]+1, dent->d_name+len-(exttable[ext][0]))) break; // extension comparison - if (ext == NUM_EXT_TABLE && !cv_addons_showall.value) continue; // not an addfile-able (or exec-able) file - ext += EXT_START; // moving to be appropriate position - if (ext >= EXT_LOADSTART) + if (replayhut) { - size_t i; - for (i = 0; i < numwadfiles; i++) + if (strcasecmp(".lmp", dent->d_name+len-5)) continue; // Not a replay + ext = EXT_TXT; // This isn't used anywhere but better safe than sorry for messing with this... + } + else + { + for (; ext < NUM_EXT_TABLE; ext++) + if (!strcasecmp(exttable[ext]+1, dent->d_name+len-(exttable[ext][0]))) break; // extension comparison + if (ext == NUM_EXT_TABLE && !cv_addons_showall.value) continue; // not an addfile-able (or exec-able) file + ext += EXT_START; // moving to be appropriate position + + if (ext >= EXT_LOADSTART) { - if (!filenamebuf[i][0]) + size_t i; + for (i = 0; i < numwadfiles; i++) { - strncpy(filenamebuf[i], wadfiles[i]->filename, MAX_WADPATH); - filenamebuf[i][MAX_WADPATH - 1] = '\0'; - nameonly(filenamebuf[i]); + if (!filenamebuf[i][0]) + { + strncpy(filenamebuf[i], wadfiles[i]->filename, MAX_WADPATH); + filenamebuf[i][MAX_WADPATH - 1] = '\0'; + nameonly(filenamebuf[i]); + } + + if (strcmp(dent->d_name, filenamebuf[i])) + continue; + if (cv_addons_md5.value && !checkfilemd5(menupath, wadfiles[i]->md5sum)) + continue; + + ext |= EXT_LOADED; } - - if (strcmp(dent->d_name, filenamebuf[i])) - continue; - if (cv_addons_md5.value && !checkfilemd5(menupath, wadfiles[i]->md5sum)) - continue; - - ext |= EXT_LOADED; } - } - else if (ext == EXT_TXT) - { - if (!strcmp(dent->d_name, "log.txt") || !strcmp(dent->d_name, "errorlog.txt")) + else if (ext == EXT_TXT) + { + if (!strcmp(dent->d_name, "log.txt") || !strcmp(dent->d_name, "errorlog.txt")) + ext |= EXT_LOADED; + } + + if (!strcmp(dent->d_name, configfile)) ext |= EXT_LOADED; } - if (!strcmp(dent->d_name, configfile)) - ext |= EXT_LOADED; - folder = 0; } else // directory @@ -881,6 +896,8 @@ boolean preparefilemenu(boolean samedepth) strcpy(temp+len, PATHSEP); coredirmenu[folderpos++] = temp; } + else if (replayhut) // Reverse-alphabetical on just the files; acts as a fake "most recent first" with the current filename format + coredirmenu[sizecoredirmenu - 1 - pos++] = temp; else coredirmenu[numfolders + pos++] = temp; } diff --git a/src/filesrch.h b/src/filesrch.h index 01a528482..dbcc5fc37 100644 --- a/src/filesrch.h +++ b/src/filesrch.h @@ -94,6 +94,6 @@ typedef enum void closefilemenu(boolean validsize); void searchfilemenu(char *tempname); -boolean preparefilemenu(boolean samedepth); +boolean preparefilemenu(boolean samedepth, boolean replayhut); #endif // __FILESRCH_H__ diff --git a/src/g_game.c b/src/g_game.c index 8f513554b..2fbe6f5f0 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -14,6 +14,7 @@ #include "doomdef.h" #include "console.h" #include "d_main.h" +#include "d_clisrv.h" #include "d_player.h" #include "f_finale.h" #include "filesrch.h" // for refreshdirmenu @@ -100,7 +101,6 @@ UINT8 numDemos = 0; //3; -- i'm FED UP of losing my skincolour to a broken UINT32 demoDelayTime = 15*TICRATE; UINT32 demoIdleTime = 3*TICRATE; -boolean timingdemo; // if true, exit with report on completion boolean nodrawers; // for comparative timing purposes boolean noblit; // for comparative timing purposes static tic_t demostarttime; // for comparative timing purposes @@ -112,10 +112,7 @@ boolean addedtogame; player_t players[MAXPLAYERS]; INT32 consoleplayer; // player taking events and displaying -INT32 displayplayer; // view being displayed -INT32 secondarydisplayplayer; // for splitscreen -INT32 thirddisplayplayer; -INT32 fourthdisplayplayer; +INT32 displayplayers[MAXSPLITSCREENPLAYERS]; // view being displayed tic_t gametic; tic_t levelstarttic; // gametic at level start @@ -286,20 +283,16 @@ UINT32 timesBeaten; UINT32 timesBeatenWithEmeralds; //UINT32 timesBeatenUltimate; -static char demoname[64]; -boolean demorecording; -boolean demoplayback; -boolean titledemo; // Title Screen demo can be cancelled by any key -boolean fromtitledemo; // SRB2Kart: Don't stop the music +//@TODO put these all in a struct for namespacing purposes? +static char demoname[128]; static UINT8 *demobuffer = NULL; -static UINT8 *demo_p, *demotime_p; +static UINT8 *demo_p, *demotime_p, *demoinfo_p; static UINT8 *demoend; static UINT8 demoflags; -static UINT16 demoversion; -boolean singledemo; // quit after playing a demo from cmdline -boolean demo_start; // don't start playing demo right away static boolean demosynced = true; // console warning message +struct demovars_s demo; + boolean metalrecording; // recording as metal sonic mobj_t *metalplayback; static UINT8 *metalbuffer = NULL; @@ -316,10 +309,15 @@ static struct { // EZT_SCALE fixed_t scale, lastscale; + // EZT_KART + INT32 kartitem, kartamount, kartbumpers; + + UINT8 desyncframes; // Don't try to resync unless we've been off for two frames, to monkeypatch a few trouble spots + // EZT_HIT UINT16 hits; mobj_t **hitlist; -} ghostext; +} ghostext[MAXPLAYERS]; // Your naming conventions are stupid and useless. // There is no conflict here. @@ -329,6 +327,12 @@ boolean precache = true; // if true, load all graphics at start INT16 prevmap, nextmap; +static CV_PossibleValue_t recordmultiplayerdemos_cons_t[] = {{0, "Disabled"}, {1, "Manual Save"}, {2, "Auto Save"}, {0, NULL}}; +consvar_t cv_recordmultiplayerdemos = {"netdemo_record", "Manual Save", CV_SAVE, recordmultiplayerdemos_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; + +static CV_PossibleValue_t netdemosyncquality_cons_t[] = {{1, "MIN"}, {35, "MAX"}, {0, NULL}}; +consvar_t cv_netdemosyncquality = {"netdemo_syncquality", "1", CV_SAVE, netdemosyncquality_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; + static UINT8 *savebuffer; // Analog Control @@ -1219,9 +1223,9 @@ INT32 JoyAxis(axis_input_e axissel, UINT8 p) // // set secondaryplayer true to build player 2's ticcmd in splitscreen mode // -INT32 localaiming, localaiming2, localaiming3, localaiming4; -angle_t localangle, localangle2, localangle3, localangle4; -boolean camspin, camspin2, camspin3, camspin4; +INT32 localaiming[MAXSPLITSCREENPLAYERS]; +angle_t localangle[MAXSPLITSCREENPLAYERS]; +boolean camspin[MAXSPLITSCREENPLAYERS]; static fixed_t forwardmove[2] = {25<>16, 50<>16}; static fixed_t sidemove[2] = {2<>16, 4<>16}; @@ -1238,51 +1242,36 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) camera_t *thiscam; angle_t lang; - static INT32 turnheld, turnheld2, turnheld3, turnheld4; // for accelerative turning - static boolean keyboard_look, keyboard_look2, keyboard_look3, keyboard_look4; // true if lookup/down using keyboard - static boolean resetdown, resetdown2, resetdown3, resetdown4; // don't cam reset every frame + static INT32 turnheld[MAXSPLITSCREENPLAYERS]; // for accelerative turning + static boolean keyboard_look[MAXSPLITSCREENPLAYERS]; // true if lookup/down using keyboard + static boolean resetdown[MAXSPLITSCREENPLAYERS]; // don't cam reset every frame + + if (demo.playback) return; + + player = &players[displayplayers[ssplayer-1]]; + if (ssplayer == 2) + thiscam = (player->bot == 2 ? &camera[0] : &camera[ssplayer-1]); + else + thiscam = &camera[ssplayer-1]; + lang = localangle[ssplayer-1]; + laim = localaiming[ssplayer-1]; + th = turnheld[ssplayer-1]; + kbl = keyboard_look[ssplayer-1]; + rd = resetdown[ssplayer-1]; switch (ssplayer) { case 2: - player = &players[secondarydisplayplayer]; - thiscam = (player->bot == 2 ? &camera : &camera2); - lang = localangle2; - laim = localaiming2; - th = turnheld2; - kbl = keyboard_look2; - rd = resetdown2; G_CopyTiccmd(cmd, I_BaseTiccmd2(), 1); break; case 3: - player = &players[thirddisplayplayer]; - thiscam = &camera3; - lang = localangle3; - laim = localaiming3; - th = turnheld3; - kbl = keyboard_look3; - rd = resetdown3; G_CopyTiccmd(cmd, I_BaseTiccmd3(), 1); break; case 4: - player = &players[fourthdisplayplayer]; - thiscam = &camera4; - lang = localangle4; - laim = localaiming4; - th = turnheld4; - kbl = keyboard_look4; - rd = resetdown4; G_CopyTiccmd(cmd, I_BaseTiccmd4(), 1); break; case 1: default: - player = &players[consoleplayer]; - thiscam = &camera; - lang = localangle; - laim = localaiming; - th = turnheld; - kbl = keyboard_look; - rd = resetdown; G_CopyTiccmd(cmd, I_BaseTiccmd(), 1); // empty, or external driver break; } @@ -1573,42 +1562,12 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) if (!hu_stopped) { - switch (ssplayer) - { - case 2: - localangle2 = lang; - localaiming2 = laim; - keyboard_look2 = kbl; - turnheld2 = th; - resetdown2 = rd; - camspin2 = InputDown(gc_lookback, ssplayer); - break; - case 3: - localangle3 = lang; - localaiming3 = laim; - keyboard_look3 = kbl; - turnheld3 = th; - resetdown3 = rd; - camspin3 = InputDown(gc_lookback, ssplayer); - break; - case 4: - localangle4 = lang; - localaiming4 = laim; - keyboard_look4 = kbl; - turnheld4 = th; - resetdown4 = rd; - camspin4 = InputDown(gc_lookback, ssplayer); - break; - case 1: - default: - localangle = lang; - localaiming = laim; - keyboard_look = kbl; - turnheld = th; - resetdown = rd; - camspin = InputDown(gc_lookback, ssplayer); - break; - } + localangle[ssplayer-1] = lang; + localaiming[ssplayer-1] = laim; + keyboard_look[ssplayer-1] = kbl; + turnheld[ssplayer-1] = th; + resetdown[ssplayer-1] = rd; + camspin[ssplayer-1] = InputDown(gc_lookback, ssplayer); } /* Lua: Allow this hook to overwrite ticcmd. @@ -1628,8 +1587,8 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) //Reset away view if a command is given. if ((cmd->forwardmove || cmd->sidemove || cmd->buttons) - && displayplayer != consoleplayer && ssplayer == 1) - displayplayer = consoleplayer; + && displayplayers[0] != consoleplayer && ssplayer == 1) + displayplayers[0] = consoleplayer; } @@ -1781,27 +1740,24 @@ void G_DoLoadLevel(boolean resetplayer) if (!resetplayer) P_FindEmerald(); - displayplayer = consoleplayer; // view the guy you are playing - if (!splitscreen && !botingame) - secondarydisplayplayer = consoleplayer; - if (splitscreen < 2) - thirddisplayplayer = consoleplayer; - if (splitscreen < 3) - fourthdisplayplayer = consoleplayer; + displayplayers[0] = consoleplayer; // view the guy you are playing + + for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) + { + if (i > 0 && !(i == 1 && botingame) && splitscreen < i) + displayplayers[i] = consoleplayer; + } gameaction = ga_nothing; #ifdef PARANOIA Z_CheckHeap(-2); #endif - if (camera.chase) - P_ResetCamera(&players[displayplayer], &camera); - if (camera2.chase && splitscreen) - P_ResetCamera(&players[secondarydisplayplayer], &camera2); - if (camera3.chase && splitscreen > 1) - P_ResetCamera(&players[thirddisplayplayer], &camera3); - if (camera4.chase && splitscreen > 2) - P_ResetCamera(&players[fourthdisplayplayer], &camera4); + for (i = 0; i <= splitscreen; i++) + { + if (camera[i].chase) + P_ResetCamera(&players[displayplayers[i]], &camera[i]); + } // clear cmd building stuff memset(gamekeydown, 0, sizeof (gamekeydown)); @@ -1830,8 +1786,8 @@ static INT32 spectatedelay, spectatedelay2, spectatedelay3, spectatedelay4 = 0; boolean G_Responder(event_t *ev) { // any other key pops up menu if in demos - if (gameaction == ga_nothing && !singledemo && - ((demoplayback && !modeattacking && !titledemo) || gamestate == GS_TITLESCREEN)) + if (gameaction == ga_nothing && !demo.quitafterplaying && + ((demo.playback && !modeattacking && !demo.title && !multiplayer) || gamestate == GS_TITLESCREEN)) { if (ev->type == ev_keydown && ev->data1 != 301) { @@ -1840,7 +1796,7 @@ boolean G_Responder(event_t *ev) } return false; } - else if (demoplayback && titledemo) + else if (demo.playback && demo.title) { // Title demo uses intro responder if (F_IntroResponder(ev)) @@ -1910,76 +1866,76 @@ boolean G_Responder(event_t *ev) if (gamestate == GS_LEVEL && ev->type == ev_keydown && (ev->data1 == KEY_F12 || ev->data1 == gamecontrol[gc_viewpoint][0] || ev->data1 == gamecontrol[gc_viewpoint][1])) { - if (splitscreen || !netgame) - displayplayer = consoleplayer; + if (!demo.playback && (splitscreen || !netgame)) + displayplayers[0] = consoleplayer; else { - UINT8 i = 0; // spy mode - for (i = 0; i < MAXPLAYERS; i++) - { - displayplayer++; - if (displayplayer == MAXPLAYERS) - displayplayer = 0; - - if (displayplayer == consoleplayer) - break; // End loop - - if (!playeringame[displayplayer]) - continue; - - if (players[displayplayer].spectator) - continue; - - // SRB2Kart: Only go through players who are actually playing - if (players[displayplayer].exiting) - continue; - - if (players[displayplayer].pflags & PF_TIMEOVER) - continue; - - // I don't know if we want this actually, but I'll humor the suggestion anyway - if (G_BattleGametype()) - { - if (players[displayplayer].kartstuff[k_bumper] <= 0) - continue; - } - - // SRB2Kart: we have no team-based modes, YET... - /*if (G_GametypeHasTeams()) - { - if (players[consoleplayer].ctfteam - && players[displayplayer].ctfteam != players[consoleplayer].ctfteam) - continue; - } - else if (gametype == GT_HIDEANDSEEK) - { - if (players[consoleplayer].pflags & PF_TAGIT) - continue; - } - // Other Tag-based gametypes? - else if (G_TagGametype()) - { - if (!players[consoleplayer].spectator - && (players[consoleplayer].pflags & PF_TAGIT) != (players[displayplayer].pflags & PF_TAGIT)) - continue; - } - else if (G_GametypeHasSpectators() && G_BattleGametype()) - { - if (!players[consoleplayer].spectator) - continue; - }*/ - - break; - } + G_AdjustView(1, 1, true); // change statusbar also if playing back demo - if (singledemo) + if (demo.quitafterplaying) ST_changeDemoView(); return true; } } + if (gamestate == GS_LEVEL && ev->type == ev_keydown && multiplayer && demo.playback) + { + if (ev->data1 == gamecontrolbis[gc_viewpoint][0] || ev->data1 == gamecontrolbis[gc_viewpoint][1]) + { + G_AdjustView(2, 1, true); + + return true; + } + else if (ev->data1 == gamecontrol3[gc_viewpoint][0] || ev->data1 == gamecontrol3[gc_viewpoint][1]) + { + G_AdjustView(3, 1, true); + + return true; + } + else if (ev->data1 == gamecontrol4[gc_viewpoint][0] || ev->data1 == gamecontrol4[gc_viewpoint][1]) + { + G_AdjustView(4, 1, true); + + return true; + } + + // Allow pausing + if ( + ev->data1 == gamecontrol[gc_pause][0] + || ev->data1 == gamecontrol[gc_pause][1] + || ev->data1 == KEY_PAUSE + ) + { + paused = !paused; + + if (demo.rewinding) + { + G_ConfirmRewind(leveltime); + paused = true; + S_PauseAudio(); + } + else if (paused) + S_PauseAudio(); + else + S_ResumeAudio(); + + return true; + } + + // Anything else opens the menu if not already open, except for a few keys... + if (!( + // Rankings + ev->data1 == gamecontrol[gc_scores][0] || ev->data1 == gamecontrol[gc_scores][1] + )) + { + M_StartControlPanel(); + + return true; + } + } + // update keys current state G_MapEventsToControls(ev); @@ -2102,6 +2058,255 @@ boolean G_Responder(event_t *ev) return false; } +// +// G_CouldView +// Return whether a player could be viewed by any means. +// +boolean G_CouldView(INT32 playernum) +{ + player_t *player; + + if (playernum < 0 || playernum > MAXPLAYERS-1) + return false; + + if (!playeringame[playernum]) + return false; + + player = &players[playernum]; + + if (player->spectator) + return false; + + // SRB2Kart: Only go through players who are actually playing + if (player->exiting) + return false; + if (( player->pflags & PF_TIMEOVER )) + return false; + + // I don't know if we want this actually, but I'll humor the suggestion anyway + if (G_BattleGametype() && !demo.playback) + { + if (player->kartstuff[k_bumper] <= 0) + return false; + } + + // SRB2Kart: we have no team-based modes, YET... + /*if (G_GametypeHasTeams()) + { + if (players[consoleplayer].ctfteam + && player->ctfteam != players[consoleplayer].ctfteam) + return false; + } + else if (gametype == GT_HIDEANDSEEK) + { + if (players[consoleplayer].pflags & PF_TAGIT) + return false; + } + // Other Tag-based gametypes? + else if (G_TagGametype()) + { + if (!players[consoleplayer].spectator + && (players[consoleplayer].pflags & PF_TAGIT) != (player->pflags & PF_TAGIT)) + return false; + } + else if (G_GametypeHasSpectators() && G_BattleGametype()) + { + if (!players[consoleplayer].spectator) + return false; + }*/ + + return true; +} + +// +// G_CanView +// Return whether a player can be viewed on a particular view (splitscreen). +// +boolean G_CanView(INT32 playernum, UINT8 viewnum, boolean onlyactive) +{ + UINT8 splits; + UINT8 viewd; + INT32 *displayplayerp; + + if (!(onlyactive ? G_CouldView(playernum) : (playeringame[playernum] && !players[playernum].spectator))) + return false; + + splits = splitscreen+1; + if (viewnum > splits) + viewnum = splits; + + for (viewd = 1; viewd < viewnum; ++viewd) + { + displayplayerp = (&displayplayers[viewd-1]); + if ((*displayplayerp) == playernum) + return false; + } + for (viewd = viewnum + 1; viewd <= splits; ++viewd) + { + displayplayerp = (&displayplayers[viewd-1]); + if ((*displayplayerp) == playernum) + return false; + } + + return true; +} + +// +// G_FindView +// Return the next player that can be viewed on a view, wraps forward. +// An out of range startview is corrected. +// +INT32 G_FindView(INT32 startview, UINT8 viewnum, boolean onlyactive, boolean reverse) +{ + INT32 i, dir = reverse ? -1 : 1; + startview = min(max(startview, 0), MAXPLAYERS); + for (i = startview; i < MAXPLAYERS && i >= 0; i += dir) + { + if (G_CanView(i, viewnum, onlyactive)) + return i; + } + for (i = (reverse ? MAXPLAYERS-1 : 0); i != startview; i += dir) + { + if (G_CanView(i, viewnum, onlyactive)) + return i; + } + return -1; +} + +INT32 G_CountPlayersPotentiallyViewable(boolean active) +{ + INT32 total = 0; + INT32 i; + for (i = 0; i < MAXPLAYERS; ++i) + { + if (active ? G_CouldView(i) : (playeringame[i] && !players[i].spectator)) + total++; + } + return total; +} + +// +// G_ResetView +// Correct a viewpoint to playernum or the next available, wraps forward. +// Also promotes splitscreen up to available viewable players. +// An out of range playernum is corrected. +// +void G_ResetView(UINT8 viewnum, INT32 playernum, boolean onlyactive) +{ + UINT8 splits; + UINT8 viewd; + + INT32 *displayplayerp; + camera_t *camerap; + + INT32 olddisplayplayer; + INT32 playersviewable; + + splits = splitscreen+1; + + /* Promote splits */ + if (viewnum > splits) + { + playersviewable = G_CountPlayersPotentiallyViewable(onlyactive); + if (playersviewable < splits)/* do not demote */ + return; + + if (viewnum > playersviewable) + viewnum = playersviewable; + splitscreen = viewnum-1; + + /* Prepare extra views for G_FindView to pass. */ + for (viewd = splits+1; viewd < viewnum; ++viewd) + { + displayplayerp = (&displayplayers[viewd-1]); + (*displayplayerp) = INT32_MAX; + } + + R_ExecuteSetViewSize(); + } + + displayplayerp = (&displayplayers[viewnum-1]); + olddisplayplayer = (*displayplayerp); + + /* Check if anyone is available to view. */ + if (( playernum = G_FindView(playernum, viewnum, onlyactive, playernum < olddisplayplayer) ) == -1) + return; + + /* Focus our target view first so that we don't take its player. */ + (*displayplayerp) = playernum; + if ((*displayplayerp) != olddisplayplayer) + { + camerap = &camera[viewnum-1]; + P_ResetCamera(&players[(*displayplayerp)], camerap); + } + + if (viewnum > splits) + { + for (viewd = splits+1; viewd < viewnum; ++viewd) + { + displayplayerp = (&displayplayers[viewd-1]); + camerap = &camera[viewd]; + + (*displayplayerp) = G_FindView(0, viewd, onlyactive, false); + + P_ResetCamera(&players[(*displayplayerp)], camerap); + } + } + + if (viewnum == 1 && demo.playback) + consoleplayer = displayplayers[0]; +} + +// +// G_AdjustView +// Increment a viewpoint by offset from the current player. A negative value +// decrements. +// +void G_AdjustView(UINT8 viewnum, INT32 offset, boolean onlyactive) +{ + INT32 *displayplayerp, oldview; + displayplayerp = &displayplayers[viewnum-1]; + oldview = (*displayplayerp); + G_ResetView(viewnum, ( (*displayplayerp) + offset ), onlyactive); + + // If no other view could be found, go back to what we had. + if ((*displayplayerp) == -1) + (*displayplayerp) = oldview; +} + +// +// G_ResetViews +// Ensures all viewpoints are valid +// Also demotes splitscreen down to one player. +// +void G_ResetViews(void) +{ + UINT8 splits; + UINT8 viewd; + + INT32 playersviewable; + + splits = splitscreen+1; + + playersviewable = G_CountPlayersPotentiallyViewable(false); + /* Demote splits */ + if (playersviewable < splits) + { + splits = playersviewable; + splitscreen = max(splits-1, 0); + R_ExecuteSetViewSize(); + } + + /* + Consider installing a method to focus the last + view elsewhere if all players spectate? + */ + for (viewd = 1; viewd <= splits; ++viewd) + { + G_AdjustView(viewd, 0, false); + } +} + // // G_Ticker // Make ticcmd_ts for the players. @@ -2151,6 +2356,7 @@ void G_Ticker(boolean run) buf = gametic % BACKUPTICS; + if (!demo.playback) // read/write demo and check turbo cheat for (i = 0; i < MAXPLAYERS; i++) { @@ -2158,15 +2364,18 @@ void G_Ticker(boolean run) if (playeringame[i]) { + //@TODO all this throwdir stuff shouldn't be here! But it stays for now to maintain 1.0.4 compat... + // Remove for 1.1! + // SRB2kart // Save the dir the player is holding // to allow items to be thrown forward or backward. if (cmd->buttons & BT_FORWARD) - players[i].kartstuff[k_throwdir] = 1; + players[i].kartstuff[k_throwdir] = 1; else if (cmd->buttons & BT_BACKWARD) - players[i].kartstuff[k_throwdir] = -1; + players[i].kartstuff[k_throwdir] = -1; else - players[i].kartstuff[k_throwdir] = 0; + players[i].kartstuff[k_throwdir] = 0; G_CopyTiccmd(cmd, &netcmds[buf][i], 1); @@ -2179,7 +2388,7 @@ void G_Ticker(boolean run) switch (gamestate) { case GS_LEVEL: - if (titledemo) + if (demo.title) F_TitleDemoTicker(); P_Ticker(run); // tic the game ST_Ticker(); @@ -2309,7 +2518,7 @@ static inline void G_PlayerFinishLevel(INT32 player) // SRB2kart: Increment the "matches played" counter. if (player == consoleplayer) { - if (legitimateexit && !demoplayback && !mapreset) // (yes you're allowed to unlock stuff this way when the game is modified) + if (legitimateexit && !demo.playback && !mapreset) // (yes you're allowed to unlock stuff this way when the game is modified) { matchesplayed++; if (M_UpdateUnlockablesAndExtraEmblems(true)) @@ -2555,22 +2764,22 @@ void G_PlayerReborn(INT32 player) { if (p == &players[consoleplayer]) CV_SetValue(&cv_playercolor, skincolor_redteam); - else if (p == &players[secondarydisplayplayer]) + else if (p == &players[displayplayers[1]]) CV_SetValue(&cv_playercolor2, skincolor_redteam); - else if (p == &players[thirddisplayplayer]) + else if (p == &players[displayplayers[2]]) CV_SetValue(&cv_playercolor3, skincolor_redteam); - else if (p == &players[fourthdisplayplayer]) + else if (p == &players[displayplayers[3]]) CV_SetValue(&cv_playercolor4, skincolor_redteam); } else if (p->ctfteam == 2 && p->skincolor != skincolor_blueteam) { if (p == &players[consoleplayer]) CV_SetValue(&cv_playercolor, skincolor_blueteam); - else if (p == &players[secondarydisplayplayer]) + else if (p == &players[displayplayers[1]]) CV_SetValue(&cv_playercolor2, skincolor_blueteam); - else if (p == &players[thirddisplayplayer]) + else if (p == &players[displayplayers[2]]) CV_SetValue(&cv_playercolor3, skincolor_blueteam); - else if (p == &players[fourthdisplayplayer]) + else if (p == &players[displayplayers[3]]) CV_SetValue(&cv_playercolor4, skincolor_blueteam); } }*/ @@ -2675,18 +2884,18 @@ void G_SpawnPlayer(INT32 playernum, boolean starpost) if (nummapthings) { if (playernum == consoleplayer - || (splitscreen && playernum == secondarydisplayplayer) - || (splitscreen > 1 && playernum == thirddisplayplayer) - || (splitscreen > 2 && playernum == fourthdisplayplayer)) + || (splitscreen && playernum == displayplayers[1]) + || (splitscreen > 1 && playernum == displayplayers[2]) + || (splitscreen > 2 && playernum == displayplayers[3])) CONS_Alert(CONS_ERROR, M_GetText("No player spawns found, spawning at the first mapthing!\n")); spawnpoint = &mapthings[0]; } else { if (playernum == consoleplayer - || (splitscreen && playernum == secondarydisplayplayer) - || (splitscreen > 1 && playernum == thirddisplayplayer) - || (splitscreen > 2 && playernum == fourthdisplayplayer)) + || (splitscreen && playernum == displayplayers[1]) + || (splitscreen > 1 && playernum == displayplayers[2]) + || (splitscreen > 2 && playernum == displayplayers[3])) CONS_Alert(CONS_ERROR, M_GetText("No player spawns found, spawning at the origin!\n")); //P_MovePlayerToSpawn handles this fine if the spawnpoint is NULL. } @@ -2706,9 +2915,9 @@ mapthing_t *G_FindCTFStart(INT32 playernum) if (!numredctfstarts && !numbluectfstarts) //why even bother, eh? { if (playernum == consoleplayer - || (splitscreen && playernum == secondarydisplayplayer) - || (splitscreen > 1 && playernum == thirddisplayplayer) - || (splitscreen > 2 && playernum == fourthdisplayplayer)) + || (splitscreen && playernum == displayplayers[1]) + || (splitscreen > 1 && playernum == displayplayers[2]) + || (splitscreen > 2 && playernum == displayplayers[3])) CONS_Alert(CONS_WARNING, M_GetText("No CTF starts in this map!\n")); return NULL; } @@ -2718,9 +2927,9 @@ mapthing_t *G_FindCTFStart(INT32 playernum) if (!numredctfstarts) { if (playernum == consoleplayer - || (splitscreen && playernum == secondarydisplayplayer) - || (splitscreen > 1 && playernum == thirddisplayplayer) - || (splitscreen > 2 && playernum == fourthdisplayplayer)) + || (splitscreen && playernum == displayplayers[1]) + || (splitscreen > 1 && playernum == displayplayers[2]) + || (splitscreen > 2 && playernum == displayplayers[3])) CONS_Alert(CONS_WARNING, M_GetText("No Red Team starts in this map!\n")); return NULL; } @@ -2733,9 +2942,9 @@ mapthing_t *G_FindCTFStart(INT32 playernum) } if (playernum == consoleplayer - || (splitscreen && playernum == secondarydisplayplayer) - || (splitscreen > 1 && playernum == thirddisplayplayer) - || (splitscreen > 2 && playernum == fourthdisplayplayer)) + || (splitscreen && playernum == displayplayers[1]) + || (splitscreen > 1 && playernum == displayplayers[2]) + || (splitscreen > 2 && playernum == displayplayers[3])) CONS_Alert(CONS_WARNING, M_GetText("Could not spawn at any Red Team starts!\n")); return NULL; } @@ -2744,9 +2953,9 @@ mapthing_t *G_FindCTFStart(INT32 playernum) if (!numbluectfstarts) { if (playernum == consoleplayer - || (splitscreen && playernum == secondarydisplayplayer) - || (splitscreen > 1 && playernum == thirddisplayplayer) - || (splitscreen > 2 && playernum == fourthdisplayplayer)) + || (splitscreen && playernum == displayplayers[1]) + || (splitscreen > 1 && playernum == displayplayers[2]) + || (splitscreen > 2 && playernum == displayplayers[3])) CONS_Alert(CONS_WARNING, M_GetText("No Blue Team starts in this map!\n")); return NULL; } @@ -2758,9 +2967,9 @@ mapthing_t *G_FindCTFStart(INT32 playernum) return bluectfstarts[i]; } if (playernum == consoleplayer - || (splitscreen && playernum == secondarydisplayplayer) - || (splitscreen > 1 && playernum == thirddisplayplayer) - || (splitscreen > 2 && playernum == fourthdisplayplayer)) + || (splitscreen && playernum == displayplayers[1]) + || (splitscreen > 1 && playernum == displayplayers[2]) + || (splitscreen > 2 && playernum == displayplayers[3])) CONS_Alert(CONS_WARNING, M_GetText("Could not spawn at any Blue Team starts!\n")); return NULL; } @@ -2781,17 +2990,17 @@ mapthing_t *G_FindMatchStart(INT32 playernum) return deathmatchstarts[i]; } if (playernum == consoleplayer - || (splitscreen && playernum == secondarydisplayplayer) - || (splitscreen > 1 && playernum == thirddisplayplayer) - || (splitscreen > 2 && playernum == fourthdisplayplayer)) + || (splitscreen && playernum == displayplayers[1]) + || (splitscreen > 1 && playernum == displayplayers[2]) + || (splitscreen > 2 && playernum == displayplayers[3])) CONS_Alert(CONS_WARNING, M_GetText("Could not spawn at any Deathmatch starts!\n")); return NULL; } if (playernum == consoleplayer - || (splitscreen && playernum == secondarydisplayplayer) - || (splitscreen > 1 && playernum == thirddisplayplayer) - || (splitscreen > 2 && playernum == fourthdisplayplayer)) + || (splitscreen && playernum == displayplayers[1]) + || (splitscreen > 1 && playernum == displayplayers[2]) + || (splitscreen > 2 && playernum == displayplayers[3])) CONS_Alert(CONS_WARNING, M_GetText("No Deathmatch starts in this map!\n")); return NULL; } @@ -2857,17 +3066,17 @@ mapthing_t *G_FindRaceStart(INT32 playernum) //return playerstarts[0]; if (playernum == consoleplayer - || (splitscreen && playernum == secondarydisplayplayer) - || (splitscreen > 1 && playernum == thirddisplayplayer) - || (splitscreen > 2 && playernum == fourthdisplayplayer)) + || (splitscreen && playernum == displayplayers[1]) + || (splitscreen > 1 && playernum == displayplayers[2]) + || (splitscreen > 2 && playernum == displayplayers[3])) CONS_Alert(CONS_WARNING, M_GetText("Could not spawn at any Race starts!\n")); return NULL; } if (playernum == consoleplayer - || (splitscreen && playernum == secondarydisplayplayer) - || (splitscreen > 1 && playernum == thirddisplayplayer) - || (splitscreen > 2 && playernum == fourthdisplayplayer)) + || (splitscreen && playernum == displayplayers[1]) + || (splitscreen > 1 && playernum == displayplayers[2]) + || (splitscreen > 2 && playernum == displayplayers[3])) CONS_Alert(CONS_WARNING, M_GetText("No Race starts in this map!\n")); return NULL; } @@ -2959,14 +3168,11 @@ void G_DoReborn(INT32 playernum) if (player->starpostnum) // SRB2kart starpost = true; - if (camera.chase) - P_ResetCamera(&players[displayplayer], &camera); - if (camera2.chase && splitscreen > 0) - P_ResetCamera(&players[secondarydisplayplayer], &camera2); - if (camera3.chase && splitscreen > 1) - P_ResetCamera(&players[thirddisplayplayer], &camera3); - if (camera4.chase && splitscreen > 2) - P_ResetCamera(&players[fourthdisplayplayer], &camera4); + for (i = 0; i <= splitscreen; i++) + { + if (camera[i].chase) + P_ResetCamera(&players[displayplayers[i]], &camera[i]); + } // clear cmd building stuff memset(gamekeydown, 0, sizeof (gamekeydown)); @@ -2988,8 +3194,8 @@ void G_DoReborn(INT32 playernum) if (botingame) { // Bots respawn next to their master. - players[secondarydisplayplayer].playerstate = PST_REBORN; - G_SpawnPlayer(secondarydisplayplayer, false); + players[displayplayers[1]].playerstate = PST_REBORN; + G_SpawnPlayer(displayplayers[1], false); } } else @@ -3028,6 +3234,8 @@ void G_AddPlayer(INT32 playernum) p->jointime = 0; p->playerstate = PST_REBORN; + + demo_extradata[playernum] |= DXD_PLAYSTATE|DXD_COLOR|DXD_NAME|DXD_SKIN; // Set everything } void G_ExitLevel(void) @@ -3050,6 +3258,8 @@ void G_ExitLevel(void) // Remove CEcho text on round end. HU_ClearCEcho(); + + // Don't save demos immediately here! Let standings write first } } @@ -3114,7 +3324,7 @@ boolean G_GametypeHasSpectators(void) #if 0 return (gametype != GT_COOP && gametype != GT_COMPETITION && gametype != GT_RACE); #else - return (netgame); //true + return (netgame || (multiplayer && demo.playback)); //true #endif } @@ -3440,7 +3650,7 @@ static void G_DoCompleted(void) } // play some generic music if there's no win/cool/lose music going on (for exitlevel commands) - if (G_RaceGametype() && j == splitscreen+1 && (cv_inttime.value > 0)) + if (G_RaceGametype() && ((multiplayer && demo.playback) || j == splitscreen+1) && (cv_inttime.value > 0)) S_ChangeMusicInternal("racent", true); if (automapactive) @@ -3450,6 +3660,8 @@ static void G_DoCompleted(void) prevmap = (INT16)(gamemap-1); + if (demo.playback) goto demointermission; + // go to next level // nextmap is 0-based, unlike gamemap if (nextmapoverride != 0) @@ -3552,12 +3764,15 @@ static void G_DoCompleted(void) nextmap = G_RandMap(G_TOLFlag(gametype), prevmap, false, 0, false, NULL); } + // We are committed to this map now. // We may as well allocate its header if it doesn't exist // (That is, if it's a real map) if (nextmap < NUMMAPS && !mapheaderinfo[nextmap]) P_AllocMapHeader(nextmap); +demointermission: + if (skipstats && !modeattacking) // Don't skip stats if we're in record attack G_AfterIntermission(); else @@ -3572,6 +3787,20 @@ void G_AfterIntermission(void) HU_ClearCEcho(); //G_NextLevel(); + if (demo.playback) + { + G_StopDemo(); + + if (demo.inreplayhut) + M_ReplayHut(0); + else + D_StartTitle(); + + return; + } + else if (demo.recording && (modeattacking || demo.savemode != DSM_NOTSAVING)) + G_SaveDemo(); + if (modeattacking) // End the run. { M_EndModeAttackRun(); @@ -3716,6 +3945,9 @@ static void G_DoContinued(void) // when something new is added. void G_EndGame(void) { + if (demo.recording && (modeattacking || demo.savemode != DSM_NOTSAVING)) + G_SaveDemo(); + // Only do evaluation and credits in coop games. if (gametype == GT_COOP) { @@ -4083,7 +4315,7 @@ static void M_ForceLoadGameResponse(INT32 ch) //set cursaveslot to -1 so nothing gets saved. cursaveslot = -1; - displayplayer = consoleplayer; + displayplayers[0] = consoleplayer; multiplayer = false; splitscreen = 0; SplitScreen_OnChange(); // not needed? @@ -4147,7 +4379,7 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride) } save_p += VERSIONSIZE; - if (demoplayback) // reset game engine + if (demo.playback) // reset game engine G_StopDemo(); // paused = false; @@ -4173,7 +4405,7 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride) // gameaction = ga_nothing; // G_SetGamestate(GS_LEVEL); - displayplayer = consoleplayer; + displayplayers[0] = consoleplayer; multiplayer = false; splitscreen = 0; SplitScreen_OnChange(); // not needed? @@ -4238,7 +4470,7 @@ void G_SaveGame(UINT32 savegameslot) // // G_DeferedInitNew // Can be called by the startup code or the menu task, -// consoleplayer, displayplayer, playeringame[] should be set. +// consoleplayer, displayplayers[], playeringame[] should be set. // void G_DeferedInitNew(boolean pencoremode, const char *mapname, INT32 pickedchar, UINT8 ssplayers, boolean FLS) { @@ -4246,7 +4478,7 @@ void G_DeferedInitNew(boolean pencoremode, const char *mapname, INT32 pickedchar UINT8 color = 0; paused = false; - if (demoplayback) + if (demo.playback) COM_BufAddText("stopdemo\n"); while (ghosts) @@ -4308,7 +4540,7 @@ void G_InitNew(UINT8 pencoremode, const char *mapname, boolean resetplayer, bool legitimateexit = false; // SRB2Kart comebackshowninfo = false; - if (!demoplayback && !netgame) // Netgame sets random seed elsewhere, demo playback sets seed just before us! + if (!demo.playback && !netgame) // Netgame sets random seed elsewhere, demo playback sets seed just before us! P_SetRandSeed(M_RandomizedSeed()); // Use a more "Random" random seed //SRB2Kart - Score is literally the only thing you SHOULDN'T reset at all times @@ -4354,7 +4586,7 @@ void G_InitNew(UINT8 pencoremode, const char *mapname, boolean resetplayer, bool players[i].marescore = 0; - if (resetplayer) // SRB2Kart + if (resetplayer && !(multiplayer && demo.playback)) // SRB2Kart { players[i].score = 0; } @@ -4463,7 +4695,7 @@ char *G_BuildMapTitle(INT32 mapnum) // DEMO RECORDING // -#define DEMOVERSION 0x0001 +#define DEMOVERSION 0x0002 #define DEMOHEADER "\xF0" "KartReplay" "\x0F" #define DF_GHOST 0x01 // This demo contains ghost data too! @@ -4471,6 +4703,16 @@ char *G_BuildMapTitle(INT32 mapnum) #define DF_NIGHTSATTACK 0x04 // This demo is from NiGHTS attack and contains its time left, score, and mares! #define DF_ATTACKMASK 0x06 // This demo is from ??? attack and contains ??? #define DF_ATTACKSHIFT 1 +#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 #define ZT_FWD 0x01 @@ -4479,9 +4721,20 @@ char *G_BuildMapTitle(INT32 mapnum) #define ZT_BUTTONS 0x08 #define ZT_AIMING 0x10 #define ZT_DRIFT 0x20 +#define ZT_LATENCY 0x40 #define DEMOMARKER 0x80 // demoend -static ticcmd_t oldcmd; +UINT8 demo_extradata[MAXPLAYERS]; +UINT8 demo_writerng; // 0=no, 1=yes, 2=yes but on a timeout +static ticcmd_t oldcmd[MAXPLAYERS]; + +#define DW_END 0xFF // End of extradata block +#define DW_RNG 0xFE // Check RNG seed! + +#define DW_EXTRASTUFF 0xFE // Numbers below this are reserved for writing player slot data + +// Below consts are only used for demo extrainfo sections +#define DW_STANDING 0x00 // For Metal Sonic and time attack ghosts #define GZT_XYZ 0x01 @@ -4503,8 +4756,9 @@ static ticcmd_t oldcmd; #define EZT_SCALE 0x10 // Changed size #define EZT_HIT 0x20 // Damaged a mobj #define EZT_SPRITE 0x40 // Changed sprite set completely out of PLAY (NiGHTS, SOCs, whatever) +#define EZT_KART 0x80 // SRB2Kart: Changed current held item/quantity and bumpers for battle -static mobj_t oldmetal, oldghost; +static mobj_t oldmetal, oldghost[MAXPLAYERS]; void G_SaveMetal(UINT8 **buffer) { @@ -4542,37 +4796,280 @@ ticcmd_t *G_MoveTiccmd(ticcmd_t* dest, const ticcmd_t* src, const size_t n) return dest; } +// Finds a skin with the closest stats if the expected skin doesn't exist. +static INT32 GetSkinNumClosestToStats(UINT8 kartspeed, UINT8 kartweight) +{ + INT32 i, closest_skin = 0; + UINT8 closest_stats = UINT8_MAX, stat_diff; + + for (i = 0; i < numskins; i++) + { + stat_diff = abs(skins[i].kartspeed - kartspeed) + abs(skins[i].kartweight - kartweight); + if (stat_diff < closest_stats) + { + closest_stats = stat_diff; + closest_skin = i; + } + } + + return closest_skin; +} + +static void FindClosestSkinForStats(UINT32 p, UINT8 kartspeed, UINT8 kartweight) +{ + INT32 closest_skin = GetSkinNumClosestToStats(kartspeed, kartweight); + + //CONS_Printf("Using %s instead...\n", skins[closest_skin].name); + SetPlayerSkinByNum(p, closest_skin); +} + +void G_ReadDemoExtraData(void) +{ + INT32 p, extradata, i; + char name[17]; + + memset(name, '\0', 17); + + p = READUINT8(demo_p); + + while (p < DW_EXTRASTUFF) + { + extradata = READUINT8(demo_p); + + if (extradata & DXD_RESPAWN) + { + if (players[p].mo) + P_DamageMobj(players[p].mo, NULL, NULL, 10000); // Is this how this should work..? + } + if (extradata & DXD_SKIN) + { + UINT8 kartspeed, kartweight; + + // Skin + M_Memcpy(name, demo_p, 16); + demo_p += 16; + SetPlayerSkin(p, name); + + kartspeed = READUINT8(demo_p); + kartweight = READUINT8(demo_p); + + + if (stricmp(skins[players[p].skin].name, name) != 0) + FindClosestSkinForStats(p, kartspeed, kartweight); + + players[p].kartspeed = kartspeed; + players[p].kartweight = kartweight; + } + if (extradata & DXD_COLOR) + { + // Color + M_Memcpy(name, demo_p, 16); + demo_p += 16; + for (i = 0; i < MAXSKINCOLORS; i++) + if (!stricmp(KartColor_Names[i], name)) // SRB2kart + { + players[p].skincolor = i; + if (players[p].mo) + players[p].mo->color = i; + break; + } + } + if (extradata & DXD_NAME) + { + // Name + M_Memcpy(player_names[p],demo_p,16); + demo_p += 16; + } + if (extradata & DXD_PLAYSTATE) + { + extradata = READUINT8(demo_p); + + switch (extradata) { + case DXD_PST_PLAYING: + players[p].pflags |= PF_WANTSTOJOIN; // fuck you + break; + + case DXD_PST_SPECTATING: + players[p].pflags &= ~PF_WANTSTOJOIN; // double-fuck you + if (!playeringame[p]) + { + CL_ClearPlayer(p); + playeringame[p] = true; + G_AddPlayer(p); + players[p].spectator = true; + + // There's likely an off-by-one error in timing recording or playback of joins. This hacks around it so I don't have to find out where that is. \o/ + if (oldcmd[p].forwardmove) + P_RandomByte(); + } + else + { + players[p].spectator = true; + if (players[p].mo) + P_DamageMobj(players[p].mo, NULL, NULL, 10000); + else + players[p].playerstate = PST_REBORN; + } + break; + + case DXD_PST_LEFT: + CL_RemovePlayer(p, 0); + break; + } + + G_ResetViews(); + + // maybe these are necessary? + if (G_BattleGametype()) + K_CheckBumpers(); // SRB2Kart + else if (G_RaceGametype()) + P_CheckRacers(); // also SRB2Kart + } + + + p = READUINT8(demo_p); + } + + while (p != DW_END) + { + UINT32 rng; + + switch (p) + { + case DW_RNG: + rng = READUINT32(demo_p); + if (P_GetRandSeed() != rng) + { + P_SetRandSeed(rng); + + if (demosynced) + CONS_Alert(CONS_WARNING, M_GetText("Demo playback has desynced!\n")); + demosynced = false; + } + } + + p = READUINT8(demo_p); + } + + if (!(demoflags & DF_GHOST) && *demo_p == DEMOMARKER) + { + // end of demo data stream + G_CheckDemoStatus(); + return; + } +} + +void G_WriteDemoExtraData(void) +{ + INT32 i; + char name[16]; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (demo_extradata[i]) + { + WRITEUINT8(demo_p, i); + WRITEUINT8(demo_p, demo_extradata[i]); + + //if (demo_extradata[i] & DXD_RESPAWN) has no extra data + if (demo_extradata[i] & DXD_SKIN) + { + // Skin + memset(name, 0, 16); + strncpy(name, skins[players[i].skin].name, 16); + M_Memcpy(demo_p,name,16); + demo_p += 16; + + WRITEUINT8(demo_p, skins[players[i].skin].kartspeed); + WRITEUINT8(demo_p, skins[players[i].skin].kartweight); + } + if (demo_extradata[i] & DXD_COLOR) + { + // Color + memset(name, 0, 16); + strncpy(name, KartColor_Names[players[i].skincolor], 16); + M_Memcpy(demo_p,name,16); + demo_p += 16; + } + if (demo_extradata[i] & DXD_NAME) + { + // Name + memset(name, 0, 16); + strncpy(name, player_names[i], 16); + M_Memcpy(demo_p,name,16); + demo_p += 16; + } + if (demo_extradata[i] & DXD_PLAYSTATE) + { + demo_writerng = 1; + if (!playeringame[i]) + WRITEUINT8(demo_p, DXD_PST_LEFT); + else if ( + players[i].spectator && + !(players[i].pflags & PF_WANTSTOJOIN) // <= fuck you specifically + ) + WRITEUINT8(demo_p, DXD_PST_SPECTATING); + else + WRITEUINT8(demo_p, DXD_PST_PLAYING); + } + } + + demo_extradata[i] = 0; + } + + // May not be necessary, but might as well play it safe... + if ((leveltime & 255) == 128) + demo_writerng = 1; + + { + static UINT8 timeout = 0; + + if (timeout) timeout--; + + if (demo_writerng == 1 || (demo_writerng == 2 && timeout == 0)) + { + demo_writerng = 0; + timeout = 16; + WRITEUINT8(demo_p, DW_RNG); + WRITEUINT32(demo_p, P_GetRandSeed()); + } + } + + WRITEUINT8(demo_p, DW_END); +} + void G_ReadDemoTiccmd(ticcmd_t *cmd, INT32 playernum) { UINT8 ziptic; - (void)playernum; - if (!demo_p || !demo_start) + if (!demo_p || !demo.deferstart) return; ziptic = READUINT8(demo_p); if (ziptic & ZT_FWD) - oldcmd.forwardmove = READSINT8(demo_p); + oldcmd[playernum].forwardmove = READSINT8(demo_p); if (ziptic & ZT_SIDE) - oldcmd.sidemove = READSINT8(demo_p); + oldcmd[playernum].sidemove = READSINT8(demo_p); if (ziptic & ZT_ANGLE) - oldcmd.angleturn = READINT16(demo_p); + oldcmd[playernum].angleturn = READINT16(demo_p); if (ziptic & ZT_BUTTONS) - oldcmd.buttons = (oldcmd.buttons & (BT_FORWARD|BT_BACKWARD)) | (READUINT16(demo_p) & ~(BT_FORWARD|BT_BACKWARD)); + oldcmd[playernum].buttons = READUINT16(demo_p); if (ziptic & ZT_AIMING) - oldcmd.aiming = READINT16(demo_p); + oldcmd[playernum].aiming = READINT16(demo_p); if (ziptic & ZT_DRIFT) - oldcmd.driftturn = READINT16(demo_p); + oldcmd[playernum].driftturn = READINT16(demo_p); + if (ziptic & ZT_LATENCY) + oldcmd[playernum].latency = READUINT8(demo_p); - G_CopyTiccmd(cmd, &oldcmd, 1); + G_CopyTiccmd(cmd, &oldcmd[playernum], 1); // SRB2kart: Copy-pasted from ticcmd building, removes that crappy demo cam - if (((players[displayplayer].mo && players[displayplayer].speed > 0) // Moving + if (((players[displayplayers[0]].mo && players[displayplayers[0]].speed > 0) // Moving || (leveltime > starttime && (cmd->buttons & BT_ACCELERATE && cmd->buttons & BT_BRAKE)) // Rubber-burn turn - || (players[displayplayer].kartstuff[k_respawn]) // Respawning - || (players[displayplayer].spectator || objectplacing)) // Not a physical player - && !(players[displayplayer].kartstuff[k_spinouttimer] && players[displayplayer].kartstuff[k_sneakertimer])) // Spinning and boosting cancels out spinout - localangle += (cmd->angleturn<<16); + || (players[displayplayers[0]].kartstuff[k_respawn]) // Respawning + || (players[displayplayers[0]].spectator || objectplacing)) // Not a physical player + && !(players[displayplayers[0]].kartstuff[k_spinouttimer] && players[displayplayers[0]].kartstuff[k_sneakertimer])) // Spinning and boosting cancels out spinout + localangle[0] += (cmd->angleturn<<16); if (!(demoflags & DF_GHOST) && *demo_p == DEMOMARKER) { @@ -4586,54 +5083,60 @@ void G_WriteDemoTiccmd(ticcmd_t *cmd, INT32 playernum) { char ziptic = 0; UINT8 *ziptic_p; - (void)playernum; if (!demo_p) return; ziptic_p = demo_p++; // the ziptic, written at the end of this function - if (cmd->forwardmove != oldcmd.forwardmove) + if (cmd->forwardmove != oldcmd[playernum].forwardmove) { WRITEUINT8(demo_p,cmd->forwardmove); - oldcmd.forwardmove = cmd->forwardmove; + oldcmd[playernum].forwardmove = cmd->forwardmove; ziptic |= ZT_FWD; } - if (cmd->sidemove != oldcmd.sidemove) + if (cmd->sidemove != oldcmd[playernum].sidemove) { WRITEUINT8(demo_p,cmd->sidemove); - oldcmd.sidemove = cmd->sidemove; + oldcmd[playernum].sidemove = cmd->sidemove; ziptic |= ZT_SIDE; } - if (cmd->angleturn != oldcmd.angleturn) + if (cmd->angleturn != oldcmd[playernum].angleturn) { WRITEINT16(demo_p,cmd->angleturn); - oldcmd.angleturn = cmd->angleturn; + oldcmd[playernum].angleturn = cmd->angleturn; ziptic |= ZT_ANGLE; } - if (cmd->buttons != oldcmd.buttons) + if (cmd->buttons != oldcmd[playernum].buttons) { WRITEUINT16(demo_p,cmd->buttons); - oldcmd.buttons = cmd->buttons; + oldcmd[playernum].buttons = cmd->buttons; ziptic |= ZT_BUTTONS; } - if (cmd->aiming != oldcmd.aiming) + if (cmd->aiming != oldcmd[playernum].aiming) { WRITEINT16(demo_p,cmd->aiming); - oldcmd.aiming = cmd->aiming; + oldcmd[playernum].aiming = cmd->aiming; ziptic |= ZT_AIMING; } - if (cmd->driftturn != oldcmd.driftturn) + if (cmd->driftturn != oldcmd[playernum].driftturn) { WRITEINT16(demo_p,cmd->driftturn); - oldcmd.driftturn = cmd->driftturn; + oldcmd[playernum].driftturn = cmd->driftturn; ziptic |= ZT_DRIFT; } + if (cmd->latency != oldcmd[playernum].latency) + { + WRITEUINT8(demo_p,cmd->latency); + oldcmd[playernum].latency = cmd->latency; + ziptic |= ZT_LATENCY; + } + *ziptic_p = ziptic; // attention here for the ticcmd size! @@ -4645,71 +5148,93 @@ void G_WriteDemoTiccmd(ticcmd_t *cmd, INT32 playernum) } } -void G_GhostAddThok(void) +void G_GhostAddThok(INT32 playernum) { - if (!demorecording || !(demoflags & DF_GHOST)) + if (!demo.recording || !(demoflags & DF_GHOST)) return; - ghostext.flags = (ghostext.flags & ~EZT_THOKMASK) | EZT_THOK; + ghostext[playernum].flags = (ghostext[playernum].flags & ~EZT_THOKMASK) | EZT_THOK; } -void G_GhostAddSpin(void) +void G_GhostAddSpin(INT32 playernum) { - if (!demorecording || !(demoflags & DF_GHOST)) + if (!demo.recording || !(demoflags & DF_GHOST)) return; - ghostext.flags = (ghostext.flags & ~EZT_THOKMASK) | EZT_SPIN; + ghostext[playernum].flags = (ghostext[playernum].flags & ~EZT_THOKMASK) | EZT_SPIN; } -void G_GhostAddRev(void) +void G_GhostAddRev(INT32 playernum) { - if (!demorecording || !(demoflags & DF_GHOST)) + if (!demo.recording || !(demoflags & DF_GHOST)) return; - ghostext.flags = (ghostext.flags & ~EZT_THOKMASK) | EZT_REV; + ghostext[playernum].flags = (ghostext[playernum].flags & ~EZT_THOKMASK) | EZT_REV; } -void G_GhostAddFlip(void) +void G_GhostAddFlip(INT32 playernum) { - if (!demorecording || !(demoflags & DF_GHOST)) + if (!demo.recording || !(demoflags & DF_GHOST)) return; - ghostext.flags |= EZT_FLIP; + ghostext[playernum].flags |= EZT_FLIP; } -void G_GhostAddColor(ghostcolor_t color) +void G_GhostAddColor(INT32 playernum, ghostcolor_t color) { - if (!demorecording || !(demoflags & DF_GHOST)) + if (!demo.recording || !(demoflags & DF_GHOST)) return; - if (ghostext.lastcolor == (UINT8)color) + if (ghostext[playernum].lastcolor == (UINT8)color) { - ghostext.flags &= ~EZT_COLOR; + ghostext[playernum].flags &= ~EZT_COLOR; return; } - ghostext.flags |= EZT_COLOR; - ghostext.color = (UINT8)color; + ghostext[playernum].flags |= EZT_COLOR; + ghostext[playernum].color = (UINT8)color; } -void G_GhostAddScale(fixed_t scale) +void G_GhostAddScale(INT32 playernum, fixed_t scale) { - if (!demorecording || !(demoflags & DF_GHOST)) + if (!demo.recording || !(demoflags & DF_GHOST)) return; - if (ghostext.lastscale == scale) + if (ghostext[playernum].lastscale == scale) { - ghostext.flags &= ~EZT_SCALE; + ghostext[playernum].flags &= ~EZT_SCALE; return; } - ghostext.flags |= EZT_SCALE; - ghostext.scale = scale; + ghostext[playernum].flags |= EZT_SCALE; + ghostext[playernum].scale = scale; } -void G_GhostAddHit(mobj_t *victim) +void G_GhostAddHit(INT32 playernum, mobj_t *victim) { - if (!demorecording || !(demoflags & DF_GHOST)) + if (!demo.recording || !(demoflags & DF_GHOST)) return; - ghostext.flags |= EZT_HIT; - ghostext.hits++; - ghostext.hitlist = Z_Realloc(ghostext.hitlist, ghostext.hits * sizeof(mobj_t *), PU_LEVEL, NULL); - ghostext.hitlist[ghostext.hits-1] = victim; + ghostext[playernum].flags |= EZT_HIT; + ghostext[playernum].hits++; + ghostext[playernum].hitlist = Z_Realloc(ghostext[playernum].hitlist, ghostext[playernum].hits * sizeof(mobj_t *), PU_LEVEL, NULL); + ghostext[playernum].hitlist[ghostext[playernum].hits-1] = victim; } -void G_WriteGhostTic(mobj_t *ghost) +void G_WriteAllGhostTics(void) +{ + INT32 i, counter = leveltime; + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + continue; + + if (!players[i].mo) + continue; + + counter++; + + if (counter % cv_netdemosyncquality.value != 0) // Only write 1 in this many ghost datas per tic to cut down on multiplayer replay size. + continue; + + WRITEUINT8(demo_p, i); + G_WriteGhostTic(players[i].mo, i); + } + WRITEUINT8(demo_p, 0xFF); +} + +void G_WriteGhostTic(mobj_t *ghost, INT32 playernum) { char ziptic = 0; UINT8 *ziptic_p; @@ -4731,41 +5256,41 @@ void G_WriteGhostTic(mobj_t *ghost) ziptic_p = demo_p++; // the ziptic, written at the end of this function - #define MAXMOM (0xFFFF<<8) + #define MAXMOM (0x7FFF<<8) // GZT_XYZ is only useful if you've moved 256 FRACUNITS or more in a single tic. - if (abs(ghost->x-oldghost.x) > MAXMOM - || abs(ghost->y-oldghost.y) > MAXMOM - || abs(ghost->z-oldghost.z) > MAXMOM - || (leveltime & 255) == 1) // Hack to enable slightly nicer resyncing + if (abs(ghost->x-oldghost[playernum].x) > MAXMOM + || abs(ghost->y-oldghost[playernum].y) > MAXMOM + || abs(ghost->z-oldghost[playernum].z) > MAXMOM + || ((UINT8)(leveltime & 255) > 0 && (UINT8)(leveltime & 255) <= (UINT8)cv_netdemosyncquality.value)) // Hack to enable slightly nicer resyncing { - oldghost.x = ghost->x; - oldghost.y = ghost->y; - oldghost.z = ghost->z; + oldghost[playernum].x = ghost->x; + oldghost[playernum].y = ghost->y; + oldghost[playernum].z = ghost->z; ziptic |= GZT_XYZ; - WRITEFIXED(demo_p,oldghost.x); - WRITEFIXED(demo_p,oldghost.y); - WRITEFIXED(demo_p,oldghost.z); + WRITEFIXED(demo_p,oldghost[playernum].x); + WRITEFIXED(demo_p,oldghost[playernum].y); + WRITEFIXED(demo_p,oldghost[playernum].z); } else { // For moving normally: // Store one full byte of movement, plus one byte of fractional movement. - INT16 momx = (INT16)((ghost->x-oldghost.x + (1<<4))>>8); - INT16 momy = (INT16)((ghost->y-oldghost.y + (1<<4))>>8); - if (momx != oldghost.momx - || momy != oldghost.momy) + INT16 momx = (INT16)((ghost->x-oldghost[playernum].x + (1<<4))>>8); + INT16 momy = (INT16)((ghost->y-oldghost[playernum].y + (1<<4))>>8); + if (momx != oldghost[playernum].momx + || momy != oldghost[playernum].momy) { - oldghost.momx = momx; - oldghost.momy = momy; + oldghost[playernum].momx = momx; + oldghost[playernum].momy = momy; ziptic |= GZT_MOMXY; WRITEINT16(demo_p,momx); WRITEINT16(demo_p,momy); } - momx = (INT16)((ghost->z-oldghost.z + (1<<4))>>8); - if (momx != oldghost.momz) + momx = (INT16)((ghost->z-oldghost[playernum].z + (1<<4))>>8); + if (momx != oldghost[playernum].momz) { - oldghost.momz = momx; + oldghost[playernum].momz = momx; ziptic |= GZT_MOMZ; WRITEINT16(demo_p,momx); } @@ -4773,9 +5298,9 @@ void G_WriteGhostTic(mobj_t *ghost) // This SHOULD set oldghost.x/y/z to match ghost->x/y/z // but it keeps the fractional loss of one byte, // so it will hopefully be made up for in future tics. - oldghost.x += oldghost.momx<<8; - oldghost.y += oldghost.momy<<8; - oldghost.z += oldghost.momz<<8; + oldghost[playernum].x += oldghost[playernum].momx<<8; + oldghost[playernum].y += oldghost[playernum].momy<<8; + oldghost[playernum].z += oldghost[playernum].momz<<8; } #undef MAXMOM @@ -4783,56 +5308,72 @@ void G_WriteGhostTic(mobj_t *ghost) // Only store the 8 most relevant bits of angle // because exact values aren't too easy to discern to begin with when only 8 angles have different sprites // and it does not affect this mode of movement at all anyway. - if (ghost->angle>>24 != oldghost.angle) + if (ghost->angle>>24 != oldghost[playernum].angle) { - oldghost.angle = ghost->angle>>24; + oldghost[playernum].angle = ghost->angle>>24; ziptic |= GZT_ANGLE; - WRITEUINT8(demo_p,oldghost.angle); + WRITEUINT8(demo_p,oldghost[playernum].angle); } // Store the sprite frame. frame = ghost->frame & 0xFF; - if (frame != oldghost.frame) + if (frame != oldghost[playernum].frame) { - oldghost.frame = frame; + oldghost[playernum].frame = frame; ziptic |= GZT_SPRITE; - WRITEUINT8(demo_p,oldghost.frame); + WRITEUINT8(demo_p,oldghost[playernum].frame); } // Check for sprite set changes sprite = ghost->sprite; - if (sprite != oldghost.sprite) + if (sprite != oldghost[playernum].sprite) { - oldghost.sprite = sprite; - ghostext.flags |= EZT_SPRITE; + oldghost[playernum].sprite = sprite; + ghostext[playernum].flags |= EZT_SPRITE; } - if (ghostext.flags) + if (ghost->player) + { + if ( + ghostext[playernum].kartitem != ghost->player->kartstuff[k_itemtype] || + ghostext[playernum].kartamount != ghost->player->kartstuff[k_itemamount] || + ghostext[playernum].kartbumpers != ghost->player->kartstuff[k_bumper] + ) + { + ghostext[playernum].flags |= EZT_KART; + ghostext[playernum].kartitem = ghost->player->kartstuff[k_itemtype]; + ghostext[playernum].kartamount = ghost->player->kartstuff[k_itemamount]; + ghostext[playernum].kartbumpers = ghost->player->kartstuff[k_bumper]; + + } + } + + if (ghostext[playernum].color == ghostext[playernum].lastcolor) + ghostext[playernum].flags &= ~EZT_COLOR; + if (ghostext[playernum].scale == ghostext[playernum].lastscale) + ghostext[playernum].flags &= ~EZT_SCALE; + + if (ghostext[playernum].flags) { ziptic |= GZT_EXTRA; + WRITEUINT8(demo_p,ghostext[playernum].flags); - if (ghostext.color == ghostext.lastcolor) - ghostext.flags &= ~EZT_COLOR; - if (ghostext.scale == ghostext.lastscale) - ghostext.flags &= ~EZT_SCALE; - - WRITEUINT8(demo_p,ghostext.flags); - if (ghostext.flags & EZT_COLOR) + if (ghostext[playernum].flags & EZT_COLOR) { - WRITEUINT8(demo_p,ghostext.color); - ghostext.lastcolor = ghostext.color; + WRITEUINT8(demo_p,ghostext[playernum].color); + ghostext[playernum].lastcolor = ghostext[playernum].color; } - if (ghostext.flags & EZT_SCALE) + if (ghostext[playernum].flags & EZT_SCALE) { - WRITEFIXED(demo_p,ghostext.scale); - ghostext.lastscale = ghostext.scale; + WRITEFIXED(demo_p,ghostext[playernum].scale); + ghostext[playernum].lastscale = ghostext[playernum].scale; } - if (ghostext.flags & EZT_HIT) + if (ghostext[playernum].flags & EZT_HIT) { - WRITEUINT16(demo_p,ghostext.hits); - for (i = 0; i < ghostext.hits; i++) + WRITEUINT16(demo_p,ghostext[playernum].hits); + for (i = 0; i < ghostext[playernum].hits; i++) { - mobj_t *mo = ghostext.hitlist[i]; + mobj_t *mo = ghostext[playernum].hitlist[i]; WRITEUINT32(demo_p,UINT32_MAX); // reserved for some method of determining exactly which mobj this is. (mobjnum doesn't work here.) WRITEUINT32(demo_p,mo->type); WRITEUINT16(demo_p,(UINT16)mo->health); @@ -4841,13 +5382,19 @@ void G_WriteGhostTic(mobj_t *ghost) WRITEFIXED(demo_p,mo->z); WRITEANGLE(demo_p,mo->angle); } - Z_Free(ghostext.hitlist); - ghostext.hits = 0; - ghostext.hitlist = NULL; + Z_Free(ghostext[playernum].hitlist); + ghostext[playernum].hits = 0; + ghostext[playernum].hitlist = NULL; } - if (ghostext.flags & EZT_SPRITE) + if (ghostext[playernum].flags & EZT_SPRITE) WRITEUINT8(demo_p,sprite); - ghostext.flags = 0; + if (ghostext[playernum].flags & EZT_KART) + { + WRITEINT32(demo_p, ghostext[playernum].kartitem); + WRITEINT32(demo_p, ghostext[playernum].kartamount); + WRITEINT32(demo_p, ghostext[playernum].kartbumpers); + } + ghostext[playernum].flags = 0; } *ziptic_p = ziptic; @@ -4861,9 +5408,27 @@ void G_WriteGhostTic(mobj_t *ghost) } } +void G_ConsAllGhostTics(void) +{ + UINT8 p = READUINT8(demo_p); + + while (p != 0xFF) + { + G_ConsGhostTic(p); + p = READUINT8(demo_p); + } + + if (*demo_p == DEMOMARKER) + { + // end of demo data stream + G_CheckDemoStatus(); + return; + } +} + // Uses ghost data to do consistency checks on your position. // This fixes desynchronising demos when fighting eggman. -void G_ConsGhostTic(void) +void G_ConsGhostTic(INT32 playernum) { UINT8 ziptic; fixed_t px,py,pz,gx,gy,gz; @@ -4871,34 +5436,34 @@ void G_ConsGhostTic(void) fixed_t syncleeway; boolean nightsfail = false; - if (!demo_p || !demo_start) + if (!demo_p || !demo.deferstart) return; if (!(demoflags & DF_GHOST)) return; // No ghost data to use. - testmo = players[0].mo; + testmo = players[playernum].mo; // Grab ghost data. ziptic = READUINT8(demo_p); if (ziptic & GZT_XYZ) { - oldghost.x = READFIXED(demo_p); - oldghost.y = READFIXED(demo_p); - oldghost.z = READFIXED(demo_p); + oldghost[playernum].x = READFIXED(demo_p); + oldghost[playernum].y = READFIXED(demo_p); + oldghost[playernum].z = READFIXED(demo_p); syncleeway = 0; } else { if (ziptic & GZT_MOMXY) { - oldghost.momx = READINT16(demo_p)<<8; - oldghost.momy = READINT16(demo_p)<<8; + oldghost[playernum].momx = READINT16(demo_p)<<8; + oldghost[playernum].momy = READINT16(demo_p)<<8; } if (ziptic & GZT_MOMZ) - oldghost.momz = READINT16(demo_p)<<8; - oldghost.x += oldghost.momx; - oldghost.y += oldghost.momy; - oldghost.z += oldghost.momz; + oldghost[playernum].momz = READINT16(demo_p)<<8; + oldghost[playernum].x += oldghost[playernum].momx; + oldghost[playernum].y += oldghost[playernum].momy; + oldghost[playernum].z += oldghost[playernum].momz; syncleeway = FRACUNIT; } if (ziptic & GZT_ANGLE) @@ -4906,7 +5471,7 @@ void G_ConsGhostTic(void) if (ziptic & GZT_SPRITE) demo_p++; if(ziptic & GZT_NIGHTS) { - if (!testmo->player || !(testmo->player->pflags & PF_NIGHTSMODE) || !testmo->tracer) + if (!testmo || !testmo->player || !(testmo->player->pflags & PF_NIGHTSMODE) || !testmo->tracer) nightsfail = true; else testmo = testmo->tracer; @@ -4962,27 +5527,68 @@ void G_ConsGhostTic(void) } if (ziptic & EZT_SPRITE) demo_p++; + if (ziptic & EZT_KART) + { + ghostext[playernum].kartitem = READINT32(demo_p); + ghostext[playernum].kartamount = READINT32(demo_p); + ghostext[playernum].kartbumpers = READINT32(demo_p); + } } - // Re-synchronise - px = testmo->x; - py = testmo->y; - pz = testmo->z; - gx = oldghost.x; - gy = oldghost.y; - gz = oldghost.z; - - if (nightsfail || abs(px-gx) > syncleeway || abs(py-gy) > syncleeway || abs(pz-gz) > syncleeway) + if (testmo) { - if (demosynced) - CONS_Alert(CONS_WARNING, M_GetText("Demo playback has desynced!\n")); - demosynced = false; + // Re-synchronise + px = testmo->x; + py = testmo->y; + pz = testmo->z; + gx = oldghost[playernum].x; + gy = oldghost[playernum].y; + gz = oldghost[playernum].z; - P_UnsetThingPosition(testmo); - testmo->x = oldghost.x; - testmo->y = oldghost.y; - P_SetThingPosition(testmo); - testmo->z = oldghost.z; + if (nightsfail || abs(px-gx) > syncleeway || abs(py-gy) > syncleeway || abs(pz-gz) > syncleeway) + { + ghostext[playernum].desyncframes++; + + if (ghostext[playernum].desyncframes >= 2) + { + if (demosynced) + CONS_Alert(CONS_WARNING, M_GetText("Demo playback has desynced!\n")); + demosynced = false; + + P_UnsetThingPosition(testmo); + testmo->x = oldghost[playernum].x; + testmo->y = oldghost[playernum].y; + P_SetThingPosition(testmo); + testmo->z = oldghost[playernum].z; + + if (abs(testmo->z - testmo->floorz) < 4*FRACUNIT) + testmo->z = testmo->floorz; // Sync players to the ground when they're likely supposed to be there... + + ghostext[playernum].desyncframes = 2; + } + } + 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 (demosynced) + CONS_Alert(CONS_WARNING, M_GetText("Demo playback has desynced!\n")); + demosynced = false; + + 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 (*demo_p == DEMOMARKER) @@ -5000,6 +5606,38 @@ 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 + { + ziptic = READUINT8(g->p); + if (ziptic & DXD_SKIN) + g->p += 18; // We _could_ read this info, but it shouldn't change anything in record attack... + if (ziptic & DXD_COLOR) + g->p += 16; // Same tbh + if (ziptic & DXD_NAME) + g->p += 16; // yea + if (ziptic & DXD_PLAYSTATE && READUINT8(g->p) != DXD_PST_PLAYING) + I_Error("Ghost is not a record attack ghost"); //@TODO lmao don't blow up like this + } + else if (ziptic == DW_RNG) + g->p += 4; // RNG seed + else + I_Error("Ghost is not a record attack ghost"); //@TODO lmao don't blow up like this + + ziptic = READUINT8(g->p); + } + + ziptic = READUINT8(g->p); // Back to actual ziptic stuff +#ifdef DEMO_COMPAT_100 + } +#endif + if (ziptic & ZT_FWD) g->p++; if (ziptic & ZT_SIDE) @@ -5012,9 +5650,24 @@ void G_GhostTicker(void) g->p += 2; if (ziptic & ZT_DRIFT) g->p += 2; + if (ziptic & ZT_LATENCY) + g->p += 1; // 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); @@ -5151,8 +5804,21 @@ void G_GhostTicker(void) } if (ziptic & EZT_SPRITE) g->mo->sprite = READUINT8(g->p); + if (ziptic & EZT_KART) + 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) switch(g->color) { @@ -5182,6 +5848,179 @@ void G_GhostTicker(void) } } +// Demo rewinding functions +typedef struct rewindinfo_s { + tic_t leveltime; + + struct { + boolean ingame; + player_t player; + mobj_t mobj; + } playerinfo[MAXPLAYERS]; + + struct rewindinfo_s *prev; +} rewindinfo_t; + +static tic_t currentrewindnum; +static rewindinfo_t *rewindhead = NULL; // Reverse chronological order + +void G_InitDemoRewind(void) +{ + while (rewindhead) + { + rewindinfo_t *p = rewindhead->prev; + Z_Free(rewindhead); + rewindhead = p; + } + + currentrewindnum = 0; +} + +void G_StoreRewindInfo(void) +{ + static UINT8 timetolog = 8; + rewindinfo_t *info; + size_t i; + + if (timetolog-- > 0) + return; + timetolog = 8; + + info = Z_Calloc(sizeof(rewindinfo_t), PU_STATIC, NULL); + + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + { + info->playerinfo[i].ingame = false; + continue; + } + + info->playerinfo[i].ingame = true; + memcpy(&info->playerinfo[i].player, &players[i], sizeof(player_t)); + if (players[i].mo) + memcpy(&info->playerinfo[i].mobj, players[i].mo, sizeof(mobj_t)); + } + + info->leveltime = leveltime; + info->prev = rewindhead; + rewindhead = info; +} + +void G_PreviewRewind(tic_t previewtime) +{ + size_t i, j; + fixed_t tweenvalue = 0; + rewindinfo_t *info = rewindhead, *next_info = rewindhead; + + if (!info) + return; + + while (info->leveltime > previewtime && info->prev) + { + next_info = info; + info = info->prev; + } + if (info != next_info) + tweenvalue = FixedDiv(previewtime - info->leveltime, next_info->leveltime - info->leveltime); + + + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + { + if (info->playerinfo[i].player.mo) + { + //@TODO spawn temp object to act as a player display + } + + continue; + } + + if (!info->playerinfo[i].ingame || !info->playerinfo[i].player.mo) + { + if (players[i].mo) + players[i].mo->flags2 |= MF2_DONTDRAW; + + continue; + } + + if (!players[i].mo) + continue; //@TODO spawn temp object to act as a player display + + players[i].mo->flags2 &= ~MF2_DONTDRAW; + + P_UnsetThingPosition(players[i].mo); +#define TWEEN(pr) info->playerinfo[i].mobj.pr + FixedMul((INT32) (next_info->playerinfo[i].mobj.pr - info->playerinfo[i].mobj.pr), tweenvalue) + players[i].mo->x = TWEEN(x); + players[i].mo->y = TWEEN(y); + players[i].mo->z = TWEEN(z); + players[i].mo->angle = TWEEN(angle); +#undef TWEEN + P_SetThingPosition(players[i].mo); + + players[i].frameangle = info->playerinfo[i].player.frameangle + FixedMul((INT32) (next_info->playerinfo[i].player.frameangle - info->playerinfo[i].player.frameangle), tweenvalue); + + players[i].mo->sprite = info->playerinfo[i].mobj.sprite; + players[i].mo->frame = info->playerinfo[i].mobj.frame; + + players[i].realtime = info->playerinfo[i].player.realtime; + for (j = 0; j < NUMKARTSTUFF; j++) + players[i].kartstuff[j] = info->playerinfo[i].player.kartstuff[j]; + } + + for (i = splitscreen+1; i > 0; i--) + P_ResetCamera(&players[displayplayers[i]], &camera[i]); +} + +void G_ConfirmRewind(tic_t rewindtime) +{ + tic_t i; + boolean oldmenuactive = menuactive, oldsounddisabled = sound_disabled; + + INT32 olddp1 = displayplayers[0], olddp2 = displayplayers[1], olddp3 = displayplayers[2], olddp4 = displayplayers[3]; + UINT8 oldss = splitscreen; + + menuactive = false; // Prevent loops + + CV_StealthSetValue(&cv_renderview, 0); + + if (rewindtime > starttime) + { + sound_disabled = true; // Prevent sound spam + demo.rewinding = true; + } + else + demo.rewinding = false; + + G_DoPlayDemo(NULL); // Restart the current demo + + for (i = 0; i < rewindtime && leveltime < rewindtime; i++) + { + //TryRunTics(1); + G_Ticker((i % NEWTICRATERATIO) == 0); + } + + demo.rewinding = false; + menuactive = oldmenuactive; // Bring the menu back up + sound_disabled = oldsounddisabled; // Re-enable SFX + + wipegamestate = gamestate; // No fading back in! + + COM_BufInsertText("renderview on\n"); + + splitscreen = oldss; + displayplayers[0] = olddp1; + displayplayers[1] = olddp2; + displayplayers[2] = olddp3; + displayplayers[3] = olddp4; + R_ExecuteSetViewSize(); + G_ResetViews(); + + for (i = splitscreen+1; i > 0; i--) + P_ResetCamera(&players[displayplayers[i]], &camera[i]); +} + void G_ReadMetalTic(mobj_t *metal) { UINT8 ziptic; @@ -5396,9 +6235,12 @@ void G_RecordDemo(const char *name) { INT32 maxsize; + CONS_Printf("Recording demo %s.lmp\n", name); + strcpy(demoname, name); strcat(demoname, ".lmp"); - maxsize = 1024*1024; + //@TODO make a maxdemosize cvar + maxsize = 1024*1024*2; if (M_CheckParm("-maxdemo") && M_IsNextParm()) maxsize = atoi(M_GetNextParm()) * 1024; // if (demobuffer) @@ -5407,7 +6249,7 @@ void G_RecordDemo(const char *name) demobuffer = malloc(maxsize); demoend = demobuffer + maxsize; - demorecording = true; + demo.recording = true; } void G_RecordMetal(void) @@ -5424,16 +6266,23 @@ void G_RecordMetal(void) void G_BeginRecording(void) { - UINT8 i; + UINT8 i, p; char name[16]; player_t *player = &players[consoleplayer]; + char *filename; + UINT8 totalfiles; + UINT8 *m; + if (demo_p) return; memset(name,0,sizeof(name)); demo_p = demobuffer; - demoflags = DF_GHOST|(modeattacking<important) + { + nameonly(( filename = va("%s", wadfiles[i]->filename) )); + WRITESTRINGN(demo_p, filename, 64); + WRITEMEM(demo_p, wadfiles[i]->md5sum, 16); + + totalfiles++; + } + + WRITEUINT8(m, totalfiles); + switch ((demoflags & DF_ATTACKMASK)>>DF_ATTACKSHIFT) { case ATTACKING_NONE: // 0 @@ -5470,66 +6342,69 @@ void G_BeginRecording(void) WRITEUINT32(demo_p,P_GetInitSeed()); - // Name - for (i = 0; i < 16 && cv_playername.string[i]; i++) - name[i] = cv_playername.string[i]; - for (; i < 16; i++) - name[i] = '\0'; - M_Memcpy(demo_p,name,16); - demo_p += 16; + // Reserved for extrainfo location from start of file + demoinfo_p = demo_p; + WRITEUINT32(demo_p, 0); - // Skin - for (i = 0; i < 16 && cv_skin.string[i]; i++) - name[i] = cv_skin.string[i]; - for (; i < 16; i++) - name[i] = '\0'; - M_Memcpy(demo_p,name,16); - demo_p += 16; + // Save netvars + CV_SaveNetVars(&demo_p, true); - // Color - for (i = 0; i < 16 && cv_playercolor.string[i]; i++) - name[i] = cv_playercolor.string[i]; - for (; i < 16; i++) - name[i] = '\0'; - M_Memcpy(demo_p,name,16); - demo_p += 16; + // Now store some info for each in-game player + for (p = 0; p < MAXPLAYERS; p++) { + if (playeringame[p]) { + player = &players[p]; - // Stats - demo_p++; // charability - demo_p++; // charability2 - demo_p++; // actionspd - demo_p++; // mindash - demo_p++; // maxdash - // SRB2Kart - WRITEUINT8(demo_p,player->kartspeed); - WRITEUINT8(demo_p,player->kartweight); - // - demo_p++; // normalspeed - demo_p++; // runspeed - demo_p++; // thrustfactor - demo_p++; // accelstart - demo_p++; // acceleration - demo_p += 4; // jumpfactor + WRITEUINT8(demo_p, p | (player->spectator ? DEMO_SPECTATOR : 0)); - // Save netvar data (SONICCD, etc) - CV_SaveNetVars(&demo_p); + // Name + memset(name, 0, 16); + strncpy(name, player_names[p], 16); + M_Memcpy(demo_p,name,16); + demo_p += 16; + + // Skin + memset(name, 0, 16); + strncpy(name, skins[player->skin].name, 16); + M_Memcpy(demo_p,name,16); + demo_p += 16; + + // Color + memset(name, 0, 16); + strncpy(name, KartColor_Names[player->skincolor], 16); + M_Memcpy(demo_p,name,16); + demo_p += 16; + + // Score, since Kart uses this to determine where you start on the map + WRITEUINT32(demo_p, player->score); + + // Kart speed and weight + WRITEUINT8(demo_p, skins[player->skin].kartspeed); + WRITEUINT8(demo_p, skins[player->skin].kartweight); + } + } + + WRITEUINT8(demo_p, 0xFF); // Denote the end of the player listing memset(&oldcmd,0,sizeof(oldcmd)); memset(&oldghost,0,sizeof(oldghost)); memset(&ghostext,0,sizeof(ghostext)); - ghostext.lastcolor = ghostext.color = GHC_NORMAL; - ghostext.lastscale = ghostext.scale = FRACUNIT; - if (player->mo) + for (i = 0; i < MAXPLAYERS; i++) { - oldghost.x = player->mo->x; - oldghost.y = player->mo->y; - oldghost.z = player->mo->z; - oldghost.angle = player->mo->angle; + ghostext[i].lastcolor = ghostext[i].color = GHC_NORMAL; + ghostext[i].lastscale = ghostext[i].scale = FRACUNIT; - // preticker started us gravity flipped - if (player->mo->eflags & MFE_VERTICALFLIP) - ghostext.flags |= EZT_FLIP; + if (players[i].mo) + { + oldghost[i].x = players[i].mo->x; + oldghost[i].y = players[i].mo->y; + oldghost[i].z = players[i].mo->z; + oldghost[i].angle = players[i].mo->angle; + + // preticker started us gravity flipped + if (players[i].mo->eflags & MFE_VERTICALFLIP) + ghostext[i].flags |= EZT_FLIP; + } } } @@ -5561,9 +6436,44 @@ void G_BeginMetal(void) oldmetal.angle = mo->angle; } +void G_WriteStanding(UINT8 ranking, char *name, INT32 skinnum, UINT8 color, UINT32 val) +{ + char temp[16]; + + if (demoinfo_p && (UINT32)(*demoinfo_p) == 0) + { + WRITEUINT8(demo_p, DEMOMARKER); // add the demo end marker + WRITEUINT32(demoinfo_p, demo_p - demobuffer); + } + + WRITEUINT8(demo_p, DW_STANDING); + WRITEUINT8(demo_p, ranking); + + // Name + memset(temp, 0, 16); + strncpy(temp, name, 16); + M_Memcpy(demo_p,temp,16); + demo_p += 16; + + // Skin + memset(temp, 0, 16); + strncpy(temp, skins[skinnum].name, 16); + M_Memcpy(demo_p,temp,16); + demo_p += 16; + + // Color + memset(temp, 0, 16); + strncpy(temp, KartColor_Names[color], 16); + M_Memcpy(demo_p,temp,16); + demo_p += 16; + + // Score/time/whatever + WRITEUINT32(demo_p, val); +} + void G_SetDemoTime(UINT32 ptime, UINT32 plap) { - if (!demorecording || !demotime_p) + if (!demo.recording || !demotime_p) return; if (demoflags & DF_RECORDATTACK) { @@ -5579,6 +6489,165 @@ void G_SetDemoTime(UINT32 ptime, UINT32 plap) }*/ } +static void G_LoadDemoExtraFiles(UINT8 **pp) +{ + UINT8 totalfiles; + char filename[MAX_WADPATH]; + UINT8 md5sum[16]; + filestatus_t ncs; + boolean toomany = false; + boolean alreadyloaded; + UINT8 i, j; + + totalfiles = READUINT8((*pp)); + for (i = 0; i < totalfiles; ++i) + { + if (toomany) + SKIPSTRING((*pp)); + else + { + strlcpy(filename, (char *)(*pp), sizeof filename); + SKIPSTRING((*pp)); + } + READMEM((*pp), md5sum, 16); + + if (!toomany) + { + alreadyloaded = false; + + for (j = 0; j < numwadfiles; ++j) + { + if (memcmp(md5sum, wadfiles[j]->md5sum, 16) == 0) + { + alreadyloaded = true; + break; + } + } + + if (alreadyloaded) + continue; + + if (numwadfiles >= MAX_WADFILES) + toomany = true; + else + ncs = findfile(filename, md5sum, false); + + if (toomany) + { + CONS_Alert(CONS_WARNING, M_GetText("Too many files loaded to add anymore for demo playback\n")); + if (!CON_Ready()) + M_StartMessage(M_GetText("There are too many files loaded to add this demo's add-ons.\n\nDemo playback may desync.\n\nPress ESC\n"), NULL, MM_NOTHING); + } + else if (ncs != FS_FOUND) + { + if (ncs == FS_NOTFOUND) + CONS_Alert(CONS_NOTICE, M_GetText("You do not have a copy of %s\n"), filename); + else if (ncs == FS_MD5SUMBAD) + CONS_Alert(CONS_NOTICE, M_GetText("Checksum mismatch on %s\n"), filename); + else + CONS_Alert(CONS_NOTICE, M_GetText("Unknown error finding file %s\n"), filename); + + if (!CON_Ready()) + M_StartMessage(M_GetText("There were errors trying to add this demo's add-ons. Check the console for more information.\n\nDemo playback may desync.\n\nPress ESC\n"), NULL, MM_NOTHING); + } + else + { + P_AddWadFile(filename); + } + } + } +} + +static void G_SkipDemoExtraFiles(UINT8 **pp) +{ + UINT8 totalfiles; + UINT8 i; + + totalfiles = READUINT8((*pp)); + for (i = 0; i < totalfiles; ++i) + { + SKIPSTRING((*pp));// file name + (*pp) += 16;// md5 + } +} + +// G_CheckDemoExtraFiles: checks if our loaded WAD list matches the demo's. +// Enabling quick prevents filesystem checks to see if needed files are available to load. +static UINT8 G_CheckDemoExtraFiles(UINT8 **pp, boolean quick) +{ + UINT8 totalfiles, filesloaded, nmusfilecount; + char filename[MAX_WADPATH]; + UINT8 md5sum[16]; + boolean toomany = false; + boolean alreadyloaded; + UINT8 i, j; + UINT8 error = 0; + + totalfiles = READUINT8((*pp)); + filesloaded = 0; + for (i = 0; i < totalfiles; ++i) + { + if (toomany) + SKIPSTRING((*pp)); + else + { + strlcpy(filename, (char *)(*pp), sizeof filename); + SKIPSTRING((*pp)); + } + READMEM((*pp), md5sum, 16); + + if (!toomany) + { + alreadyloaded = false; + nmusfilecount = 0; + + for (j = 0; j < numwadfiles; ++j) + { + if (wadfiles[j]->important && j > mainwads) + nmusfilecount++; + else + continue; + + if (memcmp(md5sum, wadfiles[j]->md5sum, 16) == 0) + { + alreadyloaded = true; + + if (i != nmusfilecount-1 && error < DFILE_ERROR_OUTOFORDER) + error |= DFILE_ERROR_OUTOFORDER; + + break; + } + } + + if (alreadyloaded) + { + filesloaded++; + continue; + } + + if (numwadfiles >= MAX_WADFILES) + error = DFILE_ERROR_CANNOTLOAD; + else if (!quick && findfile(filename, md5sum, false) != FS_FOUND) + error = DFILE_ERROR_CANNOTLOAD; + else if (error < DFILE_ERROR_INCOMPLETEOUTOFORDER) + error |= DFILE_ERROR_NOTLOADED; + } else + error = DFILE_ERROR_CANNOTLOAD; + } + + // Get final file count + nmusfilecount = 0; + + for (j = 0; j < numwadfiles; ++j) + if (wadfiles[j]->important && j > mainwads) + nmusfilecount++; + + if (!error && filesloaded < nmusfilecount) + error = DFILE_ERROR_EXTRAFILES; + + return error; +} + // Returns bitfield: // 1 == new demo has lower time // 2 == new demo has higher score @@ -5609,12 +6678,15 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname) I_Assert(c == SUBVERSION); s = READUINT16(p); I_Assert(s == DEMOVERSION); + p += 64; // full demo title p += 16; // demo checksum I_Assert(!memcmp(p, "PLAY", 4)); p += 4; // PLAY p += 2; // gamemap p += 16; // map md5 flags = READUINT8(p); // demoflags + p++; // gametype + G_SkipDemoExtraFiles(&p); aflags = flags & (DF_RECORDATTACK|DF_NIGHTSATTACK); I_Assert(aflags); @@ -5655,7 +6727,15 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname) switch(oldversion) // demoversion { 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); @@ -5672,6 +6752,8 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname) p += 2; // gamemap p += 16; // mapmd5 flags = READUINT8(p); + p++; // gametype + G_SkipDemoExtraFiles(&p); if (!(flags & aflags)) { CONS_Alert(CONS_NOTICE, M_GetText("File '%s' not from same game mode. It will be overwritten.\n"), oldname); @@ -5703,6 +6785,188 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname) return c; } +void G_LoadDemoInfo(menudemo_t *pdemo) +{ + UINT8 *infobuffer, *info_p, *extrainfo_p; + UINT8 version, subversion, pdemoflags; + UINT16 pdemoversion, count; + + if (!FIL_ReadFile(pdemo->filepath, &infobuffer)) + { + CONS_Alert(CONS_ERROR, M_GetText("Failed to read file '%s'.\n"), pdemo->filepath); + pdemo->type = MD_INVALID; + sprintf(pdemo->title, "INVALID REPLAY"); + + return; + } + + info_p = infobuffer; + + if (memcmp(info_p, DEMOHEADER, 12)) + { + CONS_Alert(CONS_ERROR, M_GetText("%s is not a SRB2Kart replay file.\n"), pdemo->filepath); + pdemo->type = MD_INVALID; + sprintf(pdemo->title, "INVALID REPLAY"); + Z_Free(infobuffer); + return; + } + + pdemo->type = MD_LOADED; + + info_p += 12; // DEMOHEADER + + version = READUINT8(info_p); + subversion = READUINT8(info_p); + pdemoversion = READUINT16(info_p); + + switch(pdemoversion) + { + case DEMOVERSION: // latest always supported + // demo title + M_Memcpy(pdemo->title, info_p, 64); + 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); + pdemo->type = MD_INVALID; + sprintf(pdemo->title, "INVALID REPLAY"); + Z_Free(infobuffer); + return; + } + + if (version != VERSION || subversion != SUBVERSION) + pdemo->type = MD_OUTDATED; + + info_p += 16; // demo checksum + if (memcmp(info_p, "PLAY", 4)) + { + CONS_Alert(CONS_ERROR, M_GetText("%s is the wrong type of recording and cannot be played.\n"), pdemo->filepath); + pdemo->type = MD_INVALID; + sprintf(pdemo->title, "INVALID REPLAY"); + Z_Free(infobuffer); + return; + } + info_p += 4; // "PLAY" + pdemo->map = READINT16(info_p); + info_p += 16; // mapmd5 + + pdemoflags = READUINT8(info_p); + + // temp? + if (!(pdemoflags & DF_MULTIPLAYER)) + { + CONS_Alert(CONS_ERROR, M_GetText("%s is not a multiplayer replay and can't be listed on this menu fully yet.\n"), pdemo->filepath); + 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); + + pdemo->addonstatus = G_CheckDemoExtraFiles(&info_p, true); + info_p += 4; // RNG seed + + extrainfo_p = infobuffer + READUINT32(info_p); + + // Pared down version of CV_LoadNetVars to find the kart speed + pdemo->kartspeed = 1; // Default to normal speed + count = READUINT16(info_p); + while (count--) + { + UINT16 netid; + char *svalue; + + netid = READUINT16(info_p); + svalue = (char *)info_p; + SKIPSTRING(info_p); + info_p++; // stealth + + if (netid == cv_kartspeed.netid) + { + UINT8 j; + for (j = 0; kartspeed_cons_t[j].strvalue; j++) + if (!stricmp(kartspeed_cons_t[j].strvalue, svalue)) + pdemo->kartspeed = kartspeed_cons_t[j].value; + } + else if (netid == cv_basenumlaps.netid && pdemo->gametype == GT_RACE) + pdemo->numlaps = atoi(svalue); + } + + if (pdemoflags & DF_ENCORE) + pdemo->kartspeed |= DF_ENCORE; + + /*// Temporary info until this is actually present in replays. + (void)extrainfo_p; + sprintf(pdemo->winnername, "transrights420"); + pdemo->winnerskin = 1; + pdemo->winnercolor = SKINCOLOR_MOONSLAM; + pdemo->winnertime = 6666;*/ + + // Read standings! + count = 0; + + while (READUINT8(extrainfo_p) == DW_STANDING) // Assume standings are always first in the extrainfo + { + INT32 i; + char temp[16]; + + pdemo->standings[count].ranking = READUINT8(extrainfo_p); + + // Name + M_Memcpy(pdemo->standings[count].name, extrainfo_p, 16); + extrainfo_p += 16; + + // Skin + M_Memcpy(temp,extrainfo_p,16); + extrainfo_p += 16; + pdemo->standings[count].skin = UINT8_MAX; + for (i = 0; i < numskins; i++) + if (stricmp(skins[i].name, temp) == 0) + { + pdemo->standings[count].skin = i; + break; + } + + // Color + M_Memcpy(temp,extrainfo_p,16); + extrainfo_p += 16; + for (i = 0; i < MAXSKINCOLORS; i++) + if (!stricmp(KartColor_Names[i],temp)) // SRB2kart + { + pdemo->standings[count].color = i; + break; + } + + // Score/time/whatever + pdemo->standings[count].timeorscore = READUINT32(extrainfo_p); + + count++; + + if (count >= MAXPLAYERS) + break; //@TODO still cycle through the rest of these if extra demo data is ever used + } + + // I think that's everything we need? + Z_Free(infobuffer); +} + // // G_PlayDemo // @@ -5710,7 +6974,7 @@ void G_DeferedPlayDemo(const char *name) { COM_BufAddText("playdemo \""); COM_BufAddText(name); - COM_BufAddText("\"\n"); + COM_BufAddText("\" -addfiles\n"); } // @@ -5719,61 +6983,76 @@ void G_DeferedPlayDemo(const char *name) #define SKIPERRORS void G_DoPlayDemo(char *defdemoname) { - UINT8 i; + UINT8 i, p; lumpnum_t l; char skin[17],color[17],*n,*pdemoname; - UINT8 version,subversion,kartspeed,kartweight; + UINT8 version,subversion; UINT32 randseed; char msg[1024]; #if defined(SKIPERRORS) && !defined(DEVELOP) boolean skiperrors = false; #endif + boolean spectator; + UINT8 slots[MAXPLAYERS], kartspeed[MAXPLAYERS], kartweight[MAXPLAYERS], numslots = 0; + + G_InitDemoRewind(); skin[16] = '\0'; color[16] = '\0'; - n = defdemoname+strlen(defdemoname); - while (*n != '/' && *n != '\\' && n != defdemoname) - n--; - if (n != defdemoname) - n++; - pdemoname = ZZ_Alloc(strlen(n)+1); - strcpy(pdemoname,n); - - // Internal if no extension, external if one exists - if (FIL_CheckExtension(defdemoname)) + // No demo name means we're restarting the current demo + if (defdemoname == NULL) { - //FIL_DefaultExtension(defdemoname, ".lmp"); - if (!FIL_ReadFile(defdemoname, &demobuffer)) + demo_p = demobuffer; + pdemoname = ZZ_Alloc(1); // Easier than adding checks for this everywhere it's freed + } + else + { + n = defdemoname+strlen(defdemoname); + while (*n != '/' && *n != '\\' && n != defdemoname) + n--; + if (n != defdemoname) + n++; + pdemoname = ZZ_Alloc(strlen(n)+1); + strcpy(pdemoname,n); + + M_SetPlaybackMenuPointer(); + + // Internal if no extension, external if one exists + if (FIL_CheckExtension(defdemoname)) { - snprintf(msg, 1024, M_GetText("Failed to read file '%s'.\n"), defdemoname); + //FIL_DefaultExtension(defdemoname, ".lmp"); + if (!FIL_ReadFile(defdemoname, &demobuffer)) + { + snprintf(msg, 1024, M_GetText("Failed to read file '%s'.\n"), defdemoname); + CONS_Alert(CONS_ERROR, "%s", msg); + gameaction = ga_nothing; + M_StartMessage(msg, NULL, MM_NOTHING); + return; + } + demo_p = demobuffer; + } + // load demo resource from WAD + else if ((l = W_CheckNumForName(defdemoname)) == LUMPERROR) + { + snprintf(msg, 1024, M_GetText("Failed to read lump '%s'.\n"), defdemoname); CONS_Alert(CONS_ERROR, "%s", msg); gameaction = ga_nothing; M_StartMessage(msg, NULL, MM_NOTHING); return; } - demo_p = demobuffer; - } - // load demo resource from WAD - else if ((l = W_CheckNumForName(defdemoname)) == LUMPERROR) - { - snprintf(msg, 1024, M_GetText("Failed to read lump '%s'.\n"), defdemoname); - CONS_Alert(CONS_ERROR, "%s", msg); - gameaction = ga_nothing; - M_StartMessage(msg, NULL, MM_NOTHING); - return; - } - else // it's an internal demo - { - demobuffer = demo_p = W_CacheLumpNum(l, PU_STATIC); + else // it's an internal demo + { + demobuffer = demo_p = W_CacheLumpNum(l, PU_STATIC); #if defined(SKIPERRORS) && !defined(DEVELOP) - skiperrors = true; // SRB2Kart: Don't print warnings for staff ghosts, since they'll inevitably happen when we make bugfixes/changes... + skiperrors = true; // SRB2Kart: Don't print warnings for staff ghosts, since they'll inevitably happen when we make bugfixes/changes... #endif + } } // read demo header gameaction = ga_nothing; - demoplayback = true; + demo.playback = true; if (memcmp(demo_p, DEMOHEADER, 12)) { snprintf(msg, 1024, M_GetText("%s is not a SRB2Kart replay file.\n"), pdemoname); @@ -5781,19 +7060,27 @@ void G_DoPlayDemo(char *defdemoname) M_StartMessage(msg, NULL, MM_NOTHING); Z_Free(pdemoname); Z_Free(demobuffer); - demoplayback = false; - titledemo = false; + demo.playback = false; + demo.title = false; return; } demo_p += 12; // DEMOHEADER version = READUINT8(demo_p); subversion = READUINT8(demo_p); - demoversion = READUINT16(demo_p); - switch(demoversion) + demo.version = READUINT16(demo_p); + switch(demo.version) { case DEMOVERSION: // latest always supported + // demo title + M_Memcpy(demo.titlename, demo_p, 64); + 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); @@ -5801,8 +7088,8 @@ void G_DoPlayDemo(char *defdemoname) M_StartMessage(msg, NULL, MM_NOTHING); Z_Free(pdemoname); Z_Free(demobuffer); - demoplayback = false; - titledemo = false; + demo.playback = false; + demo.title = false; return; } demo_p += 16; // demo checksum @@ -5813,8 +7100,8 @@ void G_DoPlayDemo(char *defdemoname) M_StartMessage(msg, NULL, MM_NOTHING); Z_Free(pdemoname); Z_Free(demobuffer); - demoplayback = false; - titledemo = false; + demo.playback = false; + demo.title = false; return; } demo_p += 4; // "PLAY" @@ -5822,7 +7109,87 @@ 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. + G_SkipDemoExtraFiles(&demo_p); + else if (demo.loadfiles) + G_LoadDemoExtraFiles(&demo_p); + else if (demo.ignorefiles) + G_SkipDemoExtraFiles(&demo_p); + else + { + UINT8 error = G_CheckDemoExtraFiles(&demo_p, false); + + if (error) + { + switch (error) + { + case DFILE_ERROR_NOTLOADED: + snprintf(msg, 1024, + M_GetText("Required files for this demo are not loaded.\n\nUse\n\"playdemo %s -addfiles\"\nto load them and play the demo.\n"), + pdemoname); + break; + + case DFILE_ERROR_OUTOFORDER: + snprintf(msg, 1024, + M_GetText("Required files for this demo are loaded out of order.\n\nUse\n\"playdemo %s -force\"\nto play the demo anyway.\n"), + pdemoname); + break; + + case DFILE_ERROR_INCOMPLETEOUTOFORDER: + snprintf(msg, 1024, + M_GetText("Required files for this demo are not loaded, and some are out of order.\n\nUse\n\"playdemo %s -addfiles\"\nto load needed files and play the demo.\n"), + pdemoname); + break; + + case DFILE_ERROR_CANNOTLOAD: + snprintf(msg, 1024, + M_GetText("Required files for this demo cannot be loaded.\n\nUse\n\"playdemo %s -force\"\nto play the demo anyway.\n"), + pdemoname); + break; + + case DFILE_ERROR_EXTRAFILES: + snprintf(msg, 1024, + M_GetText("You have additional files loaded beyond the demo's file list.\n\nUse\n\"playdemo %s -force\"\nto play the demo anyway.\n"), + pdemoname); + break; + } + + CONS_Alert(CONS_ERROR, "%s", msg); + if (!CON_Ready()) // In the console they'll just see the notice there! No point pulling them out. + M_StartMessage(msg, NULL, MM_NOTHING); + Z_Free(pdemoname); + Z_Free(demobuffer); + demo.playback = false; + demo.title = false; + return; + } + } +#ifdef DEMO_COMPAT_100 + } +#endif + modeattacking = (demoflags & DF_ATTACKMASK)>>DF_ATTACKSHIFT; + multiplayer = !!(demoflags & DF_MULTIPLAYER); CON_ToggleOff(); hu_demotime = UINT32_MAX; @@ -5847,34 +7214,109 @@ 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 - // Player name - M_Memcpy(player_names[0],demo_p,16); - demo_p += 16; +#ifdef DEMO_COMPAT_100 + if (demo.version == 0x0001) + { + // Player name + M_Memcpy(player_names[0],demo_p,16); + demo_p += 16; - // Skin - M_Memcpy(skin,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; + // Color + M_Memcpy(color,demo_p,16); + demo_p += 16; - demo_p++; // charability - demo_p++; // charability2 - demo_p++; // actionspd - demo_p++; // mindash - demo_p++; // maxdash - // SRB2kart - kartspeed = READUINT8(demo_p); - kartweight = READUINT8(demo_p); - // - demo_p++; // normalspeed - demo_p++; // runspeed - demo_p++; // thrustfactor - demo_p++; // accelstart - demo_p++; // acceleration - demo_p += 4; // jumpfactor + 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; + } + + 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; + } +#endif // net var data CV_LoadNetVars(&demo_p); @@ -5887,34 +7329,8 @@ void G_DoPlayDemo(char *defdemoname) M_StartMessage(msg, NULL, MM_NOTHING); Z_Free(pdemoname); Z_Free(demobuffer); - demoplayback = false; - titledemo = false; - return; - } - - // 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); - demoplayback = false; - titledemo = 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); - demoplayback = false; - titledemo = false; + demo.playback = false; + demo.title = false; return; } @@ -5922,6 +7338,7 @@ void G_DoPlayDemo(char *defdemoname) memset(&oldcmd,0,sizeof(oldcmd)); memset(&oldghost,0,sizeof(oldghost)); + memset(&ghostext,0,sizeof(ghostext)); #if defined(SKIPERRORS) && !defined(DEVELOP) if ((VERSION != version || SUBVERSION != subversion) && !skiperrors) @@ -5938,42 +7355,131 @@ void G_DoPlayDemo(char *defdemoname) #endif // didn't start recording right away. - demo_start = false; + demo.deferstart = false; /*#ifdef HAVE_BLUA LUAh_MapChange(gamemap); #endif*/ - displayplayer = consoleplayer = 0; + displayplayers[0] = consoleplayer = 0; memset(playeringame,0,sizeof(playeringame)); - playeringame[0] = true; - P_SetRandSeed(randseed); - G_InitNew(false, G_BuildMapName(gamemap), true, true); // Doesn't matter whether you reset or not here, given changes to resetplayer. - // Set color - for (i = 0; i < MAXSKINCOLORS; i++) - if (!stricmp(KartColor_Names[i],color)) // SRB2kart - { - players[0].skincolor = i; - break; - } - //CV_StealthSetValue(&cv_playercolor, players[0].skincolor); -- as far as I can tell this is more trouble than it's worth - if (players[0].mo) + // Load players that were in-game when the map started + p = READUINT8(demo_p); + + for (i = 1; i < MAXSPLITSCREENPLAYERS; i++) + displayplayers[i] = INT32_MAX; + + while (p != 0xFF) { - players[0].mo->color = players[0].skincolor; - oldghost.x = players[0].mo->x; - oldghost.y = players[0].mo->y; - oldghost.z = players[0].mo->z; + spectator = false; + if (p & DEMO_SPECTATOR) + { + spectator = true; + p &= ~DEMO_SPECTATOR; + + if (modeattacking) + { + snprintf(msg, 1024, M_GetText("%s is a record attack replay with spectators, and is thus invalid.\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; + } + } + slots[numslots] = p; numslots++; + + if (modeattacking && numslots > 1) + { + snprintf(msg, 1024, M_GetText("%s is a record attack replay with multiple players, and is thus invalid.\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; + } + + if (!playeringame[displayplayers[0]] || players[displayplayers[0]].spectator) + displayplayers[0] = consoleplayer = serverplayer = p; + + playeringame[p] = true; + players[p].spectator = spectator; + + // Name + M_Memcpy(player_names[p],demo_p,16); + demo_p += 16; + + // Skin + M_Memcpy(skin,demo_p,16); + demo_p += 16; + SetPlayerSkin(p, skin); + + // Color + M_Memcpy(color,demo_p,16); + demo_p += 16; + for (i = 0; i < MAXSKINCOLORS; i++) + if (!stricmp(KartColor_Names[i],color)) // SRB2kart + { + players[p].skincolor = i; + break; + } + + // Score, since Kart uses this to determine where you start on the map + players[p].score = READUINT32(demo_p); + + // Kart stats, temporarily + kartspeed[p] = READUINT8(demo_p); + kartweight[p] = READUINT8(demo_p); + + if (stricmp(skins[players[p].skin].name, skin) != 0) + FindClosestSkinForStats(p, kartspeed[p], kartweight[p]); + + // Look for the next player + p = READUINT8(demo_p); } - // Set saved attribute values - // No cheat checking here, because even if they ARE wrong... - // it would only break the replay if we clipped them. - // SRB2kart - players[0].kartspeed = kartspeed; - players[0].kartweight = kartweight; - // + splitscreen = 0; - demo_start = true; + if (demo.title) + { + splitscreen = M_RandomKey(6)-1; + splitscreen = min(min(3, numslots-1), splitscreen); // Bias toward 1p and 4p views + + for (p = 0; p <= splitscreen; p++) + G_ResetView(p+1, slots[M_RandomKey(numslots)], false); + } + + 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. + + for (i = 0; i < MAXPLAYERS; i++) + { + if (players[i].mo) + { + players[i].mo->color = players[i].skincolor; + oldghost[i].x = players[i].mo->x; + oldghost[i].y = players[i].mo->y; + oldghost[i].z = players[i].mo->z; + } + + // Set saved attribute values + // No cheat checking here, because even if they ARE wrong... + // it would only break the replay if we clipped them. + players[i].kartspeed = kartspeed[i]; + players[i].kartweight = kartweight[i]; + } + + demo.deferstart = true; } #undef SKIPERRORS @@ -5988,6 +7494,7 @@ void G_AddGhost(char *defdemoname) mapthing_t *mthing; UINT16 count, ghostversion; skin_t *ghskin = &skins[0]; + UINT8 kartspeed = UINT8_MAX, kartweight = UINT8_MAX; name[16] = '\0'; skin[16] = '\0'; @@ -6030,14 +7537,22 @@ void G_AddGhost(char *defdemoname) Z_Free(pdemoname); Z_Free(buffer); return; - } p += 12; // DEMOHEADER + } + + p += 12; // DEMOHEADER p++; // VERSION p++; // SUBVERSION + ghostversion = READUINT16(p); switch(ghostversion) { 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); @@ -6045,6 +7560,7 @@ void G_AddGhost(char *defdemoname) Z_Free(buffer); return; } + M_Memcpy(md5, p, 16); p += 16; // demo checksum for (gh = ghosts; gh; gh = gh->next) if (!memcmp(md5, gh->checksum, 16)) // another ghost in the game already has this checksum? @@ -6054,16 +7570,21 @@ void G_AddGhost(char *defdemoname) Z_Free(buffer); return; } + if (memcmp(p, "PLAY", 4)) { CONS_Alert(CONS_NOTICE, M_GetText("Ghost %s: Demo format unacceptable.\n"), pdemoname); Z_Free(pdemoname); Z_Free(buffer); return; - } p += 4; // "PLAY" + } + + p += 4; // "PLAY" p += 2; // gamemap p += 16; // mapmd5 (possibly check for consistency?) + flags = READUINT8(p); + if (!(flags & DF_GHOST)) { CONS_Alert(CONS_NOTICE, M_GetText("Ghost %s: No ghost data in this demo.\n"), pdemoname); @@ -6071,6 +7592,16 @@ void G_AddGhost(char *defdemoname) Z_Free(buffer); return; } + +#ifdef DEMO_COMPAT_100 + if (ghostversion != 0x0001) +#endif + p++; // gametype + +#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 @@ -6087,34 +7618,41 @@ void G_AddGhost(char *defdemoname) p += 4; // random seed - // Player name (TODO: Display this somehow if it doesn't match cv_playername!) - M_Memcpy(name, p,16); - p += 16; +#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; + // Skin + M_Memcpy(skin, p,16); + p += 16; - // Color - M_Memcpy(color, 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 + // 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 count = READUINT16(p); @@ -6133,6 +7671,46 @@ 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); + Z_Free(pdemoname); + Z_Free(buffer); + return; + } + + // 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; + + p += 4; // score + + kartspeed = READUINT8(p); + kartweight = READUINT8(p); + + if (READUINT8(p) != 0xFF) + { + CONS_Alert(CONS_NOTICE, M_GetText("Failed to add ghost %s: Invalid player slot.\n"), pdemoname); + Z_Free(pdemoname); + Z_Free(buffer); + return; + } +#ifdef DEMO_COMPAT_100 + } +#endif + for (i = 0; i < numskins; i++) if (!stricmp(skins[i].name,skin)) { @@ -6142,10 +7720,10 @@ void G_AddGhost(char *defdemoname) if (i == numskins) { - CONS_Alert(CONS_NOTICE, M_GetText("Failed to add ghost %s: Invalid character.\n"), pdemoname); - Z_Free(pdemoname); - Z_Free(buffer); - return; + if (kartspeed != UINT8_MAX && kartweight != UINT8_MAX) + ghskin = &skins[GetSkinNumClosestToStats(kartspeed, kartweight)]; + + CONS_Alert(CONS_NOTICE, M_GetText("Ghost %s: Invalid character. Falling back to %s.\n"), pdemoname, ghskin->name); } gh = Z_Calloc(sizeof(demoghost), PU_LEVEL, NULL); @@ -6222,30 +7800,56 @@ void G_UpdateStaffGhostName(lumpnum_t l) if (memcmp(p, DEMOHEADER, 12)) { goto fail; - } p += 12; // DEMOHEADER + } + + p += 12; // DEMOHEADER p++; // VERSION p++; // SUBVERSION + ghostversion = READUINT16(p); switch(ghostversion) { 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; } + p += 16; // demo checksum + if (memcmp(p, "PLAY", 4)) { goto fail; - } p += 4; // "PLAY" + } + + p += 4; // "PLAY" p += 2; // gamemap p += 16; // mapmd5 (possibly check for consistency?) + flags = READUINT8(p); if (!(flags & DF_GHOST)) { 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 @@ -6259,9 +7863,34 @@ void G_UpdateStaffGhostName(lumpnum_t l) default: // 3 break; } + p += 4; // random seed - // Player name + +#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 + ghostversion = READUINT16(p); + while (ghostversion--) + { + p += 2; + SKIPSTRING(p); + p++; // stealth + } + + // Assert first player is in and then read name + if (READUINT8(p) != 0) + goto fail; M_Memcpy(dummystaffname, p,16); dummystaffname[16] = '\0'; @@ -6284,7 +7913,7 @@ void G_TimeDemo(const char *name) restorecv_vidwait = cv_vidwait.value; if (cv_vidwait.value) CV_Set(&cv_vidwait, "0"); - timingdemo = true; + demo.timing = true; singletics = true; framecount = 0; demostarttime = I_GetTime(); @@ -6332,6 +7961,10 @@ 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")); @@ -6414,13 +8047,16 @@ void G_StopDemo(void) { Z_Free(demobuffer); demobuffer = NULL; - demoplayback = false; - if (titledemo) + demo.playback = false; + if (demo.title) modeattacking = false; - titledemo = false; - timingdemo = false; + demo.title = false; + demo.timing = false; singletics = false; + CV_SetValue(&cv_playbackspeed, 1); + demo.rewinding = false; + if (gamestate == GS_LEVEL && rendermode != render_none) { V_SetPaletteLump("PLAYPAL"); // Reset the palette @@ -6439,8 +8075,6 @@ void G_StopDemo(void) boolean G_CheckDemoStatus(void) { - boolean saved; - while (ghosts) { demoghost *next = ghosts->next; @@ -6451,7 +8085,7 @@ boolean G_CheckDemoStatus(void) // DO NOT end metal sonic demos here - if (timingdemo) + if (demo.timing) { INT32 demotime; double f1, f2; @@ -6459,7 +8093,7 @@ boolean G_CheckDemoStatus(void) if (!demotime) return true; G_StopDemo(); - timingdemo = false; + demo.timing = false; f1 = (double)demotime; f2 = (double)framecount*TICRATE; CONS_Printf(M_GetText("timed %u gametics in %d realtics\n%f seconds, %f avg fps\n"), leveltime,demotime,f1/TICRATE,f2/f1); @@ -6469,49 +8103,165 @@ boolean G_CheckDemoStatus(void) return true; } - if (demoplayback) + if (demo.playback) { - if (singledemo) + if (demo.quitafterplaying) I_Quit(); - G_StopDemo(); - if (modeattacking) - M_EndModeAttackRun(); + if (multiplayer && !demo.title) + G_ExitLevel(); else - D_AdvanceDemo(); - - return true; - } - - if (demorecording) - { - UINT8 *p = demobuffer+16; // checksum position -#ifdef NOMD5 - UINT8 i; - WRITEUINT8(demo_p, DEMOMARKER); // add the demo end marker - for (i = 0; i < 16; i++, p++) - *p = P_RandomByte(); // This MD5 was chosen by fair dice roll and most likely < 50% correct. -#else - WRITEUINT8(demo_p, DEMOMARKER); // add the demo end marker - md5_buffer((char *)p+16, demo_p - (p+16), p); // make a checksum of everything after the checksum in the file. -#endif - saved = FIL_WriteFile(va(pandf, srb2home, demoname), demobuffer, demo_p - demobuffer); // finally output the file. - free(demobuffer); - demorecording = false; - - if (modeattacking != ATTACKING_RECORD) { - if (saved) - CONS_Printf(M_GetText("Demo %s recorded\n"), demoname); + G_StopDemo(); + + if (modeattacking) + M_EndModeAttackRun(); else - CONS_Alert(CONS_WARNING, M_GetText("Demo %s not saved\n"), demoname); + D_AdvanceDemo(); } + return true; } + if (demo.recording && (modeattacking || demo.savemode != DSM_NOTSAVING)) + { + G_SaveDemo(); + return true; + } + demo.recording = false; + return false; } +void G_SaveDemo(void) +{ + UINT8 *p = demobuffer+16; // checksum position +#ifdef NOMD5 + UINT8 i; +#endif + + // Ensure extrainfo pointer is always available, even if no info is present. + if (demoinfo_p && (UINT32)(*demoinfo_p) == 0) + { + WRITEUINT8(demo_p, DEMOMARKER); // add the demo end marker + WRITEUINT32(demoinfo_p, (UINT32)(demo_p - demobuffer)); + } + WRITEUINT8(demo_p, DW_END); // Mark end of demo extra data. + + M_Memcpy(p, demo.titlename, 64); // Write demo title here + p += 64; + + if (multiplayer) + { + // Change the demo's name to be a slug of the title + char demo_slug[128]; + char *writepoint; + size_t i, strindex = 0; + boolean dash = true; + + for (i = 0; demo.titlename[i] && i < 127; i++) + { + if ((demo.titlename[i] >= 'a' && demo.titlename[i] <= 'z') || + (demo.titlename[i] >= '0' && demo.titlename[i] <= '9')) + { + demo_slug[strindex] = demo.titlename[i]; + strindex++; + dash = false; + } + else if (demo.titlename[i] >= 'A' && demo.titlename[i] <= 'Z') + { + demo_slug[strindex] = demo.titlename[i] + 'a' - 'A'; + strindex++; + dash = false; + } + else if (!dash) + { + demo_slug[strindex] = '-'; + strindex++; + dash = true; + } + } + + demo_slug[strindex] = 0; + if (dash) demo_slug[strindex-1] = 0; + + writepoint = strstr(demoname, "-") + 1; + demo_slug[128 - (writepoint - demoname) - 4] = 0; + sprintf(writepoint, "%s.lmp", demo_slug); + } + +#ifdef NOMD5 + for (i = 0; i < 16; i++, p++) + *p = M_RandomByte(); // This MD5 was chosen by fair dice roll and most likely < 50% correct. +#else + // Make a checksum of everything after the checksum in the file up to the end of the standard data. Extrainfo is freely modifiable. + md5_buffer((char *)p+16, (demobuffer + (UINT32)*demoinfo_p) - (p+16), p); +#endif + + + if (FIL_WriteFile(va(pandf, srb2home, demoname), demobuffer, demo_p - demobuffer)) // finally output the file. + demo.savemode = DSM_SAVED; + free(demobuffer); + demo.recording = false; + + if (modeattacking != ATTACKING_RECORD) + { + if (demo.savemode == DSM_SAVED) + CONS_Printf(M_GetText("Demo %s recorded\n"), demoname); + else + CONS_Alert(CONS_WARNING, M_GetText("Demo %s not saved\n"), demoname); + } +} + +boolean G_DemoTitleResponder(event_t *ev) +{ + size_t len; + INT32 ch; + + if (ev->type != ev_keydown) + return false; + + ch = (INT32)ev->data1; + + // Only ESC and non-keyboard keys abort connection + if (ch == KEY_ESCAPE) + { + demo.savemode = (cv_recordmultiplayerdemos.value == 2) ? DSM_WILLAUTOSAVE : DSM_NOTSAVING; + return true; + } + + if (ch == KEY_ENTER || ch >= KEY_MOUSE1) + { + demo.savemode = DSM_WILLSAVE; + return true; + } + + if ((ch >= HU_FONTSTART && ch <= HU_FONTEND && hu_font[ch-HU_FONTSTART]) + || ch == ' ') // Allow spaces, of course + { + len = strlen(demo.titlename); + if (len < 64) + { + demo.titlename[len+1] = 0; + demo.titlename[len] = CON_ShiftChar(ch); + } + } + else if (ch == KEY_BACKSPACE) + { + if (shiftdown) + memset(demo.titlename, 0, sizeof(demo.titlename)); + else + { + len = strlen(demo.titlename); + + if (len > 0) + demo.titlename[len-1] = 0; + } + } + + return true; +} + // // G_SetGamestate // diff --git a/src/g_game.h b/src/g_game.h index 9681491b7..27cf60d4e 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -36,11 +36,61 @@ extern boolean playeringame[MAXPLAYERS]; // ====================================== // demoplaying back and demo recording -extern boolean demoplayback, titledemo, fromtitledemo, demorecording, timingdemo; +extern consvar_t cv_recordmultiplayerdemos, cv_netdemosyncquality; + +// Publicly-accessible demo vars +struct demovars_s { + char titlename[65]; + boolean recording, playback, timing; + UINT16 version; // Current file format of the demo being played + boolean title; // Title Screen demo can be cancelled by any key + boolean rewinding; // Rewind in progress + + boolean loadfiles, ignorefiles; // Demo file loading options + boolean fromtitle; // SRB2Kart: Don't stop the music + boolean inreplayhut; // Go back to replayhut after demos + boolean quitafterplaying; // quit after playing a demo from cmdline + boolean deferstart; // don't start playing demo right away + + tic_t savebutton; // Used to determine when the local player can choose to save the replay while the race is still going + enum { + DSM_NOTSAVING, + DSM_WILLAUTOSAVE, + DSM_TITLEENTRY, + DSM_WILLSAVE, + DSM_SAVED + } savemode; +}; + +extern struct demovars_s demo; + +typedef enum { + MD_NOTLOADED, + MD_LOADED, + MD_SUBDIR, + MD_OUTDATED, + MD_INVALID +} menudemotype_e; + +typedef struct menudemo_s { + char filepath[256]; + menudemotype_e type; + + char title[65]; // Null-terminated for string prints + UINT16 map; + UINT8 addonstatus; // What do we need to do addon-wise to play this demo? + UINT8 gametype; + UINT8 kartspeed; // Add OR DF_ENCORE for encore mode, idk + UINT8 numlaps; + + struct { + UINT8 ranking; + char name[17]; + UINT8 skin, color; + UINT32 timeorscore; + } standings[MAXPLAYERS]; +} menudemo_t; -// Quit after playing a demo from cmdline. -extern boolean singledemo; -extern boolean demo_start; extern mobj_t *metalplayback; @@ -102,9 +152,9 @@ INT16 G_SoftwareClipAimingPitch(INT32 *aiming); boolean InputDown(INT32 gc, UINT8 p); INT32 JoyAxis(axis_input_e axissel, UINT8 p); -extern angle_t localangle, localangle2, localangle3, localangle4; -extern INT32 localaiming, localaiming2, localaiming3, localaiming4; // should be an angle_t but signed -extern boolean camspin, camspin2, camspin3, camspin4; // SRB2Kart +extern angle_t localangle[MAXSPLITSCREENPLAYERS]; +extern INT32 localaiming[MAXSPLITSCREENPLAYERS]; // should be an angle_t but signed +extern boolean camspin[MAXSPLITSCREENPLAYERS]; // SRB2Kart // // GAME @@ -128,6 +178,7 @@ void G_DeferedInitNew(boolean pencoremode, const char *mapname, INT32 pickedchar UINT8 ssplayers, boolean FLS); void G_DoLoadLevel(boolean resetplayer); +void G_LoadDemoInfo(menudemo_t *pdemo); void G_DeferedPlayDemo(const char *demo); // Can be called by the startup code or M_Responder, calls P_SetupLevel. @@ -144,6 +195,7 @@ void G_BeginRecording(void); void G_BeginMetal(void); // Only called by shutdown code. +void G_WriteStanding(UINT8 ranking, char *name, INT32 skinnum, UINT8 color, UINT32 val); void G_SetDemoTime(UINT32 ptime, UINT32 plap); UINT8 G_CmpDemoTime(char *oldname, char *newname); @@ -155,19 +207,41 @@ typedef enum GHC_INVINCIBLE } ghostcolor_t; +extern UINT8 demo_extradata[MAXPLAYERS]; +extern UINT8 demo_writerng; +#define DXD_RESPAWN 0x01 // "respawn" command in console +#define DXD_SKIN 0x02 // skin changed +#define DXD_NAME 0x04 // name changed +#define DXD_COLOR 0x08 // color changed +#define DXD_PLAYSTATE 0x10 // state changed between playing, spectating, or not in-game + +#define DXD_PST_PLAYING 0x01 +#define DXD_PST_SPECTATING 0x02 +#define DXD_PST_LEFT 0x03 + // Record/playback tics +void G_ReadDemoExtraData(void); +void G_WriteDemoExtraData(void); void G_ReadDemoTiccmd(ticcmd_t *cmd, INT32 playernum); void G_WriteDemoTiccmd(ticcmd_t *cmd, INT32 playernum); -void G_GhostAddThok(void); -void G_GhostAddSpin(void); -void G_GhostAddRev(void); -void G_GhostAddColor(ghostcolor_t color); -void G_GhostAddFlip(void); -void G_GhostAddScale(fixed_t scale); -void G_GhostAddHit(mobj_t *victim); -void G_WriteGhostTic(mobj_t *ghost); -void G_ConsGhostTic(void); +void G_GhostAddThok(INT32 playernum); +void G_GhostAddSpin(INT32 playernum); +void G_GhostAddRev(INT32 playernum); +void G_GhostAddColor(INT32 playernum, ghostcolor_t color); +void G_GhostAddFlip(INT32 playernum); +void G_GhostAddScale(INT32 playernum, fixed_t scale); +void G_GhostAddHit(INT32 playernum, mobj_t *victim); +void G_WriteAllGhostTics(void); +void G_WriteGhostTic(mobj_t *ghost, INT32 playernum); +void G_ConsAllGhostTics(void); +void G_ConsGhostTic(INT32 playernum); void G_GhostTicker(void); + +void G_InitDemoRewind(void); +void G_StoreRewindInfo(void); +void G_PreviewRewind(tic_t previewtime); +void G_ConfirmRewind(tic_t rewindtime); + void G_ReadMetalTic(mobj_t *metal); void G_WriteMetalTic(mobj_t *metal); void G_SaveMetal(UINT8 **buffer); @@ -184,6 +258,13 @@ typedef struct demoghost { } demoghost; extern demoghost *ghosts; +// G_CheckDemoExtraFiles: checks if our loaded WAD list matches the demo's. +#define DFILE_ERROR_NOTLOADED 0x01 // Files are not loaded, but can be without a restart. +#define DFILE_ERROR_OUTOFORDER 0x02 // Files are loaded, but out of order. +#define DFILE_ERROR_INCOMPLETEOUTOFORDER 0x03 // Some files are loaded out of order, but others are not. +#define DFILE_ERROR_CANNOTLOAD 0x04 // Files are missing and cannot be loaded. +#define DFILE_ERROR_EXTRAFILES 0x05 // Extra files outside of the replay's file list are loaded. + void G_DoPlayDemo(char *defdemoname); void G_TimeDemo(const char *name); void G_AddGhost(char *defdemoname); @@ -194,6 +275,8 @@ void G_StopMetalDemo(void); ATTRNORETURN void FUNCNORETURN G_StopMetalRecording(void); void G_StopDemo(void); boolean G_CheckDemoStatus(void); +void G_SaveDemo(void); +boolean G_DemoTitleResponder(event_t *ev); boolean G_IsSpecialStage(INT32 mapnum); boolean G_GametypeUsesLives(void); @@ -214,6 +297,16 @@ void G_EndGame(void); // moved from y_inter.c/h and renamed void G_Ticker(boolean run); boolean G_Responder(event_t *ev); +boolean G_CouldView(INT32 playernum); +boolean G_CanView(INT32 playernum, UINT8 viewnum, boolean onlyactive); + +INT32 G_FindView(INT32 startview, UINT8 viewnum, boolean onlyactive, boolean reverse); +INT32 G_CountPlayersPotentiallyViewable(boolean active); + +void G_ResetViews(void); +void G_ResetView(UINT8 viewnum, INT32 playernum, boolean onlyactive); +void G_AdjustView(UINT8 viewnum, INT32 offset, boolean onlyactive); + void G_AddPlayer(INT32 playernum); void G_SetExitGameFlag(void); diff --git a/src/hardware/hw3sound.c b/src/hardware/hw3sound.c index f7c6e1da0..2594a5df9 100644 --- a/src/hardware/hw3sound.c +++ b/src/hardware/hw3sound.c @@ -296,7 +296,7 @@ static void HW3S_FillSourceParameters data->max_distance = MAX_DISTANCE; data->min_distance = MIN_DISTANCE; - if (origin && origin != players[displayplayer].mo) + if (origin && origin != players[displayplayers[0]].mo) { data->head_relative = false; @@ -356,10 +356,10 @@ INT32 HW3S_I_StartSound(const void *origin_p, source3D_data_t *source_parm, chan source3D_data_t source3d_data; INT32 s_num = 0; source_t *source = NULL; - mobj_t *listenmobj = players[displayplayer].mo; + mobj_t *listenmobj = players[displayplayers[0]].mo; // TODO: Kart 4P does not support sounds properly here mobj_t *listenmobj2 = NULL; - if (splitscreen) listenmobj2 = players[secondarydisplayplayer].mo; + if (splitscreen) listenmobj2 = players[displayplayers[1]].mo; if (sound_disabled) return -1; @@ -876,12 +876,12 @@ static void HW3S_Update3DSource(source_t *src) void HW3S_UpdateSources(void) { - mobj_t *listener = players[displayplayer].mo; + mobj_t *listener = players[displayplayers[0]].mo; mobj_t *listener2 = NULL; source_t *src; INT32 audible, snum, volume, sep, pitch; - if (splitscreen) listener2 = players[secondarydisplayplayer].mo; + if (splitscreen) listener2 = players[displayplayers[1]].mo; HW3S_UpdateListener2(listener2); HW3S_UpdateListener(listener); diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index 83d0a417e..86666b845 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -63,7 +63,7 @@ struct hwdriver_s hwdriver; // ========================================================================== -static void HWR_AddSprites(sector_t *sec, UINT8 ssplayer); +static void HWR_AddSprites(sector_t *sec); static void HWR_ProjectSprite(mobj_t *thing); #ifdef HWPRECIP static void HWR_ProjectPrecipitationSprite(precipmobj_t *thing); @@ -3389,7 +3389,7 @@ static void HWR_AddPolyObjectPlanes(void) // : Draw one or more line segments. // Notes : Sets gr_cursectorlight to the light of the parent sector, to modulate wall textures // -----------------+ -static void HWR_Subsector(size_t num, UINT8 ssplayer) +static void HWR_Subsector(size_t num) { INT16 count; seg_t *line; @@ -3754,7 +3754,7 @@ static void HWR_Subsector(size_t num, UINT8 ssplayer) { // draw sprites first, coz they are clipped to the solidsegs of // subsectors more 'in front' - HWR_AddSprites(gr_frontsector, ssplayer); + HWR_AddSprites(gr_frontsector); //Hurdler: at this point validcount must be the same, but is not because // gr_frontsector doesn't point anymore to sub->sector due to @@ -3806,7 +3806,7 @@ static boolean HWR_CheckHackBBox(fixed_t *bb) // BP: big hack for a test in lighning ref : 1249753487AB fixed_t *hwbbox; -static void HWR_RenderBSPNode(INT32 bspnum, UINT8 ssplayer) +static void HWR_RenderBSPNode(INT32 bspnum) { /*//GZDoom code if(bspnum == -1) @@ -3846,12 +3846,12 @@ static void HWR_RenderBSPNode(INT32 bspnum, UINT8 ssplayer) if (bspnum == -1) { //*(gr_drawsubsector_p++) = 0; - HWR_Subsector(0, ssplayer); + HWR_Subsector(0); } else { //*(gr_drawsubsector_p++) = bspnum&(~NF_SUBSECTOR); - HWR_Subsector(bspnum&(~NF_SUBSECTOR), ssplayer); + HWR_Subsector(bspnum&(~NF_SUBSECTOR)); } return; } @@ -3863,14 +3863,14 @@ static void HWR_RenderBSPNode(INT32 bspnum, UINT8 ssplayer) hwbbox = bsp->bbox[side]; // Recursively divide front space. - HWR_RenderBSPNode(bsp->children[side], ssplayer); + HWR_RenderBSPNode(bsp->children[side]); // Possibly divide back space. if (HWR_CheckBBox(bsp->bbox[side^1])) { // BP: big hack for a test in lighning ref : 1249753487AB hwbbox = bsp->bbox[side^1]; - HWR_RenderBSPNode(bsp->children[side^1], ssplayer); + HWR_RenderBSPNode(bsp->children[side^1]); } } @@ -4097,14 +4097,14 @@ static void HWR_DrawSpriteShadow(gr_vissprite_t *spr, GLPatch_t *gpatch, float t angle_t shadowdir; // Set direction - if (splitscreen && stplyr == &players[secondarydisplayplayer]) - shadowdir = localangle2 + FixedAngle(cv_cam2_rotate.value); - else if (splitscreen > 1 && stplyr == &players[thirddisplayplayer]) - shadowdir = localangle3 + FixedAngle(cv_cam3_rotate.value); - else if (splitscreen > 2 && stplyr == &players[fourthdisplayplayer]) - shadowdir = localangle4 + FixedAngle(cv_cam4_rotate.value); + if (splitscreen && stplyr == &players[displayplayers[1]]) + shadowdir = localangle[1] + FixedAngle(cv_cam2_rotate.value); + else if (splitscreen > 1 && stplyr == &players[displayplayers[2]]) + shadowdir = localangle[2] + FixedAngle(cv_cam3_rotate.value); + else if (splitscreen > 2 && stplyr == &players[displayplayers[3]]) + shadowdir = localangle[3] + FixedAngle(cv_cam4_rotate.value); else - shadowdir = localangle + FixedAngle(cv_cam_rotate.value); + shadowdir = localangle[0] + FixedAngle(cv_cam_rotate.value); // Find floorheight floorheight = HWR_OpaqueFloorAtPos( @@ -5283,7 +5283,7 @@ static void HWR_DrawSprites(void) // During BSP traversal, this adds sprites by sector. // -------------------------------------------------------------------------- static UINT8 sectorlight; -static void HWR_AddSprites(sector_t *sec, UINT8 ssplayer) +static void HWR_AddSprites(sector_t *sec) { mobj_t *thing; #ifdef HWPRECIP @@ -5316,19 +5316,19 @@ static void HWR_AddSprites(sector_t *sec, UINT8 ssplayer) if (splitscreen) { if (thing->eflags & MFE_DRAWONLYFORP1) - if (ssplayer != 1) + if (viewssnum != 0) continue; if (thing->eflags & MFE_DRAWONLYFORP2) - if (ssplayer != 2) + if (viewssnum != 1) continue; if (thing->eflags & MFE_DRAWONLYFORP3 && splitscreen > 1) - if (ssplayer != 3) + if (viewssnum != 2) continue; if (thing->eflags & MFE_DRAWONLYFORP4 && splitscreen > 2) - if (ssplayer != 4) + if (viewssnum != 3) continue; } @@ -5351,19 +5351,19 @@ static void HWR_AddSprites(sector_t *sec, UINT8 ssplayer) if (splitscreen) { if (thing->eflags & MFE_DRAWONLYFORP1) - if (ssplayer != 1) + if (viewssnum != 0) continue; if (thing->eflags & MFE_DRAWONLYFORP2) - if (ssplayer != 2) + if (viewssnum != 1) continue; if (thing->eflags & MFE_DRAWONLYFORP3 && splitscreen > 1) - if (ssplayer != 3) + if (viewssnum != 2) continue; if (thing->eflags & MFE_DRAWONLYFORP4 && splitscreen > 2) - if (ssplayer != 4) + if (viewssnum != 3) continue; } @@ -5906,33 +5906,8 @@ void HWR_RenderSkyboxView(INT32 viewnumber, player_t *player) { const float fpov = FIXED_TO_FLOAT(cv_fov.value+player->fovadd); postimg_t *type; - UINT8 ssplayer = 0; - if (splitscreen) - { - if (player == &players[secondarydisplayplayer]) - { - type = &postimgtype2; - ssplayer = 2; - } - else if (splitscreen > 1 && player == &players[thirddisplayplayer]) - { - type = &postimgtype3; - ssplayer = 3; - } - else if (splitscreen > 2 && player == &players[fourthdisplayplayer]) - { - type = &postimgtype4; - ssplayer = 4; - } - else - { - type = &postimgtype; - ssplayer = 1; - } - } - else - type = &postimgtype; + type = &postimgtype[viewnumber]; { // do we really need to save player (is it not the same)? @@ -6056,36 +6031,36 @@ if (0) validcount++; - HWR_RenderBSPNode((INT32)numnodes-1, ssplayer); + HWR_RenderBSPNode((INT32)numnodes-1); #ifndef NEWCLIP // Make a viewangle int so we can render things based on mouselook if (player == &players[consoleplayer]) - viewangle = localaiming; - else if (splitscreen && player == &players[secondarydisplayplayer]) - viewangle = localaiming2; - else if (splitscreen > 1 && player == &players[thirddisplayplayer]) - viewangle = localaiming3; - else if (splitscreen > 2 && player == &players[fourthdisplayplayer]) - viewangle = localaiming4; + viewangle = localaiming[0]; + else if (splitscreen && player == &players[displayplayers[1]]) + viewangle = localaiming[1]; + else if (splitscreen > 1 && player == &players[displayplayers[2]]) + viewangle = localaiming[2]; + else if (splitscreen > 2 && player == &players[displayplayers[3]]) + viewangle = localaiming[3]; // Handle stuff when you are looking farther up or down. if ((aimingangle || cv_fov.value+player->fovadd > 90*FRACUNIT)) { dup_viewangle += ANGLE_90; HWR_ClearClipSegs(); - HWR_RenderBSPNode((INT32)numnodes-1, ssplayer); //left + HWR_RenderBSPNode((INT32)numnodes-1); //left dup_viewangle += ANGLE_90; if (((INT32)aimingangle > ANGLE_45 || (INT32)aimingangle<-ANGLE_45)) { HWR_ClearClipSegs(); - HWR_RenderBSPNode((INT32)numnodes-1, ssplayer); //back + HWR_RenderBSPNode((INT32)numnodes-1); //back } dup_viewangle += ANGLE_90; HWR_ClearClipSegs(); - HWR_RenderBSPNode((INT32)numnodes-1, ssplayer); //right + HWR_RenderBSPNode((INT32)numnodes-1); //right dup_viewangle += ANGLE_90; } @@ -6149,38 +6124,13 @@ if (0) void HWR_RenderPlayerView(INT32 viewnumber, player_t *player) { const float fpov = FIXED_TO_FLOAT(cv_fov.value+player->fovadd); - postimg_t *type; - UINT8 ssplayer = 0; + postimg_t *type = &postimgtype[viewnumber]; const boolean skybox = (skyboxmo[0] && cv_skybox.value); // True if there's a skybox object and skyboxes are on FRGBAFloat ClearColor; - if (splitscreen) - { - if (player == &players[secondarydisplayplayer]) - { - type = &postimgtype2; - ssplayer = 2; - } - else if (splitscreen > 1 && player == &players[thirddisplayplayer]) - { - type = &postimgtype3; - ssplayer = 3; - } - else if (splitscreen > 2 && player == &players[fourthdisplayplayer]) - { - type = &postimgtype4; - ssplayer = 4; - } - else - { - type = &postimgtype; - ssplayer = 1; - } - } - else - type = &postimgtype; + type = &postimgtype[viewnumber]; ClearColor.red = 0.0f; ClearColor.green = 0.0f; @@ -6315,36 +6265,36 @@ if (0) validcount++; - HWR_RenderBSPNode((INT32)numnodes-1, ssplayer); + HWR_RenderBSPNode((INT32)numnodes-1); #ifndef NEWCLIP // Make a viewangle int so we can render things based on mouselook if (player == &players[consoleplayer]) - viewangle = localaiming; - else if (splitscreen && player == &players[secondarydisplayplayer]) - viewangle = localaiming2; - else if (splitscreen > 1 && player == &players[thirddisplayplayer]) - viewangle = localaiming3; - else if (splitscreen > 2 && player == &players[fourthdisplayplayer]) - viewangle = localaiming4; + viewangle = localaiming[0]; + else if (splitscreen && player == &players[displayplayers[1]]) + viewangle = localaiming[1]; + else if (splitscreen > 1 && player == &players[displayplayers[2]]) + viewangle = localaiming[2]; + else if (splitscreen > 2 && player == &players[displayplayers[3]]) + viewangle = localaiming[3]; // Handle stuff when you are looking farther up or down. if ((aimingangle || cv_fov.value+player->fovadd > 90*FRACUNIT)) { dup_viewangle += ANGLE_90; HWR_ClearClipSegs(); - HWR_RenderBSPNode((INT32)numnodes-1, ssplayer); //left + HWR_RenderBSPNode((INT32)numnodes-1); //left dup_viewangle += ANGLE_90; if (((INT32)aimingangle > ANGLE_45 || (INT32)aimingangle<-ANGLE_45)) { HWR_ClearClipSegs(); - HWR_RenderBSPNode((INT32)numnodes-1, ssplayer); //back + HWR_RenderBSPNode((INT32)numnodes-1); //back } dup_viewangle += ANGLE_90; HWR_ClearClipSegs(); - HWR_RenderBSPNode((INT32)numnodes-1, ssplayer); //right + HWR_RenderBSPNode((INT32)numnodes-1); //right dup_viewangle += ANGLE_90; } @@ -6802,16 +6752,17 @@ INT32 HWR_GetTextureUsed(void) void HWR_DoPostProcessor(player_t *player) { - postimg_t *type; + postimg_t *type = &postimgtype[0]; + UINT8 i; - if (splitscreen > 2 && player == &players[fourthdisplayplayer]) - type = &postimgtype4; - else if (splitscreen > 1 && player == &players[thirddisplayplayer]) - type = &postimgtype3; - else if (splitscreen && player == &players[secondarydisplayplayer]) - type = &postimgtype2; - else - type = &postimgtype; + for (i = splitscreen; i > 0; i--) + { + if (player == &players[displayplayers[i]]) + { + type = &postimgtype[i]; + break; + } + } // Armageddon Blast Flash! // Could this even be considered postprocessor? diff --git a/src/hu_stuff.c b/src/hu_stuff.c index cb9499c6d..b7eb1e1f3 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -39,7 +39,7 @@ #include "am_map.h" #include "d_main.h" -#include "p_local.h" // camera, camera2, camera3, camera4 +#include "p_local.h" // camera[] #include "p_tick.h" #ifdef HWRENDER @@ -1998,7 +1998,7 @@ static void HU_DrawChat_Old(void) if (!i) return; - if ((netgame || multiplayer) && players[displayplayer].spectator) + if ((netgame || multiplayer) && players[displayplayers[0]].spectator) return; #ifdef HWRENDER @@ -2025,7 +2025,7 @@ static inline void HU_DrawCrosshair2(void) if (!i) return; - if ((netgame || multiplayer) && players[secondarydisplayplayer].spectator) + if ((netgame || multiplayer) && players[displayplayers[1]].spectator) return; #ifdef HWRENDER @@ -2072,7 +2072,7 @@ static inline void HU_DrawCrosshair3(void) if (!i) return; - if ((netgame || multiplayer) && players[thirddisplayplayer].spectator) + if ((netgame || multiplayer) && players[displayplayers[2]].spectator) return; #ifdef HWRENDER @@ -2109,7 +2109,7 @@ static inline void HU_DrawCrosshair4(void) if (!i) return; - if ((netgame || multiplayer) && players[fourthdisplayplayer].spectator) + if ((netgame || multiplayer) && players[displayplayers[3]].spectator) return; #ifdef HWRENDER @@ -2210,8 +2210,16 @@ UINT32 hu_demolap; static void HU_DrawDemoInfo(void) { - V_DrawCenteredString((BASEVIDWIDTH/2), BASEVIDHEIGHT-40, 0, M_GetText("Replay:")); - V_DrawCenteredString((BASEVIDWIDTH/2), BASEVIDHEIGHT-32, V_ALLOWLOWERCASE, player_names[0]); + if (!multiplayer)/* netreplay */ + { + V_DrawCenteredString((BASEVIDWIDTH/2), BASEVIDHEIGHT-40, 0, M_GetText("Replay:")); + V_DrawCenteredString((BASEVIDWIDTH/2), BASEVIDHEIGHT-32, V_ALLOWLOWERCASE, player_names[0]); + } + else + { + V_DrawRightAlignedThinString(BASEVIDWIDTH-2, BASEVIDHEIGHT-10, V_ALLOWLOWERCASE, demo.titlename); + } + if (modeattacking) { V_DrawRightAlignedString((BASEVIDWIDTH/2)-4, BASEVIDHEIGHT-24, V_YELLOWMAP|V_MONOSPACE, "BEST TIME:"); @@ -2238,7 +2246,7 @@ static void HU_DrawDemoInfo(void) // // Song credits // -static void HU_DrawSongCredits(void) +void HU_DrawSongCredits(void) { char *str; INT32 len, destx; @@ -2329,10 +2337,7 @@ void HU_Drawer(void) if (cechotimer) HU_DrawCEcho(); - if (demoplayback && hu_showscores) - HU_DrawDemoInfo(); - - if (!Playing() + if (!( Playing() || demo.playback ) || gamestate == GS_INTERMISSION || gamestate == GS_CUTSCENE || gamestate == GS_CREDITS || gamestate == GS_EVALUATION || gamestate == GS_GAMEEND @@ -2352,24 +2357,28 @@ void HU_Drawer(void) LUAh_ScoresHUD(); #endif } + if (demo.playback) + { + HU_DrawDemoInfo(); + } } if (gamestate != GS_LEVEL) return; // draw the crosshair, not when viewing demos nor with chasecam - /*if (!automapactive && !demoplayback) + /*if (!automapactive && !demo.playback) { - if (cv_crosshair.value && !camera.chase && !players[displayplayer].spectator) + if (cv_crosshair.value && !camera[0].chase && !players[displayplayers[0]].spectator) HU_DrawCrosshair(); - if (cv_crosshair2.value && !camera2.chase && !players[secondarydisplayplayer].spectator) + if (cv_crosshair2.value && !camera[1].chase && !players[displayplayers[1]].spectator) HU_DrawCrosshair2(); - if (cv_crosshair3.value && !camera3.chase && !players[thirddisplayplayer].spectator) + if (cv_crosshair3.value && !camera[2].chase && !players[displayplayers[2]].spectator) HU_DrawCrosshair3(); - if (cv_crosshair4.value && !camera4.chase && !players[fourthdisplayplayer].spectator) + if (cv_crosshair4.value && !camera[3].chase && !players[displayplayers[3]].spectator) HU_DrawCrosshair4(); }*/ @@ -3011,7 +3020,7 @@ static void HU_DrawRankings(void) // When you play, you quickly see your score because your name is displayed in white. // When playing back a demo, you quickly see who's the view. if (!splitscreen) - whiteplayer = demoplayback ? displayplayer : consoleplayer; + whiteplayer = demo.playback ? displayplayers[0] : consoleplayer; scorelines = 0; memset(completed, 0, sizeof (completed)); diff --git a/src/hu_stuff.h b/src/hu_stuff.h index 0f316bc78..be6798a82 100644 --- a/src/hu_stuff.h +++ b/src/hu_stuff.h @@ -109,6 +109,7 @@ void HU_Start(void); boolean HU_Responder(event_t *ev); void HU_Ticker(void); +void HU_DrawSongCredits(void); void HU_Drawer(void); char HU_dequeueChatChar(void); void HU_Erase(void); diff --git a/src/k_kart.c b/src/k_kart.c index 288685c89..526564a6f 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -1062,34 +1062,14 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) } // This makes the roulette produce the random noises. - if ((player->kartstuff[k_itemroulette] % 3) == 1 && P_IsLocalPlayer(player)) + if ((player->kartstuff[k_itemroulette] % 3) == 1 && P_IsDisplayPlayer(player)) { -#define PLAYROULETTESND S_StartSound(NULL, sfx_itrol1 + ((player->kartstuff[k_itemroulette] / 3) % 8)); - if (splitscreen) +#define PLAYROULETTESND S_StartSound(NULL, sfx_itrol1 + ((player->kartstuff[k_itemroulette] / 3) % 8)) + for (i = 0; i <= splitscreen; i++) { - if (players[displayplayer].kartstuff[k_itemroulette]) - { - if (player == &players[displayplayer]) - PLAYROULETTESND; - } - else if (players[secondarydisplayplayer].kartstuff[k_itemroulette]) - { - if (player == &players[secondarydisplayplayer]) - PLAYROULETTESND; - } - else if (players[thirddisplayplayer].kartstuff[k_itemroulette] && splitscreen > 1) - { - if (player == &players[thirddisplayplayer]) - PLAYROULETTESND; - } - else if (players[fourthdisplayplayer].kartstuff[k_itemroulette] && splitscreen > 2) - { - if (player == &players[fourthdisplayplayer]) - PLAYROULETTESND; - } + if (player == &players[displayplayers[i]] && players[displayplayers[i]].kartstuff[k_itemroulette]) + PLAYROULETTESND; } - else - PLAYROULETTESND; #undef PLAYROULETTESND } @@ -1116,7 +1096,7 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) //player->kartstuff[k_itemblinkmode] = 1; player->kartstuff[k_itemroulette] = 0; player->kartstuff[k_roulettetype] = 0; - if (P_IsLocalPlayer(player)) + if (P_IsDisplayPlayer(player)) S_StartSound(NULL, sfx_itrole); return; } @@ -1129,7 +1109,7 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) player->kartstuff[k_itemblinkmode] = 2; player->kartstuff[k_itemroulette] = 0; player->kartstuff[k_roulettetype] = 0; - if (P_IsLocalPlayer(player)) + if (P_IsDisplayPlayer(player)) S_StartSound(NULL, sfx_dbgsal); return; } @@ -1161,7 +1141,7 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) player->kartstuff[k_itemamount] = 1; } - if (P_IsLocalPlayer(player)) + if (P_IsDisplayPlayer(player)) S_StartSound(NULL, ((player->kartstuff[k_roulettetype] == 1) ? sfx_itrolk : (mashed ? sfx_itrolm : sfx_itrolf))); player->kartstuff[k_itemblink] = TICRATE; @@ -4369,10 +4349,7 @@ static void K_UpdateEngineSounds(player_t *player, ticcmd_t *cmd) if (!playeringame[i] || !players[i].mo || players[i].spectator || players[i].exiting) continue; - if ((i == displayplayer) - || (i == secondarydisplayplayer && splitscreen) - || (i == thirddisplayplayer && splitscreen > 1) - || (i == fourthdisplayplayer && splitscreen > 2)) + if (P_IsDisplayPlayer(&players[i])) { volumedampen += FRACUNIT; // We already know what this is gonna be, let's not waste our time. continue; @@ -5745,13 +5722,13 @@ void K_MoveKartPlayer(player_t *player, boolean onground) if (player->kartstuff[k_hyudorotimer] >= (1*TICRATE/2) && player->kartstuff[k_hyudorotimer] <= hyudorotime-(1*TICRATE/2)) { - if (player == &players[secondarydisplayplayer]) + if (player == &players[displayplayers[1]]) player->mo->eflags |= MFE_DRAWONLYFORP2; - else if (player == &players[thirddisplayplayer] && splitscreen > 1) + else if (player == &players[displayplayers[2]] && splitscreen > 1) player->mo->eflags |= MFE_DRAWONLYFORP3; - else if (player == &players[fourthdisplayplayer] && splitscreen > 2) + else if (player == &players[displayplayers[3]] && splitscreen > 2) player->mo->eflags |= MFE_DRAWONLYFORP4; - else if (player == &players[consoleplayer]) + else if (player == &players[displayplayers[0]]) player->mo->eflags |= MFE_DRAWONLYFORP1; else player->mo->flags2 |= MF2_DONTDRAW; @@ -5761,8 +5738,8 @@ void K_MoveKartPlayer(player_t *player, boolean onground) } else { - if (player == &players[displayplayer] - || (player != &players[displayplayer] && (player->kartstuff[k_hyudorotimer] < (1*TICRATE/2) || player->kartstuff[k_hyudorotimer] > hyudorotime-(1*TICRATE/2)))) + if (P_IsDisplayPlayer(player) + || (!P_IsDisplayPlayer(player) && (player->kartstuff[k_hyudorotimer] < (1*TICRATE/2) || player->kartstuff[k_hyudorotimer] > hyudorotime-(1*TICRATE/2)))) { if (leveltime & 1) player->mo->flags2 |= MF2_DONTDRAW; @@ -5862,7 +5839,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) } // Play the starting countdown sounds - if (player == &players[displayplayer]) // Don't play louder in splitscreen + if (player == &players[displayplayers[0]]) // Don't play louder in splitscreen { if ((leveltime == starttime-(3*TICRATE)) || (leveltime == starttime-(2*TICRATE)) || (leveltime == starttime-TICRATE)) S_StartSound(NULL, sfx_s3ka7); @@ -6747,17 +6724,17 @@ INT32 K_calcSplitFlags(INT32 snapflags) if (splitscreen == 0) return snapflags; - if (stplyr != &players[displayplayer]) + if (stplyr != &players[displayplayers[0]]) { - if (splitscreen == 1 && stplyr == &players[secondarydisplayplayer]) + if (splitscreen == 1 && stplyr == &players[displayplayers[1]]) { splitflags |= V_SPLITSCREEN; } else if (splitscreen > 1) { - if (stplyr == &players[thirddisplayplayer] || stplyr == &players[fourthdisplayplayer]) + if (stplyr == &players[displayplayers[2]] || (splitscreen == 3 && stplyr == &players[displayplayers[3]])) splitflags |= V_SPLITSCREEN; - if (stplyr == &players[secondarydisplayplayer] || stplyr == &players[fourthdisplayplayer]) + if (stplyr == &players[displayplayers[1]] || (splitscreen == 3 && stplyr == &players[displayplayers[3]])) splitflags |= V_HORZSCREEN; } } @@ -7019,25 +6996,25 @@ static void K_drawKartItem(void) } // pain and suffering defined below - if (splitscreen < 2) // don't change shit for THIS splitscreen. + if (splitscreen < 2) // don't change shit for THIS splitscreen. { fx = ITEM_X; fy = ITEM_Y; fflags = K_calcSplitFlags(V_SNAPTOTOP|V_SNAPTOLEFT); } - else // now we're having a fun game. + else // now we're having a fun game. { - if (stplyr == &players[displayplayer] || stplyr == &players[thirddisplayplayer]) // If we are P1 or P3... + if (stplyr == &players[displayplayers[0]] || stplyr == &players[displayplayers[2]]) // If we are P1 or P3... { fx = ITEM_X; fy = ITEM_Y; - fflags = V_SNAPTOLEFT|((stplyr == &players[thirddisplayplayer]) ? V_SPLITSCREEN : V_SNAPTOTOP); // flip P3 to the bottom. + fflags = V_SNAPTOLEFT|((stplyr == &players[displayplayers[2]]) ? V_SPLITSCREEN : V_SNAPTOTOP); // flip P3 to the bottom. } else // else, that means we're P2 or P4. { fx = ITEM2_X; fy = ITEM2_Y; - fflags = V_SNAPTORIGHT|((stplyr == &players[fourthdisplayplayer]) ? V_SPLITSCREEN : V_SNAPTOTOP); // flip P4 to the bottom + fflags = V_SNAPTORIGHT|((stplyr == &players[displayplayers[3]]) ? V_SPLITSCREEN : V_SNAPTOTOP); // flip P4 to the bottom flipamount = true; } } @@ -7050,10 +7027,10 @@ static void K_drawKartItem(void) // Then, the numbers: if (stplyr->kartstuff[k_itemamount] >= numberdisplaymin && !stplyr->kartstuff[k_itemroulette]) { - V_DrawScaledPatch(fx + (flipamount ? 48 : 0), fy, V_HUDTRANS|fflags|(flipamount ? V_FLIP : 0), kp_itemmulsticker[offset]); // flip this graphic for p2 and p4 in split and shift it. + V_DrawScaledPatch(fx + (flipamount ? 48 : 0), fy, V_HUDTRANS|fflags|(flipamount ? V_FLIP : 0), kp_itemmulsticker[offset]); // flip this graphic for p2 and p4 in split and shift it. V_DrawFixedPatch(fx<kartstuff[k_itemamount])); else V_DrawString(fx+24, fy+31, V_ALLOWLOWERCASE|V_HUDTRANS|fflags, va("x%d", stplyr->kartstuff[k_itemamount])); @@ -7169,7 +7146,7 @@ void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT16 emblemmap, UI else if ((drawtime/TICRATE) & 1) V_DrawKartString(TX, TY+3, splitflags, va("99'59\"99")); - if (emblemmap && (modeattacking || (mode == 1)) && !demoplayback) // emblem time! + if (emblemmap && (modeattacking || (mode == 1)) && !demo.playback) // emblem time! { INT32 workx = TX + 96, worky = TY+18; SINT8 curemb = 0; @@ -7280,7 +7257,7 @@ static void K_DrawKartPositionNum(INT32 num) else if (splitscreen == 1) // for this splitscreen, we'll use case by case because it's a bit different. { fx = POSI_X; - if (stplyr == &players[displayplayer]) // for player 1: display this at the top right, above the minimap. + if (stplyr == &players[displayplayers[0]]) // for player 1: display this at the top right, above the minimap. { fy = 30; fflags = V_SNAPTOTOP|V_SNAPTORIGHT; @@ -7295,11 +7272,11 @@ static void K_DrawKartPositionNum(INT32 num) } else { - if (stplyr == &players[displayplayer] || stplyr == &players[thirddisplayplayer]) // If we are P1 or P3... + if (stplyr == &players[displayplayers[0]] || stplyr == &players[displayplayers[2]]) // If we are P1 or P3... { fx = POSI_X; fy = POSI_Y; - fflags = V_SNAPTOLEFT|((stplyr == &players[thirddisplayplayer]) ? V_SPLITSCREEN|V_SNAPTOBOTTOM : 0); // flip P3 to the bottom. + fflags = V_SNAPTOLEFT|((stplyr == &players[displayplayers[2]]) ? V_SPLITSCREEN|V_SNAPTOBOTTOM : 0); // flip P3 to the bottom. flipdraw = true; if (num && num >= 10) fx += W; // this seems dumb, but we need to do this in order for positions above 10 going off screen. @@ -7308,7 +7285,7 @@ static void K_DrawKartPositionNum(INT32 num) { fx = POSI2_X; fy = POSI2_Y; - fflags = V_SNAPTORIGHT|((stplyr == &players[fourthdisplayplayer]) ? V_SPLITSCREEN|V_SNAPTOBOTTOM : 0); // flip P4 to the bottom + fflags = V_SNAPTORIGHT|((stplyr == &players[displayplayers[3]]) ? V_SPLITSCREEN|V_SNAPTOBOTTOM : 0); // flip P4 to the bottom } } @@ -7634,17 +7611,17 @@ static void K_drawKartLaps(void) } else { - if (stplyr == &players[displayplayer] || stplyr == &players[thirddisplayplayer]) // If we are P1 or P3... + if (stplyr == &players[displayplayers[0]] || stplyr == &players[displayplayers[2]]) // If we are P1 or P3... { fx = LAPS_X; fy = LAPS_Y; - fflags = V_SNAPTOLEFT|((stplyr == &players[thirddisplayplayer]) ? V_SPLITSCREEN|V_SNAPTOBOTTOM : 0); // flip P3 to the bottom. + fflags = V_SNAPTOLEFT|((stplyr == &players[displayplayers[2]]) ? V_SPLITSCREEN|V_SNAPTOBOTTOM : 0); // flip P3 to the bottom. } else // else, that means we're P2 or P4. { fx = LAPS2_X; fy = LAPS2_Y; - fflags = V_SNAPTORIGHT|((stplyr == &players[fourthdisplayplayer]) ? V_SPLITSCREEN|V_SNAPTOBOTTOM : 0); // flip P4 to the bottom + fflags = V_SNAPTORIGHT|((stplyr == &players[displayplayers[3]]) ? V_SPLITSCREEN|V_SNAPTOBOTTOM : 0); // flip P4 to the bottom flipstring = true; // make the string right aligned and other shit } } @@ -7716,17 +7693,17 @@ static void K_drawKartBumpersOrKarma(void) // we will reuse lap coords here since it's essentially the same shit. - if (stplyr == &players[displayplayer] || stplyr == &players[thirddisplayplayer]) // If we are P1 or P3... + if (stplyr == &players[displayplayers[0]] || stplyr == &players[displayplayers[2]]) // If we are P1 or P3... { fx = LAPS_X; fy = LAPS_Y; - fflags = V_SNAPTOLEFT|((stplyr == &players[thirddisplayplayer]) ? V_SPLITSCREEN|V_SNAPTOBOTTOM : 0); // flip P3 to the bottom. + fflags = V_SNAPTOLEFT|((stplyr == &players[displayplayers[2]]) ? V_SPLITSCREEN|V_SNAPTOBOTTOM : 0); // flip P3 to the bottom. } else // else, that means we're P2 or P4. { fx = LAPS2_X; fy = LAPS2_Y; - fflags = V_SNAPTORIGHT|((stplyr == &players[fourthdisplayplayer]) ? V_SPLITSCREEN|V_SNAPTOBOTTOM : 0); // flip P4 to the bottom + fflags = V_SNAPTORIGHT|((stplyr == &players[displayplayers[3]]) ? V_SPLITSCREEN|V_SNAPTOBOTTOM : 0); // flip P4 to the bottom flipstring = true; } @@ -7803,7 +7780,7 @@ static void K_drawKartWanted(void) UINT8 *colormap = NULL; INT32 basex = 0, basey = 0; - if (stplyr != &players[displayplayer]) + if (stplyr != &players[displayplayers[0]]) return; for (i = 0; i < 4; i++) @@ -7883,7 +7860,7 @@ static void K_drawKartPlayerCheck(void) if (stplyr->awayviewtics) return; - if (camspin) + if (camspin[0]) return; for (i = 0; i < MAXPLAYERS; i++) @@ -8029,7 +8006,7 @@ static void K_drawKartMinimap(void) if (gamestate != GS_LEVEL) return; - if (stplyr != &players[displayplayer]) + if (stplyr != &players[displayplayers[0]]) return; lumpnum = W_CheckNumForName(va("%sR", G_BuildMapName(gamemap))); @@ -8103,7 +8080,7 @@ static void K_drawKartMinimap(void) if (!players[i].mo || players[i].spectator) continue; - if (i != displayplayer || splitscreen) + if (i != displayplayers[0] || splitscreen) { if (G_BattleGametype() && players[i].kartstuff[k_bumper] <= 0) continue; @@ -8117,7 +8094,7 @@ static void K_drawKartMinimap(void) } } - if (i == displayplayer || i == secondarydisplayplayer || i == thirddisplayplayer || i == fourthdisplayplayer) + if (P_IsDisplayPlayer(&players[i])) { // Draw display players on top of everything else localplayers[numlocalplayers] = i; @@ -8187,7 +8164,7 @@ static void K_drawKartFinish(void) xval = (SHORT(kp_racefinish[pnum]->width)<kartstuff[k_cardanimation])*(xval > x ? xval : x))/TICRATE; - if (splitscreen && stplyr == &players[secondarydisplayplayer]) + if (splitscreen && stplyr == &players[displayplayers[1]]) x = -x; V_DrawFixedPatch(x + (STCD_X<>1), @@ -8206,9 +8183,9 @@ static void K_drawBattleFullscreen(void) if (splitscreen) { - if ((splitscreen == 1 && stplyr == &players[secondarydisplayplayer]) - || (splitscreen > 1 && (stplyr == &players[thirddisplayplayer] - || (stplyr == &players[fourthdisplayplayer] && splitscreen > 2)))) + if ((splitscreen == 1 && stplyr == &players[displayplayers[1]]) + || (splitscreen > 1 && (stplyr == &players[displayplayers[2]] + || (stplyr == &players[displayplayers[3]] && splitscreen > 2)))) { y = 232-(stplyr->kartstuff[k_cardanimation]/2); splitflags = V_SNAPTOBOTTOM; @@ -8220,8 +8197,8 @@ static void K_drawBattleFullscreen(void) { scale /= 2; - if (stplyr == &players[secondarydisplayplayer] - || (stplyr == &players[fourthdisplayplayer] && splitscreen > 2)) + if (stplyr == &players[displayplayers[1]] + || (stplyr == &players[displayplayers[3]] && splitscreen > 2)) x = 3*BASEVIDWIDTH/4; else x = BASEVIDWIDTH/4; @@ -8230,7 +8207,7 @@ static void K_drawBattleFullscreen(void) { if (stplyr->exiting) { - if (stplyr == &players[secondarydisplayplayer]) + if (stplyr == &players[displayplayers[1]]) x = BASEVIDWIDTH-96; else x = 96; @@ -8242,7 +8219,7 @@ static void K_drawBattleFullscreen(void) if (stplyr->exiting) { - if (stplyr == &players[displayplayer]) + if (stplyr == &players[displayplayers[0]]) V_DrawFadeScreen(0xFF00, 16); if (stplyr->exiting < 6*TICRATE && !stplyr->spectator) { @@ -8272,9 +8249,9 @@ static void K_drawBattleFullscreen(void) { if (splitscreen > 1) ty = (BASEVIDHEIGHT/4)+33; - if ((splitscreen == 1 && stplyr == &players[secondarydisplayplayer]) - || (stplyr == &players[thirddisplayplayer] && splitscreen > 1) - || (stplyr == &players[fourthdisplayplayer] && splitscreen > 2)) + if ((splitscreen == 1 && stplyr == &players[displayplayers[1]]) + || (stplyr == &players[displayplayers[2]] && splitscreen > 1) + || (stplyr == &players[displayplayers[3]] && splitscreen > 2)) ty += (BASEVIDHEIGHT/2); } else @@ -8301,7 +8278,7 @@ static void K_drawBattleFullscreen(void) // check to see if there's anyone else at all for (i = 0; i < MAXPLAYERS; i++) { - if (i == displayplayer) + if (i == displayplayers[0]) continue; if (playeringame[i] && !stplyr->spectator) return; @@ -8327,11 +8304,11 @@ static void K_drawKartFirstPerson(void) if (stplyr->spectator || !stplyr->mo || (stplyr->mo->flags2 & MF2_DONTDRAW)) return; - if (stplyr == &players[secondarydisplayplayer] && splitscreen) + if (stplyr == &players[displayplayers[1]] && splitscreen) { pn = pnum[1]; tn = turn[1]; dr = drift[1]; } - else if (stplyr == &players[thirddisplayplayer] && splitscreen > 1) + else if (stplyr == &players[displayplayers[2]] && splitscreen > 1) { pn = pnum[2]; tn = turn[2]; dr = drift[2]; } - else if (stplyr == &players[fourthdisplayplayer] && splitscreen > 2) + else if (stplyr == &players[displayplayers[3]] && splitscreen > 2) { pn = pnum[3]; tn = turn[3]; dr = drift[3]; } else { pn = pnum[0]; tn = turn[0]; dr = drift[0]; } @@ -8456,11 +8433,11 @@ static void K_drawKartFirstPerson(void) V_DrawFixedPatch(x, y, scale, splitflags, kp_fpview[target], colmap); - if (stplyr == &players[secondarydisplayplayer] && splitscreen) + if (stplyr == &players[displayplayers[1]] && splitscreen) { pnum[1] = pn; turn[1] = tn; drift[1] = dr; } - else if (stplyr == &players[thirddisplayplayer] && splitscreen > 1) + else if (stplyr == &players[displayplayers[2]] && splitscreen > 1) { pnum[2] = pn; turn[2] = tn; drift[2] = dr; } - else if (stplyr == &players[fourthdisplayplayer] && splitscreen > 2) + else if (stplyr == &players[displayplayers[3]] && splitscreen > 2) { pnum[3] = pn; turn[3] = tn; drift[3] = dr; } else { pnum[0] = pn; turn[0] = tn; drift[0] = dr; } @@ -8482,19 +8459,12 @@ static void K_drawInput(void) if (timeinmap < 113) { INT32 count = ((INT32)(timeinmap) - 105); - offs = (titledemo ? 128 : 64); + offs = 64; while (count-- > 0) offs >>= 1; x += offs; } - if (titledemo) - { - V_DrawTinyScaledPatch(x-54, 128, splitflags, W_CachePatchName("TTKBANNR", PU_CACHE)); - V_DrawTinyScaledPatch(x-54, 128+25, splitflags, W_CachePatchName("TTKART", PU_CACHE)); - return; - } - #define BUTTW 8 #define BUTTH 11 @@ -8688,7 +8658,7 @@ static void K_drawDistributionDebugger(void) boolean dontforcespb = false; boolean spbrush = false; - if (stplyr != &players[displayplayer]) // only for p1 + if (stplyr != &players[displayplayers[0]]) // only for p1 return; // The only code duplication from the Kart, just to avoid the actual item function from calculating pingame twice @@ -8752,7 +8722,7 @@ static void K_drawDistributionDebugger(void) static void K_drawCheckpointDebugger(void) { - if (stplyr != &players[displayplayer]) // only for p1 + if (stplyr != &players[displayplayers[0]]) // only for p1 return; if (stplyr->starpostnum >= (numstarposts - (numstarposts/2))) @@ -8766,20 +8736,21 @@ void K_drawKartHUD(void) { boolean isfreeplay = false; boolean battlefullscreen = false; + UINT8 i; // Define the X and Y for each drawn object // This is handled by console/menu values K_initKartHUD(); // Draw that fun first person HUD! Drawn ASAP so it looks more "real". - if ((stplyr == &players[displayplayer] && !camera.chase) - || ((splitscreen && stplyr == &players[secondarydisplayplayer]) && !camera2.chase) - || ((splitscreen > 1 && stplyr == &players[thirddisplayplayer]) && !camera3.chase) - || ((splitscreen > 2 && stplyr == &players[fourthdisplayplayer]) && !camera4.chase)) - K_drawKartFirstPerson(); + for (i = 0; i <= splitscreen; i++) + { + if (stplyr == &players[displayplayers[i]] && !camera[i].chase) + K_drawKartFirstPerson(); + } // Draw full screen stuff that turns off the rest of the HUD - if (mapreset && stplyr == &players[displayplayer]) + if (mapreset && stplyr == &players[displayplayers[0]]) { K_drawChallengerScreen(); return; @@ -8792,10 +8763,10 @@ void K_drawKartHUD(void) && comeback && stplyr->playerstate == PST_LIVE))); - if (!battlefullscreen || splitscreen) + if (!demo.title && (!battlefullscreen || splitscreen)) { // Draw the CHECK indicator before the other items, so it's overlapped by everything else - if (cv_kartcheck.value && !splitscreen && !players[displayplayer].exiting) + if (cv_kartcheck.value && !splitscreen && !players[displayplayers[0]].exiting) K_drawKartPlayerCheck(); // Draw WANTED status @@ -8807,7 +8778,7 @@ void K_drawKartHUD(void) K_drawKartWanted(); } - if (cv_kartminimap.value && !titledemo) + if (cv_kartminimap.value) { #ifdef HAVE_BLUA if (LUA_HudEnabled(hud_minimap)) @@ -8829,7 +8800,7 @@ void K_drawKartHUD(void) K_drawKartItem(); // If not splitscreen, draw... - if (!splitscreen && !titledemo) + if (!splitscreen && !demo.title) { // Draw the timestamp #ifdef HAVE_BLUA @@ -8849,25 +8820,44 @@ void K_drawKartHUD(void) if (!stplyr->spectator) // Bottom of the screen elements, don't need in spectate mode { - if (G_RaceGametype()) // Race-only elements + if (demo.title) // Draw title logo instead in demo.titles { - if (!titledemo) - { - // Draw the lap counter -#ifdef HAVE_BLUA - if (LUA_HudEnabled(hud_gametypeinfo)) -#endif - K_drawKartLaps(); + INT32 x = BASEVIDWIDTH - 32, y = 128, offs; - if (!splitscreen) - { - // Draw the speedometer - // TODO: Make a better speedometer. + if (splitscreen == 3) + { + x = BASEVIDWIDTH/2 + 10; + y = BASEVIDHEIGHT/2 - 30; + } + + if (timeinmap < 113) + { + INT32 count = ((INT32)(timeinmap) - 104); + offs = 256; + while (count-- > 0) + offs >>= 1; + x += offs; + } + + V_DrawTinyScaledPatch(x-54, y, 0, W_CachePatchName("TTKBANNR", PU_CACHE)); + V_DrawTinyScaledPatch(x-54, y+25, 0, W_CachePatchName("TTKART", PU_CACHE)); + } + else if (G_RaceGametype()) // Race-only elements + { + // Draw the lap counter #ifdef HAVE_BLUA - if (LUA_HudEnabled(hud_speedometer)) + if (LUA_HudEnabled(hud_gametypeinfo)) #endif - K_drawKartSpeedometer(); - } + K_drawKartLaps(); + + if (!splitscreen) + { + // Draw the speedometer + // TODO: Make a better speedometer. +#ifdef HAVE_BLUA + if (LUA_HudEnabled(hud_speedometer)) +#endif + K_drawKartSpeedometer(); } if (isfreeplay) @@ -8880,7 +8870,7 @@ void K_drawKartHUD(void) #endif K_DrawKartPositionNum(stplyr->kartstuff[k_position]); } - else //if (!(demoplayback && hu_showscores)) + else //if (!(demo.playback && hu_showscores)) { // Draw the input UI #ifdef HAVE_BLUA diff --git a/src/lua_baselib.c b/src/lua_baselib.c index 541514530..17d06b1d6 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -163,6 +163,7 @@ static int lib_pRandomFixed(lua_State *L) { NOHUD lua_pushfixed(L, P_RandomFixed()); + demo_writerng = 2; return 1; } @@ -170,6 +171,7 @@ static int lib_pRandomByte(lua_State *L) { NOHUD lua_pushinteger(L, P_RandomByte()); + demo_writerng = 2; return 1; } @@ -181,6 +183,7 @@ static int lib_pRandomKey(lua_State *L) if (a > 65536) LUA_UsageWarning(L, "P_RandomKey: range > 65536 is undefined behavior"); lua_pushinteger(L, P_RandomKey(a)); + demo_writerng = 2; return 1; } @@ -198,6 +201,7 @@ static int lib_pRandomRange(lua_State *L) if ((b-a+1) > 65536) LUA_UsageWarning(L, "P_RandomRange: range > 65536 is undefined behavior"); lua_pushinteger(L, P_RandomRange(a, b)); + demo_writerng = 2; return 1; } @@ -207,6 +211,7 @@ static int lib_pRandom(lua_State *L) NOHUD LUA_Deprecated(L, "P_Random", "P_RandomByte"); lua_pushinteger(L, P_RandomByte()); + demo_writerng = 2; return 1; } @@ -214,6 +219,7 @@ static int lib_pSignedRandom(lua_State *L) { NOHUD lua_pushinteger(L, P_SignedRandom()); + demo_writerng = 2; return 1; } @@ -222,6 +228,7 @@ static int lib_pRandomChance(lua_State *L) fixed_t p = luaL_checkfixed(L, 1); NOHUD lua_pushboolean(L, P_RandomChance(p)); + demo_writerng = 2; return 1; } diff --git a/src/lua_consolelib.c b/src/lua_consolelib.c index 0ea0c8097..299870e00 100644 --- a/src/lua_consolelib.c +++ b/src/lua_consolelib.c @@ -118,14 +118,14 @@ void COM_Lua_f(void) flags = (UINT8)lua_tointeger(gL, -1); lua_pop(gL, 1); // pop flags - if (flags & 2) // flag 2: splitscreen player command. + if (flags & 2) // flag 2: splitscreen player command. TODO: support 4P { if (!splitscreen) { lua_pop(gL, 1); // pop command info table return; // can't execute splitscreen command without player 2! } - playernum = secondarydisplayplayer; + playernum = displayplayers[1]; } if (netgame) diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c index fb6814b25..31beecf09 100644 --- a/src/lua_hudlib.c +++ b/src/lua_hudlib.c @@ -797,24 +797,24 @@ void LUAh_GameHUD(player_t *stplayr) lua_remove(gL, -3); // pop HUD LUA_PushUserdata(gL, stplayr, META_PLAYER); - if (splitscreen > 2 && stplayr == &players[fourthdisplayplayer]) + if (splitscreen > 2 && stplayr == &players[displayplayers[3]]) { - LUA_PushUserdata(gL, &camera4, META_CAMERA); + LUA_PushUserdata(gL, &camera[3], META_CAMERA); camnum = 4; } - else if (splitscreen > 1 && stplayr == &players[thirddisplayplayer]) + else if (splitscreen > 1 && stplayr == &players[displayplayers[2]]) { - LUA_PushUserdata(gL, &camera3, META_CAMERA); + LUA_PushUserdata(gL, &camera[2], META_CAMERA); camnum = 3; } - else if (splitscreen && stplayr == &players[secondarydisplayplayer]) + else if (splitscreen && stplayr == &players[displayplayers[1]]) { - LUA_PushUserdata(gL, &camera2, META_CAMERA); + LUA_PushUserdata(gL, &camera[1], META_CAMERA); camnum = 2; } else { - LUA_PushUserdata(gL, &camera, META_CAMERA); + LUA_PushUserdata(gL, &camera[0], META_CAMERA); camnum = 1; } diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c index b56538d0f..dfb344e34 100644 --- a/src/lua_mobjlib.c +++ b/src/lua_mobjlib.c @@ -421,13 +421,13 @@ static int mobj_set(lua_State *L) case mobj_angle: mo->angle = luaL_checkangle(L, 3); if (mo->player == &players[consoleplayer]) - localangle = mo->angle; - else if (mo->player == &players[secondarydisplayplayer]) - localangle2 = mo->angle; - else if (mo->player == &players[thirddisplayplayer]) - localangle3 = mo->angle; - else if (mo->player == &players[fourthdisplayplayer]) - localangle4 = mo->angle; + localangle[0] = mo->angle; + else if (mo->player == &players[displayplayers[1]]) + localangle[1] = mo->angle; + else if (mo->player == &players[displayplayers[2]]) + localangle[2] = mo->angle; + else if (mo->player == &players[displayplayers[3]]) + localangle[3] = mo->angle; break; case mobj_sprite: mo->sprite = luaL_checkinteger(L, 3); diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index 46f996b68..3cca1f91f 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -354,13 +354,13 @@ static int player_set(lua_State *L) else if (fastcmp(field,"aiming")) { plr->aiming = luaL_checkangle(L, 3); if (plr == &players[consoleplayer]) - localaiming = plr->aiming; - else if (plr == &players[secondarydisplayplayer]) - localaiming2 = plr->aiming; - else if (plr == &players[thirddisplayplayer]) - localaiming3 = plr->aiming; - else if (plr == &players[fourthdisplayplayer]) - localaiming4 = plr->aiming; + localaiming[0] = plr->aiming; + else if (plr == &players[displayplayers[1]]) + localaiming[1] = plr->aiming; + else if (plr == &players[displayplayers[2]]) + localaiming[2] = plr->aiming; + else if (plr == &players[displayplayers[3]]) + localaiming[3] = plr->aiming; } else if (fastcmp(field,"health")) plr->health = (INT32)luaL_checkinteger(L, 3); diff --git a/src/m_cheat.c b/src/m_cheat.c index 2fc2e85c9..e7e877ada 100644 --- a/src/m_cheat.c +++ b/src/m_cheat.c @@ -273,7 +273,7 @@ boolean cht_Responder(event_t *ev) #define REQUIRE_OBJECTPLACE if (!objectplacing)\ { CONS_Printf(M_GetText("OBJECTPLACE must be enabled.\n")); return; } -#define REQUIRE_INLEVEL if (gamestate != GS_LEVEL || demoplayback)\ +#define REQUIRE_INLEVEL if (gamestate != GS_LEVEL || demo.playback)\ { CONS_Printf(M_GetText("You must be in a level to use this.\n")); return; } #define REQUIRE_SINGLEPLAYER if (netgame || multiplayer)\ diff --git a/src/m_menu.c b/src/m_menu.c index 83f89be5e..0f7e285b6 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -233,7 +233,9 @@ static char *M_GetConditionString(condition_t cond); menu_t SR_MainDef, SR_UnlockChecklistDef; // Misc. Main Menu +#if 0 // Bring this back when we have actual single-player static void M_SinglePlayerMenu(INT32 choice); +#endif static void M_Options(INT32 choice); static void M_Manual(INT32 choice); static void M_SelectableClearMenus(INT32 choice); @@ -317,7 +319,7 @@ menu_t OP_SoundOptionsDef; //static void M_RestartAudio(void); //Misc -menu_t /*OP_DataOptionsDef,*/ OP_ScreenshotOptionsDef, OP_EraseDataDef; +menu_t OP_DataOptionsDef, OP_ScreenshotOptionsDef, OP_EraseDataDef; menu_t OP_HUDOptionsDef, OP_ChatOptionsDef; menu_t OP_GameOptionsDef, OP_ServerOptionsDef; #ifndef NONET @@ -334,8 +336,29 @@ static patch_t *addonsp[NUM_EXT+5]; #define numaddonsshown 4 +// Replay hut +menu_t MISC_ReplayHutDef; +menu_t MISC_ReplayOptionsDef; +static void M_HandleReplayHutList(INT32 choice); +static void M_DrawReplayHut(void); +static void M_DrawReplayStartMenu(void); +static boolean M_QuitReplayHut(void); +static void M_HutStartReplay(INT32 choice); + +static void M_DrawPlaybackMenu(void); +static void M_PlaybackRewind(INT32 choice); +static void M_PlaybackPause(INT32 choice); +static void M_PlaybackFastForward(INT32 choice); +static void M_PlaybackAdvance(INT32 choice); +static void M_PlaybackSetViews(INT32 choice); +static void M_PlaybackAdjustView(INT32 choice); +static void M_PlaybackQuit(INT32 choice); + +static UINT8 playback_enterheld = 0; // horrid hack to prevent holding the button from being extremely fucked + // Drawing functions static void M_DrawGenericMenu(void); +static void M_DrawGenericBackgroundMenu(void); static void M_DrawCenteredMenu(void); static void M_DrawAddons(void); static void M_DrawSkyRoom(void); @@ -500,12 +523,13 @@ static consvar_t cv_dummystaff = {"dummystaff", "0", CV_HIDEN|CV_CALL, dummystaf // --------- static menuitem_t MainMenu[] = { - {IT_SUBMENU|IT_STRING, NULL, "Extras", &SR_UnlockChecklistDef, 76}, - {IT_CALL |IT_STRING, NULL, "1 Player", M_SinglePlayerMenu, 84}, - {IT_SUBMENU|IT_STRING, NULL, "Multiplayer", &MP_MainDef, 92}, - {IT_CALL |IT_STRING, NULL, "Options", M_Options, 100}, - {IT_CALL |IT_STRING, NULL, "Addons", M_Addons, 108}, - {IT_CALL |IT_STRING, NULL, "Quit Game", M_QuitSRB2, 116}, + {IT_SUBMENU|IT_STRING, NULL, "Extras", &SR_MainDef, 76}, + //{IT_CALL |IT_STRING, NULL, "1 Player", M_SinglePlayerMenu, 84}, + {IT_CALL |IT_STRING, NULL, "Time Attack", M_TimeAttack, 84}, + {IT_SUBMENU|IT_STRING, NULL, "Multiplayer", &MP_MainDef, 92}, + {IT_CALL |IT_STRING, NULL, "Options", M_Options, 100}, + {IT_CALL |IT_STRING, NULL, "Addons", M_Addons, 108}, + {IT_CALL |IT_STRING, NULL, "Quit Game", M_QuitSRB2, 116}, }; typedef enum @@ -523,6 +547,65 @@ static menuitem_t MISC_AddonsMenu[] = {IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleAddons, 0}, // dummy menuitem for the control func }; +static menuitem_t MISC_ReplayHutMenu[] = +{ + {IT_KEYHANDLER|IT_NOTHING, NULL, "", M_HandleReplayHutList, 0}, // Dummy menuitem for the replay list + {IT_NOTHING, NULL, "", NULL, 0}, // Dummy for handling wrapping to the top of the menu.. +}; + +static menuitem_t MISC_ReplayStartMenu[] = +{ + {IT_CALL |IT_STRING, NULL, "Load Addons and Watch", M_HutStartReplay, 0}, + {IT_CALL |IT_STRING, NULL, "Watch Without Addons", M_HutStartReplay, 10}, + {IT_CALL |IT_STRING, NULL, "Watch Replay", M_HutStartReplay, 10}, + {IT_SUBMENU |IT_STRING, NULL, "Back", &MISC_ReplayHutDef, 30}, +}; + +static menuitem_t MISC_ReplayOptionsMenu[] = +{ + {IT_CVAR|IT_STRING, NULL, "Record Replays", &cv_recordmultiplayerdemos, 0}, + {IT_CVAR|IT_STRING, NULL, "Sync Check Interval", &cv_netdemosyncquality, 10}, +}; + +static menuitem_t PlaybackMenu[] = +{ + {IT_CALL | IT_STRING, "M_PHIDE", "Hide Menu", M_SelectableClearMenus, 0}, + + {IT_CALL | IT_STRING, "M_PREW", "Rewind", M_PlaybackRewind, 20}, + {IT_CALL | IT_STRING, "M_PPAUSE", "Pause", M_PlaybackPause, 36}, + {IT_CALL | IT_STRING, "M_PFFWD", "Fast-Foward", M_PlaybackFastForward, 52}, + {IT_CALL | IT_STRING, "M_PSTEPB", "Backup Frame", M_PlaybackRewind, 20}, + {IT_CALL | IT_STRING, "M_PRESUM", "Resume", M_PlaybackPause, 36}, + {IT_CALL | IT_STRING, "M_PFADV", "Advance Frame", M_PlaybackAdvance, 52}, + + {IT_ARROWS | IT_STRING, "M_PVIEWS", "View Count", M_PlaybackSetViews, 72}, + {IT_ARROWS | IT_STRING, "M_PNVIEW", "Viewpoint", M_PlaybackAdjustView, 88}, + {IT_ARROWS | IT_STRING, "M_PNVIEW", "Viewpoint 2", M_PlaybackAdjustView, 104}, + {IT_ARROWS | IT_STRING, "M_PNVIEW", "Viewpoint 3", M_PlaybackAdjustView, 120}, + {IT_ARROWS | IT_STRING, "M_PNVIEW", "Viewpoint 4", M_PlaybackAdjustView, 136}, + + //{IT_CALL | IT_STRING, "M_POPTS", "More Options...", M_ReplayHut, 156}, + //{IT_CALL | IT_STRING, "M_PEXIT", "Stop Playback", M_PlaybackQuit, 172}, + {IT_CALL | IT_STRING, "M_PEXIT", "Stop Playback", M_PlaybackQuit, 156}, +}; +typedef enum +{ + playback_hide, + playback_rewind, + playback_pause, + playback_fastforward, + playback_backframe, + playback_resume, + playback_advanceframe, + playback_viewcount, + playback_view1, + playback_view2, + playback_view3, + playback_view4, + //playback_moreoptions, + playback_quit +} playback_e; + // --------------------------------- // Pause Menu Mode Attacking Edition // --------------------------------- @@ -695,7 +778,9 @@ static menuitem_t SR_PandorasBox[] = // Sky Room Custom Unlocks static menuitem_t SR_MainMenu[] = { - {IT_STRING|IT_SUBMENU,NULL, "Secrets Checklist", &SR_UnlockChecklistDef, 0}, + {IT_STRING|IT_SUBMENU, NULL, "Unlockables", &SR_UnlockChecklistDef, 100}, + {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 {IT_DISABLED, NULL, "", NULL, 0}, // Custom2 {IT_DISABLED, NULL, "", NULL, 0}, // Custom3 @@ -1044,15 +1129,13 @@ static menuitem_t OP_MainMenu[] = {IT_SUBMENU|IT_STRING, NULL, "Sound Options...", &OP_SoundOptionsDef, 40}, {IT_SUBMENU|IT_STRING, NULL, "HUD Options...", &OP_HUDOptionsDef, 60}, - {IT_STRING|IT_CALL, NULL, "Screenshot Options...", M_ScreenshotOptions, 70}, + {IT_SUBMENU|IT_STRING, NULL, "Gameplay Options...", &OP_GameOptionsDef, 70}, + {IT_SUBMENU|IT_STRING, NULL, "Server Options...", &OP_ServerOptionsDef, 80}, - {IT_SUBMENU|IT_STRING, NULL, "Gameplay Options...", &OP_GameOptionsDef, 90}, - {IT_SUBMENU|IT_STRING, NULL, "Server Options...", &OP_ServerOptionsDef, 100}, - {IT_STRING|IT_CALL, NULL, "Add-on Options...", M_AddonsOptions, 110}, + {IT_SUBMENU|IT_STRING, NULL, "Data Options...", &OP_DataOptionsDef, 100}, - {IT_CALL|IT_STRING, NULL, "Tricks & Secrets (F1)", M_Manual, 130}, - {IT_CALL|IT_STRING, NULL, "Play Credits", M_Credits, 140}, - {IT_SUBMENU|IT_STRING, NULL, "Erase Data...", &OP_EraseDataDef, 150}, + {IT_CALL|IT_STRING, NULL, "Tricks & Secrets (F1)", M_Manual, 120}, + {IT_CALL|IT_STRING, NULL, "Play Credits", M_Credits, 130}, }; static menuitem_t OP_ControlsMenu[] = @@ -1325,12 +1408,14 @@ static menuitem_t OP_SoundOptionsMenu[] = {IT_STRING|IT_CVAR, NULL, "Play SFX While Unfocused", &cv_playsoundifunfocused, 135}, }; -/*static menuitem_t OP_DataOptionsMenu[] = +static menuitem_t OP_DataOptionsMenu[] = { - {IT_STRING | IT_CALL, NULL, "Screenshot Options...", M_ScreenshotOptions, 10}, + {IT_STRING | IT_CALL, NULL, "Screenshot Options...", M_ScreenshotOptions, 10}, + {IT_STRING | IT_CALL, NULL, "Add-on Options...", M_AddonsOptions, 20}, + {IT_STRING | IT_SUBMENU, NULL, "Replay Options...", &MISC_ReplayOptionsDef, 30}, - {IT_STRING | IT_SUBMENU, NULL, "Erase Data...", &OP_EraseDataDef, 30}, -};*/ + {IT_STRING | IT_SUBMENU, NULL, "Erase Data...", &OP_EraseDataDef, 50}, +}; static menuitem_t OP_ScreenshotOptionsMenu[] = { @@ -1571,7 +1656,7 @@ menu_t MISC_AddonsDef = { NULL, sizeof (MISC_AddonsMenu)/sizeof (menuitem_t), - &MainDef, + &OP_DataOptionsDef, MISC_AddonsMenu, M_DrawAddons, 50, 28, @@ -1579,6 +1664,54 @@ menu_t MISC_AddonsDef = NULL }; +menu_t MISC_ReplayHutDef = +{ + NULL, + sizeof (MISC_ReplayHutMenu)/sizeof (menuitem_t), + NULL, + MISC_ReplayHutMenu, + M_DrawReplayHut, + 30, 80, + 0, + M_QuitReplayHut +}; + +menu_t MISC_ReplayOptionsDef = +{ + "M_REPOPT", + sizeof (MISC_ReplayOptionsMenu)/sizeof (menuitem_t), + &OP_DataOptionsDef, + MISC_ReplayOptionsMenu, + M_DrawGenericMenu, + 27, 40, + 0, + NULL +}; + +menu_t MISC_ReplayStartDef = +{ + NULL, + sizeof (MISC_ReplayStartMenu)/sizeof (menuitem_t), + &MISC_ReplayHutDef, + MISC_ReplayStartMenu, + M_DrawReplayStartMenu, + 30, 90, + 0, + NULL +}; + +menu_t PlaybackMenuDef = { + NULL, + sizeof (PlaybackMenu)/sizeof (menuitem_t), + NULL, + PlaybackMenu, + M_DrawPlaybackMenu, + //BASEVIDWIDTH/2 - 94, 2, + BASEVIDWIDTH/2 - 88, 2, + 0, + NULL +}; + menu_t MAPauseDef = PAUSEMENUSTYLE(MAPauseMenu, 40, 72); menu_t SPauseDef = PAUSEMENUSTYLE(SPauseMenu, 40, 72); menu_t MPauseDef = PAUSEMENUSTYLE(MPauseMenu, 40, 72); @@ -1676,17 +1809,7 @@ menu_t SR_PandoraDef = 0, M_ExitPandorasBox }; -menu_t SR_MainDef = -{ - "M_SECRET", - sizeof (SR_MainMenu)/sizeof (menuitem_t), - &MainDef, - SR_MainMenu, - M_DrawSkyRoom, - 60, 40, - 0, - NULL -}; +menu_t SR_MainDef = CENTERMENUSTYLE(NULL, SR_MainMenu, &MainDef, 72); //menu_t SR_LevelSelectDef = MAPICONMENUSTYLE(NULL, SR_LevelSelectMenu, &SR_MainDef); @@ -1694,7 +1817,7 @@ menu_t SR_UnlockChecklistDef = { NULL, 1, - &MainDef, //&SR_MainDef + &SR_MainDef, SR_UnlockChecklistMenu, M_DrawChecklist, 280, 185, @@ -1732,7 +1855,7 @@ menu_t SP_LevelStatsDef = { "M_STATS", 1, - &SP_MainDef, + &SR_MainDef, SP_LevelStatsMenu, M_DrawLevelStats, 280, 185, @@ -2030,10 +2153,10 @@ menu_t OP_OpenGLColorDef = NULL }; #endif -//menu_t OP_DataOptionsDef = DEFAULTMENUSTYLE("M_DATA", OP_DataOptionsMenu, &OP_MainDef, 60, 30); -menu_t OP_ScreenshotOptionsDef = DEFAULTMENUSTYLE("M_SCSHOT", OP_ScreenshotOptionsMenu, &OP_MainDef, 30, 30); -menu_t OP_AddonsOptionsDef = DEFAULTMENUSTYLE("M_ADDONS", OP_AddonsOptionsMenu, &OP_MainDef, 30, 30); -menu_t OP_EraseDataDef = DEFAULTMENUSTYLE("M_DATA", OP_EraseDataMenu, &OP_MainDef, 30, 30); +menu_t OP_DataOptionsDef = DEFAULTMENUSTYLE("M_DATA", OP_DataOptionsMenu, &OP_MainDef, 60, 30); +menu_t OP_ScreenshotOptionsDef = DEFAULTMENUSTYLE("M_SCSHOT", OP_ScreenshotOptionsMenu, &OP_DataOptionsDef, 30, 30); +menu_t OP_AddonsOptionsDef = DEFAULTMENUSTYLE("M_ADDONS", OP_AddonsOptionsMenu, &OP_DataOptionsDef, 30, 30); +menu_t OP_EraseDataDef = DEFAULTMENUSTYLE("M_DATA", OP_EraseDataMenu, &OP_DataOptionsDef, 30, 30); // ========================================================================== // CVAR ONCHANGE EVENTS GO HERE @@ -2444,7 +2567,7 @@ boolean M_Responder(event_t *ev) static INT32 lastx = 0, lasty = 0; void (*routine)(INT32 choice); // for some casting problem - if (dedicated || (demoplayback && titledemo) + if (dedicated || (demo.playback && demo.title) || gamestate == GS_INTRO || gamestate == GS_CUTSCENE || gamestate == GS_GAMEEND || gamestate == GS_CREDITS || gamestate == GS_EVALUATION) return false; @@ -2564,8 +2687,6 @@ boolean M_Responder(event_t *ev) } } } - else if (ev->type == ev_keydown) // Preserve event for other responders - ch = ev->data1; if (ch == -1) return false; @@ -2711,6 +2832,19 @@ boolean M_Responder(event_t *ev) routine = M_ChangeCvar; } + if (currentMenu == &PlaybackMenuDef) + { + // Flip left/right with up/down for the playback menu, since it's a horizontal icon row. + switch (ch) + { + case KEY_LEFTARROW: ch = KEY_UPARROW; break; + case KEY_UPARROW: ch = KEY_RIGHTARROW; break; + case KEY_RIGHTARROW: ch = KEY_DOWNARROW; break; + case KEY_DOWNARROW: ch = KEY_LEFTARROW; break; + default: break; + } + } + // Keys usable within menu switch (ch) { @@ -2757,6 +2891,15 @@ boolean M_Responder(event_t *ev) case KEY_ENTER: noFurtherInput = true; currentMenu->lastOn = itemOn; + + if (currentMenu == &PlaybackMenuDef) + { + boolean held = (boolean)playback_enterheld; + playback_enterheld = TICRATE/7; + if (held) + return true; + } + if (routine) { if (((currentMenu->menuitems[itemOn].status & IT_TYPE)==IT_CALL @@ -2869,7 +3012,7 @@ void M_Drawer(void) if (menuactive) { // now that's more readable with a faded background (yeah like Quake...) - if (!WipeInAction) + if (!WipeInAction && currentMenu != &PlaybackMenuDef) // Replay playback has its own background V_DrawFadeScreen(0xFF00, 16); if (currentMenu->drawroutine) @@ -2915,14 +3058,6 @@ void M_Drawer(void) // void M_StartControlPanel(void) { - // time attack HACK - if (modeattacking && demoplayback) - { - G_CheckDemoStatus(); - S_ChangeMusicInternal("racent", true); - return; - } - // intro might call this repeatedly if (menuactive) { @@ -2932,7 +3067,11 @@ void M_StartControlPanel(void) menuactive = true; - if (!Playing()) + if (demo.playback) + { + currentMenu = &PlaybackMenuDef; + } + else if (!Playing()) { // Secret menu! //MainMenu[secrets].status = (M_AnySecretUnlocked()) ? (IT_STRING | IT_CALL) : (IT_DISABLED); @@ -3164,6 +3303,14 @@ void M_Ticker(void) if (--skullAnimCounter <= 0) skullAnimCounter = 8; + if (currentMenu == &PlaybackMenuDef) + { + if (playback_enterheld > 0) + playback_enterheld--; + } + else + playback_enterheld = 0; + //added : 30-01-98 : test mode for five seconds if (vidm_testingmode > 0) { @@ -3680,6 +3827,12 @@ static void M_DrawGenericMenu(void) } } +static void M_DrawGenericBackgroundMenu(void) +{ + V_DrawPatchFill(W_CachePatchName("SRB2BACK", PU_CACHE)); + M_DrawGenericMenu(); +} + static void M_DrawPauseMenu(void) { #if 0 @@ -4213,7 +4366,7 @@ void M_StartMessage(const char *string, void *routine, M_StartControlPanel(); // can't put menuactive to true if (currentMenu == &MessageDef) // Prevent recursion - MessageDef.prevMenu = &MainDef; + MessageDef.prevMenu = ((demo.playback) ? &PlaybackMenuDef : &MainDef); else MessageDef.prevMenu = currentMenu; @@ -4464,7 +4617,7 @@ static void M_Addons(INT32 choice) else --menupathindex[menudepthleft]; - if (!preparefilemenu(false)) + if (!preparefilemenu(false, false)) { M_StartMessage(va("No files/folders found.\n\n%s\n\n(Press a key)\n", (recommendedflags == V_SKYMAP ? LOCATIONSTRING2 : LOCATIONSTRING1)),NULL,MM_NOTHING); return; @@ -4588,7 +4741,7 @@ static void M_AddonsClearName(INT32 choice) // returns whether to do message draw static boolean M_AddonsRefresh(void) { - if ((refreshdirmenu & REFRESHDIR_NORMAL) && !preparefilemenu(true)) + if ((refreshdirmenu & REFRESHDIR_NORMAL) && !preparefilemenu(true, false)) { UNEXIST; if (refreshdirname) @@ -4837,7 +4990,7 @@ static void M_HandleAddons(INT32 choice) if (dirmenu && dirmenu[dir_on[menudepthleft]]) tempname = Z_StrDup(dirmenu[dir_on[menudepthleft]]+DIR_STRING); // don't need to I_Error if can't make - not important, just QoL #if 0 // much slower - if (!preparefilemenu(true)) + if (!preparefilemenu(true, false)) { UNEXIST; return; @@ -4891,13 +5044,13 @@ static void M_HandleAddons(INT32 choice) menupathindex[--menudepthleft] = strlen(menupath); menupath[menupathindex[menudepthleft]] = 0; - if (!preparefilemenu(false)) + if (!preparefilemenu(false, false)) { S_StartSound(NULL, sfx_s224); M_StartMessage(va("%c%s\x80\nThis folder is empty.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), M_AddonsHeaderPath()),NULL,MM_NOTHING); menupath[menupathindex[++menudepthleft]] = 0; - if (!preparefilemenu(true)) + if (!preparefilemenu(true, false)) { UNEXIST; return; @@ -4920,7 +5073,7 @@ static void M_HandleAddons(INT32 choice) case EXT_UP: S_StartSound(NULL, sfx_menu1); menupath[menupathindex[++menudepthleft]] = 0; - if (!preparefilemenu(false)) + if (!preparefilemenu(false, false)) { UNEXIST; return; @@ -4977,6 +5130,830 @@ static void M_HandleAddons(INT32 choice) } } +// ---- REPLAY HUT ----- +menudemo_t *demolist; + +#define DF_ENCORE 0x40 +static INT16 replayScrollTitle = 0; +static SINT8 replayScrollDelay = TICRATE, replayScrollDir = 1; + +static void PrepReplayList(void) +{ + size_t i; + + if (demolist) + Z_Free(demolist); + + demolist = Z_Calloc(sizeof(menudemo_t) * sizedirmenu, PU_STATIC, NULL); + + for (i = 0; i < sizedirmenu; i++) + { + if (dirmenu[i][DIR_TYPE] == EXT_UP) + { + demolist[i].type = MD_SUBDIR; + sprintf(demolist[i].title, "UP"); + } + else if (dirmenu[i][DIR_TYPE] == EXT_FOLDER) + { + demolist[i].type = MD_SUBDIR; + strncpy(demolist[i].title, dirmenu[i] + DIR_STRING, 64); + } + else + { + demolist[i].type = MD_NOTLOADED; + snprintf(demolist[i].filepath, 255, "%s%s", menupath, dirmenu[i] + DIR_STRING); + sprintf(demolist[i].title, "....."); + } + } +} + +void M_ReplayHut(INT32 choice) +{ + (void)choice; + + if (!demo.inreplayhut) + { + snprintf(menupath, 1024, "%s"PATHSEP"replay"PATHSEP"online"PATHSEP, srb2home); + menupathindex[(menudepthleft = menudepth-1)] = strlen(menupath); + } + if (!preparefilemenu(false, true)) + { + M_StartMessage("No replays found.\n\n(Press a key)\n", NULL, MM_NOTHING); + return; + } + else if (!demo.inreplayhut) + dir_on[menudepthleft] = 0; + demo.inreplayhut = true; + + replayScrollTitle = 0; replayScrollDelay = TICRATE; replayScrollDir = 1; + + PrepReplayList(); + + menuactive = true; + M_SetupNextMenu(&MISC_ReplayHutDef); + G_SetGamestate(GS_TIMEATTACK); + + demo.rewinding = false; + + S_ChangeMusicInternal("replst", true); +} + +static void M_HandleReplayHutList(INT32 choice) +{ + switch (choice) + { + case KEY_UPARROW: + if (dir_on[menudepthleft]) + dir_on[menudepthleft]--; + else + M_PrevOpt(); + + S_StartSound(NULL, sfx_menu1); + replayScrollTitle = 0; replayScrollDelay = TICRATE; replayScrollDir = 1; + break; + + case KEY_DOWNARROW: + if (dir_on[menudepthleft] < sizedirmenu-1) + dir_on[menudepthleft]++; + else + itemOn = 0; // Not M_NextOpt because that would take us to the extra dummy item + + S_StartSound(NULL, sfx_menu1); + replayScrollTitle = 0; replayScrollDelay = TICRATE; replayScrollDir = 1; + break; + + case KEY_ESCAPE: + M_QuitReplayHut(); + break; + + case KEY_ENTER: + switch (dirmenu[dir_on[menudepthleft]][DIR_TYPE]) + { + case EXT_FOLDER: + strcpy(&menupath[menupathindex[menudepthleft]],dirmenu[dir_on[menudepthleft]]+DIR_STRING); + if (menudepthleft) + { + menupathindex[--menudepthleft] = strlen(menupath); + menupath[menupathindex[menudepthleft]] = 0; + + if (!preparefilemenu(false, true)) + { + S_StartSound(NULL, sfx_s224); + M_StartMessage(va("%c%s\x80\nThis folder is empty.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), M_AddonsHeaderPath()),NULL,MM_NOTHING); + menupath[menupathindex[++menudepthleft]] = 0; + + if (!preparefilemenu(true, true)) + { + M_QuitReplayHut(); + return; + } + } + else + { + S_StartSound(NULL, sfx_menu1); + dir_on[menudepthleft] = 1; + PrepReplayList(); + } + } + else + { + S_StartSound(NULL, sfx_s26d); + M_StartMessage(va("%c%s\x80\nThis folder is too deep to navigate to!\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), M_AddonsHeaderPath()),NULL,MM_NOTHING); + menupath[menupathindex[menudepthleft]] = 0; + } + break; + case EXT_UP: + S_StartSound(NULL, sfx_menu1); + menupath[menupathindex[++menudepthleft]] = 0; + if (!preparefilemenu(false, true)) + { + M_QuitReplayHut(); + return; + } + PrepReplayList(); + break; + default: + // We can't just use M_SetupNextMenu because that'll run ReplayDef's quitroutine and boot us back to the title screen! + currentMenu->lastOn = itemOn; + currentMenu = &MISC_ReplayStartDef; + + replayScrollTitle = 0; replayScrollDelay = TICRATE; replayScrollDir = 1; + + switch (demolist[dir_on[menudepthleft]].addonstatus) + { + case DFILE_ERROR_CANNOTLOAD: + // Only show "Watch Replay Without Addons" + MISC_ReplayStartMenu[0].status = IT_DISABLED; + MISC_ReplayStartMenu[1].status = IT_CALL|IT_STRING; + //MISC_ReplayStartMenu[1].alphaKey = 0; + MISC_ReplayStartMenu[2].status = IT_DISABLED; + itemOn = 1; + break; + + case DFILE_ERROR_NOTLOADED: + case DFILE_ERROR_INCOMPLETEOUTOFORDER: + // Show "Load Addons and Watch Replay" and "Watch Replay Without Addons" + MISC_ReplayStartMenu[0].status = IT_CALL|IT_STRING; + MISC_ReplayStartMenu[1].status = IT_CALL|IT_STRING; + //MISC_ReplayStartMenu[1].alphaKey = 10; + MISC_ReplayStartMenu[2].status = IT_DISABLED; + itemOn = 0; + break; + + case DFILE_ERROR_EXTRAFILES: + case DFILE_ERROR_OUTOFORDER: + default: + // Show "Watch Replay" + MISC_ReplayStartMenu[0].status = IT_DISABLED; + MISC_ReplayStartMenu[1].status = IT_DISABLED; + MISC_ReplayStartMenu[2].status = IT_CALL|IT_STRING; + //MISC_ReplayStartMenu[2].alphaKey = 0; + itemOn = 2; + break; + } + } + + break; + } +} + +#define SCALEDVIEWWIDTH (vid.width/vid.dupx) +#define SCALEDVIEWHEIGHT (vid.height/vid.dupy) +static void DrawReplayHutReplayInfo(void) +{ + lumpnum_t lumpnum; + patch_t *patch; + UINT8 *colormap; + INT32 x, y, w, h; + + switch (demolist[dir_on[menudepthleft]].type) + { + case MD_NOTLOADED: + V_DrawCenteredString(160, 40, V_SNAPTOTOP, "Loading replay information..."); + break; + + case MD_INVALID: + V_DrawCenteredString(160, 40, V_SNAPTOTOP|warningflags, "This replay cannot be played."); + break; + + case MD_SUBDIR: + break; // Can't think of anything to draw here right now + + case MD_OUTDATED: + V_DrawThinString(17, 64, V_SNAPTOTOP|V_ALLOWLOWERCASE|V_TRANSLUCENT|highlightflags, "Recorded on an outdated version."); + /*fallthru*/ + default: + // Draw level stuff + x = 15; y = 15; + + // A 160x100 image of the level as entry MAPxxP + //CONS_Printf("%d %s\n", demolist[dir_on[menudepthleft]].map, G_BuildMapName(demolist[dir_on[menudepthleft]].map)); + lumpnum = W_CheckNumForName(va("%sP", G_BuildMapName(demolist[dir_on[menudepthleft]].map))); + if (lumpnum != LUMPERROR) + patch = W_CachePatchNum(lumpnum, PU_CACHE); + else + patch = W_CachePatchName("M_NOLVL", PU_CACHE); + + if (!(demolist[dir_on[menudepthleft]].kartspeed & DF_ENCORE)) + V_DrawSmallScaledPatch(x, y, V_SNAPTOTOP, patch); + else + { + w = SHORT(patch->width); + h = SHORT(patch->height); + V_DrawSmallScaledPatch(x+(w>>1), y, V_SNAPTOTOP|V_FLIP, patch); + + { + static angle_t rubyfloattime = 0; + const fixed_t rubyheight = FINESINE(rubyfloattime>>ANGLETOFINESHIFT); + V_DrawFixedPatch((x+(w>>2))<>2))<width), y+20, V_SNAPTOTOP, patch, colormap); + + break; + } +} + +static void M_DrawReplayHut(void) +{ + INT32 x, y, cursory = 0; + INT16 i; + INT16 replaylistitem = currentMenu->numitems-2; + boolean processed_one_this_frame = false; + + static UINT16 replayhutmenuy = 0; + + V_DrawPatchFill(W_CachePatchName("SRB2BACK", PU_CACHE)); + + if (cv_vhseffect.value) + V_DrawVhsEffect(false); + + // Draw menu choices + x = currentMenu->x; + y = currentMenu->y; + + if (itemOn > replaylistitem) + { + itemOn = replaylistitem; + dir_on[menudepthleft] = sizedirmenu-1; + replayScrollTitle = 0; replayScrollDelay = TICRATE; replayScrollDir = 1; + } + else if (itemOn < replaylistitem) + { + dir_on[menudepthleft] = 0; + replayScrollTitle = 0; replayScrollDelay = TICRATE; replayScrollDir = 1; + } + + if (itemOn == replaylistitem) + { + INT32 maxy; + // Scroll menu items if needed + cursory = y + currentMenu->menuitems[replaylistitem].alphaKey + dir_on[menudepthleft]*10; + maxy = y + currentMenu->menuitems[replaylistitem].alphaKey + sizedirmenu*10; + + if (cursory > maxy - 20) + cursory = maxy - 20; + + if (cursory - replayhutmenuy > SCALEDVIEWHEIGHT-50) + replayhutmenuy += (cursory-SCALEDVIEWHEIGHT-replayhutmenuy + 51)/2; + else if (cursory - replayhutmenuy < 110) + replayhutmenuy += (max(0, cursory-110)-replayhutmenuy - 1)/2; + } + else + replayhutmenuy /= 2; + + y -= replayhutmenuy; + + // Draw static menu items + for (i = 0; i < replaylistitem; i++) + { + INT32 localy = y + currentMenu->menuitems[i].alphaKey; + + if (localy < 65) + continue; + + if (i == itemOn) + cursory = localy; + + if ((currentMenu->menuitems[i].status & IT_DISPLAY)==IT_STRING) + V_DrawString(x, localy, V_SNAPTOTOP|V_SNAPTOLEFT, currentMenu->menuitems[i].text); + else + V_DrawString(x, localy, V_SNAPTOTOP|V_SNAPTOLEFT|highlightflags, currentMenu->menuitems[i].text); + } + + y += currentMenu->menuitems[replaylistitem].alphaKey; + + for (i = 0; i < (INT16)sizedirmenu; i++) + { + INT32 localy = y+i*10; + INT32 localx = x; + + if (localy < 65) + continue; + if (localy >= SCALEDVIEWHEIGHT) + break; + + if (demolist[i].type == MD_NOTLOADED && !processed_one_this_frame) + { + processed_one_this_frame = true; + G_LoadDemoInfo(&demolist[i]); + } + + if (demolist[i].type == MD_SUBDIR) + { + localx += 8; + V_DrawScaledPatch(x - 4, localy, V_SNAPTOTOP|V_SNAPTOLEFT, W_CachePatchName(dirmenu[i][DIR_TYPE] == EXT_UP ? "M_RBACK" : "M_RFLDR", PU_CACHE)); + } + + if (itemOn == replaylistitem && i == (INT16)dir_on[menudepthleft]) + { + cursory = localy; + + if (replayScrollDelay) + replayScrollDelay--; + else if (replayScrollDir > 0) + { + if (replayScrollTitle < (V_StringWidth(demolist[i].title, 0) - (SCALEDVIEWWIDTH - (x<<1)))<<1) + replayScrollTitle++; + else + { + replayScrollDelay = TICRATE; + replayScrollDir = -1; + } + } + else + { + if (replayScrollTitle > 0) + replayScrollTitle--; + else + { + replayScrollDelay = TICRATE; + replayScrollDir = 1; + } + } + + V_DrawString(localx - (replayScrollTitle>>1), localy, V_SNAPTOTOP|V_SNAPTOLEFT|highlightflags|V_ALLOWLOWERCASE, demolist[i].title); + } + else + V_DrawString(localx, localy, V_SNAPTOTOP|V_SNAPTOLEFT|V_ALLOWLOWERCASE, demolist[i].title); + } + + // Draw scrollbar + y = sizedirmenu*10 + currentMenu->menuitems[replaylistitem].alphaKey + 30; + if (y > SCALEDVIEWHEIGHT-80) + { + V_DrawFill(BASEVIDWIDTH-4, 75, 4, SCALEDVIEWHEIGHT-80, V_SNAPTOTOP|V_SNAPTORIGHT|239); + V_DrawFill(BASEVIDWIDTH-3, 76 + (SCALEDVIEWHEIGHT-80) * replayhutmenuy / y, 2, (((SCALEDVIEWHEIGHT-80) * (SCALEDVIEWHEIGHT-80))-1) / y - 1, V_SNAPTOTOP|V_SNAPTORIGHT|229); + } + + // Draw the cursor + V_DrawScaledPatch(currentMenu->x - 24, cursory, V_SNAPTOTOP|V_SNAPTOLEFT, + W_CachePatchName("M_CURSOR", PU_CACHE)); + V_DrawString(currentMenu->x, cursory, V_SNAPTOTOP|V_SNAPTOLEFT|highlightflags, currentMenu->menuitems[itemOn].text); + + // Now draw some replay info! + V_DrawFill(10, 10, 300, 60, V_SNAPTOTOP|239); + + if (itemOn == replaylistitem) + { + DrawReplayHutReplayInfo(); + } +} + +static void M_DrawReplayStartMenu(void) +{ + const char *warning; + UINT8 i; + + M_DrawGenericBackgroundMenu(); + +#define STARTY 62-(replayScrollTitle>>1) + // Draw rankings beyond first + for (i = 1; i < MAXPLAYERS && demolist[dir_on[menudepthleft]].standings[i].ranking; i++) + { + patch_t *patch; + UINT8 *colormap; + + V_DrawRightAlignedString(BASEVIDWIDTH-100, STARTY + i*20, V_SNAPTOTOP|highlightflags, va("%2d", demolist[dir_on[menudepthleft]].standings[i].ranking)); + V_DrawThinString(BASEVIDWIDTH-96, STARTY + i*20, V_SNAPTOTOP|V_ALLOWLOWERCASE, demolist[dir_on[menudepthleft]].standings[i].name); + + if (demolist[dir_on[menudepthleft]].standings[i].timeorscore == UINT32_MAX-1) + V_DrawThinString(BASEVIDWIDTH-92, STARTY + i*20 + 9, V_SNAPTOTOP, "NO CONTEST"); + else if (demolist[dir_on[menudepthleft]].gametype == GT_RACE) + V_DrawRightAlignedString(BASEVIDWIDTH-40, STARTY + i*20 + 9, V_SNAPTOTOP, va("%d'%02d\"%02d", + G_TicsToMinutes(demolist[dir_on[menudepthleft]].standings[i].timeorscore, true), + G_TicsToSeconds(demolist[dir_on[menudepthleft]].standings[i].timeorscore), + G_TicsToCentiseconds(demolist[dir_on[menudepthleft]].standings[i].timeorscore) + )); + else + V_DrawString(BASEVIDWIDTH-92, STARTY + i*20 + 9, V_SNAPTOTOP, va("%d", demolist[dir_on[menudepthleft]].standings[i].timeorscore)); + + // Character face! + if (W_CheckNumForName(skins[demolist[dir_on[menudepthleft]].standings[i].skin].facerank) != LUMPERROR) + { + patch = facerankprefix[demolist[dir_on[menudepthleft]].standings[i].skin]; + colormap = R_GetTranslationColormap( + demolist[dir_on[menudepthleft]].standings[i].skin, + demolist[dir_on[menudepthleft]].standings[i].color, + GTC_MENUCACHE); + } + else + { + patch = W_CachePatchName("M_NORANK", PU_CACHE); + colormap = R_GetTranslationColormap( + TC_RAINBOW, + demolist[dir_on[menudepthleft]].standings[i].color, + GTC_MENUCACHE); + } + + V_DrawMappedPatch(BASEVIDWIDTH-5 - SHORT(patch->width), STARTY + i*20, V_SNAPTOTOP, patch, colormap); + } +#undef STARTY + + // Handle scrolling rankings + if (replayScrollDelay) + replayScrollDelay--; + else if (replayScrollDir > 0) + { + if (replayScrollTitle < (i*20 - SCALEDVIEWHEIGHT + 100)<<1) + replayScrollTitle++; + else + { + replayScrollDelay = TICRATE; + replayScrollDir = -1; + } + } + else + { + if (replayScrollTitle > 0) + replayScrollTitle--; + else + { + replayScrollDelay = TICRATE; + replayScrollDir = 1; + } + } + + V_DrawFill(10, 10, 300, 60, V_SNAPTOTOP|239); + DrawReplayHutReplayInfo(); + + V_DrawString(10, 72, V_SNAPTOTOP|highlightflags|V_ALLOWLOWERCASE, demolist[dir_on[menudepthleft]].title); + + // Draw a warning prompt if needed + switch (demolist[dir_on[menudepthleft]].addonstatus) + { + case DFILE_ERROR_CANNOTLOAD: + warning = "Some addons in this replay cannot be loaded.\nYou can watch anyway, but desyncs may occur."; + break; + + case DFILE_ERROR_NOTLOADED: + case DFILE_ERROR_INCOMPLETEOUTOFORDER: + warning = "Loading addons will mark your game as modified, and record attack may be unavailable.\nYou can watch without loading addons, but desyncs may occur."; + break; + + case DFILE_ERROR_EXTRAFILES: + warning = "You have addons loaded that were not present in this replay.\nYou can watch anyway, but desyncs may occur."; + break; + + case DFILE_ERROR_OUTOFORDER: + warning = "You have this replay's addons loaded, but they are out of order.\nYou can watch anyway, but desyncs may occur."; + break; + + default: + return; + } + + V_DrawSmallString(4, BASEVIDHEIGHT-14, V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, warning); +} + +static boolean M_QuitReplayHut(void) +{ + // D_StartTitle does its own wipe, since GS_TIMEATTACK is now a complete gamestate. + menuactive = false; + D_StartTitle(); + + if (demolist) + Z_Free(demolist); + demolist = NULL; + + demo.inreplayhut = false; + + return true; +} + +static void M_HutStartReplay(INT32 choice) +{ + (void)choice; + + M_ClearMenus(false); + demo.loadfiles = (itemOn == 0); + demo.ignorefiles = (itemOn != 0); + + G_DoPlayDemo(demolist[dir_on[menudepthleft]].filepath); +} + +void M_SetPlaybackMenuPointer(void) +{ + itemOn = playback_pause; +} + +static void M_DrawPlaybackMenu(void) +{ + INT16 i; + patch_t *icon; + UINT8 *activemap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_GOLD, GTC_MENUCACHE); + + // Toggle items + if (paused && !demo.rewinding) + { + PlaybackMenu[playback_pause].status = PlaybackMenu[playback_fastforward].status = PlaybackMenu[playback_rewind].status = IT_DISABLED; + PlaybackMenu[playback_resume].status = PlaybackMenu[playback_advanceframe].status = PlaybackMenu[playback_backframe].status = IT_CALL|IT_STRING; + + if (itemOn >= playback_rewind && itemOn <= playback_fastforward) + itemOn += playback_backframe - playback_rewind; + } + else + { + PlaybackMenu[playback_pause].status = PlaybackMenu[playback_fastforward].status = PlaybackMenu[playback_rewind].status = IT_CALL|IT_STRING; + PlaybackMenu[playback_resume].status = PlaybackMenu[playback_advanceframe].status = PlaybackMenu[playback_backframe].status = IT_DISABLED; + + if (itemOn >= playback_backframe && itemOn <= playback_advanceframe) + itemOn -= playback_backframe - playback_rewind; + } + + if (modeattacking) + { + for (i = playback_viewcount; i <= playback_view4; i++) + PlaybackMenu[i].status = IT_DISABLED; + + //PlaybackMenu[playback_moreoptions].alphaKey = 72; + //PlaybackMenu[playback_quit].alphaKey = 88; + PlaybackMenu[playback_quit].alphaKey = 72; + + //currentMenu->x = BASEVIDWIDTH/2 - 52; + currentMenu->x = BASEVIDWIDTH/2 - 44; + } + else + { + PlaybackMenu[playback_viewcount].status = IT_ARROWS|IT_STRING; + + for (i = 0; i <= splitscreen; i++) + PlaybackMenu[playback_view1+i].status = IT_ARROWS|IT_STRING; + for (i = splitscreen+1; i < 4; i++) + PlaybackMenu[playback_view1+i].status = IT_DISABLED; + + //PlaybackMenu[playback_moreoptions].alphaKey = 156; + //PlaybackMenu[playback_quit].alphaKey = 172; + PlaybackMenu[playback_quit].alphaKey = 156; + + //currentMenu->x = BASEVIDWIDTH/2 - 94; + currentMenu->x = BASEVIDWIDTH/2 - 88; + } + + // wip + //M_DrawTextBox(currentMenu->x-68, currentMenu->y-7, 15, 15); + //M_DrawCenteredMenu(); + + for (i = 0; i < currentMenu->numitems; i++) + { + UINT8 *inactivemap = NULL; + + if (i >= playback_view1 && i <= playback_view4) + { + if (modeattacking) continue; + + if (splitscreen >= i - playback_view1) + { + INT32 ply = displayplayers[i - playback_view1]; + + icon = facerankprefix[players[ply].skin]; + if (i != itemOn) + inactivemap = R_GetTranslationColormap(players[ply].skin, players[ply].skincolor, GTC_MENUCACHE); + } + else if (currentMenu->menuitems[i].patch && W_CheckNumForName(currentMenu->menuitems[i].patch) != LUMPERROR) + icon = W_CachePatchName(currentMenu->menuitems[i].patch, PU_CACHE); + else + icon = W_CachePatchName("PLAYRANK", PU_CACHE); // temp + } + else if (currentMenu->menuitems[i].status == IT_DISABLED) + continue; + else if (currentMenu->menuitems[i].patch && W_CheckNumForName(currentMenu->menuitems[i].patch) != LUMPERROR) + icon = W_CachePatchName(currentMenu->menuitems[i].patch, PU_CACHE); + else + icon = W_CachePatchName("PLAYRANK", PU_CACHE); // temp + + if ((i == playback_fastforward && cv_playbackspeed.value > 1) || (i == playback_rewind && demo.rewinding)) + V_DrawMappedPatch(currentMenu->x + currentMenu->menuitems[i].alphaKey, currentMenu->y, V_SNAPTOTOP, icon, R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_JAWZ, GTC_MENUCACHE)); + else + V_DrawMappedPatch(currentMenu->x + currentMenu->menuitems[i].alphaKey, currentMenu->y, V_SNAPTOTOP, icon, (i == itemOn) ? activemap : inactivemap); + + if (i == itemOn) + { + V_DrawCharacter(currentMenu->x + currentMenu->menuitems[i].alphaKey + 4, currentMenu->y + 14, + '\x1A' | V_SNAPTOTOP|highlightflags, false); + + V_DrawCenteredString(BASEVIDWIDTH/2, currentMenu->y + 18, V_SNAPTOTOP|V_ALLOWLOWERCASE, currentMenu->menuitems[i].text); + + if ((currentMenu->menuitems[i].status & IT_TYPE) == IT_ARROWS) + { + char *str; + + if (!(i == playback_viewcount && splitscreen == 3)) + V_DrawCharacter(BASEVIDWIDTH/2 - 4, currentMenu->y + 28 - (skullAnimCounter/5), + '\x1A' | V_SNAPTOTOP|highlightflags, false); // up arrow + + if (!(i == playback_viewcount && splitscreen == 0)) + V_DrawCharacter(BASEVIDWIDTH/2 - 4, currentMenu->y + 48 + (skullAnimCounter/5), + '\x1B' | V_SNAPTOTOP|highlightflags, false); // down arrow + + switch (i) + { + case playback_viewcount: + str = va("%d", splitscreen+1); + break; + + case playback_view1: + case playback_view2: + case playback_view3: + case playback_view4: + str = player_names[displayplayers[i - playback_view1]]; // 0 to 3 + break; + + default: // shouldn't ever be reached but whatever + continue; + } + + V_DrawCenteredString(BASEVIDWIDTH/2, currentMenu->y + 38, V_SNAPTOTOP|V_ALLOWLOWERCASE|highlightflags, str); + } + } + } +} + +static void M_PlaybackRewind(INT32 choice) +{ + static tic_t lastconfirmtime; + + (void)choice; + + if (!demo.rewinding) + { + if (paused) + { + G_ConfirmRewind(leveltime-1); + paused = true; + S_PauseAudio(); + } + else + demo.rewinding = paused = true; + } + else if (lastconfirmtime + TICRATE/2 < I_GetTime()) + { + lastconfirmtime = I_GetTime(); + G_ConfirmRewind(leveltime); + } + + CV_SetValue(&cv_playbackspeed, 1); +} + +static void M_PlaybackPause(INT32 choice) +{ + (void)choice; + + paused = !paused; + + if (demo.rewinding) + { + G_ConfirmRewind(leveltime); + paused = true; + S_PauseAudio(); + } + else if (paused) + S_PauseAudio(); + else + S_ResumeAudio(); + + CV_SetValue(&cv_playbackspeed, 1); +} + +static void M_PlaybackFastForward(INT32 choice) +{ + (void)choice; + + if (demo.rewinding) + { + G_ConfirmRewind(leveltime); + paused = false; + S_ResumeAudio(); + } + CV_SetValue(&cv_playbackspeed, cv_playbackspeed.value == 1 ? 4 : 1); +} + +static void M_PlaybackAdvance(INT32 choice) +{ + (void)choice; + + paused = false; + TryRunTics(1); + paused = true; +} + + +static void M_PlaybackSetViews(INT32 choice) +{ + if (choice > 0) + { + if (splitscreen < 3) + G_AdjustView(splitscreen + 2, 0, true); + } + else if (splitscreen) + { + splitscreen--; + R_ExecuteSetViewSize(); + } +} + +static void M_PlaybackAdjustView(INT32 choice) +{ + G_AdjustView(itemOn - playback_viewcount, (choice > 0) ? 1 : -1, true); +} + +static void M_PlaybackQuit(INT32 choice) +{ + (void)choice; + G_StopDemo(); + + if (demo.inreplayhut) + M_ReplayHut(choice); + else if (modeattacking) + { + M_EndModeAttackRun(); + S_ChangeMusicInternal("racent", true); + } + else + D_StartTitle(); +} + static void M_PandorasBox(INT32 choice) { (void)choice; @@ -5109,11 +6086,10 @@ static void M_Options(INT32 choice) (void)choice; // if the player is not admin or server, disable gameplay & server options - OP_MainMenu[5].status = OP_MainMenu[6].status = (Playing() && !(server || IsPlayerAdmin(consoleplayer))) ? (IT_GRAYEDOUT) : (IT_STRING|IT_SUBMENU); + OP_MainMenu[4].status = OP_MainMenu[5].status = (Playing() && !(server || IsPlayerAdmin(consoleplayer))) ? (IT_GRAYEDOUT) : (IT_STRING|IT_SUBMENU); - // if the player is playing _at all_, disable the erase data & credits options - OP_MainMenu[9].status = (Playing()) ? (IT_GRAYEDOUT) : (IT_STRING|IT_CALL); - OP_MainMenu[10].status = (Playing()) ? (IT_GRAYEDOUT) : (IT_STRING|IT_SUBMENU); + OP_MainMenu[8].status = (Playing()) ? (IT_GRAYEDOUT) : (IT_STRING|IT_CALL); // Play credits + OP_DataOptionsMenu[3].status = (Playing()) ? (IT_GRAYEDOUT) : (IT_STRING|IT_SUBMENU); // Erase data OP_GameOptionsMenu[3].status = (M_SecretUnlocked(SECRET_ENCORE)) ? (IT_CVAR|IT_STRING) : IT_SECRET; // cv_kartencore @@ -5618,6 +6594,7 @@ static void M_Credits(INT32 choice) // SINGLE PLAYER MENU // ================== +#if 0 // Bring this back when we have actual single-player static void M_SinglePlayerMenu(INT32 choice) { (void)choice; @@ -5628,6 +6605,7 @@ static void M_SinglePlayerMenu(INT32 choice) M_SetupNextMenu(&SP_MainDef); } +#endif /*static void M_LoadGameLevelSelect(INT32 choice) { @@ -6945,6 +7923,7 @@ static void M_HandleStaffReplay(INT32 choice) break; M_ClearMenus(true); modeattacking = ATTACKING_RECORD; + demo.loadfiles = false; demo.ignorefiles = true; // Just assume that record attack replays have the files needed G_DoPlayDemo(va("%sS%02u",G_BuildMapName(cv_nextmap.value),cv_dummystaff.value)); break; default: @@ -6965,6 +7944,7 @@ static void M_ReplayTimeAttack(INT32 choice) const char *which; M_ClearMenus(true); modeattacking = ATTACKING_RECORD; // set modeattacking before G_DoPlayDemo so the map loader knows + demo.loadfiles = false; demo.ignorefiles = true; // Just assume that record attack replays have the files needed if (currentMenu == &SP_ReplayDef) { @@ -7161,7 +8141,7 @@ static void M_ExitGameResponse(INT32 ch) static void M_EndGame(INT32 choice) { (void)choice; - if (demoplayback || demorecording) + if (demo.playback) return; if (!Playing()) @@ -7587,6 +8567,8 @@ static void M_StartServer(INT32 choice) multiplayer = true; + strncpy(connectedservername, cv_servername.string, MAXSERVERNAME); + // Still need to reset devmode cv_debug = 0; @@ -7595,7 +8577,7 @@ static void M_StartServer(INT32 choice) else joinpasswordset = false; - if (demoplayback) + if (demo.playback) G_StopDemo(); if (metalrecording) G_StopMetalDemo(); @@ -8489,7 +9471,7 @@ static void M_SetupMultiPlayer2(INT32 choice) strcpy (setupm_name, cv_playername2.string); // set for splitscreen secondary player - setupm_player = &players[secondarydisplayplayer]; + setupm_player = &players[displayplayers[1]]; setupm_cvskin = &cv_skin2; setupm_cvcolor = &cv_playercolor2; setupm_cvname = &cv_playername2; @@ -8501,7 +9483,7 @@ static void M_SetupMultiPlayer2(INT32 choice) setupm_fakecolor = setupm_cvcolor->value; // disable skin changes if we can't actually change skins - if (splitscreen && !CanChangeSkin(secondarydisplayplayer)) + if (splitscreen && !CanChangeSkin(displayplayers[1])) MP_PlayerSetupMenu[2].status = (IT_GRAYEDOUT); else MP_PlayerSetupMenu[2].status = (IT_KEYHANDLER | IT_STRING); @@ -8520,7 +9502,7 @@ static void M_SetupMultiPlayer3(INT32 choice) strcpy(setupm_name, cv_playername3.string); // set for splitscreen third player - setupm_player = &players[thirddisplayplayer]; + setupm_player = &players[displayplayers[2]]; setupm_cvskin = &cv_skin3; setupm_cvcolor = &cv_playercolor3; setupm_cvname = &cv_playername3; @@ -8532,7 +9514,7 @@ static void M_SetupMultiPlayer3(INT32 choice) setupm_fakecolor = setupm_cvcolor->value; // disable skin changes if we can't actually change skins - if (splitscreen > 1 && !CanChangeSkin(thirddisplayplayer)) + if (splitscreen > 1 && !CanChangeSkin(displayplayers[2])) MP_PlayerSetupMenu[2].status = (IT_GRAYEDOUT); else MP_PlayerSetupMenu[2].status = (IT_KEYHANDLER | IT_STRING); @@ -8551,7 +9533,7 @@ static void M_SetupMultiPlayer4(INT32 choice) strcpy(setupm_name, cv_playername4.string); // set for splitscreen fourth player - setupm_player = &players[fourthdisplayplayer]; + setupm_player = &players[displayplayers[3]]; setupm_cvskin = &cv_skin4; setupm_cvcolor = &cv_playercolor4; setupm_cvname = &cv_playername4; @@ -8563,7 +9545,7 @@ static void M_SetupMultiPlayer4(INT32 choice) setupm_fakecolor = setupm_cvcolor->value; // disable skin changes if we can't actually change skins - if (splitscreen > 2 && !CanChangeSkin(fourthdisplayplayer)) + if (splitscreen > 2 && !CanChangeSkin(displayplayers[3])) MP_PlayerSetupMenu[2].status = (IT_GRAYEDOUT); else MP_PlayerSetupMenu[2].status = (IT_KEYHANDLER | IT_STRING); @@ -8920,7 +9902,7 @@ static void M_Setup1PControlsMenu(INT32 choice) OP_AllControlsMenu[15].status = IT_CONTROL; // Chat //OP_AllControlsMenu[16].status = IT_CONTROL; // Team-chat OP_AllControlsMenu[16].status = IT_CONTROL; // Rankings - OP_AllControlsMenu[17].status = IT_CONTROL; // Viewpoint + //OP_AllControlsMenu[17].status = IT_CONTROL; // Viewpoint // 18 is Reset Camera, 19 is Toggle Chasecam OP_AllControlsMenu[20].status = IT_CONTROL; // Pause OP_AllControlsMenu[21].status = IT_CONTROL; // Screenshot @@ -8952,7 +9934,7 @@ static void M_Setup2PControlsMenu(INT32 choice) OP_AllControlsMenu[15].status = IT_GRAYEDOUT2; // Chat //OP_AllControlsMenu[16].status = IT_GRAYEDOUT2; // Team-chat OP_AllControlsMenu[16].status = IT_GRAYEDOUT2; // Rankings - OP_AllControlsMenu[17].status = IT_GRAYEDOUT2; // Viewpoint + //OP_AllControlsMenu[17].status = IT_GRAYEDOUT2; // Viewpoint // 18 is Reset Camera, 19 is Toggle Chasecam OP_AllControlsMenu[20].status = IT_GRAYEDOUT2; // Pause OP_AllControlsMenu[21].status = IT_GRAYEDOUT2; // Screenshot @@ -8984,7 +9966,7 @@ static void M_Setup3PControlsMenu(INT32 choice) OP_AllControlsMenu[15].status = IT_GRAYEDOUT2; // Chat //OP_AllControlsMenu[16].status = IT_GRAYEDOUT2; // Team-chat OP_AllControlsMenu[16].status = IT_GRAYEDOUT2; // Rankings - OP_AllControlsMenu[17].status = IT_GRAYEDOUT2; // Viewpoint + //OP_AllControlsMenu[17].status = IT_GRAYEDOUT2; // Viewpoint // 18 is Reset Camera, 19 is Toggle Chasecam OP_AllControlsMenu[20].status = IT_GRAYEDOUT2; // Pause OP_AllControlsMenu[21].status = IT_GRAYEDOUT2; // Screenshot @@ -9016,7 +9998,7 @@ static void M_Setup4PControlsMenu(INT32 choice) OP_AllControlsMenu[15].status = IT_GRAYEDOUT2; // Chat //OP_AllControlsMenu[16].status = IT_GRAYEDOUT2; // Team-chat OP_AllControlsMenu[16].status = IT_GRAYEDOUT2; // Rankings - OP_AllControlsMenu[17].status = IT_GRAYEDOUT2; // Viewpoint + //OP_AllControlsMenu[17].status = IT_GRAYEDOUT2; // Viewpoint // 18 is Reset Camera, 19 is Toggle Chasecam OP_AllControlsMenu[20].status = IT_GRAYEDOUT2; // Pause OP_AllControlsMenu[21].status = IT_GRAYEDOUT2; // Screenshot diff --git a/src/m_menu.h b/src/m_menu.h index 33dc1e407..ef0381d11 100644 --- a/src/m_menu.h +++ b/src/m_menu.h @@ -235,6 +235,9 @@ void Screenshot_option_Onchange(void); // Addons menu updating void Addons_option_Onchange(void); +void M_ReplayHut(INT32 choice); +void M_SetPlaybackMenuPointer(void); + INT32 HU_GetHighlightColor(void); // These defines make it a little easier to make menus diff --git a/src/m_misc.c b/src/m_misc.c index c95aa392c..f4a4ec291 100644 --- a/src/m_misc.c +++ b/src/m_misc.c @@ -743,12 +743,12 @@ static void M_PNGText(png_structp png_ptr, png_infop png_info_ptr, PNG_CONST png else snprintf(lvlttltext, 48, "Unknown"); - if (gamestate == GS_LEVEL && &players[displayplayer] && players[displayplayer].mo) + if (gamestate == GS_LEVEL && &players[displayplayers[0]] && players[displayplayers[0]].mo) snprintf(locationtxt, 40, "X:%d Y:%d Z:%d A:%d", - players[displayplayer].mo->x>>FRACBITS, - players[displayplayer].mo->y>>FRACBITS, - players[displayplayer].mo->z>>FRACBITS, - FixedInt(AngleFixed(players[displayplayer].mo->angle))); + players[displayplayers[0]].mo->x>>FRACBITS, + players[displayplayers[0]].mo->y>>FRACBITS, + players[displayplayers[0]].mo->z>>FRACBITS, + FixedInt(AngleFixed(players[displayplayers[0]].mo->angle))); else snprintf(locationtxt, 40, "Unknown"); diff --git a/src/p_enemy.c b/src/p_enemy.c index 599d1f2a5..bf1cacdc7 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -3079,7 +3079,7 @@ void A_Invincibility(mobj_t *actor) { S_StopMusic(); if (mariomode) - G_GhostAddColor(GHC_INVINCIBLE); + G_GhostAddColor((INT32) (player - players), GHC_INVINCIBLE); S_ChangeMusicInternal((mariomode) ? "minvnc" : "invinc", false); } } @@ -4174,12 +4174,12 @@ void A_OverlayThink(mobj_t *actor) { angle_t viewingangle; - if (players[displayplayer].awayviewtics) - viewingangle = R_PointToAngle2(actor->target->x, actor->target->y, players[displayplayer].awayviewmobj->x, players[displayplayer].awayviewmobj->y); - else if (!camera.chase && players[displayplayer].mo) - viewingangle = R_PointToAngle2(actor->target->x, actor->target->y, players[displayplayer].mo->x, players[displayplayer].mo->y); + if (players[displayplayers[0]].awayviewtics) + viewingangle = R_PointToAngle2(actor->target->x, actor->target->y, players[displayplayers[0]].awayviewmobj->x, players[displayplayers[0]].awayviewmobj->y); + else if (!camera[0].chase && players[displayplayers[0]].mo) + viewingangle = R_PointToAngle2(actor->target->x, actor->target->y, players[displayplayers[0]].mo->x, players[displayplayers[0]].mo->y); else - viewingangle = R_PointToAngle2(actor->target->x, actor->target->y, camera.x, camera.y); + viewingangle = R_PointToAngle2(actor->target->x, actor->target->y, camera[0].x, camera[0].y); destx = actor->target->x + P_ReturnThrustX(actor->target, viewingangle, FixedMul(FRACUNIT, actor->scale)); desty = actor->target->y + P_ReturnThrustY(actor->target, viewingangle, FixedMul(FRACUNIT, actor->scale)); diff --git a/src/p_floor.c b/src/p_floor.c index e11fe4030..ccbfd6eae 100644 --- a/src/p_floor.c +++ b/src/p_floor.c @@ -2536,9 +2536,9 @@ void T_CameraScanner(elevator_t *elevator) lastleveltime = leveltime; } - if (players[displayplayer].mo) + if (players[displayplayers[0]].mo) { - if (players[displayplayer].mo->subsector->sector == elevator->actionsector) + if (players[displayplayers[0]].mo->subsector->sector == elevator->actionsector) { if (t_cam_dist == -42) t_cam_dist = cv_cam_dist.value; @@ -2564,9 +2564,9 @@ void T_CameraScanner(elevator_t *elevator) } } - if (splitscreen && players[secondarydisplayplayer].mo) + if (splitscreen && players[displayplayers[1]].mo) { - if (players[secondarydisplayplayer].mo->subsector->sector == elevator->actionsector) + if (players[displayplayers[1]].mo->subsector->sector == elevator->actionsector) { if (t_cam2_rotate == -42) t_cam2_dist = cv_cam2_dist.value; @@ -2592,9 +2592,9 @@ void T_CameraScanner(elevator_t *elevator) } } - if (splitscreen > 1 && players[thirddisplayplayer].mo) + if (splitscreen > 1 && players[displayplayers[2]].mo) { - if (players[thirddisplayplayer].mo->subsector->sector == elevator->actionsector) + if (players[displayplayers[2]].mo->subsector->sector == elevator->actionsector) { if (t_cam3_rotate == -42) t_cam3_dist = cv_cam3_dist.value; @@ -2620,9 +2620,9 @@ void T_CameraScanner(elevator_t *elevator) } } - if (splitscreen > 2 && players[fourthdisplayplayer].mo) + if (splitscreen > 2 && players[displayplayers[3]].mo) { - if (players[fourthdisplayplayer].mo->subsector->sector == elevator->actionsector) + if (players[displayplayers[3]].mo->subsector->sector == elevator->actionsector) { if (t_cam4_rotate == -42) t_cam4_dist = cv_cam4_dist.value; diff --git a/src/p_inter.c b/src/p_inter.c index 673df055a..e6cb19993 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -62,11 +62,11 @@ void P_ForceConstant(const BasicFF_t *FFInfo) ConstantQuake.Magnitude = FFInfo->Magnitude; if (FFInfo->player == &players[consoleplayer]) I_Tactile(ConstantForce, &ConstantQuake); - else if (splitscreen && FFInfo->player == &players[secondarydisplayplayer]) + else if (splitscreen && FFInfo->player == &players[displayplayers[1]]) I_Tactile2(ConstantForce, &ConstantQuake); - else if (splitscreen > 1 && FFInfo->player == &players[thirddisplayplayer]) + else if (splitscreen > 1 && FFInfo->player == &players[displayplayers[2]]) I_Tactile3(ConstantForce, &ConstantQuake); - else if (splitscreen > 2 && FFInfo->player == &players[fourthdisplayplayer]) + else if (splitscreen > 2 && FFInfo->player == &players[displayplayers[3]]) I_Tactile4(ConstantForce, &ConstantQuake); } void P_RampConstant(const BasicFF_t *FFInfo, INT32 Start, INT32 End) @@ -83,11 +83,11 @@ void P_RampConstant(const BasicFF_t *FFInfo, INT32 Start, INT32 End) RampQuake.End = End; if (FFInfo->player == &players[consoleplayer]) I_Tactile(ConstantForce, &RampQuake); - else if (splitscreen && FFInfo->player == &players[secondarydisplayplayer]) + else if (splitscreen && FFInfo->player == &players[displayplayers[1]]) I_Tactile2(ConstantForce, &RampQuake); - else if (splitscreen > 1 && FFInfo->player == &players[thirddisplayplayer]) + else if (splitscreen > 1 && FFInfo->player == &players[displayplayers[2]]) I_Tactile3(ConstantForce, &RampQuake); - else if (splitscreen > 2 && FFInfo->player == &players[fourthdisplayplayer]) + else if (splitscreen > 2 && FFInfo->player == &players[displayplayers[3]]) I_Tactile4(ConstantForce, &RampQuake); } @@ -218,7 +218,7 @@ void P_DoNightsScore(player_t *player) dummymo->fuse = 3*TICRATE; // What?! NO, don't use the camera! Scale up instead! - //P_InstaThrust(dummymo, R_PointToAngle2(dummymo->x, dummymo->y, camera.x, camera.y), 3*FRACUNIT); + //P_InstaThrust(dummymo, R_PointToAngle2(dummymo->x, dummymo->y, camera[0].x, camera[0].y), 3*FRACUNIT); dummymo->scalespeed = FRACUNIT/25; dummymo->destscale = 2*FRACUNIT; } @@ -851,7 +851,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) // Secret emblem thingy case MT_EMBLEM: { - if (demoplayback || player->bot) + if (demo.playback || player->bot) return; emblemlocations[special->health-1].collected = true; @@ -1180,13 +1180,13 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) toucher->angle = special->angle; if (player == &players[consoleplayer]) - localangle = toucher->angle; - else if (player == &players[secondarydisplayplayer]) - localangle2 = toucher->angle; - else if (player == &players[thirddisplayplayer]) - localangle3 = toucher->angle; - else if (player == &players[fourthdisplayplayer]) - localangle4 = toucher->angle; + localangle[0] = toucher->angle; + else if (player == &players[displayplayers[1]]) + localangle[1] = toucher->angle; + else if (player == &players[displayplayers[2]]) + localangle[2] = toucher->angle; + else if (player == &players[displayplayers[3]]) + localangle[3] = toucher->angle; P_ResetPlayer(player); @@ -1209,7 +1209,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) } // CECHO showing you what this item is - if (player == &players[displayplayer] || G_IsSpecialStage(gamemap)) + if (player == &players[displayplayers[0]] || G_IsSpecialStage(gamemap)) { HU_SetCEchoFlags(V_AUTOFADEOUT); HU_SetCEchoDuration(4); @@ -1231,7 +1231,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) } // CECHO showing you what this item is - if (player == &players[displayplayer] || G_IsSpecialStage(gamemap)) + if (player == &players[displayplayers[0]] || G_IsSpecialStage(gamemap)) { HU_SetCEchoFlags(V_AUTOFADEOUT); HU_SetCEchoDuration(4); @@ -1263,7 +1263,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) } // CECHO showing you what this item is - if (player == &players[displayplayer] || G_IsSpecialStage(gamemap)) + if (player == &players[displayplayers[0]] || G_IsSpecialStage(gamemap)) { HU_SetCEchoFlags(V_AUTOFADEOUT); HU_SetCEchoDuration(4); @@ -1293,7 +1293,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) } // CECHO showing you what this item is - if (player == &players[displayplayer] || G_IsSpecialStage(gamemap)) + if (player == &players[displayplayers[0]] || G_IsSpecialStage(gamemap)) { HU_SetCEchoFlags(V_AUTOFADEOUT); HU_SetCEchoDuration(4); @@ -1321,7 +1321,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) } // CECHO showing you what this item is - if (player == &players[displayplayer] || G_IsSpecialStage(gamemap)) + if (player == &players[displayplayers[0]] || G_IsSpecialStage(gamemap)) { HU_SetCEchoFlags(V_AUTOFADEOUT); HU_SetCEchoDuration(4); @@ -1433,7 +1433,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) return; player->powers[pw_shield] |= SH_FIREFLOWER; toucher->color = SKINCOLOR_WHITE; - G_GhostAddColor(GHC_FIREFLOWER); + G_GhostAddColor(player - players, GHC_FIREFLOWER); break; // *************** // @@ -2324,17 +2324,17 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source) AM_Stop(); //added : 22-02-98: recenter view for next life... - localaiming = 0; + localaiming[0] = 0; } - if (target->player == &players[secondarydisplayplayer]) + if (target->player == &players[displayplayers[1]]) { // added : 22-02-98: recenter view for next life... - localaiming2 = 0; + localaiming[1] = 0; } - if (target->player == &players[thirddisplayplayer]) - localaiming3 = 0; - if (target->player == &players[fourthdisplayplayer]) - localaiming4 = 0; + if (target->player == &players[displayplayers[2]]) + localaiming[2] = 0; + if (target->player == &players[displayplayers[3]]) + localaiming[3] = 0; //tag deaths handled differently in suicide cases. Don't count spectators! /*if (G_TagGametype() @@ -2978,7 +2978,7 @@ void P_RemoveShield(player_t *player) if (!player->powers[pw_super]) { player->mo->color = player->skincolor; - G_GhostAddColor(GHC_NORMAL); + G_GhostAddColor((INT32) (player - players), GHC_NORMAL); } } else if ((player->powers[pw_shield] & SH_NOSTACK) == SH_BOMB) // Give them what's coming to them! @@ -3409,7 +3409,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da target->health -= damage; if (source && source->player && target) - G_GhostAddHit(target); + G_GhostAddHit((INT32) (source->player - players), target); if (target->health <= 0) { diff --git a/src/p_local.h b/src/p_local.h index 2362476dd..0d0ddc89b 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -22,6 +22,7 @@ #include "p_tick.h" #include "r_defs.h" #include "p_maputl.h" +#include "doomstat.h" // MAXSPLITSCREENPLAYERS #define FLOATSPEED (FRACUNIT*4) @@ -108,7 +109,7 @@ typedef struct camera_s fixed_t pan; } camera_t; -extern camera_t camera, camera2, camera3, camera4; +extern camera_t camera[MAXSPLITSCREENPLAYERS]; extern consvar_t cv_cam_dist, cv_cam_still, cv_cam_height; extern consvar_t cv_cam_speed, cv_cam_rotate, cv_cam_rotspeed; @@ -137,6 +138,7 @@ boolean P_PlayerInPain(player_t *player); void P_DoPlayerPain(player_t *player, mobj_t *source, mobj_t *inflictor); void P_ResetPlayer(player_t *player); boolean P_IsLocalPlayer(player_t *player); +boolean P_IsDisplayPlayer(player_t *player); boolean P_SpectatorJoinGame(player_t *player); boolean P_IsObjectInGoop(mobj_t *mo); diff --git a/src/p_map.c b/src/p_map.c index 07f8abbda..2c766349d 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -212,16 +212,16 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object) { object->angle = spring->angle; - if (!demoplayback || P_AnalogMove(object->player)) + if (!demo.playback || P_AnalogMove(object->player)) { if (object->player == &players[consoleplayer]) - localangle = spring->angle; - else if (object->player == &players[secondarydisplayplayer]) - localangle2 = spring->angle; - else if (object->player == &players[thirddisplayplayer]) - localangle3 = spring->angle; - else if (object->player == &players[fourthdisplayplayer]) - localangle4 = spring->angle; + localangle[0] = spring->angle; + else if (object->player == &players[displayplayers[1]]) + localangle[1] = spring->angle; + else if (object->player == &players[displayplayers[2]]) + localangle[2] = spring->angle; + else if (object->player == &players[displayplayers[3]]) + localangle[3] = spring->angle; } } @@ -1264,16 +1264,16 @@ static boolean PIT_CheckThing(mobj_t *thing) thing->angle = tmthing->angle; - if (!demoplayback || P_AnalogMove(thing->player)) + if (!demo.playback || P_AnalogMove(thing->player)) { if (thing->player == &players[consoleplayer]) - localangle = thing->angle; - else if (thing->player == &players[secondarydisplayplayer]) - localangle2 = thing->angle; - else if (thing->player == &players[thirddisplayplayer]) - localangle3 = thing->angle; - else if (thing->player == &players[fourthdisplayplayer]) - localangle4 = thing->angle; + localangle[0] = thing->angle; + else if (thing->player == &players[displayplayers[1]]) + localangle[1] = thing->angle; + else if (thing->player == &players[displayplayers[2]]) + localangle[2] = thing->angle; + else if (thing->player == &players[displayplayers[3]]) + localangle[3] = thing->angle; } return true; @@ -2504,41 +2504,46 @@ boolean P_TryCameraMove(fixed_t x, fixed_t y, camera_t *thiscam) subsector_t *s = R_PointInSubsector(x, y); boolean retval = true; boolean itsatwodlevel = false; + UINT8 i; floatok = false; - if (twodlevel - || (thiscam == &camera && players[displayplayer].mo && (players[displayplayer].mo->flags2 & MF2_TWOD)) - || (thiscam == &camera2 && players[secondarydisplayplayer].mo && (players[secondarydisplayplayer].mo->flags2 & MF2_TWOD)) - || (thiscam == &camera3 && players[thirddisplayplayer].mo && (players[thirddisplayplayer].mo->flags2 & MF2_TWOD)) - || (thiscam == &camera4 && players[fourthdisplayplayer].mo && (players[fourthdisplayplayer].mo->flags2 & MF2_TWOD))) + if (twodlevel) itsatwodlevel = true; + else + { + for (i = 0; i <= splitscreen; i++) + { + if (thiscam == &camera[i] && players[displayplayers[i]].mo + && (players[displayplayers[i]].mo->flags2 & MF2_TWOD)) + { + itsatwodlevel = true; + break; + } + } + } - if (!itsatwodlevel && players[displayplayer].mo) + if (!itsatwodlevel && players[displayplayers[0]].mo) { fixed_t tryx = thiscam->x; fixed_t tryy = thiscam->y; + for (i = 0; i <= splitscreen; i++) + { #ifndef NOCLIPCAM - if ((thiscam == &camera && (players[displayplayer].pflags & PF_NOCLIP)) - || (thiscam == &camera2 && (players[secondarydisplayplayer].pflags & PF_NOCLIP)) - || (thiscam == &camera3 && (players[thirddisplayplayer].pflags & PF_NOCLIP)) - || (thiscam == &camera4 && (players[fourthdisplayplayer].pflags & PF_NOCLIP)) - || (leveltime < introtime)) + if ((thiscam == &camera[i] && (players[displayplayers[i]].pflags & PF_NOCLIP)) || (leveltime < introtime)) // Noclipping player camera noclips too!! #else - if ((thiscam == &camera && !(players[displayplayer].pflags & PF_TIMEOVER)) - || (thiscam == &camera2 && !(players[secondarydisplayplayer].pflags & PF_TIMEOVER)) - || (thiscam == &camera3 && !(players[thirddisplayplayer].pflags & PF_TIMEOVER)) - || (thiscam == &camera4 && !(players[fourthdisplayplayer].pflags & PF_TIMEOVER))) + if (thiscam == &camera[i] && !(players[displayplayers[i]].pflags & PF_TIMEOVER)) // Time Over should not clip through walls #endif - { // Noclipping player camera noclips too!! - floatok = true; - thiscam->floorz = thiscam->z; - thiscam->ceilingz = thiscam->z + thiscam->height; - thiscam->x = x; - thiscam->y = y; - thiscam->subsector = s; - return true; + { + floatok = true; + thiscam->floorz = thiscam->z; + thiscam->ceilingz = thiscam->z + thiscam->height; + thiscam->x = x; + thiscam->y = y; + thiscam->subsector = s; + return true; + } } do { diff --git a/src/p_maputl.c b/src/p_maputl.c index c5a593d3e..355c58db8 100644 --- a/src/p_maputl.c +++ b/src/p_maputl.c @@ -339,9 +339,9 @@ void P_CameraLineOpening(line_t *linedef) frontceiling = sectors[front->camsec].ceilingheight; #ifdef ESLOPE if (sectors[front->camsec].f_slope) // SRB2CBTODO: ESLOPE (sectors[front->heightsec].f_slope) - frontfloor = P_GetZAt(sectors[front->camsec].f_slope, camera.x, camera.y); + frontfloor = P_GetZAt(sectors[front->camsec].f_slope, camera[0].x, camera[0].y); if (sectors[front->camsec].c_slope) - frontceiling = P_GetZAt(sectors[front->camsec].c_slope, camera.x, camera.y); + frontceiling = P_GetZAt(sectors[front->camsec].c_slope, camera[0].x, camera[0].y); #endif } @@ -351,9 +351,9 @@ void P_CameraLineOpening(line_t *linedef) frontceiling = sectors[front->heightsec].ceilingheight; #ifdef ESLOPE if (sectors[front->heightsec].f_slope) // SRB2CBTODO: ESLOPE (sectors[front->heightsec].f_slope) - frontfloor = P_GetZAt(sectors[front->heightsec].f_slope, camera.x, camera.y); + frontfloor = P_GetZAt(sectors[front->heightsec].f_slope, camera[0].x, camera[0].y); if (sectors[front->heightsec].c_slope) - frontceiling = P_GetZAt(sectors[front->heightsec].c_slope, camera.x, camera.y); + frontceiling = P_GetZAt(sectors[front->heightsec].c_slope, camera[0].x, camera[0].y); #endif } else @@ -367,9 +367,9 @@ void P_CameraLineOpening(line_t *linedef) backceiling = sectors[back->camsec].ceilingheight; #ifdef ESLOPE if (sectors[back->camsec].f_slope) // SRB2CBTODO: ESLOPE (sectors[front->heightsec].f_slope) - frontfloor = P_GetZAt(sectors[back->camsec].f_slope, camera.x, camera.y); + frontfloor = P_GetZAt(sectors[back->camsec].f_slope, camera[0].x, camera[0].y); if (sectors[back->camsec].c_slope) - frontceiling = P_GetZAt(sectors[back->camsec].c_slope, camera.x, camera.y); + frontceiling = P_GetZAt(sectors[back->camsec].c_slope, camera[0].x, camera[0].y); #endif } else if (back->heightsec >= 0) @@ -378,9 +378,9 @@ void P_CameraLineOpening(line_t *linedef) backceiling = sectors[back->heightsec].ceilingheight; #ifdef ESLOPE if (sectors[back->heightsec].f_slope) // SRB2CBTODO: ESLOPE (sectors[front->heightsec].f_slope) - frontfloor = P_GetZAt(sectors[back->heightsec].f_slope, camera.x, camera.y); + frontfloor = P_GetZAt(sectors[back->heightsec].f_slope, camera[0].x, camera[0].y); if (sectors[back->heightsec].c_slope) - frontceiling = P_GetZAt(sectors[back->heightsec].c_slope, camera.x, camera.y); + frontceiling = P_GetZAt(sectors[back->heightsec].c_slope, camera[0].x, camera[0].y); #endif } else diff --git a/src/p_mobj.c b/src/p_mobj.c index 508acc0b1..f7f2afe34 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -1125,7 +1125,7 @@ static void P_PlayerFlip(mobj_t *mo) if (!mo->player) return; - G_GhostAddFlip(); + G_GhostAddFlip((INT32) (mo->player - players)); // Flip aiming to match! if (mo->player->pflags & PF_NIGHTSMODE) // NiGHTS doesn't use flipcam @@ -1135,45 +1135,21 @@ static void P_PlayerFlip(mobj_t *mo) } else if (mo->player->pflags & PF_FLIPCAM) { + UINT8 i; + mo->player->aiming = InvAngle(mo->player->aiming); - if (mo->player-players == displayplayer) + + for (i = 0; i <= splitscreen; i++) { - localaiming = mo->player->aiming; - if (camera.chase) { - camera.aiming = InvAngle(camera.aiming); - camera.z = mo->z - camera.z + mo->z; - if (mo->eflags & MFE_VERTICALFLIP) - camera.z += FixedMul(20*FRACUNIT, mo->scale); - } - } - else if (mo->player-players == secondarydisplayplayer) - { - localaiming2 = mo->player->aiming; - if (camera2.chase) { - camera2.aiming = InvAngle(camera2.aiming); - camera2.z = mo->z - camera2.z + mo->z; - if (mo->eflags & MFE_VERTICALFLIP) - camera2.z += FixedMul(20*FRACUNIT, mo->scale); - } - } - else if (mo->player-players == thirddisplayplayer) - { - localaiming3 = mo->player->aiming; - if (camera3.chase) { - camera3.aiming = InvAngle(camera3.aiming); - camera3.z = mo->z - camera3.z + mo->z; - if (mo->eflags & MFE_VERTICALFLIP) - camera3.z += FixedMul(20*FRACUNIT, mo->scale); - } - } - else if (mo->player-players == fourthdisplayplayer) - { - localaiming4 = mo->player->aiming; - if (camera4.chase) { - camera4.aiming = InvAngle(camera4.aiming); - camera4.z = mo->z - camera4.z + mo->z; - if (mo->eflags & MFE_VERTICALFLIP) - camera4.z += FixedMul(20*FRACUNIT, mo->scale); + if (mo->player-players == displayplayers[i]) + { + localaiming[i] = mo->player->aiming; + if (camera[i].chase) { + camera[i].aiming = InvAngle(camera[i].aiming); + camera[i].z = mo->z - camera[i].z + mo->z; + if (mo->eflags & MFE_VERTICALFLIP) + camera[i].z += FixedMul(20*FRACUNIT, mo->scale); + } } } } @@ -3546,17 +3522,26 @@ boolean P_CameraThinker(player_t *player, camera_t *thiscam, boolean resetcalled { boolean itsatwodlevel = false; postimg_t postimg = postimg_none; + UINT8 i; // This can happen when joining if (thiscam->subsector == NULL || thiscam->subsector->sector == NULL) return true; - if (twodlevel - || (thiscam == &camera && players[displayplayer].mo && (players[displayplayer].mo->flags2 & MF2_TWOD)) - || (thiscam == &camera2 && players[secondarydisplayplayer].mo && (players[secondarydisplayplayer].mo->flags2 & MF2_TWOD)) - || (thiscam == &camera3 && players[thirddisplayplayer].mo && (players[thirddisplayplayer].mo->flags2 & MF2_TWOD)) - || (thiscam == &camera4 && players[fourthdisplayplayer].mo && (players[fourthdisplayplayer].mo->flags2 & MF2_TWOD))) + if (twodlevel) itsatwodlevel = true; + else + { + for (i = 0; i <= splitscreen; i++) + { + if (thiscam == &camera[i] && players[displayplayers[i]].mo + && (players[displayplayers[i]].mo->flags2 & MF2_TWOD)) + { + itsatwodlevel = true; + break; + } + } + } if (encoremode) postimg = postimg_mirror; @@ -3588,14 +3573,11 @@ boolean P_CameraThinker(player_t *player, camera_t *thiscam, boolean resetcalled if (postimg != postimg_none) { - if (splitscreen && player == &players[secondarydisplayplayer]) - postimgtype2 = postimg; - else if (splitscreen > 1 && player == &players[thirddisplayplayer]) - postimgtype3 = postimg; - else if (splitscreen > 2 && player == &players[fourthdisplayplayer]) - postimgtype4 = postimg; - else - postimgtype = postimg; + for (i = 0; i <= splitscreen; i++) + { + if (player == &players[displayplayers[i]]) + postimgtype[i] = postimg; + } } if (thiscam->momx || thiscam->momy) @@ -3641,11 +3623,11 @@ boolean P_CameraThinker(player_t *player, camera_t *thiscam, boolean resetcalled fixed_t cam_height = cv_cam_height.value; thiscam->z = thiscam->floorz; - if (player == &players[secondarydisplayplayer]) + if (player == &players[displayplayers[1]]) cam_height = cv_cam2_height.value; - if (player == &players[thirddisplayplayer]) + if (player == &players[displayplayers[2]]) cam_height = cv_cam3_height.value; - if (player == &players[fourthdisplayplayer]) + if (player == &players[displayplayers[3]]) cam_height = cv_cam4_height.value; if (thiscam->z > player->mo->z + player->mo->height + FixedMul(cam_height*FRACUNIT + 16*FRACUNIT, player->mo->scale)) { @@ -5897,7 +5879,7 @@ void P_SetScale(mobj_t *mobj, fixed_t newscale) if (player) { - G_GhostAddScale(newscale); + G_GhostAddScale((INT32) (player - players), newscale); player->viewheight = FixedMul(FixedDiv(player->viewheight, oldscale), newscale); // Nonono don't calculate viewheight elsewhere, this is the best place for it! player->dashspeed = FixedMul(FixedDiv(player->dashspeed, oldscale), newscale); // Prevents the player from having to re-charge up spindash if the player grew in size } @@ -6101,12 +6083,12 @@ void P_RunOverlays(void) { angle_t viewingangle; - if (players[displayplayer].awayviewtics) - viewingangle = R_PointToAngle2(mo->target->x, mo->target->y, players[displayplayer].awayviewmobj->x, players[displayplayer].awayviewmobj->y); - else if (!camera.chase && players[displayplayer].mo) - viewingangle = R_PointToAngle2(mo->target->x, mo->target->y, players[displayplayer].mo->x, players[displayplayer].mo->y); + if (players[displayplayers[0]].awayviewtics) + viewingangle = R_PointToAngle2(mo->target->x, mo->target->y, players[displayplayers[0]].awayviewmobj->x, players[displayplayers[0]].awayviewmobj->y); + else if (!camera[0].chase && players[displayplayers[0]].mo) + viewingangle = R_PointToAngle2(mo->target->x, mo->target->y, players[displayplayers[0]].mo->x, players[displayplayers[0]].mo->y); else - viewingangle = R_PointToAngle2(mo->target->x, mo->target->y, camera.x, camera.y); + viewingangle = R_PointToAngle2(mo->target->x, mo->target->y, camera[0].x, camera[0].y); if (!(mo->state->frame & FF_ANIMATE) && mo->state->var1) viewingangle += ANGLE_180; @@ -6680,7 +6662,7 @@ void P_MobjThinker(mobj_t *mobj) if (mobj->target && mobj->target->health && mobj->target->player && !mobj->target->player->spectator && mobj->target->player->health && mobj->target->player->playerstate != PST_DEAD - /*&& players[displayplayer].mo && !players[displayplayer].spectator*/) + /*&& players[displayplayers[0]].mo && !players[displayplayers[0]].spectator*/) { fixed_t scale = 3*mobj->target->scale; mobj->color = mobj->target->color; @@ -6688,7 +6670,7 @@ void P_MobjThinker(mobj_t *mobj) if ((G_RaceGametype() || mobj->target->player->kartstuff[k_bumper] <= 0) #if 1 // Set to 0 to test without needing to host - || ((mobj->target->player == &players[displayplayer]) || P_IsLocalPlayer(mobj->target->player)) + || ((mobj->target->player == &players[displayplayers[0]]) || P_IsLocalPlayer(mobj->target->player)) #endif ) mobj->flags2 |= MF2_DONTDRAW; @@ -6699,10 +6681,10 @@ void P_MobjThinker(mobj_t *mobj) mobj->angle = R_PointToAngle(mobj->x, mobj->y) + ANGLE_90; // literally only happened because i wanted to ^L^R the SPR_ITEM's - if (!splitscreen && players[displayplayer].mo) + if (!splitscreen && players[displayplayers[0]].mo) { - scale = mobj->target->scale + FixedMul(FixedDiv(abs(P_AproxDistance(players[displayplayer].mo->x-mobj->target->x, - players[displayplayer].mo->y-mobj->target->y)), RING_DIST), mobj->target->scale); + 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); if (scale > 16*mobj->target->scale) scale = 16*mobj->target->scale; } @@ -6887,7 +6869,7 @@ void P_MobjThinker(mobj_t *mobj) if (mobj->target && mobj->target->health && mobj->tracer && mobj->target->player && !mobj->target->player->spectator && mobj->target->player->health && mobj->target->player->playerstate != PST_DEAD - && players[displayplayer].mo && !players[displayplayer].spectator) + && players[displayplayers[0]].mo && !players[displayplayers[0]].spectator) { fixed_t scale = 3*mobj->target->scale; @@ -6909,8 +6891,8 @@ void P_MobjThinker(mobj_t *mobj) if (!splitscreen) { - scale = mobj->target->scale + FixedMul(FixedDiv(abs(P_AproxDistance(players[displayplayer].mo->x-mobj->target->x, - players[displayplayer].mo->y-mobj->target->y)), RING_DIST), mobj->target->scale); + 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); if (scale > 16*mobj->target->scale) scale = 16*mobj->target->scale; } @@ -8296,12 +8278,12 @@ void P_MobjThinker(mobj_t *mobj) angle_t viewingangle; statenum_t curstate = ((mobj->tics == 1) ? (mobj->state->nextstate) : ((statenum_t)(mobj->state-states))); - if (players[displayplayer].awayviewtics) - viewingangle = R_PointToAngle2(mobj->target->x, mobj->target->y, players[displayplayer].awayviewmobj->x, players[displayplayer].awayviewmobj->y); - else if (!camera.chase && players[displayplayer].mo) - viewingangle = R_PointToAngle2(mobj->target->x, mobj->target->y, players[displayplayer].mo->x, players[displayplayer].mo->y); + if (players[displayplayers[0]].awayviewtics) + viewingangle = R_PointToAngle2(mobj->target->x, mobj->target->y, players[displayplayers[0]].awayviewmobj->x, players[displayplayers[0]].awayviewmobj->y); + else if (!camera[0].chase && players[displayplayers[0]].mo) + viewingangle = R_PointToAngle2(mobj->target->x, mobj->target->y, players[displayplayers[0]].mo->x, players[displayplayers[0]].mo->y); else - viewingangle = R_PointToAngle2(mobj->target->x, mobj->target->y, camera.x, camera.y); + viewingangle = R_PointToAngle2(mobj->target->x, mobj->target->y, camera[0].x, camera[0].y); if (curstate > S_THUNDERSHIELD15) viewingangle += ANGLE_180; @@ -10562,13 +10544,13 @@ void P_PrecipitationEffects(void) // Local effects from here on out! // If we're not in game fully yet, we don't worry about them. - if (!playeringame[displayplayer] || !players[displayplayer].mo) + if (!playeringame[displayplayers[0]] || !players[displayplayers[0]].mo) return; if (sound_disabled) return; // Sound off? D'aw, no fun. - if (players[displayplayer].mo->subsector->sector->ceilingpic == skyflatnum) + if (players[displayplayers[0]].mo->subsector->sector->ceilingpic == skyflatnum) volume = 255; // Sky above? We get it full blast. else { @@ -10576,17 +10558,17 @@ void P_PrecipitationEffects(void) fixed_t closedist, newdist; // Essentially check in a 1024 unit radius of the player for an outdoor area. - yl = players[displayplayer].mo->y - 1024*FRACUNIT; - yh = players[displayplayer].mo->y + 1024*FRACUNIT; - xl = players[displayplayer].mo->x - 1024*FRACUNIT; - xh = players[displayplayer].mo->x + 1024*FRACUNIT; + yl = players[displayplayers[0]].mo->y - 1024*FRACUNIT; + yh = players[displayplayers[0]].mo->y + 1024*FRACUNIT; + xl = players[displayplayers[0]].mo->x - 1024*FRACUNIT; + xh = players[displayplayers[0]].mo->x + 1024*FRACUNIT; closedist = 2048*FRACUNIT; for (y = yl; y <= yh; y += FRACUNIT*64) for (x = xl; x <= xh; x += FRACUNIT*64) { if (R_PointInSubsector(x, y)->sector->ceilingpic == skyflatnum) // Found the outdoors! { - newdist = S_CalculateSoundDistance(players[displayplayer].mo->x, players[displayplayer].mo->y, 0, x, y, 0); + newdist = S_CalculateSoundDistance(players[displayplayers[0]].mo->x, players[displayplayers[0]].mo->y, 0, x, y, 0); if (newdist < closedist) closedist = newdist; } @@ -10601,7 +10583,7 @@ void P_PrecipitationEffects(void) volume = 255; if (sounds_rain && (!leveltime || leveltime % 80 == 1)) - S_StartSoundAtVolume(players[displayplayer].mo, sfx_rainin, volume); + S_StartSoundAtVolume(players[displayplayers[0]].mo, sfx_rainin, volume); if (!sounds_thunder) return; @@ -10609,7 +10591,7 @@ void P_PrecipitationEffects(void) if (effects_lightning && lightningStrike && volume) { // Large, close thunder sounds to go with our lightning. - S_StartSoundAtVolume(players[displayplayer].mo, sfx_litng1 + M_RandomKey(4), volume); + S_StartSoundAtVolume(players[displayplayers[0]].mo, sfx_litng1 + M_RandomKey(4), volume); } else if (thunderchance < 20) { @@ -10617,7 +10599,7 @@ void P_PrecipitationEffects(void) if (volume < 80) volume = 80; - S_StartSoundAtVolume(players[displayplayer].mo, sfx_athun1 + M_RandomKey(2), volume); + S_StartSoundAtVolume(players[displayplayers[0]].mo, sfx_athun1 + M_RandomKey(2), volume); } } @@ -10789,7 +10771,8 @@ void P_SpawnPlayer(INT32 playernum) } // spawn as spectator determination - if (!G_GametypeHasSpectators()) + if (multiplayer && demo.playback); // Don't mess with spectator values since the demo setup handles them already. + else if (!G_GametypeHasSpectators()) p->spectator = false; else if (netgame && p->jointime <= 1 && pcount) { @@ -10923,15 +10906,21 @@ void P_AfterPlayerSpawn(INT32 playernum) { player_t *p = &players[playernum]; mobj_t *mobj = p->mo; + UINT8 i; if (playernum == consoleplayer) - localangle = mobj->angle; - else if (playernum == secondarydisplayplayer) - localangle2 = mobj->angle; - else if (playernum == thirddisplayplayer) - localangle3 = mobj->angle; - else if (playernum == fourthdisplayplayer) - localangle4 = mobj->angle; + localangle[0] = mobj->angle; + else if (splitscreen) + { + for (i = 1; i <= splitscreen; i++) + { + if (playernum == displayplayers[i]) + { + localangle[i] = mobj->angle; + break; + } + } + } p->viewheight = 32<x, mobj->y, mobj->angle); - if (camera.chase) + for (i = 0; i <= splitscreen; i++) { - if (displayplayer == playernum) - P_ResetCamera(p, &camera); - } - if (camera2.chase && splitscreen) - { - if (secondarydisplayplayer == playernum) - P_ResetCamera(p, &camera2); - } - if (camera3.chase && splitscreen > 1) - { - if (thirddisplayplayer == playernum) - P_ResetCamera(p, &camera3); - } - if (camera4.chase && splitscreen > 2) - { - if (fourthdisplayplayer == playernum) - P_ResetCamera(p, &camera4); + if (camera[i].chase) + { + if (displayplayers[i] == playernum) + P_ResetCamera(p, &camera[i]); + } } if (CheckForReverseGravity) diff --git a/src/p_polyobj.c b/src/p_polyobj.c index 34402f1ac..03fb10d0f 100644 --- a/src/p_polyobj.c +++ b/src/p_polyobj.c @@ -1336,13 +1336,13 @@ static void Polyobj_rotateThings(polyobj_t *po, vertex_t origin, angle_t delta, if (turnthings == 2 || (turnthings == 1 && !mo->player)) { mo->angle += delta; if (mo->player == &players[consoleplayer]) - localangle = mo->angle; - else if (mo->player == &players[secondarydisplayplayer]) - localangle2 = mo->angle; - else if (mo->player == &players[thirddisplayplayer]) - localangle3 = mo->angle; - else if (mo->player == &players[fourthdisplayplayer]) - localangle4 = mo->angle; + localangle[0] += delta; + else if (mo->player == &players[displayplayers[1]]) + localangle[1] += delta; + else if (mo->player == &players[displayplayers[2]]) + localangle[2] += delta; + else if (mo->player == &players[displayplayers[3]]) + localangle[3] += delta; } } } diff --git a/src/p_saveg.c b/src/p_saveg.c index fb82ceb23..7d2e9a307 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -2078,13 +2078,13 @@ static void LoadMobjThinker(actionf_p1 thinker) mobj->player->mo = mobj; // added for angle prediction if (consoleplayer == i) - localangle = mobj->angle; - if (secondarydisplayplayer == i) - localangle2 = mobj->angle; - if (thirddisplayplayer == i) - localangle3 = mobj->angle; - if (fourthdisplayplayer == i) - localangle4 = mobj->angle; + localangle[0] = mobj->angle; + if (displayplayers[1] == i) + localangle[1] = mobj->angle; + if (displayplayers[2] == i) + localangle[2] = mobj->angle; + if (displayplayers[3] == i) + localangle[3] = mobj->angle; } if (diff & MD_MOVEDIR) mobj->movedir = READANGLE(save_p); @@ -3423,7 +3423,7 @@ void P_SaveNetGame(void) mobj_t *mobj; INT32 i = 1; // don't start from 0, it'd be confused with a blank pointer otherwise - CV_SaveNetVars(&save_p); + CV_SaveNetVars(&save_p, false); P_NetArchiveMisc(); // Assign the mobjnumber for pointer tracking diff --git a/src/p_setup.c b/src/p_setup.c index ba4554e68..c85e95307 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -65,6 +65,10 @@ #include "lua_script.h" #include "lua_hook.h" +#if !defined (UNDER_CE) +#include +#endif + #if defined (_WIN32) || defined (_WIN32_WCE) #include #include @@ -2265,7 +2269,7 @@ static void P_LevelInitStuff(void) leveltime = 0; - localaiming = localaiming2 = localaiming3 = localaiming4 = 0; + memset(localaiming, 0, sizeof(localaiming)); // map object scale mapobjectscale = mapheaderinfo[gamemap-1]->mobj_scale; @@ -2532,29 +2536,29 @@ static void P_ForceCharacter(const char *forcecharskin) { if (splitscreen) { - SetPlayerSkin(secondarydisplayplayer, forcecharskin); - if ((unsigned)cv_playercolor2.value != skins[players[secondarydisplayplayer].skin].prefcolor && !modeattacking) + SetPlayerSkin(displayplayers[1], forcecharskin); + if ((unsigned)cv_playercolor2.value != skins[players[displayplayers[1]].skin].prefcolor && !modeattacking) { - CV_StealthSetValue(&cv_playercolor2, skins[players[secondarydisplayplayer].skin].prefcolor); - players[secondarydisplayplayer].skincolor = skins[players[secondarydisplayplayer].skin].prefcolor; + CV_StealthSetValue(&cv_playercolor2, skins[players[displayplayers[1]].skin].prefcolor); + players[displayplayers[1]].skincolor = skins[players[displayplayers[1]].skin].prefcolor; } if (splitscreen > 1) { - SetPlayerSkin(thirddisplayplayer, forcecharskin); - if ((unsigned)cv_playercolor3.value != skins[players[thirddisplayplayer].skin].prefcolor && !modeattacking) + SetPlayerSkin(displayplayers[2], forcecharskin); + if ((unsigned)cv_playercolor3.value != skins[players[displayplayers[2]].skin].prefcolor && !modeattacking) { - CV_StealthSetValue(&cv_playercolor3, skins[players[thirddisplayplayer].skin].prefcolor); - players[thirddisplayplayer].skincolor = skins[players[thirddisplayplayer].skin].prefcolor; + CV_StealthSetValue(&cv_playercolor3, skins[players[displayplayers[2]].skin].prefcolor); + players[displayplayers[2]].skincolor = skins[players[displayplayers[2]].skin].prefcolor; } if (splitscreen > 2) { - SetPlayerSkin(fourthdisplayplayer, forcecharskin); - if ((unsigned)cv_playercolor4.value != skins[players[fourthdisplayplayer].skin].prefcolor && !modeattacking) + SetPlayerSkin(displayplayers[3], forcecharskin); + if ((unsigned)cv_playercolor4.value != skins[players[displayplayers[3]].skin].prefcolor && !modeattacking) { - CV_StealthSetValue(&cv_playercolor4, skins[players[fourthdisplayplayer].skin].prefcolor); - players[fourthdisplayplayer].skincolor = skins[players[fourthdisplayplayer].skin].prefcolor; + CV_StealthSetValue(&cv_playercolor4, skins[players[displayplayers[3]].skin].prefcolor); + players[displayplayers[3]].skincolor = skins[players[displayplayers[3]].skin].prefcolor; } } } @@ -2725,7 +2729,7 @@ static boolean P_CanSave(void) if ((cursaveslot < 0) // Playing without saving || (modifiedgame && !savemoddata) // Game is modified || (netgame || multiplayer) // Not in single-player - || (demoplayback || demorecording || metalrecording) // Currently in demo + || (demo.playback || demo.recording || metalrecording) // Currently in demo || (players[consoleplayer].lives <= 0) // Completely dead || (modeattacking || ultimatemode || G_IsSpecialStage(gamemap))) // Specialized instances return false; @@ -2789,7 +2793,8 @@ boolean P_SetupLevel(boolean skipprecip) P_LevelInitStuff(); - postimgtype = postimgtype2 = postimgtype3 = postimgtype4 = postimg_none; + for (i = 0; i <= splitscreen; i++) + postimgtype[i] = postimg_none; if (mapheaderinfo[gamemap-1]->forcecharacter[0] != '\0' && atoi(mapheaderinfo[gamemap-1]->forcecharacter) != 255) @@ -2825,7 +2830,7 @@ boolean P_SetupLevel(boolean skipprecip) // Encore mode fade to pink to white // This is handled BEFORE sounds are stopped. - if (rendermode != render_none && encoremode && !prevencoremode) + if (rendermode != render_none && encoremode && !prevencoremode && !demo.rewinding) { tic_t locstarttime, endtime, nowtime; @@ -2877,7 +2882,7 @@ boolean P_SetupLevel(boolean skipprecip) // Let's fade to white here // But only if we didn't do the encore startup wipe - if (rendermode != render_none && !ranspecialwipe) + if (rendermode != render_none && !ranspecialwipe && !demo.rewinding) { F_WipeStartScreen(); V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, levelfadecol); @@ -3109,9 +3114,9 @@ boolean P_SetupLevel(boolean skipprecip) } } - if (modeattacking == ATTACKING_RECORD && !demoplayback) + if (modeattacking == ATTACKING_RECORD && !demo.playback) P_LoadRecordGhosts(); - /*else if (modeattacking == ATTACKING_NIGHTS && !demoplayback) + /*else if (modeattacking == ATTACKING_NIGHTS && !demo.playback) P_LoadNightsGhosts();*/ if (G_TagGametype()) @@ -3159,25 +3164,25 @@ boolean P_SetupLevel(boolean skipprecip) ? cv_basenumlaps.value : mapheaderinfo[gamemap - 1]->numlaps); + // Start recording replay in multiplayer with a temp filename + //@TODO I'd like to fix dedis crashing when recording replays for the future too... + if (!demo.playback && multiplayer && !dedicated) { + static char buf[256]; + sprintf(buf, "replay"PATHSEP"online"PATHSEP"%d-%s", (int) (time(NULL)), G_BuildMapName(gamemap)); + + I_mkdir(va("%s"PATHSEP"replay", srb2home), 0755); + I_mkdir(va("%s"PATHSEP"replay"PATHSEP"online", srb2home), 0755); + G_RecordDemo(buf); + } + // =========== // landing point for netgames. netgameskip: if (!dedicated) { - P_SetupCamera(displayplayer, &camera); - if (splitscreen) - { - P_SetupCamera(secondarydisplayplayer, &camera2); - if (splitscreen > 1) - { - P_SetupCamera(thirddisplayplayer, &camera3); - if (splitscreen > 2) - { - P_SetupCamera(fourthdisplayplayer, &camera4); - } - } - } + for (i = 0; i <= splitscreen; i++) + P_SetupCamera(displayplayers[i], &camera[i]); // Salt: CV_ClearChangedFlags() messes with your settings :( /*if (!cv_cam_height.changed) @@ -3218,7 +3223,7 @@ boolean P_SetupLevel(boolean skipprecip) /*if (rendermode != render_none) CV_Set(&cv_fov, cv_fov.defaultvalue);*/ - displayplayer = consoleplayer; // Start with your OWN view, please! + displayplayers[0] = consoleplayer; // Start with your OWN view, please! } /*if (cv_useranalog.value) @@ -3297,7 +3302,10 @@ boolean P_SetupLevel(boolean skipprecip) savedata.lives = 0; } - skyVisible = skyVisible1 = skyVisible2 = skyVisible3 = skyVisible4 = true; // assume the skybox is visible on level load. + // assume the skybox is visible on level load. + skyVisible = true; + memset(skyVisiblePerPlayer, true, sizeof(skyVisiblePerPlayer)); + if (loadprecip) // uglier hack { // to make a newly loaded level start on the second frame. INT32 buf = gametic % BACKUPTICS; diff --git a/src/p_spec.c b/src/p_spec.c index 67bb74720..9ed17eaa7 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -2243,7 +2243,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) I_Assert(!mo || !P_MobjWasRemoved(mo)); // If mo is there, mo must be valid! if (mo && mo->player && botingame) - bot = players[secondarydisplayplayer].mo; + bot = players[displayplayers[1]].mo; // note: only commands with linedef types >= 400 && < 500 can be used switch (line->special) @@ -2381,35 +2381,21 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) if (mo->player) { + UINT8 i; + if (bot) // This might put poor Tails in a wall if he's too far behind! D: But okay, whatever! >:3 P_TeleportMove(bot, bot->x + x, bot->y + y, bot->z + z); - if (splitscreen > 2 && mo->player == &players[fourthdisplayplayer] && camera4.chase) + + for (i = 0; i <= splitscreen; i++) { - camera4.x += x; - camera4.y += y; - camera4.z += z; - camera4.subsector = R_PointInSubsector(camera4.x, camera4.y); - } - else if (splitscreen > 1 && mo->player == &players[thirddisplayplayer] && camera3.chase) - { - camera3.x += x; - camera3.y += y; - camera3.z += z; - camera3.subsector = R_PointInSubsector(camera3.x, camera3.y); - } - else if (splitscreen && mo->player == &players[secondarydisplayplayer] && camera2.chase) - { - camera2.x += x; - camera2.y += y; - camera2.z += z; - camera2.subsector = R_PointInSubsector(camera2.x, camera2.y); - } - else if (camera.chase && mo->player == &players[displayplayer]) - { - camera.x += x; - camera.y += y; - camera.z += z; - camera.subsector = R_PointInSubsector(camera.x, camera.y); + if (mo->player == &players[displayplayers[i]] && camera[i].chase) + { + camera[i].x += x; + camera[i].y += y; + camera[i].z += z; + camera[i].subsector = R_PointInSubsector(camera[i].x, camera[i].y); + break; + } } } } @@ -2515,8 +2501,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) if (line->flags & ML_NOCLIMB) { // play the sound from nowhere, but only if display player triggered it - if (mo && mo->player && (mo->player == &players[displayplayer] || mo->player == &players[secondarydisplayplayer] - || mo->player == &players[thirddisplayplayer] || mo->player == &players[fourthdisplayplayer])) + if (mo && mo->player && P_IsDisplayPlayer(mo->player)) S_StartSound(NULL, sfxnum); } else if (line->flags & ML_EFFECT4) @@ -3834,16 +3819,16 @@ DoneSection2: if (player->mo->scale > mapobjectscale) linespeed = FixedMul(linespeed, mapobjectscale + (player->mo->scale - mapobjectscale)); - if (!demoplayback || P_AnalogMove(player)) + if (!demo.playback || P_AnalogMove(player)) { if (player == &players[consoleplayer]) - localangle = player->mo->angle; - else if (player == &players[secondarydisplayplayer]) - localangle2 = player->mo->angle; - else if (player == &players[thirddisplayplayer]) - localangle3 = player->mo->angle; - else if (player == &players[fourthdisplayplayer]) - localangle4 = player->mo->angle; + localangle[0] = player->mo->angle; + else if (player == &players[displayplayers[1]]) + localangle[1] = player->mo->angle; + else if (player == &players[displayplayers[2]]) + localangle[2] = player->mo->angle; + else if (player == &players[displayplayers[3]]) + localangle[3] = player->mo->angle; } if (!(lines[i].flags & ML_EFFECT4)) @@ -7841,44 +7826,44 @@ void T_Pusher(pusher_t *p) thing->player->pflags |= PF_SLIDING; thing->angle = R_PointToAngle2 (0, 0, xspeed<<(FRACBITS-PUSH_FACTOR), yspeed<<(FRACBITS-PUSH_FACTOR)); - if (!demoplayback || P_AnalogMove(thing->player)) + if (!demo.playback || P_AnalogMove(thing->player)) { if (thing->player == &players[consoleplayer]) { - if (thing->angle - localangle > ANGLE_180) - localangle -= (localangle - thing->angle) / 8; + if (thing->angle - localangle[0] > ANGLE_180) + localangle[0] -= (localangle[0] - thing->angle) / 8; else - localangle += (thing->angle - localangle) / 8; + localangle[0] += (thing->angle - localangle[0]) / 8; } - else if (thing->player == &players[secondarydisplayplayer]) + else if (thing->player == &players[displayplayers[1]]) { - if (thing->angle - localangle2 > ANGLE_180) - localangle2 -= (localangle2 - thing->angle) / 8; + if (thing->angle - localangle[1] > ANGLE_180) + localangle[1] -= (localangle[1] - thing->angle) / 8; else - localangle2 += (thing->angle - localangle2) / 8; + localangle[1] += (thing->angle - localangle[1]) / 8; } - else if (thing->player == &players[thirddisplayplayer]) + else if (thing->player == &players[displayplayers[2]]) { - if (thing->angle - localangle3 > ANGLE_180) - localangle3 -= (localangle3 - thing->angle) / 8; + if (thing->angle - localangle[2] > ANGLE_180) + localangle[2] -= (localangle[2] - thing->angle) / 8; else - localangle3 += (thing->angle - localangle3) / 8; + localangle[2] += (thing->angle - localangle[2]) / 8; } - else if (thing->player == &players[fourthdisplayplayer]) + else if (thing->player == &players[displayplayers[3]]) { - if (thing->angle - localangle4 > ANGLE_180) - localangle4 -= (localangle4 - thing->angle) / 8; + if (thing->angle - localangle[3] > ANGLE_180) + localangle[3] -= (localangle[3] - thing->angle) / 8; else - localangle4 += (thing->angle - localangle4) / 8; + localangle[3] += (thing->angle - localangle[3]) / 8; } /*if (thing->player == &players[consoleplayer]) - localangle = thing->angle; - else if (thing->player == &players[secondarydisplayplayer]) - localangle2 = thing->angle; - else if (thing->player == &players[thirddisplayplayer]) - localangle3 = thing->angle; - else if (thing->player == &players[fourthdisplayplayer]) - localangle4 = thing->angle;*/ + localangle[0] = thing->angle; + else if (thing->player == &players[displayplayers[1]]) + localangle[1] = thing->angle; + else if (thing->player == &players[displayplayers[2]]) + localangle[2] = thing->angle; + else if (thing->player == &players[displayplayers[3]]) + localangle[3] = thing->angle;*/ } } diff --git a/src/p_telept.c b/src/p_telept.c index 24e201fc1..74f9d462c 100644 --- a/src/p_telept.c +++ b/src/p_telept.c @@ -36,6 +36,7 @@ void P_MixUp(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z, angle_t angle, INT32 flags2) { const INT32 takeflags2 = MF2_TWOD|MF2_OBJECTFLIP; + UINT8 i; // the move is ok, // so link the thing into its new position @@ -64,23 +65,25 @@ void P_MixUp(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z, angle_t angle, // absolute angle position if (thing == players[consoleplayer].mo) - localangle = angle; - if (thing == players[secondarydisplayplayer].mo) - localangle2 = angle; - if (thing == players[thirddisplayplayer].mo) - localangle3 = angle; - if (thing == players[fourthdisplayplayer].mo) - localangle4 = angle; + localangle[0] = angle; + else if (splitscreen) + { + for (i = 1; i <= splitscreen; i++) + { + if (thing == players[displayplayers[i]].mo) + { + localangle[i] = angle; + break; + } + } + } // move chasecam at new player location - if (splitscreen > 2 && camera4.chase && thing->player == &players[fourthdisplayplayer]) - P_ResetCamera(thing->player, &camera4); - else if (splitscreen > 1 && camera3.chase && thing->player == &players[thirddisplayplayer]) - P_ResetCamera(thing->player, &camera3); - else if (splitscreen && camera2.chase && thing->player == &players[secondarydisplayplayer]) - P_ResetCamera(thing->player, &camera2); - else if (camera.chase && thing->player == &players[displayplayer]) - P_ResetCamera(thing->player, &camera); + for (i = 0; i <= splitscreen; i++) + { + if (thing->player == &players[displayplayers[i]] && camera[i].chase) + P_ResetCamera(thing->player, &camera[i]); + } // don't run in place after a teleport thing->player->cmomx = thing->player->cmomy = 0; @@ -123,6 +126,8 @@ void P_MixUp(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z, angle_t angle, */ boolean P_Teleport(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z, angle_t angle, boolean flash, boolean dontstopmove) { + UINT8 i; + if (!P_TeleportMove(thing, x, y, z)) return false; @@ -144,24 +149,26 @@ boolean P_Teleport(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z, angle_t angle thing->reactiontime = TICRATE/2; // don't move for about half a second // absolute angle position - if (thing->player == &players[consoleplayer]) - localangle = angle; - if (thing->player == &players[secondarydisplayplayer]) - localangle2 = angle; - if (thing->player == &players[thirddisplayplayer]) - localangle3 = angle; - if (thing->player == &players[fourthdisplayplayer]) - localangle4 = angle; + if (thing == players[consoleplayer].mo) + localangle[0] = angle; + else if (splitscreen) + { + for (i = 1; i <= splitscreen; i++) + { + if (thing == players[displayplayers[i]].mo) + { + localangle[i] = angle; + break; + } + } + } // move chasecam at new player location - if (splitscreen > 2 && camera4.chase && thing->player == &players[fourthdisplayplayer]) - P_ResetCamera(thing->player, &camera4); - else if (splitscreen > 1 && camera3.chase && thing->player == &players[thirddisplayplayer]) - P_ResetCamera(thing->player, &camera3); - else if (splitscreen && camera2.chase && thing->player == &players[secondarydisplayplayer]) - P_ResetCamera(thing->player, &camera2); - else if (camera.chase && thing->player == &players[displayplayer]) - P_ResetCamera(thing->player, &camera); + for (i = 0; i <= splitscreen; i++) + { + if (thing->player == &players[displayplayers[i]] && camera[i].chase) + P_ResetCamera(thing->player, &camera[i]); + } // don't run in place after a teleport if (!dontstopmove) diff --git a/src/p_tick.c b/src/p_tick.c index 85eaea9ba..2502c7213 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -13,6 +13,7 @@ #include "doomstat.h" #include "g_game.h" +#include "g_input.h" #include "p_local.h" #include "z_zone.h" #include "s_sound.h" @@ -582,7 +583,7 @@ void P_Ticker(boolean run) { P_MapStart(); OP_ObjectplaceMovement(&players[0]); - P_MoveChaseCamera(&players[0], &camera, false); + P_MoveChaseCamera(&players[0], &camera[0], false); P_MapEnd(); return; } @@ -590,18 +591,60 @@ void P_Ticker(boolean run) // Check for pause or menu up in single player if (paused || P_AutoPause()) - return; + { + if (demo.rewinding && leveltime > 0) + { + leveltime = (leveltime-1) & ~3; + G_PreviewRewind(leveltime); + } - postimgtype = postimgtype2 = postimgtype3 = postimgtype4 = postimg_none; + return; + } + + for (i = 0; i <= splitscreen; i++) + postimgtype[i] = postimg_none; P_MapStart(); if (run) { - if (demorecording) - G_WriteDemoTiccmd(&players[consoleplayer].cmd, 0); - if (demoplayback) - G_ReadDemoTiccmd(&players[consoleplayer].cmd, 0); + if (demo.recording) + { + G_WriteDemoExtraData(); + for (i = 0; i < MAXPLAYERS; i++) + if (playeringame[i]) + G_WriteDemoTiccmd(&players[i].cmd, i); + } + 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 + } for (i = 0; i < MAXPLAYERS; i++) if (playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo)) @@ -609,7 +652,7 @@ void P_Ticker(boolean run) } // Keep track of how long they've been playing! - if (!demoplayback) // Don't increment if a demo is playing. + if (!demo.playback) // Don't increment if a demo is playing. totalplaytime++; /*if (!useNightsSS && G_IsSpecialStage(gamemap)) @@ -705,10 +748,25 @@ void P_Ticker(boolean run) G_ReadMetalTic(metalplayback); if (metalrecording) G_WriteMetalTic(players[consoleplayer].mo); - if (demorecording) - G_WriteGhostTic(players[consoleplayer].mo); - if (demoplayback) // Use Ghost data for consistency checks. - G_ConsGhostTic(); + + if (demo.recording) + { + G_WriteAllGhostTics(); + + if (cv_recordmultiplayerdemos.value && (demo.savemode == DSM_NOTSAVING || demo.savemode == DSM_WILLAUTOSAVE)) + if (demo.savebutton && demo.savebutton + 3*TICRATE < leveltime && InputDown(gc_lookback, 1)) + demo.savemode = DSM_TITLEENTRY; + } + 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(); + } + if (modeattacking) G_GhostTicker(); @@ -719,17 +777,17 @@ void P_Ticker(boolean run) } // Always move the camera. - if (camera.chase) - P_MoveChaseCamera(&players[displayplayer], &camera, false); - if (splitscreen && camera2.chase) - P_MoveChaseCamera(&players[secondarydisplayplayer], &camera2, false); - if (splitscreen > 1 && camera3.chase) - P_MoveChaseCamera(&players[thirddisplayplayer], &camera3, false); - if (splitscreen > 2 && camera4.chase) - P_MoveChaseCamera(&players[fourthdisplayplayer], &camera4, false); + for (i = 0; i <= splitscreen; i++) + { + if (camera[i].chase) + P_MoveChaseCamera(&players[displayplayers[i]], &camera[i], false); + } P_MapEnd(); + if (demo.playback) + G_StoreRewindInfo(); + // Z_CheckMemCleanup(); } @@ -739,7 +797,8 @@ void P_PreTicker(INT32 frames) INT32 i,framecnt; ticcmd_t temptic; - postimgtype = postimgtype2 = postimgtype3 = postimgtype4 = postimg_none; + for (i = 0; i <= splitscreen; i++) + postimgtype[i] = postimg_none; for (framecnt = 0; framecnt < frames; ++framecnt) { diff --git a/src/p_user.c b/src/p_user.c index 9999b5a9d..6e4890676 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -169,10 +169,10 @@ fixed_t P_ReturnThrustY(mobj_t *mo, angle_t angle, fixed_t move) boolean P_AutoPause(void) { // Don't pause even on menu-up or focus-lost in netgames or record attack - if (netgame || modeattacking) + if (netgame || modeattacking || demo.title) return false; - return (menuactive || ( window_notinfocus && cv_pauseifunfocused.value )); + return ((menuactive && !demo.playback) || ( window_notinfocus && cv_pauseifunfocused.value )); } // @@ -654,13 +654,13 @@ static void P_DeNightserizePlayer(player_t *player) // Restore aiming angle if (player == &players[consoleplayer]) - localaiming = 0; - else if (player == &players[secondarydisplayplayer]) - localaiming2 = 0; - else if (player == &players[thirddisplayplayer]) - localaiming3 = 0; - else if (player == &players[fourthdisplayplayer]) - localaiming4 = 0; + localaiming[0] = 0; + else if (player == &players[displayplayers[1]]) + localaiming[1] = 0; + else if (player == &players[displayplayers[2]]) + localaiming[2] = 0; + else if (player == &players[displayplayers[3]]) + localaiming[3] = 0; if (player->mo->tracer) P_RemoveMobj(player->mo->tracer); @@ -1147,29 +1147,32 @@ boolean P_EndingMusic(player_t *player) if (!P_IsLocalPlayer(player)) // Only applies to a local player return false; + if (multiplayer && demo.playback) // Don't play this in multiplayer replays + return false; + // Event - Level Finish // Check for if this is valid or not if (splitscreen) { - if (!((players[displayplayer].exiting || (players[displayplayer].pflags & PF_TIMEOVER)) - || (players[secondarydisplayplayer].exiting || (players[secondarydisplayplayer].pflags & PF_TIMEOVER)) - || ((splitscreen < 2) && (players[thirddisplayplayer].exiting || (players[thirddisplayplayer].pflags & PF_TIMEOVER))) - || ((splitscreen < 3) && (players[fourthdisplayplayer].exiting || (players[fourthdisplayplayer].pflags & PF_TIMEOVER))))) + if (!((players[displayplayers[0]].exiting || (players[displayplayers[0]].pflags & PF_TIMEOVER)) + || (players[displayplayers[1]].exiting || (players[displayplayers[1]].pflags & PF_TIMEOVER)) + || ((splitscreen < 2) && (players[displayplayers[2]].exiting || (players[displayplayers[2]].pflags & PF_TIMEOVER))) + || ((splitscreen < 3) && (players[displayplayers[3]].exiting || (players[displayplayers[3]].pflags & PF_TIMEOVER))))) return false; - bestlocalplayer = &players[displayplayer]; - bestlocalpos = ((players[displayplayer].pflags & PF_TIMEOVER) ? MAXPLAYERS+1 : players[displayplayer].kartstuff[k_position]); + bestlocalplayer = &players[displayplayers[0]]; + bestlocalpos = ((players[displayplayers[0]].pflags & PF_TIMEOVER) ? MAXPLAYERS+1 : players[displayplayers[0]].kartstuff[k_position]); #define setbests(p) \ if (((players[p].pflags & PF_TIMEOVER) ? MAXPLAYERS+1 : players[p].kartstuff[k_position]) < bestlocalpos) \ { \ bestlocalplayer = &players[p]; \ bestlocalpos = ((players[p].pflags & PF_TIMEOVER) ? MAXPLAYERS+1 : players[p].kartstuff[k_position]); \ } - setbests(secondarydisplayplayer); + setbests(displayplayers[1]); if (splitscreen > 1) - setbests(thirddisplayplayer); + setbests(displayplayers[2]); if (splitscreen > 2) - setbests(fourthdisplayplayer); + setbests(displayplayers[3]); #undef setbests } else @@ -1250,12 +1253,12 @@ void P_RestoreMusic(player_t *player) else if (players[p].kartstuff[k_invincibilitytimer] > bestlocaltimer) \ { wantedmus = 1; bestlocaltimer = players[p].kartstuff[k_invincibilitytimer]; } \ } - setbests(displayplayer); - setbests(secondarydisplayplayer); + setbests(displayplayers[0]); + setbests(displayplayers[1]); if (splitscreen > 1) - setbests(thirddisplayplayer); + setbests(displayplayers[2]); if (splitscreen > 2) - setbests(fourthdisplayplayer); + setbests(displayplayers[3]); #undef setbests } else @@ -1513,10 +1516,39 @@ fixed_t P_GetPlayerSpinHeight(player_t *player) // boolean P_IsLocalPlayer(player_t *player) { - return ((splitscreen > 2 && player == &players[fourthdisplayplayer]) - || (splitscreen > 1 && player == &players[thirddisplayplayer]) - || (splitscreen && player == &players[secondarydisplayplayer]) - || player == &players[consoleplayer]); + UINT8 i; + + if (player == &players[consoleplayer]) + return true; + else if (splitscreen) + { + for (i = 1; i <= splitscreen; i++) // Skip P1 + { + if (player == &players[displayplayers[i]]) + return true; + } + } + + return false; +} + +// +// P_IsDisplayPlayer +// +// Returns true if player is +// currently being watched. +// +boolean P_IsDisplayPlayer(player_t *player) +{ + UINT8 i; + + for (i = 0; i <= splitscreen; i++) // DON'T skip P1 + { + if (player == &players[displayplayers[i]]) + return true; + } + + return false; } // @@ -1662,11 +1694,7 @@ void P_DoPlayerExit(player_t *player) if (player->exiting || mapreset) return; - if ((player == &players[consoleplayer] - || (splitscreen && player == &players[secondarydisplayplayer]) - || (splitscreen > 1 && player == &players[thirddisplayplayer]) - || (splitscreen > 2 && player == &players[fourthdisplayplayer])) - && (!player->spectator && !demoplayback)) + if (P_IsLocalPlayer(player) && (!player->spectator && !demo.playback)) legitimateexit = true; if (G_RaceGametype()) // If in Race Mode, allow @@ -1725,6 +1753,9 @@ void P_DoPlayerExit(player_t *player) player->powers[pw_spacetime] = 0; player->kartstuff[k_cardanimation] = 0; // srb2kart: reset battle animation + if (player == &players[consoleplayer]) + demo.savebutton = leveltime; + /*if (playeringame[player-players] && netgame && !circuitmap) CONS_Printf(M_GetText("%s has completed the level.\n"), player_names[player-players]);*/ } @@ -2306,12 +2337,12 @@ static void P_CheckInvincibilityTimer(player_t *player) //if (player->powers[pw_shield] & SH_FIREFLOWER) //{ // player->mo->color = SKINCOLOR_WHITE; - // G_GhostAddColor(GHC_FIREFLOWER); + // G_GhostAddColor((INT32) (player - players), GHC_FIREFLOWER); //} //else { player->mo->color = player->skincolor; - G_GhostAddColor(GHC_NORMAL); + G_GhostAddColor((INT32) (player - players), GHC_NORMAL); } } @@ -2402,8 +2433,7 @@ static void P_DoPlayerHeadSigns(player_t *player) // If you're "IT", show a big "IT" over your head for others to see. if (player->pflags & PF_TAGIT) { - if (!(player == &players[consoleplayer] || player == &players[displayplayer] || player == &players[secondarydisplayplayer] - || player == &players[thirddisplayplayer] || player == &players[fourthdisplayplayer])) // Don't display it on your own view. + if (!P_IsDisplayPlayer(player)) // Don't display it on your own view. { if (!(player->mo->eflags & MFE_VERTICALFLIP)) P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height, MT_TAG); @@ -2891,13 +2921,13 @@ static void P_DoClimbing(player_t *player) // SRB2kart - unused } if (player == &players[consoleplayer]) - localangle = player->mo->angle; - else if (player == &players[secondarydisplayplayer]) - localangle2 = player->mo->angle; - else if (player == &players[thirddisplayplayer]) - localangle3 = player->mo->angle; - else if (player == &players[fourthdisplayplayer]) - localangle4 = player->mo->angle; + localangle[0] = player->mo->angle; + else if (player == &players[displayplayers[1]]) + localangle[1] = player->mo->angle; + else if (player == &players[displayplayers[2]]) + localangle[2] = player->mo->angle; + else if (player == &players[displayplayers[3]]) + localangle[3] = player->mo->angle; if (player->climbing == 0) P_SetPlayerMobjState(player->mo, S_PLAY_ATK1); @@ -3576,8 +3606,8 @@ static void P_DoSpinDash(player_t *player, ticcmd_t *cmd) // SRB2kart - unused. // Now spawn the color thok circle. P_SpawnSpinMobj(player, player->revitem); - if (demorecording) - G_GhostAddRev(); + if (demo.recording) + G_GhostAddRev((INT32) (player - players)); } } // If not moving up or down, and travelling faster than a speed of four while not holding @@ -3735,14 +3765,14 @@ boolean P_AnalogMove(player_t *player) fixed_t tempx = 0, tempy = 0; angle_t tempangle, origtempangle; - if (splitscreen > 2 && player == &players[fourthdisplayplayer]) - thiscam = &camera4; - else if (splitscreen > 1 && player == &players[thirddisplayplayer]) - thiscam = &camera3; - else if (splitscreen && player == &players[secondarydisplayplayer]) - thiscam = &camera2; + if (splitscreen > 2 && player == &players[displayplayers[3]]) + thiscam = &camera[3]; + else if (splitscreen > 1 && player == &players[displayplayers[2]]) + thiscam = &camera[2]; + else if (splitscreen && player == &players[displayplayers[1]]) + thiscam = &camera[1]; else - thiscam = &camera; + thiscam = &camera[0]; if (!cmd->forwardmove && !cmd->sidemove) return 0; @@ -3883,13 +3913,13 @@ static void P_2dMovement(player_t *player) } if (player == &players[consoleplayer]) - localangle = player->mo->angle; - else if (player == &players[secondarydisplayplayer]) - localangle2 = player->mo->angle; - else if (player == &players[thirddisplayplayer]) - localangle3 = player->mo->angle; - else if (player == &players[fourthdisplayplayer]) - localangle4 = player->mo->angle; + localangle[0] = player->mo->angle; + else if (player == &players[displayplayers[1]]) + localangle[1] = player->mo->angle; + else if (player == &players[displayplayers[2]]) + localangle[2] = player->mo->angle; + else if (player == &players[displayplayers[3]]) + localangle[3] = player->mo->angle; if (player->pflags & PF_GLIDING) movepushangle = player->mo->angle; @@ -5419,13 +5449,13 @@ static void P_NiGHTSMovement(player_t *player) P_SetMobjStateNF(player->mo->tracer, leveltime & 1 ? flystate : flystate+1); if (player == &players[consoleplayer]) - localangle = player->mo->angle; - else if (player == &players[secondarydisplayplayer]) - localangle2 = player->mo->angle; - else if (player == &players[thirddisplayplayer]) - localangle3 = player->mo->angle; - else if (player == &players[fourthdisplayplayer]) - localangle4 = player->mo->angle; + localangle[0] = player->mo->angle; + else if (player == &players[displayplayers[1]]) + localangle[1] = player->mo->angle; + else if (player == &players[displayplayers[2]]) + localangle[2] = player->mo->angle; + else if (player == &players[displayplayers[3]]) + localangle[3] = player->mo->angle; if (still) { @@ -5452,13 +5482,13 @@ static void P_NiGHTSMovement(player_t *player) movingangle = InvAngle(movingangle); if (player == &players[consoleplayer]) - localaiming = movingangle; - else if (player == &players[secondarydisplayplayer]) - localaiming2 = movingangle; - else if (player == &players[thirddisplayplayer]) - localaiming3 = movingangle; - else if (player == &players[fourthdisplayplayer]) - localaiming4 = movingangle; + localaiming[0] = movingangle; + else if (player == &players[displayplayers[1]]) + localaiming[1] = movingangle; + else if (player == &players[displayplayers[2]]) + localaiming[2] = movingangle; + else if (player == &players[displayplayers[3]]) + localaiming[3] = movingangle; player->mo->tracer->angle = player->mo->angle; @@ -5722,7 +5752,7 @@ static void P_MovePlayer(player_t *player) { if (G_IsSpecialStage(gamemap)) { - if (player == &players[displayplayer]) // only play the sound for yourself landing + if (player == &players[displayplayers[0]]) // only play the sound for yourself landing S_StartSound(NULL, sfx_s3k6a); for (i = 0; i < MAXPLAYERS; i++) if (playeringame[i]) @@ -6130,8 +6160,8 @@ static void P_MovePlayer(player_t *player) if (player->pflags & PF_SPINNING && player->speed > FixedMul(15<mo->scale) && !(player->pflags & PF_JUMPED)) { P_SpawnSpinMobj(player, player->spinitem); - if (demorecording) - G_GhostAddSpin(); + if (demo.recording) + G_GhostAddSpin((INT32) (player - players)); } */ @@ -6228,13 +6258,13 @@ static void P_MovePlayer(player_t *player) // Update the local angle control. if (player == &players[consoleplayer]) - localangle = player->mo->angle; - else if (player == &players[secondarydisplayplayer]) - localangle2 = player->mo->angle; - else if (player == &players[thirddisplayplayer]) - localangle3 = player->mo->angle; - else if (player == &players[fourthdisplayplayer]) - localangle4 = player->mo->angle; + localangle[0] = player->mo->angle; + else if (player == &players[displayplayers[1]]) + localangle[1] = player->mo->angle; + else if (player == &players[displayplayers[2]]) + localangle[2] = player->mo->angle; + else if (player == &players[displayplayers[3]]) + localangle[3] = player->mo->angle; } #endif @@ -6556,13 +6586,13 @@ static void P_DoZoomTube(player_t *player) player->mo->angle = R_PointToAngle2(player->mo->x, player->mo->y, player->mo->tracer->x, player->mo->tracer->y); if (player == &players[consoleplayer]) - localangle = player->mo->angle; - else if (player == &players[secondarydisplayplayer]) - localangle2 = player->mo->angle; - else if (player == &players[thirddisplayplayer]) - localangle3 = player->mo->angle; - else if (player == &players[fourthdisplayplayer]) - localangle4 = player->mo->angle; + localangle[0] = player->mo->angle; + else if (player == &players[displayplayers[1]]) + localangle[1] = player->mo->angle; + else if (player == &players[displayplayers[2]]) + localangle[2] = player->mo->angle; + else if (player == &players[displayplayers[3]]) + localangle[3] = player->mo->angle; } #if 0 if (player->mo->state != &states[S_KART_SPIN]) @@ -6952,13 +6982,13 @@ void P_HomingAttack(mobj_t *source, mobj_t *enemy) // Home in on your target if (source->player) { if (source->player == &players[consoleplayer]) - localangle = source->angle; - else if (source->player == &players[secondarydisplayplayer]) - localangle2 = source->angle; - else if (source->player == &players[thirddisplayplayer]) - localangle3 = source->angle; - else if (source->player == &players[fourthdisplayplayer]) - localangle4 = source->angle; + localangle[0] = source->angle; + else if (source->player == &players[displayplayers[1]]) + localangle[1] = source->angle; + else if (source->player == &players[displayplayers[2]]) + localangle[2] = source->angle; + else if (source->player == &players[displayplayers[3]]) + localangle[3] = source->angle; } // change slope @@ -7091,7 +7121,7 @@ notrealplayer: // P_MoveCamera: make sure the camera is not outside the world and looks at the player avatar // -camera_t camera, camera2, camera3, camera4; // Four cameras, three for splitscreen +camera_t camera[MAXSPLITSCREENPLAYERS]; // Four cameras, three for splitscreen static void CV_CamRotate_OnChange(void) { @@ -7196,10 +7226,10 @@ void P_ResetCamera(player_t *player, camera_t *thiscam) thiscam->y = y; thiscam->z = z; - if (!(thiscam == &camera && (cv_cam_still.value || cv_analog.value)) - && !(thiscam == &camera2 && (cv_cam2_still.value || cv_analog2.value)) - && !(thiscam == &camera3 && (cv_cam3_still.value || cv_analog3.value)) - && !(thiscam == &camera4 && (cv_cam4_still.value || cv_analog4.value))) + if (!(thiscam == &camera[0] && (cv_cam_still.value || cv_analog.value)) + && !(thiscam == &camera[1] && (cv_cam2_still.value || cv_analog2.value)) + && !(thiscam == &camera[2] && (cv_cam3_still.value || cv_analog3.value)) + && !(thiscam == &camera[3] && (cv_cam4_still.value || cv_analog4.value))) { thiscam->angle = player->mo->angle; thiscam->aiming = 0; @@ -7257,46 +7287,49 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall if (player->spectator) // force cam off for spectators return true; - if (!cv_chasecam.value && thiscam == &camera) + if (!cv_chasecam.value && thiscam == &camera[0]) return true; - if (!cv_chasecam2.value && thiscam == &camera2) + if (!cv_chasecam2.value && thiscam == &camera[1]) return true; - if (!cv_chasecam3.value && thiscam == &camera3) + if (!cv_chasecam3.value && thiscam == &camera[2]) return true; - if (!cv_chasecam4.value && thiscam == &camera4) + if (!cv_chasecam4.value && thiscam == &camera[3]) return true; } if (!thiscam->chase && !resetcalled) { if (player == &players[consoleplayer]) - focusangle = localangle; - else if (player == &players[secondarydisplayplayer]) - focusangle = localangle2; - else if (player == &players[thirddisplayplayer]) - focusangle = localangle3; - else if (player == &players[fourthdisplayplayer]) - focusangle = localangle4; + focusangle = localangle[0]; + else if (player == &players[displayplayers[1]]) + focusangle = localangle[1]; + else if (player == &players[displayplayers[2]]) + focusangle = localangle[2]; + else if (player == &players[displayplayers[3]]) + focusangle = localangle[3]; else focusangle = mo->angle; - if (thiscam == &camera) + + if (thiscam == &camera[0]) camrotate = cv_cam_rotate.value; - else if (thiscam == &camera2) + else if (thiscam == &camera[1]) camrotate = cv_cam2_rotate.value; - else if (thiscam == &camera3) + else if (thiscam == &camera[2]) camrotate = cv_cam3_rotate.value; - else if (thiscam == &camera4) + else if (thiscam == &camera[3]) camrotate = cv_cam4_rotate.value; else camrotate = 0; + if (leveltime < introtime) // Whoooshy camera! { const INT32 introcam = (introtime - leveltime); camrotate += introcam*5; } + thiscam->angle = focusangle + FixedAngle(camrotate*FRACUNIT); P_ResetCamera(player, thiscam); return true; @@ -7310,30 +7343,30 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall // if (leveltime > 0 && timeinmap <= 0) // return true; - if (demoplayback) + if (demo.playback) { focusangle = mo->angle; focusaiming = 0; } else if (player == &players[consoleplayer]) { - focusangle = localangle; - focusaiming = localaiming; + focusangle = localangle[0]; + focusaiming = localaiming[0]; } - else if (player == &players[secondarydisplayplayer]) + else if (player == &players[displayplayers[1]]) { - focusangle = localangle2; - focusaiming = localaiming2; + focusangle = localangle[1]; + focusaiming = localaiming[1]; } - else if (player == &players[thirddisplayplayer]) + else if (player == &players[displayplayers[2]]) { - focusangle = localangle3; - focusaiming = localaiming3; + focusangle = localangle[2]; + focusaiming = localaiming[2]; } - else if (player == &players[fourthdisplayplayer]) + else if (player == &players[displayplayers[3]]) { - focusangle = localangle4; - focusaiming = localaiming4; + focusangle = localangle[3]; + focusaiming = localaiming[3]; } else { @@ -7344,17 +7377,8 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall if (P_CameraThinker(player, thiscam, resetcalled)) return true; - if (thiscam == &camera) - { - num = 0; - camspeed = cv_cam_speed.value; - camstill = cv_cam_still.value; - camrotate = cv_cam_rotate.value; - camdist = FixedMul(cv_cam_dist.value, mapobjectscale); - camheight = FixedMul(cv_cam_height.value, mapobjectscale); - lookback = camspin; - } - else if (thiscam == &camera2) // Camera 2 + + if (thiscam == &camera[1]) // Camera 2 { num = 1; camspeed = cv_cam2_speed.value; @@ -7362,9 +7386,9 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall camrotate = cv_cam2_rotate.value; camdist = FixedMul(cv_cam2_dist.value, mapobjectscale); camheight = FixedMul(cv_cam2_height.value, mapobjectscale); - lookback = camspin2; + lookback = camspin[1]; } - else if (thiscam == &camera3) // Camera 3 + else if (thiscam == &camera[2]) // Camera 3 { num = 2; camspeed = cv_cam3_speed.value; @@ -7372,9 +7396,9 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall camrotate = cv_cam3_rotate.value; camdist = FixedMul(cv_cam3_dist.value, mapobjectscale); camheight = FixedMul(cv_cam3_height.value, mapobjectscale); - lookback = camspin3; + lookback = camspin[2]; } - else // Camera 4 + else if (thiscam == &camera[3]) // Camera 4 { num = 3; camspeed = cv_cam4_speed.value; @@ -7382,7 +7406,17 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall camrotate = cv_cam4_rotate.value; camdist = FixedMul(cv_cam4_dist.value, mapobjectscale); camheight = FixedMul(cv_cam4_height.value, mapobjectscale); - lookback = camspin4; + lookback = camspin[3]; + } + else // Camera 1 + { + num = 0; + camspeed = cv_cam_speed.value; + camstill = cv_cam_still.value; + camrotate = cv_cam_rotate.value; + camdist = FixedMul(cv_cam_dist.value, mapobjectscale); + camheight = FixedMul(cv_cam_height.value, mapobjectscale); + lookback = camspin[0]; } if (timeover) @@ -7443,10 +7477,10 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall } if (!resetcalled && (leveltime > starttime && timeover != 2) - && ((thiscam == &camera && t_cam_rotate != -42) - || (thiscam == &camera2 && t_cam2_rotate != -42) - || (thiscam == &camera3 && t_cam3_rotate != -42) - || (thiscam == &camera4 && t_cam4_rotate != -42))) + && ((thiscam == &camera[0] && t_cam_rotate != -42) + || (thiscam == &camera[1] && t_cam2_rotate != -42) + || (thiscam == &camera[2] && t_cam3_rotate != -42) + || (thiscam == &camera[3] && t_cam4_rotate != -42))) { angle = FixedAngle(camrotate*FRACUNIT); thiscam->angle = angle; @@ -7841,8 +7875,8 @@ boolean P_SpectatorJoinGame(player_t *player) player->playerstate = PST_REBORN; //Reset away view - if (P_IsLocalPlayer(player) && displayplayer != consoleplayer) - displayplayer = consoleplayer; + if (P_IsLocalPlayer(player) && displayplayers[0] != consoleplayer) + displayplayers[0] = consoleplayer; if (changeto == 1) CONS_Printf(M_GetText("%s switched to the %c%s%c.\n"), player_names[player-players], '\x85', M_GetText("Red team"), '\x80'); @@ -7865,8 +7899,8 @@ boolean P_SpectatorJoinGame(player_t *player) player->playerstate = PST_REBORN; //Reset away view - if (P_IsLocalPlayer(player) && displayplayer != consoleplayer) - displayplayer = consoleplayer; + if (P_IsLocalPlayer(player) && displayplayers[0] != consoleplayer) + displayplayers[0] = consoleplayer; HU_AddChatText(va(M_GetText("\x82*%s entered the game."), player_names[player-players]), false); return true; // no more player->mo, cannot continue. @@ -7877,9 +7911,10 @@ boolean P_SpectatorJoinGame(player_t *player) static void P_CalcPostImg(player_t *player) { sector_t *sector = player->mo->subsector->sector; - postimg_t *type; + postimg_t *type = postimg_none; INT32 *param; fixed_t pviewheight; + UINT8 i; if (player->mo->eflags & MFE_VERTICALFLIP) pviewheight = player->mo->z + player->mo->height - player->viewheight; @@ -7892,25 +7927,14 @@ static void P_CalcPostImg(player_t *player) pviewheight = player->awayviewmobj->z + 20*FRACUNIT; } - if (splitscreen > 2 && player == &players[fourthdisplayplayer]) + for (i = 0; i <= splitscreen; i++) { - type = &postimgtype4; - param = &postimgparam4; - } - else if (splitscreen > 1 && player == &players[thirddisplayplayer]) - { - type = &postimgtype3; - param = &postimgparam3; - } - else if (splitscreen && player == &players[secondarydisplayplayer]) - { - type = &postimgtype2; - param = &postimgparam2; - } - else - { - type = &postimgtype; - param = &postimgparam; + if (player == &players[displayplayers[i]]) + { + type = &postimgtype[i]; + param = &postimgparam[i]; + break; + } } // see if we are in heat (no, not THAT kind of heat...) @@ -8018,11 +8042,7 @@ void P_DoTimeOver(player_t *player) player->pflags |= PF_TIMEOVER; - if ((player == &players[consoleplayer] - || (splitscreen && player == &players[secondarydisplayplayer]) - || (splitscreen > 1 && player == &players[thirddisplayplayer]) - || (splitscreen > 2 && player == &players[fourthdisplayplayer])) - && !demoplayback) + if (P_IsLocalPlayer(player) && !demo.playback) legitimateexit = true; // SRB2kart: losing a race is still seeing it through to the end :p if (player->mo) @@ -8069,7 +8089,7 @@ void P_PlayerThink(player_t *player) } #ifdef SEENAMES - if (netgame && player == &players[displayplayer] && !(leveltime % (TICRATE/5)) && !splitscreen) + if (netgame && player == &players[displayplayers[0]] && !(leveltime % (TICRATE/5)) && !splitscreen) { seenplayer = NULL; @@ -8131,6 +8151,19 @@ 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. + if (cmd->buttons & BT_FORWARD) + player->kartstuff[k_throwdir] = 1; + else if (cmd->buttons & BT_BACKWARD) + player->kartstuff[k_throwdir] = -1; + else + player->kartstuff[k_throwdir] = 0; +#endif + // Add some extra randomization. if (cmd->forwardmove) P_RandomFixed(); @@ -8403,7 +8436,9 @@ void P_PlayerThink(player_t *player) || player->kartstuff[k_driftboost] || player->kartstuff[k_sneakertimer] || player->kartstuff[k_startboost]) && !player->kartstuff[k_invincibilitytimer] // SRB2kart && (player->speed + abs(player->mo->momz)) > FixedMul(20*FRACUNIT,player->mo->scale)) { + UINT8 i; mobj_t *gmobj = P_SpawnGhostMobj(player->mo); + gmobj->fuse = 2; if (leveltime & 1) { @@ -8411,15 +8446,17 @@ void P_PlayerThink(player_t *player) gmobj->frame |= tr_trans70< 1 && player == &players[thirddisplayplayer] && !camera3.chase) - || (splitscreen > 2 && player == &players[fourthdisplayplayer] && !camera4.chase)) - gmobj->flags2 |= MF2_DONTDRAW; + for (i = 0; i <= splitscreen; i++) + { + if (player == &players[displayplayers[i]] && !camera[i].chase) + { + gmobj->flags2 |= MF2_DONTDRAW; + break; + } + } } #endif @@ -8619,6 +8656,7 @@ void P_PlayerAfterThink(player_t *player) ticcmd_t *cmd; //INT32 oldweapon = player->currentweapon; // SRB2kart - unused camera_t *thiscam = NULL; // if not one of the displayed players, just don't bother + UINT8 i; #ifdef PARANOIA if (!player->mo) @@ -8641,14 +8679,14 @@ void P_PlayerAfterThink(player_t *player) P_PlayerInSpecialSector(player); #endif - if (splitscreen > 2 && player == &players[fourthdisplayplayer]) - thiscam = &camera4; - else if (splitscreen > 1 && player == &players[thirddisplayplayer]) - thiscam = &camera3; - else if (splitscreen && player == &players[secondarydisplayplayer]) - thiscam = &camera2; - else if (player == &players[displayplayer]) - thiscam = &camera; + for (i = 0; i <= splitscreen; i++) + { + if (player == &players[displayplayers[i]]) + { + thiscam = &camera[i]; + break; + } + } if (player->playerstate == PST_DEAD) { @@ -8833,13 +8871,13 @@ void P_PlayerAfterThink(player_t *player) player->mo->angle = player->mo->tracer->angle; if (player == &players[consoleplayer]) - localangle = player->mo->angle; - else if (player == &players[secondarydisplayplayer]) - localangle2 = player->mo->angle; - else if (player == &players[thirddisplayplayer]) - localangle3 = player->mo->angle; - else if (player == &players[fourthdisplayplayer]) - localangle4 = player->mo->angle; + localangle[0] = player->mo->angle; + else if (player == &players[displayplayers[1]]) + localangle[1] = player->mo->angle; + else if (player == &players[displayplayers[2]]) + localangle[2] = player->mo->angle; + else if (player == &players[displayplayers[3]]) + localangle[3] = player->mo->angle; } if (P_AproxDistance(player->mo->x - player->mo->tracer->x, player->mo->y - player->mo->tracer->y) > player->mo->radius) @@ -8907,13 +8945,13 @@ void P_PlayerAfterThink(player_t *player) player->mo->angle += cmd->sidemove< ANGLE_MAX if (player == &players[consoleplayer]) - localangle = player->mo->angle; // Adjust the local control angle. - else if (player == &players[secondarydisplayplayer]) - localangle2 = player->mo->angle; - else if (player == &players[thirddisplayplayer]) - localangle3 = player->mo->angle; - else if (player == &players[fourthdisplayplayer]) - localangle4 = player->mo->angle; + localangle[0] = player->mo->angle; // Adjust the local control angle. + else if (player == &players[displayplayers[1]]) + localangle[1] = player->mo->angle; + else if (player == &players[displayplayers[2]]) + localangle[2] = player->mo->angle; + else if (player == &players[displayplayers[3]]) + localangle[3] = player->mo->angle; } } diff --git a/src/r_bsp.c b/src/r_bsp.c index b819735e1..296cbbe87 100644 --- a/src/r_bsp.c +++ b/src/r_bsp.c @@ -252,20 +252,23 @@ sector_t *R_FakeFlat(sector_t *sec, sector_t *tempsec, INT32 *floorlightlevel, mobj_t *viewmobj = viewplayer->mo; INT32 heightsec; boolean underwater; + UINT8 i; - if (splitscreen > 2 && viewplayer == &players[fourthdisplayplayer] && camera4.chase) - heightsec = R_PointInSubsector(camera4.x, camera4.y)->sector->heightsec; - else if (splitscreen > 1 && viewplayer == &players[thirddisplayplayer] && camera3.chase) - heightsec = R_PointInSubsector(camera3.x, camera3.y)->sector->heightsec; - else if (splitscreen && viewplayer == &players[secondarydisplayplayer] && camera2.chase) - heightsec = R_PointInSubsector(camera2.x, camera2.y)->sector->heightsec; - else if (camera.chase && viewplayer == &players[displayplayer]) - heightsec = R_PointInSubsector(camera.x, camera.y)->sector->heightsec; - else if (viewmobj) + for (i = 0; i <= splitscreen; i++) + { + if (viewplayer == &players[displayplayers[i]] && camera[i].chase) + { + heightsec = R_PointInSubsector(camera[i].x, camera[i].y)->sector->heightsec; + break; + } + } + + if (i > splitscreen && viewmobj) heightsec = R_PointInSubsector(viewmobj->x, viewmobj->y)->sector->heightsec; else return sec; - underwater = heightsec != -1 && viewz <= sectors[heightsec].floorheight; + + underwater = (heightsec != -1 && viewz <= sectors[heightsec].floorheight); // Replace sector being drawn, with a copy to be hacked *tempsec = *sec; @@ -827,7 +830,7 @@ static void R_AddPolyObjects(subsector_t *sub) drawseg_t *firstseg; -static void R_Subsector(size_t num, UINT8 viewnumber) +static void R_Subsector(size_t num) { INT32 count, floorlightlevel, ceilinglightlevel, light; seg_t *line; @@ -1149,7 +1152,7 @@ static void R_Subsector(size_t num, UINT8 viewnumber) // Either you must pass the fake sector and handle validcount here, on the // real sector, or you must account for the lighting in some other way, // like passing it as an argument. - R_AddSprites(sub->sector, (floorlightlevel+ceilinglightlevel)/2, viewnumber); + R_AddSprites(sub->sector, (floorlightlevel+ceilinglightlevel)/2); firstseg = NULL; @@ -1355,7 +1358,7 @@ INT32 R_GetPlaneLight(sector_t *sector, fixed_t planeheight, boolean underside) // // killough 5/2/98: reformatted, removed tail recursion -void R_RenderBSPNode(INT32 bspnum, UINT8 viewnumber) +void R_RenderBSPNode(INT32 bspnum) { node_t *bsp; INT32 side; @@ -1366,7 +1369,7 @@ void R_RenderBSPNode(INT32 bspnum, UINT8 viewnumber) // Decide which side the view point is on. side = R_PointOnSide(viewx, viewy, bsp); // Recursively divide front space. - R_RenderBSPNode(bsp->children[side], viewnumber); + R_RenderBSPNode(bsp->children[side]); // Possibly divide back space. @@ -1384,5 +1387,5 @@ void R_RenderBSPNode(INT32 bspnum, UINT8 viewnumber) portalcullsector = NULL; } - R_Subsector(bspnum == -1 ? 0 : bspnum & ~NF_SUBSECTOR, viewnumber); + R_Subsector(bspnum == -1 ? 0 : bspnum & ~NF_SUBSECTOR); } diff --git a/src/r_bsp.h b/src/r_bsp.h index 7810c9b5c..e3662e2e6 100644 --- a/src/r_bsp.h +++ b/src/r_bsp.h @@ -37,7 +37,7 @@ extern INT32 doorclosed; void R_ClearClipSegs(void); void R_PortalClearClipSegs(INT32 start, INT32 end); void R_ClearDrawSegs(void); -void R_RenderBSPNode(INT32 bspnum, UINT8 viewnumber); +void R_RenderBSPNode(INT32 bspnum); void R_AddPortal(INT32 line1, INT32 line2, INT32 x1, INT32 x2); #ifdef POLYOBJECTS diff --git a/src/r_data.c b/src/r_data.c index 1a74f7336..7fb11855f 100644 --- a/src/r_data.c +++ b/src/r_data.c @@ -1600,7 +1600,7 @@ void R_PrecacheLevel(void) thinker_t *th; spriteframe_t *sf; - if (demoplayback) + if (demo.playback) return; // do not flush the memory, Z_Malloc twice with same user will cause error in Z_CheckHeap() diff --git a/src/r_main.c b/src/r_main.c index a69ce9691..94c18d4ba 100644 --- a/src/r_main.c +++ b/src/r_main.c @@ -30,6 +30,7 @@ #include "p_spec.h" // skyboxmo #include "z_zone.h" #include "m_random.h" // quake camera shake +#include "doomstat.h" // MAXSPLITSCREENPLAYERS #ifdef HWRENDER #include "hardware/hw_main.h" @@ -65,9 +66,10 @@ size_t loopcount; fixed_t viewx, viewy, viewz; angle_t viewangle, aimingangle; +UINT8 viewssnum; fixed_t viewcos, viewsin; boolean viewsky, skyVisible; -boolean skyVisible1, skyVisible2, skyVisible3, skyVisible4; // saved values of skyVisible for P1/P2/P3/P4, for splitscreen +boolean skyVisiblePerPlayer[MAXSPLITSCREENPLAYERS]; // saved values of skyVisible for each splitscreen player sector_t *viewsector; player_t *viewplayer; @@ -193,19 +195,12 @@ void SplitScreen_OnChange(void) // recompute screen size R_ExecuteSetViewSize(); - if (!demoplayback && !botingame) + if (!demo.playback && !botingame) { - for (i = 1; i < 3; i++) + for (i = 1; i < MAXSPLITSCREENPLAYERS; i++) { if (i > splitscreen) - { - if (i == 1) - CL_RemoveSplitscreenPlayer(secondarydisplayplayer); - else if (i == 2) - CL_RemoveSplitscreenPlayer(thirddisplayplayer); - else if (i == 3) - CL_RemoveSplitscreenPlayer(fourthdisplayplayer); - } + CL_RemoveSplitscreenPlayer(displayplayers[i]); else CL_AddSplitscreenPlayer(); } @@ -215,21 +210,27 @@ void SplitScreen_OnChange(void) } else { - secondarydisplayplayer = consoleplayer; - thirddisplayplayer = consoleplayer; - fourthdisplayplayer = consoleplayer; + for (i = 1; i < MAXSPLITSCREENPLAYERS; i++) + displayplayers[i] = consoleplayer; + for (i = 0; i < MAXPLAYERS; i++) + { if (playeringame[i] && i != consoleplayer) { - if (secondarydisplayplayer == consoleplayer) - secondarydisplayplayer = i; - else if (thirddisplayplayer == consoleplayer) - thirddisplayplayer = i; - else if (fourthdisplayplayer == consoleplayer) - fourthdisplayplayer = i; - else + UINT8 j; + for (j = 1; j < MAXSPLITSCREENPLAYERS; j++) + { + if (displayplayers[j] == consoleplayer) + { + displayplayers[j] = i; + break; + } + } + + if (j == MAXSPLITSCREENPLAYERS) break; } + } } } static void Fov_OnChange(void) @@ -844,16 +845,20 @@ static void R_SetupFreelook(void) void R_SkyboxFrame(player_t *player) { - camera_t *thiscam; + camera_t *thiscam = &camera[0]; + UINT8 i; - if (splitscreen > 2 && player == &players[fourthdisplayplayer]) - thiscam = &camera4; - else if (splitscreen > 1 && player == &players[thirddisplayplayer]) - thiscam = &camera3; - else if (splitscreen && player == &players[secondarydisplayplayer]) - thiscam = &camera2; - else - thiscam = &camera; + if (splitscreen) + { + for (i = 1; i <= splitscreen; i++) + { + if (player == &players[displayplayers[i]]) + { + thiscam = &camera[i]; + break; + } + } + } // cut-away view stuff viewsky = true; @@ -879,27 +884,24 @@ void R_SkyboxFrame(player_t *player) { aimingangle = player->aiming; viewangle = player->mo->angle; - if (/*!demoplayback && */player->playerstate != PST_DEAD) + if (/*!demo.playback && */player->playerstate != PST_DEAD) { if (player == &players[consoleplayer]) { - viewangle = localangle; // WARNING: camera uses this - aimingangle = localaiming; + viewangle = localangle[0]; // WARNING: camera uses this + aimingangle = localaiming[0]; } - else if (player == &players[secondarydisplayplayer]) + else if (splitscreen) { - viewangle = localangle2; - aimingangle = localaiming2; - } - else if (player == &players[thirddisplayplayer]) - { - viewangle = localangle3; - aimingangle = localaiming3; - } - else if (player == &players[fourthdisplayplayer]) - { - viewangle = localangle4; - aimingangle = localaiming4; + for (i = 1; i <= splitscreen; i++) + { + if (player == &players[displayplayers[i]]) + { + viewangle = localangle[i]; + aimingangle = localaiming[i]; + break; + } + } } } } @@ -1078,24 +1080,24 @@ void R_SetupFrame(player_t *player, boolean skybox) camera_t *thiscam; boolean chasecam = false; - if (splitscreen > 2 && player == &players[fourthdisplayplayer]) + if (splitscreen > 2 && player == &players[displayplayers[3]]) { - thiscam = &camera4; + thiscam = &camera[3]; chasecam = (cv_chasecam4.value != 0); } - else if (splitscreen > 1 && player == &players[thirddisplayplayer]) + else if (splitscreen > 1 && player == &players[displayplayers[2]]) { - thiscam = &camera3; + thiscam = &camera[2]; chasecam = (cv_chasecam3.value != 0); } - else if (splitscreen && player == &players[secondarydisplayplayer]) + else if (splitscreen && player == &players[displayplayers[1]]) { - thiscam = &camera2; + thiscam = &camera[1]; chasecam = (cv_chasecam2.value != 0); } else { - thiscam = &camera; + thiscam = &camera[0]; chasecam = (cv_chasecam.value != 0); } @@ -1141,27 +1143,25 @@ void R_SetupFrame(player_t *player, boolean skybox) aimingangle = player->aiming; viewangle = viewmobj->angle; - if (/*!demoplayback && */player->playerstate != PST_DEAD) + if (/*!demo.playback && */player->playerstate != PST_DEAD) { if (player == &players[consoleplayer]) { - viewangle = localangle; // WARNING: camera uses this - aimingangle = localaiming; + viewangle = localangle[0]; // WARNING: camera uses this + aimingangle = localaiming[0]; } - else if (player == &players[secondarydisplayplayer]) + else if (splitscreen) { - viewangle = localangle2; - aimingangle = localaiming2; - } - else if (player == &players[thirddisplayplayer]) - { - viewangle = localangle3; - aimingangle = localaiming3; - } - else if (player == &players[fourthdisplayplayer]) - { - viewangle = localangle4; - aimingangle = localaiming4; + UINT8 i; + for (i = 1; i <= splitscreen; i++) + { + if (player == &players[displayplayers[i]]) + { + viewangle = localangle[i]; + aimingangle = localaiming[i]; + break; + } + } } } } @@ -1323,19 +1323,10 @@ void R_RenderPlayerView(player_t *player) { portal_pair *portal; const boolean skybox = (skyboxmo[0] && cv_skybox.value); - UINT8 viewnumber; - - if (player == &players[secondarydisplayplayer] && splitscreen) - viewnumber = 1; - else if (player == &players[thirddisplayplayer] && splitscreen > 1) - viewnumber = 2; - else if (player == &players[fourthdisplayplayer] && splitscreen > 2) - viewnumber = 3; - else - viewnumber = 0; + UINT8 i; // if this is display player 1 - if (cv_homremoval.value && player == &players[displayplayer]) + if (cv_homremoval.value && player == &players[displayplayers[0]]) { if (cv_homremoval.value == 1) V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); // No HOM effect! @@ -1343,7 +1334,7 @@ void R_RenderPlayerView(player_t *player) V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 128+(timeinmap&15)); } // Draw over the fourth screen so you don't have to stare at a HOM :V - else if (splitscreen == 2 && player == &players[thirddisplayplayer]) + else if (splitscreen == 2 && player == &players[displayplayers[2]]) #if 1 { // V_DrawPatchFill, but for the fourth screen only @@ -1362,14 +1353,14 @@ void R_RenderPlayerView(player_t *player) #endif // load previous saved value of skyVisible for the player - if (splitscreen > 2 && player == &players[fourthdisplayplayer]) - skyVisible = skyVisible4; - else if (splitscreen > 1 && player == &players[thirddisplayplayer]) - skyVisible = skyVisible3; - else if (splitscreen && player == &players[secondarydisplayplayer]) - skyVisible = skyVisible2; - else - skyVisible = skyVisible1; + for (i = 0; i <= splitscreen; i++) + { + if (player == &players[displayplayers[i]]) + { + skyVisible = skyVisiblePerPlayer[i]; + break; + } + } portalrender = 0; portal_base = portal_cap = NULL; @@ -1386,7 +1377,7 @@ void R_RenderPlayerView(player_t *player) R_ClearVisibleFloorSplats(); #endif - R_RenderBSPNode((INT32)numnodes - 1, viewnumber); + R_RenderBSPNode((INT32)numnodes - 1); R_ClipSprites(); R_DrawPlanes(); #ifdef FLOORSPLATS @@ -1419,7 +1410,7 @@ void R_RenderPlayerView(player_t *player) mytotal = 0; ProfZeroTimer(); #endif - R_RenderBSPNode((INT32)numnodes - 1, viewnumber); + R_RenderBSPNode((INT32)numnodes - 1); R_ClipSprites(); #ifdef TIMING RDMSR(0x10, &mycount); @@ -1444,7 +1435,7 @@ void R_RenderPlayerView(player_t *player) validcount++; - R_RenderBSPNode((INT32)numnodes - 1, viewnumber); + R_RenderBSPNode((INT32)numnodes - 1); R_ClipSprites(); //R_DrawPlanes(); //R_DrawMasked(); @@ -1470,16 +1461,16 @@ void R_RenderPlayerView(player_t *player) // Check for new console commands. NetUpdate(); - // save value to skyVisible1 or skyVisible2 + // save value to skyVisiblePerPlayer // this is so that P1 can't affect whether P2 can see a skybox or not, or vice versa - if (splitscreen > 2 && player == &players[fourthdisplayplayer]) - skyVisible4 = skyVisible; - else if (splitscreen > 1 && player == &players[thirddisplayplayer]) - skyVisible3 = skyVisible; - else if (splitscreen && player == &players[secondarydisplayplayer]) - skyVisible2 = skyVisible; - else - skyVisible1 = skyVisible; + for (i = 0; i <= splitscreen; i++) + { + if (player == &players[displayplayers[i]]) + { + skyVisiblePerPlayer[i] = skyVisible; + break; + } + } } // ========================================================================= diff --git a/src/r_plane.c b/src/r_plane.c index 0ff97fcc3..db5bfbda2 100644 --- a/src/r_plane.c +++ b/src/r_plane.c @@ -883,12 +883,12 @@ void R_DrawSinglePlane(visplane_t *pl) if (bottom > vid.height) bottom = vid.height; - if (splitscreen > 2 && viewplayer == &players[fourthdisplayplayer]) // Only copy the part of the screen we need + if (splitscreen > 2 && viewplayer == &players[displayplayers[3]]) // Only copy the part of the screen we need scr = (screens[0] + (top+(viewheight))*vid.width + viewwidth); - else if ((splitscreen == 1 && viewplayer == &players[secondarydisplayplayer]) - || (splitscreen > 1 && viewplayer == &players[thirddisplayplayer])) + else if ((splitscreen == 1 && viewplayer == &players[displayplayers[1]]) + || (splitscreen > 1 && viewplayer == &players[displayplayers[2]])) scr = (screens[0] + (top+(viewheight))*vid.width); - else if (splitscreen > 1 && viewplayer == &players[secondarydisplayplayer]) + else if (splitscreen > 1 && viewplayer == &players[displayplayers[1]]) scr = (screens[0] + ((top)*vid.width) + viewwidth); else scr = (screens[0] + ((top)*vid.width)); diff --git a/src/r_state.h b/src/r_state.h index d6d123e99..e37bdf52e 100644 --- a/src/r_state.h +++ b/src/r_state.h @@ -17,6 +17,7 @@ // Need data structure definitions. #include "d_player.h" #include "r_data.h" +#include "doomstat.h" // MAXSPLITSCREENPLAYERS #ifdef __GNUG__ #pragma interface @@ -88,8 +89,9 @@ extern side_t *sides; // extern fixed_t viewx, viewy, viewz; extern angle_t viewangle, aimingangle; +extern UINT8 viewssnum; // splitscreen view number extern boolean viewsky, skyVisible; -extern boolean skyVisible1, skyVisible2, skyVisible3, skyVisible4; // saved values of skyVisible for P1 and P2, for splitscreen +extern boolean skyVisiblePerPlayer[MAXSPLITSCREENPLAYERS]; // saved values of skyVisible of each splitscreen player extern sector_t *viewsector; extern player_t *viewplayer; extern UINT8 portalrender; diff --git a/src/r_things.c b/src/r_things.c index f8d131bbe..b2170924e 100644 --- a/src/r_things.c +++ b/src/r_things.c @@ -927,6 +927,13 @@ static void R_DrawVisSprite(vissprite_t *vis) if (vis->x2 >= vid.width) vis->x2 = vid.width-1; +#if 1 + // Something is occasionally setting 1px-wide sprites whose frac is exactly the width of the sprite, causing crashes due to + // accessing invalid column info. Until the cause is found, let's try to correct those manually... + while (frac + vis->xiscale*(vis->x2-vis->x1) > SHORT(patch->width)<x2 >= vis->x1) + vis->x2--; +#endif + for (dc_x = vis->x1; dc_x <= vis->x2; dc_x++, frac += vis->xiscale) { if (vis->scalestep) // currently papersprites only @@ -1697,7 +1704,7 @@ static void R_ProjectPrecipitationSprite(precipmobj_t *thing) // R_AddSprites // During BSP traversal, this adds sprites by sector. // -void R_AddSprites(sector_t *sec, INT32 lightlevel, UINT8 viewnumber) +void R_AddSprites(sector_t *sec, INT32 lightlevel) { mobj_t *thing; precipmobj_t *precipthing; // Tails 08-25-2002 @@ -1743,19 +1750,19 @@ void R_AddSprites(sector_t *sec, INT32 lightlevel, UINT8 viewnumber) if (splitscreen) { if (thing->eflags & MFE_DRAWONLYFORP1) - if (viewnumber != 0) + if (viewssnum != 0) continue; if (thing->eflags & MFE_DRAWONLYFORP2) - if (viewnumber != 1) + if (viewssnum != 1) continue; if (thing->eflags & MFE_DRAWONLYFORP3 && splitscreen > 1) - if (viewnumber != 2) + if (viewssnum != 2) continue; if (thing->eflags & MFE_DRAWONLYFORP4 && splitscreen > 2) - if (viewnumber != 3) + if (viewssnum != 3) continue; } @@ -1778,19 +1785,19 @@ void R_AddSprites(sector_t *sec, INT32 lightlevel, UINT8 viewnumber) if (splitscreen) { if (thing->eflags & MFE_DRAWONLYFORP1) - if (viewnumber != 0) + if (viewssnum != 0) continue; if (thing->eflags & MFE_DRAWONLYFORP2) - if (viewnumber != 1) + if (viewssnum != 1) continue; if (thing->eflags & MFE_DRAWONLYFORP3 && splitscreen > 1) - if (viewnumber != 2) + if (viewssnum != 2) continue; if (thing->eflags & MFE_DRAWONLYFORP4 && splitscreen > 2) - if (viewnumber != 3) + if (viewssnum != 3) continue; } @@ -2652,15 +2659,15 @@ void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum) player->kartspeed = skin->kartspeed; player->kartweight = skin->kartweight; - /*if (!(cv_debug || devparm) && !(netgame || multiplayer || demoplayback || modeattacking)) + /*if (!(cv_debug || devparm) && !(netgame || multiplayer || demo.playback || modeattacking)) { if (playernum == consoleplayer) CV_StealthSetValue(&cv_playercolor, skin->prefcolor); - else if (playernum == secondarydisplayplayer) + else if (playernum == displayplayers[1]) CV_StealthSetValue(&cv_playercolor2, skin->prefcolor); - else if (playernum == thirddisplayplayer) + else if (playernum == displayplayers[2]) CV_StealthSetValue(&cv_playercolor3, skin->prefcolor); - else if (playernum == fourthdisplayplayer) + else if (playernum == displayplayers[3]) CV_StealthSetValue(&cv_playercolor4, skin->prefcolor); player->skincolor = skin->prefcolor; if (player->mo) @@ -2669,6 +2676,9 @@ void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum) if (player->mo) P_SetScale(player->mo, player->mo->scale); + + demo_extradata[playernum] |= DXD_SKIN; + return; } diff --git a/src/r_things.h b/src/r_things.h index 825ff3b98..697cde256 100644 --- a/src/r_things.h +++ b/src/r_things.h @@ -55,7 +55,7 @@ void R_DelSpriteDefs(UINT16 wadnum); #endif //SoM: 6/5/2000: Light sprites correctly! -void R_AddSprites(sector_t *sec, INT32 lightlevel, UINT8 viewnumber); +void R_AddSprites(sector_t *sec, INT32 lightlevel); void R_InitSprites(void); void R_ClearSprites(void); void R_ClipSprites(void); diff --git a/src/s_sound.c b/src/s_sound.c index 58cc0592f..545336e0f 100644 --- a/src/s_sound.c +++ b/src/s_sound.c @@ -438,7 +438,7 @@ void S_StartSoundAtVolume(const void *origin_p, sfxenum_t sfx_id, INT32 volume) listener_t listener3 = {0,0,0,0}; listener_t listener4 = {0,0,0,0}; - mobj_t *listenmobj = players[displayplayer].mo; + mobj_t *listenmobj = players[displayplayers[0]].mo; mobj_t *listenmobj2 = NULL; mobj_t *listenmobj3 = NULL; mobj_t *listenmobj4 = NULL; @@ -450,26 +450,26 @@ void S_StartSoundAtVolume(const void *origin_p, sfxenum_t sfx_id, INT32 volume) if (sfx_id == sfx_None) return; - if (players[displayplayer].awayviewtics) - listenmobj = players[displayplayer].awayviewmobj; + if (players[displayplayers[0]].awayviewtics) + listenmobj = players[displayplayers[0]].awayviewmobj; if (splitscreen) { - listenmobj2 = players[secondarydisplayplayer].mo; - if (players[secondarydisplayplayer].awayviewtics) - listenmobj2 = players[secondarydisplayplayer].awayviewmobj; + listenmobj2 = players[displayplayers[1]].mo; + if (players[displayplayers[1]].awayviewtics) + listenmobj2 = players[displayplayers[1]].awayviewmobj; if (splitscreen > 1) { - listenmobj3 = players[thirddisplayplayer].mo; - if (players[thirddisplayplayer].awayviewtics) - listenmobj3 = players[thirddisplayplayer].awayviewmobj; + listenmobj3 = players[displayplayers[2]].mo; + if (players[displayplayers[2]].awayviewtics) + listenmobj3 = players[displayplayers[2]].awayviewmobj; if (splitscreen > 2) { - listenmobj4 = players[fourthdisplayplayer].mo; - if (players[fourthdisplayplayer].awayviewtics) - listenmobj4 = players[fourthdisplayplayer].awayviewmobj; + listenmobj4 = players[displayplayers[3]].mo; + if (players[displayplayers[3]].awayviewtics) + listenmobj4 = players[displayplayers[3]].awayviewmobj; } } } @@ -482,12 +482,12 @@ void S_StartSoundAtVolume(const void *origin_p, sfxenum_t sfx_id, INT32 volume) }; #endif - if (camera.chase && !players[displayplayer].awayviewtics) + if (camera[0].chase && !players[displayplayers[0]].awayviewtics) { - listener.x = camera.x; - listener.y = camera.y; - listener.z = camera.z; - listener.angle = camera.angle; + listener.x = camera[0].x; + listener.y = camera[0].y; + listener.z = camera[0].z; + listener.angle = camera[0].angle; } else if (listenmobj) { @@ -501,12 +501,12 @@ void S_StartSoundAtVolume(const void *origin_p, sfxenum_t sfx_id, INT32 volume) if (listenmobj2) { - if (camera2.chase && !players[secondarydisplayplayer].awayviewtics) + if (camera[1].chase && !players[displayplayers[1]].awayviewtics) { - listener2.x = camera2.x; - listener2.y = camera2.y; - listener2.z = camera2.z; - listener2.angle = camera2.angle; + listener2.x = camera[1].x; + listener2.y = camera[1].y; + listener2.z = camera[1].z; + listener2.angle = camera[1].angle; } else { @@ -519,12 +519,12 @@ void S_StartSoundAtVolume(const void *origin_p, sfxenum_t sfx_id, INT32 volume) if (listenmobj3) { - if (camera3.chase && !players[thirddisplayplayer].awayviewtics) + if (camera[2].chase && !players[displayplayers[2]].awayviewtics) { - listener3.x = camera3.x; - listener3.y = camera3.y; - listener3.z = camera3.z; - listener3.angle = camera3.angle; + listener3.x = camera[2].x; + listener3.y = camera[2].y; + listener3.z = camera[2].z; + listener3.angle = camera[2].angle; } else { @@ -537,12 +537,12 @@ void S_StartSoundAtVolume(const void *origin_p, sfxenum_t sfx_id, INT32 volume) if (listenmobj4) { - if (camera4.chase && !players[fourthdisplayplayer].awayviewtics) + if (camera[3].chase && !players[displayplayers[3]].awayviewtics) { - listener4.x = camera4.x; - listener4.y = camera4.y; - listener4.z = camera4.z; - listener4.angle = camera4.angle; + listener4.x = camera[3].x; + listener4.y = camera[3].y; + listener4.z = camera[3].z; + listener4.angle = camera[3].angle; } else { @@ -899,7 +899,7 @@ void S_UpdateSounds(void) listener_t listener3; listener_t listener4; - mobj_t *listenmobj = players[displayplayer].mo; + mobj_t *listenmobj = players[displayplayers[0]].mo; mobj_t *listenmobj2 = NULL; mobj_t *listenmobj3 = NULL; mobj_t *listenmobj4 = NULL; @@ -935,36 +935,36 @@ void S_UpdateSounds(void) if (dedicated || sound_disabled) return; - if (players[displayplayer].awayviewtics) - listenmobj = players[displayplayer].awayviewmobj; + if (players[displayplayers[0]].awayviewtics) + listenmobj = players[displayplayers[0]].awayviewmobj; if (splitscreen) { - listenmobj2 = players[secondarydisplayplayer].mo; - if (players[secondarydisplayplayer].awayviewtics) - listenmobj2 = players[secondarydisplayplayer].awayviewmobj; + listenmobj2 = players[displayplayers[1]].mo; + if (players[displayplayers[1]].awayviewtics) + listenmobj2 = players[displayplayers[1]].awayviewmobj; if (splitscreen > 1) { - listenmobj3 = players[thirddisplayplayer].mo; - if (players[thirddisplayplayer].awayviewtics) - listenmobj3 = players[thirddisplayplayer].awayviewmobj; + listenmobj3 = players[displayplayers[2]].mo; + if (players[displayplayers[2]].awayviewtics) + listenmobj3 = players[displayplayers[2]].awayviewmobj; if (splitscreen > 2) { - listenmobj4 = players[fourthdisplayplayer].mo; - if (players[fourthdisplayplayer].awayviewtics) - listenmobj4 = players[fourthdisplayplayer].awayviewmobj; + listenmobj4 = players[displayplayers[3]].mo; + if (players[displayplayers[3]].awayviewtics) + listenmobj4 = players[displayplayers[3]].awayviewmobj; } } } - if (camera.chase && !players[displayplayer].awayviewtics) + if (camera[0].chase && !players[displayplayers[0]].awayviewtics) { - listener.x = camera.x; - listener.y = camera.y; - listener.z = camera.z; - listener.angle = camera.angle; + listener.x = camera[0].x; + listener.y = camera[0].y; + listener.z = camera[0].z; + listener.angle = camera[0].angle; } else if (listenmobj) { @@ -989,12 +989,12 @@ void S_UpdateSounds(void) if (listenmobj2) { - if (camera2.chase && !players[secondarydisplayplayer].awayviewtics) + if (camera[1].chase && !players[displayplayers[1]].awayviewtics) { - listener2.x = camera2.x; - listener2.y = camera2.y; - listener2.z = camera2.z; - listener2.angle = camera2.angle; + listener2.x = camera[1].x; + listener2.y = camera[1].y; + listener2.z = camera[1].z; + listener2.angle = camera[1].angle; } else { @@ -1007,12 +1007,12 @@ void S_UpdateSounds(void) if (listenmobj3) { - if (camera3.chase && !players[thirddisplayplayer].awayviewtics) + if (camera[2].chase && !players[displayplayers[2]].awayviewtics) { - listener3.x = camera3.x; - listener3.y = camera3.y; - listener3.z = camera3.z; - listener3.angle = camera3.angle; + listener3.x = camera[2].x; + listener3.y = camera[2].y; + listener3.z = camera[2].z; + listener3.angle = camera[2].angle; } else { @@ -1025,12 +1025,12 @@ void S_UpdateSounds(void) if (listenmobj4) { - if (camera4.chase && !players[fourthdisplayplayer].awayviewtics) + if (camera[3].chase && !players[displayplayers[3]].awayviewtics) { - listener4.x = camera4.x; - listener4.y = camera4.y; - listener4.z = camera4.z; - listener4.angle = camera4.angle; + listener4.x = camera[3].x; + listener4.y = camera[3].y; + listener4.z = camera[3].z; + listener4.angle = camera[3].angle; } else { @@ -1060,9 +1060,9 @@ void S_UpdateSounds(void) // check non-local sounds for distance clipping // or modify their params if (c->origin && ((c->origin != players[consoleplayer].mo) - || (splitscreen && c->origin != players[secondarydisplayplayer].mo) - || (splitscreen > 1 && c->origin != players[thirddisplayplayer].mo) - || (splitscreen > 2 && c->origin != players[fourthdisplayplayer].mo))) + || (splitscreen && c->origin != players[displayplayers[1]].mo) + || (splitscreen > 1 && c->origin != players[displayplayers[2]].mo) + || (splitscreen > 2 && c->origin != players[displayplayers[3]].mo))) { // Whomever is closer gets the sound, but only in splitscreen. if (splitscreen) @@ -1071,13 +1071,10 @@ void S_UpdateSounds(void) fixed_t recdist = -1; INT32 i, p = -1; - for (i = 0; i < 4; i++) + for (i = 0; i <= splitscreen; i++) { fixed_t thisdist = -1; - if (i > splitscreen) - break; - if (i == 0 && listenmobj) thisdist = P_AproxDistance(listener.x-soundmobj->x, listener.y-soundmobj->y); else if (i == 1 && listenmobj2) @@ -1250,33 +1247,33 @@ INT32 S_AdjustSoundParams(const mobj_t *listener, const mobj_t *source, INT32 *v if (!listener) return false; - if (listener == players[displayplayer].mo && camera.chase) + if (listener == players[displayplayers[0]].mo && camera[0].chase) { - listensource.x = camera.x; - listensource.y = camera.y; - listensource.z = camera.z; - listensource.angle = camera.angle; + listensource.x = camera[0].x; + listensource.y = camera[0].y; + listensource.z = camera[0].z; + listensource.angle = camera[0].angle; } - else if (splitscreen && listener == players[secondarydisplayplayer].mo && camera2.chase) + else if (splitscreen && listener == players[displayplayers[1]].mo && camera[1].chase) { - listensource.x = camera2.x; - listensource.y = camera2.y; - listensource.z = camera2.z; - listensource.angle = camera2.angle; + listensource.x = camera[1].x; + listensource.y = camera[1].y; + listensource.z = camera[1].z; + listensource.angle = camera[1].angle; } - else if (splitscreen > 1 && listener == players[thirddisplayplayer].mo && camera3.chase) + else if (splitscreen > 1 && listener == players[displayplayers[2]].mo && camera[2].chase) { - listensource.x = camera3.x; - listensource.y = camera3.y; - listensource.z = camera3.z; - listensource.angle = camera3.angle; + listensource.x = camera[2].x; + listensource.y = camera[2].y; + listensource.z = camera[2].z; + listensource.angle = camera[2].angle; } - else if (splitscreen > 2 && listener == players[fourthdisplayplayer].mo && camera4.chase) + else if (splitscreen > 2 && listener == players[displayplayers[3]].mo && camera[3].chase) { - listensource.x = camera4.x; - listensource.y = camera4.y; - listensource.z = camera4.z; - listensource.angle = camera4.angle; + listensource.x = camera[3].x; + listensource.y = camera[3].y; + listensource.z = camera[3].z; + listensource.angle = camera[3].angle; } else { @@ -1733,7 +1730,7 @@ void S_ShowMusicCredit(void) { musicdef_t *def = musicdefstart; - if (!cv_songcredits.value) + if (!cv_songcredits.value || demo.rewinding) return; if (!def) // No definitions @@ -1920,7 +1917,8 @@ void S_ChangeMusic(const char *mmusic, UINT16 mflags, boolean looping) #endif if (S_MusicDisabled() - || titledemo) // SRB2Kart: Demos don't interrupt title screen music + || demo.rewinding // Don't mess with music while rewinding! + || demo.title) // SRB2Kart: Demos don't interrupt title screen music return; // No Music (empty string) @@ -1955,7 +1953,8 @@ void S_ChangeMusic(const char *mmusic, UINT16 mflags, boolean looping) void S_StopMusic(void) { if (!I_SongPlaying() - || titledemo) // SRB2Kart: Demos don't interrupt title screen music + || demo.rewinding // Don't mess with music while rewinding! + || demo.title) // SRB2Kart: Demos don't interrupt title screen music return; if (I_SongPaused()) diff --git a/src/screen.c b/src/screen.c index 4de2abd05..4cb8bac5d 100644 --- a/src/screen.c +++ b/src/screen.c @@ -59,6 +59,8 @@ INT32 setmodeneeded; //video mode change needed if > 0 (the mode number to set + static CV_PossibleValue_t scr_depth_cons_t[] = {{8, "8 bits"}, {16, "16 bits"}, {24, "24 bits"}, {32, "32 bits"}, {0, NULL}}; +static CV_PossibleValue_t shittyscreen_cons_t[] = {{0, "Okay"}, {1, "Shitty"}, {2, "Extra Shitty"}, {0, NULL}}; + //added : 03-02-98: default screen mode, as loaded/saved in config #ifdef WII consvar_t cv_scr_width = {"scr_width", "640", CV_SAVE, CV_Unsigned, NULL, 0, NULL, NULL, 0, 0, NULL}; @@ -70,6 +72,8 @@ consvar_t cv_scr_height = {"scr_height", "800", CV_SAVE, CV_Unsigned, NULL, 0, N consvar_t cv_scr_depth = {"scr_depth", "16 bits", CV_SAVE, scr_depth_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; #endif consvar_t cv_renderview = {"renderview", "On", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; +consvar_t cv_vhseffect = {"vhspause", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; +consvar_t cv_shittyscreen = {"televisionsignal", "Okay", CV_NOSHOWHELP, shittyscreen_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; static void SCR_ChangeFullscreen (void); diff --git a/src/screen.h b/src/screen.h index 5b4a8e583..2e4d29b95 100644 --- a/src/screen.h +++ b/src/screen.h @@ -158,7 +158,7 @@ extern INT32 setmodeneeded; // mode number to set if needed, or 0 extern INT32 scr_bpp; extern UINT8 *scr_borderpatch; // patch used to fill the view borders -extern consvar_t cv_scr_width, cv_scr_height, cv_scr_depth, cv_renderview, cv_fullscreen; +extern consvar_t cv_scr_width, cv_scr_height, cv_scr_depth, cv_renderview, cv_fullscreen, cv_vhseffect, cv_shittyscreen; // wait for page flipping to end or not extern consvar_t cv_vidwait; diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c index f92f1f14a..c0fca64da 100644 --- a/src/sdl/i_system.c +++ b/src/sdl/i_system.c @@ -3061,7 +3061,7 @@ void I_Quit(void) //added:16-02-98: when recording a demo, should exit using 'q' key, // but sometimes we forget and use 'F10'.. so save here too. - if (demorecording) + if (demo.recording) G_CheckDemoStatus(); if (metalrecording) G_StopMetalRecording(); @@ -3179,7 +3179,7 @@ void I_Error(const char *error, ...) G_SaveGameData(false); // Tails 12-08-2002 // Shutdown. Here might be other errors. - if (demorecording) + if (demo.recording) G_CheckDemoStatus(); if (metalrecording) G_StopMetalRecording(); diff --git a/src/st_stuff.c b/src/st_stuff.c index cd4ed7e25..50bac3eef 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -372,7 +372,7 @@ static inline void ST_InitData(void) // 'link' the statusbar display to a player, which could be // another player than consoleplayer, for example, when you // change the view in a multiplayer demo with F12. - stplyr = &players[displayplayer]; + stplyr = &players[displayplayers[0]]; st_palette = -1; } @@ -442,7 +442,7 @@ static INT32 SCY(INT32 y) if (splitscreen) { y >>= 1; - if (stplyr != &players[displayplayer]) + if (stplyr != &players[displayplayers[0]]) y += vid.height / 2; } return y; @@ -458,7 +458,7 @@ static INT32 STRINGY(INT32 y) if (splitscreen) { y >>= 1; - if (stplyr != &players[displayplayer]) + if (stplyr != &players[displayplayers[0]]) y += BASEVIDHEIGHT / 2; } return y; @@ -471,7 +471,7 @@ static INT32 SPLITFLAGS(INT32 f) // Pass this V_SNAPTO(TOP|BOTTOM) and it'll trim them to account for splitscreen! -Red if (splitscreen) { - if (stplyr != &players[displayplayer]) + if (stplyr != &players[displayplayers[0]]) f &= ~V_SNAPTOTOP; else f &= ~V_SNAPTOBOTTOM; @@ -498,7 +498,7 @@ static INT32 SCR(INT32 r) if (splitscreen) { y >>= 1; - if (stplyr != &players[displayplayer]) + if (stplyr != &players[displayplayers[0]]) y += vid.height / 2; } return FixedInt(FixedDiv(y, vid.fdupy)); @@ -701,7 +701,7 @@ static inline void ST_drawRings(void) // SRB2kart - unused. /* static void ST_drawLives(void) // SRB2kart - unused. { - const INT32 v_splitflag = (splitscreen && stplyr == &players[displayplayer] ? V_SPLITSCREEN : 0); + const INT32 v_splitflag = (splitscreen && stplyr == &players[displayplayers[0]] ? V_SPLITSCREEN : 0); if (!stplyr->skincolor) return; // Just joined a server, skin isn't loaded yet! @@ -1019,7 +1019,7 @@ static void ST_drawNiGHTSHUD(void) // SRB2kart - unused. if (G_IsSpecialStage(gamemap)) { // Since special stages share score, time, rings, etc. // disable splitscreen mode for its HUD. - if (stplyr != &players[displayplayer]) + if (stplyr != &players[displayplayers[0]]) return; nosshack = splitscreen; splitscreen = 0; @@ -1124,7 +1124,7 @@ static void ST_drawNiGHTSHUD(void) // SRB2kart - unused. V_DrawScaledPatch(locx, STRINGY(locy)-3, V_HUDTRANS, drillbar); for (dfill = 0; dfill < stplyr->drillmeter/20 && dfill < 96; ++dfill) V_DrawScaledPatch(locx + 2 + dfill, STRINGY(locy + 3), V_HUDTRANS, drillfill[fillpatch]); - stplyr = &players[secondarydisplayplayer]; + stplyr = &players[displayplayers[1]]; if (stplyr->pflags & PF_DRILLING) fillpatch = (stplyr->drillmeter & 1) + 1; else @@ -1132,7 +1132,7 @@ static void ST_drawNiGHTSHUD(void) // SRB2kart - unused. V_DrawScaledPatch(locx, STRINGY(locy-3), V_SNAPTOBOTTOM|V_HUDTRANS, drillbar); for (dfill = 0; dfill < stplyr->drillmeter/20 && dfill < 96; ++dfill) V_DrawScaledPatch(locx + 2 + dfill, STRINGY(locy + 3), V_SNAPTOBOTTOM|V_HUDTRANS, drillfill[fillpatch]); - stplyr = &players[displayplayer]; + stplyr = &players[displayplayers[0]]; splitscreen = 0; } else @@ -1881,7 +1881,7 @@ static void ST_overlayDrawer(void) ST_drawTeamName(); // Special Stage HUD - if (!useNightsSS && G_IsSpecialStage(gamemap) && stplyr == &players[displayplayer]) + if (!useNightsSS && G_IsSpecialStage(gamemap) && stplyr == &players[displayplayers[0]]) ST_drawSpecialStageHUD(); // Emerald Hunt Indicators @@ -1894,22 +1894,46 @@ static void ST_overlayDrawer(void) V_DrawScaledPatch(hudinfo[HUD_GRAVBOOTSICO].x, STRINGY(hudinfo[HUD_GRAVBOOTSICO].y), V_SNAPTORIGHT, gravboots); */ - if(!P_IsLocalPlayer(stplyr)) + if (!(multiplayer && demo.playback)) { - /*char name[MAXPLAYERNAME+1]; - // shorten the name if its more than twelve characters. - strlcpy(name, player_names[stplyr-players], 13);*/ + if(!P_IsLocalPlayer(stplyr)) + { + /*char name[MAXPLAYERNAME+1]; + // shorten the name if its more than twelve characters. + strlcpy(name, player_names[stplyr-players], 13);*/ - // Show name of player being displayed - V_DrawCenteredString((BASEVIDWIDTH/2), BASEVIDHEIGHT-40, 0, M_GetText("Viewpoint:")); - V_DrawCenteredString((BASEVIDWIDTH/2), BASEVIDHEIGHT-32, V_ALLOWLOWERCASE, player_names[stplyr-players]); + // Show name of player being displayed + V_DrawCenteredString((BASEVIDWIDTH/2), BASEVIDHEIGHT-40, 0, M_GetText("Viewpoint:")); + V_DrawCenteredString((BASEVIDWIDTH/2), BASEVIDHEIGHT-32, V_ALLOWLOWERCASE, player_names[stplyr-players]); + } + } + else if (!demo.title) + { + + if (!splitscreen) + { + V_DrawCenteredString((BASEVIDWIDTH/2), BASEVIDHEIGHT-40, V_HUDTRANSHALF, M_GetText("Viewpoint:")); + V_DrawCenteredString((BASEVIDWIDTH/2), BASEVIDHEIGHT-32, V_HUDTRANSHALF|V_ALLOWLOWERCASE, player_names[stplyr-players]); + } + else if (splitscreen == 1) + { + char name[MAXPLAYERNAME+12]; + + INT32 y = (stplyr == &players[displayplayers[0]]) ? 4 : BASEVIDHEIGHT/2-12; + sprintf(name, "VIEWPOINT: %s", player_names[stplyr-players]); + V_DrawRightAlignedThinString(BASEVIDWIDTH-40, y, V_HUDTRANSHALF|V_ALLOWLOWERCASE|K_calcSplitFlags(V_SNAPTOTOP|V_SNAPTOBOTTOM|V_SNAPTORIGHT), name); + } + else if (splitscreen) + { + V_DrawCenteredThinString((vid.width/vid.dupx)/4, BASEVIDHEIGHT/2 - 12, V_HUDTRANSHALF|V_ALLOWLOWERCASE|K_calcSplitFlags(V_SNAPTOBOTTOM|V_SNAPTOLEFT), player_names[stplyr-players]); + } } // This is where we draw all the fun cheese if you have the chasecam off! - /*if ((stplyr == &players[displayplayer] && !camera.chase) - || ((splitscreen && stplyr == &players[secondarydisplayplayer]) && !camera2.chase) - || ((splitscreen > 1 && stplyr == &players[thirddisplayplayer]) && !camera3.chase) - || ((splitscreen > 2 && stplyr == &players[fourthdisplayplayer]) && !camera4.chase)) + /*if ((stplyr == &players[displayplayers[0]] && !camera[0].chase) + || ((splitscreen && stplyr == &players[displayplayers[1]]) && !camera[1].chase) + || ((splitscreen > 1 && stplyr == &players[displayplayers[2]]) && !camera[2].chase) + || ((splitscreen > 2 && stplyr == &players[displayplayers[3]]) && !camera[3].chase)) { ST_drawFirstPersonHUD(); }*/ @@ -1990,9 +2014,65 @@ static void ST_overlayDrawer(void) } } + // Replay manual-save stuff + if (demo.recording && multiplayer && demo.savebutton && demo.savebutton + 3*TICRATE < leveltime) + { + switch (demo.savemode) + { + case DSM_NOTSAVING: + V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, V_HUDTRANS|V_SNAPTOTOP|V_SNAPTORIGHT|V_ALLOWLOWERCASE|(G_BattleGametype() ? V_REDMAP : V_SKYMAP), "Look Backward: Save replay"); + break; + + case DSM_WILLAUTOSAVE: + V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, V_HUDTRANS|V_SNAPTOTOP|V_SNAPTORIGHT|V_ALLOWLOWERCASE|(G_BattleGametype() ? V_REDMAP : V_SKYMAP), "Replay will be saved. (Look Backward: Change title)"); + break; + + case DSM_WILLSAVE: + V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, V_HUDTRANS|V_SNAPTOTOP|V_SNAPTORIGHT|V_ALLOWLOWERCASE|(G_BattleGametype() ? V_REDMAP : V_SKYMAP), "Replay will be saved."); + break; + + case DSM_TITLEENTRY: + ST_DrawDemoTitleEntry(); + break; + + default: // Don't render anything + break; + } + } + ST_drawDebugInfo(); } +void ST_DrawDemoTitleEntry(void) +{ + static UINT8 skullAnimCounter = 0; + char *nametodraw; + + skullAnimCounter++; + skullAnimCounter %= 8; + + nametodraw = demo.titlename; + while (V_StringWidth(nametodraw, 0) > MAXSTRINGLENGTH*8 - 8) + nametodraw++; + +#define x (BASEVIDWIDTH/2 - 139) +#define y (BASEVIDHEIGHT/2) + M_DrawTextBox(x, y + 4, MAXSTRINGLENGTH, 1); + V_DrawString(x + 8, y + 12, V_ALLOWLOWERCASE, nametodraw); + if (skullAnimCounter < 4) + V_DrawCharacter(x + 8 + V_StringWidth(nametodraw, 0), y + 12, + '_' | 0x80, false); + + M_DrawTextBox(x + 30, y - 24, 26, 1); + V_DrawString(x + 38, y - 16, V_ALLOWLOWERCASE, "Enter the name of the replay."); + + M_DrawTextBox(x + 50, y + 20, 20, 1); + V_DrawThinString(x + 58, y + 28, V_ALLOWLOWERCASE, "Escape - Cancel"); + V_DrawRightAlignedThinString(x + 220, y + 28, V_ALLOWLOWERCASE, "Enter - Confirm"); +#undef x +#undef y +} + // MayonakaStatic: draw Midnight Channel's TV-like borders static void ST_MayonakaStatic(void) { @@ -2006,8 +2086,10 @@ static void ST_MayonakaStatic(void) void ST_Drawer(void) { + UINT8 i; + #ifdef SEENAMES - if (cv_seenames.value && cv_allowseenames.value && displayplayer == consoleplayer && seenplayer && seenplayer->mo && !mapreset) + if (cv_seenames.value && cv_allowseenames.value && displayplayers[0] == consoleplayer && seenplayer && seenplayer->mo && !mapreset) { if (cv_seenames.value == 1) V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2 + 15, V_HUDTRANSHALF, player_names[seenplayer-players]); @@ -2041,26 +2123,12 @@ void ST_Drawer(void) if (st_overlay) { // No deadview! - stplyr = &players[displayplayer]; - ST_overlayDrawer(); - - if (splitscreen) + for (i = 0; i <= splitscreen; i++) { - stplyr = &players[secondarydisplayplayer]; + stplyr = &players[displayplayers[i]]; ST_overlayDrawer(); - - if (splitscreen > 1) - { - stplyr = &players[thirddisplayplayer]; - ST_overlayDrawer(); - - if (splitscreen > 2) - { - stplyr = &players[fourthdisplayplayer]; - ST_overlayDrawer(); - } - } } + // draw Midnight Channel's overlay ontop if (mapheaderinfo[gamemap-1]->typeoflevel & TOL_TV) // Very specific Midnight Channel stuff. ST_MayonakaStatic(); diff --git a/src/st_stuff.h b/src/st_stuff.h index f96aee938..16f7b8811 100644 --- a/src/st_stuff.h +++ b/src/st_stuff.h @@ -26,6 +26,9 @@ // Called by main loop. void ST_Ticker(void); +// Called when naming a replay. +void ST_DrawDemoTitleEntry(void); + // Called by main loop. void ST_Drawer(void); diff --git a/src/v_video.c b/src/v_video.c index fbcaa33b6..3966924dc 100644 --- a/src/v_video.c +++ b/src/v_video.c @@ -1214,6 +1214,66 @@ void V_DrawPatchFill(patch_t *pat) } } +void V_DrawVhsEffect(boolean rewind) +{ + static fixed_t upbary = 100, downbary = 150; + + UINT8 *buf = screens[0], *tmp = screens[4]; + UINT16 y; + UINT32 x, pos = 0; + + UINT8 *normalmapstart = ((UINT8 *)transtables + (8<>1; + + if (rewind) + V_DrawVhsEffect(false); // experimentation + + upbary -= vid.dupy * (rewind ? 3 : 1.8f); + downbary += vid.dupy * (rewind ? 2 : 1); + if (upbary < -barsize) upbary = vid.height; + if (downbary > vid.height) downbary = -barsize; + + for (y = 0; y < vid.height; y+=2) + { + thismapstart = normalmapstart; + offs = 0; + + if (y >= upbary && y < upbary+barsize) + { + thismapstart -= (2<= downbary && y < downbary+barsize) + { + thismapstart -= (2<= vid.height-2 && offs > 0) offs = 0; + + for (x = pos+vid.rowbytes*2; pos < x; pos++) + { + tmp[pos] = thismapstart[buf[pos+offs]]; +#ifdef HQ_VHS + tmp[pos] = tmapstart[buf[pos]<<8 | tmp[pos]]; +#endif + } + } + + memcpy(buf, tmp, vid.rowbytes*vid.height); +} + // // Fade all the screen buffer, so that the menu is more readable, // especially now that we use the small hufont in the menus... @@ -1234,9 +1294,12 @@ void V_DrawFadeScreen(UINT16 color, UINT8 strength) #endif { - const UINT8 *fadetable = ((color & 0xFF00) // Color is not palette index? - ? ((UINT8 *)colormaps + strength*256) // Do COLORMAP fade. - : ((UINT8 *)transtables + ((9-strength)< 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)< sorttic) + if (sorttic != -1 && intertic > sorttic && !demo.playback) { INT32 count = (intertic - sorttic); @@ -551,11 +560,37 @@ void Y_IntermissionDrawer(void) dotimer: if (timer) { + char *string; INT32 tickdown = (timer+1)/TICRATE; + + if (multiplayer && demo.playback) + string = va("Replay ends in %d", tickdown); + else + string = va("%s starts in %d", cv_advancemap.string, tickdown); + V_DrawCenteredString(BASEVIDWIDTH/2, 188, hilicol, - va("%s starts in %d", cv_advancemap.string, tickdown)); + string); } + if ((demo.recording || demo.savemode == DSM_SAVED) && !demo.playback) + switch (demo.savemode) + { + case DSM_NOTSAVING: + V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, V_SNAPTOTOP|V_SNAPTORIGHT|V_ALLOWLOWERCASE|hilicol, "Look Backward: Save replay"); + break; + + case DSM_SAVED: + V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, V_SNAPTOTOP|V_SNAPTORIGHT|V_ALLOWLOWERCASE|hilicol, "Replay saved!"); + break; + + case DSM_TITLEENTRY: + ST_DrawDemoTitleEntry(); + break; + + default: // Don't render any text here + 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!")); @@ -571,6 +606,15 @@ void Y_Ticker(void) if (intertype == int_none) return; + if (demo.recording) + { + if (demo.savemode == DSM_NOTSAVING && InputDown(gc_lookback, 1)) + demo.savemode = DSM_TITLEENTRY; + + if (demo.savemode == DSM_WILLSAVE || demo.savemode == DSM_WILLAUTOSAVE) + G_SaveDemo(); + } + // Check for pause or menu up in single player if (paused || P_AutoPause()) return; @@ -618,7 +662,7 @@ void Y_Ticker(void) { if (sorttic == -1) sorttic = intertic + max((cv_inttime.value/2)-2, 2)*TICRATE; // 8 second pause after match results - else + else if (!(multiplayer && demo.playback)) // Don't advance to rankings in replays { if (!data.match.rankingsmode && (intertic >= sorttic + 8)) Y_CalculateMatchData(1, Y_CompareRank); @@ -767,6 +811,8 @@ void Y_StartIntermission(void) { if (cv_inttime.value == 0 && gametype == GT_COOP) timer = 0; + else if (demo.playback) // Override inttime (which is pulled from the replay anyway + timer = 10*TICRATE; else { timer = cv_inttime.value*TICRATE; @@ -801,7 +847,7 @@ void Y_StartIntermission(void) } case int_race: // (time-only race) { - if (!majormods && !multiplayer && !demoplayback) // remove this once we have a proper time attack screen + if (!majormods && !multiplayer && !demo.playback) // remove this once we have a proper time attack screen { // Update visitation flags mapvisited[gamemap-1] |= MV_BEATEN; @@ -1010,19 +1056,19 @@ void Y_VoteDrawer(void) { case 1: thiscurs = cursor2; - p = secondarydisplayplayer; + p = displayplayers[1]; break; case 2: thiscurs = cursor3; - p = thirddisplayplayer; + p = displayplayers[2]; break; case 3: thiscurs = cursor4; - p = fourthdisplayplayer; + p = displayplayers[3]; break; default: thiscurs = cursor1; - p = displayplayer; + p = displayplayers[0]; break; } @@ -1177,10 +1223,7 @@ static void Y_VoteStops(SINT8 pick, SINT8 level) S_StartSound(NULL, sfx_noooo2); // gasp else if (mapheaderinfo[nextmap] && (mapheaderinfo[nextmap]->menuflags & LF2_HIDEINMENU)) S_StartSound(NULL, sfx_noooo1); // this is bad - else if (netgame && (pick == consoleplayer - || pick == secondarydisplayplayer - || pick == thirddisplayplayer - || pick == fourthdisplayplayer)) + else if (netgame && P_IsLocalPlayer(&players[pick])) S_StartSound(NULL, sfx_yeeeah); // yeeeah! else S_StartSound(NULL, sfx_kc48); // just a cool sound @@ -1313,13 +1356,13 @@ void Y_VoteTicker(void) switch (i) { case 1: - p = secondarydisplayplayer; + p = displayplayers[1]; break; case 2: - p = thirddisplayplayer; + p = displayplayers[2]; break; case 3: - p = fourthdisplayplayer; + p = displayplayers[3]; break; default: p = consoleplayer;