Git Repos / fte_dogmode / qc / base_proj.qc
Last update to this file was on 2024-06-15 at 19:50.
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
//======================================================================
#ifdef SSQC
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
#endif
//======================================================================
// fields
//======================================================================
#ifdef SSQC
.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
#endif
//======================================================================
// forward declarations
//======================================================================
#ifdef SSQC
// 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;
#endif
#ifdef SSQC
// 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;
#endif
#ifdef SSQC
// base_projectile_qcphys
void() base_projectile_qcphys_physics;
void(entity e) base_projectile_qcphys_init;
strip void() base_projectile_qcphys;
#endif
//------------------------------------------------------------------------------
#ifdef SSQC
//----------------------------------------------------------------------
// 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.th_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);
};
// };
#endif
#ifdef SSQC
//----------------------------------------------------------------------
// 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,
VOL_HIGH, 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",
// VOL_HIGH, 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;
}
// corpses are SOLID_TRIGGER & we want to touch them -- CEV
if (other.classgroup & CG_CORPSE)
return FALSE;
// don't touch trigger fields
if (other.solid == SOLID_TRIGGER)
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);
};
// };
#endif
#ifdef SSQC
//----------------------------------------------------------------------
// 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);
};
// };
#endif
Return to the top of this page or return to the overview of this repo.
Log base_proj.qc
Date | Commit Message | Author | + | - |
---|---|---|---|---|
2024-06-15 | Major update, committing as-is, will have bugs | cev | +25 | -4 |
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.