Merge branch 'make-way-bots' into 'master'

Kick CPU players to make room for human players that are waiting to play

See merge request KartKrew/Kart!2259
This commit is contained in:
Oni 2024-04-12 03:41:38 +00:00 committed by NepDisk
parent 32b4c85649
commit 313846cb60
5 changed files with 134 additions and 46 deletions

View file

@ -3991,9 +3991,27 @@ static void HandleConnect(SINT8 node)
UINT8 maxplayers = min((dedicated ? MAXPLAYERS-1 : MAXPLAYERS), cv_maxplayers.value);
UINT8 connectedplayers = 0;
for (UINT8 i = dedicated ? 1 : 0; i < MAXPLAYERS; i++)
if (playernode[i] != UINT8_MAX) // We use this to count players because it is affected by SV_AddWaitingPlayers when more than one client joins on the same tic, unlike playeringame and D_NumPlayers. UINT8_MAX denotes no node for that player
for (i = dedicated ? 1 : 0; i < MAXPLAYERS; i++)
{
// We use this to count players because it is affected by SV_AddWaitingPlayers when
// more than one client joins on the same tic, unlike playeringame and D_NumPlayers.
// UINT8_MAX denotes no node for that player.
if (playernode[i] != UINT8_MAX)
{
// Sal: This hack sucks, but it should be safe.
// playeringame is set for bots immediately; they are deterministic instead of a netxcmd.
// If a bot is added with netxcmd in the future, then the node check is still here too.
// So at worst, a theoretical netxcmd added bot will block real joiners for the time
// it takes for the command to process, but not cause any horrifying player overwriting.
if (playeringame[i] && players[i].bot)
{
continue;
}
connectedplayers++;
}
}
if (bannednode && bannednode[node].banid != SIZE_MAX)
{

View file

@ -2103,7 +2103,7 @@ void G_Ticker(boolean run)
&& (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION || gamestate == GS_VOTING // definitely good
|| gamestate == GS_WAITINGPLAYERS)) // definitely a problem if we don't do it at all in this gamestate, but might need more protection?
{
K_CheckSpectateStatus();
K_CheckSpectateStatus(true);
}
if (pausedelay && pausedelay != INT32_MIN)

View file

