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 + self.cnt;
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_z = (remainder(v - self.owner.angles_z, 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 self.avelocity_z = 0.0000001;
540 if not(InitMovingBrushTrigger())
545 // find pendulum length (same formula as Q3A)
546 self.freq = 1 / (M_PI * 2) * sqrt(autocvar_sv_gravity / (3 * max(8, fabs(self.mins_z))));
549 // copy initial angle
550 self.cnt = self.angles_z;
552 // wait for targets to spawn
553 controller = spawn();
554 controller.classname = "func_pendulum_controller";
555 controller.owner = self;
556 controller.nextthink = time + 1;
557 controller.think = func_pendulum_controller_think;
558 self.nextthink = self.ltime + 999999999;
559 self.think = SUB_Null;
561 //self.effects |= EF_LOWPRECISION;
563 // TODO make a reset function for this one
566 // button and multiple button
569 void() button_return;
573 self.state = STATE_TOP;
574 self.nextthink = self.ltime + self.wait;
575 self.think = button_return;
576 activator = self.enemy;
578 self.frame = 1; // use alternate textures
583 self.state = STATE_BOTTOM;
588 self.state = STATE_DOWN;
589 SUB_CalcMove (self.pos1, self.speed, button_done);
590 self.frame = 0; // use normal textures
592 self.takedamage = DAMAGE_YES; // can be shot again
596 void button_blocked()
598 // do nothing, just don't come all the way back out
604 self.health = self.max_health;
605 self.takedamage = DAMAGE_NO; // will be reset upon return
607 if (self.state == STATE_UP || self.state == STATE_TOP)
610 if (self.noise != "")
611 sound (self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
613 self.state = STATE_UP;
614 SUB_CalcMove (self.pos2, self.speed, button_wait);
619 self.health = self.max_health;
620 setorigin(self, self.pos1);
621 self.frame = 0; // use normal textures
622 self.state = STATE_BOTTOM;
624 self.takedamage = DAMAGE_YES; // can be shot again
629 // if (activator.classname != "player")
631 // dprint(activator.classname);
632 // dprint(" triggered a button\n");
635 if not (self.active == ACTIVE_ACTIVE)
638 self.enemy = activator;
644 // if (activator.classname != "player")
646 // dprint(activator.classname);
647 // dprint(" touched a button\n");
651 if not(other.iscreature)
653 if(other.velocity * self.movedir < 0)
657 self.enemy = other.owner;
661 void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
663 if(self.spawnflags & DOOR_NOSPLASH)
664 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
666 self.health = self.health - damage;
667 if (self.health <= 0)
669 // if (activator.classname != "player")
671 // dprint(activator.classname);
672 // dprint(" killed a button\n");
674 self.enemy = damage_attacker;
680 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
681 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.
683 "angle" determines the opening direction
684 "target" all entities with a matching targetname will be used
685 "speed" override the default 40 speed
686 "wait" override the default 1 second wait (-1 = never return)
687 "lip" override the default 4 pixel lip remaining at end of move
688 "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
695 void spawnfunc_func_button()
699 if not(InitMovingBrushTrigger())
701 self.effects |= EF_LOWPRECISION;
703 self.blocked = button_blocked;
704 self.use = button_use;
706 // if (self.health == 0) // all buttons are now shootable
710 self.max_health = self.health;
711 self.event_damage = button_damage;
712 self.takedamage = DAMAGE_YES;
715 self.touch = button_touch;
725 precache_sound(self.noise);
727 self.active = ACTIVE_ACTIVE;
729 self.pos1 = self.origin;
730 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
731 self.flags |= FL_NOTARGET;
737 float DOOR_START_OPEN = 1;
738 float DOOR_DONT_LINK = 4;
739 float DOOR_TOGGLE = 32;
743 Doors are similar to buttons, but can spawn a fat trigger field around them
744 to open without a touch, and they link together to form simultanious
747 Door.owner is the master door. If there is only one door, it points to itself.
748 If multiple doors, all will point to a single one.
750 Door.enemy chains from the master door through all doors linked in the chain.
755 =============================================================================
759 =============================================================================
764 void() door_rotating_go_down;
765 void() door_rotating_go_up;
770 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
771 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
774 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
775 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
777 //Dont chamge direction for dead or dying stuff
778 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
781 if (self.state == STATE_DOWN)
782 if (self.classname == "door")
787 door_rotating_go_up ();
790 if (self.classname == "door")
795 door_rotating_go_down ();
799 //gib dying stuff just to make sure
800 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
801 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
805 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
806 // if a door has a negative wait, it would never come back if blocked,
807 // so let it just squash the object to death real fast
808 /* if (self.wait >= 0)
810 if (self.state == STATE_DOWN)
821 if (self.noise1 != "")
822 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
823 self.state = STATE_TOP;
824 if (self.spawnflags & DOOR_TOGGLE)
825 return; // don't come down automatically
826 if (self.classname == "door")
828 self.think = door_go_down;
831 self.think = door_rotating_go_down;
833 self.nextthink = self.ltime + self.wait;
836 void door_hit_bottom()
838 if (self.noise1 != "")
839 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
840 self.state = STATE_BOTTOM;
845 if (self.noise2 != "")
846 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
849 self.takedamage = DAMAGE_YES;
850 self.health = self.max_health;
853 self.state = STATE_DOWN;
854 SUB_CalcMove (self.pos1, self.speed, door_hit_bottom);
859 if (self.state == STATE_UP)
860 return; // already going up
862 if (self.state == STATE_TOP)
863 { // reset top wait time
864 self.nextthink = self.ltime + self.wait;
868 if (self.noise2 != "")
869 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
870 self.state = STATE_UP;
871 SUB_CalcMove (self.pos2, self.speed, door_hit_top);
874 oldmessage = self.message;
877 self.message = oldmessage;
882 =============================================================================
886 =============================================================================
894 if (self.owner != self)
895 objerror ("door_fire: self.owner != self");
899 if (self.spawnflags & DOOR_TOGGLE)
901 if (self.state == STATE_UP || self.state == STATE_TOP)
906 if (self.classname == "door")
912 door_rotating_go_down ();
915 } while ( (self != starte) && (self != world) );
921 // trigger all paired doors
925 if (self.classname == "door")
930 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
931 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
933 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
934 self.pos2 = '0 0 0' - self.pos2;
936 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
937 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
938 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
940 door_rotating_go_up ();
944 } while ( (self != starte) && (self != world) );
953 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
964 void door_trigger_touch()
966 if (other.health < 1)
967 if not(other.iscreature && other.deadflag == DEAD_NO)
970 if (time < self.attack_finished_single)
972 self.attack_finished_single = time + 1;
981 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
984 if(self.spawnflags & DOOR_NOSPLASH)
985 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
987 self.health = self.health - damage;
988 if (self.health <= 0)
992 self.health = self.max_health;
993 self.takedamage = DAMAGE_NO; // wil be reset upon return
1009 if(other.classname != "player")
1011 if (self.owner.attack_finished_single > time)
1014 self.owner.attack_finished_single = time + 2;
1016 if (!(self.owner.dmg) && (self.owner.message != ""))
1018 if (other.flags & FL_CLIENT)
1019 centerprint (other, self.owner.message);
1020 play2(other, "misc/talk.wav");
1025 void door_generic_plat_blocked()
1028 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
1029 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1032 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
1033 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1035 //Dont chamge direction for dead or dying stuff
1036 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
1039 if (self.state == STATE_DOWN)
1040 door_rotating_go_up ();
1042 door_rotating_go_down ();
1045 //gib dying stuff just to make sure
1046 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
1047 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1051 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1052 // if a door has a negative wait, it would never come back if blocked,
1053 // so let it just squash the object to death real fast
1054 /* if (self.wait >= 0)
1056 if (self.state == STATE_DOWN)
1057 door_rotating_go_up ();
1059 door_rotating_go_down ();
1065 void door_rotating_hit_top()
1067 if (self.noise1 != "")
1068 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
1069 self.state = STATE_TOP;
1070 if (self.spawnflags & DOOR_TOGGLE)
1071 return; // don't come down automatically
1072 self.think = door_rotating_go_down;
1073 self.nextthink = self.ltime + self.wait;
1076 void door_rotating_hit_bottom()
1078 if (self.noise1 != "")
1079 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
1080 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
1082 self.pos2 = '0 0 0' - self.pos2;
1085 self.state = STATE_BOTTOM;
1088 void door_rotating_go_down()
1090 if (self.noise2 != "")
1091 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1092 if (self.max_health)
1094 self.takedamage = DAMAGE_YES;
1095 self.health = self.max_health;
1098 self.state = STATE_DOWN;
1099 SUB_CalcAngleMove (self.pos1, self.speed, door_rotating_hit_bottom);
1102 void door_rotating_go_up()
1104 if (self.state == STATE_UP)
1105 return; // already going up
1107 if (self.state == STATE_TOP)
1108 { // reset top wait time
1109 self.nextthink = self.ltime + self.wait;
1112 if (self.noise2 != "")
1113 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1114 self.state = STATE_UP;
1115 SUB_CalcAngleMove (self.pos2, self.speed, door_rotating_hit_top);
1118 oldmessage = self.message;
1121 self.message = oldmessage;
1128 =============================================================================
1132 =============================================================================
1136 entity spawn_field(vector fmins, vector fmaxs)
1138 local entity trigger;
1139 local vector t1, t2;
1142 trigger.classname = "doortriggerfield";
1143 trigger.movetype = MOVETYPE_NONE;
1144 trigger.solid = SOLID_TRIGGER;
1145 trigger.owner = self;
1146 trigger.touch = door_trigger_touch;
1150 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1155 float EntitiesTouching(entity e1, entity e2)
1157 if (e1.absmin_x > e2.absmax_x)
1159 if (e1.absmin_y > e2.absmax_y)
1161 if (e1.absmin_z > e2.absmax_z)
1163 if (e1.absmax_x < e2.absmin_x)
1165 if (e1.absmax_y < e2.absmin_y)
1167 if (e1.absmax_z < e2.absmin_z)
1182 local entity t, starte;
1183 local vector cmins, cmaxs;
1186 return; // already linked by another door
1187 if (self.spawnflags & 4)
1189 self.owner = self.enemy = self;
1197 self.trigger_field = spawn_field(self.absmin, self.absmax);
1199 return; // don't want to link this door
1202 cmins = self.absmin;
1203 cmaxs = self.absmax;
1210 self.owner = starte; // master door
1213 starte.health = self.health;
1215 starte.targetname = self.targetname;
1216 if (self.message != "")
1217 starte.message = self.message;
1219 t = find(t, classname, self.classname);
1222 self.enemy = starte; // make the chain a loop
1224 // shootable, or triggered doors just needed the owner/enemy links,
1225 // they don't spawn a field
1236 self.owner.trigger_field = spawn_field(cmins, cmaxs);
1241 if (EntitiesTouching(self,t))
1244 objerror ("cross connected doors");
1249 if (t.absmin_x < cmins_x)
1250 cmins_x = t.absmin_x;
1251 if (t.absmin_y < cmins_y)
1252 cmins_y = t.absmin_y;
1253 if (t.absmin_z < cmins_z)
1254 cmins_z = t.absmin_z;
1255 if (t.absmax_x > cmaxs_x)
1256 cmaxs_x = t.absmax_x;
1257 if (t.absmax_y > cmaxs_y)
1258 cmaxs_y = t.absmax_y;
1259 if (t.absmax_z > cmaxs_z)
1260 cmaxs_z = t.absmax_z;
1267 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK x x TOGGLE
1268 if two doors touch, they are assumed to be connected and operate as a unit.
1270 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1272 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).
1274 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1275 "angle" determines the opening direction
1276 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1277 "health" if set, door must be shot open
1278 "speed" movement speed (100 default)
1279 "wait" wait before returning (3 default, -1 = never return)
1280 "lip" lip remaining at end of move (8 default)
1281 "dmg" damage to inflict when blocked (2 default)
1288 FIXME: only one sound set available at the time being
1292 void door_init_startopen()
1294 setorigin (self, self.pos2);
1295 self.pos2 = self.pos1;
1296 self.pos1 = self.origin;
1301 setorigin(self, self.pos1);
1302 self.velocity = '0 0 0';
1303 self.state = STATE_BOTTOM;
1304 self.think = SUB_Null;
1307 void spawnfunc_func_door()
1309 //if (!self.deathtype) // map makers can override this
1310 // self.deathtype = " got in the way";
1313 self.max_health = self.health;
1314 if not(InitMovingBrushTrigger())
1316 self.effects |= EF_LOWPRECISION;
1317 self.classname = "door";
1319 self.blocked = door_blocked;
1320 self.use = door_use;
1322 if(self.spawnflags & 8)
1325 if(self.dmg && (!self.message))
1326 self.message = "was squished";
1327 if(self.dmg && (!self.message2))
1328 self.message2 = "was squished by";
1330 if (self.sounds > 0)
1332 precache_sound ("plats/medplat1.wav");
1333 precache_sound ("plats/medplat2.wav");
1334 self.noise2 = "plats/medplat1.wav";
1335 self.noise1 = "plats/medplat2.wav";
1345 self.pos1 = self.origin;
1346 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1348 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1349 // but spawn in the open position
1350 if (self.spawnflags & DOOR_START_OPEN)
1351 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1353 self.state = STATE_BOTTOM;
1357 self.takedamage = DAMAGE_YES;
1358 self.event_damage = door_damage;
1364 self.touch = door_touch;
1366 // LinkDoors can't be done until all of the doors have been spawned, so
1367 // the sizes can be detected properly.
1368 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1370 self.reset = door_reset;
1373 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1374 if two doors touch, they are assumed to be connected and operate as a unit.
1376 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1378 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1379 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1380 must have set trigger_reverse to 1.
1381 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1383 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).
1385 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1386 "angle" determines the destination angle for opening. negative values reverse the direction.
1387 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1388 "health" if set, door must be shot open
1389 "speed" movement speed (100 default)
1390 "wait" wait before returning (3 default, -1 = never return)
1391 "dmg" damage to inflict when blocked (2 default)
1398 FIXME: only one sound set available at the time being
1401 void door_rotating_reset()
1403 self.angles = self.pos1;
1404 self.avelocity = '0 0 0';
1405 self.state = STATE_BOTTOM;
1406 self.think = SUB_Null;
1409 void door_rotating_init_startopen()
1411 self.angles = self.movedir;
1412 self.pos2 = '0 0 0';
1413 self.pos1 = self.movedir;
1417 void spawnfunc_func_door_rotating()
1420 //if (!self.deathtype) // map makers can override this
1421 // self.deathtype = " got in the way";
1423 // I abuse "movedir" for denoting the axis for now
1424 if (self.spawnflags & 64) // X (untested)
1425 self.movedir = '0 0 1';
1426 else if (self.spawnflags & 128) // Y (untested)
1427 self.movedir = '1 0 0';
1429 self.movedir = '0 1 0';
1431 if (self.angles_y==0) self.angles_y = 90;
1433 self.movedir = self.movedir * self.angles_y;
1434 self.angles = '0 0 0';
1436 self.max_health = self.health;
1437 self.avelocity = self.movedir;
1438 if not(InitMovingBrushTrigger())
1440 self.velocity = '0 0 0';
1441 //self.effects |= EF_LOWPRECISION;
1442 self.classname = "door_rotating";
1444 self.blocked = door_blocked;
1445 self.use = door_use;
1447 if(self.spawnflags & 8)
1450 if(self.dmg && (!self.message))
1451 self.message = "was squished";
1452 if(self.dmg && (!self.message2))
1453 self.message2 = "was squished by";
1455 if (self.sounds > 0)
1457 precache_sound ("plats/medplat1.wav");
1458 precache_sound ("plats/medplat2.wav");
1459 self.noise2 = "plats/medplat1.wav";
1460 self.noise1 = "plats/medplat2.wav";
1467 self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1469 self.pos1 = '0 0 0';
1470 self.pos2 = self.movedir;
1472 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1473 // but spawn in the open position
1474 if (self.spawnflags & DOOR_START_OPEN)
1475 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1477 self.state = STATE_BOTTOM;
1481 self.takedamage = DAMAGE_YES;
1482 self.event_damage = door_damage;
1488 self.touch = door_touch;
1490 // LinkDoors can't be done until all of the doors have been spawned, so
1491 // the sizes can be detected properly.
1492 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1494 self.reset = door_rotating_reset;
1498 =============================================================================
1502 =============================================================================
1505 void() fd_secret_move1;
1506 void() fd_secret_move2;
1507 void() fd_secret_move3;
1508 void() fd_secret_move4;
1509 void() fd_secret_move5;
1510 void() fd_secret_move6;
1511 void() fd_secret_done;
1513 float SECRET_OPEN_ONCE = 1; // stays open
1514 float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
1515 float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
1516 float SECRET_NO_SHOOT = 8; // only opened by trigger
1517 float SECRET_YES_SHOOT = 16; // shootable even if targeted
1520 void fd_secret_use()
1523 string message_save;
1525 self.health = 10000;
1526 self.bot_attack = TRUE;
1528 // exit if still moving around...
1529 if (self.origin != self.oldorigin)
1532 message_save = self.message;
1533 self.message = ""; // no more message
1534 SUB_UseTargets(); // fire all targets / killtargets
1535 self.message = message_save;
1537 self.velocity = '0 0 0';
1539 // Make a sound, wait a little...
1541 if (self.noise1 != "")
1542 sound(self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
1543 self.nextthink = self.ltime + 0.1;
1545 temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1546 makevectors(self.mangle);
1550 if (self.spawnflags & SECRET_1ST_DOWN)
1551 self.t_width = fabs(v_up * self.size);
1553 self.t_width = fabs(v_right * self.size);
1557 self.t_length = fabs(v_forward * self.size);
1559 if (self.spawnflags & SECRET_1ST_DOWN)
1560 self.dest1 = self.origin - v_up * self.t_width;
1562 self.dest1 = self.origin + v_right * (self.t_width * temp);
1564 self.dest2 = self.dest1 + v_forward * self.t_length;
1565 SUB_CalcMove(self.dest1, self.speed, fd_secret_move1);
1566 if (self.noise2 != "")
1567 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1570 // Wait after first movement...
1571 void fd_secret_move1()
1573 self.nextthink = self.ltime + 1.0;
1574 self.think = fd_secret_move2;
1575 if (self.noise3 != "")
1576 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1579 // Start moving sideways w/sound...
1580 void fd_secret_move2()
1582 if (self.noise2 != "")
1583 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1584 SUB_CalcMove(self.dest2, self.speed, fd_secret_move3);
1587 // Wait here until time to go back...
1588 void fd_secret_move3()
1590 if (self.noise3 != "")
1591 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1592 if (!(self.spawnflags & SECRET_OPEN_ONCE))
1594 self.nextthink = self.ltime + self.wait;
1595 self.think = fd_secret_move4;
1600 void fd_secret_move4()
1602 if (self.noise2 != "")
1603 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1604 SUB_CalcMove(self.dest1, self.speed, fd_secret_move5);
1608 void fd_secret_move5()
1610 self.nextthink = self.ltime + 1.0;
1611 self.think = fd_secret_move6;
1612 if (self.noise3 != "")
1613 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1616 void fd_secret_move6()
1618 if (self.noise2 != "")
1619 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1620 SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done);
1623 void fd_secret_done()
1625 if (self.spawnflags&SECRET_YES_SHOOT)
1627 self.health = 10000;
1628 self.takedamage = DAMAGE_YES;
1629 //self.th_pain = fd_secret_use;
1631 if (self.noise3 != "")
1632 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1635 void secret_blocked()
1637 if (time < self.attack_finished_single)
1639 self.attack_finished_single = time + 0.5;
1640 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1652 if not(other.iscreature)
1654 if (self.attack_finished_single > time)
1657 self.attack_finished_single = time + 2;
1661 if (other.flags & FL_CLIENT)
1662 centerprint (other, self.message);
1663 play2(other, "misc/talk.wav");
1669 if (self.spawnflags&SECRET_YES_SHOOT)
1671 self.health = 10000;
1672 self.takedamage = DAMAGE_YES;
1674 setorigin(self, self.oldorigin);
1675 self.think = SUB_Null;
1678 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1679 Basic secret door. Slides back, then to the side. Angle determines direction.
1680 wait = # of seconds before coming back
1681 1st_left = 1st move is left of arrow
1682 1st_down = 1st move is down from arrow
1683 always_shoot = even if targeted, keep shootable
1684 t_width = override WIDTH to move back (or height if going down)
1685 t_length = override LENGTH to move sideways
1686 "dmg" damage to inflict when blocked (2 default)
1688 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1695 void spawnfunc_func_door_secret()
1697 /*if (!self.deathtype) // map makers can override this
1698 self.deathtype = " got in the way";*/
1704 self.mangle = self.angles;
1705 self.angles = '0 0 0';
1706 self.classname = "door";
1707 if not(InitMovingBrushTrigger())
1709 self.effects |= EF_LOWPRECISION;
1711 self.touch = secret_touch;
1712 self.blocked = secret_blocked;
1714 self.use = fd_secret_use;
1719 self.spawnflags |= SECRET_YES_SHOOT;
1721 if(self.spawnflags&SECRET_YES_SHOOT)
1723 self.health = 10000;
1724 self.takedamage = DAMAGE_YES;
1725 self.event_damage = fd_secret_use;
1727 self.oldorigin = self.origin;
1729 self.wait = 5; // 5 seconds before closing
1731 self.reset = secret_reset;
1735 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1736 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1737 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
1738 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1739 height: amplitude modifier (default 32)
1740 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1741 noise: path/name of looping .wav file to play.
1742 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1746 void func_fourier_controller_think()
1751 self.nextthink = time + 0.1;
1752 if not (self.owner.active == ACTIVE_ACTIVE)
1754 self.owner.velocity = '0 0 0';
1759 n = floor((tokenize_console(self.owner.netname)) / 5);
1760 t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1762 v = self.owner.destvec;
1764 for(i = 0; i < n; ++i)
1766 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1767 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;
1770 if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed
1771 // * 10 so it will arrive in 0.1 sec
1772 self.owner.velocity = (v - self.owner.origin) * 10;
1775 void spawnfunc_func_fourier()
1777 local entity controller;
1778 if (self.noise != "")
1780 precache_sound(self.noise);
1781 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
1788 self.destvec = self.origin;
1789 self.cnt = 360 / self.speed;
1791 self.blocked = generic_plat_blocked;
1792 if(self.dmg & (!self.message))
1793 self.message = " was squished";
1794 if(self.dmg && (!self.message2))
1795 self.message2 = "was squished by";
1796 if(self.dmg && (!self.dmgtime))
1797 self.dmgtime = 0.25;
1798 self.dmgtime2 = time;
1800 if(self.netname == "")
1801 self.netname = "1 0 0 0 1";
1803 if not(InitMovingBrushTrigger())
1806 self.active = ACTIVE_ACTIVE;
1808 // wait for targets to spawn
1809 controller = spawn();
1810 controller.classname = "func_fourier_controller";
1811 controller.owner = self;
1812 controller.nextthink = time + 1;
1813 controller.think = func_fourier_controller_think;
1814 self.nextthink = self.ltime + 999999999;
1815 self.think = SUB_Null;
1817 // Savage: Reduce bandwith, critical on e.g. nexdm02
1818 self.effects |= EF_LOWPRECISION;
1820 // TODO make a reset function for this one
1823 // reusing some fields havocbots declared
1824 .entity wp00, wp01, wp02, wp03;
1826 .float targetfactor, target2factor, target3factor, target4factor;
1827 .vector targetnormal, target2normal, target3normal, target4normal;
1829 vector func_vectormamamam_origin(entity o, float t)
1841 p = e.origin + t * e.velocity;
1843 v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
1845 v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
1851 p = e.origin + t * e.velocity;
1853 v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
1855 v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
1861 p = e.origin + t * e.velocity;
1863 v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
1865 v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
1871 p = e.origin + t * e.velocity;
1873 v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
1875 v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
1881 void func_vectormamamam_controller_think()
1883 self.nextthink = time + 0.1;
1885 if not (self.owner.active == ACTIVE_ACTIVE)
1887 self.owner.velocity = '0 0 0';
1891 if(self.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed
1892 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
1895 void func_vectormamamam_findtarget()
1897 if(self.target != "")
1898 self.wp00 = find(world, targetname, self.target);
1900 if(self.target2 != "")
1901 self.wp01 = find(world, targetname, self.target2);
1903 if(self.target3 != "")
1904 self.wp02 = find(world, targetname, self.target3);
1906 if(self.target4 != "")
1907 self.wp03 = find(world, targetname, self.target4);
1909 if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
1910 objerror("No reference entity found, so there is nothing to move. Aborting.");
1912 self.destvec = self.origin - func_vectormamamam_origin(self.owner, 0);
1914 local entity controller;
1915 controller = spawn();
1916 controller.classname = "func_vectormamamam_controller";
1917 controller.owner = self;
1918 controller.nextthink = time + 1;
1919 controller.think = func_vectormamamam_controller_think;
1922 void spawnfunc_func_vectormamamam()
1924 if (self.noise != "")
1926 precache_sound(self.noise);
1927 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
1930 if(!self.targetfactor)
1931 self.targetfactor = 1;
1933 if(!self.target2factor)
1934 self.target2factor = 1;
1936 if(!self.target3factor)
1937 self.target3factor = 1;
1939 if(!self.target4factor)
1940 self.target4factor = 1;
1942 if(vlen(self.targetnormal))
1943 self.targetnormal = normalize(self.targetnormal);
1945 if(vlen(self.target2normal))
1946 self.target2normal = normalize(self.target2normal);
1948 if(vlen(self.target3normal))
1949 self.target3normal = normalize(self.target3normal);
1951 if(vlen(self.target4normal))
1952 self.target4normal = normalize(self.target4normal);
1954 self.blocked = generic_plat_blocked;
1955 if(self.dmg & (!self.message))
1956 self.message = " was squished";
1957 if(self.dmg && (!self.message2))
1958 self.message2 = "was squished by";
1959 if(self.dmg && (!self.dmgtime))
1960 self.dmgtime = 0.25;
1961 self.dmgtime2 = time;
1963 if(self.netname == "")
1964 self.netname = "1 0 0 0 1";
1966 if not(InitMovingBrushTrigger())
1969 // wait for targets to spawn
1970 self.nextthink = self.ltime + 999999999;
1971 self.think = SUB_Null;
1973 // Savage: Reduce bandwith, critical on e.g. nexdm02
1974 self.effects |= EF_LOWPRECISION;
1976 self.active = ACTIVE_ACTIVE;
1978 InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);