2 void generic_plat_blocked()
4 if(self.dmg && other.takedamage != DAMAGE_NO) {
5 if(self.dmgtime2 < time) {
6 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
7 self.dmgtime2 = time + self.dmgtime;
10 // Gib dead/dying stuff
11 if(other.deadflag != DEAD_NO)
12 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
18 float STATE_BOTTOM = 1;
22 .entity trigger_field;
24 void() plat_center_touch;
25 void() plat_outside_touch;
26 void() plat_trigger_use;
30 float PLAT_LOW_TRIGGER = 1;
32 void plat_spawn_inside_trigger()
35 local vector tmin, tmax;
38 trigger.touch = plat_center_touch;
39 trigger.movetype = MOVETYPE_NONE;
40 trigger.solid = SOLID_TRIGGER;
43 tmin = self.absmin + '25 25 0';
44 tmax = self.absmax - '25 25 -8';
45 tmin_z = tmax_z - (self.pos1_z - self.pos2_z + 8);
46 if (self.spawnflags & PLAT_LOW_TRIGGER)
49 if (self.size_x <= 50)
51 tmin_x = (self.mins_x + self.maxs_x) / 2;
54 if (self.size_y <= 50)
56 tmin_y = (self.mins_y + self.maxs_y) / 2;
60 setsize (trigger, tmin, tmax);
65 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
67 self.think = plat_go_down;
68 self.nextthink = self.ltime + 3;
71 void plat_hit_bottom()
73 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
79 sound (self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
81 SUB_CalcMove (self.pos2, self.speed, plat_hit_bottom);
86 sound (self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
88 SUB_CalcMove (self.pos1, self.speed, plat_hit_top);
91 void plat_center_touch()
93 if not(other.iscreature)
96 if (other.health <= 0)
102 else if (self.state == 1)
103 self.nextthink = self.ltime + 1; // delay going down
106 void plat_outside_touch()
108 if not(other.iscreature)
111 if (other.health <= 0)
119 void plat_trigger_use()
122 return; // already activated
129 if((self.spawnflags & 4) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
130 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
132 if((self.dmg) && (other.takedamage != DAMAGE_NO)) { // Shall we bite?
133 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
134 // Gib dead/dying stuff
135 if(other.deadflag != DEAD_NO)
136 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
141 else if (self.state == 3)
144 objerror ("plat_crush: bad self.state\n");
152 objerror ("plat_use: not in up state");
156 .string sound1, sound2;
162 setorigin (self, self.pos1);
168 setorigin (self, self.pos2);
170 self.use = plat_trigger_use;
174 void spawnfunc_path_corner() { };
175 void spawnfunc_func_plat()
182 if (self.sounds == 0)
185 if(self.spawnflags & 4)
188 if(self.dmg && (!self.message))
189 self.message = "was squished";
190 if(self.dmg && (!self.message2))
191 self.message2 = "was squished by";
193 if (self.sounds == 1)
195 precache_sound ("plats/plat1.wav");
196 precache_sound ("plats/plat2.wav");
197 self.noise = "plats/plat1.wav";
198 self.noise1 = "plats/plat2.wav";
201 if (self.sounds == 2)
203 precache_sound ("plats/medplat1.wav");
204 precache_sound ("plats/medplat2.wav");
205 self.noise = "plats/medplat1.wav";
206 self.noise1 = "plats/medplat2.wav";
211 precache_sound (self.sound1);
212 self.noise = self.sound1;
216 precache_sound (self.sound2);
217 self.noise1 = self.sound2;
220 self.mangle = self.angles;
221 self.angles = '0 0 0';
223 self.classname = "plat";
224 if not(InitMovingBrushTrigger())
226 self.effects |= EF_LOWPRECISION;
227 setsize (self, self.mins , self.maxs);
229 self.blocked = plat_crush;
234 self.pos1 = self.origin;
235 self.pos2 = self.origin;
236 self.pos2_z = self.origin_z - self.size_z + 8;
238 plat_spawn_inside_trigger (); // the "start moving" trigger
240 self.reset = plat_reset;
248 self.think = train_next;
249 self.nextthink = self.ltime + self.wait;
252 stopsoundto(MSG_BROADCAST, self, CHAN_TRIGGER); // send this as unreliable only, as the train will resume operation shortly anyway
258 targ = find(world, targetname, self.target);
259 self.target = targ.target;
261 objerror("train_next: no next target");
262 self.wait = targ.wait;
268 SUB_CalcMove(targ.origin - self.mins, targ.speed, train_next);
270 SUB_CalcMove(targ.origin - self.mins, self.speed, train_next);
275 SUB_CalcMove(targ.origin - self.mins, targ.speed, train_wait);
277 SUB_CalcMove(targ.origin - self.mins, self.speed, train_wait);
281 sound(self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
284 void func_train_find()
287 targ = find(world, targetname, self.target);
288 self.target = targ.target;
290 objerror("func_train_find: no next target");
291 setorigin(self, targ.origin - self.mins);
292 self.nextthink = self.ltime + 1;
293 self.think = train_next;
296 /*QUAKED spawnfunc_func_train (0 .5 .8) ?
297 Ridable platform, targets spawnfunc_path_corner path to follow.
298 speed : speed the train moves (can be overridden by each spawnfunc_path_corner)
299 target : targetname of first spawnfunc_path_corner (starts here)
301 void spawnfunc_func_train()
303 if (self.noise != "")
304 precache_sound(self.noise);
307 objerror("func_train without a target");
311 if not(InitMovingBrushTrigger())
313 self.effects |= EF_LOWPRECISION;
315 // wait for targets to spawn
316 InitializeEntity(self, func_train_find, INITPRIO_SETLOCATION);
318 self.blocked = generic_plat_blocked;
319 if(self.dmg & (!self.message))
320 self.message = " was squished";
321 if(self.dmg && (!self.message2))
322 self.message2 = "was squished by";
323 if(self.dmg && (!self.dmgtime))
325 self.dmgtime2 = time;
327 // TODO make a reset function for this one
330 void func_rotating_setactive(float astate)
333 if (astate == ACTIVE_TOGGLE)
335 if(self.active == ACTIVE_ACTIVE)
336 self.active = ACTIVE_NOT;
338 self.active = ACTIVE_ACTIVE;
341 self.active = astate;
343 if(self.active == ACTIVE_NOT)
344 self.avelocity = '0 0 0';
346 self.avelocity = self.pos1;
349 /*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS
350 Brush model that spins in place on one axis (default Z).
351 speed : speed to rotate (in degrees per second)
352 noise : path/name of looping .wav file to play.
353 dmg : Do this mutch dmg every .dmgtime intervall when blocked
357 void spawnfunc_func_rotating()
359 if (self.noise != "")
361 precache_sound(self.noise);
362 ambientsound(self.origin, self.noise, VOL_BASE, ATTN_IDLE);
365 self.active = ACTIVE_ACTIVE;
366 self.setactive = func_rotating_setactive;
370 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
371 if (self.spawnflags & 4) // X (untested)
372 self.avelocity = '0 0 1' * self.speed;
373 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
374 else if (self.spawnflags & 8) // Y (untested)
375 self.avelocity = '1 0 0' * self.speed;
376 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
378 self.avelocity = '0 1 0' * self.speed;
380 self.pos1 = self.avelocity;
382 if(self.dmg & (!self.message))
383 self.message = " was squished";
384 if(self.dmg && (!self.message2))
385 self.message2 = "was squished by";
388 if(self.dmg && (!self.dmgtime))
391 self.dmgtime2 = time;
393 if not(InitMovingBrushTrigger())
395 // no EF_LOWPRECISION here, as rounding angles is bad
397 self.blocked = generic_plat_blocked;
399 // wait for targets to spawn
400 self.nextthink = self.ltime + 999999999;
401 self.think = SUB_Null;
403 // TODO make a reset function for this one
407 void func_bobbing_controller_think()
410 self.nextthink = time + 0.1;
412 if not (self.owner.active == ACTIVE_ACTIVE)
414 self.owner.velocity = '0 0 0';
418 // calculate sinewave using makevectors
419 makevectors((self.nextthink * self.owner.cnt + self.owner.phase * 360) * '0 1 0');
420 v = self.owner.destvec + self.owner.movedir * v_forward_y;
421 if(self.owner.classname == "func_bobbing") // don't brake stuff if the func_bobbing was killtarget'ed
422 // * 10 so it will arrive in 0.1 sec
423 self.owner.velocity = (v - self.owner.origin) * 10;
426 /*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
427 Brush model that moves back and forth on one axis (default Z).
428 speed : how long one cycle takes in seconds (default 4)
429 height : how far the cycle moves (default 32)
430 phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
431 noise : path/name of looping .wav file to play.
432 dmg : Do this mutch dmg every .dmgtime intervall when blocked
435 void spawnfunc_func_bobbing()
437 local entity controller;
438 if (self.noise != "")
440 precache_sound(self.noise);
441 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
447 // center of bobbing motion
448 self.destvec = self.origin;
449 // time scale to get degrees
450 self.cnt = 360 / self.speed;
452 self.active = ACTIVE_ACTIVE;
454 // damage when blocked
455 self.blocked = generic_plat_blocked;
456 if(self.dmg & (!self.message))
457 self.message = " was squished";
458 if(self.dmg && (!self.message2))
459 self.message2 = "was squished by";
460 if(self.dmg && (!self.dmgtime))
462 self.dmgtime2 = time;
465 if (self.spawnflags & 1) // X
466 self.movedir = '1 0 0' * self.height;
467 else if (self.spawnflags & 2) // Y
468 self.movedir = '0 1 0' * self.height;
470 self.movedir = '0 0 1' * self.height;
472 if not(InitMovingBrushTrigger())
475 // wait for targets to spawn
476 controller = spawn();
477 controller.classname = "func_bobbing_controller";
478 controller.owner = self;
479 controller.nextthink = time + 1;
480 controller.think = func_bobbing_controller_think;
481 self.nextthink = self.ltime + 999999999;
482 self.think = SUB_Null;
484 // Savage: Reduce bandwith, critical on e.g. nexdm02
485 self.effects |= EF_LOWPRECISION;
487 // TODO make a reset function for this one
491 void func_pendulum_controller_think()
494 self.nextthink = time + 0.1;
496 if not (self.owner.active == ACTIVE_ACTIVE)
498 self.owner.avelocity_x = 0;
502 // calculate sinewave using makevectors
503 makevectors((self.nextthink * self.owner.freq + self.owner.phase) * '0 360 0');
504 v = self.owner.speed * v_forward_y;
505 if(self.owner.classname == "func_pendulum") // don't brake stuff if the func_bobbing was killtarget'ed
507 // * 10 so it will arrive in 0.1 sec
508 self.owner.avelocity_x = (remainder(v - self.owner.angles_x, 360)) * 10;
512 void spawnfunc_func_pendulum()
514 local entity controller;
515 if (self.noise != "")
517 precache_sound(self.noise);
518 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
521 self.active = ACTIVE_ACTIVE;
523 // keys: angle, speed, phase, noise, freq
527 // not initializing self.dmg to 2, to allow damageless pendulum
529 if(self.dmg & (!self.message))
530 self.message = " was squished";
531 if(self.dmg && (!self.message2))
532 self.message2 = "was squished by";
533 if(self.dmg && (!self.dmgtime))
535 self.dmgtime2 = time;
537 self.blocked = generic_plat_blocked;
539 if not(InitMovingBrushTrigger())
544 // find pendulum length (same formula as Q3A)
545 self.freq = 1 / (M_PI * 2) * sqrt(cvar("sv_gravity") / (3 * fabs(self.mins_z)));
548 // wait for targets to spawn
549 controller = spawn();
550 controller.classname = "func_pendulum_controller";
551 controller.owner = self;
552 controller.nextthink = time + 1;
553 controller.think = func_pendulum_controller_think;
554 self.nextthink = self.ltime + 999999999;
555 self.think = SUB_Null;
557 //self.effects |= EF_LOWPRECISION;
559 // TODO make a reset function for this one
562 // button and multiple button
565 void() button_return;
569 self.state = STATE_TOP;
570 self.nextthink = self.ltime + self.wait;
571 self.think = button_return;
572 activator = self.enemy;
574 self.frame = 1; // use alternate textures
579 self.state = STATE_BOTTOM;
584 self.state = STATE_DOWN;
585 SUB_CalcMove (self.pos1, self.speed, button_done);
586 self.frame = 0; // use normal textures
588 self.takedamage = DAMAGE_YES; // can be shot again
592 void button_blocked()
594 // do nothing, just don't come all the way back out
600 self.health = self.max_health;
601 self.takedamage = DAMAGE_NO; // will be reset upon return
603 if (self.state == STATE_UP || self.state == STATE_TOP)
606 if (self.noise != "")
607 sound (self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
609 self.state = STATE_UP;
610 SUB_CalcMove (self.pos2, self.speed, button_wait);
615 self.health = self.max_health;
616 setorigin(self, self.pos1);
617 self.frame = 0; // use normal textures
618 self.state = STATE_BOTTOM;
620 self.takedamage = DAMAGE_YES; // can be shot again
625 // if (activator.classname != "player")
627 // dprint(activator.classname);
628 // dprint(" triggered a button\n");
631 if not (self.active == ACTIVE_ACTIVE)
634 self.enemy = activator;
640 // if (activator.classname != "player")
642 // dprint(activator.classname);
643 // dprint(" touched a button\n");
647 if not(other.iscreature)
649 if(other.velocity * self.movedir < 0)
653 self.enemy = other.owner;
657 void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
659 if(self.spawnflags & DOOR_NOSPLASH)
660 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
662 self.health = self.health - damage;
663 if (self.health <= 0)
665 // if (activator.classname != "player")
667 // dprint(activator.classname);
668 // dprint(" killed a button\n");
670 self.enemy = damage_attacker;
676 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
677 When a button is touched, it moves some distance in the direction of it's angle, triggers all of it's targets, waits some time, then returns to it's original position where it can be triggered again.
679 "angle" determines the opening direction
680 "target" all entities with a matching targetname will be used
681 "speed" override the default 40 speed
682 "wait" override the default 1 second wait (-1 = never return)
683 "lip" override the default 4 pixel lip remaining at end of move
684 "health" if set, the button must be killed instead of touched. If set to -1, the button will fire on ANY attack, even damageless ones like the MinstaGib laser
691 void spawnfunc_func_button()
695 if not(InitMovingBrushTrigger())
697 self.effects |= EF_LOWPRECISION;
699 self.blocked = button_blocked;
700 self.use = button_use;
702 // if (self.health == 0) // all buttons are now shootable
706 self.max_health = self.health;
707 self.event_damage = button_damage;
708 self.takedamage = DAMAGE_YES;
711 self.touch = button_touch;
721 precache_sound(self.noise);
723 self.active = ACTIVE_ACTIVE;
725 self.pos1 = self.origin;
726 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
727 self.flags |= FL_NOTARGET;
733 float DOOR_START_OPEN = 1;
734 float DOOR_DONT_LINK = 4;
735 float DOOR_TOGGLE = 32;
739 Doors are similar to buttons, but can spawn a fat trigger field around them
740 to open without a touch, and they link together to form simultanious
743 Door.owner is the master door. If there is only one door, it points to itself.
744 If multiple doors, all will point to a single one.
746 Door.enemy chains from the master door through all doors linked in the chain.
751 =============================================================================
755 =============================================================================
760 void() door_rotating_go_down;
761 void() door_rotating_go_up;
766 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
767 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
770 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
771 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
773 //Dont chamge direction for dead or dying stuff
774 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
777 if (self.state == STATE_DOWN)
778 if (self.classname == "door")
783 door_rotating_go_up ();
786 if (self.classname == "door")
791 door_rotating_go_down ();
795 //gib dying stuff just to make sure
796 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
797 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
801 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
802 // if a door has a negative wait, it would never come back if blocked,
803 // so let it just squash the object to death real fast
804 /* if (self.wait >= 0)
806 if (self.state == STATE_DOWN)
817 if (self.noise1 != "")
818 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
819 self.state = STATE_TOP;
820 if (self.spawnflags & DOOR_TOGGLE)
821 return; // don't come down automatically
822 if (self.classname == "door")
824 self.think = door_go_down;
827 self.think = door_rotating_go_down;
829 self.nextthink = self.ltime + self.wait;
832 void door_hit_bottom()
834 if (self.noise1 != "")
835 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
836 self.state = STATE_BOTTOM;
841 if (self.noise2 != "")
842 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
845 self.takedamage = DAMAGE_YES;
846 self.health = self.max_health;
849 self.state = STATE_DOWN;
850 SUB_CalcMove (self.pos1, self.speed, door_hit_bottom);
855 if (self.state == STATE_UP)
856 return; // already going up
858 if (self.state == STATE_TOP)
859 { // reset top wait time
860 self.nextthink = self.ltime + self.wait;
864 if (self.noise2 != "")
865 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
866 self.state = STATE_UP;
867 SUB_CalcMove (self.pos2, self.speed, door_hit_top);
870 oldmessage = self.message;
873 self.message = oldmessage;
878 =============================================================================
882 =============================================================================
890 if (self.owner != self)
891 objerror ("door_fire: self.owner != self");
895 if (self.spawnflags & DOOR_TOGGLE)
897 if (self.state == STATE_UP || self.state == STATE_TOP)
902 if (self.classname == "door")
908 door_rotating_go_down ();
911 } while ( (self != starte) && (self != world) );
917 // trigger all paired doors
921 if (self.classname == "door")
926 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
927 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
929 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
930 self.pos2 = '0 0 0' - self.pos2;
932 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
933 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
934 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
936 door_rotating_go_up ();
940 } while ( (self != starte) && (self != world) );
949 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
960 void door_trigger_touch()
962 if (other.health < 1)
963 if not(other.iscreature && other.deadflag == DEAD_NO)
966 if (time < self.attack_finished_single)
968 self.attack_finished_single = time + 1;
977 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
980 if(self.spawnflags & DOOR_NOSPLASH)
981 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
983 self.health = self.health - damage;
984 if (self.health <= 0)
988 self.health = self.max_health;
989 self.takedamage = DAMAGE_NO; // wil be reset upon return
1005 if(other.classname != "player")
1007 if (self.owner.attack_finished_single > time)
1010 self.owner.attack_finished_single = time + 2;
1012 if (!(self.owner.dmg) && (self.owner.message != ""))
1014 if (other.flags & FL_CLIENT)
1015 centerprint (other, self.owner.message);
1016 play2(other, "misc/talk.wav");
1021 void door_generic_plat_blocked()
1024 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
1025 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1028 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
1029 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1031 //Dont chamge direction for dead or dying stuff
1032 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
1035 if (self.state == STATE_DOWN)
1036 door_rotating_go_up ();
1038 door_rotating_go_down ();
1041 //gib dying stuff just to make sure
1042 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
1043 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1047 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1048 // if a door has a negative wait, it would never come back if blocked,
1049 // so let it just squash the object to death real fast
1050 /* if (self.wait >= 0)
1052 if (self.state == STATE_DOWN)
1053 door_rotating_go_up ();
1055 door_rotating_go_down ();
1061 void door_rotating_hit_top()
1063 if (self.noise1 != "")
1064 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
1065 self.state = STATE_TOP;
1066 if (self.spawnflags & DOOR_TOGGLE)
1067 return; // don't come down automatically
1068 self.think = door_rotating_go_down;
1069 self.nextthink = self.ltime + self.wait;
1072 void door_rotating_hit_bottom()
1074 if (self.noise1 != "")
1075 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
1076 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
1078 self.pos2 = '0 0 0' - self.pos2;
1081 self.state = STATE_BOTTOM;
1084 void door_rotating_go_down()
1086 if (self.noise2 != "")
1087 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1088 if (self.max_health)
1090 self.takedamage = DAMAGE_YES;
1091 self.health = self.max_health;
1094 self.state = STATE_DOWN;
1095 SUB_CalcAngleMove (self.pos1, self.speed, door_rotating_hit_bottom);
1098 void door_rotating_go_up()
1100 if (self.state == STATE_UP)
1101 return; // already going up
1103 if (self.state == STATE_TOP)
1104 { // reset top wait time
1105 self.nextthink = self.ltime + self.wait;
1108 if (self.noise2 != "")
1109 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1110 self.state = STATE_UP;
1111 SUB_CalcAngleMove (self.pos2, self.speed, door_rotating_hit_top);
1114 oldmessage = self.message;
1117 self.message = oldmessage;
1124 =============================================================================
1128 =============================================================================
1132 entity spawn_field(vector fmins, vector fmaxs)
1134 local entity trigger;
1135 local vector t1, t2;
1138 trigger.classname = "doortriggerfield";
1139 trigger.movetype = MOVETYPE_NONE;
1140 trigger.solid = SOLID_TRIGGER;
1141 trigger.owner = self;
1142 trigger.touch = door_trigger_touch;
1146 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1151 float EntitiesTouching(entity e1, entity e2)
1153 if (e1.absmin_x > e2.absmax_x)
1155 if (e1.absmin_y > e2.absmax_y)
1157 if (e1.absmin_z > e2.absmax_z)
1159 if (e1.absmax_x < e2.absmin_x)
1161 if (e1.absmax_y < e2.absmin_y)
1163 if (e1.absmax_z < e2.absmin_z)
1178 local entity t, starte;
1179 local vector cmins, cmaxs;
1182 return; // already linked by another door
1183 if (self.spawnflags & 4)
1185 self.owner = self.enemy = self;
1193 self.trigger_field = spawn_field(self.absmin, self.absmax);
1195 return; // don't want to link this door
1198 cmins = self.absmin;
1199 cmaxs = self.absmax;
1206 self.owner = starte; // master door
1209 starte.health = self.health;
1211 starte.targetname = self.targetname;
1212 if (self.message != "")
1213 starte.message = self.message;
1215 t = find(t, classname, self.classname);
1218 self.enemy = starte; // make the chain a loop
1220 // shootable, or triggered doors just needed the owner/enemy links,
1221 // they don't spawn a field
1232 self.owner.trigger_field = spawn_field(cmins, cmaxs);
1237 if (EntitiesTouching(self,t))
1240 objerror ("cross connected doors");
1245 if (t.absmin_x < cmins_x)
1246 cmins_x = t.absmin_x;
1247 if (t.absmin_y < cmins_y)
1248 cmins_y = t.absmin_y;
1249 if (t.absmin_z < cmins_z)
1250 cmins_z = t.absmin_z;
1251 if (t.absmax_x > cmaxs_x)
1252 cmaxs_x = t.absmax_x;
1253 if (t.absmax_y > cmaxs_y)
1254 cmaxs_y = t.absmax_y;
1255 if (t.absmax_z > cmaxs_z)
1256 cmaxs_z = t.absmax_z;
1263 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK x x TOGGLE
1264 if two doors touch, they are assumed to be connected and operate as a unit.
1266 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1268 START_OPEN causes the door to move to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors).
1270 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1271 "angle" determines the opening direction
1272 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1273 "health" if set, door must be shot open
1274 "speed" movement speed (100 default)
1275 "wait" wait before returning (3 default, -1 = never return)
1276 "lip" lip remaining at end of move (8 default)
1277 "dmg" damage to inflict when blocked (2 default)
1284 FIXME: only one sound set available at the time being
1288 void door_init_startopen()
1290 setorigin (self, self.pos2);
1291 self.pos2 = self.pos1;
1292 self.pos1 = self.origin;
1297 setorigin(self, self.pos1);
1298 self.velocity = '0 0 0';
1299 self.state = STATE_BOTTOM;
1300 self.think = SUB_Null;
1303 void spawnfunc_func_door()
1305 //if (!self.deathtype) // map makers can override this
1306 // self.deathtype = " got in the way";
1309 self.max_health = self.health;
1310 if not(InitMovingBrushTrigger())
1312 self.effects |= EF_LOWPRECISION;
1313 self.classname = "door";
1315 self.blocked = door_blocked;
1316 self.use = door_use;
1318 if(self.spawnflags & 8)
1321 if(self.dmg && (!self.message))
1322 self.message = "was squished";
1323 if(self.dmg && (!self.message2))
1324 self.message2 = "was squished by";
1326 if (self.sounds > 0)
1328 precache_sound ("plats/medplat1.wav");
1329 precache_sound ("plats/medplat2.wav");
1330 self.noise2 = "plats/medplat1.wav";
1331 self.noise1 = "plats/medplat2.wav";
1341 self.pos1 = self.origin;
1342 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1344 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1345 // but spawn in the open position
1346 if (self.spawnflags & DOOR_START_OPEN)
1347 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1349 self.state = STATE_BOTTOM;
1353 self.takedamage = DAMAGE_YES;
1354 self.event_damage = door_damage;
1360 self.touch = door_touch;
1362 // LinkDoors can't be done until all of the doors have been spawned, so
1363 // the sizes can be detected properly.
1364 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1366 self.reset = door_reset;
1369 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1370 if two doors touch, they are assumed to be connected and operate as a unit.
1372 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1374 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1375 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1376 must have set trigger_reverse to 1.
1377 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1379 START_OPEN causes the door to move to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not usefull for touch or takedamage doors).
1381 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1382 "angle" determines the destination angle for opening. negative values reverse the direction.
1383 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1384 "health" if set, door must be shot open
1385 "speed" movement speed (100 default)
1386 "wait" wait before returning (3 default, -1 = never return)
1387 "dmg" damage to inflict when blocked (2 default)
1394 FIXME: only one sound set available at the time being
1397 void door_rotating_reset()
1399 self.angles = self.pos1;
1400 self.avelocity = '0 0 0';
1401 self.state = STATE_BOTTOM;
1402 self.think = SUB_Null;
1405 void door_rotating_init_startopen()
1407 self.angles = self.movedir;
1408 self.pos2 = '0 0 0';
1409 self.pos1 = self.movedir;
1413 void spawnfunc_func_door_rotating()
1416 //if (!self.deathtype) // map makers can override this
1417 // self.deathtype = " got in the way";
1419 // I abuse "movedir" for denoting the axis for now
1420 if (self.spawnflags & 64) // X (untested)
1421 self.movedir = '0 0 1';
1422 else if (self.spawnflags & 128) // Y (untested)
1423 self.movedir = '1 0 0';
1425 self.movedir = '0 1 0';
1427 if (self.angles_y==0) self.angles_y = 90;
1429 self.movedir = self.movedir * self.angles_y;
1430 self.angles = '0 0 0';
1432 self.max_health = self.health;
1433 if not(InitMovingBrushTrigger())
1435 //self.effects |= EF_LOWPRECISION;
1436 self.classname = "door_rotating";
1438 self.blocked = door_blocked;
1439 self.use = door_use;
1441 if(self.spawnflags & 8)
1444 if(self.dmg && (!self.message))
1445 self.message = "was squished";
1446 if(self.dmg && (!self.message2))
1447 self.message2 = "was squished by";
1449 if (self.sounds > 0)
1451 precache_sound ("plats/medplat1.wav");
1452 precache_sound ("plats/medplat2.wav");
1453 self.noise2 = "plats/medplat1.wav";
1454 self.noise1 = "plats/medplat2.wav";
1461 self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1463 self.pos1 = '0 0 0';
1464 self.pos2 = self.movedir;
1466 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1467 // but spawn in the open position
1468 if (self.spawnflags & DOOR_START_OPEN)
1469 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1471 self.state = STATE_BOTTOM;
1475 self.takedamage = DAMAGE_YES;
1476 self.event_damage = door_damage;
1482 self.touch = door_touch;
1484 // LinkDoors can't be done until all of the doors have been spawned, so
1485 // the sizes can be detected properly.
1486 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1488 self.reset = door_rotating_reset;
1492 =============================================================================
1496 =============================================================================
1499 void() fd_secret_move1;
1500 void() fd_secret_move2;
1501 void() fd_secret_move3;
1502 void() fd_secret_move4;
1503 void() fd_secret_move5;
1504 void() fd_secret_move6;
1505 void() fd_secret_done;
1507 float SECRET_OPEN_ONCE = 1; // stays open
1508 float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
1509 float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
1510 float SECRET_NO_SHOOT = 8; // only opened by trigger
1511 float SECRET_YES_SHOOT = 16; // shootable even if targeted
1514 void fd_secret_use()
1517 string message_save;
1519 self.health = 10000;
1520 self.bot_attack = TRUE;
1522 // exit if still moving around...
1523 if (self.origin != self.oldorigin)
1526 message_save = self.message;
1527 self.message = ""; // no more message
1528 SUB_UseTargets(); // fire all targets / killtargets
1529 self.message = message_save;
1531 self.velocity = '0 0 0';
1533 // Make a sound, wait a little...
1535 if (self.noise1 != "")
1536 sound(self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
1537 self.nextthink = self.ltime + 0.1;
1539 temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1540 makevectors(self.mangle);
1544 if (self.spawnflags & SECRET_1ST_DOWN)
1545 self.t_width = fabs(v_up * self.size);
1547 self.t_width = fabs(v_right * self.size);
1551 self.t_length = fabs(v_forward * self.size);
1553 if (self.spawnflags & SECRET_1ST_DOWN)
1554 self.dest1 = self.origin - v_up * self.t_width;
1556 self.dest1 = self.origin + v_right * (self.t_width * temp);
1558 self.dest2 = self.dest1 + v_forward * self.t_length;
1559 SUB_CalcMove(self.dest1, self.speed, fd_secret_move1);
1560 if (self.noise2 != "")
1561 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1564 // Wait after first movement...
1565 void fd_secret_move1()
1567 self.nextthink = self.ltime + 1.0;
1568 self.think = fd_secret_move2;
1569 if (self.noise3 != "")
1570 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1573 // Start moving sideways w/sound...
1574 void fd_secret_move2()
1576 if (self.noise2 != "")
1577 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1578 SUB_CalcMove(self.dest2, self.speed, fd_secret_move3);
1581 // Wait here until time to go back...
1582 void fd_secret_move3()
1584 if (self.noise3 != "")
1585 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1586 if (!(self.spawnflags & SECRET_OPEN_ONCE))
1588 self.nextthink = self.ltime + self.wait;
1589 self.think = fd_secret_move4;
1594 void fd_secret_move4()
1596 if (self.noise2 != "")
1597 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1598 SUB_CalcMove(self.dest1, self.speed, fd_secret_move5);
1602 void fd_secret_move5()
1604 self.nextthink = self.ltime + 1.0;
1605 self.think = fd_secret_move6;
1606 if (self.noise3 != "")
1607 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1610 void fd_secret_move6()
1612 if (self.noise2 != "")
1613 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1614 SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done);
1617 void fd_secret_done()
1619 if (self.spawnflags&SECRET_YES_SHOOT)
1621 self.health = 10000;
1622 self.takedamage = DAMAGE_YES;
1623 //self.th_pain = fd_secret_use;
1625 if (self.noise3 != "")
1626 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1629 void secret_blocked()
1631 if (time < self.attack_finished_single)
1633 self.attack_finished_single = time + 0.5;
1634 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1646 if not(other.iscreature)
1648 if (self.attack_finished_single > time)
1651 self.attack_finished_single = time + 2;
1655 if (other.flags & FL_CLIENT)
1656 centerprint (other, self.message);
1657 play2(other, "misc/talk.wav");
1663 if (self.spawnflags&SECRET_YES_SHOOT)
1665 self.health = 10000;
1666 self.takedamage = DAMAGE_YES;
1668 setorigin(self, self.oldorigin);
1669 self.think = SUB_Null;
1672 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1673 Basic secret door. Slides back, then to the side. Angle determines direction.
1674 wait = # of seconds before coming back
1675 1st_left = 1st move is left of arrow
1676 1st_down = 1st move is down from arrow
1677 always_shoot = even if targeted, keep shootable
1678 t_width = override WIDTH to move back (or height if going down)
1679 t_length = override LENGTH to move sideways
1680 "dmg" damage to inflict when blocked (2 default)
1682 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1689 void spawnfunc_func_door_secret()
1691 /*if (!self.deathtype) // map makers can override this
1692 self.deathtype = " got in the way";*/
1698 self.mangle = self.angles;
1699 self.angles = '0 0 0';
1700 self.classname = "door";
1701 if not(InitMovingBrushTrigger())
1703 self.effects |= EF_LOWPRECISION;
1705 self.touch = secret_touch;
1706 self.blocked = secret_blocked;
1708 self.use = fd_secret_use;
1713 self.spawnflags |= SECRET_YES_SHOOT;
1715 if(self.spawnflags&SECRET_YES_SHOOT)
1717 self.health = 10000;
1718 self.takedamage = DAMAGE_YES;
1719 self.event_damage = fd_secret_use;
1721 self.oldorigin = self.origin;
1723 self.wait = 5; // 5 seconds before closing
1725 self.reset = secret_reset;
1729 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1730 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1731 netname: list of <frequencymultiplier> <phase> <x> <y> <z> quadruples, separated by spaces; note that phase 0 represents a sine wave, and phase 0.25 a cosine wave (by default, it uses 1 0 0 0 1, to match func_bobbing's defaults
1732 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1733 height: amplitude modifier (default 32)
1734 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1735 noise: path/name of looping .wav file to play.
1736 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1740 void func_fourier_controller_think()
1745 self.nextthink = time + 0.1;
1746 if not (self.owner.active == ACTIVE_ACTIVE)
1748 self.owner.velocity = '0 0 0';
1753 n = floor((tokenize_console(self.owner.netname)) / 5);
1754 t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1756 v = self.owner.destvec;
1758 for(i = 0; i < n; ++i)
1760 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1761 v = v + ('1 0 0' * stof(argv(i*5+2)) + '0 1 0' * stof(argv(i*5+3)) + '0 0 1' * stof(argv(i*5+4))) * self.owner.height * v_forward_y;
1764 if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed
1765 // * 10 so it will arrive in 0.1 sec
1766 self.owner.velocity = (v - self.owner.origin) * 10;
1769 void spawnfunc_func_fourier()
1771 local entity controller;
1772 if (self.noise != "")
1774 precache_sound(self.noise);
1775 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
1782 self.destvec = self.origin;
1783 self.cnt = 360 / self.speed;
1785 self.blocked = generic_plat_blocked;
1786 if(self.dmg & (!self.message))
1787 self.message = " was squished";
1788 if(self.dmg && (!self.message2))
1789 self.message2 = "was squished by";
1790 if(self.dmg && (!self.dmgtime))
1791 self.dmgtime = 0.25;
1792 self.dmgtime2 = time;
1794 if(self.netname == "")
1795 self.netname = "1 0 0 0 1";
1797 if not(InitMovingBrushTrigger())
1800 self.active = ACTIVE_ACTIVE;
1802 // wait for targets to spawn
1803 controller = spawn();
1804 controller.classname = "func_fourier_controller";
1805 controller.owner = self;
1806 controller.nextthink = time + 1;
1807 controller.think = func_fourier_controller_think;
1808 self.nextthink = self.ltime + 999999999;
1809 self.think = SUB_Null;
1811 // Savage: Reduce bandwith, critical on e.g. nexdm02
1812 self.effects |= EF_LOWPRECISION;
1814 // TODO make a reset function for this one
1817 // reusing some fields havocbots declared
1818 .entity wp00, wp01, wp02, wp03;
1820 .float targetfactor, target2factor, target3factor, target4factor;
1821 .vector targetnormal, target2normal, target3normal, target4normal;
1823 vector func_vectormamamam_origin(entity o, float t)
1835 p = e.origin + t * e.velocity;
1837 v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
1839 v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
1845 p = e.origin + t * e.velocity;
1847 v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
1849 v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
1855 p = e.origin + t * e.velocity;
1857 v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
1859 v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
1865 p = e.origin + t * e.velocity;
1867 v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
1869 v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
1875 void func_vectormamamam_controller_think()
1877 self.nextthink = time + 0.1;
1879 if not (self.owner.active == ACTIVE_ACTIVE)
1881 self.owner.velocity = '0 0 0';
1885 if(self.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed
1886 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
1889 void func_vectormamamam_findtarget()
1891 if(self.target != "")
1892 self.wp00 = find(world, targetname, self.target);
1894 if(self.target2 != "")
1895 self.wp01 = find(world, targetname, self.target2);
1897 if(self.target3 != "")
1898 self.wp02 = find(world, targetname, self.target3);
1900 if(self.target4 != "")
1901 self.wp03 = find(world, targetname, self.target4);
1903 if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
1904 objerror("No reference entity found, so there is nothing to move. Aborting.");
1906 self.destvec = self.origin - func_vectormamamam_origin(self.owner, 0);
1908 local entity controller;
1909 controller = spawn();
1910 controller.classname = "func_vectormamamam_controller";
1911 controller.owner = self;
1912 controller.nextthink = time + 1;
1913 controller.think = func_vectormamamam_controller_think;
1916 void spawnfunc_func_vectormamamam()
1918 if (self.noise != "")
1920 precache_sound(self.noise);
1921 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
1924 if(!self.targetfactor)
1925 self.targetfactor = 1;
1927 if(!self.target2factor)
1928 self.target2factor = 1;
1930 if(!self.target3factor)
1931 self.target3factor = 1;
1933 if(!self.target4factor)
1934 self.target4factor = 1;
1936 if(vlen(self.targetnormal))
1937 self.targetnormal = normalize(self.targetnormal);
1939 if(vlen(self.target2normal))
1940 self.target2normal = normalize(self.target2normal);
1942 if(vlen(self.target3normal))
1943 self.target3normal = normalize(self.target3normal);
1945 if(vlen(self.target4normal))
1946 self.target4normal = normalize(self.target4normal);
1948 self.blocked = generic_plat_blocked;
1949 if(self.dmg & (!self.message))
1950 self.message = " was squished";
1951 if(self.dmg && (!self.message2))
1952 self.message2 = "was squished by";
1953 if(self.dmg && (!self.dmgtime))
1954 self.dmgtime = 0.25;
1955 self.dmgtime2 = time;
1957 if(self.netname == "")
1958 self.netname = "1 0 0 0 1";
1960 if not(InitMovingBrushTrigger())
1963 // wait for targets to spawn
1964 self.nextthink = self.ltime + 999999999;
1965 self.think = SUB_Null;
1967 // Savage: Reduce bandwith, critical on e.g. nexdm02
1968 self.effects |= EF_LOWPRECISION;
1970 self.active = ACTIVE_ACTIVE;
1972 InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);