/*====================================================================== TRAP Sawblade * Originally based on code from Rogue Software * Extra code support from lunaran ======================================================================*/ float SAW_STARTON = 1; float SAW_XAXIS = 16; float SAW_REVERSE = 32; /*====================================================================== /*QUAKED trap_sawbladex (0 .5 .8) (-18 -18 -18) (18 18 18) STARTON x x x x REVERSE STARTOFF x Rotating Saw Blade -------- KEYS -------- targetname : toggle state (use trigger ent for exact state) target : Name of first path_corner to start at (instant move) speed : Speed to follow path, def=100 yaw_speed : Rotation speed for Saw Blade (def=180) dmg : Damage to do for each contact, def=4 waitmin : Damage throttle for ON touch function, def=0.1s height : Damage throttle for OFF touch function, def=1s lip : Deceleration time; def=2s, =-1 instant stop sounds : 0=Silent, 1=Woodmill, 5=Custom sounds noise : custom sound - stopped noise1 : custom sound - moving (looping) -------- SPAWNFLAGS -------- STARTON : Start moving straight away if targetname is used REVERSE : Start going backward through path_corner chain STARTOFF : Starts off and waits for trigger -------- NOTES -------- Rotating Saw Blade ======================================================================*/ void() trap_sawblade_touch = { // Blade can still hurt if touched, except when OFF = not visible if (self.estate & ESTATE_OFF) return; // Double check if dead monster body! if (trigger_check_body(other,DEAD_EXPLODE)) return; if (other.health < 1 || other.takedamage == DAMAGE_NO) return; if (self.attack_finished > time) return; // --- lunaran ------------------- // Different touch damage based on blade state if (self.state == STATE_ON) { if (self.waitmin > 0) self.attack_finished = time + self.waitmin; // Hit sounded added by Lunaran, added sound string check if (self.noise2 !="") sound (self, CHAN_AUTO, self.noise2, 1, 1); } else { if (self.height > 0) self.attack_finished = time + self.height; } // Instant death for monsters if (other.flags & FL_MONSTER) T_Damage (other, self, self, other.max_health + 100, DAMARMOR); else // Small damage but can be lethal because of how often touch runs T_Damage (other, self, self, self.dmg, DAMARMOR); // Blood and gore at object location not sawblade SpawnMeatSpray ( other, other, crandom() * 200); }; //---------------------------------------------------------------------- // New slowdown/stop function by lunaran //---------------------------------------------------------------------- void() trap_sawblade_stopthink = { float remaining; // If slow down time expired, make sure blade has stopped if (self.pain_finished <= time) { self.velocity = self.avelocity = '0 0 0'; return; } remaining = (self.pain_finished - time) / self.lip; //---------------------------------------------------------------------- // quit moving if we've hit the next pathcorner // it'd be cooler to continue on the track as we decelerate but this way // we don't have to muck about in the func_train code //---------------------------------------------------------------------- if (self.rad_time && self.rad_time <= time) { self.velocity = '0 0 0'; setorigin(self, self.finaldest); self.rad_time = 0; } // ramp down from original speed and spinrate over self.lip seconds if (CheckZeroVector(self.velocity) == FALSE) self.velocity = normalize(self.velocity) * self.speed * remaining; self.avelocity = normalize(self.avelocity) * self.yaw_speed * remaining; // Double tick time to smooth out slowdown self.nextthink = time + 0.05; } //---------------------------------------------------------------------- // New slowdown/stop function by lunaran //---------------------------------------------------------------------- void() trap_sawblade_stop = { float d, timeToPathCorner; // Play wind down stop sound sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); // save the time we become completely still in pain_finished self.pain_finished = time + self.lip; // calculate when we'll hit the next pathcorner (if ever) // so we can stop there timeToPathCorner = vlen(self.origin-self.finaldest) / self.speed; // linear deceleration = stopping distance of exactly 1/2 speed*stoptime if (self.lip * 0.5 > timeToPathCorner ) { // linear deceleration = exponential time taken relative to distance d = mathlib_sqrt( 1 - ( 2 * timeToPathCorner / self.lip ) ); d = self.lip * ( 1 - d ); // save the time in rad_time self.rad_time = time + d; } self.think = trap_sawblade_stopthink; self.think(); }; //---------------------------------------------------------------------- void() trap_sawblade_use = { // Block entity state OFF + DISABLE if (self.estate & ESTATE_BLOCK) return; // Toggle function if (self.state == STATE_OFF) { self.state = STATE_ON; // use special train resume function func_train_resume(); } else { self.state = STATE_OFF; trap_sawblade_stop(); } }; //---------------------------------------------------------------------- void() trap_sawblade_setup = { // Deal with START OFF functionality first if (self.spawnflags & ENT_STARTOFF) { // Remove start off flag and switch on entity self.spawnflags = self.spawnflags - ENT_STARTOFF; } self.estate_on(); // Switch on entity func_train_startcorner(); // Move to first corner self.estate_use = trap_sawblade_use;// Switch ON sawblade self.estate_fire = func_train_next; // Move to next path corner self.state = STATE_OFF; // sawblade is OFF, waiting // Sharp pointy edges! self.touch = trap_sawblade_touch; // Did the first corner (owner) exist? if (self.owner && self.spawnflags & SAW_STARTON) { self.nextthink = time + 0.1; self.think = self.estate_use; } }; //---------------------------------------------------------------------- void() trap_sawblade_on = { // Stop re-triggering ON state if (self.estate == ESTATE_ON) return; // Turn on model/world interaction self.estate = ESTATE_ON; // Switch on entity self.solid = SOLID_TRIGGER; // Keep on cutting! self.movetype = MOVETYPE_NOCLIP; // Free movement setmodel (self, self.mdl); // Show bmodel/model setsize (self, self.bbmins , self.bbmaxs); // Use pre-defined size // Stop any current sounds, new sound will play in func_train function sound (self, CHAN_VOICE, SOUND_EMPTY, 1, ATTN_NORM); self.velocity = self.avelocity = '0 0 0'; // reset velocity/rotation }; //---------------------------------------------------------------------- void() trap_sawbladey = { // 0 = No sound, 1 = buzzzzz, 5 = custom if (self.sounds > 0) { if (self.sounds == 1) { self.noise = ("traps/sawblade_off.wav"); self.noise1 = ("traps/sawblade_on.wav"); self.noise2 = ("traps/sawstrike1.wav"); // lunaran } else if (self.sounds == 5) { if (self.noise == "") self.noise = SOUND_EMPTY; if (self.noise1 == "") self.noise1 = SOUND_EMPTY; if (self.noise2 == "") self.noise2 = SOUND_EMPTY; // lunaran } precache_sound (self.noise); precache_sound (self.noise1); precache_sound (self.noise2); // lunaran } self.mdl = "progs/trap_sawblade.mdl"; precache_model (self.mdl); self.classtype = CT_SAWBLADE; self.state = STATE_OFF; self.volume = 0.5; // Error - Check for first path corner target if (self.target == "") { dprint("\b[SAWBLADE]\b missing path corner target!\n"); } // Setup orientiation and bounding box parameters // required for state on and touch function if (self.spawnflags & SAW_XAXIS) { self.angles = '0 0 0'; self.bbmins = '-20 -2 -20'; self.bbmaxs = '20 2 20'; } else { self.angles = '0 90 0'; self.bbmins = '-2 -20 -20'; self.bbmaxs = '2 20 20'; } if (!self.dmg) self.dmg = 4; // Default touch damage if (!self.waitmin) self.waitmin = 0.1; // Damage throttle ON state if (self.speed < 1) self.speed = 100; // Default path speed self.wait = self.delay = 0; // reset wait and delay // --- lunaran ---------------------- if (!self.height) self.height = 1; // Damage throttle OFF state if (!self.lip) self.lip = 2; // Deceleration time, -1 for none if (self.lip < 0) self.lip = 0; // Convert -1 switch to 0 time // Setup default rotation speed if (!self.yaw_speed) self.yaw_speed = 180; self.v_angle = '0 0 0'; self.v_angle_x = self.yaw_speed; // Save spawn location self.dest0 = self.finaldest = self.origin; if (self.target == "") self.owner = self; self.bsporigin = FALSE; // Cannot have start ON and OFF together, remove OFF state! if (self.spawnflags & SAW_STARTON && self.spawnflags & ENT_STARTOFF) self.spawnflags = self.spawnflags - ENT_STARTOFF; // Setup train direction (switchable via path corners) if (self.spawnflags & SAW_REVERSE) self.direction = 1; else self.direction = 0; // Setup Entity State functionality if (self.targetname != "") self.use = entity_state_use; self.estate_on = trap_sawblade_on; self.estate_off = func_train_off; self.estate_use = trap_sawblade_setup; self.estate_reset = func_train_reset; self.estate_disable = func_train_disable; if (self.spawnflags & ENT_STARTOFF) self.estate_off(); else { self.nextthink = time + 0.1; self.think = self.estate_use; } }; //---------------------------------------------------------------------- void() trap_sawbladex = { self.spawnflags = self.spawnflags | SAW_XAXIS; trap_sawbladey(); };