hexen2/H2W/HCode/mg_ai.hc
2000-11-10 00:00:00 +00:00

1212 lines
31 KiB
C++

/*
==================================================================
MG_AI.HC
Michael Gummelt Artificial Intelligence Routines!!!
US Patent# 2.56734376314532533 + E17
Hoo-hah!
==================================================================
*/
/*
==============================================
GENERAL
==============================================
*/
/*
=============
get_visibility
Checks for drf_translucent and abslight of an object,
and uses that and it's world lighting value to set it's
visibility value
>=1 = Totally visible (default)
.
.
.
<=0 = Totally invisible
This value should be used in monster aiming as well.
NOTE: This only works on players since light_level info
is taken from player's weaponmodel lighting (0-255)
=============
*/
void get_visibility (entity targ , float range_mod)
{
//NOTE: incorporate distance?
float base, divider, attack_mod;
//FIXME: .light_level gives a value of 0 if MLS_POWERMODE is on...
//Temp fix for now...
if(targ.classname!="player"||targ.drawflags&MLS_POWERMODE)
{
targ.visibility=1;
return;
}
if(targ.effects&EF_NODRAW)
{
targ.visibility=0;
return;
}
if(targ.drawflags&DRF_TRANSLUCENT)
{
if(targ.model=="models/assassin.mdl")
divider=3+targ.level;//Bonus for hiding in shadows
else
divider=3; //Makes it 3 times harder to see
}
else
divider=1;
if(targ.drawflags&MLS_ABSLIGHT)
base=targ.abslight/2.5;
else
base=targ.light_level/75;//75 is semi-fullbright
if(range_mod)
range_mod=vlen(targ.origin-self.origin)/333;
else
range_mod = 1;
if(targ.last_attack>time - 3)//Remember where they were when fired
attack_mod=time - targ.last_attack;
targ.visibility=base/divider/range_mod + attack_mod;
}
/*
=============
float visibility_good (entity targ,float chance_mod)
MG
Does a random check to see if self can see the target based
it's visibility (calls get_visibility for that targ first)
The higher the chance_mod, the lower the chance of
good visibility.
=============
*/
float visibility_good (entity targ,float chance_mod)
{
if(!targ)
return FALSE;
get_visibility(targ,TRUE);
if(random(chance_mod)<targ.visibility)
return TRUE;
return FALSE;
}
/*
=============
float FindMonsterTarget ()
MG
Called by FindTarget, checks for anything alive and
visible within range and sets it as enemy (as long as
it's not the monster's controller, or has the same
controller). Returns TRUE if it finds something,
FALSE if not.
=============
*/
float FindMonsterTarget ()
{
entity found;
float okay;
if(self.controller.enemy!=world)
if(self.controller.enemy.flags2&FL_ALIVE)
if(self.controller.enemy!=self)
if(self.controller.enemy!=self.controller)
if(visible(self.controller.enemy))
{
self.enemy=self.controller.enemy;
return TRUE;
}
okay=FALSE;
found=findradius(self.origin,1000);
while(found!=world)
{
if(found.flags2&FL_ALIVE)
if(found!=self)
if(found!=self.controller)
if(found.controller!=self.controller)
if(visible(found))
{
if(coop)
{
if(found.classname!="player")
okay = TRUE;
}
else if(teamplay)
{
if(found.team!=self.controller.team)
okay = TRUE;
}
else
okay = TRUE;
if(okay)
{
self.enemy=found;
return TRUE;
}
}
found=found.chain;
}
if(self.classname=="monster_imp_lord")
self.enemy=self.controller;
return FALSE;
}
/*
==================================================================
float CheckJump()
MG
Checks to see if the enemy is not at the same level as monster
or something is blocking the path of the monster. If there is
a clear jump arc to the enemy and the monster will not land in
water or lava, the monster will attampt to jump the distance.
==================================================================
*/
float CheckJump ()
{
local vector spot1, spot2, jumpdir;
float jump_height, jumpup, ignore_height;
makevectors(self.angles);
jumpdir=normalize(self.goalentity.origin-self.origin);
jumpdir_z=0;
jump_height=jumpdir*v_forward;
if(jump_height<0.3)
return FALSE;
spot1=self.origin;
spot2=self.enemy.origin;
spot1_z=0;
spot2_z=0;
jump_height=16;
if(pointcontents(spot1+v_forward*24-'0 0 10')!=CONTENT_SOLID)
ignore_height=TRUE;
if(self.classname!="monster_mezzoman"&&!self.spiderType)
if(vlen(spot1-spot2)>256)
ignore_height=FALSE;
//also check to make sure you can't walkmove forward
if(self.jump_flag>time) //Don't jump too many times in a row
{
// dprint("just jumped\n");
return FALSE;
}
else if(pointcontents(self.goalentity.origin)!=CONTENT_EMPTY)
{
// dprint("goalentity in water or lava\n");
return FALSE;
}
else if(!visible(self.goalentity))
{
// dprint("can't see goalentity\n");
return FALSE;
}
else if(!ignore_height&&self.goalentity.absmin_z+36>=self.absmin_z&&self.think!=SpiderJumpBegin&&self.classname!="monster_mezzoman")
{
// dprint("not above goalentity, and not spider\n");
return FALSE;
}
else if(!self.flags&FL_ONGROUND)
{
// dprint("not on ground\n");
return FALSE;
}
else if(!self.goalentity.flags&FL_ONGROUND&&self.goalentity.classname!="waypoint")
{
// dprint("goalentity in air\n");
return FALSE;
}
else if(!infront(self.goalentity))
{
// dprint("goalentity not in front\n");
return FALSE;
}
else if(vlen(spot1-spot2)>777&&!ignore_height)
{
// dprint("too far away\n");
return FALSE;
}
else if(vlen(spot1-spot2)<=100)//&&self.think!=SpiderMeleeBegin)
{
// dprint("too close & not spider\n");
return FALSE;
}
if(self.think==SpiderJumpBegin)
jump_height=vlen((self.goalentity.absmax+self.goalentity.absmin)*0.5-self.origin)/13;
else if(self.classname=="monster_mezzoman")
if(self.goalentity.absmin_z>=self.absmin_z+36)
{
jump_height=vlen((self.goalentity.absmax+self.goalentity.absmin)*0.5-self.origin)/13;
jumpup=TRUE;
}
else if(self.goalentity.absmin_z>self.absmin_z - 36)
{
if(ignore_height)
jump_height=vlen((self.goalentity.absmax+self.goalentity.absmin)*0.5-self.origin)/13;
else
// dprint("Mezzo: Goal not above and not below\n");
return FALSE;
}
spot1=self.origin;
spot1_z=self.absmax_z;
spot2=spot1;
spot2_z+=36;
traceline(spot1, spot2,FALSE,self);
if(trace_fraction<1)
{
// dprint("not enough room above\n");
return FALSE;
}
if(!jumpup)
{
// spot1+=normalize(v_forward)*((self.maxs_x+self.maxs_y)*0.5);
spot1+=jumpdir*((self.maxs_x+self.maxs_y)*0.5);
traceline(self.origin, spot1 + '0 0 36',FALSE,self);
if(trace_fraction<1)
{
// dprint("not enough room in front\n");
return FALSE;
}
traceline(spot1,spot1+jumpdir*64 - '0 0 500',FALSE,self);
if(pointcontents(trace_endpos)==CONTENT_WATER||pointcontents(trace_endpos)==CONTENT_SLIME||pointcontents(trace_endpos)==CONTENT_LAVA)
{
// dprint("won't jump in water\n");
return FALSE;
}
}
ai_face();
// self.ideal_yaw=jumpdir_y;
// ChangeYaw();
if(self.think!=SpiderJumpBegin)
{
self.jump_flag=time + 7; //Only try to jump once every 7 seconds
SightSound();
if(!jumpup)
{
self.velocity=jumpdir*jump_height*17*self.scale;
self.velocity_z = jump_height*12*self.scale;
}
else
{
self.velocity=jumpdir*jump_height*10*self.scale;
self.velocity_z = jump_height*14*self.scale;
}
self.flags(-)FL_ONGROUND;
if(self.th_jump)
self.th_jump();
else
thinktime self : 0.3;
}
else
{
self.level=jump_height;
return TRUE;
}
}
/*
====================================================================
void MonsterCheckContents ()
MG
Monsters check to see if they're in lava or under water and
do damage do themselves if appropriate.
void do_contents_dam ()
Just spawns a temporary ent to damage self, using T_Damage on
self does weird stuff- won't kill self, just become invincible
====================================================================
*/
void do_contents_dam ()
{
T_Damage(self.enemy,world,world,self.dmg);
if(self.dmg==5)
{
self.classname="contents damager";
setorigin(self,self.enemy.origin+self.enemy.view_ofs);
// DeathBubbles(1);
}
remove(self);
}
void MonsterCheckContents ()
{
if(random()>0.3)
return;
if(pointcontents(self.origin)==CONTENT_LAVA)
{
if(self.flags&FL_FIREHEAL)
{
if(self.health<self.max_health)
self.health+=1;
}
else
{
newmis=spawn();
newmis.think=do_contents_dam;
newmis.enemy=self;
newmis.dmg=30;
thinktime newmis : 0;
}
}
if(self.movetype==MOVETYPE_SWIM||self.model=="models/skullwiz.mdl"||self.netname=="golem")
return;
if(pointcontents(self.origin+self.view_ofs)==CONTENT_WATER)
{
if(self.air_finished<time) //Start drowning
{
// dprint("drowning!\n");
newmis=spawn();
newmis.think=do_contents_dam;
newmis.enemy=self;
newmis.dmg=5;
thinktime newmis : 0;
}
}
else
self.air_finished=time+12;
}
/*
====================================================================
void pitch_roll_for_slope (vector slope)
(My personal favorite!)
MG
This will adjust the pitch and roll of a monster to match
a given slope - if a non-'0 0 0' slope is passed, it will
use that value, otherwise it will use the ground underneath
the monster. If it doesn't find a surface, it does nothinh\g
and returns.
====================================================================
*/
void pitch_roll_for_slope (vector slope)
{
vector new_angles,new_angles2,old_forward,old_right;
float dot,mod;
makevectors(self.angles);
old_forward=v_forward;
old_right=v_right;
if(slope=='0 0 0')
{
traceline(self.origin,self.origin-'0 0 300',TRUE,self);
if(trace_fraction>0.05&&self.movetype==MOVETYPE_STEP)
self.flags(-)FL_ONGROUND;
if(trace_fraction==1)
return;
slope=trace_plane_normal;
}
new_angles=vectoangles(slope);
new_angles_x=(90-new_angles_x)*-1;//Gets actual slope
new_angles2='0 0 0';
new_angles2_y=new_angles_y;
makevectors(new_angles2);
mod=v_forward*old_right;
if(mod<0)
mod=1;
else
mod=-1;
dot=v_forward*old_forward;
self.angles_x=dot*new_angles_x;
self.angles_z=(1-fabs(dot))*new_angles_x*mod;
}
/*
==============================================
IMP
==============================================
*/
/*
================================================================
checkenemy()
Checks to see if enemy is of the same monstertype and old enemy
is alive and visible. If so, changes back to it's last enemy.
================================================================
*/
void checkenemy (void)
{
entity oldtarget;
/* if(self.enemy==world)
{
if(!LocateTarget())
{
if(self.controller.classname=="player")
self.enemy=self.controller;
else
{
self.enemy=world;
self.think=self.th_stand;
}
}
self.goalentity=self.enemy;
return;
}
*/
if(self.enemy.classname=="player"&&self.enemy.flags2&FL_ALIVE&&self.enemy!=self.controller)
return;
if (!self.enemy.flags2&FL_ALIVE||self.enemy==self.controller)
{
if(self.controller.classname=="player")
{
self.enemy = self.controller;
self.goalentity=self.enemy;
}
else
self.enemy = world;
if (self.oldenemy.flags2&FL_ALIVE)
{
self.enemy = self.oldenemy;
self.goalentity = self.enemy;
self.think = self.th_run;
}
else if(LocateTarget())
{
self.goalentity = self.enemy;
self.think = self.th_run;
}
else
{
if(self.controller.classname=="player")
self.goalentity=self.enemy=self.controller;
else
self.goalentity=self.enemy=world;
if (self.pathentity)
self.think=self.th_walk;
else
self.think=self.th_stand;
}
thinktime self : 0;
return;
}
if(self.classname=="monster_imp_lord")
return;
if(self.oldenemy.classname=="player"&&(self.oldenemy.flags2&FL_ALIVE)&&visible(self.oldenemy))
{
if((self.model=="models/spider.mdl"||self.model=="models/scorpion.mdl")&&self.enemy.model==self.model)
self.enemy=self.oldenemy;
else
{
oldtarget=self.enemy;
self.enemy=self.oldenemy;
self.oldenemy=oldtarget;
}
self.goalentity=self.enemy;
}
}
/*
================================================================
fov()
Field-Of-View
Returns TRUE if vector from entity "from" to entity "targ" is
within "scope" degrees of entity "from"'s forward angle.
================================================================
*/
float fov(entity targ,entity from,float scope)
{
vector spot1,spot2;
float dot;
spot1=from.origin+from.proj_ofs;
spot2=(targ.absmin+targ.absmax)*0.5;
if(from.classname=="player")
makevectors(from.v_angle);
else
makevectors(from.angles);
// scope=1 - (scope/180);//converts angles into %
dot=normalize(spot2-spot1)*v_forward;
dot=180 - (dot*180);
// dprintf("FOV value : %s\n",dot);
if(dot<=scope)
return TRUE;
return FALSE;
}
/*
================================================================
check_pos_enemy()
MG
Checks to see if enemy is visible, if so, remember the spot for
waypoints, else set your waypoint at the last spot you saw him.
Also resets search_time timer if you see him.
================================================================
*/
void check_pos_enemy ()
{
if(!self.mintel)
return;
if(!visible(self.enemy))
{
self.attack_state = AS_STRAIGHT;
SetNextWaypoint();
if(self.model=="models/imp.mdl") //Imps keep looking in general area for a while
if(self.search_time<time&&self.goalentity==self.enemy&&self.trigger_field.classname=="waypoint")
self.goalentity=self.trigger_field;
}
else
{
if(self.model=="models/imp.mdl")
self.search_time=time+5; //If lose sight, keep searching for 5 secs
self.goalentity=self.enemy;
self.wallspot=(self.enemy.absmin+self.enemy.absmax)*0.5;
}
}
/*
================================================================
float clear_path (entity targ,float whole_body)
MG
returns TRUE if there is a clear shot or path between self
and "targ". "whole_body" TRUE will check for a path.
================================================================
*/
float clear_path (entity targ,float whole_body)
{
vector destiny,org;
destiny=targ.origin+targ.proj_ofs;
if(self.attack_state!=AS_FERRY)
self.attack_state = AS_STRAIGHT;
if(whole_body)
{
org=(self.absmin+self.absmax)*0.5;
// tracearea (org, destiny, '-16 -16 0','16 16 28',FALSE,self);
tracearea (org, destiny, self.mins,self.maxs,FALSE,self);
}
else
{
org=self.origin+self.proj_ofs;
traceline (org, destiny,FALSE,self);
}
if(!whole_body&&trace_ent.thingtype>=THINGTYPE_WEBS)
traceline (trace_endpos, destiny, FALSE, trace_ent);
if (trace_ent == targ)
return TRUE;
if(whole_body)
{
if(self.attack_state!=AS_FERRY)
self.attack_state = AS_SLIDING;
return FALSE;
}
if(trace_ent.health>25||!trace_ent.takedamage||(trace_ent.flags&FL_MONSTER&&trace_ent.classname!="player_sheep"))
{//Don't have a clear shot, and don't want to shoot obstruction
self.attack_state = AS_SLIDING;
return FALSE;
}
return TRUE;
}
/*
================================================================
check_view(entity targ,vector org,vector dir,float dist,float interval)
MG
Will see if it can see the targ entity along the dir
given to it- used to determine which direction a monster
should move in to get a clear line of sight to the targ.
Returns the distance it took to see targ, FALSE if couldn't.
================================================================
*/
float check_view(entity targ,vector org,vector dir,float dist,float interval)
{
float dist_counter;
newmis=spawn();
dir=normalize(dir);
while(dist_counter<dist)
{
dist_counter+=interval;
setorigin(newmis,org+dir*dist_counter);
if(visible2ent(targ,newmis))
{
traceline (newmis.origin,(targ.absmin+targ.absmax)*0.5,FALSE,self);
if (trace_ent == targ)
{
remove(newmis);
return dist_counter;
}
}
}
remove(newmis);
return FALSE;
}
/*
================================================================
vector check_axis_move (vector checkdir,float minspeed,float maxspeed)
MG
Calls check_view for enemy along given vector, and if it
fails, checks again along opposite direction on that vector.
If check_view is successful, returns the vector*the distance
along that vector at which it found the goalentity, this
"speed" maxes out at "maxpeed" and is at least "minspeed".
Not used for waypoint or path navigation, only for going
after enemies.
================================================================
*/
vector check_axis_move (vector checkdir,float minspeed,float maxspeed)
{
float go_dist;
checkdir=normalize(checkdir);
if(random()<0.5)
checkdir=checkdir*-1;
go_dist=check_view(self.enemy,self.origin+self.view_ofs,checkdir,500,30);
if(!go_dist&&random()<0.5)
{
checkdir*=-1;
go_dist=check_view(self.enemy,self.origin+self.view_ofs,checkdir,500,30);
}
if(go_dist)
{
if(go_dist>maxspeed)
go_dist=maxspeed;
else if(go_dist<minspeed)
go_dist=minspeed;
checkdir=checkdir*go_dist;
return checkdir;
}
return '0 0 0';
}
/*
================================================================
float check_z_move(float maxdist)
MG
Intended for flying monsters, will try to move up if enemy
or goalentity is above monster, down if it is below. Uses
movestep, not velocity.
Will move a maximum step of "maxdist".
Returns FALSE if blocked, TRUE if movement completed.
================================================================
*/
float check_z_move(float maxdist)
{
float goaldist;
entity targ;
if(self.enemy!=world&&visible(self.enemy))
targ=self.enemy;
else if(self.goalentity!=world)
targ=self.goalentity;
else
return FALSE;
/*What does this do?
if(fabs(targ.origin_z-self.origin_z)<48&&!visible(targ))
return FALSE; //FIXME: Find an up or down
*/
if(targ.origin_z!=self.absmin_z)
{
goaldist=(targ.absmin_z+targ.absmax_z)*0.5-(self.absmax_z+self.absmin_z)*0.5;
maxdist=fabs(maxdist);
if(fabs(goaldist)>maxdist)
if(goaldist>0)
goaldist=maxdist;
else
goaldist=maxdist*-1;
if(!movestep(0,0,goaldist, FALSE))
return FALSE;
}
return TRUE;
}
/*
====================================================================
MEDUSA
====================================================================
*/
/*
============================================================
float lineofsight(entity targ, entity from)
MG
Traces a line along "from"'s view_ofs along v_forward and returns
VRAI if it hits targ, FAUX if not, mon ami.
============================================================
*/
float lineofsight(entity targ, entity from)
{
//FIXME: account for monster's lack of pitch if z diff
vector org,dir;
if(from.classname=="player")
makevectors(from.v_angle);
/* else if(from.classname=="monster_medusa")
makevectors(from.angles+from.angle_ofs);
*/ else
makevectors(from.angles);
org=from.origin+from.view_ofs;
dir=normalize(v_forward);
traceline(org, org+dir*1000,FALSE,from);
if(trace_ent!=targ)
return FALSE;
else
{
// dprint("Line of sight from ");
// dprint(from.classname);
// dprint(" to ");
// dprint(targ.classname);
// dprint(" confirmed\n");
return TRUE;
}
}
/*
====================================================================
EIDOLON
====================================================================
*/
/*
=====================================================
entity riderpath_findbest(entity subject_path)
MG
Returns closest rider path corner that "subject_path"
leads to. Used for Rider Bosses.
=====================================================
*/
entity riderpath_findbest(entity subject_path)
{
entity search,found,best_path;
float next,num_points,position,bestdist,lastdist;
num_points = 0;
if (subject_path.next_path_1)
num_points += 1;
if (subject_path.next_path_2)
num_points += 1;
if (subject_path.next_path_3)
num_points += 1;
if (subject_path.next_path_4)
num_points += 1;
if (subject_path.next_path_5)
num_points += 1;
if (subject_path.next_path_6)
num_points += 1;
if (!num_points)
{
dprintf("rider path %s has no next points\n",subject_path.path_id);
remove(self);
return world;
}
bestdist=vlen(self.goalentity.origin-self.origin);
lastdist=bestdist;
position=0;
best_path=world;
while(position<num_points)
{
position+=1;
if (position==1)
next = subject_path.next_path_1;
else if (position==2)
next = subject_path.next_path_2;
else if (position==3)
next = subject_path.next_path_3;
else if (position==4)
next = subject_path.next_path_4;
else if (position==5)
next = subject_path.next_path_5;
else if (position==6)
next = subject_path.next_path_6;
found = world;
search = find(world, classname, "rider_path");
while(search != world && found == world)
{
if (search.path_id == next)
found = search;
else
search = find(search, classname, "rider_path");
}
if (!found)
{
dprintf("Could not find rider path %s\n",next);
remove(self);
return world;
}
else
{
lastdist=vlen(self.goalentity.origin-found.origin);
if(lastdist<bestdist)
{
best_path=found;
bestdist=lastdist;
}
}
}
if (!best_path)
return world;
return best_path;
}
/*
=====================================================================
MEZZOMAN
(Personal Favorite!)
=====================================================================
*/
/*
=========================================================
entity look_projectiles ()
MG
WARNING! Expensive, should not be called often at all!
Searches a radius 1000 around self to find projectiles
(anything with movetype bounce, flymissile, bouncemissile)
then checks to see if it's heading at self (see "heading"
function in PROJBHVR.HC). It finds the closest of these
and returns that entity. If it finds nothing, returns
"world".
=========================================================
*/
entity look_projectiles ()
{
entity found, enemy_proj;
float dist, bestdist;
found=findradius(self.origin,1000);
bestdist=1001;
while(found)
{
if(found.movetype==MOVETYPE_FLYMISSILE||found.movetype==MOVETYPE_BOUNCE||found.movetype==MOVETYPE_BOUNCEMISSILE)
if(visible(found))
{
dist=vlen(found.origin-self.origin);
if(dist<bestdist)
{
if(heading(self,found,0.9))//Try this small range for heading, but problem is, they won't split up & surround you as much...
{
bestdist=dist;
enemy_proj=found;
}
}
}
found=found.chain;
}
if(enemy_proj)
{
self.level=bestdist/vlen(enemy_proj.velocity);
return enemy_proj;
}
else
return world;
}
/*
======================================================
float solid_under(vector startpos , vector endpos)
MG
Will check in increments of 5 pixels along a given
path from startpos to endpos if there is solid gound
at least 18 pixels below (18 is the monster step-height).
If so, returns TRUE, if there is a gap anywhere along
that, returns FALSE.
Used mainly by Mezzomen to see if they should jump
across gaps at enemy.
======================================================
*/
float solid_under(vector startpos , vector endpos)
{
float diff_count;
vector dir;
dir=normalize(endpos-startpos);
diff_count=vlen(endpos-startpos)/5;
while(diff_count>0)
{
traceline(startpos,startpos-'0 0 18',TRUE,self);
if(trace_fraction==1)
return FALSE;
startpos+=dir*5;
diff_count-=1;
}
return TRUE;
}
/*
======================================================
float check_heading_left_or_right (entity object)
MG
Will check to see if the given object will be to
the left or the right of self once it gets to
self. Uses it's current position and extrapolates
based on it's heading (velocity).
Will return:
1 = left
-1 = right
0 = neither.
Special case: If called by a monster that's not
awake, will return opposite of these assuming
that the monster wants to cut off player-
only used by the Rolling Ambush Mezzomen.
======================================================
*/
float check_heading_left_or_right (entity object)
{
vector spot1, spot2, vec;
float dot, rng, reverse;
makevectors (self.angles);
spot1 = self.origin + self.view_ofs;
spot2 = object.origin;
//To get the eventual location of the projectile when it gets to him...
rng=vlen(spot1-spot2);
spot2+=normalize(object.velocity)*(rng+15);//Add a bit for good measure
vec = normalize (spot2 - spot1);
//FIXME? What about behind me?
if(object.classname=="player"&&!self.monster_awake)
{
self.monster_awake=TRUE;
sound(self,CHAN_VOICE,"mezzo/attack.wav",1,ATTN_NORM);
reverse=-1;
}
else
reverse=1;
dot = vec * v_right;
if ( dot > 0)
return -1*reverse;
dot = vec * (v_right*-1);
if ( dot > 0)
return 1*reverse;
return 0;
}
/*
======================================================
float navigate (float walkspeed)
MG
Checks to see which side of the entity is blocked
and will move in the opposite direction using
walkmove (for left-right) or movestep (for up-down)
if it can. Will move the specified distance.
If it can't move that way or it doesn't find a blocked
side, it returns false.
Meant for use with flying and swimming monsters
because movetogoal doesn't make them navigate!
======================================================
*/
float navigate (float walkspeed)
{
vector checkdir,org,new_angle;
float vert_size,horz_size;
makevectors(self.angles);
checkdir=v_right;
org=self.origin+checkdir*self.size_x;
vert_size=self.size_z/2;
horz_size=(self.size_x+self.size_y)/4;
traceline(org,org+v_forward*horz_size,FALSE,self);
if(trace_fraction==1&&!trace_allsolid)
{
checkdir=v_right*-1;
org=self.origin+checkdir*horz_size;
traceline(org,org+v_forward*horz_size,FALSE,self);
}
if(self.flags&FL_FLY||self.flags&FL_SWIM)
{
if(trace_fraction==1&&!trace_allsolid)
{
checkdir=v_up;
org=self.origin+checkdir*vert_size;
traceline(org,org+v_forward*horz_size,FALSE,self);
}
if(trace_fraction==1&&!trace_allsolid)
{
checkdir=v_up*-1;
org=self.origin+checkdir*vert_size;
traceline(org,org+v_forward*horz_size,FALSE,self);
}
}
if(trace_fraction<1||trace_allsolid)
{
if(checkdir==v_right||checkdir==v_right*-1)
{
new_angle=vectoangles(checkdir*-1);
if(!walkmove(new_angle_y,walkspeed,FALSE))
{
// dprint("Couldn't Side-step\n");
return FALSE;
}
// dprint("Side-stepped\n");
return TRUE;
}
if(checkdir==v_up)
walkspeed*=-1;
if(!movestep(0,0,walkspeed,FALSE))
{
// dprint("couldn't move up/down\n");
return FALSE;
}
// dprint("up-down move\n");
return TRUE;
}
// dprint("Not blocked\n");
return FALSE;//FOUND NO BLOCKING!!!
}
/*
=============================================================
vector extrapolate_pos_for_speed (vector p1,float pspeed,entity targ,float accept)
MG
Estimates where the "targ" will be by the time a projectile
travelling at "pspeed" leaving "org" arrives at "targ"'s origin.
It then calculates a new spot to shoot at so that the
projectile will arrive at such spot at the same time as
"targ". Will return '0 0 0' (FALSE) if there is not a clear
line of fire to the spot or if the new vector is out of the
acceptable range (based on dot product of original vec and
the new vec).
=============================================================
*/
vector extrapolate_pos_for_speed (vector p1,float pspeed,entity targ,float accept)
{
float dist1,dist2,tspeed,dot,eta1,eta2,eta_delta,failed;
vector p2,p3,targ_dir,vec1,vec2;
// dprint("Extrapolating\n");
p2=targ.origin+targ.view_ofs; //current target viewport
vec1=p2 - p1; //vector to p2
dist1=vlen(vec1); //distance to p2
vec1=normalize(vec1); //direction to p2
targ_dir=targ.velocity; //target velocity
tspeed=vlen(targ_dir); //target speed
targ_dir=normalize(targ_dir); //target direction
eta1=dist1/pspeed; //Estimated time of arrival of projectile to p2
p3=p2 + targ_dir * tspeed * eta1; //Extrapolated postion of targ at time + eta1
dist2=vlen(p3-p1); //new distance to p3
eta2=dist2/pspeed; //ETA of projectile to p3
eta_delta=eta2-eta1; //change in ETA's
p3+=targ_dir*tspeed*eta_delta*random();//Add any diff in ETA to p3's location,random a little in case they slow down
traceline(p1,p3,FALSE,self);
if(trace_fraction<1)
{
if(trace_ent.thingtype>=THINGTYPE_WEBS)
traceline (trace_endpos, p3, FALSE, trace_ent);
if(trace_fraction<1)
if(trace_ent.health>25||!trace_ent.takedamage||(trace_ent.flags&FL_MONSTER&&trace_ent.classname!="player_sheep"))
{//Don't have a clear shot, and don't want to shoot obstruction
// dprint("No clear shot\n");
self.attack_state = AS_SLIDING;
failed=TRUE;
}
}
vec2=normalize(p3-p1); //New vector to p3
dot=vec1*vec2;
if(dot<accept) //Change in dir too great
{
// dprint("Out of range\n");
failed=TRUE;
}
if(failed)
p3='0 0 0';
// else
// dprint("Successful extrapolation\n");
/*Was using this to show blue for failed extrap, red for succ.
WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
WriteByte (MSG_BROADCAST, TE_STREAM_COLORBEAM); //beam type
WriteEntity (MSG_BROADCAST, self); //owner
WriteByte (MSG_BROADCAST, 0); //tag + flags
WriteByte (MSG_BROADCAST, 20); //time
WriteByte (MSG_BROADCAST, failed); //color
WriteCoord (MSG_BROADCAST, p1_x);
WriteCoord (MSG_BROADCAST, p1_y);
WriteCoord (MSG_BROADCAST, p1_z);
WriteCoord (MSG_BROADCAST, p3_x);
WriteCoord (MSG_BROADCAST, p3_y);
WriteCoord (MSG_BROADCAST, p3_z);
*/
return p3;
}
/*
=============================================================
vector aim_adjust (entity targ)
MG
Will return a nprmalized offset vector based on the targ's
light level, used for monster aiming at shadow-hiding players.
=============================================================
*/
vector aim_adjust (entity targ)
{
float ofs;
vector vofs;
if(!targ)
return '0 0 0';
makevectors(self.angles);
get_visibility(targ,TRUE);
ofs=(1 - targ.visibility - skill/10)*0.1;
if(skill<3&&ofs>0)
{
vofs=v_up*0.5*random(0-ofs,ofs)+v_right*1.5*random(0-ofs,ofs);
return vofs;
}
return '0 0 0';
}