djcev.com

//

Git Repos / fte_dogmode / qc / base_proj.qc

Last update to this file was on 2024-04-12 at 18:56.

Show base_proj.qc

//==============================================================================
// base_projectile.qc -- generated projectile entities -- CEV
//==============================================================================

/*
========================================================================
Projectile init checklist, required & optional settings, in suggested
order, for reference when writing a projectile constructor function

1. classtype & classname (required)
2. movetype & solid (required)
3. set touch, pain, and destroy (if desired)
4. set aflags
a. set PROJECTILE_EXPLOSIVE if necessary (parse_projexpl)
5. set health and takedamage if desired (for explosives)
6. angles (required) & avelocity
7. check (and set if necessary) proj_basespeed
8. check (and set if necessary) direct_damage and splash_damage
9. setmodel and skin (required)
10. setup_homing
11. setsize, setorigin (required)
12. think & nextthink (if not capable of homing)

see the files in projectiles/ for examples -- CEV
========================================================================
*/

//======================================================================
// Constants
//======================================================================

enum
{
EXPLOSION_THINK_1, // explosion frame/think tracking
EXPLOSION_THINK_2,
EXPLOSION_THINK_3,
EXPLOSION_THINK_4,
EXPLOSION_THINK_5,
EXPLOSION_THINK_6,
EXPLOSION_THINK_REMOVE
};

enumflags
{
PROJECTILE_EXPLOSIVE, // .aflag projectile options
PROJECTILE_DESTROYED
};

const float BP_BOUNCE_BACKOFF = 1.5; // MOVETYPE_BOUNCE clipvel mult; id1 1.5
const float BP_BOUNCE_MIN_ZVEL = 60; // MOVETYPE_BOUNCE stop if _z less than

//======================================================================
// fields
//======================================================================

.float direct_damage; // for t_damage
.float splash_damage; // for t_radiusdamage

// the following are really monster fields. they alter projectile
// behavior and get transferred to projectile objects so they're
// defined here -- CEV
.float homing; // projectile homing
.float projexpl; // explosive?
.float proj_speed_mod; // projectile speed modifier
.float proj_basespeed; // projectile base speed
.vector cust_avelocity; // projectile custom avelocity

//======================================================================
// forward declarations
//======================================================================

// base_explosion
void(vector org) write_explosion;
void(vector org) write_explosion2;
void() base_explosion_think;
void(entity e) become_base_explosion;
entity(vector org) spawn_base_explosion;
void(entity e) base_explosion_init;
strip void() base_explosion;

// base_projectile
float(float testexpl, float offset) base_projectile_parse_projexpl;
void(entity p) write_projectile_impact;
void() base_projectile_homing_think;
void(entity mis, float speed) base_projectile_setup_homing;
void() base_projectile_touch_explosive;
void() base_projectile_touch_normal;
float() base_projectile_check_touch;
void() base_projectile_touch;
void(entity e) base_projectile_init;
strip void() base_projectile;

// base_projectile_qcphys
void() base_projectile_qcphys_physics;
void(entity e) base_projectile_qcphys_init;
strip void() base_projectile_qcphys;

//------------------------------------------------------------------------------

//----------------------------------------------------------------------
// standard id1 explosion sprite + a few helper functions -- CEV
//----------------------------------------------------------------------
// class base_explosion: base_tempentity
// {
//--------------------------------------------------------------
void(vector org) write_explosion =
{
WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
WriteByte (MSG_BROADCAST, TE_EXPLOSION);
WriteCoord (MSG_BROADCAST, org_x);
WriteCoord (MSG_BROADCAST, org_y);
WriteCoord (MSG_BROADCAST, org_z);
};

//--------------------------------------------------------------
void(vector org) write_explosion2 =
{
WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
WriteByte (MSG_BROADCAST, TE_EXPLOSION2);
WriteCoord (MSG_BROADCAST, org_x);
WriteCoord (MSG_BROADCAST, org_y);
WriteCoord (MSG_BROADCAST, org_z);
};

//==============================================================
// Interaction
//==============================================================

//--------------------------------------------------------------
void() base_explosion_think =
{
if (self.state == EXPLOSION_THINK_REMOVE)
{
remove (self);
return;
}

if (self.state == EXPLOSION_THINK_1)
self.frame = 0;
else
self.frame += 1;

self.state += 1;
self.nextthink = time + 0.1;
};

