Increase bot difficulty every match based on placement, implement rival tag

Rival tag does not have an in-game indicator, but it is there & it gives the bot better odds
This commit is contained in:
Sally Coolatta 2020-05-14 02:18:32 -04:00
parent 6c8d67ab86
commit 47c2c875d5
10 changed files with 236 additions and 14 deletions

View file

@ -650,6 +650,8 @@ static inline void resynch_write_player(resynch_pak *rsp, const size_t i)
rsp->bot = players[i].bot;
rsp->bot_difficulty = players[i].botvars.difficulty;
rsp->bot_diffincrease = players[i].botvars.diffincrease;
rsp->bot_rival = players[i].botvars.rival;
rsp->bot_itemdelay = players[i].botvars.itemdelay;
rsp->bot_itemconfirm = players[i].botvars.itemconfirm;
rsp->bot_lastturn = players[i].botvars.lastturn;
@ -780,6 +782,8 @@ static void resynch_read_player(resynch_pak *rsp)
players[i].bot = rsp->bot;
players[i].botvars.difficulty = rsp->bot_difficulty;
players[i].botvars.diffincrease = rsp->bot_diffincrease;
players[i].botvars.rival = rsp->bot_rival;
players[i].botvars.itemdelay = rsp->bot_itemdelay;
players[i].botvars.itemconfirm = rsp->bot_itemconfirm;
players[i].botvars.lastturn = rsp->bot_lastturn;

View file

@ -288,6 +288,8 @@ typedef struct
boolean bot;
UINT8 bot_difficulty;
UINT8 bot_diffincrease;
boolean bot_rival;
tic_t bot_itemdelay;
tic_t bot_itemconfirm;
INT16 bot_lastturn;

View file

@ -423,12 +423,14 @@ typedef enum
// player_t struct for all bot variables
typedef struct botvars_s
{
UINT8 difficulty;
UINT8 difficulty; // Bot's difficulty setting
UINT8 diffincrease; // In GP: bot difficulty will increase this much next round
boolean rival; // If true, they're the GP rival
tic_t itemdelay;
tic_t itemconfirm;
tic_t itemdelay; // Delay before using item at all
tic_t itemconfirm; // When high enough, they will use their item
INT16 lastturn;
INT16 lastturn; // Last turn direction
} botvars_t;
// ========================================================================

View file

@ -2598,6 +2598,8 @@ void G_PlayerReborn(INT32 player)
boolean spectator;
boolean bot;
UINT8 botdifficulty;
UINT8 botdiffincrease;
boolean botrival;
SINT8 pity;
// SRB2kart
@ -2652,6 +2654,8 @@ void G_PlayerReborn(INT32 player)
mare = players[player].mare;
bot = players[player].bot;
botdifficulty = players[player].botvars.difficulty;
botdiffincrease = players[player].botvars.diffincrease;
botrival = players[player].botvars.rival;
pity = players[player].pity;
// SRB2kart
@ -2733,6 +2737,8 @@ void G_PlayerReborn(INT32 player)
p->mare = mare;
p->bot = bot;
p->botvars.difficulty = botdifficulty;
p->botvars.diffincrease = botdiffincrease;
p->botvars.rival = botrival;
p->pity = pity;
// SRB2kart

View file

