From 02605a5aceb4282b435c52cb002bebb480169925 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Sun, 24 May 2020 15:08:10 -0400 Subject: [PATCH] Split bot code into multiple files & clean up --- src/CMakeLists.txt | 2 + src/Makefile | 2 + src/k_bot.c | 1655 ++------------------------------------------ src/k_bot.h | 196 +++++- src/k_botitem.c | 1095 +++++++++++++++++++++++++++++ src/k_botsearch.c | 749 ++++++++++++++++++++ 6 files changed, 2109 insertions(+), 1590 deletions(-) create mode 100644 src/k_botitem.c create mode 100644 src/k_botsearch.c diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 59cb146c0..2cba3c836 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -166,6 +166,8 @@ set(SRB2_CORE_GAME_SOURCES k_waypoint.c k_color.c k_bot.c + k_botitem.c + k_botsearch.c p_local.h p_maputl.h diff --git a/src/Makefile b/src/Makefile index 21a08eaab..84d47da5c 100644 --- a/src/Makefile +++ b/src/Makefile @@ -563,6 +563,8 @@ OBJS:=$(i_main_o) \ $(OBJDIR)/lzf.o \ $(OBJDIR)/vid_copy.o \ $(OBJDIR)/k_bot.o \ + $(OBJDIR)/k_botitem.o \ + $(OBJDIR)/k_botsearch.o \ $(i_cdmus_o) \ $(i_net_o) \ $(i_system_o) \ diff --git a/src/k_bot.c b/src/k_bot.c index da4f432a5..927fc357a 100644 --- a/src/k_bot.c +++ b/src/k_bot.c @@ -1,14 +1,13 @@ -// SONIC ROBO BLAST 2 +// SONIC ROBO BLAST 2 KART //----------------------------------------------------------------------------- -// Copyright (C) 2007-2016 by John "JTE" Muniz. -// Copyright (C) 2011-2018 by Sonic Team Junior. +// Copyright (C) 2018-2020 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_bot.c -/// \brief Basic bot handling +/// \brief Bot logic & ticcmd generation code #include "doomdef.h" #include "d_player.h" @@ -27,6 +26,12 @@ #include "m_random.h" #include "r_things.h" // numskins + +/*-------------------------------------------------- + boolean K_AddBot(UINT8 skin, UINT8 difficulty, UINT8 *p) + + See header file for description. +--------------------------------------------------*/ boolean K_AddBot(UINT8 skin, UINT8 difficulty, UINT8 *p) { UINT8 buf[3]; @@ -93,6 +98,11 @@ boolean K_AddBot(UINT8 skin, UINT8 difficulty, UINT8 *p) return true; } +/*-------------------------------------------------- + void K_UpdateMatchRaceBots(void) + + See header file for description. +--------------------------------------------------*/ void K_UpdateMatchRaceBots(void) { const UINT8 difficulty = cv_kartbot.value; @@ -197,6 +207,11 @@ void K_UpdateMatchRaceBots(void) // We should have enough bots now :) } +/*-------------------------------------------------- + boolean K_PlayerUsesBotMovement(player_t *player) + + See header file for description. +--------------------------------------------------*/ boolean K_PlayerUsesBotMovement(player_t *player) { if (player->bot || player->exiting) @@ -205,6 +220,11 @@ boolean K_PlayerUsesBotMovement(player_t *player) return false; } +/*-------------------------------------------------- + boolean K_BotCanTakeCut(player_t *player) + + See header file for description. +--------------------------------------------------*/ boolean K_BotCanTakeCut(player_t *player) { if (!K_ApplyOffroad(player) @@ -217,6 +237,18 @@ boolean K_BotCanTakeCut(player_t *player) return false; } +/*-------------------------------------------------- + static UINT32 K_BotRubberbandDistance(player_t *player) + + Calculates the distance away from 1st place that the + bot should rubberband to. + + Input Arguments:- + player - Player to compare. + + Return:- + Distance to add, as an integer. +--------------------------------------------------*/ static UINT32 K_BotRubberbandDistance(player_t *player) { const UINT32 spacing = 2048; @@ -252,6 +284,11 @@ static UINT32 K_BotRubberbandDistance(player_t *player) return (pos * spacing); } +/*-------------------------------------------------- + fixed_t K_BotRubberband(player_t *player) + + See header file for description. +--------------------------------------------------*/ fixed_t K_BotRubberband(player_t *player) { fixed_t rubberband = FRACUNIT; @@ -313,7 +350,12 @@ fixed_t K_BotRubberband(player_t *player) return rubberband; } -static fixed_t K_DistanceOfLineFromPoint(fixed_t v1x, fixed_t v1y, fixed_t v2x, fixed_t v2y, fixed_t cx, fixed_t cy) +/*-------------------------------------------------- + fixed_t K_DistanceOfLineFromPoint(fixed_t v1x, fixed_t v1y, fixed_t v2x, fixed_t v2y, fixed_t cx, fixed_t cy) + + See header file for description. +--------------------------------------------------*/ +fixed_t K_DistanceOfLineFromPoint(fixed_t v1x, fixed_t v1y, fixed_t v2x, fixed_t v2y, fixed_t cx, fixed_t cy) { fixed_t v1toc[2] = {cx - v1x, cy - v1y}; fixed_t v1tov2[2] = {v2x - v1x, v2y - v1y}; @@ -337,294 +379,17 @@ static fixed_t K_DistanceOfLineFromPoint(fixed_t v1x, fixed_t v1y, fixed_t v2x, return P_AproxDistance(cx - px, cy - py); } -static boolean K_BotHatesThisSectorsSpecial(player_t *player, sector_t *sec) -{ - switch (GETSECSPECIAL(sec->special, 1)) - { - case 1: // Damage - case 5: // Spikes - case 6: case 7: // Death Pit - case 8: // Instant Kill - return true; - //case 2: case 3: // Offroad (let's let them lawnmower) - case 4: // Offroad (Strong) - if (!K_BotCanTakeCut(player)) - return true; - default: - break; - } +/*-------------------------------------------------- + static botprediction_t *K_CreateBotPrediction(player_t *player) - return false; -} + Calculates a point further along the track to attempt to drive towards. -static boolean K_BotHatesThisSector(player_t *player, sector_t *sec) -{ - const boolean flip = (player->mo->eflags & MFE_VERTICALFLIP); - INT32 flag; - ffloor_t *rover; - - if (flip) - { - flag = SF_FLIPSPECIAL_CEILING; - } - else - { - flag = SF_FLIPSPECIAL_FLOOR; - } - - if (sec->flags & flag) - { - if (K_BotHatesThisSectorsSpecial(player, sec)) - { - return true; - } - } - - for (rover = sec->ffloors; rover; rover = rover->next) - { - if (!(rover->flags & FF_EXISTS)) - { - continue; - } - - if (!(rover->master->frontsector->flags & flag)) - { - continue; - } - - if (((*rover->bottomheight >= player->mo->z + player->mo->height) && (flip)) - || ((*rover->topheight <= player->mo->z) && (!flip))) - { - if (K_BotHatesThisSectorsSpecial(player, sec)) - { - return true; - } - } - } - - return false; -} - -mobj_t *botmo = NULL; -fixed_t distancetocheck = 0; - -fixed_t closestlinedist = INT32_MAX; - -INT16 curturn = 0; -INT16 badsteerglobal = 0; - -fixed_t eggboxx, eggboxy; -UINT8 randomitems = 0; -UINT8 eggboxes = 0; - -static boolean K_FindEggboxes(mobj_t *thing) -{ - fixed_t dist; - - if (thing->type != MT_RANDOMITEM && thing->type != MT_EGGMANITEM) - { - return true; - } - - if (!thing->health) - { - return true; - } - - dist = P_AproxDistance(thing->x - eggboxx, thing->y - eggboxy); - - if (dist > distancetocheck) - { - return true; - } - - if (thing->type == MT_RANDOMITEM) - { - randomitems++; - } - else - { - eggboxes++; - } - return true; -} - -static UINT8 K_EggboxStealth(fixed_t x, fixed_t y) -{ - INT32 xl, xh, yl, yh, bx, by; - - eggboxx = x; - eggboxy = y; - distancetocheck = (mapobjectscale * 256); - randomitems = 0; - eggboxes = 0; - - xl = (unsigned)(eggboxx - distancetocheck - bmaporgx)>>MAPBLOCKSHIFT; - xh = (unsigned)(eggboxx + distancetocheck - bmaporgx)>>MAPBLOCKSHIFT; - yl = (unsigned)(eggboxy - distancetocheck - bmaporgy)>>MAPBLOCKSHIFT; - yh = (unsigned)(eggboxy + 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 (randomitems * eggboxes); -} - -static inline 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; - - if (!botmo || P_MobjWasRemoved(botmo) || !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(botmo->x, 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, botmo); - - if (botmo->player->kartstuff[k_waterskip]) - maxstep += maxstepmove; - - if (P_MobjTouchingSectorSpecial(botmo, 1, 13, false)) - maxstep <<= 1; - else if (P_MobjTouchingSectorSpecial(botmo, 1, 12, false)) - maxstep = 0; - - if ((openrange < botmo->height) // doesn't fit - || (opentop - botmo->z < botmo->height) // mobj is too high - || (openbottom - botmo->z > maxstep)) // too big a step up - { - goto blocked; - } - - if (!K_BotHatesThisSector(botmo->player, botmo->subsector->sector)) - { - // Treat damage sectors like walls - - if (lineside) - { - if (K_BotHatesThisSector(botmo->player, line->frontsector)) - goto blocked; - } - else - { - if (K_BotHatesThisSector(botmo->player, line->backsector)) - goto blocked; - } - } - - // We weren't blocked! - return true; - -blocked: - linedist = K_DistanceOfLineFromPoint(line->v1->x, line->v1->y, line->v2->x, line->v2->y, botmo->x, botmo->y); - linedist -= (botmo->radius * 8); // Maintain a reasonable distance away from it - - if (linedist > distancetocheck) - { - return true; - } - - if (linedist <= 0) - { - closestlinedist = 0; - return false; - } - - if (linedist < closestlinedist) - { - closestlinedist = linedist; - } - - return true; -} - -static fixed_t K_BotReducePrediction(player_t *player) -{ - INT32 xl, xh, yl, yh, bx, by; - - botmo = player->mo; - distancetocheck = (player->mo->radius * 8) + (player->speed * 4); - closestlinedist = INT32_MAX; - - tmx = player->mo->x; - tmy = player->mo->y; - - xl = (unsigned)(tmx - distancetocheck - bmaporgx)>>MAPBLOCKSHIFT; - xh = (unsigned)(tmx + distancetocheck - bmaporgx)>>MAPBLOCKSHIFT; - yl = (unsigned)(tmy - distancetocheck - bmaporgy)>>MAPBLOCKSHIFT; - yh = (unsigned)(tmy + distancetocheck - bmaporgy)>>MAPBLOCKSHIFT; - - BMBOUNDFIX(xl, xh, yl, yh); - - tmbbox[BOXTOP] = tmy + distancetocheck; - tmbbox[BOXBOTTOM] = tmy - distancetocheck; - tmbbox[BOXRIGHT] = tmx + distancetocheck; - tmbbox[BOXLEFT] = tmx - 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 (closestlinedist == INT32_MAX) - { - return FRACUNIT; - } - - return FixedDiv(closestlinedist, distancetocheck); -} + Input Arguments:- + player - Player to compare. + Return:- + Bot prediction struct. +--------------------------------------------------*/ 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 @@ -766,581 +531,11 @@ static botprediction_t *K_CreateBotPrediction(player_t *player) return predict; } -static void K_SteerFromObject(mobj_t *bot, mobj_t *thing, fixed_t fulldist, fixed_t xdist, boolean towards, INT16 amount) -{ - angle_t destangle = R_PointToAngle2(bot->x, bot->y, thing->x, thing->y); - angle_t angle; - SINT8 flip = 1; - - amount = (amount * FixedDiv(distancetocheck - fulldist, distancetocheck)) / FRACUNIT; - - if (amount == 0) - { - // Shouldn't happen - return; - } - - if (towards) - { - if (xdist < FixedHypot(bot->radius, thing->radius)) - { - // Don't need to turn any harder! - - if (abs(badsteerglobal) <= amount) - { - badsteerglobal = 0; - } - else - { - if (badsteerglobal > 0) - { - badsteerglobal -= amount; - } - else if (badsteerglobal < 0) - { - badsteerglobal += amount; - } - } - - return; - } - - // Still turning towards it, flip. - flip = -flip; - } - - angle = (bot->angle - destangle); - if (angle < ANGLE_180) - { - flip = -flip; - } - - // If going in the opposite direction of where you wanted to turn, - // then reduce the amount that you can turn in that direction. - if ((flip == 1 && curturn < 0) - || (flip == -1 && curturn > 0)) - { - amount /= 4; - } - - badsteerglobal += amount * flip; -} - -static boolean K_BotSteerObjects(mobj_t *thing) -{ - INT16 anglediff; - fixed_t xdist, ydist, fulldist; - angle_t destangle, angle; - INT16 attack = ((9 - botmo->player->kartspeed) * KART_FULLTURN) / 8; // Acceleration chars are more aggressive - INT16 dodge = ((9 - botmo->player->kartweight) * KART_FULLTURN) / 8; // Handling chars dodge better - - if (!botmo || P_MobjWasRemoved(botmo) || !botmo->player) - { - return false; - } - - if (!thing->health) - { - return true; - } - - if (botmo == thing) - { - return true; - } - - xdist = K_DistanceOfLineFromPoint( - botmo->x, botmo->y, - botmo->x + FINECOSINE(botmo->angle >> ANGLETOFINESHIFT), botmo->y + FINESINE(botmo->angle >> ANGLETOFINESHIFT), - thing->x, thing->y - ) / 2; // weight x dist more heavily than y dist - - ydist = K_DistanceOfLineFromPoint( - botmo->x, botmo->y, - botmo->x + FINECOSINE((botmo->angle + ANGLE_90) >> ANGLETOFINESHIFT), botmo->y + FINESINE((botmo->angle + ANGLE_90) >> ANGLETOFINESHIFT), - thing->x, thing->y - ); - - fulldist = FixedHypot(xdist, ydist); - - if (fulldist > distancetocheck) - { - return true; - } - - if (!P_CheckSight(botmo, thing)) - { - return true; - } - - destangle = R_PointToAngle2(botmo->x, botmo->y, thing->x, thing->y); - angle = (botmo->angle - destangle); - - if (angle < ANGLE_180) - { - anglediff = AngleFixed(angle)>>FRACBITS; - } - else - { - anglediff = 360-(AngleFixed(angle)>>FRACBITS); - } - - anglediff = abs(anglediff); - -#define PlayerAttackSteer(botcond, thingcond) \ - if ((botcond) && !(thingcond)) \ - { \ - K_SteerFromObject(botmo, thing, fulldist, xdist, true, 2 * (KART_FULLTURN + attack)); \ - } \ - else if ((thingcond) && !(botcond)) \ - { \ - K_SteerFromObject(botmo, thing, fulldist, xdist, false, 2 * (KART_FULLTURN + dodge)); \ - } - - 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_DUD: - case MT_JAWZ_SHIELD: - case MT_SSMINE: - case MT_SSMINE_SHIELD: - case MT_BALLHOG: - case MT_SPB: - case MT_BUBBLESHIELDTRAP: - K_SteerFromObject(botmo, thing, fulldist, xdist, false, 2 * (KART_FULLTURN + dodge)); - break; - case MT_RANDOMITEM: - if (anglediff >= 60) - { - break; - } - - if (P_CanPickupItem(botmo->player, 1)) - { - K_SteerFromObject(botmo, thing, fulldist, xdist, true, KART_FULLTURN + attack); - } - break; - case MT_EGGMANITEM: - if (anglediff >= 60) - { - break; - } - - if (P_CanPickupItem(botmo->player, 1)) // Can pick up an actual item - { - const UINT8 stealth = K_EggboxStealth(thing->x, thing->y); - const UINT8 requiredstealth = (botmo->player->botvars.difficulty * botmo->player->botvars.difficulty); - - if (stealth >= requiredstealth) - { - K_SteerFromObject(botmo, thing, fulldist, xdist, true, 2 * (KART_FULLTURN + attack)); - } - else - { - K_SteerFromObject(botmo, thing, fulldist, xdist, false, 2 * (KART_FULLTURN + dodge)); - } - } - break; - case MT_FLOATINGITEM: - if (anglediff >= 60) - { - break; - } - - if (P_CanPickupItem(botmo->player, 3)) - { - K_SteerFromObject(botmo, thing, fulldist, xdist, true, KART_FULLTURN + attack); - } - break; - case MT_RING: - case MT_FLINGRING: - if (anglediff >= 60) - { - break; - } - - if ((RINGTOTAL(botmo->player) < 20 && !botmo->player->kartstuff[k_ringlock] - && P_CanPickupItem(botmo->player, 0)) - && !thing->extravalue1 - && (botmo->player->kartstuff[k_itemtype] != KITEM_THUNDERSHIELD)) - { - K_SteerFromObject(botmo, thing, fulldist, xdist, true, - (RINGTOTAL(botmo->player) < 3 - ? (4 * (KART_FULLTURN + attack)) - : (KART_FULLTURN + attack)) - ); - } - break; - case MT_PLAYER: - if (thing->player - && !thing->player->kartstuff[k_hyudorotimer] - && !botmo->player->kartstuff[k_hyudorotimer]) - { - // There REALLY ought to be a better way to handle this logic, right?! - // Squishing - PlayerAttackSteer( - botmo->scale > thing->scale + (mapobjectscale/8), - thing->scale > botmo->scale + (mapobjectscale/8) - ) - // Invincibility - else PlayerAttackSteer( - botmo->player->kartstuff[k_invincibilitytimer], - thing->player->kartstuff[k_invincibilitytimer] - ) - // Thunder Shield - else PlayerAttackSteer( - botmo->player->kartstuff[k_itemtype] == KITEM_THUNDERSHIELD, - thing->player->kartstuff[k_itemtype] == KITEM_THUNDERSHIELD - ) - // Bubble Shield - else PlayerAttackSteer( - botmo->player->kartstuff[k_itemtype] == KITEM_BUBBLESHIELD, - thing->player->kartstuff[k_itemtype] == KITEM_BUBBLESHIELD - ) - // Flame Shield - else PlayerAttackSteer( - botmo->player->kartstuff[k_itemtype] == KITEM_FLAMESHIELD, - thing->player->kartstuff[k_itemtype] == KITEM_FLAMESHIELD - ) - // Has held item shield - else PlayerAttackSteer( - (botmo->player->kartstuff[k_itemheld] || botmo->player->kartstuff[k_eggmanheld]), - (thing->player->kartstuff[k_itemheld] || thing->player->kartstuff[k_eggmanheld]) - ) - // Ring Sting - else PlayerAttackSteer( - thing->player->kartstuff[k_rings] <= 0, - botmo->player->kartstuff[k_rings] <= 0 - ) - else - { - // After ALL of that, we can do standard bumping - fixed_t ourweight = K_GetMobjWeight(botmo, thing); - fixed_t theirweight = K_GetMobjWeight(thing, botmo); - fixed_t weightdiff = 0; - - if (anglediff >= 90) - { - weightdiff = theirweight - ourweight; - } - else - { - weightdiff = ourweight - theirweight; - } - - if (weightdiff > mapobjectscale) - { - K_SteerFromObject(botmo, thing, fulldist, xdist, true, KART_FULLTURN + attack); - } - else - { - K_SteerFromObject(botmo, thing, fulldist, xdist, false, KART_FULLTURN + dodge); - } - } - } - break; - case MT_BOTHINT: - if (anglediff >= 60) - { - break; - } - - if (thing->extravalue1 == 0) - { - K_SteerFromObject(botmo, thing, fulldist, xdist, false, thing->extravalue2 * (KART_FULLTURN + dodge)); - } - { - K_SteerFromObject(botmo, thing, fulldist, xdist, true, thing->extravalue2 * (KART_FULLTURN + attack)); - } - break; - default: - if (thing->flags & (MF_SOLID|MF_ENEMY|MF_BOSS|MF_PAIN|MF_MISSILE|MF_FIRE)) - { - K_SteerFromObject(botmo, thing, fulldist, xdist, false, 2 * (KART_FULLTURN + dodge)); - } - break; - } - - return true; -} - -static INT16 K_BotFindObjects(player_t *player, INT16 turn) -{ - INT32 xl, xh, yl, yh, bx, by; - - badsteerglobal = 0; - - botmo = player->mo; - curturn = turn; - distancetocheck = 2048*mapobjectscale; - - xl = (unsigned)(botmo->x - distancetocheck - bmaporgx)>>MAPBLOCKSHIFT; - xh = (unsigned)(botmo->x + distancetocheck - bmaporgx)>>MAPBLOCKSHIFT; - yl = (unsigned)(botmo->y - distancetocheck - bmaporgy)>>MAPBLOCKSHIFT; - yh = (unsigned)(botmo->y + 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_BotSteerObjects); - } - } - - return badsteerglobal; -} - -static boolean K_BotUseItemNearPlayer(player_t *player, ticcmd_t *cmd, fixed_t radius) -{ - UINT8 i; - - if (player->pflags & PF_ATTACKDOWN) - { - return false; - } - - for (i = 0; i < MAXPLAYERS; i++) - { - player_t *target = NULL; - fixed_t dist = INT32_MAX; - - if (!playeringame[i]) - { - continue; - } - - target = &players[i]; - - if (target->mo == NULL || P_MobjWasRemoved(target->mo) - || player == target || target->spectator - || target->powers[pw_flashing]) - { - continue; - } - - dist = P_AproxDistance(P_AproxDistance( - player->mo->x - target->mo->x, - player->mo->y - target->mo->y), - (player->mo->z - target->mo->z) / 4 - ); - - if (dist <= radius) - { - cmd->buttons |= BT_ATTACK; - return true; - } - } - - return false; -} - -static boolean K_PlayerNearSpot(player_t *player, fixed_t x, fixed_t y, fixed_t radius) -{ - UINT8 i; - - for (i = 0; i < MAXPLAYERS; i++) - { - player_t *target = NULL; - fixed_t dist = INT32_MAX; - - if (!playeringame[i]) - { - continue; - } - - target = &players[i]; - - if (target->mo == NULL || P_MobjWasRemoved(target->mo) - || player == target || target->spectator - || target->powers[pw_flashing]) - { - continue; - } - - dist = P_AproxDistance( - x - target->mo->x, - y - target->mo->y - ); - - if (dist <= radius) - { - return true; - } - } - - return false; -} - -static boolean K_BotRevealsBanana(player_t *player, INT16 turnamt, boolean mine) -{ - UINT8 i; - - // Only get out bananas if you have a target - - if (abs(turnamt) >= KART_FULLTURN/2) - { - return false; - } - else - { - UINT32 airtime = FixedDiv((30 * player->mo->scale) + player->mo->momz, gravity); - fixed_t throwspeed = FixedMul(82 * mapobjectscale, K_GetKartGameSpeedScalar(gamespeed)); - const angle_t momangle = R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy); - fixed_t estx = player->mo->x + P_ReturnThrustX(NULL, momangle, (throwspeed + player->speed) * airtime); - fixed_t esty = player->mo->y + P_ReturnThrustY(NULL, momangle, (throwspeed + player->speed) * airtime); - - if (K_PlayerNearSpot(player, estx, esty, player->mo->radius * 2)) - { - return true; - } - - if (mine) - { - airtime = FixedDiv((40 * player->mo->scale) + player->mo->momz, gravity); - throwspeed = FixedMul(82 * mapobjectscale, K_GetKartGameSpeedScalar(gamespeed)) * 2; - estx = player->mo->x + P_ReturnThrustX(NULL, momangle, (throwspeed + player->speed) * airtime); - esty = player->mo->y + P_ReturnThrustY(NULL, momangle, (throwspeed + player->speed) * airtime); - - if (K_PlayerNearSpot(player, estx, esty, player->mo->radius * 2)) - { - return true; - } - } - } - - for (i = 0; i < MAXPLAYERS; i++) - { - player_t *target = NULL; - fixed_t dist = INT32_MAX; - - if (!playeringame[i]) - { - continue; - } - - target = &players[i]; - - if (target->mo == NULL || P_MobjWasRemoved(target->mo) - || player == target || target->spectator - || target->powers[pw_flashing] - || !P_CheckSight(player->mo, target->mo)) - { - continue; - } - - dist = P_AproxDistance(P_AproxDistance( - player->mo->x - target->mo->x, - player->mo->y - target->mo->y), - (player->mo->z - target->mo->z) / 4 - ); - - if (dist <= (player->mo->radius * 16)) - { - angle_t a = player->mo->angle - R_PointToAngle2(player->mo->x, player->mo->y, target->mo->x, target->mo->y); - INT16 ad = 0; - const INT16 cone = 10; - - if (a < ANGLE_180) - { - ad = AngleFixed(a)>>FRACBITS; - } - else - { - ad = 360-(AngleFixed(a)>>FRACBITS); - } - - ad = abs(ad); - - if (ad >= 180-cone) - { - return true; - } - } - } - - return false; -} - -static boolean K_BotRevealsEggbox(player_t *player) -{ - const UINT32 airtime = FixedDiv((30 * player->mo->scale) + player->mo->momz, gravity); - const fixed_t throwspeed = FixedMul(82 * mapobjectscale, K_GetKartGameSpeedScalar(gamespeed)); - const angle_t momangle = R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy); - const fixed_t estx = player->mo->x + P_ReturnThrustX(NULL, momangle, (throwspeed + player->speed) * airtime); - const fixed_t esty = player->mo->y + P_ReturnThrustY(NULL, momangle, (throwspeed + player->speed) * airtime); - const UINT8 stealth = K_EggboxStealth(player->mo->x, player->mo->y); - UINT8 i; - - if (stealth > 1) - { - return true; - } - - if (K_PlayerNearSpot(player, estx, esty, player->mo->radius * 2)) - { - return true; - } - - for (i = 0; i < MAXPLAYERS; i++) - { - player_t *target = NULL; - fixed_t dist = INT32_MAX; - - if (!playeringame[i]) - { - continue; - } - - target = &players[i]; - - if (target->mo == NULL || P_MobjWasRemoved(target->mo) - || player == target || target->spectator - || target->powers[pw_flashing] - || !P_CheckSight(player->mo, target->mo)) - { - continue; - } - - dist = P_AproxDistance(P_AproxDistance( - player->mo->x - target->mo->x, - player->mo->y - target->mo->y), - (player->mo->z - target->mo->z) / 4 - ); - - if (dist <= (player->mo->radius * 16)) - { - angle_t a = player->mo->angle - R_PointToAngle2(player->mo->x, player->mo->y, target->mo->x, target->mo->y); - INT16 ad = 0; - const INT16 cone = 10; - - if (a < ANGLE_180) - { - ad = AngleFixed(a)>>FRACBITS; - } - else - { - ad = 360-(AngleFixed(a)>>FRACBITS); - } - - ad = abs(ad); - - if (ad >= 180-cone) - { - return true; - } - } - } - - return false; -} +/*-------------------------------------------------- + void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd) + See header file for description. +--------------------------------------------------*/ void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd) { botprediction_t *predict = NULL; @@ -1377,11 +572,19 @@ void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd) // Start boost handler if (leveltime <= starttime) { - if (leveltime >= starttime-35) + tic_t boosthold = starttime - TICRATE; + + boosthold -= (MAXBOTDIFFICULTY - player->botvars.difficulty); + + if (leveltime >= boosthold) + { cmd->buttons |= BT_ACCELERATE; + } + return; } + // Handle steering towards waypoints! if (player->nextwaypoint != NULL && player->nextwaypoint->mobj != NULL && !P_MobjWasRemoved(player->nextwaypoint->mobj)) { SINT8 turnsign = 0; @@ -1495,723 +698,8 @@ void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd) } } - if (player->kartstuff[k_userings] == 1) - { - if (!player->exiting) - { - INT32 saferingsval = 16 - K_GetKartRingPower(player); - - if (player->speed < K_GetKartSpeed(player, false)/2 // Being slowed down too much - || player->kartstuff[k_speedboost] > (FRACUNIT/5)) // Have another type of boost (tethering) - { - saferingsval -= 5; - } - - if (player->kartstuff[k_rings] > saferingsval) - { - cmd->buttons |= BT_ATTACK; - } - } - } - else if (player->kartstuff[k_itemroulette] && !(player->pflags & PF_ATTACKDOWN)) - { - // Mashing behaviors - - if (player->kartstuff[k_rings] < 0 && cv_superring.value) - { - // Uh oh, we need a loan! - // It'll be better in the long run for bots to lose an item set for 10 free rings. - cmd->buttons |= BT_ATTACK; - } - } - else - { - if (player->botvars.itemdelay) - { - player->botvars.itemdelay--; - player->botvars.itemconfirm = 0; - } - else if (player->kartstuff[k_eggmanexplode]) - { - if (player->kartstuff[k_position] == 1) - { - cmd->forwardmove /= 2; - cmd->buttons |= BT_BRAKE; - } - - K_BotUseItemNearPlayer(player, cmd, 128*player->mo->scale); - } - else if (player->kartstuff[k_eggmanheld]) - { - const UINT32 airtime = FixedDiv((30 * player->mo->scale) + player->mo->momz, gravity); - const fixed_t throwspeed = FixedMul(82 * mapobjectscale, K_GetKartGameSpeedScalar(gamespeed)); - const angle_t momangle = R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy); - const fixed_t estx = player->mo->x + P_ReturnThrustX(NULL, momangle, (throwspeed + player->speed) * airtime); - const fixed_t esty = player->mo->y + P_ReturnThrustY(NULL, momangle, (throwspeed + player->speed) * airtime); - const UINT8 stealth = K_EggboxStealth(player->mo->x, player->mo->y); - SINT8 throwdir = -1; - UINT8 i; - - player->botvars.itemconfirm++; - - if (K_PlayerNearSpot(player, estx, esty, player->mo->radius * 2)) - { - player->botvars.itemconfirm += player->botvars.difficulty / 2; - throwdir = 1; - } - - for (i = 0; i < MAXPLAYERS; i++) - { - player_t *target = NULL; - fixed_t dist = INT32_MAX; - - if (!playeringame[i]) - { - continue; - } - - target = &players[i]; - - if (target->mo == NULL || P_MobjWasRemoved(target->mo) - || player == target || target->spectator - || target->powers[pw_flashing] - || !P_CheckSight(player->mo, target->mo)) - { - continue; - } - - dist = P_AproxDistance(P_AproxDistance( - player->mo->x - target->mo->x, - player->mo->y - target->mo->y), - (player->mo->z - target->mo->z) / 4 - ); - - if (dist <= (player->mo->radius * 16)) - { - angle_t a = player->mo->angle - R_PointToAngle2(player->mo->x, player->mo->y, target->mo->x, target->mo->y); - INT16 ad = 0; - const INT16 cone = 10; - - if (a < ANGLE_180) - { - ad = AngleFixed(a)>>FRACBITS; - } - else - { - ad = 360-(AngleFixed(a)>>FRACBITS); - } - - ad = abs(ad); - - if (ad >= 180-cone) - { - player->botvars.itemconfirm += player->botvars.difficulty; - throwdir = -1; - } - } - } - - if (stealth > 1) - { - player->botvars.itemconfirm += player->botvars.difficulty * 4; - throwdir = -1; - } - - if (player->kartstuff[k_itemroulette] > 0) // Just grabbed an item - { - player->botvars.itemconfirm += player->botvars.difficulty * 4; - throwdir = -1; - } - - if ((player->botvars.itemconfirm > 2*TICRATE || player->kartstuff[k_bananadrag] >= TICRATE) - && !(player->pflags & PF_ATTACKDOWN)) - { - if (throwdir == 1) - { - cmd->buttons |= BT_FORWARD; - } - else if (throwdir == -1) - { - cmd->buttons |= BT_BACKWARD; - } - - cmd->buttons |= BT_ATTACK; - player->botvars.itemconfirm = 0; - } - } - else if (player->kartstuff[k_rocketsneakertimer] > 0) - { - if (player->botvars.itemconfirm > TICRATE) - { - if (!player->kartstuff[k_sneakertimer] && !(player->pflags & PF_ATTACKDOWN)) - { - cmd->buttons |= BT_ATTACK; - player->botvars.itemconfirm = 0; - } - } - else - { - player->botvars.itemconfirm++; - } - } - else if (player->kartstuff[k_stealingtimer] == 0 && player->kartstuff[k_stolentimer] == 0) - { - switch (player->kartstuff[k_itemtype]) - { - case KITEM_INVINCIBILITY: - case KITEM_SPB: - case KITEM_GROW: - case KITEM_SHRINK: - case KITEM_HYUDORO: - case KITEM_SUPERRING: - if (!(player->pflags & PF_ATTACKDOWN)) - { - cmd->buttons |= BT_ATTACK; - player->botvars.itemconfirm = 0; - } - break; - case KITEM_SNEAKER: - if ((player->kartstuff[k_offroad] && K_ApplyOffroad(player)) // Stuck in offroad, use it NOW - || K_GetWaypointIsShortcut(player->nextwaypoint) == true // Going toward a shortcut! - || player->speed < K_GetKartSpeed(player, false)/2 // Being slowed down too much - || player->kartstuff[k_speedboost] > (FRACUNIT/8) // Have another type of boost (tethering) - || player->botvars.itemconfirm > 4*TICRATE) // Held onto it for too long - { - if (!player->kartstuff[k_sneakertimer] && !(player->pflags & PF_ATTACKDOWN)) - { - cmd->buttons |= BT_ATTACK; - player->botvars.itemconfirm = 2*TICRATE; - } - } - else - { - player->botvars.itemconfirm++; - } - break; - case KITEM_ROCKETSNEAKER: - if (player->kartstuff[k_rocketsneakertimer] <= 0 && !(player->pflags & PF_ATTACKDOWN)) - { - cmd->buttons |= BT_ATTACK; - player->botvars.itemconfirm = 0; - } - break; - case KITEM_BANANA: - if (!player->kartstuff[k_itemheld]) - { - if ((K_BotRevealsBanana(player, turnamt, false) || (player->botvars.itemconfirm++ > 5*TICRATE)) - && !(player->pflags & PF_ATTACKDOWN)) - { - cmd->buttons |= BT_ATTACK; - player->botvars.itemconfirm = 0; - } - } - else - { - SINT8 throwdir = -1; - UINT8 i; - - player->botvars.itemconfirm++; - - if (abs(turnamt) >= KART_FULLTURN/2) - { - player->botvars.itemconfirm += player->botvars.difficulty / 2; - } - else - { - const UINT32 airtime = FixedDiv((30 * player->mo->scale) + player->mo->momz, gravity); - const fixed_t throwspeed = FixedMul(82 * mapobjectscale, K_GetKartGameSpeedScalar(gamespeed)); - const angle_t momangle = R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy); - const fixed_t estx = player->mo->x + P_ReturnThrustX(NULL, momangle, (throwspeed + player->speed) * airtime); - const fixed_t esty = player->mo->y + P_ReturnThrustY(NULL, momangle, (throwspeed + player->speed) * airtime); - - if (K_PlayerNearSpot(player, estx, esty, player->mo->radius * 2)) - { - player->botvars.itemconfirm += player->botvars.difficulty * 2; - throwdir = 1; - } - } - - for (i = 0; i < MAXPLAYERS; i++) - { - player_t *target = NULL; - fixed_t dist = INT32_MAX; - - if (!playeringame[i]) - { - continue; - } - - target = &players[i]; - - if (target->mo == NULL || P_MobjWasRemoved(target->mo) - || player == target || target->spectator - || target->powers[pw_flashing] - || !P_CheckSight(player->mo, target->mo)) - { - continue; - } - - dist = P_AproxDistance(P_AproxDistance( - player->mo->x - target->mo->x, - player->mo->y - target->mo->y), - (player->mo->z - target->mo->z) / 4 - ); - - if (dist <= (player->mo->radius * 16)) - { - angle_t a = player->mo->angle - R_PointToAngle2(player->mo->x, player->mo->y, target->mo->x, target->mo->y); - INT16 ad = 0; - const INT16 cone = 10; - - if (a < ANGLE_180) - { - ad = AngleFixed(a)>>FRACBITS; - } - else - { - ad = 360-(AngleFixed(a)>>FRACBITS); - } - - ad = abs(ad); - - if (ad >= 180-cone) - { - player->botvars.itemconfirm += player->botvars.difficulty; - throwdir = -1; - } - } - } - - if ((player->botvars.itemconfirm > 2*TICRATE || player->kartstuff[k_bananadrag] >= TICRATE) - && !(player->pflags & PF_ATTACKDOWN)) - { - if (throwdir == 1) - { - cmd->buttons |= BT_FORWARD; - } - else if (throwdir == -1) - { - cmd->buttons |= BT_BACKWARD; - } - - cmd->buttons |= BT_ATTACK; - player->botvars.itemconfirm = 0; - } - } - break; - case KITEM_EGGMAN: - if (!player->kartstuff[k_eggmanheld]) - { - if ((K_BotRevealsEggbox(player) || (player->botvars.itemconfirm++ > 20*TICRATE)) - && !(player->pflags & PF_ATTACKDOWN)) - { - cmd->buttons |= BT_ATTACK; - player->botvars.itemconfirm = 0; - } - } - break; - case KITEM_ORBINAUT: - if (!player->kartstuff[k_itemheld] && !(player->pflags & PF_ATTACKDOWN)) - { - cmd->buttons |= BT_ATTACK; - player->botvars.itemconfirm = 0; - } - else if (player->kartstuff[k_position] != 1) // Hold onto orbiting items when in 1st :) - /* FALL-THRU */ - case KITEM_BALLHOG: - { - const fixed_t topspeed = K_GetKartSpeed(player, false); - fixed_t radius = (player->mo->radius * 32); - SINT8 throwdir = -1; - UINT8 i; - - if (player->speed > topspeed) - { - radius = FixedMul(radius, FixedDiv(player->speed, topspeed)); - } - - for (i = 0; i < MAXPLAYERS; i++) - { - player_t *target = NULL; - fixed_t dist = INT32_MAX; - - if (!playeringame[i]) - { - continue; - } - - target = &players[i]; - - if (target->mo == NULL || P_MobjWasRemoved(target->mo) - || player == target || target->spectator - || target->powers[pw_flashing] - || !P_CheckSight(player->mo, target->mo)) - { - continue; - } - - dist = P_AproxDistance(P_AproxDistance( - player->mo->x - target->mo->x, - player->mo->y - target->mo->y), - (player->mo->z - target->mo->z) / 4 - ); - - if (dist <= radius) - { - angle_t a = player->mo->angle - R_PointToAngle2(player->mo->x, player->mo->y, target->mo->x, target->mo->y); - INT16 ad = 0; - const INT16 cone = 10; - - if (a < ANGLE_180) - { - ad = AngleFixed(a)>>FRACBITS; - } - else - { - ad = 360-(AngleFixed(a)>>FRACBITS); - } - - ad = abs(ad); - - if (ad <= cone) - { - player->botvars.itemconfirm += player->botvars.difficulty * 2; - throwdir = 1; - } - else if (ad >= 180-cone) - { - player->botvars.itemconfirm += player->botvars.difficulty; - } - } - } - - if ((player->botvars.itemconfirm > 5*TICRATE) - && !(player->pflags & PF_ATTACKDOWN)) - { - if (throwdir == 1) - { - cmd->buttons |= BT_FORWARD; - } - else if (throwdir == -1) - { - cmd->buttons |= BT_BACKWARD; - } - - cmd->buttons |= BT_ATTACK; - player->botvars.itemconfirm = 0; - } - } - break; - case KITEM_JAWZ: - if (!player->kartstuff[k_itemheld] && !(player->pflags & PF_ATTACKDOWN)) - { - cmd->buttons |= BT_ATTACK; - player->botvars.itemconfirm = 0; - } - else if (player->kartstuff[k_position] != 1) // Hold onto orbiting items when in 1st :) - { - SINT8 throwdir = 1; - UINT8 i; - - player->botvars.itemconfirm++; - - for (i = 0; i < MAXPLAYERS; i++) - { - player_t *target = NULL; - fixed_t dist = INT32_MAX; - - if (!playeringame[i]) - { - continue; - } - - target = &players[i]; - - if (target->mo == NULL || P_MobjWasRemoved(target->mo) - || player == target || target->spectator - || target->powers[pw_flashing] - || !P_CheckSight(player->mo, target->mo)) - { - continue; - } - - dist = P_AproxDistance(P_AproxDistance( - player->mo->x - target->mo->x, - player->mo->y - target->mo->y), - (player->mo->z - target->mo->z) / 4 - ); - - if (dist <= (player->mo->radius * 32)) - { - angle_t a = player->mo->angle - R_PointToAngle2(player->mo->x, player->mo->y, target->mo->x, target->mo->y); - INT16 ad = 0; - const INT16 cone = 10; - - if (a < ANGLE_180) - { - ad = AngleFixed(a)>>FRACBITS; - } - else - { - ad = 360-(AngleFixed(a)>>FRACBITS); - } - - ad = abs(ad); - - if (ad >= 180-cone) - { - player->botvars.itemconfirm += player->botvars.difficulty; - throwdir = -1; - } - } - } - - if (player->kartstuff[k_lastjawztarget] != -1) - { - player->botvars.itemconfirm += player->botvars.difficulty * 2; - throwdir = 1; - } - - if ((player->botvars.itemconfirm > 5*TICRATE) - && !(player->pflags & PF_ATTACKDOWN)) - { - if (throwdir == 1) - { - cmd->buttons |= BT_FORWARD; - } - else if (throwdir == -1) - { - cmd->buttons |= BT_BACKWARD; - } - - cmd->buttons |= BT_ATTACK; - player->botvars.itemconfirm = 0; - } - } - break; - case KITEM_MINE: - if (!player->kartstuff[k_itemheld]) - { - if ((K_BotRevealsBanana(player, turnamt, true) || (player->botvars.itemconfirm++ > 5*TICRATE)) - && !(player->pflags & PF_ATTACKDOWN)) - { - cmd->buttons |= BT_ATTACK; - player->botvars.itemconfirm = 0; - } - } - else - { - SINT8 throwdir = 0; - UINT8 i; - - player->botvars.itemconfirm++; - - for (i = 0; i < MAXPLAYERS; i++) - { - player_t *target = NULL; - fixed_t dist = INT32_MAX; - - if (!playeringame[i]) - { - continue; - } - - target = &players[i]; - - if (target->mo == NULL || P_MobjWasRemoved(target->mo) - || player == target || target->spectator - || target->powers[pw_flashing] - || !P_CheckSight(player->mo, target->mo)) - { - continue; - } - - dist = P_AproxDistance(P_AproxDistance( - player->mo->x - target->mo->x, - player->mo->y - target->mo->y), - (player->mo->z - target->mo->z) / 4 - ); - - if (dist <= (player->mo->radius * 16)) - { - angle_t a = player->mo->angle - R_PointToAngle2(player->mo->x, player->mo->y, target->mo->x, target->mo->y); - INT16 ad = 0; - const INT16 cone = 10; - - if (a < ANGLE_180) - { - ad = AngleFixed(a)>>FRACBITS; - } - else - { - ad = 360-(AngleFixed(a)>>FRACBITS); - } - - ad = abs(ad); - - if (ad >= 180-cone) - { - player->botvars.itemconfirm += player->botvars.difficulty; - throwdir = -1; - } - } - } - - if (abs(turnamt) >= KART_FULLTURN/2) - { - player->botvars.itemconfirm += player->botvars.difficulty / 2; - throwdir = -1; - } - else - { - UINT32 airtime = FixedDiv((30 * player->mo->scale) + player->mo->momz, gravity); - fixed_t throwspeed = FixedMul(82 * mapobjectscale, K_GetKartGameSpeedScalar(gamespeed)); - angle_t momangle = R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy); - fixed_t estx = player->mo->x + P_ReturnThrustX(NULL, momangle, (throwspeed + player->speed) * airtime); - fixed_t esty = player->mo->y + P_ReturnThrustY(NULL, momangle, (throwspeed + player->speed) * airtime); - - if (K_PlayerNearSpot(player, estx, esty, player->mo->radius * 2)) - { - player->botvars.itemconfirm += player->botvars.difficulty * 2; - throwdir = 0; - } - - airtime = FixedDiv((40 * player->mo->scale) + player->mo->momz, gravity); - throwspeed = FixedMul(82 * mapobjectscale, K_GetKartGameSpeedScalar(gamespeed)) * 2; - estx = player->mo->x + P_ReturnThrustX(NULL, player->mo->angle, (throwspeed + player->speed) * airtime); - esty = player->mo->y + P_ReturnThrustY(NULL, player->mo->angle, (throwspeed + player->speed) * airtime); - - if (K_PlayerNearSpot(player, estx, esty, player->mo->radius * 2)) - { - player->botvars.itemconfirm += player->botvars.difficulty / 2; - throwdir = 1; - } - } - - if (((player->botvars.itemconfirm > 2*TICRATE) - || (player->kartstuff[k_bananadrag] >= TICRATE)) - && !(player->pflags & PF_ATTACKDOWN)) - { - if (throwdir == 1) - { - cmd->buttons |= BT_FORWARD; - } - else if (throwdir == -1) - { - cmd->buttons |= BT_BACKWARD; - } - - cmd->buttons |= BT_ATTACK; - player->botvars.itemconfirm = 0; - } - } - break; - case KITEM_THUNDERSHIELD: - if (!K_BotUseItemNearPlayer(player, cmd, 192*player->mo->scale)) - { - if (player->botvars.itemconfirm > 10*TICRATE && !(player->pflags & PF_ATTACKDOWN)) - { - cmd->buttons |= BT_ATTACK; - player->botvars.itemconfirm = 0; - } - else - { - player->botvars.itemconfirm++; - } - } - break; - case KITEM_BUBBLESHIELD: - { - boolean hold = false; - - if (player->kartstuff[k_bubbleblowup] <= 0) - { - UINT8 i; - - player->botvars.itemconfirm++; - - if (player->kartstuff[k_bubblecool] <= 0) - { - const fixed_t radius = 192 * player->mo->scale; - - for (i = 0; i < MAXPLAYERS; i++) - { - player_t *target = NULL; - fixed_t dist = INT32_MAX; - - if (!playeringame[i]) - { - continue; - } - - target = &players[i]; - - if (target->mo == NULL || P_MobjWasRemoved(target->mo) - || player == target || target->spectator - || target->powers[pw_flashing]) - { - continue; - } - - dist = P_AproxDistance(P_AproxDistance( - player->mo->x - target->mo->x, - player->mo->y - target->mo->y), - (player->mo->z - target->mo->z) / 4 - ); - - if (dist <= radius) - { - hold = true; - break; - } - } - } - } - else if (player->kartstuff[k_bubbleblowup] >= bubbletime) - { - if (player->botvars.itemconfirm >= 10*TICRATE) - { - hold = true; - } - } - else if (player->kartstuff[k_bubbleblowup] < bubbletime) - { - hold = true; - } - - if (hold && player->kartstuff[k_holdready]) - { - cmd->buttons |= BT_ATTACK; - } - } - break; - case KITEM_FLAMESHIELD: - if (player->botvars.itemconfirm > 0) - { - player->botvars.itemconfirm--; - } - else if (player->kartstuff[k_holdready]) - { - INT32 flamemax = player->kartstuff[k_flamelength] * flameseg; - - if (player->kartstuff[k_flamemeter] < flamemax || flamemax == 0) - { - cmd->buttons |= BT_ATTACK; - } - else - { - player->botvars.itemconfirm = 3*flamemax/4; - } - } - break; - default: - if (player->kartstuff[k_itemtype] != KITEM_NONE && !(player->pflags & PF_ATTACKDOWN)) - cmd->buttons |= BT_ATTACK; - player->botvars.itemconfirm = 0; - break; - } - } - } + // Handle item usage + K_BotItemUsage(player, cmd, turnamt); if (turnamt != 0) { @@ -2253,12 +741,13 @@ void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd) if (abs(player->botvars.turnconfirm) >= BOTTURNCONFIRM) { - // You're probably commiting to your turn, here you go. + // You're commiting to your turn, you're allowed! cmd->driftturn = turnamt; cmd->angleturn += K_GetKartTurnValue(player, turnamt); } } + // Free the prediction we made earlier if (predict != NULL) { Z_Free(predict); diff --git a/src/k_bot.h b/src/k_bot.h index 0525480b4..81257ecbf 100644 --- a/src/k_bot.h +++ b/src/k_bot.h @@ -1,14 +1,16 @@ -// SONIC ROBO BLAST 2 +// SONIC ROBO BLAST 2 KART //----------------------------------------------------------------------------- -// Copyright (C) 2007-2016 by John "JTE" Muniz. -// Copyright (C) 2012-2018 by Sonic Team Junior. +// Copyright (C) 2018-2020 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_bot.h -/// \brief Basic bot handling +/// \brief Bot logic & ticcmd generation code + +#ifndef __K_BOT__ +#define __K_BOT__ #include "k_waypoint.h" #include "d_player.h" @@ -20,16 +22,196 @@ // Made it as small as possible without making it look like the bots are twitching constantly. #define BOTTURNCONFIRM 7 -// Path that bot will attempt to take +// Point for bots to aim for typedef struct botprediction_s { fixed_t x, y; fixed_t radius; angle_t dir; } botprediction_t; -boolean K_AddBot(UINT8 skin, UINT8 difficulty, UINT8 *newplayernum); -void K_UpdateMatchRaceBots(void); + +// AVAILABLE FOR LUA + + +/*-------------------------------------------------- + boolean K_PlayerUsesBotMovement(player_t *player); + + Tells if this player is being controlled via bot movement code (is a bot, or is exiting). + + Input Arguments:- + player - Player to check. + + Return:- + true if using bot movement code, otherwise false. +--------------------------------------------------*/ + boolean K_PlayerUsesBotMovement(player_t *player); + + +/*-------------------------------------------------- + boolean K_BotCanTakeCut(player_t *player); + + Tells if this bot is able to take shortcuts (currently unaffected by offroad, + or has certain items ready). + + Input Arguments:- + player - Player to check. + + Return:- + true if able to take shortcuts, otherwise false. +--------------------------------------------------*/ + boolean K_BotCanTakeCut(player_t *player); + + +/*-------------------------------------------------- + fixed_t K_BotRubberband(player_t *player); + + Gives a multiplier for a bot's rubberbanding. Meant to be used for top speed, + acceleration, and handling. + + Input Arguments:- + player - Player to check. + + Return:- + A multiplier in fixed point scale, between 0.875 and 2.0. +--------------------------------------------------*/ + fixed_t K_BotRubberband(player_t *player); + + +/*-------------------------------------------------- + fixed_t K_DistanceOfLineFromPoint(fixed_t v1x, fixed_t v1y, fixed_t v2x, fixed_t v2y, fixed_t cx, fixed_t cy); + + Gets the distance of a point away from a line. + TODO: Could go in another file? + + Input Arguments:- + v1x - Line's first vertex x position. + v1y - Line's first vertex y position. + v2x - Line's second vertex x position. + v2y - Line's second vertex y position. + cx - Point's x position. + cy - Point's y position. + + Return:- + The distance between the point and the line. +--------------------------------------------------*/ + +fixed_t K_DistanceOfLineFromPoint(fixed_t v1x, fixed_t v1y, fixed_t v2x, fixed_t v2y, fixed_t cx, fixed_t cy); + + +// NOT AVAILABLE FOR LUA + + +/*-------------------------------------------------- + boolean K_AddBot(UINT8 skin, UINT8 difficulty, UINT8 *newplayernum); + + Returns the waypoint actually being used as the finish line. + + Input Arguments:- + skin - Skin number that the bot will use. + difficulty - Difficulty level this bot will use. + newplayernum - Pointer to the last valid player slot number. + Is a pointer so that this function can be called multiple times to add more than one bot. + + Return:- + true if a bot packet can be sent, otherwise false. +--------------------------------------------------*/ + +boolean K_AddBot(UINT8 skin, UINT8 difficulty, UINT8 *newplayernum); + + +/*-------------------------------------------------- + void K_UpdateMatchRaceBots(void); + + Updates the number of bots in the server and their difficulties for Match Race. +--------------------------------------------------*/ + +void K_UpdateMatchRaceBots(void); + + +/*-------------------------------------------------- + UINT8 K_EggboxStealth(fixed_t x, fixed_t y); + + Gets a "stealth" value for a position, to figure out how + well Eggman boxes blend into random items. + + Input Arguments:- + x - X coordinate to check. + y - Y coordinate to check. + + Return:- + Stealth value for the position. +--------------------------------------------------*/ + +UINT8 K_EggboxStealth(fixed_t x, fixed_t y); + + +/*-------------------------------------------------- + fixed_t K_BotReducePrediction(player_t *player); + + Finds walls nearby the specified player, to create a multiplier + to pull bot predictions back by. + + Input Arguments:- + player - Player to compare. + + Return:- + Multiplier in fixed point scale. +--------------------------------------------------*/ + +fixed_t K_BotReducePrediction(player_t *player); + + +/*-------------------------------------------------- + INT16 K_BotFindObjects(player_t *player, INT16 turn); + + Generates a sum for objects to steer towards/away from. + + Input Arguments:- + player - Player to compare. + turn - Turn value before object steering. + + Return:- + Turn amount sum to add to final product. +--------------------------------------------------*/ + +INT16 K_BotFindObjects(player_t *player, INT16 turn); + + +/*-------------------------------------------------- + void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd); + + Gives a multiplier for a bot's rubberbanding. Meant to be used for top speed, + acceleration, and handling. + + Input Arguments:- + player - Player to generate the ticcmd for. + cmd - The player's ticcmd to modify. + + Return:- + None +--------------------------------------------------*/ + void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd); + + +/*-------------------------------------------------- + void K_BotItemUsage(player_t *player, ticcmd_t *cmd, INT16 turnamt); + + Item usage part of ticcmd generation. + + Input Arguments:- + player - Player to generate the ticcmd for. + cmd - The player's ticcmd to modify. + turnamt - How hard the bot is turning. + + Return:- + None +--------------------------------------------------*/ + +void K_BotItemUsage(player_t *player, ticcmd_t *cmd, INT16 turnamt); + + +#endif diff --git a/src/k_botitem.c b/src/k_botitem.c new file mode 100644 index 000000000..55f8e15e4 --- /dev/null +++ b/src/k_botitem.c @@ -0,0 +1,1095 @@ +// SONIC ROBO BLAST 2 KART +//----------------------------------------------------------------------------- +// Copyright (C) 2018-2020 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_botitem.c +/// \brief Bot item usage logic + +#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 + +/*-------------------------------------------------- + static boolean K_BotUseItemNearPlayer(player_t *player, ticcmd_t *cmd, fixed_t radius) + + Looks for players around the bot, and presses the item button + if there is one in range. + + Input Arguments:- + player - Bot to compare against. + cmd - The bot's ticcmd. + radius - The radius to look for players in. + + Return:- + true if a player was found & we can press the item button, otherwise false. +--------------------------------------------------*/ +static boolean K_BotUseItemNearPlayer(player_t *player, ticcmd_t *cmd, fixed_t radius) +{ + UINT8 i; + + if (player->pflags & PF_ATTACKDOWN) + { + return false; + } + + for (i = 0; i < MAXPLAYERS; i++) + { + player_t *target = NULL; + fixed_t dist = INT32_MAX; + + if (!playeringame[i]) + { + continue; + } + + target = &players[i]; + + if (target->mo == NULL || P_MobjWasRemoved(target->mo) + || player == target || target->spectator + || target->powers[pw_flashing]) + { + continue; + } + + dist = P_AproxDistance(P_AproxDistance( + player->mo->x - target->mo->x, + player->mo->y - target->mo->y), + (player->mo->z - target->mo->z) / 4 + ); + + if (dist <= radius) + { + cmd->buttons |= BT_ATTACK; + return true; + } + } + + return false; +} + +/*-------------------------------------------------- + static boolean K_PlayerNearSpot(player_t *player, fixed_t x, fixed_t y, fixed_t radius) + + Looks for players around a specified x/y coordinate. + + Input Arguments:- + player - Bot to compare against. + x - X coordinate to look around. + y - Y coordinate to look around. + radius - The radius to look for players in. + + Return:- + true if a player was found around the coordinate, otherwise false. +--------------------------------------------------*/ +static boolean K_PlayerNearSpot(player_t *player, fixed_t x, fixed_t y, fixed_t radius) +{ + UINT8 i; + + for (i = 0; i < MAXPLAYERS; i++) + { + player_t *target = NULL; + fixed_t dist = INT32_MAX; + + if (!playeringame[i]) + { + continue; + } + + target = &players[i]; + + if (target->mo == NULL || P_MobjWasRemoved(target->mo) + || player == target || target->spectator + || target->powers[pw_flashing]) + { + continue; + } + + dist = P_AproxDistance( + x - target->mo->x, + y - target->mo->y + ); + + if (dist <= radius) + { + return true; + } + } + + return false; +} + +/*-------------------------------------------------- + static boolean K_PlayerPredictThrow(player_t *player, UINT8 extra) + + Looks for players around the predicted coordinates of their thrown item. + + Input Arguments:- + player - Bot to compare against. + extra - Extra throwing distance, for aim forward on mines. + + Return:- + true if a player was found around the coordinate, otherwise false. +--------------------------------------------------*/ +static boolean K_PlayerPredictThrow(player_t *player, UINT8 extra) +{ + const fixed_t dist = (30 + (extra * 10)) * player->mo->scale; + const UINT32 airtime = FixedDiv(dist + player->mo->momz, gravity); + const fixed_t throwspeed = FixedMul(82 * mapobjectscale, K_GetKartGameSpeedScalar(gamespeed)); + const fixed_t estx = player->mo->x + P_ReturnThrustX(NULL, player->mo->angle, (throwspeed + player->speed) * airtime); + const fixed_t esty = player->mo->y + P_ReturnThrustY(NULL, player->mo->angle, (throwspeed + player->speed) * airtime); + return K_PlayerNearSpot(player, estx, esty, player->mo->radius * 2); +} + +/*-------------------------------------------------- + static boolean K_PlayerInCone(player_t *player, UINT16 cone, boolean flip) + + Looks for players in the . + + Input Arguments:- + player - Bot to compare against. + radius - How far away the targets can be. + cone - Size of cone, in degrees as an integer. + flip - If true, look behind. Otherwise, check in front of the player. + + Return:- + true if a player was found in the cone, otherwise false. +--------------------------------------------------*/ +static boolean K_PlayerInCone(player_t *player, fixed_t radius, UINT16 cone, boolean flip) +{ + UINT8 i; + + for (i = 0; i < MAXPLAYERS; i++) + { + player_t *target = NULL; + fixed_t dist = INT32_MAX; + + if (!playeringame[i]) + { + continue; + } + + target = &players[i]; + + if (target->mo == NULL || P_MobjWasRemoved(target->mo) + || player == target || target->spectator + || target->powers[pw_flashing] + || !P_CheckSight(player->mo, target->mo)) + { + continue; + } + + dist = P_AproxDistance(P_AproxDistance( + player->mo->x - target->mo->x, + player->mo->y - target->mo->y), + (player->mo->z - target->mo->z) / 4 + ); + + if (dist <= radius) + { + angle_t a = player->mo->angle - R_PointToAngle2(player->mo->x, player->mo->y, target->mo->x, target->mo->y); + INT16 ad = 0; + + if (a < ANGLE_180) + { + ad = AngleFixed(a)>>FRACBITS; + } + else + { + ad = 360-(AngleFixed(a)>>FRACBITS); + } + + ad = abs(ad); + + if (flip) + { + if (ad >= 180-cone) + { + return true; + } + } + else + { + if (ad <= cone) + { + return true; + } + } + } + } + + return false; +} + +/*-------------------------------------------------- + static boolean K_BotGenericPressItem(player_t *player, ticcmd_t *cmd, SINT8 dir) + + Presses the item button & aim buttons for the bot. + + Input Arguments:- + player - Bot to do this for. + cmd - Bot's ticcmd to edit. + dir - Aiming direction: 1 for forwards, -1 for backwards, 0 for neutral. + + Return:- + true if we could press, false if not. +--------------------------------------------------*/ +static boolean K_BotGenericPressItem(player_t *player, ticcmd_t *cmd, SINT8 dir) +{ + if (player->pflags & PF_ATTACKDOWN) + { + return false; + } + + if (dir == 1) + { + cmd->buttons |= BT_FORWARD; + } + else if (dir == -1) + { + cmd->buttons |= BT_BACKWARD; + } + + cmd->buttons |= BT_ATTACK; + player->botvars.itemconfirm = 0; + return true; +} + +/*-------------------------------------------------- + static void K_BotItemGenericTap(player_t *player, ticcmd_t *cmd) + + Item usage for generic items that you need to tap. + + Input Arguments:- + player - Bot to do this for. + cmd - Bot's ticcmd to edit. + + Return:- + None +--------------------------------------------------*/ +static void K_BotItemGenericTap(player_t *player, ticcmd_t *cmd) +{ + if (!(player->pflags & PF_ATTACKDOWN)) + { + cmd->buttons |= BT_ATTACK; + player->botvars.itemconfirm = 0; + } +} + +/*-------------------------------------------------- + static boolean K_BotRevealsGenericTrap(player_t *player, INT16 turnamt, boolean mine) + + Decides if a bot is ready to reveal their trap item or not. + + Input Arguments:- + player - Bot that has the banana. + turnamt - How hard they currently are turning. + mine - Set to true to handle Mine-specific behaviors. + + Return:- + true if we want the bot to reveal their banana, otherwise false. +--------------------------------------------------*/ +static boolean K_BotRevealsGenericTrap(player_t *player, INT16 turnamt, boolean mine) +{ + if (abs(turnamt) >= KART_FULLTURN/2) + { + // DON'T reveal on turns, we can place bananas on turns whenever we have multiple to spare, + // or if you missed your intentioned throw/place on a player. + return false; + } + + // Check the predicted throws. + if (K_PlayerPredictThrow(player, 0)) + { + return true; + } + + if (mine) + { + if (K_PlayerPredictThrow(player, 1)) + { + return true; + } + } + + // Check your behind. + if (K_PlayerInCone(player, player->mo->radius * 16, 10, true)) + { + return true; + } + + return false; +} + +/*-------------------------------------------------- + static void K_BotItemGenericTrapShield(player_t *player, ticcmd_t *cmd, INT16 turnamt, boolean mine) + + Item usage for Eggman shields. + + Input Arguments:- + player - Bot to do this for. + cmd - Bot's ticcmd to edit. + turnamt - How hard they currently are turning. + mine - Set to true to handle Mine-specific behaviors. + + Return:- + None +--------------------------------------------------*/ +static void K_BotItemGenericTrapShield(player_t *player, ticcmd_t *cmd, INT16 turnamt, boolean mine) +{ + if (player->kartstuff[k_itemheld]) + { + return; + } + + if (K_BotRevealsGenericTrap(player, turnamt, mine) || (player->botvars.itemconfirm++ > 5*TICRATE)) + { + K_BotGenericPressItem(player, cmd, 0); + } +} + +/*-------------------------------------------------- + static void K_BotItemGenericOrbitShield(player_t *player, ticcmd_t *cmd) + + Item usage for orbitting shields. + + Input Arguments:- + player - Bot to do this for. + cmd - Bot's ticcmd to edit. + + Return:- + None +--------------------------------------------------*/ +static void K_BotItemGenericOrbitShield(player_t *player, ticcmd_t *cmd) +{ + if (player->kartstuff[k_itemheld]) + { + return; + } + + K_BotGenericPressItem(player, cmd, 0); +} + +/*-------------------------------------------------- + static void K_BotItemSneaker(player_t *player, ticcmd_t *cmd) + + Item usage for sneakers. + + Input Arguments:- + player - Bot to do this for. + cmd - Bot's ticcmd to edit. + + Return:- + None +--------------------------------------------------*/ +static void K_BotItemSneaker(player_t *player, ticcmd_t *cmd) +{ + if ((player->kartstuff[k_offroad] && K_ApplyOffroad(player)) // Stuck in offroad, use it NOW + || K_GetWaypointIsShortcut(player->nextwaypoint) == true // Going toward a shortcut! + || player->speed < K_GetKartSpeed(player, false)/2 // Being slowed down too much + || player->kartstuff[k_speedboost] > (FRACUNIT/8) // Have another type of boost (tethering) + || player->botvars.itemconfirm > 4*TICRATE) // Held onto it for too long + { + if (!player->kartstuff[k_sneakertimer] && !(player->pflags & PF_ATTACKDOWN)) + { + cmd->buttons |= BT_ATTACK; + player->botvars.itemconfirm = 2*TICRATE; + } + } + else + { + player->botvars.itemconfirm++; + } +} + +/*-------------------------------------------------- + static void K_BotItemRocketSneaker(player_t *player, ticcmd_t *cmd) + + Item usage for rocket sneakers. + + Input Arguments:- + player - Bot to do this for. + cmd - Bot's ticcmd to edit. + + Return:- + None +--------------------------------------------------*/ +static void K_BotItemRocketSneaker(player_t *player, ticcmd_t *cmd) +{ + if (player->botvars.itemconfirm > TICRATE) + { + if (!player->kartstuff[k_sneakertimer] && !(player->pflags & PF_ATTACKDOWN)) + { + cmd->buttons |= BT_ATTACK; + player->botvars.itemconfirm = 0; + } + } + else + { + player->botvars.itemconfirm++; + } +} + + +/*-------------------------------------------------- + static void K_BotItemBanana(player_t *player, ticcmd_t *cmd, INT16 turnamt) + + Item usage for trap item throwing. + + Input Arguments:- + player - Bot to do this for. + cmd - Bot's ticcmd to edit. + turnamt - How hard they currently are turning. + + Return:- + None +--------------------------------------------------*/ +static void K_BotItemBanana(player_t *player, ticcmd_t *cmd, INT16 turnamt) +{ + SINT8 throwdir = -1; + + player->botvars.itemconfirm++; + + if (abs(turnamt) >= KART_FULLTURN/2) + { + player->botvars.itemconfirm += player->botvars.difficulty / 2; + throwdir = -1; + } + else + { + if (K_PlayerPredictThrow(player, 0)) + { + player->botvars.itemconfirm += player->botvars.difficulty * 2; + throwdir = 1; + } + } + + if (K_PlayerInCone(player, player->mo->radius * 16, 10, true)) + { + player->botvars.itemconfirm += player->botvars.difficulty; + throwdir = -1; + } + + if (player->botvars.itemconfirm > 2*TICRATE || player->kartstuff[k_bananadrag] >= TICRATE) + { + K_BotGenericPressItem(player, cmd, throwdir); + } +} + +/*-------------------------------------------------- + static void K_BotItemMine(player_t *player, ticcmd_t *cmd, INT16 turnamt) + + Item usage for trap item throwing. + + Input Arguments:- + player - Bot to do this for. + cmd - Bot's ticcmd to edit. + turnamt - How hard they currently are turning. + + Return:- + None +--------------------------------------------------*/ +static void K_BotItemMine(player_t *player, ticcmd_t *cmd, INT16 turnamt) +{ + SINT8 throwdir = 0; + + player->botvars.itemconfirm++; + + if (K_PlayerInCone(player, player->mo->radius * 16, 10, true)) + { + player->botvars.itemconfirm += player->botvars.difficulty; + throwdir = -1; + } + + if (abs(turnamt) >= KART_FULLTURN/2) + { + player->botvars.itemconfirm += player->botvars.difficulty / 2; + throwdir = -1; + } + else + { + if (K_PlayerPredictThrow(player, 0)) + { + player->botvars.itemconfirm += player->botvars.difficulty * 2; + throwdir = 0; + } + + if (K_PlayerPredictThrow(player, 1)) + { + player->botvars.itemconfirm += player->botvars.difficulty * 2; + throwdir = 1; + } + } + + + + if (player->botvars.itemconfirm > 2*TICRATE || player->kartstuff[k_bananadrag] >= TICRATE) + { + K_BotGenericPressItem(player, cmd, throwdir); + } +} + +/*-------------------------------------------------- + static void K_BotItemEggman(player_t *player, ticcmd_t *cmd) + + Item usage for Eggman item throwing. + + Input Arguments:- + player - Bot to do this for. + cmd - Bot's ticcmd to edit. + + Return:- + None +--------------------------------------------------*/ +static void K_BotItemEggman(player_t *player, ticcmd_t *cmd) +{ + const UINT8 stealth = K_EggboxStealth(player->mo->x, player->mo->y); + SINT8 throwdir = -1; + + player->botvars.itemconfirm++; + + if (K_PlayerPredictThrow(player, 0)) + { + player->botvars.itemconfirm += player->botvars.difficulty / 2; + throwdir = 1; + } + + if (K_PlayerInCone(player, player->mo->radius * 16, 10, true)) + { + player->botvars.itemconfirm += player->botvars.difficulty; + throwdir = -1; + } + + if (stealth > 1 || player->kartstuff[k_itemroulette] > 0) + { + player->botvars.itemconfirm += player->botvars.difficulty * 4; + throwdir = -1; + } + + if (player->botvars.itemconfirm > 2*TICRATE || player->kartstuff[k_bananadrag] >= TICRATE) + { + K_BotGenericPressItem(player, cmd, throwdir); + } +} + +/*-------------------------------------------------- + static boolean K_BotRevealsEggbox(player_t *player) + + Decides if a bot is ready to place their Eggman item or not. + + Input Arguments:- + player - Bot that has the eggbox. + + Return:- + true if we want the bot to reveal their eggbox, otherwise false. +--------------------------------------------------*/ +static boolean K_BotRevealsEggbox(player_t *player) +{ + const UINT8 stealth = K_EggboxStealth(player->mo->x, player->mo->y); + + // This is a stealthy spot for an eggbox, lets reveal it! + if (stealth > 1) + { + return true; + } + + // Check the predicted throws. + if (K_PlayerPredictThrow(player, 0)) + { + return true; + } + + // Check your behind. + if (K_PlayerInCone(player, player->mo->radius * 16, 10, true)) + { + return true; + } + + return false; +} + +/*-------------------------------------------------- + static void K_BotItemEggmanShield(player_t *player, ticcmd_t *cmd) + + Item usage for Eggman shields. + + Input Arguments:- + player - Bot to do this for. + cmd - Bot's ticcmd to edit. + + Return:- + None +--------------------------------------------------*/ +static void K_BotItemEggmanShield(player_t *player, ticcmd_t *cmd) +{ + if (player->kartstuff[k_eggmanheld]) + { + return; + } + + if (K_BotRevealsEggbox(player) || (player->botvars.itemconfirm++ > 20*TICRATE)) + { + K_BotGenericPressItem(player, cmd, 0); + } +} + +/*-------------------------------------------------- + static void K_BotItemEggmanExplosion(player_t *player, ticcmd_t *cmd) + + Item usage for Eggman explosions. + + Input Arguments:- + player - Bot to do this for. + cmd - Bot's ticcmd to edit. + + Return:- + None +--------------------------------------------------*/ +static void K_BotItemEggmanExplosion(player_t *player, ticcmd_t *cmd) +{ + if (player->kartstuff[k_position] == 1) + { + cmd->forwardmove /= 2; + cmd->buttons |= BT_BRAKE; + } + + K_BotUseItemNearPlayer(player, cmd, 128*player->mo->scale); +} + +/*-------------------------------------------------- + static void K_BotItemOrbinaut(player_t *player, ticcmd_t *cmd) + + Item usage for Orbinaut throwing. + + Input Arguments:- + player - Bot to do this for. + cmd - Bot's ticcmd to edit. + + Return:- + None +--------------------------------------------------*/ +static void K_BotItemOrbinaut(player_t *player, ticcmd_t *cmd) +{ + const fixed_t topspeed = K_GetKartSpeed(player, false); + fixed_t radius = (player->mo->radius * 32); + SINT8 throwdir = -1; + + if (player->speed > topspeed) + { + radius = FixedMul(radius, FixedDiv(player->speed, topspeed)); + } + + player->botvars.itemconfirm++; + + if (K_PlayerInCone(player, radius, 10, false)) + { + player->botvars.itemconfirm += player->botvars.difficulty * 2; + throwdir = 1; + } + else if (K_PlayerInCone(player, radius, 10, true)) + { + player->botvars.itemconfirm += player->botvars.difficulty; + throwdir = -1; + } + + if (player->botvars.itemconfirm > 5*TICRATE) + { + K_BotGenericPressItem(player, cmd, throwdir); + } +} + +/*-------------------------------------------------- + static void K_BotItemJawz(player_t *player, ticcmd_t *cmd) + + Item usage for Jawz throwing. + + Input Arguments:- + player - Bot to do this for. + cmd - Bot's ticcmd to edit. + + Return:- + None +--------------------------------------------------*/ +static void K_BotItemJawz(player_t *player, ticcmd_t *cmd) +{ + const fixed_t topspeed = K_GetKartSpeed(player, false); + fixed_t radius = (player->mo->radius * 32); + SINT8 throwdir = 1; + + if (player->speed > topspeed) + { + radius = FixedMul(radius, FixedDiv(player->speed, topspeed)); + } + + player->botvars.itemconfirm++; + + if (K_PlayerInCone(player, radius, 10, true)) + { + player->botvars.itemconfirm += player->botvars.difficulty; + throwdir = -1; + } + + if (player->kartstuff[k_lastjawztarget] != -1) + { + player->botvars.itemconfirm += player->botvars.difficulty * 2; + throwdir = 1; + } + + if (player->botvars.itemconfirm > 5*TICRATE) + { + K_BotGenericPressItem(player, cmd, throwdir); + } +} + +/*-------------------------------------------------- + static void K_BotItemThunder(player_t *player, ticcmd_t *cmd) + + Item usage for Thunder Shield. + + Input Arguments:- + player - Bot to do this for. + cmd - Bot's ticcmd to edit. + + Return:- + None +--------------------------------------------------*/ +static void K_BotItemThunder(player_t *player, ticcmd_t *cmd) +{ + if (!K_BotUseItemNearPlayer(player, cmd, 192*player->mo->scale)) + { + if (player->botvars.itemconfirm > 10*TICRATE) + { + K_BotGenericPressItem(player, cmd, 0); + } + else + { + player->botvars.itemconfirm++; + } + } +} + +/*-------------------------------------------------- + static void K_BotItemBubble(player_t *player, ticcmd_t *cmd) + + Item usage for Bubble Shield. + + Input Arguments:- + player - Bot to do this for. + cmd - Bot's ticcmd to edit. + + Return:- + None +--------------------------------------------------*/ +static void K_BotItemBubble(player_t *player, ticcmd_t *cmd) +{ + boolean hold = false; + + if (player->kartstuff[k_bubbleblowup] <= 0) + { + UINT8 i; + + player->botvars.itemconfirm++; + + if (player->kartstuff[k_bubblecool] <= 0) + { + const fixed_t radius = 192 * player->mo->scale; + + for (i = 0; i < MAXPLAYERS; i++) + { + player_t *target = NULL; + fixed_t dist = INT32_MAX; + + if (!playeringame[i]) + { + continue; + } + + target = &players[i]; + + if (target->mo == NULL || P_MobjWasRemoved(target->mo) + || player == target || target->spectator + || target->powers[pw_flashing]) + { + continue; + } + + dist = P_AproxDistance(P_AproxDistance( + player->mo->x - target->mo->x, + player->mo->y - target->mo->y), + (player->mo->z - target->mo->z) / 4 + ); + + if (dist <= radius) + { + hold = true; + break; + } + } + } + } + else if (player->kartstuff[k_bubbleblowup] >= bubbletime) + { + if (player->botvars.itemconfirm >= 10*TICRATE) + { + hold = true; + } + } + else if (player->kartstuff[k_bubbleblowup] < bubbletime) + { + hold = true; + } + + if (hold && player->kartstuff[k_holdready]) + { + cmd->buttons |= BT_ATTACK; + } +} + +/*-------------------------------------------------- + static void K_BotItemFlame(player_t *player, ticcmd_t *cmd) + + Item usage for Flame Shield. + + Input Arguments:- + player - Bot to do this for. + cmd - Bot's ticcmd to edit. + + Return:- + None +--------------------------------------------------*/ +static void K_BotItemFlame(player_t *player, ticcmd_t *cmd) +{ + if (player->botvars.itemconfirm > 0) + { + player->botvars.itemconfirm--; + } + else if (player->kartstuff[k_holdready]) + { + INT32 flamemax = player->kartstuff[k_flamelength] * flameseg; + + if (player->kartstuff[k_flamemeter] < flamemax || flamemax == 0) + { + cmd->buttons |= BT_ATTACK; + } + else + { + player->botvars.itemconfirm = 3*flamemax/4; + } + } +} + +/*-------------------------------------------------- + static void K_BotItemRings(player_t *player, ticcmd_t *cmd) + + Item usage for rings. + + Input Arguments:- + player - Bot to do this for. + cmd - Bot's ticcmd to edit. + + Return:- + None +--------------------------------------------------*/ +static void K_BotItemRings(player_t *player, ticcmd_t *cmd) +{ + INT32 saferingsval = 16 - K_GetKartRingPower(player); + + if (player->speed < K_GetKartSpeed(player, false)/2 // Being slowed down too much + || player->kartstuff[k_speedboost] > (FRACUNIT/5)) // Have another type of boost (tethering) + { + saferingsval -= 5; + } + + if (player->kartstuff[k_rings] > saferingsval) + { + cmd->buttons |= BT_ATTACK; + } +} + +/*-------------------------------------------------- + static void K_BotItemRouletteMash(player_t *player, ticcmd_t *cmd) + + Item usage for item roulette mashing. + + Input Arguments:- + player - Bot to do this for. + cmd - Bot's ticcmd to edit. + + Return:- + None +--------------------------------------------------*/ +static void K_BotItemRouletteMash(player_t *player, ticcmd_t *cmd) +{ + boolean mash = false; + + if (player->pflags & PF_ATTACKDOWN) + { + return; + } + + if (player->kartstuff[k_rings] < 0 && cv_superring.value) + { + // Uh oh, we need a loan! + // It'll be better in the long run for bots to lose an item set for 10 free rings. + mash = true; + } + + // TODO: Mash based on how far behind you are, when items are + // almost garantueed to be in your favor. + + if (mash == true) + { + cmd->buttons |= BT_ATTACK; + } +} + +/*-------------------------------------------------- + void K_BotItemUsage(player_t *player, ticcmd_t *cmd, INT16 turnamt) + + See header file for description. +--------------------------------------------------*/ +void K_BotItemUsage(player_t *player, ticcmd_t *cmd, INT16 turnamt) +{ + if (player->kartstuff[k_userings] == 1) + { + // Use rings! + + if (!player->exiting) + { + K_BotItemRings(player, cmd); + } + } + else + { + if (player->botvars.itemdelay) + { + player->botvars.itemdelay--; + player->botvars.itemconfirm = 0; + return; + } + + if (player->kartstuff[k_itemroulette]) + { + // Mashing behaviors + K_BotItemRouletteMash(player, cmd); + return; + } + + if (player->kartstuff[k_stealingtimer] == 0 && player->kartstuff[k_stolentimer] == 0) + { + if (player->kartstuff[k_eggmanexplode]) + { + K_BotItemEggmanExplosion(player, cmd); + } + else if (player->kartstuff[k_eggmanheld]) + { + K_BotItemEggman(player, cmd); + } + else if (player->kartstuff[k_rocketsneakertimer] > 0) + { + K_BotItemRocketSneaker(player, cmd); + } + else + { + switch (player->kartstuff[k_itemtype]) + { + default: + if (player->kartstuff[k_itemtype] != KITEM_NONE) + { + K_BotItemGenericTap(player, cmd); + } + + player->botvars.itemconfirm = 0; + break; + case KITEM_INVINCIBILITY: + case KITEM_SPB: + case KITEM_GROW: + case KITEM_SHRINK: + case KITEM_HYUDORO: + case KITEM_SUPERRING: + K_BotItemGenericTap(player, cmd); + break; + case KITEM_ROCKETSNEAKER: + if (player->kartstuff[k_rocketsneakertimer] <= 0) + { + K_BotItemGenericTap(player, cmd); + } + break; + case KITEM_SNEAKER: + K_BotItemSneaker(player, cmd); + break; + case KITEM_BANANA: + if (!player->kartstuff[k_itemheld]) + { + K_BotItemGenericTrapShield(player, cmd, turnamt, false); + } + else + { + K_BotItemBanana(player, cmd, turnamt); + } + break; + case KITEM_EGGMAN: + K_BotItemEggmanShield(player, cmd); + break; + case KITEM_ORBINAUT: + if (!player->kartstuff[k_itemheld]) + { + K_BotItemGenericOrbitShield(player, cmd); + } + else if (player->kartstuff[k_position] != 1) // Hold onto orbiting items when in 1st :) + /* FALL-THRU */ + case KITEM_BALLHOG: + { + K_BotItemOrbinaut(player, cmd); + } + break; + case KITEM_JAWZ: + if (!player->kartstuff[k_itemheld]) + { + K_BotItemGenericOrbitShield(player, cmd); + } + else if (player->kartstuff[k_position] != 1) // Hold onto orbiting items when in 1st :) + { + K_BotItemJawz(player, cmd); + } + break; + case KITEM_MINE: + if (!player->kartstuff[k_itemheld]) + { + K_BotItemGenericTrapShield(player, cmd, turnamt, true); + } + else + { + K_BotItemMine(player, cmd, turnamt); + } + break; + case KITEM_THUNDERSHIELD: + K_BotItemThunder(player, cmd); + break; + case KITEM_BUBBLESHIELD: + K_BotItemBubble(player, cmd); + break; + case KITEM_FLAMESHIELD: + K_BotItemFlame(player, cmd); + break; + } + } + } + } +} diff --git a/src/k_botsearch.c b/src/k_botsearch.c new file mode 100644 index 000000000..28bef819e --- /dev/null +++ b/src/k_botsearch.c @@ -0,0 +1,749 @@ +// SONIC ROBO BLAST 2 KART +//----------------------------------------------------------------------------- +// Copyright (C) 2018-2020 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.c +/// \brief Bot blockmap search functions + +#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 + +struct globalsmuggle +{ + mobj_t *botmo; + fixed_t distancetocheck; + + fixed_t closestlinedist; + + INT16 curturn; + INT16 steer; + + fixed_t eggboxx, eggboxy; + UINT8 randomitems; + UINT8 eggboxes; +} globalsmuggle; + +/*-------------------------------------------------- + static boolean 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:- + true continues searching, false ends the search early. +--------------------------------------------------*/ +static boolean K_FindEggboxes(mobj_t *thing) +{ + fixed_t dist; + + if (thing->type != MT_RANDOMITEM && thing->type != MT_EGGMANITEM) + { + return true; + } + + if (!thing->health) + { + return true; + } + + dist = P_AproxDistance(thing->x - globalsmuggle.eggboxx, thing->y - globalsmuggle.eggboxy); + + if (dist > globalsmuggle.distancetocheck) + { + return true; + } + + if (thing->type == MT_RANDOMITEM) + { + globalsmuggle.randomitems++; + } + else + { + globalsmuggle.eggboxes++; + } + + return true; +} + +/*-------------------------------------------------- + UINT8 K_EggboxStealth(fixed_t x, fixed_t y) + + See header file for description. +--------------------------------------------------*/ +UINT8 K_EggboxStealth(fixed_t x, fixed_t y) +{ + INT32 xl, xh, yl, yh, bx, by; + + memset(&globalsmuggle, 0, sizeof (struct globalsmuggle)); + globalsmuggle.eggboxx = x; + globalsmuggle.eggboxy = y; + globalsmuggle.distancetocheck = (mapobjectscale * 256); + globalsmuggle.randomitems = 0; + globalsmuggle.eggboxes = 0; + + xl = (unsigned)(globalsmuggle.eggboxx - globalsmuggle.distancetocheck - bmaporgx)>>MAPBLOCKSHIFT; + xh = (unsigned)(globalsmuggle.eggboxx + globalsmuggle.distancetocheck - bmaporgx)>>MAPBLOCKSHIFT; + yl = (unsigned)(globalsmuggle.eggboxy - globalsmuggle.distancetocheck - bmaporgy)>>MAPBLOCKSHIFT; + yh = (unsigned)(globalsmuggle.eggboxy + globalsmuggle.distancetocheck - bmaporgy)>>MAPBLOCKSHIFT; + + BMBOUNDFIX(xl, xh, yl, yh); + + for (bx = xl; bx <= xh; bx++) + { + for (by = yl; by <= yh; by++) + { + P_BlockThingsIterator(bx, by, K_FindEggboxes); + } + } + + return (globalsmuggle.randomitems * globalsmuggle.eggboxes); +} + +/*-------------------------------------------------- + static boolean K_BotHatesThisSectorsSpecial(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(player_t *player, sector_t *sec) +{ + switch (GETSECSPECIAL(sec->special, 1)) + { + case 1: // Damage + case 5: // Spikes + case 6: case 7: // Death Pit + case 8: // Instant Kill + return true; + //case 2: case 3: // Offroad (let's let them lawnmower) + case 4: // Offroad (Strong) + if (!K_BotCanTakeCut(player)) + { + return true; + } + break; + default: + break; + } + + return false; +} + +/*-------------------------------------------------- + static boolean K_BotHatesThisSector(player_t *player, sector_t *sec) + + Tells us if a bot will play more careful around + this sector. + + Input Arguments:- + player - Player to check against. + sec - Sector to check against. + + Return:- + true if avoiding this sector, false otherwise. +--------------------------------------------------*/ +static boolean K_BotHatesThisSector(player_t *player, sector_t *sec) +{ + const boolean flip = (player->mo->eflags & MFE_VERTICALFLIP); + INT32 flag; + ffloor_t *rover; + + if (flip) + { + flag = SF_FLIPSPECIAL_CEILING; + } + else + { + flag = SF_FLIPSPECIAL_FLOOR; + } + + if (sec->flags & flag) + { + if (K_BotHatesThisSectorsSpecial(player, sec)) + { + return true; + } + } + + for (rover = sec->ffloors; rover; rover = rover->next) + { + if (!(rover->flags & FF_EXISTS)) + { + continue; + } + + if (!(rover->master->frontsector->flags & flag)) + { + continue; + } + + if (((*rover->bottomheight >= player->mo->z + player->mo->height) && (flip)) + || ((*rover->topheight <= player->mo->z) && (!flip))) + { + if (K_BotHatesThisSectorsSpecial(player, sec)) + { + return true; + } + } + } + + return false; +} + +/*-------------------------------------------------- + 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; + + 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->kartstuff[k_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; + } + + if (!K_BotHatesThisSector(globalsmuggle.botmo->player, globalsmuggle.botmo->subsector->sector)) + { + // Treat damage sectors like walls + + if (lineside) + { + if (K_BotHatesThisSector(globalsmuggle.botmo->player, line->frontsector)) + goto blocked; + } + else + { + if (K_BotHatesThisSector(globalsmuggle.botmo->player, line->backsector)) + 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; + + memset(&globalsmuggle, 0, sizeof (struct globalsmuggle)); + globalsmuggle.botmo = player->mo; + globalsmuggle.distancetocheck = (player->mo->radius * 8) + (player->speed * 4); + 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 FixedDiv(globalsmuggle.closestlinedist, globalsmuggle.distancetocheck); +} + +/*-------------------------------------------------- + static void K_SteerFromObject(mobj_t *bot, mobj_t *thing, fixed_t fulldist, fixed_t xdist, boolean towards, INT16 amount) + + Handles steering away/towards the specified object. + + Input Arguments:- + bot - Bot's mobj. + thing - Mobj to steer towards/away from. + fulldist - Distance away from object. + xdist - Horizontal distance away from object. + towards - If true, steer towards the object. Otherwise, steer away. + amount - How hard to turn. + + Return:- + None +--------------------------------------------------*/ +static void K_SteerFromObject(mobj_t *bot, mobj_t *thing, fixed_t fulldist, fixed_t xdist, boolean towards, INT16 amount) +{ + angle_t destangle = R_PointToAngle2(bot->x, bot->y, thing->x, thing->y); + angle_t angle; + SINT8 flip = 1; + + amount = (amount * FixedDiv(globalsmuggle.distancetocheck - fulldist, globalsmuggle.distancetocheck)) / FRACUNIT; + + if (amount == 0) + { + // Shouldn't happen + return; + } + + if (towards) + { + if (xdist < FixedHypot(bot->radius, thing->radius)) + { + // Don't need to turn any harder! + + if (abs(globalsmuggle.steer) <= amount) + { + globalsmuggle.steer = 0; + } + else + { + if (globalsmuggle.steer > 0) + { + globalsmuggle.steer -= amount; + } + else if (globalsmuggle.steer < 0) + { + globalsmuggle.steer += amount; + } + } + + return; + } + + // Still turning towards it, flip. + flip = -flip; + } + + angle = (bot->angle - destangle); + if (angle < ANGLE_180) + { + flip = -flip; + } + + // If going in the opposite direction of where you wanted to turn, + // then reduce the amount that you can turn in that direction. + if ((flip == 1 && globalsmuggle.curturn < 0) + || (flip == -1 && globalsmuggle.curturn > 0)) + { + amount /= 4; + } + + globalsmuggle.steer += amount * flip; +} + +/*-------------------------------------------------- + static boolean K_BotSteerObjects(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:- + true continues searching, false ends the search early. +--------------------------------------------------*/ +static boolean K_BotSteerObjects(mobj_t *thing) +{ + INT16 anglediff; + fixed_t xdist, ydist, fulldist; + angle_t destangle, angle; + INT16 attack = ((9 - globalsmuggle.botmo->player->kartspeed) * KART_FULLTURN) / 8; // Acceleration chars are more aggressive + INT16 dodge = ((9 - globalsmuggle.botmo->player->kartweight) * KART_FULLTURN) / 8; // Handling chars dodge better + + if (!globalsmuggle.botmo || P_MobjWasRemoved(globalsmuggle.botmo) || !globalsmuggle.botmo->player) + { + return false; + } + + if (!thing->health) + { + return true; + } + + if (globalsmuggle.botmo == thing) + { + return true; + } + + xdist = K_DistanceOfLineFromPoint( + globalsmuggle.botmo->x, globalsmuggle.botmo->y, + globalsmuggle.botmo->x + FINECOSINE(globalsmuggle.botmo->angle >> ANGLETOFINESHIFT), globalsmuggle.botmo->y + FINESINE(globalsmuggle.botmo->angle >> ANGLETOFINESHIFT), + thing->x, thing->y + ) / 2; // weight x dist more heavily than y dist + + ydist = K_DistanceOfLineFromPoint( + globalsmuggle.botmo->x, globalsmuggle.botmo->y, + globalsmuggle.botmo->x + FINECOSINE((globalsmuggle.botmo->angle + ANGLE_90) >> ANGLETOFINESHIFT), globalsmuggle.botmo->y + FINESINE((globalsmuggle.botmo->angle + ANGLE_90) >> ANGLETOFINESHIFT), + thing->x, thing->y + ); + + fulldist = FixedHypot(xdist, ydist); + + if (fulldist > globalsmuggle.distancetocheck) + { + return true; + } + + if (!P_CheckSight(globalsmuggle.botmo, thing)) + { + return true; + } + + destangle = R_PointToAngle2(globalsmuggle.botmo->x, globalsmuggle.botmo->y, thing->x, thing->y); + angle = (globalsmuggle.botmo->angle - destangle); + + if (angle < ANGLE_180) + { + anglediff = AngleFixed(angle)>>FRACBITS; + } + else + { + anglediff = 360-(AngleFixed(angle)>>FRACBITS); + } + + anglediff = abs(anglediff); + +#define PlayerAttackSteer(botcond, thingcond) \ + if ((botcond) && !(thingcond)) \ + { \ + K_SteerFromObject(globalsmuggle.botmo, thing, fulldist, xdist, true, 2 * (KART_FULLTURN + attack)); \ + } \ + else if ((thingcond) && !(botcond)) \ + { \ + K_SteerFromObject(globalsmuggle.botmo, thing, fulldist, xdist, false, 2 * (KART_FULLTURN + dodge)); \ + } + + 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_DUD: + case MT_JAWZ_SHIELD: + case MT_SSMINE: + case MT_SSMINE_SHIELD: + case MT_BALLHOG: + case MT_SPB: + case MT_BUBBLESHIELDTRAP: + K_SteerFromObject(globalsmuggle.botmo, thing, fulldist, xdist, false, 2 * (KART_FULLTURN + dodge)); + break; + case MT_RANDOMITEM: + if (anglediff >= 60) + { + break; + } + + if (P_CanPickupItem(globalsmuggle.botmo->player, 1)) + { + K_SteerFromObject(globalsmuggle.botmo, thing, fulldist, xdist, true, KART_FULLTURN + attack); + } + break; + case MT_EGGMANITEM: + if (anglediff >= 60) + { + break; + } + + if (P_CanPickupItem(globalsmuggle.botmo->player, 1)) // Can pick up an actual item + { + const UINT8 stealth = K_EggboxStealth(thing->x, thing->y); + const UINT8 requiredstealth = (globalsmuggle.botmo->player->botvars.difficulty * globalsmuggle.botmo->player->botvars.difficulty); + + if (stealth >= requiredstealth) + { + K_SteerFromObject(globalsmuggle.botmo, thing, fulldist, xdist, true, 2 * (KART_FULLTURN + attack)); + } + else + { + K_SteerFromObject(globalsmuggle.botmo, thing, fulldist, xdist, false, 2 * (KART_FULLTURN + dodge)); + } + } + break; + case MT_FLOATINGITEM: + if (anglediff >= 60) + { + break; + } + + if (P_CanPickupItem(globalsmuggle.botmo->player, 3)) + { + K_SteerFromObject(globalsmuggle.botmo, thing, fulldist, xdist, true, KART_FULLTURN + attack); + } + break; + case MT_RING: + case MT_FLINGRING: + if (anglediff >= 60) + { + break; + } + + if ((RINGTOTAL(globalsmuggle.botmo->player) < 20 && !globalsmuggle.botmo->player->kartstuff[k_ringlock] + && P_CanPickupItem(globalsmuggle.botmo->player, 0)) + && !thing->extravalue1 + && (globalsmuggle.botmo->player->kartstuff[k_itemtype] != KITEM_THUNDERSHIELD)) + { + K_SteerFromObject(globalsmuggle.botmo, thing, fulldist, xdist, true, + (RINGTOTAL(globalsmuggle.botmo->player) < 3 + ? (4 * (KART_FULLTURN + attack)) + : (KART_FULLTURN + attack)) + ); + } + break; + case MT_PLAYER: + if (thing->player + && !thing->player->kartstuff[k_hyudorotimer] + && !globalsmuggle.botmo->player->kartstuff[k_hyudorotimer]) + { + // There REALLY ought to be a better way to handle this logic, right?! + // Squishing + PlayerAttackSteer( + globalsmuggle.botmo->scale > thing->scale + (mapobjectscale/8), + thing->scale > globalsmuggle.botmo->scale + (mapobjectscale/8) + ) + // Invincibility + else PlayerAttackSteer( + globalsmuggle.botmo->player->kartstuff[k_invincibilitytimer], + thing->player->kartstuff[k_invincibilitytimer] + ) + // Thunder Shield + else PlayerAttackSteer( + globalsmuggle.botmo->player->kartstuff[k_itemtype] == KITEM_THUNDERSHIELD, + thing->player->kartstuff[k_itemtype] == KITEM_THUNDERSHIELD + ) + // Bubble Shield + else PlayerAttackSteer( + globalsmuggle.botmo->player->kartstuff[k_itemtype] == KITEM_BUBBLESHIELD, + thing->player->kartstuff[k_itemtype] == KITEM_BUBBLESHIELD + ) + // Flame Shield + else PlayerAttackSteer( + globalsmuggle.botmo->player->kartstuff[k_itemtype] == KITEM_FLAMESHIELD, + thing->player->kartstuff[k_itemtype] == KITEM_FLAMESHIELD + ) + // Has held item shield + else PlayerAttackSteer( + (globalsmuggle.botmo->player->kartstuff[k_itemheld] || globalsmuggle.botmo->player->kartstuff[k_eggmanheld]), + (thing->player->kartstuff[k_itemheld] || thing->player->kartstuff[k_eggmanheld]) + ) + // Ring Sting + else PlayerAttackSteer( + thing->player->kartstuff[k_rings] <= 0, + globalsmuggle.botmo->player->kartstuff[k_rings] <= 0 + ) + else + { + // After ALL of that, we can do standard bumping + fixed_t ourweight = K_GetMobjWeight(globalsmuggle.botmo, thing); + fixed_t theirweight = K_GetMobjWeight(thing, globalsmuggle.botmo); + fixed_t weightdiff = 0; + + if (anglediff >= 90) + { + weightdiff = theirweight - ourweight; + } + else + { + weightdiff = ourweight - theirweight; + } + + if (weightdiff > mapobjectscale) + { + K_SteerFromObject(globalsmuggle.botmo, thing, fulldist, xdist, true, KART_FULLTURN + attack); + } + else + { + K_SteerFromObject(globalsmuggle.botmo, thing, fulldist, xdist, false, KART_FULLTURN + dodge); + } + } + } + break; + case MT_BOTHINT: + if (anglediff >= 60) + { + break; + } + + if (thing->extravalue1 == 0) + { + K_SteerFromObject(globalsmuggle.botmo, thing, fulldist, xdist, false, thing->extravalue2 * (KART_FULLTURN + dodge)); + } + { + K_SteerFromObject(globalsmuggle.botmo, thing, fulldist, xdist, true, thing->extravalue2 * (KART_FULLTURN + attack)); + } + break; + default: + if (thing->flags & (MF_SOLID|MF_ENEMY|MF_BOSS|MF_PAIN|MF_MISSILE|MF_FIRE)) + { + K_SteerFromObject(globalsmuggle.botmo, thing, fulldist, xdist, false, 2 * (KART_FULLTURN + dodge)); + } + break; + } + + return true; +} + +/*-------------------------------------------------- + INT16 K_BotFindObjects(player_t *player, INT16 turn) + + See header file for description. +--------------------------------------------------*/ +INT16 K_BotFindObjects(player_t *player, INT16 turn) +{ + INT32 xl, xh, yl, yh, bx, by; + + memset(&globalsmuggle, 0, sizeof (struct globalsmuggle)); + globalsmuggle.steer = 0; + globalsmuggle.botmo = player->mo; + globalsmuggle.curturn = turn; + globalsmuggle.distancetocheck = 2048*mapobjectscale; + + xl = (unsigned)(globalsmuggle.botmo->x - globalsmuggle.distancetocheck - bmaporgx)>>MAPBLOCKSHIFT; + xh = (unsigned)(globalsmuggle.botmo->x + globalsmuggle.distancetocheck - bmaporgx)>>MAPBLOCKSHIFT; + yl = (unsigned)(globalsmuggle.botmo->y - globalsmuggle.distancetocheck - bmaporgy)>>MAPBLOCKSHIFT; + yh = (unsigned)(globalsmuggle.botmo->y + globalsmuggle.distancetocheck - bmaporgy)>>MAPBLOCKSHIFT; + + BMBOUNDFIX(xl, xh, yl, yh); + + for (bx = xl; bx <= xh; bx++) + { + for (by = yl; by <= yh; by++) + { + P_BlockThingsIterator(bx, by, K_BotSteerObjects); + } + } + + return globalsmuggle.steer; +}