djcev.com

//

Git Repos / fte_dogmode / qc / base_entities.qc

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

Show base_entities.qc

//==============================================================================
// base_entities.qc -- top-level entities (entity, tempentity, mapentity)
//==============================================================================

//======================================================================
// constants
//======================================================================

const float STATE_ACTIVE = 0; // .estate field values
const float STATE_INACTIVE = 1;
const float STATE_INVISIBLE = 8;

//======================================================================
// globals
//======================================================================

#ifdef SSQC
entity activator; // entity that activated trigger/brush
entity damage_attacker; // set by T_Damage
// string lastnameused; // targetname last used to trigger
#endif

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

.float aflag; // trigger_filter, func_counter, item_
.float alpha; // translucency in supported engines
.float cnt; // misc flag
.float color; // Hipnotic
.float count; // for counting triggers
.float damage_mod; // dumptruck_ds
.float delay; // time from activation to firing
.float distance;
.float dmg; // damage done by door when hit
.float height;
.float gravity; // from custdefs.qc by way of Hipnotic
.float is_waiting; // wait until activated before trigger?
.float lefty; // used by monsters and func_bob
.float light_lev; // not used ingame, parsed by light util
.float speed;
.float speed2;
.float state; // plats / doors / buttons
.float style, style2;
.float t_length, t_width; // func_, monster face, player LG, etc
.float wait; // time from firing to restarting
.float waitmin; // sounds (removed: waitmax)
.float wantedgravity; // thanks Spike!
.float volume; // sounds

.float estate; // entity state
.float prevstate; // previous entity state
.void() olduse; // previous use function

.string mdl; // object stuff
.string message2; // func_laser & trigger_heal
.string noise4; // noise & noise1-3 defined in entvars_t
.string origmodel; // switchables brushes (added by bmFbr)

.vector mangle; // angle at start
.vector pos1; // used by some func_ classes
.vector pos2;

.entity oldenemy; // mad at this ent before taking damage
.entity trigger_field; // TODO CEV: cutscene, door, shambler

// variables for enhanced triggering from Custents -- dumptruck_ds
// .string target // defined in entvars_t -- CEV
.string target2; // second target's name
.string target3; // third target's name
.string target4; // fourth target's name
// .string targetname // defined in entvars_t -- CEV
.string targetname2; // second name
.string targetname3; // third name
.string targetname4; // fourth name
.string killtarget; // first target to kill
.string killtarget2; // second target to kill
.string pain_target; // dumptruck_ds

.void(entity src, float amount) pain; // th_pain
.void(vector dir) destroy; // th_die

#ifdef SSQC
// Called by the engine whenever an entity needs to be (re)sent to a client's
// csprogs, either because SendFlags was set or because data was lost. Must
// write its data to the MSG_ENTITY buffer. Will be called at the engine's
// leasure.
.float(entity e, float changedflags) SendEntity;
// Indicates that something in the entity has been changed, and that it needs
// to be updated to all players that can see it. The engine will clear it at
// some point, with the cleared bits appearing in the 'changedflags' argument
// of the SendEntity method.
.float SendFlags;
#endif

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

// base entity
#ifdef SSQC
float(entity src, entity dest) can_damage;
void(entity targ, entity inflictor, entity attacker, vector dir) killed;
void(entity inflictor, entity attacker, float damage, entity ignore)
t_radiusdamage2;
void(entity attacker, float damage) t_beamdamage2;
void(entity targ, entity inflictor, entity attacker, float damage) t_damage2;
#endif
#if defined(CSQC) || defined(SSQC)
void() sub_null; // Subs
void(entity attacker, float damage) sub_nullpain;
void(vector dir) sub_nulldestroy;
void() sub_remove;
void() sub_remove_fade;
void(entity doas, void() f) sub_runvoidas;
float(entity doas, float() f) sub_runfloatas;
float(entity toucher) sub_checkvalidtouch;
#endif
#ifdef SSQC
void() sub_usetargets;
void(string matchstring, .string matchfield) sub_killtarget;
void(string matchstring, .string matchfield) sub_usetarget;
#endif

