WIP: Foes

This commit is contained in:
Antonio Martinez 2025-07-31 06:12:20 -04:00 committed by AJ Martinez
parent 5aa6af4f97
commit 76b88990cc
9 changed files with 148 additions and 55 deletions

View file

@ -418,6 +418,7 @@ struct botvars_t
UINT8 difficulty; // Bot's difficulty setting UINT8 difficulty; // Bot's difficulty setting
INT16 diffincrease; // In GP: bot difficulty will increase this much next round INT16 diffincrease; // In GP: bot difficulty will increase this much next round
boolean rival; // If true, they're the GP rival boolean rival; // If true, they're the GP rival
boolean foe; // If true, in contention for top X
// All entries above persist between rounds and must be recorded in demos // All entries above persist between rounds and must be recorded in demos

View file

@ -2271,6 +2271,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
INT16 botdiffincrease; INT16 botdiffincrease;
boolean botrival; boolean botrival;
boolean botfoe;
boolean cangrabitems; boolean cangrabitems;
@ -2381,6 +2382,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
botdiffincrease = players[player].botvars.diffincrease; botdiffincrease = players[player].botvars.diffincrease;
botrival = players[player].botvars.rival; botrival = players[player].botvars.rival;
botfoe = players[player].botvars.foe;
totalring = players[player].totalring; totalring = players[player].totalring;
xtralife = players[player].xtralife; xtralife = players[player].xtralife;
@ -2641,6 +2643,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
p->spheres = spheres; p->spheres = spheres;
p->botvars.diffincrease = botdiffincrease; p->botvars.diffincrease = botdiffincrease;
p->botvars.rival = botrival; p->botvars.rival = botrival;
p->botvars.foe = botfoe;
p->xtralife = xtralife; p->xtralife = xtralife;
// SRB2kart // SRB2kart

View file