//==============================================================
// Initialization
//==============================================================

//--------------------------------------------------------------
// replacement for BecomeExplosion -- CEV
//--------------------------------------------------------------
void(entity e) become_base_explosion =
{
e.customphysics = __NULL__;
e.destroy = __NULL__;
e.pain = __NULL__;
e.takedamage = DAMAGE_NO;
base_explosion_init (e);
};

//--------------------------------------------------------------
// spawn a brand-spanking new explosion at org -- CEV
//--------------------------------------------------------------
entity(vector org) spawn_base_explosion =
{
local entity e = spawn ();

e.origin = org;
base_explosion_init (e);
return e;
};

//--------------------------------------------------------------
void(entity e) base_explosion_init =
{
base_tempentity_init (e);

e.classname = "base_explosion";
e.classtype = CT_TEMP_EXPLOSION;
e.movetype = MOVETYPE_NONE;
e.solid = SOLID_NOT;
e.state = EXPLOSION_THINK_1;
e.think = base_explosion_think;
e.touch = sub_null;
e.use = sub_null;
e.velocity = '0 0 0';
if (e.origin == '0 0 0')
dprint ("spawn_base_explosion: zero origin\n");
setorigin (e, e.origin);
setmodel (e, "progs/s_explod.spr");
sub_runvoidas (e, base_explosion_think);
};

//--------------------------------------------------------------
strip void() base_explosion =
{
base_explosion_init (self);
};
// };

//----------------------------------------------------------------------
// class base_projectile: base_tempentity
// {

//--------------------------------------------------------------
float(float testexpl, float offset) base_projectile_parse_projexpl =
{
return testexpl == 1 ||
(testexpl == 2 && offset % 2 == 0) ||
(testexpl == 3 && random() * 2 < 1);
};

//--------------------------------------------------------------
// I'm sure there's a better way to do this -- CEV
//--------------------------------------------------------------
void(entity p) write_projectile_impact =
{
WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
if (p.snd_hit != __NULL__ && p.snd_hit != "")
{
// dumptruck_ds
sound (p, CHAN_WEAPON, p.snd_hit, 1, ATTN_STATIC);
WriteByte (MSG_BROADCAST, TE_GUNSHOT);
}
else if (p.classtype == CT_PROJECTILE_BULLET)
{
WriteByte (MSG_BROADCAST, TE_GUNSHOT);
}
else if (p.classtype == CT_PROJECTILE_WIZARDMISSILE)
{
WriteByte (MSG_BROADCAST, TE_WIZSPIKE);
}
else if (p.classtype == CT_PROJECTILE_HKNIGHT)
{
WriteByte (MSG_BROADCAST, TE_KNIGHTSPIKE);
}
else
{
WriteByte (MSG_BROADCAST, TE_SPIKE);
}
WriteCoord (MSG_BROADCAST, p.origin_x);
WriteCoord (MSG_BROADCAST, p.origin_y);
WriteCoord (MSG_BROADCAST, p.origin_z);
};

//--------------------------------------------------------------
void() base_projectile_homing_think =
{
local vector dir, vtemp;
vtemp = self.enemy.origin + '0 0 10';

if (self.enemy.health < 1)
{
remove (self);
return;
}

dir = normalize (vtemp - self.origin);
// can't do better than 100% homing
if (self.homing < 1 && self.homing > 0)
{
// This finds a vector somewhere between the vector
// the projectile is currently travelling on and
// the vector that it would normally snap to for
// homing
//
// homing = .25 means it will go 25% to the new
// direction, but keep 75% of the original vector,
// resulting in a wider turning range.
dir = normalize((dir * self.homing) +
normalize(self.velocity * (1 - self.homing)));
}

if (!self.avelocity)
self.angles = vectoangles (dir);

// SetSpeed
self.velocity = dir * min (self.proj_basespeed,
world_maxvelocity);

if (self.homing > 0)
{
if (self.homing < 1 && self.attack_finished &&
self.attack_finished < time)
{
// dprint("incrementing homing | ");
// dprint("old: ");
// dprint(ftos(self.homing));
// dprint(" | new: ");
self.homing = self.homing + 0.005;
// dprint(ftos(self.homing));
// dprint("\n");
}

self.nextthink = time + 0.2;
self.think = base_projectile_homing_think;
}
};

