hexen2/H2W/HCode/projbhvr.hc

520 lines
15 KiB
C++
Raw Permalink Normal View History

2000-11-10 00:00:00 +00:00
/*
====================================================================
PROJBHVR.HC
Projectile Behaviours
By Michael Gummelt
Various routines for projectile behaviours.
===================================================================
*/
void(float num_bubbles) DeathBubbles;
/*
====================================================
void Skip()
MG
Returns True if the projectile hit a surface at
a slight enough angle to deflect. False if not
(meaning the missile should be destroyed in
the touch function this was called from).
The missile must be a MOVETYPE_BOUNCEMISSILE for
this to work, and it's o_angle must match it's
last known velocity. For now the angle of
deflection is anything below the 0.2 (using the dot
product of the negative value of the o_angle
and the trace_plane_normal of the surface it
hit.) But this could be easily customized to
use a parameter or entity field.
Values Used: .o_angle (.movetype must be
MOVETYPE_BOUNCEMISSILE)
=====================================================
*/
float Skip (void)
{
vector dir1,dir2;
float dot;
dir1 = normalize(self.velocity);
traceline(self.origin-dir1*4,self.origin+dir1*16,TRUE,self);
dir2=trace_plane_normal;
dir1*=-1;
dot=dir1*dir2;
if(dot<=0.15&&trace_fraction<1)
return TRUE;
else
return FALSE;
}
/*
====================================================
void Veer(float amount)
MG
This function will make a projectile
wander from it's course in a random
manner. It does not actually directly
use the .veer value, you must send the
veer amount value to the function as
a parameter. But this allows it to
be used in other ways (call it once,
etc.) So you can call it by using
Veer(self.veer) or Veer(random()*300)
or Veer([any number]), etc.
=====================================================
*/
void Veer(float amount)
{
//useful code for making projectiles wander randomly to a specified degree
vector veerdir;
veerdir_x=veerdir_y=veerdir_z=amount;
self.velocity+=RandomVector(veerdir);
self.angles=vectoangles(self.velocity);
}
void VeerThink ()
{
Veer(self.veer);
if(self.think==Veer)
thinktime self : 0.1;
}
/*
=========================================================
float ahead (entity loser, entity from)
MG
Checks to see if "loser" is within the forward cone
(based on facing, NOT velocity!) of "from".
Cone size defaults to 0.2 unless crossbow arrows (they
do a one-time narrow-cone aquisition of 0.8)
NOTE: "accept" can be a value between -1 and 1, the
higher the value, the smaller the cone.
Returns TRUE if so, FALSE if not.
=========================================================
*/
float ahead (entity loser, entity from)
{
vector proj_dir, spot1, spot2, vec;
float accept, dot;
proj_dir=normalize(from.velocity);
spot1 = from.origin;
spot2 = (loser.absmin+loser.absmax)*0.5;
if(from.classname=="flaming arrow"||from.classname=="bolt")
accept=0.875;
else
accept=0.2;
vec = normalize (spot2 - spot1);
dot = vec * proj_dir;
if ( dot > accept)
return TRUE;
return FALSE;
}
/*
=========================================================
float heading (entity loser, entity from, float accept)
MG
Checks to see if "loser" is within the forward cone
(based on velocity, NOT facing!) of "from".
"accept" is the size of the cone (see "ahead"), which,
if none is sent, defaults to 0.8 (rather narrow).
Returns TRUE if so, FALSE if not.
=========================================================
*/
float heading (entity loser, entity from, float accept)
{
vector proj_dir, spot1, spot2, vec;
float dot;
proj_dir=normalize(from.velocity);
spot1 = from.origin;
spot2 = (loser.absmin+loser.absmax)*0.5;
if(!accept)
accept=0.8;
vec = normalize (spot2 - spot1);
dot = vec * proj_dir;
if ( dot > accept)
return TRUE;
return FALSE;
}
void()HomeThink;
/*
======================================
entity HomeFindTarget()
MG
Simply looks for and returns a target
to go after.
======================================
*/
entity HomeFindTarget()
{
entity loser;
float dist, bestdist;
if(self.think!=HomeThink)//one-time only acquisition
bestdist=5000;
else
bestdist=1000;
loser=findradius(self.origin,bestdist);
bestdist+=1;
while (loser)
{
if(loser.health&&loser.takedamage&&(loser.flags2&FL_ALIVE)&&visible(loser)&&loser!=self&&loser!=world&&loser!=self.owner&&!loser.effects&EF_NODRAW)//&&!(loser.artifact_active&ARTFLAG_STONED) Why Not?
{
if((!self.aflag||self.ideal_yaw)&&!ahead(loser,self)) //looks for someone in front first time
dprint("");//not infront\n");
else if(teamplay&&loser.classname=="player"&&((loser.team==self.owner.team&&self.owner.classname=="player")||(loser.team==self.controller.team&&self.owner.classname=="player")))
dprint("");//targeting teammate\n");
else if(coop&&loser.classname=="player"&&(self.owner.classname=="player"||self.controller.classname=="player"))
dprint("");//target coop player\n");
else if((self.classname=="flame arrow"||self.classname=="bolt")&&deathmatch&&vlen(loser.velocity)>300)
dprint("");//DM: player moving too fast\n");
else if(loser.controller==self.owner&&self.owner.classname=="player")//don't home in on owner's imps
dprint("");//owner's thing\n");
else
{
//make it wait for closest (by vlen) or just go for first found?
dist=vlen(self.origin-loser.origin);
if(dist<bestdist)
{
bestdist=dist;
self.enemy=loser;
}
if(bestdist<100)//don't look for anything close, that's good enough
{
self.aflag=TRUE;
return self.enemy;
}
}
}
loser=loser.chain;
}
self.aflag=TRUE;
if(self.enemy)
return self.enemy;
else
return world;
}
entity HomeFindTargetEnt(entity who)
{
entity loser;
float dist, bestdist;
if(who.think!=HomeThink)//one-time only acquisition
bestdist=5000;
else
bestdist=1000;
loser=findradius(who.origin,bestdist);
bestdist+=1;
while (loser)
{
if(loser.health&&loser.takedamage&&(loser.flags2&FL_ALIVE)&&visible(loser)&&loser!=self&&loser!=world&&loser!=who.owner&&!loser.effects&EF_NODRAW)//&&!(loser.artifact_active&ARTFLAG_STONED) Why Not?
{
if((!who.aflag||who.ideal_yaw)&&!ahead(loser,who)) //looks for someone in front first time
{
8;
// dprint("");//not infront\n");
}
else if(teamplay&&loser.classname=="player"&&((loser.team==who.owner.team&&who.owner.classname=="player")||(loser.team==who.controller.team&&who.owner.classname=="player")))
{
8;
// dprint("");//targeting teammate\n");
}
else if(coop&&loser.classname=="player"&&(who.owner.classname=="player"||who.controller.classname=="player"))
{
8;
// dprint("");//target coop player\n");
}
else if((who.classname=="flame arrow"||who.classname=="bolt")&&deathmatch&&vlen(loser.velocity)>150)
{
8;
// dprint("");//DM: player moving too fast\n");
}
else if((who.classname=="chainball")&&!ahead(loser,who))//only look ahead if yer the scarab staff
{
8;
// dprint("");//not infront\n");
}
else if(loser.controller==who.owner&&who.owner.classname=="player")//don't home in on owner's imps
{
8;
// dprint("");//owner's thing\n");
}
else
{
//make it wait for closest (by vlen) or just go for first found?
dist=vlen(who.origin-loser.origin);
if(dist<bestdist)
{
bestdist=dist;
who.enemy=loser;
}
if(bestdist<100)//don't look for anything close, that's good enough
{
who.aflag=TRUE;
return who.enemy;
}
}
}
loser=loser.chain;
}
who.aflag=TRUE;
if(who.enemy)
return who.enemy;
else
return world;
}
/*
===================================================================
void HomeThink()
MG
Makes a projectile find a visible enemy
and head towards it. If you set the projectile's
.ideal_yaw to TRUE, it will only look in front
of itself for enemies (makes for poor tracking,
but this is desirable sometimes). If the value is
FALSE, It will look in front until it locks onto
something, once it does that, it will look
for anything in any direction. This way when you
fire it in a certain direction, it has
a better chance of locking onto something
in front of you, not the closest thing (which
could likely be behind you. You can go to
this function after a delay so a projectile
won't start homing until it's been in the
air for a little. You can set how often the
projectile will correct it's velocity with
the .homerate value, which is the .nextthink of
this function, so the lower the number,
the more active the homing. Also, HomeThink has
a built in Veer() function call, to give it some
randomness. If you don't want it to veer,
just set the .veer to FALSE.
The rate of turning is controlled by the
.turn_time value. A value of 0 will make
it turn instantly, a value of 1 will take the
average of the current and desired velocities.
Higher values will result in a LOWER turn
rate. Here's the calculation, simplified a bit:
new_v=(old_v*self.turn_time + ideal_v)/(self.turn_time+1)
The speed of the projectile is controlled by
self.speed.
If .hoverz is true, the projectile will slow down
the tighter the turn is- makes for better tracking.
If the Homethink is the think of the projectile,
it will check for .lifetime, if the lifetime has
expired, it will execute it's th_die function,
which you must declare!
Note that you can give a projectile an enemy and
it will start tracking that one. If you give the
projectile a lockentity, it will never try to
acquire a different entity.
Values Used: .speed, .homerate, .veer,
.turn_time, .ideal_yaw, .lifetime, .th_die,
.hoverz, .enemy, .lockentity
============================================================
*/
void HomeThink()
{
local vector huntdir;
/* if(self.classname=="lightning ball")
{
updateSoundPos(self,CHAN_BODY);
updateSoundPos(self,CHAN_WEAPON);
if(self.t_width<time)
{
sound(self,CHAN_BODY,"succubus/buzz2.wav",1,ATTN_LOOP);
self.t_width=time+99999999999999;
}
}
*/
if(self.thingtype==THINGTYPE_FIRE)
{
local float waterornot;
waterornot=pointcontents(self.origin);
if(waterornot==CONTENT_WATER||waterornot==CONTENT_SLIME)
DeathBubbles(1);
}
if(self.enemy!=world&&!self.lockentity)
if(!visible(self.enemy)||!self.enemy.health||!self.enemy.flags2&FL_ALIVE||self.enemy.effects&EF_NODRAW)
{
//if you can't see him, don't track (and look for someone else?)
self.oldenemy=self.enemy;//remember him
self.enemy=world;
}
if(self.enemy==world)
{
if(random()<0.3||self.think!=HomeThink)//findradius was too damn costly!!!
HomeFindTarget();
if(self.enemy==world&&self.oldenemy!=world&&visible(self.oldenemy)&&self.oldenemy.health&&(self.oldenemy.flags2&FL_ALIVE))
self.enemy=self.oldenemy;
}
if(self.enemy!=world&&visible(self.enemy))
{
vector olddir, newdir;
float oldvelmult , newveldiv, speed_mod;
olddir=normalize(self.velocity);
if(self.enemy.classname=="player"&&self.enemy.view_ofs!='0 0 0')
huntdir=self.enemy.origin+self.enemy.view_ofs;
else
huntdir=(self.enemy.absmin+self.enemy.absmax)*0.5;
huntdir = normalize(huntdir-self.origin);
oldvelmult = self.turn_time;
newveldiv = 1/(self.turn_time + 1);
newdir=(olddir*oldvelmult + huntdir)*newveldiv;
if(self.hoverz)//Slow down on turns
speed_mod=olddir*newdir;
else
speed_mod=1;
if(speed_mod<0.05)
speed_mod=0.05;
if(self.velocity!=huntdir*self.speed)
self.velocity=(olddir*oldvelmult + huntdir)*newveldiv*self.speed*speed_mod;
}
//give slight waver
if(self.veer)
Veer(self.veer);
self.movedir=normalize(self.velocity);
if(self.think==HomeThink)
{
if(self.lifetime<time)
self.th_die();
else
thinktime self : self.homerate;
}
}
void HomeThinkEnt(entity who)
{
local vector huntdir;
//fire reaction to water removed b/c depended on what self was
if(who.enemy!=world&&!who.lockentity)
if(!visible2ent(who.enemy,who)||!who.enemy.health||!who.enemy.flags2&FL_ALIVE)
{
//if you can't see him, don't track (and look for someone else?)
who.oldenemy=who.enemy;//remember him
who.enemy=world;
}
if(who.enemy==world)
{
if(random()<0.3||who.think!=HomeThink)//findradius was too damn costly!!!
HomeFindTargetEnt(who);
if(who.enemy==world&&who.oldenemy!=world&&visible(who.oldenemy)&&who.oldenemy.health&&(who.oldenemy.flags2&FL_ALIVE))
who.enemy=who.oldenemy;
}
if(who.enemy!=world&&visible2ent(who.enemy,who))
{
vector olddir, newdir;
float oldvelmult , newveldiv, speed_mod;
olddir=normalize(who.velocity);
if(who.enemy.classname=="player"&&who.enemy.view_ofs!='0 0 0')
huntdir=who.enemy.origin+who.enemy.view_ofs;
else
huntdir=(who.enemy.absmin+who.enemy.absmax)*0.5;
huntdir = normalize(huntdir-who.origin);
oldvelmult = who.turn_time;
newveldiv = 1/(who.turn_time + 1);
newdir=(olddir*oldvelmult + huntdir)*newveldiv;
if(who.hoverz)//Slow down on turns
speed_mod=olddir*newdir;
else
speed_mod=1;
if(speed_mod<0.05)
speed_mod=0.05;
if(who.velocity!=huntdir*who.speed)
who.velocity=(olddir*oldvelmult + huntdir)*newveldiv*who.speed*speed_mod;
}
//give slight waver
//veer gone (ref to self)
who.movedir=normalize(who.velocity);
if(who.think==HomeThink)
{
if(who.lifetime<time)
who.th_die();
else
thinktime who : who.homerate;
}
}
/*
=====================================================
void SpiralThink()
MG
Makes a spinning projectile move in a spiral
pattern based on how fast it's spinning.
NOTE: Has to be rolling (have avelocity_z) to work
Values used:
.movedir is forward,
.speed is speed;
.anglespeed is spiral width
.cnt is how much to increase anglespeed each think
====================================================
*/
void SpiralThink()
{
vector newangles;
makevectors(self.angles);
self.velocity=self.movedir*self.speed;
newangles=vectoangles(self.velocity);
self.angles_y=newangles_y;
self.angles_x=newangles_x;
if(self.cnt!=0)
self.anglespeed+=self.cnt;
self.velocity+=v_right*self.anglespeed;
if(self.think==SpiralThink)
thinktime self : 0.1;
}
/*
=====================================================
void Missile_Arc (void)
MG
Simulates pull of gravity on missiles without
independant means of propulsion, without having
to use MOVETYPE_BOUNCE where things fall too fast.
Also adjusts the pitch to the new dir, which is
nice for arrows and ballista shots.
A good idea is to call this after a delay time
so the missile won't start falling right away.
====================================================
*/
void Missile_Arc (void)
{
vector newpitch;
self.velocity_z-=60;
newpitch=vectoangles(self.velocity);
self.angles_x=newpitch_x;
self.think=Missile_Arc;
thinktime self : 0.1;
}