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.
This commit is contained in:
Sally Coolatta 2021-12-10 17:42:00 -05:00
parent 07d5691b73
commit 19463d6b20
6 changed files with 265 additions and 209 deletions

View file

@ -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(

View file

@ -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);
/*--------------------------------------------------

View file

@ -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)

View file

@ -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);

View file

@ -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;

View file

@ -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);
}