// base_tempentity
#ifdef SSQC
void(entity e) base_tempentity_init;
strip void() base_tempentity;
#endif

// temp_delayed_targets
#ifdef SSQC
void() temp_delayed_targets_think;
entity() spawn_temp_delayed_targets;
void(entity e) temp_delayed_targets_init;
strip void() temp_delayed_targets;
#endif

// base_mapentity
#ifdef SSQC
void(entity e) sub_setmovedir;
void() sub_useandforgettargets;
void(entity e) base_mapentity_init;
strip void() base_mapentity;
#endif

// noclass
#ifdef SSQC
void() noclass;
#endif

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

//----------------------------------------------------------------------
// class base_entity: entity
// {
//==============================================================
// Damage Functions
//==============================================================

#ifdef SSQC

//--------------------------------------------------------------
// CanDamage
// Returns true if the source (inflictor) can directly damage
// the destination (target). Used for explosions and melee attacks.
//--------------------------------------------------------------
float(entity src, entity dest) can_damage =
{
// bmodels need special checking because their origin is 0,0,0
if (dest.movetype == MOVETYPE_PUSH)
{
traceline (src.origin,
0.5 * (dest.absmin + dest.absmax), TRUE, self);
if (trace_fraction == 1)
return TRUE;
if (trace_ent == dest)
return TRUE;
return FALSE;
}

traceline (src.origin, dest.origin, TRUE, self);
if (trace_fraction == 1)
return TRUE;
traceline (src.origin, dest.origin + '15 15 0', TRUE, self);
if (trace_fraction == 1)
return TRUE;
traceline (src.origin, dest.origin + '-15 -15 0', TRUE, self);
if (trace_fraction == 1)
return TRUE;
traceline (src.origin, dest.origin + '-15 15 0', TRUE, self);
if (trace_fraction == 1)
return TRUE;
traceline (src.origin, dest.origin + '15 -15 0', TRUE, self);
if (trace_fraction == 1)
return TRUE;

return FALSE;
};

//--------------------------------------------------------------
// Killed
//--------------------------------------------------------------
void(entity targ, entity inflictor, entity attacker, vector dir)
killed =
{
local entity stemp = self;
self = targ;

if (self.flags & FL_CLIENT && self.health < -99)
// don't let sbar look bad if a player
// also limits gib speed (velocity_for_damage) -- CEV
self.health = -99;

if (self.movetype == MOVETYPE_NONE ||
self.movetype == MOVETYPE_PUSH ||
self.movetype == MOVETYPE_FLYMISSILE ||
self.movetype == MOVETYPE_BOUNCE)
{
// doors, triggers, missiles, etc
if (self.destroy)
{
// origin, absmin/max, and other properties
// are different for brush entities so figure
// dir from the inflictor's angles -- CEV
makevectors (inflictor.angles);
self.destroy (normalize(v_forward));
}
self = stemp;
return;
}

self.enemy = attacker;

// bump the monster counter
if (self.flags & FL_MONSTER)
{
killed_monsters = killed_monsters + 1;
WriteByte (MSG_ALL, SVC_KILLEDMONSTER);
}

ClientObituary (self, inflictor, attacker);

self.takedamage = DAMAGE_NO;
self.touch = sub_null;

if (self.classgroup & CG_MONSTER)
sub_death_use ();

if (self.destroy)
self.destroy (dir);

self = stemp;
};

//--------------------------------------------------------------
// T_RadiusDamage
//--------------------------------------------------------------
void(entity inflictor, entity attacker, float damage, entity ignore)
t_radiusdamage2 =
{
local float points;
local entity head;
local vector org;

head = findradius (inflictor.origin, damage + 40);

while (head)
{
if (head == ignore || !head.takedamage)
{
head = head.chain;
continue;
}

org = head.origin + (head.mins + head.maxs) * 0.5;
points = 0.5 * vlen (inflictor.origin - org);
if (points < 0)
points = 0;
points = damage - points;
if (head == attacker)
points = points * 0.5;

if (points > 0)
{
if (can_damage(inflictor, head))
{
// shambler takes half damage from
// all explosions
if (head.classtype ==
CT_MONSTER_SHAMBLER)
{
t_damage2 (head, inflictor,
attacker, points * 0.5);
}
else
{
t_damage2 (head, inflictor,
attacker, points);
}
}
}
head = head.chain;
}
};

