Files
quakemapping/mod_xj19/my_progs/targets.qc
2020-01-01 23:35:17 +01:00

874 lines
31 KiB
Plaintext

/*======================================================================
TARGET FUNCTIONS
Trigger_relay and trigger_count are not triggers, they don't have
any touch functionality and don't even set/use any of the activator
system used by most trigger/func entities.
These entities are essentially targets that reply on input from other
targets (use) and then use their targets (conditionally). They should
be re-classifed as target_relay and target_count, but it is too late now
for such a change, the opportunity has long gone!
======================================================================*/
float TRIG_COUNTNOMESSAGE = 1; // Prevents all count messages
float TRIG_COUNTEXACTNO = 16; // Display exact count number
float TRIG_COUNTSTARTDIS = 32; // Count starts in disabled state
float TRIG_RANDOMTARGET = 2; // Randomly trigger target/target2
float TRIG_RELAYSTARTDIS = 32; // trigger_delay starts disabled
float TRIG_EXPLODENOEFF = 2; // No old particle effect
float TRIG_EXPLODEDUST = 4; // Exploding projectile dust
float TRIG_MONKILLDFUNC = 16; // kill monsters via death function
float TRIG_ENGFITZ = 1; // Fitz engine
float TRIG_ENGDP = 2; // DP engine
float TRIG_ENGFTE = 4; // FTE/QSS engine
float TRIG_ENGRAIN = 16; // Check for rain effects
float TRIG_ENGSNOW = 32; // Check for snow effects
/*======================================================================
/*QUAKED trigger_relay (0.5 0 0.5) (-8 -8 -8) (8 8 8) x RANDOM x x x STARTDIS x x Not_Easy Not_Normal Not_Hard
Triggers target(s) with custom sounds and messages
-------- KEYS --------
targetname : trigger entity
target : targets to trigger when relay is activated
target2 : secondary targets to trigger when activated
upgrade_ssg : = 1 will only trigger if shotgun upgrade active on server
upgrade_axe : = 1 will only trigger if axe upgrade active on server
upgrade_lg : = 1 will only trigger if lightning gun upgrade active on server
wait : -1 = will only fire targets once
delay : delay before firing (after being triggered)
cnt : random amount of time to add to delay
waitmin : % random chance between target/target2
sounds : 1=Secret,2=talk(def),3=switch,4=silent,5=custom,6=secret2
noise : custom sound to play when triggered
message : message to display when triggered
-------- SPAWNFLAGS --------
RANDOM : Will randomly select between target/target2
STARTDIS : Will start disabled, will req trigger_entitystate_on to enable
-------- NOTES --------
This fixed size trigger cannot be touched, it can only be fired by other events.
Can contain killtargets, targets, delays, and messages.
======================================================================*/
void() trigger_relay_use =
{
if (self.estate & ESTATE_BLOCK) return;
if (self.attack_finished > time) return;
//----------------------------------------------------------------------
// The Shotgun and Axe upgrades can make maps too easy, allow for
// triggers to not fire if the key is TRUE and inventory empty
//----------------------------------------------------------------------
if ( other.flags & FL_CLIENT ) {
if (self.upgrade_axe && !(other.moditems & IT_UPGRADE_AXE)) return;
if (self.upgrade_ssg && !(other.moditems & IT_UPGRADE_SSG) ) return;
if (self.upgrade_lg && !(other.moditems & IT_UPGRADE_LG) ) return;
}
// Setup to trigger once?
if (self.wait < 0) self.attack_finished = LARGE_TIMER;
if (self.noise != "") sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM);
// Randomly pick between target/target2
// SUB_UseTargets will fire both target strings if found
// the random choices need to be stored elsewhere (noise3/4)
if (self.spawnflags & TRIG_RANDOMTARGET) {
if (random() < self.waitmin) self.target = self.noise3;
else self.target = self.noise4;
}
// One thing to note about trigger_relay is that it does not change the
// activator global variable to the entity that used this trigger last
// This is handy for client test triggers (like trigger_secret)
SUB_UseTargets();
};
//----------------------------------------------------------------------
void() trigger_relay =
{
trigger_bmodel_sounds(); // Precache any sounds
self.classtype = CT_TRIGRELAY;
if (self.delay <= 0) self.delay = 0;
if (self.cnt > 0) self.delay = self.delay + random()*self.cnt;
// Setup random trigger selection strings and random %
// No checks are done on the target/target so that they can
// be blank and the mapper can randomly select an empty trigger
// only check = The random % has got to exist between 0-1
if (self.spawnflags & TRIG_RANDOMTARGET) {
if (self.waitmin <=0 || self.waitmin >= 1) self.waitmin = 0.5;
self.noise3 = self.noise4 = ""; // Initialize strings
self.noise3 = self.target; // Copy over strings
self.noise4 = self.target2;
self.target = self.target2 = ""; // Remove originals
}
// Check for firing conditions (nightmare, coop)
if (check_nightmare() == TRUE) return;
if (check_coop() == TRUE) return;
// Setup Entity State functionality
if (self.targetname != "") self.use = entity_state_use;
self.estate_use = trigger_relay_use;
// Setup intial entity state (can start disabled)
if (self.spawnflags & TRIG_RELAYSTARTDIS) self.estate = ESTATE_DISABLE;
else self.estate = ESTATE_ON;
};
/*======================================================================
/*QUAKED trigger_counter (0.5 0 0.1) (-8 -8 -8) (8 8 8) NOMESSAGE x x x EXACTNO STARTDIS STARTOFF x Not_Easy Not_Normal Not_Hard Not_DM
A counter which triggers target(s) when complete
-------- KEYS --------
targetname : trigger entity (works with entity state system)
target : trigger target(s) when complete
message : message to display when complete (ignores NOMESSAGE spawnflag)
startmsg : message to display before counting begins (use wait for pause time)
count : number of triggers needed to fire own target, (def=2)
delay : time delay to fire final trigger event
sounds : 0=silent,1=Secret,2=talk,3=switch,5=custom,6=secret2
noise : custom sound to play when complete
wait : time to pause before starting to count (def=2s)
-------- SPAWNFLAGS --------
NOMESSAGE : disables count display
EXACTNO : display exact number when counting down
STARTDIS : Starts disabled and waits for trigger
STARTOFF : Requires trigger to activate
-------- NOTES --------
A counter which triggers target(s) when complete
======================================================================*/
void() trigger_counter_reset =
{
// Reset counter to initial (spawning) value
self.count = self.height;
};
//----------------------------------------------------------------------
void() trigger_counter_use =
{
if (self.estate & ESTATE_BLOCK) return;
// Check for any pre-count messages
if (self.startmsg != "") {
centerprint (activator, self.startmsg);
self.startmsg = "";
self.nextthink = time + self.wait;
self.think = self.estate_use;
return;
}
self.count = self.count - 1;
if (self.count < 0) return;
// Count down messages for trigger
if (self.count > 0) {
if (activator.flags & FL_CLIENT && !(self.spawnflags & TRIG_COUNTNOMESSAGE) ) {
if (self.count >= 4) {
if (self.spawnflags & TRIG_COUNTEXACTNO)
centerprint3 (activator, "Only ", ftos(self.count), " more to go...");
else centerprint (activator, "There are more to go...");
}
else if (self.count == 3) centerprint (activator, "Only 3 more to go...");
else if (self.count == 2) centerprint (activator, "Only 2 more to go...");
else centerprint (activator, "Only 1 more to go...");
}
return;
}
// Reach zero on counter, time to trigger counter target
if (activator.flags & FL_CLIENT) {
if (self.message2 != "") centerprint(activator, self.message2);
else if ( !(self.spawnflags & TRIG_COUNTNOMESSAGE) )
centerprint(activator, "Sequence completed!");
}
// If sound defined, play sound
if (self.noise != "") sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM);
// This is a big problem with the trigger_counter entity, it does not update
// the activator global variable and does not wake up enemies correctly.
// If a trigger_count is targetting monsters then they will not get angry
// at the player, the count has to target a _once or _multi trigger instead.
self.enemy = activator;
SUB_UseTargets();
};
//----------------------------------------------------------------------
void() trigger_counter_delay =
{
if (self.estate & ESTATE_BLOCK) return;
// Remove the trigger delay function
if (self.spawnflags & ENT_STARTOFF)
self.spawnflags = self.spawnflags - ENT_STARTOFF;
// Re-route use function to actual counter
self.estate_use = trigger_counter_use;
};
//----------------------------------------------------------------------
void() trigger_counter =
{
trigger_bmodel_sounds(); // Precache any sounds
if (!self.count) self.count = 2; // default count
self.height = self.count; // Save for later, reset
if (self.wait <= 0) self.wait = 2; // Default pre-message time
self.classtype = CT_TRIGCOUNT;
// Check for firing conditions (nightmare, coop)
if (check_nightmare() == TRUE) return;
if (check_coop() == TRUE) return;
// Setup Entity State functionality
if (self.targetname != "") self.use = entity_state_use;
self.estate_reset = trigger_counter_reset;
if (!self.estate) self.estate = ESTATE_ON;
// Check for disable state (need trigger_entityon to work again)
if (self.spawnflags & TRIG_COUNTSTARTDIS) self.estate = ESTATE_DISABLE;
// The delay function is not switched off, its activate to use
if (self.spawnflags & ENT_STARTOFF) self.estate_use = trigger_counter_delay;
else self.estate_use = trigger_counter_use;
};
/*======================================================================
/*QUAKED trigger_engine (0 0 1) (-8 -8 -16) (8 8 16) FITZ DP FTE x RAIN SNOW x x
Triggers target(s) when certain engine active
-------- KEYS --------
targetname : trigger entity
target : targets to trigger when relay is activated
wait : -1 = will only fire targets once
delay : delay before firing (after being triggered)
cnt : random amount of time to add to delay
-------- SPAWNFLAGS --------
FITZ : Only trigger for Fitz engines (default type)
DP : Only trigger for DP engine
FTE : Only trigger for FTE/QSS engines
RAIN : Check for rain effect being active
SNOW : Check for snow effect being active
-------- NOTES --------
Triggers target(s) when certain engine active
======================================================================*/
void() trigger_engine_fire =
{
// Parameter to check at of tests
self.aflag = FALSE;
// Check each engine type for conditions
// This trigger will not reset the activator
// Should be done with one of the targets instead
if (self.spawnflags & TRIG_ENGFITZ && engine == ENG_FITZ) self.aflag = TRUE;
else if (self.spawnflags & TRIG_ENGDP && engine == ENG_DPEXT) self.aflag = TRUE;
// Extra check for FTE/QSS (they support DP particles)
else if (self.spawnflags & TRIG_ENGFTE && engine == ENG_DPEXT) {
if (checkextension("FTE_SV_POINTPARTICLES")) self.aflag = TRUE;
}
if (self.aflag == TRUE) {
// Check for engine weather effects
if (self.spawnflags & TRIG_ENGRAIN && !ext_dprain) self.aflag = FALSE;
if (self.spawnflags & TRIG_ENGSNOW && !ext_dpsnow) self.aflag = FALSE;
// Check for weather being disabled
if (self.spawnflags & (TRIG_ENGRAIN | TRIG_ENGSNOW)) {
if (query_configflag(SVR_WEATHER)) self.aflag = FALSE;
}
// If engine and/or weather active, fire targets!
if (self.aflag == TRUE) SUB_UseTargets();
}
};
//----------------------------------------------------------------------
void() trigger_engine_use =
{
if (self.estate & ESTATE_BLOCK) return;
if (self.attack_finished > time) return;
// Setup to trigger once?
if (self.wait < 0) self.attack_finished = LARGE_TIMER;
// Check for any trigger delay?
if (self.delay > 0) {
self.think = trigger_engine_fire;
self.nextthink = time + self.delay;
}
else trigger_engine_fire();
};
//----------------------------------------------------------------------
void() trigger_engine =
{
self.classtype = CR_TRIGENG;
// Check for firing conditions (nightmare, coop)
if (check_nightmare() == TRUE) return;
if (check_coop() == TRUE) return;
// Setup Entity State functionality
if (self.targetname != "") self.use = entity_state_use;
self.estate_use = trigger_engine_use;
self.estate = ESTATE_ON;
};
/*======================================================================
/*QUAKED trigger_clientmsg (0.5 0.5 0) (-8 -8 -16) (8 8 16) x
Centerprints a message to all clients
-------- KEYS --------
targetname : trigger entity
message : Text to center print
sounds : 1=Secret,2=talk,3=switch,4=silent(def),5=custom,6=secret2
noise : custom sound to play when triggered
-------- SPAWNFLAGS --------
-------- NOTES --------
Centerprints a message to all clients
======================================================================*/
void() trigger_clientmsg_use =
{
if (self.estate & ESTATE_BLOCK) return;
// Write message to all clients
msg_entity = self;
WriteByte (MSG_ALL, SVC_CENTERPRINT);
WriteString (MSG_ALL, self.message);
// If any sounds defined play them at entity source
if (self.noise != "") sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM);
};
//----------------------------------------------------------------------
void() trigger_clientmsg =
{
// Default = the sound of silence!
if (self.sounds == 0) self.sounds = 4;
trigger_bmodel_sounds();
// Setup default message
if (self.message == "") self.message = "Default Trigger Message!\n";
self.classtype = CT_TRIGCLMSG;
// Check for firing conditions (nightmare, coop)
if (check_nightmare() == TRUE) return;
if (check_coop() == TRUE) return;
// Setup Entity State functionality
if (self.targetname != "") self.use = entity_state_use;
self.estate_use = trigger_clientmsg_use;
self.estate = ESTATE_ON;
};
/*======================================================================
/*QUAKED trigger_cdtrack (0.8 0.5 0) (-8 -8 -16) (8 8 16) x
Change CD track for all clients
-------- KEYS --------
targetname : trigger entity
count : CD track number (eg. 0-x)
-------- SPAWNFLAGS --------
-------- NOTES --------
Change CD track for all clients
======================================================================*/
void() trigger_cdtrack_change =
{
// Write CD track to all clients
msg_entity = self;
WriteByte (MSG_ALL, SVC_CDTRACK);
WriteByte (MSG_ALL, trig_cdtrack);
WriteByte (MSG_ALL, trig_cdtrack);
};
//----------------------------------------------------------------------
void() trigger_cdtrack_use =
{
// Check for entity state system block
if (self.estate & ESTATE_BLOCK) return;
// Make sure CD track change in savefile
trig_cdtrack = self.count;
// Write message to all clients
trigger_cdtrack_change();
};
//----------------------------------------------------------------------
void() trigger_cdtrack =
{
self.classtype = CT_TRIGCDTRACK;
// Make sure specified cd track is integer number
self.count = fabs(rint(self.count));
// Check for firing conditions (nightmare, coop)
if (check_nightmare() == TRUE) return;
if (check_coop() == TRUE) return;
// Setup Entity State functionality
if (self.targetname != "") self.use = entity_state_use;
self.estate_use = trigger_cdtrack_use;
self.estate = ESTATE_ON;
};
/*======================================================================
/*QUAKED trigger_skybox (0.9 0.9 0.9) (-8 -8 -16) (8 8 16) x
Load/Change Skybox for all clients
-------- KEYS --------
targetname : trigger entity
message : Skybox name (eg moonrise_)
-------- SPAWNFLAGS --------
-------- NOTES --------
Load/Change Skybox for all clients
======================================================================*/
void(entity targ) trigger_skybox_change =
{
// Write skybox change to all clients
stuffcmd(targ, "SKY ");
stuffcmd(targ, trig_skybox);
stuffcmd(targ, "\n");
};
//----------------------------------------------------------------------
void() trigger_skybox_use =
{
// Check for entity state system block
if (self.estate & ESTATE_BLOCK) return;
// Check for client trigger first
if (activator.classtype == CT_PLAYER) self.enemy = activator;
// Find a player for the stuff command
if (!self.enemy) {
self.enemy = find(world,targetname,"player");
if (self.enemy.classtype != CT_PLAYER) return;
}
// Write skybox name to all clients
trig_skybox = self.message;
trigger_skybox_change(self.enemy);
};
//----------------------------------------------------------------------
void() trigger_skybox =
{
self.classtype = CT_TRIGSKYBOX;
if (self.message == "") self.message = "Sky";
// Check for firing conditions (nightmare, coop)
if (check_nightmare() == TRUE) return;
if (check_coop() == TRUE) return;
// Setup Entity State functionality
if (self.targetname != "") self.use = entity_state_use;
self.estate_use = trigger_skybox_use;
self.estate = ESTATE_ON;
};
/*======================================================================
/*QUAKED trigger_itemrespawnupd (0.5 0 0.5) (-8 -8 -16) (8 8 16) x x x x RESPAWN x x x
Change the state of respawn flag on items
-------- KEYS --------
targetname : trigger entity
target : this points to the item to affect
-------- SPAWNFLAGS --------
RESPAWN : Value of respawn flag to copy to item
-------- NOTES --------
Change the state of respawn flag on items
Useful for switching off respawning items after arena fight is over
======================================================================*/
void() trigger_itemrespawnupd_use =
{
if (self.estate & ESTATE_BLOCK) return;
// Loop around target(s) and update respawn flag
self.enemy = find (world, targetname, self.target);
while(self.enemy) {
// only work with active items
if (self.enemy.flags & FL_ITEM) {
if (self.spawnflags & ITEM_RESPAWN) {
self.enemy.spawnflags = self.enemy.spawnflags | ITEM_RESPAWN;
}
else {
// Remove respawn flag (even if missing)
self.enemy.spawnflags = self.enemy.spawnflags - (self.enemy.spawnflags & ITEM_RESPAWN);
}
}
// Are there anymore targets left in the list?
self.enemy = find (self.enemy, targetname, self.target);
}
};
//----------------------------------------------------------------------
void() trigger_itemrespawnupd =
{
self.classtype = CT_TRIGITEMFLAG;
// Check for firing conditions (nightmare, coop)
if (check_nightmare() == TRUE) return;
if (check_coop() == TRUE) return;
// Setup Entity State functionality
if (self.targetname != "") self.use = entity_state_use;
self.estate_use = trigger_itemrespawnupd_use;
self.estate = ESTATE_ON;
};
/*======================================================================
QUAKED trigger_monstermovespeed (0.5 0 0.5) (-8 -8 -16) (8 8 16) x
Toggle the monster move speed state
-------- KEYS --------
targetname : trigger entity (works with entity state system)
target : this points to the monster(s) to affect
state : -1 = No movement, 0 = Toggle, 1 = Free movement
wait : -1 = trigger once only
-------- SPAWNFLAGS --------
-------- NOTES --------
Toggle the monster move speed state
======================================================================*/
void() trigger_monstermovespeed_use =
{
if (self.estate & ESTATE_BLOCK) return;
// Loop around target(s) and update move speed
self.enemy = find (world, targetname, self.target);
while(self.enemy) {
// only work with living monsters (ignore rest)
if (self.enemy.flags & FL_MONSTER && self.enemy.health > 0) {
// Is the state a toggle or exact update?
if (self.state == 0) {
if (self.enemy.movespeed < 1) self.enemy.movespeed = 1;
else self.enemy.movespeed = -1;
}
// Exact value update
else self.enemy.movespeed = self.state;
}
// Are there anymore targets left in the list?
self.enemy = find (self.enemy, targetname, self.target);
}
// Trigger once functionality?
if (self.wait < 0) self.estate = ESTATE_OFF;
};
//----------------------------------------------------------------------
void() trigger_monstermovespeed =
{
self.classtype = CT_TRIGMONMOVE;
if (self.target == "") {
dprint("\b[MON_MOVESPEED]\b No target set, removing\n");
remove(self);
}
// make sure state has the correct values
if (self.state < -1 || self.state > 1) self.state = 0;
// Check for firing conditions (nightmare, coop)
if (check_nightmare() == TRUE) return;
if (check_coop() == TRUE) return;
// Setup Entity State functionality
if (self.targetname != "") self.use = entity_state_use;
self.estate_use = trigger_monstermovespeed_use;
self.estate = ESTATE_ON;
};
/*======================================================================
/*QUAKED trigger_monsterkill (0.5 0 0.5) (-8 -8 -16) (8 8 16) x x x x DEATH x x x
Remove monster(s) from the map
-------- KEYS --------
targetname : Name of this trigger entity
target : Name of monster(s) to remove/stop
-------- SPAWNFLAGS --------
DEATH : kill monsters via death function
-------- NOTES --------
Remove monster(s) from the map
======================================================================*/
void() trigger_monsterkill_use =
{
if (self.estate & ESTATE_BLOCK) return;
// This only works once!
self.use = SUB_Null;
// Loop around target(s) and remove the game
self.enemy = find (world, targetname, self.target);
while(self.enemy) {
// There are always exceptions to the use of the monster flag
// Point Hell knights don't use monster flag to prevent damage!
if (self.enemy.spawnflags & MON_POINT_KNIGHT && self.enemy.health > 0)
self.enemy.flags = self.enemy.flags | FL_MONSTER;
// only work with monsters, can't be dead, dying or negative health!
if (self.enemy.flags & FL_MONSTER && self.enemy.health > 0 && !self.enemy.deadflag) {
// Check if death function is required for monster
// Also double check they have a death function setup!
if (self.spawnflags & TRIG_MONKILLDFUNC && self.enemy.th_die) {
self.enemy.takedamage = DAMAGE_YES;
// Always do enough damage to kill the monster
// Can be a problem for ammo resistant
T_Damage (self.enemy, self, self, self.enemy.health+1, NOARMOR);
}
else {
self.enemy.deadflag = DEAD_DEAD;
self.enemy.enemy = world;
self.enemy.health = self.enemy.gibhealth;
// If a monster marked with no monster count dies
// update HUD totals to reflect death properly
if (self.enemy.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);
// Stop all animation think functions
self.enemy.think = SUB_Null;
self.enemy.nextthink = time + LARGE_TIMER;
// Finally stop and hide all models
// Should not remove monster, it might have dependancies
entity_hide(self.enemy);
entity_remove(self.enemy.attachment, 0.1);
entity_remove(self.enemy.attachment2, 0.1);
entity_remove(self.enemy.attachment3, 0.1);
}
}
// Are there anymore targets left in the list?
self.enemy = find (self.enemy, targetname, self.target);
}
};
//----------------------------------------------------------------------
void() trigger_monsterkill =
{
self.classtype = CT_TRIGMONKILL;
// Check for firing conditions (nightmare, coop)
if (check_nightmare() == TRUE) return;
if (check_coop() == TRUE) return;
// Setup Entity State functionality
if (self.targetname != "") self.use = entity_state_use;
self.estate_use = trigger_monsterkill_use;
self.estate = ESTATE_ON;
};
/*======================================================================
/*QUAKED trigger_monsterattack (0.5 0 0.5) (-8 -8 -16) (8 8 16) x
Force a monster to attack a certain target
-------- KEYS --------
targetname : Name of this trigger entity
target : Name of monster to affect
target2 : Name of entity to attack
-------- SPAWNFLAGS --------
-------- NOTES --------
Force a monster to attack a certain target
======================================================================*/
void() trigger_monsterattack_use =
{
if (self.estate & ESTATE_BLOCK) return;
// Find target monster first and see if alive?
self.enemy = find (world, targetname, self.target);
// only work with monsters, can't be dead, dying or negative health!
if (self.enemy.flags & FL_MONSTER && self.enemy.health > 0) {
self.oldenemy = find(world, targetname, self.target2);
// Check if attacking target is alive and can be damaged?
if (self.oldenemy.health > 0 && self.oldenemy.takedamage != DAMAGE_NO) {
// Setup monster to attack new target (use damage function)
T_Damage (self.enemy, self.oldenemy, self.oldenemy, 1, NOARMOR);
}
}
};
//----------------------------------------------------------------------
void() trigger_monsterattack =
{
self.classtype = CT_TRIGMONATT;
// Check for firing conditions (nightmare, coop)
if (check_nightmare() == TRUE) return;
if (check_coop() == TRUE) return;
// Setup Entity State functionality
if (self.targetname != "") self.use = entity_state_use;
self.estate_use = trigger_monsterattack_use;
self.estate = ESTATE_ON;
};
//======================================================================
// General purpose sprite+particle explosion
// Entity is fired once (default)
//======================================================================
/*QUAKED trigger_explode (.8 .5 .5) (-4 -4 -4) (4 4 4) x EXPLODENOEFF DUST
Triggered entity producing damage + sprite explosion
-------- KEYS --------
targetname : trigger entity (works with entity state system)
dmg : explosive radius damage (def=40, -1=no damage)
delay : time delay to explosion (def=0s)
noise : string name for explosion sound (def=weapons/r_exp3b.wav)
wait : re-trigger explosions (def=-1 trigger once)
style : 0=Explosion, 1=Plasma, 2=Poison, 3=Electric, 4=Burst (Smoke/Flame/Poison)
height : 0=Small, 1=Medium, 2=Large, -1=Random
count : Random amount of dust particles to spawn (1-x)
pos1 : Base dust velocity (X=Forward, Y=Right, Z=Up)
pos2 : Random dust velocity (X=Forward, Y=Right, Z=Up)
-------- SPAWNFLAGS --------
EXPLODENOEFF : no old school particle explosion
DUST : Dust particle explosion (use angle for direction)
-------- NOTES --------
Triggered entity producing damage + sprite explosion
=============================================================================*/
void() trig_explode_fire =
{
// Dust particles are empty models with rocket smoke trails
if (self.spawnflags & TRIG_EXPLODEDUST) {
// Setup direction of projectile
makevectors(self.angles);
// Always spawn at least 1 dust particle
self.lip = 1 + rint(random() * self.count);
while (self.lip > 0) {
// Keep spawning temporary entities
newmis = spawn();
newmis.classgroup = CG_TEMPENT;
newmis.movetype = MOVETYPE_TOSS;
newmis.solid = SOLID_NOT;
setmodel(newmis, MODEL_PROJ_SMOKE);
// Randomize the origin of the particles
self.oldorigin = self.origin + (v_right*(crandom()*self.pos1_y));
setorigin(newmis, self.oldorigin);
setsize (newmis, VEC_ORIGIN, VEC_ORIGIN);
// Use pos1+pos2 vector (calculated from angles)
self.pos3_x = self.pos1_x + ( random() * self.pos2_x );
self.pos3_y = crandom() * (self.pos1_y + ( random() * self.pos2_y ));
self.pos3_z = self.pos1_z + ( random() * self.pos2_z );
newmis.velocity = (v_forward*self.pos3_x) + (v_right*self.pos3_y) + (v_up*self.pos3_z);
// Setup removal and keep counting
newmis.nextthink = time + 1 + random()*3;
newmis.think = SUB_Remove;
self.lip = self.lip - 1;
}
// No damage or sprites needed
return;
}
// create any explosive radius damage
if (self.dmg > 0) T_RadiusDamage (self, self, self.dmg, self, DAMAGEALL);
// Check for old particle effect
if ( !(self.spawnflags & TRIG_EXPLODENOEFF) ) {
// classic ID explosion
if (self.style == 0) {
WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
WriteByte (MSG_BROADCAST, TE_EXPLOSION);
WriteCoord (MSG_BROADCAST, self.origin_x);
WriteCoord (MSG_BROADCAST, self.origin_y);
WriteCoord (MSG_BROADCAST, self.origin_z);
}
// New Rogue extension - Blue
else if (self.style == 1) {
WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
WriteByte (MSG_BROADCAST, TE_EXPLOSION2);
WriteCoord (MSG_BROADCAST, self.origin_x);
WriteCoord (MSG_BROADCAST, self.origin_y);
WriteCoord (MSG_BROADCAST, self.origin_z);
WriteByte (MSG_BROADCAST, 39); // Blue 32 + 7
WriteByte (MSG_BROADCAST, 8); // Colour range
}
// New Rogue extension - Green
else if (self.style == 2) {
WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
WriteByte (MSG_BROADCAST, TE_EXPLOSION2);
WriteCoord (MSG_BROADCAST, self.origin_x);
WriteCoord (MSG_BROADCAST, self.origin_y);
WriteCoord (MSG_BROADCAST, self.origin_z);
WriteByte (MSG_BROADCAST, 55); // Green 48 + 7
WriteByte (MSG_BROADCAST, 8); // Colour range
}
}
// Check for randomly sized explosion type
if (self.height == -1) self.lip = rint(0.5+ random() * 2.5);
else self.lip = rint(self.height);
// Work out explosion type offset
self.lip = self.lip + rint(self.style) * 10;
// Use global function (Fitz/DP aware)
SpawnExplosion(self.lip, self.origin, self.noise);
};
//----------------------------------------------------------------------
void() trig_explode_use =
{
if (self.estate & ESTATE_BLOCK) return;
if (self.attack_finished > time) return;
// Check for Trigger once setting
if (self.wait < 0) self.attack_finished = LARGE_TIMER;
// Check for explosion delay or straight away blow up
if (self.delay > 0) {
self.nextthink = time + self.delay;
self.think = trig_explode_fire;
}
else trig_explode_fire();
};
//----------------------------------------------------------------------
void() trigger_explode =
{
// Default settings
self.classtype = CT_TRIGEXPLODE;
if (self.dmg >= 0) self.dmg = DAMAGE_MONROCKET;
if (self.wait == 0) self.wait = -1;
if (self.height == 0 || self.height > 3) self.height = 1;
if (self.style < 0 || self.style > 4) self.style = 0;
self.attack_finished = 0;
if (self.noise == "") {
if (self.style == 3) self.noise = SOUND_PLASMA_HIT;
else if (self.style == 4) self.noise = SOUND_RESIST_ROCKET;
else self.noise = SOUND_REXP3; // Default
}
precache_sound (self.noise);
// Precache smoke particles and set default movement
if (self.spawnflags & TRIG_EXPLODEDUST) {
precache_model(MODEL_PROJ_SMOKE);
if (CheckZeroVector(self.angles)) self.angles_y = 360;
if (CheckZeroVector(self.pos1)) self.pos1 = '100 25 100';
if (CheckZeroVector(self.pos2)) self.pos2 = '100 25 100';
}
// Burst Sprites are NOT precached in world.qc
if (self.style == 4) {
// Check for random selection
if (self.height == -1) {
precache_model(SBURST_SMOKE);
precache_model(SBURST_FLAME);
precache_model(SBURST_POISON);
}
else if (self.height == 1) precache_model(SBURST_SMOKE);
else if (self.height == 2) precache_model(SBURST_FLAME);
else precache_model(SBURST_POISON);
}
// Check for firing conditions (nightmare, coop)
if (check_nightmare() == TRUE) return;
if (check_coop() == TRUE) return;
// Setup Entity State functionality
if (self.targetname != "") self.use = entity_state_use;
self.estate_use = trig_explode_use;
self.estate = ESTATE_ON;
};