Rework how Bots handle their skin availabilities

Before we can add extra unlock features, we need to make sure we're not building on a house of sand.
- R_SkinUsable: Use Net Unlock system if playernum is -1
- R_BotDefaultSkin: Move to r_skins.c, cache skin search
- R_GetSkinAvailabilities: Use Net Unlock when called for bots (and always permit R_BotDefaultSkin)
- Got_AddBot: Call R_GetSkinAvailabilities for summoned bots to guarantee sync status of available skins
- K_UpdateMatchRaceBots: Tidy up to match grand prix bot skin selection system, hiding server-locked skins and defaulting to R_BotDefaultSkin if you don't have enough unlocked for the remaining player slots
This commit is contained in:
toaster 2023-03-05 18:06:09 +00:00
parent 1ae5df651d
commit 0c75c40060
7 changed files with 70 additions and 87 deletions

View file

@ -824,7 +824,7 @@ static boolean CL_SendJoin(void)
for (; i < MAXSPLITSCREENPLAYERS; i++)
strncpy(netbuffer->u.clientcfg.names[i], va("Player %c", 'A' + i), MAXPLAYERNAME);
memcpy(&netbuffer->u.clientcfg.availabilities, R_GetSkinAvailabilities(false), MAXAVAILABILITY*sizeof(UINT8));
memcpy(&netbuffer->u.clientcfg.availabilities, R_GetSkinAvailabilities(false, false), MAXAVAILABILITY*sizeof(UINT8));
return HSendPacket(servernode, false, 0, sizeof (clientconfig_pak));
}
@ -3723,7 +3723,7 @@ static void Got_RemovePlayer(UINT8 **p, INT32 playernum)
static void Got_AddBot(UINT8 **p, INT32 playernum)
{
INT16 newplayernum;
UINT8 i, skinnum = 0;
UINT8 skinnum = 0;
UINT8 difficulty = DIFFICULTBOT;
if (playernum != serverplayer && !IsPlayerAdmin(playernum))
@ -3753,14 +3753,8 @@ static void Got_AddBot(UINT8 **p, INT32 playernum)
playernode[newplayernum] = servernode;
// todo find a way to have all auto unlocked for dedicated
if (playeringame[0])
{
for (i = 0; i < MAXAVAILABILITY; i++)
{
players[newplayernum].availabilities[i] = players[0].availabilities[i];
}
}
// this will permit unlocks
memcpy(&players[newplayernum].availabilities, R_GetSkinAvailabilities(false, true), MAXAVAILABILITY*sizeof(UINT8));
players[newplayernum].splitscreenindex = 0;
players[newplayernum].bot = true;
@ -3936,7 +3930,7 @@ boolean SV_SpawnServer(void)
// strictly speaking, i'm not convinced the following is necessary
// but I'm not confident enough to remove it entirely in case it breaks something
{
UINT8 *availabilitiesbuffer = R_GetSkinAvailabilities(false);
UINT8 *availabilitiesbuffer = R_GetSkinAvailabilities(false, false);
SINT8 node = 0;
for (; node < MAXNETNODES; node++)
result |= SV_AddWaitingPlayers(node, availabilitiesbuffer, cv_playername[0].zstring, cv_playername[1].zstring, cv_playername[2].zstring, cv_playername[3].zstring);

View file

@ -2239,7 +2239,7 @@ static void G_SaveDemoSkins(UINT8 **pp)
{
char skin[16];
UINT8 i;
UINT8 *availabilitiesbuffer = R_GetSkinAvailabilities(true);
UINT8 *availabilitiesbuffer = R_GetSkinAvailabilities(true, false);
WRITEUINT8((*pp), numskins);
for (i = 0; i < numskins; i++)

View file

@ -108,13 +108,15 @@ boolean K_AddBot(UINT8 skin, UINT8 difficulty, UINT8 *p)
--------------------------------------------------*/
void K_UpdateMatchRaceBots(void)
{
const UINT8 defaultbotskin = R_BotDefaultSkin();
const UINT8 difficulty = cv_kartbot.value;
UINT8 pmax = min((dedicated ? MAXPLAYERS-1 : MAXPLAYERS), cv_maxconnections.value);
UINT8 numplayers = 0;
UINT8 numbots = 0;
UINT8 numwaiting = 0;
SINT8 wantedbots = 0;
boolean skinusable[MAXSKINS];
UINT8 usableskins = 0;
UINT8 grabskins[MAXSKINS+1];
UINT8 i;
if (!server)
@ -122,18 +124,12 @@ void K_UpdateMatchRaceBots(void)
return;
}
// init usable bot skins list
for (i = 0; i < MAXSKINS; i++)
// Init usable bot skins list
for (i = 0; i < numskins; i++)
{
if (i < numskins)
{
skinusable[i] = true;
}
else
{
skinusable[i] = false;
}
grabskins[usableskins++] = i;
}
grabskins[usableskins] = MAXSKINS;
if (cv_maxplayers.value > 0)
{
@ -146,7 +142,7 @@ void K_UpdateMatchRaceBots(void)
{
if (!players[i].spectator)
{
skinusable[players[i].skin] = false;
grabskins[players[i].skin] = MAXSKINS;
if (players[i].bot)
{
@ -185,48 +181,42 @@ void K_UpdateMatchRaceBots(void)
{
// We require MORE bots!
UINT8 newplayernum = 0;
boolean usedallskins = false;
if (dedicated)
{
newplayernum = 1;
}
// Rearrange usable bot skins list to prevent gaps for randomised selection
for (i = 0; i < usableskins; i++)
{
if (!(grabskins[i] == MAXSKINS || !R_SkinUsable(-1, grabskins[i], true)))
continue;
while (usableskins > i && (grabskins[usableskins] == MAXSKINS || !R_SkinUsable(-1, grabskins[usableskins], true)))
{
usableskins--;
}
grabskins[i] = grabskins[usableskins];
grabskins[usableskins] = MAXSKINS;
}
while (numbots < wantedbots)
{
UINT8 skin = M_RandomKey(numskins);
UINT8 skinnum = defaultbotskin;
if (usedallskins == false)
if (usableskins > 0)
{
UINT8 loops = 0;
while (!skinusable[skin])
{
if (loops >= numskins)
{
// no more skins, stick to our first choice
usedallskins = true;
break;
}
skin++;
if (skin >= numskins)
{
skin = 0;
}
loops++;
}
UINT8 index = M_RandomKey(usableskins);
skinnum = grabskins[index];
grabskins[index] = grabskins[--usableskins];
}
if (!K_AddBot(skin, difficulty, &newplayernum))
if (!K_AddBot(skinnum, difficulty, &newplayernum))
{
// Not enough player slots to add the bot, break the loop.
break;
}
skinusable[skin] = false;
numbots++;
}
}

View file

@ -95,25 +95,6 @@ INT16 K_CalculateGPRankPoints(UINT8 position, UINT8 numplayers)
return points;
}
/*--------------------------------------------------
UINT8 K_BotDefaultSkin(void)
See header file for description.
--------------------------------------------------*/
UINT8 K_BotDefaultSkin(void)
{
const char *defaultbotskinname = "eggrobo";
INT32 defaultbotskin = R_SkinAvailable(defaultbotskinname);
if (defaultbotskin == -1)
{
// This shouldn't happen, but just in case
defaultbotskin = 0;
}
return (UINT8)defaultbotskin;
}
/*--------------------------------------------------
void K_InitGrandPrixBots(void)
@ -121,7 +102,7 @@ UINT8 K_BotDefaultSkin(void)
--------------------------------------------------*/
void K_InitGrandPrixBots(void)
{
const UINT8 defaultbotskin = K_BotDefaultSkin();
const UINT8 defaultbotskin = R_BotDefaultSkin();
const UINT8 startingdifficulty = K_BotStartingDifficulty(grandprixinfo.gamespeed);
UINT8 difficultylevels[MAXPLAYERS];
@ -519,7 +500,7 @@ void K_IncreaseBotDifficulty(player_t *bot)
--------------------------------------------------*/
void K_RetireBots(void)
{
const UINT8 defaultbotskin = K_BotDefaultSkin();
const UINT8 defaultbotskin = R_BotDefaultSkin();
SINT8 newDifficulty;
UINT8 usableskins;

View file

@ -71,16 +71,6 @@ UINT8 K_BotStartingDifficulty(SINT8 value);
INT16 K_CalculateGPRankPoints(UINT8 position, UINT8 numplayers);
/*--------------------------------------------------
UINT8 K_BotDefaultSkin(void);
Returns the skin number of the skin the game
uses as a fallback option.
--------------------------------------------------*/
UINT8 K_BotDefaultSkin(void);
/*--------------------------------------------------
void K_InitGrandPrixBots(void);

View file

@ -129,6 +129,27 @@ static void Sk_SetDefaultValue(skin_t *skin)
skin->soundsid[S_sfx[i].skinsound] = i;
}
// Grab the default skin
UINT8 R_BotDefaultSkin(void)
{
static INT32 defaultbotskin = -1;
if (defaultbotskin == -1)
{
const char *defaultbotskinname = "eggrobo";
defaultbotskin = R_SkinAvailable(defaultbotskinname);
if (defaultbotskin == -1)
{
// This shouldn't happen, but just in case
defaultbotskin = 0;
}
}
return (UINT8)defaultbotskin;
}
//
// Initialize the basic skins
//
@ -159,11 +180,12 @@ void R_InitSkins(void)
M_UpdateConditionSetsPending();
}
UINT8 *R_GetSkinAvailabilities(boolean demolock)
UINT8 *R_GetSkinAvailabilities(boolean demolock, boolean forbots)
{
UINT8 i, shif, byte;
INT32 skinid;
static UINT8 responsebuffer[MAXAVAILABILITY];
UINT8 defaultbotskin = R_BotDefaultSkin();
memset(&responsebuffer, 0, sizeof(responsebuffer));
@ -172,15 +194,17 @@ UINT8 *R_GetSkinAvailabilities(boolean demolock)
if (unlockables[i].type != SECRET_SKIN)
continue;
// NEVER EVER EVER M_CheckNetUnlockByID
if (gamedata->unlocked[i] != true && !demolock)
continue;
skinid = M_UnlockableSkinNum(&unlockables[i]);
if (skinid < 0 || skinid >= MAXSKINS)
continue;
if ((forbots
? (M_CheckNetUnlockByID(i) || skinid == defaultbotskin) // Assert the host's lock.
: gamedata->unlocked[i]) // Assert the local lock.
!= true && !demolock)
continue;
shif = (skinid % 8);
byte = (skinid / 8);
@ -251,8 +275,11 @@ boolean R_SkinUsable(INT32 playernum, INT32 skinnum, boolean demoskins)
return !!(players[playernum].availabilities[byte] & (1 << shif));
}
// Use the host's if it's checking general state
if (playernum == -1)
return M_CheckNetUnlockByID(i);
// Use the unlockables table directly
// NOTE: M_CheckNetUnlockByID would be correct in many circumstances... but not all. TODO figure out how to discern.
return (boolean)(gamedata->unlocked[i]);
}

View file

@ -91,8 +91,9 @@ void ClearFakePlayerSkin(player_t* player);
boolean R_SkinUsable(INT32 playernum, INT32 skinnum, boolean demoskins);
INT32 GetSkinNumClosestToStats(UINT8 kartspeed, UINT8 kartweight, UINT32 flags, boolean unlock);
UINT8 *R_GetSkinAvailabilities(boolean demolock);
UINT8 *R_GetSkinAvailabilities(boolean demolock, boolean forbots);
INT32 R_SkinAvailable(const char *name);
UINT8 R_BotDefaultSkin(void);
void R_PatchSkins(UINT16 wadnum, boolean mainfile);
void R_AddSkins(UINT16 wadnum, boolean mainfile);