@ -9012,33 +9012,65 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
}
}
void K_CheckSpectateStatus(void)
void K_CheckSpectateStatus(boolean considermapreset)
{
UINT8 respawnlist[MAXPLAYERS];
UINT8 i, j, numingame = 0, numjoiners = 0;
UINT8 previngame = 0;
UINT8 numhumans = 0, numbots = 0;
// Maintain spectate wait timer
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i])
{
continue;
if (players[i].spectator && (players[i].pflags & PF_WANTSTOJOIN))
players[i].spectatewait++;
else
}
if (!players[i].spectator)
{
numingame++;
if (players[i].bot)
{
numbots++;
}
else
{
numhumans++;
}
players[i].spectatewait = 0;
if (gamestate != GS_LEVEL)
players[i].spectatorreentry = 0;
continue;
}
if ((players[i].pflags & PF_WANTSTOJOIN))
{
players[i].spectatewait++;
}
else
{
players[i].spectatewait = 0;
}
if (gamestate != GS_LEVEL || considermapreset == false)
{
players[i].spectatorreentry = 0;
}
else if (players[i].spectatorreentry > 0)
players[i].spectatorreentry--;
{
players[i].spectatorreentry--;
}
}
// No one's allowed to join
if (!cv_allowteamchange.value)
return;
// DON'T allow if you've hit the in-game player cap
if (cv_maxplayers.value && numhumans >= cv_maxplayers.value)
return;
// Get the number of players in game, and the players to be de-spectated.
for (i = 0; i < MAXPLAYERS; i++)
{
@ -9047,52 +9079,56 @@ void K_CheckSpectateStatus(void)
if (!players[i].spectator)
{
numingame++;
// DON'T allow if you've hit the in-game player cap
if (cv_ingamecap.value && numingame >= cv_ingamecap.value)
return;
// Allow if you're not in a level
if (gamestate != GS_LEVEL)
if (gamestate != GS_LEVEL)
continue;
// DON'T allow if anyone's exiting
if (players[i].exiting)
if (players[i].exiting)
return;
// Allow if the match hasn't started yet
if (numingame < 2 || leveltime < starttime || mapreset)
if (numingame < 2 || leveltime < starttime || mapreset)
continue;
// DON'T allow if the match is 20 seconds in
if (leveltime > (starttime + 20*TICRATE))
if (leveltime > (starttime + 20*TICRATE))
return;
// DON'T allow if the race is at 2 laps
if (gametype == GT_RACE && players[i].laps >= 2) // DON'T allow if the race is at 2 laps
if ((gametyperules & GTR_CIRCUIT) && players[i].laps >= 2)
return;
continue;
}
else if (players[i].bot || !(players[i].pflags & PF_WANTSTOJOIN))
if (players[i].bot)
{
// Spectating bots are controlled by other mechanisms.
continue;
}
if (!(players[i].pflags & PF_WANTSTOJOIN))
{
// This spectator does not want to join.
continue;
}
if (netgame && numingame > 0 && players[i].spectatorreentry > 0)
{
// This person has their reentry cooldown active.
continue;
}
respawnlist[numjoiners++] = i;
}
// The map started as a legitimate race, but there's still the one player.
// Don't allow new joiners, as they're probably a ragespeccer.
if ((gametyperules & GTR_CIRCUIT) && startedInFreePlay == false && numingame == 1)
{
return;
}
// literally zero point in going any further if nobody is joining
// Literally zero point in going any further if nobody is joining.
if (!numjoiners)
return;
// Organize by spectate wait timer
#if 0
if (cv_ingamecap.value)
#endif
// Organize by spectate wait timer (if there's more than one to sort)
if (cv_maxplayers.value && numjoiners > 1)
{
UINT8 oldrespawnlist[MAXPLAYERS];
memcpy(oldrespawnlist, respawnlist, numjoiners);
@ -9116,25 +9152,54 @@ void K_CheckSpectateStatus(void)
}
}
// Finally, we can de-spectate everyone in the list!
previngame = numingame;
const UINT8 previngame = numingame;
INT16 removeBotID = MAXPLAYERS - 1;
// Finally, we can de-spectate everyone!
for (i = 0; i < numjoiners; i++)
{
// Hit the in-game player cap while adding people?
if (cv_ingamecap.value && numingame+i >= cv_ingamecap.value)
break;
// This person has their reentry cooldown active.
if (players[i].spectatorreentry > 0 && numingame > 0)
continue;
if (cv_maxplayers.value && numingame >= cv_maxplayers.value)
{
if (numbots > 0)
{
// Find a bot to kill to make room
while (removeBotID >= 0)
{
if (playeringame[removeBotID] && players[removeBotID].bot)
{
//CONS_Printf("bot %s kicked to make room on tic %d\n", player_names[removeBotID], leveltime);
CL_RemovePlayer(removeBotID, KR_LEAVE);
numbots--;
numingame--;
break;
}
removeBotID--;
}
if (removeBotID < 0)
{
break;
}
}
else
{
break;
}
}
//CONS_Printf("player %s is joining on tic %d\n", player_names[respawnlist[i]], leveltime);
P_SpectatorJoinGame(&players[respawnlist[i]]);
numhumans++;
numingame++;
}
// Reset the match if you're in an empty server
if (considermapreset == false)
return;
// Reset the match when 2P joins 1P, DUEL mode
// Reset the match when 3P joins 1P and 2P, DUEL mode must be disabled
if (!mapreset && gamestate == GS_LEVEL && (previngame < 2 && numingame >= 2))
{
S_ChangeMusicInternal("chalng", false); // COME ON

View file

@ -127,7 +127,7 @@ fixed_t K_GetNewSpeed(player_t *player);
fixed_t K_3dKartMovement(player_t *player);
SINT8 K_Sliptiding(player_t *player);
void K_MoveKartPlayer(player_t *player, boolean onground);
void K_CheckSpectateStatus(void);
void K_CheckSpectateStatus(boolean considermapreset);
UINT8 K_GetInvincibilityItemFrame(void);
UINT8 K_GetOrbinautItemFrame(UINT8 count);
boolean K_IsSPBInGame(void);

View file

@ -8526,6 +8526,11 @@ void P_PostLoadLevel(void)
{
P_MapStart(); // tm.thing can be used starting from this point
if (G_GametypeHasSpectators())
{
K_CheckSpectateStatus(false);
}
if (demo.playback)
;
else if (grandprixinfo.gp == true)