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 void plat_init_movedown()
158 setorigin (self, self.pos2);
162 .string sound1, sound2;
164 void spawnfunc_path_corner() { };
165 void spawnfunc_func_plat()
172 if (self.sounds == 0)
175 if(self.spawnflags & 4)
178 if(self.dmg && (!self.message))
179 self.message = "was squished";
180 if(self.dmg && (!self.message2))
181 self.message2 = "was squished by";
183 if (self.sounds == 1)
185 precache_sound ("plats/plat1.wav");
186 precache_sound ("plats/plat2.wav");
187 self.noise = "plats/plat1.wav";
188 self.noise1 = "plats/plat2.wav";
191 if (self.sounds == 2)
193 precache_sound ("plats/medplat1.wav");
194 precache_sound ("plats/medplat2.wav");
195 self.noise = "plats/medplat1.wav";
196 self.noise1 = "plats/medplat2.wav";
201 precache_sound (self.sound1);
202 self.noise = self.sound1;
206 precache_sound (self.sound2);
207 self.noise1 = self.sound2;
210 self.mangle = self.angles;
211 self.angles = '0 0 0';
213 self.classname = "plat";
214 if not(InitMovingBrushTrigger())
216 self.effects |= EF_LOWPRECISION;
217 setsize (self, self.mins , self.maxs);
219 self.blocked = plat_crush;
224 self.pos1 = self.origin;
225 self.pos2 = self.origin;
226 self.pos2_z = self.origin_z - self.size_z + 8;
228 self.use = plat_trigger_use;
230 plat_spawn_inside_trigger (); // the "start moving" trigger
238 InitializeEntity(self, plat_init_movedown, INITPRIO_SETLOCATION);
245 self.think = train_next;
246 self.nextthink = self.ltime + self.wait;
249 sound(self, CHAN_TRIGGER, STR_MISC_NULL_WAV, VOL_BASE, ATTN_NORM);
255 targ = find(world, targetname, self.target);
256 self.target = targ.target;
258 objerror("train_next: no next target");
259 self.wait = targ.wait;
265 SUB_CalcMove(targ.origin - self.mins, targ.speed, train_next);
267 SUB_CalcMove(targ.origin - self.mins, self.speed, train_next);
272 SUB_CalcMove(targ.origin - self.mins, targ.speed, train_wait);
274 SUB_CalcMove(targ.origin - self.mins, self.speed, train_wait);
278 sound(self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
281 void func_train_find()
284 targ = find(world, targetname, self.target);
285 self.target = targ.target;
287 objerror("func_train_find: no next target");
288 setorigin(self, targ.origin - self.mins);
289 self.nextthink = self.ltime + 1;
290 self.think = train_next;
293 /*QUAKED spawnfunc_func_train (0 .5 .8) ?
294 Ridable platform, targets spawnfunc_path_corner path to follow.
295 speed : speed the train moves (can be overridden by each spawnfunc_path_corner)
296 target : targetname of first spawnfunc_path_corner (starts here)
298 void spawnfunc_func_train()
300 if (self.noise != "")
301 precache_sound(self.noise);
304 objerror("func_train without a target");
308 if not(InitMovingBrushTrigger())
310 self.effects |= EF_LOWPRECISION;
312 // wait for targets to spawn
313 InitializeEntity(self, func_train_find, INITPRIO_SETLOCATION);
315 self.blocked = generic_plat_blocked;
316 if(self.dmg & (!self.message))
317 self.message = " was squished";
318 if(self.dmg && (!self.message2))
319 self.message2 = "was squished by";
320 if(self.dmg && (!self.dmgtime))
322 self.dmgtime2 = time;
325 /*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS
326 Brush model that spins in place on one axis (default Z).
327 speed : speed to rotate (in degrees per second)
328 noise : path/name of looping .wav file to play.
329 dmg : Do this mutch dmg every .dmgtime intervall when blocked
333 void spawnfunc_func_rotating()
335 if (self.noise != "")
337 precache_sound(self.noise);
338 ambientsound(self.origin, self.noise, VOL_BASE, ATTN_IDLE);
342 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
343 if (self.spawnflags & 4) // X (untested)
344 self.avelocity = '0 0 1' * self.speed;
345 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
346 else if (self.spawnflags & 8) // Y (untested)
347 self.avelocity = '1 0 0' * self.speed;
348 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
350 self.avelocity = '0 1 0' * self.speed;
352 if(self.dmg & (!self.message))
353 self.message = " was squished";
354 if(self.dmg && (!self.message2))
355 self.message2 = "was squished by";
358 if(self.dmg && (!self.dmgtime))
361 self.dmgtime2 = time;
363 if not(InitMovingBrushTrigger())
365 // no EF_LOWPRECISION here, as rounding angles is bad
367 self.blocked = generic_plat_blocked;
369 // wait for targets to spawn
370 self.nextthink = self.ltime + 999999999;
371 self.think = SUB_Null;
375 void func_bobbing_controller_think()
378 self.nextthink = time + 0.1;
379 // calculate sinewave using makevectors
380 makevectors((self.nextthink * self.owner.cnt + self.owner.phase * 360) * '0 1 0');
381 v = self.owner.destvec + self.owner.movedir * v_forward_y;
382 // * 10 so it will arrive in 0.1 sec
383 self.owner.velocity = (v - self.owner.origin) * 10;
386 void bobbing_blocked()
388 // no need to duplicate code
389 generic_plat_blocked();
392 /*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
393 Brush model that moves back and forth on one axis (default Z).
394 speed : how long one cycle takes in seconds (default 4)
395 height : how far the cycle moves (default 32)
396 phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
397 noise : path/name of looping .wav file to play.
398 dmg : Do this mutch dmg every .dmgtime intervall when blocked
401 void spawnfunc_func_bobbing()
403 local entity controller;
404 if (self.noise != "")
406 precache_sound(self.noise);
407 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
413 // center of bobbing motion
414 self.destvec = self.origin;
415 // time scale to get degrees
416 self.cnt = 360 / self.speed;
418 // damage when blocked
419 self.blocked = bobbing_blocked;
420 if(self.dmg & (!self.message))
421 self.message = " was squished";
422 if(self.dmg && (!self.message2))
423 self.message2 = "was squished by";
424 if(self.dmg && (!self.dmgtime))
426 self.dmgtime2 = time;
429 if (self.spawnflags & 1) // X
430 self.movedir = '1 0 0' * self.height;
431 else if (self.spawnflags & 2) // Y
432 self.movedir = '0 1 0' * self.height;
434 self.movedir = '0 0 1' * self.height;
436 if not(InitMovingBrushTrigger())
439 // wait for targets to spawn
440 controller = spawn();
441 controller.classname = "func_bobbing_controller";
442 controller.owner = self;
443 controller.nextthink = time + 1;
444 controller.think = func_bobbing_controller_think;
445 self.nextthink = self.ltime + 999999999;
446 self.think = SUB_Null;
448 // Savage: Reduce bandwith, critical on e.g. nexdm02
449 self.effects |= EF_LOWPRECISION;
452 // button and multiple button
455 void() button_return;
459 self.state = STATE_TOP;
460 self.nextthink = self.ltime + self.wait;
461 self.think = button_return;
462 activator = self.enemy;
464 self.frame = 1; // use alternate textures
469 self.state = STATE_BOTTOM;
474 self.state = STATE_DOWN;
475 SUB_CalcMove (self.pos1, self.speed, button_done);
476 self.frame = 0; // use normal textures
478 self.takedamage = DAMAGE_YES; // can be shot again
482 void button_blocked()
484 // do nothing, just don't come all the way back out
490 self.health = self.max_health;
491 self.takedamage = DAMAGE_NO; // will be reset upon return
493 if (self.state == STATE_UP || self.state == STATE_TOP)
496 if (self.noise != "")
497 sound (self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
499 self.state = STATE_UP;
500 SUB_CalcMove (self.pos2, self.speed, button_wait);
506 // if (activator.classname != "player")
508 // dprint(activator.classname);
509 // dprint(" triggered a button\n");
511 self.enemy = activator;
517 // if (activator.classname != "player")
519 // dprint(activator.classname);
520 // dprint(" touched a button\n");
524 if not(other.iscreature)
526 if(other.velocity * self.movedir < 0)
530 self.enemy = other.owner;
534 void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
536 self.health = self.health - damage;
537 if (self.health <= 0)
539 // if (activator.classname != "player")
541 // dprint(activator.classname);
542 // dprint(" killed a button\n");
544 self.enemy = damage_attacker;
550 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
551 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.
553 "angle" determines the opening direction
554 "target" all entities with a matching targetname will be used
555 "speed" override the default 40 speed
556 "wait" override the default 1 second wait (-1 = never return)
557 "lip" override the default 4 pixel lip remaining at end of move
558 "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
565 void spawnfunc_func_button()
569 if not(InitMovingBrushTrigger())
571 self.effects |= EF_LOWPRECISION;
573 self.blocked = button_blocked;
574 self.use = button_use;
576 // if (self.health == 0) // all buttons are now shootable
580 self.max_health = self.health;
581 self.event_damage = button_damage;
582 self.takedamage = DAMAGE_YES;
585 self.touch = button_touch;
595 precache_sound(self.noise);
597 self.state = STATE_BOTTOM;
599 self.pos1 = self.origin;
600 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
604 float DOOR_START_OPEN = 1;
605 float DOOR_DONT_LINK = 4;
606 float DOOR_TOGGLE = 32;
610 Doors are similar to buttons, but can spawn a fat trigger field around them
611 to open without a touch, and they link together to form simultanious
614 Door.owner is the master door. If there is only one door, it points to itself.
615 If multiple doors, all will point to a single one.
617 Door.enemy chains from the master door through all doors linked in the chain.
622 =============================================================================
626 =============================================================================
631 void() door_rotating_go_down;
632 void() door_rotating_go_up;
637 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
638 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
641 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
642 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
644 //Dont chamge direction for dead or dying stuff
645 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
648 if (self.state == STATE_DOWN)
649 if (self.classname == "door")
654 door_rotating_go_up ();
657 if (self.classname == "door")
662 door_rotating_go_down ();
666 //gib dying stuff just to make sure
667 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
668 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
672 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
673 // if a door has a negative wait, it would never come back if blocked,
674 // so let it just squash the object to death real fast
675 /* if (self.wait >= 0)
677 if (self.state == STATE_DOWN)
688 if (self.noise1 != "")
689 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
690 self.state = STATE_TOP;
691 if (self.spawnflags & DOOR_TOGGLE)
692 return; // don't come down automatically
693 if (self.classname == "door")
695 self.think = door_go_down;
698 self.think = door_rotating_go_down;
700 self.nextthink = self.ltime + self.wait;
703 void door_hit_bottom()
705 if (self.noise1 != "")
706 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
707 self.state = STATE_BOTTOM;
712 if (self.noise2 != "")
713 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
716 self.takedamage = DAMAGE_YES;
717 self.health = self.max_health;
720 self.state = STATE_DOWN;
721 SUB_CalcMove (self.pos1, self.speed, door_hit_bottom);
726 if (self.state == STATE_UP)
727 return; // already going up
729 if (self.state == STATE_TOP)
730 { // reset top wait time
731 self.nextthink = self.ltime + self.wait;
735 if (self.noise2 != "")
736 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
737 self.state = STATE_UP;
738 SUB_CalcMove (self.pos2, self.speed, door_hit_top);
741 oldmessage = self.message;
744 self.message = oldmessage;
749 =============================================================================
753 =============================================================================
761 if (self.owner != self)
762 objerror ("door_fire: self.owner != self");
766 if (self.spawnflags & DOOR_TOGGLE)
768 if (self.state == STATE_UP || self.state == STATE_TOP)
773 if (self.classname == "door")
779 door_rotating_go_down ();
782 } while ( (self != starte) && (self != world) );
788 // trigger all paired doors
792 if (self.classname == "door")
797 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
798 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
800 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
801 self.pos2 = '0 0 0' - self.pos2;
803 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
804 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
805 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
807 door_rotating_go_up ();
811 } while ( (self != starte) && (self != world) );
820 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
831 void door_trigger_touch()
833 if (other.health < 1)
834 if not(other.iscreature && other.deadflag == DEAD_NO)
837 if (time < self.attack_finished_single)
839 self.attack_finished_single = time + 1;
848 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
851 self.health = self.health - damage;
852 if (self.health <= 0)
856 self.health = self.max_health;
857 self.takedamage = DAMAGE_NO; // wil be reset upon return
873 if(other.classname != "player")
875 if (self.owner.attack_finished_single > time)
878 self.owner.attack_finished_single = time + 2;
880 if (!(self.owner.dmg) && (self.owner.message != ""))
882 if (other.flags & FL_CLIENT)
883 centerprint (other, self.owner.message);
884 play2(other, "misc/talk.wav");
889 void door_generic_plat_blocked()
892 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
893 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
896 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
897 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
899 //Dont chamge direction for dead or dying stuff
900 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
903 if (self.state == STATE_DOWN)
904 door_rotating_go_up ();
906 door_rotating_go_down ();
909 //gib dying stuff just to make sure
910 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
911 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
915 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
916 // if a door has a negative wait, it would never come back if blocked,
917 // so let it just squash the object to death real fast
918 /* if (self.wait >= 0)
920 if (self.state == STATE_DOWN)
921 door_rotating_go_up ();
923 door_rotating_go_down ();
929 void door_rotating_hit_top()
931 if (self.noise1 != "")
932 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
933 self.state = STATE_TOP;
934 if (self.spawnflags & DOOR_TOGGLE)
935 return; // don't come down automatically
936 self.think = door_rotating_go_down;
937 self.nextthink = self.ltime + self.wait;
940 void door_rotating_hit_bottom()
942 if (self.noise1 != "")
943 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
944 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
946 self.pos2 = '0 0 0' - self.pos2;
949 self.state = STATE_BOTTOM;
952 void door_rotating_go_down()
954 if (self.noise2 != "")
955 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
958 self.takedamage = DAMAGE_YES;
959 self.health = self.max_health;
962 self.state = STATE_DOWN;
963 SUB_CalcAngleMove (self.pos1, self.speed, door_rotating_hit_bottom);
966 void door_rotating_go_up()
968 if (self.state == STATE_UP)
969 return; // already going up
971 if (self.state == STATE_TOP)
972 { // reset top wait time
973 self.nextthink = self.ltime + self.wait;
976 if (self.noise2 != "")
977 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
978 self.state = STATE_UP;
979 SUB_CalcAngleMove (self.pos2, self.speed, door_rotating_hit_top);
982 oldmessage = self.message;
985 self.message = oldmessage;
992 =============================================================================
996 =============================================================================
1000 entity spawn_field(vector fmins, vector fmaxs)
1002 local entity trigger;
1003 local vector t1, t2;
1006 trigger.classname = "doortriggerfield";
1007 trigger.movetype = MOVETYPE_NONE;
1008 trigger.solid = SOLID_TRIGGER;
1009 trigger.owner = self;
1010 trigger.touch = door_trigger_touch;
1014 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1019 float EntitiesTouching(entity e1, entity e2)
1021 if (e1.absmin_x > e2.absmax_x)
1023 if (e1.absmin_y > e2.absmax_y)
1025 if (e1.absmin_z > e2.absmax_z)
1027 if (e1.absmax_x < e2.absmin_x)
1029 if (e1.absmax_y < e2.absmin_y)
1031 if (e1.absmax_z < e2.absmin_z)
1046 local entity t, starte;
1047 local vector cmins, cmaxs;
1050 return; // already linked by another door
1051 if (self.spawnflags & 4)
1053 self.owner = self.enemy = self;
1061 self.trigger_field = spawn_field(self.absmin, self.absmax);
1063 return; // don't want to link this door
1066 cmins = self.absmin;
1067 cmaxs = self.absmax;
1074 self.owner = starte; // master door
1077 starte.health = self.health;
1079 starte.targetname = self.targetname;
1080 if (self.message != "")
1081 starte.message = self.message;
1083 t = find(t, classname, self.classname);
1086 self.enemy = starte; // make the chain a loop
1088 // shootable, or triggered doors just needed the owner/enemy links,
1089 // they don't spawn a field
1100 self.owner.trigger_field = spawn_field(cmins, cmaxs);
1105 if (EntitiesTouching(self,t))
1108 objerror ("cross connected doors");
1113 if (t.absmin_x < cmins_x)
1114 cmins_x = t.absmin_x;
1115 if (t.absmin_y < cmins_y)
1116 cmins_y = t.absmin_y;
1117 if (t.absmin_z < cmins_z)
1118 cmins_z = t.absmin_z;
1119 if (t.absmax_x > cmaxs_x)
1120 cmaxs_x = t.absmax_x;
1121 if (t.absmax_y > cmaxs_y)
1122 cmaxs_y = t.absmax_y;
1123 if (t.absmax_z > cmaxs_z)
1124 cmaxs_z = t.absmax_z;
1131 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK x x TOGGLE
1132 if two doors touch, they are assumed to be connected and operate as a unit.
1134 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1136 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).
1138 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1139 "angle" determines the opening direction
1140 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1141 "health" if set, door must be shot open
1142 "speed" movement speed (100 default)
1143 "wait" wait before returning (3 default, -1 = never return)
1144 "lip" lip remaining at end of move (8 default)
1145 "dmg" damage to inflict when blocked (2 default)
1152 FIXME: only one sound set available at the time being
1156 void door_init_startopen()
1158 setorigin (self, self.pos2);
1159 self.pos2 = self.pos1;
1160 self.pos1 = self.origin;
1163 void spawnfunc_func_door()
1165 //if (!self.deathtype) // map makers can override this
1166 // self.deathtype = " got in the way";
1169 self.max_health = self.health;
1170 if not(InitMovingBrushTrigger())
1172 self.effects |= EF_LOWPRECISION;
1173 self.classname = "door";
1175 self.blocked = door_blocked;
1176 self.use = door_use;
1178 if(self.spawnflags & 8)
1181 if(self.dmg && (!self.message))
1182 self.message = "was squished";
1183 if(self.dmg && (!self.message2))
1184 self.message2 = "was squished by";
1186 if (self.sounds > 0)
1188 precache_sound ("plats/medplat1.wav");
1189 precache_sound ("plats/medplat2.wav");
1190 self.noise2 = "plats/medplat1.wav";
1191 self.noise1 = "plats/medplat2.wav";
1201 self.pos1 = self.origin;
1202 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1204 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1205 // but spawn in the open position
1206 if (self.spawnflags & DOOR_START_OPEN)
1207 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1209 self.state = STATE_BOTTOM;
1213 self.takedamage = DAMAGE_YES;
1214 self.event_damage = door_damage;
1220 self.touch = door_touch;
1222 // LinkDoors can't be done until all of the doors have been spawned, so
1223 // the sizes can be detected properly.
1224 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1227 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1228 if two doors touch, they are assumed to be connected and operate as a unit.
1230 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1232 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1233 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1234 must have set trigger_reverse to 1.
1235 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1237 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).
1239 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1240 "angle" determines the destination angle for opening. negative values reverse the direction.
1241 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1242 "health" if set, door must be shot open
1243 "speed" movement speed (100 default)
1244 "wait" wait before returning (3 default, -1 = never return)
1245 "dmg" damage to inflict when blocked (2 default)
1252 FIXME: only one sound set available at the time being
1255 void door_rotating_init_startopen()
1257 self.angles = self.movedir;
1258 self.pos2 = '0 0 0';
1259 self.pos1 = self.movedir;
1263 void spawnfunc_func_door_rotating()
1266 //if (!self.deathtype) // map makers can override this
1267 // self.deathtype = " got in the way";
1269 // I abuse "movedir" for denoting the axis for now
1270 if (self.spawnflags & 64) // X (untested)
1271 self.movedir = '0 0 1';
1272 else if (self.spawnflags & 128) // Y (untested)
1273 self.movedir = '1 0 0';
1275 self.movedir = '0 1 0';
1277 if (self.angles_y==0) self.angles_y = 90;
1279 self.movedir = self.movedir * self.angles_y;
1280 self.angles = '0 0 0';
1282 self.max_health = self.health;
1283 if not(InitMovingBrushTrigger())
1285 //self.effects |= EF_LOWPRECISION;
1286 self.classname = "door_rotating";
1288 self.blocked = door_blocked;
1289 self.use = door_use;
1291 if(self.spawnflags & 8)
1294 if(self.dmg && (!self.message))
1295 self.message = "was squished";
1296 if(self.dmg && (!self.message2))
1297 self.message2 = "was squished by";
1299 if (self.sounds > 0)
1301 precache_sound ("plats/medplat1.wav");
1302 precache_sound ("plats/medplat2.wav");
1303 self.noise2 = "plats/medplat1.wav";
1304 self.noise1 = "plats/medplat2.wav";
1311 self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1313 self.pos1 = '0 0 0';
1314 self.pos2 = self.movedir;
1316 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1317 // but spawn in the open position
1318 if (self.spawnflags & DOOR_START_OPEN)
1319 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1321 self.state = STATE_BOTTOM;
1325 self.takedamage = DAMAGE_YES;
1326 self.event_damage = door_damage;
1332 self.touch = door_touch;
1334 // LinkDoors can't be done until all of the doors have been spawned, so
1335 // the sizes can be detected properly.
1336 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1340 =============================================================================
1344 =============================================================================
1347 void() fd_secret_move1;
1348 void() fd_secret_move2;
1349 void() fd_secret_move3;
1350 void() fd_secret_move4;
1351 void() fd_secret_move5;
1352 void() fd_secret_move6;
1353 void() fd_secret_done;
1355 float SECRET_OPEN_ONCE = 1; // stays open
1356 float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
1357 float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
1358 float SECRET_NO_SHOOT = 8; // only opened by trigger
1359 float SECRET_YES_SHOOT = 16; // shootable even if targeted
1362 void fd_secret_use()
1365 string message_save;
1367 self.health = 10000;
1368 self.bot_attack = TRUE;
1370 // exit if still moving around...
1371 if (self.origin != self.oldorigin)
1374 message_save = self.message;
1375 self.message = ""; // no more message
1376 SUB_UseTargets(); // fire all targets / killtargets
1377 self.message = message_save;
1379 self.velocity = '0 0 0';
1381 // Make a sound, wait a little...
1383 if (self.noise1 != "")
1384 sound(self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
1385 self.nextthink = self.ltime + 0.1;
1387 temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1388 makevectors(self.mangle);
1392 if (self.spawnflags & SECRET_1ST_DOWN)
1393 self.t_width = fabs(v_up * self.size);
1395 self.t_width = fabs(v_right * self.size);
1399 self.t_length = fabs(v_forward * self.size);
1401 if (self.spawnflags & SECRET_1ST_DOWN)
1402 self.dest1 = self.origin - v_up * self.t_width;
1404 self.dest1 = self.origin + v_right * (self.t_width * temp);
1406 self.dest2 = self.dest1 + v_forward * self.t_length;
1407 SUB_CalcMove(self.dest1, self.speed, fd_secret_move1);
1408 if (self.noise2 != "")
1409 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1412 // Wait after first movement...
1413 void fd_secret_move1()
1415 self.nextthink = self.ltime + 1.0;
1416 self.think = fd_secret_move2;
1417 if (self.noise3 != "")
1418 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1421 // Start moving sideways w/sound...
1422 void fd_secret_move2()
1424 if (self.noise2 != "")
1425 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1426 SUB_CalcMove(self.dest2, self.speed, fd_secret_move3);
1429 // Wait here until time to go back...
1430 void fd_secret_move3()
1432 if (self.noise3 != "")
1433 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1434 if (!(self.spawnflags & SECRET_OPEN_ONCE))
1436 self.nextthink = self.ltime + self.wait;
1437 self.think = fd_secret_move4;
1442 void fd_secret_move4()
1444 if (self.noise2 != "")
1445 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1446 SUB_CalcMove(self.dest1, self.speed, fd_secret_move5);
1450 void fd_secret_move5()
1452 self.nextthink = self.ltime + 1.0;
1453 self.think = fd_secret_move6;
1454 if (self.noise3 != "")
1455 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1458 void fd_secret_move6()
1460 if (self.noise2 != "")
1461 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1462 SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done);
1465 void fd_secret_done()
1467 if (self.spawnflags&SECRET_YES_SHOOT)
1469 self.health = 10000;
1470 self.takedamage = DAMAGE_YES;
1471 //self.th_pain = fd_secret_use;
1473 if (self.noise3 != "")
1474 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1477 void secret_blocked()
1479 if (time < self.attack_finished_single)
1481 self.attack_finished_single = time + 0.5;
1482 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1494 if not(other.iscreature)
1496 if (self.attack_finished_single > time)
1499 self.attack_finished_single = time + 2;
1503 if (other.flags & FL_CLIENT)
1504 centerprint (other, self.message);
1505 play2(other, "misc/talk.wav");
1510 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1511 Basic secret door. Slides back, then to the side. Angle determines direction.
1512 wait = # of seconds before coming back
1513 1st_left = 1st move is left of arrow
1514 1st_down = 1st move is down from arrow
1515 always_shoot = even if targeted, keep shootable
1516 t_width = override WIDTH to move back (or height if going down)
1517 t_length = override LENGTH to move sideways
1518 "dmg" damage to inflict when blocked (2 default)
1520 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1527 void spawnfunc_func_door_secret()
1529 /*if (!self.deathtype) // map makers can override this
1530 self.deathtype = " got in the way";*/
1536 self.mangle = self.angles;
1537 self.angles = '0 0 0';
1538 self.classname = "door";
1539 if not(InitMovingBrushTrigger())
1541 self.effects |= EF_LOWPRECISION;
1543 self.touch = secret_touch;
1544 self.blocked = secret_blocked;
1546 self.use = fd_secret_use;
1551 self.spawnflags |= SECRET_YES_SHOOT;
1553 if(self.spawnflags&SECRET_YES_SHOOT)
1555 self.health = 10000;
1556 self.takedamage = DAMAGE_YES;
1557 self.event_damage = fd_secret_use;
1559 self.oldorigin = self.origin;
1561 self.wait = 5; // 5 seconds before closing
1564 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1565 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1566 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
1567 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1568 height: amplitude modifier (default 32)
1569 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1570 noise: path/name of looping .wav file to play.
1571 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1575 void func_fourier_controller_think()
1580 self.nextthink = time + 0.1;
1582 n = floor((tokenize_sane(self.owner.netname)) / 5);
1583 t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1585 v = self.owner.destvec;
1587 for(i = 0; i < n; ++i)
1589 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1590 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;
1593 // * 10 so it will arrive in 0.1 sec
1594 self.owner.velocity = (v - self.owner.origin) * 10;
1597 void spawnfunc_func_fourier()
1599 local entity controller;
1600 if (self.noise != "")
1602 precache_sound(self.noise);
1603 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
1610 self.destvec = self.origin;
1611 self.cnt = 360 / self.speed;
1613 self.blocked = generic_plat_blocked;
1614 if(self.dmg & (!self.message))
1615 self.message = " was squished";
1616 if(self.dmg && (!self.message2))
1617 self.message2 = "was squished by";
1618 if(self.dmg && (!self.dmgtime))
1619 self.dmgtime = 0.25;
1620 self.dmgtime2 = time;
1622 if(self.netname == "")
1623 self.netname = "1 0 0 0 1";
1625 if not(InitMovingBrushTrigger())
1628 // wait for targets to spawn
1629 controller = spawn();
1630 controller.classname = "func_fourier_controller";
1631 controller.owner = self;
1632 controller.nextthink = time + 1;
1633 controller.think = func_fourier_controller_think;
1634 self.nextthink = self.ltime + 999999999;
1635 self.think = SUB_Null;
1637 // Savage: Reduce bandwith, critical on e.g. nexdm02
1638 self.effects |= EF_LOWPRECISION;
1641 // reusing some fields havocbots declared
1642 .entity wp00, wp01, wp02, wp03;
1644 .float targetfactor, target2factor, target3factor, target4factor;
1646 void func_vectormamamam_controller_think()
1651 self.nextthink = time + 0.1;
1653 v = self.owner.destvec;
1655 e = self.owner.wp00;
1657 v = v + e.origin + 0.1 * e.velocity;
1659 e = self.owner.wp01;
1661 v = v + e.origin + 0.1 * e.velocity;
1663 e = self.owner.wp02;
1665 v = v + e.origin + 0.1 * e.velocity;
1667 e = self.owner.wp03;
1669 v = v + e.origin + 0.1 * e.velocity;
1671 self.owner.velocity = (v - self.owner.origin) * 10;
1674 void func_vectormamamam_findtarget()
1680 if(self.target != "")
1682 self.wp00 = find(world, targetname, self.target);
1684 s0 = s0 + self.wp00.origin * self.targetfactor;
1687 if(self.target2 != "")
1689 self.wp01 = find(world, targetname, self.target2);
1691 s0 = s0 + self.wp01.origin * self.target2factor;
1694 if(self.target3 != "")
1696 self.wp02 = find(world, targetname, self.target3);
1698 s0 = s0 + self.wp02.origin * self.target3factor;
1701 if(self.target4 != "")
1703 self.wp03 = find(world, targetname, self.target4);
1705 s0 = s0 + self.wp03.origin * self.target4factor;
1708 if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
1709 objerror("No reference entity found, so there is nothing to move. Aborting.");
1711 self.destvec = self.origin - s0;
1713 local entity controller;
1714 controller = spawn();
1715 controller.classname = "func_vectormamamam_controller";
1716 controller.owner = self;
1717 controller.nextthink = time + 1;
1718 controller.think = func_vectormamamam_controller_think;
1721 void spawnfunc_func_vectormamamam()
1723 if (self.noise != "")
1725 precache_sound(self.noise);
1726 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
1729 if(!self.targetfactor)
1730 self.targetfactor = 1;
1732 if(!self.target2factor)
1733 self.target2factor = 1;
1735 if(!self.target3factor)
1736 self.target3factor = 1;
1738 if(!self.target4factor)
1739 self.target4factor = 1;
1741 self.blocked = generic_plat_blocked;
1742 if(self.dmg & (!self.message))
1743 self.message = " was squished";
1744 if(self.dmg && (!self.message2))
1745 self.message2 = "was squished by";
1746 if(self.dmg && (!self.dmgtime))
1747 self.dmgtime = 0.25;
1748 self.dmgtime2 = time;
1750 if(self.netname == "")
1751 self.netname = "1 0 0 0 1";
1753 if not(InitMovingBrushTrigger())
1756 // wait for targets to spawn
1757 self.nextthink = self.ltime + 999999999;
1758 self.think = SUB_Null;
1760 // Savage: Reduce bandwith, critical on e.g. nexdm02
1761 self.effects |= EF_LOWPRECISION;
1763 InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);