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;
249 stopsoundto(MSG_BROADCAST, self, CHAN_TRIGGER); // send this as unreliable only, as the train will resume operation shortly anyway
257 self.think = train_next;
258 self.nextthink = self.ltime + self.wait;
272 targ = find(world, targetname, self.target);
274 self.target = targ.target;
276 objerror("train_next: no next target");
277 self.wait = targ.wait;
282 SUB_CalcMove(targ.origin - self.mins, targ.speed, train_wait);
284 SUB_CalcMove(targ.origin - self.mins, self.speed, train_wait);
287 sound(self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
290 void func_train_find()
293 targ = find(world, targetname, self.target);
294 self.target = targ.target;
296 objerror("func_train_find: no next target");
297 setorigin(self, targ.origin - self.mins);
298 self.nextthink = self.ltime + 1;
299 self.think = train_next;
302 /*QUAKED spawnfunc_func_train (0 .5 .8) ?
303 Ridable platform, targets spawnfunc_path_corner path to follow.
304 speed : speed the train moves (can be overridden by each spawnfunc_path_corner)
305 target : targetname of first spawnfunc_path_corner (starts here)
307 void spawnfunc_func_train()
309 if (self.noise != "")
310 precache_sound(self.noise);
313 objerror("func_train without a target");
317 if not(InitMovingBrushTrigger())
319 self.effects |= EF_LOWPRECISION;
321 // wait for targets to spawn
322 InitializeEntity(self, func_train_find, INITPRIO_SETLOCATION);
324 self.blocked = generic_plat_blocked;
325 if(self.dmg & (!self.message))
326 self.message = " was squished";
327 if(self.dmg && (!self.message2))
328 self.message2 = "was squished by";
329 if(self.dmg && (!self.dmgtime))
331 self.dmgtime2 = time;
333 // TODO make a reset function for this one
336 void func_rotating_setactive(float astate)
339 if (astate == ACTIVE_TOGGLE)
341 if(self.active == ACTIVE_ACTIVE)
342 self.active = ACTIVE_NOT;
344 self.active = ACTIVE_ACTIVE;
347 self.active = astate;
349 if(self.active == ACTIVE_NOT)
350 self.avelocity = '0 0 0';
352 self.avelocity = self.pos1;
355 /*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS
356 Brush model that spins in place on one axis (default Z).
357 speed : speed to rotate (in degrees per second)
358 noise : path/name of looping .wav file to play.
359 dmg : Do this mutch dmg every .dmgtime intervall when blocked
363 void spawnfunc_func_rotating()
365 if (self.noise != "")
367 precache_sound(self.noise);
368 ambientsound(self.origin, self.noise, VOL_BASE, ATTN_IDLE);
371 self.active = ACTIVE_ACTIVE;
372 self.setactive = func_rotating_setactive;
376 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
377 if (self.spawnflags & 4) // X (untested)
378 self.avelocity = '0 0 1' * self.speed;
379 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
380 else if (self.spawnflags & 8) // Y (untested)
381 self.avelocity = '1 0 0' * self.speed;
382 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
384 self.avelocity = '0 1 0' * self.speed;
386 self.pos1 = self.avelocity;
388 if(self.dmg & (!self.message))
389 self.message = " was squished";
390 if(self.dmg && (!self.message2))
391 self.message2 = "was squished by";
394 if(self.dmg && (!self.dmgtime))
397 self.dmgtime2 = time;
399 if not(InitMovingBrushTrigger())
401 // no EF_LOWPRECISION here, as rounding angles is bad
403 self.blocked = generic_plat_blocked;
405 // wait for targets to spawn
406 self.nextthink = self.ltime + 999999999;
407 self.think = SUB_Null;
409 // TODO make a reset function for this one
413 void func_bobbing_controller_think()
416 self.nextthink = time + 0.1;
418 if not (self.owner.active == ACTIVE_ACTIVE)
420 self.owner.velocity = '0 0 0';
424 // calculate sinewave using makevectors
425 makevectors((self.nextthink * self.owner.cnt + self.owner.phase * 360) * '0 1 0');
426 v = self.owner.destvec + self.owner.movedir * v_forward_y;
427 if(self.owner.classname == "func_bobbing") // don't brake stuff if the func_bobbing was killtarget'ed
428 // * 10 so it will arrive in 0.1 sec
429 self.owner.velocity = (v - self.owner.origin) * 10;
432 /*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
433 Brush model that moves back and forth on one axis (default Z).
434 speed : how long one cycle takes in seconds (default 4)
435 height : how far the cycle moves (default 32)
436 phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
437 noise : path/name of looping .wav file to play.
438 dmg : Do this mutch dmg every .dmgtime intervall when blocked
441 void spawnfunc_func_bobbing()
443 local entity controller;
444 if (self.noise != "")
446 precache_sound(self.noise);
447 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
453 // center of bobbing motion
454 self.destvec = self.origin;
455 // time scale to get degrees
456 self.cnt = 360 / self.speed;
458 self.active = ACTIVE_ACTIVE;
460 // damage when blocked
461 self.blocked = generic_plat_blocked;
462 if(self.dmg & (!self.message))
463 self.message = " was squished";
464 if(self.dmg && (!self.message2))
465 self.message2 = "was squished by";
466 if(self.dmg && (!self.dmgtime))
468 self.dmgtime2 = time;
471 if (self.spawnflags & 1) // X
472 self.movedir = '1 0 0' * self.height;
473 else if (self.spawnflags & 2) // Y
474 self.movedir = '0 1 0' * self.height;
476 self.movedir = '0 0 1' * self.height;
478 if not(InitMovingBrushTrigger())
481 // wait for targets to spawn
482 controller = spawn();
483 controller.classname = "func_bobbing_controller";
484 controller.owner = self;
485 controller.nextthink = time + 1;
486 controller.think = func_bobbing_controller_think;
487 self.nextthink = self.ltime + 999999999;
488 self.think = SUB_Null;
490 // Savage: Reduce bandwith, critical on e.g. nexdm02
491 self.effects |= EF_LOWPRECISION;
493 // TODO make a reset function for this one
497 void func_pendulum_controller_think()
500 self.nextthink = time + 0.1;
502 if not (self.owner.active == ACTIVE_ACTIVE)
504 self.owner.avelocity_x = 0;
508 // calculate sinewave using makevectors
509 makevectors((self.nextthink * self.owner.freq + self.owner.phase) * '0 360 0');
510 v = self.owner.speed * v_forward_y + self.cnt;
511 if(self.owner.classname == "func_pendulum") // don't brake stuff if the func_bobbing was killtarget'ed
513 // * 10 so it will arrive in 0.1 sec
514 self.owner.avelocity_z = (remainder(v - self.owner.angles_z, 360)) * 10;
518 void spawnfunc_func_pendulum()
520 local entity controller;
521 if (self.noise != "")
523 precache_sound(self.noise);
524 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
527 self.active = ACTIVE_ACTIVE;
529 // keys: angle, speed, phase, noise, freq
533 // not initializing self.dmg to 2, to allow damageless pendulum
535 if(self.dmg & (!self.message))
536 self.message = " was squished";
537 if(self.dmg && (!self.message2))
538 self.message2 = "was squished by";
539 if(self.dmg && (!self.dmgtime))
541 self.dmgtime2 = time;
543 self.blocked = generic_plat_blocked;
545 self.avelocity_z = 0.0000001;
546 if not(InitMovingBrushTrigger())
551 // find pendulum length (same formula as Q3A)
552 self.freq = 1 / (M_PI * 2) * sqrt(autocvar_sv_gravity / (3 * max(8, fabs(self.mins_z))));
555 // copy initial angle
556 self.cnt = self.angles_z;
558 // wait for targets to spawn
559 controller = spawn();
560 controller.classname = "func_pendulum_controller";
561 controller.owner = self;
562 controller.nextthink = time + 1;
563 controller.think = func_pendulum_controller_think;
564 self.nextthink = self.ltime + 999999999;
565 self.think = SUB_Null;
567 //self.effects |= EF_LOWPRECISION;
569 // TODO make a reset function for this one
572 // button and multiple button
575 void() button_return;
579 self.state = STATE_TOP;
580 self.nextthink = self.ltime + self.wait;
581 self.think = button_return;
582 activator = self.enemy;
584 self.frame = 1; // use alternate textures
589 self.state = STATE_BOTTOM;
594 self.state = STATE_DOWN;
595 SUB_CalcMove (self.pos1, self.speed, button_done);
596 self.frame = 0; // use normal textures
598 self.takedamage = DAMAGE_YES; // can be shot again
602 void button_blocked()
604 // do nothing, just don't come all the way back out
610 self.health = self.max_health;
611 self.takedamage = DAMAGE_NO; // will be reset upon return
613 if (self.state == STATE_UP || self.state == STATE_TOP)
616 if (self.noise != "")
617 sound (self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
619 self.state = STATE_UP;
620 SUB_CalcMove (self.pos2, self.speed, button_wait);
625 self.health = self.max_health;
626 setorigin(self, self.pos1);
627 self.frame = 0; // use normal textures
628 self.state = STATE_BOTTOM;
630 self.takedamage = DAMAGE_YES; // can be shot again
635 // if (activator.classname != "player")
637 // dprint(activator.classname);
638 // dprint(" triggered a button\n");
641 if not (self.active == ACTIVE_ACTIVE)
644 self.enemy = activator;
650 // if (activator.classname != "player")
652 // dprint(activator.classname);
653 // dprint(" touched a button\n");
657 if not(other.iscreature)
659 if(other.velocity * self.movedir < 0)
663 self.enemy = other.owner;
667 void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
669 if(self.spawnflags & DOOR_NOSPLASH)
670 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
672 self.health = self.health - damage;
673 if (self.health <= 0)
675 // if (activator.classname != "player")
677 // dprint(activator.classname);
678 // dprint(" killed a button\n");
680 self.enemy = damage_attacker;
686 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
687 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.
689 "angle" determines the opening direction
690 "target" all entities with a matching targetname will be used
691 "speed" override the default 40 speed
692 "wait" override the default 1 second wait (-1 = never return)
693 "lip" override the default 4 pixel lip remaining at end of move
694 "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
701 void spawnfunc_func_button()
705 if not(InitMovingBrushTrigger())
707 self.effects |= EF_LOWPRECISION;
709 self.blocked = button_blocked;
710 self.use = button_use;
712 // if (self.health == 0) // all buttons are now shootable
716 self.max_health = self.health;
717 self.event_damage = button_damage;
718 self.takedamage = DAMAGE_YES;
721 self.touch = button_touch;
731 precache_sound(self.noise);
733 self.active = ACTIVE_ACTIVE;
735 self.pos1 = self.origin;
736 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
737 self.flags |= FL_NOTARGET;
743 float DOOR_START_OPEN = 1;
744 float DOOR_DONT_LINK = 4;
745 float DOOR_TOGGLE = 32;
749 Doors are similar to buttons, but can spawn a fat trigger field around them
750 to open without a touch, and they link together to form simultanious
753 Door.owner is the master door. If there is only one door, it points to itself.
754 If multiple doors, all will point to a single one.
756 Door.enemy chains from the master door through all doors linked in the chain.
761 =============================================================================
765 =============================================================================
770 void() door_rotating_go_down;
771 void() door_rotating_go_up;
776 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
777 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
780 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
781 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
783 //Dont chamge direction for dead or dying stuff
784 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
787 if (self.state == STATE_DOWN)
788 if (self.classname == "door")
793 door_rotating_go_up ();
796 if (self.classname == "door")
801 door_rotating_go_down ();
805 //gib dying stuff just to make sure
806 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
807 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
811 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
812 // if a door has a negative wait, it would never come back if blocked,
813 // so let it just squash the object to death real fast
814 /* if (self.wait >= 0)
816 if (self.state == STATE_DOWN)
827 if (self.noise1 != "")
828 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
829 self.state = STATE_TOP;
830 if (self.spawnflags & DOOR_TOGGLE)
831 return; // don't come down automatically
832 if (self.classname == "door")
834 self.think = door_go_down;
837 self.think = door_rotating_go_down;
839 self.nextthink = self.ltime + self.wait;
842 void door_hit_bottom()
844 if (self.noise1 != "")
845 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
846 self.state = STATE_BOTTOM;
851 if (self.noise2 != "")
852 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
855 self.takedamage = DAMAGE_YES;
856 self.health = self.max_health;
859 self.state = STATE_DOWN;
860 SUB_CalcMove (self.pos1, self.speed, door_hit_bottom);
865 if (self.state == STATE_UP)
866 return; // already going up
868 if (self.state == STATE_TOP)
869 { // reset top wait time
870 self.nextthink = self.ltime + self.wait;
874 if (self.noise2 != "")
875 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
876 self.state = STATE_UP;
877 SUB_CalcMove (self.pos2, self.speed, door_hit_top);
880 oldmessage = self.message;
883 self.message = oldmessage;
888 =============================================================================
892 =============================================================================
900 if (self.owner != self)
901 objerror ("door_fire: self.owner != self");
905 if (self.spawnflags & DOOR_TOGGLE)
907 if (self.state == STATE_UP || self.state == STATE_TOP)
912 if (self.classname == "door")
918 door_rotating_go_down ();
921 } while ( (self != starte) && (self != world) );
927 // trigger all paired doors
931 if (self.classname == "door")
936 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
937 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
939 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
940 self.pos2 = '0 0 0' - self.pos2;
942 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
943 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
944 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
946 door_rotating_go_up ();
950 } while ( (self != starte) && (self != world) );
959 print(sprintf("door_use: self=%s owner=%s fire\n", self.targetname, self.owner.targetname));
961 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
972 void door_trigger_touch()
974 if (other.health < 1)
975 if not(other.iscreature && other.deadflag == DEAD_NO)
978 if (time < self.attack_finished_single)
980 self.attack_finished_single = time + 1;
989 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
992 if(self.spawnflags & DOOR_NOSPLASH)
993 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
995 self.health = self.health - damage;
996 if (self.health <= 0)
1000 self.health = self.max_health;
1001 self.takedamage = DAMAGE_NO; // wil be reset upon return
1017 if(other.classname != "player")
1019 if (self.owner.attack_finished_single > time)
1022 self.owner.attack_finished_single = time + 2;
1024 if (!(self.owner.dmg) && (self.owner.message != ""))
1026 if (other.flags & FL_CLIENT)
1027 centerprint (other, self.owner.message);
1028 play2(other, "misc/talk.wav");
1033 void door_generic_plat_blocked()
1036 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
1037 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1040 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
1041 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1043 //Dont chamge direction for dead or dying stuff
1044 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
1047 if (self.state == STATE_DOWN)
1048 door_rotating_go_up ();
1050 door_rotating_go_down ();
1053 //gib dying stuff just to make sure
1054 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
1055 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1059 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1060 // if a door has a negative wait, it would never come back if blocked,
1061 // so let it just squash the object to death real fast
1062 /* if (self.wait >= 0)
1064 if (self.state == STATE_DOWN)
1065 door_rotating_go_up ();
1067 door_rotating_go_down ();
1073 void door_rotating_hit_top()
1075 if (self.noise1 != "")
1076 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
1077 self.state = STATE_TOP;
1078 if (self.spawnflags & DOOR_TOGGLE)
1079 return; // don't come down automatically
1080 self.think = door_rotating_go_down;
1081 self.nextthink = self.ltime + self.wait;
1084 void door_rotating_hit_bottom()
1086 if (self.noise1 != "")
1087 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
1088 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
1090 self.pos2 = '0 0 0' - self.pos2;
1093 self.state = STATE_BOTTOM;
1096 void door_rotating_go_down()
1098 if (self.noise2 != "")
1099 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1100 if (self.max_health)
1102 self.takedamage = DAMAGE_YES;
1103 self.health = self.max_health;
1106 self.state = STATE_DOWN;
1107 SUB_CalcAngleMove (self.pos1, self.speed, door_rotating_hit_bottom);
1110 void door_rotating_go_up()
1112 if (self.state == STATE_UP)
1113 return; // already going up
1115 if (self.state == STATE_TOP)
1116 { // reset top wait time
1117 self.nextthink = self.ltime + self.wait;
1120 if (self.noise2 != "")
1121 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1122 self.state = STATE_UP;
1123 SUB_CalcAngleMove (self.pos2, self.speed, door_rotating_hit_top);
1126 oldmessage = self.message;
1129 self.message = oldmessage;
1136 =============================================================================
1140 =============================================================================
1144 entity spawn_field(vector fmins, vector fmaxs)
1146 local entity trigger;
1147 local vector t1, t2;
1150 trigger.classname = "doortriggerfield";
1151 trigger.movetype = MOVETYPE_NONE;
1152 trigger.solid = SOLID_TRIGGER;
1153 trigger.owner = self;
1154 trigger.touch = door_trigger_touch;
1158 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1163 float EntitiesTouching(entity e1, entity e2)
1165 if (e1.absmin_x > e2.absmax_x)
1167 if (e1.absmin_y > e2.absmax_y)
1169 if (e1.absmin_z > e2.absmax_z)
1171 if (e1.absmax_x < e2.absmin_x)
1173 if (e1.absmax_y < e2.absmin_y)
1175 if (e1.absmax_z < e2.absmin_z)
1190 local entity t, starte;
1191 local vector cmins, cmaxs;
1194 return; // already linked by another door
1195 if (self.spawnflags & 4)
1197 self.owner = self.enemy = self;
1205 self.trigger_field = spawn_field(self.absmin, self.absmax);
1207 return; // don't want to link this door
1210 cmins = self.absmin;
1211 cmaxs = self.absmax;
1218 self.owner = starte; // master door
1221 starte.health = self.health;
1223 starte.targetname = self.targetname;
1224 if (self.message != "")
1225 starte.message = self.message;
1227 t = find(t, classname, self.classname);
1230 self.enemy = starte; // make the chain a loop
1232 // shootable, or triggered doors just needed the owner/enemy links,
1233 // they don't spawn a field
1244 self.owner.trigger_field = spawn_field(cmins, cmaxs);
1249 if (EntitiesTouching(self,t))
1252 objerror ("cross connected doors");
1257 if (t.absmin_x < cmins_x)
1258 cmins_x = t.absmin_x;
1259 if (t.absmin_y < cmins_y)
1260 cmins_y = t.absmin_y;
1261 if (t.absmin_z < cmins_z)
1262 cmins_z = t.absmin_z;
1263 if (t.absmax_x > cmaxs_x)
1264 cmaxs_x = t.absmax_x;
1265 if (t.absmax_y > cmaxs_y)
1266 cmaxs_y = t.absmax_y;
1267 if (t.absmax_z > cmaxs_z)
1268 cmaxs_z = t.absmax_z;
1275 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK x x TOGGLE
1276 if two doors touch, they are assumed to be connected and operate as a unit.
1278 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1280 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).
1282 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1283 "angle" determines the opening direction
1284 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1285 "health" if set, door must be shot open
1286 "speed" movement speed (100 default)
1287 "wait" wait before returning (3 default, -1 = never return)
1288 "lip" lip remaining at end of move (8 default)
1289 "dmg" damage to inflict when blocked (2 default)
1296 FIXME: only one sound set available at the time being
1300 void door_init_startopen()
1302 setorigin (self, self.pos2);
1303 self.pos2 = self.pos1;
1304 self.pos1 = self.origin;
1309 setorigin(self, self.pos1);
1310 self.velocity = '0 0 0';
1311 self.state = STATE_BOTTOM;
1312 self.think = SUB_Null;
1315 void spawnfunc_func_door()
1317 //if (!self.deathtype) // map makers can override this
1318 // self.deathtype = " got in the way";
1321 self.max_health = self.health;
1322 if not(InitMovingBrushTrigger())
1324 self.effects |= EF_LOWPRECISION;
1325 self.classname = "door";
1327 self.blocked = door_blocked;
1328 self.use = door_use;
1330 if(self.spawnflags & 8)
1333 if(self.dmg && (!self.message))
1334 self.message = "was squished";
1335 if(self.dmg && (!self.message2))
1336 self.message2 = "was squished by";
1338 if (self.sounds > 0)
1340 precache_sound ("plats/medplat1.wav");
1341 precache_sound ("plats/medplat2.wav");
1342 self.noise2 = "plats/medplat1.wav";
1343 self.noise1 = "plats/medplat2.wav";
1353 self.pos1 = self.origin;
1354 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1356 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1357 // but spawn in the open position
1358 if (self.spawnflags & DOOR_START_OPEN)
1359 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1361 self.state = STATE_BOTTOM;
1365 self.takedamage = DAMAGE_YES;
1366 self.event_damage = door_damage;
1372 self.touch = door_touch;
1374 // LinkDoors can't be done until all of the doors have been spawned, so
1375 // the sizes can be detected properly.
1376 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1378 self.reset = door_reset;
1381 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1382 if two doors touch, they are assumed to be connected and operate as a unit.
1384 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1386 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1387 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1388 must have set trigger_reverse to 1.
1389 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1391 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).
1393 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1394 "angle" determines the destination angle for opening. negative values reverse the direction.
1395 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1396 "health" if set, door must be shot open
1397 "speed" movement speed (100 default)
1398 "wait" wait before returning (3 default, -1 = never return)
1399 "dmg" damage to inflict when blocked (2 default)
1406 FIXME: only one sound set available at the time being
1409 void door_rotating_reset()
1411 self.angles = self.pos1;
1412 self.avelocity = '0 0 0';
1413 self.state = STATE_BOTTOM;
1414 self.think = SUB_Null;
1417 void door_rotating_init_startopen()
1419 self.angles = self.movedir;
1420 self.pos2 = '0 0 0';
1421 self.pos1 = self.movedir;
1425 void spawnfunc_func_door_rotating()
1428 //if (!self.deathtype) // map makers can override this
1429 // self.deathtype = " got in the way";
1431 // I abuse "movedir" for denoting the axis for now
1432 if (self.spawnflags & 64) // X (untested)
1433 self.movedir = '0 0 1';
1434 else if (self.spawnflags & 128) // Y (untested)
1435 self.movedir = '1 0 0';
1437 self.movedir = '0 1 0';
1439 if (self.angles_y==0) self.angles_y = 90;
1441 self.movedir = self.movedir * self.angles_y;
1442 self.angles = '0 0 0';
1444 self.max_health = self.health;
1445 self.avelocity = self.movedir;
1446 if not(InitMovingBrushTrigger())
1448 self.velocity = '0 0 0';
1449 //self.effects |= EF_LOWPRECISION;
1450 self.classname = "door_rotating";
1452 self.blocked = door_blocked;
1453 self.use = door_use;
1455 if(self.spawnflags & 8)
1458 if(self.dmg && (!self.message))
1459 self.message = "was squished";
1460 if(self.dmg && (!self.message2))
1461 self.message2 = "was squished by";
1463 if (self.sounds > 0)
1465 precache_sound ("plats/medplat1.wav");
1466 precache_sound ("plats/medplat2.wav");
1467 self.noise2 = "plats/medplat1.wav";
1468 self.noise1 = "plats/medplat2.wav";
1475 self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1477 self.pos1 = '0 0 0';
1478 self.pos2 = self.movedir;
1480 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1481 // but spawn in the open position
1482 if (self.spawnflags & DOOR_START_OPEN)
1483 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1485 self.state = STATE_BOTTOM;
1489 self.takedamage = DAMAGE_YES;
1490 self.event_damage = door_damage;
1496 self.touch = door_touch;
1498 // LinkDoors can't be done until all of the doors have been spawned, so
1499 // the sizes can be detected properly.
1500 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1502 self.reset = door_rotating_reset;
1506 =============================================================================
1510 =============================================================================
1513 void() fd_secret_move1;
1514 void() fd_secret_move2;
1515 void() fd_secret_move3;
1516 void() fd_secret_move4;
1517 void() fd_secret_move5;
1518 void() fd_secret_move6;
1519 void() fd_secret_done;
1521 float SECRET_OPEN_ONCE = 1; // stays open
1522 float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
1523 float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
1524 float SECRET_NO_SHOOT = 8; // only opened by trigger
1525 float SECRET_YES_SHOOT = 16; // shootable even if targeted
1528 void fd_secret_use()
1531 string message_save;
1533 self.health = 10000;
1534 self.bot_attack = TRUE;
1536 // exit if still moving around...
1537 if (self.origin != self.oldorigin)
1540 message_save = self.message;
1541 self.message = ""; // no more message
1542 SUB_UseTargets(); // fire all targets / killtargets
1543 self.message = message_save;
1545 self.velocity = '0 0 0';
1547 // Make a sound, wait a little...
1549 if (self.noise1 != "")
1550 sound(self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
1551 self.nextthink = self.ltime + 0.1;
1553 temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1554 makevectors(self.mangle);
1558 if (self.spawnflags & SECRET_1ST_DOWN)
1559 self.t_width = fabs(v_up * self.size);
1561 self.t_width = fabs(v_right * self.size);
1565 self.t_length = fabs(v_forward * self.size);
1567 if (self.spawnflags & SECRET_1ST_DOWN)
1568 self.dest1 = self.origin - v_up * self.t_width;
1570 self.dest1 = self.origin + v_right * (self.t_width * temp);
1572 self.dest2 = self.dest1 + v_forward * self.t_length;
1573 SUB_CalcMove(self.dest1, self.speed, fd_secret_move1);
1574 if (self.noise2 != "")
1575 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1578 // Wait after first movement...
1579 void fd_secret_move1()
1581 self.nextthink = self.ltime + 1.0;
1582 self.think = fd_secret_move2;
1583 if (self.noise3 != "")
1584 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1587 // Start moving sideways w/sound...
1588 void fd_secret_move2()
1590 if (self.noise2 != "")
1591 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1592 SUB_CalcMove(self.dest2, self.speed, fd_secret_move3);
1595 // Wait here until time to go back...
1596 void fd_secret_move3()
1598 if (self.noise3 != "")
1599 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1600 if (!(self.spawnflags & SECRET_OPEN_ONCE))
1602 self.nextthink = self.ltime + self.wait;
1603 self.think = fd_secret_move4;
1608 void fd_secret_move4()
1610 if (self.noise2 != "")
1611 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1612 SUB_CalcMove(self.dest1, self.speed, fd_secret_move5);
1616 void fd_secret_move5()
1618 self.nextthink = self.ltime + 1.0;
1619 self.think = fd_secret_move6;
1620 if (self.noise3 != "")
1621 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1624 void fd_secret_move6()
1626 if (self.noise2 != "")
1627 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1628 SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done);
1631 void fd_secret_done()
1633 if (self.spawnflags&SECRET_YES_SHOOT)
1635 self.health = 10000;
1636 self.takedamage = DAMAGE_YES;
1637 //self.th_pain = fd_secret_use;
1639 if (self.noise3 != "")
1640 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1643 void secret_blocked()
1645 if (time < self.attack_finished_single)
1647 self.attack_finished_single = time + 0.5;
1648 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1660 if not(other.iscreature)
1662 if (self.attack_finished_single > time)
1665 self.attack_finished_single = time + 2;
1669 if (other.flags & FL_CLIENT)
1670 centerprint (other, self.message);
1671 play2(other, "misc/talk.wav");
1677 if (self.spawnflags&SECRET_YES_SHOOT)
1679 self.health = 10000;
1680 self.takedamage = DAMAGE_YES;
1682 setorigin(self, self.oldorigin);
1683 self.think = SUB_Null;
1686 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1687 Basic secret door. Slides back, then to the side. Angle determines direction.
1688 wait = # of seconds before coming back
1689 1st_left = 1st move is left of arrow
1690 1st_down = 1st move is down from arrow
1691 always_shoot = even if targeted, keep shootable
1692 t_width = override WIDTH to move back (or height if going down)
1693 t_length = override LENGTH to move sideways
1694 "dmg" damage to inflict when blocked (2 default)
1696 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1703 void spawnfunc_func_door_secret()
1705 /*if (!self.deathtype) // map makers can override this
1706 self.deathtype = " got in the way";*/
1712 self.mangle = self.angles;
1713 self.angles = '0 0 0';
1714 self.classname = "door";
1715 if not(InitMovingBrushTrigger())
1717 self.effects |= EF_LOWPRECISION;
1719 self.touch = secret_touch;
1720 self.blocked = secret_blocked;
1722 self.use = fd_secret_use;
1727 self.spawnflags |= SECRET_YES_SHOOT;
1729 if(self.spawnflags&SECRET_YES_SHOOT)
1731 self.health = 10000;
1732 self.takedamage = DAMAGE_YES;
1733 self.event_damage = fd_secret_use;
1735 self.oldorigin = self.origin;
1737 self.wait = 5; // 5 seconds before closing
1739 self.reset = secret_reset;
1743 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1744 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1745 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
1746 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1747 height: amplitude modifier (default 32)
1748 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1749 noise: path/name of looping .wav file to play.
1750 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1754 void func_fourier_controller_think()
1759 self.nextthink = time + 0.1;
1760 if not (self.owner.active == ACTIVE_ACTIVE)
1762 self.owner.velocity = '0 0 0';
1767 n = floor((tokenize_console(self.owner.netname)) / 5);
1768 t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1770 v = self.owner.destvec;
1772 for(i = 0; i < n; ++i)
1774 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1775 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;
1778 if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed
1779 // * 10 so it will arrive in 0.1 sec
1780 self.owner.velocity = (v - self.owner.origin) * 10;
1783 void spawnfunc_func_fourier()
1785 local entity controller;
1786 if (self.noise != "")
1788 precache_sound(self.noise);
1789 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
1796 self.destvec = self.origin;
1797 self.cnt = 360 / self.speed;
1799 self.blocked = generic_plat_blocked;
1800 if(self.dmg & (!self.message))
1801 self.message = " was squished";
1802 if(self.dmg && (!self.message2))
1803 self.message2 = "was squished by";
1804 if(self.dmg && (!self.dmgtime))
1805 self.dmgtime = 0.25;
1806 self.dmgtime2 = time;
1808 if(self.netname == "")
1809 self.netname = "1 0 0 0 1";
1811 if not(InitMovingBrushTrigger())
1814 self.active = ACTIVE_ACTIVE;
1816 // wait for targets to spawn
1817 controller = spawn();
1818 controller.classname = "func_fourier_controller";
1819 controller.owner = self;
1820 controller.nextthink = time + 1;
1821 controller.think = func_fourier_controller_think;
1822 self.nextthink = self.ltime + 999999999;
1823 self.think = SUB_Null;
1825 // Savage: Reduce bandwith, critical on e.g. nexdm02
1826 self.effects |= EF_LOWPRECISION;
1828 // TODO make a reset function for this one
1831 // reusing some fields havocbots declared
1832 .entity wp00, wp01, wp02, wp03;
1834 .float targetfactor, target2factor, target3factor, target4factor;
1835 .vector targetnormal, target2normal, target3normal, target4normal;
1837 vector func_vectormamamam_origin(entity o, float t)
1849 p = e.origin + t * e.velocity;
1851 v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
1853 v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
1859 p = e.origin + t * e.velocity;
1861 v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
1863 v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
1869 p = e.origin + t * e.velocity;
1871 v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
1873 v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
1879 p = e.origin + t * e.velocity;
1881 v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
1883 v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
1889 void func_vectormamamam_controller_think()
1891 self.nextthink = time + 0.1;
1893 if not (self.owner.active == ACTIVE_ACTIVE)
1895 self.owner.velocity = '0 0 0';
1899 if(self.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed
1900 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
1903 void func_vectormamamam_findtarget()
1905 if(self.target != "")
1906 self.wp00 = find(world, targetname, self.target);
1908 if(self.target2 != "")
1909 self.wp01 = find(world, targetname, self.target2);
1911 if(self.target3 != "")
1912 self.wp02 = find(world, targetname, self.target3);
1914 if(self.target4 != "")
1915 self.wp03 = find(world, targetname, self.target4);
1917 if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
1918 objerror("No reference entity found, so there is nothing to move. Aborting.");
1920 self.destvec = self.origin - func_vectormamamam_origin(self.owner, 0);
1922 local entity controller;
1923 controller = spawn();
1924 controller.classname = "func_vectormamamam_controller";
1925 controller.owner = self;
1926 controller.nextthink = time + 1;
1927 controller.think = func_vectormamamam_controller_think;
1930 void spawnfunc_func_vectormamamam()
1932 if (self.noise != "")
1934 precache_sound(self.noise);
1935 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
1938 if(!self.targetfactor)
1939 self.targetfactor = 1;
1941 if(!self.target2factor)
1942 self.target2factor = 1;
1944 if(!self.target3factor)
1945 self.target3factor = 1;
1947 if(!self.target4factor)
1948 self.target4factor = 1;
1950 if(vlen(self.targetnormal))
1951 self.targetnormal = normalize(self.targetnormal);
1953 if(vlen(self.target2normal))
1954 self.target2normal = normalize(self.target2normal);
1956 if(vlen(self.target3normal))
1957 self.target3normal = normalize(self.target3normal);
1959 if(vlen(self.target4normal))
1960 self.target4normal = normalize(self.target4normal);
1962 self.blocked = generic_plat_blocked;
1963 if(self.dmg & (!self.message))
1964 self.message = " was squished";
1965 if(self.dmg && (!self.message2))
1966 self.message2 = "was squished by";
1967 if(self.dmg && (!self.dmgtime))
1968 self.dmgtime = 0.25;
1969 self.dmgtime2 = time;
1971 if(self.netname == "")
1972 self.netname = "1 0 0 0 1";
1974 if not(InitMovingBrushTrigger())
1977 // wait for targets to spawn
1978 self.nextthink = self.ltime + 999999999;
1979 self.think = SUB_Null;
1981 // Savage: Reduce bandwith, critical on e.g. nexdm02
1982 self.effects |= EF_LOWPRECISION;
1984 self.active = ACTIVE_ACTIVE;
1986 InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);