Files
2019-12-30 22:24:44 +01:00

1748 lines
59 KiB
Plaintext

/*======================================================================
MISC ENTITIES
======================================================================*/
float MISC_STARTOFF = 1; // Start OFF (use DELAY instead!)
float MISC_FBALLSLIME = 32; // Slime green fire ball
float MISC_DRIPSILENT = 2; // misc_drip has no sound on splash
float MISC_DRIPBLOOD = 16; // blood red drips
float MISC_DRIPSLIME = 32; // slime green drips
float MISC_SMOKENODPMDL = 2; // Do not draw smoke model in DP
float MISC_SMOKENODPFX = 4; // Do not produce any DP smoke effects
float MISC_SMOKENOQSMDL = 8; // Do not draw smoke model in QS
float MISC_SPARKBLUE = 2; // misc_spark produces Blue sparks
float MISC_SPARKPALE = 4; // misc_spark produces Pale Yellow sparks
float MISC_SPARKRED = 8; // misc_spark produces Red sparks
float MISC_COLLISION = 2; // misc_model has collision enabled
float MISC_MOVEMENT = 4; // misc_model can be moved around
float MISC_SHAKEVIEWONLY = 2; // No velocity movement
float MISC_EXPLBOX_MAX = 5; // Maximum amount of skins available
/*======================================================================
/*QUAKED misc_explobox (0 0.5 0.8) (-16 -16 0) (16 16 64) x x x x x FLOAT STARTOFF
{ model(":progs/explode_box1.mdl"); }
Large exploding box
-------- KEYS --------
target : trigger events when box explodes
skin_override : 0=original, 1=rubicon2, 3=plasma, 4=toxic, 5-6=wood
noise : Explosion sound (def=weapons/r_exp3.wav)
health : Amount of health before exploding (def=15)
dmg : Override radius damage (def=160)
-------- SPAWNFLAGS --------
FLOAT : No drop to floor test
STARTOFF : Starts off and waits for trigger
-------- NOTES --------
Large exploding box
/*QUAKED misc_explobox2 (0 0.5 0.8) (-16 -16 0) (16 16 32) x x x x x FLOAT STARTOFF
{ model(":progs/explode_box2.mdl"); }
Small exploding box
-------- KEYS --------
target : trigger events when box explodes
skin_override : 0=original, 1=rubicon2, 3=plasma, 4=toxic, 5-6=wood
noise : Explosion sound (def=weapons/r_exp3.wav)
health : Amount of health before exploding (def=15)
dmg : Override radius damage (def=160)
-------- SPAWNFLAGS --------
FLOAT : No drop to floor test
STARTOFF : Starts off and waits for trigger
-------- NOTES --------
Small exploding box
/*QUAKED func_explobox (0 0.5 0.8) ? x x x x x x STARTOFF x
Exploding box (bmodel)
-------- KEYS --------
target : trigger events when box explodes
noise : Explosion sound (def=weapons/r_exp3.wav)
health : Amount of health before exploding (def=15)
dmg : Override radius damage (def=160)
_dirt : -1 = will be excluded from dirtmapping
_minlight : Minimum light level for any surface of the brush model
_mincolor : Minimum light color for any surface (def='1 1 1' RGB)
_shadow : Will cast shadows on other models and itself
_shadowself : Will cast shadows on itself
-------- SPAWNFLAGS --------
STARTOFF : Starts off and waits for trigger
-------- NOTES --------
Exploding box (bmodel)
======================================================================*/
// Animate model skin slowly = 0.3s
//----------------------------------------------------------------------
void() misc_ebox_0 = [0, misc_ebox_0] {
if (self.attack_finished < time) {
self.lefty = 1 - self.lefty;
self.skin = self.lefty + (self.skin_override * 2);
self.nextthink = time + MODEL_ANIM_SPEED;
}
};
//----------------------------------------------------------------------
void() misc_explod_delay =
{
// Explosive damage and sound+fx
T_RadiusDamage (self, self, self.dmg, world, DAMAGEALL);
// Particle explosion drifting upward
particle_explode(self.origin, 50+random()*50, 1, self.part_style, PARTICLE_BURST_UPWARD);
// Play original explosion sound
sound (self, CHAN_WEAPON, self.noise, 1, ATTN_NORM);
// Special type (hard-coded) of particle explosion
// that only works if particle count=255 :)
particle (self.origin, '0 0 0', 0, 255);
// Check for any explosion triggers
if (self.target != "") trigger_strs(self.target, other);
self.origin = self.origin + '0 0 32';
SpawnExplosion(EXPLODE_SMALL, self.origin, SOUND_REXP3);
entity_hide(self);
};
// Blow up the box
//----------------------------------------------------------------------
void() misc_explod_fire =
{
local entity ent_explode;
if (self.attack_finished > time) return;
self.attack_finished = time + LARGE_TIMER;
// Save origin for later
if (self.bsporigin) self.oldorigin = bmodel_origin(self);
else self.oldorigin = self.origin;
// Switch off everything
self.estate_off();
// Stop recursive loops with T_RadiusDamage
// Delay each explosion so its not all at once
// Spawn the explosion/dmg/fx as a new entity
// func_explode does not want to set correct origin
ent_explode = spawn();
setorigin(ent_explode, self.oldorigin);
ent_explode.noise = self.noise;
ent_explode.dmg = self.dmg;
ent_explode.think = misc_explod_delay;
ent_explode.nextthink = 0.01 + random()*0.1;
};
//----------------------------------------------------------------------
void() misc_explobox_use =
{
// If box is setup off, switch on first
if (self.spawnflags & ENT_STARTOFF) {
self.spawnflags = self.spawnflags - ENT_STARTOFF;
self.estate_on();
}
// No toggle function, just blow it up!
else misc_explod_fire();
};
//----------------------------------------------------------------------
void() misc_explobox_on =
{
// If the box has exploded, do nothing
if (self.attack_finished > time) return;
// Stop re-triggering ON state
if (self.estate == ESTATE_ON) return;
self.estate = ESTATE_ON;
if (self.bsporigin) {
self.solid = SOLID_BSP;
self.movetype = MOVETYPE_PUSH;
}
else {
self.solid = SOLID_BBOX;
self.movetype = MOVETYPE_NONE;
}
setmodel (self, self.mdl);
self.skin = self.skin_override*2;
setsize(self, self.bbmins, self.bbmaxs);
self.th_die = misc_explod_fire;
self.takedamage = DAMAGE_AIM;
self.nextthink = time + 0.1;
self.think = misc_ebox_0;
};
//----------------------------------------------------------------------
void() misc_explobox_off =
{
self.estate = ESTATE_OFF;
self.solid = SOLID_NOT;
self.modelindex = 0; // Make sure no model
self.model = ""; // hide model
self.takedamage = DAMAGE_NO;
self.th_die = SUB_Null;
self.think = SUB_Null;
};
//----------------------------------------------------------------------
void() misc_explobox_setup =
{
if (self.noise == "") self.noise = SOUND_REXP3;
precache_sound (self.noise);
self.classtype = CT_EXPLO_BOX;
self.classgroup = CG_MISCENT;
if (self.health < 1) self.health = 15; // ID def = 20
if (!self.dmg) self.dmg = 160;
// Check and setup default box skin
// 0=Original, 1=rubicon2, 3=plasma, 4=toxic
if (self.skin_override < 1 || self.skin_override > MISC_EXPLBOX_MAX)
self.skin_override = 0;
// Setup particle explosion colour
if (self.skin_override == 2) self.part_style = PARTICLE_BURST_BLUE;
else if (self.skin_override == 3) self.part_style = PARTICLE_BURST_GREEN;
else self.part_style = PARTICLE_BURST_WHITE;
// Setup exploding model
if (self.bsporigin) {
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;
}
else {
// Query console variable 'temp1' for model upgrade option.
// Cannot use global vars because they don't exist at this point
// Move the new centered exploding models to match old box origin
// The default is to move all boxes to suit original id maps
if (!query_configflag(SVR_ITEMOFFSET)) {
self.oldorigin = self.origin + '16 16 0';
setorigin(self, self.oldorigin);
}
// Setting the angle key in the editor to UP/DOWN = random rotation
if (self.angles_y <= 0) self.angles_y = rint(random()*359);
// Temporarily enable model/bbox for collision test
// Finalize box location (check drop to floor)
if( !(self.spawnflags & ITEM_FLOATING) ) {
setmodel (self, self.mdl);
setsize(self, self.bbmins, self.bbmaxs);
self.solid = SOLID_BBOX;
self.movetype = MOVETYPE_TOSS;
self.origin_z = self.origin_z + 6;
droptofloor();
if (pointcontents(self.origin) == CONTENT_SOLID) {
dprint ("\n\b[ExplBox]\b "); dprint (self.classname);
dprint (" stuck at ("); dprint (vtos(self.origin)); dprint (")\n");
spawn_marker(self.origin, SPNMARK_YELLOW);
remove(self);
return;
}
}
}
// Setup Entity State functionality
if (self.targetname != "") self.use = entity_state_use;
self.estate_on = misc_explobox_on;
self.estate_off = misc_explobox_off;
self.estate_use = misc_explobox_use;
if (self.spawnflags & ENT_STARTOFF) self.estate_off();
else self.estate_on();
};
//----------------------------------------------------------------------
void() misc_explobox =
{
self.mdl = "progs/explode_box1.mdl"; // maps/b_explob.bsp
precache_model (self.mdl);
self.bbmins = '-16 -16 0';
self.bbmaxs = '16 16 64';
misc_explobox_setup();
};
void() misc_explobox2 =
{
self.mdl = "progs/explode_box2.mdl"; // maps/b_explob2.bsp
precache_model (self.mdl);
self.bbmins = '-16 -16 0';
self.bbmaxs = '16 16 32';
misc_explobox_setup();
};
//----------------------------------------------------------------------
void () func_explobox =
{
if (check_bmodel_keys()) return; // Check for bmodel errors
self.mdl = self.model; // Brushwork version
self.bsporigin = TRUE; // bmodel object
self.angles = '0 0 0'; // Make sure no angle twist
// Cannot be setup floating (always remove flag)
self.spawnflags = self.spawnflags - (self.spawnflags & ITEM_FLOATING);
misc_explobox_setup();
};
//======================================================================
/*QUAKED misc_fireball (0 .5 .8) (-8 -8 -8) (8 8 8) x x x x x SLIME STARTOFF x
Lava Balls, with damage on impact
-------- KEYS --------
targetname : toggle state (use trigger ent for exact state)
speed : vertical speed (default 1000)
dmg : impact damage (default 5)
delay : base time between spawning fireballs (default 3)
wait : random time default 5 (= time + self.delay + (random() x self.wait) )
-------- SPAWNFLAGS --------
SLIME : Green slime version (smoke trail)
STARTOFF : Always Starts off and waits for trigger
-------- NOTES --------
Lava Balls, with damage on impact
*/
//======================================================================
void() misc_lavaball_fly;
void() misc_lavaball_reset =
{
self.touch = SUB_Null;
self.flags = 0;
self.modelindex = 0; // Make sure no model
self.model = ""; // Hide model
self.solid = SOLID_NOT;
self.movetype = MOVETYPE_NONE;
setsize (self, VEC_ORIGIN, VEC_ORIGIN);
self.velocity = self.avelocity = '0 0 0';
self.nextthink = time + self.delay + (random() * self.wait);
self.think = misc_lavaball_fly;
};
//----------------------------------------------------------------------
void() misc_lavaball_touch =
{
self.touch = SUB_Null;
if (other.takedamage) T_Damage (other, self, self, self.dmg, DAMARMOR);
misc_lavaball_reset();
};
//----------------------------------------------------------------------
void() misc_lavaball_fly =
{
if (self.estate & ESTATE_BLOCK) return;
setmodel (self, self.mdl);
setorigin (self, self.oldorigin);
// Remove all fly/onground flags
self.flags = 0;
self.solid = SOLID_TRIGGER;
self.movetype = MOVETYPE_TOSS;
setsize (self, VEC_ORIGIN, VEC_ORIGIN);
self.velocity_x = (random() * 100) - 50;
self.velocity_y = (random() * 100) - 50;
self.velocity_z = self.speed + (random() * 200);
self.nextthink = time + 5;
self.think = misc_lavaball_reset;
self.touch = misc_lavaball_touch;
};
//----------------------------------------------------------------------
void() misc_lavaball_on =
{
self.estate = ESTATE_ON;
self.nextthink = time + self.delay + (random() * self.wait);
self.think = misc_lavaball_fly;
};
//----------------------------------------------------------------------
void() misc_fireball =
{
if (self.spawnflags & MISC_FBALLSLIME) self.mdl = MODEL_PROJ_SLIME;
else self.mdl = MODEL_PROJ_LAVA;
precache_model (self.mdl);
self.classtype = CT_FIREBALL;
self.classgroup = CG_MISCENT;
self.oldorigin = self.origin;
if (self.speed <= 0) self.speed = 1000;
if (self.dmg <= 0) self.dmg = 20;
if (self.wait <= 0) self.wait = 5;
if (self.delay <= 0) self.delay = 3;
// Setup Entity State functionality
if (self.targetname != "") self.use = entity_state_use;
self.estate_on = misc_lavaball_on;
if (self.spawnflags & (MISC_STARTOFF | ENT_STARTOFF)) entity_state_off();
else entity_state_on();
};
/*======================================================================
/*QUAKED air_bubbles (0 .5 .8) (-8 -8 -8) (8 8 8) x x x x x x STARTOFF x
sprite based bubble that floats upward
-------- KEYS --------
targetname : toggle state (use trigger ent for exact state)
style : 1-15 (grey,brown1,blue1,green1,red1,brown2,pinkyel,brown3,purp1,purp2,brown4,green2,yellow,blue2,red2)
-------- SPAWNFLAGS --------
STARTOFF : Always Starts off and waits for trigger
-------- NOTES --------
sprite based bubble that floats upward
======================================================================*/
void() misc_bubble_bob;
void() misc_bubble_remove =
{
if (other.classtype == self.classtype) return;
remove(self);
};
//----------------------------------------------------------------------
void() misc_bubble_split =
{
local entity bubble;
bubble = spawn();
bubble.classname = self.classname;
bubble.classtype = CT_BUBBLE;
bubble.classgroup = CG_TEMPENT;
setmodel (bubble, self.mdl);
setorigin (bubble, self.origin);
bubble.movetype = MOVETYPE_NOCLIP;
bubble.solid = SOLID_NOT;
bubble.velocity = self.velocity;
bubble.nextthink = time + 0.5;
bubble.think = misc_bubble_bob;
bubble.touch = misc_bubble_remove;
bubble.frame = 1;
bubble.cnt = 10;
setsize (bubble, '-8 -8 -8', '8 8 8');
self.frame = 1; // Smaller bubble
self.cnt = 10;
if (self.waterlevel != 3) remove (self);
};
//----------------------------------------------------------------------
void() misc_bubble_bob =
{
local float rnd1, rnd2, rnd3;
self.cnt = self.cnt + 1;
if (self.cnt == 4) misc_bubble_split();
if (self.cnt == 20) { remove(self); return; }
rnd1 = self.velocity_x + (-10 + (random() * 20));
rnd2 = self.velocity_y + (-10 + (random() * 20));
rnd3 = self.velocity_z + 10 + random() * 10;
if (rnd1 > 10) rnd1 = 5;
if (rnd1 < -10) rnd1 = -5;
if (rnd2 > 10) rnd2 = 5;
if (rnd2 < -10) rnd2 = -5;
if (rnd3 < 10) rnd3 = 15;
if (rnd3 > 30) rnd3 = 25;
self.velocity_x = rnd1;
self.velocity_y = rnd2;
self.velocity_z = rnd3;
self.nextthink = time + 0.5;
self.think = misc_bubble_bob;
};
//----------------------------------------------------------------------
void() misc_bubble_spawn =
{
local entity bubble;
if (self.estate & ESTATE_BLOCK) return;
bubble = spawn();
bubble.classname = self.classname;
bubble.classtype = CT_BUBBLE;
bubble.classgroup = CG_TEMPENT;
bubble.owner = self;
setmodel (bubble, self.mdl);
setorigin (bubble, self.origin);
bubble.movetype = MOVETYPE_NOCLIP;
bubble.solid = SOLID_NOT;
bubble.velocity = '0 0 15';
bubble.nextthink = time + 0.5;
bubble.think = misc_bubble_bob;
bubble.touch = misc_bubble_remove;
bubble.frame = bubble.cnt = 0;
setsize (bubble, '-8 -8 -8', '8 8 8');
self.nextthink = time + random() + 0.5;
self.think = misc_bubble_spawn;
};
// Map hack entry point
void() make_bubbles = {
self.mdl = SBUBBLE_DROWN;
misc_bubble_spawn();
};
//----------------------------------------------------------------------
void() misc_bubble_on =
{
self.estate = ESTATE_ON;
self.nextthink = time + 1 + random();
self.think = misc_bubble_spawn;
};
//----------------------------------------------------------------------
void() air_bubbles =
{
if (deathmatch) { remove(self); return; }
self.mdl = SBUBBLE_DROWN;
precache_model (self.mdl);
// Allow for custom bubble sprites
if (self.style > 0) trigger_setup_bubbles();
// Setup Entity State functionality
if (self.targetname != "") self.use = entity_state_use;
self.estate_on = misc_bubble_on;
if (self.spawnflags & (MISC_STARTOFF | ENT_STARTOFF)) entity_state_off();
else entity_state_on();
};
/*=============================================================================
FX drips (based on code from RRP by ijed)
- Rewritten to not keep spawning endless entities
- Modified to have on/off/toggle state
/*QUAKED misc_drip (0 .5 .8) (-8 -8 -8) (8 8 8) x SILENT x x BLOOD SLIME STARTOFF x
Falling water drip with splash and sound
-------- KEYS --------
targetname : toggle state (use trigger ent for exact state)
wait : random time between drips (=random() + self.wait)
-------- SPAWNFLAGS --------
BLOOD : Blood red drips
SLIME : Slime green drips
SILENT : Don't make any drip sound (good for multiple drips)
STARTOFF : Always Starts off and waits for trigger
-------- NOTES --------
Falling water drip with splash and sound
=============================================================================*/
void() misc_drip_spawn;
void() misc_drip_reset =
{
self.touch = SUB_Null;
self.frame = self.flags = 0;
setmodel (self, "");
self.solid = SOLID_NOT;
self.movetype = MOVETYPE_NONE;
setsize (self, VEC_ORIGIN, VEC_ORIGIN);
self.velocity = self.avelocity = '0 0 0';
self.nextthink = time + random() + self.wait;
self.think = misc_drip_spawn;
};
//----------------------------------------------------------------------
// splash animation (runs at 20fps)
void() s_splash1 = [0, s_splash2] {self.nextthink = time+0.05;};
void() s_splash2 = [1, s_splash3] {self.nextthink = time+0.05;};
void() s_splash3 = [2, s_splash4] {self.nextthink = time+0.05;};
void() s_splash4 = [3, s_splash5] {self.nextthink = time+0.05;};
void() s_splash5 = [4, s_splash6] {self.nextthink = time+0.05;};
void() s_splash6 = [5, misc_drip_reset] {self.nextthink = time+0.05;};
//----------------------------------------------------------------------
void() misc_drip_touch =
{
self.touch = SUB_Null;
self.solid = SOLID_NOT;
self.movetype = MOVETYPE_NONE;
// If water below, shift origin to water surface
if (self.cnt) self.origin = self.pos1;
setorigin(self, self.origin + '0 0 12');
// play a random drip sound
if (!(self.spawnflags & MISC_DRIPSILENT)) {
self.lip = random() * 3;
if (self.lip < 1) sound (self, CHAN_AUTO, self.noise1, 1, ATTN_STATIC);
else if (self.lip < 2) sound (self, CHAN_AUTO, self.noise2, 1, ATTN_STATIC);
else sound (self, CHAN_AUTO, self.noise3, 1, ATTN_STATIC);
}
// small particle effect when hitting something
particle (self.origin+'0 0 1', '0 0 0.5', self.aflag+random()*4, 5+random()*5);
// Switch to splash sprite and animate for 5 frames
setmodel (self, self.headmdl);
s_splash1();
};
//----------------------------------------------------------------------
// Keep checking while falling for liquid impact
//----------------------------------------------------------------------
void() misc_drip_water =
{
if (self.attack_finished < time) misc_drip_reset();
// Extremely simplified water surface check (pre-calculated)
if (self.origin_z < self.pos1_z) misc_drip_touch();
self.nextthink = time + 0.1;
};
//----------------------------------------------------------------------
// Setup new drip and wait for touch/death/water
//----------------------------------------------------------------------
void() misc_drip_spawn =
{
// Is the entity OFF or BLOCKED?
if (self.estate & ESTATE_BLOCK) return;
// make sure player distance check is available
if (!self.enemy) self.enemy = find(world, classname, "player");
if ( !(self.enemy.flags & FL_CLIENT) ) {
self.nextthink = time + 0.1;
self.think = misc_drip_spawn;
return;
}
// Is the misc_drip close to the player?
if (range_distance(self.enemy, TRUE) > self.wakeup_dist) {
self.nextthink = time + random() + self.wait;
self.think = misc_drip_spawn;
return;
}
// Move drip to start position and setup sprite
setorigin (self, self.oldorigin);
setmodel (self, self.mdl);
self.solid = SOLID_TRIGGER;
if (self.cnt) self.movetype = MOVETYPE_NOCLIP;
else self.movetype = MOVETYPE_FLY;
setsize (self, VEC_ORIGIN, VEC_ORIGIN);
self.velocity_z = -map_gravity;
// Drip only lasts 3s, don't want to travel forever
self.think = misc_drip_reset;
self.nextthink = time + 3;
self.touch = misc_drip_touch;
// Is there any water underneath drip?
if (self.cnt) {
self.attack_finished = self.nextthink;
self.nextthink = time + 0.1;
self.think = misc_drip_water;
}
};
//----------------------------------------------------------------------
void() misc_drip_on =
{
self.estate = ESTATE_ON;
self.nextthink = time + random() + self.wait;
self.think = misc_drip_spawn;
};
//----------------------------------------------------------------------
void() misc_drip =
{
// Pick type of drip sprite based on spawnflags
if (self.spawnflags & MISC_DRIPBLOOD) {
self.mdl = SBLOOD_DRIP;
self.headmdl = SBLOOD_SPLASH;
self.aflag = 64;
}
else if (self.spawnflags & MISC_DRIPSLIME) {
self.mdl = SSLIME_DRIP;
self.headmdl = SSLIME_SPLASH;
self.aflag = 48;
}
else {
self.mdl = SWATER_DRIP;
self.headmdl = SWATER_SPLASH;
self.aflag = 0;
}
// Default cache - water
precache_model (self.mdl);
precache_model (self.headmdl);
self.noise1 = "misc/drip1.wav";
self.noise2 = "misc/drip2.wav";
self.noise3 = "misc/drip3.wav";
precache_sound (self.noise1);
precache_sound (self.noise2);
precache_sound (self.noise3);
// default frequency to 3 seconds
if (self.wait <= 0) self.wait = 3;
// No point dripping if no drippy player around!
if (self.wakeup_dist <= 0) self.wakeup_dist = 1024;
self.classtype = CT_MISCDRIP;
self.classgroup = CG_MISCENT;
self.solid = SOLID_NOT;
self.movetype = MOVETYPE_NONE;
setsize(self, VEC_ORIGIN, VEC_ORIGIN);
// Check point content for wierd setups (inside liquids/solids)
self.height = pointcontents(self.origin);
if (self.height < CONTENT_SOLID) {
dprint ("\b[MISCDRIP]\b Spawned inside liquid!\n");
spawn_marker(self.origin, SPNMARK_YELLOW);
remove(self);
return;
}
// If flush against a ceiling, move slightly down
else if (self.height == CONTENT_SOLID) {
self.origin_z = self.origin_z - 2;
}
// Setup drip particle in the correct start location
self.oldorigin = self.origin;
setorigin(self, self.origin);
// Find out bottom (world) position first
self.pos1 = self.origin;
traceline (self.pos1, self.pos1 + '0 0 -4096', TRUE, self);
self.pos2 = trace_endpos;
// Only do loop test if water exists below
self.count = 8;
if (trace_inwater) self.cnt = TRUE;
else self.count = 0;
// Binary divide the distance to find water surface
while (self.count > 0) {
// Break out early from loop if <8 from water surface
if (fabs(self.pos2_z-self.pos1_z) < 8) self.count = 0;
// Calculate midway point between origin and endtrace
self.pos3 = self.pos1;
self.pos3_z = self.pos1_z + ((self.pos2_z - self.pos1_z)*0.5);
// Test which half has water and shift top/bottom positions
traceline (self.pos1, self.pos3, TRUE, self);
if (trace_inwater) self.pos2 = self.pos3;
else self.pos1 = self.pos3;
// Only loop a limited amount of times
self.count = self.count - 1;
}
// Setup Entity State functionality
if (self.targetname != "") self.use = entity_state_use;
self.estate_on = misc_drip_on;
if (self.spawnflags & (MISC_STARTOFF | ENT_STARTOFF)) entity_state_off();
else entity_state_on();
};
// Re-direct to correct entity name
void() fx_drip = { misc_drip(); };
//======================================================================
/*QUAKED misc_steam (.5 .5 .75) (-8 -8 -8) (8 8 8) x x x x x x STARTOFF x
Steam/Smoke particles
-------- KEYS --------
targetname : toggle state (use trigger ent for exact state)
target : targeting entity used for custom direction
angles : 'pitch roll yaw' up/down, angle, tilt left/right
wait : time between generation of smoke particles (def=0.1, min=0.01)
delay : random amount of time delay ( time = wait + delay x random() )
height : Percentage of velocity distance travelled (def=1, range=0-1+)
-------- SPAWNFLAGS --------
STARTOFF : Always Starts off and waits for trigger
-------- NOTES --------
Steam/Smoke particles
======================================================================*/
//======================================================================
/*QUAKED misc_smoke (.5 .5 .75) (-8 -8 -8) (8 8 192) x NODPMDL NODPFX NOQSMDL x x STARTOFF x
Smoke model (+DP only smoke effect)
-------- KEYS --------
targetname : toggle state (use trigger ent for exact state)
target : targeting entity used for custom direction
angles : 'pitch roll yaw' up/down, angle, tilt left/right
exactskin : 0=Gunsmoke, 1=Soot, 2=Steam, 3=Toxin, 4=Plague, 5=Incense, 6=Lithium, 7=Flames
alpha : alpha value for model (def=0.65)
wait : time between generation of smoke particles (def=0.1, min=0.01)
delay : random amount of time delay ( time = wait + delay x random() )
height : Percentage of velocity distance travelled (def=1, range=0-1+)
-------- SPAWNFLAGS --------
NODPMDL : Do not draw smoke model in DP engine
NODPFX : Do not draw DP smoke particle effect
NOQSMDL : Do not draw smoke model in QS/Fitz engines
STARTOFF : Always Starts off and waits for trigger
-------- NOTES --------
Smoke model, +DP only smoke effect (wait/delay/height DP only)
======================================================================*/
void() misc_smoke_model =
{
self.count = self.count + 1;
if (self.count > 59) self.count = 0;
self.frame = self.count;
self.think = misc_smoke_model;
self.nextthink = time + 0.1;
};
void() misc_smoke_on =
{
self.estate = ESTATE_ON;
// Switch on particle emitter if was setup
if (self.part_emitter) misc_particle_on(self.part_emitter);
// Restore model/size/skin
if (self.mdl != "") {
setmodel (self, self.mdl);
setsize (self, VEC_ORIGIN, VEC_ORIGIN);
self.skin = self.exactskin;
self.count = rint(random()*59);
misc_smoke_model();
}
};
//----------------------------------------------------------------------
void() misc_smoke_off =
{
self.estate = ESTATE_OFF;
// Turn off model if setup
if (self.mdl != "") {
self.modelindex = 0; // Make sure no model
self.model = ""; // Hide model
}
};
//----------------------------------------------------------------------
void() misc_smoke_setup =
{
// If target is setup, calculate new facing angle
if (self.target != "") {
if (!self.movetarget)
self.movetarget = find(world, targetname, self.target);
if (self.movetarget) {
// Check for a Bmodel object (special origin)
if (self.movetarget.bsporigin) self.dest1 = bmodel_origin(self.movetarget);
else self.dest1 = self.movetarget.origin;
// Calculate facing angle towards target
self.movedir = normalize(self.dest1 - self.origin);
self.angles = vectoangles(self.movedir);
self.angles_y = self.angles_y + 180;
// Update velocity direction for DP effect
if (self.part_emitter) self.part_emitter.dpp_vel = self.movedir*self.height;
}
}
// Setup Entity State functionality
if (self.targetname != "") self.use = entity_state_use;
self.estate_on = misc_smoke_on;
self.estate_off = misc_smoke_off;
if (self.spawnflags & (MISC_STARTOFF | ENT_STARTOFF)) entity_state_off();
else entity_state_on();
};
//----------------------------------------------------------------------
void() misc_smoke =
{
self.mdl = "progs/misc_smoke.mdl";
precache_model (self.mdl);
self.classtype = CT_MISCSMOKE;
self.classgroup = CG_MISCENT;
self.solid = SOLID_NOT; // No world interaction
self.movetype = MOVETYPE_NONE; // Static item, no movement
if (!self.exactskin) self.exactskin = 0; // Default = 0
if (self.wait < 0.1) self.wait = 0.1;
if (self.delay <= 0) self.delay = 0.1;
if (self.height <= 0) self.height = 0.5 + random()*0.5;
// If DP engine active remove particle shadow
if (engine == ENG_DPEXT) {
// Originally had ef_additive but produced sorting errors
self.effects = self.effects + EF_NOSHADOW;
}
else {
// Setup alpha for non DP engines
if (!self.alpha) self.alpha = 0.5+random()*0.25;
}
// Calculate smoke particle movedir from angles
makevectors(self.angles);
self.movedir = v_up;
// Setup some random Y axis rotation if nothing set
if (CheckZeroVector(self.angles)) self.angles_y = rint(random()*360);
// DP particle effects active?
if (ext_dppart) {
// Remove the model if spawnflag set
if (self.spawnflags & MISC_SMOKENODPMDL) self.mdl = "";
// Spawn particle emitter if particles active and not blocked
if (query_configflag(SVR_PARTICLES) && !(self.spawnflags & MISC_SMOKENODPFX) ) {
self.part_active = PARTICLE_STYLE_SMOKE;
if (self.spawnflags & (MISC_STARTOFF | ENT_STARTOFF))
self.part_emitter = spawn_pemitter(self, self, self.part_active, PARTICLE_START_OFF);
else self.part_emitter = spawn_pemitter(self, self, self.part_active, PARTICLE_START_ON);
}
}
else {
// Check for QS model exception
if (self.spawnflags & MISC_SMOKENOQSMDL) self.mdl = "";
}
// Setup target and delay starting model animation
self.nextthink = time + 0.1 + (rint(random()*10) * 0.1);
self.think = misc_smoke_setup;
};
/*======================================================================
spark effect (based on code from Rubicon2 by JohnFitz)
- Modified to have on/off/toggle state via triggers
- extended parameters for angle/speed/custom sounds
/*QUAKED misc_spark (.5 .75 .5) (-8 -8 -8) (8 8 8) x BLUE PALE RED x x STARTOFF x
Produces a burst of sparks at random intervals
-------- KEYS --------
targetname : toggle state (use trigger ent for exact state)
target : If target is a light, will be switched on/off in sync
wait : time delay between bursts Def=2, spark once=-1
cnt : number of sparks in burst (0.5 + random() x 0.5) Def=16
angle : direction of sparks to follow, use "360" for 0
fixangle: 1 = Random Y axis direction of sparks
speed : velocity speed of sparks (def=40)
height : random velocity modifier (def=+/-20)
sounds : 1=sparks, 4=silent, 5=custom
noise : custom sound for sparks
-------- SPAWNFLAGS --------
BLUE : sparks are blue in colour (def=yellow)
PALE : sparks are pale yellow in colour (def=yellow)
RED : sparks are red in colour (def=yellow)
STARTOFF : Always Starts off and waits for trigger
-------- NOTES --------
Produces a burst of sparks at random intervals
If targeting a light, it must start switched off (lights spawnflag=1)
======================================================================*/
void() misc_sparks_fade1 = [0, misc_sparks_fade2] {self.alpha = 0.8; self.nextthink = time + 0.05;};
void() misc_sparks_fade2 = [0, misc_sparks_fade3] {self.alpha = 0.6; self.nextthink = time + 0.05;};
void() misc_sparks_fade3 = [0, misc_sparks_fade4] {self.alpha = 0.4; self.nextthink = time + 0.05;};
void() misc_sparks_fade4 = [0, SUB_Remove] {self.alpha = 0.2; self.nextthink = time + 0.05;};
//----------------------------------------------------------------------
void() misc_spark_spawn;
void() misc_spark_switchoff =
{
// Always switch off trigger (lights)
SUB_UseTargets();
// Is the entity OFF or BLOCKED?
if (self.estate & ESTATE_BLOCK) return;
// Only spark once (wait for trigger)
if (self.wait > 0) {
self.think = misc_spark_spawn;
self.nextthink = time + self.wait + (random()*self.wait);
}
}
//----------------------------------------------------------------------
void() misc_spark_spawn =
{
local float loopvar;
local entity spark;
// Is the entity OFF or BLOCKED?
if (self.estate & ESTATE_BLOCK) return;
// Check for random rotation, most be set for whole batch
// otherwise the sparks will all go in different directions
if (self.fixangle > 0) {
self.angles = '0 0 0';
self.angles_y = rint(random()*360);
makevectors (self.angles);
self.movedir = v_forward;
}
// Work out how many sparks to spawn
loopvar = (0.5 + random()*0.5)*self.cnt;
while (loopvar > 0) {
spark = spawn();
spark.classtype = CT_TEMPSPARK;
spark.classgroup = CG_TEMPENT;
spark.owner = self;
spark.movetype = MOVETYPE_BOUNCE;
spark.solid = SOLID_TRIGGER;
setmodel (spark, self.mdl);
setorigin (spark, self.origin);
setsize (spark, VEC_ORIGIN, VEC_ORIGIN);
spark.gravity = 0.3;
spark.velocity = vecrand(0,self.height,TRUE);
spark.velocity = spark.velocity + (self.movedir * self.speed);
spark.avelocity = '300 300 300';
spark.nextthink = time + 0.5 + 1.5*random();
spark.think = misc_sparks_fade1;
// If DP engine active remove particle shadow
if (engine == ENG_DPEXT) spark.effects = spark.effects + EF_NOSHADOW;
// Some brightness variety
if (random() < 0.33) spark.skin = 0;
else if (random() < 0.5) spark.skin = 1;
else spark.skin = 2;
// Alternative colours (blue, pale yellow & red)
if (self.spawnflags & MISC_SPARKBLUE) spark.skin = spark.skin + 3;
else if (self.spawnflags & MISC_SPARKPALE) spark.skin = spark.skin + 6;
else if (self.spawnflags & MISC_SPARKRED) spark.skin = spark.skin + 9;
loopvar = loopvar - 1;
}
// Play any spark sound and switch ON any target lights
if (self.noise != "") sound (self, CHAN_VOICE, self.noise, self.volume, self.distance);
// Is there any target(s) to switch on
if (self.target) {
SUB_UseTargets();
// Setup timer to switch off
self.nextthink = time + 0.1 + random() * 0.2;
self.think = misc_spark_switchoff;
}
else {
// Only spark once (wait for trigger)
if (self.wait > 0) {
self.nextthink = time + 0.2 + self.wait + (random()*self.wait);
self.think = misc_spark_spawn;
}
}
};
//----------------------------------------------------------------------
void() misc_spark_on =
{
self.estate = ESTATE_ON;
self.nextthink = time + 0.1 + random();
self.think = misc_spark_spawn;
};
//----------------------------------------------------------------------
void() misc_spark =
{
self.mdl = "progs/misc_spark.mdl";
precache_model (self.mdl);
if (self.sounds == 1) self.noise = "misc/spark.wav";
if (self.noise != "") precache_sound (self.noise);
// Allow for volume/atten to be customized
if (self.distance < 0.1 || self.distance > ATTN_STATIC) self.distance = ATTN_STATIC;
if (self.volume < 0.1 || self.volume > 1) self.volume = 1;
self.classtype = CT_MISCSPARK;
self.classgroup = CG_MISCENT;
self.solid = SOLID_NOT; // No world interaction
self.movetype = MOVETYPE_NONE; // Static item, no movement
if (!self.wait) self.wait = 2; // -1 = spark once and turn off
if (!self.cnt) self.cnt = 16;
if (!self.speed) self.speed = 40;
if (!self.height) self.height = 20;
self.estate = ESTATE_OFF;
// Always convert 0 angle to 360 for setmovedir function
if (CheckZeroVector(self.angles)) self.angles = '0 360 0';
self.mangle = self.angles;
SetMovedir();
// Setup Entity State functionality
if (self.targetname != "") self.use = entity_state_use;
self.estate_on = misc_spark_on;
if (self.spawnflags & (MISC_STARTOFF | ENT_STARTOFF)) entity_state_off();
else entity_state_on();
};
/*======================================================================
Screen Shake (based on code from RRP by ijed/supa)
- Modified to have on/off/toggle state
- added extra sound options
/*QUAKED misc_shake (.5 .5 .9) (-16 -16 -8) (16 16 8) x VIEWONLY x x x x x x
Shake players view and/or velocity around center of entity
-------- KEYS --------
targetname : toggle state (use trigger ent for exact state)
count : radius of shake (def = 200)
wait : duration of shake (def = 2s)
dmg : strength at center (def = 200)
sounds : 1=loud rumble (no default)
noise1 : noise to play when starting to shake
noise2 : noise to play when stopping
-------- SPAWNFLAGS --------
VIEWONLY : Shakes the view, but player movement is not affected
-------- NOTES --------
Shake players view and/or velocity around center of entity.
Always starts off, requires triggers to activate
======================================================================*/
void() misc_shake_think =
{
local entity plyr;
local float d;
// Is the shaking over?
if (self.attack_finished < time || self.estate & ESTATE_BLOCK) {
if (self.noise2) sound (self, CHAN_VOICE, self.noise2, 1, ATTN_NORM);
// Check state before changing it, entity may be disabled
if (self.estate == ESTATE_ON) self.estate = ESTATE_OFF;
return;
}
// Create a list of entities to check for players
plyr = findradius(self.origin, self.count);
while(plyr) {
// Only shake players (clients)
if (plyr.flags & FL_CLIENT) {
// Scale effect by distance
d = vlen(self.origin - plyr.origin);
d = (self.count - d)/self.count;
if (d > 0) {
// shake up the view
plyr.punchangle_x = -1 * (random() + (0.025*self.dmg*d));
// push the player around
if (plyr.flags & FL_ONGROUND && !(self.spawnflags & MISC_SHAKEVIEWONLY)) {
d = self.dmg*d;
plyr.velocity_x = plyr.velocity_x + (random()*d*2 - d);
plyr.velocity_y = plyr.velocity_y + (random()*d*2 - d);
plyr.velocity_z = plyr.velocity_z + (random()*d);
}
}
}
// Find next entity in chain
plyr = plyr.chain;
}
// keep shaking!
self.nextthink = time + 0.1;
self.think = misc_shake_think;
};
//----------------------------------------------------------------------
void() misc_shake_on =
{
self.estate = ESTATE_ON;
// Play earthquake LOOP sound
if (self.noise1) sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM);
self.attack_finished = time + self.wait;
// keep checking for players to shake!
self.nextthink = time + 0.1;
self.think = misc_shake_think;
};
//----------------------------------------------------------------------
void() misc_shake =
{
if (self.sounds == 1) {
self.noise1 = "misc/rumbleloop.wav";
self.noise2 = "misc/rumbleoff.wav";
}
else {
if (self.noise1 == "") self.noise1 = SOUND_EMPTY;
if (self.noise2 == "") self.noise2 = SOUND_EMPTY;
}
precache_sound (self.noise1);
precache_sound (self.noise2);
self.classtype = CT_MISCSHAKE;
self.classgroup = CG_MISCENT;
if (!self.dmg) self.dmg = 120;
if (self.count <= 0) self.count = 200;
if (self.wait <= 0) self.wait = 2;
self.attack_finished = 0;
// Setup Entity State functionality
if (self.targetname != "") self.use = entity_state_use;
self.estate_on = misc_shake_on;
self.estate = ESTATE_OFF;
};
/*======================================================================
/*QUAKED misc_model (1 .5 .25) (-16 -16 -16) (16 16 16) x COLLISION MOVEMENT x x STATIC STARTOFF x Not_Easy Not_Normal Not_Hard Not_DM
MDL files that can be setup with specific frame/skin and animate groups
-------- KEYS --------
mdl : specify model to load, full path (progs/candle.mdl)
targetname : toggle state (use trigger ent for exact state)
angle : facing angle (-1 = random position)
angles : 'pitch yaw roll' up/down, angle, tilt left/right
ideal_yaw : = 1 Setup model with random Y axis rotation
pos1 : used for selection of frame(s) has several setups
X=0, Y=0, Z=exact frame number
X->Y, Z=0 Randomly pick a frame from the X,Y range
X->Y, Z=-1 Animate between the X,Y range, can forward or backward setup
pos2 : used for the selection of skin(s) has several setups
X=0, Y=0, Z=exact skin number
X->Y, Z=0 Randomly pick a skin from the X,Y range
-------- SPAWNFLAGS --------
COLLISION : model bbox collision enabled
MOVEMENT : model can be moved around like an item
STATIC : Turn entity into static upon spawn (frame 0)
STARTOFF : Always Starts off and waits for trigger
-------- NOTES --------
MDL files that can be setup with specific frame/skin and animate groups
======================================================================*/
void() misc_model_loop =
{
if (self.estate & ESTATE_BLOCK) return;
self.height = self.height + self.lip;
if (self.lip > 0 && self.height > self.pos1_y) self.height = self.pos1_x;
else if (self.lip < 0 && self.height < self.pos1_y) self.height = self.pos1_x;
self.frame = self.height;
self.think = misc_model_loop;
self.nextthink = time + self.speed;
};
//----------------------------------------------------------------------
void() misc_model_on =
{
// Check if there is any spawn/on delay setup
// Useful for delay spawning models on to platform/doors
if (self.delay > 0) {
// Feed back into this function
self.think = misc_model_on;
self.nextthink = time + self.delay;
// The delay only works once
self.delay = 0;
return;
}
self.estate = ESTATE_ON;
setmodel (self, self.mdl);
// Restore model/size and check if collision needed
if (self.spawnflags & MISC_COLLISION) {
self.solid = SOLID_BBOX;
setsize (self, self.mins , self.maxs);
}
else setsize (self, VEC_ORIGIN, VEC_ORIGIN);
setorigin(self, self.oldorigin + self.view_ofs);
if (self.spawnflags & MISC_MOVEMENT) {
// Turn misc model into an item (can work with plats/doors)
self.movetype = MOVETYPE_TOSS;
self.solid = SOLID_TRIGGER;
setsize (self, VEC_ORIGIN, VEC_ORIGIN);
self.velocity = '0 0 0';
// Make sure the misc model starts on the ground
self.origin_z = self.origin_z + 6;
if (!droptofloor()) {
dprint ("\n\b[Model]\b "); dprint (self.mdl);
dprint (" stuck at ("); dprint (vtos(self.origin)); dprint (")\n");
spawn_marker(self.origin, SPNMARK_YELLOW);
remove(self);
return;
}
// Make sure no touch function is active
self.touch = SUB_Null;
}
// Setup skin number (range or exact)
self.skin = self.pos2_z;
// Exact frame number
self.frame = self.pos1_z;
// Check for static entity option first
if (self.spawnflags & ENT_SPNSTATIC) makestatic(self);
else {
// Check for manual animation loops
if (self.pos1_z == -1) {
self.height = self.pos1_x;
if (self.pos1_x < self.pos1_y) self.lip = 1;
else self.lip = -1;
// Manually animate model
misc_model_loop();
}
}
};
//----------------------------------------------------------------------
void() misc_model_off =
{
// Turn off model/world interaction
self.estate = ESTATE_OFF;
self.model = string_null; // hide bmodel surface
self.movetype = MOVETYPE_NONE; // Create baseline
self.solid = SOLID_NOT;
self.nextthink = 0;
};
//----------------------------------------------------------------------
void() misc_model =
{
// Is the model defined using the noise key?
if (!self.mdl) {
dprint("\b[MISCMDL]\b Missing model to load\n");
spawn_marker(self.origin, SPNMARK_YELLOW);
remove(self);
return;
}
precache_model (self.mdl);
self.classtype = CT_MISCMODEL;
self.classgroup = CG_MISCENT;
self.solid = SOLID_NOT; // No world interaction
self.movetype = MOVETYPE_NONE; // Static item, no movement
self.oldorigin = self.origin; // Store for later
// Random Rotation : UP/DOWN angle or use the ideal_yaw key
if (self.angles_y < 0 || self.ideal_yaw > 0)
self.angles_y = rint(random()*360);
self.mangle = self.angles; // Save for later
if (!self.speed) self.speed = 0.1; // Manual animation tick speed
// Has a frame range been defined?
if (self.pos1_x != self.pos1_y) {
// Make sure the range is the right way around
// X has to be the lowest number of the two (X/Y)
if (self.pos1_x > self.pos1_y) {
self.frame_box = self.pos1_x;
self.pos1_x = self.pos1_y;
self.pos1_y = self.frame_box;
}
// Randomly pick frame number from a range?
// Work out random different and add to X base
if (self.pos1_z == 0) {
// Double check lower limit is not negative
if (self.pos1_x < 0) self.pos1_x = 0;
// Work out random range first and then add to base
self.frame_box = fabs(self.pos1_y - self.pos1_x);
self.pos1_z = self.pos1_x + rint(random()*self.frame_box);
// Double check the frame is within the specified range
if (self.pos1_z < self.pos1_x) self.pos1_z = self.pos1_x;
if (self.pos1_z > self.pos1_y) self.pos1_z = self.pos1_y;
}
// Manual frame animation required
else self.pos1_z = -1;
}
else {
// If no exact frame bas specified, reset frame to default = 0
if (self.pos1_z < 1) self.pos1 = '0 0 0';
}
// Has a skin range been defined?
if (self.pos2_x != self.pos2_y) {
self.pos2_z = rint( random() * fabs(self.pos2_y - self.pos2_x) );
}
else {
// If no exact frame bas specified, reset frame to default = 0
if (self.pos2_z < 1) self.pos2 = '0 0 0';
}
// Cannot have static and movement at the same time!
if (self.spawnflags & MISC_MOVEMENT) {
self.spawnflags = self.spawnflags - (self.spawnflags & ENT_SPNSTATIC);
}
// Check for static entity option first
if (self.spawnflags & ENT_SPNSTATIC) misc_model_on();
else {
// Setup Entity State functionality
if (self.targetname != "") self.use = entity_state_use;
self.estate_on = misc_model_on;
self.estate_off = misc_model_off;
if (self.spawnflags & (MISC_STARTOFF | ENT_STARTOFF)) entity_state_off();
else entity_state_on();
}
};
//----------------------------------------------------------------------
// Allows GTK editors to work with Q1 assets easier
void() misc_gtkmodel = { misc_model(); };
//======================================================================
// All dead bodies use the same on/off states
//======================================================================
void() misc_deadbody_on =
{
if (self.gibbed == TRUE) return;
self.estate = ESTATE_ON; // Show entity
self.solid = SOLID_NOT; // No world interaction
self.movetype = MOVETYPE_NONE; // Static item, no movement
setmodel(self,self.mdl); // Show model
setsize (self, self.bbmins, self.bbmaxs);
self.bodyonflr = MON_ONFLR; // Let Shadow Axe interact
};
//----------------------------------------------------------------------
void() misc_deadbody_off =
{
if (self.gibbed == TRUE) return;
self.estate = ESTATE_OFF; // Hide entity
self.solid = SOLID_NOT; // No world interaction
self.movetype = MOVETYPE_NONE; // Static item, no movement
setmodel(self,""); // Show model
setsize (self, VEC_ORIGIN, VEC_ORIGIN);
self.bodyonflr = ""; // No Shadows Axe interaction
};
//----------------------------------------------------------------------
void() misc_deadbody_setup =
{
self.classtype = CT_MISCMODEL;
self.classgroup = CG_MISCENT;
self.oldorigin = self.origin; // Store for later
self.takedamage = DAMAGE_NO; // No projectile interaction
self.deadflag = DEAD_DEAD; // Body is really dead!
self.health = self.max_health = -1;
self.blockudeath = TRUE; // Body is dead, no human death noise
self.gibbed = FALSE; // Still in one piece!
// Random Rotation : UP/DOWN angle or use the ideal_yaw key
if (self.angles_y < 0 || self.ideal_yaw > 0)
self.angles_y = rint(random()*360);
// Setup Entity State functionality
if (self.targetname != "") self.use = entity_state_use;
self.estate_on = misc_deadbody_on;
self.estate_off = misc_deadbody_off;
if (self.spawnflags & ENT_STARTOFF) entity_state_off();
else entity_state_on();
};
/*======================================================================
/*QUAKED misc_player (1 .5 .25) (-16 -16 -24) (16 16 32) BACK DOWN1 WALL DOWN2 DOWN3 SIDE STARTOFF x
Dead Player MDL for poses
-------- KEYS --------
targetname : toggle state (use trigger ent for exact state)
angle : facing angle (-1 = random position)
ideal_yaw : = 1 Setup model with random Y axis rotation
fixangle : 1 = Use misc_player.mdl instead (less hassle)
frame : body pose (49=on back, 67-69=against wall, 60/84/93=face down, 102=on side)
exactskin : -1= Random, 0-1 Original, 2-3 Green, 4-5 Yellow, 6-7 Red
-------- SPAWNFLAGS --------
BACK : 49=On back
DOWN1 : 60=Face Down
WALL : 69=Against wall
DOWN2 : 84=Face Down
DOWN3 : 93=Face Down
SIDE : 102=On Side
STARTOFF : Always Starts off and waits for trigger
-------- NOTES --------
Dead Player MDL for poses
*/
/*======================================================================
/*QUAKED misc_demon (1 .5 .25) (-32 -32 -24) (32 32 64) x x x x x x STARTOFF x
Dead demon/fiend for poses
-------- KEYS --------
targetname : toggle state (use trigger ent for exact state)
angle : facing angle (-1 = random position)
ideal_yaw : = 1 Setup model with random Y axis rotation
frame : body pose (53=on back - def)
exactskin : -1= Random, 0= Original, 1= Green
-------- SPAWNFLAGS --------
STARTOFF : Always Starts off and waits for trigger
-------- NOTES --------
Dead demon/fiend for poses
*/
/*======================================================================
/*QUAKED misc_dknight (0.75 0.25 0) (-16 -16 -24) (16 16 40) x x x x x x STARTOFF x
Dead Death Knight for poses
-------- KEYS --------
targetname : toggle state (use trigger ent for exact state)
angle : facing angle (-1 = random position)
ideal_yaw : = 1 Setup model with random Y axis rotation
frame : body pose (223=on front, 237-243=on back - def)
-------- SPAWNFLAGS --------
STARTOFF : Always Starts off and waits for trigger
-------- NOTES --------
Dead Death Knight for poses
*/
//----------------------------------------------------------------------
void() misc_player =
{
self.mdl = "progs/player.mdl"; // Standard player model
self.headmdl = "progs/h_soldier.mdl"; // Ugly monster mug
self.gib1mdl = "progs/gib_soldfoot1.mdl"; // Upright foot
self.gib2mdl = "progs/gib_soldfoot2.mdl"; // Fallen down foot
// Special player model?
if (self.fixangle) {
self.mdl = "progs/misc_player.mdl"; // Custom model
self.gib3mdl = "progs/gib_5.mdl"; // Gib for precache
// using special model, simple frame setup
if (self.spawnflags & 2) self.frame = 1;
else if (self.spawnflags & 4) self.frame = 2;
else if (self.spawnflags & 8) self.frame = 3;
else if (self.spawnflags & 16) self.frame = 4;
else if (self.spawnflags & 32) self.frame = 5;
// Must always reset frame
else self.frame = 0;
}
// Normal player model
else {
self.mdl = "progs/player.mdl"; // Standard player model
self.gib3mdl = "progs/w_soldiergun.mdl";// Unique weapon
self.gib3sound = GIB_IMPACT_WOOD;
// Using original model, skip to correct frames
if (self.spawnflags & 1) self.frame = 49;
else if (self.spawnflags & 2) self.frame = 60;
else if (self.spawnflags & 4) self.frame = 69;
else if (self.spawnflags & 8) self.frame = 84;
else if (self.spawnflags & 16) self.frame = 93;
else if (self.spawnflags & 32) self.frame = 102;
}
// Make sure stuff is cached
precache_model (self.mdl);
precache_model (self.headmdl);
precache_model (self.gib1mdl);
precache_model (self.gib2mdl);
precache_model (self.gib3mdl);
// uses short red knight bounding box
if (self.bboxtype < 1) self.bboxtype = BBOX_SHORT;
monster_bbox();
// Setup random/exact skin choices
if (self.exactskin < 0) self.exactskin = rint(0.5 + random()*7);
if (self.exactskin > 7) self.exactskin = 7;
self.skin = self.exactskin;
misc_deadbody_setup();
};
//----------------------------------------------------------------------
void() misc_demon =
{
self.mdl = "progs/mon_demon.mdl";
self.headmdl = "progs/h_demon.mdl";
self.gib1mdl = "progs/gib_dmleg1.mdl"; // Left leg
self.gib2mdl = "progs/gib_dmleg2.mdl"; // Right leg
self.gib3mdl = "progs/gib_dmtail.mdl"; // Tail
self.gib4mdl = "progs/gib_dmclaw1.mdl"; // Claw 1
self.gib5mdl = "progs/gib_dmclaw2.mdl"; // Claw 2
precache_model (self.mdl);
precache_model (self.headmdl);
precache_model (self.gib1mdl);
precache_model (self.gib2mdl);
precache_model (self.gib3mdl);
precache_model (self.gib4mdl); // Always precache extra models
precache_model (self.gib5mdl); // regardless if picked or not
// Randomly swap in demon claws instead of legs
if (random() < 0.5) self.gib1mdl = self.gib4mdl;
if (random() < 0.5) self.gib2mdl = self.gib5mdl;
self.gib4mdl = ""; self.gib5mdl = "";
// Setup bounding box based on presets
if (self.bboxtype < 1) self.bboxtype = BBOX_WIDE;
monster_bbox();
// Default pose for dead demons
if (self.frame < 0) self.frame = 0;
if (self.frame == 0) self.frame = 53;
// Setup random/exact skin choices
if (self.exactskin < 0) {
if (random() < 0.5) self.exactskin = 0;
else self.exactskin = 1;
}
if (self.exactskin > 1) self.exactskin = 1;
self.skin = self.exactskin;
// Sync gib model skin to demon skin
self.gib1skin = self.gib2skin = self.gib3skin = self.exactskin;
misc_deadbody_setup();
};
//----------------------------------------------------------------------
void() misc_dknight =
{
self.mdl = "progs/mon_dknight.mdl"; // New Hell Knight
self.headmdl = "progs/h_dknight.mdl";
self.gib1mdl = "progs/w_dknightsword.mdl"; // Unique sword
self.gib2mdl = "progs/gib_knfoot_l.mdl"; // left foot
self.gib3mdl = "progs/gib_knfoot_r.mdl"; // right foot
precache_model (self.mdl);
precache_model (self.headmdl);
precache_model (self.gib2mdl);
precache_model (self.gib3mdl);
precache_model (self.gib1mdl);
self.gib1sound = GIB_IMPACT_METALA;
if (random() < 0.5) self.gib2mdl = string_null;
if (random() < 0.5) self.gib3mdl = string_null;
// Setup bounding box based on presets
if (self.bboxtype < 1) self.bboxtype = BBOX_TALL;
monster_bbox();
// Default pose for dead death knights
if (self.frame < 0) self.frame = 0;
if (self.frame == 0) self.frame = 243;
if (self.exactskin > 0) self.skin = self.exactskin;
misc_deadbody_setup();
};
/*======================================================================
/*QUAKED misc_builtineffect (0 .5 .8) (-8 -8 -8) (8 8 8) x
Spawns a builtin particle effect, will toggle if active
-------- KEYS --------
targetname : toggle state (use trigger ent for exact state)
target : destination of effect (self -> target)
wait : time between firing of effect (def=0)
delay : random time between firing of effect (def=0s)
sounds : = 4 silent (no default additional sounds)
count : type of effect to fire
0=TE_SPIKE (def), 1=TE_SUPERSPIKE, 2=TE_GUNSHOT,
3=TE_EXPLOSION (sprites), 4=TE_TAREXPLOSION (purple ver)
5=TE_LIGHTNING1 (Shambler ver), 6=TE_LIGHTNING2 (Player ver)
7=TE_WIZSPIKE, 8=TE_KNIGHTSPIKE, 9=TE_LIGHTNING3 (boss ver)
10=TE_LAVASPLASH (boss wakeup), 11=TE_TELEPORT (sparkles)
-------- SPAWNFLAGS --------
-------- NOTES --------
Spawns a builtin particle effect, will toggle if active
Always starts off, requires triggers to activate
======================================================================*/
void() misc_builtineffects_fire =
{
// Check if disabled/off first
if (self.estate & ESTATE_BLOCK) self.state = STATE_OFF;
if (self.state == STATE_OFF) return;
// Play builtin effects at target/self location
WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
WriteByte (MSG_BROADCAST, self.count);
self.pos2 = self.origin;
// If lightning effect (self -> target)
if (self.count == TE_LIGHTNING1 || self.count == TE_LIGHTNING2 ||
self.count == TE_LIGHTNING3) {
// Check if target is a Bmodel? (different origin location)
if (self.movetarget.bsporigin) self.pos1 = bmodel_origin(self.movetarget);
else self.pos1 = self.movetarget.origin;
// The lightning model is made from 30 unit sections stitched together
// reduce the vector length down to 30 unit chunks so it does not
// poke through the destination object
self.pos2 = self.pos1 - self.origin;
self.t_width = vlen(self.pos2);
if (self.t_width > 30) self.t_length = floor(self.t_width / 30) * 30;
else self.t_length = self.t_width;
self.pos2 = normalize(self.pos2);
self.pos2 = self.origin + (self.pos2 * self.t_length);
WriteEntity (MSG_BROADCAST, self);
WriteCoord (MSG_BROADCAST, self.origin_x);
WriteCoord (MSG_BROADCAST, self.origin_y);
WriteCoord (MSG_BROADCAST, self.origin_z);
}
WriteCoord (MSG_BROADCAST, self.pos2_x);
WriteCoord (MSG_BROADCAST, self.pos2_y);
WriteCoord (MSG_BROADCAST, self.pos2_z);
// Using the standard for triggers (4 == no extra sound)
if (self.sounds != 4) {
// The effect sounds have to come after the write commands
// otherwise the engine will get confused and think the protocol
// has changed, there is an exact format for effects
if (self.count == TE_TELEPORT) {
self.lip = rint(random()*5);
if (self.lip == 0) sound (self.movetarget, CHAN_VOICE, "misc/r_tele1.wav", 1, ATTN_NORM);
else if (self.lip == 1) sound (self.movetarget, CHAN_VOICE, "misc/r_tele2.wav", 1, ATTN_NORM);
else if (self.lip == 2) sound (self.movetarget, CHAN_VOICE, "misc/r_tele3.wav", 1, ATTN_NORM);
else if (self.lip == 3) sound (self.movetarget, CHAN_VOICE, "misc/r_tele4.wav", 1, ATTN_NORM);
else sound (self.movetarget, CHAN_VOICE, "misc/r_tele5.wav", 1, ATTN_NORM);
}
else if (self.count == TE_LIGHTNING1 || self.count == TE_LIGHTNING2 ||
self.count == TE_LIGHTNING3) {
// Play lightning sound (LG weapon hit)
// Stop the sound constantly playing
if (self.waitmin < time) {
sound (self.movetarget, CHAN_WEAPON, "weapons/lhit.wav", 1, ATTN_NORM);
self.waitmin = time + 0.6;
}
}
else if (self.count == TE_LAVASPLASH)
sound (self.movetarget, CHAN_BODY, "boss1/out1.wav", 1, ATTN_NORM);
else if (self.count == TE_EXPLOSION || self.count == TE_TAREXPLOSION)
// Play original explosion sound
sound(self.movetarget, CHAN_WEAPON, SOUND_REXP3, 1, ATTN_NORM);
}
// Continuous mode?
if (self.wait > 0) {
self.think = misc_builtineffects_fire;
self.nextthink = time + self.wait + random()*self.delay;
}
// Fire once and switch off
else self.state = STATE_OFF;
};
//----------------------------------------------------------------------
void() misc_builtineffects_use =
{
// Check if disabled/off first
if (self.estate & ESTATE_BLOCK) return;
// Toggle shooter on/off
if (self.state == STATE_OFF) self.state = STATE_ON;
else self.state = STATE_OFF;
misc_builtineffects_fire();
};
//----------------------------------------------------------------------
void() misc_builtineffects_reset =
{
self.state = STATE_OFF;
};
//----------------------------------------------------------------------
void() misc_builtineffects_setup =
{
// Find any target destinations
if (self.target != "") self.movetarget = find(world, targetname, self.target);
else self.movetarget = self;
// Lightning effects need source and target to work
// Check target is valid before trying to setup this effect
if (self.count == TE_LIGHTNING1 || self.count == TE_LIGHTNING2 ||
self.count == TE_LIGHTNING3 && !self.movetarget) {
dprint("\b[MISC_EFFECTS]\b Missing target for lightning\n");
spawn_marker(self.origin, SPNMARK_YELLOW);
remove(self);
}
// Setup Entity State functionality
if (self.targetname != "") self.use = entity_state_use;
self.estate_use = misc_builtineffects_use;
self.estate_reset = misc_builtineffects_reset;
self.estate = ESTATE_ON;
self.state = STATE_OFF;
};
//----------------------------------------------------------------------
void() misc_builtineffects =
{
// Precache sounds for effects
if (self.count == TE_LAVASPLASH) precache_sound ("boss1/out1.wav");
self.classtype = CT_PARTICLEEMIT;
if (self.wait < 0) self.wait = 0;
if (self.delay < 0) self.delay = 0;
if (!self.count) self.count = 0; // def=TE_SPIKE
self.think = misc_builtineffects_setup;
self.nextthink = time + 0.1 + random();
};