//--------------------------------------------------------------
// T_BeamDamage
//--------------------------------------------------------------
void(entity attacker, float damage) t_beamdamage2 =
{
local float points;
local entity head;

head = findradius (attacker.origin, damage + 40);

while (head)
{
if (!head.takedamage)
{
head = head.chain;
continue;
}

points = 0.5 * vlen (attacker.origin - head.origin);
if (points < 0)
points = 0;
points = damage - points;
if (head == attacker)
points = points * 0.5;
if (points > 0)
{
if (can_damage(attacker, head))
{
if (head.classtype ==
CT_MONSTER_SHAMBLER)
{
t_damage2 (head, attacker,
attacker, points * 0.5);
}
else
{
t_damage2 (head, attacker,
attacker, points);
}
}
}
head = head.chain;
}
};

//--------------------------------------------------------------
// T_Damage
// The damage is coming from inflictor, but get mad at attacker
// This should be the only function that ever reduces health.
//--------------------------------------------------------------
void(entity targ, entity inflictor, entity attacker, float damage)
t_damage2 =
{
local entity stemp;
local vector dir = '0 0 0';
local float save;
local float take;
local float ignore_armor; // johnfitz
local string death_type; // johnfitz

// don't try to damage the world. Not healthy -- bmFbr
if (!targ)
return;

// make sure targ.deathtype doesn't keep stale info
// after this function is done -- johnfitz
death_type = targ.deathtype;
targ.deathtype = "";
// johnfitz

if (!targ.takedamage)
return;

// some func_breakables ignore monster damage -- johnfitz
// dded from Rubicon2 combat.qc -- dumptruck_ds
if (targ.classtype == CT_FUNC_BREAKABLE)
{
if (targ.spawnflags & BREAKABLE_NO_MONSTERS &&
attacker.flags & FL_MONSTER)
{
return;
}
}
// johnfitz

// used by buttons and triggers to set activator for
// target firing
damage_attacker = attacker;

// check for quad damage powerup on the attacker
if (attacker.super_damage_finished > time)
damage = damage * 4;

// damage mod for monsters -- dumptruck_ds
if (attacker.damage_mod)
damage = damage * attacker.damage_mod;

// don't deplete armor if drowning/burning, or
// protected by biosuit/pentagram/godmode (note:
// in ID1 // pentagram/godmode doesn't actually
// protect your armor)
if (death_type == "burning" || death_type == "drowning" ||
targ.invincible_finished >= time ||
targ.flags & FL_GODMODE)
{
ignore_armor = TRUE;
}
else
{
ignore_armor = FALSE;
}
// johnfitz

// save damage based on the target's armor level
if (ignore_armor)
{
// some damage doesn't deplete armor -- johnfitz
save = 0;
}
else
{
save = ceil (targ.armortype * damage);
if (save >= targ.armorvalue)
{
save = targ.armorvalue;
// lost all armor
targ.armortype = 0;
targ.items = targ.items - (targ.items &
(IT_ARMOR1 | IT_ARMOR2 | IT_ARMOR3));
}
}
// 1998-08-12 Drowning doesn't hurt armor by Maddes/Athos start
// else
// {
// save = 0;
// }
// 1998-08-12 Drowning doesn't hurt armor by Maddes/Athos end
targ.armorvalue = targ.armorvalue - save;
take = ceil (damage - save);

// add to the damage total for clients, which will be
// sent as a single message at the end of the frame
// FIXME: remove after combining shotgun blasts?
if (targ.flags & FL_CLIENT)
{
targ.dmg_take = targ.dmg_take + take;
targ.dmg_save = targ.dmg_save + save;
targ.dmg_inflictor = inflictor;
}

// apply momentum change to players and monsters -- CEV
if (inflictor != world && (
targ.movetype == MOVETYPE_WALK ||
targ.movetype == MOVETYPE_STEP ||
targ.movetype == MOVETYPE_FLY))
{
// figure momentum direction
dir = targ.origin -
(inflictor.absmin + inflictor.absmax) * 0.5;
dir = normalize (dir);

if (targ.flags & FL_MONSTER && targ.flags & FL_ONGROUND)
targ.flags &= ~FL_ONGROUND;

if (attacker.classtype == CT_PLAYER && attacker == targ)
// standard knockback for self-damage -- CEV
targ.velocity += dir * damage * 8;
else
// decreased knockback otherwise -- CEV
targ.velocity += dir * damage * 4;

/*
dprint (sprintf("t_damage2: inflictor %s adds "
"momentum in direction %v to target %s\n",
inflictor.classname, dir, targ.classname));
*/
}

// check for godmode or invincibility
if (targ.flags & FL_GODMODE)
return;
if (targ.invincible_finished >= time)
{
if (self.invincible_sound < time)
{
sound (targ, CHAN_ITEM, "items/protect3.wav",
1, ATTN_NORM);
self.invincible_sound = time + 2;
}
return;
}

// team play damage avoidance
// 1998-07-29 Teamplay 1 fix by Maddes start
if ((teamplay == 1) && (targ.team > 0) &&
(targ.team == attacker.team) &&
(targ != attacker) &&
(attacker.classtype == CT_PLAYER) &&
// because squishing a teammate is still possible
(inflictor.classtype != CT_FUNC_DOOR))
{
return;
}
// 1998-07-29 Teamplay 1 fix by Maddes end

// do the damage
targ.health = targ.health - take;

// fire pain_target if appropriate
if ((targ.flags & FL_MONSTER) &&
targ.pain_target != "" &&
targ.health <= targ.pain_threshold)
{
sub_runvoidas (targ, sub_pain_use);
}

if (targ.health <= 0)
{
killed (targ, inflictor, attacker, dir);
return;
}

// react to the damage
stemp = self;
self = targ;

if ((self.flags & FL_MONSTER) && attacker != world)
{
// get mad unless of the same class (except soldiers)
if (self != attacker && attacker != self.enemy)
{
local float mode = 0;
// take highest mode so infighting
// happens consistently
if (self.infight_mode == -1 ||
self.infight_mode > mode)
{
mode = self.infight_mode;
}
if (mode != -1 && attacker.infight_mode > mode)
{
mode = attacker.infight_mode;
}
// soldiers of the same style will infight
// -- dumptruck_ds - thanks for c0burn and
// Shamblernaut for your help!
if (mode == -1)
{
if (attacker.classtype == CT_PLAYER)
{
if (self.enemy.classtype ==
CT_PLAYER)
self.oldenemy =
self.enemy;
self.enemy = attacker;
ai_foundtarget ();
}
}
else if ((self.classname != attacker.classname)
|| ((self.classtype == CT_MONSTER_GRUNT)
&& (self.style == attacker.style)) ||
// infight if different models
(mode > 0 &&
self.mdl_body != attacker.mdl_body) ||
// infight if different skin
(mode > 1 &&
self.skin != attacker.skin) ||
// always infight
(mode > 2))
{
if (self.enemy.classtype == CT_PLAYER)
self.oldenemy = self.enemy;
self.enemy = attacker;
ai_foundtarget ();
}
}
}

if (self.pain)
{
self.pain (attacker, take);
// nightmare mode monsters don't go into
// pain frames often
if (skill == 3)
self.pain_finished = time + 5;
}

self = stemp;
};
#endif

