From 19463d6b207afb1a90f9e1b009ff48b473833fc0 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Fri, 10 Dec 2021 17:42:00 -0500 Subject: [PATCH] New bot prediction wall detection Instead of searching for walls around the player, and then deciding to make the radius tighter if it found anyway, it instead checks if the waypoint it's trying to predict towards was blocked by any walls / hazards. Needs adjusted some, I think its being pulled back too hard sometimes, but I am optimistic about some of the improvements I already saw. --- src/k_bot.c | 37 ++++++++-- src/k_bot.h | 15 ++-- src/k_botsearch.c | 181 +-------------------------------------------- src/p_local.h | 3 + src/p_map.c | 56 +++++++++----- src/p_sight.c | 182 +++++++++++++++++++++++++++++++++++++++++++++- 6 files changed, 265 insertions(+), 209 deletions(-) diff --git a/src/k_bot.c b/src/k_bot.c index 9c5dff35b..1d9b8ff83 100644 --- a/src/k_bot.c +++ b/src/k_bot.c @@ -575,14 +575,11 @@ static botprediction_t *K_CreateBotPrediction(player_t *player) const INT16 handling = K_GetKartTurnValue(player, KART_FULLTURN); // Reduce prediction based on how fast you can turn const INT16 normal = KART_FULLTURN; // "Standard" handling to compare to - const fixed_t distreduce = K_BotReducePrediction(player); - const fixed_t radreduce = min(distreduce + FRACUNIT/4, FRACUNIT); - const tic_t futuresight = (TICRATE * normal) / max(1, handling); // How far ahead into the future to try and predict - const fixed_t speed = max(P_AproxDistance(player->rmomx, player->rmomy), K_GetKartSpeed(player, false)); + const fixed_t speed = P_AproxDistance(player->rmomx, player->rmomy); - const INT32 startDist = (768 * mapobjectscale) / FRACUNIT; - const INT32 distance = ((FixedMul(speed, distreduce) / FRACUNIT) * futuresight) + startDist; + const INT32 startDist = (1536 * mapobjectscale) / FRACUNIT; + const INT32 distance = ((speed / FRACUNIT) * futuresight) + startDist; botprediction_t *predict = Z_Calloc(sizeof(botprediction_t), PU_STATIC, NULL); waypoint_t *wp = player->nextwaypoint; @@ -591,6 +588,9 @@ static botprediction_t *K_CreateBotPrediction(player_t *player) fixed_t smallestradius = INT32_MAX; angle_t angletonext = ANGLE_MAX; + // Halves radius when encountering a wall on your way to your destination. + fixed_t radreduce = FRACUNIT; + size_t nwp; size_t i; @@ -603,7 +603,7 @@ static botprediction_t *K_CreateBotPrediction(player_t *player) { predict->x = wp->mobj->x; predict->y = wp->mobj->y; - predict->radius = FixedMul(wp->mobj->radius, radreduce); + predict->radius = wp->mobj->radius; return predict; } @@ -653,6 +653,11 @@ static botprediction_t *K_CreateBotPrediction(player_t *player) continue; } + if (P_TraceBotTraversal(player->mo, wp->nextwaypoints[i]->mobj) == false) + { + continue; + } + // Unlike the other parts of this function, we're comparing the player's physical position, NOT the position of the waypoint!! // This should roughly correspond with how players will think about path splits. a = R_PointToAngle2( @@ -672,6 +677,24 @@ static botprediction_t *K_CreateBotPrediction(player_t *player) delta = a; } } + + if (i == wp->numnextwaypoints) + { + // No usable waypoint, we don't want to check any further + radreduce /= 2; + distanceleft = 0; + break; + } + } + else + { + if (P_TraceBotTraversal(player->mo, wp->nextwaypoints[nwp]->mobj) == false) + { + // If we can't get a direct path to this waypoint, we don't want to check any further. + radreduce /= 2; + distanceleft = 0; + break; + } } angletonext = R_PointToAngle2( diff --git a/src/k_bot.h b/src/k_bot.h index 94694c2dd..41c8df5e0 100644 --- a/src/k_bot.h +++ b/src/k_bot.h @@ -185,19 +185,22 @@ UINT8 K_EggboxStealth(fixed_t x, fixed_t y); /*-------------------------------------------------- - fixed_t K_BotReducePrediction(player_t *player); + boolean K_BotHatesThisSector(player_t *player, sector_t *sec, fixed_t x, fixed_t y) - Finds walls nearby the specified player, to create a multiplier - to pull bot predictions back by. + Tells us if a bot will play more careful around + this sector. Checks FOFs in the sector, as well. Input Arguments:- - player - Player to compare. + player - Player to check against. + sec - Sector to check against. + x - Linedef cross X position, for slopes + y - Linedef cross Y position, for slopes Return:- - Multiplier in fixed point scale. + true if avoiding this sector, false otherwise. --------------------------------------------------*/ -fixed_t K_BotReducePrediction(player_t *player); +boolean K_BotHatesThisSector(player_t *player, sector_t *sec, fixed_t x, fixed_t y); /*-------------------------------------------------- diff --git a/src/k_botsearch.c b/src/k_botsearch.c index 80650030d..846521e6f 100644 --- a/src/k_botsearch.c +++ b/src/k_botsearch.c @@ -162,21 +162,11 @@ static boolean K_BotHatesThisSectorsSpecial(player_t *player, sector_t *sec) } /*-------------------------------------------------- - static boolean K_BotHatesThisSector(player_t *player, sector_t *sec, fixed_t x, fixed_t y) + boolean K_BotHatesThisSector(player_t *player, sector_t *sec, fixed_t x, fixed_t y) - Tells us if a bot will play more careful around - this sector. Checks FOFs in the sector, as well. - - Input Arguments:- - player - Player to check against. - sec - Sector to check against. - x - Linedef cross X position, for slopes - y - Linedef cross Y position, for slopes - - Return:- - true if avoiding this sector, false otherwise. + See header file for description. --------------------------------------------------*/ -static boolean K_BotHatesThisSector(player_t *player, sector_t *sec, fixed_t x, fixed_t y) +boolean K_BotHatesThisSector(player_t *player, sector_t *sec, fixed_t x, fixed_t y) { const boolean flip = (player->mo->eflags & MFE_VERTICALFLIP); INT32 specialflag = 0; @@ -257,171 +247,6 @@ static boolean K_BotHatesThisSector(player_t *player, sector_t *sec, fixed_t x, return K_BotHatesThisSectorsSpecial(player, bestsector); } -/*-------------------------------------------------- - static boolean K_FindBlockingWalls(line_t *line) - - Blockmap search function. - Reels the bot prediction back in based on solid walls - or other obstacles surrounding the bot. - - Input Arguments:- - line - Linedef passed in from iteration. - - Return:- - true continues searching, false ends the search early. ---------------------------------------------------*/ -static boolean K_FindBlockingWalls(line_t *line) -{ - // Condensed version of PIT_CheckLine - const fixed_t maxstepmove = FixedMul(MAXSTEPMOVE, mapobjectscale); - fixed_t maxstep = maxstepmove; - fixed_t linedist = INT32_MAX; - INT32 lineside = 0; - vertex_t pos; - - if (!globalsmuggle.botmo || P_MobjWasRemoved(globalsmuggle.botmo) || !globalsmuggle.botmo->player) - { - return false; - } - - if (line->polyobj && !(line->polyobj->flags & POF_SOLID)) - { - return true; - } - - if (tmbbox[BOXRIGHT] <= line->bbox[BOXLEFT] || tmbbox[BOXLEFT] >= line->bbox[BOXRIGHT] - || tmbbox[BOXTOP] <= line->bbox[BOXBOTTOM] || tmbbox[BOXBOTTOM] >= line->bbox[BOXTOP]) - { - return true; - } - - if (P_BoxOnLineSide(tmbbox, line) != -1) - { - return true; - } - - lineside = P_PointOnLineSide(globalsmuggle.botmo->x, globalsmuggle.botmo->y, line); - - // one sided line - if (!line->backsector) - { - if (lineside) - { - // don't hit the back side - return true; - } - - goto blocked; - } - - if ((line->flags & ML_IMPASSABLE) || (line->flags & ML_BLOCKPLAYERS)) - { - goto blocked; - } - - // set openrange, opentop, openbottom - P_LineOpening(line, globalsmuggle.botmo); - - if (globalsmuggle.botmo->player->waterskip) - maxstep += maxstepmove; - - if (P_MobjTouchingSectorSpecial(globalsmuggle.botmo, 1, 13, false)) - maxstep <<= 1; - else if (P_MobjTouchingSectorSpecial(globalsmuggle.botmo, 1, 12, false)) - maxstep = 0; - - if ((openrange < globalsmuggle.botmo->height) // doesn't fit - || (opentop - globalsmuggle.botmo->z < globalsmuggle.botmo->height) // mobj is too high - || (openbottom - globalsmuggle.botmo->z > maxstep)) // too big a step up - { - goto blocked; - } - - // Treat damage sectors like walls - P_ClosestPointOnLine(globalsmuggle.botmo->x, globalsmuggle.botmo->y, line, &pos); - - if (lineside) - { - if (K_BotHatesThisSector(globalsmuggle.botmo->player, line->frontsector, pos.x, pos.y)) - goto blocked; - } - else - { - if (K_BotHatesThisSector(globalsmuggle.botmo->player, line->backsector, pos.x, pos.y)) - goto blocked; - } - - // We weren't blocked! - return true; - -blocked: - linedist = K_DistanceOfLineFromPoint(line->v1->x, line->v1->y, line->v2->x, line->v2->y, globalsmuggle.botmo->x, globalsmuggle.botmo->y); - linedist -= (globalsmuggle.botmo->radius * 8); // Maintain a reasonable distance away from it - - if (linedist > globalsmuggle.distancetocheck) - { - return true; - } - - if (linedist <= 0) - { - globalsmuggle.closestlinedist = 0; - return false; - } - - if (linedist < globalsmuggle.closestlinedist) - { - globalsmuggle.closestlinedist = linedist; - } - - return true; -} - -/*-------------------------------------------------- - fixed_t K_BotReducePrediction(player_t *player) - - See header file for description. ---------------------------------------------------*/ -fixed_t K_BotReducePrediction(player_t *player) -{ - INT32 xl, xh, yl, yh, bx, by; - - globalsmuggle.botmo = player->mo; - globalsmuggle.distancetocheck = (player->mo->radius * 32); - globalsmuggle.closestlinedist = INT32_MAX; - - tmx = player->mo->x; - tmy = player->mo->y; - - xl = (unsigned)(tmx - globalsmuggle.distancetocheck - bmaporgx)>>MAPBLOCKSHIFT; - xh = (unsigned)(tmx + globalsmuggle.distancetocheck - bmaporgx)>>MAPBLOCKSHIFT; - yl = (unsigned)(tmy - globalsmuggle.distancetocheck - bmaporgy)>>MAPBLOCKSHIFT; - yh = (unsigned)(tmy + globalsmuggle.distancetocheck - bmaporgy)>>MAPBLOCKSHIFT; - - BMBOUNDFIX(xl, xh, yl, yh); - - tmbbox[BOXTOP] = tmy + globalsmuggle.distancetocheck; - tmbbox[BOXBOTTOM] = tmy - globalsmuggle.distancetocheck; - tmbbox[BOXRIGHT] = tmx + globalsmuggle.distancetocheck; - tmbbox[BOXLEFT] = tmx - globalsmuggle.distancetocheck; - - // Check for lines that the bot might collide with - for (bx = xl; bx <= xh; bx++) - { - for (by = yl; by <= yh; by++) - { - P_BlockLinesIterator(bx, by, K_FindBlockingWalls); - } - } - - if (globalsmuggle.closestlinedist == INT32_MAX) - { - return FRACUNIT; - } - - return (FRACUNIT/2) + (FixedDiv(globalsmuggle.closestlinedist, globalsmuggle.distancetocheck) / 2); -} - /*-------------------------------------------------- static void K_AddAttackObject(mobj_t *thing, UINT8 side, UINT8 weight) diff --git a/src/p_local.h b/src/p_local.h index db9e9e6dd..d90bfde4c 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -404,6 +404,8 @@ boolean P_IsLineBlocking(const line_t *ld, const mobj_t *thing); boolean P_IsLineTripWire(const line_t *ld); boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y); boolean P_CheckCameraPosition(fixed_t x, fixed_t y, camera_t *thiscam); +fixed_t P_BaseStepUp(void); +fixed_t P_GetThingStepUp(mobj_t *thing); boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff); boolean P_Move(mobj_t *actor, fixed_t speed); boolean P_SetOrigin(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z); @@ -413,6 +415,7 @@ void P_BouncePlayerMove(mobj_t *mo); void P_BounceMove(mobj_t *mo); boolean P_CheckSight(mobj_t *t1, mobj_t *t2); boolean P_TraceBlockingLines(mobj_t *t1, mobj_t *t2); +boolean P_TraceBotTraversal(mobj_t *t1, mobj_t *t2); void P_CheckHoopPosition(mobj_t *hoopthing, fixed_t x, fixed_t y, fixed_t z, fixed_t radius); boolean P_CheckSector(sector_t *sector, boolean crunch); diff --git a/src/p_map.c b/src/p_map.c index bcecb1dfa..966c332b8 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -2428,6 +2428,42 @@ static boolean P_WaterStepUp(mobj_t *thing) P_WaterRunning(thing); } +fixed_t P_BaseStepUp(void) +{ + return FixedMul(MAXSTEPMOVE, mapobjectscale); +} + +fixed_t P_GetThingStepUp(mobj_t *thing) +{ + const fixed_t maxstepmove = P_BaseStepUp(); + fixed_t maxstep = maxstepmove; + + if (thing->type == MT_SKIM) + { + // Skim special (not needed for kart?) + return 0; + } + + if (P_WaterStepUp(thing) == true) + { + // Add some extra stepmove when waterskipping + maxstep += maxstepmove; + } + + if (P_MobjTouchingSectorSpecial(thing, 1, 13, false)) + { + // If using type Section1:13, double the maxstep. + maxstep <<= 1; + } + else if (P_MobjTouchingSectorSpecial(thing, 1, 12, false)) + { + // If using type Section1:12, no maxstep. For short walls, like Egg Zeppelin + maxstep = 0; + } + + return maxstep; +} + // // P_TryMove // Attempt to move to a new position. @@ -2489,21 +2525,7 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff) if (!(thing->flags & MF_NOCLIP)) { //All things are affected by their scale. - const fixed_t maxstepmove = FixedMul(MAXSTEPMOVE, mapobjectscale); - fixed_t maxstep = maxstepmove; - - if (thing->player && P_WaterStepUp(thing)) - maxstep += maxstepmove; // Add some extra stepmove when waterskipping - - // If using type Section1:13, double the maxstep. - if (P_MobjTouchingSectorSpecial(thing, 1, 13, false)) - maxstep <<= 1; - // If using type Section1:12, no maxstep. For short walls, like Egg Zeppelin - else if (P_MobjTouchingSectorSpecial(thing, 1, 12, false)) - maxstep = 0; - - if (thing->type == MT_SKIM) - maxstep = 0; + fixed_t maxstep = P_GetThingStepUp(thing); if (tmceilingz - tmfloorz < thing->height) { @@ -2740,7 +2762,7 @@ boolean P_SceneryTryMove(mobj_t *thing, fixed_t x, fixed_t y) if (!(thing->flags & MF_NOCLIP)) { - const fixed_t maxstep = FixedMul(MAXSTEPMOVE, mapobjectscale); + const fixed_t maxstep = P_BaseStepUp(); if (tmceilingz - tmfloorz < thing->height) return false; // doesn't fit @@ -3202,7 +3224,7 @@ static boolean PTR_LineIsBlocking(line_t *li) if (opentop - slidemo->z < slidemo->height) return true; // mobj is too high - if (openbottom - slidemo->z > FixedMul(MAXSTEPMOVE, mapobjectscale)) + if (openbottom - slidemo->z > P_GetThingStepUp(slidemo)) return true; // too big a step up return false; diff --git a/src/p_sight.c b/src/p_sight.c index 38a50df36..a5aebf55c 100644 --- a/src/p_sight.c +++ b/src/p_sight.c @@ -18,6 +18,8 @@ #include "r_main.h" #include "r_state.h" +#include "k_bot.h" // K_BotHatesThisSector + // // P_CheckSight // @@ -572,7 +574,7 @@ static boolean P_CrossBlockingSubsector(size_t num, register traceblocking_t *tb if (P_IsLineBlocking(line, tb->compareThing) == true) { - // This line will block us + // This line will always block us return false; } } @@ -656,3 +658,181 @@ boolean P_TraceBlockingLines(mobj_t *t1, mobj_t *t2) // the head node is the last node output return P_CrossBSPNodeBlocking((INT32)numnodes - 1, &tb); } + +// +// ANOTHER version, this time for bot traversal. +// (TODO: since we have so many versions of this function, the differences +// should maybe just be a function var that gets called?) +// + +static boolean P_CrossBotTraversalSubsector(size_t num, register traceblocking_t *tb) +{ + seg_t *seg; + INT32 count; + +#ifdef RANGECHECK + if (num >= numsubsectors) + I_Error("P_CrossBotTraversalSubsector: ss %s with numss = %s\n", sizeu1(num), sizeu2(numsubsectors)); +#endif + + // haleyjd 02/23/06: this assignment should be after the above check + seg = segs + subsectors[num].firstline; + + for (count = subsectors[num].numlines; --count >= 0; seg++) // check lines + { + line_t *line = seg->linedef; + divline_t divl; + const vertex_t *v1,*v2; + fixed_t maxstep = INT32_MAX; + + if (seg->glseg) + continue; + + // already checked other side? + if (line->validcount == validcount) + continue; + + line->validcount = validcount; + + // OPTIMIZE: killough 4/20/98: Added quick bounding-box rejection test + if (line->bbox[BOXLEFT ] > tb->bbox[BOXRIGHT ] || + line->bbox[BOXRIGHT ] < tb->bbox[BOXLEFT ] || + line->bbox[BOXBOTTOM] > tb->bbox[BOXTOP ] || + line->bbox[BOXTOP] < tb->bbox[BOXBOTTOM]) + continue; + + v1 = line->v1; + v2 = line->v2; + + // line isn't crossed? + if (P_DivlineSide(v1->x, v1->y, &tb->strace) == + P_DivlineSide(v2->x, v2->y, &tb->strace)) + continue; + + // stop because it is not two sided anyway + if (!(line->flags & ML_TWOSIDED)) + return false; + + divl.dx = v2->x - (divl.x = v1->x); + divl.dy = v2->y - (divl.y = v1->y); + + // line isn't crossed? + if (P_DivlineSide(tb->strace.x, tb->strace.y, &divl) == + P_DivlineSide(tb->t2x, tb->t2y, &divl)) + continue; + + if (P_IsLineBlocking(line, tb->compareThing) == true) + { + // This line will always block us + return false; + } + + // set openrange, opentop, openbottom + P_LineOpening(line, tb->compareThing); + maxstep = P_GetThingStepUp(tb->compareThing); + + if ((openrange < tb->compareThing->height) // doesn't fit + || (opentop - tb->compareThing->z < tb->compareThing->height) // mobj is too high + || (openbottom - tb->compareThing->z > maxstep)) // too big a step up + { + // This line situationally blocks us + return false; + } + + // Treat damage sectors like walls + if (tb->compareThing->player != NULL) + { + INT32 lineside = 0; + vertex_t pos; + + P_ClosestPointOnLine(tb->compareThing->x, tb->compareThing->y, line, &pos); + lineside = P_PointOnLineSide(tb->compareThing->x, tb->compareThing->y, line); + + if (K_BotHatesThisSector(tb->compareThing->player, lineside ? line->frontsector : line->backsector, pos.x, pos.y)) + { + // This line does not block us, but we don't want to be in it. + return false; + } + } + } + + // passed the subsector ok + return true; +} + +static boolean P_CrossBSPNodeBotTraversal(INT32 bspnum, register traceblocking_t *tb) +{ + while (!(bspnum & NF_SUBSECTOR)) + { + register node_t *bsp = nodes + bspnum; + INT32 side = P_DivlineSide(tb->strace.x,tb->strace.y,(divline_t *)bsp)&1; + if (side == P_DivlineSide(tb->t2x, tb->t2y, (divline_t *) bsp)) + bspnum = bsp->children[side]; // doesn't touch the other side + else // the partition plane is crossed here + { + if (!P_CrossBSPNodeBotTraversal(bsp->children[side], tb)) + return false; // cross the starting side + else + bspnum = bsp->children[side^1]; // cross the ending side + } + } + + return P_CrossBotTraversalSubsector((bspnum == -1 ? 0 : bspnum & ~NF_SUBSECTOR), tb); +} + +boolean P_TraceBotTraversal(mobj_t *t1, mobj_t *t2) +{ + const sector_t *s1, *s2; + size_t pnum; + traceblocking_t tb; + + // First check for trivial rejection. + if (!t1 || !t2) + return false; + + I_Assert(!P_MobjWasRemoved(t1)); + I_Assert(!P_MobjWasRemoved(t2)); + + if (!t1->subsector || !t2->subsector + || !t1->subsector->sector || !t2->subsector->sector) + return false; + + s1 = t1->subsector->sector; + s2 = t2->subsector->sector; + pnum = (s1-sectors)*numsectors + (s2-sectors); + + if (rejectmatrix != NULL) + { + // Check in REJECT table. + if (rejectmatrix[pnum>>3] & (1 << (pnum&7))) // can't possibly be connected + return false; + } + + // killough 11/98: shortcut for melee situations + // same subsector? obviously visible + // haleyjd 02/23/06: can't do this if there are polyobjects in the subsec + if (!t1->subsector->polyList && + t1->subsector == t2->subsector) + return true; + + validcount++; + + tb.strace.dx = (tb.t2x = t2->x) - (tb.strace.x = t1->x); + tb.strace.dy = (tb.t2y = t2->y) - (tb.strace.y = t1->y); + + if (t1->x > t2->x) + tb.bbox[BOXRIGHT] = t1->x, tb.bbox[BOXLEFT] = t2->x; + else + tb.bbox[BOXRIGHT] = t2->x, tb.bbox[BOXLEFT] = t1->x; + + if (t1->y > t2->y) + tb.bbox[BOXTOP] = t1->y, tb.bbox[BOXBOTTOM] = t2->y; + else + tb.bbox[BOXTOP] = t2->y, tb.bbox[BOXBOTTOM] = t1->y; + + tb.compareThing = t1; + + // the head node is the last node output + return P_CrossBSPNodeBotTraversal((INT32)numnodes - 1, &tb); +} +