2016-03-01 15:47:10 +00:00
/*******************************
* B_spawn . c *
* Description : *
* various procedures that the *
* bot need to work *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# include <stdlib.h>
# include "doomtype.h"
# include "doomdef.h"
# include "doomstat.h"
# include "p_local.h"
# include "p_maputl.h"
# include "b_bot.h"
# include "g_game.h"
# include "m_random.h"
# include "r_sky.h"
# include "st_stuff.h"
# include "stats.h"
# include "i_system.h"
# include "s_sound.h"
# include "d_event.h"
# include "d_player.h"
# include "p_spec.h"
# include "p_checkposition.h"
static FRandom pr_botdofire ( " BotDoFire " ) ;
//Checks TRUE reachability from bot to a looker.
bool DBot : : Reachable ( AActor * rtarget )
{
if ( player - > mo = = rtarget )
return false ;
2016-03-30 07:41:46 +00:00
if ( ( rtarget - > Sector - > ceilingplane . ZatPoint ( rtarget ) -
rtarget - > Sector - > floorplane . ZatPoint ( rtarget ) )
2016-03-25 23:34:56 +00:00
< player - > mo - > Height ) //Where rtarget is, player->mo can't be.
2016-03-01 15:47:10 +00:00
return false ;
sector_t * last_s = player - > mo - > Sector ;
2016-03-30 07:41:46 +00:00
double last_z = last_s - > floorplane . ZatPoint ( player - > mo ) ;
2016-03-25 23:34:56 +00:00
double estimated_dist = player - > mo - > Distance2D ( rtarget ) ;
2016-03-01 15:47:10 +00:00
bool reachable = true ;
2016-03-25 23:34:56 +00:00
FPathTraverse it ( player - > mo - > X ( ) + player - > mo - > Vel . X , player - > mo - > Y ( ) + player - > mo - > Vel . Y , rtarget - > X ( ) , rtarget - > Y ( ) , PT_ADDLINES | PT_ADDTHINGS ) ;
2016-03-01 15:47:10 +00:00
intercept_t * in ;
while ( ( in = it . Next ( ) ) )
{
2016-03-25 23:34:56 +00:00
double hitx , hity ;
double frac ;
2016-03-01 15:47:10 +00:00
line_t * line ;
AActor * thing ;
2016-03-25 23:34:56 +00:00
double dist ;
2016-03-01 15:47:10 +00:00
sector_t * s ;
2016-03-31 14:52:25 +00:00
frac = in - > frac - 4 / MAX_TRAVERSE_DIST ;
2016-03-25 23:34:56 +00:00
dist = frac * MAX_TRAVERSE_DIST ;
2016-03-01 15:47:10 +00:00
2016-03-25 23:34:56 +00:00
hitx = it . Trace ( ) . x + player - > mo - > Vel . X * frac ;
hity = it . Trace ( ) . y + player - > mo - > Vel . Y * frac ;
2016-03-01 15:47:10 +00:00
if ( in - > isaline )
{
line = in - > d . line ;
if ( ! ( line - > flags & ML_TWOSIDED ) | | ( line - > flags & ( ML_BLOCKING | ML_BLOCKEVERYTHING | ML_BLOCK_PLAYERS ) ) )
{
return false ; //Cannot continue.
}
else
{
//Determine if going to use backsector/frontsector.
s = ( line - > backsector = = last_s ) ? line - > frontsector : line - > backsector ;
2016-03-25 23:34:56 +00:00
double ceilingheight = s - > ceilingplane . ZatPoint ( hitx , hity ) ;
double floorheight = s - > floorplane . ZatPoint ( hitx , hity ) ;
2016-03-01 15:47:10 +00:00
if ( ! bglobal . IsDangerous ( s ) & & //Any nukage/lava?
( floorheight < = ( last_z + MAXMOVEHEIGHT )
& & ( ( ceilingheight = = floorheight & & line - > special )
2016-03-25 23:34:56 +00:00
| | ( ceilingheight - floorheight ) > = player - > mo - > Height ) ) ) //Does it fit?
2016-03-01 15:47:10 +00:00
{
last_z = floorheight ;
last_s = s ;
continue ;
}
else
{
return false ;
}
}
}
if ( dist > estimated_dist )
{
return true ;
}
thing = in - > d . thing ;
if ( thing = = player - > mo ) //Can't reach self in this case.
continue ;
2016-03-30 07:41:46 +00:00
if ( thing = = rtarget & & ( rtarget - > Sector - > floorplane . ZatPoint ( rtarget ) < = ( last_z + MAXMOVEHEIGHT ) ) )
2016-03-01 15:47:10 +00:00
{
return true ;
}
reachable = false ;
}
return reachable ;
}
//doesnt check LOS, checks visibility with a set view angle.
//B_Checksight checks LOS (straight line)
//----------------------------------------------------------------------
//Check if mo1 has free line to mo2
//and if mo2 is within mo1 viewangle (vangle) given with normal degrees.
//if these conditions are true, the function returns true.
//GOOD TO KNOW is that the player's view angle
//in doom is 90 degrees infront.
2016-03-25 23:34:56 +00:00
bool DBot : : Check_LOS ( AActor * to , DAngle vangle )
2016-03-01 15:47:10 +00:00
{
if ( ! P_CheckSight ( player - > mo , to , SF_SEEPASTBLOCKEVERYTHING ) )
return false ; // out of sight
2016-03-25 23:34:56 +00:00
if ( vangle > = 360. )
2016-03-01 15:47:10 +00:00
return true ;
if ( vangle = = 0 )
return false ; //Looker seems to be blind.
2016-03-25 23:34:56 +00:00
return absangle ( player - > mo - > AngleTo ( to ) , player - > mo - > Angles . Yaw ) < = ( vangle / 2 ) ;
2016-03-01 15:47:10 +00:00
}
//-------------------------------------
//Bot_Dofire()
//-------------------------------------
//The bot will check if it's time to fire
//and do so if that is the case.
void DBot : : Dofire ( ticcmd_t * cmd )
{
bool no_fire ; //used to prevent bot from pumping rockets into nearby walls.
int aiming_penalty = 0 ; //For shooting at shading target, if screen is red, MAKEME: When screen red.
int aiming_value ; //The final aiming value.
2016-03-25 23:34:56 +00:00
double Dist ;
DAngle an ;
DAngle m ;
2016-03-23 09:42:41 +00:00
double fm ;
2016-03-01 15:47:10 +00:00
if ( ! enemy | | ! ( enemy - > flags & MF_SHOOTABLE ) | | enemy - > health < = 0 )
return ;
if ( player - > ReadyWeapon = = NULL )
return ;
if ( player - > damagecount > skill . isp )
{
first_shot = true ;
return ;
}
//Reaction skill thing.
if ( first_shot & &
! ( player - > ReadyWeapon - > WeaponFlags & WIF_BOT_REACTION_SKILL_THING ) )
{
t_react = ( 100 - skill . reaction + 1 ) / ( ( pr_botdofire ( ) % 3 ) + 3 ) ;
}
first_shot = false ;
if ( t_react )
return ;
//MAKEME: Decrease the rocket suicides even more.
no_fire = true ;
//Distance to enemy.
2016-03-25 23:34:56 +00:00
Dist = player - > mo - > Distance2D ( enemy , player - > mo - > Vel . X - enemy - > Vel . X , player - > mo - > Vel . Y - enemy - > Vel . Y ) ;
2016-03-01 15:47:10 +00:00
//FIRE EACH TYPE OF WEAPON DIFFERENT: Here should all the different weapons go.
if ( player - > ReadyWeapon - > WeaponFlags & WIF_MELEEWEAPON )
{
if ( ( player - > ReadyWeapon - > ProjectileType ! = NULL ) )
{
if ( player - > ReadyWeapon - > CheckAmmo ( AWeapon : : PrimaryFire , false , true ) )
{
// This weapon can fire a projectile and has enough ammo to do so
goto shootmissile ;
}
else if ( ! ( player - > ReadyWeapon - > WeaponFlags & WIF_AMMO_OPTIONAL ) )
{
// Ammo is required, so don't shoot. This is for weapons that shoot
// missiles that die at close range, such as the powered-up Phoneix Rod.
return ;
}
}
else
{
//*4 is for atmosphere, the chainsaws sounding and all..
2016-12-10 15:35:48 +00:00
no_fire = ( Dist > DEFMELEERANGE * 4 ) ;
2016-03-01 15:47:10 +00:00
}
}
else if ( player - > ReadyWeapon - > WeaponFlags & WIF_BOT_BFG )
{
//MAKEME: This should be smarter.
if ( ( pr_botdofire ( ) % 200 ) < = skill . reaction )
if ( Check_LOS ( enemy , SHOOTFOV ) )
no_fire = false ;
}
else if ( player - > ReadyWeapon - > ProjectileType ! = NULL )
{
if ( player - > ReadyWeapon - > WeaponFlags & WIF_BOT_EXPLOSIVE )
{
//Special rules for RL
an = FireRox ( enemy , cmd ) ;
2016-03-25 23:34:56 +00:00
if ( an ! = 0 )
2016-03-01 15:47:10 +00:00
{
2016-03-25 23:34:56 +00:00
Angle = an ;
2016-03-01 15:47:10 +00:00
//have to be somewhat precise. to avoid suicide.
2016-03-25 23:34:56 +00:00
if ( absangle ( an , player - > mo - > Angles . Yaw ) < 12. )
2016-03-01 15:47:10 +00:00
{
t_rocket = 9 ;
no_fire = false ;
}
}
}
// prediction aiming
shootmissile :
2016-03-25 23:34:56 +00:00
Dist = player - > mo - > Distance2D ( enemy ) ;
fm = Dist / GetDefaultByType ( player - > ReadyWeapon - > ProjectileType ) - > Speed ;
2016-03-23 09:42:41 +00:00
bglobal . SetBodyAt ( enemy - > Pos ( ) + enemy - > Vel . XY ( ) * fm * 2 , 1 ) ;
2016-03-25 23:34:56 +00:00
Angle = player - > mo - > AngleTo ( bglobal . body1 ) ;
2016-03-01 15:47:10 +00:00
if ( Check_LOS ( enemy , SHOOTFOV ) )
no_fire = false ;
}
else
{
//Other weapons, mostly instant hit stuff.
2016-03-25 23:34:56 +00:00
Angle = player - > mo - > AngleTo ( enemy ) ;
2016-03-01 15:47:10 +00:00
aiming_penalty = 0 ;
if ( enemy - > flags & MF_SHADOW )
aiming_penalty + = ( pr_botdofire ( ) % 25 ) + 10 ;
if ( enemy - > Sector - > lightlevel < WHATS_DARK /* && !(player->powers & PW_INFRARED)*/ )
aiming_penalty + = pr_botdofire ( ) % 40 ; //Dark
if ( player - > damagecount )
aiming_penalty + = player - > damagecount ; //Blood in face makes it hard to aim
aiming_value = skill . aiming - aiming_penalty ;
if ( aiming_value < = 0 )
aiming_value = 1 ;
m = ( ( SHOOTFOV / 2 ) - ( aiming_value * SHOOTFOV / 200 ) ) ; //Higher skill is more accurate
if ( m < = 0 )
2016-03-25 23:34:56 +00:00
m = 1. ; //Prevents lock.
2016-03-01 15:47:10 +00:00
2016-03-25 23:34:56 +00:00
if ( m ! = 0 )
2016-03-01 15:47:10 +00:00
{
if ( increase )
2016-03-25 23:34:56 +00:00
Angle + = m ;
2016-03-01 15:47:10 +00:00
else
2016-03-25 23:34:56 +00:00
Angle - = m ;
2016-03-01 15:47:10 +00:00
}
2016-03-25 23:34:56 +00:00
if ( absangle ( Angle , player - > mo - > Angles . Yaw ) < 4. )
2016-03-01 15:47:10 +00:00
{
increase = ! increase ;
}
if ( Check_LOS ( enemy , ( SHOOTFOV / 2 ) ) )
no_fire = false ;
}
if ( ! no_fire ) //If going to fire weapon
{
cmd - > ucmd . buttons | = BT_ATTACK ;
}
//Prevents bot from jerking, when firing automatic things with low skill.
}
bool FCajunMaster : : IsLeader ( player_t * player )
{
for ( int count = 0 ; count < MAXPLAYERS ; count + + )
{
if ( players [ count ] . Bot ! = NULL
& & players [ count ] . Bot - > mate = = player - > mo )
{
return true ;
}
}
return false ;
}
2016-03-26 00:30:28 +00:00
extern int BotWTG ;
void FCajunMaster : : BotTick ( AActor * mo )
{
BotSupportCycles . Clock ( ) ;
bglobal . m_Thinking = true ;
for ( int i = 0 ; i < MAXPLAYERS ; i + + )
{
if ( ! playeringame [ i ] | | players [ i ] . Bot = = NULL )
continue ;
if ( mo - > flags3 & MF3_ISMONSTER )
{
if ( mo - > health > 0
& & ! players [ i ] . Bot - > enemy
& & mo - > player ? ! mo - > IsTeammate ( players [ i ] . mo ) : true
& & mo - > Distance2D ( players [ i ] . mo ) < MAX_MONSTER_TARGET_DIST
& & P_CheckSight ( players [ i ] . mo , mo , SF_SEEPASTBLOCKEVERYTHING ) )
{ //Probably a monster, so go kill it.
players [ i ] . Bot - > enemy = mo ;
}
}
else if ( mo - > flags & MF_SPECIAL )
{ //Item pickup time
//clock (BotWTG);
players [ i ] . Bot - > WhatToGet ( mo ) ;
//unclock (BotWTG);
BotWTG + + ;
}
else if ( mo - > flags & MF_MISSILE )
{
if ( ! players [ i ] . Bot - > missile & & ( mo - > flags3 & MF3_WARNBOT ) )
{ //warn for incoming missiles.
if ( mo - > target ! = players [ i ] . mo & & players [ i ] . Bot - > Check_LOS ( mo , 90. ) )
players [ i ] . Bot - > missile = mo ;
}
}
}
bglobal . m_Thinking = false ;
BotSupportCycles . Unclock ( ) ;
}
2016-03-01 15:47:10 +00:00
//This function is called every
//tick (for each bot) to set
//the mate (teammate coop mate).
AActor * DBot : : Choose_Mate ( )
{
int count ;
2016-03-25 23:34:56 +00:00
double closest_dist , test ;
2016-03-01 15:47:10 +00:00
AActor * target ;
AActor * observer ;
//is mate alive?
if ( mate )
{
if ( mate - > health < = 0 )
mate = NULL ;
else
last_mate = mate ;
}
if ( mate ) //Still is..
return mate ;
//Check old_mates status.
if ( last_mate )
if ( last_mate - > health < = 0 )
last_mate = NULL ;
target = NULL ;
2016-03-25 23:34:56 +00:00
closest_dist = FLT_MAX ;
2016-03-01 15:47:10 +00:00
if ( bot_observer )
observer = players [ consoleplayer ] . mo ;
else
observer = NULL ;
//Check for player friends
for ( count = 0 ; count < MAXPLAYERS ; count + + )
{
player_t * client = & players [ count ] ;
if ( playeringame [ count ]
& & client - > mo
& & player - > mo ! = client - > mo
& & ( player - > mo - > IsTeammate ( client - > mo ) | | ! deathmatch )
& & client - > mo - > health > 0
& & client - > mo ! = observer
& & ( ( player - > mo - > health / 2 ) < = client - > mo - > health | | ! deathmatch )
& & ! bglobal . IsLeader ( client ) ) //taken?
{
if ( P_CheckSight ( player - > mo , client - > mo , SF_IGNOREVISIBILITY ) )
{
2016-03-25 23:34:56 +00:00
test = client - > mo - > Distance2D ( player - > mo ) ;
2016-03-01 15:47:10 +00:00
if ( test < closest_dist )
{
closest_dist = test ;
target = client - > mo ;
}
}
}
}
/*
//Make a introducing to mate.
if ( target & & target ! = last_mate )
{
if ( ( P_Random ( ) % ( 200 * bglobal . botnum ) ) < 3 )
{
chat = c_teamup ;
if ( target - > bot )
strcpy ( c_target , botsingame [ target - > bot_id ] ) ;
else if ( target - > player )
strcpy ( c_target , player_names [ target - > play_id ] ) ;
}
}
*/
return target ;
}
//MAKEME: Make this a smart decision
AActor * DBot : : Find_enemy ( )
{
int count ;
2016-03-25 23:34:56 +00:00
double closest_dist , temp ; //To target.
2016-03-01 15:47:10 +00:00
AActor * target ;
2016-03-25 23:34:56 +00:00
DAngle vangle ;
2016-03-01 15:47:10 +00:00
AActor * observer ;
if ( ! deathmatch )
{ // [RH] Take advantage of the Heretic/Hexen code to be a little smarter
return P_RoughMonsterSearch ( player - > mo , 20 ) ;
}
//Note: It's hard to ambush a bot who is not alone
if ( allround | | mate )
2016-03-25 23:34:56 +00:00
vangle = 360. ;
2016-03-01 15:47:10 +00:00
else
vangle = ENEMY_SCAN_FOV ;
allround = false ;
target = NULL ;
2016-03-25 23:34:56 +00:00
closest_dist = FLT_MAX ;
2016-03-01 15:47:10 +00:00
if ( bot_observer )
observer = players [ consoleplayer ] . mo ;
else
observer = NULL ;
for ( count = 0 ; count < MAXPLAYERS ; count + + )
{
player_t * client = & players [ count ] ;
if ( playeringame [ count ]
& & ! player - > mo - > IsTeammate ( client - > mo )
& & client - > mo ! = observer
& & client - > mo - > health > 0
& & player - > mo ! = client - > mo )
{
if ( Check_LOS ( client - > mo , vangle ) ) //Here's a strange one, when bot is standing still, the P_CheckSight within Check_LOS almost always returns false. tought it should be the same checksight as below but.. (below works) something must be fuckin wierd screded up.
//if(P_CheckSight(player->mo, players[count].mo))
{
2016-03-25 23:34:56 +00:00
temp = client - > mo - > Distance2D ( player - > mo ) ;
2016-03-01 15:47:10 +00:00
//Too dark?
if ( temp > DARK_DIST & &
client - > mo - > Sector - > lightlevel < WHATS_DARK /*&&
player - > Powers & PW_INFRARED */ )
continue ;
if ( temp < closest_dist )
{
closest_dist = temp ;
target = client - > mo ;
}
}
}
}
return target ;
}
//Creates a temporary mobj (invisible) at the given location.
2016-03-23 09:42:41 +00:00
void FCajunMaster : : SetBodyAt ( const DVector3 & pos , int hostnum )
2016-03-01 15:47:10 +00:00
{
if ( hostnum = = 1 )
{
if ( body1 )
{
2016-03-23 09:42:41 +00:00
body1 - > SetOrigin ( pos , false ) ;
2016-03-01 15:47:10 +00:00
}
else
{
2016-03-23 09:42:41 +00:00
body1 = Spawn ( " CajunBodyNode " , pos , NO_REPLACE ) ;
2016-03-01 15:47:10 +00:00
}
}
else if ( hostnum = = 2 )
{
if ( body2 )
{
2016-03-23 09:42:41 +00:00
body2 - > SetOrigin ( pos , false ) ;
2016-03-01 15:47:10 +00:00
}
else
{
2016-03-23 09:42:41 +00:00
body2 = Spawn ( " CajunBodyNode " , pos , NO_REPLACE ) ;
2016-03-01 15:47:10 +00:00
}
}
}
//------------------------------------------
// FireRox()
//
//Returns NULL if shouldn't fire
//else an angle (in degrees) are given
2016-03-25 17:19:54 +00:00
//This function assumes actor->player->angle
2016-03-01 15:47:10 +00:00
//has been set an is the main aiming angle.
//Emulates missile travel. Returns distance travelled.
2016-03-25 23:34:56 +00:00
double FCajunMaster : : FakeFire ( AActor * source , AActor * dest , ticcmd_t * cmd )
2016-03-01 15:47:10 +00:00
{
2016-03-23 09:42:41 +00:00
AActor * th = Spawn ( " CajunTrace " , source - > PosPlusZ ( 4 * 8. ) , NO_REPLACE ) ;
2016-03-01 15:47:10 +00:00
th - > target = source ; // where it came from
2016-03-19 23:54:18 +00:00
th - > Vel = source - > Vec3To ( dest ) . Resized ( th - > Speed ) ;
2016-03-01 15:47:10 +00:00
2016-03-25 23:34:56 +00:00
double dist = 0 ;
2016-03-01 15:47:10 +00:00
while ( dist < SAFE_SELF_MISDIST )
{
2016-03-25 23:34:56 +00:00
dist + = th - > Speed ;
th - > Move ( th - > Vel ) ;
if ( ! CleanAhead ( th , th - > X ( ) , th - > Y ( ) , cmd ) )
2016-03-01 15:47:10 +00:00
break ;
}
th - > Destroy ( ) ;
return dist ;
}
2016-03-25 23:34:56 +00:00
DAngle DBot : : FireRox ( AActor * enemy , ticcmd_t * cmd )
2016-03-01 15:47:10 +00:00
{
2016-03-23 09:42:41 +00:00
double dist ;
2016-03-01 15:47:10 +00:00
AActor * actor ;
2016-03-23 09:42:41 +00:00
double m ;
2016-03-01 15:47:10 +00:00
2016-03-23 09:42:41 +00:00
bglobal . SetBodyAt ( player - > mo - > PosPlusZ ( player - > mo - > Height / 2 ) + player - > mo - > Vel . XY ( ) * 5 , 2 ) ;
2016-03-01 15:47:10 +00:00
actor = bglobal . body2 ;
2016-03-23 09:42:41 +00:00
dist = actor - > Distance2D ( enemy ) ;
2016-03-25 23:34:56 +00:00
if ( dist < SAFE_SELF_MISDIST )
return 0. ;
2016-03-01 15:47:10 +00:00
//Predict.
2016-03-23 09:42:41 +00:00
m = ( ( dist + 1 ) / GetDefaultByName ( " Rocket " ) - > Speed ) ;
2016-03-01 15:47:10 +00:00
2016-03-23 09:42:41 +00:00
bglobal . SetBodyAt ( DVector3 ( ( enemy - > Pos ( ) + enemy - > Vel * ( m + 2 ) ) , ONFLOORZ ) , 1 ) ;
2016-03-01 15:47:10 +00:00
//try the predicted location
if ( P_CheckSight ( actor , bglobal . body1 , SF_IGNOREVISIBILITY ) ) //See the predicted location, so give a test missile
{
FCheckPosition tm ;
2016-03-25 23:34:56 +00:00
if ( bglobal . SafeCheckPosition ( player - > mo , actor - > X ( ) , actor - > Y ( ) , tm ) )
2016-03-01 15:47:10 +00:00
{
if ( bglobal . FakeFire ( actor , bglobal . body1 , cmd ) > = SAFE_SELF_MISDIST )
{
2016-03-25 23:34:56 +00:00
return actor - > AngleTo ( bglobal . body1 ) ;
2016-03-01 15:47:10 +00:00
}
}
}
//Try fire straight.
if ( P_CheckSight ( actor , enemy , 0 ) )
{
if ( bglobal . FakeFire ( player - > mo , enemy , cmd ) > = SAFE_SELF_MISDIST )
{
2016-03-25 23:34:56 +00:00
return player - > mo - > AngleTo ( enemy ) ;
2016-03-01 15:47:10 +00:00
}
}
2016-03-25 23:34:56 +00:00
return 0. ;
2016-03-01 15:47:10 +00:00
}
// [RH] We absolutely do not want to pick things up here. The bot code is
// executed apart from all the other simulation code, so we don't want it
// creating side-effects during gameplay.
2016-03-25 23:34:56 +00:00
bool FCajunMaster : : SafeCheckPosition ( AActor * actor , double x , double y , FCheckPosition & tm )
2016-03-01 15:47:10 +00:00
{
ActorFlags savedFlags = actor - > flags ;
actor - > flags & = ~ MF_PICKUP ;
2016-03-25 23:34:56 +00:00
bool res = P_CheckPosition ( actor , DVector2 ( x , y ) , tm ) ;
2016-03-01 15:47:10 +00:00
actor - > flags = savedFlags ;
return res ;
}
void FCajunMaster : : StartTravel ( )
{
for ( int i = 0 ; i < MAXPLAYERS ; + + i )
{
if ( players [ i ] . Bot ! = NULL )
{
players [ i ] . Bot - > ChangeStatNum ( STAT_TRAVELLING ) ;
}
}
}
void FCajunMaster : : FinishTravel ( )
{
for ( int i = 0 ; i < MAXPLAYERS ; + + i )
{
if ( players [ i ] . Bot ! = NULL )
{
players [ i ] . Bot - > ChangeStatNum ( STAT_BOT ) ;
}
}
}