//==============================================================
// Subs
//==============================================================

#if defined(CSQC) || defined(SSQC)
//--------------------------------------------------------------
void() sub_null =
{
// no-op
};

//--------------------------------------------------------------
void(entity attacker, float damage) sub_nullpain =
{
// no-op
};

//--------------------------------------------------------------
void(vector dir) sub_nulldestroy =
{
// no-op
};

//--------------------------------------------------------------
void() sub_remove =
{
remove (self);
};

//--------------------------------------------------------------
void() sub_remove_fade =
{
if (self.alpha <= 0)
{
remove (self);
}
else
{
self.alpha -= 0.05;
self.think = sub_remove_fade;
self.nextthink = time + 0.1;
}
};

//--------------------------------------------------------------
// SUB_CallAsSelf
//--------------------------------------------------------------
void(entity doas, void() f) sub_runvoidas =
{
if (doas == world)
return;

local entity stemp = self;
self = doas;
f ();
self = stemp;
};

//--------------------------------------------------------------
// SUB_CallAsSelfFloat
//--------------------------------------------------------------
float(entity doas, float() f) sub_runfloatas =
{
if (doas == world)
return 0;

local entity stemp = self;
local float fl;
self = doas;
fl = f ();
self = stemp;
return fl;
};

//--------------------------------------------------------------
// was CheckValidTouch(); needed for both spawnable map entities
// and temporary entities -- CEV
//--------------------------------------------------------------
float(entity toucher) sub_checkvalidtouch =
{
if (toucher.classtype != CT_PLAYER)
return FALSE;

if (toucher.health <= 0)
return FALSE;

if (toucher.movetype == MOVETYPE_NOCLIP)
return FALSE;

if (self.estate != STATE_ACTIVE)
return FALSE;

return TRUE;
};
#endif

