diff --git a/src/k_bot.cpp b/src/k_bot.cpp index fcd3e500d..440a646d0 100644 --- a/src/k_bot.cpp +++ b/src/k_bot.cpp @@ -449,6 +449,25 @@ static line_t *K_FindBotController(mobj_t *mo) } } +/*-------------------------------------------------- + fixed_t K_BotMapModifier(void) + + See header file for description. +--------------------------------------------------*/ +fixed_t K_BotMapModifier(void) +{ + constexpr INT32 complexity_scale = 10000; + constexpr INT32 modifier_max = FRACUNIT * 2; + + const fixed_t complexity_value = std::clamp( + FixedDiv(K_GetTrackComplexity(), complexity_scale), + -modifier_max, + modifier_max * 2 + ); + + return FRACUNIT + complexity_value; +} + /*-------------------------------------------------- static UINT32 K_BotRubberbandDistance(player_t *player) @@ -465,7 +484,7 @@ static UINT32 K_BotRubberbandDistance(player_t *player) { const UINT32 spacing = FixedDiv(640 * mapobjectscale, K_GetKartGameSpeedScalar(gamespeed)) / FRACUNIT; const UINT8 portpriority = player - players; - UINT8 pos = 0; + UINT8 pos = 1; UINT8 i; if (player->botvars.rival) @@ -509,15 +528,22 @@ static UINT32 K_BotRubberbandDistance(player_t *player) --------------------------------------------------*/ fixed_t K_BotRubberband(player_t *player) { + constexpr fixed_t rubberdeltabase = FRACUNIT / 4; // +/- x0.25 + + // Lv. 1: x0.50 avg + // Lv. 9: x1.50 avg const fixed_t difficultyEase = ((player->botvars.difficulty - 1) * FRACUNIT) / (DIFFICULTBOT - 1); + const fixed_t rubberavg = Easing_Linear(difficultyEase, FRACUNIT / 2, FRACUNIT * 3 / 2); // Lv. 1: x0.35 min // Lv. 9: x1.35 min - const fixed_t rubbermin = Easing_Linear(difficultyEase, FRACUNIT * 35 / 100, FRACUNIT * 135 / 100); + const fixed_t rubberdeltamin = FixedMul(rubberdeltabase, K_BotMapModifier()); + const fixed_t rubbermin = std::max(rubberavg - rubberdeltamin, FRACUNIT/3); - // Lv. 1: x1.0 max + // Lv. 1: x0.65 max // Lv. 9: x1.65 max - const fixed_t rubbermax = Easing_Linear(difficultyEase, FRACUNIT, FRACUNIT * 165 / 100); + const fixed_t rubberdeltamax = FixedMul(rubberdeltabase, K_BotMapModifier()); + const fixed_t rubbermax = std::min(rubberavg - rubberdeltamax, FRACUNIT*3); fixed_t rubberband = FRACUNIT >> 1; player_t *firstplace = nullptr; @@ -550,6 +576,12 @@ fixed_t K_BotRubberband(player_t *player) continue; } + // Don't rubberband to ourselves... + if (player == &players[i]) + { + continue; + } + #if 0 // Only rubberband up to players. if (players[i].bot) @@ -566,15 +598,7 @@ fixed_t K_BotRubberband(player_t *player) if (firstplace != nullptr) { - // Lv. 1: 5120 units - // Lv. 9: 320 units - const fixed_t spacing = FixedDiv( - std::max( - 80 * mapobjectscale, - Easing_Linear(difficultyEase, 5120 * mapobjectscale, 320 * mapobjectscale) - ), - K_GetKartGameSpeedScalar(gamespeed) - ) / FRACUNIT; + const fixed_t spacing = FixedDiv(2560 * mapobjectscale, K_GetKartGameSpeedScalar(gamespeed)) / FRACUNIT; const UINT32 wanteddist = firstplace->distancetofinish + K_BotRubberbandDistance(player); const INT32 distdiff = player->distancetofinish - wanteddist; @@ -745,7 +769,7 @@ static botprediction_t *K_CreateBotPrediction(player_t *player) const precise_t time = I_GetPreciseTime(); // Stair janking makes it harder to steer, so attempt to steer harder. - const UINT8 jankDiv = (player->stairjank > 0) ? 2 : 1; + const UINT8 jankDiv = (player->stairjank > 0) ? 4 : 1; const INT16 handling = K_GetKartTurnValue(player, KART_FULLTURN) / jankDiv; // Reduce prediction based on how fast you can turn diff --git a/src/k_bot.h b/src/k_bot.h index 4d265f5d2..441bbffec 100644 --- a/src/k_bot.h +++ b/src/k_bot.h @@ -26,10 +26,10 @@ extern "C" { #endif // Maximum value of botvars.difficulty -#define MAXBOTDIFFICULTY 13 +#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 +#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. @@ -51,7 +51,6 @@ struct botprediction_t fixed_t radius, baseRadius; }; - // AVAILABLE FOR LUA @@ -86,6 +85,26 @@ boolean K_PlayerUsesBotMovement(player_t *player); boolean K_BotCanTakeCut(player_t *player); +/*-------------------------------------------------- + fixed_t K_BotMapModifier(void); + + Gives a multiplier, based on the track complexity. + Track complexity is a measure of how easy it is for + the bots to continuously rubberband. This is used + to make the rubberbanding and other difficulty + adjustments feel roughly the same between wildly + different layouts. + + Input Arguments:- + N/A + + Return:- + A multiplier in fixed point scale, between 0.0 and 2.0. +--------------------------------------------------*/ + +fixed_t K_BotMapModifier(void); + + /*-------------------------------------------------- fixed_t K_BotRubberband(player_t *player); diff --git a/src/k_botitem.c b/src/k_botitem.c index 72da58a5e..98fcc44d9 100644 --- a/src/k_botitem.c +++ b/src/k_botitem.c @@ -1449,11 +1449,16 @@ static void K_BotItemInstashield(player_t *player, ticcmd_t *cmd) if (dist <= radius) { - // Use it!! - cmd->buttons |= BT_ATTACK; - break; + K_ItemConfirmForTarget(player, target, player->botvars.difficulty * 2); } } + + if (player->botvars.itemconfirm > 10*TICRATE) + { + // Use it!! + cmd->buttons |= BT_ATTACK; + player->botvars.itemconfirm = 0; + } } /*-------------------------------------------------- @@ -1494,7 +1499,7 @@ static void K_BotItemRouletteMash(player_t *player, ticcmd_t *cmd) player->itemRoulette.itemList[ player->itemRoulette.index ] ); - if (player->botvars.roulettePriority == currentPriority) + if (player->botvars.roulettePriority == currentPriority) { // This is the item we want! Start timing! player->botvars.itemconfirm++; diff --git a/src/k_botsearch.c b/src/k_botsearch.c index c02186461..e4227f777 100644 --- a/src/k_botsearch.c +++ b/src/k_botsearch.c @@ -28,6 +28,7 @@ #include "r_things.h" // numskins #include "p_slopes.h" // P_GetZAt #include "m_perfstats.h" +#include "k_objects.h" /*-------------------------------------------------- static BlockItReturn_t K_FindEggboxes(mobj_t *thing) @@ -477,7 +478,7 @@ static BlockItReturn_t K_FindObjectsForNudging(mobj_t *thing) if (P_CanPickupItem(g_nudgeSearch.botmo->player, 1)) { - K_AddAttackObject(thing, side, 10); + K_AddAttackObject(thing, side, ((thing->extravalue1 < RINGBOX_TIME) ? 10 : 20)); } break; case MT_EGGMANITEM: @@ -493,11 +494,11 @@ static BlockItReturn_t K_FindObjectsForNudging(mobj_t *thing) if (stealth >= requiredstealth) { - K_AddAttackObject(thing, side, 10); + K_AddAttackObject(thing, side, 20); } else { - K_AddDodgeObject(thing, side, 10); + K_AddDodgeObject(thing, side, 20); } } break; diff --git a/src/k_grandprix.c b/src/k_grandprix.c index 23e5758fd..41522f3d8 100644 --- a/src/k_grandprix.c +++ b/src/k_grandprix.c @@ -667,18 +667,25 @@ void K_RetireBots(void) grabskins[usableskins] = MAXSKINS; } - if (!grandprixinfo.gp) // Sure, let's let this happen all the time :) + if (grandprixinfo.gp) // Sure, let's let this happen all the time :) { - newDifficulty = cv_kartbot.value; + if (grandprixinfo.masterbots == true) + { + newDifficulty = MAXBOTDIFFICULTY; + } + else + { + const UINT8 startingdifficulty = K_BotStartingDifficulty(grandprixinfo.gamespeed); + newDifficulty = startingdifficulty - 4; + if (roundqueue.size > 0) + { + newDifficulty += roundqueue.roundnum; + } + } } else { - const UINT8 startingdifficulty = K_BotStartingDifficulty(grandprixinfo.gamespeed); - newDifficulty = startingdifficulty - 4; - if (roundqueue.size > 0) - { - newDifficulty += roundqueue.roundnum; - } + newDifficulty = cv_kartbot.value; } if (newDifficulty > MAXBOTDIFFICULTY) diff --git a/src/k_kart.c b/src/k_kart.c index ea3002089..c4c48c630 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -3345,8 +3345,9 @@ fixed_t K_GetKartSpeed(player_t *player, boolean doboostpower, boolean dorubberb if (K_PlayerUsesBotMovement(player)) { - // Increase bot speed by 1-10% depending on difficulty - fixed_t add = (player->botvars.difficulty * (FRACUNIT/10)) / DIFFICULTBOT; + // Increase bot speed by 0-10% depending on difficulty + const fixed_t modifier = K_BotMapModifier(); + fixed_t add = ((player->botvars.difficulty-1) * FixedMul(FRACUNIT / 10, modifier)) / (DIFFICULTBOT-1); finalspeed = FixedMul(finalspeed, FRACUNIT + add); if (player->bot && player->botvars.rival) @@ -9098,15 +9099,23 @@ void K_UpdateDistanceFromFinishLine(player_t *const player) INT32 K_GetKartRingPower(player_t *player, boolean boosted) { - INT32 ringPower = ((9 - player->kartspeed) + (9 - player->kartweight)) / 2; + fixed_t ringPower = ((9 - player->kartspeed) + (9 - player->kartweight)) * (FRACUNIT/2); if (boosted == true && K_PlayerUsesBotMovement(player)) { - // Double for Lv. 9 - ringPower += (player->botvars.difficulty * ringPower) / DIFFICULTBOT; + // x2.0 for Lv. 9 + const fixed_t modifier = K_BotMapModifier(); + fixed_t add = ((player->botvars.difficulty-1) * modifier) / (DIFFICULTBOT-1); + ringPower = FixedMul(ringPower, FRACUNIT + add); + + if (player->botvars.rival == true) + { + // x2.0 for Rival + ringPower = FixedMul(ringPower, 2*FRACUNIT); + } } - return ringPower; + return ringPower / FRACUNIT; } // Returns false if this player being placed here causes them to collide with any other player @@ -10644,10 +10653,11 @@ fixed_t K_PlayerBaseFriction(player_t *player, fixed_t original) frict -= FixedMul(FRACUNIT >> 5, factor); // Bots gain more traction as they rubberband. - if (player->botvars.rubberband > FRACUNIT) + fixed_t traction_value = FixedMul(player->botvars.rubberband, max(FRACUNIT, K_BotMapModifier())); + if (traction_value > FRACUNIT) { const fixed_t extraFriction = FixedMul(FRACUNIT >> 5, factor); - const fixed_t mul = player->botvars.rubberband - FRACUNIT; + const fixed_t mul = traction_value - FRACUNIT; frict -= FixedMul(extraFriction, mul); } } diff --git a/src/k_waypoint.cpp b/src/k_waypoint.cpp index 127cee7a0..c46fd3fe2 100644 --- a/src/k_waypoint.cpp +++ b/src/k_waypoint.cpp @@ -21,6 +21,8 @@ #include "g_game.h" #include "p_slopes.h" +#include "cxxutil.hpp" + // The number of sparkles per waypoint connection in the waypoint visualisation static const UINT32 SPARKLES_PER_CONNECTION = 16U; @@ -30,12 +32,16 @@ static const UINT32 SPARKLES_PER_CONNECTION = 16U; #define CLOSEDSET_BASE_SIZE (256U) #define NODESARRAY_BASE_SIZE (256U) -static waypoint_t *waypointheap = NULL; +static waypoint_t *waypointheap = NULL; static waypoint_t *firstwaypoint = NULL; static waypoint_t *finishline = NULL; +static waypoint_t *startingwaypoint = NULL; static UINT32 circuitlength = 0U; +#define BASE_TRACK_COMPLEXITY (-5000) // Arbritrary, vibes-based value +static INT32 trackcomplexity = 0; + static size_t numwaypoints = 0U; static size_t numwaypointmobjs = 0U; static size_t baseopensetsize = OPENSET_BASE_SIZE; @@ -53,6 +59,16 @@ waypoint_t *K_GetFinishLineWaypoint(void) return finishline; } +/*-------------------------------------------------- + waypoint_t *K_GetStartingWaypoint(void) + + See header file for description. +--------------------------------------------------*/ +waypoint_t *K_GetStartingWaypoint(void) +{ + return startingwaypoint; +} + /*-------------------------------------------------- boolean K_GetWaypointIsFinishline(waypoint_t *waypoint) @@ -268,6 +284,16 @@ UINT32 K_GetCircuitLength(void) return circuitlength; } +/*-------------------------------------------------- + INT32 K_GetTrackComplexity(void) + + See header file for description. +--------------------------------------------------*/ +INT32 K_GetTrackComplexity(void) +{ + return trackcomplexity; +} + /*-------------------------------------------------- waypoint_t *K_GetClosestWaypointToMobj(mobj_t *const mobj) @@ -1909,13 +1935,34 @@ static UINT32 K_SetupCircuitLength(void) // line places people correctly relative to each other if ((mapheaderinfo[gamemap - 1]->levelflags & LF_SECTIONRACE) == LF_SECTIONRACE) { - circuitlength = 0U; + path_t bestsprintpath = {0}; + auto sprint_finally = srb2::finally([&bestsprintpath]() { Z_Free(bestsprintpath.array); }); + + const boolean useshortcuts = false; + const boolean huntbackwards = true; + const UINT32 traveldist = UINT32_MAX - UINT16_MAX; // Go as far back as possible. Not exactly UINT32_MAX to avoid possible overflow. + + boolean pathfindsuccess = K_PathfindThruCircuit( + finishline, traveldist, + &bestsprintpath, + useshortcuts, huntbackwards + ); + + circuitlength = bestsprintpath.totaldist; + + if (pathfindsuccess == true) + { + startingwaypoint = (waypoint_t *)bestsprintpath.array[ bestsprintpath.numnodes - 1 ].nodedata; + } } else { // Create a fake finishline waypoint, then try and pathfind to the finishline from it waypoint_t fakefinishline = *finishline; + path_t bestcircuitpath = {0}; + auto circuit_finally = srb2::finally([&bestcircuitpath]() { Z_Free(bestcircuitpath.array); }); + const boolean useshortcuts = false; const boolean huntbackwards = false; @@ -1923,7 +1970,12 @@ static UINT32 K_SetupCircuitLength(void) circuitlength = bestcircuitpath.totaldist; - Z_Free(bestcircuitpath.array); + if (finishline->numnextwaypoints > 0) + { + // TODO: Implementing a version of the fakefinishline hack for + // this instead would be the most ideal + startingwaypoint = finishline->nextwaypoints[0]; + } } return circuitlength; @@ -2127,8 +2179,7 @@ static waypoint_t *K_SetupWaypoint(mobj_t *const mobj) } else { - CONS_Alert( - CONS_WARNING, "Waypoint with ID %d has no next waypoint.\n", K_GetWaypointID(thiswaypoint)); + CONS_Debug(DBG_SETUP, "Waypoint with ID %d has no next waypoint.\n", K_GetWaypointID(thiswaypoint)); } } else @@ -2214,6 +2265,412 @@ static void K_FreeWaypoints(void) K_ClearWaypoints(); } +namespace +{ + +/*-------------------------------------------------- + BlockItReturn_t K_TrackWaypointNearOffroad(line_t *line) + + Blockmap iteration function to check in an extra radius + around a waypoint to find any solid walls around it. +--------------------------------------------------*/ +static fixed_t g_track_wp_x = INT32_MAX; +static fixed_t g_track_wp_y = INT32_MAX; +static fixed_t g_track_wp_radius = INT32_MAX; + +static BlockItReturn_t K_TrackWaypointNearOffroad(line_t *line) +{ + fixed_t dist = INT32_MAX; + vertex_t v = {0}; + + P_ClosestPointOnLine( + g_track_wp_x, g_track_wp_y, + line, + &v + ); + + dist = R_PointToDist2( + g_track_wp_x, g_track_wp_y, + v.x, v.y + ); + + const fixed_t buffer = FixedMul(mobjinfo[MT_PLAYER].radius * 2, mapobjectscale) * 3; + dist -= buffer; + + if (dist <= 0) // line gets crossed + { + if ((line->flags & (ML_TWOSIDED|ML_IMPASSABLE|ML_BLOCKPLAYERS|ML_MIDSOLID)) == ML_TWOSIDED) + { + // double-sided, and no blocking flags -- it's not a wall + const INT32 side = P_PointOnLineSide(g_track_wp_x, g_track_wp_y, line); + const sector_t *sec = side ? line->frontsector : line->backsector; + + if (sec != nullptr && (sec->damagetype == SD_DEATHPIT || sec->damagetype == SD_INSTAKILL)) + { + // force kill sectors to be more complex + return BMIT_STOP; + } + } + else + { + // actually is a wall + return BMIT_ABORT; + } + } + + // not crossed, or not a wall + return BMIT_CONTINUE; +} + +/*-------------------------------------------------- + boolean K_SneakerPanelOverlap(struct sneakerpanel &panelA, struct sneakerpanel &panelB) + + Returns whenever or not a sneaker panel sector / thing overlap +--------------------------------------------------*/ +struct complexity_sneaker_s +{ + fixed_t bbox[4]; + //std::vector sectors; + //std::vector things; + + complexity_sneaker_s(sector_t *sec) + { + M_ClearBox(bbox); + + for (size_t i = 0; i < sec->linecount; i++) + { + line_t *const ld = sec->lines[i]; + + M_AddToBox(bbox, ld->bbox[BOXRIGHT], ld->bbox[BOXTOP]); + M_AddToBox(bbox, ld->bbox[BOXLEFT], ld->bbox[BOXBOTTOM]); + } + } + + complexity_sneaker_s(mapthing_t *mt) + { + M_ClearBox(bbox); + + fixed_t x = mt->x << FRACBITS; + fixed_t y = mt->y << FRACBITS; + fixed_t radius = FixedMul(FixedMul(mobjinfo[MT_SNEAKERPANEL].radius, mt->scale), mapobjectscale); + + M_AddToBox(bbox, x - radius, y - radius); + M_AddToBox(bbox, x + radius, y + radius); + } +}; + +static boolean K_SneakerPanelOverlap(complexity_sneaker_s &panelA, complexity_sneaker_s &panelB) +{ + const fixed_t overlap_extra = 528 * mapobjectscale; // merge ones this close together + + const fixed_t a_width_half = (panelA.bbox[BOXRIGHT] - panelA.bbox[BOXLEFT]) / 2; + const fixed_t a_height_half = (panelA.bbox[BOXTOP] - panelA.bbox[BOXBOTTOM]) / 2; + const fixed_t a_x = panelA.bbox[BOXLEFT] + a_width_half; + const fixed_t a_y = panelA.bbox[BOXBOTTOM] + a_height_half; + + const fixed_t b_width_half = (panelB.bbox[BOXRIGHT] - panelB.bbox[BOXLEFT]) / 2; + const fixed_t b_height_half = (panelB.bbox[BOXTOP] - panelB.bbox[BOXBOTTOM]) / 2; + const fixed_t b_x = panelB.bbox[BOXLEFT] + b_width_half; + const fixed_t b_y = panelB.bbox[BOXBOTTOM] + b_height_half; + + const fixed_t dx = b_x - a_x; + const fixed_t px = (b_width_half - a_width_half) - abs(dx); + if (px <= -overlap_extra) + { + return false; + } + + const fixed_t dy = b_y - a_y; + const fixed_t py = (b_height_half - a_height_half) - abs(dy); + if (py <= -overlap_extra) + { + return false; + } + + return true; +} + +/*-------------------------------------------------- + INT32 K_CalculateTrackComplexity(void) + + Sets the value of trackcomplexity. This value accumulates all of the + turn angle deltas to get an idea of how complicated the map is. +--------------------------------------------------*/ +static INT32 K_CalculateTrackComplexity(void) +{ + const boolean huntbackwards = false; + const boolean useshortcuts = false; + + boolean pathfindsuccess = false; + path_t path = {0}; + + trackcomplexity = BASE_TRACK_COMPLEXITY; + + if (startingwaypoint == NULL || finishline == NULL) + { + return trackcomplexity; + } + + pathfindsuccess = K_PathfindToWaypoint( + startingwaypoint, finishline, + &path, + useshortcuts, huntbackwards + ); + + if (pathfindsuccess == true) + { + auto path_finally = srb2::finally([&path]() { Z_Free(path.array); }); + + for (size_t i = 1; i < path.numnodes-1; i++) + { + waypoint_t *const start = (waypoint_t *)path.array[ i - 1 ].nodedata; + waypoint_t *const mid = (waypoint_t *)path.array[ i ].nodedata; + waypoint_t *const end = (waypoint_t *)path.array[ i + 1 ].nodedata; + + const fixed_t start_mid_dist = R_PointToDist2( + start->mobj->x, start->mobj->y, + mid->mobj->x, mid->mobj->y + ); + const fixed_t mid_end_dist = R_PointToDist2( + mid->mobj->x, mid->mobj->y, + end->mobj->x, end->mobj->y + ); + + const angle_t start_mid_angle = R_PointToAngle2( + start->mobj->x, start->mobj->y, + mid->mobj->x, mid->mobj->y + ); + const angle_t mid_end_angle = R_PointToAngle2( + mid->mobj->x, mid->mobj->y, + end->mobj->x, end->mobj->y + ); + + const angle_t start_mid_pitch = R_PointToAngle2( + 0, start->mobj->z, + start_mid_dist, mid->mobj->z + ); + const angle_t mid_end_pitch = R_PointToAngle2( + 0, mid->mobj->z, + mid_end_dist, end->mobj->z + ); + + const fixed_t avg_radius = (start->mobj->radius + mid->mobj->radius + end->mobj->radius) / 3; + const fixed_t base_scale = DEFAULT_WAYPOINT_RADIUS * mapobjectscale; + + // Reduce complexity with wider turns. + fixed_t radius_factor = FixedDiv( + base_scale, + std::max( + 1, + avg_radius + ) + ); + radius_factor = FRACUNIT + ((radius_factor - FRACUNIT) / 2); // reduce how much it's worth + + // Reduce complexity with wider spaced waypoints. + fixed_t dist_factor = FixedDiv( + base_scale, + std::max( + 1, + start_mid_dist + mid_end_dist + ) + ); + + fixed_t wall_factor = FRACUNIT; + + constexpr fixed_t minimum_turn = 10 * FRACUNIT; // If the delta is lower than this, it's practically a straight-away. + fixed_t delta = AngleFixed( + AngleDelta( + start_mid_angle, + mid_end_angle + ) + ) - minimum_turn; + + if (delta < 0) + { + dist_factor = FixedDiv(FRACUNIT, std::max(1, dist_factor)); + radius_factor = FixedDiv(FRACUNIT, std::max(1, radius_factor)); + } + else + { + // Weight turns hard enough + delta = FixedMul(delta, delta); + + // Reduce turn complexity in walled maps. + wall_factor = FRACUNIT; + + g_track_wp_x = mid->mobj->x; + g_track_wp_y = mid->mobj->y; + g_track_wp_radius = mid->mobj->radius; + + const fixed_t searchRadius = /*g_track_wp_radius +*/ MAXRADIUS; + INT32 xl, xh, yl, yh; + INT32 bx, by; + + const fixed_t c = FixedMul(g_track_wp_radius, FINECOSINE((start_mid_angle + ANGLE_90) >> ANGLETOFINESHIFT)); + const fixed_t s = FixedMul(g_track_wp_radius, FINESINE((start_mid_angle + ANGLE_90) >> ANGLETOFINESHIFT)); + + validcount++; // used to make sure we only process a line once + + xl = (unsigned)((g_track_wp_x + c - searchRadius) - bmaporgx)>>MAPBLOCKSHIFT; + xh = (unsigned)((g_track_wp_x + c + searchRadius) - bmaporgx)>>MAPBLOCKSHIFT; + yl = (unsigned)((g_track_wp_y + s - searchRadius) - bmaporgy)>>MAPBLOCKSHIFT; + yh = (unsigned)((g_track_wp_y + s + searchRadius) - bmaporgy)>>MAPBLOCKSHIFT; + + BMBOUNDFIX(xl, xh, yl, yh); + + for (bx = xl; bx <= xh; bx++) + { + for (by = yl; by <= yh; by++) + { + if (P_BlockLinesIterator(bx, by, K_TrackWaypointNearOffroad) == false) + { + wall_factor /= 4; + bx = xh + 1; + by = yh + 1; + } + } + } + + validcount++; // used to make sure we only process a line once + + xl = (unsigned)((g_track_wp_x - c - searchRadius) - bmaporgx)>>MAPBLOCKSHIFT; + xh = (unsigned)((g_track_wp_x - c + searchRadius) - bmaporgx)>>MAPBLOCKSHIFT; + yl = (unsigned)((g_track_wp_y - s - searchRadius) - bmaporgy)>>MAPBLOCKSHIFT; + yh = (unsigned)((g_track_wp_y - s + searchRadius) - bmaporgy)>>MAPBLOCKSHIFT; + + BMBOUNDFIX(xl, xh, yl, yh); + + for (bx = xl; bx <= xh; bx++) + { + for (by = yl; by <= yh; by++) + { + if (P_BlockLinesIterator(bx, by, K_TrackWaypointNearOffroad) == false) + { + wall_factor /= 4; + bx = xh + 1; + by = yh + 1; + } + } + } + } + + fixed_t pitch_delta = AngleFixed( + AngleDelta( + start_mid_pitch, + mid_end_pitch + ) + ); + + constexpr fixed_t minimum_drop = 45 * FRACUNIT; // If the delta is lower than this, it's probably just a slope. + if (pitch_delta > minimum_drop) + { + // bonus complexity for drop-off / ramp + delta += FixedMul(pitch_delta, FRACUNIT + (pitch_delta - minimum_drop)); + } + + delta = FixedMul(delta, FixedMul(FixedMul(dist_factor, radius_factor), wall_factor)); + + CONS_Printf( + "TURN [%d]: r: %.2f, d: %.2f, w: %.2f, r*d*w: %.2f, DELTA: %d\n", + i, + FixedToFloat(radius_factor), + FixedToFloat(dist_factor), + FixedToFloat(wall_factor), + FixedToFloat(FixedMul(FixedMul(dist_factor, radius_factor), wall_factor)), + (delta / FRACUNIT) + ); + trackcomplexity += (delta / FRACUNIT); + } + + std::vector sneaker_panels; + + for (size_t i = 0; i < numsectors; i++) + { + sector_t *const sec = §ors[i]; + if (sec->linecount == 0) + { + continue; + } + + terrain_t *terrain_f = K_GetTerrainForFlatNum(sec->floorpic); + terrain_t *terrain_c = K_GetTerrainForFlatNum(sec->ceilingpic); + + if ((terrain_f != nullptr && (terrain_f->flags & TRF_SNEAKERPANEL)) + || (terrain_c != nullptr && (terrain_c->flags & TRF_SNEAKERPANEL))) + { + complexity_sneaker_s new_panel(sec); + boolean create_new = true; + + for (size_t j = 0; j < sec->linecount; j++) + { + line_t *const ld = sec->lines[j]; + + M_AddToBox(new_panel.bbox, ld->bbox[BOXRIGHT], ld->bbox[BOXTOP]); + M_AddToBox(new_panel.bbox, ld->bbox[BOXLEFT], ld->bbox[BOXBOTTOM]); + } + + for (auto &panel : sneaker_panels) + { + if (K_SneakerPanelOverlap(new_panel, panel) == true) + { + // merge together + M_AddToBox(panel.bbox, new_panel.bbox[BOXRIGHT], new_panel.bbox[BOXTOP]); + M_AddToBox(panel.bbox, new_panel.bbox[BOXLEFT], new_panel.bbox[BOXBOTTOM]); + //panel.sectors.push_back(sec); + create_new = false; + break; + } + } + + if (create_new == true) + { + //new_panel.sectors.push_back(sec); + sneaker_panels.push_back(new_panel); + } + } + } + + for (size_t i = 0; i < nummapthings; i++) + { + mapthing_t *const mt = &mapthings[i]; + if (mt->type != mobjinfo[MT_SNEAKERPANEL].doomednum) + { + continue; + } + + complexity_sneaker_s new_panel(mt); + boolean create_new = true; + + for (auto &panel : sneaker_panels) + { + if (K_SneakerPanelOverlap(new_panel, panel) == true) + { + // merge together + M_AddToBox(panel.bbox, new_panel.bbox[BOXRIGHT], new_panel.bbox[BOXTOP]); + M_AddToBox(panel.bbox, new_panel.bbox[BOXLEFT], new_panel.bbox[BOXBOTTOM]); + create_new = false; + break; + } + } + + if (create_new == true) + { + sneaker_panels.push_back(new_panel); + } + } + + CONS_Printf("Num sneaker panel sets: %d\n", sneaker_panels.size()); + trackcomplexity -= sneaker_panels.size() * 1250; + + CONS_Printf(" ** COMPLEXITY: %d\n", trackcomplexity); + } + + return trackcomplexity; +} + +}; // namespace + /*-------------------------------------------------- boolean K_SetupWaypointList(void) @@ -2263,6 +2720,11 @@ boolean K_SetupWaypointList(void) CONS_Alert(CONS_ERROR, "Circuit track waypoints do not form a circuit.\n"); } + if (startingwaypoint != NULL) + { + K_CalculateTrackComplexity(); + } + setupsuccessful = true; } } @@ -2281,9 +2743,11 @@ void K_ClearWaypoints(void) waypointheap = NULL; firstwaypoint = NULL; finishline = NULL; + startingwaypoint = NULL; numwaypoints = 0U; numwaypointmobjs = 0U; circuitlength = 0U; + trackcomplexity = 0U; } /*-------------------------------------------------- diff --git a/src/k_waypoint.h b/src/k_waypoint.h index 68d9746ef..d2886d030 100644 --- a/src/k_waypoint.h +++ b/src/k_waypoint.h @@ -55,6 +55,21 @@ struct waypoint_t waypoint_t *K_GetFinishLineWaypoint(void); +/*-------------------------------------------------- + waypoint_t *K_GetStartingWaypoint(void); + + Return the waypoint farthest from the finish line. + + Input Arguments:- + None + + Return:- + The waypoint that is being used as the startingwaypoint. +--------------------------------------------------*/ + +waypoint_t *K_GetStartingWaypoint(void); + + /*-------------------------------------------------- boolean K_GetWaypointIsFinishline(waypoint_t *waypoint) @@ -170,9 +185,26 @@ waypoint_t *K_GetWaypointFromID(INT32 waypointID); Return:- The circuit length. --------------------------------------------------*/ + UINT32 K_GetCircuitLength(void); +/*-------------------------------------------------- + INT32 K_GetTrackComplexity(void) + + Returns the track complexity values. This depends + on how many turns the map has, and is used for + bot code to determine their rubberbanding. + + Input Arguments:- + + Return:- + The track complexity value. +--------------------------------------------------*/ + +INT32 K_GetTrackComplexity(void); + + /*-------------------------------------------------- waypoint_t *K_GetClosestWaypointToMobj(mobj_t *const mobj) diff --git a/src/objects/ufo.c b/src/objects/ufo.c index f9c9809d6..cb9351aed 100644 --- a/src/objects/ufo.c +++ b/src/objects/ufo.c @@ -1249,31 +1249,7 @@ static mobj_t *InitSpecialUFO(waypoint_t *start) mobj_t *Obj_CreateSpecialUFO(void) { - waypoint_t *finishWaypoint = K_GetFinishLineWaypoint(); - waypoint_t *startWaypoint = NULL; - - if (finishWaypoint != NULL) - { - const boolean huntbackwards = true; - const boolean useshortcuts = false; - const UINT32 traveldist = INT32_MAX; // Go as far back as possible. Not UINT32_MAX to avoid possible overflow. - boolean pathfindsuccess = false; - path_t pathtofinish = {0}; - - pathfindsuccess = K_PathfindThruCircuit( - finishWaypoint, traveldist, - &pathtofinish, - useshortcuts, huntbackwards - ); - - if (pathfindsuccess == true) - { - startWaypoint = (waypoint_t *)pathtofinish.array[ pathtofinish.numnodes - 1 ].nodedata; - Z_Free(pathtofinish.array); - } - } - - return InitSpecialUFO(startWaypoint); + return InitSpecialUFO(K_GetStartingWaypoint()); } UINT32 K_GetSpecialUFODistance(void)