diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 7f6e6f287..d8d52220a 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -3413,7 +3413,7 @@ static void Got_AddBot(UINT8 **p, INT32 playernum) { INT16 newplayernum; UINT8 skinnum = 0; - UINT8 difficulty = MAXBOTDIFFICULTY; + UINT8 difficulty = DIFFICULTBOT; if (playernum != serverplayer && !IsPlayerAdmin(playernum)) { @@ -5092,6 +5092,8 @@ static void SV_Maketic(void) { INT32 i; + ps_botticcmd_time = 0; + for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i]) @@ -5099,7 +5101,9 @@ static void SV_Maketic(void) if (K_PlayerUsesBotMovement(&players[i])) { + precise_t t = I_GetPreciseTime(); K_BuildBotTiccmd(&players[i], &netcmds[maketic%BACKUPTICS][i]); + ps_botticcmd_time += I_GetPreciseTime() - t; continue; } diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 51b6b97d6..855353d9f 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -399,9 +399,13 @@ static CV_PossibleValue_t kartbot_cons_t[] = { {7, "Lv.7"}, {8, "Lv.8"}, {9, "Lv.9"}, + {10,"Lv.10"}, + {11,"Lv.11"}, + {12,"Lv.12"}, + {13,"Lv.MAX"}, {0, NULL} }; -consvar_t cv_kartbot = CVAR_INIT ("kartbot", "0", CV_NETVAR|CV_CHEAT, kartbot_cons_t, NULL); +consvar_t cv_kartbot = CVAR_INIT ("kartbot", "0", CV_NETVAR, kartbot_cons_t, NULL); consvar_t cv_karteliminatelast = CVAR_INIT ("karteliminatelast", "Yes", CV_NETVAR|CV_CHEAT|CV_CALL, CV_YesNo, KartEliminateLast_OnChange); diff --git a/src/k_bot.c b/src/k_bot.c index f31317546..b3f73b8ac 100644 --- a/src/k_bot.c +++ b/src/k_bot.c @@ -167,7 +167,7 @@ void K_UpdateMatchRaceBots(void) } } - if (difficulty == 0 || bossinfo.boss == true) + if (difficulty == 0 || !(gametyperules & GTR_BOTS) || bossinfo.boss == true) { wantedbots = 0; } @@ -294,6 +294,67 @@ boolean K_BotCanTakeCut(player_t *player) return false; } +/*-------------------------------------------------- + static fixed_t K_BotSpeedScaled(player_t *player, fixed_t speed) + + Gets the bot's speed value, adjusted for predictions. + Mainly to make bots brake earlier when on friction sectors. + + Input Arguments:- + player - The bot player to calculate speed for. + speed - Raw speed value. + + Return:- + The bot's speed value for calculations. +--------------------------------------------------*/ +static fixed_t K_BotSpeedScaled(player_t *player, fixed_t speed) +{ + fixed_t result = speed; + + if (player->mo->movefactor != FRACUNIT) + { + fixed_t moveFactor = player->mo->movefactor; + + if (moveFactor == 0) + { + moveFactor = 1; + } + + // Reverse against friction. Allows for bots to + // acknowledge they'll be moving faster on ice, + // and to steer harder / brake earlier. + moveFactor = FixedDiv(FRACUNIT, moveFactor); + + // The full value is way too strong, reduce it. + moveFactor -= (moveFactor - FRACUNIT)*3/4; + + result = FixedMul(result, moveFactor); + } + + if (player->mo->standingslope != NULL) + { + const pslope_t *slope = player->mo->standingslope; + + if (!(slope->flags & SL_NOPHYSICS) && abs(slope->zdelta) >= FRACUNIT/21) + { + fixed_t slopeMul = FRACUNIT; + angle_t angle = K_MomentumAngle(player->mo) - slope->xydirection; + + if (P_MobjFlip(player->mo) * slope->zdelta < 0) + angle ^= ANGLE_180; + + // Going uphill: 0 + // Going downhill: FRACUNIT*2 + slopeMul = FRACUNIT + FINECOSINE(angle >> ANGLETOFINESHIFT); + + // Range: 0.9 to 1.1 + result = FixedMul(result, (FRACUNIT*9/10) + (slopeMul/10)); + } + } + + return result; +} + /*-------------------------------------------------- static line_t *K_FindBotController(mobj_t *mo) @@ -477,24 +538,26 @@ fixed_t K_BotRubberband(player_t *player) if (wanteddist > player->distancetofinish) { // Whoa, you're too far ahead! Slow back down a little. - rubberband += (MAXBOTDIFFICULTY - player->botvars.difficulty) * (distdiff / 3); + rubberband += (DIFFICULTBOT - min(DIFFICULTBOT, player->botvars.difficulty)) * (distdiff / 3); } else { // Catch up to your position! - rubberband += (2*player->botvars.difficulty) * distdiff; + rubberband += player->botvars.difficulty * distdiff; } } - // Lv. 1: x1.0 max - // Lv. 5: x1.5 max - // Lv. 9: x2.0 max - max = FRACUNIT + ((FRACUNIT * (player->botvars.difficulty - 1)) / (MAXBOTDIFFICULTY - 1)); + // Lv. 1: x1.0 max + // Lv. 5: x1.4 max + // Lv. 9: x1.8 max + // Lv. MAX: x2.2 max + max = FRACUNIT + ((FRACUNIT * (player->botvars.difficulty - 1)) / 10); - // Lv. 1: x0.75 min - // Lv. 5: x0.875 min - // Lv. 9: x1.0 min - min = FRACUNIT - (((FRACUNIT/4) * (MAXBOTDIFFICULTY - player->botvars.difficulty)) / (MAXBOTDIFFICULTY - 1)); + // Lv. 1: x0.75 min + // Lv. 5: x0.875 min + // Lv. 9: x1.0 min + // Lv. MAX: x1.0 min + min = FRACUNIT - (((FRACUNIT/4) * (DIFFICULTBOT - min(DIFFICULTBOT, player->botvars.difficulty))) / (DIFFICULTBOT - 1)); if (rubberband > max) { @@ -573,8 +636,9 @@ fixed_t K_BotTopSpeedRubberband(player_t *player) --------------------------------------------------*/ fixed_t K_BotFrictionRubberband(player_t *player, fixed_t frict) { + const fixed_t value = 20776; fixed_t rubberband = K_BotRubberband(player) - FRACUNIT; - fixed_t origFrict, newFrict; + fixed_t newFrict = frict; if (rubberband <= 0) { @@ -584,30 +648,11 @@ fixed_t K_BotFrictionRubberband(player_t *player, fixed_t frict) if (player->tiregrease > 0) { - // This isn't great -- it means rubberbanding will slow down when they hit a spring - // But it's better than the opposite where they accelerate into hyperspace :V - // (would appreciate an actual fix though ... could try being additive instead of multiplicative) + // Bots will lose all of their momentum without this. return frict; } - origFrict = FixedDiv(ORIG_FRICTION, FRACUNIT + (rubberband / 2)); - - if (frict == ORIG_FRICTION) - { - newFrict = origFrict; - } - else - { - // Do some mumbo jumbo to make our friction value - // relative to what it WOULD be for ORIG_FRICTION. - // (I hate multiplicative friction :/) - - fixed_t offset = ORIG_FRICTION - frict; - fixed_t ratio = FixedDiv(frict, ORIG_FRICTION); - - offset = FixedDiv(offset, ratio); - newFrict = frict + offset; - } + newFrict = frict - FixedMul(value, rubberband); if (newFrict < 0) newFrict = 0; @@ -673,7 +718,7 @@ static botprediction_t *K_CreateBotPrediction(player_t *player) const INT16 normal = KART_FULLTURN; // "Standard" handling to compare to const tic_t futuresight = (TICRATE * normal) / max(1, handling); // How far ahead into the future to try and predict - const fixed_t speed = P_AproxDistance(player->mo->momx, player->mo->momy); + const fixed_t speed = K_BotSpeedScaled(player, P_AproxDistance(player->mo->momx, player->mo->momy)); const INT32 startDist = (768 * mapobjectscale) / FRACUNIT; const INT32 distance = ((speed / FRACUNIT) * futuresight) + startDist; @@ -851,7 +896,7 @@ static UINT8 K_TrySpindash(player_t *player) { INT32 boosthold = starttime - K_GetSpindashChargeTime(player); - boosthold -= (MAXBOTDIFFICULTY - player->botvars.difficulty) * difficultyModifier; + boosthold -= (DIFFICULTBOT - min(DIFFICULTBOT, player->botvars.difficulty)) * difficultyModifier; if (leveltime >= (unsigned)boosthold) { @@ -1017,6 +1062,259 @@ static void K_BotTrick(player_t *player, ticcmd_t *cmd, line_t *botController) } } +/*-------------------------------------------------- + static INT32 K_HandleBotTrack(player_t *player, ticcmd_t *cmd, botprediction_t *predict) + + Determines inputs for standard track driving. + + Input Arguments:- + player - Player to generate the ticcmd for. + cmd - The player's ticcmd to modify. + predict - Pointer to the bot's prediction. + + Return:- + New value for turn amount. +--------------------------------------------------*/ +static INT32 K_HandleBotTrack(player_t *player, ticcmd_t *cmd, botprediction_t *predict, angle_t destangle) +{ + // Handle steering towards waypoints! + INT32 turnamt = 0; + SINT8 turnsign = 0; + angle_t moveangle, angle; + INT16 anglediff; + + I_Assert(predict != NULL); + + moveangle = player->mo->angle; + angle = (moveangle - destangle); + + if (angle < ANGLE_180) + { + turnsign = -1; // Turn right + anglediff = AngleFixed(angle)>>FRACBITS; + } + else + { + turnsign = 1; // Turn left + anglediff = 360-(AngleFixed(angle)>>FRACBITS); + } + + anglediff = abs(anglediff); + turnamt = KART_FULLTURN * turnsign; + + if (anglediff > 90) + { + // Wrong way! + cmd->forwardmove = -MAXPLMOVE; + cmd->buttons |= BT_BRAKE; + } + else + { + const fixed_t playerwidth = (player->mo->radius * 2); + fixed_t realrad = predict->radius - (playerwidth * 4); // Remove a "safe" distance away from the edges of the road + fixed_t rad = realrad; + fixed_t dirdist = K_DistanceOfLineFromPoint( + player->mo->x, player->mo->y, + player->mo->x + FINECOSINE(moveangle >> ANGLETOFINESHIFT), player->mo->y + FINESINE(moveangle >> ANGLETOFINESHIFT), + predict->x, predict->y + ); + + if (anglediff > 0) + { + // Become more precise based on how hard you need to turn + // This makes predictions into turns a little nicer + // Facing 90 degrees away from the predicted point gives you a 1/3 radius + rad = FixedMul(rad, ((135 - anglediff) * FRACUNIT) / 135); + } + + if (rad > realrad) + { + rad = realrad; + } + else if (rad < playerwidth) + { + rad = playerwidth; + } + + cmd->buttons |= BT_ACCELERATE; + + // Full speed ahead! + cmd->forwardmove = MAXPLMOVE; + + if (dirdist <= rad) + { + fixed_t speedmul = FixedDiv(K_BotSpeedScaled(player, player->speed), K_GetKartSpeed(player, false)); + fixed_t speedrad = rad/4; + + if (speedmul > FRACUNIT) + { + speedmul = FRACUNIT; + } + + // Increase radius with speed + // At low speed, the CPU will try to be more accurate + // At high speed, they're more likely to lawnmower + speedrad += FixedMul(speedmul, rad - speedrad); + + if (speedrad < playerwidth) + { + speedrad = playerwidth; + } + + if (dirdist <= speedrad) + { + // Don't turn at all + turnamt = 0; + } + } + } + + return turnamt; +} + +/*-------------------------------------------------- + static INT32 K_HandleBotReverse(player_t *player, ticcmd_t *cmd, botprediction_t *predict) + + Determines inputs for reversing. + + Input Arguments:- + player - Player to generate the ticcmd for. + cmd - The player's ticcmd to modify. + predict - Pointer to the bot's prediction. + + Return:- + New value for turn amount. +--------------------------------------------------*/ +static INT32 K_HandleBotReverse(player_t *player, ticcmd_t *cmd, botprediction_t *predict, angle_t destangle) +{ + // Handle steering towards waypoints! + INT32 turnamt = 0; + SINT8 turnsign = 0; + angle_t moveangle, angle; + INT16 anglediff, momdiff; + + if (predict != NULL) + { + // TODO: Should we reverse through bot controllers? + return K_HandleBotTrack(player, cmd, predict, destangle); + } + + if (player->nextwaypoint == NULL + || player->nextwaypoint->mobj == NULL + || P_MobjWasRemoved(player->nextwaypoint->mobj)) + { + // No data available... + return 0; + } + + if ((player->nextwaypoint->prevwaypoints != NULL) + && (player->nextwaypoint->numprevwaypoints > 0U)) + { + size_t i; + for (i = 0U; i < player->nextwaypoint->numprevwaypoints; i++) + { + if (!K_GetWaypointIsEnabled(player->nextwaypoint->prevwaypoints[i])) + { + continue; + } + + destangle = R_PointToAngle2( + player->nextwaypoint->prevwaypoints[i]->mobj->x, player->nextwaypoint->prevwaypoints[i]->mobj->y, + player->nextwaypoint->mobj->x, player->nextwaypoint->mobj->y + ); + + break; + } + } + + // Calculate turn direction first. + moveangle = player->mo->angle; + angle = (moveangle - destangle); + + if (angle < ANGLE_180) + { + turnsign = -1; // Turn right + anglediff = AngleFixed(angle)>>FRACBITS; + } + else + { + turnsign = 1; // Turn left + anglediff = 360-(AngleFixed(angle)>>FRACBITS); + } + + anglediff = abs(anglediff); + turnamt = KART_FULLTURN * turnsign; + + // Now calculate momentum + momdiff = 180; + if (player->speed > player->mo->scale) + { + momdiff = 0; + moveangle = K_MomentumAngle(player->mo); + angle = (moveangle - destangle); + + if (angle < ANGLE_180) + { + momdiff = AngleFixed(angle)>>FRACBITS; + } + else + { + momdiff = 360-(AngleFixed(angle)>>FRACBITS); + } + + momdiff = abs(momdiff); + } + + if (anglediff > 90 || momdiff < 90) + { + // We're not facing the track, + // or we're going too fast. + // Let's E-Brake. + cmd->forwardmove = 0; + cmd->buttons |= BT_ACCELERATE|BT_BRAKE; + } + else + { + fixed_t slopeMul = FRACUNIT; + + if (player->mo->standingslope != NULL) + { + const pslope_t *slope = player->mo->standingslope; + + if (!(slope->flags & SL_NOPHYSICS) && abs(slope->zdelta) >= FRACUNIT/21) + { + angle_t sangle = player->mo->angle - slope->xydirection; + + if (P_MobjFlip(player->mo) * slope->zdelta < 0) + sangle ^= ANGLE_180; + + slopeMul = FRACUNIT - FINECOSINE(sangle >> ANGLETOFINESHIFT); + } + } + +#define STEEP_SLOPE (FRACUNIT*11/10) + if (slopeMul > STEEP_SLOPE) + { + // Slope is too steep to reverse -- EBrake. + cmd->forwardmove = 0; + cmd->buttons |= BT_ACCELERATE|BT_BRAKE; + } + else + { + cmd->forwardmove = -MAXPLMOVE; + cmd->buttons |= BT_BRAKE; //|BT_LOOKBACK + } +#undef STEEP_SLOPE + + if (anglediff < 10) + { + turnamt = 0; + } + } + + return turnamt; +} + /*-------------------------------------------------- void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd) @@ -1026,6 +1324,7 @@ void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd) { botprediction_t *predict = NULL; boolean trySpindash = true; + angle_t destangle = 0; UINT8 spindash = 0; INT32 turnamt = 0; line_t *botController = NULL; @@ -1039,13 +1338,11 @@ void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd) // Remove any existing controls memset(cmd, 0, sizeof(ticcmd_t)); - if ( - gamestate != GS_LEVEL + if (gamestate != GS_LEVEL || player->mo->scale <= 1 || player->playerstate == PST_DEAD || leveltime <= introtime - || (player->exiting && !(gametyperules & GTR_CIRCUIT)) - ) + || !(gametyperules & GTR_BOTS)) { // No need to do anything else. return; @@ -1067,170 +1364,137 @@ void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd) return; } - if ((player->nextwaypoint != NULL - && player->nextwaypoint->mobj != NULL - && !P_MobjWasRemoved(player->nextwaypoint->mobj)) - || (botController != NULL)) + destangle = player->mo->angle; + + if (botController != NULL && (botController->flags & ML_EFFECT1)) { - // Handle steering towards waypoints! - SINT8 turnsign = 0; - angle_t destangle, moveangle, angle; - INT16 anglediff; + const fixed_t dist = (player->mo->radius * 4); - if (botController != NULL && (botController->flags & ML_EFFECT1)) - { - const fixed_t dist = (player->mo->radius * 4); + // X Offset: Movement direction + destangle = FixedAngle(sides[botController->sidenum[0]].textureoffset); - // X Offset: Movement direction - destangle = FixedAngle(sides[botController->sidenum[0]].textureoffset); + // Overwritten prediction + predict = Z_Calloc(sizeof(botprediction_t), PU_STATIC, NULL); - // Overwritten prediction - predict = Z_Calloc(sizeof(botprediction_t), PU_STATIC, NULL); - - predict->x = player->mo->x + FixedMul(dist, FINECOSINE(destangle >> ANGLETOFINESHIFT)); - predict->y = player->mo->y + FixedMul(dist, FINESINE(destangle >> ANGLETOFINESHIFT)); - predict->radius = (DEFAULT_WAYPOINT_RADIUS / 4) * mapobjectscale; - } - else - { - predict = K_CreateBotPrediction(player); - - K_NudgePredictionTowardsObjects(predict, player); - - destangle = R_PointToAngle2(player->mo->x, player->mo->y, predict->x, predict->y); - } - - moveangle = player->mo->angle; - angle = (moveangle - destangle); - - if (angle < ANGLE_180) - { - turnsign = -1; // Turn right - anglediff = AngleFixed(angle)>>FRACBITS; - } - else - { - turnsign = 1; // Turn left - anglediff = 360-(AngleFixed(angle)>>FRACBITS); - } - - anglediff = abs(anglediff); - turnamt = KART_FULLTURN * turnsign; - - if (anglediff > 90) - { - // Wrong way! - cmd->forwardmove = -MAXPLMOVE; - cmd->buttons |= BT_BRAKE; - } - else - { - const fixed_t playerwidth = (player->mo->radius * 2); - fixed_t realrad = predict->radius - (playerwidth * 4); // Remove a "safe" distance away from the edges of the road - fixed_t rad = realrad; - fixed_t dirdist = K_DistanceOfLineFromPoint( - player->mo->x, player->mo->y, - player->mo->x + FINECOSINE(moveangle >> ANGLETOFINESHIFT), player->mo->y + FINESINE(moveangle >> ANGLETOFINESHIFT), - predict->x, predict->y - ); - - if (anglediff > 0) - { - // Become more precise based on how hard you need to turn - // This makes predictions into turns a little nicer - // Facing 90 degrees away from the predicted point gives you a 1/3 radius - rad = FixedMul(rad, ((135 - anglediff) * FRACUNIT) / 135); - } - - if (rad > realrad) - { - rad = realrad; - } - else if (rad < playerwidth) - { - rad = playerwidth; - } - - cmd->buttons |= BT_ACCELERATE; - - // Full speed ahead! - cmd->forwardmove = MAXPLMOVE; - - if (dirdist <= rad) - { - fixed_t speedmul = FixedDiv(player->speed, K_GetKartSpeed(player, false)); - fixed_t speedrad = rad/4; - - if (speedmul > FRACUNIT) - { - speedmul = FRACUNIT; - } - - // Increase radius with speed - // At low speed, the CPU will try to be more accurate - // At high speed, they're more likely to lawnmower - speedrad += FixedMul(speedmul, rad - speedrad); - - if (speedrad < playerwidth) - { - speedrad = playerwidth; - } - - if (dirdist <= speedrad) - { - // Don't turn at all - turnamt = 0; - } - } - } + predict->x = player->mo->x + FixedMul(dist, FINECOSINE(destangle >> ANGLETOFINESHIFT)); + predict->y = player->mo->y + FixedMul(dist, FINESINE(destangle >> ANGLETOFINESHIFT)); + predict->radius = (DEFAULT_WAYPOINT_RADIUS / 4) * mapobjectscale; } if (leveltime <= starttime && finishBeamLine != NULL) { + // Handle POSITION!! const fixed_t distBase = 384*mapobjectscale; const fixed_t distAdjust = 64*mapobjectscale; const fixed_t closeDist = distBase + (distAdjust * (9 - player->kartweight)); const fixed_t farDist = closeDist + (distAdjust * 2); + const tic_t futureSight = (TICRATE >> 1); + fixed_t distToFinish = K_DistanceOfLineFromPoint( finishBeamLine->v1->x, finishBeamLine->v1->y, finishBeamLine->v2->x, finishBeamLine->v2->y, player->mo->x, player->mo->y - ) - player->speed; + ) - (K_BotSpeedScaled(player, player->speed) * futureSight); // Don't run the spindash code at all until we're in the right place trySpindash = false; - // If you're too far, enable spindash & stay still. - // If you're too close, start backing up. - if (distToFinish < closeDist) { - // Silly way of getting us to reverse, but it respects the above code - // where we figure out what the shape of the track looks like. - UINT16 oldButtons = cmd->buttons; - - cmd->buttons &= ~(BT_ACCELERATE|BT_BRAKE); - - if (oldButtons & BT_ACCELERATE) - { - cmd->buttons |= BT_BRAKE; - } - - if (oldButtons & BT_BRAKE) - { - cmd->buttons |= BT_ACCELERATE; - } - - cmd->forwardmove = -cmd->forwardmove; + // We're too close, we need to start backing up. + turnamt = K_HandleBotReverse(player, cmd, predict, destangle); } else if (distToFinish < farDist) { - // We're in about the right place, spindash now. - cmd->forwardmove = 0; - trySpindash = true; + INT32 bullyTurn = INT32_MAX; + + // We're in about the right place, let's do whatever we want to. + + if (player->kartspeed >= 5) + { + // Faster characters want to spindash. + // Slower characters will use their momentum. + trySpindash = true; + } + + // Look for characters to bully. + bullyTurn = K_PositionBully(player); + if (bullyTurn == INT32_MAX) + { + // No one to bully, just go for a spindash as anyone. + if (predict == NULL) + { + // Create a prediction. + if (player->nextwaypoint != NULL + && player->nextwaypoint->mobj != NULL + && !P_MobjWasRemoved(player->nextwaypoint->mobj)) + { + predict = K_CreateBotPrediction(player); + K_NudgePredictionTowardsObjects(predict, player); + destangle = R_PointToAngle2(player->mo->x, player->mo->y, predict->x, predict->y); + } + } + + turnamt = K_HandleBotTrack(player, cmd, predict, destangle); + cmd->buttons &= ~(BT_ACCELERATE|BT_BRAKE); + cmd->forwardmove = 0; + trySpindash = true; + } + else + { + turnamt = bullyTurn; + + // If already spindashing, wait until we get a relatively OK charge first. + if (player->spindash > 0 && player->spindash <= TICRATE) + { + trySpindash = true; + } + else + { + trySpindash = false; + cmd->buttons |= BT_ACCELERATE; + cmd->forwardmove = MAXPLMOVE; + } + } } + else + { + // Too far away, we need to just drive up. + if (predict == NULL) + { + // Create a prediction. + if (player->nextwaypoint != NULL + && player->nextwaypoint->mobj != NULL + && !P_MobjWasRemoved(player->nextwaypoint->mobj)) + { + predict = K_CreateBotPrediction(player); + K_NudgePredictionTowardsObjects(predict, player); + destangle = R_PointToAngle2(player->mo->x, player->mo->y, predict->x, predict->y); + } + } + + turnamt = K_HandleBotTrack(player, cmd, predict, destangle); + } + } + else + { + // Handle steering towards waypoints! + if (predict == NULL) + { + // Create a prediction. + if (player->nextwaypoint != NULL + && player->nextwaypoint->mobj != NULL + && !P_MobjWasRemoved(player->nextwaypoint->mobj)) + { + predict = K_CreateBotPrediction(player); + K_NudgePredictionTowardsObjects(predict, player); + destangle = R_PointToAngle2(player->mo->x, player->mo->y, predict->x, predict->y); + } + } + + turnamt = K_HandleBotTrack(player, cmd, predict, destangle); } if (trySpindash == true) @@ -1315,4 +1579,3 @@ void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd) Z_Free(predict); } } - diff --git a/src/k_bot.h b/src/k_bot.h index a04d5c174..d608fe367 100644 --- a/src/k_bot.h +++ b/src/k_bot.h @@ -18,7 +18,10 @@ #include "r_defs.h" // Maximum value of botvars.difficulty -#define MAXBOTDIFFICULTY 9 +#define MAXBOTDIFFICULTY 13 + +// Level of a "difficult" bot. The max bot level was increased, but this keeps all of the same calculations. +#define DIFFICULTBOT 9 // How many tics in a row do you need to turn in this direction before we'll let you turn. // Made it as small as possible without making it look like the bots are twitching constantly. @@ -220,6 +223,21 @@ boolean K_BotHatesThisSector(player_t *player, sector_t *sec, fixed_t x, fixed_t void K_NudgePredictionTowardsObjects(botprediction_t *predict, player_t *player); +/*-------------------------------------------------- + INT32 K_PositionBully(player_t *player) + + Calculates a turn value to reach a player that can be bullied. + + Input Arguments:- + player - Bot to run this for. + + Return:- + INT32_MAX if couldn't find anything, otherwise a steering value. +--------------------------------------------------*/ + +INT32 K_PositionBully(player_t *player); + + /*-------------------------------------------------- void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd); diff --git a/src/k_botsearch.c b/src/k_botsearch.c index 6e256a9e5..a234bf418 100644 --- a/src/k_botsearch.c +++ b/src/k_botsearch.c @@ -40,6 +40,9 @@ struct globalsmuggle INT64 avoidAvgX[2], avoidAvgY[2]; UINT32 avoidObjs[2]; + fixed_t annoyscore; + mobj_t *annoymo; + fixed_t closestlinedist; fixed_t eggboxx, eggboxy; @@ -771,3 +774,151 @@ void K_NudgePredictionTowardsObjects(botprediction_t *predict, player_t *player) } } } + +/*-------------------------------------------------- + static boolean K_FindPlayersToBully(mobj_t *thing) + + Blockmap search function. + Finds players around the bot to bump. + + Input Arguments:- + thing - Object passed in from iteration. + + Return:- + true continues searching, false ends the search early. +--------------------------------------------------*/ +static boolean K_FindPlayersToBully(mobj_t *thing) +{ + INT16 anglediff; + fixed_t fulldist; + fixed_t ourweight, theirweight, weightdiff; + angle_t ourangle, destangle, angle; + + if (!globalsmuggle.botmo || P_MobjWasRemoved(globalsmuggle.botmo) || !globalsmuggle.botmo->player) + { + return false; + } + + if (thing->health <= 0) + { + return true; + } + + if (!thing->player) + { + return true; + } + + if (globalsmuggle.botmo == thing) + { + return true; + } + + fulldist = R_PointToDist2(globalsmuggle.botmo->x, globalsmuggle.botmo->y, thing->x, thing->y) - thing->radius; + + if (fulldist > globalsmuggle.distancetocheck) + { + return true; + } + + if (P_CheckSight(globalsmuggle.botmo, thing) == false) + { + return true; + } + + ourangle = globalsmuggle.botmo->angle; + destangle = R_PointToAngle2(globalsmuggle.botmo->x, globalsmuggle.botmo->y, thing->x, thing->y); + angle = (ourangle - destangle); + + if (angle < ANGLE_180) + { + anglediff = AngleFixed(angle)>>FRACBITS; + } + else + { + anglediff = 360-(AngleFixed(angle)>>FRACBITS); + } + + anglediff = abs(anglediff); + + ourweight = K_GetMobjWeight(globalsmuggle.botmo, thing); + theirweight = K_GetMobjWeight(thing, globalsmuggle.botmo); + weightdiff = 0; + + if (anglediff >= 90) + { + weightdiff = theirweight - ourweight; + } + else + { + weightdiff = ourweight - theirweight; + } + + if (weightdiff > mapobjectscale && weightdiff > globalsmuggle.annoyscore) + { + globalsmuggle.annoyscore = weightdiff; + globalsmuggle.annoymo = thing; + } + + return true; +} + +/*-------------------------------------------------- + INT32 K_PositionBully(player_t *player) + + See header file for description. +--------------------------------------------------*/ +INT32 K_PositionBully(player_t *player) +{ + INT32 xl, xh, yl, yh, bx, by; + + angle_t ourangle, destangle, angle; + INT16 anglediff; + + globalsmuggle.botmo = player->mo; + globalsmuggle.distancetocheck = 1024*player->mo->scale; + + globalsmuggle.annoymo = NULL; + globalsmuggle.annoyscore = 0; + + xl = (unsigned)(globalsmuggle.botmo->x - globalsmuggle.distancetocheck - bmaporgx)>>MAPBLOCKSHIFT; + xh = (unsigned)(globalsmuggle.botmo->x + globalsmuggle.distancetocheck - bmaporgx)>>MAPBLOCKSHIFT; + yl = (unsigned)(globalsmuggle.botmo->y - globalsmuggle.distancetocheck - bmaporgy)>>MAPBLOCKSHIFT; + yh = (unsigned)(globalsmuggle.botmo->y + globalsmuggle.distancetocheck - bmaporgy)>>MAPBLOCKSHIFT; + + BMBOUNDFIX(xl, xh, yl, yh); + + for (bx = xl; bx <= xh; bx++) + { + for (by = yl; by <= yh; by++) + { + P_BlockThingsIterator(bx, by, K_FindPlayersToBully); + } + } + + if (globalsmuggle.annoymo == NULL) + { + return INT32_MAX; + } + + ourangle = globalsmuggle.botmo->angle; + destangle = R_PointToAngle2(globalsmuggle.botmo->x, globalsmuggle.botmo->y, globalsmuggle.annoymo->x, globalsmuggle.annoymo->y); + angle = (ourangle - destangle); + + if (angle < ANGLE_180) + { + anglediff = AngleFixed(angle)>>FRACBITS; + } + else + { + anglediff = 360-(AngleFixed(angle)>>FRACBITS); + } + + if (anglediff < 30) + return 0; + + if (anglediff < 0) + return -KART_FULLTURN; + + return KART_FULLTURN; +} diff --git a/src/k_grandprix.c b/src/k_grandprix.c index c07bf2a81..332224479 100644 --- a/src/k_grandprix.c +++ b/src/k_grandprix.c @@ -95,6 +95,25 @@ INT16 K_CalculateGPRankPoints(UINT8 position, UINT8 numplayers) return points; } +/*-------------------------------------------------- + SINT8 K_BotDefaultSkin(void) + + See header file for description. +--------------------------------------------------*/ +SINT8 K_BotDefaultSkin(void) +{ + const char *defaultbotskinname = "eggrobo"; + SINT8 defaultbotskin = R_SkinAvailable(defaultbotskinname); + + if (defaultbotskin == -1) + { + // This shouldn't happen, but just in case + defaultbotskin = 0; + } + + return defaultbotskin; +} + /*-------------------------------------------------- void K_InitGrandPrixBots(void) @@ -102,8 +121,7 @@ INT16 K_CalculateGPRankPoints(UINT8 position, UINT8 numplayers) --------------------------------------------------*/ void K_InitGrandPrixBots(void) { - const char *defaultbotskinname = "eggrobo"; - SINT8 defaultbotskin = R_SkinAvailable(defaultbotskinname); + const SINT8 defaultbotskin = K_BotDefaultSkin(); const UINT8 startingdifficulty = K_BotStartingDifficulty(grandprixinfo.gamespeed); UINT8 difficultylevels[MAXPLAYERS]; @@ -121,12 +139,6 @@ void K_InitGrandPrixBots(void) UINT8 newplayernum = 0; UINT8 i, j; - if (defaultbotskin == -1) - { - // This shouldn't happen, but just in case - defaultbotskin = 0; - } - memset(competitors, MAXPLAYERS, sizeof (competitors)); memset(botskinlist, defaultbotskin, sizeof (botskinlist)); @@ -144,7 +156,7 @@ void K_InitGrandPrixBots(void) } #if MAXPLAYERS != 16 - I_Error("GP bot difficulty levels need rebalacned for the new player count!\n"); + I_Error("GP bot difficulty levels need rebalanced for the new player count!\n"); #endif if (grandprixinfo.masterbots) @@ -500,6 +512,104 @@ void K_IncreaseBotDifficulty(player_t *bot) } } +/*-------------------------------------------------- + void K_ReplaceBot(player_t *bot) + + See header file for description. +--------------------------------------------------*/ +void K_ReplaceBot(player_t *bot) +{ + const SINT8 defaultbotskin = K_BotDefaultSkin(); + SINT8 newDifficulty; + + boolean skinusable[MAXSKINS]; + UINT8 skinnum; + UINT8 loops = 0; + + UINT8 i; + + if (grandprixinfo.gp == true && grandprixinfo.roundnum >= grandprixinfo.cup->numlevels) + { + // Was last map, no replacement. + return; + } + + // init usable bot skins list + for (i = 0; i < MAXSKINS; i++) + { + if (i < numskins) + { + skinusable[i] = true; + } + else + { + skinusable[i] = false; + } + } + + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] && !players[i].spectator) + { + skinusable[players[i].skin] = false; + } + } + + skinnum = P_RandomKey(numskins); + + while (!skinusable[skinnum]) + { + if (loops >= numskins) + { + // no more skins + break; + } + + skinnum++; + + if (skinnum >= numskins) + { + skinnum = 0; + } + + loops++; + } + + if (loops >= numskins) + { + // Use default skin + skinnum = defaultbotskin; + } + + if (!grandprixinfo.gp) // Sure, let's let this happen all the time :) + { + newDifficulty = cv_kartbot.value; + } + else + { + const UINT8 startingdifficulty = K_BotStartingDifficulty(grandprixinfo.gamespeed); + newDifficulty = startingdifficulty - 4 + grandprixinfo.roundnum; + } + + if (newDifficulty > MAXBOTDIFFICULTY) + { + newDifficulty = MAXBOTDIFFICULTY; + } + else if (newDifficulty < 1) + { + newDifficulty = 1; + } + + bot->botvars.difficulty = newDifficulty; + bot->botvars.diffincrease = 0; + + SetPlayerSkinByNum(bot - players, skinnum); + bot->skincolor = skins[skinnum].prefcolor; + sprintf(player_names[bot - players], "%s", skins[skinnum].realname); + + bot->score = 0; +} + /*-------------------------------------------------- void K_FakeBotResults(player_t *bot) diff --git a/src/k_grandprix.h b/src/k_grandprix.h index 9f27b485b..f951860d8 100644 --- a/src/k_grandprix.h +++ b/src/k_grandprix.h @@ -62,6 +62,16 @@ UINT8 K_BotStartingDifficulty(SINT8 value); INT16 K_CalculateGPRankPoints(UINT8 position, UINT8 numplayers); +/*-------------------------------------------------- + SINT8 K_BotDefaultSkin(void); + + Returns the skin number of the skin the game + uses as a fallback option. +--------------------------------------------------*/ + +SINT8 K_BotDefaultSkin(void); + + /*-------------------------------------------------- void K_InitGrandPrixBots(void); @@ -95,6 +105,22 @@ void K_UpdateGrandPrixBots(void); void K_IncreaseBotDifficulty(player_t *bot); +/*-------------------------------------------------- + void K_ReplaceBot(player_t *bot); + + "Replaces" a bot, by refreshing their difficulty + and changing their skin. + + Input Arguments:- + bot - Player to do this for. + + Return:- + None +--------------------------------------------------*/ + +void K_ReplaceBot(player_t *bot); + + /*-------------------------------------------------- void K_FakeBotResults(player_t *bot); diff --git a/src/k_kart.c b/src/k_kart.c index d4d9ebcd7..01ab4a9e2 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -2982,7 +2982,7 @@ fixed_t K_GetKartSpeed(player_t *player, boolean doboostpower) if (K_PlayerUsesBotMovement(player)) { // Increase bot speed by 1-10% depending on difficulty - fixed_t add = (player->botvars.difficulty * (FRACUNIT/10)) / MAXBOTDIFFICULTY; + fixed_t add = (player->botvars.difficulty * (FRACUNIT/10)) / DIFFICULTBOT; finalspeed = FixedMul(finalspeed, FRACUNIT + add); if (player->botvars.rival == true) @@ -7949,7 +7949,7 @@ INT32 K_GetKartRingPower(player_t *player, boolean boosted) if (boosted == true && K_PlayerUsesBotMovement(player)) { // Double for Lv. 9 - ringPower += (player->botvars.difficulty * ringPower) / MAXBOTDIFFICULTY; + ringPower += (player->botvars.difficulty * ringPower) / DIFFICULTBOT; } return ringPower; diff --git a/src/m_perfstats.c b/src/m_perfstats.c index b58599b6d..03930f495 100644 --- a/src/m_perfstats.c +++ b/src/m_perfstats.c @@ -46,6 +46,7 @@ static precise_t ps_frametime = 0; precise_t ps_tictime = 0; precise_t ps_playerthink_time = 0; +precise_t ps_botticcmd_time = 0; precise_t ps_thinkertime = 0; precise_t ps_thlist_times[NUM_THINKERLISTS]; @@ -363,6 +364,7 @@ static void M_DrawTickStats(void) perfstatrow_t extra_thinker_time_row[] = { {"lthinkf", "LUAh_ThinkFrame:", &ps_lua_thinkframe_time}, + {"botcmd ", "Bot logic: ", &ps_botticcmd_time}, {"other ", "Other: ", &extratime}, {0} }; diff --git a/src/m_perfstats.h b/src/m_perfstats.h index 1ca71957f..dae2f2030 100644 --- a/src/m_perfstats.h +++ b/src/m_perfstats.h @@ -19,6 +19,7 @@ extern precise_t ps_tictime; extern precise_t ps_playerthink_time; +extern precise_t ps_botticcmd_time; extern precise_t ps_thinkertime; extern precise_t ps_thlist_times[]; diff --git a/src/y_inter.c b/src/y_inter.c index a1a414d19..69cbf2660 100644 --- a/src/y_inter.c +++ b/src/y_inter.c @@ -316,25 +316,27 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32)) data.pos[data.numplayers] = data.numplayers+1; } - if ((powertype == PWRLV_DISABLED) - && (!rankingsmode) - && !(players[i].pflags & PF_NOCONTEST) - && (data.pos[data.numplayers] < (numplayersingame + numgriefers))) + if (!rankingsmode) { - // Online rank is handled further below in this file. - data.increase[i] = K_CalculateGPRankPoints(data.pos[data.numplayers], numplayersingame + numgriefers); - players[i].score += data.increase[i]; - } + if ((powertype == PWRLV_DISABLED) + && !(players[i].pflags & PF_NOCONTEST) + && (data.pos[data.numplayers] < (numplayersingame + numgriefers))) + { + // Online rank is handled further below in this file. + data.increase[i] = K_CalculateGPRankPoints(data.pos[data.numplayers], numplayersingame + numgriefers); + players[i].score += data.increase[i]; + } - if (demo.recording && !rankingsmode) - { - G_WriteStanding( - data.pos[data.numplayers], - data.name[data.numplayers], - *data.character[data.numplayers], - *data.color[data.numplayers], - data.val[data.numplayers] - ); + if (demo.recording) + { + G_WriteStanding( + data.pos[data.numplayers], + data.name[data.numplayers], + *data.character[data.numplayers], + *data.color[data.numplayers], + data.val[data.numplayers] + ); + } } data.numplayers++; @@ -582,6 +584,12 @@ void Y_IntermissionDrawer(void) V_DrawScaledPatch(x+16, y-4, 0, W_CachePatchName(va("K_CHILI%d", cursorframe+1), PU_CACHE)); } + if (!data.rankingsmode && (players[data.num[i]].pflags & PF_NOCONTEST) && players[data.num[i]].bot) + { + // RETIRED!! + V_DrawScaledPatch(x+12, y-7, 0, W_CachePatchName("K_NOBLNS", PU_CACHE)); + } + STRBUFCPY(strtime, data.name[i]); y2 = y; @@ -803,6 +811,15 @@ void Y_Ticker(void) { if (!data.rankingsmode && sorttic != -1 && (intertic >= sorttic + 8)) { + UINT8 i; + for (i = 0; i < MAXPLAYERS; i++) + { + if ((players[i].pflags & PF_NOCONTEST) && players[i].bot) + { + K_ReplaceBot(&players[i]); + } + } + Y_CalculateMatchData(1, Y_CompareRank); }