2017-04-17 11:33:19 +00:00
|
|
|
/*
|
|
|
|
**
|
|
|
|
**
|
|
|
|
**---------------------------------------------------------------------------
|
|
|
|
** Copyright 1999 Martin Colberg
|
|
|
|
** Copyright 1999-2016 Randy Heit
|
|
|
|
** Copyright 2005-2016 Christoph Oelckers
|
|
|
|
** All rights reserved.
|
|
|
|
**
|
|
|
|
** Redistribution and use in source and binary forms, with or without
|
|
|
|
** modification, are permitted provided that the following conditions
|
|
|
|
** are met:
|
|
|
|
**
|
|
|
|
** 1. Redistributions of source code must retain the above copyright
|
|
|
|
** notice, this list of conditions and the following disclaimer.
|
|
|
|
** 2. Redistributions in binary form must reproduce the above copyright
|
|
|
|
** notice, this list of conditions and the following disclaimer in the
|
|
|
|
** documentation and/or other materials provided with the distribution.
|
|
|
|
** 3. The name of the author may not be used to endorse or promote products
|
|
|
|
** derived from this software without specific prior written permission.
|
|
|
|
**
|
|
|
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
|
|
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
|
|
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
|
|
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
|
|
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
|
|
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
|
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
|
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
|
|
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
**---------------------------------------------------------------------------
|
|
|
|
**
|
|
|
|
*/
|
2016-03-01 15:47:10 +00:00
|
|
|
/********************************
|
|
|
|
* B_Think.c *
|
|
|
|
* Description: *
|
|
|
|
* Movement/Roaming code for *
|
|
|
|
* the bot's *
|
|
|
|
*********************************/
|
|
|
|
|
|
|
|
#include "doomdef.h"
|
|
|
|
#include "doomstat.h"
|
|
|
|
#include "p_local.h"
|
|
|
|
#include "b_bot.h"
|
|
|
|
#include "g_game.h"
|
2017-03-09 22:30:42 +00:00
|
|
|
#include "d_protocol.h"
|
2016-03-01 15:47:10 +00:00
|
|
|
#include "m_random.h"
|
|
|
|
#include "i_system.h"
|
|
|
|
#include "p_lnspec.h"
|
|
|
|
#include "gi.h"
|
|
|
|
#include "a_keys.h"
|
|
|
|
#include "d_event.h"
|
|
|
|
#include "p_enemy.h"
|
|
|
|
#include "d_player.h"
|
|
|
|
#include "p_spec.h"
|
|
|
|
#include "p_checkposition.h"
|
2017-03-11 18:02:00 +00:00
|
|
|
#include "actorinlines.h"
|
2016-03-11 14:45:47 +00:00
|
|
|
#include "math/cmath.h"
|
2016-03-01 15:47:10 +00:00
|
|
|
|
|
|
|
static FRandom pr_botopendoor ("BotOpenDoor");
|
|
|
|
static FRandom pr_bottrywalk ("BotTryWalk");
|
|
|
|
static FRandom pr_botnewchasedir ("BotNewChaseDir");
|
|
|
|
|
|
|
|
// borrow some tables from p_enemy.cpp
|
|
|
|
extern dirtype_t opposite[9];
|
|
|
|
extern dirtype_t diags[4];
|
|
|
|
|
|
|
|
//Called while the bot moves after its dest mobj
|
|
|
|
//which can be a weapon/enemy/item whatever.
|
|
|
|
void DBot::Roam (ticcmd_t *cmd)
|
|
|
|
{
|
|
|
|
|
|
|
|
if (Reachable(dest))
|
|
|
|
{ // Straight towards it.
|
2016-03-25 23:34:56 +00:00
|
|
|
Angle = player->mo->AngleTo(dest);
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
else if (player->mo->movedir < 8) // turn towards movement direction if not there yet
|
|
|
|
{
|
2016-03-25 23:34:56 +00:00
|
|
|
// no point doing this with floating point angles...
|
|
|
|
unsigned angle = Angle.BAMs() & (unsigned)(7 << 29);
|
|
|
|
int delta = angle - (player->mo->movedir << 29);
|
2016-03-01 15:47:10 +00:00
|
|
|
|
|
|
|
if (delta > 0)
|
2016-03-25 23:34:56 +00:00
|
|
|
Angle -= 45;
|
2016-03-01 15:47:10 +00:00
|
|
|
else if (delta < 0)
|
2016-03-25 23:34:56 +00:00
|
|
|
Angle += 45;
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// chase towards destination.
|
|
|
|
if (--player->mo->movecount < 0 || !Move (cmd))
|
|
|
|
{
|
|
|
|
NewChaseDir (cmd);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool DBot::Move (ticcmd_t *cmd)
|
|
|
|
{
|
2016-03-25 23:34:56 +00:00
|
|
|
double tryx, tryy;
|
2016-03-01 15:47:10 +00:00
|
|
|
bool try_ok;
|
|
|
|
int good;
|
|
|
|
|
2017-02-04 15:29:01 +00:00
|
|
|
if (player->mo->movedir >= DI_NODIR)
|
|
|
|
{
|
|
|
|
player->mo->movedir = DI_NODIR; // make sure it's valid.
|
2016-03-01 15:47:10 +00:00
|
|
|
return false;
|
2017-02-04 15:29:01 +00:00
|
|
|
}
|
2016-03-01 15:47:10 +00:00
|
|
|
|
2016-03-25 23:34:56 +00:00
|
|
|
tryx = player->mo->X() + 8*xspeed[player->mo->movedir];
|
|
|
|
tryy = player->mo->Y() + 8*yspeed[player->mo->movedir];
|
2016-03-01 15:47:10 +00:00
|
|
|
|
|
|
|
try_ok = bglobal.CleanAhead (player->mo, tryx, tryy, cmd);
|
|
|
|
|
|
|
|
if (!try_ok) //Anything blocking that could be opened etc..
|
|
|
|
{
|
|
|
|
if (!spechit.Size ())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
player->mo->movedir = DI_NODIR;
|
|
|
|
|
|
|
|
good = 0;
|
|
|
|
spechit_t spechit1;
|
|
|
|
line_t *ld;
|
|
|
|
|
|
|
|
while (spechit.Pop (spechit1))
|
|
|
|
{
|
|
|
|
ld = spechit1.line;
|
|
|
|
bool tryit = true;
|
|
|
|
|
|
|
|
if (ld->special == Door_LockedRaise && !P_CheckKeys (player->mo, ld->args[3], false))
|
|
|
|
tryit = false;
|
|
|
|
else if (ld->special == Generic_Door && !P_CheckKeys (player->mo, ld->args[4], false))
|
|
|
|
tryit = false;
|
|
|
|
|
|
|
|
if (tryit &&
|
|
|
|
(P_TestActivateLine (ld, player->mo, 0, SPAC_Use) ||
|
|
|
|
P_TestActivateLine (ld, player->mo, 0, SPAC_Push)))
|
|
|
|
{
|
|
|
|
good |= ld == player->mo->BlockingLine ? 1 : 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (good && ((pr_botopendoor() >= 203) ^ (good & 1)))
|
|
|
|
{
|
|
|
|
cmd->ucmd.buttons |= BT_USE;
|
|
|
|
cmd->ucmd.forwardmove = FORWARDRUN;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else //Move forward.
|
|
|
|
cmd->ucmd.forwardmove = FORWARDRUN;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool DBot::TryWalk (ticcmd_t *cmd)
|
|
|
|
{
|
|
|
|
if (!Move (cmd))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
player->mo->movecount = pr_bottrywalk() & 60;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void DBot::NewChaseDir (ticcmd_t *cmd)
|
|
|
|
{
|
|
|
|
dirtype_t d[3];
|
|
|
|
|
|
|
|
int tdir;
|
|
|
|
dirtype_t olddir;
|
|
|
|
|
|
|
|
dirtype_t turnaround;
|
|
|
|
|
|
|
|
if (!dest)
|
|
|
|
{
|
|
|
|
#ifndef BOT_RELEASE_COMPILE
|
|
|
|
Printf ("Bot tried move without destination\n");
|
|
|
|
#endif
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
olddir = (dirtype_t)player->mo->movedir;
|
|
|
|
turnaround = opposite[olddir];
|
|
|
|
|
2016-03-25 23:34:56 +00:00
|
|
|
DVector2 delta = player->mo->Vec2To(dest);
|
2016-03-01 15:47:10 +00:00
|
|
|
|
2016-03-25 23:34:56 +00:00
|
|
|
if (delta.X > 10)
|
2016-03-01 15:47:10 +00:00
|
|
|
d[1] = DI_EAST;
|
2016-03-25 23:34:56 +00:00
|
|
|
else if (delta.X < -10)
|
2016-03-01 15:47:10 +00:00
|
|
|
d[1] = DI_WEST;
|
|
|
|
else
|
|
|
|
d[1] = DI_NODIR;
|
|
|
|
|
2016-03-25 23:34:56 +00:00
|
|
|
if (delta.Y < -10)
|
2016-03-01 15:47:10 +00:00
|
|
|
d[2] = DI_SOUTH;
|
2016-03-25 23:34:56 +00:00
|
|
|
else if (delta.Y > 10)
|
2016-03-01 15:47:10 +00:00
|
|
|
d[2] = DI_NORTH;
|
|
|
|
else
|
|
|
|
d[2] = DI_NODIR;
|
|
|
|
|
|
|
|
// try direct route
|
|
|
|
if (d[1] != DI_NODIR && d[2] != DI_NODIR)
|
|
|
|
{
|
2016-03-25 23:34:56 +00:00
|
|
|
player->mo->movedir = diags[((delta.Y < 0) << 1) + (delta.X > 0)];
|
2016-03-01 15:47:10 +00:00
|
|
|
if (player->mo->movedir != turnaround && TryWalk(cmd))
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// try other directions
|
2016-03-25 23:34:56 +00:00
|
|
|
if (pr_botnewchasedir() > 200
|
|
|
|
|| fabs(delta.Y) > fabs(delta.X))
|
|
|
|
{
|
|
|
|
tdir = d[1];
|
|
|
|
d[1] = d[2];
|
|
|
|
d[2] = (dirtype_t)tdir;
|
|
|
|
}
|
2016-03-01 15:47:10 +00:00
|
|
|
|
|
|
|
if (d[1]==turnaround)
|
|
|
|
d[1]=DI_NODIR;
|
|
|
|
if (d[2]==turnaround)
|
|
|
|
d[2]=DI_NODIR;
|
|
|
|
|
|
|
|
if (d[1]!=DI_NODIR)
|
|
|
|
{
|
|
|
|
player->mo->movedir = d[1];
|
|
|
|
if (TryWalk (cmd))
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (d[2]!=DI_NODIR)
|
|
|
|
{
|
|
|
|
player->mo->movedir = d[2];
|
|
|
|
|
|
|
|
if (TryWalk(cmd))
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// there is no direct path to the player,
|
|
|
|
// so pick another direction.
|
|
|
|
if (olddir!=DI_NODIR)
|
|
|
|
{
|
|
|
|
player->mo->movedir = olddir;
|
|
|
|
|
|
|
|
if (TryWalk(cmd))
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// randomly determine direction of search
|
|
|
|
if (pr_botnewchasedir()&1)
|
|
|
|
{
|
|
|
|
for ( tdir=DI_EAST;
|
|
|
|
tdir<=DI_SOUTHEAST;
|
|
|
|
tdir++ )
|
|
|
|
{
|
|
|
|
if (tdir!=turnaround)
|
|
|
|
{
|
|
|
|
player->mo->movedir = tdir;
|
|
|
|
|
|
|
|
if (TryWalk(cmd))
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
for ( tdir=DI_SOUTHEAST;
|
|
|
|
tdir != (DI_EAST-1);
|
|
|
|
tdir-- )
|
|
|
|
{
|
|
|
|
if (tdir!=turnaround)
|
|
|
|
{
|
|
|
|
player->mo->movedir = tdir;
|
|
|
|
|
|
|
|
if (TryWalk(cmd))
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (turnaround != DI_NODIR)
|
|
|
|
{
|
|
|
|
player->mo->movedir = turnaround;
|
|
|
|
if (TryWalk(cmd))
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
player->mo->movedir = DI_NODIR; // can not move
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// B_CleanAhead
|
|
|
|
// Check if a place is ok to move towards.
|
|
|
|
// This is also a traverse function for
|
|
|
|
// bots pre-rocket fire (preventing suicide)
|
|
|
|
//
|
2016-03-25 23:34:56 +00:00
|
|
|
bool FCajunMaster::CleanAhead (AActor *thing, double x, double y, ticcmd_t *cmd)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
|
|
|
FCheckPosition tm;
|
|
|
|
|
|
|
|
if (!SafeCheckPosition (thing, x, y, tm))
|
|
|
|
return false; // solid wall or thing
|
|
|
|
|
|
|
|
if (!(thing->flags & MF_NOCLIP) )
|
|
|
|
{
|
2016-03-20 19:55:06 +00:00
|
|
|
if (tm.ceilingz - tm.floorz < thing->Height)
|
2016-03-01 15:47:10 +00:00
|
|
|
return false; // doesn't fit
|
|
|
|
|
2016-03-25 23:34:56 +00:00
|
|
|
double maxmove = MAXMOVEHEIGHT;
|
2016-03-01 15:47:10 +00:00
|
|
|
if (!(thing->flags&MF_MISSILE))
|
|
|
|
{
|
2016-03-25 23:34:56 +00:00
|
|
|
if(tm.floorz > (thing->Sector->floorplane.ZatPoint(x, y)+maxmove)) //Too high wall
|
2016-03-01 15:47:10 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
//Jumpable
|
2016-03-25 23:34:56 +00:00
|
|
|
if(tm.floorz > (thing->Sector->floorplane.ZatPoint(x, y)+thing->MaxStepHeight))
|
2016-03-01 15:47:10 +00:00
|
|
|
cmd->ucmd.buttons |= BT_JUMP;
|
|
|
|
|
|
|
|
|
|
|
|
if ( !(thing->flags & MF_TELEPORT) &&
|
2016-03-20 12:32:53 +00:00
|
|
|
tm.ceilingz < thing->Top())
|
2016-03-01 15:47:10 +00:00
|
|
|
return false; // mobj must lower itself to fit
|
|
|
|
|
|
|
|
// jump out of water
|
|
|
|
// if((thing->eflags & (MF_UNDERWATER|MF_TOUCHWATER))==(MF_UNDERWATER|MF_TOUCHWATER))
|
2016-03-25 23:34:56 +00:00
|
|
|
// maxstep=37;
|
2016-03-01 15:47:10 +00:00
|
|
|
|
|
|
|
if ( !(thing->flags & MF_TELEPORT) &&
|
2016-03-22 11:42:27 +00:00
|
|
|
(tm.floorz - thing->Z() > thing->MaxStepHeight) )
|
2016-03-01 15:47:10 +00:00
|
|
|
return false; // too big a step up
|
|
|
|
|
|
|
|
|
|
|
|
if ( !(thing->flags&(MF_DROPOFF|MF_FLOAT))
|
2016-03-23 11:21:52 +00:00
|
|
|
&& tm.floorz - tm.dropoffz > thing->MaxDropOffHeight )
|
2016-03-01 15:47:10 +00:00
|
|
|
return false; // don't stand over a dropoff
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-03-16 11:41:26 +00:00
|
|
|
#define OKAYRANGE (5) //counts *2, when angle is in range, turning is not executed.
|
|
|
|
#define MAXTURN (15) //Max degrees turned in one tic. Lower is smother but may cause the bot not getting where it should = crash
|
2016-03-01 15:47:10 +00:00
|
|
|
#define TURNSENS 3 //Higher is smoother but slower turn.
|
|
|
|
|
|
|
|
void DBot::TurnToAng ()
|
|
|
|
{
|
2016-03-16 11:41:26 +00:00
|
|
|
double maxturn = MAXTURN;
|
2016-03-01 15:47:10 +00:00
|
|
|
|
|
|
|
if (player->ReadyWeapon != NULL)
|
|
|
|
{
|
|
|
|
if (player->ReadyWeapon->WeaponFlags & WIF_BOT_EXPLOSIVE)
|
|
|
|
{
|
|
|
|
if (t_roam && !missile)
|
|
|
|
{ //Keep angle that where when shot where decided.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(enemy)
|
|
|
|
if(!dest) //happens when running after item in combat situations, or normal, prevents weak turns
|
|
|
|
if(player->ReadyWeapon->ProjectileType == NULL && !(player->ReadyWeapon->WeaponFlags & WIF_MELEEWEAPON))
|
2016-03-25 23:34:56 +00:00
|
|
|
if(Check_LOS(enemy, SHOOTFOV+5))
|
2016-03-01 15:47:10 +00:00
|
|
|
maxturn = 3;
|
|
|
|
}
|
|
|
|
|
2016-03-25 23:34:56 +00:00
|
|
|
DAngle distance = deltaangle(player->mo->Angles.Yaw, Angle);
|
2016-03-01 15:47:10 +00:00
|
|
|
|
2016-03-16 11:41:26 +00:00
|
|
|
if (fabs (distance) < OKAYRANGE && !enemy)
|
2016-03-01 15:47:10 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
distance /= TURNSENS;
|
2016-03-16 11:41:26 +00:00
|
|
|
if (fabs (distance) > maxturn)
|
2016-03-01 15:47:10 +00:00
|
|
|
distance = distance < 0 ? -maxturn : maxturn;
|
|
|
|
|
2016-03-16 11:41:26 +00:00
|
|
|
player->mo->Angles.Yaw += distance;
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void DBot::Pitch (AActor *target)
|
|
|
|
{
|
|
|
|
double aim;
|
|
|
|
double diff;
|
|
|
|
|
2016-03-25 23:34:56 +00:00
|
|
|
diff = target->Z() - player->mo->Z();
|
|
|
|
aim = g_atan(diff / player->mo->Distance2D(target));
|
2016-03-30 14:30:22 +00:00
|
|
|
player->mo->Angles.Pitch = DAngle::ToDegrees(aim);
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//Checks if a sector is dangerous.
|
|
|
|
bool FCajunMaster::IsDangerous (sector_t *sec)
|
|
|
|
{
|
|
|
|
return sec->damageamount > 0;
|
|
|
|
}
|