mirror of
https://github.com/jmarshall23/DoomRTX.git
synced 2026-08-14 01:01:05 +02:00
1404 lines
37 KiB
C++
1404 lines
37 KiB
C++
// BotAI.cpp
|
|
//
|
|
|
|
#pragma hdrstop
|
|
#include "precompiled.h"
|
|
#include "../Game_local.h"
|
|
#include "../ai/AASCallback_FindCoverArea.h"
|
|
|
|
#define IDEAL_ATTACKDIST 140
|
|
|
|
int rvmBot::WP_MACHINEGUN = -1;
|
|
int rvmBot::WP_SHOTGUN = -1;
|
|
int rvmBot::WP_PLASMAGUN = -1;
|
|
int rvmBot::WP_ROCKET_LAUNCHER = -1;
|
|
|
|
/*
|
|
=========================
|
|
rvmBot::BotIsDead
|
|
=========================
|
|
*/
|
|
bool rvmBot::BotIsDead( bot_state_t* bs )
|
|
{
|
|
idPlayer* player = gameLocal.GetClientByNum( bs->client );
|
|
if( player->health <= 0 )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
rvmBot::BotReachedGoal
|
|
==================
|
|
*/
|
|
bool rvmBot::BotReachedGoal( bot_state_t* bs, bot_goal_t* goal )
|
|
{
|
|
if( goal->flags & GFL_ITEM )
|
|
{
|
|
//if touching the goal
|
|
if( botGoalManager.BotTouchingGoal( bs->origin, goal ) )
|
|
{
|
|
if( !( goal->flags & GFL_DROPPED ) )
|
|
{
|
|
botGoalManager.BotSetAvoidGoalTime( bs->gs, goal->number, -1 );
|
|
}
|
|
return true;
|
|
}
|
|
//if the goal isn't there
|
|
if( botGoalManager.BotItemGoalInVisButNotVisible( bs->entitynum, bs->eye, bs->viewangles, goal ) )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//if touching the goal
|
|
if( botGoalManager.BotTouchingGoal( bs->origin, goal ) )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
rvmBot::BotChooseWeapon
|
|
==================
|
|
*/
|
|
void rvmBot::BotChooseWeapon( bot_state_t* bs )
|
|
{
|
|
int newweaponnum;
|
|
|
|
//if (bs->cur_ps.weaponstate == WEAPON_RAISING ||
|
|
// bs->cur_ps.weaponstate == WEAPON_DROPPING) {
|
|
// //trap_EA_SelectWeapon(bs->client, bs->weaponnum);
|
|
// bs->input.weapon = bs->weaponnum;
|
|
//}
|
|
//else {
|
|
newweaponnum = botWeaponInfoManager.BotChooseBestFightWeapon( bs->ws, bs->inventory );
|
|
if( bs->weaponnum != newweaponnum )
|
|
{
|
|
bs->weaponchange_time = Bot_Time();
|
|
}
|
|
bs->weaponnum = newweaponnum;
|
|
//BotAI_Print(PRT_MESSAGE, "bs->weaponnum = %d\n", bs->weaponnum);
|
|
//trap_EA_SelectWeapon(bs->client, bs->weaponnum);
|
|
bs->botinput.weapon = bs->weaponnum;
|
|
//}
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
rvmBot::BotGetItemLongTermGoal
|
|
==================
|
|
*/
|
|
int rvmBot::BotGetItemLongTermGoal( bot_state_t* bs, int tfl, bot_goal_t* goal )
|
|
{
|
|
//if the bot has no goal
|
|
if( !botGoalManager.BotGetTopGoal( bs->gs, goal ) )
|
|
{
|
|
//BotAI_Print(PRT_MESSAGE, "no ltg on stack\n");
|
|
bs->ltg_time = 0;
|
|
}
|
|
//if the bot touches the current goal
|
|
else if( BotReachedGoal( bs, goal ) )
|
|
{
|
|
BotChooseWeapon( bs );
|
|
bs->ltg_time = 0;
|
|
}
|
|
|
|
// Check to see that we can get to our goal, if not get a new goal.
|
|
//if (bs->numMovementWaypoints > 0)
|
|
//{
|
|
// trace_t tr;
|
|
// gentity_t* ent = &g_entities[bs->client];
|
|
// vec3_t waypoint;
|
|
//
|
|
// VectorCopy(bs->movement_waypoints[bs->currentWaypoint], waypoint);
|
|
// waypoint[2] += 5.0f;
|
|
//
|
|
// trap_Trace(&tr, ent->r.currentOrigin, NULL, NULL, waypoint, bs->client, CONTENTS_SOLID);
|
|
//
|
|
// if (tr.fraction <= 0.7f)
|
|
// {
|
|
// BotChooseWeapon(bs);
|
|
// bs->ltg_time = 0;
|
|
// }
|
|
//}
|
|
|
|
//if it is time to find a new long term goal
|
|
if( bs->ltg_time == 0 )
|
|
{
|
|
//pop the current goal from the stack
|
|
botGoalManager.BotPopGoal( bs->gs );
|
|
//BotAI_Print(PRT_MESSAGE, "%s: choosing new ltg\n", ClientName(bs->client, netname, sizeof(netname)));
|
|
//choose a new goal
|
|
//BotAI_Print(PRT_MESSAGE, "%6.1f client %d: BotChooseLTGItem\n", Bot_Time(), bs->client);
|
|
if( botGoalManager.BotChooseLTGItem( bs->gs, bs->origin, bs->inventory, tfl ) )
|
|
{
|
|
char buf[128];
|
|
//get the goal at the top of the stack
|
|
botGoalManager.BotGetTopGoal( bs->gs, goal );
|
|
botGoalManager.BotGoalName( goal->number, buf, sizeof( buf ) );
|
|
common->Printf( "%1.1f: new long term goal %s\n", Bot_Time(), buf );
|
|
|
|
bs->ltg_time = Bot_Time() + 20;
|
|
bs->currentGoal.framenum = gameLocal.framenum;
|
|
}
|
|
else //the bot gets sorta stuck with all the avoid timings, shouldn't happen though
|
|
{
|
|
//
|
|
//trap_BotDumpAvoidGoals(bs->gs);
|
|
//reset the avoid goals and the avoid reach
|
|
botGoalManager.BotResetAvoidGoals( bs->gs );
|
|
//BotResetAvoidReach(bs->ms);
|
|
}
|
|
//get the goal at the top of the stack
|
|
if( !botGoalManager.BotGetTopGoal( bs->gs, goal ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bs->currentGoal.framenum = gameLocal.framenum;
|
|
|
|
return true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
rvmBot::EntityIsDead
|
|
==================
|
|
*/
|
|
bool rvmBot::EntityIsDead( idEntity* entity )
|
|
{
|
|
{
|
|
idPlayer* player = entity->Cast<idPlayer>();
|
|
if( player && player->health <= 0 )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
BotEntityVisibleTest
|
|
|
|
returns visibility in the range [0, 1] taking fog and water surfaces into account
|
|
==================
|
|
*/
|
|
float rvmBot::BotEntityVisibleTest( int viewer, idVec3 eye, idAngles viewangles, float fov, int ent, bool allowHeightTest )
|
|
{
|
|
int i, contents_mask, passent, hitent, infog, inwater, otherinfog, pc;
|
|
float squaredfogdist, waterfactor, vis, bestvis;
|
|
trace_t trace;
|
|
idEntity* entinfo;
|
|
idVec3 dir, start, end, middle;
|
|
idAngles entangles;
|
|
idPlayer* viewEnt;
|
|
|
|
//calculate middle of bounding box
|
|
//BotEntityInfo(ent, &entinfo);
|
|
entinfo = gameLocal.entities[ent];
|
|
viewEnt = gameLocal.entities[viewer]->Cast<idPlayer>();
|
|
|
|
//VectorAdd(entinfo->r.mins, entinfo->r.maxs, middle);
|
|
//VectorScale(middle, 0.5, middle);
|
|
//VectorAdd(entinfo->GetOrigin(), middle, middle);
|
|
middle = entinfo->GetPhysics()->GetBounds().GetCenter();
|
|
middle += entinfo->GetOrigin();
|
|
|
|
//check if entity is within field of vision
|
|
//VectorSubtract(middle, eye, dir);
|
|
dir = middle - eye;
|
|
entangles = dir.ToAngles();
|
|
|
|
if (!viewEnt->CheckFOV(entinfo->GetOrigin()))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
pc = gameLocal.clip.PointContents( eye );
|
|
infog = ( pc & CONTENTS_FOG );
|
|
inwater = ( pc & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) );
|
|
bestvis = 0;
|
|
for( i = 0; i < 3; i++ )
|
|
{
|
|
//if the point is not in potential visible sight
|
|
//if (!AAS_inPVS(eye, middle)) continue;
|
|
//
|
|
contents_mask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP;
|
|
passent = viewer;
|
|
hitent = ent;
|
|
//VectorCopy(eye, start);
|
|
start = eye;
|
|
//VectorCopy(middle, end);
|
|
end = middle;
|
|
//if the entity is in water, lava or slime
|
|
if( gameLocal.clip.PointContents( middle ) & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) )
|
|
{
|
|
contents_mask |= ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER );
|
|
}
|
|
//if eye is in water, lava or slime
|
|
if( inwater )
|
|
{
|
|
if( !( contents_mask & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) )
|
|
{
|
|
passent = ent;
|
|
hitent = viewer;
|
|
VectorCopy( middle, start );
|
|
VectorCopy( eye, end );
|
|
}
|
|
contents_mask ^= ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER );
|
|
}
|
|
|
|
//trace from start to end
|
|
gameLocal.Trace( trace, start, end, contents_mask, passent );
|
|
// jmarshall
|
|
if( trace.fraction < 0.9f && allowHeightTest )
|
|
{
|
|
end[2] += 50.0f;
|
|
gameLocal.Trace( trace, start, end, contents_mask, passent );
|
|
}
|
|
// jmarshall end
|
|
|
|
//if water was hit
|
|
waterfactor = 1.0;
|
|
if( trace.c.contents & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) )
|
|
{
|
|
//if the water surface is translucent
|
|
if( 1 )
|
|
{
|
|
//trace through the water
|
|
contents_mask &= ~( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER );
|
|
//trap_Trace(&trace, trace.endpos, NULL, NULL, end, passent, contents_mask);
|
|
gameLocal.Trace( trace, trace.endpos, end, contents_mask, passent );
|
|
waterfactor = 0.5;
|
|
}
|
|
}
|
|
|
|
//if a full trace or the hitent was hit
|
|
if( trace.fraction >= 1 || trace.c.entityNum == hitent )
|
|
{
|
|
//check for fog, assuming there's only one fog brush where
|
|
//either the viewer or the entity is in or both are in
|
|
otherinfog = ( gameLocal.clip.PointContents( middle ) & CONTENTS_FOG );
|
|
if( infog && otherinfog )
|
|
{
|
|
VectorSubtract( trace.endpos, eye, dir );
|
|
squaredfogdist = dir.LengthSqr();
|
|
}
|
|
else if( infog )
|
|
{
|
|
VectorCopy( trace.endpos, start );
|
|
//trap_Trace(&trace, start, NULL, NULL, eye, viewer, CONTENTS_FOG);
|
|
gameLocal.Trace( trace, start, eye, CONTENTS_FOG, viewer );
|
|
VectorSubtract( eye, trace.endpos, dir );
|
|
squaredfogdist = dir.LengthSqr();
|
|
}
|
|
else if( otherinfog )
|
|
{
|
|
VectorCopy( trace.endpos, end );
|
|
//trap_Trace(&trace, eye, NULL, NULL, end, viewer, CONTENTS_FOG);
|
|
gameLocal.Trace( trace, eye, end, CONTENTS_FOG, viewer );
|
|
VectorSubtract( end, trace.endpos, dir );
|
|
squaredfogdist = dir.LengthSqr();
|
|
}
|
|
else
|
|
{
|
|
//if the entity and the viewer are not in fog assume there's no fog in between
|
|
squaredfogdist = 0;
|
|
}
|
|
//decrease visibility with the view distance through fog
|
|
vis = 1 / ( ( squaredfogdist * 0.001 ) < 1 ? 1 : ( squaredfogdist * 0.001 ) );
|
|
|
|
//if entering water visibility is reduced
|
|
vis *= waterfactor;
|
|
|
|
if( vis > bestvis )
|
|
{
|
|
bestvis = vis;
|
|
}
|
|
|
|
//if pretty much no fog
|
|
if( bestvis >= 0.95 )
|
|
{
|
|
return bestvis;
|
|
}
|
|
}
|
|
//check bottom and top of bounding box as well
|
|
if( i == 0 )
|
|
{
|
|
middle[2] += entinfo->GetPhysics()->GetBounds()[0][2]; // r.mins[2];
|
|
}
|
|
else if( i == 1 )
|
|
{
|
|
middle[2] += entinfo->GetPhysics()->GetBounds()[1][2] - entinfo->GetPhysics()->GetBounds()[0][2]; //entinfo->r.maxs[2] - entinfo->r.mins[2];
|
|
}
|
|
}
|
|
return bestvis;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
rvmBot::BotEntityVisible
|
|
==================
|
|
*/
|
|
float rvmBot::BotEntityVisible( int viewer, idVec3 eye, idAngles viewangles, float fov, int ent )
|
|
{
|
|
return BotEntityVisibleTest( viewer, eye, viewangles, fov, ent, true );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
rvmBot::BotUpdateBattleInventory
|
|
==================
|
|
*/
|
|
void rvmBot::BotUpdateBattleInventory( bot_state_t* bs, int enemy )
|
|
{
|
|
idVec3 dir;
|
|
idEntity* entinfo;
|
|
|
|
entinfo = gameLocal.entities[enemy];
|
|
|
|
VectorSubtract( entinfo->GetOrigin(), bs->origin, dir );
|
|
bs->inventory[ENEMY_HEIGHT] = ( int )dir[2];
|
|
dir[2] = 0;
|
|
bs->inventory[ENEMY_HORIZONTAL_DIST] = ( int )dir.Length();
|
|
//FIXME: add num visible enemies and num visible team mates to the inventory
|
|
}
|
|
|
|
/*
|
|
==================
|
|
rvmBot::BotAggression
|
|
==================
|
|
*/
|
|
float rvmBot::BotAggression( bot_state_t* bs )
|
|
{
|
|
//if the bot has quad
|
|
if( bs->inventory[INVENTORY_QUAD] )
|
|
{
|
|
//if the bot is not holding the gauntlet or the enemy is really nearby
|
|
if( bs->weaponnum != 0 ||
|
|
bs->inventory[ENEMY_HORIZONTAL_DIST] < 80 )
|
|
{
|
|
return 70;
|
|
}
|
|
}
|
|
//if the enemy is located way higher than the bot
|
|
if( bs->inventory[ENEMY_HEIGHT] > 200 )
|
|
{
|
|
return 0;
|
|
}
|
|
//if the bot is very low on health
|
|
if( bs->inventory[INVENTORY_HEALTH] < 60 )
|
|
{
|
|
return 0;
|
|
}
|
|
//if the bot is low on health
|
|
if( bs->inventory[INVENTORY_HEALTH] < 80 )
|
|
{
|
|
//if the bot has insufficient armor
|
|
if( bs->inventory[INVENTORY_ARMOR] < 40 )
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
//if the bot can use the bfg
|
|
if( bs->inventory[INVENTORY_BFG10K] > 0 &&
|
|
bs->inventory[INVENTORY_BFGAMMO] > 7 )
|
|
{
|
|
return 100;
|
|
}
|
|
//if the bot can use the railgun
|
|
if( bs->inventory[INVENTORY_RAILGUN] > 0 &&
|
|
bs->inventory[INVENTORY_SLUGS] > 5 )
|
|
{
|
|
return 95;
|
|
}
|
|
//if the bot can use the lightning gun
|
|
if( bs->inventory[INVENTORY_LIGHTNING] > 0 &&
|
|
bs->inventory[INVENTORY_LIGHTNINGAMMO] > 50 )
|
|
{
|
|
return 90;
|
|
}
|
|
//if the bot can use the rocketlauncher
|
|
if( bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 &&
|
|
bs->inventory[INVENTORY_ROCKETS] > 5 )
|
|
{
|
|
return 90;
|
|
}
|
|
//if the bot can use the plasmagun
|
|
if( bs->inventory[INVENTORY_PLASMAGUN] > 0 &&
|
|
bs->inventory[INVENTORY_CELLS] > 40 )
|
|
{
|
|
return 85;
|
|
}
|
|
//if the bot can use the grenade launcher
|
|
if( bs->inventory[INVENTORY_GRENADELAUNCHER] > 0 &&
|
|
bs->inventory[INVENTORY_GRENADES] > 10 )
|
|
{
|
|
return 80;
|
|
}
|
|
//if the bot can use the shotgun
|
|
if( bs->inventory[INVENTORY_SHOTGUN] > 0 &&
|
|
bs->inventory[INVENTORY_SHELLS] > 10 )
|
|
{
|
|
return 50;
|
|
}
|
|
|
|
if( bs->inventory[INVENTORY_BULLETS] > 0 )
|
|
{
|
|
return 60;
|
|
}
|
|
|
|
//otherwise the bot is not feeling too good
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
rvmBot::BotWantsToRetreat
|
|
==================
|
|
*/
|
|
int rvmBot::BotWantsToRetreat( bot_state_t* bs )
|
|
{
|
|
if( BotAggression( bs ) < 50 )
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
rvmBot::BotBattleUseItems
|
|
==================
|
|
*/
|
|
void rvmBot::BotBattleUseItems( bot_state_t* bs )
|
|
{
|
|
if( bs->inventory[INVENTORY_HEALTH] < 40 )
|
|
{
|
|
if( bs->inventory[INVENTORY_TELEPORTER] > 0 )
|
|
{
|
|
bs->botinput.actionflags |= ACTION_USE;
|
|
}
|
|
}
|
|
if( bs->inventory[INVENTORY_HEALTH] < 60 )
|
|
{
|
|
if( bs->inventory[INVENTORY_MEDKIT] > 0 )
|
|
{
|
|
//trap_EA_Use(bs->client);
|
|
bs->botinput.actionflags |= ACTION_USE;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
rvmBot::BotFindEnemy
|
|
==================
|
|
*/
|
|
int rvmBot::BotFindEnemy( bot_state_t* bs, int curenemy )
|
|
{
|
|
int i, healthdecrease;
|
|
float f, alertness, easyfragger, vis;
|
|
float squaredist, cursquaredist;
|
|
idPlayer* entinfo = NULL;
|
|
idPlayer* curenemyinfo = NULL;
|
|
idVec3 dir;
|
|
idAngles angles;
|
|
idPlayer* clientEnt;
|
|
|
|
alertness = botCharacterStatsManager.Characteristic_BFloat( bs->character, CHARACTERISTIC_ALERTNESS, 0, 1 );
|
|
easyfragger = botCharacterStatsManager.Characteristic_BFloat( bs->character, CHARACTERISTIC_EASY_FRAGGER, 0, 1 );
|
|
|
|
clientEnt = gameLocal.entities[bs->client]->Cast<idPlayer>();
|
|
|
|
//check if the health decreased
|
|
healthdecrease = bs->lasthealth > bs->inventory[INVENTORY_HEALTH];
|
|
|
|
//remember the current health value
|
|
bs->lasthealth = bs->inventory[INVENTORY_HEALTH];
|
|
//
|
|
if( curenemy >= 0 )
|
|
{
|
|
//BotEntityInfo(curenemy, &curenemyinfo);
|
|
curenemyinfo = gameLocal.entities[curenemy]->Cast<idPlayer>();
|
|
// jmarshall - add flag support.
|
|
//if (EntityCarriesFlag(&curenemyinfo)) return qfalse;
|
|
// jmarshall end
|
|
//VectorSubtract(curenemyinfo->r.currentOrigin, bs->origin, dir);
|
|
dir = curenemyinfo->GetPhysics()->GetOrigin() - bs->origin;
|
|
cursquaredist = dir.LengthSqr();// VectorLengthSquared(dir);
|
|
}
|
|
else
|
|
{
|
|
cursquaredist = 0;
|
|
}
|
|
|
|
for( i = 0; i < MAX_CLIENTS; i++ )
|
|
{
|
|
|
|
if( i == bs->client )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//if it's the current enemy
|
|
if( i == curenemy )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
entinfo = gameLocal.entities[i]->Cast<idPlayer>();
|
|
if( !entinfo )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//if the enemy isn't dead and the enemy isn't the bot self
|
|
if( EntityIsDead( entinfo ) || i == bs->entitynum )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (entinfo->spectating) {
|
|
continue;
|
|
}
|
|
|
|
//if the enemy is invisible and not shooting
|
|
// jmarshall - add invis
|
|
//if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) {
|
|
// continue;
|
|
//}
|
|
// jmarshall end
|
|
|
|
// jmarshall - eval, looks like code to not shoot chatting players or players that just spawned in.
|
|
// do we care about this?
|
|
//if not an easy fragger don't shoot at chatting players
|
|
//if (easyfragger < 0.5 && EntityIsChatting(&entinfo))
|
|
// continue;
|
|
|
|
//
|
|
//if (lastteleport_time > Bot_Time() - 3) {
|
|
// VectorSubtract(entinfo.origin, lastteleport_origin, dir);
|
|
// if (VectorLengthSquared(dir) < Square(70))
|
|
// continue;
|
|
//}
|
|
// jmarshall end
|
|
|
|
//calculate the distance towards the enemy
|
|
idVec3 potentialTargetOrigin = entinfo->GetPhysics()->GetOrigin();
|
|
dir = potentialTargetOrigin - bs->origin;
|
|
squaredist = dir.LengthSqr();
|
|
|
|
// jmarshall
|
|
//if this entity is not carrying a flag
|
|
//if (!EntityCarriesFlag(&entinfo))
|
|
//{
|
|
//if this enemy is further away than the current one
|
|
if( curenemy >= 0 && squaredist > cursquaredist )
|
|
{
|
|
continue;
|
|
}
|
|
//}
|
|
// jmarshall end
|
|
|
|
//if the bot has no
|
|
if( squaredist > Square( 900.0 + alertness * 4000.0 ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// jmarshall - teams!
|
|
//if on the same team
|
|
//if (BotSameTeam(bs, i))
|
|
// continue;
|
|
// jmarshall end
|
|
//if the bot's health decreased or the enemy is shooting
|
|
if( curenemy < 0 && ( healthdecrease || entinfo->IsShooting() ) )
|
|
{
|
|
f = 360;
|
|
}
|
|
else
|
|
{
|
|
f = 90 + 90 - ( 90 - ( squaredist > Square( 810 ) ? Square( 810 ) : squaredist ) / ( 810 * 9 ) );
|
|
}
|
|
//check if the enemy is visible
|
|
|
|
// If we were last hit by someone then assume they are visible.
|
|
if( bs->attackerEntity != entinfo )
|
|
{
|
|
vis = BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, f, i );
|
|
if( vis <= 0 )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
//if the enemy is quite far away, not shooting and the bot is not damaged
|
|
if( curenemy < 0 && squaredist > Square( 100 ) && !healthdecrease && !entinfo->IsShooting() )
|
|
{
|
|
//check if we can avoid this enemy
|
|
VectorSubtract( bs->origin, entinfo->GetOrigin(), dir );
|
|
angles = dir.ToAngles();
|
|
|
|
//if the bot isn't in the fov of the enemy
|
|
if( !clientEnt->CheckFOV( entinfo->GetOrigin() ) )
|
|
{
|
|
//update some stuff for this enemy
|
|
BotUpdateBattleInventory( bs, i );
|
|
|
|
//if the bot doesn't really want to fight
|
|
if( BotWantsToRetreat( bs ) )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
//found an enemy
|
|
bs->enemy = i;//entinfo.number;
|
|
if( curenemy >= 0 )
|
|
{
|
|
bs->enemysight_time = Bot_Time() - 2;
|
|
}
|
|
else
|
|
{
|
|
bs->enemysight_time = Bot_Time();
|
|
}
|
|
bs->enemysuicide = false;
|
|
bs->enemydeath_time = 0;
|
|
bs->enemyvisible_time = Bot_Time();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
rvmBot::BotMoveToGoal
|
|
==================
|
|
*/
|
|
void rvmBot::BotMoveToGoal( bot_state_t* bs, bot_goal_t* goal )
|
|
{
|
|
bs->currentGoal = *goal;
|
|
bs->currentGoal.framenum = gameLocal.framenum;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
rvmBot::BotAimAtEnemy
|
|
==================
|
|
*/
|
|
void rvmBot::BotAimAtEnemy( bot_state_t* bs )
|
|
{
|
|
int i, enemyvisible;
|
|
float dist, f, aim_skill, aim_accuracy, speed, reactiontime;
|
|
idVec3 dir, bestorigin, end, start, groundtarget, cmdmove, enemyvelocity;
|
|
idVec3 mins( -4, -4, -4 );
|
|
idVec3 maxs( 4, 4, 4 );
|
|
weaponinfo_t wi;
|
|
//aas_entityinfo_t entinfo;
|
|
idPlayer* entinfo;
|
|
bot_goal_t goal;
|
|
trace_t trace;
|
|
idVec3 target;
|
|
idPlayer* self;
|
|
|
|
//if the bot has no enemy
|
|
if( bs->enemy < 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
self = gameLocal.entities[bs->entitynum]->Cast<idPlayer>();
|
|
|
|
//get the enemy entity information
|
|
//BotEntityInfo(bs->enemy, &entinfo);
|
|
entinfo = gameLocal.entities[bs->enemy]->Cast<idPlayer>();
|
|
|
|
//if this is not a player (should be an obelisk)
|
|
if( bs->enemy >= MAX_CLIENTS )
|
|
{
|
|
//if the obelisk is visible
|
|
VectorCopy( entinfo->GetOrigin(), target );
|
|
#ifdef MISSIONPACK
|
|
// if attacking an obelisk
|
|
if( bs->enemy == redobelisk.entitynum ||
|
|
bs->enemy == blueobelisk.entitynum )
|
|
{
|
|
target[2] += 32;
|
|
}
|
|
#endif
|
|
//aim at the obelisk
|
|
VectorSubtract( target, bs->eye, dir );
|
|
//vectoangles(dir, bs->viewangles);
|
|
bs->viewangles = dir.ToAngles();
|
|
|
|
//set the aim target before trying to attack
|
|
VectorCopy( target, bs->aimtarget );
|
|
return;
|
|
}
|
|
|
|
//
|
|
//BotAI_Print(PRT_MESSAGE, "client %d: aiming at client %d\n", bs->entitynum, bs->enemy);
|
|
//
|
|
aim_skill = botCharacterStatsManager.Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_SKILL, 0, 1 );
|
|
aim_accuracy = botCharacterStatsManager.Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_ACCURACY, 0, 1 );
|
|
//
|
|
if( aim_skill > 0.95 )
|
|
{
|
|
//don't aim too early
|
|
reactiontime = 0.5 * botCharacterStatsManager.Characteristic_BFloat( bs->character, CHARACTERISTIC_REACTIONTIME, 0, 1 );
|
|
if( bs->enemysight_time > Bot_Time() - reactiontime )
|
|
{
|
|
return;
|
|
}
|
|
if( bs->teleport_time > Bot_Time() - reactiontime )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
//get the weapon information
|
|
botWeaponInfoManager.BotGetWeaponInfo( bs->ws, bs->weaponnum, &wi );
|
|
|
|
//get the weapon specific aim accuracy and or aim skill
|
|
if( wi.number == WP_MACHINEGUN )
|
|
{
|
|
aim_accuracy = botCharacterStatsManager.Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_ACCURACY_MACHINEGUN, 0, 1 );
|
|
}
|
|
else if( wi.number == WP_SHOTGUN )
|
|
{
|
|
aim_accuracy = botCharacterStatsManager.Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_ACCURACY_SHOTGUN, 0, 1 );
|
|
}
|
|
//else if (wi.number == WP_GRENADE_LAUNCHER) {
|
|
// aim_accuracy = Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_GRENADELAUNCHER, 0, 1);
|
|
// aim_skill = Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_GRENADELAUNCHER, 0, 1);
|
|
//}
|
|
else if( wi.number == WP_ROCKET_LAUNCHER )
|
|
{
|
|
aim_accuracy = botCharacterStatsManager.Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_ACCURACY_ROCKETLAUNCHER, 0, 1 );
|
|
aim_skill = botCharacterStatsManager.Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_SKILL_ROCKETLAUNCHER, 0, 1 );
|
|
}
|
|
//else if (wi.number == WP_LIGHTNING) {
|
|
// aim_accuracy = Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_LIGHTNING, 0, 1);
|
|
//}
|
|
//else if (wi.number == WP_RAILGUN) {
|
|
// aim_accuracy = Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_RAILGUN, 0, 1);
|
|
//}
|
|
else if( wi.number == WP_PLASMAGUN )
|
|
{
|
|
aim_accuracy = botCharacterStatsManager.Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_ACCURACY_PLASMAGUN, 0, 1 );
|
|
aim_skill = botCharacterStatsManager.Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_SKILL_PLASMAGUN, 0, 1 );
|
|
}
|
|
//else if (wi.number == WP_BFG) {
|
|
// aim_accuracy = Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_BFG10K, 0, 1);
|
|
// aim_skill = Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_BFG10K, 0, 1);
|
|
//}
|
|
//
|
|
if( aim_accuracy <= 0 )
|
|
{
|
|
aim_accuracy = 0.0001f;
|
|
}
|
|
//get the enemy entity information
|
|
//BotEntityInfo(bs->enemy, &entinfo);
|
|
entinfo = gameLocal.entities[bs->enemy]->Cast<idPlayer>();
|
|
|
|
//if the enemy is invisible then shoot crappy most of the time
|
|
//if (entinfo->IsInvisible()) {
|
|
// if (rvmBotUtil::random() > 0.1)
|
|
// aim_accuracy *= 0.4f;
|
|
//}
|
|
// jmarshall - fix aim accuracy.
|
|
//VectorSubtract(entinfo->GetOrigin(), entinfo.lastvisorigin, enemyvelocity);
|
|
//VectorScale(enemyvelocity, 1 / entinfo.update_time, enemyvelocity);
|
|
////enemy origin and velocity is remembered every 0.5 seconds
|
|
//if (bs->enemyposition_time < Bot_Time()) {
|
|
// //
|
|
// bs->enemyposition_time = Bot_Time() + 0.5;
|
|
// VectorCopy(enemyvelocity, bs->enemyvelocity);
|
|
// VectorCopy(entinfo.origin, bs->enemyorigin);
|
|
//}
|
|
////if not extremely skilled
|
|
//if (aim_skill < 0.9) {
|
|
// VectorSubtract(entinfo.origin, bs->enemyorigin, dir);
|
|
// //if the enemy moved a bit
|
|
// if (VectorLengthSquared(dir) > Square(48)) {
|
|
// //if the enemy changed direction
|
|
// if (DotProduct(bs->enemyvelocity, enemyvelocity) < 0) {
|
|
// //aim accuracy should be worse now
|
|
// aim_accuracy *= 0.7f;
|
|
// }
|
|
// }
|
|
//}
|
|
// jmarshall end
|
|
|
|
//check visibility of enemy
|
|
enemyvisible = BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy );
|
|
|
|
//if the enemy is visible
|
|
if( enemyvisible )
|
|
{
|
|
//
|
|
VectorCopy( entinfo->GetOrigin(), bestorigin );
|
|
bestorigin[2] += 8;
|
|
//get the start point shooting from
|
|
//NOTE: the x and y projectile start offsets are ignored
|
|
VectorCopy( bs->origin, start );
|
|
start[2] += self->GetViewHeight();// bs->cur_ps.viewheight;
|
|
start[2] += wi.offset[2];
|
|
//
|
|
//trap_Trace(&trace, start, mins, maxs, bestorigin, bs->entitynum, MASK_SHOT);
|
|
gameLocal.Trace( trace, start, bestorigin, MASK_SHOT, bs->entitynum );
|
|
//if the enemy is NOT hit
|
|
if( trace.fraction <= 1 && trace.c.entityNum != bs->enemy )
|
|
{
|
|
bestorigin[2] += 16;
|
|
}
|
|
//if it is not an instant hit weapon the bot might want to predict the enemy
|
|
if( wi.speed )
|
|
{
|
|
//
|
|
VectorSubtract( bestorigin, bs->origin, dir );
|
|
dist = dir.Length();
|
|
VectorSubtract( entinfo->GetOrigin(), bs->enemyorigin, dir );
|
|
//if the enemy is NOT pretty far away and strafing just small steps left and right
|
|
if( !( dist > 100 && dir.LengthSqr() < Square( 32 ) ) )
|
|
{
|
|
//if skilled anough do exact prediction
|
|
if( aim_skill > 0.8 &&
|
|
//if the weapon is ready to fire
|
|
!self->IsShooting() )
|
|
{
|
|
//aas_clientmove_t move;
|
|
idVec3 origin;
|
|
idVec3 last_enemy_visible_position;
|
|
VectorCopy( last_enemy_visible_position, bs->last_enemy_visible_position );
|
|
last_enemy_visible_position[2] += 20.0f;
|
|
|
|
//
|
|
VectorSubtract( entinfo->GetOrigin(), bs->origin, dir );
|
|
|
|
////distance towards the enemy
|
|
//dist = VectorLength(dir);
|
|
////direction the enemy is moving in
|
|
VectorSubtract( entinfo->GetOrigin(), last_enemy_visible_position, dir );
|
|
////
|
|
//VectorScale(dir, 1 / entinfo->update_time, dir);
|
|
////
|
|
//VectorCopy(entinfo->origin, origin);
|
|
//origin[2] += 1;
|
|
|
|
////
|
|
//VectorClear(cmdmove);
|
|
////AAS_ClearShownDebugLines();
|
|
//trap_AAS_PredictClientMovement(&move, bs->enemy, origin,
|
|
// PRESENCE_CROUCH, qfalse,
|
|
// dir, cmdmove, 0,
|
|
// dist * 10 / wi.speed, 0.1f, 0, 0, qfalse);
|
|
//VectorCopy(move.endpos, bestorigin);
|
|
//BotAI_Print(PRT_MESSAGE, "%1.1f predicted speed = %f, frames = %f\n", Bot_Time(), VectorLength(dir), dist * 10 / wi.speed);
|
|
|
|
bot_goal_t goal;
|
|
VectorMA( entinfo->GetOrigin(), 30, dir, goal.origin );
|
|
BotMoveToGoal( bs, &goal );
|
|
}
|
|
//if not that skilled do linear prediction
|
|
else if( aim_skill > 0.4 )
|
|
{
|
|
// jmarshall - fix linear prediction.
|
|
idVec3 last_enemy_visible_position;
|
|
VectorCopy( last_enemy_visible_position, bs->last_enemy_visible_position );
|
|
last_enemy_visible_position[2] += 20.0f;
|
|
|
|
//VectorSubtract(entinfo->GetOrigin(), bs->origin, dir);
|
|
////distance towards the enemy
|
|
//dist = VectorLength(dir);
|
|
////direction the enemy is moving in
|
|
VectorSubtract( entinfo->GetOrigin(), last_enemy_visible_position, dir );
|
|
//dir[2] = 0;
|
|
////
|
|
//speed = VectorNormalize(dir) / entinfo.update_time;
|
|
////botimport.Print(PRT_MESSAGE, "speed = %f, wi->speed = %f\n", speed, wi->speed);
|
|
////best spot to aim at
|
|
//VectorMA(entinfo.origin, (dist / wi.speed) * speed, dir, bestorigin);
|
|
bot_goal_t goal;
|
|
VectorMA( entinfo->GetOrigin(), 30, dir, goal.origin );
|
|
BotMoveToGoal( bs, &goal );
|
|
// jmarshall end
|
|
}
|
|
}
|
|
}
|
|
//if the projectile does radial damage
|
|
if( aim_skill > 0.6 && wi.proj.damagetype & DAMAGETYPE_RADIAL )
|
|
{
|
|
//if the enemy isn't standing significantly higher than the bot
|
|
if( entinfo->GetOrigin()[2] < bs->origin[2] + 16 )
|
|
{
|
|
//try to aim at the ground in front of the enemy
|
|
VectorCopy( entinfo->GetOrigin(), end );
|
|
end[2] -= 64;
|
|
//trap_Trace(&trace, entinfo->GetOrigin(), NULL, NULL, end, bs->enemy, MASK_SHOT);
|
|
gameLocal.Trace( trace, entinfo->GetOrigin(), end, MASK_SHOT, bs->enemy );
|
|
//
|
|
VectorCopy( bestorigin, groundtarget );
|
|
// jmarshall - add start solid
|
|
//if (trace.startsolid)
|
|
// groundtarget[2] = entinfo->GetOrigin()[2] - 16;
|
|
//else
|
|
groundtarget[2] = trace.endpos[2] - 8;
|
|
// jmarshall end
|
|
//trace a line from projectile start to ground target
|
|
//trap_Trace(&trace, start, NULL, NULL, groundtarget, bs->entitynum, MASK_SHOT);
|
|
gameLocal.Trace( trace, start, groundtarget, MASK_SHOT, bs->entitynum );
|
|
//if hitpoint is not vertically too far from the ground target
|
|
if( idMath::Fabs( trace.endpos[2] - groundtarget[2] ) < 50 )
|
|
{
|
|
VectorSubtract( trace.endpos, groundtarget, dir );
|
|
//if the hitpoint is near anough the ground target
|
|
if( dir.LengthSqr() < Square( 60 ) )
|
|
{
|
|
VectorSubtract( trace.endpos, start, dir );
|
|
//if the hitpoint is far anough from the bot
|
|
if( dir.LengthSqr() > Square( 100 ) )
|
|
{
|
|
//check if the bot is visible from the ground target
|
|
trace.endpos[2] += 1;
|
|
//trap_Trace(&trace, trace.endpos, NULL, NULL, entinfo->GetOrigin(), bs->enemy, MASK_SHOT);
|
|
gameLocal.Trace( trace, trace.endpos, entinfo->GetOrigin(), MASK_SHOT, bs->enemy );
|
|
if( trace.fraction >= 1 )
|
|
{
|
|
//botimport.Print(PRT_MESSAGE, "%1.1f aiming at ground\n", AAS_Time());
|
|
VectorCopy( groundtarget, bestorigin );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
bestorigin[0] += 20 * rvmBotUtil::crandom() * ( 1 - aim_accuracy );
|
|
bestorigin[1] += 20 * rvmBotUtil::crandom() * ( 1 - aim_accuracy );
|
|
bestorigin[2] += 10 * rvmBotUtil::crandom() * ( 1 - aim_accuracy );
|
|
}
|
|
else
|
|
{
|
|
//
|
|
VectorCopy( bs->lastenemyorigin, bestorigin );
|
|
bestorigin[2] += 8;
|
|
// jmarshall - fix this.
|
|
//if the bot is skilled anough
|
|
//if (aim_skill > 0.5) {
|
|
// //do prediction shots around corners
|
|
// if (wi.number == WP_BFG ||
|
|
// wi.number == WP_ROCKET_LAUNCHER ||
|
|
// wi.number == WP_GRENADE_LAUNCHER) {
|
|
// //create the chase goal
|
|
// goal.entitynum = bs->client;
|
|
// goal.areanum = bs->areanum;
|
|
// VectorCopy(bs->eye, goal.origin);
|
|
// VectorSet(goal.mins, -8, -8, -8);
|
|
// VectorSet(goal.maxs, 8, 8, 8);
|
|
// //
|
|
// if (trap_BotPredictVisiblePosition(bs->lastenemyorigin, bs->lastenemyareanum, &goal, TFL_DEFAULT, target)) {
|
|
// VectorSubtract(target, bs->eye, dir);
|
|
// if (VectorLengthSquared(dir) > Square(80)) {
|
|
// VectorCopy(target, bestorigin);
|
|
// bestorigin[2] -= 20;
|
|
// }
|
|
// }
|
|
// aim_accuracy = 1;
|
|
// }
|
|
//}
|
|
// jmarshall end
|
|
}
|
|
//
|
|
if( enemyvisible )
|
|
{
|
|
//trap_Trace(&trace, bs->eye, NULL, NULL, bestorigin, bs->entitynum, MASK_SHOT);
|
|
gameLocal.Trace( trace, bs->eye, bestorigin, MASK_SHOT, bs->entitynum );
|
|
VectorCopy( trace.endpos, bs->aimtarget );
|
|
}
|
|
else
|
|
{
|
|
VectorCopy( bestorigin, bs->aimtarget );
|
|
}
|
|
//get aim direction
|
|
VectorSubtract( bestorigin, bs->eye, dir );
|
|
//
|
|
if( wi.number == WP_MACHINEGUN ||
|
|
wi.number == WP_SHOTGUN /*|| // jmarshall add lighting railgun.
|
|
wi.number == WP_LIGHTNING ||
|
|
wi.number == WP_RAILGUN*/ )
|
|
{
|
|
//distance towards the enemy
|
|
dist = dir.Length();// VectorLength(dir);
|
|
if( dist > 150 )
|
|
{
|
|
dist = 150;
|
|
}
|
|
f = 0.6 + dist / 150 * 0.4;
|
|
aim_accuracy *= f;
|
|
}
|
|
//add some random stuff to the aim direction depending on the aim accuracy
|
|
if( aim_accuracy < 0.8 )
|
|
{
|
|
dir.Normalize();
|
|
for( i = 0; i < 3; i++ )
|
|
{
|
|
dir[i] += 0.3 * rvmBotUtil::crandom() * ( 1 - aim_accuracy );
|
|
}
|
|
}
|
|
//set the ideal view angles
|
|
//vectoangles(dir, bs->viewangles);
|
|
bs->viewangles = dir.ToAngles();
|
|
|
|
//take the weapon spread into account for lower skilled bots
|
|
bs->viewangles[PITCH] += 6 * wi.vspread * rvmBotUtil::crandom() * ( 1 - aim_accuracy );
|
|
bs->viewangles[PITCH] = idMath::AngleMod( bs->viewangles[PITCH] );
|
|
bs->viewangles[YAW] += 6 * wi.hspread * rvmBotUtil::crandom() * ( 1 - aim_accuracy );
|
|
bs->viewangles[YAW] = idMath::AngleMod( bs->viewangles[YAW] );
|
|
// jmarshall - add bot_challenge.
|
|
//if the bots should be really challenging
|
|
//if (bot_challenge.integer) {
|
|
// //if the bot is really accurate and has the enemy in view for some time
|
|
// if (aim_accuracy > 0.9 && bs->enemysight_time < Bot_Time() - 1) {
|
|
// //set the view angles directly
|
|
// if (bs->ideal_viewangles[PITCH] > 180) bs->ideal_viewangles[PITCH] -= 360;
|
|
// VectorCopy(bs->ideal_viewangles, bs->viewangles);
|
|
// trap_EA_View(bs->client, bs->viewangles);
|
|
// }
|
|
//}
|
|
//vectoangles(bi->dir, bi->viewangles);
|
|
// jmarshall end
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
rvmBot::BotWantsToChase
|
|
==================
|
|
*/
|
|
bool rvmBot::BotWantsToChase( bot_state_t* bs )
|
|
{
|
|
if( BotAggression( bs ) > 50 )
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
==================
|
|
rvmBot::BotNearbyGoal
|
|
==================
|
|
*/
|
|
int rvmBot::BotNearbyGoal( bot_state_t* bs, int tfl, bot_goal_t* ltg, float range )
|
|
{
|
|
int ret;
|
|
|
|
// jmarshall - check for air.
|
|
//check if the bot should go for air
|
|
//if (BotGoForAir(bs, tfl, ltg, range)) return qtrue;
|
|
////if the bot is carrying the enemy flag
|
|
//if (BotCTFCarryingFlag(bs)) {
|
|
// //if the bot is just a few secs away from the base
|
|
// if (trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin,
|
|
// bs->teamgoal.areanum, TFL_DEFAULT) < 300) {
|
|
// //make the range really small
|
|
// range = 50;
|
|
// }
|
|
//}
|
|
// jmarshall end
|
|
|
|
//
|
|
ret = botGoalManager.BotChooseNBGItem( bs->gs, bs->origin, bs->inventory, tfl, ltg, range );
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*
|
|
=======================
|
|
rvmBot::BotGetRandomPointNearPosition
|
|
=======================
|
|
*/
|
|
void rvmBot::BotGetRandomPointNearPosition(const idVec3 point, idVec3& randomPoint, float radius) {
|
|
randomPoint = point;
|
|
|
|
idAAS* aas = this->aas;
|
|
if (!aas) {
|
|
return;
|
|
}
|
|
|
|
const int areaNum = aas->PointAreaNum(point);
|
|
if (areaNum <= 0) {
|
|
return;
|
|
}
|
|
|
|
if (radius <= 0.0f) {
|
|
randomPoint = aas->AreaCenter(areaNum);
|
|
aas->PushPointIntoAreaNum(areaNum, randomPoint);
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < 16; i++) {
|
|
const float angle = gameLocal.random.RandomFloat() * idMath::TWO_PI;
|
|
const float dist = gameLocal.random.RandomFloat() * radius;
|
|
|
|
idVec3 testPoint = point;
|
|
testPoint.x += idMath::Cos(angle) * dist;
|
|
testPoint.y += idMath::Sin(angle) * dist;
|
|
|
|
const int testAreaNum = aas->PointAreaNum(testPoint);
|
|
if (testAreaNum <= 0) {
|
|
continue;
|
|
}
|
|
|
|
aas->PushPointIntoAreaNum(testAreaNum, testPoint);
|
|
|
|
randomPoint = testPoint;
|
|
return;
|
|
}
|
|
|
|
randomPoint = aas->AreaCenter(areaNum);
|
|
aas->PushPointIntoAreaNum(areaNum, randomPoint);
|
|
}
|
|
|
|
/*
|
|
=======================
|
|
rvmBot::BotMoveInRandomDirection
|
|
=======================
|
|
*/
|
|
int rvmBot::BotMoveInRandomDirection( bot_state_t* bs )
|
|
{
|
|
rvmBot* ent = gameLocal.entities[bs->client]->Cast<rvmBot>();
|
|
|
|
//ent->ResetPathFinding();
|
|
|
|
float dist = idMath::Distance( ent->GetPhysics()->GetOrigin(), bs->random_move_position );
|
|
|
|
if( !ent->PointVisible( bs->random_move_position ) )
|
|
{
|
|
dist = 0;
|
|
}
|
|
|
|
if( dist < 25 || bs->random_move_position.Length() == 0 )
|
|
{
|
|
BotGetRandomPointNearPosition( ent->GetPhysics()->GetOrigin(), bs->random_move_position, 50.0f );
|
|
}
|
|
|
|
VectorSubtract( bs->random_move_position, ent->GetPhysics()->GetOrigin(), bs->botinput.dir );
|
|
|
|
idAngles ang( 0, bs->botinput.dir.ToYaw(), 0 );
|
|
bs->botinput.speed = pm_runspeed.GetInteger();
|
|
bs->botinput.dir.Normalize();
|
|
bs->useRandomPosition = true;
|
|
bs->botinput.speed = 400; // 200 = walk, 400 = run.
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
============
|
|
rvmBot::ShowHideArea
|
|
============
|
|
*/
|
|
void rvmBot::MoveToCoverPoint(void)
|
|
{
|
|
int areaNum, numObstacles;
|
|
idVec3 target;
|
|
aasGoal_t goal;
|
|
aasObstacle_t obstacles[10];
|
|
idVec3 origin = GetOrigin();
|
|
idAAS* aas = gameLocal.GetBotAAS();
|
|
|
|
areaNum = gameLocal.GetBotAAS()->PointReachableAreaNum(origin, aas->DefaultSearchBounds(), (AREA_REACHABLE_WALK | AREA_REACHABLE_FLY));
|
|
target = aas->AreaCenter(aas->PointAreaNum(gameLocal.GetLocalPlayer()->GetOrigin()));
|
|
|
|
// consider the target an obstacle
|
|
obstacles[0].absBounds = idBounds(target).Expand(16);
|
|
numObstacles = 1;
|
|
|
|
idAASCallback_FindCoverArea findCover(target);
|
|
if (aas->FindNearestGoal(goal, areaNum, origin, target, TFL_WALK | TFL_AIR, obstacles, numObstacles, findCover))
|
|
{
|
|
bs.currentGoal.origin = goal.origin;
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
rvmBot::BotCheckAttack
|
|
==================
|
|
*/
|
|
void rvmBot::BotCheckAttack( bot_state_t* bs )
|
|
{
|
|
float points, reactiontime, fov, firethrottle;
|
|
int attackentity;
|
|
trace_t bsptrace;
|
|
//float selfpreservation;
|
|
idVec3 forward, right, start, end, dir;
|
|
idAngles angles;
|
|
weaponinfo_t wi;
|
|
trace_t trace;
|
|
//aas_entityinfo_t entinfo;
|
|
idEntity* entinfo;
|
|
idVec3 mins( -8, -8, -8 );
|
|
idVec3 maxs( 8, 8, 8 );
|
|
idPlayer* self;
|
|
|
|
attackentity = bs->enemy;
|
|
//
|
|
//BotEntityInfo(attackentity, &entinfo);
|
|
entinfo = gameLocal.entities[attackentity];
|
|
|
|
self = gameLocal.entities[bs->client]->Cast<idPlayer>();
|
|
|
|
//
|
|
reactiontime = botCharacterStatsManager.Characteristic_BFloat( bs->character, CHARACTERISTIC_REACTIONTIME, 0, 1 );
|
|
if( bs->enemysight_time > Bot_Time() - reactiontime )
|
|
{
|
|
return;
|
|
}
|
|
if( bs->teleport_time > Bot_Time() - reactiontime )
|
|
{
|
|
return;
|
|
}
|
|
|
|
//if changing weapons
|
|
if( bs->weaponchange_time > Bot_Time() - 0.1 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
//check fire throttle characteristic
|
|
if( bs->firethrottlewait_time > Bot_Time() )
|
|
{
|
|
return;
|
|
}
|
|
// jmarshall - this wasn't original behaivor, but multiplying it by 40 feels better for gameplay
|
|
firethrottle = botCharacterStatsManager.Characteristic_BFloat( bs->character, CHARACTERISTIC_FIRETHROTTLE, 0, 1 ) * 40.0f;
|
|
// jmarshall end
|
|
if( bs->firethrottleshoot_time < Bot_Time() )
|
|
{
|
|
if( rvmBotUtil::random() > firethrottle )
|
|
{
|
|
bs->firethrottlewait_time = Bot_Time() + firethrottle;
|
|
bs->firethrottleshoot_time = 0;
|
|
}
|
|
else
|
|
{
|
|
bs->firethrottleshoot_time = Bot_Time() + 1 - firethrottle;
|
|
bs->firethrottlewait_time = 0;
|
|
}
|
|
}
|
|
|
|
VectorSubtract( bs->aimtarget, bs->eye, dir );
|
|
|
|
// jmarshall - add gauntlet
|
|
//if (bs->weaponnum == WP_GAUNTLET) {
|
|
// if (dir.LengthSqr() > Square(60)) {
|
|
// return;
|
|
// }
|
|
//}
|
|
// jmarshall end
|
|
if( dir.LengthSqr() < Square( 100 ) )
|
|
{
|
|
fov = 120;
|
|
}
|
|
else
|
|
{
|
|
fov = 50;
|
|
}
|
|
|
|
//vectoangles(dir, angles);
|
|
angles = dir.ToAngles();
|
|
if( !self->CheckFOV( entinfo->GetPhysics()->GetOrigin() ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
//trap_Trace(&bsptrace, bs->eye, NULL, NULL, bs->aimtarget, bs->client, CONTENTS_SOLID | CONTENTS_PLAYERCLIP);
|
|
gameLocal.Trace( bsptrace, bs->eye, bs->aimtarget, CONTENTS_SOLID | CONTENTS_PLAYERCLIP, bs->client );
|
|
|
|
if( bsptrace.fraction < 1 && bsptrace.c.entityNum != attackentity )
|
|
{
|
|
return;
|
|
}
|
|
|
|
//get the weapon info
|
|
botWeaponInfoManager.BotGetWeaponInfo( bs->ws, bs->weaponnum, &wi );
|
|
|
|
//get the start point shooting from
|
|
VectorCopy( bs->origin, start );
|
|
start[2] += self->GetViewHeight();// bs->cur_ps.viewheight;
|
|
bs->viewangles.ToVectors( &forward, &right, NULL );
|
|
|
|
start[0] += forward[0] * wi.offset[0] + right[0] * wi.offset[1];
|
|
start[1] += forward[1] * wi.offset[0] + right[1] * wi.offset[1];
|
|
start[2] += forward[2] * wi.offset[0] + right[2] * wi.offset[1] + wi.offset[2];
|
|
//end point aiming at
|
|
VectorMA( start, 1000, forward, end );
|
|
//a little back to make sure not inside a very close enemy
|
|
VectorMA( start, -12, forward, start );
|
|
//trap_Trace(&trace, start, mins, maxs, end, bs->entitynum, MASK_SHOT);
|
|
gameLocal.Trace( trace, start, end, MASK_SHOT, bs->entitynum );
|
|
|
|
//if the entity is a client
|
|
if( trace.c.entityNum > 0 && trace.c.entityNum <= MAX_CLIENTS )
|
|
{
|
|
if( trace.c.entityNum != attackentity )
|
|
{
|
|
// jmarshall - teams
|
|
//if a teammate is hit
|
|
//if (BotSameTeam(bs, trace.entityNum))
|
|
// return;
|
|
// jmarshall end
|
|
}
|
|
}
|
|
//if won't hit the enemy or not attacking a player (obelisk)
|
|
if( trace.c.entityNum != attackentity || attackentity >= MAX_CLIENTS )
|
|
{
|
|
//if the projectile does radial damage
|
|
if( wi.proj.damagetype & DAMAGETYPE_RADIAL )
|
|
{
|
|
if( trace.fraction * 1000 < wi.proj.radius )
|
|
{
|
|
points = ( wi.proj.damage - 0.5 * trace.fraction * 1000 ) * 0.5;
|
|
if( points > 0 )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
//FIXME: check if a teammate gets radial damage
|
|
}
|
|
}
|
|
//if fire has to be release to activate weapon
|
|
if( wi.flags & WFL_FIRERELEASED )
|
|
{
|
|
if( bs->flags & BFL_ATTACKED )
|
|
{
|
|
//trap_EA_Attack(bs->client);
|
|
bs->botinput.actionflags |= ACTION_ATTACK;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//trap_EA_Attack(bs->client);
|
|
bs->botinput.actionflags |= ACTION_ATTACK;
|
|
}
|
|
bs->flags ^= BFL_ATTACKED;
|
|
}
|