268 lines
10 KiB
Plaintext
268 lines
10 KiB
Plaintext
/*======================================================================
|
|
MINIONS FUNCTIONS
|
|
|
|
Minion Support functions
|
|
Setup and update minion counters centralized to reduce errors
|
|
monster_death_precheck remove from the counter (dying event)
|
|
minion_voreling, minion_spider add to the counter (spawn event)
|
|
|
|
======================================================================*/
|
|
|
|
.float minion_baseattack; // Block base attack within melee range
|
|
.float minion_count; // Current active amount of minions
|
|
.float minion_maxcount; // Maximum amount of active minions
|
|
.float minion_trigger; // Spawn counter for trigger event
|
|
.float minion_maxtrigger; // Spawn total for trigger event
|
|
.string miniontarget; // targetname string for trigger event
|
|
.float minion_throwspeed; // Starting forward speed of ball
|
|
.float minion_throwextra; // Extra random forward speed of ball
|
|
.float minion_throwside; // Extra sideways momentum (+/-)
|
|
.entity minion_test; // Test entity for minion collision
|
|
float MIN_MAXACTIVE = 5; // Maximum amount active at once
|
|
float MIN_MAXTRIGGER = 5; // How many spawns before triggering
|
|
float MIN_THROWSPD = 200; // Monster jump speed
|
|
float MIN_THROWRND = 50; // Some randomness
|
|
float MIN_THROWSIDE = 50; // Sideways movement (+/-)
|
|
|
|
void(vector org, vector dir, vector avel, float proj_type) Launch_Grenade;
|
|
|
|
//----------------------------------------------------------------------
|
|
void() setup_minionsupport =
|
|
{
|
|
// Reset all counters
|
|
self.minion_active = TRUE;
|
|
self.minion_count = 0;
|
|
self.minion_trigger = 0;
|
|
|
|
// Setup default max counters (check for existing values)
|
|
if (!self.minion_maxcount) self.minion_maxcount = MIN_MAXACTIVE;
|
|
if (!self.minion_maxtrigger) self.minion_maxtrigger = MIN_MAXTRIGGER;
|
|
|
|
// Chance to fire original (base) attack of monster
|
|
if (!self.minion_baseattack) self.minion_baseattack = TRUE;
|
|
|
|
// Throw speed of minion ball/egg
|
|
if (!self.minion_throwspeed) self.minion_throwspeed = MIN_THROWSPD;
|
|
if (!self.minion_throwextra) self.minion_throwextra = MIN_THROWRND;
|
|
if (!self.minion_throwside) self.minion_throwside = MIN_THROWSIDE;
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void(entity msource, float countupd) update_minioncount =
|
|
{
|
|
if (!msource.minion_active) return;
|
|
// Add to the active minion counters (check range limits)
|
|
msource.minion_count = msource.minion_count + countupd;
|
|
if (msource.minion_count < 0) msource.minion_count = 0;
|
|
if (msource.minion_count > msource.minion_maxcount)
|
|
msource.minion_count = msource.minion_maxcount;
|
|
|
|
// Check for trigger events (ignore death triggers)
|
|
if (countupd > 0) {
|
|
msource.minion_trigger = msource.minion_trigger + countupd;
|
|
if (msource.minion_trigger >= msource.minion_maxtrigger) {
|
|
msource.minion_trigger = 0;
|
|
// Check for trigger event string?
|
|
if (msource.miniontarget != "") {
|
|
trigger_strs(msource.miniontarget, msource);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
// Check if anymore minions can be spawned at the moment
|
|
//----------------------------------------------------------------------
|
|
float(entity msource) query_minionactive =
|
|
{
|
|
if (!msource.minion_active) return NEGATIVE;
|
|
if (msource.minion_count < msource.minion_maxcount) return TRUE;
|
|
else return FALSE;
|
|
};
|
|
|
|
//======================================================================
|
|
// Check if there is space to spawn a minion
|
|
//
|
|
// Version 1 - use a touch trigger to detect players
|
|
// Version 2 - use a findradius command to find players
|
|
//======================================================================
|
|
void() delete_minionspace = { if (self.minion_test) entity_remove(self.minion_test,0.1); }
|
|
void() touch_minionspace = { if (other.flags & FL_CLIENT) self.aflag = TRUE; };
|
|
|
|
//----------------------------------------------------------------------
|
|
// Touch trigger version for detecting client/players too close
|
|
//----------------------------------------------------------------------
|
|
void(vector org, vector minspace, vector maxspace) setup_minionspace =
|
|
{
|
|
// Create a touch trigger if one does not exist
|
|
if (!self.minion_test) self.minion_test = spawn();
|
|
// reset all parameters
|
|
self.minion_test.movetype = MOVETYPE_NONE;
|
|
self.minion_test.solid = SOLID_TRIGGER;
|
|
setmodel(self.minion_test, MODEL_EMPTY);
|
|
self.minion_test.skin = self.minion_test.frame = 0;
|
|
self.minion_test.flags = 0;
|
|
|
|
// Move touch trigger to correct location
|
|
setorigin(self.minion_test, org);
|
|
setsize (self.minion_test, minspace, maxspace);
|
|
// Enable touch function and reset collision flag
|
|
self.minion_test.touch = touch_minionspace;
|
|
self.minion_test.aflag = FALSE;
|
|
};
|
|
|
|
float() test_minionspace =
|
|
{
|
|
// Quick exit if no touch entity exists
|
|
if (!self.minion_test) return FALSE;
|
|
|
|
// Basic world collision test for origin/min/max of spawn
|
|
if (pointcontents(self.minion_test.origin) != CONTENT_EMPTY) return FALSE;
|
|
if (pointcontents(self.minion_test.origin+'-16 -16 -24') != CONTENT_EMPTY) return FALSE;
|
|
if (pointcontents(self.minion_test.origin+'16 16 40') != CONTENT_EMPTY) return FALSE;
|
|
|
|
// Switch off touch function and check for collision
|
|
self.minion_test.touch = SUB_Null;
|
|
if (self.minion_test.aflag == TRUE) return FALSE;
|
|
else return TRUE;
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
// Findradius version for detecting client/players too close
|
|
//----------------------------------------------------------------------
|
|
float(vector org) find_minionspace =
|
|
{
|
|
local entity minstuff;
|
|
local float foundclient;
|
|
|
|
// Basic world collision test for origin/min/max of spawn
|
|
if (pointcontents(org) != CONTENT_EMPTY) return FALSE;
|
|
if (pointcontents(org+'-16 -16 -24') != CONTENT_EMPTY) return FALSE;
|
|
if (pointcontents(org+'16 16 40') != CONTENT_EMPTY) return FALSE;
|
|
|
|
foundclient = TRUE;
|
|
// The player can cover crazy amount of distance very fast
|
|
// Extend radius out further to catch running players
|
|
minstuff = findradius(org, MONAI_RANGESUMMON);
|
|
while (minstuff) {
|
|
if (minstuff.flags & FL_CLIENT) {
|
|
foundclient = FALSE;
|
|
minstuff = world;
|
|
}
|
|
minstuff = minstuff.chain;
|
|
}
|
|
return foundclient;
|
|
};
|
|
|
|
/*======================================================================
|
|
MINION EGGS
|
|
* Spawns a projecile (egg) at the player
|
|
* Wait for egg to bounce and settle
|
|
* Spawn (rotating) minion inside egg and then explode
|
|
======================================================================*/
|
|
void(vector minion_org, entity minion_targ) minion_spider;
|
|
void(vector minion_org, entity minion_targ) minion_scorpion;
|
|
void(vector minion_org, entity minion_targ) minion_voreling;
|
|
|
|
//----------------------------------------------------------------------------
|
|
void() Explode_Egg =
|
|
{
|
|
// Remove egg and throw gibs up as minion grows
|
|
setmodel (self, "");
|
|
if (self.classgroup == CT_MONWRAITH) self.gib1mdl = MODEL_PROJ_WSHELL;
|
|
else if (self.classgroup == CT_MONSHAL) self.gib1mdl = MODEL_PROJ_SSHELL;
|
|
self.gib1frame = 9;
|
|
self.max_health = MON_GIBFOUNTAIN;
|
|
ThrowGib(11,rint(2+random()*4));
|
|
|
|
// Safely remove egg
|
|
self.think = SUB_Remove;
|
|
self.nextthink = time + 0.1;
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
// Once the egg has landed via Grenade Touch function
|
|
// The minion is spawned and the egg is set to explode
|
|
//----------------------------------------------------------------------
|
|
void() Hatch_Egg =
|
|
{
|
|
// Block this functions
|
|
if (self.state == STATE_DISABLED) return;
|
|
// Has the egg been marked for removal?
|
|
if (self.touchedvoid) return;
|
|
|
|
// Its possible for the egg to get stuck, setup death timer
|
|
if (!self.gibbed) {
|
|
self.gibbed = TRUE;
|
|
self.attack_finished = time + 6;
|
|
}
|
|
// Egg has been stuck for too long, destroy it
|
|
if (self.attack_finished < time) {
|
|
self.state = STATE_DISABLED;
|
|
self.think = Explode_Egg;
|
|
self.nextthink = time + 0.1;
|
|
return;
|
|
}
|
|
|
|
// Turn off touch function and any movement
|
|
self.touch = SUB_Null;
|
|
self.movetype = MOVETYPE_NONE;
|
|
self.takedamage = DAMAGE_NO;
|
|
self.velocity = '0 0 0';
|
|
|
|
// Check if the egg can hatch? turn on collision
|
|
// setup new bounds and extend upwards for testing
|
|
setsize (self, VEC_HULLT_MIN, VEC_HULLT_MAX);
|
|
self.solid = SOLID_SLIDEBOX;
|
|
self.movetype = MOVETYPE_TOSS;
|
|
self.oldorigin = self.origin;
|
|
self.origin_z = self.origin_z + 8;
|
|
droptofloor();
|
|
|
|
// Can the egg hatch yet? Solid content?
|
|
if (!walkmove(0,0) || pointcontents(self.origin) == CONTENT_SOLID) {
|
|
self.origin = self.oldorigin;
|
|
self.think = Hatch_Egg;
|
|
self.nextthink = time + 1 + random();
|
|
self.solid = SOLID_NOT; // turn off again
|
|
return;
|
|
}
|
|
|
|
// Setup egg ready for minion
|
|
self.state = STATE_DISABLED;
|
|
|
|
// Setup spawn location for minion
|
|
traceline (self.origin+'0 0 128', self.origin-'0 0 256', TRUE, world);
|
|
self.oldorigin = trace_endpos + '0 0 32';
|
|
|
|
// Spawn minion inside of egg model
|
|
// self.owner is re-assigned to minion to avoid shell gibs interacting
|
|
if (self.owner.classtype == CT_MONWRAITH) {
|
|
if (self.owner.spawnflags & MON_WRAITH_SCORPIONS) minion_scorpion(self.oldorigin, self.enemy);
|
|
else minion_spider(self.oldorigin, self.enemy);
|
|
}
|
|
else if (self.owner.classtype == CT_MONSHAL) minion_voreling(self.oldorigin, self.enemy);
|
|
|
|
// Skip a couple of frames
|
|
self.think = Explode_Egg;
|
|
self.nextthink = time + 0.4;
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
// Work out direction speed of egg and then launch it!
|
|
//----------------------------------------------------------------------
|
|
void(vector eggofs) Create_Egg =
|
|
{
|
|
local vector org, dir;
|
|
local float egg_speed;
|
|
if (self.health < 1) return;
|
|
|
|
// Turn towards player and drop the egg!
|
|
makevectors(self.angles);
|
|
org = self.origin + attack_vector(eggofs);
|
|
egg_speed = self.minion_throwspeed + (random() * self.minion_throwextra);
|
|
dir = (v_forward * egg_speed) + (v_right * (crandom()*self.minion_throwside));
|
|
Launch_Grenade(org,dir,'0 0 0',CT_PROJ_MEGG);
|
|
SUB_AttackFinished (2 + random());
|
|
};
|