//--------------------------------------------------------------
void(entity mis, float speed) base_projectile_setup_homing =
{
local vector dir;
local float dist, flytime, speedmod;

if (!mis.owner)
{
objerror (sprintf("base_projectile_setup_homing: "
"%s has no owner!\n", mis.classname));
return;
}

if (!mis.enemy)
{
objerror (sprintf("base_projectile_setup_homing: "
"%s has no enemy!\n", mis.classname));
return;
}

if (mis.owner.proj_speed_mod > 1)
speedmod = 1 / mis.owner.proj_speed_mod;
else if (speed > 250)
speedmod = 1 / (speed / 250);
else
speedmod = 1;

dir = normalize ((mis.enemy.origin + '0 0 10') - mis.origin);
dist = vlen (mis.enemy.origin - mis.origin);
flytime = dist * 0.002 * speedmod;
if (flytime < 0.1)
flytime = 0.1;

mis.proj_basespeed = speed;
mis.homing = mis.owner.homing;
mis.think = base_projectile_homing_think;
mis.nextthink = flytime + time;
if (mis.owner.waitmin > 0)
// store time to start increasing
mis.attack_finished = time + mis.owner.waitmin;
};

//==============================================================
// Interaction
//==============================================================

//--------------------------------------------------------------
// was T_MissileTouch -- CEV
//--------------------------------------------------------------
void() base_projectile_touch_explosive =
{
// standard id1 behavior is to add 1-20 on a direct hit.
// I've removed that here. -- CEV
// damg = direct_damage + random() * 20;

if (other)
if (other.health)
// TODO CEV
if (other.classtype == CT_MONSTER_SHAMBLER)
// mostly immune
t_damage2 (other, self, self.owner,
self.direct_damage * 0.5);
else
t_damage2 (other, self, self.owner,
self.direct_damage);
else
other = world;

// do before radius damage -- CEV
if (self.takedamage)
self.takedamage = DAMAGE_NO;

// don't do radius damage to the other, because all the damage
// was done in the impact
t_radiusdamage2 (self, self.owner, self.splash_damage, other);

// sound (self, CHAN_WEAPON, "weapons/r_exp3.wav",
// 1, ATTN_NORM);
self.origin = self.origin - 8 * normalize (self.velocity);

// BecomeExplosion
write_explosion (self.origin);
become_base_explosion (self);
};

//--------------------------------------------------------------
// was spike_touch -- CEV
//--------------------------------------------------------------
void() base_projectile_touch_normal =
{
// hit something that bleeds
if (other.takedamage)
{
spawn_touchblood (self.direct_damage);
t_damage2 (other, self, self.owner, self.direct_damage);
}
else
{
write_projectile_impact (self);
}

remove (self);
};


//--------------------------------------------------------------
// returns TRUE if the touch function shouldn't continue
//--------------------------------------------------------------
float() base_projectile_check_touch =
{
// don't damage or touch owner -- CEV
if (other == self.owner)
return TRUE;

// remove any projectile that hits the sky -- CEV
if (pointcontents(self.origin) == CONTENT_SKY)
{
remove (self);
return TRUE;
}

if (other.solid == SOLID_TRIGGER)
// trigger field, do nothing
return TRUE;

return FALSE;
};

//--------------------------------------------------------------
void() base_projectile_touch =
{
if (base_projectile_check_touch())
return;

if (self.aflag & PROJECTILE_EXPLOSIVE)
base_projectile_touch_explosive ();
else
base_projectile_touch_normal ();
};

//==============================================================
// Initialization
//==============================================================

//--------------------------------------------------------------
void(entity e) base_projectile_init =
{
base_tempentity_init (e);

e.classgroup |= CG_PROJECTILE;

// default value for proj_speed_mod is 1 -- CEV
if (!e.proj_speed_mod)
e.proj_speed_mod = 1;

e.touch = base_projectile_touch;
};

//--------------------------------------------------------------
strip void() base_projectile =
{
base_projectile_init (self);
};
// };