#ifdef SSQC
//--------------------------------------------------------------
void() sub_usetargets =
{
if (self.estate != STATE_ACTIVE)
return;

if (self.delay)
{
// create a temp object to fire at a later time
spawn_temp_delayed_targets ();
return;
}

local entity t;
local string s = "";

// print the message
if (self.message != "" && !(self.flags & FL_NOCENTERPRINT))
{
if (self.spawnflags & TRIGGER_CENTERPRINTALL)
{
t = findfloat (world, classtype, CT_PLAYER);
while (t)
{
centerprint (t, self.message);
if (!self.noise)
sound (t, CHAN_VOICE,
"misc/talk.wav",
1, ATTN_NORM);
t = findfloat (t, classtype, CT_PLAYER);
}
}
else if (activator.classtype == CT_PLAYER)
{
centerprint (activator, self.message);
if (!self.noise)
sound (activator, CHAN_VOICE,
"misc/talk.wav", 1, ATTN_NORM);
}
}

// killtargets
for (float g = 0; g < 2; g++)
{
if (g == 1)
s = self.killtarget2;
else if (g == 0)
s = self.killtarget;

if (s == __NULL__ || s == "")
continue;

for (float h = 0; h < 4; h++)
{
if (h == 3)
sub_killtarget (s, targetname4);
else if (h == 2)
sub_killtarget (s, targetname3);
else if (h == 1)
sub_killtarget (s, targetname2);
else
sub_killtarget (s, targetname);
}

s = "";
}

// regular targets
for (float i = 0; i < 4; i++)
{
if (i == 3)
s = self.target4;
else if (i == 2)
s = self.target3;
else if (i == 1)
s = self.target2;
else
s = self.target;

if (s == __NULL__ || s == "")
continue;

for (float j = 0; j < 4; j++)
{
if (j == 3)
sub_usetarget (s, targetname4);
else if (j == 2)
sub_usetarget (s, targetname3);
else if (j == 1)
sub_usetarget (s, targetname2);
else if (j == 0)
sub_usetarget (s, targetname);
}

s = "";
}
};

//--------------------------------------------------------------
void(string matchstring, .string matchfield) sub_killtarget =
{
local entity t = find (world, matchfield, matchstring);
while (t != world)
{
if (t.switchshadstyle)
lightstyle (t.switchshadstyle, "m");
if (t)
remove (t);
t = find (t, matchfield, matchstring);
}
};

