diff --git a/src/acs/call-funcs.cpp b/src/acs/call-funcs.cpp index 3283bb865..eeb093fcf 100644 --- a/src/acs/call-funcs.cpp +++ b/src/acs/call-funcs.cpp @@ -1271,7 +1271,7 @@ bool CallFunc_EndPrintBold(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM bool CallFunc_PlayerTeam(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { auto info = &static_cast(thread)->info; - UINT8 teamID = 0; + UINT8 teamID = TEAM_UNASSIGNED; (void)argV; (void)argC; @@ -1280,7 +1280,7 @@ bool CallFunc_PlayerTeam(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM:: && (info->mo != NULL && P_MobjWasRemoved(info->mo) == false) && (info->mo->player != NULL)) { - teamID = info->mo->player->ctfteam; + teamID = info->mo->player->team; } thread->dataStk.push(teamID); diff --git a/src/cvars.cpp b/src/cvars.cpp index 1a8f7904e..de8859f4a 100644 --- a/src/cvars.cpp +++ b/src/cvars.cpp @@ -597,9 +597,6 @@ consvar_t cv_sleep = Server("cpusleep", "1").min_max(0, 1000/TICRATE); // There's a separate section for netvars that don't save... // -void AutoBalance_OnChange(void); -consvar_t cv_autobalance = NetVar("autobalance", "Off").on_off().onchange(AutoBalance_OnChange); - consvar_t cv_blamecfail = NetVar("blamecfail", "Off").on_off(); // Speed of file downloading (in packets per tic) @@ -628,10 +625,6 @@ consvar_t cv_noticedownload = NetVar("noticedownload", "Off").on_off(); consvar_t cv_pingtimeout = NetVar("maxdelaytimeout", "10").min_max(8, 120); consvar_t cv_resynchattempts = NetVar("resynchattempts", "2").min_max(1, 20, {{0, "No"}}); -void TeamScramble_OnChange(void); -consvar_t cv_scrambleonchange = NetVar("scrambleonchange", "Off").values({{0, "Off"}, {1, "Random"}, {2, "Points"}}); -consvar_t cv_teamscramble = NetVar("teamscramble", "Off").values({{0, "Off"}, {1, "Random"}, {2, "Points"}}).onchange_noinit(TeamScramble_OnChange); - consvar_t cv_showjoinaddress = NetVar("showjoinaddress", "Off").on_off(); consvar_t cv_zvote_delay = NetVar("zvote_delay", "20").values(CV_Unsigned); consvar_t cv_zvote_length = NetVar("zvote_length", "20").values(CV_Unsigned); @@ -736,6 +729,8 @@ consvar_t cv_kartfrantic = UnsavedNetVar("franticitems", "Off").on_off().onchang void KartSpeed_OnChange(void); consvar_t cv_kartspeed = UnsavedNetVar("gamespeed", "Auto Gear").values(kartspeed_cons_t).onchange_noinit(KartSpeed_OnChange); +consvar_t cv_teamplay = UnsavedNetVar("teamplay", "Off").on_off(); + consvar_t cv_kartusepwrlv = UnsavedNetVar("usepwrlv", "Yes").yes_no(); void LiveStudioAudience_OnChange(void); @@ -997,9 +992,6 @@ consvar_t cv_dummyspectate = MenuDummy("dummyspectate", "Spectator").values({{0, extern CV_PossibleValue_t dummystaff_cons_t[]; consvar_t cv_dummystaff = MenuDummy("dummystaff", "0").values(dummystaff_cons_t); -consvar_t cv_dummyteam = MenuDummy("dummyteam", "Spectator").values({{0, "Spectator"}, {1, "Red"}, {2, "Blue"}}); - - // // lastprofile // diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 870b2cc1a..30e4fe110 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -1145,28 +1145,32 @@ static void SV_SendPlayerInfo(INT32 node) //No, don't do that, you fuckface. memset(netbuffer->u.playerinfo[i].address, 0, 4); - if (G_GametypeHasTeams()) + if (players[i].spectator) { - if (!players[i].ctfteam) - netbuffer->u.playerinfo[i].team = 255; - else - netbuffer->u.playerinfo[i].team = (UINT8)players[i].ctfteam; + netbuffer->u.playerinfo[i].team = 255; } else { - if (players[i].spectator) - netbuffer->u.playerinfo[i].team = 255; + if (G_GametypeHasTeams()) + { + if (players[i].team == TEAM_UNASSIGNED) + { + netbuffer->u.playerinfo[i].team = 255; + } + else + { + netbuffer->u.playerinfo[i].team = players[i].team; + } + } else + { netbuffer->u.playerinfo[i].team = 0; + } } netbuffer->u.playerinfo[i].score = LONG(players[i].score); netbuffer->u.playerinfo[i].timeinserver = SHORT((UINT16)(players[i].jointime / TICRATE)); - netbuffer->u.playerinfo[i].skin = (UINT8)(players[i].skin -#ifdef DEVELOP // it's safe to do this only because PLAYERINFO isn't read by the game itself - % 3 -#endif - ); + netbuffer->u.playerinfo[i].skin = (UINT8)(players[i].skin); // Extra data netbuffer->u.playerinfo[i].data = 0; //players[i].skincolor; diff --git a/src/d_clisrv.h b/src/d_clisrv.h index 3b6889fcb..191dc0ee6 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -357,7 +357,7 @@ struct plrconfig UINT16 color; UINT32 pflags; UINT32 score; - UINT8 ctfteam; + UINT8 team; } ATTRPACK; struct filesneededconfig_pak diff --git a/src/d_netcmd.c b/src/d_netcmd.c index e926cf520..4e24613c0 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -102,7 +102,8 @@ static void Got_Addfilecmd(const UINT8 **cp, INT32 playernum); static void Got_Pause(const UINT8 **cp, INT32 playernum); static void Got_RandomSeed(const UINT8 **cp, INT32 playernum); static void Got_RunSOCcmd(const UINT8 **cp, INT32 playernum); -static void Got_Teamchange(const UINT8 **cp, INT32 playernum); +static void Got_Spectate(const UINT8 **cp, INT32 playernum); +static void Got_TeamChange(const UINT8 **cp, INT32 playernum); static void Got_Clearscores(const UINT8 **cp, INT32 playernum); static void Got_DiscordInfo(const UINT8 **cp, INT32 playernum); static void Got_ScheduleTaskcmd(const UINT8 **cp, INT32 playernum); @@ -155,11 +156,6 @@ static void Command_ExitLevel_f(void); static void Command_Showmap_f(void); static void Command_Mapmd5_f(void); -static void Command_Teamchange_f(void); -static void Command_Teamchange2_f(void); -static void Command_Teamchange3_f(void); -static void Command_Teamchange4_f(void); - static void Command_ServerTeamChange_f(void); static void Command_Clearscores_f(void); @@ -293,7 +289,7 @@ const char *netxcmdnames[MAXNETXCMD - 1] = "ADDFILE", // XD_ADDFILE "PAUSE", // XD_PAUSE "ADDPLAYER", // XD_ADDPLAYER - "TEAMCHANGE", // XD_TEAMCHANGE + "SPECTATE", // XD_SPECTATE "CLEARSCORES", // XD_CLEARSCORES "VERIFIED", // XD_VERIFIED "RANDOMSEED", // XD_RANDOMSEED @@ -325,6 +321,7 @@ const char *netxcmdnames[MAXNETXCMD - 1] = "MAPQUEUE", // XD_MAPQUEUE "CALLZVOTE", // XD_CALLZVOTE "SETZVOTE", // XD_SETZVOTE + "TEAMCHANGE", // XD_TEAMCHANGE }; // ========================================================================= @@ -388,7 +385,8 @@ void D_RegisterServerCommands(void) COM_AddCommand("motd", Command_MotD_f); RegisterNetXCmd(XD_SETMOTD, Got_MotD_f); // For remote admin - RegisterNetXCmd(XD_TEAMCHANGE, Got_Teamchange); + RegisterNetXCmd(XD_SPECTATE, Got_Spectate); + RegisterNetXCmd(XD_TEAMCHANGE, Got_TeamChange); COM_AddCommand("serverchangeteam", Command_ServerTeamChange_f); RegisterNetXCmd(XD_CLEARSCORES, Got_Clearscores); @@ -504,11 +502,6 @@ void D_RegisterClientCommands(void) if (dedicated) return; - COM_AddCommand("changeteam", Command_Teamchange_f); - COM_AddCommand("changeteam2", Command_Teamchange2_f); - COM_AddCommand("changeteam3", Command_Teamchange3_f); - COM_AddCommand("changeteam4", Command_Teamchange4_f); - COM_AddCommand("invite", Command_Invite_f); COM_AddCommand("cancelinvite", Command_CancelInvite_f); COM_AddCommand("acceptinvite", Command_AcceptInvite_f); @@ -996,11 +989,6 @@ static void SendNameAndColor(const UINT8 n) cv_follower[n].value = -1; } - if (sendColor == SKINCOLOR_NONE) - { - sendColor = skins[cv_skin[n].value].prefcolor; - } - if (sendFollowerColor == SKINCOLOR_NONE) { if (cv_follower[n].value >= 0) @@ -1015,11 +1003,11 @@ static void SendNameAndColor(const UINT8 n) // Don't send if everything was identical. if (!strcmp(cv_playername[n].string, player_names[playernum]) - && sendColor == player->skincolor - && !stricmp(cv_skin[n].string, skins[player->skin].name) + && sendColor == player->prefcolor + && !stricmp(cv_skin[n].string, skins[player->prefskin].name) && !stricmp(cv_follower[n].string, - (player->followerskin < 0 ? "None" : followers[player->followerskin].name)) - && sendFollowerColor == player->followercolor) + (player->preffollower < 0 ? "None" : followers[player->preffollower].name)) + && sendFollowerColor == player->preffollowercolor) { return; } @@ -1123,7 +1111,6 @@ static void Got_NameAndColor(const UINT8 **cp, INT32 playernum) UINT16 color, followercolor; UINT8 skin; INT16 follower; - SINT8 localplayer = -1; UINT8 i; #ifdef PARANOIA @@ -1145,8 +1132,6 @@ static void Got_NameAndColor(const UINT8 **cp, INT32 playernum) I_Error("snacpending[%d] negative!", i); } #endif - - localplayer = i; break; } } @@ -1164,95 +1149,26 @@ static void Got_NameAndColor(const UINT8 **cp, INT32 playernum) SetPlayerName(playernum, name); } - // set color - p->skincolor = color % numskincolors; - if (p->mo) - p->mo->color = (UINT16)p->skincolor; - demo_extradata[playernum] |= DXD_COLOR; - - // normal player colors - if (server && !P_IsMachineLocalPlayer(p)) + // queue the rest for next round + p->prefcolor = color % numskincolors; + if (K_ColorUsable(p->prefcolor, false, false) == false) { - boolean kick = false; - - // don't allow inaccessible colors - if (K_ColorUsable(p->skincolor, false, false) == false) - { - kick = true; - } - - if (kick) - { - CONS_Alert(CONS_WARNING, M_GetText("Illegal color change received from %s, color: %d)\n"), player_names[playernum], p->skincolor); - SendKick(playernum, KICK_MSG_CON_FAIL); - return; - } + p->prefcolor = SKINCOLOR_NONE; } - // set skin - if (cv_forceskin.value >= 0 && K_CanChangeRules(true)) // Server wants everyone to use the same player + p->prefskin = skin; + p->preffollowercolor = followercolor; + p->preffollower = follower; + + if ( + (p->jointime <= 1) // Just entered + || (cv_restrictskinchange.value == 0 // Not restricted + && !Y_IntermissionPlayerLock()) // Not start of intermission + ) { - const INT32 forcedskin = cv_forceskin.value; - SetPlayerSkinByNum(playernum, forcedskin); - - if (localplayer != -1) - CV_StealthSet(&cv_skin[localplayer], skins[forcedskin].name); + // update preferences immediately + G_UpdatePlayerPreferences(p); } - else - { - UINT8 oldskin = players[playernum].skin; - - SetPlayerSkinByNum(playernum, skin); - - // The following is a miniature subset of Got_Teamchange. - if ((gamestate == GS_LEVEL) // In a level? - && (players[playernum].jointime > 1) // permit on join - && (leveltime > introtime) // permit during intro turnaround - && (players[playernum].skin != oldskin)) // a skin change actually happened? - { - players[playernum].roundconditions.switched_skin = true; - - if ( - cv_restrictskinchange.value // Skin changes are restricted? - && G_GametypeHasSpectators() // not a spectator... - && players[playernum].spectator == false // ...but could be? - ) - { - for (i = 0; i < MAXPLAYERS; ++i) - { - if (i == playernum) - continue; - if (!playeringame[i]) - continue; - if (players[i].spectator) - continue; - break; - } - - if (i != MAXPLAYERS // Someone on your server who isn't you? - && LUA_HookTeamSwitch(&players[playernum], 0, false, false, false)) // fiiiine, lua can except it - { - P_DamageMobj(players[playernum].mo, NULL, NULL, 1, DMG_SPECTATOR); - - if (players[i].spectator) - { - HU_AddChatText(va("\x82*%s became a spectator.", player_names[playernum]), false); - - FinalisePlaystateChange(playernum); - } - } - } - } - } - - // set follower colour: - // Don't bother doing garbage and kicking if we receive None, - // this is both silly and a waste of time, - // this will be handled properly in K_HandleFollower. - p->followercolor = followercolor; - - // set follower - K_SetFollowerByNum(playernum, follower); } enum { @@ -3477,127 +3393,13 @@ static void Got_Clearscores(const UINT8 **cp, INT32 playernum) CONS_Printf(M_GetText("Scores have been reset by the server.\n")); } -// Team changing functions -static void HandleTeamChangeCommand(UINT8 localplayer) -{ - const char *commandname = NULL; - changeteam_union NetPacket; - boolean error = false; - UINT16 usvalue; - NetPacket.value.l = NetPacket.value.b = 0; - - switch (localplayer) - { - case 0: - commandname = "changeteam"; - break; - default: - commandname = va("changeteam%d", localplayer+1); - break; - } - - // 0 1 - // changeteam - - if (COM_Argc() <= 1) - { - if (G_GametypeHasTeams()) - CONS_Printf(M_GetText("%s : switch to a new team (%s)\n"), commandname, "red, blue or spectator"); - else if (G_GametypeHasSpectators()) - CONS_Printf(M_GetText("%s : switch to a new team (%s)\n"), commandname, "spectator or playing"); - else - CONS_Alert(CONS_NOTICE, M_GetText("This command cannot be used in this gametype.\n")); - return; - } - - if (G_GametypeHasTeams()) - { - if (!strcasecmp(COM_Argv(1), "red") || !strcasecmp(COM_Argv(1), "1")) - NetPacket.packet.newteam = 1; - else if (!strcasecmp(COM_Argv(1), "blue") || !strcasecmp(COM_Argv(1), "2")) - NetPacket.packet.newteam = 2; - else if (!strcasecmp(COM_Argv(1), "spectator") || !strcasecmp(COM_Argv(1), "0")) - NetPacket.packet.newteam = 0; - else - error = true; - } - else if (G_GametypeHasSpectators()) - { - if (!strcasecmp(COM_Argv(1), "spectator") || !strcasecmp(COM_Argv(1), "0")) - NetPacket.packet.newteam = 0; - else if (!strcasecmp(COM_Argv(1), "playing") || !strcasecmp(COM_Argv(1), "1")) - NetPacket.packet.newteam = 3; - else - error = true; - } - else - { - CONS_Alert(CONS_NOTICE, M_GetText("This command cannot be used in this gametype.\n")); - return; - } - - if (error) - { - if (G_GametypeHasTeams()) - CONS_Printf(M_GetText("%s : switch to a new team (%s)\n"), commandname, "red, blue or spectator"); - else if (G_GametypeHasSpectators()) - CONS_Printf(M_GetText("%s : switch to a new team (%s)\n"), commandname, "spectator or playing"); - return; - } - - if (players[g_localplayers[localplayer]].spectator) - error = !(NetPacket.packet.newteam || (players[g_localplayers[localplayer]].pflags & PF_WANTSTOJOIN)); // :lancer: - else if (G_GametypeHasTeams()) - error = (NetPacket.packet.newteam == players[g_localplayers[localplayer]].ctfteam); - else if (G_GametypeHasSpectators() && !players[g_localplayers[localplayer]].spectator) - error = (NetPacket.packet.newteam == 3); -#ifdef PARANOIA - else - I_Error("Invalid gametype after initial checks!"); -#endif - - if (error) - { - CONS_Alert(CONS_NOTICE, M_GetText("You're already on that team!\n")); - return; - } - - if (!cv_allowteamchange.value && NetPacket.packet.newteam) // allow swapping to spectator even in locked teams. - { - CONS_Alert(CONS_NOTICE, M_GetText("The server is not allowing team changes at the moment.\n")); - return; - } - - usvalue = SHORT(NetPacket.value.l|NetPacket.value.b); - SendNetXCmdForPlayer(localplayer, XD_TEAMCHANGE, &usvalue, sizeof(usvalue)); -} - -static void Command_Teamchange_f(void) -{ - HandleTeamChangeCommand(0); -} - -static void Command_Teamchange2_f(void) -{ - HandleTeamChangeCommand(1); -} - -static void Command_Teamchange3_f(void) -{ - HandleTeamChangeCommand(2); -} - -static void Command_Teamchange4_f(void) -{ - HandleTeamChangeCommand(3); -} - static void Command_ServerTeamChange_f(void) { - changeteam_union NetPacket; - boolean error = false; - UINT16 usvalue; - NetPacket.value.l = NetPacket.value.b = 0; + UINT8 buf[2]; + UINT8 *p = buf; + + UINT8 new_team = TEAM_UNASSIGNED; + UINT8 player_num = consoleplayer; if (!(server || (IsPlayerAdmin(consoleplayer)))) { @@ -3605,96 +3407,63 @@ static void Command_ServerTeamChange_f(void) return; } + if (G_GametypeHasTeams() == false) + { + CONS_Alert(CONS_NOTICE, M_GetText("This command cannot be used currently.\n")); + return; + } + // 0 1 2 // serverchangeteam if (COM_Argc() < 3) { - if (G_GametypeHasTeams()) - CONS_Printf(M_GetText("serverchangeteam : switch player to a new team (%s)\n"), "red, blue or spectator"); - else if (G_GametypeHasSpectators()) - CONS_Printf(M_GetText("serverchangeteam : switch player to a new team (%s)\n"), "spectator or playing"); - else - CONS_Alert(CONS_NOTICE, M_GetText("This command cannot be used in this gametype.\n")); + CONS_Printf(M_GetText("serverchangeteam : switch player to a new team (%s)\n"), "orange, blue, or auto"); return; } - if (G_GametypeHasTeams()) + if (!strcasecmp(COM_Argv(2), "orange") || !strcasecmp(COM_Argv(2), "1")) { - if (!strcasecmp(COM_Argv(2), "red") || !strcasecmp(COM_Argv(2), "1")) - NetPacket.packet.newteam = 1; - else if (!strcasecmp(COM_Argv(2), "blue") || !strcasecmp(COM_Argv(2), "2")) - NetPacket.packet.newteam = 2; - else if (!strcasecmp(COM_Argv(2), "spectator") || !strcasecmp(COM_Argv(2), "0")) - NetPacket.packet.newteam = 0; - else - error = true; + new_team = TEAM_ORANGE; } - else if (G_GametypeHasSpectators()) + else if (!strcasecmp(COM_Argv(2), "blue") || !strcasecmp(COM_Argv(2), "2")) { - if (!strcasecmp(COM_Argv(2), "spectator") || !strcasecmp(COM_Argv(2), "0")) - NetPacket.packet.newteam = 0; - else if (!strcasecmp(COM_Argv(2), "playing") || !strcasecmp(COM_Argv(2), "1")) - NetPacket.packet.newteam = 3; - else - error = true; + new_team = TEAM_BLUE; + } + else if (!strcasecmp(COM_Argv(2), "auto") || !strcasecmp(COM_Argv(2), "0")) + { + new_team = TEAM_UNASSIGNED; } else { - CONS_Alert(CONS_NOTICE, M_GetText("This command cannot be used in this gametype.\n")); + CONS_Printf(M_GetText("serverchangeteam : switch player to a new team (%s)\n"), "orange, blue, or auto"); return; } - if (error) + player_num = atoi(COM_Argv(1)); + + if (playeringame[player_num] == false) { - if (G_GametypeHasTeams()) - CONS_Printf(M_GetText("serverchangeteam : switch player to a new team (%s)\n"), "red, blue or spectator"); - else if (G_GametypeHasSpectators()) - CONS_Printf(M_GetText("serverchangeteam : switch player to a new team (%s)\n"), "spectator or playing"); + CONS_Alert(CONS_NOTICE, M_GetText("There is no player %d!\n"), player_num); return; } - NetPacket.packet.playernum = atoi(COM_Argv(1)); - - if (!playeringame[NetPacket.packet.playernum]) - { - CONS_Alert(CONS_NOTICE, M_GetText("There is no player %d!\n"), NetPacket.packet.playernum); - return; - } - - if (G_GametypeHasTeams()) - { - if (NetPacket.packet.newteam == players[NetPacket.packet.playernum].ctfteam || - (players[NetPacket.packet.playernum].spectator && !NetPacket.packet.newteam)) - error = true; - } - else if (G_GametypeHasSpectators()) - { - if ((players[NetPacket.packet.playernum].spectator && !NetPacket.packet.newteam) || - (!players[NetPacket.packet.playernum].spectator && NetPacket.packet.newteam == 3)) - error = true; - } -#ifdef PARANOIA - else - I_Error("Invalid gametype after initial checks!"); -#endif - - if (error) + if (new_team == players[player_num].team) { CONS_Alert(CONS_NOTICE, M_GetText("That player is already on that team!\n")); return; } - NetPacket.packet.verification = true; // This signals that it's a server change + WRITEUINT8(p, new_team); + WRITEUINT8(p, player_num); - usvalue = SHORT(NetPacket.value.l|NetPacket.value.b); - SendNetXCmd(XD_TEAMCHANGE, &usvalue, sizeof(usvalue)); + SendNetXCmd(XD_TEAMCHANGE, &buf, p - buf); } void P_SetPlayerSpectator(INT32 playernum) { //Make sure you're in the right gametype. - if (!G_GametypeHasTeams() && !G_GametypeHasSpectators()) + if (!G_GametypeHasSpectators()) return; // Don't duplicate efforts. @@ -3703,148 +3472,105 @@ void P_SetPlayerSpectator(INT32 playernum) players[playernum].spectator = true; players[playernum].pflags &= ~PF_WANTSTOJOIN; + G_AssignTeam(&players[playernum], TEAM_UNASSIGNED); players[playernum].playerstate = PST_REBORN; } -//todo: This and the other teamchange functions are getting too long and messy. Needs cleaning. -static void Got_Teamchange(const UINT8 **cp, INT32 playernum) +static void Got_Spectate(const UINT8 **cp, INT32 playernum) { - changeteam_union NetPacket; - boolean error = false, wasspectator = false; - NetPacket.value.l = NetPacket.value.b = READINT16(*cp); + UINT8 edit_player = READUINT8(*cp); + UINT8 desired_state = READUINT8(*cp); - if (!G_GametypeHasTeams() && !G_GametypeHasSpectators()) //Make sure you're in the right gametype. + if (playeringame[edit_player] == false) { - // this should never happen unless the client is hacked/buggy - CONS_Alert(CONS_WARNING, M_GetText("Illegal team change received from player %s\n"), player_names[playernum]); + return; + } + + if (playernum != playerconsole[edit_player] + && playernum != serverplayer + && IsPlayerAdmin(playernum) == false) + { + CONS_Alert(CONS_WARNING, M_GetText("Illegal spectate command received from player %s\n"), player_names[playernum]); if (server) + { SendKick(playernum, KICK_MSG_CON_FAIL); - } - - if (NetPacket.packet.verification) // Special marker that the server sent the request - { - if (playernum != serverplayer && (!IsPlayerAdmin(playernum))) - { - CONS_Alert(CONS_WARNING, M_GetText("Illegal team change received from player %s\n"), player_names[playernum]); - if (server) - SendKick(playernum, KICK_MSG_CON_FAIL); - return; - } - playernum = NetPacket.packet.playernum; - } - - // Prevent multiple changes in one go. - if (players[playernum].spectator && !(players[playernum].pflags & PF_WANTSTOJOIN) && !NetPacket.packet.newteam) - return; - else if (G_GametypeHasTeams()) - { - if (NetPacket.packet.newteam && (NetPacket.packet.newteam == (unsigned)players[playernum].ctfteam)) - return; - } - else if (G_GametypeHasSpectators()) - { - if (!players[playernum].spectator && NetPacket.packet.newteam == 3) - return; - } - else - { - if (playernum != serverplayer && (!IsPlayerAdmin(playernum))) - { - CONS_Alert(CONS_WARNING, M_GetText("Illegal team change received from player %s\n"), player_names[playernum]); - if (server) - SendKick(playernum, KICK_MSG_CON_FAIL); } return; } - // Don't switch team, just go away, please, go awaayyyy, aaauuauugghhhghgh - if (!LUA_HookTeamSwitch(&players[playernum], NetPacket.packet.newteam, players[playernum].spectator, NetPacket.packet.autobalance, NetPacket.packet.scrambled)) - return; - - //Make sure that the right team number is sent. Keep in mind that normal clients cannot change to certain teams in certain gametypes. -#ifdef PARANOIA - if (!G_GametypeHasTeams() && !G_GametypeHasSpectators()) - I_Error("Invalid gametype after initial checks!"); -#endif - - if (!cv_allowteamchange.value) + if (G_GametypeHasSpectators() == false) { - if (!NetPacket.packet.verification && NetPacket.packet.newteam) - error = true; //Only admin can change status, unless changing to spectator. - } - - if (server && ((NetPacket.packet.newteam < 0 || NetPacket.packet.newteam > 3) || error)) - { - CONS_Alert(CONS_WARNING, M_GetText("Illegal team change received from player %s\n"), player_names[playernum]); - SendKick(playernum, KICK_MSG_CON_FAIL); return; } - //Safety first! - // (not respawning spectators here...) - wasspectator = (players[playernum].spectator == true); + player_t *const player = &players[edit_player]; - if (!wasspectator) + // Safety first! + const boolean was_spectator = (player->spectator == true); + if (was_spectator == false) { - if (gamestate == GS_LEVEL && players[playernum].mo) + if (gamestate == GS_LEVEL && player->mo != NULL) { // The following will call P_SetPlayerSpectator if successful - P_DamageMobj(players[playernum].mo, NULL, NULL, 1, DMG_SPECTATOR); + P_DamageMobj(player->mo, NULL, NULL, 1, DMG_SPECTATOR); } //...but because the above could return early under some contexts, we try again here - P_SetPlayerSpectator(playernum); + P_SetPlayerSpectator(edit_player); + HU_AddChatText(va("\x82*%s became a spectator.", player_names[edit_player]), false); } - //Now that we've done our error checking and killed the player - //if necessary, put the player on the correct team/status. - - // This serves us in both teamchange contexts. - if (NetPacket.packet.newteam != 0) + if (desired_state != 0) { - players[playernum].pflags |= PF_WANTSTOJOIN; + player->pflags |= PF_WANTSTOJOIN; } else { - players[playernum].pflags &= ~PF_WANTSTOJOIN; + player->pflags &= ~PF_WANTSTOJOIN; } - if (G_GametypeHasTeams()) + if (gamestate != GS_LEVEL || was_spectator == true) { - // This one is, of course, specific. - players[playernum].ctfteam = NetPacket.packet.newteam; - } - - if (NetPacket.packet.autobalance) - { - if (NetPacket.packet.newteam == 1) - CONS_Printf(M_GetText("%s was autobalanced to the %c%s%c.\n"), player_names[playernum], '\x85', M_GetText("Red Team"), '\x80'); - else if (NetPacket.packet.newteam == 2) - CONS_Printf(M_GetText("%s was autobalanced to the %c%s%c.\n"), player_names[playernum], '\x84', M_GetText("Blue Team"), '\x80'); - } - else if (NetPacket.packet.scrambled) - { - if (NetPacket.packet.newteam == 1) - CONS_Printf(M_GetText("%s was scrambled to the %c%s%c.\n"), player_names[playernum], '\x85', M_GetText("Red Team"), '\x80'); - else if (NetPacket.packet.newteam == 2) - CONS_Printf(M_GetText("%s was scrambled to the %c%s%c.\n"), player_names[playernum], '\x84', M_GetText("Blue Team"), '\x80'); - } - else if (NetPacket.packet.newteam == 1) - { - CONS_Printf(M_GetText("%s switched to the %c%s%c.\n"), player_names[playernum], '\x85', M_GetText("Red Team"), '\x80'); - } - else if (NetPacket.packet.newteam == 2) - { - CONS_Printf(M_GetText("%s switched to the %c%s%c.\n"), player_names[playernum], '\x84', M_GetText("Blue Team"), '\x80'); - } - else if (NetPacket.packet.newteam == 0 && !wasspectator) - HU_AddChatText(va("\x82*%s became a spectator.", player_names[playernum]), false); // "entered the game" text was moved to P_SpectatorJoinGame - - if (gamestate != GS_LEVEL || wasspectator == true) return; + } - FinalisePlaystateChange(playernum); + FinalisePlaystateChange(edit_player); +} + +static void Got_TeamChange(const UINT8 **cp, INT32 playernum) +{ + UINT8 new_team = READUINT8(*cp); + UINT8 edit_player = READUINT8(*cp); + + if (playernum != serverplayer && IsPlayerAdmin(playernum) == false) + { + CONS_Alert(CONS_WARNING, M_GetText("Illegal team change received from player %s\n"), player_names[playernum]); + if (server) + { + SendKick(playernum, KICK_MSG_CON_FAIL); + } + return; + } + + if (G_GametypeHasTeams() == false) + { + return; + } + + if (new_team >= TEAM__MAX) + { + new_team = TEAM_UNASSIGNED; + } + + player_t *const player = &players[edit_player]; + G_AssignTeam(player, new_team); + + if (player->team == TEAM_UNASSIGNED) + { + // auto assign + G_AutoAssignTeam(player); + } } // @@ -5430,22 +5156,6 @@ void D_GameTypeChanged(INT32 lastgametype) if (oldgt && newgt && (lastgametype != gametype)) CONS_Printf(M_GetText("Gametype was changed from %s to %s\n"), oldgt, newgt); } - - // don't retain teams in other modes or between changes from ctf to team match. - // also, stop any and all forms of team scrambling that might otherwise take place. - if (G_GametypeHasTeams()) - { - INT32 i; - for (i = 0; i < MAXPLAYERS; i++) - if (playeringame[i]) - players[i].ctfteam = 0; - - if (server || (IsPlayerAdmin(consoleplayer))) - { - CV_StealthSetValue(&cv_teamscramble, 0); - teamscramble = 0; - } - } } void Gravity_OnChange(void); @@ -5480,165 +5190,6 @@ void SoundTest_OnChange(void) S_StartSound(NULL, cv_soundtest.value); } -void AutoBalance_OnChange(void); -void AutoBalance_OnChange(void) -{ - autobalance = (INT16)cv_autobalance.value; -} - -void TeamScramble_OnChange(void); -void TeamScramble_OnChange(void) -{ - INT16 i = 0, j = 0, playercount = 0; - boolean repick = true; - INT32 blue = 0, red = 0; - INT32 maxcomposition = 0; - INT16 newteam = 0; - INT32 retries = 0; - boolean success = false; - - // Don't trigger outside level or intermission! - if (!(gamestate == GS_LEVEL || gamestate == GS_INTERMISSION || gamestate == GS_VOTING)) - return; - - if (!cv_teamscramble.value) - teamscramble = 0; - - if (!G_GametypeHasTeams() && (server || IsPlayerAdmin(consoleplayer))) - { - CONS_Alert(CONS_NOTICE, M_GetText("This command cannot be used in this gametype.\n")); - CV_StealthSetValue(&cv_teamscramble, 0); - return; - } - - // If a team scramble is already in progress, do not allow another one to be started! - if (teamscramble) - return; - -retryscramble: - - // Clear related global variables. These will get used again in p_tick.c/y_inter.c as the teams are scrambled. - memset(&scrambleplayers, 0, sizeof(scrambleplayers)); - memset(&scrambleteams, 0, sizeof(scrambleplayers)); - scrambletotal = scramblecount = 0; - blue = red = maxcomposition = newteam = playercount = 0; - repick = true; - - // Put each player's node in the array. - for (i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i] && !players[i].spectator) - { - scrambleplayers[playercount] = i; - playercount++; - } - } - - if (playercount < 2) - { - CV_StealthSetValue(&cv_teamscramble, 0); - return; // Don't scramble one or zero players. - } - - // Randomly place players on teams. - if (cv_teamscramble.value == 1) - { - maxcomposition = playercount / 2; - - // Now randomly assign players to teams. - // If the teams get out of hand, assign the rest to the other team. - for (i = 0; i < playercount; i++) - { - if (repick) - newteam = (INT16)((M_RandomByte() % 2) + 1); - - // One team has the most players they can get, assign the rest to the other team. - if (red == maxcomposition || blue == maxcomposition) - { - if (red == maxcomposition) - newteam = 2; - else //if (blue == maxcomposition) - newteam = 1; - - repick = false; - } - - scrambleteams[i] = newteam; - - if (newteam == 1) - red++; - else - blue++; - } - } - else if (cv_teamscramble.value == 2) // Same as before, except split teams based on current score. - { - // Now, sort the array based on points scored. - for (i = 1; i < playercount; i++) - { - for (j = i; j < playercount; j++) - { - INT16 tempplayer = 0; - - if ((players[scrambleplayers[i-1]].score > players[scrambleplayers[j]].score)) - { - tempplayer = scrambleplayers[i-1]; - scrambleplayers[i-1] = scrambleplayers[j]; - scrambleplayers[j] = tempplayer; - } - } - } - - // Now assign players to teams based on score. Scramble in pairs. - // If there is an odd number, one team will end up with the unlucky slob who has no points. =( - for (i = 0; i < playercount; i++) - { - if (repick) - { - newteam = (INT16)((M_RandomByte() % 2) + 1); - repick = false; - } - // (i != 2) means it does ABBABABA, instead of ABABABAB. - // Team A gets 1st, 4th, 6th, 8th. - // Team B gets 2nd, 3rd, 5th, 7th. - // So 1st on one team, 2nd/3rd on the other, then alternates afterwards. - // Sounds strange on paper, but works really well in practice! - else if (i != 2) - { - // We will only randomly pick the team for the first guy. - // Otherwise, just alternate back and forth, distributing players. - newteam = 3 - newteam; - } - - scrambleteams[i] = newteam; - } - } - - // Check to see if our random selection actually - // changed anybody. If not, we run through and try again. - for (i = 0; i < playercount; i++) - { - if (players[scrambleplayers[i]].ctfteam != scrambleteams[i]) - success = true; - } - - if (!success && retries < 5) - { - retries++; - goto retryscramble; //try again - } - - // Display a witty message, but only during scrambles specifically triggered by an admin. - if (cv_teamscramble.value) - { - scrambletotal = playercount; - teamscramble = (INT16)cv_teamscramble.value; - - if (!(gamestate == GS_INTERMISSION && cv_scrambleonchange.value)) - CONS_Printf(M_GetText("Teams will be scrambled next round.\n")); - } -} - static void Command_Showmap_f(void) { if (gamestate == GS_LEVEL) diff --git a/src/d_netcmd.h b/src/d_netcmd.h index 5a1a79255..3f4980f61 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -57,10 +57,6 @@ extern UINT32 timelimitintics, extratimeintics, secretextratime; extern UINT32 g_pointlimit; extern consvar_t cv_allowexitlevel; -extern consvar_t cv_autobalance; -extern consvar_t cv_teamscramble; -extern consvar_t cv_scrambleonchange; - extern consvar_t cv_netstat; extern consvar_t cv_countdowntime; @@ -83,6 +79,7 @@ extern consvar_t cv_karthorns; extern consvar_t cv_kartbot; extern consvar_t cv_karteliminatelast; extern consvar_t cv_thunderdome; +extern consvar_t cv_teamplay; extern consvar_t cv_kartusepwrlv; #ifdef DEVELOP extern consvar_t cv_kartencoremap; @@ -155,7 +152,7 @@ typedef enum XD_ADDFILE, // 8 XD_PAUSE, // 9 XD_ADDPLAYER, // 10 - XD_TEAMCHANGE, // 11 + XD_SPECTATE, // 11 XD_CLEARSCORES, // 12 XD_VERIFIED, // 13 XD_RANDOMSEED, // 14 @@ -187,52 +184,13 @@ typedef enum XD_MAPQUEUE, // 38 XD_CALLZVOTE, // 39 XD_SETZVOTE, // 40 + XD_TEAMCHANGE, // 41 MAXNETXCMD } netxcmd_t; extern const char *netxcmdnames[MAXNETXCMD - 1]; -#if defined(_MSC_VER) -#pragma pack(1) -#endif - -#ifdef _MSC_VER -#pragma warning(disable : 4214) -#endif - -//Packet composition for Command_TeamChange_f() ServerTeamChange, etc. -//bitwise structs make packing bits a little easier, but byte alignment harder? -//todo: decide whether to make the other netcommands conform, or just get rid of this experiment. -struct changeteam_packet_t { - UINT32 playernum : 5; // value 0 to 31 - UINT32 newteam : 5; // value 0 to 31 - UINT32 verification : 1; // value 0 to 1 - UINT32 autobalance : 1; // value 0 to 1 - UINT32 scrambled : 1; // value 0 to 1 -} ATTRPACK; - -#ifdef _MSC_VER -#pragma warning(default : 4214) -#endif - -struct changeteam_value_t { - UINT16 l; // liitle endian - UINT16 b; // big enian -} ATTRPACK; - -//Since we do not want other files/modules to know about this data buffer we union it here with a Short Int. -//Other files/modules will hand the INT16 back to us and we will decode it here. -//We don't have to use a union, but we would then send four bytes instead of two. -typedef union { - changeteam_packet_t packet; - changeteam_value_t value; -} ATTRPACK changeteam_union; - -#if defined(_MSC_VER) -#pragma pack() -#endif - // add game commands, needs cleanup void D_RegisterServerCommands(void); void D_RegisterClientCommands(void); diff --git a/src/d_player.h b/src/d_player.h index a63a145b6..f6397d682 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -671,6 +671,11 @@ struct player_t UINT8 carry; UINT16 dye; + INT32 prefskin; // Queued skin change + UINT16 prefcolor; // Queued color change + INT32 preffollower; // Queued follower change + UINT16 preffollowercolor; // Queued follower color change + // SRB2kart stuff INT32 karthud[NUMKARTHUD]; @@ -678,12 +683,19 @@ struct player_t UINT8 position; // Used for Kart positions, mostly for deterministic stuff UINT8 oldposition; // Used for taunting when you pass someone UINT8 positiondelay; // Used for position number, so it can grow when passing + + UINT8 teamposition; // Position, but only against other teams -- not your own. + UINT8 teamimportance; // Opposite of team position x2, with +1 for being in 1st. + UINT32 distancetofinish; UINT32 distancetofinishprev; + UINT32 lastpickupdistance; // Anti item set farming UINT8 lastpickuptype; + waypoint_t *currentwaypoint; waypoint_t *nextwaypoint; + respawnvars_t respawn; // Respawn info mobj_t *ringShooter; // DEZ respawner object tic_t airtime; // Used to track just air time, but has evolved over time into a general "karted" timer. Rename this variable? @@ -926,7 +938,7 @@ struct player_t INT32 cheatchecknum; // The number of the last cheatcheck you hit INT32 checkpointId; // Players respawn here, objects/checkpoint.cpp - UINT8 ctfteam; // 0 == Spectator, 1 == Red, 2 == Blue + UINT8 team; // 0 == Spectator, 1 == Red, 2 == Blue UINT8 checkskip; // Skipping checkpoints? Oh no no no diff --git a/src/deh_soc.c b/src/deh_soc.c index 32475d0a6..a4e80a9c2 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -3551,22 +3551,6 @@ void readmaincfg(MYFILE *f, boolean mainfile) COM_BufInsertText(W_CacheLumpNum(lumpnum, PU_CACHE)); } } - else if (fastcmp(word, "REDTEAM")) - { - skincolor_redteam = (UINT16)get_number(word2); - } - else if (fastcmp(word, "BLUETEAM")) - { - skincolor_blueteam = (UINT16)get_number(word2); - } - else if (fastcmp(word, "REDRING")) - { - skincolor_redring = (UINT16)get_number(word2); - } - else if (fastcmp(word, "BLUERING")) - { - skincolor_bluering = (UINT16)get_number(word2); - } else if (fastcmp(word, "INVULNTICS")) { invulntics = (UINT16)get_number(word2); diff --git a/src/doomdef.h b/src/doomdef.h index 844be1261..3b0df2279 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -544,6 +544,7 @@ typedef enum DBG_LUA = 0x00000800, DBG_RNG = 0x00001000, DBG_DEMO = 0x00002000, + DBG_TEAMS = 0x00004000, } debugFlags_t; struct debugFlagNames_s diff --git a/src/doomstat.h b/src/doomstat.h index b880a4c22..363c9f330 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -277,9 +277,6 @@ extern UINT8 tutorialchallenge; #define TUTORIALSKIP_FAILED 1 #define TUTORIALSKIP_INPROGRESS 2 -// CTF colors. -extern UINT16 skincolor_redteam, skincolor_blueteam, skincolor_redring, skincolor_bluering; - extern boolean exitfadestarted; struct scene_t @@ -762,8 +759,24 @@ extern INT32 nummaprings; //keep track of spawned rings/coins extern UINT8 nummapspraycans; extern UINT16 numchallengedestructibles; -extern UINT32 bluescore; ///< Blue Team Scores -extern UINT32 redscore; ///< Red Team Scores +// Teamplay +typedef enum +{ + TEAM_UNASSIGNED = 0, + TEAM_ORANGE, + TEAM_BLUE, + TEAM__MAX +} team_e; + +struct teaminfo_t +{ + const char *name; + skincolornum_t color; + UINT32 chat_color; +}; + +extern teaminfo_t g_teaminfo[TEAM__MAX]; +extern UINT32 g_teamscores[TEAM__MAX]; // Eliminates unnecessary searching. extern boolean CheckForBustableBlocks; @@ -845,19 +858,12 @@ extern struct maplighting angle_t angle; } maplighting; -//for CTF balancing -extern INT16 autobalance; -extern INT16 teamscramble; -extern INT16 scrambleplayers[MAXPLAYERS]; //for CTF team scramble -extern INT16 scrambleteams[MAXPLAYERS]; //for CTF team scramble -extern INT16 scrambletotal; //for CTF team scramble -extern INT16 scramblecount; //for CTF team scramble - // SRB2kart extern UINT8 numlaps; extern UINT8 gamespeed; extern boolean franticitems; extern boolean encoremode, prevencoremode; +extern boolean g_teamplay; extern tic_t wantedcalcdelay; extern tic_t itemCooldowns[NUMKARTITEMS - 1]; @@ -891,8 +897,7 @@ extern tic_t gametic; // Player spawn spots. extern mapthing_t *playerstarts[MAXPLAYERS]; // Cooperative -extern mapthing_t *bluectfstarts[MAXPLAYERS]; // CTF -extern mapthing_t *redctfstarts[MAXPLAYERS]; // CTF +extern mapthing_t *teamstarts[TEAM__MAX][MAXPLAYERS]; // Teamplay extern mapthing_t *faultstart; // Kart Fault #define TUBEWAYPOINTSEQUENCESIZE 256 diff --git a/src/g_game.c b/src/g_game.c index 5e9f7634e..e23ecc709 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -182,11 +182,6 @@ char * podiummap = NULL; // map to load for podium char * tutorialchallengemap = NULL; // map to load for tutorial skip UINT8 tutorialchallenge = TUTORIALSKIP_NONE; -UINT16 skincolor_redteam = SKINCOLOR_RED; -UINT16 skincolor_blueteam = SKINCOLOR_BLUE; -UINT16 skincolor_redring = SKINCOLOR_RASPBERRY; -UINT16 skincolor_bluering = SKINCOLOR_PERIWINKLE; - boolean exitfadestarted = false; cutscene_t *cutscenes[128]; @@ -222,7 +217,7 @@ INT32 luabanks[NUM_LUABANKS]; // Temporary holding place for nights data for the current map //nightsdata_t ntemprecords; -UINT32 bluescore, redscore; // CTF and Team Match team scores +UINT32 g_teamscores[TEAM__MAX]; // ring count... for PERFECT! INT32 nummaprings = 0; @@ -289,13 +284,6 @@ fixed_t mapobjectscale; struct maplighting maplighting; -INT16 autobalance; //for CTF team balance -INT16 teamscramble; //for CTF team scramble -INT16 scrambleplayers[MAXPLAYERS]; //for CTF team scramble -INT16 scrambleteams[MAXPLAYERS]; //for CTF team scramble -INT16 scrambletotal; //for CTF team scramble -INT16 scramblecount; //for CTF team scramble - // SRB2Kart // Cvars that we don't want changed mid-game UINT8 numlaps; // Removed from Cvar hell @@ -304,6 +292,10 @@ boolean encoremode = false; // Encore Mode currently enabled? boolean prevencoremode; boolean franticitems; // Frantic items currently enabled? +// Server wants to enable teams? +// (Certain gametypes can override this -- prefer using G_GametypeHasTeams().) +boolean g_teamplay; + // Voting system UINT16 g_voteLevels[4][2]; // Levels that were rolled by the host SINT8 g_votes[VOTE_TOTAL]; // Each player's vote @@ -1559,7 +1551,7 @@ boolean G_CouldView(INT32 playernum) // SRB2Kart: we have no team-based modes, YET... if (G_GametypeHasTeams()) { - if (players[consoleplayer].ctfteam && player->ctfteam != players[consoleplayer].ctfteam) + if (players[consoleplayer].spectator == false && player->team != players[consoleplayer].team) return false; } @@ -1792,6 +1784,73 @@ void G_FixCamera(UINT8 view) R_ResetViewInterpolation(view); } +void G_UpdatePlayerPreferences(player_t *const player) +{ + if (demo.playback) + return; + + // set skin + INT32 new_skin = player->prefskin; + if (K_CanChangeRules(true) == true && cv_forceskin.value >= 0) + { + // Server wants everyone to use the same player + new_skin = cv_forceskin.value; + } + + if (player->skin != new_skin) + { + SetPlayerSkinByNum(player - players, new_skin); + } + + // set color + UINT16 new_color = player->prefcolor; + if (new_color == SKINCOLOR_NONE) + { + new_color = skins[player->skin].prefcolor; + } + + if (G_GametypeHasTeams() == true && player->team != TEAM_UNASSIGNED) + { + new_color = g_teaminfo[player->team].color; + } + + if (player->skincolor != new_color) + { + player->skincolor = new_color; + K_KartResetPlayerColor(player); + } + + // set follower + if (player->followerskin != player->preffollower) + { + K_SetFollowerByNum(player - players, player->preffollower); + } + + // set follower color + if (player->followercolor != player->preffollowercolor) + { + // Don't bother doing garbage and kicking if we receive None, + // this is both silly and a waste of time, + // this will be handled properly in K_HandleFollower. + player->followercolor = player->preffollowercolor; + } +} + +void G_UpdateAllPlayerPreferences(void) +{ + INT32 i; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] == false) + { + continue; + } + + G_UpdatePlayerPreferences(&players[i]); + } +} + // // G_Ticker // Make ticcmd_ts for the players. @@ -1885,6 +1944,13 @@ void G_Ticker(boolean run) K_UpdateAllPlayerPositions(); } } + else if (Playing() && !Y_IntermissionPlayerLock()) + { + if (run) + { + G_UpdateAllPlayerPreferences(); + } + } P_MapEnd(); @@ -2136,7 +2202,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) INT32 pflags; - UINT8 ctfteam; + UINT8 team; INT32 cheatchecknum; INT32 exiting; @@ -2200,6 +2266,11 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) tic_t laptime[LAP__MAX]; + UINT16 prefcolor; + INT32 prefskin; + UINT16 preffollowercolor; + INT32 preffollower; + INT32 i; // This needs to be first, to permit it to wipe extra information @@ -2212,7 +2283,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) score = players[player].score; lives = players[player].lives; - ctfteam = players[player].ctfteam; + team = players[player].team; splitscreenindex = players[player].splitscreenindex; spectator = players[player].spectator; @@ -2223,6 +2294,11 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) skincolor = players[player].skincolor; skin = players[player].skin; + prefcolor = players[player].prefcolor; + prefskin = players[player].prefskin; + preffollower = players[player].preffollower; + preffollowercolor = players[player].preffollowercolor; + if (betweenmaps) { fakeskin = MAXSKINS; @@ -2463,7 +2539,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) p->roundscore = roundscore; p->lives = lives; p->pflags = pflags; - p->ctfteam = ctfteam; + p->team = team; p->jointime = jointime; p->splitscreenindex = splitscreenindex; p->spectator = spectator; @@ -2477,6 +2553,11 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) p->skincolor = skincolor; p->skin = skin; + p->prefcolor = prefcolor; + p->prefskin = prefskin; + p->preffollower = preffollower; + p->preffollowercolor = preffollowercolor; + p->fakeskin = fakeskin; p->kartspeed = kartspeed; p->kartweight = kartweight; @@ -2559,36 +2640,17 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) //p->follower = NULL; // respawn a new one with you, it looks better. // ^ Not necessary anyway since it will be respawned regardless considering it doesn't exist anymore. + if (G_GametypeHasTeams() == true + && p->team == TEAM_UNASSIGNED + && p->spectator == false) + { + // No team? + G_AutoAssignTeam(p); + } + p->playerstate = PST_LIVE; p->panim = PA_STILL; // standing animation - // Check to make sure their color didn't change somehow... - if (G_GametypeHasTeams()) - { - if (p->ctfteam == 1 && p->skincolor != skincolor_redteam) - { - for (i = 0; i <= splitscreen; i++) - { - if (p == &players[g_localplayers[i]]) - { - CV_SetValue(&cv_playercolor[i], skincolor_redteam); - break; - } - } - } - else if (p->ctfteam == 2 && p->skincolor != skincolor_blueteam) - { - for (i = 0; i <= splitscreen; i++) - { - if (p == &players[g_localplayers[i]]) - { - CV_SetValue(&cv_playercolor[i], skincolor_blueteam); - break; - } - } - } - } - if (p->spectator == false && !betweenmaps) { if (enteredGame == true) @@ -2698,56 +2760,78 @@ void G_MovePlayerToSpawnOrCheatcheck(INT32 playernum) mapthing_t *G_FindTeamStart(INT32 playernum) { - const boolean doprints = P_IsPartyPlayer(&players[playernum]); - INT32 i,j; + const boolean do_prints = P_IsPartyPlayer(&players[playernum]); + INT32 i, j; - if (!numredctfstarts && !numbluectfstarts) //why even bother, eh? + for (i = 0; i < TEAM__MAX; i++) { - if ((gametyperules & GTR_TEAMSTARTS) && doprints) - CONS_Alert(CONS_WARNING, M_GetText("No CTF starts in this map!\n")); + if (numteamstarts[i] > 0) + { + break; + } + } + + if (i == TEAM__MAX) + { + // No team starts are counted? + // Why even bother, eh? + + if (do_prints == true && (gametyperules & GTR_TEAMSTARTS) == GTR_TEAMSTARTS) + { + CONS_Alert(CONS_WARNING, M_GetText("No team starts in this map!\n")); + } + return NULL; } - if ((!players[playernum].ctfteam && numredctfstarts && (!numbluectfstarts || P_RandomChance(PR_PLAYERSTARTS, FRACUNIT/2))) || players[playernum].ctfteam == 1) //red + UINT8 use_team = players[playernum].team; + if (players[playernum].spectator == true) { - if (!numredctfstarts) + // Spawn at any team start as a spectator. + i = P_RandomKey(PR_PLAYERSTARTS, TEAM__MAX); + + for (j = 0; j < TEAM__MAX; j++) { - if (doprints) - CONS_Alert(CONS_WARNING, M_GetText("No Red Team starts in this map!\n")); - return NULL; + if (numteamstarts[i] > 0) + { + break; + } + + i++; + if (i >= TEAM__MAX) + { + i = 0; + } } - for (j = 0; j < 32; j++) + use_team = i; + } + + if (numteamstarts[use_team] <= 0) + { + if (do_prints == true) { - i = P_RandomKey(PR_PLAYERSTARTS, numredctfstarts); - if (G_CheckSpot(playernum, redctfstarts[i])) - return redctfstarts[i]; + CONS_Alert(CONS_WARNING, M_GetText("No %s Team starts in this map!\n"), g_teaminfo[use_team].name); } - if (doprints) - CONS_Alert(CONS_WARNING, M_GetText("Could not spawn at any Red Team starts!\n")); return NULL; } - else if (!players[playernum].ctfteam || players[playernum].ctfteam == 2) //blue - { - if (!numbluectfstarts) - { - if (doprints) - CONS_Alert(CONS_WARNING, M_GetText("No Blue Team starts in this map!\n")); - return NULL; - } - for (j = 0; j < 32; j++) + for (j = 0; j < 32; j++) + { + i = P_RandomKey(PR_PLAYERSTARTS, numteamstarts[use_team]); + + if (G_CheckSpot(playernum, teamstarts[use_team][i])) { - i = P_RandomKey(PR_PLAYERSTARTS, numbluectfstarts); - if (G_CheckSpot(playernum, bluectfstarts[i])) - return bluectfstarts[i]; + return teamstarts[use_team][i]; } - if (doprints) - CONS_Alert(CONS_WARNING, M_GetText("Could not spawn at any Blue Team starts!\n")); - return NULL; } - //should never be reached but it gets stuff to shut up + + if (do_prints == true) + { + CONS_Alert(CONS_WARNING, M_GetText("Could not spawn at any %s Team starts!\n"), g_teaminfo[use_team].name); + } + return NULL; } @@ -2977,7 +3061,7 @@ mapthing_t *G_FindMapStart(INT32 playernum) // -- CTF -- // Order: CTF->DM->Race - else if ((gametyperules & GTR_TEAMSTARTS) && players[playernum].ctfteam) + else if ((gametyperules & GTR_TEAMSTARTS) && players[playernum].spectator == false) spawnpoint = G_FindTeamStartOrFallback(playernum); // -- DM/Tag/CTF-spectator/etc -- @@ -3089,7 +3173,7 @@ void G_SpectatePlayerOnJoin(INT32 playernum) // This is only ever called shortly after the above. // That calls CL_ClearPlayer, so spectator is false by default - if (!netgame && !G_GametypeHasTeams() && !G_GametypeHasSpectators()) + if (!netgame && !G_GametypeHasSpectators()) return; // These are handled automatically elsewhere @@ -3219,14 +3303,6 @@ void G_FinishExitLevel(void) gameaction = ga_completed; lastdraw = true; - // If you want your teams scrambled on map change, start the process now. - // The teams will scramble at the start of the next round. - if (cv_scrambleonchange.value && G_GametypeHasTeams()) - { - if (server) - CV_SetValue(&cv_teamscramble, cv_scrambleonchange.value); - } - CON_LogMessage(M_GetText("The round has ended.\n")); // Remove CEcho text on round end. @@ -3525,19 +3601,20 @@ boolean G_GametypeAllowsRetrying(void) // boolean G_GametypeHasTeams(void) { - if (gametyperules & GTR_TEAMS) + const UINT32 rules = (gametyperules & (GTR_TEAMS|GTR_NOTEAMS)); + if (rules == GTR_TEAMS) { // Teams forced on by this gametype return true; } - else if (gametyperules & GTR_NOTEAMS) + else if (rules == GTR_NOTEAMS) { // Teams forced off by this gametype return false; } - // Teams are determined by the "teamplay" modifier! - return false; // teamplay + // Teams are determined by the server's preference! + return g_teamplay; } // @@ -4719,9 +4796,13 @@ static void G_DoCompleted(void) { Y_StartIntermission(); } - else if (grandprixinfo.gp == true) + else { - K_UpdateGPRank(&grandprixinfo.rank); + Y_MidIntermission(); + if (grandprixinfo.gp == true) + { + K_UpdateGPRank(&grandprixinfo.rank); + } } G_UpdateVisited(); @@ -5255,9 +5336,14 @@ void G_InitNew(UINT8 pencoremode, INT32 map, boolean resetplayer, boolean skippr } // Clear a bunch of variables - redscore = bluescore = lastmap = 0; + lastmap = 0; racecountdown = exitcountdown = musiccountdown = mapreset = exitfadestarted = 0; + for (i = 0; i < TEAM__MAX; i++) + { + g_teamscores[i] = 0; + } + for (i = 0; i < MAXPLAYERS; i++) { players[i].playerstate = PST_REBORN; @@ -5730,3 +5816,204 @@ INT32 G_TicsToMilliseconds(tic_t tics) { return (INT32)((tics%TICRATE) * (1000.00f/TICRATE)); } + +teaminfo_t g_teaminfo[TEAM__MAX] = +{ + // TEAM_UNASSIGNED + // These values should not be reached most of the time, + // but it is a necessary evil for this to exist. + { + "Unassigned", + SKINCOLOR_NONE, + 0, + }, + // TEAM_ORANGE + { + "Orange", + SKINCOLOR_TANGERINE, + V_ORANGEMAP, + }, + // TEAM_BLUE + { + "Blue", + SKINCOLOR_SAPPHIRE, + V_BLUEMAP, + }, +}; + +void G_AssignTeam(player_t *const p, UINT8 new_team) +{ + if (p->team != new_team) + { + CONS_Debug(DBG_TEAMS, "%s >> Changed from team %s to team %s.\n", player_names[p - players], g_teaminfo[p->team].name, g_teaminfo[new_team].name); + } + + p->team = new_team; + + if (new_team && p->skincolor != g_teaminfo[new_team].color) + { + p->skincolor = g_teaminfo[new_team].color; + if (G_GamestateUsesLevel()) + { + K_KartResetPlayerColor(p); + } + } +} + +boolean G_SameTeam(const player_t *a, const player_t *b) +{ + if (a == NULL || b == NULL) + { + return false; + } + + if (G_GametypeHasTeams() == true) + { + if (a->team == TEAM_UNASSIGNED || b->team == TEAM_UNASSIGNED) + { + // Unassigned is not a real team. + // Treat them as lone wolves. + return false; + } + + // You share a team! + return (a->team == b->team); + } + + // Free for all. + return false; +} + +UINT8 G_CountTeam(UINT8 team) +{ + UINT8 count = 0; + + for (UINT8 i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] == false || players[i].spectator == true) + { + continue; + } + + if (players[i].team == team) + { + count++; + } + } + + return count; +} + +void G_AutoAssignTeam(player_t *const p) +{ + if (G_GametypeHasTeams() == false) + { + CONS_Debug(DBG_TEAMS, "%s >> Teams are disabled.\n", player_names[p - players]); + G_AssignTeam(p, TEAM_UNASSIGNED); + return; + } + + if (p->spectator == true) + { + CONS_Debug(DBG_TEAMS, "%s >> Why are you giving a spectator a team?\n", player_names[p - players]); + G_AssignTeam(p, TEAM_UNASSIGNED); + return; + } + + if (p->team != TEAM_UNASSIGNED) + { + CONS_Debug(DBG_TEAMS, "%s >> Already assigned a team.\n", player_names[p - players]); + return; + } + + const UINT8 orange_count = G_CountTeam(TEAM_ORANGE); + const UINT8 blue_count = G_CountTeam(TEAM_BLUE); + + if (orange_count == blue_count) + { + CONS_Debug(DBG_TEAMS, "%s >> Team assigned randomly.\n", player_names[p - players]); + G_AssignTeam(p, (P_Random(PR_TEAMS) & 1) ? TEAM_BLUE : TEAM_ORANGE); + return; + } + + CONS_Debug(DBG_TEAMS, "%s >> Team imbalance.\n", player_names[p - players]); + + if (blue_count < orange_count) + { + G_AssignTeam(p, TEAM_BLUE); + } + else + { + G_AssignTeam(p, TEAM_ORANGE); + } +} + +void G_AddTeamScore(UINT8 team, INT32 amount, player_t *source) +{ + if (team == TEAM_UNASSIGNED || G_GametypeHasTeams() == false) + { + return; + } + + if ((gametyperules & GTR_POINTLIMIT) == 0) + { + return; + } + +#if 1 + if (amount <= 0) + { + // Don't allow players to intentionally + // tank the team score. Might not be necessary? + return; + } +#endif + + (void)source; // Just included in case we need the scorer later. + + // Don't underflow. + // Don't go above MAXSCORE. + if (amount < 0 && (UINT32)-amount > g_teamscores[team]) + { + g_teamscores[team] = 0; + } + else if (g_teamscores[team] + amount < MAXSCORE) + { + if (g_teamscores[team] < g_pointlimit + && g_pointlimit <= g_teamscores[team] + amount) + { + INT32 i; + for (i = 0; i < MAXPLAYERS; i++) + { + player_t *const p = &players[i]; + + if (playeringame[i] == false || p->spectator == true) + { + continue; + } + + if (p->team == team) + { + HU_DoTitlecardCEchoForDuration(p, "K.O. READY!", true, 5*TICRATE/2); + } + } + } + + g_teamscores[team] += amount; + } + else + { + g_teamscores[team] = MAXSCORE; + } +} + +UINT32 G_TeamOrIndividualScore(const player_t *player) +{ + if (G_GametypeHasTeams() == true && player->team != TEAM_UNASSIGNED) + { + return g_teamscores[player->team]; + } + + return player->roundscore; +} + diff --git a/src/g_game.h b/src/g_game.h index 221643c04..b022b3e04 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -234,6 +234,9 @@ void G_UpdateTimeStickerMedals(UINT16 map, boolean showownrecord); void G_TickTimeStickerMedals(void); void G_UpdateRecords(void); +void G_UpdatePlayerPreferences(player_t *const player); +void G_UpdateAllPlayerPreferences(void); + void G_Ticker(boolean run); boolean G_Responder(event_t *ev); @@ -290,6 +293,13 @@ void G_AddMapToBuffer(UINT16 map); void G_UpdateVisited(void); +boolean G_SameTeam(const player_t *a, const player_t *b); +UINT8 G_CountTeam(UINT8 team); +void G_AssignTeam(player_t *const p, UINT8 new_team); +void G_AutoAssignTeam(player_t *const p); +void G_AddTeamScore(UINT8 team, INT32 amount, player_t *source); +UINT32 G_TeamOrIndividualScore(const player_t *player); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/hu_stuff.c b/src/hu_stuff.c index c866184b4..2c50bb99f 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -621,7 +621,7 @@ static void Command_Sayteam_f(void) return; } - if (G_GametypeHasTeams()) // revert to normal say if we don't have teams in this gametype. + if (G_GametypeHasTeams()) // revert to normal say if we don't have teams in this gametype. DoSayPacketFromCommand(-1, 1, 0); else DoSayPacketFromCommand(0, 1, 0); @@ -779,16 +779,8 @@ static void Got_Saycmd(const UINT8 **p, INT32 playernum) } else if (target == -1) // say team { - if (players[playernum].ctfteam == 1) - { - // red text - cstart = textcolor = "\x85"; - } - else - { - // blue text - cstart = textcolor = "\x84"; - } + sprintf(color_prefix, "%c", '\x80' + (g_teaminfo[ players[playernum].team ].chat_color >> V_CHARCOLORSHIFT)); + cstart = textcolor = color_prefix; } else { @@ -1593,12 +1585,7 @@ static void HU_DrawChat(void) if (teamtalk) { talk = ttalk; -#if 0 - if (players[consoleplayer].ctfteam == 1) - t = '\0x85'; // Red - else if (players[consoleplayer].ctfteam == 2) - t = '\0x84'; // Blue -#endif + //t = '\x80' + (g_teaminfo[ players[consoleplayer].team ].chat_color >> V_CHARCOLORSHIFT); } typelines = 1; @@ -2569,8 +2556,6 @@ static void HU_DrawRankings(void) completed[i] = true; - standings.character[standings.numplayers] = players[i].skin; - standings.color[standings.numplayers] = players[i].skincolor; standings.pos[standings.numplayers] = players[i].position; #define strtime standings.strval[standings.numplayers] @@ -2608,6 +2593,8 @@ static void HU_DrawRankings(void) standings.numplayers++; } + standings.halfway = (standings.numplayers-1)/2; + // Returns early if there's no players to draw Y_PlayerStandingsDrawer(&standings, 0); diff --git a/src/k_battle.c b/src/k_battle.c index 9c82c38dc..6886c9371 100644 --- a/src/k_battle.c +++ b/src/k_battle.c @@ -985,7 +985,16 @@ boolean K_EndBattleRound(player_t *victor) if (gametyperules & GTR_POINTLIMIT) { // Lock the winner in before the round ends. + + // TODO: a "won the round" bool used for sorting + // position / intermission, so we aren't completely + // clobbering the individual scoring. victor->roundscore = 100; + + if (G_GametypeHasTeams() == true && victor->team != TEAM_UNASSIGNED) + { + g_teamscores[victor->team] = 100; + } } } diff --git a/src/k_bot.cpp b/src/k_bot.cpp index 5e00814ff..e5dc4fd63 100644 --- a/src/k_bot.cpp +++ b/src/k_bot.cpp @@ -174,16 +174,20 @@ void K_SetBot(UINT8 newplayernum, UINT8 skinnum, UINT8 difficulty, botStyle_e st break; } } - players[newplayernum].skincolor = color; - K_SetNameForBot(newplayernum, realname); - SetPlayerSkinByNum(newplayernum, skinnum); + K_SetNameForBot(newplayernum, realname); for (UINT8 i = 0; i < PWRLV_NUMTYPES; i++) { clientpowerlevels[newplayernum][i] = 0; } + players[newplayernum].prefcolor = color; + players[newplayernum].prefskin = skinnum; + players[newplayernum].preffollower = -1; + players[newplayernum].preffollowercolor = SKINCOLOR_NONE; + G_UpdatePlayerPreferences(&players[newplayernum]); + if (netgame) { HU_AddChatText(va("\x82*Bot %d has been added to the game", newplayernum+1), false); @@ -630,6 +634,12 @@ static UINT32 K_BotRubberbandDistance(const player_t *player) continue; } + if (G_SameTeam(player, &players[i]) == true) + { + // Don't consider friendlies with your rubberbanding. + continue; + } + // First check difficulty levels, then score, then settle it with port priority! if (player->botvars.difficulty < players[i].botvars.difficulty) { @@ -716,6 +726,12 @@ fixed_t K_BotRubberband(const player_t *player) continue; } + // Don't rubberband to friendlies... + if (G_SameTeam(player, &players[i]) == true) + { + continue; + } + #if 0 // Only rubberband up to players. if (players[i].bot) diff --git a/src/k_botitem.cpp b/src/k_botitem.cpp index 227e55be7..4bff3ce40 100644 --- a/src/k_botitem.cpp +++ b/src/k_botitem.cpp @@ -89,6 +89,7 @@ static boolean K_BotUseItemNearPlayer(const player_t *player, ticcmd_t *cmd, fix if (target->mo == NULL || P_MobjWasRemoved(target->mo) || player == target || target->spectator + || G_SameTeam(player, target) || target->flashing) { continue; @@ -144,6 +145,7 @@ static player_t *K_PlayerNearSpot(const player_t *player, fixed_t x, fixed_t y, if (target->mo == NULL || P_MobjWasRemoved(target->mo) || player == target || target->spectator + || G_SameTeam(player, target) || target->flashing) { continue; @@ -222,6 +224,7 @@ static player_t *K_PlayerInCone(const player_t *player, fixed_t radius, UINT16 c if (target->mo == NULL || P_MobjWasRemoved(target->mo) || player == target || target->spectator + || G_SameTeam(player, target) || target->flashing || !P_CheckSight(player->mo, target->mo)) { @@ -1142,28 +1145,31 @@ static void K_BotItemJawz(const player_t *player, ticcmd_t *cmd) && players[lastTarg].mo != NULL && P_MobjWasRemoved(players[lastTarg].mo) == false) { - mobj_t *targMo = players[lastTarg].mo; - mobj_t *mobj = NULL, *next = NULL; - boolean targettedAlready = false; - target = &players[lastTarg]; - // Make sure no other Jawz are targetting this player. - for (mobj = trackercap; mobj; mobj = next) + if (G_SameTeam(player, target) == false) { - next = mobj->itnext; + mobj_t *targMo = players[lastTarg].mo; + mobj_t *mobj = NULL, *next = NULL; + boolean targettedAlready = false; - if (mobj->type == MT_JAWZ && mobj->target == targMo) + // Make sure no other Jawz are targetting this player. + for (mobj = trackercap; mobj; mobj = next) { - targettedAlready = true; - break; - } - } + next = mobj->itnext; - if (targettedAlready == false) - { - K_ItemConfirmForTarget(player, cmd, target, player->botvars.difficulty * snipeMul); - throwdir = 1; + if (mobj->type == MT_JAWZ && mobj->target == targMo) + { + targettedAlready = true; + break; + } + } + + if (targettedAlready == false) + { + K_ItemConfirmForTarget(player, cmd, target, player->botvars.difficulty * snipeMul); + throwdir = 1; + } } } @@ -1253,6 +1259,7 @@ static void K_BotItemBubble(const player_t *player, ticcmd_t *cmd) if (target->mo == NULL || P_MobjWasRemoved(target->mo) || player == target || target->spectator + || G_SameTeam(player, target) || target->flashing) { continue; @@ -1528,6 +1535,7 @@ static void K_BotItemInstashield(const player_t *player, ticcmd_t *cmd) if (P_MobjWasRemoved(target->mo) == true || player == target || target->spectator == true + || G_SameTeam(player, target) == true || target->flashing != 0) { continue; diff --git a/src/k_botsearch.cpp b/src/k_botsearch.cpp index 6d1e2c85c..fd4de8a95 100644 --- a/src/k_botsearch.cpp +++ b/src/k_botsearch.cpp @@ -353,13 +353,14 @@ static void K_AddDodgeObject(mobj_t *thing, UINT8 side, UINT8 weight) } /*-------------------------------------------------- - static boolean K_PlayerAttackSteer(mobj_t *thing, UINT8 side, UINT8 weight, boolean attackCond, boolean dodgeCond) + static boolean K_PlayerAttackSteer(mobj_t *thing, boolean friendly_fire, UINT8 side, UINT8 weight, boolean attackCond, boolean dodgeCond) Checks two conditions to determine if the object should be attacked or dodged. Input Arguments:- thing - Object to move towards/away from. + friendly_fire - If the attack would be against a friendly player. side - Which side -- 0 for left, 1 for right weight - How important this object is. attackCond - If this is true, and dodgeCond isn't, then we go towards the object. @@ -368,8 +369,15 @@ static void K_AddDodgeObject(mobj_t *thing, UINT8 side, UINT8 weight) Return:- true if either condition is successful. --------------------------------------------------*/ -static boolean K_PlayerAttackSteer(mobj_t *thing, UINT8 side, UINT8 weight, boolean attackCond, boolean dodgeCond) +static boolean K_PlayerAttackSteer(mobj_t *thing, boolean friendly_fire, UINT8 side, UINT8 weight, boolean attackCond, boolean dodgeCond) { + if (friendly_fire == true && attackCond == true && dodgeCond == false) + { + // Dodge, don't attack. + attackCond = false; + dodgeCond = true; + } + if (attackCond == true && dodgeCond == false) { K_AddAttackObject(thing, side, weight); @@ -547,9 +555,11 @@ static BlockItReturn_t K_FindObjectsForNudging(mobj_t *thing) && !thing->player->hyudorotimer && !g_nudgeSearch.botmo->player->hyudorotimer) { + const boolean same_team = G_SameTeam(g_nudgeSearch.botmo->player, thing->player); + // There REALLY ought to be a better way to handle this logic, right?! // Squishing - if (K_PlayerAttackSteer(thing, side, 20, + if (K_PlayerAttackSteer(thing, same_team, side, 20, K_IsBigger(g_nudgeSearch.botmo, thing), K_IsBigger(thing, g_nudgeSearch.botmo) )) @@ -557,7 +567,7 @@ static BlockItReturn_t K_FindObjectsForNudging(mobj_t *thing) break; } // Invincibility - else if (K_PlayerAttackSteer(thing, side, 20, + else if (K_PlayerAttackSteer(thing, same_team, side, 20, g_nudgeSearch.botmo->player->invincibilitytimer, thing->player->invincibilitytimer )) @@ -565,7 +575,7 @@ static BlockItReturn_t K_FindObjectsForNudging(mobj_t *thing) break; } // Lightning Shield - else if (K_PlayerAttackSteer(thing, side, 20, + else if (K_PlayerAttackSteer(thing, same_team, side, 20, g_nudgeSearch.botmo->player->itemtype == KITEM_LIGHTNINGSHIELD, thing->player->itemtype == KITEM_LIGHTNINGSHIELD )) @@ -573,7 +583,7 @@ static BlockItReturn_t K_FindObjectsForNudging(mobj_t *thing) break; } // Bubble Shield - else if (K_PlayerAttackSteer(thing, side, 20, + else if (K_PlayerAttackSteer(thing, same_team, side, 20, g_nudgeSearch.botmo->player->itemtype == KITEM_BUBBLESHIELD, thing->player->itemtype == KITEM_BUBBLESHIELD )) @@ -581,7 +591,7 @@ static BlockItReturn_t K_FindObjectsForNudging(mobj_t *thing) break; } // Flame Shield - else if (K_PlayerAttackSteer(thing, side, 20, + else if (K_PlayerAttackSteer(thing, same_team, side, 20, g_nudgeSearch.botmo->player->itemtype == KITEM_FLAMESHIELD, thing->player->itemtype == KITEM_FLAMESHIELD )) @@ -589,7 +599,7 @@ static BlockItReturn_t K_FindObjectsForNudging(mobj_t *thing) break; } // Has held item shield - else if (K_PlayerAttackSteer(thing, side, 20, + else if (K_PlayerAttackSteer(thing, same_team, side, 20, (thing->player->itemflags & (IF_ITEMOUT|IF_EGGMANOUT)), (g_nudgeSearch.botmo->player->itemflags & (IF_ITEMOUT|IF_EGGMANOUT)) )) @@ -597,7 +607,7 @@ static BlockItReturn_t K_FindObjectsForNudging(mobj_t *thing) break; } // Ring Sting - else if (K_PlayerAttackSteer(thing, side, 20, + else if (K_PlayerAttackSteer(thing, same_team, side, 20, thing->player->rings <= 0, g_nudgeSearch.botmo->player->rings <= 0 )) @@ -620,7 +630,7 @@ static BlockItReturn_t K_FindObjectsForNudging(mobj_t *thing) weightdiff = ourweight - theirweight; } - if (weightdiff > mapobjectscale) + if (weightdiff > mapobjectscale && same_team == false) { K_AddAttackObject(thing, side, 20); } diff --git a/src/k_grandprix.c b/src/k_grandprix.c index 8991a9606..3ffaab7bb 100644 --- a/src/k_grandprix.c +++ b/src/k_grandprix.c @@ -791,10 +791,14 @@ void K_RetireBots(void) bot->botvars.difficulty = newDifficulty; bot->botvars.diffincrease = 0; - SetPlayerSkinByNum(i, skinnum); - bot->skincolor = skins[skinnum].prefcolor; K_SetNameForBot(i, skins[skinnum].realname); + bot->prefskin = skinnum; + bot->prefcolor = skins[skinnum].prefcolor; + bot->preffollower = -1; + bot->preffollowercolor = SKINCOLOR_NONE; + G_UpdatePlayerPreferences(bot); + bot->score = 0; bot->pflags &= ~PF_NOCONTEST; } diff --git a/src/k_hud.cpp b/src/k_hud.cpp index fc4948357..2b89011b3 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -2428,7 +2428,7 @@ struct PositionFacesInfo void draw_4p_battle(int x, int y, INT32 flags); player_t* top() const { return &players[rankplayer[0]]; } - UINT32 top_score() const { return top()->roundscore; } + UINT32 top_score() const { return G_TeamOrIndividualScore( top() ); } bool near_goal() const { @@ -2548,7 +2548,7 @@ void PositionFacesInfo::draw_1p() } // Draw GOAL - bool skull = g_pointlimit && (g_pointlimit <= stplyr->roundscore); + bool skull = g_pointlimit && (g_pointlimit <= G_TeamOrIndividualScore(stplyr)); INT32 height = i*18; INT32 GOAL_Y = Y-height; @@ -3035,6 +3035,34 @@ INT32 K_GetTransFlagFromFixed(fixed_t value) } } +static void K_drawKartTeamScores(void) +{ + if (G_GametypeHasTeams() == false) + { + return; + } + + for (INT32 i = TEAM_UNASSIGNED+1; i < TEAM__MAX; i++) + { + INT32 x = BASEVIDWIDTH/2; + + x += -12 + (24 * (i - 1)); + + V_DrawCenteredString(x, 5, g_teaminfo[i].chat_color, va("%d", g_teamscores[i])); + + if (stplyr->team == i) + { + UINT32 individual_score = stplyr->teamimportance; + if (gametyperules & GTR_POINTLIMIT) + { + individual_score = stplyr->roundscore; + } + + V_DrawCenteredString(x, 15, g_teaminfo[i].chat_color, va("+%d", individual_score)); + } + } +} + static void K_drawKartLaps(void) { INT32 splitflags = V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_SPLITSCREEN; @@ -6720,7 +6748,14 @@ void K_drawKartHUD(void) K_drawKartEmeralds(); } else if (!islonesome && !K_Cooperative()) + { K_DrawKartPositionNum(stplyr->position); + } + } + + if (G_GametypeHasTeams() == true) + { + K_drawKartTeamScores(); } if (LUA_HudEnabled(hud_gametypeinfo)) diff --git a/src/k_kart.c b/src/k_kart.c index 8de6f538e..d1c957d81 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -4030,6 +4030,12 @@ angle_t K_MomentumAngleReal(const mobj_t *mo) // Scale amp rewards for crab bucketing. Play ambitiously! boolean K_PvPAmpReward(UINT32 award, player_t *attacker, player_t *defender) { + if (G_SameTeam(attacker, defender) == true) + { + // Do not reward amps for friendly fire. + return 0; + } + UINT32 epsilon = FixedMul(2048/4, mapobjectscale); // How close is close enough that full reward seems fair, even if you're technically ahead? UINT32 range = FixedMul(2048, mapobjectscale); UINT32 atkdist = attacker->distancetofinish + epsilon; @@ -4054,6 +4060,9 @@ void K_SpawnAmps(player_t *player, UINT8 amps, mobj_t *impact) if (gametyperules & GTR_SPHERES) return; + if (amps == 0) + return; + UINT16 scaledamps = min(amps, amps * (10 + (9-player->kartspeed) - (9-player->kartweight)) / 10); /* @@ -4317,6 +4326,12 @@ void K_BattleAwardHit(player_t *player, player_t *victim, mobj_t *inflictor, UIN return; } + if (G_SameTeam(player, victim) == true) + { + // No farming points off of teammates, either. + return; + } + if (player->exiting) { // The round has already ended, don't mess with points @@ -4347,7 +4362,7 @@ void K_BattleAwardHit(player_t *player, player_t *victim, mobj_t *inflictor, UIN } // Check this before adding to player score - if ((gametyperules & GTR_BUMPERS) && finishOff && g_pointlimit <= player->roundscore) + if ((gametyperules & GTR_BUMPERS) && finishOff && g_pointlimit <= G_TeamOrIndividualScore(player)) { K_EndBattleRound(player); @@ -8294,7 +8309,7 @@ mobj_t *K_FindJawzTarget(mobj_t *actor, player_t *source, angle_t range) continue; } - if (G_GametypeHasTeams() && source != NULL && source->ctfteam == player->ctfteam) + if (G_SameTeam(source, player) == false) { // Don't home in on teammates. continue; @@ -10090,9 +10105,7 @@ void K_KartResetPlayerColor(player_t *player) if (player->mo->health <= 0 || player->playerstate == PST_DEAD || (player->respawn.state == RESPAWNST_MOVE)) // Override everything { - player->mo->colorized = (player->dye != 0); - player->mo->color = player->dye ? player->dye : player->skincolor; - goto finalise; + goto base; } if (player->eggmanexplode) // You're gonna diiiiie @@ -10204,8 +10217,18 @@ void K_KartResetPlayerColor(player_t *player) goto finalise; } - player->mo->colorized = (player->dye != 0); - player->mo->color = player->dye ? player->dye : player->skincolor; +base: + + if (player->dye) + { + player->mo->colorized = true; + player->mo->color = player->dye; + } + else + { + player->mo->colorized = false; + player->mo->color = player->skincolor; + } finalise: @@ -11740,13 +11763,18 @@ static void K_KartDrift(player_t *player, boolean onground) else player->pflags &= ~PF_BRAKEDRIFT; } + // // K_KartUpdatePosition // void K_KartUpdatePosition(player_t *player) { - fixed_t position = 1; - fixed_t oldposition = player->position; + UINT8 position = 1; + UINT8 oldposition = player->position; + + UINT8 team_position = 1; + UINT32 team_importance = 0; + fixed_t i; INT32 realplayers = 0; @@ -11755,6 +11783,8 @@ void K_KartUpdatePosition(player_t *player) // Ensure these are reset for spectators player->position = 0; player->positiondelay = 0; + player->teamposition = 0; + player->teamimportance = 0; return; } @@ -11779,23 +11809,40 @@ void K_KartUpdatePosition(player_t *player) realplayers++; + const boolean same_team = G_SameTeam(player, &players[i]); + +#define increment_position(condition) \ + if (condition) \ + { \ + position++; \ + if (!same_team) \ + { \ + team_position++; \ + } \ + } \ + else \ + { \ + if (!same_team) \ + { \ + team_importance++; \ + } \ + } + if (gametyperules & GTR_CIRCUIT) { if (player->exiting) // End of match standings { // Only time matters - if (players[i].realtime < player->realtime) - position++; + increment_position(players[i].realtime < player->realtime) } else { // I'm a lap behind this player OR // My distance to the finish line is higher, so I'm behind - if ((players[i].laps > player->laps) - || (players[i].distancetofinish < player->distancetofinish)) - { - position++; - } + increment_position( + (players[i].laps > player->laps) + || (players[i].distancetofinish < player->distancetofinish) + ) } } else @@ -11803,8 +11850,7 @@ void K_KartUpdatePosition(player_t *player) if (player->exiting) // End of match standings { // Only score matters - if (players[i].roundscore > player->roundscore) - position++; + increment_position(players[i].roundscore > player->roundscore) } else { @@ -11814,26 +11860,25 @@ void K_KartUpdatePosition(player_t *player) // First compare all points if (players[i].roundscore > player->roundscore) { - position++; + increment_position(true) } else if (players[i].roundscore == player->roundscore) { // Emeralds are a tie breaker if (yourEmeralds > myEmeralds) { - position++; + increment_position(true) } else if (yourEmeralds == myEmeralds) { // Bumpers are the second tier tie breaker - if (K_Bumpers(&players[i]) > K_Bumpers(player)) - { - position++; - } + increment_position(K_Bumpers(&players[i]) > K_Bumpers(player)) } } } } + +#undef increment_position } } @@ -11887,6 +11932,15 @@ void K_KartUpdatePosition(player_t *player) } player->position = position; + player->teamposition = team_position; + + // "Team importance" is used for scoring + // in gametypes without scoring / point limit. + player->teamimportance = (team_importance * 2); + if (position == 1) + { + player->teamimportance++; + } } void K_UpdateAllPlayerPositions(void) @@ -11927,6 +11981,26 @@ void K_UpdateAllPlayerPositions(void) K_KartUpdatePosition(&players[i]); } } + + // Team Race: Live update score. + if (G_GametypeHasTeams() == true && (gametyperules & GTR_POINTLIMIT) == 0) + { + for (i = 0; i < TEAM__MAX; i++) + { + g_teamscores[i] = 0; + } + + for (i = 0; i < MAXPLAYERS; i++) + { + const player_t *player = &players[i]; + if (playeringame[i] == false || player->spectator == true || player->team == TEAM_UNASSIGNED) + { + continue; + } + + g_teamscores[player->team] += player->teamimportance; + } + } } // @@ -14968,7 +15042,7 @@ UINT32 K_PointLimitForGametype(void) // counted. for (i = 0; i < MAXPLAYERS; ++i) { - if (D_IsPlayerHumanAndGaming(i)) + if (playeringame[i] == true && players[i].spectator == false) { ptsCap += 3; } @@ -14978,6 +15052,43 @@ UINT32 K_PointLimitForGametype(void) { ptsCap = 16; } + + if (G_GametypeHasTeams() == true) + { + // Scale up the point limit based on + // the team sizes. Based upon the smallest + // team, because it would make an uneven + // fucked up 1v15 possible to win, even + // if it was still unbalanced. + const UINT32 old_ptsCap = ptsCap; + UINT8 smallest_team = MAXPLAYERS; + + for (i = TEAM_UNASSIGNED+1; i < TEAM__MAX; i++) + { + UINT8 countteam = G_CountTeam(i); + smallest_team = min( smallest_team, countteam ); + } + + if (smallest_team > 1) + { + UINT8 pts_accumulator = ptsCap / 2; + for (i = 0; i < smallest_team - 1; i++) + { + if (pts_accumulator == 0) + { + break; + } + + ptsCap += pts_accumulator; + pts_accumulator /= 2; + } + } + + CONS_Debug( + DBG_TEAMS, "Team Battle: points cap increased from %u to %u. (team size is %u)\n", + old_ptsCap, ptsCap, smallest_team + ); + } } return ptsCap; @@ -15081,13 +15192,19 @@ fixed_t K_GetExpAdjustment(player_t *player) fixed_t exp_stablerate = 3*FRACUNIT/10; // how low is your placement before losing XP? 4*FRACUNIT/10 = top 40% of race will gain fixed_t result = 0; - INT32 live_players = 0; + INT32 live_players = 0; // players we are competing against for (INT32 i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i] || players[i].spectator || player == players+i) continue; + if (G_SameTeam(player, &players[i]) == true) + { + // You don't win/lose against your teammates. + continue; + } + live_players++; } @@ -15102,6 +15219,12 @@ fixed_t K_GetExpAdjustment(player_t *player) if (!playeringame[i] || players[i].spectator || player == players+i) continue; + if (G_SameTeam(player, &players[i]) == true) + { + // You don't win/lose against your teammates. + continue; + } + if (player->position < players[i].position) result += exp_power; } diff --git a/src/k_menu.h b/src/k_menu.h index 7f49fa60f..01681be74 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -477,15 +477,17 @@ extern menu_t OPTIONS_HUDOnlineDef; typedef enum { gopt_spacer0 = 0, - gopt_gamespeed, + gopt_teamplay, gopt_frantic, + gopt_spacer1, + gopt_gamespeed, gopt_encore, gopt_exitcountdown, - gopt_spacer1, + gopt_spacer2, gopt_timelimit, gopt_pointlimit, gopt_startingbumpers, - gopt_spacer2, + gopt_spacer3, gopt_itemtoggles } gopt_e; diff --git a/src/k_pwrlv.c b/src/k_pwrlv.c index ca2c09a0a..a420bff4e 100644 --- a/src/k_pwrlv.c +++ b/src/k_pwrlv.c @@ -245,6 +245,12 @@ void K_UpdatePowerLevels(player_t *player, UINT8 lap, boolean forfeit) continue; } + if (G_SameTeam(player, &players[i]) == true) + { + // You don't win/lose against your teammates. + continue; + } + CONS_Debug(DBG_PWRLV, "%s VS %s:\n", player_names[playerNum], player_names[i]); theirPower = clientpowerlevels[i][powerType]; diff --git a/src/lua_hook.h b/src/lua_hook.h index cb126c534..851a82738 100644 --- a/src/lua_hook.h +++ b/src/lua_hook.h @@ -143,7 +143,7 @@ int LUA_HookMapThingSpawn(mobj_t *, mapthing_t *); int LUA_HookFollowMobj(player_t *, mobj_t *); int LUA_HookPlayerCanDamage(player_t *, mobj_t *); void LUA_HookPlayerQuit(player_t *, kickreason_t); -int LUA_HookTeamSwitch(player_t *, int newteam, boolean fromspectators, boolean tryingautobalance, boolean tryingscramble); +//int LUA_HookTeamSwitch(player_t *, int newteam, boolean fromspectators, boolean tryingautobalance, boolean tryingscramble); int LUA_HookViewpointSwitch(player_t *player, player_t *newdisplayplayer, boolean forced); int LUA_HookSeenPlayer(player_t *player, player_t *seenfriend); diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c index 1ccedd091..bb66f7729 100644 --- a/src/lua_hooklib.c +++ b/src/lua_hooklib.c @@ -961,6 +961,7 @@ void LUA_HookPlayerQuit(player_t *plr, kickreason_t reason) } } +/* int LUA_HookTeamSwitch(player_t *player, int newteam, boolean fromspectators, boolean tryingautobalance, boolean tryingscramble) { Hook_State hook; @@ -975,6 +976,7 @@ int LUA_HookTeamSwitch(player_t *player, int newteam, boolean fromspectators, bo } return hook.status; } +*/ int LUA_HookViewpointSwitch(player_t *player, player_t *newdisplayplayer, boolean forced) { diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index e96a2db13..2b6c7b07e 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -226,6 +226,10 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->oldposition); else if (fastcmp(field,"positiondelay")) lua_pushinteger(L, plr->positiondelay); + else if (fastcmp(field,"teamposition")) + lua_pushinteger(L, plr->teamposition); + else if (fastcmp(field,"teamimportance")) + lua_pushinteger(L, plr->teamimportance); else if (fastcmp(field,"distancetofinish")) lua_pushinteger(L, plr->distancetofinish); else if (fastcmp(field,"distancetofinishprev")) @@ -561,6 +565,14 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->followercolor); else if (fastcmp(field,"follower")) LUA_PushUserdata(L, plr->follower, META_MOBJ); + else if (fastcmp(field,"prefskin")) + lua_pushinteger(L, plr->prefskin); + else if (fastcmp(field,"prefcolor")) + lua_pushinteger(L, plr->prefcolor); + else if (fastcmp(field,"preffollower")) + lua_pushinteger(L, plr->preffollower); + else if (fastcmp(field,"preffollowercolor")) + lua_pushinteger(L, plr->preffollowercolor); // // rideroids @@ -675,8 +687,8 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->laps); else if (fastcmp(field,"latestlap")) lua_pushinteger(L, plr->latestlap); - else if (fastcmp(field,"ctfteam")) - lua_pushinteger(L, plr->ctfteam); + else if (fastcmp(field,"team")) + lua_pushinteger(L, plr->team); else if (fastcmp(field,"checkskip")) lua_pushinteger(L, plr->checkskip); else if (fastcmp(field,"cheatchecknum")) @@ -820,6 +832,10 @@ static int player_set(lua_State *L) plr->oldposition = luaL_checkinteger(L, 3); else if (fastcmp(field,"positiondelay")) plr->positiondelay = luaL_checkinteger(L, 3); + else if (fastcmp(field,"teamposition")) + plr->teamposition = luaL_checkinteger(L, 3); + else if (fastcmp(field,"teamimportance")) + plr->teamimportance = luaL_checkinteger(L, 3); else if (fastcmp(field,"distancetofinish")) return NOSET; else if (fastcmp(field,"distancetofinishprev")) @@ -1130,8 +1146,16 @@ static int player_set(lua_State *L) plr->followercolor = luaL_checkinteger(L, 3); else if (fastcmp(field,"followerready")) plr->followerready = luaL_checkboolean(L, 3); - else if (fastcmp(field,"follower")) // it's probably best we don't allow the follower mobj to change. - return NOSET; + else if (fastcmp(field,"follower")) + return NOSET; // it's probably best we don't allow the follower mobj to change. + else if (fastcmp(field,"prefskin")) + return NOSET; // don't allow changing user preferences + else if (fastcmp(field,"prefcolor")) + return NOSET; // don't allow changing user preferences + else if (fastcmp(field,"preffollower")) + return NOSET; // don't allow changing user preferences + else if (fastcmp(field,"preffollowercolor")) + return NOSET; // don't allow changing user preferences // time to add to the endless elseif list!!!! // rideroids @@ -1252,8 +1276,8 @@ static int player_set(lua_State *L) plr->laps = (UINT8)luaL_checkinteger(L, 3); else if (fastcmp(field,"latestlap")) plr->latestlap = (UINT8)luaL_checkinteger(L, 3); - else if (fastcmp(field,"ctfteam")) - plr->ctfteam = (INT32)luaL_checkinteger(L, 3); + else if (fastcmp(field,"team")) + G_AssignTeam(plr, (UINT8)luaL_checkinteger(L, 3)); else if (fastcmp(field,"checkskip")) plr->checkskip = (INT32)luaL_checkinteger(L, 3); else if (fastcmp(field,"cheatchecknum")) diff --git a/src/lua_script.c b/src/lua_script.c index 3a4f64f4a..335682e15 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -197,12 +197,6 @@ int LUA_PushGlobals(lua_State *L, const char *word) } else if (fastcmp(word,"paused")) { lua_pushboolean(L, paused); return 1; - } else if (fastcmp(word,"bluescore")) { - lua_pushinteger(L, bluescore); - return 1; - } else if (fastcmp(word,"redscore")) { - lua_pushinteger(L, redscore); - return 1; } else if (fastcmp(word,"timelimit")) { lua_pushinteger(L, timelimitintics); return 1; @@ -226,20 +220,6 @@ int LUA_PushGlobals(lua_State *L, const char *word) lua_pushstring(L, tutorialchallengemap); return 1; // end map vars - // begin CTF colors - } else if (fastcmp(word,"skincolor_redteam")) { - lua_pushinteger(L, skincolor_redteam); - return 1; - } else if (fastcmp(word,"skincolor_blueteam")) { - lua_pushinteger(L, skincolor_blueteam); - return 1; - } else if (fastcmp(word,"skincolor_redring")) { - lua_pushinteger(L, skincolor_redring); - return 1; - } else if (fastcmp(word,"skincolor_bluering")) { - lua_pushinteger(L, skincolor_bluering); - return 1; - // end CTF colors // begin timers } else if (fastcmp(word,"invulntics")) { lua_pushinteger(L, invulntics); @@ -351,6 +331,9 @@ int LUA_PushGlobals(lua_State *L, const char *word) } else if (fastcmp(word,"franticitems")) { lua_pushboolean(L, franticitems); return 1; + } else if (fastcmp(word,"teamplay")) { + lua_pushboolean(L, g_teamplay); + return 1; } else if (fastcmp(word,"wantedcalcdelay")) { lua_pushinteger(L, wantedcalcdelay); return 1; @@ -392,19 +375,7 @@ int LUA_PushGlobals(lua_State *L, const char *word) // See the above. int LUA_WriteGlobals(lua_State *L, const char *word) { - if (fastcmp(word, "redscore")) - redscore = (UINT32)luaL_checkinteger(L, 2); - else if (fastcmp(word, "bluescore")) - bluescore = (UINT32)luaL_checkinteger(L, 2); - else if (fastcmp(word, "skincolor_redteam")) - skincolor_redteam = (UINT16)luaL_checkinteger(L, 2); - else if (fastcmp(word, "skincolor_blueteam")) - skincolor_blueteam = (UINT16)luaL_checkinteger(L, 2); - else if (fastcmp(word, "skincolor_redring")) - skincolor_redring = (UINT16)luaL_checkinteger(L, 2); - else if (fastcmp(word, "skincolor_bluering")) - skincolor_bluering = (UINT16)luaL_checkinteger(L, 2); - else if (fastcmp(word, "gravity")) + if (fastcmp(word, "gravity")) gravity = (fixed_t)luaL_checkinteger(L, 2); else if (fastcmp(word, "stoppedclock")) stoppedclock = luaL_checkboolean(L, 2); diff --git a/src/m_cheat.c b/src/m_cheat.c index 0635b1f6f..9b0e9ec20 100644 --- a/src/m_cheat.c +++ b/src/m_cheat.c @@ -290,6 +290,8 @@ struct debugFlagNames_s const debug_flag_names[] = {"PowerLevel", DBG_PWRLV}, // alt name {"Demo", DBG_DEMO}, {"Replay", DBG_DEMO}, // alt name + {"Teams", DBG_TEAMS}, + {"Teamplay", DBG_TEAMS}, // alt name {NULL, 0} }; diff --git a/src/m_random.h b/src/m_random.h index 6f57efd8a..412694cde 100644 --- a/src/m_random.h +++ b/src/m_random.h @@ -90,6 +90,7 @@ typedef enum PROLDDEMO, // The number of RNG classes in versions that didn't write down how many RNG classes they had in their replays. PR_ITEM_SPAWNER = PROLDDEMO, // Battle mode item spawners + PR_TEAMS, // Teamplay shuffling PRNUMSYNCED, diff --git a/src/menus/options-gameplay-1.c b/src/menus/options-gameplay-1.c index fc81174de..3ce2c6630 100644 --- a/src/menus/options-gameplay-1.c +++ b/src/menus/options-gameplay-1.c @@ -14,6 +14,14 @@ menuitem_t OPTIONS_Gameplay[] = { + {IT_HEADER, "Global...", NULL, + NULL, {NULL}, 0, 0}, + + {IT_STRING | IT_CVAR, "Teamplay", "Split the game between two teams!", + NULL, {.cvar = &cv_teamplay}, 0, 0}, + + {IT_STRING | IT_CVAR, "Frantic Items", "Make item odds crazier with more powerful items!", + NULL, {.cvar = &cv_kartfrantic}, 0, 0}, {IT_HEADER, "Race...", NULL, NULL, {NULL}, 0, 0}, @@ -21,9 +29,6 @@ menuitem_t OPTIONS_Gameplay[] = {IT_STRING | IT_CVAR, "Game Speed", "Gear for the next map.", NULL, {.cvar = &cv_kartspeed}, 0, 0}, - {IT_STRING | IT_CVAR, "Frantic Items", "Make item odds crazier with more powerful items!", - NULL, {.cvar = &cv_kartfrantic}, 0, 0}, - {IT_STRING | IT_CVAR, "Encore Mode", "Play in Encore Mode next map.", NULL, {.cvar = &cv_kartencore}, 0, 0}, diff --git a/src/menus/play-char-select.c b/src/menus/play-char-select.c index ed8606ff0..1358a2db9 100644 --- a/src/menus/play-char-select.c +++ b/src/menus/play-char-select.c @@ -753,6 +753,20 @@ static void M_HandleBackToChars(setup_player_t *p) static boolean M_HandleBeginningColors(setup_player_t *p) { p->mdepth = CSSTEP_COLORS; + + if (Playing() && G_GametypeHasTeams()) + { + size_t pnum = (p - setup_player); + if (pnum <= splitscreen) + { + if (players[g_localplayers[pnum]].team != TEAM_UNASSIGNED) + { + p->color = g_teaminfo[players[g_localplayers[pnum]].team].color; + return false; + } + } + } + M_NewPlayerColors(p); if (p->colors.listLen != 1) return true; diff --git a/src/menus/transient/pause-game.c b/src/menus/transient/pause-game.c index 7e283504e..fb684650f 100644 --- a/src/menus/transient/pause-game.c +++ b/src/menus/transient/pause-game.c @@ -11,6 +11,7 @@ /// \file menus/transient/pause-game.c /// \brief In-game/pause menus +#include "../../byteptr.h" #include "../../d_netcmd.h" #include "../../i_time.h" #include "../../k_menu.h" @@ -478,50 +479,33 @@ void M_HandleSpectateToggle(INT32 choice) return; } - boolean tospectator = false; + // Identify relevant spectator state of pausemenu.splitscreenfocusid. + // See also M_DrawPause. + + const UINT8 splitspecid = + g_localplayers[pausemenu.splitscreenfocusid]; + + const UINT8 joingame = ( + players[splitspecid].spectator == true + && ((players[splitspecid].pflags & PF_WANTSTOJOIN) == 0) + ) ? 1 : 0; + + if (joingame && !cv_allowteamchange.value) { - // Identify relevant spectator state of pausemenu.splitscreenfocusid. - // See also M_DrawPause. - - const UINT8 splitspecid = - g_localplayers[pausemenu.splitscreenfocusid]; - - tospectator = ( - players[splitspecid].spectator == false - || (players[splitspecid].pflags & PF_WANTSTOJOIN) - ); - } - - if (!tospectator && !cv_allowteamchange.value) - { - M_StartMessage("Team Change", M_GetText("The server is not allowing\nteam changes at this time.\n"), NULL, MM_NOTHING, NULL, NULL); + M_StartMessage("Joining Play", M_GetText("The server is not allowing\njoining play at this time.\n"), NULL, MM_NOTHING, NULL, NULL); return; } M_QuitPauseMenu(-1); - const char *destinationstate = tospectator ? "spectator" : "playing"; + // Send spectate + UINT8 buf[2]; + UINT8 *p = buf; - // These console command names... - if (pausemenu.splitscreenfocusid == 0) - { - COM_ImmedExecute( - va( - "changeteam %s", - destinationstate - ) - ); - } - else - { - COM_ImmedExecute( - va( - "changeteam%u %s", - pausemenu.splitscreenfocusid + 1, - destinationstate - ) - ); - } + WRITEUINT8(p, splitspecid); + WRITEUINT8(p, joingame); + + SendNetXCmd(XD_SPECTATE, &buf, p - buf); return; } diff --git a/src/objects/super-flicky.cpp b/src/objects/super-flicky.cpp index aa5e659f4..594fc760b 100644 --- a/src/objects/super-flicky.cpp +++ b/src/objects/super-flicky.cpp @@ -727,6 +727,12 @@ void Controller::search() continue; } + // Do not target someone on the same team as our owner. + if (G_SameTeam(player, source()->player) == true) + { + continue; + } + // Target is already being hunted. if (player->flickyAttacker) { diff --git a/src/p_enemy.c b/src/p_enemy.c index 0cfc0d5e5..13a7e5cda 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -5128,10 +5128,7 @@ void A_OldRingExplode(mobj_t *actor) { if (changecolor) { - if (!(gametyperules & GTR_TEAMS)) - mo->color = actor->target->color; //copy color - else if (actor->target->player->ctfteam == 2) - mo->color = skincolor_bluering; + P_ColorTeamMissile(mo, actor->target->player); } } @@ -5144,10 +5141,7 @@ void A_OldRingExplode(mobj_t *actor) { if (changecolor) { - if (!(gametyperules & GTR_TEAMS)) - mo->color = actor->target->color; //copy color - else if (actor->target->player->ctfteam == 2) - mo->color = skincolor_bluering; + P_ColorTeamMissile(mo, actor->target->player); } mo = P_SpawnMobj(actor->x, actor->y, actor->z, locvar1); @@ -5159,10 +5153,7 @@ void A_OldRingExplode(mobj_t *actor) { if (changecolor) { - if (!(gametyperules & GTR_TEAMS)) - mo->color = actor->target->color; //copy color - else if (actor->target->player->ctfteam == 2) - mo->color = skincolor_bluering; + P_ColorTeamMissile(mo, actor->target->player); } } diff --git a/src/p_inter.c b/src/p_inter.c index 01928424d..7cd45a40b 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -49,11 +49,6 @@ #include "m_easing.h" #include "k_hud.h" // K_AddMessage - -// CTF player names -#define CTFTEAMCODE(pl) pl->ctfteam ? (pl->ctfteam == 1 ? "\x85" : "\x84") : "" -#define CTFTEAMENDCODE(pl) pl->ctfteam ? "\x80" : "" - void P_ForceFeed(const player_t *player, INT32 attack, INT32 fade, tic_t duration, INT32 period) { BasicFF_t Basicfeed; @@ -1510,13 +1505,15 @@ void P_CheckPointLimit(void) return; // pointlimit is nonzero, check if it's been reached by this player - if (G_GametypeHasTeams()) + if (G_GametypeHasTeams() == true) { - // Just check both teams - if (g_pointlimit <= redscore || g_pointlimit <= bluescore) + for (i = 0; i < TEAM__MAX; i++) { - if (server) - SendNetXCmd(XD_EXITLEVEL, NULL, 0); + if (g_pointlimit <= g_teamscores[i]) + { + P_DoAllPlayersExit(0, false); + return; + } } } else @@ -1529,10 +1526,7 @@ void P_CheckPointLimit(void) if (g_pointlimit <= players[i].roundscore) { P_DoAllPlayersExit(0, false); - - /*if (server) - SendNetXCmd(XD_EXITLEVEL, NULL, 0);*/ - return; // good thing we're leaving the function immediately instead of letting the loop get mangled! + return; } } } @@ -2506,12 +2500,11 @@ static boolean P_PlayerHitsPlayer(mobj_t *target, mobj_t *inflictor, mobj_t *sou if (source == target) return false; - if (G_GametypeHasTeams()) - { - // Don't hurt your team, either! - if (source->player->ctfteam == target->player->ctfteam) - return false; - } +#if 0 + // Don't hurt your team, either! + if (G_SameTeam(source->player, target->player) == true) + return false; +#endif } return true; diff --git a/src/p_mobj.c b/src/p_mobj.c index 89deadbaf..e6b683fc3 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -12168,34 +12168,13 @@ void P_SpawnPlayer(INT32 playernum) G_PlayerReborn(playernum, false); } - if (G_GametypeHasTeams()) + if (G_GametypeHasTeams() == true) { // If you're in a team game and you don't have a team assigned yet... - if (!p->spectator && p->ctfteam == 0) + if (p->spectator == false && p->team == TEAM_UNASSIGNED) { - changeteam_union NetPacket; - UINT16 usvalue; - NetPacket.value.l = NetPacket.value.b = 0; - - // Spawn as a spectator, - // yes even in splitscreen mode - p->spectator = true; - - // but immediately send a team change packet. - NetPacket.packet.playernum = playernum; - NetPacket.packet.verification = true; - NetPacket.packet.newteam = !(playernum&1) + 1; - - usvalue = SHORT(NetPacket.value.l|NetPacket.value.b); - SendNetXCmd(XD_TEAMCHANGE, &usvalue, sizeof(usvalue)); + G_AssignTeam(p, !(playernum & 1) + 1); } - - // Fix team colors. - // This code isn't being done right somewhere else. Oh well. - if (p->ctfteam == 1) - p->skincolor = skincolor_redteam; - else if (p->ctfteam == 2) - p->skincolor = skincolor_blueteam; } if (leveltime > introtime && K_PodiumSequence() == false) @@ -12605,23 +12584,23 @@ static boolean P_SpawnNonMobjMapThing(mapthing_t *mthing) } return true; } - else if (mthing->type == 34) // Red CTF starts + else if (mthing->type == 34) // Orange team starts { - if (numredctfstarts < MAXPLAYERS) + if (numteamstarts[TEAM_ORANGE] < MAXPLAYERS) { - redctfstarts[numredctfstarts] = mthing; + teamstarts[TEAM_ORANGE][numteamstarts[TEAM_ORANGE]] = mthing; mthing->type = 0; - numredctfstarts++; + numteamstarts[TEAM_ORANGE]++; } return true; } - else if (mthing->type == 35) // Blue CTF starts + else if (mthing->type == 35) // Blue team starts { - if (numbluectfstarts < MAXPLAYERS) + if (numteamstarts[TEAM_BLUE] < MAXPLAYERS) { - bluectfstarts[numbluectfstarts] = mthing; + teamstarts[TEAM_BLUE][numteamstarts[TEAM_BLUE]] = mthing; mthing->type = 0; - numbluectfstarts++; + numteamstarts[TEAM_BLUE]++; } return true; } @@ -14909,17 +14888,19 @@ mobj_t *P_SpawnMissile(mobj_t *source, mobj_t *dest, mobjtype_t type) // void P_ColorTeamMissile(mobj_t *missile, player_t *source) { - if (G_GametypeHasTeams()) + if (missile == NULL || source == NULL) { - if (source->ctfteam == 2) - missile->color = skincolor_bluering; - else if (source->ctfteam == 1) - missile->color = skincolor_redring; + return; + } + + if (source->team > TEAM_UNASSIGNED && source->team < TEAM__MAX) + { + missile->color = g_teaminfo[source->team].color; } - /* else - missile->color = player->mo->color; //copy color - */ + { + missile->color = source->skincolor; + } } // diff --git a/src/p_saveg.c b/src/p_saveg.c index 8497b4506..f7e4b53ae 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -247,7 +247,7 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEUINT16(save->p, players[i].flashpal); WRITEUINT16(save->p, players[i].flashcount); - WRITEUINT8(save->p, players[i].skincolor); + WRITEUINT16(save->p, players[i].skincolor); WRITEINT32(save->p, players[i].skin); for (j = 0; j < MAXAVAILABILITY; j++) @@ -257,6 +257,12 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEUINT8(save->p, players[i].fakeskin); WRITEUINT8(save->p, players[i].lastfakeskin); + + WRITEUINT16(save->p, players[i].prefcolor); + WRITEINT32(save->p, players[i].prefskin); + WRITEUINT16(save->p, players[i].preffollowercolor); + WRITEINT32(save->p, players[i].preffollower); + WRITEUINT32(save->p, players[i].score); WRITESINT8(save->p, players[i].lives); WRITESINT8(save->p, players[i].xtralife); @@ -287,7 +293,7 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEINT32(save->p, players[i].cheatchecknum); WRITEINT32(save->p, players[i].checkpointId); - WRITEUINT8(save->p, players[i].ctfteam); + WRITEUINT8(save->p, players[i].team); WRITEUINT8(save->p, players[i].checkskip); @@ -424,6 +430,8 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEUINT8(save->p, players[i].position); WRITEUINT8(save->p, players[i].oldposition); WRITEUINT8(save->p, players[i].positiondelay); + WRITEUINT8(save->p, players[i].teamposition); + WRITEUINT8(save->p, players[i].teamimportance); WRITEUINT32(save->p, players[i].distancetofinish); WRITEUINT32(save->p, players[i].distancetofinishprev); WRITEUINT32(save->p, players[i].lastpickupdistance); @@ -921,7 +929,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].flashpal = READUINT16(save->p); players[i].flashcount = READUINT16(save->p); - players[i].skincolor = READUINT8(save->p); + players[i].skincolor = READUINT16(save->p); players[i].skin = READINT32(save->p); for (j = 0; j < MAXAVAILABILITY; j++) @@ -931,6 +939,12 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].fakeskin = READUINT8(save->p); players[i].lastfakeskin = READUINT8(save->p); + + players[i].prefcolor = READUINT16(save->p); + players[i].prefskin = READINT32(save->p); + players[i].preffollowercolor = READUINT16(save->p); + players[i].preffollower = READINT32(save->p); + players[i].score = READUINT32(save->p); players[i].lives = READSINT8(save->p); players[i].xtralife = READSINT8(save->p); // Ring Extra Life counter @@ -961,7 +975,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].cheatchecknum = READINT32(save->p); players[i].checkpointId = READINT32(save->p); - players[i].ctfteam = READUINT8(save->p); // 1 == Red, 2 == Blue + players[i].team = READUINT8(save->p); players[i].checkskip = READUINT8(save->p); @@ -1051,6 +1065,8 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].position = READUINT8(save->p); players[i].oldposition = READUINT8(save->p); players[i].positiondelay = READUINT8(save->p); + players[i].teamposition = READUINT8(save->p); + players[i].teamimportance = READUINT8(save->p); players[i].distancetofinish = READUINT32(save->p); players[i].distancetofinishprev = READUINT32(save->p); players[i].lastpickupdistance = READUINT32(save->p); @@ -6610,28 +6626,13 @@ static void P_NetArchiveMisc(savebuffer_t *save, boolean resending) WRITEUINT8(save->p, globools); } - WRITEUINT32(save->p, bluescore); - WRITEUINT32(save->p, redscore); - - WRITEUINT16(save->p, skincolor_redteam); - WRITEUINT16(save->p, skincolor_blueteam); - WRITEUINT16(save->p, skincolor_redring); - WRITEUINT16(save->p, skincolor_bluering); + for (i = 0; i < TEAM__MAX; i++) + { + WRITEUINT32(save->p, g_teamscores[i]); + } WRITEINT32(save->p, modulothing); - WRITEINT16(save->p, autobalance); - WRITEINT16(save->p, teamscramble); - - for (i = 0; i < MAXPLAYERS; i++) - WRITEINT16(save->p, scrambleplayers[i]); - - for (i = 0; i < MAXPLAYERS; i++) - WRITEINT16(save->p, scrambleteams[i]); - - WRITEINT16(save->p, scrambletotal); - WRITEINT16(save->p, scramblecount); - WRITEUINT32(save->p, racecountdown); WRITEUINT32(save->p, exitcountdown); @@ -6654,6 +6655,7 @@ static void P_NetArchiveMisc(savebuffer_t *save, boolean resending) WRITEUINT8(save->p, gamespeed); WRITEUINT8(save->p, numlaps); WRITEUINT8(save->p, franticitems); + WRITEUINT8(save->p, g_teamplay); WRITESINT8(save->p, speedscramble); WRITESINT8(save->p, encorescramble); @@ -6816,28 +6818,13 @@ static boolean P_NetUnArchiveMisc(savebuffer_t *save, boolean reloading) stoppedclock = !!(globools & (1<<1)); } - bluescore = READUINT32(save->p); - redscore = READUINT32(save->p); - - skincolor_redteam = READUINT16(save->p); - skincolor_blueteam = READUINT16(save->p); - skincolor_redring = READUINT16(save->p); - skincolor_bluering = READUINT16(save->p); + for (i = 0; i < TEAM__MAX; i++) + { + g_teamscores[i] = READUINT32(save->p); + } modulothing = READINT32(save->p); - autobalance = READINT16(save->p); - teamscramble = READINT16(save->p); - - for (i = 0; i < MAXPLAYERS; i++) - scrambleplayers[i] = READINT16(save->p); - - for (i = 0; i < MAXPLAYERS; i++) - scrambleteams[i] = READINT16(save->p); - - scrambletotal = READINT16(save->p); - scramblecount = READINT16(save->p); - racecountdown = READUINT32(save->p); exitcountdown = READUINT32(save->p); @@ -6859,6 +6846,7 @@ static boolean P_NetUnArchiveMisc(savebuffer_t *save, boolean reloading) gamespeed = READUINT8(save->p); numlaps = READUINT8(save->p); franticitems = (boolean)READUINT8(save->p); + g_teamplay = (boolean)READUINT8(save->p); speedscramble = READSINT8(save->p); encorescramble = READSINT8(save->p); diff --git a/src/p_setup.cpp b/src/p_setup.cpp index e719c755a..5ae710677 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -196,13 +196,12 @@ precipmobj_t **precipblocklinks; UINT8 *rejectmatrix; // Maintain single and multi player starting spots. -INT32 numdmstarts, numcoopstarts, numredctfstarts, numbluectfstarts; +INT32 numdmstarts, numcoopstarts, numteamstarts[TEAM__MAX]; INT32 numfaultstarts; mapthing_t *deathmatchstarts[MAX_DM_STARTS]; mapthing_t *playerstarts[MAXPLAYERS]; -mapthing_t *bluectfstarts[MAXPLAYERS]; -mapthing_t *redctfstarts[MAXPLAYERS]; +mapthing_t *teamstarts[TEAM__MAX][MAXPLAYERS]; mapthing_t *faultstart; // Global state for PartialAddWadFile/MultiSetupWadFiles @@ -5508,7 +5507,7 @@ static void P_ConvertBinaryLinedefTypes(void) lines[i].args[0] = (lines[i].flags & ML_NOTBOUNCY) ? TMT_EACHTIMEENTERANDEXIT : TMT_EACHTIMEENTER; else lines[i].args[0] = TMT_CONTINUOUS; - lines[i].args[1] = (lines[i].special > 310) ? TMT_BLUE : TMT_RED; + lines[i].args[1] = (lines[i].special > 310) ? TMT_BLUE : TMT_ORANGE; lines[i].special = 309; break; case 313: //No more enemies - once @@ -7684,6 +7683,7 @@ static void P_InitLevelSettings(void) const boolean multi_speed = (gametypes[gametype]->speed == KARTSPEED_AUTO); gamespeed = multi_speed ? KARTSPEED_EASY : gametypes[gametype]->speed; franticitems = false; + g_teamplay = false; if (K_PodiumSequence() == true) { @@ -7730,6 +7730,7 @@ static void P_InitLevelSettings(void) gamespeed = (UINT8)cv_kartspeed.value; } franticitems = (boolean)cv_kartfrantic.value; + g_teamplay = (boolean)cv_teamplay.value; // we will overwrite this later if there is not enough players } memset(&battleovertime, 0, sizeof(struct battleovertime)); @@ -7780,16 +7781,12 @@ void P_RespawnThings(void) static void P_ResetSpawnpoints(void) { - UINT8 i; - - numdmstarts = numredctfstarts = numbluectfstarts = 0; - numfaultstarts = 0; - faultstart = NULL; + UINT8 i, j; // reset the player starts for (i = 0; i < MAXPLAYERS; i++) { - playerstarts[i] = bluectfstarts[i] = redctfstarts[i] = NULL; + playerstarts[i] = NULL; if (playeringame[i]) { @@ -7798,9 +7795,23 @@ static void P_ResetSpawnpoints(void) } } + numfaultstarts = 0; + faultstart = NULL; + + numdmstarts = 0; for (i = 0; i < MAX_DM_STARTS; i++) deathmatchstarts[i] = NULL; + for (i = 0; i < TEAM__MAX; i++) + { + numteamstarts[i] = 0; + + for (j = 0; j < MAXPLAYERS; j++) + { + teamstarts[i][j] = NULL; + } + } + for (i = 0; i < 16; i++) skyboxviewpnts[i] = skyboxcenterpnts[i] = NULL; } @@ -7971,6 +7982,75 @@ static void P_InitCamera(void) } } +static void P_ShuffleTeams(void) +{ + size_t i; + + if (G_GametypeHasTeams() == false) + { + // Teams are not enabled, force to TEAM_UNASSIGNED + + for (i = 0; i < MAXPLAYERS; i++) + { + players[i].team = TEAM_UNASSIGNED; + } + + return; + } + + // The following will sort TEAM_UNASSIGNED players at random. + // In addition, you should know all players will have their team + // unset every round unless certain conditions are met. + // See Y_MidIntermission, G_InitNew (where resetplayer == true) + + CONS_Debug(DBG_TEAMS, "Shuffling player teams...\n"); + + std::vector player_shuffle; + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] == false || players[i].spectator == true) + { + continue; + } + player_shuffle.push_back(i); + } + + size_t n = player_shuffle.size(); + if (inDuel == true || n <= 2) // cv_teamplay_min.value + { + CONS_Debug(DBG_TEAMS, "Not enough players to support teams; forcing teamplay preference off.\n"); + + // Not enough players for teams. + // Turn off the preference for this match. + g_teamplay = false; + + // But we may still be in a forced + // teams gametype, so only return false + // if our preference means anything. + if (G_GametypeHasTeams() == false) + { + return; + } + } + + if (n > 1) + { + for (i = n - 1; i > 0; i--) + { + size_t j = P_RandomKey(PR_TEAMS, i + 1); + + size_t temp = player_shuffle[i]; + player_shuffle[i] = player_shuffle[j]; + player_shuffle[j] = temp; + } + } + + for (i = 0; i < n; i++) + { + G_AutoAssignTeam(&players[ player_shuffle[i] ]); + } +} + static void P_InitPlayers(void) { INT32 i, skin = -1, follower = -1; @@ -7978,6 +8058,9 @@ static void P_InitPlayers(void) // Make sure objectplace is OFF when you first start the level! OP_ResetObjectplace(); + // Update skins / colors between levels. + G_UpdateAllPlayerPreferences(); + // Are we forcing a character? if (gametype == GT_TUTORIAL) { @@ -8900,6 +8983,8 @@ void P_PostLoadLevel(void) } } + P_ShuffleTeams(); + K_TimerInit(); P_InitPlayers(); diff --git a/src/p_setup.h b/src/p_setup.h index 012094ea9..e0ffdb23b 100644 --- a/src/p_setup.h +++ b/src/p_setup.h @@ -30,7 +30,7 @@ extern unsigned char mapmd5[16]; // Player spawn spots for deathmatch. #define MAX_DM_STARTS 64 extern mapthing_t *deathmatchstarts[MAX_DM_STARTS]; -extern INT32 numdmstarts, numcoopstarts, numredctfstarts, numbluectfstarts, numfaultstarts; +extern INT32 numdmstarts, numcoopstarts, numteamstarts[TEAM__MAX], numfaultstarts; extern boolean levelloading; extern boolean g_reloadinggamestate; diff --git a/src/p_spec.c b/src/p_spec.c index 220954e82..9aec8ed53 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -1535,7 +1535,7 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller // Only red/blue team members can activate this. if (!(actor && actor->player)) return false; - if (actor->player->ctfteam != ((triggerline->args[1] == TMT_RED) ? 1 : 2)) + if (actor->player->team != ((triggerline->args[1] == TMT_ORANGE) ? TEAM_ORANGE : TEAM_BLUE)) return false; break; case 314: diff --git a/src/p_spec.h b/src/p_spec.h index 39ec0752c..e7020d6f0 100644 --- a/src/p_spec.h +++ b/src/p_spec.h @@ -289,7 +289,7 @@ typedef enum typedef enum { - TMT_RED = 0, + TMT_ORANGE = 0, TMT_BLUE = 1, } textmapteam_t; diff --git a/src/p_tick.c b/src/p_tick.c index 796724e5b..0860abb21 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -585,146 +585,6 @@ static void P_RunThinkers(void) ps_acs_time = I_GetPreciseTime() - ps_acs_time; } -// -// P_DoAutobalanceTeams() -// -// Determine if the teams are unbalanced, and if so, move a player to the other team. -// -static void P_DoAutobalanceTeams(void) -{ - changeteam_union NetPacket; - UINT16 usvalue; - INT32 i=0; - INT32 red=0, blue=0; - INT32 redarray[MAXPLAYERS], bluearray[MAXPLAYERS]; - //INT32 redflagcarrier = 0, blueflagcarrier = 0; - INT32 totalred = 0, totalblue = 0; - - NetPacket.value.l = NetPacket.value.b = 0; - memset(redarray, 0, sizeof(redarray)); - memset(bluearray, 0, sizeof(bluearray)); - - // Only do it if we have enough room in the net buffer to send it. - // Otherwise, come back next time and try again. - if (sizeof(usvalue) > GetFreeXCmdSize(0)) - return; - - //We have to store the players in an array with the rest of their team. - //We can then pick a random player to be forced to change teams. - for (i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i] && players[i].ctfteam) - { - if (players[i].ctfteam == 1) - { - //if (!players[i].gotflag) - { - redarray[red] = i; //store the player's node. - red++; - } - /*else - redflagcarrier++;*/ - } - else - { - //if (!players[i].gotflag) - { - bluearray[blue] = i; //store the player's node. - blue++; - } - /*else - blueflagcarrier++;*/ - } - } - } - - totalred = red;// + redflagcarrier; - totalblue = blue;// + blueflagcarrier; - - if ((abs(totalred - totalblue) > max(1, (totalred + totalblue) / 8))) - { - if (totalred > totalblue) - { - i = M_RandomKey(red); - NetPacket.packet.newteam = 2; - NetPacket.packet.playernum = redarray[i]; - NetPacket.packet.verification = true; - NetPacket.packet.autobalance = true; - - usvalue = SHORT(NetPacket.value.l|NetPacket.value.b); - SendNetXCmd(XD_TEAMCHANGE, &usvalue, sizeof(usvalue)); - } - else //if (totalblue > totalred) - { - i = M_RandomKey(blue); - NetPacket.packet.newteam = 1; - NetPacket.packet.playernum = bluearray[i]; - NetPacket.packet.verification = true; - NetPacket.packet.autobalance = true; - - usvalue = SHORT(NetPacket.value.l|NetPacket.value.b); - SendNetXCmd(XD_TEAMCHANGE, &usvalue, sizeof(usvalue)); - } - } -} - -// -// P_DoTeamscrambling() -// -// If a team scramble has been started, scramble one person from the -// pre-made scramble array. Said array is created in TeamScramble_OnChange() -// -void P_DoTeamscrambling(void) -{ - changeteam_union NetPacket; - UINT16 usvalue; - NetPacket.value.l = NetPacket.value.b = 0; - - // Only do it if we have enough room in the net buffer to send it. - // Otherwise, come back next time and try again. - if (sizeof(usvalue) > GetFreeXCmdSize(0)) - return; - - if (scramblecount < scrambletotal) - { - if (players[scrambleplayers[scramblecount]].ctfteam != scrambleteams[scramblecount]) - { - NetPacket.packet.newteam = scrambleteams[scramblecount]; - NetPacket.packet.playernum = scrambleplayers[scramblecount]; - NetPacket.packet.verification = true; - NetPacket.packet.scrambled = true; - - usvalue = SHORT(NetPacket.value.l|NetPacket.value.b); - SendNetXCmd(XD_TEAMCHANGE, &usvalue, sizeof(usvalue)); - } - - scramblecount++; //Increment, and get to the next player when we come back here next time. - } - else - CV_SetValue(&cv_teamscramble, 0); -} - -static inline void P_DoTeamStuff(void) -{ - // Automatic team balance for CTF and team match - if (leveltime % (TICRATE * 5) == 0) //only check once per five seconds for the sake of CPU conservation. - { - // Do not attempt to autobalance and scramble teams at the same time. - // Only the server should execute this. No verified admins, please. - if ((cv_autobalance.value && !cv_teamscramble.value) && cv_allowteamchange.value && server) - P_DoAutobalanceTeams(); - } - - // Team scramble code for team match and CTF. - if ((leveltime % (TICRATE/7)) == 0) - { - // If we run out of time in the level, the beauty is that - // the Y_Ticker() team scramble code will pick it up. - if (cv_teamscramble.value && server) - P_DoTeamscrambling(); - } -} - static inline void P_DeviceRumbleTick(void) { UINT8 i; @@ -1225,9 +1085,6 @@ void P_Ticker(boolean run) timeinmap++; } - if (G_GametypeHasTeams()) - P_DoTeamStuff(); - if (run) { if (racecountdown > 1) diff --git a/src/p_user.c b/src/p_user.c index c39b105bd..478503157 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -73,6 +73,7 @@ #include "k_hud.h" // K_AddMessage #include "m_easing.h" #include "acs/interface.h" +#include "byteptr.h" #ifdef HWRENDER #include "hardware/hw_light.h" @@ -514,25 +515,44 @@ void P_GivePlayerLives(player_t *player, INT32 numlives) // Adds to the player's score void P_AddPlayerScore(player_t *player, INT32 amount) { - if (!((gametyperules & GTR_POINTLIMIT))) + if ((gametyperules & GTR_POINTLIMIT) == 0) + { return; + } if (player->exiting) // srb2kart + { return; + } + + const boolean teams = G_GametypeHasTeams(); // Don't underflow. // Don't go above MAXSCORE. if (amount < 0 && (UINT32)-amount > player->roundscore) + { player->roundscore = 0; + } else if (player->roundscore + amount < MAXSCORE) { - if (player->roundscore < g_pointlimit && g_pointlimit <= player->roundscore + amount) + if (player->roundscore < g_pointlimit + && g_pointlimit <= player->roundscore + amount + && teams == false) // We want the normal scoring function to update roundscore, but this notification will be done by G_AddTeamScore. + { HU_DoTitlecardCEchoForDuration(player, "K.O. READY!", true, 5*TICRATE/2); + } player->roundscore += amount; } else + { player->roundscore = MAXSCORE; + } + + if (teams == true) + { + G_AddTeamScore(player->team, amount, player); + } } void P_PlayRinglossSound(mobj_t *source) @@ -3742,49 +3762,9 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall boolean P_SpectatorJoinGame(player_t *player) { - INT32 changeto = 0; const char *text = NULL; - // Team changing isn't allowed. - if (!cv_allowteamchange.value) - return false; - - // Team changing in Team Match and CTF - // Pressing fire assigns you to a team that needs players if allowed. - // Partial code reproduction from p_tick.c autobalance code. - // a surprise tool that will help us later... - if (G_GametypeHasTeams() && player->ctfteam == 0) - { - INT32 z, numplayersred = 0, numplayersblue = 0; - - //find a team by num players, score, or random if all else fails. - for (z = 0; z < MAXPLAYERS; ++z) - if (playeringame[z]) - { - if (players[z].ctfteam == 1) - ++numplayersred; - else if (players[z].ctfteam == 2) - ++numplayersblue; - } - // for z - - if (numplayersblue > numplayersred) - changeto = 1; - else if (numplayersred > numplayersblue) - changeto = 2; - else if (bluescore > redscore) - changeto = 1; - else if (redscore > bluescore) - changeto = 2; - else - changeto = (P_RandomFixed(PR_RULESCRAMBLE) & 1) + 1; - - if (!LUA_HookTeamSwitch(player, changeto, true, false, false)) - return false; - } - // no conditions that could cause the gamejoin to fail below this line - if (player->mo) { P_RemoveMobj(player->mo); @@ -3793,7 +3773,7 @@ boolean P_SpectatorJoinGame(player_t *player) player->spectator = false; player->pflags &= ~PF_WANTSTOJOIN; player->spectatewait = 0; - player->ctfteam = changeto; + player->team = TEAM_UNASSIGNED; // We will auto-assign later. player->playerstate = PST_REBORN; player->enteredGame = true; @@ -3805,12 +3785,7 @@ boolean P_SpectatorJoinGame(player_t *player) } // a surprise tool that will help us later... - if (changeto == 1) - text = va("\x82*%s switched to the %c%s%c team.\n", player_names[player-players], '\x85', "RED", '\x82'); - else if (changeto == 2) - text = va("\x82*%s switched to the %c%s%c team.\n", player_names[player-players], '\x85', "BLU", '\x82'); - else - text = va("\x82*%s entered the game.", player_names[player-players]); + text = va("\x82*%s entered the game.", player_names[player-players]); HU_AddChatText(text, false); return true; // no more player->mo, cannot continue. @@ -4891,16 +4866,13 @@ void P_CheckRaceGriefing(player_t *player, boolean dopunishment) else { // Send spectate - changeteam_union NetPacket; - UINT16 usvalue; + UINT8 buf[2]; + UINT8 *p = buf; - NetPacket.value.l = NetPacket.value.b = 0; - NetPacket.packet.newteam = 0; - NetPacket.packet.playernum = n; - NetPacket.packet.verification = true; + WRITEUINT8(p, n); + WRITEUINT8(p, 0); - usvalue = SHORT(NetPacket.value.l|NetPacket.value.b); - SendNetXCmd(XD_TEAMCHANGE, &usvalue, sizeof(usvalue)); + SendNetXCmd(XD_SPECTATE, &buf, p - buf); } } } diff --git a/src/r_skins.c b/src/r_skins.c index fb184172f..2cb8793a8 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -408,22 +408,6 @@ static void SetSkin(player_t *player, INT32 skinnum) player->kartweight = skin->kartweight; player->charflags = skin->flags; -#if 0 - if (!CV_CheatsEnabled() && !(netgame || multiplayer || demo.playback)) - { - for (i = 0; i <= r_splitscreen; i++) - { - if (playernum == g_localplayers[i]) - { - CV_StealthSetValue(&cv_playercolor[i], skin->prefcolor); - } - } - - player->skincolor = skin->prefcolor; - K_KartResetPlayerColor(player); - } -#endif - if (player->followmobj) { P_RemoveMobj(player->followmobj); diff --git a/src/st_stuff.c b/src/st_stuff.c index 5531316cd..f81aa535f 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -96,13 +96,11 @@ boolean ST_SameTeam(player_t *a, player_t *b) if (G_GametypeHasTeams() == true) { // You get team messages if you're on the same team. - return (a->ctfteam == b->ctfteam); - } - else - { - // Not that everyone's not on the same team, but team messages go to normal chat if everyone's not in the same team. - return true; + return (a->team == b->team); } + + // Not that everyone's not on the same team, but team messages go to normal chat if everyone's not in the same team. + return true; } static boolean st_stopped = true; diff --git a/src/typedef.h b/src/typedef.h index 31fe9f698..44341bd6a 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -144,6 +144,7 @@ TYPEDEF (unloaded_cupheader_t); TYPEDEF (exitcondition_t); TYPEDEF (darkness_t); TYPEDEF (musicfade_t); +TYPEDEF (teaminfo_t); // font.h TYPEDEF (font_t); diff --git a/src/y_inter.cpp b/src/y_inter.cpp index b48828395..f4c809e1a 100644 --- a/src/y_inter.cpp +++ b/src/y_inter.cpp @@ -103,6 +103,11 @@ static boolean Y_CanSkipIntermission(void) return false; } +boolean Y_IntermissionPlayerLock(void) +{ + return (gamestate == GS_INTERMISSION && data.rankingsmode == false); +} + static void Y_UnloadData(void); // @@ -191,8 +196,6 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32)) numplayersingame++; } - memset(data.color, 0, sizeof (data.color)); - memset(data.character, 0, sizeof (data.character)); memset(completed, 0, sizeof (completed)); data.numplayers = 0; data.showroundnum = false; @@ -202,9 +205,63 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32)) srb2::StandingsJson standings {}; bool savestandings = (!rankingsmode && demo.recording); + // Team stratification (this code only barely supports more than 2 teams) + data.winningteam = TEAM_UNASSIGNED; + data.halfway = UINT8_MAX; + + UINT8 countteam[TEAM__MAX]; + UINT8 smallestteam = UINT8_MAX; + memset(countteam, 0, sizeof(countteam)); + + if (rankingsmode == 0 && G_GametypeHasTeams()) + { + for (i = data.winningteam+1; i < TEAM__MAX; i++) + { + countteam[i] = G_CountTeam(i); + + if (g_teamscores[data.winningteam] < g_teamscores[i]) + { + data.winningteam = i; + } + + if (smallestteam > countteam[i]) + { + smallestteam = countteam[i]; + } + } + + if (countteam[data.winningteam]) + { + data.halfway = countteam[data.winningteam] - 1; + } + } + for (j = 0; j < numplayersingame; j++) { - for (i = 0; i < MAXPLAYERS; i++) + i = 0; + + if (data.winningteam != TEAM_UNASSIGNED) + { + for (; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator || completed[i]) + continue; + + if (players[i].team != data.winningteam) + continue; + + comparison(i); + } + + if (data.val[data.numplayers] == UINT32_MAX) + { + // Only run the un-teamed loop if everybody + // on the winning team was previously placed + i = 0; + } + } + + for (; i < MAXPLAYERS; i++) { if (!playeringame[i] || players[i].spectator || completed[i]) continue; @@ -217,9 +274,6 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32)) completed[i] = true; data.grade[i] = K_PlayerTallyActive(&players[i]) ? players[i].tally.rank : GRADE_INVALID; - data.color[data.numplayers] = players[i].skincolor; - data.character[data.numplayers] = players[i].skin; - if (data.numplayers && (data.val[data.numplayers] == data.val[data.numplayers-1])) { data.pos[data.numplayers] = data.pos[data.numplayers-1]; @@ -235,13 +289,33 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32)) if (!rankingsmode) { - if ((powertype == PWRLV_DISABLED) - && !(players[i].pflags & PF_NOCONTEST) - && (data.pos[data.numplayers] < (numplayersingame + spectateGriefed))) + // Online rank is handled further below in this file. + if (powertype == PWRLV_DISABLED) { - // Online rank is handled further below in this file. - data.increase[i] = K_CalculateGPRankPoints(data.pos[data.numplayers], numplayersingame + spectateGriefed); - players[i].score += data.increase[i]; + if (data.winningteam != TEAM_UNASSIGNED) + { + // TODO ASK TYRON + if (smallestteam != 0 + && players[i].team == data.winningteam) + { + data.increase[i] = 1; + } + } + else + { + UINT8 pointgetters = numplayersingame + spectateGriefed; + + if (data.pos[data.numplayers] < pointgetters + && !(players[i].pflags & PF_NOCONTEST)) + { + data.increase[i] = K_CalculateGPRankPoints(data.pos[data.numplayers], pointgetters); + } + } + + if (data.increase[i] > 0) + { + players[i].score += data.increase[i]; + } } if (savestandings) @@ -249,8 +323,8 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32)) srb2::StandingJson standing {}; standing.ranking = data.pos[data.numplayers]; standing.name = std::string(player_names[i]); - standing.demoskin = data.character[data.numplayers]; - standing.skincolor = std::string(skincolors[data.color[data.numplayers]].name); + standing.demoskin = players[i].skin; + standing.skincolor = std::string(skincolors[players[i].skincolor].name); standing.timeorscore = data.val[data.numplayers]; standings.standings.emplace_back(std::move(standing)); } @@ -290,6 +364,14 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32)) data.numplayers++; } + if (data.numplayers <= 2 + || data.halfway == UINT8_MAX + || data.halfway >= 8 + || (data.numplayers - data.halfway) >= 8) + { + data.halfway = (data.numplayers-1)/2; + } + if (savestandings) { srb2::write_current_demo_end_marker(); @@ -386,7 +468,19 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32)) { data.mainplayer = i; - if (!(players[i].pflags & PF_NOCONTEST)) + if (data.winningteam != TEAM_UNASSIGNED + && players[i].team != TEAM_UNASSIGNED) + { + data.gotthrough = true; + + snprintf(data.headerstring, + sizeof data.headerstring, + "%s TEAM", + g_teaminfo[players[i].team].name); + + data.showroundnum = true; + } + else if (!(players[i].pflags & PF_NOCONTEST)) { data.gotthrough = true; @@ -506,11 +600,13 @@ void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset) x2 -= 9; } - if (standings->numplayers > 10) + UINT8 halfway = standings->halfway; + + if (halfway > 4) { yspacing--; } - else if (standings->numplayers <= 6) + else if (halfway <= 2) { yspacing++; if (verticalresults) @@ -545,7 +641,7 @@ void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset) ); i = 0; - UINT8 halfway = (standings->numplayers-1)/2; + if (doreverse) { i = standings->numplayers-1; @@ -563,13 +659,13 @@ void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset) else { UINT8 *charcolormap = NULL; - if (!R_CanShowSkinInDemo(standings->character[i])) + if (!R_CanShowSkinInDemo(players[pnum].skin)) { - charcolormap = R_GetTranslationColormap(TC_BLINK, static_cast(standings->color[i]), GTC_CACHE); + charcolormap = R_GetTranslationColormap(TC_BLINK, static_cast(players[pnum].skincolor), GTC_CACHE); } - else if (standings->color[i] != SKINCOLOR_NONE) + else { - charcolormap = R_GetTranslationColormap(standings->character[i], static_cast(standings->color[i]), GTC_CACHE); + charcolormap = R_GetTranslationColormap(players[pnum].skin, static_cast(players[pnum].skincolor), GTC_CACHE); } if (standings->isduel) @@ -588,7 +684,7 @@ void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset) M_DrawCharacterSprite( duelx + 40, duely + 78, - standings->character[i], + players[pnum].skin, spr2, (datarightofcolumn ? 1 : 7), 0, @@ -640,7 +736,7 @@ void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset) V_DrawRightAlignedThinString(x+13, y-2, 0, va("%d", standings->pos[i])); - if (standings->color[i] != SKINCOLOR_NONE) + //if (players[pnum].skincolor != SKINCOLOR_NONE) { if ((players[pnum].pflags & PF_NOCONTEST) && players[pnum].bot) { @@ -649,15 +745,15 @@ void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset) x+14, y-5, 0, static_cast(W_CachePatchName("MINIDEAD", PU_CACHE)), - R_GetTranslationColormap(TC_DEFAULT, static_cast(standings->color[i]), GTC_CACHE) + R_GetTranslationColormap(TC_DEFAULT, static_cast(players[pnum].skincolor), GTC_CACHE) ); } else { - charcolormap = R_GetTranslationColormap(standings->character[i], static_cast(standings->color[i]), GTC_CACHE); + charcolormap = R_GetTranslationColormap(players[pnum].skin, static_cast(players[pnum].skincolor), GTC_CACHE); V_DrawMappedPatch(x+14, y-5, 0, - R_CanShowSkinInDemo(standings->character[i]) ? - faceprefix[standings->character[i]][FACE_MINIMAP] : kp_unknownminimap, + R_CanShowSkinInDemo(players[pnum].skin) ? + faceprefix[players[pnum].skin][FACE_MINIMAP] : kp_unknownminimap, charcolormap); } } @@ -1820,13 +1916,15 @@ void Y_Ticker(void) // Team scramble code for team match and CTF. // Don't do this if we're going to automatically scramble teams next round. - /*if (G_GametypeHasTeams() && cv_teamscramble.value && !cv_scrambleonchange.value && server) + /* + if (G_GametypeHasTeams() && cv_teamscramble.value && !cv_scrambleonchange.value && server) { // If we run out of time in intermission, the beauty is that // the P_Ticker() team scramble code will pick it up. if ((intertic % (TICRATE/7)) == 0) P_DoTeamscrambling(); - }*/ + } + */ if ((timer < INFINITE_TIMER && --timer <= 0) || (intertic == endtic)) @@ -1892,8 +1990,7 @@ void Y_Ticker(void) { if (!data.rankingsmode && sorttic != -1 && (intertic >= sorttic + 8)) { - // Anything with post-intermission consequences here should also occur in Y_EndIntermission. - K_RetireBots(); + Y_MidIntermission(); Y_CalculateMatchData(1, Y_CompareRank); } @@ -2345,6 +2442,27 @@ void Y_StartIntermission(void) // ====== +// +// Y_MidIntermission +// +void Y_MidIntermission(void) +{ + // Replacing bots that fail out of play + K_RetireBots(); + + // If tournament play is not in action... + if (roundqueue.position == 0) + { + // Unset player teams in anticipation of P_ShuffleTeams + + UINT8 i; + for (i = 0; i < MAXPLAYERS; i++) + { + players[i].team = TEAM_UNASSIGNED; + } + } +} + // // Y_EndIntermission // @@ -2352,7 +2470,7 @@ void Y_EndIntermission(void) { if (!data.rankingsmode) { - K_RetireBots(); + Y_MidIntermission(); } Y_UnloadData(); diff --git a/src/y_inter.h b/src/y_inter.h index 51665f734..393d5abce 100644 --- a/src/y_inter.h +++ b/src/y_inter.h @@ -25,18 +25,17 @@ typedef struct boolean showrank; // show rank-restricted queue entry at the end, if it exists boolean encore; // encore mode boolean isduel; // duel mode + UINT8 winningteam; // teamplay boolean showroundnum; // round number char headerstring[64]; // holds levelnames up to 64 characters UINT8 numplayers; // Number of players being displayed + UINT8 halfway; // Position at which column switches SINT8 num[MAXPLAYERS]; // Player # UINT8 pos[MAXPLAYERS]; // player positions. used for ties - UINT8 character[MAXPLAYERS]; // Character # - UINT16 color[MAXPLAYERS]; // Color # - UINT32 val[MAXPLAYERS]; // Gametype-specific value char strval[MAXPLAYERS][MAXPLAYERNAME+1]; @@ -59,6 +58,7 @@ void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations, void Y_DrawIntermissionButton(INT32 startslide, INT32 through, boolean widescreen); void Y_StartIntermission(void); +void Y_MidIntermission(void); void Y_EndIntermission(void); boolean Y_ShouldDoIntermission(void); @@ -66,6 +66,8 @@ void Y_DetermineIntermissionType(void); void Y_PlayIntermissionMusic(void); +boolean Y_IntermissionPlayerLock(void); + typedef enum { int_none,