/*====================================================================== 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(); };