From 34b12cc43604da4d9032e7e51a429c78d28c5443 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Sun, 14 May 2023 18:26:31 -0400 Subject: [PATCH 01/12] Bots: Improve spindash behaviors further - Added new spindashing conditions: - In offroad. - Trying to go up-hill on a steep slope. - On a conveyor going the opposite direction they're trying to go. (Barren Badlands) - Instead of checking for any spindash condition and adding to the confirm timer, it adds to the confirm timer for every single condition. - Increased the confirm time from 2sec to 3sec to accommodate for this change. - Fixed not using precise momentum angle, causing the low-speed scenarios spindash is intended for to not always work. - Bots now check for spindash conditions even in damage states, allowing for spindashes after a spinout. - Fixed the spindash confirm not decreasing over time when entering a condition and then exiting it, causing random spindashes when it wouldn't make sense. --- src/k_bot.c | 51 ++++++++++++++++++++++++++++++++++++++++++++------- src/k_bot.h | 2 +- src/k_kart.c | 23 +++++++++++++++-------- src/k_kart.h | 5 +++-- 4 files changed, 63 insertions(+), 18 deletions(-) diff --git a/src/k_bot.c b/src/k_bot.c index 9f7fdc25e..b03b0d1b7 100644 --- a/src/k_bot.c +++ b/src/k_bot.c @@ -859,10 +859,10 @@ static UINT8 K_TrySpindash(player_t *player) const fixed_t baseAccel = K_GetNewSpeed(player) - oldSpeed; const fixed_t speedDiff = player->speed - player->lastspeed; - const INT32 angleDiff = AngleDelta(player->mo->angle, K_MomentumAngle(player->mo)); + const INT32 angleDiff = AngleDelta(player->mo->angle, K_MomentumAngleReal(player->mo)); if (player->spindashboost || player->tiregrease // You just released a spindash, you don't need to try again yet, jeez. - || P_PlayerInPain(player) || !P_IsObjectOnGround(player->mo)) // Not in a state where we want 'em to spindash. + || P_IsObjectOnGround(player->mo) == false) // Not in a state where we want 'em to spindash. { player->botvars.spindashconfirm = 0; return 0; @@ -913,15 +913,52 @@ static UINT8 K_TrySpindash(player_t *player) else { // Logic for normal racing. - if (speedDiff < (baseAccel / 8) // Moving too slowly - || angleDiff > ANG60) // Being pushed backwards + boolean anyCondition = false; + boolean uphill = false; + +#define AddForCondition(x) \ + if (x) \ + { \ + anyCondition = true;\ + if (player->botvars.spindashconfirm < BOTSPINDASHCONFIRM) \ + { \ + player->botvars.spindashconfirm++; \ + } \ + } + + if (player->mo->standingslope != NULL) { - if (player->botvars.spindashconfirm < BOTSPINDASHCONFIRM) + const pslope_t *slope = player->mo->standingslope; + + if (!(slope->flags & SL_NOPHYSICS) && abs(slope->zdelta) >= FRACUNIT/21) { - player->botvars.spindashconfirm++; + fixed_t slopeDot = 0; + angle_t angle = K_MomentumAngle(player->mo) - slope->xydirection; + + if (P_MobjFlip(player->mo) * slope->zdelta < 0) + { + angle ^= ANGLE_180; + } + + slopeDot = FINECOSINE(angle >> ANGLETOFINESHIFT); + uphill = (slopeDot < -FRACUNIT/2); } } - else if (player->botvars.spindashconfirm >= BOTSPINDASHCONFIRM) + + AddForCondition(player->offroad > 0); // In offroad + AddForCondition(speedDiff < (baseAccel >> 4)); // Moving too slowly + AddForCondition(angleDiff > ANG60); // Being pushed backwards + AddForCondition(uphill == true); // Going up a steep slope + + if (player->cmomx || player->cmomy) + { + angle_t cAngle = R_PointToDist2(0, 0, player->cmomx, player->cmomy); + angle_t cDelta = AngleDelta(player->mo->angle, cAngle); + + AddForCondition(cDelta > ANGLE_90); // Conveyor going against you + } + + if (anyCondition == false) { if (player->botvars.spindashconfirm > 0) { diff --git a/src/k_bot.h b/src/k_bot.h index d98f7a1e9..630d1fbb0 100644 --- a/src/k_bot.h +++ b/src/k_bot.h @@ -32,7 +32,7 @@ extern "C" { #define BOTTURNCONFIRM 4 // How many tics without being able to accelerate before we'll let you spindash. -#define BOTSPINDASHCONFIRM (2*TICRATE) +#define BOTSPINDASHCONFIRM (3*TICRATE) // Point for bots to aim for struct botprediction_t { diff --git a/src/k_kart.c b/src/k_kart.c index 8f4240414..c535a8c18 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -2622,7 +2622,7 @@ static void K_HandleDelayedHitByEm(player_t *player) void K_MomentumToFacing(player_t *player) { - angle_t dangle = player->mo->angle - K_MomentumAngle(player->mo); + angle_t dangle = player->mo->angle - K_MomentumAngleReal(player->mo); if (dangle > ANGLE_180) dangle = InvAngle(dangle); @@ -3468,14 +3468,21 @@ fixed_t K_3dKartMovement(player_t *player) return finalspeed; } -fixed_t K_MomentumThreshold(const mobj_t *mo) +angle_t K_MomentumAngleEx(const mobj_t *mo, const fixed_t threshold) { - return 6 * mo->scale; + if (FixedHypot(mo->momx, mo->momy) > threshold) + { + return R_PointToAngle2(0, 0, mo->momx, mo->momy); + } + else + { + return mo->angle; // default to facing angle, rather than 0 + } } -angle_t K_MomentumAngle(mobj_t *mo) +angle_t K_MomentumAngleReal(const mobj_t *mo) { - if (FixedHypot(mo->momx, mo->momy) > K_MomentumThreshold(mo)) + if (mo->momx || mo->momy) { return R_PointToAngle2(0, 0, mo->momx, mo->momy); } @@ -3530,8 +3537,8 @@ void K_SetHitLagForObjects(mobj_t *mo1, mobj_t *mo2, INT32 tics, boolean fromDam const fixed_t scaleDiff = abs(mo2->scale - mo1->scale); - angle_t mo1angle = K_MomentumAngle(mo1); - angle_t mo2angle = K_MomentumAngle(mo2); + angle_t mo1angle = K_MomentumAngleReal(mo1); + angle_t mo2angle = K_MomentumAngleReal(mo2); INT32 angleDiff = 0; if (mo1speed > 0 && mo2speed > 0) @@ -10229,7 +10236,7 @@ boolean K_FastFallBounce(player_t *player) static void K_AirFailsafe(player_t *player) { - const fixed_t maxSpeed = K_MomentumThreshold(player->mo); + const fixed_t maxSpeed = K_MomentumAngle(player->mo); const fixed_t thrustSpeed = 6*player->mo->scale; // 10*player->mo->scale if (player->speed > maxSpeed // Above the max speed that you're allowed to use this technique. diff --git a/src/k_kart.h b/src/k_kart.h index bddba4024..839cadc6c 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -83,8 +83,9 @@ void K_KartResetPlayerColor(player_t *player); boolean K_PressingEBrake(player_t *player); void K_KartPlayerThink(player_t *player, ticcmd_t *cmd); void K_KartPlayerAfterThink(player_t *player); -fixed_t K_MomentumThreshold(const mobj_t *mo); -angle_t K_MomentumAngle(mobj_t *mo); +angle_t K_MomentumAngleEx(const mobj_t *mo, const fixed_t threshold); +angle_t K_MomentumAngleReal(const mobj_t *mo); +#define K_MomentumAngle(mo) K_MomentumAngleEx(mo, 6 * mo->scale) void K_AddHitLag(mobj_t *mo, INT32 tics, boolean fromDamage); void K_SetHitLagForObjects(mobj_t *mo1, mobj_t *mo2, INT32 tics, boolean fromDamage); void K_AwardPlayerRings(player_t *player, INT32 rings, boolean overload); From d55c9038cc1b0834078b9adb114c664fb3cd3329 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Sun, 14 May 2023 19:32:51 -0400 Subject: [PATCH 02/12] Bots: Can now use Ring Shooter Reuses most of the anti-grief code, but with a very low timer (5sec) and they simply hold Y when reaching it until they eventually respawn from the ring shooter. --- src/d_player.h | 1 + src/k_bot.c | 40 +++++++++++++++++++++++++++++++++++ src/k_bot.h | 5 ++++- src/k_respawn.c | 3 ++- src/p_local.h | 1 + src/p_saveg.c | 2 ++ src/p_user.c | 55 +++++++++++++++++++++++++++---------------------- 7 files changed, 80 insertions(+), 27 deletions(-) diff --git a/src/d_player.h b/src/d_player.h index e9f166399..1cb70aeaa 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -332,6 +332,7 @@ struct botvars_t SINT8 turnconfirm; // Confirm turn direction tic_t spindashconfirm; // When high enough, they will try spindashing + UINT32 respawnconfirm; // When high enough, they will use Ring Shooter }; // player_t struct for round-specific condition tracking diff --git a/src/k_bot.c b/src/k_bot.c index b03b0d1b7..dd777b0bb 100644 --- a/src/k_bot.c +++ b/src/k_bot.c @@ -29,6 +29,7 @@ #include "k_race.h" // finishBeamLine #include "m_perfstats.h" #include "k_podium.h" +#include "k_respawn.h" /*-------------------------------------------------- boolean K_AddBot(UINT8 skin, UINT8 difficulty, UINT8 *p) @@ -971,6 +972,38 @@ static UINT8 K_TrySpindash(player_t *player) return 0; } +/*-------------------------------------------------- + static boolean K_TryRingShooter(player_t *player) + + Determines conditions where the bot should attempt to respawn. + + Input Arguments:- + player - Bot player to check. + + Return:- + true if we want to hold the respawn button, otherwise false. +--------------------------------------------------*/ +static boolean K_TryRingShooter(player_t *player) +{ + if (player->respawn.state != RESPAWNST_NONE) + { + // We're already respawning! + return false; + } + + if ((gametyperules & GTR_CIRCUIT) == 0 || (leveltime <= starttime)) + { + // Only do this during a Race that has started. + return false; + } + + // Our anti-grief system is already a perfect system + // for determining if we're not making progress, so + // lets reuse it for bot respawning! + P_IncrementGriefValue(player, &player->botvars.respawnconfirm, BOTRESPAWNCONFIRM); + return (player->botvars.respawnconfirm >= BOTRESPAWNCONFIRM); +} + /*-------------------------------------------------- static void K_DrawPredictionDebug(botprediction_t *predict, player_t *player) @@ -1484,6 +1517,13 @@ void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd) return; } + if (K_TryRingShooter(player) == true) + { + // We want to respawn. Simply hold Y and stop here! + cmd->buttons |= (BT_RESPAWN | BT_EBRAKEMASK); + return; + } + if (player->trickpanel != 0) { K_BotTrick(player, cmd, botController); diff --git a/src/k_bot.h b/src/k_bot.h index 630d1fbb0..274b57858 100644 --- a/src/k_bot.h +++ b/src/k_bot.h @@ -31,9 +31,12 @@ extern "C" { // Made it as small as possible without making it look like the bots are twitching constantly. #define BOTTURNCONFIRM 4 -// How many tics without being able to accelerate before we'll let you spindash. +// How many tics with only one spindash-viable condition before we'll let you spindash. #define BOTSPINDASHCONFIRM (3*TICRATE) +// How many tics without being able to make progress before we'll let you respawn. +#define BOTRESPAWNCONFIRM (5*TICRATE) + // Point for bots to aim for struct botprediction_t { fixed_t x, y; diff --git a/src/k_respawn.c b/src/k_respawn.c index 8f0c756f1..46851a02f 100644 --- a/src/k_respawn.c +++ b/src/k_respawn.c @@ -147,7 +147,6 @@ void K_DoIngameRespawn(player_t *player) K_DoFault(player); } - player->ringboost = 0; player->driftboost = player->strongdriftboost = 0; player->gateBoost = 0; @@ -292,6 +291,8 @@ void K_DoIngameRespawn(player_t *player) player->respawn.airtimer = player->airtime; player->respawn.truedeath = !!(player->pflags & PF_FAULT); + player->botvars.respawnconfirm = 0; + player->mo->flags |= MF_NOCLIPTHING; } diff --git a/src/p_local.h b/src/p_local.h index 59e9c0315..738faffd3 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -209,6 +209,7 @@ void P_PlayerAfterThink(player_t *player); void P_DoPlayerExit(player_t *player, pflags_t flags); void P_DoAllPlayersExit(pflags_t flags, boolean givelife); void P_DoTimeOver(player_t *player); +void P_IncrementGriefValue(player_t *player, UINT32 *grief, const UINT32 griefMax); void P_CheckRaceGriefing(player_t *player, boolean dopunishment); void P_ResetPlayerCheats(void); diff --git a/src/p_saveg.c b/src/p_saveg.c index 027174dad..9acb3d980 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -443,6 +443,7 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEUINT32(save->p, players[i].botvars.itemconfirm); WRITESINT8(save->p, players[i].botvars.turnconfirm); WRITEUINT32(save->p, players[i].botvars.spindashconfirm); + WRITEUINT32(save->p, players[i].botvars.respawnconfirm); // itemroulette_t WRITEUINT8(save->p, players[i].itemRoulette.active); @@ -825,6 +826,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].botvars.itemconfirm = READUINT32(save->p); players[i].botvars.turnconfirm = READSINT8(save->p); players[i].botvars.spindashconfirm = READUINT32(save->p); + players[i].botvars.respawnconfirm = READUINT32(save->p); // itemroulette_t players[i].itemRoulette.active = (boolean)READUINT8(save->p); diff --git a/src/p_user.c b/src/p_user.c index a3ac135a6..3afb5b7bf 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -4640,11 +4640,8 @@ void P_PlayerAfterThink(player_t *player) player->mo->pmomz = 0; } -void P_CheckRaceGriefing(player_t *player, boolean dopunishment) +void P_IncrementGriefValue(player_t *player, UINT32 *grief, const UINT32 griefMax) { - const UINT32 griefMax = cv_antigrief.value * TICRATE; - const UINT8 n = player - players; - const fixed_t requireDist = (12*player->mo->scale) / FRACUNIT; INT32 progress = player->distancetofinishprev - player->distancetofinish; boolean exceptions = ( @@ -4654,6 +4651,34 @@ void P_CheckRaceGriefing(player_t *player, boolean dopunishment) || (player->justbumped > 0 && player->justbumped < bumptime-1) ); + if (!exceptions && (progress < requireDist)) + { + // If antigrief is disabled, we don't want the + // player getting into a hole so deep no amount + // of good behaviour could ever make up for it. + if (*grief < griefMax) + { + // Making no progress, start counting against you. + *grief = *grief + 1; + if (progress < -requireDist && *grief < griefMax) + { + // Making NEGATIVE progress? Start counting even harder. + *grief = *grief + 1; + } + } + } + else if (*grief > 0) + { + // Playing normally. + *grief = *grief - 1; + } +} + +void P_CheckRaceGriefing(player_t *player, boolean dopunishment) +{ + const UINT32 griefMax = cv_antigrief.value * TICRATE; + const UINT8 n = player - players; + // Don't punish if the cvar is turned off, // otherwise NOBODY would be able to play! if (griefMax == 0) @@ -4661,27 +4686,7 @@ void P_CheckRaceGriefing(player_t *player, boolean dopunishment) dopunishment = false; } - if (!exceptions && (progress < requireDist)) - { - // If antigrief is disabled, we don't want the - // player getting into a hole so deep no amount - // of good behaviour could ever make up for it. - if (player->griefValue < griefMax) - { - // Making no progress, start counting against you. - player->griefValue++; - if (progress < -requireDist && player->griefValue < griefMax) - { - // Making NEGATIVE progress? Start counting even harder. - player->griefValue++; - } - } - } - else if (player->griefValue > 0) - { - // Playing normally. - player->griefValue--; - } + P_IncrementGriefValue(player, &player->griefValue, griefMax); if (dopunishment && player->griefValue >= griefMax) { From 77ab86ab34174723dd4c71c05859a1ce7079729c Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Sun, 14 May 2023 19:33:38 -0400 Subject: [PATCH 03/12] Bots: Increase spindash confirm further Felt like they were spindashing just sliiiightly too often. --- src/k_bot.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_bot.h b/src/k_bot.h index 274b57858..c06c90a93 100644 --- a/src/k_bot.h +++ b/src/k_bot.h @@ -32,7 +32,7 @@ extern "C" { #define BOTTURNCONFIRM 4 // How many tics with only one spindash-viable condition before we'll let you spindash. -#define BOTSPINDASHCONFIRM (3*TICRATE) +#define BOTSPINDASHCONFIRM (4*TICRATE) // How many tics without being able to make progress before we'll let you respawn. #define BOTRESPAWNCONFIRM (5*TICRATE) From d367bacc39dd8d98ed9b240833a4a18ac013880b Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Mon, 15 May 2023 03:05:28 -0400 Subject: [PATCH 04/12] Bots: Decide what item they want without using RNG Makes Lv.1 a lot less aggressive, and Lv.MAX slightly more aggressive. But the main advantage is simply that they are deterministic again and I can :D --- src/d_player.h | 20 +++++++ src/k_bot.h | 22 ++++++++ src/k_botitem.c | 134 +++++++++++++++++++++++++++++++++++++++++++++-- src/k_roulette.c | 72 +++++++++++++++++++++++-- src/k_roulette.h | 17 ++++++ src/p_saveg.c | 4 ++ 6 files changed, 263 insertions(+), 6 deletions(-) diff --git a/src/d_player.h b/src/d_player.h index 1cb70aeaa..a6aa69675 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -333,6 +333,9 @@ struct botvars_t tic_t spindashconfirm; // When high enough, they will try spindashing UINT32 respawnconfirm; // When high enough, they will use Ring Shooter + + UINT8 roulettePriority; // What items to go for on the roulette + tic_t rouletteTimeout; // If it takes too long to decide, try lowering priority until we find something valid. }; // player_t struct for round-specific condition tracking @@ -399,6 +402,23 @@ struct itemroulette_t boolean eggman; }; +// enum for bot item priorities +typedef enum +{ + BOT_ITEM_PR__FALLBACK, // Priority decrement fallback -- end the bot's roulette asap + BOT_ITEM_PR_NEUTRAL, // Default priority + BOT_ITEM_PR_FRONTRUNNER, + BOT_ITEM_PR_SPEED, + // Priorities beyond this point are explicitly + // used when any item from their priority group + // exists in the roulette at all. + BOT_ITEM_PR__OVERRIDES, + BOT_ITEM_PR_RINGDEBT = BOT_ITEM_PR__OVERRIDES, + BOT_ITEM_PR_POWER, + BOT_ITEM_PR_SPB, + BOT_ITEM_PR__MAX +} botItemPriority_e; + // player_t struct for loop state typedef struct { fixed_t radius; diff --git a/src/k_bot.h b/src/k_bot.h index c06c90a93..595938af6 100644 --- a/src/k_bot.h +++ b/src/k_bot.h @@ -37,6 +37,9 @@ extern "C" { // How many tics without being able to make progress before we'll let you respawn. #define BOTRESPAWNCONFIRM (5*TICRATE) +// How long it takes for a Lv.1 bot to decide to pick an item. +#define BOT_ITEM_DECISION_TIME (3*TICRATE) + // Point for bots to aim for struct botprediction_t { fixed_t x, y; @@ -277,6 +280,25 @@ void K_UpdateBotGameplayVars(player_t *player); void K_BotItemUsage(player_t *player, ticcmd_t *cmd, INT16 turnamt); + +/*-------------------------------------------------- + void K_BotPickItemPriority(player_t *player) + + Sets a bot's desired item classification + based on what they're , + and delays based on their difficulty + intended to be run when starting a roulette. + + Input Arguments:- + player - Bot to set the item classification for. + + Return:- + N/A +--------------------------------------------------*/ + +void K_BotPickItemPriority(player_t *player); + + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/k_botitem.c b/src/k_botitem.c index 58e448d1f..c4ecc4af3 100644 --- a/src/k_botitem.c +++ b/src/k_botitem.c @@ -1394,15 +1394,54 @@ static void K_BotItemRings(player_t *player, ticcmd_t *cmd) --------------------------------------------------*/ static void K_BotItemRouletteMash(player_t *player, ticcmd_t *cmd) { + // 12 tics late for Lv.1, frame-perfect for Lv.MAX + const tic_t confirmTime = (MAXBOTDIFFICULTY - player->botvars.difficulty); + if (K_ItemButtonWasDown(player) == true) { return; } - // TODO: Would be nice to implement smarter behavior - // for selecting items. + if (player->botvars.roulettePriority == BOT_ITEM_PR__FALLBACK) + { + // No items were part of our list, so set immediately. + player->botvars.itemconfirm = confirmTime + 1; + } + else if (player->botvars.itemconfirm > 0) + { + // Delaying our reaction time a bit... + player->botvars.itemconfirm++; + } + else + { + botItemPriority_e currentPriority = K_GetBotItemPriority( + player->itemRoulette.itemList[ player->itemRoulette.index ] + ); - cmd->buttons |= BT_ATTACK; + if (player->botvars.roulettePriority == currentPriority) + { + // This is the item we want! Start timing! + player->botvars.itemconfirm++; + } + else + { + // Not the time we want... if we take too long, + // reduce priority until we get to a valid one. + player->botvars.rouletteTimeout++; + + if (player->botvars.rouletteTimeout > player->itemRoulette.itemListLen * player->itemRoulette.speed) + { + player->botvars.roulettePriority--; + player->botvars.rouletteTimeout = 0; + } + } + } + + if (player->botvars.itemconfirm > confirmTime) + { + // We've waited out our reaction time -- press the button now! + cmd->buttons |= BT_ATTACK; + } } /*-------------------------------------------------- @@ -1568,3 +1607,92 @@ void K_BotItemUsage(player_t *player, ticcmd_t *cmd, INT16 turnamt) } } } + +/*-------------------------------------------------- + void K_BotPickItemPriority(player_t *player) + + See header file for description. +--------------------------------------------------*/ +void K_BotPickItemPriority(player_t *player) +{ + const fixed_t closeDistance = FixedMul(1280 * mapobjectscale, K_GetKartGameSpeedScalar(gamespeed)); + size_t i; + + // Roulette reaction time. This is how long to wait before considering items. + // Takes 3 seconds for Lv.1, is instant for Lv.MAX + player->botvars.itemdelay = ((MAXBOTDIFFICULTY - player->botvars.difficulty) * BOT_ITEM_DECISION_TIME) / (MAXBOTDIFFICULTY - 1); + player->botvars.itemconfirm = 0; + + // Set neutral items by default. + player->botvars.roulettePriority = BOT_ITEM_PR_NEUTRAL; + player->botvars.rouletteTimeout = 0; + + // Check for items that are extremely high priority. + for (i = 0; i < player->itemRoulette.itemListLen; i++) + { + botItemPriority_e priority = K_GetBotItemPriority( player->itemRoulette.itemList[i] ); + + if (priority < BOT_ITEM_PR__OVERRIDES) + { + // Not high enough to override. + continue; + } + + player->botvars.roulettePriority = max( player->botvars.roulettePriority, priority ); + } + + if (player->botvars.roulettePriority >= BOT_ITEM_PR__OVERRIDES) + { + // Selected a priority in the loop above. + return; + } + + for (i = 0; i < MAXPLAYERS; i++) + { + player_t *other = NULL; + fixed_t distance = INT32_MAX; + + if (playeringame[i] == false) + { + continue; + } + + other = &players[i]; + if (other->spectator == true || P_MobjWasRemoved(other->mo) == true) + { + continue; + } + + distance = P_AproxDistance( + P_AproxDistance( + other->mo->x - player->mo->x, + other->mo->y - player->mo->y + ), + other->mo->z - player->mo->z + ); + + if (distance < closeDistance) + { + // A player is relatively close. + break; + } + } + + if (i == MAXPLAYERS) + { + // Players are nearby, stay as neutral priority. + return; + } + + // Players are far away enough to give you breathing room. + if (player->position == 1) + { + // Frontrunning, so pick frontrunner items! + player->botvars.roulettePriority = BOT_ITEM_PR_FRONTRUNNER; + } + else + { + // Behind, so pick speed items! + player->botvars.roulettePriority = BOT_ITEM_PR_SPEED; + } +} diff --git a/src/k_roulette.c b/src/k_roulette.c index 4efa164a4..db97e1695 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -262,6 +262,71 @@ boolean K_ItemSingularity(kartitems_t item) } } +/*-------------------------------------------------- + botItemPriority_e K_GetBotItemPriority(kartitems_t type) + + See header file for description. +--------------------------------------------------*/ +botItemPriority_e K_GetBotItemPriority(kartitems_t type) +{ + switch (type) + { + case KITEM_SPB: + { + // Items that are intended to improve the game balance for everyone. + return BOT_ITEM_PR_SPB; + } + case KITEM_INVINCIBILITY: + case KITEM_GROW: + case KITEM_SHRINK: + case KITEM_LIGHTNINGSHIELD: + case KITEM_BUBBLESHIELD: + case KITEM_FLAMESHIELD: + { + // Items that drastically improve your own defense and/or speed. + return BOT_ITEM_PR_POWER; + } + case KITEM_SUPERRING: + { + // Items that get you out of ring debt. + return BOT_ITEM_PR_RINGDEBT; + } + case KITEM_SNEAKER: + case KITEM_ROCKETSNEAKER: + case KITEM_GARDENTOP: + case KITEM_POGOSPRING: + { + // Used when not in 1st place and relatively far from players. + // Items that give you speed with no protection. + return BOT_ITEM_PR_SPEED; + } + case KITEM_HYUDORO: + case KITEM_LANDMINE: + case KITEM_DROPTARGET: + case KITEM_EGGMAN: + case KITEM_GACHABOM: + { + // Used when in 1st place and relatively far from players. + // Typically attack items that don't give you protection. + return BOT_ITEM_PR_FRONTRUNNER; + } + case KITEM_ORBINAUT: + case KITEM_BALLHOG: + case KITEM_JAWZ: + case KITEM_BANANA: + case KITEM_MINE: + { + // Used in all other instances (close to other players, no priority override) + // Typically attack items that give you protection. + return BOT_ITEM_PR_NEUTRAL; + } + default: + { + return BOT_ITEM_PR__FALLBACK; + } + } +} + /*-------------------------------------------------- static fixed_t K_ItemOddsScale(UINT8 playerCount) @@ -1351,9 +1416,10 @@ void K_StartItemRoulette(player_t *const player) K_FillItemRouletteData(player, roulette); - // Make the bots select their item after a little while. - // One of the few instances of bot RNG, would be nice to remove it. - player->botvars.itemdelay = P_RandomRange(PR_UNDEFINED, TICRATE, TICRATE*3); + if (K_PlayerUsesBotMovement(player) == true) + { + K_BotPickItemPriority(player); + } // Prevent further duplicates of items that // are intended to only have one out at a time. diff --git a/src/k_roulette.h b/src/k_roulette.h index f4afb4988..f31345e8f 100644 --- a/src/k_roulette.h +++ b/src/k_roulette.h @@ -57,6 +57,23 @@ boolean K_ItemEnabled(kartitems_t item); boolean K_ItemSingularity(kartitems_t item); +/*-------------------------------------------------- + botItemPriority_e K_GetBotItemPriority(kartitems_t type) + + Returns an item's priority value, which + bots use to determine what kind of item they + want when the roulette is started. + + Input Arguments:- + item - The item to check. + + Return:- + The item's priority type. +--------------------------------------------------*/ + +botItemPriority_e K_GetBotItemPriority(kartitems_t type); + + /*-------------------------------------------------- INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, UINT8 pos, kartitems_t item); diff --git a/src/p_saveg.c b/src/p_saveg.c index 9acb3d980..88c031329 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -444,6 +444,8 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITESINT8(save->p, players[i].botvars.turnconfirm); WRITEUINT32(save->p, players[i].botvars.spindashconfirm); WRITEUINT32(save->p, players[i].botvars.respawnconfirm); + WRITEUINT8(save->p, players[i].botvars.roulettePriority); + WRITEUINT32(save->p, players[i].botvars.rouletteTimeout); // itemroulette_t WRITEUINT8(save->p, players[i].itemRoulette.active); @@ -827,6 +829,8 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].botvars.turnconfirm = READSINT8(save->p); players[i].botvars.spindashconfirm = READUINT32(save->p); players[i].botvars.respawnconfirm = READUINT32(save->p); + players[i].botvars.roulettePriority = READUINT8(save->p); + players[i].botvars.rouletteTimeout = READUINT32(save->p); // itemroulette_t players[i].itemRoulette.active = (boolean)READUINT8(save->p); From a7f27948f333e7f33fa8fe38fb72de1c516e1bca Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Mon, 15 May 2023 04:00:24 -0400 Subject: [PATCH 05/12] Waypoints: If a normal & shortcut paths overlap, use the normal path --- src/k_waypoint.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/k_waypoint.cpp b/src/k_waypoint.cpp index 191a751ba..640eee0a3 100644 --- a/src/k_waypoint.cpp +++ b/src/k_waypoint.cpp @@ -327,6 +327,13 @@ static void K_CompareOverlappingWaypoint boolean pathfindsuccess = false; path_t pathtofinish = {0}; + if (K_GetWaypointIsShortcut(*bestwaypoint) == false + && K_GetWaypointIsShortcut(checkwaypoint) == true) + { + // If it's a shortcut, don't use it. + return; + } + pathfindsuccess = K_PathfindToWaypoint(checkwaypoint, finishline, &pathtofinish, useshortcuts, huntbackwards); From c43fc4e924afb6d546f3fef046aebbdfa3ef7944 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Mon, 15 May 2023 06:13:01 -0400 Subject: [PATCH 06/12] GP: New difficulty increase algorithm Before, it did level up based on how much the status quo was messed up (if a loser bot got pushed in front, or a rival bot got pushed ahead). Now, bots will additionally level up based on the best of the 4 players' overall performance, and the performance of the human player that beat them. This means that back of the pack bots will level up more often and the rival won't level up if you place poorly. The new formula ensures ALL bots are guaranteed at least 1 level up if you come in first place, whereas the old formula had a decent chance of not leveling up some bots. However, loser Chao getting boosted from 12th into 1st, or your rival getting sent to 7th, will still wildly piss them off :B --- src/k_grandprix.c | 86 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 66 insertions(+), 20 deletions(-) diff --git a/src/k_grandprix.c b/src/k_grandprix.c index 52c82d7a3..6b19b8f06 100644 --- a/src/k_grandprix.c +++ b/src/k_grandprix.c @@ -469,8 +469,22 @@ static UINT8 K_BotExpectedStanding(player_t *bot) --------------------------------------------------*/ void K_IncreaseBotDifficulty(player_t *bot) { - UINT8 expectedstanding; - INT16 standingdiff; + UINT8 playerCount = 0; + UINT8 wonCount = 0; + + UINT8 humanThatBeatUs = 0; + INT16 beatenDelta = 0; + + UINT8 winnerHuman = UINT8_MAX; + INT16 winnerDelta = 0; + + UINT8 statusQuo = 1; + INT16 disruptDelta = 0; + + INT16 increase = 1; + size_t i = SIZE_MAX; + + bot->botvars.diffincrease = 0; if (bot->botvars.difficulty >= MAXBOTDIFFICULTY) { @@ -478,33 +492,65 @@ void K_IncreaseBotDifficulty(player_t *bot) return; } - // Increment bot difficulty based on what position you were meant to come in! - expectedstanding = K_BotExpectedStanding(bot); - standingdiff = expectedstanding - bot->position; + // Increment bot difficulty based on + // how much they were beaten by a player! - if (standingdiff >= -2) + // Find the worst-placing player that still beat us. + for (i = 0; i < MAXPLAYERS; i++) { - UINT8 increase; + player_t *other = NULL; - if (standingdiff > 5) + if (playeringame[i] == false) { - increase = 3; - } - else if (standingdiff > 2) - { - increase = 2; - } - else - { - increase = 1; + continue; } - bot->botvars.diffincrease = increase; + other = &players[i]; + if (other->spectator == true) + { + continue; + } + + playerCount++; + if (other->bot == true) + { + continue; + } + + if (other->position <= bot->position && other->position > humanThatBeatUs) + { + humanThatBeatUs = other->position; + } + + if (other->position < winnerHuman) + { + winnerHuman = other->position; + } } - else + + wonCount = playerCount / 2; + if (playerCount & 1) { - bot->botvars.diffincrease = 0; + // Round up + wonCount++; } + + statusQuo = K_BotExpectedStanding(bot); + + // How many levels they gain depends on how hard they beat us, + // and how much the status quo was disturbed. + beatenDelta = bot->position - humanThatBeatUs; + winnerDelta = wonCount - winnerHuman; + disruptDelta = abs(statusQuo - bot->position); + + increase = (beatenDelta + winnerDelta + disruptDelta - 2) / 3; + if (increase <= 0) + { + // No increase... + return; + } + + bot->botvars.diffincrease = increase; } /*-------------------------------------------------- From 12ff9dc2ba68aede36a3e7ef8906ef9b8a1d6868 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Mon, 15 May 2023 06:31:01 -0400 Subject: [PATCH 07/12] Bots: Lv.1 doesn't wait as long for items 3sec -> 2sec --- src/k_bot.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_bot.h b/src/k_bot.h index 595938af6..9b598dc09 100644 --- a/src/k_bot.h +++ b/src/k_bot.h @@ -38,7 +38,7 @@ extern "C" { #define BOTRESPAWNCONFIRM (5*TICRATE) // How long it takes for a Lv.1 bot to decide to pick an item. -#define BOT_ITEM_DECISION_TIME (3*TICRATE) +#define BOT_ITEM_DECISION_TIME (2*TICRATE) // Point for bots to aim for struct botprediction_t { From 7d7875d7bedaf31d102bb4ec7b45ae2bc62e395e Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Mon, 15 May 2023 06:49:31 -0400 Subject: [PATCH 08/12] Bots: New spindash conditions are more specific - Uphill check is ignored if you have slope resistance, or if you're moving fast enough to overpower it. - Offroad check is ignored if it wouldn't slow you down. - Made slow acceleration check have a higher range. --- src/k_bot.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/k_bot.c b/src/k_bot.c index dd777b0bb..7756c7fe6 100644 --- a/src/k_bot.c +++ b/src/k_bot.c @@ -927,12 +927,13 @@ static UINT8 K_TrySpindash(player_t *player) } \ } - if (player->mo->standingslope != NULL) + if (K_SlopeResistance(player) == false && player->mo->standingslope != NULL) { const pslope_t *slope = player->mo->standingslope; - if (!(slope->flags & SL_NOPHYSICS) && abs(slope->zdelta) >= FRACUNIT/21) + if ((slope->flags & SL_NOPHYSICS) == 0 && abs(slope->zdelta) >= FRACUNIT/21) { + const fixed_t speedPercent = FixedDiv(player->speed, 20 * player->mo->scale); fixed_t slopeDot = 0; angle_t angle = K_MomentumAngle(player->mo) - slope->xydirection; @@ -942,14 +943,14 @@ static UINT8 K_TrySpindash(player_t *player) } slopeDot = FINECOSINE(angle >> ANGLETOFINESHIFT); - uphill = (slopeDot < -FRACUNIT/2); + uphill = ((slopeDot + (speedPercent / 2)) < -FRACUNIT/2); } } - AddForCondition(player->offroad > 0); // In offroad - AddForCondition(speedDiff < (baseAccel >> 4)); // Moving too slowly + AddForCondition(K_ApplyOffroad(player) == true && player->offroad > 0); // Slowed by offroad + AddForCondition(speedDiff < (baseAccel >> 3)); // Accelerating slower than expected AddForCondition(angleDiff > ANG60); // Being pushed backwards - AddForCondition(uphill == true); // Going up a steep slope + AddForCondition(uphill == true); // Going up a steep slope without speed if (player->cmomx || player->cmomy) { From 69181e4ff2af247f005cd99f5a5da5d0068a8116 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Mon, 15 May 2023 06:51:13 -0400 Subject: [PATCH 09/12] Bots: Fix a couple bugs with item priority - Fix Ring Debt priorities being used even when not at 0 rings - Fix extra item results not being considered for priorities. --- src/k_botitem.c | 9 +++++++++ src/k_roulette.c | 8 +++++--- src/k_roulette.h | 6 +++--- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/k_botitem.c b/src/k_botitem.c index c4ecc4af3..8758db8dc 100644 --- a/src/k_botitem.c +++ b/src/k_botitem.c @@ -1638,6 +1638,15 @@ void K_BotPickItemPriority(player_t *player) continue; } + if (priority == BOT_ITEM_PR_RINGDEBT) + { + if (player->rings > 0) + { + // Only consider this priority when in ring debt. + continue; + } + } + player->botvars.roulettePriority = max( player->botvars.roulettePriority, priority ); } diff --git a/src/k_roulette.c b/src/k_roulette.c index db97e1695..afb9ef519 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -263,13 +263,15 @@ boolean K_ItemSingularity(kartitems_t item) } /*-------------------------------------------------- - botItemPriority_e K_GetBotItemPriority(kartitems_t type) + botItemPriority_e K_GetBotItemPriority(kartitems_t result) See header file for description. --------------------------------------------------*/ -botItemPriority_e K_GetBotItemPriority(kartitems_t type) +botItemPriority_e K_GetBotItemPriority(kartitems_t result) { - switch (type) + result = K_ItemResultToType(result); + + switch (result) { case KITEM_SPB: { diff --git a/src/k_roulette.h b/src/k_roulette.h index f31345e8f..61624525d 100644 --- a/src/k_roulette.h +++ b/src/k_roulette.h @@ -58,20 +58,20 @@ boolean K_ItemSingularity(kartitems_t item); /*-------------------------------------------------- - botItemPriority_e K_GetBotItemPriority(kartitems_t type) + botItemPriority_e K_GetBotItemPriority(kartitems_t result) Returns an item's priority value, which bots use to determine what kind of item they want when the roulette is started. Input Arguments:- - item - The item to check. + result - The item result type to check. Return:- The item's priority type. --------------------------------------------------*/ -botItemPriority_e K_GetBotItemPriority(kartitems_t type); +botItemPriority_e K_GetBotItemPriority(kartitems_t result); /*-------------------------------------------------- From fc1f586f478b1614b70378749b76eb6a5dfdf700 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Mon, 15 May 2023 19:04:06 -0400 Subject: [PATCH 10/12] Fix K_AirFailsafe speed --- src/k_kart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_kart.c b/src/k_kart.c index c535a8c18..91b2802ee 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -10236,7 +10236,7 @@ boolean K_FastFallBounce(player_t *player) static void K_AirFailsafe(player_t *player) { - const fixed_t maxSpeed = K_MomentumAngle(player->mo); + const fixed_t maxSpeed = 6*player->mo->scale; const fixed_t thrustSpeed = 6*player->mo->scale; // 10*player->mo->scale if (player->speed > maxSpeed // Above the max speed that you're allowed to use this technique. From 728cd1bd7b41b7083de151fb7f04419bcdc984b4 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Mon, 15 May 2023 19:08:54 -0400 Subject: [PATCH 11/12] Bots: Add Kitchen Sink to K_BotPickItemPriority --- src/k_bot.h | 2 +- src/k_roulette.c | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/k_bot.h b/src/k_bot.h index 9b598dc09..e0d175aa5 100644 --- a/src/k_bot.h +++ b/src/k_bot.h @@ -285,7 +285,7 @@ void K_BotItemUsage(player_t *player, ticcmd_t *cmd, INT16 turnamt); void K_BotPickItemPriority(player_t *player) Sets a bot's desired item classification - based on what they're , + based on what is happening around them, and delays based on their difficulty intended to be run when starting a roulette. diff --git a/src/k_roulette.c b/src/k_roulette.c index afb9ef519..5a6f79dda 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -307,6 +307,7 @@ botItemPriority_e K_GetBotItemPriority(kartitems_t result) case KITEM_DROPTARGET: case KITEM_EGGMAN: case KITEM_GACHABOM: + case KITEM_KITCHENSINK: { // Used when in 1st place and relatively far from players. // Typically attack items that don't give you protection. From f66798f0a7e1e84638de8f78da0550d41d81e9e5 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Mon, 15 May 2023 19:44:20 -0400 Subject: [PATCH 12/12] Bots: Nudge towards other Ring Shooters --- src/k_botsearch.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/k_botsearch.c b/src/k_botsearch.c index 6d3e97ed3..4d5088aa7 100644 --- a/src/k_botsearch.c +++ b/src/k_botsearch.c @@ -642,6 +642,16 @@ static BlockItReturn_t K_FindObjectsForNudging(mobj_t *thing) } } break; + case MT_RINGSHOOTER: + if (anglediff >= 45) + { + break; + } + else + { + K_AddAttackObject(thing, side, 50); + } + break; default: if (thing->flags & (MF_SOLID|MF_ENEMY|MF_BOSS|MF_PAIN|MF_MISSILE)) {