@ -248,6 +248,179 @@ void K_InitGrandPrixBots(void)
}
}
static INT16 K_RivalScore(player_t *bot)
{
const UINT16 difficulty = bot->botvars.difficulty;
const UINT16 score = bot->score;
const SINT8 roundsleft = grandprixinfo.cup->numlevels - grandprixinfo.roundnum;
// In the early game, difficulty is more important for long-term challenge.
// When we're running low on matches left though, we need to focus more on score.
return (difficulty * roundsleft) + (score * grandprixinfo.roundnum);
}
void K_UpdateGrandPrixBots(void)
{
player_t *oldrival = NULL;
player_t *newrival = NULL;
UINT16 newrivalscore = 0;
UINT8 i;
// Find the rival.
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].spectator || !players[i].bot)
{
continue;
}
if (players[i].botvars.diffincrease)
{
players[i].botvars.difficulty += players[i].botvars.diffincrease;
if (players[i].botvars.difficulty > MAXBOTDIFFICULTY)
{
players[i].botvars.difficulty = MAXBOTDIFFICULTY;
}
players[i].botvars.diffincrease = 0;
}
if (players[i].botvars.rival)
{
if (oldrival == NULL)
{
// Record the old rival to compare with our calculated new rival
oldrival = &players[i];
}
else
{
// Somehow 2 rivals were set?!
// Let's quietly fix our mess...
players[i].botvars.rival = false;
}
}
}
// Find the bot with the best average of score & difficulty.
for (i = 0; i < MAXPLAYERS; i++)
{
UINT16 ns = 0;
if (!playeringame[i] || players[i].spectator || !players[i].bot)
{
continue;
}
ns = K_RivalScore(&players[i]);
if (ns > newrivalscore)
{
newrival = &players[i];
newrivalscore = ns;
}
}
// Even if there's a new rival, we want to make sure that they're a better fit than the current one!
if (oldrival != newrival)
{
if (oldrival != NULL)
{
UINT16 os = K_RivalScore(oldrival);
if (newrivalscore <= os + 100)
{
// Our current rival's just fine, thank you very much.
return;
}
// Hand over your badge.
oldrival->botvars.rival = false;
}
// Set our new rival!
newrival->botvars.rival = true;
CONS_Printf("Rival is now %s\n", player_names[newrival - players]);
}
}
static UINT8 K_BotExpectedStanding(player_t *bot)
{
UINT8 pos = 1;
UINT8 i;
for (i = 0; i < MAXPLAYERS; i++)
{
if (i == (bot - players))
{
continue;
}
if (playeringame[i] && !players[i].spectator)
{
if (players[i].bot)
{
if (players[i].botvars.difficulty > bot->botvars.difficulty)
{
pos++;
}
else if (players[i].score > bot->score)
{
pos++;
}
}
else
{
// Human player, always increment
pos++;
}
}
}
return pos;
}
void K_IncreaseBotDifficulty(player_t *bot)
{
UINT8 expectedstanding;
INT16 standingdiff;
if (bot->botvars.difficulty >= MAXBOTDIFFICULTY)
{
// Already at max difficulty, don't need to increase
return;
}
// Increment bot difficulty based on what position you were meant to come in!
expectedstanding = K_BotExpectedStanding(bot);
standingdiff = expectedstanding - bot->kartstuff[k_position];
if (standingdiff >= -2)
{
UINT8 increase;
if (standingdiff > 5)
{
increase = 3;
}
else if (standingdiff > 2)
{
increase = 2;
}
else
{
increase = 1;
}
bot->botvars.diffincrease = increase;
}
else
{
bot->botvars.diffincrease = 0;
}
}
void K_FakeBotResults(player_t *bot)
{
const UINT32 distfactor = FixedMul(32 * bot->mo->scale, K_GetKartGameSpeedScalar(gamespeed)) / FRACUNIT;
@ -298,6 +471,7 @@ void K_FakeBotResults(player_t *bot)
bot->exiting = 2;
bot->realtime += (bot->distancetofinish / distfactor);
bot->distancetofinish = 0;
K_IncreaseBotDifficulty(bot);
}
void K_PlayerLoseLife(player_t *player)

View file

@ -18,6 +18,8 @@ extern struct grandprixinfo
UINT8 K_BotStartingDifficulty(SINT8 value);
INT16 K_CalculateGPRankPoints(UINT8 position, UINT8 numplayers);
void K_InitGrandPrixBots(void);
void K_UpdateGrandPrixBots(void);
void K_IncreaseBotDifficulty(player_t *bot);
void K_FakeBotResults(player_t *bot);
void K_PlayerLoseLife(player_t *player);

View file

