Files
2020-01-01 23:35:17 +01:00

866 lines
30 KiB
Plaintext

/*======================================================================
SUB FUNCTIONS
======================================================================*/
void() SUB_Null = {};
void() SUB_Remove = {remove(self);};
void(entity inflictor, entity attacker, float damage) SUB_Null_pain = {};
void() SUB_CalcMoveDone;
void() SUB_CalcAngleMoveDone;
void(vector marker_org, float marker_type) spawn_marker;
void(float hud_item) update_hud_totals;
//======================================================================
// To determine if an entity is still active this function generates
// an unique number that can be checked/shared between linked entities
//----------------------------------------------------------------------
void(entity targ) gen_unique_no =
{
// Check if target entity has an unique number already
if (targ.entno_unique == 0)
targ.entno_unique = rint(random()*1000) * rint(random()*1000);
};
//======================================================================
// Most of the monsters have an attack_offset setup to spawn their
// proejctiles in the right location relative to their gun model
// This function expects the makevectors has been done already
// makevectors (self.angles); <--- if missing, vector will be rubbish!
//----------------------------------------------------------------------
vector(vector attack_dir) attack_vector =
{
local vector retvec;
retvec = v_forward * attack_dir_x;
retvec = retvec + (v_right * attack_dir_y);
retvec = retvec + (v_up * attack_dir_z);
return retvec;
};
//======================================================================
// Bmodels have a 0,0,0 origin key, need to calculate from mins/maxs
// This function will return the true origin using mins/max vectors
//----------------------------------------------------------------------
vector(entity bmodel_ent) bmodel_origin =
{
local vector vdiff, vorigin;
// Calculate the middle point of the mins/maxs size vectors
vdiff_x = (bmodel_ent.maxs_x - bmodel_ent.mins_x) / 2;
vdiff_y = (bmodel_ent.maxs_y - bmodel_ent.mins_y) / 2;
vdiff_z = (bmodel_ent.maxs_z - bmodel_ent.mins_z) / 2;
// The mins vector is an absolute value of where the Bmodel is located
// Add mins vector to middle point to get true origin
// When bmodel do move they also update their origin co-ordinates
// So any additional movement needs to be added as well
// This means that bmodels can be tracked by movement functions
vorigin = bmodel_ent.origin + bmodel_ent.mins + vdiff;
// Return origin
return vorigin;
};
//======================================================================
// Check for bmodel errors, model key empty (can crash engine)
//----------------------------------------------------------------------
float() check_bmodel_keys =
{
if (self.model == "") {
// this error can be fatal for most engines, show warning
dprint ("\b[BMODEL]\b "); dprint (self.classname);
dprint (" ("); dprint (self.targetname);
dprint (") missing MODEL key!\n");
self.classtype = CT_DEVMARKER; // Useless entity
self.movetype = MOVETYPE_NONE; // Stationary
self.solid = SOLID_NOT; // no world interaction
// If developer active show yellow diamond
if (developer > 0) setmodel(self, MODEL_BROKEN);
else setmodel(self,"");
// Make sure no world interaction
setsize (self, VEC_ORIGIN, VEC_ORIGIN);
// Randomly place debug diamond above 0,0,0
self.pos1 = '0 0 0';
self.pos1_z = random()*64;
setorigin(self, self.pos1);
// Do nothing with this entity
return TRUE;
}
else return FALSE;
};
//----------------------------------------------------------------------
// Cannot have multiple upgrade restrictions on monster/items
//----------------------------------------------------------------------
void() remove_duplicate_upgrades =
{
if (self.upgrade_axe) {
if (self.upgrade_ssg) self.upgrade_ssg = FALSE;
else if (self.upgrade_lg) self.upgrade_lg = FALSE;
}
else if (self.upgrade_ssg) {
if (self.upgrade_axe) self.upgrade_axe = FALSE;
else if (self.upgrade_lg) self.upgrade_lg = FALSE;
}
else if (self.upgrade_lg) {
if (self.upgrade_axe) self.upgrade_axe = FALSE;
else if (self.upgrade_ssg) self.upgrade_ssg = FALSE;
}
}
/*======================================================================
QuakeEd only writes a single float for angles (bad idea),
so up and down are just constant angles.
======================================================================*/
void() SetMovedir =
{
if (self.angles == '0 -1 0') self.movedir = '0 0 1';
else if (self.angles == '0 -2 0') self.movedir = '0 0 -1';
else {
makevectors (self.angles);
self.movedir = v_forward;
}
// Bmodels need angles key reset otherwise twisted on spawn
self.angles = '0 0 0';
};
//----------------------------------------------------------------------
float(vector check_vec) CheckZeroVector =
{
if (check_vec_x == 0 && check_vec_y == 0 && check_vec_z == 0)
return TRUE;
else return FALSE;
};
//----------------------------------------------------------------------
// Some entities have directional parameters (angles -> movedir)
// This function searches for a target entity (useful for tracking)
// Always finds the first target key (will fail if multiple targets)
//----------------------------------------------------------------------
void() TargetMovedir =
{
local entity targ;
local vector org, targorg;
if (self.target == "") return;
// Check self for Bmodel origin
if (self.bsporigin) org = bmodel_origin(self);
else org = self.origin;
// Find target entity
targ = find(world, targetname, self.target);
if (targ) {
// Check for a Bmodel object (special origin)
if (targ.bsporigin) targorg = bmodel_origin(targ);
else targorg = targ.origin;
// Calculate facing angle towards target
self.movedir = normalize(targorg - org);
}
};
/*======================================================================
InitTrigger
======================================================================*/
void() InitTrigger =
{
// trigger angles are used for one-way touches. An angle of 0 is assumed
// to mean no restrictions, so use a yaw of 360 instead.
if (CheckZeroVector(self.angles) == FALSE) SetMovedir ();
self.solid = SOLID_TRIGGER;
self.movetype = MOVETYPE_NONE;
self.mdl = self.model; // Save model for later
setmodel (self, self.mdl); // set size and link into world
self.bbmins = self.mins; // Save bmodel bounds for later
self.bbmaxs = self.maxs;
self.bsporigin = TRUE; // bmodel origin 0,0,0
self.modelindex = 0;
self.model = ""; // hide bmodel surface
};
/*======================================================================
SUB_CalcMove
calculate self.velocity and self.nextthink to reach dest from
self.origin traveling at speed
======================================================================*/
void(vector tdest, float tspeed, void() func) SUB_CalcMove =
{
local vector vdestdelta;
local float len, traveltime;
if (!tspeed) tspeed = 40; // Create default speed if none specified
self.think1 = func; // Finished moving function
self.finaldest = tdest; // Final destination
self.think = SUB_CalcMoveDone; // Tidy up function
vdestdelta = tdest - self.origin; // set destdelta to the vector needed to move
len = vlen (vdestdelta); // calculate length of vector
traveltime = len / tspeed; // divide by speed to get time to reach dest
// If move distance or time too small, snap to final location
if (traveltime < 0.1 || tdest == self.origin) {
self.velocity = '0 0 0';
self.nextthink = self.ltime + 0.1;
return;
}
// scale the destdelta vector by the time spent traveling to get velocity
self.velocity = vdestdelta * (1/traveltime); // qcc won't take vec/float
self.nextthink = self.ltime + traveltime;
};
/*======================================================================
After moving, set origin to exact final destination
======================================================================*/
void() SUB_CalcMoveDone =
{
self.velocity = '0 0 0';
setorigin(self, self.finaldest);
self.nextthink = -1;
if (self.think1) self.think1();
};
//----------------------------------------------------------------------
// Not used anywhere
//----------------------------------------------------------------------
void(entity ent, vector tdest, float tspeed, void() func) SUB_CalcMoveEnt =
{
local entity stemp;
stemp = self;
self = ent;
SUB_CalcMove (tdest, tspeed, func);
self = stemp;
};
/*======================================================================
SUB_CalcMoveEntity
calculate self.velocity and self.nextthink to reach dest from
self.origin traveling at speed
======================================================================*/
void() SUB_MoveEntityDone =
{
self.movetype = self.classmove;
SUB_CalcMoveDone();
};
//----------------------------------------------------------------------
void(vector tdest, float tspeed, void() func) SUB_CalcMoveEntity =
{
local vector vdestdelta;
local float traveltime;
if (!tspeed) tspeed = 4; // Create default travel time if none specified
self.classmove = self.movetype; // save previous movetype
self.movetype = MOVETYPE_FLY; // Allow entity to move smoothly
self.think1 = func; // Store final function
self.finaldest = tdest; // Store final destination
vdestdelta = tdest - self.origin; // set destdelta to the vector needed to move
traveltime = 1 / tspeed; // Work out percentage travel velocity
self.velocity = vdestdelta * traveltime; // Calc time travel fraction
self.nextthink = time + tspeed;
self.think = SUB_MoveEntityDone;
};
/*======================================================================
SUB_CalcAngleMove
calculate self.avelocity and self.nextthink to reach destangle from
self.angles rotating
The calling function should make sure self.think is valid
======================================================================*/
void(vector destangle, float tspeed, void() func) SUB_CalcAngleMove =
{
local vector destdelta;
local float len, traveltime;
if (!tspeed) dprint("[SUB_CALC_ANGLE] No speed is defined!");
// set destdelta to the vector needed to move
destdelta = destangle - self.angles;
// calculate length of vector
len = vlen (destdelta);
// divide by speed to get time to reach dest
traveltime = len / tspeed;
// set nextthink to trigger a think when dest is reached
self.nextthink = self.ltime + traveltime;
// scale the destdelta vector by the time spent traveling to get velocity
self.avelocity = destdelta * (1 / traveltime);
self.think1 = func;
self.finalangle = destangle;
self.think = SUB_CalcAngleMoveDone;
};
/*======================================================================
After rotating, set angle to exact final angle
======================================================================*/
void() SUB_CalcAngleMoveDone =
{
self.angles = self.finalangle;
self.avelocity = '0 0 0';
self.nextthink = -1;
if (self.think1)
self.think1();
};
//----------------------------------------------------------------------
// Not used anywhere
//----------------------------------------------------------------------
void(entity ent, vector destangle, float tspeed, void() func) SUB_CalcAngleMoveEnt =
{
local entity stemp;
stemp = self;
self = ent;
SUB_CalcAngleMove (destangle, tspeed, func);
self = stemp;
};
//----------------------------------------------------------------------
void() DelayThink =
{
activator = self.enemy;
SUB_UseTargets ();
remove(self);
};
/*======================================================================
SUB_UseTargets
the global "activator" should be set to the entity that initiated the firing.
If self.delay is set, a DelayedUse entity will be created that will actually
do the SUB_UseTargets after that many seconds have passed.
Centerprints any self.message to the activator.
Removes all entities with a targetname that match self.killtarget,
and removes them, so some events can remove other triggers.
Search for (string)targetname in all entities that
match (string)self.target and call their .use function
======================================================================*/
void() SUB_UseTargets =
{
local entity t, stemp, otemp, act;
//----------------------------------------------------------------------
// Check for recursive loops!?!
//----------------------------------------------------------------------
if (self.targetname != "") {
if (self.targetname == self.target || self.targetname == self.target2) {
dprint("\n----------------------------------------\n");
dprint("\b[USE_TARGET]\b target == targetname, really bad!!!\n");
dprint(self.classname);
dprint(" ("); dprint(self.targetname); dprint(") at ");
if (self.bsporigin) dprint(vtos(bmodel_origin(self)));
else dprint(vtos(self.origin));
dprint("\ntarg ("); dprint(self.target);
dprint(") targ2 ("); dprint(self.target2);
dprint(") kill ("); dprint(self.killtarget);
dprint(")\n----------------------------------------\n");
return;
}
}
//----------------------------------------------------------------------
// check for a delay
//----------------------------------------------------------------------
if (self.delay) {
// create a temp object to fire at a later time
t = spawn();
t.classname = "DelayedUse";
t.classtype = CT_TRIGRELAYDELAY;
t.owner = self;
t.nextthink = time + self.delay;
t.think = DelayThink;
t.enemy = activator;
t.message = self.message;
t.killtarget = self.killtarget;
t.target = self.target;
t.target2 = self.target2;
t.noise = self.noise;
return;
}
//----------------------------------------------------------------------
// Check if previous delay has been disabled
// Unfortunately start off = OFF state and not easy to check for
// because some entities use that spawnflag bit for other things!
//----------------------------------------------------------------------
if (self.classtype == CT_TRIGRELAYDELAY) {
if (self.owner.estate & ESTATE_DISABLE) return;
}
//----------------------------------------------------------------------
// print any messages
//----------------------------------------------------------------------
if (activator.flags & FL_CLIENT && self.message != "") {
if (coop > 0) {
// Write message to all clients
msg_entity = self;
WriteByte (MSG_ALL, SVC_CENTERPRINT);
WriteString (MSG_ALL, self.message);
}
// Write message to single client
else centerprint (activator, self.message);
// Default noise = classic beep beep sound
if (self.noise == "") sound (activator, CHAN_VOICE, SOUND_TALK, 1, ATTN_NORM);
}
//----------------------------------------------------------------------
// kill targets
// I really do hate this function because there is hardly any checks
// on entities being removed, added a player check for sanity!
//----------------------------------------------------------------------
if (self.killtarget != "") {
t = find (world, targetname, self.killtarget);
while(t) {
// Check for activate mosnters? Tidy up kill counter
if (t.flags & FL_MONSTER && t.health > 0) {
// If a monster marked with no monster count dies
// update HUD totals to reflect death properly
if (t.nomonstercount) {
total_monsters = total_monsters + 1;
update_hud_totals(HUD_MONSTERS);
}
// Update global monster death totals
killed_monsters = killed_monsters + 1;
WriteByte (MSG_ALL, SVC_KILLEDMONSTER);
}
// Added extra check to prevent player client removal!?!
if (!(t.flags & FL_CLIENT)) {
// Try to minimize the entity chain damage, check for
// entity attachments and delete them as well!
if (t.attachment) remove(t.attachment);
if (t.attachment2) remove(t.attachment2);
if (t.attachment3) remove(t.attachment3);
// Make sure any unique ent number is reset
if (t.entno_unique > 0) t.entno_unique = 0;
// Remove entity from chain list
remove (t);
}
// Find the next killtarget
t = find (t, targetname, self.killtarget);
}
}
//----------------------------------------------------------------------
// fire targets
//----------------------------------------------------------------------
if (self.target != "") {
act = activator; // ID Code - Never switch around, not sure why it is here?
t = find (world, targetname, self.target);
stemp = self; otemp = other; // Store self/other
while(t) {
if (t.use != SUB_Null) {
if (t.use) {
self = t; other = stemp; // Switch self/other
self.activate = stemp; // used by the breakable system
self.use (); // fire trigger
self = stemp; other = otemp; // restore self/other
activator = act; // ID code - Never changed, why restore it?
}
}
// Are there anymore targets left in the list?
t = find (t, targetname, self.target);
}
}
//----------------------------------------------------------------------
if (self.target2 != "") {
act = activator; // ID Code - Never switch around, not sure why it is here?
t = find (world, targetname, self.target2);
stemp = self; otemp = other; // Store self/other
while(t) {
if (t.use != SUB_Null) {
if (t.use) {
self = t; other = stemp;// Switch self/other
self.activate = stemp; // used mostly by the breakable system
self.use (); // fire trigger
self = stemp; other = otemp; // restore self/other
activator = act; // ID code - Never changed, why restore it?
}
}
t = find (t, targetname, self.target2);
}
}
};
/*======================================================================
SUB_AttackFinished
in nightmare mode, all attack_finished times become 0
some monsters refire twice automatically (using .cnt variable)
Change nightmare to be less range spam and better time scaling
======================================================================*/
void(float normal) SUB_AttackFinished =
{
local float nightadj; // Easier to work out percentage
self.cnt = 0; // reset checkfire counter
// Nightmare skill attacks are faster (turret like) and with
// hitscan enemies this can easily overwhelm targets locking
// them into damage/pain function loops. This can especially be
// be bad if a large enough enemy group is involved!
// Changed - NM behaviour for boss fights is default
//
if (skill == SKILL_NIGHTMARE && self.enemy.bossflag == FALSE) {
// Nightmare mode is just endless range attacks!
// I never thought this was a good way to end skill levels
// Changed nightmare mode to 50-75% decrease in time
// Exceptions to nightmare mode (minion spawning) - 75-100% time
nightadj = 0.5 + (random()*0.25);
if (self.minion_active) nightadj = nightadj + 0.25;
// The original ID shamblers were lightning turrets on Nightmare
// Shamblers don't scale with skill levels in AD because
// they don't have projectiles, so they need a switch instead
// On Nightmare skill Shamblers go from 2-4s to 1s!
// Instead of 2-4s, NM is 0.6-1.2s, Average 1.2s +2 frames
if (self.classtype == CT_MONSHAM) nightadj = 0.4;
self.attack_finished = time + (normal*nightadj);
}
// Easy, Nornal and Hard skill all pass through, no change
else self.attack_finished = time + normal;
//dprint("Time ("); dprint(ftos(time));
//dprint(") AF ("); dprint(ftos(normal));
//dprint(") Dif ("); dprint(ftos(self.attack_finished - time));
//dprint(") Att ("); dprint(ftos(self.attack_finished));
//dprint(")\n");
};
//----------------------------------------------------------------------
// Used by soliders and enforcers (special double fire mode)
//----------------------------------------------------------------------
void (void() thinkst) SUB_CheckRefire =
{
if (skill != SKILL_NIGHTMARE) return;
if (self.cnt == 1) return;
if (!visible (self.enemy)) return;
self.cnt = 1;
self.think = thinkst;
};
//----------------------------------------------------------------------
// Not used anywhere in QC
//----------------------------------------------------------------------
/*float() SUB_CountTargets =
{
local entity t;
local float i;
i = 0;
if (self.target != "") {
t = world;
do {
t = find(t, targetname, self.target);
if (!t) break;
i = i + 1;
} while ( 1 );
}
return i;
};*/
/*======================================================================
ChooseTurn
- can't find any reference to this in the QC
======================================================================*/
/*void(vector dest3) ChooseTurn =
{
local vector dir, newdir;
dir = self.origin - dest3;
newdir_x = trace_plane_normal_y;
newdir_y = 0 - trace_plane_normal_x;
newdir_z = 0;
if (dir * newdir > 0) {
dir_x = 0 - trace_plane_normal_y;
dir_y = trace_plane_normal_x;
}
else {
dir_x = trace_plane_normal_y;
dir_y = 0 - trace_plane_normal_x;
}
dir_z = 0;
self.ideal_yaw = vectoyaw(dir);
};*/
/*======================================================================
SPAWN_TFOG and SPAWN_TDEATH
These functions were originally in triggers.qc, but are used all
over the place by monsters, items and players. Subs seems a better
home for these generic functions.
======================================================================*/
void() play_teleport =
{
self.lip = random() * 5;
if (self.lip < 1) self.noise = "misc/r_tele1.wav";
else if (self.lip < 2) self.noise = "misc/r_tele2.wav";
else if (self.lip < 3) self.noise = "misc/r_tele3.wav";
else if (self.lip < 4) self.noise = "misc/r_tele4.wav";
else self.noise = "misc/r_tele5.wav";
sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM);
self.nextthink = time + 2; // Allow time for the sound to finish
self.think = SUB_Remove; // tidy up function
};
//----------------------------------------------------------------------
void(vector org) spawn_tfog =
{
local entity spawneffect;
spawneffect = spawn ();
spawneffect.origin = org;
spawneffect.nextthink = time + 0.2;
spawneffect.think = play_teleport;
WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
WriteByte (MSG_BROADCAST, TE_TELEPORT);
WriteCoord (MSG_BROADCAST, org_x);
WriteCoord (MSG_BROADCAST, org_y);
WriteCoord (MSG_BROADCAST, org_z);
};
//----------------------------------------------------------------------
void() tdeath_touch =
{
// Cannot telefrag yourself!?!
if (other == self.owner) return;
// Check if found a breakable than cannot be broken
if (ai_foundbreakable(self, other, FALSE)) return;
// A boss cannot be telefragged (except shub)
// Telefrag entity trying to telefrag boss instead!?!
if (other.bossflag == TRUE) {
if (other.classtype != CT_MONIDSHUB) {
T_Damage (self.owner, other, other, MEGADEATH, NOARMOR);
return;
}
}
// frag anyone who teleports in on top of an invincible player
if (other.flags & FL_CLIENT) {
if (other.invincible_finished > time) self.classname = "teledeath2";
// other monsters explode themselves
if ( !(self.owner.flags & FL_CLIENT) ) {
T_Damage (self.owner, self, self, MEGADEATH, NOARMOR);
return;
}
}
// Sudden death incoming ...
if (other.health) T_Damage (other, self, self, MEGADEATH, NOARMOR);
};
//----------------------------------------------------------------------
void(vector org, entity death_owner) spawn_tdeath =
{
local entity death;
death = spawn();
death.classname = "teledeath";
death.owner = death_owner;
death.movetype = MOVETYPE_NONE;
death.solid = SOLID_TRIGGER;
death.angles = '0 0 0';
setsize (death, death_owner.mins - '1 1 1', death_owner.maxs + '1 1 1');
setorigin (death, org);
death.touch = tdeath_touch;
death.nextthink = time + 0.2;
death.think = SUB_Remove;
// This is extremely costly on the engine because it re-checks
// every trigger in the map to see if it is touching something
force_retouch = 2;
};
/*======================================================================
skill_chanceattack
Calculate chance to attack player based on skill level and distance
The standard ID range formula
(enemy_range == RANGE_MELEE) chance = 0.9
(enemy_range == RANGE_NEAR) chance = 0.4
(enemy_range == RANGE_MID) chance = 0.05
else chance = 0;
The problem with this formula is that the chance factor is not gradual
and will spike in chance when close to range boundaries
Also this formula has no skill modifiers leading to limited gameplay rules
======================================================================*/
float() SUB_ChanceAttack =
{
local float chrange, chskill, chance;
chrange = chskill = 0; // taniwha patch
// Upper and lower limits of range have fixed chance rates
if (self.enemydist > MON_MAX_RANGE) chance = 0.05;
else if (self.enemydist < MON_RANGE_MELEE) chance = 0.95;
else {
// Random chance of range attack based on player distance
chrange = ((MON_MAX_RANGE - self.enemydist) / MON_STEPRANGE) / 10;
// Skill modifier to chance (Easy -0.2 Norm -0.1, Hard 0, Night 0.1)
chskill = ((skill-SKILL_HARD)*0.1);
chance = chrange + chskill;
// There Should always be a chance to HIT or MISS
if (chance > 0.95) chance = 0.95;
else if (chance < 0.05) chance = 0.05;
}
// Return result and finish debug information line
if (random() < chance) return TRUE;
else return FALSE;
};
/*======================================================================
skill_subattack
Calculate attack_finished time based on skill level
======================================================================*/
float (float min_time, float ranbase_time) SUB_SkillAttack =
{
local float ran_time, sub_time;
// Easy = min + random(base + 1.5) Normal = min + random(base + 1.0)
// Hard = min + random(base + 0.5) Nightm = min + random(base + 0.0)
ran_time = ranbase_time + ((SKILL_NIGHTMARE - skill) * 0.5);
sub_time = min_time + (ran_time * random());
return sub_time;
};
/*======================================================================
SUB_Tracking
Progressively tracks the origin of enemies making attacks more accurate
* Original code by Necros (http://shoresofnis.com/)
======================================================================*/
vector(vector iorg, vector org_offset, entity targ, float zspeed) SUB_Tracking =
{
local vector vec, org;
local float diff;
// Allow for source origin to be fine tuned (directional)
makevectors (self.angles);
org = self.origin + attack_vector(org_offset);
diff = vlen(iorg - org) / zspeed; // Workout difference
vec = targ.velocity;
if (targ.flags & FL_MONSTER) vec_z = 0; // Flatten Z axis for monsters
else vec_z = vec_z * 0.2; // Aim for headshot with players
iorg = targ.origin + diff * vec; // Refine target origin
return iorg;
};
/*======================================================================
SUB_Elevation
Calculates the Elevation for Z Aware monsters
* Original code by Preach (https://tomeofpreach.wordpress.com/)
* Added support for variable origin locations and proj speeds
* Added support for distance scaling linked to skill levels
======================================================================*/
float(float theta, vector sorg, vector mdest, float zspeed) SUB_Elevation =
{
local float a, b, c; //constants in the equation to be solved
local vector ofs; //displacement we wish the projectile to travel
local float y, z; //horizontal and vertical components of ofs
local float tan_theta; //trig values of the angle theta
local vector vec, eorg;
local float dist, sperc;
// Place projectile destination at player head level
if (self.classtype == CT_MONSPIDER) mdest = mdest + ELEV_SPID_OFS;
else mdest = mdest + ELEV_VIEW_OFS;
// Work out vector distance between source and destination
// Normalize the distance and create a percentage of that
// This is a skill based calculation so the aiming gets better
// The grenade is aimed infront of target so that it bounces
// No check for vertical difference so it bad going up height!
// Zombie, poison spiders zaware calculation is fine, exception!
if (self.classgroup != CG_ZOMBIE && self.classtype != CT_MONSPIDER
&& self.classtype != CT_MONBOGLORD) {
vec = mdest - sorg;
dist = vlen(vec);
vec = normalize(vec);
sperc = 0.45 + skill*0.15;
eorg = sorg + (vec * (dist*sperc));
ofs = eorg - sorg;
}
else ofs = mdest - sorg;
//calculate how far we are firing
// ofs = mdest - sorg;
z = ofs_z;
ofs_z = 0;
y = vlen(ofs);
// Map gravity is a global variable defined in world.qc
//find the coefficients of the quadratic in tan(theta)
a = 0.5 * map_gravity * y * y / (zspeed * zspeed);
b = -y;
c = a + z;
//check if the destination is too far to reach
if(b*b < 4*a*c) return ELEV_DEFAULT;
//calculate the tan value of the given theta
tan_theta = mathlib_tan(theta);
//reuse ang to create the improved firing direction
theta = mathlib_atan2(a*tan_theta*tan_theta - c, 2*a*tan_theta + b);
//constrain the values to stop anything too mad happening
while(theta > 90) theta = theta - 180;
return theta;
};
/*======================================================================
SUB_Elevat (Alternative version, not used anymore, left for reference)
Calculates the Elevation for Z Aware monsters
* Original formula WIKI with QC help from ericw
======================================================================*/
/*float(vector source, vector targ, float zspeed) SUB_Elevat =
{
local float g; // Gravity
local float targx, targy; // Sub parts of distance
local float v2, v4; // speed*speed & speed*speed*speed*speed
local vector dist; // Distance to travel
local float ang, theta;
g = map_gravity; // default 800
dist = targ - source; // vector distance
targy = dist_z; // Up direction
dist_z = 0;
targx = vlen(dist); // X,Y direction
v2 = zspeed * zspeed;
v4 = v2 * v2;
// First part of equation
ang = v4 - (g * ( (g * targx * targx) + (2 * targy * v2) ) );
// Cap elevation angle at default value
//if(ang < 0) return ELEV_DEFAULT;
// Second section of equation
theta = mathlib_atan2 ((v2 - mathlib_sqrt(ang)) / (g * targx),1);
return theta;
}; */