mirror of
https://github.com/ZDoom/gzdoom.git
synced 2024-12-01 00:12:27 +00:00
e4af82ae96
velocity, and now it's known as such. The actor variables momx/momy/momz are now known as velx/vely/velz, and the ACS functions GetActorMomX/Y/Z are now known as GetActorVelX/Y/Z. For compatibility, momx/momy/momz will continue to work as aliases from DECORATE. The ACS functions, however, require you to use the new name, since they never saw an official release yet. SVN r1689 (trunk)
562 lines
15 KiB
C++
562 lines
15 KiB
C++
/*******************************
|
|
* 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 "b_bot.h"
|
|
#include "g_game.h"
|
|
#include "m_random.h"
|
|
#include "r_sky.h"
|
|
#include "r_main.h"
|
|
#include "st_stuff.h"
|
|
#include "stats.h"
|
|
#include "i_system.h"
|
|
#include "s_sound.h"
|
|
#include "d_event.h"
|
|
|
|
static FRandom pr_botdofire ("BotDoFire");
|
|
|
|
|
|
//Checks TRUE reachability from
|
|
//one looker to another. First mobj (looker) is looker.
|
|
bool FCajunMaster::Reachable (AActor *looker, AActor *rtarget)
|
|
{
|
|
if (looker == rtarget)
|
|
return false;
|
|
|
|
if ((rtarget->Sector->ceilingplane.ZatPoint (rtarget->x, rtarget->y) -
|
|
rtarget->Sector->floorplane.ZatPoint (rtarget->x, rtarget->y))
|
|
< looker->height) //Where rtarget is, looker can't be.
|
|
return false;
|
|
|
|
sector_t *last_s = looker->Sector;
|
|
fixed_t last_z = last_s->floorplane.ZatPoint (looker->x, looker->y);
|
|
fixed_t estimated_dist = P_AproxDistance (looker->x - rtarget->x, looker->y - rtarget->y);
|
|
bool reachable = true;
|
|
|
|
FPathTraverse it(looker->x+looker->velx, looker->y+looker->vely, rtarget->x, rtarget->y, PT_ADDLINES|PT_ADDTHINGS);
|
|
intercept_t *in;
|
|
while ((in = it.Next()))
|
|
{
|
|
fixed_t hitx, hity;
|
|
fixed_t frac;
|
|
line_t *line;
|
|
AActor *thing;
|
|
fixed_t dist;
|
|
sector_t *s;
|
|
|
|
frac = in->frac - FixedDiv (4*FRACUNIT, MAX_TRAVERSE_DIST);
|
|
dist = FixedMul (frac, MAX_TRAVERSE_DIST);
|
|
|
|
hitx = it.Trace().x + FixedMul (looker->velx, frac);
|
|
hity = it.Trace().y + FixedMul (looker->vely, frac);
|
|
|
|
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;
|
|
fixed_t ceilingheight = s->ceilingplane.ZatPoint (hitx, hity);
|
|
fixed_t floorheight = s->floorplane.ZatPoint (hitx, hity);
|
|
|
|
if (!bglobal.IsDangerous (s) && //Any nukage/lava?
|
|
(floorheight <= (last_z+MAXMOVEHEIGHT)
|
|
&& ((ceilingheight == floorheight && line->special)
|
|
|| (ceilingheight - floorheight) >= looker->height))) //Does it fit?
|
|
{
|
|
last_z = floorheight;
|
|
last_s = s;
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (dist > estimated_dist)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
thing = in->d.thing;
|
|
if (thing == looker) //Can't reach self in this case.
|
|
continue;
|
|
if (thing == rtarget && (rtarget->Sector->floorplane.ZatPoint (rtarget->x, rtarget->y) <= (last_z+MAXMOVEHEIGHT)))
|
|
{
|
|
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.
|
|
bool FCajunMaster::Check_LOS (AActor *from, AActor *to, angle_t vangle)
|
|
{
|
|
if (!P_CheckSight (from, to, 2))
|
|
return false; // out of sight
|
|
if (vangle == ANGLE_MAX)
|
|
return true;
|
|
if (vangle == 0)
|
|
return false; //Looker seems to be blind.
|
|
|
|
return (angle_t)abs (R_PointToAngle2 (from->x, from->y, to->x, to->y) - from->angle) <= vangle/2;
|
|
}
|
|
|
|
//-------------------------------------
|
|
//Bot_Dofire()
|
|
//-------------------------------------
|
|
//The bot will check if it's time to fire
|
|
//and do so if that is the case.
|
|
void FCajunMaster::Dofire (AActor *actor, 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.
|
|
fixed_t dist;
|
|
angle_t an;
|
|
int m;
|
|
static bool inc[MAXPLAYERS];
|
|
AActor *enemy = actor->player->enemy;
|
|
|
|
if (!enemy || !(enemy->flags & MF_SHOOTABLE) || enemy->health <= 0)
|
|
return;
|
|
|
|
if (actor->player->ReadyWeapon == NULL)
|
|
return;
|
|
|
|
if (actor->player->damagecount > actor->player->skill.isp)
|
|
{
|
|
actor->player->first_shot = true;
|
|
return;
|
|
}
|
|
|
|
//Reaction skill thing.
|
|
if (actor->player->first_shot &&
|
|
!(actor->player->ReadyWeapon->WeaponFlags & WIF_BOT_REACTION_SKILL_THING))
|
|
{
|
|
actor->player->t_react = (100-actor->player->skill.reaction+1)/((pr_botdofire()%3)+3);
|
|
}
|
|
actor->player->first_shot = false;
|
|
if (actor->player->t_react)
|
|
return;
|
|
|
|
//MAKEME: Decrease the rocket suicides even more.
|
|
|
|
no_fire = true;
|
|
//actor->player->angle = R_PointToAngle2(actor->x, actor->y, actor->player->enemy->x, actor->player->enemy->y);
|
|
//Distance to enemy.
|
|
dist = P_AproxDistance ((actor->x + actor->velx) - (enemy->x + enemy->velx),
|
|
(actor->y + actor->vely) - (enemy->y + enemy->vely));
|
|
|
|
//FIRE EACH TYPE OF WEAPON DIFFERENT: Here should all the different weapons go.
|
|
if (actor->player->ReadyWeapon->WeaponFlags & WIF_BOT_MELEE)
|
|
{
|
|
if ((actor->player->ReadyWeapon->ProjectileType != NULL))
|
|
{
|
|
if (actor->player->ReadyWeapon->CheckAmmo (AWeapon::PrimaryFire, false, true))
|
|
{
|
|
// This weapon can fire a projectile and has enough ammo to do so
|
|
goto shootmissile;
|
|
}
|
|
else if (!(actor->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..
|
|
no_fire = (dist > (MELEERANGE*4));
|
|
}
|
|
}
|
|
else if (actor->player->ReadyWeapon->WeaponFlags & WIF_BOT_BFG)
|
|
{
|
|
//MAKEME: This should be smarter.
|
|
if ((pr_botdofire()%200)<=actor->player->skill.reaction)
|
|
if(Check_LOS(actor, actor->player->enemy, SHOOTFOV))
|
|
no_fire = false;
|
|
}
|
|
else if (actor->player->ReadyWeapon->ProjectileType != NULL)
|
|
{
|
|
if (actor->player->ReadyWeapon->WeaponFlags & WIF_BOT_EXPLOSIVE)
|
|
{
|
|
//Special rules for RL
|
|
an = FireRox (actor, enemy, cmd);
|
|
if(an)
|
|
{
|
|
actor->player->angle = an;
|
|
//have to be somewhat precise. to avoid suicide.
|
|
if (abs (actor->player->angle - actor->angle) < 12*ANGLE_1)
|
|
{
|
|
actor->player->t_rocket = 9;
|
|
no_fire = false;
|
|
}
|
|
}
|
|
}
|
|
// prediction aiming
|
|
shootmissile:
|
|
dist = P_AproxDistance (actor->x - enemy->x, actor->y - enemy->y);
|
|
m = dist / GetDefaultByType (actor->player->ReadyWeapon->ProjectileType)->Speed;
|
|
SetBodyAt (enemy->x + enemy->velx*m*2, enemy->y + enemy->vely*m*2, enemy->z, 1);
|
|
actor->player->angle = R_PointToAngle2 (actor->x, actor->y, body1->x, body1->y);
|
|
if (Check_LOS (actor, enemy, SHOOTFOV))
|
|
no_fire = false;
|
|
}
|
|
else
|
|
{
|
|
//Other weapons, mostly instant hit stuff.
|
|
actor->player->angle = R_PointToAngle2 (actor->x, actor->y, enemy->x, enemy->y);
|
|
aiming_penalty = 0;
|
|
if (enemy->flags & MF_SHADOW)
|
|
aiming_penalty += (pr_botdofire()%25)+10;
|
|
if (enemy->Sector->lightlevel<WHATS_DARK/* && !(actor->player->powers & PW_INFRARED)*/)
|
|
aiming_penalty += pr_botdofire()%40;//Dark
|
|
if (actor->player->damagecount)
|
|
aiming_penalty += actor->player->damagecount; //Blood in face makes it hard to aim
|
|
aiming_value = actor->player->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)
|
|
m = 1; //Prevents lock.
|
|
|
|
if (m)
|
|
{
|
|
if (inc[actor->id])
|
|
actor->player->angle += m;
|
|
else
|
|
actor->player->angle -= m;
|
|
}
|
|
|
|
if (abs (actor->player->angle - actor->angle) < 4*ANGLE_1)
|
|
{
|
|
inc[actor->id] = !inc[actor->id];
|
|
}
|
|
|
|
if (Check_LOS (actor, 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.
|
|
//actor->angle = R_PointToAngle2(actor->x, actor->y, actor->player->enemy->x, actor->player->enemy->y);
|
|
}
|
|
|
|
|
|
//This function is called every
|
|
//tick (for each bot) to set
|
|
//the mate (teammate coop mate).
|
|
AActor *FCajunMaster::Choose_Mate (AActor *bot)
|
|
{
|
|
int count;
|
|
int count2;
|
|
fixed_t closest_dist, test;
|
|
AActor *target;
|
|
AActor *observer;
|
|
bool p_leader[MAXPLAYERS];
|
|
|
|
//is mate alive?
|
|
if (bot->player->mate)
|
|
{
|
|
if (bot->player->mate->health <= 0)
|
|
bot->player->mate = NULL;
|
|
else
|
|
bot->player->last_mate = bot->player->mate;
|
|
}
|
|
if (bot->player->mate) //Still is..
|
|
return bot->player->mate;
|
|
|
|
//Check old_mates status.
|
|
if (bot->player->last_mate)
|
|
if (bot->player->last_mate->health <= 0)
|
|
bot->player->last_mate = NULL;
|
|
|
|
for (count = 0; count < MAXPLAYERS; count++)
|
|
{
|
|
if (!playeringame[count])
|
|
continue;
|
|
p_leader[count] = false;
|
|
for (count2 = 0; count2 < MAXPLAYERS; count2++)
|
|
{
|
|
if (players[count].isbot
|
|
&& players[count2].mate == players[count].mo)
|
|
{
|
|
p_leader[count] = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
target = NULL;
|
|
closest_dist = FIXED_MAX;
|
|
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
|
|
&& bot != client->mo
|
|
&& (bot->IsTeammate (client->mo) || !deathmatch)
|
|
&& client->mo->health > 0
|
|
&& client->mo != observer
|
|
&& ((bot->health/2) <= client->mo->health || !deathmatch)
|
|
&& !p_leader[count]) //taken?
|
|
{
|
|
|
|
if (P_CheckSight (bot, client->mo, 1))
|
|
{
|
|
test = P_AproxDistance (client->mo->x - bot->x,
|
|
client->mo->y - bot->y);
|
|
|
|
if (test < closest_dist)
|
|
{
|
|
closest_dist = test;
|
|
target = client->mo;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
//Make a introducing to mate.
|
|
if(target && target!=bot->player->last_mate)
|
|
{
|
|
if((P_Random()%(200*bglobal.botnum))<3)
|
|
{
|
|
bot->player->chat = c_teamup;
|
|
if(target->bot)
|
|
strcpy(bot->player->c_target, botsingame[target->bot_id]);
|
|
else if(target->player)
|
|
strcpy(bot->player->c_target, player_names[target->play_id]);
|
|
}
|
|
}
|
|
*/
|
|
|
|
return target;
|
|
|
|
}
|
|
|
|
//MAKEME: Make this a smart decision
|
|
AActor *FCajunMaster::Find_enemy (AActor *bot)
|
|
{
|
|
int count;
|
|
fixed_t closest_dist, temp; //To target.
|
|
AActor *target;
|
|
angle_t vangle;
|
|
AActor *observer;
|
|
|
|
if (!deathmatch)
|
|
{ // [RH] Take advantage of the Heretic/Hexen code to be a little smarter
|
|
return P_RoughMonsterSearch (bot, 20);
|
|
}
|
|
|
|
//Note: It's hard to ambush a bot who is not alone
|
|
if (bot->player->allround || bot->player->mate)
|
|
vangle = ANGLE_MAX;
|
|
else
|
|
vangle = ENEMY_SCAN_FOV;
|
|
bot->player->allround = false;
|
|
|
|
target = NULL;
|
|
closest_dist = FIXED_MAX;
|
|
if (bot_observer)
|
|
observer = players[consoleplayer].mo;
|
|
else
|
|
observer = NULL;
|
|
|
|
for (count = 0; count < MAXPLAYERS; count++)
|
|
{
|
|
player_t *client = &players[count];
|
|
if (playeringame[count]
|
|
&& !bot->IsTeammate (client->mo)
|
|
&& client->mo != observer
|
|
&& client->mo->health > 0
|
|
&& bot != client->mo)
|
|
{
|
|
if (Check_LOS (bot, 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( bot, players[count].mo))
|
|
{
|
|
temp = P_AproxDistance (client->mo->x - bot->x,
|
|
client->mo->y - bot->y);
|
|
|
|
//Too dark?
|
|
if (temp > DARK_DIST &&
|
|
client->mo->Sector->lightlevel < WHATS_DARK /*&&
|
|
bot->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.
|
|
void FCajunMaster::SetBodyAt (fixed_t x, fixed_t y, fixed_t z, int hostnum)
|
|
{
|
|
if (hostnum == 1)
|
|
{
|
|
if (body1)
|
|
{
|
|
body1->SetOrigin (x, y, z);
|
|
}
|
|
else
|
|
{
|
|
body1 = Spawn ("CajunBodyNode", x, y, z, NO_REPLACE);
|
|
}
|
|
}
|
|
else if (hostnum == 2)
|
|
{
|
|
if (body2)
|
|
{
|
|
body2->SetOrigin (x, y, z);
|
|
}
|
|
else
|
|
{
|
|
body2 = Spawn ("CajunBodyNode", x, y, z, NO_REPLACE);
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------
|
|
// FireRox()
|
|
//
|
|
//Returns NULL if shouldn't fire
|
|
//else an angle (in degrees) are given
|
|
//This function assumes actor->player->angle
|
|
//has been set an is the main aiming angle.
|
|
|
|
|
|
//Emulates missile travel. Returns distance travelled.
|
|
fixed_t FCajunMaster::FakeFire (AActor *source, AActor *dest, ticcmd_t *cmd)
|
|
{
|
|
AActor *th = Spawn ("CajunTrace", source->x, source->y, source->z + 4*8*FRACUNIT, NO_REPLACE);
|
|
|
|
th->target = source; // where it came from
|
|
|
|
float speed = (float)th->Speed;
|
|
|
|
FVector3 velocity;
|
|
velocity[0] = FIXED2FLOAT(dest->x - source->x);
|
|
velocity[1] = FIXED2FLOAT(dest->y - source->y);
|
|
velocity[2] = FIXED2FLOAT(dest->z - source->z);
|
|
velocity.MakeUnit();
|
|
th->velx = FLOAT2FIXED(velocity[0] * speed);
|
|
th->vely = FLOAT2FIXED(velocity[1] * speed);
|
|
th->velz = FLOAT2FIXED(velocity[2] * speed);
|
|
|
|
fixed_t dist = 0;
|
|
|
|
while (dist < SAFE_SELF_MISDIST)
|
|
{
|
|
dist += th->Speed;
|
|
th->SetOrigin (th->x + th->velx, th->y + th->vely, th->z + th->velz);
|
|
if (!CleanAhead (th, th->x, th->y, cmd))
|
|
break;
|
|
}
|
|
th->Destroy ();
|
|
return dist;
|
|
}
|
|
|
|
angle_t FCajunMaster::FireRox (AActor *bot, AActor *enemy, ticcmd_t *cmd)
|
|
{
|
|
fixed_t dist;
|
|
angle_t ang;
|
|
AActor *actor;
|
|
int m;
|
|
|
|
SetBodyAt (bot->x + FixedMul(bot->velx, 5*FRACUNIT),
|
|
bot->y + FixedMul(bot->vely, 5*FRACUNIT),
|
|
bot->z + (bot->height / 2), 2);
|
|
|
|
actor = bglobal.body2;
|
|
|
|
dist = P_AproxDistance (actor->x-enemy->x, actor->y-enemy->y);
|
|
if (dist < SAFE_SELF_MISDIST)
|
|
return 0;
|
|
//Predict.
|
|
m = (((dist+1)/FRACUNIT) / GetDefaultByName("Rocket")->Speed);
|
|
|
|
SetBodyAt (enemy->x + FixedMul(enemy->velx, (m+2*FRACUNIT)),
|
|
enemy->y + FixedMul(enemy->vely, (m+2*FRACUNIT)), ONFLOORZ, 1);
|
|
dist = P_AproxDistance(actor->x-bglobal.body1->x, actor->y-bglobal.body1->y);
|
|
//try the predicted location
|
|
if (P_CheckSight (actor, bglobal.body1, 1)) //See the predicted location, so give a test missile
|
|
{
|
|
FCheckPosition tm;
|
|
if (SafeCheckPosition (bot, actor->x, actor->y, tm))
|
|
{
|
|
if (FakeFire (actor, bglobal.body1, cmd) >= SAFE_SELF_MISDIST)
|
|
{
|
|
ang = R_PointToAngle2 (actor->x, actor->y, bglobal.body1->x, bglobal.body1->y);
|
|
return ang;
|
|
}
|
|
}
|
|
}
|
|
//Try fire straight.
|
|
if (P_CheckSight (actor, enemy, 0))
|
|
{
|
|
if (FakeFire (bot, enemy, cmd) >= SAFE_SELF_MISDIST)
|
|
{
|
|
ang = R_PointToAngle2(bot->x, bot->y, enemy->x, enemy->y);
|
|
return ang;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// [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.
|
|
bool FCajunMaster::SafeCheckPosition (AActor *actor, fixed_t x, fixed_t y, FCheckPosition &tm)
|
|
{
|
|
int savedFlags = actor->flags;
|
|
actor->flags &= ~MF_PICKUP;
|
|
bool res = P_CheckPosition (actor, x, y, tm);
|
|
actor->flags = savedFlags;
|
|
return res;
|
|
}
|