@ -854,7 +854,7 @@ static void K_KartGetItemResult(player_t *player, SINT8 getitem)
\return void
*/
static INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, fixed_t mashed, boolean spbrush, boolean bot)
static INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, fixed_t mashed, boolean spbrush, boolean bot, boolean rival)
{
INT32 newodds;
INT32 i;
@ -932,10 +932,12 @@ static INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, fixed_t mashed, boolean sp
#define POWERITEMODDS(odds) {\
if (franticitems) \
odds <<= 1; \
odds = FixedMul(odds<<FRACBITS, FRACUNIT + ((PLAYERSCALING << FRACBITS) / 25)) >> FRACBITS; \
odds *= 2; \
if (rival) \
odds *= 2; \
odds = FixedMul(odds * FRACUNIT, FRACUNIT + ((PLAYERSCALING * FRACUNIT) / 25)) / FRACUNIT; \
if (mashed > 0) \
odds = FixedDiv(odds<<FRACBITS, FRACUNIT + mashed) >> FRACBITS; \
odds = FixedDiv(odds * FRACUNIT, FRACUNIT + mashed) / FRACUNIT; \
}
#define COOLDOWNONSTART (leveltime < (30*TICRATE)+starttime)
@ -1071,7 +1073,7 @@ static UINT8 K_FindUseodds(player_t *player, fixed_t mashed, UINT32 pdis, UINT8
for (j = 1; j < NUMKARTRESULTS; j++)
{
if (K_KartGetItemOdds(i, j, mashed, spbrush, player->bot) > 0)
if (K_KartGetItemOdds(i, j, mashed, spbrush, player->bot, (player->bot && player->botvars.rival)) > 0)
{
available = true;
break;
@ -1233,7 +1235,9 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd)
pdis = FixedDiv(pdis * FRACUNIT, mapobjectscale) / FRACUNIT;
if (franticitems) // Frantic items make the distances between everyone artifically higher, for crazier items
{
pdis = (15 * pdis) / 14;
}
if (spbplace != -1 && player->kartstuff[k_position] == spbplace+1) // SPB Rush Mode: It's 2nd place's job to catch-up items and make 1st place's job hell
{
@ -1241,6 +1245,12 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd)
spbrush = true;
}
if (player->bot && player->botvars.rival)
{
// Rival has better odds :)
pdis = (15 * pdis) / 14;
}
pdis = ((28 + (8-pingame)) * pdis) / 28; // scale with player count
// SPECIAL CASE No. 1:
@ -1369,7 +1379,7 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd)
useodds = K_FindUseodds(player, mashed, pdis, bestbumper, spbrush);
for (i = 1; i < NUMKARTRESULTS; i++)
spawnchance[i] = (totalspawnchance += K_KartGetItemOdds(useodds, i, mashed, spbrush, player->bot));
spawnchance[i] = (totalspawnchance += K_KartGetItemOdds(useodds, i, mashed, spbrush, player->bot, (player->bot && player->botvars.rival)));
// Award the player whatever power is rolled
if (totalspawnchance > 0)
@ -11277,13 +11287,19 @@ static void K_drawDistributionDebugger(void)
spbrush = true;
}
if (stplyr->bot && stplyr->botvars.rival)
{
// Rival has better odds :)
pdis = (15 * pdis) / 14;
}
pdis = ((28 + (8-pingame)) * pdis) / 28; // scale with player count
useodds = K_FindUseodds(stplyr, 0, pdis, bestbumper, spbrush);
for (i = 1; i < NUMKARTRESULTS; i++)
{
const INT32 itemodds = K_KartGetItemOdds(useodds, i, 0, spbrush, stplyr->bot);
const INT32 itemodds = K_KartGetItemOdds(useodds, i, 0, spbrush, stplyr->bot, (stplyr->bot && stplyr->botvars.rival));
if (itemodds <= 0)
continue;

View file

@ -271,6 +271,8 @@ static void P_NetArchivePlayers(void)
WRITEUINT32(save_p, K_GetWaypointHeapIndex(players[i].nextwaypoint));
WRITEUINT8(save_p, players[i].botvars.difficulty);
WRITEUINT8(save_p, players[i].botvars.diffincrease);
WRITEUINT8(save_p, players[i].botvars.rival);
WRITEUINT32(save_p, players[i].botvars.itemdelay);
WRITEUINT32(save_p, players[i].botvars.itemconfirm);
WRITEINT16(save_p, players[i].botvars.lastturn);
@ -448,6 +450,8 @@ static void P_NetUnArchivePlayers(void)
players[i].nextwaypoint = (waypoint_t *)(size_t)READUINT32(save_p);
players[i].botvars.difficulty = READUINT8(save_p);
players[i].botvars.diffincrease = READUINT8(save_p);
players[i].botvars.rival = (boolean)READUINT8(save_p);
players[i].botvars.itemdelay = READUINT32(save_p);
players[i].botvars.itemconfirm = READUINT32(save_p);
players[i].botvars.lastturn = READINT16(save_p);

View file

@ -3375,6 +3375,10 @@ boolean P_SetupLevel(boolean skipprecip)
K_InitGrandPrixBots();
grandprixinfo.initalize = false;
}
else
{
K_UpdateGrandPrixBots();
}
}
else if (!modeattacking)
{

View file

@ -1735,10 +1735,18 @@ void P_DoPlayerExit(player_t *player)
else
player->exiting = raceexittime+2; // Accidental death safeguard???
if (grandprixinfo.roundnum > 0 && !losing && !player->bot)
if (grandprixinfo.roundnum > 0)
{
// YOU WIN
grandprixinfo.wonround = true;
if (player->bot)
{
// Bots are going to get harder... :)
K_IncreaseBotDifficulty(player);
}
else if (!losing)
{
// YOU WIN
grandprixinfo.wonround = true;
}
}
player->powers[pw_underwater] = 0;