Merge branch 'queue-skin' into 'master'

Queued skin/color changes

See merge request KartKrew/Kart!2452
This commit is contained in:
Oni 2024-09-28 17:46:42 +00:00
commit 71588f490f
47 changed files with 1292 additions and 1309 deletions

View file

@ -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 *>(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);

View file

@ -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
//

View file

@ -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;

View file

@ -357,7 +357,7 @@ struct plrconfig
UINT16 color;
UINT32 pflags;
UINT32 score;
UINT8 ctfteam;
UINT8 team;
} ATTRPACK;
struct filesneededconfig_pak

View file

@ -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 <color>
if (COM_Argc() <= 1)
{
if (G_GametypeHasTeams())
CONS_Printf(M_GetText("%s <team>: switch to a new team (%s)\n"), commandname, "red, blue or spectator");
else if (G_GametypeHasSpectators())
CONS_Printf(M_GetText("%s <team>: 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 <team>: switch to a new team (%s)\n"), commandname, "red, blue or spectator");
else if (G_GametypeHasSpectators())
CONS_Printf(M_GetText("%s <team>: 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 <playernum> <team>
if (COM_Argc() < 3)
{
if (G_GametypeHasTeams())
CONS_Printf(M_GetText("serverchangeteam <playernum> <team>: switch player to a new team (%s)\n"), "red, blue or spectator");
else if (G_GametypeHasSpectators())
CONS_Printf(M_GetText("serverchangeteam <playernum> <team>: 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 <playernum> <team>: 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 <playernum> <team>: 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 <playernum> <team>: switch player to a new team (%s)\n"), "red, blue or spectator");
else if (G_GametypeHasSpectators())
CONS_Printf(M_GetText("serverchangeteam <playernum> <team>: 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)

View file

@ -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);

View file

@ -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

View file

@ -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);

View file

@ -544,6 +544,7 @@ typedef enum
DBG_LUA = 0x00000800,
DBG_RNG = 0x00001000,
DBG_DEMO = 0x00002000,
DBG_TEAMS = 0x00004000,
} debugFlags_t;
struct debugFlagNames_s

View file

@ -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

View file

@ -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;
}

View file

@ -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

View file

@ -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);

View file

@ -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;
}
}
}

View file

@ -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)

View file

@ -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;

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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))

View file

@ -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;
}

View file

@ -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;

View file

@ -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];

View file

@ -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);

View file

@ -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)
{

View file

@ -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"))

View file

@ -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);

View file

@ -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}
};

View file

@ -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,

View file

@ -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},

View file

@ -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;

View file

@ -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;
}

View file

@ -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)
{

View file

@ -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);
}
}

View file

@ -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;

View file

@ -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;
}
}
//

View file

@ -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);

View file

@ -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<UINT8> 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();

View file

@ -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;

View file

@ -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:

View file

@ -289,7 +289,7 @@ typedef enum
typedef enum
{
TMT_RED = 0,
TMT_ORANGE = 0,
TMT_BLUE = 1,
} textmapteam_t;

View file

@ -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)

View file

@ -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);
}
}
}

View file

@ -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);

View file

@ -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;

View file

@ -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);

View file

@ -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<skincolornum_t>(standings->color[i]), GTC_CACHE);
charcolormap = R_GetTranslationColormap(TC_BLINK, static_cast<skincolornum_t>(players[pnum].skincolor), GTC_CACHE);
}
else if (standings->color[i] != SKINCOLOR_NONE)
else
{
charcolormap = R_GetTranslationColormap(standings->character[i], static_cast<skincolornum_t>(standings->color[i]), GTC_CACHE);
charcolormap = R_GetTranslationColormap(players[pnum].skin, static_cast<skincolornum_t>(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<patch_t*>(W_CachePatchName("MINIDEAD", PU_CACHE)),
R_GetTranslationColormap(TC_DEFAULT, static_cast<skincolornum_t>(standings->color[i]), GTC_CACHE)
R_GetTranslationColormap(TC_DEFAULT, static_cast<skincolornum_t>(players[pnum].skincolor), GTC_CACHE)
);
}
else
{
charcolormap = R_GetTranslationColormap(standings->character[i], static_cast<skincolornum_t>(standings->color[i]), GTC_CACHE);
charcolormap = R_GetTranslationColormap(players[pnum].skin, static_cast<skincolornum_t>(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();

View file

@ -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,