mirror of
https://github.com/KartKrewDev/RingRacers.git
synced 2025-10-30 08:01:28 +00:00
1025 lines
24 KiB
C++
1025 lines
24 KiB
C++
// DR. ROBOTNIK'S RING RACERS
|
|
//-----------------------------------------------------------------------------
|
|
// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour
|
|
// Copyright (C) 2024 by Kart Krew
|
|
//
|
|
// This program is free software distributed under the
|
|
// terms of the GNU General Public License, version 2.
|
|
// See the 'LICENSE' file for more details.
|
|
//-----------------------------------------------------------------------------
|
|
/// \file k_botsearch.cpp
|
|
/// \brief Bot blockmap search functions
|
|
|
|
#include <algorithm>
|
|
|
|
#include <tracy/tracy/Tracy.hpp>
|
|
|
|
#include "doomdef.h"
|
|
#include "d_player.h"
|
|
#include "g_game.h"
|
|
#include "r_main.h"
|
|
#include "p_local.h"
|
|
#include "k_bot.h"
|
|
#include "lua_hook.h"
|
|
#include "byteptr.h"
|
|
#include "d_net.h" // nodetoplayer
|
|
#include "k_kart.h"
|
|
#include "z_zone.h"
|
|
#include "i_system.h"
|
|
#include "p_maputl.h"
|
|
#include "d_ticcmd.h"
|
|
#include "m_random.h"
|
|
#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)
|
|
|
|
Blockmap search function.
|
|
Increments the random items and egg boxes counters.
|
|
|
|
Input Arguments:-
|
|
thing - Object passed in from iteration.
|
|
|
|
Return:-
|
|
BlockItReturn_t enum, see its definition for more information.
|
|
--------------------------------------------------*/
|
|
static struct eggboxSearch_s
|
|
{
|
|
fixed_t distancetocheck;
|
|
fixed_t eggboxx, eggboxy;
|
|
UINT8 randomitems;
|
|
UINT8 eggboxes;
|
|
} g_eggboxSearch;
|
|
|
|
static BlockItReturn_t K_FindEggboxes(mobj_t *thing)
|
|
{
|
|
fixed_t dist;
|
|
|
|
if (thing->type != MT_RANDOMITEM && thing->type != MT_EGGMANITEM)
|
|
{
|
|
return BMIT_CONTINUE;
|
|
}
|
|
|
|
if (!thing->health)
|
|
{
|
|
return BMIT_CONTINUE;
|
|
}
|
|
|
|
dist = P_AproxDistance(thing->x - g_eggboxSearch.eggboxx, thing->y - g_eggboxSearch.eggboxy);
|
|
|
|
if (dist > g_eggboxSearch.distancetocheck)
|
|
{
|
|
return BMIT_CONTINUE;
|
|
}
|
|
|
|
if (thing->type == MT_RANDOMITEM)
|
|
{
|
|
g_eggboxSearch.randomitems++;
|
|
}
|
|
else
|
|
{
|
|
g_eggboxSearch.eggboxes++;
|
|
}
|
|
|
|
return BMIT_CONTINUE;
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
UINT8 K_EggboxStealth(fixed_t x, fixed_t y)
|
|
|
|
See header file for description.
|
|
--------------------------------------------------*/
|
|
UINT8 K_EggboxStealth(fixed_t x, fixed_t y)
|
|
{
|
|
ZoneScoped;
|
|
|
|
INT32 xl, xh, yl, yh, bx, by;
|
|
|
|
g_eggboxSearch.eggboxx = x;
|
|
g_eggboxSearch.eggboxy = y;
|
|
g_eggboxSearch.distancetocheck = (mapobjectscale * 256);
|
|
g_eggboxSearch.randomitems = 0;
|
|
g_eggboxSearch.eggboxes = 0;
|
|
|
|
xl = (unsigned)(g_eggboxSearch.eggboxx - g_eggboxSearch.distancetocheck - bmaporgx)>>MAPBLOCKSHIFT;
|
|
xh = (unsigned)(g_eggboxSearch.eggboxx + g_eggboxSearch.distancetocheck - bmaporgx)>>MAPBLOCKSHIFT;
|
|
yl = (unsigned)(g_eggboxSearch.eggboxy - g_eggboxSearch.distancetocheck - bmaporgy)>>MAPBLOCKSHIFT;
|
|
yh = (unsigned)(g_eggboxSearch.eggboxy + g_eggboxSearch.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_FindEggboxes);
|
|
}
|
|
}
|
|
|
|
return (g_eggboxSearch.randomitems * (g_eggboxSearch.eggboxes + 1));
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static boolean K_BotHatesThisSectorsSpecial(const player_t *player, sector_t *sec)
|
|
|
|
Tells us if a bot will play more careful around
|
|
this sector's special type.
|
|
|
|
Input Arguments:-
|
|
player - Player to check against.
|
|
sec - Sector to check the specials of.
|
|
|
|
Return:-
|
|
true if avoiding this sector special, false otherwise.
|
|
--------------------------------------------------*/
|
|
static boolean K_BotHatesThisSectorsSpecial(const player_t *player, sector_t *sec, const boolean flip)
|
|
{
|
|
terrain_t *terrain = K_GetTerrainForFlatNum(flip ? sec->ceilingpic : sec->floorpic);
|
|
|
|
if (terrain != NULL)
|
|
{
|
|
if (terrain->damageType != SD_NONE)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (terrain->offroad > 0)
|
|
{
|
|
return !K_BotCanTakeCut(player);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (sec->damagetype != SD_NONE)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (sec->offroad > 0)
|
|
{
|
|
return !K_BotCanTakeCut(player);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
boolean K_BotHatesThisSector(const player_t *player, sector_t *sec, fixed_t x, fixed_t y)
|
|
|
|
See header file for description.
|
|
--------------------------------------------------*/
|
|
boolean K_BotHatesThisSector(const player_t *player, sector_t *sec, fixed_t x, fixed_t y)
|
|
{
|
|
const boolean flip = (player->mo->eflags & MFE_VERTICALFLIP);
|
|
fixed_t highestfloor = INT32_MAX;
|
|
sector_t *bestsector = NULL;
|
|
ffloor_t *rover;
|
|
|
|
// TODO: Properly support MSF_FLIPSPECIAL_FLOOR / MSF_FLIPSPECIAL_CEILING.
|
|
// An earlier attempt at it caused lots of false positives and other weird
|
|
// quirks with intangible FOFs.
|
|
|
|
if (flip == true)
|
|
{
|
|
highestfloor = P_GetZAt(sec->c_slope, x, y, sec->ceilingheight);
|
|
}
|
|
else
|
|
{
|
|
highestfloor = P_GetZAt(sec->f_slope, x, y, sec->floorheight);
|
|
}
|
|
|
|
bestsector = sec;
|
|
|
|
for (rover = sec->ffloors; rover; rover = rover->next)
|
|
{
|
|
fixed_t top = INT32_MAX;
|
|
fixed_t bottom = INT32_MAX;
|
|
|
|
if (!(rover->fofflags & FOF_EXISTS))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
top = P_GetZAt(*rover->t_slope, x, y, *rover->topheight);
|
|
bottom = P_GetZAt(*rover->b_slope, x, y, *rover->bottomheight);
|
|
|
|
if (!(rover->fofflags & FOF_BLOCKPLAYER))
|
|
{
|
|
if ((top >= player->mo->z) && (bottom <= player->mo->z + player->mo->height)
|
|
&& K_BotHatesThisSectorsSpecial(player, rover->master->frontsector, flip))
|
|
{
|
|
// Bad intangible sector at our height, so we DEFINITELY want to avoid
|
|
return true;
|
|
}
|
|
|
|
// Ignore them, we want the one below it.
|
|
continue;
|
|
}
|
|
|
|
// Find the highest FOF floor beneath the player, and check it at the end.
|
|
if (flip == true)
|
|
{
|
|
if (bottom < highestfloor
|
|
&& bottom >= player->mo->z + player->mo->height)
|
|
{
|
|
bestsector = rover->master->frontsector;
|
|
highestfloor = bottom;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (top > highestfloor
|
|
&& top <= player->mo->z)
|
|
{
|
|
bestsector = rover->master->frontsector;
|
|
highestfloor = top;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bestsector == NULL)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return K_BotHatesThisSectorsSpecial(player, bestsector, flip);
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static void K_AddAttackObject(mobj_t *thing, UINT8 side, UINT8 weight)
|
|
|
|
Adds an object to the list that the bot wants to go towards.
|
|
|
|
Input Arguments:-
|
|
thing - Object to move towards.
|
|
side - Which side -- 0 for left, 1 for right
|
|
weight - How important this object is.
|
|
|
|
Return:-
|
|
None
|
|
--------------------------------------------------*/
|
|
static struct nudgeSearch_s
|
|
{
|
|
mobj_t *botmo;
|
|
angle_t angle;
|
|
fixed_t distancetocheck;
|
|
|
|
INT64 gotoAvgX[2], gotoAvgY[2];
|
|
UINT32 gotoObjs[2];
|
|
|
|
INT64 avoidAvgX[2], avoidAvgY[2];
|
|
UINT32 avoidObjs[2];
|
|
} g_nudgeSearch;
|
|
|
|
static void K_AddAttackObject(mobj_t *thing, UINT8 side, UINT8 weight)
|
|
{
|
|
fixed_t x, y;
|
|
angle_t a, dir;
|
|
UINT8 i;
|
|
|
|
I_Assert(side <= 1);
|
|
|
|
if (weight == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
x = thing->x;
|
|
y = thing->y;
|
|
a = R_PointToAngle2(g_nudgeSearch.botmo->x, g_nudgeSearch.botmo->y, x, y);
|
|
|
|
dir = a + (side ? -ANGLE_90 : ANGLE_90);
|
|
x += FixedMul(thing->radius, FINECOSINE(dir >> ANGLETOFINESHIFT));
|
|
y += FixedMul(thing->radius, FINESINE(dir >> ANGLETOFINESHIFT));
|
|
|
|
x /= mapobjectscale;
|
|
y /= mapobjectscale;
|
|
|
|
for (i = 0; i < weight; i++)
|
|
{
|
|
g_nudgeSearch.gotoAvgX[side] += x;
|
|
g_nudgeSearch.gotoAvgY[side] += y;
|
|
g_nudgeSearch.gotoObjs[side]++;
|
|
}
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static void K_AddDodgeObject(mobj_t *thing, UINT8 side, UINT8 weight)
|
|
|
|
Adds an object to the list that the bot wants to dodge.
|
|
|
|
Input Arguments:-
|
|
thing - Object to move away from.
|
|
side - Which side -- 0 for left, 1 for right
|
|
weight - How important this object is.
|
|
|
|
Return:-
|
|
None
|
|
--------------------------------------------------*/
|
|
static void K_AddDodgeObject(mobj_t *thing, UINT8 side, UINT8 weight)
|
|
{
|
|
fixed_t x, y;
|
|
angle_t a, dir;
|
|
UINT8 i;
|
|
|
|
I_Assert(side <= 1);
|
|
|
|
if (weight == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
x = thing->x;
|
|
y = thing->y;
|
|
a = R_PointToAngle2(g_nudgeSearch.botmo->x, g_nudgeSearch.botmo->y, x, y);
|
|
|
|
dir = a + (side ? -ANGLE_90 : ANGLE_90);
|
|
x += FixedMul(thing->radius, FINECOSINE(dir >> ANGLETOFINESHIFT));
|
|
y += FixedMul(thing->radius, FINESINE(dir >> ANGLETOFINESHIFT));
|
|
|
|
x /= mapobjectscale;
|
|
y /= mapobjectscale;
|
|
|
|
for (i = 0; i < weight; i++)
|
|
{
|
|
g_nudgeSearch.avoidAvgX[side] += x;
|
|
g_nudgeSearch.avoidAvgY[side] += y;
|
|
g_nudgeSearch.avoidObjs[side]++;
|
|
}
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static boolean K_PlayerAttackSteer(mobj_t *thing, UINT8 side, UINT8 weight, boolean attackCond, boolean dodgeCond)
|
|
|
|
Checks two conditions to determine if the object should be
|
|
attacked or dodged.
|
|
|
|
Input Arguments:-
|
|
thing - Object to move towards/away from.
|
|
side - Which side -- 0 for left, 1 for right
|
|
weight - How important this object is.
|
|
attackCond - If this is true, and dodgeCond isn't, then we go towards the object.
|
|
dodgeCond - If this is true, and attackCond isn't, then we move away from the object.
|
|
|
|
Return:-
|
|
true if either condition is successful.
|
|
--------------------------------------------------*/
|
|
static boolean K_PlayerAttackSteer(mobj_t *thing, UINT8 side, UINT8 weight, boolean attackCond, boolean dodgeCond)
|
|
{
|
|
if (attackCond == true && dodgeCond == false)
|
|
{
|
|
K_AddAttackObject(thing, side, weight);
|
|
return true;
|
|
}
|
|
else if (dodgeCond == true && attackCond == false)
|
|
{
|
|
K_AddDodgeObject(thing, side, weight);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static BlockItReturn_t K_FindObjectsForNudging(mobj_t *thing)
|
|
|
|
Blockmap search function.
|
|
Finds objects around the bot to steer towards/away from.
|
|
|
|
Input Arguments:-
|
|
thing - Object passed in from iteration.
|
|
|
|
Return:-
|
|
BlockItReturn_t enum, see its definition for more information.
|
|
--------------------------------------------------*/
|
|
static BlockItReturn_t K_FindObjectsForNudging(mobj_t *thing)
|
|
{
|
|
ZoneScoped;
|
|
|
|
INT16 angledelta, anglediff;
|
|
angle_t destangle, angle;
|
|
UINT8 side = 0;
|
|
|
|
if (!g_nudgeSearch.botmo || P_MobjWasRemoved(g_nudgeSearch.botmo) || !g_nudgeSearch.botmo->player)
|
|
{
|
|
return BMIT_ABORT;
|
|
}
|
|
|
|
if (thing->health <= 0)
|
|
{
|
|
return BMIT_CONTINUE;
|
|
}
|
|
|
|
if (g_nudgeSearch.botmo == thing)
|
|
{
|
|
return BMIT_CONTINUE;
|
|
}
|
|
|
|
const fixed_t xDelta = abs(g_nudgeSearch.botmo->x - thing->x);
|
|
const fixed_t yDelta = abs(g_nudgeSearch.botmo->y - thing->y);
|
|
const fixed_t fullDist = (FixedMul(xDelta, xDelta) + FixedMul(yDelta, yDelta)) - FixedMul(thing->radius, thing->radius);
|
|
|
|
if (fullDist > g_nudgeSearch.distancetocheck)
|
|
{
|
|
return BMIT_CONTINUE;
|
|
}
|
|
|
|
#if 0
|
|
// this is very expensive to do, and probably not worth it.
|
|
if (P_CheckSight(g_nudgeSearch.botmo, thing) == false)
|
|
{
|
|
return BMIT_CONTINUE;
|
|
}
|
|
#endif
|
|
|
|
destangle = R_PointToAngle2(g_nudgeSearch.botmo->x, g_nudgeSearch.botmo->y, thing->x, thing->y);
|
|
angle = (g_nudgeSearch.angle - destangle);
|
|
|
|
if (angle < ANGLE_180)
|
|
{
|
|
angledelta = AngleFixed(angle)>>FRACBITS;
|
|
}
|
|
else
|
|
{
|
|
angledelta = 360-(AngleFixed(angle)>>FRACBITS);
|
|
side = 1;
|
|
}
|
|
|
|
anglediff = abs(angledelta);
|
|
|
|
switch (thing->type)
|
|
{
|
|
case MT_BANANA:
|
|
case MT_BANANA_SHIELD:
|
|
case MT_EGGMANITEM_SHIELD:
|
|
case MT_ORBINAUT:
|
|
case MT_ORBINAUT_SHIELD:
|
|
case MT_JAWZ:
|
|
case MT_JAWZ_SHIELD:
|
|
case MT_SSMINE:
|
|
case MT_SSMINE_SHIELD:
|
|
case MT_LANDMINE:
|
|
case MT_DROPTARGET:
|
|
case MT_DROPTARGET_SHIELD:
|
|
case MT_BALLHOG:
|
|
case MT_SPB:
|
|
case MT_BUBBLESHIELDTRAP:
|
|
case MT_DUELBOMB:
|
|
case MT_GACHABOM:
|
|
case MT_SPECIALSTAGEBOMB:
|
|
K_AddDodgeObject(thing, side, 20);
|
|
break;
|
|
case MT_SHRINK_GUN:
|
|
if (thing->target == g_nudgeSearch.botmo)
|
|
{
|
|
K_AddAttackObject(thing, side, 20);
|
|
}
|
|
else
|
|
{
|
|
K_AddDodgeObject(thing, side, 20);
|
|
}
|
|
break;
|
|
case MT_RANDOMITEM:
|
|
if (anglediff >= 45)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (P_CanPickupItem(g_nudgeSearch.botmo->player, 1))
|
|
{
|
|
K_AddAttackObject(thing, side, ((thing->extravalue1 < RINGBOX_TIME) ? 10 : 20));
|
|
}
|
|
break;
|
|
case MT_EGGMANITEM:
|
|
if (anglediff >= 45)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (P_CanPickupItem(g_nudgeSearch.botmo->player, 1)) // Can pick up an actual item
|
|
{
|
|
const UINT8 stealth = K_EggboxStealth(thing->x, thing->y);
|
|
const UINT8 requiredstealth = (g_nudgeSearch.botmo->player->botvars.difficulty * g_nudgeSearch.botmo->player->botvars.difficulty);
|
|
|
|
if (stealth >= requiredstealth)
|
|
{
|
|
K_AddAttackObject(thing, side, 20);
|
|
}
|
|
else
|
|
{
|
|
K_AddDodgeObject(thing, side, 20);
|
|
}
|
|
}
|
|
break;
|
|
case MT_FLOATINGITEM:
|
|
if (anglediff >= 45)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (P_CanPickupItem(g_nudgeSearch.botmo->player, 3))
|
|
{
|
|
K_AddAttackObject(thing, side, 20);
|
|
}
|
|
break;
|
|
case MT_RING:
|
|
case MT_FLINGRING:
|
|
if (anglediff >= 45)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if ((RINGTOTAL(g_nudgeSearch.botmo->player) < 20 && !(g_nudgeSearch.botmo->player->pflags & PF_RINGLOCK)
|
|
&& P_CanPickupItem(g_nudgeSearch.botmo->player, 0))
|
|
&& !thing->extravalue1
|
|
&& (g_nudgeSearch.botmo->player->itemtype != KITEM_LIGHTNINGSHIELD))
|
|
{
|
|
K_AddAttackObject(thing, side, (RINGTOTAL(g_nudgeSearch.botmo->player) < 3) ? 5 : 1);
|
|
}
|
|
break;
|
|
case MT_PLAYER:
|
|
if (thing->player
|
|
&& !thing->player->spectator
|
|
&& !thing->player->hyudorotimer
|
|
&& !g_nudgeSearch.botmo->player->hyudorotimer)
|
|
{
|
|
// There REALLY ought to be a better way to handle this logic, right?!
|
|
// Squishing
|
|
if (K_PlayerAttackSteer(thing, side, 20,
|
|
K_IsBigger(g_nudgeSearch.botmo, thing),
|
|
K_IsBigger(thing, g_nudgeSearch.botmo)
|
|
))
|
|
{
|
|
break;
|
|
}
|
|
// Invincibility
|
|
else if (K_PlayerAttackSteer(thing, side, 20,
|
|
g_nudgeSearch.botmo->player->invincibilitytimer,
|
|
thing->player->invincibilitytimer
|
|
))
|
|
{
|
|
break;
|
|
}
|
|
// Lightning Shield
|
|
else if (K_PlayerAttackSteer(thing, side, 20,
|
|
g_nudgeSearch.botmo->player->itemtype == KITEM_LIGHTNINGSHIELD,
|
|
thing->player->itemtype == KITEM_LIGHTNINGSHIELD
|
|
))
|
|
{
|
|
break;
|
|
}
|
|
// Bubble Shield
|
|
else if (K_PlayerAttackSteer(thing, side, 20,
|
|
g_nudgeSearch.botmo->player->itemtype == KITEM_BUBBLESHIELD,
|
|
thing->player->itemtype == KITEM_BUBBLESHIELD
|
|
))
|
|
{
|
|
break;
|
|
}
|
|
// Flame Shield
|
|
else if (K_PlayerAttackSteer(thing, side, 20,
|
|
g_nudgeSearch.botmo->player->itemtype == KITEM_FLAMESHIELD,
|
|
thing->player->itemtype == KITEM_FLAMESHIELD
|
|
))
|
|
{
|
|
break;
|
|
}
|
|
// Has held item shield
|
|
else if (K_PlayerAttackSteer(thing, side, 20,
|
|
(thing->player->itemflags & (IF_ITEMOUT|IF_EGGMANOUT)),
|
|
(g_nudgeSearch.botmo->player->itemflags & (IF_ITEMOUT|IF_EGGMANOUT))
|
|
))
|
|
{
|
|
break;
|
|
}
|
|
// Ring Sting
|
|
else if (K_PlayerAttackSteer(thing, side, 20,
|
|
thing->player->rings <= 0,
|
|
g_nudgeSearch.botmo->player->rings <= 0
|
|
))
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// After ALL of that, we can do standard bumping
|
|
fixed_t ourweight = K_GetMobjWeight(g_nudgeSearch.botmo, thing);
|
|
fixed_t theirweight = K_GetMobjWeight(thing, g_nudgeSearch.botmo);
|
|
fixed_t weightdiff = 0;
|
|
|
|
if (anglediff >= 90)
|
|
{
|
|
weightdiff = theirweight - ourweight;
|
|
}
|
|
else
|
|
{
|
|
weightdiff = ourweight - theirweight;
|
|
}
|
|
|
|
if (weightdiff > mapobjectscale)
|
|
{
|
|
K_AddAttackObject(thing, side, 20);
|
|
}
|
|
else
|
|
{
|
|
K_AddDodgeObject(thing, side, 20);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case MT_BOTHINT:
|
|
if (anglediff >= 45)
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
UINT8 weight = 20;
|
|
|
|
if (thing->extravalue2 > 0)
|
|
{
|
|
weight = thing->extravalue2 * 5;
|
|
}
|
|
|
|
if (thing->extravalue1 == 0)
|
|
{
|
|
K_AddDodgeObject(thing, side, weight);
|
|
}
|
|
else
|
|
{
|
|
K_AddAttackObject(thing, side, weight);
|
|
}
|
|
}
|
|
break;
|
|
case MT_RINGSHOOTER:
|
|
if (anglediff >= 45)
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
K_AddAttackObject(thing, side, 50);
|
|
}
|
|
break;
|
|
default:
|
|
if (thing->flags & (MF_SOLID|MF_ENEMY|MF_BOSS|MF_PAIN|MF_MISSILE))
|
|
{
|
|
K_AddDodgeObject(thing, side, 20);
|
|
}
|
|
break;
|
|
}
|
|
|
|
return BMIT_CONTINUE;
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
void K_NudgePredictionTowardsObjects(botprediction_t *predict, const player_t *player)
|
|
|
|
See header file for description.
|
|
--------------------------------------------------*/
|
|
void K_NudgePredictionTowardsObjects(botprediction_t *predict, const player_t *player)
|
|
{
|
|
ZoneScoped;
|
|
|
|
const precise_t time = I_GetPreciseTime();
|
|
|
|
INT32 xl, xh, yl, yh, bx, by;
|
|
|
|
fixed_t distToPredict = 0;
|
|
fixed_t radToPredict = 0;
|
|
angle_t angleToPredict = 0;
|
|
|
|
fixed_t avgX = 0, avgY = 0;
|
|
fixed_t avgDist = 0;
|
|
|
|
fixed_t baseNudge = 0;
|
|
fixed_t maxNudge = 0;
|
|
fixed_t nudgeDist = 0;
|
|
angle_t nudgeDir = 0;
|
|
|
|
SINT8 gotoSide = -1;
|
|
UINT8 i;
|
|
|
|
if (predict == NULL)
|
|
{
|
|
ps_bots[player - players].nudge += I_GetPreciseTime() - time;
|
|
return;
|
|
}
|
|
|
|
distToPredict = R_PointToDist2(player->mo->x, player->mo->y, predict->x, predict->y);
|
|
angleToPredict = R_PointToAngle2(player->mo->x, player->mo->y, predict->x, predict->y);
|
|
|
|
radToPredict = distToPredict >> 1;
|
|
g_nudgeSearch.distancetocheck = FixedMul(radToPredict, radToPredict);
|
|
|
|
baseNudge = predict->baseRadius >> 3;
|
|
maxNudge = predict->baseRadius - baseNudge;
|
|
|
|
g_nudgeSearch.botmo = player->mo;
|
|
g_nudgeSearch.angle = angleToPredict;
|
|
|
|
// silly variable reuse
|
|
avgX = g_nudgeSearch.botmo->x + FixedMul(radToPredict, FINECOSINE(angleToPredict >> ANGLETOFINESHIFT));
|
|
avgY = g_nudgeSearch.botmo->y + FixedMul(radToPredict, FINESINE(angleToPredict >> ANGLETOFINESHIFT));
|
|
|
|
for (i = 0; i < 2; i++)
|
|
{
|
|
g_nudgeSearch.gotoAvgX[i] = g_nudgeSearch.gotoAvgY[i] = 0;
|
|
g_nudgeSearch.gotoObjs[i] = 0;
|
|
|
|
g_nudgeSearch.avoidAvgX[i] = g_nudgeSearch.avoidAvgY[i] = 0;
|
|
g_nudgeSearch.avoidObjs[i] = 0;
|
|
}
|
|
|
|
xl = (unsigned)(avgX - (radToPredict + MAXRADIUS) - bmaporgx)>>MAPBLOCKSHIFT;
|
|
xh = (unsigned)(avgX + (radToPredict + MAXRADIUS) - bmaporgx)>>MAPBLOCKSHIFT;
|
|
yl = (unsigned)(avgY - (radToPredict + MAXRADIUS) - bmaporgy)>>MAPBLOCKSHIFT;
|
|
yh = (unsigned)(avgY + (radToPredict + MAXRADIUS) - bmaporgy)>>MAPBLOCKSHIFT;
|
|
|
|
BMBOUNDFIX(xl, xh, yl, yh);
|
|
|
|
for (bx = xl; bx <= xh; bx++)
|
|
{
|
|
for (by = yl; by <= yh; by++)
|
|
{
|
|
P_BlockThingsIterator(bx, by, K_FindObjectsForNudging);
|
|
}
|
|
}
|
|
|
|
// Handle dodge characters
|
|
if (g_nudgeSearch.avoidObjs[1] > 0 || g_nudgeSearch.avoidObjs[0] > 0)
|
|
{
|
|
if (g_nudgeSearch.avoidObjs[1] > g_nudgeSearch.avoidObjs[0])
|
|
{
|
|
gotoSide = 1;
|
|
}
|
|
else
|
|
{
|
|
gotoSide = 0;
|
|
}
|
|
|
|
avgX = (g_nudgeSearch.avoidAvgX[gotoSide] / g_nudgeSearch.avoidObjs[gotoSide]) * mapobjectscale;
|
|
avgY = (g_nudgeSearch.avoidAvgY[gotoSide] / g_nudgeSearch.avoidObjs[gotoSide]) * mapobjectscale;
|
|
|
|
avgDist = R_PointToDist2(
|
|
avgX, avgY,
|
|
predict->x, predict->y
|
|
);
|
|
|
|
// High handling characters dodge better
|
|
nudgeDist = ((9 - g_nudgeSearch.botmo->player->kartweight) + 1) * baseNudge;
|
|
if (nudgeDist > maxNudge)
|
|
{
|
|
nudgeDist = maxNudge;
|
|
}
|
|
|
|
// Point away
|
|
nudgeDir = R_PointToAngle2(
|
|
avgX, avgY,
|
|
predict->x, predict->y
|
|
);
|
|
|
|
predict->x += FixedMul(nudgeDist, FINECOSINE(nudgeDir >> ANGLETOFINESHIFT));
|
|
predict->y += FixedMul(nudgeDist, FINESINE(nudgeDir >> ANGLETOFINESHIFT));
|
|
predict->radius = std::max(predict->radius - nudgeDist, baseNudge);
|
|
|
|
distToPredict = R_PointToDist2(player->mo->x, player->mo->y, predict->x, predict->y);
|
|
|
|
// Flip side, since we want to check for objects to steer towards on the side we're NOT dodging.
|
|
if (gotoSide == 0)
|
|
{
|
|
gotoSide = 1;
|
|
}
|
|
else
|
|
{
|
|
gotoSide = 0;
|
|
}
|
|
}
|
|
|
|
if (gotoSide == -1)
|
|
{
|
|
// Pick a side here if there were no objects to dodge.
|
|
// We don't want to pick contradictory sides, so keep the old side otherwise,
|
|
// even if there's more to grab on the other side.
|
|
|
|
if (g_nudgeSearch.gotoObjs[1] > g_nudgeSearch.gotoObjs[0])
|
|
{
|
|
gotoSide = 1;
|
|
}
|
|
else
|
|
{
|
|
gotoSide = 0;
|
|
}
|
|
}
|
|
|
|
// Check if our side is invalid, if so, don't do the code below.
|
|
if (gotoSide != -1 && g_nudgeSearch.gotoObjs[gotoSide] == 0)
|
|
{
|
|
// Do not use a side
|
|
gotoSide = -1;
|
|
}
|
|
|
|
if (gotoSide != -1)
|
|
{
|
|
avgX = (g_nudgeSearch.gotoAvgX[gotoSide] / g_nudgeSearch.gotoObjs[gotoSide]) * mapobjectscale;
|
|
avgY = (g_nudgeSearch.gotoAvgY[gotoSide] / g_nudgeSearch.gotoObjs[gotoSide]) * mapobjectscale;
|
|
|
|
avgDist = R_PointToDist2(
|
|
predict->x, predict->y,
|
|
avgX, avgY
|
|
);
|
|
|
|
// Acceleration characters are more aggressive
|
|
nudgeDist = ((9 - g_nudgeSearch.botmo->player->kartspeed) + 1) * baseNudge;
|
|
if (nudgeDist > maxNudge)
|
|
{
|
|
nudgeDist = maxNudge;
|
|
}
|
|
|
|
if (avgDist <= nudgeDist)
|
|
{
|
|
predict->x = avgX;
|
|
predict->y = avgY;
|
|
predict->radius = baseNudge;
|
|
}
|
|
else
|
|
{
|
|
// Point towards
|
|
nudgeDir = R_PointToAngle2(
|
|
predict->x, predict->y,
|
|
avgX, avgY
|
|
);
|
|
|
|
predict->x += FixedMul(nudgeDist, FINECOSINE(nudgeDir >> ANGLETOFINESHIFT));
|
|
predict->y += FixedMul(nudgeDist, FINESINE(nudgeDir >> ANGLETOFINESHIFT));
|
|
predict->radius = std::max(predict->radius - nudgeDist, baseNudge);
|
|
|
|
//distToPredict = R_PointToDist2(player->mo->x, player->mo->y, predict->x, predict->y);
|
|
}
|
|
}
|
|
|
|
ps_bots[player - players].nudge += I_GetPreciseTime() - time;
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
static BlockItReturn_t K_FindPlayersToBully(mobj_t *thing)
|
|
|
|
Blockmap search function.
|
|
Finds players around the bot to bump.
|
|
|
|
Input Arguments:-
|
|
thing - Object passed in from iteration.
|
|
|
|
Return:-
|
|
BlockItReturn_t enum, see its definition for more information.
|
|
--------------------------------------------------*/
|
|
static struct bullySearch_s
|
|
{
|
|
mobj_t *botmo;
|
|
fixed_t distancetocheck;
|
|
|
|
fixed_t annoyscore;
|
|
mobj_t *annoymo;
|
|
} g_bullySearch;
|
|
|
|
static BlockItReturn_t K_FindPlayersToBully(mobj_t *thing)
|
|
{
|
|
INT16 anglediff;
|
|
fixed_t fulldist;
|
|
fixed_t ourweight, theirweight, weightdiff;
|
|
angle_t ourangle, destangle, angle;
|
|
|
|
if (!g_bullySearch.botmo || P_MobjWasRemoved(g_bullySearch.botmo) || !g_bullySearch.botmo->player)
|
|
{
|
|
return BMIT_ABORT;
|
|
}
|
|
|
|
if (thing->health <= 0)
|
|
{
|
|
return BMIT_CONTINUE;
|
|
}
|
|
|
|
if (!thing->player || thing->player->spectator)
|
|
{
|
|
return BMIT_CONTINUE;
|
|
}
|
|
|
|
if (g_bullySearch.botmo == thing)
|
|
{
|
|
return BMIT_CONTINUE;
|
|
}
|
|
|
|
fulldist = R_PointToDist2(g_bullySearch.botmo->x, g_bullySearch.botmo->y, thing->x, thing->y) - thing->radius;
|
|
|
|
if (fulldist > g_bullySearch.distancetocheck)
|
|
{
|
|
return BMIT_CONTINUE;
|
|
}
|
|
|
|
if (P_CheckSight(g_bullySearch.botmo, thing) == false)
|
|
{
|
|
return BMIT_CONTINUE;
|
|
}
|
|
|
|
ourangle = g_bullySearch.botmo->angle;
|
|
destangle = R_PointToAngle2(g_bullySearch.botmo->x, g_bullySearch.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(g_bullySearch.botmo, thing);
|
|
theirweight = K_GetMobjWeight(thing, g_bullySearch.botmo);
|
|
weightdiff = 0;
|
|
|
|
if (anglediff >= 90)
|
|
{
|
|
weightdiff = theirweight - ourweight;
|
|
}
|
|
else
|
|
{
|
|
weightdiff = ourweight - theirweight;
|
|
}
|
|
|
|
if (weightdiff > mapobjectscale && weightdiff > g_bullySearch.annoyscore)
|
|
{
|
|
g_bullySearch.annoyscore = weightdiff;
|
|
g_bullySearch.annoymo = thing;
|
|
}
|
|
|
|
return BMIT_CONTINUE;
|
|
}
|
|
|
|
/*--------------------------------------------------
|
|
INT32 K_PositionBully(const player_t *player)
|
|
|
|
See header file for description.
|
|
--------------------------------------------------*/
|
|
INT32 K_PositionBully(const player_t *player)
|
|
{
|
|
ZoneScoped;
|
|
|
|
INT32 xl, xh, yl, yh, bx, by;
|
|
|
|
angle_t ourangle, destangle, angle;
|
|
INT16 anglediff;
|
|
|
|
g_bullySearch.botmo = player->mo;
|
|
g_bullySearch.distancetocheck = 1024*player->mo->scale;
|
|
|
|
g_bullySearch.annoymo = NULL;
|
|
g_bullySearch.annoyscore = 0;
|
|
|
|
xl = (unsigned)(g_bullySearch.botmo->x - g_bullySearch.distancetocheck - bmaporgx)>>MAPBLOCKSHIFT;
|
|
xh = (unsigned)(g_bullySearch.botmo->x + g_bullySearch.distancetocheck - bmaporgx)>>MAPBLOCKSHIFT;
|
|
yl = (unsigned)(g_bullySearch.botmo->y - g_bullySearch.distancetocheck - bmaporgy)>>MAPBLOCKSHIFT;
|
|
yh = (unsigned)(g_bullySearch.botmo->y + g_bullySearch.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 (g_bullySearch.annoymo == NULL)
|
|
{
|
|
return INT32_MAX;
|
|
}
|
|
|
|
ourangle = g_bullySearch.botmo->angle;
|
|
destangle = R_PointToAngle2(g_bullySearch.botmo->x, g_bullySearch.botmo->y, g_bullySearch.annoymo->x, g_bullySearch.annoymo->y);
|
|
angle = (ourangle - destangle);
|
|
|
|
if (angle < ANGLE_180)
|
|
{
|
|
anglediff = AngleFixed(angle)>>FRACBITS;
|
|
}
|
|
else
|
|
{
|
|
anglediff = 360-(AngleFixed(angle)>>FRACBITS);
|
|
}
|
|
|
|
if (abs(anglediff) < 30)
|
|
return 0;
|
|
|
|
if (anglediff < 0)
|
|
return -KART_FULLTURN;
|
|
|
|
return KART_FULLTURN;
|
|
}
|