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 /*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS
331 Brush model that spins in place on one axis (default Z).
332 speed : speed to rotate (in degrees per second)
333 noise : path/name of looping .wav file to play.
334 dmg : Do this mutch dmg every .dmgtime intervall when blocked
338 void spawnfunc_func_rotating()
340 if (self.noise != "")
342 precache_sound(self.noise);
343 ambientsound(self.origin, self.noise, VOL_BASE, ATTN_IDLE);
347 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
348 if (self.spawnflags & 4) // X (untested)
349 self.avelocity = '0 0 1' * self.speed;
350 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
351 else if (self.spawnflags & 8) // Y (untested)
352 self.avelocity = '1 0 0' * self.speed;
353 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
355 self.avelocity = '0 1 0' * self.speed;
357 if(self.dmg & (!self.message))
358 self.message = " was squished";
359 if(self.dmg && (!self.message2))
360 self.message2 = "was squished by";
363 if(self.dmg && (!self.dmgtime))
366 self.dmgtime2 = time;
368 if not(InitMovingBrushTrigger())
370 // no EF_LOWPRECISION here, as rounding angles is bad
372 self.blocked = generic_plat_blocked;
374 // wait for targets to spawn
375 self.nextthink = self.ltime + 999999999;
376 self.think = SUB_Null;
378 // TODO make a reset function for this one
382 void func_bobbing_controller_think()
385 self.nextthink = time + 0.1;
386 // calculate sinewave using makevectors
387 makevectors((self.nextthink * self.owner.cnt + self.owner.phase * 360) * '0 1 0');
388 v = self.owner.destvec + self.owner.movedir * v_forward_y;
389 // * 10 so it will arrive in 0.1 sec
390 self.owner.velocity = (v - self.owner.origin) * 10;
393 void bobbing_blocked()
395 // no need to duplicate code
396 generic_plat_blocked();
399 /*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
400 Brush model that moves back and forth on one axis (default Z).
401 speed : how long one cycle takes in seconds (default 4)
402 height : how far the cycle moves (default 32)
403 phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
404 noise : path/name of looping .wav file to play.
405 dmg : Do this mutch dmg every .dmgtime intervall when blocked
408 void spawnfunc_func_bobbing()
410 local entity controller;
411 if (self.noise != "")
413 precache_sound(self.noise);
414 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
420 // center of bobbing motion
421 self.destvec = self.origin;
422 // time scale to get degrees
423 self.cnt = 360 / self.speed;
425 // damage when blocked
426 self.blocked = bobbing_blocked;
427 if(self.dmg & (!self.message))
428 self.message = " was squished";
429 if(self.dmg && (!self.message2))
430 self.message2 = "was squished by";
431 if(self.dmg && (!self.dmgtime))
433 self.dmgtime2 = time;
436 if (self.spawnflags & 1) // X
437 self.movedir = '1 0 0' * self.height;
438 else if (self.spawnflags & 2) // Y
439 self.movedir = '0 1 0' * self.height;
441 self.movedir = '0 0 1' * self.height;
443 if not(InitMovingBrushTrigger())
446 // wait for targets to spawn
447 controller = spawn();
448 controller.classname = "func_bobbing_controller";
449 controller.owner = self;
450 controller.nextthink = time + 1;
451 controller.think = func_bobbing_controller_think;
452 self.nextthink = self.ltime + 999999999;
453 self.think = SUB_Null;
455 // Savage: Reduce bandwith, critical on e.g. nexdm02
456 self.effects |= EF_LOWPRECISION;
458 // TODO make a reset function for this one
461 // button and multiple button
464 void() button_return;
468 self.state = STATE_TOP;
469 self.nextthink = self.ltime + self.wait;
470 self.think = button_return;
471 activator = self.enemy;
473 self.frame = 1; // use alternate textures
478 self.state = STATE_BOTTOM;
483 self.state = STATE_DOWN;
484 SUB_CalcMove (self.pos1, self.speed, button_done);
485 self.frame = 0; // use normal textures
487 self.takedamage = DAMAGE_YES; // can be shot again
491 void button_blocked()
493 // do nothing, just don't come all the way back out
499 self.health = self.max_health;
500 self.takedamage = DAMAGE_NO; // will be reset upon return
502 if (self.state == STATE_UP || self.state == STATE_TOP)
505 if (self.noise != "")
506 sound (self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
508 self.state = STATE_UP;
509 SUB_CalcMove (self.pos2, self.speed, button_wait);
514 self.health = self.max_health;
515 setorigin(self, self.pos1);
516 self.frame = 0; // use normal textures
517 self.state = STATE_BOTTOM;
519 self.takedamage = DAMAGE_YES; // can be shot again
524 // if (activator.classname != "player")
526 // dprint(activator.classname);
527 // dprint(" triggered a button\n");
529 self.enemy = activator;
535 // if (activator.classname != "player")
537 // dprint(activator.classname);
538 // dprint(" touched a button\n");
542 if not(other.iscreature)
544 if(other.velocity * self.movedir < 0)
548 self.enemy = other.owner;
552 void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
554 self.health = self.health - damage;
555 if (self.health <= 0)
557 // if (activator.classname != "player")
559 // dprint(activator.classname);
560 // dprint(" killed a button\n");
562 self.enemy = damage_attacker;
568 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
569 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.
571 "angle" determines the opening direction
572 "target" all entities with a matching targetname will be used
573 "speed" override the default 40 speed
574 "wait" override the default 1 second wait (-1 = never return)
575 "lip" override the default 4 pixel lip remaining at end of move
576 "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
583 void spawnfunc_func_button()
587 if not(InitMovingBrushTrigger())
589 self.effects |= EF_LOWPRECISION;
591 self.blocked = button_blocked;
592 self.use = button_use;
594 // if (self.health == 0) // all buttons are now shootable
598 self.max_health = self.health;
599 self.event_damage = button_damage;
600 self.takedamage = DAMAGE_YES;
603 self.touch = button_touch;
613 precache_sound(self.noise);
615 self.pos1 = self.origin;
616 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
622 float DOOR_START_OPEN = 1;
623 float DOOR_DONT_LINK = 4;
624 float DOOR_TOGGLE = 32;
628 Doors are similar to buttons, but can spawn a fat trigger field around them
629 to open without a touch, and they link together to form simultanious
632 Door.owner is the master door. If there is only one door, it points to itself.
633 If multiple doors, all will point to a single one.
635 Door.enemy chains from the master door through all doors linked in the chain.
640 =============================================================================
644 =============================================================================
649 void() door_rotating_go_down;
650 void() door_rotating_go_up;
655 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
656 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
659 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
660 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
662 //Dont chamge direction for dead or dying stuff
663 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
666 if (self.state == STATE_DOWN)
667 if (self.classname == "door")
672 door_rotating_go_up ();
675 if (self.classname == "door")
680 door_rotating_go_down ();
684 //gib dying stuff just to make sure
685 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
686 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
690 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
691 // if a door has a negative wait, it would never come back if blocked,
692 // so let it just squash the object to death real fast
693 /* if (self.wait >= 0)
695 if (self.state == STATE_DOWN)
706 if (self.noise1 != "")
707 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
708 self.state = STATE_TOP;
709 if (self.spawnflags & DOOR_TOGGLE)
710 return; // don't come down automatically
711 if (self.classname == "door")
713 self.think = door_go_down;
716 self.think = door_rotating_go_down;
718 self.nextthink = self.ltime + self.wait;
721 void door_hit_bottom()
723 if (self.noise1 != "")
724 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
725 self.state = STATE_BOTTOM;
730 if (self.noise2 != "")
731 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
734 self.takedamage = DAMAGE_YES;
735 self.health = self.max_health;
738 self.state = STATE_DOWN;
739 SUB_CalcMove (self.pos1, self.speed, door_hit_bottom);
744 if (self.state == STATE_UP)
745 return; // already going up
747 if (self.state == STATE_TOP)
748 { // reset top wait time
749 self.nextthink = self.ltime + self.wait;
753 if (self.noise2 != "")
754 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
755 self.state = STATE_UP;
756 SUB_CalcMove (self.pos2, self.speed, door_hit_top);
759 oldmessage = self.message;
762 self.message = oldmessage;
767 =============================================================================
771 =============================================================================
779 if (self.owner != self)
780 objerror ("door_fire: self.owner != self");
784 if (self.spawnflags & DOOR_TOGGLE)
786 if (self.state == STATE_UP || self.state == STATE_TOP)
791 if (self.classname == "door")
797 door_rotating_go_down ();
800 } while ( (self != starte) && (self != world) );
806 // trigger all paired doors
810 if (self.classname == "door")
815 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
816 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
818 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
819 self.pos2 = '0 0 0' - self.pos2;
821 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
822 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
823 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
825 door_rotating_go_up ();
829 } while ( (self != starte) && (self != world) );
838 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
849 void door_trigger_touch()
851 if (other.health < 1)
852 if not(other.iscreature && other.deadflag == DEAD_NO)
855 if (time < self.attack_finished_single)
857 self.attack_finished_single = time + 1;
866 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
869 self.health = self.health - damage;
870 if (self.health <= 0)
874 self.health = self.max_health;
875 self.takedamage = DAMAGE_NO; // wil be reset upon return
891 if(other.classname != "player")
893 if (self.owner.attack_finished_single > time)
896 self.owner.attack_finished_single = time + 2;
898 if (!(self.owner.dmg) && (self.owner.message != ""))
900 if (other.flags & FL_CLIENT)
901 centerprint (other, self.owner.message);
902 play2(other, "misc/talk.wav");
907 void door_generic_plat_blocked()
910 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
911 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
914 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
915 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
917 //Dont chamge direction for dead or dying stuff
918 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
921 if (self.state == STATE_DOWN)
922 door_rotating_go_up ();
924 door_rotating_go_down ();
927 //gib dying stuff just to make sure
928 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
929 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
933 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
934 // if a door has a negative wait, it would never come back if blocked,
935 // so let it just squash the object to death real fast
936 /* if (self.wait >= 0)
938 if (self.state == STATE_DOWN)
939 door_rotating_go_up ();
941 door_rotating_go_down ();
947 void door_rotating_hit_top()
949 if (self.noise1 != "")
950 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
951 self.state = STATE_TOP;
952 if (self.spawnflags & DOOR_TOGGLE)
953 return; // don't come down automatically
954 self.think = door_rotating_go_down;
955 self.nextthink = self.ltime + self.wait;
958 void door_rotating_hit_bottom()
960 if (self.noise1 != "")
961 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
962 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
964 self.pos2 = '0 0 0' - self.pos2;
967 self.state = STATE_BOTTOM;
970 void door_rotating_go_down()
972 if (self.noise2 != "")
973 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
976 self.takedamage = DAMAGE_YES;
977 self.health = self.max_health;
980 self.state = STATE_DOWN;
981 SUB_CalcAngleMove (self.pos1, self.speed, door_rotating_hit_bottom);
984 void door_rotating_go_up()
986 if (self.state == STATE_UP)
987 return; // already going up
989 if (self.state == STATE_TOP)
990 { // reset top wait time
991 self.nextthink = self.ltime + self.wait;
994 if (self.noise2 != "")
995 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
996 self.state = STATE_UP;
997 SUB_CalcAngleMove (self.pos2, self.speed, door_rotating_hit_top);
1000 oldmessage = self.message;
1003 self.message = oldmessage;
1010 =============================================================================
1014 =============================================================================
1018 entity spawn_field(vector fmins, vector fmaxs)
1020 local entity trigger;
1021 local vector t1, t2;
1024 trigger.classname = "doortriggerfield";
1025 trigger.movetype = MOVETYPE_NONE;
1026 trigger.solid = SOLID_TRIGGER;
1027 trigger.owner = self;
1028 trigger.touch = door_trigger_touch;
1032 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1037 float EntitiesTouching(entity e1, entity e2)
1039 if (e1.absmin_x > e2.absmax_x)
1041 if (e1.absmin_y > e2.absmax_y)
1043 if (e1.absmin_z > e2.absmax_z)
1045 if (e1.absmax_x < e2.absmin_x)
1047 if (e1.absmax_y < e2.absmin_y)
1049 if (e1.absmax_z < e2.absmin_z)
1064 local entity t, starte;
1065 local vector cmins, cmaxs;
1068 return; // already linked by another door
1069 if (self.spawnflags & 4)
1071 self.owner = self.enemy = self;
1079 self.trigger_field = spawn_field(self.absmin, self.absmax);
1081 return; // don't want to link this door
1084 cmins = self.absmin;
1085 cmaxs = self.absmax;
1092 self.owner = starte; // master door
1095 starte.health = self.health;
1097 starte.targetname = self.targetname;
1098 if (self.message != "")
1099 starte.message = self.message;
1101 t = find(t, classname, self.classname);
1104 self.enemy = starte; // make the chain a loop
1106 // shootable, or triggered doors just needed the owner/enemy links,
1107 // they don't spawn a field
1118 self.owner.trigger_field = spawn_field(cmins, cmaxs);
1123 if (EntitiesTouching(self,t))
1126 objerror ("cross connected doors");
1131 if (t.absmin_x < cmins_x)
1132 cmins_x = t.absmin_x;
1133 if (t.absmin_y < cmins_y)
1134 cmins_y = t.absmin_y;
1135 if (t.absmin_z < cmins_z)
1136 cmins_z = t.absmin_z;
1137 if (t.absmax_x > cmaxs_x)
1138 cmaxs_x = t.absmax_x;
1139 if (t.absmax_y > cmaxs_y)
1140 cmaxs_y = t.absmax_y;
1141 if (t.absmax_z > cmaxs_z)
1142 cmaxs_z = t.absmax_z;
1149 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK x x TOGGLE
1150 if two doors touch, they are assumed to be connected and operate as a unit.
1152 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1154 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).
1156 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1157 "angle" determines the opening direction
1158 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1159 "health" if set, door must be shot open
1160 "speed" movement speed (100 default)
1161 "wait" wait before returning (3 default, -1 = never return)
1162 "lip" lip remaining at end of move (8 default)
1163 "dmg" damage to inflict when blocked (2 default)
1170 FIXME: only one sound set available at the time being
1174 void door_init_startopen()
1176 setorigin (self, self.pos2);
1177 self.pos2 = self.pos1;
1178 self.pos1 = self.origin;
1183 setorigin(self, self.pos1);
1184 self.state = STATE_BOTTOM;
1185 self.think = SUB_Null;
1188 void spawnfunc_func_door()
1190 //if (!self.deathtype) // map makers can override this
1191 // self.deathtype = " got in the way";
1194 self.max_health = self.health;
1195 if not(InitMovingBrushTrigger())
1197 self.effects |= EF_LOWPRECISION;
1198 self.classname = "door";
1200 self.blocked = door_blocked;
1201 self.use = door_use;
1203 if(self.spawnflags & 8)
1206 if(self.dmg && (!self.message))
1207 self.message = "was squished";
1208 if(self.dmg && (!self.message2))
1209 self.message2 = "was squished by";
1211 if (self.sounds > 0)
1213 precache_sound ("plats/medplat1.wav");
1214 precache_sound ("plats/medplat2.wav");
1215 self.noise2 = "plats/medplat1.wav";
1216 self.noise1 = "plats/medplat2.wav";
1226 self.pos1 = self.origin;
1227 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1229 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1230 // but spawn in the open position
1231 if (self.spawnflags & DOOR_START_OPEN)
1232 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1234 self.state = STATE_BOTTOM;
1238 self.takedamage = DAMAGE_YES;
1239 self.event_damage = door_damage;
1245 self.touch = door_touch;
1247 // LinkDoors can't be done until all of the doors have been spawned, so
1248 // the sizes can be detected properly.
1249 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1251 self.reset = door_reset;
1254 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1255 if two doors touch, they are assumed to be connected and operate as a unit.
1257 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1259 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1260 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1261 must have set trigger_reverse to 1.
1262 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1264 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).
1266 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1267 "angle" determines the destination angle for opening. negative values reverse the direction.
1268 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1269 "health" if set, door must be shot open
1270 "speed" movement speed (100 default)
1271 "wait" wait before returning (3 default, -1 = never return)
1272 "dmg" damage to inflict when blocked (2 default)
1279 FIXME: only one sound set available at the time being
1282 void door_rotating_reset()
1284 self.angles = self.pos1;
1285 self.state = STATE_BOTTOM;
1286 self.think = SUB_Null;
1289 void door_rotating_init_startopen()
1291 self.angles = self.movedir;
1292 self.pos2 = '0 0 0';
1293 self.pos1 = self.movedir;
1297 void spawnfunc_func_door_rotating()
1300 //if (!self.deathtype) // map makers can override this
1301 // self.deathtype = " got in the way";
1303 // I abuse "movedir" for denoting the axis for now
1304 if (self.spawnflags & 64) // X (untested)
1305 self.movedir = '0 0 1';
1306 else if (self.spawnflags & 128) // Y (untested)
1307 self.movedir = '1 0 0';
1309 self.movedir = '0 1 0';
1311 if (self.angles_y==0) self.angles_y = 90;
1313 self.movedir = self.movedir * self.angles_y;
1314 self.angles = '0 0 0';
1316 self.max_health = self.health;
1317 if not(InitMovingBrushTrigger())
1319 //self.effects |= EF_LOWPRECISION;
1320 self.classname = "door_rotating";
1322 self.blocked = door_blocked;
1323 self.use = door_use;
1325 if(self.spawnflags & 8)
1328 if(self.dmg && (!self.message))
1329 self.message = "was squished";
1330 if(self.dmg && (!self.message2))
1331 self.message2 = "was squished by";
1333 if (self.sounds > 0)
1335 precache_sound ("plats/medplat1.wav");
1336 precache_sound ("plats/medplat2.wav");
1337 self.noise2 = "plats/medplat1.wav";
1338 self.noise1 = "plats/medplat2.wav";
1345 self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1347 self.pos1 = '0 0 0';
1348 self.pos2 = self.movedir;
1350 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1351 // but spawn in the open position
1352 if (self.spawnflags & DOOR_START_OPEN)
1353 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1355 self.state = STATE_BOTTOM;
1359 self.takedamage = DAMAGE_YES;
1360 self.event_damage = door_damage;
1366 self.touch = door_touch;
1368 // LinkDoors can't be done until all of the doors have been spawned, so
1369 // the sizes can be detected properly.
1370 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1372 self.reset = door_rotating_reset;
1376 =============================================================================
1380 =============================================================================
1383 void() fd_secret_move1;
1384 void() fd_secret_move2;
1385 void() fd_secret_move3;
1386 void() fd_secret_move4;
1387 void() fd_secret_move5;
1388 void() fd_secret_move6;
1389 void() fd_secret_done;
1391 float SECRET_OPEN_ONCE = 1; // stays open
1392 float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
1393 float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
1394 float SECRET_NO_SHOOT = 8; // only opened by trigger
1395 float SECRET_YES_SHOOT = 16; // shootable even if targeted
1398 void fd_secret_use()
1401 string message_save;
1403 self.health = 10000;
1404 self.bot_attack = TRUE;
1406 // exit if still moving around...
1407 if (self.origin != self.oldorigin)
1410 message_save = self.message;
1411 self.message = ""; // no more message
1412 SUB_UseTargets(); // fire all targets / killtargets
1413 self.message = message_save;
1415 self.velocity = '0 0 0';
1417 // Make a sound, wait a little...
1419 if (self.noise1 != "")
1420 sound(self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
1421 self.nextthink = self.ltime + 0.1;
1423 temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1424 makevectors(self.mangle);
1428 if (self.spawnflags & SECRET_1ST_DOWN)
1429 self.t_width = fabs(v_up * self.size);
1431 self.t_width = fabs(v_right * self.size);
1435 self.t_length = fabs(v_forward * self.size);
1437 if (self.spawnflags & SECRET_1ST_DOWN)
1438 self.dest1 = self.origin - v_up * self.t_width;
1440 self.dest1 = self.origin + v_right * (self.t_width * temp);
1442 self.dest2 = self.dest1 + v_forward * self.t_length;
1443 SUB_CalcMove(self.dest1, self.speed, fd_secret_move1);
1444 if (self.noise2 != "")
1445 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1448 // Wait after first movement...
1449 void fd_secret_move1()
1451 self.nextthink = self.ltime + 1.0;
1452 self.think = fd_secret_move2;
1453 if (self.noise3 != "")
1454 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1457 // Start moving sideways w/sound...
1458 void fd_secret_move2()
1460 if (self.noise2 != "")
1461 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1462 SUB_CalcMove(self.dest2, self.speed, fd_secret_move3);
1465 // Wait here until time to go back...
1466 void fd_secret_move3()
1468 if (self.noise3 != "")
1469 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1470 if (!(self.spawnflags & SECRET_OPEN_ONCE))
1472 self.nextthink = self.ltime + self.wait;
1473 self.think = fd_secret_move4;
1478 void fd_secret_move4()
1480 if (self.noise2 != "")
1481 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1482 SUB_CalcMove(self.dest1, self.speed, fd_secret_move5);
1486 void fd_secret_move5()
1488 self.nextthink = self.ltime + 1.0;
1489 self.think = fd_secret_move6;
1490 if (self.noise3 != "")
1491 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1494 void fd_secret_move6()
1496 if (self.noise2 != "")
1497 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1498 SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done);
1501 void fd_secret_done()
1503 if (self.spawnflags&SECRET_YES_SHOOT)
1505 self.health = 10000;
1506 self.takedamage = DAMAGE_YES;
1507 //self.th_pain = fd_secret_use;
1509 if (self.noise3 != "")
1510 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1513 void secret_blocked()
1515 if (time < self.attack_finished_single)
1517 self.attack_finished_single = time + 0.5;
1518 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1530 if not(other.iscreature)
1532 if (self.attack_finished_single > time)
1535 self.attack_finished_single = time + 2;
1539 if (other.flags & FL_CLIENT)
1540 centerprint (other, self.message);
1541 play2(other, "misc/talk.wav");
1547 if (self.spawnflags&SECRET_YES_SHOOT)
1549 self.health = 10000;
1550 self.takedamage = DAMAGE_YES;
1552 setorigin(self, self.oldorigin);
1553 self.think = SUB_Null;
1556 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1557 Basic secret door. Slides back, then to the side. Angle determines direction.
1558 wait = # of seconds before coming back
1559 1st_left = 1st move is left of arrow
1560 1st_down = 1st move is down from arrow
1561 always_shoot = even if targeted, keep shootable
1562 t_width = override WIDTH to move back (or height if going down)
1563 t_length = override LENGTH to move sideways
1564 "dmg" damage to inflict when blocked (2 default)
1566 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1573 void spawnfunc_func_door_secret()
1575 /*if (!self.deathtype) // map makers can override this
1576 self.deathtype = " got in the way";*/
1582 self.mangle = self.angles;
1583 self.angles = '0 0 0';
1584 self.classname = "door";
1585 if not(InitMovingBrushTrigger())
1587 self.effects |= EF_LOWPRECISION;
1589 self.touch = secret_touch;
1590 self.blocked = secret_blocked;
1592 self.use = fd_secret_use;
1597 self.spawnflags |= SECRET_YES_SHOOT;
1599 if(self.spawnflags&SECRET_YES_SHOOT)
1601 self.health = 10000;
1602 self.takedamage = DAMAGE_YES;
1603 self.event_damage = fd_secret_use;
1605 self.oldorigin = self.origin;
1607 self.wait = 5; // 5 seconds before closing
1609 self.reset = secret_reset;
1613 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1614 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1615 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
1616 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1617 height: amplitude modifier (default 32)
1618 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1619 noise: path/name of looping .wav file to play.
1620 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1624 void func_fourier_controller_think()
1629 self.nextthink = time + 0.1;
1631 n = floor((tokenize_sane(self.owner.netname)) / 5);
1632 t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1634 v = self.owner.destvec;
1636 for(i = 0; i < n; ++i)
1638 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1639 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;
1642 // * 10 so it will arrive in 0.1 sec
1643 self.owner.velocity = (v - self.owner.origin) * 10;
1646 void spawnfunc_func_fourier()
1648 local entity controller;
1649 if (self.noise != "")
1651 precache_sound(self.noise);
1652 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
1659 self.destvec = self.origin;
1660 self.cnt = 360 / self.speed;
1662 self.blocked = generic_plat_blocked;
1663 if(self.dmg & (!self.message))
1664 self.message = " was squished";
1665 if(self.dmg && (!self.message2))
1666 self.message2 = "was squished by";
1667 if(self.dmg && (!self.dmgtime))
1668 self.dmgtime = 0.25;
1669 self.dmgtime2 = time;
1671 if(self.netname == "")
1672 self.netname = "1 0 0 0 1";
1674 if not(InitMovingBrushTrigger())
1677 // wait for targets to spawn
1678 controller = spawn();
1679 controller.classname = "func_fourier_controller";
1680 controller.owner = self;
1681 controller.nextthink = time + 1;
1682 controller.think = func_fourier_controller_think;
1683 self.nextthink = self.ltime + 999999999;
1684 self.think = SUB_Null;
1686 // Savage: Reduce bandwith, critical on e.g. nexdm02
1687 self.effects |= EF_LOWPRECISION;
1689 // TODO make a reset function for this one
1692 // reusing some fields havocbots declared
1693 .entity wp00, wp01, wp02, wp03;
1695 .float targetfactor, target2factor, target3factor, target4factor;
1696 .vector targetnormal, target2normal, target3normal, target4normal;
1698 vector func_vectormamamam_origin(entity o, float t)
1710 p = e.origin + t * e.velocity;
1712 v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
1714 v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
1720 p = e.origin + t * e.velocity;
1722 v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
1724 v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
1730 p = e.origin + t * e.velocity;
1732 v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
1734 v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
1740 p = e.origin + t * e.velocity;
1742 v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
1744 v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
1750 void func_vectormamamam_controller_think()
1752 self.nextthink = time + 0.1;
1753 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
1756 void func_vectormamamam_findtarget()
1758 if(self.target != "")
1759 self.wp00 = find(world, targetname, self.target);
1761 if(self.target2 != "")
1762 self.wp01 = find(world, targetname, self.target2);
1764 if(self.target3 != "")
1765 self.wp02 = find(world, targetname, self.target3);
1767 if(self.target4 != "")
1768 self.wp03 = find(world, targetname, self.target4);
1770 if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
1771 objerror("No reference entity found, so there is nothing to move. Aborting.");
1773 self.destvec = self.origin - func_vectormamamam_origin(self.owner, 0);
1775 local entity controller;
1776 controller = spawn();
1777 controller.classname = "func_vectormamamam_controller";
1778 controller.owner = self;
1779 controller.nextthink = time + 1;
1780 controller.think = func_vectormamamam_controller_think;
1783 void spawnfunc_func_vectormamamam()
1785 if (self.noise != "")
1787 precache_sound(self.noise);
1788 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
1791 if(!self.targetfactor)
1792 self.targetfactor = 1;
1794 if(!self.target2factor)
1795 self.target2factor = 1;
1797 if(!self.target3factor)
1798 self.target3factor = 1;
1800 if(!self.target4factor)
1801 self.target4factor = 1;
1803 if(vlen(self.targetnormal))
1804 self.targetnormal = normalize(self.targetnormal);
1806 if(vlen(self.target2normal))
1807 self.target2normal = normalize(self.target2normal);
1809 if(vlen(self.target3normal))
1810 self.target3normal = normalize(self.target3normal);
1812 if(vlen(self.target4normal))
1813 self.target4normal = normalize(self.target4normal);
1815 self.blocked = generic_plat_blocked;
1816 if(self.dmg & (!self.message))
1817 self.message = " was squished";
1818 if(self.dmg && (!self.message2))
1819 self.message2 = "was squished by";
1820 if(self.dmg && (!self.dmgtime))
1821 self.dmgtime = 0.25;
1822 self.dmgtime2 = time;
1824 if(self.netname == "")
1825 self.netname = "1 0 0 0 1";
1827 if not(InitMovingBrushTrigger())
1830 // wait for targets to spawn
1831 self.nextthink = self.ltime + 999999999;
1832 self.think = SUB_Null;
1834 // Savage: Reduce bandwith, critical on e.g. nexdm02
1835 self.effects |= EF_LOWPRECISION;
1837 InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);