mirror of
https://git.code.sf.net/p/quake/game-source
synced 2024-11-10 06:31:52 +00:00
471 lines
8.1 KiB
C++
471 lines
8.1 KiB
C++
#include "common.qh"
|
|
#include "protocol.qh"
|
|
#include "misc.qh"
|
|
#include "server.qh"
|
|
#include "teleport.qh"
|
|
#include "weapon.qh"
|
|
|
|
.float tl_notsolid;
|
|
|
|
// ===================================================================== //
|
|
|
|
entity (string clname)
|
|
spawn =
|
|
{
|
|
local entity e;
|
|
|
|
e = BUILTIN_spawn ();
|
|
e.classname = clname;
|
|
|
|
return e;
|
|
};
|
|
|
|
// ===================================================================== //
|
|
|
|
void ()
|
|
SUB_remove =
|
|
{
|
|
remove (self);
|
|
};
|
|
|
|
void (entity e)
|
|
safe_remove =
|
|
{
|
|
e.takedamage = DAMAGE_NO;
|
|
e.solid = SOLID_NOT;
|
|
e.model = nil;
|
|
e.modelindex = 0;
|
|
|
|
e.think = SUB_remove;
|
|
e.nextthink = time;
|
|
};
|
|
|
|
// ===================================================================== //
|
|
|
|
void ()
|
|
SUB_makestatic =
|
|
{
|
|
makestatic (self);
|
|
};
|
|
|
|
void (entity e)
|
|
safe_makestatic =
|
|
{
|
|
if (sv_spawning) {
|
|
makestatic (self);
|
|
return;
|
|
}
|
|
|
|
e.takedamage = DAMAGE_NO;
|
|
e.solid = SOLID_NOT;
|
|
|
|
e.think = SUB_makestatic;
|
|
e.nextthink = time;
|
|
};
|
|
|
|
// ===================================================================== //
|
|
|
|
void ()
|
|
_sound_vector_think =
|
|
{
|
|
sound (self, CHAN_VOICE, self.noise1, self.volume, self.attenuation);
|
|
remove (self);
|
|
};
|
|
|
|
void (vector org, string samp, float vol, float atten)
|
|
sound_vector =
|
|
{
|
|
local entity e;
|
|
|
|
e = spawn("SOUND");
|
|
e.origin = org;
|
|
e.noise1 = samp;
|
|
e.volume = vol;
|
|
e.attenuation = atten;
|
|
|
|
e.think = _sound_vector_think;
|
|
e.nextthink = time;
|
|
};
|
|
|
|
// ===================================================================== //
|
|
|
|
float (vector org)
|
|
_missile_th_teleport =
|
|
{
|
|
self.owner = world;
|
|
return TRUE;
|
|
};
|
|
|
|
void (entity mis) missile_check_teleport;
|
|
|
|
void ()
|
|
_missile_always_trigger =
|
|
{
|
|
self.think = SUB_remove;
|
|
self.nextthink = self.attack_finished;
|
|
|
|
if ((self.movetype != MOVETYPE_FLYMISSILE)
|
|
&& (self.movetype != MOVETYPE_FLY))
|
|
return;
|
|
|
|
tl_proj_begin ();
|
|
/* Make sure we're still going to hit the thing */
|
|
traceline (self.origin, self.origin + self.velocity * sv_maxtic * 2, FALSE,
|
|
self);
|
|
if (trace_ent == self.enemy)
|
|
tl_touch (trace_ent, self);
|
|
else
|
|
self.enemy = world;
|
|
tl_proj_end ();
|
|
|
|
missile_check_teleport (self);
|
|
};
|
|
|
|
void (entity mis)
|
|
missile_check_teleport =
|
|
{
|
|
local float duration;
|
|
|
|
tl_proj_begin ();
|
|
|
|
trace_ent = mis;
|
|
trace_endpos = mis.origin;
|
|
duration = mis.attack_finished - time;
|
|
|
|
while (duration > sv_maxtic) {
|
|
traceline (trace_endpos, trace_endpos + mis.velocity * duration,
|
|
FALSE, trace_ent);
|
|
|
|
if (!trace_ent || trace_ent.solid == SOLID_BSP) {
|
|
/* Don't bother tracing through BSP, it won't happen. */
|
|
break;
|
|
}
|
|
|
|
if (tl_issolid (trace_ent) || mis.enemy == trace_ent
|
|
|| (duration * trace_fraction) <= sv_maxtic) {
|
|
/* We hit a triggered trigger,
|
|
a solid ent, or something we _just_ hit */
|
|
if (trace_fraction == 0) {
|
|
/* We're not going anywhere. Fudge it. */
|
|
break;
|
|
}
|
|
/* Trace on past it. */
|
|
duration -= duration * trace_fraction;
|
|
continue;
|
|
}
|
|
|
|
/* Reached a [new] trigger */
|
|
mis.enemy = trace_ent;
|
|
mis.think = _missile_always_trigger;
|
|
mis.nextthink = time + duration * trace_fraction - sv_maxtic;
|
|
break;
|
|
}
|
|
|
|
tl_proj_end ();
|
|
};
|
|
|
|
entity (string clname, string mod, vector org, vector dir, float spd)
|
|
spawn_missile =
|
|
{
|
|
newmis = spawn (clname);
|
|
|
|
newmis.owner = self;
|
|
newmis.goalentity = self;
|
|
|
|
newmis.movetype = MOVETYPE_FLYMISSILE;
|
|
newmis.solid = SOLID_BBOX;
|
|
newmis.deadflag = DEAD_NONLIVING;
|
|
|
|
newmis.th_teleport = _missile_th_teleport;
|
|
|
|
setmodel (newmis, mod);
|
|
setsize (newmis, '0 0 0', '0 0 0');
|
|
setorigin (newmis, org);
|
|
|
|
newmis.angles = vectoangles (dir);
|
|
newmis.speed = spd;
|
|
newmis.velocity = dir * spd;
|
|
|
|
newmis.attack_finished = time + (6000 / spd);
|
|
|
|
newmis.think = SUB_remove;
|
|
newmis.nextthink = newmis.attack_finished;
|
|
|
|
missile_check_teleport (newmis);
|
|
|
|
return newmis;
|
|
};
|
|
|
|
// ===================================================================== //
|
|
|
|
entity tl_first;
|
|
.entity tl_next;
|
|
void ()
|
|
tl_proj_begin =
|
|
{
|
|
local entity walk;
|
|
|
|
tl_first = world;
|
|
for (walk = nextent (world); walk; walk = nextent (walk)) {
|
|
if (walk.solid != SOLID_TRIGGER)
|
|
continue;
|
|
|
|
walk.tl_next = tl_first;
|
|
tl_first = walk;
|
|
|
|
walk.solid = SOLID_BBOX;
|
|
walk.tl_notsolid = TRUE;
|
|
setorigin (walk, walk.origin); // relink
|
|
}
|
|
};
|
|
|
|
void ()
|
|
tl_proj_end =
|
|
{
|
|
local entity walk;
|
|
|
|
for (walk = tl_first; walk; walk = walk.tl_next) {
|
|
walk.solid = SOLID_TRIGGER;
|
|
walk.tl_notsolid = FALSE;
|
|
setorigin (walk, walk.origin); // relink
|
|
}
|
|
};
|
|
|
|
void (entity trigger, entity fake_proj)
|
|
tl_touch =
|
|
{
|
|
local entity oldself, oldother;
|
|
|
|
if (!trigger.touch)
|
|
return;
|
|
|
|
tl_proj_end ();
|
|
|
|
oldself = self;
|
|
oldother = other;
|
|
|
|
self = trigger;
|
|
other = fake_proj;
|
|
|
|
self.touch ();
|
|
|
|
self = oldself;
|
|
other = oldother;
|
|
|
|
tl_proj_begin ();
|
|
};
|
|
|
|
// ===================================================================== //
|
|
|
|
string (entity e)
|
|
name =
|
|
{
|
|
if (e.netname)
|
|
return e.netname;
|
|
if (e.classname)
|
|
return e.classname;
|
|
|
|
if (e.velocity)
|
|
return "an unidentified flying object";
|
|
return "an unidentified stationary object";
|
|
};
|
|
|
|
// ===================================================================== //
|
|
|
|
void (.string fld, string match, void (entity e) func)
|
|
foreach =
|
|
{
|
|
local entity ent;
|
|
|
|
ent = world;
|
|
while ((ent = find (ent, fld, match))) {
|
|
if (!func) {
|
|
dprint ("NULL function in foreach, classname: ", ent.classname,
|
|
"\n");
|
|
continue;
|
|
}
|
|
|
|
func (ent);
|
|
}
|
|
};
|
|
|
|
void (.string fld, string match, .void () func)
|
|
foreach_field =
|
|
{
|
|
local entity oldself;
|
|
|
|
oldself = self;
|
|
|
|
self = world;
|
|
while ((self = find (self, fld, match))) {
|
|
if (!self.func) {
|
|
dprint ("NULL function in foreach_field, classname: ",
|
|
self.classname, "\n");
|
|
continue;
|
|
}
|
|
|
|
self.func ();
|
|
}
|
|
|
|
self = oldself;
|
|
};
|
|
|
|
// ===================================================================== //
|
|
|
|
float (entity newself, entity newother, float () func)
|
|
switcheroo = {
|
|
local entity oldself, oldother;
|
|
local float ret;
|
|
|
|
oldself = self;
|
|
oldother = other;
|
|
|
|
self = newself;
|
|
other = newother;
|
|
|
|
ret = func ();
|
|
|
|
self = oldself;
|
|
other = oldother;
|
|
|
|
return ret;
|
|
};
|
|
|
|
// ===================================================================== //
|
|
|
|
float (float current, float increment, float max)
|
|
increase_bound =
|
|
{
|
|
local float diff;
|
|
|
|
diff = max - current;
|
|
if (diff <= 0)
|
|
return current;
|
|
if (diff > increment)
|
|
diff = increment;
|
|
return current + diff;
|
|
};
|
|
|
|
float (.float fld, float increment, float max)
|
|
increase_field =
|
|
{
|
|
local float new;
|
|
|
|
new = increase_bound (self.fld, increment, max);
|
|
if (new == self.fld)
|
|
return FALSE;
|
|
|
|
self.fld = new;
|
|
|
|
return TRUE;
|
|
};
|
|
|
|
// ===================================================================== //
|
|
|
|
/* Not to be confused with maxspeed */
|
|
float (entity e)
|
|
calc_max_speed =
|
|
{
|
|
local float spd;
|
|
|
|
spd = e.maxspeed;
|
|
|
|
if (self.waterlevel >= 2)
|
|
spd = spd * 0.7; // Bah. Hard coded in engine.
|
|
else if (!self.waterlevel && !(self.flags & FL_ONGROUND)) {
|
|
if (spd > 30)
|
|
spd = 30; // Also hard coded.
|
|
}
|
|
|
|
return spd;
|
|
};
|
|
|
|
// ===================================================================== //
|
|
|
|
entity _xprint_client;
|
|
float _xprint_level;
|
|
void (entity client, float level)
|
|
xprint_start =
|
|
{
|
|
_xprint_client = client;
|
|
_xprint_level = level;
|
|
};
|
|
|
|
void (string str)
|
|
xprint_str =
|
|
{
|
|
msg_entity = _xprint_client;
|
|
WriteBytes (MSG_ONE, SVC_PRINT, _xprint_level);
|
|
WriteString (MSG_ONE, str);
|
|
};
|
|
|
|
// ===================================================================== //
|
|
|
|
entity (.string fld, string str)
|
|
find_random =
|
|
{
|
|
local float r, numents;
|
|
local entity ent;
|
|
|
|
numents = 0;
|
|
r = floor (random () * 512);
|
|
|
|
while (1) {
|
|
ent = find (world, fld, str);
|
|
if (!ent)
|
|
return world;
|
|
|
|
while (ent) {
|
|
numents++;
|
|
|
|
r--;
|
|
if (r <= 0)
|
|
return ent;
|
|
|
|
ent = find (ent, fld, str);
|
|
}
|
|
|
|
r -= numents * floor (r / numents);
|
|
}
|
|
|
|
return world;
|
|
};
|
|
|
|
// ===================================================================== //
|
|
|
|
entity (entity ech)
|
|
random_enemy_chain =
|
|
{
|
|
local entity walk;
|
|
local float tot, r;
|
|
|
|
r = floor (random () * 64) + 1;
|
|
|
|
tot = 1;
|
|
for (walk = ech.enemy; walk != ech; walk = walk.enemy) {
|
|
r--;
|
|
if (r <= 0)
|
|
return walk;
|
|
|
|
tot++;
|
|
}
|
|
|
|
if (!tot)
|
|
return ech;
|
|
|
|
/* Ok, only look at half the remaining ones */
|
|
tot = floor (tot * 0.5);
|
|
if (!tot)
|
|
return ech.enemy;
|
|
|
|
r -= tot * floor(r / tot);
|
|
|
|
for (walk = ech.enemy; walk; walk = walk.enemy) {
|
|
if (r <= 0)
|
|
return walk;
|
|
|
|
r--;
|
|
}
|
|
|
|
return world;
|
|
};
|