@ -620,7 +620,7 @@ static UINT32 K_BotRubberbandDistance(const player_t *player)
UINT8 pos = 1; UINT8 pos = 1;
UINT8 i; UINT8 i;
if (player->botvars.rival || cv_levelskull.value) if (player->botvars.rival || player->botvars.foe || cv_levelskull.value)
{ {
// The rival should always try to be the front runner for the race. // The rival should always try to be the front runner for the race.
return 0; return 0;
@ -694,6 +694,8 @@ fixed_t K_BotRubberband(const player_t *player)
{ {
UINT8 levelreduce = std::min<UINT8>(3, player->botvars.difficulty/4); // How much to drop the "effective level" of bots that are consistently behind UINT8 levelreduce = std::min<UINT8>(3, player->botvars.difficulty/4); // How much to drop the "effective level" of bots that are consistently behind
expreduce = Easing_Linear((K_EffectiveGradingFactor(player) - MINGRADINGFACTOR) * 2, levelreduce*FRACUNIT, 0); expreduce = Easing_Linear((K_EffectiveGradingFactor(player) - MINGRADINGFACTOR) * 2, levelreduce*FRACUNIT, 0);
if (player->botvars.foe)
expreduce /= 2;
} }
fixed_t difficultyEase = (((player->botvars.difficulty - 1) * FRACUNIT) - expreduce) / (MAXBOTDIFFICULTY - 1); fixed_t difficultyEase = (((player->botvars.difficulty - 1) * FRACUNIT) - expreduce) / (MAXBOTDIFFICULTY - 1);

View file

@ -299,7 +299,7 @@ static boolean K_RivalBotAggression(const player_t *bot, const player_t *target)
return false; return false;
} }
if (bot->botvars.rival == false && !cv_levelskull.value) if (!(bot->botvars.rival || bot->botvars.foe) && !cv_levelskull.value)
{ {
// Not the rival, we aren't self-aware. // Not the rival, we aren't self-aware.
return false; return false;

View file

@ -98,6 +98,117 @@ static UINT8 K_GetOffsetStartingDifficulty(const UINT8 startingdifficulty, UINT8
return startingdifficulty - offset; return startingdifficulty - offset;
} }
/*--------------------------------------------------
static INT16 K_RivalScore(player_t *bot)
Creates a "rival score" for a bot, used to determine which bot is the
most deserving of the rival status.
Input Arguments:-
bot - Player to check.
Return:-
"Rival score" value.
--------------------------------------------------*/
static INT16 K_RivalScore(player_t *bot)
{
const UINT16 difficulty = bot->botvars.difficulty;
const UINT16 score = bot->score;
SINT8 roundnum = 1, roundsleft = 1;
UINT16 lowestscore = UINT16_MAX;
UINT8 lowestdifficulty = MAXBOTDIFFICULTY;
UINT8 i;
if (grandprixinfo.cup != NULL && roundqueue.size > 0)
{
roundnum = roundqueue.roundnum;
roundsleft = grandprixinfo.cup->numlevels - roundnum;
}
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].spectator)
{
continue;
}
if (players[i].score < lowestscore)
{
lowestscore = players[i].score;
}
if (players[i].bot == true && players[i].botvars.difficulty < lowestdifficulty)
{
lowestdifficulty = players[i].botvars.difficulty;
}
}
// In the early game, difficulty is more important.
// This will try to influence the higher difficulty bots to get rival more often & get even more points.
// However, when we're running low on matches left, we need to focus more on raw score!
return ((difficulty - lowestdifficulty) * roundsleft) + ((score - lowestscore) * roundnum);
}
static boolean CompareRivals(player_t *a, player_t *b)
{
CONS_Printf("compare foes\n");
if (a == NULL)
return false;
if (b == NULL)
return true;
CONS_Printf("%s %d VS %s %d\n", player_names[a-players], K_RivalScore(a), player_names[b-players], K_RivalScore(b));
if (K_RivalScore(a) != K_RivalScore(b))
{
// Push bad position to the back.
CONS_Printf("returning known\n");
return (K_RivalScore(a) > K_RivalScore(b));
}
CONS_Printf("returning shuffle\n");
// They are equals, so just randomize
return (P_Random(PR_BOTS) & 1);
}
static void K_AssignFoes(void)
{
std::vector<player_t *> bots;
CONS_Printf("foe assignment\n");
for (UINT8 i = 0; i < MAXPLAYERS; i++)
{
if (playeringame[i] == false)
continue;
player_t *player = &players[i];
if (!player->spectator && player->bot)
{
CONS_Printf("added %s\n", player_names[i]);
bots.push_back(player);
player->botvars.foe = false;
}
}
CONS_Printf("sort foes\n");
std::stable_sort(bots.begin(), bots.end(), CompareRivals);
UINT8 i = 0;
for (auto &bot : bots)
{
CONS_Printf("assign foes\n");
if (bot != NULL)
bot->botvars.foe = true;
i++;
if (i > 2)
break;
}
}
/*-------------------------------------------------- /*--------------------------------------------------
void K_InitGrandPrixBots(void) void K_InitGrandPrixBots(void)
@ -254,6 +365,8 @@ void K_InitGrandPrixBots(void)
{ {
break; break;
} }
if (i <= 2)
players[newplayernum-1].botvars.foe = true;
} }
} }
@ -289,64 +402,13 @@ void K_LoadGrandPrixSaveGame(void)
K_SetBot(i, savedata.bots[i].skin, savedata.bots[i].difficulty, BOT_STYLE_NORMAL); K_SetBot(i, savedata.bots[i].skin, savedata.bots[i].difficulty, BOT_STYLE_NORMAL);
players[i].botvars.rival = savedata.bots[i].rival; players[i].botvars.rival = savedata.bots[i].rival;
players[i].botvars.foe = savedata.bots[i].foe;
players[i].score = savedata.bots[i].score; players[i].score = savedata.bots[i].score;
players[i].spectator = K_BotDefaultSpectator(); players[i].spectator = K_BotDefaultSpectator();
} }
} }
/*--------------------------------------------------
static INT16 K_RivalScore(player_t *bot)
Creates a "rival score" for a bot, used to determine which bot is the
most deserving of the rival status.
Input Arguments:-
bot - Player to check.
Return:-
"Rival score" value.
--------------------------------------------------*/
static INT16 K_RivalScore(player_t *bot)
{
const UINT16 difficulty = bot->botvars.difficulty;
const UINT16 score = bot->score;
SINT8 roundnum = 1, roundsleft = 1;
UINT16 lowestscore = UINT16_MAX;
UINT8 lowestdifficulty = MAXBOTDIFFICULTY;
UINT8 i;
if (grandprixinfo.cup != NULL && roundqueue.size > 0)
{
roundnum = roundqueue.roundnum;
roundsleft = grandprixinfo.cup->numlevels - roundnum;
}
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].spectator)
{
continue;
}
if (players[i].score < lowestscore)
{
lowestscore = players[i].score;
}
if (players[i].bot == true && players[i].botvars.difficulty < lowestdifficulty)
{
lowestdifficulty = players[i].botvars.difficulty;
}
}
// In the early game, difficulty is more important.
// This will try to influence the higher difficulty bots to get rival more often & get even more points.
// However, when we're running low on matches left, we need to focus more on raw score!
return ((difficulty - lowestdifficulty) * roundsleft) + ((score - lowestscore) * roundnum);
}
/*-------------------------------------------------- /*--------------------------------------------------
void K_UpdateGrandPrixBots(void) void K_UpdateGrandPrixBots(void)
@ -374,6 +436,8 @@ void K_UpdateGrandPrixBots(void)
players[i].spectator = K_BotDefaultSpectator(); players[i].spectator = K_BotDefaultSpectator();
} }
K_AssignFoes();
if (grandprixinfo.wonround == false) if (grandprixinfo.wonround == false)
{ {
return; return;

View file

@ -7314,7 +7314,7 @@ static void K_DrawBotDebugger(void)
V_DrawSmallString(8, 14, 0, va("Difficulty: %d / %d", bot->botvars.difficulty, MAXBOTDIFFICULTY)); V_DrawSmallString(8, 14, 0, va("Difficulty: %d / %d", bot->botvars.difficulty, MAXBOTDIFFICULTY));
V_DrawSmallString(8, 18, 0, va("Difficulty increase: %d", bot->botvars.diffincrease)); V_DrawSmallString(8, 18, 0, va("Difficulty increase: %d", bot->botvars.diffincrease));
V_DrawSmallString(8, 22, 0, va("Rival: %d", (UINT8)(bot->botvars.rival == true))); V_DrawSmallString(8, 22, 0, va("Rival / Foe: %d / %d", (UINT8)(bot->botvars.rival == true), (UINT8)(bot->botvars.foe == true)));
V_DrawSmallString(8, 26, 0, va("Rubberbanding: %.02f", FIXED_TO_FLOAT(bot->botvars.rubberband) * 100.0f)); V_DrawSmallString(8, 26, 0, va("Rubberbanding: %.02f", FIXED_TO_FLOAT(bot->botvars.rubberband) * 100.0f));
V_DrawSmallString(8, 32, 0, va("Item delay: %d", bot->botvars.itemdelay)); V_DrawSmallString(8, 32, 0, va("Item delay: %d", bot->botvars.itemdelay));

View file

@ -1621,6 +1621,8 @@ static boolean K_TryDraft(player_t *player, mobj_t *dest, fixed_t minDist, fixed
// Double speed for the rival! // Double speed for the rival!
if (player->botvars.rival || cv_levelskull.value) if (player->botvars.rival || cv_levelskull.value)
player->draftpower += add; player->draftpower += add;
else if (player->botvars.foe)
player->draftpower += add/2;
else if (dest->player->bot) // Reduce bot gluts. else if (dest->player->bot) // Reduce bot gluts.
player->draftpower -= 3*add/4; player->draftpower -= 3*add/4;
} }
@ -3554,6 +3556,10 @@ static fixed_t K_RingDurationBoost(const player_t *player)
// x2.0 for Rival // x2.0 for Rival
ret *= 2; ret *= 2;
} }
else if (player->botvars.foe)
{
ret = 3 * ret / 2;
}
} }
return ret; return ret;
@ -3976,6 +3982,11 @@ fixed_t K_GetKartSpeed(const player_t *player, boolean doboostpower, boolean dor
// +10% top speed for the rival // +10% top speed for the rival
finalspeed = FixedMul(finalspeed, 11*FRACUNIT/10); finalspeed = FixedMul(finalspeed, 11*FRACUNIT/10);
} }
else if (player->bot && player->botvars.foe)
{
// +5% for foes
finalspeed = FixedMul(finalspeed, 21*FRACUNIT/20);
}
} }
} }
@ -10965,6 +10976,13 @@ void K_KartResetPlayerColor(player_t *player)
goto base; goto base;
} }
if (player->botvars.foe)
{
player->mo->colorized = true;
player->mo->color = SKINCOLOR_BLACK;
goto finalise;
}
if (player->eggmanexplode) // You're gonna diiiiie if (player->eggmanexplode) // You're gonna diiiiie
{ {
const INT32 flashtime = 4<<(player->eggmanexplode/TICRATE); const INT32 flashtime = 4<<(player->eggmanexplode/TICRATE);

View file

@ -138,6 +138,7 @@ static inline void P_ArchivePlayer(savebuffer_t *save)
WRITEUINT8(save->p, players[i].botvars.difficulty); WRITEUINT8(save->p, players[i].botvars.difficulty);
WRITEUINT8(save->p, (UINT8)players[i].botvars.rival); WRITEUINT8(save->p, (UINT8)players[i].botvars.rival);
WRITEUINT8(save->p, (UINT8)players[i].botvars.foe);
WRITEUINT32(save->p, players[i].score); WRITEUINT32(save->p, players[i].score);
} }
@ -195,6 +196,7 @@ static boolean P_UnArchivePlayer(savebuffer_t *save)
savedata.bots[pid].difficulty = READUINT8(save->p); savedata.bots[pid].difficulty = READUINT8(save->p);
savedata.bots[pid].rival = (boolean)READUINT8(save->p); savedata.bots[pid].rival = (boolean)READUINT8(save->p);
savedata.bots[pid].foe = (boolean)READUINT8(save->p);
savedata.bots[pid].score = READUINT32(save->p); savedata.bots[pid].score = READUINT32(save->p);
} }
@ -765,6 +767,7 @@ static void P_NetArchivePlayers(savebuffer_t *save)
WRITEUINT8(save->p, players[i].botvars.difficulty); WRITEUINT8(save->p, players[i].botvars.difficulty);
WRITEUINT8(save->p, players[i].botvars.diffincrease); WRITEUINT8(save->p, players[i].botvars.diffincrease);
WRITEUINT8(save->p, players[i].botvars.rival); WRITEUINT8(save->p, players[i].botvars.rival);
WRITEUINT8(save->p, players[i].botvars.foe);
WRITEFIXED(save->p, players[i].botvars.rubberband); WRITEFIXED(save->p, players[i].botvars.rubberband);
WRITEUINT8(save->p, players[i].botvars.bumpslow); WRITEUINT8(save->p, players[i].botvars.bumpslow);
WRITEUINT32(save->p, players[i].botvars.itemdelay); WRITEUINT32(save->p, players[i].botvars.itemdelay);
@ -1430,6 +1433,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
players[i].botvars.difficulty = READUINT8(save->p); players[i].botvars.difficulty = READUINT8(save->p);
players[i].botvars.diffincrease = READUINT8(save->p); players[i].botvars.diffincrease = READUINT8(save->p);
players[i].botvars.rival = (boolean)READUINT8(save->p); players[i].botvars.rival = (boolean)READUINT8(save->p);
players[i].botvars.foe = (boolean)READUINT8(save->p);
players[i].botvars.rubberband = READFIXED(save->p); players[i].botvars.rubberband = READFIXED(save->p);
players[i].botvars.bumpslow = READUINT8(save->p); players[i].botvars.bumpslow = READUINT8(save->p);
players[i].botvars.itemdelay = READUINT32(save->p); players[i].botvars.itemdelay = READUINT32(save->p);

View file

@ -46,6 +46,7 @@ struct savedata_bot_s
UINT8 skin; UINT8 skin;
UINT8 difficulty; UINT8 difficulty;
boolean rival; boolean rival;
boolean foe;
UINT32 score; UINT32 score;
}; };