//--------------------------------------------------------------
void(string matchstring, .string matchfield) sub_usetarget =
{
if (matchstring == __NULL__ || matchstring == "")
return;

local entity stemp, otemp;
local entity atemp = activator;
local entity t = find (world, matchfield, matchstring);

while (t != world)
{
stemp = self;
otemp = other;
self = t;
other = stemp;
if (self.use != sub_null)
{
if (self.use)
{
// lastnameused = matchstring;
self.use ();
}
}
self = stemp;
other = otemp;
activator = atemp;
t = find (t, matchfield, matchstring);
}
};
#endif
// };

#ifdef SSQC
//----------------------------------------------------------------------
// base_tempentity -- QC generated temporary entities
//----------------------------------------------------------------------
// class base_tempentity: base_entity
// {
//--------------------------------------------------------------
void(entity e) base_tempentity_init =
{
e.classgroup |= CG_TEMPENTITY;
};

//--------------------------------------------------------------
strip void() base_tempentity =
{
base_tempentity_init (self);
};
// };
#endif

#ifdef SSQC
//----------------------------------------------------------------------
// temp_delayed_targets -- to facilitate delayed SUB_UseTargets
//----------------------------------------------------------------------
// class temp_delayed_targets: base_tempentity
// {
//--------------------------------------------------------------
void() temp_delayed_targets_think =
{
activator = self.enemy;
sub_usetargets ();
remove (self);
};

//--------------------------------------------------------------
// 'self' in this context is the caller, not temp_delayed_targets
//--------------------------------------------------------------
entity() spawn_temp_delayed_targets =
{
// create a temp object to fire at a later time
local entity e = spawn ();

temp_delayed_targets_init (e);

e.enemy = activator;
e.message = self.message;
e.killtarget = self.killtarget;
e.killtarget2 = self.killtarget2;
e.target = self.target;
e.target2 = self.target2;
e.target3 = self.target3;
e.target4 = self.target4;
e.nextthink = time + self.delay;

return e;
};

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

e.classname = "DelayedUse";
e.classtype = CT_TEMP_DELAYEDUSE;
e.think = temp_delayed_targets_think;
};

//--------------------------------------------------------------
strip void() temp_delayed_targets =
{
temp_delayed_targets_init (self);
};
// };
#endif

#ifdef SSQC
//----------------------------------------------------------------------
// base_mapentity -- spawnable mapper-placeable entities
//----------------------------------------------------------------------
// class base_mapentity: base_entity
// {
//==============================================================
// Subs
//==============================================================

//--------------------------------------------------------------
void(entity e) sub_setmovedir =
{
// QuakeEd only writes a single float for angles (bad idea),
// so up and down are just constant angles.
if (e.angles == '0 -1 0')
{
e.movedir = '0 0 1';
}
else if (e.angles == '0 -2 0')
{
e.movedir = '0 0 -1';
}
else
{
makevectors (e.angles);
e.movedir = v_forward;
}

e.angles = '0 0 0';
};

//--------------------------------------------------------------
void() sub_useandforgettargets =
{
sub_usetargets ();

self.delay = 0;
self.killtarget = "";
self.killtarget2 = "";
self.message = "";
self.target = "";
self.target2 = "";
self.target3 = "";
self.target4 = "";
};

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

//--------------------------------------------------------------
void(entity e) base_mapentity_init =
{
e.classgroup |= CG_MAPENTITY;
};

//--------------------------------------------------------------
strip void() base_mapentity =
{
base_mapentity_init (self);
};
// };
#endif

#ifdef SSQC
/*QUAKED noclass (0 0 0) (-8 -8 -8) (8 8 8)
prints a warning message when spawned
*/
//----------------------------------------------------------------------
void() noclass =
{
// new spawnflags for all entities -- iw
if (SUB_Inhibit())
return;

dprint (sprintf("noclass: spawned at %v\n", self.origin));
remove (self);
};
#endif

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

Log base_entities.qc

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