//----------------------------------------------------------------------
// Custom Physics for projectiles. Subclass this if you want a projectile
// to have one size for other objects to collide with but another size
// when moving through the world. Useful for creating shootable grenades
// and missiles. This is slower than engine physics so use sparingly. -- CEV
//----------------------------------------------------------------------
// class base_projectile_qcphys: base_projectile
// {
//--------------------------------------------------------------
// This is a vague imitation of SV_Physics_Toss as found in
// Ironwail but with an alternate mins and maxs when testing
// movement -- CEV
//--------------------------------------------------------------
void() base_projectile_qcphys_physics =
{
if (self.think && self.nextthink > 0.0f)
{
if (self.nextthink < time)
{
self.nextthink = 0.0f;
self.think ();
}
}

// were we removed by think?
if (!self)
return;

// are we onground?
if (self.flags & FL_ONGROUND)
return;

// restore mins/maxs
if (self.mins == '0 0 0' || self.maxs == '0 0 0')
{
self.mins = self.pos1;
self.maxs = self.pos2;
}

// move angles
if (self.avelocity)
self.angles += self.avelocity * frametime;

// apply gravity if desired
if (self.movetype == MOVETYPE_BOUNCE)
{
local float grav = self.gravity ? self.gravity : 1.0;
self.velocity_z -= grav * world_gravity * frametime;
}

// test the move with an alternate mins and maxs (pos1 and
// pos2) and suitable flags for FLYMISSILE if set -- CEV
tracebox (self.origin, self.pos1, self.pos2,
self.origin + (self.velocity * frametime),
self.movetype == MOVETYPE_FLYMISSILE ?
MOVE_MISSILE: FALSE,
self);

if (trace_allsolid || trace_startsolid)
{
// we're stuck in something or other
dprint (sprintf("base_projectile_qcphys_physics: "
"missile classname %s in solid entity %s "
"at %v\n", self.classname, trace_ent.classname,
trace_endpos));

self.solid = SOLID_NOT;
local entity oldother2 = other;
other = trace_ent;
if (self.destroy)
self.destroy ('0 0 0');
else if (self.touch)
self.touch ();
else
remove (self);
other = oldother2;
return;
}

// clear to accept the move
setorigin (self, trace_endpos);

if (trace_fraction >= 1.0f)
{
// didn't hit anything; touch triggers and exit
touchtriggers ();
return;
}

// immediate clipvelocity for MOVETYPE_BOUNCE -- CEV
if (self.movetype == MOVETYPE_BOUNCE)
{
self.velocity -= trace_plane_normal *
((self.velocity * trace_plane_normal) *
BP_BOUNCE_BACKOFF);

// set ONGROUND while we're at it -- CEV
if (trace_plane_normal_z > 0.7)
{
if (self.velocity_z < BP_BOUNCE_MIN_ZVEL)
{
self.flags |= FL_ONGROUND;
self.groundentity = trace_ent;
self.groundnormal = trace_plane_normal;
self.velocity = '0 0 0';
self.avelocity = '0 0 0';
}
}
}

// SV_Impact lookalike -- CEV
local entity oldother = other;
// store trace_ent in case touch functions below perform
// a trace -- CEV
local entity touched = trace_ent;
if (self && touched)
{
// we can make some assumptions and skip a few
// checks here -- CEV
other = touched;
self.touch ();

// check trace_ent again just in case the call
// to touch above somehow removed it -- CEV
if (touched && touched.touch &&
touched.touch != sub_null &&
touched.solid != SOLID_NOT)
{
if (self)
other = self;
else
other = world;

local entity stemp = self;
self = touched;
self.touch ();
self = stemp;
}
}
else if (self && trace_fraction < 1.0f)
{
// for the bounce sound
other = world;
self.touch ();
}
other = oldother;

// be sure to touch triggers! -- CEV
if (self)
touchtriggers ();
};

//--------------------------------------------------------------
void(entity e) base_projectile_qcphys_init =
{
base_projectile_init (e);

if (!e.pos1)
e.pos1 = '-1 -1 -1';
if (!e.pos2)
e.pos2 = '1 1 1';

e.customphysics = base_projectile_qcphys_physics;
};

//--------------------------------------------------------------
strip void() base_projectile_qcphys =
{
base_projectile_qcphys_init (self);
};
// };

Return to the top of this page or return to the overview of this repo.

Log base_proj.qc

Date Commit Message Author + -
2024-04-12 Moveable gibs, heads, some bugfixes cev +16 -20
2024-04-08 Registered monsters, projectile bugfixes cev +44 -26
2024-04-05 Player footsteps, shareware monsters, misc? cev +3 -2
2024-03-24 Fix projectile and func_ blocked interaction cev +7  
2024-03-24 2nd pass refactor, rework QC class structure cev +615  

Return to the top of this page or return to the overview of this repo.